1. 先从“两线制”说起I2C物理层到底在干什么做嵌入式这几年I2C 大概是除了 UART 之外我打交道最多的通信协议了。小到一块 EEPROM、一颗温度传感器大到触摸屏控制器、电源管理芯片几乎都能看到它的身影。很多朋友刚开始学 I2C 的时候最先接触的往往是一堆时序图、起始条件、停止条件、ACK 应答这些东西背得滚瓜烂熟可真到了板子调不通、波形拉不起来的时候才发现自己对 I2C 的底层逻辑其实根本没吃透。尤其是一个经典问题为什么 I2C 必须是开漏输出面试官爱问实际调试中也绕不开。你要是答一句“因为协议规定”那基本等于没答。真正搞懂这个问题得从 I2C 的物理层设计说起而物理层的核心就是标题里这五个字——两线制。所谓两线制指的是 I2C 总线只靠两根线完成所有通信一根串行数据线 SDA一根串行时钟线 SCL。对比一下 SPI 至少四根线MOSI、MISO、SCLK、CS、UART 至少两根线TX、RX但只能点对点I2C 用两根线就能挂一堆设备靠的就是物理层一套非常巧妙的设计。这套设计的灵魂正是“开漏 上拉电阻”。这篇文章我想从最底层的电气结构讲起把“两线制”和“开漏”这两件事彻底掰开揉碎。不扯虚的全部是实际工程里能直接用到的理解方式和调试经验。不管你是刚入门的学生还是被 I2C 时序折磨过的工程师这篇内容应该都能帮你把这块短板补上。1.1 SDA 和 SCL各管各的活I2C 的两根线分工非常明确。SCL 提供时钟由主设备掌控用来同步总线上所有设备的节奏SDA 负责传数据数据位在 SCL 高电平期间必须保持稳定在 SCL 低电平期间才允许切换。这个“SCL 高电平采样、低电平变化”的规矩是所有 I2C 通信的基础。但这里有个容易被忽略的细节I2C 是半双工通信SDA 这一根线既要承担主机发数据给从机的任务也要承担从机回复数据、返回 ACK 应答的任务。也就是说一根线在同一个通信过程中需要来回切换方向。这就带来一个问题——如果 SDA 这根线上同时有两个设备想输出不同的电平会发生什么在回答这个之前得先理解为什么 I2C 不像 UART 那样用两根独立的收发线。原因很简单I2C 诞生之初的目标就是省引脚、省布线用最少的线连接最多的设备。两根线、多个设备共享这种拓扑结构天然要求总线具备“被多个设备共享且不能互相打架”的能力。而实现这个能力的关键就在于每个设备的 SDA/SCL 引脚都不能是普通的推挽输出必须是开漏。1.2 只有两根线怎么完成“一问一答”咱们拿一次最简单的读操作来感受一下两线制的工作过程。主机先发起始条件SCL 高电平期间 SDA 拉低然后发送 7 位从机地址加一位读写标志接着等从机拉低 SDA 回一个 ACK。地址对了之后主机继续发寄存器地址从机再回 ACK。然后主机发重复起始条件重新发地址这次读写位是“读”从机开始把数据一位一位放到 SDA 上主机每收一个字节回一个 ACK直到最后主机回一个 NACK 再发停止条件。这一整套流程里SDA 线在主机和从机之间来回切换所有权。主机发地址的时候SDA 是主机控制的从机回 ACK 的时候SDA 是从机控制的读数据的时候SDA 又是从机控制的。一根线多个控制者分时复用。如果这跟线被某一个设备强硬地“推”到高电平或者低电平其他设备根本没有机会在这根线上表达自己的意愿。所以物理层必须设计成任何设备都只能把总线拉低而不能主动把总线推高。总线的高电平由外部的上拉电阻“兜底”提供。谁想发数据就控制自己的开漏管拉低总线谁不发数据就把自己的管脚放开让上拉电阻把总线恢复成高电平。这就是“线与”的雏形也是 I2C 物理层最核心的思想。1.3 物理层设计精髓把复杂留给协议理解 I2C 物理层有一条主线值得记在心里物理层负责提供“多设备安全共享一根线”的基础复杂的仲裁、时序、寻址这些事全部交给协议层去处理。物理层只保证一个最基本的底线——多个设备同时操作总线时不会因为电平冲突而损坏硬件而且总线状态仍然可以被所有设备一致地解读。这个设计哲学非常务实。如果物理层用的是推挽输出那么两个设备同时往总线上输出相反电平就是电源对地短路轻则通信异常重则烧毁芯片引脚。开漏输出从硬件结构上就杜绝了这种情况因为没有任何设备能把总线主动推高“打架”最多表现为多个设备同时拉低总线而总线电平依然是低不会损坏任何东西。从这个角度再看“为什么 I2C 必须开漏”答案就清晰了不是协议闲着没事规定了一个奇怪的要求而是两线制、多设备共享、双向半双工通信这几个需求叠加在一起开漏是唯一能在成本、可靠性、简单性之间取得平衡的方案。2. 推挽与开漏同样是输出电气结构完全不同要彻底理解开漏最好的办法是拿它和常见的推挽输出做对比。很多朋友在单片机上配置 GPIO 的时候见过“推挽输出”和“开漏输出”这两个选项但搞不清区别或者只知道“I2C 要用开漏”却不知道背后原因。这一节咱们就从 MOS 管级别看看这两种输出结构到底差在哪。2.1 推挽输出一个推一个拉速度快但不适合共享推挽输出Push-Pull的结构可以简单理解为一个 P 沟道 MOS 管和一个 N 沟道 MOS 管上下串联中间引出输出端。输出高电平时上方 P 管导通输出端被直接接到 VCC输出低电平时下方 N 管导通输出端被直接接到 GND。两个管子轮流工作“推”和“拉”都能主动出力所以叫推挽。这个结构最大的优点是驱动能力强、电平转换速度快。输出高电平的时候引脚电压被死死钉在 VCC输出低电平的时候引脚电压被死死钉在 GND。不管是驱动 LED、控制继电器还是跑 SPI推挽输出都是很好的选择。但推挽输出有一个致命问题不允许两个推挽输出直接接在同一根线上。想象一下如果设备 A 输出高电平、设备 B 输出低电平把两个引脚接在同一根线上那这根线就相当于同时接到了 VCC 和 GND。结果就是两个设备之间形成一条低阻抗短路路径电流飙升轻则电平混乱重则烧毁引脚甚至整颗芯片。这跟短路没什么区别。2.2 开漏输出只负责拉低高电平全靠“外包”开漏输出Open-Drain的结构比推挽简单得多。它只有一个 N 沟道 MOS 管漏极直接引到引脚上没有上方的 P 管。所以这个引脚本身不能主动输出高电平只有两种状态内部 MOS 管导通引脚被拉低到 GND或者内部 MOS 管关断引脚对外呈现高阻态Hi-Z。注意这个“高阻态”很关键。引脚处于高阻态时它既不输出高电平也不输出低电平对外相当于断开了。这时候如果外部电路不接任何东西引脚上的电压就是浮空的不确定。所以在开漏输出的典型应用中外部必须接一个上拉电阻到电源 VCC。高阻态时上拉电阻把引脚电压拉高到接近 VCCMOS 管导通时电流从上拉电阻流经 MOS 管到 GND引脚被拉低。所以开漏输出的本质可以用一句话概括管脚只会拉低不会推高高电平是“蹭”外部上拉电阻的。这看起来像是阉割了驱动能力但恰恰是这种“只拉低”的特性让它成了共享总线的理想选择。2.3 用生活类比记住开漏的本质如果说推挽输出是“两个人抢一根绳子一个往左拉一个往右拉各不相让”那开漏输出就是“一根绳子挂了一排钩子谁想把绳子拉下来谁就拉松手之后绳子靠自身重力回到原位”。钩子只能往下拉不能往上推重力就是那个“上拉电阻”负责把绳子恢复到高位。这样一来不管有多少钩子同时往下拉绳子最多也就是被拉到最低点没有任何冲突和损坏。谁需要表达“低电平”谁就拉一下绳子谁不说话谁就松手旁观。这个类比虽然简单但真的能帮人记住开漏的精髓。特性推挽输出开漏输出主动输出高电平可以直接接 VCC不可以只能靠外部上拉主动输出低电平可以直接接 GND可以MOS 管导通拉低高阻态无有MOS 管关断时多设备共享一根线危险可能短路安全天然支持线与典型应用GPIO 驱动 LED、SPI、PWMI2C、中断线、电平转换3. 为什么是开漏从总线需求反推出来的必然答案前面把物理结构和基础概念铺完了现在回到核心问题I2C 为什么必须开漏这一节我会从五个角度来拆每一个角度都是一道独立的工程难题而开漏是同时解决所有难题的最优解。3.1 多设备共享推挽一旦同时输出就是短路事故I2C 总线上可以挂很多设备这是它的重要特性。理论上一个 I2C 总线上可以挂 128 个设备7 位地址实际受总线电容和地址数量限制但挂十几个是常有的事。所有设备的 SDA 和 SCL 都并联在同一对线上它们分时共享这两根线。这种并联结构如果用推挽输出会非常危险。假设设备 1 想发一个“0”把 SDA 拉低设备 2 这时候也在发数据想发一个“1”把 SDA 拉高。两个设备同时在驱动这根线结果就是上面说的短路路径。就算通信时序上能保证“同一时刻只有一个设备在发数据”但实际上总线释放、切换主从、设备上电时序异常等情况下多设备同时驱动的窗口是不可避免的。开漏从物理结构上消灭了这个风险因为你根本做不到“推高”最多就是大家都拉低总线照样是低电平谁也不会受伤。3.2 “线与”逻辑硬件层面自动完成的仲裁开漏输出带来的附加好处是“线与”Wired-AND逻辑。你把所有设备的输出通过开漏接在一起再通过上拉电阻接电源这条线的电平就是所有设备输出电平的“与”结果。任何一个设备输出低电平总线就是低电平只有所有设备都释放总线高阻总线才是高电平。这个特性在 I2C 的仲裁机制里是核心中的核心。I2C 支持多主机模式两个主机可能同时往总线上发数据。如果没有仲裁机制两个主机各自发各自的总线就乱了。有了线与逻辑仲裁变得极其优雅两个主机同时在 SCL 上产生时钟同时在 SDA 上发数据位。如果某个主机发送了高电平但采样到总线是低电平因为另一个主机正在拉低它就意识到自己“输了”立即停止发送。整个过程在硬件层面自动完成不需要额外的仲裁线不需要复杂的握手协议一根线与逻辑就搞定了。这个设计精妙在哪精妙在“让位”是零成本的。谁先拉低谁就占住总线后拉低的人发现总线已经被拉低了自己主动退出。这就像几个人争一个发言权谁先张嘴谁说话其他人听到有人说话了就自动闭嘴完全不需要主持人协调。3.3 一根线双向复用开漏天然解决收发冲突I2C 只有一根 SDA 线既要发又要收。如果是推挽输出数据方向切换的时候必须非常小心主机从输出模式切到输入模式如果不经过高阻态直接切换可能会出现瞬间的驱动冲突。而开漏输出天然自带高阻态你要么拉低输出 0要么释放相当于输入不存在“主动输出 1”的状态。这大大简化了双向通信的实现。发送数据的时候要发 0 就拉低要发 1 就释放接收数据的时候直接把整个引脚设为输入模式读取引脚电平即可。甚至有些单片机在开漏模式下不需要切换方向寄存器因为“释放外部上拉拉高”和“输入模式”在电气上是等价的。引脚读取到的电平就是总线的实际电平不需要额外的方向切换逻辑这在底层实现上非常友好。实际写单片机驱动代码的时候你会发现 I2C 的 GPIO 模拟实现基本都是这个套路// I2C GPIO模拟发送一个bit void i2c_sda_put(uint8_t bit) { if (bit) { SDA_PORT-ODR | SDA_PIN; // 释放靠上拉拉高 } else { SDA_PORT-ODR ~SDA_PIN; // 拉低 } } // 读取一个bit直接读IDR uint8_t i2c_sda_get(void) { return (SDA_PORT-IDR SDA_PIN) ? 1 : 0; }SDA 引脚配置成开漏输出模式之后发送和接收根本不需要切换方向靠 ODR 控制拉低还是释放需要读的时候直接读 IDR。这个“懒人操作法”之所以成立完全是因为开漏结构天然提供了双向复用能力。3.4 时钟同步与多主机开漏让“让步”成为可能SCL 这根时钟线为什么也要开漏很多初学者不理解数据线 SDA 开漏我能理解时钟线 SCL 不是主机独占的吗为什么也要开漏答案是SCL 也必须支持“时钟同步”和“时钟拉伸”Clock Stretching。先看时钟同步。多主机模式下多个主机同时产生时钟它们的时钟频率、相位不可能完全一致。如果 SCL 是推挽的两个主机的时钟信号会在线上打架。开漏模式下SCL 上的电平也是线与关系只要有一个主机拉低 SCL整条总线就是低电平。这样一来SCL 的低电平时间是所有主机低电平时间的“并集”高电平时间是所有主机高电平时间的“交集”。所有主机都会自动被“拉”到同一个节奏上最快的那个也不得不等待最慢的那个这就是时钟同步。再看时钟拉伸这个在读取低速设备时特别重要。部分从机比如一些老式的 EEPROM、传感器处理数据需要时间它们会在主机要求数据的时候把 SCL 拉低不放表示“我还没准备好你等等”。主机检测到 SCL 一直为低就知道从机在拉伸时钟于是等待 SCL 被释放后再继续。如果 SCL 不是开漏从机根本没有能力“按住”时钟线这个机制就完全无法实现。时钟拉伸是 I2C 协议里一个非常实用的功能也是很多工程师调试时遇到的隐藏坑如果你用逻辑分析仪看波形发现 SCL 低电平的时间偶尔特别长别急着怀疑硬件故障很可能是从机在进行时钟拉伸。而这个功能能存在前提就是 SCL 开漏。3.5 附赠福利跨电压域通信不需要电平转换芯片开漏设计还带来一个很实用的小福利不同电压域的设备可以直接挂在同一条 I2C 总线上。举个例子主控是 3.3V 的传感器是 5V 的。如果把二者直接相连3.3V 的引脚可能承受不了 5V 的高电平。但在开漏模式下上拉电阻接的电源决定了总线高电平的电压。只要上拉电阻接 3.3V那么总线高电平就是 3.3V而 5V 设备识别 3.3V 为高电平也绰绰有余一般 TTL 高电平门槛是 2.0V 左右。同时因为设备引脚都是开漏不会往总线上推高电平所以 5V 设备不会把 3.3V 引脚打成 5V。如果总线上既有 3.3V 设备又有 5V 设备往往会在 3.3V 这一侧接上拉到 3.3V5V 设备也能正常识别。这比 UART 那种必须电平转换芯片方便多了。当然如果电平差得太悬殊比如 1.8V 和 5V还是建议加电平转换芯片I2C 专用的电平转换芯片比如 PCA9306原理也是利用开漏MOS 管实现的本质上还是开漏思想的延伸。4. 上拉电阻不是随便选的计算与实践开漏输出本身决定了总线“能”工作但真正决定总线“工作得好不好”的是那个不起眼的上拉电阻。电阻选大了信号上升沿太慢高速模式直接翻车电阻选小了灌电流过大低电平拉不下去甚至损坏芯片。这一节把上拉电阻的计算方法和实际选型讲透。4.1 电阻太小拉不低直接烧 IO上拉电阻的上限容易理解下限很多人没概念。很多人以为电阻越小越好驱动能力越强实际上根本不是这么回事。开漏输出拉低总线时电流路径是VCC → 上拉电阻 → 引脚内部 MOS 管 → GND。这个电流就是所谓的“灌电流”Sink Current。芯片的数据手册里通常会标一个最大灌电流常见的是 3mA、5mA 或者 20mA不同芯片差异很大。如果上拉电阻太小VCC 通过电阻灌进引脚的电流就会超过芯片的承受能力。轻则导致 VOL低电平输出电压过高从机识别不了低电平重则烧毁引脚。取一个常见参数算一下。VCC 3.3V芯片最大灌电流 IOL 3mA引脚低电平输出上限 VOL 0.4V。那么在引脚拉低时电阻上的压降是VR VCC - VOL 3.3 - 0.4 2.9V I VR / Rp为了不超过 3mA需要Rp 2.9V / 3mA ≈ 966Ω所以这种情况下上拉电阻最小不能小于 1kΩ 左右。这里注意不同芯片的 IOL 参数差异很大有的芯片引脚能承受 20mA那最小上拉可以到 150Ω 左右但实际没人这么用因为没必要给自己留风险。工程上常见的最小值一般就是 1kΩ。4.2 电阻太大上升沿不够快时序翻车上拉电阻的下限是保护引脚最大值则受限于信号完整性。开漏总线的上升沿过程可以等效为一个 RC 充电电路上拉电阻 Rp 对总线电容 Cbus 充电。总线上挂了越多设备、布线越长Cbus 就越大典型值每根线 10pF~50pF甚至更高。时间常数 τ Rp × Cbus信号上升时间大约为 0.8473 × τ从 10% 到 90%。I2C 协议标准对上升时间有明确要求I2C 模式最高频率最大上升时间 tr标准模式100kHz1000ns快速模式400kHz300ns快速模式1MHz120ns以标准模式 100kHz 为例如果总线电容 Cbus 100pF那么上拉电阻最大可以选tr 1000ns 0.8473 × Rp × Cbus 1000ns Rp 1000ns / (0.8473 × 100pF) ≈ 11.8kΩ也就是说在 100pF 总线电容下100kHz 工作频率的上拉电阻不能超过约 11.8kΩ取整一般用 10kΩ。这正好是 I2C 默认上拉电阻最常用的值。如果同样的电容换到 400kHz 快速模式上升时间要求就变成 300nsRp 300ns / (0.8473 × 100pF) ≈ 3.54kΩ这时候 10kΩ 就不行了得用 3.3kΩ 或 2.2kΩ。这就是很多人高速模式跑不起来的原因之一时序频率提高了上拉电阻却没跟着改。4.3 实战计算从 100kHz 到 1MHz 怎么取值把上面的结论整理成一张速查表方便实际项目直接查。以常见总线电容 50pF 和 100pF 为参考工作模式频率Cbus50pF 推荐Cbus100pF 推荐电阻范围标准模式100kHz10kΩ10kΩ1k~23kΩ快速模式400kHz4.7kΩ3.3kΩ1k~7kΩ快速模式1MHz2.2kΩ1.5kΩ1k~3kΩ实际项目中的取法我一般这么干先估算总线电容每个设备引脚加走线按 5~10pF 算PCB 走线每厘米约 1pF。然后按目标频率计算可行范围再取一个偏中间的值。100kHz 低速场景无脑用 10kΩ400kHz 场景用 3.3kΩ 或 4.7kΩ1MHz 尽量把电容压下来用 1.5k~2.2kΩ。如果总线上挂的设备特别多总线电容大上拉电阻又必须压低这时候要检查最低电阻是否满足 IOL 能力。之前做一块板子总线上挂了六个设备总线电容实测 200pF 左右400kHz 模式下算出来上拉必须小于 1.77kΩ但主控 IOL 只有 3mA最小上拉又要大于 966Ω最终选了 1.5kΩ留的余量不多但实测稳定。如果余量不足就得上 I2C 总线缓冲器或者分两路总线了。4.4 容性负载和布线长度的影响总线上每个设备都会贡献电容其中包括引脚封装电容、PCB 走线电容、过孔电容等。设备挂得越多线拉得越长总线电容越大。电容直接影响上升沿时间所以 I2C 对总线电容有一个经验上限标准模式 400pF快速模式 200pF 左右。超过这个值信号完整性会明显恶化。实际项目中如果总线很长、设备很多我有几个经验做法尽量把总线走成直线不要来回绕避免和强干扰信号并行。上拉电阻靠近主控放置或者放在总线物理位置的中点效果差别不大但离主控近一些总是好排查。高速模式下每一个设备的地线都要处理好地弹噪声会直接体现在 SDA 的波形上。如果总线电容实在压不下去可以考虑把总线拆成两路分两个 I2C 控制器或者加一个 I2C MUX比如 TCA9548A这是最省心的解法。5. 开漏总线排查实录那些年我踩过的坑理论说了一堆最后还是得回到实战。开漏和上拉电阻这套机制平时不出问题则已一出问题往往就是整个总线瘫掉而且现象千奇百怪。我把这些年调 I2C 踩过的坑、总结出来的排查思路整理一下希望能帮你少走些弯路。5.1 示波器一挂问题现原形排查 I2C 问题第一件事永远是看波形。别急着猜代码逻辑先用示波器同时挂 SDA 和 SCL抓一次通信过程基本能把问题定位到七八成。重点看几件事SCL 的频率对不对SDA 和 SCL 的高电平是不是达到了预期的电压上升沿是不是明显偏缓低电平能不能拉到 0V 附近有没有多余的毛刺如果高电平只有 1V 左右上拉电阻可能接错电压或者阻值太小。如果上升沿是个很缓的斜坡上拉电阻太大或者总线电容太大。如果低电平有 1V 多压降灌电流超了引脚快扛不住了。还有一种情况示波器探头一挂上去本来不通信的板子突然能通信了。这说明总线信号本来就处于临界状态探头的电容和阻抗改变了总线特性把信号“救”回来了。这种问题最难查我遇到过一次最后发现是上拉电阻用了 100kΩ——虚焊导致实际只有这一个电阻在起作用而 100kΩ 对总线来说太大了。5.2 上拉电阻选错导致的诡异现象分享一个典型的案例。之前做一块板子I2C 总线上挂了一个温度传感器主控是 STM32。代码逻辑检查了很多遍没问题但传感器就是偶尔读不到数据而且温度越高越容易出错。用示波器一看SDA 的上升沿明显偏慢高电平时间被压缩了。算了一下板子上拉电阻用的是 10kΩ总线电容因为布线问题大约有 150pF。100kHz 模式虽然理论余量还有但布线质量差再加上温度升高时器件阈值电压漂移时序余量就不够了。换成 4.7kΩ 之后问题彻底消失。这个案例的教训是上拉电阻不是随便放一个 10kΩ 就完事了要考虑实际的布线电容和温度余量。特别是工业场景温度范围宽一定要留足时序余量。还有一次板子打样回来发现 I2C 完全不通信排查半天发现上拉电阻根本没贴——BOM 里漏了。开漏总线没上拉SDA 和 SCL 就是浮空的怎么可能通信。这大概是所有开漏总线问题里最基础但也最容易犯的了。5.3 引脚模式配置错开漏被当成推挽用另外一个非常隐蔽的坑在单片机 GPIO 模拟 I2C 的时候很容易遇到。很多 MCU 的 GPIO 模式是可配置的你需要在代码里把 SDA 和 SCL 引脚设为开漏输出。但有些初始化库函数默认配置的是推挽输出或者你在调试其他功能的时候把引脚模式改掉了I2C 就莫名其妙地挂了。还有一种情况是引脚为“开漏模式但内部上拉没开”。某些 MCU 的开漏输出模式可以配合内部上拉电阻使用外部可以省掉上拉电阻。这时候如果你开的不是内部上拉又没有外部上拉总线还是浮空。反过来如果你既开了内部上拉又接了外部上拉那相当于两个上拉并联阻值变小一般也能工作但功耗会稍微高一点。最保险的做法无论 MCU 内部有没有上拉硬件上都设计外部上拉电阻软件上不要依赖内部上拉。这样代码兼容性最好也方便排查问题。我个人调试 I2C 的时候第一步就是确认引脚电气特性——万用表量 SDA 对地正常情况下总线空闲时应该接近 VCC如果量出来是 0V说明有设备在死拉总线或者引脚配置成普通推挽输出了。5.4 常见问题速查表现象可能原因排查方向总线完全无响应SDA 恒为低有设备死拉总线从机卡死、上拉电阻缺失或虚焊逐个断开设备检查上拉电阻高电平电压偏低上拉电阻阻值过小、上拉电源电压不对测静态电压核对电源上升沿明显变缓上拉电阻过大、总线电容过大计算 RC 时间常数换小电阻高速模式跑不起来低速正常上拉电阻没按高频要求选小按 400kHz/1MHz 重新计算电阻偶尔通信失败温度升高时更严重时序余量不足、上拉电阻偏大换更小的上拉电阻改善布线板子能通信但 IO 发烫灌电流过大上拉电阻太小用万用表测电流换更大的电阻一个设备单独测试正常全部挂上不行总线电容超限、地址冲突拆分总线或者用 MUXSDA 波形有毛刺或者台阶总线过长、阻抗不连续、地线噪声改善布线缩短走线加强地最后再分享一个我自己的习惯硬件设计阶段我就会把上拉电阻的封装画成 0603阻值先按目标频率计算好同时预留一个并联焊盘。如果调试时发现需要调整直接并联一个电阻就能改阻值不用重新打板。开漏和上拉这套机制原理讲透其实不难真正考验人的是实际工程中的那些边角情况。希望这篇文章能把 I2C 物理层这块讲明白让你下次遇到总线问题的时候能少一点瞎猜多一点底气。
