Search This Blog

Sunday, February 24, 2013

Floating Point Arithmetic


If you want to multiply a number by a fixed floating point constant, here is a
pretty efficient method.

The example that brought this up was trying to measure the distance from the Ping ultrasonic sensor.  On trigger, the Ping sends out a pulse.  200 us later, you can start timing, waiting for the trigger pin to go low.  This yields a value between 0-18500 at 4MHz clock, and it corresponds to the number of microseconds ( 1 per clock pulse ) that the round trip of the ultrasonic burst took to hit and come back.

To get the distance, you first divide this by 2 to get the single trip value.

Sound travels about 343.2 meters/second.  That is 34320 cm/sec.  Invert this to 2.91375e-5 * 1e6 = 29.13 microseconds per cm.  That means we need to divide the time value by 29.13 microseconds to get the distance. Another way to divide by 29.1375 is multiply by 1/29.1375 = .03432.

Well, that doesn't help much does it?  Here's the trick.  You can create something called a "fixed point" to simplify the math.  To do this, we figure out how to represent .03432 in a 16 bit number and remember the offset.  So, multiply .03432 * 65536 = 2249.19552.  Round to 2249.  We've used a 16 bit offset, so we will have to shift things back 16 places at the end.

Now, in C, you can assign things like this for our Ping sensor:

unsigned int sensorRaw = 18500;  // the 2 way time measured.  This is the max value.
unsigned long cm = 2249; // this is our fixed point, calculated above.
unsigned long distanceCm; // this will hold the distance in cm.

// first, get the single trip time by dividing by 2 ( shift right one ).
sensorRaw = sensor >> 1;
// now, multiply by our fixed point
distanceCm = sensorRaw * cm;

Doing it this way takes about 128 us at 4MHz.  It seems consistent in time for all numbers I tried.  Doing a raw multiply of sensorRaw * .03432 took anywhere from 220us to 450us.  Pretty good improvement.  You could probably get this down to under 30 us
if you did it in assembly with the hardware multiplier.  The 16 bit shift just means you only use the two upper bytes of the 32 bit answer.  The algorithm for this is in the datasheet for the 18f2455 and takes 28us at 4MHz.  128us was good enough for me!

Here is the original quote that helped me from the PicList from Byron A Jeff:

You may want to think about fixed point. You can create a fixed point number
simply by taking the original number and multiplying it by the range that
you wish to represent it. For example if you wanted to represent .1 in 16
bits, take .1 and multiply it by 65536 giving 6553.6. Round to 6554. Store
this in 16 bits and use a 16 bit multiply followed by a 16 bit right shift
(which means simply leave off the lower 16 bits).

Here an example. Say we wanted to multiply 20 by our .1. So multiply 20 by
6554 giving 131080. Then shift 16 bits, which effectively divides by 65536.
The result of 2.0001220703 is close enough for govt. work

No comments:

Post a Comment