news 2026/10/25 1:12:40

SPI vs I2C:嵌入式总线选型与调试实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SPI vs I2C:嵌入式总线选型与调试实战指南

说实话,第一次看到“从沙子到车辙”这个系列名的时候,我以为是讲硬件从零开始DIY的,结果读了几篇才发现,这是讲嵌入式底层通信的,从“沙子”里的硅片讲到“车辙”一样清晰的信号路径,脑洞确实大。这一篇聊的是“从片内到片间”,也就是芯片和芯片之间、模块和模块之间怎么把数据说清楚。而说到这个,绕不开的两大主角就是SPI和I2C。

做嵌入式这么多年,和这两种总线打的交道实在太多了。很多刚入门的朋友一上来就背协议时序图,背完就忘,遇到波形不对还是不会调。这篇我打算换个角度,用做事踩坑的经验来讲清楚SPI和I2C到底是什么,它们各自的性格、脾气,以及哪种场景选谁更合适。顺便把搜索热词里大家最关心的几个问题——比如I2C开漏模式、SPI硬件片选和软件片选怎么选、软件SPI和硬件SPI的差距、SPI接DMA要怎么配——一并梳理清楚。

1. 内容整体设计与思路拆解

1.1 两种总线,其实是两种世界观

先聊个最基础但很多人没深想的问题:为什么有了UART,还要搞SPI和I2C?

UART是异步的,双方各用各的时钟,靠起始位和停止位把数据框起来。它的好处是简单,两根线就能跑,但缺点是速度上不去,也不适合一拖多。而SPI和I2C解决的核心问题都是“同步通信”,就是收和发共用同一个时钟节拍,时序精确可控。这两兄弟在大学教材里经常挨着讲,但实际上是两种完全不同的设计思路。

SPI的全称是Serial Peripheral Interface,串行外设接口。它是一个主子架构的总线,主设备提供时钟SCLK,数据在MOSI和MISO两根线上各走各的,全双工,想多挂设备就靠片选信号CS一个个点名。它的风格是“我给你时钟,你就得立刻回话”。

I2C的全称是Inter-Integrated Circuit,集成电路间总线。它只有两根线,一根时钟SCL,一根数据SDA,半双工。它带设备地址,总线上挂一堆设备,靠地址区分谁应答。它的风格是“我叫你,你应一声,我才继续说”。

用生活化的类比来说,SPI像是上课点名,老师挨个儿被点到谁谁就回答问题,没点到的不要插嘴,效率高但人多的时候老师点名点到手软。I2C更像对讲机群聊,大家一起听一个频道,但每条消息带个呼号,只有呼号对得上的那台才回应,线少但规矩多。

这个本质差异决定了后续的选型。SPI适合量大、要求速度、主从关系明确的场景,比如Flash存储、SD卡、ADC采样、屏幕刷新。I2C适合数量多、速率要求不高的传感器、RTC、EEPROM这类设备。

1.2 选型思路:速度之外还要看什么

很多人选总线只看速度,SPI几十MHz,I2C通常几百kHz到几MHz,那是不是无脑上SPI就行了?不是这样的。我见过有人用I2C读一个加速度计,每秒采样几百次,完全跑得动,但他偏要换SPI,结果改动了一大堆驱动代码,把CS引脚接错,折腾了两天才好。反过来,也有朋友在SPI上挂了一个温湿度传感器,非要按I2C的时序去读卡的,最后连设备地址都找不到。

选型的时候,速度只是一个维度。还要看引脚数量够不够。SPI最少四根线,I2C只要两根。在引脚紧张的板子上,I2C的复用优势就很明显。还要看设备数量和寻址方式。I2C天然支持一总线多设备,SPI每多挂一个设备就要多占一个片选引脚。还有实时性的要求,SPI的时序更确定,适合硬实时场景。

我把这两种总线的关键属性整理成一个对照表,大家在选型的时候可以对着看:

