ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SPI协议深度解析:从四线机制到STM32硬件外设与DMA实战避坑指南

SPI协议深度解析:从四线机制到STM32硬件外设与DMA实战避坑指南 1. SPI协议到底是什么先把它从“神秘黑盒”变成“四根线的事”做嵌入式开发这几年我越来越觉得SPISerial Peripheral Interface串行外设接口是日常打交道最多的通信接口之一。为什么这么说因为你在板子上看到的Flash芯片、屏幕驱动、SD卡、传感器、ADC/DAC几乎都能用SPI接。它的地位和IIC、UART基本并列属于嵌入式工程师默认“必须刻进DNA”的三板斧。但很多刚入行的朋友对SPI的理解停留在“照着例程抄能跑就行”。一旦遇到显示花屏、Flash读写偶发失败、DMA数据错位这类问题就完全无从下手。这不是你不够努力而是对协议底层的机制缺乏系统性理解。这篇文章我打算把SPI从原理到实践完整拆一遍重点放在那些文档里不会写、但实际调试中一定会踩的坑上。SPI是一个主从架构的四线同步串行接口。所谓“四线”就是SCLK时钟线、MOSI主出从入、MISO主入从出、CS/SS片选线。所谓“同步”就是指数据的收发完全由主设备产生的时钟驱动从设备自己不产生时钟只跟着主设备的节奏走。它解决的什么问题呢简单说就是在嵌入式系统内部让主控芯片能高速、可靠地与周边的外设芯片交换数据。注意这里有个关键词“内部”——SPI不用于远距离传输它的战场就在一块板子内部走线长度一般不超过十几厘米。你要是想把SPI拉到一两米外时序基本就崩了。谁适合认真看这篇文章我觉得只要你写过STM32、ESP32、Arduino这类主控的驱动代码或者用过FPGA做接口逻辑又或者只是好奇屏幕/Flash芯片背后到底怎么通信的都值得把下面的内容通读一遍。尤其是那些准备自己画板子、做产品的朋友SPI的理解深度直接决定你后期调试的痛苦程度。我写这篇文章的底气来自这几年实际调过的设备从最普通的W25Q64 Flash到各种SPI接口的LCD屏幕ST7789、ILI9341再到传感器、EEPROM、FPGA之间的互连。踩过的坑多了自然就知道哪些环节容易出问题、哪些配置看起来不起眼却致命。2. SPI通信机制拆解四根线背后的时钟、相位和片选博弈2.1 四根线各自干什么以及为什么是“同步”先看SPI的物理层。四条线各司其职SCLKSerial Clock主设备产生的时钟信号线。它决定了数据传输的速率也决定了数据在哪个边沿被采样。没有时钟就没有SPI通信这是“同步”二字的根基。MOSIMaster Out Slave In主设备发送数据到从设备的数据线。注意方向是主→从。MISOMaster In Slave Out从设备返回数据给主设备的数据线。方向是从→主。CS/SSChip Select / Slave Select片选线低电平有效。主设备把某个从设备的CS拉低就表示“我现在要跟你讲话了”其他没被拉低的从设备则保持沉默不占用总线。如果类比日常生活SPI有点像老师主设备点名提问SCLK是老师的节奏拍子MOSI是老师说的话MISO是学生的回答CS是老师点到的那个学生的名字。老师敲一下桌子时钟边沿学生就得在那一下的瞬间把话说完双方必须严格对齐。这里值得强调一个很多人忽略的点SPI是全双工通信。主设备发数据的同时从设备也在往MISO线上送数据。硬件上数据是通过移位寄存器实现的——主设备把要发的数据移出到MOSI同时把MISO上收到的位移入自己的寄存器。所以一次时钟脉冲既有“发”也有“收”两边同步进行。这在很多人的直观理解里容易被忽略。2.2 时钟极性CPOL和时钟相位CPHA到底怎么影响数据SPI最劝退人的地方就是CPOL和CPHA也就是所谓的4种SPI Mode。CPOLClock Polarity时钟极性决定空闲时SCLK的电平是低还是高。CPOL0表示空闲低电平CPOL1表示空闲高电平。CPHAClock Phase时钟相位决定数据是在SCLK的第一个边沿采样还是第二个边沿采样。CPHA0表示第一个边沿采样CPHA1表示第二个边沿采样。组合出来就是以下4种模式模式CPOLCPHA采样边沿数据输出边沿Mode 000上升沿下降沿Mode 101下降沿上升沿Mode 210下降沿上升沿Mode 311上升沿下降沿实际刷屏、读写Flash、读取传感器时最常见的是Mode 0和Mode 3。为什么因为很多芯片默认推荐这两种而且它们的本质都是“在时钟边沿中间采样留给数据足够的建立时间和保持时间”抗干扰能力强。Mode 1和Mode 2不是不能用而是用得少。调试的时候怎么确认应该用哪个模式两个办法。第一查从设备数据手册里SPI时序图图上会明确标注CPOL和CPHA或者直接告诉你支持Mode几。第二如果数据手册写得含糊就用示波器抓波形对比——在初始化时写一个读ID的指令逐个模式试能读到正确ID的模式就是对的。这办法笨但有效。2.3 硬件片选和软件片选各有各的坑片选信号是SPI里最容易出问题的环节特别是当你在一块总线上挂了多个从设备时。硬件片选硬件NSS/CS引脚由MCU的SPI外设自动控制。比如STM32的NSS硬件模式主设备在每次通信开始时自动拉低片选通信结束自动拉高。软件片选片选引脚不作为SPI外设功能而是普通GPIO通信前手动拉低通信结束手动拉高。两者的核心区别在于时序控制的精度和灵活性。如果用硬件片选优点是CPU不需要额外干预片选状态机减轻负载缺点也不少。第一硬件片选的高低切换时机由外设时序决定某些芯片比如一些传感器对片选信号的建立时间、保持时间要求比较苛刻硬件片选可能来不及满足。第二如果你的MCU的NSS引脚冲突了或者你需要在一次“原子操作”中只传输几个字节硬件片选会更难控制。第三DMA和高负载场景下硬件片选提前释放也会导致从设备数据错乱。软件片选是我个人更推荐的做法几乎是各厂驱动库里的事实标准。理由很简单你完全掌控片选拉低、等待、传输、拉高的每个时间点。在从设备要求严格的片选时序时比如Flash写状态寄存器时需要片选拉低直到命令完成软件片选就是救命稻草。缺点是CPU要花几条指令去翻转GPIO但对绝大多数应用来说这个额外开销可以忽略不计。实际操作中我还建议一件事片选引脚一定要配置成推挽输出且初始状态须为高电平。很多人在初始化GPIO时忘了置高导致上电后从设备被意外选中一直处于通信状态功耗异常甚至通信失败。3. SPI与IIC、UART的对比什么场景该选谁“SPI和IIC有什么区别”这是嵌入式初学者问得最多的问题之一。热搜词里也有“iic和spi的区别”说明大家确实在这两个协议之间纠结。我直接说结论没有谁更好只有谁更合适。把三者放在一张表里对比一下特性SPIIICUART线数4根SCLK/MOSI/MISO/CS2根SCL/SDA2根TX/RX通信方式同步全双工同步半双工异步全双工速率很高数十MHz轻松可达中低标准100k/400k高速可达3.4M中低常见9600到几M多设备支持通过片选每设备一根CS通过地址最多127个设备点对点硬件复杂度较高线多低低抗干扰能力中高速下对走线敏感中较强差分版如RS485更强我一般按这个逻辑选型如果只有一个从设备且对速度要求高屏幕刷新、Flash读写优先SPI如果总线上要挂多个设备且不想多拉线优先IIC如果两个设备距离超过20厘米或者环境干扰大就考虑UART必要时转RS232/RS485。值得注意的一点是IIC的地址机制让它在“一条总线挂多个设备”的场景下非常优雅但IIC的速率天花板太低刷屏和写Flash就是生死攸关的问题。我试过用IIC刷一个128x160的小屏刷新率惨不忍睹换成SPI之后速度直接提升了一个数量级。这也是为什么绝大多数中高分辨率屏幕都选择SPI接口的原因。4. 软件模拟SPI从零手写底层协议彻底搞懂时序本质4.1 为什么工作中还要“造轮子”看到这你可能想问“现在的MCU不是都有硬件SPI外设吗直接配置寄存器不就行了为什么还要软件模拟”这个问题我当年也纠结过。原因有两个。第一不是所有MCU都有足够的硬件SPI外设。比如你用一颗只有2个SPI的MCU但板子上挂了LCD、Flash、SD卡、传感器外设不够怎么办最灵活的办法就是GPIO模拟。第二某些国产芯片、低端MCU的硬件SPI写得很烂时序不稳定模拟反而更可靠。还有一种常见情况是“三线SPI”。有些从设备没有MISO线比如纯写设备只需要CLK、MOSI、CS三根线。这时硬件SPI外设依然可以配置成只发不收但用软件模拟更简单直接。4.2 基于GPIO的完整软件SPI实现我以最常见的Mode 0CPOL0, CPHA0为例写一份可以直接套用的模拟代码。这段代码其实没什么高深的地方但它把SPI的核心原理体现得淋漓尽致。// 伪代码适配不同MCU时替换为对应GPIO操作宏 #define SPI_CLK_H() gpio_write(CLK_PIN, 1) #define SPI_CLK_L() gpio_write(CLK_PIN, 0) #define SPI_MOSI_H() gpio_write(MOSI_PIN, 1) #define SPI_MOSI_L() gpio_write(MOSI_PIN, 0) #define SPI_MISO_READ() gpio_read(MISO_PIN) #define SPI_CS_L() gpio_write(CS_PIN, 0) #define SPI_CS_H() gpio_write(CS_PIN, 1) void spi_init(void) { // 配置CLK、MOSI、CS为推挽输出初始高电平 // 配置MISO为输入如果是硬件引脚则开上拉视从设备而定 spi_cs_high(); spi_clk_low(); // CPOL0时空闲为低注意初始状态 } void spi_write_byte(uint8_t data) { for (int i 7; i 0; i--) { // 先准备好数据线上的电平 if (data (1 i)) { SPI_MOSI_H(); } else { SPI_MOSI_L(); } // 产生一个上升沿先拉低再拉高从低到高是上升沿 // 对于CPHA0数据在上升沿被采样所以数据必须提前准备好 SPI_CLK_L(); delay_dummy(); // 稍微延时保证建立时间 SPI_CLK_H(); delay_dummy(); // 保证保持时间 } } uint8_t spi_read_byte(void) { uint8_t data 0; for (int i 7; i 0; i--) { // 对于读在上升沿采样MISO SPI_CLK_H(); // 先拉高产生上升沿 delay_dummy(); if (SPI_MISO_READ()) { data | (1 i); } SPI_CLK_L(); delay_dummy(); } return data; } void spi_transfer(uint8_t *tx, uint8_t *rx, uint32_t len) { SPI_CS_L(); for (uint32_t i 0; i len; i) { uint8_t byte 0; for (int bit 7; bit 0; bit--) { // 发 if (tx (tx[i] (1 bit))) SPI_MOSI_H(); else SPI_MOSI_L(); SPI_CLK_H(); // 收 if (rx SPI_MISO_READ()) byte | (1 bit); SPI_CLK_L(); } if (rx) rx[i] byte; } SPI_CS_H(); }这里最核心的一点是发送时数据电平必须在时钟边沿之前就稳定。然后时钟的跳变才会让从设备采样到正确的电平。如果你把顺序搞反了比如先拉高时钟再设置MOSI从设备采样到的就是上一次的数据整个通信直接错乱。我见过不少新手抄了网上的模拟代码跑不通最后查出来都是GPIO初始电平不对。以CPOL0为例空闲时CLK必须为低。如果你上电后拉了高那么第一个“上升沿”其实是从高到低是下降沿而非上升沿所有数据边沿对不上后面全乱。4.3 三线SPI和半双工变种有些项目里要做“三线SPI”或“半双工SPI”比如DHT11这类单总线传感器虽然严格说不算SPI但原理是边沿采样。模拟实现时只需要把MOSI和MISO复用在同一个引脚上收发切换需要在函数内部增加方向控制。这种场景下软件模拟比硬件外设更容易实现因为你完全掌握引脚的输入输出方向切换时机。5. STM32硬件SPI与CubeMX配置从HAL库到DMA的实战经验5.1 CubeMX里这些参数到底怎么选很多人在CubeMX里看到SPI配置界面一脸懵Baud Rate Prescaler选多少Clock Polarity和Clock Phase选啥Data Size是8位还是16位First Bit是MSB还是LSB我先给一份“照着填不会错”的默认配置以STM32F1/F4系列为例Mode选择Master主模式Hardware NSS Signal如果用软件片选就选Disable或NSS Soft这里强烈建议选Disable然后用普通GPIO管理片选。Data Size8位绝大多数SPI从设备都是8位字节操作First BitMSB First绝大多数芯片高位先发但也有少数存储器低位先发需查手册Prescaler先选一个较低的分频如32分频调通后再逐步提高CPOL/CPHA照从设备手册填不确定就逐个试TX/RX DMA如果需要高吞吐勾上DMA通道关于预分频Baud Rate Prescaler有一个容易忽略的点SPI的时钟速率不是越高越好而是要在从设备能承受的范围内并且要考虑PCB走线质量。比如W25Q系列Flash支持最高104MHz写时钟但实际布线不好、过孔多的情况下跑到40MHz就不稳定了花屏、读写错乱接踵而至。我一般先把分频调到10MHz左右验证基本功能稳定后逐级提速每级都做读写校验。5.2 HAL库收发函数的选择与典型坑STM32的HAL库提供了几个常用的SPI收发函数HAL_SPI_Transmit()只发不收HAL_SPI_Receive()只收不发对于需要主设备主动提供时钟的从设备这个函数会发送一个dummy字节HAL_SPI_TransmitReceive()同时收发DMA版本HAL_SPI_Transmit_DMA()/HAL_SPI_Receive_DMA()/HAL_SPI_TransmitReceive_DMA()实际调试中最大的坑在只收不发这个函数上。很多从设备比如Flash、传感器读取数据时主设备必须先发送一个命令字节可能是读命令地址然后从设备才会在后续时钟下返回数据。如果你直接调用HAL_SPI_Receive()它虽然会输出时钟但输出的数据是0x00或0xFF很多从设备不认这个空命令读回来的就是垃圾数据。正确做法是使用HAL_SPI_TransmitReceive()把tx buffer填成你要发的命令字节rx buffer用来收数据。比如读W25Q64的JEDEC IDuint8_t tx[4] {0x9F, 0x00, 0x00, 0x00}; // 0x9F是读JEDEC ID命令 uint8_t rx[4] {0}; HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); HAL_SPI_TransmitReceive(hspi1, tx, rx, 4, 100); HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // 此时rx[1]是厂商IDrx[2]是容量等5.3 DMA传输为什么能提高性能又带来什么新麻烦当要传输的数据量大时比如刷一张全屏图片、写入一页FlashCPU一直盯着SPI寄存器会浪费大量时间。用DMA可以把数据在内存和外设之间自动搬运CPU去干别的事。这是很常见的性能优化手段。但DMA有它自己的坑。第一个是缓冲区生命周期问题如果在DMA还没传完时你的tx buffer是局部变量或者被释放了DMA会继续从废弃的内存读数据产生不可预测的结果。解决办法是确保缓冲区在DMA传输完成前一直有效使用HAL_SPI_Transmit_DMA()前把buffer定义为全局或静态变量并在回调函数中确认传输完成。第二个是片选时序问题硬件片选时DMA结束后片选才恢复但软件片选时你必须在DMA传输完成回调函数中再拉高片选这就增加了异步逻辑的复杂度。我的建议是小数据量几十字节内用阻塞式收发大数据量屏幕缓冲、多页Flash写入才用DMA。有一个非常实用的小技巧把“发命令字收数据”合并成一个DMA收发流程。在tx buffer前面填充好命令字节在接收buffer中跳过前面几个字节一次性完成整个读写周期。这样可以减少片选拉低/拉高的频次提升整体效率。缺点是逻辑复杂度增加需要做好数组偏移处理。6. SPI在常见外设上的应用实践Flash、屏幕、SD卡与传感器6.1 SPI Flash读写时序与软件下载算法SPI Flash以W25Q系列为代表是我用过最多的SPI从设备。它支持标准的读、写、擦除操作本质都是通过发送命令字节地址来完成的。读懂Flash数据手册里的命令表和时序图是写好驱动的前提。以W25Q64为例几个关键命令0x9F读JEDEC ID判断芯片型号0x06写使能每次写操作前都要发送0x02页编程一次最多写256字节0x20扇区擦除一次擦除4KB0x03读数据这里最容易忽略的是写使能这一步骤。很多新手直接发页编程命令结果Flash没有任何反应查了半天发现忘了发0x06。而且每次写操作后状态寄存器会显示忙状态BUSY位必须轮询状态寄存器直到不忙才能进行下一次操作。另外如果你在做STM32F429这类MCU的外部Flash下载算法热搜里的“stm32f429的w25q256的spi下载算法”本质就是实现一个Bootloader能识别的编程接口Init、EraseChip、EraseSector、ProgramPage、Verify等。这里特别要提醒下载算法的代码运行在RAM中不能用Flash存储否则算法加载时会自己把自己覆盖。在实际开发中我见过有人把W25Q64的页编程当普通写操作一次写超过256字节结果数据错乱。原因就是页编程超过一页边界后会自动回卷覆盖当前页开头的数据。正确做法是如果写入数据跨页要拆分成多次页编程每次不超过页边界。6.2 SPI显示屏ST7789/ILI9341刷新率与通信优化Arduino接ST7789这类SPI屏幕是很多人的入门作品。但每次全屏刷新卡顿、花屏的问题也让不少人头疼。SPI屏幕刷新率受三个因素影响SPI时钟频率、像素格式、显示控制器性能。ST7789一般支持最高几十MHz的SPI时钟。如果你以典型的240x240分辨率、16位色深刷一帧数据量240×240×2115200字节。假设SPI时钟为20MHz理论刷新时间约46ms但实际考虑命令发送、等待TE信号、DMA启动开销一帧在60-80ms很正常对应13-16fps。如果想提升刷屏速度我有几条实测有效的经验使用DMA传输像素数据CPU不用逐字节搬运。ST7789的初始化命令交互频率低可以阻塞发命令但图像数据一定要走DMA。配置“写RAM”命令后的连续写模式避免每写一行就重复发送命令和坐标。如果屏幕支持8位并口或QSPI优先用这些方式刷新率能比标准SPI再快很多。不过对普通玩家标准SPI加DMA已经够用。另外中高分辨率彩屏的花屏问题很多是SPI时钟过快且走线过长导致的。我建议屏幕排线超过10cm时SPI时钟保守一点不要超过10MHz有条件的话在SCLK上串一个33Ω电阻减少过冲和振铃。6.3 ESP32上屏幕与SD卡共享SPI的取舍热搜里有个非常具体的问题“esp32屏幕与sd卡共享spi哪个好”这个问题很典型因为很多离线语音助手、相册项目都要同时挂屏幕和SD卡而ESP32的可用SPI外设有限。为了节省IO不少人把屏幕和SD卡挂到同一条SPI总线上用不同的CS片选来区分。这个方案的优点是省IO、接线简单缺点也很明显总线仲裁问题两个设备不能同时占用总线驱动层必须做好互斥。用完SD卡要立刻释放总线屏幕刷新才能继续。速度互相拖累SD卡读文件时屏幕不能刷新反之亦然整体体验会卡顿。干扰问题SD卡的数据线往往走线较长高频信号在SD卡操作时会对屏幕信号产生干扰表现为花屏或偶发闪烁。我的建议是如果项目不复杂直接共享SPI没问题只要在应用层设计好“先读卡、再刷屏”或“刷屏间隙读卡”的调度逻辑。如果对流畅度要求较高预算IO充足就把屏幕和SD卡分开屏幕用高速SPISD卡用SDMMC接口或另一路SPI各跑各的互不干扰。我做离线语音相册时踩过这个坑一开始共享SPI刷屏加读卡发现每次切图都卡得不行后来把SD卡换到SDMMC接口才彻底解决。所以如果你在ESP32上做类似项目这是一条值得提前避开的弯路。6.4 SPI传感器DHT11、AFE4490这类特殊从设备并不是所有“长得像SPI”的器件都老老实实按标准SPI工作。比如DHT11虽然它有CLK和DATA线但它的时序是单总线式的和标准SPI并不兼容。这种器件用软件模拟反而更方便因为它要求的是精确的us级延时和边沿判断硬件SPI外设反而不容易满足。另一个例子是AFE4490这是一颗血氧模拟前端芯片它的SPI接口就是比较标准的从设备。用它时的重点是读出来的数据是24位或32位的原始ADC值必须配合采样时序和通道配置寄存器才能正确解析。所以这种传感器的驱动开发SPI时序本身不是难点数据手册里的寄存器映射才是核心。通用的经验是对于任何SPI传感器第一步一定是读ID或状态寄存器确认SPI配置正确再去琢磨功能寄存器。否则如果SPI都没配对后面写再多寄存器都是白费。7. 高云FPGA的SPI远程升级、Verilog实现和SPI时钟约束7.1 SPI在FPGA里的地位主控与外设的桥梁FPGA场景下SPI常常以两种角色出现一是作为从设备接收MCU发来的配置数据二是作为主设备去控制板上的Flash、ADC等外设。热搜里“高云 基于spi接口的fpga远程升级实现”以及“spi slave verilog”、“spi verilog”都指向同一个需求用Verilog/VHDL自己写SPI控制器或做在线升级逻辑。写Verilog SPI模块时核心就是状态机。一个最简单的SPI Master发送模块至少有这样几个状态空闲IDLE、发送TRANSFER、结束DONE。在TRANSFER状态中通过一个计数器产生SCLK边沿同时在恰当的边沿把MOSI数据送出或者采样MISO数据。我简单贴一个单字节SPI发送模块的思路Mode 0module spi_master( input clk, // 系统时钟比如50MHz input start, // 启动发送脉冲 input [7:0] tx_data, output reg sclk, output reg mosi, output reg done ); localparam IDLE 2b00, TRANS 2b01, DONE 2b10; reg [1:0] state; reg [3:0] bit_cnt; reg [15:0] clk_div; always (posedge clk) begin case (state) IDLE: begin sclk 0; done 0; if (start) begin state TRANS; bit_cnt 0; clk_div 0; end end TRANS: begin clk_div clk_div 1; if (clk_div 0) begin sclk ~sclk; if (sclk) begin mosi tx_data[7 - bit_cnt]; bit_cnt bit_cnt 1; end end if (bit_cnt 8) state DONE; end DONE: begin done 1; state IDLE; end endcase end endmodule注意最后强调一点在FPGA中使用SPI时时序约束非常重要。如果输入时钟频率很高而你在FPGA内部用组合逻辑再分频产生SCLK很容易出现时钟树偏差和毛刺。推荐做法是使用FPGA内部的PLL/DLL产生SPI时钟并加上适当的约束文件SDC来保证建立保持时间。这也是“fpga spi”项目里最容易被忽略、但直接影响稳定性的环节。7.2 基于SPI的FPGA远程升级实现思路与坑简单说说远程升级的思路通过某种通道网口、串口、或者纯SPI Bootloader接收新固件写入外部Flash然后重启引导加载。如果用在SPI Flash上通常流程是擦除对应扇区→写入新固件→设置启动标志→软复位。这个过程对SPI的可靠性要求极高因为一旦写坏启动区板子就变砖了。所以在升级方案里我一般建议留一个“双备份”机制固化一个最小Bootloader在内部Flash或OTP区上电先检查外部Flash中是否有有效固件有效才跳转。这样可以大大降低升级失败导致变砖的风险。另外在擦除和写入过程中要加CRC校验或者长度校验防止写入不完整就被启动。8. 常见问题排查与避坑技巧实测经验速查表8.1 SPI调试必知的排查顺序每次SPI通信有问题我几乎都按下面这个顺序排查大多数问题都能在这些步骤里找到先确认接线SCLK、MOSI、MISO、CS四根线有没有接对。特别是MOSI和MISO很容易接反从者的MOSI要接主者的MOSI吗不从者的MOSI要接主者的MOSI这个一定要看主从定义。我见过太多人把主机的MOSI接到从机的MISO上结果完全不通。再确认电平用万用表量一下空闲时的CS是否为高、SCLK是否符合CPOL配置。如果CS空闲被拉低说明GPIO初始化有问题。确认时钟配置CPOL/CPHA、分频值是否合适。分频太高太快时可能不稳定先降到较慢速度验证。用示波器或逻辑分析仪抓波形这是最快定位问题的方式。看CS是否正常拉低SCLK是否有脉冲MOSI上的数据是否符合预期MISO上从设备有没有返回数据。最后怀疑芯片自身如果以上都正常但就是不通有可能是从设备的供电、复位、使能引脚没配置好。很多芯片有EN或RESET引脚必须正确控制才会响应SPI。8.2 几个“看起来诡异但真实存在”的经典问题问题一SPI通信偶尔出错但不是每次。这是最磨人的问题。通常诱因有三类时钟太快导致建立/保持时间不足电源纹波大、地线阻抗高导致逻辑电平抖动片选时序没满足从设备要求。处理办法就是降速、加强电源滤波、检查片选时序。如果数据量不大可以在关键操作后加一个“读回来校验”的机制发现错误就重试这比单纯追求高速可靠得多。问题二用DMA刷屏会花屏但阻塞发送就正常。这大概率是缓冲区和时序问题。花屏多发生在DMA和屏幕控制器时序不同步时常见原因是DMA传输完成后屏幕还在处理上一帧数据此时开始下一次传输就会造成显示撕裂或花屏。解决办法是用TETearing Effect信号或延时等待前一帧完成另外检查DMA的buffer是否被意外释放或覆盖。问题三读回的数据全是0xFF或0x00。排除接线问题后大概率是读命令没被从设备识别。原因是主设备在“读”之前发送的数据不是从设备期望的命令字。解决办法是用TransmitReceive函数同时收发不要单独用Receive函数。8.3 SPI调试小工具箱工欲善其事必先利其器。SPI调试时下面这几样东西能救你命逻辑分析仪八通道、24MHz采样率的逻辑分析仪就够用了。抓取CS/SCLK/MOSI/MISO四根线的时序一秒定位问题。社区版的PulseView软件使用PulseView时可以查看总线协议解码SPI解码非常方便。示波器逻辑分析仪只能看数字电平高低如果存在信号完整性问题过冲、振铃、边沿太慢必须用示波器。带宽100MHz起步。USB转SPI适配器有些时候你想在不写主控代码的情况下先用电脑验证从设备是否正常USB转SPI适配器比如FT2232H模块就很有用了。8.4 ARDUINO引脚映射、极路由SPI编程器等趣味案例热搜里“arduino st7789 spi屏”还有一个典型问题是Arduino的SPI引脚映射。UNO的SPI引脚固定在D13SCLK、D11MOSI、D12MISO、D10CS但可以改到任意引脚。很多国产板或自定义板卡引脚的映射不同网上抄的例程如果不改引脚定义必然白屏。所以用Arduino接SPI屏幕时要么用官方SPI库的默认引脚定义要么自己在初始化函数里指定引脚。至于“极路由4增强版 spi 编程器 固件”这种关键词反映的是路由器主板上的SPI Flash编程应用。这类操作需要把Flash芯片吹下来或用烧录夹夹住用编程器直接读写Flash。原理和普通SPI Flash读写完全一致只是工具换成了专用编程器。自己玩的时候注意备份原始固件设置烧录前校验避免变砖。9. SPI通信的未来方向与协议升级从单线到多线、从并行到点到点很多初学者以为SPI是一个“过去的技术”跟不上时代了。但事实恰好相反SPI在嵌入式内部通信中的地位依然稳固而且在不断演化。QSPIQuad SPI就是在标准SPI基础上增加了4根数据线IO0~IO3一次时钟可以传4个bit速度和吞吐量大幅提升。新型Flash、屏幕驱动芯片普遍支持QSPI。OSPIOcta SPI更进一步使用8根数据线速率可以跑到数百MHz。很多高端MCU比如STM32H7系列内置QSPI/OSPI控制器就是为了匹配高吞吐外设。同时SPI在多设备总线上也有改进传统的“一根CS对应一个设备”在设备很多时会消耗大量GPIO。一些芯片开始支持“菊花链”Daisy Chain拓扑或者使用带地址编码的SPI协议变体比如SPI with addressable slave从而减少片选线数量。从协议层面看SPI的简单性本身就是它最强的生命力。它没有IIC那种复杂的总线仲裁、也没有UART的波特率对齐问题时序直白调试方便。只要你在选型和实现上多留一个心眼它就能稳定跑很多年。在可预见的未来SPI依然会活跃在MCU与传感器、存储、显示设备之间的“最后一厘米”通信中。哪怕有了更高速的接口SPI作为一种优雅、简单、高效的点对点同步通信方式仍然值得每一个嵌入式开发者花时间去学透、用活。
返回列表