circuitcellar.com
Magazine Support   Digital Library   Products & Services   Suppliers Directory 
 
 





 

January 2005, Issue 174

Microcontroller-Based Nitrox Analyer


by David Smith


FIRMWARE

I wrote the AT90S4433’s firmware in C and compiled it with an ImageCraft C compiler. The structure is the typical foreground-background paradigm. The majority of the code is executed in the foreground, with interrupts performing specific functions in the background, such as timeout management. Because power consumption is an issue with battery-powered devices, the firmware places the microcontroller in a low-power, idle Sleep mode during delays.

The calculations are performed with fixed-point arithmetic, which is a method of representing numbers with a fractional component as integers (using programmer-specified precision). This method is beneficial because it’s treated as integer math by the compiler and the microcontroller, which means floating-point libraries don’t need to be linked in. This saves a substantial amount of valuable code space. 

Fixed-point math has two main shortcomings. First, it creates the burden of manually keeping track of the decimal location during calculations. Second, it requires you to know the exact range of the results of all the calculations in order to maximize the precision and to prevent numerical overflow. Early in the project, I experimented with floating-point math and quickly realized that there wouldn’t be enough code space to accomplish what I wanted without switching to fixed-point math. That’s when the project became a personal challenge to wring out as much functionality as possible from the AT90S4433’s 4 KB of code space.

To understand what’s involved in fixed-point computations, compare Listing 1, which is a fixed-point version of the statement that determines MOD, to its floating-point equivalent. Obviously, the floating-point expression is easy to read. The MOD is 46.2 divided by the percentage of oxygen minus 33. The result is then typecast to an unsigned short, which results in the depth being truncated to an integral number of feet. 

The fixed-point version is more difficult to understand. During the compiling process, the C preprocessor converts ((ulong)46.2*10000)<<12) to a 4-byte constant value of 1.892352 × 109. This represents 462,000 with an implied decimal point between bit 12 and bit 11. The usPercentO2x10000 variable is the percentage of oxygen scaled by 10,000. Because fixed-point calculations are integer-based, fractional values must be manipulated so they can be represented as integers with enough bits to ensure that calculations are performed with precision. For example, 32.6% oxygen scaled by 10,000 results in 3,260, which can be stored in usPercentO2x10000. Next, usPercentO2x10000 is typecast to be 4 bytes long and divided into 1.892352 × 109. The result is shifted 12 bits to the right to move the implied decimal point to just before bit 0 and then typecast to an unsigned short. Finally, 33 is subtracted from the value to obtain the depth truncated to an integral number of feet.

Another technique I employed to reduce the size of the compiled executable was based on analysis of the assembly output produced by the C compiler. I found that combining multiple statements of a complex calculation into a single C statement significantly reduced the amount of code generated by the compiler. This is largely due to the elimination of many unnecessary register loads and stores that were occurring as values were swapped in and out of SRAM. Specifically, this technique resulted in an 18% reduction in the code produced for one particularly large calculation.

Another method I used to conserve code flash memory was to relocate the LCD text strings to EEPROM. This freed up an additional 244 bytes for the executable. Although this may not sound like much, it represents around 6% of the microcontroller’s entire code flash memory space.