位域写入大小
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")));
ARM 编译器 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
我有一个 volatile struct/bitfield 用于 ARM 处理器上的内存映射寄存器。特定外设必须通过字访问。
struct
{
unsigned field1 : 1;
unsigned field2 : 3;
unsigned field3 : 4;
unsigned : 24;
} volatile my_variable __attribute__((section(".bss.my_periph")));
ARM 编译器 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