
嵌入式面试里有一个高频问题I2C、I2S、SPI、UART有什么区别我面试过不少候选人能背出“SPI全双工、I2C半双工、UART异步”的很多但追问到“板子上为什么选2.2k上拉而不是10k”“SPI的Mode 0和Mode 3到底差在哪”就露馅了。这四个词几乎每个嵌入式工程师都见过可真正在项目里同时用过四样、把各自脾气摸清楚的人并不多。这篇文章我想换一个角度来讲。不是把四份协议手册抄一遍而是站在“实际项目选型和调试”的视角把这四种协议从原理、对比到排障完整捋一遍。适合正在学嵌入式的学生、要画原理图的硬件工程师以及写驱动时被外设手册绕晕的软件工程师。看完你至少能回答三件事这个设备该挂到哪条总线上挂上去之后波形大概长什么样出了问题第一步该查什么。先说一个我认为最重要的结论这四个协议不是同一赛道的四个选手而是四条平行的“电路语言”。很多时候我们不是在“四选一”而是在决定“哪个设备接到哪条总线上”。1. 为什么这四个词总被绑在一起聊1.1 都是串行协议但出身和设计目标完全不同I2C由飞利浦在1982年提出目标很明确用尽量少的引脚在板级连接一堆低速外设比如EEPROM、温度传感器、触摸屏控制器。SPI出自摩托罗拉思路更直接给MCU和外设建立一条高速全双工通道不搞花哨的寻址和应答谁选谁就通信。UART的历史比前两者更早是从电脑串口、调制解调器时代一路走下来的本质就是两个设备之间简单的异步收发。I2S同样是飞利浦在同期推动的但它不和上面三位竞争它只做一件事传输数字音频流。它们都有一个共同点数据按位逐个发送线少引脚省。但往下拆差异就出来了——是同步还是异步是单主多从还是多主多从有没有设备地址能不能同时收发要不要应答机制。这些维度才决定了你在实际项目中怎么选。1.2 “对比”最常见的误区拿着同一个标尺量四个不同工具很多人习惯列一张表问“哪个快、哪个慢、哪个好”然后试图选出一个“最优协议”。这是最大的误区。UART是两个设备之间的点对点电话线不关心线上还有谁。I2C是一条小区总线所有设备挂在同两根线上靠门牌号找人。SPI更像项目经理临时点名拉低某根片选线告诉某个外设“现在只和你说话”说完就松手。I2S则是一条音频流水线数据不分包、不寻址、不停顿由时钟推着连续往前流。弄清楚了这一点后面所有对比才有意义。面试里那些“I2C和SPI区别”的问题本质上都是在考你能不能从“设计目标”推演出“技术特性”。理解了I2C为了省引脚牺牲了速率、SPI为了追求速度放弃了应答很多细节就自然记住了不用死背。2. 逐个拆解四种协议各自的工作原理与脾气2.1 I2C两根线挂一堆设备靠地址和应答说话I2C硬件上只有两根线SDA数据和SCL时钟。两根线都是开漏输出外面接上拉电阻到电源空闲时两根线都是高电平。主机通过拉低SCL产生时钟通信双方在SDA上传数据半双工同一时刻只能一个方向。帧结构也不复杂主机拉低SDA表示起始条件然后发出7位设备地址加1位读写标志第9个时钟是ACK应答位被寻址到的从机必须在这个位把SDA拉低相当于说“我在”。之后就是数据字节和对应的ACK直到主机发出停止条件。很多新手看到8位地址0xA0和7位地址0x50会懵其实就是0x50左移一位最低位补了读写标志。I2C标准速率常见100k、400k快速模式到1MHz高速模式能到3.4MHz但真正跑2MHz以上的场景很少。I2C一个容易被忽略的特点是开漏结构。正因为线是“只能拉低、不能主动拉高”的上拉电阻的大小直接决定上升沿快慢。3.3V系统、挂几个设备我一般选2.2k到4.7k设备多了或者线长了就要往小选。标准模式100k下上升时间上限约1微秒快速模式变成300纳秒线缆电容一大就超标波形变成圆角梯形通信就出错。开漏带来的另一个好处是支持多主机仲裁。两个主机同时抢总线时谁先拉低SDA谁就赢了因为在“线与”逻辑下低电平覆盖高电平。仲裁失败的一方自动退出。这在别的协议里很难做到。还有一个常见认知误区I2C从机只能被动响应不能主动发起数据。有些传感器想“主动上报”本质是拉一个中断引脚通知主机快来读协议层还是主机在发起。有人搜“I2C从机主动更新主机寄存器”其实就是这个中断加轮询的思路。2.2 SPI全双工、快、没有应答片选决定一切SPI通常四根线SCLK时钟、MOSI主机输出从机输入、MISO主机输入从机输出以及CS/SS片选。主机通过拉低某个从机的片选来指定通信对象所以理论上挂多少个设备就需要多少根片选线。和I2C不同SPI是推挽输出高低电平都是主动驱动的上升沿做得很快速率很容易跑几MHz甚至几十MHz。SPI本质上是两个移位寄存器互相交换数据。主机在SCLK的某个边沿把数据移出去同时把从机发来的数据移进来每一个时钟周期完成一位双向交换所以天然全双工。也正因为就是这样简单的移位逻辑SPI速度可以极高代价是没有应答机制。主机发完一帧并不知道从机到底收到没有只能靠从机返回的数据做判断或者干脆信不过。SPI最大的坑是模式。CPOL决定空闲时时钟是高还是低CPHA决定数据在哪个边沿采样组合出Mode 0到Mode 3四种模式。双方必须完全一致否则读回来的数据要么错位要么全错。这个后面我会专门讲。片选也有讲究。硬件片选由外设控制器自动拉低拉高节省CPU但容易出现帧与帧之间CS闪断的情况。比如连续写多个字节时硬件NSS在每字节之间跳了一下从机就认为一次传输结束了。我做过SPI接口的ADC采集遇到过Data Ready引脚上时序对不上最后发现是主控的硬件NSS在每次16位传输之间自动释放了片选改成软件片选用GPIO手动拉低问题就没了。软件片选的好处是灵活但必须留够从机手册上要求的建立时间和保持时间。像MT6701这类磁编码器读角度CS拉低之后不要立刻打时钟先等一小段时间再开始读出来的数据更稳。CS从拉低到第一个时钟的最小时间以及最后一个时钟到CS释放的最小时间各厂家手册里都有标几百纳秒到几微秒不等。2.3 UART真正“异步”的串口波特率误差是头号杀手UART只有TX和RX两根线没有时钟线。两个设备各自用自己的时钟来采样通信前约定波特率。每一帧数据是起始位低电平、5到8位数据、可选的奇偶校验位、停止位高电平。最常用的配置是8N18位数据、无校验、1个停止位。每传一个字节实际线上要跑10位左右所以115200波特率换算成字节速率大约是11.5KB/s。UART是全双工点对点没有“总线”概念。它不像I2C有地址可以挂多个从机除非应用层自己在协议里加地址字段或者物理层换成RS-485。UART在真正干活时最大的隐患是波特率偏差。两边各自用自己的晶振或内部RC振荡器产生波特率如果偏差超过2%到3%高速率下就会偶发乱码或丢字节。用MCU内部RC跑115200经常出问题换外部晶振就好很多。另外接收端需要用16倍或64倍波特率采样晶振频率算出来不能整除波特率误差就会累积。老工程师的习惯是配置完成后先自发自收测一下再对接外部设备。还有一个经常被问的“阻塞和非阻塞”问题。串口处理不定长数据更好的做法是DMA加空闲中断完整收到一段数据后告诉主程序来处理而不是在中断里一个字节一个字节往外搬。早期标准库时代有人图省事在主循环里阻塞读串口一帧超过几个字节就卡死。这个问题不是协议本身的问题是软件架构的取舍但很多人在这里栽过跟头。2.4 I2S音频专用名字像I2C但不是I2CI2S和I2C只差一个字母很容易被当成“I2C的音频版本”。实际上两者完全不是一回事。I2S是飞利浦为数字音频设备之间传输PCM音频流设计的同步串行接口典型有三根线BCLK位时钟、WS左右声道选择、DATA数据。有些系统还会加MCLK主时钟给音频Codec做内部时钟参考。I2S没有设备地址也没有应答机制。它做的事情很简单持续不断地把PCM采样值送进DAC或者从ADC送出来。WS低电平通常表示左声道高电平表示右声道数据按二进制补码传输最高位MSB优先。经典Philips I2S格式里数据比WS变化晚一个BCLK周期左对齐格式是数据和WS同时右对齐则方向相反。格式配置错了播放出来就是噪声或者左右声道错位。一个容易混淆的点是很多板子上MCU先用I2C配置音频Codec的寄存器再用I2S传音频数据。I2C是控制通道I2S是数据通道两者配合工作但协议本身没有任何关联。主从关系上通常是主控做主机输出BCLK和WS但也有的系统把Codec做主机数据从外部输入。还有人会拿I2S和SPI比较说SPI也能传音频数据确实能但SPI的突发式传输不适合持续的实时音频流I2S的帧同步和声道设计才是为音频专门优化的。3. 一张对比表与四个选型问题3.1 核心对比表维度I2CSPIUARTI2S线数2根SDA、SCL4根SCLK、MOSI、MISO、CS可多CS2根TX、RX3根BCLK、WS、DATA可加MCLK同步方式同步主机提供SCL同步主机提供SCLK异步双方各自时钟同步主机提供BCLK和WS传输模式半双工全双工全双工同步全双工连续流设备寻址7位/10位硬件地址总线式片选线CS选从机无地址点对点无地址专门传音频流应答机制有ACK/NACK无无无多主机能力支持有仲裁一般单主机不支持一般单主机典型速率100k/400k/1M/3.4M常见1M~几十M常见9600~921600BCLK随采样率几百k到十几M典型应用EEPROM、触摸屏、传感器、电源管理Flash、SD卡、ADC、显示、FPGA调试日志、GPS、蓝牙/WiFi模块DAC/ADC、音频Codec、语音处理调试难度需要看ACK和上拉需要确认Mode和CS时序需要关注波特率误差需要确认格式和左右声道对齐3.2 有多少设备、引脚紧不紧张如果你要把很多低速外设接到一个MCU上I2C是最划算的。一条总线上挂8个设备只需要两根线加地址区分而SPI挂8个设备至少需要一根时钟、一根数据输出、一根数据输入外加8根片选线引脚成本非常高。很多传感器、EEPROM、RTC默认都支持I2C地址引脚可以靠硬件跳线错开。但I2C也有上限总线上拉电阻和总线电容决定最大挂载数量。线缆太长、设备太多导致电容超过400pF上升沿会变得很钝。设备一多单点故障影响整条总线一个从机把SDA钳位在低电平整条总线就废了。如果遇到地址冲突可以考虑用I2C多路复用器把总线切成多段比如PCA9548A这种代价是驱动复杂度变高。3.3 数据量多大、要连续还是要突发数据量是选型最核心的标尺。读一个温度传感器每秒一次16位数据I2C绰绰有余。给屏幕刷一张图或者往SD卡写日志I2C就扛不住了SPI更合适。SPI的突发传输效率高一个块几十KB连续写完时钟一直打中间不用释放总线。音频是特殊场景。举个例子44.1kHz采样率、16位、双声道的立体声数据率是44.1k乘16乘2约1.4Mbps。如果采样率升到192kHz、位深32位数据率就超过12Mbps。I2S的BCLK就是跟着这个数据率走的。用SPI也能把PCM数据搬过去但音频设备要求字时钟左右声道切换和位时钟严格同步、且不能有长时间停顿I2S的WS和BCLK天然满足这个需求。所以判断标准不是“谁快”而是“你的数据是不是音频流”。3.4 是否要求“有反馈”和“能仲裁”I2C有ACK机制主机每发一个字节都能确认从机是否收到对“寄存器写了没有”这种问题有明确答案。SPI完全没有读一个寄存器时从机可能在用旧数据回你必须靠波形和数据内容自己判断。UART一样没有应答应用层如果要做可靠通信必须自己封装帧格式、校验、重传。多主机场景也是个重要的区分点。I2C靠开漏仲裁支持多主机几个MCU可以共享一条总线虽然实际项目里多主机不多见但确实能做。SPI和I2S基本默认单主机UART则完全不存在这个概念。很多做工业现场的人最后选了CAN或RS-485就是看中了多节点和冲突处理能力这一点I2C在短距离板内能用长距离就不行了。4. 工程中容易被忽略的四个细节4.1 电平转换3.3V、5V、1.8V混接不能想当然很多人画原理图时默认大家都用3.3V忘了某些老外设是5V某些传感器核心是1.8V。I2C因为开漏加上拉天然适合做电平转换把上拉电阻接到目标电压域即可如果用MOSFET双向电平转换电路可以做到两个方向都安全。只要确保两侧都能拉低到各自的逻辑低阈值就行。SPI和UART是推挽输出电平转换就没有I2C那么优雅。电阻分压加钳位在高频下会牺牲边沿质量正规做法是上电平转换芯片比如TXS0108这类自动双向电平转换器或者用带方向控制脚的转换器。我见过有人用1.8V的传感器板子直接被3.3V的SPI信号打坏就是漏了这一步。如果主控引脚的IO bank支持软件配置开漏也能用和I2C类似的方式上拉到目标电平但会影响速度不建议在高速SPI上这么干。4.2 SPI的Mode不是猜出来的CPOL和CPHA组成了四种SPI模式。CPOL决定空闲时SCLK是高还是低CPHA决定从机在第一个边沿还是第二个边沿采样数据。模式CPOLCPHA特点Mode 000时钟空闲低上升沿采样最常用Mode 101时钟空闲低下降沿采样Mode 210时钟空闲高下降沿采样Mode 311时钟空闲高上升沿采样也较常见排查SPI读回全0xFF或全0x00的时候第一个怀疑对象就是Mode不匹配。不要猜去看从机datasheet里的时序图数清楚它是在哪个沿采数据再调主控寄存器。如果手册画得不清楚就用逻辑分析仪抓一次波形对比采样沿和数据的稳定期至少能定位一半问题。SPI和I2C还有一个本质区别SPI没有像I2C规范那样的国际标准文档协议定义分散在各大芯片厂商的datasheet里。“Mode 0到3”只是Motorola约定俗成的叫法不同厂商可能还叫“SPI Mode 0/1/2/3”对应不同含义一定要以实际手册为准。4.3 UART波特率误差与I2S时钟源UART的问题是异步采样。如果主控用8MHz时钟无法精确分频出115200实际波特率和标称值之间就有误差。两个设备的误差方向一致还好一个偏正一个偏负累计超过容限就丢字节。对外部无源晶振的依赖非常强内部RC校准不好跑115200以上就要小心。I2S的问题相反它是同步传输但音频时钟要求高精度。MCLK和BCLK必须严格按采样率成比例分配否则播放速度会整体偏快或偏慢。很多Codec板子默认用24.576MHz或者22.5792MHz晶振就是为了同时兼顾48kHz和44.1kHz的整数分频。做音频项目时别随便用一个12MHz晶振去顶分频出来可能全是小数主控输出的BCLK频率漂移Codec就会出杂音或者失锁。这时候用逻辑分析仪看BCLK频率和WS频率一算就知道是不是时钟源的问题。4.4 外设资源复用与启动引导场景选型不只是选协议还要考虑主控里有没有足够的可用外设。以STM32为例很多型号的SPI和I2S在硬件上是同一个模块引脚和时钟树高度重叠你用了SPI3往往就不能同时用I2S3。RK3588这类SoC的SPI控制器则要和DMA配合否则大批量读Flash时CPU占用会很难看。之前有人做混合存储方案用SPI NOR保存引导镜像用PCIe NVMe SSD放系统结果卡在引导阶段——SoC的BootROM读SPI NOR时对片选、时钟频率和镜像偏移都有要求SPI速率设太高NOR Flash在BootROM阶段根本没初始化好。这种问题既不是协议错也不是器件坏而是“启动场景下的时序余量”和“正常驱动的时序余量”不一样。还有一个实用提醒市面上很多USB转SPI适配器比如FT232H、CH347用Python脚本可以很方便地把寄存器数据读出来看这对验证外设非常有帮助。但这些工具几乎都只支持主机模式。想调试SPI从机还是要靠MCU、FPGA或者专业的总线分析仪。有人急着找“Python调用USB模拟SPI”来当主机抓从机数据方案本身没错但期望它做从机协议分析就是方向错了。5. 真实调试中的波形判断与故障定位顺序5.1 用逻辑分析仪看波形抓哪里、怎么抓低速协议调试逻辑分析仪是最趁手的工具比示波器直观比打印日志完整。抓I2C就夹SDA和SCL加GND抓UART就夹TX、RX加GND抓SPI至少夹CLK、MOSI、MISO和CS抓I2S夹BCLK、WS、DATA和GND。采样率设置为信号速率的5到10倍以上。I2C的400k用5M采样绰绰有余UART的115200很轻松但I2S的BCLK如果到了12MHz左右普通24MHz逻辑分析仪就有点捉襟见肘了有些波形会变形最好用50MHz以上采样率的。我自己的习惯是原厂驱动调不通第一件事不是改代码而是把逻辑分析仪夹上去抓波形。看波形不会说谎比翻手册猜参数快得多。抓回来之后用协议解码器自动解出帧内容一般每个分析仪软件都支持I2C/SPI/UART/I2S解码。发现波形完全没动静先查供电和复位有波形但数据不对再查配置数据对但功能不对最后才查软件逻辑。这个顺序能省下一大半的排查时间。5.2 I2C排查没有ACK先怀疑什么I2C出问题时最典型的现象是主机发完地址后收不到ACK。排查顺序我一般这样来先看SCL有没有正常的时钟脉冲没有就查主机配置和引脚有的话看SDA上地址字节是否正确地址可能因为7位/8位换算搞错地址对但没ACK怀疑从机没上电或者复位没释放如果之前都好突然某次操作后总线卡死SDA一直为低大概率是有从机把总线钳住了。遇到SDA被钳死的情况有一个通用的恢复手段主机连续发9个SCL脉冲大多数从机会在此期间释放SDA这就是I2C规范里说的总线清理机制。如果还不行就只能逐个断开设备找元凶。这在实际项目中很常见我调试GT911触摸屏的时候就被SDA拉死折腾了一晚上最后发现是触摸的复位时序没做好芯片半初始化状态卡住了总线。还有一个高频问题I2C总线上地址冲突。两个设备默认地址一样其中一个会一直不响应。解决办法是看硬件地址引脚能不能改不能改就上I2C多路复用器把一个物理总线切分成两路。5.3 SPI排查满屏0xFF先看CS和ModeSPI回读数据全是0xFF先别怀疑芯片坏了。第一看CS有没有真的拉低很多主控的NSS配置在软件模式下时CS信号根本没输出第二看Mode对不对CPOL和CPHA错了数据全乱第三看时钟有没有送到从机特别是从机要求时钟空闲高你配成空闲低数据就完全对不上第四看MISO引脚是不是配成了复用功能或者外部上拉把空闲状态拉高了导致没数据时一直读回1。还有一个隐蔽问题CS释放得太快。有些从机要求CS在最后一个时钟后保持低电平一段固定时间再释放如果主机把帧和帧之间处理得太紧从机就会漏掉最后一拍数据。做SPI读写Flash测试时读出来的最后一个字节总是错的十有八九就是CS释放时序不够。加一个几百纳秒的延时通常能解决。5.4 UART与I2S的常见坑UART乱码优先级最高的是查波特率偏差。先把两边的配置统一成8N1再把波特率降到9600试试如果降速后不乱码基本可以确定是波特率误差。如果是USB转串口工具注意芯片驱动是否正常FT232R这类芯片一定要装对官方驱动老克隆芯片经常出现装不上驱动或者识别不到的问题。收发反了也是常见的低级错误两个设备接串口线时TX接RXRX接TX接成直连就连指示灯的闪烁情况都对不上。I2S调试时没声音先查WS极性立体声左右声道数据完全一致但听起来是单声道查是不是把左右声道数据错发到了同一个槽位有声音但噪声大查格式是标准I2S还是左对齐右对齐以及主从时钟是否匹配。音频的问题用耳朵判断很可靠但要让耳朵有参考标准先把1kHz正弦波灌进去输出波形应该非常干净任何杂音都说明链路里有配置错误。5.5 一个真实案例GT911触摸屏的I2C地址之谜我用GT911时遇到过典型的“I2C波形正常但没有ACK”的谜团。SCL有脉冲SDA上地址字节也有但从机就是不回应。当时怀疑芯片坏了换了一片还是不行。后来看手册仔细才发现GT911的I2C地址不固定由INT引脚的电平状态决定复位时INT是高还是低对应的地址就会在两组地址之间切换。我把INT脚配置成了上拉输入芯片上电后进入了另一套地址而驱动里写死的是默认地址当然一直NACK。这种问题在传感器和触摸控制器里非常典型。排查顺序是先确认上电时序——复位脚、中断脚、I2C地址脚谁先谁后再确认引脚电平配置——是输入还是输出默认高还是低最后才去查I2C通信本身。很多“I2C通信失败”最终都不是I2C协议的问题而是外围引脚状态不对。调试协议类问题我最深的体会是别靠猜抓波形。逻辑分析仪加上协议解码能把绝大多数通信问题压缩到十五分钟以内。这四种协议没有谁绝对好谁绝对差I2C适合低速多设备总控SPI适合高速数据块搬运UART简单通用距离灵活I2S则是音频场景的专用道。选型时回到物理层、回到数据形态、回到系统约束答案自然就出来了。