From c9acae7b97a9e81ee1a2b9e939150e616efaacd5 Mon Sep 17 00:00:00 2001 From: kopecdav Date: Mon, 30 Jun 2025 15:50:29 +0200 Subject: [PATCH] feat(core): fuel gauge compensation in hibernation mode. [no changelog] --- core/embed/projects/bootloader/main.c | 8 +- .../sys/power_manager/stm32u5/power_manager.c | 86 +++++++++++++++++-- .../stm32u5/power_manager_internal.h | 17 +++- .../power_manager/stm32u5/power_monitoring.c | 18 +++- 4 files changed, 112 insertions(+), 17 deletions(-) diff --git a/core/embed/projects/bootloader/main.c b/core/embed/projects/bootloader/main.c index 3c7b0a402e..a491dcd782 100644 --- a/core/embed/projects/bootloader/main.c +++ b/core/embed/projects/bootloader/main.c @@ -125,6 +125,10 @@ static secbool boot_sequence(secbool manufacturing_mode) { haptic_init(); #endif +#ifdef USE_RTC + rtc_init(); +#endif + #ifdef USE_POWER_MANAGER pm_init(false); @@ -245,10 +249,6 @@ static void drivers_init(secbool manufacturing_mode, tamper_init(); #endif -#ifdef USE_RTC - rtc_init(); -#endif - #ifndef LAZY_DISPLAY_INIT display_touch_init(manufacturing_mode, touch_initialized); #endif diff --git a/core/embed/sys/power_manager/stm32u5/power_manager.c b/core/embed/sys/power_manager/stm32u5/power_manager.c index 20c533175f..053a01b0c2 100644 --- a/core/embed/sys/power_manager/stm32u5/power_manager.c +++ b/core/embed/sys/power_manager/stm32u5/power_manager.c @@ -107,6 +107,27 @@ pm_status_t pm_init(bool inherit_state) { irq_key_t irq_key = irq_lock(); if (recovery_ok) { +#ifdef USE_RTC + + // RTC compensation should happen only during initialization in bootloader + if (!inherit_state) { + // Get RTC timestamp and compare it with the timestamp from recovery data + // to estimate time off and compensate self-discharge of the battery. + uint32_t rtc_timestamp; + if (recovery.last_capture_timestamp != 0 && + rtc_get_timestamp(&rtc_timestamp)) { + // 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); + } + } + } + +#endif + fuel_gauge_set_soc(&drv->fuel_gauge, recovery.soc, recovery.P); } else { pm_battery_initial_soc_guess(); @@ -256,6 +277,13 @@ pm_status_t pm_suspend(wakeup_flags_t* wakeup_reason) { wakeup_flags_t wakeup_flags = system_suspend(); + // Wait for pmic measurements to stabilize the fuel gauge estimation. + pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS); + if (status != PM_OK) { + // timeout during state machine stabilization + return false; + } + // TODO: Handle wake-up flags // UNUSED(wakeup_flags); @@ -443,6 +471,13 @@ pm_status_t pm_store_data_to_backup_ram() { recovery.bat_critical = drv->battery_critical; recovery.bootloader_exit_state = drv->state; +#ifdef USE_RTC + if (!rtc_get_timestamp(&recovery.last_capture_timestamp)) { + // If RTC timestamp cannot be obtained, set it to 0 + recovery.last_capture_timestamp = 0; + } +#endif + irq_unlock(irq_key); bool write_ok = @@ -532,6 +567,33 @@ bool pm_driver_suspend(void) { return false; } + irq_key_t irq_key = irq_lock(); + + if (drv->woke_up_from_suspend) { + // Driver just woke up from suspend and have no data available yet. + // Request the suspend but wait for the next pmic_meausrement + drv->suspending = true; + + } else { + pm_schedule_rtc_wakeup(); + drv->suspended = true; + } + + // Delete the monitoring timer to stop the periodic sampling + systimer_delete(drv->monitoring_timer); + + irq_unlock(irq_key); + + return true; +} + +bool pm_schedule_rtc_wakeup(void) { + pm_driver_t* drv = &g_pm; + + if (!drv->initialized) { + return false; + } + #ifdef USE_RTC // Capture the timestamp when device was active for the last time. @@ -570,6 +632,7 @@ bool pm_driver_suspend(void) { systimer_delete(drv->monitoring_timer); irq_unlock(irq_key); + drv->suspended = true; return true; } @@ -580,6 +643,12 @@ bool pm_driver_resume(void) { return false; } + if (!drv->suspended) { + // Already resumed, nothing to do + return true; + } + + drv->suspended = false; drv->woke_up_from_suspend = true; #ifdef USE_RTC @@ -603,18 +672,18 @@ bool pm_driver_resume(void) { // Set the periodic sampling period systimer_set_periodic(drv->monitoring_timer, PM_BATTERY_SAMPLING_PERIOD_MS); - // Wait for pmic measurements to stabilize the fuel gauge estimation. - pm_status_t status = pm_wait_to_stabilize(drv, PM_STABILIZATION_TIMEOUT_MS); - if (status != PM_OK) { - // timeout during state machine stabilization - return false; - } return true; } bool pm_driver_is_suspended(void) { - // No specific pending tasks, just return true - return true; + pm_driver_t* drv = &g_pm; + + bool suspended; + irq_key_t irq_key = irq_lock(); + suspended = drv->suspended; + irq_unlock(irq_key); + + return suspended; } void pm_compensate_fuel_gauge(float* soc, uint32_t elapsed_s, @@ -623,7 +692,6 @@ void pm_compensate_fuel_gauge(float* soc, uint32_t elapsed_s, bool discharging_mode = battery_current_ma >= 0.0f; *soc -= (compensation_mah / battery_total_capacity(bat_temp_c, discharging_mode)); - return; } static pm_status_t pm_wait_to_stabilize(pm_driver_t* drv, uint32_t timeout_ms) { diff --git a/core/embed/sys/power_manager/stm32u5/power_manager_internal.h b/core/embed/sys/power_manager/stm32u5/power_manager_internal.h index 279ba629a1..261613b50e 100644 --- a/core/embed/sys/power_manager/stm32u5/power_manager_internal.h +++ b/core/embed/sys/power_manager/stm32u5/power_manager_internal.h @@ -40,8 +40,8 @@ #define PM_BATTERY_CHARGING_CURRENT_MIN PMIC_CHARGING_LIMIT_MIN #define PM_BATTERY_SAMPLING_BUF_SIZE 10 -#define PM_SELF_DISG_RATE_HIBERATION_MAH 0.004f -#define PM_SELF_DISG_RATE_SUSPEND_MAH 0.032f +#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 2000.0f @@ -70,6 +70,14 @@ typedef struct { bool state_machine_stabilized; pm_power_status_t state; + // Set if the driver was requested to suspend background operations. + // IF so, the driver waits until the last operation is finished, + // then enters suspended mode. + bool suspending; + + // Set if the driver's background operations are suspended. + bool suspended; + // Fuel gauge fuel_gauge_state_t fuel_gauge; bool fuel_gauge_initialized; @@ -156,3 +164,8 @@ pm_status_t pm_store_data_to_backup_ram(void); // 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. +bool pm_schedule_rtc_wakeup(void); diff --git a/core/embed/sys/power_manager/stm32u5/power_monitoring.c b/core/embed/sys/power_manager/stm32u5/power_monitoring.c index 160e7f4e45..4ba1f4720c 100644 --- a/core/embed/sys/power_manager/stm32u5/power_monitoring.c +++ b/core/embed/sys/power_manager/stm32u5/power_monitoring.c @@ -78,10 +78,17 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) { } else { // Use known battery self-discharge rate to compensate the fuel gauge // estimation during the suspend period. Since this period may be very - // long and the batery temperature may vary, use the average ambient + // 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_MAH, 25.0f); + PM_SELF_DISG_RATE_SUSPEND_MA, 25.0f); + + // TODO: Currently in suspend mode we use single self-discharge rate + // but in practive the discharge rate may change in case the BLE chip + // remains active. Since the device is very likely to stay in suspend + // mode for limited time, for now we decided to neglect this. but in + // the future we may want to distinguish between suspend mode + // with/without BLE and use different self-discharge rates. } fuel_gauge_set_soc(&drv->fuel_gauge, drv->fuel_gauge.soc, @@ -117,6 +124,13 @@ void pm_pmic_data_ready(void* context, pmic_report_t* report) { pm_process_state_machine(); pm_store_data_to_backup_ram(); + + if (drv->suspending) { + pm_schedule_rtc_wakeup(); + drv->suspending = false; + drv->suspended = true; + } + drv->state_machine_stabilized = true; } }