☰
基于STM32的MLX90614红外测温完整链路与避坑指南
2026/10/14 1:27:03 网站建设 项目流程

简介:本资源是一套基于STM32F103C8T6的MLX90614红外测温与OLED显示完整工程,面向嵌入式初学者、电子竞赛参赛者及需要快速落地非接触测温项目的开发者。工程涵盖I2C驱动、SPI驱动、MLX90614传感器库、温度换算及OLED显示逻辑,并配有可直接烧录使用的源码与工程配置,适合对照学习STM32外设开发与传感器数据链路搭建。压缩包共157个文件,包含41个h头文件、38个c源文件,另有uvprojx/uvoptx工程文件、axf可执行文件及编译中间文件等,包体约3.05MB,目录结构完整,便于直接导入Keil工程进行修改和调试。资源已有818人浏览学习,附带的“MLX90614测温软件V1.1”为实际可用版本,读者可在此基础上理解红外测温协议、显示刷新流程及嵌入式模块化编程思路,是入门STM32与红外传感显示应用的实用参考资料。

1. 基于STM32的MLX90614测温显示:一个工程文件里藏着的红外测温完整链路

把“基于STM32的MLX90614测温显示.rar”这个压缩包解开,里面其实是一整套红外测温的最小可行方案:MCU用I2C总线去读MLX90614红外温度传感器,拿到环境温度和物体温度,再送到OLED或者串口上显示出来。这看起来只是个课程设计级别的Demo,但实际上它覆盖了嵌入式开发里最容易被忽视的几个环节:SMBus和I2C的兼容差异、传感器校准、数据滤波、显示刷新策略。如果你正打算做非接触测温、热源检测或者人体感应类的小产品,这个方案就是最好的起点——它比DS18B20那种接触式测温更灵活,比热成像模块便宜一个数量级,而且STM32的HAL库生态让整个移植过程非常顺。

我见过不少人拿这个方案翻车,主要不是电路接错,而是没搞懂MLX90614的通信时序和寄存器布局。比如读回来的温度一直是0xFFFF,或者数据跳变超过5摄氏度,这些问题多半和PEC校验、I2C时钟频率、供电纹波有关。接下来的内容会按硬件连接、寄存器读写、代码实现、避坑排查、进阶扩展这五条线展开,让你从零开始把这个方案跑通,并且知道每个参数为什么这么设。

2. 先把硬件链路铺好:MLX90614的引脚、供电与I2C时序

2.1 引脚定义与最小接线方案

MLX90614是一个TO-39金属封装的传感器,看起来像个小三极管,但引脚只有四个:VCC、GND、SDA、SCL。VCC的输入范围是2.6V到3.6V(部分型号支持5V供电,但查看数据手册最稳妥),SDA和SCL都是开漏输出,必须外接上拉电阻。最常见的接法是VCC接3.3V,SDA和SCL各接一个4.7kΩ电阻到3.3V,然后连到STM32的PB6和PB7(I2C1的默认引脚)。

连线清单: STM32 PB6 (I2C1_SCL) —— 4.7kΩ上拉至3.3V —— MLX90614 SCL STM32 PB7 (I2C1_SDA) —— 4.7kΩ上拉至3.3V —— MLX90614 SDA STM32 3.3V —— MLX90614 VCC STM32 GND —— MLX90614 GND

上拉电阻的取值不是随便定的。4.7kΩ在100kHz的I2C标准模式下是安全的,但如果后续你想把I2C频率提到400kHz,建议换成2.2kΩ,否则上升沿太慢会导致通信错位。我一般会先在STM32CubeMX里把I2C1的时钟设为100kHz,先把数据读回来,再去尝试调高频率,而不是直接上400kHz。

供电方面有个很容易踩的坑:MLX90614内部有一个红外热电堆和信号调理电路,它对电源纹波特别敏感。如果你用同一个LDO同时给传感器和OLED供电,OLED刷新时的高频电流尖峰可能会耦合到MLX90614的VCC上,导致温度读数周期性跳变。稳妥做法是在传感器VCC和GND之间放一个100nF的陶瓷电容,位置尽量靠近传感器引脚,如果条件允许再加一个10μF的钽电容做低频滤波。

