尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

嵌入式必知:I2C、SPI、UART、I2S四大串行总线对比与避坑指南

嵌入式必知:I2C、SPI、UART、I2S四大串行总线对比与避坑指南 第一次用逻辑分析仪同时抓这四条总线的时候我才真正体会到所谓的对比不能只看速率表格。同样是串行通信I2C、I2S、SPI、UART从线数、时钟来源到数据格式几乎没有一个地方是相同的。这篇文章想做的事很简单就是把这四种嵌入式开发里出镜率最高的通信协议放到一起掰开揉碎讲清楚它们各自的原理、适用场景和真实调试中会遇到的坑。无论你是刚入门选型还是做主控板调不通中断这份对比都能让你少走一些弯路。很多人问为什么会有这么多协议就不能统一成一种吗答案是不能因为四种协议解决的问题方向完全不一样。UART 解决的是两个设备隔着不太远的距离可靠通信SPI 解决的是主控和从机之间快速吞吐大量数据I2C 解决的是只用两根线挂一大堆低速外设而 I2S 解决的是数字音频的节奏怎么严格对齐。真正的区别不在哪个快哪个慢而在于各自的适用边界。下面我从机制开始拆最后给选型方法和踩坑记录。1. 先搞明白这四条总线本质上在解决一件什么事1.1 从一根线的串行传输说起并行总线现在还在用的场景不多了嵌入式里绝大多数通信都是串行——数据按位排着队走一根线或几根线。为什么因为并行线在高速下会有严重的信号偏移和串扰问题十六根线同时翻转PCB 布局和时序约束能把人逼疯。串行虽然位传输速率低一点但线少、成本低、可靠而且可以通过提高时钟频率把总吞吐做得很高。这四种协议都属于串行通信但它们对时钟的态度完全不同。SPI 和 I2S 是同步串行发送方和接收方共用一根时钟线边沿到了就采样双方不用猜。UART 是异步串行没有时钟线全靠双方预先约好一个波特率靠起始位的下降沿来对齐节奏。I2C 是同步和异步的混合体它有 SCL 时钟线但挂同一总线上的多个设备要能协调占用总线因此还带一套地址、应答和仲裁机制。这个本质区别决定了后面几乎所有行为差异。1.2 四种协议的社会分工和选型直觉我用一个不算严谨但好记的比喻来建立直觉。UART 像两个人打电话一条线给 A 说、一条线给 B 说全程独占谁也不用等别人。SPI 像老板叫某个下属来办公室单独汇报老板是主下属是从老板说一句、下属答一句全双工效率很高但老板得一个个叫线也得多拉几根。I2C 像一群人开电话会议所有人都并线在同一根线上每个人有分机号主持人喊到谁谁说话说完还要回一声收到因为人多了要避免同时开口所以协议内置了应答和仲裁规则。I2S 则像一支乐队的指挥指挥棒BCLK和总指挥WS 声道选择同步所有乐手数据一声接一声按严格的节拍输出不需要地址不需要应答因为它是点对点的音频流。从这个比喻就能看明白选型不是挑一个最好的而是找一个最不别扭的。低速率、设备多、引脚紧张优先 I2C高速率、大数据块、从机数量少优先 SPI两个设备之间简单传数据没有任何从机结构用 UART 最直接只要你传输的是音频数据流I2S 无可替代。下面把这四种协议的机制拆开。2. 四种协议的底层工作机制逐项拆解2.1 I2C两根线挂一总线靠地址和设备自己说话I2C 由 Philips现在的 NXP在 1982 年提出全称 Inter-Integrated Circuit。它只有两根信号线SDA数据线和 SCL时钟线。这两根线是开漏结构必须外接上拉电阻空闲时都被拉高。开漏意味着设备只能把线拉低不能主动推高所以高电平完全靠上拉电阻提供。理解了这一点你就能理解为什么 I2C 适合挂多个设备且不需要额外的片选线——所有设备都并在同一对线上。一次 I2C 传输的完整过程是这样的起始条件SCL 为高时SDA 从高拉低。这个下降沿告诉总线上所有设备要开始了一帧传输。地址阶段主机在 SCL 的每个高电平期间通过 SDA 送入一个字节前 7 位是从机地址第 8 位是读写标志。总线上的每个从机都会检查这 7 位是不是自己的地址。应答阶段第 9 个时钟脉冲被寻址的从机把 SDA 拉低表示我在继续。没有设备应答主机就应在 SCL 高电平检测 SDA 保持高然后可以发停止条件或者重试。数据阶段继续发若干字节每字节后面都跟一个 ACK 位。停止条件SCL 为高时SDA 从低拉高。数据采样有个关键规则SDA 电平必须在 SCL 为高时保持稳定只有在 SCL 为低时 SDA 才允许变化。很多人第一次用 GPIO 模拟 I2C 时踩坑就是因为在 SCL 拉高后立刻改 SDA导致从机采到错误数据。正确的软件实现是先改 SDA再拉高 SCL再拉低 SCL最后再改下一次数据。I2C 常见速率档位标准模式 100 kbit/s、快速模式 400 kbit/s、快速 模式 1 Mbit/s、高速模式 3.4 Mbit/s。实际项目里 100 kbit/s 和 400 kbit/s 用得最多。典型外设包括温湿度传感器SHT30、AHT20、气压计、EEPROM 芯片、触摸屏控制器比如 GT911、实时时钟等。它是嵌入式里每比特代价最低的板内总线之一。2.2 SPI一根时钟带四根线全双工高速传输SPI 由 Motorola 提出全称 Serial Peripheral Interface。标准四线分别是SCLK串行时钟由主机产生决定通信速率。MOSI主出从入主机发送数据给从机。MISO主入从出从机发送数据给主机。CS/SS片选低电平有效。主机拉低哪个从机的 CS哪个从机就被激活。SPI 没有地址的概念靠的就是 CS 这根独立的片选线。你要挂四个从机就得准备四根 CS。也正因为这样SPI 非常灵活主机可以随时决定跟谁通信不需要像 I2C 那样发送地址字节协议本身的开销小得多。SPI 是全双工的。在每一个 SCLK 周期内主机通过 MOSI 发一位同时从机通过 MISO 回一位。所以它的吞吐率很高常见 SCLK 在 10 MHz 到几十 MHz性能好的器件甚至能跑到 100 MHz 以上。但这也带了麻烦SPI 没有应答机制。主机写一个寄存器从机有没有收到SPI 协议本身完全不关心。你必须通过读回寄存器确认内容对不对这个特点后面避坑章节还会重点提。SPI 有四种工作模式本质是时钟极性和相位组合。CPOL 决定时钟空闲时是低电平还是高电平CPHA 决定数据在第一个边沿采样还是第二个边沿采样。对应关系如下表模式CPOLCPHA空闲电平采样边沿常见场景Mode 000低上升沿最常见很多 Flash、屏幕、传感器Mode 101低下降沿部分音频/协议芯片Mode 210高下降沿部分特定器件Mode 311高上升沿很多 SD 卡协议如果你的主控和从机模式不匹配会出现一个极其迷惑的现象数据完全错位读上来的东西是一堆乱七八糟的比特。调试遇到这种问题第一反应就应该是核对四模式。2.3 UART没有时钟线的异步串口靠双方约定速率UART 的全称是 Universal Asynchronous Receiver/Transmitter它定义的是一种异步串行帧格式。没有 SCLK、没有 SDA、没有 CS通常就两条线TX 和 RX交叉连接。通信的前提是双方事先约定好完全相同的波特率比如 9600、115200、460800 等。UART 帧的基本结构空闲状态为高电平一帧从起始位开始起始位是一个位宽的低电平然后是数据位通常 8 位LSB 在前接着是可选的校验位最后是停止位1 或 2 个位宽的高电平。接收方就是靠检测到高到低的跳变认定起始位来了然后按照约定的波特率在每个位的中间点采样一次避免采样到信号抖动区域。正因为是异步的双方的时钟误差非常关键。一般 UART 要求波特率误差控制在 ±2% 到 ±3% 以内。举个实际例子用 24 MHz 晶振跑 115200 波特率分频系数是 24000000 / 115200 ≈ 208.33。取整 208 后实际波特率是 24000000 / 208 ≈ 115384误差约 0.16%完全没问题。但如果你用一颗非标准晶振而分频系数取整误差很大可能出现近距离没问题、稍微拉长线就乱码的情况。UART 的应用面很广调试日志输出、GPS 模块、蓝牙模块、RS-485 总线、老式工控设备。它最大的优势是简单、点对点、跨设备通用缺点是只能一对一且速率上限通常比 SPI 低。RS-232 电平与 TTL 电平还不一样RS-232 用正负电压表示逻辑TTL 用 3.3V/5V 和 0V 表示逻辑拿 TTL 和 RS-232 混接轻则通信失败重则烧引脚。2.4 I2S为音频量身定做的同步串行接口I2S 全称 Inter-IC Sound是 Philips 为数字音频设备之间的音频数据传输定义的接口。它和前三者的最大区别是它不传控制命令只传音频数据流。典型场景是单片机接音频 Codec、ADC、DAC、数字麦克风比如在 ESP32 上接一个 I2S 解码板播放 WAV 音乐或者用 I2S 麦克风做语音采集。I2S 的核心线有三根BCLK位时钟每个脉冲对应一个音频数据位。WS/ LRCK声道选择信号。低电平通常表示左声道高电平表示右声道或者是反过来看具体约定。SD串行数据线用来传音频采样值。它的时序特点是数据先发高位MSB first而且数据和 WS 边沿之间通常有 1 个 BCLK 的延迟。这是标准 I2S 格式此外还有左对齐右对齐DSP/TDM 等变体不同 Codec 手册里都会写明支持哪种对齐方式。举个具体数字说明速率关系。如果你的音频系统是 48 kHz 采样率、16 bit 位宽、双声道那么 BCLK 48000 × 16 × 2 1.536 MHz。如果位宽是 24 bitBCLK 2.304 MHz。很多 I2S 器件还要求额外的主时钟 MCLK一般取采样率的整数倍比如 128 × 48 kHz 6.144 MHz256 × 48 kHz 12.288 MHz。MCLK 用于内部的 Delta-Sigma 调制器和时钟恢复配置不对会导致音频设备完全不出声或者发出刺耳噪声。I2S 没有地址、没有 ACK、没有仲裁本质上是一条流式单向或双向管道。它既可以配置为主机模式由主控产生 BCLK 和 WS也可以配置为从机模式让外部 Codec 做主。选主从模式要看系统中哪个设备更容易提供稳定时钟这里面有讲究后面踩坑部分再说。3. 把四个协议放同一张表里关键差异一眼看清3.1 关键维度横向对比总表维度I2CSPIUARTI2S信号线数量2SDA、SCL4SCLK、MOSI、MISO、CS2TX、RX3~4BCLK、WS、SD、可选MCLK同步/异步同步有 SCL同步有 SCLK异步无时钟线同步有 BCLK时钟来源主机产生 SCL主机产生 SCLK双方各自约定波特率主机产生 BCLK/WS拓扑结构多主多从总线一主多从每从一根 CS点对点点对点可TDM扩展寻址方式7位/10位地址 ACK硬件片选 CS无寻址无寻址应答机制有 ACK/NACK无应答无应答无应答全双工否半双工是是可单向也可配置双数据线典型速率100k~3.4Mbit/s几十 MHz 甚至更高通常 9600~921600 bit/sBCLK 通常在 1~4 MHz抗干扰能力一般依赖上拉质量较好推挽输出TTL 一般RS-485 很强较好但时钟敏感典型应用传感器、EEPROM、触摸屏Flash、屏幕、SD卡、ADC调试口、GPS、蓝牙、RS-485音频 Codec、数字麦克风、DAC这张表基本上把所有关键差异都放出来了。但我必须强调表格只能帮你快速回忆真正的理解在于那些表格里没写的坑。比如 I2C 半双工意味着同一时刻只能有一个方向的数据流动你要读一个传感器寄存器得先写寄存器地址、再切换方向读数据这个过程本身就有协议开销。SPI 全双工看起来是同时收发但在实际项目里大部分场景并不是真的利用双向带宽但它的优势是没有方向切换的延迟所以特别适合连续大块数据读写。3.2 表中几个看似不起眼却决定成败的差异第一同步 vs 异步决定了你调试时看什么波形。SPI 和 I2S 的数据有效性永远和时钟边沿挂钩只要时钟还在数据就能被采样UART 则完全依赖起始位和波特率线上一旦出现长时间干扰接收端可能反复尝试同步表现为连续乱码。调 UART 先看电平空闲状态是否是稳定的高电平再看起始位下降沿是否干净调 SPI/I2S 则优先看时钟频率是否在器件支持范围、数据建立时间是否足够。第二有没有应答机制决定了错误能不能被立刻发现。I2C 自带 ACK从机没收到或不响应主机在第九个时钟就能感知所以写命令失败通常比较容易暴露。SPI 没有 ACK表面上读写正常但可能从机根本没接到有效的 CS 低电平或数据线被别的信号干扰。我有一次调 STM32 的 SPI 读 Flash读回来的 ID 永远是 0xFF时钟和数据波形看着都对最后发现是 CS 引脚初始化成开漏输出拉低能力不够换推挽输出就好了。这就是典型的没有 ACK 的排查成本。第三I2S 的时钟频率不是随便定的它跟采样率严格挂钩。不像 SPI 你可以随时把 SCLK 从 1 MHz 调到 10 MHzI2S 的 BCLK 和 WS 必须跟采样率和位宽匹配否则音频采样率会发生偏移。这个约束让 I2S 的调试变得很敏感——你改了一个采样率MCLK 也要跟着改你用了外部晶振给 Codec 做主时钟主控恢复出来的音频流有可能出现周期性的卡顿或爆音本质是主从数据速率不匹配。4. 选型不靠背参数靠反向问需求4.1 三个真实项目场景的选型过程我拿三个我实际做过的项目来说明选型逻辑。第一个项目是做一个温湿度采集小模块主控是一颗国产 Cortex-M0 单片机要从板上读一个 SHT30 传感器还要挂一个小的 EEPROM 存校准参数。这个场景的典型选择就是 I2C。为什么传感器输出的是几字节温度湿度数据一次读取几十个字节顶天了对速率完全没压力而 I2C 只需要两根线引脚占用最少还能让传感器和 EEPROM 共用一条总线。你说 SPI 行不行也行但每个设备要多一根 CS 引脚而且对这么低速的应用完全没必要。第二个项目是驱动一块 1.8 寸 TFT 彩屏分辨率 128×160RGB565刷一帧大约要 128×160×2 40960 字节。如果我用 I2C 的 400 kHz 速率刷屏理论需要 40960×8÷400000 ≈ 0.82 秒实际带上地址和 ACK 开销要超过 1 秒这个刷新率会让人觉得屏幕卡到没法用。换成 SPISCLK 跑到 24 MHz光传输就是 40960×8÷24000000 ≈ 13.7 毫秒加上 DMA 的辅助刷屏完全顺滑。所以选屏场景不看别的先算数据量再算可接受延迟直接决定协议。第三个项目是做一个简易录音笔需要 I2S 数字麦克风采集音频再通过 I2S DAC 播放。这个场景没有任何悬念必须用 I2S因为数字音频的时序精度要求在这里用 SPI 去模拟音频时序会让你为保持每个采样点间隔完全一致付出巨大代价。I2S 天生就是为音频流设计的WS 自动切换左右声道BCLK 按位驱动数据线主控只需要用 DMA 搬数据。所以协议选型往往不是横向比较谁更好而是纵向看这个应用的核心诉求是什么。4.2 我自己总结的五问选型法如果你拿不准选哪个可以按这五个问题往下走传的是什么连续音频流选 I2S大块数据读改写选 SPI分散的小命令小参数选 I2C任意文本/字节流选 UART。要挂多少设备、还剩几根引脚设备多且引脚紧张I2C 优势大设备少且需要高吞吐SPI 更优点对点且简单UART 最省心。需要从机主动上报吗I2C 支持多主和从机时钟拉伸部分场景下从机可以通过某种机制通知主机但整体上主从模型为主。UART 天然支持设备任意时刻发送数据比如 GPS 一直往外吐 NMEA 语句所以 GPS 模块用 UART 最自然。距离远不远板内几厘米I2C/SPI/I2S 都没问题板间接线超过几十厘米甚至几米UART 转 RS-485 才是可靠方案。SPI 拉长线会因信号完整性问题出现错误I2C 长线加上大上拉电阻会导致边沿变慢、时序超标。对成本和通用性的要求非常简单、几十厘米内、只想点对点传数据UART 是所有 MCU 的标配几乎不会出问题想在总线上扩设备又不想重新布线I2C 能让你用最少的线挂最多的芯片。这套方法不保证你选到最优解但能帮你避开最低级的错误用 I2C 去刷屏、用 UART 去传大文件、用 SPI 去接数字麦克风——这些组合不是不能跑而是会让你在调试和性能上付出不必要的代价。5. 实测避坑四条总线各自的老玩家才知道的坑5.1 I2C上拉电阻和地址冲突是两大入门杀手I2C 最常被忽略的问题就是上拉电阻取值。电阻太小比如 100ΩSDA 拉低时电流过大低电平可能达不到器件阈值还会增加功耗电阻太大比如 100kΩRC 常数太大SCL 和 SDA 的上升沿变得很缓在高速模式下可能被判定为时序违规。一般 3.3V 系统取 2.2kΩ 到 4.7kΩ5V 系统取 4.7kΩ 到 10kΩ总线上设备越多、线越长上拉电阻要适当选小一些去补偿电容。其次是地址冲突。I2C 的 7 位地址空间里某些地址是保留的比如 0x00 是 general call而很多传感器厂商把默认地址做成 0x48、0x4E 这类常见值。如果一块板子上挂了两颗相同型号的传感器而它们又没有可配置的地址引脚就会撞车主机发命令时两个芯片都会应答。解决方案包括用 TCA9548A 这样的 I2C 多路复用器或者干脆换一颗支持地址配置的器件。排查地址冲突时用逻辑分析仪抓一下 ACK 阶段两条不同器件的总线上如果看到同一个地址出现了两次 ACK基本就可以断定撞车了。还有一个坑叫时钟拉伸。有些从机处理速度慢会在某个阶段把 SCL 拉低来暂停通信让主机等它准备好。支持时钟拉伸的是标准行为但如果你用的 MCU 硬件 I2C 外设不支持或者你的软件实现里没有处理 SCL 被外部拉低的情况就可能直接超时死掉。遇到这种问题很多人的第一反应是死循环了实际上应该怀疑主机没有正确响应从机的时钟拉伸。5.2 SPI片选毛刺和读回校验缺一不可SPI 的 CS 片选线是低电平有效这就带来一个毛刺问题。如果你的主控在初始化时 CS 引脚还没配置成推挽输出或者你用的是软件控制 CS片选和数据通过 GPIO 分别控制飞线干扰或代码执行顺序不当从机端可能收到一个极窄的低脉冲误以为主机要开始通信。解决方法是CS 引脚尽快配置为推挽输出并默认拉高最好利用硬件外设的自动 NSS 功能如果一定要软件 CS要在拉低 CS 后加至少几百纳秒的等待保证从机稳定进入工作状态。第二件事是没有 ACK 不代表不检查。SPI 写数据后一定要习惯性地读回寄存器验证。比如写 Flash 的状态寄存器、写音频 Codec 的控制寄存器写入后读回判断是否一致。我做过一个项目SPI 写 ADC 的配置寄存器示波器看波形完全正确但 ADC 输出就是不对最后读回寄存器发现配置寄存器的 D3 位没有生效原因是该位是保留位要求写固定值手册里小字写了must be written as 1我没看。这种问题只有读回校验才能快速暴露。第三是总线上多个从机的 MISO 要三态。SPI 的 MISO 是所有从机共用的如果某个从机的 MISO 不是高阻态输出而它的 CS 又没被拉低就会和当前活跃从机的 MISO 打架导致数据错误。选从机芯片时看一下 datasheet 的 MISO 是否支持高阻态不支持就必须在 CS 电路上做额外处理或者每个从机串电阻隔离。5.3 UART波特率算不对一切白搭UART 看似简单但波特率误差是个隐蔽的坑。很多低成本 MCU 的波特率发生器是整数分频的如果你的系统主频不是波特率的整数倍实际波特率就会有偏差。通讯距离短、时钟容限大时看不出来一旦线一长、双方时钟误差叠加接收端就出现误码。排查方法是查看芯片参考手册里波特率寄存器的分频表计算最接近实际值的那一档必要时改用系统 PLL 提供精确时钟。还有一个高频问题TTL UART 和 RS-232 混接。TTL 电平逻辑1是 2.4V 以上0是 0.5V 以下RS-232 则是1为 -3V 到 -15V0为 3V 到 15V。如果把两个 TTL 设备和 RS-232 设备直接连线电平范围不对等芯片可能识别不了。解决办法很简单中间加一颗 MAX3232 之类的电平转换芯片。接 RS-485 则要配置方向控制引脚半双工模式下方向和收发切换的时序要处理好A/B 线不能接反终端电阻也不要随便加。5.4 I2S主时钟和左右声道对齐搞错就出怪声I2S 的问题往往不是完全没有声音而是有声音但不对。最常见的现象是播放出来的声音有持续爆音、左右声道错乱、或者采样率漂移。爆音的根源多半是 MCLK 没配好。很多 Codec 需要一个稳定的主时钟 MCLK通常是 256 × fs 或 512 × fs。如果你的主控给 I2S 外设的时钟源算出来不是整数比输出采样率就会偏离标称值内部的 Delta-Sigma 调制器工作不正常噪声就出来了。左右声道对齐同样关键。不同的音频 Codec 可能要求标准 I2S 格式、左对齐格式或右对齐格式。标准 I2S 的数据位会比 WS 跳变晚一个 BCLK左对齐格式则是数据和 WS 同时变化。如果你在代码里用标准 I2S 发而 Codec 配置成了左对齐格式声音听起来可能是破的或者左右声道混在一起。调试 I2S 时逻辑分析仪同时抓 BCLK、WS 和 SD 三根线用每 1 bit 对应 BCLK 一个周期、每 16 bit 对应 WS 半周期这个节奏去对很快就能看出来是哪种格式。还有一个容易被忽视的点主从模式的选择。如果外部 Codec 需要自己产生时钟并让主控去跟它同步那主控 I2S 就要配成从模式。从模式下主控不能随便暂停 DMA 或改变时钟否则可能出现 FIFO 溢出或下溢表现为忽快忽慢。我自己做 ESP32-C3 接 I2S DAC 播放音频时就把 I2S 外设配成主模式由主控统一产生 BCLK/WS这样时钟链路最简单软件上少很多麻烦。5.5 通用习惯抓波形永远比猜更快最后说一条贯穿所有协议的经验无论你调哪条总线先把逻辑分析仪或示波器接上去抓真实波形。我调试的固定动作是先看波形再写代码。I2C 抓起始条件、地址 ACK、停止条件SPI 抓 CS 下降沿到第一个 SCLK 上升沿的间距、模式匹配情况UART 抓起始位下降沿和停止位高电平I2S 抓 BCLK 和 WS 的关系。波形不会骗人它能帮你把代码问题和硬件问题快速区分开。这四条总线发展到今天都已经是极其成熟的技术随便一颗单片机都内置了对应的硬件外设。但它们不是替代关系而是互补关系UART 管点对点、SPI 管高速率、I2C 管多设备、I2S 管音频流。你越早建立这种按需求选型的意识后面踩的坑就越少。哪怕只是记着先算数据量、再看设备数、最后想时钟来源这三句话你在项目里做通信方案时就能少纠结很多。
返回列表