简介面向嵌入式开发与红外热成像领域这份资源以正点原子探索者STM32F407开发板为基础集成MLX90640红外热成像传感器和LCD显示屏构成一套可直接运行的32x24像素热像采集与显示方案。代码通过双线性插值算法对传感器原始数据进行优化无需改动即可适配2.8寸、3.5寸、4.3寸等多种LCD尺寸便于在不同硬件条件下快速验证。压缩包共206个文件以C语言源码为主54个c、56个h同时包含Keil工程文件uvprojx/uvoptx、编译产物axf/hex/o及链接映射文件等整体约6.89MB结构清晰。工程按OBJ、FWLIB、PROJECT、HARDWARE、SYSTEM、CORE、USER模块组织分别对应目标文件、固件库、工程配置、硬件驱动、系统启动、底层核心和应用逻辑可帮助开发者理解STM32F407从底层驱动到上层应用的完整开发流程。目前已有2700余人学习下载对学习嵌入式系统、热成像处理及LCD显示均有较高参考价值。1. 热成像不是“画面”是 768 个温度点LCD 上重构它要过三关把热成像 MLX90640 直接接在正点原子探索者 STM32F407 上第一次点亮屏幕的那一刻大多数人看到的不是热像图而是满屏噪声和色斑。这很正常因为 MLX90640 本质上是 32×24 共 768 个测温点的 IIC 温度阵列传感器它输出的是一个温度矩阵不是画面。要让它在 LCD 上变成“能看”的红外图像中间隔着三件事把 IIC 数据稳定读出来、按厂家标定参数换算成真实温度、再用插值和伪彩色映射到屏幕上。下面记录的是我在探索者 F407 开发板上把这套流程完整跑通的经过包括代码、参数和反复翻车的几个坑。适合手里有 MLX90640、想把它做成热成像仪原型或毕业设计项目的工程师和电子爱好者参考。2. 了解 MLX90640 的输出形态110° 还是 55°、0x33 还是 0x352.1 两个关键选型参数视场角和 IIC 地址MLX90640 的 32×24、768 像素规格决定了它与单点测温的 MLX90614 有本质区别。MLX90614 只能告诉你“面前这一个点大概多少度”MLX90640 则能给你一整个区域的热分布。它的测温范围是 -40~300°C出厂标定精度在 ±1°C 量级NETD噪声等效温差约 0.1°C这个精度水平用于人体表面温度初筛、电气柜热点巡检、PCBA 短路发热定位都够用。选型时先看芯片后缀MLX90640ESF-BAB 是 110°×75° 宽视场适合放在设备前方做大范围监测MLX90640ESF-AAF 是 55°×35° 窄视场适合测几厘米到几十厘米外的小目标。市面上大多数拆机模块印的都是 BAB 型号也就是宽视场版本。我这次用的就是 BAB装在一个 3D 打印支架上距离被测目标 0.5~1.5 米左右效果最合适。如果你要精确测量很小的目标宽视场反而会把目标周围背景混进来导致边缘温度被拉低。另一个容易被忽略的参数是 IIC 地址。MLX90640 出厂后地址一般默认是 0x33但部分模块在出厂时被配置成了 0x35或者模块上留有 A0/A1 地址焊盘可以改。买模块时最好确认这一点否则你的驱动代码里写死 0x33读回来全是一堆 0xFF你会误以为模块坏了。2.2 探索者 F407 上的接线与供电要求正点原子探索者 STM32F407 的例程里MLX90640 的驱动通常用软件模拟 IIC这样引脚自由度很高不用锁死在某个 IIC 外设上。我习惯把 SCL 接到 PB10、SDA 接到 PB11例程里如果写的是 PB6/PB7也不要照搬确认一下引脚有没有被其他外设占用。MLX90640 模块常见的引脚是 SCL、SDA、VDD、GND 四个部分模块还引出 SOF帧同步信号在正点原子这套方案里 SOF 可不接。信号MLX90640 模块引脚探索者 F407 引脚说明SCLSCLPB10软件模拟 IIC 时钟线SDASDAPB11软件模拟 IIC 数据线VDDVCC3.3V传感器供电严禁接 5VGNDGNDGND与开发板共地SOFSOF不接如出现帧错乱再考虑引出供电要特别注意MLX90640 典型工作电流在 15mA 左右开发板的 3.3V LDO 能扛住但如果模块到开发板之间用了 20cm 以上的杜邦线线损会让 VDD 在读取瞬间跌落测温结果会整体偏低。我一般会在模块 VDD 和 GND 之间就近焊一个 10uF 陶瓷电容这个电容比换更好的杜邦线还管用。2.3 寄存器地图RAM、EEPROM 与两个控制寄存器MLX90640 的寄存器空间分三块理解它就是你调试时少翻车的关键。RAM 区起始地址 0x0400连续 768 个字对应 768 个像素的原始 ADC 值。RAM 末尾还放着 Vdd供电电压和 Ta环境温度的原始数据。EEPROM 区起始地址 0x2400存放出厂标定参数校准斜率、增益、偏置、发射率相关的系数。这些参数每一颗芯片都不同必须读出来用不能用别处抄来的烧录值。控制寄存器 1 地址 0x800D低 4 位决定帧率控制寄存器 2 地址 0x800E里面是镜像模式和分辨率相关的位。新手常犯的一个错误是每刷一帧屏幕就去读一遍 EEPROM。完全没必要EEPROM 参数固化在芯片里上电初始化时读一次缓存在内存数组里就行。后面温度换算一帧要用这组参数直接查内存比反复访问 EEPROM 快得多。3. STM32F407 读取与温度换算从 IIC 原始值到浮点温度矩阵3.1 软件模拟 IIC 的初始化为什么用开漏输出正点原子探索者的软件模拟 IIC 驱动很适合直接拿来做 MLX90640 的读取但有一个前提不要把它跑到 400kHz 极限。MLX90640 的读取操作动辄连续读写几百个字节长时间高速传输时杜邦线之间的耦合干扰很容易让某个字节错位。我一般把 SCL 拉低延时放到 5us 左右等效时钟约 100kHz虽然慢了一点但换来的是稳定。void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_10 | GPIO_Pin_11; GPIO_InitStructure.GPIO_Mode GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType GPIO_OType_OD; GPIO_InitStructure.GPIO_PuPd GPIO_PuPd_UP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); IIC_SCL 1; IIC_SDA 1; }逻辑说明配置成开漏输出并打开内部上拉这是模拟 IIC 的标准做法。开漏模式允许总线被多个设备拉低也允许从机在应答时将 SDA 拉低来通知主机如果用推挽输出总线仲裁时可能会有电流灌入的风险。GPIO_Speed 设为 50MHz 不是让传输跑到 50MHz只是保证边沿足够陡。参数说明如果开发板上 SCL/SDA 外部上拉电阻已经焊好内部上拉开不开都行如果是裸模块飞线连接内部上拉能省掉两颗电阻。缺点是内部上拉电阻阻值偏大总线上升沿慢所以时钟频率不能再往上提这是软件模拟 IIC 的天然边界。3.2 一次读完整帧从 0x0400 连读 768 个字MLX90640 每一帧的数据分布在 RAM 区 0x0400 之后按顺序把 768 个 16 位原始值读出来就得到这一帧的原始矩阵。注意是高位在前接收方要拼成 uint16_t。uint8_t MLX90640_ReadRAM(uint16_t *frame) { uint16_t addr 0x0400; IIC_Start(); IIC_SendByte(0x66); // MLX90640 写地址0x33 1 if (IIC_WaitAck()) { IIC_Stop(); return 1; } IIC_SendByte(addr 8); IIC_WaitAck(); IIC_SendByte(addr 0xFF); IIC_WaitAck(); IIC_Start(); // 重复起始信号切换到读方向 IIC_SendByte(0x67); // 读地址(0x33 1) | 1 IIC_WaitAck(); for (uint16_t i 0; i 767; i) { frame[i] IIC_ReadByte(1); // 前 767 个字每读完一个回 ACK } frame[767] IIC_ReadByte(0); // 最后一个字回 NACK通知从机结束 IIC_Stop(); return 0; }逻辑说明第一段写完寄存器地址后不直接读而是用重复起始信号把总线方向切换为读。这样省掉了一次 STOP/START 的电平翻转时序更紧凑。IIC_ReadByte 的参数 1 表示读到字节后回 ACK0 表示回 NACK最后一个字节必须回 NACK否则从机不知道主机要结束本次传输可能会继续拉低总线。参数说明0x66/0x67 是地址 0x33 左移一位后的写和读 ID。如果模块实际地址是 0x35需要改成 0x6A 和 0x6B。5V 单片机上跑这个驱动时要做电平转换但 F407 是 3.3V 系统可以直接连。4Hz 帧率下用 100kHz 软件 IIC 读一帧大约耗时 70~100ms实际体验是每秒 2~4 帧够用了。3.3 温度换算不要把原始值直接当温度用MLX90640 的 RAM 原始值是 17 位补码形式的 ADC 数据不是温度。如果直接乘以某个固定系数你会得到一堆看起来很接近但实际偏差四五度的数。正确路径是先读 EEPROM 中的标定参数再按 Melexis 官方算法做二阶补偿。float vdd; // 当前供电电压 float ta; // 芯片环境温度 float emissivity 0.95f; // 目标表面发射率 float temp[768]; // 换算后的摄氏温度矩阵 // eeData 在初始化阶段一次性读入见驱动中的 MLX90640_ReadEEPROM() MLX90640_CalculateVdd(vdd, frame, eeData); MLX90640_CalculateTa(ta, frame, eeData, vdd); MLX90640_CalculateTo(temp, frame, eeData, vdd, ta, emissivity);逻辑说明环境温度 ta 不是直接从某个“环境温度传感器”输出的它是 RAM 末尾的 Ta 原始值结合 EEPROM 中的标定关系算出来的Vdd 同理由 RAM 中的电源电压原始值换算得到。CalculateTo 再用这两个结果与四组标定参数做二阶补偿最终得到每个像素的真实温度。这套算法是 Melexis 驱动库的主体正点原子例程里已经集成直接用即可不要自己从头造公式。参数说明发射率 0.95 适合人体、木材、塑料、墙面等大多数非金属表面如果测抛光铝、铜这类低发射率表面反射背景温度会对结果产生明显影响这时候需要额外做反射温度补偿不属于基础案例的范畴。做体温筛查场景时建议先用恒温水浴或商用黑体对整机做一次标定再决定要不要在软件里加修正值。4. 从温度矩阵到 LCD 热像图双线性插值 伪彩色映射4.1 为什么插值不只是“好看”的问题32×24 的矩阵如果直接放大到 LCD 上比如探索者 F407 适配的 4.3 寸屏 480×272每个原始像素点会变成大约 15×11 的矩形色块。那种马赛克效果不仅丑还会让人误判目标边缘位置。双线性插值能把这些色块过渡平滑形成连续渐变的热像。插值后的图像分辨率变高了但请注意一点插值出来的中间灰度不代表真实测温点它的作用纯粹是显示层面的视觉平滑。768 个像素仍然是这套系统有效的测温位置插值点只是相邻像素之间的数学过渡。理解这一点你就不会在做温度分析时把插值像素当成真实数据。4.2 把温度矩阵先归一化成灰度图再做双线性插值温度矩阵是 float像素显示是 RGB中间必须做一次值域压缩。我用温度上下限映射到 0~255 灰度再做插值。这样插值过程处理的是整数灰度运算量小很多。void TempToGray(float *temp, uint8_t *gray) { float t_min 20.0f, t_max 38.0f; // 显示量程按场景调整 for (uint16_t i 0; i 768; i) { float t temp[i]; if (t t_min) t t_min; if (t t_max) t t_max; gray[i] (uint8_t)(255.0f * (t - t_min) / (t_max - t_min)); } }逻辑说明把温度高于 t_max 的像素统一压成 255低于 t_min 的统一压成 0。这样画面会保留最大对比度不会因为某个发热点把整个色标范围拉爆。显示体温场景时我会把 t_min 设在 30°C 附近t_max 设在 42°C 附近人脸背景和体温目标对比明显。void BilinearInterp(const uint8_t *src, uint16_t srcW, uint16_t srcH, uint8_t *dst, uint16_t dstW, uint16_t dstH) { for (uint16_t y 0; y dstH; y) { float sy (float)y * (srcH - 1) / (dstH - 1); uint16_t y0 (uint16_t)sy, y1 y0 1; float dy sy - y0; if (y1 srcH) y1 srcH - 1; for (uint16_t x 0; x dstW; x) { float sx (float)x * (srcW - 1) / (dstW - 1); uint16_t x0 (uint16_t)sx, x1 x0 1; float dx sx - x0; if (x1 srcW) x1 srcW - 1; dst[y * dstW x] (uint8_t)( src[y0 * srcW x0] * (1.0f - dx) * (1.0f - dy) src[y0 * srcW x1] * dx * (1.0f - dy) src[y1 * srcW x0] * (1.0f - dx) * dy src[y1 * srcW x1] * dx * dy 0.5f); } } }逻辑说明每个目标像素按比例映射回源图坐标得到浮点位置取它四个相邻源像素做距离加权平均。边界处强制取边缘像素避免越界。这是计算量最低、视觉最平滑的插值方案STM32F407 的 FPU 跑这个规模没有压力。参数说明srcW32、srcH24dstW/dstH 是显示分辨率。320×240 的插值输出单片执行约 20~40ms4Hz 帧率下完全够。如果嫌弃浮点消耗可以改成 Q16 定点数速度再提升一截但代码可读性会差一些。4.3 伪彩色映射与 LCD 刷新查表比逐点算快得多人眼对灰度梯度不敏感对色差敏感。热成像行业普遍用“黑-蓝-紫-黄-白”或“蓝-青-黄-红”这类伪彩色曲线。预先构建 256 项色表把灰度直接查表转成 RGB比在刷屏循环里做浮点分段换算快得多。typedef struct { uint8_t r, g, b; } RGB_t; void GrayToRGB(const uint8_t *gray, RGB_t *rgb, uint32_t len) { for (uint32_t i 0; i len; i) { rgb[i] color_lut[gray[i]]; // color_lut 在初始化阶段生成 } }逻辑说明色表 color_lut 的本质是 0~255 灰度到 RGB 的映射数组256×3 字节占用 SRAM 不到 1KB。生成规则采用分段插值比如 0~64 段从纯黑过渡到纯蓝64~128 段从蓝过渡到黄再往后到红和白。分段值可以根据审美调整但整体趋势是暗处用冷色、亮处用暖色。刷屏时探索者 F407 的 4.3 寸 RGB 屏走的是 LTDC 控制器可以直接把 RGB 缓冲区写到显示区域。缓冲区大小 320×240×2 字节约 150KBF407 内部 SRAM 勉强够如果做到 480×272需要把缓冲区放到外部 SDRAM。正点原子工程默认已经初始化了 SDRAM声明外部数组时用对应的内存分段即可。如果手里的是 SPI 接口的小屏把最后一步换成逐点打点就行前面插值逻辑完全不用动。5. 避坑实战MLX90640 上板后 5 个翻车现场排查5.1 现象读取结果恒为 0xFFFF温度值固定在 -40°C 附近原因IIC 地址不匹配是最高频的原因。模块实际地址是 0x35代码里写死 0x33从机根本不响应主机读到空总线默认为 0xFF。另一种可能是 SCL/SDA 引脚接反。解决先跑一个 IIC 扫描程序把总线上 0x00~0x7F 所有地址试探一遍看设备实际出现在哪个地址。然后把驱动里的写地址和读地址同步改成扫描结果。接反的问题更好排查用万用表量一下模块 SCL 引脚到开发板目标引脚的连通性。5.2 现象画面呈斜条纹或错位瀑布像花屏原因像素的行列顺序与显示方向不一致。MLX90640 的 32×24 矩阵在芯片引脚排列上有固定方向如果镜头朝上安装和朝前安装画面上下或左右是镜像的。另外部分模块读出来的行序是隔行扫描的直接按顺序填充屏幕就会出现条纹。解决先打印前 32 个数据点的值并用一个点热源比如烙铁头对准阵列的不同角落确认哪个角对应画面左上角。然后决定是做行列转置还是用控制寄存器 2 里的镜像位翻转。两者选其一不要两个都做否则画面又翻回去了。5.3 现象温度读数和测温枪差 3~5°C原因发射率设置不当占一半原因另一半原因是反射温度补偿缺失。人体皮肤发射率约 0.98普通 PCB 约 0.90抛光铝表面可能只有 0.05。测温枪同样受发射率影响但如果两者设置不同差值就会非常大。解决测人体时把发射率调到 0.98 附近测目标温度高于 100°C 的场景时再重新评估。MLX90640 对近距离小目标有边缘混叠效应目标占不满两个像素时读数会被背景拉低这种情况不要只靠单点像素温度下结论取 3×3 邻域最大值更可靠。5.4 现象LCD 刷新闪烁、有撕裂感原因刷屏期间还在读传感器两个任务共享 CPU 和总线LTDC 显存被边写边读就会在屏幕上看到撕裂横线。解决把整帧流程改为阶段处理先读完 768 个原始值再统一完成温度换算、插值、伪彩色映射最后一次性把整帧写到显存。不要让刷屏循环中间插 IIC 读取。如果仍然撕裂给刷屏过程加一个垂直同步等待。5.5 现象帧率从 4Hz 调到 16Hz 后数据跳动明显原因软件模拟 IIC 的时序在高速下不稳定尤其是杜邦线较长的场景信号上升沿变缓数据位错位。另一个因素是模块供电被拉低16Hz 帧率下内部 ADC 工作更频繁瞬时电流更大。解决把 IIC 的整体延时从 1us 放回 5us帧率控制在 4~8Hz。检查 VDD 纹波并在模块电源引脚就近加电容。如果你的项目确实需要 16Hz 以上的实时性建议换用硬件 IIC DMA 方案正点原子例程里也有对应的接口可以迁移。6. 进阶调试帧率、镜像与自动量程的运行时调整技巧6.1 帧率与镜像的寄存器写法改了不生效是怎么回事MLX90640 的控制寄存器 10x800D低四位存放帧率设定0 对应 0.5Hz1 对应 1Hz2 对应 2Hz3 对应 4Hz4 对应 8Hz5 对应 16Hz6 对应 32Hz。修改时先读回当前寄存器值只改写低四位其他位保持不变。经常有人直接往里写一个 5结果把其他功能位冲掉传感器进入异常模式。uint16_t reg MLX90640_ReadReg(0x800D); reg 0xFFF0; // 清掉低四位 reg | 4; // 设定 8Hz MLX90640_WriteReg(0x800D, reg);镜像位在控制寄存器 20x800E具体是哪一位以驱动源码中 MIRROR 宏定义为准。我的调试习惯是镜像只调一次并固化帧率做成运行时参数通过按键或串口调节避免每次改需求都重新烧固件。6.2 自动量程固定量程在冬天和夏天都不好用固定测温量程时夏天环境温度 35°C画面整片偏红冬天环境温度 10°C整片偏蓝。最简单的改进是每帧统计 768 个温度的最小值和最大值把映射量程动态调整到 min~max 附近。#define AUTO_SCALE_SMOOTH 0.3f t_min t_min_new * AUTO_SCALE_SMOOTH t_min_old * (1.0f - AUTO_SCALE_SMOOTH); t_max t_max_new * AUTO_SCALE_SMOOTH t_max_old * (1.0f - AUTO_SCALE_SMOOTH);这段平滑滤波的用意是防止画面中突然出现一个高温点导致量程瞬间跳变整幅画面对比度失衡。系数 0.3 表示新值权重数值越小越平稳建议在 0.2~0.4 之间选择。做这套东西我最大的教训是不要第一次拿到模块就直接接屏幕调界面。第一次把 IIC 地址搞错那次我对着屏幕看了半小时“雪花点”后来才意识到是地址问题。从那以后每换一个 MLX90640 模块我都会先跑一遍 IIC 扫描读一次 EEPROM 标定参数再对着 40°C 的热水杯测一组数据确认数据能复现后才开始调插值和配色。这套流程看着多花了几分钟实际上帮我省掉了几小时的无效调试。这套拆机经验如果能帮你少走一次弯路就很值了。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?