ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

单片机非接触式红外体温计设计全攻略:从选型到校准

单片机非接触式红外体温计设计全攻略:从选型到校准

简介:一份基于单片机的非接触式红外体温计设计文档,面向电子信息、自动化等专业学生及嵌入式系统初学者。资源以STC89C52单片机为核心,结合TN901红外测温模块与LCD1602显示器,完整介绍了非接触式体温计的设计方案,涵盖系统总体构造、主控电路、红外温度传感器电路、LCD1602原理图、按键电路及电源电路等硬件设计,并给出KeilC51环境下的软件流程、红外测温程序流程、系统调试与误差分析,适合作为毕业设计或课程设计的参考范本。资源包共1个文件,以doc文档形式呈现,压缩包大小约2.84MB,文档结构完整、目录清晰,便于按章节查阅对应设计细节。目前已有317人学习下载,对有类似设计需求或希望掌握单片机测温系统开发流程的读者具有直接参考价值。

1. 基于单片机的非接触式红外体温计:原理简单,做出来却老是被吐槽不准

基于单片机的非接触式红外体温计这个课题,每年做课程设计和毕业设计的人都不少,但大多数实物在验收时都会撞上同一个场面:读数和腋下体温计对不上,差个一两度还不稳定。原理上说,它就是让单片机通过 SMBus 总线读一颗红外温度传感器,再做显示和报警,逻辑很简单;工程上,发射率、测量距离、供电噪声和总线时序会联合作祟,把简单原理变成玄学现场。这篇笔记不打算帮你整理 doc 目录,而是把一条能实物跑通的主线拆开讲:先选型,再接线,然后写固件、避坑、校准,按这个顺序做不会走偏,适合正在做课设、入门嵌入式红外测温的开发者。

2. 非接触式红外体温计方案选型:51 主控够用,MLX90614 是主流

选型阶段最容易犯的错是先把单片机平台定死,再回过头找传感器。做这个设计的核心矛盾其实就两个:第一,温度数据能不能稳定读回来;第二,读回来的数能不能代表真实体温。前者由传感器和总线路由决定,后者由校准决定。所以先把主控和传感器这两样定了,其余外设按数据流补齐就行。

2.1 主控选 51 还是 STM32:性能不是第一位,开发惯性才是

常见做法是走两条线:一类用 51 内核,常见型号像 STC89C52;另一类用 STM32F103。如果这个设计只是驱动 LCD1602、几个按键、一个蜂鸣器,再读一颗 I2C/SMBus 接口的传感器,8 位机 12MHz 主频完全够用,因为 MLX90614 的总线标准速率上限就是 100kHz,真用不上 DMA、RTOS 和浮点加速。反而 51 的参考代码和教程存量最大,遇到问题随手就能搜到,适合在有限时间内把实物做出来。

如果后续想加蓝牙上报、图形界面或者触摸交互,再考虑换 STM32 或者 ESP32 这类自带无线功能的平台,省得中途换主控重写一遍驱动。我一般会先问三个问题:显示是什么?要不要联网?要几个人同时测?答案都简单的时候,51 就是风险最低的选择。下表是常见选型对比,按这个表做决策基本不会返工。

主控方向成本上手难度适合场景
51 内核(如 STC89C52)低低纯本地显示、报警、单传感器
STM32F103中中需要屏显、菜单、多传感器扩展
带无线 SoC(如 ESP32)中中高需要手机端曲线、云端记录

2.2 MLX90614 的测量原理:为什么内部还要测环境温度

传感器方向基本不用纠结,主流方案就是 MLX90614。这颗芯片把红外热电堆、信号放大、ADC、环境温度补偿和数字接口打包在一起,单片机不需要碰模拟放大这块硬骨头。原理上,所有高于绝对零度的物体都在向外辐射红外能量,辐射功率随表面温度的四次方上升;MLX90614 用热电堆感受目标辐射,同时用内部热敏电阻测量自身温度,通过冷端补偿和发射率修正,最终输出一个 16 位数字量,分辨率 0.02℃。

我见过有人想用分立红外热电堆自己搭,后来基本都放弃了,因为后端放大和冷端补偿非常难调,体温这种窄范围高精度场景很容易翻车。MLX90614 相当于把黑匣子变成了白盒子,设计者只需要关心 SMBus 时序和校准,这是它成为主流的核心原因。常见版本在 0~50℃ 区间精度标称在 ±0.5℃ 左右,医用级定制版更好,可以说方案上限不在传感器,而在校准。

