Just announced: blueAI, the AI debugging copilot for Bluetooth LE
Bluetooth LE · · 16 min read

Why Battery Percentage Can't Predict a Bluetooth LE Brown-Out

The battery level your device reports is read at rest and tracks energy. It browns out under load, when the voltage sags. Here is why your battery percentage can't see the reset coming, shown on the bench, and how to design around it.

Flat illustration of a coin-cell battery beside a voltage line dipping sharply below the brown-out threshold.

Somewhere in your coin-cell product’s firmware, the device reads its own battery and reports a level: a percentage over the Bluetooth LE Battery Service, a bar in an app, a number on your fleet dashboard. The number looks fine. Then units in the field start resetting, and the battery telemetry never warned you.

I’ve found that this catches a lot of teams off guard, and it is not a firmware bug. The level your device reports is measured at rest, and the failure happens under load. Worse, battery percentage is answering the wrong question. It estimates how much energy is left in the cell. Your Bluetooth LE device doesn’t brown out when it runs low on energy. It browns out when the voltage sags too far during a transmit, and that is a different thing entirely.

Let me walk through what your battery percentage actually measures, why it is blind to the failure that takes your device down, what the real cutoff looks like on the bench, and what you can do about it.

What your battery percentage actually measures

Start with how that number gets made. Most coin-cell devices don’t have a dedicated fuel-gauge chip; it costs money and board space they don’t have. Instead the firmware reads the supply voltage through the SoC’s ADC (directly, or through a resistor divider) and maps that voltage to a percentage using a discharge curve. That percentage is what gets reported, often as a 0 to 100% Battery Level over the Bluetooth LE Battery Service, or pushed to your cloud as battery telemetry.

Two things about that measurement decide everything that follows.

First, it is a voltage reading taken at rest. The ADC conversion is a brief, low-current sample, and firmware almost always takes it between radio events, not in the middle of a transmit. So the reported number is built from the resting voltage, the same value a multimeter on the bench would show, and it never sees the dip that happens under load.

Second, that percentage tracks the wrong physical quantity. It estimates energy remaining, and energy remaining is not what fails first. Your device browns out when the voltage sags under a current pulse, and how far it sags is set by the cell’s resistance, a separate property from its remaining charge. So even a more capable gauge that counts charge in and out (a coulomb counter, the kind you find in a phone and rarely on a coin cell) tracks energy more accurately and still misses the loaded-voltage margin that decides whether the next transmit browns out the chip.

So whether your reported percentage is crude or precise, it is blind to the failure mode that takes the device down. To see why, let’s look at what the cell does under load.

A coin cell is not a fuel tank

The reason that resting reading misleads you sits in the battery itself. The intuition that gets people into trouble, the same one behind both a “2-year battery life” estimate and a battery-percentage gauge, treats the cell like a fuel tank: it holds X amount of energy, you burn it at some rate, and the device runs until the tank hits empty.

A real battery doesn’t behave that way. A more accurate model is an ideal voltage source in series with a resistor, called the internal resistance (ESR) of the cell (you’ll also see it written as internal resistance or R_int; Qoitech and capacitor datasheets tend to use “ESR,” for equivalent series resistance). The voltage you actually see at the terminals is:

V_terminal = OCV − (I × ESR)

where OCV is the open-circuit voltage (the voltage with no load) and I is the current you’re drawing. This is the standard Thevenin model of a cell, and it has two consequences that matter a lot for coin cells.

First, the voltage measured with no load is the OCV. On a coin cell the OCV holds up well for most of the discharge and sags only gradually, and even deep into the cell’s life it stays above the chip’s minimum operating voltage. That is exactly the voltage your at-rest ADC sample reads, and it is why a resting reading almost always looks survivable, right up until the device stops working.

Second, the term that actually bites is I × ESR, and the ESR is not constant. A fresh CR2032 has an internal resistance on the order of 10 Ω (Energizer’s datasheet plots it as a curve that starts around 5 to 15 Ω, not a single spec). As the cell discharges, that resistance climbs, and near end of life it climbs steeply. TI measured this directly in their coin-cell application note (SWRA349): the internal resistance sits in the 10 to 15 Ω range through mid-life, then spikes past 45 Ω and onward toward 100+ Ω as the last of the capacity is used.

