根据参数值调用不同宏的C宏

C Macros that invoke different Macros according to the argument values

我正在尝试在 C2000 微控制器上实现寄存器访问宏。遗憾的是,这个微控制器没有 8 位类型,所以当我需要访问一个字节时,我需要调用一个宏 __byte(temp,0) = 0xCA

我的一些寄存器是字节,另一些是 16 位字。但是,我想使用相同的宏来访问数据。

#define REG_WIDGET_ADDR 0x1000
#define REG_WIDGET_IS_BYTE 1

#define REG_GADGET_ADDR 0x1002
#define REG_GADGET_IS_BYTE 0

我想像这样访问寄存器。

REG_ACCESS(WIDGET) = 0x0A;
REG_ACCESS(GADGET) = 0xCAFE;

这将被转换为不同的宏,如

REG_ACCESS2(REG_WIDGET_ADDR, REG_WIDGET_IS_BYTE) = 0x0A
REG_ACCESS2(REG_GADGET_ADDR, REG_GADGET_IS_BYTE) = 0xCAFE

然后当寄存器是一个字节时,REG_ACCESS2宏应该被转换成类似__byte(*(0x1000),0) = 0xA的东西。当寄存器为 16 位字时,宏应转换为 (*(0x1002) = 0xCAFE

我知道如何创建在参数数量改变时调用不同宏的宏,但不知道如何在参数值改变时调用不同宏。我该如何进行?

编辑:

C2000没有8位寄存器,只有16位寄存器。但我想使用 DMA 对外部设备进行编程。我想在C2000 Ram中设置8位和16位“寄存器”,然后通过DMA传输到外部设备。

此代码可以满足您的要求:

#define REG_WIDGET_ADDR 0x1000
#define REG_WIDGET_IS_BYTE 1

#define REG_GADGET_ADDR 0x1002
#define REG_GADGET_IS_BYTE 0

//  These two macros provide the requested expansions for word and byte access.
#define REG_ACCESS_T0(a)    *(a)
#define REG_ACCESS_T1(a)    __byte(*(a), 0)

//  This macro uses argument b to select between the two macros above.
#define REG_ACCESS3(a, b)   REG_ACCESS_T##b(a)

//  This macro is needed to let argument b be replaced.
#define REG_ACCESS2(a, b)   REG_ACCESS3(a, b)

//  This macro uses x to get the corresponding address and is-byte macros.
#define REG_ACCESS(x)       REG_ACCESS2(REG_##x##_ADDR, REG_##x##_IS_BYTE)

REG_ACCESS(WIDGET) = 0x0A;
REG_ACCESS(GADGET) = 0xCAFE;

宏替换结果为:

__byte(*(0x1000), 0) = 0x0A;
*(0x1002) = 0xCAFE;

第 1 部分

解决此预处理问题的经典方法涉及从片段中粘贴标记,然后匹配某些定义的标记。令牌碎片化的问题在于它不利于您的基本开发环境工具,例如“查找使用此标识符的位置”。

相反,我们可以采用将寄存器定义为数据帧的方法,如下所示:

#define WIDGET 0x1000, BYTE
#define GADGET 0x1002, WORD

但是:让我们不要完全采用这种未封装的方法。让我们为这个“数据类型”创建一个“构造函数”并使用它。原因是:如果我们向我们的寄存器添加另一个 属性,我们可以只向 constructor-like 宏添加一个参数,然后编译器将找到所有需要参数的地方,所以我们不需要'忘记任何:

#define REG(ADDR, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD

#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)

另一个优点是“属性”现在有了名称。在我们的编程编辑器或IDE中,我们可以跳转到REG的定义,看看WIDGET的定义中0x1000是什么意思,可以看到它对应一个名为ADDR.

所以现在,定义了这个“数据结构”,我们可以编写访问器:

#define REG(ADDR, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD

// retrieve addr
#define ADDR(ADDR, BYTE_OR_WORD) ADDR

// retrieve BYTE or WORD
#define BYTE_OR_WORD(ADDR, BYTE_OR_WORD) BYTE_OR_WORD

那么我们可以像这样编写访问宏:

#define REG_ACCESS(REG) BYTE_OR_WORD(REG)(ADDR(REG))

例如

REG_ACCESS(WIDGET)

扩展到

BYTE(0x1000)

现在我们只需要定义 BYTE()WORD() 来进行访问。这是一个文件中的完整图片:

// REG type constructor
#define REG(ADDRESS, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD

// Retrieve address field of type by destructuring and selecting
#define ADDR(ADDR, BOW) ADDR

// Retrieve BYTE-or-WORD field likewise
#define BYTE_OR_WORD(ADDR, BOW) BOW

// Access macro: retrieves the fields and builds expression
#define REG_ACCESS(REG) BYTE_OR_WORD(REG)(ADDR(REG))

// construct instances of various registers
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)

