SPI通信协议详解:从时序原理到STM32配置与故障排查
2026/9/5 4:23:39 网站建设 项目流程

刚把一块屏点亮的时候,画面整体偏色还带雪花点,排查了半天发现根本不是屏的问题,而是SPI的时钟极性和相位配错了。类似这种"看似硬件故障、实为时序配置翻车"的案例,在我接触过的SPI通信项目里一抓一大把。SPI作为嵌入式里出场率最高的通信协议之一,门槛看着不高,但真正想调通、调稳、调到高速不丢数据,还是有不少门道。

这篇内容从SPI的核心机制讲起,把四线职责、主从模式、四种工作模式、片选策略、Cubemx配置、DMA传输这些关键点逐一拆开,最后再给一份从波形到寄存器的排查思路。不管是刚接触通信协议的新手,还是已经在用SPI但经常被时序问题困扰的工程师,都能在这篇里找到对应的答案。

1. SPI到底解决什么问题——从一条调试线上的低谷说起

我第一次认真研究SPI,是因为一个再常见不过的场景:MCU要外挂一颗Flash芯片存参数,一开始想用I2C,结果发现写入速度实在跟不上业务需求,换用SPI之后,同样是这颗芯片,时钟从400kHz拉到几兆赫兹甚至更高,性能完全不是一个量级。

SPI全称Serial Peripheral Interface,串行外设接口,由Motorola在1970年代末提出。它解决的核心问题是:在芯片与芯片之间提供一种简单、高效、全双工的数据交换通道。简单到什么程度呢?它没有复杂的协议帧、没有地址解析、没有应答重传机制,就是最朴素的"主设备控制时钟,主从设备同步移位交换数据"。这种设计哲学让它的实现成本极低,无论你在MCU上做软件模拟,还是用FPGA写Verilog逻辑,都能很快跑起来。

在实际选型中,SPI通常出现在这样几类场景:

  • 板级短距离通信,一般不超过几十厘米,走线越短越稳。
  • 对吞吐率有要求的场景,比如驱动TFT屏幕刷新、读写外部Flash、采集高速ADC数据。
  • 从设备是"被动响应型"的外设,比如传感器、存储芯片、SD卡、以太网控制器等。

反过来说,SPI不太适合做长距离传输,也几乎没有错误重传机制,数据线受到干扰时只能靠上层协议去兜底。理解它的边界,和会用它的API一样重要。

2. 四根线怎么把数据"推"出去——SPI核心机制与数据交换模型

SPI的物理层最多四根线,职责非常清晰,新手把它们记熟,后面所有时序分析都建立在这四根线上。

信号线全称方向职责
SCLKSerial Clock主→从产生移位时钟,所有数据位都在这个时钟边沿上被采样或移出
MOSIMaster Out Slave In主→从主设备发送数据给从设备
MISOMaster In Slave Out从→主从设备发送数据给主设备
CS/SSChip Select / Slave Select主→从低电平有效,选中某个从设备,让它在总线上"开口说话"

在SPI的世界里,通信的最小单位不是"字节指令",而是"时钟沿驱动的移位操作"。主设备在SCLK的某一个边沿把MOSI上的一位数据推出去,同时在另一个边沿从MISO上采回一位数据。于是,发送一个字节的过程,同时也接收一个字节,这就是SPI全双工的本质。

用生活化一点的比喻:SPI就像两个人面对面,中间放了一条传送带,SCLK是传送带转动的节奏。每响一次节拍,甲方往传送带上放一个球(MOSI发送),同时从传送带上取走乙方放的球(MISO接收)。不管你有没有球要放,节拍不能停,传送带一直转,双方始终在同时进行"放球"和"取球"这两个动作。

这个机制带出一个特别重要的推论:在SPI通信中,主设备每发出一个字节,必然会收到一个字节。哪怕你只是想读从设备的数据,也必须先发出对应的时钟脉冲,而这个时钟脉冲通常会配合发送一个"空字节"或者"读命令字节"。这也是为什么很多SPI从设备的读操作都要求主设备先发送命令地址,再持续给时钟把数据"读"出来。

3. 主从模式与多设备拓扑:谁控制时钟,谁就掌握主动权

SPI总线上永远只有一个主设备,可以有多个从设备。主设备拥有SCLK的控制权,从设备绝对不能在总线上主动发起通信,它只能在片选被拉低、并且SCLK到来时被动响应。

多从设备拓扑主要有两种连接方式,很多人第一次画原理图时容易混淆。

3.1 独立片选模式

