
简介这份资源是MLX90614红外温度传感器的Linux驱动源码面向嵌入式Linux驱动开发者、Android底层工程师及传感器驱动学习者解决在特定内核环境下快速接入非接触式红外测温模块的问题。源码已在Android 6.0、内核3.4.39的实际产品中验证使用具备工程参考价值。压缩包内共1个文件为C语言驱动源码整体约5KB代码中完整实现了SMBus起始位、停止位、ACK/NACK时序以及RAM与EEPROM访问命令并包含SCL/SDA引脚配置、GPIO功能描述结构体与设备名定义可直接作为驱动移植与调试的起点。目前已有464人学习下载适合需要理解I2C类传感器驱动框架、掌握内核模块注册与GPIO控制流程的读者参考也可用于课程设计或产品原型开发中的温度采集方案验证。1. 从一颗 SMBus 温度芯片说起这份 MLX90614 Linux 驱动源码能省掉你多少试错如果你手头有一块 MLX90614 红外温度传感器跑在 Android 6.0、内核 3.4.39 的全志平台上想用最少的代码把它接进系统那这份mlx90614_linux驱动.c值得先看一遍。它不是那种只跑通 I2C 读一个寄存器的 demo而是把 SMBus 时序、GPIO 模拟、misc 设备节点、ioctl 接口都揉进了一个文件里属于产品上验证过的驱动骨架。MLX90614 本身是单点红外测温出厂校准过通过 SMBus 兼容接口读 RAM 里的 TOBJ1、TOBJ2、TA 三个量难点从来不在协议本身而在于时序容错和平台适配。这份源码把这两块都摊开了适合做嵌入式 Linux 驱动练手也适合直接拿去做产品移植的起点。2. 拆开驱动看结构SMBus 时序、GPIO 模拟与 misc 设备怎么串起来2.1 为什么不用内核 i2c-gpio而是自己写 SMBus 时序MLX90614 的物理层是 SMBus和 I2C 电气兼容但协议层有区别它要求 PEC 可选、时钟拉伸容忍度低、重复起始位的时序窗口窄。内核自带的i2c-gpio驱动走的是标准 I2C 状态机遇到 MLX90614 这种对 Start/Stop 之间延时敏感的从机容易出现读回全 0 或者 NACK 的情况。这份源码选择自己用 GPIO 翻转来模拟 SMBus把 SCL、SDA 的拉高拉低、采样点全部握在手里代价是要自己处理延时和 ACK 判断收益是时序可控。源码里定义了两个 GPIO 描述结构#define SCL_NAME sensor_sck #define SDA_NAME sensor_sda struct gpio_func_desc { unsigned short pin; char *name; }; struct gpio_func_desc SCLK {0, SCL_NAME}; struct gpio_func_desc SDIN {0, SDA_NAME};pin字段初始为 0实际引脚号在 probe 阶段通过script_item_u从 sys_config 里取。这是全志平台的老套路Android 6.0 时代 sys_config.fex 还没被设备树完全取代GPIO 编号、上下拉、驱动能力都写在 fex 里驱动运行时用script_get_item读出来再gpio_request。如果你换到设备树平台这一段要改成of_get_named_gpio但后面的 SMBus 时序逻辑可以原样保留。2.2 SMBus 读写函数的实现要点源码里声明了SMBus_StartBit、SMBus_StopBit后面还有SMBus_SendByte、SMBus_ReceiveByte、SMBus_ReadMemory这一串。核心逻辑是Start 时 SCL 高电平期间 SDA 由高变低Stop 时 SCL 高电平期间 SDA 由低变高发送字节时 MSB 先出每个字节后跟一个 ACK 位。MLX90614 的 RAM 读取命令格式是 SlaveAddr Command 重复起始 SlaveAddr1 读数据 PEC。关键参数在头文件宏里#define ACK 0 #define NACK 1 #define SA 0x5a // 单个 MLX90614 时地址为 0x00多个时默认 0x5a #define RAM_ACCESS 0x00 #define EEPROM_ACCESS 0x20 #define RAM_TOBJ1 0x07 #define RAM_TOBJ2 0x08 #define RAM_TA 0x06SA这里写 0x5a注释说单个时地址为 0x00多个时默认 0x5a。实际 MLX90614 的 7 位从机地址出厂默认就是 0x5A左移一位后写地址 0xB4、读地址 0xB5。注释里那句“单个时地址为 0x00”容易让人误解我一般会直接按 0x5A 处理除非你改过 EEPROM 里的 SMBus 地址。读温度的函数大致长这样u16 SMBus_ReadMemory(u8 slaveAddress, u8 command) { u16 data; u8 pec; u8 arr[3]; SMBus_StartBit(); SMBus_SendByte(slaveAddress); // 写地址 SMBus_ReceiveBit(); // 读 ACK SMBus_SendByte(command); // 命令字 SMBus_ReceiveBit(); SMBus_StartBit(); // 重复起始 SMBus_SendByte(slaveAddress 1); // 读地址 SMBus_ReceiveBit(); arr[0] SMBus_ReceiveByte(ACK); // 低字节 arr[1] SMBus_ReceiveByte(ACK); // 高字节 pec SMBus_ReceiveByte(NACK); SMBus_StopBit(); data (arr[1] 8) | arr[0]; return data; }逻辑说明先发写地址再发 RAM 命令字然后重复起始位切换成读地址连续读两个字节最后发 NACK 再 Stop。data是 16 位原始值低字节在前。参数说明slaveAddress传 0xB4command传RAM_TOBJ1即 0x07。读回来的原始值要转成摄氏度公式是temp data * 0.02 - 273.15这个 0.02 是 MLX90614 的固定分辨率别改成别的。2.3 misc 设备节点与 ioctl 接口驱动用miscdevice注册设备名mlx90614这样系统起来后/dev/mlx90614就能直接 open。ioctl 命令一般定义成读 TOBJ1、读 TOBJ2、读 TA 三个#define MLX90614_GET_TOBJ1 _IOR(m, 0x01, int) #define MLX90614_GET_TOBJ2 _IOR(m, 0x02, int) #define MLX90614_GET_TA _IOR(m, 0x03, int) static long mlx90614_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { int temp; switch (cmd) { case MLX90614_GET_TOBJ1: temp mlx90614_read_temp(RAM_TOBJ1); break; case MLX90614_GET_TOBJ2: temp mlx90614_read_temp(RAM_TOBJ2); break; case MLX90614_GET_TA: temp mlx90614_read_temp(RAM_TA); break; default: return -EINVAL; } if (copy_to_user((int __user *)arg, temp, sizeof(temp))) return -EFAULT; return 0; }mlx90614_read_temp内部调用SMBus_ReadMemory把原始值转成 0.01 摄氏度单位的整数返回避免浮点进内核。用户态拿到后除以 100 就是摄氏度。这种设计比直接暴露 sysfs 更灵活适合需要高频轮询的场景但要注意 ioctl 里不能睡太久SMBus 一次读大概几百微秒可以接受。3. 从编译到出数把驱动挂进 3.4.39 内核的完整步骤3.1 编译环境与 Makefile 写法Android 6.0 时代的内核编译通常走make -C $(KERNEL_DIR) M$(PWD) modules。这份源码是单文件Makefile 可以写得很短obj-m mlx90614_linux驱动.o mlx90614_linux驱动-objs : mlx90614_linux驱动.o KERNEL_DIR ? /path/to/android/kernel PWD : $(shell pwd) all: $(MAKE) -C $(KERNEL_DIR) M$(PWD) modules clean: $(MAKE) -C $(KERNEL_DIR) M$(PWD) clean注意文件名里有中文和空格风险实际编译前建议把mlx90614_linux驱动.c改成mlx90614_linux.cMakefile 里对应改掉。obj-m那行如果源文件名带中文make 解析时可能出问题这是血泪经验别问为什么编译报No rule to make target。编译前确认内核已经编译过一次Module.symvers存在否则模块加载时会出现disagrees about version of symbol的玄学错误。全志 3.4.39 内核的交叉编译工具链一般是arm-linux-gnueabi-或arm-none-linux-gnueabi-在 Makefile 里通过CROSS_COMPILE传入。3.2 设备树与 sys_config 的适配Android 6.0 全志平台可能还在用 sys_config.fex也可能已经切到设备树。如果是 fex在[sensor]段加[sensor_sck] gpio port:PH051defaultdefaultdefault [sensor_sda] gpio port:PH061defaultdefaultdefault驱动里用script_get_item(sensor_sck, gpio, item)读出来再gpio_request、gpio_direction_output。如果是设备树节点写成mlx90614 { compatible mlx90614; scl-gpios pio 5 0; sda-gpios pio 6 0; status okay; };驱动 probe 里用of_get_named_gpio取。两种方式选一种别混用混用会出现 GPIO 申请失败但 probe 不报错的情况最后读出来全是 0xFF。3.3 加载模块与验证读数编译出mlx90614_linux驱动.ko后push 到设备adb push mlx90614_linux驱动.ko /data/local/tmp/ adb shell insmod /data/local/tmp/mlx90614_linux驱动.ko adb shell dmesg | tail -20dmesg 里应该看到mlx90614: probe success之类的打印。然后写个用户态测试程序#include fcntl.h #include sys/ioctl.h #include stdio.h #define MLX90614_GET_TOBJ1 _IOR(m, 0x01, int) int main(void) { int fd open(/dev/mlx90614, O_RDWR); if (fd 0) { perror(open); return -1; } int temp; ioctl(fd, MLX90614_GET_TOBJ1, temp); printf(TOBJ1: %.2f C\n, temp / 100.0); close(fd); return 0; }交叉编译后 push 进去跑手放在传感器前面读数应该从室温往 30 多度爬。如果一直是 0 或者 65535先查 GPIO 有没有被别的驱动占用再查 SCL/SDA 有没有接反。MLX90614 的 SDA 需要上拉模块上一般自带 4.7k如果没有读出来会全是 NACK。4. 避坑与排查MLX90614 驱动最容易翻车的五个地方4.1 读回全 0 或 0xFFFF现象ioctl 返回 0 或者 65535温度换算后是 -273.15 或 1037.55。 原因SMBus 时序不对ACK 没收到就继续读或者 GPIO 方向没切对。MLX90614 在 Start 之后如果 100us 内没收到时钟会释放总线。 解决用示波器抓 SCL/SDA确认 Start 后第 9 个时钟 SDA 被从机拉低。没有示波器就加大udelay把 SCL 高电平时间从 1us 调到 5us 试。4.2 GPIO 申请失败但 probe 不报错现象insmod 成功dmesg 没有 error但读数是 0。 原因sys_config 里 GPIO 被其他驱动先占了gpio_request返回 -EBUSY但代码里没检查返回值。 解决在gpio_request后加if (ret) { printk(gpio request fail %d\n, ret); return ret; }把错误暴露出来。常见占用者是 backlight 或者 tp 驱动。4.3 温度跳变超过 1 度现象同一目标连续读值在 25.0 和 27.0 之间跳。 原因MLX90614 的 TOBJ1 是 0.02 分辨率但内部滤波没开或者读的时候刚好跨了刷新周期。 解决连续读三次取中间值或者在 EEPROM 里把 IIR 滤波系数设成 50%。源码里没做滤波用户态可以自己加滑动平均。4.4 模块加载后系统卡死现象insmod 后 adb 掉线屏幕黑。 原因SMBus 时序里用了mdelay而不是udelay在原子上下文里睡太久触发调度异常。 解决所有延时改成udelay单次不超过 100us。如果必须长延时放到工作队列里做。4.5 换平台后编译报mach/sys_config.h找不到现象从全志 3.4.39 换到 4.9 或 5.4 内核编译直接失败。 原因mach/sys_config.h和mach/platform.h是全志老平台私有头文件新内核没有。 解决把 GPIO 获取改成设备树方式删掉这两个 include用linux/gpio.h和linux/of_gpio.h替代。SMBus 时序部分不用动。5. 进阶把这份驱动改成设备树 标准 i2c 客户端的思路如果你想把这份源码往新内核上迁最省事的路径不是继续用 GPIO 模拟而是注册成标准 i2c 客户端让内核 i2c 子系统去处理时序。MLX90614 在标准 i2c 下也能读只是要处理 PEC 和重复起始位。具体做法是把SMBus_ReadMemory换成i2c_smbus_read_word_data设备树里加compatible melexis,mlx90614然后在 probe 里i2c_set_clientdata。这样代码量能砍掉三分之二但前提是你的平台 i2c 控制器支持重复起始位全志的 i2c 控制器在 3.4.39 上对重复起始支持一般所以原作者才自己写 GPIO 模拟。我一般会保留 GPIO 模拟版本作为兜底同时写一个 i2c 客户端版本用 Kconfig 切换。验证方法很简单两个版本都读室温差值在 0.5 度以内就算通过。如果 i2c 版本读出来是 0先查设备树里 i2c 节点的clock-frequency是不是 100kMLX90614 不支持 400k。从那以后我每次移植这类 SMBus 传感器都强制先用示波器抓一遍 Start 和第一个 ACK确认时序没问题再写上层逻辑。希望帮到你。本文还有配套的精品资源点击获取