STM32 Low-Power Modes: Sleep, Stop and Standby with HAL Examples
A microcontroller running a while (1) loop at full speed draws current all the time, even when it has nothing to do. For a battery-powered sensor node that takes one reading per minute, the CPU might be idle 99.9 percent of the time, and that idle current decides whether the battery lasts days or years. STM32 chips offer several low-power modes that switch off progressively more of the chip and wake up on chosen events.
This tutorial explains the three classic modes of the STM32F4, Sleep, Stop and Standby, and demonstrates all three on a Nucleo-F401RE in one program. You will also learn how to restore the clock after Stop mode, how to detect a wake-up from Standby, and how to measure the current on the board.
The Three Modes Compared
| Mode | What stops | What keeps running | Typical wake-up sources | After wake-up |
|---|---|---|---|---|
| Sleep | CPU clock only | All peripherals, SRAM, clocks | Any enabled interrupt | Continues after the WFI instruction |
| Stop | All clocks in the 1.2 V domain, PLL, HSI, HSE | SRAM and register contents, LSE/LSI, RTC | Any EXTI line (pins, RTC alarm, RTC wakeup) | Continues after WFI, running on HSI |
| Standby | Almost everything; 1.2 V domain powered off | Backup domain (RTC, backup registers), WKUP pin logic | WKUP pin rising edge, RTC alarm or wakeup, NRST, IWDG | Full reset; SRAM contents lost |
The deeper the mode, the lower the current and the more work it takes to resume. Sleep saves the least but is almost free to use. Stop saves a great deal while keeping all variables. Standby saves the most, but the chip effectively restarts from main() when it wakes. The exact current figures depend on voltage, temperature and regulator settings; look them up in the electrical characteristics section of the STM32F401 datasheet rather than relying on rough numbers.
Hardware Setup
- Sleep and Stop wake-up: the blue B1 button on PC13, configured as an EXTI falling-edge interrupt.
- Standby wake-up: the WKUP1 pin, which is PA0 (Arduino A0) on the STM32F401. A rising edge wakes the chip; touch a jumper wire from PA0 to 3V3 briefly. Do not configure PA0 for anything else in CubeMX.
- Current measurement: the Nucleo-F401RE has an IDD jumper (JP6) in the supply of the microcontroller. Remove it and connect a multimeter in current mode across its two pins to measure only the MCU current.
- USART2 prints which mode is being entered, so keep the virtual COM port open at 115200 baud.
CubeMX Configuration
- Start from the Nucleo-F401RE board template. Confirm PC13 is GPIO_EXTI13 with falling edge, and enable EXTI line[15:10] interrupts in the NVIC.
- Keep USART2 and LD2 (PA5) as they are.
- Optional but recommended for real products: in the pinout view, set unused pins to Analog. Floating digital inputs can draw extra current in low-power modes.
- Generate the code.
Demonstrating Sleep, Stop and Standby
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* USER CODE BEGIN 0 */
int _write(int file, char *ptr, int len)
{
(void)file;
HAL_UART_Transmit(&huart2, (uint8_t *)ptr, (uint16_t)len, HAL_MAX_DELAY);
return len;
}
static void blink(uint32_t times)
{
for (uint32_t i = 0; i < times * 2U; i++)
{
HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin);
HAL_Delay(150);
}
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);
}
/* B1 interrupt: only needed to wake the CPU, nothing else to do */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
(void)GPIO_Pin;
}
/* USER CODE END 0 */
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
/* Did we just come out of Standby? SB flag survives the wake-up reset */
if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)
{
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);
printf("\r\nWoke up from STANDBY (program restarted)\r\n");
}
else
{
printf("\r\nNormal power-on or reset\r\n");
}
/* USER CODE END 2 */
while (1)
{
/* USER CODE BEGIN 3 */
/* ---------- 1. SLEEP: CPU stops, peripherals keep running ---------- */
blink(3);
printf("Entering SLEEP, press B1 to wake\r\n");
HAL_SuspendTick(); /* otherwise the 1 ms SysTick wakes us at once */
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
HAL_ResumeTick();
printf("Woke from SLEEP\r\n");
/* ---------- 2. STOP: clocks off, RAM kept, wake by EXTI ---------- */
blink(3);
printf("Entering STOP, press B1 to wake\r\n");
HAL_SuspendTick();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
/* Execution resumes here on HSI 16 MHz: rebuild the 84 MHz clock */
SystemClock_Config();
HAL_ResumeTick();
printf("Woke from STOP, clock restored to %lu Hz\r\n", SystemCoreClock);
/* ---------- 3. STANDBY: lowest power, wake = reset ---------- */
blink(3);
printf("Entering STANDBY, touch PA0 to 3V3 to wake\r\n");
HAL_Delay(20); /* let the UART finish */
HAL_PWR_DisableWakeUpPin(PWR_WAKEUP_PIN1);
__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU); /* clear any old wake-up flag */
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
HAL_PWR_EnterSTANDBYMode();
/* Never reached: wake-up from Standby restarts at main() */
/* USER CODE END 3 */
}
}
Why Each Step Is Needed
- HAL_SuspendTick: the HAL's SysTick interrupt fires every millisecond. In Sleep mode any interrupt wakes the CPU, so without suspending it the chip would sleep for less than 1 ms.
- PWR_SLEEPENTRY_WFI: the core executes Wait For Interrupt. The alternative, WFE, waits for an event and is used with event-mode EXTI lines.
- PWR_LOWPOWERREGULATOR_ON: in Stop mode the internal regulator can switch to its low-power state, giving lower current at the cost of a slightly longer wake-up time.
- SystemClock_Config after Stop: the PLL and HSE are switched off in Stop mode, and the chip wakes on the 16 MHz HSI. Without reconfiguring, UART baud rates and timer periods are wrong.
- Clearing PWR_FLAG_WU: if the wake-up flag is already set when you enter Standby, the chip wakes immediately. Clearing it first avoids this.
- PWR_FLAG_SB at start-up: this flag tells you the reset came from Standby. Use it to skip slow start-up work or to continue a measurement cycle stored in backup registers.
Designing a Battery-Powered Node
A typical pattern combines these modes with the RTC from the previous tutorial: wake from Standby on an RTC wakeup timer, read a sensor, send the data, store a counter in a backup register, and return to Standby. Average current then depends mostly on how long the chip stays awake, so keep the awake phase short and turn off external modules, such as sensors and radios, with a GPIO-controlled MOSFET before sleeping.
Troubleshooting
- Debugger disconnects when the chip enters Stop or Standby: expected. Call
HAL_DBGMCU_EnableDBGStopMode()andHAL_DBGMCU_EnableDBGStandbyMode()while developing, and remove them for real current measurements. - Cannot reflash after a low-power program: use Connect under reset in the debug configuration.
- Sleep returns immediately: another interrupt, such as a UART or timer, is still enabled and firing.
- Garbled UART text after Stop:
SystemClock_Config()was not called after waking. - Standby wakes instantly: the WU flag was not cleared, or PA0 is already high.
Summary
Sleep stops only the CPU, Stop freezes the clocks while keeping RAM, and Standby powers down almost everything and restarts the program on wake-up. You entered each mode with the HAL, suspended the SysTick, restored the PLL clock after Stop, and detected a Standby wake-up with the SB flag. Next, you will structure larger programs as separate tasks using FreeRTOS.