通过结构访问 ARM 外设寄存器
Accessing ARM peripheral registers through structures
我想为支持 ARM Cortex-M3 的设备创建自己的库。当前写入寄存器如下所示:
(*((unsigned int volatile * const)(0x400E0410))) = (1 << 11) | (1 << 12);
其中0x400E0410
是一个32位外设寄存器的地址(本例中是电源管理控制器的地址'Peripheral Clock Enable Register')。
所以我希望将外围设备抽象到 struct
中,这样它对用户更友好,可读性更强,并且允许在 IDE 中自动完成。前面的例子看起来像这样:
PMC.PCER = PORTB.ID | PORTC.ID;
我不能在 struct
或其成员上使用 volatile
,否则(据我所知)即使它实际上没有被使用,也会始终在最终代码中包含该结构对于代码中的任何内容。我还注意到,即使结构是 name-less
并且它的所有成员都已初始化为 const 值,编译器会为它创建一个构造函数并将其存储在 RAM 而不是真正次优的 FLASH 中。
最好我还希望 struct
方法生成这样的汇编代码(第一个示例的反汇编):
不是像这样的代码,而是从 RAM 中读取结构变量(我的方法是在结构中使用 volatile 成员):
如何在不影响程序大小或性能的情况下实现这一点?
编辑:我的方法的 C++ 代码,u32v 是一个无符号 volatile 32 位整数,u32c 是一个无符号 const 32 位整数
我不确定为什么我以前没有考虑过这一点,但是函数内部的 volatile
只有在您使用该函数时才会被编译器读取。所以我只是使用 operator=(int)
和 operator int()
重载制作了一个结构。
使用优化标志时,编译器会丢弃用户不使用的所有内容,这正是我所需要的。
So I wish to abstract peripherals into struct so it's a lot more user-friendly ...
许多“MCAL”数据包(用于汽车行业的硬件抽象)都是这样做的。示例:
typedef struct {
unsigned IN; /* offset 0 */
unsigned _unused1[3];
unsigned OUT; /* offset 0x10 */
unsigned _unused2[3];
unsigned DIR; /* offset 0x20 */
} PortStruct;
#define PORTA (*(volatile PortStruct *)0x80001000))
#define PORTB (*(volatile PortStruct *)0x80002000))
...所以你可以通过以下方式访问外设寄存器:
PORTA.OUT |= (1<<4);
我也看到了这样的结构被声明为变量:
extern volatile PortStruct PORTA;
...并且“变量”是通过使用特定于编译器的关键字、手写汇编代码或链接器配置文件中的特殊指令在固定地址(示例中的 0x80001000)定义的。
I can't use volatile
... on its members
似乎有些编译器甚至不允许成员使用 volatile
,而只允许整个 struct
。
... the compiler makes a constructor for it and stores it in RAM instead of the FLASH which is really suboptimal.
你使用这些 struct
的方式对我来说有点奇怪。
我刚刚使用适用于 ARM 的 GCC 工具链(C,而非 C++)尝试了以下代码并打开了优化:
typedef struct {
unsigned hello;
unsigned world;
unsigned foo;
unsigned bar;
unsigned PCER;
unsigned example;
} PortType;
#define PMC (*(volatile PortType *)0x400E0400)
void test(void)
{
PMC.PCER = 5;
}
结果(这里是目标文件):
00000000 <test>:
0: 4b01 ldr r3, [pc, #4] ; (8 <test+0x8>)
2: 2205 movs r2, #5
4: 611a str r2, [r3, #16]
6: 4770 bx lr
8: 400e0400 .word 0x400e0400
没有生成初始化代码、构造函数或类似代码。
我也尝试了extern volatile PortStruct
方法:
typedef struct {
...
} PortType;
extern volatile PortType PMC;
void test(void)
{
PMC.PCER = 5;
}
如前所述,需要一些手写的汇编代码或链接器脚本中的一些信息才能将伪变量“PMC”的地址定义为 0x400E0400。
我尝试了两种方法(汇编和链接描述文件):结果与 #define
方法完全相同。
编辑
我还使用 C++ 编译器而不是 C 编译器编译了代码:生成的代码是相同的。
我想为支持 ARM Cortex-M3 的设备创建自己的库。当前写入寄存器如下所示:
(*((unsigned int volatile * const)(0x400E0410))) = (1 << 11) | (1 << 12);
其中0x400E0410
是一个32位外设寄存器的地址(本例中是电源管理控制器的地址'Peripheral Clock Enable Register')。
所以我希望将外围设备抽象到 struct
中,这样它对用户更友好,可读性更强,并且允许在 IDE 中自动完成。前面的例子看起来像这样:
PMC.PCER = PORTB.ID | PORTC.ID;
我不能在 struct
或其成员上使用 volatile
,否则(据我所知)即使它实际上没有被使用,也会始终在最终代码中包含该结构对于代码中的任何内容。我还注意到,即使结构是 name-less
并且它的所有成员都已初始化为 const 值,编译器会为它创建一个构造函数并将其存储在 RAM 而不是真正次优的 FLASH 中。
最好我还希望 struct
方法生成这样的汇编代码(第一个示例的反汇编):
不是像这样的代码,而是从 RAM 中读取结构变量(我的方法是在结构中使用 volatile 成员):
如何在不影响程序大小或性能的情况下实现这一点?
编辑:我的方法的 C++ 代码,u32v 是一个无符号 volatile 32 位整数,u32c 是一个无符号 const 32 位整数
我不确定为什么我以前没有考虑过这一点,但是函数内部的 volatile
只有在您使用该函数时才会被编译器读取。所以我只是使用 operator=(int)
和 operator int()
重载制作了一个结构。
使用优化标志时,编译器会丢弃用户不使用的所有内容,这正是我所需要的。
So I wish to abstract peripherals into struct so it's a lot more user-friendly ...
许多“MCAL”数据包(用于汽车行业的硬件抽象)都是这样做的。示例:
typedef struct {
unsigned IN; /* offset 0 */
unsigned _unused1[3];
unsigned OUT; /* offset 0x10 */
unsigned _unused2[3];
unsigned DIR; /* offset 0x20 */
} PortStruct;
#define PORTA (*(volatile PortStruct *)0x80001000))
#define PORTB (*(volatile PortStruct *)0x80002000))
...所以你可以通过以下方式访问外设寄存器:
PORTA.OUT |= (1<<4);
我也看到了这样的结构被声明为变量:
extern volatile PortStruct PORTA;
...并且“变量”是通过使用特定于编译器的关键字、手写汇编代码或链接器配置文件中的特殊指令在固定地址(示例中的 0x80001000)定义的。
I can't use
volatile
... on its members
似乎有些编译器甚至不允许成员使用 volatile
,而只允许整个 struct
。
... the compiler makes a constructor for it and stores it in RAM instead of the FLASH which is really suboptimal.
你使用这些 struct
的方式对我来说有点奇怪。
我刚刚使用适用于 ARM 的 GCC 工具链(C,而非 C++)尝试了以下代码并打开了优化:
typedef struct {
unsigned hello;
unsigned world;
unsigned foo;
unsigned bar;
unsigned PCER;
unsigned example;
} PortType;
#define PMC (*(volatile PortType *)0x400E0400)
void test(void)
{
PMC.PCER = 5;
}
结果(这里是目标文件):
00000000 <test>:
0: 4b01 ldr r3, [pc, #4] ; (8 <test+0x8>)
2: 2205 movs r2, #5
4: 611a str r2, [r3, #16]
6: 4770 bx lr
8: 400e0400 .word 0x400e0400
没有生成初始化代码、构造函数或类似代码。
我也尝试了extern volatile PortStruct
方法:
typedef struct {
...
} PortType;
extern volatile PortType PMC;
void test(void)
{
PMC.PCER = 5;
}
如前所述,需要一些手写的汇编代码或链接器脚本中的一些信息才能将伪变量“PMC”的地址定义为 0x400E0400。
我尝试了两种方法(汇编和链接描述文件):结果与 #define
方法完全相同。
编辑
我还使用 C++ 编译器而不是 C 编译器编译了代码:生成的代码是相同的。