ESP32 Power Management & Deep Sleep¶
Power is the hidden constraint of every battery-operated project. A device that drains a 2000 mAh LiPo in 6 hours instead of 6 months is a design failure. These patterns get you to <100 Β΅A sleep current.
Power Architecture Overview¶
[LiPo / 18650]
β
βββ Charger IC (TP4056 or MCP73831)
β
βββ Protection IC (DW01A / FS8205A β short, overcurrent, overdischarge)
β
βββ Buck or LDO regulator β 3.3 V rail
β
βββ ESP32 (3.3 V)
βββ Sensor VCC (3.3 V or 1.8 V β check datasheet)
βββ E-Ink VCC (3.3 V)
LDO (e.g., AMS1117-3.3): simple, cheap, dissipates excess voltage as heat. Fine for USB-powered projects or short battery life (<1 week).
Buck converter (e.g., MP2307, TPS563201): 85β95% efficient. Use for any project aiming at weeks or months on battery.
ESP32 Sleep Modes¶
| Mode | CPU | Peripherals | RTC | Typical current |
|---|---|---|---|---|
| Active (WiFi TX) | ON | ON | ON | 240β320 mA |
| Active (no WiFi) | ON | ON | ON | 30β80 mA |
| Light sleep | Paused | Paused | ON | 0.8β1.0 mA |
| Deep sleep | OFF | OFF | ON | 10β150 Β΅A |
| Hibernation | OFF | OFF | Minimal | ~5 Β΅A |
For a readβrenderβsleep device, deep sleep is the right choice. WiFi-free designs can hit 10β30 Β΅A.
Deep Sleep API¶
#include <esp_sleep.h>
// Timer wake: wake after N microseconds
esp_sleep_enable_timer_wakeup(300ULL * 1000000ULL); // 5 minutes
// GPIO wake (level trigger on GPIO 33, active LOW)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 0);
// Multiple GPIO wake (any of the listed pins, active LOW)
esp_sleep_enable_ext1_wakeup(
(1ULL << GPIO_NUM_33) | (1ULL << GPIO_NUM_34),
ESP_EXT1_WAKEUP_ANY_LOW
);
// Enter deep sleep β execution does NOT return here
esp_deep_sleep_start();
After waking from deep sleep, the ESP32 re-runs setup() (Arduino) or app_main() (IDF). Treat every boot as cold start unless you check esp_sleep_get_wakeup_cause().
RTC Memory β Surviving Deep Sleep¶
Normal RAM is wiped on deep sleep. Use RTC-tagged variables to persist state:
RTC_DATA_ATTR uint32_t bootCount = 0;
RTC_DATA_ATTR float lastTempC = 0.0f;
RTC_DATA_ATTR uint8_t errorFlags = 0;
void setup() {
bootCount++;
auto wakeReason = esp_sleep_get_wakeup_cause();
if (wakeReason == ESP_SLEEP_WAKEUP_TIMER) {
// Normal scheduled wake β skip re-init
}
}
RTC memory is 8 KB total on ESP32. Don't stuff large buffers there.
Peripheral Power Gating¶
Power-gate sensors and peripherals that draw current during sleep:
#define SENSOR_POWER_PIN 26 // N-channel MOSFET gate or PNP base
void powerSensorsOn() {
pinMode(SENSOR_POWER_PIN, OUTPUT);
digitalWrite(SENSOR_POWER_PIN, HIGH);
delay(20); // stabilization
}
void powerSensorsOff() {
digitalWrite(SENSOR_POWER_PIN, LOW);
}
Use a P-channel MOSFET (e.g., AO3407) on the high side for clean power switching. Check that pinMode on unused GPIOs is INPUT before deep sleep β floating driven GPIOs waste current.
Battery Safety Checklist¶
- Never charge LiPo above 4.2 V β use a dedicated charger IC, not a raw voltage source
- Never discharge LiPo below 3.0 V β implement software brownout cutoff at 3.3 V
- Over-discharge protection IC is mandatory β don't rely on software alone
- Use a fuse or polyfuse on the battery positive terminal (250 mAβ1 A for typical IoT loads)
- Charge in a fire-safe location during development β LiPo thermal runaway is real
- Short-circuit protection β DW01A + FS8205A combo is standard for single-cell packs
- No puncture, no bending, no compression of Li cells
- Temperature monitoring β halt charging if cell > 45 Β°C
- Labeled polarity β LiPo connectors are not polarized by default; use JST-PH with correct orientation
Measuring Real Current Draw¶
Theoretical estimates are usually wrong. Measure:
- ΞΌCurrent Gold / Nordic PPK2 β purpose-built for embedded current measurement (nA resolution)
- Multimeter in series β works for mA range; too slow for microsecond peaks
- Current-sense resistor + oscilloscope β 0.1 Ξ© shunt on battery negative; voltage = current Γ R
Profile all phases: - Wake β init: peak current duration - Sensor read: IΒ²C/SPI active current - Render: e-ink update peak (can be 20β30 mA for 1β2 seconds) - Sleep: steady-state idle
Budget example for 5-minute update cycle on 2000 mAh cell:
| Phase | Duration | Current | Energy (Β΅Ah) |
|---|---|---|---|
| Wake + sensors | 500 ms | 40 mA | 5555 |
| E-ink update | 2 s | 25 mA | 13888 |
| Deep sleep | 297 s | 20 Β΅A | 1650 |
| Total per cycle | 300 s | β | ~21 mAh |
2000 mAh Γ· 21 mAh/cycle Γ (300 s / 3600) β ~8 days battery life. Real-world: ~6 days with regulator and protection losses.
Brownout Detection¶
// Reduce brownout voltage threshold (default ~2.45V is conservative)
// Useful if LDO dropout is near 3.3V rail
WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); // disable (risky, for debugging only)
// Better: implement software low-battery detection
#include <driver/adc.h>
float readBatteryVoltage() {
// Voltage divider: BAT+ β 100k β GPIO35 β 100k β GND
// Full scale 3.3 V ADC = 4.2 V battery
int raw = analogRead(35);
return (raw / 4095.0f) * 3.3f * 2.0f; // 2.0 = divider ratio
}
Halt and enter hibernation when battery < 3.2 V to prevent over-discharge.
See Also¶
See also
- ESP32 Hardware & Electrical Safety β power circuits and GPIO safety
- ESP32 E-Ink Environmental Monitor β deep sleep applied in a battery sensor node
- ESP32 RF Room Light Controller β always-on USB controller: why deep sleep is skipped and what to use instead
- ESP32 Programming Architecture β how to structure the wake/sleep state machine