# Secret Arduino Voltmeter – Measure Battery Voltage

A little known feature of Arduinos and many other AVR chips is the ability to measure the internal 1.1 volt reference. This feature can be exploited to improve the accuracy of the Arduino function – analogRead() when using the default analog reference. It can also be used to measure the Vcc supplied to the AVR chip, which provides a means of monitoring battery voltage without using a precious analog pin to do so.

I first learned of this technique from these articles – Making accurate ADC readings on the Arduino, and Secret Voltmeter. In this article, I have incorporated some additional improvements.

## Motivation

There are at least two reasons to measure the voltage supplied to our Arduino (Vcc). One is if our project is battery powered, we may want to monitor that voltage to measure battery levels. Also, when battery powered, Vcc is not going to be 5.0 volts, so if we wish to make analog measurements we need to either use the internal voltage reference of 1.1 volts, or an external voltage reference. Why?

A common assumption when using analogRead() is that the analog reference voltage is 5.0 volts, when in reality it may be quite different. The official Arduino documentation even leads us to this wrong assumption. The fact is the default analog reference is not 5.0 volts, but whatever the current level of Vcc is being supplied to the chip. If our power supply is not perfectly regulated or if we are running on battery power, this voltage can vary quite a bit. Here is example code illustrating the problem:

double Vcc = 5.0; // not necessarily true
int value = analogRead(0);
double volt = (value / 1023.0) * Vcc; // only correct if Vcc = 5.0 volts

In order to measure analog voltage accurately, we need an accurate voltage reference. Most AVR chips provide three possible sources – an internal 1.1 volt source (some have a 2.56 internal voltage source), an external reference source or Vcc. An external voltage reference is the most accurate, but requires extra hardware. The internal reference is stable, but has about a +/- 10% error. Vcc is completely untrustworthy in most cases. The choice of the internal reference is inexpensive and stable, but most of the time, we would like to measure a broader range, so the Vcc reference is the most practical, but potentially the least accurate. In some cases it can be completely unreliable!

## How-To

Many AVR chips including the ATmega series and many ATtiny series provide a means to measure the internal voltage reference. Why would anyone want to do so? The reason is simple – by measuring the internal reference, we can determine the value of Vcc. Here’s how:

1. First set the voltage reference to Vcc.
2. Measure the value of the internal reference.
3. Calculate the value of Vcc.

Our measured voltage is:

Vcc * (ADC-measurement) / 1023

which as we know is 1.1 volts. Solving for Vcc, we get:

Vcc = 1.1 * 1023 / ADC-measurement

Putting it altogether, here’s the code:

// 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

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
}

## Usage

### Checking Vcc or Battery Voltage

You can call this function – readVcc(), if you want to monitor your Vcc. One example would be for checking your battery charge level. You could also use it to determine if you are connected to a power source or running from batteries.

### Measuring Vcc for Analog Reference

You can also use it to get a correct value for Vcc to use with analogRead() when using the default (Vcc) voltage reference. Unless you are using a regulated supply, you can’t be sure Vcc is 5.0 volts or not. This function will provide the correct value to use. There is one caveat though…

One of the articles I cited earlier made the claim that this function could be used to improve the accuracy of the analog measurement in cases where Vcc wasn’t exactly 5.0 volts. Unfortunately, this procedure will not provide that result. Why? It is dependent on the accuracy of the internal voltage reference. The spec sheet gives a nominal value of 1.1 volts, but states that it can vary from 1.0 to 1.2 volts. That means that any measurement of Vcc could be off by as much as 10%. Such a measurement could be less accurate than our power supply for the Arduino!

### Improving Accuracy

While the large tolerance of the internal 1.1 volt reference greatly limits the accuracy of this measurement, for individual projects we can compensate for greater accuracy. To do so, simply measure your Vcc with a voltmeter and with our readVcc() function. Then, replace the constant 1125300L with a new constant:

scale_constant = internal1.1Ref * 1023 * 1000

where

internal1.1Ref = 1.1 * Vcc1 (per voltmeter) / Vcc2 (per readVcc() function)

This calibrated value will be good for the AVR chip measured only, and may be subject to temperature variation. Feel free to experiment with your own measurements.

## Conclusion

You can do a lot with this little function. You can use a stable voltage reference close to 5.0 volts without having to rely on your Vcc actually being 5.0 volts. You can measure your battery voltage or even see if you are running on battery or A/C power.

Lastly, the code provided will support all the Arduino variants, including the new Leonardo, as well as the ATtinyX4 and ATtinyX5 series chips.

Please share any corrections in the comments below.

This entry was posted in Tech and tagged , . Section: . Bookmark the permalink. Both comments and trackbacks are currently closed.

1. Posted August 5, 2012 at 1:34 pm | Permalink

hey Scott, this is incredibly helpful. I’ve been struggling with measuring voltage with the Arduino for a while, and I note that different boards and different batteries lead to really different results on all my analog readings. I have a few questions:

— is one of the lines of code essentially doing a analogReference() call? If so, is there any risk of doing that call out of sequence with the reads, or another call to that function?

— As you can tell from the previous question, I don’t follow the code you’ve posted, though I figure the variables IN CAPS are internal Arduino vars. Is there a list of those vars you point me to?

— Can you give me a bit more detail on how this would be used to correct analog references?

thanks! — wylbur.

• Posted August 13, 2012 at 11:59 pm | Permalink

Yes – the first line of actual code (3 variants) does two things. 1) It sets the analog ref to Vcc. 2) It sets the measurement to not a pin, but the internal 1.1v reference. The rest of the code simply makes the analog measurement (ADC conversion). The first line must come before the rest. It can of course be changed for subsequent measurements.

As for the constants, those are standard AVR constants (not Arduino, but more low level). You can peruse the documentation for the AVR LibC here, or review the Atmel spec sheets.

The whole trick of this code (readVcc) is to figure out the supply voltage (Vcc) by reading the internal 1.1 volt reference using Vcc as the reference. With simple math, the real Vcc can then be calculated.

• hary
Posted March 9, 2014 at 5:14 pm | Permalink

Hi.

1. So we need to “calibrate” VCC before doing an anlogRead.
But it has to be done each time, and just before we need the analogRead !
Otherwise, our calculation could be false : In the case the power supply is unstable, or we feed some load on some pin (digitalWrite) with a weak power supply just before the analogRead ! And our VCC reference could change

2. What is the constant 1125300L you’re using ? Is it the constant for your personel board ? I guess yes. And what does the L Means ?

Many thanks for your explanation. Really intersting and well done.

2. tytower
Posted August 15, 2012 at 11:15 pm | Permalink

Those Arduino boards I have measured give about 4.85 V or thereabouts .Put a multimeter on the regulator output or stick it across the 5V out pinand ground pin and use the voltage read in your adc conversion formula directly -accuracy will be improved. Look at the chips datasheet and most of them have a tollerance of +/-1 degree when measuring temperature so atm your temp measurements can be up to 2 degrees out!

3. Posted August 16, 2012 at 12:17 am | Permalink

Hihi,
Would it be possible to read the internal temperature sensor of the Arduino Leonardo ( 32U4)
in a similar way ?

• Posted August 17, 2012 at 12:32 am | Permalink

Yes. I haven’t written an article here yet that shows how, but I recently posted an Instructable with the code to do exactly that, including support for the 32U4 chip.

• retrolefty
Posted August 18, 2012 at 11:05 am | Permalink

No, reading the chip internal temperature will not be a help in ‘calibrating’ the A/D reference voltage. The chip’s internal temperature is more a reflection of how fast it is being clocked and how much current is being sunk or sourced via it’s output pins, as well as external ambient temperature.

4. goebish
Posted August 16, 2012 at 4:22 am | Permalink

Nice trick, too bad the ATtinyx5 series can’t handle this, I love these small chips 🙂

