如何以可读的方式使用 C 初始化寄存器中的位

How to initialize the bits in a register using C in a readable manner

我有一个包含多个字段的 24 位寄存器。例如,高 3 位是 "mode",低 10 位是 "data rate divisor",等等。现在,我可以计算出这 24 位中必须包含的内容并将其编码为单个十六进制数0xNNNNNN。但是,对于任何试图维护它的人来说,这都是相当不可读的。

问题是,如果我分别定义每个子字段,那么将它们一起编码的最佳方式是什么?

经典方法是对常量值使用 << 左移运算符,并将所有值与 +| 组合。例如:

*register_address = (SYNC_MODE << 21) | ... | DEFAULT_RATE;

解决方案 1

解决此问题的 "standard" 方法是使用 structbitfield 成员。像这样:

typedef struct {
    int divisor: 10;
    unsigned int field1: 9;
    char field2: 2;
    unsigned char mode: 3;
} fields;

每个字段名称后的数字指定该成员使用的位数。在上面的示例中,字段 divisor 使用 10 位,可以存储 -512511 之间的值(有符号整数),而 mode 可以存储 3 位无符号值:介于 07.

每个字段的值范围使用有关 signed/unsigned 的常用规则,但字段长度 (char/int/long) 限于指定的位数。当然,一个 char 仍然可以容纳最多 8 位,一个 short 最多可以容纳 16 a.s.o。强制规则是字段类型的通常规则,考虑到它们的大小(即在 mode 中存储 -5 会将其转换为 unsigned (实际值可能是3).

有几个问题需要注意(其中一些问题在bit fields文档页面的注释部分也提到了:

  • 结构中声明的总位数必须为 24(您的寄存器的大小);
  • 由于您的结构使用 3 个字节,因此此类结构数组中的某些位置可能会表现得很奇怪,因为它们跨越了分配单元大小(通常为 4 或 8 个字节,具体取决于硬件);
  • 标准不保证分配单元中位字段的顺序;根据体系结构,在最后的 3 字节包中,字段 mode 可能包含最高有效的 3 位或最低有效的 3 位;不过你可以很容易地解决这个问题。

您可能需要一次性处理存储在 fields 结构中的所有值。为此,您可以将结构嵌入联合中:

typedef union {
   fields f;
   unsigned int a;
} reg;

reg x;
/* Access individual fields */
x.f.mode = 2;
x.f.divisor = 42;
/* Get the entire register */
printf("%06X\n", x.a);

解决方案 2

做(某种)相同事情的另一种方法是使用宏来提取字段并组成整个寄存器:

#define MAKE_REG(mode, field2, field1, divisor) \
         ((((mode) & 0x07) << 21) | \
         (((field2) & 0x03) << 19) | \
         (((field1) & 0x01FF) << 10 )| \
         ((divisor) & 0x03FF))

#define GET_MODE(reg) (((reg) & 0xE00000) >> 21)
#define GET_FIELD2(reg) (((reg) & 0x180000) >> 19)
#define GET_FIELD1(reg) (((reg) & 0x07FC00) >> 10)
#define GET_DIVISOR(reg) ((reg) & 0x0003FF)

第一个宏将 modefield2field1divisor 值组合成一个 3 字节整数。另一组宏提取各个字段的值。他们都假设处理后的数字是无符号的。

优缺点

struct(嵌入在union)解决方案:

  • [+] 它允许编译器对您要放入字段的值进行一些检查(并发出警告);此外,它在有符号和无符号之间进行正确的转换;

宏解决方案:

  • [+]对内存对齐问题不敏感,你把位放在你想要的地方;
  • (-) 它不会检查您在字段中输入的值的范围;
  • (-) 使用宏处理有符号值有点棘手;此处建议的宏仅适用于无符号值;需要进行更多移位才能使用有符号值。