2.2 SMBus与I2C:同一个物理层,两套时序规矩

很多初学者第一次读MLX90614的寄存器时都会碰壁,原因是数据手册上写着它用的是SMBus协议,而不是标准I2C。这两者在物理层基本一样,都是开漏加时钟同步,但时序细节有差别。SMBus要求时钟低电平时间有最小限制,而且所有的读写操作都支持PEC(Packet Error Checking)——一个CRC8校验字节。MLX90614默认是启用PEC的,但也可以配置为禁用。

在实际代码里,最常见的做法是用STM32的硬件I2C外设去做SMBus的时序发送,因为硬件I2C能保证起始条件、停止条件、应答位的时序是严格准确的。如果你用软件模拟I2C,也不是不行,但必须注意SMBus对时钟低电平时间的下限要求,延时太短会导致传感器不响应。

这里还要注意地址的问题。MLX90614的7位I2C地址是0x5A,左移一位后是0xB4(写地址)和0xB5(读地址)。在半字读取模式下,你向传感器发送地址0x00和命令0x07,然后传感器会返回三个字节的数据:低字节、高字节、PEC校验字节。如果你忽略了最后一个PEC字节,直接把前两个字节当作温度数据,读出来的值大多数情况下是对的,但偶尔会错得离谱,这就变成了一个“玄学”问题。

2.3 电源去耦与PCB布局对测温精度的影响

这个问题容易被忽略,但在实际项目中影响最大。MLX90614的输出分辨率是0.02摄氏度,理论精度在±0.5摄氏度左右,但这是在你给它一个“干净”的电源前提下才能达到的。如果VCC上有200mV以上的纹波,ADC的参考电压会跟着抖动,读出来的物体温度就会在真实值附近来回跳动。

我自己做这个方案时习惯使用一个单独的LDO(比如HT7333或者类似低dropout的器件)给传感器供电,而不是直接从STM32的3.3V引脚取电。STM32内部的数字电路会产生大量的开关噪声,尤其是在GPIO翻转、定时器中断、串口发送的时候,这些噪声会通过VCC耦合到传感器,让测温结果看起来像得了“帕金森”。

3. 寄存器读写是核心:RAM映射、I2C命令序列与温度转换

3.1 温度数据在哪个寄存器:RAM映射与关键地址

MLX90614内部有两类存储空间:EEPROM(0x00到0x1F)和RAM(0x00到0x1E)。EEPROM主要存放校准参数和配置位,出厂时已经写好了,一般不需要动。RAM空间存放的是实时测量结果,我们真正要读的是其中三个地址:

RAM地址内容说明
0x06TObj1物体温度(红外热电堆输出),单位是开尔文
0x07Ta环境温度(芯片内部温度传感器输出),单位是开尔文
0x05TObj2物体温度第二通道(部分型号使用,一般用0x06就够)

这里有个很多教程没讲清楚的地方:RAM地址是8位的,但读取时用的是16位命令格式。具体来说,你要发送两个字节:第一个字节是RAM地址,第二个字节是命令字。读取0x07时发送的第一个字节不是0x07,而是0x07左移一位后的0x0E?不对——实际上发送的RAM地址字节就是0x07本身,命令字节是0x00表示读RAM。这个细节如果看数据手册的时序图很容易误解。

3.2 一条完整的温度读取命令序列

用软件I2C读取MLX90614环境温度(RAM地址0x07)的完整时序如下:

START -> 发送从机地址0xB4 (写) -> 发送RAM地址0x07 -> 发送命令字0x00 -> 读第一帧数据(低字节)-> 读第二帧数据(高字节)-> 读第三帧数据(PEC)-> STOP

在STM32的HAL库里面,用硬件I2C实现这个序列非常简单。下面是完整的读取函数,以读取RAM地址0x07(环境温度)为例:

