Tuesday, February 8, 2011

Battery monitoring using a diode/LED and resistor

Summary: A common problem with wireless battery powered sensors is knowing when the battery is about to expire in sufficient time to replace the battery before it fails. This post outlines a method that achieves this with nothing more than one resistor, one diode (or LED), one ADC input and one digital output. A practical application using a PIC 12F675 is described and some results presented.

There are many approaches to the problem of gauging a battery State-of-Charge (SoC) or battery low indicator. Modern "smart batteries" maintain a SoC by use of complex algorithms combining battery/cell voltage, 'coulomb counting' and a battery charge/discharge history.

For small devices based on microcontrollers (MCUs) like the PIC 12F675 the only practical metric is battery voltage under load. But measuring this is not as straight forward as it may first seem.

Many MCU analog to digital converters (ADCs) will measure from 0V up to power supply voltage (Vbat or Vdd). Therefore connecting the ADC to the battery will always report the full scale value (Amax) by definition. Not very useful.

Here is an idea:

Between Vbat and 0V connect a resistor and diode in series. Connect the center point (Vd) to the ADC. Vd is voltage drop across the diode which will be a constant value – about 0.7V for a silicon diode. If A is the ADC reading, and Amax is the full scale value of the ADC then Vbat = Amax * Vd /A.

As the battery voltage dwindles the value of A will increase because the fixed diode voltage drop (Vd) forms a bigger fraction of Vbat.

The design can be further improved: in the above configuration, current (I = (Vbat - Vd) / R) is being continuously drawn which will have a detrimental effect on battery life. Current is only required to flow when a measurement is being taken. So the resistor + diode circuit is connected to digital output line (Dout) instead. When set to logic 0 no current will flow. When set to logic 1 the voltage applied will be almost the same as Vbat.

A further improvement is to replace the silicon diode with a LED.  This way the battery measurement circuit can double as a visual indicator (eg a periodic heartbeat).

One point to note about LEDs: the junction voltage drop (Vd) is significantly higher than that of a silicon diode and varies considerably depending on the color and chemistry (about 1.7V for red and as much as 4V for other 'exotic' colors). You will need to consult the manufacturer's datasheet or measure it yourself (most digital multimeters have this function). Also bear in mind that the diode voltage drop needs to be less than the application's minimum supply voltage, which might rule out use of green or blue LEDs if operating on 2 x NiMH cells in series.

Here is a real application – a wireless environment sensor powered by 2 x AAA NiMH cells in series using a PIC12F675  MCU. The sensor unit periodically transmits environment data to a receiver which is directly connected a server running data acquisition software.

The LED is a typical red LED (Vd = 1.7V) and the series resistor is 3.3kΩ. The radio transmitter is a Holy Stone Enterprise Co. MO-SAWR-A (sold by SparkFun, SKU WRL-08945 @ $4 each). The environment sensor circuitry has been omitted as it's not relevant to this post.

The 12F675 has just 6 IO/analog pins so it's important to maximize use of each pin. In this configuration, pin GP4 is configured as a digital output and serves three functions:
  • Applies a voltage to the resistor-diode network for battery voltage measurement
  • Illuminates a red indicator LED
  • Acts as the baseband signal input to the radio module (a high impedance input, so it will not interfere with the battery measurements)
A desirable side-effect of this arrangement is the LED will illuminate every time the radio is used. This serves as heartbeat / activity indicator.

Another desirable side-effect is that the the transition from logic 0 to logic 1 will energize the radio module, drawing a relatively large current (about 10mA, compared to about 0.5mA consumed by an active 12F675). Battery voltage is best measured under the maximum possible load for the application. (A completely depleted battery can still show a healthy 'open circuit' voltage). Since the radio module requires a 20ms warm up period prior to transmitting data, I use this opportunity to take a battery voltage measurement, ensuring that none of the precious battery charge goes to waste.

The following chart is what the battery voltage vs time look like. I'm assuming Vd is 1.7V.

The chart looks like the classic NiMH/NiCd battery discharge curve (which is similar to alkaline cells too). There is an initial voltage drop at the start of the cycle, followed by a relatively flat curve for most of the discharge cycle and a sudden drop near the end of the charge cycle. If you're curious about the small 'bumps' see footnote [1].

In applications such as laptops and electric cars it's important to know the State-of-Charge at any point in time (as a percentage, kWh, miles or time remaining depending on the application). In my application this is not important. All I require is to get an battery low alert in sufficient time to act on it (a few days will do).

