In addition to being sometimes erroneous, I notice the first reading of the ADC also seems to take quite a bit longer than subsequent readings. On an atmega328p running at 8Mhz, what I get is:
0. raw relative bandgap voltage=486 Elapsed time=0uS
1. raw relative bandgap voltage=397 Elapsed time=216uS deltaT=216uS
2. raw relative bandgap voltage=360 Elapsed time=336uS deltaT=120uS
3. raw relative bandgap voltage=348 Elapsed time=456uS deltaT=120uS
4. raw relative bandgap voltage=344 Elapsed time=576uS deltaT=120uS
5. raw relative bandgap voltage=343 Elapsed time=696uS deltaT=120uS
6. raw relative bandgap voltage=343 Elapsed time=816uS deltaT=120uS
7. raw relative bandgap voltage=343 Elapsed time=936uS deltaT=120uS
8. raw relative bandgap voltage=343 Elapsed time=1056uS deltaT=120uS
9. raw relative bandgap voltage=343 Elapsed time=1176uS deltaT=120uS
from this sketch:
// number of microseconds for ADC to setttle before taking a measurement
#define ADC_SETTLE_MICROSECONDS 1000
#define NUM_ADC_SAMPLES 10
uint16_t getRelativeBandgapVoltage() {
uint16_t rawBandgapMeasurement;
// Read bandgap voltage reference (~1.1V) against AVcc
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); //need to do this for every sample or just the first?
//delayMicroseconds(ADC_SETTLE_MICROSECONDS); // Settle before taking ADC measurement
ADCSRA |= _BV(ADSC); // Convert
while (bit_is_set(ADCSRA,ADSC));
rawBandgapMeasurement = ADCL; //get the low order bits of ADC
rawBandgapMeasurement |= ADCH<<8; //combine with high order bits of ADC
return rawBandgapMeasurement;
}
void setup() {
long timeInMicroseconds[NUM_ADC_SAMPLES];
uint16_t rbgv[NUM_ADC_SAMPLES]; // relative bandgap voltage
Serial.begin(115200);
for (int i=0;i<NUM_ADC_SAMPLES;i++) {
timeInMicroseconds[i] = micros();
rbgv[i] = getRelativeBandgapVoltage();
}
for (int i=0;i<NUM_ADC_SAMPLES;i++) {
Serial.print(i);
Serial.print(F(". raw relative bandgap voltage="));
Serial.print( rbgv[i]);
Serial.print(F(" Elapsed time="));
Serial.print(timeInMicroseconds[i] - timeInMicroseconds[0]);
Serial.print(F("uS"));
if (i>0) {
Serial.print(F(" deltaT="));
Serial.print(timeInMicroseconds[i]-timeInMicroseconds[i-1]);
Serial.print(F("uS"));
}
Serial.println();
Serial.flush();
}
}
void loop() {
}
With just minimal testing so far, I've already noticed a variation of anywhere from one to seven samples needed before arriving at the final number, so Tom's idea of only waiting for the same number to repeat two times in a row sounds nicely frugal while remaining easy to implement. Not sure if there are conditions under which more than two in a row would be preferable.
The number of cycles can also be reduced just by adding some delay (ADC_SETTLE_MICROSECONDS, which is presently commented out in the above sketch). So, maybe adding some sleep time (or simply attending to other matters) in place of a pure delay would be even more energy efficient. Some of the common sketches for readVcc do sleep during the samples (to minimize noise and/or save energy), but I don't know of any that purposely sleep extra long so as to reduce the number of samples needed.
As illustrated by the above example in this post, the amount of elapsed time before converging on a particular number can definitely add-up to become non-trivial. With that in mind, I'm curious to know whether freezing temperatures and/or high ambient heat and/or low VCC and/or a weak battery might affect the convergence time, and so I'll eventually run some tests to try to characterize that. For instance, a weak battery might easily affect the settle time and/or samples required because AVcc might be falling from one sample to the next due to load. For that reason, perhaps the number of samples and/or settle time should be reported and monitored as yet another possible indicator that a battery may need to be changed. In the worse case, a weak battery might become even weaker just from chasing after an accurate measure of how weak it is! With BOD turned off, it might even run itself into the ground. So, for really weak batteries that are just barely holding on, maybe it might even make sense to halt reading the Vcc until it can be replaced...
Can anyone think of any other concerns that might play a role? If so, I can maybe run some tests for those as well and post the results afterward.