每个从设备占用一个独立的CS引脚,主设备的GPIO够多时就采用这种方式。所有从设备的SCLK、MOSI、MISO并接在总线上,但CS互不相同。主设备要跟谁通信,就把谁的CS拉低,确保同一时刻只有一个从设备在MISO上驱动数据。

这种方式的优点是简单可靠,软件逻辑清晰,缺点是占用主设备引脚多。比如一个MCU接了4个SPI从设备,主控就要额外牺牲4个GPIO来做片选。

3.2 菊花链模式

所有从设备共用一根CS,数据的传递像串糖葫芦一样,主设备的MOSI先进入第一个从设备,它的输出再接到第二个从设备的输入,依次往后串。每个SCLK脉冲,数据在整条链路上往后移动一位。这种模式适合多个同型号从设备需要同时更新数据的场景,最典型的就是多片LED驱动芯片级联、多片移位寄存器级联。

菊花链的缺点是延迟是累加的,写一个数据要经过所有从设备,而且如果链路中某一片芯片不支持菊花链特性,整个方案就直接作废。

对于大多数工程师来说,独立片选模式是首选。它让软件调试的复杂度降到最低,也方便你单独对某个设备做读写验证。如果主控GPIO确实不够,再考虑用GPIO扩展芯片或者改走菊花链。

4. 四种工作模式不是玄学——CPOL/CPHA与时序图的对应关系

SPI协议里最容易让人犯迷糊、也是面试和技术社区里被反复讨论的点,就是四种工作模式。模式由两个参数决定:CPOL(Clock Polarity,时钟极性)和CPHA(Clock Phase,时钟相位)。

先给结论:CPOL决定SCLK空闲时的电平,CPHA决定数据在SCLK的哪个边沿被采样。

  • CPOL = 0:SCLK空闲时为低电平,有效脉冲是高电平。
  • CPOL = 1:SCLK空闲时为高电平,有效脉冲是低电平。对于新手来说,空闲电平乍一看是反的,容易记混。
  • CPHA = 0:数据在SCLK的第一个边沿被采样。如果CPOL=0,第一个边沿就是上升沿;如果CPOL=1,第一个边沿就是下降沿。
  • CPHA = 1:数据在SCLK的第二个边沿被采样。也就是第一个边沿用来移位输出数据,第二个边沿用来采样。

这四种组合形成了SPI Mode 0到Mode 3。它们与常见的器件默认配置对应关系如下:

模式CPOLCPHASCLK空闲电平数据采样边沿常见器件
Mode 000低电平上升沿绝大多数Flash、传感器、屏幕
Mode 101低电平下降沿部分音频芯片
Mode 210高电平下降沿部分特定外设
Mode 311高电平上升沿SD卡、部分无线芯片

不少芯片手册会直接写"SPI Mode 0/1/2/3"或者"CPOL=1, CPHA=1",你只需要对应成表格里的配置写到寄存器里就行。但坑在于,很多芯片手册用的是波形图而不是文字描述,你得能看懂时序图。判断方法很简单:看波形里SCLK空闲在哪儿,再看数据线在哪个边沿跳变、在哪个边沿保持稳定。数据在某个边沿跳变,意味着前一个边沿被用于采样。

我在调ST7789屏幕驱动时踩过这个坑。数据手册画的是"Data setup on rising edge, data change on falling edge",这句话翻译过来就是数据在上升沿采样、在下降沿变化,对应CPOL=0、CPHA=0的Mode 0。但有人想当然地以为"数据变化沿=采样沿",结果配成了Mode 1,屏幕整体花掉。遇到这种问题,不要急着怀疑芯片坏了,先回来看时序图。

5. 片选为什么让很多人翻车——硬件片选与软件片选的取舍

SPI协议里片选看起来简单,就是拉低选中、拉高释放,但实际项目中因为片选处理不当引发的通信故障非常多。我整理了一下,主要有三种典型场景。

5.1 先拉CS再给时钟,还是先给时钟再拉CS

绝大多数从设备要求CS先拉低、再启动SCLK,因为从设备内部需要检测CS的下降沿来复位内部状态机、准备接收第一个字节。反过来,如果SCLK先跑起来,从设备可能还在上一个通信会话的状态里,数据对不上。正确做法是:先在软件里拉低CS,然后延时一小段(哪怕只有几百纳秒,视器件要求而定),再写数据到SPI数据寄存器。通信完毕,要等最后一个字节的TXE标志置位、确保移位寄存器全部送完,再拉高CS。

5.2 硬件片选与软件片选的迷惑行为

