☰
MLX90614红外温度传感器Linux驱动开发:I2C协议、源码编译与设备树配置
2026/10/26 17:01:57 网站建设 项目流程

简介:这份资源是面向嵌入式Linux驱动开发者的MLX90614红外温度传感器驱动源码,已在Android 6.0、内核3.4.39的实际产品中验证使用,适合作为学习设计参考。压缩包内共1个C语言源文件,大小约5KB,核心文件为mlx90614_linux驱动.c,代码中完整实现了SMBus起始位、停止位等底层时序,并定义了从机地址、RAM与EEPROM访问命令、目标温度与环境温度寄存器等关键宏,同时包含GPIO引脚配置与misc设备注册逻辑,可直接对照移植到类似平台。目前已有464人学习下载,说明该驱动在同类开发场景中具有一定参考价值。读者可从中掌握红外温度传感器的I2C/SMBus通信协议、Linux字符设备驱动框架以及GPIO控制方法,快速理解从硬件时序到内核模块的完整实现路径,为产品开发或课程设计提供可复用的代码基础。

1. MLX90614 红外温度传感器在 Linux 下的驱动落地:从 I2C 协议到源码编译

拿到一个名为MLX90614红外温度传感器_linux驱动源码.zip的包,很多做嵌入式 Linux 的同行第一反应是「先解压看看有没有现成的 .ko」。但真正决定这个驱动能不能在你板子上跑起来的,不是源码里写了多少行,而是你有没有搞清楚 MLX90614 挂在哪条 I2C 总线上、内核版本对 I2C 子系统 API 的改动、以及 SMBus 协议里那个容易被忽略的 PEC 校验位。MLX90614 是 Melexis 出的单点红外测温芯片,出厂校准、数字输出、视场角从 5° 到 90° 可选,测温范围覆盖 -70°C 到 380°C 物体温度,在工业设备状态监测、非接触式体温筛查、打印机定影辊测温这些场景里出镜率很高。它走的是标准 I2C 接口,7 位地址默认 0x5A,读写的是内部 RAM 和 EEPROM 里的 16 位温度寄存器。Linux 内核从早期版本就自带mlx90614的 hwmon 驱动,但很多项目仍然选择自己写或改一份独立驱动,原因通常是:需要同时读多个传感器、需要把原始数据直接送进应用层做算法、或者内核自带的驱动版本太老不支持某些寄存器操作。这篇笔记就按「协议理解 → 环境准备 → 源码编译 → 设备树配置 → 数据读取 → 避坑」的顺序,把这份驱动源码怎么在嵌入式 Linux 上跑通讲清楚,适合正在做国产 Linux 板卡外设适配、或者第一次接触 I2C 传感器驱动的工程师跟做。

2. 先把 MLX90614 的 I2C 协议和寄存器摸清楚

2.1 为什么不能直接抄一份 I2C 读写就完事

MLX90614 的通信协议看着简单,实际上有几个地方和常见的 EEPROM、RTC 芯片不一样。它的内部地址空间分三块:RAM(易失,存放当前温度值)、EEPROM(非易失,存放配置和校准系数)、以及一个全局的访问控制。上电后芯片会自动以 10Hz 左右的频率刷新 RAM 里的温度数据,主机只需要读对应的寄存器就行,不需要发启动转换命令。这一点和 DS18B20 那种需要手动触发转换的传感器完全不同,也是很多人第一次写驱动时容易多此一举的地方。

关键寄存器地址如下表,这是写驱动时必须对照的:

寄存器名称地址(十六进制)位宽说明
TOBJ10x0716 bit物体温度,默认输出
TA0x0616 bit环境温度
TOMAX0x0016 bit发射率相关,EEPROM
TOMIN0x0116 bit同上
PWMCTRL0x0216 bitPWM 配置
TA_RANGE0x0316 bit环境温度范围
EMISSIVITY0x0416 bit发射率修正,EEPROM
CONFIG0x0516 bit配置寄存器
SMBUS_ADDR0x0E16 bit从机地址,EEPROM
ID1~ID40x1C~0x1F16 bit厂商 ID

