三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

嵌入式通信时序图解析:SPI、I2C、UART协议核心与调试实战

嵌入式通信时序图解析:SPI、I2C、UART协议核心与调试实战

1. 从“信号跳舞”说起:为什么时序图是嵌入式开发的“心电图”

干了这么多年嵌入式,调试过无数个SPI、I2C、UART外设,我越来越觉得,看时序图就像医生看心电图。波形的高低起伏、信号的先后顺序,直接反映了通信协议的“健康状况”。很多新手朋友拿到一个传感器或者屏幕,照着例程把代码一抄,发现通信不通,然后就懵了。这时候,十有八九的问题,都出在对时序的理解偏差上。时序图,就是那个能帮你精准定位病灶的“心电图”。

SPI、I2C、UART,这三个可以说是嵌入式世界里最经典、最基础的通信协议了。它们各有各的“性格”:SPI像个急性子,全双工、高速率,但线多;I2C像个讲究人,两根线搞定一切,但规矩多、速度慢;UART则是个独行侠,简单直接,点对点通信,异步进行。无论你用的是STM32、GD32、ESP32还是RK3588,无论你驱动的是LCD(比如ST7789)、EEPROM、传感器还是USB转串口芯片(如FT232R、CP2102N),都绕不开对它们时序的深刻理解。

很多人觉得看芯片手册里的时序图很头疼,一堆参数:t_SU;STA,t_HD;DAT,t_V…… 其实,这些参数背后,是数字电路在物理世界运行必须遵守的“交通规则”。时钟线就是红绿灯,数据线就是车流,建立时间、保持时间就是车辆在路口停稳和启动的时机。时序图,就是用图形化的方式,把这些复杂的“交通规则”一目了然地展示出来。

这篇文章,我就结合自己踩过的坑和调通的经验,带你像看故事书一样,把SPI、I2C、UART这三种协议的时序图彻底掰开揉碎讲明白。我们不只讲“图上画了什么”,更要讲“为什么这么画”,以及“写代码时如何对应这些图”。当你真正看懂时序图,你会发现,之前那些玄学般的通信失败、数据错乱、器件无响应,都有了清晰的排查思路。

2. UART时序图:异步通信的“心跳与脉搏”

我们先从最简单的UART开始。它没有时钟线,通信双方全靠事先约定好的“心跳”(波特率)来同步,所以时序图的核心,就是解读这个异步的“脉搏”信号。

2.1 帧结构:起始位、数据位、停止位的“三部曲”

一张标准的UART时序图,展现的是一个完整的数据帧。我们以传输一个字节0x55(二进制01010101)为例,设置波特率为9600,8位数据位,无校验位,1位停止位。

想象一条水平的时间轴,代表UART的TX或RX引脚上的电平变化。默认状态下,线路处于空闲(Idle)状态,为高电平(逻辑‘1’)。

起始位(Start Bit):这是帧开始的“发令枪”。线路会从高电平拉低到低电平(逻辑‘0’),并持续一个比特的时间。这个下降沿告诉接收方:“注意,数据要来了!” 在代码配置中,你必须确保发送和接收双方对起始位的判定逻辑一致。有些低级驱动库可能会在起始位检测上做滤波,防止干扰,这就是为什么接线过长或干扰大时,UART容易出错的第一道关卡。

数据位(Data Bits):紧接着起始位之后,就是实际要传输的数据,从最低有效位(LSB)开始发送。对于0x5501010101),LSB是‘1’,所以时序图上第一个数据位是高电平;接着是‘0’(低电平),以此类推。这里的关键是采样点。接收方会在每个比特时间的中间点(通常是50%位置)对线路电平进行采样,以此判定是‘0’还是‘1’。因此,发送和接收的波特率必须尽可能一致。9600波特率下,一个比特时间约104微秒。如果双方晶振误差累积导致采样点漂移到比特的边缘,就可能采错数据。这就是为什么一些高精度应用要用外部晶振或者通信双方时钟同源。

停止位(Stop Bit):数据位发送完毕后,线路必须拉回高电平(逻辑‘1’),并至少持续1个(可配置为1.5或2个)比特时间。这个高电平段有两个作用:一是作为本帧数据的结束标志;二是为下一帧可能的起始位下降沿提供必要的空闲状态。如果停止位被检测为低电平,接收端会报告一个“帧错误”(Framing Error),这在逻辑分析仪或调试串口中常见。