特性SPII2C
线数4根(SCLK、MOSI、MISO、CS)2根(SCL、SDA)
通信模式全双工半双工
速率范围通常数十MHz通常数百kHz到数MHz
设备寻址靠硬件片选CS靠7位或10位软件地址
拓扑结构主从点对点为主多主多从总线型
电气特性推挽输出开漏输出加上拉
适合场景高速数据、存储、采样低速传感器、RTC、EEPROM、配置芯片

拿做项目来说,我常挂在嘴边的一句话是:SPI干的是累活,I2C干的是杂活。累活要的是力气,杂活要的是人脉。理解了这个比喻,选型思路就清晰了一大半。

2. 核心细节解析与实操要点

2.1 SPI的四种模式,你真的会配吗

很多朋友在SPI上踩的第一个坑就是模式配错。SPI有四种模式,由CPOL(时钟极性)和CPHA(时钟相位)共同决定。CPOL决定空闲时时钟是高还是低,CPHA决定数据是在第一个跳变沿还是第二个跳变沿采样。

具体来说:

  • 模式0:CPOL=0,CPHA=0,空闲时钟低,第一个沿采样
  • 模式1:CPOL=0,CPHA=1,空闲时钟低,第二个沿采样
  • 模式2:CPOL=1,CPHA=0,空闲时钟高,第一个沿采样
  • 模式3:CPOL=1,CPHA=1,空闲时钟高,第二个沿采样

绝大多数Flash芯片、SD卡、显示屏都支持模式0和模式3,你在网上搜别人的代码,很多默认配的也是模式0。但这不是想当然的,我遇到过一款传感器,手册里写得明明白白是模式1,有人没看手册直接用模式0去读,出来的数据全是歪的,波形看着好像有数据,但整理出来基本是乱码。

配模式这件事,唯一的正确做法就是翻芯片手册,找到SPI章节的时序图,看清楚CPOL和CPHA,然后和你代码里的配置对应上。没有第二种捷径。如果条件允许,先用示波器或者逻辑分析仪抓一下主设备发出的时钟和数据波形,对照手册觉得没问题再往深处调试。

2.2 I2C的开漏模式与上拉电阻

搜索热词里有一句“I2C的推挽模式和开漏模式”,这个值得好好展开。I2C的SDA和SCL必须是开漏输出,这是协议层面规定的,不是硬件设计时随随便便定的。开漏的意思是,芯片内部不主动输出高电平,只能把引脚拉低或者释放。释放之后,引脚的电平由外部上拉电阻决定。

这个设计精妙在哪?在于它天然支持“线与”逻辑。任何一台设备想拉低总线,都能把总线拉低,多个设备同时驱动也不会出现短路冲突。这在多主设备场景下特别重要,因为总线仲裁就是靠“看谁先把总线拉低”来判定的。

但开漏也带来一个非常实际的问题:速度上不去。因为释放引脚之后,高电平要靠上拉电阻慢慢把寄生电容充满,电阻越大,上升沿越缓,速率越高越容易出错。所以上拉电阻的取值是个权衡,常见的取值范围在1k到10k欧姆之间。

具体怎么选?我之前做过一个项目,I2C总线上挂了三个从设备,总线长度大概10厘米,我用的是4.7k的上拉,跑400kHz标准模式,很稳定。后来加了一块距离稍远的扩展板,总线长了之后,4.7k就偶尔出现ACK丢失,换成了2.2k才好。一般来说:

  • 低速模式,100kHz,总线短,可以用10k或4.7k,省电
  • 标准模式,400kHz,推荐4.7k
  • 高速模式,1MHz以上,总线短,用2.2k或1k更稳

