UNIT 14 · LESSON 1 OF 6

Sleep States and Wake-Up Sources

What does it take to make a microcontroller actually sleep, and what wakes it again?

INTERACTIVEWhat each sleep state switches off
Which clocks, oscillators, memories and supplies a sleep state stopsSTM32F4 Stop: SLEEPDEEP = 1CPU clockstoppedperipheral clocksstoppedPLLstoppedmain oscillatorstoppedRTC clockif configuredSRAM contentsretainedcore supplyon or low-powerEnter: HAL_PWR_EnterSTOPMode() sets SLEEPDEEP, then executes WFI or WFE. The PLL, HSIand HSE stop; SRAM and registers are preserved.Wakes on: any EXTI line in interrupt or event mode: a pin, or the RTC alarm, wake-uptimer, tamper or time stamp.Resumes: after the WFI, running from the HSI RC oscillator. Restart the HSE and PLLbefore anything timing-critical.
Which clocks, oscillators, memories and supplies a sleep state stopsSTM32F4 Stop: SLEEPDEEP = 1CPU clockstoppedperipheral clocksstoppedPLLstoppedmain oscillatorstoppedRTC clockif configuredSRAM contentsretainedcore supplyon or low-powerEnter: HAL_PWR_EnterSTOPMode() sets SLEEPDEEP, thenexecutes WFI or WFE. The PLL, HSI and HSE stop; SRAMand registers are preserved.Wakes on: any EXTI line in interrupt or event mode:a pin, or the RTC alarm, wake-up timer, tamper ortime stamp.Resumes: after the WFI, running from the HSI RCoscillator. Restart the HSE and PLL before anythingtiming-critical.

Try this

State
STM32F4 Stop: SLEEPDEEP = 1. Wakes on an EXTI line (pin or RTC event); resumes after the WFI, on the HSI.

A sleep state is a list of things that stop. Plain sleep stops only the CPU clock; deeper states also stop peripheral clocks, the PLL and the oscillators, and the deepest turn off the core supply, losing SRAM. Each step down saves current and costs something: fewer wake-up sources, a slower wake-up, and more to restore afterwards. The rows come from the STM32F4 HAL’s power driver and the RP2040 sleep code in pico-extras; “if configured” means the RTC keeps running only if firmware set it up.

What you will be able to do
  • Explain why current flows in an idle loop and which of clock gating, lower voltage and powering domains down attacks which part of it.
  • Describe what the Cortex-M WFI instruction and the SLEEPDEEP bit do, and compare the STM32F4’s Sleep, Stop and Standby and the RP2040’s sleep and dormant states.
  • Choose a sleep state from the wake-up source an application needs, and list what firmware must restore after waking.
  • Write a sleep sequence that cannot miss an interrupt arriving just before WFI.
  • Compute the break-even idle time above which a deeper sleep saves charge.
Before you start
  • WFI, WFE and the interrupt conditions (unit 9, lesson 1).
  • Clock sources, PLLs and clock trees (unit 8, lesson 1).
Steps in this lesson
  1. Idle is not asleep
  2. A ladder of sleep states
  3. What can wake it
  4. Going to sleep without missing the wake-up
  5. The price of waking up
  6. Worked example: events every 10 ms
  7. Common misconceptions

The puzzle

A sensor node wakes once a minute, reads a sensor for 20 ms and waits again. On the bench its battery should last years; in the field it lasts three weeks. The firmware “waits” in a loop that checks the time until the next reading is due. To the program that is idle. To the battery it is a processor running flat out, doing nothing, all minute long. What does it take to make a microcontroller actually sleep, and what wakes it again?

STEP 1

Idle is not asleep

A CMOS chip draws current in two ways. Every time a node switches, charge moves in and out of its capacitance: the dynamic power

Pdyn≈α C V2fP_{\text{dyn}} \approx \alpha\, C\, V^{2} f

grows with the clock frequency ff, the switched capacitance CC, the fraction α\alpha of it that switches each cycle, and the square of the supply voltage VV. On top of that, transistors that are nominally off still leak: static power, present whenever the supply is on. An idle loop keeps every clock running, so it pays the full dynamic cost for nothing.

Three levers follow from the formula. Stop the clocks that are not needed (dynamic power falls to almost nothing for that block). Lower the voltage where the chip allows it; the pico-sdk’s vreg_set_voltage(), for example, sets the RP2040’s core regulator anywhere from 0.85 V to 1.30 V (1.10 V by default), and the V2V^2 term rewards every step down, though a lower voltage may not support the full clock speed. Switch power off to whole domains, which removes leakage too, at the cost of losing their state. Sleep states are packaged combinations of these levers.

