Operating ESP32 in Power-Saving Mode: Months of Battery Life – Deep Sleep, Wake Triggers, and Measurements
Achieving months-long battery life with an ESP32 is absolutely feasible — but only if you optimize consistently for average current rather than “peak current.” The ESP32 can briefly draw tens to hundreds of milliamps during radio operation, but if these peaks are rare and short and you spend most of the time truly in Deep Sleep, the average current lands in the double-digit microamp range. This is the crucial factor that determines whether your project will last weeks, months, or just days.
The kicker: Many people measure “Deep Sleep” on a DevKit and suddenly see 0.2–2 mA. This is not proof against Deep Sleep, but almost always a sign of board peripherals (USB-UART, LDO, power LED, pullups, sensors), incorrect pin states, or measurement errors. On a bare module, single-digit µA is realistic — Espressif shows for example 8.14 µA for an ESP32-S3-WROOM-1 in Deep Sleep measured with a Joulescope.
1) The Most Important Lever: Average Current Instead of Instantaneous Current
Battery life roughly follows:
Runtime (h) = Capacity (mAh) / Average Current (mA)
The average current derives from sleep and active phases:
I_avg = (I_active · t_active + I_sleep · t_sleep) / (t_active + t_sleep)
This quickly shows why “a few hundred µA too much” can ruin your months:
- Good Setup: 10 µA sleep, every 10 minutes 0.2 s active at 80 mA → I_avg ≈ 36–37 µA → theoretically very long runtime (practically limited by self-discharge, temperature, radio quality, battery chemistry).
- DevKit Reality: 500 µA sleep (board/regulator only), every 10 minutes 1 s active at 80 mA → I_avg ≈ 0.63 mA → from 2400 mAh roughly ~5 months (without safety margin).
This is the core: Deep Sleep only yields “months” when you really press sleep current into the µA range and keep active time short.
2) Sleep Modes Brief Overview: What Truly Enables Months?
For “months on battery,” Deep Sleep is the standard tool. Light Sleep can help if you need faster response or want RAM/peripherals to remain running — but it costs orders of magnitude more.
Some reliable reference values from datasheets:
- ESP32 (classic): Deep sleep with RTC timer + RTC memory ~10 µA, Hibernation (RTC timer only) ~2.5 µA; with active ULP coprocessor, about ~0.15 mA.
- ESP32-S3: Light sleep typically 240 µA; Deep sleep typically 7–8 µA (depending on RTC peripherals enabled); “Power off” typically ~1 µA.
Important: These values are for the chip/module under defined conditions — not automatically for your entire board.
3) Deep Sleep in Practice: You Don't Wake Up — You Boot Anew
Deep Sleep isn’t “press pause.” The ESP32 shuts down the CPUs; upon waking, in many setups it’s like a reset: your program restarts at setup()/app_main().
That means:
- You need clean boot logic ("Did I just start fresh or wake from Deep Sleep?").
- Store states either in RTC memory (survives Deep Sleep) or flash (NVS/EEPROM), depending on write frequency.
In ESP-IDF you can place variables in RTC memory (e.g., with RTC_DATA_ATTR). The ESP-IDF documentation also describes how Deep Sleep variables fit in RTC memory and how memory regions are assigned.
Minimal flow (ESP-IDF, simplified):
#include "esp_sleep.h"
#include "esp_log.h"
RTC_DATA_ATTR int boot_count = 0;
void app_main(void) {
boot_count++;
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
// ... read sensor, send data, etc.
// Timer wakeup in 10 minutes:
esp_sleep_enable_timer_wakeup(10ULL * 60ULL * 1000000ULL);
esp_deep_sleep_start();
}
Arduino looks similar:
#include "esp_sleep.h"
RTC_DATA_ATTR int bootCount = 0;
void setup() {
bootCount++;
// ... do work
esp_sleep_enable_timer_wakeup(10ULL * 60ULL * 1000000ULL);
esp_deep_sleep_start();
}
void loop() {}
4) Wake Triggers: Timer, EXT0, EXT1, Touch, ULP — and Typical Pitfalls
The ESP32 has several wake sources you can also combine. In practice for battery devices, the most common are:
Timer Wakeup
The classic: wake up every X minutes, measure, send, sleep.
In ESP-IDF it is esp_sleep_enable_timer_wakeup(time_in_us).
Note that the RTC slow clock isn’t ultra-precise — good for “about every 10 minutes,” unsuitable for a precise clock.
EXT0 (one pin, level, RTC-IO)
EXT0 is great when you have exactly one wake pin (e.g., button, reed switch). It uses RTC_IO and needs RTC-IO capable pins. ESP-IDF lists for ESP32 e.g. GPIO 0, 2, 4, 12–15, 25–27, 32–39.
Important limitation: On ESP32, EXT0 cannot be combined with Touch or ULP as wake source (conflicts).
EXT1 (multiple pins, RTC controller)
EXT1 is often better if you need multiple wake pins. Advantage: EXT1 works even if you turn off RTC peripherals in sleep; disadvantage: internal pullups/pulldowns then don’t work reliably — you need external resistors or explicitly keep RTC peripherals on. Espressif describes this thoroughly in ESP-IDF docs for EXT1 with a note on the HOLD mechanism.
Touch Wakeup
Practical for touch buttons but can add extra current depending on configuration and conflicts on ESP32 with EXT0.
If maximal lifetime is desired, Touch often becomes a power budget trade-off.
ULP Coprocessor
ULP can make measurements while sleeping and wake only if needed (e.g., “wake me if ADC threshold exceeded”). This is powerful but not “free”: classic ESP32 datasheet states notably more than 10 µA when ULP powered on (~0.15 mA range).
The S3’s ULP is more modern, but still: plan carefully with measurements rather than hope.
5) Why DevBoards Often Can’t Last Months — and How to Do It Right
If you want “months,” hardware is at least as important as calling esp_deep_sleep_start().
The Three Usual Current Hogs
Voltage Regulators (IQ / Quiescent Current) Many dev boards have LDOs with tens to hundreds of µA quiescent current. This immediately kills your µA targets. For battery operation, look for LDOs/switching regulators with quiescent current in the single-digit µA range (depending on load profile).
USB-UART and Power LED CP2102/CH340/FTDI and a constantly lit LED are nice in the lab but poison for battery devices. Sometimes you only get low current by physically removing or custom designing your own board.
Sensors and Voltage Dividers Always Powered A voltage divider for battery measurement with 2×100 kΩ already draws ~16 µA at 3.3 V — possibly more than the whole ESP32 in Deep Sleep. Solutions: use larger resistors (mind ADC input), activate measurement only briefly (MOSFET), or use a fuel gauge/ADC with enable pin.
Pin States: Floating Pins Kill
Pins floating in sleep can cause leakage or partially turn on external components. Especially critical are inputs connected to sensors/transistors and "strapping pins." In Deep Sleep, you should:
- Keep outputs at defined levels or use the "Hold" feature,
- Design external pullups/pulldowns carefully,
- For EXT1, remember internal pullups don’t work if RTC peripherals are off (solve externally or keep RTC peripheral on).
6) Real Measurements: What You Can Expect (and Why You Often Miss the Mark)
Chip/Module: µA Are Real
For ESP32-S3, datasheet shows Deep Sleep typically 7–8 µA (depending on RTC peripherals). Espressif also measured practically: On ESP32-S3-WROOM-1 measurement instructions give 8.14 µA Deep Sleep and ~23.88 mA active (waveform, example project, touch wakeup disabled).
Classic ESP32 datasheet lists around 10 µA (RTC timer + RTC memory) as magnitude.
DevBoards: 100 µA to >1 mA Are Sadly Normal
Seeing 100 µA in Deep Sleep instead of 10 µA is a known pattern (usually board design/regulator/leakage paths).
If your board runs towards ~1 mA in Deep Sleep, it’s almost never the ESP32, but peripherals, pins, or power circuits.
The conclusion is simple: If you want months, either use a board specifically designed for low power or make your own small board/breakout where you control the power path.
7) Measure Like a Pro: Otherwise You're Flying Blind
Deep Sleep currents in single-digit µA range are tricky to measure because many multimeters create noticeable burden voltage in the µA range. This can alter system behavior (brownout, regulator states, wrong peaks).
Best practice:
- Measure in series on the battery path, but with tools suited to dynamic currents (Joulescope/Otii/Nordic PPK2 or a clean shunt + oscilloscope). Espressif uses Joulescope in their guide and shows the waveform.
- Watch for peaks + sleep: a measurement of “0.01 mA” alone means nothing if your device does 2 seconds of Wi-Fi every minute.
- Measure final setup: connected sensors, pullups in place, housing/temperature similar to field setup.
A practical workflow: first stabilize sleep current (µA), then shorten active phase (ms), then optimize radio (reduce retries, improve RSSI).
8) Example Budget: Hard Calculation of "Months"
Let's take a typical battery logger:
- Wakes every 10 minutes
- 200 ms active “all done” (sensor read + packet send)
- Deep Sleep
Case A (clean): Sleep 10 µA, active 80 mA · 0.2 s / 600 s → Average roughly ~36–37 µA That’s the world where months are easily achieved.
Case B (DevKit):
Sleep 500 µA, active 80 mA · 1 s / 600 s
→ Average roughly 0.63 mA
With 2400 mAh you get roughly **5 months**.
And now the point many overlook: if instead of 500 µA sleep you get 50 µA, you often gain more than saving “another 50 ms of Wi-Fi.”
9) Event-Driven Sensoring and Short Radio Windows for Maximum Runtime
In interplay of sensors, Wi-Fi, and Bluetooth, you gain most by running everything consistently event- and packet-oriented: sensors stay off or in their own low-power modes until measuring is truly needed, and you power them off completely if possible via load switch (P-MOSFET/power IC) instead of “just putting them to sleep via I²C.”
Watch out for hidden continuous loads like I²C bus pullups, voltage dividers, or INT pins floating in sleep causing leakage; defined levels and clean pull resistors are mandatory.
For sensors, batching measurements often pays off: wake briefly, initialize sensor, take few samples (instead of a long warm-up), locally aggregate results (mean/min/max/delta logic), then radio only if something relevant changed.
For Wi-Fi, the gold standard is a very short online window: avoid scans (use known SSID/BSSID/channel), speed up connection, keep payload small, select protocols to avoid long handshakes (e.g. few requests instead of permanently open socket; TLS is possible but handshake costs time and energy).
Bluetooth should generally be used as BLE for battery devices: ideally only advertising with long intervals, and if connection needed, choose “lean” connection parameters (long intervals, short active time, few notifications, then disconnect immediately).
Avoid running Wi-Fi and BLE continuously in parallel: coexistence costs not only airtime but often prolongs CPU active phases.
The golden thread: operate sensors with short, well-defined measurement windows and radio rarely, briefly, and deterministically online — then sleep time dominates and runtime progresses from weeks to months.
10) Checklist: The Top 10 Mistakes Preventing Months
If you don’t reach µA range in measurements, go through these points — the error is almost always here:
- Regulator quiescent current too high (wrong LDO/step-down)
- USB-UART chip still on battery path
- Power LED or level shifter draws current permanently
- Sensors still running because no real shutdown
- Voltage divider for battery measurement draws continuously
- Pins floating causing leakage (external/strapping)
- Pullups/pulldowns wrong: with EXT1 + RTC peripheral off usually need external resistors
- Using Touch/ULP/EXT0 in a combination conflicting on ESP32
- Wrong measurement (burden voltage / wrong range / wrong point)
- Testing with wrong supply (USB 5 V → different path than battery)
11) Practical Example: Power-Saving IoT Design with ESP32 and E-Ink
A very concrete practical example is my FireBeetle 2 ESP32-E: I use it because it’s compact for IoT and provides a solid base for battery operation (including charging electronics) — and with this I achieved a system with sensors, E-Ink display, and Wi-Fi running on a 3000-mAh battery for about 1.5 years.
The key was that the E-Ink only draws power on refresh, sensors are mostly off or in their own sleep modes, and Wi-Fi runs really only in short bursts: wake, measure, update display if needed, send data, immediately back to Deep Sleep.
Roughly this corresponds over time to about 0.23 mA (≈ 230 µA) average current — exactly the order of magnitude that proves “months to years” are realistic if sleep time dominates and all active phases are short and planned.
Conclusion
“Months on battery” with ESP32 is no magic trick. It’s careful engineering: use Deep Sleep consistently, choose wake triggers correctly, trim board hardware to low power and verify with real measurements.
On module level, ~7–10 µA Deep Sleep is realistic (depending on ESP32 variant and RTC configuration).
The crucial step is then that your entire system (regulator + sensors + pullups + board peripherals) does not transform “10 µA chip” into “500 µA device.”