2. I2C协议详解:起始条件、停止条件、数据有效性、应答机制、时钟同步
好,咱们进入正题。I2C协议,说白了就是两根线(SCL时钟线、SDA数据线)搞定一切通信。很多刚入行的朋友觉得它简单,但实际调起来,坑还真不少。我个人习惯先把协议层面的几个关键动作讲透,这样后面看波形、写驱动才不会懵。
2.1 起始条件与停止条件
这两个条件是I2C通信的“开关”。没有它们,总线上的设备就不知道什么时候该听、什么时候该停。
起始条件(S):SCL为高电平时,SDA从高电平切换到低电平。
停止条件(P):SCL为高电平时,SDA从低电平切换到高电平。
嗯,这里要注意:这两个动作必须由主机(Master)发起。从机(Slave)绝对不能主动产生起始或停止条件,否则总线就乱套了。
关键点:起始条件和停止条件都是SCL高电平时SDA的变化。正常数据传输时,SDA只能在SCL低电平时变化。所以这两个条件在时序上是“特殊”的,所有I2C设备都会专门检测它们。
我在项目中遇到过一件事:有个同事把起始条件的保持时间搞短了,结果从机偶尔能识别、偶尔不能。查了半天,发现是SCL高电平后SDA下降沿的建立时间不够。嗯,I2C规范里对tHD;STA有明确要求,最低4.7μs(标准模式)。别小看这几微秒,省了它,通信就不稳。
2.2 数据有效性
数据怎么才算有效?规则很简单:SCL高电平时,SDA上的数据必须稳定。SDA只能在SCL低电平时改变。
你想想看,如果SCL高电平时SDA还在跳变,那到底是传数据还是传起始/停止条件?所以这个规则保证了数据的唯一性。
具体来说:
- 每个数据位对应一个SCL脉冲
- 数据在SCL上升沿被采样(从机在此时读取SDA)
- 数据在SCL下降沿后可以变化(主机在此时更新SDA)
我曾经调试一个EEPROM驱动时,发现读出来的数据总是错位。用逻辑分析仪一看,原来是主控的SCL占空比不太对,高电平时间太短,从机来不及采样。说白了,就是数据有效窗口被压缩了。后来调整了I2C时钟的占空比,问题解决。
个人经验:调试I2C时,逻辑分析仪是你的好朋友。别光看代码,抓一下波形,数据有效性对不对一眼就能看出来。我习惯把采样点设在SCL上升沿后1/4高电平位置,这样最保险。
2.3 应答机制
I2C的应答机制,是它可靠性的核心。每次主机发送完一个字节(8位数据),都会在第9个SCL时钟周期释放SDA,然后从机拉低SDA表示“收到了”。
具体流程:
- 主机发送8位数据(高位在前)
- 主机在第9个SCL时钟释放SDA(设为输入)
- 从机拉低SDA表示应答(ACK),或保持高电平表示非应答(NACK)
- 主机检测SDA电平,决定下一步操作
这里有个容易忽略的点:应答信号是必须的。如果主机发送地址后没收到应答,说明从机不存在或忙。如果主机发送数据后从机NACK,说明从机无法接收更多数据(比如缓冲区满了)。
避坑指南:我曾经遇到一个传感器,它在内部转换数据时不会应答。我一开始以为硬件坏了,后来看数据手册才发现,转换期间它确实会NACK。正确的做法是:收到NACK后等一会儿再重试,而不是直接报错。
另外,主机也可以主动发送NACK来结束读取操作。比如读多个字节时,主机在最后一个字节回复NACK,然后发送停止条件。从机看到NACK就知道“别发了,我要停了”。
2.4 时钟同步
时钟同步是I2C多主机通信的基础,但很多单主机的场景下大家不太关注它。其实,就算只有一个主机,时钟同步机制也在默默工作——它让从机可以“拉低时钟”来延长传输时间。
时钟同步的原理:
- 多个主机都可以驱动SCL
- 谁先拉低SCL,SCL就是低电平
- 谁最后释放SCL(拉高),SCL才变高
- 这样,慢速设备可以通过拉低SCL来“拖住”快速主机
说白了,就是“慢者决定速度”。这个机制叫时钟拉伸(Clock Stretching)。很多从机在准备数据时会拉低SCL,告诉主机“等等我,我还没准备好”。
我记得有一次用树莓派读取一个温湿度传感器,数据老是超时。查驱动代码发现,I2C控制器默认不支持时钟拉伸。而那个传感器偏偏需要拉伸时钟来准备数据。后来换了一个支持时钟拉伸的I2C适配器,问题就解决了。
重要提醒:不是所有I2C控制器都支持时钟拉伸!选型时一定要看数据手册里有没有提到“Clock Stretching Support”。如果从机需要拉伸而主机不支持,通信就会失败。我建议在驱动初始化时,主动测试一下从机是否支持拉伸,或者干脆选支持拉伸的主控。
2.5 实际调试中的几个要点
讲完理论,我分享几个实战经验:
| 问题现象 | 可能原因 | 排查方法 |
|---|---|---|
| 地址发送后无应答 | 从机地址错误、从机未上电、总线被拉死 | 用万用表测SDA/SCL电平,看是否被拉低 |
| 数据偶尔错位 | 时钟频率过高、上升时间太长 | 降低I2C频率,检查上拉电阻值 |
| 通信卡死 | 从机时钟拉伸超时、总线竞争 | 加超时处理,复位从机 |
| 起始条件不识别 | tHD;STA不满足、SCL抖动 | 检查SCL高电平时间,加滤波电容 |
嗯,最后说一句:I2C协议虽然简单,但每个细节都关系到系统稳定性。起始条件、停止条件、数据有效性、应答机制、时钟同步,这五个点你吃透了,I2C通信就基本不会出大问题。我每次做新板子,都会先拿逻辑分析仪抓一下这几个关键时序,确认没问题再往下走。
我的习惯:在设备树里配置I2C时,我会把clock-frequency设得保守一点,比如标准模式用100kHz,快速模式用300kHz(而不是400kHz)。留点余量,抗干扰能力会好很多。等调稳了再慢慢提频率。