2.3 系统三层架构:感知、处理、交互怎么分工

整个设计按数据流可以切成三层:感知层是 MLX90614,负责把物体温度变成数字量;处理层是单片机,负责读数据、滤波、阈值判断;交互层是 LCD1602、按键和蜂鸣器,负责显示和报警。结构很简单,但这个分层思路价值很大——感知层故障表现为读数错,处理层故障表现为逻辑错,交互层故障表现为显示和响应不对,出了问题能快速隔离到具体模块。

3. 硬件搭建:MLX90614 接线、上拉与电源处理

选型定了,下一步就是把实物搭起来。这一章说的三个点最容易在硬件上埋雷:接线、上拉、供电。这三个地方错了,后面代码怎么调都白搭,而且错误现象会被误判成软件 bug,浪费大把时间。

3.1 传感器引脚定义与一张能直接照抄的接线表

MLX90614 模块对外一般引出 VDD、GND、SDA、SCL 四个引脚,SDA 和 SCL 是开漏输出,必须依靠外部上拉电阻才能保证总线空闲时是高电平。以 51 内核为例,我常用的接法是 SDA 接 P1.0、SCL 接 P1.1,VDD 和 GND 分别接电源正负极,表格如下。

MLX90614 引脚单片机引脚说明
VDDVCC按模块要求接 3.3V 或 5V
GNDGND共地必须可靠
SDAP1.0开漏总线,需要上拉
SCLP1.1开漏总线,需要上拉

注意:如果模块引出 SA0 引脚,把它接地时器件地址为 0x5A,接 VDD 时为 0x5B,不要悬空。

接线本身不复杂,但用杜邦线连接时 VDD 一定先确认模块供电范围,SCL 和 SDA 更不能接反,两根线接反了不会烧芯片,但读回来永远是 0xFFFF,查起来非常容易忽略。地线尽量短,如果电源线和地线走线太长,SMBus 信号容易被污染,属于隐蔽问题。

3.2 上拉电阻和电源滤波不能省

SMBus 标准要求总线上必须有上拉电阻,典型取 4.7kΩ 接到 VDD。有些模块板载了上拉,有些没有,拿到手先确认。判断方法很简单:万用表打到电阻挡,量 SDA 对 VDD 的阻值,如果有明显的几十千欧阻抗,说明板载已经有上拉;如果量出来接近开路,就自己在 SDA、SCL 上各加一个 4.7kΩ 电阻。漏掉这一步,总线时序会偶尔正常偶尔失败,现象极其迷惑。

电源方面,MLX90614 内部有斩波稳零放大器,对电源纹波比较敏感,VDD 和 GND 之间要并一颗 100nF 陶瓷电容,尽量贴近传感器管脚;如果是电池供电,再并一颗 10~47uF 电解电容。没有这颗小电容,读到的温度会在零点几度范围乱漂,而这很容易被当成软件问题处理。真实项目里这个电容救过我好几次,属于性价比最高的硬件投资。

3.3 LCD1602、按键与蜂鸣器的接法

显示和交互部分,我一般用 LCD1602 的四线模式,比八线模式省 4 个 GPIO。D4~D7 接 P2.4~P2.7,RS 接 P2.0,RW 接 P2.1,E 接 P2.2;按键接 P3.2,蜂鸣器接 P3.3 经三极管驱动,不能直接用单片机引脚驱动有源蜂鸣器,电流不够。接线表如下。

外设引脚单片机引脚
LCD1602 D4~D711~14P2.4~P2.7
LCD1602 RS4P2.0
LCD1602 RW5P2.1
LCD1602 E6P2.2
按键一端P3.2,另一端 GND
蜂鸣器正极P3.3,经三极管驱动

如果主控供电是 5V,传感器模块也接 5V,两边电平一致最省事;如果传感器只能 3.3V 供电,SDA 和 SCL 就不能直接让 5V 单片机的 IO 去驱动,常见做法是加电平转换模块,或者用开漏加 3.3V 上拉。这个细节不处理,传感器偶尔工作正常偶尔死机,属于最典型的不稳定坑。

4. 固件开发:模拟 SMBus 读温度,从总线时序到滤波

