PIC Maths. trying to calculate %

Thread Starter

chrisw1990

Joined Oct 22, 2011
551
Rich (BB code):
float AccelPerc=0, BrakePerc=0;

AccelPerc=(AccelPedV/1024)*100;  //where accelpedv is 10-bit ADC Sample.
it always returns zero.. what am i doing wrong?
 

sbixby

Joined May 8, 2010
57
Saw this after-the-fact, but I wouldn't say "prefers".

I know nothing of PIC Basic (?), but I'm a long-time software engineer.

I suspect that AccelPedV is an integer type and thus AccelPedV / 1023 (or 1024) is defaulting to an integral value. Since AccelPedV is *always* less than 1024, being a 10-bit value, the division always comes out to zero.

Also - multiplying by 100 first and then dividing still does integer math, although that's undoubtedly more efficient on an MPU, and probably precise enough.

To be more precise in a floating point value, you'd want to convert AccelPedV to a floating type first, then do the math around it, so that the division and multiplication parts (in either order) result in floating-point intermediate values.
 

vpoko

Joined Jan 5, 2012
267
Why? A 10bit ADC output is in binary 2^10 which is scaled at 1024.

In the same way a percentage is scaled in decimal at 100 (10^2) and math uses the constant 100.
1023 is right. A 10 bit number can range from 0..1023 (1024 distinct possibilities). If you divide by 1024, then the maximum output won't be 100%.
 

ErnieM

Joined Apr 24, 2011
8,377
2^10 has 1024 states. These are numbered 0 thru 1023.

In binary they look like 0b00,0000,0000 thru 0b11,1111,1111. Commas added to help you count the bits, of course.

