Back to Technology

STM32 Part 10: Low-Power Modes

March 31, 2026 Wasil Zafar 28 min read

Shipping a battery-powered product means understanding exactly what draws current in your MCU — from which peripheral clocks are running to which RAM banks stay alive in Stop mode. Master every STM32 power mode.

Table of Contents

  1. STM32 Power Architecture
  2. Sleep Mode
  3. Stop Mode
  4. Standby Mode
  5. RTC Wakeup Timer
  6. LPUART & Low-Power Peripherals
  7. Power Profiling Techniques
  8. Exercises
  9. Low-Power Configuration Canvas
  10. Conclusion & Next Steps
Series Overview: This is Part 10 of our 18-part STM32 Unleashed series. We now shift focus from interrupt management to power optimisation — the skill that separates a prototype from a shippable battery-powered product.

STM32 Unleashed: HAL Driver Development

Your 18-step learning path • Currently on Step 10
1
Architecture & CubeMX Setup
STM32 family, clock tree, HAL vs LL, CubeMX workflow, first project
Completed
2
GPIO & Button Debounce
GPIO modes, pull-up/down, EXTI, software debounce, HAL_GPIO_ReadPin
Completed
3
UART Communication
Polling, interrupt, DMA modes, printf retargeting, ring buffers
Completed
4
Timers, PWM & Input Capture
TIM basics, PWM generation, input capture, encoder mode
Completed
5
ADC & DAC
Single/continuous conversion, DMA, injected channels, DAC waveforms
Completed
6
SPI Protocol
SPI master/slave, full-duplex, DMA transfers, sensor drivers
Completed
7
I2C Protocol
I2C master, 7/10-bit addressing, DMA, multi-master, error handling
Completed
8
DMA & Memory Efficiency
DMA streams, circular mode, memory-to-memory, zero-copy patterns
Completed
9
Interrupt Management & NVIC
Priority grouping, preemption, ISR design, HAL callbacks, latency
Completed
10
Low-Power Modes
Sleep, Stop, Standby modes, RTC wakeup, LP UART, power profiling
You Are Here
11
RTC & Calendar
RTC configuration, alarms, backup registers, calendar subseconds
12
CAN Bus
FDCAN/bxCAN, filters, message frames, error handling, automotive use
13
USB CDC Virtual COM Port
USB FS/HS, CDC class, virtual serial, control transfers, descriptors
14
FreeRTOS Integration
Tasks, queues, semaphores, mutexes, CMSIS-RTOS2 wrapper, stack sizing
15
Bootloader Development
Custom IAP bootloader, UART/USB DFU, flash programming, jump-to-app
16
External Storage: SD & QSPI Flash
FATFS on SD card, QSPI NOR flash, memory-mapped execution, wear levelling
17
Ethernet & TCP/IP Stack
LwIP integration, DHCP, TCP server, HTTP, MQTT, Ethernet DMA descriptors
18
Production Readiness
Watchdog, HardFault handler, flash option bytes, code signing, CI/CD

STM32 Power Architecture

Before you can optimise power consumption you need to understand the physical power domains that make up your STM32. Each domain can be independently enabled or disabled, and they interact in ways that determine which peripherals survive a low-power transition — and which are wiped back to reset state.

Voltage Domains on STM32F4/L4/U5

The STM32 is not a single monolithic power rail. It is partitioned into several distinct voltage domains, each with a specific purpose and a different lifetime in low-power modes:

  • VDD (1.71 V – 3.6 V): The main supply domain. Powers the CPU core, SRAM, flash, and most peripherals. Removing VDD resets the entire device (except the backup domain).
  • VDDA (1.71 V – 3.6 V): Analog supply, typically tied to VDD but may be independently filtered. Powers the ADC, DAC, comparators, and internal voltage reference. Noise on VDDA appears directly in ADC readings.
  • VBAT (1.65 V – 3.6 V): Battery backup domain. Powers the RTC, RTC backup registers, and the 32.768 kHz oscillator. Remains active even when VDD is fully removed — provided a coin cell or supercap is connected. On STM32L4/H7, also powers the backup SRAM.
  • VDDIO (bank-specific): I/O voltage for GPIO ports. On devices with multiple I/O banks (e.g. STM32H7), different banks can operate at different voltages (3.3 V and 1.8 V simultaneously), enabling direct interfacing with mixed-voltage systems.

