10 Things Draining Your ESP32 Battery That Aren't the Radio

Common hidden power drains in ESP32 projects beyond the radio, like GPIO leakage and voltage regulators

You disabled Wi-Fi. You disabled BLE. You called esp_deep_sleep_start(). The datasheet says 10 µA. Your meter says 8 mA. That’s an 800x gap, and the radio is completely off.

Where’s the other 7,990 µA going?

It’s hiding in 10 places you probably haven’t checked. Each one has a specific cost in microamps, and each one has a fix. Some are firmware changes you can make in 5 minutes. Others require a soldering iron or a different board. I’ll tell you which is which.

This assumes you’re measuring with a PPK2, µCurrent Gold, or shunt resistor. If you’re using a bench multimeter on the mA range, you might not catch the smaller items, but the big ones will jump out.

1. The USB-UART Bridge Is Burning 15 mA While Your ESP32 Sleeps

That CP2102 or CH340 on your dev board? It doesn’t care that your ESP32 is in deep sleep. It’s powered from the USB rail (or worse, from the 3.3V rail), and it draws 10 to 20 mA continuously.

This is probably your single largest drain. It dwarfs everything else on this list combined.

Affected: every dev board with an onboard USB-to-serial chip. ESP32-S3 and C6 dev kits with USB-JTAG have a similar issue. The USB-JTAG peripheral draws less, around 0.5 mA when the host isn’t connected, but it’s still not zero.

Fix: For production, use bare modules. For development, cut the bridge’s power trace or put it behind a load switch you can kill before sleep. There’s no firmware fix for an external chip that’s always on.

2. Your Voltage Regulator Drinks More Than Your Microcontroller

The AMS1117-3.3 is on most cheap ESP32 dev boards. Its quiescent current is roughly 5,000 µA. That’s 5 mA just sitting there, doing nothing but keeping the regulator alive.

Regulator       | Iq (typical) | Common On
----------------|-------------|------------------
AMS1117-3.3     | ~5,000 µA   | Most cheap dev boards
AP2112K-3.3     |    ~55 µA   | Adafruit, some S3 boards
HT7333          |     ~4 µA   | DIY battery builds
TPS7A02         |  ~0.025 µA  | Ultra-low-power custom

The difference between the worst and best regulator here is five orders of magnitude. Swapping from an AMS1117 to an HT7333 saves you roughly 5 mA with zero firmware changes.

Fix: Hardware only. Replace the regulator, or design your custom board with a low-Iq part from the start. The TPS7A02 at 25 nA is effectively free in your power budget.

3. Floating GPIOs Leak Current Pin by Pin

An input pin floating between logic levels lets shoot-through current flow in the input buffer. Each floating pin can waste 1 to 50 µA depending on the voltage it’s hovering at. With 20+ unused pins, that adds up fast.

Affected: all ESP32 variants.

Fix: Isolate RTC-capable pins and disable the rest before sleep:

// For RTC GPIOs (pins that survive deep sleep)
rtc_gpio_isolate(GPIO_NUM_12);
rtc_gpio_isolate(GPIO_NUM_15);

// For non-RTC GPIOs
gpio_config_t io_conf = {
    .pin_bit_mask = UNUSED_PIN_MASK,
    .mode = GPIO_MODE_DISABLE,
    .pull_down_en = GPIO_PULLDOWN_ENABLE,
};
gpio_config(&io_conf);

Walk through every pin in your schematic. If it’s not connected to anything, isolate it. If it’s connected to an external component, pull it to a known level.

4. That Power LED (and Every Other Peripheral) Stays Lit

The red power LED on most dev boards pulls 1 to 5 mA through its current-limiting resistor. An I2C sensor left powered might draw 0.5 to 15 mA depending on its state. A level shifter, an SD card in idle mode, an OLED display with its controller still running: each one adds to the pile.

Fix: Power external peripherals through a GPIO-controlled P-channel MOSFET or load switch. Kill them before sleep:

gpio_set_level(SENSOR_PWR_PIN, 0);  // Cut power to sensor
gpio_set_level(LED_PWR_PIN, 0);      // Kill indicator LED
esp_deep_sleep_start();

The power LED on a dev board? Desolder it, or cut the trace. For production boards, either omit it or put it behind a FET.

5. Internal Pull-ups and Pull-downs Keep Burning in Sleep

Every enabled internal pull-up or pull-down sources current through a roughly 45 kΩ resistor. That’s about 10 to 50 µA per pin, and they stay enabled through deep sleep unless you explicitly disable them.

The ESP32-C6 and H2 have strapping pins that often get configured with pulls during boot. Those pulls persist.

Fix: Explicitly strip them before sleep:

gpio_pullup_dis(GPIO_NUM_0);
gpio_pulldown_dis(GPIO_NUM_0);
// Repeat for every pin you're not using as a wake source

If you’re using a pin as an EXT0 or EXT1 wake source, you probably need its pull-up. Every other pin: disable.

6. Your SPI Flash Isn’t Actually Sleeping

The internal SPI flash chip has a standby current of 15 to 30 µA. Its deep power-down mode drops that to about 1 µA. On the original ESP32, flash does NOT automatically enter power-down during deep sleep unless you tell it to.

ESP32-S3 and C6 handle this better for internal flash, but external flash on a custom board still needs manual control.

Fix:

// For internal flash on ESP32 (original)
esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);

// For external flash, send the power-down command (0xB9) before sleep

