8

I am using a 5v Arduino pro mini (with regulator and power led removed).

I have been reading how to accurately measure Li-ion batteries when powering from the same battery you are trying to measure from. Using the 1.1V internal analog reference to measure a draining VCC source by using a voltage divider on it, and math to show that converted reading.

Reading the Arduino forums I came across this answer.

  long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1126400L / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println( readVcc(), DEC );
  delay(1000);
}

However, I cant see a analogRead anywhere in this sketch. (Is this a strictly internal reference?) At the bottom of the thread someone is saying that you can do this without a voltage divider on the analog pin.

Can someone confirm or explain this? I don't want to fry my Arduino...

0
7

Using the 1.1V internal analog reference to measure a draining VCC source by using a voltage divider on it

You could indeed use a voltage divider, and measure a scaled-down Vcc against the internal 1.1 V reference. This is, however, not what the code you posted is doing. It is instead measuring the internal reference against Vcc, as stated in the comment within the code. The REFS0 bit selects Vcc as the reference for the ADC. The bits MUX1...MUX3 select the internal reference as the input channel.

result = ADCL;
result |= ADCH<<8;

This idiom dates back from a time when the compiler did not know how to read 16-bit I/O registers: you had to explicitly read one byte at a time and then put these together into a 16-bit word. This time is long gone, and you can now just access the 16-bit register as ADC directly. I would even get rid of the result variable and just:

return 1126400L / ADC;  // Vcc in mV

Edit: As suggested by the busybee, here is the explanation of the factor 1126400: The ADC meaures the ratio of its input voltage to its reference voltage, and returns this ratio scaled by a factor 1024. In this case,

ADC = 1024 × (Vbg ÷ Vcc)

where Vbg is the voltage of the 1.1 V bandgap reference. Thus,

Vcc = 1024 × Vbg ÷ ADC

If we want Vcc in millivolts, we plus the Vbg voltage in millivolts in the above equation and get

Vcc (in mV) = 1024 × 1100 ÷ ADC = 1126400 ÷ ADC

6
  • You might add that 1126400 is 1024 * 1100, which is scaling the result, and why. – the busybee Oct 14 '20 at 10:37
  • @thebusybee: Thanks for the suggestion. Added. – Edgar Bonet Oct 14 '20 at 14:21
  • Outstanding answer, as usual. (voted) – Duncan C Oct 14 '20 at 15:28
  • 1
    wouldn't it be 1.1v x 1023 x 1000? the 1023 being the 10bit total value for the ADC? – Lindsay Cox Oct 14 '20 at 17:02
  • 2
    @LindsayCox: No, it's 1024, although 1023 is a very widespread error, presumably because it's the highest reading you can get. According to the datasheet, “the maximum value represents the voltage on the AREF pin minus 1 LSB.” (emphasis mine) and “ADC = Vin ⋅ 1024 ÷ Vref”. The datasheet is the only authoritative source. Note that, given the uncertainty in Vbg, it doesn't really matter. – Edgar Bonet Oct 14 '20 at 20:42
4

If anyone else is confused by this:

Will it hurt your Arduino?

  • No, this is a internal voltage reference between VCC & the 1.1v internal analog reference.

Is a voltage divider necessary?

  • Not unless you have something external of the Arduino to measure!

Is the internal reference actually 1.1v?

  • No, it seems like each pro mini I have the the 1.1v reference is slightly different. My pro minis do not have a break out for the AREF pin, so you need to figure out what the value is for each individual Arduino.

I used this,

1.15 x 1023 x 1000 = [value in quotation from second to last line in the Function]

return "1125300L" / ADC; 

I just incremented the 1.1v reference by 0.01 a couple of times until the output matched the voltage reading on my digital multi-meter. I would think with a known voltage you could do this math backwards to find what AREF actually is. I am sure that would be easier.

A big thanks to JRobert & @ EdgarBonnet for your answers!

2

Instead of calling analogRead(), this sketch performs the equivalent actions by directly manipulating the hardware registers to begin a conversion, wait until the conversion is complete, and collect the converted value.

Just reading the final value is accomplished by the statements:

result = ADCL;
result |= ADCH<<8;

All of the statements following delay(2) up to and including the above 2, taken together, do what analogRead() does.

2
  • 1
    Re “All of the statements following delay(2)”: Note that analogRead() does set ADMUX. – Edgar Bonet Oct 14 '20 at 7:36
  • 1
    Yup - I missed that. Thanks, @EdgarBonet. – JRobert Oct 14 '20 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.