SAE Teensy ECU
IIT SAE Microcontroller programming
|
This Page helps explain how X Macros are used in this project.
There are resources linked that can also explain their usage however this page hopes to explain their usage in this project.
The basic idea behind X Macros in this project is that, using the C preprocessor, we can essentially generate code by just using definitions.
In this project, X Macros have two parts, the defining part, and the generating part.
The defining part mostly occurs in .def
files, the X macro definition can look as such.
If you are inexperienced with macros, know that the \
at the end of each line just means that this is a multi-line macro.
This means the final macro actually will look like this.
\
, as the \
tells the preprocessor that there is another line it should append to the same define, just take note of that on the example NAME_OF_X_MACRO
.Each line after the define must call a non existent macro simply called X
.
Technically it does not have to be called X
but that is what this project uses.
Note that each line gives the same number of arguments and those arguments also are of the same data type respectively.
Using the right generating techniques, the number of arguments and their data types would not matter, however this project does do that.
Using NAME_OF_X_MACRO
, we can interact with it's calls to X
in many different ways.
We know that the first parameter of each macro is an integer, and the second a string. Say we want to log each of these macros. Using Log_t it would look as such.
Note that we first define X
, call the NAME_OF_X_MACRO
macro, and then undefine X
with #undef
.
In the preprocessor, NAME_OF_X_MACRO
will expand as follows.
Because the macro X
exists after we have defined it, each line will now expand to whatever X is as such.
So essentially, after the preprocessor, the final code before compilation will look like this.
And, as you can hopefully see, this means we can generate a lot of code using just some input parameters.
The following sections discuss generation techniques used in this project that may be confusing to some.
One technique that is used throughout this project that may be confusing is this method of generation.
Using our previous definition of NAME_OF_X_MACRO
, LOG_COUNT
will expand to 3.
This is done by using the macro PP_NARG_MO
, which simply counts how many parameters are passed to it. This macro is defined in PPHelp.h.
However, to the preprocessor, each parameter technically does not need to actually exists, simply putting a ,
means that there is a parameter.
Therefore, the next expansion of LOG_COUNT
would look as such
This tells the preprocessor that it should pass 4 arguments and PP_NARG_MO
counts this. Also note, however, that we have three calls to X
, not 4. There will always be one extra, so PP_NARG_MO
ensures that it removes one each time this happens.
Note however of a limitation, PP_NARG_MO
technically is not counting, the only thing one must know is that PP_NARG_MO
currently has a limit of 64
parameters. This can be increased however I am too lazy and I also have not ran into any issues with this limit.
Mainly used in Pins.cpp, this generation method uses macro concatenation to build the name of a function to then call it.
This example is part of the source code for Pins::getPinValue()
Note that the ...
just means there is other code in between the parts we are looking at that we do not care about for now.
The main definition to take note of is ECU_PINS
on line 19
.
the X Macro definition on line 18
takes in a pin number, a type, whether this pin is input or output, and the initial value of the pin.
An example call to X
is as follows
Pin 45 will be read as digital input, with no initial value as it is input.
Out current definition of X
at line 18
would interpret this as follows
Which, looking at line 0
, would be expand to
Where Pins::getPinValue(), after the preprocessor, would end up looking like this
If our call to X
was instead this
It would expand to __READPIN_ANALOGOUTPUT(10)
However, because we do not read from output pins, __READPIN_ANALOGOUTPUT()
is an empty macro, meaning nothing is done inside Pins::getPinValue()