Preprocessor macro concatenation strings with dots

278 Views Asked by At

I am trying to write macros set_DIR(), clr_DIR(), etc. in a library for microcontrollers from family AVR16-AVR128.

I have problem wiht adding letter V to port names in a macro.

In code below I described when is a place, where preprocessor should add letter V if virtual ports are enabled.

I can't use functions. Virtual ports changes bits in ports in 1 cycle of CPU clock, and I need it. So I must use macros to generate fastest code.

//=== From  microcontroller toolchain header: ioavr128db64.h  =========

/* Virtual Ports */
typedef struct VPORT_struct
{
    register8_t DIR;
    register8_t OUT;
    register8_t IN;
    register8_t INTFLAGS;
} VPORT_t;

#define VPORTA      (*(VPORT_t *) 0x0000)
#define VPORTB      (*(VPORT_t *) 0x0004)
#define VPORTC      (*(VPORT_t *) 0x0008)
// ... etc.

//--- PORTS definitions  (normal ports) -----

typedef struct PORT_struct
{
    register8_t DIRSET;
    register8_t DIRCLR;
    // etc.
} PORT_t;

#define PORTA       (*(PORT_t *) 0x0400)
#define PORTB       (*(PORT_t *) 0x0420)
#define PORTC       (*(PORT_t *) 0x0440)
// ... etc.


//==========  My definitions  ==================================


/* chceck if virtual ports are available */
// Caution! VVV is place where preprocessor should add letter V to choosen port
#ifdef VPORTA
    #define VPORTS  1
    #define set_DIR(x, y) VVV(x).DIR |= (y)     /* should generate: VPORTA.DIR |=  3 */
    #define clr_DIR(x, y) VVV(x).DIR &=~(y)     /* should generate: VPORTA.DIR &=~ 3 */
    /* etc. */
#else
    #define VPORTS  0
    #define set_DIR(x, y) (x).DIRSET = (y)      /* should generate: PORTA.DIRSET = 3 */
    #define clr_DIR(x, y) (x).DIRCLR = (y)      /* should generate: PORTA.DIRCLR = 3 */
    /* etc. */
#endif

//Caution! Its very important - must be like this!
#define PORT PORTA  /* may be chanched e.g. PORTC */

int main(void) 
{
    //example
    set_DIR(PORT, 3);
  
    return 0;
}

When I place where are VVV i pasted one letter V an error occurs:

Severity    Code    Description Project File    Line
Warning     implicit declaration of function 'V' [-Wimplicit-function-declaration]  tests   main.c  11

How can fix it?

5

There are 5 best solutions below

2
Eric Postpischil On

Change #define set_DIR(x, y) x ## .DIR |= ## y to #define set_DIR(x, y) ((x).DIR |= (y)).

First: Code above generated error didn't convert PORT to VPORTA.

In macro processing, ## is processed before the result is rescanned for further macro replacement, so PORT was combined with another token before it could be replaced by VPORTA. When the ## are removed, that is not an issue.

Second: Join with dot generate error.

Parsing expressions is part of the main C processing. It is not part of preprocessing, including macro replacement, and you should not try to use the ## operator for that. ## is for combining grammatical tokens.

In macros, you mostly simply write C code as if the macro parameters were ordinary identifiers, except they should often be parenthesized so that, when the argument is an expression with operators, it is taken as a single item in parentheses in the code resulting from macro expansion.

15
gulpr On
  1. Avoid!!! Do not use those macros at all. Use functions and trust your compiler
  2. Do not dereference those pointers in macros
  3. If it is a hardware register you need to make it volatile as you want to work on the actual value stored in the register (which may be changed by hardware or async code), not on the value stored in the processor register.
#if defined(__GNUC__)
#define ALWAYSINLINE __attribute__((always_inline))
#else   //you can add here compiler specific stuff in similar macros
#define ALWAYSINLINE
#endif

typedef struct VPORT_struct
{
    volatile uint8_t DIR;
    volatile uint8_t OUT;
    volatile uint8_t IN;
    volatile uint8_t INTFLAGS;
} VPORT_t;

#define VPORTA      ((VPORT_t *) 0x0000)
#define VPORTB      ((VPORT_t *) 0x0004)
#define VPORTC      ((VPORT_t *) 0x0008)

inline static void ALWAYSINLINE set_DIR(VPORT_t *x, const uint8_t y) 
{
    x -> DIR |= y;
}

void foo(void)
{
    set_DIR(VPORTA, 3);
}

example generated code:

