ARTICLE DETAIL

资讯详情

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

OpenHarmony I2C总线开发与排障:从协议原理到系统级问题定位

OpenHarmony I2C总线开发与排障:从协议原理到系统级问题定位 做I2C开发这些年我最大的感触是这个总线协议看着简单两根线一个地址就能通信可真到了板子上跑不起来的时候排查起来往往比SPI、UART这类接口更让人头疼。尤其在OpenHarmony这种分布式系统里从内核驱动到用户态服务中间隔了好几层一个问题可能藏在硬件上也可能藏在HDI接口层甚至可能只是设备节点权限没给对。这篇文章我就把自己在OpenHarmony设备上使用I2C总线、以及排查各类I2C通信问题的经验完整写下来从协议本身到底层架构再到实操步骤和排障链路尽量让不同类型的开发者都能找到自己需要的那一部分。I2C在鸿蒙生态里特别常见像GT911触摸屏、各种温湿度传感器、EEPROM存储芯片、电量计、陀螺仪基本都是走I2C。我见过太多人卡在同一个地方明明代码照着示例写寄存器地址也对可读回来的数据就是不对或者驱动probe直接失败。所以这篇教程的思路是先帮大家把I2C的底层原理理清楚再结合OpenHarmony的软件分层讲明白调用路径然后用一个完整的传感器读写实操把流程串起来最后给出一套我自己常用的排障方法论保证以后你遇到I2C问题能有个清晰的排查方向。1. 先把I2C的底子打牢两根线上到底发生了什么很多人用I2C的时候习惯直接复制驱动代码寄存器读写函数调一下就完事完全没想过SDA和SCL这两根线上的电平变化是怎么被对端识别的。这样一旦遇到信号异常就会无从下手。所以我觉得不管你是做应用层还是做驱动都应该先理解I2C协议的几个核心机制后面排障时你才知道该用逻辑分析仪看什么。1.1 物理连接和上拉电阻的讲究I2C总线只有两条线串行数据线SDA和串行时钟线SCL都是双向的。所有设备挂在这两条线上通过设备地址区分彼此。这里有个经常被忽略的点——这两根线必须是开漏输出然后外部接上拉电阻到电源。开漏的意思就是芯片内部只能把线拉低不能主动拉高拉高靠上拉电阻完成。所以选上拉电阻的阻值很有讲究电阻太大比如100kΩ上升沿太慢高速模式400kbit/s及以上下时序容易不过关电阻太小比如1kΩ灌电流过大可能导致低电平无法被正确识别甚至损坏端口常见做法是4.7kΩ针对100kbit/s标准模式2.2kΩ或1.8kΩ针对400kbit/s快速模式具体还要看总线上挂了多少设备、走线多长。我实测过一块板子SDA和SCL本来用10kΩ上拉通信速率400kHz结果偶尔出现数据错误逻辑分析仪看波形上升沿明显成了圆弧。换成2.2kΩ后问题就消失了。所以在排障清单里上拉电阻永远是第一个要检查的物理量。1.2 地址机制与读写方向的确定I2C设备地址分7位和10位两种。绝大多数传感器是7位地址比如EEPROM的地址常常是0x50或者0x57陀螺仪可能是0x6B。但这里有个容易搞混的点芯片手册上给出的地址一般是一个字节的8位形式最低位是读写标志位。比如手册写“器件地址为0xD0”这其实是7位地址0x68左移一位加0得到的读地址。如果你直接用0xD0去Linux的I2C层或OpenHarmony的HDI接口里做读操作就会因为地址不匹配而失败。实际编码时标准做法是在应用或驱动中传入7位地址0x68由底层I2C控制器自动拼上读写位。所以遇到读写失败时第一件事不是怀疑时序而是去核对一下你传的地址到底是7位还是8位。我曾接手过一个项目同事在代码里写的是0xA08位写地址驱动却一直报“无响应”就是因为他给底层传的是8位地址导致地址被再次移位设备根本没被寻址到。1.3 时序图怎么看起始、停止、数据和ACK在网上搜I2C相关内容总能看到各种时序图比如“I2C时序图”“I2C数据帧格式”之类。其实只要抓住几个关键点时序图就很好懂起始条件SCL保持高电平时SDA产生一个下降沿表示总线开始传输停止条件SCL保持高电平时SDA产生一个上升沿表示总线结束传输数据传输每个数据位在SCL的高电平期间必须保持稳定数据变化只允许发生在SCL低电平期间。这一点特别重要如果SDA在SCL高电平时跳变会被误判为起始或停止ACK/NACK每传输完8个bit接收方要在第9个时钟周期把SDA拉低表示响应。如果接收方没拉低就说明设备没准备好或者地址不对主机会收到NACK。用逻辑分析仪抓到的I2C波形本质上就是对照这些条件去看。我在排障时经常碰到一种情况从机确实有响应但数据读出来每一位都在跳变仔细一看是SCL线上有毛刺SDA翻转的时机正好卡在SCL上升沿附近这就是典型的时序不满足建立时间/保持时间导致的问题。这种问题靠代码很难修多半要调硬件或者降低速率。1.4 读流程与写流程的标准动作不管是读写EEPROM还是读传感器寄存器I2C通信流程都有固定套路理解了这个套路你在写驱动时就不会把“写寄存器地址”和“写数据”搞混。最常见的写操作流程是这样的主机发起起始条件发送设备地址写标志位最低位为0等待从机ACK发送要写入的内部寄存器地址一个字节或两个字节等待ACK发送数据字节每发一个都等ACK全部发完后主机发起停止条件。而读操作流程稍微特殊一点通常采用“伪写”方式先定位寄存器起始条件后发设备地址写标志发寄存器地址等ACK重新发起起始条件部分设备是停止再启动但一般支持重复起始发设备地址读标志最低位为1从机返回数据主机在每个字节后回ACK除了最后一个字节回NACK主机发起停止条件。很多传感器驱动里会用到“读多个寄存器”的功能例如读三轴加速度计连续读6个字节这个时候在最后一个字节前主机不要回ACK而是NACK表示“我要停止读取了”。这个细节常被忽略导致读出来的数据多一位或少一位。在OpenHarmony的HDI接口里通常I2cRead函数会帮你处理连续读的情况但如果你是自己通过字操作拼接就要注意这个协议细节。2. OpenHarmony中的I2C软件栈从HDF框架到用户态的逻辑以前做单片机开发I2C就是直接操作寄存器读写或者调用HAL库函数。但在OpenHarmony系统里I2C被抽象成了好几层。如果你只知道写寄存器不知道数据是怎么从用户态传到硬件上的排障时就会感觉像隔了一层雾。所以这一节专门讲OpenHarmony的I2C软件架构。2.1 HDF驱动框架与HDI接口的关系OpenHarmony的驱动框架叫HDFHardware Driver Foundation所有硬件设备驱动都挂在HDF上。HDF下面有各类设备模型I2C这种总线类设备有专门的适配器。HDIHardware Device Interface则是面向系统服务层和应用层的统一硬件访问接口。简单理解HDI是对上提供的APIHDF是对下管理驱动生命周期和消息分发的框架。在OpenHarmony源码里I2C HDI接口的典型路径是drivers/peripheral/i2c里面定义了I2cTransfer、I2cRead、I2cWrite这类方法。这些接口最终会通过HDF的消息通道调用到具体的I2C控制器驱动。所以你在用户态做I2C读写时其实经历了应用/服务 - HDI接口 - HDF驱动框架 - I2C控制器驱动 - 硬件寄存器这个链路。这里有一个排障的关键点如果上层调用I2C接口返回错误码不一定代表硬件失败也可能是在HDF层消息解析失败或者驱动加载失败。这时候就需要看dmesg或者hilog里HDF驱动相关的标签而不是死盯着传感器本身。2.2 用户态访问I2C的设备节点路径在基于Linux内核的OpenHarmony标准系统里I2C控制器通常会被注册为/dev/i2c-x字符设备其中x是控制器编号。比如I2C0就是/dev/i2c-0。你可以在shell里用ls /dev/i2c*查看系统上有哪些I2C总线。要从用户态操作I2C最常见的方式是通过ioctl系统调用使用I2C_RDWR或者I2C_SMBUS命令。在OpenHarmony的应用层Native C或C你可以直接打开设备节点构造i2c_msg结构体数组然后调用ioctl(fd, I2C_RDWR, rdwr)完成一次读写。如果只做简单的寄存器读写很多开发者会选择用i2c-dev内核模块提供的用户态接口它允许你直接指定从机地址、读长度、写长度底层帮你完成时序。但这里有个大坑在OpenHarmony的多进程架构里普通应用不一定有权限直接访问/dev/i2c-x。如果你在应用里open失败第一反应别忙着改代码先看看是不是SELinux权限策略挡住了。通常需要配置用户态服务对应的SELinux上下文允许它对I2C设备节点的访问。否则你的Native代码写得再完美也依然会拿到“Permission denied”。2.3 写一个最简单的I2C读取工具假设你已经确认系统里有/dev/i2c-1这个设备节点且目标传感器挂在I2C1上7位地址为0x48一个常见的温度传感器地址。下面这段C代码演示了怎么用ioctl和I2C_RDWR方式读取该传感器0x00寄存器的两个字节#include stdio.h #include fcntl.h #include unistd.h #include sys/ioctl.h #include linux/i2c-dev.h #include linux/i2c.h int main(void) { int fd open(/dev/i2c-1, O_RDWR); if (fd 0) { perror(open /dev/i2c-1 failed); return -1; } unsigned char reg 0x00; unsigned char buf[2] {0}; struct i2c_msg msgs[2]; struct i2c_rdwr_ioctl_data rdwr; // 第一个消息写入寄存器地址 msgs[0].addr 0x48; // 这里传7位地址 msgs[0].flags 0; // 写方向 msgs[0].len 1; msgs[0].buf reg; // 第二个消息读取2字节数据 msgs[1].addr 0x48; msgs[1].flags I2C_M_RD; // 读方向 msgs[1].len 2; msgs[1].buf buf; rdwr.msgs msgs; rdwr.nmsgs 2; if (ioctl(fd, I2C_RDWR, rdwr) 0) { perror(I2C_RDWR ioctl failed); close(fd); return -1; } printf(reg0: 0x%02x, reg1: 0x%02x\n, buf[0], buf[1]); close(fd); return 0; }这段代码的好处是绕开了复杂的HDF框架直接对设备节点操作适合快速验证I2C链路是否正常。但需要注意的是这种方式要求设备节点已经暴露给了当前进程并且内核里i2c-dev模块已经加载。在OpenHarmony的release版本里不一定默认加载需要确认内核配置CONFIG_I2C_CHARDEV是否打开。2.4 在HDF框架中注册I2C设备驱动的步骤如果你需要把I2C设备做成一个正式的鸿蒙驱动那就不能只在用户态操作了而是要采用HDF驱动模型。流程大致如下在drivers/hdf_core/adapter/khdf/linux/platform/i2c目录下找到I2C控制器适配代码确认你的I2C控制器已被HDF管理为具体的从机设备编写驱动实现Bind、Init、Release三个标准回调在设备配置HCS文件中为你的设备定义device_info指定host对应哪个I2C控制器、periph信息以及参数如地址、速率在驱动代码中通过I2cOpen或者平台接口获取I2C控制器句柄然后调用I2cTransfer做数据交换编译为HDF驱动so后加载进内核使用hilog看到初始化日志。这个过程的细节比较多不过我这里想强调一个常见错误在HCS文件里配置I2C外设的速率属性时很多芯片只支持100k或400k如果你填了10000001MHz驱动初始化时可能自动降级也可能直接报“参数不支持”。所以配置前务必查清从机数据手册里的最高速率限制。3. 完整实操OpenHarmony上读写EEPROM的示例与验证纸上谈兵没用这里我用一个EEPROM的I2C读写项目串一遍完整的开发过程。选EEPROM做例子是因为它逻辑简单、寄存器概念少最适合用来理解I2C通读通的原理。同时我会把GT911触摸屏遇到I2C通信失败的真实案例放在第4节讲这样既有成功路径又有失败路径。3.1 硬件准备与接线检查我用的是一块带OpenHarmony标准系统的开发板I2C总线引到了排针上。EEPROM选用常见的AT24C02容量256字节I2C地址由A0/A1/A2引脚决定。这里有个连接要点AT24C02的WP写保护引脚如果拉高就不能写入数据只能读。很多人在OpenHarmony里用ioctl写寄存器时遇到“写入无效”结果发现是WP引脚悬空被内部上拉拉高了。建议直接把WP接地或者在使用前用万用表量一下引脚电平。接线完成后用逻辑分析仪探钩夹在SDA和SCL上然后上电先看空闲状态两条线都应该是高电平。如果SDA或SCL有一根被拉低大概率是从机设备异常或者上拉没接好。3.2 编写读写函数与错误重试AT24C02的写操作需要在发送设备地址后发送字节地址再发送数据。它一页是8字节写入超过一页会回卷覆盖所以写多字节时要分页。下面是一个基于I2C_RDWR的用户态读写函数示例直接用open设备节点的方式int eeprom_write_byte(int fd, unsigned char dev_addr, unsigned char byte_addr, unsigned char data) { unsigned char outbuf[2] {byte_addr, data}; struct i2c_msg msg { .addr dev_addr, .flags 0, .len 2, .buf outbuf, }; struct i2c_rdwr_ioctl_data rdwr { .msgs msg, .nmsgs 1, }; if (ioctl(fd, I2C_RDWR, rdwr) 0) { return -1; } // 注意EEPROM在内部写周期内不应进行其他操作这里可以加一个5ms延时 usleep(5000); return 0; } int eeprom_read_byte(int fd, unsigned char dev_addr, unsigned char byte_addr) { unsigned char data 0; struct i2c_msg msgs[2] { { .addr dev_addr, .flags 0, .len 1, .buf byte_addr }, { .addr dev_addr, .flags I2C_M_RD, .len 1, .buf data }, }; struct i2c_rdwr_ioctl_data rdwr { .msgs msgs, .nmsgs 2, }; if (ioctl(fd, I2C_RDWR, rdwr) 0) { return -1; } return data; }这里有个很典型的坑写完EEPROM后立刻读读回来的可能是旧值。因为EEPROM需要几毫秒的内部写周期在写周期结束前芯片不会响应I2C请求甚至返回NACK。所以写函数里我加了5ms延时。如果你在高并发场景写入建议做成带重试的轮询比如读到NACK就延时1ms再试最多试10次。校验写入是否正确也很简单先写0x5A到某个地址再读回来看是否等于0x5A。如果反复读都是0xFF说明信号没传进去如果写入后读出来的是随机值那可能是地址不稳定或电平问题。3.3 通过逻辑分析仪验证波形如果你手头没有逻辑分析仪我强烈建议花几十块钱买个24MHz采样的8通道逻辑分析仪。在OpenHarmony开发里用逻辑分析仪检查I2C波形是最高效的排障手段之一。把通道0接SCL通道1接SDA公共地接好然后跑一次读操作观察解码结果启动帧是否正常设备地址字节里第七位是否正确每个字节后有没有ACK位数据位是否和预期一致大部分逻辑分析仪自带的协议解码器可以自动识别I2C并显示“读地址0x50时ACK”。如果你看到的波形只有起始信号随后没有任何ACK基本可以断定是地址不对或者从机没上电。还有一个进阶技巧同时测量SCL的波形占空比。如果SCL出现非标准占空比比如高电平时间明显缩短可能是主控端时钟配置有问题。比如把I2C控制器时钟源算错导致实际波特率偏离目标值太多。在OpenHarmony的HDF配置里I2C的时钟分频系数如果算错就会出现这种隐性时序问题用逻辑分析仪一抓立刻现形。4. 排障实操链条从GT911通信失败看到的典型I2C故障GT911是电容触摸屏控制芯片走I2C接口很多带屏的OpenHarmony设备都在用它。我遇到过不少GT911 I2C通信失败的案例下面我把一次完整的排障过程复盘出来带大家走一遍从我拿到“触摸屏不工作”这个现象到最终找到根因的完整链路。这个过程比直接说“你应该检查xxx”更有价值因为排障思维是可以复用的。4.1 现象触摸屏完全无反应驱动probe失败开发板跑OpenHarmony触摸屏用GT911上电后发现触摸无响应。查看hilog出现类似“gt911_i2c_probe failedI2C communication error”的日志。当时我第一反应是驱动配置问题于是把驱动代码翻了几遍寄存器地址也没发现异常。排查陷入僵局。接下来我换了个思路先不管驱动直接在shell里用find /dev -name i2c*看有哪些I2C总线。然后尝试用i2c-tools里的i2cdetect -y 1扫描I2C1总线看能不能发现GT911的地址0x5D或0x14GT911有两个可选地址。扫描结果是一片空白所有地址都显示“--”说明总线上根本没有设备应答。这意味着问题在硬件链路或者上电时序而不是驱动。4.2 硬件排查从供电到引脚接触逐一排除GT911的工作电压常见有3.3V和2.8V。我去测量了触摸屏模组的VDD和IOVDD发现电压正常。但测量INT引脚时发现电平不对——GT911的中断输出默认应该是高电平或低电平视配置而定实际量到的是一个悬空跳变信号。顺着INT引脚查下去发现是触摸屏的FPC排线接触不良导致I2C总线上的走线也被连带影响。重新插紧排线后再用i2cdetect扫描GT911的地址出现了。但到这里还没有完全解决。地址能扫到了说明I2C基本物理链路通了可驱动依然报通信失败。于是继续用逻辑分析仪去抓上电时的I2C时序。这次抓到了关键问题GT911在上电后会发出中断请求主控需要延时一段时间后才能访问它的寄存器否则芯片还在内部初始化阶段对I2C请求不会正确响应。4.3 时序问题上电延时和复位序列多数电容触摸IC都有上电时序要求GT911也不例外。数据手册里明确写了“上电后至少等待10ms再发I2C命令且需要复位周期”。很多驱动里只有一次msleep(5)就急着读版本号这在某些批次芯片上没问题但在环境温度低或者电源启动慢的情况下5ms根本不够。我把驱动里的初始化延时从5ms改成20ms后触摸屏稳定工作了。这提醒我遇到I2C无响应不要只怀疑地址和上拉也要看看是否存在上电时序问题。特别是有些设备需要主控先拉低复位脚再拉高然后等待固定时间才能访问。4.4 用“二分法”缩小故障范围通过上面这个案例我总结出了一套“二分法”排查思路大家以后遇到I2C问题可以照着做先用i2cdetect或等效工具扫描总线确认设备地址是否可见。这一步能区分是硬件链路问题还是软件协议问题。如果地址不可见按顺序检查供电、接地、上拉电阻、SDA/SCL是否接反、设备使能引脚、复位引脚。只要有一根线接触不良扫描结果都是空白。如果地址可见但读写数据不对用逻辑分析仪抓取读时序比对设备手册的时序参数。如果时序正常但应用层数据不对再看寄存器地址、数据长度、字节序、ACK处理等软件逻辑。这个方法看起来简单但实际能解决90%的I2C问题。很多人一上来就怀疑驱动框架有问题其实绝大多数情况是硬件接触不良、地址搞错、或者上电时序没满足。4.5 常见错误速查表错误码对照我整理一份排障时常用的现象与根因对照表方便大家直接对照现象可能的根因快速验证方法扫描不到任何设备总线卡死SDA或SCL被拉低万用表量空闲时的电平应都为高扫描显示地址为0x00或0x77多设备地址冲突或总线短接断开可疑设备逐个排除能扫描到设备但读写总是NACK地址错误、设备未就绪或供电电流不足i2cget读一个已知寄存器测试读回数据全为0xFF上拉电阻缺失、设备没响应、读方向错误逻辑分析仪看ACK位写入无效WP引脚拉高、内部写周期未完成量WP电平延时后重读偶发通信失败时序不满足、噪声干扰、速率过高降低I2C速率至100k检查线长休眠后通信失败I2C控制器没有恢复、设备进入低功耗未唤醒重新初始化控制器并复位设备4.6 地址扩展与多路复用I2C扩展器的典型用法当总线上设备过多或从机地址固定不变时就需要用到I2C扩展器或多路复用器比如TCA9548A。这类芯片本身也是一个I2C设备通过配置它的通道选择寄存器可以把总线切换连接到不同的子总线。在OpenHarmony里如果要用扩展器常见做法是在驱动初始化时先选择通道再访问目标设备。有个细节要提醒TCA9548A的通道选择寄存器是0x00往里面写0x01选通道0写0x02选通道1。如果你在驱动里忘了在每次访问前重新选择通道多个设备就会串线。我见过一位朋友把两个不同地址的传感器挂在同一个扩展器的不同通道上因为没切通道初始化时第一个传感器正常第二个传感器却读到了第一个传感器的数据。这种问题排查起来也挺有迷惑性因为看着数据好像也有就是不对。5. 进阶场景休眠唤醒后I2C挂死与常见的坑到了系统级集成阶段I2C经常遇到的问题是低功耗。OpenHarmony设备会进入休眠I2C控制器也会跟着挂起。休眠唤醒后有的设备能继续正常工作有的就会挂死表现为通信超时或返回垃圾数据。我在这里集中聊几个高频坑和方法。5.1 ESP32等低功耗平台上的I2C复位问题热词里有一条“esp32 休眠 i2c复位”这确实是嵌入式开发的高频问题。在OpenHarmony兼容的ESP32平台上I2C外设进入低功耗模式后部分寄存器会被断电清零唤醒后如果不重新初始化I2C控制器总线就会保持异常状态。我和团队在实际项目里的解法是在系统休眠回调中先关闭I2C控制器时钟唤醒后重新调用I2C控制器初始化并把从机设备同样复位一次通常拉一下复位引脚。这个操作要放在驱动PM回调里而不是在上层应用里做因为应用层不知道硬件控制器当前的电源状态。有一个容易忽略的点I2C控制器重新初始化后原来的设备节点和句柄可能仍然有效但底层硬件已经被重置了。此时如果你继续用之前缓存的I2C消息去读写有可能得到错误结果。稳妥的做法是每次唤醒后重新open设备节点或者重新获取HDI句柄。很多“休眠唤醒后第一次读失败、第二次成功”的问题往往就是因为没有重新初始化句柄。5.2 时钟拉伸与多主机仲裁I2C协议有个特性叫时钟拉伸Clock stretching。当从机处理速度慢时它会在接收数据期间拉低SCL请求主机等待。如果主机的I2C控制器不支持时钟拉伸就会在从机没有释放时钟线的时候强行发送下一个bit导致通信错乱。这在某些传感器上经常出现比如一些老款气压传感器。排查时钟拉伸问题的方法很简单用逻辑分析仪看SCL是否有额外的低电平延长。如果SCL高电平时间正常但低电平时间明显比主机产生的一个bit时间长就说明发生了时钟拉伸。如果主控I2C控制器不支持就需要在驱动层加延时或者改用软件模拟I2C。至于多主机仲裁OpenHarmony里用到多主机场景不多但如果你在一条总线上既挂了可热插拔的设备又挂了需要实时通信的主控就要注意总线冲突。常见现象是“开始几分钟正常后面就开始随机报错”。这时建议检查是否有两个Master在同时尝试发送起始条件逻辑分析仪能看到异常的重叠SDA下降沿。5.3 I2C设备找不到足够资源的系统级报错热词里还有一条“i2c hid该设备找不到足够资源可以使用。 (代码 12)”这实际上是Windows系统下的HID-over-I2C问题但类似的“资源不足”错误在OpenHarmony下也会出现。一个典型场景是HDF驱动加载时系统为每个I2C设备分配一个I2C句柄资源如果说I2C控制器初始化失败或者中断号冲突就会出现驱动加载失败日志里提示注册资源失败。遇到这类问题我的建议是先去/sys/class/i2c-dev/或者/sys/bus/i2c/devices/下看看设备有没有注册成功。如果设备列表里没有对应条目说明驱动根本没绑定硬件。这时候不要卡在上层应用日志里要回到HDF的dmesg里看控制器初始化部分。最常见的资源冲突是I2C控制器复用GPIO引脚引脚在别的驱动里已经被申请了。这种情况在鸿蒙的多驱动环境下特别常见因为系统里很多功能会抢占GPIO。解决方式是把不必要的功能先禁用或者调整设备树中的引脚复用配置。5.4 软件模拟I2C与硬件I2C的选择在很多OpenHarmony开发板上硬件I2C控制器的数量有限有时候需要把一个不常用的传感器挂到普通GPIO上用软件模拟I2C。软件模拟I2C的优点是引脚随意、移植方便缺点是占用CPU时间片、时序不够精准且不能很好地支持时钟拉伸。我自己在项目里一般遵循这个选择准则优先用硬件I2C特别是高速传感器或者要求低功耗的场景GPIO不够用或者硬件控制器资源冲突时才用软件模拟。软件模拟I2C最关键的是要严格实现起始、停止、每个bit的翻转、ACK采样。这里有一个容易错的地方在输出模式下读取SDA引脚状态前必须把SDA引脚切换成输入模式否则读到的永远是你当前输出的电平。很多软件I2C代码写出来死活收不到ACK就是因为没做I/O方向切换。5.5 一次真实的“速率过高”排障过程最后分享一个实际的坑。有块板子的触摸屏和温湿度传感器挂同一条I2C总线开发板默认配置I2C速率是400kHz。触摸屏没问题但温湿度传感器支持最高速率只有100kHz经常间歇性读回非法湿度值。我一开始以为是传感器坏了换了好几颗都没解决。后来用逻辑分析仪抓波形发现实测SCL频率大约390kHz而传感器数据手册里明确写着“操作频率不可超过100kHz”所以它误码不奇怪。解决方式有两种一是把整条总线降到100kHz但这样触摸屏性能会受影响二是把传感器挪到另一条I2C总线或用软件模拟I2C接它。实测后我选了第二种方案。类似这样“有的设备高速、有的设备低速”混挂的情况一定要确认每个从机的最高频率不要以为所有I2C设备都能跑400k。SPI的速率至少能分频I2C没有片选概念同一总线的速率只能取最低所以在硬件设计阶段就要规划好。6. 从经验到方法我在I2C排障中最常强调的几件事这篇文章写到这里核心内容已经讲得差不多了。我想最后再以个人经验的角度强调几个很多人容易忽视的点这也是我在带团队和帮人看代码时反复念叨的东西。第一永远先确认物理连接。无论是新手还是老手在I2C出问题时最不该跳过的一步就是检查SDA和SCL的电平。用万用表测量是最快的。只要有一根线在空闲状态不是高电平后面的所有软件分析和代码修改都是在浪费时间。第二花点时间学会用逻辑分析仪。这可能是投入产出比最高的一项技能。你不需要很复杂的设备一个基础的8通道逻辑分析仪加上开源软件就能把I2C通信的每一个bit都看得清清楚楚。我经常在遇到难缠问题的时候说“别猜了抓波形吧。”一旦你亲眼看到ACK位置、数据字节、时序偏差很多“仿佛不可能”的问题都会变得非常具体。第三在OpenHarmony这类操作系统里不要把目光只锁在底层驱动上。设备树的引脚冲突、SELinux权限、HDI服务框架的消息通道任何一个环节都可能让I2C通信失败。我见过一个诡异的“开机后几分钟内触摸失灵”问题最终查出来是另一个驱动在初始化时把I2C用的引脚重新配置成GPIO了。这已经超出了I2C协议本身完全属于系统级的资源管理问题。第四尽量把驱动写的带有重试和错误分类。在OpenHarmony的I2C HDI接口中通常返回码已经区分了I2C_ERR_NACK和I2C_ERR_TIMEOUT。驱动里不要把所有错误一视同仁应该针对NACK重试针对超时报错。如果直接粗暴地报-1后期排查会相当痛苦。说到底I2C这总线不难难的是当你面对一个封闭黑盒子的设备时如何一步步把问题从系统里剥离出来。我的经验就是先看波形再读手册最后才是改代码。希望这篇教程能帮你减少一些从头到尾的痛苦排查时间。下次再在OpenHarmony上遇到I2C不工作你打开逻辑分析仪的那一刻问题其实就已经解决一半了。
返回列表