0% found this document useful (0 votes)
56 views60 pages

Secret Arduino Voltmeter - Measure Battery Voltage

The document discusses measuring battery voltage on an Arduino without using an analog pin. It explains that the Arduino can measure its internal 1.1 volt reference to determine the voltage (Vcc) being supplied to the chip. By measuring this reference voltage and using a calculation, the code provided can determine the current battery voltage powering the Arduino board.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
56 views60 pages

Secret Arduino Voltmeter - Measure Battery Voltage

The document discusses measuring battery voltage on an Arduino without using an analog pin. It explains that the Arduino can measure its internal 1.1 volt reference to determine the voltage (Vcc) being supplied to the chip. By measuring this reference voltage and using a calculation, the code provided can determine the current battery voltage powering the Arduino board.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 60

Homepage About Us Contact Us

Food Energy Health Tech Living More…

Tech
Secret Arduino Voltmeter – Measure Battery
Voltage
By SCOTT | Published: JULY 9, 2012
Search
A little known feature of Arduinos and many other
To search, type and hit enter
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 – Pages
analogRead() when using the default analog About Us
reference. It can also be used to measure the Vcc Articles
supplied to the AVR chip, which provides a means Checkout
of monitoring battery voltage without using a Contact Us
precious analog pin to do so. Home
Polls Archive
I first learned of this technique from these articles Privacy
– Making accurate ADC readings on the Arduino, Store
and Secret Voltmeter. In this article, I have Store Information
incorporated some additional improvements. Product Liability
Shipping

Motivation Warranty & Returns


Why Buy From Us?
There are at least two reasons to measure the voltage supplied to our Arduino Store-Energy
(Vcc). One is if our project is battery powered, we may want to monitor that Store-Food
voltage to measure battery levels. Also, when battery powered, Vcc is not going to Store-Health
be 5.0 volts, so if we wish to make analog measurements we need to either use Terms & Conditions
the internal voltage reference of 1.1 volts, or an external voltage reference. Why? Thank you for your
Message/Question
A common assumption when using analogRead() is that the analog reference Thank You For Your Purchase
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 Categories
analog reference is not 5.0 volts, but whatever the current level of Vcc is being Construction
supplied to the chip. If our power supply is not perfectly regulated or if we are Education
running on battery power, this voltage can vary quite a bit. Here is example code Energy
illustrating the problem: Food
General
double Vcc = 5.0; // not necessarily true Health
int value = analogRead(0);
Household
double volt = (value / 1023.0) * Vcc; // only correct if Vcc = 5.0 volts
Job
Living
In order to measure analog voltage accurately, we need an accurate voltage
Skills
reference. Most AVR chips provide three possible sources – an internal 1.1 volt
Tech
source (some have a 2.56 internal voltage source), an external reference source or
Transportation
Vcc. An external voltage reference is the most accurate, but requires extra
Uncategorized
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
Archives
April 2016
range, so the Vcc reference is the most practical, but potentially the least accurate.
November 2015
In some cases it can be completely unreliable!
October 2015
July 2014
How-To July 2013
June 2013
Many AVR chips including the ATmega series and many ATtiny series provide a
May 2013
means to measure the internal voltage reference. Why would anyone want to do
April 2013
so? The reason is simple – by measuring the internal reference, we can determine
March 2013
the value of Vcc. Here’s how: February 2013
January 2013
1. First set the voltage reference to Vcc.
December 2012
2. Measure the value of the internal reference. November 2012
3. Calculate the value of Vcc. October 2012
Our measured voltage is: September 2012
August 2012
Vcc * (ADC-measurement) / 1023 July 2012
June 2012
which as we know is 1.1 volts. Solving for Vcc, we get:
May 2012
April 2012
Vcc = 1.1 * 1023 / ADC-measurement
March 2012
February 2012
January 2012
Putting it altogether, here’s the code: December 2011
October 2011
long readVcc() {
September 2011
// Read 1.1V reference against AVcc
// set the reference to Vcc and the measurement to the internal 1.1V referen August 2011
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__ July 2011
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); June 2011
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__A May 2011
ADMUX = _BV(MUX5) | _BV(MUX0);
March 2011
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__A
ADMUX = _BV(MUX3) | _BV(MUX2); February 2011
#else January 2011
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); November 2010
#endif
October 2010

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


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 Blogroll
Documentation
long result = (high<<8) | low;
Plugins
result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*10 Suggest Ideas
return result; // Vcc in millivolts Support Forum
} Themes
WordPress Blog
WordPress Planet
Usage RSS Links
All posts
Checking Vcc or Battery Voltage All comments
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 Meta
determine if you are connected to a power source or running from batteries.
Log in

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.

