1. 四种通信协议到底该怎么选:从踩坑现场说起
i2c、i2s、spi、uart这四个词,凡是摸过单片机、写过驱动、调过传感器的人,几乎每天都在跟它们打交道。但真要让人用一句话说清楚“什么时候该用哪个”,很多人还是会卡壳。我自己在早期做项目时,就干过拿UART去接高速ADC的蠢事,也试过用I2C驱动音频编解码器结果发现根本带不动数据量。后来踩的坑多了,才慢慢把这四者的脾气摸清楚。
这篇内容就是把这四种协议拉到同一张桌子上,从电气特性、时序结构、典型速率、拓扑方式、软硬件开销这几个维度做一次彻底对比。不管你是刚入门的新手,还是已经能写驱动但想系统梳理一遍的老手,都能从中找到可以直接拿去用的判断依据。我会尽量少堆术语,多用实际项目里的场景来解释——比如为什么ESP32-C3的I2S输出要特别关注时钟配置,为什么RK3588的SPI接口在挂NOR Flash时要注意片选建立时间,为什么STM32F103用标准库做UART DMA接收时容易丢第一帧数据。
核心关键词i2c、i2s、spi、uart会贯穿全文,但不会为了堆词而堆词。每个协议我都会给出时序图级别的细节拆解,同时配上实际选型时的决策逻辑。看完之后,你至少能做到:拿到一个传感器或外设,扫一眼它的接口类型,就能判断该用哪种总线、大概能跑多快、软件上要预留多少资源。
2. 四兄弟的出身与定位:先搞懂它们各自为什么被发明
2.1 I2C:两根线挂一堆设备的“公交车”
I2C的全称是Inter-Integrated Circuit,中文常叫“集成电路总线”。它诞生的初衷非常明确:在板级芯片之间用最少的引脚实现多设备通信。两根线——SCL和SDA,一根时钟一根数据,所有设备都挂在这两条线上,靠7位或10位地址区分彼此。这就好比一条公交线路,所有乘客共享同一辆车,每个人靠“名字”判断是不是叫自己。
它的典型速率是100kbps、400kbps,高速模式下能到3.4Mbps,但实际项目中超过1Mbps的I2C并不多见。原因在于I2C是开漏输出,需要外部上拉电阻,上升沿的斜率直接受RC时间常数限制。你如果用过逻辑分析仪抓I2C波形,会发现SCL和SDA的上升沿往往不是陡直的,而是带一点弧度,这就是上拉电阻和线容共同作用的结果。
I2C最舒服的场景是:挂一堆低速传感器、EEPROM、IO扩展芯片。比如温度传感器、加速度计、触摸屏控制器GT911,这些设备数据量小、实时性要求不高,用I2C既省引脚又省事。但它也有明显的短板:半双工、速率有限、总线锁死风险高。我遇到过好几次I2C从机在异常复位后把SDA拉低不放的情况,主机这边怎么发时钟都没用,最后只能靠硬件复位或者手动发9个时钟脉冲来解锁。
2.2 SPI:四根线跑出高速的“点对点专线”
SPI是Serial Peripheral Interface,串行外设接口。它比I2C多两根线:SCK时钟、MOSI主出从入、MISO主入从出,再加上每个从机一根CS片选。四根线换来了全双工和更高的速率。SPI没有地址概念,靠片选线选设备,所以挂N个从机就需要N根CS线(或者用译码器扩展)。
SPI的速率可以做到几十MHz甚至上百MHz,具体取决于主控和从机的能力。比如STM32F103的硬件SPI最高能到18MHz,而RK3588的SPI接口可以轻松跑到50MHz以上。这也是为什么高速ADC、LCD屏、Flash存储器几乎都选SPI——它就像一条点对点的专线,没有总线仲裁开销,数据可以连续不断地推。
但SPI的“坑”也不少。首先是模式问题:CPOL和CPHA组合出四种模式,主从双方必须严格匹配,否则采样时刻错位,读回来的数据全是乱的。我见过太多人调试SPI Flash时因为模式设错,读ID都读不出来。其次是片选管理:硬件片选由控制器自动拉低拉高,软件片选则需要手动控制GPIO,后者在DMA传输时容易出问题,因为DMA搬完数据后不会自动释放片选,需要中断里手动处理。
2.3 UART:最古老也最顽强的“异步串口”
UART是Universal Asynchronous Receiver/Transmitter,通用异步收发器。它没有时钟线,靠双方约定的波特率来同步。两根线TX和RX,交叉连接即可通信。因为不需要时钟线,UART的硬件成本极低,这也是为什么它从计算机诞生初期一直活到现在,16550行业标准UART至今仍在很多芯片里作为调试串口使用。
UART的典型波特率是9600、115200、921600,高一点的能到3Mbps甚至更高。但它是异步的,没有时钟线意味着对波特率误差敏感。双方波特率偏差超过2%左右,采样就会错位,出现帧错误。我在用STM32F103标准库做UART DMA接收时,就遇到过因为外部晶振精度不够导致高波特率下误码率飙升的情况,后来换成内部RC校准才稳定下来。
UART最适合的场景是:调试信息输出、模块间低速通信、与PC通信。比如FT232R、FT231X这类USB转UART芯片,就是用来把MCU的UART接到电脑USB口上的。它的协议简单到几乎不需要硬件控制器,软件模拟一个UART也不难,但高速时对中断响应要求较高。
2.4 I2S:为音频而生的“同步串行总线”
I2S是Inter-IC Sound,专门为数字音频传输设计。它和SPI长得很像,都有时钟和数据线,但I2S的时钟体系更复杂:除了位时钟SCK,还有帧时钟WS(也叫LRCK),用来区分左右声道。数据在WS变化后延迟一个SCK周期开始传输,这个细节决定了I2S和SPI的本质区别。
I2S的速率取决于采样率、位深和声道数。比如48kHz采样率、24位深、双声道,位时钟就是48k×24×2=2.304MHz。如果是192kHz、32位、8声道,位时钟能到49MHz以上。所以I2S的时钟配置非常关键,尤其是用ESP32-C3做I2S输出时,时钟源选择、分频系数、MCLK输出与否都会影响音频质量。
I2S只传音频数据,不传控制信息。所以音频编解码器通常还需要一个I2C接口来配置寄存器,这就形成了“I2C管控制、I2S管数据”的经典组合。如果你用逻辑分析仪抓I2S波形,会看到WS在左右声道间切换,SD线上一串连续的位流,SCK则均匀地跳动。
3. 关键参数硬碰硬:速率、拓扑、开销全对比
3.1 电气特性与线数对比
先看一张表,把四者的物理层差异摆清楚:
| 协议 | 线数 | 方向 | 时钟 | 拓扑 | 典型上拉 |
|---|---|---|---|---|---|
| I2C | 2 | 半双工 | 同步 | 多主多从 | 需要 |
| SPI | 4+N | 全双工 | 同步 | 一主多从 | 不需要 |
| UART | 2 | 全双工 | 异步 | 点对点 | 不需要 |
| I2S | 3+ | 半双工/全双工 | 同步 | 一主一从 | 不需要 |
I2C的开漏结构决定了它必须加上拉电阻,典型值4.7kΩ或10kΩ。上拉越小,上升沿越快,但功耗越大。我一般会在400kbps速率下用4.7kΩ,100kbps用10kΩ。SPI是推挽输出,驱动能力强,不需要上拉,但线长了要注意阻抗匹配。UART也是推挽,但异步采样对边沿要求高,线太长容易受干扰。I2S的时钟线频率高,PCB走线要尽量等长,否则左右声道数据可能错位。
3.2 速率与数据吞吐能力
速率这块,直接给结论:SPI > I2S > UART > I2C(典型配置下)。但具体数字要看实现:
- I2C:标准100kbps,快速400kbps,高速3.4Mbps。实际有效数据率要扣除地址、ACK、起始停止位,大概只有标称的70%左右。
- SPI:几MHz到上百MHz。比如STM32F103硬件SPI最高18MHz,RK3588的SPI可以到50MHz。全双工意味着同时收发,有效吞吐率接近时钟频率。
- UART:常见115200bps到3Mbps。每字节有起始位和停止位,有效数据率约为波特率的80%。
- I2S:位时钟从几百kHz到几十MHz。只传音频数据,没有地址和ACK开销,有效数据率接近100%。
这里要特别提一下SPI的片选建立时间。CS拉低到第一个SCK边沿之间需要一定延时,这个时间由从机手册给出。比如某些SPI Flash要求CS建立时间最小20ns,如果你主控SPI时钟太快,CS还没稳定就开始发时钟,第一个位就会丢。我在调试RK3588挂SPI NOR时,就因为在设备树里没配好cs-setup-delay,导致引导失败。
3.3 软硬件开销与调试难度
软件开销方面,UART最简单,配置好波特率、数据位、停止位、校验位就能收发。I2C需要处理起始、停止、ACK、地址、寄存器地址,代码量中等。SPI需要管理片选、模式、DMA,代码量稍大但逻辑直接。I2S最复杂,时钟树配置、DMA双缓冲、左右声道对齐,每一项都容易出错。
硬件开销方面,I2C最省引脚,SPI最费引脚。UART和I2S居中。调试难度上,UART最容易用示波器看波形,I2C和SPI用逻辑分析仪抓一次就清楚,I2S的波形最难看懂,因为WS和SD的关系需要对着时序图慢慢数。
4. 实战选型:拿到项目该怎么拍板
4.1 按数据量选:小数据走I2C,大数据走SPI
这是最朴素的判断逻辑。如果你要接的是温度传感器、EEPROM、IO扩展芯片,数据量每次几个字节,速率要求不高,I2C是首选。比如I2C读写EEPROM的代码,Verilog里实现一个I2C主机控制器大概几百行,Python用smbus库几行就能跑。
如果你要接的是ADC、LCD、Flash、以太网控制器,数据量大、速率要求高,SPI更合适。比如FPGA通过SPI接ADC,采样率几MSPS,数据连续不断,只有SPI能扛住。MT6701磁编码器用SPI输出角度数据,也是因为SPI速率够快,能满足实时控制需求。
4.2 按实时性选:UART适合异步,I2S适合音频
UART的异步特性决定了它不适合硬实时场景,因为波特率误差和中断延迟都会引入抖动。但它非常适合调试输出和低速命令交互。比如STM32F103用UART DMA中断接收发送,可以做到不阻塞主循环,但接收超时判断需要额外定时器。
I2S是音频专用,实时性由硬件时钟保证。ESP32-C3的I2S输出配置时,要特别注意MCLK是否需要输出给编解码器,以及DMA缓冲区大小是否够大,否则音频会断断续续。我试过用ESP32-C3做蓝牙音频接收,I2S输出到DAC,缓冲区设小了就会有咔哒声。
4.3 按拓扑选:多设备用I2C,点对点用SPI/UART
I2C的多设备能力是它的核心优势。一条总线挂七八个传感器很常见,地址不冲突就行。但要注意I2C的电容负载限制,总线电容超过400pF就会影响上升沿。如果设备太多,可以用I2C多路复用器扩展。
SPI挂多设备需要多根CS线,如果CS不够用,可以用GPIO扩展或者译码器。UART是点对点,两个设备之间通信,不能挂多个。I2S也是一对一,音频编解码器和主控之间直接连。
4.4 混合使用:I2C控制加I2S数据是经典组合
很多音频编解码器同时提供I2C和I2S接口。I2C用来配置寄存器,比如设置采样率、增益、输入输出通道;I2S用来传音频数据。这种组合非常常见,因为控制信息量小、速率低,数据信息量大、速率高,各取所长。
类似的还有PMBus和I2C的关系。PMBus在物理层兼容I2C,但协议层增加了电源管理专用命令。如果你用I2C去读PMBus设备,可能能读到数据,但命令格式不对,读出来的值没有意义。
5. 时序细节深挖:逻辑分析仪抓波形时该看什么
5.1 I2C时序:起始、地址、ACK、数据、停止
I2C的起始条件是SCL高时SDA从高变低,停止条件是SCL高时SDA从低变高。地址帧是7位地址加1位读写位,然后从机拉低SDA表示ACK。数据帧每8位后跟一个ACK位。如果从机不ACK,主机就会发停止或重启。
抓I2C波形时,重点看几个地方:起始条件是否干净、地址是否匹配、ACK是否正常、时钟频率是否稳定。我遇到过GT911 I2C通信失败,抓波形发现地址发出去后从机不ACK,后来查出来是上拉电阻太大,上升沿太慢,从机采样不到。
I2C自由数据模式是一种特殊用法,可以绕过寄存器地址直接读写数据,但需要从机支持。这种模式在调试时很有用,但正式产品里要谨慎,因为兼容性不好。
5.2 SPI时序:模式0到模式3的区别
SPI的四种模式由CPOL和CPHA决定:
| 模式 | CPOL | CPHA | 采样边沿 | 移出边沿 |
|---|---|---|---|---|
| 0 | 0 | 0 | 上升沿 | 下降沿 |
| 1 | 0 | 1 | 下降沿 | 上升沿 |
| 2 | 1 | 0 | 下降沿 | 上升沿 |
| 3 | 1 | 1 | 上升沿 | 下降沿 |
模式0和模式3最常用。模式0是空闲低电平,上升沿采样;模式3是空闲高电平,上升沿采样。调试SPI时,先用示波器看SCK空闲电平,确定CPOL,再看数据在哪个边沿稳定,确定CPHA。
SPI硬件片选和软件片选的区别也很关键。硬件片选由SPI控制器自动管理,传输开始拉低,结束拉高,时序精准。软件片选需要手动控制GPIO,在DMA传输时容易提前释放或延迟释放。我一般建议高速传输用硬件片选,低速或多设备用软件片选。
5.3 UART时序:起始位、数据位、校验位、停止位
UART帧格式是:1个起始位(低电平)、5到9个数据位、0或1个校验位、1或2个停止位(高电平)。接收方在起始位下降沿开始采样,每个位采样一次或多次。波特率误差要控制在2%以内,否则采样点会偏移。
抓UART波形时,先测位宽,算出波特率。比如位宽8.68微秒,波特率就是115200。然后看数据位是否对齐,停止位是否完整。我遇到过UART通信时好时坏,抓波形发现停止位偶尔变成低电平,后来查出来是发送方在停止位期间被中断打断,导致帧错误。
5.4 I2S时序:WS、SCK、SD的相位关系
I2S的WS信号在左右声道间切换,通常WS低电平是左声道,高电平是右声道。数据在WS变化后的第二个SCK边沿开始有效。这个“延迟一个SCK周期”是I2S和SPI的关键区别,也是很多人配置I2S时容易搞错的地方。
抓I2S波形时,先看WS频率,应该是采样率。再看SCK频率,应该是采样率×位深×声道数。最后看SD数据是否在WS变化后稳定。如果左右声道数据反了,可能是WS极性设反了。
6. 常见问题与排查技巧实录
6.1 I2C总线锁死怎么救
I2C从机异常时可能把SDA拉低不放,导致总线锁死。解决方法有两种:一是硬件复位从机,二是主机手动发送9个SCK脉冲,让从机完成当前位传输后释放SDA。具体操作是:把SCL配置为GPIO输出,发送9个时钟,然后发停止条件。这个技巧我救过好几次现场。
6.2 SPI读不到数据先查什么
先查片选是否拉低,再查时钟是否有时钟,然后查模式是否匹配,最后查MISO是否有数据。如果MISO一直高阻,可能是从机没供电或片选没接对。如果数据错位,多半是模式设错。如果数据偶尔错,可能是时钟太快或线太长。
6.3 UART丢数据怎么定位
先看波特率误差,用示波器测位宽。再看中断优先级,UART接收中断是否被高优先级中断打断。然后看DMA配置,缓冲区是否够大,是否使能了空闲中断。STM32F103标准库UART DMA接收时,如果没使能空闲中断,最后一帧数据可能留在缓冲区里读不出来。
6.4 I2S音频有杂音怎么调
先查时钟配置,MCLK、SCK、WS频率是否正确。再查DMA缓冲区,是否太小导致欠载。然后查电源,音频编解码器供电是否干净。最后查PCB布局,I2S时钟线是否远离模拟信号。我遇到过ESP32-C3 I2S输出有咔哒声,最后发现是DMA缓冲区只有64字节,改成1024字节就消失了。
6.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| I2C无ACK | 地址错、上拉不对、从机没供电 | 抓波形看地址和上拉 |
| SPI数据错位 | 模式不匹配、时钟太快 | 对时序图查CPOL/CPHA |
| UART帧错误 | 波特率偏差、停止位不足 | 测位宽算波特率 |
| I2S左右声道反 | WS极性设反 | 查WS高低电平对应声道 |
| 片选不释放 | 软件片选未手动拉高 | 检查DMA完成中断 |
7. 工具与调试手段:没有逻辑分析仪怎么办
逻辑分析仪是调这四种协议的神器,但不是每个人都有。没有的话,示波器也能凑合看。I2C和UART用双通道示波器就能看个大概,SPI需要四通道,I2S需要三通道。如果连示波器都没有,可以用GPIO翻转法:在代码里关键位置翻转一个IO,用示波器或LED观察。
软件层面,Linux下可以用i2c-tools、spidev测试工具。Python调用USB模拟SPI接口也是一种办法,适合快速验证。FT232R、FT231X这类USB转UART芯片,装好驱动后就能当串口用,调试UART很方便。
对于FPGA开发者,SPI、I2C、UART的Verilog实现都是经典练手项目。I2C读写EEPROM的Verilog代码网上很多,但要注意时序参数要对着手册改。SPI主机控制器相对简单,但高速时要注意跨时钟域处理。
8. 选型决策树与个人经验收尾
如果非要给一个决策树,我会这样写:先看数据量,小数据低速选I2C,大数据高速选SPI;再看实时性,音频选I2S,调试选UART;最后看拓扑,多设备选I2C,点对点选SPI或UART。混合场景就组合使用,I2C管控制,I2S或SPI管数据。
我个人在实际操作中的体会是:不要迷信任何一种协议,也不要为了省引脚而牺牲性能。我见过太多项目因为选了不合适的总线,后期改板改到崩溃。前期多花十分钟评估,后期少花十天调试。另外,逻辑分析仪一定要买一个,哪怕是最便宜的,它能让你少熬很多夜。