OpenHarmony HDF驱动开发实战:MLX90614红外测温传感器I2C通信与设备树配置
发布时间:2026/10/3 12:23:37来源:尧图网络
1. 从一颗红外测温芯片说起为什么选MLX90614和OpenHarmony第一次拿到MLX90614这颗芯片的时候我其实没太当回事。TO-90封装四个引脚看起来比DS18B20还简单。但真正把它跑在OpenHarmony上从设备树配到HDI接口打通前后折腾了差不多一周。踩的坑不算少但回头看这套流程一旦跑通后面接任何I2C传感器都是复制粘贴的事。MLX90614是Melexis出的一款红外非接触式测温芯片出厂就做了温度校准直接输出数字量不需要外部运放和ADC。测温范围覆盖-70到380摄氏度医疗级精度能到±0.2度。它挂在I2C总线上从机地址默认0x5A读的是内部RAM里的物体温度和芯片自身温度。相比热电堆加运放自己搭电路这颗芯片省掉了模拟前端的所有麻烦特别适合做额温枪、工业测温节点、智能家居里的环境感知模块。OpenHarmony这边驱动开发走的是HDIHardware Driver Interface那一套。和传统Linux字符设备驱动不太一样OpenHarmony把驱动分成了内核态和用户态两部分传感器类的外设通常走用户态驱动模型通过HDFHardware Driver Foundation框架来注册设备、绑定配置、暴露接口。这意味着你不能像写Linux驱动那样直接register_chrdev完事得按HDF的规矩来写驱动入口、实现Bind和Init、配置HCS文件、注册设备节点。这套东西对刚接触OpenHarmony的人来说门槛主要在三个地方一是HDF的驱动模型和Linux内核驱动思维不一样二是设备树HCS的语法和Linux DTS有差异三是I2C通信在用户态怎么发起、怎么保证时序。我写这篇东西就是想把这三块串起来给正在做OpenHarmony外设驱动的朋友一个能直接抄的参考。不管你是刚上手OpenHarmony的嵌入式工程师还是从STM32裸机转过来的老手只要手上有MLX90614和一块跑OpenHarmony的开发板跟着走就能跑通。2. 整体设计思路为什么这么搭2.1 驱动架构选型用户态HDF还是内核态OpenHarmony的驱动开发有两条路内核态驱动和用户态驱动。内核态驱动跑在Linux内核里性能好但调试麻烦崩了直接挂系统。用户态驱动跑在用户空间通过HDI接口和内核通信调试方便出问题最多进程挂掉系统还在。MLX90614这种传感器数据更新率撑死几十HzI2C速率100kHz到400kHz对实时性要求不高。所以我选的是用户态HDF驱动模型。具体来说驱动作为一个独立进程运行通过HDF框架提供的I2C控制器接口去读写硬件。这样做的好处是第一调试的时候可以直接用printf打日志不用搞内核printk那一套第二驱动崩溃不会拖垮整个系统第三升级驱动不用重新编译内核。代价也有每次I2C读写都要走一次用户态到内核态的切换延迟比内核态驱动高。但MLX90614的读取周期本来就是毫秒级这点开销完全可以接受。2.2 I2C通信方案硬件控制器还是GPIO模拟OpenHarmony的HDF框架提供了标准I2C控制器接口底层对接的是SoC的硬件I2C控制器。我用的开发板是RK3568板子上引出了几路I2C其中I2C3接了一个OLEDI2C5空着正好拿来接MLX90614。为什么不选GPIO模拟I2C因为硬件I2C控制器有现成的HDF驱动配置好设备树就能用不用自己写时序。而且硬件I2C的时钟稳定性比GPIO模拟好得多MLX90614对时序虽然不苛刻但SCL频率抖动太大会导致读出来的温度值跳变。实测下来硬件I2C在400kHz下读MLX90614连续读100次温度波动在0.1度以内GPIO模拟的话波动能到0.5度。当然如果你板子上的硬件I2C都被占完了或者需要接多个同地址的MLX90614通过I2C多路复用器切换那GPIO模拟也是可行的。OpenHarmony的HDF框架也支持GPIO模拟I2C但需要自己实现一套bit-banging的时序这个后面再说。2.3 设备树配置思路HCS文件怎么写OpenHarmony的设备树用的是HCSHardware Configuration Source格式语法上像DTS但又不完全一样。HCS文件分三层板级配置、驱动配置、设备配置。板级配置描述SoC的I2C控制器驱动配置描述MLX90614驱动本身设备配置把驱动和具体的I2C总线、从机地址绑在一起。我一开始犯了个错把MLX90614的设备节点直接写在板级I2C控制器下面结果驱动加载的时候找不到设备。后来才搞明白OpenHarmony的HDF框架要求设备节点必须挂在对应的驱动模型下面通过match_attr来匹配。正确的做法是在device_info里声明设备在host里声明驱动然后用match_attr把两者关联起来。2.4 数据读取策略轮询还是中断MLX90614没有中断引脚只能轮询。但轮询频率不能太高因为芯片内部有温度转换周期典型值是100ms左右。如果你每10ms读一次读到的可能是上一次的转换结果甚至读到无效数据。我的做法是驱动内部起一个定时器每200ms读一次读到的数据缓存起来。上层应用通过HDI接口拿数据的时候直接返回缓存值。这样既保证了数据新鲜度又不会因为频繁I2C读写拖慢系统。如果你需要更快的响应可以把轮询周期降到100ms但再低就没意义了芯片本身转换不过来。3. 核心细节解析与实操要点3.1 MLX90614的I2C通信协议细节MLX90614的I2C通信遵循标准协议但有几个细节容易踩坑。第一从机地址。出厂默认是0x5A7位地址但如果你买的是带PWM输出的版本地址可能被改成0x5B。读之前先用i2cdetect扫一下总线确认地址。第二寄存器地址。MLX90614的内部RAM地址是8位但读写的时候要发16位低8位是RAM地址高8位是命令。比如读物体温度RAM地址是0x07发送的时候要发0x07 0x00先发低字节再发高字节。读芯片自身温度是0x06 0x00。第三读写时序。MLX90614支持标准I2C时序但有一个特殊点读操作的时候先写地址写操作然后重复起始条件再读数据。这个和很多I2C传感器一样但MLX90614要求两次传输之间不能有stop条件必须是重复起始。第四数据格式。读出来的温度是16位低字节在前高字节在后。实际温度值 原始值 × 0.02 - 273.15。比如读到0x3B 0x0C原始值是0x0C3B 31313131 × 0.02 62.62减去273.15 -210.53不对这里我算错了。实际上MLX90614的输出是开尔文温度的0.02倍所以温度 原始值 × 0.02。0x0C3B 31313131 × 0.02 62.62K换算成摄氏度是62.62 - 273.15 -210.53度这显然不对。正确的计算是原始值 × 0.02 开尔文温度摄氏度 开尔文 - 273.15。但MLX90614的输出范围是-70到380摄氏度对应开尔文是203到653K乘以0.02后是4.06到13.06。所以原始值应该在203到653之间。如果读到3131那肯定是读错了。后来发现是我把高低字节搞反了应该是高字节在前。重新读高字节0x3B低字节0x0C原始值0x3B0C 1511615116 × 0.02 302.32K302.32 - 273.15 29.17度。这才对。注意MLX90614的I2C读写寄存器地址是16位低字节在前。温度数据是16位高字节在前。这两个顺序不一样很容易搞混。3.2 HDF驱动框架的入口和绑定OpenHarmony的HDF驱动入口是一个HdfDriverEntry结构体里面有三个函数指针Bind、Init、Release。Bind函数在驱动加载的时候调用用来做设备绑定。对于I2C设备通常在这里获取I2C控制器句柄初始化设备结构体。Init函数在Bind之后调用用来做实际的硬件初始化比如配置MLX90614的寄存器、启动轮询定时器。Release函数在驱动卸载的时候调用用来释放资源。我一开始把I2C控制器的获取放在Init里结果发现Init调用的时候I2C控制器还没准备好。后来改到Bind里先DeviceGetService拿到I2C控制器再在Init里用。这个顺序不能反。static int32_t MlX90614Bind(struct HdfDeviceObject *device) { if (device NULL) { return HDF_ERR_INVALID_OBJECT; } struct MlX90614DrvData *drvData (struct MlX90614DrvData *)OsalMemCalloc(sizeof(*drvData)); if (drvData NULL) { return HDF_ERR_MALLOC_FAIL; } drvData-i2cHandle NULL; drvData-device device; device-service drvData-service; return HDF_SUCCESS; }Bind里只做内存分配和service挂载不碰硬件。硬件操作全部放到Init里。3.3 HCS设备树配置的坑HCS文件的语法和DTS很像但有几个关键区别。第一HCS用{}包裹节点用[]包裹数组用赋值。DTS用{}和。比如配置I2C从机地址HCS写reg [0x5A]DTS写reg 0x5A。第二HCS的match_attr是字符串用来匹配驱动和设备。驱动在HdfDriverEntry里声明moduleName设备在HCS里声明match_attr两者一致才能绑定。第三HCS的device_info里要声明hosthost里要声明device。我一开始只写了device没写host结果驱动加载了但设备没注册。正确的HCS配置大概长这样device_mlx90614 :: device { device0 :: deviceNode { policy 2; priority 100; preload 0; permission 0664; moduleName mlx90614_driver; serviceName mlx90614_service; deviceMatchAttr mlx90614_config; } } mlx90614_config :: config { i2cBus 5; i2cAddr 0x5A; pollInterval 200; }policy 2表示驱动对内核和用户态都可见。preload 0表示不预加载按需加载。moduleName要和驱动代码里的moduleName一致。3.4 I2C读写接口的封装OpenHarmony的HDF框架提供了I2cTransfer接口但直接调用比较繁琐需要自己组I2cMsg数组。我封装了两个函数Mlx90614ReadReg和Mlx90614WriteReg。static int32_t Mlx90614ReadReg(struct MlX90614DrvData *drvData, uint8_t reg, uint8_t *data, uint16_t len) { int32_t ret; uint8_t regBuf[2] {reg, 0x00}; struct I2cMsg msg[2] { { .addr drvData-i2cAddr, .buf regBuf, .len 2, .flags 0, }, { .addr drvData-i2cAddr, .buf data, .len len, .flags I2C_FLAG_READ, }, }; ret I2cTransfer(drvData-i2cHandle, msg, 2); if (ret ! 2) { HDF_LOGE(Mlx90614ReadReg failed, ret %d, ret); return HDF_FAILURE; } return HDF_SUCCESS; }这里的关键是msg数组有两个元素第一个是写寄存器地址第二个是读数据。flags字段写操作是0读操作是I2C_FLAG_READ。I2cTransfer的返回值是成功传输的msg数量如果是2说明两次都成功了。注意I2cTransfer的返回值不是0表示成功而是返回实际传输的msg数量。如果返回1说明只成功了第一次写第二次读失败了。这个和很多I2C接口不一样容易误判。4. 实操过程与核心环节实现4.1 环境准备和编译配置我用的开发板是RK3568OpenHarmony版本是3.2 Release。编译环境是Ubuntu 20.04代码拉的是官方仓库的master分支。第一步在vendor/xxx/rk3568目录下找到板级配置确认I2C5没有被其他驱动占用。如果被占了要么换一路I2C要么把占用的驱动关掉。第二步在drivers/peripheral/sensor目录下新建mlx90614文件夹把驱动代码放进去。目录结构大概是drivers/peripheral/sensor/mlx90614/ ├── BUILD.gn ├── include/ │ └── mlx90614.h └── src/ └── mlx90614.c第三步修改BUILD.gn把驱动编译成动态库。关键配置是ohos_shared_library(mlx90614_driver)依赖//drivers/hdf_core/framework/core:hdfframework和//drivers/hdf_core/framework/support/platform:i2c。第四步在板级配置的hdf_config里引入MLX90614的HCS文件。通常是在vendor/xxx/rk3568/hdf_config/khdf/hdf_default.hcb里加一行include mlx90614.hcs。第五步编译整个系统烧录。启动后用hdc shell连上板子执行ls /dev/hdf看看有没有mlx90614设备节点。如果没有说明驱动没加载成功需要看内核日志。4.2 驱动加载和调试驱动加载失败是最常见的问题。我总结了几种情况第一种moduleName不匹配。驱动代码里写的是mlx90614_driverHCS里写的是mlx90614两者不一致驱动不会绑定。解决办法是统一命名建议用下划线分隔。第二种I2C控制器没使能。RK3568的I2C5默认是关闭的需要在设备树里把status改成okay。这个在kernel/linux/arch/arm64/boot/dts/rockchip/rk3568.dtsi里改。第三种从机地址不对。MLX90614的默认地址是0x5A但有些模块出厂时改了地址。用i2cdetect -y 5扫一下确认地址。第四种权限问题。OpenHarmony的HDF设备节点默认权限是0660普通用户访问不了。要么改成0666要么把应用加到hdf用户组。调试的时候我习惯在Init函数里加一句HDF_LOGI(Mlx90614Init enter)在Bind里加HDF_LOGI(Mlx90614Bind enter)。启动后看日志如果只看到Bind没看到Init说明设备匹配失败如果两个都没看到说明驱动根本没加载。4.3 温度读取的完整流程温度读取的完整流程分四步第一步驱动初始化。在Init函数里先调用Mlx90614ReadReg读一下芯片ID地址0x1E确认通信正常。MLX90614的ID应该是0x5A。如果读不到说明I2C通信有问题。第二步配置轮询定时器。用OsalTimerCreate创建一个定时器周期200ms回调函数里调用Mlx90614ReadTemp。第三步Mlx90614ReadTemp里读物体温度地址0x07和芯片温度地址0x06计算实际温度值存到drvData-objectTemp和drvData-ambientTemp。第四步上层应用通过HDI接口拿数据。HDI接口的实现里直接返回drvData里缓存的值。static int32_t Mlx90614ReadTemp(struct MlX90614DrvData *drvData) { uint8_t buf[3] {0}; int32_t ret; uint16_t raw; float temp; ret Mlx90614ReadReg(drvData, 0x07, buf, 3); if (ret ! HDF_SUCCESS) { return ret; } raw (buf[1] 8) | buf[0]; temp raw * 0.02 - 273.15; drvData-objectTemp temp; ret Mlx90614ReadReg(drvData, 0x06, buf, 3); if (ret ! HDF_SUCCESS) { return ret; } raw (buf[1] 8) | buf[0]; temp raw * 0.02 - 273.15; drvData-ambientTemp temp; return HDF_SUCCESS; }这里读3个字节第一个字节是低字节第二个是高字节第三个是PEC校验字节。PEC可以忽略但读的时候必须读3个字节否则MLX90614会一直拉低SDA。注意MLX90614的读操作必须读3个字节即使你不需要PEC。如果只读2个字节芯片会认为传输未完成下次通信会出错。4.4 设备树配置的完整示例完整的HCS配置分三部分板级I2C控制器、驱动设备节点、驱动配置。板级I2C控制器配置在device_info里i2c5 :: i2c { match_attr rockchip_i2c5; busNum 5; regBase 0xFE5C0000; regSize 0x1000; irqNum 75; clkFreq 100000000; speed 400000; }驱动设备节点配置device_mlx90614 :: device { device0 :: deviceNode { policy 2; priority 100; preload 0; permission 0664; moduleName mlx90614_driver; serviceName mlx90614_service; deviceMatchAttr mlx90614_config; } }驱动配置mlx90614_config :: config { i2cBus 5; i2cAddr 0x5A; pollInterval 200; tempMin -70; tempMax 380; }i2cBus要和板级配置里的busNum一致。i2cAddr是7位地址不要左移。pollInterval单位是毫秒。4.5 编译和烧录的注意事项编译OpenHarmony的时候如果只改了驱动代码不需要全量编译。用hb build加上--target参数只编译驱动模块。全量编译一次要半小时增量编译几分钟就搞定。烧录的时候如果只改了HCS文件不需要重新烧录整个系统。HCS文件编译后会生成.hcb文件放在/vendor/etc/hdfconfig/目录下。用hdc file send把新的.hcb推上去重启hdf_devhost进程就行。hdc file send out/rk3568/vendor/etc/hdfconfig/mlx90614.hcb /vendor/etc/hdfconfig/ hdc shell killall hdf_devhosthdf_devhost会自动重启重新加载HCS配置。5. 常见问题与排查技巧实录5.1 I2C通信失败排查表现象可能原因排查方法解决办法I2cTransfer返回-1I2C控制器未使能ls /dev/i2c-5看设备节点是否存在在DTS里把I2C5的status改成okayI2cTransfer返回1从机无应答用i2cdetect -y 5扫总线检查MLX90614供电和上拉电阻读到的温度值固定不变轮询定时器未启动看日志里有没有定时器回调检查OsalTimerCreate返回值温度值跳变超过1度I2C速率太高把speed从400k降到100k修改HCS里的speed字段驱动加载后无设备节点moduleName不匹配对比驱动代码和HCS里的moduleName统一命名应用打开设备失败权限不足ls -l /dev/hdf看权限把permission改成06665.2 温度读数异常的几种情况第一种读数一直是-273.15。这是原始值为0的情况说明I2C读回来的全是0。原因通常是SDA或SCL线没接好或者上拉电阻没焊。MLX90614的I2C需要4.7k上拉有些模块自带有些不带。第二种读数在0度附近跳。这是环境温度不是物体温度。检查一下读的是0x07还是0x06。0x07是物体温度0x06是芯片温度。如果你对着空气读物体温度确实接近环境温度。第三种读数超过380度。MLX90614的量程是-70到380度超过这个范围输出会饱和。如果读到400度以上说明原始值计算错了检查高低字节顺序。第四种读数波动大。除了I2C速率还有一个原因是电源纹波。MLX90614对电源敏感建议在VCC和GND之间加一个0.1uF的陶瓷电容。5.3 HDF驱动加载失败的排查思路驱动加载失败先看内核日志。用dmesg | grep hdf过滤HDF相关的日志。常见的错误码HDF_ERR_INVALID_OBJECT设备对象为空检查Bind函数的参数。HDF_ERR_MALLOC_FAIL内存分配失败通常是内存不够。HDF_ERR_NOT_SUPPORT不支持的操作检查HCS配置里的policy。HDF_ERR_IOI2C读写失败检查硬件连接。如果日志里什么都没有说明驱动根本没被加载。检查BUILD.gn里有没有把驱动加到编译目标里检查hdf_default.hcb里有没有include对应的HCS文件。5.4 实操心得几个省时间的技巧第一个先用i2cget和i2cset命令行工具验证硬件。在板子上执行i2cget -y 5 0x5A 0x07 w如果能读到数据说明硬件没问题问题在驱动代码。如果读不到先查硬件。第二个驱动代码里加一个/proc节点把原始温度值、计算后的温度值、I2C错误计数都暴露出来。调试的时候cat /proc/mlx90614就能看到所有信息比看日志方便。第三个HCS文件改完后不用重新编译整个系统。用hdc shell mount -o rw,remount /把根文件系统改成可写然后直接推.hcb文件重启hdf_devhost就行。这个技巧能省掉每次改配置都要烧录的麻烦。第四个如果I2C总线上挂了多个设备用i2cdetect扫的时候注意地址冲突。MLX90614默认0x5A有些OLED也是0x5A两个挂一起会冲突。解决办法是用I2C多路复用器或者改MLX90614的地址写0x2E寄存器可以改地址但改完就回不去了慎用。5.5 性能优化的几个方向如果你觉得200ms的轮询周期还是太慢可以试试这几个优化第一把I2C速率提到400kHz。MLX90614支持400kHz实测下来读取一次温度大概2ms200ms周期里大部分时间都在等定时器。第二用DMA传输。OpenHarmony的I2C控制器支持DMA但HDF框架默认用的是中断模式。要改DMA的话得改I2C控制器的驱动这个工作量比较大不建议新手折腾。第三降低轮询频率。MLX90614的内部转换周期是100ms你200ms读一次已经够了。再快也不会更准反而增加I2C总线负载。第四如果多个MLX90614挂同一条总线用I2C多路复用器切换。每次切换后要等1ms让总线稳定再发起通信。6. 从MLX90614到其他I2C传感器的移植思路MLX90614跑通之后我接着把BH1750光照传感器和SHT30温湿度传感器也挂到了同一条I2C总线上。整个过程基本就是复制MLX90614的驱动框架改几个地方第一改HCS里的从机地址和寄存器地址。BH1750的地址是0x23SHT30是0x44。寄存器地址各不一样但读写流程一样。第二改数据解析逻辑。MLX90614是16位温度值BH1750是16位光照值SHT30是16位温湿度值。解析公式不同但I2C读写接口可以复用。第三改轮询周期。BH1750的转换时间最长180msSHT30是15ms。根据芯片手册调整pollInterval。第四改HDI接口的数据结构。MLX90614返回一个float温度值BH1750返回一个uint16光照值SHT30返回两个float。HDI接口的Read函数里根据设备类型返回不同的数据。这套框架跑通之后接任何I2C传感器都是半天的事。关键是理解HDF的驱动模型Bind里拿资源Init里初始化硬件HDI接口暴露数据。剩下的就是填芯片手册里的寄存器地址和计算公式。提示如果你要接多个同型号的MLX90614比如做多点测温阵列有两个办法。一是用I2C多路复用器每个通道挂一个地址都是0x5A。二是改MLX90614的地址写0x2E寄存器把地址改成0x5B、0x5C等。但改地址是不可逆的改之前想清楚。7. 我个人在实际操作中的体会MLX90614这颗芯片硬件上没什么好说的四个引脚接上拉电阻供电3.3V就能跑。真正花时间的是OpenHarmony的HDF驱动框架。我一开始用Linux驱动的思维去写在probe函数里直接操作I2C结果编译都过不了。后来把HDF的文档翻了两遍才搞明白Bind和Init的分工。HCS文件也是个大坑。语法和DTS像但细节不一样。我建议你写HCS的时候先找一个现成的传感器驱动抄一遍比如OpenHarmony源码里自带的bmp180或者ap3216c。抄的时候注意match_attr和moduleName的对应关系这两个不匹配驱动永远加载不了。调试的时候hdc shell和dmesg是你的好朋友。驱动加载失败先看dmesg | grep hdf再看ls /dev/hdf。如果设备节点存在但应用打不开检查权限。如果权限没问题但读不到数据用i2cget命令行工具验证硬件。最后分享一个小技巧在驱动里加一个ioctl接口允许上层应用动态修改轮询周期和I2C速率。这样调试的时候不用重新编译驱动直接改参数就行。我加了这个接口之后调MLX90614的响应速度从半天缩短到半小时。这个内容后续还可以这样扩展把MLX90614的数据通过OpenHarmony的分布式软总线传到另一台设备上做远程测温。或者结合OpenHarmony的AI框架做温度异常检测。这些等我把基础驱动跑稳了再折腾。
网站建设高端定制企业官网