资讯动态

STM32与MLX90614红外测温:从SMBus协议到I2C调试避坑指南

发布时间:2026/10/14 2:34:29 来源:尧图企业网站定制
简介基于STM32F103C8T6的MLX90614红外测温与OLED显示工程资源面向嵌入式初学者和开发者提供可直接烧录的完整固件工程。资源包共157个文件约3.05MB包含41个.h头文件、38个.c源文件以及工程配置、编译输出与调试相关文件.uvprojx、.axf、.map等覆盖STM32标准外设库、I2C驱动、SPI显示驱动及MLX90614传感器库等模块。已有818人学习下载。除源码外压缩包还提供工程备份和链接脚本可基于STM32F103C8T6开发板快速复现非接触测温显示系统理解I2C读取温度数据、OLED实时显示等关键实现。无论用于课程设计还是物联网温度监测入门均能提供从寄存器配置到上层显示逻辑的完整参考。1. 先回答三个问题STM32 读 MLX90614这条路到底值不值得走想做非接触测温时MLX90614 几乎是课堂和产品原型里最常见的红外传感器配合 STM32 只需要一路 I2C 就能把温度读出来显示在 OLED 上看起来简单实际调试时却有不少隐性成本。我见过不少开发者卡在同样几个地方上拉电阻没接、地址偏移写错、PEC 校验没消费、发射率被改乱最后花在排查上的时间比写代码还多。这篇笔记准备把这些点一次讲透从通信原理、工程代码到调试避坑、标定方法尽量让新手能照着搭起来让已经踩过坑的人知道当时到底错在哪。适合正在做温控小产品、毕设或者想快速验证红外测温方案的从业者。2. MLX90614 通信原理从 SMBus 协议到 STM32 硬件 I2C 的映射2.1 先想清楚你在测什么热电堆、TO 和 TA 的差别MLX90614 内部的关键部件是热电堆。目标物体发出的红外辐射透过传感器顶端的窗口照射到热电堆上热电堆因吸收辐射产生微弱温差电压这个电压经过内部 ADC 数字化再交给 DSP 换算成温度。整个测量的物理本质决定了它的行为这不是接触式测温而是把“视场角内所有物体的红外辐射”做平均。锥形视场里如果目标物体没有填满背景辐射就会混进读数这是后面所有校准时都要记住的。芯片内部会同时给出两个温度量RAM 地址 0x06 是环境温度 TA0x07 是目标温度 TO。TA 是传感器自己所在环境的温度用来做内部补偿TO 才是我们要显示和使用的目标温度。很多初学案例把 0x06 当成目标温度读出来显示因为 TA 看起来也“合理”这就丢掉了非接触测温的核心价值产品形态上等于做了一个带数字显示的室温计这一点在看别人代码或者自己写读取逻辑时都要先分辨清楚。温度换算也比较直接RAM 数据是 16 位无符号整数实际温度按线性关系换算T摄氏度 raw × 0.02 − 273.15。273.15 这个常数最容易写错有人写 273有人写 273.16在 30°C 附近会造成约 0.15°C 到 0.2°C 的偏差。说实话对消费级产品这个偏差可以容忍但如果你要做标定和计量底数必须统一否则后面无论怎么做两点校准系统误差都抹不掉。2.2 SMBus 与 I2C同族不同命PEC 和时钟频率是关键MLX90614 对外宣称兼容 I2C 和 SMBus但严格说它实现的是 SMBus 规范和标准 I2C 有几点差异会直接影响代码。首先是地址长度MLX90614 是 7 位从机地址出厂默认 0x5A而 STM32 的 HAL 库函数要求传入 8 位地址也就是要左移一位变成 0xB4写和 0xB5读。这里写错一个 bit从机就不会 ACK你在总线上读到的所有数据都是 0xFF。其次是时钟频率。SMBus 标准的工作范围是 10kHz 到 100kHz超过 100kHz 虽然器件在多数情况下也能响应但时序余量变小信号完整性一旦变差就会出现偶发错位。MLX90614 的转换周期本来就有几十毫秒测温应用根本不需要高速通信所以不要为了“看起来更快”把 I2C 时钟调到 400kHz那样只会徒增排查难度。我一般固定配置成 100kHz稳定优先。第三是 PEC 包错误校验。SMBus 协议里最后会跟一个 CRC8 字节用于校验MLX90614 默认开启 PEC。读操作时从机会在返回的数据字节后面再送一个 PEC 字节许多人写例程时只读低字节和高字节之后直接丢弃 PEC这在短线连接、实验室环境下很常见也没出问题但长导线或者电源纹波大的场景里偶发读取错误就会变成莫名其妙的“温度跳一下”。后面我会给出完整的 PEC 校验实现。还有一个和重复起始相关的细节。读 MLX90614 的标准流程是先发送从机写地址和命令字节然后主机再发起一次重复起始并切换为读方向从机在第二个传输阶段把数据推回来。STM32 的 HAL 库把这个过程拆成 Master_Transmit 和 Master_Receive 两个函数Receive 调用内部会自动发出重复起始你不需要自己去构造 START 条件但要保证两次调用之间没有过长的延时否则从机可能认为总线出错而复位。2.3 读 RAM 寄存器的完整时序逻辑分析仪看起来是什么样用逻辑分析仪抓一次读取比任何文字描述都直观。抓到的波形大致是这样先是 SCL 上的 START 条件然后主机发出 0xB4MLX90614 在第 9 个时钟周期拉低 SDA 表示 ACK主机再发命令字节 0x07从机再次 ACK第一段传输结束。第二段主机发出重复起始然后发地址 0xB5从机返回 ACK接下来依次送出三个字节RAM 低字节、RAM 高字节、PEC 字节最后主机发 STOP。注意数据字节是低字节在前也就是 LSB first 传输。初次上手的人经常把高低字节组合写反导致读出的温度变成一个离谱的负数或者几千度的大数。组合方式应该是 raw (buf[1] 8) | buf[0]不要写成 (buf[0] 8) | buf[1]。还有一个时序参数容易被忽略MLX90614 的测量周期最长约 34ms。如果你想让它一直输出最新数据就要保证两次读取之间留有足够的间隔。如果程序里用 while 循环不断读也没有加延时从机可能来不及完成新一次测量读出来的数据始终是上一次的结果表现就是“显示的温度长时间不刷新”或者“刷新很卡”。我在代码里一般把读取周期控制在 100ms 以上既满足从机节奏也给 I2C 总线留出余量。2.4 硬件连接上拉电阻和电平匹配决定通信能否稳定MLX90614 的供电范围宽3.3V 到 5V 都可以SDA 和 SCL 是开漏输出必须在总线上接上拉电阻。和 STM32 配合时我建议直接使用 3.3V 供电这样 SCL、SDA 的电平和 STM32 的 GPIO 电平完全一致不需要额外做电平转换。如果你图省事给 MLX90614 供 5V而 STM32 的引脚是 3.3V 容忍上拉电阻接到 5V 后高低电平的阈值会变得微妙能跑但不放心尤其是不太干净的电源下容易出现随机错误。上拉电阻的值一般选 4.7kΩ 到 10kΩ。要注意的是同一个 I2C 总线上如果挂了多片器件每个器件都有自己的上拉那么这些电阻是并联的总等效阻值会变小。等效电阻太小会导致 SDA 上升沿过于陡峭SMBus 规范对上升时间有上限要求反过来如果只有一组很弱的上拉却挂了多个器件边沿变缓通信又会变得脆弱。一个实用的做法是总线上每增加一个 I2C 器件就重新估算一次上拉或者干脆把 OLED 这类显示器件放到另一条 I2C 总线上让 MLX90614 独占一条总线出问题时也好排查。我见过的很多 OLED 白屏、测温偶发错误的案例最后排查下来都和上拉有关而不是代码逻辑问题。3. STM32 工程落地从 CubeMX 配置到 OLED 显示温度3.1 CubeMX 里的引脚和时钟配置工程配置用 STM32CubeMX 生成即可具体芯片型号不限制以我常用的 F103 系列为例核心步骤是RCC 选择外部晶振SYS 的 Debug 选 Serial Wire方便调试器连接I2C1 的模式选 I2C把时钟频率设置为 100kHz如果你的显示屏走硬件 I2C也挂到 I2C2如果引脚不够也可以用软件模拟 I2C 驱动 OLED。生成代码后I2C 的初始化函数会自动把 GPIO 配置为开漏复用模式。有一点需要确认开漏模式下外部上拉必须存在如果整个板上没有接上拉电阻初始化完总线上 SDA 和 SCL 都是低电平一启动就表现为总线忙这一步检查比写代码更重要。CubeMX 里 I2C 的 Timing 参数在标准外设库中不需要手工调整HAL 会按你填的时钟频率自动计算。但要注意单片机主频不同I2C 外设的实际时钟也不同如果你改了系统时钟最好重新生成一次初始化代码否则实际 I2C 频率可能和你预期的偏差很大。3.2 读取函数的核心实现Transmit 加 ReceiveMLX90614 的读取不复杂核心代码可以封装成下面这个函数#define MLX90614_ADDR 0x5A // 7位默认地址 #define MLX90614_TO 0x07 // 目标温度寄存器 #define MLX90614_TA 0x06 // 环境温度寄存器 uint8_t MLX90614_Read(uint8_t reg, uint16_t *raw) { uint8_t cmd reg; uint8_t rx[3] {0, 0, 0}; // 第一段发送从机写地址和命令字节 if (HAL_I2C_Master_Transmit(hi2c1, (MLX90614_ADDR 1), cmd, 1, 100) ! HAL_OK) return 1; // 第二段HAL内部发重复起始然后读3个字节低字节在前 if (HAL_I2C_Master_Receive(hi2c1, (MLX90614_ADDR 1), rx, 3, 100) ! HAL_OK) return 2; *raw ((uint16_t)rx[1] 8) | rx[0]; return 0; }逻辑说明第一个 HAL_I2C_Master_Transmit 发送了一个字节的命令寄存器地址HAL 在收到从机 ACK 后结束本次传输但不会发 STOP而是等下一个传输。紧接着的 HAL_I2C_Master_Receive 会自动产生重复起始条件然后从机在时钟驱动下依次送出数据字节HAL 收到 buf[2] 后发 STOP。实际上 HAL 库内部已经处理了重复起始你不需要调用其他接口。参数说明第三个参数 100 是超时时间单位是毫秒在正常 100kHz 下这个操作通常只需要几毫秒但如果总线被拉死HAL 会等到超时才返回错误超时值设得太大容易让程序卡死设得太小在调试暂停后恢复时容易误报超时我用 100ms 比较平衡。buf 长度我固定读 3 个字节第三字节是 PEC即使不用它也要消费掉否则 PEC 残留在总线上会导致下一帧错位。3.3 加上 PEC 校验排除偶发坏帧PEC 是 SMBus 的 CRC8多项式是 x^8 x^2 x 1和 I2C 协议里常见的多项式不同。它把整个通信过程中出现的从机地址位、命令字节、读地址位和数据字节都算进去最终生成一个 8 位校验值。实现如下uint8_t MLX90614_PEC(uint8_t *data, uint8_t len) { uint8_t crc 0; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { if (crc 0x80) crc (uint8_t)((crc 1) ^ 0x07); else crc (uint8_t)(crc 1); } } return crc; }调用时要把参与计算的所有字节按顺序填进去写方向的从机地址 0xB4、命令字节 0x07、读方向的从机地址 0xB5、读回来的低字节和高字节。注意这里用的是 8 位地址形式因为 SMBus 的 CRC 计算基于线上实际传输的字节。uint8_t CheckPEC(uint8_t reg, uint8_t *rx, uint8_t pecRx) { uint8_t buf[5]; buf[0] (MLX90614_ADDR 1); // 写方向地址 buf[1] reg; // 命令寄存器 buf[2] (MLX90614_ADDR 1) | 0x01; // 读方向地址 buf[3] rx[0]; // 数据低字节 buf[4] rx[1]; // 数据高字节 return (MLX90614_PEC(buf, 5) pecRx); }如果校验通过再使用数据不通过就丢弃这一帧等下一周期重新读取。这样处理后偶发的错误温度不会再直接显示出来从读数稳定性上改善非常明显尤其是在电机、继电器这类电磁干扰较强的环境里。3.4 温度换算与 OLED 显示换算时需要注意气温在零下时raw 是 16 位有符号数MLX90614 返回的是两字节补码形式。常规做法是先用无符号组合再判断符号位float temperature 0; uint16_t raw 0; if (MLX90614_Read(MLX90614_TO, raw) 0) { int16_t signed_raw (int16_t)raw; temperature (float)signed_raw * 0.02f - 273.15f; }这里的关键点是把 unsigned 强转成 int16_t。范围在正常体温附近时结果都是正数两种写法看不出差别但如果测量冷冻环境或者靠近冰块的物体不做符号扩展会把负温度算成一个很大的正值显示出来的结果会吓人一跳。OLED 显示我一般用 SSD1306 的移植库I2C 地址是 0x3C。显示部分做两行内容一行显示温度值一行加一个小写字母 c 表示摄氏度char line[16]; snprintf(line, sizeof(line), T:%.1f C, temperature); OLED_ShowString(2, 0, line);显示代码本身不复杂重点在刷新策略。OLED 局部刷新即可不要整屏反复清否则不仅闪烁还会给 I2C 总线增加不必要的负载。我习惯在读取新温度后才更新显示而不是用固定频率刷屏这样即便测量值暂时不变屏幕也不会一直闪。4. 避坑笔记MLX90614 调试中常见的 5 个坑4.1 读出来全是 0xFF先查上拉和你的地址写法现象程序能正常执行I2C 也没有报错但读回来的二进制数据全是 0xFF换算出来的温度是几千度。原因通常是两个MLX90614 没有正确上电或者从机根本没有应答。0xFF 代表 SDA 始终被上拉电阻拉高说明总线上没有任何器件拉低时钟应答。解决先把万用表打到电压挡测量传感器 VDD 和 GND 之间的电压确认 3.3V 供电到位。然后查 SDA 和 SCL 是否都接了上拉电阻到了这根线上用逻辑分析仪直接看有没有 ACK 位如果只有在主机地址发出后 SDA 一直是高那大概率是地址算错了。检查代码里传的是 7 位地址还是 8 位地址HAL 库的第二个参数必须传 8 位地址 0xB4不是 0x5A。4.2 温度读数某个值反复跳变发射率被改过现象传感器对准一个固定目标显示温度反复跳 0.3~1°C而且整体读数总是偏高或者偏低怎么摆都回不到真实值。原因MLX90614 内部 EEPROM 的发射率寄存器被人动过。发射率默认是 1.0也就是按黑体处理。大多数普通物体发射率在 0.8~0.95如果前面有人测试时把发射率改成了 0.85 而没有恢复芯片会按 0.85 的补偿逻辑输出温度读数自然系统性偏高。解决先用前面的读取函数把 EEPROM 0x04 读出来。标准值应该是 0xFFFF如果版本被改动就把发射率寄存器重新写回 0xFFFF同时确认晶振配置正确。另外要提醒的是MLX90614 的 EEPROM 写入有复杂的擦写步骤比如 0x20 命令擦除0x21 命令写入写错顺序或者中断可能会把 EEPROM 锁住所以不要在不清楚流程的情况下乱写。4.3 温度显示得异常准确但方向不对读到了 TA 而不是 TO现象手靠近传感器显示的温度最多涨一两度反应很迟钝用手握住传感器外壳反而温度变化更明显。原因读取的寄存器是 0x06 环境温度而不是 0x07 目标温度。环境温度反映的是传感器自身所处环境手靠近带来的红外辐射虽然会引起 TA 轻微变化但并非直接测量目标变化幅度很小。解决检查命令字节把寄存器的值改成 0x07。这两个值在环境稳定时往往只差几度单独读一个看起来都正常但现场验证时会露出破绽。快速分辨方法用手掌迅速盖住传感器窗口TO 会在 1 秒内明显跳变而 TA 变化缓慢。4.4 OLED 白屏、I2C 总线被拉死一个隐蔽的初始化顺序问题现象单独初始化 OLED 正常单独初始化 MLX90614 也正常两个挂到同一条总线上以后其中一个器件偶尔不接受访问甚至整个总线的 SDA 被拉低程序卡死在 HAL_I2C 等待标志位的循环里。原因两个器件的上拉电阻并联后等效阻值太小总线边沿过冲触发从机内部保护或者先初始化的器件处于半使能状态在另一器件通信时把总线当作自己的复位窗口。更常见的是 OLED 和 MLX90614 的 SDA、SCL 引脚在板子上被复用到了同一个引脚CubeMX 生成的中断优先级和 I2C 状态机互相干扰。解决如果项目允许KL 片内 I2C1 挂 MLX90614I2C2 挂 OLED把显示和测温隔离。如果引脚不够就把 OLED 改软件模拟 I2C用任意两个 GPIO 做实测在 100kHz 下没有问题。还有就是要保证总线上只有一个器件在上拉OLED 模组板上自带的上拉一般已经够了不要再额外并联过小的上拉电阻。4.5 连续读取时出现偶发一帧错误PEC 字节没人消费现象逻辑上看起来都正常但程序长时间运行后温度偶尔会跳成 0°C 或者 300°C 一次然后又恢复正常。原因读取字节数不够PEC 留在总线上。比如 HAL_I2C_Master_Receive 只接收 2 字节后从机已经把 PEC 字节推到 SDA 上HAL 发 STOP 时这个字节没能完整传输TP 中断状态残留导致下一帧数据错位。解决把 receive 长度改成 3主动消费掉 PEC。即使不做校验计算也不要留下这个字节不管这是 SMBus 设备和普通 I2C 设备在使用上的一个核心差异。如果接收长度确实没法改可以考虑读取后发一个 NACK 并主动清掉 I2C 错误标志位但代码会变复杂不值得。5. 把温度测准发射率标定和高低温校验的几点经验5.1 现场校验口诀先黑体后实测在谈算法之前先解决一个最实际的问题怎么判断你的系统测出来准不准。不要把烙铁头或者人手当成标准源人手温度受环境影响大烙铁头表面发射率不稳定。最可靠的做法是找一个发射率接近 1 的参考源常见的是黑体炉没有黑体炉时可以把传感器对准一杯充分搅拌的温水同时用一支接触式温度计放到水里等几分钟均匀后对比。注意搅拌要彻底水温均匀后读数才有参考价值。5.2 发射率补偿的工程做法如果被测物体的发射率稳定且已知可以通过设置 MLX90614 的发射率寄存器来提高读数准确度。发射率存储在 EEPROM 0x04 地址对应关系是实际发射率 EEPROM 值 / 65535。比如目标表面发射率是 0.95就往 0x04 写入 0xF3330.95 × 65535 ≈ 62259。这个写入要严格按 SMBus 的命令顺序来通常是用 0x20 命令擦除该字节再写数据。写入是个高风险操作断电中断可能损坏 EEPROM不要在产品运行时频繁执行最好在出厂标定时一次性写入。不同材料的发射率差异很大常见的有黑色哑光表面接近 0.95氧化铝约 0.2~0.4抛光金属可能低到 0.05裸露的铜和铝几乎就是红外反射镜。这时候单纯调寄存器没有意义必须考虑反射干扰。工业上有一个土办法在被测表面贴一块黑色哑光胶带把胶带表面当成测量目标发射率接近 0.95比直接测金属可靠得多。5.3 更稳的系统设计习惯温度精度要求高的场景我习惯同时读 TA 和 TO并在软件里保存最近 N 次有效帧通过 PEC 校验后才取平均N 取 5 到 10 即可。如果连续 3 帧 PEC 都失败多半是硬件在退化而不是偶发噪声这时就要检查焊接和线缆了。换一个角度说MLX90614 的关键参数其实只有一个真实测量值来源软件做再多滤波也无法修正视场角带来的混入辐射所以安装时还要确保目标物在传感器视场角范围内离传感器太近或太远读数都会偏。传感器距离目标一般控制在 2~10cm 比较实用具体视场角可以查手册。最后说一点经验我做过一阵红外测温后给我留下印象最深的不是代码而是容易把发射率设置和供电环境想得太简单。很多看起来是“玄学”的测量异常最后都会落到这两个物理实际上。把发射率当作一个必须校对的参数把供电当作一条需要被验证的链路再回来写代码你会觉得整个系统一下子变透明了。希望以上这些踩坑与验证思路能帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