So as the cell ages, every milliamp you draw costs more and more voltage. The OCV (what your gauge reads) looks fine. The loaded voltage (what the chip sees) is quietly collapsing.

Internal resistance of a CR2032 versus capacity used: flat for most of life, then a steep hockey-stick climb near end of life.
A CR2032’s internal resistance (ESR) stays low and roughly flat for most of its life, then climbs steeply near end of life, from about 9 Ω fresh to over 100 Ω when nearly empty. Source: Energizer CR2032 discharge profile (Qoitech battery library), consistent with TI SWRA349.

Bluetooth LE is close to the worst-case load

Bluetooth LE makes this problem sharper. It was designed to sip power by sleeping most of the time and waking for very short bursts to transmit or receive. That’s great for average current. It’s rough on a high-resistance cell.

The reason: the cell doesn’t care about your average current during those bursts. It sees a short, tall current pulse. On an efficient part like the Nordic nRF54L15, a 1 Mbps (LE 1M PHY) transmit at 0 dBm draws roughly 4 to 5 mA of total current at 3 V (Nordic’s system figure; the radio block alone is about 3.7 mA in the datasheet). On top of that, the on-chip DC/DC converter recharges its input in brief, higher current bursts, so the instantaneous current the cell sees during a wake is spikier than the average suggests.

Now multiply that pulse by a rising internal resistance. A burst that was a non-event when the cell was fresh (a few milliamps across 10 Ω is a few tens of millivolts of droop) becomes a cliff when the same burst hits 100 Ω late in life.

A quick illustrative example (round numbers; we’ll confirm them on the bench below):

That 0.95 V is the number that decides whether your device lives. And it shows up only during the burst, for a few hundred microseconds, which is exactly the moment your at-rest battery reading skips over.

The cliff: where the chip gives up

Every Bluetooth LE chip has a voltage floor, and crossing it briefly is enough to take the device down. For the Nordic nRF54L15, the numbers (from the datasheet) are:

Let’s put the two halves together. The illustrative 0.95 V burst-time rail from the last section is well under the 1.57 to 1.64 V brown-out band. The chip resets. When it tries to come back up, it needs 1.75 V to restart cleanly, but the next transmit burst drags it down again. You get a device that boots, transmits, browns out, and reboots, over and over, while its own battery reading, taken at rest, still reports a voltage well above the 1.7 V floor.

One more useful detail: the nRF54L15 runs the whole radio off a single VDD rail (1.7 to 3.6 V) with a single-inductor DC/DC converter, and there’s no separate higher-voltage requirement for higher transmit power. So the failure here is purely the loaded-voltage droop, not a “you need more volts for more dBm” effect.

What this looks like on the bench

That is the arithmetic. Here is the same failure on real silicon.

I built a rig that swaps the coin cell for two instruments: one that can fake a depleting cell, and one that can see the burst that kills it.

Measurement rig: a Qoitech Otii Ace battery emulator feeds a Joulescope JS320 in-line ammeter into an nRF54L15-DK, which runs the real firmware and browns out.
The bench rig: a Qoitech Otii Ace emulates a depleting CR2032 and sags the rail under load, a Joulescope JS320 sits in line as the ammeter, and the supply feeds an nRF54L15-DK at its current-tap header so the real firmware browns out for real. Device images courtesy of Qoitech, Joulescope, and Nordic Semiconductor.

The battery emulator is the piece that makes this honest. Per Qoitech, the “Otii Battery Toolbox emulates the total ESR,” and it “will adjust the output voltage depending on the load, just like a real battery with internal resistance.” So it can hold a chosen state of charge and sag the rail under load the way an aging cell would, which a bench power supply (a stiff, ideal source) simply cannot do. A lab supply would happily hold a perfect rail, and you would never see the failure.