还有一件事容易被忽略,就是上拉电阻接的电源电压要和器件电平匹配。比如MCU是3.3V,但I2C从设备是5V的,这种场景电平转换不是简单串联电阻能搞定的。最可靠的办法是用电平转换芯片,或者用支持混合电压的开漏结构自己搭离散MOSFET转换。网上热词里“I2C需要电平转换吗”问的就是这个,我说句经验之谈:如果两边电平不一致,接口处标称又没写“电平兼容”,那大概率需要做转换。

2.3 硬件片选与软件片选之争

SPI的片选看起来最简单,就一根线拉低选通、拉高释放。但这里藏着一个老生常谈的问题:硬件片选(硬件NSS自动控制)和软件片选(GPIO手动控制)到底用哪个?

不少MCU的SPI外设自带NSS引脚,启用硬件片选后,外设会自动管理片选的时序。听起来很智能,但实际用起来反而容易出事。因为硬件片选在一些MCU上的行为和预期不一样,尤其在连续传输多字节数据时,NSS可能在每个字节之间跳变一次,导致从设备以为传输结束了,直接把数据丢掉。我调过的某个STM32型号就有这种表现,当年排查了好几个小时,最后把CS改成软件控制立刻就好了。

软件片选的好处是控制权完全在你手里。你可以在一次事务开始之前把CS拉低,传输结束后手动拉高,中间不管发多少个字节都不会中断。对多数从设备来说,这是最稳的。那什么情况用硬件片选?有的MCU结合DMA做数据流传输时,硬件NSS的翻转和DMA搬运配合得非常好,能省CPU干预。但现在很多工程师的做法是:默认软件片选,除非你对这颗MCU的硬件NSS行为非常了解,并且验证过它不会中途乱跳。

还有个小坑是片选引脚的电平极性。绝大多数器件是低有效,但有些屏幕模块、射频芯片是高有效片选。代码里如果默认拉低选通,遇到高有效器件就永远选不中。解决办法还是看手册,看清CS的active level,然后代码里配置好。

3. 实操过程与核心环节实现

3.1 用硬件SPI驱动一款Flash存储芯片

纸上谈兵没意思,来点实际的。我之前做过一个数据记录器的小板子,MCU要把采集的数据存进一颗SPI接口的NOR Flash。当时用的具体型号不说了,反正时序是标准SPI模式0。整个配置流程可以分成几步,第一步是把SPI外设和GPIO初始化好,第二步是CS引脚拉高,Flash默认不选中,第三步是写使能,第四步是发写指令和地址,第五步是把数据发出去,最后轮询状态寄存器等忙标志清掉。

初始化SPI的时候,时钟速率我当时设的是系统主频的四分之一分频。选四分之一不是拍脑袋的,因为Flash手册里写的最大时钟是某一档,我的分频之后约等于那个值的一半,留了余量。如果分频太狠,速度上不去,写入很慢;如果分频太激进,时序余量不足,遇到批次不太一样的芯片就容易偶发错误。我曾经图快把SPI时钟调到和手册极限值差不多的位置,板子单独跑测试没问题,但装上整机之后因为电源纹波变大,偶尔写出来的数据校验不过,把时钟降回来之后就再也没出过事。所以SPI时钟设置的第一个原则是别贴着极限跑。

代码层面的核心逻辑大致是:

flash_cs_low(); spi_write_byte(CMD_READ); // 读指令 spi_write_byte(addr >> 16); // 地址高字节 spi_write_byte(addr >> 8); // 地址中字节 spi_write_byte(addr & 0xFF); // 地址低字节 for (i = 0; i < len; i++) { buf[i] = spi_read_write_byte(0x00); } flash_cs_high();

这段代码有一个容易被忽略的点:读数据时主设备必须不停地给从设备提供一个时钟才能让数据出来,而这个“喂”时钟的动作在SPI里就是发送任意字节。我这里发的0x00,有些驱动里写成0xFF也没问题,只要保证时钟在跑就行。有的同学以为读数据不用发东西,结果时序出来对不上,就是这个原理没理解透。

