基于#define 名称的条件语句

Conditional statements based on #define's name

我刚开始做一些 C(来自 Java)。我试图弄清楚该语言基于定义名称的条件方法是什么。

例如我有一个巨大的头文件,我不能(不应该)用很多定义来编辑它。

#define GPIO_OTYPER_OT_0                     ((uint32_t)0x00000001)
#define GPIO_OTYPER_OT_1                     ((uint32_t)0x00000002)
#define GPIO_OTYPER_OT_2                     ((uint32_t)0x00000004)
#define GPIO_OTYPER_OT_3                     ((uint32_t)0x00000008)
#define GPIO_OTYPER_OT_4                     ((uint32_t)0x00000010)
#define GPIO_OTYPER_OT_5                     ((uint32_t)0x00000020)

以此类推;

我想制作一个 function/declaration(或任何解决方案)来作用于定义的 _# 部分。

(伪代码)

void initialize(int X) {
  GPIOA->MODER |= GPIO_MODER_MODER%X_5;
  GPIOA->OTYPER &= ~GPIO_OTYPER_OT_%X;
  GPIOA->OSPEEDR |= GPIO_OSPEEDER_OSPEEDR%X;
  GPIOA->PUPDR &= ~GPIO_PUPDR_PUPDR5;
  GPIOA->ODR |= GPIO_ODR_ODR_%X;
}

其中 %X 是整数 X

我能想到的就是每个 X 的 switch 语句,但是 X 的范围很大,所以源文件会很大。

编辑: https://github.com/espruino/Espruino/blob/master/targetlibs/stm32f4/lib/stm32f411xe.h 是头文件。

无法将任意整数插入到宏名称中。但是,您的整数会很小(肯定小于 32,因为您所有的常量都是 32 位类型)。因此,您可以将 switch 语句转换为单行表达式,如下所示:

x == 0 ? GPIO_OTYPER_OT_0 : \
x == 1 ? GPIO_OTYPER_OT_1 : \
x == 2 ? GPIO_OTYPER_OT_2 : \
...
x == 31 ? GPIO_OTYPER_OT_31 : 0

在这里,你甚至可以创建一个 "default" 表达式,它会产生一个运行时错误——类似于 (abort(), (uint32_t)0).

为了使其更通用,将 GPIO_OTYPER_OT_ 部分分成宏参数,并使用 "paste operator" ##:

#define MY_MACRO(name, x) \
x == 0 ? name ## 0 : \
x == 1 ? name ## 1 : \
x == 2 ? name ## 2 : \
...
x == 31 ? name ## _31 : \
(abort(), name ## 0)

用法示例:

GPIOA->ODR |= MY_MACRO(GPIO_ODR_ODR_, x);

您必须为那些中间有 x 的名称创建一个单独的宏:

#define MY_MACRO2(prefix, x, suffix) ( \
(x) == 0 ? prefix ## 0 ## suffix : \
(x) == 1 ? prefix ## 1 ## suffix : \
...
(x) == 31 ? prefix ## 31 ## suffix : \
(abort(), prefix ## 0 ## suffix))

这里我还添加了必要的括号(围绕 x 和整个宏),就像 C 宏的习惯一样。


P.S。如果你的大头文件没有定义数字最大为 31 的宏,但有一个更小的限制,你不能使用提到所有这些名称的宏,因为你会得到一个编译错误。在这种情况下,将最大值插入宏的名称中。然后你可以用"recursive"的方式定义它们:

#define MY_MACRO_MAX1(prefix, x) \
x == 0 ? prefix ## 0 ## suffix : prefix ## 1 ## suffix

#define MY_MACRO_MAX2(prefix, x) \
x == 2 ? prefix ## 2 ## suffix : MY_MACRO_MAX1(prefix, x)

#define MY_MACRO_MAX3(prefix, x) \
x == 3 ? prefix ## 3 ## suffix : MY_MACRO_MAX2(prefix, x)

#define MY_MACRO_MAX4(prefix, x) \
x == 4 ? prefix ## 4 ## suffix : MY_MACRO_MAX3(prefix, x)

#define MY_MACRO_MAX5(prefix, x) \
x == 5 ? prefix ## 5 ## suffix : MY_MACRO_MAX4(prefix, x)

...

使用ST的GPIO抽象层,可以找到here。值得注意的是,请参阅 GPIO_InitTypeDef,它为您提供了上述操作的结构,以及 GPIO_Init,它实际上会执行您想要的操作。初始化结构将引脚作为位掩码,因此正如@artless noise 在评论中建议的那样,您只需执行 1<<X 即可创建您的掩码。所有特定于 MCU 的行为和寄存器映射都隐藏在您的代码之外。

如果你正在尝试实现自己的驱动层作为练习,或者因为你认为 ST 库不是很好,那么我仍然会看看他们是如何实现 GPIO_InitC file。他们使用移位,但你会注意到,在处理寄存器时,它并不总是像 1<<X 那样容易(不过,请注意,对于它们的配置结构,它总是 就这么简单)。一些寄存器的每个引脚都有多个位(模式:2 位,拉动配置:2 位,备用功能:4 位,拆分为多个寄存器)。

编辑:我并不是建议添加更多您还没有的库。 library/code 你引用头文件的基础已经包含了 ST 的外设库,所以(对我来说)使用它是有意义的。