I2C 这玩意儿说简单也简单两根线一挂地址一写数据就来回跑说坑也真坑硬件 I2C 和软件 I2C 这两条路几乎每个做嵌入式的人都至少在其中一条上摔过跟头。我这些年从 STM32 到 GD32、从 CH32V 到各种国产 MCU再到 Linux 下的 I2C 子系统硬件 I2C 死锁、软件 I2C 时序跑飞、上拉电阻选错、电平不匹配烧器件这些场景基本都亲手经历过一遍。这篇就围绕硬件 I2C 和软件 I2C 到底谁更坑这个老生常谈的话题把两者的底层机制、适用边界、踩坑链路和选型逻辑掰开揉碎讲清楚不管你是刚上手 I2C 读写 EEPROM 的新手还是已经在调 OLED12864、I2C 编码器、数字电位器的老手都能从里面找到能直接抄作业的东西。1. 先把 I2C 的物理层讲透不然选型全是瞎猜很多人一上来就问硬件 I2C 和软件 I2C 哪个好其实这个问题本身就问错了。要回答它得先搞清楚 I2C 在物理层到底是怎么工作的因为硬件和软件 I2C 的差异本质上是对物理层时序控制方式的不同而不是协议本身有什么两样。1.1 开漏输出和上拉电阻I2C 的命根子I2C 只有两根线SDA数据线和 SCL时钟线它们都是开漏Open-Drain输出结构。什么叫开漏你可以把它想象成一个只能往下拉、不能往上推的开关。引脚内部只有一个 NMOS 管导通时把线拉到 GND截止时引脚处于高阻态什么都不做。那线怎么变高靠外部的上拉电阻把线拉到 VCC。这个设计不是随便定的它解决了一个核心问题多设备共享总线时的电平冲突。如果两个设备同时驱动一根线一个想输出高、一个想输出低推挽结构下就是电源直接对地短路芯片当场冒烟。而开漏结构下任何设备都只能拉低谁都不拉的时候线才被上拉电阻拉高天然实现了线与逻辑永远不会短路。所以你在配置 GPIO 的时候I2C 的 SDA 和 SCL 必须配成开漏模式Open-Drain而不是推挽。这一点在 STM32 的 GPIO 八种工作模式里对应的是GPIO_Mode_AF_OD复用开漏或者软件模拟时的GPIO_Mode_Out_OD。我见过太多新手把 I2C 引脚配成推挽输出结果单设备测试能跑一挂第二个设备就通信失败查半天查不出来。上拉电阻的取值也不是拍脑袋定的。它由两个因素共同决定总线电容走线越长、挂的设备越多等效电容越大上升沿越慢。I2C 标准规定总线电容不超过 400pF。上升时间要求标准模式100kHz上升时间不超过 1000ns快速模式400kHz不超过 300ns。粗略估算公式是Rp(max) ≈ tr / (0.8473 × Cb)。假设总线电容 200pF快速模式要求 tr ≤ 300ns那 Rp 最大约 1.77kΩ。而 Rp 最小则受限于器件灌电流能力一般不低于 1kΩ。所以常见取值是2.2kΩ 到 4.7kΩ总线短、设备少用 4.7k总线长、速率高用 2.2k 甚至 1.5k。提示很多模块板比如常见的 OLED12864 模块、EEPROM 模块自带上拉电阻如果你再外接一组等效阻值变小灌电流可能超标。挂多个模块时一定要确认板上是否已有上拉避免重复。1.2 推挽模式和开漏模式的本质区别既然热词里提到了i2c 的推挽模式和开漏模式这里必须澄清一个常见误解I2C 总线本身只能用开漏推挽是给别的场景用的。但为什么有人会问这个因为在某些特殊情况下比如单主单从、距离极短、又不想加上拉电阻的场合有人会尝试用推挽硬驱这在非标准场景下偶尔能跑但一旦挂第二个设备就完蛋。推挽输出的特点是能主动输出高和低驱动能力强、上升沿陡峭适合 SPI、UART 这类点对点或单向总线。而 I2C 是多主多从的共享总线必须开漏。这个区别决定了硬件 I2C 外设内部已经帮你处理好了开漏时序而软件 I2C 需要你自己把 GPIO 配成开漏并手动控制拉高拉低。1.3 电平转换3.3V 和 5V 设备混挂的必修课热词里还有一条i2c 需要电平转换吗答案是只要总线上有不同供电电压的器件就必须做电平转换。比如你的 MCU 是 3.3V挂了一个 5V 的 EEPROM直接连会怎样5V 器件的开漏引脚在高阻态时上拉电阻如果接到 5V那 3.3V 的 MCU 引脚就会被灌入 5V长期下来 IO 口损坏。标准做法是用专用的 I2C 电平转换芯片如 PCA9306、TXS0102或者用最简单的双 NMOS 电平转换电路两个 NMOS 的栅极接低压侧 VCC源极接低压侧 SDA/SCL漏极接高压侧 SDA/SCL两侧各自上拉到自己的 VCC。这个电路利用 NMOS 的体二极管和导通特性实现双向电平转换成本极低是很多成熟产品的选择。2. 硬件 I2C 的真实工作流程与它的死穴搞清楚了物理层我们来看硬件 I2C。所谓硬件 I2C就是 MCU 内部有一个专门的 I2C 外设控制器你只需要配置寄存器、往数据寄存器里写数据剩下的起始位、地址、ACK、数据、停止位全部由硬件自动生成。听起来很美好但为什么那么多人说硬件 I2C 坑2.1 硬件 I2C 的状态机模型硬件 I2C 本质上是一个状态机。以 STM32 的 I2C 外设为例它有一堆状态寄存器SR1、SR2每个状态对应一个事件比如事件状态标志含义EV5SB1起始位已发送EV6ADDR1地址已发送并收到 ACKEV7RXNE1收到一个字节EV8TXE1数据寄存器空可以写下一个字节EV8_2BTF1字节传输完成EV9ADDR1收到自己的地址从机模式你写驱动的时候就是在一个while循环里不断查询这些标志位按状态机顺序推进。这套机制在理想情况下工作得很好但问题在于一旦某个事件没等到程序就卡死在 while 循环里。2.2 硬件 I2C 死锁最经典的坑硬件 I2C 最臭名昭著的问题就是死锁Bus Lock / Deadlock。典型场景是这样的主机正在读数据从机因为某种原因比如被复位、供电抖动没有释放 SDA 线或者主机在传输过程中被中断打断导致 SCL 和 SDA 的状态错乱。这时候 I2C 外设的状态机就卡在某个状态while(!I2C_CheckEvent(...))永远等不到整个程序挂死。更麻烦的是STM32 早期型号比如 F1 系列的 I2C 外设硬件 bug 是出了名的官方勘误手册里列了一堆 I2C 相关的问题比如总线忙标志 BUSY 位在某些情况下无法清除从机模式下时钟拉伸Clock Stretching处理异常仲裁丢失后外设无法自动恢复这些硬件层面的问题靠软件很难完全规避只能通过加超时、加总线复位逻辑来兜底。2.3 硬件 I2C 的恢复机制怎么写既然死锁不可避免那就得会恢复。标准的 I2C 总线恢复流程是这样的关闭 I2C 外设把 SCL 和 SDA 引脚切回普通 GPIO 开漏模式。手动发送 9 个 SCL 时钟脉冲每个脉冲期间检测 SDA 是否被释放。如果 SDA 恢复高电平说明从机已经释放总线发送一个 STOP 条件。重新初始化 I2C 外设。代码大概长这样void I2C_BusRecovery(void) { GPIO_InitTypeDef gpio; // 切回普通开漏 GPIO gpio.GPIO_Pin SCL_PIN | SDA_PIN; gpio.GPIO_Mode GPIO_Mode_Out_OD; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(I2C_GPIO_PORT, gpio); // 确保 SDA 为高释放 GPIO_SetBits(I2C_GPIO_PORT, SDA_PIN); for (int i 0; i 9; i) { GPIO_ResetBits(I2C_GPIO_PORT, SCL_PIN); delay_us(5); GPIO_SetBits(I2C_GPIO_PORT, SCL_PIN); delay_us(5); if (GPIO_ReadInputDataBit(I2C_GPIO_PORT, SDA_PIN)) { break; // SDA 已释放 } } // 发送 STOP 条件 GPIO_ResetBits(I2C_GPIO_PORT, SDA_PIN); delay_us(5); GPIO_SetBits(I2C_GPIO_PORT, SCL_PIN); delay_us(5); GPIO_SetBits(I2C_GPIO_PORT, SDA_PIN); delay_us(5); // 重新初始化 I2C 外设 I2C_DeInit(I2Cx); I2C_Init(I2Cx, i2c_init_struct); I2C_Cmd(I2Cx, ENABLE); }这段代码我在多个项目里用过实测下来能解决 90% 以上的总线卡死问题。剩下的 10% 通常是硬件本身的问题比如上拉电阻虚焊、走线太长导致信号完整性差那就得从硬件下手了。注意恢复流程里的延时不能省尤其是 SCL 频率较高的时候从机需要时间响应。我一般用 5us 起步如果从机比较慢比如某些老式 EEPROM可以加到 10us。2.4 硬件 I2C 的优势场景说了这么多坑硬件 I2C 也不是一无是处。它的优势在于CPU 占用低数据传输由硬件完成CPU 只需要处理中断或轮询标志位在高速率400kHz、1MHz下优势明显。时序精确硬件生成的时序不受中断延迟影响抖动小。支持多主仲裁多主机场景下硬件自动处理仲裁软件模拟几乎做不到。所以在高速、多主、对 CPU 占用敏感的场景硬件 I2C 是首选。比如汽车嵌入式开发里的传感器网络、工业控制里的多主总线基本都是硬件 I2C。3. 软件 I2C 的时序控制与它的软肋软件 I2C顾名思义就是用普通 GPIO 口通过软件控制引脚的高低电平变化手动模拟出 I2C 的时序。它的核心就是精确的延时控制。3.1 软件 I2C 的时序本质I2C 的时序其实不复杂核心就是几个动作起始条件STARTSCL 为高时SDA 由高变低。停止条件STOPSCL 为高时SDA 由低变高。数据位传输SCL 为低时改变 SDASCL 为高时 SDA 保持稳定。ACK/NACK第 9 个时钟周期接收方拉低 SDA 表示 ACK。软件 I2C 就是把这几个动作翻译成 GPIO 操作加延时。比如发送一个字节void I2C_WriteByte(uint8_t data) { for (int i 0; i 8; i) { SCL_LOW(); delay_us(2); if (data 0x80) SDA_HIGH(); else SDA_LOW(); data 1; delay_us(2); SCL_HIGH(); delay_us(2); } // 第 9 个时钟读取 ACK SCL_LOW(); delay_us(2); SDA_HIGH(); // 释放 SDA由从机控制 delay_us(2); SCL_HIGH(); delay_us(2); // 读 SDA 判断 ACK if (SDA_READ() 0) { // ACK } else { // NACK } SCL_LOW(); delay_us(2); }看起来很简单对吧但坑就藏在细节里。3.2 延时精度软件 I2C 的命门软件 I2C 的速率完全由延时决定。delay_us(2)这个 2us 是怎么来的假设你要跑 100kHz 的 I2C一个时钟周期是 10us高电平 5us、低电平 5us。但实际代码里每个 GPIO 操作本身也有耗时所以延时值需要根据实际测量调整。问题在于不同编译器优化等级、不同主频、不同 MCUGPIO 操作耗时都不一样。你在 STM32F103 上调试好的延时换到 GD32F407 上可能就完全不对了。更坑的是如果你用了-O2优化编译器可能把一些 GPIO 操作重排甚至优化掉导致时序完全乱套。我的经验是软件 I2C 的延时函数一定要用volatile修饰的循环或者硬件定时器绝对不能用编译器可能优化的空循环。而且延时值要留有余量宁可慢一点也不要快到从机反应不过来。3.3 中断干扰软件 I2C 最怕的事软件 I2C 的另一个软肋是中断。因为整个时序是靠 CPU 一条条指令执行出来的如果传输过程中来了一个中断CPU 跑去处理中断SCL 和 SDA 的电平就停在原地不动了。对于 I2C 从机来说时钟线停住超过一定时间通常是几十毫秒它可能就认为总线超时直接复位状态机。所以软件 I2C 传输期间通常需要关中断或者把 I2C 传输放在高优先级中断里。但关中断时间太长又会影响系统实时性这是个两难。我一般的做法是软件 I2C 的整个字节传输过程关中断字节之间开中断。这样单次关中断时间控制在几十微秒对大多数系统来说可以接受。如果系统对实时性要求极高那就老老实实上硬件 I2C。3.4 软件 I2C 的灵活性优势软件 I2C 最大的好处是灵活。引脚随便选不受硬件外设的引脚映射限制。你可以在任意两个 GPIO 上模拟 I2C这在引脚资源紧张或者硬件 I2C 引脚被占用的时候非常有用。另外软件 I2C 调试起来直观。你可以用逻辑分析仪或者示波器直接看波形哪里不对一目了然。硬件 I2C 出问题的时候你只能看寄存器状态有时候真的是一头雾水。还有一个场景某些从机不标准。比如有些器件对时序要求特别宽松或者特别苛刻硬件 I2C 的固定时序可能不匹配这时候软件 I2C 可以灵活调整延时来适配。4. 硬件 I2C vs 软件 I2C一张表看清选型逻辑讲了这么多是时候做个系统对比了。下面这张表是我根据多年项目经验总结的覆盖了选型时最关心的几个维度。对比维度硬件 I2C软件 I2CCPU 占用低硬件自动传输高CPU 全程参与最高速率可达 1MHz快速模式通常 100-400kHz受 CPU 主频限制时序精度高不受中断影响低受中断和编译器优化影响引脚灵活性固定引脚受复用功能限制任意 GPIO多主支持支持硬件仲裁几乎无法实现死锁风险有且恢复复杂低因为完全可控调试难度高依赖寄存器状态低波形直观代码复杂度中需处理状态机低逻辑直白移植性差不同 MCU 差异大好换 MCU 只需改 GPIO 宏适用场景高速、多主、低 CPU 占用低速、引脚受限、调试友好从这张表能看出来两者没有绝对的优劣关键看场景。我的选型原则是速率要求 400kHz或者需要多主硬件 I2C。引脚受限或者从机时序特殊软件 I2C。系统实时性要求高不能长时间关中断硬件 I2C。调试阶段或者从机行为不确定先用软件 I2C 跑通再考虑换硬件。5. 实战I2C 读写 EEPROM 的完整踩坑链路光讲理论不够我们拿一个最经典的场景——I2C 读写 EEPROM比如 AT24C02——来走一遍完整的踩坑链路。这个场景几乎每个嵌入式新手都会遇到也是硬件 I2C 和软件 I2C 差异体现得最明显的地方。5.1 为什么 EEPROM 是 I2C 的试金石AT24C02 这类 EEPROM 有几个特点让它成为检验 I2C 驱动质量的试金石写周期长一次写操作后EEPROM 内部需要 5ms 左右的时间完成擦写这期间它不响应任何 I2C 命令。页写限制AT24C02 的页大小是 8 字节跨页写会回卷覆盖。地址递增连续读的时候地址自动递增但连续写不会自动跨页。这些特性决定了如果你不处理写周期等待连续写就会失败如果你不处理页边界数据就会写错位置。5.2 写 EEPROM 的完整流程以 AT24C02 为例写一个字节的流程是发送 START。发送设备地址 写标志0xA0。等待 ACK。发送字节地址0x00-0xFF。等待 ACK。发送数据字节。等待 ACK。发送 STOP。等待 5ms 写周期完成或者用 ACK 轮询。第 9 步是最容易漏掉的。很多人写完就直接读结果读出来还是旧数据因为 EEPROM 还在忙。正确的做法是ACK 轮询不断发送 START 设备地址直到收到 ACK说明 EEPROM 空闲了。void EEPROM_WaitReady(void) { uint32_t timeout 0; do { I2C_GenerateSTART(I2Cx, ENABLE); while (!I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2Cx, 0xA0, I2C_Direction_Transmitter); timeout; } while (I2C_CheckEvent(I2Cx, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED) ERROR timeout 10000); I2C_GenerateSTOP(I2Cx, ENABLE); }这段代码用硬件 I2C 实现注意那个timeout没有它就可能死循环。这就是硬件 I2C 的典型写法每个 while 等待都要加超时。5.3 页写边界一个隐蔽的坑AT24C02 页大小 8 字节假设你从地址 0x06 开始写 4 个字节会写到 0x06、0x07、0x00、0x01——因为到了页边界 0x07 之后回卷到 0x00。这个行为在数据手册里写了但很多人不看结果数据莫名其妙写错位置。正确的做法是按页拆分写操作void EEPROM_WriteBuffer(uint8_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint8_t page_offset addr % 8; uint8_t page_remain 8 - page_offset; uint8_t write_len (len page_remain) ? len : page_remain; EEPROM_WritePage(addr, buf, write_len); EEPROM_WaitReady(); addr write_len; buf write_len; len - write_len; } }这个逻辑看起来简单但实际项目中我见过太多人直接一把梭写一整块结果只有第一页数据对后面全乱。5.4 软件 I2C 读写 EEPROM 的差异如果用软件 I2C 读写 EEPROM流程是一样的但实现方式不同。软件 I2C 没有状态机你需要手动控制每个时序。好处是调试直观拿逻辑分析仪一挂波形对不对一眼就看出来。但软件 I2C 在 EEPROM 场景下有个额外注意点写周期等待。因为软件 I2C 没有硬件状态机你没法用检查事件的方式判断 EEPROM 是否就绪只能靠固定延时或者手动发地址读 ACK。固定延时简单但浪费时间手动读 ACK 高效但代码稍复杂。我的建议是如果 EEPROM 写入不频繁用固定延时 5-10ms 最省事如果写入频繁用 ACK 轮询。6. 那些年我踩过的 I2C 硬件坑软件层面的坑讲完了再来说说硬件层面的。I2C 这个协议软件调不通的时候十有八九是硬件问题。6.1 上拉电阻缺失或阻值不当最常见的硬件坑就是上拉电阻。有些模块板上没有上拉你直接连 MCUSDA 和 SCL 永远是低电平通信当然失败。还有些模块板上有上拉你又外接了一组等效阻值太小灌电流超标波形上升沿虽然陡峭但幅度不够。判断方法很简单用示波器看 SDA 和 SCL 的高电平幅度。如果高电平达不到 VCC 的 70% 以上基本就是上拉有问题。6.2 走线过长导致的信号完整性问题I2C 标准规定总线电容不超过 400pF但实际项目中如果你的 I2C 走线超过 30cm或者挂了七八个设备电容很容易超标。表现就是通信时好时坏速率越低越稳定。解决办法降低速率400kHz 降到 100kHz。减小上拉电阻4.7k 降到 2.2k。用 I2C 缓冲器/中继器如 PCA9515分段驱动。6.3 地址冲突挂多个同型号器件I2C 设备地址是 7 位的理论上可以挂 128 个设备但实际很多器件的地址是固定的或者只有几个可选地址。比如你挂两个 AT24C02默认地址都是 0xA0那就冲突了。解决办法选带地址选择引脚的型号AT24C02 的 A0/A1/A2 引脚可以改地址。用 I2C 多路复用器如 TCA9548A分通道。换用地址可配置的器件。6.4 电源和地线问题I2C 通信失败有时候根本不是 I2C 本身的问题而是电源和地线。比如 MCU 和从机不共地或者地线上有较大压差都会导致电平判断错误。我遇到过一次设备之间地线接得不好I2C 时通时不通查了两天才发现是地线接触电阻太大。提示调试 I2C 时先用万用表量一下 MCU 和从机之间的地线压差正常应该接近 0mV。如果超过几十 mV先解决地线问题。7. Linux 下的 I2C另一套完全不同的玩法前面讲的都是裸机 MCU 场景如果你做的是嵌入式 LinuxI2C 的玩法就完全不一样了。Linux 内核有完整的 I2C 子系统分为I2C 适配器驱动和I2C 设备驱动两层。7.1 Linux I2C 子系统的分层结构Linux 的 I2C 子系统大致分三层I2C 核心层i2c-core提供统一的 API管理适配器和设备。I2C 适配器驱动对应具体的 I2C 控制器硬件比如 SoC 内部的 I2C 控制器。I2C 设备驱动对应挂载在总线上的具体器件比如 EEPROM、传感器。在设备树Device Tree里I2C 设备和适配器的关系是这样描述的i2c1 { status okay; clock-frequency 100000; eeprom50 { compatible atmel,24c02; reg 0x50; }; };这里的reg 0x50就是设备地址clock-frequency是总线速率。内核启动时会根据设备树自动匹配驱动。7.2 用户空间操作 I2Ci2c-tools如果你不想写内核驱动只想在用户空间快速读写 I2C 设备可以用i2c-tools# 查看有哪些 I2C 适配器 i2cdetect -l # 扫描某条总线上的设备 i2cdetect -y 1 # 读取 EEPROM 地址 0x50 的 0x00 寄存器 i2cget -y 1 0x50 0x00 # 写入数据 i2cset -y 1 0x50 0x00 0xAB这套工具在调试阶段非常有用能快速确认硬件是否正常。我一般拿到一块新板子第一件事就是i2cdetect扫一遍看看设备地址能不能识别到。7.3 Linux 下硬件 I2C 和软件 I2C 的选择Linux 下同样有硬件 I2C 和软件 I2Ci2c-gpio两种。硬件 I2C 由 SoC 的 I2C 控制器驱动软件 I2C 通过i2c-gpio驱动用普通 GPIO 模拟。选择逻辑和裸机类似高速、稳定用硬件 I2C引脚受限或者需要灵活配置用 i2c-gpio。不过 Linux 下软件 I2C 的性能受调度影响更大因为用户空间和内核空间的切换、中断处理都会引入延迟所以 Linux 下软件 I2C 的速率通常比裸机更低。8. 我的选型决策树和几条硬核经验讲了这么多最后给一个我实际项目中用的选型决策树以及几条用血泪换来的经验。8.1 选型决策树速率要求是否超过 400kHz是 → 硬件 I2C否 → 进入下一步。是否需要多主是 → 硬件 I2C否 → 进入下一步。引脚是否受限硬件 I2C 引脚是否被占用是 → 软件 I2C否 → 进入下一步。系统是否对 CPU 占用敏感是 → 硬件 I2C否 → 进入下一步。从机时序是否特殊需要灵活调整是 → 软件 I2C否 → 硬件 I2C。8.2 几条硬核经验经验一硬件 I2C 的每个等待循环都必须加超时。这是铁律没有例外。我见过太多项目因为一个while(!I2C_CheckEvent(...))没加超时现场设备随机死机查了几个月才定位到。经验二软件 I2C 的延时函数用volatile变量控制。不要用for(i0;i10;i);这种空循环编译器优化等级一变延时全乱。用volatile uint32_t delay_cnt;然后while(delay_cnt--);保证不被优化。经验三I2C 总线上拉电阻宁小勿大但要算灌电流。上拉太小灌电流超标会损坏器件上拉太大上升沿太慢通信失败。2.2k-4.7k 是安全区间特殊场景再调整。经验四调试 I2C 先看波形再看代码。逻辑分析仪是 I2C 调试的必备工具一百多块的那种就够用。波形对了再查代码波形不对直接查硬件能省大量时间。经验五多设备 I2C 总线先单独测每个设备再一起挂。一起挂出问题的时候你根本不知道是哪个设备的问题。单独测通了再合并问题范围小很多。经验六Linux 下优先用设备树 标准驱动别急着写用户空间代码。内核里已经有大量成熟的 I2C 设备驱动能用现成的就别重复造轮子。用户空间的i2c-tools只适合调试不适合产品。8.3 关于 I2C 扩展和编码器的一点补充热词里提到了i2c 扩展和i2c 编码器这里简单说两句。I2C 扩展通常指用 I2C 接口的 IO 扩展芯片如 PCF8574来增加 GPIO 数量这在引脚紧张的场合很有用。但要注意I2C 扩展的 IO 速率远低于 MCU 原生 GPIO不适合高速场景。I2C 编码器则是指带 I2C 接口的旋转编码器或者磁性编码器芯片比如 AS5600。这类器件通常对时序要求不高硬件 I2C 和软件 I2C 都能驱动选哪个主要看你的引脚和速率需求。说到底硬件 I2C 和软件 I2C 谁更坑取决于你的场景和你的经验。硬件 I2C 坑在死锁和硬件 bug但一旦跑通就很稳软件 I2C 坑在时序和中断但胜在灵活可控。我的个人体会是新手先用软件 I2C 把协议吃透再上硬件 I2C 做产品。因为软件 I2C 逼着你去理解每一个时序细节等你把这些都搞明白了硬件 I2C 的那些状态机也就不是事了。反过来一上来就用硬件 I2C 库函数出了问题你连从哪查都不知道。
阅读完成 · 觉得有帮助?