结构和位域的奇怪行为
Struct and bitfield strange behaviour
我正在尝试修改寄存器中的位域。这是我定义了位域的结构:
struct GROUP_tag
{
...
union
{
uint32_t R;
struct
{
uint64_t bitfield1:10;
uint64_t bitfield2:10;
uint64_t bitfield3:3;
uint64_t bitfield4:1;
} __attribute__((packed)) B;
} __attribute__((aligned(4))) myRegister;
...
}
#define GROUP (*(volatile struct GROUP_tag *) 0x400FE000)
当我使用以下行时:
GROUP.myRegister.B.bitfield1 = 0x60;
它不仅改变了bitfield1,还改变了bitfield2。该寄存器的值为 0x00006060
。
代码被编译为以下汇编代码:
ldr r3,[pc,#005C]
add r3,r3,#00000160
ldrb r2,[r3,#00]
mov r2,#00
orr r2,#00000060
strb r2,[r3,#00]
ldrb r2,[r3,#01]
bic r2,r2,#00000003
strb r2,[r3,#01]
如果我尝试直接操作寄存器:
int volatile * reg = (int *) 0x400FE160;
*reg = 0x60
寄存器的值为0x00000060
.
我正在使用 GCC 编译器。
为什么我使用结构和位域时值重复?
编辑
我发现另一个奇怪的行为:
GROUP.myRegister.R = 0x12345678; // value of register is 0x00021212
*reg = 0x12345678; // value of register is 0x0004567, this is correct (I am programming microcontroller and some bits in register can't be changed)
我更改寄存器值(使用结构和位域)的方法被编译为:
ldr r3,[pc,#00B4]
ldrb r2,[r3,#0160]
mov r2,#00
orr r2,#00000078
strb r2,[r3,#0160]
ldrb r2,[r3,#0160]
mov r2,#00
orr r2,#00000056
strb r2,[r3,#0161]
ldrb r2,[r3,#0162]
mov r2,#00
orr r2,#00000034
strb r2,[r3,#0162]
ldrb r2,[r3,#0163]
mov r2,#00
orr r2,#00000012
strb r2,[r3,#0163]
啊,我明白了。编译器使用 strb
两次将两个最低有效字节写入特殊功能寄存器。但是硬件每次都执行一个字写入(大概是 32 位),因为不支持对特殊功能寄存器的字节写入。怪不得不行!
至于如何解决这个问题,这取决于您的编译器,以及它对 SFR 的了解程度。作为一种快速而肮脏的修复方法,您可以在 R
上使用位操作;而不是
GROUP.myRegister.B.bitfield1 = 0x60;
使用例如
GROUP.myRegister.R = (GROUP.myRegister.R & ~0x3FF) | 0x60;
PS 另一种可能性:您似乎关闭了优化(我在其中看到了多余的 ldrb r2,[r3,#00]
指令)。也许如果你打开它,编译器就会明白过来?值得一试...
PPS请将uint64_t
改为uint32_t
。它让我的牙齿受伤!
PPPS 想想看,packed
可能会让编译器关闭,导致它假定位域结构可能不是字对齐的(因此强制字节-按字节访问)。您是否尝试过删除它?
我正在尝试修改寄存器中的位域。这是我定义了位域的结构:
struct GROUP_tag
{
...
union
{
uint32_t R;
struct
{
uint64_t bitfield1:10;
uint64_t bitfield2:10;
uint64_t bitfield3:3;
uint64_t bitfield4:1;
} __attribute__((packed)) B;
} __attribute__((aligned(4))) myRegister;
...
}
#define GROUP (*(volatile struct GROUP_tag *) 0x400FE000)
当我使用以下行时:
GROUP.myRegister.B.bitfield1 = 0x60;
它不仅改变了bitfield1,还改变了bitfield2。该寄存器的值为 0x00006060
。
代码被编译为以下汇编代码:
ldr r3,[pc,#005C]
add r3,r3,#00000160
ldrb r2,[r3,#00]
mov r2,#00
orr r2,#00000060
strb r2,[r3,#00]
ldrb r2,[r3,#01]
bic r2,r2,#00000003
strb r2,[r3,#01]
如果我尝试直接操作寄存器:
int volatile * reg = (int *) 0x400FE160;
*reg = 0x60
寄存器的值为0x00000060
.
我正在使用 GCC 编译器。
为什么我使用结构和位域时值重复?
编辑
我发现另一个奇怪的行为:
GROUP.myRegister.R = 0x12345678; // value of register is 0x00021212
*reg = 0x12345678; // value of register is 0x0004567, this is correct (I am programming microcontroller and some bits in register can't be changed)
我更改寄存器值(使用结构和位域)的方法被编译为:
ldr r3,[pc,#00B4]
ldrb r2,[r3,#0160]
mov r2,#00
orr r2,#00000078
strb r2,[r3,#0160]
ldrb r2,[r3,#0160]
mov r2,#00
orr r2,#00000056
strb r2,[r3,#0161]
ldrb r2,[r3,#0162]
mov r2,#00
orr r2,#00000034
strb r2,[r3,#0162]
ldrb r2,[r3,#0163]
mov r2,#00
orr r2,#00000012
strb r2,[r3,#0163]
啊,我明白了。编译器使用 strb
两次将两个最低有效字节写入特殊功能寄存器。但是硬件每次都执行一个字写入(大概是 32 位),因为不支持对特殊功能寄存器的字节写入。怪不得不行!
至于如何解决这个问题,这取决于您的编译器,以及它对 SFR 的了解程度。作为一种快速而肮脏的修复方法,您可以在 R
上使用位操作;而不是
GROUP.myRegister.B.bitfield1 = 0x60;
使用例如
GROUP.myRegister.R = (GROUP.myRegister.R & ~0x3FF) | 0x60;
PS 另一种可能性:您似乎关闭了优化(我在其中看到了多余的 ldrb r2,[r3,#00]
指令)。也许如果你打开它,编译器就会明白过来?值得一试...
PPS请将uint64_t
改为uint32_t
。它让我的牙齿受伤!
PPPS 想想看,packed
可能会让编译器关闭,导致它假定位域结构可能不是字对齐的(因此强制字节-按字节访问)。您是否尝试过删除它?