如果您能提供一个MCVE 通常会有所帮助,这样其他人就可以轻松编译您的代码,了解它是如何工作的,并尝试对其进行调整。
您不需要在代码中定义 DDRA 和 PA1 之类的内容。只需将适当的选项传递给编译器以指定您正在使用的 AVR(例如 -mmcu=atmega1284p),然后在程序顶部添加 #include <avr/io.h> 以获取这些定义。将io.h 中的这些定义复制到 StackOverflow 上的问题中通常没有多大意义,因为它们非常标准。这些定义来自 avr-libc,所以如果你真的想提供这些细节,你可以直接说出你使用的是什么版本的 avr-libc。
您问题的一个主要前提是您发布的带有数组和宏的代码等同于DDRA |= (1 << PA1);。不幸的是,这个前提是不正确的。当 GCC 看到 DDRA |= (1 << PA1); 时,它实际上可以将其编译为单个原子 AVR 指令,该指令设置 DDRA 寄存器的第 1 位。当 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 数组并不是什么大不了的事,无论如何它们可能会在编译时被优化掉。您可以做的是扩展这些数组,以便它们包含芯片上每个引脚的条目。然后,当您想写入引脚时,您只需使用作为数组索引的引脚号(而不需要定义新数组)。然后,您的绝大多数代码将处理这些引脚编号,而不必担心数组。所以我会这样写:
#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 Core 代码,您会发现像这样的数组。 (但是 Arduino 人错误地在他们的 pinMode 和 digitalWrite 函数中以浪费的方式访问这些数组。)
请注意,使用我上面提供的代码,您可能会意外传递一个不是编译时常量的引脚号,因此编译器将无法对其进行优化,并产生浪费/不安全的代码。这就是为什么最好使用像 FastGPIO library 这样的内联汇编和 C++ 模板的原因之一。