refactor(core): encapsulate fuel_gauge and battery model under single API.
What changed, and why it matters
This commit is a code refactor that moves the battery fuel-gauge and battery-model code into a single, cleaner API. It does not add new user-facing features or fix a security bug. The change reorganizes existing battery estimation logic so other parts of the firmware call a new 'battery' module instead of calling the fuel-gauge and battery-model pieces directly. There is no indication in the commit that this is a security patch.
No immediate security action required. Treat as routine maintenance refactor. If reviewing for release readiness, verify that the new bat_* wrapper preserves the previous locking/initialization behavior and that power_manager recovery paths still set and read SOC correctly after suspend/hibernation.
Security signals we found
Large refactor of power-management code touching battery state estimation
No changelog entry provided
No explicit security relevance stated by vendor
No CVE, advisory, or researcher attribution present in commit
Evidence from the diff
The change relocates fuel_gauge and battery_model sources from core/embed/io/power_manager/fuel_gauge/ to core/embed/io/power_manager/battery/, introduces a new battery.c/h wrapper that encapsulates the fuel-gauge state, battery model, and a 10-sample circular buffer, and updates power_manager.c and power_monitoring.c to use the new bat_* API. The EKF parameters are moved from power_manager_internal.h into fuel_gauge.c as compile-time constants. The battery model selection remains based on unit_properties.battery_type. Functionally the same estimation algorithm is preserved; this is an architectural encapsulation refactor with [no changelog].
Changed components
core/embed/io/power_manager/battery/*core/embed/io/power_manager/stm32u5/power_manager.ccore/embed/io/power_manager/stm32u5/power_manager_internal.hcore/embed/io/power_manager/stm32u5/power_monitoring.ccore/site_scons/models/T3W1/*Inspect captured patch +1657 / −1349
diff --git a/core/embed/io/power_manager/battery/battery.c b/core/embed/io/power_manager/battery/battery.c
new file mode 100644
index 000000000..7756e3bc6
--- /dev/null
+++ b/core/embed/io/power_manager/battery/battery.c
@@ -0,0 +1,248 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef KERNEL_MODE
+
+#include <trezor_rtl.h>
+
+#include "battery.h"
+#include "battery_model.h"
+#include "fuel_gauge.h"
+
+typedef struct {
+ float voltage_V;
+ float current_mA;
+ float temp_C;
+} bat_sample_t;
+
+typedef struct {
+ bat_sample_t samples[BAT_FG_SAMPLE_BUF_SIZE];
+ uint8_t tail_idx;
+ uint8_t head_idx;
+} bat_sample_buffer_t;
+
+typedef struct {
+ bool initialized;
+
+ // Fuel gauge state initialized and locked, could be updated based on battery
+ // measurements
+ bool fg_locked;
+
+ fuel_gauge_state_t fg_state;
+ battery_model_t battery_model;
+ bat_sample_buffer_t sample_buf;
+
+} bat_driver_t;
+
+bat_driver_t g_bat_driver = {
+ .initialized = false,
+};
+
+void bat_init(void) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (drv->initialized) {
+ return; // Already initialized
+ }
+
+ memset(drv, 0, sizeof(bat_driver_t));
+
+ battery_model_init(&drv->battery_model);
+ fuel_gauge_init(&drv->fg_state);
+
+ drv->fg_locked = false;
+ drv->initialized = true;
+}
+
+ts_t bat_fg_set_soc(float soc, float P) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return TS_ENOINIT;
+ }
+
+ fuel_gauge_set_soc(&drv->fg_state, soc, P);
+
+ drv->fg_locked = true;
+
+ return TS_OK;
+}
+
+ts_t bat_fg_feed_sample(float voltage_V, float current_mA, float temp_C) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return TS_ENOINIT;
+ }
+
+ // Store battery data in the buffer
+ drv->sample_buf.samples[drv->sample_buf.head_idx].voltage_V = voltage_V;
+ drv->sample_buf.samples[drv->sample_buf.head_idx].current_mA = current_mA;
+ drv->sample_buf.samples[drv->sample_buf.head_idx].temp_C = temp_C;
+
+ // Update head index
+ drv->sample_buf.head_idx++;
+ if (drv->sample_buf.head_idx >= BAT_FG_SAMPLE_BUF_SIZE) {
+ drv->sample_buf.head_idx = 0;
+ }
+
+ // Check if the buffer is full
+ if (drv->sample_buf.head_idx == drv->sample_buf.tail_idx) {
+ // Buffer is full, move tail index forward
+ drv->sample_buf.tail_idx++;
+ if (drv->sample_buf.tail_idx >= BAT_FG_SAMPLE_BUF_SIZE) {
+ drv->sample_buf.tail_idx = 0;
+ }
+ }
+
+ return TS_OK;
+}
+
+ts_t bat_fg_initial_guess() {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return TS_ENOINIT;
+ }
+
+ if (drv->sample_buf.head_idx == drv->sample_buf.tail_idx) {
+ // Buffer is empty, no data to process
+ return TS_EINVAL;
+ }
+
+ // Calculate average voltage, current and temperature from the sampling
+ // buffer and run the fuel gauge initial guess
+ uint8_t buf_idx = drv->sample_buf.tail_idx;
+ uint8_t samples_cnt = 0;
+ float vbat_avg = 0.0f;
+ float ibat_avg = 0.0f;
+ float ntc_temp_avg = 0.0f;
+ while (drv->sample_buf.head_idx != buf_idx) {
+ vbat_avg += drv->sample_buf.samples[buf_idx].voltage_V;
+ ibat_avg += drv->sample_buf.samples[buf_idx].current_mA;
+ ntc_temp_avg += drv->sample_buf.samples[buf_idx].temp_C;
+ buf_idx++;
+ if (buf_idx >= BAT_FG_SAMPLE_BUF_SIZE) {
+ buf_idx = 0;
+ }
+
+ samples_cnt++;
+ }
+
+ // Calculate average values
+ vbat_avg /= samples_cnt;
+ ibat_avg /= samples_cnt;
+ ntc_temp_avg /= samples_cnt;
+
+ fuel_gauge_initial_guess(&drv->fg_state, &drv->battery_model, vbat_avg,
+ ibat_avg, ntc_temp_avg);
+
+ drv->fg_locked = true;
+
+ return TS_OK;
+}
+
+bool bat_fg_is_locked(void) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return false;
+ }
+
+ return drv->fg_locked;
+}
+
+ts_t bat_fg_get_state(bat_fg_state_t* data) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return TS_ENOINIT;
+ }
+
+ if (data == NULL) {
+ return TS_EINVAL;
+ }
+
+ data->soc = drv->fg_state.soc;
+ data->soc_latched = drv->fg_state.soc_latched;
+ data->P = drv->fg_state.P;
+
+ return TS_OK;
+}
+
+ts_t bat_fg_update(uint32_t dt_ms, float voltage_V, float current_mA,
+ float temp_C) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return TS_ENOINIT;
+ }
+ if (!drv->fg_locked) {
+ return TS_EINVAL;
+ }
+
+ fuel_gauge_update(&drv->fg_state, &drv->battery_model, dt_ms, voltage_V,
+ current_mA, temp_C);
+
+ return TS_OK;
+}
+
+ts_t bat_fg_compensate_soc(float* soc, uint32_t elapsed_s,
+ float avg_bat_current_mA, float avg_temp_C) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return TS_ENOINIT;
+ }
+
+ if (!drv->fg_locked) {
+ return TS_EINVAL;
+ }
+
+ float compensation_mah = ((avg_bat_current_mA)*elapsed_s) / 3600.0f;
+ bool discharging_mode = avg_bat_current_mA >= 0.0f;
+ *soc -=
+ (compensation_mah / battery_total_capacity(&drv->battery_model,
+ avg_temp_C, discharging_mode));
+
+ return TS_OK;
+}
+
+float bat_soc_to_ocv(float soc, float temp_C, bool discharging_mode) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return 0.0f;
+ }
+
+ return battery_ocv(&drv->battery_model, soc, temp_C, discharging_mode);
+}
+
+float bat_meas_to_ocv(float voltage_V, float current_mA, float temp_C) {
+ bat_driver_t* drv = &g_bat_driver;
+
+ if (!drv->initialized) {
+ return 0.0f;
+ }
+
+ return battery_meas_to_ocv(&drv->battery_model, voltage_V, current_mA,
+ temp_C);
+}
+
+#endif
diff --git a/core/embed/io/power_manager/battery/battery.h b/core/embed/io/power_manager/battery/battery.h
new file mode 100644
index 000000000..9f99d0981
--- /dev/null
+++ b/core/embed/io/power_manager/battery/battery.h
@@ -0,0 +1,177 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @file battery.h
+ * @brief Battery management driver with Extended Kalman Filter fuel gauge
+ *
+ * This driver provides battery state estimation using an Extended Kalman Filter
+ * (EKF) based fuel gauge algorithm. It estimates the State of Charge (SOC) by
+ * processing battery voltage, current, and temperature measurements along with
+ * a battery model.
+ *
+ * ## Usage:
+ * 1. Initialize the driver with `bat_init()`
+ * 2. Set the initial fuel gauge state using one of two approaches:
+ * - **If SOC is already known** (e.g., from persistent storage): Use
+ * `bat_fg_set_soc()` to directly set the fuel gauge state and lock it for
+ * operation
+ * - **If SOC is unknown**: Feed several measurement samples using
+ * `bat_fg_feed_sample()`, then call `bat_fg_initial_guess()` to estimate the
+ * initial SOC based on the collected voltage, current, and temperature data
+ * 3. Continuously update the fuel gauge with new measurements using
+ * `bat_fg_update()`
+ * 4. Retrieve the current SOC estimate using `bat_fg_get_state()`
+ *
+ * The driver maintains an internal battery model for voltage-to-SOC conversion
+ * and uses temperature compensation for improved accuracy across operating
+ * conditions.
+ */
+
+#pragma once
+
+#include <trezor_rtl.h>
+
+#define BAT_FG_SAMPLE_BUF_SIZE 10
+
+/** @brief Bat fuel gauge state structure */
+typedef struct {
+ float soc; ///< State of charge estimate (0.0 to 1.0)
+ float soc_latched; ///< Latched SOC (the one that gets reported)
+ float P; ///< Error covariance
+} bat_fg_state_t;
+
+/**
+ * @brief Initialize the battery module
+ */
+void bat_init(void);
+
+/**
+ * @brief Set the fuel gauge state to given SOC value
+ *
+ * This function will force set the fuel gauge SoC to given value and lock it.
+ * May be used even if the fuel gauge was already locked.
+ *
+ **/
+ts_t bat_fg_set_soc(float soc, float P);
+
+/**
+ * @brief Feed a new measurement sample to the unlocked fuel gauge.
+ *
+ * This function is used in case the fuel gauge was not yet initialized and
+ * its state is unknown. To improve the state initial guess, user may use
+ * this function to feed several samples first into the buffer, and then call
+ * `bat_fg_initial_guess()` to compute the inital guess of the fuel gauge
+ * state on larger set of samples.
+ *
+ * sampling buffer has size of `BAT_FG_SAMPLE_BUF_SIZE` and is build as circular
+ * buffer, so after feeding more samples than the buffer size, only the most
+ * recent samples are used for the initial guess estimation.
+ *
+ * @param voltage_V Measured battery voltage in volts
+ * @param current_mA Measured battery current in mA (positive for discharge)
+ * @param temp_C Battery temperature in Celsius
+ * @return TS_OK on success, error code otherwise
+ */
+ts_t bat_fg_feed_sample(float voltage_V, float current_mA, float temp_C);
+
+/**
+ * @brief Make fuel gauge initial SOC guess based on the buffered samples.
+ *
+ * calling this funtion will process all the samples fed into the sampling
+ * buffer with `bat_fg_feed_sample()` and compute the initial SOC guess
+ * estimate. the fuel gauge state will be marked as locked after this call
+ * and may be updated with `bat_fg_update()`.
+ *
+ */
+ts_t bat_fg_initial_guess();
+
+/**
+ * @brief Check if the fuel gauge state is initialized and locked
+ *
+ * locked fuel gauge represents that fuel gauge state was correctly initialized
+ * and may be updated based on the battery measuremets with `bat_fg_update()`.
+ *
+ * @return true if locked, false otherwise
+ */
+bool bat_fg_is_locked(void);
+
+/**
+ * @brief Get the current fuel gauge state
+ *
+ * @param data Pointer to the fuel gauge state structure to be filled.
+ * @return TS_OK on success, error code otherwise
+ */
+ts_t bat_fg_get_state(bat_fg_state_t* data);
+
+/**
+ * @brief Update the fuel gauge EKD with the new measurement
+ *
+ * @param dt_ms Time delta since last update in milliseconds
+ * @param voltage_V Measured battery voltage in volts
+ * @param current_mA Measured battery current in mA (positive for discharge)
+ * @param temp_C Battery temperature in Celsius
+ * @return TS_OK on success, error code otherwise
+ */
+ts_t bat_fg_update(uint32_t dt_ms, float voltage_V, float current_mA,
+ float temp_C);
+
+/**
+ * @brief Compensate the fuel gauge SoC for constant charge/discharge over the
+ * elapsed time period.
+ *
+ * This function adjust and returns the fuel gauge state of charge (SOC)
+ * estimate with respect to the average battery current over a specified
+ * elapsed time. Compenstation is useful if the battery has been
+ * charging/discharging under static conditions without ability to update the
+ * fuel gauge normally. (e.g., during system suspend or hibernation).
+ *
+ * @param soc Pointer to the fuel gauge state of charge (0.0 to 1.0) to be
+ * compensated
+ * @param elapsed_s Elapsed time period in seconds
+ * @param avg_bat_current_mA Average battery current in mA (positive for
+ * discharge)
+ * @param avg_temp_C Average battery temperature in Celsius
+ * @return TS_OK on success, error code otherwise
+ */
+ts_t bat_fg_compensate_soc(float* soc, uint32_t elapsed_s,
+ float avg_bat_current_mA, float avg_temp_C);
+
+/**
+ * @brief Convert battery SOC to OCV according to the battery model at given
+ * temperature point.
+ *
+ * @param soc State of charge (0.0 to 1.0)
+ * @param temp_C Temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Open circuit voltage in volts
+ */
+float bat_soc_to_ocv(float soc, float temp_C, bool discharging_mode);
+
+/**
+ * @brief Convert measured battery voltage and current to OCV according to the
+ * battery model at given temperature point.
+ *
+ * @param voltage_V Measured battery voltage in volts
+ * @param current_mA Measured battery current in mA (positive for discharge)
+ * @param temp_C Battery temperature in Celsius
+ * @return Open circuit voltage in volts
+ *
+ */
+float bat_meas_to_ocv(float voltage_V, float current_mA, float temp_C);
diff --git a/core/embed/io/power_manager/battery/battery_data/hcf343837ncz.h b/core/embed/io/power_manager/battery/battery_data/hcf343837ncz.h
new file mode 100644
index 000000000..9f9d16c94
--- /dev/null
+++ b/core/embed/io/power_manager/battery/battery_data/hcf343837ncz.h
@@ -0,0 +1,232 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Battery Data: HCF343837NCZ
+ * Auto-generated from battery characterization data
+ * Contains lookup tables and parameters for the specific battery model
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+/**
+ * Battery Specifications:
+ * Model: HCF343837NCZ
+ * Chemistry: LiFePO4
+ */
+
+// Configuration
+#define BATTERY_HCF343837NCZ_NUM_TEMP_POINTS 9
+
+// SOC breakpoints for piecewise functions
+#define BATTERY_HCF343837NCZ_SOC_BREAKPOINT_1 0.2f
+#define BATTERY_HCF343837NCZ_SOC_BREAKPOINT_2 0.7f
+
+// Temperature points arrays (in Celsius)
+// Discharge temperatures
+static const float BATTERY_HCF343837NCZ_TEMP_POINTS_DISCHG
+ [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS] = {
+ 1.02f, 5.86f, 10.72f, 15.56f, 20.49f, 30.36f, 35.31f, 40.32f, 45.34f};
+
+// Charge temperatures
+static const float
+ BATTERY_HCF343837NCZ_TEMP_POINTS_CHG[BATTERY_HCF343837NCZ_NUM_TEMP_POINTS] =
+ {2.39f, 7.37f, 12.68f, 17.53f, 22.46f, 32.26f, 37.22f, 42.17f, 47.15f};
+
+// Internal resistance curve parameters (rational function parameters
+// a+b*t)/(c+d*t)
+static const float BATTERY_HCF343837NCZ_R_INT_PARAMS[4] = {
+ // a, b, c, d for rational function (a + b*t)/(c + d*t)
+ 5148.694149f, 126.612310f, 6446.087576f, 437.006759f};
+
+// Discharge OCV curve parameters for each temperature
+static const float BATTERY_HCF343837NCZ_OCV_DISCHARGE_PARAMS
+ [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][10] = {
+ // Temperature: 1.02°C (key: 0)
+ {
+ 0.132744f, 3.209158f, // m, b (linear segment)
+ -377.860239f, -5173.820403f, -123.475291f,
+ -1567.959576f, // a1, b1, c1, d1 (first rational segment)
+ 6416.385096f, -6367.610930f, 1943.981986f,
+ -1931.427092f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 5.86°C (key: 5)
+ {
+ 0.126718f, 3.219663f, // m, b (linear segment)
+ -658.352709f, -9465.436646f, -215.671511f,
+ -2858.499585f, // a1, b1, c1, d1 (first rational segment)
+ 1103.603880f, -1089.643805f, 333.742552f,
+ -329.897901f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 10.72°C (key: 10)
+ {
+ 0.108352f, 3.229992f, // m, b (linear segment)
+ -2493.910531f, -28133.328290f, -817.138793f,
+ -8431.792420f, // a1, b1, c1, d1 (first rational segment)
+ 1615.858515f, -4201.535059f, 475.291094f,
+ -1251.915058f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 15.56°C (key: 15)
+ {
+ 0.111289f, 3.231867f, // m, b (linear segment)
+ -4966.693381f, -49873.913621f, -1623.977390f,
+ -14898.978255f, // a1, b1, c1, d1 (first rational segment)
+ 1012.376908f, -1993.599184f, 301.394188f,
+ -595.999103f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 20.49°C (key: 20)
+ {
+ 0.120106f, 3.229563f, // m, b (linear segment)
+ 268.096564f, 2874.958069f, 87.433893f,
+ 861.258620f, // a1, b1, c1, d1 (first rational segment)
+ -1770.509656f, 3559.122578f, -527.259884f,
+ 1063.803643f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 30.36°C (key: 30)
+ {
+ 0.137237f, 3.223399f, // m, b (linear segment)
+ 1038.166704f, 18767.842731f, 339.512092f,
+ 5677.727929f, // a1, b1, c1, d1 (first rational segment)
+ -1351.860989f, 2612.204208f, -403.454434f,
+ 781.043397f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 35.31°C (key: 35)
+ {
+ 0.143897f, 3.223448f, // m, b (linear segment)
+ 13894.623462f, 290634.247388f, 4549.590583f,
+ 88020.681469f, // a1, b1, c1, d1 (first rational segment)
+ -2937.654993f, 148.254576f, -891.816102f,
+ 59.282724f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 40.32°C (key: 40)
+ {
+ 0.156721f, 3.219657f, // m, b (linear segment)
+ 486.002705f, 11027.269400f, 159.266245f,
+ 3343.274892f, // a1, b1, c1, d1 (first rational segment)
+ -362.733040f, 361.915501f, -108.955783f,
+ 108.840505f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 45.34°C (key: 45)
+ {
+ 0.156192f, 3.221027f, // m, b (linear segment)
+ 477.405525f, 10988.255232f, 156.524863f,
+ 3330.213529f, // a1, b1, c1, d1 (first rational segment)
+ 496.936214f, -496.194305f, 149.219279f,
+ -149.173026f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Charge OCV curve parameters for each temperature
+static const float BATTERY_HCF343837NCZ_OCV_CHARGE_PARAMS
+ [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][10] = {
+ // Temperature: 2.39°C (key: 0)
+ {
+ 0.087208f, 3.332748f, // m, b (linear segment)
+ 262.388264f, 33277.522403f, 82.663155f,
+ 9902.336513f, // a1, b1, c1, d1 (first rational segment)
+ 350.518328f, -324.259695f, 104.652569f,
+ -97.343933f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 7.37°C (key: 5)
+ {
+ 0.137945f, 3.291426f, // m, b (linear segment)
+ 244.061830f, 14327.472407f, 78.683519f,
+ 4286.625957f, // a1, b1, c1, d1 (first rational segment)
+ 120.553563f, -109.825804f, 36.170685f,
+ -33.200196f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 12.68°C (key: 10)
+ {
+ 0.145137f, 3.277093f, // m, b (linear segment)
+ 63.104078f, 2993.710560f, 20.495998f,
+ 897.719518f, // a1, b1, c1, d1 (first rational segment)
+ 648.380265f, -559.549859f, 196.709317f,
+ -171.937183f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 17.53°C (key: 15)
+ {
+ 0.136263f, 3.272912f, // m, b (linear segment)
+ 410.756037f, 20687.122117f, 133.039482f,
+ 6220.274827f, // a1, b1, c1, d1 (first rational segment)
+ 354.112527f, -327.223549f, 106.665466f,
+ -99.204169f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 22.46°C (key: 20)
+ {
+ 0.134281f, 3.270871f, // m, b (linear segment)
+ 518.429721f, 24701.422489f, 168.372867f,
+ 7429.281380f, // a1, b1, c1, d1 (first rational segment)
+ 253.931799f, -239.537581f, 76.297951f,
+ -72.320239f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 32.26°C (key: 30)
+ {
+ 0.127118f, 3.265582f, // m, b (linear segment)
+ 266.663194f, 9172.306429f, 86.480023f,
+ 2759.277240f, // a1, b1, c1, d1 (first rational segment)
+ 161.967344f, -159.021597f, 48.451837f,
+ -47.664881f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 37.22°C (key: 35)
+ {
+ 0.120619f, 3.268397f, // m, b (linear segment)
+ 173.617347f, 5728.679569f, 56.323639f,
+ 1721.968776f, // a1, b1, c1, d1 (first rational segment)
+ 80.505082f, -79.040169f, 24.096966f,
+ -23.704603f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 42.17°C (key: 40)
+ {
+ 0.092222f, 3.287151f, // m, b (linear segment)
+ -57.313130f, -822.995842f, -17.883998f,
+ -246.343999f, // a1, b1, c1, d1 (first rational segment)
+ 440.636027f, -435.083990f, 131.783395f,
+ -130.300948f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 47.15°C (key: 45)
+ {
+ 0.128932f, 3.261763f, // m, b (linear segment)
+ 120.858087f, 3927.216714f, 39.505813f,
+ 1180.973085f, // a1, b1, c1, d1 (first rational segment)
+ 447.508970f, -441.668629f, 133.782446f,
+ -132.244176f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Battery capacity data for each temperature
+static const float
+ BATTERY_HCF343837NCZ_CAPACITY[BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][2] = {
+ // Temperature: 1.02°C (key: 0)
+ {274.60f, 301.37f},
+ // Temperature: 5.86°C (key: 5)
+ {305.00f, 362.34f},
+ // Temperature: 10.72°C (key: 10)
+ {327.00f, 382.02f},
+ // Temperature: 15.56°C (key: 15)
+ {338.20f, 384.26f},
+ // Temperature: 20.49°C (key: 20)
+ {354.99f, 389.48f},
+ // Temperature: 30.36°C (key: 30)
+ {362.31f, 389.79f},
+ // Temperature: 35.31°C (key: 35)
+ {364.07f, 389.98f},
+ // Temperature: 40.32°C (key: 40)
+ {363.84f, 353.93f},
+ // Temperature: 45.34°C (key: 45)
+ {364.62f, 391.60f}};
diff --git a/core/embed/io/power_manager/battery/battery_data/jyhpfl333838.h b/core/embed/io/power_manager/battery/battery_data/jyhpfl333838.h
new file mode 100644
index 000000000..5dd8073bd
--- /dev/null
+++ b/core/embed/io/power_manager/battery/battery_data/jyhpfl333838.h
@@ -0,0 +1,252 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * Battery Data: JYHPFL333838
+ * Auto-generated from battery characterization data
+ * Contains lookup tables and parameters for the specific battery model
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+/**
+ * Battery Specifications:
+ * Model: JYHPFL333838
+ * Chemistry: LiFePO4
+ */
+
+// Configuration
+#define BATTERY_JYHPFL333838_NUM_TEMP_POINTS 10
+
+// SOC breakpoints for piecewise functions
+#define BATTERY_JYHPFL333838_SOC_BREAKPOINT_1 0.25f
+#define BATTERY_JYHPFL333838_SOC_BREAKPOINT_2 0.8f
+
+// Temperature points arrays (in Celsius)
+// Discharge temperatures
+static const float BATTERY_JYHPFL333838_TEMP_POINTS_DISCHG
+ [BATTERY_JYHPFL333838_NUM_TEMP_POINTS] = {0.80f, 5.78f, 10.64f, 15.55f,
+ 20.65f, 25.43f, 31.41f, 35.41f,
+ 40.38f, 45.28f};
+
+// Charge temperatures
+static const float
+ BATTERY_JYHPFL333838_TEMP_POINTS_CHG[BATTERY_JYHPFL333838_NUM_TEMP_POINTS] =
+ {2.32f, 7.28f, 12.60f, 17.51f, 22.50f,
+ 27.38f, 32.31f, 37.36f, 42.34f, 47.37f};
+
+// Internal resistance curve parameters (rational function parameters
+// a+b*t)/(c+d*t)
+static const float BATTERY_JYHPFL333838_R_INT_PARAMS[4] = {
+ // a, b, c, d for rational function (a + b*t)/(c + d*t)
+ 3.700987f, 0.063115f, 4.059870f, 0.273364f};
+
+// Discharge OCV curve parameters for each temperature
+static const float BATTERY_JYHPFL333838_OCV_DISCHARGE_PARAMS
+ [BATTERY_JYHPFL333838_NUM_TEMP_POINTS][10] = {
+ // Temperature: 0.80°C (key: 0)
+ {
+ 0.126550f, 3.211777f, // m, b (linear segment)
+ 19.098931f, 149.532130f, 6.226951f,
+ 44.749610f, // a1, b1, c1, d1 (first rational segment)
+ 1209.541847f, -1204.643391f, 365.238423f,
+ -363.797538f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 5.78°C (key: 5)
+ {
+ 0.118854f, 3.221712f, // m, b (linear segment)
+ 510.794524f, 4147.718053f, 167.121531f,
+ 1235.569720f, // a1, b1, c1, d1 (first rational segment)
+ 2461.131197f, -2454.069820f, 742.268667f,
+ -740.201672f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 10.64°C (key: 10)
+ {
+ 0.116679f, 3.228096f, // m, b (linear segment)
+ 4.902604f, 39.346060f, 1.602605f,
+ 11.689570f, // a1, b1, c1, d1 (first rational segment)
+ 1195.141551f, -1192.318620f, 359.914069f,
+ -359.086078f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 15.55°C (key: 15)
+ {
+ 0.114185f, 3.232650f, // m, b (linear segment)
+ 60.048245f, 556.913282f, 19.591820f,
+ 166.054156f, // a1, b1, c1, d1 (first rational segment)
+ 3200.736593f, -3191.682291f, 963.249686f,
+ -960.608735f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 20.65°C (key: 20)
+ {
+ 0.114688f, 3.233887f, // m, b (linear segment)
+ 62.865854f, 711.447068f, 20.495367f,
+ 213.158188f, // a1, b1, c1, d1 (first rational segment)
+ 1180.804882f, -1178.074609f, 355.167808f,
+ -354.373464f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 25.43°C (key: 25)
+ {
+ 0.117543f, 3.236008f, // m, b (linear segment)
+ 78.102339f, 1077.942159f, 25.484299f,
+ 323.846567f, // a1, b1, c1, d1 (first rational segment)
+ 1130.318095f, -1127.865886f, 339.544458f,
+ -338.836536f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 31.41°C (key: 30)
+ {
+ 0.117363f, 3.238875f, // m, b (linear segment)
+ 88.813605f, 1434.949559f, 29.026877f,
+ 431.654409f, // a1, b1, c1, d1 (first rational segment)
+ 2953.178047f, -2947.773461f, 886.333650f,
+ -884.769279f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 35.41°C (key: 35)
+ {
+ 0.123731f, 3.236223f, // m, b (linear segment)
+ 129.239103f, 2182.616715f, 42.262528f,
+ 657.226142f, // a1, b1, c1, d1 (first rational segment)
+ 4774.132278f, -4767.725320f, 1431.719239f,
+ -1429.869351f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 40.38°C (key: 40)
+ {
+ 0.123696f, 3.237016f, // m, b (linear segment)
+ 221.044970f, 3715.383819f, 72.272903f,
+ 1118.389323f, // a1, b1, c1, d1 (first rational segment)
+ -4039.944265f, 4034.063152f, -1211.294227f,
+ 1209.598810f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 45.28°C (key: 45)
+ {
+ 0.125006f, 3.235912f, // m, b (linear segment)
+ 151.467401f, 2582.526900f, 49.550052f,
+ 777.691108f, // a1, b1, c1, d1 (first rational segment)
+ 1112.445343f, -1110.999778f, 333.537185f,
+ -333.119361f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Charge OCV curve parameters for each temperature
+static const float BATTERY_JYHPFL333838_OCV_CHARGE_PARAMS
+ [BATTERY_JYHPFL333838_NUM_TEMP_POINTS][10] = {
+ // Temperature: 2.32°C (key: 0)
+ {
+ 0.133654f, 3.292145f, // m, b (linear segment)
+ 2424.212366f, 87282.185143f, 753.227933f,
+ 26148.817273f, // a1, b1, c1, d1 (first rational segment)
+ -20885.884413f, 19421.324650f, -6263.752097f,
+ 5862.671202f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 7.28°C (key: 5)
+ {
+ 0.119964f, 3.293413f, // m, b (linear segment)
+ 2732.271006f, 75783.249716f, 850.221080f,
+ 22690.534179f, // a1, b1, c1, d1 (first rational segment)
+ -4317.842520f, 4121.656010f, -1290.625974f,
+ 1236.917022f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 12.60°C (key: 10)
+ {
+ 0.129891f, 3.273207f, // m, b (linear segment)
+ 846.503340f, 15988.928725f, 265.726589f,
+ 4798.200588f, // a1, b1, c1, d1 (first rational segment)
+ -60068.019107f, 55820.453762f, -18158.693048f,
+ 16993.928616f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 17.51°C (key: 15)
+ {
+ 0.115653f, 3.274031f, // m, b (linear segment)
+ 237.696958f, 1964.826751f, 74.429637f,
+ 585.013431f, // a1, b1, c1, d1 (first rational segment)
+ 968.862408f, -935.338129f, 290.869232f,
+ -281.680851f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 22.50°C (key: 20)
+ {
+ 0.118277f, 3.272331f, // m, b (linear segment)
+ -26.011330f, -198.086479f, -8.180144f,
+ -58.781818f, // a1, b1, c1, d1 (first rational segment)
+ 983.725464f, -950.493228f, 295.209174f,
+ -286.098635f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 27.38°C (key: 25)
+ {
+ 0.111950f, 3.273751f, // m, b (linear segment)
+ -25.521502f, -274.597696f, -8.095046f,
+ -81.706277f, // a1, b1, c1, d1 (first rational segment)
+ 1073.930068f, -1053.131703f, 321.219989f,
+ -315.514254f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 32.31°C (key: 30)
+ {
+ 0.105879f, 3.276268f, // m, b (linear segment)
+ 154.842986f, 1451.215812f, 48.865130f,
+ 431.470258f, // a1, b1, c1, d1 (first rational segment)
+ -3747.632139f, 3694.953159f, -1119.887101f,
+ 1105.424025f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 37.36°C (key: 35)
+ {
+ 0.103781f, 3.277949f, // m, b (linear segment)
+ 42.648170f, 582.113263f, 13.465663f,
+ 173.961241f, // a1, b1, c1, d1 (first rational segment)
+ 1109.000316f, -1096.490080f, 331.110598f,
+ -327.675032f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 42.34°C (key: 40)
+ {
+ 0.105248f, 3.278675f, // m, b (linear segment)
+ 86.330934f, 1157.715172f, 27.596520f,
+ 344.393006f, // a1, b1, c1, d1 (first rational segment)
+ 1018.218867f, -1006.960134f, 303.810937f,
+ -300.720120f // a3, b3, c3, d3 (third rational segment)
+ },
+ // Temperature: 47.37°C (key: 45)
+ {
+ 0.102922f, 3.281457f, // m, b (linear segment)
+ 133.236246f, 1680.463569f, 42.563832f,
+ 499.016826f, // a1, b1, c1, d1 (first rational segment)
+ 731.525161f, -722.630154f, 218.130924f,
+ -215.651760f // a3, b3, c3, d3 (third rational segment)
+ }};
+
+// Battery capacity data for each temperature
+static const float
+ BATTERY_JYHPFL333838_CAPACITY[BATTERY_JYHPFL333838_NUM_TEMP_POINTS][2] = {
+ // Temperature: 0.80°C (key: 0)
+ {297.56f, 315.21f},
+ // Temperature: 5.78°C (key: 5)
+ {325.07f, 336.55f},
+ // Temperature: 10.64°C (key: 10)
+ {343.23f, 366.44f},
+ // Temperature: 15.55°C (key: 15)
+ {355.86f, 378.79f},
+ // Temperature: 20.65°C (key: 20)
+ {362.69f, 394.38f},
+ // Temperature: 25.43°C (key: 25)
+ {357.80f, 383.75f},
+ // Temperature: 31.41°C (key: 30)
+ {361.17f, 379.75f},
+ // Temperature: 35.41°C (key: 35)
+ {357.76f, 366.75f},
+ // Temperature: 40.38°C (key: 40)
+ {357.29f, 383.63f},
+ // Temperature: 45.28°C (key: 45)
+ {353.41f, 377.66f}};
diff --git a/core/embed/io/power_manager/battery/battery_model.c b/core/embed/io/power_manager/battery/battery_model.c
new file mode 100644
index 000000000..b9bfa584b
--- /dev/null
+++ b/core/embed/io/power_manager/battery/battery_model.c
@@ -0,0 +1,346 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <math.h>
+
+#include <sec/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) {
+ // Prevent division by zero
+ if (fabsf(x2 - x1) < 1e-6f) {
+ return (y1 + y2) / 2.0f; // Return average if x values are too close
+ }
+ return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
+}
+
+// Calculate OCV for specific parameters and SOC
+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 <= model->soc_breakpoint_2) {
+ // Middle segment (linear function): m*x + b
+ float m = params[0];
+ float b = params[1];
+ return m * soc + b;
+ } else {
+ // Third segment (rational function): (a3 + b3*x)/(c3 + d3*x)
+ float a3 = params[6];
+ float b3 = params[7];
+ float c3 = params[8];
+ float d3 = params[9];
+ return (a3 + b3 * soc) / (c3 + d3 * soc);
+ }
+}
+
+// Calculate OCV slope for specific parameters and SOC
+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];
+ float c1 = params[4];
+ float d1 = params[5];
+ float denominator = c1 + d1 * soc;
+ return (b1 * c1 - a1 * d1) / (denominator * denominator);
+ } else if (soc <= model->soc_breakpoint_2) {
+ // Middle segment (linear function derivative)
+ float m = params[0];
+ return m;
+ } else {
+ // Third segment (rational function derivative)
+ float a3 = params[6];
+ float b3 = params[7];
+ float c3 = params[8];
+ float d3 = params[9];
+ float denominator = c3 + d3 * soc;
+ return (b3 * c3 - a3 * d3) / (denominator * denominator);
+ }
+}
+
+// Calculate SOC from OCV for specific parameters
+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(model, params, model->soc_breakpoint_1);
+ float ocv_breakpoint_2 = calc_ocv(model, params, model->soc_breakpoint_2);
+
+ // Extract parameters
+ float m = params[0];
+ float b = params[1];
+ float a1 = params[2];
+ float b1 = params[3];
+ float c1 = params[4];
+ float d1 = params[5];
+ float a3 = params[6];
+ float b3 = params[7];
+ float c3 = params[8];
+ float d3 = params[9];
+
+ if (ocv < ocv_breakpoint_1) {
+ // First segment (rational function inverse)
+ return (a1 - c1 * ocv) / (d1 * ocv - b1);
+ } else if (ocv <= ocv_breakpoint_2) {
+ // Middle segment (linear function inverse)
+ return (ocv - b) / m;
+ } else {
+ // Third segment (rational function inverse)
+ return (a3 - c3 * ocv) / (d3 * ocv - b3);
+ }
+}
+
+float battery_rint(const battery_model_t* model, float temperature) {
+ // Calculate R_int using rational function: (a + b*t)/(c + d*t)
+ 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(const battery_model_t* model, float temperature,
+ bool discharging_mode) {
+ // Select appropriate temperature array based on mode
+ 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 model->capacity[0][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 < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ return linear_interpolate(
+ temperature, temp_points[i],
+ model->capacity[i][discharging_mode ? 0 : 1], temp_points[i + 1],
+ model->capacity[i + 1][discharging_mode ? 0 : 1]);
+ }
+ }
+
+ // Should never reach here
+ return model->capacity[0][discharging_mode ? 0 : 1];
+}
+
+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(model, temperature));
+}
+
+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 ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ 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[model->num_temp_points - 1]) {
+ const float* params =
+ discharging_mode
+ ? 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 < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ const float* params_low = discharging_mode
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
+
+ const float* params_high = discharging_mode
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
+
+ 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);
+ }
+ }
+
+ // Should never reach here
+ 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(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 ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ 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[model->num_temp_points - 1]) {
+ const float* params =
+ discharging_mode
+ ? 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 < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ const float* params_low = discharging_mode
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
+
+ const float* params_high = discharging_mode
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
+
+ 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);
+ }
+ }
+
+ // Should never reach here
+ 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(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 ? model->temp_points_discharge
+ : model->temp_points_charge;
+
+ // Handle out-of-bounds temperatures
+ if (temperature <= temp_points[0]) {
+ 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[model->num_temp_points - 1]) {
+ const float* params =
+ discharging_mode
+ ? 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 < model->num_temp_points - 1; i++) {
+ if (temperature < temp_points[i + 1]) {
+ const float* params_low = discharging_mode
+ ? model->ocv_discharge_params[i]
+ : model->ocv_charge_params[i];
+
+ const float* params_high = discharging_mode
+ ? model->ocv_discharge_params[i + 1]
+ : model->ocv_charge_params[i + 1];
+
+ 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);
+ }
+ }
+
+ // Should never reach here
+ 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;
+ break;
+ case 1:
+ model->soc_breakpoint_1 = BATTERY_HCF343837NCZ_SOC_BREAKPOINT_1;
+ model->soc_breakpoint_2 = BATTERY_HCF343837NCZ_SOC_BREAKPOINT_2;
+ model->num_temp_points = BATTERY_HCF343837NCZ_NUM_TEMP_POINTS;
+ model->temp_points_charge = BATTERY_HCF343837NCZ_TEMP_POINTS_CHG;
+ model->temp_points_discharge = BATTERY_HCF343837NCZ_TEMP_POINTS_DISCHG;
+ model->r_int_params = BATTERY_HCF343837NCZ_R_INT_PARAMS;
+ model->ocv_charge_params = BATTERY_HCF343837NCZ_OCV_CHARGE_PARAMS;
+ model->ocv_discharge_params = BATTERY_HCF343837NCZ_OCV_DISCHARGE_PARAMS;
+ model->capacity = BATTERY_HCF343837NCZ_CAPACITY;
+ break;
+ }
+}
+
+#endif
diff --git a/core/embed/io/power_manager/battery/battery_model.h b/core/embed/io/power_manager/battery/battery_model.h
new file mode 100644
index 000000000..5a9c198fc
--- /dev/null
+++ b/core/embed/io/power_manager/battery/battery_model.h
@@ -0,0 +1,104 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+/**
+ * Battery data headers - correct battery data will be selected
+ * based on the unit variant.
+ */
+#include "battery_data/hcf343837ncz.h"
+#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(const battery_model_t* model, float temperature);
+
+/**
+ * Get battery total capacity at the given temperature and discharge mode
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Total capacity in mAh
+ */
+float battery_total_capacity(const battery_model_t* model, float temperature,
+ bool discharging_mode);
+
+/**
+ * Calculate OCV from measured voltage and current
+ * @param voltage_V Measured battery voltage in volts
+ * @param current_mA Measured battery current in mA (positive for discharge)
+ * @param temperature Battery temperature in Celsius
+ * @return Open circuit voltage (OCV) in volts
+ */
+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
+ * @param soc State of charge (0.0 to 1.0)
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Open circuit voltage in volts
+ */
+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
+ * @param soc State of charge (0.0 to 1.0)
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return Slope of OCV curve (dOCV/dSOC) in volts
+ */
+float battery_ocv_slope(const battery_model_t* model, float soc,
+ float temperature, bool discharging_mode);
+
+/**
+ * Get SOC for given OCV and temperature
+ * @param ocv Open circuit voltage in volts
+ * @param temperature Battery temperature in Celsius
+ * @param discharging_mode true if discharging, false if charging
+ * @return State of charge (0.0 to 1.0)
+ */
+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/io/power_manager/battery/fuel_gauge.c b/core/embed/io/power_manager/battery/fuel_gauge.c
new file mode 100644
index 000000000..7aee32281
--- /dev/null
+++ b/core/embed/io/power_manager/battery/fuel_gauge.c
@@ -0,0 +1,154 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifdef KERNEL_MODE
+
+#include <math.h>
+
+#include "battery_model.h"
+#include "fuel_gauge.h"
+
+// Fuel gauge extended kalman filter parameters
+#define FUEL_GAUGE_R 3500.0f
+#define FUEL_GAUGE_Q 0.0001f
+#define FUEL_GAUGE_R_AGGRESSIVE 3000.0f
+#define FUEL_GAUGE_Q_AGGRESSIVE 0.0002f
+#define FUEL_GAUGE_P_INIT 0.1f
+
+void fuel_gauge_init(fuel_gauge_state_t* state) {
+ // Initialize state
+ fuel_gauge_reset(state);
+ state->P = FUEL_GAUGE_P_INIT; // Initial error covariance
+}
+
+void fuel_gauge_reset(fuel_gauge_state_t* state) {
+ state->soc = 0.0f;
+ state->soc_latched = 0.0f;
+}
+
+void fuel_gauge_set_soc(fuel_gauge_state_t* state, float soc, float P) {
+ soc = fmaxf(0.0f, fminf(soc, 1.0f)); // Clamp SOC to [0, 1]
+
+ // Set SOC directly
+ state->soc = soc;
+ state->soc_latched = soc;
+ state->P = P; // Set error covariance
+}
+
+void fuel_gauge_initial_guess(fuel_gauge_state_t* state, battery_model_t* model,
+ float voltage_V, float current_mA,
+ float temperature) {
+ // Determine if we're in discharge mode
+ bool discharging_mode = current_mA >= 0.0f;
+
+ // Calculate OCV from terminal voltage and current
+ float ocv = battery_meas_to_ocv(model, voltage_V, current_mA, temperature);
+
+ // Extract SoC from battery model
+ state->soc = battery_soc(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;
+}
+
+float fuel_gauge_update(fuel_gauge_state_t* state, battery_model_t* model,
+ uint32_t dt_ms, float voltage_V, float current_mA,
+ float temperature) {
+ if (current_mA == 0.0f) {
+ // No current flow, return latched SOC without updating
+ return state->soc_latched;
+ }
+
+ // Determine if we're in discharge mode
+ bool discharging_mode = current_mA >= 0.0f;
+
+ // Choose filter parameters based on temperature and SOC
+ float R = FUEL_GAUGE_R;
+ float Q = FUEL_GAUGE_Q;
+
+ // When in low temperature or at the edge of the charging/dischargins
+ // profile, use more agressive EKF settings to rely more on the ocv
+ // curves rather then on current model
+ if (temperature < 10.0f) {
+ R = FUEL_GAUGE_R_AGGRESSIVE;
+ Q = FUEL_GAUGE_Q_AGGRESSIVE;
+ } else {
+ if (discharging_mode && state->soc_latched < 0.2f) {
+ R = FUEL_GAUGE_R_AGGRESSIVE;
+ Q = FUEL_GAUGE_Q_AGGRESSIVE;
+ } else if (!discharging_mode && state->soc_latched > 0.8f) {
+ R = FUEL_GAUGE_R_AGGRESSIVE;
+ Q = FUEL_GAUGE_Q_AGGRESSIVE;
+ }
+ }
+
+ // Convert milliseconds to seconds
+ float dt_sec = dt_ms / 1000.0f;
+
+ // Get total capacity at current temperature
+ float total_capacity =
+ battery_total_capacity(model, temperature, discharging_mode);
+
+ // State prediction (coulomb counting)
+ // SOC_k+1 = SOC_k - (I*dt)/(3600*capacity)
+ float x_k1_k =
+ 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(model, x_k1_k, temperature, discharging_mode);
+
+ // Error covariance prediction
+ float P_k1_k = state->P + Q;
+
+ // Calculate innovation covariance
+ float S = h_jacobian * P_k1_k * h_jacobian + R;
+
+ // Calculate Kalman gain
+ float K_k1_k = P_k1_k * h_jacobian / S;
+
+ // Calculate predicted terminal voltage
+ float v_pred = battery_ocv(model, x_k1_k, temperature, discharging_mode) -
+ (current_mA / 1000.0f) * battery_rint(model, temperature);
+
+ // State update
+ float x_k1_k1 = x_k1_k + K_k1_k * (voltage_V - v_pred);
+
+ // Error covariance update
+ float P_k1_k1 = (1.0f - K_k1_k * h_jacobian) * P_k1_k;
+
+ // Enforce SOC boundaries
+ state->soc = (x_k1_k1 < 0.0f) ? 0.0f : ((x_k1_k1 > 1.0f) ? 1.0f : x_k1_k1);
+ state->P = P_k1_k1;
+
+ // Update latched SOC based on current direction
+ if (current_mA > 0.0f) {
+ // Discharging, SOC should move only in negative direction
+ if (state->soc < state->soc_latched) {
+ state->soc_latched = state->soc;
+ }
+ } else {
+ // Charging, SOC should move only in positive direction
+ if (state->soc > state->soc_latched) {
+ state->soc_latched = state->soc;
+ }
+ }
+
+ return state->soc_latched;
+}
+
+#endif
diff --git a/core/embed/io/power_manager/battery/fuel_gauge.h b/core/embed/io/power_manager/battery/fuel_gauge.h
new file mode 100644
index 000000000..7df0a3b7d
--- /dev/null
+++ b/core/embed/io/power_manager/battery/fuel_gauge.h
@@ -0,0 +1,86 @@
+/*
+ * This file is part of the Trezor project, https://trezor.io/
+ *
+ * Copyright (c) SatoshiLabs
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <trezor_types.h>
+
+#include "battery_model.h"
+
+/**
+ * @brief Fuel gauge state structure
+ */
+typedef struct {
+ float soc; ///< State of charge estimate (0.0 to 1.0)
+ float soc_latched; ///< Latched SOC (the one that gets reported)
+ float P; ///< Error covariance
+} fuel_gauge_state_t;
+
+/**
+ * @brief Initialize the fuel gauge state
+ *
+ * @param state Pointer to EKF state structure
+ * @param R Measurement noise variance
+ * @param Q Process noise variance
+ * @param R_aggressive Aggressive mode measurement noise variance
+ * @param Q_aggressive Aggressive mode process noise variance
+ * @param P_init Initial error covariance
+ */
+void fuel_gauge_init(fuel_gauge_state_t* state);
+
+/**
+ * @brief Reset the EKF state
+ *
+ * @param state Pointer to EKF state structure
+ */
+void fuel_gauge_reset(fuel_gauge_state_t* state);
+
+/**
+ * @brief Set SOC directly
+ *
+ * @param state Pointer to EKF state structure
+ * @param soc State of charge (0.0 to 1.0)
+ */
+void fuel_gauge_set_soc(fuel_gauge_state_t* state, float soc, float P);
+
+/**
+ * @brief Make initial SOC guess based on OCV
+ *
+ * @param state Pointer to EKF state structure
+ * @param voltage_V Current battery voltage (V)
+ * @param current_mA Current battery current (mA), positive for discharge
+ * @param temperature Battery temperature (°C)
+ */
+void fuel_gauge_initial_guess(fuel_gauge_state_t* state,
+ battery_model_t* battery_model, float voltage_V,
+ float current_mA, float temperature);
+
+/**
+ * @brief Update the fuel gauge with new measurements
+ *
+ * @param state Pointer to EKF state structure
+ * @param dt_ms Time step in milliseconds
+ * @param voltage_V Current battery voltage (V)
+ * @param current_mA Current battery current (mA), positive for discharge
+ * @param temperature Battery temperature (°C)
+ * @return Updated SOC estimate (0.0 to 1.0)
+ */
+float fuel_gauge_update(fuel_gauge_state_t* state,
+ battery_model_t* battery_model, uint32_t dt_ms,
+ float voltage_V, float current_mA, float temperature);
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_data_hcf343837ncz.h b/core/embed/io/power_manager/fuel_gauge/battery_data_hcf343837ncz.h
deleted file mode 100644
index 9f9d16c94..000000000
--- a/core/embed/io/power_manager/fuel_gauge/battery_data_hcf343837ncz.h
+++ /dev/null
@@ -1,232 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Battery Data: HCF343837NCZ
- * Auto-generated from battery characterization data
- * Contains lookup tables and parameters for the specific battery model
- */
-
-#pragma once
-
-#include <trezor_types.h>
-
-/**
- * Battery Specifications:
- * Model: HCF343837NCZ
- * Chemistry: LiFePO4
- */
-
-// Configuration
-#define BATTERY_HCF343837NCZ_NUM_TEMP_POINTS 9
-
-// SOC breakpoints for piecewise functions
-#define BATTERY_HCF343837NCZ_SOC_BREAKPOINT_1 0.2f
-#define BATTERY_HCF343837NCZ_SOC_BREAKPOINT_2 0.7f
-
-// Temperature points arrays (in Celsius)
-// Discharge temperatures
-static const float BATTERY_HCF343837NCZ_TEMP_POINTS_DISCHG
- [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS] = {
- 1.02f, 5.86f, 10.72f, 15.56f, 20.49f, 30.36f, 35.31f, 40.32f, 45.34f};
-
-// Charge temperatures
-static const float
- BATTERY_HCF343837NCZ_TEMP_POINTS_CHG[BATTERY_HCF343837NCZ_NUM_TEMP_POINTS] =
- {2.39f, 7.37f, 12.68f, 17.53f, 22.46f, 32.26f, 37.22f, 42.17f, 47.15f};
-
-// Internal resistance curve parameters (rational function parameters
-// a+b*t)/(c+d*t)
-static const float BATTERY_HCF343837NCZ_R_INT_PARAMS[4] = {
- // a, b, c, d for rational function (a + b*t)/(c + d*t)
- 5148.694149f, 126.612310f, 6446.087576f, 437.006759f};
-
-// Discharge OCV curve parameters for each temperature
-static const float BATTERY_HCF343837NCZ_OCV_DISCHARGE_PARAMS
- [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][10] = {
- // Temperature: 1.02°C (key: 0)
- {
- 0.132744f, 3.209158f, // m, b (linear segment)
- -377.860239f, -5173.820403f, -123.475291f,
- -1567.959576f, // a1, b1, c1, d1 (first rational segment)
- 6416.385096f, -6367.610930f, 1943.981986f,
- -1931.427092f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 5.86°C (key: 5)
- {
- 0.126718f, 3.219663f, // m, b (linear segment)
- -658.352709f, -9465.436646f, -215.671511f,
- -2858.499585f, // a1, b1, c1, d1 (first rational segment)
- 1103.603880f, -1089.643805f, 333.742552f,
- -329.897901f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 10.72°C (key: 10)
- {
- 0.108352f, 3.229992f, // m, b (linear segment)
- -2493.910531f, -28133.328290f, -817.138793f,
- -8431.792420f, // a1, b1, c1, d1 (first rational segment)
- 1615.858515f, -4201.535059f, 475.291094f,
- -1251.915058f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 15.56°C (key: 15)
- {
- 0.111289f, 3.231867f, // m, b (linear segment)
- -4966.693381f, -49873.913621f, -1623.977390f,
- -14898.978255f, // a1, b1, c1, d1 (first rational segment)
- 1012.376908f, -1993.599184f, 301.394188f,
- -595.999103f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 20.49°C (key: 20)
- {
- 0.120106f, 3.229563f, // m, b (linear segment)
- 268.096564f, 2874.958069f, 87.433893f,
- 861.258620f, // a1, b1, c1, d1 (first rational segment)
- -1770.509656f, 3559.122578f, -527.259884f,
- 1063.803643f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 30.36°C (key: 30)
- {
- 0.137237f, 3.223399f, // m, b (linear segment)
- 1038.166704f, 18767.842731f, 339.512092f,
- 5677.727929f, // a1, b1, c1, d1 (first rational segment)
- -1351.860989f, 2612.204208f, -403.454434f,
- 781.043397f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 35.31°C (key: 35)
- {
- 0.143897f, 3.223448f, // m, b (linear segment)
- 13894.623462f, 290634.247388f, 4549.590583f,
- 88020.681469f, // a1, b1, c1, d1 (first rational segment)
- -2937.654993f, 148.254576f, -891.816102f,
- 59.282724f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 40.32°C (key: 40)
- {
- 0.156721f, 3.219657f, // m, b (linear segment)
- 486.002705f, 11027.269400f, 159.266245f,
- 3343.274892f, // a1, b1, c1, d1 (first rational segment)
- -362.733040f, 361.915501f, -108.955783f,
- 108.840505f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 45.34°C (key: 45)
- {
- 0.156192f, 3.221027f, // m, b (linear segment)
- 477.405525f, 10988.255232f, 156.524863f,
- 3330.213529f, // a1, b1, c1, d1 (first rational segment)
- 496.936214f, -496.194305f, 149.219279f,
- -149.173026f // a3, b3, c3, d3 (third rational segment)
- }};
-
-// Charge OCV curve parameters for each temperature
-static const float BATTERY_HCF343837NCZ_OCV_CHARGE_PARAMS
- [BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][10] = {
- // Temperature: 2.39°C (key: 0)
- {
- 0.087208f, 3.332748f, // m, b (linear segment)
- 262.388264f, 33277.522403f, 82.663155f,
- 9902.336513f, // a1, b1, c1, d1 (first rational segment)
- 350.518328f, -324.259695f, 104.652569f,
- -97.343933f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 7.37°C (key: 5)
- {
- 0.137945f, 3.291426f, // m, b (linear segment)
- 244.061830f, 14327.472407f, 78.683519f,
- 4286.625957f, // a1, b1, c1, d1 (first rational segment)
- 120.553563f, -109.825804f, 36.170685f,
- -33.200196f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 12.68°C (key: 10)
- {
- 0.145137f, 3.277093f, // m, b (linear segment)
- 63.104078f, 2993.710560f, 20.495998f,
- 897.719518f, // a1, b1, c1, d1 (first rational segment)
- 648.380265f, -559.549859f, 196.709317f,
- -171.937183f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 17.53°C (key: 15)
- {
- 0.136263f, 3.272912f, // m, b (linear segment)
- 410.756037f, 20687.122117f, 133.039482f,
- 6220.274827f, // a1, b1, c1, d1 (first rational segment)
- 354.112527f, -327.223549f, 106.665466f,
- -99.204169f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 22.46°C (key: 20)
- {
- 0.134281f, 3.270871f, // m, b (linear segment)
- 518.429721f, 24701.422489f, 168.372867f,
- 7429.281380f, // a1, b1, c1, d1 (first rational segment)
- 253.931799f, -239.537581f, 76.297951f,
- -72.320239f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 32.26°C (key: 30)
- {
- 0.127118f, 3.265582f, // m, b (linear segment)
- 266.663194f, 9172.306429f, 86.480023f,
- 2759.277240f, // a1, b1, c1, d1 (first rational segment)
- 161.967344f, -159.021597f, 48.451837f,
- -47.664881f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 37.22°C (key: 35)
- {
- 0.120619f, 3.268397f, // m, b (linear segment)
- 173.617347f, 5728.679569f, 56.323639f,
- 1721.968776f, // a1, b1, c1, d1 (first rational segment)
- 80.505082f, -79.040169f, 24.096966f,
- -23.704603f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 42.17°C (key: 40)
- {
- 0.092222f, 3.287151f, // m, b (linear segment)
- -57.313130f, -822.995842f, -17.883998f,
- -246.343999f, // a1, b1, c1, d1 (first rational segment)
- 440.636027f, -435.083990f, 131.783395f,
- -130.300948f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 47.15°C (key: 45)
- {
- 0.128932f, 3.261763f, // m, b (linear segment)
- 120.858087f, 3927.216714f, 39.505813f,
- 1180.973085f, // a1, b1, c1, d1 (first rational segment)
- 447.508970f, -441.668629f, 133.782446f,
- -132.244176f // a3, b3, c3, d3 (third rational segment)
- }};
-
-// Battery capacity data for each temperature
-static const float
- BATTERY_HCF343837NCZ_CAPACITY[BATTERY_HCF343837NCZ_NUM_TEMP_POINTS][2] = {
- // Temperature: 1.02°C (key: 0)
- {274.60f, 301.37f},
- // Temperature: 5.86°C (key: 5)
- {305.00f, 362.34f},
- // Temperature: 10.72°C (key: 10)
- {327.00f, 382.02f},
- // Temperature: 15.56°C (key: 15)
- {338.20f, 384.26f},
- // Temperature: 20.49°C (key: 20)
- {354.99f, 389.48f},
- // Temperature: 30.36°C (key: 30)
- {362.31f, 389.79f},
- // Temperature: 35.31°C (key: 35)
- {364.07f, 389.98f},
- // Temperature: 40.32°C (key: 40)
- {363.84f, 353.93f},
- // Temperature: 45.34°C (key: 45)
- {364.62f, 391.60f}};
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_data_jyhpfl333838.h b/core/embed/io/power_manager/fuel_gauge/battery_data_jyhpfl333838.h
deleted file mode 100644
index 5dd8073bd..000000000
--- a/core/embed/io/power_manager/fuel_gauge/battery_data_jyhpfl333838.h
+++ /dev/null
@@ -1,252 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-/**
- * Battery Data: JYHPFL333838
- * Auto-generated from battery characterization data
- * Contains lookup tables and parameters for the specific battery model
- */
-
-#pragma once
-
-#include <trezor_types.h>
-
-/**
- * Battery Specifications:
- * Model: JYHPFL333838
- * Chemistry: LiFePO4
- */
-
-// Configuration
-#define BATTERY_JYHPFL333838_NUM_TEMP_POINTS 10
-
-// SOC breakpoints for piecewise functions
-#define BATTERY_JYHPFL333838_SOC_BREAKPOINT_1 0.25f
-#define BATTERY_JYHPFL333838_SOC_BREAKPOINT_2 0.8f
-
-// Temperature points arrays (in Celsius)
-// Discharge temperatures
-static const float BATTERY_JYHPFL333838_TEMP_POINTS_DISCHG
- [BATTERY_JYHPFL333838_NUM_TEMP_POINTS] = {0.80f, 5.78f, 10.64f, 15.55f,
- 20.65f, 25.43f, 31.41f, 35.41f,
- 40.38f, 45.28f};
-
-// Charge temperatures
-static const float
- BATTERY_JYHPFL333838_TEMP_POINTS_CHG[BATTERY_JYHPFL333838_NUM_TEMP_POINTS] =
- {2.32f, 7.28f, 12.60f, 17.51f, 22.50f,
- 27.38f, 32.31f, 37.36f, 42.34f, 47.37f};
-
-// Internal resistance curve parameters (rational function parameters
-// a+b*t)/(c+d*t)
-static const float BATTERY_JYHPFL333838_R_INT_PARAMS[4] = {
- // a, b, c, d for rational function (a + b*t)/(c + d*t)
- 3.700987f, 0.063115f, 4.059870f, 0.273364f};
-
-// Discharge OCV curve parameters for each temperature
-static const float BATTERY_JYHPFL333838_OCV_DISCHARGE_PARAMS
- [BATTERY_JYHPFL333838_NUM_TEMP_POINTS][10] = {
- // Temperature: 0.80°C (key: 0)
- {
- 0.126550f, 3.211777f, // m, b (linear segment)
- 19.098931f, 149.532130f, 6.226951f,
- 44.749610f, // a1, b1, c1, d1 (first rational segment)
- 1209.541847f, -1204.643391f, 365.238423f,
- -363.797538f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 5.78°C (key: 5)
- {
- 0.118854f, 3.221712f, // m, b (linear segment)
- 510.794524f, 4147.718053f, 167.121531f,
- 1235.569720f, // a1, b1, c1, d1 (first rational segment)
- 2461.131197f, -2454.069820f, 742.268667f,
- -740.201672f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 10.64°C (key: 10)
- {
- 0.116679f, 3.228096f, // m, b (linear segment)
- 4.902604f, 39.346060f, 1.602605f,
- 11.689570f, // a1, b1, c1, d1 (first rational segment)
- 1195.141551f, -1192.318620f, 359.914069f,
- -359.086078f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 15.55°C (key: 15)
- {
- 0.114185f, 3.232650f, // m, b (linear segment)
- 60.048245f, 556.913282f, 19.591820f,
- 166.054156f, // a1, b1, c1, d1 (first rational segment)
- 3200.736593f, -3191.682291f, 963.249686f,
- -960.608735f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 20.65°C (key: 20)
- {
- 0.114688f, 3.233887f, // m, b (linear segment)
- 62.865854f, 711.447068f, 20.495367f,
- 213.158188f, // a1, b1, c1, d1 (first rational segment)
- 1180.804882f, -1178.074609f, 355.167808f,
- -354.373464f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 25.43°C (key: 25)
- {
- 0.117543f, 3.236008f, // m, b (linear segment)
- 78.102339f, 1077.942159f, 25.484299f,
- 323.846567f, // a1, b1, c1, d1 (first rational segment)
- 1130.318095f, -1127.865886f, 339.544458f,
- -338.836536f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 31.41°C (key: 30)
- {
- 0.117363f, 3.238875f, // m, b (linear segment)
- 88.813605f, 1434.949559f, 29.026877f,
- 431.654409f, // a1, b1, c1, d1 (first rational segment)
- 2953.178047f, -2947.773461f, 886.333650f,
- -884.769279f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 35.41°C (key: 35)
- {
- 0.123731f, 3.236223f, // m, b (linear segment)
- 129.239103f, 2182.616715f, 42.262528f,
- 657.226142f, // a1, b1, c1, d1 (first rational segment)
- 4774.132278f, -4767.725320f, 1431.719239f,
- -1429.869351f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 40.38°C (key: 40)
- {
- 0.123696f, 3.237016f, // m, b (linear segment)
- 221.044970f, 3715.383819f, 72.272903f,
- 1118.389323f, // a1, b1, c1, d1 (first rational segment)
- -4039.944265f, 4034.063152f, -1211.294227f,
- 1209.598810f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 45.28°C (key: 45)
- {
- 0.125006f, 3.235912f, // m, b (linear segment)
- 151.467401f, 2582.526900f, 49.550052f,
- 777.691108f, // a1, b1, c1, d1 (first rational segment)
- 1112.445343f, -1110.999778f, 333.537185f,
- -333.119361f // a3, b3, c3, d3 (third rational segment)
- }};
-
-// Charge OCV curve parameters for each temperature
-static const float BATTERY_JYHPFL333838_OCV_CHARGE_PARAMS
- [BATTERY_JYHPFL333838_NUM_TEMP_POINTS][10] = {
- // Temperature: 2.32°C (key: 0)
- {
- 0.133654f, 3.292145f, // m, b (linear segment)
- 2424.212366f, 87282.185143f, 753.227933f,
- 26148.817273f, // a1, b1, c1, d1 (first rational segment)
- -20885.884413f, 19421.324650f, -6263.752097f,
- 5862.671202f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 7.28°C (key: 5)
- {
- 0.119964f, 3.293413f, // m, b (linear segment)
- 2732.271006f, 75783.249716f, 850.221080f,
- 22690.534179f, // a1, b1, c1, d1 (first rational segment)
- -4317.842520f, 4121.656010f, -1290.625974f,
- 1236.917022f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 12.60°C (key: 10)
- {
- 0.129891f, 3.273207f, // m, b (linear segment)
- 846.503340f, 15988.928725f, 265.726589f,
- 4798.200588f, // a1, b1, c1, d1 (first rational segment)
- -60068.019107f, 55820.453762f, -18158.693048f,
- 16993.928616f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 17.51°C (key: 15)
- {
- 0.115653f, 3.274031f, // m, b (linear segment)
- 237.696958f, 1964.826751f, 74.429637f,
- 585.013431f, // a1, b1, c1, d1 (first rational segment)
- 968.862408f, -935.338129f, 290.869232f,
- -281.680851f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 22.50°C (key: 20)
- {
- 0.118277f, 3.272331f, // m, b (linear segment)
- -26.011330f, -198.086479f, -8.180144f,
- -58.781818f, // a1, b1, c1, d1 (first rational segment)
- 983.725464f, -950.493228f, 295.209174f,
- -286.098635f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 27.38°C (key: 25)
- {
- 0.111950f, 3.273751f, // m, b (linear segment)
- -25.521502f, -274.597696f, -8.095046f,
- -81.706277f, // a1, b1, c1, d1 (first rational segment)
- 1073.930068f, -1053.131703f, 321.219989f,
- -315.514254f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 32.31°C (key: 30)
- {
- 0.105879f, 3.276268f, // m, b (linear segment)
- 154.842986f, 1451.215812f, 48.865130f,
- 431.470258f, // a1, b1, c1, d1 (first rational segment)
- -3747.632139f, 3694.953159f, -1119.887101f,
- 1105.424025f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 37.36°C (key: 35)
- {
- 0.103781f, 3.277949f, // m, b (linear segment)
- 42.648170f, 582.113263f, 13.465663f,
- 173.961241f, // a1, b1, c1, d1 (first rational segment)
- 1109.000316f, -1096.490080f, 331.110598f,
- -327.675032f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 42.34°C (key: 40)
- {
- 0.105248f, 3.278675f, // m, b (linear segment)
- 86.330934f, 1157.715172f, 27.596520f,
- 344.393006f, // a1, b1, c1, d1 (first rational segment)
- 1018.218867f, -1006.960134f, 303.810937f,
- -300.720120f // a3, b3, c3, d3 (third rational segment)
- },
- // Temperature: 47.37°C (key: 45)
- {
- 0.102922f, 3.281457f, // m, b (linear segment)
- 133.236246f, 1680.463569f, 42.563832f,
- 499.016826f, // a1, b1, c1, d1 (first rational segment)
- 731.525161f, -722.630154f, 218.130924f,
- -215.651760f // a3, b3, c3, d3 (third rational segment)
- }};
-
-// Battery capacity data for each temperature
-static const float
- BATTERY_JYHPFL333838_CAPACITY[BATTERY_JYHPFL333838_NUM_TEMP_POINTS][2] = {
- // Temperature: 0.80°C (key: 0)
- {297.56f, 315.21f},
- // Temperature: 5.78°C (key: 5)
- {325.07f, 336.55f},
- // Temperature: 10.64°C (key: 10)
- {343.23f, 366.44f},
- // Temperature: 15.55°C (key: 15)
- {355.86f, 378.79f},
- // Temperature: 20.65°C (key: 20)
- {362.69f, 394.38f},
- // Temperature: 25.43°C (key: 25)
- {357.80f, 383.75f},
- // Temperature: 31.41°C (key: 30)
- {361.17f, 379.75f},
- // Temperature: 35.41°C (key: 35)
- {357.76f, 366.75f},
- // Temperature: 40.38°C (key: 40)
- {357.29f, 383.63f},
- // Temperature: 45.28°C (key: 45)
- {353.41f, 377.66f}};
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_model.c b/core/embed/io/power_manager/fuel_gauge/battery_model.c
deleted file mode 100644
index b9bfa584b..000000000
--- a/core/embed/io/power_manager/fuel_gauge/battery_model.c
+++ /dev/null
@@ -1,346 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifdef KERNEL_MODE
-
-#include <math.h>
-
-#include <sec/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) {
- // Prevent division by zero
- if (fabsf(x2 - x1) < 1e-6f) {
- return (y1 + y2) / 2.0f; // Return average if x values are too close
- }
- return y1 + (x - x1) * (y2 - y1) / (x2 - x1);
-}
-
-// Calculate OCV for specific parameters and SOC
-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 <= model->soc_breakpoint_2) {
- // Middle segment (linear function): m*x + b
- float m = params[0];
- float b = params[1];
- return m * soc + b;
- } else {
- // Third segment (rational function): (a3 + b3*x)/(c3 + d3*x)
- float a3 = params[6];
- float b3 = params[7];
- float c3 = params[8];
- float d3 = params[9];
- return (a3 + b3 * soc) / (c3 + d3 * soc);
- }
-}
-
-// Calculate OCV slope for specific parameters and SOC
-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];
- float c1 = params[4];
- float d1 = params[5];
- float denominator = c1 + d1 * soc;
- return (b1 * c1 - a1 * d1) / (denominator * denominator);
- } else if (soc <= model->soc_breakpoint_2) {
- // Middle segment (linear function derivative)
- float m = params[0];
- return m;
- } else {
- // Third segment (rational function derivative)
- float a3 = params[6];
- float b3 = params[7];
- float c3 = params[8];
- float d3 = params[9];
- float denominator = c3 + d3 * soc;
- return (b3 * c3 - a3 * d3) / (denominator * denominator);
- }
-}
-
-// Calculate SOC from OCV for specific parameters
-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(model, params, model->soc_breakpoint_1);
- float ocv_breakpoint_2 = calc_ocv(model, params, model->soc_breakpoint_2);
-
- // Extract parameters
- float m = params[0];
- float b = params[1];
- float a1 = params[2];
- float b1 = params[3];
- float c1 = params[4];
- float d1 = params[5];
- float a3 = params[6];
- float b3 = params[7];
- float c3 = params[8];
- float d3 = params[9];
-
- if (ocv < ocv_breakpoint_1) {
- // First segment (rational function inverse)
- return (a1 - c1 * ocv) / (d1 * ocv - b1);
- } else if (ocv <= ocv_breakpoint_2) {
- // Middle segment (linear function inverse)
- return (ocv - b) / m;
- } else {
- // Third segment (rational function inverse)
- return (a3 - c3 * ocv) / (d3 * ocv - b3);
- }
-}
-
-float battery_rint(const battery_model_t* model, float temperature) {
- // Calculate R_int using rational function: (a + b*t)/(c + d*t)
- 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(const battery_model_t* model, float temperature,
- bool discharging_mode) {
- // Select appropriate temperature array based on mode
- 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 model->capacity[0][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 < model->num_temp_points - 1; i++) {
- if (temperature < temp_points[i + 1]) {
- return linear_interpolate(
- temperature, temp_points[i],
- model->capacity[i][discharging_mode ? 0 : 1], temp_points[i + 1],
- model->capacity[i + 1][discharging_mode ? 0 : 1]);
- }
- }
-
- // Should never reach here
- return model->capacity[0][discharging_mode ? 0 : 1];
-}
-
-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(model, temperature));
-}
-
-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 ? model->temp_points_discharge
- : model->temp_points_charge;
-
- // Handle out-of-bounds temperatures
- if (temperature <= temp_points[0]) {
- 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[model->num_temp_points - 1]) {
- const float* params =
- discharging_mode
- ? 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 < model->num_temp_points - 1; i++) {
- if (temperature < temp_points[i + 1]) {
- const float* params_low = discharging_mode
- ? model->ocv_discharge_params[i]
- : model->ocv_charge_params[i];
-
- const float* params_high = discharging_mode
- ? model->ocv_discharge_params[i + 1]
- : model->ocv_charge_params[i + 1];
-
- 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);
- }
- }
-
- // Should never reach here
- 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(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 ? model->temp_points_discharge
- : model->temp_points_charge;
-
- // Handle out-of-bounds temperatures
- if (temperature <= temp_points[0]) {
- 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[model->num_temp_points - 1]) {
- const float* params =
- discharging_mode
- ? 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 < model->num_temp_points - 1; i++) {
- if (temperature < temp_points[i + 1]) {
- const float* params_low = discharging_mode
- ? model->ocv_discharge_params[i]
- : model->ocv_charge_params[i];
-
- const float* params_high = discharging_mode
- ? model->ocv_discharge_params[i + 1]
- : model->ocv_charge_params[i + 1];
-
- 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);
- }
- }
-
- // Should never reach here
- 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(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 ? model->temp_points_discharge
- : model->temp_points_charge;
-
- // Handle out-of-bounds temperatures
- if (temperature <= temp_points[0]) {
- 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[model->num_temp_points - 1]) {
- const float* params =
- discharging_mode
- ? 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 < model->num_temp_points - 1; i++) {
- if (temperature < temp_points[i + 1]) {
- const float* params_low = discharging_mode
- ? model->ocv_discharge_params[i]
- : model->ocv_charge_params[i];
-
- const float* params_high = discharging_mode
- ? model->ocv_discharge_params[i + 1]
- : model->ocv_charge_params[i + 1];
-
- 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);
- }
- }
-
- // Should never reach here
- 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;
- break;
- case 1:
- model->soc_breakpoint_1 = BATTERY_HCF343837NCZ_SOC_BREAKPOINT_1;
- model->soc_breakpoint_2 = BATTERY_HCF343837NCZ_SOC_BREAKPOINT_2;
- model->num_temp_points = BATTERY_HCF343837NCZ_NUM_TEMP_POINTS;
- model->temp_points_charge = BATTERY_HCF343837NCZ_TEMP_POINTS_CHG;
- model->temp_points_discharge = BATTERY_HCF343837NCZ_TEMP_POINTS_DISCHG;
- model->r_int_params = BATTERY_HCF343837NCZ_R_INT_PARAMS;
- model->ocv_charge_params = BATTERY_HCF343837NCZ_OCV_CHARGE_PARAMS;
- model->ocv_discharge_params = BATTERY_HCF343837NCZ_OCV_DISCHARGE_PARAMS;
- model->capacity = BATTERY_HCF343837NCZ_CAPACITY;
- break;
- }
-}
-
-#endif
diff --git a/core/embed/io/power_manager/fuel_gauge/battery_model.h b/core/embed/io/power_manager/fuel_gauge/battery_model.h
deleted file mode 100644
index 56115d721..000000000
--- a/core/embed/io/power_manager/fuel_gauge/battery_model.h
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <trezor_types.h>
-
-// Include the battery data header - this will be selected at compile time
-// based on which battery is being used
-#include "battery_data_hcf343837ncz.h"
-#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(const battery_model_t* model, float temperature);
-
-/**
- * Get battery total capacity at the given temperature and discharge mode
- * @param temperature Battery temperature in Celsius
- * @param discharging_mode true if discharging, false if charging
- * @return Total capacity in mAh
- */
-float battery_total_capacity(const battery_model_t* model, float temperature,
- bool discharging_mode);
-
-/**
- * Calculate OCV from measured voltage and current
- * @param voltage_V Measured battery voltage in volts
- * @param current_mA Measured battery current in mA (positive for discharge)
- * @param temperature Battery temperature in Celsius
- * @return Open circuit voltage (OCV) in volts
- */
-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
- * @param soc State of charge (0.0 to 1.0)
- * @param temperature Battery temperature in Celsius
- * @param discharging_mode true if discharging, false if charging
- * @return Open circuit voltage in volts
- */
-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
- * @param soc State of charge (0.0 to 1.0)
- * @param temperature Battery temperature in Celsius
- * @param discharging_mode true if discharging, false if charging
- * @return Slope of OCV curve (dOCV/dSOC) in volts
- */
-float battery_ocv_slope(const battery_model_t* model, float soc,
- float temperature, bool discharging_mode);
-
-/**
- * Get SOC for given OCV and temperature
- * @param ocv Open circuit voltage in volts
- * @param temperature Battery temperature in Celsius
- * @param discharging_mode true if discharging, false if charging
- * @return State of charge (0.0 to 1.0)
- */
-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/io/power_manager/fuel_gauge/fuel_gauge.c b/core/embed/io/power_manager/fuel_gauge/fuel_gauge.c
deleted file mode 100644
index 3f07ff88e..000000000
--- a/core/embed/io/power_manager/fuel_gauge/fuel_gauge.c
+++ /dev/null
@@ -1,157 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-#ifdef KERNEL_MODE
-
-#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) {
- state->R = R;
- state->Q = Q;
- state->R_aggressive = R_aggressive;
- state->Q_aggressive = Q_aggressive;
-
- // Initialize state
- 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) {
- // Reset state but keep filter parameters
- state->soc = 0.0f;
- state->soc_latched = 0.0f;
-}
-
-void fuel_gauge_set_soc(fuel_gauge_state_t* state, float soc, float P) {
- soc = fmaxf(0.0f, fminf(soc, 1.0f)); // Clamp SOC to [0, 1]
-
- // Set SOC directly
- state->soc = soc;
- state->soc_latched = soc;
- state->P = P; // Set error covariance
-}
-
-void fuel_gauge_initial_guess(fuel_gauge_state_t* state, float voltage_V,
- float current_mA, float temperature) {
- // Determine if we're in discharge mode
- bool discharging_mode = current_mA >= 0.0f;
-
- // Calculate OCV from terminal voltage and current
- float ocv =
- battery_meas_to_ocv(&state->model, voltage_V, current_mA, temperature);
-
- // Extract SoC from battery model
- 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;
-}
-
-float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
- float voltage_V, float current_mA, float temperature) {
- if (current_mA == 0.0f) {
- // No current flow, return latched SOC without updating
- return state->soc_latched;
- }
-
- // Determine if we're in discharge mode
- bool discharging_mode = current_mA >= 0.0f;
-
- // Choose filter parameters based on temperature and SOC
- float R = state->R;
- float Q = state->Q;
-
- // When in low temperature or at the edge of the charging/dischargins
- // profile, use more agressive EKF settings to rely more on the ocv
- // curves rather then on current model
- if (temperature < 10.0f) {
- R = state->R_aggressive;
- Q = state->Q_aggressive;
- } else {
- if (discharging_mode && state->soc_latched < 0.2f) {
- R = state->R_aggressive;
- Q = state->Q_aggressive;
- } else if (!discharging_mode && state->soc_latched > 0.8f) {
- R = state->R_aggressive;
- Q = state->Q_aggressive;
- }
- }
-
- // Convert milliseconds to seconds
- float dt_sec = dt_ms / 1000.0f;
-
- // Get total capacity at current temperature
- float total_capacity =
- battery_total_capacity(&state->model, temperature, discharging_mode);
-
- // State prediction (coulomb counting)
- // SOC_k+1 = SOC_k - (I*dt)/(3600*capacity)
- float x_k1_k =
- 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(&state->model, x_k1_k, temperature, discharging_mode);
-
- // Error covariance prediction
- float P_k1_k = state->P + Q;
-
- // Calculate innovation covariance
- float S = h_jacobian * P_k1_k * h_jacobian + R;
-
- // Calculate Kalman gain
- float K_k1_k = P_k1_k * h_jacobian / S;
-
- // Calculate predicted terminal voltage
- 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);
-
- // Error covariance update
- float P_k1_k1 = (1.0f - K_k1_k * h_jacobian) * P_k1_k;
-
- // Enforce SOC boundaries
- state->soc = (x_k1_k1 < 0.0f) ? 0.0f : ((x_k1_k1 > 1.0f) ? 1.0f : x_k1_k1);
- state->P = P_k1_k1;
-
- // Update latched SOC based on current direction
- if (current_mA > 0.0f) {
- // Discharging, SOC should move only in negative direction
- if (state->soc < state->soc_latched) {
- state->soc_latched = state->soc;
- }
- } else {
- // Charging, SOC should move only in positive direction
- if (state->soc > state->soc_latched) {
- state->soc_latched = state->soc;
- }
- }
-
- return state->soc_latched;
-}
-
-#endif
diff --git a/core/embed/io/power_manager/fuel_gauge/fuel_gauge.h b/core/embed/io/power_manager/fuel_gauge/fuel_gauge.h
deleted file mode 100644
index 2e8b1a897..000000000
--- a/core/embed/io/power_manager/fuel_gauge/fuel_gauge.h
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * This file is part of the Trezor project, https://trezor.io/
- *
- * Copyright (c) SatoshiLabs
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#pragma once
-
-#include <trezor_types.h>
-
-#include "battery_model.h"
-
-/**
- * @brief Fuel gauge state structure
- */
-typedef struct {
- battery_model_t model; ///< Battery model parameters
-
- /** @name State estimates */
- /**@{*/
- float soc; ///< State of charge estimate (0.0 to 1.0)
- float soc_latched; ///< Latched SOC (the one that gets reported)
- float P; ///< Error covariance
- /**@}*/
-
- /** @name Filter parameters */
- /**@{*/
- float R; ///< Measurement noise variance
- float Q; ///< Process noise variance
- float R_aggressive; ///< Aggressive measurement noise variance
- float Q_aggressive; ///< Aggressive process noise variance
- /**@}*/
-} fuel_gauge_state_t;
-
-/**
- * @brief Initialize the fuel gauge state
- *
- * @param state Pointer to EKF state structure
- * @param R Measurement noise variance
- * @param Q Process noise variance
- * @param R_aggressive Aggressive mode measurement noise variance
- * @param Q_aggressive Aggressive mode process noise variance
- * @param P_init Initial error covariance
- */
-void fuel_gauge_init(fuel_gauge_state_t* state, float R, float Q,
- float R_aggressive, float Q_aggressive, float P_init);
-
-/**
- * @brief Reset the EKF state
- *
- * @param state Pointer to EKF state structure
- */
-void fuel_gauge_reset(fuel_gauge_state_t* state);
-
-/**
- * @brief Set SOC directly
- *
- * @param state Pointer to EKF state structure
- * @param soc State of charge (0.0 to 1.0)
- */
-void fuel_gauge_set_soc(fuel_gauge_state_t* state, float soc, float P);
-
-/**
- * @brief Make initial SOC guess based on OCV
- *
- * @param state Pointer to EKF state structure
- * @param voltage_V Current battery voltage (V)
- * @param current_mA Current battery current (mA), positive for discharge
- * @param temperature Battery temperature (°C)
- */
-void fuel_gauge_initial_guess(fuel_gauge_state_t* state, float voltage_V,
- float current_mA, float temperature);
-
-/**
- * @brief Update the fuel gauge with new measurements
- *
- * @param state Pointer to EKF state structure
- * @param dt_ms Time step in milliseconds
- * @param voltage_V Current battery voltage (V)
- * @param current_mA Current battery current (mA), positive for discharge
- * @param temperature Battery temperature (°C)
- * @return Updated SOC estimate (0.0 to 1.0)
- */
-float fuel_gauge_update(fuel_gauge_state_t* state, uint32_t dt_ms,
- float voltage_V, float current_mA, float temperature);
diff --git a/core/embed/io/power_manager/stm32u5/power_manager.c b/core/embed/io/power_manager/stm32u5/power_manager.c
index ea500c33a..abe115847 100644
--- a/core/embed/io/power_manager/stm32u5/power_manager.c
+++ b/core/embed/io/power_manager/stm32u5/power_manager.c
@@ -32,7 +32,7 @@
#include <sys/rtc_scheduler.h>
#endif
-#include "../fuel_gauge/battery_model.h"
+#include "../battery/battery.h"
#include "../power_manager_poll.h"
#include "../stwlc38/stwlc38.h"
#include "power_manager_internal.h"
@@ -69,10 +69,8 @@ pm_status_t pm_init(bool inherit_state) {
return PM_ERROR;
}
- // Initialize fuel gauge
- fuel_gauge_init(&drv->fuel_gauge, PM_FUEL_GAUGE_R, PM_FUEL_GAUGE_Q,
- PM_FUEL_GAUGE_R_AGGRESSIVE, PM_FUEL_GAUGE_Q_AGGRESSIVE,
- PM_FUEL_GAUGE_P_INIT);
+ // Initialize battery model with fuel gauge estimator
+ bat_init();
// Create monitoring timer
drv->monitoring_timer = systimer_create(pm_monitoring_timer_handler, NULL);
@@ -120,9 +118,9 @@ pm_status_t pm_init(bool inherit_state) {
// If the RTC timestamp is older than the last captured timestamp,
// we will not use it.
if (rtc_timestamp >= recovery.last_capture_timestamp) {
- pm_compensate_fuel_gauge(
- &recovery.soc, rtc_timestamp - recovery.last_capture_timestamp,
- PM_SELF_DISG_RATE_HIBERNATION_MA, 25.0f);
+ bat_fg_compensate_soc(&recovery.soc,
+ rtc_timestamp - recovery.last_capture_timestamp,
+ PM_SELF_DISG_RATE_HIBERNATION_MA, 25.0f);
}
}
}
@@ -130,9 +128,9 @@ pm_status_t pm_init(bool inherit_state) {
#endif
drv->battery_critical = recovery.bat_critical;
- fuel_gauge_set_soc(&drv->fuel_gauge, recovery.soc, recovery.P);
+ bat_fg_set_soc(recovery.soc, recovery.P);
} else {
- pm_battery_initial_soc_guess();
+ bat_fg_initial_guess();
}
if (inherit_state) {
@@ -423,8 +421,12 @@ pm_status_t pm_get_report(pm_report_t* report) {
report->battery_voltage_v = drv->pmic_data.vbat;
report->battery_current_ma = drv->pmic_data.ibat;
report->battery_temp_c = drv->pmic_data.ntc_temp;
- report->battery_soc = drv->fuel_gauge.soc;
- report->battery_soc_latched = drv->fuel_gauge.soc_latched;
+
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+ report->battery_soc = fg_state.soc;
+ report->battery_soc_latched = fg_state.soc_latched;
+
report->pmic_temp_c = drv->pmic_data.die_temp;
report->wireless_rectifier_voltage_v = drv->wireless_data.vrect;
report->wireless_output_voltage_v = drv->wireless_data.vout;
@@ -500,8 +502,11 @@ pm_status_t pm_store_data_to_backup_ram() {
pm_recovery_data_t recovery = {.version = PM_RECOVERY_DATA_VERSION};
- recovery.soc = drv->fuel_gauge.soc;
- recovery.P = drv->fuel_gauge.P;
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+
+ recovery.soc = fg_state.soc;
+ recovery.P = fg_state.P;
// Power manager state
recovery.bat_critical = drv->battery_critical;
@@ -739,21 +744,6 @@ bool pm_driver_is_suspended(void) {
return suspended;
}
-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(&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) {
uint32_t expire_time = ticks_timeout(timeout_ms);
diff --git a/core/embed/io/power_manager/stm32u5/power_manager_internal.h b/core/embed/io/power_manager/stm32u5/power_manager_internal.h
index 4b23e23f1..48ce426ca 100644
--- a/core/embed/io/power_manager/stm32u5/power_manager_internal.h
+++ b/core/embed/io/power_manager/stm32u5/power_manager_internal.h
@@ -26,7 +26,6 @@
#include <sys/rtc_scheduler.h>
#include <sys/systimer.h>
-#include "../fuel_gauge/fuel_gauge.h"
#include "../stwlc38/stwlc38.h"
// Power manager thresholds & timings
@@ -37,18 +36,10 @@
#define PM_BATTERY_LOW_THRESHOLD_SOC 15
#define PM_BATTERY_CHARGING_CURRENT_MAX PMIC_CHARGING_LIMIT_MAX
#define PM_BATTERY_CHARGING_CURRENT_MIN PMIC_CHARGING_LIMIT_MIN
-#define PM_BATTERY_SAMPLING_BUF_SIZE 10
#define PM_SELF_DISG_RATE_HIBERNATION_MA 0.004f
#define PM_SELF_DISG_RATE_SUSPEND_MA 0.032f
-// Fuel gauge extended kalman filter parameters
-#define PM_FUEL_GAUGE_R 3500.0f
-#define PM_FUEL_GAUGE_Q 0.0001f
-#define PM_FUEL_GAUGE_R_AGGRESSIVE 3000.0f
-#define PM_FUEL_GAUGE_Q_AGGRESSIVE 0.0002f
-#define PM_FUEL_GAUGE_P_INIT 0.1f
-
// Timeout after which the device automatically transit from suspend to
// hibernation
#define PM_AUTO_HIBERNATE_TIMEOUT_S (2 * 60 * 60) // 2 hours
@@ -65,13 +56,6 @@
#define PM_TEMP_CONTROL_BAND_3_MAX_TEMP 45.0f
#define PM_TEMP_CONTROL_BAND_4_MAX_TEMP 47.0f
-// Power manager battery sampling data structure
-typedef struct {
- float vbat; // Battery voltage [V]
- float ibat; // Battery current [mA]
- float ntc_temp; // NTC temperature [°C]
-} pm_sampling_data_t;
-
// Power manager core driver structure
typedef struct {
bool initialized;
@@ -87,11 +71,7 @@ typedef struct {
bool suspended;
// Fuel gauge
- fuel_gauge_state_t fuel_gauge;
bool fuel_gauge_initialized;
- pm_sampling_data_t bat_sampling_buf[PM_BATTERY_SAMPLING_BUF_SIZE];
- uint8_t bat_sampling_buf_tail_idx;
- uint8_t bat_sampling_buf_head_idx;
uint8_t soc_ceiled;
uint8_t soc_target;
@@ -176,21 +156,9 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report);
// pm_monitor_power_sources() to control the charging current and state.
void pm_charging_controller(pm_driver_t* drv);
-// Battery initial state of charge guess function. This function uses the
-// sampled battery data to guess the initial state of charge in case its
-// unknown.
-void pm_battery_initial_soc_guess(void);
-
// Store power manager data to backup RAM
pm_status_t pm_store_data_to_backup_ram(void);
-// Direct coulomb counter compensation of the SoC based on the battery current,
-// temp and elapsed time, this function is used to compensate the fuel gauge
-// estimation during the periods where the EKF could not be used, such as
-// suspend or hibernation.
-void pm_compensate_fuel_gauge(float* soc, uint32_t elapsed_s,
- float battery_current_mah, float bat_temp_c);
-
// Schedule the RTC wakeup when going into suspend mode.
// Return false if the driver was not initialized or the RTC timestamp is
// not available.
diff --git a/core/embed/io/power_manager/stm32u5/power_monitoring.c b/core/embed/io/power_manager/stm32u5/power_monitoring.c
index 61621712b..125152e62 100644
--- a/core/embed/io/power_manager/stm32u5/power_monitoring.c
+++ b/core/embed/io/power_manager/stm32u5/power_monitoring.c
@@ -29,8 +29,7 @@
#include <sec/telemetry.h>
#endif
-#include "../fuel_gauge/battery_model.h"
-#include "../fuel_gauge/fuel_gauge.h"
+#include "../battery/battery.h"
#include "../stwlc38/stwlc38.h"
#include "power_manager_internal.h"
@@ -38,7 +37,6 @@
static void pm_temperature_controller(pm_driver_t* drv);
#endif
-static void pm_battery_sampling(float vbat, float ibat, float ntc_temp);
static void pm_parse_power_source_state(pm_driver_t* drv);
#ifdef PM_ENABLE_TEMP_CONTROL
@@ -91,17 +89,18 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) {
// Run battery charging controller
pm_charging_controller(drv);
- drv->battery_ocv =
- battery_meas_to_ocv(&drv->fuel_gauge.model, drv->pmic_data.vbat,
- drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
+ drv->battery_ocv = bat_meas_to_ocv(drv->pmic_data.vbat, drv->pmic_data.ibat,
+ drv->pmic_data.ntc_temp);
if (!drv->fuel_gauge_initialized) {
// Fuel gauge not initialized yet, battery SoC not available, sample the
// battery data into the circular buffer.
- pm_battery_sampling(drv->pmic_data.vbat, drv->pmic_data.ibat,
- drv->pmic_data.ntc_temp);
+ bat_fg_feed_sample(drv->pmic_data.vbat, drv->pmic_data.ibat,
+ drv->pmic_data.ntc_temp);
} else {
+ bat_fg_state_t fg_state;
+
if (drv->woke_up_from_suspend) {
#ifdef USE_RTC
@@ -109,8 +108,10 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) {
// estimation during the suspend period. Since this period may be very
// long and the battery temperature may vary, use the average ambient
// temperature.
- pm_compensate_fuel_gauge(&drv->fuel_gauge.soc, drv->time_in_suspend_s,
- PM_SELF_DISG_RATE_SUSPEND_MA, 25.0f);
+
+ bat_fg_get_state(&fg_state);
+ bat_fg_compensate_soc(&fg_state.soc, drv->time_in_suspend_s,
+ PM_SELF_DISG_RATE_SUSPEND_MA, 25.0f);
// TODO: Currently in suspend mode we use single self-discharge rate
// but in practice the discharge rate may change in case some components
@@ -118,9 +119,7 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) {
// mode for limited time, for now we decided to neglect this. but in
// the future we may want to distinguish between different suspend modes
// and use different self-discharge rates.
-
- fuel_gauge_set_soc(&drv->fuel_gauge, drv->fuel_gauge.soc,
- drv->fuel_gauge.P);
+ bat_fg_set_soc(fg_state.soc, fg_state.P);
#endif // USE_RTC
@@ -128,16 +127,16 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) {
drv->woke_up_from_suspend = false;
} else {
- fuel_gauge_update(&drv->fuel_gauge, drv->pmic_sampling_period_ms,
- drv->pmic_data.vbat, drv->pmic_data.ibat,
- drv->pmic_data.ntc_temp);
+ bat_fg_update(drv->pmic_sampling_period_ms, drv->pmic_data.vbat,
+ drv->pmic_data.ibat, drv->pmic_data.ntc_temp);
}
// Charging completed flag from PMIC controller
if (drv->pmic_data.charge_status & 0x2) {
// Force fuel gauge to 100%, keep the covariance
drv->fully_charged = true;
- fuel_gauge_set_soc(&drv->fuel_gauge, 1.0f, drv->fuel_gauge.P);
+ bat_fg_get_state(&fg_state);
+ bat_fg_set_soc(1.0f, fg_state.P);
} else {
if (drv->pmic_data.ibat > 0) {
drv->fully_charged = false;
@@ -145,7 +144,8 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) {
}
// Ceil the float soc to user-friendly integer
- drv->soc_ceiled = (uint8_t)(drv->fuel_gauge.soc_latched * 100 + 0.999f);
+ bat_fg_get_state(&fg_state);
+ drv->soc_ceiled = (uint8_t)(fg_state.soc_latched * 100 + 0.999f);
// Check battery voltage for low threshold
if (drv->soc_ceiled <= PM_BATTERY_LOW_THRESHOLD_SOC && !drv->battery_low) {
@@ -211,13 +211,11 @@ void pm_charging_controller(pm_driver_t* drv) {
} else if (fabsf((-drv->pmic_data.ibat) - (float)drv->i_chg_target_ma) <=
20.0f) {
// Translate SoC target to charging voltage via battery model
- float target_ocv_voltage_v =
- battery_ocv(&drv->fuel_gauge.model, drv->soc_target / 100.0f,
- drv->pmic_data.ntc_temp, false);
+ float target_ocv_voltage_v = bat_soc_to_ocv(drv->soc_target / 100.0f,
+ drv->pmic_data.ntc_temp, false);
- 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);
+ float battery_ocv_v = bat_meas_to_ocv(
+ 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) +
@@ -227,9 +225,9 @@ void pm_charging_controller(pm_driver_t* drv) {
// current voltage is within tight bounds of target voltage,
// we may also force SoC estimate to target value.
if (drv->target_battery_ocv_v_tau < target_ocv_voltage_v + 0.15) {
- fuel_gauge_set_soc(&drv->fuel_gauge,
- (drv->soc_target / 100.0f) - 0.0001f,
- drv->fuel_gauge.P);
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+ bat_fg_set_soc((drv->soc_target / 100.0f) - 0.0001f, fg_state.P);
}
drv->soc_target_reached = true;
@@ -294,30 +292,6 @@ static void pm_temperature_controller(pm_driver_t* drv) {
#endif
-static void pm_battery_sampling(float vbat, float ibat, float ntc_temp) {
- pm_driver_t* drv = &g_pm;
-
- // Store battery data in the buffer
- drv->bat_sampling_buf[drv->bat_sampling_buf_head_idx].vbat = vbat;
- drv->bat_sampling_buf[drv->bat_sampling_buf_head_idx].ibat = ibat;
- drv->bat_sampling_buf[drv->bat_sampling_buf_head_idx].ntc_temp = ntc_temp;
-
- // Update head index
- drv->bat_sampling_buf_head_idx++;
- if (drv->bat_sampling_buf_head_idx >= PM_BATTERY_SAMPLING_BUF_SIZE) {
- drv->bat_sampling_buf_head_idx = 0;
- }
-
- // Check if the buffer is full
- if (drv->bat_sampling_buf_head_idx == drv->bat_sampling_buf_tail_idx) {
- // Buffer is full, move tail index forward
- drv->bat_sampling_buf_tail_idx++;
- if (drv->bat_sampling_buf_tail_idx >= PM_BATTERY_SAMPLING_BUF_SIZE) {
- drv->bat_sampling_buf_tail_idx = 0;
- }
- }
-}
-
static void pm_parse_power_source_state(pm_driver_t* drv) {
// Check USB power source status
if (drv->pmic_data.usb_status != 0x0) {
@@ -345,60 +319,21 @@ static void pm_parse_power_source_state(pm_driver_t* drv) {
}
}
+ bat_fg_state_t fg_state;
+ bat_fg_get_state(&fg_state);
+
// Check battery voltage for critical (undervoltage) threshold
if ((drv->pmic_data.vbat < PM_BATTERY_UNDERVOLT_THR_V) &&
!drv->battery_critical && !drv->usb_connected) {
// Force Fuel gauge to 0, keep the covariance
- fuel_gauge_set_soc(&drv->fuel_gauge, 0.0f, drv->fuel_gauge.P);
+ bat_fg_set_soc(0.0f, fg_state.P);
drv->battery_critical = true;
- } else if (drv->fuel_gauge.soc_latched >=
- (PM_BATTERY_CRITICAL_RECOVERY_SOC) ||
+ } else if (fg_state.soc_latched >= (PM_BATTERY_CRITICAL_RECOVERY_SOC) ||
drv->usb_connected) {
// Restore the battery critical state
drv->battery_critical = false;
}
}
-void pm_battery_initial_soc_guess(void) {
- pm_driver_t* drv = &g_pm;
-
- irq_key_t irq_key = irq_lock();
-
- // Check if the buffer is full
- if (drv->bat_sampling_buf_head_idx == drv->bat_sampling_buf_tail_idx) {
- // Buffer is empty, no data to process
- return;
- }
-
- // Calculate average voltage, current and temperature from the sampling
- // buffer and run the fuel gauge initial guess
- uint8_t buf_idx = drv->bat_sampling_buf_tail_idx;
- uint8_t samples_count = 0;
- float vbat_g = 0.0f;
- float ibat_g = 0.0f;
- float ntc_temp_g = 0.0f;
- while (drv->bat_sampling_buf_head_idx != buf_idx) {
- vbat_g += drv->bat_sampling_buf[buf_idx].vbat;
- ibat_g += drv->bat_sampling_buf[buf_idx].ibat;
- ntc_temp_g += drv->bat_sampling_buf[buf_idx].ntc_temp;
-
- buf_idx++;
- if (buf_idx >= PM_BATTERY_SAMPLING_BUF_SIZE) {
- buf_idx = 0;
- }
-
- samples_count++;
- }
-
- // Calculate average values
- vbat_g /= samples_count;
- ibat_g /= samples_count;
- ntc_temp_g /= samples_count;
-
- fuel_gauge_initial_guess(&drv->fuel_gauge, vbat_g, ibat_g, ntc_temp_g);
-
- irq_unlock(irq_key);
-}
-
#endif
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revA.py b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
index 334648760..0d595cbe5 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revA.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revA.py
@@ -285,8 +285,9 @@ def configure(
"embed/io/power_manager/stm32u5/power_manager.c",
"embed/io/power_manager/stm32u5/power_monitoring.c",
"embed/io/power_manager/stm32u5/power_states.c",
- "embed/io/power_manager/fuel_gauge/fuel_gauge.c",
- "embed/io/power_manager/fuel_gauge/battery_model.c",
+ "embed/io/power_manager/battery/battery.c",
+ "embed/io/power_manager/battery/fuel_gauge.c",
+ "embed/io/power_manager/battery/battery_model.c",
"embed/io/power_manager/stwlc38/stwlc38.c",
"embed/io/power_manager/stwlc38/stwlc38_patching.c",
"embed/io/power_manager/power_manager_poll.c",
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revB.py b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
index 9d10e9a7f..78de3c3d7 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revB.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revB.py
@@ -294,8 +294,9 @@ def configure(
"embed/io/power_manager/stm32u5/power_manager.c",
"embed/io/power_manager/stm32u5/power_monitoring.c",
"embed/io/power_manager/stm32u5/power_states.c",
- "embed/io/power_manager/fuel_gauge/fuel_gauge.c",
- "embed/io/power_manager/fuel_gauge/battery_model.c",
+ "embed/io/power_manager/battery/battery.c",
+ "embed/io/power_manager/battery/fuel_gauge.c",
+ "embed/io/power_manager/battery/battery_model.c",
"embed/io/power_manager/stwlc38/stwlc38.c",
"embed/io/power_manager/stwlc38/stwlc38_patching.c",
"embed/io/power_manager/power_manager_poll.c",
diff --git a/core/site_scons/models/T3W1/trezor_t3w1_revC.py b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
index fc47cdade..f05ed85aa 100644
--- a/core/site_scons/models/T3W1/trezor_t3w1_revC.py
+++ b/core/site_scons/models/T3W1/trezor_t3w1_revC.py
@@ -297,8 +297,9 @@ def configure(
"embed/io/power_manager/stm32u5/power_manager.c",
"embed/io/power_manager/stm32u5/power_monitoring.c",
"embed/io/power_manager/stm32u5/power_states.c",
- "embed/io/power_manager/fuel_gauge/fuel_gauge.c",
- "embed/io/power_manager/fuel_gauge/battery_model.c",
+ "embed/io/power_manager/battery/battery.c",
+ "embed/io/power_manager/battery/fuel_gauge.c",
+ "embed/io/power_manager/battery/battery_model.c",
"embed/io/power_manager/stwlc38/stwlc38.c",
"embed/io/power_manager/stwlc38/stwlc38_patching.c",
"embed/io/power_manager/power_manager_poll.c",
Why this scored 19/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.