So how to determine the battery low condition? And who's job is figure this out – the hardware constrained sensor/transmitter MCU or the substantially more powerful receiver/data acquisition server. Or both?

Making the battery low determination at the data acquisition end has the advantage of having a powerful CPU with megabytes of RAM available for the task. It makes sense to transmit the battery voltage as part of the data packet for this purpose. But my sensor unit only has a radio transmitter, so it's not possible to communicate the battery low status back to it.

Ideally whatever algorithm is used should be simple enough to run on the limited resources of the sensor MCU. Detecting battery low condition here has advantages. For example it could conserve charge by decreasing the sampling frequency.

I can see two approaches:

  • An absolute battery voltage threshold
  • A rate of change of battery voltage threshold

In theory the absolute voltage threshold is simple to implement. You figure out what is the minimum voltage required for reliable operation (about 2.2V in this application). Then add 100mV or 200mV to that value to arrive at the battery low threshold. The problem with this approach is that the threshold may be in the rapidly decaying part of the battery discharge curve giving little or no useful warning. If set too high the warning kicks in way too early. Getting this right is going to be tricky.

The rate of change approach has its own problems. Here is a chart of the rate of change of ADC values vs time with ADC vs time for comparison. Remember that ADC values increase as the battery voltage decreases.

The problem is the difference between ADC readings from one sample to the next is most likely going to be exactly zero ­– there is insufficient resolution available in the ADC. Some sort of low pass filter (LPF) or sub-sampling needs to be applied to the signal first. The following is mean ADC values in 1 hour bins and the time derivative of that.

Setting a dADC/dt threshold of 2 units / hour will catch the end of battery decay, with perhaps the odd false alarm (eg at day 18). But the capability of performing 1 hour averages is pushing the capability of the 12F675 (considering all the other tasks that need to be performed must be stuffed into its tiny 1Kbyte program memory). Sub-sampling is an alternative. This chart was obtained by sampling one in every 360 samples:

It can be seen to be almost identical to the 1 hour bin chart. Sub-sampling is certainly do-able on a 12F675.

Unfortunately I don't yet have enough data to decide which approach is best. Each battery discharge cycle is over two weeks long and right now I have only one sensor utilizing 2 x AAA cells. It has been running since November 2010. Interestingly a second identical sensor powered by 4 x high capacity AA NiMH cells has been running continuously on the same discharge cycle since November 2010 (it's now February 2011)!

I will post a further analysis when more data has been obtained.


Measuring the ADC value of the voltage drop across a diode (signal diode or LED) seems to be a feasible way of gauging the battery voltage of a battery powered device. In a hardware constrained MCU like the PIC12F675 the measurement circuit can double as a LED indicator (and other functions) making maximum use of precious IO pins.

However translating this voltage to a reliable State-of-Charge or battery low indicator is a trickier problem and not one that I've fully solved yet.

Footnote 1: It is interesting to note there are small 'bumps' on an otherwise smooth voltage-time curve. It can be seen to correlate with ambient temperature. I'm assuming this is due to the effect of temperature on cell voltage.


Don Pierman said...

Thanks, I've been scratching my head over this for some time. It couldn't be simpler. The A/D reading is just the difference between two points. It doesn't matter which one is the reference. The A/D reading will be inverted but a little math will fix that. Don Pierman North Myrtle Beach SC

ChrisJX said...

I love the simplicity of your solution. I am using the XBee series 1 modules to retrieve temperature data. The unit is being powered by a LiPo 3.7V battery which is solar charged. Everything is running from 3.3V regulator.

I was thinking it might be possible to use a DIO pin to periodically test the voltage and use one of the ADC pins as input. There is no external microcontroller in my current setup. Hoping to just use what's available on the XBee module.

On the receiving end, I could read the ADC value and send an appropriate alert about the battery status.

Thanks for any tips,

Contrapants said...

I've done something similar. I've used a 3.3V zener diode connected to a 5V PIC. I never thought to use an LED before.

Good idea! Thanks for the write-up.

*.* said...
This comment has been removed by the author.
*.* said...

I am trying to get this to work, but somehow the formula doesn't give me the right results.

I am using an XBee Series 1 running at 2.8 V and a diode with 1V forwarding voltage.

My code looks like this:

batteryLevel = (2.8 * diodeFwdVoltage) / (batterySample * (2.8/1024));

What am I doing wrong??