It is possible to read the Vcc by an AVR itself, you do this by reading the internal reference relative to Vcc, since you "know" the internal reference, and you don't know Vcc, but you do know that a full Vcc read will give you a certain result of the ADC, it's easy to "solve for Vcc".
However as noted the internal reference isn't that accurate, indeed it can be really inaccurate. However, it is pretty stable, so what you do, is calibrate it first. You do your "read the Vcc" code, get the result that it calcualted, then you actually measure your Vcc (with a multimeter) and then you can calculate what the internal reference actually is (or, just a scaling factor) and adjust that in your code (or eeprom or however you want to store it). Once you have calibrated that once you are good-enough.
Here is some code for doing that, readVcc() on an ATMega328 (Arduino Uno, Pro-Mini, Nano), but the theory is the same for other AVR, just the registers might be different. This was ripped out of one of my programs, I think it should be stand-alone though. You can see that when I calibrated the internal "1.1v" reference on whatever I was putting this code onto, I found it was 1.081 (and some change), so quite far out, but it is relatively stable at that.
/** Read the AVR's own VCC.
*
* Note that the internalReference must be calibrated
* perform a reading, and get the result, measure the actual Vcc
*
* if the actual Vcc is lower, the internalReference needs to decrease
* internalReference = internalReference * ( readVcc / realVcc )
*
* if the actual Vcc is higher, the internalReference needs to increase
* internalReference = internalReference * ( realVcc / readVcc )
*
*
*/
float readVcc(uint8_t force)
{
uint32_t result = 0;
const uint8_t analogDiscard = 1;
const uint8_t analogSamples = 5;
const float internalReference = 1.081187434;
static float lastReading = 0;
static uint32_t lastRefVoltageUpdate = 0;
if(!force && (lastReading != 0 && (millis() - lastRefVoltageUpdate) < 10000))
{
return lastReading;
}
lastRefVoltageUpdate = millis();
// Connect the internal 1.1v to the ADC MUX
// (leave the Analog Reference as Vcc_
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
delay(2); // Wait for Vref to settle
// Discard some results to get better accuracy
for(uint8_t dummy = 0; dummy < analogDiscard; dummy++)
{
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
}
uint32_t resultSum = 0;
// Average some conversions
for(uint8_t dummy = 0; dummy < analogSamples; dummy++)
{
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
result = ADCL;
result |= ADCH<<8;
resultSum += result;
}
// Read the ADC result
// result = ADCL;
// result |= ADCH<<8;
result = resultSum / analogSamples;
// We know we measured 1.1v
// So the result 0 to 1023 is a proportion of Vcc
// 1.1 = (Result / 1023) * Vcc
// 1.1 = Vcc * (Result / 1023)
// 1.1 / (Result / 1023) = Vcc
lastReading = (internalReference / ( (float)result / 1023.0 ));
return lastReading;
}
1023 vs 1024Additional because somebody might point it out in my code...
I use "1023" for ADC operations, others use "1024", there are valid arguments for and against either.
In short, we need to remember that an ADC result doesn't represent a specific voltage, it's a voltage range (of course since we have a discrete number of steps), assuming a 5v reference for sake of demonstration, it's roughly a 5mv range for each ADC result.
So we get 1024 boxes numbered 0 - 1023, box 0 (ADC result 0) contains any readings 0v to approx 5mv, box 1023 contains any readings approx 4.995v to 5v.
Dividing the reference by 1024 (number of boxes) you always wind up calculating to the lower value, 0 = 0v, and 1023 = 4.996v, that's totally fine and correct, provided you remember that it's actually "0-0.005mv" and "4.996-5v".
If you divide by 1023 (maximum box number), you will calculate the lower value of the range at the bottom (0 = 0v) and the upper value of the range at the top (1023 = 5v)
Both approaches have merit but I prefer 1023.
You are not really introducing any error that isn't already there, just moving it from always being a negative error of up to 5mv, to being an error that can vary +/-2.5mv (indeed, see this
google sheet I made comparing 1023 to 1024 )
Of course 5mv is probably in the noise anyway on pretty much any sort of Arduino design, so this is all pretty academic.