• Posted August 17, 2012 at 12:45 am | Permalink

You can – thanks to Doug (below) for pointing that out. I have amended the code in the article to support the ATtinyx5 series chips as well.

5. loopingz
Posted August 16, 2012 at 5:17 am | Permalink

One question. On a arduino board, when plugged on USB, arduino uses a 5V Vcc, but what is the regulation when I feed it with 9V?

• Posted August 17, 2012 at 12:34 am | Permalink

Vcc ought to still be 5v

• Harvey
Posted February 20, 2013 at 3:03 pm | Permalink

Tolerance depends on the Voltage regulator chip used. Typically 4.9 to 5.1V

• Posted January 16, 2016 at 5:53 pm | Permalink

if Vcc becomes 5v even when the actual Vin is 9v or 12v (in my case- SLA) battery how do I relate the charge state of the 12v battery to the ~5v Vcc reading?

• John Morris
Posted August 28, 2016 at 12:54 am | Permalink

I suspect the author is assuming people running on battery aren’t using the fancy boards with regulators. Linear regulators like almost every Arduino has are poorly suited to battery operation. Most folks just hook up three AA (5.1V ish with really fresh alkalines) batteries straight or four with a voltage dropping / reverse polarity protection diode. The four battery configuration is a little out of spec but people get away with it. An AVR chip If you don’t need to blaze away at the max CPU clock you can keep going until the cells are pretty dead.

• Akhlesh Kumar
Posted February 2, 2017 at 1:03 am | Permalink

Sir
how can I measure the value of zero to 3.3 volts on arduino board for changing the value zero to FF.
Pl. suggest me.

• retrolefty
Posted August 18, 2012 at 11:10 am | Permalink

When powered via USB then Vcc is whatever the PC’s USB voltage is and can vary from I think 4.5 to 5.5vdc and still be in specification? If powered via external power then the board’s Vcc will be whatever the on-board 5 volt regulator voltage is with a similar specification tolerance. So there is no true 5.00000 board voltage, rather it’s whatever actual value within normal tolerance specs of whatever voltage source is being used.

6. Doug
Posted August 16, 2012 at 8:03 am | Permalink

Maybe I’m missing something, but I don’t see why you can’t use this on an ATTiny25/45/85.

Can’t you just set ADMUX to

#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
ADMUX = _BV(MUX3) | _BV(MUX2) ;

and readthe bandgap reference (i.e. Vref) ?

If I’m mistaken, please educate me as to my error. I’m always interested in learning more.

• Posted August 17, 2012 at 12:35 am | Permalink

You are absolutely right. I overlooked that entry in the Atmel spec sheet for that series AVR chip. I will amend the code in the article.

• Doug
Posted August 20, 2012 at 7:12 am | Permalink

You should edit your conclusion paragraph as well, just to avoid confusion. 🙂

• Posted August 28, 2012 at 2:08 am | Permalink

Fixed – thanks for catching that.

7. Posted August 16, 2012 at 1:00 pm | Permalink

I used the 1.1V internal reference on an Arduino a few months ago. But I found out (the hard way) that the tolerance on the 1.1V reference is really poor. It’s specified to be between 1.0V and 1.2V, or roughly +/- 10%. In my case, this made the reference useless. I added an external TL431 2.5V reference, with a tolerance closer to 1%.

• Posted August 17, 2012 at 12:39 am | Permalink

As I mentioned in the article, the accuracy of the 1.1v reference is poor. It still has value however – 1) in battery operated projects, it is better than using Vcc (it is at least constant) and you can use it to monitor battery voltage, and 2) you can calibrate for the error. You can even automatically calibrate for each chip automatically as part of the programming process. If you really want an accurate voltage reference however, a separate chip is the way to go – just like you said.

• hary
Posted March 9, 2014 at 5:20 pm | Permalink

You wrote:
“You can even automatically calibrate for each chip automatically as part of the programming process”

Could you give more details. I’ve no idea you can do this !
Wouldn’t you need to know at least the real and precise value of the 1.1V reference do achive such a task ?

• Posted February 13, 2015 at 1:02 pm | Permalink

You can extend your programming setup with an external reference voltage connected to one of the pins. Then after programming you could run the program on chip for the first time, and it will calibrate the internal reference using the external one, and store the calibration results into EEPROM.

• shiva kumar
Posted March 5, 2017 at 2:11 am | Permalink

Hey! I am working on project which involves measuring the voltage of a lead-acid battery and I need to be fairly accurate. Could you please do me a favour and share the schematic of how you used TL431 to set the external reference on AREF.
Thank you

8. Scott216
Posted August 18, 2012 at 9:51 am | Permalink

This is very cool. After I call your function, should I set the reference back to default with
analogReference(DEFAULT);

Just so I’m clear, this does not use any of the analog I/O pins, right?

As noted already, the 1.1 voltage reference is not very accurate. Is there a way to measure that with a voltmeter so I can see what it is for a specific Arduino device?

• retrolefty
Posted August 18, 2012 at 11:18 am | Permalink

The internal 1.1 volt reference voltage does have a pretty wide specification, however it is pretty stable for any specific chip within the tolerance range, so the trick is to somehow calculate or measure the actual 1.1 vdc reference for your chip. If you select the internal 1.1 reference voltage in a sketch, you can actually measure it on the Aref pin of the chip. Also I have just tweeked the reference voltage in my calculation until the results of a analog read using a known external test voltage value agreed with the calculated/corrected value.

• Posted August 21, 2012 at 5:55 pm | Permalink

You can measure actual 1,1V reference voltage from Aref pin, after you have muxed internal 1,1V there. You can connect a small capacitor there, to make it more stable.

• fab
Posted October 20, 2012 at 7:59 am | Permalink

Hi,
Could you please describe how to “mux the 1.1 ref to pin AREF” ?
And do i need to switch it back after that ?

thks

• Posted August 28, 2012 at 2:22 am | Permalink

1) No need to change analog reference. This function sets it to the default = Vcc.
2) It uses none of the I/O pins.
3) To measure the internal ref voltage, you can use the excellent suggestions already made, or else calculate it from the value of Vcc given by the ReadVcc() function based on actual Vcc measured with a volt meter.

• Walt
Posted September 9, 2012 at 1:21 am | Permalink

Very interesting function. I tried it out and found a very interesting result with my setup. I fed the sketch to my Arduino uno rev3 which I use to provide 5 Vcc to a couple of prototype boards. I’d forgotten I had a ATmega328 plugged into the proto board running a sketch which lights all the pins 0 -13 as well as A0 thru A5. After lighting them one by one the sketch lights all 19 at once. When I was running the Vcc function on the factory built arduino the 5Vcc line would vary by as much as 110 millivolts when all leds were on with the bare minimum ATmega328. I thought that was a fairly large drop but I have no idea what resistance is between the USB port and the arduino.
I did measure the DC current to the proto board with all leds lit and it was 85ma.
Just goes to show that even though the Arduino is supposed to supply 500ma when connected to USB, that may not be without consequences even at much lower loads. When I plugged a 12 volt power pack into the line in jack the voltage was rock solid.

9. Posted October 16, 2012 at 2:29 am | Permalink

Occasionally, but not always, the statement “long result = (high<<8) | low;" returns a zero. Why would this be and what can I do about it? This is a very cool routine but this behaviour is driving me a little mad!

Thanks, Will

• Posted October 18, 2012 at 3:49 am | Permalink

Clarification on the above. This never happens on the first read following a restart of the controller….so I just make sure I restart the controller before I get my reading! In any case I have taken what I think I learned from this post and applied it to my project. I then documented what I did here. It should be noted that I am a software guy and this hardware stuff confuses me! I get the answer that I want (e.g. a voltage reading that matches my meter) but I am not sure I have done so in the ‘right’ manner.

