1. I2C协议基础回顾:总线架构、时序图、7位与10位地址格式

做驱动移植,I2C协议是绕不开的基础。说实话,很多开发者在遇到从机地址冲突、时钟拉伸导致挂死这类问题时,才回头翻协议细节。我个人习惯是先把协议骨架理清楚,再动手调代码。这一节我们就从总线架构、时序和地址格式三个角度,把I2C的底子夯实。

1.1 总线架构:两根线,多个设备

I2C总线就两根线:SCL(时钟线)和SDA(数据线)。所有设备都挂在这两根线上,通过唯一地址区分。主设备发起通信,从设备响应。

我在项目中遇到过一种情况:一个板子上挂了5个I2C设备,结果有两个从机地址冲突了。排查了半天才发现是芯片型号不同但地址相同。所以,了解地址格式是第一步。

核心特点:

  • 主从架构:一个主设备(通常是MCU或SoC)控制时钟,多个从设备共享总线。
  • 双向半双工:SDA线在时钟配合下双向传输数据,同一时刻只能一个方向。
  • 开漏输出:SCL和SDA都是开漏结构,需要上拉电阻。这个设计决定了时钟拉伸的可行性。
  • 多主支持:理论上多个主设备可以仲裁,但嵌入式场景下通常单主。

你想想看,两根线就能挂几十个设备,布线省了多少事。但代价就是协议本身有一些约束,比如最大总线电容、上拉电阻取值等。嗯,这些在驱动移植时都会遇到。

1.2 时序图:读懂SCL和SDA的配合

I2C的时序其实不复杂,但几个关键信号必须记清楚。我刚开始学的时候,总觉得时序图密密麻麻,后来发现只要抓住四个基本动作:起始、停止、数据位、应答。

下面这张SVG图把I2C单字节写操作的时序拆解清楚了:

I2C单字节写操作时序图 I2C 单字节写操作时序(主设备视角) SCL SDA S 起始 7位从机地址 (例如 0x3C) R/W 0=写 ACK 从机应答 8位数据 (寄存器值) ACK 应答 P 停止 关键时序点: • 起始条件:SCL高电平时,SDA从高变低 • 停止条件:SCL高电平时,SDA从低变高 • 数据采样:SCL低电平时SDA变化,高电平时采样

时序上有个细节我特别想强调:数据在SCL高电平时被采样,在SCL低电平时允许变化。这个规则直接决定了时钟拉伸的实现方式——从设备可以拉低SCL,让主设备等待。

我的经验:调试时用逻辑分析仪抓SCL和SDA,比看代码靠谱得多。我曾经花了两天查一个I2C挂死问题,最后发现是从设备时钟拉伸时间过长,主设备超时复位了。抓波形一眼就看出SCL被拉低的时间异常。

1.3 7位与10位地址格式

地址格式是驱动移植中最容易踩坑的地方。I2C协议定义了两种地址长度:7位和10位。绝大多数传感器、EEPROM、ADC等外设都使用7位地址。10位地址主要用于需要挂载大量从机的场景。

特性 7位地址 10位地址
地址范围 0x00 ~ 0x7F(128个,保留16个) 0x000 ~ 0x3FF(1024个)
总线占用 1字节(地址+读写位) 2字节(首字节标识+次字节地址)
首字节特征 高7位为地址,最低位为R/W 高5位为11110,次2位为地址高2位
兼容性 所有I2C设备都支持 需要从机明确支持
常见场景 传感器、存储、IO扩展 多路复用器、复杂系统

7位地址的传输格式很简单:主设备发送起始条件后,先发送1个字节,高7位是地址,最低位是读写标志(0=写,1=读)。然后等待从设备应答。

10位地址的格式稍微绕一点。首字节的高5位固定为11110,接着是地址的高2位,最后是R/W位。第二个字节才是地址的低8位。我刚开始接触10位地址时,总觉得协议栈里地址处理逻辑怪怪的,后来看了内核源码才明白——Linux I2C子系统对10位地址有专门的标志位处理。

注意:很多芯片的数据手册里写的地址是7位左对齐的格式(比如0x3C << 1),但Linux内核中i2c_client的addr字段存储的是原始7位地址(0x3C)。驱动移植时一定要确认你的地址格式是否匹配。我曾经因为这个问题,在移植一个温度传感器驱动时折腾了整整一个下午。

1.4 时钟拉伸:从设备的“慢一点”信号

时钟拉伸(Clock Stretching)是I2C协议中一个容易被忽视但极其重要的特性。它允许从设备在需要更多时间处理数据时,主动拉低SCL线,迫使主设备等待。

为什么会需要这个?举个例子:一个ADC芯片在转换期间无法提供数据,它就可以在收到读请求后拉低SCL,直到转换完成再释放。主设备检测到SCL被拉低,就会一直等待,不会继续发送时钟脉冲。

我在项目中遇到过一种情况:某个从设备在特定条件下会拉伸时钟超过Linux I2C控制器默认的超时时间(通常35ms左右),导致通信失败。解决办法是调整控制器的超时参数,或者在驱动中增加重试机制。

时钟拉伸的关键点:

  • 从设备主导:只有从设备可以拉伸时钟,主设备只能被动等待。
  • 发生在SCL低电平期间:从设备在SCL低电平时将其拉低,主设备检测到后停止产生时钟。
  • 超时风险:如果从设备拉伸时间过长,主设备可能触发超时复位,导致总线错误。
  • 不是所有从设备都支持:有些从设备不支持时钟拉伸,驱动中需要做兼容处理。

嗯,这里要注意:时钟拉伸在标准模式(100kHz)下问题不大,但在快速模式(400kHz)或高速模式(3.4MHz)下,从设备的拉伸时间必须严格控制。Linux内核中,i2c_adapter的超时参数通常可以通过设备树或驱动代码调整。

1.5 小结:这些基础怎么用?

回顾一下,我们讲了总线架构、时序、地址格式和时钟拉伸。这些知识在驱动移植中具体怎么用?我简单列几个场景:

  • 设备树配置:reg属性填的就是从机地址,7位还是10位由驱动标志决定。
  • 驱动probe流程:i2c_check_functionality检查控制器是否支持所需功能(比如时钟拉伸)。
  • 读写时序:根据从机数据手册的时序要求,设置合适的传输速率和超时时间。
  • 调试手段:抓波形确认起始/停止条件、地址应答、时钟拉伸是否正常。

这些内容看起来基础,但我在实际项目中见过太多因为地址写错、时序不匹配、时钟拉伸没处理好而导致的疑难杂症。把基础打牢,后面调试才能有的放矢。