news 2026/9/24 9:10:33

I2C、SPI、UART、I2S总线选型指南:从原理到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
I2C、SPI、UART、I2S总线选型指南:从原理到实战

1. 四种总线摆在面前,先搞清楚它们各自在解决什么问题

做嵌入式开发的人,早晚都会遇到同一个场景:手头一块主控板,要接传感器、存储器、显示屏、调试串口,翻遍芯片手册发现能用的通信外设就那么几组,于是开始纠结——这个器件到底挂I2C还是SPI?那个模块用UART行不行?音频数据走I2S会不会更合适?

我刚开始做硬件驱动那几年,最怕的就是选错总线。选错了轻则速率上不去,重则整个方案推倒重来。后来踩的坑多了,慢慢总结出一个判断逻辑:不要先问“哪个协议更好”,而是先问“这个器件的通信需求是什么”。速率、距离、引脚数、拓扑结构、是否需要多设备共享、有没有时钟同步要求——把这几个维度列出来,答案基本就浮出水面了。

I2C、SPI、UART、I2S这四种总线,本质上解决的是不同层面的问题。I2C和SPI是板级芯片间通信的主力,UART更多用于设备与设备之间的异步串行通信,I2S则是专门为音频数据流设计的同步串行接口。它们之间不是替代关系,而是各有各的生态位。

这篇文章我会从实际项目出发,把四种总线的核心机制、选型逻辑、典型应用场景、以及我在调试中踩过的坑,尽可能讲透。不管你是刚接触嵌入式的新手,还是已经用过几种总线但想系统梳理一遍的老手,应该都能从中找到对自己有用的东西。

2. I2C总线:两根线挂一堆设备,但别把它当高速通道用

2.1 I2C的物理层设计:开漏输出加外部上拉电阻的用意

I2C最让人印象深刻的就是它的引脚数——只有两根线,SDA(数据线)和SCL(时钟线)。这两根线都是开漏输出结构,也就是说芯片内部的输出级只能把线拉低,不能主动拉高。线要变高,必须靠外部的上拉电阻。

这个设计不是随便选的。开漏输出加上拉电阻的组合,天然实现了线与逻辑:只要总线上任何一个设备把线拉低,整条线就是低电平;只有所有设备都释放总线,线才被上拉电阻拉高。这就意味着多个设备可以同时连接到同一对线上,不会出现一个设备输出高电平、另一个输出低电平导致短路的情况。

上拉电阻的取值是个经典问题。阻值太小,功耗大,而且灌电流可能超过芯片IO的承受能力;阻值太大,上升沿变缓,高速通信时波形还没拉到高电平就被下一个时钟沿打断了。经验公式是这样的:先根据总线电容和上升时间要求算出最大值,再根据芯片灌电流能力算出最小值,取中间值。

标准模式100kHz和快速模式400kHz下,常见的上拉电阻取值在4.7kΩ到10kΩ之间。如果总线电容比较大(比如挂了七八个设备,走线又长),可能需要降到2.2kΩ甚至1.5kΩ。我遇到过一块板子I2C死活通信不上,用示波器一看,SCL上升沿像山坡一样缓慢,换了2.2kΩ上拉之后波形立刻方正了。所以I2C上拉电阻小了不通信,大了同样可能不通信,关键看总线电容和速率要求。

2.2 I2C的协议层:起始、地址、应答、数据的完整时序

I2C的通信过程有一套严格的时序规范。起始条件(Start)是SCL为高时SDA从高变低;停止条件(Stop)是SCL为高时SDA从低变高。这两个条件都由主机发起,用来框定一次完整的传输。

起始条件之后,主机发送7位从机地址加1位读写标志位。地址发完,主机释放SDA线,等待从机拉低SDA作为应答(ACK)。如果从机不存在或者地址不对,SDA保持高电平,就是非应答(NACK),主机据此判断通信失败。

