2. I2C总线协议:从物理层到时钟拉伸

大家好,我是老张。今天我们来聊聊I2C总线协议。说实话,这玩意儿在光模块里太常见了——EEPROM读写、DDM监控、激光器配置,几乎全靠它。我刚开始做光模块那会儿,就因为I2C时序没搞对,折腾了整整三天才找到问题。嗯,今天就把这些经验分享给你。

2.1 I2C物理层:两根线搞定一切

I2C总线只需要两根线:SDA(数据线)和SCL(时钟线)。这两根线都是开漏输出,需要外接上拉电阻。

为什么用开漏?说白了,就是为了实现多设备通信。任何设备都可以把总线拉低,但谁都不能主动拉高。这样就不会出现短路问题。我见过有人把上拉电阻选得太小,结果总线波形方方正正,但功耗上去了;也有人选得太大,波形圆滚滚的,通信距离一长就出错。

上拉电阻经验值:

  • 标准模式(100kHz):4.7kΩ ~ 10kΩ
  • 快速模式(400kHz):2.2kΩ ~ 4.7kΩ
  • 高速模式(3.4MHz):1kΩ ~ 2.2kΩ

光模块里常用4.7kΩ,兼容性最好。

2.2 起始条件和停止条件

I2C通信的开始和结束,有明确的信号约定:

  • 起始条件(S):SCL为高电平时,SDA从高切换到低
  • 停止条件(P):SCL为高电平时,SDA从低切换到高

这两个条件由主机(Master)产生。从机(Slave)只能被动响应。我曾经在调试时发现,如果起始条件不完整——比如SDA下降沿还没到位SCL就拉低了——从机根本不会响应。这种问题用逻辑分析仪一看就明白,但用示波器单次触发反而容易漏掉。

小技巧:写代码时,起始条件和停止条件之间要留足够的建立时间(tSU;STA 和 tHD;STA)。标准模式下至少4.7μs,快速模式至少0.6μs。别问我怎么知道的——有一次我图省事,把延时写成了微秒级,结果100个模块里有3个死活读不到数据。

2.3 数据帧格式:字节是基本单位

I2C的数据传输以字节为单位,每个字节8位,后面跟一个应答位(ACK)。

基本流程是这样的:

  1. 主机发送起始条件
  2. 主机发送7位从机地址 + 1位读写位(0=写,1=读)
  3. 从机发送应答位(ACK)
  4. 主机发送/接收数据字节,每个字节后跟应答位
  5. 主机发送停止条件

你想想看,如果从机没应答,说明什么?要么地址错了,要么从机没上电,要么总线被拉死了。我排查过最诡异的一次——从机明明有应答,但数据全是0xFF。后来发现是上拉电阻虚焊,SDA线一直浮空。

注意:应答位是低电平有效。从机在SCL第9个时钟周期拉低SDA表示ACK,保持高电平表示NACK。主机在读取最后一个字节时,应该发送NACK来告诉从机「够了,别发了」。

2.4 7位地址与10位地址

光模块EEPROM通常使用7位地址。比如常见的24C02系列,地址是0x50(1010000)。但有些复杂器件会用10位地址。

7位地址格式:

起始条件 + [A6 A5 A4 A3 A2 A1 A0 R/W] + ACK + 数据...

10位地址格式:

起始条件 + [1111 0 A9 A8 R/W] + ACK + [A7 A6 A5 A4 A3 A2 A1 A0] + ACK + 数据...

说白了,10位地址就是把地址分成两段发。第一字节的前5位固定为11110,后两位是地址的高两位。第二字节是地址的低8位。我建议你尽量用7位地址,除非器件手册明确要求10位。因为很多便宜的MCU的I2C外设对10位地址支持不好,容易出bug。

地址类型 地址范围 光模块常见器件
7位 0x00 ~ 0x7F EEPROM (0x50~0x57), 温度传感器 (0x48~0x4F)
10位 0x000 ~ 0x3FF 某些数字电位器、复杂PMBus器件

2.5 时钟拉伸:从机的「慢一点」信号

时钟拉伸(Clock Stretching)是I2C协议里一个容易被忽略的特性。当从机来不及处理数据时,它会主动拉低SCL线,告诉主机「等一下,我还没准备好」。

主机检测到SCL被拉低后,必须等待SCL释放才能继续发送时钟脉冲。这个机制在光模块里很常见——比如EEPROM在写操作时,内部擦写需要时间,就会用时钟拉伸来延迟主机的下一个操作。

我踩过最大的坑就在这里。有一次用某款国产MCU做主控,它的I2C外设在硬件上不支持时钟拉伸。结果EEPROM一拉伸时钟,MCU就超时退出,数据写一半就丢了。后来只能改用GPIO模拟I2C,自己处理时钟拉伸逻辑。

时钟拉伸的典型场景:

  • EEPROM页写入后内部擦写(最长5ms)
  • ADC转换期间(转换时间几百μs)
  • 某些传感器在测量时(如温度传感器转换时间)

嗯,这里要注意:不是所有从机都支持时钟拉伸。有些便宜器件压根不实现这个功能,它们用NACK来表示「忙」。所以写驱动时,最好两种方式都兼容。

2.6 知识体系总览

下面这张图把I2C协议的核心逻辑串起来了。你可以把它当作调试时的速查表:

I2C总线协议知识体系 物理层 SDA + SCL 两根线 开漏输出 + 上拉电阻 多设备共享总线 信号条件 起始条件:SCL高→SDA下降 停止条件:SCL高→SDA上升 由主机产生 数据帧格式 8位数据 + 1位ACK 地址 + 读写位 字节传输,MSB优先 地址类型 7位地址:0x00~0x7F 10位地址:0x000~0x3FF 光模块常用7位 时钟拉伸 从机拉低SCL 主机等待释放 EEPROM写操作常见 光模块应用 EEPROM读写 DDM监控数据采集 激光器参数配置 核心要点 两根线、开漏输出、起始/停止条件、字节传输+应答、7位/10位地址、时钟拉伸

这张图把I2C的六个核心模块串在了一起。你调试时遇到问题,可以对照着看——是物理层的问题(波形不对),还是信号条件的问题(起始/停止没做好),还是地址或时钟拉伸的问题。我每次排查I2C故障,都是按这个思路来的。

个人习惯:我写I2C驱动时,一定会先写一个「总线扫描」函数——遍历所有可能的7位地址,看哪些设备有应答。这样能快速确认硬件连接和地址配置是否正确。这个习惯帮我省了不少排查时间。


公众号:蓝海资料掘金营,微信deep3321