Measurement of supply voltage using internal voltage reference (bandgap)

Hello
for some of my little projects I want to measure the battery voltage that the arduino is running from.

In the old forum I have found this interesting thread:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1294478456/8
´Coding Badly´ (well, not really :wink: and ´retrolefty´ discussed the matter there and gave interesting code examples.

I have tried the given examples on different arduinos and also tried some modifications of the code, but was not able to determine battery voltage. Whatever I did i got a reading of about 478 (with some jitter) or, when running from 11V readings around 492.

I must be doing something wrong...

Do I have to change the code for arduino version 22?

I´m using serial over USB, so maybe I´m always measuring USB voltage? I realize that I do not really know how the arduino determines which supply source to use when both USB and Vin are powered. Do I have to use a real serial line for the examples to work?

Suggestions very welcome.

Can you post exactly what code you are using (use the # icon)
I do know that in arduino 0022 accessing A/D channels over 8 are masked off and so you have to have code that gets round that.

Grumpy_Mike:
Can you post exactly what code you are using

Sure. I just did not want to post code here that others have written...
I just tried the following version (which is more or left code that ´retrolefty´ gave in the above mentioned thread)
on a duemillanove. I get 465 or 467 cV when running from USB power and 477 when i connect 10V to Vin.

int battVolts;   // made global for wider avaliblity throughout a sketch if needed, example for a low voltage alarm, etc
// value is volts X 100, 5 vdc = 500

void setup(void)
    {
     Serial.begin(38400);
     // Serial.print("volts X 100");
     // Serial.println( "\r\n\r\n" );
     // delay(100);
    }

void loop(void)
    {
     for (int i=0; i <= 3; i++) battVolts=getBandgap();  //4 readings required for best stable value?
     Serial.print("Battery Vcc centivolts =  ");
     Serial.println(battVolts);
     // Serial.print("Analog pin 0 voltage = ");
     // Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));
     // Serial.println();
     delay(1000);
    }

int getBandgap(void)
    {

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
     // For mega boards
     const long InternalReferenceVoltage = 1115L;  // Adust this value to your boards specific internal BG voltage x1000
	  // REFS1 REFS0	    --> 0 1, AVcc internal ref.
	  // MUX4 MUX3 MUX2 MUX1 MUX0  --> 11110 1.1V (VBG)
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
#else
     // For 168/328 boards
     const long InternalReferenceVoltage = 1050L;  // Adust this value to your boards specific internal BG voltage x1000
	  // REFS1 REFS0	    --> 0 1, AVcc internal ref.
	  // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
#endif
	  // Start a conversion
     ADCSRA |= _BV( ADSC );
	  // Wait for it to complete
     while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
	  // Scale the value
     int results = (((InternalReferenceVoltage * 1023L) / ADC) + 5L) / 10L;
     return results;
    }

Your results are not too far off in my opinion. What you probably need to adjust is the line of code that actually factors in the band-gap voltage for your specific processor chip as there is variation for the nominal 1.1vdc datasheet value.

When I ran it on one of my 328 based boards I had to use this value:

const long InternalReferenceVoltage = 1050L;  // Adust this value to your boards specific internal BG voltage x1000

And on my Seeeduino mega 1280 based board I had to use this value:

const long InternalReferenceVoltage = 1115L;  // Adust this value to your boards specific internal BG voltage x1000

You either have to keep 'adjusting' this value until the results agree with a reading you take on Avcc pin with a decent digital volt meter. Or if you read somewhere in the original posting thread, Coding Badly showed a method to measure the the actual band-gap voltage at the Aref pin after you select it as a reference. I just found it simpler and quicker to 'adjust' the constant value shown above until agreement. After that the voltage reading was pretty stable and would track changes between Avcc changing.

Keep in mind that this sketch does not measure battery voltage unless the board is being powered directly from batteries Via the +5vdc arduino pin (say with 3 AA cells running 4.5vdc), as any battery voltage feeding the external power connector just results in reading the output voltage of the on-board +5vdc voltage regulator.

The main advantage of this program is showing how one can improve the basic calibration accuracy of the arduion analogRead() command. As absolute accuracy can only be as good as the default reference voltage used, which is whatever voltage is on the Avcc pin. As the USB and on-board voltage regulator can both vary from exactly 5.000 volts DC, this sketch can factor in the actual variation of the boards 5vdc bus and correct for it. You commented out the line where this is performed while reading a voltage from analog pin 0:

// Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));

Hope that clears up a few points for you. If not state your questions.

Lefty

Hi retrolefty,
nice to see you here :slight_smile:

Your results are not too far off in my opinion. What you probably need to adjust is the line of
code that actually factors in the band-gap voltage for your specific processor chip as there
is variation for the nominal 1.1vdc datasheet value. [...]

My very first test was to change power supply voltages around and showed immediately
that something was fundamentally wrong, as the results did only show very little influence
of supply voltage changes. So I did not do any calibration yet, but wanted to find the bug
first :wink:

Keep in mind that this sketch does not measure battery voltage unless the board is being
powered directly from batteries Via the +5vdc arduino pin (say with 3 AA cells running 4.5vdc),
as any battery voltage feeding the external power connector just results in reading the output
voltage of the on-board +5vdc voltage regulator.

Stupid me! This must be the reason for the failing. I applied the voltage through the Vin pin.
So I actually tested onboard supply voltage regulation :wink:

Hmm, I wanted to use 4 AA NiMh cells, so that can go over 5V.
I don´t think I want to connect that to the 5V arduino pin, or maybe I could?

Time to think over my project a bit...

Hope that clears up a few points for you.

Well it did, thank you.

So let´s say I connect something like 4.5V to the +5V arduino pin and then plug USB to check the values printed over serial line. So USB supplies its 5V power. Would this work or do I have to set up another serial connection independent of USB to avoid problems?

Can the voltage externally supplied on the 5V pin slightly exceed 5V (up to something like 5.6V)?

OutOfLine:
So let´s say I connect something like 4.5V to the +5V arduino pin and then plug USB to check the values printed over serial line. So USB supplies its 5V power. Would this work or do I have to set up another serial connection independent of USB to avoid problems?

Having an external 4.5vdc wired to arduino +5vdc pin and then plugging into USB results in the 4.5v wired to the USB +5vdc bus, not a good idea or practice. Best to just unplug your 4.5vdc source when needing to upload a new sketch or run a sketch that talks to the PC via USB.

Can the voltage externally supplied on the 5V pin slightly exceed 5V (up to something like 5.6V)?

I think the datasheet absolute max rating is 6.0 and 5.5 max recommended voltage, but 5.6 probably could work. One can always wire a series diode to a 5.6v source which would drop .6vdc to the load being wired to.