2026-06-15 · embedded · low-power · battery · STM32 · firmware

Low-power firmware: sleep modes, wake sources and measuring µA

A battery device’s lifetime is decided almost entirely by what the firmware does between the useful moments — the 99.9% of the time it should be doing nothing at all. Get that wrong and a coin cell lasts a week; get it right and the same cell lasts years, on the same hardware. The difference is never a faster algorithm; it’s keeping the MCU in its deepest safe sleep for as long as possible, waking only on a real event, and proving it with a real measurement. Let’s make that concrete.

It’s the area under the curve

Plot current against time for a typical sensor node and the shape tells the whole story: brief, tall spikes of activity separated by long, flat stretches of sleep.

A current-versus-time plot on a light energy-dashboard style, with a logarithmic current axis from 1 microamp up to 10 milliamps. The green trace sits at about 2 microamps (Stop mode, RTC on, RAM kept) for long stretches, with three brief tall spikes up to about 10 milliamps (Run, CPU plus radio active), each triggered by an amber RTC-wake marker on the time axis. The area under the curve is lightly shaded to suggest charge. A caption notes battery life is the area under this curve, not the peak — the long microamp stretches dominate, so cut how often you wake and how long you stay up. Figure 1 — The duty-cycle that defines battery life. The peaks look dramatic, but it’s the long µA valleys — and how often you leave them — that drain the cell.

The energy used is the integral of current over time, so on this curve the area is what matters, not the height of any spike. A 10 mA spike that lasts 10 ms costs 0.1 mA·s; sitting at 2 µA for 3 seconds costs 6 µA·s — but you do the second thing all the time. That’s why the two levers that matter are: how deep you sleep between events, and how often and how long you wake. Shaving a wakeup from 50 ms to 5 ms, or from once a second to once a minute, beats almost any optimisation of the active code.

The low-power modes — and what each costs you

Every MCU offers a ladder of sleep modes. Each step down saves more current but turns off more of the chip, so you wake more slowly and lose more state. On STM32 the rungs are Sleep, Stop, Standby and Shutdown:

A light table titled power modes, what you trade for microamps, with a coloured strip down the left going from red at the top to green at the bottom to show decreasing current. Rows: Run — CPU running, all clocks and RAM on, 10 to 50 milliamps. Sleep — CPU halted with WFI, clocks and RAM retained, wakes on any interrupt in about 1 microsecond, 1 to 10 milliamps. Stop — CPU and clocks off, RAM retained, wakes via EXTI or RTC in microseconds to milliseconds, 1 to 10 microamps. Standby — core off, RAM lost except the backup domain, wakes via RTC or a wake pin in milliseconds like a reset, about 1 microamp. Shutdown — everything off including RAM, wakes via wake pin or RTC with a full reset taking milliseconds, nanoamps to microamps. A footnote notes the backup domain and RTC registers survive Standby, and that lower modes wake more slowly and lose more state. Figure 2 — The trade is always the same: deeper modes draw less but keep less alive and wake more slowly. Pick the deepest mode whose retained state and wake latency your application can tolerate.

The decision is mostly about what state you can afford to lose and how fast you must wake:

  • Sleep stops the CPU (WFI/WFE) but leaves clocks and peripherals running, so a DMA transfer or a timer keeps going and any interrupt wakes you in ~1 µs. This is the default “idle” — pair it with DMA so the core sleeps while data moves, and with an RTOS idle hook so it sleeps whenever no task is ready.
  • Stop turns the clocks off but retains RAM and registers, so you resume exactly where you were, woken by an EXTI line or the RTC. This is the workhorse for a battery node: single-digit µA, but your context survives.
  • Standby powers down the core domain — RAM is lost (only the small backup domain and RTC survive). You wake via a wake pin or the RTC, but it’s effectively a reset, so you re-run startup and rebuild state.
  • Shutdown is the deepest: even the regulator is off, nA-class current, wake only from a pin or RTC, full reset on wake.

A good rule: live in Stop between events if you need your state and fast-ish wakeups, and drop to Standby/Shutdown only for very long idle periods where a reset-on-wake is acceptable.

