ESP32 Deep Sleep & RTC GPIO: Complete Power Management Guide

Battery life is the #1 challenge in IoT devices. The ESP32's Deep Sleep mode drops power consumption from ~75 mA (active) to as low as 5 µA — a 15,000× reduction. This guide covers every wake source, which pins remain functional during sleep, the ULP co-processor, and real-world power optimization strategies.

Bottom line: A 2000 mAh battery running an ESP32 in Deep Sleep with hourly wake-ups lasts 2+ years. In active mode, the same battery lasts ~26 hours.

ESP32 Power Modes Comparison

ModeTypical CurrentCPURTCULPRAM Retention
Active (Modem)75–160 mA✅ RunningN/AFull
Modem Sleep3–30 mA✅ RunningN/AFull
Light Sleep0.5–4.0 mA⛔ PausedFull
Deep Sleep5–10 µA⛔ OffRTC fast memory only
Hibernation2.5–5 µA⛔ Off⛔ OffNone

RTC GPIO Pins: What Works During Deep Sleep

In Deep Sleep, the main CPU and most peripherals are powered off. However, the RTC (Real-Time Clock) domain remains active and can control a subset of GPIO pins. These RTC-capable pins can be used for wake-up detection and ULP co-processor I/O.

RTC GPIOStandard GPIOFunction During Sleep
RTC_GPIO0GPIO36 (INPUT_ONLY)Wake from ext0/ext1
RTC_GPIO1GPIO37 (INPUT_ONLY)Wake from ext0/ext1
RTC_GPIO2GPIO38 (INPUT_ONLY)Wake from ext0/ext1
RTC_GPIO3GPIO39 (INPUT_ONLY)Wake from ext0/ext1
RTC_GPIO4GPIO34 (INPUT_ONLY)Wake from ext0/ext1
RTC_GPIO5GPIO35 (INPUT_ONLY)Wake from ext0/ext1
RTC_GPIO6GPIO25Wake, ULP I/O, ADC2
RTC_GPIO7GPIO26Wake, ULP I/O, ADC2
RTC_GPIO8GPIO33Wake, ULP I/O, touch T8
RTC_GPIO9GPIO32Wake, ULP I/O, touch T9
RTC_GPIO10GPIO4Wake, ULP I/O, touch T0, ADC2
RTC_GPIO11GPIO0Wake, ULP I/O, touch T1, strapping
RTC_GPIO12GPIO2Wake, ULP I/O, touch T2, strapping
RTC_GPIO13GPIO15Wake, ULP I/O, touch T3, strapping
RTC_GPIO14GPIO13Wake, ULP I/O, touch T4
RTC_GPIO15GPIO12Wake, ULP I/O, touch T5, strapping
RTC_GPIO16GPIO14Wake, ULP I/O, touch T6
RTC_GPIO17GPIO27Wake, ULP I/O, touch T7
⚠️ Input-only pins: GPIO34–39 (RTC_GPIO4–RTC_GPIO0) are input-only. They have no internal pull-up/pull-down resistors and cannot be used as outputs — even during Deep Sleep.

Wake Sources for Deep Sleep

The ESP32 can wake from Deep Sleep via five mechanisms:

1. Timer Wake-Up

The simplest method: wake after a fixed interval. Ideal for periodic sensor readings.

// ESP32 Timer Wake-Up Example — Wake every 10 seconds
#include <esp_sleep.h>

#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP  10  // seconds

void setup() {
    Serial.begin(115200);
    delay(100);

    esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
    Serial.println("Going to sleep for " + String(TIME_TO_SLEEP) + " seconds");

    esp_deep_sleep_start();
}

void loop() { }  // Never reached

2. External Wake (ext0)

Wake on a single RTC GPIO pin going HIGH or LOW. Uses very little power.

// ESP32 ext0 Wake-Up — Wake when GPIO33 goes HIGH
esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, HIGH);

3. External Wake (ext1)

Wake when any combination of up to 8 RTC GPIO pins changes state. Most flexible for multiple wake triggers.

// ESP32 ext1 Wake-Up — Wake when GPIO33 OR GPIO32 goes HIGH
const uint64_t ext1_mask = (1ULL << GPIO_NUM_33) | (1ULL << GPIO_NUM_32);
esp_sleep_enable_ext1_wakeup(ext1_mask, ESP_EXT1_WAKEUP_ANY_HIGH);

