简介:这是一份基于51单片机与MXL90614红外测温模块的完整工程代码包,适合单片机初学者、嵌入式爱好者以及需要快速实现非接触测温显示的开发者使用。工程直接面向“读取红外传感器—数据处理—LCD1602显示”的完整链路,能帮助读者直接用现成代码搭建一个可运行的温度监测系统。压缩包内共8个文件,包含4个C源程序(如主测试程序、1602驱动、延时函数等)、3个头文件以及1个Keil工程文件(Uv2),整体仅8KB,结构紧凑、便于移植。目前已有1075人学习下载。代码经过实际验证,可用于温度数据的采集与显示,读者可据此理解MXL90614模拟输出转温度值的换算思路,也能学习51单片机I/O口控制LCD1602的底层写法,适合作为课设改造或入门项目参考。
1. 红外温度测温项目里,MLX90614到底是省心还是玄学
很多人第一次用 MLX90614 做红外温度测温,都会经历一轮“翻车”:5 分钟接好线,读出来的数据却忽高忽低,对着同一杯热水测三次能差出 3℃,拿水银温度计一比,更是对不上。于是论坛上开始出现“这颗传感器是玄学”的说法。实际上一线项目里 MLX90614 是相当成熟的红外温度测温方案,绝大多数读数异常不是传感器的问题,而是发射率没设、SMBus 时序没吃透、视场角被忽略。这篇文章按我做过的测温项目顺序,把 MLX90614 从接线、读寄存器、标定到排错讲完整,适合刚入手型号的新手,也适合正在为读数飘忽不定头疼的熟手——你会发现所谓玄学,基本都是没按章法来。
2. 看懂MLX90614的数据链路再做测温:发射率、SMBus与14bit精度的关系
2.1 热电堆传感器的物理输出与片上信号链
MLX90614 的核心是一颗热电堆传感器,它不像 NTC 或 PT100 那样需要接触被测物,而是被动接收物体表面辐射的红外能量。热电堆输出的本质是一个极其微弱的电压信号,幅度通常在微伏级别,必须经过片内低噪声放大器和高精度 ADC 才能变成数字量。这也是为什么 MLX90614 的测温结果比普通模拟红外探头稳定:信号链全部集成在封装内部,外部电路很难干扰到模拟部分。
理解这条链路对排查问题很有用。当你在 I2C 总线上读到温度数据时,实际上已经经过了“热电堆电压 → 环境温度参考 → ADC 数字量 → 温度查表”这几道工序。片上还有一个环境温度传感器(也就是芯片自身的温度),用于热结冷端补偿。如果环境温度传感器测量的数值偏移,物温也会跟着偏,这在后面标定章节会专门说明。
选型时要注意,芯片有 10 位和 14 位输出模式的区别。14 位模式下温度分辨率可以做到 0.02℃,10 位模式下只有约 0.18℃。做人体测温、热源定位这类对温度趋势敏感但不追求绝对精度的场景,10 位够用;做工业过程监测、质量检测这类需要记录微小温差的项目,建议直接选 14 位模式。我一般固定用 14 位,因为读取代码可以统一,省得为两种模式写两套换算。
2.2 SMBus读回来的原始数据怎么换算成温度
MLX90614 的对外接口是 SMBus,兼容 I2C,默认地址是 7 位地址 0x5A。读温度时,常用 RAM 地址有两个:0x07 是物体温度,0x06 是环境温度。每个地址读出的是 16 位数据,其中高字节是符号位和整数部分,低字节是小数部分。换算公式很简单:温度 = 原始值 × 0.02 - 273.15。
这里有个常见误区:有人拿到的数据是十六进制,比如 0x1A2B,直接当十进制数除以 100 就完事了,结果完全不对。正确做法是先判断第 15 位(符号位),如果为 1 说明温度是负的,需要先对整字求补码再乘以 0.02。摄氏度结果最终范围是 -40℃ 到 125℃。温度数据换算代码如下:
import smbus2 bus = smbus2.SMBus(1) addr = 0x5A def read_temp(reg): raw = bus.read_word_data(addr, reg) if raw > 0x7FFF: # 符号位判断 raw -= 0x10000 return raw * 0.02 - 273.15 obj_t = read_temp(0x07) # 物体温度 amb_t = read_temp(0x06) # 环境温度 print(f"物体温度: {obj_t:.2f} C, 环境温度:{amb_t:.2f} C")read_word_data读回来的是低字节在前,也就是 little-endian 序,这在 Linux 单板机上用 smbus2 库不用额外处理。但如果你用自己写的 I2C 驱动,就要注意先把低字节和高字节拼成完整 16 位,再判断符号位。还有一个细节:读 0x07 之前建议先读 0x06,让片内信号链稳定下来,极端情况下连续读 0x07 两次时,第二次的值会略有变化,这不是故障,是上一条读取操作对片内电容的残余影响。
2.3 发射率寄存器为什么是测温误差的最大来源
红外测温的原理决定了它默认被测物是黑体,发射率等于 1.0。MLX90614 出厂默认发射率也是 1.0,但现实中几乎没有任何物体是理想黑体:抛光金属大约 0.1,氧化铁皮约 0.7,人体皮肤约 0.98。如果被测物发射率是 0.7,而你按 1.0 去算,读出来的温度会比真实值低,而且温度越高偏差越大。
发射率存放在 EEPROM 地址 0x04,默认值对应 1.0。这个值可以通过写 EEPROM 指令修改,但 EEPROM 的写入次数有限,工业现场不建议频繁改写。更常见的做法是:给不同被测物建立发射率对照表,代码里根据物品种类动态切换。比如检测 PCB 板时用 0.92,检测黑色塑料壳时用 0.95,检测铝片时用 0.15。
修改发射率寄存器之前,必须先写 SMBus 访问密钥 0xFFFF 到地址 0x00,再写目标值到 0x04。写完后还要读一遍做校验。不少人在这一步栽跟头,以为写完立即生效,实际 EEPROM 写入耗时约 20ms,写完立刻读会拿到旧值。发射率精度建议保留 2 位小数,因为该寄存器对数值变化非常敏感,差 0.01 在 100℃ 时能引起大约 0.3℃ 的偏差。
3. 把MLX90614接进你的开发板:硬件接线、I2C枚举与最小读温命令
3.1 接线与电平匹配:3.3V直连还是需要转换
MLX90614 的供电范围是 3.3V 到 5V,但 SMBus 引脚(SDA、SCL)的电平以供电电压为准。如果你的开发板 I2C 引脚是 3.3V,传感器也用 3.3V 供电,直接连接没有任何问题。如果传感器用 5V 供电而控制端是 3.3V,就需要电平转换,否则长期运行可能损伤控制端 I/O。常见做法是用双向电平转换模块,或者给 SDA、SCL 串接限流电阻做简单保护,但后者只适合低速短距离场景。
接线方面还需要注意 MLX90614 模块上的上拉电阻。很多成品模块出厂时已经板载了 4.7kΩ 上拉,这时如果控制端 I2C 总线本身也带 1.8kΩ 上拉,两组合并后总阻值约 1.3kΩ,总线会被拉得很“硬”,信号边沿变陡,在长线场景下可能引发振铃。我习惯先查模块原理图,确认上拉电阻值再做判断。如果是自己画板的裸芯片,则必须外接上拉电阻,推荐 4.7kΩ 到 10kΩ 之间,I2C 标准模式 100kHz 下这个范围最稳。
供电质量容易被忽视。MLX90614 内部 ADC 对环境温度测量要求参考电压稳定,如果用开关电源毛刺较多,读数会周期性跳动。我在项目中会在 VDD 到 GND 之间加 0.1μF 和 10μF 电容,位置尽量贴近传感器引脚。如果传感器和 MCU 共用电源,还要注意 MCU 拉电流造成的电压跌落,最稳妥是给传感器单独走一条电源线。
3.2 用i2cdetect确认设备地址:0x5A是怎么来的
拿到模块第一步不是写代码,而是先枚举 I2C 总线,确认设备真的在线上。Linux 开发板上用 i2c-tools 最直接:
sudo apt install i2c-tools sudo i2cdetect -y 1如果看到总线地址列表的 0x5A 位置出现编号,说明设备通信正常;如果只出现“UU”,说明该地址被内核驱动占用,需要先卸载对应驱动再测。总线号要按实际硬件来,有些单板机是 0 号总线,有些是 1 号,查ls /dev/i2c*就能确认。
枚举不到设备时,先量传感器 VDD 和地之间的电压是否在 3.3V 上下,再量 SDA、SCL 是否被拉高到供电电压。I2C 总线的空闲状态应该是高电平,如果 SDA 或 SCL 是 0V,说明总线被某个设备拉死,逐个断开从设备就能找到元凶。还有一种隐蔽情况:模块的地址引脚 ADDR 被接到了 VDD,此时地址会变成 0x30 左右的偏移值,用 0x5A 当然找不到。
地址问题解决后,再进入下一步写读取代码。我建议把i2cdetect的结果截图或记录,作为现场调试的凭证。单品测试时如果地址发生变化,排除硬件损坏之外,绝大部分是 ADDR 引脚的接线方式和你预期不一致。
3.3 最小读温代码:一次读取物体温度与环境温度
跑通 I2C 枚举之后,写最小读温程序就顺理成章了。这里用 Python 的 smbus2 库,因为它天然支持 SMBus 读字操作,不需要手工处理 start/stop 条件。下面是完整的最小工程代码:
import smbus2 import time bus = smbus2.SMBus(1) ADDR = 0x5A def read_temperature(reg): data = bus.read_word_data(ADDR, reg) if data > 0x7FFF: data -= 0x10000 return data * 0.02 - 273.15 while True: obj = read_temperature(0x07) amb = read_temperature(0x06) print(f"object={obj:.2f}C ambient={amb:.2f}C") time.sleep(1)这段代码做了三件事:打开 I2C 总线、按 RAM 地址读字、把原始值换算成摄氏度。需要注意SMBus(1)里的数字是总线号,如果你的传感器接在另一条总线上,这里必须改成对应编号,否则一运行就报错。read_word_data一次读 16 位,这对 MLX90614 是有效的,因为 SMBus 规范支持读字命令。
代码跑起来后,先用手掌靠近传感器,观察数值是否快速上升。手掌温度大约在 35℃ 左右,如果读数在 20℃ 附近徘徊,很可能传感器没有对准手掌,或者视场角太宽把周围环境也收了进去。此时可以拿一杯 50℃ 左右的温水做目标,把传感器固定在杯口上方 3cm 处,读数应稳定在 50℃ 上下,误差不超过 2℃,这就能确认链路已经通了。
4. 标定与补偿:让MLX90614在不同被测物上真正“测温成功”
4.1 先用热电偶建立参考温度,别拿手摸当基准
很多项目说“测温成功”,实际只是“读出了数字”,并没有验证数字准不准。真正的标定必须有一个可信的参考源。我常用的方案是:一个恒温加热台,一台精度 0.1℃ 的 K 型热电偶温度计,把热电偶探头贴在样品表面,同时让 MLX90614 对准样品同一点。加热台从 30℃ 升到 100℃,每 5℃ 记录一组热电偶读数和 MLX90614 读数。
标定时最容易犯的错是传感器没对准热电偶的测量点。红外测温得到的是视场角内所有物体辐射的平均值,如果热电偶没贴在视场中心,两者比较就没有意义。操作上先用激光笔(如果模块带)或通过计算视场角找出中心位置,用记号笔在样品上画点,热电偶就固定在这个点上。距离也要固定,我习惯把传感器安装在支架上,用游标卡尺控制距离在 5cm 左右。
记录下来的数据不要只保存最终温度值,还要保存环境温度。因为 MLX90614 的物温计算依赖环境温度传感器做补偿,环境温度变化会移动整条标定曲线。做标定实验时最好在常温恒温室内进行,避免空调风口直吹传感器。标定数据至少重复 3 轮,取同一设定温度下读数的平均值作为最终值,这样可以把偶然波动滤掉。
有了温差数据后,可以有两种处理策略。如果偏差线性,直接做一个偏移修正;如果偏差随温度变化,则需要做线性回归或分段线性插值。工业项目里我更推荐分段线性插值,因为红外传感器在低温段和高温段的响应斜率不完全一致,一个全局线性修正往往两头都顾不好。
4.2 修改发射率寄存器并校验:从0x04到0x0E的写入链路
发射率寄存器的写入有一套严格时序,顺序错了传感器不会响应,或者写入不生效。先看一眼官方寄存器布局:0x00 是操作指令区,0x04 是发射率寄存器,0x0E 是传感器 ID 和配置字区域。写发射率时,必须先把 0xFFFF 写入 0x00 解锁,然后把新的发射率编码写入 0x04。
发射率编码不是直接写 0.95,而是把发射率值乘以 0xFFFF 后取整,得到 16 位十六进制数。例如发射率 0.95,计算方法是 int(0.95 × 65535),结果是 0xF2B2。写入后等待 20ms 以上,再读 0x04 校验,读出来的值换算回十进制再除以 65535,应该约等于 0.95。代码如下:
import smbus2 import time bus = smbus2.SMBus(1) ADDR = 0x5A def write_emissivity(value): code = int(value * 65535) bus.write_word_data(ADDR, 0x00, 0xFFFF) # 解锁命令 time.sleep(0.01) bus.write_word_data(ADDR, 0x04, code) # 写入发射率 time.sleep(0.05) check = bus.read_word_data(ADDR, 0x04) return check / 65535 emissivity = write_emissivity(0.95) print(f"verify emissivity = {emissivity:.3f}")注意write_word_data的第三个参数是数据值,低字节在前,smbus2 会自动处理字节序。写入发射率后,物体温度读数的换算公式不变,但片内算法会按新的发射率重新计算温度。这里有一个容易忽略的坑:写 EEPROM 次数有限,规格书标称约十万次擦写寿命,定期标定写入没问题,但不要在代码里每次都写,否则寿命消耗很快。
修改完发射率后,重新做一轮热电偶对照实验。你会发现低温段偏差变小,但高温段可能仍有残差,这时不要继续动发射率,而是检查辐射源的有效面积是否充满视场。如果视场里有背景物体混入,发射率怎么调都救不回来。
4.3 距离与视场对读数的干扰:D:S比怎么用
MLX90614 的视场角常见规格有 35°、50°、90° 等,视场角越大,相同距离下覆盖区域越大。D:S 比表示“距离:光斑直径”,意思是传感器到被测物距离与视场内光斑直径之比。比如 D:S = 12:1,距离 12cm 时测量光斑直径约 1cm。想让读数代表被测点温度,就要保证光斑直径远小于被测物表面尺寸,一般光斑直径不超过被测物最大宽度的三分之一。
实际操作中,传感器离被测物太远时,视场边缘会把桌面、夹具、手等背景物体收进来,读数会莫名偏低或者波动。这时候增大发射率起不到作用,正确做法是缩小距离,或者加一个遮光管限制视场。遮光管长度与直径比一般做到 3:1 以上,内壁涂黑色无光漆,可以显著减少杂散辐射。
验证视场有没有混入背景的方法很简单:传感器对准被测物,然后用手在视场边缘晃动,如果读数出现明显变化,说明边缘物体已经被纳入计算。此时要么移动传感器,要么加遮光管。做完这一步再做发射率标定,你会发现数据稳定很多,验证成功的概率也大幅提升。
5. 避坑排查:MLX90614读数异常的5个高频现象与修复
5.1 读取返回0xFFFF:从I2C地址到总线复位的排查顺序
现象:用 i2cdetect 能看到设备,但read_word_data返回 0xFFFF,换算后是一个极大或极小的温度值。
原因几乎都出在通信时序上。SMBus 读字要求主机发送设备地址+写位,再写寄存器地址,再发重复起始信号,最后读两个字节。如果 MCU 的 I2C 控制器在发完寄存器地址后没有正确产生重复起始条件,传感器就不会返回数据,总线上的数据线保持高电平,读出来就是 0xFFFF。
解决步骤:先用逻辑分析仪抓取总线波形,确认有没有重复起始信号。如果波形正确,再用示波器量 SCL 和 SDA 的上升沿,上升沿过缓说明上拉电阻过大或总线电容过大,把上拉电阻换成 2.2kΩ 再试。还有一种情况是 MCU 的 I2C 时钟频率配得过高,SMBus 标准是 100kHz,有人为跑高速配置成了 400kHz,传感器也能响应但偶发错误,将频率降到 100kHz 后问题消失。
5.2 读数比水银温度计低2~5℃:先把发射率和视场角一起查
现象:传感器对准热水杯,读数稳定但偏低,偏差在 2℃ 到 5℃ 之间,手动补偿后能对上,但换个物体又偏了。
这种系统性偏低不是随机噪声,首先怀疑发射率。默认发射率 1.0 适合黑体,但塑料、木头、皮肤等真实物体发射率在 0.85 到 0.98 之间,按 1.0 计算必然偏低。其次怀疑视场角没充满,视场里存在大面积冷背景,传感器按辐射比例加权平均,结果也会偏低。
排查时先看距离和光斑。把传感器靠近被测物,让光斑缩小到物体表面以内,如果读数上升,说明之前有背景混入。再看发射率,查表得到被测物材料的典型发射率,写入后重新测试。如果数据仍然偏低,就采用第四章的热电偶对照法,建立实际修正曲线。这三步做完,绝大多数“低2到5℃”问题都可以解决。
5.3 数值跳变、偶发异常大数:SMBus时序与CRC校验
现象:读取程序运行时,大部分时间数据正常,但每隔几次会出现一个离谱的值,比如 200℃ 或者 -100℃,下一个周期又恢复正常。
这种偶发异常通常是读到的 16 位数据中某个字节被干扰。SMBus 没有内建的 CRC 校验,数据错误只能靠应用层判断。我在正式项目里会加一个简单滤波:连续读三次,去掉最大值和最小值,取中间值;如果中间值与上次有效值之差超过 5℃,丢弃本次并重新采样。
另外检查 MCU 中断和 I2C 操作的优先级冲突。如果 I2C 传输中途被高优先级中断打断,字节序会错位,低字节和高字节被交换,换算出的温度自然离谱。解决办法是在读取期间关闭可屏蔽中断,或者用 DMA 方式传输。还有一个隐蔽原因:电源引脚接触不良导致瞬间电压跌落,传感器内部 ADC 转换出错。给电源加 100μF 电容能明显减少这类问题。
5.4 线长超过20cm就出错:总线电容与上拉电阻的平衡
现象:把传感器用延长线接到几十厘米外的控制板上,读数频繁出错,缩短线缆后恢复。
I2C 总线设计目标偏重短距离片内通信,长线会带来电容和压摆率问题。线缆长度超过 20cm 时,分布电容可能达到几百皮法,连同上拉电阻形成的 RC 延迟会破坏时序。解决方向有两个:一是降低线上电容,使用双绞线或屏蔽线;二是换上拉电阻值,比如从 4.7kΩ 换成 2.2kΩ,加快上升沿。
但换小的上拉电阻会增加低电平灌电流,要求控制器 I/O 能承受更大的灌电流,否则会拉低电平不达标。如果要走到 50cm 以上,更稳妥的做法是改用 I2C 总线扩展芯片,或者把传感器做成独立节点,本地完成温度读取,再通过 UART 传给主机。这个改动看似增加成本,但能彻底消除长线问题。
5.5 刚上电读数慢慢漂:TO-39封装的片上自热效应
现象:冷启动后读数逐渐变化,起初几分钟内上升,然后趋于稳定,与环境温度变化不符。
这是封装自热效应。TO-39 金属封装导热速度快,片上信号链工作时会产生少量热量,导致环境温度传感器读到的温度略高于真实环境温度,进而影响物温计算结果。上电瞬间传感器处于热平衡建立过程中,读数持续漂移是正常现象,等几分钟后会进入稳态。
对策很简单:软件层面做开机预热,代码里启动后延迟 5 分钟再进行关键测量;硬件层面避免传感器靠近发热元件,比如 MCU 或电机驱动芯片。如果你的应用必须快速读数,可以考虑在上电后立即读取环境温度,然后每隔一秒重新读取,用动态变化率判断热平衡是否建立。还有一种硬办法:给传感器加隔热安装座,降低与主板的热耦合。
6. 进阶技巧:滑动窗口滤波与距离标定表,把MLX90614读数做成可交付的数据
6.1 一个自适应滑动窗口滤波
裸读数直接交给业务逻辑,往往会因为偶发尖峰产生误判。我在最终交付代码里会加一个环形缓冲区滤波,窗口长度根据温度变化速率自适应:温差变化快时窗口短,保证响应速度;稳定时窗口拉长,抑制噪声。实现并不复杂:
from collections import deque class TempFilter: def __init__(self, maxlen=10): self.buf = deque(maxlen=maxlen) self.last = None def push(self, value): if self.last is not None and abs(value - self.last) > 8: return self.last self.buf.append(value) self.last = sum(self.buf) / len(self.buf) return self.last滤波逻辑里设置了一个 8℃ 的阈值,超过这个变化量的数据直接丢弃。这个阈值要根据实际场景调整:做热风枪测温时变化很快,阈值可以放宽到 15℃;做体温监测时变化很慢,阈值设置 3℃ 就足以识别异常。窗口长度我常用 5 或者 10,数据稳定后用 10,需要快速响应时用 5。
6.2 距离-温度修正表与一次性拟合
红外测温的读数与距离有关,但关系不是线性的。把传感器固定在被测物上方不同高度,记录同一热源下的读数,你会发现距离越远读数越低,而且低到一定程度后趋于平缓。我在项目里会制作一张距离-偏差表,用分段线性插值实时修正。实测数据不必很多,5 到 7 个距离点就够了,多余的点只会加重拟合过拟合。
修正表可以嵌入代码或放在配置文件里。现场安装时,测量固定安装距离,查表得到修正值加回读数。这个技巧特别适合无法用激光瞄准的封闭空间,比如管道内部或者加热腔体,只要固定距离已知,修正就能稳定工作。一个细节:修正表要在稳定的热源下标定,热源温度尽量接近实际工况,否则标定的偏差曲线在不同温度下会漂移。
这个项目最后交付时,我保留了标定原始记录和滤波代码。后来另一个产品直接复用这套方案,只改了发射率和距离表,十分钟就跑通了。那次经历让我养成一个习惯:凡是传感器类项目,标定数据和修正逻辑必须跟着产品文档走,而不是散落在某个实验脚本里。希望这些经验也能帮到你,让你在下一个红外温度测温项目里少走几步弯路。
本文还有配套的精品资源,点击获取