尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

OpenHarmony I2C实战:RK3568设备树配置与排障全链路

发布时间:2026/9/26 12:18:04

资讯中心
01
ARTICLE

OpenHarmony I2C实战:RK3568设备树配置与排障全链路

OpenHarmony I2C实战:RK3568设备树配置与排障全链路
I2C 这东西刚接触嵌入式的人觉得它简单——两根线一根时钟一根数据挂几个从设备就完事了。但真到了 OpenHarmony 上跑起来尤其是 RK3568 这类平台你会发现事情没那么轻松设备树里 reg 地址写错一位总线直接扫不到设备上拉电阻阻值选大了波形上升沿软得像面条多设备共用一条总线某个从机死锁把 SDA 拉低不放整条总线全瘫。我在实际项目里被 I2C 折腾过不少回从传感器读取间歇性失败到 EEPROM 写入丢数据每一次排查都逼着我把时序图翻出来重新看。这篇内容就围绕 OpenHarmony 系统下的 I2C 实战展开从协议底层逻辑讲到设备树配置再到排障的完整链路适合正在做 OpenHarmony 驱动开发、或者被 I2C 通信问题卡住的同行参考。1. 先搞清楚 I2C 到底在两根线上跑了什么1.1 起始条件、地址帧与应答一次完整通信的拆解很多人调 I2C 出问题根源在于对时序的理解停留在“知道有起始和停止”这个层面。真正排障时你需要能对着逻辑分析仪的波形逐位判断问题出在哪个阶段。I2C 通信的物理层只有两根线SCL串行时钟和 SDA串行数据。两根线都是开漏输出结构这意味着任何设备都只能把线拉低不能主动拉高——高电平靠上拉电阻把线“拽”上去。这个结构决定了 I2C 天生支持多主多从的总线仲裁但也埋下了上拉电阻选型这个坑。一次典型的 I2C 传输流程是这样的起始条件Start主机在 SCL 保持高电平时把 SDA 从高拉低。这个“高拉低”的动作就是起始信号所有挂在总线上的从机都会注意到。地址帧主机发送 7 位从机地址加 1 位读写方向位0 写1 读共 8 位。地址从高位到低位依次在 SCL 的每个时钟周期送出。应答位ACK/NACK第 9 个时钟周期主机释放 SDA被寻址的从机如果存在且准备好就把 SDA 拉低表示应答ACK如果没有从机响应或从机忙SDA 保持高电平就是非应答NACK。数据传输每个字节 8 位后面跟 1 位应答循环进行。停止条件Stop主机在 SCL 高电平时把 SDA 从低释放到高。注意起始和停止条件都发生在 SCL 为高电平期间这是 I2C 协议里唯一允许 SDA 在 SCL 高电平期间变化的情况。数据位传输时SDA 必须在 SCL 低电平期间改变在 SCL 高电平期间保持稳定。我在用逻辑分析仪抓 RK3568 的 I2C 波形时最常看到的异常就是起始条件之后地址帧发出去第 9 个时钟没有 ACK。这时候基本可以断定三种可能从机地址写错了、从机供电没起来、或者从机根本没焊好。用示波器量一下从机 VCC 引脚往往能快速排除后两种。1.2 时钟拉伸与总线仲裁多设备场景下的隐形陷阱时钟拉伸Clock Stretching是 I2C 协议里一个容易被忽略但实际很常见的机制。从机如果处理不过来可以在应答位之后把 SCL 拉低强制主机等待直到从机释放 SCL。这个机制本身是好的但在 OpenHarmony 的 I2C 控制器驱动里如果超时配置不合理时钟拉伸可能导致传输超时失败。RK3568 的 I2C 控制器支持时钟拉伸但驱动层有一个timeout参数控制单次传输的最大等待时间。默认值在某些场景下偏短比如你挂了一个 EEPROM写入操作内部需要 5ms 的擦写周期如果驱动超时设成 1ms写入就会报-ETIMEDOUT。我一般会在设备树里把超时适当调大具体值根据从机手册里标称的最大处理时间来定。总线仲裁则是多主机场景下的机制。当两个主机同时发起传输它们会逐位比较 SDA 上的电平谁先发出高电平而总线上实际是低电平谁就失去仲裁自动转为从机模式。这个机制在单主机系统里用不到但理解它有助于你明白为什么 I2C 的 SDA 和 SCL 必须用开漏结构。1.3 标准模式、快速模式与高速模式速率选择背后的取舍I2C 协议定义了多种速率等级模式最高速率典型上拉电阻适用场景标准模式100 kbps4.7kΩ~10kΩ低速传感器、EEPROM快速模式400 kbps2.2kΩ~4.7kΩ大多数传感器、触摸屏快速模式1 Mbps1kΩ~2.2kΩ高速 ADC、IMU高速模式3.4 Mbps特殊驱动高速数据采集速率越高上拉电阻要越小因为总线电容和上拉电阻构成 RC 电路上升时间与 R×C 成正比。RK3568 的 I2C 控制器默认跑 100kHz但你可以通过设备树里的clock-frequency属性改成 400kHz。我实测下来GT911 触摸屏在 400kHz 下工作稳定但某些国产温湿度传感器在 400kHz 下会偶发 NACK降到 100kHz 就正常——这种时候不要硬撑降速是最省事的方案。2. OpenHarmony 下 I2C 的设备树配置与驱动框架2.1 RK3568 设备树里 I2C 节点的关键属性OpenHarmony 在 RK3568 上的 I2C 配置核心在设备树文件里。以rk3568.dtsi为基础板级文件里覆盖具体参数。一个典型的 I2C 控制器节点长这样i2c1: i2cfe5a0000 { compatible rockchip,rk3568-i2c, rockchip,rk3399-i2c; reg 0x0 0xfe5a0000 0x0 0x1000; interrupts GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH; clocks cru CLK_I2C1, cru PCLK_I2C1; clock-names i2c, pclk; pinctrl-names default; pinctrl-0 i2c1_xfer; #address-cells 1; #size-cells 0; status okay; clock-frequency 400000; };这里有几个属性值得展开说compatible匹配驱动用的。rockchip,rk3568-i2c是具体型号rockchip,rk3399-i2c是兼容回退。OpenHarmony 的 I2C 驱动会按这个顺序匹配。reg控制器寄存器的物理地址和长度。这个地址不能写错写错了驱动 probe 会直接失败内核日志里能看到ioremap failed。clock-frequency总线速率单位 Hz。不写的话默认 100kHz。pinctrl-0引脚复用配置。RK3568 的 I2C1 可以复用到不同的物理引脚组这个引用决定了用哪组引脚。挂载在 I2C 总线下的从设备作为子节点写在控制器节点内部i2c1 { status okay; clock-frequency 400000; gt911: touchscreen5d { compatible goodix,gt911; reg 0x5d; interrupt-parent gpio0; interrupts RK_PB5 IRQ_TYPE_EDGE_FALLING; reset-gpios gpio0 RK_PB6 GPIO_ACTIVE_LOW; irq-gpios gpio0 RK_PB5 GPIO_ACTIVE_HIGH; }; };从设备节点的reg属性就是 I2C 从机地址。GT911 的地址可以是 0x5d 或 0x14取决于上电时 INT 引脚的电平状态。如果你在设备树里写了 0x5d但硬件上电时序导致 GT911 实际地址是 0x14那驱动 probe 就会失败日志里报no such device。2.2 OpenHarmony I2C 驱动框架的分层逻辑OpenHarmony 的 I2C 驱动框架分三层I2C 核心层i2c_core提供总线注册、设备注册、传输接口等通用能力。这一层不关心具体硬件。I2C 控制器驱动i2c_controller对接具体 SoC 的 I2C 控制器实现transfer回调。RK3568 的控制器驱动在drivers/hdf/khdf/platform/rockchip/i2c目录下。I2C 设备驱动i2c_client具体从设备的驱动比如 GT911 触摸屏驱动、EEPROM 驱动。这一层通过I2cTransfer接口和控制器层交互。这个分层的好处是你换一个 SoC只需要改控制器驱动上层设备驱动不用动。但实际调试时问题往往出在层与层之间的衔接上——比如设备树里的reg地址和驱动里硬编码的地址不一致或者控制器驱动的transfer实现有 bug 导致时序不对。2.3 从设备地址的确认方法别靠猜用工具扫设备树里写从机地址之前最稳妥的办法是先扫一遍总线确认设备实际响应的地址。OpenHarmony 标准系统里可以用i2cdetect工具如果移植了的话或者自己写一个简单的扫描程序#include stdio.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/i2c.h #include linux/i2c-dev.h int main() { int fd open(/dev/i2c-1, O_RDWR); if (fd 0) { perror(open i2c-1); return -1; } for (int addr 0x03; addr 0x78; addr) { if (ioctl(fd, I2C_SLAVE, addr) 0) continue; char buf; if (read(fd, buf, 1) 1) printf(Device found at 0x%02x\n, addr); } close(fd); return 0; }这段代码在 Linux 用户态下能跑OpenHarmony 标准系统如果保留了/dev/i2c-X设备节点同样适用。扫描时注意有些从设备在未初始化时不会响应比如某些传感器需要先给一个唤醒命令才应答。这种情况下扫描不到不代表设备不在需要结合硬件手册判断。3. I2C 排障的完整链路从现象到根因3.1 第一步永远是确认硬件电压、上拉、焊接我排 I2C 问题的习惯是先不动软件拿万用表和示波器把硬件过一遍。软件层面折腾半天最后发现是上拉电阻没焊这种事我遇到过不止一次。硬件检查清单供电电压从设备 VCC 是否在手册规定范围内。3.3V 器件挂到 5V 总线上轻则不响应重则烧毁。上拉电阻SCL 和 SDA 是否都有上拉阻值是否合适。用万用表量 SCL 对 VCC 的电阻正常应该是上拉电阻的阻值。如果量出来是无穷大说明上拉缺失。焊接质量从设备引脚是否虚焊。用镊子轻轻拨动引脚同时观察波形变化。总线电容挂的设备越多、走线越长总线电容越大上升沿越缓。如果上升时间超过协议规定值标准模式 1000ns快速模式 300ns需要减小上拉电阻或降低速率。提示示波器看 I2C 波形时用双通道同时抓 SCL 和 SDA触发设在 SDA 下降沿起始条件。这样能一眼看出起始、地址、ACK 的完整过程。3.2 逻辑分析仪抓包读懂波形里的每一个异常逻辑分析仪是 I2C 排障的利器。我用的是 Saleae 兼容的分析仪配合开源软件 Sigrok/PulseView能直接解码 I2C 协议把波形翻译成地址、数据、ACK/NACK。常见的波形异常和对应根因波形现象可能根因排查方向起始条件后无 ACK地址错误、从机未供电、从机损坏核对地址、量电压、换器件ACK 后数据位全高从机未驱动 SDA、从机忙检查从机初始化时序SCL 被拉低不释放从机时钟拉伸超时、从机死锁检查从机状态、复位从机SDA 一直被拉低从机死锁、总线短路断电重启、检查焊接波形上升沿过缓上拉电阻过大、总线电容过大减小上拉、缩短走线偶发 NACK时序余量不足、电源纹波降速、加去耦电容我印象最深的一次是 DS18B20 挂在 I2C 总线上DS18B20 其实是 1-Wire 协议但有人会通过 I2C-1-Wire 桥接芯片挂载读取温度时偶尔返回 85°C——这是 DS18B20 的默认上电值说明转换还没完成主机就来读了。这种问题逻辑分析仪一看便知主机发读命令的时机太早从机还没准备好数据。3.3 内核日志与 HDF 日志的联合分析OpenHarmony 的驱动框架是 HDFHardware Driver FoundationI2C 相关的日志会打在 HDF 日志里。排查时先看内核日志dmesg | grep i2c典型的错误信息包括i2c i2c-1: timeout waiting for bus ready总线被拉低不释放检查从机是否死锁。i2c i2c-1: sendbytes: NAK bailout从机 NACK地址或时序问题。rockchip-i2c fe5a0000.i2c: i2c bus error控制器层面错误可能是时钟或引脚配置问题。HDF 日志则通过hilog查看hilog | grep -i i2cHDF 层的日志会显示设备注册、传输调用等信息。如果设备树里配了从设备但驱动没 probeHDF 日志里会缺少对应的设备注册记录这时候要检查compatible字符串是否和驱动匹配。3.4 一个真实案例GT911 触摸屏 I2C 通信失败项目背景RK3568 板子上接 GT911 触摸屏设备树配了 0x5d 地址驱动 probe 失败报gt911: i2c read failed。排查过程量电压GT911 的 VDD 3.3V 正常VDDIO 1.8V 正常。量上拉SCL 和 SDA 对 3.3V 的电阻都是 2.2kΩ正常。逻辑分析仪抓包起始条件后主机发 0x5d 地址第 9 个时钟 SDA 保持高电平——NACK。换地址扫描用扫描程序从 0x03 扫到 0x77发现 0x14 有响应。查 GT911 手册GT911 的 I2C 地址由上电时 INT 引脚电平决定。INT 为低时地址是 0x5dINT 为高时地址是 0x14。查硬件原理图INT 引脚通过 10kΩ 上拉到 1.8V上电时默认高电平所以实际地址是 0x14。修改设备树把reg 0x5d改成reg 0x14重新编译烧录触摸屏正常工作。这个案例的教训是从设备地址不是你想写多少就写多少硬件设计决定了实际地址。设备树里的reg必须和硬件实际地址一致。4. 高频踩坑场景与实操经验4.1 多设备共用总线时的地址冲突与速率妥协一条 I2C 总线上挂多个设备时地址冲突是最常见的问题。7 位地址空间里0x00 是广播地址0x01~0x07 和 0x78~0x7F 是保留地址实际可用地址有限。如果你挂了两片同型号的 EEPROM它们的地址引脚配置必须不同否则就会冲突。速率方面一条总线上所有设备必须支持同一个速率。如果挂了一个 100kHz 的传感器和一个 400kHz 的触摸屏整条总线只能跑 100kHz。我一般会在硬件设计阶段就把高速设备和低速设备分到不同的 I2C 控制器上避免互相拖累。4.2 EEPROM 写入丢数据页写边界与写周期等待EEPROM 的写入有两个坑页写边界EEPROM 内部按页组织比如 24C02 每页 8 字节。如果你从地址 0x06 开始写 4 个字节会写到 0x06、0x07、0x00、0x01——跨页后地址回卷覆盖了前面的数据。正确做法是每次写入不跨页或者从页对齐地址开始写。写周期等待EEPROM 每次写入后需要 5ms 左右的内部擦写时间这期间它不会应答任何命令。如果你连续写第二次写会 NACK。解决办法是每次写完后延时 5ms或者用“应答轮询”方式——反复发起始条件加地址直到收到 ACK 再继续。// 应答轮询示例 int eeprom_wait_ready(int fd, uint8_t addr) { int retries 100; while (retries--) { if (ioctl(fd, I2C_SLAVE, addr) 0) { char buf; if (write(fd, buf, 0) 0) return 0; } usleep(1000); } return -1; }4.3 从机死锁导致总线瘫痪的恢复手段从机死锁是 I2C 最棘手的问题之一。某个从机因为电源波动或时序异常内部状态机卡住把 SDA 拉低不放主机发什么它都不理整条总线瘫痪。恢复手段有两种硬件复位给从机断电再上电。这是最彻底的但需要硬件支持可控电源。软件恢复主机发送 9 个时钟脉冲让从机把剩余的数据位移出然后发停止条件。具体做法是把 SCL 配成 GPIO 输出手动翻转 9 次再发停止条件。// 软件恢复手动翻转 SCL 9 次 void i2c_bus_recover(int scl_gpio, int sda_gpio) { gpio_direction_output(scl_gpio, 1); gpio_direction_output(sda_gpio, 1); for (int i 0; i 9; i) { gpio_set_value(scl_gpio, 0); udelay(5); gpio_set_value(scl_gpio, 1); udelay(5); } // 发停止条件SCL 高时 SDA 从低到高 gpio_set_value(sda_gpio, 0); udelay(5); gpio_set_value(scl_gpio, 1); udelay(5); gpio_set_value(sda_gpio, 1); udelay(5); }这段代码在 RK3568 上实测有效但要注意 SCL 和 SDA 的 GPIO 编号要和设备树里的 pinctrl 配置对应。4.4 设备树 reg 地址与驱动硬编码不一致的隐蔽问题有些从设备驱动在代码里硬编码了 I2C 地址而不是从设备树的reg属性读取。这种情况下设备树里写什么地址都没用驱动只认代码里的。排查方法是看驱动源码里有没有client-addr的赋值如果有硬编码要么改驱动要么确保硬件地址和硬编码一致。OpenHarmony 的 HDF 驱动框架鼓励从设备树读取配置但一些移植过来的老驱动可能还保留着硬编码。遇到 probe 失败但设备树看着没问题的情况翻一翻驱动源码往往能找到线索。5. 调试工具链的搭建与使用技巧5.1 逻辑分析仪的选择与 I2C 解码配置逻辑分析仪我推荐至少 8 通道、采样率 24MHz 以上的型号。I2C 跑 400kHz 时采样率至少要是信号频率的 10 倍24MHz 足够覆盖。Sigrok/PulseView 是开源方案支持 I2C 解码器配置步骤通道 0 接 SCL通道 1 接 SDA地线共地。采样率设 24MHz采样深度根据传输长度定一般 1M 采样点够用。添加 I2C 解码器SCL 选通道 0SDA 选通道 1。触发条件设 SDA 下降沿抓起始条件。解码后PulseView 会把波形翻译成地址、读写位、数据、ACK/NACK一目了然。5.2 用 GPIO 模拟 I2C 验证硬件通路当控制器驱动的 I2C 死活不通时可以用 GPIO 模拟 I2C 来验证硬件通路是否正常。如果 GPIO 模拟能通说明硬件没问题问题在控制器驱动或设备树配置如果 GPIO 模拟也不通那就是硬件问题。GPIO 模拟 I2C 的核心是精确控制 SCL 和 SDA 的翻转时序。在 OpenHarmony 上可以用gpio_set_value和udelay实现速率不用高10kHz 就行目的是验证通路。5.3 内核态与用户态调试手段的取舍OpenHarmony 下调试 I2C 有两种路径用户态通过/dev/i2c-X设备节点用ioctl和read/write操作。适合快速验证和扫描。内核态在驱动里加打印用i2c_transfer直接调用。适合排查驱动层问题。我的习惯是先用用户态工具确认硬件和地址再进内核态调驱动。用户态能通但内核态不通说明驱动有问题用户态也不通先查硬件。6. 从协议到实战几个容易混淆的概念澄清6.1 I2C 与 SMBus、PMBus 的区别与兼容性SMBus 是 I2C 的子集电气特性更严格比如规定了最小总线速率 10kHz、超时 35ms协议上多了几种命令类型。PMBus 又建立在 SMBus 之上面向电源管理。实际使用中I2C 控制器通常兼容 SMBus 设备但 SMBus 的超时机制可能导致 I2C 设备通信失败——因为 I2C 设备可能时钟拉伸超过 35ms。6.2 7 位地址与 10 位地址的识别标准 I2C 用 7 位地址但协议也支持 10 位地址。10 位地址的识别方式是前 5 位固定为11110后面跟 2 位地址高位和读写位然后再发 8 位地址低位。设备树里的reg属性对于 10 位地址设备写法是0x780这样的完整 10 位值。大多数传感器用 7 位地址就够了10 位地址多见于一些特殊器件。6.3 重复起始条件的使用场景重复起始条件Repeated Start是在不发送停止条件的情况下重新发送起始条件。典型场景是读寄存器先写寄存器地址起始地址写寄存器地址然后不发停止直接发重复起始地址读读取数据。这样避免了总线释放后被其他主机抢占。OpenHarmony 的i2c_transfer接口支持消息链把写和读两条消息放在一个传输里中间自动插入重复起始条件。struct i2c_msg msgs[2]; msgs[0].addr client-addr; msgs[0].flags 0; msgs[0].len 1; msgs[0].buf reg_addr; msgs[1].addr client-addr; msgs[1].flags I2C_M_RD; msgs[1].len len; msgs[1].buf data; i2c_transfer(client-adapter, msgs, 2);这段代码在 OpenHarmony 的 I2C 设备驱动里很常见理解消息链的机制对调试读操作问题很有帮助。7. 一些零散但实用的经验I2C 总线上拉电阻的选型我一般按这个经验公式估算R (VCC - VOL) / IOL其中 VOL 是从机输出低电平时的最大电压通常 0.4VIOL 是灌电流通常 3mA。3.3V 系统算下来 R ≈ (3.3 - 0.4) / 0.003 ≈ 970Ω但实际要考虑总线电容通常取 2.2kΩ~4.7kΩ 之间。总线电容大就取小值设备少就取大值。设备树里clock-frequency改了之后记得同步检查从机手册支持的最高速率。我见过有人把 100kHz 的传感器配成 400kHz结果数据偶尔出错查了半天以为是驱动问题最后发现是超频了。逻辑分析仪抓包时如果波形上看到 SCL 周期不均匀有的周期长有的周期短那是时钟拉伸导致的不是故障。从机在忙的时候会拉低 SCL 延长周期这是正常行为。OpenHarmony 的 HDF 驱动框架里I2C 设备的probe函数如果返回失败设备不会注册但总线控制器本身还是正常的。所以排查时先确认控制器probe成功日志里有i2c controller init success之类的信息再看从设备probe。最后说一个我踩过的坑RK3568 的某些引脚组默认功能不是 I2C需要在 pinctrl 里显式配置。如果设备树里pinctrl-0引用的引脚组没有正确配置复用功能I2C 波形根本出不来。这种问题在逻辑分析仪上表现为 SCL 和 SDA 都是高电平不动看起来像从机死锁实际是引脚没配对。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。