Making accurate ADC readings on the Arduino

There are many sensors out there which output a voltage as a function of the supply voltage as their sensed value. Temperature sensors, light sensors, all sorts.

Measuring that voltage, and converting it in to real figures for whatever is being sensed is not actually as simple as you might at first think.

There are many examples on the internet for converting an ADC value into a voltage, but basically it boils down to:

  • Divide the ADC value by the ADC maximum value
  • Multiply by the supply voltage

And that sounds simple enough, doesn’t it?

unsigned int ADCValue;
double Voltage;

ADCValue = analogRead(0);
Voltage = (ADCValue / 1023.0) * 5.0;

Surely that looks OK, yes? You’ve got your Arduino plugged into the USB, which is supposedly 5 volts – after all, all the examples on the web just say 5v.

Wrong!

What you have there is a rough approximation. Nothing more.

If you want to make ACCURATE readings you have to know exactly what your supply voltage is.

Measuring the 5V connection on my Arduino while plugged in to the USB is actually reading 5.12V. That makes a big difference to the results of the conversion from ADC to voltage value. And it fluctuates. Sometimes it’s 5.12V, sometimes it’s 5.14V. so, you really need to know the supply voltage at the time you are doing your ADC reading.

Sounds tricky, yes?

Yes.

However, if you have a known precise voltage you can measure using the ADC, then it is possible to calculate what your supply voltage is. Fortunately, some of the AVR chips used on Arduinos have just such a voltage available, and can be measured with the ADC. Any Arduino based on the 328 or 168 chips has this facility.

I came across this nice piece of code on the TinkerIt site. It measures this 1.1V reference voltage, and uses the resultant ADV value to work out what the supply voltage must be.

long readVcc() {
  long result;
  // Read 1.1V reference against AVcc
  ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Convert
  while (bit_is_set(ADCSRA,ADSC));
  result = ADCL;
  result |= ADCH<<8;
  result = 1125300L / result; // Back-calculate AVcc in mV
  return result;
}

void setup() {
  Serial.begin(9600);
}

void loop() {
  Serial.println( readVcc(), DEC );
  delay(1000);
}

Very nice. very elegant. And, more importantly, very useful.

So now, using that, your ADC code could now look like this:

unsigned int ADCValue;
double Voltage;
double Vcc;

Vcc = readVcc()/1000.0;
ADCValue = analogRead(0);
Voltage = (ADCValue / 1023.0) * Vcc;

And it will be a whole lot more accurate.

7 thoughts on “Making accurate ADC readings on the Arduino

    1. majenko Post author

      Around 5V, yes, though not always precisely. But of course that only has any effect when you are powering it through the barrel jack. Through USB the regulator is not in use, so it could be anywhere from 4.75 to 5.25V.

      Like

      Reply
  1. Norbert

    Great! – Q: for a LED-Display (3 digits) as Digital-Voltmeter – do you purpose to scan “readVcc()” only once in the setup or continuously (1x per loop)?

    Like

    Reply
    1. majenko Post author

      That all depends on what Vcc is. If it’s a reasonably stable source that is unlikely to change, such as USB or an external power supply, then just once, or maybe just “once in a while” is fine. If its a battery, though, where the voltage is likely to decrease over time (batteries have quite a sharp initial decrease, then a gentle slope down, followed by a rapid drop off) then you should check more often. How often really is up to you and depends on just how accurate you want your results. Doing it before every reading will be, of course, the most accurate, but will slow your readings down somewhat. For displaying on an LED display you don’t need to be sampling that fast anyway.

      Like

      Reply
      1. Norbert

        Thanks so much! Your answer helped me out!
        OK, so I understand if the Supply-Voltage changes I have to sample it. In my case I’ll use it for a DIY-Power-Supply (60V/5A), where a separate smaller 5V/1A Aux.-Supply is available. The hole thing is powered from normal 230VAC, so I think the deviation of the 5V-Power for the Arduino would be only small, (caused f.ex. by temperature-fluctuation). So I think to scan it every ~1min. with a “millis()”-counter (-Interrupt) would be in the right bandwidth.

        Like

  2. Pingback: Using the ACS712 Current Sensor – Arduino++

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s