21  Lab: Power & Sleep Modes

21.1 Learning Objectives

By the end of this lab, you will be able to:

  • Implement and verify ESP32 deep sleep modes
  • 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

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.

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 alive
    for (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 behavior
    float active_time_ms = 1800;  // ~1.8 seconds active
    float sleep_time_ms = SLEEP_SECONDS * 1000;
    float cycle_time_ms = active_time_ms + sleep_time_ms;

    float active_current_ma = 40.0;  // With Serial
    float sleep_current_ma = 0.010;  // 10 µA

    float 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:

Try it: Adjust the sleep time slider to find the interval needed for 5-year battery life (target: ~2015 seconds or 33.6 minutes).

Let’s solve for the required sleep interval to achieve 5-year battery life:

Given constraints:

  • Battery capacity: \(C = 2000 \text{ mAh}\)
  • Target lifetime: \(T = 5 \text{ years} = 5 \times 365 \times 24 = 43,800 \text{ hours}\)
  • Active current: \(I_{\text{active}} = 40 \text{ mA}\)
  • Sleep current: \(I_{\text{sleep}} = 0.01 \text{ mA}\) (10 µA)
  • Active time per cycle: \(t_{\text{active}} = 1.8 \text{ s}\)

Required average current: \[I_{\text{avg}} = \frac{C}{T} = \frac{2000 \text{ mAh}}{43,800 \text{ h}} = 0.0457 \text{ mA} = 45.7 \text{ µA}\]

Average current equation (duty cycle formula): \[I_{\text{avg}} = \frac{I_{\text{active}} \times t_{\text{active}} + I_{\text{sleep}} \times t_{\text{sleep}}}{t_{\text{active}} + t_{\text{sleep}}}\]

Solving for sleep time: \[0.0457 = \frac{40 \times 1.8 + 0.01 \times t_{\text{sleep}}}{1.8 + t_{\text{sleep}}}\]

\[0.0457 \times (1.8 + t_{\text{sleep}}) = 72 + 0.01 \times t_{\text{sleep}}\]

\[0.082 + 0.0457 \times t_{\text{sleep}} = 72 + 0.01 \times t_{\text{sleep}}\]

\[0.0357 \times t_{\text{sleep}} = 71.918\]

\[t_{\text{sleep}} = \frac{71.918}{0.0357} = 2015 \text{ seconds} \approx 33.6 \text{ minutes}\]

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-up

void 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();
    } else if (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;
        return 60;
    } else if (delta > 0.5) {
        // Moderate change - sample every 5 minutes
        consecutive_stable = 0;
        return 300;
    } 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:

21.7 Key Takeaways from This Lab

After completing this lab, you should understand:

  1. Deep Sleep is Essential: Moving from idle (15-80mA) to deep sleep (10µA) provides 1,000-8,000× power reduction

  2. RTC Memory Survives Sleep: Use RTC_DATA_ATTR to preserve variables across deep sleep cycles

  3. Wake-Up Sources: Timer for periodic sensing, GPIO for event-driven response

  4. Duty Cycle Math: Use \(I_{avg} = I_{active} \times D + I_{sleep} \times (1-D)\) to calculate battery life

  5. 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.

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:

  1. Disable RTC peripherals not needed: saves 1 µA
  2. Configure unused GPIOs as INPUT with no pull: saves 0.8 µA
  3. Use MCU internal pulls instead of external 10kΩ: saves 2 µA
  4. 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.

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).

21.10 See Also

Related Lab Exercises:

Theoretical Foundations:

ESP32-Specific Resources:

Advanced Topics:

Real-World Deployments:

21.11 Summary

This lab demonstrated:

  1. ESP32 deep sleep implementation and wake-up sources
  2. RTC memory for preserving state across sleep cycles
  3. Power calculation and battery life estimation
  4. Adaptive duty cycling strategies
  5. Multi-source wake-up configuration

Continue to Interactive Tools for calculators and simulation tools.

21.12 Knowledge Check

Common Pitfalls

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.

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.

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.

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.

21.13 What’s Next

If you want to… Read this
Use interactive power budget calculators Interactive Tools
Study real-world optimization examples Case Studies and Best Practices
Learn advanced context-aware techniques Context-Aware Energy Management
Understand energy measurement methods Energy-Aware Measurement
Explore energy harvesting for perpetual operation Energy Harvesting Design