STEP 2

A ladder of sleep states

On a Cortex-M the usual way in is one instruction: WFI (or WFE, unit 9). What the chip does when the core executes it depends on one bit, SLEEPDEEP in the System Control Register (SCB->SCR, bit 2), and on the vendor’s power controller, which decides what “deep” means on that chip. (The RP2040’s dormant state, below, is the exception: a register write enters it.)

↑ This step uses the figure at the top of the page.

The STM32F4 HAL’s power driver lists three states. Sleep (SLEEPDEEP clear) stops the core; peripherals keep running and any interrupt wakes it. Stop (SLEEPDEEP set) stops every clock in the 1.2 V core domain and the PLL and both high-speed oscillators, but keeps SRAM and registers; the regulator stays on, optionally in a low-power mode. Standby turns the core regulator off entirely, so SRAM and registers are lost apart from the RTC, its backup registers and the backup SRAM.

The RP2040 does it differently. It gates clocks through two registers, SLEEP_EN0 and SLEEP_EN1, that say which clocks stay on while the processor is in deep sleep; they reset to all ones, so a deep sleep with them untouched keeps every clock running. The sleep code in the pico-extras library first moves the system onto the crystal or ring oscillator and stops both PLLs, then clears the enables for everything except the RTC’s clock, sets SLEEPDEEP and executes WFI. Its dormant state goes further: writing a magic value to the oscillator’s DORMANT register stops the oscillator itself, and with it every clock derived from it.

STEP 3

What can wake it

The deeper the state, the less of the chip is awake to notice an event, so the list of wake-up sources shrinks:

statewakes onresumes
Sleep (WFI)any enabled interruptafter the WFI
STM32F4 Stopan EXTI line: a pin, or an RTC alarm, wake-up timer, tamper or time stampafter the WFI, on the HSI oscillator
STM32F4 StandbyWKUP pin rising edge, RTC events, the NRST pin, an IWDG resetfrom the reset vector
RP2040 sleepwhatever the clocks left running can raise (the RTC alarm in the SDK example)after the WFI
RP2040 dormanta GPIO edge or level set up as a dormant wake-upafter the oscillator restarts

Choose the state from the event, not the other way round. A UART in Stop mode has no clock, so it cannot receive the byte that should wake the system; the pin can be an EXTI line instead, but that first byte is lost unless the protocol repeats it. And look at what must happen after waking: an STM32 leaving Stop runs from its internal RC oscillator until firmware restarts the crystal and PLL, so a UART or timer configured for the PLL clock runs at the wrong rate until then. Standby is not a pause at all: the chip restarts, and the start-up code must look at the standby flag (PWR_FLAG_SB) to know it is waking rather than powering up.

Pins need thought too. In Sleep and Stop the STM32F4 keeps every pin in its state; in Standby most pins are high-impedance, so an output that held a MOSFET off now floats, and only an external pull resistor (unit 1, lesson 3) keeps it defined.

STEP 4

Going to sleep without missing the wake-up

The obvious idle loop has a race:

while (!data_ready) {   /* the interrupt sets data_ready */
    __WFI();            /* if the interrupt fires between the test and here, */
}                       /* the core sleeps until the NEXT interrupt          */

If the interrupt arrives after the test but before WFI, its handler runs, sets the flag, and returns; the core then sleeps with the flag already set and nothing left to wake it. The fix relies on a property of WFI from unit 9: it wakes on a pending interrupt even when PRIMASK masks it. Mask interrupts, test, sleep, then unmask, so the handler runs after the wake-up instead of in the gap:

__disable_irq();        /* PRIMASK = 1: interrupts pend but do not run */
if (!data_ready)
    __WFI();            /* still wakes when the interrupt becomes pending */
__enable_irq();         /* the handler runs here */

FreeRTOS’s tickless idle for the Cortex-M0 does exactly this. When every task is blocked for at least a couple of ticks, vPortSuppressTicksAndSleep() executes cpsid i (the comment explains that its usual critical-section macro would mask the interrupts that must end the sleep), checks that no task has become ready, reprograms SysTick to fire at the end of the expected idle time instead of every tick, and only then executes wfi. Without tickless idle a 1 kHz tick would wake the chip a thousand times a second for nothing.

For designs that do everything in interrupt handlers, SLEEPONEXIT (SCR bit 1) sends the core back to sleep automatically on return from the last handler, without returning to the main loop at all.

STEP 5

The price of waking up

