尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SPI通信协议详解:从时序原理到STM32工程实战与调试

SPI通信协议详解:从时序原理到STM32工程实战与调试 1. 先从一根线说起SPI通信协议到底是什么说实话嵌入式开发里SPISerial Peripheral Interface串行外设接口这东西可能是我日常打交道最多的通信协议之一。不管是ST的MCU还是ESP32几乎每块开发板上都有SPI接口接Flash、接屏幕、接传感器、接SD卡甚至在FPGA的远程升级、DSP与ADC的数据交互里SPI都是常客。很多人一上来就直接翻数据手册查寄存器、配置时序、写收发函数结果配置出来要么数据全乱码要么从机完全不响应最后只能对着示波器发呆。我倒觉得与其一上来就扎进工程细节不如先把SPI这群“线”之间的关系理清楚。1.1 SPI的核心四根线一个指挥体系SPI通信协议的名字听起来很高大上本质就是一个“主从式、同步、全双工”的串行通信机制。所谓主从式就是整个总线上有一个主机Master负责发起通信、产生时钟、控制节奏其他的都是从机Slave只能被动响应。所谓同步就是主机给一根时钟线SCLK打节拍数据跟着节拍一位一位往外走收发双方以同一套时钟为准谁也不用额外对齐。所谓全双工就是在同一个节拍里主机能同时发出去一位数据、收回来一位数据收发并行不冲突。SPI通常就是四根线SCLKSerial Clock主机输出的时钟信号相当于整个通信的“节拍器”MOSIMaster Out Slave In主机输出、从机输入的数据线负责主机向从机发数据MISOMaster In Slave Out从机输出、主机输入的数据线负责从机向主机回数据CS/SSChip Select / Slave Select片选信号低电平有效主机拉低哪个从机的CS哪个从机就进入工作状态就这么看SPI很像一个老师傅带好几个学徒老师傅手里握着一根节拍棒SCLK嘴里喊口令MOSI学徒回答MISO点到谁的名字谁就回话CS。这个类比虽然简单但基本把SPI最关键的特征都点出来了谁控制节奏、谁选人回答、什么时候数据有效。1.2 主从架构与全双工SPI为什么能这么快SPI通信协议之所以在嵌入式领域经久不衰核心优势就是快。这个“快”来自两个方面。一个是协议本身设计得干脆。相比I2C那种带应答、带地址、带起始停止条件的复杂帧格式SPI的通信帧极其简单——CS拉低时钟开始跑每一位时钟对应一位数据时钟停下来传输结束。没有地址帧、没有应答位、没有复杂的仲裁逻辑所有开销都省掉了数据吞吐自然就上去了。常见SPI时钟能达到几十MHz很多MCU的SPI外设跑到40MHz、50MHz都没问题比UART不知道快到哪里去了。另一个是全双工机制。SPI在每一个时钟周期里MOSI往从机送一位数据的同时MISO也在往主机回一位数据。这就意味着对主机来说发一个字节和收一个字节是同一件事的两个面——你发出8个时钟脉冲既送出去8 bit也收回来8 bit。所以很多传感器的读操作都是这么设计的先通过MOSI把寄存器的地址和读命令发过去然后继续打时钟从机就会在MISO线上把寄存器内容送回来。整个过程中主机只需要持续输出时钟数据自然就来了。我刚开始接触SPI的时候有一个误区以为读操作就要用单独的接收函数等待数据。实际上在SPI的世界里接收函数内部也必然在发送数据哪怕是发0x00或0xFF因为时钟必须由主机产生从机没有时钟根本不会往外送数据。理解了这一点SPI的很多操作就不会觉得别扭了。1.3 为什么很多场合选SPI而不是I2C每次聊SPI就绕不开I2C。两者是嵌入式里最常用的板级通信协议但选型的逻辑完全不同。我做了几个对比表方便大家一把梭。对比项SPII2C引脚数量4根SCLK、MOSI、MISO、CS2根SCL、SDA速率通常可达几十MHz标准100kHz/400kHz快速模式1MHz通信方式全双工半双工同一时刻只能单向传输从机选择独立CS管脚硬件选通7位/10位地址寻址帧结构无固定格式自由定义固定格式起始、地址、应答、停止多主机不擅长基本单主机支持多主机仲裁长距离不适合板级通信为主也不适合但抗干扰略好选型逻辑其实很简单如果项目里只有一块Flash、一块屏幕时序要求高、数据吞吐量大那SPI几乎是必然选择。如果总线上要挂很多传感器、设备地址清晰、速率要求不高I2C更合适——少两根线还能多设备挂同一总线。但是SPI也有个明显的软肋每增加一个从机就要占用主控一个CS引脚。一条SPI总线挂5个从机光片选就占了5个IO这在管脚紧张的芯片上是个不小的开销。有些外设支持菊花链Daisy Chain拓扑比如某些移位寄存器、LED驱动芯片可以省CS脚但大多数情况下还是老老实实一设备一脚。2. 时序才是SPI的灵魂模式、边沿与片选SPI协议之所以让新手头疼的一大半原因出在时序上。很多人的板子和代码都是照着教程抄的但换了芯片、换了屏幕、换了Flash之后代码就不灵了。为什么因为SPI有四种工作模式不同外设要求的工作模式不一样配置错了收发时序就会错位表现出来就是乱码、通信失败、设备无响应。2.1 CPOL与CPHA四种模式到底怎么选SPI协议的模式由两个参数决定CPOLClock Polarity时钟极性和CPHAClock Phase时钟相位。CPOL决定的是SCLK在空闲状态下的电平。CPOL0表示空闲时SCLK为低电平工作时拉高CPOL1表示空闲时SCLK为高电平工作时拉低。CPHA决定的是数据采样发生在时钟的哪个边沿。CPHA0表示在第一个边沿也就是时钟从空闲电平翻转到工作电平的那一刻采样数据CPHA1表示在第二个边沿时钟从工作电平翻回到空闲电平的那一刻采样数据。这两个参数组合起来就是SPI的四种模式SPI模式CPOLCPHA采样边沿Mode 000第一个边沿上升沿Mode 101第二个边沿下降沿Mode 210第一个边沿下降沿Mode 311第二个边沿上升沿实际项目中遇到最多的是Mode 0很多Flash、SD卡、屏幕控制器默认就是Mode 0。我的习惯是拿到一个新外设第一件事就是翻数据手册里的时序图看它要求CPOL和CPHA分别是什么然后去对应配置MCU的SPI外设。千万不要默认所有设备都是Mode 0很多传感器就是Mode 3配置错了折腾半天还找不出原因。说到看时序图我教大家一个笨但有效的方法找到数据手册里传输时序图数一下第一个时钟跳变沿再看数据线在那个沿附近是稳定还是翻转基本就能判断CPHA。如果数据在第一个沿之前就已经稳定说明发送方在第一个沿之前就把数据放好了接收方在第一个沿采样此外设多半要求CPHA0如果数据是在第一个沿之后才稳定多半是CPHA1。这个要配合具体设备手册看但思路是对的。2.2 硬件片选与软件片选别在这个坑里栽跟头片选信号的管理是我见过最多人出问题的地方。SPI片选引脚有两个来源一个是MCU SPI外设自带的硬件NSS引脚由片内外设自动控制另一个是普通的GPIO通过软件写高低电平来控制。硬件片选的优点是不用CPU操心发送数据时硬件自动把NSS拉低传输结束自动拉高。听起来很美好但实际用起来问题不少。NSS引脚的自动控制时机、与数据发送的精确同步在不同芯片、不同库的底层实现里并不完全一致。有时候数据刚送完NSS就立刻拉高了但从机可能还需要一点时间来完成内部处理导致数据没稳定就被打断了。所以我在实际项目里除非是很简单、很单一从机的场景否则我基本都倾向用软件片选。用一个普通GPIO接到从机的CS脚在发送数据前手动拉低传输完成后再手动拉高。这样做的好处是时序完全可控什么时候拉低、拉高全由代码说了算适合各种带握手时序的外设。举个典型场景很多SPI屏幕控制器比如ST7789、ILI9341这类它们在初始化配置和显存写入时对CS的时序都有一定要求。如果CS拉高得太早最后几个像素数据可能没写进去屏幕就出现诡异的条纹或花屏。用软件片选的话我可以在所有SPI发送函数返回之后再拉高CS确保从机已经把数据锁存。注意软件片选时CS拉低之后要稍微延时一下再开始发数据特别是对于像SD卡这类速度要求不高但从机启动有延迟的设备。延时不用太长几十微秒就够但能避免很多奇奇怪怪的首字节丢失问题。2.3 片选释放的时机一个容易被忽略的细节片选信号的操作里除了拉低和拉高这两个动作本身还有一个容易被忽略的细节片选释放的时机。释放早了最后一字节可能没被从机锁住释放晚了从机可能一直占着数据总线不放特别是在总线上挂了多个从机的时候。我的经验是软件片选时要确保SPI外设已经把发送缓冲区里的数据全部移出去了再拉高CS。用HAL库的话等待HAL_SPI_GetState返回ready或者直接用阻塞式收发函数函数返回就代表传输完成再拉高CS。如果用的是中断或DMA方式必须等传输完成回调触发后再释放片选。还有一种情况某些从机在连续传输过程中要求CS在整个传输期间保持低电平不能逐字节拉高再拉低。最典型的是一些移位寄存器链、菊花链设备它们靠CS的沿来锁存数据如果每个字节都折腾一次CS收到的数据就会错位。这种场景下最好用一次性的连续SPI传输把所有字节打包发完再释放CS。3. 从原理到工程STM32上SPI通信协议实战讲完理论我们实打实跑一遍。我用STM32平台举例因为这块的生态和资料最丰富大家也好复现。CubeMX加HAL库的组合是目前最快能把SPI跑通的方式同时也有不少需要留心的细节。3.1 CubeMX里的SPI配置参数怎么填打开CubeMX选择你的MCU型号在Pinout Configuration里找到SPI外设使能它然后在参数配置界面里会看到一堆配置项。很多人看到这些参数就懵了其实搞懂了含义就很清晰。Mode选Master或Slave。绝大多数场景MCU是主机选Master。Hardware NSS Signal如果要用软件片选这里选Disable如果要用硬件片选选NSS Output或NSS Output Enable。Data Size选8位。大部分SPI外设都是按字节操作虽然支持16位但8位兼容性最好。First Bit选MSB First。多数SPI设备要求高位先出某些特定的芯片要求LSB First需要看手册。Prescaler波特率分频这个要算一下SPI时钟不能超过从机允许的最大频率也不能超过MCU SPI外设的上限。CPOL/CPHA按从机时序图的要求选。波特率分频的计算举个例子如果MCU系统时钟是72MHz从机Flash最大SPI时钟是33MHz那72MHz除以2正好36MHz超了除以4是18MHz稳妥。这时候我会选分频4SPI时钟18MHz既满足速度要求信号完整性也好很多。如果干脆不知道从机频率上限就选一个保守的分频值比如系统时钟除以8先让通信跑起来再逐步提高。提示SPI时钟不是越高越好。线长了、阻抗不匹配、上拉下拉不到位高速SPI很容易出现误码。稳定压倒一切先低后高慢慢调。3.2 写一个简单的SPI读写函数CubeMX配置完成后生成工程HAL库已经帮你把底层初始化的代码写好了。你需要做的就是调用HAL_SPI_Transmit、HAL_SPI_Receive、HAL_SPI_TransmitReceive这几个API。我以W25Q128这颗常见的SPI Flash为例演示一下完整的读ID操作。W25Q128是华邦的128Mbit SPI NOR Flash工作在Mode 0或Mode 3支持最高133MHz时钟我们用Mode 0即可。先定义片选控制和基本读写的宏#define W25Q_CS_LOW() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET) #define W25Q_CS_HIGH() HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET) // 写一个字节并同时读一个字节 uint8_t W25Q_SwapByte(uint8_t byte) { uint8_t rx 0; HAL_SPI_TransmitReceive(hspi1, byte, rx, 1, 100); return rx; }然后通过这个SwapByte函数来发送命令并读取数据uint32_t W25Q_ReadID(void) { uint8_t cmd 0x9F; // JEDEC ID命令 uint8_t manufacturer 0; uint8_t memoryType 0; uint8_t capacity 0; W25Q_CS_LOW(); W25Q_SwapByte(cmd); manufacturer W25Q_SwapByte(0x00); memoryType W25Q_SwapByte(0x00); capacity W25Q_SwapByte(0x00); W25Q_CS_HIGH(); return (manufacturer 16) | (memoryType 8) | capacity; }为什么读ID要用TransmitReceive而不是先Transmit再Receive因为SPI是主机产生时钟如果你只调HAL_SPI_Receive主机在接收的每一个字节也必须发送数据来产生时钟。HAL_SPI_Receive内部发送的其实是0x00或0xFF取决于HAL库的具体实现。在通信层面你必须确保每个时钟节拍都有数据在线上流动读Flash时发出去的0x00就是“陪跑”时钟作用仅仅是让从机输出数据。用TransmitReceive的好处是收发同时完成逻辑清晰也避免了先收后发可能带来的时序空隙。3.3 SPI DMA大块数据传输的正确姿势前面说过SPI是全双工的。在传输大块数据时如果你用字节轮询的方式一个字节一个字节发CPU会被完全占用期间几乎没法做别的事。比如给1.8寸TFT屏幕刷一帧图像RGB565格式320x240一帧就是150KB数据轮询发送不仅慢还让主控卡死。SPI DMA就是为这种场景设计的。DMA可以把数据从内存搬到SPI发送寄存器或者从SPI接收寄存器搬到内存全程不需要CPU介入。配置好DMA后启动一次传输然后去做别的事等传输完成中断通知你再处理。用CubeMX配置SPI DMA时注意这几个点发送和接收各需要一个DMA通道很多MCU的SPI发送和接收DMA通道是可以单独配置的DMA模式一般选Normal不用Circular除非你要做持续的数据流数据宽度要和SPI的数据宽度匹配SPI配置成8位DMA也选Byte启动SPI DMA发送的代码示例uint8_t framebuffer[150 * 1024]; // 显存缓冲 // 假设已经初始化好SPI和DMA HAL_SPI_Transmit_DMA(hspi1, framebuffer, sizeof(framebuffer));启动DMA传输之后函数很快就返回了CPU可以继续跑其他任务。等DMA传输完成会触发SPI的TxCpltCallback回调在回调里拉高CS或者置一个标志位。这里有一个大坑DMA传输期间里传入的buffer绝对不能是局部变量也不能被其他代码修改。一旦DMA还在搬运你提前把数据改了或者释放了内存就会出现随机乱码、数据错位。我的习惯是在文件作用域定义大的buffer传输完成前不去动里面的内容。另外SPI DMA加上CS的控制需要特别小心。DMA传输是异步的函数返回不代表传输完成。如果函数返回就立刻把CS拉高从机收到一半就断了。必须在DMA传输完成的回调函数里释放CS这样时序才是对的。3.4 多设备共享SPI总线的设计SPI总线上挂多个外设是很正常的用法。比如ESP32上一块屏幕和一个SD卡同时挂同一条SPI总线这也是很多人的疑问屏幕和SD卡共享SPI到底哪个好直接用同一个SPI外设、分时操作是完全可以的。关键就在于片选信号的分时管理访问屏幕之前拉低屏幕的CS、拉高SD卡的CS保证SD卡不参与反过来访问SD卡时拉低SD卡的CS、拉高屏幕的CS。这样两个设备物理上共用SCLK、MOSI、MISO三根线但在逻辑上任意时刻只有一个设备在通信互不干扰。我推荐ESP32项目里用硬件SPI同时挂屏幕和SD卡分时片选来操作。前提是这两个设备支持的SPI模式要一致屏幕控制器通常支持Mode 0SD卡也支持Mode 0并且它们的最高SPI时钟都能满足你的设定。如果模式不一致要么牺牲性能统一到兼容模式要么拆成两路SPI分别控制。另外要注意挂多个从机的时候MISO线上可能有多个设备在输出。理想情况下只有被选中的从机才会让MISO有输出其他从机应该是高阻态。但有些从机设计得比较“粗糙”CS释放后MISO引脚仍然是驱动状态。这时候如果两个设备都试图驱动MISO总线就会打架出现电平冲突。解决办法是加串联电阻或者在硬件选型时避开这类设备。关于硬件和软件片选的取舍在我的项目里如果挂多个从机一定是软件片选。硬件NSS只有一个根本管不过来多个从机。4. 常见问题排查SPI通信协议调试实录SPI调试是门手艺活。网上那么多关于SPI的提问什么读出来全是0xFF、数据错位、屏幕花屏、Flash容量不对归根结底就那么几个原因。我结合自己调SPI的经验把最常见的问题和排查方法整理成一套流程。4.1 读回来的数据全是0xFF或0x00这是SPI调试里最常见的现象。读Flash ID读出0xFFFFFF读传感器寄存器读出0xFF百分之八九十的原因出在片选或接线。排查步骤按顺序来先查接线MISO和MOSI有没有接反很多模块的丝印标注并不是很直观我踩过好几次MOSI和MISO接反的坑。实在不确定就用万用表量一下主控引脚和模块引脚的连接关系。再查CSCS有没有在通信前拉低拉低的是不是正确的从机如果CS一直保持高电平从机压根不工作MISO自然就没有有效数据输出读回来就是0xFF。检查SCLK空闲电平如果SCLK空闲电平和从机要求的不一致时钟边沿对不上数据可能在错误的边沿被采样收到的自然全是垃圾。最后检查从机供电很多模块的供电引脚上有个使能位或者需要某个IO口拉高才能进入工作状态。供电不正确芯片根本没启动读回来当然全0xFF。4.2 数据总是错位或者首字节丢帧如果你发现读出来的数据整体偏差一个字节或者中间偶尔多个字节、少个字节基本都是片选时序和时钟节拍对不上的问题。排查方向CS拉低后到发送第一个字节之间给从机的准备时间够不够可以在CS拉低后加一个短的延时特别是对于慢速从机。发送过程中有没有逐字节操作CS有些代码写得不注意在循环里每发一个字节就拉一次CS从机把每一次CS的沿都当成锁存信号数据就全乱了。有没有正确区分“写命令”和“写数据”的时序很多屏幕和传感器在命令阶段和数据阶段CS或者DC引脚的电平要求不一样。DC引脚数据/命令选择电平没切换对数据就会被当成命令解析整个通信逻辑全乱套。4.3 用逻辑分析仪看时序排查SPI问题光靠眼睛和代码单步调试是不够的我最推荐的工具就是逻辑分析仪价格便宜功能却很实用。把SCLK、MOSI、MISO、CS这几根线都接到逻辑分析仪上触发条件设置为CS下降沿抓一次通信过程就能清楚看到每一根线上的电平变化和从机数据手册的时序图一比对问题往往一眼就出来了。使用逻辑分析仪时注意设置足够的采样率。SPI时钟如果是10MHz逻辑分析仪的采样率至少要25MHz以上最好50MHz才能准确还原波形。采样率不够的话波形会杂散误判反而更多。经验之谈SPI调试99%的问题最终都能通过逻辑分析仪找到答案。先看CS是不是准时拉低拉高再看SCLK有没有毛刺再看MOSI/MISO两个数据线上数据边沿是不是落在正确的采样窗口里逐个排除。4.4 常见问题速查表现象可能原因解决办法读回全0xFF从机未使能、CS未拉低、供电异常、MISO虚焊检查接线确认CS用万用表测供电读回全0x00MOSI/MISO短路或接地、从机内部故障断开从机测试主控自发自收数据乱码CPOL/CPHA配置与从机不符查阅从机数据手册配置正确SPI模式首字节丢失CS拉低后准备时间不足CS拉低后加延时再发数据大量数据错位发送过程中CS被逐字节操作确保一个完整事务期间CS保持低DMA传输不稳定buffer被修改、CS释放过早DMA完成回调中确认后再拉高CS高波特率误码线太长、干扰大降低SPI时钟缩短引线增加去耦4.5 关于IO口模拟SPI一种被低估的救场方案有些项目里MCU的硬件SPI引脚被占用完了或者只有一个SPI外设但需要同时接多个设备这时候IO口模拟SPI就成了一个很实用的备选方案。IO口模拟SPI就是用GPIO直接翻转来产生SCLK、控制CS用GPIO输出模拟MOSI输入读取模拟MISO。方法听起来很原始但在很多场景下完全够用。比如用STM32的普通IO模拟三线SPI只有SCLK、MOSI、喂CS没有MISO去驱动一些单总线LED灯珠或者某些传感器代码其实不复杂。模拟SPI的要点在于时序。以Mode 0为例发送一位数据的逻辑是void SPI_Sim_WriteByte(uint8_t data) { for (int i 7; i 0; i--) { SPI_SCLK_LOW(); if (data (1 i)) SPI_MOSI_HIGH(); else SPI_MOSI_LOW(); SPI_SCLK_HIGH(); // 上升沿从机采样 } SPI_SCLK_LOW(); }这段代码的核心逻辑是先把时钟拉低准备好数据再把时钟拉高产生上升沿。从机在上升沿采样正好符合Mode 0。如果要求Mode 3就把初始状态反过来时钟空闲拉高下降沿采样。模拟SPI的速度取决于GPIO翻转速度和循环开销一般能跑到几百kHz到1MHz。用来驱动屏幕和Flash这种慢速场景还行高速场景就力不从心了。但作为救场方案IO模拟SPI的灵活性和可移植性是真的好不受芯片外设限制换个平台改一下GPIO宏就能用。5. SPI的进阶应用与性能分析SPI通信协议的应用远不止读写Flash这么简单。在实际产品开发中SPI还广泛用于显示驱动、数据采集、多设备互联、甚至远程升级和FPGA配置。这些场景对SPI的理解要求更深一层不仅仅是能调通还要考虑性能和可靠性。5.1 SPI Flash的下载算法与远程升级在STM32、FPGA这类平台上SPI Flash通常是存储固件或配置数据的核心介质。STM32F429配W25Q256这种组合很常见通过SPI接口可以扩展外部存储器存放代码、字库、图片资源提高主控的存储能力。调试SPI Flash时一个高频需求是给Flash编写下载算法FLM文件这样IDE比如Keil可以直接通过调试器把固件下载到外部Flash里启动。下载算法的本质就是一组运行在RAM里的代码通过SPI接口操作Flash的擦除、编程、校验等函数。调试这种下载算法时最常见的坑是SPI时钟配置太快Flash在编程状态时需要的等待时间没给够就会导致校验失败。远程升级场景里SPI Flash的作用就更重要了。比如高云FPGA的方案就是通过SPI接口把新的配置数据写入SPI Flash然后触发重配置让FPGA从Flash加载新固件。这里的关键是升级过程的原子性如果在写Flash的中途断电Flash里可能既不是旧固件也不是新固件设备变砖。所以可靠的远程升级方案通常会准备双备份一个Boot区一个App区先擦写备用区校验通过后再切换启动指针。5.2 SPI屏幕的刷新率瓶颈在哪现在很多项目用SPI接口的屏幕比如ST7789这种240x320的TFT关于SPI屏幕刷新率的讨论一直很热。很多人问我SPI屏幕到底能跑多快刷新率受什么制约刷新率的瓶颈主要有三个SPI时钟频率、像素数据的传输量、主控的搬运能力。以ST7789为例RGB565格式320x240分辨率一帧图像数据量是320x240x2153600字节。如果SPI时钟20MHz理论传输一帧需要153600x8/20000000≈61.4ms换算成帧率也就16帧/秒左右。如果SPI时钟能跑到40MHz帧率可以翻倍到32帧/秒左右。这还没算命令开销和刷新等待时间。所以想提高SPI屏幕刷新率第一优先级是把SPI时钟尽量调高第二优先级是用DMA来搬运显存数据避免CPU轮询拖慢速度。第三优先级是精简初始化命令减少不必要的命令开销。实际上很多廉价模块的PCB布线比较随意40MHz以上容易受干扰所以很多时候大家会停留在20MHz到30MHz这个档位稳定优先。顺带说一句如果对刷新率要求很高比如流畅播放视频动画SPI屏幕的天花板是肉眼可见的。这时候就要考虑并口屏幕如8080并行接口或RGB接口屏幕了。SPI屏幕更适合静态图片、慢速UI、小尺寸显示这类场景。5.3 高速SPI的硬件设计别让PCB板子毁了协议很多人觉得SPI是数字信号随便飞线也能跑。低速确实如此但一旦频率提升到20MHz以上PCB设计就会成为通信可靠性的分水岭。高频SPI信号最容易出问题的几个点SCLK和数据线走线过长会产生反射波形出现振铃导致采样点误判MOSI和MISO靠得太近容易产生串扰特别是双向同时传输时电源去耦不充分电流波动引发电平抖动地线不完整回流路径过长干扰直接耦合进信号线如果板子已经打样回来信号完整性问题比较难从硬件层面根除可以做的补救有降低SPI时钟频率、在信号线上串联33欧姆左右的电阻来抑制振铃、缩短杜邦线、加粗地线。如果是自己做项目建议从一开始就注意布局SCLK尽量短且远离其他信号线CS线也尽量短MISO和MOSI不要长距离平行走线。5.4 SPI从机的Verilog实现与FPGA场景聊到SPI的底层实现就绕不开FPGA。在FPGA里实现SPI主从机是很多同学学习状态机的经典项目。特别是SPI Slave对时序要求很高因为在从机模式下时钟由外部输入任何毛刺或亚稳态都可能让状态机跑飞。实现SPI Slave的核心思路很简单用SCLK作为驱动时钟检测CS的下降沿作为一帧传输的开始在每一个采样沿把MOSI上的数据移入移位寄存器同时把移位寄存器的最高位移到MISO输出。等到CS拉高锁存收到的字节触发一个接收完成信号。这里有一个容易忽略的坑SCLK是外部时钟和FPGA内部时钟不在同一个时钟域直接用它驱动状态机很容易产生亚稳态。稳妥的做法是先用内部时钟对SCLK打两拍同步再检测SCLK的边沿用同步后的边沿信号作为移位使能。这样虽然会引入一两拍延时但换来了稳定可靠在高速SPI场景下尤其重要。FPGA的SPI实现还有一个优势可以很方便地做高速数据流处理和协议转换。比如通过SPI Slave接口接收上位机数据FPGA解析后把数据转换成并行总线送到LCD并口屏或者把ADC采样数据通过SPI Master发送给MCU。这种灵活度是纯MCU方案很难做到的。我在做语音采集和播放项目时就特别喜欢用FPGA做SPI从机把高速ADC数据缓冲后再整包发给MCU极大减轻了MCU的实时处理负担。5.5 更复杂的通信协议栈SPI只是数据管道最后补充一点SPI协议本身只是“数据管道”它不像CAN那样自带完整的报文格式、仲裁机制和错误处理。在实际应用中SPI之上往往需要自己定义一套应用层协议来保证通信的完整性和可靠性。比如在智能硬件里Ble通信协议设计中常常会涉及授权与签名机制很多时候底层的物理传输就是SPI或I2C。MCU通过SPI与蓝牙SoC通信数据包里不仅要包含载荷数据还要加上设备地址、命令字、长度字段、校验字段甚至token和签名信息。SPI只保证这些字节能正确送达至于字节流怎么分割成有效报文、怎么验证合法性全得靠上层协议来定义。这也是为什么在很多通信协议栈里SPI属于最底层的一环。它简单、高效但协议内容需要使用者自行定义。理解了这一点你就不会碰到“SPI明明通了但通信就是不稳定”这种迷惑问题了——很多时候问题不在SPI本身而在上层协议没有做好帧同步、可靠性重传和错误处理。我用SPI做了这么多年项目最大的体会是SPI通信协议本身并不难难点在于把时序、片选、DMA、多设备调度和应用层协议组合起来形成一套稳定可靠的通信机制。刚入门的朋友先从读Flash ID开始再用逻辑分析仪看波形一步一步积累经验SPI这个协议总有一天会成为你得心应手的工具。最后分享一个小技巧调试SPI时如果条件允许一定要准备一个USB转SPI调试工具或者逻辑分析仪。很多时候代码看起来完全正确但问题就出在线序、电平、时序这些“看不见的细节”上有工具辅助几分钟就能定位到问题所在比自己反复猜要高效得多。
返回列表