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.
Other Links to this Post
-
Robust C Code Part 3 – Wrapping C | myBitBox — December 9, 2012 @ 4:31 pm
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.