注意:很多人在使用USB转TTL模块(如FT232R、CH340、CP2102)时,只接TX、RX和GND,不接VCC,这在大多数情况下没问题。但有些目标板需要从USB转接板获取一个参考电平来确定逻辑‘1’的电压,如果遇到通信不稳定,检查并连接VCC(3.3V或5V)或许能解决问题。这就是时序中“高电平”的具体电压定义问题。

2.2 关键时序参数与软件实现要点

UART的时序看似简单,但魔鬼在细节里。除了波特率,还有几个软件需要关注的点:

  1. 波特率容差:如前所述,收发双方波特率不一致会导致采样点偏移。通常,误差需要控制在2-3%以内(具体看芯片手册)。STM32等MCU的UART波特率发生器计算时,有时为了取整会有微小误差,需要评估这个误差是否在容限内。
  2. 缓冲区与流控:当发送速度大于接收方处理速度时,就需要流控(RTS/CTS)。时序图上,RTS和CTS就是两根协调流量的“握手线”。如果没有硬件流控,就需要在软件层设计足够的缓冲区,并可能使用XON/XOFF软件流控。否则,数据会覆盖丢失,表现为数据不完整。
  3. 中断与DMA:在代码层面,时序的“实时性”体现在如何及时响应数据。对于接收,通常配置为“每收到一个字节产生一次中断”。在中断服务程序里,你必须尽快将数据从硬件寄存器读到内存缓冲区。如果中断处理函数写得过于复杂或被打断,可能导致下一个字节已经到来并覆盖了当前寄存器,造成“溢出错误”(Overrun Error)。使用DMA可以大大缓解此问题,DMA会在硬件层面自动搬运数据,解放CPU。

一个典型的排查案例:设备发送数据正常,但PC端用串口助手接收时,末尾字符总是乱码或重复。这很可能是因为你的发送代码在发送完最后一个字节后,立即关闭了UART外设或进入了低功耗模式。UART硬件发送一个字节需要时间,你需要等待“发送完成”(TC)标志位,或者“发送数据寄存器空”(TXE)标志位,确保最后一帧数据的停止位已经完全发出,线路回到了空闲高电平状态,然后再进行后续操作。否则,最后一帧的停止位可能被“截断”,导致接收方帧错误。这就是时序完整性在代码上的体现。

3. I2C时序图:两根线上的精密“社交礼仪”

如果说UART是独白,那I2C就是一场在两根线(SDA数据线、SCL时钟线)上进行的精密双人舞,有着严格的礼仪(协议)。其时序图充满了各种条件、状态和时限。

3.1 起止信号、数据传输与应答:一次完整的“对话”

I2C的通信总是由主机(Master)发起和控制时钟(SCL)。我们分解一次完整的写数据过程。

起始条件(START Condition):当时钟线SCL为高电平时,主机将数据线SDA从高拉低。这个独特的“高电平期间的下跳沿”是一个全局的、不可忽视的信号,所有挂在总线上的从机(Slave)都会检测到这个信号,并准备接收接下来的地址帧。在软件模拟I2C时,你必须确保SDA的变化发生在SCL为高期间,并且拉低SDA的速度(斜率)不能太慢,否则可能被误认为是干扰。

从机地址与读写位:起始条件后,主机开始发送第一个字节。这个字节的高7位是从机地址(如0x50),最低位是读写控制位(0表示写,1表示读)。注意,数据是在SCL为低电平时变化,在SCL为高电平时保持稳定,供从机采样。这就是I2C数据传输的基本单元。

应答(ACK)与非应答(NACK):每个字节(8位)发送完毕后,发送方(无论是主机还是从机)会释放SDA线(输出高阻态,由上拉电阻拉高),并在第9个时钟脉冲期间,由接收方拉低SDA线表示应答(ACK)。如果接收方没有拉低(SDA保持高),则表示非应答(NACK)。ACK是I2C协议中至关重要的“确认收到”机制。主机发送地址后,如果对应地址的从机存在,它必须回ACK,否则主机会认为寻址失败。同样,主机接收完最后一个字节后,应回NACK,示意从机停止发送。

