I’ve been trying to accurately measure voltage for my open-source charge controller project. This project relies upon accurate measurement of voltage as this is used to control the regulator.

I have been having some difficulty getting reliable and accurate voltage measurement, so I wanted to figure out why that was.

I am using a simple potential divider to measure the voltage. This is being read by the 10-bit accuracy analog to digital converter (ADC).

In this post I’m going to run through the options available to improve measurement accuracy. I have covered some of this in my voltage measurement post, but this post mainly relates to using the options within the microcontroller to improve accuracy.

I’ve also included some Arduino code to automatically calibrate the Vref value and store it in EEPROM for use in other programs.

Voltage measurement

I’m using the following circuit to measure voltage. This gives me some smoothing via the capacitor, over-voltage protection via the 5.1V zener diode and the resistors themselves reduce the input voltage to a useful range.

Voltage divider

Lets look into how we can improve the accuracy of the circuit.

Resistor tolerance

The resistors used for the potential divider have a specified tolerance, typically 1% or 5%. This means that a 1000 ohm resistor with a 5% tolerance, could have a value from 950 ohms to 1050 ohms.

If the input is 10V then we should expect to see 10V *100k / (680k+100k) = 1.282V at the output.

In the worst case for this potential divider, using 5% accuracy resistors then the 100k resistor could be 95k and the 680k resistor could be 714k.

In this situation the output will be 10V x 95k / (95k + 714k) = 1.174V, which is 91.5% of the expected value, so out by 8.5%!

For this circuit I am using the slightly more expensive 1% tolerance devices. 0.1% tolerance devices are available, but their cost is prohibitive in this case.

Another item relating to resistance, which I have just read in the data sheet, is the input impedance of the ADC input. This is designed to be optimal for around 10k input impedance. With a combined impedance of the 680k & 100k resistor network this will be much higher than the 10k suggested. This will mean that the capacitor used to do the ADC will take longer to fill and hence the ADC conversion time will be longer than optimal.

ADC resolution

This relates to the fact that we are converting an analogue signal into a digital signal. (This is sometimes called quantization). Digital signals can either be on or off. Analogue signals can be anything at all and can vary infinitely within their range. We are trying to get this varying analogue data into our digital device (the micro-controller). In order to do this we use the concept of levels (also called quanta).

Say we have a sine wave signal varying from 0 to 5V. Lets say we only have one bit resolution. The digital representation can only be on or off. If we set the level at 2.5V then we will see the digital signal to be 0 then 1 then 0 then 1 as the waveform goes above and below 2.5V. You can see that we do not get much detail from the signal. This might be enough data to do what we want, but generally we use more levels. The more levels, the higher the resolution and hence the better data accuracy.

Adding an extra bit to the resolution increases the resolution by a factor of 2. Hence 1 bit = 2 levels, 2bits – 4 levels, 3 bits = 8 levels. This quickly multiplies up and at 8 bits we have 256 levels or at 10bits we have 1024 levels. A typical micro-controller (such as a PIC 18 series, or the Atmel chip in an Arduino Uno) has 10-bit accuracy. Some have 12, 14 or even 16 bits. If you go to higher numbers of bits this puts more work onto the microprocessor, so you can also use special analogue to digital chips (ADC) which process the data and sends it to the micro-controller.

In this case we do not want to add any additional components (due to cost and complexity), so we must make the most of the 1024 steps available to us in a 10 bit ADC.


The micro-controller ADC uses a reference voltage in order to make a comparison and hence perform and analog to digital comparison. In the case of the ATMEL microcontrollers I have been using (ATmega328 and ATTiny85) this reference voltage can be obtained in a number of places. These are listed here in the Arduino analogueReference pages:

  • DEFAULT: the default analog reference of 5 volts (on 5V Arduino boards) or 3.3 volts (on 3.3V Arduino boards)
  • INTERNAL: an built-in reference, equal to 1.1 volts on the ATmega168 or ATmega328 and 2.56 volts on the ATmega8 (not available on the Arduino Mega)
  • INTERNAL1V1: a built-in 1.1V reference (Arduino Mega only)
  • INTERNAL2V56: a built-in 2.56V reference (Arduino Mega only)
  • EXTERNAL: the voltage applied to the AREF pin (0 to 5V only) is used as the reference.

