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位地址的传输分两个阶段:
- 第一个字节:高5位固定为11110,接着是地址的高2位(bit 9:8),最后是R/W位。
- 第二个字节:地址的低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 写操作流程
写操作,就是主设备往从设备里送数据。流程其实很简单:
- 主设备发送START条件
- 主设备发送从机地址 + 写标志(R/W = 0)
- 从机应答ACK
- 主设备发送寄存器地址(如果有的话)
- 从机应答ACK
- 主设备发送数据字节
- 从机应答ACK
- 重复步骤6-7,直到数据发送完毕
- 主设备发送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)。
- 主设备发送START条件
- 主设备发送从机地址 + 写标志(R/W = 0)
- 从机应答ACK
- 主设备发送要读取的寄存器地址
- 从机应答ACK
- 主设备发送重复START条件(Sr)
- 主设备发送从机地址 + 读标志(R/W = 1)
- 从机应答ACK
- 从机发送数据字节
- 主设备应答ACK(如果还要继续读)或NACK(最后一个字节)
- 重复步骤9-10,直到数据接收完毕
- 主设备发送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地址和读写流程串起来了,方便你理解整体脉络:
3.7 避坑指南
我曾经踩过的坑: 有一次调试一个温湿度传感器,读出来的数据总是0xFF。查了半天,发现是读操作时忘了发重复START,直接从写模式切到了读模式。从机以为还在写,结果数据全乱了。记住:读操作必须先写寄存器地址,再发Sr,再发地址+读标志。
调试建议: 如果你手头有逻辑分析仪,抓一下I2C波形。看地址字节对不对,ACK位有没有拉低,数据字节顺序对不对。没有逻辑分析仪?用GPIO模拟I2C,配合串口打印,也能定位大部分问题。