A deeper state saves current while asleep, but waking from it costs time and charge: the crystal must restart, the PLL must lock, a regulator coming out of low-power mode must settle (the STM32 HAL notes the extra start-up delay when the regulator is in low-power mode during Stop). If the chip draws IwI_w for a wake-up time twt_w, then over an idle gap TT deep sleep beats a light sleep drawing IlI_l only when

T>T∗=tw Iw−IdIl−IdT > T^{*} = t_w\,\frac{I_w - I_d}{I_l - I_d}

where IdI_d is the deep-sleep current. The wake-up time also adds straight onto the response time, which may rule a state out regardless of the charge.

Operating systems encode the same trade-off. Each power state in Zephyr declares a minimum residency and an exit latency, and its residency policy enters the state that saves the most, provided the time to the next scheduled event is at least their sum. It also reminds the application that setting up a wake-up event is its job, and that not every wake-up source works in every power mode.

INTERACTIVEWhen is a deeper sleep worth it?
Average current over an idle gap for light and deep sleep, with the break-even gap0.1 ms1 ms10 ms0.1 s1 s10 s10 µA0.1 mA1 mA10 mAorange: light sleep; teal: deep sleep, averaged over the gap; shaded: gap shorter thanthe wake-upbreak-even gap = wake time × (wake current − deep current) / (light current − deepcurrent) = 5.03 msDeep sleep averages 1.02 mA over this gap against 2 mA for light sleep: 49.2 % less.
Average current over an idle gap for light and deep sleep, with the break-even gap0.1 ms1 ms10 ms0.1 s1 s10 s10 µA0.1 mA1 mA10 mAorange: light sleep; teal: deep sleep, averaged overthe gap; shaded: gap shorter than the wake-upbreak-even gap = wake time × (wake current − deepcurrent) / (light current − deep current) = 5.03 msDeep sleep averages 1.02 mA over this gap against 2mA for light sleep: 49.2 % less.
Light-sleep current
Break-even gap 5.03 ms; at 10 ms: light 2 mA, deep 1.02 mA.

Waking from a deep sleep is not free: the oscillator and PLL must restart and the core supply must settle, and all that time the chip draws current without doing useful work. Averaged over one idle gap, deep sleep costs its sleep current plus the wake-up charge spread over the gap. For short gaps a light sleep, which wakes almost instantly, uses less. The currents are illustrative: 20 µA in deep sleep and 5 mA during the wake-up; measure your own part.

STEP 6

Worked example: events every 10 ms

A device must handle an event every 10 ms. Assume (illustratively) a light sleep at Il=2I_l = 2 mA that wakes instantly, and a deep sleep at Id=20I_d = 20 µA that takes tw=2t_w = 2 ms at Iw=5I_w = 5 mA to wake.

The break-even gap is

T∗=2 ms×5−0.022−0.02=2 ms×2.515≈5.03 msT^{*} = 2\ \text{ms} \times \frac{5 - 0.02}{2 - 0.02} = 2\ \text{ms} \times 2.515 \approx 5.03\ \text{ms}

so the 10 ms gap is long enough. Averaged over one gap, deep sleep draws 0.02+4.98×2/10=1.0160.02 + 4.98 \times 2/10 = 1.016 mA against 2 mA: about half. With a 1 s gap the wake-up is spread thinner and deep sleep averages 0.02+4.98×0.002≈0.0300.02 + 4.98 \times 0.002 \approx 0.030 mA, some 67 times less than light sleep. With a 3 ms gap it would average 3.34 mA, worse than not bothering. And if each event had to be answered within 1 ms, the 2 ms wake-up rules deep sleep out whatever the charge.

MYTHS AND FACTS

Common misconceptions

WFI switches the chip off

It stops the core’s clock. Peripherals, oscillators and PLLs keep running unless SLEEPDEEP and the vendor’s power controller stop them.

A deeper sleep always saves energy

Only for idle gaps longer than the break-even time; below it the wake-up costs more than it saves.

After waking, the program carries on as before

From Stop the STM32 runs on its RC oscillator until the clocks are rebuilt; from Standby it starts again from reset.

Any interrupt will wake it

Only sources that are still clocked and routed to the wake-up logic: EXTI lines in Stop, a few pins and the RTC in Standby, configured GPIO in RP2040 dormant.

Check yourself

Answer in your head, then open the card.

With Il=1I_l = 1 mA, Id=10I_d = 10 µA, Iw=4I_w = 4 mA and tw=1t_w = 1 ms, what is the break-even gap?

T∗=1 ms×(4−0.01)/(1−0.01)=3.99/0.99≈4.03T^{*} = 1\ \text{ms} \times (4 - 0.01) / (1 - 0.01) = 3.99 / 0.99 \approx 4.03 ms.

