The CR2032 3V lithium button cell battery is commonly used in small electronic devices, and especially BLE IoT devices.
In this article I explain the complications associated with estimating the remaining charge level (“state of charge”) of this battery, and recommend a simple strategy for reasonably accurate measurement. I demonstrate this strategy in the Uncannier Thunderboard React and Uncannier Thunderboard Sense 2 projects. The Silicon Labs reference firmware for these modules has less-than-spectacular charge level measurement, and I seek to improve it.
CR2032 Discharge Performance
Two aspects of CR2032 lithium battery discharge performance need to be understood.
Firstly, the voltage does not change very much over the first 75% of the discharge. As can be seen below, there’s only ~200 mV difference between 100% charge and 25% charge, for a constant low current draw of 190uA.
Secondly, the voltage is greatly dependent on the load.
As can be seen, the voltage is quite steady for loads up to 200uA, but begins to fall away very rapidly as the load increases from there. The voltage is more affected by the instantaneous current draw than by the charge level. The consequence is that the instantaneous voltage is useless as a charge estimate if the instantaneous current draw is unknown.
This is quite different, for example, to a VARTA CP1654 A3 Lithium-Ion 120mAh rechargeable battery. For these commonly-used rechargeable batteries, the voltage is not greatly affected by the current draw.
Note that 1C equates to 120mA and 0.1C therefore equates to 12mA. Although not shown, lower current draws cluster near to the 0.1C line.
Charge Level Estimation Strategies
Let’s rephrase the key point:
Instantaneous voltage, by itself, tells us nothing about the CR2032 charge level.
Hardware Fuel Gauge
Essentially these gauges just integrate the instantaneous current draw to accumulate the amount of charge expended. However, devices that use the CR2032 are typically very cost sensitive. Sometimes they are even disposable, and the battery is not replaceable. Therefore a fuel gauge is routinely omitted, as is current sensing. Typically, the battery voltage is the only metric the firmware has.
Software Fuel Gauge
If you don’t have a hardware fuel gauge, and you like to go a bit crazy, you could implement a fuel gauge in software. You would have to profile your device’s current draw in every state. If your device is non-trivial, there will be a lot of states, and this will be a lot of work. If you have current sensing, it will be easier to implement a software fuel gauge. Even then, it’s still not at the top of my bucket list.
Sample on Wake
Typically a device that uses a CR2032 is a low duty-cycle device; a very low power device that spends most of its life asleep. It then wakes up for short periods, fulfills its purpose in life, and goes back to sleep. This is especially the case for BLE IoT peripherals.
If your device is in this category, life gets easier. Typically the device will not consume more than a few percent of charge in any one waking period. Often less than 1%. So you can measure the voltage immediately upon waking, and only measure it at that moment. No need for frequent updates. We have a winner.
The Thunderboard React and Thunderboard Sense 2 both lack a Coulomb counting fuel gauge and both lack current sensing. The battery voltage level is the only metric available for charge level estimation.
Out of the Box Charge Level Estimation
Both devices measure the battery voltage every 10s when a BLE connection exists and battery service indications are enabled.
They both use a lookup table and linear interpolation to convert the instantaneous voltage to a charge level.
Current draw is not even mentioned in the comments. I would hazard a guess that the lookup table is the average of the voltage curves for the 190uA (15 kOhm) load.
Real World Performance
When the Thunderboards are streaming IMU acceleration and orientation data over the BLE connection, the current draw averages ~4mA. With a lot of peaks and valleys as BLE packets are transmitted and received. Here’s what the battery voltage and reported charge level do on a Sense 2 over just 5 minutes in this state (at one second intervals):
The voltage trends down ~100mV over 5 minutes under the ~4mA load. The reported charge level likewise trends down by ~20%. This is nonsense. As can be inferred from the following chart, 5 minutes of 4mA load does not even consume 1% of CR2032 charge.
Worse still, the current spikes (caused by the radio transmissions) result in a lot of high frequency noise in the voltage and the charge level. In the space of 1s, the Sense 2 does crazy things like report a 17% drop in the charge level. The React behaviour is very similar. Clearly the reference firmware is a somewhat naive implementation that gives no useful indication of the actual battery charge level.
When the load is removed, the voltage rises sharply. It then drifts higher as the battery recovers. Following a BLE disconnection, recovery period, and re-connection, the Thunderboard claims that the battery charge level has recovered most or all of that lost 20% of charge. A different kind of crazy.
Uncannier Charge Level Estimation
The Thunderboards are indeed devices with a low duty cycle, spending the majority of their lives asleep. When the React is asleep, it draws ~40uA, and the Sense 2 draws ~1uA.
Consequently I take the approach of removing the frequent voltage sampling and instead changing the system to a Sample on Wake method. That is, I only measure the voltage, and calculate the charge, just before the BLE advertising starts, and before any BLE connection is formed. By doing this, the current draw is always the same when the voltage is sampled. By doing it at wake-up, the battery has also just had a period of recovery.
I still allow the BLE central to ask for the charge many times per connection, but the reported value is constant for the whole connection session. This is adequate if we can assume that connection sessions are never very prolonged. Only 1% of charge is lost every ~20 minutes when the ~4mA load is applied (when streaming IMU data).
I continue to use the lookup table supplied by the reference firmware. This could be tuned to suit the current draw at the moment of sampling.
Uncannier Pull Requests