
Every milliampere matters when your device runs on a coin cell. Battery-powered embedded systems — from wearable health monitors to remote environmental sensors — must squeeze months or years of operation from a limited energy budget. The key to achieving this lies not in any single optimization, but in a layered approach: understanding hardware sleep modes, leveraging RTOS-level tickless idle, gating peripheral clocks dynamically, and making smart trade-offs between wake-up latency and power savings.
This article walks through the practical power management techniques that embedded engineers should master, with concrete examples on ARM Cortex-M processors and FreeRTOS.
Every ARM Cortex-M microcontroller implements at least three architecturally defined power modes: Run, Sleep, and Deep Sleep. The mode entered when the processor idles is controlled by the SLEEPDEEP bit in the System Control Register (SCB->SCR).
| Mode | CPU Clock | Peripheral Clocks | Typical Current |
|---|---|---|---|
| Run | Active | Active | 10-30 mA |
| Sleep (WFI) | Stopped | Active | 3-10 mA |
| Deep Sleep | Stopped | Configurable | 1-50 uA |
Two instructions trigger entry into these modes:
If SLEEPDEEP = 0, WFI enters Sleep mode (CPU clock stopped, peripherals alive). If SLEEPDEEP = 1, WFI enters Deep Sleep (CPU clock stopped, system clock and optionally PLL and flash turned off). Many vendors extend these with additional modes — NXP Kinetis-L parts, for instance, offer Very Low-Leakage Sleep drawing nanoamps.
The deeper you sleep, the more you save — but wake-up time also increases. Some deep sleep modes require as long to resume as a full boot sequence. This latency-power trade-off is the central design decision in low-power firmware.
For devices that spend most of their time waiting for events (sensor readings, button presses, radio packets), the Sleep-On-Exit feature is ideal. When SLEEPONEXIT in SCB->SCR is set, the processor automatically re-enters sleep mode after completing any ISR, without returning to the main thread. This eliminates unnecessary context switches and keeps the processor sleeping as much as possible.
Enable it in CMSIS code:
/* Stay in sleep after each ISR — ideal for event-driven firmware */SCB->SCR |= SCB_SCR_SLEEPONEXIT_Msk;
When combined with WFI in the idle loop, the processor wakes purely for interrupt service and goes right back to sleep.
The simplest and most overlooked power optimization is clock gating — disabling the clock to any peripheral that is not actively in use. Most microcontrollers provide a Peripheral Clock Control (PCC) or equivalent register where individual peripheral clocks can be enabled or disabled.
Before entering sleep, scan your peripheral usage and gate off everything unnecessary:
/* Gate clock to unused peripherals before entering sleep */CLOCK_DisableClock(kCLOCK_Uart0);CLOCK_DisableClock(kCLOCK_Spi0);CLOCK_DisableClock(kCLOCK_Adc0);/* Keep I2C clock if sensor needs it in deep sleep */
This alone can significantly reduce active and sleep mode current, saving precious milliamperes. To reach the microampere range, however, you must enter Deep Sleep. The catch: re-enabling a peripheral clock may require waiting for the clock to stabilize before accessing peripheral registers. Build that delay into your wake-up path.
A real-time operating system complicates power management because the tick interrupt fires periodically (typically every 1 ms), waking the processor even when nothing needs to happen. If the tick period is short relative to average idle time, the energy spent entering and exiting sleep accumulates quickly.
FreeRTOS tickless idle solves this by stopping the tick interrupt during idle periods and suppressing it for as long as no task is due to run. The tick count is corrected upon wake-up using an independent low-power timer (RTC, LPTimer, etc.).
Enable it in FreeRTOSConfig.h:
#define configUSE_TICKLESS_IDLE 1/* Minimum idle time entering sleep (in ticks).Avoids spending more energy entering/exiting sleep than saved. */#define configEXPECTED_IDLE_TIME_BEFORE_SLEEP 2
When the kernel detects that the Idle task is the only runnable task and the next deadline is at least configEXPECTED_IDLE_TIME_BEFORE_SLEEP ticks away, it calls portSUPPRESS_TICKS_AND_SLEEP(xExpectedIdleTime). This macro typically maps to a function that:
SLEEPDEEP = 1).vTaskStepTick(xTicksToCatchUp).+--------+ +--------+ +-----------------------+| Wake | --> | ISR | --> | Idle Task Running || Event | | Handler| +----------+------------++--------+ +--------+ || All tasks blocked?| Next deadline > threshold?v+------+------+| tickless || sleep entry |+------+------+|+------v------+| WFI + || SLEEPDEEP |+------+------+|Wake on IRQ or low-power timer|+------v------+| Correct tick|| count, || resume sched|+-------------+
The configPRE_SLEEP_PROCESSING() and configPOST_SLEEP_PROCESSING() macros let you add custom pre- and post-sleep actions:
/* FreeRTOSConfig.h */#ifndef configPRE_SLEEP_PROCESSING#define configPRE_SLEEP_PROCESSING( x ) vPreSleepProcessing( x )#endif#ifndef configPOST_SLEEP_PROCESSING#define configPOST_SLEEP_PROCESSING( x ) vPostSleepProcessing( x )#endif
void vPreSleepProcessing( TickType_t xExpectedIdleTime ){/* Gate off debug UART clock to save ~2 mA */CLOCK_DisableClock(kCLOCK_DebugUart);/* Reduce core voltage regulator if supported */POWER_SetRegulatorMode(POWER_LOW_VOLTAGE);}void vPostSleepProcessing( TickType_t xExpectedIdleTime ){/* Restore clocks and regulator */CLOCK_EnableClock(kCLOCK_DebugUart);POWER_SetRegulatorMode(POWER_NORMAL_VOLTAGE);}
Some microcontrollers support multiple run modes at different voltage and frequency levels. NXP S32K14x parts, for example, offer HSRUN (112 MHz), RUN (80 MHz), and VLPR (4 MHz). Switching to a lower-frequency mode reduces dynamic power consumption proportionally:
+--------+ +--------+ +--------+| HSRUN | --> | RUN | --> | VLPR || 112MHz | | 80MHz | | 4MHz || ~30mA | | ~15mA | | ~2mA |+--------+ +--------+ +--------+
The trade-off is obvious: lower frequency means slower computation. The strategy is to do burst processing at high frequency, then drop to the lowest viable mode. On ARM Cortex-M, you can also simply reduce the system clock prescaler before entering sleep and restore it on wake-up.
A powerful technique for reducing CPU active time is to offload data transfers to DMA (Direct Memory Access). The CPU sets up a transfer, enters sleep, and the DMA controller moves data between memory and peripherals autonomously. On completion, a DMA interrupt wakes the CPU.
This is especially effective for communication peripherals (UART, SPI, I2C) and ADCs. On NXP S32K devices, DMA can operate even in VLPS (Very Low Power Stop) mode, allowing the system to transfer UART data while the core clock is gated off entirely.
/* Setup DMA transfer from buffer to UART */DMA_SetChannelSource(DMA_CHANNEL, kDmaRequestMux0LPUART0Tx);DMA_SetDestinationAddress(DMA_CHANNEL, (uint32_t)&LPUART0->DATA);DMA_SetSourceAddress(DMA_CHANNEL, (uint32_t)txBuffer);DMA_SetTransferCount(DMA_CHANNEL, txLength);DMA_EnableChannelRequest(DMA_CHANNEL);/* Now enter tickless sleep — DMA will complete and wake via IRQ */
Here is a concise checklist for designing low-power embedded firmware:
+----------------------------------------------------------+| LOW-POWER DESIGN CHECKLIST |+----------------------------------------------------------+| || [ ] Gate clocks to ALL unused peripherals || [ ] Configure unused GPIOs as analog (no pull) || [ ] Enable tickless idle in RTOS || [ ] Set SLEEPDEEP for deep sleep in idle || [ ] Use Sleep-On-Exit for interrupt-driven designs || [ ] Offload transfers to DMA where possible || [ ] Scale clock frequency to minimum viable || [ ] Use WFI/WFE — never busy-wait in idle loop || [ ] Measure current with a real ammeter, not datasheet || [ ] Profile wake-up latency vs. power savings || |+----------------------------------------------------------+
Power management in embedded systems is not a single technique — it is a layered strategy. Start with the hardware: understand your MCU’s sleep modes, gate peripheral clocks aggressively, and use WFI/WFE to enter the deepest safe sleep. At the RTOS layer, enable tickless idle to eliminate periodic tick wake-ups. Use DMA to keep the CPU asleep during data transfers. And always measure real current consumption — datasheet numbers are best-case, and your board layout, pull-up resistors, and firmware overhead all add up.
The best low-power designs are the ones where the processor spends 99% of its time in deep sleep, waking only for brief, deterministic bursts of work. Master these techniques and your battery-powered devices will run for years, not months.
Quick Links
Legal Stuff