• Posted October 24, 2012 at 1:22 pm | Permalink

I am not sure why that is happening. I took the analog read code straight from the Arduino library. Are both high & low zero as well (should be obvious, but you never know)? Try adding a short delay before reading the register values for high & low.

Seeing that it works the first time and then not afterward, check to make sure the ADLAR bit is not being set somehow in the ADMUX register – that could cause the data to be left shifted and possible read as zero.

A last thing to try is to check to make sure the ADIF bit is set in register ADCSRA before reading the data registers. You may want to study up the Analog to Digital Conversion in the datasheet if all this fails.

If you find the problem, please post the result. Good luck!

• Posted October 24, 2012 at 3:12 pm | Permalink

Scott:

Thanks. I have not been able to reproduce this problem on a regular basis but will try to do so. The first thing I will try is the one thing that I understand how to do…put in a delay!

Cheers,
Will

10. Martin
Posted October 23, 2012 at 11:55 am | Permalink

Thanks for posting the code with explanation! It was exactly what I was looking for. I would like to be able to monitor how much life the battery has left. I’m a total newbie when it comes to electronics and the Arduino so please excuse if my question is a little naive: After uploading the sketch to my Uno Rev 3 which was connected to my computer via USB and running from a 9V battery (I connected the battery via VIN and GND) I checked the Serial output and it reads a consistent 4855. I was assuming that it would read somewhere between 8000 and 9000 since I was connected via a new 9V battery. Since the Arduino uses 5V will it always read around 4855 until the battery is drained and dips below that value?

• Posted October 24, 2012 at 1:28 pm | Permalink

That is pretty close. What the function does is measure the voltage supplied to the ATmega chip itself. The Uno has a 5 volt regulator which provides the 4.855 volts your are reading (the actual voltage is probably closer to 5 volts, but that is a limitation on the internal ref’s accuracy). The regulator probably has around 1.2 to 2 volts of dropout, so the voltage you read will stay the same until your power source drops to less than 6 to 7 volts.

If you want to monitor your battery voltage before the regulator, you will have to use an analog pin to do so. For monitoring battery voltage, this function will only work when powering the IC directly and not using a voltage regulator. Make sense?

• Martin
Posted October 25, 2012 at 1:03 pm | Permalink

Hi Scott,

Thanks so much! Makes absolute sense.

Best regards,
Martin.

• Posted October 24, 2012 at 3:11 pm | Permalink

Martin:

I think you also need to have a voltage divider to take the 9v to under 5v for the arduino to be able to read it. In my post right above yours there is a link to what I have done that may help. I am a complete hardware novice as well……..

Cheers, Will

• Martin
Posted October 25, 2012 at 1:03 pm | Permalink

Thanks for the info. I’ll check it out the link!

11. Ace
Posted November 27, 2012 at 6:41 pm | Permalink

Hi excellent article thanks. I have a question if I may. I’m intending to run an atmega328p from a single 3.6v lion cell and I’m confused as to how I can take accurate ADC readings on my A0-A5 pins with VCC slowly dropping down. I figured I would just use readVcc then keep the battery between 4200(4.2v) and 3000(3v) but what is the reference for ADC readings the current vcc? If so how can I maintain accuracy with a constantly changing ratio? I’m reading 0-24v through a resistive divide using a ratio of 70Mohm 10Mohm giving me 0-3v out. But at 4.2v 3v is 730 ish and at 3v 3v is 1023. So how can I adjust the value to track vcc?

Kind Thanks

• Posted December 6, 2012 at 11:04 am | Permalink

You have two options: either use a voltage reference (internal or external) or use the standard Vcc reference (as you are doing) and scale it using the readVcc function. Here’s how to do the latter.

long millivolts = readVcc();
long measured = analogRead(A0);
long voltage = millivolts * measured / 1023; // answer is in millivolts

You don’t have to call readVcc everytime – just often enough to track the battery voltage.

12. atflaryon
Posted December 26, 2012 at 1:00 pm | Permalink

is this code useful for atmega168? thanks

• Posted December 26, 2012 at 6:19 pm | Permalink

Yes – most of not all ATmega chips support this feature.

13. evolion
Posted January 21, 2013 at 4:22 am | Permalink

I don’t quite understand all the info presented here but perhaps I could have some feedback. Part of the project I have just started on is to measure sensor input to my vehicle’s computer(PCM). Power to the sensor is +5Vn. If I were to use the same +5Vn to power the arduino, would that automatically act as a reference for a fairly accurate reading of the sensor output? or should it also be input into the AREF to obtain an accuracy of +/- .01V?(is this accuracy even possible?) I would assume that as long as I read the same value that would appear in the PCM, it wouldn’t matter that power to the sensor is not exactly 5V.
Thanx

• Posted January 24, 2013 at 2:47 pm | Permalink

That is a tough question to answer without knowing more details. Is the 5V source accurate? (I doubt it is +/- 0.01 volts). Is the PCM relying on the +5v source?

As indicated by this article, the default analogRead uses the AVR’s Vcc for a voltage reference. The technique given to measure Vcc is somewhat crude (about 10% accuracy). If you need an absolute accurate voltage reading (such as the 0.01 volts cited), then you will need to use a precision voltage reference chip and feed it to AREF. Even then, that kind of accuracy might be iffy at best. If you meant +/- 0.1 volts, then the precision ref will work.

• evolion
Posted January 25, 2013 at 3:54 am | Permalink

The 5V source would be from the PCM(powertrain control module, aka ECU) and it probably isn’t an accurate value, I’ve measured it before but don’t remember what it was ~4.8 to 4.9 maybe.

The PCM supplies 5V to most all of the sensors which are predominately variable resistive loads that return a voltage less 5V. I’m not sure what resolution the PCM reads.

I want to begin only measuring the post cat O2 sensor which returns oscillating signals from around .3V to .7V up to 3 times a second where each high and low value may possibly vary +/-.2V. Eventually, I would like to interrupt the signal from the O2 to PCM with the arduino, in real time, feeding the PCM a predetermined, or augmented through calculation, signal for every O2 pulse. Essentially fooling the PCM that the engine is running normally as I make changes to the system that would otherwise be automatically defeated by the PCM’s readjustments.

Do you think that if I use the sensor input voltage for AREF, I will still have only 10% accuracy?
Would you have suggestions for a particular reference chip to use if I need to use one? I didn’t figure it would be a difficult measurement seeing as how my \$5 voltmeter reads to .01V, but I’m a novice and the arduino experience is very new to me.

I appreciate the input.

• Posted January 28, 2013 at 4:23 pm | Permalink

Since your sensor input voltage appears to be only 5% accurate, the same will be true of any measurement made with it for a reference. On a related note, who knows what effect the sensor input voltage has on the sensor accuracy? I wonder if you really need absolute accuracy or relative accuracy. If the sensor reading vary with their input voltage, absolute accuracy may not do you any good.

Therefore, I suggest just using the sensor input voltage for AREF and do some testing. I would also monitor this voltage itself as well. Does it change with temperature for example? If you find you need a precision voltage reference, the SC431 (available from Digikey) will serve you well. It works just like a 2.5 volt Zener diode, except it is 1% accurate. Just hook a 1 to 10K resistor to Vcc and then your volt ref and input that to your AREF pin.

• Harvey
Posted February 20, 2013 at 3:16 pm | Permalink

If the Arduino supply Voltage is made to track the PCM 5V all measurements will be ratiometric and the errors drop out.

14. Jorge
Posted February 28, 2013 at 9:09 pm | Permalink

Hi,

This is all very helpful, but what if I want to measure the voltage across another power line? I am working on a project in which I need to continuously measure the voltage of the line powering the project. I am using regulators to regulate, but I need a way to read and store that reading for data analysis after the project. What is the best way I can do that? I’ve been looking around but I can’t find anything.

