news 2026/9/5 13:50:19

SPI通信协议详解:从时序原理到DMA实战与调试避坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SPI通信协议详解:从时序原理到DMA实战与调试避坑

1. SPI通信到底在解决什么问题,为什么今天还在用

做嵌入式开发的朋友,几乎没有不认识SPI的。它全称Serial Peripheral Interface,串行外设接口,是Motorola在八十年代推出来的一套同步串行通信标准。很多新手第一次接触SPI,是在STM32驱动W25Q128这类Flash芯片、或者点亮一块ST7789屏幕的时候——点一下就通了,好像也没什么了不起。但你真把它放进一个稍微复杂的系统里,比如同时挂Flash、屏幕、SD卡甚至多个传感器,问题就全出来了:引脚冲突、时钟极性搞错、DMA搬运错数据、片选被中断打断导致通信错帧,各种妖魔鬼怪。

这篇文章我想从实践角度把SPI彻底拆开讲一遍。不单讲概念,还把时序、配置、DMA、硬件片选和软件片选的选择、以及实际调试中的坑都过一遍。适合刚学完单片机基础、开始用SPI接外设的初学者,也适合做了几年开发但遇到疑难问题想回头补底层细节的工程师。

我一直有个观点:能用UART解决的问题,其实没必要上SPI;但一旦外设吞吐量上来了,比如刷屏、读写Flash、采集ADC高速数据,SPI几乎是绕不开的选择。它的核心优势就三条:全双工、高速率、简单直接。没有地址帧、没有应答位、没有复杂的仲裁机制,就是主设备把时钟拉起来,数据线上一位一位挪过去。你可以把它理解成一条“同步流水线”:主设备发一个字节的同时,从设备也回一个字节,谁都不等谁。这套机制决定了SPI的上限很高,但同时对时序的严谨性要求也比UART严格得多。

顺带说一句,很多人上来就问:SPI和IIC哪个好?这个问题其实没法直接回答。IIC的优点在于引脚少、支持多设备总线仲裁,适合低速小数据量的场景;而SPI牺牲了引脚数量,换来了速度和全双工能力。后面我会专门用一节对比这三种常见协议,先在这儿提个醒:选型不看“哪个技术先进”,而是看“哪个匹配你的系统需求”。

2. 先从物理层和时序说起:SPI的四种模式到底是啥

2.1 四根线,各自干什么

SPI最少需要四根线,如果只是单向只写场景还能砍到三根。这四根分别是:

  • SCK:串行时钟,由主设备产生,决定了通信速率。时钟是一连串方波,每个边沿对应一位数据的采样或移位。
  • MOSI:主设备输出、从设备输入。主设备在这根线上发送数据。
  • MISO:主设备输入、从设备输出。从设备在这根线上回数据。
  • CS/SS:片选,低电平有效。主设备把CS拉低,表示“我开始跟你通话了”;拉高,表示“通话结束,你可以休息了”。

这里有个关键认知:SPI的时钟永远由主设备产生,没有时钟就没有数据流动。从设备永远是被动的,它不能主动发起通信。所以SPI的物理层根本没有“从设备请求”这种概念,一切以主设备的SCK为准。

还有一个新手经常忽略的点:MISO和MOSI是分开的,所以SPI天然支持全双工。你在主设备往MOSI上移位发送一个字节的时候,MISO上同时也在接收一个字节。这意味着很多SPI外设“读”操作其实是“先写一个命令字节,然后继续发哑字节把数据顶回来”的过程,而不是像IIC那样有单独的读信号。

2.2 CPOL和CPHA,两只拦路虎

SPI模式由两个参数决定:CPOL(时钟极性)和CPHA(时钟相位)。它们组合出Mode 0到Mode 3四种时序:

  • CPOL决定空闲时SCK是低电平还是高电平。CPOL=0,空闲低;CPOL=1,空闲高。
  • CPHA决定数据在SCK的哪个边沿被采样。CPHA=0,第一个边沿采样(对大多数MCU来说就是上升沿);CPHA=1,第二个边沿采样(通常是下降沿)。

