3. SPI 硬件控制器:寄存器映射与核心寄存器详解
在嵌入式系统中,SPI 外设的硬件控制完全通过一组内存映射寄存器实现。以典型的 STM32 MCU 为例,其 SPI 外设被映射到特定的地址空间,CPU 通过读写这些寄存器来配置模式、检查状态和收发数据。理解寄存器映射和每个控制/状态位的含义,是编写高效 SPI 驱动的基础。
3.1 SPI 外设寄存器映射概览
STM32 的 SPI 外设(如 SPI1、SPI2)挂载在高级高性能总线(AHB)或外设总线(APB)上,每个外设占用 1KB 的地址空间。以下为典型的 SPI 寄存器映射表(以 SPI1 为例,基地址 0x40013000):
| 偏移地址 | 寄存器名称 | 缩写 | 复位值 | 功能描述 |
|---|---|---|---|---|
| 0x00 | 控制寄存器 1 | CR1 | 0x0000 | SPI 使能、波特率、时钟极性和相位、主从模式等 |
| 0x04 | 控制寄存器 2 | CR2 | 0x0000 | 中断使能、DMA 使能、帧格式、SS 输出使能等 |
| 0x08 | 状态寄存器 | SR | 0x0002 | 忙标志、发送缓冲区空、接收缓冲区非空、模式错误等 |
| 0x0C | 数据寄存器 | DR | 0x0000 | 发送/接收数据缓冲区(双缓冲) |
| 0x10 | CRC 多项式寄存器 | CRCPR | 0x0007 | CRC 计算多项式系数 |
| 0x14 | RX CRC 寄存器 | RXCRCR | 0x0000 | 接收 CRC 值 |
| 0x18 | TX CRC 寄存器 | TXCRCR | 0x0000 | 发送 CRC 值 |
| 0x1C | I2S 配置寄存器 | I2SCFGR | 0x0000 | I2S 模式配置(仅 SPI/I2S 复用外设) |
| 0x20 | I2S 预分频寄存器 | I2SPR | 0x0002 | I2S 时钟分频 |
关键提示: 在驱动开发中,CR1、CR2、SR 和 DR 是最常操作的四个寄存器。CRC 相关寄存器通常在需要高可靠性通信(如 Flash 编程)时使用。I2S 寄存器仅在支持音频的外设中有效。
3.2 控制寄存器 1 (CR1) 详解
CR1 是 SPI 外设的核心配置寄存器,决定了通信的基本参数。其位定义如下:
| 位段 | 名称 | 类型 | 描述 |
|---|---|---|---|
| 15 | BIDIMODE | RW | 双向数据模式使能:0=双线单向,1=单线双向 |
| 14 | BIDIOE | RW | 双向模式下输出使能:0=接收,1=发送 |
| 13 | CRCEN | RW | 硬件 CRC 校验使能 |
| 12 | CRCNEXT | RW | 下一个数据为 CRC 值 |
| 11 | DFF | RW | 数据帧格式:0=8 位,1=16 位 |
| 10 | RXONLY | RW | 只接收模式:0=全双工,1=只接收 |
| 9 | SSM | RW | 软件从设备管理使能:0=硬件 NSS,1=软件 NSS |
| 8 | SSI | RW | 内部从设备选择位(配合 SSM 使用) |
| 7 | LSBFIRST | RW | 帧格式:0=MSB 先行,1=LSB 先行 |
| 6 | SPE | RW | SPI 外设使能:1=使能,0=禁止 |
| 5:3 | BR[2:0] | RW | 波特率控制:000=fPCLK/2,001=fPCLK/4,...,111=fPCLK/256 |
| 2 | MSTR | RW | 主从选择:1=主设备,0=从设备 |
| 1 | CPOL | RW | 时钟极性:0=空闲低电平,1=空闲高电平 |
| 0 | CPHA | RW | 时钟相位:0=第一个边沿采样,1=第二个边沿采样 |
配置示例: 设置 SPI1 为主设备、8 位数据、CPOL=0、CPHA=0、波特率 fPCLK/8:
SPI1->CR1 = (0x2 << 3) | (1 << 2) | (0 << 1) | (0 << 0); // BR=010, MSTR=1, CPOL=0, CPHA=0
SPI1->CR1 |= (1 << 6); // 使能 SPE
3.3 控制寄存器 2 (CR2) 详解
CR2 主要负责中断控制、DMA 传输和帧格式扩展:
| 位段 | 名称 | 类型 | 描述 |
|---|---|---|---|
| 15 | RXDMAEN | RW | 接收缓冲区 DMA 使能:1=使能 DMA 请求 |
| 14 | TXDMAEN | RW | 发送缓冲区 DMA 使能:1=使能 DMA 请求 |
| 13 | SSOE | RW | SS 输出使能:主模式下自动控制 NSS 引脚 |
| 12 | FRF | RW | 帧格式:0=Motorola SPI,1=TI 同步串行 |
| 11 | ERRIE | RW | 错误中断使能(CRC、模式、溢出、帧错误) |
| 10 | RXNEIE | RW | 接收缓冲区非空中断使能 |
| 9 | TXEIE | RW | 发送缓冲区空中断使能 |
| 8 | DS[3:0] | RW | 数据大小(STM32F4/F7 系列):0000=保留,0001=4 位,...,1111=16 位 |
| 7:0 | 保留 | - | 保留位,必须保持复位值 |
避坑指南: 在配置 DMA 传输时,必须同时设置 CR2 中的 RXDMAEN 和 TXDMAEN。如果只使能其中一个,会导致 DMA 传输卡死或数据错位。另外,DS 位段(数据大小)在较新系列中用于支持 4-16 位可变帧长,旧系列使用 CR1 的 DFF 位仅支持 8/16 位。
3.4 状态寄存器 (SR) 详解
SR 是驱动轮询或中断查询的关键寄存器,反映外设当前工作状态:
| 位段 | 名称 | 类型 | 描述 |
|---|---|---|---|
| 7 | BSY | R | 忙标志:1=正在通信或发送缓冲区非空,0=空闲 |
| 6 | OVR | R | 溢出错误:1=接收溢出,需软件清零 |
| 5 | MODF | R | 模式错误:1=多主冲突,需软件清零 |
| 4 | CRCERR | R | CRC 错误:1=接收 CRC 不匹配,需软件清零 |
| 3 | UDR | R | 欠载错误(I2S 模式) |
| 2 | CHSIDE | R | 声道侧(I2S 模式) |
| 1 | TXE | R | 发送缓冲区空:1=可以写入新数据,0=数据未发送完 |
| 0 | RXNE | R | 接收缓冲区非空:1=有数据可读,0=无数据 |
轮询发送/接收流程:
// 发送一个字节
while (!(SPI1->SR & SPI_SR_TXE)); // 等待 TXE=1
SPI1->DR = data;
// 接收一个字节
while (!(SPI1->SR & SPI_SR_RXNE)); // 等待 RXNE=1
received = SPI1->DR;
3.5 数据寄存器 (DR) 与双缓冲机制
DR 是 SPI 外设与 CPU 或 DMA 交换数据的窗口。STM32 的 SPI 采用双缓冲结构:
- 发送路径: CPU/DMA 写入 DR → 发送缓冲区(TX Buffer)→ 移位寄存器 → MOSI 引脚逐位输出。
- 接收路径: MISO 引脚逐位输入 → 移位寄存器 → 接收缓冲区(RX Buffer)→ CPU/DMA 读取 DR。
双缓冲的优势在于:当移位寄存器正在处理当前数据时,CPU 可以提前写入下一个数据或读取上一个已接收的数据,从而减少等待时间。
性能要点: 在高速 SPI 通信中,DR 的访问速度直接影响吞吐量。使用 DMA 传输时,DMA 控制器直接读写 DR,无需 CPU 干预,可以最大化利用双缓冲的并行能力。DR 的位宽(8 位或 16 位)由 CR1 的 DFF 或 CR2 的 DS 位段决定。
3.6 SPI 寄存器操作流程 SVG 图
下图展示了 SPI 外设寄存器之间的数据流和控制关系:
3.7 寄存器操作时序与注意事项
在实际驱动开发中,寄存器操作需要遵循特定的时序要求:
- 使能顺序: 必须先配置 CR1 的所有参数(MSTR、CPOL、CPHA、BR 等),最后再置位 SPE 使能外设。中途修改关键参数时,建议先清零 SPE。
- DR 访问时序: 写入 DR 前必须确认 TXE=1;读取 DR 前必须确认 RXNE=1。连续写入时,需等待 TXE 再次置位。
- BSY 标志: 在关闭 SPI 或修改配置前,必须等待 BSY=0,确保当前传输完成。BSY 在双线全双工模式下,从最后一个数据位采样完成后自动清零。
- 错误处理: OVR、MODF、CRCERR 等错误标志必须由软件通过读取 SR 后写入 DR 或执行特定序列清零,否则外设可能卡死。
常见错误: 初学者容易在未检查 TXE 的情况下连续写入 DR,导致数据覆盖。在高速 SPI 中,建议使用 DMA 或 FIFO(如果硬件支持)来避免此类问题。另外,在从模式下,如果 NSS 信号不稳定,可能导致 MODF 错误,需确保 NSS 引脚电平正确。
3.8 寄存器映射在内存中的布局
在 STM32 的 CMSIS 头文件中,SPI 寄存器被定义为一个结构体,方便通过指针访问:
typedef struct {
__IO uint16_t CR1; // 控制寄存器 1,偏移 0x00
__IO uint16_t CR2; // 控制寄存器 2,偏移 0x04
__IO uint16_t SR; // 状态寄存器,偏移 0x08
__IO uint16_t DR; // 数据寄存器,偏移 0x0C
__IO uint16_t CRCPR; // CRC 多项式,偏移 0x10
__IO uint16_t RXCRCR; // RX CRC,偏移 0x14
__IO uint16_t TXCRCR; // TX CRC,偏移 0x18
__IO uint16_t I2SCFGR; // I2S 配置,偏移 0x1C
__IO uint16_t I2SPR; // I2S 预分频,偏移 0x20
} SPI_TypeDef;
#define SPI1_BASE 0x40013000
#define SPI1 ((SPI_TypeDef *) SPI1_BASE)
这种结构体映射方式使得寄存器访问如同普通变量操作,编译器会自动生成合适的 LDR/STR 指令。在性能调优时,可以进一步使用 __IO 限定词确保每次访问都直接操作外设寄存器,避免编译器优化导致的数据不一致。
内存映射优势: 通过结构体指针访问寄存器,代码可读性高且易于移植。在 DMA 传输中,DMA 控制器直接使用外设地址(如 &SPI1->DR)作为源或目标地址,无需 CPU 介入地址计算。