1596  Hands-On Lab: Power Monitoring and Sleep Modes

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

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

1596.3 Hands-On Lab: Power Monitoring and Sleep Modes

NoteWokwi Simulation: ESP32 Deep Sleep

This simulation demonstrates ESP32 deep sleep with timer and GPIO wake-up. The serial output shows current state transitions.

1596.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);
}

1596.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.046 mA = 46 µ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.

1596.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();
    }
}

1596.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));
    }
}

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

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

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