位域写入大小

Bitfield write size

我有一个 volatile struct/bitfield 用于 ARM 处理器上的内存映射寄存器。特定外设必须通过字访问。

struct
{
  unsigned field1 : 1;
  unsigned field2 : 3;
  unsigned field3 : 4;
  unsigned : 24;
} volatile my_variable __attribute__((section(".bss.my_periph")));

A​​RM 编译器 V5 生成 32 位访问。 ARM Compiler V6 足够聪明,可以看到只有 field2 发生了变化并生成 8 位访问。这些 8 位访问打破了世界。

有没有办法确保访问是按字完成的?

我希望这样的事情会起作用,但我宁愿避免合并:

union
{
  struct
  {
    unsigned field1 : 1;
    unsigned field2 : 3;
    unsigned field3 : 4;
    unsigned : 24;
  } fields;
  unsigned word;
} volatile my_variable __attribute__((section(".bss.my_periph")));

我发现以下方法可以强制按字读写。

结构:

union my_type
{
  struct
  {
    unsigned field1 : 1;
    unsigned field2 : 3;
    unsigned field3 : 4;
    unsigned : 24;
  } fields;
  unsigned word;
} volatile my_variable __attribute__((section(".bss.my_periph")));

阅读:

union my_type my_type;
my_type.word = my_variable.word;
return my_type.field2;

写:

union my_type my_type = {0};
my_type.field3 = 5;
my_variable.word = my_type.word;

读取、修改、写入:

union my_type my_type;
my_type.word = my_variable.word;
mytype.field2 = 7;
my_variable.word = my_type.word;

Is there a way to ensure that accesses are done by word?

是的,不使用位域,但 uint32_t。这也消除了由位字段引起的大量其他问题,例如未定义的位顺序、字节顺序、对齐、填充、“存储单元边界”、实际支持的整数类型、有符号与无符号等。所有这些问题的根源是几乎不存在的位域标准化。

改为使用按位运算符和掩码。您可以只选择 my_var = THIS | THAT | ~(NOT_THAT);,这对于大多数用例来说可能都很好。保持简单。

如果我们坚持更高级、更奇特的解决方案,我们可以编写更详细的宏 - 这是一个非常冗长的解决方案,但我只想展示选项:

// positions of fields within the bit-field
#define MYVAR_FIELD1_POS 31
#define MYVAR_FIELD2_POS 28
#define MYVAR_FIELD3_POS 24

// sizes of fields
#define MYVAR_FIELD1_SIZE 1
#define MYVAR_FIELD2_SIZE 3
#define MYVAR_FIELD3_SIZE 4

// bit masks based on sizes
#define MYVAR_FIELD1_MASK ((1u << MYVAR_FIELD1_SIZE)-1)
#define MYVAR_FIELD2_MASK ((1u << MYVAR_FIELD2_SIZE)-1)
#define MYVAR_FIELD3_MASK ((1u << MYVAR_FIELD3_SIZE)-1)

// bit masks with a value shifted to the correct position
#define MYVAR_FIELD1(val) ( ((val) & MYVAR_FIELD1_MASK) << MYVAR_FIELD1_POS )
#define MYVAR_FIELD2(val) ( ((val) & MYVAR_FIELD2_MASK) << MYVAR_FIELD2_POS )
#define MYVAR_FIELD3(val) ( ((val) & MYVAR_FIELD3_MASK) << MYVAR_FIELD3_POS )

这是许多为微控制器编写得更恰当的寄存器映射的工作原理。现在你可以写:

my_var = MYVAR_FIELD1(1) | MYVAR_FIELD2(5) | MYVAR_FIELD3(15);

你会得到 0xDF000000(第一个半字节为 1 | 5,然后下一个半字节为 0xF)。例如,在 gcc x86 上对上述内容进行基准测试会产生一条指令:

mov     DWORD PTR [rsp+12], -553648128

其中幻数-553648128 = 0xDF000000.

对于单条指令,只要我们传递整型常量,上面就可以归结为一个整型常量表达式。如果我们传递变量,那么它不能在编译时计算(这里的位域也没有什么不同)。对于这种情况我们应该养成使用临时变量的习惯:

uint32_t tmp_var = MYVAR_FIELD1(x) | MYVAR_FIELD2(y) | MYVAR_FIELD3(z);
volatile my_var  = tmp_var; // access the volatile qualified variable on a line of its own