Thanks,

• Posted March 6, 2013 at 11:54 pm | Permalink

You’ll just need to use an analog pin to make the measurement. Use two resistors to make a voltage divider that drops your maximum line voltage just under 5 volts (or 1.1 volts if using the internal reference) and measure your voltage at that point.

15. Marc
Posted March 12, 2013 at 4:14 pm | Permalink

I would like to change the reference on the Arduino DUO board. The analogReference(INTERNAL1V1); code returns an error.
Do you think that reference could be changed with lower level code?

• Posted April 24, 2013 at 12:39 pm | Permalink

I haven’t played with the DUO yet. Look in the spec sheet for the appropriate analog reference bit to use.

• Fx
Posted July 22, 2013 at 9:32 am | Permalink

Hi Marc, did you manage to change de reference on an Arduino due board?

I’m having the same issue and don’t know how to get a reference voltage.

Thanks,

16. britesc
Posted April 6, 2013 at 12:21 pm | Permalink

Hi,
I have a large number of 2VDC 1200Ah Lead Acid Batteries that I use for off-grid power.
I need to be able to monitor the voltage, amperage and temperature of each battery. The “device” should be self powering, so I have settled on the ATTINY43U as this operates from 0.8 to 5.5 VDC and is well within the operable battery voltage range of 1.57 to 2.85 etc.
Your routine should I believe, give me the first part of the problem, namely the battery voltage.
Is that correct?
Can the ATTINY43U also provide the amperage?
I also read that it can return the temperature of the chip for reference value.
I intend to use a copper lug with an LM35 inside sealed with hot glue and feed that output back to the ATTINY43U but that requires 5VDC? So somehow I need to increase the voltage from the battery to deliver that??

Could you or any of your stalwarts advise me as to whether this is feasible and help me with a little bit of code / hardware advice to get me going, please?
Thanks and kind regards,
jB

• Posted April 24, 2013 at 12:38 pm | Permalink

Yes, you can measure the voltage using this technique. You can also measure the ambient temperature with a similar technique. The latter is not real precise, but is probably sufficient with your application (you will have to calibrate the chip however). Alternatively, you can use a temperature chip as you mentioned, but will need a boost converter to get 5 volts.

For current, that is not a property of each cell, but the entire battery of cells. You will want to use another circuit for that. You will need to measure it using an analog pin and a current sense resistor.

17. paul
Posted May 5, 2013 at 10:37 am | Permalink

Hi scott
I am using atmega2560 (arduino)
I had added your code for reading chip Vcc using the internal 1.1 vref and its working great
The problem is that I need to read some value from other analog pin in my program also
And iam doing it like this:
#define VPowerLeg 8
Serial1.print(“Voltage:”);
When I add this to my code your function returns -1
If I use “analog read” function or readVcc() separately they work fine, but not together

• paul
Posted May 8, 2013 at 8:23 am | Permalink

the solution for this issue is to add:
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
ADCSRB &= ~_BV(MUX5); // Without this the function always returns -1 on the ATmega2560

Posted May 13, 2013 at 1:54 am | Permalink

The correct value is 1024 not 1023.

19. flegmatoid
Posted May 14, 2013 at 2:05 pm | Permalink

it took me a while to figure-out the problem, but on MEGA 2560, immediately after analogRead(A8), ADCL started returning zero. So every attempt to read from A8-A16 on Arduino MEGA will damage the functionality of readVcc().
I’ve resolved the problem by adding:
just before
delay(2);

yet, I cannot explain why this has such an impact.

20. Mark
Posted May 26, 2013 at 9:59 am | Permalink

Question, adjusting the constant (I had to change from 1126400 to 1172504.467) is only a one time action? is this a deviation in the hardware or to I need to recheck it when using another source voltage?

I’ve tested on USB and external power supply. I’m gonna bring my board to a battery with a voltage that might swing a bit.

If I’ve found the right constant with my board can I then use this to measure the battery voltage (is also the powersupply for my duino) with a voltage devider bringing it <1.1 Volts to A0 against internal reference?

regards,

Mark

• Posted May 30, 2013 at 11:08 pm | Permalink

Mark,

You might want to reread the Improving Accuracy section. The internal reference voltage of 1.1 volts can vary by as much as 10%. Therefore, the constant value you discovered will be valid only for that chip. It may also vary over temperature as well.

As far as measuring your battery voltage, you won’t even need any hardware or the A0 pin. Just use the Vcc function. For battery monitoring, the 10% error is probably not a big deal, even without calibration.

21. Scot
Posted June 10, 2013 at 6:11 pm | Permalink

From your article, it sounds like the 1.1V reference, while perhaps not accurate to 1.1V, is precise to whatever it’s set to. So maybe my unit reference is 1.0V but it will always be 1.0V, (ignoring temperature variation). Is that correct or might the reference voltage change over time?

Also, there’s no convenient way to set different analog pins to different reference voltages is there? I’m using a Mega so I have 16 Analog pins separated into two banks.

• Posted June 16, 2013 at 1:25 am | Permalink

Aside from temperature variation, the other cause of variation in the internal 1.1v reference is from chip to chip. It should be very stable over time. It is based on the semiconductor band gap voltage of that particular chip. It would be pretty easy to calibrate for a given device (even automatically is possible). Once calibrated, it would be stable over time.

For different pins, they would all use the same voltage reference that you choose at that time. You can easily change your reference before making a measurement, which can changed then for different pins. You usually have 3 choices – internal 1.1v, external (whatever you choose), and Vcc). So you can choose one reference before measuring one pin, and then change it for another.

22. Tom
Posted August 15, 2013 at 7:16 pm | Permalink

Scott,
Thanks for a terrific solution to measuring the supply voltage on the arduino.
I am using arduino mega 2560
On May 8, 2013, Paul posted a code change he says is needed on the mega 2560
He appears to add
ADCSRB &= ~_BV(MUX5); // Without this the function always returns -1 on the ATmega2560
after the ADMUX = line for the mega2560

Question: can you confirm it is needed? If yes will you update the code listing for your readVcc function?

On May 14, 2013, flegmatoid posted another code change related to using analogread(pin) before calling readVcc, he added ADCSRB = 0 just above the delay(2) statement.
Question: is this need also on the Mega2560

I wonder if they both are needed only for the Mega2560 because of its additional analog input pins.

Please email me if you can.
Thanks

• Buzzy
Posted January 23, 2017 at 5:24 pm | Permalink

Adding this solved my problem with Mega2560
#if defined(__AVR_ATmega2560__)
ADCSRB &= ~_BV(MUX5); // Without this the function always returns -1 on the ATmega2560
#endif

23. Saqib
Posted August 27, 2013 at 7:24 am | Permalink

I am using a standalone atmega8 with arduino bootloader. Kindly suggest me the changes to be made in readVcc function.

24. nickdigger
Posted September 12, 2013 at 5:03 pm | Permalink

Just tested this on my 168. The adc value is 118, which according to the formula 1.1*1023*1000 / adc = 9.536v, quite different from the 4.92 I measured with a voltmeter. I also muxed the 1.1v to the Aref pin, and measured it to be 1.05v.

Not impressed with the 9.536 result, I checked the datasheet, which says the 1.1v is based on an internal Vcc reference of 2.7v.

A revised formula, (1.05 * 1023 * 1000 / adc) * 2.7/5.0 gives a result of 4.915 — pretty darn close to my 4.92.

Hope this helps.

• nickdigger
Posted September 14, 2013 at 3:46 am | Permalink

ahhh, whoops. I noticed a potential bug in my code, where i failed to put parentheses around a #define, which could have affected my ADC settings.

After putting in the parentheses, my voltage was reading about 2.6v. Removing the 2.7/5.0 fudge factor above, got my results back to 4.92.

