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.