
嵌入式工程师对这四个词应该都不陌生I2C、I2S、SPI、UART。原理图上到处是它们的名字芯片手册里躺着它们的时序图调试日志更是靠UART一路输出的。但说实话很多兄弟只停留在会用状态——对着初始化代码抄一遍波形不对就换模式时序错了就调参数至于为什么是这套机制很少停下来细想。我见过把I2S当I2C来配的也见过SPI片选用软件模拟导致ADC数据偶尔跳变、折腾了两天查不出原因的。这些问题的根源往往不是代码写错了而是对协议本质的理解有偏差。不管你是刚入门的嵌入式新人还是已经在产品里调通了好几颗外设的老手这篇对比都能给你一些平时文档里翻不到的细节。1. 四种协议到底是什么别急着背参数开门见山先说它们为什么能共存。串行通信的共同点是拿一根或几根线把数据一位一位传出去省引脚、好布线。但不同的应用场景对传多快、能连多少个、能不能双向、能不能确认收到都有完全不同的要求于是芯片行业演化出了这四种形态各有专攻。1.1 同步、异步与约定这件事UART是异步通信收发双方之间没有时钟线全靠事先约定的波特率来采样。SPI、I2C、I2S都有时钟线属于同步通信谁发时钟谁做主时序由硬件保证。这条差异决定了它们在抗干扰、传输距离和速率上限上的基本性格。异步看似省了布线但代价是双方必须接受波特率时钟源有偏差的现实。晶振差几个ppm在115200波特率下一帧误差不到半个位宽问题不大要是把波特率顶到921600再让板子跑在高温环境误差就可能吃掉半个位周期导致乱码。所以UART高速率场景我从来不用内部RC振荡器老老实实外接晶振这是第一个容易忽略的坑。1.2 UART点对点、老而弥坚UART全称通用异步收发器基本原理就是两根线TX和RX交叉连接全双工。帧格式是大家熟悉的那套空闲时总线为高电平检测到下降沿就是起始位然后依次传数据位通常是8位、LSB在前加上可选的奇偶校验位最后是停止位拉高至少一个位周期。接收端从检测到下降沿那一刻起就要按波特率一位一位地采样所以双方时钟偏差一多采样点就会慢慢滑向位边界。热词里那个16550行业标准UART值得多说一句。16550是当年PC上的经典串口控制器因为引入了16字节FIFO一下子解决了一个字节一中断、CPU被中断风暴打垮的问题。后来的芯片几乎都在寄存器布局上兼容它直到今天Linux下的串口驱动里还有大量ns16550的血统。UART的短板是只能点对点一条串口线上只有收发两端想挂多个设备就得转RS485变成半双工总线还要自己定义地址协议。好处的老练在于简单、可靠、距离远MCU基本都有调试口首选基本上就是它。USB转串口芯片干的活本质是把UART信号桥接成USB让电脑把它识别成一个COM口。FT231X、FT232R这两颗我用了不少驱动出问题的概率其实不大真出问题多数是驱动版本和Win10/Win11的兼容性后文会专门讲。1.3 I2C两根线、开漏、一挂一大串I2C是NXP前身飞利浦搞的总线两条线SCL时钟、SDA数据都靠开漏输出加外部上拉电阻工作。开漏意味着任何设备都可以把线拉低也可以释放掉让上拉电阻把线拉回高。这个结构是I2C一切机制的基石多设备共享不需要三态门争抢从机还能把SCL主动拉低来让主机等等我这就是时钟拉伸。理解了开漏你自然就明白I2C的速率为什么被卡在几百KHz到几MHz。上拉电阻要对总线电容充电阻值越小上升沿越快但功耗越大、灌电流也越大不能一味减小。所以I2C走线不能太长超过十厘米就开始有信号完整性问题。I2C的数据帧格式说穿了很简单起始条件SCL高电平时SDA由高到低然后是8位地址加读写位最后是应答位。从机在地址匹配后拉低SDA回ACK主机收到ACK才继续发数据。读操作的最后主机回一个NACK再给停止条件意思是够了别发了。有热词搜i2c自由数据模式很多芯片手册里叫无格式或连续读写模式本质是地址自动递增主机不用每字节重发地址非常适合读写EEPROM这类连续存储块。7位地址在不扩展的情况下最多挂127个设备如果总线上必须挂多颗同地址芯片就得分组供电或者用I2C多路复用器这也是i2c控制的多路复用解决的问题。1.4 SPI全双工、高速、片选为王SPI全称串行外设接口最少四根线SCLK时钟、MOSI主机出从机入、MISO从机出主机入、CS片选通常低有效。跟I2C最大的不同在于SPI是全双工主机时钟每打一拍同时移出一位、移入一位效率比I2C高出一截。速率上限通常也不受电容充电约束几十MHz很常见配DMA可以持续搬数据。SPI的四种工作模式由CPOL和CPHA组合决定。CPOL是时钟空闲电平CPHA决定采样沿新手最常犯的错就是把主从双方的模式配反波形看着似乎有传输但数据全是乱的。多从机场景有两种典型拓扑独立片选每个从机一根CS菊花链数据从第一个从机串到下一个。独立片选占用引脚多但灵活菊花链省引脚但延迟和软件寻址都变复杂。SPI没有标准地址也没有ACK机制。发一个字节从机到底收没收到协议层根本不管。所以很多器件会扩展出状态寄存器、忙引脚、中断脚本质上都是在弥补无应答的缺失。这颗雷新手踩得特别多选型时就要想清楚如果对端不支持状态检查上层就得自己加校验。1.5 I2S名字像I2C其实是音频专用道I2S是飞利浦为数字音频定义的接口名字和I2C就差一个字母常被新手混为一谈其实它和I2C除了都是串行没有任何亲缘关系。I2S有三根核心线BCLK位时钟LRCK/WS左右声道选择也叫帧同步SD串行数据可以有多根。标准I2S格式里数据要比WS的变化晚一个BCLKWS低电平通常代表左声道高电平代表右声道。很多人配I2S只盯着BCLK和LRCK漏了MCLK主时钟。很多音频Codec要求MCU提供MCLK比如12.288MHz或24.576MHz是采样率整数倍。对新手来说这是最隐蔽的坑逻辑分析仪一看I2S数据流波形正常喇叭就是不出声或者只出沙沙声先查MCLK有没有给。I2S的数据格式也不只有一种常见的有I2S标准、左对齐、右对齐位数有16/24/32bitDMA配置时数据宽度和声道数搞错声音就会变调或左右颠倒。热词里那个esp32-c3 i2s输出经常翻车原因大多不是硬件而是slot、位数和时钟分频没对齐。波形能看到但声音不对去对照Codec手册的音频格式映射表基本都能找到答案。2. 参数横评和选型逻辑别再凭感觉挑协议2.1 一张表看穿速度、引脚和拓扑先把四种协议的硬指标摆在一起后面选型思路都围绕这张表展开。对比项UARTI2CSPII2S信号线2根TX/RX2根SDA/SCL4根以上SCLK/MOSI/MISO/CS3根以上BCLK/LRCK/SD同步方式异步同步同步同步拓扑点对点可转RS485多设备总线带地址多从机独立片选/菊花链点对点/少设备常见速率0.3~12Mbps看电平100k/400k/1M/3.4M几MHz~几十MHz最高几十MHz双工方式全双工半双工全双工全双工数据流方向固定应答机制无有ACK/NACK、时钟拉伸无无传输距离较长短板级短板级短板级典型场景调试日志、GPS、MODBUS传感器、EEPROM、PMBus、SMBusFlash、LCD、ADC、FPGA配置音频Codec、DAC/ADC2.2 选型要看这五件事别只看速率第一件是引脚预算。一个项目能用多少IO能不能给每颗外设分到独立的时钟线和片选往往比速率瓶颈更先卡脖子。引脚紧张又要挂多个从机I2C两根线就能解决引脚充足且希望以最快速度搬数据直接SPI。第二件是从机数量和寻址方式。总线上挂一堆同型号传感器又没有地址跳线区分用I2C会非常痛苦要么靠多路复用器要么改软件枚举SPI就简单粗暴许多每个从机一根CS但每多一个从机就多占一个GPIO。第三件是需不需要确认收到。I2C自带ACK读写EEPROM时每字节都能感知从机是否响应调试阶段极其友好SPI无应答一次突发传完才发现错误就得靠CRC这类上层协议兜底。如果设备可靠度要求高、希望能即时发现通信异常I2C的确认机制是实打实的优势。第四件是板内还是板间。I2C和SPI都是板级总线走线长了信号完整性就开始恶化尤其是I2C的上拉电阻和长线电容组合波形上升沿会明显变缓。UART走线随意得多配上RS485或CAN解决板间通信。很多路由器、服务器主板保留UART做带外管理靠的就是这个长距离优势。第五件如果数据是单向音频流、又需要和采样率同步的帧时钟直接选I2S别拿SPI硬扛。SPI也能传音频比特流但你要自己在软件里对齐帧、管理左右声道和位钟纯粹是自我折磨。2.3 影响范围从传感器到存储再到音频一线定全局选协议影响的往往不只是某颗器件的接法而是整个系统架构。传感器领域温湿度、光照、触摸屏、陀螺仪大量用I2C因为低功耗、少引脚、多地址。像GT911这类触摸屏控制器通常挂I2C通信失败的原因往往不是时序不对而是地址或复位时序没处理对后面章节展开。存储和显示领域Flash、LCD、SD卡几乎都是SPI既要速率又要QSPI扩展。主控端RK3588这类SoC上SPI接口经常接NOR Flash做启动引导PCIe/NVMe存系统盘一套混合存储方案里既有SPI又有高速接口各管一段。音频领域则是很典型的组合拳Codec的控制寄存器走I2C音频数据流走I2S两条总线并行各司其职。电源管理领域有个容易搞混的点PMBus和SMBus底层都是I2C。PMBus是电源管理协议跑在I2C物理层之上给电源模块读写寄存器、调压、读状态用的它和I2C不是并列关系而是上下层关系。有人问pmbus和i2c区别一句话I2C是传输层协议PMBus是应用层命令集。还有linux phy不使用mdio使用i2c的情况部分PHY芯片确实没有MDIO管理口而是I2C管理口这说明选型永远要跟着芯片能力走不能只看习惯。3. 实操从波形到代码亲手验证四种差异3.1 逻辑分析仪抓波形四种协议一眼区分总有人问我怎么快速判断一条总线上跑的是什么协议。我的答案始终是抓波形。逻辑分析仪挂好参考地所有通道全部接上先看有几根活跃线一根线空闲是高电平偶尔来个起始位加一串等间隔脉冲多半是UART两根线一个明显是时钟、另一个是数据而且数据在时钟高电平期间保持稳定是I2C四根以上线时钟速率快每传输一个字节片选都会拉低再拉高是SPI三根线其中一根按固定频率连续翻转另一根是低得多的低频方波第三根是数据基本就是I2S。I2C解码非常直观起始条件后面跟着地址字节低7位是地址第8位是读写位之后每个字节后面都有ACK位。用逻辑分析仪软件解码时注意设对电压阈值1.8V的总线别拿3.3V阈值去解不然结果乱七八糟。热词里有人搜i2s逻辑分析仪波形标准做法是把BCLK设为采样参考在LRCK边沿附近看数据的起始位置确定是I2S标准还是左对齐。I2S数据是MSB先出按这个顺序拼位就对了。3.2 STM32上的初始化配置示例同等配置逻辑全覆盖以STM32 HAL库为例演示四种外设的最小初始化。代码不是让你照抄而是体会每种协议的配置逻辑差异。UARTUSART1波特率115200UART_HandleTypeDef huart1; huart1.Instance USART1; huart1.Init.BaudRate 115200; huart1.Init.WordLength UART_WORDLENGTH_8B; huart1.Init.StopBits UART_STOPBITS_1; huart1.Init.Parity UART_PARITY_NONE; huart1.Init.Mode UART_MODE_TX_RX; HAL_UART_Init(huart1);UART的初始化逻辑最少因为协议本身简单波特率、数据位、停止位、校验设置完就结束。I2CI2C1400kHz7位地址主机模式I2C_HandleTypeDef hi2c1; hi2c1.Instance I2C1; hi2c1.Init.ClockSpeed 400000; hi2c1.Init.OwnAddress 0; hi2c1.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c1.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; HAL_I2C_Init(hi2c1);注意I2C即使做主机也要设置OwnAddress还要挑地址模式。之后调用HAL_I2C_Master_Transmit/Receive时传入的参数就是目标从机地址。SPISPI1Mode 08位主模式SPI_HandleTypeDef hspi1; hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_64; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1);SPI的配置项在四者里最多CPOL/CPHA、NSS软硬件、分频系数、MSB/LSB每一项都可能决定通信成败。I2SSPI2复用为I2S飞利浦标准16bit48kHzI2S_HandleTypeDef hi2s2; hi2s2.Instance SPI2; hi2s2.Init.Mode I2S_MODE_MASTER_TX; hi2s2.Init.Standard I2S_STANDARD_PHILIPS; hi2s2.Init.DataFormat I2S_DATAFORMAT_16B; hi2s2.Init.MCLKOutput I2S_MCLKOUTPUT_ENABLE; hi2s2.Init.AudioFreq I2S_AUDIOFREQ_48K; hi2s2.Init.CPOL I2S_CPOL_LOW; HAL_I2S_Init(hi2s2);I2S在STM32上是挂在SPI外设上的所以Instance还是SPI2但配置项换成了Standard、DataFormat、AudioFreq、CPOL这些I2S专属选项MCLK输出也在这里控制音频分频由AudioFreq自动算。实际调试顺序我建议固定下来先用UART输出日志确认系统活着再用I2C读一颗设备的ID验证总线接着用SPI读写Flash最后才轮到I2S出声音。每一步都有大量可观测输出出错了也容易定位。3.3 硬件片选与软件片选的取舍SPI硬件片选与软件片选这个问题几乎每次都会被问。硬件片选是SPI外设自己控制的NSS引脚软件片选是代码手动拉一个GPIO。我的结论是硬件片选适合高速传输和需要极低延时的场景比如ADC连续采样、Flash大数据块读写因为外设能在字节之间精确控制片选脉冲省去GPIO操作的额外开销。缺点是硬件NSS引脚固定一旦被其他功能复用就变成被迫切到软件片选。软件片选灵活任何GPIO都能做CS移植性好。但在高速率下两次CS之间的GPIO翻转延迟会拉长字节间隙极端情况下会被从机误判成一帧中的空闲段。软件片选还有一个特别容易忽略的操作顺序必须先拉低CS再启动SPI外设传输传完最后一个字节后马上拉高。顺序反了从机把CS边沿当帧边界收到的数据必然错位。热词里那个cs最小能做到多少其实是在问片选脉冲最小宽度。芯片手册通常会给CS Setup和CS Hold时间比如ADC要求CS低电平至少保持几百纳秒。如果软件模拟时GPIO翻转太慢或者SPI时钟太快导致CS脉冲太窄采到的ADC数据就会偶尔跳变。用示波器或逻辑分析仪看CS和SCLK的相对时序很容易确认问题。4. 踩坑实录这四兄弟的常见问题排查4.1 I2C地址、死锁、从机不响应I2C的坑基本集中在三个地方。地址不对是很常见的一种。GT911触摸屏有多个7位地址手册有时写7位、有时写含读写位的8位形式对不齐就会一直NACK。注意I2C地址有两种表示法纯7位地址和包含读写位的字节两者数值差一位。比如0x28是7位地址换算成字节形式可能是0x50背错一位就全错。SDA死锁是最让人抓狂的。主机收到NACK或者程序中途停住总线停在SDA为低后面所有通信全部失败。这是因为某个设备把SDA拉低了却没人释放。排查手法很简单连续给出9个SCL时钟脉冲让从机把未完成的字节发完实在不行把SCL和SDA临时配成GPIO输出高人为复位总线。时钟拉伸被卡则多出现在软件模拟I2C的场景。某些低速从机会拉低SCL让主机等待如果你写代码时没检查SCL是否回到高就直接发下一个时钟通信就会卡死。软件模拟的基本功就是每次驱动时钟之前先等SCL释放回来。顺带提一个Windows下的HID问题HID设备找不到足够资源代码12多半是触摸屏同时挂I2C和HID且驱动资源冲突先卸载过时驱动、换个USB口或改BIOS的HID选项通常能缓解跟总线本身关系不大。4.2 SPI模式错位、片选毛刺、采样噪声SPI的坑第一是模式错配。从机手册写明是Mode 0还是Mode 3主从必须一致。一个隐蔽现象是主机配成Mode 1从机是Mode 0数据偶尔对、偶尔全错因为双方在相位和极性的组合上始终差半拍。手头没有逻辑分析仪就先用示波器看SCK边沿和MISO数据变化的相对位置能看出端倪。第二是片选毛刺。软件片选的GPIO如果在上电初始化时默认输出低会导致从机误触发传输。初始化时务必先把CS引脚配成高电平再去初始化SPI外设。硬件NSS如果被其他复用拉低拉高也有类似问题。第三是ADC采样噪声。FPGA或MCU通过SPI接高速ADC时SCK本身就是采样时钟的一部分CS的建立保持时间、SCK走线、电源纹波都会直接影响采样精度。我遇到过SPI 10MHz读ADC偶尔出毛刺的情况用示波器一查SCK上升沿和CS下降沿只差了2ns手册要求至少5ns把速率降到6.6MHz并加了点RC延时就好了。注意SPI高速并不是只调分频器那么简单模拟时间预算才是真正的门槛。4.3 UART波特率误差和驱动装上识别不了UART的坑相对温和但也有隐蔽的。波特率误差始终存在。STM32的PCLK如果不是波特率的整倍数实际波特率和理论值就对不上。误差超过2%~3%就有风险。配置时看HAL库算出来的USARTDIV余数尽量选误差小的组合。921600这类高速率误差被放大得更明显调试时乱码先查这一项。USB转串口驱动识别不了FT231X和FT232R在Windows下不识别大概率不是硬件坏了是驱动版本和系统兼容问题。旧版驱动在Win10/Win11上经常会弹设备无法启动代码10或者干脆不识别。换装FTDI官方最新驱动确认设备管理器的VID/PID是0403/6001就行。别一看到无法识别就怀疑芯片是假货先看描述符。乱码的原因一般是波特率不一致、电平不匹配或者共地问题。共地这个坑太容易被忽视两个板子各插一个USB转串口却不共地数据就偶尔对偶尔错经常被误判成时序问题。两根走线的地没连上电平参考点漂移什么波特率都救不回来。4.4 I2S时钟没给全、格式错位、左右声道颠倒I2S的坑第一是MCLK缺失。很多Codec必须要有MCLK才工作但新手往往只配BCLK和LRCK。表现形式是I2S波形看起来完全正常喇叭不出声或只有高频噪声。第二是声道与slot错位。16bit数据放进32bit slot是左对齐还是右对齐Codec手册一定有讲配错了声音就变调或者感觉慢速倒放。像ESP32-C3这类芯片做I2S输出时DMA的位宽和I2S配置的slot宽度必须统一不然数据被错位截断。第三是音频格式不匹配。I2S和DSP/PCM格式之间通常只差一个位的偏移声音表现是轻微沙沙加正常说话混合。排查套路很固定逻辑分析仪抓LRCK下降沿和数据第一位对照Codec手册的时机图。凡是出现波形正常但声音不对的99%是格式配错了。最后分享一个我自己一直保持的习惯任何新板子到手先抓一遍所有总线的波形再动手写驱动。抓波形不是为了炫技而是把协议到底在干什么这个问题在落地前就先在心里种下答案。每次改协议配置也只动一个变量再抓波形看变化而不是一把梭全改完再慢慢猜。这个习惯帮我省下的排查时间比我写这篇对比用的时间多得多。希望你也试试。