ESP32 Power Consumption in BLE Mode: What to Expect from Advertising, Scanning, and Connected States

ESP32 microcontroller with Bluetooth Low Energy antenna highlighting power measurement components

Your BLE project works perfectly on the bench. The phone connects, data flows, and everything behaves exactly as designed. Then you unplug the USB cable, wire in a CR2032, and the coin cell is dead by lunchtime. The ESP32 datasheet says “light sleep: 0.8 mA.” So what went wrong?

Nothing went wrong. You just didn’t configure it. The gap between 100 mA and 0.15 mA isn’t a hardware problem; it’s a software configuration gap. And every contradictory power number you’ve seen online is probably correct, just measured under different settings that nobody bothered to document.

BLE power consumption on the ESP32 varies by more than 100× depending on your software choices. Advertising interval, connection parameters, sleep mode configuration: these are all levers you control in code. This article walks through each BLE state, gives you the concrete current draw numbers to expect, and shows you exactly which settings to change.

Scope: Original ESP32 (WROOM-32). Software-side optimizations. ESP-IDF primarily, with Arduino notes. The C3, S3, C6, and H2 have meaningfully different power profiles and deserve their own treatment.

Where the Power Actually Goes

BLE is a duty-cycled protocol at its core: wake up, blast some radio packets, go back to sleep. Repeat. Your average current draw is a function of three things:

  1. Peak current during radio TX/RX (~100–130 mA, essentially fixed by the hardware)
  2. How long each radio event lasts (typically 1–5 ms)
  3. How often events occur (your advertising interval or connection interval)
  4. What the chip does between events (this is where most developers leave massive savings on the table)
Current
(mA)
130 |    ┌─┐         ┌─┐         ┌─┐
    |    │ │         │ │         │ │  ← Radio TX/RX events
    |    │ │         │ │         │ │
  5 |────┘ └─────────┘ └─────────┘ └──── ← Light sleep between events
    |
0.8 |  (deep sleep baseline — no BLE)
    └──────────────────────────────────── Time
         |← adv interval →|

The radio peaks are largely outside your control. The interval between them and the baseline current between them: that’s where all the optimization lives. This article covers three states: advertising, scanning, and connected.

Advertising State: The Interval Is Everything

During advertising, the ESP32 wakes up, transmits on three BLE advertising channels (37, 38, 39), optionally listens for scan requests, then sleeps. Each advertising event takes roughly 2–4 ms at ~130 mA. The math is straightforward: more frequent events means higher average current.

┌──────────────────────┬─────────────────┬────────────────────┐
│ Advertising Interval │ Avg Current     │ Approx Battery Life│
│                      │ (light sleep)   │ (230 mAh CR2032)   │
├──────────────────────┼─────────────────┼────────────────────┤
│ 20 ms (min)          │ ~15–25 mA       │ ~10–15 hours       │
│ 100 ms               │ ~3–8 mA         │ ~1–3 days          │
│ 1000 ms (1s)         │ ~0.5–1.5 mA     │ ~7–20 days         │
│ 10,000 ms (10s)      │ ~0.1–0.3 mA     │ ~1–6 months        │
└──────────────────────┴─────────────────┴────────────────────┘
Note: Without light sleep, idle current stays ~100 mA,
making the interval nearly irrelevant.

That last line is the critical one. If you haven’t enabled light sleep, your CPU is burning ~100 mA between advertising events, and no amount of interval tuning will save you.

Software levers

Advertising interval is your primary control. In ESP-IDF:

esp_ble_adv_params_t adv_params = {
    .adv_int_min = 1600,  // 1000 ms (units of 0.625 ms)
    .adv_int_max = 1600,
    .adv_type = ADV_TYPE_IND,  // connectable undirected
    .channel_map = ADV_CHNL_ALL,
    // ...
};
esp_ble_gap_start_advertising(&adv_params);

Advertising type matters too. Non-connectable advertising (ADV_TYPE_NONCONN_IND) skips the listen window for scan requests, shaving time off each event. If you’re building a beacon that doesn’t need connections, use it.

TX power has a surprisingly modest impact. The delta between 0 dBm and +9 dBm is only ~20 mA during the TX burst, and that burst is 1–2 ms. Over a 1-second interval, the average current difference is negligible. Set it via esp_ble_tx_power_set(), but don’t expect miracles.

If you’re new to BLE advertising on the ESP32, our ESP32 BLE getting started guide covers the foundational setup.