温度值的换算公式是固定的:T = (raw * 0.02) - 273.15,单位摄氏度。raw 是 16 位无符号数,低 8 位和高 8 位分别对应寄存器的低字节和高字节。注意 MLX90614 的 I2C 时序里,读操作是「写地址 → 重复起始 → 读两字节」,中间没有停止位,这是 SMBus 读字的标准格式,Linux 的i2c_smbus_read_word_data()封装的就是这个流程。

2.2 驱动源码里必须有的四个函数

一份能用的 MLX90614 Linux 驱动,不管代码风格怎么变,核心逻辑绕不开这四个部分。下面用伪代码结构说明,实际源码里函数名可能不同,但职责是对应的。

/* 1. 探测函数:确认设备在线并初始化 */ static int mlx90614_probe(struct i2c_client *client, const struct i2c_device_id *id) { /* 读 ID 寄存器验证芯片存在,0x1C 开始四个寄存器 */ u16 id1 = i2c_smbus_read_word_data(client, 0x1C); if (id1 < 0) { dev_err(&client->dev, "MLX90614 not found at 0x%02x\n", client->addr); return -ENODEV; } /* 分配 hwmon 或字符设备结构体,注册接口 */ return 0; } /* 2. 读温度函数:读 TOBJ1 并换算 */ static int mlx90614_read_temp(struct i2c_client *client, int *temp_mc) { s32 raw = i2c_smbus_read_word_data(client, 0x07); if (raw < 0) return raw; /* 注意字节序:SMBus 读回来的是小端,但 MLX90614 输出高字节在前 */ raw = ((raw & 0xFF) << 8) | ((raw >> 8) & 0xFF); *temp_mc = (raw * 20) - 273150; /* 毫摄氏度 */ return 0; } /* 3. 写配置函数:修改发射率等 EEPROM 参数 */ static int mlx90614_write_emissivity(struct i2c_client *client, u16 val) { /* EEPROM 写需要先擦除再写,且每次写之间要延时 */ int ret = i2c_smbus_write_word_data(client, 0x04, val); msleep(10); /* 血泪经验:不延时连续写会丢数据 */ return ret; } /* 4. 移除函数:注销接口,释放资源 */ static int mlx90614_remove(struct i2c_client *client) { /* 注销 hwmon 设备或字符设备 */ return 0; }

这里最容易被忽略的是字节序处理。i2c_smbus_read_word_data()返回的是主机字节序,而 MLX90614 的数据手册明确写了温度寄存器是「低字节在前、高字节在后」的格式,但 SMBus 读字操作本身会把第一个字节放到低 8 位。实际调试时如果发现读出来的温度是负数或者大得离谱,八成就是字节序没翻转。我一般会在 probe 里先读环境温度 TA 做一次 sanity check,室温下应该在 20°C 到 30°C 之间,如果读出来是 6000 多或者负数,直接检查字节序。

2.3 内核自带驱动和这份源码的关系

Linux 内核drivers/hwmon/mlx90614.c从 4.x 版本就存在了,它注册的是 hwmon 接口,用户态通过/sys/class/hwmon/hwmonX/temp1_input读温度。这份独立驱动源码通常做的是两件事之一:要么是内核版本太老没有这个驱动,要么是需要额外的 ioctl 接口直接读原始寄存器。判断方法很简单,在你的内核源码目录下执行:

find drivers -name "mlx90614*" -o -name "*mlx90614*"

如果有输出,说明内核已经支持,优先用内核自带的,省去维护成本。如果没有,或者你需要非标准功能,再考虑编译这份源码。国产 Linux 发行版和嵌入式 Linux 项目里,内核裁剪时经常把 hwmon 子系统整个去掉,这时候独立驱动就是唯一选择。

3. 编译环境准备:交叉工具链和内核头文件

3.1 确认目标板的内核版本和架构

