ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

SPI通信协议详解:从底层机制到工程实战

SPI通信协议详解:从底层机制到工程实战 大家平时做嵌入式开发I2C、UART、SPI这几个词肯定天天见。但说句实在话很多人调通了 SPI 就算完事对协议本身的细节、为什么这么设计、实际项目中那些坑其实没完全吃透。我自己也是从“能用就行”到“仔细抠协议细节”走过来的中间踩过的雷不少尤其在 STM32、ESP32、FPGA 这几个平台上都折腾过 SPI今天想把关于 SPI 通信协议的经验完整梳理一遍。这篇内容不会只贴一个时序图就完事我想把 SPI 从底层机制到工程落地讲透四线机制的本质、四种模式怎么选、硬件片选和软件片选怎么权衡、共享总线怎么设计、DMA 怎么配合、常见的脏数据和干扰问题怎么排查。不管你是刚入门的小白还是已经调过几个 SPI 设备的工程师应该都能从中找到点有用的东西。1. SPI 协议的本质与适用场景1.1 为什么叫“串行外设接口”SPI 的全称是 Serial Peripheral Interface串行外设接口由 Motorola 在 1970 年代末提出。早期微控制器外设越来越多如果每个外设都占一组并行数据线芯片引脚根本不够用所以需要一种高速、简单、引脚占用少的通信方式。SPI 就是在这个背景下普及开来的。和 UART 的异步通信不同SPI 属于同步通信。所谓同步就是通信双方共用一个时钟信号数据在时钟边沿被采样接收方不需要自己去恢复时钟。这一点极其重要因为同步机制决定了 SPI 可以跑很高的速率不受波特率误差累积的影响。从本质上看SPI 就是一个移位寄存器换另一个移位寄存器。主机的移位寄存器移出一位数据从机的移位寄存器就移出一位自己的数据两者同步完成这个机制就是 SPI 可以“全双工”的原因。1.2 SPI 与 I2C、UART 的取舍很多初学者分不清这三个协议的使用场景我提供一个简单的口诀UART 管远距离低速率I2C 管多设备少引脚SPI 管高速率大数据量。项目SPII2CUART信号线数量4根SCLK、MOSI、MISO、CS2根SCL、SDA2根TX、RX通信方式同步全双工同步半双工异步全双工速率上限极高几十 Mbps 到上百 Mbps中等通常几 Mbps 以下较低通常几 Mbps 以下多设备支持需独立片选线设备多时占引脚地址寻址一根总线挂上百设备点对点或需额外机制硬件复杂度简单无地址帧较复杂有起始停止位和 ACK最简单典型应用Flash、屏幕、ADC、SD卡传感器、EEPROM、PMIC调试日志、GPS、蓝牙模块从横向对比里能看出来SPI 在速率和全双工能力上有绝对优势但代价是引脚占用。一个 SPI 主机如果挂了 8 个从设备至少需要 4 8 根线共享时钟和数据线 8根片选线。有时候硬件工程师为了省引脚会在软件里通过 GPIO 扩展片选这个后面会细讲。1.3 SPI 能做什么不能做什么SPI 的典型应用场景包括存储器SPI NOR FlashW25Q64、W25Q256 等、SD/TF 卡显示TFT-LCD 屏幕ST7789、ILI9341 等传感器加速度计、陀螺仪、温度传感器很多 SPI 接口转换器ADC、DAC对速率要求高时 SPI 比 I2C 更合适无线模块LoRa 模块SX1268 等、Wi-Fi 模块的宿主通信电机驱动部分步进/伺服驱动芯片使用 SPI 配置寄存器SPI 不适合做什么长距离传输SPI 的时钟信号在长线上容易畸变一般板内通信为主超过 30cm 就要仔细考虑信号完整性多主通信SPI 协议本身没有多主机仲裁机制极不推荐用它做多主通信哪怕通过片选硬切也会有很多细节问题需要应答的场景SPI 没有 ACK 机制发出去的命令是否正确执行主机无从得知需要从设计层面保证可靠性2. SPI 通信机制与关键参数详解2.1 四根线各司其职SPI 有四根核心信号线搞清楚每根线的角色后面所有的时序分析都不会迷路。SCLKSerial Clock串行时钟由主机产生决定了通信速率。每个时钟脉冲对应一位数据的传输。时钟信号像一个节拍器告诉双方在哪一拍对齐。MOSIMaster Out Slave In主机输出、从机输入。主机在时钟边沿把数据发送到这条线上从机从这条线上读取数据。MISOMaster In Slave Out主机输入、从机输出。从机利用这条线把数据回传给主机这就是 SPI 全双工的关键路径。CS/SSChip Select / Slave Select片选信号低电平有效。主机把某个从设备的 CS 拉低表示“我开始跟你说话了”其他没有被选中的从设备保持高阻态不参与通信。有一个细节很多新手会忽略CS 不仅仅是“选择设备”它还是从机内部逻辑的“使能信号”。比如 Flash 芯片只有 CS 拉低时才会响应命令CS 拉高后内部自动回到 standby 状态。所以片选信号的时序建立时间、保持时间在很多从机数据手册里都有明确要求不能随便乱拉。2.2 四种工作模式的本质CPOL 和 CPHA这是 SPI 最让人头疼的部分。网上一搜 SPI 模式全是表格Mode 0、Mode 1、Mode 2、Mode 3每个对应不同的 CPOL/CPHA 组合。但很多文章只给结论不给原理导致大家只会背不会用。我来拆穿它的本质。SPI 是同步通信数据在时钟边沿被采样但问题是在时钟的上升沿采样还是下降沿采样在采样之前数据需要保持稳定还是在采样之后才变化这两个问题的答案组合起来就是四种模式。CPOLClock Polarity决定时钟空闲电平CPOL 0SCLK 空闲时为低电平一个完整数据传输周期内先从低变高再回低CPOL 1SCLK 空闲时为高电平先从高变低再回高CPHAClock Phase决定数据采样边沿CPHA 0在第一个时钟边沿采样数据对 CPOL0 来说是上升沿对 CPOL1 来说是下降沿。数据在边沿之前就准备好即数据在第一个边沿被“锁存”CPHA 1在第二个时钟边沿采样数据对 CPOL0 来说是下降沿对 CPOL1 来说是上升沿。数据在第一个边沿变化第二个边沿被采样把这两个维度组合起来模式CPOLCPHA空闲电平采样边沿数据变化边沿Mode 000低上升沿下降沿Mode 101低下降沿上升沿Mode 210高下降沿上升沿Mode 311高上升沿下降沿从时序图上观察会更直观Mode 0 是“空闲低、上升沿采样”Mode 3 是“空闲高、上升沿采样”。大多数 SPI Flash、SD 卡默认支持 Mode 0 和 Mode 3因为这两种模式在时钟上升沿采样数据对数据建立时间要求相对宽松。2.3 数据格式MSB first vs LSB firstSPI 的另一个关键参数是位序。大多数芯片默认是 MSB first最高位先发但有些设备要求 LSB first比如某些编解码器、部分射频芯片。如果不能确定翻开数据手册看时序图图中会标注第一位传的是 bit7 还是 bit0。在实际工程中位序出错是那种最头疼的 bug通信看起来一切正常波形也漂亮但读回来的数据乱码。如果遇到这种情况先别怀疑时序先检查位序对不对。2.4 SPI 速率的上限怎么定SPI 的速率由 SCLK 的频率决定。但 SCLK 能跑多快不取决于主机支持多快而取决于通信中两个最慢的环节从机的最大 SCLK数据手册会标注比如 W25Q64 最大支持 133MHz实际还受读取命令限制ST7789 显示屏一般是 15.5MHz 左右PCB 走线的信号完整性SCLK 频率高了之后反射、串扰、地弹都会导致数据采样出错工程上有一个很实用的经验先按从机手册标称值的 1/2 到 2/3 来配跑稳定了再逐步往上调。比如某 Flash 标称 104MHz你从 40MHz 开始测一遍读写再调到 50MHz、60MHz压测没问题再提。如果直接拉满大概率会在某个温度或者某块板子上翻车。3. 从理论到实践SPI 在主流平台上的落地3.1 硬件连接与电平匹配SPI 连接看起来简单SCLK、MOSI、MISO、CS 一一对应接上就行。但有几个坑必须提醒电平匹配问题。很多传感器是 3.3V 供电但 MCU 是 5V 电平这时候 MOSI、SCLK、CS 这三个主机输出的信号如果不做电平转换轻则读不到数据重则烧毁从机芯片。MISO 是从机输出给主机的从机 3.3V 输出到 MCU 的 5V 引脚反而相对安全大多数 MCU 引脚容忍 5V但要注意具体型号。我也见过某些 3.3V 的芯片其 MISO 引脚并非真正的开漏或推挽需要仔细读手册确认驱动能力。电平转换芯片如 TXS0108E、SN74LVC4245A几十块钱一片但很多项目为了省成本直接用电阻分压只适用于低速场合。SPI 高速通信时电阻分压的上升沿会变慢容易造成时序问题。CS 上拉电阻不能省。CS 低有效如果主机复位过程中 GPIO 输出高阻态CS 悬空从机可能会误触发。硬件设计时在 CS 线上加上拉电阻10kΩ 到 VCC确保没有主机驱动时 CS 保持高电平。这个教训我付出过好几块 PCB 的代价。MISO 线上的下拉/上拉电阻慎加。MISO 在从机未被选中时为高阻态如果主机侧没有内部下拉这根线会浮空导致主机读回随机数据。很多 MCU 的 SPI 外设内部有弱上拉/下拉但默认未必开着需要根据从机的输出类型推挽还是开漏来配置。3.2 STM32 CubeMX 实战配置STM32 用户最常用的还是 CubeMX 自动生成初始化代码但自动生成的代码不一定满足实际需求这地方有太多可以手动优化的空间。在 CubeMX 中配置 SPI 时需要关注以下几个参数Direction选择“Full-Duplex Master”或“Half-Duplex”等。全双工接 Flash、屏幕、传感器半双工接某些特殊器件如单线 SPI 的 OLEDHardware NSS Signal是否启用硬件片选。如果要手动软件控制片选此处选 DisableData Size8 bit 最常用但部分设备需要 16 bit 模式First BitMSB First / LSB First根据器件手册定PrescalerSCLK 分频系数根据目标速率配置CPOL/CPHA根据器件支持的 SPI Mode 配置配置完成后生成的 HAL 代码我一般会做两处修改第一片选信号不用 HAL 库的 NSS 自动控制改成手动 GPIO 控制。原因是 HAL 的硬件 NSS 在每次传输前后会自动拉低拉高但多字节传输时你往往需要保持 CS 在整个操作期间持续为低比如 Flash 的 Page Program 要连续发命令、地址、数据中间 CS 不能拉高。硬件 NSS 的自动控制无法满足这类场景所以大多数老工程师都选择把 CS 当普通 GPIO 用。第二调整 SPI 时钟极性为从机要求的模式后再初始化。有时 CubeMX 默认的是 Mode 0但设备要求 Mode 3改完参数后重新生成代码即可很多人忽略了这一点导致整块板子读不到数据。实际操作时我会用逻辑分析仪抓一次时序确认 CS 的拉低时机、SCLK 的极性、数据与 SCLK 的相位关系全部符合预期后再写业务代码。3.3 ESP32 平台上的 SPI 设计经验ESP32 的 SPI 外设比 STM32 复杂不少它支持多条 SPI 总线且每路都可以映射到任意 GPIOGPIO Matrix这解决了 STM32 上 IO 重映射的很多限制但也引入了新的问题。ESP32 官方推荐内置 Flash 使用 SPI0/SPI1用户可用的通用 SPI 是 SPI2HSPI和 SPI3VSPI。选哪条总线取决于你接什么外设屏幕ST7789、ILI9341 这类建议独占一条 SPI 总线因为高帧率屏幕需要持续占用总线和别的设备共享容易互相干扰SD 卡和 Flash 这类偶尔操作的设备可以共享一条总线用不同 CS 区分如果你有两个速率要求差异很大的设备一个屏幕一个温湿度传感器建议拆分到不同总线上否则总线速率要迁就慢设备屏幕帧率会受影响ESP32 上还有一个关键问题SPI 主机时钟频率不是随便设的。ESP32 的 APB 时钟通常是 80MHzSPI 时钟由 APB 分频而来因此实际速率和期望值会有差异。比如你想跑 10MHz实际可能得到 8MHz 或者 13.3MHz用逻辑分析仪测一下最靠谱。另外ESP32 的 SPI 在 DMA 模式下有性能优势但 DMA 缓冲区要求 4 字节对齐而且 DMA 描述符本身也要放在内部 RAM。很多人第一次做 ESP32 屏幕显示DMA 始终不工作十有八九是缓冲区对齐没处理好。3.4 分享 SPI 总线的设计权衡“ESP32 屏幕与 SD 卡共享 SPI 哪个好”这类问题经常被问到其实核心不是好不好而是你怎么设计。屏幕尤其是 TFT-LCD需要持续刷新SPI 总线被占用的时间长。SD 卡写文件的时候一次写入可能持续几十毫秒期间屏幕刷新就得暂停。如果这个暂停导致肉眼可见的闪烁那体验就很差。几个选项分两条 SPI 总线屏幕独占一条SD 卡和其他慢速设备共享另一条。需要多占用一组 SPI 引脚但对于 ESP32 这种引脚富余的芯片来说没问题体验最好。共享一条总线但做好任务调度屏幕刷新和 SD 卡读写不要同时发起通过 RTOS 信号量或标志位互斥。缺点是当 SD 卡写入频繁时屏幕刷新间隔被拉长。共享一条总线 硬件片选区分CS 分别控制因为 SPI 总线协议本身就支持这种方式只要软件上保证同一时刻只操作一个设备即可。我的建议是能分则分。屏幕这类的显示设备一旦刷新率跟不上用户感知非常明显而 SD 卡写慢一点用户感知不强。优先保证体验更敏感的设备。3.5 用 FPGA 实现 SPI 的关键点很多 FPGA 工程师会用 Verilog 自己写 SPI 控制器或者把 SPI 作为内部模块嵌入系统中。FPGA 实现 SPI 的核心难点不是协议时序本身而是时钟域处理和状态机的健壮性。一个标准的 SPI Master Verilog 实现包含一个状态机IDLE - TRANSFER - DONE一个分频计数器通过系统时钟分频产生 SCLK移位寄存器发送和接收各一个同步移位CS 控制逻辑根据配置提前拉低 CS传输结束后拉高写 SPI 控制器时最容易犯的错误是 SCLK 的相位和移位数据的对齐关系搞错。很多初学者从网上抄了一段代码仿真没问题上板就连不上后来一查发现 SCLK 的下降沿采样但数据在下降沿同时变化建立时间被破坏。一种稳妥的做法数据在 SCLK 的反相边沿更新。也就是说如果你的设备是上升沿采样那么数据应该在 SCLK 为低时更新SCLK 为高时数据保持稳定。这样正好满足采样需求。FPGA 做 SPI Slave 则复杂一些因为 SCLK 是外部输入的异步信号需要做两级同步器消除亚稳态然后用 SCLK 的边沿做采样时钟。很多网上代码直接用 SCLK 作为触发沿这在低速下能跑高速下很容易出现亚稳态问题导致偶发数据错误。3.6 SPI DMA什么时候该用怎么用SPI 开 DMA 可以在不占用 CPU 的情况下完成大批量数据传输特别适合屏幕刷新、Flash 读写、SD 卡读写等场景。以 STM32 为例SPI 发送 DMA 的标准流程是将待发送数据放入内存缓冲区配置 DMA 通道外设地址指向 SPI_DR内存地址指向缓冲区方向是内存到外设设置传输长度使能 DMA 通道等待 DMA 传输完成中断如果需要接收数据同时配置接收 DMA方向是外设到内存一个非常关键的注意点SPI 全双工时发送和接收 DMA 必须同时开启且传输长度一致。SPI 的收发是联动的只开发送 DMA 不开接收 DMA数据会堆积在 RX 缓冲区里后续传输容易乱。另一个坑是内存缓冲区的大小端和字对齐问题。多数 MCU 的 DMA 要求外设数据宽度和内存数据宽度一致如果你传输的是 8bit 数据但 DMA 配置成 16bit 宽度数据就会错乱。这个问题在初始化配置里改一个参数就能解决但定位过程可能要花半天。DMA 适合什么场合简单说单次传输超过 16 字节且 CPU 有其他事可做就用得值。如果一次只传几个字节DMA 的上下文切换开销可能比直接中断读写还大。4. 那些让你调一晚上的疑难杂症4.1 常见问题速查与排查思路我整理了一个 SPI 问题排查表基本都是实战里高频踩坑的现象可能原因排查方法数据全 0xFFMISO 没接好/从机未选中/从机供电异常用万用表测电压逻辑分析仪看 CS 是否拉低数据全 0x00主机输出无效/MOSI 接错检查 MOSI 波形确认发送缓冲区正确读写数据错位CPOL/CPHA 配置错/位序错逐个切换 4 种 Mode 测试确认 First Bit偶发一个 bit 错误速率过高/信号完整性差/地线噪声降低 SCLK 频率缩短走线检查地线只能写不能读方向配置错/从机 MISO 引脚未使能检查 SPI Direction 配置查从机寄存器配置多设备下乱数据软件片选切换时序/总线争用确保 CS 拉高后延时再切换设备高速率下屏幕花屏SCLK 速率过高/屏幕初始化不完善/供电不足降频、重新初始化屏幕、检查电源纹波上电瞬间从机乱动作CS 悬空/MOSI 毛刺加 CS 上拉电阻MOSI 串电阻或加下拉排查 SPI 问题我强烈建议备一个逻辑分析仪。别买那种几百块的网上几十块的 8 通道逻辑分析器完全够用配合 PulseView 软件抓取 SCLK、MOSI、MISO、CS 四根线一眼就能看出波形和预期是否一致。很多软件问题看波形十秒钟就能定位。曾经有个项目SPI 读 Flash 一直偶发错误逻辑分析仪抓到 CS 低脉冲宽度正常但 SCLK 输出频率比配置高了整整一倍最后发现是分频系数配置被另一个初始化函数覆盖了。这种 bug 靠看代码很难找但看波形路径清晰。4.2 软件片选和硬件片选的恩怨情仇“SPI 硬件片选与软件片选”这个话题网上讨论很多我直接给结论大部分应用场景下用软件片选GPIO 控制更好。原因有三灵活性硬件片选由 SPI 外设自动控制但遇到连续传输比如 Flash 编程要一直保持 CS 低时硬件片选容易“自作聪明”地提前拉高多从设备切分方便软件片选你可以自由决定片选信号的有效时序想延时多久延时多久调试方便逻辑分析仪上看 CS 波形时软件片选的时间点和预期更可控硬件片选也不是一无是处。在极高速通信时比如几十 MHz 以上手动 GPIO 翻转 CS 可能引入不确定的延时而硬件片选由外设内部逻辑控制时序精确到时钟周期。另外硬件片选占用的 CPU 开销比软件控制更低。但即使是硬件片选模式我仍不建议开启自动 NSS 功能而是在发送前手动把 NSS 拉低发送后拉高。这样既能获得硬件片选的精确时序又能保持对片选时机的完全控制。4.3 共享 SPI 总线的冲突根源SPI 总线上挂多个设备时最常见的坑是“设备间互相干扰”。现象是单独测每个设备都正常但两个设备都工作后其中一个或两个都不正常。原因往往是 MISO 线冲突或 CS 选择逻辑不严谨。SPI 从机的 MISO 输出通常是推挽输出或高阻态两种状态切换。当 CS 为高时MISO 应该处于高阻态不影响总线。但某些从机芯片存在“CS 拉高后 MISO 没有立即高阻”的情况或者 MISO 输出配置错误导致两个从机同时驱动 MISO造成电流倒灌数据被拉偏。解决办法在每个从机的 MISO 线上串联一个几十欧的电阻比如 33Ω降低总线争用时的电流冲击。或者在选择从机芯片时明确要求其数据手册标注“MISO tri-state when CS inactive”。另一个共享总线的问题就是片选信号边沿不同步。比如两个设备 CS 由不同的 GPIO 控制软件执行“A 的 CS 拉高”和“B 的 CS 拉低”之间哪怕只有几条指令的间隔B 就可能抢在 A 完全释放之前开始驱动 MISO导致 A 最后一个字节出现毛刺。解决办法是切换设备时先拉高所有 CS再延时几个时钟周期再拉低目标设备的 CS。4.4 SPI 时序测量的实战方法手头没有逻辑分析仪怎么办用示波器也可以但抓边沿要仔细。示波器测量 SPI 时序时关键看三个信号的关系SCLK、数据线MOSI 或 MISO、CS。将示波器触发设置为 SCLK 的边沿观察数据信号相对 SCLK 的建立时间和保持时间。如果数据在 SCLK 采样边沿附近还在变化说明建立时间不够需要降低 SCLK 频率或优化 PCB 走线。还有一个技巧用示波器的“余辉”模式观察多个周期的波形叠加。如果数据信号的波形在采样点附近有明显的抖动抖动大于几个 ns大概率是信号完整性或电源噪声问题这时候优先检查地线和电源去耦电容。4.5 一个真实的 ESP32 屏幕SD 卡调试案例去年在一款产品上我用 ESP32 驱动一块 ST7789 屏幕同时需要把采集的数据写入 MicroSD 卡。一开始的设计是屏幕和 SD 卡共享 SPI 总线软件上做了互斥处理。实际测试发现屏幕刷新帧率掉到 20fps 以下肉眼能感觉到卡顿。进一步定位发现SD 卡每写一个扇区512 字节总线被占用的时间约为几毫秒而屏幕刷新一帧需要多次连续传输每次都被 SD 卡写入打断整体刷新时间拉长了很多。改成双 SPI 总线方案后屏幕走 SPI2SD 卡走 SPI3两个互不干扰帧率恢复到 40fps。这个改动只需要改一下 GPIO 配置和驱动初始化的总线号工作量不大但体验提升是质变的。这个案例想说明的是SPI 总线规划要在硬件设计阶段就想好不是软件层面能完全弥补的。ESP32、STM32 这类多 SPI 外设的芯片硬件设计时不要吝啬引脚把显示类设备和其他设备分开走线后面省心很多。4.6 关于逻辑分析仪的购买与使用建议再单独说一句逻辑分析仪的事因为它在 SPI 调试中太值了。选购时注意三点采样率至少要买 100MHz 采样率的SPI 跑 20MHz 时100MHz 采样率刚好能采到每个时钟沿。再低就采不准了通道数8 通道起步因为光 SPI 就要 4 通道其余通道还能抓中断、电源引脚协议解码PulseView 免费支持 SPI 解码在界面上直接看到解码后的字节不要买那种只能看波形不能解码的用逻辑分析仪调试 SPI 的正确姿势是先抓 CS 拉低这一瞬间的波形对照数据手册确认 CPOL/CPHA 是否符合预期再核对第一个字节的 bit 序列最后看整帧传输的字节数和预期是否一致。三步走完80% 的问题都能定位。5. 从会用 SPI 到用好 SPI一些长期经验5.1 多设备总线拓扑的三种形态SPI 的多设备连接方式不只有一种“CS 并联”的思路了解全部形态有助于你在项目设计时做最优选择。独立片选最常用所有设备共享 SCLK、MOSI、MISO每个设备一根独立 CS。优点是布线简单缺点是 CS 数量随设备数线性增加。菊花链Daisy Chain设备首尾相连——主机的 MOSI 接第一个设备的数据输入第一个设备的输出接第二个设备的输入依次串联。主机发数据时数据像流水线一样穿过所有设备。这种拓扑适合移位寄存器类的设备比如 74HC595 多片级联或者多片 ADC 串接。菊花链省引脚但延迟会累计不适合需要实时响应的设备。多核/多总线MCU 自带多个 SPI 外设如 STM32 有 3-6 个 SPI把不同速率要求的设备分到不同总线上。这种设计的本质是用外设资源换总线带宽和稳定性。从工程角度优先推荐独立片选因为它最直观、最容易调试。菊花链只在硬件设计受限引脚紧张时使用而且要让所有设备支持菊花链模式自己有没有输出直通功能。5.2 速率与可靠性的平衡SPI 超频测试方法学“能不能在标称速率以上工作”——这不叫超频这叫冒险。我一般不会去挑战从机的绝对极限但我会测试确定一个“量产安全速率”。方法很简单选定一个通信场景比如连续读 Flash 1MB 数据从标称速率的一半开始以 5-10MHz 步进递增每个速率下跑 100 次完整的读写校验记录出错时的速率回退一档作为安全速率在不同温度下常温、低温、高温各跑一遍如果条件允许把同一速率在不同批次的主控和从机上测试因为芯片工艺差异会导致极限速率不完全一致。量产时留足 20%-30% 的余量是非常有必要的。5.3 SPI 协议在存储类设备中的额外讲究SPI NOR Flash 是 SPI 协议最典型的应用之一但它比普通传感器多了很多命令层。比如写使能WREN向 Flash 写任何数据之前必须先发送 0x06 命令将 Flash 内部的 WEL 位置 1否则写入操作会被忽略。这个机制是防止误写的重要保护但也是很多新手调 Flash 时卡住的地方——发了 Page Program 命令数据也发完了读出来全是 0xFF就是因为前面没发 WREN。状态寄存器轮询Flash 写操作需要时间几毫秒到几十毫秒通过读取状态寄存器0x05的 BUSY 位来判断是否完成。注意期间 CS 可以拉高等一会儿再轮询不用一直拉低。读命令与读 ID 命令的时序差异W25Q256 这类芯片读 ID0x9F只需要发命令后马上读数据但读数据0x03需要发命令3 字节地址之后才能读数据。很多代码问题出在“忘了发地址”或者“地址字节数不对”。4 字节地址模式W25Q256 容量超过 16MB需要进入 4 字节地址模式0xB7或者使用带有 4 字节地址的读命令0x13。如果芯片处于 3 字节地址模式访问高端地址会出错。这个问题在升级固件时特别常见因为经常要读写 Flash 的高地址区域。5.4 高性能 SPI 屏幕驱动的几个细节SPI 屏幕ST7789、ILI9341 等的驱动性能瓶颈往往不在协议而在数据组织方式。屏幕初始化后刷一帧全屏数据通常要发几千到几万个像素点。如果每个像素都用 CPU 转发主频再高也容易卡顿。实际做法是DMA 传输把图像数据准备好后一次性 DMA 发给屏幕CPU 可以去处理其他逻辑双缓冲一个缓冲区在发送另一个缓冲区在填充新数据发送完成后切换用 RGB 565 格式16bit 每个像素SPI 一次传两个字节比 RGB888 少传 1/3 数据量SPI 时钟频率优化在屏幕规格允许的范围内尽量提高比如 ST7789 标称最大 15.5MHz实测能跑到 20MHz 还稳定但建议还是按标称来还有一点当屏幕和 Flash 共享 SPI 总线时Flash 的编程操作要放在屏幕不刷新的间隙否则 Flash 写操作会阻塞屏幕刷新。我在某个项目里用 FreeRTOS 的任务通知来协调屏幕刷新任务和 Flash 写入任务的优先级合理配置保证了显示流畅且数据不丢。5.5 不同 MCU 平台的 SPI 实现差异你会发现 STM32 的 HAL 库、ESP32 的 esp-idf、Arduino 的 SPI 库使用体验差别很大但底层协议完全一样。这个差异主要来自寄存器组织方式和驱动框架设计。STM32 HAL配置繁琐但可控性最高。HAL_SPI_TransmitReceive 是全双工的核心函数注意它的超时参数不能设太短否则大数据传输时会因超时失败。ESP32 ESP-IDFspi_bus_initialize spi_bus_add_device 两段式配置需要先用 spi_transfer 结构体描述传输内容然后调用 spi_device_transmit。ESP-IDF 的 SPI 驱动会处理很多底层细节但前提是你要正确设置 clock_speed_hz。Arduino SPI 库最简单SPI.beginTransaction(settings) SPI.transfer(data) 就完事。但封装好的代价是很多细节不可控比如 CS 的时序Arduino 默认的 SPI_CS 引脚时序和某些芯片可能不完全匹配。Linux 用户空间 SPIspidev也是常见场景需要 ioctl SPI_IOC_WR_MODE 设置模式SPI_IOC_WR_BITS_PER_WORD 设置数据位宽SPI_IOC_MESSAGE 发送消息。Linux 的 SPI 驱动框架支持将多个 SPI 传输组合成一个消息序列CS 在整个消息期间保持有效这对 Flash 编程和屏幕刷新都很有用。6. 一次典型 SPI 调试实战复盘6.1 背景与现象有一款基于 STM32F407 的数据采集设备外接一个 SPI 接口的 ADCAD7606 的 SPI 模式同时挂了一片 W25Q128 Flash 用于存储采集数据。硬件工程师反馈ADC 采集到的数据偶发毛刺大概每 100 次出现 1-2 次异常值Flash 读写倒是没问题。6.2 排查过程第一步确认通信模式。AD7606 手册要求 SPI Mode 1CPOL0CPHA1W25Q128 支持 Mode 0 和 Mode 3。我检查了 CubeMX 配置确认 ADC 对应的 SPI 是 Mode 1Flash 对应的是 Mode 3两者分属不同 SPI 外设没有共用总线。第二步用逻辑分析仪抓取 ADC 通信波形。观察发现异常数据出现的时刻SCLK 波形出现了明显的振铃ringing且 MISO 线上的数据信号在采样点附近不稳定。这说明信号完整性出了问题。第三步检查 PCB。ADC 的 MISO 走线经过了一个过孔且和数据采集前端的高频信号线平行走了大约 3cm。高速信号通过过孔时阻抗不连续和平行布线耦合过来的噪声叠加造成 MISO 信号质量恶化。第四步硬件调整。在 ADC 的 MISO 线靠近 STM32 端加了一个 22Ω 的串联电阻同时让 PCB 走线远离模拟前端。改版后重新测试异常概率从 1% 降到了 0.01% 以下满足需求。6.3 复盘总结这个案例的教训是SPI 调试不只是软件层面的时序问题更多时候是 PCB 布局和信号完整性问题。高速 SPI尤其是 SCLK 超过 10MHz走线长度、过孔数量、串扰源都是潜在风险点。工程调试时不要只盯着软件涉及硬件的问题先拿示波器看物理波形再决定是改代码还是改板子。7. 最后说点实在的SPI 这个协议从 1979 年诞生到现在已经四十多年新出的 MCU 依然把 SPI 作为标配外设它的地位无可替代。但协议本身简单不是我们轻视它的理由恰恰因为简单任何一个细节放松都可能在真实项目中爆雷。我个人在实际调试过程中最大的体会是SPI 的坑大多不是协议本身带来的而是我们对协议的“想当然”造成的。以为 Mode 0 万能、以为速率随便拉、以为 CS 就是普通控制引脚、以为共享总线就是连上就行——这些想当然才是真正的坑。如果你正在被 SPI 问题折磨我的建议是从时序图出发用逻辑分析仪实测把每个参数对照手册确认一遍。大多数问题都能在这个过程里水落石出。最后再分享一个小技巧调试 SPI 时先用 1MHz 左右的极低速率把通信跑通再去追求高速。低速跑通意味着协议本身没问题剩下的就是速率和信号质量问题。别一上来就拉满速出了问题很难定位是时序配置错、接线错还是信号完整性差——低速跑通了就是最快的定位路径。
返回列表