4. I2C实战:主机发送与接收流程、从机地址配置、读写操作时序

好,咱们直接进入正题。I2C 通信,说白了就是主机和从机之间的一场「对话」。谁先开口,谁回应,数据怎么传,什么时候结束——这些都有严格的规矩。我刚开始调 I2C 的时候,总觉得时序图看着头疼,后来发现,只要抓住几个关键节点,其实没那么玄乎。

4.1 主机发送流程:从起始条件到停止条件

主机要发数据,第一步就是拉低 SDA(在 SCL 高电平时),告诉所有从机:「我要开始说话了」。这就是起始条件。之后,主机发出 7 位从机地址 + 1 位写标志(0)。每个从机都会收到这个地址,只有地址匹配的那个才会拉低 SDA 回应一个 ACK。

我个人的习惯是,在发送地址之前,先确认总线是空闲的——SCL 和 SDA 都是高电平。有一次我忽略了这一步,结果总线一直被某个从机拉着,地址发出去根本没回应。

主机发送单字节的标准流程:

  1. 主机产生起始条件(SDA 在 SCL 高时从高变低)
  2. 主机发送 7 位从机地址 + 写位(0)
  3. 从机在第 9 个 SCL 周期拉低 SDA 作为 ACK
  4. 主机发送 8 位数据字节
  5. 从机在第 9 个 SCL 周期拉低 SDA 作为 ACK
  6. 重复步骤 4-5 直到数据发完
  7. 主机产生停止条件(SDA 在 SCL 高时从低变高)

这里有个坑:如果从机没回应 ACK(SDA 保持高),主机就得停止发送。我曾经遇到过从机地址写错了一位,结果从机死活不 ACK,我查了半天才发现是 datasheet 上地址是 7 位,我多写了一位。

4.2 主机接收流程:从机如何把数据交给主机

主机接收和发送的流程类似,但方向反了。主机先发地址 + 读位(1),从机回应 ACK 后,主机变成接收者,从机开始发数据。每收到一个字节,主机要主动拉低 SDA 回应 ACK,告诉从机「继续发」。如果主机想结束接收,就在最后一个字节回应 NACK(保持 SDA 高),然后发停止条件。

你想想看,如果主机在最后一个字节还回 ACK,从机会以为你还要数据,继续发下一个字节,那就乱套了。

步骤 主机动作 从机动作 SDA 状态
1 发起始条件 等待 高→低
2 发地址 + 读位 地址匹配 主机驱动
3 释放 SDA 拉低 SDA(ACK) 从机驱动
4 接收 8 位数据 发送数据 从机驱动
5 拉低 SDA(ACK) 检测 ACK 主机驱动
6 重复 4-5 继续发送 交替
7 发 NACK + 停止 释放总线 主机驱动

小技巧:很多 I2C 从机(比如传感器)在读取多字节数据时,内部地址会自动递增。你只需要先写寄存器地址,再切换为读模式,就能连续读出多个寄存器的值。我常用这个方法来批量读取传感器数据,省掉重复写地址的步骤。

4.3 从机地址配置:7位地址与10位地址

I2C 从机地址最常见的是 7 位,范围 0x00~0x7F。但 0x00 是通用呼叫地址,0x01~0x07 保留,实际可用的是 0x08~0x77。每个从机芯片的 datasheet 都会写明它的地址,有些芯片会留出几个引脚(比如 A0、A1、A2)让你自己配置。

我记得有一次项目里用了 4 个同样的温度传感器,每个传感器的地址引脚接不同的电平(GND、VCC、悬空、接 SCL),这样就能在一条 I2C 总线上挂 4 个从机。如果地址冲突了,两个从机同时拉低 SDA,总线就乱了。

注意:10 位地址不常用,但如果你遇到,它的格式是:第一个字节的前 5 位是 11110,后 2 位是地址的高 2 位,第二个字节是地址的低 8 位。读写位还是放在第一个字节的最后一位。我做了这么多年,只在一些高端 ADC 和 EEPROM 上见过 10 位地址。

4.4 读写操作时序:组合格式与重复起始条件

I2C 的读写操作,很多时候不是单纯的发或收。比如你要读一个寄存器的值,得先告诉从机你要读哪个寄存器(写操作),然后再切换成读模式。这就用到了「重复起始条件」——不先发停止条件,直接再发一次起始条件,然后切换读写位。

说白了,就是一次完整的传输里,可以包含多个起始条件,但只有一个停止条件。这种组合格式在传感器和存储器驱动里非常常见。

// 伪代码:读取从机寄存器 0x10 的值
// 1. 起始条件
I2C_Start();
// 2. 发送从机地址 + 写位 (0x50 << 1 | 0)
I2C_SendByte(0xA0);  // 假设从机地址 0x50
// 3. 等待 ACK
if (!I2C_WaitAck()) error();
// 4. 发送寄存器地址
I2C_SendByte(0x10);
// 5. 等待 ACK
if (!I2C_WaitAck()) error();
// 6. 重复起始条件(不停止)
I2C_RepeatedStart();
// 7. 发送从机地址 + 读位 (0x50 << 1 | 1)
I2C_SendByte(0xA1);
// 8. 等待 ACK
if (!I2C_WaitAck()) error();
// 9. 读取一个字节,并回应 NACK
uint8_t data = I2C_ReadByte();
I2C_SendNack();
// 10. 停止条件
I2C_Stop();

这个流程里,重复起始条件是最容易出问题的地方。我见过有人在这里忘了释放 SDA,导致重复起始条件没产生,从机一直等着。嗯,这里要注意:重复起始条件的时序和起始条件完全一样,只是之前没有发停止条件。

4.5 知识结构图:I2C 主机读写完整流程

下面这张图把主机发送、接收、组合读写串在一起了。你可以把它当成调试时的路线图。

I2C 主机读写完整流程 起始条件 (S) 发送地址 + 写位 (0) ACK? NACK → 错误处理 ACK → 发送数据/寄存器地址 发送数据字节 需要读? 重复起始 (Sr) 发送地址 + 读位 (1) 接收数据字节 停止条件 (P) 关键点 • 重复起始不先发停止 • 最后一个字节回 NACK • 地址冲突会导致总线锁死

4.6 常见问题与避坑指南

  • 从机不 ACK:先检查地址对不对,再看从机是否上电、总线是否被拉死。我曾经遇到过从机地址的 datasheet 写的是 7 位,但实际芯片用的是 8 位格式,多了一位左移。
  • 总线锁死(SDA 一直被拉低):这通常是从机在等待数据或时钟,但主机提前停止了。解决办法是发 9 个以上的 SCL 时钟脉冲,让从机复位。我一般会在初始化时加一段「总线恢复」代码。
  • 时序太快:I2C 有标准模式(100kHz)、快速模式(400kHz)和高速模式(3.4MHz)。如果从机不支持高速,你跑 400kHz 以上,它可能丢数据。我习惯先跑 100kHz 调通,再慢慢提速度。
  • 重复起始条件没产生:检查代码里是否在发重复起始前误发了停止条件。用逻辑分析仪抓波形,一眼就能看出来。

调试利器:逻辑分析仪是调 I2C 最好的工具。你不需要多贵的,几十块钱的就能抓到起始条件、地址、ACK/NACK、数据、停止条件。我每次调 I2C 都会先抓一段波形,确认时序对不对,再去看代码逻辑。