Voltage Regulator Scaling

The STM32F4 and L4 families include an internal LDO voltage regulator that supplies the 1.2 V digital core. This regulator supports multiple operating scales, allowing you to trade clock speed for current consumption:

  • Scale 1 (highest performance): Full core voltage (~1.2 V). Required for maximum clock speeds (168 MHz on F4, 80 MHz on L4). Dynamic current ~100 mA at full load.
  • Scale 2 (balanced): Reduced core voltage (~1.05 V). Limits clock to 84 MHz on F4 / 26 MHz on L4. Typical current reduced by 20–30% compared to Scale 1.
  • Scale 3 (lowest power, L4/U5 only): Minimum core voltage (~1.0 V). Maximum clock 26 MHz (L4) or 16 MHz (U5). Enables the deepest Stop mode variants. Best for duty-cycled sensor applications.

Power Consumption Formula

Understanding the physics of MCU power consumption allows you to predict and optimise rather than guess:

Dynamic power (switching): Pdyn = C × V2 × f — where C is the effective capacitance being switched each cycle, V is supply voltage, and f is operating frequency. This is why reducing both voltage (via scaling) and frequency gives a quadratic benefit for voltage and linear for frequency.

Static power (leakage): Pstatic = Ileakage × V — always present regardless of clock activity. Dominates in Stop and Standby modes. Leakage increases exponentially with temperature, which is why specifying a current target at 85°C matters for industrial designs.

Power Mode CPU Clocks RAM Retention Wake Sources Typical Current (STM32L4)
Run Active All on Full N/A 5–35 mA (scale/freq dependent)
Sleep Halted Peripherals on Full Any interrupt 1–5 mA
Low-Power Sleep Halted Low-speed only Full Any interrupt 200–500 µA
Stop 0 Off HSI/HSE off, LSE on Full SRAM EXTI, LPUART, LPTIM, RTC ~100 µA
Stop 1 Off Most clocks off Full SRAM EXTI, LPUART, RTC ~10–30 µA
Stop 2 Off Most clocks off, LDO low-power Full SRAM EXTI, LPUART, RTC alarm ~1–5 µA
Standby Off All off except VBAT domain VBAT registers only WKUP pin, RTC alarm, NRST ~0.5–3 µA
Shutdown Off All off including LDO None (BKP regs cleared) WKUP pin, NRST ~30–50 nA
/* Set voltage scaling to Scale 2 for reduced current at 84 MHz (STM32F4) */
/* Must be called before changing SYSCLK — scaling change requires flash wait
   state update if reducing, or flash wait state reduction after if increasing */

#include "stm32f4xx_hal.h"

void set_voltage_scale_2(void)
{
    /* Enable Power Control Clock — required before writing PWR registers */
    __HAL_RCC_PWR_CLK_ENABLE();

    /* Switch to Scale 2: core voltage ~1.05 V, max clock 84 MHz */
    __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

    /* Wait until the voltage scaling is effective */
    while (!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) { }

    /* Now safe to configure PLL for 84 MHz operation */
    /* HCLK = 84 MHz, APB1 = 42 MHz, APB2 = 84 MHz                   */
    /* Flash wait states: 2 WS @ 84 MHz, VCC 2.7–3.6 V (ref manual)  */
    __HAL_FLASH_SET_LATENCY(FLASH_LATENCY_2);
}

/* Companion: read current VOS setting for diagnostic logging */
uint32_t get_voltage_scale(void)
{
    return (PWR->CR & PWR_CR_VOS_Msk) >> PWR_CR_VOS_Pos;
    /* Returns 0x01 = Scale 3, 0x02 = Scale 2, 0x03 = Scale 1 */
}

Sleep Mode

Sleep mode is the lowest-effort power optimisation available — and often the most effective for interrupt-driven applications that spend most of their time waiting for events. The CPU clock is gated off, but all peripherals (DMA, timers, UART, ADC) continue running exactly as configured. The moment any interrupt fires, the CPU wakes, executes the ISR, and returns to sleep.

Entry and Wake Sources