The ammeter has to be the right kind, too. The signal here lives in two places at once: a sleep current down in the microamps, and a transmit burst up in the milliamps that lasts only a few hundred microseconds. A meter that autoranges between those two worlds, or that averages over a slow window, smears the burst into a number that looks harmless. The Joulescope JS320 measures at its set range straight through the range switches at 1 MS/s, so the microamp idle floor and the milliamp pulse both survive in the same trace. That dip is exactly the thing you need to see, because that dip is what browns out the chip, and it is the thing your at-rest battery reading cannot capture.

Then I swept the emulated state of charge from full down toward empty and watched two voltages at once: the resting voltage (what your battery gauge would read) and the worst transmit-time dip (what the chip actually sees):

This is the gap in one place. At 3% state of charge, a battery gauge sampling the rail at rest reads about 2.29 V and reports a device that is alive and has voltage to spare. The chip, hit by a transmit burst at that same moment, sees 0.95 V and resets. Same battery, same instant, two completely different stories, and the reported number is telling you the wrong one.

There’s an irony in that gap. The Bluetooth LE Battery Service spec defines Battery Level 0% as the point where the battery is “unable to power the device.” By that definition the cell at 3% has already hit 0%: it can no longer carry the chip through a transmit without browning it out. A voltage-based percentage just doesn’t see it, because it reads the rail at rest, while the spec’s own definition of empty is about whether the device can actually run.

The tell wasn’t the average current, either. During reset-cycling, the average current actually reads higher (it jumped from about 0.14 mA to 0.37 mA at 3% in my run, because the chip keeps paying the reboot inrush), so an “average current” view makes a dying device look busy and healthy. Judge “alive” by whether the device is still doing its job, not by a resting voltage and not by how much current it’s drawing.

One honest caveat: the exact percentage where this happens is not a universal constant. It depends on the specific cell’s resistance curve and on your device’s pulse profile. The profile I used is the stock published Energizer CR2032 model, not one I aged or edited, and its end-of-life resistance climbs past 85 Ω, so treat the single-digit numbers above as “this is the shape of the failure,” not “every CR2032 dies at exactly 3%.” The point that holds for every coin cell is the one in the intro: the device stops working before the cell is empty, and no resting battery reading sees it coming.

Loaded voltage versus state of charge: the resting voltage stays above 2.2 V while the worst transmit-burst dip falls off a cliff and crosses the brown-out band at single-digit state of charge.
What your gauge reads versus what the chip sees. The resting voltage (the value an at-rest ADC sample reports) stays comfortably above the chip’s 1.7 V floor for the whole sweep, but the worst transmit-burst dip falls off a cliff and crosses the nRF54L15 brown-out band (1.57 to 1.64 V) at around 4 to 5% state of charge. Source: bench capture, Otii Ace (stock CR2032 profile) plus Joulescope JS320 plus nRF54L15-DK.

That chart traces the trend across state of charge. Here is the same story as a live current-and-voltage trace, healthy at full charge and browning out at 3%:

Current and voltage waveforms at 100% and 3% state of charge: at full charge the chip sleeps and the rail holds steady; at 3% the transmit bursts crash the rail through the brown-out band and the chip reset-cycles.
Current and rail voltage on one 1 MS/s timeline, healthy (100%) versus brown-out (3%). At 3% the transmit bursts drag the rail through the brown-out band, to 1.09 V in this capture. Dip depth varies run to run (the sweep above reached 0.95 V); both sit well below the band. Source: Otii Ace (stock CR2032 profile), Joulescope JS320, nRF54L15-DK.

So when does the battery “really” die?

The reframe is this: for a pulsed Bluetooth LE load on a coin cell, end of life is the moment the transmit-time droop first crosses the chip’s brown-out band, and that moment arrives with meaningful capacity still sitting in the cell. In my run that moment arrived around 4 to 5% state of charge, with the cell still resting above 2.3 V.

Your usable capacity is smaller than your rated capacity, and the gap is set by your peak pulse current times the cell’s end-of-life resistance. The bigger your bursts and the higher the cell’s late-life resistance, the more capacity you leave stranded. That’s the energy you never get to use, and it’s why both dividing rated mAh by average current and trusting a resting battery percentage overestimate how long the device keeps working.