Code calls HAL_PWR_EnterSTOPMode() and, after waking, prints at 115 200 baud; the text is garbled. Why?

On leaving Stop the STM32F4 runs from the HSI oscillator, not the PLL. The UART’s divisor was computed for the PLL-derived clock, so its baud rate is wrong until firmware restores the clock configuration.

Why does the safe idle sequence disable interrupts before testing the flag, and why does that not stop WFI waking?

Masking closes the window in which an interrupt could set the flag between the test and WFI. WFI wakes on any pending interrupt of sufficient priority even while PRIMASK masks it, so the core still wakes; the handler then runs as soon as interrupts are re-enabled.

An RP2040 enters deep sleep with SLEEPDEEP set but the current barely changes. What was probably forgotten?

The SLEEP_EN0/SLEEP_EN1 registers, which reset to all ones and so keep every clock running in deep sleep; and the PLLs, which the pico-extras code stops by moving to the crystal or ring oscillator first.

Sources (7)
  1. STMicroelectronics, stm32f4xx-hal-driver, Src/stm32f4xx_hal_pwr.c and Inc/stm32f4xx_hal_pwr.h — “Sleep mode: Cortex-M4 core stopped, peripherals kept running. Stop mode: all clocks are stopped … Standby mode: 1.2V domain powered off”; Stop exits on “Any EXTI Line (Internal or External)”, and “the HSI RC oscillator is selected as system clock”; in Standby “SRAM and register contents are lost except for the RTC registers, RTC backup registers, backup SRAM”; HAL_PWR_EnterSTOPMode sets SCB_SCR_SLEEPDEEP then executes WFI or WFE; in Sleep and Stop “all I/O pins keep the same state as in Run mode”, in Standby “all I/O pins are high impedance” except a listed few; pwr.h: PWR_FLAG_SB “indicates that the system was resumed from StandBy mode”
  2. Raspberry Pi Ltd, pico-extras (sdk-1.5.1 tag), src/rp2_common/pico_sleep/sleep.c — “The difference between sleep and dormant is that ALL clocks are stopped in dormant mode”; sleep_run_from_dormant_source() moves clk_ref and clk_sys to the XOSC or ROSC and calls pll_deinit(); sleep_goto_sleep_until() writes sleep_en0 = CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS, sleep_en1 = 0, sets M0PLUS_SCR_SLEEPDEEP_BITS and calls __wfi(); dormant wake-up through gpio_set_dormant_irq_enabled(). The master branch adds sleep_power_up() to restore the clocks after waking, and its include/pico/sleep.h notes that dormant wake-up by the RTC needs the RTC “driven by an external clock”.
  3. Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_xosc/xosc.c and rp2040/hardware_regs/include/hardware/regs/clocks.h — xosc_dormant(): “WARNING: This stops the xosc until woken up by an irq”, then waits for XOSC_STATUS_STABLE; CLOCKS_SLEEP_EN0 “enable clock in sleep mode”, reset value 0xffffffff (SLEEP_EN1 0x00007fff)
  4. Raspberry Pi Ltd, pico-sdk 1.5.1, hardware_vreg/include/hardware/vreg.h — enum vreg_voltage from VREG_VOLTAGE_0_85 to VREG_VOLTAGE_1_30; VREG_VOLTAGE_DEFAULT = VREG_VOLTAGE_1_10, “Default voltage on power up”
  5. Arm, CMSIS 6, CMSIS/Core/Include/core_cm0plus.h — SCB->SCR bits SEVONPEND (bit 4), SLEEPDEEP (bit 2) and SLEEPONEXIT (bit 1)
  6. Zephyr Project, doc/services/pm/system.rst “System Power Management” — “It is the application's responsibility to set up a wake-up event … some wake-up sources may not be usable in all power modes”; the idle thread locks interrupts before pm_system_suspend(); residency policy: enter a state only if “time_to_next_scheduled_event >= (state.min_residency_us + state.exit_latency)”
  7. FreeRTOS-Kernel, portable/GCC/ARM_CM0/port.c and include/FreeRTOS.h — vPortSuppressTicksAndSleep() (configUSE_TICKLESS_IDLE = 1): “cpsid i” rather than taskENTER_CRITICAL(), “as that will mask interrupts that should exit sleep mode”; aborts if eTaskConfirmSleepModeStatus() says so; reprograms SysTick for the expected idle time, then dsb, wfi, isb and “cpsie i”; configEXPECTED_IDLE_TIME_BEFORE_SLEEP defaults to 2 and must not be less than 2