拓冰建站拓冰建站
首页 / 资讯中心 / 正文

嵌入式通信协议选型指南:I2C、SPI、UART、I2S原理与实战

1. 四种通信协议到底该怎么选嵌入式开发绕不开的一个话题就是通信协议选型。你画完原理图MCU跟传感器、存储器、屏幕、音频编解码器之间怎么连用哪条总线这个问题几乎每个项目都会遇到。I2C、I2S、SPI、UART这四个名字做过单片机的人肯定都见过但真要把它们之间的差异说清楚、在具体项目里一次选对其实没那么简单。我自己在STM32F103、RK3588S这些平台上都踩过不少坑I2C上拉电阻选错导致通信时好时坏、SPI硬件片选和软件片选搞混、UART DMA接收丢包、I2S时钟配置不对导致音频全是噪声。这些问题看起来是细节但根子上都是对协议本身理解不够透彻。这篇内容适合谁看如果你正在做嵌入式硬件设计或者驱动开发需要在几个外设之间做总线选型或者你已经选了某个协议但调试一直不顺利那这篇应该能帮到你。我会从协议原理、时序特征、典型应用场景、实操配置和常见问题几个维度把这四种协议彻底拆开讲一遍。不会只讲教科书上的定义更多是我自己在项目里积累的判断依据和避坑经验。2. 四种协议的核心差异一张表说清2.1 先看全局对比在深入每个协议之前先用一张表把核心差异拉出来这样你心里有个整体框架。特性I2CSPIUARTI2S线数2SCLSDA4SCLKMOSIMISOCS2TXRX3BCLKLRCLKSDATA拓扑多主多从一主多从点对点一主一从音频专用速率100k/400k/1M/3.4M几M到上百M通常≤1M可更高取决于音频采样率时钟同步主机产生同步主机产生异步同步主机产生寻址7位/10位地址片选线选通无无双工半双工全双工全双工全双工数据时钟分离典型场景传感器、EEPROMFlash、屏幕、ADC调试、模块通信音频编解码器这张表建议你先记住几个关键判断需要多设备挂在同一组线上、速率要求不高选I2C需要高速传输大量数据选SPI两个设备之间简单通信、不需要时钟线选UART传音频数据选I2S。2.2 为什么I2C用开漏输出加外部上拉这个问题被问得最多也是面试高频题。I2C的SDA和SCL两根线都是开漏输出结构也就是说芯片内部只能把线拉到低电平没法主动输出高电平。高电平是靠外部上拉电阻拉上去的。为什么要这么设计核心原因是I2C支持多主多从同一根线上可能挂了好几个设备。如果两个设备同时想输出一个想输出高、一个想输出低推挽输出就会短路电流直接灌穿。开漏结构就不会有这个问题——任何设备都只能拉低谁拉低谁赢高电平靠上拉电阻自然恢复。这就是所谓的“线与”逻辑。上拉电阻的选值也有讲究。阻值太大上升沿变缓高速通信时波形还没到高电平就被拉低了通信失败阻值太小低电平时灌电流太大可能超过芯片的驱动能力。经验公式是Rp(max) tr / (0.8473 × Cb)其中tr是上升时间要求Cb是总线电容。100kHz标准模式下tr最大1000ns400kHz快速模式下tr最大300ns。总线电容一般按100pF到200pF估算。实际项目中4.7kΩ是最常用的值对应100kHz和400kHz基本都能用。如果你挂的设备多、走线长电容大了可能要降到2.2kΩ甚至1kΩ。我遇到过一次I2C上拉用了10kΩ100kHz能通一升到400kHz就频繁NACK换成4.7kΩ立刻稳定。所以调试I2C通信问题时上拉电阻是第一个要检查的点。2.3 SPI的片选方式硬件片选和软件片选SPI没有地址概念选通哪个从设备靠的是片选信号CS。每个从设备需要一根独立的CS线主机拉低哪根CS就选中哪个设备。硬件片选是指用SPI外设自动管理CS信号配置好之后发送数据时硬件自动拉低CS发完自动拉高。STM32的硬件NSS模式就属于这种。优点是时序精确、不占CPU缺点是CS引脚必须接到SPI外设指定的NSS引脚上灵活性差。软件片选是自己用GPIO控制CS引脚发送前手动拉低发送后手动拉高。优点是CS引脚可以随便选不受外设限制缺点是需要CPU干预如果中间被中断打断CS拉高的时机可能延迟导致从设备误判。我一般推荐用软件片选因为STM32的SPI外设NSS引脚位置固定很多时候PCB布线不方便。而且软件片选配合DMA传输时可以在DMA传输完成中断里拉高CS时序也很准。但要注意一点拉高CS之前必须确认最后一个字节已经完全移出移位寄存器否则会截断数据。STM32的SPI有BSY标志位等BSY清零再拉高CS最稳妥。2.4 UART的阻塞、非阻塞和DMA模式UART是最简单的通信方式没有时钟线双方靠约定的波特率来同步。也正因为没有时钟线波特率误差不能太大一般要求双方误差在2%以内否则采样点偏移会导致误码。STM32上UART发送有三种方式。阻塞发送就是调用HAL_UART_TransmitCPU一直等到发完才返回简单但浪费CPU。非阻塞发送是HAL_UART_Transmit_IT发完触发中断CPU可以去干别的。DMA发送是HAL_UART_Transmit_DMA数据交给DMA搬运CPU完全不参与适合大量数据发送。接收端更复杂。阻塞接收HAL_UART_Receive会一直等容易卡死。中断接收HAL_UART_Receive_IT每收一个字节进一次中断波特率高的时候中断太频繁。DMA接收是最优解但有个经典问题DMA接收不知道对方什么时候发完数据长度不固定。常见解决方案是用空闲中断IDLE总线空闲时触发中断这时候读取DMA剩余计数就能算出收到多少字节。我在STM32F103上用标准库做过UART DMA加IDLE中断的接收方案实测下来非常稳定。配置步骤是DMA设为循环模式UART开启IDLE中断在IDLE中断里计算接收长度、处理数据、重置DMA计数。这个方案适合Modbus、GPS模块这类不定长数据的接收场景。3. 逐个拆解时序、原理和典型应用3.1 I2C的时序图到底怎么看I2C的时序图是很多人的噩梦其实抓住几个关键点就不难。起始条件是SCL为高时SDA从高变低停止条件是SCL为高时SDA从低变高。数据在SCL低电平期间变化在SCL高电平期间保持稳定接收方在高电平期间采样。每传输一个字节是8位高位在前第9个时钟是ACK/NACK位。发送方释放SDA接收方如果拉低SDA就是ACK保持高就是NACK。主机读数据时读完最后一个字节要发NACK告诉从机别再发了然后发停止条件。I2C的数据帧格式通常是起始条件、从机地址加读写位、ACK、寄存器地址、ACK、数据、ACK/NACK、停止条件。读写EEPROM就是这个流程的典型应用。用Verilog写I2C读写EEPROM的代码时核心就是一个状态机把起始、发送地址、等待ACK、发送数据、等待ACK、停止这些状态串起来。状态机的时钟分频要算好100kHz的SCL需要根据系统时钟做分频。逻辑分析仪分析I2C数据时触发条件设成SDA下降沿同时SCL为高就能抓到起始条件。解码器选I2C设置好地址位宽就能直接看到地址和数据。如果解码出来全是NACK先查上拉电阻再查地址对不对最后查从设备供电和复位状态。3.2 SPI的四种模式和时序细节SPI有四种模式由CPOL和CPHA两个参数组合决定。CPOL是时钟极性0表示空闲时SCLK为低1表示空闲时为高。CPHA是时钟相位0表示第一个边沿采样1表示第二个边沿采样。模式CPOLCPHA采样边沿Mode 000上升沿Mode 101下降沿Mode 210下降沿Mode 311上升沿Mode 0和Mode 3最常用。选哪个模式取决于从设备的数据手册主从必须一致否则数据全错。我调试MT6701磁编码器的时候手册上写的是Mode 3我一开始配成了Mode 0读出来的角度值一直在跳换成Mode 3就正常了。SPI是全双工发送和接收同时进行。主机产生时钟每个时钟周期移出一位、移入一位。传输N个字节就是N×8个时钟。SPI没有应答机制主机发了数据不知道从机收没收到所以高速传输时要注意信号完整性。走线长了要加匹配电阻时钟频率太高要降低速率试试。STM32F103的硬件SPI通过DMA读取芯片数据是很常见的需求。配置流程是SPI配置好模式和速率DMA配置好源地址和目的地址使能SPI的DMA请求然后启动传输。注意SPI的DMA请求分发送和接收两个通道全双工时要同时配置。如果只读不写可以只开接收DMA但发送DMA也要处理否则时钟不会产生。简单做法是发送DMA指向一个空缓冲区或者用HAL库的HAL_SPI_Receive_DMA它内部会处理。3.3 UART波形和协议细节UART的波形很直观空闲时是高电平起始位是一个低电平然后是8位数据低位在前可选的校验位最后是停止位高电平。波特率决定了每一位的宽度9600波特率下每位约104微秒115200波特率下每位约8.68微秒。用逻辑分析仪看UART波形时触发条件设成下降沿就能抓到起始位。解码器选UART设置好波特率和数据位就能看到数据。如果解码出来是乱码先确认波特率对不对再确认数据位、停止位、校验位配置是否一致。FT231X这类USB转UART芯片驱动装好之后在系统里会虚拟出一个串口用串口助手打开就能通信。如果打不开检查驱动是否装好、串口号是否被占用。UART通信协议没有标准的数据帧格式帧头、帧尾、校验方式都是自定义的。常见做法是帧头加长度加数据加校验和。校验和可以用累加和、异或或者CRC。我一般用CRC16虽然计算量大一点但检错能力强。STM32有硬件CRC外设可以加速计算。3.4 I2S协议和音频数据传输I2S是专门为音频数据传输设计的协议和SPI长得很像但用途完全不同。I2S有三根主要信号线BCLK是位时钟LRCLK是左右声道选择SDATA是数据。BCLK的频率等于采样率乘以位深乘以声道数。比如44.1kHz采样率、16位深度、双声道BCLK就是44100×16×21.4112MHz。LRCLK的频率等于采样率低电平表示左声道高电平表示右声道。数据在BCLK的边沿变化在另一个边沿被采样。I2S的时序要求很严格BCLK和LRCLK的相位关系必须正确否则左右声道会颠倒或者数据错位。I2S和SPI的区别在于SPI是通用数据传输I2S是音频专用SPI的片选是CSI2S的声道选择是LRCLKSPI的数据格式灵活I2S的数据格式固定为音频采样。有些MCU的I2S外设和SPI外设是复用的配置时要注意切换。4. 实操配置从STM32到实际项目4.1 STM32F103硬件SPI加DMA读取数据以STM32F103读取SPI Flash为例用CubeMX配置的步骤是这样的。先使能SPI1模式选全双工主模式数据宽度8位CPOL和CPHA根据Flash手册设置一般Mode 0或Mode 3。预分频系数根据Flash支持的最高速率来定STM32F103的SPI1挂在APB2上72MHz时钟预分频8就是9MHz预分频4就是18MHz。W25Q系列Flash支持到80MHz以上但为了稳定我一般先用9MHz调通再往上加。DMA配置SPI1_RX加到DMA1通道2SPI1_TX加到DMA1通道3模式都选Normal数据宽度Byte。中断里使能DMA传输完成中断。代码里先拉低CS然后调用HAL_SPI_TransmitReceive_DMA在DMA完成回调里拉高CS。这里有个坑HAL_SPI_TransmitReceive_DMA要求发送和接收缓冲区长度一样如果只想读数据发送缓冲区也要给同样大小随便填什么值都行因为MOSI上的数据从设备不关心。另一个坑是DMA传输完成中断触发时SPI可能还有最后一个字节在移位寄存器里没发完直接拉高CS会截断。解决办法是在回调里先等SPI的BSY标志清零再拉高CS。4.2 I2C读写EEPROM的Verilog实现思路用Verilog写I2C主机控制器核心是一个状态机。状态包括IDLE、START、SEND_ADDR、WAIT_ACK、SEND_REG、WAIT_ACK、SEND_DATA、WAIT_ACK、STOP。时钟分频模块产生SCL100kHz对应系统时钟100MHz的话分频系数是1000。SCL高电平和低电平各占一半所以计数器到500时翻转SCL。SDA是三态门输出0时拉低输出1时释放高阻输入时直接读引脚值。Verilog里用inout端口加assign语句实现。起始条件SCL为高时SDA从1变0。停止条件SCL为高时SDA从0变1。发送数据SCL为低时改变SDASCL为高时SDA保持稳定。接收ACK发送完8位后释放SDA在第9个SCL高电平期间读SDA0就是ACK。读写EEPROM的完整流程是起始、发送设备地址加写位、等ACK、发送寄存器地址、等ACK、发送数据、等ACK、停止。读操作稍微复杂起始、发送设备地址加写位、等ACK、发送寄存器地址、等ACK、起始、发送设备地址加读位、等ACK、读数据、发NACK、停止。4.3 UART DMA加IDLE中断接收不定长数据这个方案我在多个项目里用过稳定性很好。配置步骤UART波特率设好开启接收DMADMA模式选循环缓冲区大小设成最大帧长的两倍。开启UART的IDLE中断。IDLE中断服务函数里先清除IDLE标志然后计算接收到的数据长度长度等于缓冲区大小减去DMA剩余计数。处理完数据后重置DMA计数准备接收下一帧。注意点DMA循环模式下如果数据超过缓冲区大小会覆盖前面的数据所以缓冲区要足够大。IDLE中断触发后到重新使能DMA之间有一小段时间如果这期间有新数据来可能会丢。解决办法是用双缓冲区一个在处理时另一个在接收。STM32F103标准库的代码大概是这样DMA_InitStructure.DMA_Mode DMA_Mode_CircularUSART_ITConfig(USART1, USART_IT_IDLE, ENABLE)在中断里用USART_GetITStatus判断IDLE标志然后调用DMA_GetCurrDataCounter获取剩余计数。4.4 I2S配置音频编解码器以WM8978为例STM32的I2S配置步骤I2S模式选主模式发送或接收标准选I2S标准数据格式选16位或24位时钟极性选低电平有效。采样率通过I2S分频系数设置STM32F103的I2S时钟来自PLL3需要根据目标采样率计算分频值。MCLK是主时钟一般是采样率的256倍WM8978需要MCLK才能工作。BCLK是位时钟LRCLK是帧时钟。配置好之后先初始化WM8978的寄存器设置音量、输入输出通道、采样率等参数然后就可以通过I2S发送音频数据了。常见问题是没声音或者全是噪声。先查MCLK有没有输出再查I2S的时钟配置对不对最后查WM8978的寄存器配置。用示波器看BCLK和LRCLK的频率如果不对就是分频系数算错了。5. 常见问题排查速查表5.1 I2C通信失败排查现象可能原因排查方法完全无响应上拉电阻缺失或过大测SDA/SCL空闲时是否为高偶尔NACK上拉电阻过大、总线电容大减小上拉电阻缩短走线高速时失败上升沿太缓换小阻值上拉查总线电容地址无应答地址错误、设备未复位用逻辑分析仪看地址字节数据错位时钟拉伸未处理检查从机是否支持时钟拉伸I2C上拉电阻小了不通信这个说法其实不准确电阻小了应该是灌电流大可能导致低电平不够低。真正不通信往往是电阻大了导致上升沿太慢。我实测过3.3V系统下1kΩ上拉低电平能到0.1V以下没问题10kΩ上拉400kHz时上升沿超过1微秒通信就不稳了。5.2 SPI通信不生效排查SPI通信不生效按这个顺序查第一CS有没有拉低用示波器看CS引脚第二SCLK有没有波形没有就是SPI外设没使能或者DMA没启动第三CPOL和CPHA对不对这个最容易被忽略第四MOSI和MISO有没有接反第五从设备供电和复位状态。STM32半双工SPI模式下MOSI和MISO合并成一根线配置时要把SPI设成半双工模式否则数据方向不对。半双工模式适合单线通信的场景比如某些传感器只支持单线SPI。5.3 UART通信异常排查UART通信异常先看波形。TX有波形但RX没数据查RX线有没有接对、波特率是否一致。波形有但数据错查数据位、停止位、校验位配置。数据偶尔错查波特率误差和地线连接。长距离通信要加隔离或者用RS485差分。UART阻塞和非阻塞的选择如果发送数据量小、实时性要求不高用阻塞发送最简单如果发送数据量大或者要同时处理其他任务用DMA发送。接收端强烈建议用DMA加IDLE中断不要用阻塞接收容易卡死。5.4 I2S音频问题排查I2S没声音先查MCLK。很多编解码器没有MCLK就不工作。然后查BCLK和LRCLK频率BCLK应该是采样率×位深×声道数LRCLK应该等于采样率。如果频率对但没声音查数据格式I2S标准、左对齐、右对齐这几种格式数据位置不同配错了就是噪声。I2S和SPI复用引脚时配置I2S之前要确保SPI已经关闭否则引脚状态冲突。STM32的I2S和SPI共用外设切换模式要重新初始化。6. 选型建议和实战经验6.1 什么场景选什么协议传感器网络、多个低速设备挂一条总线选I2C。EEPROM、温度传感器、加速度计、IO扩展芯片这些都是I2C的典型应用。I2C的优点是线少、支持多设备缺点是速率低、有总线仲裁开销。需要高速传输大量数据选SPI。SPI Flash、TFT屏幕、高速ADC、以太网控制器这些都用SPI。SPI的优点是速率高、全双工、协议简单缺点是没有应答机制、每个设备一根CS线。两个设备之间简单通信选UART。调试串口、GPS模块、蓝牙模块、4G模块这些都用UART。UART的优点是简单、通用、不需要时钟线缺点是没有多设备能力、速率有限。音频数据传输选I2S。音频编解码器、数字麦克风、音频DAC这些都用I2S。I2S的优点是专为音频设计、时钟精确、支持多声道缺点是只能用于音频。6.2 我踩过的坑和实操心得第一个坑I2C上拉电阻用10kΩ100kHz能通400kHz就不行。后来换成4.7kΩ解决。教训是I2C上拉电阻要根据速率和总线电容算不能随便选。第二个坑SPI的CS用软件控制在DMA传输完成中断里拉高但没等BSY清零导致最后一个字节偶尔丢失。后来在拉高CS前加了while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_BSY) SET)等待问题消失。第三个坑UART用阻塞接收主循环里等数据结果其他任务全卡住。后来改成DMA加IDLE中断主循环完全不受影响。第四个坑I2S配置时MCLK没使能WM8978一直没声音。查了半天寄存器配置最后发现是MCLK引脚没配置成复用输出。教训是I2S调试先查时钟再查数据。第五个坑SPI硬件片选和软件片选混用CubeMX里选了硬件NSS但代码里又手动控制CS引脚结果两个信号打架通信时好时坏。后来统一用软件片选问题解决。6.3 逻辑分析仪的使用技巧逻辑分析仪是调试通信协议的利器。抓I2C时触发条件设成SDA下降沿且SCL为高采样率至少是SCL频率的10倍。抓SPI时触发条件设成CS下降沿采样率至少是SCLK的10倍。抓UART时触发条件设成RX下降沿采样率至少是波特率的10倍。解码器配置要跟协议参数一致。I2C要设地址位宽SPI要设CPOL和CPHAUART要设波特率和数据位。解码结果如果不对先检查采样率够不够再检查触发条件对不对。我习惯用逻辑分析仪同时抓多路信号比如I2C的SCL和SDA、SPI的CS和SCLK这样能直观看到时序关系。Saleae的逻辑分析仪软件很好用解码器丰富推荐。6.4 关于PMBus和I2C的区别PMBus是建立在I2C之上的电源管理协议物理层完全兼容I2C区别在应用层。PMBus定义了标准的命令格式用于读取电压、电流、温度等电源参数。如果你用I2C去读PMBus设备需要按照PMBus的命令格式来发数据不能随便读写寄存器。I2C扩展方面如果设备地址冲突可以用I2C多路复用器比如TCA9548A一路I2C扩展成八路每路可以挂相同地址的设备。这个方案在多传感器项目里很实用。7. 最后说几句实在的这四个协议没有谁好谁坏只有合不合适。我见过有人用SPI去接EEPROM也见过有人用I2C去接屏幕都能跑但都不是最优解。选型的核心是看你的需求速率、距离、设备数量、功耗、引脚资源这几个维度权衡一下答案就出来了。调试通信协议逻辑分析仪比万用表有用得多。万用表只能测静态电平逻辑分析仪能看时序。我建议每个做嵌入式的都备一个几百块的够用了。还有一点数据手册一定要仔细看。时序参数、模式配置、上拉电阻要求这些都在手册里写着。我踩过的坑大部分都是没仔细看手册导致的。手册看三遍调试少三天这话不夸张。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门