驱动编译最怕的就是「编译过了但 insmod 报 invalid module format」。根因是编译时用的内核源码版本、配置、编译器版本和板子上跑的内核不一致。所以第一步永远是确认目标板的内核信息。在板子上执行:

uname -r cat /proc/version

uname -r给出内核版本号,/proc/version给出编译用的 GCC 版本。比如输出Linux version 5.10.110 (gcc version 10.3.0),那你就必须用 5.10.110 的内核源码和 gcc 10.3.0 的交叉工具链。差一个小版本号都可能加载失败,这是嵌入式 Linux 驱动开发里最常见的翻车点。

3.2 交叉编译工具链的获取和验证

工具链一般由板卡厂商提供,常见的是arm-linux-gnueabihf-或aarch64-linux-gnu-前缀。拿到之后先验证:

# 假设工具链解压在 /opt/toolchain export PATH=/opt/toolchain/bin:$PATH arm-linux-gnueabihf-gcc -v

输出里能看到 gcc 版本和 target 架构。如果板子是 64 位的,前缀通常是aarch64-linux-gnu-。注意有些国产 Linux 板卡用的是 MIPS 或 RISC-V 架构,工具链前缀完全不同,一定要看板卡手册确认。工具链的 sysroot 里要有目标板的 libc 头文件,否则编译驱动时找不到linux/i2c.h之类的内核头文件。

3.3 内核源码的准备和编译

驱动编译需要内核源码树,而且必须是「已经配置过并编译过」的状态,因为Module.symvers和include/generated/目录下的文件是编译外部模块时必需的。步骤:

# 1. 解压内核源码 tar -xf linux-5.10.110.tar.xz cd linux-5.10.110 # 2. 导入板卡厂商的默认配置 # 配置文件通常在 arch/arm/configs/ 下,名字类似 xxx_defconfig make ARCH=arm xxx_defconfig # 3. 编译内核模块准备 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- modules_prepare

modules_prepare会生成编译外部模块所需的脚本和符号表。如果跳过这一步直接编译驱动,会报Module.symvers not found或者scripts/basic/fixdep找不到。注意modules_prepare不会编译全部模块,只准备基础设施,所以速度比完整编译内核快很多。

提示:如果板卡厂商提供了完整的内核源码包,优先用厂商的,不要自己从 kernel.org 下主线内核。厂商内核里通常有板卡特有的补丁和配置,用主线内核编译出来的驱动可能因为配置差异无法加载。

4. 驱动源码的编译、加载与设备树配置

4.1 源码目录结构和 Makefile 修改

解压MLX90614红外温度传感器_linux驱动源码.zip后,典型的目录结构是:

mlx90614_driver/ ├── Makefile ├── mlx90614.c ├── mlx90614.h └── README.md

Makefile 通常长这样,需要根据你的内核路径修改KDIR:

obj-m += mlx90614.o KDIR := /home/user/linux-5.10.110 PWD := $(shell pwd) all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean

obj-m表示编译成可加载模块,如果要做成内置驱动就改成obj-y并放进内核源码树。KDIR必须指向你刚才执行过modules_prepare的内核源码目录。编译命令:

make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-

编译成功后目录下会出现mlx90614.ko。用file命令确认架构:

file mlx90614.ko # 应输出 ELF 32-bit LSB relocatable, ARM, EABI5 version 1

如果输出的是 x86-64,说明交叉编译前缀没生效,检查CROSS_COMPILE变量。

4.2 设备树里怎么描述 MLX90614 节点

现代嵌入式 Linux 都用设备树描述硬件,MLX90614 挂在哪个 I2C 控制器下、地址是多少、中断脚接哪里,都要在.dts里写清楚。典型节点:

&i2c1 { status = "okay"; clock-frequency = <100000>; mlx90614: mlx90614@5a { compatible = "melexis,mlx90614"; reg = <0x5a>; /* 如果驱动支持中断,加上中断脚 */ interrupt-parent = <&gpio1>; interrupts = <12 IRQ_TYPE_EDGE_FALLING>; }; };

