ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SPI通信协议详解:从四线时序到工程调试与选型实践

2026/9/7 10:43:11 拓冰建站 浏览量
SPI通信协议详解:从四线时序到工程调试与选型实践 SPI通信协议到底该怎么用搞嵌入式这么多年板子上跑得最多的串行总线除了UART就是SPISerial Peripheral Interface串行外设接口。UART负责跟人聊天SPI负责跟器件干活。Flash存储、LCD屏幕、SD卡、传感器、ADC采样哪个不是SPI的忠实用户。今天我把SPI这玩意儿从头到尾捋一遍从物理层四根线讲到时序模式再到工程里的实际配置和排障套路尽量一篇讲透。这套内容适合刚入门单片机还分不清模式0和模式3的在校学生也适合被某某屏幕花屏折腾了一下午的工程师。SPI本身不难但坑全在细节里时序参数配错一位板子就给你脸色看。1. SPI是什么四根线打天下的同步通信1.1 先看物理层MOSI、MISO、SCLK、CS各管什么SPI是主从结构的同步串行通信名字很直白Serial Peripheral Interface串行外设接口。最早是摩托罗拉推出来的后来成了事实标准。它干活靠四根线分别是MOSI主出从入、MISO主入从出、SCLK串行时钟和CS片选有的也叫SS或NSS。主设备负责产生时钟什么时候收发数据节奏完全由SCLK决定。MOSI上跑的是主机发给从机的数据MISO上跑的是从机回给主机的数据。CS这根线是当门卫的低电平有效拉低哪个从机的CS哪个从机就知道“主机要跟我说话”其他从机老老实实待在总线上看戏MISO保持高阻不掺和。这里有个很多人忽略的点SPI收发是同时进行的。主机往MOSI上发一个字节的同时从机也在MISO上把数据推回来。所以从软件角度看SPI的收发其实是同一个动作“你发送一个字节”的同时“你收到了一个字节”。这也意味着如果只想读从机的数据主机也得照样发哑巴字节通常发0x00或0xFF把时钟跑起来。这个细节在驱动W25Q256这类Flash读数据的时候特别明显读命令发完之后后续每一字节都是“边发边收”。1.2 没有地址、没有应答为什么反而成了优势用过I2C的人都知道I2C有7位从机地址总线上挂多个器件靠地址区分。SPI不走这套它没有地址的概念完全靠CS片选来区分设备。总线上一根CS对应一个从机接几个从机就引出几根CS线。这样做的好处是协议栈极简没有起始条件、停止条件、ACK应答这些繁文缛节想传数据拉低CS时钟一推就完事。坏处也很明显CS引脚消耗大。4个SPI从机就得占4个GPIO当片选。而且SPI根本没有应答机制主机发出去的数据从机到底收到没有从协议层面是完全不知道的。如果从机没上电、接线松了、或者时钟配置不对主机只能读到一堆0xFF或者垃圾数据反馈全靠业务逻辑去判断。那为什么SPI至今还是嵌入式领域的主力因为它快而且快得很简单。标准SPI可以跑到几十兆赫兹时钟W25Q256这种Flash在四线QSPI模式下甚至能到上百兆。对比I2C的标准模式才100kHz快速模式400kHz高速模式也就3.4MHz左右。SPI的全双工和高速特性在高吞吐场景下根本无人能敌。LCD刷屏、Flash读写、ADC高速采样这几个场景只要上了量I2C直接拉胯SPI才是正解。2. 模式与时序CPOL和CPHA是SPI的第一道坎2.1 四种工作模式到底在折腾什么参数SPI有四种种工作模式分别叫模式0、模式1、模式2、模式3。这四种模式由两个参数决定一个是CPOL时钟极性一个是CPHA时钟相位。很多初学者在这地方直接懵圈其实拆开看没那么玄乎。CPOL决定的是SCLK在空闲状态下是高电平还是低电平。CPOL为0时SCLK空闲拉低第一个边沿是上升沿CPOL为1时SCLK空闲拉高第一个边沿是下降沿。CPHA决定的是数据在哪个边沿被采样。CPHA为0时数据在第一个时钟边沿采样CPHA为1时数据在第二个时钟边沿才被采样。组合起来就是那张经典的表模式CPOLCPHA空闲时钟电平采样边沿Mode 000低上升沿第一个边沿Mode 101低下降沿第二个边沿Mode 210高下降沿第一个边沿Mode 311高上升沿第二个边沿工程上用的最多的是模式0和模式3。模式0是很多传感器、Flash默认的配置比如W25Q系列上电默认就是模式0和模式3都支持。模式3在一些LCD控制器上比较常见。其实模式0和模式3在绝大部分场景下都能混用因为CPOL不同但CPHA都是第一个边沿采样只要从机不挑剔主机把时钟极性改了数据照样能对得上。真正难搞的是CPHA配置错误。CPHA在模式0下上升沿采样在模式1下变成下降沿采样差了半个时钟周期。这个错位会导致数据完全串位表现出来就是主机读到的字节完全是乱的而且不是偶发是每个字节都错。遇到这种情况先别怀疑人生把CPOL和CPHA从默认配置翻出来对一遍再说。2.2 时序图阅读要点采样边沿和建立保持时间看SPI时序图是基本功。不管哪个芯片的手册SPI部分永远会画一张或多张时序图标注SCLK、MOSI、MISO、CS的波形关系。很多人拿到时序图第一反应是头大其实抓关键就够。第一看CS和SCLK的相对关系有的从机要求CS拉低之后先等一小段时间tCSS再开始跑时钟有的要求最后一个时钟结束之后再保持一段时间tCSH才允许CS拉高。这两个参数虽然以纳秒为单位但高速运行时真的会有影响尤其SCLK跑到几十兆的时候。第二看数据的建立时间tSU和保持时间tH。建立时间指数据线上电平稳定之后到采样边沿之间的时间保持时间指采样边沿之后数据还需要维持的时间。主机侧的SPI外设硬件已经处理了大部分时序约束但如果你用GPIO模拟SPI就得自己在代码里保证延时满足要求。第三看数据是在上升沿还是下降沿变化。SPI规定数据在时钟的一个边沿变化在另一个边沿被采样。如果数据变化边沿和采样边沿搞反了就必须把CPHA调换或者把CPOL调换。我的经验是先用模式0跑大多数器件都能工作如果不工作看手册里的时序图数清楚“锁存”到底是哪个边沿再回头改配置。3. 工程落地从配置到总线共享的实操记录3.1 CubeMX里SPI配置参数怎么填最稳用STM32的朋友肯定绕不开CubeMX。SPI配置界面里有几个关键参数逐个说清楚按这套填基本不会翻车。第一个是帧格式Frame Format。常用的有Motorola格式和TI格式选Motorola就对了绝大多数SPI器件都是这个格式TI格式是另一种带起始脉冲的变体预留位选8位除非器件手册特别说明要16位否则别乱动。数据大小选8位这点要注意如果一个字节是8位但器件手册里寄存器地址是按16位组织的那是传输次数的问题不是帧长的问题。第二个是时钟参数就是前面说的CPOL和CPHA。还有时钟频率Baud Rate Prescaler。时钟频率的配置原则是不要超过从机手册标称的最大SCLK频率留20%-30%的余量更稳妥。举个例子W25Q256标称最大支持104MHz但从机内部Flash操作本身有限速读的时候跑高一点无所谓写的时候太快也没意义所以我一般跑20-40MHz稳如老狗。第三个是片选策略。CubeMX的SPI配置里有个NSS引脚设置选项包括硬件流控Hardware NSS Flow Control和软件管理Software NSS。这个后面细说先记住关键结论除非你有明确的硬件连接和设计要求否则用软件片选把CS引脚配成普通GPIO输出自己控制拉高拉低能省掉一大半麻烦。关于分频系数的选择可以倒着算。比如F407的APB2时钟是84MHz如果配SPI2挂在APB1上42MHz要跑10.5MHz分频系数就是4。公式很简单SCLK频率 外设时钟 / 分频系数。CubeMX里会直接显示计算后的实际频率注意看显示出来的值别只看分频系数大小。3.2 硬件片选还是软件片选怎么选不后悔SPI的片选有两种控制方式硬件片选和软件片选。硬件片选模式下CS引脚由SPI外设自动管理。传输开始时硬件自动拉低CS传输结束后自动拉高。看起来很美但坑也不少。首先如果你用SPI连续传输多个字节中间不希望CS变化就得开“SPI_NSS_PULSE_DISABLE”之类的选项否则每个字节之间CS会跳一下很多从机直接不认。其次硬件NSS和DMA配合使用的时候CS的释放时机经常跟DMA传输完成事件对不上调试起来相当痛苦。软件片选就是CS配成普通GPIO在SPI传输之前手动拉低传输结束再手动拉高。灵活性最大想什么时候拉低就什么时候拉低想保持多久就保持多久。配合DMA的时候可以在DMA传输完成中断里再拉高CS节奏完全可控。我的个人习惯是所有项目一律软件片选除非这个MCU的SPI控制器硬件NSS有特别可靠的实现。因为软件片选牺牲的那一点CPU时间换来的是排障时极大的便利。你永远清楚CS现在是什么状态不用去猜硬件自动控制到底拉没拉。而且多从机总线共享的时候软件片选天然就适合做互斥逻辑。3.3 屏幕和SD卡共享SPI总线到底行不行网友问得特别多的一个问题是ESP32上ST7789屏幕和SD卡都挂在SPI上能不能共用哪个挂前面哪个挂后面比较好。答案是可以共用但要看情况。如果屏幕刷新率不要求很高而且SD卡的读写频率也不频繁共用一条SPI总线完全没问题。反过来如果你要做的是高刷新率的GUI屏幕每帧都在刷同时SD卡又要频繁记录数据那别犹豫分两条SPI总线一条专门伺候屏幕一条给SD卡。共用一条总线的技术关键有三点。第一是片选互斥。屏幕和SD卡各占一个CS引脚同一时刻只能有一个CS被拉低绝对禁止两个CS同时为低否则两个从机同时在MISO上驱动数据总线直接短路逻辑打架。第二是时钟速率分别配置。ST7789这种屏幕通常可以跑40MHz甚至更高而SD卡在SPI模式下很多只敢跑到25MHz左右再快就容易出错。如果共用一条总线SCLK频率就得取两者的交集要么都跑25MHz要么在切换设备时动态改分频系数。动态改分频在RTOS环境下会引入竞态所以我建议直接全局跑低一档别折腾。第三是线序和寄生电容。ESP32这类开发板引出的SPI排线如果比较长高速时钟边沿会变缓屏幕刷新时容易闪屏。实际项目中我习惯把SCLK走线尽量短、尽量直MOSI和MISO也避免与电源线长距离平行减少串扰。SD卡的话把它的CS线拉一个10k上拉电阻到VCC防止SD卡在上电瞬间因为CS悬空而误进入SPI模式。这个细节很多参考设计都会漏但少了它SD卡偶尔会初始化失败。4. SPI调试排障实录踩过的坑和排查套路4.1 常见故障现象与定位思路SPI调试到现在我总结下来最常见的故障无非下面几类按出现频率排个序每个都配上排查思路当速查表用。故障现象可能原因优先级排查方向读回全0xFFCS没拉低、从机没上电、MISO虚焊先用万用表量从机供电再量CS电平读回全0x00数据线接反、从机地址错误、模式不匹配核对MOSI/MISO是否交叉对应数据错位乱码但顺序固定CPOL/CPHA配置错了调整模式从模式0开始逐个试偶尔出错复现性差电源纹波大、SCLK太快、线太长降速到1MHz验证再逐步提高第一个字节丢后续正常CS释放太快或SCLK启动太快在CS拉低后加微小延时再开时钟DMA传输时CS提前释放硬件片选或DMA完成事件时序不对改用软件片选在DMA完成中断中拉高CS这里特别想说一说“读回全0xFF”这个现象。SPI从机的MISO引脚在没有被CS选中时通常是高阻态外部上拉电阻会把电平拉高所以主机读到的就是0xFF。如果你读到的全是0xFF第一反应不要怀疑数据对不对先确认CS有没有真正被拉低。很多时候是GPIO复用配置错了CS压根没按要求输出低电平。用示波器或者逻辑分析仪夹在CS引脚上看一眼问题马上就暴露了。另一个容易忽略的点是MOSI和MISO交叉接反。SPI主机和从机连接MOSI接从机的MOSI或者SDIMISO接从机的MISO或者SDO这是常规接法。但有些廉价扩展板丝印印得不清不楚容易把SDI和SDO搞混。遇到读回数据全是重复的固定字节或者写进去读出来对不上先检查物理连接别急着改软件。4.2 几个值得收藏的排查技巧先说逻辑分析仪。调试SPI逻辑分析仪是刚需哪怕是二十块钱的8通道小玩意儿也比没有强。把SCLK、MOSI、MISO、CS四根线夹上抓一段波形用软件自带的SPI协议解析器看数据内容和时序参数。你会发现很多百思不得其解的问题波形一出来就全明白了。比如数据错位波形上直接能看出采样边沿对不对。CS时序问题波形上也能明显看到CS提前拉高的毛刺。第二个技巧是降速验证。遇到任何SPI诡异问题第一件事把时钟频率降到1MHz重新跑一遍。很多看似玄学的“偶发错误”本质都是时钟太快、建立时间不够。降到1MHz之后如果问题消失那基本可以断定是高速时序裕量不足。这时候再想怎么优化布线、缩短线距、调整上下拉电阻而不是去改业务逻辑。第三个技巧是回环测试。怀疑MCU的SPI外设配置有问题时把MOSI和MISO短接在一起主机自己发自己收。如果收发数据一致说明外设配置没问题问题出在跟从机的系统集成上。这个招数在系统联调时特别管用能快速把问题边界缩小到“MCU这边”还是“从机那边”。第四个技巧要提一下SD卡和Flash这类器件的初始化延迟。很多从机上电之后需要一小段稳定时间太快拉CS发命令会导致器件没准备好返回垃圾数据。我习惯在主控初始化SPI外设之后延时个10毫秒再操作第一个从机给电源和器件内部上电时序留够时间。这个经验在iOS开发里叫“等一等再说话”在嵌入式里同样适用。最后分享一个我自己踩过的坑。有一次调试W25Q256读JEDEC ID偶尔会读到0xFFFFFF但不是每次都错。查了半天最后发现是CS引脚的上拉电阻焊错了位置跟另一个信号短了。万用表量的时候没量出来因为静态电平看起来正常一跑高速就出问题。后来换了逻辑分析仪抓波形才发现CS在传输过程中有个毛刺跳变。所以提醒一句抓不到问题时别不信邪换个工具重新看波形答案都在波形里。5. SPI与I2C、UART的选型对比5.1 一张表看懂SPI、I2C、UART三兄弟很多人纠结同一个外设到底用SPI还是I2C还是UART这里直接列个对比表方便选型时做决策。对比维度SPII2CUART引脚数量4根可共享时钟数据加片选2根SDASCL2根TXRX通信方式同步全双工同步半双工异步全双工从机寻址靠CS片选有7位/10位地址无寻址点对点速率量级几十MHz甚至上百MHz标准100kHz高速3.4MHz常见115200bps~几Mbps应答机制无有ACK/NACK无多从机能力靠CS线扩展靠地址扩展无最多一主一从或组网典型应用Flash、LCD、ADC、SD卡传感器、EEPROM、RTC调试串口、蓝牙模块、GPSI2C的优势在于引脚少、能挂很多器件而且有应答机制写数据之后从机不回应主机能感知到。缺点是速度慢、半双工、逻辑复杂一点。UART的优势是异步不用时钟线但收发双方得提前约定波特率而且没有寻址能力想多设备通信就得自己定义协议。工程选型逻辑其实很简单速率不够就换SPI引脚不够就换I2C远距离传输或设备互联就用UART。SPI在嵌入式里最不怂的就是速度需要刷屏、写Flash、跑大吞吐数据不要犹豫直接SPI。5.2 从成本角度看SPI的隐蔽开销SPI的引脚成本其实不止四根线那么简单。每加一个从机就要多占一个GPIO。如果MCU的GPIO本来就紧张四五个从机接上去AFIO和GPIO分配的复杂度比I2C高不少。另外SPI没有标准的外设抽象层每个芯片的SPI控制器寄存器都不太一样代码移植时得改不少底层驱动。但SPI也有一个隐形福利支持DMA的能力普遍很强尤其是STM32这类MCU的SPIDMA组合刷屏时CPU几乎不用参与。在LCD显示这种场景SPI配合DMA可以把CPU占用率压到极低这是UART和I2C在高速场景下很难做到的。如果你在选型时遇到“这个传感器只有I2C版本那个Flash只有SPI版本”的情况也不要慌。很多MCU的I2C和SPI外设引脚可以复用用CubeMX或类似工具重新映射一下就解决了。实在不行还可以用GPIO模拟但仅限于低速场景。对于高速Flash或LCD模拟SPI不是好选择CPU会忙死还是老老实实用硬件外设。我个人在实际项目里对待SPI的态度一直很明确能用硬件外设就用硬件外设逻辑分析仪什么时候都别离手CPOL/CPHA搞不定的日子就按模式0到模式3一个个试试完再看波形确认。从最早的51单片机GPIO模拟SPI读SD卡到后来STM32的硬件SPI驱动LCD刷动画再到现在物联网项目里用SPI接各种协处理器SPI这条路走了十年它依然是嵌入式系统里最可靠、最快、最直接的通信方式之一。