I have built an Arduino to accurately test a 12Vdc (goes from 14Vdc to 0 Vdc) battery under load over time. Works well but seems a bit inaccurate.
I use a resistor voltage divider mounted on a proto board to reduce the Arduino analog input voltage to below the 5Vdc maximum. But when I measure the real battery voltage with a good quality voltmeter I don't get the same (calculated) value via the analog pin. It can be off by multiple tenths of volts.
To increase accuracy I measured the 5% resistors in the divider to make sure my conversion formula of analog pins volts to real battery volts is as accurate as it can be.
As far as I can tell from my reading the 5Vdc supply is the REFERENCE voltage used for measuring the analog inputs. True? Assuming this to be true I measured the 5Vdc reference and found it off by a few tenths. Thus my real battery voltage calculations would be off too. Why is the 5Vdc low?
My working theory is that my 240x320 TFT LCD touch color display shield is dragging down the 5Vdc rail. The Arduino is using an external power supply, and/or the USB input - didn't make any difference. I did try changing my voltage divider calculations by using my accurate measurement of the 5Vdc reference. That helps but is specific to the Arduino being used. Some boards seem "better" than others.
Then I hit upon the idea of using another analog input to accurately measure the 5Vdc reference and then factor this into my calculations in real time! Smart!
Not so much. The value I read on the analog input pin is always 1023 (i.e. 5.00 Vdc). Well, duh. If the (bad) 5Vdc reference is used to determine the analog pin input voltage then OF COURSE it will always be 5Vdc. Chasing my tail. Sigh.
Now I could add an external 5Vdc reference chip to my proto board then feed that new reference voltage into the AREF pin and setting analogReference(EXTERNAL). But, this seems "over the top" for my purposes. P.S. I assume (!) that I do not need to set analogReference(DEFAULT) in my code.
Ask the device supplying the 5 volts! Are you relying on the USB connection? Are you powering the Arduino from a phone charger? What?
Research using the internal Arduino reference voltage. You will need to change the resistors in your voltage divider, but once you have measured the internal reference voltage, it will not change for that particular Arduino. Then you can use that voltage in your calculation.
// Here is a trick that allows you to check the
// power rail voltage against the 1.1V (+/- 10%)
// internal reference.
//
// Run the sketch, send 'R', and use a good meter
// to measure the voltage from the AREF pin to
// Ground. Put the measured reference voltage
// in millivolts (should be between 1000 and
// 1200) into the sketch as
// 'InternalReferenceMillivolts'.
//
// Upload the (now-calibrated) sketch again and
// send 'V' to read the current "+5V" voltage
// (should be around 5000 mV). Compare that to
// the voltage measured at the +5V pin. If the
// answer is not close enough, adjust the
// InternalReferenceMillivolts to get a closer
// result.
//
// Now you can use "(getVccMillivolts()/1000.0)"
// in place of "5.0" in your sketch to get analog
// readings less dependent on fluctuations in the
// "5V" line.
void setup()
{
Serial.begin(115200);
while (!Serial); // Wait for USB connection on Leonardo/Micro
delay(1000);
Serial.println("Send 'R' to put reference voltage on AREF pin for measurement.");
Serial.println("Send 'V' to measure supply voltage.");
}
void loop()
{
switch (Serial.read())
{
case 'r':
case 'R':
analogReference(INTERNAL);
delay(500);
Serial.println("Now measure the voltage at the AREF pin.");
Serial.println("Put that value in this sketch as InternalReferenceMillivolts.");
break;
case 'v':
case 'V':
analogReference(DEFAULT);
Serial.print("Power rail (millivolts): ");
Serial.println(getVccMillivolts());
break;
}
}
/*VVVVVVVVVVVVV The part below here goes into your sketch VVVVVVVVVVVV*/
// Adjust this value to your boards specific
// internal bandgap reference voltage in millivolts
const unsigned long InternalReferenceMillivolts = 1080UL;
// Returns actual value of Vcc in millivolts
int getVccMillivolts()
{
// Set the analog reference to DEFAULT (AVcc == Vcc power rail)
// REFS1 REFS0 --> 0b01 -Selects DEFAULT (AVcc) reference
// Set the analog input to channel 14: the INTERNAL bandgap reference (1.1V +/- 10%)
// MUX3 MUX2 MUX1 MUX0 --> 0b1110 -Selects channel 14, bandgap voltage, to measure
ADMUX = (0 << REFS1) | (1 << REFS0) | (0 << ADLAR) | (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0);
delay(50); // Let mux settle a little to get a more stable A/D conversion
// Start a conversion to measure the INTERNAL reference relative to the DEFAULT (Vcc) reference.
ADCSRA |= _BV( ADSC );
// Wait for it to complete
while (ADCSRA & (1 << ADSC));
// Calculate the power rail voltage (reference voltage) relative to the known voltage
return (InternalReferenceMillivolts * 1024UL) / ADC;
}
Switch to the stable internal 1.1volt reference that most Arduinos have.
Must of course change your voltage divider to <1volt as well.
Example sketch attached. The averaging might not be needed.
Leo..
/*
0 - ~17volt voltmeter
works with 3.3volt and 5volt Arduinos
uses the stable internal 1.1volt reference
10k resistor from A0 to ground, and 150k resistor from A0 to +batt
(1k8:27k or 2k2:33k are also valid 1:15 ratios)
100n capacitor from A0 to ground for stable readings
*/
unsigned int total; // holds readings
float voltage; // converted to volt
void setup() {
analogReference(INTERNAL); // use the internal ~1.1volt reference | change (INTERNAL) to (INTERNAL1V1) for a Mega
Serial.begin(9600); // ---set serial monitor to this value---
}
void loop() {
total = 0; // reset
analogRead(A0); // one unused reading to clear any ghost charge
for (int x = 0; x < 64; x++) { // 64 analogue readings for averaging
total += analogRead(A0); // add each value
}
voltage = total * 0.0002567; // convert readings to volt | ---calibrate by changing the last two or three digits---
Serial.print("The battery is ");
Serial.print(voltage);
Serial.println(" volt");
delay(1000); // use a non-blocking delay when used with other code
}