1. 四种总线到底该怎么选先搞清楚它们各自在解决什么问题嵌入式开发干久了你会发现一个很有意思的现象新手最纠结的往往不是算法而是“这个传感器到底该接哪条总线”。i2c、i2s、spi、uart这四个名字天天在数据手册里晃但真到画板子、写驱动的时候选错了轻则通信不稳重则整个方案推倒重来。我自己就经历过因为把一颗音频codec挂到i2c上做数据流传输结果带宽完全不够最后不得不飞线改spi的尴尬事。先把这四个东西的本质说清楚。它们都是串行通信接口区别在于“为谁服务、跑多快、占几根线、能不能一对多”。你可以把它们想象成四种不同性格的快递员uart是那种慢悠悠但特别靠谱的老派快递一对一发件双方约好时间就行i2c是公交车一根线上挂一堆站点谁上车谁下车靠地址区分spi是专车加双向车道速度快但每多一个乘客就得多开一条专属通道i2s则是专门给音频数据开的VIP通道只管把声音样本准时送到别的不管。这四个协议覆盖了嵌入式系统里90%以上的板级通信需求。传感器、EEPROM、屏幕触摸芯片大多走i2cFlash、ADC、高速传感器走spi音频codec、数字麦克风走i2s调试口、模组通信、GPS、蓝牙模块走uart。理解它们的差异本质上是在理解带宽、引脚成本、拓扑复杂度、实时性这四个维度的权衡。这篇文章适合谁看如果你正在做原理图选型、写裸机驱动、调Linux设备树或者单纯被数据手册里的时序图搞得头大那接下来的内容应该能帮你少走不少弯路。我会从协议原理讲到实际踩坑尽量把每个“为什么这么设计”都说明白而不是只丢一堆参数让你背。2. 四兄弟的核心参数横向拆解2.1 一张表看懂线数、速率与拓扑在深入细节之前先把最关键的对比维度摆出来。这张表是我自己整理选型时最常用的参考参数基于常见MCU和SoC的典型实现具体数值会因芯片和布线而异。特性UARTI2CSPII2S信号线数2TX/RX2SCL/SDA4SCLK/MOSI/MISO/CS3BCLK/LRCLK/SDATA拓扑结构点对点多主多从总线一主多从片选点对点主从典型速率9.6k~3Mbps100k~3.4Mbps1M~100Mbps取决于BCLK常见1.5~12MHz时钟方式异步同步同步同步寻址方式无约定双方7位/10位地址片选线无固定主从双工能力全双工半双工全双工单向为主引脚开销极低低高每从机一根CS中典型应用调试、模组传感器、EEPROMFlash、ADC、屏幕音频codec、麦克风这张表里最容易被低估的是引脚开销这一行。i2c挂10个传感器还是2根线spi挂10个从机就是41014根线PCB走线和引脚资源立刻吃紧。所以低速多设备场景i2c几乎是默认答案而高速场景才值得为spi付出引脚代价。2.2 为什么速率差异这么大时钟来源决定一切uart是异步的没有时钟线收发双方靠事先约定的波特率各自计时。这就像两个人约好“每隔一秒说一个字”但两个人的表如果差了一点说到后面就全乱了。所以uart对晶振精度有要求通常误差要控制在2%以内波特率越高对时钟越敏感。这也是为什么很多低成本方案用内部RC振荡器跑uart时高波特率下误码率会飙升。i2c、spi、i2s都是同步的有一根明确的时钟线由主机驱动从机跟着时钟采样。同步的好处是时钟和数据一起传接收方不需要自己计时天然抗时钟漂移。代价是多一根线但换来的是速率可以拉得很高。spi能轻松跑到几十MHzi2s的BCLK也能到十几MHz就是这个道理。这里有个常见误区很多人以为i2c速率低是因为协议本身慢。其实i2c标准模式100kHz、快速模式400kHz、高速模式3.4MHz慢的主要原因是开漏输出加外部上拉。开漏结构导致上升沿靠上拉电阻给寄生电容充电RC时间常数限制了边沿速度。你把上拉电阻从10k换成2.2k波形立刻陡峭很多但功耗和总线电容的约束又上来了。这是i2c设计里最经典的权衡。2.3 拓扑与寻址公交车、专车和专线的区别i2c是真正的总线结构。所有设备并联在SCL和SDA上每个从机有唯一地址。主机发起传输时先发地址被点名的从机应答其他从机装聋作哑。这种设计的优雅之处在于扩展性极好但隐患也明显地址冲突、总线电容累积、某个从机拉死总线导致全挂。spi没有地址概念靠片选线CS来选设备。主机拉低哪个CS哪个从机才理睬时钟。这就像老师点名每个学生一根专属线点谁谁答。好处是简单直接、速率高、全双工坏处是每加一个从机就多一根线而且CS的时序管理在软件上要格外小心。i2s更特殊它压根不是通用总线而是音频数据专用通道。它只关心一件事把PCM采样数据按时钟节奏送出去或收进来。所以它有帧时钟LRCLK区分左右声道有BCLK做位时钟数据线SDATA单向传输。你没法用i2s去读传感器就像不能用货运火车去送快递。uart最简单点对点没有地址没有片选双方约定好波特率、数据位、停止位、校验位就能通。它的“拓扑”就是两根线连两个设备想连三个就得加switch或者用多个uart。3. 逐个拆解每个协议的时序细节与实操要点3.1 UART最古老也最容易被小看的协议uart的帧结构是起始位数据位校验位停止位。空闲时线是高电平起始位是一个低电平脉冲接收方检测到这个下降沿就开始按波特率采样。数据位通常8位LSB先发。校验位可选奇偶或无。停止位1或2位高电平。这里有个实操细节很多人忽略采样点位置。接收方通常在起始位下降沿后延迟1.5个位时间开始采第一位数据之后每隔1个位时间采一次。这样采样点落在每位中间容错最好。如果你用逻辑分析仪抓uart波形会看到数据位中间对齐得很整齐就是这个原因。波特率计算是另一个坑。假设MCU主频72MHz想要115200波特率分频系数72M/(16*115200)39.0625。很多MCU的uart是16倍过采样分频寄存器只能取整39和40之间选哪个选39误差0.16%选40误差2.4%。所以标准库和HAL库都会帮你算好但如果你自己写寄存器这个取整误差必须算清楚否则高波特率下累积误差会导致帧错误。实操心得调试uart不通时第一步永远是用示波器或逻辑分析仪量TX线有没有波形第二步量波特率对不对第三步看电平标准TTL还是RS232。我遇到过太多次“代码没问题但线接反了”或者“TX接TX”的低级错误。关于DMA收发STM32上用DMA空闲中断接收不定长数据是经典方案。配置DMA循环模式接收开启UART空闲中断空闲中断里计算已接收长度并处理。这样CPU几乎不参与效率极高。但要注意DMA缓冲区的对齐和长度以及空闲中断的清除时机否则会丢数据。3.2 I2C开漏、上拉与时钟拉伸的博弈i2c的电气特性是它一切行为的根源。SCL和SDA都是开漏输出意味着设备只能把线拉低不能主动拉高。高电平靠外部上拉电阻提供。这就解释了为什么i2c总线上的设备不能同时一个拉高一个拉低——拉低永远赢这就是线与逻辑也是仲裁机制的基础。上拉电阻的选择是个经典计算题。总线电容Cb包括PCB走线、引脚、器件电容典型值100~400pF。上升时间tr≈0.847×R×Cb从0.3VDD到0.7VDD。标准模式要求tr≤1000ns快速模式≤300ns。假设Cb200pF快速模式要tr≤300ns则R≤300ns/(0.847×200pF)≈1.77kΩ。所以快速模式常用2.2k~4.7k标准模式可以用10k。电阻越小边沿越陡但功耗越大这是硬约束。时钟拉伸是i2c独有的机制。从机如果处理不过来可以把SCL拉低主机发现SCL没按预期变高就知道从机在忙于是等待。这个机制很优雅但也是很多通信失败的元凶。有些主机的i2c控制器不支持时钟拉伸遇到会拉伸的从机就死锁。GT911触摸芯片通信失败很多时候就是时钟拉伸或上电时序问题。i2c的数据帧格式起始条件SCL高时SDA下降→ 7位地址读写位 → 从机ACK → 数据字节ACK → ... → 停止条件SCL高时SDA上升。每个字节后必须跟一个ACK位从机拉低SDA表示应答。如果从机不ACK主机通常会产生停止条件或重试。注意i2c的“自由数据模式”指的是某些器件支持不按标准寄存器地址访问直接读写数据流。用之前务必确认器件手册否则可能读到意外数据。关于i2c扩展和多路复用当总线上设备地址冲突或者电容过大时用i2c多路复用器如TCA9548A把总线分成多路每路独立。这招在摄像头模组、多传感器场景很常用。3.3 SPI速度之王但片选管理是门学问spi的核心是移位寄存器模型。主机和从机各有一个移位寄存器时钟每跳一次主机移出一位到MOSI从机移出一位到MISO同时各自移入一位。8个时钟后两个寄存器的内容就交换了。这就是spi全双工的物理本质——它本质上是在做数据交换而不是单向发送。spi有四种模式由CPOL时钟极性和CPHA时钟相位组合决定。CPOL0表示空闲时SCLK为低CPOL1表示空闲为高。CPHA0表示第一个边沿采样CPHA1表示第二个边沿采样。四种模式对应关系必须和从机手册一致否则数据全错。我见过太多人因为模式选错读出来全是0xFF或0x00。模式CPOLCPHA采样边沿空闲电平Mode 000上升沿低Mode 101下降沿低Mode 210下降沿高Mode 311上升沿高片选管理是spi实操中最容易出问题的地方。硬件片选由spi控制器自动拉低拉高CS时序精准适合高速传输。软件片选用GPIO手动控制灵活但要注意在传输前后正确拉低拉高且传输期间不能被其他任务打断。如果软件片选在传输中被调度器切走CS中途变高从机就会认为传输结束数据就废了。CS的最小脉宽能到多少这取决于从机手册的tCSS、tCSH等参数。一般从机要求CS建立时间几十ns保持时间几十ns。spi控制器在几十MHz下这些都不是问题但软件片选如果中间插入了函数调用或中断就可能超时。所以高速场景优先用硬件片选。关于spi dmaSTM32 CubeMX配置spi dma时要注意DMA请求的使能顺序和传输完成标志。常见坑是DMA发送完成中断里立刻拉高CS但此时spi移位寄存器可能还没发完最后一位。正确做法是等待spi的TXE和BSY标志都清零再拉CS。3.4 I2S音频数据的专属高速公路i2s的时序围绕音频采样展开。BCLK是位时钟每个音频位一个周期。LRCLK是帧时钟频率等于采样率低电平左声道高电平右声道或反之取决于格式。SDATA是数据线在BCLK边沿输出或采样。i2s的数据格式有几种标准i2sPhilips格式数据比LRCLK边沿延迟一个BCLK左对齐格式数据与LRCLK边沿对齐右对齐格式数据在帧末尾。选错格式会导致声道错位或数据偏移一位听感上就是左右声道反了或者全是噪声。BCLK频率采样率×位深×声道数。比如48kHz、24位、双声道BCLK48000×24×22.304MHz。MCLK通常是BCLK的256倍或384倍给codec内部PLL用。ESP32-C3的i2s输出配置时MCLK、BCLK、LRCLK的分频关系要算准否则采样率会偏音调就不对。实操心得调i2s最有效的手段是逻辑分析仪抓BCLK、LRCLK、SDATA三根线。先看BCLK频率对不对再看LRCLK频率是不是等于采样率最后看SDATA在LRCLK低电平期间是否有数据。三步定位问题比盲猜快得多。i2s和spi的关系i2s可以看作是spi的一个特化版本很多MCU的i2s外设和spi外设是复用的。但i2s有专门的音频时钟管理和帧同步机制不能简单用spi模拟否则时钟抖动和帧对齐会出问题。4. 选型实战从需求反推该用哪条总线4.1 按带宽需求快速筛选选型第一步永远是算带宽。一个传感器每秒产生多少字节一个音频流需要多少Mbps算清楚了选择范围立刻缩小。低于100kbpsuart或i2c都行看设备接口和引脚。100kbps~1Mbpsi2c快速模式或spi低速。1Mbps~10Mbpsspi或i2s。10Mbps以上spi高速或并行接口。举个例子MPU6050陀螺仪加速度计输出速率最高1kHz每次14字节带宽约14kB/s112kbpsi2c 400kHz绰绰有余。而一颗SPI Flash要跑80MHz那就只能spi。音频codec要传48kHz×24bit×22.3Mbpsi2s是唯一选择。4.2 按拓扑和引脚预算权衡如果板子上有8个传感器要接i2c两根线搞定spi要4812根线。这时候除非某个传感器带宽要求特别高否则全挂i2c是明智的。如果其中有一个高速ADC必须用spi那就单独给它一组spi其余走i2c。引脚预算紧张的MCU比如只有20个IO的小芯片i2c几乎是唯一选择。而FPGA这种IO丰富的平台spi可以随便铺。4.3 按实时性和可靠性要求决策uart没有时钟线长距离传输时双方时钟漂移会累积所以uart适合短距离板内或带流控的场合。i2c有ACK机制每字节确认可靠性好但速率受限。spi没有ACK主机发了就发从机收没收到不知道需要应用层做校验。i2s是流式数据丢一帧就是爆音所以对时钟稳定性要求极高。注意i2c的ACK机制在从机忙时可能不响应主机要处理NACK重试。spi没有流控高速传输时如果从机处理不过来只能靠主机降速或加延时。5. 常见问题与排查技巧实录5.1 通信完全不通先查电气再查协议遇到通信不通我的排查顺序永远是供电→地→电平→时钟→数据→协议配置。供电不对芯片不工作地没共参考电平就乱电平不匹配3.3V对5V可能烧引脚或读不到时钟没有后面全免谈。用万用表量电压用示波器量波形用逻辑分析仪看时序。这三样工具是嵌入式调试的标配。逻辑分析仪尤其重要几十块钱的8通道分析仪能抓i2c、spi、uart、i2s的波形并自动解码效率提升巨大。5.2 时好时坏上拉、电容与干扰i2c时好时坏十有八九是上拉电阻偏大或总线电容偏大。用示波器看SCL上升沿如果圆滚滚的像馒头就是RC时间常数太大。换小电阻或减少总线设备。spi时好时坏检查CS时序和时钟相位以及是否有其他设备干扰。5.3 常见问题速查表现象可能原因排查方法i2c无ACK地址错、从机没供电、上拉缺失量地址、量供电、量上拉i2c总线死锁从机拉低SDA不放、时钟拉伸示波器看SDA/SCL、复位从机spi读全0xFFMISO没接、模式错、CS没拉低量MISO、核对模式、量CSspi读全0x00从机没供电、时钟没输出量供电、量SCLKuart乱码波特率错、时钟源错、电平反量波特率、核对时钟、量电平uart丢数据无流控、中断优先级低、DMA配置错加流控、提优先级、查DMAi2s噪声BCLK/LRCLK频率错、格式错量时钟频率、核对格式i2s无声SDATA没数据、MCLK缺失量SDATA、量MCLK5.4 独家避坑技巧第一个技巧i2c地址扫描。写个简单的扫描程序遍历所有7位地址发起始条件看哪个地址有ACK。这能快速确认从机是否在线、地址是否配错。很多传感器地址可以通过引脚配置扫描能帮你确认实际地址。第二个技巧spi回环测试。把MOSI和MISO短接发什么收什么验证spi控制器和DMA配置是否正确。这个测试能在接从机之前排除一半问题。第三个技巧uart自发自收。TX和RX短接发一串数据看能否原样收到验证波特率和配置。这是最基础的验证但很多人跳过这步直接接设备结果问题定位范围扩大。第四个技巧逻辑分析仪解码。不要只看波形用分析仪的解码功能直接看协议层数据。i2c解码能看到地址和数据字节spi解码能看到每个字节uart解码能看到字符。这比人眼数位快一万倍。6. 从裸机到Linux不同平台上的实现差异6.1 MCU裸机寄存器与HAL库在STM32这类MCU上i2c、spi、uart都有硬件外设配置寄存器或调用HAL库即可。裸机的优势是实时性可控中断响应快。但要注意HAL库的阻塞式传输在高速场景下会占CPU建议用DMA或中断方式。STM32F103标准库的uart DMA中断接收发送是经典组合。配置DMA接收循环模式开启空闲中断空闲中断里处理数据。发送用DMA普通模式发送完成中断里拉高CS或做后续处理。6.2 Linux设备树与驱动框架在RK3588这类SoC上跑Linuxi2c、spi、uart都要在设备树里描述。i2c设备节点要指定reg地址spi设备节点要指定片选和模式uart要指定波特率和流控。驱动层用Linux的i2c_client、spi_device、tty框架。Linux下调试i2c可以用i2cdetect、i2cget、i2cset工具非常方便。spi可以用spidev从用户态直接读写。uart就是/dev/ttyS*用termios配置。实操心得RK3588的spi接口在设备树里配置时要注意pinctrl的引脚复用和驱动能力。我遇到过spi时钟上不去的情况最后发现是引脚驱动能力设成了默认低档改成高档就好了。6.3 特殊场景USB转串口与模拟接口FT232R、FT231X这类USB转uart芯片在PC上需要装驱动在Linux下通常内核自带ftdi_sio驱动。用Python调用USB模拟spi接口也是可行的通过USB转spi桥接芯片或者用GPIO模拟但速率和实时性远不如硬件spi只适合低速调试。7. 几个容易混淆的边界问题7.1 I2C与PMBus、SMBus的关系PMBus和SMBus都基于i2c物理层但协议层有差异。SMBus有超时机制和特定的命令格式PMBus在SMBus上定义了电源管理相关命令。用i2c控制器读PMBus设备通常可以但要注意超时和协议差异。7.2 SPI与I2S能否互转有些MCU的spi和i2s共用外设通过配置切换。但i2s的帧同步和音频时钟要求比spi严格不能简单把spi当i2s用。反过来i2s也不能当通用spi因为它没有片选和双向数据的概念。7.3 UART与GPIO模拟没有硬件uart时可以用GPIO模拟但时序精度差高波特率下误码率高。同理GPIO模拟i2c也常见但要注意开漏配置和上拉。模拟spi相对容易因为时钟由主机控制GPIO翻转速度够就行。8. 我个人的选型决策树最后分享一个我自己用的决策流程不一定最优但很实用先看设备手册支持哪些接口。设备只支持i2c那就没得选。算带宽。超过1Mbps优先spi音频优先i2s低速优先i2c或uart。数设备数量。多设备低速选i2c少设备高速选spi。看引脚预算。引脚紧张选i2c引脚富裕选spi。看实时性。硬实时选spi或i2s软实时i2c非实时uart。看调试便利性。uart最容易调试i2c有地址扫描spi要抓波形i2s要听声音。这套流程帮我省了很多反复改板的时间。实际项目中往往一个系统里四种总线同时存在各司其职关键是让每条总线干它最擅长的事。i2c管低速传感器spi管高速存储和外设i2s管音频uart管调试和模组这样搭配下来系统稳定性和开发效率都能兼顾。
