Arduino Voltage Reference

The Arduino Voltage reference:

What if you need to know the voltage value of your supply?

  • Ok you say. Fine, I'll just measure it with the ADC.

  • The trouble is you get the same reading of 1023 whatever the supply is set to!

  • The ADC is ratiometric - it's not an absolute measurement as it uses the power supply as a reference. You need to use a separate accurate voltage source as a reference to find out an absolute voltage measurement.

  • There are two things you can do:
    1. Supply an external - accurate - voltage to Aref.
    2. Use the internal bandgap 1V1 voltage reference.

  • The internal bandgap is not without a problem - its initial accuracy is ±9%!

  • Find out here how to calibrate it, and use it to measure your power supply voltage.

The Arduino Voltage Reference (1.1V) is built into some Arduino microcontrollers. The Uno and the Nano both have one, and quite a few of the other Arduinos have them.

This page:

  1. Shows you how to measure the "real" value of Vcc (using the 1V1 reference).
  2. Attempts to quantify the internal voltage reference (how good is it?).

The first item above is interesting; when you use the 1V1 internal voltage reference as a reference to the ADC you can only measure voltages up to 1V1 but in this document you can find out how to "calibrate" the supply voltage.

This is an unusual technique where you use the supply as a reference and measure the 1V1 voltage. If you have previously calibrated the 1V1 reference (to get rid of the 10% error) then your measurement of the supply voltage will be as accurate as the calibration of the 1V1 reference!

The advantage is that you can now measure the complete supply range - using the calibrated supply voltage. The disadvantage is that you need to re-measure the supply voltage  to account for variations over time/temperature changes.

Note: Of course, you could add an external voltage divider to bring the measured voltage into the range of 1V1, but this technique saves those components.

Note: Although the Internal Arduino Voltage Reference is stable with temperature and voltage, its initial value varies between chips (probably due to differences in chip manufacture). You need to calibrate it (see this page).

The Arduino voltage reference is useful when you don't want to go to the trouble of adding an external chip to figure out voltages and need a more stable reference than Vcc or where the supply voltage changes (and you also don't want to add a voltage divider).

For instance the Arduino Uno/Nano can operate from 1V8 to 5V5 and using two AA cells would allow operation (but only if you set the clock to 4MHz - see the datasheet section 29.3 "Speed Grades"). You can use the internal voltage reference to monitor the state of the battery - with no extra components.

Bandgap Voltage Reference

The type of reference in the ATMega328P is a bandgap type. This uses two elements, one with the negative temperature coefficient and one with a positive temperature coefficient which cancel out temperature effects.

While not giving you ultra precise voltage readings in the Arduino, the circuit is designed to be stable against changes in temperature and supply voltage.

This type of circuit layout is used in state of the art references e.g. MAX6143. However, in the ATMega328 and similar, the process to control the layout of the chip on silicon is not as well defined and does not use laser trimming, hence the poor initial accuracy of the Arduino voltage reference (±9%).

You can find out how to eliminate this offset later in this page.


Note: If you use an external reference the ADC measurement will be more accurate since you control the chip specification.
However, remember the internal ADC in the Arduino is not too accurate: Absolute accuracy 2LSB (including INL, DNL, quantization, gain and offset errors).

Warning: Never use an external voltage reference and at the same time enable the internal reference - you may get magic smoke!

Uses for the Arduino Voltage Reference

The Arduino voltage reference can be used in one of five different ways:

  1. For brown-out detection (See datasheet to enable).
  2. As a measurement value in the ADC (fed in to one of the inputs).
  3. As the reference for ADC measurements.
  4. As an input to the comparator.
  5. Temperature measurement (The ADC must use the internal reference).

Brown out detection

Brown-out detection allows the processor to identify when the supply voltage has dipped below a threshold but has not completely gone to zero. This is used to flag to the processor that memory may have been written badly. It just provides an extra layer of reliability to an application.

An input to the ADC

Measuring the reference at an ADC input would allow calibration of a different reference source e.g. the supply (see here) or as a crude comparison to other ADC inputs (unless you calibrate the reference first as shown in this page - in which case it could be quite accurate (~1%)).

ADC reference

Using the Arduino voltage reference as you would expect it to be used!

