I2C这东西,刚入行的时候觉得它简单得不行——两根线,一根时钟一根数据,挂一堆从机,地址一喊谁应答谁说话,能有多难?结果真到了项目里,SCL被拉死、总线锁死、多主机打架、上拉电阻选错导致波形爬不起来、逻辑分析仪抓出来一堆莫名其妙的START,才发现这两根线背后藏着的门道,比很多协议都深。这篇就把我这些年踩过的坑、翻过的数据手册、用逻辑分析仪一帧一帧抠出来的经验,从开漏物理层一路讲到多主仲裁,尽量把I2C彻底讲透。不管你是刚接触单片机的学生,还是被I2C总线锁死折磨过的嵌入式工程师,看完应该都能对这两根线有个全新的认识。
1. 为什么I2C非得用开漏输出,推挽不行吗
很多人学I2C的第一反应是:既然SCL和SDA都是输出高低电平,那用普通推挽输出不就行了?我一开始也这么想过,直到有一次用推挽驱动I2C,两个设备同时想拉低总线,一个输出高一个输出低,直接短路,芯片烫得能煎鸡蛋。从那以后我才真正理解开漏的意义。
1.1 开漏输出的物理本质
开漏(Open-Drain)在CMOS工艺里叫开漏,在TTL工艺里叫开集(Open-Collector),本质是一回事:输出级的MOS管只有下拉能力,没有上拉能力。也就是说,这个引脚只能主动把线拉到地(逻辑0),没法主动把线拉到VCC(逻辑1)。想要高电平,必须靠外部的上拉电阻把线拉上去。
这个设计乍看很反直觉——为什么要把输出能力阉割掉一半?答案就在I2C的核心需求上:多设备共享总线。
想象一条走廊,两边有很多房间,每个房间的人都只能"关门"(拉低),不能"开门"(拉高)。门默认是弹簧开着的(上拉电阻),只要没人关,门就是开的(高电平)。任何一个人关门,门就关上了(低电平)。这样无论多少人同时想关门,结果都只是"门关着",不会出现一个人想开一个人想关的冲突。这就是开漏的精髓——线与逻辑。
如果换成推挽,每个房间的人既能开门又能关门,那一个人想开、一个人想关,门就被硬生生掰断了,对应到电路上就是两个MOS管一个导通到VCC一个导通到GND,形成低阻通路,大电流烧毁器件。
1.2 上拉电阻到底怎么选
开漏输出必须配上拉电阻,这个电阻的取值不是随便拍脑袋的,它直接决定了波形的上升沿质量和总线能否正常工作。选大了,上升沿爬不上去,高速通信直接失败;选小了,低电平灌电流太大,器件可能扛不住。
上拉电阻的最小值由灌电流决定。I2C标准规定,标准模式(100kHz)和快速模式(400kHz)下,器件拉低时灌电流一般不超过3mA,快速模式+(1MHz)和高速模式(3.4MHz)另有规定。假设VCC是3.3V,VOL(低电平输出电压)最大0.4V,那么:
Rmin = (VCC - VOL) / IOL = (3.3 - 0.4) / 0.003 ≈ 967Ω所以3.3V系统下,上拉电阻一般不小于1kΩ。
上拉电阻的最大值由上升时间决定。I2C的上升时间有明确上限:标准模式1000ns,快速模式300ns,快速模式+120ns。上升时间由RC充电决定,公式是:
tr ≈ 0.847 × R × C其中C是总线总电容,包括走线电容、引脚电容、器件电容,标准规定总线电容不超过400pF。假设C=200pF,快速模式要求tr≤300ns:
Rmax = tr / (0.847 × C) = 300ns / (0.847 × 200pF) ≈ 1771Ω所以这个场景下,上拉电阻要在1kΩ到1.77kΩ之间选,通常取1.5kΩ或1.8kΩ。
实际工程里,我一般这么选:
| 模式 | 速率 | 典型上拉 | 总线电容上限 |
|---|---|---|---|
| 标准模式 | 100kHz | 4.7kΩ | 400pF |
| 快速模式 | 400kHz | 2.2kΩ | 400pF |
| 快速模式+ | 1MHz | 1kΩ | 550pF |
| 高速模式 | 3.4MHz | 300Ω左右 | 100pF |
注意:上拉电阻不是越小越好。我曾经为了追求上升沿陡峭,把上拉降到470Ω,结果从机拉低时灌电流超过10mA,用了半年后从机引脚出现间歇性失效。后来老老实实按灌电流上限反推,才稳定下来。
1.3 总线电容这个隐形杀手
总线电容是I2C设计里最容易被忽略的参数。每挂一个器件,引脚电容大概5到10pF;每厘米PCB走线,大概1到2pF;如果用了排线或者飞线,电容更大。挂十个器件加上走线,轻松超过100pF。
总线电容大了会怎样?上升沿变缓,波形变成"圆肩",逻辑分析仪上看就是上升沿拖泥带水。速率低的时候还能凑合,一旦上到400kHz,上升时间超过300ns,从机可能还没采到高电平,主机已经开始下一个时钟了,通信直接崩。
解决办法有几个:一是减少挂载器件,用I2C多路复用器(比如TCA9548A)把总线分成几段,每段单独上拉;二是缩短走线,把器件尽量靠近;三是降低速率,用100kHz换稳定性。我做过一个项目,总线上挂了12个传感器,400kHz死活跑不通,换成100kHz立刻稳定,后来加了多路复用器才把速率提回去。
2. SCL和SDA的时序到底谁说了算
I2C的时序规则看起来简单,但真正理解"谁在什么时候控制哪根线",是写出稳定驱动和快速定位问题的关键。很多人调I2C调不通,根本原因就是没搞清楚SCL和SDA的控制权在主机和从机之间是怎么切换的。
2.1 起始条件和停止条件的本质
起始条件(START)的定义是:SCL为高时,SDA由高变低。停止条件(STOP)的定义是:SCL为高时,SDA由低变高。
为什么这么定义?因为正常传数据的时候,SDA只在SCL为低的时候变化,SCL为高的时候SDA必须稳定,这样接收方才能在SCL高电平期间采样到有效数据。起始和停止条件故意违反这个规则,就是为了在不占用额外信号线的情况下,标记一帧数据的开始和结束。
这里有个细节很多人不知道:起始和停止条件永远由主机产生。从机只能被动响应,不能主动发起或终止传输。这也是为什么I2C是多主总线但实际应用中绝大多数场景都是单主机——多主机仲裁机制虽然存在,但实现复杂,一般项目用不上。
2.2 数据位的采样时机
数据位的传输规则是:SDA在SCL低电平期间改变,在SCL高电平期间保持稳定。接收方在SCL高电平期间采样SDA。
这个规则决定了主机和从机的操作节奏:
- 主机发送数据时:主机在SCL拉低后改变SDA,然后拉高SCL,从机在高电平期间采样。
- 从机发送数据时:从机在SCL拉低后改变SDA,主机在SCL拉高后采样。
注意,SCL始终由主机控制(除了时钟拉伸的情况),从机只是"借用"SCL低电平的窗口来改变SDA。
2.3 时钟拉伸:从机唯一的反抗手段
时钟拉伸(Clock Stretching)是从机唯一能主动干预总线节奏的机制。当从机需要更多时间处理数据时,它可以在主机释放SCL后,继续把SCL拉低,主机检测到SCL没有按预期变高,就知道从机还没准备好,于是等待。
这个机制在EEPROM写操作、传感器转换等待等场景很常见。比如AT24C系列EEPROM,写一个字节后需要5ms左右的内部写周期,期间从机就会拉低SCL,主机必须等它释放才能继续。
但时钟拉伸也是坑最多的地方。有些主机的I2C外设不支持时钟拉伸,或者支持得不完整,遇到从机拉低SCL就报超时错误。我遇到过STM32的硬件I2C在某些情况下处理时钟拉伸有问题,最后改用软件模拟I2C才解决。所以选型的时候,一定要确认主机的I2C控制器是否完整支持时钟拉伸。
提示:如果你的从机需要时钟拉伸,但主机不支持,可以考虑降低通信速率,给从机留足处理时间,或者改用软件模拟I2C,自己控制时序。
2.4 建立时间和保持时间的实际约束
I2C标准对建立时间(tSU)和保持时间(tHD)有明确规定:
| 参数 | 标准模式 | 快速模式 | 快速模式+ |
|---|---|---|---|
| 数据建立时间 tSU:DAT | 250ns | 100ns | 50ns |
| 数据保持时间 tHD:DAT | 0ns | 0ns | 0ns |
| 起始条件建立时间 tSU:STA | 4.7μs | 0.6μs | 0.26μs |
| 起始条件保持时间 tHD:STA | 4.0μs | 0.6μs | 0.26μs |
注意tHD:DAT是0ns,意思是数据在SCL下降沿之后可以立即改变,不需要额外保持时间。但tSU:DAT要求数据在SCL上升沿之前必须稳定足够长时间,标准模式是250ns。
这些参数在低速的时候很容易满足,但速率一上去就紧张了。特别是用软件模拟I2C的时候,如果GPIO翻转速度不够快,或者中断打断了时序,很容易违反这些约束。我的经验是,软件模拟I2C在100kHz下比较稳,400kHz就要小心了,1MHz基本别想。
3. 多主仲裁:两根线怎么做到不打架
多主仲裁是I2C最精妙的设计之一,也是很多人觉得"这也能行?"的地方。多个主机同时想通信,怎么保证不冲突?答案是:靠开漏的线与特性和一套逐位仲裁规则。
3.1 仲裁的基本规则
仲裁发生在SCL为高的时候。每个主机在发送每一位数据后,都会回读SDA的实际电平,和自己发送的电平比较:
- 如果自己发的是高,但读回来是低,说明有别的设备把它拉低了,自己仲裁失败,立即退出,转为从机模式。
- 如果自己发的是低,读回来也是低,说明要么没人竞争,要么别人也发了低,继续。
- 如果自己发的是高,读回来也是高,继续。
关键点:仲裁失败的主机不会破坏总线上的数据。因为开漏的线与特性,多个主机同时拉低,结果还是低,不会短路。仲裁失败的主机只是停止驱动SDA和SCL,让获胜的主机继续。
3.2 仲裁为什么不会丢数据
仲裁是逐位进行的,而且是在数据位和地址位上同时进行。假设主机A发送地址0x50,主机B发送地址0x52,二进制分别是:
0x50 = 0101 0000 0x52 = 0101 0010前六位完全一样,第七位A发0,B发1。当SCL为高时,A拉低SDA,B释放SDA(想发1)。B回读SDA,发现是低(被A拉低了),B就知道自己仲裁失败,退出。A继续发送,不受影响。
整个过程B没有破坏A的任何数据位,因为B在发现自己失败的那一刻就停止驱动了。这就是为什么仲裁不会丢数据——失败方在造成冲突之前就退出了。
3.3 仲裁和时钟同步的区别
很多人把仲裁和时钟同步搞混。时钟同步是多个主机在SCL上"协商"出一个共同的时钟,规则是:SCL的实际电平是所有主机SCL输出的线与结果。任何一个主机拉低SCL,SCL就是低;所有主机都释放SCL,SCL才变高。
时钟同步保证了即使多个主机的时钟频率不同,也能在SCL上达成一致。仲裁则是在数据线上决定谁获胜。两者配合,才能实现多主总线的无冲突通信。
实际项目中,多主仲裁用得很少,因为大多数系统只有一个主机。但理解这个机制,对理解I2C为什么这么设计、为什么开漏是必须的,非常有帮助。
3.4 多主场景下的实际坑
虽然多主仲裁理论上很完美,但实际用起来坑不少:
- 时钟同步对速率的影响:多个主机时钟频率不同,同步后的时钟频率由最慢的主机决定。如果有一个主机时钟特别慢,整个总线都被拖慢。
- 仲裁失败后的处理:仲裁失败的主机需要能够正确切换到从机模式,并重新发起通信。很多硬件I2C控制器对仲裁失败的处理不完善,需要软件介入。
- 总线锁死:如果某个主机在仲裁过程中异常复位,可能留下SCL或SDA被拉低的状态,导致总线锁死。
我做过一个双主机的项目,两个MCU共享一条I2C总线,结果经常出现总线锁死。后来发现是其中一个MCU复位时,I2C引脚状态不确定,把SDA拉低了。解决办法是在复位后先手动发送9个时钟脉冲,把总线"敲"醒,再初始化I2C。
4. 总线锁死:I2C最让人头疼的故障
总线锁死是I2C最经典的故障,几乎每个用I2C的人都遇到过。现象是:SCL或SDA被某个设备一直拉低,主机无法发起新的通信,整个总线瘫痪。
4.1 锁死的根本原因
锁死通常发生在以下场景:
- 主机在发送数据过程中被复位,从机还在等待下一个时钟,但主机已经不发了,从机把SDA拉低等待,导致SDA一直低。
- 从机在发送数据过程中,主机突然停止时钟,从机正在输出低电平,SDA被卡住。
- 电源上电顺序问题,某个设备先上电,把总线拉低,主机后上电,检测到总线忙,一直等待。
根本原因是:I2C的状态机在异常中断后无法自动恢复。从机不知道主机已经复位,还在等时钟;主机不知道从机在等什么,无法继续。
4.2 用时钟脉冲解锁总线
最常用的解锁方法是在SCL上手动发送9个时钟脉冲。原理是:从机在等待时钟时,每来一个时钟就移出一位数据,最多9个时钟后,从机的数据移位寄存器会全部移出,SDA被释放。
具体操作:
- 把SCL配置为推挽输出,SDA配置为输入。
- 发送9个SCL脉冲,每个脉冲高电平至少1.3μs(100kHz)。
- 检查SDA是否变高,如果变高,说明解锁成功。
- 发送一个STOP条件(SCL高时SDA由低变高),让总线回到空闲状态。
- 重新初始化I2C。
代码示例(伪代码):
void i2c_bus_recovery(void) { gpio_set_output(SCL_PIN); gpio_set_input(SDA_PIN); for (int i = 0; i < 9; i++) { gpio_write(SCL_PIN, 0); delay_us(5); gpio_write(SCL_PIN, 1); delay_us(5); if (gpio_read(SDA_PIN) == 1) { break; // SDA释放了 } } // 发送STOP条件 gpio_write(SDA_PIN, 0); delay_us(5); gpio_write(SCL_PIN, 1); delay_us(5); gpio_write(SDA_PIN, 1); delay_us(5); // 重新初始化I2C i2c_init(); }注意:发送时钟脉冲时,SCL必须用推挽输出,因为此时总线可能被从机拉低,开漏输出可能无法产生有效的上升沿。但发送完脉冲后,一定要把SCL恢复为开漏模式,否则会破坏I2C的线与特性。
4.3 硬件层面的预防措施
软件解锁是事后补救,更好的做法是硬件层面预防:
- 串联电阻:在SCL和SDA上串联100Ω左右的电阻,限制异常情况下的电流,保护器件。
- 总线缓冲器:用PCA9515之类的I2C缓冲器,隔离总线段,一段锁死不影响另一段。
- 电源监控:用复位芯片确保所有设备在电源稳定后才开始工作,避免上电顺序问题。
- 看门狗:主机加看门狗,异常时复位并执行总线恢复流程。
我现在的项目里,I2C总线上都会预留串联电阻的位置,虽然大部分时候不焊,但遇到问题可以焊上试试。另外,主机固件里一定会包含总线恢复函数,在I2C通信超时后自动调用。
4.4 逻辑分析仪在锁死排查中的作用
逻辑分析仪是排查I2C问题的神器。抓一帧波形,看SCL和SDA的状态,基本就能定位问题:
- SCL一直低:某个从机在时钟拉伸,或者SCL被短路。
- SDA一直低:从机在等待时钟,或者SDA被短路。
- 起始条件后没有地址:主机发送有问题。
- 地址后没有ACK:从机没响应,地址错了或者从机没上电。
我用的是Saleae Logic系列,配合I2C协议解码,能直接看到地址、数据、ACK/NACK,非常直观。便宜的可以用逻辑分析仪模块配合开源软件,也能满足基本需求。
5. 从地址到数据帧:I2C通信的完整拆解
理解了物理层和仲裁机制,接下来把一帧完整的I2C通信拆开看。这部分是实际写驱动、调通信的基础,每个细节都可能成为通信失败的原因。
5.1 7位地址和10位地址的区别
I2C支持7位和10位两种地址格式。7位地址是主流,10位地址用于地址空间不足的场景,但实际用得很少。
7位地址帧格式:起始条件 + 7位地址 + 1位读写位 + ACK/NACK。
读写位:0表示写,1表示读。所以一个7位地址0x50的设备,写操作时发送0xA0,读操作时发送0xA1。
10位地址帧格式:起始条件 + 11110xx + 10位地址的高2位 + 读写位 + ACK + 10位地址的低8位 + ACK。
10位地址兼容7位地址,因为11110xx是保留地址段,不会和7位地址冲突。但10位地址的通信流程更复杂,一般项目用不上。
5.2 ACK和NACK的时机与含义
ACK/NACK是I2C通信的确认机制。每传输8位数据后,接收方需要发送1位ACK(拉低SDA)或NACK(释放SDA)。
ACK/NACK出现的时机:
- 主机发送地址后,从机如果地址匹配,发送ACK。
- 主机发送数据后,从机发送ACK。
- 从机发送数据后,主机发送ACK(继续读)或NACK(停止读)。
NACK的几种含义:
- 主机读操作最后一个字节后发NACK,告诉从机停止发送。
- 从机地址不匹配,发NACK,主机收到后发送STOP或重新START。
- 从机忙,无法响应,发NACK。
很多通信失败就是ACK没收到。用逻辑分析仪看,如果地址后是NACK,说明从机没响应,检查地址、电源、上拉电阻。
5.3 重复起始条件的使用场景
重复起始条件(Repeated START)是在不发送STOP的情况下,重新发送START。用于以下场景:
- 读操作:先写寄存器地址,然后重复START,再读数据。
- 多主机环境下,不想释放总线。
以读EEPROM为例:
START + 写地址(0xA0) + ACK + 寄存器地址 + ACK + Repeated START + 读地址(0xA1) + ACK + 数据 + NACK + STOP注意最后是NACK,因为主机只读一个字节,读完发NACK告诉从机停止。
5.4 数据帧格式的常见误解
很多人以为I2C的数据帧是固定长度的,其实不是。I2C只定义了字节传输的规则,具体的数据格式由设备决定。比如:
- EEPROM:地址 + 数据,地址自动递增。
- 传感器:寄存器地址 + 数据。
- OLED:命令 + 数据,用控制字节区分。
所以调一个新器件,第一件事是看它的数据手册,搞清楚它的数据帧格式,而不是套用其他器件的代码。
6. 逻辑分析仪实战:抓一帧I2C看看到底发生了什么
理论讲再多,不如抓一帧波形看。这部分用一个实际案例,演示怎么用逻辑分析仪分析I2C通信,定位问题。
6.1 抓取前的准备工作
抓I2C波形需要:
- 逻辑分析仪,至少2通道,采样率至少4倍于SCL频率(400kHz SCL需要至少1.6MHz采样率,建议10MHz以上)。
- 探头接SCL和SDA,地线接GND。
- 协议解码器,Saleae Logic自带I2C解码,开源软件PulseView也有。
设置触发条件:一般用SCL下降沿触发,或者SDA下降沿触发(起始条件)。如果想抓特定地址,可以设置地址匹配触发。
6.2 一帧正常通信的波形解读
假设读一个AT24C02的EEPROM,地址0xA0,读寄存器0x00:
波形上会看到:
- START:SCL高时SDA下降。
- 地址0xA0:8位数据,最后一位0(写)。
- ACK:从机拉低SDA。
- 寄存器地址0x00:8位数据。
- ACK:从机拉低SDA。
- Repeated START:SCL高时SDA下降。
- 地址0xA1:8位数据,最后一位1(读)。
- ACK:从机拉低SDA。
- 数据:8位数据。
- NACK:主机释放SDA。
- STOP:SCL高时SDA上升。
逻辑分析仪的协议解码器会直接把这些解析成可读的地址、数据、ACK/NACK,非常直观。
6.3 常见异常波形分析
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 地址后NACK | 从机没响应 | 检查地址、电源、上拉 |
| SCL一直低 | 时钟拉伸或短路 | 检查从机、断开总线分段排查 |
| SDA一直低 | 从机等待或短路 | 执行总线恢复、检查器件 |
| 起始条件后无数据 | 主机问题 | 检查主机I2C配置、GPIO模式 |
| 数据位错误 | 时序问题 | 检查上拉、总线电容、速率 |
| 上升沿过缓 | 上拉过大或电容过大 | 减小上拉、缩短走线 |
6.4 用逻辑分析仪定位GT911触摸屏通信失败
GT911是一款常见的电容触摸屏控制器,I2C接口。我遇到过GT911通信失败的情况,用逻辑分析仪抓波形发现:
- 地址0x5D发送后,GT911没有ACK。
- 检查电源,发现GT911的复位时序不对,复位引脚释放太早,芯片还没准备好。
- 调整复位时序,延时100ms后再通信,问题解决。
这个案例说明,I2C通信失败不一定是I2C本身的问题,可能是从机的初始化时序不对。逻辑分析仪能帮你排除I2C层面的问题,把注意力引到正确的方向。
7. 软件模拟I2C vs 硬件I2C:怎么选,怎么用
实际项目中,I2C的实现方式有两种:用MCU自带的硬件I2C外设,或者用GPIO软件模拟。两者各有优劣,选错了会带来很多麻烦。
7.1 硬件I2C的优势与坑
硬件I2C的优势:
- 不占用CPU,通信过程由硬件完成。
- 时序精确,速率高。
- 支持中断和DMA。
硬件I2C的坑:
- 不同MCU的硬件I2C行为不一致,移植麻烦。
- 某些硬件I2C对时钟拉伸、仲裁失败处理不完善。
- 调试困难,出问题不好定位。
- 引脚固定,布线不灵活。
我用过STM32、ESP32、NXP的硬件I2C,各有各的脾气。STM32的硬件I2C在早期型号上有著名的"死锁"问题,后来用软件模拟才稳定。ESP32的硬件I2C相对好用,但时钟拉伸支持有限。
7.2 软件模拟I2C的适用场景
软件模拟I2C的优势:
- 引脚任意,布线灵活。
- 时序完全可控,方便调试。
- 移植性好,换MCU基本不用改代码。
- 可以处理硬件I2C处理不了的异常情况。
软件模拟I2C的劣势:
- 占用CPU,高速通信时CPU负载高。
- 速率受限,一般100kHz比较稳,400kHz勉强。
- 中断打断可能导致时序错误。
我的经验是:低速、少量数据、引脚受限的场景用软件模拟;高速、大量数据、CPU负载敏感的场景用硬件I2C。如果硬件I2C有问题,先尝试软件模拟,往往能快速绕过问题。
7.3 软件模拟I2C的时序控制要点
软件模拟I2C的核心是精确控制SCL和SDA的翻转时序。关键点:
- 延时函数要准确,用示波器或逻辑分析仪校准。
- 关中断或者用高优先级任务保证时序不被打断。
- SCL和SDA的GPIO模式要正确:输出时开漏,输入时浮空或上拉。
- 起始、停止、数据位的时序要符合I2C标准。
一个典型的软件I2C延时:
#define I2C_DELAY() do { \ for (volatile int i = 0; i < 10; i++); \ } while(0) void i2c_start(void) { SDA_HIGH(); SCL_HIGH(); I2C_DELAY(); SDA_LOW(); I2C_DELAY(); SCL_LOW(); I2C_DELAY(); }延时长度需要根据MCU主频调整,用逻辑分析仪确认SCL频率在目标范围内。
7.4 混合方案:硬件I2C加软件恢复
实际项目中,我常用一种混合方案:正常通信用硬件I2C,检测到超时或异常后,切换到软件模拟执行总线恢复,然后切回硬件I2C。这样兼顾了硬件I2C的效率和软件模拟的灵活性。
实现要点:
- 硬件I2C和软件模拟共用同一组GPIO。
- 切换时重新配置GPIO模式。
- 恢复流程:发送9个时钟脉冲 + STOP条件 + 重新初始化硬件I2C。
8. 那些年我踩过的I2C坑
最后这部分,分享一些具体的踩坑经历,都是实际项目中遇到的,希望能帮你少走弯路。
8.1 上拉电阻漏焊导致通信不稳定
有一次画板子,I2C上拉电阻忘了焊,调试的时候通信时好时坏。用逻辑分析仪看,上升沿非常缓,因为MCU内部有弱上拉,勉强能拉高,但速率一快就不行。焊上4.7kΩ上拉后,立刻稳定。
教训:I2C上拉电阻是必须的,不能依赖MCU内部上拉。内部上拉通常几十kΩ,只适合极低速场景。
8.2 地址冲突导致通信失败
一个项目里挂了两个同型号的传感器,地址都是0x68,结果通信混乱。后来发现其中一个传感器的地址引脚可以配置,改成0x69后解决。
教训:挂多个同型号器件时,一定要确认地址是否可配置,避免地址冲突。
8.3 电源域不同导致电平不匹配
一个系统里,主机是3.3V,从机是5V,I2C直接连,结果从机的5V电平灌到主机引脚,长期运行后主机引脚损坏。后来加了电平转换芯片才解决。
教训:不同电源域的I2C设备之间必须加电平转换,不能直接连。
8.4 长走线导致信号完整性差
一个项目里,I2C走线长达30cm,400kHz通信失败。用示波器看,上升沿有振铃,下降沿有毛刺。后来降低到100kHz,并加了串联电阻,才稳定。
教训:I2C走线尽量短,长走线要加缓冲器或降低速率。
8.5 中断打断导致时序错误
软件模拟I2C时,没关中断,结果一个高优先级中断打断了SCL高电平期间,导致从机采样错误。后来在I2C操作期间关中断,问题解决。
教训:软件模拟I2C的时序敏感操作要关中断,或者用硬件I2C。
8.6 从机时钟拉伸导致主机超时
一个传感器需要时钟拉伸,但主机的硬件I2C不支持,通信总是超时。后来改用软件模拟I2C,手动处理时钟拉伸,问题解决。
教训:选型时要确认主机的I2C控制器是否支持从机的所有特性,特别是时钟拉伸。
8.7 EEPROM写周期导致的通信失败
AT24C02写一个字节后需要5ms内部写周期,期间不响应任何通信。我一开始没加延时,连续写导致后面的写操作全部失败。后来在每次写后加5ms延时,或者用ACK轮询,问题解决。
教训:EEPROM写操作后必须等待写周期完成,可以用固定延时或ACK轮询。
8.8 逻辑分析仪采样率不足导致误判
用低采样率的逻辑分析仪抓400kHz I2C,波形失真,误判为时序问题。后来换高采样率分析仪,发现波形正常,问题在别处。
教训:逻辑分析仪的采样率至少是SCL频率的4倍,建议10倍以上。
9. 把I2C彻底讲透之后的一些体会
I2C这两根线,表面上简单,实际上从物理层到协议层,每一层都有讲究。开漏输出决定了多设备共享的可行性,上拉电阻决定了波形的质量,仲裁机制决定了多主总线的无冲突,时钟拉伸给了从机喘息的空间,ACK/NACK保证了数据的可靠传输。每一个设计都有其存在的理由,理解了这些理由,遇到问题才能快速定位。
我现在的习惯是,拿到一个新的I2C器件,先看数据手册的时序图,确认地址、速率、时钟拉伸支持情况,然后用逻辑分析仪抓一帧实际波形,确认通信正常,再开始写驱动。这个流程看起来麻烦,但能避免很多后期调试的痛苦。
最后分享一个小技巧:如果你怀疑I2C通信有问题,但不确定是硬件还是软件,先用逻辑分析仪抓波形。波形不会骗人,SCL和SDA的状态一目了然。如果波形正常但通信失败,问题在软件;如果波形异常,问题在硬件或时序。这个判断方法帮我节省了大量排查时间。