ESP32 Deep Sleep & Battery Optimization for Solar/Battery Projects
Why This Matters
An ESP32 running normally draws roughly 80-260mA depending on WiFi activity — that's single-digit hours on a small battery. In deep sleep, draw drops to single-digit microamps — the difference between a battery project lasting days versus months or years. Getting deep sleep right is the single highest-leverage thing you can do for any battery-powered ESP32 project.
Basic Deep Sleep (Timer Wake)
#define SLEEP_SECONDS 300 // wake every 5 minutes void setup() { // ... do your sensor reading / transmission work here ... esp_sleep_enable_timer_wakeup(SLEEP_SECONDS * 1000000ULL); // microseconds esp_deep_sleep_start(); } void loop() { // never reached — deep sleep restarts execution from setup() on wake }Critically: deep sleep wake is a full restart, not a resume — your code re-runs from setup() every wake cycle, not from wherever it called sleep. Design your logic around this rather than expecting state to persist normally.
Preserving State Across Sleep Cycles
Use RTC_DATA_ATTR to mark variables that survive deep sleep (backed by a small always-on RTC memory region, unlike normal RAM which is fully powered down):
RTC_DATA_ATTR int bootCount = 0; void setup() { bootCount++; // bootCount persists correctly across sleep cycles }External Wake Sources (Not Just Timer)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_13, HIGH); // wake on this pin going high esp_deep_sleep_start();This is the pattern used in the mailbox/doorbell guide — waking specifically on a PIR or reed switch trigger rather than (or in addition to) a fixed timer interval.
The WiFi Reconnection Cost
Reconnecting to WiFi from a cold boot after deep sleep takes real time (typically 1-3 seconds) and draws full active current the whole time — for battery-critical projects, this reconnection overhead often dominates total power budget more than the sensor reading itself. Two mitigations: cache the WiFi channel/BSSID in RTC memory from the previous connection to speed reconnection, or batch multiple sensor readings in RTC memory across several sleep cycles and only connect/transmit once every N wakes rather than every single wake.
Solar Charging Considerations
If pairing with a solar panel + LiPo charging circuit (TP4056-based modules are common and cheap): size the panel for your worst-case (cloudy day, winter sun angle) charging scenario, not best-case — a project that works fine in summer testing and dies in December is a common and avoidable mistake. Add battery voltage monitoring (a simple voltage divider into an ADC pin) so your code can reduce transmission frequency or skip non-critical work when battery is low, rather than just running until it browns out unpredictably.
Measuring Actual Current Draw
Datasheet deep sleep current figures are best-case — actual draw depends on which peripherals you've properly disabled before sleeping. A USB power meter with current measurement, or better, a dedicated tool like a Nordic Power Profiler or even a multimeter in series with the battery, tells you your project's real numbers — don't just trust the datasheet figure when calculating expected battery life.
Related Guides
- Build a Long-Range ESP32 LoRa Sensor Node for Off-WiFi-Grid Monitoring
- Buck, Boost, and Buck-Boost Converters Explained: Choosing and Wiring a Switching Regulator
- Battery Fuel Gauge ICs for Lithium Projects: MAX17048, BQ27441, and Coulomb Counting Explained
- Driving E-Paper Displays with ESP32 and Arduino: SPI Wiring, GxEPD2, and Partial Refresh
- Build a Battery-Powered ESP32 Smart Mailbox Notifier
- How to Build a LiPo Battery Charger with the TP4056: Circuits, Safety, and BMS Integration
- How to Program Addressable LED Strips: WS2812B Patterns, Effects, and Power Design
- I2C vs SPI vs UART: How to Choose and Use Serial Communication Protocols