TIP: Using the 1V1 reference means the 10 bit ADC has an LSB value of  ~1mV (1.074mV/LSB). However, you can then only measure voltages up to 1.1V!

Comparator input

Using the voltage reference allows crude comparison i.e you can only compare any input voltage to 1V1. One interesting point is that any of the ADC inputs can be compared by the comparator - and that is without using any more external components. See section 23.2 of the datasheet labelled "Analog Comparator Multiplexed Input".

You could ask the question "Why bother using the comparator when you can easily calculate the result using the ADC?" The answer is speed.

The comparator can compare two voltages within 100s of nanoseconds whereas the ADC is a successive approximation type and takes about 13us to gather the ADC value. This value has to be retrieved by the processor and then you would compare the value to a limit setting, taking far more time.

Temperature Measurement

Since the Arduino voltage reference is independent of temperature it can be compared to the output of a temperature dependent circuit - a circuit inside the chip. This allows you to measure temperature in the ATMega328P.

The temperature sensing element in the ADC can be set to one of the ADC inputs. The datasheet states that the temperature coefficient is 1mV/ºC but that the initial accuracy is ±10ºC due to process variation in chip manufacture. (See section 24.8 of the datasheet labelled "Temperature Measurement"). To get an accurate reading requires calibration of this system.

How to Measure the Supply Voltage

You can operate the microcontroller (ATMega328p - Uno, Nano) from between 1V8 and 5V5. The microcontroller can not detect this "real" value of the supply voltage this unless you use a known reference voltage to figure it out. This is because the ADC measures everything relative to a reference i.e. the supply voltage is the reference!

Warning: At VCC=4.5V clock can be 20MHz and at VCC=2.7V the clock speed must be <= 10MHz, and for VCC=1.8V the clock speed must be <= 4MHz). See section "29.3 Speed Grades" which shows that the clock frequency must be linearly reduced with decreasing supply voltage.

This method is a sort-of reverse method of ADC measurement. Instead of using the voltage reference you use the supply voltage as the reference and the "Arduino internal voltage reference" as the quantity to be measured!

Since you know that the bandgap reference is 1.1V then you can figure out the supply voltage value by doing a simple bit of maths (below).

TIP: Calibrate the Arduino voltage reference for better accuracy.

It is done in this way since you can't select the Vcc voltage as an input to the ADC. See the "ADC Mux block diagram" below.

In this case the Supply voltage "reference" is worth:

    VBIN = VCC/1024  V/LSB   [ pow(2,10) = 1024]

In the above equation VCC is unknown (to the microcontroller!).

By measuring the 1V1 reference, and obtaining the ADC value for it you can solve the equation

    VBIN = VREF / ADCVALUE

Therefore:

    VCC/1024 = VREF / ADCVALUE

So

    VCC = 1024 * (VREF / ADCVALUE)

and...

    VCC = 1024 * (1V1 / ADCVALUE)

Note: The value 1024 is correct (1024 is the divisor of VCC for the reference voltage).

TIP: For a better result calibrate the Arduino voltage reference.

To figure out a value of the supply voltage using the Arduino voltage reference:

  • Set the ADC reference source to Vcc.
    (This is an option in the ADC mux - see diagram below)
  • Take an ADC reading of the Bandgap voltage (Arduino voltage reference).

You can use the equations above to calculate the supply voltage or follow the example below.

Example Sketch1 Measuring Supply Voltage

Put the following code into your sketch within the setup() function.

If you calibrate the reference then change the value of ...
	arduinoADCcal 

...to your measured value. The representation of 1.1V is 11000 which is a fixed decimal point representation where the decimal point is assumed to be one place to the right. So you can think of 11000 representing the number of 0.1mVs in the actual voltage i.e. 11000 x 0.1e-3 = 1.1V

Using an ADS1115 or good voltmeter will give you enough digits. If you use a standard DVM then you will only get 3 digits  (on a 2 volt range) so set the last two digits to zero.

