Phone:

Hidden from the page source until you click: friction against scrapers, not a guarantee.

Email:

[email protected]

Category:

Embedded

Tags:
  • esp32
  • embedded
  • low-power
  • battery
  • iot
  • power-management

ESP32 Deep Sleep Power Budgeting: Why 'Low Power' Mode Still Drains a Battery in Days

The datasheet says deep sleep on an ESP32 pulls somewhere between 10 and 150 microamps depending on what you keep alive in the RTC domain. Do the arithmetic against a 2000mAh LiPo and you get a number with "years" in it. Then you build the actual board, flash the actual firmware, and the battery is flat in under two weeks. This is a common enough experience that it's worth working through where the current actually goes, because it's almost never the chip's fault.

The naive calculation

The seductive maths goes like this: 2000mAh battery, 100uA deep sleep draw, therefore 2000 / 0.1 = 20,000 hours, call it two and a bit years. That number is real, but it only describes the ESP32 die itself, sitting in deep sleep, forever, with everything else on the board switched off by magic. Nothing else on your board is switched off by magic.

Culprit one: the voltage regulator you didn't think about

Most ESP32 dev boards, and a lot of hand-built ones, use a cheap linear regulator like the AMS1117 to get from a 5V or LiPo rail down to 3.3V. The AMS1117 has a quiescent current in the region of 5 to 10mA. Not microamps, milliamps. That current flows continuously, whether the ESP32 is transmitting on Wi-Fi or sitting in its deepest sleep state, because the regulator itself doesn't know or care what the downstream chip is doing.

Put a 6mA quiescent regulator between your battery and an ESP32 that's asleep at 100uA, and the regulator is responsible for roughly 98% of your total current draw. Your 2000mAh battery now lasts 2000 / 6 ≈ 333 hours, about 14 days, regardless of how carefully you tuned the firmware's sleep behaviour. This is the single most common reason a "low power" ESP32 project dies in under a month: nobody checked the regulator's own datasheet.

The fix is to spend the extra thirty pence on a regulator designed for battery applications: something like the MCP1700 (around 1.6uA quiescent) or a TPS7A02 (tens of nanoamps). If you're running directly off a LiPo through a boost or buck-boost converter instead, check that converter's no-load quiescent current with the same suspicion, some switching converters are worse than a linear regulator at light load.

Culprit two: the wake-up spike, not the sleep floor

Even with a sensible regulator, the number that actually determines battery life is rarely the sleep current. It's what happens during the brief windows when the chip is awake. Waking from deep sleep, reconnecting to Wi-Fi, and doing anything useful with TLS involves current spikes of 150 to 300mA for anywhere from a few hundred milliseconds to a couple of seconds, depending on association time, DHCP, and whatever protocol handshake you're doing on top.

A duty-cycle calculation makes this concrete. Average current is just a weighted mix of the sleep and active states:

avg_current = (sleep_fraction * sleep_current) + (active_fraction * active_current)

Take a board with a good regulator, 100uA deep sleep current, waking once a minute for 300ms at 150mA to read a sensor and publish over Wi-Fi:

active_fraction = 0.3s / 60s = 0.005
avg_current = 0.995 * 0.0001A + 0.005 * 0.15A
            = 0.0000995A + 0.00075A
            = 0.00085A  (0.85mA)

2000mAh / 0.85mA ~= 2350 hours ~= 98 days

Stretch the wake interval from once a minute to once every ten minutes and the active fraction drops by a factor of ten, but the average current doesn't drop by the same factor, because the active term already dominates:

active_fraction = 0.3s / 600s = 0.0005
avg_current = 0.9995 * 0.0001A + 0.0005 * 0.15A
            = 0.0000999A + 0.000075A
            = 0.000175A (0.175mA)

2000mAh / 0.175mA ~= 11,400 hours ~= 475 days

Going from a 60 second wake interval to a 600 second one gets you roughly 5x the battery life, not 10x, because the wake-up spike itself, not the interval between them, is the thing that costs you. Shortening the active window matters more than almost anything else you can do. Reconnecting Wi-Fi from a cold association can take 200 to 500ms before you've sent a single useful byte; that's pure overhead sitting in the 150mA+ band.

Getting the active window down

A few things actually move the needle here, roughly in order of effort versus payoff:

  • Cache the AP's BSSID and channel in RTC memory (it survives deep sleep) and pass them to esp_wifi_set_config so the radio doesn't have to scan every channel to find the access point again.
  • Use a static IP instead of DHCP if your network allows it, DHCP negotiation is pure dead time with the radio drawing current.
  • Avoid a fresh TLS handshake on every wake if you can; session resumption or a lighter transport (MQTT over an already-warm connection, or ESP-NOW if you control both ends and don't need internet routing) cuts the active window substantially.
  • Do sensor reads and any SPI/I2C work before bringing the radio up, not after, so the radio's active time is spent purely on the send.

Culprit three: everything else still awake

Deep sleep powers down the CPU cores and most peripherals, but it doesn't power down anything external to the chip. An I2C sensor left un-configured for its own low power mode will happily keep drawing its own quiescent current the whole time the ESP32 is "asleep". An LED tied to a GPIO through a resistor to the 3.3V rail, rather than switched, does the same. Floating GPIOs can also leak more current than you'd expect through internal pull structures; the ESP-IDF exposes rtc_gpio_isolate() for exactly this, cutting a pin off from its digital circuitry before sleep so it stops contributing:

// Before entering deep sleep: isolate any RTC-capable GPIO
// that's floating or driving something you don't want powered.
rtc_gpio_isolate(GPIO_NUM_12);

esp_sleep_enable_timer_wakeup(600 * 1000000ULL); // 600s, in microseconds
esp_deep_sleep_start();

If you're driving an external sensor or an SD card, switch its supply with a MOSFET or load switch controlled by a GPIO that's high only during the active window, rather than leaving it permanently powered and hoping it sleeps well on its own.

Measuring it properly

None of the above matters if you're guessing. A cheap way to see the real profile is a multimeter with a min/max/average hold function in series with the battery lead, but it can't resolve the shape of a 300ms current spike against a 100uA baseline, the dynamic range is too wide for a single reading to be meaningful. Something like a Nordic Power Profiler Kit II or a Qoitech Otii, or even a shunt resistor into an oscilloscope, will show you the actual waveform: a flat low floor punctuated by spikes, and it's usually obvious at a glance which part of that picture is eating the battery.

One side effect worth watching for once you're this close to the noise floor on a low-capacity battery: a fresh CR2032 or a partly discharged LiPo has enough internal resistance that a sudden 150mA draw sags the rail hard enough to trip the ESP32's brownout detector, resetting the chip mid-transmission. If your device works fine on the bench with a lab supply but resets intermittently on battery, especially as the battery ages, that voltage sag under load is the first thing to check, not the firmware.