停止条件(STOP Condition):通信结束,主机需要发出停止信号。当时钟线SCL为高电平时,主机将数据线SDA从低拉高。这个“高电平期间的上跳沿”标志本次传输终结,总线恢复空闲(SDA和SCL均被上拉电阻拉高)。

3.2 关键时序参数与硬件/软件陷阱

I2C的时序参数繁多,是调试的重灾区。芯片手册里通常会给出如下参数:

  • t_{SU;STA}: 起始条件建立时间。在发起START前,SDA和SCL高电平需要保持的最小时间。
  • t_{HD;STA}: 起始条件保持时间。START条件中SDA拉低后,SCL继续保持高电平的最小时间。
  • t_{LOW}/t_{HIGH}: SCL时钟低电平/高电平时间。这决定了通信速率(标准模式100kbps,快速模式400kbps)。
  • t_{SU;DAT}: 数据建立时间。SDA数据必须在SCL上升沿到来之前保持稳定的最小时间。
  • t_{HD;DAT}: 数据保持时间。在SCL下降沿之后,SDA数据还需要保持稳定的最小时间。
  • t_{SU;STO}: 停止条件建立时间。在发起STOP前,SCL低电平需要保持的最小时间。

为什么需要上拉电阻?SDA和SCL线是开漏(Open-Drain)输出。这意味着器件只能把线拉低(输出0),不能主动拉高(输出1)。总线的高电平状态完全依靠外部上拉电阻(通常3.3K-10K)将电压拉至VCC。没有上拉电阻,总线永远无法呈现高电平,通信根本无法开始。电阻值的选择是个平衡:太小则功耗大,且下拉速度过快可能产生过冲;太大则上升沿太慢,可能无法在高速模式下满足上升时间要求,导致时序 violation。

电平转换的坑:当总线上的器件使用不同电压(如5V MCU和3.3V传感器)时,需要电平转换。一个简单常用的方案是用两个NMOS管搭建双向电平转换电路。但这里有个经典陷阱:倒灌电流。如果设计不当,当低压侧试图输出高电平时,高压侧会通过MOSFET的体二极管向低压侧供电,可能导致低压侧芯片损坏或工作异常。确保转换电路中的MOSFET具有独立的体二极管,或者使用专用的双向电平转换芯片(如TXS0102),是更稳妥的做法。

软件模拟 vs 硬件I2C:很多开发者因为STM32标准库的硬件I2C不好用而转向软件模拟(GPIO模拟时序)。软件模拟非常灵活,不受硬件bug影响,但缺点明显:占用CPU、时序精度受中断影响、难以实现高速率。而硬件I2C效率高、不占CPU。像GD32、STM32的LL库或HAL库,其硬件I2C实现已经完善很多。使用硬件I2C的关键是正确配置时序寄存器,根据上拉电阻、布线电容计算出的总线上升时间,来设置I2C_TIMINGR寄存器(对于STM32Cube系列),让硬件产生的SCL波形严格满足从机器件手册的要求。直接套用例程的配置值,而不考虑自己板子的实际情况,是硬件I2C失败的主要原因之一。

关于“重复起始条件(Repeated Start)”:这不是一个独立的起止信号。它是指在一次通信中,不发停止条件,直接再次发一个起始条件。常用于切换读写操作。例如,主机先发送设备地址(写),然后发送存储寄存器地址,接着再发一个重复起始条件,并发送设备地址(读),开始读取数据。这保证了整个读写过程总线控制权不释放,防止其他主机干扰。标准I2C协议是支持重复起始条件的,它在协议层面被视为一次新的开始,而非停止后再开始。

4. SPI时序图:全双工高速“流水线”

SPI是同步、全双工的通信协议,像一条高速流水线。其时序图的核心是时钟极性(CPOL)和时钟相位(CPHA)的组合,这决定了数据采样的精确时刻。

4.2 四种模式与采样边沿:CPOL和CPHA的排列组合

SPI模式由CPOL和CPHA两个参数决定,共四种模式(Mode 0-3)。这是理解SPI时序的钥匙。

  • CPOL (Clock Polarity):时钟极性。
    • CPOL=0:SCK空闲时为低电平。
    • CPOL=1:SCK空闲时为高电平。
  • CPHA (Clock Phase):时钟相位。
    • CPHA=0:数据在SCK的第一个边沿(如果CPOL=0,就是上升沿;如果CPOL=1,就是下降沿)被采样,在第二个边沿切换。
    • CPHA=1:数据在SCK的第二个边沿被采样,在第一个边沿切换。

