Why ADC/1024 is correct, and ADC/1023 is just plain wrong!

Thread Starter

THE_RB

Joined Feb 11, 2008
5,438
It's common to need to scale a microcontroller ADC result to give a value in some other scale. One example is to scale the ADC to read in actual volts like read from 0.00v -> 5.00v.

People sometimes use ADC *500 /1023 as this gives a reading of 500 (5.00v) for the top ADC value of 1023.

Doing that math; *x/(n-1) or *x/1023 does perform a kind of rounding function, but it "fudges" the rounding and gives both scaling and rounding errors.

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

To magnify and show the errors caused by *x/(n-1) this table shows a simple ADC that has 5 values. (This is just like a PIC ADC but has 5 possible ADC values, not 1024).

Rich (BB code):
First the incorrect *x/(n-1) math;
input		ADC	math		result	average		output
voltage		value	ADC*x/(n-1)		error		result
4.00-4.99	4	*5 /4		5.00	+0.50		5
3.00-3.99	3	*5 /4		3.75	+0.25		3
2.00-2.99	2	*5 /4		2.50	 0.00		2
1.00-1.99	1	*5 /4		1.25	-0.25		1
0.00-0.99	0	*5 /4		0.00	-0.50		0
Note that for the 5 possible ADC values, the output scale now has 6 values (0-5) and the value 4 can never occur! And although the average error might look to be nicely distributed +/- the centre of scale, the actual output result proves to be much uglier.

So if you had a slowly increasing voltage, the ADC would read; 0, 1, 2, 3, 5!!

The maximum error in real world use is 2v, because that very tiny change of 3.999v - 4.001v would cause an output change of 3v -> 5v or a change of 2v!

Rich (BB code):
Here is the correct scaling math *x/n;
input		ADC	math		result	average		output
voltage		value	ADC*x/n			error		result
4.00-4.99	4	*5 /5		4.00	-0.50		4
3.00-3.99	3	*5 /5		3.00	-0.50		3
2.00-2.99	2	*5 /5		2.00	-0.50		2
1.00-1.99	1	*5 /5		1.00	-0.50		1
0.00-0.99	0	*5 /5		0.00	-0.50		0
All output values are properly scaled and all are represented. The average error is never greater than 0.5 (no more average error than the /(n-1) example), and the average error is always one fixed value (-0.5) making it very easy to compensate (see below).

The maximum error at any time is 1v, this is half the max error of the /(n-1) example, which can introduce extra error up to 1 in high value ADC readings.

Understanding the *x/(n-1) problem.

The problem with /(n-1) is that it corrupts the SCALING of data. Because it forces the top ADC unit value to be the top of the scale the scale no longer is an accurate conversion of data.



This corruption means the true scale of 1024:5 is being represented as 1023:5 so the data on the output is now larger than life;



However if the correct scaling is used; ADC*5/1024, then any input data is correctly represented at the output;



All data is rounded down to the nearest ADC unit, so all the output data is correctly scaled but will have an average error of -0.5 ADC units.

This error is actually a property of the ADC module hardware AND the ratio scaling math. This is because the ADC hardware rounds all voltages down to the nearest ADC unit, and because the integer division (/1024) also rounds data down. (More on compensating for this later).

---continued---
 

Thread Starter

THE_RB

Joined Feb 11, 2008
5,438
Understanding ratios and ratio math!

A ratio is one scale compared to another. The result can always be calculated as *x /y (or *y /x) and will always appear as a linear line on a graph.

Forget ADCs for the moment and let's look at ratio scaling two other real world scales. For instance scaling Hz to RPM. The ratio is known; 1 Hz = 60 RPM, so the ratio is 60:1 and we can convert Hz to RPM by doing the math; Hz *1 /60 = RPM.

This math will give perfect scaled conversion of Hz to RPM. Note that there is NO correct solution using /(n-1) and *1 /59 will NOT properly convert Hz to RPM!

With a PIC ADC module there are exactly 1024 ADC units (numbered 0-1023) for every 5v. The ratio
to calculate voltage from ADC units is 1024:5 and that ratio is performed with this math; *5 /1024.

This ratio can be shown as a graph;



If the ADC happens to have 11bits (has 2048 ADC units) and runs from 10v the ratio remains the same, as the relationship of ADC units to voltage is still 1024:5.

Doing the incorrect math of ADC *5 /1023, gives ONE advantage, it means that the top ADC reading of 1023 will give an output result of "5v". The problem is that doing this kludge in code gives incorrect scaling of ALL the ADC data by adding more +error to the data as the values increase;



