How Do You Manage Power and Use Deep Sleep on the ESP32?
Last updated 29 June 2026 · 8 min read
Direct Answer
The ESP32 has four primary power modes. Active mode: both cores running at up to 240 MHz, Wi-Fi or BLE active — typical current 80–240 mA (dominated by the radio). Modem sleep: CPU active but radio off between DTIM beacons — typical current 20 mA. Light sleep: CPU and radio paused, RTC domain active, context preserved — typical current 0.8 mA, wake sources include RTC timer, GPIO, UART, and touch. Deep sleep: main cores off, most peripherals powered down, RTC domain and memory optionally active — typical current 10 µA. A wake stub can run from RTC fast memory without reinitialising the full firmware. Wake from deep sleep restarts the firmware from app_main(); RTC memory and RTC GPIOs retain their state.
Detailed Explanation
Battery life in an ESP32 product is almost entirely determined by how much time the chip spends at each power level. Getting from a device that runs for a week to one that runs for a year requires moving the ESP32 into lower-power modes for as much time as possible.
Understanding the four power modes, their trade-offs, and the constraints each imposes on the firmware design is the foundation of ESP32 power optimisation.
Power Mode Overview
All current figures are typical values from Espressif datasheets — actual measurements will vary with supply voltage, operating temperature, software configuration, and radio activity. Always measure your actual application's current waveform to validate the design.
| Mode | CPU | Wi-Fi/BLE | RTC | Typical current | Wake latency |
|---|---|---|---|---|---|
| Active (radio on) | Running | Transmitting/receiving | Active | 80–240 mA peak | — |
| Active (radio off) | Running | Off | Active | 20–30 mA | — |
| Modem sleep | Running | Between beacons | Active | ~20 mA avg | < 1 ms |
| Light sleep | Paused | Between beacons | Active | ~0.8 mA | < 1 ms |
| Deep sleep | Off | Off | Active | ~10 µA | ~100 ms (full boot) |
| Hibernation | Off | Off | Timer only | ~2.5 µA | full boot |
Active Mode
Active mode covers both "radio idle" (CPU running, no transmission) and "radio active" (CPU running plus Wi-Fi or BLE transmission). The current is dominated by the radio:
- Wi-Fi TX peak: up to ~240 mA at 802.11n peak throughput (device-dependent, see datasheet table).
- Wi-Fi RX: ~80 mA continuous.
- BLE advertising: ~15 mA peak per advertising event, short duration.
- CPU only (no radio): ~20–30 mA at 240 MHz; ~5 mA at 80 MHz.
For battery-powered designs, minimise time in radio-active mode. Connect to Wi-Fi, publish data, disconnect — do not maintain a continuous Wi-Fi connection if the device sends data only periodically.
Modem Sleep
Modem sleep keeps the CPU running but turns off the radio between DTIM beacon intervals. The CPU can still process sensor data, update a display, or run control loops while the radio is off between beacon windows.
/* Enable modem sleep after connecting to Wi-Fi */
esp_wifi_set_ps(WIFI_PS_MIN_MODEM); /* wake at every DTIM beacon */
/* or */
esp_wifi_set_ps(WIFI_PS_MAX_MODEM); /* wake at AP's DTIM interval * listen_interval */
Average current in modem sleep with a DTIM interval of 3 (≈ 307 ms): approximately 15–20 mA average, versus 80 mA for continuous receive. Modem sleep is the right mode for applications that need to receive incoming data (MQTT subscribe, HTTP server) but can tolerate DTIM-interval latency.
Light Sleep
Light sleep pauses the CPU and halts peripheral clocks, but preserves CPU state in SRAM. The Wi-Fi and BLE stacks can maintain connections during light sleep with modem sleep enabled (the radio wakes at beacon intervals automatically).
/* Configure wake sources before entering light sleep */
esp_sleep_enable_timer_wakeup(5000000ULL); /* wake after 5 seconds */
esp_sleep_enable_gpio_wakeup(); /* wake on GPIO level change */
gpio_wakeup_enable(GPIO_NUM_4, GPIO_INTR_LOW_LEVEL);
/* Enter light sleep (function returns when woken) */
esp_light_sleep_start();
/* Execution continues here after wake */
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
Light sleep wake latency is under 1 ms — the firmware continues execution immediately after esp_light_sleep_start() returns. This makes light sleep suitable for applications where a short response time to external events is needed but full deep sleep/boot time is unacceptable.
Deep Sleep
Deep sleep powers off the main CPUs and most peripherals. Only the RTC domain (RTC timer, RTC memory, RTC GPIOs, and the ULP co-processor) remains active. On wake, the chip restarts from app_main() — deep sleep is a partial power cycle.
#include "esp_sleep.h"
/* Wake after 30 seconds */
esp_sleep_enable_timer_wakeup(30ULL * 1000000ULL); /* microseconds */
/* Or wake on GPIO level (must be RTC GPIO) */
esp_sleep_enable_ext0_wakeup(GPIO_NUM_4, 0); /* wake when GPIO4 goes LOW */
/* Save state to RTC memory before sleep */
RTC_DATA_ATTR static int boot_count = 0; /* survives deep sleep */
RTC_DATA_ATTR static float last_temperature = 0.0f;
boot_count++;
last_temperature = read_sensor();
/* Enter deep sleep — does not return */
esp_deep_sleep_start();
RTC_DATA_ATTR marks a variable to be placed in RTC slow memory, which retains its value across deep sleep cycles. Up to 8 KB of RTC slow memory is available for this purpose on the ESP32 (check the specific variant's datasheet — sizes differ).
Wake sources from deep sleep:
esp_sleep_enable_timer_wakeup(useconds)— RTC timer, most common for periodic sensor logging.esp_sleep_enable_ext0_wakeup(gpio, level)— single RTC GPIO level.esp_sleep_enable_ext1_wakeup(gpio_mask, mode)— multiple RTC GPIOs, any-high or all-low.esp_sleep_enable_touchpad_wakeup()— capacitive touch (ESP32 classic only).esp_sleep_enable_ulp_wakeup()— ULP co-processor triggered wake.
ULP (Ultra-Low Power) Co-Processor
The ESP32 includes a ULP (Ultra-Low Power) co-processor — a simple finite-state machine that runs during deep sleep while the main cores are off. The ULP can:
- Read ADC channels periodically and compare to thresholds.
- Read and write RTC GPIOs.
- Access RTC memory.
- Wake the main core when a condition is met (e.g. ADC reading exceeds a threshold).
This allows the device to remain in deep sleep (10 µA) while the ULP (typically 100–300 µA additional when active, much less when idle) monitors a sensor without waking the main CPU for every sample.
The ULP is programmed in ULP assembly or using the ULP FSM API in ESP-IDF. For the ESP32-S3, the ULP is a RISC-V core that can run C code (CONFIG_ULP_COPROC_TYPE_RISCV).
/* Configure ULP to read ADC and wake main CPU if value > threshold */
#include "ulp.h"
#include "driver/rtc_io.h"
#include "driver/adc.h"
/* ULP programs are written in ULP assembly (or C for S3 RISC-V ULP) */
/* This example shows the ESP-IDF ULP FSM setup — see ESP-IDF examples for full code */
Battery Life Estimation
For a periodic sensor node that wakes every 30 seconds, connects to Wi-Fi for 2 seconds to send data, then returns to deep sleep:
Active time per cycle: 2 s at ~80 mA (Wi-Fi active) = 160 mAs
Deep sleep per cycle: 28 s at ~10 µA = 0.28 mAs
Total per cycle: ~160.3 mAs
Average current = 160.3 mAs / 30 s = ~5.3 mA average
With a 2000 mAh LiPo battery:
Runtime ≈ 2000 mAh / 5.3 mA = ~378 hours ≈ 15 days
To extend to months or years:
- Reduce Wi-Fi connection time per cycle (pre-connect to a faster DHCP, use static IP).
- Increase the sleep period (30 s → 5 min).
- Use a low-power variant (ESP32-C3 has lower active current than ESP32 classic).
- Cut power to external peripherals (sensors, LEDs) during sleep with a GPIO-controlled MOSFET switch — see GPIO configuration and output drive on the ESP32 for the
gpio_config()API.
For the full battery life estimation methodology — including multi-state current budgets, temperature derating, and design margins — see battery life calculation for embedded devices.
For battery-powered IoT product firmware optimisation, Zeus Design's firmware team designs ESP32 power management strategies from hardware architecture through firmware implementation.
Design Considerations
- Measure actual current with a high-bandwidth analyser. A multimeter averages current and misses short peaks. An ESP32 connecting to Wi-Fi draws a 200 mA peak for 10–20 ms during the DHCP transaction. These peaks matter for battery selection (voltage sag under pulse load) and for actual runtime estimates. Use a Nordic PPK2 or Otii Arc for accurate waveform capture.
- Set all GPIOs to a defined state before entering deep sleep. Floating input GPIOs can draw several hundred µA through internal substrate diodes depending on the driven voltage level. Call
gpio_deep_sleep_hold_en()for any output GPIOs that must maintain their level during deep sleep, and ensure all inputs have pull-ups or pull-downs. - Use static IP for faster Wi-Fi reconnect. DHCP adds 50–200 ms to each Wi-Fi connection cycle. For periodic-wake designs where every millisecond of radio-active time costs battery, configuring a static IP eliminates the DHCP transaction. Call
esp_netif_dhcpc_stop()andesp_netif_set_ip_info()before connecting.
Common Mistakes
- Forgetting to disable the watchdog before entering deep sleep. The task watchdog timer fires if deep sleep entry takes longer than expected (e.g. waiting for Flash cache to flush). Call
esp_task_wdt_delete(NULL)beforeesp_deep_sleep_start()if the task watchdog is enabled. - Assuming deep sleep current matches the datasheet without external loads. The datasheet specifies ESP32 SoC current only. External components — voltage regulators in quiescent mode, sensors with always-on oscillators, RGB LEDs with pull resistors — all add to the measured system current during deep sleep. Model and measure the full system, not just the SoC.
- Using non-RTC GPIOs as wake sources. Only specific GPIO pins support wake from deep sleep (the RTC GPIOs, typically GPIO0–GPIO5 and GPIO12–GPIO39 on the ESP32 — check the datasheet for the specific variant). Configuring a non-RTC GPIO as a wake source via
ext0produces an error or silent failure. Usertc_gpio_is_valid_gpio()to validate the chosen pin. - Not testing the full boot sequence on wakeup. After deep sleep,
app_main()runs from scratch including all global variable initialisers, NVS reads, and peripheral initialisations. Time this sequence — it can take 300–800 ms before the application begins useful work, which is significant for applications that wake frequently.
Frequently Asked Questions
- Why does my ESP32 deep sleep current measure much higher than the datasheet 10 µA?
- Several common causes: (1) GPIO pins left floating or driving loads during deep sleep — input pins without pull-ups or pull-downs can draw current through internal substrate diodes; set all output pins to a defined state before entering sleep. (2) External hardware (LED, voltage regulator, sensor) still powered during sleep — add a GPIO-controlled power switch to cut power to peripherals. (3) RTC peripherals left enabled — explicitly disable any RTC-domain peripherals that are not needed for the wake condition. (4) ESP32 variant: the ESP32-C3 and ESP32-S3 have different typical deep sleep currents than the original ESP32 — check the datasheet for the specific variant. Measuring actual current requires a current analyser or precision ammeter with sufficient bandwidth (Nordic PPK2 or Otii Arc are common choices); a regular multimeter misses short current spikes and reports an inaccurately low average.
- What data survives a deep sleep cycle?
- By default, nothing in regular SRAM survives deep sleep (the main SRAMs are powered off). What does survive: (1) RTC fast memory and RTC slow memory — data placed in these regions with the RTC_DATA_ATTR or RTC_RODATA_ATTR attribute in the code; up to 8 KB RTC fast and 8 KB RTC slow on the ESP32. (2) RTC GPIO pin states — GPIOs configured as RTC GPIO retain their output level during deep sleep. (3) RTC timer — the internal 150 kHz RTC oscillator continues running, enabling timed wake-ups. (4) External flash — data explicitly written to flash (NVS, file system) survives any reset. Use RTC_DATA_ATTR for variables that must be available immediately after wake without reading from flash.
- Can the ESP32 receive Wi-Fi data during light sleep?
- Yes, with modem sleep enabled and the DTIM beacon interval configured. In light sleep mode with Wi-Fi modem sleep, the ESP32 wakes periodically at the configured DTIM interval (typically 102.4 ms × DTIM count, configurable in the access point) to receive buffered packets from the router. The ESP32 wakes, receives any pending packets from the AP's buffer, and returns to light sleep. TCP connections are maintained during this process. This is the standard mechanism for always-on Wi-Fi devices that need to receive incoming data (e.g. MQTT subscribe) with reduced average power. BLE connections can similarly be maintained during light sleep at the BLE connection interval.
References
Related Questions
ESP32 Variants Compared: How Do You Choose the Right One?
Compare ESP32 variants: ESP32 classic, S3 (ML/USB), S2 (USB), C3 and C6 (RISC-V BLE+WiFi), and H2 (Thread/Zigbee). When to choose each.
ESP-IDF vs Arduino for ESP32: Which Framework Should You Use?
ESP-IDF gives full FreeRTOS control and is production-grade; Arduino is faster to start. Covers the differences, limitations of each, and when to switch.
How Do You Set Up Wi-Fi and Provision an ESP32 Device?
Covers ESP32 Wi-Fi station and AP mode in ESP-IDF, event-loop connection handling, SoftAP provisioning, and the ESP32 HTTP server for local API endpoints.
How Do You Use GPIO, ADC, and Timers on the ESP32?
ESP32 GPIO, ADC, and timers in ESP-IDF: pin configuration, interrupts, ADC calibration and attenuation, and periodic timers with esp_timer and GPTimer.
How Do You Calculate Battery Life for an Embedded Device?
Battery life = capacity (mAh) ÷ average current (mA). Learn how to calculate average current for embedded devices with active and sleep states.
How Do You Minimise Current Draw on an nRF52 in BLE Applications?
Minimise nRF52 BLE current draw: DCDC converter, advertising interval, connection interval, System OFF mode, Zephyr PM, and PPK2 measurement techniques.
Related Forum Discussions
ESP32 keeps dropping Wi-Fi after 20–30 minutes in deployed location — reconnect loop doesn't always recover
Having a frustrating one. Built an ESP32 environmental monitor (SHT40 temp/humidity, reports to an MQTT broker every 5 minutes). Works flawl
Is a double-sided PCB enough for a simple ESP32 sensor board, or should I go multi-layer?
Building a little battery-powered sensor board around an ESP32 module (the kind with the PCB antenna already built into the module, not desi