Implement effective sleep mode strategies for microcontrollers
Design duty-cycling schemes for optimal power efficiency
Apply voltage and frequency scaling techniques
Manage peripheral power consumption effectively
Configure and use wake-up sources appropriately
Optimize firmware for minimal energy consumption
Key Concepts
Sleep Mode Hierarchy: MCU sleep states from shallow to deep: idle/WFI (CPU stopped, peripherals active), light sleep (CPU + most peripherals off, RAM retained), deep sleep (nearly all power off, RTC active), and hibernate (only RTC + SRAM with battery backup)
Wake-Up Latency: Time required to exit sleep mode and resume execution; ranges from <1 µs (idle/WFI) to 1 s (deep sleep with full reinitialization)
Peripheral Power Gating: Using GPIO-controlled load switches to completely disconnect power from sensors, displays, or communication modules when not in use
Dynamic Voltage and Frequency Scaling (DVFS): Reducing CPU clock frequency and supply voltage proportionally when less compute performance is needed; power scales approximately with f × V²
Event-Driven Architecture: Structuring firmware around interrupt handlers rather than polling loops; CPU sleeps until an interrupt fires, minimizing active time
Radio Duty Cycling: Keeping the radio powered down between transmissions and waking only to transmit/receive; fundamental for all low-power wireless protocols (BLE, LoRa, Zigbee)
Batch Processing: Accumulating multiple sensor readings in memory before transmitting them in a single radio session; amortizes radio startup overhead across many samples
In 60 Seconds
Low-power IoT design combines three layers of optimization: hardware sleep modes (reduce MCU to <10 µA), duty cycling (minimize active time fraction), and firmware efficiency (minimize work during each active window) — applied together, these can extend battery life from days to years.
For Beginners: Low-Power Design Strategies
Energy and power management determines how long your IoT device can operate between battery changes or charges. Think of packing for a camping trip with limited battery packs – every bit of power must be used wisely. Since many IoT sensors need to run for months or years unattended, power management is often the single most important engineering decision.
Sensor Squad: The Power Nap Strategies!
“The number one strategy for saving energy is SLEEP,” said Max the Microcontroller. “Not just ‘turn off the screen’ sleep – deep sleep where I shut down almost everything and use 1,000 times less power. I can wake up from a timer, a button press, or even when Sammy detects something interesting.”
Bella the Battery described voltage scaling: “Running at a lower voltage uses less power. It is like driving a car in a lower gear—slower but more fuel efficient. Max can run at 1.8 volts instead of 3.3 volts when he does not need full speed, and that alone saves 40 percent of my energy.”
Sammy the Sensor added peripheral management: “Turn off what you are not using! If you only need temperature readings, power down the GPS, the accelerometer, and the microphone. Each peripheral draws current even when idle. It is like turning off lights in rooms nobody is in.” Lila the LED agreed, “And I only light up for 100 milliseconds when there is something to report. That brief flash uses 10,000 times less energy than staying on continuously!”
Interactive: ESP32 Wi-Fi Connection State Machine
Interactive: Sleep Mode Optimizer
6.2 Low-Power Design Strategies
Achieving multi-year battery life requires systematic application of low-power design techniques at every level: hardware selection, circuit design, and firmware implementation.
6.2.1 The Fundamental Principle: IoT Transceivers Are “Mostly Off”
The Sleep-First Design Philosophy
A well-designed battery-powered IoT device spends 99%+ of its time in deep sleep. The goal is to:
Minimize wake time - Complete all tasks as quickly as possible
Maximize sleep depth - Use the lowest power mode that meets requirements
Reduce wake frequency - Only wake when necessary
Rule of thumb: If your device is awake more than 1% of the time, you have optimization opportunities.
Figure 6.1: For 5-year battery life, devices typically spend over 99% of time in deep sleep
6.2.2 Sleep Mode Hierarchy
Different sleep modes offer trade-offs between power savings and wake-up time:
#include "esp_sleep.h"// Configure wake-up source (timer or GPIO)esp_sleep_enable_timer_wakeup(3600*1000000ULL);// 1 houresp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);// GPIO33 high// Optional: Configure GPIO hold during sleepgpio_hold_en(GPIO_NUM_LED);// Enter deep sleep - does not return!esp_deep_sleep_start();// After wake-up, execution starts from beginning// Check wake cause:esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();switch(cause){case ESP_SLEEP_WAKEUP_TIMER:// Timer-triggered wakebreak;case ESP_SLEEP_WAKEUP_EXT0:// GPIO-triggered wakebreak;default:// First power-on or resetbreak;}
6.2.4 STM32 Low-Power Modes
#include "stm32l4xx_hal.h"// Enter STOP2 mode (1.1 µA typical)HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);// Enter STANDBY mode (0.3 µA typical)HAL_PWR_EnterSTANDBYMode();// Enter SHUTDOWN mode (30 nA typical!)HAL_PWREx_EnterSHUTDOWNMode();// Wake-up configuration for STANDBYHAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);__HAL_PWR_CLEAR_FLAG(PWR_FLAG_WUF1);
6.3 Duty Cycling Strategies
6.3.1 Time-Based Duty Cycling
Regular interval wake-ups for periodic sensing:
void duty_cycle_periodic(void){while(1){// Wake actions read_sensors(); process_data();if(should_transmit()){ transmit_data();}// Calculate next wake timeuint32_t sleep_us = INTERVAL_SECONDS *1000000;// Enter deep sleep esp_sleep_enable_timer_wakeup(sleep_us); esp_deep_sleep_start();}}
6.3.3 Real-World Power Budget: Where Does the Energy Actually Go?
A common mistake in low-power design is optimizing the wrong subsystem. The following power audit of a real LoRaWAN soil moisture node (ESP32 + SX1276 + capacitive sensor, CR123A 1,500 mAh battery) reveals where energy is actually consumed:
Activity
Current
Duration
Frequency
Daily Energy (mAh)
% of Total
Deep sleep
10 µA
23.97 hrs
Continuous
0.240
21%
Sensor read
12 mA
150 ms
288/day (every 5 min)
0.144
13%
ESP32 boot + init
40 mA
800 ms
288/day
2.560
22%
Wi-Fi scan (failed)
120 mA
200 ms
288/day (if enabled)
1.920
–
LoRa TX (SF7, 14 dBm)
120 mA
50 ms
288/day
0.480
4%
LoRa RX window
12 mA
1,000 ms
288/day
0.960
8%
Voltage regulator quiescent
5 µA
24 hrs
Continuous
0.120
1%
Leakage (PCB, capacitors)
2 µA
24 hrs
Continuous
0.048
<1%
Total
~1.16
100%
Battery life: 1,500 mAh / 1.16 mAh/day = 1,293 days = 3.5 years (before self-discharge)
The surprise: Boot + initialization consumes 22% of daily energy – more than any other single activity. Two optimizations have outsized impact:
Use RTC memory to skip full boot: Store last sensor reading and transmission state in RTC RAM. Wake, compare, and go back to sleep without initializing Wi-Fi/LoRa stack if no transmission needed. Saves ~1.8 mAh/day (extends life to 5.2 years).
Batch transmissions: Send 6 readings every 30 minutes instead of 1 every 5 minutes. Same data, but 48 boot+TX cycles instead of 288. Saves ~1.9 mAh/day (extends life to 5.5 years).
Combining both optimizations: 7+ years on a single CR123A battery – more than the battery’s shelf life.
Conclusion: To achieve 5-year battery life, the device can be active for only ~2 seconds per hour. This means:
Sensor reading must complete in <1 second
LoRa transmission must complete in <1 second
No room for Wi-Fi (connection alone takes 2-5 seconds)
Putting Numbers to It
Why does deep sleep reduce power by 8,000× but only extend battery life by 6×? The duty cycle formula reveals the answer:
Baseline (no sleep): \(I_{\text{avg}} = 80\,\text{mA}\) → Battery life = \(2400 / 80 = 30\) hours
With deep sleep: \(I_{\text{avg}} = 80 \times 0.00056 + 0.01 \times 0.99944 = 0.0548\,\text{mA}\) → Life = \(2400 / 0.0548 = 43,800\) hours (5 years)
The 8,000× sleep current reduction (\(80\,\text{mA} / 10\,\mu\text{A}\)) becomes “only” 1,460× battery life improvement because the device still spends 0.056% awake. Lesson: Even with perfect sleep, active time dominates energy budget. Every second awake costs 8,000 seconds of battery life.
Optimization Strategies:
Use hot-start GPS (1s vs 30s cold start)
Pre-compute LoRa packet while sensor is reading
Use spread factor SF7 (fastest) if range permits
Transmit only deltas/changes, not every reading
6.4 Peripheral Power Management
6.4.1 GPIO Power Consumption
Hidden GPIO Power Drains
Unused or misconfigured GPIOs can consume significant power:
Configuration
Current Draw
Floating input
0-500 µA (oscillates)
Input with 10kΩ pull-up @ 3.3V
330 µA
Input with 100kΩ pull-up @ 3.3V
33 µA
Output low (no load)
~0 µA
Output high driving LED @ 20mA
20 mA
Internal pull-up enabled
10-50 µA
Best practice: Configure unused pins as output low or use internal pull-downs.
Try It: GPIO Power Drain Calculator
Configure the state of unused GPIO pins to see how much current they waste. Many developers overlook floating inputs as a major hidden power drain.
Use GPIO or load switch to completely power off sensors:
#define SENSOR_POWER_PIN 25void power_on_sensors(void){ gpio_set_level(SENSOR_POWER_PIN, HIGH); delay_ms(10);// Sensor startup time}void power_off_sensors(void){ gpio_set_level(SENSOR_POWER_PIN, LOW);}void read_with_power_gating(void){ power_on_sensors();float temp = read_temperature();float humidity = read_humidity(); power_off_sensors();// Back to zero current! process_and_transmit(temp, humidity);}
6.4.3 Load Switch Selection
Parameter
Low-Side Switch
High-Side Switch
Integrated Load Switch
Control
MCU GPIO
Level shifter needed
Direct MCU GPIO
Quiescent Current
0 (MOSFET)
0 (MOSFET)
1-10 µA
Turn-on Time
1-10 µs
1-10 µs
10-100 µs
Inrush Limiting
No
No
Often included
Example Parts
2N7002
Si2301
TPS22917, SIP32431
Cost
$0.02
$0.05
$0.20-0.50
6.4.4 Clock and Voltage Scaling
Dynamic Voltage and Frequency Scaling (DVFS) reduces power:
\[P_{dynamic} \propto C \times V^2 \times f\]
Halving frequency: 2× power reduction
Halving voltage: 4× power reduction
Halving both: 8× power reduction!
// ESP32 frequency scaling#include "esp_pm.h"// Configure power managementesp_pm_config_esp32_t pm_config ={.max_freq_mhz =240,.min_freq_mhz =80,// Scale down when idle.light_sleep_enable =true};esp_pm_configure(&pm_config);// STM32L4 low-power run mode (down to 2 MHz)HAL_RCCEx_EnableLSECSS();HAL_PWREx_EnableLowPowerRunMode();
Try It: DVFS Power Savings Explorer
Adjust voltage and frequency to see how dynamic voltage and frequency scaling affects power consumption. The relationship P = C × V² × f means voltage reductions have a quadratic effect on power.
Show code
viewof dvfs_voltage = Inputs.range([1.0,3.3], {value:3.3,step:0.1,label:"Supply voltage (V)"})viewof dvfs_frequency = Inputs.range([2,240], {value:240,step:2,label:"Clock frequency (MHz)"})viewof dvfs_capacitance = Inputs.range([1,50], {value:10,step:1,label:"Load capacitance (pF)"})
// ESP32: Wake after 1 houresp_sleep_enable_timer_wakeup(3600*1000000ULL);// STM32: Wake using RTC alarmRTC_AlarmTypeDef alarm ={0};alarm.AlarmTime.Hours =1;alarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY;HAL_RTC_SetAlarm_IT(&hrtc,&alarm, RTC_FORMAT_BIN);
6.5.2 External GPIO Wake-Up
For event-driven wake:
// ESP32: Wake on GPIO 33 going HIGHesp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);// ESP32: Wake on ANY of multiple pinsuint64_t mask =(1ULL<< GPIO_NUM_33)|(1ULL<< GPIO_NUM_34);esp_sleep_enable_ext1_wakeup(mask, ESP_EXT1_WAKEUP_ANY_HIGH);
6.5.3 Touch Wake-Up (ESP32)
// Configure touch pin for wake-uptouch_pad_config(TOUCH_PAD_NUM8, TOUCH_THRESHOLD);esp_sleep_enable_touchpad_wakeup();
6.6 Communication Protocol Optimization
6.6.1 Minimize Transmissions
// BAD: Transmit every readingvoid bad_transmit_pattern(void){while(1){float temp = read_temperature(); transmit(temp);// Every 60 seconds sleep(60);}}// GOOD: Transmit only on change or periodicallyvoid good_transmit_pattern(void){staticfloat last_sent =0;staticint readings_since_tx =0;while(1){float temp = read_temperature(); readings_since_tx++;bool significant_change = fabs(temp - last_sent)>0.5;bool time_for_heartbeat = readings_since_tx >=60;// 1 hourif(significant_change || time_for_heartbeat){ transmit(temp); last_sent = temp; readings_since_tx =0;} sleep(60);}}
6.6.2 Batch Transmissions
#define BATCH_SIZE 10#define READING_INTERVAL 60// secondsfloat readings[BATCH_SIZE];int reading_index =0;void batched_transmit_pattern(void){// Read sensor readings[reading_index++]= read_temperature();// Transmit when batch is fullif(reading_index >= BATCH_SIZE){// One transmission for 10 readings! transmit_batch(readings, BATCH_SIZE); reading_index =0;} sleep(READING_INTERVAL);}
Try It: Batch vs Individual Transmission Energy
Compare the energy cost of sending each sensor reading individually versus batching multiple readings into a single transmission. The key insight is that radio startup and connection overhead dominate per-packet energy.
Show code
viewof tx_readings_per_day = Inputs.range([24,1440], {value:288,step:24,label:"Sensor readings per day"})viewof tx_batch_size = Inputs.range([1,60], {value:10,step:1,label:"Batch size (readings per TX)"})viewof tx_boot_ms = Inputs.range([100,2000], {value:800,step:100,label:"Boot + init time (ms)"})viewof tx_boot_mA = Inputs.range([10,80], {value:40,step:5,label:"Boot current (mA)"})viewof tx_radio_ms = Inputs.range([20,500], {value:50,step:10,label:"Radio TX time per packet (ms)"})viewof tx_radio_mA = Inputs.range([20,200], {value:120,step:10,label:"Radio TX current (mA)"})
Compare the energy cost of sending a single message across different IoT communication protocols. Adjust payload size and see how connection overhead, data rate, and TX power affect total energy per message.
// BAD: Long interrupt handlervoid IRAM_ATTR bad_isr(void){ read_sensor();// DON'T: Slow I2C in ISR process_data();// DON'T: Computation in ISR transmit_wireless();// DON'T: Blocking TX in ISR}// GOOD: Minimal interrupt, deferred processingvolatilebool sensor_ready =false;void IRAM_ATTR good_isr(void){ sensor_ready =true;// Just set flag}void main_loop(void){while(1){if(sensor_ready){ sensor_ready =false; read_sensor(); process_data(); transmit_if_needed();} enter_light_sleep();}}
6.7.2 Optimize Memory Access Patterns
// BAD: Random access (cache misses)for(int i =0; i <1000; i++){ result += data[random_index[i]];}// GOOD: Sequential access (cache friendly)for(int i =0; i <1000; i++){ result += data[i];}
6.7.3 Avoid Busy Waiting
// BAD: Busy wait (wastes power)while(!sensor_ready){// CPU runs at full power doing nothing!}// BETTER: Sleep between checkswhile(!sensor_ready){ delay_ms(10);// May still waste power}// BEST: Interrupt-drivenenable_sensor_interrupt();enter_sleep();// Zero power until interrupt
6.8 Knowledge Check
## How It Works
Low-power IoT design follows a hierarchy of power modes, each trading wake latency for power savings:
Sleep Mode Decision Tree:
Idle (1-10mA): CPU halted, peripherals on → Use when wake latency <1µs required (immediate interrupt response)
Light Sleep (100µA-1mA): CPU + most peripherals off, RAM retained → Use for frequent wake (every second), fast resume <100µs
Deep Sleep (1-100µA): Only RTC + wake logic active → Use for periodic sensing (minutes), 100-500µs wake time acceptable
Hibernate (<10µA): Almost everything off → Use for rare events (hours), 1-10ms wake time acceptable
Wake-up mechanism: Timer wake (RTC counts down, triggers interrupt) OR GPIO wake (external event like button/sensor threshold) OR hybrid (timer + event). Modern MCUs support multiple simultaneous wake sources.
Power gating workflow: Sensors consume 1-50µA even when “off” → Use GPIO-controlled MOSFET to cut power completely → Add 10ms sensor stabilization delay after power-on → Still saves energy if sensor sleeps >1 second between reads.
6.9 Concept Check
## Concept Relationships
Low-power strategies are the core implementation of energy-aware design principles:
Extends: Takes concepts from Energy-Aware Introduction and provides concrete firmware implementation (sleep API calls, power gating circuits)
Enables: Proper sleep strategies are prerequisite for achieving targets in Energy Harvesting (must minimize consumption before solar can sustain device)
Measured By: All strategies must be validated with Energy Measurement tools—theory vs reality often differs by 10-100×
Protocol Choice: Sleep strategies determine which protocols work → Deep sleep (10µA) enables LoRa; idle mode (5mA) forces mains power or large battery
Design sequence: Energy budget (introduction) → Sleep strategy (this chapter) → Measurement (next chapter) → Iteration. Never skip measurement; “expected” sleep current often hides peripheral leakage.
Hardware: ESP32 dev board, Nordic PPK2 or multimeter with µA range
Goal: Discover hidden power drains
Steps:
Configure deep sleep: esp_sleep_enable_timer_wakeup(60 * 1000000ULL); esp_deep_sleep_start();
Measure current with PPK2
Expected: 10µA. If higher, debug:
Remove USB cable (USB chip adds 15mA)
Disable GPIO pull-ups
Check for LED on board
What to observe: Dev boards often measure 5-50mA due to USB chips, LEDs, voltage regulators. Custom PCB achieves datasheet 10µA. This 1000× difference is why “always measure on production hardware.”
6.11.2 Exercise 2: Optimize Wi-Fi Power Budget
Scenario: ESP32 connects to Wi-Fi every 5 minutes to send sensor data.
Measure current during delay (should be <1mA, likely 15-80mA because no sleep)
Replace delay() with deep sleep
Use static IP to avoid DHCP (saves 2 seconds)
Calculate battery life improvement
Expected outcome: Baseline = 80mA average (9 days on 2000mAh). Optimized = 0.2mA average (>1 year). The 400× improvement comes from replacing idle delay with deep sleep.
Matching Quiz: Match Low-Power Strategies to Savings
Ordering Quiz: Order Low-Power Firmware Design Steps
Label the Diagram
💻 Code Challenge
Order the Steps
Match the Concepts
6.12 Summary
Key low-power design strategies:
Sleep First: Design for 99%+ time in deep sleep
Match Sleep Mode to Requirements: Use deepest mode that still allows required wake sources
Power Gate Peripherals: Completely shut off sensors when not in use
Minimize Wake Time: Optimize every active operation for speed
Event-Driven Design: Wake on interrupts rather than polling
Batch and Filter: Reduce transmission frequency and payload size
Choose Protocols Wisely: BLE for low power, LoRa for range, avoid Wi-Fi when possible
Common Pitfalls
1. Forgetting to Power-Gate Peripheral Sensors Before Sleep
If sensors remain powered during MCU deep sleep, their quiescent current (100 µA–1 mA each) can dwarf the MCU sleep current (1–10 µA). Always use GPIO-controlled load switches or enable pins to completely remove power from peripherals before entering deep sleep.
2. Entering Deep Sleep Without Reinitializing Peripherals on Wake
After deep sleep, peripherals lose their configuration (SPI clock polarity, I2C address, ADC settings). If firmware doesn’t reinitialize them on wake, subsequent reads return garbage or the peripheral hangs. Always reinitialize all peripherals in the wake-up sequence.
3. Applying DVFS Without Profiling the Workload
Reducing clock frequency extends active time proportionally. If active-mode power scales linearly with frequency, the total energy may be unchanged. DVFS saves energy only when the workload can finish before the next event deadline, allowing early sleep. Profile timing before applying DVFS.
4. Keeping the Radio On Between Transmissions
Leaving the radio in receive or idle mode between transmissions is the single most common cause of unexpectedly short battery life. Even “idle” radio mode draws 5–20 mA. Always power down or fully disable the radio between transmit events.