compatible字符串必须和驱动源码里of_device_id表里的完全一致,否则 probe 不会被调用。reg是 7 位 I2C 地址,MLX90614 出厂默认 0x5A,如果 EEPROM 里改过地址,这里要对应修改。clock-frequency建议先用 100kHz 标准模式,MLX90614 支持到 400kHz 快速模式,但长排线或干扰大的场合降速更稳。

设备树编译和更新:

# 编译 dtb make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs # 把生成的 dtb 拷贝到板子的 boot 分区 scp arch/arm/boot/dts/xxx.dtb root@192.168.1.100:/boot/

重启板子后,用dmesg | grep mlx看驱动有没有 probe 成功。

4.3 加载模块和验证设备节点

把mlx90614.ko传到板子上:

scp mlx90614.ko root@192.168.1.100:/tmp/ ssh root@192.168.1.100 insmod /tmp/mlx90614.ko dmesg | tail -20

如果 probe 成功,dmesg 里会看到类似mlx90614 1-005a: registered的输出。如果驱动注册的是 hwmon 接口,检查:

ls /sys/class/hwmon/ cat /sys/class/hwmon/hwmon0/temp1_input

如果注册的是字符设备,检查/dev/mlx90614或/dev/temp0是否存在。读出来的值如果是毫摄氏度,除以 1000 就是摄氏度。如果读出来是 0 或者固定不变,先确认 I2C 总线上能不能扫到设备:

i2cdetect -y 1

i2cdetect需要i2c-tools包,如果板子上没有,可以用i2cget直接读寄存器验证:

i2cget -y 1 0x5a 0x07 w

这条命令读 TOBJ1 寄存器,返回两个字节的十六进制值。如果返回0x0000或0xffff,说明 I2C 通信有问题,检查上拉电阻和接线。

5. 避坑与排查:MLX90614 驱动调试中的五个血泪教训

5.1 现象:insmod 报 “invalid module format”

原因:编译驱动的内核源码版本或配置与目标板不一致。最常见的是板子跑的是厂商定制内核,而你用了主线内核源码编译。另一个原因是编译器版本不同,内核模块的 vermagic 里包含编译器版本信息。

解决:用modinfo mlx90614.ko查看 vermagic,和板子上uname -r对比。必须完全一致。如果板子内核是厂商定制的,找厂商要对应的内核源码和工具链。实在拿不到,可以在板子上直接编译,但嵌入式板子通常没有完整的编译环境,这条路走不通。

5.2 现象:i2cdetect 能扫到 0x5a,但读温度始终是 0

原因:I2C 通信正常但寄存器读取逻辑有误。常见的是字节序没处理,或者读的寄存器地址不对。MLX90614 的 TOBJ1 地址是 0x07,但有些源码里写成了 0x08 或 0x06,读出来的是环境温度或者保留寄存器。

解决:用i2cget手动读 0x07 和 0x06,对比数据手册的换算公式。室温下 TA 应该在 0x0C00 到 0x1000 左右(对应 20°C 到 30°C)。如果手动读正常但驱动读不对,检查驱动里的字节序翻转逻辑。

5.3 现象:温度值跳变严重,波动超过 5°C

原因:MLX90614 的视场角内如果有热源快速移动,或者传感器供电纹波大,都会导致读数跳变。另一个容易被忽略的是发射率设置,默认发射率 1.0 只适用于黑体,测金属表面时发射率低,读数会偏低且不稳定。

解决:在驱动里加滑动平均滤波,或者读多次取中值。发射率通过 EEPROM 的 0x04 寄存器修改,测金属时一般设 0.1 到 0.3,具体值查材料发射率表。修改发射率后需要重新上电才生效,因为 EEPROM 写入后芯片要重新加载配置。

5.4 现象:同时挂多个 MLX90614 时地址冲突

原因:MLX90614 出厂地址都是 0x5A,一条 I2C 总线上挂多个必然冲突。虽然芯片支持通过 EEPROM 修改地址,但修改过程需要逐个操作,且修改后如果忘记新地址,芯片就「失联」了。