In the words of the esteemed Rosanne Rosannadanna, “Never mind…”

25. Posted October 7, 2013 at 1:30 am | Permalink

Hi Scott; I was looking for information on measuring voltages with the Arduino and found your article. However I didnt find ANY that allowed me to measure to the precision the arduino allows (0.25%). So I wrote my own pages to explain how I managed this. The trick is to use a voltage reference IC (LM4040 – 0.5\$) as shown here http://www.skillbank.co.uk/arduino/measure.htm
It also shows how you can measure negative voltages.

26. ems
Posted January 14, 2014 at 7:13 am | Permalink

Hi,

I wish I’d found this earlier! Spent a few hours setting up some maths and a zener diode into one port to achieve the same thing. Should have read the manual, but still, learnt something.

However (lucky me for finding this out,not) I found (at least on a sparkfun pro micro clone) that if you have something like a global var that calls readVcc() then it will appear to brick the device.

The reason this happens is because the compiler sets the global vars before anything, including the timers etc that are used for delay(). So calling delay() at such an early stage results in an infinite loop (possibly). Calling readVcc() in setup() is fine.

This may be a problem on all Arduino’s and it’s many clones, however it’s more noticeable on the Leonardo based Pro micro’s because if your code hangs at such an early stage, your main PC cannot reset the device for programming, saying it can’t find the device.

It appears bricked, but a double ‘click’ on reset—>gnd just after clicking the upload button will allow programming.

• ems
Posted January 14, 2014 at 7:18 am | Permalink

Cannot edit, so feel free to read above and substitute it’s/micro’s for its/micros etc

• AndrewK
Posted January 24, 2014 at 10:02 am | Permalink

Swap out the delay() call for a empty loop? That, or move the initialization into the setup() function.

• Gary Chapman
Posted March 11, 2016 at 9:42 am | Permalink

The Zener is probably your best bet TBH.

On a 5v chip I’d stick to using a simple LM4040 4.096v external ref (+/- 0.2%). Then the only remaining accuracy issue is the ADC’s 0.25% typical variance.

The internal reference is bloody awful – even if you calculate a correction factor for each individual chip… I see about +/- 6% variance above and ADC error. Calibrated per chip I still see between +/- 2.5% and +/- 3.2% depending on the Chip, Vcc and Temperature.

27. Mario
Posted January 27, 2014 at 8:36 pm | Permalink

love this article. Really explained the whole mess around accuracy and whys well!

28. Ross
Posted June 7, 2014 at 3:49 pm | Permalink

Hi,

I am using this for a very battery sensitive project that needs to save as much power as possible. What is the leakage current from having VCC set up to measure in this way…do you think it would be a problem long term in regard to battery life to measure it in such a way?

29. PeterDollar
Posted July 24, 2014 at 1:46 am | Permalink

Very interesting and helpful.

The ATtinyx5 and Arduino feature a 10-bit successive approximation Analog to Digital Converter (ADC). This means there are exactly 1024 possible values for any analogRead() ranging from 0 to 1023 ‘counts’.

This means the constant 1023 used in the readVcc function should be replaced with 1024.

• James Gallagher
Posted March 10, 2017 at 1:49 pm | Permalink

I think 1023 is correct.

Imagine it was 2 bits representing values 0-3, then 0 = ov, 1=0.333*vcc, 2=0.666*vcc and 3 = vcc, so you would divide by 3, similarly for 3 bits you divide by 7, for 4 bits you divide by 15, …, for 10 bits you divide by 1023.

btw Article is great and enabled me to get accurate readings with analog temp sensors TMP36 and LM35 (I did have to measure AREF with a multimeter and use a correction factoe to get best results, internal ref varies for each board as you say in the article, but it is stable for each individual board)

30. ArduinoLover
Posted September 24, 2014 at 1:14 pm | Permalink

Great! Quick question – If I called the function while plugged to the USB, is that making a common ground and therefore short circuiting it to ground? Thank you very much.

31. Posted October 2, 2014 at 2:13 pm | Permalink

It’s a pity you don’t have a donate button! I’d certainly donate to this
excellent blog! I suppose for now i’ll settle for
I look forward to fresh updates and will share this website with
my Facebook group. Talk soon!

32. Posted October 4, 2014 at 4:39 am | Permalink

Greeat info. Lucky mе I discovered your website ƅy accident (stumbleupon).
ӏ’ve book-marked іt for later!

Feel free tto visit mү site … bola online

33. Posted October 6, 2014 at 11:43 pm | Permalink

What’s up to every one, it’s truly a good for me to pay a visit this web site, it includes
precious Information.

34. Posted October 14, 2014 at 7:00 am | Permalink

ok I get the concept on how to measure internal voltage but how do I measure external voltages up to at least 24 vdc

35. Posted November 11, 2014 at 4:31 pm | Permalink

Yes! Finally something about BeCM Unlocking.

Allso visit my web blog: Range Rover BeCM – orangekit9840.Soup.io

36. insanul
Posted November 13, 2014 at 1:15 am | Permalink

I have a servo load, optocoupler, LCD, and Arduino. how to monitor a battery that has been given the burden?

37. insanul
Posted November 13, 2014 at 1:18 am | Permalink

I have a load that is servo, optocoupler, LCD, and Arduino. how to monitor a battery that has been given the burden?

38. michael
Posted November 30, 2014 at 12:51 pm | Permalink

Hi Scott! Thanks for sharing such a nice code.
I was wondering.. is it possible to use the internal reference that you are using to measure also the voltage Vin? I would like to distinguish in the code automatically if I’m powering the arduino with USB or with an external supply and to know how much voltage I have.

39. Posted December 1, 2014 at 11:57 pm | Permalink

If you would like to get much from this paragraph then you have to apply uch methods too your won website.

Feel free to vksit my web blog Pressure in the workplace
(profuselectern632.kazeo.com)

40. salvatore
Posted December 23, 2014 at 10:35 am | Permalink

Hi, great work Scott!

i am trying to use your code on my arduino micro but i have a little problem.

when i use the usb power, it measures correctly the voltage but when i use an external power source with variable voltage connecting it on the GND and Vin, i can not get the right value, also at 12v i can get max 5000mV.

I am doing something wrong?

thx

• Posted January 19, 2015 at 12:02 pm | Permalink

salvatore, your supplied 12V are converted to 5V before delivered to the arduino itself. This done by a step-down-converter (5-12V -> 5V) as the common arduinos do run at 5V or 3.3V only. Applying 12V would damage the controller.
As 5V are delivered to your arduino microcontroller, it measures 5V. regardless of what voltage you supplied to board as a whole.

regards,
christian

41. Posted February 5, 2015 at 12:20 am | Permalink

Great article, totally what I needed.

Feel frse to visit mmy page letting agents Radlett

42. jino p
Posted February 19, 2015 at 8:33 am | Permalink

I want to use the following code to measure the temperature using thermocouple . I have two thermocouple boards each have 8 to 1 MUX. I used your code to find the VCC . But I got 5046 mv once then it reads only -1. I hope it may because I use digital keys to clock the MUX.
Q1
Why does it happen ? I could I find out Vcc accurately in each step ?
Q2
How to measure the internal ref voltage,VCC by volt meter
Q3
should I use analogReference(DEFAULT);

int analogin1 = A15; // Thermocouple board 1
int analogin2 = A8; // Thermocouple board 2

int value_a[8];//reads inputs from mux 2
int value_b[8];//reads inputs from mux 2

double voltage_b[8];//input voltages from mux1
double voltage_a[8];//input voltages from mux1

double temp_b[8];//temparature readings from mux1
double temp_a[8];//temparature readings from mux1

double Vcc;
int row = 0;

int a0=24;
int a1=28; // MUX 1
int a2=32;
int b0=34;
int b1=38; // MUX 2
int b2=42;