There are two instructions that put the CPU to sleep: WFI (Wait For Interrupt) and WFE (Wait For Event). HAL wraps both:

  • WFI: CPU sleeps until any pending interrupt with sufficient NVIC priority is raised. This is the most common choice for application code.
  • WFE: CPU sleeps until an event signal is raised. Events include interrupts (when SEVONPEND is set) but also hardware events like DMA transfer complete or inter-processor signals on dual-core devices. Useful for fine-grained synchronisation without triggering the full interrupt handler.
  • Return from sleep: Execution resumes at the instruction immediately following the WFI/WFE. All registers, peripherals, and SRAM contents are intact.

Sleep-on-Exit Pattern

The Cortex-M Sleep-on-Exit feature (SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk) causes the CPU to automatically re-enter sleep mode after returning from every interrupt handler. This eliminates the overhead of the main-loop WFI call and is ideal for applications where all work happens in ISRs — such as a UART data logger where the only CPU activity is triggered by received characters.

Current Measurement Tip: To verify Sleep mode is actually reducing current, toggle a GPIO pin just before WFI and just after (in the ISR). Use a current probe overlaid with a logic analyser trace to confirm the current drops precisely when the GPIO goes low. The reduction should be visible even on a cheap USB multimeter in µA mode.
/* Sleep mode with UART RX wakeup — interrupt-driven byte processing */
/* Target: STM32F4 Nucleo board, USART2 at 115200 baud               */

#include "stm32f4xx_hal.h"

extern UART_HandleTypeDef huart2;
volatile uint8_t uart_rx_byte;
volatile uint8_t uart_rx_ready = 0;

/* Start single-byte UART receive in interrupt mode */
void uart_start_receive(void)
{
    HAL_UART_Receive_IT(&huart2, (uint8_t *)&uart_rx_byte, 1);
}

/* Called from HAL after each byte received */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == USART2)
    {
        uart_rx_ready = 1;          /* flag for main loop */
        uart_start_receive();       /* re-arm single-byte receive */
    }
}

/* Main loop: sleep between bytes */
void run_sleep_uart_loop(void)
{
    uart_start_receive();           /* arm first receive */

    /* Enable Sleep-on-Exit so CPU sleeps after every ISR return */
    /* Remove this line if main loop also needs to do work */
    /* SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk; */

    while (1)
    {
        /* Enter Sleep mode — CPU stops, UART interrupt will wake it */
        HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);

        /* CPU wakes here after UART interrupt fires and ISR completes */
        if (uart_rx_ready)
        {
            uart_rx_ready = 0;
            process_byte(uart_rx_byte);
        }
    }
}

static void process_byte(uint8_t b)
{
    /* Application-specific: echo back, accumulate into buffer, etc. */
    HAL_UART_Transmit(&huart2, &b, 1, HAL_MAX_DELAY);
}

Stop Mode

Stop mode is the most commonly used low-power mode for real applications because it offers a dramatic current reduction (down to 1–5 µA on STM32L4) while retaining all SRAM contents and peripheral register state. The CPU and most high-speed clocks are stopped, but the Cortex-M core itself is not reset — it simply picks up execution at the instruction after the Stop entry call when a wakeup event arrives.

Stop 0, Stop 1, and Stop 2

On the STM32L4, L5, and U5 families, Stop mode has three sub-variants that offer progressively lower power at the cost of longer wakeup latency and fewer available wakeup sources:

Variant Main Regulator Low-Power Reg Wakeup Latency Peripherals Active Typical Current
Stop 0 On (MR) Off ~2 µs LPUART, LPTIM, I2C, comparators ~80–130 µA
Stop 1 Off On (LP) ~5 µs LPUART, LPTIM, I2C wakeup ~10–30 µA
Stop 2 Off On (LP, reduced) ~10 µs LPUART, RTC alarm only ~1–5 µA

Clock Restoration After Stop

This is the most common bug when first implementing Stop mode: when the MCU wakes from Stop, it restores HSI as the system clock, regardless of your original clock configuration (HSE + PLL at 80 MHz). If you don't re-run SystemClock_Config(), your UART baud rate will be wrong by the ratio of HSI to your original SYSCLK, every SPI transaction will run at the wrong speed, and every HAL_Delay will be off by the same factor.

