代码之家  ›  专栏  ›  技术社区  ›  geschema

C宏:将位掩码定义的寄存器字段设置为给定值。

c
  •  2
  • geschema  · 技术社区  · 15 年前

    我有32位寄存器,字段定义为位掩码,例如

    #define BM_TEST_FIELD 0x000F0000
    

    我需要一个宏,它允许我将寄存器的字段(由它的位掩码定义)设置为给定的值(由它的地址定义)。我想到的是:

    #include <stdio.h>
    #include <assert.h>
    
    typedef unsigned int u32;
    
    /* 
     * Set a given field defined by a bit-mask MASK of a 32-bit register at address
     * ADDR to a value VALUE.
     */
    #define SET_REGISTER_FIELD(ADDR, MASK, VALUE)                                      \
    {                                                                                  \
      u32 mask=(MASK); u32 value=(VALUE);                                              \
      u32 mem_reg = *(volatile u32*)(ADDR); /* Get current register value           */ \
      assert((MASK) != 0);                  /* Null masks are not supported         */ \
      while(0 == (mask & 0x01))             /* Shift the value to the left until    */ \
      {                                     /* it aligns with the bit field         */ \
        mask = mask >> 1; value = value << 1;                                          \
      }                                                                                \
      mem_reg &= ~(MASK);                   /* Clear previous register field value  */ \
      mem_reg |= value;                     /* Update register field with new value */ \
      *(volatile u32*)(ADDR) = mem_reg;     /* Update actual register               */ \
    }
    
    /* Test case */
    #define BM_TEST_FIELD 0x000F0000
    int main()
    {
      u32 reg = 0x12345678;
      printf("Register before: 0x%.8X\n", reg);/* should be 0x12345678 */
      SET_REGISTER_FIELD(&reg, BM_TEST_FIELD, 0xA);
      printf("Register after: 0x%.8X\n", reg); /* should be 0x123A5678 */
      return 0;
    }
    

    有更简单的方法吗?

    编辑 :特别是,我正在寻找一种减少运行时计算需求的方法。有没有办法让预处理器计算值所需的左移次数?

    4 回复  |  直到 15 年前
        1
  •  4
  •   Matthew Slattery    15 年前

    编辑:特别是,我正在寻找一种减少运行时计算需求的方法。有没有办法 要让预处理器计算值所需的左移位数吗?

    对:

    value *= ((MASK) & ~((MASK) << 1))
    

    这种乘法 value 按中的最低设置位 MASK . 众所周知,乘数在编译时是2的恒定幂,因此任何远程健全的编译器都会将其编译为简单的左移位。

        2
  •  2
  •   Carl Norum    15 年前

    为什么不把蒙版和值都放在正确的位置呢?

    #define BM_TEST_FIELD (0xfUL << 16)
    #define BM_TEST_VALUE (0xaUL << 16)
    #define mmioMaskInsert(reg, mask, value) \
       (*(volatile u32 *)(reg) = (*(volatile u32 *)(reg) & ~(mask)) | value)
    

    然后你可以像这样使用它:

    mmioMaskInsert(reg, BM_TEST_FIELD, BM_TEST_VALUE);
    

    当然,你所拥有的是非常危险的。寄存器写入通常会产生副作用,这些操作:

    mem_reg &= ~(MASK);
    mem_reg |= value;
    

    实际上是两次,而不是一次,就像你想写的那样。另外,为什么不支持0的掩码?如果我想写入整个寄存器(计时器计数匹配或其他什么)?您对该操作有不同的宏吗?如果是,为什么不把它作为这个系统的一部分?

    另一个注意事项-如果有人传递的值比掩码的位更多,那么在将该值粘贴到寄存器之前将掩码应用于该值可能是一个好主意。类似:

    #define maskInsert(r, m, v) \
      (*(volatile u32 *)(r) = (*(volatile u32 *)r & ~(m)) | ((v) & ~(m)))
    
        3
  •  2
  •   Arkku    15 年前

    我愿意 考虑 使用位字段为硬件“格式化”位,例如:

    #include <stdio.h>
    #include <inttypes.h>
    
    struct myregister {
        unsigned upper_bits:12;
        unsigned myfield:4;
        unsigned lower_bits:16;
    };
    
    typedef union {
        struct myregister fields;
        uint32_t value;
    } myregister_t;
    
    int main (void) {
        myregister_t r;
        r.value = 0x12345678;
        (void) printf("Register before: 0x%.8" PRIX32 "\n", r.value);
        r.fields.myfield = 0xA;
        (void) printf("Register after: 0x%.8" PRIX32 "\n", r.value);
        return 0;
    }
    

    编辑: 注意评论中的后续讨论。虽然存在反对使用位域的有效论据,但在我看来,这也有好处(尤其是在语法方面,我非常重视)。我们应该根据使用代码的环境来决定。

        4
  •  1
  •   AnT stands with Russia    15 年前

    如果您坚持这个特定的接口(字段的位置是由掩码定义的),那么在您的实现中唯一可以更改/改进的事情可能是将值转移到适当的位置(将其与掩码对齐)的循环。基本上,你要做的就是找到 抵消 以位数表示,值左移该位数。您使用一个简单的循环来执行该操作,而不是显式计算以位为单位的偏移量,而是在每次迭代中将值左移1位。这会奏效的。然而,它可能被视为效率低下,尤其是对于位于寄存器上部的字段,因为它们将需要移位循环的更多迭代。

    为了提高效率,您还可以使用任何一种非常著名的、可能更有效的方法来计算偏移值,如中所述 this page . 不过,我不知道在你的情况下,这是否值得付出努力。它可能会提高代码的效率,但也可能降低代码的可读性。自己决定。