////////////////////////////////////////////////////////////////
void selfMeasureVcc(char *buf) {
uint8_t low,high; // ADC bytes.
// Default reference value. Set this to your measured reference voltage (or leave as is).
// The value uses an integer fixed point representation
// with the assumed decimal point one place to the right.
static unsigned long arduinoADCcal = 11000;

   if (strlen(buf)>1) {
      if (strlen(buf)<6)
         Serial.print(F("Not enough digits"));

      else if (strlen(buf)>6)
         Serial.print(F("Too many digits"));
      else
         arduinoADCcal = atoi(&buf[1]);

   } else if (strlen(buf)==1)
      Serial.print(F("Ref calibration value: ")); Serial.println(arduinoADCcal);


   // ADCSRA |= _BV(ADEN); // Enable ADC. Enabled by init() in Arduino code.

   ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // Set VCC ref and band gap as input.
   delay(2); // Vref settling time delay

   // Throw away 1st result as datasheet says
   // After switch, ref may be invalid.
   // Datasheet: "24.5.2 ADC Voltage Reference".
   ADCSRA |= _BV(ADSC); // Start conversion
   while ( bit_is_set(ADCSRA, ADSC) ); // Wait for conversion complete.

   ADCSRA |= _BV(ADSC); // Start conversion
   while ( bit_is_set(ADCSRA, ADSC) ); // Wait for conversion complete.

   // ADCL must be read first, then ADCH, to ensure that the content
   // of the Data Registers belongs to the same conversion
   // Datasheet Section "24.2 Overview"
   low = ADCL;  // Read ADCL first which blocks ADCH updates.
   high = ADCH; // After reading ADCH both registers can be updated by the chip.

   long ADCValue = (high << 8) | low;
   Serial.print(F("ADC value of 1V1 ref: "));  Serial.println(ADCValue);

   #define BINS 1024L // VCC/1024 is the value of each LSB of the ADC.

   Serial.println( (arduinoADCcal)* BINS );
   Serial.println( ((arduinoADCcal * BINS)+5L) );
   Serial.println( ((arduinoADCcal * BINS)+5L) / 10L );

   // Note +5 = middle of LSB bin
   // Calculate Vcc (in mV); 1125300 = 1.1*1024*1000
   // Here ccal value is 1.1*10e4 so no need for *1000 and must divide by 10.

   uint32_t res = (((arduinoADCcal * BINS)+5L) / 10L) / ADCValue;

   // Calculate Vcc (in mV); 1126400 = 1.1*1024*1000
   // should be 1024 / ADCValue = 1125300L / ADCValue; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
   // internal1.1Ref = 1.1 * Vcc1 (per voltmeter) / Vcc2 (per readVcc() function)
   Serial.print(F("Measured (mV) ")); Serial.print(res); Serial.println(F(" mV"));
}

Arduino Voltage Reference Block Diagrams

Arduino Uno/Nano ADC Mux block diagram

Arduino ADC mux for bandgap voltage reference operation
Source: ATMega328P datasheet

Arduino ATtiny85 ADC Mux block diagram

Attiny85 muc block diagram
Source: ATtiny85 datasheet

ATTiny85 Internal Voltage Reference

The ATTiny85 voltage reference, which is selectable as either 1V1 or 2.56V, is slightly differently arranged in the ADC. The AREF pin can only be set up as an input (there is no switching FET that connects the internal reference mux to the AREF pin). So you cannot setup the Arduino voltage reference for measurement at the AREF pin.

Therefore you can't calibrate it directly. You would either assume that it has the stated value or accurately measure the Vcc supply then take an ADC reading (but remember the ADC has its own errors as well!).

One way that would eliminate ADC errors is to feed in a variable voltage to an ADC input - which you measure. Then compare it to the reading from the voltage reference (ADC reading). Once the two readings match the input voltage would then reveal the true Arduino reference voltage (of course only to 1LSB accuracy).


TIP: Increase an ADC bit resolution by oversampling.

Calibrating the Arduino Voltage Reference

To calibrate the reference in an Uno or Nano you just turn on the reference and measure the voltage at the reference pin. Remember the only thing on the pin should be a capacitor and no other driving circuitry (if you want to preserve the chip!).

Warning: Do not connect any other voltage reference to the VREF pin when switching the Arduino (Uno/Nano) voltage reference on - there is no protection and you could damage either the external reference or the internal reference circuit.

