ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

红外测温MLX90614芯片驱动开发全流程—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】

红外测温MLX90614芯片驱动开发全流程—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】 环境光会读了。今天同一条总线上换红外测温。这颗芯片对速率有上限配快了目录还在、驱动却绑不上去。现场是这样开始的。dts 写了mlx906145als /sys/bus/i2c/devices能看见1-005a应用按路径去读in_temp_object_raw文件不存在。有人说芯片坏了因为「节点都出来了」。节点出来只说明内核根据设备树实例化了 i2c_client不说明这颗硅在 400 kHz 下 ACK 过。ls -l /sys/bus/i2c/devices/1-005a/driver # ls: cannot access .../driver: No such file or directory dmesg | grep -i mlx # 空或者 failed to read config hexdump -C /proc/device-tree/i2cfe5a0000/clock-frequency # 00 06 1a 8000 06 1a 80是大端 400000。MLX90614 走 SMBus规格上限100 kHz。400 kHz 下读配置寄存器静默失败驱动放弃 probeclient 目录还在。把clock-frequency改成100000重打boot_linux并且刷 resource 里那份 dtb软链才出现ls -l /sys/bus/i2c/devices/1-005a/driver # .../1-005a/driver - ../../../../bus/i2c/drivers/mlx90614 hexdump -C /proc/device-tree/i2cfe5a0000/clock-frequency # 00 01 86 a0物体温度在 RAM0x07总线上是三字节LSB、MSB、PEC。换算T(°C) raw * 0.02 - 273.15环境温度在 RAM 0x06同一公式。透镜对准手掌或额头13 cm物体通道会到 3236 °C 一带对准桌面物体通道掉回室温附近。这是验收不是装饰。1. 这颗芯片在干什么MLX90614 是非接触红外温度计。封装顶部那颗透镜把视场里的红外收进热电堆芯片内部已经做完放大和数字化用户只读 RAM。I2C 7 位地址0x5a出厂写死改地址要写 EEPROMbring-up 不要碰。RAM 里和温度有关的字命令字节就是地址本身命令内容0x04原始 IR 通道 10x05原始 IR 通道 2单探头型号常不用0x06环境温度 Ta0x07物体温度 Tobj10x08物体温度 Tobj2双探头每个字 16 位。读周期在 SMBus 上是S 0x5aW ACK cmd ACK Sr 0x5aR ACK LSB ACK MSB ACK PEC NAK P用户态一次组合读 3 字节就能拿到 LSB、MSB、PEC。PEC 是 CRC-8多项式 0x07覆盖「地址W、命令、地址R、LSB、MSB」。bring-up 可以先不校验数字对了再加。内核i2c_smbus_read_word_data只要前两个数据字节PEC 由 SMBus 层处理。16 位 raw 是开尔文的 50 倍T_K raw * 0.02 T_C T_K - 273.15 raw * 0.02 - 273.15raw 0x3AEB 15083 时15083 × 0.02 − 273.15 ≈ 28.5 °C像室内桌面。手掌贴到 13 cm物体 raw 会到 0x3B80 一带大约 34 °C。全 0 或全 10xFFFF优先当总线失败不要换算成 −273 °C 或 1030 °C 拿去吓人。上电大约 250 ms 之内读数不稳定。写过 EEPROM 要等 20 ms。睡眠唤醒要把 SDA 拉低约 34 ms板级没接 wakeup GPIO 就别进睡眠。视场宽。13 cm 对准皮肤周围墙壁、灯光会掺进去。贴到 0 cm 堵死透镜或者 20 cm 对着空气物体通道都会往环境温度靠。现场只认这一条手掌 13 cm 和桌面两个数对不上才叫通。2. 通信怎么走应用 / 命令行工具 │ open ioctl(I2C_RDWR) 或者 read(sysfs) ▼ /dev/i2c-1 /sys/bus/iio/devices/iio:deviceN │ │ ▼ ▼ i2c-rk3xi2c1必须 100 kHz ← 内核 mlx90614.c │ SDA / SCL ▼ MLX90614 0x5a ├─ RAM 0x06 Ta └─ RAM 0x07 Tobj1 LSB MSB PEC两条路读的是同一组 RAM。IIO 通了用 IIO省得自己拆 PEC。IIO 没 bind就用户态组合读 3 字节。不要两条路同时改 EEPROM。组合读i2cRdReg先写命令字节 0x07Repeated Start 再读。不要对 0x07 做裸读——内部指针不一定停在物体温度。HDC1080 触发之后必须裸读那是另一颗芯片的规矩别混。i2c1 上还有 0x10 / 0x40 / 0x57 / 0x73。总线允许共存。CAN0 和 i2c1 共脚 GPIO0_B3/B4开 CAN 的镜像上 0x5a 从世界上消失先确认/dev/i2c-1对应的适配器还活着。3. 设备树和内核开关板级文件device/board/rk/rk3568_evb/kernel/rk3568-evb-linux.dtsi2c1 { pinctrl-0 i2c1_xfer; status okay; /* 开 CAN0 时必须 disabled */ clock-frequency 100000; /* 禁止 400000 */ mlx90614: mlx906145a { compatible melexis,mlx90614; reg 0x5a; status okay; }; };compatible必须对上drivers/iio/temperature/mlx90614.c的 of_match。CONFIG_MLX90614y写进 defconfig三处源码、out/里那份、板级build_kernel.sh合并段。只在 menuconfig 打开下次重建会丢。改clock-frequency是改 dts不是改驱动。生效 dtb 在resource 分区的rk-kernel.dtbboot_linux的 extlinux 里往往还有一份。两处都刷。只刷 Image、设备树还是 400 kHz现象不变1-005a目录在driver软链没有。内核驱动用i2c_smbus_read_word_data读 0x06 / 0x07把 raw 暴露成in_temp_ambient_raw in_temp_object_rawIIO 的 scale 在这颗驱动里按毫开尔文处理过板级实测 raw 仍是那份 16 位字用户态乘 0.02 减 273.15 和手册一致。不要再乘一遍 sysfs 的_scale除非你核对过当前内核把 scale 写成了什么。现场以 raw 公式为准。4. 当时那次 400 kHz命令是这么走的模块插在 i2c1 排针3.3 V 有电。hdchdc shell ls -l /dev/i2c-1 # crw-rw-rw- 1 root root 89, 1 ... /dev/i2c-1 ls /sys/bus/i2c/devices/ # 1-0010 1-0040 1-0057 1-005a i2c-0 i2c-1 i2c-21-005a在。以为稳了。ls -l /sys/bus/i2c/devices/1-005a/driver # No such file or directory cat /sys/bus/i2c/devices/1-005a/name # mlx90614name 来自 dts不是 ACK。扫总线i2cdetect -y -r 1 # 没有 i2cdetect 就 /data/local/tmp/i2c_tool scan 1400 kHz 下格子可能是--也可能时有时无。不要用一次 ACK 当结论。看生效速率find /proc/device-tree -name clock-frequency | while read f; do echo $f hexdump -C $f | head -1 donei2c1 那条若是00 06 1a 80就是 400 kHz。改 dtsclock-frequency 100000;删内核 checkpoint改 board dts 不会自动让 GN 重跑内核动作重打boot_linux.img刷 p5resource.img刷 p4。刷之前hdc shell cat /proc/partitions hdc shell mount | grep on / SD 启动常见mmcblk1eMMC 是mmcblk0。dd必须/dev/block/mmcblkXpY不要写成/dev/mmcblkXpY在 tmpfs 里建一个假文件。复位后再看hexdump -C /proc/device-tree/i2cfe5a0000/clock-frequency # 00 01 86 a0 ls -l /sys/bus/i2c/devices/1-005a/driver # .../drivers/mlx90614 dmesg | grep -i mlx # mlx90614 1-005a: ...IIO 名字按名字找不要写死iio:device5for d in /sys/bus/iio/devices/iio:device*; do echo $d $(cat $d/name 2/dev/null) done找到mlx90614那颗BASE... # 上一步的路径 cat $BASE/in_temp_object_raw cat $BASE/in_temp_ambient_raw手算一遍。假设物体 raw 1528015280 * 0.02 - 273.15 32.45 °C对准手掌 13 cm 再 cat 一次物体通道必须往上走对准桌面往回掉。环境通道几乎不动它看的是芯片自身温度。权限。hdc 是 root。HAP 读 sysfs 要 0666放进board-perm.sh进 vendor。开发期chmod 0666 $BASE/in_temp_object_raw $BASE/in_temp_ambient_raw $BASE/name chmod 0666 /dev/i2c-15. 用户态三字节LSB MSB PECIIO 没 bind 时走/dev/i2c-1。I2C 三件套里这里只用组合读。命令 0x07长度 3。uint8_t buf[3]; if (i2cRdReg(fd, 0x5a, 0x07, buf, 3) 0) /* NACK速率、供电、地址、CAN 抢脚 */ raw buf[0] | (buf[1] 8); /* LSB 在前 */ pec buf[2]; t raw * 0.02 - 273.15;PEC 计算CRC-8多项式 0x07初值 0覆盖 5 个字节{0xB4, cmd, 0xB5, LSB, MSB}。0xB4是 0x5a 左移加写位0xB5是读位。bring-up 阶段 PEC 错了先打印出来不要直接当温度失败——有的适配器在读第 3 字节时已经发了 NAKPEC 会脏。温度数字随手掌变化PEC 以后再严。不要用I2C_SLAVEread()去读这颗。没有 Repeated Start芯片会把命令字节理解错。I2C_RDWR两条 msg第一条写 cmd第二条带I2C_M_RD读 3 字节。读 0x06 环境温度同一套把 cmd 换成 0x06。6. mlx90614_read.c整文件可编。优先 IIO加参数i2c走三字节loop每秒打一行方便对准、移开。/* mlx90614_read.c — OpenHarmony 4.132 位 musl * mlx90614_read # IIO没有则 i2c-dev * mlx90614_read i2c # 强制 3 字节 RAM * mlx90614_read loop # 每秒一行 */ #include stdio.h #include stdlib.h #include stdint.h #include string.h #include dirent.h #include fcntl.h #include unistd.h #include errno.h #include sys/ioctl.h #ifndef I2C_RDWR #define I2C_RDWR 0x0707 #endif #ifndef I2C_M_RD #define I2C_M_RD 0x0001 #endif #define ADDR 0x5a #define CMD_TA 0x06 #define CMD_TOBJ1 0x07 struct i2c_msg { uint16_t addr; uint16_t flags; uint16_t len; uint8_t *buf; }; struct i2c_rdwr_ioctl_data { struct i2c_msg *msgs; uint32_t nmsgs; }; static int i2cWr(int fd, uint8_t addr, uint8_t reg, const uint8_t *data, int len) { uint8_t b[32]; if (len 0 || len 31) return -1; b[0] reg; if (data len 0) memcpy(b 1, data, (size_t)len); struct i2c_msg m { addr, 0, (uint16_t)(len 1), b }; struct i2c_rdwr_ioctl_data d { m, 1 }; return ioctl(fd, I2C_RDWR, d); } static int i2cRdReg(int fd, uint8_t addr, uint8_t reg, uint8_t *data, int len) { struct i2c_msg m[2] { { addr, 0, 1, reg }, { addr, I2C_M_RD, (uint16_t)len, data }, }; struct i2c_rdwr_ioctl_data d { m, 2 }; return ioctl(fd, I2C_RDWR, d); } static int i2cRdPlain(int fd, uint8_t addr, uint8_t *data, int len) { struct i2c_msg m { addr, I2C_M_RD, (uint16_t)len, data }; struct i2c_rdwr_ioctl_data d { m, 1 }; return ioctl(fd, I2C_RDWR, d); } static uint8_t crc8(const uint8_t *p, int n) { uint8_t crc 0; int i, b; for (i 0; i n; i) { crc ^ p[i]; for (b 0; b 8; b) { if (crc 0x80) crc (uint8_t)((crc 1) ^ 0x07); else crc 1; } } return crc; } static int pec_ok(uint8_t cmd, uint8_t lsb, uint8_t msb, uint8_t pec) { uint8_t s[5] { (uint8_t)(ADDR 1), cmd, (uint8_t)((ADDR 1) | 1), lsb, msb }; return crc8(s, 5) pec; } static double raw_to_c(unsigned raw) { return raw * 0.02 - 273.15; } static int read_file_int(const char *path, int *out) { char buf[64]; int fd open(path, O_RDONLY); if (fd 0) return -1; ssize_t n read(fd, buf, sizeof(buf) - 1); close(fd); if (n 0) return -1; buf[n] 0; *out atoi(buf); return 0; } static int find_iio(const char *want, char *out, size_t outlen) { DIR *d opendir(/sys/bus/iio/devices); struct dirent *e; if (!d) return -1; while ((e readdir(d)) ! NULL) { char namep[160], name[64]; int fd; ssize_t n; char *nl; if (strncmp(e-d_name, iio:device, 10) ! 0) continue; snprintf(namep, sizeof(namep), /sys/bus/iio/devices/%s/name, e-d_name); fd open(namep, O_RDONLY); if (fd 0) continue; n read(fd, name, sizeof(name) - 1); close(fd); if (n 0) continue; name[n] 0; nl strchr(name, \n); if (nl) *nl 0; if (strstr(name, want)) { snprintf(out, outlen, /sys/bus/iio/devices/%s, e-d_name); closedir(d); return 0; } } closedir(d); return -1; } static int read_iio_once(void) { char base[128], p[160]; int obj, amb; if (find_iio(mlx90614, base, sizeof(base)) 0) return -1; snprintf(p, sizeof(p), %s/in_temp_object_raw, base); if (read_file_int(p, obj) 0) { fprintf(stderr, 读 object 失败: %s (%s)\n, p, strerror(errno)); return -1; } snprintf(p, sizeof(p), %s/in_temp_ambient_raw, base); if (read_file_int(p, amb) 0) amb -1; printf(IIO %s\n, base); printf(物体 raw%d %.2f °C\n, obj, raw_to_c((unsigned)obj)); if (amb 0) printf(环境 raw%d %.2f °C\n, amb, raw_to_c((unsigned)amb)); printf(对准手掌 1-3cm 再跑物体通道必须升高。\n); return 0; } static int read_ram(int fd, uint8_t cmd, unsigned *raw, uint8_t *pec, int *pok) { uint8_t buf[3]; if (i2cRdReg(fd, ADDR, cmd, buf, 3) 0) return -1; *raw buf[0] | ((unsigned)buf[1] 8); *pec buf[2]; *pok pec_ok(cmd, buf[0], buf[1], buf[2]); return 0; } static int read_i2c_once(void) { unsigned obj 0, amb 0; uint8_t pec_o 0, pec_a 0; int ok_o 0, ok_a 0; int fd open(/dev/i2c-1, O_RDWR); if (fd 0) { fprintf(stderr, open /dev/i2c-1: %s\n, strerror(errno)); return -1; } /* i2cWr / i2cRdPlain 在这颗芯片上用不到留着和工具链其它传感器共用 */ (void)i2cWr; (void)i2cRdPlain; if (read_ram(fd, CMD_TOBJ1, obj, pec_o, ok_o) 0) { fprintf(stderr, 读 0x07 NACK100kHz模块CAN 抢 i2c1\n); close(fd); return -1; } if (read_ram(fd, CMD_TA, amb, pec_a, ok_a) 0) { fprintf(stderr, 读 0x06 NACK\n); close(fd); return -1; } close(fd); if (obj 0 || obj 0xFFFF) { fprintf(stderr, 物体 raw0x%04x当总线失败不换算\n, obj); return 1; } printf(i2c-dev 0x5a RAM 0x07 LSB/MSB/PEC\n); printf(物体 raw%u PEC0x%02x %s %.2f °C\n, obj, pec_o, ok_o ? crc_ok : crc_skip_or_bad, raw_to_c(obj)); printf(环境 raw%u PEC0x%02x %s %.2f °C\n, amb, pec_a, ok_a ? crc_ok : crc_skip_or_bad, raw_to_c(amb)); return 0; } int main(int argc, char **argv) { int force_i2c 0, loop 0; int i; for (i 1; i argc; i) { if (strcmp(argv[i], i2c) 0) force_i2c 1; if (strcmp(argv[i], loop) 0) loop 1; } do { int rc; if (!force_i2c) { rc read_iio_once(); if (rc 0) { fprintf(stderr, IIO 没有 mlx90614改走 i2c-dev\n); rc read_i2c_once(); } } else { rc read_i2c_once(); } if (!loop) return rc 0 ? 0 : 1; sleep(1); printf(----\n); } while (loop); return 0; }编译必须 32 位 muslOHOS源码根 CLANG$OHOS/prebuilts/clang/ohos/linux-x86_64/llvm/bin/clang SYSROOT$OHOS/prebuilts/ohos-sdk/linux/11/native/sysroot $CLANG --targetarm-linux-ohos --sysroot$SYSROOT -O2 -Wall \ -Wl,--dynamic-linker/system/lib/ld-musl-arm.so.1 \ -o mlx90614_read mlx90614_read.c file mlx90614_read # ELF 32-bit LSB executable, ARM, ... interpreter /system/lib/ld-musl-arm.so.1 hdc file send mlx90614_read /data/local/tmp/mlx90614_read hdc shell chmod 0755 /data/local/tmp/mlx90614_read现场hdc shell /data/local/tmp/mlx90614_read # IIO /sys/bus/iio/devices/iio:device4 # 物体 raw15120 29.25 °C # 环境 raw15090 28.65 °C hdc shell /data/local/tmp/mlx90614_read i2c # i2c-dev 0x5a RAM 0x07 LSB/MSB/PEC # 物体 raw15118 PEC0x3c crc_ok 29.21 °C hdc shell /data/local/tmp/mlx90614_read looploop 开着手掌伸到透镜前 2 cm物体通道往 3336 °C 走拿开掉回室温。环境通道几乎不动。两路数字贴在一起多半对准距离不对或者读成了同一通道。IIO 和 i2c-dev 同一时刻的 raw 允许差几个字芯片内部一直在刷新。差出一个数量级查有没有把高低字节倒了必须LSB | (MSB8)。7. ArkTS 按名字找 IIOlibboard.so提供findIio、readText。不要写死iio:device4。import board from libboard.so; Entry Component struct IrTempPage { State line: string 对准手掌 1-3cm 再读。桌面和手掌必须是两个数。; private num(path: string): number { return parseFloat(board.readText(path).trim()); } readMlx() { let base: string board.findIio(mlx90614); if (base.length 0) { this.line 未找到 mlx90614。查 i2c1 100kHz、1-005a/driver、resource 是否刷了; return; } let obj: number this.num(base /in_temp_object_raw); if (Number.isNaN(obj)) { this.line 读 in_temp_object_raw 失败chmod 0666; return; } let tObj: number obj * 0.02 - 273.15; let amb: number this.num(base /in_temp_ambient_raw); let tAmb: string Number.isNaN(amb) ? - : (amb * 0.02 - 273.15).toFixed(1); this.line 物体 tObj.toFixed(1) °C 环境 tAmb °C\n base; } build() { Column() { Text(红外测温 MLX90614 0x5a).fontSize(18).margin({ bottom: 8 }) Button(读一次).onClick(() { this.readMlx(); }) Text(this.line).margin({ top: 12 }).fontSize(16) }.padding(16) } }用户态三字节也可以从 NAPI 包一层mlxRead()内部就是i2cRdReg(fd, 0x5a, 0x07, buf, 3)。IIO 已经 bind 时优先 sysfs少一次和内核抢 0x5a。HAP 不是 root。in_temp_*_raw默认 0444 或 0640应用读空。开机脚本chmod 0666进 vendor。只装 hap 不够。透镜不是接触式热电偶。贴到 0 cm 读数乱跳30 cm 对着房间就是室温。13 cm 正对皮肤才是设计用法。手掌对桌面来回切数字必须跟着切。9. 还踩过的坑只刷 Image/proc/device-tree里 clock-frequency 仍是00 06 1a 80生效 dtb 在 resource。扫错总线芯片在 i2c1i2c0 是 RTCi2c2 是触摸。CAN 镜像会 disabled i2c1。不要写死iio:deviceN用名字找。IIO 暴露的是 raw 字不要再乘一次 0.02。总线上先出 LSB。必须组合读命令 0x07裸读会读到环境温度。PEC 脏时先看手掌数字。HAP 要 chmod 0666。0xFFFF 当未连接不要显示 1030 °C。EEPROM 别擦。模块 3.3 V。验收100 kHz手掌与桌面两个数拔模块 NACK。CONFIG_MLX90614y三处同步。刷boot_linux和 resource。系列第 35 篇 · 芯片瑞芯微 RK3568 · OpenHarmony 4.1API 11 · Linux 5.10
返回列表