1. I2C 总线不是“接上线就能跑”的黑盒子——它是一条需要被读懂、被尊重、被亲手调通的物理信道
I2C,全称 Inter-Integrated Circuit,中文常叫“集成电路总线”,但千万别被这个名字骗了——它既不“集成”得有多智能,也不“电路”得有多简单。在 OpenHarmony 系统开发中,尤其是基于 RK3568 这类 SoC 的嵌入式场景里,I2C 是连接 OLED 屏、温湿度传感器(如 BH1750)、EEPROM、音频编解码器、摄像头模组(比如 OV5695)甚至多路复用开关(TCA9548A)最常用、也最容易“哑火”的通信通道。我带过三届 OpenHarmony 开发训练营,每期都有至少 60% 的学员卡在“OLED 不亮”“传感器读不到数据”“设备树改了但 /dev/i2c-* 没生成”这类问题上,而背后十有八九是 I2C 链路没真正打通。这不是驱动写错了,而是你还没真正理解:I2C 不是软件协议栈里一个抽象接口,它是两根物理线(SCL 时钟线 + SDA 数据线)+ 上拉电阻 + 主从设备电气特性 + 时序容忍度 + 设备树描述 + 内核驱动匹配 + 用户态访问权限,五层叠加的系统工程。比如你用野火 RK3568 开发板接 0.9 寸 SSD1306 OLED,看似只插四根线(VCC/GND/SCL/SDA),但一旦出现“i2c hid该设备找不到足够资源可以使用(代码 12)”,那大概率不是代码 bug,而是 SDA 线被其他外设(比如某个 GPIO 被误配置为开漏输出并拉低)偷偷占用了;又比如调试 OV5695 摄像头时发现 i2c read timeout,查了半天寄存器,最后发现是 RK3568 的 I2C0 控制器在设备树里被错误地配置成了 400kHz 模式,而 OV5695 的 sensor 初始化要求必须用 100kHz 标准模式才能握手成功——这种细节,文档不会明说,芯片手册里藏在第 37 页的时序表 footnote 里。所以这篇内容不讲“I2C 协议原理图”,不堆砌“起始位、地址位、应答位、停止位”的教科书定义,而是直接带你站在 RK3568 + OpenHarmony 的真实开发台前,用万用表量电压、用逻辑分析仪抓波形、用 dmesg 看内核日志、用 i2cdetect 扫设备、用 i2cget/i2cset 摸寄存器、用设备树打补丁、用 HDF 接口写驱动——所有操作都基于实测环境,所有参数都标清楚来源,所有坑都告诉你怎么绕过去。适合正在 RK3568 板子上跑 OpenHarmony、手边有 SSD1306 OLED 或 BH1750 传感器、正被“i2c 通信失败”折磨得睡不着觉的开发者。别急着写代码,先让硬件说话。
2. I2C 总线设计与排障思路:从“物理层”到“用户态”的五级穿透法
2.1 为什么不能跳过物理层直接看驱动?——I2C 是唯一一条“线一断,全盘崩”的总线
很多刚从 Linux 或 STM32 转过来的开发者有个误区:认为 I2C 和 UART 一样,只要设备树配对、驱动加载、节点生成,剩下的就是 read/write 系统调用。这是危险的错觉。UART 是点对点异步串行,哪怕波特率错一点,也能靠起始位重新同步;SPI 是主从严格同步,MOSI/MISO 各走各的,单线故障不影响另一条;唯独 I2C,SCL 和 SDA 是双向开漏线,任何时刻都可能被任意设备(主或从)拉低,整个总线状态由所有挂载设备共同决定。这意味着:
- 一根线虚焊,整条总线上所有设备都无法响应;
- 一个从设备内部短路(比如 OLED 模块电源滤波电容击穿),会把 SDA 拉死在低电平,导致 i2cdetect 扫不到任何设备;
- 两个设备地址冲突(比如两个 BH1750 都用了 0x23 地址),主设备发地址后两个从机同时应答,SCL 被锁死,总线僵死;
- 上拉电阻选错(比如用 10kΩ 在 400kHz 下),上升沿过缓,被内核判定为“时序违规”而拒绝通信。
所以我总结出 I2C 排障的“五级穿透法”,必须从下往上逐层验证,跳过任何一级都可能白忙活:
- 物理层(Physical Layer):用万用表测 SCL/SDA 对地电压(空闲时应为 VCC,约 3.3V),用示波器或逻辑分析仪看波形是否干净、上升沿是否陡峭、时钟周期是否稳定;
- 电气层(Electrical Layer):确认上拉电阻阻值(标准模式 100kHz 推荐 4.7kΩ,快速模式 400kHz 推荐 2.2kΩ,需结合总线电容计算,RK3568 板载总线电容实测约 80pF);
- 内核层(Kernel Layer):dmesg | grep i2c 看控制器是否 probe 成功,ls /sys/bus/i2c/devices/ 看总线节点是否存在,cat /sys/bus/i2c/devices/i2c-0/name 确认总线名称;
- 设备树层(DTS Layer):检查 &i2c0 节点是否 enabled,clock-frequency 是否匹配从设备要求,pinctrl 是否正确绑定(RK3568 的 I2C0 默认引脚是 GPIO0_A0/SCL 和 GPIO0_A1/SDA,但某些底板会重映射);
- 用户态层(Userspace Layer):i2cdetect -l 列出总线,i2cdetect -y 0 扫描设备地址,i2cget -y 0 0x23 0x00 w 读寄存器,确认数据可读可写。
这个顺序不是教条,而是血泪教训。去年帮一个团队调 OV5695,他们花三天改 HDF 驱动,最后发现是底板上 I2C1 的 SDA 线和 USB PHY 的某个 debug 引脚共用了一个 PCB 走线,USB 插拔时干扰了 SDA 电平——这根本不是软件问题,是 layout 问题,必须从物理层开始查。
2.2 RK3568 的 I2C 控制器特性:别拿 STM32 的经验硬套
RK3568 有 5 个 I2C 控制器(I2C0~I2C4),但实际可用的只有 I2C0、I2C1、I2C2,I2C3 和 I2C4 在官方 SDK 中默认禁用且无 pinmux 支持。每个控制器特性差异极大,直接决定你能不能用、怎么用:
- I2C0:支持标准模式(100kHz)和快速模式(400kHz),最大传输速率 400kHz,用于连接高速外设如 OV5695(sensor 初始化必须 100kHz,但寄存器配置后可切 400kHz);
- I2C1:仅支持标准模式(100kHz),无快速模式,但稳定性极高,适合接 EEPROM、RTC、温湿度传感器等低速设备;
- I2C2:支持标准/快速模式,但 RK3568 的 I2C2 在 OpenHarmony 3.2 及之前版本存在 clock-gating bug,需在设备树中显式添加 clocks = <&cru CLK_I2C2>,否则 probe 失败。
更关键的是pinmux(引脚复用)。RK3568 的 I2C 引脚不是固定死的,而是通过 CRU(Clock and Reset Unit)和 PMU(Power Management Unit)动态配置。比如 I2C0 默认用 GPIO0_A0/A1,但如果你的底板把 I2C0 重映射到了 GPIO2_B0/B1(常见于某些定制板),那么设备树里 pinctrl_i2c0_default 就必须指向新的 pin group,否则即使你写了 &i2c0 { status = "okay"; },内核也会因为找不到对应引脚而静默失败。我在野火 RK3568 开发板上实测过:官方 SDK 的 device/board/rockchip/rk3568/rockchip_rk3568.dtsi 里 I2C0 的 pinctrl 是正确的,但如果你用的是第三方底板,必须去查该底板的 schematic,找到实际焊接的 I2C 引脚,再对照 RK3568 TRM(Technical Reference Manual)第 12 章 “Pinmux Configuration” 找到对应的 pin group name,比如 GPIO2_B0 对应的 group 是 i2c2_scl_m1,而不是 i2c0_scl。这个细节,OpenHarmony 官方文档里一笔带过,但却是 70% 的“设备树改了但没反应”问题的根源。
2.3 OpenHarmony 下 I2C 的三层访问模型:HDF、内核 API、用户态工具链
在 OpenHarmony 中,I2C 访问不是单一路径,而是分三层,每层适用场景不同,选错就事倍功半:
- HDF(Hardware Driver Foundation)层:这是 OpenHarmony 官方推荐的驱动开发模型,用于编写长期运行的设备驱动(如 OLED 显示驱动、OV5695 sensor 驱动)。它通过 HDF 框架注册 I2C Host 控制器,再由具体设备驱动调用 I2C Bus Interface(如 I2cRead、I2cWrite)完成通信。优点是稳定、可热插拔、符合 OpenHarmony 架构;缺点是开发门槛高,需理解 HDF 框架、服务管理、配置文件(hcs)等。例如 SSD1306 OLED 驱动,必须在 hcs 文件中声明 i2c_bus_num = 0, slave_addr = 0x3C,然后在驱动 init 函数里调用 I2cOpen(0) 获取句柄。
- 内核 API 层:即 Linux 内核原生的 i2c-dev 接口,通过 /dev/i2c-X 文件节点暴露给用户态。OpenHarmony 默认启用此功能(CONFIG_I2C_CHARDEV=y),所以你可以直接用 open("/dev/i2c-0", O_RDWR) + ioctl() 操作。这是调试阶段最灵活的方式,适合快速验证硬件连通性、读写寄存器、dump EEPROM 数据。比如调试 BH1750,用 i2cget -y 0 0x23 0x10 b 读光照值,比写完整 HDF 驱动快十倍。
- 用户态工具链层:指 i2c-tools 包提供的命令行工具(i2cdetect, i2cget, i2cset)。它们是内核 API 的封装,无需编程,纯命令行操作。OpenHarmony 的 buildroot 配置中默认不包含 i2c-tools,需手动在 vendor/rockchip/rk3568/config/defconfig 里添加 CONFIG_PACKAGE_I2C_TOOLS=y,并重新编译固件。这是新手入门第一课,也是老手日常排查的瑞士军刀。
三者关系是:HDF 驱动最终也调用内核 API,内核 API 依赖 i2c-dev 框架,而 i2c-tools 是它的用户态前端。所以排障时,永远从最底层的 i2c-tools 开始——如果 i2cdetect 都扫不到设备,HDF 驱动写得再漂亮也没用。
3. 实操全流程:从硬件接线到 OLED 显示,手把手打通 RK3568 + OpenHarmony 的 I2C 链路
3.1 硬件准备与接线规范:0.9 寸 OLED 的“四线接法”陷阱
我们以最常见的 0.9 寸 SSD1306 OLED(I2C 接口)为例,这是 OpenHarmony 新手最常选的入门外设。但“四线接法”(VCC/GND/SCL/SDA)背后全是坑:
- VCC 电压:SSD1306 标称 3.3V,但实测在 3.0V~3.6V 均可工作。RK3568 的 GPIO 供电是 3.3V,但注意——有些 OLED 模块板载有 3.3V LDO,输入标称 5V,若你直接接 RK3568 的 3.3V,LDO 可能不启动,导致 OLED 不亮。务必用万用表测模块背面的 VCC 焊盘电压,确认是直连还是经 LDO。
- GND 必须共地:这是最容易被忽略的点。RK3568 开发板的 GND 和 OLED 模块的 GND 必须用导线可靠短接,不能只靠 USB 线或外壳间接连接。我见过太多案例:USB 供电时 OLED 亮,换 DC 电源就不亮,就是因为 DC 电源的地没和 RK3568 的地连通。
- SCL/SDA 上拉电阻:RK3568 板载 I2C 总线已内置 4.7kΩ 上拉电阻(I2C0),但 OLED 模块自身也可能带 4.7kΩ 上拉。两个上拉并联后等效电阻约 2.35kΩ,在 100kHz 下没问题,但在 400kHz 下可能导致上升沿过快、噪声增大。实测建议:若用 I2C0(支持 400kHz),移除 OLED 模块上的上拉电阻(通常丝印 R1/R2),只保留 RK3568 板载上拉;若用 I2C1(仅 100kHz),可保留双方上拉。
- 地址选择跳线:SSD1306 默认地址是 0x3C(SA0=0),但有些模块把 SA0 接到 VCC,地址变为 0x3D。用 i2cdetect -y 0 扫描,若看到 0x3D 而非 0x3C,说明 SA0 被拉高,需检查模块跳线或飞线修改。
接线完成后,第一步不是烧固件,而是用万用表测:
- SCL 和 SDA 对地电压,空闲时应为 3.3V(确认上拉有效);
- SCL 和 SDA 之间电阻,应为无穷大(排除短路);
- OLED 模块 VCC 对 GND 电压,应为 3.3V(确认供电正常)。
这三步做完,硬件连通性就算过了 60%。
3.2 设备树(DTS)精准配置:RK3568 的 I2C0 使能与引脚绑定
OpenHarmony 的设备树是 I2C 能否工作的“宪法”。RK3568 的 DTS 分为两层:
- SoC 层:arch/arm64/boot/dts/rockchip/rk3568.dtsi,定义 I2C 控制器硬件资源(寄存器地址、中断号、时钟源);
- 板级层:vendor/rockchip/rk3568/rockchip_rk3568.dts,定义具体引脚复用、上拉配置、设备挂载。
我们要改的是板级 DTS。以野火 RK3568 为例,打开 vendor/rockchip/rk3568/rockchip_rk3568.dts,找到 &i2c0 节点:
&i2c0 { status = "okay"; clock-frequency = <100000>; // 标准模式 100kHz,OV5695 初始化必须用此值 pinctrl-names = "default"; pinctrl-0 = <&i2c0_xfer>; #address-cells = <1>; #size-cells = <0>; oled@3c { compatible = "solomon,ssd1306"; reg = <0x3c>; status = "okay"; }; };关键点解析:
status = "okay":必须有,否则控制器被禁用;clock-frequency = <100000>:单位是 Hz,不是 kHz。这里设 100000(100kHz),而非 400000(400kHz),因为 SSD1306 初始化序列要求低速握手;pinctrl-0 = <&i2c0_xfer>:指向引脚配置节点,必须在同一个 DTS 文件里定义,例如:
&pinctrl { i2c0_xfer: i2c0-xfer { rockchip,pins = <0 RK_PA0 1 &pcfg_pull_up>, // SCL, GPIO0_A0 <0 RK_PA1 1 &pcfg_pull_up>; // SDA, GPIO0_A1 }; };RK_PA0是 Rockchip 的 pin name,对应 GPIO0_A0;1表示功能复用为 I2C0_SCLK;&pcfg_pull_up是上拉配置宏,定义在 arch/arm64/boot/dts/rockchip/pinconf.h 里。
oled@3c:子节点,reg = <0x3c>指定从设备地址,compatible字符串必须和 HDF 驱动的 .hcs 文件中声明的一致,否则驱动无法匹配。
改完 DTS,必须重新编译 dtb:
./build.sh --product-name rk3568 --build-target dts编译后生成的 dtb 文件在 out/ohos-arm64/obj/device/rockchip/rk3568/rk3568.dtb,烧录到板子的 boot 分区。注意:DTS 错误不会导致编译失败,但会导致 dmesg 里出现 "i2c i2c-0: Failed to register bus" 或 "i2c i2c-0: can't add device"。
3.3 内核日志诊断:dmesg 是 I2C 排障的第一现场
烧录新固件后,串口登录,第一时间执行:
dmesg | grep -i i2c正常输出应类似:
[ 1.234567] i2c i2c-0: i2c@fe5a0000: designware i2c adapter [ 1.234589] i2c i2c-0: i2c@fe5a0000: registered with bus number 0 [ 1.234612] i2c i2c-0: i2c@fe5a0000: using standard mode (100 kHz) [ 1.234634] i2c i2c-0: i2c@fe5a0000: bus is ready这表示 I2C0 控制器已成功 probe。如果看到:
i2c i2c-0: i2c@fe5a0000: failed to get clock:说明设备树里缺 clocks 属性,需添加clocks = <&cru CLK_I2C0>;;i2c i2c-0: i2c@fe5a0000: could not find pinctrl node:pinctrl 名字写错或未定义;i2c i2c-0: i2c@fe5a0000: no child devices found:子节点(如 oled@3c)语法错误或 compatible 不匹配。
更隐蔽的问题是时钟频率不匹配。比如你把 clock-frequency 设成 400000,但 SSD1306 初始化时序要求 100kHz,内核会尝试以 400kHz 发送 START 信号,但 OLED 无法识别,导致总线超时。此时 dmesg 会出现:
[ 12.345678] i2c i2c-0: i2c@fe5a0000: timeout waiting for bus ready [ 12.345690] i2c i2c-0: i2c@fe5a0000: controller timed out解决方案:先用 100kHz 初始化 OLED,待初始化完成后,再用 i2cset 切换到 400kHz 提升刷新率——这是 SSD1306 的标准操作流程,不是 bug。
3.4 用户态工具实战:i2cdetect/i2cget/i2cset 三剑客
确保 dmesg 正常后,进入用户态验证:
- 列出所有 I2C 总线:
i2cdetect -l # 输出应有: # i2c-0 i2c 3568-i2c I2C adapter- 扫描设备地址:
i2cdetect -y 0 # 输出一个 16x16 表格,若 OLED 正常,应在 0x3c 或 0x3d 位置看到 "--" 变成 "3c" # 注意:-y 参数表示“跳过用户确认”,生产环境必须加,否则会卡住如果表格全空(全是 "--"),说明:
- 硬件没接好(电压/地/上拉);
- 地址不对(SA0 接法);
- 设备树里 status = "disabled"。
- 读写寄存器验证:SSD1306 的基本寄存器是 0x00(命令模式)和 0x40(数据模式)。先写命令清屏:
# 发送 0xAE (display off) 命令 i2cset -y 0 0x3c 0x00 0xae b # 发送 0xAF (display on) 命令 i2cset -y 0 0x3c 0x00 0xaf b如果 OLED 闪一下,说明通信成功。再读一个状态寄存器(SSD1306 没有标准状态寄存器,但可读 0x00 看是否返回 0x00):
i2cget -y 0 0x3c 0x00 b # 返回 0x00 表示读成功提示:i2cset/i2cget 的
-b参数表示 byte 模式(8-bit),-w表示 word 模式(16-bit),SSD1306 全部用 byte。0x3c是设备地址,0x00是寄存器地址,0xae是写入值。
3.5 HDF 驱动开发:从零写一个 SSD1306 OLED 驱动
当用户态验证通过,就可以写正式驱动了。OpenHarmony 的 HDF I2C 驱动模板如下:
- 创建驱动目录:vendor/rockchip/rk3568/hdf/oled/
- 编写 driver.c:
#include "hdf_log.h" #include "hdf_base.h" #include "i2c_if.h" #define OLED_ADDR 0x3C #define CMD_MODE 0x00 #define DATA_MODE 0x40 static int32_t OledInit(struct HdfDeviceObject *device) { struct I2cDevHandle *handle = NULL; handle = I2cOpen(0); // 打开 I2C0 if (handle == NULL) { HDF_LOGE("I2cOpen failed"); return HDF_FAILURE; } // 发送初始化序列(省略具体命令,参考 SSD1306 datasheet) uint8_t initCmd[] = {0xAE, 0xD5, 0x80, 0xA8, 0x3F, ...}; for (int i = 0; i < sizeof(initCmd); i++) { if (I2cWrite(handle, OLED_ADDR, &initCmd[i], 1, CMD_MODE) != HDF_SUCCESS) { HDF_LOGE("I2cWrite init cmd[%d] failed", i); I2cClose(handle); return HDF_FAILURE; } } I2cClose(handle); return HDF_SUCCESS; } // HDF 驱动框架必需函数 struct HdfDriverEntry g_OledDriverEntry = { .moduleVersion = 1, .Bind = NULL, .Init = OledInit, .Release = NULL, .moduleName = "HDF_OLED", }; HDF_INIT(g_OledDriverEntry);- 编写 hcs 配置文件(oled_config.hcs):
root { oled :: device { device0 :: deviceNode { policy = 1; // 创建 dev node priority = 100; permission = 0644; moduleName = "HDF_OLED"; deviceMatchAttr = "ssd1306_oled"; } } }- 在 Kconfig 中添加配置:vendor/rockchip/rk3568/Kconfig,添加
config HDF_DRIVERS_OLED; - 在 BUILD.gn 中添加编译规则:vendor/rockchip/rk3568/BUILD.gn,添加
ohos_shared_library("hdf_oled")。
编译烧录后,ls /dev/应看到oled设备节点。此时你就可以用 HDF 接口调用OledDisplay()函数了。整个过程,核心是I2cOpen(0)和I2cWrite(),它们最终调用内核的 i2c-dev API,所以必须确保前面的用户态验证已通过。
4. 常见问题与排查技巧实录:那些让你凌晨三点还在抓头发的 I2C 故障
4.1 “i2cdetect 扫不到设备”——物理层与电气层的联合诊断表
这是最高频问题,按优先级排序排查:
| 现象 | 可能原因 | 诊断方法 | 解决方案 |
|---|---|---|---|
i2cdetect -y 0全屏 "--" | SDA 或 SCL 线断路/虚焊 | 万用表测 SDA/SCL 对地电压,应为 3.3V;若为 0V,查线路 | 重新焊接或更换杜邦线 |
i2cdetect -y 0显示 "UU" | 设备地址被占用或驱动已占用 | cat /sys/bus/i2c/devices/i2c-0/name看总线名;ls /sys/bus/i2c/devices/看是否有 0-003c 目录 | 卸载冲突驱动:rmmod ssd1306(如果有) |
i2cdetect -y 0显示部分地址(如 0x50)但缺 0x3c | OLED 模块 SA0 接法错误 | 用万用表测 SA0 引脚对地电压:0V=0x3c,3.3V=0x3d | 修改跳线或飞线接地 |
i2cdetect -y 0扫描卡死(光标不动) | 总线被锁死(某设备 SDA 拉低) | 逻辑分析仪看 SDA 是否恒低;拔掉所有从设备,逐个接入 | 更换故障 OLED 模块;检查电源纹波 |
注意:
i2cdetect卡死时,不要强行 Ctrl+C,否则可能损坏 I2C 控制器。正确做法是断电重启。
4.2 “i2cget/i2cset 报错:Connection timed out”——时序与地址的双重校验
错误信息Error: Connection timed out表明 I2C 控制器发出了 START 信号,但没收到从设备的 ACK。原因不是软件,而是硬件握手失败:
- 地址错误:SSD1306 有 0x3C 和 0x3D 两个地址,BH1750 有 0x23 和 0x27,OV5695 sensor 地址是 0x3C(但 ISP 地址是 0x6C)。用逻辑分析仪抓波形,看控制器发出的地址字节是否和模块实际地址一致。
- 时序违规:RK3568 的 I2C0 在 400kHz 下,SCL 高电平时间最小为 0.6μs,低电平最小为 1.3μs。若总线电容过大(>100pF),上升沿变缓,内核判定超时。实测方法:用示波器测 SCL 上升时间,若 >300ns,需减小上拉电阻至 2.2kΩ。
- 电源不稳:OLED 模块在初始化时电流突增(可达 20mA),若电源滤波不足,VCC 瞬间跌落,导致 OLED 无法响应。解决方案:在 OLED VCC 引脚就近加 100μF 钽电容。
4.3 “dmesg 显示 i2c i2c-0: can't add device”——设备树兼容性字符串陷阱
这个错误意味着内核找到了设备节点(oled@3c),但找不到匹配的驱动。根本原因是compatible = "solomon,ssd1306"和 HDF 驱动的 hcs 文件中deviceMatchAttr不一致。OpenHarmony 的匹配规则是:
- 内核用
compatible字符串查找 of_match_table; - HDF 用
deviceMatchAttr查找 hcs 配置; - 两者必须完全一致(包括大小写、下划线)。
常见错误:
- DTS 里写
compatible = "ssd1306",但 hcs 里写deviceMatchAttr = "ssd1306_oled"; - DTS 里写
compatible = "Solomon,ssd1306"(首字母大写),但 hcs 里是小写。
解决方案:统一用小写加下划线,如compatible = "solomon,ssd1306"和deviceMatchAttr = "solomon_ssd1306"。
4.4 “RK3568 调试 OV5695 时 i2c read timeout”——摄像头特有的初始化时序约束
OV5695 的 I2C 通信有两个致命约束:
- 初始化必须用 100kHz:datasheet 明确要求 “I2C clock frequency during initialization shall be 100kHz”。若设备树设成 400kHz,sensor 会静默失败,dmesg 只显示 timeout,不报具体原因。
- RESET 引脚必须硬件复位:OV5695 的 RESET 引脚(通常接 RK3568 的 GPIO0_B0)必须在 I2C 初始化前拉低 1ms,再拉高,否则 sensor 不进入 I2C 模式。这需要在设备树里添加 reset-gpios:
ov5695@3c { compatible = "ovti,ov5695"; reg = <0x3c>; reset-gpios = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>; ... };然后在 HDF 驱动 init 函数里调用GpioSetDir()和GpioWrite()控制 RESET。漏掉这一步,I2C 就永远读不到 sensor。
4.5 “i2c hid该设备找不到足够资源可以使用(代码 12)”——Windows 风格错误码在 OpenHarmony 的映射
这个错误码(12)源自 Windows 的 CM_ErrorCode,但在 OpenHarmony/Linux 内核中,它对应ENOMEM(内存不足)。I2C 场景下,它通常表示:
- IRQ 资源冲突:I2C 控制器的中断号被其他设备(如 USB PHY)占用。查
cat /proc/interrupts,看 IRQ 45(RK3568 I2C0 默认 IRQ)是否被多个设备共享; - DMA buffer 不足:RK3568 的 I2C 控制器支持 DMA,但 OpenHarmony 默认关闭。若你启用了 DMA(在 DTS 里加
dmas = <&dmac 12 1>),而系统 DMA buffer 不够,就会报 ENOMEM。解决方案:在 kernel cmdline 加dma_alloc_coherent=8M。
实操心得:遇到代码 12,第一反应不是查 I2C,而是
cat /proc/interrupts和dmesg | grep -i dma。