2. I2C 时序基础:起始条件、停止条件、数据有效性、字节传输格式、ACK/NACK 机制

I2C 总线上的所有通信行为都建立在严格的时序协议之上。理解起始条件、停止条件、数据有效性、字节传输格式以及应答机制,是后续配置寄存器实现从机地址匹配与应答时序的基石。本章将逐一拆解这些核心时序单元,并辅以图形化说明。

2.1 起始条件 (START Condition) 与停止条件 (STOP Condition)

SCL 为高电平时,SDA 上的特定跳变定义了总线的开始与结束。这两个条件是总线空闲与通信状态的切换标志。

条件 SCL 状态 SDA 跳变方向 功能说明
起始条件 (S) 高电平 从高电平 → 低电平 通知所有从机:总线被主机占用,准备开始传输
停止条件 (P) 高电平 从低电平 → 高电平 通知所有从机:主机释放总线,通信结束
关键时序约束:起始条件和停止条件必须在 SCL 为高电平时产生。在 SCL 低电平时,SDA 的变化被视为数据位传输,而非起始或停止信号。
I2C 起始条件与停止条件波形 起始条件 (S) 与 停止条件 (P) 波形 SCL S SDA↓ P SDA↑ SCL 高电平期间 SDA 变化

2.2 数据有效性 (Data Validity)

I2C 协议规定,数据位的采样发生在 SCL 的上升沿。为了保证数据被正确采样,SDA 上的电平必须在 SCL 低电平期间完成切换,并在 SCL 高电平期间保持稳定。

  • 数据变化:仅在 SCL 为低电平时,SDA 允许改变电平。
  • 数据采样:在 SCL 的上升沿,接收方锁存 SDA 上的当前电平。
  • 数据保持:在 SCL 高电平期间,SDA 必须保持稳定,不得跳变。
硬件设计启示:MCU 的 I2C 外设在配置 GPIO 时,必须将 SDA 和 SCL 设置为开漏输出(Open-Drain),并外接上拉电阻。开漏结构允许总线被多个设备拉低,且能实现线与逻辑,这是数据有效性和仲裁的基础。

2.3 字节传输格式 (Byte Transfer Format)

I2C 总线以字节为单位进行数据传输,每个字节固定为 8 位,且高位(MSB)在前。每传输完一个字节后,接收方必须回复一个应答位(ACK/NACK)。

单字节传输序列:

  1. 主机产生起始条件 (S)。
  2. 主机发送 8 位数据(高位在前)。
  3. 主机释放 SDA(置为高阻),等待从机应答。
  4. 从机在第 9 个 SCL 周期拉低 SDA(ACK)或保持高电平(NACK)。
  5. 主机产生停止条件 (P) 或重复起始条件 (Sr)。
SCL 周期 1 - 8 9
数据线 (SDA) 主机驱动的 8 位数据 (D7-D0) 从机驱动的应答位 (ACK/NACK)
时钟线 (SCL) 主机产生 8 个脉冲 主机产生第 9 个脉冲
数据方向 主机 → 从机 从机 → 主机
I2C 字节传输格式与应答时序 字节传输格式:8 位数据 + 1 位应答 SCL D7 D6 D5 D4 D3 D2 D1 D0 8 位数据由主机驱动 (MSB First) 第 9 个 SCL 周期:从机拉低 SDA = ACK (应答) / 保持高 = NACK (非应答) 采样点

2.4 ACK/NACK 机制详解

应答机制是 I2C 协议保证通信可靠性的核心。每传输完一个字节,接收方必须给出应答信号。主机通过检测应答位来判断下一步操作。

2.4.1 应答信号定义

  • ACK (Acknowledge):在第 9 个 SCL 周期,接收方将 SDA 拉低,表示“已成功接收字节,请继续发送”。
  • NACK (Not Acknowledge):在第 9 个 SCL 周期,接收方保持 SDA 为高电平,表示“接收失败”或“传输结束”。

2.4.2 常见 NACK 场景

场景 发送方 接收方 结果
从机地址不匹配 主机 所有从机 无从机拉低 SDA,主机收到 NACK,停止通信
从机忙或未就绪 主机 目标从机 从机无法处理数据,回复 NACK
主机读操作结束 从机 主机 主机在最后一个字节回复 NACK,通知从机停止发送
非法命令或数据 主机 目标从机 从机不支持该命令,回复 NACK 拒绝
驱动开发注意:在配置 MCU 的 I2C 从机模式时,必须正确设置从机地址寄存器(如 STM32 的 OAR1/OAR2)。当地址匹配后,硬件自动产生 ACK。如果软件未及时读取接收数据寄存器(DR),硬件可能因 RXNE 溢出而自动回复 NACK,导致通信失败。务必在中断服务函数中快速处理数据。

2.5 完整帧结构示例

一个典型的 I2C 写操作帧包含:起始条件、7 位从机地址 + 读写位(R/W)、从机 ACK、8 位数据、从机 ACK、停止条件。下图展示了主机向地址为 0x50 的从机写入一个字节 0xA5 的完整时序。

// 伪代码:主机发送一字节数据到从机
// 1. 产生起始条件
I2C_GenerateSTART(ENABLE);
// 2. 等待起始条件完成
while(!I2C_CheckEvent(I2C_EVENT_MASTER_MODE_SELECT));
// 3. 发送从机地址 + 写位 (0x50 << 1 | 0 = 0xA0)
I2C_Send7bitAddress(0x50, I2C_Direction_Transmitter);
// 4. 等待地址发送完成并收到 ACK
while(!I2C_CheckEvent(I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
// 5. 发送数据 0xA5
I2C_SendData(0xA5);
// 6. 等待数据发送完成并收到 ACK
while(!I2C_CheckEvent(I2C_EVENT_MASTER_BYTE_TRANSMITTED));
// 7. 产生停止条件
I2C_GenerateSTOP(ENABLE);
时序总结:起始条件 → 地址+R/W (8位) → ACK → 数据 (8位) → ACK → ... → 停止条件。每个字节后紧跟一个应答位,这是 I2C 协议最基础的时序单元。理解这个节奏,是后续配置寄存器实现从机应答的基础。