硬件搭完,接下来是固件。MLX90614 走的是 SMBus 协议,和 I2C 类似但时序上有差别;不少开发者想用单片机硬件 I2C 外设直接读,结果发现 PEC 校验和时序兼容性很折腾。我习惯直接用 GPIO 模拟 SMBus 时序,代码量不大,而且完全可控,出了问题也容易定位。

4.1 开发工程与 GPIO 规划

用 Keil C51 建工程,文件分成三块:main.c 负责主流程,mlx90614.c 负责传感器的 SMBus 驱动,lcd1602.c 负责显示。GPIO 规划在写代码之前就要定死:P1.0 做 SDA,P1.1 做 SCL,P2 口接 LCD 四线,P3.2 接按键,P3.3 接蜂鸣器。中途换引脚会牵连逻辑分析仪的抓取和布线,尽量一次规划到位。

单片机 IO 配置成准双向口模式即可,也就是默认输出模式配合外部上拉,读引脚前先把引脚置 1,再读取电平状态。这个细节在 51 上已经默认支持,不需要像 STM32 那样单独配置模式,但理解它有助于排查总线状态问题。

4.2 读物体温度的完整代码:SMBus 时序与关键参数

核心驱动是模拟 SMBus 的启动、停止、读写字节,然后按 MLX90614 的读流程访问 RAM。寄存器 0x06 是环境温度,0x07 是物体温度,每个寄存器返回 16 位数据,最低位代表 0.02℃,转换成摄氏温度需要先乘以 0.02,再减去 273.15。具体代码如下。

/* 模拟SMBus读取MLX90614温度的51驱动(关键部分) */ #include <reg52.h> #include <intrins.h> sbit SDA = P1^0; /* 数据线 */ sbit SCL = P1^1; /* 时钟线 */ #define I2C_DELAY_US 5 /* 总线半周期,单位us,可调整 */ void delay_us(unsigned int t) { while (t--) _nop_(); } void i2c_start(void) { SDA = 1; SCL = 1; delay_us(I2C_DELAY_US); SDA = 0; delay_us(I2C_DELAY_US); SCL = 0; } void i2c_stop(void) { SDA = 0; SCL = 1; delay_us(I2C_DELAY_US); SDA = 1; delay_us(I2C_DELAY_US); } bit i2c_write_byte(unsigned char dat) { unsigned char i; bit ack; for (i = 0; i < 8; i++) { SCL = 0; SDA = (bit)(dat & 0x80); /* 高位先出 */ delay_us(I2C_DELAY_US); SCL = 1; delay_us(I2C_DELAY_US); dat <<= 1; } SCL = 0; SDA = 1; /* 释放总线,准备接收ACK */ delay_us(I2C_DELAY_US); SCL = 1; delay_us(I2C_DELAY_US); ack = SDA; /* 器件拉低表示ACK */ SCL = 0; return ack; } unsigned char i2c_read_byte(bit send_ack) { unsigned char i, dat = 0; for (i = 0; i < 8; i++) { SCL = 0; delay_us(I2C_DELAY_US); SCL = 1; delay_us(I2C_DELAY_US); dat <<= 1; if (SDA) dat |= 0x01; } SCL = 0; SDA = send_ack ? 0 : 1; /* ACK=0,最后一字节NACK=1 */ delay_us(I2C_DELAY_US); SCL = 1; delay_us(I2C_DELAY_US); SCL = 0; SDA = 1; return dat; } float read_mlx90614_temp(unsigned char mode) { unsigned char lo, hi; unsigned int raw; /* mode=1读取物体温度(RAM 0x07),mode=0读取环境温度(RAM 0x06) */ i2c_start(); if (i2c_write_byte(0xB4)) { /* 器件地址0x5A左移1位,写方向 */ i2c_stop(); return -100.0; /* ACK失败,总线异常 */ } i2c_write_byte(mode ? 0x07 : 0x06); i2c_start(); if (i2c_write_byte(0xB5)) { /* 0x5A<<1 | 1,读方向 */ i2c_stop(); return -100.0; } lo = i2c_read_byte(1); /* 主机回ACK,继续读 */ hi = i2c_read_byte(0); /* 最后一字节,主机回NACK */ i2c_stop(); raw = ((unsigned int)hi << 8) | lo; return (float)raw * 0.02f - 273.15f; }

这段代码里,0xB4 和 0xB5 是从器件地址 0x5A 推出来的,0x5A 左移一位得 0xB4,再加上读位得 0xB5。如果 SA0 接了 VDD,器件地址变成 0x5B,对应的写地址就是 0xB6、读地址 0xB7,这个稍微改一下就能适配。

