我第一次调 I2C 的时候,心里想的是:这协议也太简单了吧,SCL 传时钟,SDA 传数据,两根线搞定一切。结果板子上新贴的温湿度传感器,怎么读都是 0xFF。后来才反应过来,0xFF 意味着 SDA 根本没被拉低过,等于从机压根没参与通信。从那时起我就明白,I2C 的坑不在“协议看不懂”,而在“时序细节一抓一大把”。
这篇东西不是抄手册,而是把我自己从读时序图、选上拉电阻、用逻辑分析仪抓波形、到给 FPGA 写 I2C 控制器这一路攒下来的经验整理出来。适合刚接触嵌入式通信的新手,也适合那种“I2C 偶尔抽风、查了半天不知道问题在哪”的工程师。看完之后,至少你能做到:看到一张时序图不慌,遇到总线通信失败知道从哪里下手查。
1. 总线物理层:开漏输出和上拉电阻为什么是 I2C 的命根子
1.1 开漏输出到底是个什么东西
先说结论:I2C 的 SDA 和 SCL 两根线,任何设备都只能用“开漏”方式驱动,网上所有 I2C 教材都会强调这句话,但很少有人把“为什么”讲透。
开漏输出的本质是:芯片引脚内部只有一个 N 沟道 MOS 管接到地,管子导通时引脚被拉低,管子截止时引脚等于悬空。你没法用引脚主动输出高电平,高电平全靠外部电阻把线“带”上去。对比常见的推挽输出——一个管子拉高、一个管子拉低,谁接管谁输出,速度又快驱动又强,但放在 I2C 这种多设备共享两根线的场景里就是灾难。
想象一下:设备 A 想发高电平,设备 B 想发低电平,推挽输出下两个设备一个往电源推、一个往地拉,轻则逻辑错误,重则灌电流过大直接烧管子。开漏输出配合“线与”逻辑就不存在这个问题:只要任何一个设备拉低,总线就是低电平,高电平只有在所有设备都释放总线时才可能出现。这就是 I2C 能支持一主多从、甚至多主机仲裁的物理基础。
1.2 上拉电阻阻值不是随便选的
既然高电平靠上拉电阻“拉”,那阻值选多少直接决定波形长什么样。我见过太多人抄别人原理图,抄了个 4.7k 就完事,换了个长线、换了个高速从机,通信就开始飘。上拉电阻本质上是在“灌电流能力”和“上升沿时间”之间找平衡。
- 最小阻值由低电平电压决定:管子拉低时,电阻上流过电流,电阻两端压降不能太大,否则总线低电平就超过了器件识别阈值。常见算法是 (Vcc - Vol) / Iol,比如 5V 系统、低电平输出电流 3mA、Vol 上限 0.4V,算出来最小大概 1.5k。阻值比这个还小,低电平就压不下去,这就是“电阻小了不通信”的典型原因。
- 最大阻值由总线电容和上升时间决定:总线上的走线、引脚、连接器都有寄生电容,上拉电阻和这些电容组成 RC 充电回路。RC 常数越大,上升沿越缓。I2C 规范对每个速率模式都规定了最大上升时间 tr,比如 100k 模式通常要求 tr 不超过 1000ns,400k 模式要求不超过 300ns。估算公式是 tr ≈ 0.8473 × R × Cbus,如果总线电容有 200pF,跑 400k 时电阻上限也就 1.8k 左右,这时用 4.7k 大概率会在高电平建立之前就采数据,出错是必然的。
实际选型,我一般这样拍:短线、100k 慢速,4.7k 起步没问题;总线稍长、跑到 400k,换 2.2k;再长或者设备多、容性负载重,直接 1k。3.3V 和 5V 系统都可以按这个思路微调,关键是用示波器看上升沿,别让波形在 SCL 高电平窗口里还歪歪扭扭。
经验补充:如果板子上挂了很多从机,总线电容会明显变大,这时“降速”比“换更小的电阻”更稳妥。无条件用示波器的话,先把速率降到 100k,能通就说明边沿问题为主。
2. 时序图逐项拆解:起始位、停止位、数据位和 ACK 的底层逻辑
2.1 时序图上一堆 tSU、tHD 到底在约束什么
I2C 时序图最劝退的地方就是满屏的 tSU;STA、tHD;DAT、tBUF 这种缩写。其实它们都是在回答一个问题:某一根线的电平变化,相对另一根线必须在什么时间窗口内完成。
拿最常见的启动时序举例,起始条件定义为:SCL 保持高电平期间,SDA 从高变低。规范要求 SDA 在 SCL 变低前先稳定一段时间,这个时间是 tHD;STA,还要保证 SCL 已经拉高了足够久才允许 SDA 拉低,这是 tSU;STA。这两者的本质是防止误触发:如果 SDA 变化时 SCL 还没稳定在高电平,从机可能把普通数据位误认为起始位。
数据位的规则更简单也更容易忽略:SCL 高电平期间,SDA 不允许变化。数据只能在 SCL 低电平期间切换,SCL 变成高电平后,SDA 必须保持稳定,等从机在 SCL 高电平中间采样。这个窗口很短,100k 模式下数据建立时间 tSU;DAT 要求 250ns 以上,400k 模式要求 100ns 以上。对 MCU 的硬件 I2C 来说一般没问题,但软件模拟时如果中断里乱插延时,很容易在 SCL 拉高后不小心动了 SDA,一帧数据全乱。
协议规范里这几个关键参数,我整理了一个常用表格,具体数值还是以 NXP 的 UM10204 为准:
| 参数 | 含义 | 100k 模式典型值 | 400k 模式典型值 |
|---|---|---|---|
| tHD;STA | 起始条件保持时间 | 4.0 µs | 0.6 µs |
| tSU;STA | 起始条件建立时间 | 4.7 µs | 0.6 µs |
| tSU;DAT | 数据建立时间 | 250 ns | 100 ns |
| tSU;STO | 停止条件建立时间 | 4.0 µs | 0.6 µs |
| tBUF | 停止到下一次起始的总线空闲时间 | 4.7 µs | 1.3 µs |
2.2 地址字节和 ACK/NACK 是怎么配合的
I2C 从机寻址是 7 位或 10 位,最常见的是 7 位地址,加上 1 位读写方向位组成第一个字节。比如一个设备地址是 0x50,写方向就是 (0x50 << 1) | 0 = 0xA0,读方向是 (0x50 << 1) | 1 = 0xA1。逻辑分析仪解码后显示的是 0xA0 还是 0xA1,就能立刻看出主机是准备写还是准备读。
每次传输完一个字节,接收方要回一个 ACK:在第 9 个时钟周期,接收方把 SDA 拉低,表示“我收到了”。如果接收方没拉低,SDA 保持高电平,这就是 NACK。NACK 的常见原因有:设备地址不对、设备进入了低功耗模式、总线地址冲突导致多个设备同时响应、或者设备正在忙内部的 EEPROM 写周期。有一次我调一块新板子,读地址写对了却一直 NACK,最后发现从机地址引脚 A0/A1 硬件接错,这种细节最消耗时间。
读数据时有个容易搞反的点:主机接收完最后一个字节后,必须主动回 NACK 而不是 ACK,然后发停止条件。这个 NACK 的作用是告诉从机“你不用再发了”。如果主机回 ACK,从机会认为你还要数据,继续驱动 SDA,主机发停止时就会产生总线冲突。很多人第一次写读 EEPROM 的驱动,在这里栽过跟头。
2.3 重复起始条件在随机读里的作用
光会发 START、发数据、发 STOP,能处理大多数简单传感器,但做 EEPROM 随机读时一定会遇到“重复起始条件(Repeated START)”。随机读取(随机地址读)的过程是:发送 START,然后发送设备地址 + 写位,再发送内部字地址;之后不发送 STOP,而是再发一个 START(即重复起始),接着发送设备地址 + 读位,之后读回数据。一切看起来和“先写后读”有点像,但差别在于:第一个事务没有以 STOP 结束,总线的所有权始终被主机握着,中间不让别的设备插进来。
时序上有个细节:在“停止条件”和“重复起始条件”之间,SDA 的跳变方向恰好都是 SCL 高电平期间 SDA 从高到低,但重复起始之前 SCL 并没有停止。所以硬件上,从机区分这两个条件的方式是看 SCL 是否有完整的低电平周期。如果 SCL 一直高着,SDA 突然拉低,从机认为这是重复起始。对软件模拟来说,实现重复起始要小心:拉低 SDA 之前必须先确保 SCL 已经是高电平,而且这个高电平之前最好先有一小段 SCL 低电平,避免被理解错。
3. 一次真实抓包:逻辑分析仪解析 EEPROM 读写全过程
3.1 逻辑分析仪的接线、采样率和触发设置
调 I2C 最忌讳“盲调”,对着代码猜来猜去不如抓一帧波形看实际发生了什么。逻辑分析仪是排查 I2C 最趁手的工具,示波器能看电气特性,但协议解码还是逻辑分析仪方便。
接线非常简单:逻辑分析仪的 CH0 接 SDA,CH1 接 SCL,前提是共地。采样率建议至少设到 4Mbps 以上,也就是被测信号最高频率的 4 到 8 倍以上。如果你在调 400k 的 I2C,4M 采样绰绰有余;如果只是 100k,2M 也能凑合。触发条件设置成 SDA 下降沿触发,这样按下开始之后,一旦检测到起始条件就自动开抓,抓到一整个事务。解码设置里选 I2C 协议,把 SDA/SCL 通道指对,设备地址模式选 7 位或 10 位,逻辑分析仪会自动标出 START、ADDR、ACK、DATA、STOP。
但我不建议完全依赖自动解码。自己数一次位,比看一百次解码结果更能加深理解。你可以把逻辑分析仪解出的波形放大,从 START 后的第一个字节开始,按位把高低电平读出来,先读地址位的最高位还是最低位、ACK 是低还是高,自己推一遍,整个过程会变得非常直观。
3.2 EEPROM 写操作在波形上的完整形态
以常见的 AT24C02 为例,器件地址是 1010 A2 A1 A0,假设 A2/A1/A0 都接地,那么 7 位地址是 0x50。写一个字节到地址 0x10,主机的完整输出是:
START + 0xA0 + ACK + 0x10 + ACK + 数据 + ACK + STOP在逻辑分析仪波形上,你会看到:
- 第一段是 START 条件:SCL 高电平期间,SDA 出现一个下降沿。
- 第二段是 8 个时钟脉冲,SDA 依次输出 1 0 1 0 0 0 0 0,对应 0xA0,第 9 个时钟 SDA 被从机拉低,这是 ACK。
- 第三段是 8 个时钟,SDA 输出 0x10,第 9 个时钟出现 ACK。
- 第四段是 8 个时钟输出数据字节,再跟一个 ACK。
- 最后是一个 STOP 条件:SCL 高电平期间,SDA 从低变高。
如果我在网上找一段 FPGA 的 Verilog 代码来写这个时序,状态机一般会分成 IDLE、START、发送地址、等待 ACK、发送字地址、等待 ACK、发送数据、等待 ACK、STOP 这几个状态。核心是每个状态下都用一个计数器产生 SCL 的翻转,并且保证 SDA 只在 SCL 低电平时变化。
localparam IDLE = 3'd0; localparam START = 3'd1; localparam SEND_ADDR = 3'd2; localparam WAIT_ACK1 = 3'd3; localparam SEND_WORD = 3'd4; localparam WAIT_ACK2 = 3'd5; localparam SEND_DATA = 3'd6; localparam WAIT_ACK3 = 3'd7; localparam SEND_STOP = 3'd8;状态机设计的关键不是“能不能跑”,而是“SDA 切换和 SCL 高低电平之间是否留足了建立时间”。很多人写 Verilog 时,在 SCL 高电平边沿同时改 SDA,仿真可能通过,上板就随机出错。正确做法是让 SDA 在 SCL 下降沿之后更新,保持至少半个 SCL 周期的稳定时间。
3.3 EEPROM 读操作的重头戏:随机读时序
读 EEPROM 不是发完地址就能直接读。最基本的当前地址读,只需要 START + 设备地址(读) + 数据 + NACK + STOP,读出来的是 EEPROM 内部地址指针指向的数据。但一般我们想要随机读:先写一个要读的字地址,然后再读。这个过程的时序是:
START + 0xA0 + ACK + 0x10 + ACK + START + 0xA1 + ACK + 读数据 + NACK + STOP波形上最明显的特点就是中间会出现两次 START,第一次 START 之后跟随的是写地址字节和字地址,第二次 START 之后直接是读地址字节。在逻辑分析仪解码结果里,如果 CRC 或者数据长度不对,多半是第二次 START 被误识别成了 STOP + START。排除这个问题,主要看主机发完“重复起始”后,SDA 拉低前 SCL 是否保持高电平足够久。
读操作的最后一个字节后面,主机输出的是 NACK 而不是 ACK。抓波形时你会看到第 9 个时钟 SDA 是高电平,然后 STOP 条件紧接着出现。如果这里你看到的是 ACK,后面 D0 数据线上可能还会出现不想看到的“多余字节”。
4. 调试时踩过的坑:从波形特征反推故障根源
4.1 上拉电阻导致的不稳定是个幽灵问题
上拉电阻选错,往往不会第一次就让你完全不通,而是“时好时坏,温度一变就出问题”。这比完全不通更折磨人。
电阻过大时,上升沿变得很缓。SCL 在低电平结束之后,需要一段时间才能充电到高电平阈值,导致从机采样时 SCL 可能还在高电平阈值的边缘徘徊。轻则数据采样错误,重则整帧乱掉。电阻过小时,低电平可能压不下去,SDA 的低电平只有 0.8V、1.0V,逻辑分析仪可能认为这是高电平,于是所有 ACK 都看不到。
用示波器看是最直接的。正常波形里,从低到高的上升沿应该是干净利落的,如果看到一条“斜率很缓”的斜线,基本可以锁定上拉电阻过大或总线电容太大。如果低电平有“台阶”,或者低电平底噪很高,就要怀疑电阻是否偏小、灌电流是否超了器件规格。
注意:测量时探头的地要尽量靠近总线,探头本身的寄生电容也会拖慢边沿,测量结果和实际信号会有差异,但趋势不会骗人。
4.2 外部上拉和内部上拉到底能不能共用
很多 MCU 的 GPIO 内部有可配置的上拉电阻,有些人为了省两个电阻,直接开启内部上拉,就以为 I2C 能用了。
内部上拉阻值一般在 30k 到 50k 之间,对 I2C 来说太弱了。弱上拉意味着充电电流小,上升沿慢,尤其是总线电容稍大一点,波形就像心电图一样歪。还有一个容易被忽略的问题:如果你既开了内部上拉,又焊了外部上拉,这两个电阻是并联关系。假设外部是 4.7k、内部是 40k,并联后约 4.2k,看起来影响不大。但问题是内部上拉阻值随温度、工艺变化很大,不是一个精确的规格,很多时候你换了颗芯片,同样的板子 I2C 波形就不一样了。
我的建议很简单:I2C 要用专门的外部上拉电阻,内部上拉全部关掉。如果只是想快速验证个功能、总线极短、速率只有 100k,那可以临时用内部上拉,但别把它当长期方案。曾经有块板子为了省料,内部上拉配 100k 的 I2C 也能跑,结果客户现场环境温度一高,通信偶发失败,最后加了两颗 4.7k 电阻才彻底解决。
4.3 总线死锁和时钟拉伸:比 ACK 异常更隐蔽
有时候 I2C 一直卡在等待 ACK,用逻辑分析仪看,SCL 一直为低,SDA 也一直为低,像是整个总线冻住了。这种大概率是从机内部时钟没跑起来,比如从机正处于复位状态、晶振没起振、或者从机在等待它的非易失存储器写操作完成。I2C 规范里允许从机拉低 SCL 来“时钟拉伸”,也就是从机告诉主机“先别继续发,我在忙”。如果从机拉低了 SCL 之后一直不释放,主机会一直等,表现就是总线挂死。
解决办法一般有三个方向:一是给从机一个硬件复位,恢复前确保总线释放;二是主机做一个超时判断,比如超过某个时间没见到 SCL 释放就主动发 STOP 或复位总线;三是用 9 个时钟脉冲把卡住的从机状态机“推”出来。实际操作里,如果你是在软件模拟 I2C,可以在等待 SCL 释放的时候加计数器;如果用的是 MCU 硬件 I2C,很多外设也没有自动超时,就得靠外部看门狗或者定时器在更高层级干预。
4.4 100k 还是 400k,为什么经常“标称支持却跑不稳”
很多传感器数据手册写着“支持 400k”,实际板上跑 400k 却不稳定。问题一般不在从机,而在主机这边的时序参数、上拉电阻和总线电容三者是否匹配。400k 模式下,数据建立时间只有 100ns,上升沿最多 300ns,如果上拉电阻偏大、总线过长,留给采样的高电平窗口就会被吃掉大半,跑不稳很正常。
我的调试顺序是:先把速率降到 100k,确认功能正常;然后看波形上升沿是否已经接近 300ns;如果边沿已经很缓,就先减小上拉电阻,再看看逻辑分析仪解码是否稳定;最后才提升速率到 400k。这样一步一步来,比直接改寄存器参数盲试要快得多。
5. 工具链和实现方式:逻辑分析仪、示波器、硬件 I2C 与软件模拟的取舍
5.1 逻辑分析仪和示波器怎么配合使用
逻辑分析仪和示波器各有所长。逻辑分析仪能解码,能按帧查看数据内容,但不擅长反映真实的电平幅度和边沿细节;示波器正好相反,能看模拟细节,但没有协议解码那么直观。调 I2C 的标准流程是:先用示波器看 SCL/SDA 的边沿是否正常、高电平是否达到 VCC、低电平是否够低,再用逻辑分析仪抓数据内容。如果一上来就用逻辑分析仪,可能会解码出一大堆“错误”数据,但不知道错误根源是电气问题还是时序问题。
手头只有一个逻辑分析仪时,可以观察一个间接信号:波形中 SCL 高电平的“占空比”和上升沿“倾斜程度”。SCL 高电平过短或 SDA 数据窗口不稳,往往就是边沿时间过长。如果逻辑分析仪显示 SDA 数据中有些位被错误识别,比如 0x50 被解成 0x40,这通常是 SDA 在 SCL 高电平时还没稳定。
5.2 硬件 I2C 还是软件模拟,时序可控性是个关键考量
做产品选型时,硬件 I2C 和软件模拟一直是个争论点。
硬件 I2C 的优势是省 CPU、速率准确、有中断和 DMA,适合大量数据的场合。缺点是引脚往往是固定的,个别 MCU 的 I2C 外设实现还有各种“奇怪的坑”,比如 SCL 低电平时间不够、总线忙标志卡死。软件模拟的优势是任意 GPIO 都能用,时序完全由自己控制,出了 bug 可以直接打调试信息;缺点是占用 CPU,且如果程序里来了高优先级中断,可能导致 SCL/SDA 电平保持时间超过规范。
软件模拟 I2C 有个核心原则:SDA 只能在 SCL 低电平时改变,SCL 高电平时 SDA 必须稳定。起始和停止条件则相反,是在 SCL 高电平时让 SDA 跳变。我写过一个最小模拟函数,看起来很简单,但很能说明问题:
void i2c_start(void) { SDA_HIGH(); SCL_HIGH(); delay_us(5); SDA_LOW(); // SCL 高电平期间,SDA 拉低 => START delay_us(5); SCL_LOW(); // 释放时钟,进入第一个字节 } void i2c_stop(void) { SDA_LOW(); SCL_HIGH(); delay_us(5); SDA_HIGH(); // SCL 高电平期间,SDA 拉高 => STOP delay_us(5); }这两个函数看起来没啥技术含量,但它演示了最关键的点:延时函数里千万不要被中断打断太久。如果你在模拟 I2C 的中间开了全局中断,一个串口中断服务函数跑了 100µs,在 100k 模式下等于多出 10 个时钟周期,从机那边早就乱套了。所以软件模拟时,要么关中断,要么把延时拆成不会被长时间阻塞的方式,要么就把 I2C 通信放到一个足够干净的上下文里执行。
5.3 多从机挂载、总线扩展和电平转换对时序的影响
I2C 从机数量不是无限制的,除了地址限制,还有总线电容限制。挂得太多,总线电容超过 400pF 之后,即使上拉电阻调到很小,波形也会劣化。这时有两种常见方案:用 I2C MUX 芯片做通道扩展,把不同从机分到不同总线段;或者用 I2C 缓冲器/中继器把长走线分段。
电压电平转换也要特别注意。3.3V 主控接 5V 传感器时,I2C 总线不能简单用两个电阻分压,因为 I2C 是双向的,低电平由双方共同产生。常见的双向电平转换电路是用一个 N 沟道 MOS 管(比如 BSS138)接在两个上拉电阻中间:一侧拉低时,MOS 管导通,把另一侧也拉低;两侧都释放时,各自的上拉电阻把电压拉回自己的电平。这个电路在工作时,低电平传输几乎没有延迟,但高电平的上升沿会受 MOS 管结电容影响,总线频率高时依然要看波形。
6. 从 I2C 看协议族:SMBus 和 I3C 带来的时序变化
6.1 SMBus 和 I2C 的差异:超时机制和分组错误检查
SMBus 是基于 I2C 衍生出来的系统管理总线,常见于电池管理、电源芯片、服务器主板。它和 I2C 的物理层几乎一样,但规定了更严格的行为约束:所有传输不能超过 25ms 超时,以防止从机死锁拖死主机;从机必须支持时钟拉伸但不能超过指定时间;还有分组错误检查(PEC)字节作为可选校验。
时序上的差异主要体现在:I2C 本身允许无限等待时钟拉伸,SMBus 则要求超时判定,所以你在驱动里不能像 I2C 那样一直等 SCL 释放,而是要加一个定时器。另外,SMBus 的读写类型(如 SMBus Read Word、Write Word)都有固定的命令格式,不能像 I2C 那样“想发什么就发什么”。如果你把 I2C 驱动一股脑套在 SMBus 设备上,有时也能跑通基本读写,但碰到 PEC 和超时相关的功能就会出问题。
6.2 I3C 保留了 I2C 兼容模式,但动态寻址和推挽输出彻底改变了时序
I3C 是 I2C 的下一代演进方向,兼容传统 I2C 设备,但引入了很多新特性:动态地址分配让多个相同器件可以在总线上共存;推挽输出在高速模式下使用,大幅提升上升沿速度;还支持带内中断,从机可以直接通过总线向主机发中断请求,省了一根中断线。
对工程师来说,最直观的变化是时序参数更短,边沿更快。I3C 高速模式下,SCL 频率从 12.5MHz 起步,远不是 I2C 的 100k/400k 能比的。如果老工程师习惯用“低速、慢边沿、大电阻”的思路去设计 I3C 总线,只会看到满屏的反射和串扰。做新项目时如果确定短时间不碰 I3C,那 I2C 的经验还是主流;如果考虑兼容未来产品,布局时就应该预留更短的总线走线和更严格的分层布线。
我个人的习惯是,拿到一块新板子,不急着写驱动,先拿逻辑分析仪抓一帧上电时的总线状态,确认 SCL 和 SDA 的静态电平都正常,再接上从机抓一次它的读序列。很多时候“驱动不对”其实是硬件时序条件没满足,而波形从来不说谎。把 I2C 的时序吃透,后面再学 SPI、UART、SMBus,都会顺畅得多,因为串行通信的底层逻辑是相通的——你在正确的时间窗口里把电平摆对,剩下的就是协议的事了。