2. STM32 SPI外设架构:从框图到寄存器,一次讲透
好,咱们进入第二章。这一章我打算把SPI外设的“里子”翻出来给你看。说白了,就是搞清楚SPI模块在芯片里到底长什么样、时钟怎么喂给它、寄存器怎么玩、中断和DMA怎么跟它搭上线。
很多初学者喜欢直接抄代码,一上来就调库函数。我个人习惯是先看框图,再看时钟树,最后才碰寄存器。为什么?因为只有知道数据从哪流到哪,你写代码时才不会“盲调”。
2.1 SPI外设框图:数据流动的“高速公路”
先看这张核心框图。我把它简化了,但保留了最关键的部分。
这张图你得记住几个关键点:
- 控制逻辑:负责解析SPI_CR1/CR2里的配置,决定主从模式、时钟极性相位、数据帧格式等。
- 波特率发生器:把PCLK时钟分频,产生SCK。分频系数由BR[2:0]决定。
- 移位寄存器:这是核心。发送时数据从SPI_DR搬到移位寄存器,再逐位从MOSI送出;接收时数据从MISO逐位移入,拼好后搬到SPI_DR。
- SPI_DR:发送和接收共用一个数据寄存器。写它等于启动发送,读它等于取走接收数据。
关键认知:SPI是全双工的。你发一个字节的同时,一定也在收一个字节。所以读SPI_DR之前,必须确保上次发送已经完成,否则会读到旧数据。我踩过这个坑,后面会细说。
2.2 时钟树与SPI时钟源:PCLK1 vs PCLK2
时钟是SPI的心脏。STM32的SPI挂在哪条总线上,决定了它的最高速度。
| SPI外设 | 挂载总线 | 时钟源 | 典型最大频率 |
|---|---|---|---|
| SPI1 | APB2 (高速总线) | PCLK2 | 36 MHz (F1系列) |
| SPI2 | APB1 (低速总线) | PCLK1 | 18 MHz (F1系列) |
| SPI3 | APB1 (低速总线) | PCLK1 | 18 MHz (F1系列) |
为什么SPI1能跑更快?因为它挂在APB2上,PCLK2最高可以到72 MHz(F1)。而APB1的PCLK1最高只有36 MHz,所以SPI2/3的时钟上限天然低一半。
我的建议:如果你的项目对SPI速率有要求(比如驱动高速ADC或TFT屏幕),优先用SPI1。我曾经在一个项目里用SPI2驱动一个5 Mbps的传感器,结果死活上不去,查了半天才发现是APB1时钟限制了。换成SPI1后,问题秒解。
时钟路径是这样的:
- 系统时钟(HSE/HIS/PLL)经过AHB预分频器得到HCLK。
- HCLK再经过APB1/APB2预分频器得到PCLK1/PCLK2。
- PCLKx直接喂给SPI的波特率发生器。
- 波特率发生器通过BR[2:0]分频,输出SCK。
举个例子:如果PCLK2 = 72 MHz,SPI1的BR[2:0]设为0b010(4分频),那么SCK = 72 / 4 = 18 MHz。
2.3 SPI寄存器详解:CR1/CR2/SR/DR
寄存器是SPI的“操作面板”。我挑最常用的几个讲,不搞寄存器手册搬运。
2.3.1 SPI_CR1 (控制寄存器1)
这个寄存器决定了SPI的基本行为。重点位如下:
| 位域 | 名称 | 作用 |
|---|---|---|
| Bit 15 | BIDIMODE | 单向/双向模式选择。一般用单向(0)。 |
| Bit 10 | SSM | 软件NSS管理。使能后NSS由内部位控制,不用外部引脚。 |
| Bit 9 | SSI | 内部NSS电平。主模式下写1,从模式下写0。 |
| Bit 8 | LSBFIRST | 数据顺序:0=MSB先行,1=LSB先行。 |
| Bit 6 | SPE | SPI使能位。写1开启外设。 |
| Bit 5:3 | BR[2:0] | 波特率分频系数。000=2分频,001=4分频...111=256分频。 |
| Bit 2 | MSTR | 主从选择:1=主模式,0=从模式。 |
| Bit 1 | CPOL | 时钟极性:0=空闲低,1=空闲高。 |
| Bit 0 | CPHA | 时钟相位:0=第一个边沿采样,1=第二个边沿采样。 |
注意:CPOL和CPHA必须和从机匹配。我曾经遇到过客户反馈通信偶尔出错,排查后发现是CPOL配置反了。SPI不像I2C有标准协议,不同厂家的从机可能用不同的极性相位组合,一定要看数据手册。
2.3.2 SPI_CR2 (控制寄存器2)
这个寄存器主要管中断和DMA:
- Bit 7 TXEIE:发送缓冲区空中断使能。当SPI_DR可写时触发。
- Bit 6 RXNEIE:接收缓冲区非空中断使能。当SPI_DR有数据可读时触发。
- Bit 5 ERRIE:错误中断使能。溢出、模式错误、CRC错误时触发。
- Bit 1 TXDMAEN:发送DMA使能。
- Bit 0 RXDMAEN:接收DMA使能。
我个人习惯:如果数据量小(几十个字节以内),用中断;数据量大(几百字节以上),必须上DMA。中断频繁进出会吃掉CPU,DMA可以解放CPU去做其他事。
2.3.3 SPI_SR (状态寄存器)
这个寄存器是只读的,用来查询SPI当前状态:
- Bit 7 BSY:忙标志。1表示正在通信或发送队列非空。发送前最好等BSY=0。
- Bit 1 TXE:发送缓冲区空。1表示SPI_DR已空,可以写入下一个数据。
- Bit 0 RXNE:接收缓冲区非空。1表示SPI_DR有数据待读取。
避坑指南:我曾经在写SPI驱动时,连续往SPI_DR写数据,没检查TXE标志。结果发现发出去的数据全是乱的。为什么?因为SPI_DR的写入速度远快于移位寄存器的发送速度,导致数据覆盖。正确做法是:每次写SPI_DR前,先等TXE=1;每次读SPI_DR前,先等RXNE=1。
2.3.4 SPI_DR (数据寄存器)
这个寄存器16位宽,但实际有效位数取决于数据帧格式(8位或16位)。
- 写操作:写入的数据进入发送缓冲区,然后自动转移到移位寄存器开始发送。
- 读操作:读取的是接收缓冲区的内容,数据来自移位寄存器。
注意:SPI_DR的读写都是直接操作硬件,没有软件缓冲。所以读写时序必须严格配合状态寄存器。
2.4 SPI中断与DMA请求映射
SPI外设可以产生三类中断事件,分别对应三个标志位:
| 中断事件 | 标志位 | 触发条件 | 典型用途 |
|---|---|---|---|
| 发送缓冲区空 | TXE | SPI_DR可写入新数据 | 中断方式发送数据 |
| 接收缓冲区非空 | RXNE | SPI_DR有数据可读 | 中断方式接收数据 |
| 错误 | OVR / MODF / CRCERR | 溢出/模式错误/CRC错误 | 异常处理 |
DMA请求映射更直接:
- 发送DMA请求:当TXE=1时,SPI向DMA控制器发出请求,DMA自动从内存搬运数据到SPI_DR。
- 接收DMA请求:当RXNE=1时,SPI向DMA控制器发出请求,DMA自动从SPI_DR搬运数据到内存。
每个STM32型号的DMA通道映射不同。以STM32F103为例:
| SPI外设 | 发送DMA通道 | 接收DMA通道 |
|---|---|---|
| SPI1 | DMA1_Channel3 | DMA1_Channel2 |
| SPI2 | DMA1_Channel5 | DMA1_Channel4 |
| SPI3 | DMA2_Channel2 | DMA2_Channel1 |
我的经验:用DMA时,记得把SPI_CR2里的TXDMAEN和RXDMAEN都置1。另外,DMA传输完成后要记得清除标志位,否则下次传输不会触发。我刚开始用DMA时,经常忘记清标志,导致DMA只跑一次就罢工了。