Critical: Always call SystemClock_Config() (or your custom clock restoration function) immediately after returning from HAL_PWR_EnterSTOPMode() or HAL_PWREx_EnterSTOP2Mode(). Failing to do so is the single most common Stop mode bug in production firmware.
/* Stop 2 mode on STM32L4 with RTC wakeup timer — full implementation */
#include "stm32l4xx_hal.h"

extern RTC_HandleTypeDef hrtc;

/* Called by application to enter Stop 2 for a specified number of seconds */
void enter_stop2_rtc_wakeup(uint32_t wakeup_seconds)
{
    /* Step 1: Configure RTC wakeup timer (see Section 5 for full detail)   */
    /* Uses RTCCLK/16 prescaler — 2 Hz ticks, period = wakeup_seconds/2    */
    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc,
                                 (uint32_t)(wakeup_seconds * 2 - 1),
                                 RTC_WAKEUPCLOCK_RTCCLK_DIV16);

    /* Step 2: Suspend SysTick to avoid spurious wakeup every 1 ms          */
    HAL_SuspendTick();

    /* Step 3: Enter Stop 2 — CPU halts here until RTC wakeup fires         */
    HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);

    /* ---- WAKEUP POINT ---- CPU resumes here after RTC event ----         */

    /* Step 4: Restore system clock (HSI started automatically on wakeup)  */
    SystemClock_Config();           /* re-configure PLL, flash latency, etc */

    /* Step 5: Re-enable SysTick for HAL_Delay and HAL_GetTick              */
    HAL_ResumeTick();

    /* Step 6: Clear wakeup timer to prevent immediate re-trigger            */
    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
}

/* RTC wakeup interrupt callback — called from HAL RTC IRQ handler          */
void HAL_RTCEx_WakeUpTimerEventCallback(RTC_HandleTypeDef *hrtc)
{
    /* Minimal ISR body — application code runs after Stop returns           */
    /* You may set a flag here if needed for post-wakeup dispatch            */
    UNUSED(hrtc);
}

Standby Mode

Standby mode is the deepest power mode that retains the RTC and backup registers. The main power regulator is switched off, all SRAM contents are lost, all peripheral configurations are reset, and the MCU will re-execute startup code (just like a reset) when it wakes. The only state that survives is what you explicitly save in the 20–32 backup registers or in Backup SRAM (H7/L4).

Entry, Wake Sources, and the Standby Flag

Before entering Standby, your application must serialise all important state to persistent storage. The wake process is indistinguishable from a reset at the hardware level — the difference is a single flag bit (PWR_FLAG_SB) that your startup code checks to decide whether to perform a full initialisation or a fast restore:

  • WKUP pin (PA0 by default): Rising edge on the WKUP pin triggers wakeup. Multiple WKUP pins available on some devices (WKUP1–WKUP5).
  • RTC alarm: Alarm A or Alarm B can trigger wakeup from Standby. This is the most common choice for periodic data loggers.
  • RTC wakeup timer: The same periodic timer used in Stop mode works from Standby.
  • RTC tamper or timestamp event: Useful for security-sensitive applications.
  • NRST pin: A hardware reset always wakes the device regardless of power mode.

Backup SRAM on H7/L4

The STM32H7 and STM32L4 include a 4 KB (H7) or 2 KB (L4) Backup SRAM that is directly connected to the VBAT domain. Unlike the 20–32 backup registers (which hold 32-bit values), Backup SRAM is addressable memory — you can store arbitrary structs, ring buffers, and measurement arrays there. Access requires enabling the DBP bit (disable backup domain write protection) in PWR->CR1.

/* Standby mode: save state, enter, detect wakeup, restore (STM32L4) */
#include "stm32l4xx_hal.h"

extern RTC_HandleTypeDef hrtc;

/* Magic word stored in backup register to detect dirty shutdown */
#define STANDBY_MAGIC_WORD  0xDEADBEEFU
#define BKP_REG_MAGIC       RTC_BKP_DR0
#define BKP_REG_BOOT_COUNT  RTC_BKP_DR1
#define BKP_REG_LAST_SENSOR RTC_BKP_DR2

