Basic ADC/Analog read question

Hi all, I had a quick question about the Arduino ADC regarding its accuracy:

If I understand correctly, if there are no analog references set, the Arduino's ADC will have a range of 1024 bits over a range of 5 V. However, isn't that 5 V rating nominal? If the voltage at the 5 V pin is actually 4.90 V then wouldn't the 1024 bits is spread over a range of 4.90 volts? If I switched to the internal 2.56 V or 1.1 V (I'm using a mega) would the accuracy of the voltage level improve? Or is building a voltage regulating circuit and running it into the aref pin the only way to ensure that the voltage is constant? The reason I ask is because I'm working with a very sensitive analog sensor that will lead to a large amount of error if this tolerance isn't accounted for.

Thank you for any and all help!

Yes, the default voltage reference for the A/D is whatever voltage is wired to the Avcc pin, normally +5vdc on most boards. And yes that is nominal and will be affected by the tolerance of the boards +5vdc and will change some depending on if you are powered via USB or external power via the on-board +5vdc regulator. Switching to the internal voltage references (1.1vdc or 2.56vdc) is an improvement as they will be much more constant but they too will have chip to chip tolerances so you may have to tweek your calibration for best accuracy. Also keep in mind that the Atmel specification for total accuracy for the A/D subsystem is +/- 2 LSB for a 10 bit A/D conversion, so you can see that the AVR’s A/D while very useful, is not really instrumentation quality A/D. There are many fairly inexpensive 12-16 bit I2C or SPI A/D converter chips avalible that work well with an arduino and will give much better accuracy and resolution Vs the built in A/D. Many have programmable gain and built in high accuracy voltage references.

Of interest to you may be a sketch that Coding Badly and I worked on a couple of years ago. It allows a running sketch to determine the actual value of the voltage on the Avcc pin and come up with a dynamic calibration factor that the sketch can use to compensate for the change of reference voltage while running. This can be very useful when one is powering the AVR chip directly with battery voltage which would change as the battery is discharged.

// Function created to obtain chip's actual Vcc voltage value, using internal bandgap reference
// This demonstrates ability to read processors Vcc voltage and the ability to maintain A/D calibration with changing Vcc
// Now works for 168/328 and mega boards.
// Thanks to "Coding Badly" for direct register control for A/D mux
// 1/9/10 "retrolefty"

int battVolts;   // made global for wider avaliblity throughout a sketch if needed, example a low voltage alarm, etc

void setup(void)
    {
     Serial.begin(38400);
     Serial.print("volts X 100");
     Serial.println( "\r\n\r\n" );
     delay(100);
    }
    
void loop(void)
    {
     battVolts=getBandgap();  //Determins what actual Vcc is, (X 100), based on known bandgap voltage
     Serial.print("Battery Vcc volts =  ");
     Serial.println(battVolts);
     Serial.print("Analog pin 0 voltage = ");
     Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));
     Serial.println();    
     delay(1000);
    }

int getBandgap(void) // Returns actual value of Vcc (x 100)
    {
        
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
     // For mega boards
     const long InternalReferenceVoltage = 1115L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc reference
        // MUX4 MUX3 MUX2 MUX1 MUX0  --> 11110 1.1V (VBG)         -Selects channel 30, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR)| (0<<MUX5) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
  
#else
     // For 168/328 boards
     const long InternalReferenceVoltage = 1056L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc external reference
        // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)         -Selects channel 14, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);
       
#endif
     delay(50);  // Let mux settle a little to get a more stable A/D conversion
        // Start a conversion  
     ADCSRA |= _BV( ADSC );
        // Wait for it to complete
     while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
        // Scale the value
     int results = (((InternalReferenceVoltage * 1023L) / ADC) + 5L) / 10L; // calculates for straight line value 
     return results;
 
    }

Lefty

Wow, this is great, thank you very much retrolefty! I thought that I would have to build a voltage regulating circuit for sure but I think I'm just going to try to find an ADC with the features you outlined. The code you posted is pretty amazing, I didn't even think that would be possible but that goes to show what I know! If you don't mind me asking, what exactly is the bandgap reference? Is wikipedia right in telling me that it is a voltage reference for the cpu?