这个案例里最重要的是理解CS和指令的配合。在一个片选周期内,你可以连续发“读指令+地址+若干数据”作为一个完整的事务。同理写操作也要把“写使能指令”和“页编程指令”分开两个片选周期完成。很多新手把指令全部串在一起,没有单独做写使能,结果写操作永远不生效,查半天找不到原因。

3.2 软件SPI和硬件SPI到底差在哪

搜索热词里有“软件SPI和硬件SPI的区别”,这个问题问得特别好。我说的软件SPI,就是不用外设,直接用GPIO翻转时钟和数据线来模拟SPI时序。硬件SPI则是MCU内部集成的SPI外设自动完成移位和电平翻转。

软件SPI和硬件SPI的最核心区别是速度。硬件SPI的时钟可以跑到几十MHz,软件SPI我实测在普通8位MCU上能稳定跑的模拟速率通常也就是几百kHz到一两MHz。因为每一位数据都要靠CPU去翻转引脚,再怎么优化也甩不开这个瓶颈。但软件SPI的价值在于引脚任意、模式随便改、调试起来直观。

我自己的经验是:如果在做原型验证或者临时替代方案,软件SPI完全可以顶上。比如某个模块的硬件SPI引脚被别的功能占用了,我就用两个普通GPIO模拟SPI,跑个1MHz,给一颗温湿度传感器做采样完全够用。但如果做产品量产,或者数据量很大,那还是老老实实硬件SPI加DMA,CPU的负担差远了。

软件SPI实现的时候要注意时钟极性和相位的模拟,尤其要保证数据的建立时间和保持时间。我写模拟SPI时习惯先在时钟边沿之前把数据线拉好,然后再翻转时钟。这个顺序不能反,反了会让从设备采样到不稳定数据。

3.3 I2C读写EEPROM的完整流程

I2C总线最经典的练手项目就是读写EEPROM。搜索热词里有一条“I2C读写EEPROM代码 Verilog”,看来硬件描述语言的场景也有人在搞,我先说通用流程,再补充一点针对性的建议。

I2C写EEPROM的标准流程是:发出起始条件,然后发设备地址加写位,等待从设备ACK,接着发寄存器地址,再等ACK,然后发送要写的数据,等待ACK,最后发停止条件。读的流程稍有不同,要把读写位从写切到读,发送一个重复起始条件,然后再读字节,读完要回NACK表示“我没有更多数据要读了”,最后停止。

实际调I2C时最常见的三个问题,我在这里集中说一下。

第一个问题是ACK检测。很多I2C控制器在发送完地址字节后会自动检查从机的ACK位,但如果用GPIO模拟I2C,就需要手动检测SDA在第九个时钟里是否为低。漏掉这一环节,可能导致你在写一个不存在的地址时,代码还继续往下发,白白浪费时间。

第二个问题是重复起始条件。有些简化版驱动读EEPROM时,发完地址后直接用停止条件再重新启动一次,这在很多情况下也能跑通,但严格按I2C规范来说应该用重复起始条件。少数器件对这两个操作的容忍度不一样,为了保险,按规范来实现不会错。

第三个问题是上拉电阻。前面说过开漏模式,如果上拉电阻太大,总线的上升沿变慢,在高速模式下就会读到不该读到的电平变化。EEPROM本身速度通常不快,100kHz和400kHz比较多,但总线电容一大,照样会出问题。

如果是Verilog方向想实现I2C控制器,思路其实也很简单,核心是一个状态机。状态机里有起始、地址发送、ACK检测、数据处理、停止这几个状态。难点反而在时钟分频和SDA方向控制上,因为双向口在FPGA里要用inout类型,要控制好什么时候当输入、什么时候当输出。网上很多开源例程的方向控制写得混乱,建议自己动手画个时序图,把方向和SCL的时序对起来,就容易理解了。

