Products > Programming

doing math, scaled up integer or float? and pre-processor stuff

(1/15) > >>

Simon:
I am working with the ADC on an AVR. I am trying to get a result in a number that makes sense so that defines can be written in say mV. So the easy sounding solution is to work in float arithmetic but my guess is that this will mean adding a library I may not have room for and lots of CPU cycles.

Alternatively I can work in scaled up 32 bit integers. To get the actual number in mV (or mA) bearing in mind any scaling due to say input resistor dividers I tried to do:

But this turns out inaccurate for low values due to the division. With 13 bits, a reference of 1177 and no hardware scaling anything less than a result of 7 will produce 0 and I will of course not be retaining my resolution as every 7 counts will produce just 1 mV/mA. When I use a voltage reduction of 40 and ADC result of 1 count will produce a result of 5.75 with 0.75 dropped so any final value counts between multiples of 5 or 6 will be useless as well.

I am forced to use 32 bit variable math anyway so wy not multiply everything by 1024 and then divide it by 1024 at the end.

("ADC result" x "ADC reference voltage in mV" x "input stepdown ratio" x 1024 / "ADC maximum count") / 1024

So for 1 count:

(1 x 1177mV x 1 x 1024 / 8191) / 1024 = 0.143 so back to square one.

So this means that I need to work in µV / µA resolution of calculations while inputting mV /mA and not re-divide by 1024. I use 1024 for obvious reasons: 2^10, as a power of 2 it will be faster to calculate or does this not matter with the hardware multiplier as I am now not dividing by this number.

The problem now is that I have exceeded my 32 bit integer limit. Even if I take out the possible multiplication by the stepdown ratio so that I get ADC value µV rather than input to the resistor divider µV I am at 9x10^9.

This now gives a single count resolution of 147 µV which is not far off but I have tipped into 64 bit integer math so may as well work at nV resolution 😂.

If I look at this in the wider context my ADC reference will be at most 5'000 mV. That means that given 13 bits of resolution to stay within a 32 bit integer I cannot multiply by more than 100 (104), either as input resistor ratio divider or as a simple multiplier to get rid of rounding errors. Of course I could just multiply by 100 anyway and then multiply the final result by the input divider ratio as I have achieved all the resolution I ever will with 32 bit math - 10µV math with a voltage reference of 5V and 13 counts.

Effectively to get the most accurate result I need to have some random multiplier that is 2^32 / ( ADC counts X ADC Vref). This will get the finest result possible and will cater to input ratios up to that value. I then need to multiply any thresholds (in mV) by this value or divide them by this value for outputting visually so that they are in the same scale and all this can be worked out in the pre-processor - ah but that does not do math.....

So this is where I also fell down and was running around putting UL on the end of numbers that were something like "50" in order to try and cast the math operations.

So how do I tell the pre-processor or the C compiler via the pre-processor defines what bit size to do the calculations in? Other that UL I don't know anything else.

I also started to resolve this by casting at least one variable in 32 bit or just the entire operation:

value = (uint32_t) (the whole math sequence as above) ;

Should I cast each variable?, or is that unnecessary and only going to slow the thing down?

Or to avoid all confusion do I just create a variable for any definition, the defines simply keep things neat and tidy in the header file.

Have I just recreated floating point math? or will this be more efficient as I am solving one scenario not the whole world?

RandallMcRee:
Simon,
You are reinventing so-called fixed point math.
You might want to google it.  There a number of libraries available but typically it’s overkill, unless you are like simulating physics in a game controller or some such. The concepts are what you need.

So I did  exactly what you are mentioning for a 24 bit adc.  As a check I wrote all the arithmetic stuff twice once in fixed point and once in 64 bit double and made sure that all fixed routines agreed for 2^^24 inputs. Of course the 64 bit routines are not included in the microcontroller.

I can show some examples if you think it will help.

Randall

nctnico:
If speed and code size aren't an issue just use float.

Other than that using fixed point math is mainly about retaining resolution. So multiply first and then divide. But you also need to make sure not to cause an overflow. Circling back to your problem: since the ADC range, reference voltage and input divider ratio are likely fixed, you can create a single number which represents the maximum value (in millivolts) for your input. Say you have a 10V input that translates to 10000mV and an ADC range of 8191 (13 bits). You can reduce the calculation to:

mVolt = (adc reading * 10000) / 8191. Worst case 10000 * 8191 = 82e6 which easely fits into a 32 bit integer. For as long as the number of milli-Volts (or whatever unit you are using) is great than the ADC range, this won't lose resolution. In this case 10000 is greater than 8191 so that requirement is met.

For better rounding you can change it to:

David Hess:
Fixed point math would be my first choice, but you need to keep track of the radix yourself.  (1) Shift the converter result up as far as possible consistent with overflow requirements.

If the result is suppose to be human readable, then there is a case for using binary coded decimal (BCD).

(1) Some C compilers have support for fixed point math and track the radix automatically, but they are nonstandard and only for DSP processors that I know of.

Simon:

--- Quote from: RandallMcRee on September 04, 2021, 09:05:20 pm ---Simon,
You are reinventing so-called fixed point math.

--- End quote ---

I know, but I am hoping to be optimised for resolution and speed.

--- Quote from: nctnico on September 04, 2021, 11:24:03 pm ---If speed and code size aren't an issue just use float.

Other than that using fixed point math is mainly about retaining resolution. So multiply first and then divide. But you also need to make sure not to cause an overflow. Circling back to your problem: since the ADC range, reference voltage and input divider ratio are likely fixed, you can create a single number which represents the maximum value (in millivolts) for your input. Say you have a 10V input that translates to 10000mV and an ADC range of 8191 (13 bits). You can reduce the calculation to:

mVolt = (adc reading * 10000) / 8191. Worst case 10000 * 8191 = 82e6 which easely fits into a 32 bit integer. For as long as the number of milli-Volts (or whatever unit you are using) is great than the ADC range, this won't lose resolution. In this case 10000 is greater than 8191 so that requirement is met.

For better rounding you can change it to:

--- End quote ---

Yes exactly. All you have done is taken the reference voltage out which looses track of what the number means. What I am looking to do is take into account the current reference voltage and then see how much more I can multiply the value by to maximise resolution. This way I actually know the difference between the scaling factor and the true value in mV. All these numbers are #define'd so they can be multiplied together by the compiler in optimizations assuming the XC8 compiler will do this much optimization without microchip wanting their pound of flesh. One day I will get the GCC compiler working in MPLABX.

Oh yes adding half the dividing number to the value before dividing is a neat idea, if I get a remainder of "0.5" it will tip it to "1", nice one.

Currently