STM32系列MCU的SPI外设支持硬件片选(NSS硬件控制)和软件片选(NSS软件管理)。很多人打开Cubemx时看到一堆NSS选项就懵了。

硬件片选模式下,NSS引脚由SPI外设自动控制,主设备发送数据前自动拉低、发送完自动拉高,好处是少占CPU、时序更精确。但坏处是,如果你在一个事务里需要连续发送多个帧而中间不允许CS翻转,硬件片选可能会在一个帧结束后就提前拉高CS,这在某些从设备看来就是一次通信中断。

软件片选模式下,NSS引脚完全由GPIO手动控制,SPI外设内部则通过配置SSI位来"欺骗"外设说片选已经有效。这种模式灵活性最高,尤其是多从设备场景,你可以完全按照自己的节奏控制每个从设备的CS时序。项目里涉及多个SPI从设备,或者需要做"连续多字节事务"时,我通常默认选软件片选配合手动GPIO。

5.3 CS引脚的电容和走线也一样重要

别以为片选只是数字电平就随意处理。CS线如果走线过长或者对地电容偏大,会导致边沿变缓。有些从设备对CS上升沿有严格的时间要求,边沿过缓可能导致它误判下一次通信的起点。我在样板调试时遇到过一种诡异现象:用示波器看波形完全正常,但换了一台机器就是偶发读写失败,最后定位到CS走线绕了很长一圈,寄生电容太大。

所以画PCB时,SPI四根线尽量等长、短走线、避免打过孔,CS线尤其不要绕着板子边缘走一大圈。频率超过10MHz时,建议在驱动端串33欧姆左右的电阻,既能抑制振铃又能减少EMI。

6. STM32 Cubemx配置SPI的完整套路与高频踩坑

STM32Cubemx的图形化配置让SPI初始化变得非常简单,但恰恰因为简单,很多细节被忽略,生成的代码里藏着隐患。我以最常见的STM32F103/F401系列为例,把配置流程捋一遍,并指出哪些地方需要手动改。

6.1 时钟与引脚的基础配置步骤

打开Cubemx后,按照这个顺序操作:

  1. 在左侧Categories里找到SPI,选择要使用的SPI外设,比如SPI1。
  2. 在Mode下选择Transmit Only Master、Receive Only Master或者Full-Duplex Master。如果是从设备就选Slave模式。
  3. 硬件NSS选择Disable,然后把片选引脚留作GPIO Output,手动控制。
  4. 在Parameter Settings里配置波特率预分频器。先看APB总线时钟频率,假设APB2是72MHz,想要SPI时钟18MHz,预分频就选4分频。
  5. 配置CPOL、CPHA为从设备要求的模式,绝大多数器件用Mode 0。
  6. 数据帧格式选8 bit(除非你确认外设需要16 bit,比如某些音频编解码器)。
  7. 配置MSB First还是LSB First。绝大多数SPI器件是MSB先行,但极少数器件是LSB先行,比如部分LED驱动芯片。这个配置错了,数据看起来就会"位序颠倒",特征是每个字节的bit0和bit7对调后的错误值。

生成代码后,Cubemx会在main函数前初始化好GPIO、时钟和SPI外设。这时候如果你是软件片选,需要在应用代码里自己写一个CS拉低/拉高的宏。

6.2 数据手册里的"读命令"和实际发送顺序不一致

SPI读操作看起来简单,但很多人第一次写外部Flash驱动时都会卡住。以W25Q256这颗典型的SPI Flash为例,读数据的流程是:

  • 拉低CS。
  • 发送命令0x03(Read Data)。
  • 发送24位地址,即3个字节,先发高字节。
  • 然后主设备连续发送任意字节(通常是0x00),每发送一个字节,就能从MISO上收到一个字节数据。
  • 读完需要的长度后,拉高CS。

有个细节需要注意:在发送命令和地址阶段的每一个字节,MISO上也在返回数据,只不过这部分数据是无效的,要丢弃。在读取数据阶段,MOSI上发的内容没有实际意义,但不能停止时钟。SPI是移位交换的,时钟停了,数据就出不来。

6.3 常见的Cubemx配置翻车点

我总结了一下平时在社区里经常看到的问题,以及对应的排查方向:

现象可能原因处理思路
读写全FFCS没拉低、模式不对、MISO没接对先检查GPIO电平,再用示波器看MISO上有没有波形
读回全00从设备没有正确驱动MISO检查从设备是否上电、复位脚是否正常、CS时序
数据错位但波形正常CPOL/CPHA配置错误回看时序图,确认采样沿和变化沿
偶尔通信失败片选时序不满足CS与SCLK之间加延时,或检查SCLK频率是否过高
首字节丢失从设备需要更长CS建立时间拉低CS后延时,再发数据