The calculator built into windows can actually be quite helpful here as in scientific mode it has hex and binary (octal too for you octogenarians). Very useful to calculate constants most easily seen in one form (say "1111111111" for binary or 0x3FF in hex.

(1023 is correct final answer.)
 

THE_RB

Joined Feb 11, 2008
5,438
1023 is right. A 10 bit number can range from 0..1023 (1024 distinct possibilities). If you divide by 1024, then the maximum output won't be 100%.
Those are 2 facts. However the number system and the correct math is /1024.

Consider if the ADC module had only 10 steps, and was measuring voltages between 0v and 5v. The result looks like this;

step0 = 0v to 0.4999v
step1 = 0.5v to 0.9999v
step2 = 1v to 1.4999v
step3 = 1.5v to 1.9999v
step4 = 2v to 2.4999v
step5 = 2.5v to 2.9999v
step6 = 3v to 3.4999v
step7 = 3.5v to 3.9999v
step8 = 4v to 4.4999v
step9 = 4.5v to 4.9999v

Note there is no 5v. 5v is an illegal value (that would actually equal step10 as this is all a base 10 system!) so it would just default to step9 since the ADC is a 10 step system.

However that is all correct and accurate as the ADC has 10 steps and is a base 10 system, you would NOT do /9 on a base 10 system as all the step values would be wrong!

In reality we don't mind that 0.0001v error by including 5.0v in step9.

In the 1024 step ADC module, this is a base 2 system (ie 2^10) and there are exactly 1024 steps, numbered 0-1023;

step0 = 0v to 0.0047v
step1 = 0.0048v to 0.0096v
(etc)
step1023 = 4.9951v to 4.9999v

this is all base 2^10 and the correct math to get every step right is /1024.

If you want to improve the rounding from rounding each step down to a more equally biased rounding you can do;
percent = (((blah * 100) + 512) / 1024)

Which keeps the important step sizes correct as it is still base 2 and /1024, but this now gives 101 possible outputs (0% to 100%) with the 0% and 100% steps halved in size.
 

ErnieM

Joined Apr 24, 2011
8,377
(Oh, go back and read it for yourself)
Let's look at a real world analog to digital converter transfer function from the PIC12F615, a typical device who's sheet I have handy:




Note the max count of 0x3FF occurred centered about an input of Vdd or Vref. As this device can use Vdd or an separate reference as Vref, those terms are used interchangeably. We'll proceed using Vref. Same is about zero, you can use either Vss (ground) or a higher voltage for the minimum. We'll use Vss (zero) as the starting point.

Note at the maximum value the transition is centered about Vref. Vref is not some unobtainable endpoint. Thus, a theoretical maximum count of 0x3FF (1023) occurs when Vin = Vref.

(Real world tolerances may make Vin slightly higher or lower, but that's another reason why we should calibrate the measurement.)

Should we wish to back convert from counts to volts, we would use:

Vin = (measured counts)/(max count) * Vref

For a input of Vin = Vref:

Vin = (1023) / (1023) * 5.00 = 5.00

If instead you use the count of states (1024) instead of the max count you get:

Vin = (measured counts)/(count of states) * Vref

Vin = (1023) / (1024) * 5.00 = 4.9951171875

which is in error by about 0.1%
 

WBahn

Joined Mar 31, 2012
30,058
Those are 2 facts. However the number system and the correct math is /1024.

Consider if the ADC module had only 10 steps, and was measuring voltages between 0v and 5v. The result looks like this;

step0 = 0v to 0.4999v
step1 = 0.5v to 0.9999v
step2 = 1v to 1.4999v
step3 = 1.5v to 1.9999v
step4 = 2v to 2.4999v
step5 = 2.5v to 2.9999v
step6 = 3v to 3.4999v
step7 = 3.5v to 3.9999v
step8 = 4v to 4.4999v
step9 = 4.5v to 4.9999v

Note there is no 5v. 5v is an illegal value (that would actually equal step10 as this is all a base 10 system!) so it would just default to step9 since the ADC is a 10 step system.
Whether 5V is in the range or not is irrelevant. The thing that is most relevant is missing. Namely, given a value from the ADC -- 0 to 9 in this case -- what estimator (the algorithm for estimating the input voltage) results in the minimum error? Now, "minimum error" depends on a number of things, but in the absence of more detailed knowledge, it is generally taken to mean the minimum mean-squared-error (MMSE) assuming a random input that is uniformly distributed over the input range.

Since we assume that we have a linear relationship, we expect the best results to come from an estimator of the form:

V = aX+b

where X is the ADC output.

By inspection, we can assert that we want the voltage estimate to lie in the middle of each range. So, for X=0 we want Vin = 0.25V meaning we need b=0.25V and for X=9cnt we want Vin = 4.75V. Solvoing for 'a', we get:

a = (4.75V-0.25V)/9cnt = 0.5V/cnt = 5.0V/10 = Vrange/states

But if we, for some reason, elect not to have an offset 'b' in our estimator, then we have:

V = aX

Under these conditions, you clearly do NOT want to use 5.0V/10 as your conversion gain because that gives you the worst possible MMSE of any estimator that at least puts the estimate within the nominal range of voltages for that ADC output.

You can crank through the math, but in this case you want the voltage estimate at the highest count to equal the max of the voltage range. This means that you are overestimating the voltages at this end, just like you are underestimating the voltages at the lower end, but in the middle of the range you are putting the estimates near the middle of the range resulting in the lowest achieveable MMSE. Thus you would use Vrange/(states-1) under these conditions.

So whether to use 9 or 10 depends on the algorithm used to obtain the estimate for the input voltage given the ADC output. In particular, whether or not you include an offset in your estimator.

If your ADC has half steps at either end, then you need to crank the numbers for that situation, taking into account the fact that the voltage is expected to be in the two end bins half as often as the other eight bins. If your input voltage has a non-uniform distribution, then you would ideally allow for that (and, in this case, would likely come up with a number somewhere between 9 and 10).
 

THE_RB

Joined Feb 11, 2008
5,438
ErnieM; The PIC ADC has 1024 linear steps, of equal size (ideally), your graph APPEARS to have a smaller step for 1023 but if you read the text it clearly states for that last step "1 LSB ideal" the same as it does for step 0.

I believe the graph shows that line extending to the right for the same reason I posted, that for all "illegal" voltages 5.0v and up will still default to the last step. But the last step 1023 does start at 4.9951v and covers the last 1024th of the range as it should in any linear ADC.

If your math is attempting to get a scaled output of 0-100% that is an output of 101 steps, and the ADC has 1024 steps, so the math is *101 /1024 to give 101 equal steps out from the 1024 equal steps in.

However that shows a problem in your thinking as "percentage" does not have 101 steps. Percentage is a very base10 or base100 value and has 100 steps in the same way "decimal" scaling does not have 11 steps (0-10) it has 9 steps (0-9).

since you are using *100 /1023 you are getting an error and should probably use *101 /x if your aim is to translate any ADC of x steps to 101 equal output units. So use *101 /1024.

WBahn, I hope I made it a bit clearer in the above post to ErnieM, but it is not about mean error it is about getting the correct number of linear output units for the number of linear ADC steps. In the case of *100 /1024 the units and steps scale perfectly in a linear fashion and are rounded down. Using *100/1023 may give lower mean error than rounded down integers (doesn't everything) but the scaling is very wrong.

Consider the example where the ADC has 5 steps only, and a 5v max.

Rich (BB code):
VOLTS	ADCout	*100 /4		*100 /5
0.0v	0	0		0
0.5v	0	0		0
1.0v	1	25		20
1.5v	1	25		20
2.0v	2	50		40
2.5v	2	50		40
3.0v	3	75		60
3.5v	3	75		60
4.0v	4	100		80
4.5v	4	100		80
(5.0v)	5	120?		100
Maybe that helps a bit. If you check the relative error on any step you can see in *100 /4 the low steps (like 0.5v) give an error that is low; 0.5v = 10%, reads as 0%.

But the high steps (like 4.5v) give an error that is high; 4.5v = 90%, but reads as 100%.

So you have a problem where low steps generally read too low, and high steps read too high, and steps near the middle can read either way...

But the mathematically correct example of *100 /5 (*output units /input units) gives a linear error where all output steps are linear and all errors are correctly rounded down to the step integer. making it ideal to correct with the desired rounding, ie; (*100 + 2.5) /5 which gives linear steps where the error on every step is now equally scaled +/- for that step.

As another example of how the scaling needs to be correct, see the last value (5.0v). If it happens that the ADC actually has 10 steps and is running from 10v then the correct scaling *100 /5 still holds true and will be the same, and just as correct for readings 5v and above. So the *100 /5 scaling works perfectly on either 5v 5steps or on 10v 10steps. All the new steps added are the same as the original steps.

But your suggestion of *100 /4 now has considerable error; 9.5v (ADC = 9) * 100 /4 = 225.

But 9.5v (ADC = 9) *100 / 5 gives the correctly scaled value of 180.
 
Last edited:

WBahn

Joined Mar 31, 2012
30,058
You need to consider what is important for the application (information which we are missing here). In almost all cases, the important thing is to minimize the error in our estimates. Now, if you have an application where it is more valuable to always underestimate the true value rather than minimize the magnitude of the error, you would use a different error metric than if it is the other way around.

If you are going to add an offset to correct for rounding down, notice that I dealt with that case and showed that you want to use the number of states. It is when you are NOT doing that as part of your estimation that you want to use a diminished number of states.
 

THE_RB

Joined Feb 11, 2008
5,438
You need to consider what is important for the application (information which we are missing here).
...
I agree we don't have the OP's exact needs. My first post was in responce to ChrisW's blanket statement "it should have been /1023 anyway" which is common thinking but wrong.

...
In almost all cases, the important thing is to minimize the error in our estimates.
...
I agree. To the point of one output unit anyway.

...
Now, if you have an application where it is more valuable to always underestimate the true value ...
...
No you are wrong here the math *100 /1024 does not "always underestimate the true value". It does round down the output to the output integer, BUT you have to remember the input is rounded down by the ADC hardware. In a 10 step ADC all values 9.0-9.9 and rounded down by the hardware itself to "ADC 9" which is then faithfully scaled without error by the math *100 /10 to be 90%, ie absolutely correctly scaled.

If the goal is to spread the error so there is the least error in any sample then the scaling math should not be corrupted to produce a non-linear error (as I said previously with *100 /1023 the lower values have a high - error, the high values have a high + error and the middle values have +/- errors about half the error of the extreme values. So *100 /1023 is a poor solution.

If it is important to reduce the max error in any one sample (and generally it's not that critical to reduce errors less than one output unit) but if it is, then adding an offset allows the scaling to remain correct and linear and gives every output sample an equal error distribution of exactly +/- half a unit.

So the scaling math would be (*output + half input) / input
or (*100 + 512) /1024

*100 /1023 is just a poor solution. Not only are the scaling linearity and error distribution wrong but /1023 takes a lot more machine cycles in processing than +512 /1024 anyway.

And if you wanted to be a stickler for errors <1 unit, then really the ADC hardware rounding should be fixed before the scaling by adding half an ADC count first;
(((ADC+0.5 counts) *100) + 512) /1024
and as micros need to use integer math that could be;
((((ADC*2)+1) *100) + 512) /2048
 
Last edited:

WBahn

Joined Mar 31, 2012
30,058
I agree we don't have the OP's exact needs. My first post was in responce to ChrisW's blanket statement "it should have been /1023 anyway" which is common thinking but wrong.
And I'm merely taking issue with your blanket statement that it should always be 1024.

No you are wrong here the math *100 /1024 does not "always underestimate the true value". It does round down the output to the output integer, BUT you have to remember the input is rounded down by the ADC hardware.
We aren't trying to estimate the "output integer" of the ADC hardware, we are trying to estimate the true value of the input that was applied to ADC hardware. If any voltage between 1V and 2V applied to the ADC produces the same output and we then estimate the input voltage to be 1V, it most surely does underestimate the true value for all input values (except, strictly speaking, exactly 1V, which is a discrete point and occurs with zero probability).

If it is important to reduce the max error in any one sample (and generally it's not that critical to reduce errors less than one output unit) but if it is, then adding an offset allows the scaling to remain correct and linear and gives every output sample an equal error distribution of exactly +/- half a unit.
If you add an offset to the estimator, then you have a different estimator and the scaling constant is dependent on the estimator. It's fine to talk about better estimators, but don't say that estimator A should use constant K because estimator B uses K. If you aren't adding an offset, then you should use the best scaling constant for THAT estimator. If you are trying to achieve MMSE with a uniform (or even Gaussian) distribution of input values, then you would use 1023 (in this case). If you are trying to achieve something else, then the constant you use might be something different than either.

... but /1023 takes a lot more machine cycles in processing than +512 /1024 anyway.
That's a different argument regarding a different issue. I agree that using 1023 is much more computationally expensive (on most MCU's, anyway) and that using 1024 over 1023 might be warranted on the basis of this point even if 1023 were absolutely, positively, and unequivically the correct value. But whether it is or is not more computationally palatable has no bearing on the issue of whether or not it is the correct value.

And if you wanted to be a stickler for errors <1 unit, then really the ADC hardware rounding should be fixed before the scaling by adding half an ADC count first;
That may be desirable, but may not be practical or possible. At some point, the measurement sensor is what it is and we simply have to match our estimator to the sensor (because, in reality, the sensor is part of the estimator).
 

ErnieM

Joined Apr 24, 2011
8,377
WBahn: Very good point. I hadn't considered a "minimum error" algorithm approach, although when possible I do use two points to calibrate a conversion so I can get a "b" coefficient out of the readings.
 

THE_RB

Joined Feb 11, 2008
5,438
...
We aren't trying to estimate the "output integer" of the ADC hardware, we are trying to estimate the true value of the input that was applied to ADC hardware. If any voltage between 1V and 2V applied to the ADC produces the same output and we then estimate the input voltage to be 1V, it most surely does underestimate the true value for all input values (except, strictly speaking, exactly 1V, which is a discrete point and occurs with zero probability).
...
Actually we are, this is not pure mathematics it is a scaling process applied between two imperfect, integer based, hardware systems.

If the rounding down error needs to be removed from the input hardware then applying (ADC + 0.5) removes the rounding down error from the first system and means the data applied to the scaling process is correct. It's an extra process but means that all values have equally distributed error, ie; any analog voltage will be correctly rounded to the nearest ADC integer, apart from discrete points of zero probability of course. ;)

The scaling process is *100 /1024 as this is the only process that produces identical error distribution per integer unit. And as I said we are forced to use integer units.

The ratio scaling is proven correct as if the ADC data is found to be of a larger system (ie 10v 10 ADC steps not 5v 5 ADC steps) there is only ONE correct ratio of input:eek:utput between both linear ratio units of input and output. The ratio input:eek:utput is known. If there was a linear voltage going in and a linear voltage coming out (like a resistor divider) the scaling ratio MUST be input:eek:utput. Any other scaling ratio is wrong.

Using the correct ratio ensures that for all input ADC steps there is an equal error bias. All steps are rounded down to the integer.

If you use an incorrect scaling ratio ie (input-1):eek:utput you have the very serious linearity problem I mentioned (that you have glossed over) where all low input values have an additional - error and all high input values have an additional + error.

I think this is one of those cases where trying to be too clever and trying to handle this as a pure math issue has led you to the wrong solution. There are two imperfect hardware integer systems and the best real world solution is the
(((ADC+0.5 counts) *100) + 512) /1024
solution if you require a properly functioning *linear* ADC result that includes 101 output values (0-100).

I have enjoyed reading your input and seeing your argument about the MMSE and gaussian distribution etc, but I believe linear scaling is a much better solution even if it means the two values 0 and 100 suffer a little in the process. This is not pure math, this is electronics, so if the input waveform varies 4v peak between 0.5v to 4.5v it is important the output values linearly reflects that differential, and not add - error to the low value and + error to the high value which gives output values NOT linearly reflecting the proper ADC voltage differential.

And in many real world cases where we don't care too much about the integer rounding of the final output to the display, then the simplified *100 /1024 still gives the correct linear ADC conversion to output.

Many of the sensors we use with ADC give a voltage linear to temperature or pressure etc, and if the sensor goes up 100'C the display needs to go up linearly 100'C. The non-linear *100 /1023 or *101 /1024 may give "better gaussian distribution" but for a real world ADC device are a poor solution when the correct ratio scaling can be used instead.
 
Last edited:

ErnieM

Joined Apr 24, 2011
8,377
They Might Be Giants said:
A woman came up to me and said
"I'd like to poison your mind
With wrong ideas that appeal to you
Though I am not unkind"
She looked at me, I looked at something
Written across her scalp
And these are the words that it faintly said
As I tried to call for help:

There's only one thing that I know how to do well
And I've often been told that you only can do
What you know how to do well
And that's be you,
Be what you're like,
Be like yourself,
And so I'm having a wonderful time
But I'd rather be whistling in the dark
Whistling in the dark
Whistling in the dark
There's only one thing that I like
And that is whistling in the dark.
 

THE_RB

Joined Feb 11, 2008
5,438
I always cheer up immensely if an attack is particularly wounding because I think, well, if they attack one personally, it means they have not a single political argument left.
-Margaret Thatcher

He who strikes the first blow admits he's lost the argument.
-Chinese Proverb
:D
 

Thread Starter

chrisw1990

Joined Oct 22, 2011
551
wow, this got HORRIBLY in depth:p i think i do kinda get whats been discussed.. damn complicated though, but the basic version seems to have done the job.. interesting on error compensation though but not needed anything that specific as yet thank goodness:)
 

ErnieM

Joined Apr 24, 2011
8,377
It's really not complicated at all. If you want to cut a loaf of bread into two pieces you cut it once. If you wish to divide a range into 1024 intervals you divide it by 1023.

There's no higher math then that to this, no matter how much you stammer and wave your hands.
 

WBahn

Joined Mar 31, 2012
30,058
It's really not complicated at all. If you want to cut a loaf of bread into two pieces you cut it once. If you wish to divide a range into 1024 intervals you divide it by 1023.

There's no higher math then that to this, no matter how much you stammer and wave your hands.

So if I have a load of bread that is one foot long and I want to divide it into 2 intervals, you are saying I divide it by 1. So now I have two pieces of bread that are each 1 foot long? Sounds somewhat biblical. ;-P

It's the classic fencepost problem: The correct number to use depends on whether you want to know how many sections or how many posts.
 
Top