数据传输阶段,每个字节8位,高位在前,每发完一个字节接收方都要回一个ACK。整个过程中,SCL始终由主机控制,SDA在SCL高电平期间必须保持稳定,数据的变化只能发生在SCL低电平期间。这个规则是I2C时序图的核心,用逻辑分析仪抓波形的时候,就是靠这个来判断数据是否有效的。

I2C支持多主机仲裁。如果两个主机同时发起传输,它们会一边发一边监听SDA电平,谁发的数据和总线上的不一致谁就退出。这个机制保证了多主机场景下不会冲突,但实际项目中很少用到多主机,大多数情况都是一个主机挂一堆从机。

2.3 I2C的典型应用与常见坑点

I2C最典型的应用场景是连接低速传感器和存储器。比如温度传感器、加速度计、EEPROM、RTC时钟芯片,这些器件数据量小、实时性要求不高,用I2C刚好合适。我做过一个环境监测项目,一颗STM32F103通过I2C挂了温度、湿度、气压三个传感器,再加一颗EEPROM存配置参数,总共就用了两根线,非常省引脚。

但I2C的坑也不少。最常见的问题是地址冲突——两个同型号的传感器地址一样,挂同一条总线上就打架了。解决办法一般是选支持地址可配置的型号,或者用I2C多路复用器扩展出多条总线。

另一个坑是时钟拉伸。有些从机处理数据慢,会在接收完一个字节后把SCL拉低,强制主机等待。如果主机不支持时钟拉伸,就会出错。我在用某款传感器的时候就遇到过,主机发完地址后从机拉低SCL等了将近1ms才释放,而主机的超时设置只有几百微秒,结果就是通信失败。后来把超时时间调大就好了。

还有一点,I2C的速率上限不高。标准模式100kHz,快速模式400kHz,高速模式3.4MHz但很少用。如果你要传大量数据,比如从EEPROM读几百KB的固件,I2C会慢得让你怀疑人生。这种场景就该考虑SPI了。

3. SPI总线:速度拉满,但引脚和片选管理是代价

3.1 SPI的四线制与全双工机制

SPI比I2C多两根线,总共四根:SCLK(时钟)、MOSI(主机输出从机输入)、MISO(主机输入从机输出)、CS(片选)。有时候也会看到三线SPI,那是把MOSI和MISO合并成一根双向线,但这样就不能全双工了。

SPI的核心是一个移位寄存器环。主机和从机各有一个移位寄存器,主机把数据移出的同时,从机的数据也在移入。每来一个时钟沿,双方各移出一位、移入一位。所以SPI天然支持全双工——你发一个字节的同时也会收到一个字节。这个特性在需要同时收发数据的场景下非常有用,比如SPI Flash的读取操作,发命令的同时就能开始接收数据。

SPI没有地址概念,从机靠片选信号区分。主机要跟哪个从机通信,就把对应的CS拉低,其他从机的CS保持高电平。这意味着每增加一个从机,就多一根CS线。挂八个从机就是八根CS线,加上SCLK、MOSI、MISO,总共十一根线。这是SPI相比I2C最大的劣势——引脚开销大。

3.2 SPI的四种模式:CPOL和CPHA的组合

SPI有四种工作模式,由CPOL(时钟极性)和CPHA(时钟相位)两个参数组合而成。CPOL决定空闲时SCLK是高还是低,CPHA决定数据在第一个时钟沿还是第二个时钟沿采样。

模式CPOLCPHA空闲电平采样沿移出沿
Mode 000上升沿下降沿
Mode 101下降沿上升沿
Mode 210下降沿上升沿
Mode 311上升沿下降沿

Mode 0和Mode 3最常用。选哪个模式取决于从机器件的要求,必须查从机手册确认。我见过有人调试SPI Flash调了半天读不出ID,最后发现是模式设错了——Flash要求Mode 0,他用的是Mode 3,数据采样时刻完全对不上。

3.3 SPI的片选方式:硬件片选与软件片选的选择

