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...
3 Answers 3
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
-
You might add that 1126400 is 1024 * 1100, which is scaling the result, and why.the busybee– the busybee2020年10月14日 10:37:45 +00:00Commented Oct 14, 2020 at 10:37
-
@thebusybee: Thanks for the suggestion. Added.Edgar Bonet– Edgar Bonet2020年10月14日 14:21:57 +00:00Commented Oct 14, 2020 at 14:21
-
Outstanding answer, as usual. (voted)Duncan C– Duncan C2020年10月14日 15:28:53 +00:00Commented Oct 14, 2020 at 15:28
-
1wouldn't it be 1.1v x 1023 x 1000? the 1023 being the 10bit total value for the ADC?Lindsay Cox– Lindsay Cox2020年10月14日 17:02:43 +00:00Commented Oct 14, 2020 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– Edgar Bonet2020年10月14日 20:42:50 +00:00Commented Oct 14, 2020 at 20:42
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!
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.
-
1Re "All of the statements following
delay(2)
": Note thatanalogRead()
does set ADMUX.Edgar Bonet– Edgar Bonet2020年10月14日 07:36:38 +00:00Commented Oct 14, 2020 at 7:36 -
1Yup - I missed that. Thanks, @EdgarBonet.JRobert– JRobert2020年10月14日 13:32:04 +00:00Commented Oct 14, 2020 at 13:32
Explore related questions
See similar questions with these tags.