ESP32 Sleep Modes Explained: Deep, Light, and Modem Sleep

An ESP32 in active mode pulls about 240 mA. Plug that into a 1000 mAh LiPo and you’ve got roughly 4 hours of runtime. That’s a demo on a bench, not a product in the field.
The frustrating part? Most of that power gets burned doing nothing. Your sensor reads once a minute, but the CPU and radio sit idle the other 59 seconds, draining current the whole time. The ESP32 has 3 distinct sleep modes designed to claw back that wasted energy, and picking the wrong one (or not picking one at all) is usually why “my battery dies in 2 days” shows up in forum posts.
This article covers all 3 modes: deep sleep, light sleep, and modem sleep. For each, you’ll get the power numbers, the tradeoffs, the wake-up sources, and compilable ESP-IDF v5.x code.
The Power Mode Spectrum
| Mode | Typical Current | CPU State | RAM State | Wi-Fi State | Wake Latency |
|---|---|---|---|---|---|
| Active | ~240 mA | Running | Retained | Available | N/A |
| Modem Sleep | ~15–20 mA | Running | Retained | Off/DTIM | < 1 ms |
| Light Sleep | ~0.8 mA | Paused | Retained | Off | ~1–3 ms |
| Deep Sleep | ~10 µA | Off | Lost* | Off | ~200–500 ms |
| Hibernation | ~5 µA | Off | Lost | Off | ~200–500 ms |
*RTC fast/slow memory is retained in deep sleep.
Hibernation strips away even RTC memory and is niche enough to leave out of scope here.
A sensor that wakes every 30 minutes has very different requirements than an MQTT client that needs to receive commands in near real-time. Match your duty cycle and connectivity needs to the right mode.
┌─────────────────────────────────┐
│ Active Mode │
│ CPU | RAM | Wi-Fi | Peripherals│ ~240 mA
├─────────────────────────────────┤
│ Modem Sleep │
│ CPU | RAM | OFF | Peripherals│ ~15-20 mA
├─────────────────────────────────┤
│ Light Sleep │
│ PAUSE| RAM | OFF | Partial │ ~0.8 mA
├─────────────────────────────────┤
│ Deep Sleep │
│ OFF | RTC | OFF | OFF │ ~10 µA
└─────────────────────────────────┘Deep Sleep: Maximum Power Savings
Deep sleep powers off the main CPU cores, most of RAM, and all digital peripherals. What survives: the RTC controller, RTC peripherals, and a small pool of RTC memory (8 KB slow + 8 KB fast). Current draw drops to around 10 µA, which means that same 1000 mAh battery could last over 11 years in theory (leakage and self-discharge will get you first).
When to Use It
Deep sleep fits projects where wake-ups happen on the scale of minutes to hours. Weather stations that report every 15 minutes. Soil moisture sensors that check once an hour. Asset trackers that ping location a few times a day.
Wake-Up Sources
You’ve got 5 options: timer, ext0 (single GPIO), ext1 (multiple GPIOs), touch pad, and the ULP coprocessor. Timer is the most common by far.
The Big Gotcha
When the ESP32 wakes from deep sleep, it reboots. Execution starts fresh from app_main(), not from where you called esp_deep_sleep_start(). All RAM variables are gone. If you need to persist a counter, a state flag, or calibration data across sleep cycles, stash it in RTC memory using RTC_DATA_ATTR or write it to NVS before sleeping.
ESP-IDF Code
#include "esp_sleep.h"
#include "esp_log.h"
RTC_DATA_ATTR int boot_count = 0;
void app_main(void)
{
boot_count++;
ESP_LOGI("sleep", "Boot count: %d", boot_count);
ESP_LOGI("sleep", "Wake cause: %d", esp_sleep_get_wakeup_cause());
// Enable timer wake-up: 30 seconds
esp_sleep_enable_timer_wakeup(30 * 1000000ULL);
// Optional: enable GPIO wake-up (ext0 on GPIO 33, active low)
// esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 0);
ESP_LOGI("sleep", "Entering deep sleep...");
esp_deep_sleep_start();
}Use esp_sleep_get_wakeup_cause() to branch your logic. If the device woke from a timer, read the sensor. If it woke from a GPIO interrupt (say, a button press), do something else entirely. The return value is an esp_sleep_wakeup_cause_t enum, so you can switch on it cleanly.
For battery-powered projects that need to transmit sensor data after waking, BLE-based approaches can reduce connection overhead compared to re-establishing Wi-Fi each cycle. If you’re exploring that path, Hubble’s device SDK introduction covers how BLE devices can offload data without managing a full Wi-Fi stack.
Light Sleep: Fast Wake with State Retention
Light sleep clock-gates the CPU. It’s paused, not powered off. RAM stays intact. When the chip wakes, execution picks up on the very next line after esp_light_sleep_start(). No reboot, no lost variables, no re-initialization.
Current draw lands around 0.8 mA, which is 80x more than deep sleep. But wake-up takes 1–3 ms instead of 200–500 ms, and you skip the entire boot sequence.
When to Use It
Light sleep shines when you’re polling every 1–10 seconds, or when you need sub-second response to an external event. Think a handheld device that sleeps between button presses, or a sensor node that samples fast but doesn’t need Wi-Fi for each reading.
Wake-Up Sources
Timer, GPIO (level-triggered), UART (with a byte threshold), and touch pad. GPIO wake-up in light sleep uses gpio_wakeup_enable() and requires a level trigger, not an edge trigger. Get this wrong and you’ll sit there wondering why it never wakes.
The Key Advantage Over Deep Sleep
You can maintain a Wi-Fi connection across light sleep cycles. Configure power save mode with WIFI_PS_MIN_MODEM or WIFI_PS_MAX_MODEM, and the radio will wake periodically to catch beacons from the AP. Your TCP connections, MQTT subscriptions, and IP address all survive.
ESP-IDF Code
#include "esp_sleep.h"
#include "esp_log.h"
void enter_light_sleep(void)
{
// Wake up after 5 seconds
esp_sleep_enable_timer_wakeup(5 * 1000000ULL);
// Optional: wake on GPIO 4 low
// gpio_wakeup_enable(GPIO_NUM_4, GPIO_INTR_LOW_LEVEL);
// esp_sleep_enable_gpio_wakeup();
ESP_LOGI("sleep", "Entering light sleep...");
esp_err_t ret = esp_light_sleep_start();
// Execution resumes HERE after wake-up
ESP_LOGI("sleep", "Woke up! Cause: %d (ret: %d)",
esp_sleep_get_wakeup_cause(), ret);
}You can also let FreeRTOS handle light sleep automatically. Enable CONFIG_FREERTOS_USE_TICKLESS_IDLE in your sdkconfig, and the scheduler will drop into light sleep whenever all tasks are blocked. The chip sleeps during idle periods without you calling esp_light_sleep_start() explicitly. Probably the easiest way to bolt on power savings to an existing project.
Modem Sleep: Stay Connected, Save Power
Modem sleep keeps the CPU running but powers down the Wi-Fi (and BT) radio between access point beacon intervals. It’s the least aggressive sleep mode, and here’s the thing: it’s enabled by default in ESP-IDF station mode. Many developers already have it on and don’t realize it.
When the CPU itself idles (no tasks running), current draw can drop to ~3 mA with the modem off. With the CPU active, expect 15–20 mA.
When to Use It
Always-connected applications: MQTT clients that need to receive messages, real-time dashboards, anything where dropping the Wi-Fi link isn’t acceptable.
Three Sub-Modes
WIFI_PS_NONE: no power saving. Radio is always on. Use this if you need minimum packet latency.WIFI_PS_MIN_MODEM: radio wakes at every DTIM beacon (typically every 100–300 ms, depending on your AP). Good default.WIFI_PS_MAX_MODEM: radio wakes at the listen interval, which can be longer than DTIM. More savings, more latency for incoming packets.
ESP-IDF Code
#include "esp_wifi.h"
// After Wi-Fi is connected:
esp_wifi_set_ps(WIFI_PS_MIN_MODEM); // or WIFI_PS_MAX_MODEM
One function call. The tradeoff: MAX_MODEM saves more power but incoming packets (like MQTT commands) might arrive with a few hundred milliseconds of extra delay.
ESP32 Light Sleep vs Deep Sleep: Picking the Right Mode
Here’s a decision tree that covers 90% of cases:
Is Wi-Fi needed while sleeping?
├── YES → Modem Sleep (WIFI_PS_MIN_MODEM / MAX_MODEM)
└── NO
├── Wake interval < 10 seconds? → Light Sleep
└── Wake interval > 10 seconds? → Deep Sleep
└── Need to persist data? → Use RTC_DATA_ATTR or NVS before sleepThese are guidelines, not hard rules. Hybrid approaches work well. You might use deep sleep for long idle periods but have the ULP coprocessor monitor an analog sensor and wake the main CPU only when a threshold is crossed. Or you might use light sleep in a loop, accumulating sensor data in RAM, and only fire up Wi-Fi every 50th cycle to do a batch upload.
The 10-second threshold is a rough heuristic. Say your wake-up cycle takes 300 ms (boot, read sensor, sleep) and you’re waking every 5 seconds. Deep sleep still gives you ~94% sleep ratio. But light sleep avoids the boot overhead entirely, and that matters more as intervals get shorter.
Pitfalls That Eat Your Power Budget
Measure real current. USB power measurements are useless for sleep currents. A USB port’s quiescent draw will mask the difference between 10 µA and 800 µA. Use a current shunt, a µCurrent Gold, or an INA219 breakout wired directly to the 3.3V rail.
Shut down peripherals before sleeping. Call esp_wifi_stop() and esp_bt_controller_disable() before entering deep sleep. If you don’t, the radio’s shutdown sequence might not complete cleanly, and you’ll draw more current than expected.
Watch for GPIO current leaks. A floating input pin can draw 50–100 µA. An LED left on its pull-up resistor can draw milliamps. Before sleeping, set unused pins to GPIO_MODE_DISABLE with gpio_set_direction(). This step alone often accounts for the gap between “datasheet says 10 µA” and “I’m measuring 2 mA.”
PSRAM gets wiped in deep sleep. If your application uses PSRAM-backed buffers, they’ll be gone on wake. Plan your data flow accordingly.
Brown-out resets on weak batteries. When the ESP32 wakes, it pulls a short current spike that can trip the brown-out detector on aging or marginal cells, causing spurious resets. Tune the threshold via CONFIG_ESP_SYSTEM_BROWNOUT_INTR or disable it (carefully) if your power supply is well-characterized.
For custom hardware designs where you need fine-grained control over BLE advertising and timing management during sleep cycles, getting the transmit window right matters as much as the sleep mode itself.
Put It Into Practice
Start with the decision tree. Pick the mode that matches your duty cycle. Copy the code block for that mode, flash it, and measure your current draw with a real instrument. Compare what you see against the datasheet numbers in the table above. If there’s a gap, work through the pitfalls checklist.
Once you’ve got a single mode working, experiment with hybrids: light sleep inside a loop with periodic deep sleep for long idle windows, or modem sleep with automatic light sleep via tickless idle. ESP-IDF is composable enough that these combinations don’t take much extra code.
The difference between a project that lasts 4 hours on a battery and one that lasts 4 months almost always comes down to sleep mode selection and peripheral housekeeping.
Hubble Network connects your ESP32 devices from anywhere on Earth via satellite—no gateways, no infrastructure, just BLE. See how it works →