2. STM32 SPI外设架构:从框图到寄存器,一次讲透

好,咱们进入第二章。这一章我打算把SPI外设的“里子”翻出来给你看。说白了,就是搞清楚SPI模块在芯片里到底长什么样、时钟怎么喂给它、寄存器怎么玩、中断和DMA怎么跟它搭上线。

很多初学者喜欢直接抄代码,一上来就调库函数。我个人习惯是先看框图,再看时钟树,最后才碰寄存器。为什么?因为只有知道数据从哪流到哪,你写代码时才不会“盲调”。

2.1 SPI外设框图:数据流动的“高速公路”

先看这张核心框图。我把它简化了,但保留了最关键的部分。

STM32 SPI外设核心架构框图 控制逻辑 SPI_CR1 / CR2 波特率发生器 BR[2:0] 分频 8/16位移位寄存器 发送/接收双缓冲 MSB/LSB 可配置 SPI_DR 发送/接收共用 引脚控制逻辑 SCK 时钟输出 MOSI 主出从入 MISO 主入从出 NSS 片选 中断 / DMA 请求 TXE / RXNE / ERR 数据流向 SPI_DR → 移位寄存器 → MOSI MISO → 移位寄存器 → SPI_DR 时钟路径 PCLK → 波特率发生器 → SCK SCK 驱动移位节奏

这张图你得记住几个关键点:

  • 控制逻辑:负责解析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后,问题秒解。

时钟路径是这样的:

  1. 系统时钟(HSE/HIS/PLL)经过AHB预分频器得到HCLK。
  2. HCLK再经过APB1/APB2预分频器得到PCLK1/PCLK2。
  3. PCLKx直接喂给SPI的波特率发生器。
  4. 波特率发生器通过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只跑一次就罢工了。