Developing a firmware feature can sometimes be painful but writing the same features over and over again is hell on Earth. Most developers want to work on new cutting edge features rather than do the same work repeatedly. A focus on developing portable firmware can help alleviate the issue but in resource constrained systems, the use of optimizations can be high which impede the portability of the code. One trick that a programmer can use to develop firmware that is portable and optimized is to use the preprocessor with compiler macros.
The easiest way to understand the preprocessor trick is to demonstrate it. Take for example a function that takes an array of ten elements and adds seven to each element. The Add7 function, shown in Figure 1, can be optimized for speed by unrolling the for loop which saves performance by not having to check the conditional each time through the loop. Performance will be improved, the code will execute faster but the code size will grow slightly due to the loop being unrolled.
Figure 1 – The Add7 Function to optimize
The compiler may decide to unroll the loop if the compiler optimization levels have been set but if the developer wants to force the speed optimization on the Add7 function, a compiler intrinsic for unrolling the loop will have to be used. The compiler intrinsic will be different depending on the compiler being used. For example, in GCC, loop unrolling can be performed using:
#pragma GCC optimize (“unroll-loops”)
but in Keil the same optimization would be:
#pragma unroll (n) where n is the number of iterations to unroll.
Just by looking at these two pragmas it is obvious that the optimization is not going to be portable. Code that isn’t portable means this function may need to be rewritten or optimized again when the code is reused in a new project. The preprocessor has a trick up its sleeve that can be used to help make the code portable using a simple conditional statements with a compiler specific macro. For example, examine Figure 2 and observe the preprocessor conditional statement.
Figure 2 – Preprocessor compiler conditional
Each compiler comes with a set of predefined macros such as __KEIL__ for Keil ARM to identify the compiler toolchain. A basic list of compiler identifying macros can be found at http://sourceforge.net/p/predef/wiki/Compilers/ but it is hardly exhaustive. A developer should check the compiler documentation for available macros.
Compiler specific optimizations can be placed within the conditional block, improving the portability of the firmware. One of the advantages of using the preprocessor in this way is that the #else block can be used to raise an error if the compiler is not supported for the optimization. There is nothing like the compiler screaming at the developer when the code isn’t portable.
The use of conditional statements and predefined compiler macros is just one simple trick to help improve the portability of firmware. What are some of your favorite optimizations?