ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenHarmony I2C驱动开发实战:从协议原理到排障指南

OpenHarmony I2C驱动开发实战:从协议原理到排障指南 1. 从一根线说起I2C 到底解决了什么问题搞嵌入式开发的人迟早会跟 I2C 打交道。不管你是在 OpenHarmony 上接一颗温湿度传感器还是在 RK3568 上调试一颗触摸屏I2C 几乎无处不在。但很多人对它的理解停留在“两根线一根 SCL 一根 SDA挂一堆设备”这个层面真到了设备不通、数据读不出来的时候就抓瞎了。这篇内容我打算把 I2C 从协议原理到 OpenHarmony 上的实战用法再到排障思路完整地捋一遍。适合谁看如果你正在做 OpenHarmony 驱动开发或者刚接触嵌入式外设调试又或者你已经在用 I2C 但遇到问题不知道怎么下手那这篇内容应该能帮到你。我会尽量用大白话把时序、设备树配置、HDI 接口这些容易让人懵的东西讲清楚同时给出可以直接参考的操作步骤和排查方法。先说结论I2C 的核心价值在于用最少的引脚挂最多的设备。两根线理论上可以挂 112 个设备7 位地址去掉保留地址每个设备有自己的地址主设备通过地址来区分跟谁说话。这在引脚资源紧张的嵌入式场景里简直是救命稻草。但代价是什么代价是它比 SPI 慢比 UART 复杂而且一旦总线上某个设备抽风可能把整条总线拉死。所以会用 I2C 只是第一步会排障才是真正的分水岭。2. I2C 协议核心机制拆解别被时序图吓到2.1 两根线背后的电气逻辑I2C 只有两根信号线SCL串行时钟线和SDA串行数据线。这两根线都是开漏输出什么意思呢就是每个设备只能把线拉低不能主动拉高。线要变高靠的是上拉电阻。这就好比一个会议室里所有人只能举手反对不能举手赞成默认状态是“赞成”谁反对谁举手把线拉低。这个设计带来的好处是多设备可以同时挂在一根线上不会因为某个设备输出高电平而跟另一个输出低电平的设备打架。因为谁都不能输出高电平只能拉低或者释放释放后由上拉电阻把线拉高。上拉电阻选多大这是第一个容易踩坑的地方。典型值是 4.7kΩ但这不是固定的。电阻越小上升沿越陡能跑的速度越高但功耗越大电阻越大功耗越低但上升沿变缓高速通信时可能还没到高电平就被拉低了。经验做法是标准模式100kHz用 4.7kΩ 到 10kΩ快速模式400kHz用 2.2kΩ 到 4.7kΩ快速模式1MHz用 1kΩ 左右。如果你总线上挂了比较多设备电容负载增大上拉电阻也要相应减小。注意有些模块自带上拉电阻你再去主板上加一对并联之后阻值变小可能导致上升沿过冲。调试时先用示波器看一眼波形比盲目换电阻靠谱得多。2.2 起始、停止、应答三个必须刻在脑子里的动作I2C 通信的每一个字节传输都围绕三个基本动作展开起始条件STARTSCL 保持高电平的时候SDA 从高变低。这个动作只能由主设备发起意思是“我要开始说话了大家注意听”。停止条件STOPSCL 保持高电平的时候SDA 从低变高。意思是“我说完了总线释放”。应答ACK/NACK每传输完 8 位数据接收方要在第 9 个时钟周期把 SDA 拉低表示“收到了”。如果接收方不拉低就是 NACK表示“没收到”或者“不要再发了”。这三个动作是 I2C 排障时最直接的观察点。用逻辑分析仪抓波形第一眼就看起始条件有没有、地址发出去之后有没有 ACK。如果地址阶段就 NACK说明从设备根本没响应要么地址错了要么设备没上电要么线没接好。如果数据阶段 NACK可能是从设备忙不过来或者寄存器地址不合法。2.3 7 位地址和 10 位地址实际用哪个I2C 支持 7 位和 10 位两种地址格式。7 位地址是主流一个字节里高 7 位是地址最低位是读写标志位0 写1 读。10 位地址用得少一般只在 7 位地址不够分配的时候才用。实际开发中你拿到的传感器手册上写的地址通常是 7 位地址。但有些手册给的是 8 位地址已经把读写位算进去了这时候你要自己右移一位。比如手册写 0x92实际 7 位地址是 0x49。这个坑我踩过不止一次地址对不上怎么调都不通最后发现是手册给的是 8 位格式。2.4 时钟拉伸从设备也会“拖堂”I2C 有一个很人性化的机制叫时钟拉伸Clock Stretching。从设备如果处理不过来可以在接收完一个字节后把 SCL 拉低强制主设备等待。等从设备准备好了再释放 SCL通信继续。这个机制在调试低速传感器时经常遇到。比如某些温湿度传感器转换一次需要几十毫秒它就会在转换期间拉低 SCL。如果你用的主控不支持时钟拉伸或者驱动里没处理这个情况就会读出错数据。排查时如果发现 SCL 被长时间拉低先别急着怀疑硬件坏了看看是不是从设备在忙。3. OpenHarmony 下的 I2C 驱动框架HDI 接口怎么用3.1 从 HDF 到 HDIOpenHarmony 的驱动分层OpenHarmony 的驱动框架叫 HDFHardware Driver Foundation它把驱动分成内核态和用户态两部分。I2C 作为一种标准总线在 HDF 里有对应的抽象层。而 HDIHardware Device Interface是给上层应用提供的统一接口让应用不需要关心底层是哪个 SoC、哪个 I2C 控制器。具体来说OpenHarmony 的 I2C 驱动栈大概是这样最底层是 SoC 的 I2C 控制器驱动负责操作寄存器、产生时序。中间是 HDF 的 I2C 核心层提供统一的 I2C 设备管理。上层是 HDI 接口暴露给应用层调用。你在应用层调用的I2cOpen、I2cTransfer这些接口最终会走到内核里的控制器驱动完成实际的波形收发。3.2 设备树配置I2C 设备怎么“挂上去”在 OpenHarmony 里I2C 设备的配置通常写在设备树Device Tree里。设备树的作用是告诉内核哪个 I2C 控制器使能了、总线上挂了哪些设备、每个设备的地址是多少、用哪个驱动。一个典型的 I2C 设备树节点长这样i2c1 { status okay; clock-frequency 400000; sensor48 { compatible vendor,tmp102; reg 0x48; status okay; }; };这里几个关键点status okay表示这个 I2C 控制器使能了。如果写成disabled整条总线都不工作。clock-frequency是总线时钟频率单位 Hz。400000 就是 400kHz。reg 0x48是从设备的 7 位地址。compatible是驱动匹配字符串内核靠它找到对应的驱动。常见坑点地址写错、频率设太高导致通信不稳定、status忘了改。我遇到过好几次设备树里status默认是disabled改完忘了编译进内核结果怎么调都不通。3.3 HDI 接口调用流程在 OpenHarmony 应用层或 HAL 层通过 HDI 接口操作 I2C 的大致流程是I2cOpen(busNum)打开指定编号的 I2C 控制器拿到一个句柄。构造I2cMsg数组每个 Msg 包含从设备地址、读写标志、数据缓冲区、数据长度。I2cTransfer(handle, msgs, count)执行传输。I2cClose(handle)关闭句柄。一个读寄存器的典型操作是两次传输先写寄存器地址再读数据。有些驱动支持组合传输Repeated START可以在一次 Transfer 里完成。I2cMsg msgs[2]; msgs[0].addr 0x48; msgs[0].flags 0; // 写 msgs[0].buf regAddr; msgs[0].len 1; msgs[1].addr 0x48; msgs[1].flags I2C_FLAG_READ; msgs[1].buf readBuf; msgs[1].len 2; I2cTransfer(handle, msgs, 2);提示组合传输时两次 Msg 之间会产生 Repeated START而不是 STOP 再 START。这个区别在有些从设备上是致命的必须用 Repeated START 才能正确读取。4. 实操在 RK3568 上点亮一颗 I2C 传感器4.1 硬件准备和接线检查我拿 RK3568 开发板接一颗 TMP102 温度传感器来演示。接线很简单VCC 接 3.3VGND 接 GNDSCL 接 I2C1_SCLSDA 接 I2C1_SDAADD0 接地决定地址是 0x48接完线第一件事不是上电写代码而是用万用表测一下 SCL 和 SDA 对地的电阻。如果阻值接近 0说明线短路了先查线。正常应该有上拉电阻的阻值比如 4.7kΩ 左右。然后上电用示波器或者逻辑分析仪看 SCL 和 SDA 的静态电平。正常应该是高电平。如果是低电平说明总线被某个设备拉死了可能是设备坏了或者地址冲突。4.2 设备树修改和内核编译在 RK3568 的 SDK 里找到对应的设备树文件通常是kernel/arch/arm64/boot/dts/rockchip/rk3568-xxx.dts。找到i2c1节点改成i2c1 { status okay; clock-frequency 100000; tmp10248 { compatible ti,tmp102; reg 0x48; status okay; }; };这里我故意把频率设成 100kHz因为第一次调试低速更稳。等通了再往上提。编译内核和设备树烧录重启。然后进系统用i2cdetect工具扫一下总线i2cdetect -y 1如果看到地址 0x48 处显示48说明设备被识别到了。如果显示--说明没识别到回去查接线和设备树。4.3 用 HDI 接口读取温度数据设备识别到之后写一个简单的测试程序通过 HDI 接口读温度寄存器TMP102 的温度寄存器地址是 0x00int16_t read_temperature(int handle) { uint8_t reg 0x00; uint8_t buf[2] {0}; I2cMsg msgs[2]; msgs[0].addr 0x48; msgs[0].flags 0; msgs[0].buf reg; msgs[0].len 1; msgs[1].addr 0x48; msgs[1].flags I2C_FLAG_READ; msgs[1].buf buf; msgs[1].len 2; if (I2cTransfer(handle, msgs, 2) ! 0) { return -1; } int16_t raw (buf[0] 8) | buf[1]; return raw 4; // TMP102 是 12 位数据右移 4 位 }读出来的 raw 值乘以 0.0625 就是摄氏度。比如 raw 是 400温度就是 25°C。4.4 逻辑分析仪抓波形验证代码跑通之后我习惯用逻辑分析仪抓一段波形确认时序没问题。重点看几个地方起始条件是否干净地址 0x48 写操作后是否有 ACK寄存器地址 0x00 写完后是否有 Repeated START读操作的两个字节后主设备是否发了 NACK 再 STOP如果这些都对说明通信完全正常。如果哪里不对波形会直接告诉你问题出在哪个阶段。5. I2C 排障实战从现象到根因的排查路径5.1 常见问题速查表现象可能原因排查方法扫描不到设备接线错误、设备没上电、地址不对万用表测电压、示波器看波形、确认地址格式地址阶段 NACK从设备地址错误、设备未就绪核对手册地址、检查上电时序数据阶段 NACK寄存器地址不合法、设备忙查手册确认寄存器、增加延时读数据全 0 或全 FF时序问题、时钟拉伸未处理逻辑分析仪抓波形、降低时钟频率总线被拉死某个设备故障、地址冲突逐个断开设备、测静态电平通信偶尔出错上拉电阻不合适、线太长调整上拉电阻、缩短走线5.2 典型排障案例GT911 触摸屏 I2C 通信失败GT911 是一颗常见的触摸屏控制器用 I2C 通信。我遇到过好几次 GT911 通信失败的情况总结下来大概有几种原因第一种上电时序不对。GT911 对复位和上电的顺序有要求如果复位引脚和电源的上电顺序错了芯片可能不响应 I2C。解决方法是严格按照手册的时序图先给电再释放复位中间加足够的延时。第二种地址冲突。GT911 的 I2C 地址可以通过 INT 引脚在上电时决定是 0x5D 还是 0x14。如果 INT 引脚状态不对地址就错了。排查时先用i2cdetect扫一遍看看到底出现在哪个地址。第三种中断引脚配置错误。GT911 用中断引脚通知主控有触摸事件如果中断引脚没配置好虽然 I2C 能通但读不到数据。这时候要检查设备树里中断引脚的配置。5.3 总线死锁最头疼的情况I2C 总线死锁是嵌入式开发里最让人头疼的问题之一。现象是 SCL 或 SDA 被某个设备一直拉低主设备无法发起新的通信。造成死锁的常见原因主设备在从设备还没释放 SDA 的时候就发了 STOP或者从设备在传输过程中复位了导致它一直拉着 SDA 不放。恢复方法给从设备发 9 个时钟脉冲让它在第 9 个脉冲后释放 SDA然后发一个 STOP 条件。很多 SoC 的 I2C 控制器支持总线恢复功能可以在驱动里配置。实操心得如果总线上挂了多个设备建议每个设备的电源单独控制出问题时可以逐个断电排查。另外在 SDA 和 SCL 上预留测试点方便接逻辑分析仪。5.4 时钟频率和上拉电阻的联合调试很多时候通信不稳定不是代码问题而是时钟频率和上拉电阻不匹配。频率越高对上升沿的要求越陡上拉电阻就要越小。但电阻太小功耗又上去了。我的经验做法是先用 100kHz 和 4.7kΩ 跑通然后逐步提高频率同时用示波器观察上升沿。如果上升沿变缓就减小上拉电阻。最终找到一个稳定工作的组合。另外总线电容也是影响因素。线越长、挂的设备越多电容越大上升沿越缓。如果总线电容超过 400pF标准模式都可能跑不稳。这时候要么缩短线要么用 I2C 缓冲器/中继器。6. 进阶话题I2C 扩展和多路复用6.1 地址冲突怎么办I2C 多路复用器当你需要挂多个相同型号的传感器时地址冲突就来了。比如你要接 4 颗同样的温度传感器它们的地址都是 0x48没法直接挂在一起。解决方案是用I2C 多路复用器比如 TCA9548A。它本身是一个 I2C 从设备有 8 个下游通道。你通过写它的寄存器来选择哪个通道导通这样每个通道上挂一个 0x48 的传感器互不干扰。在 OpenHarmony 里使用多路复用器需要在设备树里把多路复用器作为 I2C 设备配好然后在驱动里先写多路复用器选择通道再操作下游设备。6.2 软件 I2C vs 硬件 I2C有些场景下硬件 I2C 控制器不够用或者引脚被占用了就需要用 GPIO 模拟 I2C也就是软件 I2C。软件 I2C 的优点是灵活任意 GPIO 都能用缺点是占用 CPU速度慢时序精度不如硬件。在 OpenHarmony 里如果要用软件 I2C需要自己实现时序控制或者用内核提供的i2c-gpio驱动。我的建议是能用硬件 I2C 就用硬件软件 I2C 只作为备选方案。特别是高速通信场景软件 I2C 很容易出问题。6.3 I2C 和 SMBus 的区别SMBus 是基于 I2C 的一个子集主要用于电源管理和系统监控。它比 I2C 多了超时机制时钟频率固定在 10kHz 到 100kHz 之间。实际开发中很多电源管理芯片用的是 SMBus但物理层跟 I2C 兼容。如果你用 I2C 驱动去操作 SMBus 设备大部分情况下能通但要注意超时和协议细节的差异。7. 几个容易忽略的细节和实操建议7.1 地址格式的坑再强调一遍手册给的地址可能是 8 位格式实际用的时候要右移一位。我见过太多人在这上面浪费时间。拿到地址后先确认是 7 位还是 8 位不确定就用i2cdetect扫一遍看实际出现在哪个地址。7.2 上电顺序和复位时序很多 I2C 设备对电源和复位的顺序有要求。比如某些传感器要求先给电等电源稳定后再释放复位中间要延时几毫秒。如果顺序错了设备可能不响应 I2C。排查时先用示波器看电源和复位引脚的波形确认时序符合手册要求。7.3 中断引脚不要忘很多 I2C 设备有中断引脚用来通知主控数据准备好了。如果你只配了 I2C 没配中断虽然能轮询读数据但效率低而且可能错过快速变化的事件。设备树里记得把中断引脚也配好。7.4 逻辑分析仪是必备工具调试 I2C逻辑分析仪比万用表有用得多。它能直接告诉你起始条件、地址、ACK、数据、停止条件一眼就能看出问题在哪。便宜的逻辑分析仪几十块钱但能省你几个小时甚至几天的调试时间。7.5 设备树编译要确认改完设备树一定要确认编译进了内核。有时候改了 dts 但没重新编译或者编译了但烧录的不是新的都会导致配置不生效。我习惯改完之后用fdtdump或者/proc/device-tree确认一下实际生效的配置。8. 写在最后一些个人体会I2C 这个东西入门容易精通难。协议本身不复杂但实际调试中遇到的问题千奇百怪。我的经验是遇到问题先别改代码先用工具看波形。波形不会骗人它会直接告诉你问题出在哪个阶段。另外设备树配置和硬件接线是两大高频问题源。很多时候代码没问题就是设备树里某个参数写错了或者接线松了。养成“先查硬件再查软件”的习惯能省很多时间。最后分享一个小技巧如果你在 OpenHarmony 上调试 I2C 设备可以先用 Linux 下的i2c-tools验证硬件通路确认设备能扫到、能读写再去写 HDI 代码。这样能把硬件问题和软件问题分开排查起来更有条理。
返回列表