Save

Secret Arduino Voltmeter – Measure Battery Voltage by Provide Your Own


is licensed under a Creative Commons Attribution-ShareAlike 4.0
International License.
This entry was posted in Tech and tagged arduino, attiny. Section: Article. Bookmark the
permalink. Both comments and trackbacks are currently closed.
« Arduino Leonardo versus Uno – What’s Reverse Polarity Protection Circuits »
New

108 Comments

Patrick Ball
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.

Scott Daniels
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.

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!

Daniel
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 ?

Scott Daniels
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.

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

Scott Daniels
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.

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?

Scott Daniels
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

dylan
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.

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.

Scott Daniels
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.

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

Fixed – thanks for catching that.

John Honniball
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%.

Scott Daniels
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 ?

Vadim Ippolitov
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
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.

PR
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

Scott Daniels
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.

Will
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

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.

Scott Daniels
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!

Will
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

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?
Thanks in advance for your help.

Scott Daniels
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.

Will
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!

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

Scott Daniels
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.

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

is this code useful for atmega168? thanks

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

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

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

Scott Daniels
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.


Scott Daniels
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.

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,

Scott Daniels
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.

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?

Scott Daniels
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,

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

Hi,
Thank you for this article.
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

Scott Daniels
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.

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:”);
Serial1.println(analogRead(VPowerLeg));
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
Please advice

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

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

The correct value is 1024 not 1023.

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:
ADCSRB = 0;
just before
delay(2);

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

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

Scott Daniels
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.

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.

Scott Daniels
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.

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

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.

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…”

john errington
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.

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.
i.e. int Vcc=readVcc();

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.
Mario
Posted January 27, 2014 at 8:36 pm | Permalink

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

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?

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)

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.

lumix tough camera


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
book-marking and adding your RSS feed to my Google account.
I look forward to fresh updates and will share this website with
my Facebook group. Talk soon!

bola online
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

Panasonic Lumix Fz40


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.

Wild Ds Installation
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

orangekit9840.Soup.
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 –

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?

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?

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.
Thanks in advance!

profuselectern632.
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)

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

christian
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

letting agents
Posted February 5, 2015 at 12:20 am | Permalink

Great article, totally what I needed.

Feel frse to visit mmy page letting agents Radlett

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
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) ;
#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
}

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

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

pinMode(analogin1, INPUT);
pinMode(analogin2, INPUT);

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

void loop()
{
Serial.println( readVcc(), DEC ); // Fn call 1
int i=0;
// Reading Temperature
float dummy;
for (i=0;i<11;i++)
{
dummy = analogRead(analogin1);
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);
}

Vcc = readVcc()/1000.0;
value_a[i]=analogRead(analogin1);
value_b[i]=analogRead(analogin2);
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…………………………..

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

Thanks for this, exactly what I needed!!

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];

sensors[NUMADC] =

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

StartReadings = true;

if(StartReadings)

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)


{

int reading = analogRead(sensors[Pin].pin);

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

reading = analogRead(sensors[Pin].pin);

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 {

float r=(reading*100.0)/1025.0;

reading=r;

} // 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()

int reading = analogRead(VREF);

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

reading = analogRead(VREF);

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

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 ?

Dave
Posted January 27, 2016 at 4:54 pm | Permalink

1024 is the correct number to use in the scale constant and here’s why from
the

“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().

Dave
Posted January 27, 2016 at 4:58 pm | Permalink

Corrected link:

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().

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 : )

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?

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

Thanks, best trick ever. Best regards

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

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

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.

Olivier
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

Jeff
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.

Situs Judi
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!

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

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.

long readVcc() {
ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)); // Prescaler at 128
(125kHz)
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
ADCSRA |= (1 << ADEN); // enable ADC.
_delay_ms(2); // Wait for Vref to settle
ADCSRA |= (1 << 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 = 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
}

33 Trackbacks