/* On boot: check if we woke from Standby or had a cold reset */
void check_standby_and_restore(void)
{
    if (__HAL_PWR_GET_FLAG(PWR_FLAG_SB) != RESET)
    {
        /* Woke from Standby — restore state from backup registers */
        __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);

        uint32_t boot_count = HAL_RTCEx_BKUPRead(&hrtc, BKP_REG_BOOT_COUNT);
        uint32_t last_value = HAL_RTCEx_BKUPRead(&hrtc, BKP_REG_LAST_SENSOR);

        HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_BOOT_COUNT, boot_count + 1);

        log_info("Standby wakeup #%lu, last sensor = %lu", boot_count, last_value);
    }
    else
    {
        /* Cold reset — initialise backup registers */
        HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_MAGIC,      STANDBY_MAGIC_WORD);
        HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_BOOT_COUNT, 0);
        HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_LAST_SENSOR, 0);
        log_info("Cold reset — backup registers initialised");
    }
}

/* Enter Standby with RTC wakeup in 60 seconds */
void enter_standby_60s(uint32_t sensor_reading)
{
    /* Save current sensor value to backup register before power-off */
    HAL_RTCEx_BKUPWrite(&hrtc, BKP_REG_LAST_SENSOR, sensor_reading);

    /* Configure RTC wakeup at 60 second interval */
    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
    HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, 59, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);

    /* Clear Standby and Wakeup flags */
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_WU);
    __HAL_PWR_CLEAR_FLAG(PWR_FLAG_SB);

    /* Enable WKUP1 pin (PA0) as additional wakeup source */
    HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);

    /* Enter Standby — this call does not return; MCU resets on wakeup */
    HAL_PWR_EnterSTANDBYMode();
}

RTC Wakeup Timer

The RTC wakeup timer is the primary mechanism for periodic wakeup from both Stop and Standby modes. It is part of the RTC subsystem, powered from VBAT, and can operate at a wide range of intervals using two different clock sources:

  • RTCCLK/2, /4, /8, /16: Direct division of the RTC clock (typically 32.768 kHz from LSE). Gives fine-grained resolution — at RTCCLK/2 (16.384 kHz ticks), the minimum period is ~61 µs and maximum is ~4 seconds. Useful for high-frequency wakeups in Stop 0/1.
  • ck_spre (1 Hz derived clock): The 1 Hz calendar clock. The wakeup counter counts down from a 16-bit value, giving periods of 1 second to 65535 seconds (~18 hours). The RTC_WAKEUPCLOCK_CK_SPRE_16BITS flag uses this with a 17th MSB for periods up to 131072 seconds (~36 hours).

The RTC wakeup timer generates an interrupt that exits Stop mode or causes a wakeup event in Standby. The callback HAL_RTCEx_WakeUpTimerEventCallback() is called from the IRQ handler after each wakeup event.

/* Configure RTC wakeup timer for 10-second intervals (Stop mode wakeup) */
/* Requires RTC to be initialised with LSE and prescalers set for 1 Hz ck_spre */
#include "stm32l4xx_hal.h"

extern RTC_HandleTypeDef hrtc;

HAL_StatusTypeDef rtc_configure_10s_wakeup(void)
{
    HAL_StatusTypeDef status;

    /* Deactivate any existing wakeup timer configuration */
    status = HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
    if (status != HAL_OK) return status;

    /* Set wakeup period:
     *   Clock source: ck_spre (1 Hz) — one tick per second
     *   Counter value: 9 → wakeup after 9+1 = 10 seconds
     *   HAL_RTCEx_SetWakeUpTimer_IT arms the timer and enables the interrupt */
    status = HAL_RTCEx_SetWakeUpTimer_IT(
                 &hrtc,
                 9U,                                  /* 10 s - 1 */
                 RTC_WAKEUPCLOCK_CK_SPRE_16BITS);     /* 1 Hz clock */
    if (status != HAL_OK) return status;

    /* Verify the timer is armed before entering Stop */
    /* CR WUTE bit should be 1 */
    if (!(hrtc.Instance->CR & RTC_CR_WUTE))
    {
        return HAL_ERROR;
    }

    return HAL_OK;
}