所以四种模式就是:

模式CPOLCPHA数据采样边沿数据移出边沿
Mode 000上升沿下降沿
Mode 101下降沿上升沿
Mode 210下降沿上升沿
Mode 311上升沿下降沿

我统计了一下自己调试过的SPI外设,大概九成以上都是Mode 0或Mode 3。Mode 0是绝大多数MCU和外设的默认配置,W25Q系列Flash、ST7789屏幕、SD卡在SPI模式下都能跑。Mode 3其实是Mode 0的“取反版”:SCK空闲电平从低变高,采样沿从上升沿变成下降沿。所以你有时候把CPOL从0改成1,CPHA从0改成1,外设照样能用——因为收发双方仍然是“同一边沿采样、另一边沿移出”,保持了这个相对关系就可以正常通信。

踩坑提醒:一定要先看外设数据手册里写的SPI Mode,再配置MCU。我见过不止一次,有人拿着Mode 0的配置硬怼一个Mode 2的传感器,调试一整天查不出问题,最后逻辑分析仪一看时序全都是错的。

此外有些外设时序比较灵活,比如有的传感器支持Mode 0或Mode 3两种模式,你在初始化寄存器里能选。这种设计其实是为了兼容不同MCU的SPI外设。遇到这种芯片,选Mode 0就行了,兼容性最广。

2.3 时序参数不能只看频率

很多人换了主频更高的MCU,SPI速率也往上调,结果外设就不工作了。问题多半不在通信模式上,而在于时序参数。具体来说,SPI从设备数据手册里会给出这么几个时间参数:

  • f_SCK,最大时钟频率。这个最直观,超过就完蛋。
  • t_SU(setup time),数据在采样沿之前必须稳定多久。
  • t_HD(hold time),数据在采样沿之后必须保持多久。
  • t_CS(CS minimum high time),两次传输之间CS拉高的最短时间。

MCU的SPI外设时钟是可以分频的,但分频出来的实际速率未必刚好低于外设上限。举个例子:STM32的APB2外设时钟是72MHz的时候,SPI分频系数只能从2、4、8、16、32、64、128、256里选。如果某个Flash芯片最高支持40MHz,你用2分频就是36MHz,没问题;但如果你把APB2超频或者换了80MHz主频的MCU,2分频就变40MHz,正好卡在极限上,通常也不建议这么干。留余量永远是对的。

还有一个隐藏很深的问题:SCK的占空比。正常SPI主设备输出的时钟占空比接近50%,但有些MCU的分频逻辑会让占空比出现偏差,比如某些型号在奇数分频下会产生不均匀的时钟。这在低速外设上没问题,但跑在高速外设上就要注意。我实际测过一些国产MCU的SPI,边沿抖动比ST的大不少,把通信速率放低一半就稳定了。所以如果你用的是非主流MCU,SPI速率别一上来就拉满。

3. 三张图看懂SPI、IIC、UART的差异:选型别拍脑袋

3.1 物理层对比

SPI和IIC、UART是嵌入式里最常用的三种串行通信方式。我经常把它们比作三种交通工具:UART是长途大巴,双方约定好时间表,一根线发、一根线收,不需要时钟线,收发各自按波特率采样;IIC是公交车,只需要两根线(SDA和SCL),能挂很多设备,靠地址来区分,传输速度不快但线路简单;SPI是专线小车,四根线服务一个从设备,速度快、双向同时跑,但是占引脚。

物理层面差异很关键。UART是异步通信,收发双方得提前约定好波特率,数据帧里有起始位和停止位来辅助对齐;SPI和IIC是同步通信,有一根专门的时钟线,从设备跟着主设备的时钟走。同步通信容错性更强,时钟一乱数据就乱,但对MCU和外设双方来说,同步协议天然比异步协议容错性更强——因为它不需要两边各自精确对齐时间基准。

3.2 通信速率与多设备能力

