结构体指针指向固定地址后如何操作成员?ARM编译器是否视其为枚举?
我在阅读一篇ARM处理器裸机编程的文章时,遇到了如下代码:
struct systick { volatile uint32_t CTRL, LOAD, VAL, CALIB; }; #define SYSTICK ((struct systick *) 0xe000e010) #define BIT(x) (1UL << (x)) static inline void systick_init(uint32_t ticks) { if ((ticks - 1) > 0xffffff) return; // Systick timer is 24 bit SYSTICK->LOAD = ticks - 1; SYSTICK->VAL = 0; SYSTICK->CTRL = BIT(0) | BIT(1) | BIT(2); // Enable systick RCC->APB2ENR |= BIT(14); // Enable SYSCFG }
我无法理解,为何初始化一个结构体指针后,就能操作结构体内部的变量?ARM编译器是否将其视为枚举类型?
核心原理:内存映射寄存器的结构体映射
这和枚举类型完全没关系,本质是ARM处理器的内存映射外设机制,以及C语言结构体的内存布局特性:
外设寄存器的内存地址映射
ARM芯片会把所有外设(比如SysTick定时器、RCC时钟控制器)的寄存器映射到统一的内存地址空间里,0xe000e010就是SysTick定时器寄存器组的起始物理地址。结构体的内存布局匹配寄存器布局
定义的systick结构体里,成员变量CTRL、LOAD、VAL、CALIB都是uint32_t类型(4字节),C语言默认会按成员声明顺序,把它们连续排布在内存中,每个成员占4字节。这正好和ARM Cortex-M内核中SysTick寄存器组的硬件布局完全对应:- 起始地址
0xe000e010对应CTRL寄存器 - 偏移4字节(
0xe000e014)对应LOAD寄存器 - 偏移8字节(
0xe000e018)对应VAL寄存器 - 偏移12字节(
0xe000e01c)对应CALIB寄存器
- 起始地址
结构体指针的地址强制转换
#define SYSTICK ((struct systick *) 0xe000e010)这句是把物理地址0xe000e010强制转换成systick类型的指针。这样一来,SYSTICK->LOAD就等价于直接访问内存地址0xe000e010 + sizeof(uint32_t)处的4字节数据,也就是对应硬件的LOAD寄存器。volatile关键字的作用
结构体成员加volatile是告诉编译器:这个变量对应的是硬件寄存器,值可能会被硬件异步修改,不能做优化(比如缓存寄存器值、重写读写顺序),必须每次都直接访问内存地址。
总结一下:这是用C语言结构体的连续内存布局,来映射硬件外设的寄存器组,本质是直接操作指定内存地址的硬件寄存器,和枚举类型没有任何关系。
内容的提问来源于stack exchange,提问作者InputBlackBoxOutput

