3. I2C从机地址与读写操作:7位地址与10位地址格式、读操作流程、写操作流程

地址这东西,说白了就是I2C总线上的门牌号。主设备要跟谁说话,就得先喊对门牌。我刚开始调I2C时,总觉得地址就是地址,能对上就行。结果有一次,一个传感器死活不响应,查了半天才发现——我把7位地址和8位地址搞混了。嗯,这坑我替你们踩过了。

3.1 7位地址格式

标准I2C用7位地址,范围是0x00到0x7F。但实际能用的地址没这么多,0x00是广播地址,0x7F保留,所以真正可用的地址范围是0x01到0x7E。

7位地址在总线上传输时,会被左移1位,最低位填上读写标志位(R/W)。所以你在数据手册里看到的地址,通常是7位值,但示波器上抓到的第一个字节是左移后的8位值。

关键点: 7位地址左移1位 + R/W位 = 第一个字节。例如从机地址0x48,写操作时发送0x90(0x48 << 1 | 0),读操作时发送0x91(0x48 << 1 | 1)。

我个人的习惯是,在代码里直接用7位地址定义宏,然后在发送时手动左移。这样可读性更好,也方便后期维护。

3.2 10位地址格式

7位地址最多挂127个设备,实际够用。但有些复杂传感器、多通道ADC或者FPGA,地址不够用怎么办?I2C规范里定义了10位地址扩展。

10位地址的传输分两个阶段:

  1. 第一个字节:高5位固定为11110,接着是地址的高2位(bit 9:8),最后是R/W位。
  2. 第二个字节:地址的低8位(bit 7:0)。
位域 bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
第一个字节 1 1 1 1 0 A9 A8 R/W
第二个字节 A7 A6 A5 A4 A3 A2 A1 A0
注意: 不是所有I2C从机都支持10位地址。使用前一定要确认数据手册。我曾经在一个项目里想当然地用了10位地址,结果从机根本不响应,浪费了两天时间。

3.3 写操作流程

写操作,就是主设备往从设备里送数据。流程其实很简单:

  1. 主设备发送START条件
  2. 主设备发送从机地址 + 写标志(R/W = 0)
  3. 从机应答ACK
  4. 主设备发送寄存器地址(如果有的话)
  5. 从机应答ACK
  6. 主设备发送数据字节
  7. 从机应答ACK
  8. 重复步骤6-7,直到数据发送完毕
  9. 主设备发送STOP条件

你想想看,这个流程里最容易出问题的是哪一步?我个人经验是——从机不应答。原因通常是地址错了,或者从机还没准备好。遇到这种情况,先拿示波器抓波形,看SDA线上有没有ACK位拉低。

// 伪代码示例:I2C写操作
uint8_t i2c_write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len) {
    i2c_start();                    // 发送START
    i2c_send_byte(dev_addr << 1);  // 发送地址 + 写标志
    if (!i2c_wait_ack()) return ERROR;
    
    i2c_send_byte(reg_addr);        // 发送寄存器地址
    if (!i2c_wait_ack()) return ERROR;
    
    for (uint8_t i = 0; i < len; i++) {
        i2c_send_byte(data[i]);     // 发送数据
        if (!i2c_wait_ack()) return ERROR;
    }
    
    i2c_stop();                     // 发送STOP
    return OK;
}

3.4 读操作流程

读操作比写操作多一个步骤——需要先告诉从机你要读哪个寄存器,然后再重新发起START来读数据。这叫复合格式(Combined Format)。

  1. 主设备发送START条件
  2. 主设备发送从机地址 + 写标志(R/W = 0)
  3. 从机应答ACK
  4. 主设备发送要读取的寄存器地址
  5. 从机应答ACK
  6. 主设备发送重复START条件(Sr)
  7. 主设备发送从机地址 + 读标志(R/W = 1)
  8. 从机应答ACK
  9. 从机发送数据字节
  10. 主设备应答ACK(如果还要继续读)或NACK(最后一个字节)
  11. 重复步骤9-10,直到数据接收完毕
  12. 主设备发送STOP条件
小技巧: 读操作的最后,主设备必须在最后一个字节回复NACK,然后发STOP。如果回复了ACK,从机会继续发数据,导致总线卡死。我刚开始做的时候经常忘这个,结果读出来的数据总是多一个字节。
// 伪代码示例:I2C读操作
uint8_t i2c_read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *buf, uint8_t len) {
    i2c_start();                    // 发送START
    i2c_send_byte(dev_addr << 1);  // 发送地址 + 写标志
    if (!i2c_wait_ack()) return ERROR;
    
    i2c_send_byte(reg_addr);        // 发送寄存器地址
    if (!i2c_wait_ack()) return ERROR;
    
    i2c_start();                    // 发送重复START
    i2c_send_byte(dev_addr << 1 | 1); // 发送地址 + 读标志
    if (!i2c_wait_ack()) return ERROR;
    
    for (uint8_t i = 0; i < len; i++) {
        buf[i] = i2c_read_byte();
        if (i == len - 1) {
            i2c_send_nack();        // 最后一个字节回复NACK
        } else {
            i2c_send_ack();         // 非最后一个字节回复ACK
        }
    }
    
    i2c_stop();                     // 发送STOP
    return OK;
}

3.5 7位地址与10位地址对比

特性 7位地址 10位地址
地址范围 0x00 - 0x7F 0x000 - 0x3FF
可用地址数 112(扣除保留地址) 1024
传输字节数 1个字节(含R/W位) 2个字节
兼容性 所有I2C设备都支持 仅部分设备支持
总线开销 低 高(多一个字节传输)

3.6 知识结构图

下面这张图把I2C地址和读写流程串起来了,方便你理解整体脉络:

I2C从机地址与读写操作知识结构 地址格式 7位地址 10位地址 读写操作 写操作 读操作 地址左移 + R/W位 START + 地址 + ACK 数据收发 + ACK/NACK STOP条件结束 💡 核心要点 • 7位地址左移1位 + R/W位构成第一个字节 • 读操作需要重复START,先写寄存器地址再读数据 • 最后一个字节主设备必须回复NACK,否则总线卡死

3.7 避坑指南

我曾经踩过的坑: 有一次调试一个温湿度传感器,读出来的数据总是0xFF。查了半天,发现是读操作时忘了发重复START,直接从写模式切到了读模式。从机以为还在写,结果数据全乱了。记住:读操作必须先写寄存器地址,再发Sr,再发地址+读标志。
调试建议: 如果你手头有逻辑分析仪,抓一下I2C波形。看地址字节对不对,ACK位有没有拉低,数据字节顺序对不对。没有逻辑分析仪?用GPIO模拟I2C,配合串口打印,也能定位大部分问题。