## HOWTO: Avoid floating-points in (low level) embedded systems

On this forum I attend, there was a guy asking how to scale a read ADC value to be fit for human reading. I'll start out writing the exact situation and then continue with a copy of my story about avoiding floating points.

The user wanted to link a LiPo battery to a microcontroller to measure discharged capacity. The LiPo pack sources up to 4.2V to the circuit.

The used microcontroller allows for 2.5V on it's 10-bit ADC input. To scale the 4.2V to 2.5V or the ADC, a voltage divider is used. The values of the resistors would be 1k and 1.5k, resulting in

Another guy suggests translating the value as follows:

Ow! This hurts! Value (0-1023) divided by 1023 will be 0 unless value is 1023. At least, that's the case for integers! And in a microcontroller you don't want to use floating point!

So, I feel obligated to describe how this is done without making a monster of your code.

We have a value that is in the range 0 to 1023 which actually maps onto a voltage of 4.16V

I stated we don't want to use floating point, so 4.16V is actually 4167mV or 417cV (centivolts)

Would we divide 4166.6 over 1023 the value would be about 4.073. In an integer multiplication this would become 4.

The error this generates is noticably large, since 1023 * 4 = 4092 or 4.092V

If you can settle for a displayed value of 4.09V for an actual voltage of 4.17V then you're done: just multiply by 4 to get the value in millivolts.

If you want higher accuracy, seek for the larges multiplier within the 0 to 2

So, we already determined that the multiplier value is close to 4.073. Let's divide 65536/1024 by that value to see how many times we can fit that in. (65536/1024)/4.073... is about 15.7.

Too bad it fits just under 16 times, since we don't have a hardware accelerated divider we settle for 8 (highest power of 2 that fits under 15.7).

So, we have a value of (4166.6/1023)*8, which turns out to be about 32.55. Too bad this is close to 32.5. But just for the example we'll continue.

A value of 1023 would be 4.16V, so we'll multiply the value 1023 by the rounded value of 32.55, which is 33. The result is 33759. This result is now divided by 8 (23) (since that is the multiple of the constant we just used). 33759/8=4219.875. which rounds down to 4.21V.

4.09V for the formula

4.21V for the formula

Both are about the same. But what I wanted to explain is, using this little bit of math results in way smaller and way faster code, at the expense of a mere 67 millivolts worst case.

Let me add a third calculation. In the second example I showed that the multiplicand is close to 32.5. This means that multiplying it by 2 would get it close to 65.0. This is good, because a smaller discarded fraction results in a more accurate result. The expense here will be that we cannot use 16-bit (native) integer resolution anymore. We could actually expand the resolution to the full 32 bits now, but that's for example 4.

