Bare Metal STM32 · Part 7 of 7

Low-Power Stop Mode with Wake-Up on EXTI

STM32 Advanced Low Power Source Code
Bare Metal STM32 Part 7
Bare Metal STM32 — Part 7
Low-Power Stop Mode with Wake-Up on EXTI
34:12
Part 7 / 7
34:12
Advanced
STM32F4 · μA range
01

Overview

A bare-metal STM32 sitting in a while(1) loop burns 50–100 mA. Put it in Stop mode and it drops to under 100 μA — a 1000× reduction — while keeping SRAM and register state intact. This final part covers the three STM32 sleep modes, entering Stop mode bare-metal, waking on an EXTI edge, and the one thing that kills every first attempt: reconfiguring the PLL after wakeup.

  • Compare Sleep, Stop, and Standby modes — current draw, wakeup time, and what survives
  • Enter Stop mode using WFI with the main regulator in low-power mode
  • Wake on a falling EXTI edge and immediately reconfigure clocks before any peripheral access
  • Measure current in each mode with a bench supply and verify against the datasheet
02

Sleep Modes Compared

😴
Sleep Mode
CPU clock stopped. All peripherals and SRAM stay powered. Wakes on any interrupt in microseconds. Useful when you're waiting for a DMA or timer interrupt but not doing CPU work.
~10 mA · WFI instruction
💤
Stop Mode ★
All clocks stopped, PLL and HSE off. Only LSI/LSE and RTC can run. SRAM and registers preserved. Wakes on EXTI, RTC alarm, or USB. Must reinitialise PLL on wakeup.
~100 μA · Best trade-off
⚰️
Standby Mode
Nearly everything off. Only RTC and backup registers survive. Wakeup resets the device from the beginning. Use for multi-hour battery applications where wakeup latency is acceptable.
~2 μA · Full reset on wake
03

Code

01
Enter Stop mode, wake on EXTI, restore clocks
low_power.c
C
/* Call this from your main loop when there's nothing to do */
void Enter_StopMode(void) {
    /* 1. Clear any pending wakeup flags */
    PWR->CR |= PWR_CR_CWUF;

    /* 2. Select Stop mode: set PDDS=0 (Stop, not Standby)
          Use low-power regulator to save more current: LPDS=1 */
    PWR->CR &= ~PWR_CR_PDDS;
    PWR->CR |=  PWR_CR_LPDS;

    /* 3. Set SLEEPDEEP so WFI enters a deep sleep mode */
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;

    /* 4. Ensure memory writes complete before sleeping */
    __DSB();

    /* 5. Enter Stop mode — CPU halts here until an EXTI fires */
    __WFI();

    /* 6. Back from Stop — clear SLEEPDEEP immediately */
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;

    /* 7. CRITICAL: Stop mode killed the PLL. Peripherals run on
          HSI (~16 MHz) until we restore the full clock config.
          Do this BEFORE any peripheral access (UART, SPI, etc.) */
    SystemClock_Config();
}

/* EXTI wakeup ISR — fires immediately on wakeup edge */
void EXTI0_IRQHandler(void) {
    if (EXTI->PR & EXTI_PR_PR0) {
        EXTI->PR = EXTI_PR_PR0;  // Clear pending
        g_wakeup_reason = WAKE_EXTI0;
    }
}

/* Main loop pattern */
void main_loop(void) {
    while(1) {
        Do_Work();          // ~5ms active
        Enter_StopMode();   // Sleep until next event
        // Execution resumes here after EXTI fires + clocks restored
        Handle_WakeupReason(g_wakeup_reason);
    }
}
02
Sleep mode — lightweight alternative when only CPU needs to pause
sleep_mode.c
C
/* Sleep mode — much simpler, no clock reconfiguration needed */
void Enter_SleepMode(void) {
    // SLEEPDEEP must be 0 for Sleep (not Stop/Standby)
    SCB->SCR &= ~SCB_SCR_SLEEPDEEP_Msk;
    __DSB();
    __WFI();
    // Returns here on ANY enabled interrupt — no clock restore needed
}
⚠️
Reconfigure clocks immediately after waking from Stop
Stop mode powers down the PLL and HSE. On wakeup the MCU runs on the HSI at 16 MHz. If you access a UART, SPI, or timer before calling SystemClock_Config(), baud rates and timings will be completely wrong — and the bug is nearly impossible to spot without a logic analyser.

Series Complete

You've finished all 7 parts of Bare Metal STM32. Ready to apply these techniques in a real-time system? Start the FreeRTOS Deep Dive series next.