Scanning State: The Power-Hungry One

If advertising is “transmit briefly, then sleep,” scanning is the opposite: the radio must stay in RX mode for the entire scan window, listening for advertisements. RX current is roughly equal to TX current (~100–130 mA), and scan windows are typically much longer than advertising events.

┌──────────────┬──────────────┬──────────────────┐
│ Scan Window  │ Scan Interval│ Avg Current      │
├──────────────┼──────────────┼──────────────────┤
│ 50 ms        │ 100 ms       │ ~50–70 mA        │
│ 10 ms        │ 100 ms       │ ~12–20 mA        │
│ 10 ms        │ 1000 ms      │ ~2–5 mA          │
│ 100 ms       │ 100 ms       │ ~100–130 mA      │
│ (continuous) │              │ (full duty cycle) │
└──────────────┴──────────────┴──────────────────┘

The ratio of scan window to scan interval is your duty cycle, and duty cycle directly maps to average current. Configure it via esp_ble_gap_set_scan_params().

Design around scanning if you can

Most battery-constrained BLE peripherals avoid scanning entirely. The typical architecture: your ESP32 advertises, and the phone (which has a large battery) does the scanning. If your product must scan, perhaps because it’s a BLE gateway or needs to discover nearby devices, scan in short bursts, then stop completely with esp_ble_gap_stop_scanning().

If you’re starting a new product design that requires heavy scanning, seriously evaluate the ESP32-C3 or ESP32-H2. Their lower baseline current makes scanning less painful.

Connected State: Predictable and Tunable

Once a BLE connection is established, communication happens in discrete connection events at a negotiated interval. Between events, the radio sleeps. This is where BLE gets efficient, if you configure it correctly.

┌─────────────────────┬──────────────┬──────────────────────┐
│ Connection Interval │ Slave Latency│ Avg Current          │
│                     │              │ (light sleep enabled) │
├─────────────────────┼──────────────┼──────────────────────┤
│ 7.5 ms (min)        │ 0            │ ~15–30 mA            │
│ 50 ms               │ 0            │ ~2–5 mA              │
│ 500 ms              │ 0            │ ~0.3–1 mA            │
│ 50 ms               │ 10           │ ~0.3–0.8 mA          │
│ 1000 ms             │ 4            │ ~0.05–0.2 mA         │
└─────────────────────┴──────────────┴──────────────────────┘

Connection interval

The connection interval determines how often the peripheral and central exchange packets, even if there’s no data to send. Shorter intervals mean lower latency and higher throughput, but more power. For a sensor sending data every few minutes, a 500 ms or 1000 ms interval is perfectly reasonable.

Slave latency: the underused superpower

Slave latency lets the peripheral skip up to N consecutive connection events if it has nothing to send. With a 50 ms connection interval and slave latency of 10, the peripheral effectively wakes up every 550 ms but can still respond within 50 ms when it has data. Best of both worlds.

esp_ble_conn_update_params_t conn_params = {
    .min_int = 40,    // 50 ms (units of 1.25 ms)
    .max_int = 80,    // 100 ms
    .latency = 10,    // skip up to 10 events
    .timeout = 400,   // 4000 ms supervision timeout
};
// Copy peer BDA into conn_params.bda
esp_ble_gap_update_conn_params(&conn_params);

Negotiation reality: The central device ultimately decides. iOS caps connection intervals at 30 ms minimum and enforces specific timeout/latency relationships. Android is more flexible but varies by manufacturer. Set min_int and max_int as a range to give the central room to comply.

The tradeoff is real: a 1000 ms connection interval with latency 4 gives excellent power numbers, but any notification you send won’t arrive for up to 5 seconds. Design your UX around this.

For detailed code walkthroughs, see our BLE with ESP-IDF tutorial.

The Light Sleep Multiplier: The Optimization Most Developers Skip

This single configuration change likely has more impact than everything else in this article combined.

Without light sleep, the ESP32’s CPU runs at full clock speed between BLE events. That’s ~100 mA of baseline current, all the time. The radio duty cycle becomes irrelevant: you’re burning the battery whether or not the radio is active.

With automatic light sleep and tickless idle enabled, the CPU sleeps between BLE events and the BLE controller wakes it on schedule. Your idle baseline drops from ~100 mA to ~1–5 mA (light sleep with BLE controller active).

