Robust C Code Part 2 – Advanced C Preprecessor

I my last post ‘Robust C Code Part 1 – C Preprocessor‘ I talked about the C preprocessor and how it can be used to hide registers from the user without adding code space for lots of functions.  In this post I will talk a little more about the preprocessor and some of the more advanced features.

The C preprocessor also takes arguments, just like functions. Take a look at this example

#define days_in_years(value)        (365*value)

int x,y,x;
x = days_in_years(1);
y = days_in_years(10);
z = days_in_years(65);

This code works similar to the code in the first post, it replaces days_in_years(value) with (365*value), the text ‘value’ is replaced with the argument to result in (365*1), (365*10) and (365*65). The C preprocessor isn’t just a copy and paste tool for strings of text, in addition to direct replacement of text the preprocessor allows concatenation of strings using the ‘##’ directive.  Take the following example:

// Allow PortA, pin 1 to be accessed with led1() macro
#define led1(func)  porta_pin_1_##func

// 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)

This example uses the same port macros as part 1 of this series.  The new line is how to hide register access and name pins in a more developer friendly way.  Read over the next part a few times, I will take it one step at a time.

First the ‘##’ in the macro is the concatenation directive for the preprocessor, it says we should take the argument ‘func’ and replace ‘##func’ with whatever we passed in.  Lets say we have the following in our C code:

led1(somejunktext);

This would replace ‘##func’ in the macro with ‘somejunktext’ and concatenate the text string ‘porta_pin_1_’ with ‘somejunktext’ to make a new sequence of text:

porta_pin_1_somejunktext;

Now, this result is very similar to the format of the pin macros we have listed above, but instead of ‘somejunktext’ the ‘porta_pin_1_’ is followed by ‘output()’, ‘input()’, ‘high()’ and ‘low()’.  Because the preprocessor does direct text replacement you can have these strings of characters as arguments:

led1(output());
led1(high());

This results in:

porta_pin_1_output();
porta_pin_1_high();

Which turns into:

TRISAbits.TRISA1 = 0;
LATAbits.LATA1 = 1;

The really nice thing about using preprocessor string concatenation  is you can switch all references to a pin very quickly with just changing the directive:

// Allow PortA, pin 1 to be accesses with led1() macro (Not used)
//#define led1(func)  porta_pin_1_##
// Allow PortA, pin 4 to be accesses with led1() macro (Now this is on pin 4 and all the code still works)
#define led1(func)  porta_pin_4_##

Now, to really blow your mind, you can use multiple concatenation operators in a single macro, which can allow for lots of macros without the code space required for a function:

#define porta_pin_high(pinnum)       porta_pin_##pinnum##_high()
#define porta_pin_low(pinnum)         porta_pin_##pinnum##_low()

porta_pin_high(1);
porta_pin_high(2);
porta_pin_low(3);
porta_pin_low(4);
porta_pin_high(5);

Multiple concatenation makes the set function I mentioned in part 1 of the series really easy:

#define pin_output(portname,pinnum)       ##portname##_pin_##pinnum##_output()

pin_output(porta,1);
pin_output(portb,2);
pin_output(portb,7);

Take a look at this example of a header file for the PIC24 microcontroller for an idea of what can be done with this technique.

  • By uminded, October 17, 2013 @ 8:58 pm

    /* ———— Macros For Building BitFields ————– */
    #define BIT_SET(ADDRESS,BIT) (ADDRESS |= (1<<BIT))
    #define BIT_CLEAR(ADDRESS,BIT) (ADDRESS &= ~(1<<BIT))
    #define BIT_WRITE(n,ADDRESS,BIT) (n ? BIT_SET(ADDRESS,BIT) : BIT_CLEAR(ADDRESS,BIT))
    #define BIT_CHECK(ADDRESS,BIT) (ADDRESS & (1<<BIT))
    #define BIT_FLIP(ADDRESS,BIT) (ADDRESS ^= (1<<BIT))
    #define BIT_GET(ADDRESS,BIT) (ADDRESS & (1<<BIT))
    /* ——————– Example Usage ————————-
    BIT_SET(PORTB,1);
    if(BIT_GET(PINB,1) == 0) BIT_SET(PORTB,5);
    else BIT_CLEAR(PORTB,5);
    /- ——————————————————— */

    #define SBIT(port,pin) ((*(volatile struct bits*)&port).b##pin)
    /* ——————– Example Usage ————————-
    #define LED0 SBIT(PORTB,4)
    #define KEY0 SBIT(PINB,0)
    #define KEY0_PULLUP SBIT(PORTB,0)

    KEY0_PULLUP = 1;
    if(KEY0 == 0) LED0 = 1;
    else LED0 = 0;
    /- ———————————————————- */

  • By JD, October 18, 2013 @ 1:50 am

    The C preprocessor also takes arguments, just like functions. Take a look at this example

    #define days_in_years(value) (365*value)

    int x,y,x;
    x = days_in_years(1);
    y = days_in_years(10);
    z = days_in_years(65);

    This code works similar to the code in the first post, it replaces days_in_years(value) with (365*value), the text ‘value’ is replaced with the argument to result in (365*1), (365*10) and (365*65).

    And what happens when you do something like this:

    x = days_in_years(my_age + 1); // ?

    x fells short by 364 from what was intended!

    Always wrap macro arguments in parenthesis if you are using them in expressions, that forces evaluation of whatever was passed as argument.

  • By Peter, October 18, 2013 @ 2:37 am

    #define days_in_years(value) (365*value)
    —> this is BAAAAD!!!

    What if you use it like that:
    days_in_years(10+3)

    Always use parentheses (365 * (value) ) arround values!!

  • By Etienne, October 18, 2013 @ 7:14 am

    I think you may have open an interesting discussion about useful macros for embedded development.
    In my PIC projects I have heavily used the following ones (tested with MPLAB c18) :
    #define SET(signal) \
    (signal) = 1

    #define RESET(signal) \
    (signal) = 0

    #define CLEAR(signal) \
    (signal) = 0

    #define TOGGLE(signal) \
    (signal) ^= 1

    #define PIN(port, bitnum) \
    PORT##port##bits.R##port##bitnum

    #define TRIS(port, bitnum) \
    TRIS##port##bits.TRIS##port##bitnum

    #define LOW 0
    #define HIGH 1

    #define OUTPUT 0
    #define INPUT 1

    Then, in your application-specific code, simply use:
    #define LED PIN(A, 5)
    #define LED_DIR TRIS(A,5)

    LED_DIR = OUTPUT;
    LED = HIGH;
    SET(LED);
    TOGGLE(LED);

    Hope these can help some readers.

  • By s1axter, October 18, 2013 @ 8:30 am

    Thanks for the comments. The comments on the parentheses are 100% accurate, variables in macros should be wrapped in parentheses to evaluate any operations before the substitution.

Other Links to this Post

  1. Robust C Code Part 3 – Wrapping C | myBitBox — December 9, 2012 @ 4:31 pm

WordPress Themes