3. I2C地址与读写:7位地址与10位地址、读写位(R/W)、从机应答(ACK/NACK)
I2C总线上挂着那么多设备,主控怎么知道跟谁说话?说白了,靠的就是地址。每个从机设备都有一个唯一的地址,就像每家每户的门牌号。主控发地址,匹配上的那个从机就会回应一声“我在”。
这一节我们重点聊三个东西:地址格式(7位和10位)、读写控制位(R/W)、以及从机的应答机制(ACK/NACK)。这三个概念搞不清楚,写出来的驱动大概率会卡死在总线上。我早年就吃过这个亏,调了整整两天才发现是地址左移了一位没处理。
3.1 7位地址与10位地址
标准I2C规范里,地址长度有两种:7位模式和10位模式。绝大多数传感器、EEPROM、ADC芯片用的都是7位地址。10位地址是为了扩展地址空间,用得少,但你要知道它怎么工作的。
3.1.1 7位地址
7位地址的范围是0x00到0x7F,但实际可用地址要少一些。0x00是通用呼叫地址,0x01到0x07是保留地址,0x78到0x7F也是保留的。真正能用的,大概在0x08到0x77之间,一共112个。
这里有个容易踩的坑:芯片手册上写的地址,通常是7位地址。但在I2C总线上传输时,这7位地址要左移1位,空出来的最低位放读写位(R/W)。所以你在代码里看到的地址,往往是左移后的8位值。
关键点:7位地址在总线上占用一个字节的高7位,最低位是R/W位。写操作时R/W=0,读操作时R/W=1。
举个例子:某颗温度传感器的7位地址是0x48(二进制1001000)。在写操作时,主控发送的地址字节是0x90(10010000);读操作时发送0x91(10010001)。
| 操作 | 7位地址 | R/W位 | 总线发送字节 |
|---|---|---|---|
| 写 | 0x48 (1001000) | 0 | 0x90 (10010000) |
| 读 | 0x48 (1001000) | 1 | 0x91 (10010001) |
我习惯在代码里直接定义7位地址,然后在发送时手动左移并加上读写位。这样可读性更好,也方便移植。
// 我习惯的做法:定义7位地址
#define TEMP_SENSOR_ADDR_7BIT 0x48
// 发送写地址
uint8_t addr_write = (TEMP_SENSOR_ADDR_7BIT << 1) | 0;
// 发送读地址
uint8_t addr_read = (TEMP_SENSOR_ADDR_7BIT << 1) | 1;
3.1.2 10位地址
10位地址是为了解决7位地址不够用的问题。它的格式比较特殊:第一个字节的高5位固定为11110,接着是10位地址的高2位,最后是R/W位。第二个字节是10位地址的低8位。
举个例子:10位地址0x2C4(二进制10 11000100)。
- 第一个字节:11110 + 地址高2位(10) + R/W位 = 1111 0 10 R/W
- 第二个字节:地址低8位 = 11000100
写操作时,主控发送:0xF4 (11110100) + 0xC4 (11000100)。读操作时,主控先发写地址(0xF4 + 0xC4)让从机确认,然后发重复起始位,再发读地址(0xF5)。
注意:10位地址的从机在应答时,第一个字节和第二个字节都要应答。如果第二个字节没有收到ACK,说明地址匹配失败。我在调试一个高端音频Codec时遇到过这个问题,从机手册写的是10位地址,但我只发了第一个字节就等ACK,结果总线一直卡住。
3.2 读写位(R/W)
R/W位是地址字节的最低位。它告诉从机:主控接下来要干什么。
- R/W = 0:写操作。主控向从机发送数据。
- R/W = 1:读操作。主控从从机接收数据。
你可能会问:为什么不能把读写位放在别的位置?嗯,这是I2C规范定的,放在最低位方便硬件解析。地址字节发完后,从机直接检查最低位就知道该往哪个方向走。
实际项目中,读写操作往往组合使用。比如你要读一个寄存器,得先写寄存器地址,再重新发起读操作。这就是所谓的“复合格式”。
// 复合格式:先写寄存器地址,再读数据
// 步骤1:起始位 + 从机写地址 + 寄存器地址
I2C_Start();
I2C_SendByte(0x90); // 7位地址0x48左移,R/W=0
I2C_WaitAck();
I2C_SendByte(0x01); // 寄存器地址0x01
I2C_WaitAck();
// 步骤2:重复起始位 + 从机读地址 + 读取数据
I2C_Start();
I2C_SendByte(0x91); // 7位地址0x48左移,R/W=1
I2C_WaitAck();
uint8_t data = I2C_ReadByte();
I2C_SendNack();
I2C_Stop();
经验之谈:很多新手在复合格式中忘记发重复起始位,直接发了停止位再发起始位。这样会导致总线释放,中间可能被其他主控抢占总线。一定要用重复起始位(Repeated Start),不要用Stop+Start。
3.3 从机应答(ACK/NACK)
I2C总线上,每个字节(包括地址字节和数据字节)发送完后,接收方都要给一个应答信号。这是I2C协议的可靠性保障。
3.3.1 应答机制
应答发生在第9个SCL时钟周期。发送方在第9个时钟释放SDA线(拉高),接收方如果拉低SDA,表示ACK(应答);如果保持高,表示NACK(非应答)。
我画了一张图,帮你理解应答的时序:
3.3.2 ACK与NACK的含义
| 场景 | 发送方 | 接收方 | 应答信号 | 含义 |
|---|---|---|---|---|
| 地址匹配 | 主控 | 从机 | ACK | 地址匹配成功,从机准备好通信 |
| 地址不匹配 | 主控 | 从机 | NACK | 总线上没有这个地址的设备 |
| 写数据 | 主控 | 从机 | ACK | 从机已接收数据,可以继续发 |
| 写数据(从机忙) | 主控 | 从机 | NACK | 从机正忙,无法接收新数据 |
| 读数据(最后一个字节) | 从机 | 主控 | NACK | 主控通知从机:不要再发了 |
3.3.3 驱动中如何处理应答
写驱动时,应答处理是核心逻辑。我一般这样写:
// 等待从机应答
// 返回值:0=ACK,1=NACK
uint8_t I2C_WaitAck(void)
{
uint8_t timeout = 100;
// 释放SDA线,让从机控制
I2C_SDA_OUTPUT();
I2C_SDA_HIGH();
// 产生第9个时钟
I2C_SCL_HIGH();
delay_us(5);
// 读取SDA状态
while(I2C_SDA_READ() == 1)
{
if(--timeout == 0)
{
I2C_SCL_LOW();
return 1; // NACK或超时
}
delay_us(1);
}
I2C_SCL_LOW();
return 0; // ACK
}
// 主控发送NACK(读操作最后一个字节后使用)
void I2C_SendNack(void)
{
I2C_SDA_OUTPUT();
I2C_SDA_HIGH(); // 保持高电平表示NACK
I2C_SCL_HIGH();
delay_us(5);
I2C_SCL_LOW();
}
避坑指南:我曾经在一个项目中,读操作最后一个字节忘了发NACK,结果从机一直发数据,把后续的停止位给冲掉了。总线直接卡死,必须复位才能恢复。记住:主控作为接收方时,最后一个字节必须发NACK,这是告诉从机“够了,别发了”。
3.4 地址与应答的常见问题
调试I2C驱动时,地址和应答相关的问题占了七八成。我列几个最常见的:
- 地址左移问题:芯片手册写的是7位地址,但驱动里直接用了这个值没左移。结果地址字节发出去,从机根本不认。
- 应答超时:从机没拉低SDA,主控一直等ACK,程序卡死。一定要加超时处理。
- 读操作忘记发NACK:最后一个字节发了ACK,从机继续发数据,总线混乱。
- 10位地址只发了一个字节:10位地址需要两个字节,只发第一个字节从机不会应答。
调试技巧:用逻辑分析仪抓波形,直接看地址字节和应答位。我每次调I2C驱动,第一件事就是抓波形确认地址对不对、应答有没有。比盯着代码看快多了。