Modified readVcc function I am currently using between Uno R3 and R4 boards below. Accuracy / reproducibility of readVcc still seems a bit better for R3 (spot on, within accuracy of ADC) than R4 (10 - 20 mV variation). I only used 9V or USB for the power supply and tested as-is, I did not do extensive testing with variable power supplies etc. to see how everything would perform in a draining-battery scenario for example.
For R3, intVREF, stored in EEPROM location 0, represents the exact value of the 1.1 V bandgap reference (after calibration with DMM).
For R4, intVREF, stored in EEPROM location 0, represents a correction factor of around 1 which is used for the (more or less stable/fixed?) offset of the (unknown) internal reference of the RA4M1 (also after calibration with DMM).
A big thanks to everyone for their valuable input!
long readVcc()
{ // function to read actual supply voltage Vcc (in mV) of Arduino - adapted to also work with new UNO R4
long result;
// setting correct bits in ADMUX register to read internal 1.1 V ref against AVcc (based on which AVR chip, e.g. Uno R3: REFS1 = 0, REFS0 = 1, MUX3:0 = 1110),
// or directly read Vcc if newer Uno R4 (RA4M1 chip)
#if defined(ARDUINO_ARCH_AVR) // for AVR boards (such as Arduino Uno R3)
#if defined(__AVR_ATmega168__) || defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) // if e.g. (very) old Arduino, or Nano, or Uno R3 is used
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // if e.g. Arduino Leonardo, or Mega is used
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#elif defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) // if using different kinds of ATtiny
ADMUX = _BV(MUX5) | _BV(MUX0);
#elif defined(__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) // if using different kinds of ATtiny
ADMUX = _BV(MUX3) | _BV(MUX2);
#else
result = 0; // return result Vcc of 0 if chip is not supported by this function
return result;
#endif
delay(2); // Wait 2 ms for Vref to settle after change
ADCSRA |= _BV(ADSC); // Write ADSC bit to 1 in "ADC Control and Status Register A" for starting single ADC conversion
while (bit_is_set(ADCSRA, ADSC)); // when ADC conversion is over, ADSC bit will go back to 0
// ADC generates 10-bit result (containing measurement of 1.1V int. ref.) in ADC data registers: ADCH (high) and ADCL (low)
result = ADCL; // if result is left adjusted & requires <=8-bit precision, ADCH is sufficient, otherwise: ADCL read first (important!), then ADCH
result |= ADCH << 8; // ADCL, then ADCH (to ensure registers hold data of same conversion), 8-place bit shift to left: obtain full-precision 10 bit result
result = intVREFbin / result; // Calculate Vcc (in mV); intVREFbin = ~1100 mV * 1024
return result; // long result (Vcc in mV) is returned as a result of readVcc function as soon as conversion is over (ADSC = 0 again)
#elif defined(ARDUINO_ARCH_RENESAS) // for RA4M1 boards (such as Arduino Uno R4)
float Vcc = analogReference(); // analogReference() reads Vcc, based on internal reference, and returns value as a float
result = Vcc * intVREF * 1000; // intVREF * 1000; // result is corrected for incorrect internal reference (fixed factor) and converted to mV
return result; // long result (Vcc in mV) is returned as a result of readVccUnoR4 function
#else
result = 0; // return result Vcc of 0 if chip is not supported by this function
return result;
#endif
}