We repeat the calculation of dividing (2

Again we calculate (4166.6/1023)*16, which now turns out to be about 65.1678. This is quite good, since this value is close to 65.

A value of 1023 would be 4.16V, so we'll multiply the value 1023 by 65. The result is 66,495. Notice this value is larger than a native integer can contain on the MSP430; the code will be a little less efficient. This result is now divided by 16 (2

Now, for a last example, since we crossed the 16 bit boundary, let's do this again for the highest value within the 32 bit realm.

Again we calculate (2

Now we calculate again (4166.6/1023)*524,288, which is about 2,135,418.7. The fracion is not close to a whole number, but it gets divided away so heavily that we won't notice it later on.

A value of 1023 would be 4.16V, so we'll multiply the value 1023 by 2,135,418. The result is 2,184,532,614. Now the result is divided by 524,288 (2

4.09V for the formula

4.21V for the formula

4.15V for the formula

4.16V for the formula

An accuracy of about 1.4 microvolt seems awesome, but remember that you're using a 10-bit ADC, so you can be off by 4.16/1024 is about 0.004V or 4mV by the quantisation noise introduced by your ADC! Then we assume the resistors are perfect and the ADC is perfectly callibrated and perfectly linear. Neither of these things is the case.

So maybe just saying that the voltage in millivolts is equal to the value multiplied by 4 is just accurate enough.

**The situation**The user wanted to link a LiPo battery to a microcontroller to measure discharged capacity. The LiPo pack sources up to 4.2V to the circuit.

The used microcontroller allows for 2.5V on it's 10-bit ADC input. To scale the 4.2V to 2.5V or the ADC, a voltage divider is used. The values of the resistors would be 1k and 1.5k, resulting in

*Vo*=*Vi** 1.5k / (1.5k + 1k) with*Vi*being 4.16V and*Vo*being 2.5V.Another guy suggests translating the value as follows:

*You'll have to correct your voltage to be:*

voltage = value/1023*2.5*2.5/1.5;

The maximum you can measure is 4.17V.voltage = value/1023*2.5*2.5/1.5;

The maximum you can measure is 4.17V.

Ow! This hurts! Value (0-1023) divided by 1023 will be 0 unless value is 1023. At least, that's the case for integers! And in a microcontroller you don't want to use floating point!

So, I feel obligated to describe how this is done without making a monster of your code.

We have a value that is in the range 0 to 1023 which actually maps onto a voltage of 4.16V

I stated we don't want to use floating point, so 4.16V is actually 4167mV or 417cV (centivolts)

Would we divide 4166.6 over 1023 the value would be about 4.073. In an integer multiplication this would become 4.

The error this generates is noticably large, since 1023 * 4 = 4092 or 4.092V

If you can settle for a displayed value of 4.09V for an actual voltage of 4.17V then you're done: just multiply by 4 to get the value in millivolts.

If you want higher accuracy, seek for the larges multiplier within the 0 to 2

^{16}-1 (65535) range.So, we already determined that the multiplier value is close to 4.073. Let's divide 65536/1024 by that value to see how many times we can fit that in. (65536/1024)/4.073... is about 15.7.

Too bad it fits just under 16 times, since we don't have a hardware accelerated divider we settle for 8 (highest power of 2 that fits under 15.7).

So, we have a value of (4166.6/1023)*8, which turns out to be about 32.55. Too bad this is close to 32.5. But just for the example we'll continue.

A value of 1023 would be 4.16V, so we'll multiply the value 1023 by the rounded value of 32.55, which is 33. The result is 33759. This result is now divided by 8 (23) (since that is the multiple of the constant we just used). 33759/8=4219.875. which rounds down to 4.21V.

**So an actual voltage of 4.16V is displayed as**4.09V for the formula

*voltage = value * 4;*with an error of 0,06V4.21V for the formula

*voltage = (value * 33u) >> 3;*with an error of 0.043VBoth are about the same. But what I wanted to explain is, using this little bit of math results in way smaller and way faster code, at the expense of a mere 67 millivolts worst case.

Let me add a third calculation. In the second example I showed that the multiplicand is close to 32.5. This means that multiplying it by 2 would get it close to 65.0. This is good, because a smaller discarded fraction results in a more accurate result. The expense here will be that we cannot use 16-bit (native) integer resolution anymore. We could actually expand the resolution to the full 32 bits now, but that's for example 4.

We repeat the calculation of dividing (2

*/1024)/4.073... but with*^{something}*something*being 17 intead of 16. The result is (2^{17}/1024)/4.073... which is about 31.4. This again is just under 32, so we'll go for 16 now (highest power of 2 under 31.4).Again we calculate (4166.6/1023)*16, which now turns out to be about 65.1678. This is quite good, since this value is close to 65.

A value of 1023 would be 4.16V, so we'll multiply the value 1023 by 65. The result is 66,495. Notice this value is larger than a native integer can contain on the MSP430; the code will be a little less efficient. This result is now divided by 16 (2

^{4}) (since that is the multiple of the constant we just used). 66,495/16=4155.9375. which rounds down to**4.15V. Wow, that's close to 4.16V!**Now, for a last example, since we crossed the 16 bit boundary, let's do this again for the highest value within the 32 bit realm.

Again we calculate (2

*/1024)/4.073..., but now for*^{something}*something*we use 32. The result is (2^{32}/1024)/4.073... which is about 1,029,785.5, this is just under 1,048,576 (2^{20}), so we'll settle for 524,288 (2^{19}). These numbers are larger, but the calculations are identical to the previous examples.Now we calculate again (4166.6/1023)*524,288, which is about 2,135,418.7. The fracion is not close to a whole number, but it gets divided away so heavily that we won't notice it later on.

A value of 1023 would be 4.16V, so we'll multiply the value 1023 by 2,135,418. The result is 2,184,532,614. Now the result is divided by 524,288 (2

^{19}): 2,184,532,614/524,288=4166.66529... which rounds down to 4.166665V which is even closer to 4.16V**For a last summary:**4.09V for the formula