You can look up the bandgap reference at Wikipedia: Bandgap voltage reference - Wikipedia. It's a great way to determine the Vcc since the Bandgap voltage is stable while Vcc may not be. Depending on how quickly you want to sample data, determining the bandgap voltage to Vcc difference may be a great way to improve the accuracy of the analog reads. However, consider that the ADC uses a multiplexer and a sample and hold system to make a analog read (see the Atmel datasheet for the 328 for a diagram).

So, first you have to select a single channel to sample, then you have to make the reading, settle for stable results, etc. At the very least, you'll need 13 ADC clock cycles to sample each analog channel. If you change the reference voltage, the ADC needs 25 clock cycles to make the change, followed by throwing out the first sample (per Atmel it's allegedly garbage). Perhaps that is what retrolefty is accomplishing with the 50 millisecond delay in his very cool and clever code. Thus, if you have a fast changing signal (i.e. noise on the Vcc line), you may or may not capture it properly with the bandgap approach. Peaks on one signal or the other may or may not show but ideally you'd have both signals sampled at the same time to cancel out coincident noise.

If you're using a standard Atmel on an Arduino board (i.e. at 16Mhz clock speed), your highest sampling rate (if the ADC is free running) for 10-bit resolution is about 12,500 samples per second. Using analogread commands results in about a 3x slower sampling rate due to Arduino IDE overhead. Retrolefty's code is very fast because he is bypassing the Arduino analogread command and is addressing the ADC directly. I also admire how it detects what model of processor it is running on to adjust the reference voltage. To my (currently tired) eyes his code makes the most sense if you're running an Arduino off of batteries and want to see where the input voltage is to send an alert to the user (think remote weather station, for example).

I tried out a couple of external ADCs but found most of them terribly frustrating. For one, it is very hard for me to troubleshoot a SPI device that may or may not be configured properly.

As a result, I chose to use a Arduino/Atmel ADC with an external AREF voltage reference (being able to reprogram the thing and get feedback re: results has been a godsend). A small linear regulator and a 1uF capacitor provide a very smooth voltage power supply regardless of whether you are powering the board via the USB bus, an external wall-wart, or the ICSP header. That makes calibration a lot easier - i.e. regardless of power source, your results should be very stable. So even if you choose to use an ADC, consider creating a stable voltage at 3.3V, for example, to feed the ADC voltage reference and hence reduce the potential for noise.

BTW, the folks at the open energy monitor project also have a nifty implementation of a high pass filter in their basic monitor implementation / Arduino source code suggested by Atmel to remove a 2.5VDC offset that also attacks noise problems in AC signals. Last but not least, consider looking up the Atmel paper re: decimation/oversampling. If you have the luxury of a slow-moving signal, even a 10-bit ADC can produce 16-bit results under the right circumstances.

madvoid:
Wow, this is great, thank you very much retrolefty! I thought that I would have to build a voltage regulating circuit for sure but I think I'm just going to try to find an ADC with the features you outlined. The code you posted is pretty amazing, I didn't even think that would be possible but that goes to show what I know! If you don't mind me asking, what exactly is the bandgap reference? Is wikipedia right in telling me that it is a voltage reference for the cpu?

As already stated and referenced by C, the band-gap is just a semiconductor junction inside the chip that will have a constant voltage drop rerespective of variation on the Vcc voltage wired to the chip.

As far as external ADCs, there are many capable devices and modules out there. I can only speak of the one I picked out to play with, the TI ADS1115. It's simple to use and has very nice specifications and features:

http://www.ebay.com/itm/ADS1115-16-Bit-I2C-Analog-Digital-Breakout-Arduino-All-Micro-Controllers-/230706360473?pt=LH_DefaultDomain_0&hash=item35b72bb099

Here is a link to the datasheet for the ADS1115 chip:

Lefty

Constantin and Retrolefty, thank you both for your help! I have a lot more options than I originally thought I did, which is great. I might end up trying all of them separately just to compare the output.

The sensor I am running is a LiCor Li200 pyranometer that will have a total output range of 0 to ~15 mV, which will obviously need to be amplified and then read in to the Arduino. This will be one of many sensors that will be incorporated into a battery powered weather station. If I can finally get this working accurately, I will post a sensor summary for anyone else having any troubles.

Or is building a voltage regulating circuit and running it into the aref pin the only way to ensure that the voltage is constant?

I think that's the simplest way, but you don't have to build anything, voltage reference chips are a dime a dozen.


Rob

MadVoid:

Did you ever get the LiCor signal boosted and interfaced properly with the ADC on your arduino? I’ve been asked to connect a LiCor sensor for photosynthetically active radiation (PAR) to the Arduino. As with the pyrometer that you mention, the signal is very weak (0 - 10 mV) and will need a boost to interface with the 0 → 5 V analog input on the arduino.

Looking for a place to start.

Thanks much.

Hi NightTrain,

I eventually did end up interfacing the Licor Li200 with the arduino. To begin, I made a transimpedance ampliflier (as seen here: http://upload.wikimedia.org/wikipedia/en/e/e3/Classic_300.jpg) to amplify the signal and convert the current from the Licor to a voltage. The positive lead of the Licor goes into the positive input of the op-amp and the negative lead of the licor goes to the negative lead of the op-amp, while the resistor R determines the gain. The op-amp I used was a TLV2370. Instead of using the Arduino's built in ADC, I decided to use the ADS7841 because it had 12-bit resolution. For the voltage reference I used the ref5045, a 4.5V reference.

However, rereading your post tells me that you have a LiCor sensor that is not the Li200. Is this correct?

Thanks!

I have LiCor Li-190 "Quantum" PAR sensors. The model provided has a BNC type connector and then a little dongle that supposedly converts signal from current to voltage. ... think this makes it a type "SL" sensor. ... think it also provides flexibility of using current as the signal or using the 0-10 mV signal.

The manual for this series of sensors is here: http://ftp.licor.com/env/Radiation_Sensors/Manual/TerrestrialSensors_Manual.pdf

... I'll look at your post in more detail real soon.

Cool. If you decide to use the current as the signal, the solution I posted earlier should work. If you decide to use the mV signal, I'd look into using an instrumentation amplifier, or building your own noninverting amplifier.

Hi
I think this routine is exactly what I want to check the battery. I’ve noticed that when the battery goes dead, temperature results I make using a TMP36 are all wrong. If I could get this working, I could set an alarm if the battery is too low.

I’m an old person but a brand new user, new coder, new everything, so I’m pretty limited. I copied the code from lefty above and add some stuff to output onto an LCD so I could see Vcc when powered by a nine volt battery. I’m using an Uno R3 with nothing connected except the LCD. While the battery was connected, using a volt meter, I measured the voltage from Vin to Ground. I got the following results that seem to indicate something is not working as expected.

Power Source Vcc Vanalog Pin 0 Vin to Ground
USB 472 117 4.48
dead 9v bat 460 115 5.6
new 9v bat 474 117 7.98

My code is below. What to you suppose is wrong?
Thanks
Bill

// Function created to obtain chip’s actual Vcc voltage value, using internal bandgap reference
// This demonstrates ability to read processors Vcc voltage and the ability to maintain A/D calibration with changing Vcc
// Now works for 168/328 and mega boards.
// Thanks to “Coding Badly” for direct register control for A/D mux
// 1/9/10 “retrolefty” from http://arduino.cc/forum/index.php/topic,86516.0.html

#include <LiquidCrystal.h> // include the LCD library
#include <SD.h> // include the SD library
LiquidCrystal lcd(7, 6, 5, 4, 3, 2);

int battVolts; // made global for wider avaliblity throughout a sketch if needed, example a low voltage alarm, etc

void setup(void)
{
lcd.begin(16, 2); // set up the LCD’s number of columns and rows:
lcd.print(“Vcc VPin0”); // Print a header on the LCD.
Serial.begin(9600);
Serial.print(“volts X 100”);
Serial.println( “\r\n\r\n” );
Serial.print(“Initializing LCD card…”); //write to the serial monitor
delay(100);
}

void loop(void)
{
battVolts=getBandgap(); //Determins what actual Vcc is, (X 100), based on known bandgap voltage
Serial.print("Battery Vcc volts = ");
Serial.println(battVolts);
Serial.print("Analog pin 0 voltage = ");
Serial.println(map(analogRead(0), 0, 1023, 0, battVolts));
Serial.println();

//display the result on the LCD
lcd.setCursor(0, 1);
lcd.print(battVolts);
lcd.print(",");
lcd.print(map(analogRead(0), 0, 1023, 0, battVolts));

delay(1000);
}

int getBandgap(void) // Returns actual value of Vcc (x 100)
{

#if defined(AVR_ATmega1280) || defined(AVR_ATmega2560)
// For mega boards
const long InternalReferenceVoltage = 1115L; // Adjust this value to your boards specific internal BG voltage x1000
// REFS1 REFS0 → 0 1, AVcc internal ref. -Selects AVcc reference
// MUX4 MUX3 MUX2 MUX1 MUX0 → 11110 1.1V (VBG) -Selects channel 30, bandgap voltage, to measure
ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR)| (0<<MUX5) | (1<<MUX4) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);