// 读取MLX90614指定RAM地址的温度数据 // ram_addr: 0x06 (物体温度) 或 0x07 (环境温度) // 返回值为摄氏度 * 100,方便整数处理 int32_t MLX90614_ReadTemperature(uint8_t ram_addr, I2C_HandleTypeDef *hi2c) { uint8_t cmd[2] = {0}; uint8_t data[3] = {0}; int32_t temp_raw = 0; float temp_celsius = 0.0; // 第一步:发起写序列,发送RAM地址和命令字 cmd[0] = ram_addr; // RAM地址,例如0x07 cmd[1] = 0x00; // 命令字,0x00表示读RAM HAL_I2C_Master_Transmit(hi2c, 0xB4, cmd, 2, 100); // 第二步:发起读序列,接收3个字节 // 注意这里是用I2C的重复起始条件(Restart)来实现的 HAL_I2C_Master_Receive(hi2c, 0xB5, data, 3, 100); // 第三步:将低字节和高字节拼接成16位原始数据 // MLX90614的数据是16位有符号数,最高位是符号位 temp_raw = (data[1] << 8) | data[0]; // 转换成摄氏度:原始数据单位是开尔文/50 // 例如原始值0x1A2B = 6699,除以50得到133.98K,减去273.15得到-139.17摄氏度 temp_celsius = (temp_raw * 0.02) - 273.15; // 返回摄氏度 * 100,保留两位小数精度 return (int32_t)(temp_celsius * 100); }

这段代码里有几个参数需要抠一下。首先是从机地址0xB4和0xB5,这是0x5A左移一位后的结果,HAL库内部会自动处理读写方向位,所以这里直接写0xB4而不是0x5A,是因为HAL库的地址参数要求包含方向位。其次是第三个字节data[2]是PEC校验码,代码里没有校验它,实际使用中你可以忽略,但如果在工业环境或者长线缆场景下使用,建议实现PEC校验,这一步能过滤掉绝大多数总线干扰。

3.3 开尔文到摄氏度的转换陷阱

MLX90614的原始温度数据单位是0.02开尔文/位,不是0.01,这是个非常容易算错的点。比如你读到一个16位值0x0834,换算成十进制是2100,乘以0.02得到42.00K,再减去273.15变成-231.15摄氏度,这显然不对。等一下,0x0834 = 2100,2100 * 0.02 = 42.0K,减去273.15确实变成负数了。但环境温度正常应该在20到30摄氏度左右,也就是293到303K,对应的原始值应该大约在14650到15150之间,这个数值读出来没有什么问题。

真正的坑在于:如果你读到的原始值前面有符号位,那么0xFFFF可能代表的是-0.02K而不是6553.5K,这取决于你把它当作有符号数还是无符号数处理。我在实际调试中遇到的情况是,MLX90614读出来的数据是无符号的,最高位不是符号位,所以直接按无符号数处理即可。但如果你用的是某些便宜的兼容替代芯片(比如有人用GY-906模块),它内部用的可能是MLX90614ESF-BAA,数据格式完全一致,所以按无符号处理不会出错。

4. 把代码跑起来:从CubeMX配置到OLED显示温度

4.1 用STM32CubeMX生成I2C与定时器的工程骨架

如果你用的是STM32F103系列或者F401系列,用CubeMX初始化I2C1和定时器是最省事的路子。打开CubeMX后,选择芯片型号,然后在Pinout页面把PB6设为I2C1_SCL,把PB7设为I2C1_SDA。接着到Clock Configuration里确认系统时钟,I2C的时钟源要挂在APB1上,通常配置为36MHz或者42MHz,这样才能让I2C外设分频出准确的100kHz或者400kHz时序。

在I2C1的参数配置页面里,有两个参数值得注意:一个是I2C Speed Mode,另一个是I2C Clock Speed,前者选Standard Mode,后者输入100000(即100kHz)。如果你选Fast Mode但时钟源配置不对,生成的时序会比实际标称值慢很多,这在调试的时候会让人误判为传感器不响应。

生成工程代码后,你会在i2c.c文件里看到HAL_I2C_MspInit函数,这里面会自动配置GPIO、时钟使能和中断优先级(如果你没有开启I2C中断,那这里只做GPIO初始化和时钟使能)。在用HAL_I2C_Master_Transmit和HAL_I2C_Master_Receive之前,确保你的CubeMX里没有勾选I2C的“Address 10-bit”选项,否则地址会被当成10位地址发送,传感器直接无视你。