//Functions which calculates the temparature corresponding to the thermocouple

float temp(int i, float voltage)
{
if(i==0) return (261.9 * voltage – 8.893);
else if(i==1) return (270.3 * voltage – 11.96);
else if(i==2) return (259.8 * voltage – 8.888);
else if(i==3) return (261.7 * voltage – 8.271);
else if(i==4) return (280.2 * voltage – 13.33);
else if(i==5) return (267.3 * voltage – 9.606);
else if(i==6) return (273.8 * voltage – 10.87);
else if(i==7) return (258.6 * voltage – 11.05);
else if(i==8) return (258.6 * voltage – 11.05);
else if(i==9) return (258.6 * voltage – 11.05);
else if(i==10) return (256.7 * voltage – 10.50);
else if(i==11) return (257.8 * voltage – 10.52);
else if(i==12) return (0.439 * voltage + 7.432);
else if(i==13) return (0.441 * voltage + 7.313);
else if(i==14) return (0.439 * voltage + 7.536);
else if(i==15) return (0.522 * voltage + 3.005);
}
// To read Vcc from internal reference 1.1V
// 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) ;
#else
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif

delay(2); // Wait for Vref to settle

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
}

void setup()
{
pinMode(a0, OUTPUT);
pinMode(a1, OUTPUT);
pinMode(a2, OUTPUT);

pinMode(b0, OUTPUT);
pinMode(b1, OUTPUT);
pinMode(b2, OUTPUT);

// I had doubt whether to use this line below
//analogReference(DEFAULT);

Serial.begin(9600);

Serial.println("CLEARDATA");// sketch to write to excel
Serial.println("LABEL,Time,Tc1,Tc2,Tc3,Tc4,Tc5,Tc6,Tc7,Tc8,Tc9,Tc10,Tc11,Tc12,Tc13,Tc14,Tc15,Tc16");
}

void loop()
{
Serial.println( readVcc(), DEC ); // Fn call 1

int i=0;
float dummy;
for (i=0;i<11;i++)
{
delay(10);
}
//i=0;

for(i=0;i>0)&1)==1)
{
digitalWrite(a0,HIGH);
digitalWrite(b0,HIGH);
}
else
{
digitalWrite(a0,LOW);
digitalWrite(b0,LOW);
}
// code for a1 and b1
if(((i>>1)&1)==1)
{
digitalWrite(a1,HIGH);
digitalWrite(b1,HIGH);
}
else
{
digitalWrite(a1,LOW);
digitalWrite(b1,LOW);
}
// code for a2 and b2
if(((i>>2)&1)==1)
{
digitalWrite(a2,HIGH);
digitalWrite(b2,HIGH);
}
else
{
digitalWrite(a2,LOW);
digitalWrite(b2,LOW);
}

voltage_a[i]=(value_a[i] / 1023.0)* Vcc ;
voltage_b[i]=(value_b[i] / 1023.0)* Vcc ;
temp_a[i]=temp(i,voltage_a[i]);
temp_b[i]=temp(i+8,voltage_b[i]);
Serial.println( readVcc(), DEC );// Fn call 2
delay(500);
}

{
Serial.println( readVcc(), DEC );// Fn call 3
// Serial.print(“DATA,TIME,”); Serial.print(temp_a[0],DEC); Serial.print(“,”); Serial.print(temp_a[1],DEC); Serial.print(“,”);
// Serial.print(temp_a[2],DEC); Serial.print(“,”); Serial.print(temp_a[3],DEC); Serial.print(“,”);
//Serial.print(temp_a[4],DEC); Serial.print(“,”); Serial.print(temp_a[5],DEC); Serial.print(“,”);
//Serial.print(temp_a[6],DEC); Serial.print(“,”); Serial.print(temp_a[7],DEC); Serial.print(“,”);
//Serial.print(temp_b[0],DEC); Serial.print(“,”); Serial.print(temp_b[1],DEC); Serial.print(“,”);
//Serial.print(temp_b[2],DEC); Serial.print(“,”); Serial.print(temp_b[3],DEC); Serial.print(“,”);
//Serial.print(temp_b[4],DEC); Serial.print(“,”); Serial.print(temp_b[5],DEC); Serial.print(“,”);
//Serial.print(temp_b[6],DEC); Serial.print(“,”); Serial.println(temp_b[7],DEC);

if (row > 3600)
{
row=0;
Serial.println(“ROW,SET,2”);
}

row++;

delay(500);
Serial.flush();
}}

serial moniter O/P

CLEARDATA
LABEL,Time,Tc1,Tc2,Tc3,Tc4,Tc5,Tc6,Tc7,Tc8,Tc9,Tc10,Tc11,Tc12,Tc13,Tc14,Tc15,Tc16
5046
-1
-1
-1
-1…………………………..

43. Splinter
Posted February 24, 2015 at 2:47 pm | Permalink

Thanks for this, exactly what I needed!!

44. pete
Posted June 2, 2015 at 5:02 pm | Permalink

Hi i was just wondering if you would be kind enough to help me out on some arduino code im having an issue with. My code basically displays four temperatures on a lcd screen. the code works perfectly on a Pro Micro ATmega32U4 5V 16MHz. but i now want to use the code on a AMEGA328P AU.(bread boarded with all pins broken out) Problem is now my temperatures are reading incorrectly. I believe this is due to the fluctuations in voltage 5v vcc being supplied to the chip and sensors.

if i give a true perfect solid 5v my temperature readings are accurate.soon as voltage changes for example 4.95v the temp reading changes and temperature reading is incorrect by 2 Celsius

to overcome this you simply connect the AREF pin to the 5v vcc this should then correct the temperature readings. no matter that the vcc in is.i have done this and does nothing so im wondering if my code has an error somewhere making this not work.

i also have to connect 5v vcc connected to A5 to stop my temperature readings going completely wrong -120c soon as i connect this pin to 5v i get correct room temperature reading of 19c but if 5v vcc fluctuates so does the temp reading.

.

would you be able to try and help me by having a look at my code and doing any edits you may feel will fix this as im out of ideas now

i did try using a 5v voltage regulator on the vcc in but that does not give a perfect constant 5v so this is not an option unfortunately 🙁

/*

unRaidLCD.ino

CODE UPDATED 22/10/2014

A little program to display uptime and 4 temperature sensor value

The Circuit:

LCD n SymbolFunction connection

Pin

1VssDisplay power ground & 10k Potentiometer Ground (pin 1)ground (either black wire)

2VddDisplay power +5V & 10k Potentiometer pin 1) +5v (red wire)

3VoContrast Adjust. (Wiper (pin 2) on 10k Potentiometer pin 2)

4RSRegister select Arduino digital pin 7

5R/WData read/write ground (either black wire)

6EEnable strobe Arduino digital pin 6

7DB0Data Bus 0 N/C

8DB1Data Bus 1 N/C

9DB2Data Bus 2 N/C

10DB3Data Bus 3 N/C

11DB4Data Bus 4 Arduino digital pin 5

12DB5Data Bus 5 Arduino digital pin 4

13DB6Data Bus 6 Arduino digital pin 3

14DB7Data Bus 7 Arduino digital pin 2

15ALED backlight +5v+5v (red wire)

16KLED backlight groundground (either black wire)

Temperature Sensors: (Centre pin connection)

CPU Arduino pin A0

SYS Arduino pin A1

Parity Arduino pin A2

Drive Arduino pin A3

+5v connected to A5 to act as voltage reference for analog calculations

D8 Buzzer 2secs on 1 sec off

D10 LED normally on fade in and out if alarm

D11 5v Input to activate Battery Backup warning message 5v input to activate message on screen connect Ground to a 1k resistor to D11 keep pin low

This stops the message activating on screen (keeps D11 low and when 5v to D11 make the pin high which activates message

*/