I2C上传数据的速率我并不特别推荐所有人先去追求高性能,我见过很多项目跑400kHz跑得很欢,但系统其他中断一多,I2C时序被拉长,偶尔就废了。先稳后快,先把100kHz调通,再去追求高速,这条路最踏实。

3.4 SPI与DMA:解放CPU的正确姿势

热词里“SPI DMA”反复出现,这确实是SPI使用里的一个高级用法。SPI每次发一个字节,如果数据量上千,CPU就一直在循环里转悠,别的活儿全被耽误了。DMA就是把这个搬运的工作接管过去,CPU只需要告诉DMA“从哪搬、搬到哪、搬多少”,然后就去忙别的,搬完DMA会拉一个中断告诉CPU完事了。

我在GD32F303上做过一个SPI读取ADC数据流的系统,8通道同步采样的ADC,数据量很大。直接CPU轮询方式读SPI,中断响应稍微慢一点就会丢数据。后来改成SPI加DMA,读取由DMA自动完成,数据搬到一个环形缓冲区里,主程序只有缓冲区半满时才会被叫去处理数据。同样的CPU频率,处理能力一下子宽裕很多。

SPI DMA的配置有一个特别要注意的点:接收DMA和发送DMA的触发源要匹配好。SPI是全双工的,接收DMA的触发条件是RX缓冲区非空,发送DMA的触发条件是TX缓冲区空。有些MCU里这两个DMA通道是可以独立配置的,你可能会遇到“只收数据不需要发数据”的操作,但为了产生接收时钟,SPI外设还是要被动地发送一些字节去推动时钟,这时候接收DMA单独工作就无法持续触发,必须辅以一个发送DMA或者手动发送逻辑。

打个比方,SPI读数据这件事就像坐手摇船,你想要对岸的货物,可你的船必须有人摇船桨才能前进。接收端收得再快,没有发送端在摇桨,数据也不会自己从对岸飞过来。实际调的时候,可以先测发送侧,用逻辑分析仪确认时钟和数据正常,再开接收DMA,这样问题容易定位。

DMA中断里有不少小坑,最常见的两个:一个是传输完成中断触发条件要比对寄存器版本,另一点是循环模式和正常模式的差异。循环模式下DMA会在传输结束后自动重新装载地址,适合连续采样;正常模式则止步。如果你的代码里用了循环模式,但初始化时DMA中断标志没有及时清理,再次启动时可能触发一次假中断,造成缓冲区错位。这些都是真实踩过的坑,写在这里提醒大家。

3.5 关于TF卡SPI和AD7606、编码器这些实际应用

盘点这些热词,“TF卡SPI电路”也是很多人的痛点。TF卡跑SPI模式是比较经典的玩法。TF卡默认应该是SD模式,切到SPI模式需要在初始化时先给卡发至少74个时钟脉冲,然后发CMD0进入SPI模式,再发CMD8、ACMD41这些初始化指令。硬件电路上最常见的坑是TF卡的DAT1、DAT2、CD引脚在SPI模式下要接上拉,否则卡可能识别不稳定。

“安富莱AD7606”这个热词说明有人在用AD7606做高速同步采样。AD7606支持SPI接口,也支持并行接口,如果用SPI模式,要特别注意它的BUSY引脚和FRSTDATA引脚的时序关系。我调试时最喜欢的做法是用逻辑分析仪同时抓CS、SCLK、BUSY三根线,确认每次转换完成后数据输出的帧格式。AD7606可以配置成过采样,过采样倍数越高,输出数据的有效位数越好,但输出速率会降低,两者要权衡。

“I2C编码器”这个热词也挺有意思。旋转编码器通常是AB相脉冲直接接GPIO中断,但也有带I2C接口的编码器,内部带寄存器,可以读到绝对位置。这种I2C编码器对时序要求不严格,但通信频率一般不高,挂总线上注意地址别冲突就行。

