analogRead - divide by 1023 or 1024?

Following on from this thread I've been trying to get my head around converting the result from analogRead to a voltage.

Let's suppose:

int value = analogRead (A0);

And imagine we get the value of 1000.

Now (ignoring the fact that integers don't have decimal places) is the voltage (assuming we have a 5V reference):

 1000.0 / 1024 * 5.0 = 4.8828125

or:

 1000.0 / 1023 * 5.0 = 4.8875855

In other words, divide by 1023 or 1024? I should point out that dividing by 1024 immediately gives a "wrong" answer for measuring 5V:

 1023.0 / 1024 * 5.0 = 4.9951171

This is because the maximum reading is 1023.

Browsing the web:


To scale the numbers between 0.0 and 5.0, divide 5.0 by 1023.0 and multiply that by sensorValue

One vote for 1023.


http://forum.arduino.cc/index.php?topic=200447.0

Multiple schools of thought, including:

mrburnette:
You divide by 1024 if you are a mathematician or if you truly understand ADC successive approximation.


Hedging his bets:

The number 0 represents 0V and 1023 represents Aref. The voltage level at any ADC port can be determined as follows:

float analogValue = (float)digitalValue * (3.3f/ 1023f)

In theory, you should divide by 1024, because there are that many steps.


Different opinions:

The ADC returns a value from 0 to 1023 not 0 to 1024. 0 to 1023 is 1024 different values because zero is a value too. ... That is why you divide by 1023 and not 1024.

Not exactly.
Consider the nominal "width" of the ADC reading: it is 1024. That is strictly related to Vref (typically 3.3V).
Now we must slice that segment in many smaller parts: in total are 1024 "tiles" (if you like numbered from 0 to 1023).


http://forum.arduino.cc/index.php?topic=252276.0


The correct scaling math; *x/n or *x/1024 correctly scales all the output data in size, but gives an average rounding error of 0.5 ADC counts (always rounding down).


So, how come we get a "wrong" result for a value of 1023?

It seems that the ADC can return 1024 "slots" of values (from 0 to 1023) where each slot represents 1/1024 of the reference voltage.

So for a 5V reference voltage the slot width is:

 5 / 1024 = 0.0048828125V

Thus a result of 0 could be: 0V to 0.00488V
A result of 1 could be: 0.00488V to 0.009766V
...
A result of 1023 could be: 4.99511V to 5V

So really the "wrong" result for 5V is more that the voltage is not necessarily 5V, it could be 4.995.

In addition to that, the hardware rounds down the result, so you could compensate by adding in an average error of 0.5.

 1023.5 / 1024 * 5.0 = 4.99756V

So this is reasonably close to 5V. Similarly a reading of 0 should probably be treated as possibly halfway between 0 and 1, thus it could on average be:

 0.5 / 1024 * 5.0 = 0.002441V

Oh yes, and the Atmega328 datasheet mentions in the section "23.7 ADC Conversion Result" that the figure is 1024.

Personally, I would say D) None of the Above. If you're looking for that kind of accuracy, calibrate it by putting a precisely known voltage on the input, and seeing what readings you get. You're almost certain to get at least one bit of "dither" as well, as you saw in the that test you ran in another thread yesterday.

Regards,
Ray L.

Nick, what is your verdict ?

When an analog input is clipped to 5.000V, I would like to have 5.000V in my calculation. So I use 1023. I read that the ADC successive approximation requires 1024. However, I didn't think of the 0.5 bit rounding down, and I don't want it to be rounding down.

Talking about the last bit does make sense to me. With many samples for the average, it is possible to get more "relative" accuracy than 10 bits.

Any division of an ADC represents a range, not one particular voltage.
An analogRead that returns a "0" doesn't mean 0V but 0 to around 4.89mV

Refer the chart attached, sorry my rounding got a little sloppy.

IMG_1737.jpg

Hi,

The way I see it is that you have 1024 levels, GND is zero, so 1023 will be FullScale.
There are only 1023 steps.
You are counting quantization steps not the resulting levels.
Zero is a number.
Tom...... :slight_smile:

Thank you Runaway Pancake.

Then this is what I want (adding half a bit, to avoid rounding down)

float voltage = ((float) rawADC  + 0.5 ) / 1024.0 * 5.0;

The voltage would never be 0.0 and never be 5.0

Peter_n:
Nick, what is your verdict ?

First, let's go back to the datasheet:

0x000 represents analog ground, and 0x3FF represents the selected reference voltage minus one LSB.

So (contrary to what you might expect) 0x3FF (ie. 1023) does not claim to represent Vref, but rather the reference voltage minus one bit (which in the case of 5V is 5/1024 = 4.88 mV).

Thus, the fact that dividing 1023/1024 and multiplying by 5 does not give 5 is entirely consistent with the fact that it does not claim to represent Vref.

The datasheet goes on (on the previous page):

Quantization Error: Due to the quantization of the input voltage into a finite number of codes, a range of input voltages (1 LSB wide) will code to the same value. Always ±0.5 LSB.

