2、I2C协议基础:起始条件、停止条件、数据有效性、字节格式与应答机制

好,咱们正式开始聊I2C协议的核心细节。说实话,很多工程师调不通I2C,往往不是硬件设计有问题,而是对这几个基础信号的理解不够透彻。我当年刚入行时也吃过这个亏——明明示波器上波形看着都对,但设备就是不响应。后来才发现,是起始条件的时序差了那么零点几微秒。

这一节,咱们就把I2C协议的五个基本要素掰开揉碎讲清楚。你搞懂了这些,后面排查故障时心里就有底了。

2.1 起始条件(S)与停止条件(P)

I2C总线在空闲时,SCL和SDA都是高电平。这是前提,你得记住。

起始条件(Start Condition)
当SCL为高电平时,SDA从高电平切换到低电平。这个下降沿告诉所有从机:「注意,要开始通信了!」

停止条件(Stop Condition)
当SCL为高电平时,SDA从低电平切换到高电平。这个上升沿表示:「好了,本次传输结束。」

关键点:起始和停止条件都是由主机(Master)产生的。从机只能被动检测,不能主动发起。

我在项目中遇到过一种情况:某次调试时,从机总是莫名其妙地卡死。用逻辑分析仪抓波形才发现,主机在发送停止条件时,SDA的上升沿不够陡峭,从机根本没识别到。嗯,这里要注意——停止条件的建立时间(tSU:STO)至少需要4.7μs(标准模式100kHz下)。

个人经验:我建议你在写驱动时,起始和停止条件用独立的函数封装。这样后期调试时,可以单独测量这两个信号的时序,方便定位问题。

2.2 数据有效性

I2C的数据采样规则其实很简单,就一句话:

在SCL的高电平期间,SDA上的数据必须保持稳定。

换句话说,SDA只能在SCL为低电平时改变。你想想看,如果SCL高电平时SDA变了,那就会被误判为起始或停止条件。

这个规则我刚开始总觉得多余——不就是个电平变化吗?直到有一次,我调试一个温湿度传感器,读出来的数据总是跳变。查了半天,发现是SDA线上有毛刺,在SCL高电平时恰好触发了一个伪起始条件。从那以后,我设计PCB时都会在SDA和SCL上加小电容滤波,一般10pF到100pF就够用。

参数 标准模式(100kHz) 快速模式(400kHz)
数据建立时间(tSU:DAT) ≥ 250ns ≥ 100ns
数据保持时间(tHD:DAT) ≥ 0ns ≥ 0ns

避坑指南:我曾经遇到过从机要求数据保持时间大于0的情况。虽然I2C规范说tHD:DAT可以为0,但某些芯片(尤其是老款)对保持时间有额外要求。建议你设计时留点余量,比如保持100ns以上。

2.3 字节格式

I2C传输的数据是以字节为单位的,每个字节8位。传输时,高位(MSB)在前,低位(LSB)在后。

一个完整的字节传输流程是这样的:

  1. 主机在SCL低电平时逐位设置SDA电平
  2. 每发送完一个位,主机产生一个SCL脉冲
  3. 从机在SCL高电平时采样SDA
  4. 重复8次,完成一个字节
  5. 第9个SCL脉冲用于应答位

说白了,就是8个数据位加1个应答位,一共9个SCL时钟周期。

我记得有一次,一个实习生问我:「为什么I2C不一次传16位?那样不是更快吗?」我笑了笑说:「你想想看,如果一次传16位,那应答机制怎么处理?从机怎么告诉你它准备好了?这就是I2C的设计哲学——简单可靠,而不是追求极致速度。」

2.4 应答机制(ACK/NACK)

应答机制是I2C协议中最巧妙的设计之一。它让主机和从机之间有了最基本的「握手」能力。

应答(ACK)
在第9个SCL脉冲期间,接收方将SDA拉低,表示「收到,请继续」。

非应答(NACK)
在第9个SCL脉冲期间,接收方保持SDA为高电平,表示「没收到」或「不想收」。

重要规则:应答位是由接收方产生的。主机发送数据时,从机产生应答;主机接收数据时,主机自己产生应答。

这里有个容易混淆的点:主机接收数据时,如果还想继续读,就发ACK;如果不想读了,就发NACK,然后发停止条件。我刚开始做项目时,就搞反过——主机读最后一个字节时发了ACK,结果从机又继续发数据,导致总线混乱。

我曾经在一个项目中,用I2C读取一个24C02 EEPROM。每次读到最后,主机发NACK后立即发停止条件,但EEPROM就是不释放总线。后来查手册才发现,这个芯片要求NACK之后至少等待4个SCL周期才能发停止条件。嗯,这就是典型的「芯片不按套路出牌」的情况。

调试技巧:当你发现I2C通信卡死时,先检查应答位。用示波器看第9个SCL脉冲时SDA的电平。如果一直是高电平(NACK),说明从机根本没响应。这时候别急着查硬件,先确认从机地址对不对。

2.5 完整的数据帧格式

把上面这些要素组合起来,一个完整的I2C数据帧长这样:

起始条件 | 7位从机地址 | 读/写位 | 应答位 | 数据字节 | 应答位 | ... | 数据字节 | 非应答位 | 停止条件

举个例子,主机向地址为0x50的从机写入一个字节0x3A:

S 1010000 0 A 00111010 A P

其中:

  • S = 起始条件
  • 1010000 = 从机地址(0x50左移1位)
  • 0 = 写操作
  • A = 应答位
  • 00111010 = 数据字节0x3A
  • A = 从机应答
  • P = 停止条件

你可能会问:「为什么地址要左移一位?」其实I2C规范里,地址是7位的,但传输时占8位,最低位是读写标志。所以0x50这个地址,实际发送的是0xA0(写)或0xA1(读)。

我记得第一次用逻辑分析仪抓I2C波形时,看到地址是0xA0,还以为是芯片手册写错了。后来才明白这个左移的套路。说白了,这就是I2C协议为了节省一个位而做的设计。

注意事项:有些从机支持10位地址。10位地址的格式比较复杂,需要两个字节来传输地址。如果你用的芯片是10位地址,记得仔细看数据手册,别按7位地址去算。

好了,这一节的内容就这些。起始条件、停止条件、数据有效性、字节格式、应答机制——这五个要素构成了I2C通信的基石。你把这些搞懂了,后面看时序图、写驱动、排查故障都会轻松很多。

下一节,咱们聊聊I2C的三种传输模式:主机发送、主机接收、以及复合格式。到时候我会结合具体的代码示例,让你看看这些协议要素在实际中是怎么用的。