Wake sources: the other half of the design

Sleeping is easy; waking correctly is where designs fail. A device is only as low-power as its wake plan:

  • RTC — the alarm/wakeup timer keeps running in Stop and Standby on a ~µA clock, so it’s how you do periodic work (“wake every 60 s, sample, sleep”).
  • EXTI / GPIO interrupt — wake on an external event (a button, a sensor’s data-ready line, an accelerometer’s motion interrupt). This is what makes event-driven designs beat polling ones: don’t wake to check, let the event wake you.
  • Wake pins — dedicated pins that pull the chip out of Standby/Shutdown.
  • Peripheral wake — a UART or I²C that can wake the CPU on an address match or start bit, so a comms peripheral can stay armed while the core sleeps.

The trap is a stray peripheral or pin left enabled that keeps waking you — or worse, a floating input drawing current. Every µA of “why is it not sleeping?” is usually one forgotten clock, one enabled peripheral, or one pin not configured to a defined state before sleep.

Measuring it — where a multimeter lies

You cannot optimise what you can’t measure, and battery current is genuinely hard to measure because it spans five or six decades — from nA in Shutdown to tens of mA in a radio burst — often within the same second.

A light two-panel diagram. Left panel, average current sets battery life: a duty-cycle inset shows a tall narrow 12 milliamp, 10 millisecond active pulse next to a long thin 3 microamp, 3 second sleep, over one 3-second period. Below, the formula Iavg equals the sum of t times I for active and sleep, divided by the period T, worked through as (0.01 times 12mA plus 2.99 times 0.003mA) over 3, giving 43 microamps average; a highlighted result shows 230 mAh divided by 43 microamps is about 5350 hours or 223 days, noting the 10 ms burst barely matters while the 3 s of sleep does. Right panel, a plain multimeter can't see it: a current scale from nanoamps to amps marks sleep near a few microamps and active near 12 milliamps, about six decades apart, with the note that no single DMM range spans it. A block diagram shows battery into a current analyzer (auto-range, integrate I dt) into the device under test. Bullets warn that a series DMM's burden voltage sags the rail during the mA burst, one range misses either the spikes or the floor, and you should use a PPK2, Joulescope or SMU that logs current over time and sums charge to mAh. Figure 3 — Battery life is set by average current over the whole duty cycle, and measuring it needs an instrument that spans nA to mA and integrates charge — not a single multimeter reading.

What actually matters is the average current over a full duty cycle:

For the example above — a 12 mA, 10 ms burst once every 3 s, sleeping at 3 µA — the average is about 43 µA, so a 230 mAh coin cell lasts roughly 223 days. Notice the active burst contributes almost nothing; the sleep floor dominates. Two consequences for measurement:

  • A series multimeter is the wrong tool. Its burden voltage (the drop across its internal shunt) sags your supply rail exactly during the mA burst, changing the thing you’re measuring; and no single range covers nA and mA, so you either miss the spikes or can’t see the floor.
  • Use a current analyzer — a Nordic Power Profiler Kit, a Joulescope, or a source-measure unit. They auto-range across the decades, log I(t), and integrate charge so you read out actual mAh per hour or per event. Measure the whole cycle, divide charge by time, and that number predicts battery life.

Field notes

  • Optimise the duty cycle before the active code. Halving wake frequency or wake duration beats almost any code optimisation — it’s the area under Figure 1.
  • Default the idle path to the deepest mode you can resume from, usually Stop, and only stay in Sleep when a peripheral must keep running.
  • Configure every pin before sleeping. Floating inputs and forgotten pull-ups are the most common “stuck at hundreds of µA” cause; an enabled but unused peripheral clock is the second.
  • Be event-driven, not polling. Let an EXTI or sensor interrupt wake the MCU instead of waking on a timer to check — that single change often dwarfs everything else.
  • Measure with the right instrument, over a full cycle. A number from a multimeter held during sleep is meaningless; charge-per-cycle from a current analyzer is the truth. Add a wireless radio and this matters even more — TX bursts are the tallest spikes you own.