*voltage = value * 4;*with an error of 0.06V (about 67mV)4.21V for the formula

*voltage = (value * 33u) >> 3;*with an error of 0.043V (about 43mV)4.15V for the formula

*voltage = (value * 65L) >> 4;*with an error of 0.01072916V (about 11mV)4.16V for the formula

*voltage = (value * 524288uL) >> 19;*with an error of 0.0000013720194498697916V (about 1.4uV)**And a plot twist:**An accuracy of about 1.4 microvolt seems awesome, but remember that you're using a 10-bit ADC, so you can be off by 4.16/1024 is about 0.004V or 4mV by the quantisation noise introduced by your ADC! Then we assume the resistors are perfect and the ADC is perfectly callibrated and perfectly linear. Neither of these things is the case.

So maybe just saying that the voltage in millivolts is equal to the value multiplied by 4 is just accurate enough.

11-'13 Media deflatie

10-'12 Universal Differential Pair

#### Comments

When your setup allows for it, I actually do something close to what you're doing:

All my individual readings are over-sampled at a predefined value, In this example I would choose a slightly larger resistor divider ratio, lets say 2:1.

Now, let's say we have a single measurement answer of 512 bits. This will be equal to 1.25 Volts ADC, and 2.5 Volts in your circuit.

Then we'll do 78 subsequent measurements (let's assume they all give 512 a reading.) and add them all up. The result can be divided by 16 (right shift by 4 bits) which gives 2496.

And that would just be my answer in millivolts.

Now I've only used addition and shift operations, and I'm getting an error of 4 mV. In reality the error can be smaller because you're effectively increasing the amount of bits as well (13-bits) in this case.

All my individual readings are over-sampled at a predefined value, In this example I would choose a slightly larger resistor divider ratio, lets say 2:1.

Now, let's say we have a single measurement answer of 512 bits. This will be equal to 1.25 Volts ADC, and 2.5 Volts in your circuit.

Then we'll do 78 subsequent measurements (let's assume they all give 512 a reading.) and add them all up. The result can be divided by 16 (right shift by 4 bits) which gives 2496.

And that would just be my answer in millivolts.

Now I've only used addition and shift operations, and I'm getting an error of 4 mV. In reality the error can be smaller because you're effectively increasing the amount of bits as well (13-bits) in this case.

[Comment edited on Monday 1 April 2013 16:52]

Ah! Very nice, yes. Instead of using multiplication you could use oversampling.

So assuming the same voltage divider as my example, when doing 4 readings and adding them up you get a result in millivolts with an effective resolution of 10+log

So assuming the same voltage divider as my example, when doing 4 readings and adding them up you get a result in millivolts with an effective resolution of 10+log

_{2}(4) = 12 bits. (still with 67mV error)I've used the method like Infant before too, by oversampling the battery voltage x times so I could just bitshift 'noise' out and get the battery voltage in 0.1V resolution (stored as a byte).

Although it was done a 60MHz ARM7 proccesor (10-bit AD), which could easily handle abit of floating point calculating, it still is nice to reduce noise and avoid floating points.

Although it was done a 60MHz ARM7 proccesor (10-bit AD), which could easily handle abit of floating point calculating, it still is nice to reduce noise and avoid floating points.

**Comments are closed**