2. I2C协议基础:数据帧格式、地址与读写位、应答机制
各位同学,今天我们聊聊I2C协议最核心的部分。说实话,很多做嵌入式开发的朋友,调I2C设备时遇到最多的问题,就是协议细节没吃透。你想想看,一条SDA线上挂着好几个设备,怎么保证它们不乱说话?靠的就是这套严谨的帧格式和应答机制。
2.1 数据帧的骨架:起始、地址、数据、停止
I2C通信的基本单位是“帧”。一帧数据,就像一封信,有固定的格式。我个人习惯把它拆成四个部分:
- 起始条件(START):SCL高电平时,SDA从高变低。这是“我要开始说话了”的信号。
- 地址帧(Address):7位或10位从机地址,加上1位读写方向位。
- 数据帧(Data):8位数据,可以连续传多个字节。
- 停止条件(STOP):SCL高电平时,SDA从低变高。这是“我说完了”的信号。
我在项目中遇到过一位同事,写驱动时忘了发停止条件,结果总线一直卡在忙状态,所有设备都挂掉了。嗯,这里要注意:起始和停止条件必须由主机产生,从机只能被动响应。
关键点: 每次通信必须以START开始,以STOP结束。中间可以插入多次数据帧,但地址帧只出现一次。
2.2 地址与读写位:谁在跟谁说话?
I2C总线上每个从设备都有一个唯一的地址。7位地址模式最常见,比如我们常用的温度传感器LM75,地址是0x48。但注意,这个地址是7位的,实际发送时左移1位,最低位填读写位。
| 位序 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
|---|---|---|---|---|---|---|---|---|
| 内容 | A6 | A5 | A4 | A3 | A2 | A1 | A0 | R/W |
| 说明 | 7位从机地址 | 0=写,1=读 | ||||||
举个例子:如果设备地址是0x48(二进制 1001000),你要写数据,就发送0x90(10010000);你要读数据,就发送0x91(10010001)。
避坑指南: 我曾经在调试一个加速度传感器时,发现读回来的数据全是0xFF。查了半天,原来是地址发错了——我把7位地址直接当8位发了,没左移。记住:发送地址时一定要左移1位,再填读写位。
2.3 应答机制:你听到了吗?
I2C的应答机制,说白了就是“确认收到”。每发送完一个字节(8位),接收方必须在第9个SCL时钟周期拉低SDA,表示“我收到了”。如果接收方不拉低(SDA保持高),那就是NACK,表示“我没收到”或“我不想收”。
为什么会这样?因为I2C是开漏总线,SDA线通过上拉电阻接高电平。谁想发ACK,谁就把SDA拉低;谁都不拉,SDA就是高,那就是NACK。
- ACK(应答):接收方在第9个时钟拉低SDA。表示“继续发”。
- NACK(非应答):接收方在第9个时钟保持SDA高。表示“停止”或“出错”。
我记得第一次调I2C时,逻辑分析仪上看到一堆NACK,还以为是设备坏了。后来才发现,是地址发错了,设备根本不认我。NACK其实是个好信号,它告诉你“这条路走不通”。
重要: 主机在读取数据时,如果不想继续读了,必须在最后一个字节发NACK,然后发STOP。否则从机会一直等下去,总线就卡死了。
2.4 完整通信流程:一次读写操作
把上面这些串起来,一次完整的I2C写操作是这样的:
- 主机发START
- 主机发7位地址 + 写位(0)
- 从机发ACK
- 主机发1字节数据
- 从机发ACK
- 重复步骤4-5,直到数据发完
- 主机发STOP
读操作稍微复杂一点,因为要先写寄存器地址,再读数据。这叫“复合帧”:
- 主机发START
- 主机发7位地址 + 写位(0)
- 从机发ACK
- 主机发寄存器地址
- 从机发ACK
- 主机发重复起始条件(RESTART)
- 主机发7位地址 + 读位(1)
- 从机发ACK
- 从机发1字节数据,主机发ACK
- 重复步骤9,直到读完
- 主机发NACK + STOP
你想想看,如果没有重复起始条件,就得先STOP再START,中间可能被其他主机抢占总线。RESTART保证了原子性——整个操作一气呵成。
2.5 知识结构图
下面这张图把I2C协议的核心要素串起来了,建议你保存下来,调试时对照着看。
2.6 常见问题与避坑
最后,我把自己这些年踩过的坑总结一下,你写驱动时对照着检查:
| 问题现象 | 可能原因 | 解决办法 |
|---|---|---|
| 一直收到NACK | 地址错误、从机未上电、总线冲突 | 用示波器抓地址波形,核对7位地址是否左移 |
| 数据全是0xFF | 从机没响应,SDA被上拉电阻拉高 | 检查从机供电和复位引脚 |
| 总线卡死,SCL一直低 | 从机拉低SCL时钟拉伸,主机没处理 | 在驱动中加入超时处理,超过一定时间就复位总线 |
| 读回来的数据错位 | 字节顺序搞反了(MSB/LSB) | 确认传感器数据手册的字节序 |
我的习惯: 每次调新传感器,先用逻辑分析仪抓一次完整的读写波形,对照协议逐位分析。这一步做好了,后面写驱动基本不会出大问题。