【问题标题】:Accessing ARM peripheral registers through structures通过结构访问 ARM 外设寄存器
【发布时间】:2020-07-15 00:34:14
【问题描述】:

我想为支持 ARM Cortex-M3 的设备创建自己的库。当前写入寄存器如下所示:

(*((unsigned int volatile * const)(0x400E0410))) = (1 << 11) | (1 << 12);

其中0x400E0410 是 32 位外设寄存器的地址(在本例中是电源管理控制器的“外设时钟启用寄存器”的地址)。

所以我希望将外围设备抽象为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 方法都会带来使用 volatile 的副作用。
  • @old_timer 你有这些例子的链接吗?
  • st.com, github.com
  • 如果您想要基于 volatile 的方法,您的第一种方法看起来最干净。您可以将左侧的所有内容包装到一个定义中 #define SOME_REGISTER (*((unsigned int volatile * const)(0x400E0410))) .... SOME_REGISTER = (1
  • 尽管 old_timer 不喜欢使用结构来访问寄存器,但大多数用户都愿意通过使用芯片寄存器的 CMSIS 定义来沿着这个边缘跳舞。你不是有什么原因吗?

标签: c++ assembly struct arm


【解决方案1】:

所以我希望将外围设备抽象成 struct,这样对用户更加友好......

许多“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)。

我不能在其成员上使用volatile ...

似乎有些编译器甚至不允许成员使用volatile,而只允许整个struct

...编译器为其创建一个构造函数并将其存储在 RAM 中,而不是真正次优的 FLASH 中。

你使用这些structs 的方式对我来说有点奇怪。

我刚刚在 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 编译器编译了代码:生成的代码是一样的。

【讨论】:

  • 这可行,但是当将 volatile 放在结构上时,它会将整个结构包含在内存中,而不是只保留用户在代码中实际使用的部分。
【解决方案2】:

我不知道为什么我以前没有考虑过这个问题,但是函数内部的volatile 只有在您使用该函数时才会被编译器读取。所以我只是使用operator=(int)operator int() 重载创建了一个结构。

当使用优化标志时,编译器会丢弃用户不使用的所有内容,这正是我所需要的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-04
    • 1970-01-01
    • 2011-02-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多