// include the required libraries

#include

#include

// Defined constant values

#define LCD_D0 5 // LCD Data pin 0

#define LCD_D1 4 // LCD Data pin 1

#define LCD_D2 3 // LCD Data pin 2

#define LCD_D3 2 // LCD Data pin 3

#define LCD_EN 6 // PWM pin for LCD Enable

#define LCD_RS 7 // LCD RS pin

#define VREF 27 // Voltage ref analog input D9

#define BUZZPIN 8 // pin for buzzer

#define LEDPIN 10 // pin for led

#define BACKUP 11 // pin for Battery Backup indicator 5v input to activate message on screen connect 1k resistor from ground to pin D11 to keep pin low

#define DISPLAY_WIDTH 20 // width of display

#define DISPLAY_HEIGHT 4 // height of display

#define BUFSIZ 16 // Maximum size of smoothing buffer

#define BUFUSE 16 // Number to use for smoothing (must be < BUFSIZ)

#define NUMADC 4 // Number of values to display

#define FLIP_TIME 4 // nr. secs to wait before screen change

#define flipdelay 5000 // 5 second delay needed to change display_flip variable

#define BBdelay 6000 // 2 second delay interval for backup battery screen

// initialize the lcd library with the numbers of the interface pins

LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D0,LCD_D1,LCD_D2,LCD_D3);

static float voltage_ref=5.00; // Voltage reference value (will be overwritten by input reading)

int brightness=0; // Current LED brightness

int fadeAmount = 5; // how many points to fade the LED by

char str[22];

boolean shown = false, scrToggle = false, StartReadings = false;

static int display_flip=0;

unsigned long timer = 0, BB_timer = 0; // X

// ============================================================================================================

// Structure to define the sensors, text, text positions, value positions and smoothing buffer

//

// pin Analog input to read (A0 or A1 etc.)

// txt up to TXTSIZ characters to display

// type c=temperature, v-millivolts otherwise %

// txtx,txty where to start the text

// fmt format string for display of value

// valx,valy where to display the value

// ck 1 if alarm checks required, 2 if value to be output on alarm

// lo low limt alarm value

// hi High limit alarm value

// alarm result of alarm test (0 no alarm, 1=below low limit, 2=above high limit)

// lotxt text for alarm low (make this an even number of characters)

// hitxt text for alarm high (make this an even number of characters)

// bufptr internal buffer pointer for smoothing

// buffer internal buffer

//

// ============================================================================================================

struct {

int pin;

char *txt;

char type;

uint8_t txtx,txty;

char *fmt;

uint8_t valx,valy;

int ck,lo,hi;

uint8_t alarm;

char *lotxt,*hitxt;

uint8_t bufptr;

int buffer[BUFSIZ];

}

{

A0, " CPU ", 'c', 0,2, "%3dc", 4,2, 1, 0, 105, 0, "CPU Temp Lo","CPU Temp Hi", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 105 = 65C OR 90 = 55C

A1, " SYS ", 'c', 10,2, "%3dc", 15,2, 1, 0, 78, 0, "SYS Temp Lo","SYS Temp Hi", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, //78 = 50C

A2, "PARITY ", 'c', 0,3, "%3dc", 6,3, 1, 0, 94, 0, "PARITY Temp Lo","PARITY Temp Hi", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 94 = 60C

A3, " DRIVE ", 'c', 10,3, "%3dc", 16,3, 1, 0, 94, 0, "DRIVE Temp Lo","DRIVE Temp Hi", 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 // 94 = 60C

};

// ============================================================================================================

// Setup routine only runs once at power up

// ============================================================================================================

void setup()

