Use fixed-point math for embedded applications
Why is my floating point math so inaccurate?
If you lurk on embedded C forums for much time at all you will run into a question like this: "Why is my floating point math so inaccurate? I do a few operations on the number 10 and I end up with 9.99989 as the result?"
One of the other common forum complaints, especially for the small memory Microprocessors is: "Why does my Floating Point take so much room?" Just adding a single floating point multiply will necessitate the inclusion of at least the basic floating-point math routines, which on a small device can consume a lot of memory. Add a single printf and even more floating-point conversion support needs to be included.
These floating-point operations can take a lot of time also, but interestingly enough that seems to get rarely mentioned on the forums. That would suggest that our microprocessors are fast enough for most human interaction operations anyway, so people don't have as much reason to notice this.
To those of us who grew up on something similar to Apple ][ Basic this is no surprise – we are well familiar with the effects of limited precision floating point. Those who grew up on PCs, with their double precision math and nearly unlimited resources, have never experienced this problem.
The solution to most of these problems is to use fixed-point math instead of floating point .
This would seem to be a big constraint; but if you are clever enough anything can be represented in fixed-point math. My most basic C compiler for microprocessors has support for up to 32-bit integers. Using an unsigned int32 allows values of between 0 and 4,294,967,296 - that's 192 dB of dynamic range and should be plenty for nearly any application.
Some Implementation Examples
One thing I always like to do when possible is to use 12-bit data converters, as that level of resolution works out well for many, many analog control tasks. Even with no oversampling and processing gain applied, a 12-bit data converter yields 72 dB dynamic range. This is way more than most human interaction applications require.
Why 12 bits? The cost is right and if you use a 4.096-volt reference you get a bit scaling of 1 millivolt per bit! How convenient is that? 1 mV is just naturally easy to remember – full scale is then 4095 counts and 4.095 volts you can easily convert to a voltage by placing a decimal point by yourself. This in fact is how fixed point begins – the proper selection of your analog reference and data converter (or just your data) to get a basic unit of your measurement to fit nicely into an integer value.
Sometimes the implementation is a little more difficult. In a VHF receiver that I designed , the tuning was from 144 to 148 MHz in 5-kHz steps. This design used a simple low-cost PLL with a reference frequency or channel spacing of 5 kHz. This works out to be 800 channels, and internally the microprocessor kept track of the frequencies as a channel represented as an unsigned int16. So by simple math I could program the PLL by starting at 0 and then just incrementing each tuning frequency by one channel or count of the PLL feedback divider.
Naturally users don't want to see channel numbers, they want a readout in megahertz with decimals representing the kilohertz tuning portion – like: "144.525 MHz."
With the simple brute force C code segment below I was able to display on the designs internal LCD the frequency the way that the user wanted to see it.
// Calculate actual frequency for the LCD display uint16 freq; freq = Channel*5 + 4000; // Write frequency to LCD display write_lcd("14"); write_lcd((uint8)(freq / 1000) + '0'); write_lcd('.'); write_lcd((uint8)((freq / 100) % 10) + '0'); write_lcd((uint8)((freq / 10) % 10) + '0'); write_lcd((uint8)(freq % 10) + '0'); write_lcd(" MHz");
As you can see – in this design I didn't even have to keep track of the "14" portion of the MHz values as that never changed, it is hard coded. Only the MHz and kHz values changed. No floating point overhead and no math round-off errors occur in this implementation. This is low memory usage, fast and sweet.
With higher frequencies, like the 800-MHz cell phone range, you will probably need to use a dual modulus PLL or perhaps even a fractional N type. Even though the programming algorithm is more complex with these types of PLLs the same basic principles apply.
I always sit down with a notepad or spreadsheet and make a programming table with the frequencies required as an input and the programming bits required as an output.
There is always a repeating pattern that can be found; then this pattern can be implemented in fixed-point code because the programming bits to the IC are always integers themselves at their most basic level.