#else
// For 168/328 boards
const long InternalReferenceVoltage = 1056L; // Adjust this value to your boards specific internal BG voltage x1000
// REFS1 REFS0 → 0 1, AVcc internal ref. -Selects AVcc external reference
// MUX3 MUX2 MUX1 MUX0 → 1110 1.1V (VBG) -Selects channel 14, bandgap voltage, to measure
ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);

#endif
delay(50); // Let mux settle a little to get a more stable A/D conversion
// Start a conversion
ADCSRA |= _BV( ADSC );
// Wait for it to complete
while( ( (ADCSRA & (1<<ADSC)) != 0 ) );
// Scale the value
int results = (((InternalReferenceVoltage * 1023L) / ADC) + 5L) / 10L; // calculates for straight line value
return results;

}

Hi Bill,

Welcome to the Arduino Forum! I didn't find anything in your code that was drastically different from what retrolefty has posted so your code seems good. However, retrolefty specified that this method measures the voltage on the AVCC pin on the atmega328, while you said that you are measuring from the Vin pin. You may be trying to read your battery voltage, while retrolefty's code is reading the Avcc pin. Do you have any raw output values from your experimentation?

Also, for future reference, try to remember to put all code between the code tags that can be made by clicking the pound sign button when commenting. It will turn this:

void setup(){
}

into this:

void setup(){
}

which will make it easier to read for people.

I've attached a cut and paste from the serial monitor for the case of powering from the USB, but for the battery power I have no serial monitor. You have to take my word for it.:slight_smile: I am measuring between Vin and Ground on the Uno and that seems to match the battery voltage. I guess my confusion was the reference to Vcc and actual battery voltage. I'm even more confused about AVCC. It may be that I can check the Vcc that is calculated and if it ever goes below 472, then toot a horn warning of danger ahead. What is the difference between AVCC and Vcc on the UNO?
Thanks

Following is a screen shot of my serial monitor for the USB case.
volts X 100

Initializing LCD card...Battery Vcc volts = 474
Analog pin 0 voltage = 105

Battery Vcc volts = 474
Analog pin 0 voltage = 115

Battery Vcc volts = 474
Analog pin 0 voltage = 116

A simple way to monitor the voltage of your 9V battery is to use a potential divider comprising two equal resistors to halve the voltage, and feed that into an analog input. The value from an analogRead of that pin tells you the battery voltage, scaled so that 1024 = 10V. I do this on all the battery-powered projects I build.

Hi clarkwd,

So I’ll try to answer the questions I know before going onto the things I’m more unsure of. To begin, let’s outline the differences between Vin, Vcc, and AVcc. Looking at this image http://i.imgur.com/Ap12L.png, you can see that vin leads into the ncp1117 voltage regulator, which drops the Vin down to 5V. So any battery you plug into the barrel jack connector or Vin pin will be fed into the voltage regulator and be converted into 5V. Looking at page 3 of the atmega328 data sheet (http://www.atmel.com/Images/doc8161.pdf), you can see that Vcc is the digital supply voltage pin for the 328 chip itself. The 5V from the voltage regulator output will be connected to this pin to provide the Arduino with power. Looking at page for of the atmega328 data sheet, you can also see that AVcc is the supply voltage pin for the analog components of the 328 chips such as the A/D converter. The data sheet also says that AVcc should be connected to Vcc through a low pass filter. This allows for more accurate A/D conversions.

Now let’s break down retrolefty’s code, focusing on the following groups of code:

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

The internal bandgap voltage should nominally be 1.1V, but is rarely ever perfectly 1.1V. Therefore you should measure your bandgap voltage (let me know if you need to know how), multiply it by a thousand, and insert it where the “1115” is. Retrolefty’s bandgap must have been 1.115 volts.

     // For 168/328 boards
     const long InternalReferenceVoltage = 1056L;  // Adjust this value to your boards specific internal BG voltage x1000
        // REFS1 REFS0          --> 0 1, AVcc internal ref. -Selects AVcc external reference
        // MUX3 MUX2 MUX1 MUX0  --> 1110 1.1V (VBG)         -Selects channel 14, bandgap voltage, to measure
     ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);

