C语言 带点的预处理器宏串联字符串

yqhsw0fo  于 9个月前  发布在  其他
关注(0)|答案(5)|浏览(112)

我正在尝试在AVR 16-AVR 128系列微控制器的库中编写宏set_clock()、clr_clock()等。
我有问题wiht添加字母V端口名称在一个宏。
在下面的代码中,我描述了当是一个地方时,如果启用了虚拟端口,预处理器应该在其中添加字母V。
我不能使用函数。虚拟端口在CPU时钟的1个周期内改变端口中的位,我需要它。所以我必须使用宏来生成最快的代码。

//=== 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;
}

字符串
当我在VVV的地方粘贴了一个字母V时,会发生错误:

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


怎么能修好呢?

2admgd59

2admgd591#

如果你必须使用一个宏来完成这个任务,那么你似乎需要引入一些间接的方法来完成token的连接。下面是三个宏的集合,它们可以完成我认为你会问的事情:

#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)

字符串
给定#define VPORTA (*(VPORT_t *) 0x0000)#define PORT PORTA和OP问题中建议的宏调用set_DIR(PORT, 3);,这将扩展为(((*(VPORT_t *) 0x0000)).DIR |= (3));
set_DIR宏接受参数PORT3,并将PORT扩展为PORTA(来自PORT#define)在使用PORTA3调用X_set_DIR宏之前,X_set_DIR宏将标记VPORTA连接起来,然后调用XX_set_DIR宏,参数为VPORTA3。最后,XX_set_DIR宏扩展为访问VPORTA#define指示的结构体的DIR字段的代码,即最终扩展为(((*(VPORT_t *) 0x0000)).DIR |= (3));

更新

当我测试上面的代码时,我定义了VPORTA,但没有定义PORTA。在@KamilCuk的评论之后,我发现包含PORTA的定义会干扰set_DIR宏的扩展。
经过进一步的审查,似乎OP所需的解决方案是误导一般。除了使用宏在第一位(@gulpr)的可扩展性,OP选择机制的虚拟与正常端口没有意义。
特别是,OP代码有#ifdef VPORTA。这作为选择机制没有意义,因为工具链头文件已经定义了VPORTA。OP将需要找到另一种方法来指示正在使用的是虚拟端口还是普通端口。
最简单的方法是将PORT设置为所需的端口,如下所示:

/* 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;
}


如果OP需要在虚拟和正常端口之间切换的机制,则可以使用#define来具体指示端口是虚拟端口还是正常端口:

/* 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显示了上述宏的扩展。它可以工作(目前),但这是一个问题的解决方案。如果AB或任何其他端口选择器令牌在其他地方定义,这可能不会像预期的那样工作。
如果你打算使用宏来实现这个功能,只需使用简单的方法,并将PORT定义为你想要的特定端口。但是你可能需要重新考虑是否需要使用宏。无论你做什么,对端口结构的实际访问都将在运行时发生。你从宏获得的任何性能提升都将是最小的,因为使用宏会带来所有问题。

qhhrdooz

qhhrdooz2#

1.避免!根本不要使用那些宏。使用函数并信任你的编译器
1.不要在宏中取消引用这些指针
1.如果是硬件寄存器,则需要将其设置为volatile,因为您希望处理寄存器中存储的实际值(可能会被硬件或ASIC代码更改),而不是处理器寄存器中存储的值。

#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);
}

字符串
示例生成代码:
手臂:

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

编辑

#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);
    }
}

s4chpxco

s4chpxco3#

#define set_DIR(x, y) x ## .DIR |= ## y更改为#define set_DIR(x, y) ((x).DIR |= (y))
第一:上面的代码生成的错误没有将PORT转换为VPORTA。
在宏处理中,##在结果被重新扫描以进行进一步的宏替换之前被处理,因此PORT在被VPORTA替换之前与另一个令牌组合。当##被删除时,这不是问题。
第二:联接时产生网点误差。
解析表达式是C主要处理的一部分。它不是预处理的一部分,包括宏替换,你不应该尝试使用##操作符。##用于组合语法标记。
在宏中,大多数情况下,你只是简单地编写C代码,就好像宏参数是普通的标识符一样,除了它们通常应该被括号括起来,这样,当参数是一个带有运算符的表达式时,它在宏扩展产生的代码中被视为括号中的一个项目。

ntjbwcob

ntjbwcob4#

#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);

字符串
这是不可能实现的。不可能部分扩展一个宏。set_DIR(PORT的参数中的PORT要么根本不扩展,在这种情况下它保持为PORT,或者完全扩展为(*(PORT_t *) 0x0400)。不可能先将宏“部分”扩展为PORT -> PORTA,然后添加V字符。

  • 强烈 * 考虑使用其他伟大答案中提出的函数。

最简单的方法是,只需为您的转换选择一个不同的唯一PORT名称,您可以在其中定义自己的端口。

#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);

bvhaajcl

bvhaajcl5#

因为在这个主题中有关于虚拟端口的额外讨论,我决定展示avr-gcc编译器:

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.

字符串
在某些AVR系列微控制器(如AVR 128 DB 64)的引脚上使用按位操作时,可以使用单周期汇编指令:

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
    }
}


结果:

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>


在美国:

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.

相关问题