Measuring Vcc with Astar ADC using internal voltage reference

The Atmel/Microchip AT series of microprocessors has the capability of measuring processor Vcc using an internal reference voltage (colloquially known as the “secret voltmeter” on line), but the ATmega32u4 data sheet is a bit confusing on the topic.

There is a nominal 2.56V ADC reference (Vref), and also a nominal 1.1V band gap reference, but the 2.56V is derived from the 1.1V reference using an internal amplifier. The reference voltages are stable, but not calibrated, Vref can be anywhere between 2.4-2.8V, according to the data sheet.

The ADC can be internally connected to either reference, but in different ways.

The following Arduino code runs on my AStar Mini ULV and when plugged into the USB port, reads Vcc correctly as 5046 mV, after calibration against a high quality digital voltmeter. It uses the nominal 1.1V bandgap, which can actually range from about 1.0 to 1.2V.

void setup() {
  // put your setup code here, to run once:
Serial.begin(9600);
while (!Serial);
Serial.print("AStar Vcc is ");
Serial.print(readVcc());
Serial.println(" mV");

}
long readVcc() {
  // measure Vcc using the internal 1.1V reference
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #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 

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring

  // read it a second time, as recommended
 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

  long result = (high<<8) | low;

 //calibration constant, different for each processor

  result = 1125380L / result; //1.099*1024*1000
  return result; // Vcc in millivolts
}
void loop() {}
1 Like