Note: powering down VDDSDIO means you can’t use RTC memory to persist data across sleep. If you need wake stubs or ULP data, you’ll have to eat the flash standby cost.

7. RTC Domains You’re Not Using Are Still Clocked

The ESP32’s RTC subsystem has multiple power domains: RTC peripherals, RTC slow memory, and RTC fast memory. By default, ESP-IDF keeps some of these alive during deep sleep to support features you might not be using.

The cost per powered domain runs 5 to 100 µA, varying by chip variant and which peripherals are clocked. If you’re not running the ULP coprocessor, not using wake stubs, and not persisting variables in RTC memory, you’re paying for nothing.

Fix:

esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);

Careful: turning off RTC_PERIPH disables EXT0 and EXT1 wake sources. If you need GPIO wakeup, you’ll need RTC_PERIPH on. Pick your tradeoffs.

8. The Touch Controller Runs a State Machine in Your Sleep

If you called touch_pad_init() at any point during execution, the touch sensor FSM keeps running during deep sleep. On the original ESP32, this costs about 36 µA. On the S2 and S3, it’s similar.

The touch FSM keeps running even if you’re not using touch-wake. It was designed for that purpose, but it doesn’t shut itself down automatically.

Fix: Deinitialize before sleep if you’re not using touch-based wakeup:

touch_pad_deinit();

On the S3, if you ARE using touch-wake, configure touch_pad_sleep_channel_enable() only for your intended wake pins. Every extra channel costs current.

9. ADC and Hall Sensor Bias Currents Linger

The ADC’s internal reference and biasing circuitry can stay active after you take a reading, drawing 5 to 10 µA. The original ESP32’s hall sensor (which runs through the ADC) is another offender.

ESP32-C6 and H2 redesigned the ADC power gating, so this is less of a problem on newer silicon. But even there, confirm proper acquire/release pairing.

Fix:

adc_power_release();  // ESP-IDF v5.x
// Or on older IDF versions:
// adc_power_off();

Pair every adc_power_acquire() with a release. If you’re taking a reading, powering off, and sleeping, the sequence matters: read, release, sleep.

10. The Brownout Detector Costs 3 to 5 µA (and You Don’t Need It Asleep)

The brownout detector monitors supply voltage and triggers a reset if it drops too low. Useful when running. Pointless during deep sleep, because if VDD drops far enough, the RTC will lose state anyway.

It draws 3 to 5 µA across all ESP32 variants. If you’ve already squeezed everything else, this is free savings.

Fix: Disable in menuconfig:

CONFIG_ESP_BROWNOUT_DET=n

Or disable at runtime before sleep if you still want it active during normal operation. Just be aware: with it off, a slow voltage sag during active mode won’t trigger a clean reset. You’ll get undefined behavior instead.

The Pre-Sleep Checklist That Gets You to Single-Digit µA

Here’s the consolidated function:

void prepare_for_deep_sleep(void) {
    // Kill external peripherals
    gpio_set_level(SENSOR_PWR_PIN, 0);
    
    // Disable internal pulls on unused pins
    gpio_pullup_dis(GPIO_NUM_0);
    gpio_pulldown_dis(GPIO_NUM_0);
    // ... repeat for all unused pins
    
    // Isolate RTC GPIOs
    rtc_gpio_isolate(GPIO_NUM_12);
    rtc_gpio_isolate(GPIO_NUM_15);
    
    // Release ADC power
    adc_power_release();
    
    // Deinit touch if not using touch-wake
    touch_pad_deinit();
    
    // Power down unused RTC domains
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF);
    esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF);
    
    // Power down flash
    esp_sleep_pd_config(ESP_PD_DOMAIN_VDDSDIO, ESP_PD_OPTION_OFF);
    
    esp_deep_sleep_start();
}

And here’s what it buys you:

Source                  | Typical Waste | After Fix
------------------------|--------------|----------
USB-UART bridge         | ~15 mA       | 0 (removed)
LDO quiescent           | ~5 mA        | ~4 µA
Floating GPIOs          | ~50 µA       | ~0 µA
Powered peripherals     | ~5 mA        | 0
Internal pull-ups       | ~30 µA       | ~0 µA
Flash standby           | ~25 µA       | ~1 µA
RTC domains             | ~50 µA       | ~0 µA
Touch controller        | ~36 µA       | ~0 µA
ADC bias                | ~8 µA        | ~0 µA
Brownout detector       | ~4 µA        | ~0 µA
------------------------|--------------|----------
TOTAL                   | ~25.2 mA     | ~5 µA

From 25 mA to 5 µA. A 5,000x improvement, and you haven’t touched the radio config once.

Measure After Every Change, Not Just at the End

The temptation is to apply all 10 fixes and then measure. Don’t. You’ll never know which fix actually mattered for your specific board and configuration.

Apply one fix. Measure. Write down the number. Apply the next. Some of these (items 1 and 2) are hardware and might dominate your specific board. Others (items 8, 9, 10) might not even register if your measurement floor is above 10 µA.

Once you’ve stripped your deep sleep current down to single-digit microamps, the next bottleneck becomes how efficiently you wake up, do work, and get back to sleep. If your device needs to transmit data periodically over BLE, the Hubble device SDK offers a path for getting sensor data off-device with minimal active time, which matters when your power budget is measured in microamp-hours.

The radio gets all the blame for ESP32 battery drain. But in deep sleep, it’s already off. Everything else on this list is the real problem, and it’s all fixable.


Hubble Network connects your low-power BLE devices directly to satellites—no gateways, no infrastructure to maintain. See how it works →