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(非应答)。

我画了一张图,帮你理解应答的时序:

I2C应答时序图 I2C应答(ACK/NACK)时序 SCL D7 D6 D5 D4 D3 D2 D1 D0 ACK SDA 发送方在第9个时钟释放SDA,接收方拉低表示ACK,保持高表示NACK 主控作为接收方时,最后一个字节必须发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驱动时,地址和应答相关的问题占了七八成。我列几个最常见的:

  1. 地址左移问题:芯片手册写的是7位地址,但驱动里直接用了这个值没左移。结果地址字节发出去,从机根本不认。
  2. 应答超时:从机没拉低SDA,主控一直等ACK,程序卡死。一定要加超时处理。
  3. 读操作忘记发NACK:最后一个字节发了ACK,从机继续发数据,总线混乱。
  4. 10位地址只发了一个字节:10位地址需要两个字节,只发第一个字节从机不会应答。

调试技巧:用逻辑分析仪抓波形,直接看地址字节和应答位。我每次调I2C驱动,第一件事就是抓波形确认地址对不对、应答有没有。比盯着代码看快多了。