C 并集和结构中的偏移量

C 并集和结构中的偏移量,c,struct,embedded,microcontroller,unions,C,Struct,Embedded,Microcontroller,Unions,我正在为STM32F4编写自己的头文件。我想访问这样的寄存器:GPIOB\u device.GPIOB\u MODER.B.MODER0用于位,而GPIOB\u device.GPIOB\u MODER.R用于整个寄存器 struct GPIOB_tag { union { uint32_t R:32; // 0x40020400 (32 bit) struct { uint32_t MODER15:2;

我正在为STM32F4编写自己的头文件。我想访问这样的寄存器:
GPIOB\u device.GPIOB\u MODER.B.MODER0
用于位,而
GPIOB\u device.GPIOB\u 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等,如下图所示:

如果我尝试以下代码:

GPIOB_device.GPIOB_MODER.R = 0xFFFF0000;
地址0x40020400处的32位如下所示:

00000000 00000000 11111111 11111111
00000000 00000000 11111111 10111111
10000000 00000000 11111111 11111111
为什么会这样?为什么字节颠倒了

此外,如果我尝试:

GPIOB_device.GPIOB_MODER.B.MODER0 = 0b10;
寄存器如下所示:

00000000 00000000 11111111 11111111
00000000 00000000 11111111 10111111
10000000 00000000 11111111 11111111
但它应该是这样的:

00000000 00000000 11111111 11111111
00000000 00000000 11111111 10111111
10000000 00000000 11111111 11111111
我做错了什么?

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

位字段布局由实现定义。然而,我假设在您的平台上,它是从最低位数到最高位数,而不是相反,因此您需要将所有的
MODER15
反转为
MODER0

现在,如果您根本不使用位字段,而是选择使用位掩蔽和移位,这实际上会更干净-这也是因为很多时候,人们希望用变量中的端口号来寻址GPIO端口-而这在位字段中是不可能的,因此,您可能最终会同时使用这两种方法。

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

位字段布局由实现定义。然而,我假设在您的平台上,它是从最低位数到最高位数,而不是相反,因此您需要将所有的
MODER15
反转为
MODER0


现在,如果您根本不使用位字段,而是选择使用位掩蔽和移位,这实际上会更干净-这也是因为很多时候,人们希望用变量中的端口号来寻址GPIO端口-而这在位字段中是不可能的,因此,无论如何,您可能会同时使用这两种方法。

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

除a之外,从理论上讲,联合也可能包含填充(尽管在这种特定情况下不太可能)

相反,您应该使用完全可移植的行业事实上的标准方式访问寄存器。简单地做:

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

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

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

GPIOB |=(1u不要这样做。标准中对位字段的定义太差,没有什么用处。嵌入式编译器通常会提供与您发布的结构相同的无用寄存器映射,但这些结构在编译器之间不可移植,更不用说不同的MCU了。通常,编译器依赖非标准扩展来实现这些寄存器映射ister映射,因为可移植到其他编译器通常不符合编译器供应商的利益

除a之外,从理论上讲,联合也可能包含填充(尽管在这种特定情况下不太可能)

相反,您应该使用完全可移植的行业事实上的标准方式访问寄存器。只需执行以下操作:

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

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

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

GPIOB |=(1u您是如何打印这32位的内容的?您可能会看到0xFFFF0000C99 6.7.2.1-11的典型小端表示:一个实现可能会分配任何足够大的可寻址存储单元来容纳一个位字段。如果剩余的空间足够大,结构中紧跟在另一个位字段之后的位字段应打包到相邻的存储单元中相同单元的位。如果剩余空间不足,则将不适合的位字段放入下一个单元,还是与相邻单元重叠,由实现定义。单元内位字段的分配顺序(从高阶到低阶或从低阶到高阶)已定义实现。未指定可寻址存储单元的对齐方式。我建议不要编写“自己的实现”在你理解简单的endianes问题之前..顺便说一句,大多数初学者都试图重新发明Wheels。跨编译域使用结构是一种非常糟糕的做法,预计它不会工作或随着时间的推移继续工作。属于“实现定义”这意味着你在将来某一天再次编译它,使用相同或不同的编译器,没有理由期望它按预期工作。然后你错误地假设了联合的工作方式,定义了双重实现。相反,你可以轻松地编写一些非常可移植的代码,这些代码在编译后可以归结为相同的指令。为什么这是因为它是一个实现,定义为它们将位打包到哪一端或哪里。根据编译器和设置,它们甚至可以选择使用64位数量作为最小单位,并在该空间中对齐此数据。如何打印这32位的内容?您可能会看到0xFFFF的典型小端表示0000C99 6.7.2.1-11:一个实现可以分配任何足够大的可寻址存储单元来容纳一个位字段。如果剩余空间足够大,结构中紧跟另一位字段之后的位字段应打包到同一单元的相邻位中。如果剩余空间不足,是否将不适合的位字段放入网元中xt单元或重叠相邻单元由实现定义