SPI的片选有两种实现方式。硬件片选是用SPI外设自带的CS引脚,由硬件在传输开始和结束时自动拉低拉高。软件片选是用普通GPIO模拟CS信号,在代码里手动控制。

硬件片选的好处是时序精确,不占用CPU干预,适合高速传输。但缺点是SPI外设通常只有一到两个硬件CS引脚,挂多个从机就不够用了。软件片选灵活,任意GPIO都能当CS,挂多少个从机都行,但需要CPU在传输前后手动操作GPIO,增加了代码复杂度,而且如果中断打断可能导致CS时序异常。

我的经验是:单从机或者从机数量少且速率高,用硬件片选;从机多或者速率不高,用软件片选。用软件片选的时候,拉低CS和开始SPI传输之间最好加几个空操作或者短暂延时,确保CS建立时间满足从机要求。

3.4 SPI在STM32上的实战:DMA方式读取芯片数据

STM32F103的SPI外设配合DMA用起来非常顺手。以读取SPI Flash数据为例,传统方式是一个字节一个字节地读写,CPU全程参与,效率很低。用DMA的话,配置好源地址、目标地址和传输长度,启动DMA传输后CPU就可以去干别的事了。

具体配置步骤大致是这样的:先初始化SPI外设,设置好模式、速率、数据宽度;然后配置DMA通道,把SPI的DR寄存器地址作为外设地址,内存缓冲区地址作为存储器地址;接着使能SPI的DMA发送和接收请求;最后启动传输。CubeMX里可以直接勾选DMA选项,生成初始化代码,省去手动配置寄存器的麻烦。

注意:SPI的DMA发送和接收要同时使能,否则全双工模式下会出问题。只发不收的话,接收缓冲区会溢出。

用DMA方式读SPI Flash,速率可以轻松跑到几MB/s,比I2C快两个数量级。但要注意DMA传输完成中断的处理,以及CS信号在DMA传输期间必须保持有效。

4. UART:最古老的串行通信,但至今不可替代

4.1 UART的异步通信原理与波特率匹配

UART和前面两种总线最大的区别是异步。它没有时钟线,收发双方靠预先约定的波特率来同步。发送方以约定的速率一位一位地发出数据,接收方以同样的速率采样。

一帧UART数据包含起始位(低电平)、数据位(通常8位)、可选校验位、停止位(高电平)。起始位的作用是告诉接收方“数据来了”,接收方检测到下降沿后开始按波特率采样。停止位给接收方一个缓冲时间,准备接收下一帧。

波特率必须匹配。如果发送方用115200,接收方用9600,收到的就是乱码。常见的波特率有9600、19200、38400、57600、115200、230400、460800、921600等。波特率越高,传输距离越短,抗干扰能力越弱。115200在板级通信中很稳,921600在长线上就容易出错。

4.2 UART的阻塞、非阻塞与DMA接收方式

UART的发送和接收有三种方式:阻塞、中断、DMA。

阻塞方式最简单,调用发送函数后CPU一直等到数据发完才返回。适合数据量小、实时性要求不高的场景。但如果你要发几百字节,CPU就被占住了,什么都干不了。

中断方式下,每发完一个字节触发一次中断,在中断里填下一个字节。CPU不用一直等,但频繁中断也有开销。接收方向用中断方式比较常见,每收到一个字节触发中断,把数据存到缓冲区。

DMA方式是效率最高的。配置好DMA后,UART的收发完全由DMA搬运,CPU只在传输完成时处理一次中断。STM32F103的标准库和HAL库都支持UART的DMA收发。用DMA接收的时候,可以配合空闲中断来判断一帧数据是否接收完毕——总线空闲一段时间没有新数据,就认为一帧结束了。

提示:UART的DMA接收要特别注意缓冲区溢出问题。如果数据来得太快,DMA还没搬完上一批,新数据就覆盖了旧数据。解决办法是用双缓冲区或者环形缓冲区。

4.3 UART的实际应用:调试口、模块通信与USB转串口

