简介:一份基于STM32的红外测温系统设计文档,面向嵌入式系统初学者、电子竞赛参赛者及毕业设计学生,系统阐述了从课题背景、技术现状、方案论证到硬件/软件设计、系统调试与总结的完整流程。文档选用STM32F103作为主控,搭配MLX90614红外测温模块、DS18B20辅助校准、LCD1602显示,并给出复位、电源、报警等电路设计细节,同时涵盖主程序流程图、部分程序实现及软硬件调试思路,有助于读者掌握非接触测温设备的设计方法。压缩包为单个DOCX格式文件,大小约2.09MB,总计1个文件,内容组织结构清晰,目录完整,可直接作为课设或毕设的参考资料。目前已有175人学习浏览,适合需要快速了解STM32红外测温系统整体架构与关键技术的读者。 做嵌入式这几年,红外测温这个方向几乎每年都会被拿出来翻新一次。这两年医疗电子、智能家居和工业设备里,非接触测温依然是刚需——电机堵转检测、厨电防干烧、母婴护理、人车感应,都能见到它的身影。我自己用手头最常规的STM32F103C8T6主控,配MLX90614红外测温传感器,完整搭过一套红外测温系统,从传感器选型、I2C通信、数据定标,到OLED显示和超温报警,整个流程都走了一遍。这篇就把设计过程、核心代码和踩过的坑一次性整理出来,给正在做STM32相关毕业设计或想在工控、物联网项目里加测温模块的朋友一个可直接参考的方案。
1. 方案选型:为什么是STM32加MLX90614
1.1 主控选型背后的三个理由
STM32这个系列在测温项目里几乎是默认答案,尤其是F1系列,成本低、资料全、上手门槛也低。我在这个项目里用的是STM32F103C8T6,72MHz主频、20KB SRAM、64KB Flash,对红外测温这种纯数据采集加显示的任务来说完全够用。
选它的第一个理由是I2C外设齐全。MLX90614默认走SMBus协议,本质上兼容I2C,STM32F103有硬件I2C外设,可以直接挂上去用。第二个理由是生态成熟,HAL库、标准库的例程遍地都是,出问题网上基本都能搜到答案,这对学生党做毕业设计非常友好。第三个理由是后续扩展空间大,同一个主控以后还能加WiFi模块、蓝牙模块、LCD屏、语音播报,哪怕整个项目从测温扩展到全屋环境监测,也不用换芯片。
有朋友可能会问,为什么不用Arduino?Arduino写起来确实更快,但STM32能让你真正理解寄存器操作、时钟树、外设中断这些底层逻辑,而且工业环境和毕业设计答辩对这一块的认可度更高。我个人建议:如果纯粹为了验证测温功能,Arduino可以;如果想把系统做成一个完整的产品级方案,STM32是更好的选择。
1.2 红外测温传感器怎么选
红外测温传感器市面上最常见的无非几种:MLX90614、MLX90640、AMG8833、TMP006。其中MLX90614是综合性价比最高的单点测温方案,自带DSP处理,出厂校准过,直接读寄存器就能拿温度数据,不需要自己做复杂的温度补偿。
MLX90614的核心是一颗热电堆传感器,它接收被测物体辐射的红外线,通过内部算法换算成温度。它有两个关键指标:一是测量精度,典型在±0.5°C(体温范围内),足够满足绝大多数非接触测温场景;二是视场角,常见的有35°和90°两种型号,型号后缀一般会标注,比如MLX90614ESF-BCI就是90°视场角度,适合近距离大范围测温,MLX90614ESF-DCI则是35°视场角,适合远距离小目标测温。
我在项目里选的是90°视场角的BCC型号,方便在室内环境下对桌面、设备面板这类目标快速测温。如果你是要做人体额头测温,建议用SMB系列加金属网罩,并额外做距离校准,因为D:S比(距离与目标直径的比例)直接决定你在多远的距离测到的温度是准的。
注意:MLX90614是“红外辐射测温”,和那个用NEC协议传数据的红外遥控是完全两码事。前者读的是物体自身辐射的热量,后者只是用红外光做载波通信。别把这两者混在一起选型,这是很多新手容易犯的第一个概念错误。
1.3 系统架构一句话讲清楚
整个系统说白了就是四条线。传感器负责把物体辐射的红外信号变成数字量,STM32通过I2C把数字量读回来,内部换算成摄氏温度,再送给屏幕显示,同时按键设置报警阈值,温度超限就驱动蜂鸣器报警,LED指示灯同步闪烁。如果还想加功能,串口留出来,随时接个ESP8266或蓝牙模块就能把温度数据上传到云端。
这就是一个非常典型的传感器加主控加执行器的闭环结构,不复杂,但对硬件设计、通信协议、数据处理这套嵌入式基本功的考察很全面,所以特别适合做课程设计或毕业设计。
2. 硬件设计与接线细节
2.1 主控最小系统和引脚规划
STM32F103C8T6要做的事情很明确,上电初始化、读传感器、刷屏幕、响报警,所以最小系统只要保证电源、复位、时钟三样东西不出问题就行。我用的是现成的核心板,USB供电5V经过板载AMS1117稳压到3.3V,外部接了一个8MHz晶振,由内部PLL倍频到72MHz。
引脚分配这里给出我实际用的方案,你可以直接照抄:
| 功能模块 | 引脚 | 说明 |
|---|---|---|
| MLX90614 SCL | PB6 | I2C1_SCL,硬I2C |
| MLX90614 SDA | PB7 | I2C1_SDA,硬I2C |
| OLED SCL | PB8 | 软件模拟I2C |
| OLED SDA | PB9 | 软件模拟I2C |
| 蜂鸣器 | PA6 | 高电平触发 |
| LED指示灯 | PA7 | 高电平点亮 |
| 按键1(报警阈值+) | PA0 | 外部中断/轮询 |
| 按键2(报警阈值-) | PA1 | 外部中断/轮询 |
这里有个设计经验,OLED我故意没有复用硬件I2C,而是用软件模拟。原因很简单,MLX90614的SMBus时序和标准I2C有点差异,用硬件I2C调试比较麻烦,而且OLED屏幕刷新频率高,如果和温度读取共用一条I2C总线,会产生相互等待,导致屏幕卡顿或读温度超时。分开用两路I2C,软件和硬件各管一路,逻辑清晰,代码也更好写。
2.2 MLX90614接线最容易踩的坑
MLX90614模块本身引脚不多,VCC、GND、SCL、SDA四个脚,接法看起来很简单。但这里有一个非常关键的细节:I2C总线必须接上拉电阻,STM32的PB6和PB7内部虽然可以开启上拉,但有些MLX90614模块板上已经带了4.7k上拉电阻,有些则没有。
我在第一版接线时,用的是一块没上拉电阻的裸传感器模块,直接接硬件I2C,结果SCL线上的波形完全是乱的,传感器偶尔能读到数据,大多数时候直接超时。后来用万用表量了一下SCL电平,发现高电平只有1.6V左右,明显偏弱。解决办法是在SCL和SDA各加一个4.7k电阻到3.3V,波形立刻正常了。
提示:买了MLX90614模块,先看一下板子背面有没有印着472的贴片电阻,有就说明上拉已经集成,不需要额外加;没有的话自己必须补两个4.7k电阻,否则I2C通信就是玄学,时好时坏。
另外,模块的VCC我接的是3.3V,没有接5V。虽然MLX90614支持3.3V和5V两种供电,但接5V时SDA和SCL的高电平也是5V,如果STM32这边是3.3V系统,直连会有电平不匹配的问题,需要电平转换。直接统一用3.3V供电,省事且电平兼容,实测工作稳定。
2.3 显示、报警和电源的整体匹配
OLED屏我用的是一块0.96寸128x64的I2C屏幕,这种屏在STC、STM32、ESP32项目里几乎人手一个,驱动芯片是SSD1306,支持3.3V供电,引脚直接接主控的软件I2C引脚就行。唯一要注意的是OLED的I2C地址默认是0x78(8位)或0x3C(7位),在写驱动时要和实际对应上,这个后面代码部分会说。
报警部分用的是有源蜂鸣器,低电平触发还是高电平触发取决于模块上有没有三极管驱动,我买的模块是低电平触发,所以代码里引脚默认拉低,报警时输出高电平,驱动一个NPN三极管导通蜂鸣器。LED指示灯则串联一个330欧电阻接到PA7,电流大概10mA,亮度足够也不会超IO口最大电流。
电源部分要额外提醒,MLX90614在工作时需要稳定的供电,如果使用电池供电且电池电压掉到3.0V以下,传感器读出来的温度会出现很大的漂移。实测在锂电池电压从4.2V降到3.5V的过程中,显示温度会有约0.8°C的跳变,这个是传感器内部基准电压跟随电源变化导致的。解决办法是加一颗3.3V的LDO稳压,或者用AMS1117-3.3单独给传感器供电。
3. 核心代码实现与参数解读
3.1 初始化流程与I2C通信细节
我的代码基于STM32标准库开发,初始化顺序是:系统时钟72MHz、GPIO复用、I2C1主机模式、软件I2C引脚、按键GPIO、蜂鸣器和LED的GPIO,最后初始化OLED并显示开机画面。整个流程不长,但顺序有讲究,I2C外设的时钟一定要在GPIO配置之前使能,否则复用功能配置会失败。
硬件I2C1的配置如下:
void I2C1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; I2C_InitTypeDef I2C_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD; // 开漏复用 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); I2C_InitStructure.I2C_Mode = I2C_Mode_I2C; I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; // 2:1占空比,高速模式 I2C_InitStructure.I2C_OwnAddress2 = 0x00; I2C_InitStructure.I2C_Ack = I2C_Ack_Enable; I2C_InitStructure.I2C_ClockSpeed = 100000; // 100kHz标准模式 I2C_Init(I2C1, &I2C_InitStructure); I2C_Cmd(I2C1, ENABLE); }这里有个细节:GPIO模式必须是开漏复用(GPIO_Mode_AF_OD),不能是推挽输出。I2C协议本身设计就是开漏加外部上拉,这样才能实现线与功能和电平仲裁。如果你配成推挽,两设备同时拉低总线时可能损坏IO口,而且波形也不对。
时钟速度这里我特意设成了100kHz,没有用400kHz快速模式。原因在于MLX90614的SMBus从机时序比较挑剔,400kHz下对信号质量要求高,接线稍长就容易丢ACK。100kHz虽然慢一点,但每次只是读两字节数据,实际测试一秒钟刷新20次温度完全没问题,稳定压倒一切。
3.2 读取MLX90614温度的完整操作
MLX90614内部最核心的寄存器有两个:0x06是物体温度TOBJ,0x07是环境温度TA。读取时序是:主机先发送从机地址加写位(0x00),然后发送寄存器地址,再发重复起始位,最后发送从机地址加读位(0x01),从机返回两个字节的数据和一个PEC校验字节。
完整读取代码:
#define MLX90614_ADDR 0x5A #define MLX90614_TOBJ 0x07 uint8_t MLX90614_ReadBytes(uint8_t reg, uint8_t *data, uint8_t len) { uint8_t i; I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MLX90614_ADDR, I2C_Direction_Transmitter); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)); I2C_SendData(I2C1, reg); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED)); I2C_GenerateSTART(I2C1, ENABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT)); I2C_Send7bitAddress(I2C1, MLX90614_ADDR, I2C_Direction_Receiver); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)); for(i = 0; i < len; i++) { if(i == len - 1) I2C_AcknowledgeConfig(I2C1, DISABLE); while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED)); data[i] = I2C_ReceiveData(I2C1); } I2C_AcknowledgeConfig(I2C1, ENABLE); I2C_GenerateSTOP(I2C1, ENABLE); return 0; }读取到的原始数据是16位无符号整数,分辨率0.02°C,存的是开尔文温度。换算公式很简单:
float MLX90614_GetObjectTemp(void) { uint8_t buf[3]; uint16_t raw; float temp_k, temp_c; MLX90614_ReadBytes(MLX90614_TOBJ, buf, 3); raw = (uint16_t)(buf[0] | (buf[1] << 8)); temp_k = (float)raw * 0.02f; temp_c = temp_k - 273.15f; return temp_c; }第三个字节是PEC校验码,我这里没有做校验,而是直接忽略,因为实测正常环境下误码率极低。如果项目运行环境有强电磁干扰,建议把PEC校验加上,方法是对从机地址、寄存器地址、读地址和数据字节做一个CRC-8计算,和收到的PEC字节比对,不一致就丢弃这一帧。
3.3 数据处理:发射率、滤波和定标
MLX90614出厂默认发射率是0.5左右,写在内部EEPROM的0x04寄存器里。但现实世界被测物的发射率基本都在0.85到0.98之间,比如人体皮肤约0.98,黑色电路板约0.90,抛光金属表面只有0.1左右。用默认发射率测人体额温,读数会明显偏低。
所以使用前必须把发射率改成和目标匹配的值。写EEPROM的代码:
void MLX90614_SetEmissivity(float emis) { uint8_t val = (uint8_t)(emis * 256.0f); MLX90614_WriteEEPROM(0x04, val); }0.98的发射率对应十六进制0x62,写入后传感器内部就会按这个系数计算温度。注意EEPROM写次数有限,虽然MLX90614官方没给具体次数,但最好只在初始化时写一次,不要每次开机都写,否则容易把EEPROM写坏。
温度数据出来之后,还有一个痛点就是跳动。MLX90614在静止环境下读出来的数值本身比较稳定,但人拿着设备手抖、环境有气流扰动,数值就会有正负0.3°C的跳变。我在这里加了一个5点滑动平均滤波,就是保留最近5次采样,取平均值输出。这个滤波在代码里实现最简单,用一个循环队列就行,复杂度低,效果肉眼可见。
实操心得:如果你测的目标是自己烙铁头这类发射率很低的金属,会发现读数怎么都不准,这时候别怀疑代码,先测一块黑色电工胶带。把胶带贴在金属表面,温度读数会立刻变得合理,因为胶带的发射率接近0.95。这是做红外测温必须知道的一个小技巧。
3.4 OLED显示和超温报警的实现
OLED显示我用的是开源的SSD1306驱动,核心就是初始化配置寄存器列表,然后通过软件I2C把显存数据推送到屏幕。这里建议在内存里开一个128x8字节的显存缓冲,所有绘制操作先在缓冲里改数据,最后一次性刷新到屏幕,这样可以避免频繁I2C通信导致屏幕闪烁。
显示的布局我分了三个区域:第一行显示实时温度,第二行显示报警阈值,第三行显示状态(正常/超温)。温度整数部分和小数部分分开绘制,用大号字体突出显示。
报警逻辑比较简单:
if (temp_c >= alarm_threshold) { alert_flag = 1; GPIO_SetBits(GPIOA, GPIO_Pin_6); GPIO_SetBits(GPIOA, GPIO_Pin_7); } else { alert_flag = 0; GPIO_ResetBits(GPIOA, GPIO_Pin_6); GPIO_ResetBits(GPIOA, GPIO_Pin_7); }蜂鸣器这里我没有加延时函数来做间歇报警,而是用了一个全局变量timestamp,在主循环里通过时间戳判断是否到翻转时刻。这样避免delay卡死主循环,保证OLED刷新和按键扫描都不被打断。有些人在微博上问“stm32延时函数delay卡死”,多半就是因为在中断里调用了阻塞式延时,中断嵌套时导致死循环,这个习惯一定要改掉。
4. 常见问题与排查实录
4.1 I2C读取超时、读不到设备地址
这是刚把传感器焊上板子后最容易遇到的问题。排查顺序我是这么走的:先量VCC和GND有没有3.3V,再用示波器或逻辑分析仪看SCL有没有方波,最后用扫描程序遍历I2C地址,看从机有没有ACK。
如果扫描不到0x5A地址,80%是上拉电阻缺失导致的信号太弱,剩下20%是SDA和SCL接反了。我当时犯过一个低级错误,把SCL和SDA在杜邦线上接反了,扫描不到回执,检查了半小时才发现。另外,MLX90614有一个SMBus的从机地址引脚(有些模块引出,有些没有),不要随意去拉高拉低这个引脚,否则地址会偏移,导致主机找不到设备。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| I2C扫描无0x5A | 上拉电阻缺失 | 补4.7k上拉 |
| SCL无波形 | 引脚配置错误 | 检查GPIO配置为开漏复用 |
| 偶尔读到,偶尔超时 | 接线过长/供电不稳 | 缩短杜邦线,加稳压电容 |
| 地址不是0x5A | 地址引脚被拉高 | 确认地址引脚接GND |
4.2 温度数据跳变剧烈或者明显不对
如果数据在正常范围内来回乱跳,先软后硬排查。软件上,确认是不是每一次读取都发了正确的寄存器地址,有些代码会把0x06和0x07搞混,把环境温度当物体温度读出来,数值自然会随着室温波动。
硬件上,MLX90614的视场角决定了它测的是一个区域的平均温度,不是点温度。如果你的目标物体很小,而周围环境背景温度差异很大,读数就会飘。解决方法是让传感器尽量靠近目标,并保证目标填满视场。D:S比这个参数要记住,以90°FOV型号为例,距离被测物5cm时,测量区域直径约5cm,如果目标只有2cm宽,就必须拉近到2cm以内才准确。
还有一点,传感器本身会有自发热,长时间通电后,外壳温度升高会影响精度。所以我在产品结构设计时给传感器加了一个小的散热孔,避免热量堆积。裸板测试时,用手捏着传感器外壳几秒钟,温度读数就会漂移,这都是正常现象。
4.3 OLED显示乱码、刷新卡顿
OLED乱码优先检查I2C地址和波特率。SSD1306默认7位地址是0x3C,如果初始化时写成了0x3D,显示就是雪花点。另外软件模拟I2C的延时不能太短,我之前把I2C时钟时序参数调得非常快,结果OLED偶尔显示错乱,放慢到标准速率后问题解决。
刷新卡顿则多半是因为主循环里频繁调用全屏刷新,128x64的分辨率一次要送1024字节,走软件I2C大概需要几十毫秒。解决办法就是用显存缓冲,只在数据变化时局部刷新,温度小数点的位置基本只有下半屏变化,完全没必要整屏刷新。我把显示刷新频率控制在20Hz,肉眼看起来非常流畅,又不占CPU。
4.4 后续扩展方向
这套系统做完后,扩展空间其实很大。喜欢功能叠加的可以挂ESP8266,把温度数据用MQTT上报到Home Assistant;想做低功耗的可以把主控切到STOP模式,用定时唤醒采一次温度再接着睡,电池供电能撑很久;想提升测量精度的,可以换MLX90640做热成像显示,网格化温度分布配合伪彩色算法,效果直接把项目档次拉高一个级别。
如果还想继续深入,建议去研究一下HAL库版本的I2C中断和非阻塞读取方式,以及DMA配合I2C读取。这个项目里我用的标准库阻塞方式能保证结果正确,但想把实时性做好,还得靠中断加状态机。
最后再分享一个我自己的实操习惯:调试这类传感器项目时,我习惯先在串口调试助手上打印原始寄存器值,而不是直接打印换算后的温度。因为原始寄存器值能直接反映通信链路是否正常,如果原始值在乱跳,说明是通信问题;如果原始值稳定但温度不对,才需要去检查和发射率、定标相关的逻辑。这样做排查问题会快很多。
这套基于STM32的红外测温系统,硬件成本大概在40块左右,代码量也不大,但对从选型到调试的完整流程训练非常到位。如果你正在为毕业设计发愁,或者想在项目里快速塞进去一个可靠的测温模块,照着这条路走,基本能少走一半弯路。
本文还有配套的精品资源,点击获取