
简介一份面向嵌入式Linux开发者的MLX90614红外温度传感器驱动源码来自实际产品项目已在Android 6.0环境下配合内核3.4.39验证使用。驱动基于Linux杂项设备框架采用GPIO模拟SMBus时序完成单总线读写代码中完整涵盖总线起始/停止位、ACK/NACK应答、RAM与EEPROM访问命令、0x5A从机地址配置等关键环节同时结合平台sys_config管脚配置方式便于在不同单板间做引脚适配对设备节点注册、open/read/ioctl接口实现、copy_to_user数据拷贝等字符设备驱动核心要点也有清晰展示适合作为协议型传感器驱动的入门与进阶参考。资源包共1个文件为单个C语言源文件压缩后仅5KB体量小、结构紧凑包含GPIO配置结构、寄存器地址宏定义以及SMBus起始/停止和数据读写函数便于按模块拆解学习也可在同类项目中直接移植参考。已有464人浏览学习对正在调试MLX90614、研究内核驱动或GPIO模拟I2C/SMBus的开发者具有直接参考价值。1. MLX90614红外温度传感器真正难写的不是获取温度那一行MLX90614 是 Melexis 出品的一款数字红外测温芯片I2C 接口出厂校准过测温范围 -40℃ 到 125℃物体温度 -70℃ 到 382℃在很多非接触测温项目里是首选。很多人拿到手第一反应是「不就是读个 I2C 寄存器吗」但实际写 Linux 驱动时会发现坑不少它走的是 SMBus 协议RAM 地址只有 5 位数据里带 PEC 校验而且默认 7 位设备地址 0x5A 很容易和你板子上其他 I2C 器件撞车。这让「抄一个 i2c_smbus_read_word_data 就完事」的思路在真实项目里撑不住。这篇文章围绕这包驱动源码要解决的四个问题展开SMBus 和普通 I2C 读写的差别、CRC 校验怎么处理、设备树地址配置、以及发射率校准对测温精度的影响。目标是让看过的人能自己把源码编进内核而不是停留在「代码能跑」的层面。2. 驱动源码主线SMBus 协议、PEC 校验、regmap 封装2.1 为什么 MLX90614 走 SMBus 而不是直接 I2C 读MLX90614 的物理层是 I2C但它的读写时序严格遵循 SMBus 规范。区别不只是「能不能读」而是寄存器地址长度和字节序。标准 I2C 读某个寄存器通常是「先发设备地址 寄存器地址再重发设备地址读数据」寄存器地址可以是 8 位甚至 16 位。MLX90614 的 RAM 地址只有 5 位全部映射在 0x00-0x3F 空间里而且它要求在同一个 STOP 条件内完成写地址和读数据这正好符合 SMBus 的SMBUS_READ_WORD_DATA时序。#define MLX90614_RAM_TA 0x06 #define MLX90614_RAM_TOBJ 0x07内核里访问它最简洁的方式就是用i2c_smbus_read_word_data()它替你把 start、地址、寄存器、重复 start、读两个字节、stop 全部封装好。你要是自己用i2c_master_send/i2c_master_recv分开写大概率会遇到地址对不上或者波形不对的问题。很多人在这卡住换回 smbus 函数立刻就好。2.2 驱动源码里的三要素i2c_client、cdev、温度换算源码包里的mlx90614.c核心结构是典型的字符设备驱动框架probe里拿到i2c_client分配私有结构体保存数据。用alloc_chrdev_region和cdev_add创建设备节点。read函数里调用i2c_smbus_read_word_data读 RAM把 16 位数据换算成温度。温度换算公式是两个物体温度Tobj 0.02 * data - 273.15每 LSB 对应 0.02℃环境温度Ta 0.02 * data - 273.15。有个细节值得注意——MLX90614 输出的是 16 位有符号数但靠左对齐高两位是符号扩展所以直接data 0x7FFF会把负数温度变成巨大正数。正确的是用s16类型直接接收i2c_smbus_read_word_data的返回值或者手动做符号扩展。static s32 mlx90614_read_temp(struct i2c_client *client, u8 reg) { s32 data; data i2c_smbus_read_word_data(client, reg); if (data 0) return data; return (s16)le16_to_cpu(data); } static ssize_t mlx90614_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct mlx90614_data *data filp-private_data; s32 raw; int temp; raw mlx90614_read_temp(data-client, MLX90614_RAM_TOBJ); if (raw 0) return raw; temp (raw * 20) / 100; /* 0.02℃ 每 LSB放大 100 倍存整数 */ if (copy_to_user(buf, temp, sizeof(temp))) return -EFAULT; return sizeof(temp); }这里最关键的是(s16)转换它把读到的 16 位原始值按有符号处理再乘 20 除 100避免浮点运算。copy_to_user传的是放大 100 倍的整数温度用户态自己除 100 就行省去内核里禁止浮点的麻烦。2.3 CRC-8 PEC 字节怎么处理MLX90614 带 SMBus PECPacket Error Checking在读取命令里主设备可以要求从设备在数据后额外返回一个 CRC 字节。这个校验算法是 CRC-8多项式x^8 x^2 x^1 1即 0x07初始值 0x00。内核里i2c_smbus_read_word_data默认不读 PEC你拿到的就是纯数据但源码包里如果用了I2C_SMBUS_PEC标志就得知道 CRC 算在哪几个字节上。PEC 覆盖的字节顺序是从设备地址左移一位的写地址 寄存器地址 数据低字节 数据高字节。注意PEC 计算时用的地址是 7 位地址左移一位加上读写位之前的那个字节不是 7 位原值。static u8 mlx90614_crc8(u8 *buf, int len) { u8 crc 0; int i, j; for (i 0; i len; i) { crc ^ buf[i]; for (j 0; j 8; j) { if (crc 0x80) crc (crc 1) ^ 0x07; else crc 1; } } return crc; }如果你把 PEC 看成「可选」而忽略多数情况能读到数据但偶尔会因为总线时序毛刺收到错值表现是温度忽跳几十度。生产环境的做法是开启 PEC验证失败就重试一次。驱动源码里这块逻辑通常在read函数中需要用户态传入I2C_SMBUS_PEC标志且读 PEC 后和计算值比对不一致返回-EIO。3. 把驱动源码接入实际板子设备树配置和加载流程3.1 设备树节点怎么写MLX90614 挂在哪条 I2C 总线上设备树里就要把探测地址写清楚。默认地址 0x5A但你可以通过 SMBus 的 EEPROM 访问方式改地址比较少见。设备树模板大概是i2c1 { mlx906145a { compatible melexis,mlx90614; reg 0x5a; interrupt-parent gpio0; interrupts 5 IRQ_TYPE_EDGE_FALLING; }; };reg 0x5a是 I2C 从地址注意这里要写 7 位地址不是 8 位。很多刚上手的人把 datasheet 里的写地址 0xB4 填进去内核直接报-ENXIO。interrupts这行是可选的——MLX90614 没有数据就绪中断脚如果你不需要中断就删掉。驱动probe里匹配id_table用的是melexis,mlx90614这个 compatible源码里要对应写全大小写敏感。3.2 编译进内核还是以模块加载源码包的 Makefile 如果只写了一行obj-m mlx90614.o那它是按模块编译的。常见做法是交叉编译后用insmod动态加载调试方便。更好的做法是把它编进内核在drivers/misc/下建目录Kconfig 加上依赖config MLX90614 tristate Melexis MLX90614 infrared temperature sensor driver depends on I2C help Say Y here to support the MLX90614 series.模块加载后的表现是i2c: I2C adapter i2c-1 registered后多出一行设备注册信息。如果你在用户态用i2cdetect -y 1能看到5a地址说明设备树探测成功接下来就是看/dev下有没有你注册的字符设备。一般我习惯在probe里用device_create建一个mlx90614类下的设备节点或者干脆用iio框架——但原版源码包通常用 miscdevice节点名是mlx90614或带序号。3.3 用户态如何验证驱动工作模块加载成功但读不到温度最直接的排查命令是i2cdetect -y 1 i2cget -y 1 0x5a 0x07 wi2cget的w参数表示以「字」方式读取正好对应 SMBus 的 read word 时序。如果你能在命令行直接拿到一个数值比如0x7ef9说明硬件通路没问题问题在驱动。拿到原始值后自己算一下0x7ef9是 32505乘 0.02 减 273.15 等于 37.0℃这就有底气继续查驱动代码。4. 校准、发射率和排错从读数对到测温准4.1 发射率Emissivity怎么在代码里体现MLX90614 出厂默认发射率是 1.0也就是说它假设被测物是理想黑体。现实里塑料、氧化金属、皮肤都不满足。发射率可以通过写 EEPROM 单元 0x04 来调整范围 0.1 到 1.0。驱动源码如果只做了读没做写那你测金属表面温度会系统性偏低测反光铝板甚至能差十几度。实际项目里我一般不在驱动里开放 EEPROM 写操作而是留一个 sysfs 属性把发射率系数交给应用层。算法很简单把目标物体真实温度对应的辐射功率折算回黑体等效温度。Melexis 提供过一个简化公式结论是当你把发射率调为 0.5 时测同样的目标温度MLA90614 输出的温度会变低你需要乘一个比例系数修正。这块源码包里如果有emissivity参数通常实现是sensor-emissivity value后重新算内部补偿变量。4.2 三个高频坑地址冲突、EMI、总线速度I2C 地址冲突是板级最常发生的问题。MLX90614 固定地址 0x5A如果板子上还有个 AT24C02 EEPROM 也在 0x5A两个器件都会 ACK总线数据完全错乱。解决办法是通过自带的SMBus Alert机制给传感器改地址或者换别的器件地址。驱动里如果设备树reg写 0x5A 但 i2cdetect 显示两个设备都在 0x5A那就是硬件冲突。电磁干扰EMI也会让它丢数据。MLX90614 的 SDA/SCL 引脚上拉电阻最好在 4.7kΩ 到 10kΩ 之间走线不要超过 10cm否则在电机启动瞬间温度读数会卡住。这类问题驱动里只能加 PEC 重试来缓解。总线速度也有讲究MLX90614 支持 100kHz 标准模式但不少开发板 I2C 控制器默认跑 400kHz高速模式下它也能工作可时序裕量变小建议在设备树里限制clock-frequency 100000。4.3 排错时看什么dmesg、i2cdetect、逻辑分析仪遇到读不出来别急着改代码先按顺序排除dmesg | grep -i mlx90614 dmesg | grep -i i2c.*error i2cdetect -l i2cdetect -y 1dmesg里如果出现failed to read RAM之类的错误先确认 I2C 总线编号对不对。有些 SoC 有多个 I2C 控制器设备树里挂的设备不一定在 i2c-1 上。i2cdetect -l列出来的总线编号就是你的设备树里的顺序通常从 0 开始。如果驱动注册成功但read返回-EIO大概率是 PEC 计算有误——检查 CRC 覆盖的起始字节是不是从设备地址开始。5. 把驱动的 raw 值打包成「人类可读」的温度值驱动最终交付给应用层的接口形态很重要。源码包里最常见的做法是在read函数里返回 raw 值应用层自己换算差一点的驱动甚至会返回 16 位原始寄存器值不做任何处理。我习惯在内核里把温度放大 1000 倍后以整数形式返回这样既能保留 0.001℃ 的分辨率又不让内核碰浮点运算应用层也省去协议解析歧义。除了字符设备的 read还有一个值得做的操作是在unlocked_ioctl里加一条MLX90614_IOC_READ_RAW让应用可以直接读环境温度和物体温度两个通道#define MLX90614_IOC_MAGIC 0x91 #define MLX90614_IOC_READ_TA _IOR(MLX90614_IOC_MAGIC, 0x01, int) #define MLX90614_IOC_READ_TOBJ _IOR(MLX90614_IOC_MAGIC, 0x02, int)用户态读环境的代码形如int fd open(/dev/mlx90614, O_RDWR); int temp_x1000; ioctl(fd, MLX90614_IOC_READ_TOBJ, temp_x1000); printf(Tobj: %.3f\n, temp_x1000 / 1000.0);这样做的价值在于把「硬件协议、CRC、换算」全部收敛在驱动里应用层只面对一个带符号的定点数。最后再给一个验证技巧用tail -f /var/log/kern.log同时观察dmesg拿一个稳定的热源贴近传感器看温度输出是否有正常上升趋势如果读数在 0 和满量程之间乱跳多半是 PEC 重试逻辑没生效把环境温度和物体温度都打出来对比就能快速定位问题是出在 sensor 本身还是驱动换算。本文还有配套的精品资源点击获取