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

资讯详情

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

SPI总线深挖:四种模式、时序调试与系统设计要点

SPI总线深挖:四种模式、时序调试与系统设计要点 三年前我调试第一块SPI接口的Flash芯片W25Q64时差点把开发板摔了。写入单字节再读回数据全对写入整页数据再读回开头几个字节正常越往后错得越离谱。折腾两天后我才发现问题根本不在Flash芯片身上而在SPI总线的时钟极性和相位配置上——主控默认用Mode 0跟它说话这颗芯片偏偏要求Mode 3。那是我第一次意识到SPI总线这四根线看着简单但真正吃透它的人远没有想象中那么多。SPISerial Peripheral Interface串行外设接口是嵌入式系统里出现频率最高的高速同步串行总线之一Motorola在八十年代中期提出到今天依然是MCU连接Flash、SD卡、显示屏、ADC、传感器、无线模块的主力通道。它的核心标签就三个高速、全双工、协议简单。相比I2C要处理地址和应答相比UART要约定波特率SPI几乎是裸奔的——没有地址概念没有应答机制主设备给从设备一个时钟各自移位寄存器互倒数据就完事。但恰恰因为简单很多人上手就写代码跑通了就以为懂了等遇到数据错位、波形毛刺、多设备抢总线这类问题才发现自己对底层的理解全是漏洞。这篇文章就从传输原理、时钟模式、调试方法、系统设计和选型对比五个角度把SPI总线彻底聊透适合刚接触嵌入式的初学者也适合写过SPI驱动但没系统梳理过细节的工程师。1. 四根线的分工逻辑SPI为什么能全双工高速跑1.1 信号线命名里藏着的通信方向SPI总线最小系统需要四根线SCLK串行时钟、MOSI主出从入、MISO主入从出、SS/CS从设备选择通常低电平有效。很多初学者会把MOSI和MISO记混其实名字本身就是答案MOSI全称Master Out Slave InMISO全称Master In Slave Out。主设备要发数据给从设备就走MOSI从设备要回数据给主设备就走MISO。这两根数据线方向固定、互不干扰所以SPI天然支持全双工——同一时刻既能发也能收。SS线则是主设备用来点名的一个SPI主控片上往往有多组SS引脚选中哪个从设备就把对应引脚拉低其他从设备的SS保持高电平自动与总线隔离。有个细节需要注意SPI从设备的MISO引脚通常设计成三态输出。当它的SS没有被拉低未被选中时MISO引脚必须处于高阻态不能往总线上输出任何东西。否则多个从设备同时驱动MISO线轻则数据被拉偏重则烧毁引脚。这个特性在后面讲多从机拓扑时会重点展开。1.2 移位寄存器的环形接力一次时钟脉冲同时换一个比特SPI的收发本质是两个移位寄存器的环形接力。主设备内部有个8位移位寄存器从设备内部也有一个8位移位寄存器MOSI和MISO把两者接成一个首尾相连的环。每个时钟脉冲到来时主设备把移位寄存器最高位移出去经MOSI从设备把移位寄存器最低位移进来同时从设备最高位经MISO移回主设备。一个字节8个时钟脉冲走完主设备的移位寄存器变成了从设备原来的内容从设备的移位寄存器变成了主设备发过去的内容。这个机制解释了SPI新手最容易迷惑的一个问题为什么接收数据时必须先发送一个字节原因很简单——SPI是同步总线没有时钟就没有数据传输。主设备如果想从从设备读回一个字节必须先主动产生8个时钟脉冲而这8个脉冲必然会同时把主设备移位寄存器里的内容推给从设备。至于推出去的这8位是什么完全由主设备决定。所以你在读Flash的状态寄存器时会先发送一条读命令比如0x05紧接着发送一个任意字节通常写0x00或0xFF作为陪跑时钟真正要读的数据正是在发送这8个陪跑时钟时从MISO线上一位一位回来的。可以用一个生活场景类比想象两个人隔着柜台交换整盒卡片盒子一次性从左边滑到右边同时右边那盒从下方滑回左边。时钟是那个推盒子的动作每推一次双方各拿到对方的一张牌。SPI就是这么直接没有寻址、没有应答、没有流量控制所有复杂性都交给主从两侧的软件去兜底。1.3 没有应答机制的代价从设备哑巴了怎么办SPI协议本身没有内置应答ACK/NACK机制。主设备发完一个字节从设备到底收到没有、执行没有、出错没有SPI线路上完全看不出来。这一点和I2C形成鲜明对比——I2C每发送一个字节后接收方必须拉低SDA回应一个ACK位否则发送方就知道传输失败了。SPI的哑巴通信带来一个好处接口逻辑极简频率可以做得很高很多SPI Flash支持104MHz甚至更高时钟。代价则是主设备必须借助协议层面或器件层面的手段来确认从设备状态。以W25Q64为例主设备写入数据后Flash正在擦写内部存储阵列时不会响应新的读写指令所以驱动里必须反复发送读状态寄存器命令0x05轮询其中的BUSY位直到它清零才认为写操作真正完成。如果跳过这一步紧接着发读命令去读刚刚写入的数据读回来的大概率是旧数据或者全0xFF。另外一些从设备提供额外的硬件忙信号引脚比如某些触摸屏控制器用INT引脚表示可读取或者依赖主设备主动延时固定等够Datasheet标称的最坏写入时间。做驱动设计时第一件事就是翻Datasheet确认这个器件有没有状态寄存器操作完成后需要轮询还是延时把这个问题想清楚再动手写代码能少踩一半的坑。2. CPOL与CPHA不是玄学四种SPI模式选错会怎样2.1 时钟极性CPOL和时钟相位CPHA在定义什么SPI有四种工作模式由CPOLClock Polarity时钟极性和CPHAClock Phase时钟相位两个参数共同决定。数据手册里常见的SPI Mode 0Mode 3指的就是这两个参数的组合。CPOL决定SCLK在空闲状态也就是没有数据传输时的电平CPOL0表示空闲时SCLK为低电平CPOL1表示空闲时SCLK为高电平。CPHA决定数据采样发生在时钟的哪个边沿CPHA0表示在第一个边沿SCLK从空闲电平跳变的那个沿采样数据CPHA1表示在第二个边沿采样数据。两者组合出四种模式模式CPOLCPHA采样边沿以空闲低电平为例数据变化边沿Mode 000上升沿采样下降沿变化Mode 101下降沿采样上升沿变化Mode 210下降沿采样上升沿变化Mode 311上升沿采样下降沿变化为什么这个参数如此重要因为主从设备必须在同一个时钟边沿采样数据。如果主设备在上升沿采数据、从设备在下降沿采数据双方看到的输入信号就会差半个周期表现出来就是整体错一位读回来的字节要么最高位丢失要么末尾多出一个无效位。2.2 拿到一个从设备怎么快速确定该用哪种模式最权威的方法只有一种读Datasheet的时序图。以W25Q64为例它的时序图会清楚标出数据在SCLK下降沿被驱动、上升沿被采样此时CPOL0、CPHA0对应Mode 0而另一颗Flash芯片W25Q128JV在高速读模式下则使用Mode 3。市面上大量SPI Flash、SD卡、传感器都默认支持Mode 0或Mode 3所以绝大多数MCU例程里写的是SPI_MODE_0这也让不少开发者养成了上来就Mode 0的习惯——这个习惯在遇到Mode 1或Mode 2器件时会让你痛不欲生。如果手头没有Datasheet或者时序图画得含糊还有个笨办法写一个回环测试。把主控的MOSI和MISO短接或者使用支持回环模式Loopback的MCU分别用Mode 0、1、2、3发送0x55二进制01010101并读回读回数据等于发送数据的那个模式就是你主控侧的正确配置。但这只能验证主控自己的收发一致性不能保证和从设备匹配所以最终还是要以从设备时序图为准。2.3 模式不匹配的典型症状数据错位、末尾垃圾字节、读回全0xFF模式配错后不同器件表现出的现象略有差异但有几类症状非常典型整体错一位读回的数据字节看起来似曾相识但每一位都向右或向左错了一位。比如写入0xAA读回0x55或者写入0x80读回0x01这种规律性错位基本就是CPHA配置反了。读回最后一个字节多出垃圾位主设备和从设备对第一个数位在哪变化理解不一致导致从设备认为还没轮到它输出主设备却已经开始采样最终结果是最低位被补了一个不确定的值。读回全是0xFF或0x00这通常是CPOL配置错误。主设备空闲时钟电平和从设备要求相反从设备可能根本没被激活或者把空闲期的无效电平当成了有效时钟。间歇性故障Mode 0能通但偶尔出错Mode 3完全不通。这种情况往往发生在高速率下模式差异叠加信号完整性问题让时序裕量被吃掉。调试SPI时如果遇到上述现象不要急着怀疑硬件连接先花两分钟把CPOL/CPHA和从设备Datasheet核对一遍。这是成本最低的排查步骤也是我三次踩坑之后总结出的第一原则。3. 从一次Flash调试事故看SPI时序的完整排查链路3.1 故障现象写入成功但读回全是0xFF那次用STM32F407通过SPI1接口驱动W25Q64代码逻辑很简单写一整页数据再读回校验。写操作时我确实等了芯片内部完成状态寄存器的BUSY位也轮询通过了。但读回的数据前四个字节正常后面全是0xFF。起初我怀疑Flash芯片坏了换了一颗新片现象一模一样这时才意识到问题在我的SPI配置或代码逻辑。3.2 排查第一步用逻辑分析仪看真实波形盲改代码是最低效的调试方式。我直接拿逻辑分析仪同时抓SCLK、MOSI、MISO和CS四根线重点看两个点CS片选低电平的时间窗口是否覆盖了整个传输过程MISO上是否有有效数据返回。波形抓出来那一刻问题基本就暴露了我的驱动代码在每次传输一个字节后都会把CS拉高一次复位片选然后在下一个字节传输前再把CS拉低。对W25Q64这种Flash而言CS每拉高一次芯片就认为一次命令结束并复位内部状态机。所以我发读命令地址陪跑时钟时实际上被芯片理解成了三段完全独立的命令只有第一段被正确响应后面几段全都无效MISO自然返回默认的0xFF。3.3 排查第二步核对指令、字节顺序与位序波形确认CS时序后我又顺手核对了另外两个容易出问题的点字节序MSB First还是LSB First和指令本身。SPI协议绝大多数器件默认高位在前MSB First但总有个别例外。比如某些LCD控制器和温湿度传感器就是LSB First如果主控配成了MSB整个字节序就会整体颠倒。核对方法是看Datasheet里时序图最左侧标注的MSB或LSB字样。W25Q64明确是MSB先出所以这处没问题。指令核对则是拿逻辑分析仪抓出来的MOSI波形和Datasheet上的命令编码做比对。我发送的读命令0x03二进制是00000011波形显示MOSI确实先出了7个低电平再出两个高电平指令本身没问题。3.4 排查第三步验证模式与速率边界确认指令和CS问题后另外一个隐藏风险是模式。我原来用的是Mode 0而W25Q64的普通读命令0x03确实也支持Mode 0所以大部分数据能读对但由于CS拆分导致命令状态机错乱才会出现前几个字节对、后面全0xFF的现象。速率边界同样值得验证。W25Q64最高支持约104MHz时钟但这是短走线、低负载条件下的理想值。我的板子上Flash芯片离MCU走线较长且MISO线上挂了一个裸引脚没有做阻抗匹配104MHz下MISO信号已经出现明显的振铃。把SPI时钟从84MHz降到42MHz后读回的数据彻底稳定了。对大多数应用来说SPI速率并不需要顶着上限跑留出20%到50%的裕量非常有必要。3.5 最终根因与修复方案整个问题的根因明确驱动代码在字节间错误地拉高了CS破坏了Flash对一条连续命令的完整解析高速运行又放大了波形质量恶化带来的误码风险。修复方案也简单传输一整条命令期间保持CS为低命令结束后再拉高同时把SPI时钟降到42MHz并优化了MISO走线的过孔数量。这个案例给我最大的启发是排查SPI问题永远先看波形再看配置最后才怀疑芯片。逻辑分析仪是SPI调试的第一工具哪怕是十几块钱的简易逻辑分析仪只要采样率足够都能帮你把代码逻辑问题和硬件时序问题迅速区分开。没有波形再多的推理都是猜。4. 设计一个稳定可靠的SPI系统多从机、布线、上拉与速率4.1 两种多从机接法独立片选与菊花链SPI系统要挂多个从设备常见两种方式。独立片选方式每个从设备的CS引脚接到主控不同的GPIO上MOSI、MISO、SCLK三根线共享。主设备要跟哪个从设备通信就单独拉低哪个的CS。这是最常用的做法优点是各个从设备完全独立可以有不同的SPI模式和速率——只要在切换时重新配置主控即可。缺点是占用的GPIO数量随着从设备数量线性增加。菊花链方式所有从设备的MOSI和MISO串成一串主设备的MOSI接第一个从设备的MOSI输入侧第一个从设备的MISO输出侧接第二个从设备的MOSI依此类推最后一个从设备的MISO接主控的MISO。所有从设备共享同一个CS数据在主设备发来的时钟驱动下像流水线一样逐级往后传。菊花链适合对实时性要求不高、数据量大的场景比如多颗移位寄存器串联驱动LED点阵。但它的缺点是延迟随级数累加且要求每个从设备都支持菊花链功能很多普通SPI外设并不支持。4.2 片选管理的隐藏坑总线冲突与三态缓冲多从机系统里最常见的隐蔽问题是某个从设备在CS为高未选中时MISO引脚没有进入高阻态。如果这个从设备的MISO电平正好和另一个正在通信的设备的MISO电平相反两条输出线就会在共享节点上打架——表现出来是主设备读到的是两个信号叠加后的畸形波形。解决思路有三层选型阶段优先选MISO三态输出的器件。绝大多数正规芯片都会做但某些成本敏感的元器件会省略这个设计Datasheet里会有明确说明。在所有从设备的CS引脚上加上拉电阻通常10kΩ确保主控GPIO在上电瞬间处于未定义状态时从设备不会因为CS悬空误激活。这个细节很多硬件原理图都会漏但漏掉的后果是上电瞬间总线乱抢偶尔导致主控锁死或Flash被误写入。主控初始化时先把所有CS引脚配置成高电平输出再初始化SPI外设。顺序反过来的话SPI外设从复位状态释放时片选引脚可能瞬间被拉到低电平造成一次意外的总线访问。4.3 速率上限不只由Datasheet决定信号完整性才是天花板很多开发者以为只要从设备标称支持80MHz主控把SPI时钟配到80MHz就万事大吉。实际上总线能达到的真实速率上限由三件事共同决定主控SCLK输出能力、从设备最高输入时钟、PCB走线质量。其中前两个由芯片规格决定最后一个才是考验硬件功力的地方。SPI高速传输时最常见的信号完整性问题包括串扰与振铃SCLK和数据线之间距离过近时钟边沿的跳变会耦合到数据线上导致采样错误。解决方法是数据线和时钟线包地、拉开间距或者降低时钟斜率如果可以配置驱动强度。走线不等长SCLK和MOSI/MISO之间的走线长度差太大会引入相位偏移高速下表现为数据的建立时间不足。改善方法是让这组信号走线尽量等长尤其在高密度PCB上要刻意做绕线补偿。地回路不完整SPI信号在跨区域比如从数字区到模拟区布线时如果地平面被切断信号的回流路径就会被迫绕远路造成严重的EMI和误码。对SPI这类同步总线来说时钟线尤其需要紧贴地参考平面走线。对低速应用几MHz以下这些因素影响不大很多人直接飞线都能跑。但到了20MHz以上信号完整性就成了比Datasheet参数更大的瓶颈。我做过一个项目CPLD通过SPI驱动ADC采样最初在两层板上跑30MHz误码率偶尔会出现改成四层板、把SPI信号全部走在靠近完整地平面的内层后60MHz都稳定。结论是高速SPI先设计好板子再用代码去迁就板子别反着来。4.4 SPI没有时钟延展机制慢速从设备怎么办SPI和I2C有一个本质差异I2C允许从设备在传输过程中拉低时钟时钟延展来请求主设备慢一点而SPI协议里根本没有这种机制。主设备一旦把SCLK放出去就停不下来了从设备如果响应不过来数据就会被覆盖或者丢失。遇到慢速从设备常用三种处理策略主设备主动降频把SPI时钟调到从设备能承受的范围内。简单、可靠缺点是牺牲吞吐量。主设备字节间延时保持时钟频率不变但在每个字节传输完成后插入若干微秒延时给从设备留出内部处理时间。这个办法适合从设备每字节都需要时间消化的场景。轮询状态寄存器像Flash那样每发一个命令就查一次状态位等它忙完再发下一个命令。这是最稳妥的方式代价是驱动代码稍微复杂一点。不少人会问能不能让从设备用额外GPIO拉低SCLK来延展时钟理论上可以但这要求主控SCLK引脚支持外部钳制或检测绝大多数通用MCU的SPI外设并不支持这种操作。与其搞这种歪门邪道不如老老实实降频或轮询。5. 什么时候该用SPI与I2C、UART的选型逻辑5.1 三条总线核心差异一览嵌入式设备里最常用的串行总线就是SPI、I2C、UART三者选型本质上是在速度、连线、复杂度、灵活性之间做权衡。特性SPII2CUART信号线数量4根全双工可减少2根SDA、SCL半双工2根TX、RX全双工通信方式同步同步异步最高速率极高数十到上百MHz中低标准模式100kHz到3.4MHz低到中常见9600到几Mbps地址机制无靠片选线7位/10位地址无点对点应答机制无有ACK/NACK无多设备支持靠独立片选或菊花链靠地址总线直接挂多设备一般点对点从设备主动上报不支持不支持不支持典型应用Flash、SD卡、显示屏、ADC传感器、EEPROM、RTC调试串口、GPS、蓝牙模块从这个表能看出来SPI的最大优势是速度和全双工最大劣势是线多、没有寻址。I2C的最大优势是两根线可以挂几十个设备最大劣势是速度慢、协议复杂一点。UART的特点是异步收发不需要时钟线适合点对点、长距离通信。5.2 我的选型经验按器件类型快速决策实际项目中我的选型习惯基本可以总结为以下几条大容量Flash/存储无脑选SPI。SD卡虽然也支持SDIO接口但SPI模式兼容性最好、代码最通用。SPI Flash更是存储领域的标准接口几乎每颗都支持104MHz。显示屏/触摸屏分辨率高、刷新率要求高的屏比如320x240以上的TFT用SPI还是有点吃力更建议并口或RGB/MIPI/QSPI但小尺寸OLED和低分辨率LCD用SPI完全够。带触摸屏控制器时手指坐标数据量小SPI和I2C都可以看主控资源和走线空间。传感器加速度计、陀螺仪、磁力计这类低速小数据量的传感器首选I2C。一根线挂多个传感器省GPIO而且传感器本身速率就是几十到几百kHzI2C足够。个别需要高速连续采样的传感器比如某些激光测距、音频ADC才需要上SPI。无线模组Wi-Fi、LoRa、BLE模组最常用UART做AT指令交互因为它的异步透明传输特性最适合MCU只当管道用的场景。部分LoRa芯片如SX1262支持SPI直连因为需要高速读写配置寄存器和FIFO缓冲区SPI比UART更高效。多通道ADC/DAC采样率要求高的用SPI采样率低的用I2C。ADC和DAC对时序敏感SPI的全双工特性还可以做到写配置的同时读采样结果一个转换周期就完成一次读出。5.3 不要为了用SPI而用SPI低速场景下的过度设计SPI的功能很强大但它不是万能钥匙。我见过不少项目一共就挂一个每100ms读一次的温湿度传感器却为了SPI更快把四根线从主控拉到传感器附近占用了四个GPIO还因为布线引入噪声反而需要加滤波。这属于典型的过度设计。选总线时先列三个问题数据量多大更新频率多快板子上还有没有空闲GPIO如果传感器每秒只上报几十字节I2C是更优解——两根线可以顺便再挂四五个同样低速的设备。如果速率和GPIO都紧张优先考虑能不能把多个传感器合并到同一条I2C总线上省线的同时也能简化布线。如果外设本身就自带UART接口比如蓝牙模组别多此一举去选SPI版本UART的透明传输特性和流控机制对这类应用更成熟。从我个人的项目经验看SPI更适合单一或少数几个高速外设的场景I2C适合多个低速外设共享总线的场景UART适合点对点、异步、需要长距离的场景。选型没有绝对的对错只有合不合适。真正的高手不是什么高速接口都会用而是能在方案评审阶段就判断出哪条总线能少走弯路、少挖坑。回看那次Flash数据错乱的调试经历我最大的收获不是会配SPI模式了而是养成了三个习惯先看Datasheet时序图再写驱动、调试必抓波形不靠猜、速率永远留裕量。这三条习惯让我后来在做多从机SPI系统、四层板高频布线、以及把SPI驱动从STM32移植到ESP32和FPGA时都少踩了大量重复的坑。SPI总线本身并不难它的坑恰恰藏在那些看起来不重要的细节里——片选拉高的时机、从设备三态门、字节间延时、时钟相位一致性。把这些细节拿捏住了SPI就是嵌入式开发里最趁手的高速通道之一。
返回列表