Battery Fuel Gauge with Zero Parts and Zero Pins on AVR

Blink LED Cropped

It can be nice to know how much battery power you have. It becomes critically important with LiPo batteries since you can permanently damage them by running the voltage down too low. Typically battery voltage detection requires adding a circuit with extra parts and their associated power requirements. Wouldn’t it be great to be able to do this using nothing but software? Read on for a no parts, no pins, no power solution…

Perfunctory Video

Normal Low Battery Detector Circuit

Here is a typical by-the-book low battery detector for LiPo-powered devices…


The voltage divider R5/R9 scales down the battery voltage, where it is compared to an integrated reference voltage inside U3, which outputs a LOWBAT signal that would typically connect to an input pin on your micro-controller.

This circuit works fine and will indicate when the battery voltage drops below the threshold voltage (about 3.6 volts with the above values), but it has a few drawbacks…

  • The voltage divider formed by R5 and R9 is always drawing current – 12uA at the low cutout voltage.
  • The comparitor U3 is always drawing current – typically ~2.5uA at the cutout voltage.
  • The pull-up resistor R6 is drawing ~7.6uA whenever there is a low battery.
  • Requires a dedicated input pin on your micro-controller.
  • Requires 5 physical parts that cost money and use up board space.
  • Can only detect a single hardwired threshold voltage.
  • The threshold voltage depends on the tolerances of R5 and R9, and there is no way to calibrate it.

Do we really need 5 parts, an input pin, and >20uA of constant power draw just to detect a low battery?

Do we really need 5 parts, an input pin, and 20uA of constant power draw just to detect a dead battery?

Power is precious, especially if  you are using an adorably small LiPo battery. And this circuit continues to pull power even after it has detected a low battery condition- there is no way to turn it off. We can do better…

The Zero Part, Zero Pin, Zero Quiescent Power Solution

Thanks to the AVR’s flexible analog-to-digital converter, you can accurately detect battery voltage completely in software, without sacrificing a single pin or adding a single part to your design! And it does not draw any power when not in use! Yeay!

From the datasheet…

The ADC converts an analog input voltage to a 10-bit digital value through successive approximation. The minimum value represents GND and the maximum value represents the reference voltage.The voltage reference for the ADC may be selected by writing to the REFS1:0 bits in ADMUX. The VCC supply, the AREF pin or an internal 1.1V voltage reference may be selected as the ADC voltage reference.

Typically you would be measuring an unknown voltage (like an input pin) against a known scale (a reference voltage). Our trick is that we are going to do the opposite – we are going to measure a known voltage (the internal 1.1 volt reference) using an unknown scale (the Vcc voltage). Normally it would be silly to measure a known voltage (we already know what it is!), but in this case it will let us reverse-calculate the reference voltage. I wonder if this is what the engineers who designed this chip had in mind when they made it possible to use the internal reference as a input?

Normally it would be silly to measure a known voltage (we already know what it is!), but in this case it will let us reverse-calculate the reference voltage.

Here is a diagram of the analog-to-digital block with the two sources we will be using highlighted…


How do we actually compute the Vcc voltage?  Back in the data sheet  we find…


ADC here is the result value output by the analog to digital converter.

In our case, Vin is the internal 1.1 volt reference voltage and Vref is the Vcc power supply voltage. If we substitute those in, we get…


…and (if you were awake the 3rd week of 7th grade) algebra gives us…

Vcc = (1.1v * 1024) / ADC

It is so simple a child could do it! We can now compute the current Vcc voltage based solely on the output of the analog to digital converter! But what are we actually doing here?

Imagine that I gave you a stick that had 1024 evenly spaced markings along the length of it. I also gave you another shorter stick and told you that it was exactly 1.1 feet long. Could you figure out how long the longer stick was?


Of course you could! All those LSAT prep classes weren’t a waste after all!

You would take the short stick and measure it with the longer stick and count how many marks long it was. Let’s say it was 250 marks long. So…

250 marks = 1.1 inches

1 mark = 1.1 inches/ 250

1 mark = 0.0044 feet

If 1 mark is 0.0044 feet, and we know the long stick is 1024 marks long, then we know that the long stick is 1024 marks * 0.0044 feet per mark = 4.5056  feet. Our long stick is ~4.5 feet long!

Now change [feet] to [volts] and [marks] to [steps of the analog to digital converter] and… we measured the 1.1 internal reference voltage using a scale that is based on our unknown Vcc voltage and we got an ADC value of 250. This means that our Vcc is at ~4.5 volts!

How precise is this technique? The higher the Vcc, the lower our resolution so let’s take the worst case which is at the device’s maximum allowed Vcc of 5.5 volts. At this limit, a step of the ADC is equal to ~5mV. That is a couple of orders of magnitude more resolution than we need for competent state-of-charge battery measurements, which typically only call for 10ths of volts of resolution.

How accurate  is this technique? The two main sources of inaccuracy are (1) the fact that the internal reference can range from 1.0 volts to 1.2 volts depending on manufacturing processes, and (2) the inherent inaccuracies of the ADC. Practically speaking I’ve found these Vcc measurements to be accurate to within 0.1 volts right out of the box.  If you were going to use these measurements for, say, detecting the cut-off charging voltage for a LiPo battery, then you could calibrate the exact ADC measurement that matched the target voltage for each individual device and probably get within 10’s of millivolts.