4.2 软件I2C备用方案:什么时候该放弃硬件I2C

硬件I2C在绝大多数情况下是够用的,但有一个场景会让你想砸键盘:当你把MLX90614接到一组特别长的杜邦线上(超过20cm),且旁边有电机或者继电器在频繁开关,硬件I2C的应答检测可能会因为噪声干扰而卡死。这时候,硬件I2C外设会一直等待SCL线上从设备拉低的应答信号,如果应答信号没有按时出现,HAL_I2C_Master_Transmit函数会一直等到超时时间耗尽(我们代码里设的是100ms)。

我在这种场景下会临时换用软件模拟I2C。软件I2C的本质是用GPIO翻转来模拟I2C的时序,优点是时序完全受控,出现问题不会卡死整个外设,只需要在读取函数中加入超时判断就能保证主控不进入死循环。缺点是CPU占用率高,且容易受到中断干扰,所以如果你开了多个定时器中断,要确保软件I2C的时序翻转不被中断打断,否则时钟信号错位后传感器会返回乱码。

// 软件模拟I2C读取MLX90614的核心函数 // 注意本函数是阻塞式的,要求I2C总线上只有一个从设备 static uint8_t Soft_I2C_ReadByte(uint8_t ack) { uint8_t data = 0; // 设置SDA为输入模式,准备读取数据 GPIO_InitTypeDef gpio = {0}; gpio.Pin = I2C_SDA_PIN; gpio.Mode = GPIO_MODE_INPUT; gpio.Pull = GPIO_PULLUP; HAL_GPIO_Init(I2C_SDA_PORT, &gpio); for (int i = 0; i < 8; i++) { // 每个bit位开始时拉低SCL,给从设备时间准备数据 HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); DELAY_US(2); // 释放SCL,从设备在这个上升沿后把数据放到SDA上 HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); DELAY_US(2); // 读取SDA电平,得到当前bit data = (data << 1) | (HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN) ? 1 : 0); } // 拉低SCL准备发送应答/非应答信号 HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); // 配置SDA为输出模式,发送应答 gpio.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(I2C_SDA_PORT, &gpio); HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, ack ? GPIO_PIN_RESET : GPIO_PIN_SET); DELAY_US(2); // 拉高SCL,从设备在读应答期间会把SDA释放 HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_SET); DELAY_US(2); // 结束应答阶段,拉低SCL准备下一bit HAL_GPIO_WritePin(I2C_SCL_PORT, I2C_SCL_PIN, GPIO_PIN_RESET); return data; }

这段代码里面设定了2微秒的延时,对应的是100kHz的I2C时钟周期。注意这个延时函数要足够精确,最好是用定时器或者DWT计数器来实现,如果你用了HAL_Delay这种毫秒级延时,那整个I2C通信速度会慢到传感器直接超时。

4.3 数据滤波与显示:一阶低通滤波的工程价值

读回来的温度数据如果直接送到OLED上显示,你会发现小数点后两位一直在跳动,这不是传感器坏了,而是热电堆信号本身就带有毫伏级的高频噪声。要想让显示稳定又不增加成本,最简单的办法是一阶低通滤波或滑动平均滤波。滑动平均占内存,一阶低通几乎不占资源,一个乘法和一个加法就够了:

// 一阶低通滤波:alpha越小,数据越平滑,但响应越慢 #define FILTER_ALPHA 0.2f float filtered_temp = 0.0f; void Display_Temperature(float raw_celsius) { // 首次采样直接赋值,避免从0开始爬升 if (filtered_temp == 0.0f) { filtered_temp = raw_celsius; } else { filtered_temp = (1 - FILTER_ALPHA) * filtered_temp + FILTER_ALPHA * raw_celsius; } // 构造显示字符串 char display_str[16]; snprintf(display_str, sizeof(display_str), "Temp: %.2f C", filtered_temp); // 发送到OLED(以SSD1306为例) OLED_ShowString(0, 2, display_str); }

