2. 从机地址的构成:7位地址与10位地址格式、读写位(R/W)的拼接

好,咱们进入正题。I2C 通信的第一步是什么?说白了,就是主机要找到它想跟谁说话。这个“谁”,就是从机地址。

很多新手拿到 I2C 器件,第一件事就是看数据手册里的地址。但地址到底怎么构成的?7 位和 10 位有什么区别?读写位又是怎么塞进去的?这些细节搞不清楚,调试的时候就会一脸懵。

我记得刚入行那会儿,有一次调一个温度传感器,死活读不到数据。折腾了半天,才发现是我把地址左移了一位没处理对。嗯,这种坑,咱们今天一次性讲清楚。

2.1 7位地址格式

标准 I2C 总线使用 7 位地址。这 7 位由器件制造商设定,通常前几位是固定的,后几位由引脚电平决定。

举个例子,一个常见的 EEPROM 芯片,它的 7 位地址可能是 1010 A2 A1 A0。其中 1010 是固定部分,A2 A1 A0 由硬件引脚决定。你想想看,这就意味着你可以挂最多 8 个同样的芯片在同一条总线上。

关键点: 7 位地址范围是 0x00 ~ 0x7F。但 0x00 是通用呼叫地址,0x7F 保留,实际可用的是 0x01 ~ 0x7E。

我个人的习惯是,拿到一个新器件,先看数据手册里地址那一页。通常会有个表格,清清楚楚写着固定位和可配置位。

2.2 10位地址格式

7 位地址最多挂 112 个设备(去掉保留地址)。这在复杂系统里不够用。所以 I2C 规范引入了 10 位地址。

10 位地址怎么工作的?它用了两个字节来传输地址。第一个字节的前 5 位是 11110,这是 10 位地址的标志。后面跟着地址的高 2 位,再加上读写位。第二个字节是地址的低 8 位。

字节 位 7 位 6 位 5 位 4 位 3 位 2 位 1 位 0
第1字节 1 1 1 1 0 A9 A8 R/W
第2字节 A7 A6 A5 A4 A3 A2 A1 A0

你看,第一个字节的高 5 位固定是 11110,这就是 10 位地址的“身份证”。从机收到这个标志,就知道后面还有 8 位要收。

小提示: 10 位地址和 7 位地址可以混用在同一总线上。但要注意,10 位地址的第一个字节 11110xx 范围是 0x78 ~ 0x7B,这些地址在 7 位模式下是保留的,所以不会冲突。

2.3 读写位(R/W)的拼接

这是最容易出错的地方。I2C 协议里,地址字节的最低位是读写位。0 表示主机要写,1 表示主机要读。

但问题来了:数据手册里给的地址,通常是 7 位地址。比如手册说地址是 0x50,这是 7 位的。但实际发送时,你要把它左移一位,然后把读写位塞到最低位。

举个例子:

  • 手册地址:0x50 (二进制 1010000)
  • 写操作:左移一位 + 0 = 0xA0 (二进制 10100000)
  • 读操作:左移一位 + 1 = 0xA1 (二进制 10100001)

我曾经见过一个同事,直接把手册上的地址当发送字节用,结果通信死活不通。你想想看,0x50 和 0xA0 差了一倍呢。

避坑指南: 我曾经在调试一个气压传感器时,发现读回来的数据全是 0xFF。查了半天,原来是地址左移后忘了加读写位。从那以后,我写代码时都会在注释里把 7 位地址和 8 位发送地址都标出来。

2.4 地址格式对比

特性 7位地址 10位地址
地址位数 7 位 10 位
最大设备数 112 (去掉保留地址) 1024
传输字节数 1 字节 2 字节
兼容性 所有 I2C 设备 需从机支持
常见场景 传感器、EEPROM、ADC 复杂系统、多设备总线

2.5 地址格式与读写位拼接流程图

下面这张图把整个流程串起来了。从拿到手册地址,到最终发送到总线上的数据,每一步都清清楚楚。

I2C 地址格式与读写位拼接流程图 I2C 地址格式与读写位拼接流程 手册 7 位地址 左移 1 位 R/W = 0 (写) R/W = 1 (读) 发送字节 = 0xA0 发送字节 = 0xA1 示例:手册地址 0x50 (7位) → 左移后 0xA0 → 写操作加 0 → 发送 0xA0 读操作加 1 → 发送 0xA1

2.6 代码示例:地址拼接

下面这段代码展示了如何在 C 语言里处理地址拼接。我习惯用宏定义把 7 位地址和 8 位发送地址都写清楚,这样代码可读性高,也不容易出错。

/* 手册给出的 7 位从机地址 */
#define DEVICE_ADDR_7BIT  0x50

/* 左移 1 位后的基地址 */
#define DEVICE_ADDR_BASE  (DEVICE_ADDR_7BIT << 1)

/* 写操作地址 */
#define DEVICE_ADDR_WRITE (DEVICE_ADDR_BASE | 0x00)

/* 读操作地址 */
#define DEVICE_ADDR_READ  (DEVICE_ADDR_BASE | 0x01)

/* 发送写地址 */
I2C_Start();
I2C_SendByte(DEVICE_ADDR_WRITE);  /* 发送 0xA0 */
if (I2C_GetAck() == ACK) {
    /* 从机应答,继续发送数据 */
}

/* 发送读地址 */
I2C_Start();
I2C_SendByte(DEVICE_ADDR_READ);   /* 发送 0xA1 */
if (I2C_GetAck() == ACK) {
    /* 从机应答,开始接收数据 */
}
个人经验: 我建议在代码里把 7 位地址和 8 位发送地址都用注释标出来。这样过几个月回头看代码,不用再翻数据手册算一遍。省心。

2.7 常见问题与避坑

  • 地址冲突: 两个从机用了相同的地址。解决办法是检查硬件引脚配置,或者用地址转换器。
  • 左移遗漏: 直接把 7 位地址当发送字节用。这是新手最常见的错误。
  • 读写位搞反: 写操作发了读地址,读操作发了写地址。从机不会应答,或者应答了但数据不对。
  • 10 位地址兼容性: 不是所有从机都支持 10 位地址。用之前一定要看数据手册。
避坑指南: 我曾经在一个项目里用了 10 位地址的从机,结果发现主控的 I2C 外设不支持 10 位地址模式。最后只能用 GPIO 模拟 I2C 时序才搞定。所以选型的时候,一定要确认主控和从机都支持同样的地址模式。

好了,地址格式和读写位拼接就讲到这里。这些是 I2C 通信的基础,搞清楚了,后面的调试才能顺风顺水。