我正在使用以下宏函数

#define setAsOutput(bit, i)   { *bit ## _DDR[i] |= (1 << bit[i]); }

简化某些寄存器值的定义和设置
// registers
volatile uint8_t *FOO_DDR[] = {&DDRA, &DDRA};
uint8_t FOO[] = {PA1, PA2};

setAsOutput(FOO, 0);

// these are defined somewhere else
#define PA1     1
#define PA2     2
#define DDRA    _SFR_IO8(0X01)

这使得代码等价于
DDRA |= (1 << PA1);

然而,由于volatile uint8_t *FOO_DDR[] = {&DDRA, &DDRA};中的A始终在DDRA值(即FOOPA1)中重复,因此行PA2实际上是多余的。
理想情况下,它可以被完全删除,宏更改为
#define setAsOutput(bit, i)   { DDR ## <second char of bit[i]> |= (1 << bit[i]); }

但是获得bit[i]名称的第二个字符似乎是不可能的。
有没有办法重写宏函数,使FOO_DDR不需要显式定义,而是可以从{PA1, PA2}中隐含?

最佳答案

如果您可以提供MCVE这样其他人就可以轻松地编译您的代码,查看它是如何工作的,并尝试调整它,那么它通常会有帮助。
您不需要在代码中定义DDRAPA1之类的内容。只需将适当的选项传递给编译器,指定您正在使用的AVR(例如-mmcu=atmega1284p),然后在程序顶部添加#include <avr/io.h>,即可获得这些定义。通常,将这些定义从io.h复制到StackOverflow上的问题中并没有什么意义,因为它们非常标准。这些定义来自avr-libc,所以如果您真的想提供这些细节,可以只说您使用的是avr-libc的哪个版本。
问题的一个主要前提是,用数组和宏发布的代码相当于DDRA |= (1 << PA1);。不幸的是,这个前提是不正确的。当GCC看到DDRA |= (1 << PA1);时,它实际上可以将其编译为设置DDRA寄存器位1的单个原子AVR指令。当GCC看到您的代码时,它会做一些更复杂的事情,最终读取、写入和修改寄存器。因此,如果中断可能会修改DDRA寄存器,那么数组代码会浪费CPU周期并且使用起来不安全。
如果你不相信我,你可以看看这个godbolt.org链接,它比较了两种方法的程序集:
https://godbolt.org/g/jddzpK
看起来只需向数组中添加const限定符就可以解决这个问题。然后编译器将知道数组在编译时保存的值,并可以生成好的代码。

volatile uint8_t * const FOO_DDR[] = {&DDRA, &DDRA};
uint8_t const FOO[] = {PA1, PA2};

现在讨论您的主要问题,即如何消除冗余阵列。我不认为有一个简单的方法可以做到这一点,在你的程序中有两个const数组并不是什么大问题,它们可能会在编译时被优化掉。你可以做的是扩展这些阵列,使它们包含芯片上每个管脚的条目。然后,当您想写入一个pin时,只需使用一个pin号,它是数组的索引(而不需要定义新的数组)。然后,绝大多数代码将处理这些管脚号,而不必担心数组。下面是我的写作方法:
#include <avr/io.h>

// Here is a general GPIO library for your chip.
// TODO: expand these arrays to cover every pin on the chip

#define setAsOutput(i)   { *pin_dir[i] |= (1 << pin_bit[i]); }
#define setHigh(i)   { *pin_value[i] |= (1 << pin_bit[i]); }

static volatile uint8_t * const pin_dir[] = {
  &DDRA,  // Pin 0
  &DDRA,  // Pin 1
};

static volatile uint8_t * const pin_value[] = {
  &PORTA,  // Pin 0
  &PORTA,  // Pin 1
};

static const uint8_t pin_bit[] = {
  PA1,    // Pin 0
  PA2,    // Pin 1
};

// Pin definitions for your particular project.
// (e.g. pin 0 is connected to a green LED)

#define GREEN_LED_PIN 0

void nice()
{
  setAsOutput(GREEN_LED_PIN);
  setHigh(GREEN_LED_PIN);
}

上面的每个GPIO函数调用最终编译成一个汇编指令。
如果你仔细研究Arduino核心代码,你会发现像这样的数组。(但是Arduino人在其pinModedigitalWrite函数中错误地以浪费的方式访问这些数组。)
请注意,使用上面我提供的代码,有很大的风险,您可能会意外地传递一个不是编译时常量的pin号,因此编译器将无法优化它,并生成浪费/不安全的代码。这就是为什么最好使用内联汇编和C++模板,比如FastGPIO library

关于c - 从宏名称获取字符以优化宏功能,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/50547181/

10-11 16:10