Skip to main content
Graduate II
June 13, 2022
Question

How to enter Standby or Shutdown mode on STM32

  • June 13, 2022
  • 13 replies
  • 33005 views

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:

  • 1, 3b - Potential hardware bugs and could be closely related. Must be checked/confirmed and, depending on that, should be documented in erratas and/or reference manuals.
  • 2, 3a, 4 - Should be documented (reminded) in reference manuals.
  • 2, 4 - HAL functions HAL_PWR_EnterSTANDBYMode() and HAL_PWREx_EnterSHUTDOWNMode() should be fixed.
  • 1, 3 - Code examples should be fixed.
    This topic has been closed for replies.

    13 replies

    Visitor II
    September 6, 2024

    What if i wanted to switchoff(shutdown) only core2 where wireless stack is running and wakeup from button interrupt.
    I used MCU stm32wb5mmgh6tr.
    If you can help then provide the code sequence for that or any hint.

    Graduate II
    February 9, 2025

    Somehow the sleep loop:

    // Enter low-power mode
    for (;;) {
     __DSB();
     __WFI();
    }

    isn't really working for me (other tips do!), it just stays in a forever loop doing anything but sleeping (standby). So because it doesn't sleep, the wakeup pin isn't working as well. Putting a max of 50 for-loops also didn't made any difference.

    I 'solved' it by putting a sleep value in the RTC backup register, doing a HAL_NVIC_SystemReset(); reset after the failed __WFI(); and doing a proper sleep routine again (checking the state of the RTC backup register). It succeeds standby sleep after restart. 

    Annoyingly I'm not able to find the culprit, as I disabled all interrupts I could find in my code before sleeping. It didn't work. I'm using the STM32WB55, with a little bit of change. Don't use BLE.

    stm_log(&huart1, LOG_VERBOSE, "Going to standby mode");
    
    PWR->CR3 &= ~PWR_CR3_EWUP4; // Disable wakeup pin 4
    PWR->PDCRB = 0x00000030; // Set pulldown for PA2
    PWR->CR2 |= PWR_CR3_APC; // Enable pull-up and pull-down configuration for CPU1
    PWR->CR4 &= ~PWR_CR4_WP4; // Set wakeup pin 4 polarity to high
    PWR->CR3 |= PWR_CR3_EWUP4; // Enable wakeup pin 4
    
    PWR->SCR = PWR_SCR_CWUF; // Clear wakeup flags
    PWR->SCR |= PWR_SCR_CWUF4; // Double, to be removed
     
    PWR->CR1 |= PWR_CR1_LPMS_1 | PWR_CR1_LPMS_0; // Set low-power mode to Standby
    (void)PWR->CR1; // Ensure that the previous PWR register operations have been completed
     
    PWR->C2CR1 |= PWR_C2CR1_LPMS_2; // Set low-power mode to Shutdown
    (void)PWR->C2CR1; // Ensure that the previous PWR register operations have been completed
    
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk; // Enable CPU deep sleep mode
    //DBGMCU->CR = 0; // Disable debug, trace and IWDG in low-power modes
    
    stm_log(&huart1, LOG_VERBOSE, "%s", LOG_LOCATION);
    //for (;;) {
    //	 __DSB();
    	__WFI();
    //}
    
    //This should never be reached...
    HAL_Delay(1000);
    stm_log(&huart1, LOG_VERBOSE, "Fault standby mode");
    HAL_Delay(1000);
    HAL_NVIC_SystemReset(); // Reset the CPU

     

    Graduate II
    March 20, 2025

    Hello @Piranha, thank you fI or posting your solution. We are experiencing have the  That is exactly the problem we experience with the STM32G0B1RET MCU:

    During normal operation pin PC4 is used in a UART1 configuration (with an external pull-up resistor). In Standby Mode pin PC4 must be LOW in order to shut down the external device. Initially I used the function HAL_PWR_EnterSTANDBYMode(). However, when called it consistently pulls the output UP, not down.

    I adopted your code listed in this post and adjusted it for Wakeup pin 6 and for output pin PC4. See below:

    The output PC4 is reconfigured as a GPIO pin. also is pulled HI. However, the output PC4 is also pulled HI when entering Standby Mode. (Only in Debug mode PC4 remains at LOW, although that may have different reasons.)

    Please comment. What else can we try? Thank you!!

    /* Convert UART1 IO to a GPIO */
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    HAL_GPIO_WritePin(UART1_GPIO_Port, UART1_Pin, GPIO_PIN_RESET);
    
    /*Configure GPIO pin : UART1_Pin */
    GPIO_InitStruct.Pin = UART1_Pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(UART1_GPIO_Port, &GPIO_InitStruct);
    
    PWR->PDCRC = PWR_PDCRC_PD4; // Set pull-ups for standby modes
    PWR->CR4 = PWR_CR4_WP6; // Set wakeup pin 6 to low level
    PWR->CR3 = PWR_CR3_APC | PWR_CR3_EWUP6; // 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_LOWPOWERMODE_STANDBY;
    (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
    
    // Enter low-power mode
    for (;;) {
     __DSB();
     __WFI();
    }