feat(core): prepare fuel gauge / battery model for different battery types
What changed, and why it matters
This commit refactors the Trezor firmware's battery fuel-gauge code so it can support multiple battery types in the future. It replaces hard-coded battery model constants with a runtime-selected structure based on a battery_type value read from device OTP memory. There is no obvious security bug in the diff, but the change introduces a new hardware-derived input (battery_type) into power-management calculations. Currently only battery type 0 is handled, and any unknown type falls back to the same default model.
No immediate security action is required. As a defensive measure, when additional battery models are added, validate battery_type_is_valid in battery_model_init() and ensure unknown/invalid battery types trigger a safe fallback or error state rather than silently using model 0. Review that future battery model data files cannot be selected based on attacker-influenced input.
Security signals we found
New hardware-derived input (battery_type from OTP) now influences power-management state
Refactor from compile-time constants to runtime model selection
Added early-return guard for uninitialized driver in pm_compensate_fuel_gauge
No explicit validation of battery_type_is_valid before model selection
Default fallback to battery type 0 for any unrecognized value
Evidence from the diff
The change makes the fuel-gauge battery model data-driven: a new battery_model_t struct is introduced and populated by battery_model_init() from unit_properties.battery_type. All battery_ helpers now take a const battery_model_t instead of referencing global BATTERY_* constants. unit_properties_t gains battery_type and battery_type_is_valid fields, populated from OTP data[4]. The fuel_gauge_state_t now embeds battery_model_t, and callers in power_manager.c and power_monitoring.c pass &drv->fuel_gauge.model. A guard was added in pm_compensate_fuel_gauge() to return early if the driver is not initialized. No buffer overflows, integer overflows, or missing bounds checks are visible in the diff. The only potential concern is that battery_model_init() does not validate battery_type_is_valid and unconditionally defaults to model 0 for any unrecognized type, which is acceptable today because only one model exists.
Changed components
core/embed/sys/power_manager/fuel_gauge/battery_model.ccore/embed/sys/power_manager/fuel_gauge/battery_model.hcore/embed/sys/power_manager/fuel_gauge/fuel_gauge.ccore/embed/sys/power_manager/fuel_gauge/fuel_gauge.hcore/embed/sys/power_manager/stm32u5/power_manager.ccore/embed/sys/power_manager/stm32u5/power_monitoring.ccore/embed/util/unit_properties/inc/util/unit_properties.hcore/embed/util/unit_properties/stm32/unit_properties.cInspect captured patch +195 / −106
diff --git a/core/embed/sys/power_manager/fuel_gauge/battery_model.c b/core/embed/sys/power_manager/fuel_gauge/battery_model.c
index 6882207f6..65a1fd22f 100644
--- a/core/embed/sys/power_manager/fuel_gauge/battery_model.c
+++ b/core/embed/sys/power_manager/fuel_gauge/battery_model.c
@@ -18,9 +18,12 @@
*/
#ifdef KERNEL_MODE
-#include "battery_model.h"
#include <math.h>
+#include <util/unit_properties.h>
+
+#include "battery_model.h"
+
// Helper function for linear interpolation
static float linear_interpolate(float x, float x1, float y1, float x2,
float y2) {
@@ -32,15 +35,16 @@ static float linear_interpolate(float x, float x1, float y1, float x2,
}
// Calculate OCV for specific parameters and SOC
-static float calc_ocv(const float* params, float soc) {
- if (soc < BATTERY_SOC_BREAKPOINT_1) {
+static float calc_ocv(const battery_model_t* model, const float* params,
+ float soc) {
+ if (soc < model->soc_breakpoint_1) {
// First segment (rational function): (a1 + b1*x)/(c1 + d1*x)
float a1 = params[2];
float b1 = params[3];
float c1 = params[4];
float d1 = params[5];
return (a1 + b1 * soc) / (c1 + d1 * soc);
- } else if (soc <= BATTERY_SOC_BREAKPOINT_2) {
+ } else if (soc <= model->soc_breakpoint_2) {
// Middle segment (linear function): m*x + b
float m = params[0];
float b = params[1];
@@ -56,8 +60,9 @@ static float calc_ocv(const float* params, float soc) {
}
// Calculate OCV slope for specific parameters and SOC
-static float calc_ocv_slope(const float* params, float soc) {
- if (soc < BATTERY_SOC_BREAKPOINT_1) {
+static float calc_ocv_slope(const battery_model_t* model, const float* params,
+ float soc) {
+ if (soc < model->soc_breakpoint_1) {
// First segment (rational function derivative)
float a1 = params[2];
float b1 = params[3];
@@ -65,7 +70,7 @@ static float calc_ocv_slope(const float* params, float soc) {
float d1 = params[5];
float denominator = c1 + d1 * soc;
return (b1 * c1 - a1 * d1) / (denominator * denominator);
- } else if (soc <= BATTERY_SOC_BREAKPOINT_2) {
+ } else if (soc <= model->soc_breakpoint_2) {
// Middle segment (linear function derivative)
float m = params[0];
return m;
@@ -81,10 +86,11 @@ static float calc_ocv_slope(const float* params, float soc) {
}
// Calculate SOC from OCV for specific parameters
-static float calc_soc_from_ocv(const float* params, float ocv) {
+static float calc_soc_from_ocv(const battery_model_t* model,
+ const float* params, float ocv) {
// Calculate breakpoint voltages
- float ocv_breakpoint_1 = calc_ocv(params, BATTERY_SOC_BREAKPOINT_1);
- float ocv_breakpoint_2 = calc_ocv(params, BATTERY_SOC_BREAKPOINT_2);
+ float ocv_breakpoint_1 = calc_ocv(model, params, model->soc_breakpoint_1);
+ float ocv_breakpoint_2 = calc_ocv(model, params, model->soc_breakpoint_2);
// Extract parameters
float m = params[0];
@@ -110,90 +116,92 @@ static float calc_soc_from_ocv(const float* params, float ocv) {
}
}
-float battery_rint(float temperature) {
+float battery_rint(const battery_model_t* model, float temperature) {
// Calculate R_int using rational function: (a + b*t)/(c + d*t)
- float a = BATTERY_R_INT_PARAMS[0];
- float b = BATTERY_R_INT_PARAMS[1];
- float c = BATTERY_R_INT_PARAMS[2];
- float d = BATTERY_R_INT_PARAMS[3];
+ float a = model->r_int_params[0];
+ float b = model->r_int_params[1];
+ float c = model->r_int_params[2];
+ float d = model->r_int_params[3];
return (a + b * temperature) / (c + d * temperature);
}
-float battery_total_capacity(float temperature, bool discharging_mode) {
+float battery_total_capacity(const battery_model_t* model, float temperature,
+ bool discharging_mode) {
// Select appropriate temperature array based on mode
- const float* temp_points =
- discharging_mode ? BATTERY_TEMP_POINTS_DISCHG : BATTERY_TEMP_POINTS_CHG;
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
// Handle out-of-bounds temperatures
if (temperature <= temp_points[0]) {
- return BATTERY_CAPACITY[0][discharging_mode ? 0 : 1];
+ return model->capacity[0][discharging_mode ? 0 : 1];
}
- if (temperature >= temp_points[BATTERY_NUM_TEMP_POINTS - 1]) {
- return BATTERY_CAPACITY[BATTERY_NUM_TEMP_POINTS - 1]
- [discharging_mode ? 0 : 1];
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
+ return model
+ ->capacity[model->num_temp_points - 1][discharging_mode ? 0 : 1];
}
// Find temperature bracket
- for (int i = 0; i < BATTERY_NUM_TEMP_POINTS - 1; i++) {
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
if (temperature < temp_points[i + 1]) {
return linear_interpolate(
temperature, temp_points[i],
- BATTERY_CAPACITY[i][discharging_mode ? 0 : 1], temp_points[i + 1],
- BATTERY_CAPACITY[i + 1][discharging_mode ? 0 : 1]);
+ model->capacity[i][discharging_mode ? 0 : 1], temp_points[i + 1],
+ model->capacity[i + 1][discharging_mode ? 0 : 1]);
}
}
// Should never reach here
- return BATTERY_CAPACITY[0][discharging_mode ? 0 : 1];
+ return model->capacity[0][discharging_mode ? 0 : 1];
}
-float battery_meas_to_ocv(float voltage_V, float current_mA,
- float temperature) {
+float battery_meas_to_ocv(const battery_model_t* model, float voltage_V,
+ float current_mA, float temperature) {
// Convert mA to A by dividing by 1000
float current_A = current_mA / 1000.0f;
// Calculate OCV: V_OC = V_term + I * R_int
- return voltage_V + (current_A * battery_rint(temperature));
+ return voltage_V + (current_A * battery_rint(model, temperature));
}
-float battery_ocv(float soc, float temperature, bool discharging_mode) {
+float battery_ocv(const battery_model_t* model, float soc, float temperature,
+ bool discharging_mode) {
// Clamp SOC to valid range
soc = (soc < 0.0f) ? 0.0f : ((soc > 1.0f) ? 1.0f : soc);
// Select appropriate temperature array based on mode
- const float* temp_points =
- discharging_mode ? BATTERY_TEMP_POINTS_DISCHG : BATTERY_TEMP_POINTS_CHG;
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
// Handle out-of-bounds temperatures
if (temperature <= temp_points[0]) {
- const float* params = discharging_mode ? BATTERY_OCV_DISCHARGE_PARAMS[0]
- : BATTERY_OCV_CHARGE_PARAMS[0];
- return calc_ocv(params, soc);
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv(model, params, soc);
}
- if (temperature >= temp_points[BATTERY_NUM_TEMP_POINTS - 1]) {
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
const float* params =
discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[BATTERY_NUM_TEMP_POINTS - 1]
- : BATTERY_OCV_CHARGE_PARAMS[BATTERY_NUM_TEMP_POINTS - 1];
- return calc_ocv(params, soc);
+ ? model->ocv_discharge_params[model->num_temp_points - 1]
+ : model->ocv_charge_params[model->num_temp_points - 1];
+ return calc_ocv(model, params, soc);
}
// Find temperature bracket and interpolate
- for (int i = 0; i < BATTERY_NUM_TEMP_POINTS - 1; i++) {
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
if (temperature < temp_points[i + 1]) {
const float* params_low = discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[i]
- : BATTERY_OCV_CHARGE_PARAMS[i];
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
const float* params_high = discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[i + 1]
- : BATTERY_OCV_CHARGE_PARAMS[i + 1];
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
- float ocv_low = calc_ocv(params_low, soc);
- float ocv_high = calc_ocv(params_high, soc);
+ float ocv_low = calc_ocv(model, params_low, soc);
+ float ocv_high = calc_ocv(model, params_high, soc);
return linear_interpolate(temperature, temp_points[i], ocv_low,
temp_points[i + 1], ocv_high);
@@ -201,47 +209,48 @@ float battery_ocv(float soc, float temperature, bool discharging_mode) {
}
// Should never reach here
- const float* params = discharging_mode ? BATTERY_OCV_DISCHARGE_PARAMS[0]
- : BATTERY_OCV_CHARGE_PARAMS[0];
- return calc_ocv(params, soc);
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv(model, params, soc);
}
-float battery_ocv_slope(float soc, float temperature, bool discharging_mode) {
+float battery_ocv_slope(const battery_model_t* model, float soc,
+ float temperature, bool discharging_mode) {
// Clamp SOC to valid range
soc = (soc < 0.0f) ? 0.0f : ((soc > 1.0f) ? 1.0f : soc);
// Select appropriate temperature array based on mode
- const float* temp_points =
- discharging_mode ? BATTERY_TEMP_POINTS_DISCHG : BATTERY_TEMP_POINTS_CHG;
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
// Handle out-of-bounds temperatures
if (temperature <= temp_points[0]) {
- const float* params = discharging_mode ? BATTERY_OCV_DISCHARGE_PARAMS[0]
- : BATTERY_OCV_CHARGE_PARAMS[0];
- return calc_ocv_slope(params, soc);
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv_slope(model, params, soc);
}
- if (temperature >= temp_points[BATTERY_NUM_TEMP_POINTS - 1]) {
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
const float* params =
discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[BATTERY_NUM_TEMP_POINTS - 1]
- : BATTERY_OCV_CHARGE_PARAMS[BATTERY_NUM_TEMP_POINTS - 1];
- return calc_ocv_slope(params, soc);
+ ? model->ocv_discharge_params[model->num_temp_points - 1]
+ : model->ocv_charge_params[model->num_temp_points - 1];
+ return calc_ocv_slope(model, params, soc);
}
// Find temperature bracket and interpolate
- for (int i = 0; i < BATTERY_NUM_TEMP_POINTS - 1; i++) {
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
if (temperature < temp_points[i + 1]) {
const float* params_low = discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[i]
- : BATTERY_OCV_CHARGE_PARAMS[i];
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
const float* params_high = discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[i + 1]
- : BATTERY_OCV_CHARGE_PARAMS[i + 1];
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
- float slope_low = calc_ocv_slope(params_low, soc);
- float slope_high = calc_ocv_slope(params_high, soc);
+ float slope_low = calc_ocv_slope(model, params_low, soc);
+ float slope_high = calc_ocv_slope(model, params_high, soc);
return linear_interpolate(temperature, temp_points[i], slope_low,
temp_points[i + 1], slope_high);
@@ -249,44 +258,45 @@ float battery_ocv_slope(float soc, float temperature, bool discharging_mode) {
}
// Should never reach here
- const float* params = discharging_mode ? BATTERY_OCV_DISCHARGE_PARAMS[0]
- : BATTERY_OCV_CHARGE_PARAMS[0];
- return calc_ocv_slope(params, soc);
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_ocv_slope(model, params, soc);
}
-float battery_soc(float ocv, float temperature, bool discharging_mode) {
+float battery_soc(const battery_model_t* model, float ocv, float temperature,
+ bool discharging_mode) {
// Select appropriate temperature array based on mode
- const float* temp_points =
- discharging_mode ? BATTERY_TEMP_POINTS_DISCHG : BATTERY_TEMP_POINTS_CHG;
+ const float* temp_points = discharging_mode ? model->temp_points_discharge
+ : model->temp_points_charge;
// Handle out-of-bounds temperatures
if (temperature <= temp_points[0]) {
- const float* params = discharging_mode ? BATTERY_OCV_DISCHARGE_PARAMS[0]
- : BATTERY_OCV_CHARGE_PARAMS[0];
- return calc_soc_from_ocv(params, ocv);
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_soc_from_ocv(model, params, ocv);
}
- if (temperature >= temp_points[BATTERY_NUM_TEMP_POINTS - 1]) {
+ if (temperature >= temp_points[model->num_temp_points - 1]) {
const float* params =
discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[BATTERY_NUM_TEMP_POINTS - 1]
- : BATTERY_OCV_CHARGE_PARAMS[BATTERY_NUM_TEMP_POINTS - 1];
- return calc_soc_from_ocv(params, ocv);
+ ? model->ocv_discharge_params[model->num_temp_points - 1]
+ : model->ocv_charge_params[model->num_temp_points - 1];
+ return calc_soc_from_ocv(model, params, ocv);
}
// Find temperature bracket and interpolate
- for (int i = 0; i < BATTERY_NUM_TEMP_POINTS - 1; i++) {
+ for (int i = 0; i < model->num_temp_points - 1; i++) {
if (temperature < temp_points[i + 1]) {
const float* params_low = discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[i]
- : BATTERY_OCV_CHARGE_PARAMS[i];
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
const float* params_high = discharging_mode
- ? BATTERY_OCV_DISCHARGE_PARAMS[i + 1]
- : BATTERY_OCV_CHARGE_PARAMS[i + 1];
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
- float soc_low = calc_soc_from_ocv(params_low, ocv);
- float soc_high = calc_soc_from_ocv(params_high, ocv);
+ float soc_low = calc_soc_from_ocv(model, params_low, ocv);
+ float soc_high = calc_soc_from_ocv(model, params_high, ocv);
return linear_interpolate(temperature, temp_points[i], soc_low,
temp_points[i + 1], soc_high);
@@ -294,9 +304,31 @@ float battery_soc(float ocv, float temperature, bool discharging_mode) {
}
// Should never reach here
- const float* params = discharging_mode ? BATTERY_OCV_DISCHARGE_PARAMS[0]
- : BATTERY_OCV_CHARGE_PARAMS[0];
- return calc_soc_from_ocv(params, ocv);
+ const float* params = discharging_mode ? model->ocv_discharge_params[0]
+ : model->ocv_charge_params[0];
+ return calc_soc_from_ocv(model, params, ocv);
+}
+
+void battery_model_init(battery_model_t* model) {
+ unit_properties_t props = {0};
+ unit_properties_get(&props);
+
+ // todo: this is model specific, should probably be handled somewhere outside
+ // of this module but since we currently only have one model we can live with
+ // this for a while
+ switch (props.battery_type) {
+ case 0:
+ default:
+ model->soc_breakpoint_1 = BATTERY_JYHPFL333838_SOC_BREAKPOINT_1;
+ model->soc_breakpoint_2 = BATTERY_JYHPFL333838_SOC_BREAKPOINT_2;
+ model->num_temp_points = BATTERY_JYHPFL333838_NUM_TEMP_POINTS;
+ model->temp_points_charge = BATTERY_JYHPFL333838_TEMP_POINTS_CHG;
+ model->temp_points_discharge = BATTERY_JYHPFL333838_TEMP_POINTS_DISCHG;
+ model->r_int_params = BATTERY_JYHPFL333838_R_INT_PARAMS;
+ model->ocv_charge_params = BATTERY_JYHPFL333838_OCV_CHARGE_PARAMS;
+ model->ocv_discharge_params = BATTERY_JYHPFL333838_OCV_DISCHARGE_PARAMS;
+ model->capacity = BATTERY_JYHPFL333838_CAPACITY;
+ }
}
#endif
diff --git a/core/embed/sys/power_manager/fuel_gauge/battery_model.h b/core/embed/sys/power_manager/fuel_gauge/battery_model.h
index 572e090cd..4a21ef289 100644
--- a/core/embed/sys/power_manager/fuel_gauge/battery_model.h
+++ b/core/embed/sys/power_manager/fuel_gauge/battery_model.h
@@ -25,12 +25,24 @@
// based on which battery is being used
#include "battery_data_jyhpfl333838.h"
+typedef struct {
+ uint8_t num_temp_points;
+ float soc_breakpoint_1;
+ float soc_breakpoint_2;
+ const float* temp_points_discharge;
+ const float* temp_points_charge;
+ const float* r_int_params;
+ const float (*ocv_discharge_params)[10];
+ const float (*ocv_charge_params)[10];
+ const float (*capacity)[2];
+} battery_model_t;
+
/**
* Calculate internal resistance at the given temperature
* @param temperature Battery temperature in Celsius
* @return Internal resistance in ohms
*/
-float battery_rint(float temperature);
+float battery_rint(const battery_model_t* model, float temperature);
/**
* Get battery total capacity at the given temperature and discharge mode
@@ -38,7 +50,8 @@ float battery_rint(float temperature);
* @param discharging_mode true if discharging, false if charging
* @return Total capacity in mAh
*/
-float battery_total_capacity(float temperature, bool discharging_mode);
+float battery_total_capacity(const battery_model_t* model, float temperature,
+ bool discharging_mode);
/**
* Calculate OCV from measured voltage and current
@@ -47,7 +60,8 @@ float battery_total_capacity(float temperature, bool discharging_mode);
* @param temperature Battery temperature in Celsius
* @return Open circuit voltage (OCV) in volts
*/
-float battery_meas_to_ocv(float voltage_V, float current_mA, float temperature);
+float battery_meas_to_ocv(const battery_model_t* model, float voltage_V,
+ float current_mA, float temperature);
/**
* Get OCV for given SOC and temperature
@@ -56,7 +70,8 @@ float battery_meas_to_ocv(float voltage_V, float current_mA, float temperature);
* @param discharging_mode true if discharging, false if charging
* @return Open circuit voltage in volts
*/
-float battery_ocv(float soc, float temperature, bool discharging_mode);
+float battery_ocv(const battery_model_t* model, float soc, float temperature,
+ bool discharging_mode);
/**
* Get the slope of the OCV curve at a given SOC and temperature
@@ -65,7 +80,8 @@ float battery_ocv(float soc, float temperature, bool discharging_mode);
* @param discharging_mode true if discharging, false if charging
* @return Slope of OCV curve (dOCV/dSOC) in volts
*/
-float battery_ocv_slope(float soc, float temperature, bool discharging_mode);
+float battery_ocv_slope(const battery_model_t* model, float soc,
+ float temperature, bool discharging_mode);
/**
* Get SOC for given OCV and temperature
@@ -74,4 +90,12 @@ float battery_ocv_slope(float soc, float temperature, bool discharging_mode);
* @param discharging_mode true if discharging, false if charging
* @return State of charge (0.0 to 1.0)
*/
-float battery_soc(float ocv, float temperature, bool discharging_mode);
+float battery_soc(const battery_model_t* model, float ocv, float temperature,
+ bool discharging_mode);
+
+/**
+ * @brief Initializes the battery model structure based on used battery type
+ *
+ * @param model Pointer to the battery model structure to be initialized
+ */
+void battery_model_init(battery_model_t* model);
diff --git a/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.c b/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.c
index 4e3df855f..3f07ff88e 100644
--- a/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.c
+++ b/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.c
@@ -17,9 +17,11 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef KERNEL_MODE
-#include "fuel_gauge.h"
+
#include <math.h>
+
#include "battery_model.h"
+#include "fuel_gauge.h"
void fuel_gauge_init(fuel_gauge_state_t* state, float R, float Q,
float R_aggressive, float Q_aggressive, float P_init) {
@@ -32,6 +34,8 @@ void fuel_gauge_init(fuel_gauge_state_t* state, float R, float Q,
state->soc = 0.0f;
state->soc_latched = 0.0f;
state->P = P_init; // Initial error covariance
+
+ battery_model_init(&state->model);
}
void fuel_gauge_reset(fuel_gauge_state_t* state) {
@@ -55,10 +59,11 @@ void fuel_gauge_initial_guess(fuel_gauge_state_t* state, float voltage_V,
bool discharging_mode = current_mA >= 0.0f;
// Calculate OCV from terminal voltage and current
- float ocv = battery_meas_to_ocv(voltage_V, current_mA, temperature);
+ float ocv =
+ battery_meas_to_ocv(&state->model, voltage_V, current_mA, temperature);
// Extract SoC from battery model
- state->soc = battery_soc(ocv, temperature, discharging_mode);
+ state->soc = battery_soc(&state->model, ocv, temperature, discharging_mode);
state->soc = fmaxf(0.0f, fminf(state->soc, 1.0f)); // Clamp SOC to [0, 1]
state->soc_latched = state->soc;
}
@@ -97,7 +102,8 @@ float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
float dt_sec = dt_ms / 1000.0f;
// Get total capacity at current temperature
- float total_capacity = battery_total_capacity(temperature, discharging_mode);
+ float total_capacity =
+ battery_total_capacity(&state->model, temperature, discharging_mode);
// State prediction (coulomb counting)
// SOC_k+1 = SOC_k - (I*dt)/(3600*capacity)
@@ -105,7 +111,8 @@ float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
state->soc - (current_mA / (3600.0f * total_capacity)) * dt_sec;
// Calculate Jacobian of measurement function h(x) = dOCV/dSOC
- float h_jacobian = battery_ocv_slope(x_k1_k, temperature, discharging_mode);
+ float h_jacobian =
+ battery_ocv_slope(&state->model, x_k1_k, temperature, discharging_mode);
// Error covariance prediction
float P_k1_k = state->P + Q;
@@ -117,8 +124,9 @@ float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
float K_k1_k = P_k1_k * h_jacobian / S;
// Calculate predicted terminal voltage
- float v_pred = battery_ocv(x_k1_k, temperature, discharging_mode) -
- (current_mA / 1000.0f) * battery_rint(temperature);
+ float v_pred =
+ battery_ocv(&state->model, x_k1_k, temperature, discharging_mode) -
+ (current_mA / 1000.0f) * battery_rint(&state->model, temperature);
// State update
float x_k1_k1 = x_k1_k + K_k1_k * (voltage_V - v_pred);
diff --git a/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.h b/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.h
index 5dc48b0ee..88d8e872c 100644
--- a/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.h
+++ b/core/embed/sys/power_manager/fuel_gauge/fuel_gauge.h
@@ -21,8 +21,12 @@
#include <trezor_types.h>
+#include "battery_model.h"
+
// fuel gauge state structure
typedef struct {
+ battery_model_t model;
+
// State estimate (SOC)
float soc;
// Latched SOC (the one that gets reported)
diff --git a/core/embed/sys/power_manager/stm32u5/power_manager.c b/core/embed/sys/power_manager/stm32u5/power_manager.c
index 661727caf..39df59e0c 100644
--- a/core/embed/sys/power_manager/stm32u5/power_manager.c
+++ b/core/embed/sys/power_manager/stm32u5/power_manager.c
@@ -697,10 +697,17 @@ bool pm_driver_is_suspended(void) {
void pm_compensate_fuel_gauge(float* soc, uint32_t elapsed_s,
float battery_current_ma, float bat_temp_c) {
+ pm_driver_t* drv = &g_pm;
+
+ if (!drv->initialized) {
+ return;
+ }
+
float compensation_mah = ((battery_current_ma)*elapsed_s) / 3600.0f;
bool discharging_mode = battery_current_ma >= 0.0f;
*soc -=
- (compensation_mah / battery_total_capacity(bat_temp_c, discharging_mode));
+ (compensation_mah / battery_total_capacity(&drv->fuel_gauge.model,
+ bat_temp_c, discharging_mode));
}
static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms) {
diff --git a/core/embed/sys/power_manager/stm32u5/power_monitoring.c b/core/embed/sys/power_manager/stm32u5/power_monitoring.c
index e4cca84b2..24ab5dee3 100644
--- a/core/embed/sys/power_manager/stm32u5/power_monitoring.c
+++ b/core/embed/sys/power_manager/stm32u5/power_monitoring.c
@@ -186,10 +186,12 @@ void pm_charging_controller(pm_driver_t* drv) {
20.0f) {
// Translate SoC target to charging voltage via battery model
float target_ocv_voltage_v =
- battery_ocv(drv->soc_target / 100.0f, drv->pmic_data.ntc_temp, false);
+ battery_ocv(&drv->fuel_gauge.model, drv->soc_target / 100.0f,
+ drv->pmic_data.ntc_temp, false);
- float battery_ocv_v = battery_meas_to_ocv(
- drv->pmic_data.vbat, drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
+ float battery_ocv_v =
+ battery_meas_to_ocv(&drv->fuel_gauge.model, drv->pmic_data.vbat,
+ drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
drv->target_battery_ocv_v_tau =
(drv->target_battery_ocv_v_tau * 0.95f) +
diff --git a/core/embed/util/unit_properties/inc/util/unit_properties.h b/core/embed/util/unit_properties/inc/util/unit_properties.h
index 853aeae64..55d7944c3 100644
--- a/core/embed/util/unit_properties/inc/util/unit_properties.h
+++ b/core/embed/util/unit_properties/inc/util/unit_properties.h
@@ -83,6 +83,16 @@ typedef struct {
*/
bool sd_hotswap_enabled;
+ /**
+ * Type of the battery used in this unit
+ *
+ * Interpretation is model-specific.
+ */
+ uint8_t battery_type;
+ /** Validity flag for the battery_type field - set to true when battery_type
+ * contains a valid value */
+ bool battery_type_is_valid;
+
} unit_properties_t;
/**
diff --git a/core/embed/util/unit_properties/stm32/unit_properties.c b/core/embed/util/unit_properties/stm32/unit_properties.c
index c98546ab7..e58ec29fc 100644
--- a/core/embed/util/unit_properties/stm32/unit_properties.c
+++ b/core/embed/util/unit_properties/stm32/unit_properties.c
@@ -125,6 +125,8 @@ static bool detect_properties(unit_properties_t* props) {
props->btconly_is_valid = true;
props->packaging = otp_data[3];
props->packaging_is_valid = true;
+ props->battery_type = otp_data[4];
+ props->battery_type_is_valid = true;
break;
default:
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.