# Computing stuff tied to the physical world

## Measuring VCC via the bandgap

In AVR, Hardware, Software on May 4, 2012 at 00:01

The ATmega’s (and ATtiny’s for that matter) all have a 10-bit ADC which can be used measure analog voltages. These ADC’s are ratiometric, meaning they measure relative to the analog reference voltage (usually VCC).

On a 5V Arduino, that means you can measure 0..5V as 0..1023, or roughly 5 mV per step.

On a 3.3V JeeNode, the measurements are from 0 to 3.3V, or roughly 3.3 mV per step.

There’s no point connecting VCC to an analog input and trying to measure it that way, because no matter what you do, the ADC readout will be 1023.

So can we figure out what voltage we’re running at? This would be very useful when running off batteries.

Well, there is also a “bandgap reference” in each ATmega/ATtiny, which is essentially a 1.1V voltage reference. If we read out that value relative to our VCC, then a little math will do the trick:

• suppose we read out an ADC value “x” which represents 1.1V
• with 5V as VCC, that value would be around 1100 / 5000 * 1023 = 225
• whereas with 3.3V as VCC, we’d expect a reading of 1100 / 3300 * 1023 = 341
• more generally, 1100 / VCC * 1023 = x
• solving for VCC, we get VCC = 1100 / x * 1023

So all we have to do is measure that 1.1V bandgap reference voltage and we can deduce what VCC was!

Unfortunately, the Arduino’s analogRead() doesn’t support this, so I’ve set up this bandgap demo sketch:

Sample output, when run on a JeeNode SMD in this case:

There’s a delay in the vccRead() code, which helps stabilize the measurement. Here’s what happens with vccRead(10) – i.e. 10 µs delay instead of the default 250 µs:

Quite different values as you can see…

And here’s the result on an RBBB with older ATmega168 chip, running at 5V:

I don’t know whether the 168’s bandgap accuracy is lower, but as you can see these figures are about 10% off (the supply voltage was measured to be 5.12 V on my VC170 multimeter). IOW, the bandgap accuracy is not great – as stated in the datasheet, which specifies 1.0 .. 1.2V @ 25°C when VCC is 2.7V. Note also that the bandgap reference needs 70 µs to start up, so it may not immediately be usable when coming out of a power-down state.

Still, this could be an excellent way to predict low-battery conditions before an ATmega or ATtiny starts to run out of steam.

1. Can you remove the delay if you leave the value in ADMUX fixed? ie. don’t update before each conversion?

• That was certainly my observation when I tried (and the documentation supports that too)

It is a pity that the arduino analogRead() doesnt support this input. While we are looking at the bits in the ADC, there is also an on chip temperature sensor with a similar accuracy.

2. Interesting technique.

WRT the accuracy, I assume that is the range across a several ATmega chips, how is the accuracy on an individual chip? Can it be calibrated for greater accuracy?

• According to my experience, the accuracy measured over several chips is really as ‘bad’ as stated in the datasheet. No chance to use the bandgap reference in order to get rid of calibration processes in series production. But as you said, you can ‘measure’ the exact value of the bandgap by supplying VCC with a precise ‘known’ voltage and deduce the bandgap value exactly the reverse way.

It is a pitty that Atmel does not do a calibration of the bandgap reference after production (like they do with the oscillator frequency). It might be a bit off-topic, but TI does this calibration for the ‘bigger’ (but still <1€) chips in the ‘value line’ MSP430G series. There you have calibration values in the internal ‘EEPROM’ for different oscillator frequencies as well as for the offset and gain of the 12bit ADC (and some other things). I have used these for calibration of voltage measurements for a solar controller. Believe it or not, accuracy was better than 0.2% (using 0.1% resistors in the voltage divider)! Absolutely amazing.

3. Also, did you put a cap from the AREF pin to ground?

4. Interesting conversation on reducing noise in ADC measurements (last 3 posts): http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1294478456/all

5. Exactly what the doctor ordered ;). I guess this is more power efficient than having a voltage divider on the battery that is always consuming a bit of power?

6. More posts on analog input, please! Apparently there is a lake of information to absorb for the unconscious incompetent ;-) Have been struggling two nights already to read out the voltage of a CPC1824, getting just 1023 all the time (nonsense while in the dark). “just” slap in a jeeNode and measure. Yeah, sure.

7. isn’t there a simple substitute for the bandgap ref ? like a zener or a diode ( how about the “pin 9/13 led” ?). also, I understand that some type adc’s might load the input quite a bit, maybe you could improve the variation in the measurements by buffering the bandgap ref with a simple opamp (or a capacitor, like Simon suggests, i think) ?

8. What you’re measuring is the temperature of the die. The bandgap will decrease by (rule of thumb) 2 mV/K. Heating it up from ambient 20C to operating temp (likely junction temperatures of 65C or even higher) will make the bandgap drop by about a tenth of a volt.

Yes, the rule of thumb is wildly inaccurate. Google ‘bandgap temperature sensor’ to find a lot of references with better models.

9. According to the datasheet 250µs is overkill?

tBG Bandgap reference start-up time: VCC=2.7 TA =25°C typical = 40µs max= 70 µs

10. Did anyone try this on an ATtiny84 (aka JeeNode Micro)?

According to page 146 (datasheet) the 1.1V reference voltage should be available when selecting B100001 as input, everything else should be equal. Why do I always get 0 back from ADC?

``````
ADMUX=B00100001;  // Vcc as Reference, measure internal 1.1V bandgap (Datasheet: p146)
delayMicroseconds(us); // delay substantially improves accuracy
return x;
``````

• Of course… hours of debugging and 5minutes after posting it here I found the solution. This line broke it:

``PRR = bit(PRTIM1) | bit(PRUSI) | bit(PRADC); // only keep timer 0 going``

After removing this line I got some useful values.

11. With regard to the bandgap startup time, I did this same experiment and concluded that the startup time is one variable, but the larger variable was the charge and discharge time of the AREF capacitor present on jeenodes.

Doing an analogRead() as the first step of taking a reading actually does more harm than good because the discharge time is orders of magnitude greater than the charge time. When you do the read it charges the AREF cap to 3.3V, which then must drain through the analog subsystem of the AVR. In my 3.3V test, the impedance of this pathway was about 33Kohm.

Also, I’ve found the bandgap reference to be very stable across a small temperature range (+/-10C) but have a large variation from chip to chip. The best way to “calibrate” the system is to write a sketch that switches the ADMUX to the bandgap then just while(1). Measure the voltage at the AVR’s AREF pin and use that value in your actual sketch. Storing the calibration voltage in EEPROM seemed easiest to me.

For more information about the delay in switching reference voltages, see my blog article “When AREF isn’t AREF (yet)” http://capnbry.net/blog/?p=167

• Wait… I’m not switching AREF. The above trick is about measuring the bandgap while AREF is set to VCC.