尧图精选

SPI通信协议详解:从时序原理到多设备实战

🕒 发布时间:2026/9/8 0:30:03 📁 来源:尧图网络
SPI 这玩意儿我玩了快十年见过太多人把它写成“玄学通信”——波形不对就甩锅“干扰”速率上不去就怪“芯片不行”。说实话SPI 是嵌入式里最耿直的兄弟不像 I2C 有地址和 ACK也不像 UART 有帧格式和波特率误差。它就是一根时钟线拉着两根数据线跑谁拉低片选谁上场。只要你会按时序图办事SPI 能做到非常优雅代码量比 I2C 少、速度比 UART 快甚至能在一条总线上挂七八个设备还互不打扰。这篇文章我准备了很久从时序原理到硬件片选与软件片选的博弈从 W25Q64 的读写到 ESP32 屏幕与 SD 卡共享总线的坑从 STM32 的 HAL 配置到 FPGA 的 Verilog 实现全部怼在一篇里。不论你是刚点亮第一个 LED 的新人还是正被多设备共享总线折磨的老兵这篇都能给你点新东西。1. 内容整体设计与思路拆解1.1 为什么说 SPI 是“四根线走天下”先说点最核心的。很多新人一上来就背“SPI 是四线制接口”但其实理解它最好的方式是从“角色分工”去看。SPI 总线上永远有一个主设备Master和若干个从设备Slave主设备负责产生时钟 SCK从设备只能被动接受时钟驱动。四根线分别是SCK 时钟线、MOSI 主出从入、MISO 主入从出以及 CS 片选线有的也叫 SS、NSS、CSN。这里的优雅之处在于只要把 CS 拉低这根线上那个从设备就被“点名”上场其他没被选中的设备全部把 MOSI 输出口置为高阻彻底闭嘴。这种工作模式天然适合一主多从所以 SPI 在 Flash、SD 卡、LCD 屏幕、传感器这些外设里满地都是。那 SPI 和 IIC 到底差在哪我用一句话给你讲透IIC 是公共汽车车上有站牌地址大家都排队上车SPI 是专线电话主设备拨号拉低 CS直接点名不需要地址这一层。所以 SPI 的帧结构比 IIC 干净得多没有地址字节、没有 ACK 位一个时钟周期就是一位数据简单、粗暴、快。但代价就是引脚更多。IIC 两根线能挂 127 个设备SPI 每加一个从设备就必须占一个片选引脚。这是 SPI 最核心的“短板”也是后面要讨论的“硬件片选 vs 软件片选”问题的根源。1.2 数据总是在时钟沿上跳舞——弄懂时序才是优雅的前提SPI 之所以被很多人觉得难其实是被“四种模式”这个概念吓住了。CPOL时钟极性和 CPHA时钟相位这两个参数看起来像数学题本质上就是回答两个问题空闲时 SCK 是高点平还是低点平数据是在 SCK 上升沿采样还是下降沿采样CPOL0 表示空闲时 SCK 拉低CPOL1 表示空闲时 SCK 拉高。CPHA0 表示第一个时钟沿就采样CPHA1 表示第二个时钟沿才采样。四个组合就是模式 0/1/2/3。我的经验是绝大多数 SPI 外设默认配置都是模式 0CPOL0CPHA0也就是 SCK 空闲为低、第一个上升沿采样数据。遇到传感器芯片或显示屏驱动 IC 时才需要去查手册确认。实话说为了这几个模式去死记硬背真没必要。我自己的方法是直接用逻辑分析仪抓默认配置的波形看看数据在哪个沿变稳定、哪个沿被锁存如果不稳就换模式试。毕竟每个外设手册里都会把时序图画得清清楚楚你照着它的时序图配置对应模式就行。像 W25Q64 这种 Flash 手册里就明确写了支持 SPI Mode 0 和 Mode 3其他模式不用想。1.3 这次博文的整体路线聊完了原理思路就顺了。我打算从时序和物理层开始然后进入“软件模拟 vs 硬件 SPI”的实现拆解接着重点聊片选信号这个最容易栽跟头的环节再往上挂资源共享总线的实战玩法中间穿插 STM32、ESP32 和 FPGA 三个平台的实现对照最后整理一份问题排查手册。整个逻辑就是从点单个设备时序到线一根总线挂多设备再到面跨平台实战循序渐进尽量让你看完就能上手干活。2. 核心细节解析与实操要点2.1 四种模式上手指南不要死记要学会“看图说话”SPI 模式这种东西理解比记忆重要。我建议每个做单片机的朋友都学会读时序图而不是去背一堆表格。以 W25Q64 读数据为例时序图的横轴是时间纵轴是各路信号的电平。图上会用虚线箭头标出“数据建立时刻”和“数据采样时刻”你只要看数据在那个沿稳定下来、又在哪个沿被主机锁存就自然知道该配哪个模式。入门的技巧是这样的先用逻辑分析仪抓设备在默认配置下的输出观察 MISO 上的数据跳变沿。如果 MISO 在 SCK 上升沿前后有足够稳定的窗口说明主机应该在上升沿采样配 CPHA0如果数据在下降沿附近才稳定就配 CPHA1。这样一轮下来你就不需要去翻成堆的芯片手册了。不过有一点必须强调时钟极性必须完全一致否则传输立刻乱掉。我见过有人把 CPOL 设反结果是屏幕也不亮、Flash 读出来全 0xFF折腾了一下午才发现是模式 3 和模式 0 混了。2.2 硬件片选与软件片选的博弈看似小问题实则大讲究片选信号是 SPI 最容易“隐形翻车”的地方。硬件片选是指用 MCU 上专门的 NSS 引脚或者由 SPI 外设自动控制某个 GPIO在每次传输开始时由硬件自动拉低、结束时自动拉高。软件片选是自己用 GPIO 手动拉低/拉高。听起来软件片选又土又麻烦实际上绝大多数项目里我反而推荐软件片选。原因很简单硬件片选引脚往往被固定在一个特定管脚上而 SPI 从设备的 CS 时序五花八门。有些 Flash 要求 CS 拉低后要等一个 tCS 时间才能开始传数据有些芯片则在 CS 拉高后还需要保持高电平一段时间才允许下一次操作。硬件自动片选在这些场景里面非常死板你不容易插入延时或调整时序软件片选则完全可控。软件片选还有一个额外好处你有多少 GPIO 就能挂多少从设备。不管 MCU 的 SPI 外设支持几个片选引脚软件片选都不受限制。我在一块 STM32F103 上曾经同时挂了 6 个 SPI 从设备片选引脚全是普通 GPIO跑得稳稳当当。那硬件片选什么时候有用只有一种场景建议用当你做高速、大批量、连续传输时硬件片选能最大限度减少驱动时序的误差。但前提是你用的 MCU 片选引脚支持引脚重映射和极性配置且时序完全匹配。2.3 SPI 管脚初始化最容易踩的坑复用功能不是你想开就能开STM32 上初始化 SPI最常见的新手错误是把 MOSI/MISO/SCK 配成普通推挽输出然后发现数据传输卡死。SPI 作为外设复用功能GPIO 必须设置成复用推挽模式AF_PP而且 MISO 引脚必须配置为上拉输入或浮空输入。更隐蔽的坑是 MISO 引脚忘记开启上拉/下拉结果从设备 MISO 驱动能力弱时主设备读到的全是杂散电平。另外很多 STM32 型号的 SPI 引脚不是随意映射的需要参考数据手册里的 AFIO 重映射表。比如 STM32F103 的 SPI1默认引脚是 PA5、PA6、PA7部分封装支持重映射到 PB3、PB4、PB5。如果你的 PCB 走线把引脚连到了 PB3却没开启重映射那程序写到天荒地老也通不了。配置完外设时钟和 GPIO 时钟之后别忘了一条先初始化 GPIO再初始化 SPI 外设。顺序反了很容易在后续调试里出现奇怪的问题。2.4 SPI Flash 读写 W25Q64 的经典套路W25Q64 是几乎每个嵌入式工程师都摸过的芯片8MB 容量SPI 接口用来做字库存储、固件升级备份、日志记录都非常顺手。用 SPI 读写 W25Q64 的套路其实就四步发命令、发地址、传数据、收尾。比如读芯片 ID就是拉低 CS发送 0x9F 命令然后读三个字节分别是厂商 ID 和设备 ID最后拉高 CS。W25Q64 的厂商 ID 固定是 0xEF设备 ID 是 0x4014能对上说明 SPI 通了。写入操作就比较讲究了。Flash 写入前必须先擦除擦除以扇区为单位。W25Q64 一个扇区 4KB擦除命令是 0x20。擦除一次大约需要 150ms 到 400ms期间芯片会拉低 BUSY 位你要通过读状态寄存器 0x05 不断查询 WIP 位等它变 0 才能继续下一步。很多机器人车项目里 Flash 写入突然失败十有八九就是擦除还没完成就开始写。读数据时最快的方式是连续读先发送 0x03 命令再发 24 位地址之后主设备持续产生时钟W25Q64 就会一个字节接着一个字节往外吐读完整片 8MB 也就一眨眼的功夫。这里要注意W25Q64 的单次页编程最多写 256 字节跨页写必须手动处理地址对齐偷懒直接跨页写大概率会写飞。3. 实操过程与核心环节实现3.1 软件模拟 SPIGPIO 翻转也能很优雅很多人觉得软件模拟 SPI 是“性能不足”的无奈之举我倒觉得这是一种最优雅的底层能力。它不依赖任何硬件外设任何两个 GPIO 就能“徒手”搓一个 SPI 主机出来。在硬件 SPI 引脚被占用、或者你用的芯片压根没有 SPI 外设时软件模拟就是救命的稻草。软件模拟 SPI 的核心是别把时序写得太啰嗦。以模式 0 为例发送一位的逻辑是这样的先把 MOSI 置成要发送的位然后把 SCK 拉高等一小段时间再把 SCK 拉低。重复 8 次就是一个字节。代码一共不到二十行但是有一个关键细节在循环里不要调用任何带延时的库函数最好是用__nop()或者空循环制造几个时钟周期的延时否则频率一高整个时序就变形了。我之前在 STM32F0 上做过一次软件模拟用 GPIO 翻转模拟三线 SPI——也就是没有 MISO只有 SCK、MOSI、片选。因为那是一个只需要单向写数据的 LED 驱动芯片三线完全够用。当时把主频拉到 48MHz软件模拟 SPI 的时钟能跑到 1MHz 左右控制几十个 LED 灯带绰绰有余。所以“软件模拟慢”其实是个相对概念在低速外设场景下它完全够用。需要特别注意的一个问题软件模拟 SPI 读数据时MISO 引脚要在 SCK 下降沿之后去读而不是上升沿。因为很多从设备是在上升沿输出数据数据要等一小段传播延迟才稳定。你在上升沿立刻去读 GPIO很容易读到上一个 bit 的残留值。我自己就因为这个问题在读取传感器时出过数据错位后来改成下降沿读问题立刻消失。3.2 硬件 SPI 实战STM32 的 CubeMX 配置与避坑在 STM32 上搞硬件 SPI我强烈建议用 CubeMX 先做图形化配置。原因很简单SPI 外设的寄存器位太多手写初始化代码容易漏。你用 CubeMX 把 SPI 模式选为 Full-Duplex Master速率设为 18MHz 或更低CPOL/CPHA 按芯片手册填好它会自动把 GPIO 复用、外设时钟、DMA 请求这些琐碎的东西全部配好比手写省事太多。配置时会遇到一个经典问题分频系数和实际总线时钟算不对。STM32F103 的 APB1 最大 36MHzAPB2 最大 72MHz而 SPI1 挂在 APB2 上、SPI2 挂在 APB1 上。如果 SPI1 配 18M 波特率那分频就是 4但 SPI2 想配 18M 就不行了因为 APB1 只有 36MHz最大只能到 36/218MHz而且预分频必须是 2 的幂。很多同学的开发板跑 SPI2 莫名其妙速率上不去其实就是没意识到这个时钟域的差别。CubeMX 生成的初始化代码只是第一步别忘了在实时操作系统里使用 SPI 还要做好互斥保护。如果是裸机用查询方式倒没什么问题一旦跑 FreeRTOS多个任务同时访问同一个 SPI 外设不加互斥量就会出现数据交叉错乱。我当时第一次在 RTOS 下调 W25Q64 时写进 Flash 的数据总是偶发性损坏查了两天才意识到是两个任务在抢总线。后面加了一把互斥锁问题瞬间归零。硬件 SPI 的另一种优雅姿势是 DMA。配置好 DMA 通道、把外设地址指向 SPI 的 DR 寄存器、内存地址指向你的数据缓冲区然后启动一次传输CPU 就可以去干别的事。传完以后在 DMA 中断里做收尾清理比如拉高片选、置标志位。大块数据的搬运效果立竿见影尤其是 LCD 刷屏配合 DMA 之后帧率能提升一倍还多。3.3 ESP32 实战屏幕与 SD 卡共享 SPI到底哪个优先级高ESP32 上跑 SPI 是个高频场景大部分时候是屏幕和 SD 卡一起挂在同一根 SPI 总线上。很多人在网上问“用哪个 SPI 好”其实问题的核心不是用哪个硬件 SPI而是总线上匹配不同速率的外设怎么做仲裁。屏幕刷新往往需要十几 MHz 甚至更高而 SD 卡在 SPI 模式下一般跑到 20MHz 就到顶了互相迁就会造成两头不讨好。我的方案是屏幕用硬件 SPI 优先级拉满因为刷屏对实时性要求高SD 卡的读写可以稍后插入。实现上把 CS 分给屏幕和 SD 卡两个不同的 GPIO两个设备都挂在同一组 MOSI/MISO/SCK 上软件层通过分别控制 CS 来分时复用。这样屏幕在刷新时就不会被 SD 卡的慢速操作拖累SD 卡读写的时候屏幕则暂时停一下、等一个垂直消隐周期再切回来。实测下来 2.4 寸 240x320 的屏加一张普通 TF 卡在 ESP32 上刷图、存日志、读配置文件完全没问题。这里有个关键坑切换 CS 之后总线不是立刻就能用的。因为被选中的设备在 CS 拉低后往往需要一小段时间稳定内部逻辑立刻开始发数据有概率丢掉第一个字节。我的习惯是在每次 CS 拉低后插入几个空时钟周期的延时代价极低但稳定性好了很多。另外不同设备模式不同也没关系因为模式是在每一次传输前配置的切换设备时重新配置 SPI 模式即可不会有硬件冲突。还有一个非常不起眼但会咬人的细节当两种设备都挂在一条总线上时千万不要让多个设备的 CS 同时为低。否则它们的行为无法预测轻则数据错乱重则漏电损坏 IO 口。我在驱动代码里写了一个断言每次切换前都检查当前是否有设备占用总线一旦检测到重复选中就立刻报错。这个小机制后来救了我很多次。3.4 FPGA 视角Verilog 实现 SPI 协议带你拆解状态机FPGA 下的 SPI 和单片机完全是两种思维。单片机是用外设寄存器去控制时序FPGA 是你自己当“时序的上帝”。用 Verilog 实现 SPI最核心的就是状态机的架构。我常用的状态划分是IDLE、发送命令、发送地址、读/写数据、结束。每个状态之间用计数器精确控制 SCK 的翻转字节传输按位进行位计数器到 8 就跳到下一个状态。比如实现一个模式 0 的 SPI MasterSCK 空闲为低上升沿输出数据、采样输入。Verilog 逻辑大概是在偶数周期翻转 SCK在 SCK 上升沿之前把 MOSI 更新为当前发送位在 SCK 下降沿附近锁存 MISO。看起来简单但如果你的位宽、地址宽度、命令序列都是可配置参数这个模块的可复用性就很强。用 FPGA 做 SPI 最大的好处是你可以把片选时序做得非常精确甚至能在一个 SPI 事务中间插入自定义的等待周期。这是 MCU 的硬件 SPI 外设做不到的。但代价是调试难度更高因为硬件波形错了没法像单片机那样打断点。所以我的建议是先用逻辑分析仪抓波形再用 ILA集成逻辑分析仪看内部信号两手都要抓。凡是遇到时序问题先看 SCK 是不是稳定、CS 有没有毛刺、数据是否在采样沿保持稳定这三个点查完90% 的问题都能定位。4. 常见问题与排查技巧实录4.1 一张问题速查表解决 90% 的 SPI 故障平时在群里帮人看 SPI 问题来来回回就是那几类。我整理了一张速查表按出现频率排序症状可能原因解决办法MISO 一直读到 0xFF从设备没被正确选中检查 CS 引脚极性配置确认是否真正拉低MISO 读到全 0x00MOSI 或时钟没通用示波器/逻辑分析仪看 SCK 有无时钟翻转数据错位前两个字节正常CPHA 配置反了切换 CPHA调整数据采样沿数据对但偶发乱码速率太高时序余量不足降低 SPI 分频系数减小总线电容多个设备互相干扰片选信号控制逻辑有bug检查是否有两个 CS 同时拉低写入 Flash 失败没有先擦除或擦除超时未等待确认发送 0x20 擦除命令后轮询状态寄存器我对速查表的使用逻辑是先怀疑片选因为片选错了影响最大且最隐蔽再看时钟和数据线最后才怀疑从设备本身。很多人在 SPI 调不通时第一反应是换芯片、换板子其实认真查一下时序和片选问题往往出在自己眼皮底下。4.2 用逻辑分析仪抓波形从一大堆杂波中找出真凶调试 SPI 绝对不能靠肉眼猜逻辑分析仪是嵌入式工程师最值得投资的工具。便宜的 USB 逻辑分析仪支持 8 通道、采样率 24MHz 起步抓 SPI 绰绰有余。抓的时候把采样率设到 SPI 时钟的 4 倍以上否则波形不够光滑看不出数据采样沿是否安全。抓完波形以后解码是基本功。打开逻辑分析仪自带的 SPI 解码器设置好 CPOL/CPHA 和位序一般是 MSB First它会自动帮你解析出 MOSI 和 MISO 上的字节流。对比期望值一目了然。分享一个我印象很深的案例之前调试一块 LCD 屏程序逻辑看着都对但屏幕老是闪花。我抓了波形后发现SPI 数据线上的下降沿毛刺极其严重尤其在 CS 切换的瞬间。追查之下才发现是 SPI 总线上挂的 FPC 线太长寄生电容太大在 20MHz 时产生了振铃。最后把 SPI 速率降到 10MHz同时在 SCK 和 MOSI 上串了 33 欧姆的电阻做阻尼屏幕马上就稳定了。所以有时候波形不好别急着改代码先看看硬件走线和阻抗匹配。4.3 极限性能调优SPI 屏刷新率到底怎么算SPI 屏幕刷新率是很多做 UI 的人关心的重点。刷新率的理论计算公式是刷新率 SPI 时钟频率 /每像素位数 × 总像素数。比如一块 240x320 的屏共 76800 像素RGB565 格式下每像素 16 位SPI 时钟 40MHz理论视频帧率 40000000 /16 × 76800≈ 32.5 帧每秒再加上帧消隐、命令开销实际有 20 多帧就不错了。所以如果屏幕卡顿关键瓶颈往往在 SPI 时钟频率而非主频。想把刷新率提上去优先做三件事第一把 SPI 速率拉满到外设支持的上限第二用 DMA 搬数据避免 CPU 逐字节搬运第三尽量减少发送前的准备时间比如提前把一帧数据拼好再用一块连续内存一次性发出去。我实测过一次升级从普通轮询发送改到 DMA 双缓冲刷新率直接提升了接近两倍。需要注意的另一个点是屏幕驱动 IC 的数据写入速率通常高于实际显示刷新率所以真正决定丝滑程度的是 MCU 到驱动 IC 的搬运效率而不是屏幕面板本身的刷新率。这话可能有点绕但你只要记住“优化搬运而不是优化面板”这个大方向就对了。4.4 编程下载算法与工具链的小众痛点关于 STM32F429 下载算法里 SPI Flash 的配置这里值得单独提一句。很多人会在 Keil 里给外部 SPI Flash 做下载算法却反复出现RM1135 step 01 clear DL tag and setting in SPI - read reg(0xbfc7ffe0) fail这类报错。这类问题绝大多数出现在 Flash 型号与下载算法不匹配或者 SPI 模式配置错误的场景。解决办法是确认 W25Q256 的 ID0xEF 0x40 0x19与算法里的定义完全一致同时把 SPI 模式设为 Mode 0速率先降到 5MHz 试一下。烧进去之后如果校验能过再逐步提速。工具链层面我常用的调试装备是“USB 转 SPI/I2C 适配器”这类便宜的模块能让你在 PC 端直接对着寄存器读写省去反复编译下载的时间。特别适合调试传感器初始化序列这种要反复试的场景。把寄存器配置用脚本写成序列一键跑看反馈比在 MCU 上改代码重烧快一个数量级。5. 跨平台对照总结STM32、ESP32、FPGA 三路并进5.1 STM32 平台功能最全最适合系统性学习STM32 的 SPI 外设非常经典包含硬件片选、DMA、FIFO、CRC 等特性最适合初学者把这些特性吃透。STM32H743 这类高性能芯片的 SPI 甚至能跑到 100MHz 以上做高速采集都没压力。但高性能意味着更多寄存器要配所以用 HAL 库时除了初始化结构体还得留意SPI_TIMode和NSSP这些冷门配置项在普通场景下别去动它们。5.2 ESP32 平台外设少而精自由度高ESP32 只有两到三个硬件 SPI但它的优点在于引脚几乎可以任意映射。尤其在 Arduino 环境下SPI.begin()一行代码就能启用默认 SPI对于想快速上手的用户非常友好。工程上如果遇到“引脚不够、外设不够”的死局ESP32 的 GPIO 矩阵还能让你用任意引脚模拟任意外设这种灵活性很值钱。5.3 FPGA 平台万物可控但代价是复杂度FPGA 上的 SPI 是唯一能真正实现“引脚级精确控制时序”的载体。不管你要反向实现一个奇怪的从设备协议还是想同时驱动多个没有标准 SPI 接口的传感器FPGA 都能搞定。但代价是开发周期明显拉长Verilog 的调试也没那么直观。所以如果你的产品功能就是板级通信优先考虑 MCU如果你的板子就是用来做协议转换、接口扩展的“胶水芯片”FPGA 值得一上。5.4 选型建议别盲目追求外设数量最终选型不是看哪个平台的 SPI 外设多而是看整个系统的总拥有成本。STM32 生态成熟、资料多适合量产和学习ESP32 网络和蓝牙集成度高适合 IoT 项目FPGA 灵活可控适合做接口级定制。SPI 本身只是一种承载数据的物理机制真正重要的是你如何组织这些数据、如何管理总线上的设备以及如何让代码在任何平台上都保持结构清晰。6. 写在最后的几个实在建议文章写到这儿我最后再掏几句压箱底的心得。第一方向永远比努力重要。调试 SPI 的时候不要上来就对着代码逐行看先接逻辑分析仪抓波形把“电平时序”和“数据结果”对应起来找到证据链再动刀。第二片选信号是 SPI 的“灵魂”。很多玄学问题最后都落回到 CS 的时序、极性、毛刺这三点上。第三不要死守一种方式。硬件 SPI 不行就软件模拟顶一下阻塞传输不行就换 DMASPI 最大的优点就是灵活灵活的资源就该被灵活地用。如果你照着这篇文章的思路把一个 SPI 设备从零调通再成功在一条总线上挂了多个设备那我相信你已经能体会到“SPI 这么玩就很优雅”到底是什么感觉——那是一种从底层掌控了节奏而不是被时序追着跑的自由。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →