2022-06-13 02:07 PM - last edited on 2023-09-06 01:03 AM by KDJEM.1
Entering low-power modes, especially the Standby and Shutdown modes, is another mostly software related disaster from ST. Additionally there also seems to be one or two hardware bugs and/or undocumented conditions. This topic provides an example of how to do it correctly and reliably and an explanation of the issues present in the current ST provided code and documentation.
Correct example code based on L43x devices:
void Power_Off(void)
{
// Configure wake-up features
// WKUP1(PA0) - active high, WKUP4(PA2) - active low, pull-up
PWR->PUCRA = PWR_PUCRA_PA2; // Set pull-ups for standby modes
PWR->CR4 = PWR_CR4_WP4; // Set wakeup pins' polarity to low level
PWR->CR3 = PWR_CR3_APC | PWR_CR3_EWUP4 | PWR_CR3_EWUP1; // Enable pin pull configurations and wakeup pins
PWR->SCR = PWR_SCR_CWUF; // Clear wakeup flags
// Configure MCU low-power mode for CPU deep sleep mode
PWR->CR1 |= PWR_CR1_LPMS_STANDBY; // PWR_CR1_LPMS_SHUTDOWN
(void)PWR->CR1; // Ensure that the previous PWR register operations have been completed
// Configure CPU core
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // Enable CPU deep sleep mode
#ifdef NDEBUG
DBGMCU->CR = 0; // Disable debug, trace and IWDG in low-power modes
#endif
// Enter low-power mode
for (;;) {
__DSB();
__WFI();
}
}
Issues and solutions explained:
// Configure wake-up features
...
PWR->SCR = PWR_SCR_CWUF; // Clear wakeup flags
1. Clearing of the wakeup flags must be done as the last operation after configuring wakeup features. When clearing the flags is not the last operation, MCU wakes up immediately after entering the low-power mode. After that, if the firmware will enter the low-power mode again with the same wakeup configuration, then because of some internal hardware logic nuances the second time the MCU will stay in low-power mode and wait for an actual wakeup event.
(void)PWR->CR1; // Ensure that the previous PWR register operations have been completed
2. Because the PWR peripheral is located on APB bus, the values written to the PWR registers take some time (clock cycles) to reach the peripheral. To ensure that the last (and therefore all) of the previous store operations have been completed, a read operation of some PWR register is required. If this is not ensured, the CPU can enter low-power mode before the PWR peripheral is configured.
#ifdef NDEBUG
DBGMCU->CR = 0; // Disable debug, trace and IWDG in low-power modes
#endif
3a. To reach the lowest power consumption, the debug interface must also be disabled. When enabled, it typically results in up to 5 mA of additional current consumption. At least in debug builds the firmware can set these bits to enable debugging with low-power modes or, for example, J-Link debuggers always set these bits after the connection. As these bits are not reset by a system reset and power-on reset can be problematic on some devices, including because of a capacitors of a few uF can retain the voltage for several minutes, it is recommended to always clear those bits manually at least in release builds.
3b. If debug interface is enabled, contrary to the reference manual, IWDG continues running in shutdown mode and resets (and therefore wakes up) the MCU after the configured time. Another way to disable the IWDG in shutdown (and standby) mode is clearing the IWDG_STDBY bit in option bytes.
for (;;) {
__DSB();
__WFI();
}
4. If a debug request or interrupt happens at the same time the WFI instruction is executed, the WFI instruction acts as a NOP instruction. That means the CPU doesn't enter a (deep) sleep mode but instead continues executing the code. If the code relies on the idea that the CPU will not execute the instructions beyond the WFI instruction, it's a bug and the consequences can be unpredictable. As the standby and shutdown modes are designed to stop the code execution after the WFI instruction anyway, the issue can be solved by just putting the WFI instruction in an endless loop. Also before the WFI instruction HAL code uses __force_stores() intrinsic only for ARM compiler, but the __DSB() macro is universal for all compilers and is required by ARM architecture.
I can confirm this behavior on L43x devices, but it seems that it is the same at least for many other parts also. For example, this topic confirms the same for a L0 series device.
Tasks for ST to fix:
2022-06-14 03:16 AM
Would be good if marked a 'Best Reply' - so that people can see that this isn't just another question looking for help...
2022-07-13 04:28 AM
Hi, i'm using STM32WB55CE, there is an issue with IWDG and STOP mode.
As far as I understand IWDG is not running in STOP mode (option byte: 0x3BF9F1AA).
But in fact IWDG triggers reset. I have tried several solutions provided on the forum, non of it helped me.
My latest modification:
void HAL_PWREx_EnterSTOP2Mode(uint8_t STOPEntry)
{
/* Check the parameter */
assert_param(IS_PWR_STOP_ENTRY(STOPEntry));
/* next 2 lines added */
PWR->SCR = PWR_SCR_CWUF; // Clear wakeup flags
(void)PWR->CR1;
/* Set Stop mode 2 */
MODIFY_REG(PWR->CR1, PWR_CR1_LPMS, PWR_LOWPOWERMODE_STOP2);
/* Set SLEEPDEEP bit of Cortex System Control Register */
SET_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
/* added */
#ifdef NDEBUG
DBGMCU->CR = 0; // Disable debug, trace and IWDG in low-power modes
#endif
/* Select Stop mode entry --------------------------------------------------*/
if(STOPEntry == PWR_STOPENTRY_WFI)
{
/* Request Wait For Interrupt */
__DSB(); /* added */
__WFI();
__ISB(); /* added */
}
else
{
/* Request Wait For Event */
__SEV();
__WFE();
__WFE();
}
/* Reset SLEEPDEEP bit of Cortex System Control Register */
CLEAR_BIT(SCB->SCR, ((uint32_t)SCB_SCR_SLEEPDEEP_Msk));
}
Also, there is a note for this function:
Case of Stop2 mode and debugger probe attached: a workaround should be applied.
* Issue specified in "ES0394 - STM32WB55Cx/Rx/Vx device errata":
* 2.2.9 Incomplete Stop 2 mode entry after a wakeup from debug upon EXTI line 48 event
* "With the JTAG debugger enabled on GPIO pins and after a wakeup from debug triggered by an event on EXTI
* line 48 (CDBGPWRUPREQ), the device may enter in a state in which attempts to enter Stop 2 mode are not fully
* effective ..."
* Workaround implementation example using LL driver:
* LL_EXTI_DisableIT_32_63(LL_EXTI_LINE_48);
* LL_C2_EXTI_DisableIT_32_63(LL_EXTI_LINE_48);
I tried to call mentioned functions LL_EXTI... and LL_C2_EXTI... did't help.
What I have missed?
2022-07-20 12:26 PM
This should be discussed in a dedicated topic:
https://community.st.com/s/question/0D53W00001fQYksSAG/iwdg-in-stop2
2022-09-15 06:52 AM
Hello @Piranha,
Thanks for your inputs. This is being investigated by our team. Issue with Cube example is tracked internally.
Internal ticket number: 133854 (This is an internal tracking number and is not accessible or usable by customers).
Mohamed Aymen
2022-10-10 09:09 AM
@Piranha: How to leave the loop?
2022-10-10 10:42 AM
You don't and there is no point in leaving that loop. The WFI instruction puts the MCU in Standby or Shutdown mode, which means that WFI is the last instruction executed. None of these modes resume the execution at the previous code position. Instead exiting from these modes generates a system reset.
2022-10-11 09:14 AM
Sorry, I did not read the "standby" and "shutdown" keywords while I searched for a hint with my problem with stop mode entry on L4 and WL5.Some code that works on L0 never enters Stop mode. As a note: Stop mode continues after WFI with the wake-up event, but system clock will have changed.
2023-07-27 02:03 AM
Excellent! I had a problem with a STM32G491 coming out of standby directly due to debug being enabled.
2024-03-12 12:30 PM
I agree with you that this is another ST software disaster, an why there isn't a standardized power management library is beyond belief!
In my case, I need to enter low-power sleep mode for 2 seconds at a time to check a touch device, but it I use _WFI() then I have to disable a large number of interrupts leaving only the RTC alarm interrupt enabled.
I had though that there was a way to ignore all interrupts apart from the RTC, or use the WUF4 pwr event, but in MX it looks like that's related to voltage level detection.
Is there a better way, or do I have to disable all the other interrupts one by one?