拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

I2C、SPI、UART、I2S对比:选型、时序与调试指南

I2C、SPI、UART、I2S对比:选型、时序与调试指南 说实话我看到“i2c i2s spi uart对比”这个标题时第一反应不是背参数而是想起每次做板子选型时都会反复出现的一个场景一块小板上躺着四个接口引脚都有限数据量都不算大但到底用哪一条总线每次都会争半天。这四条总线被放在一起比较太正常了因为它们都是串行通信里的“老熟人”几乎出现在每一块嵌入式板卡上。可真正让我头疼的不是“谁更快”而是它们的同步方式、握手机制、拓扑结构和应用边界完全不是一回事。所以我写这篇想把这些东西一次性讲透尤其是时序细节和那些容易踩坑但又很少被写进文档的工程经验。1. 总线对比之前先把时钟、双工和拓扑这三个概念对齐1.1 四条总线各自的“本命工作”很多人一上来就问 I2C、SPI、UART、I2S 哪个快我觉得这个问题本身就问偏了。它们不是同一个赛道上的选手更像是不同工种I2C 是板级控制面SPI 是高速数据面UART 是跨盒子的点对点通道I2S 是音频电路的专用管道。I2C 最舒服的场景是挂一堆低速外设温度传感器、姿态传感器、EEPROM、RTC、电源管理芯片。这类器件只需要偶尔读几个字节、改几个寄存器数据量小但设备数量多两根线挂几十个从机很划算。SPI 则适合连续搬运数据比如刷屏幕、读写 Flash、采集高速 ADC时钟可以做得很高吞吐率比 I2C 高一个数量级。UART 做的是“盒子与盒子”之间的连接MCU 跟蓝牙模块通信、跟 PC 调试串口通信、跟 GPS 模块通信靠的就是 UART它不需要时钟线两根线就能双向收发。I2S 呢听名字容易被误认为和 I2C 是一家人实际它专门为音频服务传送的是连续采样流左声道右声道一帧一帧地搬。1.2 决定上限的从来不是线数而是同步方式和握手机制在对比具体参数之前先统一几个概念否则容易越看越乱。第一个是同步机制SPI、I2C、I2S 都是同步总线发送方会显式送一个时钟信号接收方跟着时钟边沿采样UART 是异步总线它不传时钟接收端靠预设的波特率自己去“猜”每个位从哪开始。第二个是双工方式SPI 和 UART 一般都能同时收发是全双工I2C 同一时刻只能在一个方向上传输是半双工I2S 的 DATA 线通常是一个方向但可以做多通道也可以做多条数据线来同时录音和播放。第三个是拓扑I2C 支持多主多从SPI 典型是一主多从UART 基本是点对点I2S 也是点对点为主。这个差异直接影响你板子上能挂多少个设备、要不要做总线仲裁、出了问题该怎么排查。把这三点想清楚很多困惑会立刻消失。比如有人问为什么 I2C 协议里又有地址又有 ACKSPI 却什么都没有因为 I2C 是真正意义上的“总线”多个设备共享两根线必须通过地址来区分、通过 ACK 来确认SPI 本质上是“主设备复制外设”每挂一个从机就要多一根片选线设备之间天然被 CS 隔离也就不需要地址了。2. I2C的慢不是缺点它的握手和寻址才是核心竞争力2.1 一个字节的完整时序START、数据位、ACK/NACK、STOPI2C 的物理层是开漏结构输出端只能把线拉低拉高靠外部上拉电阻。所有设备共用 SDA 和 SCL靠时序规则区分“起始”“数据”“应答”“停止”四种事件。先看最简单的写操作主机在 SCL 高电平期间把 SDA 从高拉低这叫 START随后主机按位发送 7 位从机地址再发一位 Rw 读写标志这 8 个 bit 由 SCL 低电平期间改变 SDA、SCL 高电平期间保持 SDA 来完成第 9 个时钟是高电平时的 ACK 槽从机如果在这个时刻把 SDA 拉低代表“我收到了请继续”。之后主机发寄存器地址、发数据字节每字节后都等一个 ACK最后在 SCL 高电平期间把 SDA 从低拉高产生 STOP整帧结束。读操作比写操作多一步“重启动”Repeated START先按写地址把寄存器地址写进去接着不产生 STOP而是再发一次 START这次地址末尾的 Rw 位改成 1。从机把数据放上 SDA主机每读完一个字节就回 ACK读到最后一个字节时回 NACK然后发 STOP。很多人第一次调 I2C 读失败问题往往出在这第 9 个 ACK 上你回了 ACK从机就认为你还要继续读结果多出一个字节你不回 ACK从机才结束这次读操作。2.2 上拉电阻、总线电容与速率拉胯的根因I2C 为什么跑不快很多人归咎于协议本身但根子在开漏结构。开漏输出的上升沿只能靠上拉电阻对总线电容充电完成电容越大、上升沿越缓信号到达逻辑高电平的时间就越长。所以要跑高速就得减小上拉电阻但电阻太小又会让低电平时电流过大对芯片输出级造成压力。经验值是 3.3V 系统、总线电容不太大时用 4.7kΩ 左右400kHz 快速模式常用 2.2kΩ1MHz 模式可能需要 1kΩ 级。如果你在板子上发现 I2C 波形上升沿像山坡一样平缓或者低速时正常、高速时偶尔丢字节优先检查上拉电阻和走线长度。总线电容有个实际经验上限大概 400pF 以内比较安全这也是 I2C 不适合跨长板通信的原因之一。I2C 典型速率从标准模式 100kHz、快速模式 400kHz、快速模式 1MHz到高速模式 3.4MHz但绝大多数嵌入式控制类器件用到 400kHz 就够不用盲目追求高速。2.3 多从机、多主仲裁以及SMBus/PMBus那些变种I2C 的寻址能力来自 7 位地址理论上可以挂 100 多个设备但实际板上能同时挂几十个就不少了。地址冲突是常事尤其是同一型号传感器用同一个地址时解决思路也很直接给其中一个设备改地址引脚电平或者用 I2C 多路复用器比如 PCA9548A 这类芯片把一组 I2C 总路线扩展到 8 路每一路单独挂一组同地址设备。多主仲裁是 I2C 比 SPI 优雅的地方。两个主机同时发数据时谁先发现 SDA 上自己发送 1 但实际采样到 0就立刻退让SCL 低电平时最长的那个主机决定低电平宽度多个主机因此能协商出一个干净的时序不会像两条线直接短接那样互相打架。另外要注意PMBus 和 SMBus 都基于 I2C但不等于 I2C。SMBus 增加了超时机制、最小时钟低电平时间、分组错误校验和 Alert 线PMBus 又在 SMBus 之上定义了电源管理命令集。很多 I2C 设备协议上“兼容 SMBus”但如果没有超时处理在某些主机上面就可能挂死反过来SMBus 设备挂在纯 I2C 主机上也可能因为缺少超时而出现一直等 ACK 的假死现象。至于“从机主动更新主机寄存器”这种需求纯 I2C 很难做通常要么给从机加一根中断脚要么走 SMBus 的 Host Notify 或 Alert 机制这点在选电源管理芯片时特别容易踩。3. SPI的高速代价没有握手没有地址全靠CS兜底3.1 四根线的分工与全双工原理SPI 比 I2C 少了很多“礼貌”老实说它更像一条搬运带主机把 SCLK 当节拍器CS 拉低表示开始招呼某个从机MOSI 上发数据MISO 上收数据全双工同时进行。因为输出是推挽结构不需要上拉电阻信号上升沿很陡所以速率可以拉到几十兆甚至上百兆。但高速是有代价的。SPI 没有标准地址机制每个从机必须独占一根 CS所以设备多了以后引脚消耗会急剧增加。SPI 也没有 ACK主机发出一串字节后根本无法通过协议本身知道从机有没有收到只能靠从机在 MISO 上回数据、或者在 mapped 寄存器里写状态位来间接判断。对 Flash、屏幕这类器件这点通常无所谓因为时序固定、错误率很低但对传感器或者 ADC就需要额外小心数据有效性和片选时序。3.2 CPOL/CPHA四个模式和逻辑分析仪上的第一道坎很多人第一次调 SPI 就栽在“模式”上。SPI 有四种模式由 CPOL时钟极性和 CPHA采样相位两个参数决定。CPOL0 代表 SCLK 空闲时为低CPOL1 代表空闲为高CPHA0 代表在第一个边沿采样CPHA1 代表在第二个边沿采样。组合起来就是常用的 Mode 0 到 Mode 3SPI模式CPOLCPHA空闲时钟电平数据采样边沿Mode 000低第一个边沿通常上升沿Mode 101低第二个边沿通常下降沿Mode 210高第一个边沿通常下降沿Mode 311高第二个边沿通常上升沿这里最容易翻车的地方在于不同芯片数据手册里的措辞往往不一致。有些芯片会直接写“数据在 SCLK 上升沿更新、在下降沿采样”而不说 CPOL/CPHA你要把它翻译成模式号再配置 MCU。我的习惯是抓一次逻辑分析仪波形把 SCLK 和 MOSI 放一起看如果数据在 SCLK 的上升沿稳定、随后被采样而 SCLK 空闲为低那就是 Mode 0如果数据在下降沿才切换就是 Mode 1。与其看手册猜不如直接看波形一次就能定死。3.3 硬件片选、软件片选以及CS毛刺的取舍SPI 的 CS 可以用 MCU 外设自动管理也可以用一个普通 GPIO 手动拉低拉高。硬件片选的好处是省 CPU只要配置好外设发数据时硬件自动拉 CS发完自动释放。但实际项目里硬件 NSS 并不总是好用尤其是一些 MCU 在分时复用 SPI 总线、或者外设配置还没就绪时CS 引脚可能会产生莫名其妙的毛刺把从机误触发。软件片选就是老老实实先拉低 GPIO等几十纳秒建立时间再启动 SPI 传输结束后再拉高。这样牺牲一点 CPU 时间但换来了完全可控的时序。特别是涉及到“CS 最小能做多少”这类问题时你翻芯片手册会看到 tcssCS建立时间这样的参数有些传感器要求 CS 拉低后至少等几百纳秒才能来第一个 SCLK。用硬件 NSS 时这个间隔往往不是你能精细控制的用软件 GPIO 加一个延迟就很稳。3.4 SPI外设里最值得玩的几种场景Flash、ADC与FPGA对接SPI 最常见的三个场景是读写 NOR Flash、刷 LCD/OLED、采集高速 ADC。刷屏这类应用对吞吐量要求高用 SPI 加 DMA 几乎是标配否则每个字节都占用 CPU 中断刷个 320x240 的屏能把 CPU 拖死。STM32CubeMX 里把 SPI 配成 DMA 模式只需要在传输完成中断里处理帧切换屏幕刷新效率会有本质提升。FPGA 和 ADC 的对接也经常走 SPI。FPGA 的好处是可以用硬件描述语言精确控制时序ADC 的 SCLK 频率、CS 到 SCLK 建立时间、SCLK 后沿到数据输出的延迟都能在寄存器级被控得很死。用 MCU 驱动高速 SPI ADC 时反而容易受限因为外设 FIFO 深度有限采样率稍高就会溢出。另外在 Linux 环境下RK3588 这类平台通过设备树配置 SPI常见的坑是 cs-gpios 没配好、spi-max-frequency 设得太高导致信号劣化、或者 spi-slave 节点下没指定正确的 spi-cpha/spi-cpol结果读出来的数据全是 0xFF 或乱码。4. UART靠起始位自同步波特率误差是它最容易翻车的点4.1 帧格式与波特率起始位、停止位如何完成自同步UART 是全双工异步串行它不发送时钟信号但并不是完全没有“时间基准”。它靠的是双方约定波特率同时利用帧格式里的起始位重新对齐。一帧典型的 8N1 结构是空闲状态下 TX 保持高电平发送数据时先拉低一个位时间这叫起始位接着按低位到高位发送 8 个数据位可选校验位最后至少拉高一个位时间叫停止位。接收端对 RX 线做 16 倍波特率采样检测到下降沿后把这一时刻当作起始位的中间点之后每隔一个位时间采样一次数据位。由于是一帧一帧对齐短距离通信中波特率误差在 ±2%~3% 以内通常都能稳定但长距离或者波特率特别高时误差累积效应会非常明显最后一个停止位最容易采错表现出来就是帧错误和乱码。所以 UART 调试的第一原则不是改程序而是先确认波特率对不对。很多单片机用内部 RC 振荡器跑 UART温度一变、电压一变实际波特率就偏了尤其是 115200 以上时明显。我的习惯是尽量给 UART 配外部晶振或者在单片机里打开波特率自动校准功能如果逻辑分析仪上看到每一帧起始位下降沿时间间隔不匀基本就是波特率有偏差。4.2 电平标准、USB-UART芯片和驱动排障UART 协议层的电平很单纯但现实世界中电平标准五花八门。MCU 引脚上是 3.3V TTL 电平PC 串口早期是 RS-232 的 ±12V 电平工业现场还经常用 RS-485 差分电平。直接用 RS-232 线去接 MCU 的 TX/RX轻则通信失败重则烧引脚。所以现在调试几乎都是通过 USB-UART 芯片转换FT231x、FT232R、CH340、CP2102 这类芯片做了 TTL 和 USB 之间的电平转换。FT232R 和 FT231x 的驱动问题也是老生常谈。Windows 里偶尔会看到设备管理器报“代码 12找不到足够资源”这种大概率不是芯片坏了而是 USB 控制器的资源被其他设备占满或者旧驱动没卸载干净。处理办法是先拔掉设备卸载驱动残留换一个 USB Host 控制器口重新插必要时重启电脑让系统重新枚举。如果设备还是不正常再看是不是外接 USB HUB 供电不稳导致枚举失败。UART 调试还有一个特别低级但特别常见的坑GND 没共地。信号线接对了但两边地没连波形看起来像一串噪声通信时好时坏这种情况我至少碰到过五六次。4.3 16550标准、FIFO与设计遗产很多人觉得 16550 是上个世纪的东西但其实只要去接触 USB-UART 芯片、PCIe 串口卡、FPGA 里的 UART IP就会发现它们几乎都在模拟 16550 的寄存器接口。16550 定义了发送保持寄存器、接收缓冲寄存器、中断使能寄存器、线控寄存器、状态寄存器这些标准布局还引入了 16 字节 FIFO 来降低每字节一个中断对 CPU 的冲击。在单片机上没有 16550但设计思想无处不在TXD/RXD 的 FIFO 深度、发送空中断、接收超时中断、溢出标志都是从那套体系继承来的。比如你用 DMA 接收 UART 不定长数据关键点在“空闲中断”或者“串口超时中断”对应到 16550 时代就是接收 FIFO 里等若干字符没有后续数据时发出超时中断。理解这层血脉再看各种串口驱动代码就不会觉得它们长得像但名字各不同了。UART 转 GPIB 这类工具也是一样的逻辑适配器内部的串口协议收到 SCPI 命令后转成 GPIB 总线时序只要波特率、终止符LF 还是 CRLF对了仪器控制就能稳定跑。5. I2S是个音频专用物理层别把它当I2C的进化版5.1 三条线一个帧BCLK、WS、DATA的时序关系I2S 的全称是 Inter-IC Sound它是给音频编解码器、DAC、ADC、数字功放之间传 PCM 数据流用的。它和 I2C 完全没有继承关系只是名字像而已。I2S 最基础的是三条线BCLK 位时钟、WS 声道选择、SD 串行数据。飞利浦的经典 I2S 规范里WS 高电平对应左声道低电平对应右声道SD 数据在 WS 边沿翻转后延迟一个 BCLK 周期才开始发送第一个 bit。当数据位是 16 位时BCLK 上每个声道周期内有 16 个时钟沿对应 16 个数据位剩余时钟周期可以是零填充。还有左对齐和右对齐等变体左对齐要求数据紧贴 WS 边沿开始右对齐则是数据位对齐到最后一个 BCLK 周期。因为 I2S 没有地址也没有应答本质上它更像一个“麦克风到喇叭的专线”主机负责送时钟和声明声道从机只要按节奏往 DATA 线上放数据就行。5.2 采样率、位深与BCLK的计算从48kHz一路推到3.072MHzI2S 的时钟关系用公式一算就通BCLK 采样率 × 声道数 × 每个声道的位深。比如标准 48kHz、双声道、16bit就是 48000 × 2 × 16 1.536MHz。如果用 32bit 的 slot 来传 16bit 数据BCLK 就变成 48000 × 2 × 32 3.072MHz。很多音频 codec 内部处理喜欢用定长 slot宁可在 BCLK 里留空位也要让每个声道占满 32bit这样左右声道边界清晰DMIC 和 TDM 多通道扩展也方便。除了 BCLK 和 WS不少 codec 还会要求一个 MCLK 主时钟通常是采样率的 256 倍或 512 倍比如 48kHz 配 12.288MHz。MCLK 用于 codec 内部的 Delta-Sigma 调制器和数字滤波器没有它芯片可能无法工作或者产生明显的时钟抖动噪声。做音频时MCU 内部 PLL 能不能精确分出 12.288MHz 这种频率直接关系到方案能不能用很多 SoC 为此专门提供音频 PLL而不是简单拿系统主频去分频。5.3 实际项目里的I2S坑极性、主从、爆音排查I2S 调起来比纯数字总线更磨人因为问题经常是“能工作但音质不对”。最常见的现象有三个左右声道反了、声音像机器人、偶发爆音。左右声道反多半是 WS 极性配反或者硬件走线把 LRCK 接到模块的另一个声道引脚上。把逻辑分析仪挂在 WS 和 DATA 上看 WS 高电平期间 DATA 第一个有效位是不是对应左声道一眼就能确认。声音变调往往不是因为 I2S而是播放端的采样率和 codec 实际配置不一致比如播放软件输出 44.1kHzI2S 外设却按 48kHz 生成 BCLK。爆音则要看数据帧是否连续。I2S 的 DMA 传输如果出现下溢也就是 FIFO 已经空了DAC 还在等下一个 BCLK 数据就会产生咔嚓声。解决思路是加大缓冲区、降低中断延迟、或者用 DMA 双缓冲也要确认 BCLK 极性是否正确如果在错误的边沿采样数据低位数据会错位听到的就是特殊噪声。ESP32-C3 这类低成本 SoC 做 I2S 输出外接 MAX98357A 功放时数据线接错或者 WS 极性不对功放会一直输出刺耳噪声而逻辑分析仪上看到的 BCLK 频率却完全正常这时候就要回到 WS 和 DATA 的相对相位去排查。6. 四总线横向对比与选型逻辑一张锚点表解决大多数问题6.1 一张表看清四总线底细把 I2C、SPI、UART、I2S 放在一张表里看很多特瞬间就清楚了对比项I2CSPIUARTI2S全称Inter-Integrated CircuitSerial Peripheral InterfaceUniversal Asynchronous Receiver/TransmitterInter-IC Sound最少线数2SDA、SCL4SCLK、CS、MOSI、MISO2TX、RX3BCLK、WS、DATA同步方式同步同步异步同步双工半双工全双工全双工单向数据为主可扩展多线拓扑多主多从总线一主多从点对点点对点设备寻址7位/10位地址CS片选无无应答机制ACK/NACK无无/校验位无典型速率100k/400k/1MHz/3.4MHz几十兆到上百兆9600bps~数MbpsBCLK通常几MHz主要用途低速配置、传感器、EEPROMFlash、LCD、ADC、FPGA配置调试口、蓝牙/GPS模块、工业总线音频codec、DAC、数字功放这张表只是锚点真正的选型决策还要看具体项目约束。比如 I2C 的速率上限看着低但读一个温度传感器只需要几字节根本不在乎SPI 速率再高如果要从机回传数据还得暴露出没有握手的问题。6.2 选型思路五个问题快速锁定答案我实际做选型时一般按这五个问题过一遍基本不会跑偏数据量是多大如果每次只读几个寄存器I2C 完全够如果要连续刷一帧一帧的图像SPI 优先。板上要挂几个设备设备多且都要共用两根线I2C 合适设备不多但每个都需要独立高速率高带宽SPI 合适。需不需要双向同时传输需要全双工实时交互比如传感器同时上传数据又接收配置SPI 或 UART 更方便I2C 的半双工在这种场景下会明显拖累。通信距离多远板内几厘米I2C、SPI 随便选跨板走线或机箱之间UART 加电平转换最现实I2C 跨接不好总线电容一大就完蛋。是普通数据还是音频流音频流直接选 I2S不要试图用 SPI 去硬凑环绕声和 TDM 多声道都麻烦。前四个问题确定后剩下的事就是看具体芯片外设资源。MCU 上 SPI 和 I2C 外设往往都有一堆复用引脚引脚冲突才真正决定最终选择。6.3 热搜词里的变种协议逐个说清楚平时能看到不少围绕这四总线衍生出来的热搜问题其实大多数是同一个底层问题换了种问法。比如 PMBus 和 I2C 的区别归根结底是 PMBus 基于 SMBus在普通 I2C 物理层上加了超时限制、PEC 校验、Alert 线、地址重配置机制。Linux 下管理 PHY 不用 MDIO 而是用 I2C则是有些 PHY 芯片把所有寄存器暴露在 I2C 接口上MDIO 可能没接设备树里挂一个 i2c-client 然后通过 regmap 读寄存器和在 MDIO 总线上读是同一套思想。I2C 从机主动更新主机寄存器普通 I2C 做不到SMBus 的 Host Notify 或者额外 IRQ 才是常规方案。UART 转 GPIB 则是典型的 SCPI 命令桥接重点在波特率和终止符而不是协议本身。这些变种都在强调同一件事协议不是孤立存在的它对应的是特定物理层、特定应用场景、特定握手需求和特定的调试手段。你把基础四条总线吃透了再去看任何变种方法都是通用的。7. 我的调试习惯先抓波形再动代码最后查寄存器7.1 逻辑分析仪上必须盯死的那几个点我做嵌入式通信调试这么多年最想分享的一条经验是代码报错之前先看波形。很多人遇到通信失败第一反应是改寄存器配置、换库函数参数折腾半天还找不出问题其实问题就在线上。抓 I2C 时要确认 START 条件完整、地址字节对不对、每个字节后有没有 ACK 低电平如果 ACK 一直为高说明从机根本没起来或者地址不对。抓 SPI 时先确认 CS 拉低后到第一个 SCLK 的建立时间再看 SCLK 空闲电平是低还是高最后确认数据是在哪个边沿变化模式认错解码器解出来的数据完全对不上。抓 UART 时波特率设置错一秒就能看出来因为起始位宽度和预期完全不对。抓 I2S 时重点看 BCLK 的周期是不是你算出来的那个值WS 翻转频率是不是等于采样率以及 DATA 和 WS 之间的相位关系是否符合 codec 手册。逻辑分析仪的采样率也很有讲究至少要达到被测信号最高频率的 4 倍建议 8 倍以上否则窄脉冲会被漏掉。I2C 的 STOP 条件、SPI 的片选毛刺、UART 的起始位下沿这些信息都需要足够的采样率才能看清。7.2 我自己的几个“吃过亏才记住”的原则第一个原则是通信速率宁低勿高。I2C 能用 400kHz 就不要拉到 1MHzSPI 能用 10MHz 就不要拉到 40MHz尤其在手飞线和面包板阶段。信号完整性不好的时候通信不是立即失败而是“偶尔失败”这种问题排查最耗时间。第二个原则是共地和电源去耦永远先检查。通信乱码不一定是协议配置问题经常是地电位不一致、或者电源纹波把逻辑电平搞乱了。先看示波器上高电平是不是干净再怀疑软件。第三个原则是善用芯片自带环回测试。MCU 的 UART 和 SPI 通常都有环回模式或简单地把自己 TX 接 RX 做自发自收先用这个验证外设本身没问题再连外部设备。这样能把问题精确切割到“单片机侧”还是“外部器件侧”。最后提醒一句四条总线里没有“万金油”任何一块板子上它们都是互相配合的角色。我现在的选型习惯是先把数据通路画出来哪条路是低频控制、哪条路是高吞吐搬运、哪条路是跨盒通信、哪条路是音频流再决定走 I2C、SPI、UART 还是 I2S。这样分配下来引脚不打架、带宽不浪费、调试也轻松得多。
返回列表