Renewable Energy Innovation

  • Increase font size
  • Default font size
  • Decrease font size
Email Print

No image

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:

 ATTiny Calibrate Vref 
 This code is for calibrating the internal reference in an ATTiny25/45/85
 When using the internal reference there is a wide variation in the reference tolerance
 For 1.1V reference this can be from 1.1 to 1.3V
 For 2.56V reference this can be from 2.3 to 2.8V.
 A constant and accurate reference is applied to pin 7 (A1).
 This code is designed to take in 100 samples of the analog input.
 This will give us an averaged reading (Vint).
 We can use these known values (the reading (Vint) and the input voltage (Vinput)) to find the reference (Vref).
 Vinput / (Vref /1024) = Vint
 Rearrange to give:
 Vref = (Vinput x 1024) / Vint
 This value is then stored (as a millivolt reading) in EEPROM, for use by other code.
 ATtiny25/45/85 have different amounts of EEPROM. (128/256/512 respecively).
 The EEPROM can only hold a byte (256) hence we must use 2 EEPROM locations
 We will store this number into EEPROM locations 126 and 127.
 This code is designed to run on the ATTiny 25/45/85
 The serial output only works with the larger ATTiny85 IC
 The connections to the ATTiny are as follows:
 ATTiny    Arduino    Info
 Pin  1  - 5          RESET / Rx (Not receiving any data)
 Pin  2  - 3          Tx for serial conenction
 Pin  3  - 4          FET driver (PWM)
 Pin  4  -            GND
 Pin  5  - 0          RED LED (PWM)
 Pin  6  - 1          GREEN LED
 Pin  7  - 2 / A1     Vsensor (Analog)
 Pin  8  -   +Vcc
 See for more details including flow code
 14/8/13 by Matt Little
 This example code is in the public domain.

#include stdlib.h
#include EEPROM.h

// Only use Serial if using ATTiny85
// Serial output connections:
#include SoftwareSerial.h #define rxPin 5 // We use a non-existant pin as we are not interested in receiving data #define txPin 3 SoftwareSerial serial(rxPin, txPin);

#define INTERNAL2V56NC (6) int deviceType = 85; // This specifies if it is ATTiny25/45/85 // LED output pins: int redled = 0; // Red LED attached to here (0, IC pin 5) int greenled = 1; // Green LED attached to here (1, IC pin 6) // MOSFET Driver output int FETdriver = 4; // Analog sensing pin int VsensePin = A1; // Reads in the analogue number of voltage unsigned long int Vint = 0; // Hold the Vint value unsigned long int Vinput = 1000; // This is the input voltage in millivolts unsigned long int Vref = 0; // This holds the Vref value in millivolts // Varibales for writing to EEPROM int hiByte; // These are used to store longer variables into EERPRPROM int loByte; // the setup routine runs once when you press reset: void setup() { pinMode(FETdriver, OUTPUT); digitalWrite(FETdriver, LOW); // Switch the FET OFF // Set up IO pins pinMode(rxPin, INPUT); pinMode(txPin, OUTPUT); pinMode(redled, OUTPUT); pinMode(greenled, OUTPUT); if(deviceType=85) { // Start the serial output string - Only for ATTiny85 Version serial.begin(4800); delay(100); serial.println("Calibrate Device....."); } analogReference(INTERNAL2V56NC); // This sets the internal ref to be 2.56V (or close to this) digitalWrite(redled, HIGH); // Set the RED LED ON digitalWrite(greenled, LOW); // Read in the Voltage Set-point hiByte =; loByte =; Vref = (hiByte << 8)+loByte; // Get the sensor calibrate value if(deviceType=85) { // Output the data (if ATTiny85) serial.println("Vref from EEPROM:"); serial.println(Vref); } delay(1000); // Have a delay while the device settles // We need to read in 100 samples for(int i=0;i<100;i++) { // Analogue read. We get 100 readings to average things Vint = Vint + analogRead(VsensePin); // Read the analogue voltage delay(10); // Short delay to slow things down } // Average the value Vint = Vint /100; // Calculate the value of Vref: Vref = (Vinput*1024)/(Vint); digitalWrite(redled, LOW); // Set the GREEN LED ON digitalWrite(greenled, HIGH); // Store the data to the EEPROM EEPROM.write(126, Vref >> 8); // Do this seperately EEPROM.write(127, Vref & 0xff); if(deviceType=85) { // Output the data (if ATTiny85) serial.println("Vint: "); serial.println(Vint); serial.println("Vref (mV): "); serial.println(Vref); } } void loop() { // This loop is not used. }

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:

#define INTERNAL2V56NC (6)

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

analogReference(INTERNAL2V56NC); (6)

I have updated the code above to include this.

Add comment

Shop - Buy it here!

USB reprogramming lead
Lots of the kits sold here require a programming cable to program the ATMEGA328 micrcontroller. These kits use a bare microcontroller and do not... Read More...
6 Digit LED Display
This is an all-in-one six digit 7-segment led display unit with an ATmega328 programmed with the Arduino bootloader. The displays can be used to... Read More...
Serial LCD Display
This LCD display unit displays any data sent to it via a serial connection. It is based upon the ATmega328 and the Arduino Uno bootloader. This unit... Read More...
Thermocouple Amplifier
This is a K-type thermocouple amplifier which can be used to amplify the tiny voltage from a thermocouple to a higher voltage, readable by a... Read More...
Bat Detector
Welcome to fascinating world of bats! This bat detector device converts ultrasonic sounds created by bats and convert them down to a lower frequency... Read More...

If any of the information on this site has been useful, please consider buying us a cup of coffee...


Sign up to our newsletter

I sell on Tindie



Latest Posts

8 x 32 LED display unit
I've been messing around with some 32 x 8 LED displays - as I have been wanting to create graphics.... Read More...
Design manufacturing at Nottingham University
I was asked to propose a few ideas for group projects on the product design and manufacturing... Read More...
RGB LED wireless light
Here is a post about what you can do in an afternoon or two with a whole load of electronics stuff... Read More...
Minimus, Arduino IDE and USB
I have mentioned the Minimus AVR before on this blog. It is a very low-cost ATMEL micrcontroller... Read More...
Humidity sensors part II
Following on from my previous post on measuring humidity, I decided to test another couple of... Read More...

Twitter Feed

re_innov Pedal power a cup of tea?
re_innov @markphelan @andrewdlindsay @Justin_Woods cool - looking forward to it. I'll have pedal power (just in case the grid fails)
re_innov @markphelan cool - I'll bring one to EMF. A few photos of what you make would be good (rather than write)...
re_innov @markphelan a couple of mistakes on PCB so not a kit yet. I've got one made up you can have if you write up what you do with it..
re_innov @markphelan @andrewdlindsay Sounds like a plan - let me know and I can bring a driver board to the hackspace to play around with...