我故意没有读 PEC 校验字节。MLX90614 在 SMBus 读操作结束时,如果主机用 NACK 加 STOP 结束,多数情况下可以跳过 PEC 而稳定工作;如果后续出现偶发数据错误,再补 PEC 也不迟,加校验的方式是把总线上所有字节按 SMBus 规范算一遍 CRC。I2C_DELAY_US 控制总线速率,12MHz 主频下取 5us 时 SCL 周期大概 20us,也就是 50kHz 左右,在 SMBus 的 100kHz 限制以内,不要把它调到 1us 以下,容易超出通信规格。

4.3 数据滤波与超温判断逻辑

MLX90614 读回来的原始值有一定噪声,直接显示会看到最后一位数字跳个不停。常见做法是加滑动平均滤波,队列长度取 8 到 16 之间,我一般取 8,响应速度和稳定性比较平衡。队列太短滤波效果差,太长又会让测温响应变得很拖,扫过额头之后数值半天跟不上。

/* 滑动平均滤波,N=8 */ #define FILTER_N 8 float filter_buf[FILTER_N]; unsigned char filter_cnt = 0; unsigned char filter_idx = 0; float temp_filter(float new_val) { float sum = 0; unsigned char i; filter_buf[filter_idx] = new_val; filter_idx = (filter_idx + 1) % FILTER_N; if (filter_cnt < FILTER_N) filter_cnt++; for (i = 0; i < filter_cnt; i++) sum += filter_buf[i]; return sum / filter_cnt; }

超温判断的逻辑也不建议单次触发:比如阈值设 37.3℃,如果采样一次超过就报警,人一挥手或者角度偏一下就会误报。常见做法是连续三次采样都超过阈值才触发蜂鸣器,并在屏幕上保留最后三次温度的平均值。主循环里两次采样之间加 100ms 左右的延时,一方面降低总线压力,另一方面也减少传感器自身发热的影响。

开机阶段要特别处理:传感器上电后前几百毫秒读数是不稳定的,建议丢弃前两次采样结果,或者等系统初始化完成 500ms 后再开始正式读取。这个细节很多人忽略,结果开机第一屏显示一个离谱温度,显得整体实现很粗糙。

5. 避坑排查:非接触红外体温计常见翻车点与定位手段

这一章是血泪经验,四个现象基本覆盖了大多数翻车现场,每条按现象、原因、解决来写,可以直接对着排查。

5.1 读数比水银体温计低 1~2℃

现象是传感器读数稳定,但就是比水银体温计低,差值大约在 1℃ 到 2℃。最常见原因是发射率寄存器没改。MLX90614 出厂发射率存储在 EEPROM 0x04 地址,有些批次默认值偏低,测普通物体没问题,测人体这种高发射率目标就会明显偏低。

解决方法是把发射率写入 0.95 左右,人体皮肤表面发射率通常取这个值。写 EEPROM 的命令序列是:发送写地址 0xB4,再发写命令 0x20,再发地址 0x04,然后送两个字节的数据 0xF3、0x32,最后发 STOP,等待 5ms 让芯片完成内部擦写,重新上电生效。这里 0xF332 对应的十进制 62258,除以 0xFFFF 约等于 0.95,发射率和寄存器的换算关系就是 value / 0xFFFF。除了发射率,距离太远也会把环境辐射混进来导致读数偏低,测量距离要控制在模块要求的几厘米范围,而不是隔着半米去测。

5.2 SMBus 返回值总是 0xFFFF 或 -273.15

这个现象最容易让人怀疑代码写错,但代码往往没问题。原因通常有四个:器件地址不对、上拉电阻缺失、SCL 太快、供电不稳。排查先确认 SA0 引脚接法,地址到底该用 0x5A 还是 0x5B;然后用万用表量 SDA 和 SCL 对 VDD 的阻值,确认外部上拉存在;再把 I2C_DELAY_US 调大一点,把总线速率降下来。

如果以上都正常,建议用逻辑分析仪抓一下总线波形,重点看第一步发送 0xB4 之后模块有没有把 SDA 拉低给 ACK。没有 ACK 说明器件根本没应答,问题基本在地址或供电;有 ACK 但读数据还是 0xFFFF,再往后查时序的 STOP 条件和 NACK 位是否正确。示波器或者逻辑分析仪在这个问题上比反复改代码打印快得多。