By » Arduino can be your voltmeter Fuzzy Hypothesis Online on July 12, 2012 at 11:27
am
By Belgaum news | About Belgaum | Belgaum information | Belgaum district | Belgaum
city | Belgaum Hotels | Belgaum People | Belgaum tourism | Belgaum entertainment |
Belgaum students | Inside facebook | Hack | make use of | technical news | | Arduino
voltage on August 15, 2012 at 6:07 pm
By Arduino voltage measurement tricks « My Horizon Wireless on August 16, 2012 at
5:39 am
By Secret Arduino Voltmeter — Arduino Passion on August 16, 2012 at 7:24 am
By Secret Arduino Voltmeter – Measure Battery Voltage | electronics-projects.info on
August 19, 2012 at 7:19 pm
By Measure the Vcc voltage applied to an Arduino « freetronicsblog on August 20, 2012
at 1:54 am
By Secret Arduino Voltmeter – Measure Battery Voltage | A Maker's Dream Factory on
August 29, 2012 at 8:31 pm
By Laurent Pantanacce » links to blog.pantanacce.com (weekly) on January 6, 2013 at
2:35 pm
By Accuracy of Arduino’s ADC | Baltazar's Hacks on November 19, 2013 at 12:17 am
By Hacking together a simple EMonCMS installation | Imaginary Industries on January
10, 2014 at 4:54 am
By mois-Blog - Bienenwaage: Todo on July 18, 2014 at 1:33 pm
By Arduino Mega 2560 ADC read Minivolts at ADC port A8 | CL-UAT on December 29,
2014 at 10:35 pm
By Improved RFM12B with accurate RSSI reading Library – on December 30, 2014 at
8:58 am
By Measure Arduino Battery Voltage | Begerk on February 8, 2015 at 11:18 am
By Прихований вольтметр в Arduino – вимірювання напруги батареї засобами
мікроконтролера » Блоґ Ореста on March 30, 2015 at 2:27 pm
By HOWTO: Very low power usage on Pro Mini V2 (Arduino clone) — whizzy.org on June
11, 2015 at 3:39 pm
By #Temp_Heart_Sensor | Facelesstech on July 31, 2015 at 3:28 pm
By Die Versorgungsspannung messen | wer bastelt mit? on December 8, 2015 at 4:45
pm
By Battery Voltage Drops When Driving | licsense - driving test on December 19, 2015 at
10:07 am
By Arduino power consumption and management | Raspberries and other fruits on
January 8, 2016 at 12:23 pm
By Wireless Steering Wheel Buttons - GT40s.com on January 29, 2016 at 9:57 am
By ARDUINO | Pearltrees on February 3, 2016 at 7:07 am
By Variometer selber gebaut on February 24, 2016 at 1:24 pm
By Como medir a tensão de alimentação do arduino. | AUTOMALABS on February 29,
2016 at 2:10 am
By How To Measure Voltage In Circuit | Yellow decoration on July 13, 2016 at 9:08 am
By How To Measure Voltage Drop Of Diode | Yellow decoration on July 14, 2016 at 11:44
pm
By How To Measure Resistance With A Voltmeter | Yellow decoration on July 18, 2016 at
2:17 am
By surveillance pile sur noeud mySensor | CM Ingenierie on February 11, 2017 at 7:52
am
By How To Double Battery Voltage | Information on February 18, 2017 at 9:40 pm
By Measuring Current Voltage And Power Book – inebooks Review on March 24, 2017 at
3:18 pm
By Voltuino Revisited | on April 13, 2017 at 4:08 pm
By Resources, articles and helpful links | okinesio on June 22, 2017 at 9:41 am
By Error In Voltmeter Readings on November 28, 2017 at 11:22 am

About Us Contact Us Privacy Terms & Conditions


The content of Provide Your Own is licensed under the . See Terms for more information. All content posted on this site is commentary or opinion and is protected under Free Speech.
ProvideYourOwn is not responsible for content written by contributing authors. The information on this site is provided for educational and entertainment purposes only. It is not intended
as a substitute for professional advice of any kind. ProvideYourOwn assumes no responsibility for the use or misuse of this material. Your use of this website indicates your agreement to
these terms and those published here. All trademarks, registered trademarks and servicemarks mentioned on this site are the property of their respective owners.

This website is created and maintained by Purple Cow Solutions

You might also like