January
2005, Issue 174
Microcontroller-Based
Nitrox Analyer
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.