Turning on the reference is quite simply using the following Arduino code:

    analogReference(INTERNAL); 

However, you will need to activate the ADC to enable the reference, so a simple read will do.

    analogRead(0); // Could be any channel

This is due to the way the "wiring" code is written - the mux registers and REFS0/1 are only written just before an ADC read command (within the analogRead() function).

For the arduino Mega you can choose between the following:

    INTERNAL1V1
    INTERNAL2V56

Place the above code in the setup() part of the Arduino Sketch.

Warning: Ensure NO voltage is applied to the Vref pin before this test.

If you are going to use the internal reference then there should not be an  voltage applied, but if using a circuit from somewhere else there could be an external reference of voltage divider attached.


TIP: Attach a 100nF capacitor to the Vref pin for noise reduction.
Now connect a multimeter to the VREF pin and write down the result (for use in your code - see measuring the supply voltage, specifically).

TIP: Use a standard multimeter, or for more digits use an ADS1115 in differential mode or a Fluke meter (very expensive).


To set the reference back to the supply use:

    analogReference(DEFAULT);

...again followed by...

    analogRead(0); // Could be any channel

Code for analogue operation is found here:
"C:/Program Files (x86)/Arduino/hardware/arduino/avr/cores/arduino/wiring_analog.c"

Definitions (#define ) are here:
C:/Program Files (x86)/Arduino/hardware/arduino/avr/cores/arduino/Arduino.h

The Arduino code above ( for internal reference selection ) will set the registers as follows (datasheet section: 24.9.1 ADMUX ADC Multiplexer Selection Register):

ADMUX bits 7 and 6 set high (REFS1 and REFS0).
ADMUX bits 3..0 set to B1110.

Note: The data sheet states:

"The first ADC conversion result after switching reference voltage source may be inaccurate, and the user is advised to discard this result."
 
(See datasheet section "24.5.2 ADC Voltage Reference").

So you should throw away the first ADC result.

Arduino Voltage Reference Performance

The information in the datasheet on the Arduino voltage reference states that it has maximum and minimum values of 1.0V ~ 1.2V, and nominally 1V1. Other than this there is no more information at all.

There is no information about repeatability or stability. It suggests that the voltage could vary by ±9% around 1.1V.

So the real question is:

    Is the Arduino voltage reference any good at all?

The specification seems to imply that it might vary ±9% at any time. In fact this is not true and the voltage reference is far better, and quite useful.

On this page there are some voltage reference measurements - these are not exhaustive but will give you a flavour of the bandgap reference performance.

Arduino voltage reference Questions

Questions asked here are:
  • How does the reference voltage change with temperature?
  • How does the reference voltage change with Vcc?
  • How stable is the reference voltage in general?

Remember these measurements are based on testing a few chips and one or two sets of measurements. They will therefore give an impression and not the absolute answer.

Note: All voltage measurements were made using an ADS1115 on a breakout board. All voltage measurements were made in ADS1115 differential mode. Temperature measurements were made using an MAX6675 and type J thermocouple.

Initial Accuracy

The initial accuracy is bad at ±9% but calibrating the Arduino voltage reference allows you to eliminate this error.

Note: The MAX6143 has an initial accuracy of 0.05% (180 times better c.f. 9%).

Output Stability

Two boards are measured, the first is s standard Arduino uno board and the second is an Arduino Nano board. These both use the same processor ATMega328P.

Measurements made on different time/days of an Arduino Uno powered from the USB (hub) power supply gave the following results:

Arduino Uno USB Stability Measurements

Arduino Uno (PDIP)
USB (HUB) Power Supply
Voltage (Ambient temp)
% error (5V)
Internal
Reference
% error (1V1)
4.806V (21ºC) Diff
3.9%
1.104V Diff
0.36%
4.826V (16ºC) Diff 3.5%
1.097V Diff
0.27%
4.862V (22ºC) Diff 2.8%
1.0976V Diff
0.22%
4.878V (21ºC) Diff 2.4%
1.0980V Diff
0.18%
4.842V (20ºC) Diff 3.2%
1.0975V Diff
0.23%
4.809V (19.5ºC) Diff 3.8%
1.0963V Diff
0.34%
4.788V (23ºC) Diff
4.2%
1.0957V Diff
0.39%
4.866V (23ºC) Diff 2.8% 1.0956V Diff
0.39%
4.853V (20ºC) Diff 17/2/20 2.9%
1.0958V Diff
0.38%
4.876V (17ºC) Diff 18/2/20
2.5%
1.0961V Diff
0.35%
4.860V (18ºC) Diff 24/2/20 2.8%
1.0957V Diff
0.39%

Max error change for Vcc  =  4.2  - 2.4   = 1.80 %
Max error change for Vref = 0.39 - 0.18 = 0.21%

Measurements made on different time/days of an Arduino Nano powered from a PC USB port gave the following results:

Arduino Nano USB Stability Measurements

Arduino Nano (MSOP)
USB (PC) Power Supply
Voltage (Ambient temp)
% error (5V)
Internal
Reference
% error (1V1)
4.821V (20ºC) 3.6%
1.070V
2.72%
4.824V (16ºC) 3.5%
1.069V
2.81%
4.831V (17ºC) Diff
3.4%
1.068V Diff
2.91%
4.825V (22ºC) Diff
3.5%
1.068V Diff
2.91%
4.851V (21ºC) Diff 3.0%
1.068V Diff
2.91%
4.852V (15ºC) Diff 3.0% 1.069V Diff
2.81%
4.830V (23ºC) Diff 3.4%
1.067V Diff 3.00%
4.833V (20ºC) Diff 17/2/20 3.3%
1.067V Diff
3.00%
4.862V (17ºC) Diff 19/2/20 2.8%
1.0691V Diff
2.81%
4.879V (20ºC) Diff 21/2/20
2.4%
1.0715V Diff
2.69%

Max error change for Vcc  =  3.6  - 2.4   = 1.20 %
Max error change for Vref =  3.00 - 2.69 = 0.31%

The USB voltage varies by 2.4% to 3.6% and this does not have a big effect on the error variation in the reference (See voltage measurements below).

Notice how the reference voltage remains constant i.e. has a constant offset error. Even though the voltage reference starts off from a value of 1.070V it keeps close to that value. This is probably due to the manufacturing process and seems to remain constant over time.

For the top set of results, the supply is taken from a USB hub with its own power output while for the bottom it is a direct PC USB output. The latter seems a little more stable (Nano).

In both cases using the on board internal voltage reference will give a more consistent result as it will have tighter voltage regulation.


Note: Remember you are either using the 5V USB supply directly (when working on the bench) or using an external power block. The on board 5V regulator - LD1117AG (same as NCP1117) - has a tighter error spec (0.1V in 5V = 2%) than USB power (see supply sensitivity measurements below).

Conclusion on Stability

Even though the Arduino voltage reference has an initial offset error (probably due to the manufacturing process) the stability of the reference remains very good. with measurements here showing between 0.21% ~ 0.31% change.

Voltage Supply Stability

Using an ATMega382p on a solderless breadboard and connected to a variable supply voltage.

Supply Stability Measurements

Supply Voltage

Internal
Reference
% Initial error
change(1V1).
5.261V (20ºC) Diff 1.0995V Diff
0.05%
4.948V (20ºC) Diff 1.0988V Diff
0.11%
4.389V (20ºC) Diff 1.0980V Diff
0.18%
4.056V (20ºC) Diff 1.0975V Diff
0.23%
3.615V (20ºC) Diff 1.0971V Diff
0.26%
3.370V (20ºC) Diff 1.0968V Diff
0.29%
2.759V (20ºC) Diff 1.0965V Diff
0.32%
Note: The reference produced odd values at 2.3V so they are not included. The reason is that the maximum clock speed allowed is proportional to the supply voltage - should lower clock speed to 4MHz for operation at 1V8 (See datasheet section 29.3 Speed Grades - safe operating area).

The error changes by -1.2mV per Supply volt drop when starting from from 5.26V. This is -.11% per supply volt drop.

    calculation: [ (1.0965-1.0995)/(2.759-5.261) ].

TCL
proc perr {max val} {
   puts [expr  { (($max-$val)/$max)*100.0 } ]
}

Conclusion on Supply Voltage

The bandgap voltage reference seems quite good, being insensitive to supply voltage changes. It drops by 1.2mV per supply volt drop starting at 0.05% error at 5.26V supply. If the supply is held constant then this offset error can also be eliminated by calibrating the reference.

Temperature Stability

Using a thermocouple (type J) with the MAX6675 chip and the MSOP Arduino Nano. Heating the chip for a minute and waiting until the temperature stabilised. The MAX6675 thermocouple has an offset temperature error but what we really want is the change in temperature and how that relates to the reference output. The ambient temperature is also measured using a different device to give a reference point for the thermocouple.

Arduino Nano Temperature Measurements

For the Nano ambient temperature measured by the thermocouple is 24.5ºC (original thermometer says 23ºC) so quite close. Chip temperature is 30ºC.

1st measurement:

  • Vref is 1.0674V.
  • Vref is 1.0659kV heated chip to 75ºC

Drop is : -1.5mV

Change over temperature

For a 35ºC temperature change the reference voltage changes by -1.5mV. roughly -1.5/35 = -0.043mV/ºC. However this is based only on one device and one measurement!

    Change is -0.043mv/ºC

For a temperature increase from 25ºC to 125ºC (100ºC delta) the reference should drop by 4.28mV. In percentage terms that is (4.28e-3/1.1)*100 = 0.39%.

For a temperature decrease from 25ºC to -40ºC (-65ºC delta) the reference should increase by 2.79mV. In percentage terms that is (2.79e-3/1.1)*65 = 0.16%.

If you consider a simple transistor that has a -2.1mV/ºC temperature change then for the same temperature delta you would get a 201mV voltage change (18.2% change) so the bandgap circuit is doing quite well!

2nd measurement:

  • Vref is 1.0665V.Temp 30ºC
  • Vref is 1.0652kV heated chip to 75ºC
Drop is : -1.3mV - So this is a similar result to the first one.

3rd measurement:

  • Vref is 1.0688V.Temp 30ºC
  • Vref is 1.0683kV heated chip to 75ºC
Drop is : -0.5mV - A little different, but in the same range.

Conclusion on Temperature

The temperature performance is good with only 0.55% reference change over the full temperature range (estimated from 35ºC to 75ºC results).

The reference voltage changes by 0.003% per ºC (estimated) [ 0.55%/165 (estimated) ].

Note: The MAX6143 has 1000 times better temperature performance at 3ppm/ºC.

Over the full temperature range (-40ºC to +125ºC) the reference will change by about 0.55% (0.39% + 0.16%) = 0.55% (from 1V1) which is not too bad.

The voltage reference should change by +2.79mV ~ -4.28mV (from 1V1) over these temperatures.

Conclusions on the Arduino Voltage Reference

It is possible to use the internal bandgap reference (1V1) to measure the supply voltage of your microcontroller. As long as you calibrate it first it can be accurate even with changes to temperature and supply voltage.

This makes it useful in monitoring the supply voltage especially in battery supplied systems where it is important to display to the user when the battery is going to run out!

The reference can have a large offset (due to the manufacturing process) ±9% so its initial accuracy can be bad. You can eliminate this by calibrating it with an accurate voltage measurement.

Supply voltage sensitivity: ~-1.2mV per Supply volt drop.
        This is ~-.11% per supply volt drop.

Temperature stability: ~0.003% per ºC (estimated)    
        ~0.55% over the full temperature range.

Output voltage stability: ~0.31%

To get the best results from the Arduino voltage reference you should calibrate it (just measure the voltage reference with a multimeter or ADS1115 chip. This will then eliminate offset voltages due to manufacturing process and supply volt sensitivity (if the voltage source remains constant i.e. supplied through a regulator).

This will then give a stability of 0.55+0.31 = ~0.86%.

Remember this is an estimate based on very few samples, so your results may vary, but it does show that the reference can be made more stable if calibrated. It also shows that the Arduino voltage reference may be more useful than you imagined.





Written by John Main who has a degree in Electronic Engineering.


Comments

Have your say about what you just read! Leave me a comment in the box below.

Don’t see the comments box? Log in to your Facebook account, give Facebook consent, then return to this page and refresh it.




Privacy Policy | Contact | About Me

Site Map | Terms of Use