In my previous blog entry I described adding a solar cell to the widget and how I could measure the voltage produced by the Solar Cell, however I still need to monitor actual battery voltage easily, and preferably with not a lot of additional hardware.
Problems reading ones own voltage.
Measuring ones own voltage has a couple of problems:
- You can’t just measure VCC directly, i.e. you can’t connect an analog pin to VCC. The reason is the battery voltage varies, and since the default AREF is tied to VCC you will always read VCC unless you use some form of a reference not tied to VCC.
I can also use a reference diode and a resistor between VCC and GND, to form an external reference voltage, the diode has a known voltage drop across it so this can be measured using the default AREF. We can calculate the battery voltage by measuring the volt drop across the diode. While this will work, the circuit will always consume a small amount of power, even when not being used, remember I want to get as much life out of my batteries as possible. So it rules this out as a solution.
For the same reasons as 1) I can’t use a simple voltage divider and the default AREF. As the voltage varies so too will my readings.
I can use a simple voltage divider and the internal 1.1v bandgap reference for my AREF, the same as I used for my solar cell voltage monitor in the previous blog entry. Again this will work, but also as in 2) this will always consume some power even when not being used, so is ruled out as a solution in this case.
No Extra components?
We can however measure VCC voltage with no external components by simply reading the value of the internal bandgap reference voltage (1.1v) which is always fixed regardless of the battery voltage, and by using the default AREF which is tied to VCC, so will vary as the battery voltage changes. We now have a way of measuring the voltage change against a fixed reference.
V_BAT = (VREF * 1024) / analogRead(14);
|fixed reference voltage in this case 1.1v, but could be referenced to an external reference.|
|raw ADC reading from MUX channel 14|
You might see in the above formula, that analogRead() function is reading channel 14. But analogRead() only supports channels 0-7 right?
Correct, but what we are doing is to read the internal ADC channels after we make a small change to the Arduino source code.
Changes to “wiring_analog.c”
To take an ADC measurement of the internal bandgap we need to switch to ADC MUX channel 14 (23.8.1 in reference manual). However to do this with the arduino wiring library we need to make changes to “wiring_analog.c” as per http://code.google.com/p/arduino/issues/detail?id=10. This change allows us to read from all ADC MUX channels, internal and external.
Once you’ve made the change to “wiring_analog.c” then use the following example sketch to read your VCC/Battery Voltage.
uint16_t raw_bandgap = 0; // Internal bandgap reference
float volt_battery = 0.0;
// Read Battery voltage
analogReference(DEFAULT); // use VCC as AREF
raw_bandgap = analogRead(14); // read and discard first result after changing reference (see 23.5.2 of manual)
raw_bandgap = analogRead(14); // measure internal bandgap voltage
volt_battery = (1.1 * 1024) / raw_bandgap; // calculate voltage
When this method won’t work.
If you have a voltage regulator or a protection diode inline between your battery and VCC, then this will only show the voltage at VCC, not the true battery voltage.
If you have a voltage regulator then you will always read what it puts out, and not give a true indication of battery voltage. I guess you could still use the method as a form of low battery measurement. i.e. when the battery falls below regulation values I guess the regulator will fail to regulate and you should get a lower than normal reading. however I haven’t tested this theory.
If you only have a protection Diode then measure the volt drop across it and compensate in your calculations, it will still give you a good indication of battery voltage.
Some other uses for this method is for auto-calibration of sensors as described by ladyada(you need to go to the bottom of the page)