UART最普遍的应用就是调试口。几乎每块开发板都会留一个UART接口,接上USB转串口模块就能在电脑上看打印信息。FT231X、CH340、CP2102这些都是常见的USB转UART芯片,装好驱动后电脑上会多出一个虚拟串口。

除了调试,UART还大量用于模块通信。比如GPS模块、蓝牙模块、4G模块、指纹模块,基本上都是UART接口。这些模块的数据量不大,UART的速率完全够用,而且协议简单,开发起来快。

UART还可以用来连接两个主控板。比如一块板子负责采集,另一块负责显示,两者之间用UART通信。这种情况下要注意共地,否则电平参考不一致,通信会出错。

5. I2S:音频数据的专用通道,和I2C只差一个字母但完全不同

5.1 I2S的三线制与音频数据流

I2S的名字容易让人以为它和I2C有关系,实际上两者毫无关联。I2S是专门为数字音频传输设计的接口,通常有三根线:SCK(位时钟)、WS(声道选择,也叫LRCK)、SD(数据)。

SCK的频率等于采样率乘以位宽乘以声道数。比如44.1kHz采样率、16位位宽、双声道,SCK就是44100×16×2=1.4112MHz。WS信号用来区分左右声道,低电平表示左声道,高电平表示右声道,频率等于采样率。

I2S的数据格式有几种变体:标准I2S格式、左对齐格式、右对齐格式。区别在于数据相对于WS边沿的位置。标准I2S格式下,数据比WS边沿延迟一个SCK周期;左对齐格式下,数据与WS边沿对齐;右对齐格式下,数据在WS边沿之前。选哪种格式取决于音频编解码芯片的要求。

5.2 I2S与I2C的本质区别:同步流式传输 vs 寻址式控制

I2C是寻址式总线,每次传输都要指定从机地址,适合读写寄存器这种小数据量操作。I2S是流式接口,没有地址概念,数据像流水一样连续不断地传输,适合音频这种实时性要求高、数据量大的场景。

一个典型的音频系统里,I2C和I2S经常同时出现。I2C用来配置音频编解码芯片的寄存器——设置采样率、增益、输入输出通道等;I2S用来传输实际的音频数据流。两者分工明确,各司其职。

如果你把音频数据走I2C传输,先不说速率够不够,光是每次传输的地址和应答开销就受不了。反过来,用I2S去读写传感器寄存器也不现实,因为I2S没有地址机制,不知道数据该发给谁。

5.3 I2S在音频项目中的配置要点

配置I2S的时候,主从模式要设对。主机产生SCK和WS,从机接收。如果主控和编解码芯片都支持做主,那就要明确谁做主机。一般让主控做主机,编解码芯片做从机。

数据位宽和采样率要匹配。编解码芯片支持16位、24位、32位,主控的I2S外设也要设成对应的位宽。采样率常见的有8k、16k、44.1k、48k、96k。如果主控设48k,编解码芯片设44.1k,出来的声音就会变调。

DMA在I2S传输中几乎是必须的。音频数据是连续的,用CPU一个一个搬根本来不及。配置DMA双缓冲区,一个缓冲区在播放的时候,另一个缓冲区在填充数据,如此循环,才能保证音频不断流。

6. 四种总线的横向对比与选型决策框架

6.1 速率、引脚、拓扑、距离的量化对比

维度I2CSPIUARTI2S
线数24+每从机1CS2(TX/RX)3
速率100k-3.4M几M-几十M几十k-几M几M
拓扑多主多从一主多从点对点一主一从
寻址7/10位地址片选
全双工半双工全双工全双工单向
时钟同步同步异步同步
典型距离板级板级板级到米级板级

从表里可以清楚看出,SPI在速率上有绝对优势,I2C在引脚数上最省,UART在距离和简单性上最好,I2S在音频流传输上是唯一选择。

6.2 选型决策树:从器件需求反推总线类型

我通常按这个顺序来判断:

第一步,看器件手册支持什么接口。这是硬约束,器件只支持I2C,你就不能用SPI。大多数传感器和EEPROM都支持I2C,SPI Flash和显示屏通常支持SPI,音频编解码芯片用I2S,模块类器件用UART。

第二步,看数据量。数据量小、实时性低,I2C够用。数据量大、需要高速传输,选SPI。音频流选I2S。

第三步,看引脚预算。引脚紧张就优先I2C,引脚充裕再考虑SPI。

第四步,看拓扑需求。多个同类型器件挂一条总线,I2C最方便。每个器件独立片选,SPI更合适。点对点通信,UART最简单。

6.3 混合使用场景:一个项目里四种总线同时出现

实际项目里,四种总线经常同时存在。我做过一个音频播放器方案,主控是STM32F4,通过I2S连接音频DAC传输音频数据,通过I2C连接触摸屏控制器读取触摸坐标,通过SPI连接Flash存储音频文件,通过UART连接蓝牙模块接收控制指令。四种总线各干各的活,互不干扰。

这种混合场景下,要注意引脚分配和外设资源冲突。STM32的SPI和I2S经常复用同一组引脚,不能同时用。I2C的引脚也可能和UART复用。用CubeMX配置的时候,它会自动检查冲突并标红,但最好还是提前规划好引脚分配表。

7. 调试实战:逻辑分析仪抓波形与常见故障排查

7.1 用逻辑分析仪分析I2C和SPI波形

逻辑分析仪是调试总线的利器。抓I2C波形的时候,重点看起始条件、地址字节、ACK位、数据字节。如果地址发出去没有ACK,说明从机没响应,可能是地址错了、从机没供电、或者上拉电阻有问题。

抓SPI波形的时候,重点看CS是否在传输期间保持低电平、SCLK的极性和相位是否和从机要求一致、MOSI和MISO的数据在采样沿是否稳定。如果数据不对,先检查模式设置,再检查位序(MSB first还是LSB first)。

UART波形相对简单,看起始位、数据位、停止位是否完整,波特率是否匹配。如果波形看起来对但数据是乱码,量一下位宽,算一下实际波特率,看和设定值差多少。

7.2 典型故障案例:上拉电阻、片选时序、波特率误差

案例一:I2C上拉电阻过大导致通信失败。一块板子I2C挂了三颗传感器,用10kΩ上拉,100kHz速率下偶尔能通,400kHz完全不通。示波器看SCL上升沿严重变缓,换成4.7kΩ后问题解决。原因是总线电容加上走线电容超过了上拉电阻能驱动的范围。

案例二:SPI软件片选时序不对导致数据错位。用GPIO模拟CS,代码里拉低CS后立刻调用SPI发送函数,结果第一个字节总是错。后来在拉低CS和发送之间加了2微秒延时,问题消失。原因是从机需要CS建立时间,太快了从机还没准备好。

案例三:UART波特率误差累积导致长帧出错。主控用内部RC振荡器做时钟源,标称8MHz实际偏差2%,波特率115200下每帧误差累积,短帧还能忍,长帧就出错了。换成外部晶振后问题解决。所以UART对时钟精度有要求,内部RC振荡器只适合低波特率短帧通信

7.3 排查思路:从硬件到软件逐层定位

总线通信出问题,我一般按这个顺序排查:

先查硬件。供电是否正常、地是否共了、上拉电阻是否合适、走线是否太长、有没有虚焊。用万用表量电压,用示波器看波形。

再查配置。时钟是否使能、引脚复用是否正确、速率是否匹配、模式是否选对。对照器件手册逐项核对。

最后查代码。初始化顺序对不对、发送接收函数调用是否正确、中断和DMA配置有没有问题。用调试器单步跟踪,看寄存器值是否符合预期。

这个顺序能解决90%以上的总线通信问题。剩下10%可能是器件本身的问题,换一个同型号的试试就能确认。

