
1. I2C 总线在 OpenHarmony 里的定位与整体设计思路I2C 这东西搞嵌入式的人几乎绕不开。它不像 SPI 那样追求极致速度也不像 UART 那样点对点简单粗暴它的价值在于“两根线挂一堆设备”。在 OpenHarmony 系统实战开发里I2C 是传感器、EEPROM、触摸屏、RTC、PMIC 这类外设最常用的接入方式之一。你拿到的标题是“I2C 总线怎么用怎么排障”这其实问到了两个层面一是怎么在 OpenHarmony 的 HDF 驱动框架下把 I2C 设备跑起来二是当它不工作时你怎么一步步定位问题。适合谁看适合已经能编译 OpenHarmony、知道 HDF 驱动大概长什么样但一遇到 I2C 读写失败就抓瞎的开发者。也适合从裸机或 Linux 驱动转过来的朋友因为 OpenHarmony 的设备树和 HDF 配置有自己的脾气。先把这个项目的核心思路说清楚。OpenHarmony 的 I2C 使用不是让你直接操作寄存器而是通过 HDFHardware Driver Foundation框架把 I2C 控制器抽象成统一的总线服务。应用层或内核态驱动通过I2cOpen、I2cTransfer这类接口去读写。设备树负责描述“哪个 I2C 控制器、挂在哪、从机地址是多少、寄存器位宽多少”。HDF 驱动负责把设备树信息解析出来生成对应的 I2C 客户端。整个链路是设备树 - HDF 配置 - I2C 控制器驱动 - 硬件时序 - 从机设备。任何一环出问题现象都是“读写失败”或“数据不对”。所以排障不能只盯着代码得从硬件、设备树、驱动配置、时序、电源几个维度一起看。为什么 OpenHarmony 要这么设计因为 OpenHarmony 面向的是多芯片平台瑞芯微 RK3568、海思、展锐等都可能跑。如果每个平台都写一套 I2C 操作代码移植成本太高。HDF 把控制器驱动和设备驱动分离控制器驱动由芯片原厂或社区提供设备驱动由外设厂商或开发者编写。设备树则把硬件连接关系从代码里剥离出来。这样你换一个板子只要改设备树和 HDF 配置设备驱动代码基本不用动。这个设计思路和 Linux 的 I2C 子系统很像但 OpenHarmony 的 HDF 更强调用户态驱动和内核态驱动的统一接口。实际开发中你大部分时间是在写设备驱动和调设备树而不是改控制器驱动。还有一个关键点I2C 是半双工、主从架构、开漏输出。这意味着总线上所有设备共享两根线任何时刻只能有一个主机发起传输。从机不能主动说话只能被叫到才应答。这就引出了地址冲突、总线仲裁、时钟拉伸、总线死锁这些经典问题。在 OpenHarmony 里这些问题不会因为用了 HDF 就消失反而因为多了一层抽象排查起来更隐蔽。比如你写了一个 I2C 读取函数返回失败可能是从机没上电可能是地址写错可能是设备树里控制器编号不对也可能是总线被某个设备拉死了。所以这篇内容我会把“怎么用”和“怎么排障”揉在一起讲因为实际开发中这两件事根本分不开。2. I2C 核心细节解析与 OpenHarmony 实操要点2.1 I2C 时序基础别急着写代码先看懂这四张图I2C 的时序其实不复杂但很多人栽在细节上。起始条件SSCL 高电平期间SDA 从高变低。停止条件PSCL 高电平期间SDA 从低变高。数据位SCL 低电平期间 SDA 可以变化SCL 高电平期间 SDA 必须稳定。应答位ACK主机发完 8 位数据后释放 SDA从机在第 9 个时钟周期把 SDA 拉低表示应答。非应答NACK从机不拉低 SDA或者主机在读取最后一个字节后主动发 NACK。这些是基本功但实际用逻辑分析仪抓波形时你会发现很多问题就出在这些边沿上。在 OpenHarmony 里控制器驱动会帮你处理起始、停止、ACK 这些时序。你调用I2cTransfer时传入一个I2cMsg数组每个 msg 包含从机地址、读写标志、数据缓冲区、长度。控制器驱动会按顺序执行这些 msg。但注意不是所有控制器都支持任意组合的 msg。有些控制器要求每个 msg 之间必须有停止条件有些支持重复起始条件。如果你在一个 transfer 里放了多个 msg中间没有停止条件控制器可能会报错。这个细节在设备树里通常有i2c-scl-falling-time-ns、i2c-sda-falling-time-ns这类参数来调整时序但很多开发者直接抄参考配置结果换一个从机就挂了。提示用逻辑分析仪抓 I2C 波形时一定要同时抓 SCL 和 SDA并且把触发条件设在起始条件上。否则你看到的可能是一堆无意义的电平跳变。2.2 OpenHarmony 设备树里的 I2C 节点怎么写才不踩坑设备树是 OpenHarmony I2C 使用的第一道门槛。以 RK3568 为例I2C 控制器节点通常在rk3568.dtsi里定义比如i2c0: i2cfdd40000。你要在板级设备树里覆盖它设置status okay然后添加从机子节点。从机节点必须包含reg属性值是从机地址比如reg 0x50。这个地址是 7 位地址不是 8 位。很多人把写操作地址和读操作地址搞混比如 EEPROM 的 7 位地址是 0x50写是 0xA0读是 0xA1。设备树里只写 7 位地址HDF 驱动会自动处理读写位。另一个坑是clock-frequency。默认可能是 100kHz但有些从机支持 400kHz有些只能 100kHz。如果你设了 400kHz 但从机跟不上就会出现数据错乱或 NACK。RK3568 的 I2C 控制器还支持i2c-scl-rising-time-ns和i2c-scl-falling-time-ns这些参数影响时序计算。如果你不填驱动会用默认值可能不匹配你的硬件。我一般会先用示波器测一下实际上升沿时间再填进去。还有pinctrl配置I2C 的 SCL 和 SDA 引脚必须正确复用为 I2C 功能并且上拉电阻要接。很多板子 I2C 不通最后发现是引脚复用没配或者上拉电阻没焊。在 OpenHarmony 的 HDF 配置里你还需要在device_info.hcs里声明 I2C 设备。比如device_i2c :: device { device0 :: deviceNode { policy 2; priority 50; preload 0; permission 0666; moduleName HDF_I2C; serviceName hdf_i2c; deviceMatchAttr i2c_config; }; }然后在i2c_config.hcs里配置总线号和从机信息。这里deviceMatchAttr要和设备树里的节点匹配。如果匹配不上驱动加载了但找不到设备读写就会返回-ENODEV。2.3 HDF I2C 接口怎么调从 Open 到 Transfer 的完整流程OpenHarmony 的 I2C 用户态接口在base/hiviewdfx或drivers/hdf_core里具体头文件是i2c_if.h。典型流程是I2cOpen(busNum)打开指定 I2C 总线返回一个句柄。构造I2cMsg数组填充从机地址、缓冲区、长度、读写标志。调用I2cTransfer(handle, msgs, count)执行传输。检查返回值如果小于 0 说明失败。I2cClose(handle)关闭总线。看起来简单但有几个细节。I2cMsg的addr字段是 7 位从机地址不是 8 位。flags字段用I2C_FLAG_READ或I2C_FLAG_WRITE。如果你要读寄存器通常需要两个 msg第一个写寄存器地址第二个读数据。这两个 msg 之间不能有停止条件所以要用I2C_FLAG_NO_STOP标志。但有些控制器不支持这个标志你就得分开两次 transfer中间加停止条件。分开写的问题是有些从机在停止条件后会复位内部地址指针导致读不到正确数据。所以优先用组合 msg。还有一个常见错误缓冲区长度和实际数据长度不一致。比如你定义uint8_t buf[2]但len写了 4驱动会读越界。或者你读 2 字节但从机只返回 1 字节第二个字节可能是 0xFF。这些在调试时都要用逻辑分析仪确认。注意I2cTransfer的返回值是实际传输的 msg 数量不是字节数。如果返回 1说明第一个 msg 成功了第二个可能失败了。要逐个检查。3. I2C 排障实战从现象到根因的完整链路3.1 先分清楚是总线不通还是设备不应答I2C 排障第一步是区分“总线级别故障”和“设备级别故障”。总线级别故障包括SCL 或 SDA 被拉死、上拉电阻缺失、引脚复用错误、控制器未使能。设备级别故障包括从机地址错误、从机未上电、从机内部寄存器地址错误、从机忙。怎么区分用逻辑分析仪抓波形。如果发起始条件后SCL 有 9 个时钟但 SDA 一直是高说明没有从机应答这是设备级别问题。如果 SCL 一直被拉低或者 SDA 一直被拉低说明总线被某个设备拉死了这是总线级别问题。在 OpenHarmony 里你可以先看内核日志。如果控制器驱动加载失败日志里会有i2c-rk3x或类似字样。如果控制器加载成功但传输失败日志里会有I2cTransfer failed或timeout。超时通常意味着从机没应答或者总线被拉死。NACK 通常意味着地址不对或者从机没准备好。你可以用hilog查看 HDF 的日志过滤I2C关键字。还有一个快速判断方法用万用表测 SCL 和 SDA 对地电压。正常空闲时两者都应该是高电平比如 3.3V。如果一个是 0V说明被拉死了。如果两个都是 0V可能是控制器没初始化或者电源没上。如果电压在 1.5V 左右可能是上拉电阻太大或者总线电容太大。3.2 设备树配置错误的五种典型表现设备树配错是 I2C 不工作的最常见原因。我整理了几种典型表现现象可能原因排查方法驱动加载但找不到设备deviceMatchAttr不匹配检查 hcs 和 dts 里的属性名是否一致读写返回 -ENODEV从机节点status不是okay确认设备树覆盖是否正确读写返回 -EINVALreg地址超过 7 位范围检查地址是否写成 8 位读写超时clock-frequency太高降到 100kHz 试试数据错位pinctrl配错或上拉缺失用示波器看波形质量还有一个隐蔽问题设备树里 I2C 控制器节点被其他驱动占用。比如某个 GPIO 驱动也用了同一组引脚导致 I2C 无法复用。这时候你要检查pinctrl的引用计数确保没有冲突。3.3 用逻辑分析仪抓 I2C 波形的正确姿势逻辑分析仪是 I2C 排障的核武器。但很多人抓不到有效波形原因是采样率不够或触发条件不对。I2C 最快 400kHz理论上 1MHz 采样率就够了但为了看清毛刺建议至少 10MHz。触发条件设在 SDA 下降沿且 SCL 高电平这就是起始条件。抓到的波形要能看清起始、地址、ACK、数据、停止。如果波形上出现很多毛刺说明上拉电阻太大或走线太长。如果 SCL 占空比严重不对称说明时钟配置有问题。在 OpenHarmony 里你还可以用i2c-tools的i2cdetect来扫描总线。但注意OpenHarmony 默认可能不带这个工具你需要自己交叉编译。i2cdetect -y 0会扫描总线 0 上的所有地址。如果某个地址显示UU说明该地址被驱动占用了。如果显示--说明没有设备应答。这个工具能快速告诉你从机是否在线。提示i2cdetect会发送大量探测包有些从机不支持这种探测可能会进入异常状态。所以扫描前最好确认从机手册。4. 常见问题与排查技巧实录4.1 I2C 读写 EEPROM 失败地址和页写问题EEPROM 是 I2C 最经典的从机也是坑最多的。常见问题写进去读出来不对或者写一次成功第二次失败。原因通常是页写边界。比如 AT24C02 每页 8 字节你从地址 0x07 开始写 4 字节会跨页导致数据回卷。正确做法是按页对齐写。另一个问题是写周期时间。EEPROM 写完后需要 5ms 左右的内部擦写时间这期间不响应任何 I2C 命令。如果你连续写第二次会 NACK。解决办法是写完后延时或者用 ACK 轮询。在 OpenHarmony 里你可以封装一个eeprom_write函数每次写一页然后mdelay(10)。读的时候先写寄存器地址再读数据。注意寄存器地址是 1 字节还是 2 字节取决于 EEPROM 容量。AT24C02 是 1 字节AT24C256 是 2 字节。设备树里不需要配这个但驱动代码里要区分。4.2 触摸屏 GT911 I2C 通信失败复位和地址切换GT911 是常见的 I2C 触摸屏控制器它的坑在于地址不固定。上电时如果 INT 引脚在复位释放时为低地址是 0x5D如果为高地址是 0x14。很多开发者硬件设计时没注意这个导致地址对不上。解决办法是在驱动初始化时先控制复位和 INT 引脚把地址切到期望值。然后在设备树里写对应的reg。另一个问题是 GT911 需要固件配置。如果 I2C 能通但触摸没反应可能是固件没加载。你需要通过 I2C 写入配置寄存器。这部分在 OpenHarmony 的 HDF 触摸屏驱动里有参考实现但不同板子配置不同要自己调。4.3 总线死锁一个设备拉死整条总线I2C 总线死锁的典型现象是 SCL 或 SDA 被某个设备一直拉低。原因可能是从机在传输过程中复位或者电源不稳导致状态机跑飞。解决办法是发送 9 个时钟脉冲让从机把剩余数据移完然后发停止条件。在 OpenHarmony 里你可以在控制器驱动里实现一个i2c_recover_bus函数用 GPIO 模拟时钟。但注意这需要把 SCL 和 SDA 临时切回 GPIO 模式发完脉冲再切回 I2C 模式。预防死锁的方法确保所有从机电源稳定上电时序正确在设备树里给 I2C 控制器加i2c-bus-recovery属性避免在中断上下文里做大量 I2C 传输。4.4 常见问题速查表问题现象解决地址错误NACK无应答确认 7 位地址检查读写位上拉缺失波形上升沿缓慢加 4.7k 上拉电阻时钟太快数据错乱降到 100kHz页写跨页数据回卷按页对齐写总线死锁SCL/SDA 被拉低发 9 个时钟脉冲恢复设备树不匹配驱动加载失败检查deviceMatchAttr引脚复用错误无波形检查pinctrl配置电源未上无应答测从机 VCC5. 从裸机到 OpenHarmonyI2C 调试思维的转变5.1 裸机 I2C 和 OpenHarmony I2C 的本质区别裸机写 I2C你直接操作寄存器知道每一步在干什么。OpenHarmony 写 I2C你面对的是 HDF 接口和设备树中间隔了好几层。这带来的好处是移植方便坏处是出问题时不好定位。我的经验是先用裸机或 Linux 的i2c-tools确认硬件没问题再上 OpenHarmony。如果裸机都读不到那肯定是硬件或从机问题跟 OpenHarmony 无关。如果裸机能读OpenHarmony 读不到那就是设备树或 HDF 配置问题。另一个区别是并发。裸机里你通常在一个任务里用 I2C不会有多线程竞争。OpenHarmony 里可能有多个驱动同时访问同一条 I2C 总线。HDF 的 I2C 接口内部有互斥锁但如果你在用户态和内核态同时访问还是可能出问题。所以尽量把 I2C 访问集中在一个驱动里。5.2 用 HDF 日志定位 I2C 问题的技巧OpenHarmony 的 HDF 日志可以通过hilog查看。你可以设置日志级别为 DEBUG过滤I2C或HDF_I2C。关键日志包括控制器初始化、设备匹配、传输开始、传输完成、错误码。如果传输超时日志里会有timeout和当前寄存器值。如果 NACK会有nack和从机地址。这些信息比你自己打印更准确。还有一个技巧在I2cTransfer前后加日志打印msgs的地址、长度、标志。这样你能确认传给驱动的内容是否正确。很多时候问题就出在len或flags写错了。5.3 个人实操心得先软后硬先简后繁我调 I2C 的习惯是先用一个最简单的从机比如 EEPROM验证总线通不通。如果 EEPROM 能读写说明控制器、设备树、HDF 配置都没问题。然后再上复杂的从机比如触摸屏、传感器。如果 EEPROM 都不通就别折腾复杂设备了先查硬件。查硬件时先测电压再测波形最后换从机。换从机是最快的排除法如果换一个同型号的从机就好了说明原来的坏了。还有设备树改完后一定要重新编译并更新。OpenHarmony 的设备树是打包在 boot 分区里的不是单独的文件。你改了 dts 但没重新烧录等于没改。这个坑我踩过好几次。最后再分享一个小技巧如果你怀疑是 I2C 时钟频率问题可以在设备树里把clock-frequency改成 1000010kHz慢到极致。如果 10kHz 能通说明是时序问题再逐步提高。这个方法虽然笨但很有效。