So there is an expected error of +/- 2.44 mV.

Therefore I agree with Peter_n's formula:

float voltage = ((float) rawADC  + 0.5 ) / 1024.0 * 5.0;

One of the reasons I was wondering this was that in the other thread my observations were slightly different to the calculated value, namely:

Analog port = 14, average result = 508    // 2.5V input
Analog port = 15, average result = 1022   // 5V input
Analog port = 16, average result = 672    // 3.3V input

Using the formula in the datasheet we would expect 2.5V input to give:

 2.5 * 1024 / 5 = 512

However I got 508 above.

So I checked the 5V pin with a meter and found it read 5.026V. Substituting now:

 2.5 * 1024 / 5.026 = 509.3

Much closer to the observed output (508). And since we can also factor in a 0.5 bit quantization error, that brings it down to 508.85 which, truncated, now agrees with the reading I got.

Similarly for the 3.3V input (actually measured to be 3.306V) so now we have:

 3.306 * 1024 / 5.026 = 673.57

Again that is pretty close to the program output (well, out by one).

Therefore I agree with Peter_n's formula:

If we're being pedantic, Peter_n's formula is incomplete. @Runaway Pancake nailed it. For a given ADC value a range of voltages is possible.

float voltageLo = ((float) (rawADC  + 0) ) / 1024.0 * 5.0;
float voltageHi = ((float) (rawADC  + 1) ) / 1024.0 * 5.0;

