3. I2C数据传输:数据帧格式、ACK/NACK机制、读写操作流程、多字节传输
好,咱们今天聊聊I2C的数据传输。说实话,很多工程师调I2C驱动,卡就卡在对数据帧的理解上。你看着逻辑分析仪上那一串波形,如果搞不清哪个是地址、哪个是数据、哪个是ACK,那基本就是两眼一抹黑。
我个人习惯,拿到一个I2C器件,第一件事不是写代码,而是先画一张传输时序图。把SCL、SDA的每一段都标清楚。今天我就带你把这个基本功打扎实。
3.1 数据帧格式:从START到STOP
I2C的数据帧,说白了就是一段有头有尾的串行数据流。每一帧都由以下几个部分组成:
- 起始条件(START):SCL高电平时,SDA从高变低。这是总线的“发令枪”。
- 从机地址(7位或10位):主机要跟谁说话,先喊谁的名字。
- 读写位(R/W):0表示主机要写,1表示主机要读。
- 应答位(ACK/NACK):从机收到地址后,拉低SDA表示“我在”。
- 数据字节(8位):真正要传的内容,MSB先发。
- 应答位(ACK/NACK):每发完一个字节,接收方都要回应一下。
- 停止条件(STOP):SCL高电平时,SDA从低变高。表示“我说完了”。
关键点:除了START和STOP,其他所有SDA的电平变化,都必须在SCL低电平时完成。SCL高电平时,SDA必须保持稳定。这是I2C的“铁律”,违反了就是时序违规。
嗯,这里要注意,START和STOP之间的数据字节数量没有上限。你可以只传1个字节,也可以传几百个字节。但实际项目中,我建议单次传输不要超过256字节,否则容易触发从机的内部缓冲区溢出。
3.2 ACK/NACK机制:谁控制着总线?
ACK/NACK是I2C总线的“握手信号”。每次传输完一个字节(8位数据),接收方必须在第9个SCL时钟周期内,拉低SDA表示ACK,或者保持高电平表示NACK。
为什么会NACK?我总结了几种常见情况:
| 场景 | 谁发NACK | 原因 |
|---|---|---|
| 地址发送后 | 从机 | 地址不匹配、器件不存在、器件忙 |
| 写数据后 | 从机 | 寄存器写保护、数据无效、内部错误 |
| 读数据后 | 主机 | 主机不想再读了,准备发STOP |
我曾经遇到过一个问题:从机明明在线,地址也对,但就是一直NACK。查了半天,发现是从机在上电后需要5ms的稳定时间,而我代码里上电后立即发起了通信。加了个延时,问题解决。所以,调试时如果遇到NACK,先别急着怀疑地址,看看时序和电源。
还有一个细节:主机在读取最后一个字节时,必须在收到数据后发NACK,然后发STOP。如果主机发了ACK,从机会以为主机还要继续读,就会继续发数据。这时候你发STOP,总线状态就乱了。
3.3 读写操作流程:标准套路
I2C的读写操作,其实就两种套路。我画了一张流程图,你看完就明白了。
你看,写操作和读操作的区别,主要在于读操作需要先“假装写”来设置寄存器地址,然后发一个重复起始条件(Repeated START),再切换成读模式。这就是所谓的“复合传输”。
我建议:在驱动代码里,把写操作和读操作封装成两个独立的函数。不要试图用一个万能函数处理所有情况。分开写,逻辑清晰,调试也方便。
3.4 多字节传输:批量读写
实际项目中,很少只读一个字节。比如读取一个加速度传感器的三轴数据,一次就要读6个字节。这时候就要用到多字节传输。
多字节传输的流程很简单:
- 主机发送START和从机地址(写模式)。
- 主机发送起始寄存器地址。
- 主机发送Repeated START和从机地址(读模式)。
- 主机连续读取N个字节,每收到一个字节发ACK。
- 主机收到最后一个字节后发NACK,然后发STOP。
这里有个坑,我踩过不止一次:多字节读取时,寄存器地址会自动递增。大部分I2C器件都支持地址自增,但有些器件不支持。如果不确定,一定要看数据手册里的“Auto-Increment”或“Burst Read”章节。
举个例子,读取一个24C02 EEPROM的连续8个字节:
// 伪代码:多字节读取
uint8_t i2c_read_multi(uint8_t dev_addr, uint8_t reg_addr, uint8_t *buf, uint8_t len) {
// 第一步:写寄存器地址
i2c_start();
i2c_send_byte(dev_addr << 1 | 0); // 写模式
if (i2c_wait_ack() != ACK) return ERROR;
i2c_send_byte(reg_addr); // 发送起始地址
if (i2c_wait_ack() != ACK) return ERROR;
// 第二步:重复起始,切换读模式
i2c_repeated_start();
i2c_send_byte(dev_addr << 1 | 1); // 读模式
if (i2c_wait_ack() != ACK) return ERROR;
// 第三步:连续读取len个字节
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();
return SUCCESS;
}
注意:有些从机在收到Repeated START后,需要一点时间准备数据。如果连续读取时第一个字节是错的,可以尝试在Repeated START之后加一个微小的延时(1~5μs)。我遇到过一颗温度传感器,不加延时读出来的永远是0x00。
3.5 常见故障排查清单
最后,我整理了一份调试I2C数据传输时的排查清单。每次遇到问题,按这个顺序查一遍,基本都能找到原因。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| 无ACK响应 | 地址错误、器件未上电、SCL/SDA接反 | 用示波器抓START后的地址波形,核对二进制 |
| 数据全是0xFF | 上拉电阻缺失、总线被拉高 | 测量SCL/SDA静态电平,应为高电平 |
| 数据错位 | 时钟极性错误、字节序搞反 | 检查MSB/LSB顺序,确认从机是大端还是小端 |
| 多字节读取卡死 | 从机不支持地址自增、缓冲区溢出 | 减少单次读取字节数,分多次读取 |
| 偶尔通信失败 | 中断干扰、时钟拉伸超时 | 在I2C操作前后关中断,增加超时处理 |
嗯,这张表我建议你打印出来贴在工位上。我当年刚调I2C时,就是靠这张表一步步排查问题的。说白了,I2C的坑就那么多,踩过一次,下次就知道了。