协议线数(最少)速率典型范围多设备支持全双工典型场景
UART2(TX/RX)9600bps ~ 几Mbps点对点调试日志、GPS模块、蓝牙模块
IIC2(SDA/SCL)100kbps ~ 3.4Mbps地址区分,可达上百个否(半双工)传感器读取、EEPROM、电池管理
SPI4(SCK/MOSI/MISO/CS)10Mbps ~ 上百Mbps片选区分,通常几个Flash、屏幕、ADC、SD卡

从这张表能看出几个重要差异。IIC的优势是接线少、设备多,但半双工意味着同一时刻只能一个方向发数据,协议里还有ACK应答、起始停止条件这些开销,实际吞吐量打折明显。SPI没有这些协议开销,时钟一给数据就流,所以同样是10MHz时钟,SPI的有效吞吐率接近10M字节每秒(每位对应一个时钟周期),而IIC 400kHz模式实际有效吞吐率还要扣掉地址帧、应答位、起停条件这些开销,差距非常悬殊。

3.3 为什么很多传感器用IIC,而Flash和屏幕用SPI

这不是巧合,是产业分工的结果。传感器(温度、湿度、气压、加速度计)数据量小、更新频率低,用IIC就够了,而且IIC只需要两根线,能省引脚;Flash和屏幕动不动就要写几十KB甚至几MB的数据,IIC那点速度根本喂不饱,必须上SPI。

有意思的是SD卡两种协议都支持:SDIO模式和SPI模式。如果你用SDIO接口,速度能跑到几十MB/s,但引脚多、代码复杂;SPI模式虽然慢一些,胜在简单,很多没有SDIO外设的低成本MCU也能跑。在ESP32上让屏幕和SD卡共享同一个SPI总线,就是这个逻辑——用片选来区分两个设备,牺牲一点并发吞吐,换来引脚节省。

但是这里有一个坑:屏幕和SD卡共享SPI时,刷新率和读卡速度会互相影响。如果屏幕持续刷新,总线一直被占用,SD卡的写操作会被拖慢;反过来,正在写SD卡的时候,屏幕刷新会出现瞬间卡顿。所以ESP32这类场景里,有人会选择给屏幕单独的SPI总线,或者把屏幕刷新放到DMA后台执行,让CPU在DMA搬运间隙处理SD卡。具体怎么选,取决于你的使用场景:如果只是开机加载一次图片,共享没问题;如果要一边显示动态数据一边记日志,最好分总线。

4. 硬件设计上那些容易翻车的细节

4.1 接线不是连上就行

SPI虽然是低速数字接口(相对于DDR等高速内存来说),但它跑在MHz级别时,接线不规范同样会出问题。我总结几条实际经验:

首先,主设备和从设备之间的距离越短越好,尤其是MISO这根从设备返回数据的线。SCK和MOSI是主设备发出去的信号,沿双向都还比较好处理;但MISO是从设备驱动的,它和SCK之间的时序关系经过了“从设备采样+响应”的延迟,距离越长,信号完整性越差。我见过有人在开发板上用十几厘米的杜邦线连SPI Flash,能跑通,但速率一超过20MHz就随机出错,量波形发现MISO的边沿已经严重圆滑了。

其次,尽量让SCK和MOSI离得近一些。因为MOSI上的数据是相对SCK边沿被采样的,两个信号走线长度差太远,会导致建立时间不足。PCB上让这两根线并行走、长度差控制在几毫米以内,是很实用的做法。

第三,共地是底线。SPI是同步通信,所有信号都以地为参考。如果主设备和从设备接在不同电源系统上,没有共地,MISO上的电平参照就乱了,轻则数据错乱,重则直接损坏IO口。用逻辑分析仪的时候也要注意,探头的GND必须和被测系统的GND接在一起,不然你看到的波形全都在飘。

4.2 上拉电阻和电平匹配

SPI不像IIC那样需要上拉电阻,IIC是开漏结构,必须靠上拉电阻输出高电平。SPI是推挽输出,不需要上拉。但是如果你把SPI复用成GPIO来模拟,比如用IO口模拟三线SPI去驱动某些低成本屏,这时候推挽输出还是开漏输出就要结合外部电路来确定。如果外设的IO电平和你MCU不是同一个电压域,就需要电平转换。常见的方案是:如果系统里同时存在3.3V和5V设备,一定要用双向电平转换芯片,或者在MCU侧串联电阻限流。直接拿3.3V的MCU去推5V的SPI设备,长期可靠性很差,不是你今天能用、明天就能保证不烧的。

