Articles / Embedded

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

Why an ESP32 in Deep Sleep Still Flattens the Battery

The datasheet says an ESP32 in deep sleep pulls 10 to 150 microamps. Against a 2000mAh LiPo that gives you 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 happens often enough that it's worth working through where the current actually goes, because it's almost never the chip's fault. (The exact sleep figure depends on what you keep alive in the RTC domain.)

The naive calculation only describes the bare chip

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 of 5 to 10mA. Not microamps, milliamps.

That current flows continuously, whether the ESP32 is transmitting on Wi-Fi or in its deepest sleep state. The regulator doesn't know or care what the downstream chip is doing.

Put a 6mA quiescent regulator in front of an ESP32 asleep at 100uA, and the regulator is roughly 98% of your total draw. Your 2000mAh battery now lasts 2000 / 6 ≈ 333 hours, about 14 days, however 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:

  • the MCP1700, at around 1.6uA quiescent
  • a TPS7A02, at tens of nanoamps

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 costs more than the sleep floor

Even with a sensible regulator, the number that decides 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 spikes of 150 to 300mA. They last 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. 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. The wake-up spike itself, not the interval between spikes, is what costs you.

So 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.

Four ways to shrink the active window

These actually move the needle, 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 off-chip is still awake

Deep sleep powers down the CPU cores and most peripherals, but it doesn't power down anything external to the chip. The usual suspects:

  • An I2C sensor left un-configured for its own low power mode keeps 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 leak more current than you'd expect through internal pull structures.

For that last one, the ESP-IDF exposes rtc_gpio_isolate(). It cuts 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. Control it from a GPIO that's high only during the active window, rather than leaving the peripheral permanently powered and hoping it sleeps well on its own.

You need to see the waveform, not a single reading

None of the above matters if you're guessing. A multimeter with a min/max/average hold function, in series with the battery lead, is the cheap option.

It can't resolve the shape of a 300ms spike against a 100uA baseline, though. The dynamic range is too wide for a single reading to be meaningful. These will show you the actual waveform:

  • a Nordic Power Profiler Kit II
  • a Qoitech Otii
  • even a shunt resistor into an oscilloscope

What you see is a flat low floor punctuated by spikes, and it's usually obvious at a glance which part of that picture is eating the battery.

Quick detour: the battery can reset your chip

This one's sneaky 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. Hard enough to trip the ESP32's brownout detector and reset 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, check that voltage sag under load first, not the firmware.