7. 进阶玩法:DMA搬运、共享总线与高速场景优化

SPI跑通基本收发只是第一步。实际项目里,当你需要在屏幕上高频刷新画面,或者持续从ADC采集数据写入Flash时,CPU轮询发送的方式很快就会撑不住。这时候SPI的进阶玩法就该登场了。

7.1 SPI和DMA配合,把CPU从搬运中解放出来

SPI的轮询发送有一个显著问题:每发送一个字节,CPU都要等待TXE标志置位,然后再次写数据寄存器。假设SPI时钟是18MHz,发送一个字节大约需要444纳秒,CPU如果以72MHz运行,相当于每个字节要等32个CPU周期,这段时间全部被浪费掉了。

DMA(Direct Memory Access)模式可以解决这个问题。配置好SPI的TX DMA通道后,只需要把要发送的数据缓冲区首地址和长度告诉DMA,DMA就会自动把数据一个一个搬运到SPI数据寄存器,搬运完成后再触发中断通知CPU。整套流程中CPU只在启动DMA和接收完成中断时介入。

配合DMA使用时有几个要点:

  • DMA缓冲区必须是连续内存,如果用的是RTOS,要特别注意缓冲区在DMA传输期间不能被释放。
  • 对于"发送缓冲区是const数组"的场景,DMA要配置成从Flash读取数据,这在STM32上需要把DMA的源地址指向Flash对应的总线地址范围,部分系列还要开启对应的DMA时钟。
  • 接收和发送同时开启DMA时,要留意两个DMA通道的优先级,别因为总线竞争导致SPI的FIFO溢出。

7.2 屏幕和SD卡共享SPI总线,仲裁策略决定稳定性

以ESP32驱动的TFT屏幕和SD卡为例,很多开发板把这两个外设挂在同一条SPI总线上,复用SCLK、MOSI、MISO,各自独立的CS引脚。这种共享方案可以省引脚,但引入了总线仲裁问题。

屏幕刷新通常是大批量连续数据,SD卡读写也涉及扇区级数据块,两者同时工作时容易发生CS交叉、甚至总线冲突。稳定方案是把高频率、大数据的设备优先级调高,每次传输一个完整的事务,不允许中途切换。在软件上,可以用互斥锁或者关中断的方式保证一个事务的原子性,比如关中断先写完一整行屏幕数据再开中断。

从硬件角度,如果板子上空间允许,更推荐把屏幕和SD卡分别挂在SPI1和SPI2上。两个外设虽然共用一个物理SPI控制器的MCU也有,但如果MCU有多个独立SPI外设,分开挂载能省掉很多软件协调的麻烦。

7.3 高速SPI的布线要点

SPI频率超过20MHz后,信号完整性就不能再忽视了。我在一块板子上把SPI时钟跑到40MHz,结果数据线长了2厘米就出现误码。后来做了几处调整:

  • 走线尽量短,SCLK与MOSI/MISO的长度差控制在等长范围内。
  • MISO线上不要串大电阻,接收端的寄生电容对高速信号影响明显。
  • SCLK两侧用地包一下,减少对其他走线的辐射干扰。
  • 如果从设备支持,优先使用更低的IO电压等级匹配,避免电平转换芯片引入额外延迟。

8. FPGA视角看SPI——仿真模型与Verilog验证要点

除了单片机,FPGA也是SPI协议的常见阵地。FPGA里用Verilog实现SPI主从机,是把协议理解到位的最佳训练场,同时也绕不开一个关键环节:仿真验证。很多人写SPI逻辑时,直接上板调试,波形乱成一团才回来改代码。正确做法是先建仿真环境,把时序模型跑通了再上板。

8.1 主机的Verilog设计要点

一个典型的SPI主机模块包含三个核心部分:波特率发生器(分频计数器)、移位寄存器、状态机。

  • 波特率发生器:由系统时钟分频产生SCLK。为了适应不同从设备,分频系数通常做成可配置寄存器。
  • 移位寄存器:在SCLK的下降沿把发送寄存器里的数据移出一位到MOSI,在上升沿采样MISO并移入接收寄存器。
  • 状态机:负责管理CS拉低、发送命令、地址、数据、接收数据、CS拉高等完整流程。

