1. I2C总线物理层与协议层基础:SDA/SCL线、开漏输出、上拉电阻、总线仲裁

各位同学,咱们今天聊I2C最底层的那些事儿。说实话,搞了这么多年嵌入式,我见过太多工程师上来就调代码,结果示波器一挂,波形乱七八糟,根本不知道问题出在哪。说白了,物理层没吃透,后面全是坑。

这一节,咱们就把I2C的“地基”打牢。你想想看,两根线就能挂几十个设备,还能双向通信,这背后是有道理的。

1.1 两线制:SDA与SCL的分工

I2C总线就两条线:SDA(串行数据线)和SCL(串行时钟线)。

  • SDA:负责传数据,地址、寄存器号、读写数据都走这根线。
  • SCL:负责传时钟,由主设备产生,从设备跟着这个节奏走。

我刚开始学的时候有个误区,以为SDA和SCL是“对称”的。其实不是。SCL永远是主设备在驱动,从设备只能拉低它(时钟拉伸),但绝不能主动产生时钟。这一点,你在调试时一定要记住。

关键点: 空闲时,SDA和SCL都是高电平。谁先拉低SDA,谁就开始说话。

1.2 开漏输出:为什么非它不可?

I2C的SDA和SCL引脚,内部都是开漏输出结构。什么叫开漏?说白了,就是MOS管的漏极直接接到引脚上,没有内部上拉。管子导通时,引脚被拉到GND(低电平);管子截止时,引脚浮空(高阻态)。

为什么要这么设计?两个原因:

  1. 多设备“线与”:多个设备的开漏输出可以直接连在一起。只要有一个设备拉低,总线就是低电平。所有设备都不拉低,总线才是高电平。这就实现了“多主仲裁”的基础。
  2. 电平转换方便:你可以给总线配上3.3V的上拉电阻,那高电平就是3.3V;配上5V的上拉,高电平就是5V。同一个芯片,换个上拉电压就能接不同电平的系统。

嗯,这里要注意:开漏输出只能输出低电平,不能主动输出高电平。高电平全靠外部上拉电阻提供。所以,如果你发现SDA或SCL一直低电平拉不上去,八成是上拉电阻没焊,或者阻值太小了。

我的习惯: 画原理图时,我会在SDA和SCL上各加一个测试点。调试时用示波器先看这两个点,能快速判断是主设备没发信号,还是从设备把总线拉死了。

1.3 上拉电阻:阻值选不对,通信全白费

上拉电阻是I2C总线的“命根子”。阻值选大了,上升沿太缓,高速通信时波形畸变;选小了,灌电流太大,芯片可能烧掉。

标准I2C规范里,上拉电阻的推荐范围是1kΩ ~ 10kΩ。但实际选型要看几个因素:

总线电容 通信速率 推荐上拉电阻
< 100 pF 100 kHz(标准模式) 4.7kΩ ~ 10kΩ
100 ~ 200 pF 400 kHz(快速模式) 2.2kΩ ~ 4.7kΩ
200 ~ 400 pF 1 MHz(快速+模式) 1kΩ ~ 2.2kΩ

我曾经在一个项目里,板子上挂了8个I2C从设备,总线长度接近30cm。一开始用了10kΩ上拉,结果400kHz下波形上升沿都快赶上半个时钟周期了。换成2.2kΩ,问题立刻解决。所以,总线电容越大、速率越高,上拉电阻就要越小。

避坑指南: 我曾经遇到过一块板子,SDA波形正常,SCL波形却一直有毛刺。查了半天,发现是SCL的上拉电阻焊错了,用了100Ω。芯片的灌电流直接超标,SCL被拉低后回弹特别慢。换回4.7kΩ,一切正常。

1.4 总线仲裁:多主设备怎么不打架?

I2C支持多主设备。也就是说,总线上可以有好几个主设备,谁先发起通信,谁就占用总线。但如果两个主设备同时发起呢?这就靠总线仲裁来解决。

仲裁的原理很简单:

  1. 每个主设备在发送数据时,都会同时监测SDA的电平。
  2. 如果自己发送的是高电平,但SDA上却是低电平(被另一个主设备拉低了),那它就知道自己“输”了。
  3. 输掉仲裁的主设备立即停止发送,转为从设备模式,等待下一次机会。

你想想看,这个机制依赖的就是开漏输出和上拉电阻。没有开漏,多个设备同时输出高低电平,直接就短路了。有了开漏,才能实现“线与”,才能让仲裁顺理成章。

核心逻辑: 仲裁发生在SDA上,SCL由所有主设备共同产生“线与”时钟。谁先发送低电平,谁就赢得总线。

下面这张图,把I2C物理层和协议层的关系梳理了一下,你看完应该能有个整体印象。

I2C总线物理层与协议层结构图 I2C总线物理层与协议层结构 物理层 协议层 SDA / SCL 两线制 开漏输出 + 上拉电阻 线与逻辑(多设备共用) 电平转换(3.3V / 5V) 起始条件 / 停止条件 7位 / 10位地址 + 读写位 应答位(ACK / NACK) 总线仲裁(多主竞争) 物理层是基础,协议层是规则。没有物理层的开漏和上拉,仲裁根本没法实现。

你看,物理层提供了“线与”的能力,协议层才得以实现仲裁。这两层是相辅相成的。搞懂了这些,后面讲时序、讲驱动,你才能知道为什么代码要那么写,为什么波形要那样看。

一个小技巧: 调试时如果总线卡死,先测SDA和SCL的电平。如果都是低电平,说明有设备把总线拉死了。逐个断开从设备,就能找到“凶手”。我靠这招,省了不少抓头发的时间。