“SPI引擎”这个词如果我没理解错,应该是指SPI接口的加密引擎或者硬件加速模块。这类模块一般在网络安全芯片和存储加密上用得比较多,实现原理大多是内置AES等算法的硬件电路,通过SPI接口与主控交互。这种场景对SPI时序要求非常苛刻,因为加密数据一旦因为时序问题中断,整个会话就废了,所以这类应用我强烈建议用硬件片选和DMA配合。

4. 常见问题与排查技巧实录

4.1 有波形但读不出正确数据,问题出在哪

我在调试SPI和I2C时,最怕的不是完全没波形,而是波形看着正常但数据不对。这种情况最容易把人绕晕,因为你会怀疑是软件逻辑不对,又怀疑是硬件连接不对,两边反复查。

对于SPI,第一件事就是确认时钟极性和相位。拿出逻辑分析仪,抓一次主设备发指令的波形,对照手册的时序图,一格一格看空闲电平、采样沿。数据错误绝大多数都是模式不对,尤其是用别人现成代码的时候,更容易被默认的模式带偏。

确认模式没问题之后,再检查片选时序。CS拉低之后是不是马上就发数据了?中间有没有留一点点时间让从设备准备好?有的从设备对CS建立时间有最低要求,如果主设备在CS拉低的同一瞬间就发第一个时钟,某些慢速设备会响应不过来。

对于I2C,同样用抓波形的办法,重点看起始条件、地址字节和ACK位。我经常遇到一种情况:地址不对,总线上的设备不ACK,波形里SDA在第九个时钟一直是高,代码却还硬撑着继续读。这种情况要回头查设备地址到底是多少。7位地址和8位地址经常有人搞混,很多器件手册写上的是8位含读写位的,代码里写的却是7位的,结果差一位对不上。

4.2 总线挂死和时钟拉伸,I2C专属的麻烦

I2C有一个让很多人头疼的现象:总线挂死,SDA一直为低,时钟还在跳或者干脆也不动了。这通常是某个从设备把SDA钳在低电平,等它释放却一直不释放。最简单的解救办法是把SCL多翻转几次,有些从设备在时钟翻转几次后会重新同步释放总线。如果这招不行,就只能把总线的电源断一下,或者把主设备复位一下。

另外还有一个概念叫时钟拉伸,就是某些从设备处理不过来的时候,会把SCL拉低,让主设备等一等。这在很多传感器上很常见。如果你的控制器不支持时钟拉伸,可能会因为等不到回应而超时报错。我建议在选用I2C器件时,留意一下它是否有时钟拉伸需求,特别是做高速传输的时候。

4.3 上拉电阻反复调就是不行,换思路

上拉电阻的问题,前面已经说了不少,但实际调试中还有一种情况:阻值怎么调都调不好。这时要冷静怀疑是不是I2C总线上挂的某个设备本身有问题,比如某个引脚的漏电流特别大,把总线电平拖垮了。我就遇到过一颗器件,本身有内部上拉,和外部上拉并联之后总阻值太小,低电平都拉不下去,导致整个总线工作异常。排查这种问题只能一个一个挂载设备试,把可疑的器件先摘下来,看总线状态有没有恢复。

另外,多从设备的总线,每个从设备的输入引脚都带有电容,数量一多总线等效电容就变大,即便你换更小的上拉电阻,上升沿还是不够陡峭。这时候要考虑降速,或者减少总线上挂的设备数。I2C总线扩展器就是干这个的,热词里也有“I2C扩展”,本质是把一条总线扩成多条,或者把I2C信号转成别的接口再接多个设备。扩展之后要注意延时会变大,速率不宜调高。

4.4 常见问题速查表

下面这张表是这几类问题的高频排查要点,我在项目里基本每次都参照这个顺序来排除,分享出来给大家当个参考。

