一个C预处理器宏,用于将位域打包成一个字节?

时间:2009-09-15 20:54:02

标签: c macros bit-manipulation c-preprocessor

我正在进入微控制器黑客攻击,虽然我对按位操作符和硬件说话非常熟悉,但我发现结果代码非常冗长和样板。我的高级程序员希望找到一种有效但有效的方法来清理它。

例如,寄存器中有很多设置标志:

/* Provided by the compiler */
#define SPIE 7
#define SPE 6
#define DORD 5
#define MSTR 5
#define CPOL 4
#define CPHA 3

void init_spi() {
  SPCR = (1 << SPE) | (1 << SPIE) | (1 << MSTR) | (1 << SPI2X);      
}

值得庆幸的是,有些宏隐藏了实际的端口IO操作(左侧),所以它看起来像一个简单的赋值。但对我来说,所有这些语法都很混乱。

要求是:

  • 它只需要处理多达8位,
  • 位位置必须能够以任何顺序传递,并且
  • 应该只需要传递设置位。

我想要的语法是:

SPCR =位(SPE,SPIE,MSTR,SPI2X);

到目前为止,我提出的最好的是组合宏/函数:

#define bits(...) __pack_bits(__VA_ARGS__, -1)

uint8_t __pack_bits(uint8_t bit, ...) {
    uint8_t result = 0;
    va_list args;
    va_start(args, bit);

    result |= (uint8_t) (1 << bit);

    for (;;) {
        bit = (uint8_t) va_arg(args, int);
        if (bit > 7) 
            break;
        result |= (uint8_t) (1 << bit);
    }
}

这在我的特定架构上编译为32个字节,需要61-345个周期才能执行(取决于传递了多少位)。

理想情况下,这应该在预处理器中完成,因为结果是常量,输出机器指令应该只是将8位值赋值给寄存器。

这可以做得更好吗?

2 个答案:

答案 0 :(得分:6)

是的,将ABC重新定义为1 << ABC,然后简化它。将位掩码OR化在一起是一种非常常见的习惯,任何人都会认识到这一点。让轮换位置脱离你的脸会有很大的帮助。

您的代码来自

#define SPIE 7
#define SPE 6
#define DORD 5
#define MSTR 5
#define CPOL 4
#define CPHA 3

void init_spi() {
  SPCR = (1 << SPE) | (1 << SPIE) | (1 << MSTR) | (1 << SPI2X);      
}

到这个

#define BIT(n) (1 << (n))
#define SPIE BIT(7)
#define SPE BIT(6)
#define DORD BIT(5)
#define MSTR BIT(5)
#define CPOL BIT(4)
#define CPHA BIT(3)

void init_spi() {
  SPCR =  SPE | SPIE | MSTR | SPI2X;
}

这个建议确实假设比特字段定义的使用次数比它们的定义多很多。


我觉得可能有某种方法可以使用variadic macros来实现这一点,但我无法想象任何可以轻易用作表达式的内容。但是,请考虑在生成常量的函数内创建数组文字:

#define BITS(name, ...) \
     char name() { \
         char[] bits = { __VA_ARGS__ }; \
         char byte = 0, i; \
         for (i = 0; i < sizeof(bits); ++i) byte |= (1 << bits[i]); \
         return byte; }

/*  Define the bit-mask function for this purpose */
BITS(SPCR_BITS, SPE, SPIE, MSTR, SPI2X)

void init_spi() {
    SPCR = SPCR_BITS();
}

如果您的编译器是好的,它将看到整个函数在编译时是常量,并内联结果值。

答案 1 :(得分:0)

除了预先定义的定义之外,为什么不创建自己的定义...

#define BIT_TO_MASK(n) (1 << (n))

#define SPIE_MASK BIT_TO_MASK(SPIE)
#define SPE_MASK  BIT_TO_MASK(SPE)
#define DORD_MASK BIT_TO_MASK(DORD)
#define MSTR_MASK BIT_TO_MASK(MSTR)
#define CPOL_MASK BIT_TO_MASK(CPOL)
#define CPHA_MASK BIT_TO_MASK(CPHA)

void init_spi() {
  SPCR =  SPE_MASK | SPIE_MASK | MSTR_MASK | SPI2X_MASK;
}