Enough chat chat! Show me the code!

Circuit Drawings

Scheme Capture

Board Capture


Q: Since the bandgap voltage reference is 1.1 volts, doesn’t this limit the minimum supply voltage I can measure to 1.1 volts?
A: Theoretically yes, but since a typical AVR cuts out at either 2.7 volts (or 1.8 volts for V series parts), this is not a practical limitation since the chip would already be non-functional.

Q: Won’t the battery automatically disconnect before the voltage gets low enough to damage it?
A: Some LiPo batteries come with a protection board attached and this board will typically disconnect when the voltage drops dangerously low, but not all batteries have this board – and you still probably don’t want to let your batteries get low enough to trigger the board since the number of cycles a battery can survive is dependent on the depth of discharge.

Q: Doesn’t the datasheet say that “Internal voltage reference options may not be used if an external voltage is being applied to the AREF pin”, which would imply that you would loose the use of the AREF pin (pin 13) for other functions?
A: The datasheet also says that when “Vcc [is] used as analog reference, [the ADC is] disconnected from PA0 (AREF)”, which make sense. Hmmm… I’ve actually tested this and applying an external voltage to pin 13 does not seem to affect the ability to use the internal reference at all. I have an open case with Atmel to get a clarification on this and will report back.

UPDATE 11/11/2014- I got a clarification from Atmel that on the ATTINYx4A the AREF pin is, in fact, disconnected when the Vcc is used for the Vref. My guess is that the conflicting sentence was mis-copy/pasted from another part like the ATMEGA48A which does apparently have this limitation…


From Figure 24.1, ATmega48A/PA/88A/PA/168A/PA/328/P [DATASHEET]

Q: Its not really fair to say that this solution uses _no_ power since the ADC does use power. 
A: True, the ADC does draw a tiny bit of power (100’s of uA) while it is actually doing a conversion, each conversion only takes a tiny bit of time and the rest of the time you can disable the ADC. Most importantly, the ADC is not drawing any additional current when the chip is sleeping and in shut down because of a low battery.

Q: I’m trying to do low battery detection on an ATTINY25/45/85, but I can’t figure out how to set Vin to the internal reference?
A: Apparently the ATTINYx5 does not have a Vin connection for the internal reference, so we have to get a bit more tricky. The basic idea is to do two samples – first sample the internal temp sensor using the internal 1.1v as Vref, and then sample the temp sensor again, but using the Vcc as the Vref. Since the voltage of the temp sensor is very low, you will not get great resolution but it should still be good enough for battery level gauging, and you can again do some calibration if you need more accuracy.


  1. TyTower

    Now my question is Can this be ported to an atmega328p easily and could you give me an idea of what port names here should be changed and what should they be changed to? I would like to play with your code on my friendly 328p but with my limited knowledge it will take me a week to research which ports should be which.

    • bigjosh2

      Looking quickly at the datasheet, looks like want ADMUX= 0b01001110 (use Vcc as reference, bandgap as Vin). Alternately, you could connect the Vcc pin and the AREF pin together and use ADMUX=0b00001110 (use AREF as reference, bandgap as Vin).

      You’d also probably need to change you prescaller since you are likely running your 328 faster than the 1mhz my lowly ATTINY is running at. In this case you can try ADCSRA |= 0b00000111 to uses the highest possible prescaler.

      Everything else should be the same, I think. LMK if it does work and we will figure it out!

  2. BradN

    This trick is also applicable to various PICs that have the fixed voltage reference available to the ADC as a measurement input. Potential tip on some parts: Route the FVR to the DAC (DAC output pin need not be enabled), and then measure the DAC – seemed to improve accuracy in my tests vs measuring the FVR directly, but YMMV.

  3. Redwire

    Nice, couple of suggestions:
    1. Why not have it do two sequence blink to get the millivolt reading as well.
    2. You can use the sleep mode and provide a longer delay between readings, saving some time. Or you could add a button to wake up and take a reading.
    3. By measuring the actual resistance of your voltage divider network, you could program an adjustment to the voltage formula to get better accuracy. -Fouth Grade math!

    • bigjosh2
      1. This technique is only accurate to 1/10ths of volts (especially without calibration and temp compensation), so probably not fair to ask it for millivolts.. :)
      2. Absolutely! This is just example code, but you would likely want to take readings sparsely and sleep in between to save power in a real battery powered application.
      3. Totally! There are even Atmel data sheets showing ways to do this.
  4. Nerd Ralph

    This works on the t85 without any temperature sensor reading tricks. The mux setting for Vbg is MUX[3:0} 1100 – see table 17-4 in the datasheet.
    Set REFS1 & REFS0 to 0 for Vcc.

  5. Nico Böckhoff

    Hi, i tried out this example on the attiny85 and got it to work after changing line 57
    from: ADMUX = 0b00100001; to: ADMUX = 0b00001100;

    bitnum: 76543210
    ADMUX: 00001100

    bit 6 is the ADLAR bit on the attiny85 that sets the left adjustment for the adc, we do not want the result left adjusted if we read the adc like it is done in the code (see line 96-101)

    bit 3-0 set the MUX to the internal 1.1v reference as @Nerd Ralph pointed out.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.