4. Touch Wake-Up

Wake when a capacitive touch pad is touched. See our Touch Pins Guide for full details.

// ESP32 Touch Wake-Up
touchSleepWakeUpEnable(T0, 30);  // GPIO4, threshold=30
esp_sleep_enable_touch_wakeup();

5. ULP Co-Processor Wake

The ULP (Ultra Low Power) co-processor can perform sensor readings while the main CPU is asleep, then wake the main CPU when a condition is met. See the ULP section below.

ULP Co-Processor: Computing While Asleep

The ESP32's ULP co-processor is a tiny RISC-V (ESP32-S2/S3) or custom (ESP32) processor that runs from the RTC slow memory while the main CPU and Wi-Fi/Bluetooth are powered down. It can:

  • Read analog values from the ADC (ADC1 only — ADC2 is used by Wi-Fi)
  • Read touch sensors
  • Control RTC GPIO pins (digital I/O)
  • Perform basic arithmetic and logic
  • Execute code from 8 kB of RTC slow memory
  • Wake the main CPU when a programmed condition is met
Use case: A temperature sensor that only wakes the main CPU when temperature exceeds 30°C. The ULP reads the sensor every 10 seconds, compares the value, and only wakes the power-hungry main CPU when necessary — dramatically extending battery life.

RTC Memory: Retaining Data Across Sleep Cycles

Data stored in RTC fast memory survives Deep Sleep. This is critical for counters, calibration values, and connection state:

// Data retained across Deep Sleep cycles
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR float calibrationOffset = 0.0;

void setup() {
    bootCount++;
    Serial.println("Boot count: " + String(bootCount));
    // calibrationOffset persists across sleeps
}

Power Optimization Checklist

StrategySavingsImplementation
Use Deep Sleep~75 mA → ~10 µAesp_deep_sleep_start()
Disable unused peripherals10–30 mAperiph_module_disable()
Pull unused pins LOW1–5 mASet pinMode OUTPUT + digitalWrite LOW
Reduce flash frequency5–10 mASet to 40 MHz in menuconfig
Disable brownout detector~25 µAesp_sleep_enable_timer_wakeup()
Use RTC GPIO for wake~1 µA vs touchext0/ext1 wake with pull resistors
Skip Wi-Fi reconnect every cycleSignificantStore Wi-Fi creds in RTC memory
⚠️ ADC2 + Wi-Fi conflict: ADC2 is used internally by the Wi-Fi driver. When Wi-Fi is enabled, ADC2 readings are unreliable. Use ADC1 for sensor readings during Wi-Fi operation, or schedule ADC2 readings during sleep cycles when Wi-Fi is off.

Complete Deep Sleep Example: Battery-Powered Sensor

// ESP32 Battery Sensor - Wake, Read, Send, Sleep
#include <esp_sleep.h>
#include <WiFi.h>

#define uS_TO_S_FACTOR 1000000ULL
#define SLEEP_SECONDS  300  // 5 minutes

RTC_DATA_ATTR int bootCount = 0;

const char* ssid = "your-ssid";
const char* password = "your-password";

void setup() {
    Serial.begin(115200);
    bootCount++;

    // Read sensor (ADC1 - safe with Wi-Fi)
    int sensorValue = analogRead(36);  // GPIO36 = ADC1_CH0

    // Connect to Wi-Fi
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() == WL_CONNECTED) {
        // Send data to server
        Serial.print("Boot:"); Serial.print(bootCount);
        Serial.print(" Sensor:"); Serial.println(sensorValue);
        WiFi.disconnect(true);
        WiFi.mode(WIFI_OFF);
    }

    // Configure sleep
    esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * uS_TO_S_FACTOR);
    Serial.println("Sleeping for " + String(SLEEP_SECONDS) + "s");
    esp_deep_sleep_start();
}

void loop() { }

🔧 Explore GPIO Compatibility

Use our interactive pinout tool to find RTC-capable GPIO pins and check for strapping conflicts before designing your sleep circuit.

Open Interactive Pinout

Related Guides