ARM:

        movs    r2, #0
        movs    r3, #3
        ldrb    r1, [r2]
        orrs    r3, r1
        strb    r3, [r2]
        bx      lr

AVR:

       ldi r30,0
        ldi r31,0
        ld r24,Z
        ori r24,lo8(3)
        st Z,r24
        ret

https://godbolt.org/z/jbKaaPGWE

EDIT

#define PORT VPORTA

void bar(void)
{
    set_DIR(PORT, 3);
}

VPORT_t *ports[] = {VPORTA, VPORTB, VPORTC, NULL};

void setPortsDIR(VPORT_t *ports, uint8_t dir)
{
    for(VPORT_t *port = ports; port; port++)
    {
        set_DIR(port, dir);
    }
}
2
ad absurdum On

If you must use a macro for this, it seems like you will need to introduce some indirection to accomplish the token concatenation. Here is a collection of three macros that do what it is that I think you are asking:

#define XX_set_DIR(x, y) ((x).DIR |= (y))
#define X_set_DIR(x, y) XX_set_DIR(V ## x, y)
#define set_DIR(x, y) X_set_DIR(x, y)

Given #define VPORTA (*(VPORT_t *) 0x0000), #define PORT PORTA and the macro invocation set_DIR(PORT, 3); as suggested in the OP question, this expands to (((*(VPORT_t *) 0x0000)).DIR |= (3));.

