4. 从设备实现时钟拉伸的硬件方法:开漏输出与 SCL 拉伸电路
时钟拉伸,说白了就是从设备把 SCL 拉低,让主设备等着。很多刚入行的朋友以为这是软件能搞定的。其实硬件才是根本。硬件不靠谱,软件怎么写都白搭。我在项目中见过太多直接靠 GPIO 模拟开漏输出结果导致信号毛刺吞掉时序的例子了。
这一节我们就掰开揉碎地讲清楚:从设备到底怎么在硬件上把 SCL 拉低,再安全地释放。
4.1 开漏输出——时钟拉伸的基石
I2C 总线物理层就是一根 SDA、一根 SCL,都接上拉电阻到 VCC。所有设备都只能拉低总线,不能主动拉高。这就是开漏(Open-Drain)输出。
- 输出低电平:NMOS 导通,引脚内部接地,SCL 被拉低。
- 释放总线:NMOS 关断,引脚高阻态,由上拉电阻把 SCL 拉到高电平。
“开漏”听起来玄乎,其实就是只能向下拉,不能往上推。我早期画板子时,有个新手把 SCL 接成了推挽输出,上电后直接和主设备对拉,总线锁死。嗯,教训很深刻。
4.2 SCL 拉伸电路的实际结构
从设备想要完成时钟拉伸,内部电路大概长这样(下面我画了张图帮你理解):
核心要点:拉伸的本质是NMOS开关延时关断。主设备释放SCL后,从设备继续维持低电平,直到自己准备好。
这中间任何抖毛刺、过冲、振铃都会导致主设备误判SCL为高,从而提前发起下一个操作。
4.3 开漏输出的具体电路实现
常见的实现方式有两种:分立 NMOS 方案和集成 I/O 单元方案。
| 方案 | 优点 | 缺点 | 典型应用 |
|---|---|---|---|
| 分立 NMOS + 外部上拉 | 成本低,可调整驱动能力 | PCB 面积大,寄生电容难控 | 小批量、原型验证 |
| MCU/FPGA内部开漏 I/O | 集成度高,延迟可控 | 驱动能力有限(一般 <20mA) | 量产产品、嵌入式系统 |
| 专用 I2C 缓冲器/隔离器 | 信号完整性好,热插拔安全 | 成本高,占用板空间 | 通信距离长、高可靠性场景 |
我的建议:如果你用 MCU 做从设备,先确认 I/O 是否支持开漏模式。有些便宜 MCU 只能推挽输出,这时候就必须加一个外部 NMOS 来控制 SCL。我在一次智能传感器项目里就吃了这个亏,后来不得不飞线加 2N7002。
4.4 关键硬件参数与设计原则
- NMOS 导通电阻 RDS(on) 尽量小,保证拉低时 SCL 电压低于 0.4V(VOL 标准)。
- 关断延迟 不能太长,否则主设备认为 SCL 一直被拉低而超时。我记得有一次 I2C 总线死锁,用示波器一看,从设备的 NMOS 关断要 2µs,而主设备超时只有 1.5µs。
- 上拉电阻 的选择:阻值太小,拉低电流过大;阻值太大,上升沿太慢,限制了时钟频率。
警惕毛刺:当场效应管关断时,沟道电荷通过体二极管泄放,可能在 SCL 上产生负尖峰。这个尖峰如果超过 -0.5V,会触发主设备的 ESD 保护钳位,导致假时钟。我建议在 SCL 与 GND 之间加一个小电容(5~20pF)来吸收尖峰。
4.5 从设备释放 SCL 的时刻控制
释放时机由从设备内部逻辑决定。常见做法是:
- 在接收完最后一个数据位后,内部计数器清零,开始处理数据。
- 处理完成前,SCL 保持低电平。
- 处理完成后,将 SCL 输出寄存器写 1(实际是关断 NMOS)。
// 伪代码示意:从设备触发时钟拉伸
void i2c_slave_clock_stretch(void) {
// 1. 拉低 SCL
SCL_PORT &= ~(1 << SCL_PIN); // 输出0(NMOS导通)
SCL_DDR |= (1 << SCL_PIN); // 使能输出
// 2. 处理数据(此处延时由内部状态机控制)
process_data();
// 3. 释放 SCL
SCL_DDR &= ~(1 << SCL_PIN); // 切换到输入(高阻)
// 或者使用开漏模式直接写1(部分MCU支持)
}
注意:写入 1 到开漏 I/O 寄存器时,有的 MCU 会自动切换到高阻态,但部分型号需要手动切换方向。千万要看数据手册!
4.6 硬件设计检查清单
| 检查项 | 要求 | 检查方法 |
|---|---|---|
| 开漏模式是否使能 | I/O 必须配置为开漏输出 | 读寄存器或原理图确认 |
| 下拉能力是否足够 | VOL < 0.4V @ IOL = 3mA | 负载曲线测试 |
| 上拉电阻值 | 1kΩ ~ 10kΩ (取决于总线电容) | 计算上升沿时间 tr = 0.847 × RP × Cb |
| 关断延迟 | < 1µs (最好 < 300ns) | 示波器测量从写寄存器到SCL实际上升 |
| 毛刺保护 | SCL上加RC滤波或小电容 | 在100MHz带宽下观察波形 |