I2C通信协议基础:时序、地址、读写操作
讲CRC校验之前,得先把I2C这个“运输通道”搞清楚。你想想看,数据在线上怎么传的?谁先说话?谁应答?地址怎么写的?这些基础搞不明白,后面谈错误检测就是空中楼阁。
我第一次接触I2C是调一个温度传感器,死活读不到数据。后来发现是地址写错了——7位地址左移了一位,从机根本不搭理我。嗯,这种坑,踩过一次你就记住了。
物理层:两条线搞定一切
I2C只用两根线:SDA(数据线)和SCL(时钟线)。就这么简单。但越简单的协议,细节越多。
- SDA:串行数据线,双向的。主机发数据、从机回数据都走这根线。
- SCL:串行时钟线,由主机控制。什么时候采样数据,看的就是这根线的边沿。
关键点:这两根线都是开漏输出,必须外接上拉电阻。我见过有人把上拉电阻省了,结果总线拉不起来,通信全乱套。
开漏结构的好处是什么?多个设备可以挂在同一根线上,谁拉低谁就“说话”。不会短路。我习惯选4.7kΩ的上拉电阻,100kHz模式下够用。如果总线电容大,适当减小到2.2kΩ也行。
时序:SCL高电平时采样
时序是I2C的灵魂。我当年看书觉得时序图挺吓人,其实核心就三条规则:
- 数据变化只在SCL低电平时发生。SCL低的时候,SDA爱怎么变怎么变。
- 数据采样在SCL高电平时进行。SCL高的时候,SDA必须稳定,不能变。
- 起始条件和停止条件特殊处理。SCL高电平时,SDA从高变低是起始,从低变高是停止。
这里有个常见的坑——我调试的时候发现,如果SCL的上升沿不够陡,从机可能误判采样点。所以布线时SDA和SCL尽量走短,别交叉。
⚠ 注意:起始和停止条件只能在SCL高电平时发生。如果你在SCL低电平改变SDA,会被当成数据,而不是控制信号。我曾经在中断里没处理好时序,总线直接挂死。
地址:7位还是10位?
I2C设备都有地址。最常见的是7位地址,能挂127个设备(地址0是广播地址,不用)。也有10位地址的,我很少用。
| 地址类型 | 地址字节数 | 寻址范围 | 常见场景 |
|---|---|---|---|
| 7位 | 1字节(高7位) | 1 ~ 127 | 传感器、EEPROM |
| 10位 | 2字节 | 1 ~ 1023 | 复杂外设、扩展需求 |
注意:有些设备的地址是出厂写死的,有些可以通过引脚配置。我调过一款音频Codec,地址引脚浮空时默认0x1A,拉高变0x1B。查手册时一定要看清地址配置,否则连不上。
地址字节的最后一位是读写位:0表示写,1表示读。设备收到地址后要应答——拉低SDA表示“我在”。如果不应答,说明地址不对或设备没准备好。
💡 实用技巧:写驱动时,把设备地址定义成宏,比如 #define SENSOR_ADDR 0x48。左移一位加上读写位,别手算。我见过有人把0x48写成了0x90,调了一整天。
读写操作:主机说了算
I2C通信是主从模式。主机控制时钟,发起通信,决定方向。从机只能被动响应。
写操作流程
我个人习惯按步骤理解:
- 主机发起始条件(SDA拉低,SCL高变低)。
- 主机发地址字节(7位地址 + 写位0)。
- 从机应答(拉低SDA)。
- 主机发数据字节(8位数据)。
- 从机应答。
- 重复第4、5步,直到发完所有数据。
- 主机发停止条件(SCL高,SDA拉高)。
简单吧?但实际开发中,我遇到过从机在数据阶段不应答的情况。为什么?可能是数据长度不对,或者寄存器地址写错了。检查一下时序逻辑就好。
读操作流程
读操作稍微复杂一点,需要先写后读:
- 主机发起始条件。
- 主机发地址字节 + 写位0。
- 从机应答。
- 主机发寄存器地址(要读哪个寄存器)。
- 从机应答。
- 主机发重复起始条件(REPEATED START)。
- 主机发地址字节 + 读位1。
- 从机应答。
- 主机接收数据,每收一字节发应答(非最后一字节)或不发应答(最后一字节)。
- 主机发停止条件。
注意重复起始条件——它不发送止条件,直接拉低SDA开始新一轮寻址。这样做的好处是总线不会被释放,其他设备插不进来。我调一块FPGA时,必须用重复起始才能读到正确的数据。
重点:读操作的最后,主机必须发送NACK(不应答),然后发停止条件。这样才能告诉从机“我读完了”。我当时没发NACK,从机一直发数据,总线被锁住。
核心逻辑流程图
下面这张图把I2C通信的完整流程串起来了。你看一遍应该就能理清主机、从机、时序、地址、读写的关系。
常见避坑指南
这部分是我真金白银换来的教训,分享给你:
- 应答检查不能省。每发一个字节都要检查从机是否ACK。我一开始偷懒没做,结果某个从机掉线了,整条总线卡死。
- 时钟延展。一些从机需要时间处理数据,会把SCL拉低。这时候主机必须等,不能强行拉高。我碰到过一款EEPROM,写操作需要5ms,没等时钟延展直接读,数据全错。
- 总线冲突。两个主机同时发起通信,通过仲裁决定谁说话。如果你的设备不是唯一的,要处理好冲突检测。个人建议单主机模式最省心。
- 重复起始别漏。读操作必须用重复起始,不能用停止+起始。否则总线会被释放,其他设备可能插进来。
💡 个人经验:调试I2C时,先把示波器挂上。看SDA和SCL的高低电平、边沿斜率、ACK是否正常。眼睛看到的东西,比代码靠谱。我曾经有个问题调了三天,最后发现是上拉电阻焊错了。
好了,I2C的基础就这些。掌握了时序、地址、读写操作,后面我们才能聊CRC怎么加进去,怎么检测错误。这章内容实用性强,建议你动手写个简单的I2C驱动试试。代码写一遍,比看十遍书都管用。