4.3 硬件片选还是软件片选

这是SPI开发里最经典的一个选择题。硬件片选由SPI外设自动拉低拉高,不需要CPU干预;软件片选就是你自己操作一个普通GPIO,在通信开始前拉低、结束后拉高。

硬件的优点是把片选时序做到极准,尤其在开启DMA搬运大数据块时,CPU不需要在每个字节之间去管CS电平。缺点是有些MCU的硬件片选逻辑不够灵活,比如通信异常时CS拉高的时机不受你控制,或者你需要在两次传输之间插入一个固定延时,硬件片选可能做不到。

软件片选的优点是完全灵活,你可以让CS在任意时间变化,适合模拟协议、特殊时序、多设备分时复用。缺点是在通信过程中如果CPU被中断打断,CS的切换时机可能不准——比如你正准备拉高CS结束通信,结果一个定时器中断插进来,把CS拉高的时间往后拖了几微秒,某些时序敏感的外设就会出错。

我的建议是:常规设计优先用硬件片选,配合DMA效果最佳;如果外设对CS时序有特殊要求,或者你需要几个设备分时复用同一根CS,那就用软件片选。还有一种调和方案:硬件片选用来做主要通信,软件GPIO用来做外设的复位或使能控制,两者搭配比较舒服。

5. 实际项目的SPI配置过程:从CubeMX到寄存器

5.1 用STM32CubeMX配置SPI的实战

STM32CubeMX是现在做STM32开发绕不开的工具。打开后选择芯片型号,在Pinout & Configuration面板里找到SPI1,勾选Transmit Only或Full-Duplex Master模式,然后配置参数:

  • Parameter Settings里的Mode选择Full-Duplex Master或Transmit Only。
  • 硬件片选设置为Software或者Hardware。注意,如果你选Hardware,CubeMX会分配一个专用的NSS引脚;如果选Software,你需要自己在GPIO里配置一个普通引脚来做片选。
  • 接下来配置时钟。SCK时钟来源是APBx外设时钟,CubeMX里直接配Prescaler分频系数。有些屏幕要求SPI速率不要超过10MHz,Flash则可以跑到几十MHz。我一般先把分频调到能跑通的范围,再逐步提高,看最高稳定速率是多少。
  • CPOL和CPHA按外设手册选择,默认Mode 0。
  • 数据帧格式通常8位,个别大容量Flash可以支持16位帧格式。16位帧格式有个好处:一次传输两个字节不用拆包,DMA搬运效率高一些,但不是所有外设都支持,不能想当然。

配好之后生成代码,核心是几个函数接口。HAL_SPI_Transmit用于发送,HAL_SPI_TransmitReceive用于同时收发。如果你在读Flash,典型流程是:先发读命令和地址,再连续读数据。HAL库的代码很好写,但有个很烦的特性:HAL_SPI_TransmitReceive在接收时,发送缓冲区里的字节会源源不断送出去。所以读数据的时候,发送缓冲区准备的全是0xFF哑字节——因为主设备的MOSI上如果没有数据,外设可能就不给你往外送MISO数据。

5.2 DMA搬运大数据的正确姿态

如果只是读写几十个字节的数据,轮询就够了。但刷一块320x240的屏幕,一帧全彩RGB565数据大约是320×240×2字节,约150KB。你用SPI以40MHz速度发,通过轮询一字节一字节地往DR寄存器里塞,效果就是CPU大量时间花在等待TXE事件上,主循环被完全堵死。DMA就是为了解决这个问题。

DMA配置流程其实不复杂:初始化一个DMA通道,源地址指向数据缓冲区,目的地址指向SPI的发送数据寄存器(或者反过来,接收时源地址指向SPI的接收数据寄存器,目的地址指向内存缓冲区),设置传输长度,然后启动传输。启动之后CPU就解放了,DMA每搬运完一个字节,SPI外设自己会把数据移出去。传输完成会触发中断,你在中断回调里处理后续逻辑。