In some cases you may be able to tolerate this error, if you don't mind the two problems;
Problem 1. Higher values are always rounded up, lower values are always rounded down.
Problem 2. The output data (waveforms etc) are shown to be larger than reality (scaling error).

Doing the ratio scaling math right!

First let's clear one thing up, the ratio scaling math of *x /n is perfect, it has no error.

But there are two rounding down errors that we need to deal with;

1. The ADC hardware itself causes a rounding down error. Every possible input voltage is reounded down to the nearest ADC unit. This error occurs BEFORE any ratio math is done, and can be compensated by adding 0.5 ADC counts to every ADC sample BEFORE any conversion.

Since it is impossible to add 0.5 in integer math the best way is to double the ADC value, and then just add 1, ie; (ADC*2)+1

2. The math *x /n does not introduce error with the *x but the /n operation using integer math causes a rounding down error to the nearest n unit. This integer division rounding down error can be compensated by adding half the divisor; +0.5n /n which in our case is +512 /1024.

However since we have the ADC value already doubled from the previous operation, we need to divide by double, or 2048. So it becomes; +1024 /2048.

Putting it all together.

To get a reading of 0.00v to 5.00v from the PIC ADC can be done using the correct data scaling of all samples, and properly compensated integer rounding on all samples, by the following integer math;

((((ADC*2)+1) *500) +1024) /2048

Using *x of *500 means we are converting 1024 ADC units to 500 output units (which represent 0-500 ie 0.00v to 5.00v).
 
Last edited:

thatoneguy

Joined Feb 19, 2009
6,359
Thank you for taking the time to illustrate and explain the details!

Which is faster in assembly language?

Dividing by a non power of 2, or working with longer binary numbers?
 

t06afre

Joined May 11, 2009
5,934
I have been puzzled why many have been so stubborn about this matter. Like shown in your RPM to Hz example. If 1 Hz = 60 RPM everybody would intuitively used the ratio 60:1 Not 59:1. Why should this change then it comes to data conversion? I hope this very pointless debate on this matter. Will end with this excellent proof.
Can we make this thread a sticky
 

WBahn

Joined Mar 31, 2012
29,930
I have stated, several times, that the scaling used in converting from the ADC output to the value represented has to match the scaling of the value being measured to the ADC output that results from it.

If you have an N-bit ADC that has a reference voltage and the internal scaling is such that the reference voltage should be converted to an ADC output of 2^N (assuming this were an obtainable output) then using a divisor of 2^N is the correct value to use. I have never said otherwise.

Let's look at the question that started this round of debate:

Need the formula to convert from a 10 bit ADC value to a voltage to be displayed on an LCD with 5V full scale and 2.5V ref op amp to ADC.. So 0 volts is 2.5V (or bit 511 or 512) and -75V is bit 0 (0V op amp) and 75V is bit 1023 (5V op amp)
Note that there is no mention of a Vref input to this unspecified ADC. There is only a terse description of some of the external signal conditioning circuitry. But what there is, very explicity, is the statement that -75V corresponds to an ADC output of 0 and +75V corresponds to an ADC output of 1023. As I said in that thread, "And the OP specified that he wanted the top count, 1023, to map to +75V. It's up to him to adjust his preamp circuits to make that happen, but the conversion from ADC output to display value should be based on what he has stated his scaling intentions are."

Not uncommonly you design a system that measures some quantity and part of your goal is to have a specific relationship between that measurement and the ADC output. For instance, you want 1 lsb to represent 1 gram. So you adjust your circuits, which may or may not include adjusting the Vref of the ADC, so that you get and ADC output of 1000 when you have 1kg on your scale. Other times you simply put a weight on the scale and see what the ADC output is and want to come up with the scaling parameters that are needed to be able to display the correct value.

To repeat what is apparently an inconvenient question, let's take a real simple example.

I have a 3-bit ADC. The input signal is adjusted so that the ADC will output 000 when the input is -4.0V (i.e., between -4.5V and -3.5V) and will output 111 when the input is +3V (i.e., between +2.5V and +3.5V).

What is the scale size? By how much does the input signal change for each incremental change in the ADC output?

Notice how this is very, very, very similar to the question that was asked by the OP of the other thread - a reading of 0 corresponds to -4V and a reading of 7 corresponds to +3V. What should the scaling equation be? Do we just blindly say, "Oh, it's a 3-bit ADC and therefore the only number we can use must be 2^3 or 8"? Well, that makes the scaling ratio 0.875V/lsb when, clearly, it is 1V/lsb. Now, if you want to insist on using 8 but are also willing to go, "We need to determine what the effective Full Scale range is of our ADC so that we can plug it into the equation that uses 2^N in order to tell us the resolution," then fine, do that. You will do so by going