FILTER_ALPHA这个参数决定了滤波器的“粘稠度”。0.2意味着每次采样只采纳20%的新数据,大概要5个采样周期才能从旧值过渡到新值附近。如果你的测温目标是人体或者物体表面温度(变化比较缓慢),0.1到0.2是合理区间。如果你的目标是测快速运动的物体(比如热风吹过),那alpha要调到0.5以上才能跟上变化速度,否则显示的峰值会明显偏低。

5. 避坑指南:MLX90614测温项目常见的五个翻车现场

5.1 读取的温度一直是0xFFFF或0x0000:地址字节和命令字节的错位

现象:往串口打印读回来的原始值,发现一直接收到0xFFFF,或者稳定为0x0000,而且Sensor表面明显发热但数值不变。

原因:要么是发送给传感器的地址字节和数据字节顺序反了,要么是从机地址里的读写位弄错了。I2C时序要求先发从机地址,再发RAM地址,最后发命令字。很多人把RAM地址(比如0x07)放在第一个字节位置发送,然后第二个字节发0x00,检测地址没错却读不到正确数据,其实是忘了发命令字。

解决:把发送序列拆开打印确认每一字节的内容和顺序,确保是0x07, 0x00的顺序。另外用逻辑分析仪抓一下SDA和SCL的波形,确认起始条件后面跟的是0xB4(写地址),这样最直观。

5.2 温度读数周期性跳变5摄氏度以上:电源纹波惹的祸

现象:环境温度稳定不动的场景下,MLX90614读出的物体温度每隔几秒会突然跳一个固定值,然后又跳回来。

原因:传感器VCC和GND之间的去耦电容没有焊,或者LDO的噪声抑制能力不足。当系统其它外设(比如OLED、WIFI模块)开始突发传输时,电源线上的纹波瞬间拉高了热电堆信号调理电路的参考电压抖动,反映到数据上就是几摄氏度的跳变。

解决:在MLX90614的VCC和GND引脚之间放置100nF陶瓷电容,必要时并联10μF钽电容。同时用示波器看VCC波形,如果纹波超过50mV,考虑用独立的低噪声LDO给传感器供电,而不是与MCU共用一路电源。

5.3 读取到的温度与实际差距很大且不是固定偏移:发射率参数没算

现象:测一杯热水,读出来是58摄氏度,但用接触式温度计测是65摄氏度,差距不是固定的2到3摄氏度而是随着目标温度变化。

原因:MLX90614的出厂默认发射率参数(EEPROM地址0x24)是针对黑体标定的,一般默认值是1.0。但实际目标物体(比如塑料、皮肤、氧化金属)的发射率只有0.8到0.95,传感器接收到的红外辐射能量偏低,所以读出来的温度偏低。这不是传感器坏了,而是你没有针对目标表面做发射率补偿。

解决:对于非黑体目标,可以在代码里做补偿:真正温度 ≈ 测量温度 / (发射率)^(0.25) 的近似关系,或者直接把传感器做一次两点标定。最简单的办法是在已知温度的目标物体上测一次,然后测量差做一个线性偏移修正,把这个偏移量固化成代码里的补偿参数。

5.4 物体温度总是不变:没有配置单次测量模式

现象:读回归一化后的温度数据,环境温度能正确变化,但物体温度一直稳定在某个值,怎么调整传感器方向都不变。

原因:这大概率是传感器进入了SMBus的省电模式,或者你读的是RAM地址0x06但该型号实际输出在0x05上。部分MLX90614衍生型号的寄存器布局略有差异,0x06在某些版本里是无效的。

解决:查看你的模块是MLX90614ESF-BAA(标准版)还是MLX90614ESF-BCI(医疗版),医疗版的寄存器地址和发射率默认值和标准版不同。如果是模块方式购买的,可以看PCB上的丝印,或者直接读EEPROM 0x24确认发射率信息。代码层面把读到0x06和0x05两个地址的数据都打出来,选择变化正常的那一个使用。

5.5 上电后第一次读数总是偏离5摄氏度左右:热冲击效应

现象:每次上电后立即读温度,前3到5秒的数据明显偏低(或偏高),等刷几十次之后慢慢稳定。

