1. 这不是教科书里的I2C,是OpenHarmony设备上真正能焊、能测、能跑通的I2C实战
I2C总线怎么用?怎么排障?——这句话在OpenHarmony开发者的工位上,往往不是一句技术提问,而是一声带着焊锡味的叹息。上周我调试一块搭载Hi3516DV300的OpenHarmony 4.1开发板,接了三颗传感器:BME280温湿度气压、GT911触摸IC、还有个EEPROM AT24C02。前两颗死活不响应,i2cdetect -y 0扫出来全是空行,串口打印只有一句“i2c: transfer failed”。拆掉GT911后BME280突然活了;换一根杜邦线,EEPROM写入数据读出来却是0xFF;最后发现是开发板底板上I2C0的SCL引脚和另一个GPIO复用冲突,BIOS里没关掉那个GPIO的默认功能。这些事,文档里不会写,示例代码里更不会提——它只告诉你I2cMasterOpen()返回0就成功,可现实里90%的问题根本卡在open之前。
这就是OpenHarmony下I2C的真实水位线:它不像Linux那样有成熟的sysfs节点和完备的debugfs支持,也不像Arduino那样封装到Wire.begin()就万事大吉。你面对的是裸金属级的寄存器操作、设备树硬编码、驱动加载时序、电源域隔离、甚至PCB走线阻抗匹配。但反过来说,正因为OpenHarmony把底层控制权交还给你,一旦打通,稳定性远超用户态模拟方案。我手上这块量产边缘网关,连续运行18个月没出过一次I2C通信超时,靠的就是对时序参数、上拉电阻、从机地址解析、NACK处理这四个环节的死磕。
这篇内容专为已经能编译OpenHarmony源码、会烧写固件、能看懂dmesg日志的开发者准备。不讲I2C协议基础(比如起始/停止条件、ACK/NACK电平),那些网上一搜一大把;也不讲鸿蒙应用层怎么调用I2C服务(那是ACE框架的事)。我们要干的是:把开发板焊接到位、让内核识别到I2C控制器、让设备树正确描述外设、让驱动加载无报错、让应用能稳定读写——每一步都带实测截图、寄存器值、示波器波形、以及我踩过的所有坑。如果你正被“gt911 i2c通信失败”、“i2c hid该设备找不到足够资源可以使用(代码12)”这类错误卡住,那你来对地方了。
2. I2C在OpenHarmony中的真实定位:不是“通信模块”,而是“硬件信任锚点”
2.1 为什么OpenHarmony要把I2C设计成“不可绕过”的底层能力?
在Linux里,I2C常被当作一个可选的字符设备(/dev/i2c-0),上层应用通过ioctl直接读写;而在OpenHarmony中,I2C被深度整合进HDF(Hardware Driver Foundation)框架,成为连接SoC IP核与外设驱动的“信任锚点”。这不是架构师拍脑袋决定的,而是由三个硬性约束倒逼出来的:
第一,安全启动链要求。OpenHarmony的Secure Boot流程中,TPM芯片或eFuse配置必须通过I2C读取校验值。如果I2C驱动在用户态加载,启动阶段就无法验证固件签名——所以I2C控制器驱动必须编译进内核镜像,且在early_initcall阶段就完成初始化。
第二,分布式软总线依赖。鸿蒙的分布式能力(如多设备协同、任务流转)需要设备身份认证,而设备唯一ID常固化在I2C挂载的EEPROM或OTP中。若I2C不稳定,分布式发现就会超时失败,表现为“设备列表为空”或“连接状态反复断开”。
第三,低功耗场景刚性需求。以智能门锁为例,主控休眠时,仅靠I2C唤醒中断(如触摸IC触发)就能快速响应。这种“硬件级唤醒路径”要求I2C控制器在深度睡眠模式下仍保持寄存器可访问,且中断信号能直连PMU——这决定了驱动必须掌控时钟门控、电源域切换、中断优先级等底层细节。
提示:别试图用用户态I2C工具(如i2c-tools)替代HDF驱动。OpenHarmony的i2c-tools是阉割版,缺少对HDF设备模型的支持,
i2cdetect可能扫不到设备,但hdf_i2c_test却能正常通信——这是两个完全不同的访问路径。
2.2 OpenHarmony I2C驱动栈的四层真相
很多开发者以为“写个I2C驱动就是实现read/write函数”,但在OpenHarmony里,这四层缺一不可,且每一层都有致命陷阱:
第0层:SoC原厂IP核驱动
HiSilicon、Rockchip、Allwinner等厂商提供的I2C控制器驱动,通常位于drivers/adapter/khdf/platform/i2c/。它负责操作寄存器(如SCL/SDA电平控制、时钟分频、中断使能)。这里最大的坑是时钟源配置:Hi3516DV300的I2C0默认时钟源是CLK_I2C0,但若系统时钟树被修改(如启用动态频率调节),该时钟可能被关闭。我遇到过一次,设备树里写了clocks = <&crg CLK_I2C0>,但内核启动时clk_get_rate()返回0,导致I2C控制器根本无法工作。第1层:HDF适配层
位于drivers/hdf/core/manager/src/hdf_i2c_manager.c,它把原厂驱动封装成HDF标准接口(HdfI2cMethod结构体)。关键点在于设备号映射:HDF为每个I2C控制器分配逻辑编号(如i2c0、i2c1),这个编号必须与设备树中i2c@...节点的reg属性严格对应。曾有个项目把Hi3516的I2C1控制器误配到i2c@12120000(实际物理地址是0x12130000),结果驱动加载成功,但所有通信都发往错误地址,示波器看到SCL在抖,SDA却纹丝不动。第2层:设备树描述层
arch/arm64/boot/dts/hisilicon/hi3516dv300.dtsi中定义控制器,board.dts中挂载从机。这里最易错的是pinctrl配置:I2C需要专用的复用功能(如Hi3516的PERIPH_FUNC_2),若pinctrl节点里漏了bias-pull-up,上拉电阻失效,总线永远处于低电平。更隐蔽的是时序参数覆盖:设备树中#address-cells = <1>决定从机地址宽度,若设为2但从机只支持7位地址,I2cTransfer()会直接返回-EINVAL。第3层:用户态服务层
vendor/hisilicon/hi3516dv300/hdf_config/i2c_config.hcs定义HCS(HDF Configuration Source)配置,它生成i2c_config.hcs二进制文件,被HDF框架在启动时加载。这里有个致命细节:busNum字段必须与设备树中控制器节点的reg索引一致。例如设备树中i2c@12120000是第0个I2C控制器,那么HCS里busNum = 0,否则I2cMasterOpen(0)会打开错误的控制器。
这四层不是线性调用,而是环环相扣的信任链。任何一层的微小偏差,都会导致上层出现“玄学错误”——比如I2cTransfer()返回-EIO,你以为是线路问题,其实是HCS里slaveAddr写成了0x68(BME280默认地址),而实际硬件焊接的是0x76(地址引脚接地)。
2.3 OpenHarmony与Linux I2C生态的关键差异
| 对比维度 | Linux I2C生态 | OpenHarmony I2C实践 |
|---|---|---|
| 调试工具 | i2cdetect,i2cget,i2cset全功能,支持任意地址读写 | hdf_i2c_test仅支持预设测试用例,i2cdetect需手动编译且不支持HDF设备 |
| 设备树绑定 | compatible = "nxp,pcf8574"等标准字符串,内核自动匹配驱动 | 必须在HCS中显式声明matchMode = "device_id",且device_id需与驱动HdfDriverEntry中注册的ID一致 |
| 从机地址解析 | 用户态可通过/sys/class/i2c-dev/i2c-0/device/name查看设备名 | 地址由HCS中slaveAddr字段硬编码,驱动不解析从机响应的地址,全靠人工核对 |
| 错误码语义 | -ENXIO表示地址无响应,-ETIMEDOUT表示时钟拉低超时 | -12(ENOMEM)常因HDF内存池不足导致,与I2C硬件无关;-5(EIO)可能是SCL被从机拉死,也可能是DMA缓冲区未对齐 |
| 电源管理 | runtime_pm自动处理挂起/恢复 | 必须在驱动中实现I2cPowerOn()/I2cPowerOff(),且需在HCS中配置powerDomainId关联PMU |
这些差异意味着:你在Linux上积累的I2C经验,在OpenHarmony里有60%要推翻重来。比如Linux下习惯用i2cget -y 0 0x68 0x00快速验证,但在OpenHarmony里,你得先确认HCS中busNum=0、slaveAddr=0x68、regWidth=1全部正确,再编译烧写,最后运行hdf_i2c_test -b 0 -a 0x68 -r 0x00 -l 1——少一个参数,结果就是段错误。
3. 实战排障:从“i2cdetect扫不到”到“稳定读写BME280”的全流程拆解
3.1 第一步:确认硬件连接——用万用表和示波器说话,别信原理图
所有I2C问题,70%根源在硬件。别急着敲代码,先做三件事:
第一,测上拉电阻。OpenHarmony推荐I2C总线使用2.2kΩ~4.7kΩ上拉电阻(3.3V系统)。用万用表量SCL/SDA对VCC电阻值,若小于1kΩ,说明有器件内部短路或PCB焊锡搭接;若大于10kΩ,上拉失效,总线无法释放高电平。我曾遇到一块国产开发板,SCL上拉电阻虚焊,万用表测通路,但示波器看到SCL始终为0V——因为虚焊点在热胀冷缩后断开。
第二,查电源域。Hi3516DV300的I2C0控制器由PMU_I2C0电源域供电。用示波器探头测I2C0控制器的VDD引脚(通常是VDD_I2C0),确认上电后有稳定3.3V。若无电压,检查PMU配置:在arch/arm64/boot/dts/hisilicon/hi3516dv300.dtsi中,i2c0: i2c@12120000节点下必须有power-domains = <&pmu PMU_I2C0>,且PMU驱动已加载。
第三,看信号波形。这是最直观的诊断方式。将示波器通道1接SCL,通道2接SDA,触发模式设为“边沿上升”,时基调至1μs/div。正常I2C通信应看到:
- 起始条件:SCL高时SDA从高→低跳变;
- 数据位:SCL高电平时SDA保持稳定,SCL低电平时SDA可变;
- 停止条件:SCL高时SDA从低→高跳变。
若SCL无波形,检查控制器时钟是否使能(CLK_I2C0);若SDA无变化,检查从机是否供电(量从机VCC/GND)、地址是否匹配(BME280地址引脚AD0接GND为0x76,接VCC为0x77);若波形毛刺严重,检查PCB走线是否过长(>15cm需加终端电阻)或靠近开关电源。
注意:不要用逻辑分析仪替代示波器!逻辑分析仪只能看数字电平,看不出信号完整性问题(如上升沿缓慢、振铃)。我曾用Saleae Logic Pro 16抓到“完美”的I2C波形,但实际通信失败——示波器显示SCL上升时间达800ns(标准要求<300ns),原因是上拉电阻过大。
3.2 第二步:验证内核与HDF——让dmesg说出真相
硬件没问题后,看内核日志。在OpenHarmony设备上执行:
# 查看内核启动日志 dmesg | grep -i "i2c\|hdf"正常输出应包含:
[ 1.234567] hi_i2c 12120000.i2c: i2c controller registered, bus number: 0 [ 1.234589] hdf_i2c: i2c controller 0 registered successfully [ 1.234612] hdf_i2c: slave device bme280@76 registered on bus 0若没有i2c controller registered,说明SoC驱动未加载:
- 检查
drivers/adapter/khdf/platform/i2c/hi_i2c.c是否编译进内核(CONFIG_HDF_PLATFORM_I2C=y); - 检查设备树中
i2c@12120000节点是否启用(status = "okay"); - 检查
arch/arm64/configs/hi3516dv300_defconfig中CONFIG_HISI_I2C=y是否开启。
若出现hdf_i2c: fail to add i2c device,重点查HCS配置:
- 进入
out/hi3516dv300/obj/vendor/hisilicon/hi3516dv300/hdf_config/目录,用hexdump -C i2c_config.hcs查看二进制内容; - 确认
busNum字段值(偏移0x10处4字节)与设备树控制器索引一致; - 确认
slaveAddr字段(偏移0x18处1字节)是十进制还是十六进制——HCS规范要求十进制,但很多开发者误写0x76,导致解析为118(0x76的十进制),实际从机地址却是0x76(118的十六进制)。
实操心得:HCS文件必须用
hdf_tool编译,不能直接改文本。我试过手动编辑.hcs文件后make,结果HDF框架加载时校验失败,日志只显示hdf: load config failed,没有任何具体错误——因为HCS二进制有CRC校验,手动修改会破坏校验值。
3.3 第三步:运行测试用例——用hdf_i2c_test定位问题层级
OpenHarmony SDK提供hdf_i2c_test工具,位于developtools/haps/tools/。编译后推送到设备:
# 编译测试工具 ./build.sh --product-name hi3516dv300 --target-os ohos --target-cpu arm64 # 推送并运行 adb shell "mount -o remount,rw /" adb push out/hi3516dv300/obj/developtools/haps/tools/hdf_i2c_test /system/bin/ adb shell "chmod +x /system/bin/hdf_i2c_test" adb shell "/system/bin/hdf_i2c_test -h" # 查看帮助常用命令及含义:
hdf_i2c_test -b 0 -a 0x76 -r 0xD0 -l 1:读BME280芯片ID寄存器(0xD0),长度1字节hdf_i2c_test -b 0 -a 0x76 -w 0xF4 -d 0x25:写BME280控制寄存器(0xF4)值0x25(强制测量模式)hdf_i2c_test -b 0 -s:扫描总线上所有从机地址(等效于i2cdetect)
关键观察点:
- 若
-s扫描无输出,但-r能读到数据,说明从机地址配置错误(HCS中slaveAddr与实际不符); - 若
-r返回-5(EIO),用示波器看SCL是否被从机拉低超过10ms——这是从机忙或复位未完成的典型表现; - 若
-w后-r读回值不对,检查BME280的mode寄存器(0xF4)是否被写入,再读ctrl_meas(0xF2)确认配置生效。
我调试GT911时,hdf_i2c_test -b 0 -a 0x5D -r 0x00 -l 1始终返回-12(ENOMEM)。最终发现GT911的RESET引脚悬空,上电后处于复位态,I2C地址0x5D无效。焊接RESET到VCC后,问题解决——这再次证明:I2C排障,硬件永远是第一现场。
3.4 第四步:深入寄存器——当示波器也救不了你时
当hdf_i2c_test报错但波形看似正常,就得看寄存器。Hi3516DV300的I2C控制器寄存器映射在0x12120000,关键寄存器如下:
| 寄存器偏移 | 名称 | 读写 | 关键位 | 诊断意义 |
|---|---|---|---|---|
| 0x00 | IC_CON | RW | bit[0]=enable, bit[6]=stop_det_en | 若bit[0]=0,控制器未启用;bit[6]=0则STOP条件不检测 |
| 0x04 | IC_TAR | RW | [9:0]=target_addr | 写入从机地址,若值错误,通信必然失败 |
| 0x10 | IC_ENABLE | RW | bit[0]=enable | 必须为1才能开始传输 |
| 0x70 | IC_INTR_STAT | R | bit[1]=rd_req, bit[2]=rx_full | 若rx_full置位但未读取RX_FIFO,后续传输会卡住 |
| 0x74 | IC_RAW_INTR_STAT | R | bit[0]=gen_call, bit[3]=start_det | start_det置位说明检测到START条件 |
用devmem工具读取(需root权限):
# 读IC_CON寄存器 devmem 0x12120000 32 # 读IC_TAR寄存器(确认目标地址) devmem 0x12120004 32 # 读IC_RAW_INTR_STAT(看是否有中断挂起) devmem 0x12120074 32典型故障案例:BME280读取温度时,hdf_i2c_test返回-EIO,示波器看到SCL有脉冲但SDA无响应。读IC_RAW_INTR_STAT发现bit[3](start_det)为1,但IC_INTR_STAT中rx_full为0——说明START条件被检测到,但从机没发ACK。此时查IC_TAR,发现值为0x00000077,而BME280实际地址是0x76。原来HCS中slaveAddr = 119(0x77的十进制),但硬件焊的是0x76。修正HCS重新编译,问题解决。
实操心得:寄存器地址必须用物理地址,不是虚拟地址。Hi3516DV300的I2C0控制器物理地址是
0x12120000,若用ioremap后的虚拟地址(如0xffff000012120000)去devmem,会读到错误值。
4. 稳定性加固:让I2C在OpenHarmony设备上扛住7×24小时运行
4.1 时序参数调优——不是越快越好,而是“刚刚好”
I2C标准模式(100kHz)和快速模式(400kHz)的时序要求严格。Hi3516DV300的I2C控制器通过IC_SS_SCL_HCNT和IC_SS_SCL_LCNT寄存器设置高低电平时间。计算公式为:
SCL高电平时间 = (HCNT + 7) × T_clk SCL低电平时间 = (LCNT + 9) × T_clk T_clk = 1 / f_clk其中f_clk是I2C控制器输入时钟频率(Hi3516DV300默认为50MHz)。标准模式要求SCL高≥4μs、低≥4.7μs。代入计算:
- 若
f_clk = 50MHz,T_clk = 20ns - 要求
HCNT ≥ (4000ns / 20ns) - 7 = 193,取HCNT = 200 LCNT ≥ (4700ns / 20ns) - 9 = 226,取LCNT = 230
在drivers/adapter/khdf/platform/i2c/hi_i2c.c中修改:
// 标准模式时序配置 static const struct I2cTiming i2c_timing_std = { .hcnt = 200, .lcnt = 230, .sda_hold = 100, // SDA保持时间 .sda_setup = 100, // SDA建立时间 };但实际部署中,我发现:在高温环境(>60℃)下,400kHz模式会频繁NACK。将f_clk降为25MHz,HCNT/LCNT按比例减半,反而更稳定——因为高温下晶体振荡器频偏增大,时序裕度变小。结论:时序参数必须在目标环境温度下实测,不能只按室温计算。
4.2 从机地址冲突处理——当多个设备用同一个地址时
BME280和GT911都支持地址引脚(AD0/ADD),但有些传感器(如某些EEPROM)地址固定为0x50。OpenHarmony不支持I2C多主仲裁,必须用硬件方案解决:
方案1:I2C多路复用器(TCA9548A)
将TCA9548A挂在主I2C总线上,其8个通道各接一个从机。通过向TCA9548A写入通道号(0x01~0x08),选择激活哪个通道。在HCS中为每个从机配置独立busNum,如busNum = 1对应TCA9548A通道1。方案2:软件模拟I2C(bit-banging)
用GPIO模拟I2C时序,避开硬件控制器。在drivers/adapter/khdf/platform/i2c/gpio_i2c.c中实现,但性能差(最高10kHz),仅适用于低速设备如DS18B20。方案3:地址跳线+设备树动态加载
在PCB上为每个从机设计地址跳线(0Ω电阻选择AD0接GND/VCC),设备树中为不同跳线组合定义不同节点,启动时通过GPIO读取跳线状态,动态加载对应HCS配置。
我选方案1,因为TCA9548A成本低(¥2)、体积小(MSOP-16)、且OpenHarmony已有现成驱动(drivers/adapter/khdf/platform/i2c/tca9548a.c)。关键是HCS配置:
{ "i2c": { "bus": [ { "busNum": 0, "slaveAddr": 0x70, // TCA9548A地址 "channel": 1, "devices": [ { "name": "bme280", "addr": 0x76 }, { "name": "gt911", "addr": 0x5D } ] } ] } }4.3 电源噪声抑制——让I2C在电机启停时不丢包
工业场景中,电机启停会产生>100mV的电源噪声,导致I2C通信失败。我在智能机械臂项目中,发现舵机转动时BME280读数突变为0。解决方案:
- 硬件层:在I2C总线VCC端加π型滤波(10μF钽电容 + 100nF陶瓷电容 + 10Ω磁珠),SDA/SCL线上串接22Ω电阻抑制高频振铃;
- 驱动层:在
hi_i2c_transfer()函数中增加重试机制:for (int retry = 0; retry < 3; retry++) { ret = HiI2cTransfer(controller, msgs, num); if (ret == 0) break; // 成功 if (retry < 2) usleep(1000); // 重试间隔1ms } - 应用层:对关键传感器(如温度)采用“三次采样取中值”策略,避免单次异常值影响控制逻辑。
实测表明,加装滤波后,电机启停时I2C错误率从12%降至0.3%,且重试机制将单次通信耗时从10ms控制在15ms内(三次重试上限)。
4.4 日志与监控——把I2C变成可运维的模块
OpenHarmony默认不记录I2C详细日志。在drivers/adapter/khdf/platform/i2c/hi_i2c.c中添加:
#define HDF_LOGI(fmt, ...) HDF_LOG_IMPL(HDF_LOG_LEVEL_INFO, "HI_I2C", fmt, ##__VA_ARGS__) #define HDF_LOGE(fmt, ...) HDF_LOG_IMPL(HDF_LOG_LEVEL_ERROR, "HI_I2C", fmt, ##__VA_ARGS__) // 在HiI2cTransfer()入口添加 HDF_LOGI("transfer start: bus=%d, addr=0x%x, len=%d", controller->busNum, msgs[0].addr, msgs[0].len); // 在传输失败时添加 HDF_LOGE("transfer failed: ret=%d, status=0x%x", ret, readl(base + IC_RAW_INTR_STAT));编译后,通过hilog -a -v time | grep HI_I2C实时监控。我设置了一个简单的运维脚本:
#!/bin/sh # 监控I2C错误率 while true; do errors=$(hilog -t 1000 | grep "HI_I2C.*ERROR" | wc -l) total=$(hilog -t 1000 | grep "HI_I2C.*transfer" | wc -l) if [ $total -gt 0 ] && [ $(echo "$errors * 100 / $total" | bc) -gt 5 ]; then echo "I2C error rate >5% at $(date)" | wall # 触发复位I2C控制器 devmem 0x12120010 32 0 # IC_ENABLE = 0 sleep 0.1 devmem 0x12120010 32 1 # IC_ENABLE = 1 fi sleep 10 done这套机制让I2C从“黑盒”变成“白盒”,运维人员无需登录设备,仅看告警就能判断硬件老化程度。
5. 常见问题速查表与独家避坑指南
5.1 高频问题速查表
| 现象 | 可能原因 | 快速验证方法 | 解决方案 |
|---|---|---|---|
hdf_i2c_test -s扫不到任何设备 | 1. 上拉电阻缺失或阻值过大 2. 从机未供电 3. 设备树中 status = "disabled" | 万用表测SCL/SDA对VCC电阻;测从机VCC电压;dmesg | grep i2c看控制器是否注册 | 焊接4.7kΩ上拉电阻;检查从机电源;修改设备树status = "okay" |
I2cTransfer()返回-12(ENOMEM) | 1. HDF内存池耗尽 2. HCS中 bufferSize设置过小3. DMA缓冲区未对齐 | hdf_tool -i查看HDF内存使用率;检查HCS中bufferSize字段 | 增大HDF内存池(hdf_config.hcs中memoryPoolSize);HCS中bufferSize设为256以上;确保传输缓冲区地址4字节对齐 |
| BME280读数始终为0 | 1. 未写入控制寄存器启动测量 2. 从机地址错误(0x76 vs 0x77) 3. 温度补偿参数未加载 | hdf_i2c_test -b 0 -a 0x76 -r 0xF4 -l 1读控制寄存器;hdf_i2c_test -b 0 -a 0x76 -r 0xA0 -l 1读芯片ID | 先写0xF4=0x25启动测量;确认HCS中slaveAddr=118(0x76十进制);加载BME280校准参数 |
| GT911触摸无响应 | 1. RESET引脚未拉高 2. INT中断线未连接或配置错误 3. 触摸配置寄存器未初始化 | 万用表测GT911 RESET引脚电压;dmesg | grep gpio看中断是否注册;hdf_i2c_test -b 0 -a 0x5D -r 0x80 -l 1读触摸状态 | 焊接RESET到VCC;检查设备树中interrupts = <0 10 4>(GPIO10,下降沿);写0x80=0x01使能触摸 |
| 多个I2C设备间歇性失联 | 1. 总线电容过大(>400pF) 2. 从机地址冲突 3. 电源噪声干扰 | 示波器测SCL上升时间(>300ns需优化);hdf_i2c_test -s扫描地址;用示波器看VCC纹波 | 减少总线分支长度;使用TCA9548A隔离;加装π型滤波 |
5.2 我踩过的五个深坑(含解决方案)
坑1:HCS中slaveAddr的进制陷阱
现象:HCS写slaveAddr = 0x76,编译后hdf_i2c_test报-EINVAL。
真相:HCS规范明确要求slaveAddr为十进制整数,0x76会被解析为字符串"0x76",转换为整数时失败。
解法:HCS中必须写slaveAddr = 118(0x76的十进制),而非0x76或118(字符串形式)。
坑2:设备树#address-cells与HCS的隐式耦合
现象:BME280地址0x76,在设备树中reg = <0x76>,但HCS中slaveAddr = 118,通信失败。
真相:设备树中#address-cells = <1>表示地址宽度为1字节,HCS中slaveAddr必须是1字节值(0-255)。若#address-cells = <2>,则reg需写<0x00 0x76>,HCS中slaveAddr仍为118。
解法:统一设备树#address-cells = <1>,HCS中slaveAddr用十进制。
坑3:I2C控制器时钟源被动态关闭
现象:设备运行几小时后I2C突然失效,dmesg无报错,devmem读寄存器值全0。
真相: