Measure current consumption using Wokwi simulation
Configure timer and GPIO wake-up sources
Optimize code for minimum power consumption
Calculate and validate battery life predictions
In 60 Seconds
This hands-on lab walks you through implementing ESP32 deep sleep and timer wake-up in Wokwi simulation, measuring actual current savings, and validating that your firmware achieves the target battery life calculated from your power budget.
Key Concepts
Deep Sleep: ESP32 power state drawing ~10 µA where the main CPU is off; only RTC memory and wake-up sources remain active
Light Sleep: ESP32 power state drawing ~0.8 mA where CPU is paused but RAM is retained; wakes faster than deep sleep
Timer Wake-Up: Waking from sleep after a fixed duration using the RTC timer; used for periodic sensing applications
GPIO Wake-Up: Waking from sleep when an external signal changes state; used for event-driven applications (button, PIR sensor)
ULP (Ultra-Low Power) Co-processor: A small secondary processor on ESP32 that can run simple programs while the main CPU is in deep sleep
esp_sleep_enable_timer_wakeup(): Arduino/ESP-IDF function to configure timer-based wake-up before entering deep sleep
Power Profiler: Tool that measures and logs current draw over time; Wokwi provides simulated power profiling for ESP32
For Beginners: Power & Sleep Modes
Low-power design and sleep modes are techniques for making IoT devices last much longer on battery power. Think of how you turn off lights in rooms you are not using to save on electricity. IoT devices can similarly ‘sleep’ between measurements, waking up briefly to collect data and then going back to sleep, stretching a small battery over months or years.
Interactive: Energy-Aware Design Quiz
21.2 Lab Overview
This hands-on lab uses Wokwi simulation to explore power management techniques for ESP32. While simulation cannot perfectly replicate real-world current measurements, it demonstrates the concepts and code patterns needed for low-power design.
21.3 Hands-On Lab: Power Monitoring and Sleep Modes
Wokwi Simulation: ESP32 Deep Sleep
This simulation demonstrates ESP32 deep sleep with timer and GPIO wake-up. The serial output shows current state transitions.
21.3.1 Basic Deep Sleep Code
Copy this code into the Wokwi editor to explore deep sleep:
/* * ESP32 Deep Sleep Example * Demonstrates timer-based wake-up for ultra-low power operation * * Power Profile: * - Active: ~40mA (with serial) * - Deep Sleep: ~10µA * * Battery Life Estimate: * 2000mAh / (10µA × 0.997 + 40mA × 0.003) ≈ 16,000 hours ≈ 1.8 years */#include <esp_sleep.h>#define LED_PIN 2#define SLEEP_SECONDS 10// Boot counter stored in RTC memory (survives deep sleep)RTC_DATA_ATTR int bootCount =0;void setup(){ Serial.begin(115200); delay(100);// Increment and print boot count bootCount++; Serial.println("\n\n=== ESP32 Deep Sleep Demo ==="); Serial.printf("Boot count: %d\n", bootCount);// Print wake-up reason print_wakeup_reason();// Configure LED pinMode(LED_PIN, OUTPUT);// Flash LED to show we're alivefor(int i =0; i <3; i++){ digitalWrite(LED_PIN, HIGH); delay(100); digitalWrite(LED_PIN, LOW); delay(100);}// Simulate sensor reading Serial.println("Reading sensors..."); delay(500);float temperature =20.0+(bootCount %10); Serial.printf("Temperature: %.1f°C\n", temperature);// Simulate data transmission Serial.println("Transmitting data..."); delay(1000); Serial.println("Transmission complete.");// Calculate and display power estimate calculate_power_estimate();// Configure deep sleep Serial.printf("\nEntering deep sleep for %d seconds...\n", SLEEP_SECONDS); Serial.println("See you after the nap!\n"); Serial.flush();// Configure wake-up source esp_sleep_enable_timer_wakeup(SLEEP_SECONDS *1000000ULL);// Disable peripherals to minimize sleep current// (In real hardware, this reduces from 10µA to ~5µA)// Enter deep sleep esp_deep_sleep_start();// This line will never execute Serial.println("This should never print");}void loop(){// Never reached - deep sleep restarts from setup()}void print_wakeup_reason(){esp_sleep_wakeup_cause_t reason = esp_sleep_get_wakeup_cause(); Serial.print("Wake-up reason: ");switch(reason){case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Timer wake-up");break;case ESP_SLEEP_WAKEUP_EXT0: Serial.println("External GPIO wake-up (EXT0)");break;case ESP_SLEEP_WAKEUP_EXT1: Serial.println("External GPIO wake-up (EXT1)");break;case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Touchpad wake-up");break;default: Serial.printf("Other/Power-on (code %d)\n", reason);break;}}void calculate_power_estimate(){// Estimate based on observed behaviorfloat active_time_ms =1800;// ~1.8 seconds activefloat sleep_time_ms = SLEEP_SECONDS *1000;float cycle_time_ms = active_time_ms + sleep_time_ms;float active_current_ma =40.0;// With Serialfloat sleep_current_ma =0.010;// 10 µAfloat avg_current =(active_current_ma * active_time_ms + sleep_current_ma * sleep_time_ms)/ cycle_time_ms;float battery_mah =2000;float life_hours = battery_mah / avg_current;float life_days = life_hours /24; Serial.println("\n--- Power Estimate ---"); Serial.printf("Active time: %.1f ms (%.1f mA)\n", active_time_ms, active_current_ma); Serial.printf("Sleep time: %.0f ms (%.3f mA)\n", sleep_time_ms, sleep_current_ma); Serial.printf("Average current: %.3f mA\n", avg_current); Serial.printf("Battery life (2000 mAh): %.0f hours (%.1f days)\n", life_hours, life_days);}
21.4 Challenge 1: Optimize for 5-Year Battery Life
Goal: Modify the code above to achieve 5-year battery life on a 2000mAh battery.
Constraints:
Maximum average current: 2000mAh / (5 × 365 × 24h) = 0.0457 mA = 45.7 µA
Active current: 40mA (can’t reduce in simulation)
Sleep current: 10µA
Calculate: What sleep interval is needed?
Hint: Use the duty cycle equation and solve for sleep time.
Interactive: Battery Life Calculator
Use this calculator to explore how sleep intervals affect battery life:
Show code
viewof battery_capacity = Inputs.range([100,5000], {value:2000,step:100,label:"Battery Capacity (mAh)"})viewof active_current = Inputs.range([10,100], {value:40,step:1,label:"Active Current (mA)"})viewof sleep_current = Inputs.range([0.001,1], {value:0.01,step:0.001,label:"Sleep Current (mA)"})viewof active_time = Inputs.range([0.1,10], {value:1.8,step:0.1,label:"Active Time per Cycle (seconds)"})viewof sleep_time = Inputs.range([10,7200], {value:60,step:10,label:"Sleep Time per Cycle (seconds)"})
Answer: Set SLEEP_SECONDS = 2015 (approximately 34 minutes) to achieve 5-year battery life with the given active time and currents. This means the ESP32 sleeps for 33.6 minutes, wakes for 1.8 seconds, achieving a duty cycle of 0.089% (active only 0.09% of the time).
21.5 Challenge 2: Multi-Source Wake-Up
Goal: Extend the code to support both timer AND GPIO wake-up.
// Add GPIO wake-up capability#define BUTTON_PIN 33// Use GPIO33 for wake-upvoid configure_wakeup_sources(){// Timer wake-up (periodic) esp_sleep_enable_timer_wakeup(3600*1000000ULL);// 1 hour// GPIO wake-up (motion sensor interrupt) esp_sleep_enable_ext0_wakeup(GPIO_NUM_33,1);// Wake on HIGH}// In setup(), determine which source triggered wake:void handle_wakeup(){esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();if(cause == ESP_SLEEP_WAKEUP_TIMER){// Periodic reading - send data read_and_transmit();}elseif(cause == ESP_SLEEP_WAKEUP_EXT0){// Motion detected - immediate alert! send_motion_alert();}}
21.6 Challenge 3: Adaptive Duty Cycling
Goal: Implement adaptive sampling based on temperature change rate.
RTC_DATA_ATTR float last_temp =0;RTC_DATA_ATTR int consecutive_stable =0;uint32_t calculate_next_interval(float current_temp){float delta = abs(current_temp - last_temp); last_temp = current_temp;if(delta >2.0){// Rapid change - sample every minute consecutive_stable =0;return60;}elseif(delta >0.5){// Moderate change - sample every 5 minutes consecutive_stable =0;return300;}else{// Stable - extend interval up to 30 minutes consecutive_stable++;return min(1800,300+(consecutive_stable *60));}}
Interactive: Adaptive Sampling Simulator
Explore how adaptive sampling intervals change based on temperature variations:
Deep Sleep is Essential: Moving from idle (15-80mA) to deep sleep (10µA) provides 1,000-8,000× power reduction
RTC Memory Survives Sleep: Use RTC_DATA_ATTR to preserve variables across deep sleep cycles
Wake-Up Sources: Timer for periodic sensing, GPIO for event-driven response
Duty Cycle Math: Use \(I_{avg} = I_{active} \times D + I_{sleep} \times (1-D)\) to calculate battery life
Adaptive Strategies: Adjust sampling rate based on conditions to optimize power vs responsiveness
21.8 Additional Wokwi Projects to Explore
Power Monitoring with INA219:
https://wokwi.com/projects/new/esp32
Add an INA219 current sensor to measure actual power consumption.
Multi-Sensor Low-Power Node:
Combine temperature, humidity, and motion sensors with optimized sleep.
LoRa Low-Power Transmitter:
Use SX1276 simulation for long-range, low-power communication.
Worked Example: Optimizing ESP32 Deep Sleep for Maximum Battery Life
Scenario: Reduce ESP32 sleep current from 10 µA to <5 µA for 10-year battery life.
Measurements:
Stock deep sleep: 10.5 µA measured
Target: <5 µA for 10-year life (1,000 mAh battery)
Breakdown:
- ESP32 core: 5 µA (datasheet minimum)
- RTC memory: 2 µA
- GPIO leakage: 1.5 µA
- External pull-ups: 2 µA (10kΩ at 3.3V)
Optimizations applied:
Disable RTC peripherals not needed: saves 1 µA
Configure unused GPIOs as INPUT with no pull: saves 0.8 µA
Use MCU internal pulls instead of external 10kΩ: saves 2 µA
Enable ESP32 ULP co-processor for sensor reading: saves 0.5 µA
Result: Reduced to 4.2 µA, achieving 11.7-year battery life (theory) or 8-9 years real-world.
Decision Framework: Timer Wake vs GPIO Wake
Factor
Timer Wake
GPIO Interrupt
Best Use Case
Power
Fixed intervals
Event-driven saves more
Sporadic events → GPIO
Complexity
Simple
Requires external sensor
Simple periodic → Timer
Latency
Up to full interval
Instant response
Real-time response → GPIO
Combined approach: Timer wake every 1 hour (scheduled) + GPIO for motion (immediate alerts)
Common Mistake: Not Initializing RTC_DATA Variables
The Mistake: Using RTC_DATA_ATTR int counter; without initialization. After power cycle, counter has random value (e.g., 47,293 instead of 0).
Fix:
RTC_DATA_ATTR int counter =0;// Wrong - only init on compile, not runtime// Correct:RTC_DATA_ATTR int counter;void setup(){if(esp_reset_reason()!= ESP_SLEEP_WAKEUP_TIMER){ counter =0;// Power-on reset, initialize}// Now counter is reliable}
21.9 Concept Relationships
This hands-on lab connects theoretical concepts to practical implementation:
Power Management Concepts Applied:
Deep Sleep Modes: ESP32 deep sleep reduces current from 80 mA to 10 µA (8,000× reduction)
RTC Memory Persistence: Demonstrates non-volatile state storage across power cycles
Wake-Up Sources: Timer-based (periodic) vs GPIO-based (event-driven) triggering
Embedded Programming Patterns:
State Machines: Boot counter and wake-up reason handling implement finite state machine pattern
Power State Transitions: Setup() → work → deep_sleep_start() is the fundamental low-power loop
Interrupt-Driven Architecture: GPIO wake sources leverage hardware interrupts for zero-polling energy cost
Measurement and Validation:
Empirical Profiling: Measuring actual sleep current vs datasheet values reveals real-world deviations
Battery Life Calculation: Duty cycle formula converts current profile to lifetime estimates
Margin Analysis: Conservative estimates account for temperature, aging, and component tolerance
System Design Trade-offs:
Latency vs Energy: Deep sleep saves maximum energy but has millisecond wake-up penalty
Flexibility vs Simplicity: Multi-source wake-up adds complexity but enables hybrid sampling strategies
Accuracy vs Longevity: Adaptive duty cycling trades measurement frequency for extended battery life
This lab demonstrates that effective energy management requires understanding hardware capabilities (ESP32 sleep modes), firmware design patterns (state persistence), and system-level optimization (adaptive behavior based on battery level).
GPIO pins configured as outputs and driven HIGH continue to source current during deep sleep. Set all output pins to input or low before calling esp_deep_sleep_start() to avoid leakage current that negates sleep savings.
2. Forgetting the Wi-Fi and Bluetooth Radio Current
Calling WiFi.begin() or BLE.begin() and leaving the radio on while the rest of the code sleeps defeats the purpose. Always explicitly shut down the radio (WiFi.disconnect(true); WiFi.mode(WIFI_OFF);) before entering sleep.
3. Measuring Sleep Current with USB Connected
USB connection keeps the CP2102/CH340 USB-to-serial chip powered, adding 5–15 mA to the measured current. Always disconnect USB and power from a battery to measure true sleep current.
4. Trusting Simulated Power Figures as Exact
Wokwi simulation provides good relative comparisons between sleep modes but does not model exact current draw. Always verify actual power consumption on real hardware with a current probe or INA219 before finalizing a design.