原因:传感器刚上电时,芯片内部的温度传感器还没有达到热平衡状态。MLX90614内部有一个测量芯片温度(Ta)的传感器,它测的是传感器自身的温度,而红外热电堆测的是视角内的物体温度。上电瞬间,传感器外壳被手拿过或者刚从冷环境进入热环境,Ta的读数会有一段时间的爬坡过程,直接影响物体温度的补偿计算。

解决:代码里加一个“自检延时”,上电后至少延迟5秒再开始读取和显示温度数据。或者在代码启动阶段连续丢弃前20次采样,只把后面的数据用于显示。我在实际项目中还会故意加一个小的散热片贴在传感器外壳上(如果允许的话),这样能缩短热平衡时间,而且能抑制环境温度瞬变带来的测量误差。

6. 进阶玩法:PEC校验、多点测温与可视化数据曲线

PEC校验是SMBus协议里最容易忽略但很实用的功能。它在每个数据字节的末尾追加一个CRC8校验字节,能检测出传输过程中的单比特翻转和部分双比特错误。实现方式是多项式为0x07的CRC8,SMBus规范里有完整的查表代码,网上很容易找到。把它加到你的读取函数里能省去很多“为什么数据偶尔不对”的排查时间。

另一个值得动手的方向是把单点测温扩展成多点阵列。STM32的I2C1可以同时挂多个MLX90614传感器,但每个传感器的默认地址都是0x5A,所以你必须先把其中一个的I2C地址改掉。改法是通过SMBus访问EEPROM地址0x0E,写入新的7位地址(例如0x5B),然后重启传感器。注意改地址操作一旦执行,传感器就永远保留新地址,所以改之前要确认没有冲突。

// 修改MLX90614的I2C地址为0x5B(示例,实际应用时请谨慎) // 这是SMBus的EEPROM写操作,不是普通的I2C写 void MLX90614_ChangeAddress(uint8_t new_addr, I2C_HandleTypeDef *hi2c) { // 第一步:写EEPROM访问命令到0x20寄存器(写使能) uint8_t cmd[2] = {0x20, 0x00}; HAL_I2C_Master_Transmit(hi2c, 0xB4, cmd, 2, 100); HAL_Delay(10); // 第二步:写入新地址到EEPROM地址0x0E,注意新地址要左移一位? // 不对,EEPROM里存的是7位地址,不需要左移,0x5B直接写入 cmd[0] = 0x0E; cmd[1] = new_addr; // 0x5B HAL_I2C_Master_Transmit(hi2c, 0xB4, cmd, 2, 100); HAL_Delay(100); // 必须等待EEPROM写入完成 // 第三步:重新上电或发送复位命令让新地址生效 // 最简单可靠的方式:把传感器VCC断掉再通电 }

关于显示侧,如果你已经用OLED显示温度数值了,下一步可以考虑用STM32的定时器PWM输出来控制一个发热体,让MLX90614形成一个闭环恒温控制系统——把红外温度作为反馈变量,通过PID算法控制加热。这个方向很适合做红外加热台或者热疗设备的原型验证,代码量不大但收获很多,至少PID整定的一次血泪经验能让你记住“微分项别乱加”这个教训。

在采集频率上给一个我的习惯:MLX90614的单次测量时间典型值是100ms左右(取决于滤波配置),所以我一般把I2C读取周期设为200ms,既不会因为频繁访问让传感器忙不过来,也足够平滑地显示温度变化。如果你的场景需要更快的响应,可以在EEPROM里调整IIR滤波系数,但这会让数据噪声变大,属于“去掉平滑拿响应”的取舍。

我手上有过一块板子,因为改了EEPROM的滤波配置,导致读取数据时校验一直失败,最后只能换传感器才恢复。那次之后,我养成了一个习惯:任何EEPROM写操作之前,先把原始配置读出来记录存档,万一改坏了还能写回去,这算是给未来的自己留一剂后悔药。希望这篇内容能帮你把“基于STM32的MLX90614测温显示”这个方案一次跑通,遇到问题也能迅速定位方向。祝你的红外测温项目顺利。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询