Just comparing the stability of the 3v3 against the 1.1 and it seems good.
Three standard deviations (95% confidence interval) is about 5mV - which is 1 LSB on the ADC.
3347.37 (334737 values) is about 300 times 'better' than a 10-bit A/D.
Thats just the arithmetic mean Leo, not the number of samples.
I found a better algorithm that doesnt require storing values
variance = (sum of squares)/n - square of mean
void loop() {
sum = 0; //we use this to find the mean
sumsqs=0;
// *** take a set of measurements ***
for (int i = 0 ; i < count; i++) {
//readVcc() reads the current value of Vcc by comparison with the internal 1.1V reference
V3v3 = readV3v3(readVcc()); //read the value of V3v3 using the known value of Vcc (DEFAULT) as a reference
sum += V3v3; //aggregate the sum of readings to find the mean;
fV3v3 = (float) V3v3; //needs to be a float to find the square correctly
squareval = sq(fV3v3);
sumsqs += squareval; //aggreagate the sum of the squares to find variance
but still limited to 256 values (never liked decimal, I can count on my fingers to 1023 in binary!)
as the results go wrong with larger counts as you see here. Not sure why, maybe a number getting too big.
3V3 mean of 16 samples is 3347.44 mV; max is 3349; min is 3344
3V3: variance is 3.00 stDev is 1.73mV; stError is 0.43 mV
3V3 mean of 64 samples is 3347.45 mV; max is 3350; min is 3343
3V3: variance is 3.00 stDev is 1.73mV; stError is 0.22 mV
3V3 mean of 256 samples is 3347.66 mV; max is 3352; min is 3344
3V3: variance is 3.00 stDev is 1.73mV; stError is 0.11 mV
3V3 mean of 512 samples is 3347.92 mV; max is 3350; min is 3343
3V3: variance is 39.00 stDev is 6.24mV; stError is 0.28 mV
I've put the full code below for context
/*
Sketch to measure the 3.3V level on an arduino uno for possible use as a voltage reference.
Requires only 1 jumper from the 3v3 pin to A0
Author J Errington September 2020
*/
const int nsamples = 16; //number of samples to aggregate to reduce noise
int Vcc; //our measured value of Vcc against the internal reference
int V3v3; //our measured value of 3v3 against Vcc
const int analogPin = A0;
const long cal = 1115702L; //calibration constant for particular board - just leave as set.
//statistics
const int count = 512; //*** change this as required - number of samples to take to get the stats
float fV3v3, sum, mean, squareval, sumsqs, variance, stDev, stErr;
int max = 0, min = 5000;
//function prototypes
long readVcc(); //measure Vcc using 1.1V reference and "secret voltmeter" trick
int readV3v3(long vcc); //measure V3v3 by comparison with known value of Vcc
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
sum = 0; //we use this to find the mean
sumsqs=0;
// *** take a set of measurements ***
for (int i = 0 ; i < count; i++) {
//readVcc() reads the current value of Vcc by comparison with the internal 1.1V reference
V3v3 = readV3v3(readVcc()); //read the value of V3v3 using the known value of Vcc (DEFAULT) as a reference
sum += V3v3; //aggregate the sum of readings to find the mean;
fV3v3 = (float) V3v3; //needs to be a float to find the square correctly
squareval = sq(fV3v3);
sumsqs += squareval; //aggreagate the sum of the squares to find variance
if (V3v3 > max) max = V3v3;
if (V3v3 < min) min = V3v3;
}
// *** calculate the mean ***
mean = sum / count; // sum needs to be a float to get a correct float value for the mean.
Serial.print("3V3 mean of ");
Serial.print(count);
Serial.print(" samples is ");
Serial.print(mean);
Serial.print(" mV");
Serial.print("; max is ");
Serial.print(max);
Serial.print("; min is ");
Serial.println(min);
// *** now calculate variance and standard deviation from the mean ***
variance = sumsqs / count;
variance = variance - sq(mean);
Serial.print("3V3: variance is ");
Serial.print(variance);
Serial.print(" ");
stDev = sqrt(variance);
Serial.print(" stDev is ");
Serial.print(stDev);
stErr = stDev/sqrt(count);
Serial.print("mV; stError is ");
Serial.print(stErr);
Serial.println(" mV");
Serial.println();
}
int readV3v3(long vcc) {
long vTemp, val = 0;
int voltage;
analogReference(DEFAULT); //use Vcc to measure V3v3
val = analogRead(analogPin); //dummy read
val = 0;
for (int i = 0; i < nsamples; i++) {
val += analogRead(analogPin);
delay(3);
}
vTemp = vcc * val;
vTemp = vTemp / nsamples; //divide by nsamples to get our number representing 3.3V
voltage = vTemp >> 10; // divide by 1024
return (voltage);
}
// code for secret voltmeter adapted from a program by JRemington
long readVcc() {
long result;
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V reference
//choose the right register settings for the processor in use
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
int agg = 0; // reset aggregate ready for next reading
for (int count = 0; count < nsamples; count++) {
delay(2); // Wait for Vref to settle
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA, ADSC)); // measuring
// read it a second time
ADCSRA |= _BV(ADSC); // Start conversion
while (bit_is_set(ADCSRA, ADSC)); // measuring
uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
uint8_t high = ADCH; // unlocks both
result = (high << 8) | low;
agg = agg + result; //collect and add up nsamples readings
delay(3);
}
// we dont find the average here, it gives better resolution to multiply by the calibration constant first
result = cal * nsamples / agg; // 1115702 = 1.0896 * 1024 * 1000 - calibration constant, different for each processor
return result; // Vcc in millivolts
}