C-macro:将由位掩码定义的寄存器字段设置为给定值

时间:2010-05-26 16:47:01

标签: c

我有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 个答案:

答案 0 :(得分:4)

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

是:

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

这会将value乘以MASK中的最低设置位。众所周知,乘法器在编译时是2的常数幂,因此任何远程合理的编译器都会将其编译为简单的左移。

答案 1 :(得分:2)

为什么不把面具和价值放在正确的位置?

#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)))

答案 2 :(得分:2)

我会考虑使用位域来“格式化”硬件位,例如:

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

修改:请注意评论中的后续讨论。有反对使用位域的有效论据,但在我看来,也有好处(特别是在语法方面,我非常重视)。应根据具体情况决定使用代码。

答案 3 :(得分:1)

如果你坚持使用这个特定的界面(字段的位置由掩码定义),那么在你的实现中唯一可以改变/改进的是你将值移动到正确位置的循环(将它与面具对齐)。基本上,您需要做的是找到以位数表示的偏移,并将值移位该位数。您使用普通周期来执行该操作,而不是显式计算位的偏移量,而只是在每次迭代时将值左移1位。这会奏效。但是,它可能被视为低效,特别是对于驻留在寄存器上部的字段,因为它们需要更多的移位周期迭代。

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