解决:常见做法是用 I2C 多路复用器(如 TCA9548A)扩展总线,每个传感器挂在不同通道上,地址保持默认。或者用 GPIO 控制传感器的电源,逐个上电修改地址。修改地址的流程是:只给一个传感器上电,写 SMBUS_ADDR 寄存器,断电,再给下一个上电。注意写 EEPROM 需要先写 0x00 到 0x04 擦除,再写新值,中间要延时。

5.5 现象:驱动加载后系统日志刷 “i2c timeout”

原因:I2C 总线被拉死,通常是因为某个设备在通信过程中复位或者电源不稳,导致 SDA 或 SCL 被拉低。MLX90614 在 EEPROM 写入期间如果断电,下次上电可能处于异常状态,持续拉低总线。

解决:先断电重启,看能否恢复。如果不行,检查上拉电阻是否合适,标准模式一般用 4.7kΩ,快速模式用 2.2kΩ。长排线或干扰大的场合,降低 I2C 速率到 50kHz 试试。驱动里加超时重试机制,i2c_smbus_read_word_data返回负值时不要直接返回错误,重试两到三次再报错。

6. 进阶技巧:用 IIO 子系统替代 hwmon 做高频采样

内核自带的 mlx90614 驱动走 hwmon 接口,适合低频读取,比如每秒读一次。但如果你的应用需要 10Hz 以上的采样率,或者要把数据送进 IIO(工业 I/O)子系统做缓冲和触发采集,hwmon 就不够用了。IIO 子系统支持iio_buffer和触发式采集,配合iio-trigger-sysfs可以实现定时采样,数据直接进内核缓冲区,用户态用read()批量取,延迟比 sysfs 逐次读低一个数量级。

改造思路是把驱动注册成 IIO 设备,核心改动是填充iio_dev和iio_chan_spec:

static const struct iio_chan_spec mlx90614_channels[] = { { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .address = 0x07, /* TOBJ1 */ }, { .type = IIO_TEMP, .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), .address = 0x06, /* TA */ }, }; static int mlx90614_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { struct mlx90614_data *data = iio_priv(indio_dev); s32 raw; switch (mask) { case IIO_CHAN_INFO_RAW: raw = i2c_smbus_read_word_data(data->client, chan->address); if (raw < 0) return raw; raw = ((raw & 0xFF) << 8) | ((raw >> 8) & 0xFF); *val = raw; return IIO_VAL_INT; case IIO_CHAN_INFO_SCALE: *val = 20; /* 0.02°C 的分子 */ *val2 = 1000; /* 分母,实际 scale = 20/1000 = 0.02 */ return IIO_VAL_FRACTIONAL; } return -EINVAL; }

注册 IIO 设备后,用户态通过/sys/bus/iio/devices/iio:device0/下的文件操作,或者用libiio库做缓冲采集。IIO_CHAN_INFO_SCALE返回 20/1000,表示原始值乘以 0.02 得到摄氏度,再减 273.15 得到最终温度。这个换算和 hwmon 接口的毫摄氏度不同,应用层要对应调整。

验证方法:用iio_info工具查看设备通道,用iio_readdev做连续采样:

iio_info -u local: iio_readdev -b 256 -s 1024 iio:device0 voltage0 | xxd | head

如果采样率上不去,检查 I2C 总线速率和设备树里的clock-frequency。100kHz 下读两个寄存器大约需要 1ms,理论上限 1000Hz,但实际受内核调度和 IIO 缓冲影响,稳定跑到 200Hz 到 500Hz 是合理的。需要更高采样率就得换 SPI 接口的传感器,MLX90614 只有 I2C 版本。

最后说个习惯:每次改完驱动,先在i2cget层面验证寄存器读写正常,再 insmod 看 probe,最后才测数据。跳过前两步直接测数据,出了问题你分不清是硬件、总线还是驱动逻辑的锅。这个顺序帮我省过很多次返工。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询