第3章 I2C地址与读写操作

搞 I2C 调试,第一件事就是看懂地址。很多人觉得地址简单,不就是发个字节嘛。但你想想看,地址少了一位、多了一位、甚至读写位搞反了,整个总线就乱套了。我刚开始用逻辑分析仪抓 I2C 的时候,就因为在地址阶段没搞清读写位,浪费了整整一个下午。

3.1  7 位地址与读写位

标准 I2C 地址长度是 7 位。你说 7 位怎么放?它藏在地址字节的高 7 位,最低位是读写标志:0 表示主机要写,1 表示要读。地址字节的格式很简单:

Bit 7-1Bit 0 (R/W)
7 位从机地址0 = 写, 1 = 读

举个例子,从机地址是 0x50(7 位),那写操作的地址字节就是 0xA0(0x50 << 1 | 0),读操作是 0xA1。我自己习惯在代码里这么写:

// 写操作
i2c_start();
i2c_write( (slave_addr << 1) | I2C_WRITE ); // 0xA0
// 读操作
i2c_start();
i2c_write( (slave_addr << 1) | I2C_READ );  // 0xA1

注意:很多新手把从机地址直接当字节发,丢掉了移位。结果从机没响应,还以为是线没接好。

⚠️ 千万别弄反读数位方向

我曾经帮同事排查一个 EEPROM 读数据无效的 bug,抓一看地址字节发的 0xA0,但前面已经是写状态了,后面又跟一个读命令…… 整个时序完全错位。记住:读/写位是主机决定的,不是从机。

3.2  地址字节的完整性检测

发完地址字节,从机会回一个 ACK 或者 NACK。如果地址没匹配,从机不会拉低 SDA,SCL 的第 9 个时钟里 SDA 保持高电平——这就是 NACK。这时候主机一般会发 Stop 或者重发 Start。

如果这时发生了时钟拉伸呢?(没错,时钟拉伸问题在地址阶段也可能出现)从机可能在 ACK 周期前拉低 SCL 来争取准备时间。我抓过一例:一个温湿度传感器每次读地址都要拉伸 120 μs,普通逻辑分析仪默认采样太粗,把 ACK 判断成了 NACK。所以解码的时候,一定要把 SCL 低电平超过 50 μs 的跨度标出来,才能分辨是真正的 NACK 还是被拉伸的 ACK。

💡 我的小经验: 如果数据线上看到地址字节后 SDA 高电平且 SCL 被持续拉低,别急着报地址错误,先量量低电平持续时间。超过 tLOW 最大值(标准模式 100 kHz 下约 4.7 μs)但小于 1 ms,大概率是时钟拉伸。

3.3  10 位地址与保留地址

7 位地址最多只够 127 个从机,不够用怎么办?I2C 搞了个 10 位地址扩展。用前 5 位 11110 作为标志,后面跟地址高 2 位,再加上读写位构成第一个字节;第二个字节是地址低 8 位。听起来复杂,看表就清楚了:

第一字节第二字节说明
11110 AA9 A8 R/WA7 … A0AA9A8 是地址高 2 位
1111 0xxx—保留地址(通用呼叫、START 字节等)

保留地址往往被忽略,但调试时遇到 0x00(通用呼叫)或 0x01(START 字节)的数据,你可能以为是乱码,其实主机可能在发特殊协议。我遇到过一款 10 位地址的触摸控制器,它发地址字节时第一个字节是 0xF0,第二个字节 0x3C。如果不看完整两个字节,单抓第一个字节会误判成 7 位地址。

3.4  读写操作流程

下面这张图把地址阶段到数据阶段的完整流程画出来了。建议存下来,调时序时对照着看。

I2C 地址发送与读写操作流程 START 条件 发送 7位地址+R/W 检测第9个时钟 SDA 低 = ACK SCL 低电平 > 50μs? 正常数据阶段 NACK → STOP 或 Rs 或时钟拉伸继续等待 📌 处理顺序:START → 发地址+R/W → 第9时钟判断 ACK/NACK/拉伸 → 数据或重试 时钟拉伸发生在 ACK 之前的 SCL 低电平保持期,不是真正的 NACK

3.5  避坑指南

最后说几句我踩过的坑,都是和地址读写有关的:

  • 地址移位: 把从机地址左移1位再拼接读写位是标准做法。我以前见过一个代码直接把地址字节设为 0x50(没移位),然后用 I2C_write(addr | read_flag),结果读操作变成了 0x51,差了一位,根本不对。
  • 10位地址的分离判断: 抓第一个字节为 0xF0 就立刻认为是从机 NACK,忽略了第二个字节。后来加了状态机,检测到 11110 头部后才收第二个字节。
  • 时钟拉伸在地址阶段的误判: 我有一块 FPGA 转接板模拟 I2C 从机,每次收到地址都会拉低 SCL 约 80 μs。上位机逻辑分析仪配置的采样率太低,把 ACK 判定成了异常。后来我把采样率提到 4 MHz,才看清 SCL 确实被拉低但 SDA 保持了低,确认是拉伸不是 NACK。

🔑 一句话总结: 解码 I2C 地址时,先移位再看 R/W,然后根据第9个时钟的 SDA 电平区分 ACK/NACK/拉伸 — 拉伸时 SCL 会被从机主动拉长,不要急着报 NACK。

好了,地址和读写操作这部分算讲透了。下次你抓总线看到一串 0xA0 0x01 0x02,心里应该能立马拆解成:地址 0x50 + 写方向,然后从机 ACK,接着发数据寄存器地址 0x01,再发数据 0x02。掌握这个基本功,后面谈时钟拉伸的定位才会顺手。