This chunk of code essentially sets up an analogRead() with some custom parameters. It sets the analog reference to the AVcc pin, and then tells the arduino to read the ADC channel connected to the internal bandgap voltage. The next few lines in the code start the reading and waits for it to finish.

Unfortunately, I don’t exactly understand what is happening in the following line of code where all the magic happens:
int results = (((InternalReferenceVoltage * 1023L) / ADC) + 5L) / 10L; // calculates for straight line value However, retrolefty says it computes the voltage at the AVcc pin, which is simply the filtered, regulated five volts discussed above.

Looking at your usb serial monitor output, it seems like you are getting Vcc voltages of 4.74 volts. Looking at the voltages you pasted in your original post, the USB gives you a Vcc of 4.72V, a dead 9V battery gives you a Vcc of 4.60 volts, and a fresh 9V gives you a VCC of 4.74 volts. What you may have to do is find a correlation between Vcc an Vin. Then you can put your cutoff point in the code. Alternatively, the TMP36 is an analog temperature sensor, correct? You can run the sensor off of the 5V pin, and correct for the deviation in low power by measuring Vcc. Let me know if you want to know more about that.

I’m sorry if you knew most of this stuff, it’s a bit of a long post. Hopefully it helps!

Thanks for the help. I measured the Internal Reference Voltage and made the adjustment. I've got it set up to measure Vcc with the lefty routine and Vin with a voltage divider and at the same time use the TMP36 to make a temperature measurement. As soon as Vcc starts to drop then the temperature measurement will go out of control. As you pointed out, the trick is to find the voltage where the errors start. Unfortunately, my battery is not dead yet and I need to take a nap. I'll leave it running, writing to my SD card.
Why does Vin, when connected to the USB cable, measure to be lower than Vcc?
Please send me your thoughts on correcting the measurements as Vcc drops
Thanks again for your help.
Bill

Hi Clarkwd,

What I would do is calculate the temperature from the TMP36 using the Vcc measurement. For example, with no compensation, if you took an analog reading of the TMP36 of 512, the way you would convert it to volts is like such: 5125/1023 = 2.502 Volts. With compensation, you would calculate it like such: 512VBatt/1023. As long as you are running the TMP36 off of the 5V pin, you should be able to get an accurate measurement since you've accounted for any voltage variance. If you don't mind me asking, what is the application of this project? It sounds like you are working on a weather station, which is what I do most of my experimentation on as well.

It looks like this Vcc calculation is gonna work just fine. It doesn't mean much until Vcc goes below 5 volts, but then I use it to correct the analog readings for my TMP36 and other analog sensors. I measured the temperature of a glass of ice water and it measured a pretty constant 32 degrees as the battery went dead. I added a routine that posts like a low battery on the LCD when Vcc drops below 5 Volts and stops the routine when Vcc gets below 4 volts. Thanks to Lefty and Coding Badly for the routine and the others for your comments and help. My first project is to log temperatures along the fuel system of an antique car to investigate vapor locking. Bill

clarkwd:
It looks like this Vcc calculation is gonna work just fine. It doesn't mean much until Vcc goes below 5 volts, but then I use it to correct the analog readings for my TMP36 and other analog sensors. I measured the temperature of a glass of ice water and it measured a pretty constant 32 degrees as the battery went dead. I added a routine that posts like a low battery on the LCD when Vcc drops below 5 Volts and stops the routine when Vcc gets below 4 volts. Thanks to Lefty and Coding Badly for the routine and the others for your comments and help. My first project is to log temperatures along the fuel system of an antique car to investigate vapor locking. Bill

Very good on getting useful results using the 'proof of concept' CB and I posted long ago, of the advantages of being able to measure the actual voltage (Vcc and Avcc) the AVR is seeing, and using it be able to dynamically compensate (or calibrate if you like) any analogRead values your are using. That was it's intended primary purpose and your effort has shown it indeed can be both useful and effective in maintaining accurate ADC measurements even with a possible changing Vcc voltage bus, as can happen when using battery power.

Lefty