联合和结构的偏移

时间:2018-07-16 06:49:38

标签: c struct embedded microcontroller unions

我正在为 STM32F4 编写自己的头文件。我想访问这样的寄存器:GPIOB_device.GPIOB_MODER.B.MODER0用于位,GPIOB_device.GPIOB_MODER.R用于整个寄存器。

struct GPIOB_tag {
    union {
        uint32_t R:32;              // 0x40020400 (32 bit)
        struct { 
            uint32_t MODER15:2;
            uint32_t MODER14:2; 
            uint32_t MODER13:2; 
            uint32_t MODER12:2;
            uint32_t MODER11:2;
            uint32_t MODER10:2;
            uint32_t MODER9:2;
            uint32_t MODER8:2;
            uint32_t MODER7:2;
            uint32_t MODER6:2;
            uint32_t MODER5:2;
            uint32_t MODER4:2;
            uint32_t MODER3:2;
            uint32_t MODER2:2;
            uint32_t MODER1:2;
            uint32_t MODER0:2;
        } B;
    } GPIOB_MODER;
};

#define GPIOB_device (*(volatile struct GPIOB_tag *)0x40020400)

STM32F4手册指出MODER0的偏移量为0,MODER1的偏移量为2,MODER2的偏移量为4等,如下图所示:

enter image description here

如果我尝试以下代码:

GPIOB_device.GPIOB_MODER.R = 0xFFFF0000;

地址0x40020400的32位看起来像这样:

00000000 00000000 11111111 11111111

为什么会这样?为什么字节要反转?

此外,如果我尝试:

GPIOB_device.GPIOB_MODER.B.MODER0 = 0b10;

寄存器如下:

00000000 00000000 11111111 10111111

但它应该看起来像这样:

10000000 00000000 11111111 11111111

我在做什么错了?

2 个答案:

答案 0 :(得分:3)

您的STM32F4系统为little-endian。所有值都首先存储最低有效字节。因此,0x12345678作为单个字节0x78 0x56 0x34 0x12存储到内存中,地址从左到右递增。当前几乎所有的编程平台都是这样!

位域布局是实现定义的。但是,我认为在您的平台上,它是从最低位数到最高位数,不是,因此,您需要将所有MODER15反转为MODER0

现在,如果您根本不使用位域,而是选择使用位掩码和移位,则实际上会更干净-这也是因为很多人想解决这个问题GPIO端口的端口号包含在变量中-这对于位域是不可能的,因此无论如何您最终都可能同时拥有这两种方法。

答案 1 :(得分:1)

不要这样做。标准中对位字段的定义太差而无法使用。嵌入式编译器通常会像您发布的那样提供带有结构的垃圾寄存器映射,但是这些结构在编译器之间不可移植,更不用说不同的MCU了。通常,编译器依赖于此类寄存器映射的非标准扩展,因为移植到其他编译器通常不符合编译器供应商的利益。

whole lot of issues with bit-fields and endianess外,工会在理论上也可以包含填充(尽管在这种特定情况下非常不可能)。

相反,您应该使用完全可移植的行业实际标准方法来访问寄存器。只需:

#define GPIOB (*(volatile uint32_t*)0x40020400u)

GPIOB |=  MASK; // set bit
GPIOB &= ~MASK; // clear bit

或者

GPIOB |=  (1u << MASK);    // set bit
GPIOB &= ~(1u << MASK);  // clear bit

任何一种都可以,这只是风格问题。它们是完全可移植的,并且与病历无关。

请注意,位域也非常无用,假设您想逐位访问寄存器-那么无论如何都不能使用位域:

for(uint32_t i=0; i<32; i++)
{
  do_something( GPIOB & (1u << i) );
}

旁注0b10也是非标准的。您应尽可能避免使用非标准扩展名。 (此外,使用十六进制的全部原因是因为二进制是一种可怕的可读格式。)