STM32杂记
<摘要>
地址空间与寄存器映射
STM32 采用统一编址方式,外设和存储器被统一映射到逻辑内存上。软件上可以用指针操作内存的方式访问外设对应的寄存器和硬件电路。给寄存器分配逻辑地址并对寄存器命名的过程称为寄存器映射。片上外设通常以四个字节,即 32 bit 为一个单元,每个单元对应不同的功能。
STM32 的地址范围为 0x0000 0000 到 0xFFFF FFFF,共计 4G 个地址。一个地址对应一个字节的存储单元,注意这里是按 byte 而不是 bit 编址,因此 32 位地址空间可以寻址 4GB 的逻辑内存。STM32 中的 32 位并不等价于封装上有 32 根物理地址线,而是指 MCU 内核处理数据时每次可以处理的数据位宽为 32 bit;也由于这个原因,STM32 内部寄存器通常是 32 位的。
每一个外设都占用系统中的一段地址空间,因此所有外设都是可寻址的。Cortex 内核通过 AMBA 总线矩阵可以访问到总线上的任意外设。
STM32 中的寄存器分类如下。
GPIO 与引脚复用
GPIO(General Purpose Input Output,通用输入输出端口)负责采集外部器件的信息,或者控制外部器件工作。它是最基础的输入输出外设,也是其它外设实现的物理基础,对外表现为 MCU 封装上的芯片引脚。
输入输出系统需要解决速度匹配、信号电平和驱动能力、信号形式匹配(模拟/数字)、信号格式匹配(串行/并行)、时序配合等问题。数据输入接口要求具有三态输出能力,数据输出接口要求具有数据锁存能力。逻辑电平到物理电平的转换由 GPIO 和相应的驱动电路实现。GPIO 的电路结构如下。
这样的 GPIO 电路可以通过内部寄存器配置为八种模式:
- 输入浮空
- 输入上拉
- 输入下拉
- 模拟
- 上拉或下拉的开漏输出,用于共用总线的信号
- 上拉或下拉的推挽输出,需要增强驱动能力时可以上拉
- 上拉或下拉的复用功能推挽
- 上拉或下拉的复用功能开漏
IO 端口的输入输出由 GPIO 外设控制时称为通用;IO 端口的输入输出由其它非 GPIO 外设控制时称为复用。STM32 中的 GPIO 引脚以组的形式存在,组数视芯片而定,每组包含 16 个 IO 引脚和若干 32 位寄存器。各个寄存器的具体配置需要查阅对应芯片的参考手册。
片上外设系统
在 STM32 中,外设通常指芯片内部用于实现特定功能的硬件模块,更准确地说是芯片内部的外设控制器。完整的外设系统由外设控制器实现控制逻辑,再通过 GPIO 输出驱动传感器、电机舵机、按键显示器等外部设备。
外设控制器用晶体管硬件固化具体操作的功能模块,这是外设驱动的硬件部分。在软件上,一般通过硬件抽象层(Hardware Abstract Layer,HAL)以配置寄存器的方式使用这些外设。ST 提供的参考手册会给出每个外设对应的工作原理框图和寄存器配置说明,即说明每一位代表什么。实际开发中通常不会直接跟寄存器打交道,而是使用 HAL 库和 CubeMX 这样的工具配置外设,从而隐藏硬件细节并专注于应用层开发。
外设接口通过引脚复用挂接在 GPIO 上,大致可以分为以下几类:
- 串行通信接口:USART、UART、SPI、I2C、CAN、USB
- 并行接口:FSMC、FMC、LCD
- 模拟接口:ADC、DAC
- 定时器接口:TIMx、输入捕获、输出比较
- 控制接口:EXTI、PWM 控制、电机驱动等
每个外设都会从 AHB 上分出来的 APB1 或 APB2 上经过分频器拿到自己的时钟,且基本上所有外设都配备了相应的中断。
AMBA 总线
STM32 使用 AMBA 标准总线架构(Advanced Microcontroller Bus Architecture)。AMBA 总线是芯片内部总线,即 SoC 内部总线,用于连接 CPU、内部 SRAM/Flash、DMA、外设控制器等。注意它需要和外设通信总线区分开:外设通信总线是 MCU 与外部设备或内部多个外设模块之间的通信协议。
AMBA 架构中常见的总线包括:
- AHB:高速系统总线,用于 CPU、DMA、存储器、外设之间的高速访问,支持流水线操作、多个总线主设备和 burst 传输,上升沿触发操作。
- APB:低速外设总线,用于连接定时器、串口、I2C 等低速外设,接口简单,在 Bridge 中锁存地址和控制信号,上升沿触发操作。
- AXI:高性能总线,支持并行事务和突发访问,用于 Cortex-M7 / H7 等高端系列,连接 SDRAM、DTCM、外设矩阵等模块。
- DTCM / ITCM:CPU 的快速专用存储访问通道,即紧耦合存储器接口,连接内部高速 SRAM。
不同 STM32 系列会在 AMBA 基础上进一步设计自己的总线矩阵:
- F1 系列使用较简单的两级总线结构,AHB 作为主系统总线,APB1 和 APB2 通过桥接器连接到 AHB。APB1 连接低速外设,APB2 连接高速外设。
- F4 和 F7 系列引入总线矩阵概念。I-Code-Bus 负责 CPU 取指令并连接程序代码区,D-Code-Bus 负责 CPU 取常量和数据访问并连接 SRAM 与常量区,System-Bus 连接 DMA、APB 和外设,支持并行访问 Flash 和 SRAM。
- H7 系列使用双总线域和 AXI 总线矩阵,分成三个电源/总线域,支持 CPU、DMA、MDMA、USB、ETH 等多个主机同时访问存储器,并通过 AXI 总线矩阵实现高带宽和低延迟,同时支持 Cache、Prefetch 和并发访问。
时钟树
STM32 的时钟系统构成树形结构,也称时钟树。时钟树的分布路径可以归纳为:
时钟源 -> 锁相环倍频 -> SYSCLK -> 分频器 -> AHB 和 APB 总线时钟 -> 分频器 -> 各个外设时钟
时钟源包括 HSI、HSE、LSI、LSE,分别对应内部和外部的高速、低速时钟。SYSCLK 为主系统时钟,经分频后分别提供给 AHB 总线(HCLK)、APB1 总线(PCLK1)和 APB2 总线(PCLK2)。各外设从总线上再经过分频器取得满足频率要求的时钟信号。另外,处理器内核中有一个称为 SysTick(系统滴答)的定时器,通过分频器与 HCLK 连接。
中断与事件
STM32 使用嵌套向量中断控制器(NVIC)管理中断。NVIC 接收来自各个外设(包括 EXTI)的中断请求,并根据优先级调度执行相应的中断服务函数(ISR)。
中断来源可以分为三类:
- 系统中断:来自内核本身的中断,也称系统异常,如 NMI、HardFault、SysTick、SVC、PendSV 等。
- 外部中断:由 GPIO 或特定信号线触发的中断,如 EXTI0 到 EXTI15 中断信号线引发的中断。
- 外设中断:来自各种片上外设的中断,如 TIMx、USARTx、SPIx、DMAx、I2Cx、USB、CAN、ADC 等。
EXTI 是外部中断/事件控制器,负责监听来自外部引脚(如 GPIO)或内部信号(如 RTC、USB 等)的变化,产生中断请求(IRQ)或事件信号(Event),并向 NVIC 发出中断请求信号。
事件是与中断类似的处理机制,指特定条件满足时,硬件内部产生信号并触发某个外设动作。两者的区别在于:中断会打断正在执行的程序并进入中断服务函数,需要 CPU 参与;事件不会打断 CPU,也不执行中断服务函数,而是由外设之间直接协作完成。
启动方式
STM32 提供三种上电后的引导方式:从 Flash 引导启动、从 System Memory 引导启动、从 SRAM 引导启动。启动方式由 BOOT 引脚的电平值选择,本质上是将起始地址重映射到不同的存储器上。
- 从主 Flash 启动:将主 Flash 地址 0x0800 0000 映射到 0x0000 0000。代码启动后相当于从 0x0800 0000 开始运行。使用 JTAG 或 SWD 模式下载程序时,会下载到 Flash 并从中启动。
- 从系统存储器启动:将系统存储器地址 0x1FFF F000 映射到 0x0000 0000。系统存储器是芯片内部的 ROM 区域,芯片出厂时在其中预置 Bootloader,也称 ISP 程序。Bootloader 由厂家设置,一般无法修改。
- 从内置 SRAM 启动:将 SRAM 地址 0x2000 0000 映射到 0x0000 0000。代码启动后相当于从 0x2000 0000 开始运行。由于 SRAM 是易失性存储器,因此这个模式通常只用于程序调试。
HAL 库
HAL 是硬件抽象层的缩写,设计初衷是隐藏复杂的寄存器配置细节,让开发人员专注于软件逻辑。因此有必要了解 HAL 库的架构逻辑。
HAL 库有三个常见概念:
- 句柄 Handle:句柄中包含一个外设在项目流程中需要设置的各个成员变量,使用时调用初始化阶段定义的句柄即可。
- MSP 函数:MSP 即 MCU Specific Package,指和 MCU 相关的初始化过程,可以配合句柄提高移植性。
- Callback 函数:用于帮助用户编写应用层代码。以中断为例,HAL 库的中断服务程序会接管中断判断、将数据读入缓冲区、清除中断标志位等工作,用户在 Callback 回调函数中编写处理逻辑即可。
HAL 库提供的 API 可以分为四类:
- 初始化/反初始化:HAL_PPP_Init、HAL_PPP_DeInit
- IO 操作:HAL_PPP_Read、HAL_PPP_Write、HAL_PPP_Transmit、HAL_PPP_Receive
- 控制:HAL_PPP_Set、HAL_PPP_Get
- 状态和错误:HAL_PPP_GetState、HAL_PPP_GetError
在此结构下,用户代码的处理主要分为三部分:处理外设句柄、处理 MSP、处理各种回调函数。
- 外设句柄定义:每个外设被抽象成一个名为 ppp_HandleTypeDef 的结构体,其中 ppp 是外设名。所有函数都工作在 ppp_HandleTypeDef 指针之下。外设句柄支持多实例,即每个外设或模块实例都有自己的句柄,因此实例资源相互独立。以 ADC 为例,句柄用于管理外设例程之间共享的数据资源。
- 三种编程模式:HAL 库将函数模型统一为轮询模式、中断模式、DMA 模式(如果外设支持)。三者分别对应不同类型的函数。此外,新的 HAL 库架构下统一采用宏的形式对各种中断等进行配置。
- 三大回调函数:HAL 库负责 MCU 外设处理流程,并将必要部分以回调函数的形式暴露给用户,用户只需要在对应的回调函数中编写应用逻辑。
中间件
emWin、LVGL、lwIP、uC/OS、FreeRTOS 都是嵌入式开发中常见的中间件或操作系统组件。
- emWin:全称 SEGGER emWin,是用于嵌入式系统的图形用户界面(GUI)库,需要商业授权。
- LVGL:开源嵌入式图形库,适用于资源有限的嵌入式设备,完全开源。
- lwIP:轻量级 TCP/IP 协议栈,适用于嵌入式系统,完全开源。
- uC/OS-II / uC/OS-III:硬实时内核(RTOS),需要商业授权。
- FreeRTOS:全称 Free Real-Time Operating System,是轻量级硬实时内核。
位操作
位操作在嵌入式开发中很常见。嵌入式外设一般通过内存映射寄存器控制,每个寄存器通常为 16 位或 32 位。为了节省内存,一个或几个位往往代表一个功能开关。另外,位操作在 CPU 上通常会翻译为单条指令,没有函数调用开销。在硬件结构上,寄存器本身也是按位组织的。
为了提高位操作效率,一些单片机引入了位带映射技术。该技术将每个位(bit)与一个单独的内存地址进行映射,使得对该位的操作可以像操作普通内存变量一样进行,从而简化位操作流程。它本质上是一种内存地址映射技术:给原始内存(位带区)的每 1 个 bit 分配一个专属的别名地址(位带别名区),让操作 1 个 bit 和操作 1 个普通变量一样简单,从而解决传统位操作代码复杂、效率低、易出错的问题。
单片机的 CPU 本身不直接支持只修改 1 个 bit。CPU 最小的操作单位通常是字节(8 bit)或字(16/32 bit),位带映射则通过地址别名机制把单个位操作转化为普通地址访问。