现象可能原因排查顺序
读到0xFF或全0SPI模式不对、CS未选通、从设备未上电1. 查模式 2. 查CS电平 3. 查供电
数据随机跳变时钟过快、线路过长、干扰1. 降速 2. 查波形 3. 缩短走线
I2C无ACK地址错误、总线电平不对、上拉失效1. 查地址 2. 量SDA电平 3. 查上拉
I2C总线挂死从设备锁死、主从不同步1. 翻转SCL 2. 断电复位 3. 查时钟拉伸
偶尔通信失败片选时序不达标、中断打断1. 加延时 2. 关中断 3. 加DMA
DMA数据错位标志位未清、循环模式配置错误1. 查中断初始化 2. 查缓冲区指针 3. 单步调试

看着这张表,再回头看问题,其实很多难调的bug都不是玄学,而是时序关系没有完全对上。理解每一种现象背后的协议细节,再配合示波器或逻辑分析仪去验证,问题往往很快就能锁定。

5. 实践中的选型心得与学习路径建议

5.1 我的选型决策习惯

做了这么多项目,我慢慢养成了一个选型决策习惯。第一步永远是列接口需求,把要接的器件数量、数据量、速率要求、引脚余量全部列出来。第二步才是看速度参数,但不会只盯着上限,而是看典型值和工作电压下的可靠值。第三步是画原理图时就把上拉、下拉和去耦电容的位置预留好,就算最后没用上,也比板子回来之后再飞线强。

之前有个模拟项目我印象很深:要接三颗I2C传感器加一颗I2C EEPROM,MCU引脚刚好够用,但I2C总线上四颗器件,如果全部直接挂上去,总线电容变大,可能不稳。我就干脆加了一个I2C总线扩展器,把三颗传感器分到另一条总线上,EEPROM和主控留在原始总线。这样每条总线都是轻载,运行非常稳,而且扩展器本身也增加了系统的灵活性。

另一个项目中需要接一颗SPI接口的SD卡,数据量比较大。我选了硬件SPI加DMA方案,并且用硬件片选。为什么这时候用硬件片选?因为我在那款MCU上验证过硬件NSS在大批量DMA传输时行为正常,不会中途跳变,同时软件片选在DMA连续传输中可能需要专门配合,开发量反而大。选型没有绝对的对错,只有对自己硬件外设特性的把握程度。

5.2 软件模拟和硬件外设的配合策略

很多项目初期调试阶段,硬件外设的引脚可能被调试器占用或者芯片封装引脚有限。这种情况下我的策略是先用软件模拟验证逻辑,确认器件能正常通信,然后再切到硬件外设,把速率拉起来。这个策略有一个明显好处:软件的时序完全可控,出问题容易定位;硬件外设一旦配好,性能又远超软件模拟。

切换到硬件外设时,记得把软件模拟的延时逻辑全部去掉,因为硬件外设自动管理时钟和数据,不需要你在代码里手动翻转。如果切过去之后数据不对,不要怀疑硬件外设坏了,先检查外设的配置参数,尤其是模式、位数、分频、片选极性,大概率是配置里某一位和软件模拟时有差异。

软件模拟SPI和I2C还有一层价值:当你需要把一个老设备接到新MCU上,而新MCU的外设寄存器细节你还不太熟时,可以先拿软件模拟把器件驱动起来,等验证完器件没问题,再去啃外设驱动,压力会小很多。

5.3 面向新手的学习路径建议

新手学SPI和I2C,我不建议一上来就啃协议手册或者看一堆例程。我建议按这个顺序来。

第一步,用逻辑分析仪抓现成设备通信的波形。随便找一个开发板上的加速度计或EEPROM例程,抓SCL、SDA或者SCLK、MOSI、MISO的波形,对照时序图看明白每个字节是怎么被发出去的。

第二步,动手用GPIO模拟一遍最简单的协议。不需要接真实设备,先把起始条件、停止条件、字节发送的波形模拟出来,可以用逻辑分析仪验证,确认自己完全理解协议时序。这一步做完,你会对协议有真正的“手感”,而不是只会抄代码。