这里容易踩的坑是复位后的初始状态。CS默认要拉高,SCLK要保持空闲电平,如果初始时CS恰好被拉低,从设备可能误入通信状态。

8.2 从机的Verilog设计要点

从机模块设计比主机更微妙。由于SCLK由外部主机控制,从机内部的时钟域处理是个关键点。常见做法是:

  • 用两级触发器对SCLK和CS做打拍同步,消除亚稳态。
  • 利用打拍后的信号检测CS下降沿,作为通信开始标志。
  • 在检测到SCLK的采样沿时,把MOSI上的数据移位进入接收寄存器。
  • 在SCLK的变化沿,把发送寄存器的数据移位输出到MISO。

注意MISO的输出不能一直有效,只有在片选被拉低且通信进行中才允许驱动MISO,其他时间必须释放为高阻态,否则多从机并联时会发生总线冲突。

8.3 仿真时用task建主机模型,事半功倍

在写从机RTL代码的仿真环境时,我习惯用Verilog的task来模拟主机行为,比手动拉波形高效很多:

task spi_write_byte; input [7:0] data; integer i; begin for (i = 7; i >= 0; i = i - 1) begin mosi = data[i]; #100 sclk = 1; #100 sclk = 0; end end endtask

用这种基于延时的task可以快速搭建读、写、连续传输等各种测试场景,重点验证CS时序、SCLK边沿采样点和MISO高阻态行为。等到仿真环境全部通过后,上板调试出现的问题会少一大半。

9. 一张表排查SPI通信故障:从波形到寄存器的全链路

写到这里,我相信你已经对SPI有了体系化的认识。最后分享一套我常用的SPI故障排查流程,按顺序执行,绝大部分问题都能定位到根因。

9.1 第一层:先确认电气连接

  • 测量SCLK、MOSI、MISO、CS四根线在通信时是否有波形。
  • 如果SCLK没有波形,查看SPI外设时钟是否开启,GPIO复用功能是否配置正确。
  • 如果MISO始终为高或低,考虑从设备没上电、引脚虚焊、或者从设备输出使能条件不满足。

9.2 第二层:确认时隙与模式

  • 用示波器同时抓CS和SCLK,确认CS下降沿早于SCLK第一个边沿。
  • 确认SCLK空闲电平与CPOL配置一致。
  • 对照数据手册确认数据采样沿是否和配置一致。

9.3 第三层:检查寄存器与软件逻辑

  • 读取SPI状态寄存器,查看TXE、RXNE、BSY标志位的状态是否符合预期。
  • 如果发送正常但接收全FF,检查MISO对应的GPIO是否配置成复用功能、是否有外部上拉影响电平。
  • 如果使用DMA,检查DMA状态寄存器,确认传输是否完成、是否发生传输错误。

对于调试中常见的几种波形异常,我也做了一个快速对照表:

波形现象方向处理
CS有效期间SCLK无脉冲检查波特率配置,确认SPI已使能,确认数据已写入DR
SCLK有脉冲但MOSI一直为低检查发送数据是否写入,检查MSB/LSB配置
SCLK有脉冲但MISO无响应检查从设备供电、复位,检查CS时序,确认模式是否匹配
数据线上出现明显台阶物理层调整走线,尝试降低SPI时钟频率
偶发一个字节错乱时序检查CS建立时间,检查发送完成后是否等待BSY清零再拉高CS

10. 从IO模拟到实战的一点心得

很多人纠结要不要用IO口模拟SPI。我的经验是,MCU硬件SPI外设确实好用,但在GPIO资源紧张、或者主控根本没有SPI外设时,用IO口模拟SPI是完全可行且必要的。模拟SPI的核心就是精确控制时序,用延时函数确保SCLK的每个高低电平时间满足从设备要求,采样点放在数据稳定区间。

关于模拟SPI的采样点选择,有一个小技巧:在SCLK上升沿采样数据时,最好在上升沿之前让SCLK保持半周期的低电平,确保MOSI数据已经提前建立;如果你不确定时序裕量,可以把SCLK的低电平时间拉长一点,数据建立时间就更充裕,代价是通信速率降低。

SPI这个协议,乍一看只有四根线,似乎没什么深度,但真正用起来才会发现它像一把趁手的工具——理解了它的移位交换本质,你就能解释为什么读操作也要发数据;理解了主从模式的主动权,你就知道为什么多从设备必须靠CS仲裁;理解了四种时序模式,你就能在示波器上一眼看出模式配没配错。希望这篇内容能帮你在SPI通信的路上少走一些弯路,把更多时间花在业务逻辑本身。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询