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

I2C协议,说白了就是一套“谁说话、谁听话、什么时候说、说完怎么确认”的规矩。我刚开始接触I2C时,觉得它比SPI啰嗦——又要应答又要地址,但后来发现,正是这些机制让它在多设备场景下特别稳。

咱们今天就把I2C最核心的几个动作拆开揉碎。你把这些搞懂了,后面移植驱动就是套模板的事。

2.1 起始条件与停止条件

I2C总线上,SCL(时钟线)和SDA(数据线)平时都是高电平。这个状态叫“空闲态”。什么时候总线开始干活?就看这两个信号怎么跳变。

起始条件(START): SCL为高电平时,SDA从高电平切换到低电平。

停止条件(STOP): SCL为高电平时,SDA从低电平切换到高电平。

你想想看,这两个条件都是“SCL高电平期间,SDA跳变”。为什么这么设计?因为数据采样也是在SCL高电平时做的,所以起始和停止用SDA跳变来区分,不会和数据位混淆。

我的习惯:写驱动时,我会把起始和停止封装成两个独立函数。每次调用前检查一下总线状态,防止在忙时误操作。

我曾经在一个项目里遇到过这样的坑:主机关闭后,从机还在输出低电平,导致下次起始条件发不出去。后来我加了一段“总线恢复”逻辑——先发9个时钟脉冲,让从机释放SDA。嗯,这招挺管用。

2.2 数据有效性

I2C的数据采样规则其实很简单:

  • SCL低电平期间: SDA可以变化(准备下一位数据)
  • SCL高电平期间: SDA必须稳定(主机采样数据)

换句话说,数据在SCL高电平时被“拍照”下来。你要是SCL高电平时去动SDA,要么被当成起始/停止条件,要么采样到错误电平。

注意: 数据线SDA的变化只能在SCL低电平时进行。这是硬性规定,违反了这个时序,从机根本不会理你。

我记得有一次调试一个温湿度传感器,波形怎么看都对,但读出来的数据全是0xFF。查了半天,发现是SCL高电平期间SDA有毛刺,把数据位全冲成1了。加了个小电容滤波,问题解决。

2.3 字节格式

I2C传输的基本单位是字节(8位),而且有固定的传输顺序:

  1. MSB先行: 先传最高位(bit 7),再传低位,最后是bit 0。
  2. 每字节后跟一个应答位: 第9个时钟周期用于应答。
  3. 无字节限制: 可以连续传多个字节,直到停止条件或重启条件。

举个例子,你要往一个I2C EEPROM的地址0x10写入数据0xA5,发送顺序是这样的:

起始条件 → 设备地址(0x50) + 写位(0) → 应答 → 寄存器地址(0x10) → 应答 → 数据(0xA5) → 应答 → 停止条件

这里设备地址0x50是7位地址左移1位的结果。嗯,很多新手在这里搞混——I2C地址是7位,但发送时占8位,最低位是读写标志。

位序 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
数据 1 0 1 0 0 0 0 R/W
说明 7位设备地址 (0x50) 0=写, 1=读

2.4 应答机制

应答是I2C最巧妙的设计之一。每发完一个字节,接收方必须在第9个时钟周期给出反馈:

  • ACK(应答): 接收方将SDA拉低,表示“收到了,继续发”。
  • NACK(非应答): 接收方保持SDA高电平,表示“没收到/不想收/传完了”。

关键规则: 应答信号由接收方控制SDA,主机提供第9个时钟。谁发数据,谁就释放SDA,让对方拉。

我个人的经验是:调试I2C时,先看应答位。如果地址发出去没等到ACK,八成是地址错了或者从机没上电。如果数据发出去从机回了NACK,可能是寄存器地址不对或者从机忙。

为什么会这样?因为从机在收到数据后需要时间处理,如果处理不过来,就会回NACK让主机等一下。有些从机甚至会用“时钟拉伸”来拖慢主机——把SCL拉低,逼主机等它准备好。

避坑指南: 我曾经在一个项目里忽略了从机的时钟拉伸,结果主机按固定速率发数据,从机跟不上,数据全乱套。后来加了超时检测,发现从机偶尔会拉伸时钟几百微秒。所以,写驱动时一定要处理时钟拉伸,别假设从机永远能跟上。

2.5 完整传输流程

把上面几个概念串起来,一次完整的I2C写操作流程是这样的:

  1. 主机发送起始条件
  2. 主机发送7位设备地址 + 写位(0)
  3. 从机回复ACK
  4. 主机发送8位寄存器地址
  5. 从机回复ACK
  6. 主机发送8位数据
  7. 从机回复ACK
  8. 主机发送停止条件

读操作稍微复杂一点,需要先写寄存器地址,再重启条件,然后切换成读模式。这个咱们后面章节会细讲。

I2C 协议核心时序结构 SCL SDA 起始 SCL高时 SDA下降沿 停止 SCL高时 SDA上升沿 数据采样区 SCL高电平 SDA必须稳定 字节传输 MSB → LSB 8位数据 + 1位应答 应答位 第9个时钟 接收方拉低=ACK 核心要点 起始/停止条件:SCL高时SDA跳变 | 数据有效性:SCL高采样,SCL低变化 | 字节格式:MSB先行,每字节后跟应答

2.6 常见问题与排查思路

最后,我把自己这些年踩过的坑整理成一张表,你写驱动时对照着查:

现象 可能原因 排查方法
起始条件发不出 SDA被从机拉低 发9个时钟脉冲复位从机
地址发完无ACK 地址错误/从机未上电 用示波器看SDA在第9时钟是否被拉低
数据读回全是0xFF SDA上拉电阻失效/从机未应答 检查上拉电阻(典型4.7kΩ)
偶尔通信失败 时钟拉伸未处理/中断干扰 加超时检测,关中断保护
停止条件后总线仍忙 从机未释放SDA 检查从机是否支持时钟拉伸

最后提醒一句: I2C的时序看着简单,但实际项目中干扰因素很多。上拉电阻、总线电容、从机响应时间,任何一个环节出问题,波形就变形。我建议你手边常备一个逻辑分析仪,抓一次波形比看十遍手册都管用。