第三步,再切换到硬件外设。用同一颗器件,配置好硬件外设,读数据,对比软件模拟的结果,确认两边行为一致。此时你已经理解时序本质了,硬件外设的寄存器配置对你来说只是换了一种实现方式。

第四步,进阶到DMA和中断的结合,把CPU从通信里解放出来,这时候你才算真正用一个产品的思路在做这件事。

5.4 几个容易忽略的细节彩蛋

最后讲几个容易被忽略的细节,都是我实际用过的经验。

第一个是SPI在高速模式下,MOSI和MISO之间的串扰问题。如果板子上这两根线走在一起而且靠得很近,高速翻转的时候可能会串扰,轻则丢位,重则整个数据帧乱套。解决方法是拉开间距,或者在关键信号之间加地线隔离。我做过一次板子,SPI Clock 20MHz,MISO数据偶发错误,查了半天的软件,最后发现是布线平行的锅。

第二个细节是I2C的地址冲突。有些器件支持多个地址引脚,比如A0、A1、A2,设计板子的时候要预留跳线或电阻配置,避免多颗同型号器件地址一样导致冲突。我见过有人偷懒把三颗一样的传感器A0、A1、A2全部接地,结果三颗地址完全一样,总线上永远只能识别到一颗,剩下两颗直接失联。

第三个细节是中断优先级。SPI和I2C通信过程被高优先级中断打断,可能导致时序被拉伸,尤其在严格的主从同步场景下会出问题。如果系统里有硬实时中断,比如电机控制、PWM斩波,要给通信中断设置合理的优先级,或者干脆用DMA把这些通信从CPU中断里摘出去。这点在之前那个AD7606采样系统里体现得最明显,DMA介入之后,无论中断多频繁,数据流都稳稳的。

第四个细节是关于热词里的“不拆SPI Flash芯片可以刷BIOS吗”。这其实是个很有意思的工具场景,很多主板上的SPI Flash可以通过专用的编程器夹具,用SPI协议直接在线刷写,不需要拆芯片。原理就是编程器通过夹具夹住Flash芯片的引脚,用SPI时序去读写内部数据。对于不想拆焊的人来说,这个场景本身就是SPI协议价值的又一个体现。不过操作的时候务必断开Flash芯片和主板其他部分的电气连接,至少要把主板的电源断开,不然编程器和主板上的电路同时驱动同一根信号线,轻则烧引脚,重则损坏器件。

这些零零散散的细节,单独拿出来看好像都很小,但每一个都有可能在项目关键时刻把人卡住几个小时甚至几天。通信总线的调试就是这样,基础原理全懂,细节才见真功夫。

做嵌入式的路上,大家都是从每一次波形乱掉、每一次ACK丢失、每一次SPI数据错位里爬出来的。SPI和I2C只是这个大千世界里最常见的两个接口,但把这两个接口吃透了,其他总线的学习路径也会清晰很多。希望这篇经验分享能帮你少走几步弯路,把片内到片间的这趟旅程走得顺畅一些。

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

HiL测试工程师实战:从CAN报文到故障注入,守护新能源汽车安全

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

作者头像 李华
网站建设 2026/10/25 1:11:54

数字地模拟地怎么接?PCB接地设计与EMC回流路径详解

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

作者头像 李华
网站建设 2026/10/25 1:10:40

电机驱动器频繁报堵转?从机械到电气的完整排查指南

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

作者头像 李华
网站建设 2026/10/25 1:10:38

STM32寄存器白话手册:从地址映射到时序控制的工程实践指南

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

作者头像 李华
网站建设 2026/10/25 1:09:50

ESP32隐藏IQ通路:低成本实现2.4GHz射频物理层采样

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

作者头像 李华
网站建设 2026/10/25 1:08:47

嵌入式开发:从玩具级Demo到工程级交付的闭环思维

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

作者头像 李华