4、I2C协议基础:标准模式、快速模式、高速模式时序参数对比
做嵌入式这些年,I2C 总线我接触得最多。从最早的单片机挂个 EEPROM,到后来给传感器阵列做数据采集,几乎每个项目都离不开它。很多新手拿到 I2C 设备,直接调库函数就跑,结果要么通信不稳定,要么速度上不去。说白了,问题往往出在对时序参数的理解上。
今天我们就来把 I2C 的三种模式——标准模式、快速模式、高速模式——的时序参数掰开揉碎讲清楚。你想想看,搞懂了这些,以后调 I2C 总线就像看交通信号灯一样简单。
4.1 三种模式的核心差异
I2C 总线从诞生到现在,主要经历了三个速度等级。我习惯把它们比作三种路况:
- 标准模式(Standard-mode, Sm):100 kHz,像城市普通道路,稳但慢。
- 快速模式(Fast-mode, Fm):400 kHz,像城市快速路,日常最常用。
- 高速模式(High-speed mode, Hs-mode):3.4 MHz,像高速公路,但需要特殊硬件支持。
嗯,这里要注意:高速模式不是所有 I2C 设备都支持的。我在项目中遇到过,有人把普通传感器挂到高速总线上,结果死活不通——因为从机根本不认 Hs-mode 的时序。
4.2 时序参数逐项对比
I2C 的时序参数,核心就几个:时钟频率、上升时间、下降时间、数据建立时间、数据保持时间。下面这张表我把三种模式的关键参数列出来了,建议你收藏。
| 参数 | 符号 | 标准模式 (Sm) | 快速模式 (Fm) | 高速模式 (Hs-mode) |
|---|---|---|---|---|
| 时钟频率 | fSCL | ≤ 100 kHz | ≤ 400 kHz | ≤ 3.4 MHz |
| SCL 低电平时间 | tLOW | ≥ 4.7 μs | ≥ 1.3 μs | ≥ 0.16 μs |
| SCL 高电平时间 | tHIGH | ≥ 4.0 μs | ≥ 0.6 μs | ≥ 0.06 μs |
| 上升时间 | tr | ≤ 1000 ns | ≤ 300 ns | ≤ 80 ns |
| 下降时间 | tf | ≤ 300 ns | ≤ 300 ns | ≤ 80 ns |
| 数据建立时间 | tSU;DAT | ≥ 250 ns | ≥ 100 ns | ≥ 10 ns |
| 数据保持时间 | tHD;DAT | ≥ 0 ns | ≥ 0 ns | ≥ 0 ns |
| SCL 时钟建立时间 | tSU;STO | ≥ 4.0 μs | ≥ 0.6 μs | ≥ 0.16 μs |
| 总线电容 | Cb | ≤ 400 pF | ≤ 400 pF | ≤ 100 pF |
关键点:从标准模式到快速模式,tLOW 从 4.7 μs 缩到 1.3 μs,快了 3.6 倍。而高速模式更是直接压到 0.16 μs。这意味着你的 MCU 必须能精确控制微秒甚至纳秒级别的延时。
4.3 上升时间与下降时间的陷阱
我个人觉得,上升时间(tr)和下降时间(tf)是最容易被忽略的参数。很多人只盯着时钟频率,结果总线电容一大,波形就变圆了。
为什么会这样?因为 I2C 是开漏输出,SCL 和 SDA 的上升沿是靠上拉电阻把电平拉高的。总线电容越大,RC 充电时间常数越大,上升时间就越长。
我曾经在一个项目里,把 10 个传感器挂到同一条 I2C 总线上,总线电容轻松超过 200 pF。快速模式下,上升时间直接飙到 500 ns,超过了 300 ns 的限值。结果就是:偶尔通信失败,数据错位。后来我把上拉电阻从 4.7 kΩ 换到 2.2 kΩ,问题才解决。
避坑指南:我曾经在高速模式下用过 10 kΩ 的上拉电阻,结果波形完全没法看。高速模式建议上拉电阻取 1 kΩ 到 2 kΩ 之间,同时总线电容要控制在 100 pF 以内。
4.4 数据建立时间与保持时间
数据建立时间(tSU;DAT)和数据保持时间(tHD;DAT)决定了数据在时钟边沿前后的稳定性。
- 建立时间:数据必须在 SCL 上升沿之前稳定下来。标准模式要求 250 ns,快速模式 100 ns,高速模式只有 10 ns。
- 保持时间:数据在 SCL 下降沿之后需要保持一段时间。三种模式都要求 ≥ 0 ns,但实际中我建议至少留 50 ns 的余量。
嗯,这里有个细节:保持时间虽然写的是 ≥ 0 ns,但如果你真的卡在 0 ns 边界上,噪声稍微一抖就可能出错。我个人的习惯是,在软件 I2C 实现中,每个数据位都加一个 NOP 延时,确保建立时间和保持时间都留有余量。
4.5 高速模式的特殊机制
高速模式(Hs-mode)和前面两种模式有个本质区别:它需要主机先发送一个特殊的「主代码」来切换总线状态。
流程是这样的:
- 主机先以快速模式(400 kHz)发送起始条件。
- 然后发送一个 8 位的主代码(0000 1xxx),其中 xxx 是主机标识。
- 从机收到主代码后,会切换到高速模式,并释放 SCL 和 SDA。
- 主机随后以 3.4 MHz 进行后续的数据传输。
- 传输结束后,主机发送停止条件,总线回到快速模式。
注意:高速模式下,从机必须支持 Hs-mode,否则它不会响应主代码。我曾经见过有人把普通 EEPROM 挂到高速总线上,结果从机一直 NACK,折腾了半天才发现是设备不支持。
4.6 三种模式的时序关系图
下面这张 SVG 图把三种模式的时序关系画出来了,你可以直观地看到时钟周期、高低电平时间的变化趋势。
4.7 实际移植中的参数选择建议
讲完了理论,说说实际干活时怎么选。我个人的经验是:
- 标准模式:适合低速传感器(如温度、湿度),总线长度可以到 1 米以上。上拉电阻用 4.7 kΩ 到 10 kΩ 都行。
- 快速模式:日常最常用,适合大多数 I2C 设备。总线长度建议控制在 30 cm 以内,上拉电阻用 2.2 kΩ 到 4.7 kΩ。
- 高速模式:只有在对吞吐量要求极高的场景才用,比如图像传感器、高速 ADC。总线长度必须控制在 10 cm 以内,上拉电阻用 1 kΩ 左右,而且 PCB 走线要特别注意阻抗匹配。
核心原则:能用快速模式就别上高速模式。高速模式对硬件设计的要求高出一大截,而且兼容性问题多。我见过太多人为了追求那点速度,结果花了两周调时序,得不偿失。
4.8 一个简单的时序检查代码
最后,分享一段我在调试 I2C 时序时常用的检查代码。它用 GPIO 模拟 I2C,并测量实际的 SCL 高低电平时间。
// 简易 I2C 时序检查函数(基于 GPIO 模拟)
// 返回 0 表示时序正常,返回 -1 表示时序异常
int i2c_timing_check(void) {
uint32_t t_high, t_low;
// 测量 SCL 高电平时间
GPIO_SetBits(SCL_PORT, SCL_PIN);
delay_us(1); // 等待电平稳定
t_high = get_sys_tick(); // 记录当前系统滴答
while (GPIO_ReadInputDataBit(SCL_PORT, SCL_PIN) == 1) {
// 等待 SCL 变低
}
t_high = get_sys_tick() - t_high;
// 测量 SCL 低电平时间
GPIO_ResetBits(SCL_PORT, SCL_PIN);
delay_us(1);
t_low = get_sys_tick();
while (GPIO_ReadInputDataBit(SCL_PORT, SCL_PIN) == 0) {
// 等待 SCL 变高
}
t_low = get_sys_tick() - t_low;
// 检查是否满足快速模式要求
if (t_high < 600 || t_low < 1300) {
return -1; // 时序过短,需要调整
}
return 0;
}
小技巧:如果你用的是硬件 I2C 外设,可以直接读寄存器里的时序配置值。但用软件模拟时,这个检查函数特别有用。我曾经靠它抓到一个上拉电阻虚焊的问题——当时 SCL 高电平时间只有 200 ns,明显不正常。