Power supply reference

In default the ATmega328 will utilise the Aref, external reference, but the ATTiny25/45/85 will use the power supply line as the ADC reference voltage.

The power supply voltage will be variable depending upon the load applied. This can cause some issues with accuracy. Generally it is best to have a precision reference, rather than a voltage which can vary with load applied.

External reference

An external reference can also be used, applied to the Aref pin. This is standard for the ATmega328, but needs to be specified for the ATTiny25/45/85 and is applied to pin 5.

Using an external reference will require the use of an extra external pin. This is not suitable for this design, but might be a way of performing accurate conversions.

Internal reference

An internal reference of either 1.1V or 2.56V is available for both the ATmega328 and the ATtiny25/45/85. This seems like the most suitable way of performing an accurate voltage conversion.

The main issue with the internal reference is the variation in the reference voltage. This value will be relatively stable, but there is variation between each IC due to manufacturing techniques. The datasheet for the ATTiny25/45/85 states that the 1.1V reference can vary between 1.0 and 1.2V and the 2.56V reference can vary between 2.3 and 2.8V. This will cause issues unless this variation can be cancelled.

Calculating the internal reference

In order to use the internal reference we must have some form of calibration procedure performed for each IC. To do this I needed to calculate the actual Vref for each device.

We know that the integer reading (Vint) be proportional to the Vref value and the Vinput value. The calculation is:

Vint = (Vinput x 1024) / (Vref)

If we apply a know Vinput and can record Vint, then we can calculate Vref. This can be rearranged to give:

Vref = (Vinput x 1024) / Vint

To calibrate a device we perform the following function:

  1. Apply a known accurate and stable reference voltage to Vinput (in this case 1V or 1000mV)
  2. Read a number of sample ins (in this case 100 samples)
  3. Average the Vint
  4. Perform the calculation to calculate Vref in milliVolts
  5. Store the Vref value in EEPROM – This allows it to be used in other code

I decided to store the data in EEPROM in places 126 and 127. Two locations are required as each location can only hold one byte each. These locations were chosen as they are available for each form of ATTiny IC, as the ATTiny25 only has 128 bytes of EEPROM.

The Arudino code is below:

This code worked well for me. I used a 1V input measured by a Fluke multimeter, which is pretty accurate. This gave me a reading of Vref = 2473. This meas that the actual Vref in my IC was 2.473V, rather than the 2.56V I had been using, although this is within the specification of the ATTiny.

The voltage readings from this device were not accurate and I think this might be the first place to start. I will calibrate all the ICs and the use the Vref value in my main code.

EDIT: 15/8/13

When using this code I noticed that a red LED attached to pin 5 (0 on the arduino list) was always lit but very dim. I have since looked into why this is.

From this post, it turns out that the modes for the ATTiny are:

  • DEFAULT = Vcc used as voltage reference
  • EXTERNAL = Voltage at Aref used as reference
  • INTERNAL1V1 = Internal 1.1V reference used
  • INTERNAL2V56 = Internal 2.56V reference used, with external bypass capacitor on Aref

Note the last part – for the internal 2.56V reference, it is implied that you use an external bypass capacitor on Aref, hence the LED was ‘seeing’ the Vref , which should have had a bypass/smoothing capacitor used.

According to the data sheet the bypass capacitor is optional, so we should not need to use a I/O pin to do this.

As eventually found within the readme file of this github repository, I found that the definitions “DEFAULT”, INTERNAL2V56″ etc just relate to a number sent to a register. For example the “DEFAULT” sends binary 000 = decimal 0 to the register. “INTERNAL2V56” sends binary 111 = decimal 7 which switches ON the bypass capacitor. Sending binary 101 = decimal 6 sets to internal voltage reference of 2.56V, but with no external bypass capacitor.

This meant defining a new value for the analogReference. I used:

and then called the analogReference with this value in the set-up script:

I have updated the code above to include this.

One response to “Accurate Voltage Measurement

  1. Regarding impedances. Shouldnt divider output impedance be sigificantly lower than ADC input impedance? Otherwise DAC input impedance affects (sags) dividers voltage thus readings are affected.

Leave a Reply

Your email address will not be published. Required fields are marked *