Robust C Code Part 1 – C Preprocessor

For years I tried to write C code for microcontrollers that was robust, modular, and reusable  with limited success.  It always seemed that there was some requirement that caused a module to become dedicated to a specific piece of hardware or switching to another processor caused hours of reworking register bits.  Over the last few years I was exposed to some really cool ways to make C code more robust and flexible which allows code reuse and reduces fat-finger errors of setting the wrong bits.  I wanted to create a series of posts on this and one of the first places to start is using the C preprocessor to make accessing hardware more user friendly.

Microcontroller development differs from operating system development in C in many ways, one of which is accessing hardware directly from the C language. Each microcontroller has special hardware that interfaces with the real world, for example a general purpose IO pin is used to interact with the real world using logic signals (high and low). Accessing the port pins differs wildly from processor to processor, to access the PortA pin 1 of a Microchip PIC24 chip in C would look something like this:

// Make PortA pin 1 an output
TRISAbits.TRISA1 = 0;
// Make PortA pin 1 a high value
LATAbits.LATA1 = 1;

This can look a little confusing to some, it would be much easier if there was a function that the developer could call, for example pin_output(porta,1), which would do this for you.  Now you could make a function that does that, however since microcontrollers usually have limited FLASH and RAM, making functions for each register and bit can get out of hand quickly.  The solution is the C preprocessor. The preprocessor uses ‘directives‘ which are like instructions that get run before the compiler. Some directive examples are #define, #include, and #pragma. The #define directive replaces sequences of text in C sources.  If you have ever wrote basic C the following macro will look familiar:

#define DAYS_IN_A_YEAR    365

The C preprocessor searches for the sequence of characters DAYS_IN_A_YEAR in the C source and replaces it with the numbers 365.  No checking is done, so having “#define bit 44” would replace the characters ‘bit’ with 44, which can result in the previous example turning into:

// Make PortA pin 1 an output
TRISA44s.TRISA1 = 0;
// Make PortA pin 1 a high value
LATA44s.LATA1 = 1;

Since there is no TRIS44 register in the PIC24, this won’t compile.  However you can use the direct replacement of the preprocessor to make setting bits very easy to remember and look like functions.  Take the following preprocessor #define directive for example

#define days_in_a_year()    (365)

This replaces days_in_a year() with the characters (365).  This is called a macro. Now you can have some C code that does the following

int x;
x = days_in_a_year();

days_in_a_year() is a macro, not a function, it looks like one, but when the C preprocessor runs you get the following:

int x;
x = (365);

No function code is developed and the code will (most likely) compile to a single instruction or two, to move the value 365 into the variable x. The same ‘function’ trick can be done with the port pin assignment mentioned above.  Take a look at these preprocessor directives

// PIC24 PortA pin 1 register macros
#define porta_pin_1_output()               (TRISAbits.TRISA1 = 0)
#define porta_pin_1_input()                (TRISAbits.TRISA1 = 1)
#define porta_pin_1_high()                 (LATAbits.LATA1 = 1)
#define porta_pin_1_low()                   (LATAbits.LATA1 = 0)

// Make PortA pin 1 an output
// Make PortA pin 1 a high value

If you put the directives in a header file and include it in your code, you will never have to set a bit register again.  Also, if you move processors, lets say to an Atmel Atmega, you can change the preprocessor directives and all the code is changed automatically:

// Atmel Atmega PortA pin 1 register macros
#define porta_pin_1_output()               (DDRA.1 = 1)
#define porta_pin_1_input()                (DDRA.1 = 0)
#define porta_pin_1_high()                 (PORTA.1 = 1)
#define porta_pin_1_low()                   (PORTA.1 = 0)

// Make PortA pin 1 an output
// Make PortA pin 1 a high value

That’s it for this post, there is MUCH more you can do with C and the preprocessor, so check back for some more posts in the series.

  • By Dmitry, December 8, 2012 @ 12:19 am

    Thnx! good idea,i found it useful. do you have own C library with such defines?

  • By s1axter, December 9, 2012 @ 7:38 pm

    Yeah, I have headers for different hardware using the same techniques. I use a port.h file for all the #define for pins and ports. It comes in really handy for turning port analog functions on and off.

  • By Zaid, October 18, 2013 @ 4:54 am

    Cool ! this series is amazing for embedded systems engineers .. you could easily manage code with these techniques and build portable systems !
    Thanks alot.

  • By Joe Pitz, October 18, 2013 @ 12:11 pm

    s1axter, very cool article, I have always known about function like macros, but the use of them to shorten register and port access is a great idea.

    One minor point, when passing arguments to function like macros, you always want to parenthesize the parameters to avoid parameter expansion problems, especially if your parameters contain expressions.

    I loved the article.



Other Links to this Post

  1. Robust C Code Part 2 – Advanced C Preprecessor | myBitBox — December 1, 2012 @ 9:04 am

WordPress Themes