5.3 数值在 36.5 和 38.2 之间来回跳

现象是显示温度不稳定,像随机数一样来回跳。常见原因依次为:电源纹波过大、采样间隔太短、传感器前方有气流或移动目标、滤波队列长度不够。先处理电源,VDD 和 GND 之间加 100nF 陶瓷电容,示波器量一下纹波;再把采样频率降到 10Hz 以内,配合滑动平均滤波;同时固定测量距离,避免人在传感器前来回晃动。

这个问题的隐蔽点在于电源。传感器总线上如果和蜂鸣器、继电器共用电源,动作瞬间会出现跌落,直接表现为温度跳变。我遇到过一次,最后发现是蜂鸣器动作时电流拉垮了电源,导致采样数据瞬时异常,解决方式是蜂鸣器驱动单独从电源端取电,避免和传感器共用一根细线。

5.4 距离越近读数反而越不准

现象是传感器贴近到 1cm 甚至 0.5cm 时,读数剧烈变化,而不是更准。原因是 MLX90614 的视场角 FOV 一般比较宽,近距离时视场面积反而大于目标表面,把额头边缘、头发和背景环境都收进去了,测量结果自然乱。另一个来源是近距离下目标表面温度不均,传感器收到的是混合辐射。

解决方法是固定工作距离,通常 2~5cm 比较合理,有条件的话做一个简单的遮光支架,把距离限定住;如果产品定位是极近距离测量,换用带透镜、FOV 更小的型号会更适配。这个坑在功能验证时不明显,一放到实际使用就露馅,所以设计阶段就要确定测量距离和视场的匹配关系。

5.5 数据仍然不对时的快速复查顺序

如果前面四项都查完还没解决,按下面的顺序快速走一遍,不要东一下西一下。

顺序检查项操作方法
1电源纹波示波器看 VDD 波形,确认无跌落
2上拉电阻万用表量 SDA/SCL 对 VDD 阻值
3总线地址确认 SA0 接法,0x5A 还是 0x5B
4原始值串口打印 raw 值,确认不是 0xFFFF
5发射率读 EEPROM 0x04,确认是否接近 0.95
6校准用标准温度计做两点线性校准

这个顺序基本遵循从物理层到应用层的排查路径,每一步都有明确结论,不会在软件里反复试错。

6. 校准与扩展:把设计从能读温度做到敢报温度

读数和体温计对不上的问题,不能靠代码里硬减一个偏移量解决,更可靠的做法是做一个两点线性校准。常见做法是把传感器固定在一个恒温水面上方固定距离,等水温稳定在 35~38℃ 区间后,取两个参考点,用标准温度计的读数作为真值,同时记录传感器的原始读数,然后算一组线性修正系数。

# 两个参考点,(raw1, ref1) 和 (raw2, ref2) # 例如 raw1=36.8, ref1=36.5, raw2=38.2, ref2=38.0 k = (ref2 - ref1) / (raw2 - raw1) b = ref1 - k * raw1 print(f"t_real = {k:.4f} * t_raw + {b:.4f}")

算出 k 和 b 之后,把它们存到单片机的片内 Flash 或 EEPROM,每次开机读出并应用到显示值上。注意 k 和 b 对应的是当前这一套硬件结构,一旦改了测量距离、外壳、传感器型号,就必须重新测一次;重新烧录固件不影响已存的系数,前提是分区规划时不要把参数区和代码区重叠。

扩展方向上,如果想让这个设计从课设级变成产品原型,可以往三个方向走。第一是低功耗,用 MOS 管控制传感器电源,只在采样时上电,采样完关掉,单片机进入睡眠,平均电流能压到毫安以下。第二是无线化,换成带蓝牙或 WiFi 的 SoC,或者通过串口外接透传模块,把温度数据周期上报,手机端记录曲线,这样就能做多人连续监测。第三是自动判读,除了 37.3℃ 超温报警,还可以加连续多帧确认、测量距离提示这类辅助功能,减少误报。

我自己的习惯是硬件做完后先打印原始温度并观察半小时,确认漂移在 ±0.2℃ 以内再做校准。校准看起来像玄学,其实花半小时固定距离、测两个点、解一个一次方程,比反复改软件偏移量靠谱得多。这个课题真正的门槛从来不是单片机,而是认真对待测量这件事的态度。希望帮到你。

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

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进