Go Down

Topic: Basic ADC/Analog read question (Read 5 times) previous topic - next topic

madvoid

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!


retrolefty

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.

Code: [Select]
// 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

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?

Constantin

#3
Jan 10, 2012, 10:41 pm Last Edit: Jan 10, 2012, 10:49 pm by Constantin Reason: 1
You can look up the bandgap reference at Wikipedia: http://en.wikipedia.org/wiki/Bandgap_voltage_reference. 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.

retrolefty


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:

http://www.ti.com/lit/ds/symlink/ads1115.pdf

Lefty

madvoid

#5
Jan 11, 2012, 12:35 am Last Edit: Jan 11, 2012, 12:52 am by madvoid Reason: 1
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.  

Graynomad

Quote
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
Rob Gray aka the GRAYnomad www.robgray.com

NightTrain

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.

madvoid

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?

NightTrain

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.

madvoid

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.

clarkwd


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;

    }

madvoid

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:

Code: [Select]
void setup(){
}


which will make it easier to read for people.

clarkwd


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

dc42

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.
Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Go Up