以stm32103c8t6为例,理解底层读写寄存器原理,从硬件地址到寄存器名称

从硬件地址到寄存器名称(如 GPIOA->ODR)的转换过程,是通过硬件地址映射+软件结构体封装实现的,下面以 STM32 的 GPIO 外设为例,完整展示这个过程:

步骤 1:硬件层面——外设寄存器的固定地址

STM32 芯片在设计时,已将所有外设(GPIO、RCC 等)的寄存器强制映射到 CPU 的地址空间,每个外设都有唯一的基地址,寄存器在基地址上有固定偏移量。

以 GPIOA 为例(不同型号地址可能略有差异,以下为 STM32F1 系列示例):

  • GPIOA 基地址0x40010800(硬件出厂时固定)

  • 内部寄存器偏移量(部分):

    • CRL(端口配置低寄存器):

      • 0x00(基地址 + 0x00)=0x40010800GPIOA 基地址)+0x00(偏移地址)=0x40010800(所有0x40010800就是GPIOA->CRL的地址)

    • CRH(端口配置高寄存器):0x04(基地址 + 0x04)

    • IDR(输入数据寄存器):0x08(基地址 + 0x08)

    • ODR(输出数据寄存器):0x0C(基地址 + 0x0C)

步骤 2:软件层面——用宏定义固定基地址

芯片厂商在头文件(如 stm32f1xx.h)中,将硬件基地址用宏定义封装,方便引用:

// GPIO 外设基地址(所有 GPIO 模块的起始地址)
#define PERIPH_BASE           0x40000000UL
#define APB2PERIPH_BASE       (PERIPH_BASE + 0x00010000UL)

// GPIOA 基地址(在 APB2 总线基地址上偏移 0x0800)
//算下来就是前面的0x40010800
#define GPIOA_BASE            (APB2PERIPH_BASE + 0x0800UL)
// GPIOB 基地址(同理)
#define GPIOB_BASE            (APB2PERIPH_BASE + 0x0C00UL)
// ... 其他 GPIO 端口(GPIOC、GPIOD 等)

此时 GPIOA_BASE 就等于硬件设计的 0x40010800

步骤 3:用结构体映射寄存器偏移量

头文件中定义一个结构体,其成员按寄存器在基地址上的偏移量顺序排列,每个成员对应一个寄存器:

// 定义 GPIO 寄存器组的结构体
typedef struct {
  __IO uint32_t CRL;  // 偏移 0x00:端口配置低寄存器
  __IO uint32_t CRH;  // 偏移 0x04:端口配置高寄存器
  __IO uint32_t IDR;  // 偏移 0x08:输入数据寄存器
  __IO uint32_t ODR;  // 偏移 0x0C:输出数据寄存器
  __IO uint32_t BSRR; // 偏移 0x10:位设置/清除寄存器
  __IO uint32_t BRR;  // 偏移 0x14:位清除寄存器
  __IO uint32_t LCKR; // 偏移 0x18:端口配置锁存寄存器
} GPIO_TypeDef;
  • __IO 是宏定义(volatile,告诉编译器该变量是“易变的”,防止优化导致的错误。

  • 结构体成员的顺序和大小(uint32_t 占 4 字节)严格对应硬件寄存器的偏移量(0x00、0x04、0x08...)。

    • Q:为什么这个结构体可以实现4个字节偏移呢?

      • A:首先我们要注意的是这里是指地址偏移,现在看的是CRL,CRH这些值对应的地址是多少,不是他们对应的大小是多少(大小默认为0也就是没有对寄存器操作),因为uint32_t 占 4 字节(在 32 位系统中占 4 字节),所以结构体每一个值的地址都会增加4字节。

步骤 4:将基地址转换为结构体指针

通过宏定义,将 GPIOA 基地址强制转换为 GPIO_TypeDef 类型的指针,赋予一个直观的名称(如 GPIOA):

// 将 GPIOA_BASE 地址转换为 GPIO_TypeDef 指针,命名为 GPIOA
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
// ... 其他 GPIO 端口

此时 GPIOA 就代表一个指向 GPIO_TypeDef 结构体的指针,其指向的地址是 0x40010800(注意这里需要强制转换为指针,编译器才知道这是一个地址)。

步骤 5:通过“指针->成员”访问寄存器

最终,通过 GPIOA->ODR 这样的写法访问寄存器,编译器会自动转换为对硬件地址的操作:

// 将 GPIOA_BASE 地址转换为 GPIO_TypeDef 指针,命名为 GPIOA
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)
// ... 其他 GPIO 端口

编译器的转换过程:

  1. GPIOA(GPIO_TypeDef *) 0x40010800(结构体指针)。

  2. GPIOA->ODR 访问结构体的第 4 个成员(ODR),其偏移量为 0x0C

  3. 实际访问的地址 = 基地址 + 偏移量 = 0x40010800 + 0x0C = 0x4001080C(这就是 GPIOA_ODR 寄存器的硬件地址)

总结:完整映射链

硬件地址(0x4001080C)
       ↑
GPIOA_BASE(0x40010800) + ODR 偏移量(0x0C)
       ↑
GPIOA((GPIO_TypeDef *) GPIOA_BASE)-> ODR(结构体成员)
       ↑
程序员写的代码:GPIOA->ODR

整个过程通过“硬件固定地址 + 软件结构体封装”,将底层硬件地址转换为直观的寄存器名称,既保证了操作的高效性,又简化了开发。其他外设(如 RCC、UART、SPI 等)的映射逻辑完全相同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值