这里有个常见坑:DMA和Cache的一致性。在带Cache的Cortex-M7芯片(比如STM32H7)上,如果CPU往缓冲区里写数据,然后DMA直接从缓冲区读到SPI外设发送,而这个缓冲区正好在Cache里,DMA读到的可能是旧数据。解决方法是使用不带Cache的SRAM区域,或者在写完后执行Cache Clean操作。这件事在STM32F1/F4上不存在,但在H7上必须处理,否则你会看到数据偶尔错乱、刷屏花屏之类的问题。

另一个DMA相关的坑是缓冲区生命周期。DMA是异步搬运的,你调用HAL_SPI_Transmit_DMA()启动后立刻返回,但传输还在后台进行。如果这个函数返回后你就把缓冲区释放了,或者缓冲区是局部变量,数据就会被破坏。正确做法是定义一个全局缓冲区,或者用回调函数(TxCpltCallback)来标记传输完成,等回调触发了再复用缓冲区。

5.3 用GPIO模拟SPI的适用场景

不是所有MCU都有硬件SPI外设,比如某些8位小芯片,或者你手头的SPI引脚被其他外设占用了,就得考虑用GPIO模拟。IO口模拟三线SPI其实是个很实用的技能,尤其当你只需要驱动一个只写设备(比如单色OLED屏)的时候,MISO都可以不要,只剩SCK、MOSI、CS三根线。

模拟SPI的核心就是延时精确控制时钟翻转。伪代码很简单:

