第一部分:上章笔记回顾——从#define到结构体

下面代码展示了两种访问STM32寄存器的方法:

方法一:使用#define进行直接地址访问(上章笔记讲述的方式)
#define GPIOB_CRL *(unsigned int*)(GPIOB_BASE + 0x00)
#define GPIOB_CRH *(unsigned int*)(GPIOB_BASE + 0x04)
// ... 其他寄存器定义

​这是什么?​​

这是一种最基础的寄存器映射。通过计算每个寄存器相对于外设基地址的偏移量,直接定义了一个指向该内存地址的指针。操作这些宏,就等于直接读写对应的物理寄存器。

  • ​优点​:直观,最接近硬件。

  • ​缺点​:

    1. ​代码冗长​:每个寄存器都需要一行#define。

    2. ​容易出错​:偏移地址需要手动计算和填写,容易写错。

    3. ​可读性差​:在代码中使用时,是一串独立的宏,不能很好地体现寄存器属于哪个外设。

GPIOB_BASE端口B外设的基地址,上一章节已经定义好了,这个地址在C语言中,是指针且为32位,指向1个4个字节的内存单元,指针P即为GPIOB_BASE的地址,同时也指向GPIOB_BASE的第一个地址。

方法二:使用结构体进行系统化访问(本章笔记采用的方法)
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

typedef struct {
    uint32_t CRL;
    uint32_t CRH;
    uint32_t IDR;
    uint32_t ODR;
    uint32_t BSRR;
    uint32_t BRR;
    uint32_t LCKR;
} GPIO_TypeDef;

#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)

​这是什么?​​

这是一种高级的寄存器映射方法。它利用了C语言结构体的特性:结构体内的成员在内存中是连续且顺序存放的。

  1. 定义了一个GPIO_TypeDef结构体,其成员变量(CRL, CRH...)的顺序和类型,完全按照官方手册中GPIO寄存器的排列顺序来设计。

  2. 然后将GPIOB_BASE(GPIOB端口的基础地址)​强制类型转换为一个指向GPIO_TypeDef结构体的指针GPIOB。

​这样做的神奇效果是:​​

GPIOB->CRL就等价于 *(unsigned int*)(GPIOB_BASE + 0x00)

GPIOB->ODR就等价于 *(unsigned int*)(GPIOB_BASE + 0x0C)

编译器会自动根据结构体成员的顺序和类型来计算偏移地址。

GPIOB是GPIO_TypeDef类型的指针,指向整个一大段内存,内存里有GPIOB下面的所有寄存器

第二部分:这种转换带来的巨大好处

  1. ​代码极其简洁​

    一行#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)就完成了之前7行代码的工作。对于有数十个寄存器的大型外设(如USART、TIMER),这种优势是压倒性的。

  2. ​可读性和可维护性大幅提升​

    • GPIOB->ODR = 0xFFFF;一看就知道是在操作GPIOB的输出数据寄存器。

    • GPIOA->CRL |= 0x00000001;一看就知道是在配置GPIOA的某个引脚。

      代码的意图非常清晰,更像是在描述“做什么”,而不是“怎么做”。

  3. ​避免人为错误​

    不再需要手动计算和填写0x00, 0x04, 0x08等偏移量。只要结构体定义正确,所有地址计算都由编译器自动完成,彻底避免了计算错误和笔误。

  4. ​为过渡到官方固件库打下坚实基础​

    官方STM32固件库(Standard Peripheral Library)以及现在主流的HAL/LL库,其最核心的基础正是——用结构体来描述整个外设。

第三部分:GPIOB的内涵详解

#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)这行代码定义了一个宏。

它的内涵是:

​GPIOB是一个常量指针,其值被固定为 GPIOB_BASE(GPIOB外设的基地址),并且这个指针被解释为指向 GPIO_TypeDef结构体类型的。​​

可以这样理解:

  • GPIOB:是整个GPIOB外设的“句柄”或“总管”。

  • GPIOB->CRL:是命令“总管”去操作其内部的端口配置低寄存器。

  • GPIOB->IDR:是命令“总管”去读取其内部的输入数据寄存器。

这样能够以一个整体视角来管理和操作一整个外设,而不是面对一堆分散的地址。

第四部分:如何参考官方手册写程序

​学习方法:​​

  1. ​找到外设章节​:比如要用GPIO,就看第8章;要用USART,就看第25章。

  2. ​找到寄存器描述小节​:例如8.2 GPIO寄存器描述。这里会逐个寄存器详细说明其功能、每一个比特位的作用、复位值等。这是你编程时配置什么值的依据。

  3. ​找到寄存器地址映射表​:例如8.5节的表52。这是定义结构体的根本依据。GPIO_TypeDef结构体里的成员顺序,必须严格照搬这个表格的顺序。

偏移地址

寄存器名称

你的结构体成员

0x00

CRL

uint32_t CRL;

0x04

CRH

uint32_t CRH;

0x08

IDR

uint32_t IDR;

0x0C

ODR

uint32_t ODR;

0x10

BSRR

uint32_t BSRR;

0x14

BRR

uint32_t BRR;

0x18

LCKR

uint32_t LCKR;

更多推荐