ARTICLE DETAIL

资讯详情

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

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

I2C、I2S、SPI、UART四种总线选型指南:从原理到实战 1. 四种总线到底该怎么选先搞清楚它们各自在解决什么问题嵌入式开发干久了你会发现一个很有意思的现象新手最爱问“I2C和SPI哪个更快”老手反而会先问“你这个场景到底需要几根线、挂几个设备、要不要热插拔”。I2C、I2S、SPI、UART这四种总线几乎覆盖了单片机外围通信的八成场景但它们之间不是简单的“谁替代谁”的关系而是各自有明确的势力范围。选错了轻则多飞几根线、多写几百行驱动重则通信不稳定、音频出杂音、传感器时不时掉线。这篇文章我打算把这四种总线放在一起做一次横向拆解不是照本宣科地列时序图而是从实际项目选型的角度把每种总线的核心机制、典型应用、踩坑点讲透。无论你是刚接触STM32 HAL库的新手还是正在做RK3588这类复杂平台外围设计的老手都能从中找到可以直接抄作业的判断依据。全文会围绕I2C、I2S、SPI、UART这四个关键词展开穿插ESP32-C3的I2S输出、FPGA挂SPI ADC、Linux下PHY不走MDIO走I2C、GT911触摸屏I2C通信失败等真实场景把“为什么这么选”和“怎么落地”讲清楚。先给一个最粗的结论方便你建立第一印象UART是点对点异步串行最简单但速度有限I2C是两根线的总线型同步串行省引脚但速率中等SPI是四根线的高速同步串行快但占引脚I2S是专门为音频数据流设计的同步串行本质上是SPI的一个“专业化分支”。记住这个框架后面所有细节都是往里面填肉。2. 四种总线的核心机制拆解从电气特性到时序逻辑2.1 UART没有时钟线的“约定式”通信UART的全称是通用异步收发器关键词在“异步”两个字。它没有时钟线收发双方靠事先约定好的波特率来对齐每一位数据。你可以把它想象成两个人约好每隔一秒说一个字谁也没有节拍器全靠各自的表走得准不准。一旦双方的时钟偏差超过容忍范围比如超过5%采样就会错位收到乱码。UART的帧结构很固定1个起始位低电平、5到9个数据位通常8位、可选的校验位、1到2个停止位高电平。空闲时线路保持高电平。起始位的下降沿就是接收方的“闹钟”触发它开始按波特率采样。这里有个实操细节接收方通常在起始位下降沿后延迟1.5个位时间开始采样第一位数据之后每隔1个位时间采一次这样采样点正好落在每位数据的中间容错率最高。UART的典型应用场景是调试串口、GPS模块、蓝牙模块、以及各种“AT指令”类设备。16550是经典的UART控制器行业标准现在很多MCU内部的UART外设都兼容它的寄存器模型。FT232R、FT231X这类USB转UART芯片则是PC端调试的常客驱动安装虽然偶尔折腾但胜在生态成熟。注意UART是点对点的不能像I2C那样一条总线挂多个设备。如果你需要多个UART设备要么用多个UART外设要么加多路复用器。2.2 I2C两根线挂一串设备的“总线型”方案I2C只有两根线SDA数据和SCL时钟都是开漏输出需要外接上拉电阻。开漏的好处是天然支持“线与”逻辑任何设备拉低总线都能被检测到这就实现了多设备共享总线和仲裁机制。上拉电阻的取值很讲究典型值4.7kΩ但高速模式下可能要用2.2kΩ甚至更小因为上升沿太慢会导致时序违规。I2C的通信过程是“主机发起、从机响应”。主机先发一个起始条件SCL高时SDA由高变低然后发7位从机地址加1位读写位。从机如果地址匹配就在第9个时钟拉低SDA作为ACK应答。之后就是数据字节的传输每传一个字节跟一个ACK。停止条件是SCL高时SDA由低变高。I2C最让人头疼的地方在于时钟拉伸和总线死锁。时钟拉伸是从机拉低SCL告诉主机“我还没准备好”主机必须等待。如果主机不支持时钟拉伸就会读错数据。总线死锁则常见于主机复位时从机还在拉低SDA导致总线一直忙。解决办法是主机发送9个时钟脉冲让从机把剩余数据发完释放总线。I2C的典型应用包括EEPROM读写、传感器温湿度、加速度计、触摸屏GT911就是I2C接口、以及一些编码器。GT911 I2C通信失败是很多人踩过的坑常见原因就三个上拉电阻没焊、地址配错GT911有0x5D和0x14两个地址取决于上电时INT引脚电平、以及复位时序不对。2.3 SPI四根线换来的高速与灵活SPI用四根线SCLK时钟、MOSI主出从入、MISO主入从出、CS片选。它是同步通信时钟由主机产生所以速率可以拉得很高几十MHz很常见。SPI没有地址概念靠CS片选来选中某个从机。这意味着每多一个从机就多一根CS线引脚消耗大。SPI有四种模式由CPOL时钟极性和CPHA时钟相位组合而成。CPOL0表示空闲时SCLK为低CPOL1表示空闲为高CPHA0表示第一个边沿采样CPHA1表示第二个边沿采样。模式选错是SPI调试中最常见的问题现象是读出来全是0xFF或0x00。我的经验是先查从机手册确认模式然后用逻辑分析仪抓波形看数据在哪个边沿稳定。SPI的片选分硬件片选和软件片选。硬件片选由SPI外设自动控制CS引脚时序精准软件片选则是用普通GPIO手动拉低拉高灵活但占用CPU。CS的最小脉宽取决于从机要求有些ADC要求CS在两次转换之间保持高电平至少几十纳秒这个参数在手册里叫tCSH或tCS。STM32用CubeMX配置SPI DMA时硬件片选能省不少CPU开销。SPI的典型应用包括Flash存储、ADCFPGA挂SPI ADC很常见、显示屏、以及无线模块。RK3588的SPI接口性能很强但要注意引脚复用和电平匹配。2.4 I2S为音频而生的“SPI变体”I2S全称是Inter-IC Sound专门传音频数据。它至少有三根线SCK位时钟、WS声道选择也叫LRCLK、SD数据。有时候还有MCLK主时钟。I2S的时序和SPI很像但有几个关键区别WS信号在每个声道切换时翻转决定当前数据属于左声道还是右声道数据通常在高位对齐或低位对齐标准I2S是高位对齐且比WS延迟一个SCK。I2S的采样率、位深、主从模式是三个核心配置。比如ESP32-C3的I2S输出你要配采样率44.1kHz或48kHz、位深16位或32位、以及是主模式还是从模式。主模式下ESP32-C3产生SCK和WS从模式下外部提供。用逻辑分析仪看I2S波形时重点看WS翻转频率是否等于采样率以及数据是否在SCK边沿稳定。I2S的典型应用就是音频编解码器、数字麦克风、以及音频功放。如果你只是传普通数据别用I2S用SPI更灵活。3. 横向对比速率、引脚、拓扑、场景一张表说清3.1 关键参数对比表特性UARTI2CSPII2S线数2TX/RX2SDA/SCL4SCLK/MOSI/MISO/CS3SCK/WS/SD时钟异步同步同步同步拓扑点对点总线型多主多从一主多从每从一CS点对点为主典型速率9600bps~1Mbps100k~3.4Mbps1M~50Mbps取决于音频采样率寻址方式无7位/10位地址CS片选WS声道选择引脚开销低低高中抗干扰弱中强中典型场景调试、GPS、蓝牙传感器、EEPROM、触摸Flash、ADC、屏幕音频编解码3.2 选型决策树三步锁定目标总线第一步问自己“传的是什么”。如果是音频流直接I2S别犹豫。如果是传感器数据、配置寄存器优先I2C。如果是高速数据块比如图像、大量ADC采样选SPI。如果只是调试打印或跟PC通信UART最省事。第二步问“挂几个设备”。一个设备UART或SPI都行。多个设备且引脚紧张I2C是首选。多个设备但要求高速SPI加多CS或者用I2C加多路复用器I2C控制的多路复用器很常见比如TCA9548A。第三步问“速率要求”。低于1MbpsI2C和UART都能胜任。1M到10MbpsSPI开始显现优势。高于10Mbps基本只有SPI和I2S音频专用可选。提示PMBus和I2C的区别主要在协议层PMBus是I2C的一个子集增加了电源管理相关的命令格式。硬件上完全兼容所以如果你在调电源芯片看到PMBus别慌按I2C调就行。4. 实操落地从STM32 HAL库到Linux平台的配置要点4.1 STM32 HAL库下四种外设的初始化与API调用STM32CubeMX生成代码后每种外设的初始化函数和API调用方式差异很大。UART用HAL_UART_Init发送用HAL_UART_Transmit接收用HAL_UART_Receive或中断/DMA模式。I2C用HAL_I2C_Init读写用HAL_I2C_Mem_Read和HAL_I2C_Mem_Write注意这两个函数需要指定设备地址和寄存器地址。SPI用HAL_SPI_Init收发用HAL_SPI_TransmitReceiveDMA模式下要配HAL_SPI_TransmitReceive_DMA。I2S在STM32里通常和SPI共用外设用HAL_I2S_Init配置。这里有个实操心得HAL库的I2C函数在遇到从机不响应时会阻塞很久默认超时是1000ms调试时很浪费时间。建议把超时改短比如100ms或者用中断模式。另外STM32的硬件I2C在某些型号上有已知的时序问题如果调不通可以先用软件I2CGPIO模拟验证从机是否正常再切回硬件I2C。4.2 ESP32-C3的I2S输出配置实例ESP32-C3的I2S外设配置需要关注几个参数采样率、位深、声道数、以及DMA缓冲区大小。用ESP-IDF的话调用i2s_driver_install和i2s_set_clk。主模式下ESP32-C3产生SCK和WS从模式下外部提供。如果你用ESP32-C3做蓝牙音频接收I2S输出到DAC采样率通常设44.1kHz或48kHz位深16位。实测下来ESP32-C3的I2S在48kHz/16位下很稳但DMA缓冲区别设太小否则容易断音。建议至少设1024字节。另外MCLK如果不需要就别开省引脚。4.3 Linux平台下I2C和SPI的设备树配置在RK3588这类Linux平台上I2C和SPI设备通过设备树描述。I2C设备节点里要写compatible、reg设备地址、以及具体驱动需要的属性。SPI设备节点要写compatible、reg片选号、spi-max-frequency。有个坑是Linux的I2C驱动有时会尝试用MDIO访问PHY但有些PHY只支持I2C这时候要在设备树里明确指定用I2C别让内核自动探测。RK3588的SPI接口在设备树里配置时注意pinctrl要选对引脚组spi-max-frequency别超过从机手册的最大值。如果从机是SPI NOR Flash还要配spi-tx-bus-width和spi-rx-bus-width。4.4 用Python模拟SPI接口的可行性分析有人问能不能用Python调用USB模拟SPI接口。答案是可以但有限制。FT232H这类芯片支持MPSSE模式能模拟SPI、I2C、JTAG。Python用pyftdi库可以调用。但模拟SPI的速率远低于硬件SPI通常只有几百kHz到几MHz而且时序抖动大。适合调试和低速设备不适合高速ADC或Flash。如果你只是偶尔用一下pyftdi够用。如果要稳定高速还是用硬件SPI。5. 常见问题与排查技巧实录5.1 I2C通信失败排查清单现象可能原因排查方法完全无响应上拉电阻缺失用万用表测SDA/SCL空闲时是否高电平地址无应答地址配错查手册确认7位地址注意左移一位偶发NACK总线电容过大减小上拉电阻或降低速率总线死锁从机拉低SDA发9个时钟脉冲释放GT911失败复位时序不对按手册控制INT和RST引脚5.2 SPI模式选错与CS时序问题SPI调不通先查模式。用逻辑分析仪抓SCLK和MOSI看数据在哪个边沿变化。如果数据在第一个边沿变化那是CPHA0在第二个边沿变化CPHA1。CPOL看空闲电平。CS的最小脉宽如果不够从机会忽略命令。比如有些ADC要求CS高电平至少50ns你如果连续操作没留间隔就会丢数据。解决办法是在两次传输之间加延时或者用硬件片选让外设自动处理。5.3 UART乱码与波特率误差计算UART乱码九成是波特率不对。波特率误差计算公式是误差 (实际波特率 - 目标波特率) / 目标波特率。一般要求误差小于2%最好小于1%。比如你晶振是8MHz要115200波特率分频系数是8M/11520069.44取整69实际波特率是8M/69115942误差0.64%可以接受。如果误差超过2%就要换晶振或改用更高精度的时钟源。5.4 I2S波形异常与逻辑分析仪使用I2S出问题先用逻辑分析仪看三根线。WS频率应该等于采样率SCK频率等于采样率×位深×2。如果WS不翻转可能是主从模式配错。如果数据全是0可能是DMA没启动或缓冲区没填。ESP32-C3的I2S输出如果断断续续检查DMA缓冲区是否太小或者任务优先级是否被其他任务抢占。6. 进阶话题I2C自由数据模式与SPI硬件测试用例6.1 I2C自由数据模式的应用I2C自由数据模式是指不遵循标准寄存器读写格式直接发原始字节。有些设备比如某些编码器或自定义协议就是用这种模式。实现方法是直接用HAL_I2C_Master_Transmit发原始数据不用HAL_I2C_Mem_Write。注意这种模式下没有寄存器地址概念从机怎么解析完全看它的固件。6.2 SPI硬件测试用例设计SPI硬件测试通常包括回环测试MOSI短接MISO、片选测试测CS能否正常拉低拉高、速率测试逐步提高SCLK看何时出错、以及模式测试四种模式都试一遍。回环测试最简单发什么收什么就说明硬件通路正常。速率测试能帮你找到从机的最大承受频率。6.3 从机主动更新主机寄存器的实现思路I2C从机主动更新主机寄存器标准I2C不支持因为I2C是主机主导。但可以用“从机拉低一个中断引脚主机收到中断后去读从机”的方式模拟。有些MCU的I2C外设支持从机发送模式但需要主机配合。更常见的做法是用SPI因为SPI从机可以在主机发时钟时把数据推出去。7. 我个人在实际项目中的选型体会干了这么多年我选总线的顺序基本固定先看有没有现成驱动再看引脚够不够最后才看速率。因为调试成本往往比硬件成本高。I2C虽然速率不高但两根线挂一堆传感器布线清爽驱动也成熟。SPI快是快但每加一个设备就多一根CSPCB走线麻烦。UART最简单但点对点限制大。I2S专用性强非音频场景别碰。最后分享一个小技巧如果你不确定选哪个先用UART或软件I2C把功能跑通再根据速率需求决定要不要换硬件总线。这样能最快验证核心逻辑避免一开始就陷在时序调试里。
返回列表