2. I2C通信时序详解:起始条件、停止条件、数据有效性、应答机制、时钟同步与仲裁

I2C总线的时序,说白了就是一套“谁在什么时候干什么”的规矩。我刚开始调I2C的时候,总觉得时序图看着头疼,后来发现只要抓住几个关键点,这东西其实挺直观的。咱们一个一个拆开讲。

2.1 起始条件与停止条件

总线上所有的通信,都从起始条件开始,以停止条件结束。这是I2C最基础的握手动作。

  • 起始条件(S):SCL为高电平时,SDA从高电平切换到低电平。
  • 停止条件(P):SCL为高电平时,SDA从低电平切换到高电平。

关键点:起始和停止条件都由主机产生。从机只能被动响应,不能主动发起。

我在项目中遇到过一个问题:某个传感器偶尔会“死掉”,怎么发起始条件都没反应。后来用示波器一抓,发现是停止条件没发干净,SDA被拉低了没释放。嗯,这里要注意——停止条件之后,SDA和SCL都必须回到高电平,总线才算空闲。

2.2 数据有效性

I2C的数据传输,有一个硬性规定:SCL高电平时,SDA必须稳定。只有在SCL低电平时,SDA才能变化。

为什么会这样?因为接收方是在SCL的上升沿或高电平期间采样SDA。如果SDA在SCL高电平时乱跳,采样结果就是错的。

SCL状态 SDA允许变化? 说明
高电平 不允许 数据稳定,接收方采样
低电平 允许 数据切换,准备下一位

你想想看,如果从机在SCL高电平时发现SDA变了,它会认为这是一个起始或停止条件,而不是数据位。这就是为什么I2C的时序要求这么严格。

2.3 应答机制

I2C的应答机制,是保证通信可靠性的核心。每传输一个字节(8位),接收方必须在第9个SCL周期拉低SDA,表示“我收到了”。

  • ACK(应答):第9个SCL周期,SDA为低电平。表示接收成功,可以继续发下一字节。
  • NACK(非应答):第9个SCL周期,SDA为高电平。表示接收失败或不想接收,主机应发送停止条件。

个人习惯:我写驱动时,会在每次发送完地址字节后检查ACK。如果没收到ACK,直接报错并重试,而不是继续发数据。这样可以避免很多奇怪的问题。

我曾经踩过一个坑:某个从机在写寄存器时,前两个字节都正常ACK,第三个字节突然NACK了。查了半天,发现是那个寄存器是只读的,我写了个不该写的地址。所以,应答机制不仅是“收到了”,还隐含了“我允许你继续”的意思。

2.4 时钟同步与仲裁

这两个概念,是多主机I2C系统的核心。虽然大部分场景下我们只用单主机,但理解它们能帮你更好地理解I2C的底层逻辑。

2.4.1 时钟同步

时钟同步,说白了就是多个主机同时发SCL时,谁的低电平时间长,谁就说了算。I2C总线是“线与”结构——谁拉低,总线就是低电平。

具体过程是这样的:

  1. 多个主机同时开始发送SCL时钟。
  2. 第一个主机拉低SCL,其他主机检测到SCL为低,也把自己的SCL拉低。
  3. 第一个主机释放SCL(拉高),但其他主机可能还在拉低。
  4. 总线SCL保持低电平,直到所有主机都释放了SCL。
  5. 所有主机都释放后,SCL才变高,开始下一个周期。

结果就是:SCL的低电平时间,由最慢的主机决定。这保证了所有主机都能跟上节奏。

2.4.2 仲裁

仲裁解决的是“多个主机同时发数据,谁赢”的问题。I2C的仲裁机制很巧妙——谁先发低电平,谁就赢。

过程是这样的:

  1. 多个主机同时发送起始条件,开始传输。
  2. 它们都在SDA上发送自己的数据位(包括地址位)。
  3. 如果一个主机发送高电平,但检测到SDA是低电平(因为另一个主机拉了低),它就输了。
  4. 输的主机立即释放SDA和SCL,退出竞争。
  5. 赢的主机继续传输,不受影响。

注意:仲裁只在SDA上进行,SCL的时钟同步保证了所有主机在同一个节奏下比较数据。仲裁过程中不会丢失数据,也不会破坏总线状态。

我记得有一次调试一个多主机系统,两个MCU同时访问同一个传感器。其中一个MCU总是莫名其妙地挂掉。后来发现是仲裁逻辑没处理好——输掉仲裁的MCU没有及时释放总线,导致总线锁死。解决方案很简单:在仲裁失败的中断服务程序里,把I2C外设复位一下就好了。

2.5 完整时序图

下面这张SVG图,把上面讲的几个关键时序节点串在了一起。你可以把它当作一个“速查表”,调试的时候对照着看。

I2C通信时序完整流程 I2C通信时序完整流程 起始条件 (S) 地址+读写位 ACK 数据字节 P 数据有效性规则: SCL高电平时,SDA必须稳定(不变化);SCL低电平时,SDA允许变化。 时钟同步 多个主机SCL线与 低电平时间由最慢主机决定 仲裁 谁先发低电平谁赢 输者释放总线,赢者继续 避坑指南: 1. 起始条件前确保总线空闲(SDA和SCL均为高) 2. 停止条件后留足够时间(至少4.7μs)再发下一个起始条件

2.6 时序参数速查

标准模式下,I2C的时序参数有明确要求。下面这张表是我常用的,调试时对照着看很方便。

参数 符号 标准模式 快速模式 说明
SCL时钟频率 f_SCL 100 kHz 400 kHz 最高频率
起始条件保持时间 t_HD;STA 4.0 μs 0.6 μs SCL低→SDA低
SCL低电平时间 t_LOW 4.7 μs 1.3 μs SCL低电平持续时间
SCL高电平时间 t_HIGH 4.0 μs 0.6 μs SCL高电平持续时间
数据建立时间 t_SU;DAT 250 ns 100 ns SDA稳定→SCL高
停止条件建立时间 t_SU;STO 4.0 μs 0.6 μs SCL高→SDA高

个人经验:我写驱动时,一般不会卡着最小时序参数去设计。比如标准模式下,我会把SCL周期设到12μs左右(约83kHz),留出余量。这样即使从机响应慢一点,或者总线电容大一点,通信也不会出问题。