void SPI_WriteByte(uint8_t byte) { for (int i = 7; i >= 0; i--) { // 先拉低SCK,准备移出数据 SCK_LOW(); if (byte & (1 << i)) { MOSI_HIGH(); } else { MOSI_LOW(); } // 数据稳定后拉高SCK,产生上升沿,让从设备采样 SCK_HIGH(); delay_us(1); } }

这段代码的核心思想是:在SCK上升沿之前把MOSI设置好,上升沿到来时从设备采样。如果你用CPOL=1或CPHA=1的时序,代码里的翻转顺序要相应调整。模拟SPI最大的问题就是速度上不去——软件翻转GPIO的耗时加上延时,通常只能到几百kHz到几MHz。驱动屏幕刷新率会明显变慢,但驱动传感器或EEPROM足够用了。

延时函数也值得注意。用HAL_Delay毫秒延时太粗糙,需要微秒级延时函数。要么用定时器,要么用空循环指令做粗略延时。但空循环延时的精度受编译优化影响很大,我在O0和O2优化级别下测过,同一个空循环的实际延时差了好几倍。如果你用模拟SPI驱动对时序敏感的器件,建议用一个硬件定时器做微秒延时,别看这几微秒的差距,有时候就是它能通和不能通的分界线。

5.4 片选时序:一个容易被忽略的关键参数

很多人写SPI驱动只关注时钟极性,不关注CS时序。实际上CS的拉低和拉高时机同样重要。数据手册上通常有两个参数:t_cs_setup(CS拉低到第一个SCK边沿之间的最小时间)和t_cs_hold(最后一个SCK边沿到CS拉高之间的最小时间)。一些外设还要求两次通信之间CS拉高的最小时间,比如W25Q256在写完状态寄存器后要求CS保持高电平至少t_SHSL,否则写操作可能不生效。

硬件SPI自动处理大部分片选时序,但软件控制片选时你就得自己关心。我写过一个EERPOM驱动,发现写完一页数据后偶尔丢数据,逻辑分析仪量CS时序才发现CS拉高的时间比手册要求少了几十纳秒。解决办法很简单:在通信结束后加几个空操作指令,或者延时1微秒,问题就消失了。这类问题不抓波形很难发现。

6. 真实项目的踩坑记录:从W25Q256到ST7789和AFE4490

6.1 STM32F429读取W25Q256的完整流程

W25Q256是一个常见的SPI NOR Flash,容量256Mbit(32MB)。用SPI读取它看起来很简单,实际上有几个细节。

首先要注意的是命令格式。读取数据的Read命令是0x03,后面跟3字节地址。但W25Q256因为容量超过16MB,地址变成4字节,需要使用0x13(4字节地址读命令)或者先发送Enter 4-Byte Address Mode命令(0xB7),让芯片进入4字节地址模式。很多人拿STM32F429的标准库例程去驱动W25Q256,发现读出来的全是0xFF,折腾一晚上才发现是地址位数问题。

然后是一条经验建议:写完数据以后不要立刻回读,一定要等状态寄存器的BUSY位清零。芯片在编程或擦除期间,如果你发读命令,它会忽略。具体流程是:发写使能命令(0x06)、发写命令(0x02 + 地址 + 数据)、然后反复读状态寄存器(0x05),等BIT0变为0。我在第一版驱动里图省事,直接延时几毫秒就回读,结果偶尔读出来的数据不对——不是芯片坏了,是它还没写完,你回读到的还是旧数据。

另外W25Q256擦除操作按扇区(4KB)、块(64KB)或整片进行,扇区擦除时间大概是几十到几百毫秒,整片擦除以秒为单位。这些延时都不能用简单delay解决问题,一定要查状态寄存器。

6.2 ST7789屏幕:刷新率和SPI速率的关系

ST7789是现在很常见的SPI接口TFT屏幕驱动芯片,分辨率通常是240x320或240x240。有人在网上问SPI屏幕的刷新率是多少,这个问题不能一概而论,它取决于SPI时钟速率和每帧数据量。

一帧彩色RGB565数据是240×320×2=153600字节。如果SPI时钟是40MHz,理想情况下每秒钟最多能传40M/8=5M字节,刷一帧理论耗时153600/5000000≈30.7ms,也就是说理论最高刷新率只有32fps左右。这里面还没算命令开销、行消隐和刷新间隙。如果SPI只有10MHz,那理论刷新率就掉到8fps左右,肉眼可见的卡顿。所以SPI屏幕要做到流畅刷新,SPI时钟至少30MHz,同时配合DMA减少CPU占用。

如果USB供电电压不稳,屏刷新时会出现背光波动、花屏甚至重启。解决办法是在电源入口加一个大容量的钽电容或电解电容,我用过100uF的,效果很好。

ST7789初始化序列也容易出问题。大部分模块带一个初始化代码。如果你用的模块和网上代码不是同一个厂家的,初始化的命令序列可能略有不同,重点检查Display Resolution Setting(0x36)、Data Order(0xB6)、Porch Setting(0xB0)这些跟时序相关的配置。最典型的现象:屏幕亮但是画面偏移、颜色不对、或者滚动显示异常,基本可以定位到初始化序列里的行/列扫描方向或像素格式位没配对。

6.3 AFE4490通过SPI读取脉搏数据

AFE4490是TI推出的一款专门用于血氧饱和度检测的模拟前端芯片,内部集成了LED驱动、光电二极管放大、ADC转换等功能,对外用SPI接口输出数据。用这个芯片跑SPI时,最大的难点不在通信本身,而在数据同步。

SPI只是用来和MCU交换数据,AFE4490会在准备好数据后,通过一个GPIO中断引脚通知MCU去读。如果你忽略了这个中断信号,单纯定时去读,拿到的数据时基就是乱的,心率计算根本没有意义。正确的做法是:用GPIO外部中断,在AFE4490的DRDY引脚产生下降沿时启动SPI读取,读取完解析寄存器里的数据。

还有一次我遇到SPI读回来的数据全是0xFFFFFF的情况,排查了很久发现是芯片的复位引脚没拉高,AFE4490一直处于复位状态,SPI通信本身没问题,但芯片根本没在采集数据。这种“SPI通信检测不出芯片是否工作正常”的问题在模数混合芯片上很常见,遇到异常先查电源、复位、时钟,再查SPI配置。

7. SPI调试的常用工具和排查技巧

7.1 逻辑分析仪是SPI调试的第一利器

如果你手头还没有逻辑分析仪,我强烈建议买一个。几十块钱的8通道逻辑分析仪在SPI调试里能帮你省下以天计的时间。调试SPI的接线方法:把逻辑分析仪的通道0接SCK、通道1接MOSI、通道2接MISO,GND接公共地,然后设置采样率至少是SPI时钟频率的4倍以上。

用逻辑分析仪抓到一个SPI帧后,第一件事是数时钟边沿数和数据位对齐。如果数据帧是8位的,一个字节应该有8个时钟沿;如果发送了一个字节但从设备回的数据错位了,多半是CPOL/CPHA没配对,或者是MISO线的物理连接有问题。逻辑分析仪还能直接看出CS时序问题,比如片选提前拉高了、两次传输间隔太短,这些问题在代码里根本看不出来。

7.2 示波器在SPI调试中的位置

逻辑分析仪主要看逻辑电平,但如果SPI速率很高、或者信号质量有问题,就得用示波器看模拟波形。主要看几个东西:上升沿是否缓慢(反射导致过冲、振铃)、SCK的高电平是否达到VIH门槛、MISO上的数据眼图是否闭合。

调试高速SPI时,波形上看到过冲或者振铃,最简单的办法是降低SPI速率、在信号线串联33欧姆左右的电阻。串联电阻能有效抑制振铃,但会略微增加上升时间,所以只能小阻值。如果波形已经很差,还可以考虑缩短杜邦线、改用短粗的导线或者直接用PCB排线。

7.3 常见故障速查表

现象可能原因排查方法
通信完全不通接线错误、共地缺失、外设供电异常先查电源和接线,再用逻辑分析仪看SCK/MOSI是否有波形
数据错位或乱码CPOL/CPHA不匹配、MISO硬件冲突检查外设手册中的模式,逻辑分析仪比对波形
首次通信成功,之后一直失败CS时序问题、外设进入某种锁死状态抓CS波形,检查两次片选间隔,必要时给外设复位
高速通信丢帧,低速正常信号完整性差、数据线过长降速测试,串阻尼电阻,缩短走线
DMA传输数据错乱Cache一致性问题、缓冲区生命周期错误检查缓冲区是否被覆盖、是否做了Cache Clean

7.4 普通人最容易忽略的两个SPI名坑

第一个坑是波特率或时钟速率设置太高但外设不支持。有些国产显示器模块标注的所谓高刷新率是虚标的,实际上SPI接口跑不到那么快。遇到屏闪烁、花屏,不要急着怀疑代码逻辑,先把SPI速率降到手册推荐值试一下。

第二个坑是MISO线与其他外设的IO冲突。很多MCU的SPI引脚是复用的,同时连接了调试器、LED、按钮等外设。如果某个外设把MISO线拉低或者拉高,SPI通信就会被干扰。用逻辑分析仪抓波形时,如果看到MISO上有不正常的直流电平,优先检查是不是有别的外设占用了这根线。

遇到SPI异常,我的一般排查顺序是:先量电源电压和地线共地关系,再看片选信号能不能正常翻转,然后用逻辑分析仪抓SCK和MOSI看MCU有没有正常发数据,最后检查外设的数据手册里的时序参数,逐个比对波形。

8. SPI和FPGA、Verilog之间的那些事

8.1 为什么FPGA也爱用SPI

FPGA和SPI的关系很特别。很多FPGA芯片的配置加载接口本身就是SPI(或类似SPI的协议),板载的SPI Flash不需要经过CPU,上电就直接把逻辑配置灌进去。SPI接口速度快、简单,FPGA厂商对SPI Flash的兼容性做得非常好。做FPGA开发的人经常会碰到两种SPI:一种是为MCU和FPGA之间通信做桥接,另一种是FPGA直接控制SPI Flash存储配置数据。

从逻辑设计的角度看,SPI主设备在FPGA里实现非常简单:一个移位寄存器、一个计数器、一个状态机就够了。SPI从设备稍微复杂一点,因为从设备没有时钟主动权,必须精确匹配SCK的边沿来采样。这也是热词里出现“spi slave verilog”的原因——大部分人实现主设备很容易,做从设备常常遇到时序约束不过关的问题。

如果要实现一个SPI从设备模块,核心要点是跨时钟域处理。SCK和FPGA内部时钟不是一个时钟域,必须做同步处理。最简单的做法是:用两级寄存器把SCK和CS同步到系统时钟域,然后检测CS的下降沿作为帧开始信号。SCK的上升沿或下降沿采样逻辑也要同步判断。很多初学者写的SPI从设备在仿真里一切正常,一上板子就乱套,就是因为没有处理跨时钟域问题。

8.2 SPI速率和FPGA时序约束

FPGA上跑高速SPI(比如大于50MHz)时,时序约束就很重要了。综合工具需要知道SCK和数据的相对关系,才能保证布局布线后的电路满足建立保持时间要求。如果时序约束没写,或者写错了,会出现编译一次能用、改了无关代码后就不能用的诡异问题,因为每次布局布线结果不同,时序余量也不同。

给FPGA加SPI约束时,一个常用做法是把SCK约束为主时钟,在SDC文件里声明它和MOSI/MISO的关系。如果你用的FPGA有DDR功能,某些SPI Flash还支持DDR模式,在时钟的上升沿和下降沿都采样数据,速度可以翻倍,但时序分析和PCB设计要求也更高。普通项目建议先跑SDR模式,稳定压倒一切。

9. 小结一下我个人做SPI项目沉淀的几条经验

最后分享几条我做了很多SPI设备驱动之后沉淀下来的个人经验,不是教科书上能看到的:

第一,SPI硬件外设再强大,也要清楚它内部寄存器的工作机制。很多HAL库里封装好的函数,底层无非是操作几个寄存器。遇到莫名其妙的SPI问题,建议直接读芯片参考手册里SPI章节的寄存器描述,比在网上查一百条博客都有效。

第二,沟通时序细节时,一定以数据手册为准。不同厂家的Flash、屏幕、传感器,虽然协议一样,但时序参数差异不小。同一个芯片不同批次也可能有细微区别——这时候不要想,直接按手册最严苛的条件来配置。

第三,SPI速度不是拉满就好。在低功耗/长电池设备里,SPI跑快了省的是时间,耗的是电流;跑慢了耗电更久,总能量消耗未必更低。要根据实际应用权衡。如果只是偶尔读写几KB数据,SPI速率2MHz和40MHz对系统功耗差别没那么大,但PCB布局时的高频噪声问题,低速会省很多麻烦。

第四,能用硬件SPI就尽量不要用GPIO模拟。就算你的模拟代码时序再准,也占CPU。在复杂的系统里,CPU被SPI通信占满,你的主循环就卡死了。DMA+硬件片选+硬件SPI的组合,才是稳定高效的正道。

SPI这个协议本身不难,难的是它在真实系统里和无数组件交互时衍生出的各种边界问题。希望这篇文章能帮你在调试SPI的时候少踩几个坑,遇到问题知道从哪个方向下手查。如果你在实操中遇到别的SPI疑难杂症,不妨先从片选时序和时钟极性这两个最容易翻车的点查起——我赌十有八九问题就藏在那里。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 13:49:04

Fable 5.1 跑分大幅跃升?版本性能对比的正确做法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 13:47:22

AIGC视频创作:从创意到导演级分镜的完整工作流指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 13:46:52

应对API Token配额限制:从诊断到架构的完整工程实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 13:46:47

新能源汽车动力电池CCS设计:从核心原理到工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/5 13:46:40

NACA0012.zip解析与C++网格生成实战指南

简介&#xff1a;本资源是一套面向航空工程、流体力学及CFD初学者与实践者的NACA0012翼型二维结构化网格生成工具包&#xff0c;聚焦于解决CFD仿真前处理中关键的几何离散与高质量网格构建问题。压缩包共含3个文件&#xff08;2个TecPlot兼容的.dat数据文件 1个C源码文件&…

作者头像 李华
网站建设 2026/9/5 13:46:19

ADAU1787双DSP架构详解:ANC降噪设计的实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华