做嵌入式这几年,我见过不少人把 I2C 当成“接两根线就能通”的傻瓜总线。传感器读不到数据,第一反应是换地址、换速率、换上拉电阻,试一圈不行又去怀疑芯片坏了。其实 I2C 并不难,难的是很多人没有从物理层到协议层建立起完整的概念:为什么必须是开漏,为什么上拉电阻不能随便选,为什么多主不会把总线烧掉,为什么总线上一出现 NACK 就要按协议退避。这些知识散落在芯片手册和数据手册里,读起来枯燥,踩起坑来却很要命。这篇就按我自己的学习路径,把 I2C 的物理层、时序、多主仲裁、调试工具和工程化经验从头到尾拆一遍,争取让你再用 I2C 时不再靠猜。
1. 从开漏物理层说起:为什么 I2C 只有两根线还能双向通信
1.1 开漏不是省元件,而是 I2C 能双向“说话”的根基
I2C 最容易被忽略的设计就是开漏输出。很多人看电路图,看到 SDA 和 SCL 上都挂着上拉电阻,以为只是常规的“弱上拉”,并没意识到这个结构决定了 I2C 的整个通信方式。
普通 GPIO 往往是推挽输出,内部有上下两个晶体管:输出 1 时上管导通拉到高电平,输出 0 时下管导通拉到低电平,驱动能力强。但两个推挽输出直接并联在同一根线上,一个写 1 一个写 0,就等于电源对地短路,轻则逻辑混乱,重则烧毁引脚。I2C 的引脚不一样,它内部只保留一个下拉晶体管,想发送 0 时把引脚拉低,想发送 1 时直接释放引脚,靠外部上拉电阻把电平拉回高。这就是开漏,或者英文手册里写的 open-drain。
开漏带来的第一个好处是双向。同一根 SDA 线既可以被主设备驱动,也可以被从设备驱动,不需要像普通 IO 方向切换那样频繁改变引脚模式。第二个好处是“线与”逻辑:总线上只要有一个设备输出 0,整条线就是 0;所有设备都释放,线才会被上拉电阻拉成高。这个特性是后面多主仲裁和时钟同步的地基,没有它,两根线上的设备一多,早就互相打架了。用共享走廊的灯来类比很贴切:每个房间都只有一个“按灭”按钮,想开灯就松开手,让弹簧把开关拉回。任何一个人按下去,灯就灭;所有人都松手,灯才亮。I2C 里的开灯动作,就是上拉电阻拉高总线。
1.2 上拉电阻不是随便选的,算给你看
既然是开漏输出,上拉电阻就是 I2C 硬件上最关键的选型。电阻选大了,上升沿变慢,高电平可能爬不到位,从设备就会误判成低;选小了,灌电流太大,功耗升高,有些芯片的引脚承受不了。规范的依据其实很清楚,下限由引脚允许的最大灌电流 IOL 决定,上限由允许的最大上升时间 tr 和总线电容 Cb 决定。
下限公式:R_p(min) = (VDD - VOL) / IOL。例如 VDD=3.3V,VOL=0.4V,IOL=3mA,算出来约 967Ω,实际工程上最少也取 1kΩ。
上限公式:R_p(max) = tr / (0.8473 × Cb)。标准模式 100kHz 下 tr=1000ns,快速模式 400kHz 下 tr=300ns。假设总线电容 Cb=200pF,100kHz 时上限约 5.9kΩ,400kHz 时只有约 1.77kΩ。这就是为什么速率越高,上拉电阻必须越小。
实测下来,3.3V 系统常用 2.2kΩ 到 4.7kΩ,5V 系统常用 4.7kΩ 到 10kΩ。板内短距离、100kHz,10kΩ 也能跑通,但抗干扰差;如果拉线到外设超过 20 厘米,建议直接上 2.2kΩ,把边沿做陡。注意 MCU 内部的上拉一般在 30kΩ 到 50kΩ,太弱,只适合临时测试,不能替代外部电阻。每次有人问“我这板上没加上拉怎么也能通”,我都要提醒一句:那多半是运气好,碰到某个设备内部已经做了弱上拉,随时可能因为外部噪声或总线电容变化而不稳定。
1.3 多电压、长总线这些物理层坑
I2C 电平不统一是另一个高频问题。3.3V 主控接 5V 外设,不能简单用两个电阻分压,因为 I2C 是双向的,分压只能搞定单向方向。推荐的办法是专用的双向电平转换芯片,比如 TXS0102、TCA9406,或者经典的 MOSFET 方案:两个 NMOS 管交叉连接,配上两个上拉电阻,方向自动跟随,电平自动转换。用这个方案时要注意 NMOS 的栅极电容和导通阈值,速率高时边沿会被拉慢,还是用专用芯片更省心。
长总线同样容易踩坑。I2C 本身为板内通信设计,规范对总线电容有上限,标准模式一般控制在 400pF 内,快速模式则建议更低。线缆一长、设备一多,电容叠加会让上升沿钝化,严重的直接采不到高电平。解决思路无非降速、缩短走线、用缓冲器或分段引入 P82B96、PCA9615 这类器件。还要注意挂载设备的数量:我之前在一块板上挂了 6 个 I2C 从设备,又接了一段 20cm 排线,400kHz 下波形根本不能看,降到 100kHz 才稳定。后来改用 TCA9548A 分总线,彻底不用为电容发愁。
2. 把时序图读成说话节奏:START、STOP、ACK 和数据帧
2.1 START 与 STOP:SCL 高电平期间的 SDA 跳变就是暗号
I2C 空闲时,SDA 和 SCL 都是高电平。通信不是从“发送地址”开始的,而是从一个特殊跳变——起始条件开始。起始条件是 SCL 保持高、SDA 从高跳到低;停止条件是 SCL 保持高、SDA 从低跳到高。
为什么用跳变来区分?因为数据传输规则规定,SCL 高电平时 SDA 必须保持稳定,SDA 只能在 SCL 低电平时改变。所以“SCL 高的时候 SDA 居然跳了”就是一件特殊的事,所有从设备一旦看到这个跳变,就知道主设备要开始或结束一次通信。这也是逻辑分析仪抓 I2C 时第一眼要找的东西。
还有一个容易忽略的“重复起始条件”(repeated START,缩写 Sr):在一次通信还没结束、也就是还没发 STOP 之前,主设备再次拉出一个起始条件。这个动作不会释放总线,常用于“先写寄存器地址再转读取数据”的场景。如果中间用 STOP 再 START,总线所有权会被释放,多主系统里可能出现其他主设备趁机插入,破坏一次完整事务。
2.2 字节、ACK 与 NACK 如何决定一次通信的成败
I2C 数据以字节为单位,每字节 8 位,高字节优先。发完 8 位后,第 9 个时钟周期是应答位。应答规则很简单:响应方把 SDA 拉低就是 ACK,保持高就是 NACK。
ACK 表示“我收到了”,NACK 在不同场景含义完全不同。从设备地址不存在、电源没起来、复位没完成,从设备不回 ACK;此时主设备应该发 STOP 退出,而不是继续发寄存器地址。读数据时,主设备在最后一个字节主动发 NACK,是告诉从设备“我不要了,你停吧”,从设备看到 NACK 后停止输出。多主仲裁里,主设备发送后如果发现 SDA 电平和自己预期的不同,也认为是仲裁失败而退出。很多初学者死磕 NACK 时会陷入“地址到底移没移”的困惑,其实回到波形上看一眼第 9 个脉冲,答案立刻浮现。
调试时最常见的失败现场就是从设备一直不回 ACK。这时候先别急着改代码,检查顺序应该是:供电是否正常、地址是否对应、复位引脚是否释放、上拉电阻是否接好、逻辑分析仪上有没有出现完整波形。有一次我被一个从设备 NACK 卡了大半天,最后发现是芯片的复位引脚被一个电容拉到低电平,根本没跑起来,和 I2C 时序毫无关系。
2.3 从机地址、寄存器地址与数据帧的标准格式
I2C 地址分 7 位和 10 位。7 位地址是最常见的,地址字节由 7 位地址加 1 位读写标志组成。比如一个温度传感器 I2C 地址是 0x48,写操作地址字节就是 0x90(0x48 左移一位再加 0),读操作地址字节是 0x91。很多人直接把 0x48 填进代码,通信当然失败,因为协议里不可能存在“0x48 裸地址”这种东西。10 位地址是为扩展地址空间设计的,头一个字节是 11110 加地址高位再加读写位,第二个字节放地址低位,工业 PMBus/SMBus 设备里偶尔能见到。
数据帧格式就是我们常说的“地址 + 寄存器地址 + 数据”。关键在于,I2C 协议本身只定义了地址和字节流,并没有强制规定哪个字节是寄存器地址,具体怎么组织完全由芯片厂商决定。多数传感器和 EEPROM 的做法是:主设备先发寄存器地址,再发或读数据;也有设备上电后就直接从固定寄存器开始流式输出。这也是为什么有人会困惑所谓的 I2C“自由数据模式”——本质上就是协议层没规定数据组织方式,你只能看数据手册,不能靠经验硬套。
2.4 EEPROM 读写实例:从时序到代码,一次走通
拿经典的 AT24C02 来说,I2C 从机地址通常是 0x50,换算成写地址字节是 0xA0,读地址字节是 0xA1。写单个字节的流程一目了然:发起 START,发送写地址字节 0xA0,等从设备 ACK,再发送内部寄存器地址,等 ACK,再发送数据字节,等 ACK,最后发 STOP。
随机读一个字节的流程稍微复杂:先发起 START,发送写地址字节 0xA0,ACK,再发送要读取的寄存器地址,ACK,随后发一个 REPEATED START,再发送读地址字节 0xA1,等 ACK。这时从设备会把数据放到 SDA 上,主设备读完 8 位后发 NACK 表示不再读,最后发 STOP。这个“先写地址再切换读”的套路,在绝大多数 I2C 存储芯片和传感器上都通用。
如果是在 Linux 环境调一块开发板,用 i2c-tools 就能快速验证硬件链路:
# 列出系统里所有I2C总线,确认总线编号 i2cdetect -l # 扫描总线上有哪些从设备地址 i2cdetect -y 1 # 往 0x50 的寄存器地址 0x00 写入 0xAB i2cset -y 1 0x50 0x00 0xAB # 读回 0x50 的寄存器地址 0x00 i2cget -y 1 0x50 0x00如果是在 FPGA 上用 Verilog 写控制器,核心就是一个状态机:把上面的流程拆成 IDLE、START、发送地址、等待 ACK、发送寄存器地址、发送数据、STOP,每个状态再按 SCL 的高低相位去切换 SDA 输出和采样应答。很多新手写 Verilog 时会用普通推挽输出模拟 SCL,这其实是错的,I2C 的 SCL 同样是开漏,从设备可能在通信过程中电平拉伸,你必须在每个字节发送前检查 SCL 是否真的被释放,不能把时钟当作永远可控的自家时钟。
3. 多主仲裁与时钟同步:两根线如何容纳多个大脑
3.1 多主仲裁的原理:开漏让竞争“零伤害”
多主是 I2C 比 SPI 有优势的地方。两根线上可以同时挂好几个“会主动发起通信”的主设备,而且不会烧总线。设计上很常见:CPU 和协处理器共享一条 I2C 总线,各自去访问同一片 EEPROM 或同一套传感器,没有仲裁机制早就互相覆盖了。
仲裁能成立,靠的正是开漏和线与逻辑。两个主设备同时发起 START,随后各自发送地址和数据。如果 A 想发送 1,B 想发送 0,B 把 SDA 拉低,那么实际线上的电平就是 0。A 在发送的同时回读 SDA,发现自己想发 1 但线上是 0,立即知道自己仲裁失败,马上停止发送。整个过程是一个逐位比较,赢得仲裁的设备发送内容不发错一个字节,输掉的设备只是安静退出,不会破坏总线,也不会把数据线烧掉。
用一个通信场景比喻:就像两个人同时拿起同一部共享对讲机说话,一个话音和另一个对不上,其中一个会自动闭嘴,另一个继续说完整段。这不靠随机退避,也不靠“抢锁”,而是硬件层面的自动协商。
3.2 仲裁发生在哪些阶段:地址、数据位都可能
仲裁从 START 之后的第一个地址位就开始,地址、数据、ACK 位都可能发生竞争。如果两个主设备发送的地址完全一样,它会继续在数据位仲裁;哪怕数据位也全部相同,最后还会在 ACK 位上分出胜负,因为某个从设备拉低的 ACK 和另一个主设备想发送的高电平冲突时,那个主设备也会被判定失败。所以仲裁的粒度比很多人想象的要细得多。
实际调试中想抓到仲裁现象并不容易,但确实能遇到。有一次我用逻辑分析仪抓两个主控同时读写同一个传感器,波形显示一串地址位之后,SDA 在某一位出现了一段非稳定的电平变化,随后只有一台主控继续拉出后面的字节,另一台停在原地。当时我的第一反应是数据被干扰了,后来对照电路图才反应过来,那两个主控确实在争总线。如果你只会盯着数据内容看,很容易把仲裁过程误判成毛刺。
3.3 时钟同步与时钟延展
SCL 同样是开漏,天然支持多条输出线的“线与”逻辑。多个主设备同时启动时,每个都会驱动 SCL,最早拉低的那个决定了低电平周期的起点;SCL 被释放后要等所有设备都释放才会真正变高。结果就是 SCL 的低电平周期取所有设备中最长的,高电平周期取最短的,最终合成一个大家都能跟上的综合时钟。这个现象叫时钟同步,它保证了多个主设备不会各走各的时钟。
时钟延展则是从设备主动发起的机制。慢速从设备收到数据后需要时间处理,可以在 ACK 阶段把 SCL 继续拉低,主设备看到 SCL 一直没变高,就知道对方还没准备好,必须等着。很多主控制器驱动默认不支持时钟延展,和某些传感器通信时就会出现偶发卡顿或超时错误。我自己写 I2C 主设备驱动时,习惯在每个字节的 9 个脉冲之后都做一次“等待 SCL 拉高”的超时检查,绝不假设时钟一定按自己的计数器走。
3.4 多主场景下的软件设计建议
多主功能虽然硬件支持,但工程上要收着用。仲裁的前提是每个主设备都能在发送的同时回读 SDA,片上硬件 I2C 外设没问题,软件模拟 I2C 就要在每次写 SDA 后立刻读回引脚,实时性要求更高,稍不留神就漏掉仲裁。
软件层面还要处理好重试策略。I2C 规范没有强制规定仲裁失败后多久重试,一般建议等总线空闲、也就是看到 STOP 条件出现后再发起新的 START。我会让失败设备统计退避次数,超过三次直接报错,而不是无限重试,避免两个主设备像互相顶牛一样僵持在总线上。工程上还应该为每个 I2C 事务加超时,尤其在多主系统里,一个从设备的时钟延展时间过长,会拖住整条总线上的所有主设备。
4. 工具与实战:逻辑分析仪是 I2C 调试的最强辅助
4.1 逻辑分析仪怎么接、怎么设置
抓 I2C 波形最省钱又最直观的工具就是逻辑分析仪。几十块钱的 USB 逻辑分析仪配上软件自带的 I2C 解码器,比示波器好用得多。示波器能看模拟电平,但解码不直观,时序分析也费劲;逻辑分析仪直接把 START、STOP、地址字节、ACK/NACK、数据字节标在波形上,一眼就能看懂。
接线很简单,逻辑分析仪的 GND 和板子共地,SCL 接一个通道,SDA 接另一个通道。前提是确认通道输入电压范围能承受板子电平,3.3V 和 5V 通常没问题,但高于 5V 的接口要先做处理。采样率建议不要低于 10MHz,抓 100kHz 的 I2C 时每个 SCL 周期能有 100 个采样点,足以看清上升沿;抓 400kHz 时建议 20MHz 以上。打开 I2C 解码器后,软件会自动标出起始停止位置和解码数据,这时候你的工作就是跟数据手册比对,而不是对着波形人工数数。
4.2 一份真实波形的读法:先找跳变,再对字节
拿到一段 I2C 波形后,我习惯先看 START:SCL 高电平期间,SDA 一定有一个高到低的跳变。然后数 SCL 脉冲,第一个字节是 9 个脉冲,前 8 个是地址或数据位,第 9 个是 ACK 位,看 ACK 位时 SDA 是被拉低还是保持高。
读字节内容时,在每个 SCL 高电平期间观察 SDA 是 1 还是 0,把 8 位拼出来就是地址或数据。比如地址字节 0xA0,拆成二进制最高位是 1,说明这是个 7 位地址 0x50 左移一位、最后一位是写标志的写地址。很多人困惑 i2cdetect 扫出来是 0x50,代码里下发却是 0xA0,其实就是差了一个移位。还有一种常见异常:一个字节之后从设备没有给 ACK,那么整个包就断在这里。这时要去查地址对不对、供电稳不稳、复位完成没有,不要继续往下发。
4.3 GT911 触摸屏 I2C 通信失败的排查实录
GT911 是我踩坑最多的一颗触摸屏控制器。很多人第一次接触它,照着网上的例程写 I2C,结果读不到坐标,网上搜索“GT911 I2C 通信失败”能搜出大量帖子,原因基本集中在几个点。
第一是 I2C 地址配置。GT911 的地址跟 RST 和 INT 引脚的连接方式有关,常见的是 0x5D(写地址 0xBA,读地址 0xBB)和 0x14(0x28,0x29)。如果板子在 RST 上加了电容,上电复位时序变了,地址就可能从 0x5D 变成 0x14,逻辑分析仪上明显能看到从设备不响应 0xBA。这时你要先看电路原理图,确认两颗引脚接的是高还是低,或者直接用逻辑分析仪抓设备上电后的自动地址广播。
第二是上电时序。GT911 要求先拉低 RST,再拉高,然后等待 INT 引脚出现电平变化,才算完成初始化。如果上电后立刻去读寄存器,设备还没醒。我一般会在驱动里加一步:复位后轮询某个状态寄存器,确认设备响应正常再往下走。
第三是寄存器地址宽度。GT911 的寄存器地址是 16 位,需要分高字节和低字节下发。如果驱动里按 8 位寄存器地址处理,坐标数据就会整体错位,读回来的内容天马行空。这种“I2C 通了但数据不对”的坑最隐蔽,排查时别死盯波形,先核对寄存器宽度和数据手册是否一致。
4.4 Linux PHY 不走 MDIO 改用 I2C 的配置思路
很多人问过我一个场景:以太网 PHY 的管理接口不走常见的 MDIO,而是挂在 I2C 上。不少芯片为了节省引脚或复用引脚,把管理寄存器放到了 I2C/SMBus 总线上,调试时就不能按惯性去找 mdio bus。
Linux 下的处理思路分两种情况。一种是把 PHY 挂到 I2C 总线上,通过 i2c-tools 直接读写它的寄存器,用于调试和验证。另一种是让内核通过 I2C 提供 MDIO 桥接,比如 mdio-i2c 这类机制,核心寄存器读写走 I2C,但对上层仍然表现为一个 MDIO 总线,这样标准 PHY 驱动可以继续用。Device Tree 里要把 I2C 控制器、PHY 的从机地址、页切换规则都对上,尤其当 PHY 寄存器有分页时,每次读写前要先写页选择寄存器,逻辑分析仪抓到的特征就是每次目标地址读取前都多出两个 I2C 写操作,而寄存器内容迟迟不变。
如果是在没有 OS 的裸机上做主控,做法更直接:把 PHY 当作普通 I2C 从设备,写一个封装函数读它的状态寄存器、控制寄存器,和 MDIO 方式对上层 API 保持一致即可。关键是不要把 PHY 的 I2C 地址跟它的 MDIO PHY 地址混在一起,这两个地址体系完全不同。
5. 工程化扩展:速率适配、总线扩展与系统级避坑
5.1 速率选型:标准 100kHz 与快速 400kHz 的时序约束
很多人一上来就把 I2C 配成 400kHz,觉得快就是好。但高速率对布线和上拉电阻要求更高,设备一老旧、走线一长,反而容易出问题。I2C 规范里几个关键时序参数值得背下来。
| 参数 | 标准模式 100kHz | 快速模式 400kHz |
|---|---|---|
| SCL 最大频率 | 100kHz | 400kHz |
| 最大上升时间 tr | 1000ns | 300ns |
| 数据建立时间 tSU.DAT | ≥250ns | ≥100ns |
| 数据保持时间 tHD.DAT | 0~3.45µs | 0~0.9µs |
| 最小低电平时间 tLOW | 4.7µs | 1.3µs |
| 最小高电平时间 tHIGH | 4.0µs | 0.6µs |
这些数字落到实际,就是 100kHz 时总线电容容忍度高、线长一点也能跑得动;400kHz 时上升时间只有 300ns,上拉电阻一大、总线电容一高,边沿立刻钝化。如果你不确定板子能不能跑 400kHz,抓一下 SCL 上升沿,小于 300ns 算及格。还有一些老式 EEPROM 只标称 400kHz,你在上面跑 1MHz,它会间歇性 NACK。速率不是越高越好,是够用就好。
5.2 总线扩展与多路复用:设备多于一条总线怎么办
I2C 7 位地址去掉保留地址后,能用的地址空间有限,设备一多,地址冲突迟早要面对。三个常用解法值得记一下。
最简单的做法是分总线:如果 MCU 有多个硬件 I2C 外设,把设备分散到不同控制器上,软件最直观,缺点是引脚占用多。第二个方案是 I2C 多路复用器,常见的有 TCA9548A,一颗芯片可以把一条 I2C 扩展成 8 条独立子总线,每条子总线都可以挂相同地址的设备。主设备先写选择字节切换通道,再访问目标设备。Linux 下用 i2c-mux 驱动时,你会发现 /dev/i2c-* 多出了几个节点,其实就是 mux 的虚拟通道。选 mux 时注意通道切换命令本身也是一个 I2C 写操作,逻辑分析仪上能看到切换字节,容易误判成“多了一个设备”。
第三个方向是总线缓冲器或延长器,适合解决信号衰减而不是地址冲突。P82B96、PCA9615 这类芯片把总线分成两段,每段电容独立计算,避免整条总线电容叠加。之前我在一块板上挂 6 个从设备,总线电容严重超标,后来换 PCA9615 分两段,400kHz 稳稳地跑起来了。
5.3 从机主动上报、PMBus 与 HID over I2C 的几个关键区别
有个需求经常被提起:从设备想主动告诉主设备“数据变了”,但 I2C 从设备没有总线控制权,根本不能主动发起通信。常用手段有三种:主设备轮询,简单但费电;从设备通过 GPIO 中断通知主设备,主设备收到中断再发 I2C 读操作,这是最推荐的方案;还有一种是走 SMBus 的警报机制,从设备通过 ALERT 线报告,主设备再发 ARA 地址来读取报警源。工程上“中断 + I2C 读取”的组合最高效,响应快又省功耗。
PMBus 是在 I2C 之上发展出来的电源管理协议,它在数据帧之上定义了标准寄存器、命令格式、分组协议和 PEC 校验。平时调电源芯片时,看到 read/write word、block read/write,很多就是 PMBus 语义。和裸 I2C 最大的区别是,PMBus 对字节序、循环冗余校验、命令超时都有明确约定,不能用没校验的裸 I2C 去猜。
HID over I2C 则是另一个方向,主要用于触摸屏、触摸板。它的物理层仍是 I2C,但传输内容遵循 HID 协议,由主机侧获取 HID 描述符。很多人遇到“i2c hid该设备找不到足够资源可以使用。(代码12)”这个 Windows 报错,多半卡在 IRQ 中断冲突、驱动加载时序或设备的唤醒中断引脚连接。鸿蒙开发板上如果集成触摸屏走 HID over I2C,调试思路也一样:先抓 I2C 波形确认描述符有没有读回来,再考虑 HID 层的解析,不要一上来就怀疑系统组件。
5.4 UART、SPI、I2C 之间怎么选
简单归纳一下选型。UART 是点对点、全双工,没有时钟线,适合两个设备之间的低速通信,调试打印最常用。SPI 是高速全双工四线制,有独立片选,适合大批量数据场景,比如 Flash、LCD 驱动。I2C 两根线支持多设备多点,适合挂低速传感器、EEPROM、电源管理芯片、触摸屏。如果引脚紧张、设备多、速率要求不高,优先 I2C;追求高吞吐和实时性,上 SPI;只是两板之间互传调试数据,UART 最方便。
还有几个特殊接口,I2S 是音频串行总线,专门传音频数据流;SDIO 常用于 SD 卡和无线模块;CAN 则面向工业现场的长距离可靠通信。不少人把这些时序图放在一起背,其实接口之间差异巨大,选型看的是带宽、距离、引脚数量和协议复杂度。举例来说,编码器这种设备,传统增量式走脉冲串,但磁编码器、角度传感器里用 I2C 输出的也不少,地址少、数据简单、抗干扰强,比脉冲计数起来更方便。选择的关键还是看系统主控外设是否匹配、驱动是否成熟。
最后再补一个异常速查表,都是实际排查中出现频率很高的现象,可以直接照着对。
| 现场现象 | 排查方向 |
|---|---|
| 所有从设备都不 ACK | 供电、地线、复位、地址是否位移、总线电容是否过大 |
| 读回全是 0xFF 或 0x00 | 上拉未接、芯片还在复位、地址错位 |
| 偶发超时或总线忙 | 有其他主设备占用、从设备时钟延展未处理 |
| 波形高电平期间 SDA 变化 | 时序违例、毛刺干扰、速率和上拉不匹配 |
| 数据读回来了但和手册对不上 | 寄存器宽度、字节序、设备配置页、驱动缓存 |
我自己坚持了很久的一个习惯是:任何 I2C 项目上板前,先画一张设备地址表和引脚连接图,标注每个从设备的 7 位地址、写读字节地址、供电电压、复位引脚和中断引脚。调试时对照这张表看逻辑分析仪,往往能省下大半天。把两根线背后的物理层、时序和多主仲裁逻辑真正吃透之后,I2C 就不再是玄学,而是一个“讲规矩就能好好说话”的老实协议。