8. 一些容易忽略的细节和我的个人经验

8.1 电平匹配与共地问题

不同器件的工作电压可能不同。3.3V的主控接5V的器件,直接连可能烧主控。需要用电平转换芯片,或者选支持宽电压的器件。I2C因为是开漏输出,上拉电阻接到哪一侧的电压,总线电平就是多少,所以I2C的电平转换相对简单,用MOS管就能做双向转换。

共地是另一个容易忽略的问题。两个设备通信,地必须连在一起,否则电平参考不一致,通信必挂。我见过有人用USB转串口模块接开发板,只连了TX、RX没连地,死活收不到数据,连上地就好了。

8.2 总线速率与走线长度的关系

速率越高,对走线的要求越高。100kHz的I2C随便走都能通,3.4MHz的高速I2C就要考虑阻抗匹配和走线长度了。SPI跑到几十MHz的时候,走线要尽量短,最好等长,避免过孔。UART在115200下几米线没问题,921600下超过一米就可能出错。

如果速率高、距离远,可以考虑差分信号。RS485就是UART的差分版本,能跑上千米。但那是另一个话题了。

8.3 从机地址冲突的几种解决思路

I2C地址冲突是常见问题。解决办法有几种:一是选地址可配置的器件,通过引脚电平改变地址;二是用I2C多路复用器,把一条总线扩展成多条,每条挂一个冲突的器件;三是用软件模拟I2C,用不同的GPIO组模拟出多条独立总线。

SPI没有地址冲突问题,因为每个从机有独立的CS。但SPI的CS引脚数量限制了从机数量。如果从机太多,可以用译码器扩展CS,比如3-8译码器用3根GPIO控制8个CS。

8.4 实际项目中的总线选择体会

做了这么多年项目,我的体会是:没有最好的总线,只有最合适的总线。I2C省引脚但慢,SPI快但费引脚,UART简单但只能点对点,I2S专用于音频。选型的时候不要追求“先进”,要根据器件需求、系统资源、开发周期综合权衡。

还有一点,总线协议本身不复杂,复杂的是调试。示波器和逻辑分析仪是必备工具,很多时候看波形比看代码管用。我现在的习惯是,新画一块板子,第一件事就是把所有通信总线的测试点引出来,方便后面抓波形。

最后分享一个小技巧:调试I2C的时候,如果怀疑从机没响应,可以先发一个简单的写操作,用逻辑分析仪看从机有没有回ACK。如果ACK都没有,问题就在硬件或者地址上;如果有ACK但数据不对,问题就在数据格式或者寄存器配置上。这个二分法能快速缩小排查范围。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 9:08:21

AI数据分析Agent:让实证论文从原始数据到结果一步到位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 9:07:47

王炸!OpenAI 推出 GPT-6 Sol 与 Luna 模型;Anthropic 发布 Claude Opus 5.5;OpenAI 组建数学家小组,咨询成果发布方式 | 科技日报0923

OpenAI 推出 GPT-6 Sol 与 GPT-6 Luna 模型 #1OpenAI推出GPT-6 Sol与GPT-6 Luna。OpenAI 表示,这两款模型与本月早些时候发布的GPT-6 Astra同源。Astra 被该公司称为迄今最强、能力最全面的模型,适用于计算机操作和编码等多种任务。 Sol 与 Luna 系列的首…

作者头像 李华
网站建设 2026/9/24 9:03:58

永磁同步电机FOC控制实战:TI方案实现与死区补偿调试指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 8:59:31

基于SCL与For循环的电梯楼层优先级调度算法实战详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 8:54:49

AI办公工具实测:我用了一周的AI工作台,到底省了多少事

最近把日常办公流程整体搬到了AI工作台上跑了一周,说说真实体验:哪些是噱头,哪些是真省时间。结论先放前面——对话式处理文档、会议纪要整理、多任务并行这三件事,AI办公台是真的快。 一、我测了什么场景 长文档总结:…

作者头像 李华