// Test access
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)

使用 gcc -E 进行测试:

$ gcc -E regaccess.c
# 1 "regaccess.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "regaccess.c"
# 18 "regaccess.c"
    BYTE(0x1000)
    WORD(0x1002)

请注意在解决方案中,每个标识符在某处都有一个定义,并且对该标识符的所有引用都使用该标识符:没有标识符是从无法搜索的部分粘贴在一起的。

如果我们在代码中看到REG_ACCESS(WIDGET),我们可以跳转到REG_ACCESS的定义或WIDGET的定义。

此外,该代码仅使用 ISO C 90 预处理器功能,但与函数式编程有一定关系:元组的构造、解构。这是因为预处理器被(或可以)视为一个纯 term-rewriting 系统。

第 2 部分

如果我们做的事情很简单,我们就不需要带有访问器的抽象层。因为它们只在单个宏中使用,REG_ACCESS,我们可以让它们消失:

// REG type constructor
#define REG(ADDRESS, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD

// Access macro's implementation
#define X_REG_ACCESS(ADDR, BOW) BOW(ADDR)

// Access macro's interface
#define REG_ACCESS(REG) X_REG_ACCESS(REG)

// construct instances of various registers
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)

// Test access
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)

X_REG_ACCESS 解构类型的两个部分,ADDRBOW,并且不使用任何抽象访问器,做它需要的:构造类型的应用程序 byte-or-word 函数名称到类型的地址。

它的核心是简单本身:BOW(ADDR)。将 ADDR 传递给 BYTEWORD.

第 3 部分

但是,哲学问题:我们为什么不这样做:

#define WIDGET BYTE(0x1000)
#define GADGET WORD(0x1001)

// nothing to do: WIDGET and GADGET are already access expressions:
#define REG_ACCESS(REG) REG

我们如何为早先的废话辩护呢?一个原因是早期的方法允许我们 macro-time 访问有关寄存器的信息,而不仅仅是生成访问该寄存器的代码。如果我们只想要它的地址怎么办?或者是 BYTE 还是 WORD.

我们的系统是可扩展的。假设我们也需要寄存器名称,并且布尔值 10 值谓词判断寄存器是否为字节数据类型。复杂示例:

// Machine type abstraction byte or word
#define DATA_TYPE(IS_BYTE, ACCESS_FN) IS_BYTE, ACCESS_FN

// Register abstraction
#define REG(ADDRESS, DATA_TYPE, NAME) ADDRESS, DATA_TYPE, NAME

#define X_REG_ACCESS(ADDR, IS_BYTE, ACCESS_FN, NAME) ACCESS_FN(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS(REG)

#define X_REG_ADDR(ADDR, IS_BYTE, ACCESS_FN, NAME) ADDR
#define REG_ADDR(REG) X_ADDR(REG)

#define X_REG_NAME(ADDR, IS_BYTE, ACCESS_FN, NAME) NAME
#define REG_NAME(REG) X_REG_NAME(REG)

#define X_REG_IS_BYTE(ADDR, IS_BYTE, ACCESS_FN, NAME) IS_BYTE
#define REG_IS_BYTE(REG) X_REG_IS_BYTE(REG)