模式0 (CPOL=0, CPHA=0):最常用的模式。SCK空闲低,数据在SCK上升沿被主机和从机采样,在下降沿切换。对于接收方(无论是主机还是从机),必须在上升沿到来之前,数据已经稳定在MOSI/MISO线上(满足建立时间t_{SU}),并在上升沿之后还要保持一小段时间(满足保持时间t_{HD})。

模式3 (CPOL=1, CPHA=1):另一种常用模式。SCK空闲高,数据在SCK下降沿采样,上升沿切换。

提示:主从设备的SPI模式必须完全一致,否则采样的数据全是错的。很多SPI器件手册会明确指定支持的模式。驱动LCD(如ST7789)、FLASH芯片时,第一件事就是确认模式。

4.1 核心信号线:MOSI, MISO, SCK, CS

SPI时序图通常包含四条线:

  • SCK (Serial Clock):时钟线,由主机产生。所有数据传输都以此时钟为基准。
  • MOSI (Master Out Slave In):主机输出,从机输入数据线。
  • MISO (Master In Slave Out):主机输入,从机输出数据线。注意,SPI是全双工,数据可以同时收发。
  • CS/SS (Chip Select / Slave Select):片选线,低电平有效。主机通过拉低某条CS线来选择与之通信的从机。这是SPI支持多从机的关键。

一次SPI数据传输的时序可以这样描述:主机拉低对应从机的CS线,然后开始产生SCK时钟。在SCK的每个周期,主机通过MOSI线发送一位数据,同时从机通过MISO线也发送一位数据。在SCK的某个边沿(由模式决定),双方同时采样输入线上的数据。8个或16个时钟周期后,一个字节或一个字传输完毕,主机拉高CS线。

4.3 硬件片选与软件片选:灵活性与稳定性的权衡

片选CS的控制方式有两种:

  • 硬件片选(Hardware NSS):使用MCU SPI外设专用的NSS引脚。硬件可以自动管理NSS信号,在数据传输开始时自动拉低,结束时自动拉高。这种方式省心,但通常一个SPI外设只有一个硬件NSS引脚,难以直接控制多个从机。
  • 软件片选(Software NSS):使用任意一个GPIO引脚来模拟CS功能。在通信前手动拉低GPIO,通信后再手动拉高。这是最常用、最灵活的方式,可以轻松控制多个从机(每个从机独占一个GPIO)。

使用软件片选时,有一个至关重要的细节:必须在启动SPI传输之前拉低CS,并在确认SPI传输完全结束之后再拉高CS。所谓“完全结束”,对于STM32的HAL库,意味着你需要等待HAL_SPI_TransmitReceive这类函数执行完毕(或者检查SPI->SR寄存器中的BSY标志位为0)。如果在DMA传输或硬件事务还未完成时就拉高CS,可能导致最后一个或几个比特数据传输不完整,造成通信错误。这就是为什么在复杂应用中,SPI操作需要“锁”(Lock)的原因——确保一段完整的SPI操作(包含CS控制、数据收发)不被其他任务打断,保证时序的原子性。

SPI的四种模式时序图对比:当你用逻辑分析仪抓取波形时,首先要看SCK空闲状态(确定CPOL),然后看数据是在SCK的第一个边沿还是第二个边沿稳定并被采样(确定CPHA)。通过对比抓取的波形和器件手册的时序图,就能迅速确认模式是否匹配。例如,如果手册要求模式0,但你发现数据在SCK下降沿后才变化,那很可能你配置成了模式1或3。

5. 实战对比与排查:当通信失败时,如何用时序图破案

理解了单个协议的时序图,我们还需要横向对比,并掌握一套基于时序图的排查方法。

5.1 三大协议核心差异速查表

