【目标实现】
制作一个热成像仪,测测点燃的香烟有多少度!
【硬件】
TEENSY 4.0 HEADERS开发板
ST7735LCD屏
MLX90640热成像传感器
【硬件连接】
TEENSY 4.0开发板支持硬件SPI。他的MOSI、SCK分别这11、13。然后st7735还需要DC、CS、RST两个IO。
TEENSY 4.0有st7735的现成示例,因此非常方便。在示例中定义了引脚:
#define TFT_MISO 12 #define TFT_MOSI 11 //a12 #define TFT_SCK 13 //a13 #define TFT_DC 9 #define TFT_CS 10 #define TFT_RST 8
与MLX90640采用I2C接口,使用开发板的19、18的SCL0与SDA0进行相连。
【MLX90640驱动】
MLX90640有官方Arduino的现成驱动,我们只要在驱动包输入MLX90640就可以下载他的驱动库到本地。

下载好驱动后,我们导航到他的官网,他的驱动也是非常之简单。根据他的示例,我移植好驱动如下:
#define TFT_DC 9
#define TFT_CS 10
#define TFT_RST 8
#include <ST7735_t3.h>
#include <Adafruit_MLX90640.h>
#include <SPI.h>
#include <Wire.h>
// 屏幕初始化
ST7735_t3 tft = ST7735_t3(TFT_CS, TFT_DC, TFT_RST);
// 热成像传感器初始化
Adafruit_MLX90640 mlx;
float frame[32*24]; // 原始32×24温度数据
float interpolatedFrame[64*48]; // 插值后64×48数据(2倍放大,适配Uno)
// 温度范围(可自行调整)
#define MINTEMP 10
#define MAXTEMP 50
// 热成像配色表(不变)
const uint16_t camColors[] = {0x480F,
0x400F,0x400F,0x400F,0x4010,0x3810,0x3810,0x3810,0x3810,0x3010,0x3010,
0x3010,0x2810,0x2810,0x2810,0x2810,0x2010,0x2010,0x2010,0x1810,0x1810,
0x1811,0x1811,0x1011,0x1011,0x1011,0x0811,0x0811,0x0811,0x0011,0x0011,
0x0011,0x0011,0x0011,0x0031,0x0031,0x0051,0x0072,0x0072,0x0092,0x00B2,
0x00B2,0x00D2,0x00F2,0x00F2,0x0112,0x0132,0x0152,0x0152,0x0172,0x0192,
0x0192,0x01B2,0x01D2,0x01F3,0x01F3,0x0213,0x0233,0x0253,0x0253,0x0273,
0x0293,0x02B3,0x02D3,0x02D3,0x02F3,0x0313,0x0333,0x0333,0x0353,0x0373,
0x0394,0x03B4,0x03D4,0x03D4,0x03F4,0x0414,0x0434,0x0454,0x0474,0x0474,
0x0494,0x04B4,0x04D4,0x04F4,0x0514,0x0534,0x0534,0x0554,0x0554,0x0574,
0x0574,0x0573,0x0573,0x0573,0x0572,0x0572,0x0572,0x0571,0x0591,0x0591,
0x0590,0x0590,0x058F,0x058F,0x058F,0x058E,0x05AE,0x05AE,0x05AD,0x05AD,
0x05AD,0x05AC,0x05AC,0x05AB,0x05CB,0x05CB,0x05CA,0x05CA,0x05CA,0x05C9,
0x05C9,0x05C8,0x05E8,0x05E8,0x05E7,0x05E7,0x05E6,0x05E6,0x05E6,0x05E5,
0x05E5,0x0604,0x0604,0x0604,0x0603,0x0603,0x0602,0x0602,0x0601,0x0621,
0x0621,0x0620,0x0620,0x0620,0x0620,0x0E20,0x0E20,0x0E40,0x1640,0x1640,
0x1E40,0x1E40,0x2640,0x2640,0x2E40,0x2E60,0x3660,0x3660,0x3E60,0x3E60,
0x3E60,0x4660,0x4660,0x4E60,0x4E80,0x5680,0x5680,0x5E80,0x5E80,0x6680,
0x6680,0x6E80,0x6EA0,0x76A0,0x76A0,0x7EA0,0x7EA0,0x86A0,0x86A0,0x8EA0,
0x8EC0,0x96C0,0x96C0,0x9EC0,0x9EC0,0xA6C0,0xAEC0,0xAEC0,0xB6E0,0xB6E0,
0xBEE0,0xBEE0,0xC6E0,0xC6E0,0xCEE0,0xCEE0,0xD6E0,0xD700,0xDF00,0xDEE0,
0xDEC0,0xDEA0,0xDE80,0xDE80,0xE660,0xE640,0xE620,0xE600,0xE5E0,0xE5C0,
0xE5A0,0xE580,0xE560,0xE540,0xE520,0xE500,0xE4E0,0xE4C0,0xE4A0,0xE480,
0xE460,0xEC40,0xEC20,0xEC00,0xEBE0,0xEBC0,0xEBA0,0xEB80,0xEB60,0xEB40,
0xEB20,0xEB00,0xEAE0,0xEAC0,0xEAA0,0xEA80,0xEA60,0xEA40,0xF220,0xF200,
0xF1E0,0xF1C0,0xF1A0,0xF180,0xF160,0xF140,0xF100,0xF0E0,0xF0C0,0xF0A0,
0xF080,0xF060,0xF040,0xF020,0xF800,};
// 标题文字(可自定义)
const char* title = "Thermal Camera";
// ===================== 核心工具函数 =====================
// 1. 手动计算文字宽度(解决ST7735_t3无textWidth的问题)
int16_t getTextWidth(const char* str, uint8_t textSize) {
// ST7735_t3: textSize=1时单字符6像素宽,textSize=2时12像素
uint8_t charWidth = 6 * textSize;
return strlen(str) * charWidth;
}
// 2. 双线性插值函数(放大温度数据,提升显示分辨率)
// src:原始数据, dst:插值后数据, srcW/srcH:原始宽高, dstW/dstH:目标宽高
void bilinearInterpolation(float* src, float* dst, uint16_t srcW, uint16_t srcH, uint16_t dstW, uint16_t dstH) {
float xRatio = (float)(srcW - 1) / (dstW - 1);
float yRatio = (float)(srcH - 1) / (dstH - 1);
for (uint16_t y = 0; y < dstH; y++) {
for (uint16_t x = 0; x < dstW; x++) {
// 计算对应原始数据的坐标(浮点)
float xFloat = x * xRatio;
float yFloat = y * yRatio;
// 整数部分(邻近像素)
uint16_t xInt = (uint16_t)xFloat;
uint16_t yInt = (uint16_t)yFloat;
// 小数部分(插值权重)
float xFrac = xFloat - xInt;
float yFrac = yFloat - yInt;
// 双线性插值计算
float val = src[yInt * srcW + xInt] * (1 - xFrac) * (1 - yFrac) +
src[yInt * srcW + (xInt + 1)] * xFrac * (1 - yFrac) +
src[(yInt + 1) * srcW + xInt] * (1 - xFrac) * yFrac +
src[(yInt + 1) * srcW + (xInt + 1)] * xFrac * yFrac;
dst[y * dstW + x] = val;
}
}
}
// ===================== 初始化函数 =====================
void setup(void) {
Serial.begin(9600);
// 屏幕初始化
tft.initR(INITR_BLACKTAB);
tft.fillScreen(ST7735_BLACK);
delay(500);
// 热成像传感器初始化
if (!mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire)) {
tft.setCursor(0, 0);
tft.setTextColor(ST7735_RED);
tft.setTextSize(1);
tft.println("Sensor not found");
while (1); // 传感器未连接则卡死
}
// 设置传感器最高精度
mlx.setMode(MLX90640_CHESS); // 棋盘模式(默认)
mlx.setResolution(MLX90640_ADC_19BIT); // 最高19位ADC分辨率(测温最准)
mlx.setRefreshRate(MLX90640_8_HZ); // 8Hz刷新率(兼顾速度与稳定性)
Wire.setClock(1000000); // I2C最高速率,提升数据读取速度
}
// ===================== 主循环 =====================
void loop() {
// 1. 读取原始温度数据
if (mlx.getFrame(frame) != 0) {
Serial.println("Read frame failed");
return; // 读取失败则跳过本次循环
}
// 2. 执行双线性插值(32×24 → 64×48,2倍放大)
bilinearInterpolation(frame, interpolatedFrame, 32, 24, 64, 48);
// 3. 找原始数据的最高温度+坐标(保证热点检测准确)
float maxTemp = MINTEMP;
uint8_t maxH = 0, maxW = 0;
for (uint8_t h = 0; h < 24; h++) {
for (uint8_t w = 0; w < 32; w++) {
float t = frame[h * 32 + w];
if (t > maxTemp) {
maxTemp = t;
maxH = h;
maxW = w;
}
}
}
// 4. 计算插值后图像的居中参数
uint16_t pixelSize = min(tft.width() / 64, tft.height() / 48); // 每个插值像素的屏幕尺寸
uint16_t totalWidth = 64 * pixelSize;
uint16_t totalHeight = 48 * pixelSize;
int16_t offsetX = (tft.width() - totalWidth) / 2; // 水平居中偏移
int16_t offsetY = (tft.height() - totalHeight) / 2; // 垂直居中偏移
// 5. 绘制插值后的热成像画面(居中,修复上下反转)
for (uint8_t h = 0; h < 48; h++) {
for (uint8_t w = 0; w < 64; w++) {
float t = interpolatedFrame[h * 64 + w];
t = constrain(t, MINTEMP, MAXTEMP); // 限制温度范围
uint8_t colorIndex = map(t, MINTEMP, MAXTEMP, 0, 255); // 映射到配色表索引
colorIndex = constrain(colorIndex, 0, 255); // 防止索引越界
// 关键修改1:垂直坐标反转(48-1-h 实现上下翻转)
int16_t x = offsetX + w * pixelSize;
int16_t y = offsetY + (48 - 1 - h) * pixelSize;
tft.fillRect(x, y, pixelSize, pixelSize, camColors[colorIndex]);
}
}
// 6. 绘制顶部居中标题(无换行)
tft.fillRect(0, 0, tft.width(), 16, ST7735_BLACK); // 清空标题区域
uint8_t titleTextSize = 1;
tft.setTextSize(titleTextSize);
tft.setTextColor(ST7735_YELLOW);
int16_t titleWidth = getTextWidth(title, titleTextSize);
int16_t titleX = (tft.width() - titleWidth) / 2;
tft.setCursor(titleX, 2);
tft.print(title);
// 7. 绘制底部居中最高温
int16_t bottomY = tft.height() - 12; // 底部Y坐标
tft.fillRect(0, bottomY, tft.width(), 12, ST7735_BLACK); // 清空底部区域
uint8_t tempTextSize = 1;
tft.setTextSize(tempTextSize);
tft.setTextColor(ST7735_WHITE);
// 拼接温度文字
char tempText[20];
sprintf(tempText, "Max Temper: %.1f°C", maxTemp);
// 计算文字居中坐标
int16_t tempWidth = getTextWidth(tempText, tempTextSize);
int16_t tempX = (tft.width() - tempWidth) / 2;
tft.setCursor(tempX, bottomY);
tft.print(tempText);
// 8. 绘制原始数据热点的十字标记(适配上下反转)
// 关键修改2:热点垂直坐标反转(24-1-maxH 实现原始数据行反转,再×2倍插值)
int16_t crossW = maxW * 2; // 宽度不变
int16_t crossH = (24 - 1 - maxH) * 2; // 高度反转后放大
int16_t crossX = offsetX + crossW * pixelSize + pixelSize / 2;
int16_t crossY = offsetY + crossH * pixelSize + pixelSize / 2;
uint8_t crossLen = pixelSize / 2 + 1; // 十字线长度
// 绘制红色十字
tft.drawLine(crossX - crossLen, crossY, crossX + crossLen, crossY, ST7735_RED);
tft.drawLine(crossX, crossY - crossLen, crossX, crossY + crossLen, ST7735_RED);
}【测试】
下载驱动后,我们就可以实时的测量了,首先来测测我们的Teensy的核心工作温度:

可以看到我们的开发板的核心温度最高值为35.3度。
再看看我们的笔记本最高温度为35度

再测量一下本人头像:

可以后到我们的头像成像还是挺清晰的,表面温度最高31度左右。
再看看香烟的温度是多少:498度!

因此,烟草在高温下会产生非常多的有害物质,“吸烟有害健康”!
【总结】
Teensy开发板+MLX90640可以通过Arduino生态,可以快速的制作一个热成像仪,虽然制作成功有点高加起来成本上千了,但是他的FSP速度非常高,也非常小巧。
我要赚赏金