The set_DIR macro takes the arguments PORT and 3 and expands PORT to PORTA (from the #define for PORT) before calling the X_set_DIR macro with PORTA and 3. The X_set_DIR macro concatenates the tokens V and PORTA before calling the XX_set_DIR macro with the arguments VPORTA and 3. Finally, the XX_set_DIR macro expands to code that accesses the DIR field of the struct indicated by the #define for VPORTA, i.e., the final expansion is (((*(VPORT_t *) 0x0000)).DIR |= (3));.

Update

When I tested the above code I had defined VPORTA, but not PORTA. After a comment from @KamilCuk I found that including the definition for PORTA interfered with the expansion of the set_DIR macro.

After further review, it seems that OP desired solution is misguided in general. Aside from the advisability of using macros for this in the first place (@gulpr), the OP selection mechanism for virtual vs. normal ports makes no sense.

In particular, OP code has #ifdef VPORTA. This makes no sense as a selection mechanism because the toolchain header file already has defined VPORTA. OP will need to find another way to indicate whether a virtual port or a normal port is in use.

The simplest thing to do is to set PORT to the desired port like this:

/* From toolchain header file */
#define VPORTA      (*(VPORT_t *) 0x0000)
#define VPORTB      (*(VPORT_t *) 0x0004)
#define VPORTC      (*(VPORT_t *) 0x0008)

#define PORTA       (*(PORT_t *) 0x0400)
#define PORTB       (*(PORT_t *) 0x0420)
#define PORTC       (*(PORT_t *) 0x0440)

/* Port selection mechanism.
 * Select from:
 * PORTA, PORTB, PORTC,
 * VPORTA, VPORTB, VPORTC
*/
#define PORT VPORTA  

#define set_DIR(x, y) ((x).DIR |= (y))
// etc.

int main(void) 
{
    set_DIR(PORT, 3);
    return 0;
}

If OP requires a mechanism to toggle between virtual and normal ports a #define for specifically indicating whether a port is virtual or normal can be used:

/* From toolchain header file */
#define VPORTA      (*(VPORT_t *) 0x0000)
#define VPORTB      (*(VPORT_t *) 0x0004)
#define VPORTC      (*(VPORT_t *) 0x0008)

#define PORTA       (*(PORT_t *) 0x0400)
#define PORTB       (*(PORT_t *) 0x0420)
#define PORTC       (*(PORT_t *) 0x0440)

/* Port selection mechanism. */
#define PORT A   // Select: A, B, C
#define VIRTUAL  // Enable this for virtual ports; comment out for normal ports

#define XX_set_DIR(x, y) ((x).DIR |= (y))
// etc.

#ifdef VIRTUAL
    #define X_set_DIR(x, y) XX_set_DIR(VPORT ## x, y)
    // etc.
#else
    #define X_set_DIR(x, y) XX_set_DIR(PORT ## x, y)
    // etc.
#endif

#define set_DIR(x, y) X_set_DIR(x, y)
// etc.

int main(void) 
{
    set_DIR(PORT, 3);
    return 0;
}

Here is a link to the Godbolt Compiler Explorer showing the expansion of the above macros. It works (for now) but this is a solution that is begging for problems. If A or B or any of the other port selector tokens is defined somewhere else this will probably not work as expected.

If you are going to use a macro for this, just use the simple approach and define PORT to the specific port that you want. But you might reconsider whether this needs to be a macro at all. No matter what you do, the actual access to the port structs will happen at runtime. Any performance gains that you get from macros will be minimal for the cost of all the problems that accompany using macros.

1
KamilCuk On
#define set_DIR(x, y) VVV(x).DIR |= (y)     /* should generate: VPORTA.DIR |=  3 */

#define PORTA       (*(PORT_t *) 0x0400)

#define PORT PORTA  /* may be chanched e.g. PORTC */

set_DIR(PORT, 3);

It is impossible to achieve. It is not possible to partially expand a macro. PORT in the argument of set_DIR(PORT is either not expanded at all, in which case it stays to be PORT, or is fully expanded to (*(PORT_t *) 0x0400). It is not possible to "partially" expand a macro first to PORT -> PORTA to then add a V character.

Strongly consider using functions as presented in the other great answer.

The simplest there is, just pick a different unique PORT names for your transformations, where you define your own ports.

#define PORTA       (*(PORT_t *) 0x0400)
#define VPORTA      (*(VPORT_t *) 0x0000)

#ifdef VPORTA
// map MYPORT to VPORT
#define MYPORTA  VPORTA
#define set_DIR(x, y) (x).DIR |= (y)
#else
// map MYPORT to PORT
#define MYPORTA  PORTA
#define set_DIR(x, y) (x).DIRSET |= (y)
#endif

#define PORT  MYPORTA

set_DIR(PORT, 3); // -> ((*(VPORT_t *) 0x0000)).DIR |= (3);
0
Don Jacek On

Because in this topic there is additional discusion about virtual ports i decided to show that avr-gcc compiler:

C:\Program Files (x86)\Atmel\Studio\7.0\toolchain\avr8\avr8-gnu-toolchain\bin>avr-gcc.exe --version
avr-gcc.exe (AVR_8_bit_GNU_Toolchain_3.7.0_1796) 7.3.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  

can use 1-cycle assembler instructions when using bitwise operations on pins for some AVR familly microcontrollers like AVR128DB64:

int main(void)
{
    VPORTA.DIR  |= (1<<0);
    VPORTA.DIR  |= (1<<1);
    VPORTA.DIR  |= (1<<2);

    while(1)
    {
        VPORTA.OUT    |=  (1<<0);    //Set pin PA0 to high
        VPORTA.OUT    &=~ (1<<0);    //Set pin PA0 to low
        VPORTA.OUT    |=  (1<<1);    //Set pin PA1 to high
        VPORTA.OUT    &=~ (1<<1);    //Set pin PA1 to low
        VPORTA.OUT    |=  (1<<2);    //Set pin PA2 to high
        VPORTA.OUT    &=~ (1<<2);    //Set pin PA2 to low
    }
}

result:

int main(void)
{
    VPORTA.DIR  |= (1<<0);
 11c:   00 9a           sbi 0x00, 0 ; 0
    VPORTA.DIR  |= (1<<1);
 11e:   01 9a           sbi 0x00, 1 ; 0
    VPORTA.DIR  |= (1<<2);
 120:   02 9a           sbi 0x00, 2 ; 0

    while(1)
    {
        VPORTA.OUT    |=  (1<<0);    //Set pin PA0 to high
 122:   08 9a           sbi 0x01, 0 ; 1
        VPORTA.OUT    &=~ (1<<0);    //Set pin PA0 to low
 124:   08 98           cbi 0x01, 0 ; 1
        VPORTA.OUT    |=  (1<<1);    //Set pin PA1 to high
 126:   09 9a           sbi 0x01, 1 ; 1
        VPORTA.OUT    &=~ (1<<1);    //Set pin PA1 to low
 128:   09 98           cbi 0x01, 1 ; 1
        VPORTA.OUT    |=  (1<<2);    //Set pin PA2 to high
 12a:   0a 9a           sbi 0x01, 2 ; 1
        VPORTA.OUT    &=~ (1<<2);    //Set pin PA2 to low
 12c:   0a 98           cbi 0x01, 2 ; 1
 12e:   f9 cf           rjmp    .-14        ; 0x122 <main+0x6>

In datasheet:

Virtual Ports
The Virtual PORT registers map the most frequently used regular PORT registers into the I/O Register space with
single-cycle bit access. Access to the Virtual PORT registers has the same outcome as access to the regular
registers but allows for memory-specific instructions, such as bit manipulation instructions, which cannot be used in
the extended I/O Register space where the regular PORT registers reside.