cmacros

using macros to write #defines in C


I have a need to use a macro to write #defines and other macros in C for an embedded MCU.

With the following definitions:

    // Define the pin state as wired to the board, CA (PIN controls -ve lead) or CC (PIN controls +ve lead)
#define CA_ON           LOW       // Common Anode LED ON when PIN is LOW
#define CA_OFF         HIGH       // Common Anode LED OFF when PIN is HIGH
#define CC_ON          HIGH       // Common Cathode LED ON when PIN is HIGH
#define CC_OFF          LOW       // Common Cathode LED OFF when PIN is LOW

// LED ON/OFF MACROs for Common Anode (CA) and Common Cathode (CC) LEDs
#define CA_LED_ON(PIN)   digitalWrite(PIN, CA_ON)   // Set the LED ON for Common Anode
#define CA_LED_OFF(PIN)  digitalWrite(PIN, CA_OFF)  // Set the LED OFF for Common Anode
#define CC_LED_ON(PIN)   digitalWrite(PIN, CC_ON)   // Set the LED ON for Common Cathode
#define CC_LED_OFF(PIN)  digitalWrite(PIN, CC_OFF)  // Set the LED OFF for Common Cathode

I currently have something such as the following lines that need to be repeated:

#define PIN_LED_RED 32
#define RED_LED_ON CA_LED_ON(PIN_LED_RED)
#define RED_LED_OFF CA_LED_OFF(PIN_LED_RED)
//
#define PIN_LED_BLUE 19
#define BLUE_LED_ON CC_LED_ON(PIN_LED_BLUE)
#define BLUE_LED_OFF CC_LED_OFF(PIN_LED_BLUE)
//
#define SET_BLUE_LED   pinMode(PIN_LED_BLUE, OUTPUT);

I would like to write something similar to

#define LED_CONFIGURE(ID, PIN, CA_CC) \
  #define PIN_LED_##ID PIN \
  #define ID##_LED_ON CA_CC##_LED_ON(PIN) \
  #define ID##_LED_OFF CA_CC##_LED_OFF(PIN) \
  #define ID##_CONFIGURE_LED(PIN) \
    pinMode(PIN, OUTPUT); \

Thus I can just write one line for each LED:

LED_CONFIGURE(RED, 32, CA)
LED_CONFIGURE(BLUE, 19, CC)

The pre-processor balks at the '#' symbol within a macro.

Any C MACRO Gurus know how to get around this?


Solution

  • I have a need to use a macro to write #defines and other macros in C

    That doesn't work.

    The replacement list in a macro definition cannot, in the definition itself, have the form of a macro definition because preprocessor directives must not be preceded by other tokens on their logical line. And a result of macro expansion is never interpreted as a macro definition or any other kind of preprocessor directive because the spec explicitly says not (C23 6.10.5.5/3).

    I'm inclined to think that your best option is probably to not define macros RED_PIN_ON etc at all. It's not clear to me that writing, say,

    RED_LED_ON
    

    , is significantly better than writing

    CA_LED_ON(PIN_LED_RED)
    

    , which your existing macro stack already supports. If you want to have those slightly shorter macros then you will need to define them all individually. You could probably write a code generator to produce the definitions for you, but that seems like a lot more trouble than it's worth.

    Addendum

    From a comment:

    I'm trying to write the code to require the least amount of changes when compiled for different MCUs and boards. From my experience, seeing RED_LED_ON in the code is clear, how and where it's connected is important, that's only needed to be defined once. I can put board/MCU specific definitions in different header files and/or use conditional compilation without impacting the logic part of the code.

    You do not need macros to define other macros in order to apply macros to those objectives. Instead, you can let macros expand differently according to the definitions of other macros. For example, you might like something along these lines:

    //////
    // Hardware specific definitions:
    //
    
    // Pin type and number for each LED
    
    #define PIN_TYPE_RED  CA
    #define PIN_NUMBER_RED 32
    
    #define PIN_TYPE_BLUE CC
    #define PIN_NUMBER_BLUE 19
    
    // Per-type mappings from (pin type, led state) to voltage level
    // Possibly these could be shared across different hardware.
    
    // Define the pin state as wired to the board, CA (PIN controls -ve lead) or CC (PIN controls +ve lead)
    #define CA_ON           LOW       // Common Anode LED ON when PIN is LOW
    #define CA_OFF         HIGH       // Common Anode LED OFF when PIN is HIGH
    #define CC_ON          HIGH       // Common Cathode LED ON when PIN is HIGH
    #define CC_OFF          LOW       // Common Cathode LED OFF when PIN is LOW
    
    //////
    // Common macros
    //
    
    // Helper and utility macros
    #define CONCAT(x, y) x ## _ ## y
    #define LED_TYPE(color) CONCAT(PIN_TYPE, color)
    #define LED_PIN(color) CONCAT(PIN_NUMBER, color)
    #define PIN_LEVEL(type, state) CONCAT(type, state)
    #define LED_HELPER(pin, type, state) digitalWrite(pin, PIN_LEVEL(type, state))
    // Note: CA_LED_ON, etc, not used
    
    // Primary macros for use in other code
    #define LED_ON(color) LED_HELPER(LED_PIN(color), LED_TYPE(color), ON)
    #define LED_OFF(color) LED_HELPER(LED_PIN(color), LED_TYPE(color), OFF)
    
    /////
    // Demo
    //
    
    LED_ON(RED)
    LED_OFF(RED)
    LED_ON(BLUE)
    LED_OFF(BLUE)
    

    That gives you LED_ON and LED_OFF macros taking an LED code (here expressed as color names). Although these definitions use the name color for their parameter, there's nothing particularly color-specific there. Codes such as FLUX_CAPACITOR and DEATH_RAY would work fine too, provided only that corresponding pin type and pin number definitions are provided for them.

    Nor is this inherently limited to two pin types. As long as the machine-specific definitions map a pin-type code to ON and OFF levels, those same definitions can map LED names to that pin type code, in which case LED_ON() and LED_OFF() will do their thing.