FS = [V(2^N - 1)-V(0)]*[2^N / (2^N - 1)]

So, for this example, you'll get

FS = [(3V) - (-4V)] * [8/(8-1)] = 8V

And then you'll plug that into your formula

1 lsb = FS/2^N = 8V/8 = 1V/lsb

Or, you could do exactly what I described and go:

1 lsb = [V(2^N - 1)-V(0)] / (2^N - 1)

and get

1 lsb = [(3V) - (-4V)] / (8-1)] = 1V/lsb

The two are EXACTLY and MATHEMATICALLY equivalent!
 

ErnieM

Joined Apr 24, 2011
8,377
As the OP bases his argument on the stated yet untested assumption that "(t)his is just like a PIC ADC," well, you do the math.
 

Thread Starter

THE_RB

Joined Feb 11, 2008
5,438
Let's look at the question that started this round of debate:

"Need the formula to convert from a 10 bit ADC value to a voltage to be displayed on an LCD with 5V full scale and 2.5V ref op amp to ADC.."

Note that there is no mention of a Vref input to this unspecified ADC. There is only a terse description of some of the external signal conditioning circuitry.
...
Agreed, the OP's question there is convoluted! But it does clearly state "10bit ADC value" and "5v full scale" in the same sentence, and since they are the most common ADC setup that seemed quite clear.

Please keep in mind this thread is not specifically aimed at that thread, or at your reply to the OP in that thread. :)

This /1023 vs /1024 argument has been ongoing for a long time on the forum throughout many threads, and that argument that erupts often tends to derail help that the experts like youself are giving to the OP.

If the problems with using /1023 are better understood then people might stop suggesting /1023 solutions for ADC scaling, then other people will not need to point out that using /1023 scaling introduces rounding errors and corrupts the amplitude of the output data.

There is no reason NOT to use the correct scaling of /1024, which is faster computationally anyway.
 

Thread Starter

THE_RB

Joined Feb 11, 2008
5,438
...
Which is faster in assembly language?

Dividing by a non power of 2, or working with longer binary numbers?
I would think it is faster to do assembly division in power of two, as that can be done with right shifts (through carry) and even multibyte divisions are very fast.

As for longer "binary numbers" that is totally separate from the decision to use divisions of a power of two or other divisions. The length of any binary numbers used in the calc will depend on the ratio, and what size of integers are needed to get the desired precision.

(Thanks too bance, thatoneguy and t06afre for support. :))
 

R!f@@

Joined Apr 2, 2009
9,918
Good info.

I always wondered why the heck most of you said 1023.
When I think it should be 1024.
Putting it all together.

To get a reading of 0.00v to 5.00v from the PIC ADC can be done using the correct data scaling of all samples, and properly compensated integer rounding on all samples, by the following integer math;

((((ADC*2)+1) *500) +1024) /2048

Using *x of *500 means we are converting 1024 ADC units to 500 output units (which represent 0-500 ie 0.00v to 5.00v).
Is this what you mean by Averaging the ADC to get nore accurate Voltage measurewments
 

Thread Starter

THE_RB

Joined Feb 11, 2008
5,438
No. :) Averaging the ADC is when you add up a lot of ADC readings together, then do the scaling calc after that to display it.

The calculation above is a scaling calc, to turn a 10bit ADC (0-1023) into a properly scaled result 0.00v-5.00v range for displaying.
 

djsfantasi

Joined Apr 11, 2010
9,155
The_RB said:
Putting it all together.

To get a reading of 0.00v to 5.00v from the PIC ADC can be done using the correct data scaling of all samples, and properly compensated integer rounding on all samples, by the following integer math;

((((ADC*2)+1) *500) +1024) /2048

Using *x of *500 means we are converting 1024 ADC units to 500 output units (which represent 0-500 ie 0.00v to 5.00v).
I almost understand this equation. Where does the value 500 come from? I would expect a value of 10, which is twice the maximum 5.00 VDC.
 

Thread Starter

THE_RB

Joined Feb 11, 2008
5,438
500 represents the max desired result, the "scaling" factor.

So the maximum result of the calc is the integer 500 which would represent 5.00v.
 

djsfantasi

Joined Apr 11, 2010
9,155
OK! I didn't realize you were using an integer to represent a scaled value (voltage * 100).

Exactly what I would expect with that clarification!
 
Top