Here is why that reframe is worth the trouble. Once you know the device quits at the brown-out cliff and not at empty, three things change for you as the developer. The battery-life number on your box becomes one you can defend, because you sized it against usable capacity instead of the headline mAh. You can reproduce the failure on your own bench, on purpose, before a customer hits it in the field as a mystery reset. And you know which lever to pull: the fix is to hold the loaded voltage up during the burst and to warn on the right signal, not to chase a phantom firmware bug or trust a battery percentage that can’t see the cliff.

A brown-out costs more than a reset

Watching the device reboot makes a brown-out look like a recoverable nuisance. Often it isn’t, and the reset is the least of what it does.

The one that should worry you most is a brown-out that lands in the middle of a flash write. A reboot is recoverable; a half-finished write to non-volatile memory is not. If the rail collapses while your firmware is saving bonding keys, a settings change, or a log entry, you can corrupt that data or leave the device stuck in a bad state. This is the real reason the chip carries a power-fail comparator (POF): it lets you stop and shed load before the rail reaches the brown-out band, instead of finding out about the damage after the next reset.

The failure also feeds itself. Reset-cycling keeps the chip awake and drawing far more current than it would asleep, so it runs through the sliver of capacity that’s left much faster than a sleeping device would. The more it browns out, the quicker it drives the cell toward the steep end of the resistance curve, where it browns out more easily still.

If your product holds a connection instead of just advertising, there’s one more cost. Each reset drops the Bluetooth LE connection, the link falls over on a supervision timeout and the device has to reconnect, and anything kept only in RAM (counters, buffered samples, session state) is lost every time.

What you can do about it

The good news: this is a well-understood problem with known fixes. Let’s go through the ones that matter most:

How to see it before your customer does

Fixing this is the easy part. Seeing it during development is the hard part, because a bench power supply will happily hold a perfect rail right up until the moment you’d never reach in the field, and your device’s own battery reading will agree with it. You need to reproduce a depleting, high-resistance cell. There are two ways to get there.

The first is hardware in the loop: a battery emulator that replays a real cell’s discharge and resistance curves and physically makes your board brown out, the way the Otii rig above did. This is the most convincing because your actual firmware actually resets, which surfaces the real failure modes (brown-out handlers, capacitor sizing, the POF configuration, and whatever your battery-level reporting does as the rail sags).

The second is projection from a measurement: take a measured current trace of your device and combine it with a cell’s published resistance-vs-capacity curve to compute the loaded-voltage dip and the runtime, before you ever wire up an emulator. Because the cell models are public data (Qoitech, for example, publishes a library of battery profiles as plain files), this kind of projection can live in software.

That second path is the one we’re building toward in PowerScope, our power-profiling extension for VS Code (coming soon). The premise is to profile power where you already write firmware, so it happens continuously as you develop, not once in a while on a lab bench. Most power tools hand you a current trace and leave you to work out which part of your firmware caused each spike. PowerScope does the reverse: it lines each current pulse up against the Bluetooth LE event behind it, so you can see which advertising or connection event actually spent the energy, and how much. It runs off the affordable meters already on your bench, the Nordic Power Profiler Kit II or a Joulescope, not a rack of lab instruments.

The piece still in progress is the projection: take that same capture, lay it over a published cell model, and read off a defensible battery-life number and an honest brown-out warning, without re-wiring the bench for every what-if. The aim is to turn the bench exercise above into something you run on every build, so a power regression gets caught the same way a failing test would. It is not out yet, but it is coming soon; until it ships, the bench work in this post is how you get there today.

Both get you to the same place: a battery-life claim, and a battery-level report, that survive contact with a real coin cell.

Summary

Let’s recap what we covered:

With this, you can size your next coin-cell product around the capacity you can actually use, report a battery level you can trust, and catch the brown-out cliff on your bench instead of in the field.

Mohammad Afaneh, Novel Bits

Bluetooth LE Power & Battery-Life Consulting

I help product teams measure real current draw, diagnose brown-outs, and turn battery-life claims into defensible, bench-verified numbers.

Get in touch →

Read next

Get Started with Bluetooth LE

Download the free Intro to Bluetooth Low Energy eBook — the quickest way to understand the fundamentals.

No spam. Unsubscribe with one click.