In ESP-IDF, two steps:

  1. Enable CONFIG_FREERTOS_USE_TICKLESS_IDLE and CONFIG_PM_ENABLE in menuconfig
  2. Configure power management:
esp_pm_config_esp32_t pm_config = {
    .max_freq_mhz = 240,
    .min_freq_mhz = 40,
    .light_sleep_enable = true
};
esp_pm_configure(&pm_config);

In Arduino, you can call esp_pm_configure() directly by including esp_pm.h, though support is less well-documented.

Gotchas: Light sleep disables UART during sleep (you’ll see gaps in serial output). GPIO wakeup sources need explicit configuration. WiFi and BLE light sleep coexistence is limited. If you need both, consult the ESP32 sleep modes deep dive for the specific constraints.

For more on how dev board power draw differs from bare modules (an important nuance when measuring), see our ESP32 development board comparison.

Estimating Real-World Battery Life

The formula is simple: Battery life (hours) = Battery capacity (mAh) / Average current (mA). The hard part is calculating average current across your device’s actual operating profile.

Here’s a concrete example, a BLE temperature sensor on a CR2032:

BLE Temperature Sensor (230 mAh CR2032)
Connection interval: 500 ms, Slave latency: 4, Light sleep enabled
Sends one notification every 5 minutes (300 seconds)
─────────────────────────────────────────────────
State breakdown per 5-minute cycle:
  Connected idle (light sleep):     ~0.15 mA × 299.95 sec
  Connection event (TX data):       ~130 mA  × 1.5 ms
  Sensor reading + processing:      ~40 mA   × 50 ms

Weighted average current:
  (0.15 × 299.95 + 130 × 0.0015 + 40 × 0.05) / 300
  = (44.99 + 0.195 + 2.0) / 300
  ≈ 0.16 mA

Estimated life: 230 / 0.16 ≈ 1,437 hours ≈ 60 days

Compare that to the same sensor without slave latency and with a 50 ms connection interval:

Same sensor, connection interval: 50 ms, slave latency: 0
Weighted average: ~3.5 mA
Estimated life: 230 / 3.5 ≈ 65 hours ≈ 2.7 days

Same hardware. Same battery. Software configuration alone makes a 22× difference.

Always validate with actual measurements. These are estimates; your module, firmware version, and ambient temperature all introduce variation. But they give you a sanity check: if you’re measuring 10 mA and expecting 0.15 mA, something is misconfigured.

Quick Wins Checklist, Ordered by Impact

  1. Enable light sleep with tickless idle. This alone can drop idle current by 95%.
  2. Increase advertising interval to the maximum your UX tolerates (1+ seconds if possible).
  3. After connection, request long connection interval + slave latency. 500 ms with latency 4–10 is a good starting point for low-data-rate sensors.
  4. Use notifications (server-initiated) instead of client-initiated reads to avoid the peripheral needing to wake for polling.
  5. Set TX power to the minimum that maintains reliable range at your expected deployment distance.
  6. Avoid scanning on the peripheral. Let the central (phone) do the work.
  7. Disable WiFi if not needed. esp_wifi_stop() and esp_wifi_deinit() free up significant current.
  8. Disable unused peripherals, including the brownout detector, unused GPIO pull-ups, etc.

What About the ESP32-C3, S3, C6, and H2?

Everything above applies to the original ESP32. If you’re choosing a chip for a new product, know that the newer variants have materially different power profiles. The ESP32-C3 and ESP32-H2 in particular were designed for low-power BLE applications, with lower deep sleep currents (~5 µA vs ~10 µA) and more efficient radio architectures. The H2 adds 802.15.4 support (Thread/Zigbee) for even lower-power mesh networking.

If you’re not locked into the classic ESP32, evaluate these for any new battery-powered design. We’ll cover a detailed comparison in a future article.

Building This Into Your Battery Budget

BLE power optimization on the ESP32 is fundamentally about duty cycle management, and software controls the duty cycle. The radio hardware draws what it draws; you can’t change the ~130 mA TX peak. But you absolutely control how often that peak happens and what the chip does in between.

Enable light sleep. Stretch your intervals. Use slave latency. Measure your actual device. The numbers in this article are sanity checks, not gospel. The jump from “dies in hours” to “runs for months” is achievable with software changes alone, no hardware redesign required.


Hubble Network connects BLE devices like the ESP32 directly to satellites, eliminating gateway infrastructure while keeping your power budget intact. See how it works →