This guide covers all five official ESP32 sleep modes with measured current figures, code that actually works in ESP-IDF 5.3 and Arduino Core 3.x, wiring diagrams for external wakeup, and a live battery-life calculator so you can design your project before buying a single cell.
RTC_DATA_ATTR variables.ESP32 Sleep Modes: Full Power Comparison Table (2026)
The table below uses measurements taken on an original ESP32-WROOM-32E with a 3.3 V regulated supply and no external peripherals. Values will differ slightly between chip revisions and variants (ESP32-S3, C6, etc.) — see variant notes further down.
| Mode | Current Draw | CPU | Wi-Fi / BT | SRAM Retained | Wake Time | Best Use | Battery Life* |
|---|---|---|---|---|---|---|---|
| Active | 160–240 mA | Running | On | ✅ Yes | Instant | Continuous data streaming | ~4–6 hrs |
| Modem Sleep | 3–20 mA | Running | Off (CPU active) | ✅ Yes | Instant | Local processing, no Wi-Fi needed | ~2–3 days |
| Light Sleep | 0.8 mA | Paused | Off | ✅ Yes | ~1 ms | Periodic short wakeups, retain context | ~50 days |
| Deep Sleep | 10–150 µA | Off | Off | ⚠️ RTC only (8 KB) | ~350 ms (full boot) | Battery sensors, wakeup every min–hr | ~3–18 months |
| Hibernation | ~5 µA | Off | Off | ❌ None | ~350 ms (full boot) | Ultra long-life, timer or 1 pin wakeup only | 2–11+ years |
*Battery life estimated with a 1,000 mAh Li-Po, 10 s active window every 60 s cycle. Actual results depend on peripherals and antenna activity.
Mode 1: Active Mode
Active mode is the default after boot. Both Xtensa LX6 cores run at up to 240 MHz, and the Wi-Fi and Bluetooth radio can be active simultaneously. This is the only mode suitable for continuous data streaming, real-time control loops, or maintaining a persistent TCP/MQTT connection.
The main power optimization lever in active mode is CPU frequency scaling. Dropping from 240 MHz to 80 MHz saves roughly 30–40 mA and is usually invisible to the user for non-computation-heavy tasks.
// Reduce CPU frequency to 80 MHz (saves ~30–40 mA)
// ESP-IDF 5.x
#include "esp_pm.h"
esp_pm_config_t pm_config = {
.max_freq_mhz = 80,
.min_freq_mhz = 10,
.light_sleep_enable = false
};
esp_pm_configure(&pm_config);Mode 2: Modem Sleep
Modem Sleep cuts power to the Wi-Fi/Bluetooth radio while keeping the CPU running. This is automatic when you use automatic light sleep in IDF, or you can disable Wi-Fi explicitly when you don't need it. Useful for edge processing tasks where the MCU works locally between periodic cloud uploads.
// Disable Wi-Fi to enter effective Modem Sleep
#include "esp_wifi.h"
void go_modem_sleep() {
esp_wifi_stop(); // radio off — drops to ~3–5 mA
// do local processing here...
esp_wifi_start(); // radio back on when needed
esp_wifi_connect();
}Mode 3: Light Sleep
Light sleep is the hidden gem of ESP32 power management. At 0.8 mA, it's over 200× lower than active mode, yet the CPU state and all SRAM are preserved. When the chip wakes, your code continues exactly where it left off — no reboot, no re-initialization. This makes it perfect for applications that need to check a sensor every few seconds but don't need to stay fully awake.
// Light Sleep with 5-second timer wakeup
// Works in both Arduino Core 3.x and ESP-IDF 5.x
#include "esp_sleep.h"
#include "driver/uart.h"
#define SLEEP_DURATION_US (5 * 1000000ULL) // 5 seconds
void loop() {
// Do your sensor reading here
Serial.println("Awake — reading sensor...");
delay(100); // allow serial to flush
// Configure timer wakeup
esp_sleep_enable_timer_wakeup(SLEEP_DURATION_US);
// Enter light sleep — execution resumes HERE after wakeup
esp_light_sleep_start();
// Back from sleep — no reboot needed
Serial.println("Woke from light sleep");
}Mode 4: Deep Sleep
Deep sleep is the most commonly used power mode for battery-powered IoT sensors. The main CPUs and most peripherals are shut down completely. Only the RTC domain remains active, consuming 10–150 µA depending on whether the ULP coprocessor is running and which RTC peripherals are enabled.
Retaining Data Across Deep Sleep
Normal SRAM is cleared on deep sleep. Use the RTC_DATA_ATTR macro to store variables in the 8 KB RTC slow memory that persists through the sleep cycle.
// Deep Sleep with timer wakeup + data retention
#include "esp_sleep.h"
// This variable survives deep sleep — stored in RTC memory
RTC_DATA_ATTR int bootCount = 0;
RTC_DATA_ATTR float lastTemperature = 0.0f;
#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP 60 // wake every 60 seconds
void setup() {
Serial.begin(115200);
delay(100);
bootCount++;
Serial.printf("Boot #%d | Last temp: %.2f °C\n",
bootCount, lastTemperature);
// Read sensor, store result
lastTemperature = readBME280Temperature(); // your sensor function
// Transmit via Wi-Fi / MQTT here...
// Configure and start deep sleep
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
Serial.println("Going to deep sleep for 60 s");
Serial.flush();
esp_deep_sleep_start();
// ⬆ Execution NEVER reaches past this line
}
void loop() { /* never runs during deep sleep cycle */ }External Wakeup (EXT0 — Single GPIO)
EXT0 lets a single RTC-capable GPIO pin wake the ESP32. This is ideal for PIR motion sensors, door/window reed switches, or any digital trigger.
// EXT0 wakeup on GPIO 33 — active HIGH trigger
#include "esp_sleep.h"
#define WAKEUP_PIN GPIO_NUM_33
void setup() {
Serial.begin(115200);
// Check what caused this boot
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
if (cause == ESP_SLEEP_WAKEUP_EXT0) {
Serial.println("Woken by motion sensor!");
handleMotionEvent();
}
// Re-arm the wakeup and sleep again
esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 1); // 1 = HIGH triggers wake
esp_deep_sleep_start();
}Mode 5: Hibernation Mode
Hibernation is deep sleep with the RTC slow memory and RTC peripherals also powered down. At ~5 µA, it achieves the absolute minimum power draw the chip supports. The trade-off: you cannot retain variables in RTC memory, and only one wakeup source is available — the RTC timer or a single EXT0 GPIO. There is no ULP coprocessor access.
Use hibernation when you need maximum runtime and your firmware can reconstruct all state from Flash storage or sensors on every boot (e.g. a standalone temperature logger that writes to SD card).
// Enter Hibernation (disable all RTC power domains)
#include "esp_sleep.h"
#define HIBERNATE_SECONDS 3600 // wake every 1 hour
void enterHibernation() {
// Disable all RTC peripherals to minimize current
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
// Timer wakeup only (EXT0 also valid)
esp_sleep_enable_timer_wakeup(
(uint64_t)HIBERNATE_SECONDS * 1000000ULL);
Serial.println("Entering Hibernation — see you in 1 hour");
Serial.flush();
esp_deep_sleep_start(); // IDF has no separate hibernation API; PD config achieves it
}Battery Life Calculator: Real Estimates for Your Project
Use this formula to estimate battery life. The worked example below assumes a weather station that wakes, reads a sensor, uploads to MQTT, then returns to deep sleep.
🔋 Worked Example: Weather Station (1,000 mAh Li-Po)
Active: 30 s/hr @ 80 mA · Deep Sleep: 3,570 s/hr @ 20 µA
ULP Coprocessor: Run Code at 150 µA During Deep Sleep
The Ultra-Low-Power (ULP) coprocessor is a separate, tiny RISC processor that continues running while the main CPUs are in deep sleep. It has access to RTC memory and can read analog inputs, toggle GPIOs, and communicate over I²C — then wake the main CPU only when a meaningful event occurs (e.g., temperature exceeds 35 °C).
As of ESP-IDF 5.x, you can program the ULP in C using the FSM-based ULP co-processor framework, eliminating the need to write assembly. This is a massive quality-of-life improvement for 2026 projects.
// ULP-FSM C example — read ADC, wake main CPU if threshold exceeded
// Place this file in your project as ulp_main.c
// Build with CONFIG_ULP_COPROC_TYPE_FSM=y in sdkconfig
#include "ulp_riscv.h" // for RISC-V ULP (ESP32-S3/C6)
// or
// #include "ulp_fsm_common.h" // for original ESP32 ULP-FSM
// Full ULP C integration is project-specific — see:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp.html7 Common ESP32 Sleep Mode Mistakes (And How to Fix Them)
1. Not Disabling Peripherals Before Sleep
External sensors, displays, and LEDs continue drawing current even while the ESP32 sleeps. A BME280 at 3.3 V draws ~1.8 µA in sleep, but an attached I²C OLED can draw 3–5 mA constantly. Always cut power to peripherals via a P-MOSFET or GPIO-controlled power rail before entering sleep.
2. Using GPIO Pullup/Pulldown During Sleep
Internal pullup/pulldown resistors stay active during light and deep sleep, consuming ~80 µA per enabled pin. Only RTC GPIO pullups are retained in deep sleep; all others should be disabled or replaced with external resistors.
3. Forgetting Serial.flush() Before Deep Sleep
The UART TX buffer may not have finished transmitting when you call esp_deep_sleep_start(). Always call Serial.flush() or uart_wait_tx_done() first, or your last debug line will be corrupted or missing.
4. Storing Large Data in RTC Memory
RTC slow memory is only 8 KB total. If you try to store arrays or structs larger than this across sleep cycles, your RTC_DATA_ATTR declarations will silently overflow and corrupt other variables. Use Flash (NVS) for anything larger.
5. Expecting Fast Wakeup from Deep Sleep
Deep sleep always triggers a full reboot sequence: ROM bootloader → application bootloader → your setup(). This typically takes 300–500 ms. If your application requires wakeup faster than ~50 ms, use Light Sleep instead.
6. Not Accounting for Wi-Fi Reconnection Time
After deep sleep, the ESP32 must scan for and connect to your Wi-Fi network, adding 1–5 seconds to every wakeup. Use WiFi.begin(ssid, password) with saved channel and BSSID to cut reconnection to under 500 ms:
// Fast Wi-Fi reconnect after deep sleep
// Store channel & BSSID in RTC memory before sleeping
RTC_DATA_ATTR int wifi_channel = 0;
RTC_DATA_ATTR uint8_t bssid[6] = {0};
WiFi.begin(ssid, password, wifi_channel, bssid, true);
// Saves ~1–3 s vs. full channel scan7. Ignoring Core Web Vitals on Your Project Page
Not a firmware issue — but if you're publishing your project online, slow pages mean fewer readers. Compress images, defer non-critical scripts, and aim for LCP under 2.5 s to maximize your article's search visibility in 2026.
Frequently Asked Questions
RTC_DATA_ATTR macro in your C code to keep variables alive across sleep cycles without writing to Flash.esp_sleep_enable_*_wakeup() family before calling esp_deep_sleep_start().Conclusion: Choose the Right Mode, Not the Lowest Number
The temptation is always to jump straight to Hibernation because "5 µA is the best." In reality, the right sleep mode depends on three questions:
1. Do you need to resume without rebooting? → Use Light Sleep.
2. Do you need to retain variables? → Use Deep Sleep with RTC_DATA_ATTR.
3. Can you reconstruct all state from scratch every wake? → Use Hibernation.
For most battery-powered IoT sensors — the weather station, the soil moisture monitor, the air quality logger — Deep Sleep with timer wakeup is the sweet spot. It gives months of runtime while keeping your firmware architecture simple.
If you implement the fast Wi-Fi reconnect trick and minimize your active window to under 5 seconds, you can realistically get 6–12 months from a standard 18650 Li-ion cell with no solar assist.
