【问题标题】:A C preprocessor macro to pack bitfields into a byte?将位域打包成字节的 C 预处理器宏?
【发布时间】:2009-09-15 20:54:02
【问题描述】:

我正在研究微控制器黑客,虽然我对按位运算符和硬件对话非常熟悉,但我发现生成的代码非常冗长和样板。我的高级程序员想要找到一种有效但有效的方法来清理它。

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

/* 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 位值分配给一个寄存器。

这可以做得更好吗?

【问题讨论】:

    标签: c macros bit-manipulation c-preprocessor


    【解决方案1】:

    是的,将宏 ABC 重新定义为 1 &lt;&lt; ABC,然后就可以简化了。 ORing 位掩码是任何人都会认识的非常常见的习语。把轮班职位从你的脸上去掉会有很大帮助。

    您的代码来自

    #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();
    }
    

    如果你的编译器不错,它会看到整个函数在编译时是常量,并内联结果值。

    【讨论】:

    • 如果不是因为特定于体系结构的编译器支持(在本例中为 avr-gcc)已经定义了位位置定义,我可能会这样做。我觉得很蠢。如果有任何地方必须使用它们,而不是作为左移参数......我找不到它。但事实就是如此。
    • 另外,到目前为止我看到的代码在使用 1
    • 好主意。但出于安全原因,我会在 n #define BIT(n) (1
    • @Roland:我已经解决了这个问题。谢谢。
    【解决方案2】:

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

    #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;
    }
    

    【讨论】:

      猜你喜欢
      • 2020-02-09
      • 1970-01-01
      • 2010-09-23
      • 2011-01-26
      • 1970-01-01
      • 2011-03-26
      • 2012-11-16
      • 2016-12-14
      • 2011-03-26
      相关资源
      最近更新 更多