Accurate Voltage Measurement

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.

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.

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.

AnalogReference()

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:

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:

Apply a known accurate and stable reference voltage to Vinput (in this case 1V or 1000mV)

Read a number of sample ins (in this case 100 samples)

Average the Vint

Perform the calculation to calculate Vref in milliVolts

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

/*

ATTiny Calibrate Vref

Overview:

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 www.re-innovation.co.uk for more details including flow code

14/8/13 by Matt Little

Updated:

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)

intdeviceType=85;// This specifies if it is ATTiny25/45/85

// LED output pins:

intredled=0;// Red LED attached to here (0, IC pin 5)

intgreenled=1;// Green LED attached to here (1, IC pin 6)

// MOSFET Driver output

intFETdriver=4;

// Analog sensing pin

intVsensePin=A1;// Reads in the analogue number of voltage

unsignedlongintVint=0;// Hold the Vint value

unsignedlongintVinput=1000;// This is the input voltage in millivolts

unsignedlongintVref=0;// This holds the Vref value in millivolts

// Varibales for writing to EEPROM

inthiByte;// These are used to store longer variables into EERPRPROM

intloByte;

// the setup routine runs once when you press reset:

voidsetup(){

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)

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.

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:

1

#define INTERNAL2V56NC (6)

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

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

Comment

Name *

Email *

Website

Re-Innovation

Renewable Energy Innovation specialise in electrical and electronic systems for renewable energy projects, mainly solar, wind and micro-hydro. We focus on renewable energy based stand-alone power supply systems (off-grid systems). This includes power and energy monitoring, battery charge control and wiring systems. Please contact us to talk about your project. If you require consultancy, design and implementation services for your renewable energy project please contact us.

Curious Electric Company

The Curious Electric Company specialise in creating electronic kits that help you monitor and measure the world around you, with clever data loggers and sensors you can take the pulse of your planet, collect and share data and knowledge with the world. Be informed - stay curious!

Bespoke Gear

Our sister business, Bespoke Gear have been building interactive pedal-power systems for over 15 years. Doing events and selling equipment to a wide variety of customers in that time we have learnt what works. We design and build high-quality, robust pedal-powered equipment and bespoke, interesting and interactive displays. If you are trying to promote environmental awareness, energy efficiency, getting people fit or just want something thats a bit different for your event then we might have something for you.