Where the measured voltage is greater than or equal to voltageLo and less than voltageHi. (With successive approximation converters we can never tell that we've reached exactly the voltage reference.)

Including an offset (0.5 in Peter_n's formula) can cause serious problems for some applications; like PID control. It is assumed that an ADC value of zero is close enough to zero to be considered zero. If that assumption is not true a converter with more resolution is needed.

In other words, if adding an offset solves a problem the problem is much better solved with a better converter.

1 Like

Some things I've noticed when trying to get very precise readings from an AVR based Arduino...

• USB power can be electrically noisy

• USB voltage can vary significantly with minor load changes

• Performing an analog conversion can cause the USB voltage to vary skewing the reading

• The AVR processor is electrically noisy which can affect a conversion; using the ADC Noise Canceler can make a significant difference

• A nearby pin outputting a PWM signal can wreak havoc on a conversion

• Something like this... Precision LM4040 Voltage Reference Breakout - 2.048V and 4.096V : ID 2200 : $7.50 : Adafruit Industries, Unique & fun DIY electronics and kits ...plus noise cancelling gives very good results even when the board is USB powered

• When powered from USB and not using noise cancelling ±2 bits is about as good as it gets

• The 3.3V regulator makes a fairly good reference

• Simple averaging improves the accuracy

I mentioned above that each reading represented a range, but I agree it is worth being aware of what you mentioned. As an approximation (eg. to display the temperature) "going halves" and assuming that it is in the middle of the range is probably an OK assumption.

I re-ran the test in the other thread, also measuring against Gnd, and always got a reading of 0, so it is interesting that I got 1022 (not 1023) for 5V, but Gnd was correct.

If it is any consolation, this has been bugging me for 30 years and I am still not convinced.

A idealised ADC converter would have an output which, if you plotted it on a graph as a function of output versus input, would be a straight diagonal line.

An actual adc output looks like the side view of a staircase. The theoretical diagonal line might be touching the top corners of each step, or the bottom corner of each step, or slicing through the middle or each tread and riser.

Also, the top or bottom step might be the same width as the others, or only half the width. Also, the staircase might end at the top end with a vertical step rather than a horizontal one, which might be incapable of being represented in the output available. After all, there is no really good reason why 1024 could not be a valid output for 5.0 V, it is a 16 bit number after all.

If I was to answer this question, I'd get a very stable adjustable voltage source, and actually measure the behaviour of the device. Is the bottom step a full step wide, or only half a step wide ? And so on. Once I had deduces the apparent position of the staircase, relative to the nominal diagonal line, then you are in a position to map the adc outputs back to the nominal voltage, represented by the midpoint of each step.

Well this is quite amusing. I tried to do it with noise cancelling (by going to sleep).

#include <avr/sleep.h>

void setup ()
  {
  Serial.begin (115200);
  Serial.println ();
  }  // end of setup

const int ITERATIONS = 500;
unsigned long totals [6];

// when ADC completed, take an interrupt 
EMPTY_INTERRUPT (ADC_vect);

int takeReading (byte port)
  {
  ADMUX = bit (REFS0) | ((port - A0) & 0x07);  // AVcc
  
  // ensure not interrupted before we sleep
  noInterrupts ();
  set_sleep_mode (SLEEP_MODE_ADC);    // sleep during sample
  sleep_enable();  
  
  // start the conversion
  ADCSRA |= bit (ADSC) | bit (ADIE);
  interrupts ();
  sleep_cpu ();     
  sleep_disable ();


  // reading should be done, but better make sure
  // maybe the timer interrupt fired 

  // ADSC is cleared when the conversion finishes
  while (bit_is_set (ADCSRA, ADSC))
    { }

  byte low  = ADCL;
  byte high = ADCH;

  // combine the two bytes
  return (high << 8) | low;
    
  }
  
void loop ()
  {
  for (int whichPort = A0; whichPort <= A3; whichPort++)
    totals [whichPort - A0] = 0;
  
  for (int i = 0; i < ITERATIONS; i++)
    {
    for (int whichPort = A0; whichPort <= A3; whichPort++)
       {
       int result = takeReading (whichPort);
       totals [whichPort - A0] += result;
       } 
    }

  for (int whichPort = A0; whichPort <= A3; whichPort++)
     {
     Serial.print ("Analog port = ");
     Serial.print (whichPort);
     Serial.print (", average result = ");
     Serial.println (totals [whichPort - A0] / ITERATIONS);
     } 
    
  Serial.println ();
  delay (1000);
  }  // end of loop

Output:

Analog port = 14, average result = 506
Analog port = 15, average result = 1022
Analog port = 16, average result = 670
Analog port = 17, average result = 0
(repeated)

Those figures are noticeably lower than before (506 compared to 508, and 670 compared to 672). But! During sleep mode, the load on the 5V bus decreased, and the 5V pin jumped to 5.028V. So recalculating with the new reference voltage gave similar answers to before.

This would appear to strongly support Coding Badly's suggestion of using a proper reference voltage, as clearly the 5V voltage isn't really 5V.

I support 1024 as it's mathematically "clean". The ADC gives integer results in the half-open interval [0, 1024) which map to the half-open interval [0, Vref).

michinyon:
If it is any consolation, this has been bugging me for 30 years and I am still not convinced.

A idealised ADC converter would have an output which, if you plotted it on a graph as a function of output versus input, would be a straight diagonal line.

An actual adc output looks like the side view of a staircase. The theoretical diagonal line might be touching the top corners of each step, or the bottom corner of each step, or slicing through the middle or each tread and riser.

Yes, well see the diagram on this page:

christop:
I support 1024 as it's mathematically "clean". The ADC gives integer results in the half-open interval [0, 1024) which map to the half-open interval [0, Vref).

Thanks, I was trying to remember that terminology. Half-open interval sounds right.

2 bits = 4

0-1(v)
1-2
2-3
3-4 2 bit resolution

so it should be "/1024" for arduino 10 bit adc

Bear in mind the timer 0 overflow interrupt (or any other interrupt event) can wake the processor early slightly spoiling the result.

This is the essence of what I do to deal with early waking (if you need any of the missing bits let me know)...

uint16_t analogReadRawOnce( void )
{
  uint16_t rv;

  // Generate an interrupt when the conversion is finished to wake the processor
  tcADC::EnableAndClearInterrupt();

  // Can't use SLEEP_MODE_ADC because it turns off the I/O clock (no PWM)
  set_sleep_mode( SLEEP_MODE_IDLE );
  sleep_enable();

  uint8_t SleepCount;
  uint8_t TriesRemaining;
  uint8_t SaveSREG;

  // Preserve the I-flag.  This code is unlikely to be used in an interrupt service 
  // routine so save-restore is probably not necessary but the difference is just one or 
  // two machine instructions so we'll save-restore.
  SaveSREG = SREG;

  // Make two attempts to take an ADC reading with no interruption
  TriesRemaining = 2;

  do
  {
    --TriesRemaining;

    // Any interrupt will wake the processor including the millis interrupt so we have to...
    // Loop until the conversion is finished
    SleepCount = 0;
    do
    {
      ++SleepCount;
      // The following line of code is only important on the second pass.  For the first pass it has no effect.
      // Ensure interrupts are enabled before sleeping
      sei();
      // Sleep (MUST be called immediately after sei)
      sleep_cpu();
      // Checking the conversion status has to be done with interrupts disabled to avoid a race condition
      // Disable interrupts so the while below is performed without interruption
      cli();
    }
    while ( tcADC::ConversionInProgress() );
  
    // There is a slight possibility that the interrupt flag will be set at this point.  Clear the flag.
    tcADC::ClearInterruptFlag();
  }
  while ( (SleepCount > 1) && (TriesRemaining > 0) );

  // No more sleeping
  sleep_disable();
  
  // Restore the I-flag
  SREG = SaveSREG;

  // Finished.  Disable interrupts.
  tcADC::DisableInterrupt();

  // Save the value now in case disabling the ADC interferes
  rv = tcADC::GetDataRegister();

  return( rv );
}

EMPTY_INTERRUPT( ADC_vect );

I give the answer I always give; "Ask yourself, why would a semiconductor manufacturer (Intersil, TI et al) go the trouble and expense of producing voltage reference ICs with output voltages like 1.024V and 2.048V ?"
It's not like those voltages are naturally-occurring, like bandgap voltages.