通过结构访问 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 编译器编译了代码:生成的代码是相同的。