/* Application wake-measure-sleep loop */
void periodic_wakeup_loop(void)
{
    while (1)
    {
        /* 1. Perform measurement (sensor read, UART transmit, etc.) */
        float temperature = read_i2c_temperature();
        transmit_uart_float(temperature);

        /* 2. Re-arm 10-second wakeup timer */
        if (rtc_configure_10s_wakeup() != HAL_OK) Error_Handler();

        /* 3. Enter Stop 2 — resumes here after 10 seconds */
        HAL_SuspendTick();
        HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);
        SystemClock_Config();
        HAL_ResumeTick();
    }
}

LPUART & Low-Power Peripherals

Stop mode does not mean complete communication blackout. The STM32L4/L5/U5 families include several peripherals specifically designed to operate in Stop mode, enabling the MCU to wake autonomously when data arrives or a condition is met:

  • LPUART1 (Low-Power UART): Can remain active in Stop 0, Stop 1, and Stop 2 (with restrictions in Stop 2). Operates from LSE (32.768 kHz) or HSI. Supports baud rates down to 1200 baud from LSE. Wakes the MCU on character received, idle line detection, or address match. Ideal for wireless module command interfaces (HC-05, ESP8266 AT commands).
  • LPTIM1/LPTIM2 (Low-Power Timers): Independent timers that run from LSE, LSI, or HSI16 in Stop mode. Use for periodic wakeup instead of RTC if you need sub-second precision with less RTC initialisation overhead. LPTIM supports input capture and pulse counting even in Stop mode.
  • I2C3 (in some Stop levels): On STM32L4, I2C3 can operate in Stop 0/1 to wake the MCU when a specific I2C address is received. Enables sensor-push architectures where the sensor (e.g. ADXL345 interrupt) initiates the transaction.
  • Comparators COMP1/COMP2: Analog comparators powered from VDDA, operational in Stop mode. Used for threshold wakeup without an ADC — compare an analog voltage to an internal VREFINT fraction and wake on crossing.
/* LPUART1 wakeup from Stop mode on received character (STM32L4) */
/* LPUART1 must be clocked from LSE (32.768 kHz) for Stop mode operation */
#include "stm32l4xx_hal.h"

extern UART_HandleTypeDef hlpuart1;
volatile uint8_t lp_rx_byte;

/* Configure LPUART1 wakeup — call once after LPUART init */
void lpuart_configure_stop_wakeup(void)
{
    UART_WakeUpTypeDef wakeup_config = {0};

    /* Wake on any received character (address-mark mode disabled) */
    wakeup_config.WakeUpEvent = UART_WAKEUP_ON_READDATA_NONEMPTY;

    HAL_UARTEx_StopModeWakeUpSourceConfig(&hlpuart1, wakeup_config);

    /* Enable LPUART ability to wake CPU from Stop mode */
    HAL_UARTEx_EnableStopMode(&hlpuart1);

    /* Arm the receive interrupt so we get a callback on wakeup */
    HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&lp_rx_byte, 1);
}

/* Main loop: sleep in Stop 1, wake on LPUART character */
void lpuart_sleep_loop(void)
{
    lpuart_configure_stop_wakeup();

    while (1)
    {
        /* Enter Stop 1 (LPUART active in Stop 1 on L4) */
        HAL_SuspendTick();
        HAL_PWREx_EnterSTOP1Mode(PWR_STOPENTRY_WFI);

        /* Woke up — restore clocks */
        SystemClock_Config();
        HAL_ResumeTick();

        /* Process received byte if LPUART triggered the wakeup */
        /* Re-arm is handled in HAL_UART_RxCpltCallback below  */
    }
}

/* Callback fires after LPUART byte received (also in Stop wakeup path) */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    if (huart->Instance == LPUART1)
    {
        process_lpuart_byte(lp_rx_byte);
        /* Re-arm for next byte */
        HAL_UART_Receive_IT(&hlpuart1, (uint8_t *)&lp_rx_byte, 1);
    }
}

Power Profiling Techniques

The theory is clear; the practice reveals surprises. A firmware that should draw 3 µA in Stop 2 often draws 300 µA in reality — because of a floating GPIO, an enabled peripheral clock that was never used, or the SWD debug interface holding the device in a high-current state. Systematic profiling is non-negotiable before shipping a battery-powered design.

