ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

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

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×21.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每从机1CS2TX/RX3速率100k-3.4M几M-几十M几十k-几M几M拓扑多主多从一主多从点对点一主一从寻址7/10位地址片选无无全双工半双工全双工全双工单向时钟同步同步异步同步典型距离板级板级板级到米级板级从表里可以清楚看出SPI在速率上有绝对优势I2C在引脚数上最省UART在距离和简单性上最好I2S在音频流传输上是唯一选择。6.2 选型决策树从器件需求反推总线类型我通常按这个顺序来判断第一步看器件手册支持什么接口。这是硬约束器件只支持I2C你就不能用SPI。大多数传感器和EEPROM都支持I2CSPI 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但数据不对问题就在数据格式或者寄存器配置上。这个二分法能快速缩小排查范围。
返回列表