特性维度UARTI2CSPI
通信类型异步、全双工/半双工同步、半双工同步、全双工
信号线数量最少2线 (TX, RX),可加流控2线 (SDA, SCL)3线或4线 (SCK, MOSI, MISO, CS...)
拓扑结构点对点多主多从,总线型一主多从,星型(每个从机独立CS)
时钟信号无(靠波特率)有(SCL,主机产生)有(SCK,主机产生)
数据速率低到中(常用115200 bps)低到中(标准100k,快速400k,高速3.4M)高(可达数十Mbps甚至上百Mbps)
寻址方式无(物理连接决定)7位/10位从机地址硬件片选(CS)线
流控/仲裁可硬件/软件流控有时钟拉伸和多主机仲裁无,主机完全控制
关键时序关注点波特率精度、起始/停止位起止条件、ACK、时钟拉伸、上拉电阻CPOL/CPHA模式、CS控制时机、建立保持时间
典型应用场景调试打印、模组AT指令、简单传感器板内低速外设(EEPROM, 传感器, RTC)高速外设(Flash, LCD, ADC, 音频Codec)

5.2 基于逻辑分析仪的时序问题诊断流程

当通信失败时,逻辑分析仪是你的“终极武器”。它能将抽象的时序以波形形式直观展现。以下是一个通用的排查流程:

  1. 第一步:抓取波形。将逻辑分析仪的探头连接到通信线路上(UART接TX/RX,I2C接SDA/SCL,SPI接SCK/MOSI/MISO/CS)。设置合适的采样率(至少为通信速率的4-5倍以上)。
  2. 第二步:基础检查
    • 电平是否正常?确认高电平电压是否符合预期(3.3V或5V)。如果电压不足,可能是上拉电阻过大、负载过重或驱动能力不足。
    • 信号是否干净?观察波形是否有严重的过冲、振铃或毛刺。这可能是阻抗不匹配、布线过长、靠近干扰源所致。可能需要串联小电阻(如22欧姆)或调整布局。
  3. 第三步:协议层解码与比对
    • 使用逻辑分析仪的协议解码功能(UART, I2C, SPI解码器),将波形直接翻译成数据字节。看解码出的数据是否与你代码期望发送/接收的一致。
    • 如果不一致,进入深度时序分析
      • 对于UART:测量比特宽度,计算实际波特率。检查起始位是否为明显的低电平,停止位是否为高电平。数据位采样点是否在比特中间?
      • 对于I2C:检查起始条件(SDA下降沿时SCL是否高?)、停止条件(SDA上升沿时SCL是否高?)。检查每个字节后的第9个时钟周期,SDA是否被拉低(ACK)?从机地址是否正确?测量SCL高低电平时间是否满足从机芯片手册要求(t_{LOW},t_{HIGH})?SDA数据变化是否发生在SCL低电平期间?
      • 对于SPI:确认CPOL和CPHA模式。检查数据是在哪个时钟边沿稳定的?测量数据建立时间(t_{SU})和数据保持时间(t_{HD})是否满足从机要求?检查CS信号:是否在数据帧开始前足够早拉低,结束后足够晚拉高?CS有效期间,SCK时钟脉冲数是否正确?
  4. 第四步:软件逻辑对照。将解码出的原始数据流与你代码中的发送/接收缓冲区进行对比。有时问题不在硬件时序,而在软件逻辑,比如缓冲区指针管理错误、中断与主程序竞争条件、DMA配置错误等。

一个I2C电平转换的排查实例:我曾遇到一个5V MCU与3.3V传感器通过MOSFET电平转换电路通信,时好时坏。用逻辑分析仪抓取MCU侧(高压侧)和传感器侧(低压侧)的SDA波形对比发现,当MCU发送完数据释放SDA(变为高阻)后,高压侧SDA电压上升缓慢,而低压侧上升迅速。进一步分析发现,是高压侧的上拉电阻(10k)阻值过大,而总线电容(来自走线和器件引脚)导致上升沿时间t_R过长,在400kbps快速模式下,无法在SCL高电平周期内达到稳定的高电平阈值,导致从机采样错误。将高压侧上拉电阻减小到3.3k后问题解决。这个案例说明,时序参数t_R(上升时间)同样关键,它受RC常数影响。

看懂时序图,不仅仅是能识别波形,更是要理解每个参数背后的物理意义和电路约束。它连接了芯片手册上的规范、你设计的硬件电路以及你编写的软件代码。下次再遇到通信故障,别急着瞎改代码,拿出逻辑分析仪,对照时序图,像侦探一样分析波形,你会发现解决问题的方法如此清晰直接。这份从波形中洞察真相的能力,是嵌入式工程师从入门走向精通的必经之路。

← 返回列表