Measurement Hardware

  • Series resistor shunt (0.1 Ω): Insert a 0.1 Ω resistor in the VDD supply line. Measure voltage across it with a precision DMM or oscilloscope. Resolution: ~1 µA/µV. Cheap but limited dynamic range.
  • INA219/INA226 current sensor: I2C current/power monitor with 12-bit ADC. Can be left on the PCB permanently for field diagnostics. Typical measurement range: 0–3.2 A, resolution ~100 µA. Not suitable for measuring sub-µA Standby current.
  • Nordic PPK2 (Power Profiler Kit II): Dedicated embedded power profiler, 100 nA resolution, 1 kHz–1 MHz sampling. Covers the full range from Shutdown (~30 nA) to Run mode (100 mA). The go-to tool for L4/U5 power optimisation. The companion desktop app gives current trace overlaid with time markers.
  • Otii Arc: Similar to PPK2, widely used in IoT design. Includes automated battery life calculation from current trace.

Software-Aided Current Profiling

Toggle a GPIO pin (connected to a logic analyser channel) at every state transition. Overlay the GPIO trace with the current trace from your power profiler — the correlation reveals exactly which code section is responsible for each current spike. This technique costs nothing beyond a spare GPIO and is invaluable during bring-up.

Action Current Saving Implementation
Disable unused peripheral clocks (RCC) 50–500 µA per peripheral __HAL_RCC_TIM6_CLK_DISABLE() etc.
Configure unused GPIO as input/no-pull 5–50 µA per floating pin Set all unused pins to input, GPIO_NOPULL
Disable SWD debug interface 100–500 µA DBGMCU_CR = 0 or use disable_debug_peripherals()
Use voltage scaling (Scale 2/3) 20–40% dynamic power HAL_PWREx_ControlVoltageScaling(Scale2)
Replace SysTick with LPTIM 10–100 µA Use LPTIM as FreeRTOS tick source in Stop
Use MSI clock at 100 kHz in run 90%+ dynamic reduction RCC_OscInitTypeDef with MSI Range 0
Disable flash prefetch and cache ~200 µA at low clock FLASH_ACR: PRFTEN=0, ICEN=0, DCEN=0
/* Disable debug peripherals before entering low-power modes */
/* Critical: SWD/JTAG interface and trace hardware can draw significant current */
#include "stm32l4xx_hal.h"

void disable_debug_peripherals(void)
{
    /* Disable DWT (Data Watchpoint and Trace) — used by profilers */
    CoreDebug->DEMCR &= ~CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CTRL        &= ~DWT_CTRL_CYCCNTENA_Msk;

    /* Disable ITM (Instrumentation Trace Macrocell) — SWO output */
    ITM->LAR  = 0xC5ACCE55U;   /* unlock key */
    ITM->TCR  = 0U;             /* disable ITM trace control */
    ITM->TER  = 0U;             /* disable all stimulus ports */

    /* Power-gate the Debug block via DBGMCU (also stops debug clock) */
    /* WARNING: after this call, stepping in the debugger will fail   */
    /* Only call this in release/production builds                     */
    __HAL_RCC_DBGMCU_CLK_ENABLE();
    DBGMCU->CR = 0U;            /* clear all debug enable bits */
    __HAL_RCC_DBGMCU_CLK_DISABLE();

    /* Disable trace pins (PA13/PA14 for SWDIO/SWDCLK if not needed) */
    /* Note: disabling SWD prevents re-flashing via SWD — use UART/USB DFU */
    /* GPIO_InitTypeDef g = {GPIO_PIN_13|GPIO_PIN_14, GPIO_MODE_ANALOG,
                             GPIO_NOPULL, GPIO_SPEED_FREQ_LOW, 0};
       HAL_GPIO_Init(GPIOA, &g);  // Uncomment ONLY in final production build */
}

Exercises

Beginner Exercise 1: Sleep Mode Current Measurement

Implement Sleep mode on a Nucleo board. Toggle an LED every 500 ms using the SysTick interrupt, entering WFI between ticks. Measure current with a multimeter in µA mode (insert the meter in series with the VDD supply, not the LED). Record the current during Sleep (between ticks) and during the active window (ISR + LED toggle). Compare to a busy-wait loop (while(1) { HAL_GPIO_TogglePin(...); HAL_Delay(500); }) — you should see a factor of 10–100x improvement in the Sleep version.

