9

I am looking to possibly monitor battery power to the Arduino using its ADC. This is fairly straight forward and simple (especially if using the Arduino API); however, if the battery is powering the Arduino and is unregulated externally, won't the ADC reference voltage constantly be dropping with the battery? In other words, wouldn't the ADC value would constantly read the same value (the max value) even though the battery would actually be decreasing in voltage?

If this is the case, it would be both inefficient and pointless to measure the battery voltage.

ryeager
  • 300
  • 2
  • 10

2 Answers2

4

... won't the ADC reference voltage constantly be dropping with the battery?

Yes, which is why you either use or measure an internal bandgap reference instead.

Use the analogReference() function to select a reference appropriate for the board in use. Note that you will need to use a voltage divider to reduce the battery voltage to a value below that of the selected reference if you wish to measure it.

To measure the bandgap voltage instead (using AVCC as a reference and working "backwards") you will need to set MUX[3:0] in ADMUX to 0b1110 and then perform an ADC reading directly (set ADSC in ADCSRA and wait until it resets, then read from ADC[H:L]).

As always, see the MCU datasheet for details.

Ignacio Vazquez-Abrams
  • 17,733
  • 1
  • 28
  • 32
1

@ryeager's link to http://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/#comment-71836 has this code for reading the Arduino's battery voltage:

long readVcc() {
  // Read 1.1V reference against AVcc
  // 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

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

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

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
  return result; // Vcc in millivolts
}

The trick here is that it measures its internal reference of 1.1V using the battery voltage, and then inverts it to calculate the unknown reference voltage.

The ADMUX magic in this code can enable other interesting ADC readings, such as differential measurements, and differential ADC measurements with gain, depending on the component and datasheet.

Dave X
  • 2,350
  • 15
  • 29