{

pinMode(BUZZPIN, OUTPUT); // set a pin for buzzer output

pinMode(LEDPIN, OUTPUT); // set a pin for led output

analogWrite(LEDPIN,255); // Switch the LED on

//Serial.begin(9600); // Start the serial connection with the computer to view the result open the serial monitor

lcd.begin(DISPLAY_WIDTH, DISPLAY_HEIGHT); // set up the LCD's number of columns and rows:

lcd.clear(); // Clear the LCD.

lcd.setCursor((DISPLAY_WIDTH-7)/2,0);

lcd.print(" Admin"); // Print out a title while we wait for the analog smoothing to initialise

lcd.setCursor(0,1);

lcd.print(" Peter Birkett"); //

setTime(0,0,0,1,1,1970); // set the time to 1/1/1970 00:00:00 – where the time functions start from

pinMode(VREF,INPUT); // Make sure the voltage reference is an input

pinMode(BACKUP,INPUT); // Set the Battery Backup Pin as an Input

delay(100); // Let the input switching settle

getVref(); // read the voltage reference value

for(uint8_t i=0; i= flipdelay)

{

timer = millis();

getVref(); // read the voltage reference value

display_flip=(display_flip+1)&1; // either 1 or zero each time through this routine

if (display_flip==1)

{

lcd.setCursor(0,0);

lcd.print(” unRaid Server “);

lcd.setCursor(0,1);

lcd.print(” 192.168.0.7 “);

}

else

{

lcd.setCursor(0,0);

lcd.print(” Server Uptime “);

}

// display the value titles

if(!shown)

{

for (uint8_t i=0; i<NUMADC; i++)

{

lcd.setCursor(sensors[i].txtx,sensors[i].txty);

lcd.print(sensors[i].txt);

}

shown = true;

}

// Now display the values – loop times

if (display_flip==0)

{

int d=elapsedDays(now()); // get the number of days since we powered up

sprintf(str,"%4dd %2dh %2dm %2ds",d,hour(),minute(),second()); // get the number of days since we powered up

//sprintf(str,"%4dd %2dh %2dm %2ds",0,7,17,19); // Test without sensors

lcd.setCursor(0,1);

lcd.print(str); // and display it

}

}

{

for (uint8_t i=0; i<NUMADC; i++)

{

lcd.setCursor(sensors[i].valx,sensors[i].valy);

sprintf(str,sensors[i].fmt,getVal(i));

lcd.print(str);

}

}

//

// Now check if any alarms have been raised

//

uint8_t k=1,l=0;

while (k==1)

{

int j=(-1);

for (uint8_t i=0; i=0)

{ // display warning and sound alarm

lcd.clear();

l=1;

lcd.setCursor(6,0);

lcd.print(“WARNING”);

if (sensors[j].alarm==1)// Output Low alarm text

{

lcd.setCursor((DISPLAY_WIDTH-strlen(sensors[j].lotxt))/2,1);

lcd.print(sensors[j].lotxt);

}

else if (sensors[j].alarm==2)// Output High alarm text

{

lcd.setCursor((DISPLAY_WIDTH-strlen(sensors[j].hitxt))/2,1);

lcd.print(sensors[j].hitxt);

}

lcd.setCursor(3,2);

lcd.print(“Limit Reached”);// Limit Reached

lcd.setCursor(8,3);

sprintf(str,sensors[j].fmt,getVal(j));

lcd.print(str);// Output the value

for (uint8_t l=0; l<(1000/30); l++) // 2 seconds buzz // 2000 ORIGNAL

{

analogWrite(LEDPIN, brightness); // set the brightness of pin 9:

brightness = brightness + fadeAmount; // change the brightness for next time through the loop:

if (brightness == 0 || brightness == 255) fadeAmount = -fadeAmount ; // reverse the direction of the fading at the ends of the fade:

analogWrite(BUZZPIN,200);

delay(50); // bzzzzzzz

}

analogWrite(BUZZPIN,0);

delay(30); // Switch the buzzer off

for (uint8_t l=0; l= BBdelay)

{

BB_timer = millis();

scrToggle = !scrToggle;

}

if(scrToggle)

{

//12345678901234567890 // Screen width

lcd.setCursor(0,0);

lcd.print(” WARNING “);

lcd.setCursor(0,1);

lcd.print(“AC Mains Power Fail “);

lcd.setCursor(0,2);

lcd.print(” Battery Backup On “);

lcd.setCursor(0,3);

lcd.print(” DC 12v “);

}

else lcd.clear();

shown = false;

}

}

// ============================================================================================================

// Routine to read a temperature sensor

// ============================================================================================================

int getVal(int Pin)

{

delay(44); // Get the voltage reading from the temperature sensor and give it time to settle x

delay(44); // Do it twice with a delay to ensure it settles and give it time to settle for next time x

float voltage = (reading * voltage_ref)/1025.0; // Convert that reading to voltage, for 3.3v arduino use 3.3

if (sensors[Pin].type==’c’)

{

float temperatureC = (voltage – 0.5) * 180 ; // Convert from 10 mv per degree with 500 mV offset( CHANGE 180 TO CALIBRATE SENSORS/ VOLTAGE TO ARDUINO MATTERS! INPUT VOLTAGEHAS TO BE EXACLY 5V

reading=temperatureC; // Convert to an integer value for temperature

}

else if (sensors[Pin].type==’v’) reading=(voltage*1000); // or return voltage as required

else {

} // otherwise raw value

// Note I do the alarm check on the current value rather than the smothhed one as this would cause a time lag

if (sensors[Pin].ck>0) // Do we need to alarm check this one

{

if (readingsensors[Pin].hi) sensors[Pin].alarm=2; // Above High limit?

else sensors[Pin].alarm=0; // No Alarm

}

else sensors[Pin].alarm=0; // No alarm if no check

sensors[Pin].buffer[sensors[Pin].bufptr]=reading; // Smooth out by adding to the buffer

if ((++sensors[Pin].bufptr)>=BUFUSE) sensors[Pin].bufptr=0;

unsigned long total=0;

for (uint8_t i=0; i<BUFUSE; i++) total=total+sensors[Pin].buffer[i];

reading=total/BUFUSE; // Take the average value

return reading * (3.3/5.0); // do this here

}

// ============================================================================================================

// Routine to read the voltage reference

// ============================================================================================================

void getVref()

{

delay(17); // get the voltage reading from the temperature sensor and give it time to settle

delay(13); // Do it twice with a delay to ensure it settles and give it time to settle for next time

voltage_ref = (reading * 5.0)/1023,0; // convert that reading to voltage, for 3.3v arduino use 3.3

}

45. Gauthier
Posted November 1, 2015 at 1:02 pm | Permalink

This trick is very usefull for mesuring the VCC voltage. In my project I have an Arduino mini pro 3.3V powered by a 1s lipo on the raw input voltage pin. The code tell me the 3.3v regulator output tension rather than the regulator input from raw pin. So is it possible to read the raw input voltage pin voltage rather than the vcc pin ?

46. Dave
Posted January 27, 2016 at 4:54 pm | Permalink
47. Dave
Posted January 27, 2016 at 4:58 pm | Permalink

1024 is the correct number to use in the scale constant and here’s why from the Atmel complete datasheet on this page

“The ADC converts an analog input voltage to a 10-bit digital value through successive approximation. The minimum value represents GND and the maximum value represents the voltage on the AREF pin minus 1 LSB.”

So as you can see the maximum value is not the voltage on the AREF pin but actually one unit lower. So if you’re performing an analogRead() and referencing Vcc and you get a max value from the ADC then the value should actually be treated as 1023/1024 * Vcc. It should be noted that really a max value from the ADC is not accurate since any voltage higher than the reference will report at the max value.

Furthermore, in the code in the article we measure the internal 1.1V and reference the ADC with Vcc. The equation to convert the reported voltage is:

Vcc * NumberReportedFromADC/1024 = 1.1 —>
Vcc = 1.1 * 1024 / NumberReportedFromADC.

You can see the constant is in mV is then: 1.1*1024*1000.

Also people asked if they needed to change the reference. You do not since we are referencing Vcc (which is default) but measuring the 1.1V reference. The measurement will change when you perform an analogRead().

48. Gary Chapman
Posted March 11, 2016 at 9:48 am | Permalink

/1023 ? Oh my … that poor MCU. I can almost hear it’s tortured screams from here : )

49. perigaacticon
Posted May 5, 2016 at 12:18 pm | Permalink

Hello,

I just tried this on a Mega2650, and I am getting the value of the 5V reference instead of VCC. If I try with 12V on the barrel jack VCC = 5023, with USB power only it reads 4892. Can you tell me if there are modifications I need to make to the code you posted? Thanks!

• perigaacticon
Posted May 5, 2016 at 12:24 pm | Permalink

Oh I thought this was for measuring Vin… sorry, is there a way to do that?

50. Darko
Posted May 19, 2016 at 9:20 am | Permalink

Thanks, best trick ever. Best regards

51. Posted August 16, 2016 at 11:15 am | Permalink

Life Savor, Thanks for sharing, used in two projects.

52. abel
Posted September 9, 2016 at 6:19 pm | Permalink

Great article, I learn a bit more, but couldn’t get profit of this. I want to measure my battery level and I’m powering Arduino by L298N 5Vout into Vin directly, maybe I got an error because I wasn’t really decided, since my simple circuit of 2 resistors to divide my 12V battery into 4.5V of max and taking aritmethic media of readings I got stable % when motors are off. Correct me if you still think the best way is yours, since I guess it must be powered by 5V regulator inside from a minimum of 9V input. Thanks. Keep working.

53. Posted January 18, 2017 at 10:22 am | Permalink

It took me a while to understand the code and the logic behind the bandgap value and how it relates to reading an external voltage, but now that I was finally able to understand your code and implement it successfully, I feel in deep gratitude with you. Thanks for taking the time to share your knowledge, this guide can currently be found in pretty much all Arduino voltmeter related topics on the internet 😉

54. Posted February 2, 2017 at 11:52 am | Permalink

Your instructions just solve my problem. Thank you so much. I had been looking every place and was ready to give it up and try something different.

55. Posted June 6, 2017 at 7:27 am | Permalink

This website was… how do I say it? Relevant!! Finally I have found something that helped me.
Thank you!

56. Tim C
Posted November 5, 2017 at 2:17 pm | Permalink

For Mega 2560, you also need to set ADCSRB, which has the MUX5 bit in it, otherwise you get zero’s returned

Add to the MEGA2560 section the line
ADCSRB = 0; // MUX5

57. Mike K
Posted November 24, 2017 at 3:03 pm | Permalink

I know this is an old thread, but I made some adjustments that have given me more accurate results along a wider range of Vcc. I did this by connecting Vcc to AREF externally to use to compare. It is still necessary to calculate the constant that is divided by ‘result’ (the formula is commented in the code). The constant used in the code below is specific to my particular Arduino Nano; this number will need to be changed in your case using the formula. I’ve also placed the ADC in free-running mode and set the ADC frequency to 125kHz (assuming a 16MHz clock). The adjusted code is posted below. Any critiques or comments will be appreciated.

ADMUX &= ~((1 << REFS1) | (1 << REFS0)); // Set reference to AREF
ADCSRB &= ~((1 << ADTS2) | (1 << ADTS1) | (1 << ADTS0)); // ADC in free-running mode
// Read 1.1V reference against AREF
ADMUX = (1 << MUX3) | (1 << MUX2) | (1 << MUX1); // set the measurement to the internal 1.1V reference
_delay_ms(2); // Wait for Vref to settle
ADCSRA |= (1 << ADSC); // Start conversion

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

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

result = 1083630L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000
// scale_constant = internal1.1Ref * 1023 * 1000
// internal1.1Ref = 1.1 * Vcc1 (per voltmeter) / Vcc2 (per readVcc() function)
return result; // Vcc in millivolts
}