// Database of machine types
#define BYTE DATA_TYPE(1, get_byte)
#define WORD DATA_TYPE(0, get_word)

#define WIDGET REG(0x1000, BYTE, "I2C Acme Widget")
#define GADGET REG(0x1002, WORD, "SPI Mikro Gadget")

// Debug print about register
#define PRINT_REG_INFO(REG)                                 \
  printf("register " X_REG_NAME(REG) ", @%x, is byte: %s",  \
         X_REG_ADDR(REG), X_REG_IS_BYTE(REG) ? "yes" : "no")

REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)

PRINT_REG_INFO(WIDGET);
PRINT_REG_INFO(GADGET);

输出:

get_byte(0x1000)
get_word(0x1002)

printf("register " "I2C Acme Widget" ", @%x, is byte: %s", 0x1000, 1 ? "yes" : "no");
printf("register " "SPI Mikro Gadget" ", @%x, is byte: %s", 0x1002, 0 ? "yes" : "no");

第 4 部分

注意这里的第 3 部分 DATA_TYPE 是如何拼接成 REG 类型的,它有四个元素。也就是说,REG 访问器处理展开数据类型字段的平面结构。这可能是不希望的。如果我们想向 DATA_TYPE 添加一个字段,我们必须编辑包含它的所有其他类型以向其函数添加参数。

解决方法是让我们的构造函数添加括号,如下所示:

#define REG(ADDRESS, DATA_TYPE, NAME) (ADDRESS, DATA_TYPE, NAME)

然后调用 X_ 扩展宏而不带括号:

#define X_REG_ACCESS(ADDR, DATA_TYPE, NAME) ACCESS_FN(DATA_TYPE)(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS REG

注意X_REG_ACCESS扩展如何从ACCESS_FN(ADDR)变为ACCESS_FN(DATA_TYPE)(ADDR)DATA_TYPE现在被封装了,我们必须使用它的访问器来获取访问函数名。

修改了整个第 3 部分的解决方案:REG 又是一个三元组,其中 two-element DATA_TYPE 是一个元素:

// Machine type abstraction byte or word
#define DATA_TYPE(IS_BYTE, ACCESS_FN) (IS_BYTE, ACCESS_FN)

#define X_IS_BYTE(IS_BYTE, ACCESS_FN) IS_BYTE
#define IS_BYTE(DATA_TYPE) X_IS_BYTE DATA_TYPE

#define X_ACCESS_FN(IS_BYTE, ACCESS_FN) ACCESS_FN
#define ACCESS_FN(DATA_TYPE) X_ACCESS_FN DATA_TYPE

// Register abstraction
#define REG(ADDRESS, DATA_TYPE, NAME) (ADDRESS, DATA_TYPE, NAME)

#define X_REG_ACCESS(ADDR, DATA_TYPE, NAME) ACCESS_FN(DATA_TYPE)(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS REG

#define X_REG_ADDR(ADDR, DATA_TYPE, NAME) ADDR
#define REG_ADDR(REG) X_ADDR REG

#define X_REG_NAME(ADDR, DATA_TYPE, NAME) NAME
#define REG_NAME(REG) X_REG_NAME REG

#define X_REG_IS_BYTE(ADDR, DATA_TYPE, NAME) IS_BYTE(DATA_TYPE)
#define REG_IS_BYTE(REG) X_REG_IS_BYTE REG

// Database of machine types
#define BYTE DATA_TYPE(1, get_byte)
#define WORD DATA_TYPE(0, get_word)

#define WIDGET REG(0x1000, BYTE, "I2C Acme Widget")
#define GADGET REG(0x1002, WORD, "SPI Mikro Gadget")

// Debug print about register
#define PRINT_REG_INFO(REG)                                 \
  printf("register " X_REG_NAME REG ", @%x, is byte: %s",  \
         X_REG_ADDR REG, X_REG_IS_BYTE REG ? "yes" : "no")

REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)

PRINT_REG_INFO(WIDGET);
PRINT_REG_INFO(GADGET);