5. Macro Names
Macros are normally ALL CAPS with words separated by underscores. Occasionally a function is macro-ized to force inlining, or when it is otherwise replacing or substituting for a function of similar name, it may be camel-cased similar to application functions described elsewhere. When this is done, the macro name is always prefixed by lower-case ‘m’. See below.
Exception
When macros are used to map old API names to new (changed) names so as not to break existing code — then, since C is case sensitive, the macro must match the case of what ever it is catching and converting (in legacy code) to a new name. Example:
#define printf special_printf
5.1. Macro Name Parts
Macro names follow this pattern:
[p][m]<file>[__<category>]__<precise_name>
where:
- [p]:
Project-scope macro prefix ‘p’ used when macro is defined in the project, not in source code. Example: pmSA6_PCB.
- [m]:
Macro prefix ‘m’. When used, it is to make clear in source code that the name is a macro and not a function or variable or other type of identifier. This is important when a macro may otherwise look like a function, as described above. This is not common, but it is important for visibility.
- <file>:
The name (or an abbreviation) of the FILE they are contained in. If <file> contains multiple words, they are separated by single underscores.
- [__<category>]:
When present, it gives context to the meaning of a macro name (see examples below), and often groups a set of related macros together. If <category> contains multiple words, they are separated by single underscores. See below for more information about <category>.
- <precise_name>:
If this name contains multiple words, they are separated by single underscores. Precision names are important when reading code. Example: a macro such as NANDFS__SPARE_AREA_SIZE is ambiguous. The ambiguity becomes clear at once when you realize that the NAND chip uses a 16-bit bus, and so size definitions, even in the NAND chip’s Data Sheet, are expressed in 16-bit words! Such values are also often converted to byte indexes, or 16- or 32-bit word indexes or pointer offsets, etc. So seeing code like this:
for ( i = 0; i < ( mS34MLXG1_ECC__PAGE_TOTAL_SIZE >> 2 ); i++ ) { data = gui32Buf[i]; WRITE_LONG( data ); }you can clearly see that it is impossible to tell by looking at this loop that there are no bugs in it! Even if you know that the NAND chip’s total page size is 2112 bytes (or 1056 16-bit words), it is still not possible to tell, because you don’t know the UNITS of the S34MLXG1_ECC__PAGE_TOTAL_SIZE expression. Whereas with:
for ( i = 0; i < ( mS34MLXG1_ECC__PAGE_TOTAL_SIZE_IN_BYTES >> 2 ); i++ ) { data = gui32Buf[i]; WRITE_LONG( data ); }you can clearly see that there are no bugs. This is THE reason for having precise names!
Note that TWO underscores are used to visually separate the parts above. The reason
for this is that <file>
, <category>
and <precise_name>
can themselves
contain underscores. Thus the 2 underscores together serve as a visual separator
since these parts of the macro name are conceptually separate.
The following are examples of a set of macros that relate to the GPS_TX pin on a printed circuit board (PCB). “GPS_TX” is the name given to a microcontroller pin in the PCB’s schematic — a pin used to send data from a PIC microcontroller to an external GPS device.
#define mGPS__GPS_TX__TRIS TRISFbits.TRISF5
#define mGPS__GPS_TX__TRIS_SET_REG TRISFSET
#define mGPS__GPS_TX__TRIS_CLR_REG TRISFCLR
#define mGPS__GPS_TX__TRIS_MASK _TRISF_TRISF5_MASK
#define mGPS__GPS_TX__TRIS_SET_INPUT_MODE() (mGPS__GPS_TX__TRIS_SET_REG = mGPS__GPS_TX__TRIS_MASK)
#define mGPS__GPS_TX__TRIS_SET_OUTPUT_MODE() (mGPS__GPS_TX__TRIS_CLR_REG = mGPS__GPS_TX__TRIS_MASK)
#define mGPS__GPS_TX__PIN PORTFbits.RF5
#define mGPS__GPS_TX__PIN_IS_SET() (PORTFbits.RF5)
#define mGPS__GPS_TX__PIN_IS_CLEAR() (!PORTFbits.RF5)
#define mGPS__GPS_TX__LATCH LATFbits.LATF5
#define mGPS__GPS_TX__LATCH_SET_REG LATFSET
#define mGPS__GPS_TX__LATCH_CLR_REG LATFCLR
#define mGPS__GPS_TX__LATCH_INV_REG LATFINV
#define mGPS__GPS_TX__LATCH_MASK _LATF_LATF5_MASK
#define mGPS__GPS_TX__LATCH_SET() (mGPS__GPS_TX__LATCH_SET_REG = mGPS__GPS_TX__LATCH_MASK)
#define mGPS__GPS_TX__LATCH_CLEAR() (mGPS__GPS_TX__LATCH_CLR_REG = mGPS__GPS_TX__LATCH_MASK)
#define mGPS__GPS_TX__LATCH_TOGGLE() (mGPS__GPS_TX__LATCH_INV_REG = mGPS__GPS_TX__LATCH_MASK)
#define mGPS__GPS_TX__LATCH_IS_SET() (LATFbits.LATF5)
#define mGPS__GPS_TX__LATCH_IS_CLEAR() (!LATFbits.LATF5)
etc.
Note that we use parentheses at the end of macro names when they either execute code, or return a value, even when there are no arguments. This helps clarify the fact that they are expected to execute code or return a value, and this aids programmer understanding.
As you can see, the <category> here groups these macros together, and gives clear
layers of context to this group of macros, indicating that they are all
related to a particular microcontroller pin named GPS_TX
, and are probably part
of a larger group of macros (hinted at by the higher layer of context in the macro
names: mGPS__
) that deal with all pins that contect to an external GPS device.
This also allows for shorter <precise_name> parts, which, given the context provided
by <category>, can be reduced to a clear expression WITHIN that context. Note that
names like PIN, LATCH, LATCH_MASK, etc. are used for ALL PIC microcontroller GPIO
pins and have specific meanings in that context.
The context provided by <category> is similar to “instance” variables in a
struct, or instance-scoped variables in a C/C++ object — the struct or class
provides the context, and so simplifies, and clarifies, the variable names, e.g.
gUserConfiguration.iui8GearCount
, not
gUserConfiguration.iui8GearCountConfiguration
.
In reality, there CAN be cases where <category> — that provides context to the meaning — may have more than one clear “layer” within it. Example from Texas Instruments Sitara-family SoC’s, especially when source code can be used across several SoC models within the family where such SoC’s have individual areas on the silicone that each have different power requirements (which they call “power domains”). In this case, <category> COULD be comprised of multiple layers of context as follows:
<power_domain>__<peripheral_name>
or even
<SoC_model>__<power_domain>__<peripheral_name>
As you can see, from left to right are wider-to-narrower contexts. When this is the case, <category> MAY have multiple parts (as above), each separated by two underscores (“__”). This makes “__” in reality, a context-layer separator, and indeed, that is its purpose as it lends to better understanding of the meaning. So a name like the following is perfectly legal (AM335x is a sub-family within the Sitara SoC group):
mGPS__AM335x__WKUP__UART0__RX_PIN_LEVEL
5.2. Using Macros to Force Code Inlining
Finally, there are times when forcing code in-lining with a macro is preferable to providing a function for the same operation, in order to do 2 things:
In an ISR, it can be important at times NOT to call any external functions. The reason for this is that the compiler can often shorten the prologue code (code that preserves CPU registers used in the ISR) when it can know what registers will be impacted by the ISR. When an ISR makes an external function call, that certainty can no longer be had by the compiler. So avoiding external function calls within ISRs can be important to ISR efficiency (using as little CPU time as possible in an interrupt). When the compiler cannot know the registers that will be affected, the prologue code to an ISR must preserve ALL registers (very expensive for a PIC32, which has 32+ registers that must be preserved in the generated ISR prologue assembly instructions). Some PIC32s have floating point hardware in which case it is 64+ registers that need saving! Whereas if it CAN know the registers affected by an ISR (when it makes no external calls), the number of instructions in this prologue can be greatly reduced (sometimes to just a handful), and XC compilers (among others) take advantage of this.
For some CPUs (dsPIC33EP is such an example), a call, and its return assembly instruction are very expensive in terms of CPU time, and cause the whole internal hardware instruction processing line (that which normally reduces effective instruction time for Microchip PIC to 1 SYSCLK cycle) must be discarded and so it takes that many SYSCLK cycles to re-load it again after the change of the instruction pointer (IP) register. For the dsPIC33EP for example, it costs 4 SYSCLK cycles to make the call, and 4 SYSCLK cycles again to return from that call, whereas keeping the instruction pointer within a block of code reduces the required time to execute simple operations, such as boolean tests like the following examples.
For these cases, macros are created in the applicable module .H file (when they are to be exported as part of the client API) or the .C file (when they are private to that module) and look like the following examples. Note the name of this module is “Logger State Machine” — abbreviated down to “LSM” for the “function” prefix, but they are really macros and so are also prefixed by ‘m’):
#define mLSM_qboolLoggingInProgress() \
(( giLoggerState == eLoggerState_logging ))
#define mLSM_qboolFlashDriveIsMounted() \
(( giLoggerState > eLoggerState_mounting_drive ))
#define mLSM_qboolSafeToPowerOff() \
(( (giLoggerState <= eLoggerState_waiting_for_ok_to_log) \
&& (giLoggerState != eLoggerState_loading_configuration) ))
Then these can be used like this in time-critical sections of code like this:
if (mLSM_qboolSafeToPowerOff()) {
/* Do something here */
}
Further Reading