Success criteria: Sleep-mode current < 2 mA on an STM32F4 Nucleo at 168 MHz with SysTick as the only running peripheral. Busy-wait current > 50 mA.

Intermediate Exercise 2: Wake-Measure-Sleep Datalogger

Build a wake-measure-sleep loop on an STM32L4/L5/U5 Nucleo board. The sequence: enter Stop 2 for 10 seconds (via RTC wakeup timer), wake up, read an I2C temperature/humidity sensor (e.g. SHT31 or HDC1080), send the reading over UART, re-enter Stop 2. Measure the average current over a 1-minute window using a Nordic PPK2 or a current shunt and oscilloscope. Compute the expected battery life for a 3000 mAh LiPo.

Success criteria: Average current < 10 µA over a 60-second measurement window. The current trace should show a distinct pulse (~3–5 mA) during the active phase (~50 ms) and a flat baseline (~2–3 µA) during Stop 2.

Advanced Exercise 3: Sub-5-µA Battery Data Logger Optimisation

Starting from a 2 mA baseline (HAL default configuration with all peripheral clocks enabled, debug interface active, MSI at 4 MHz), systematically reduce average current while logging BME280 environmental data every 60 seconds over LPUART to a host PC. Apply all optimisations: disable every unused peripheral clock individually (verify each saves current with a PPK2 trace), configure all unused GPIO as input/no-pull, switch to voltage Scale 3, replace SysTick with LPTIM for the wakeup counter, disable DWT/ITM, and use MSI clock at 100 kHz in the active window. Target: average current < 5 µA over a 5-minute window. Document each optimisation step and its measured current saving in a table.

Success criteria: Final average current < 5 µA. A 1000 mAh AA cell should theoretically last more than 22 years at this current level (1000 mAh / 0.005 mA = 200,000 h). Verify your calculation and discuss real-world derating factors (self-discharge, temperature, voltage cut-off).

Low-Power Configuration Canvas

Use this canvas to design your STM32 low-power strategy. Fill in your project parameters, then export as Word, Excel, PDF, or PowerPoint for design review documentation.

Conclusion & Next Steps

Low-power mastery is one of the most valuable skills in embedded systems engineering — and one that separates engineers who prototype from engineers who ship. In this article we have covered the full power-mode stack on STM32:

  • Power architecture: Voltage domains (VDD, VDDA, VBAT, VDDIO), internal LDO voltage scaling (Scale 1/2/3), and the physics of dynamic vs static power consumption.
  • Sleep mode: Lowest-effort optimisation — CPU clock gated, all peripherals continue. Interrupt-driven applications can achieve 10–100x current reduction over busy-wait with a single WFI instruction.
  • Stop mode (0/1/2): The most practical low-power mode for real applications. SRAM retained, peripherals reset on wakeup, clock must be restored manually. Stop 2 achieves 1–5 µA on STM32L4 with RTC wakeup.
  • Standby mode: Maximum power saving with SRAM lost. State must be serialised to backup registers (up to 32 × 32-bit) or Backup SRAM before entry. Boot code must check the standby flag to distinguish wakeup from cold reset.
  • RTC wakeup timer: The standard mechanism for periodic wakeup from both Stop and Standby modes. Configurable from 1-second to 18-hour intervals.
  • LPUART & low-power peripherals: LPUART, LPTIM, comparators, and I2C can remain active in Stop mode for event-driven wakeup without sacrificing connectivity.
  • Power profiling: Systematic GPIO-toggle + current-trace methodology identifies the real sources of excess current. Disable debug hardware, configure unused GPIO, and disable unused peripheral clocks before claiming a current figure.

Next in the Series

In Part 11: RTC & Calendar, we'll go deep on the STM32 Real-Time Clock — configure the calendar with LSE/LSI, read sub-second timestamps from the SSR register, implement Alarm A and Alarm B with flexible mask configurations, use the 20–32 backup registers as persistent non-volatile storage, detect tamper events, and apply smooth frequency calibration to achieve <1 second/day drift.

Technology