这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【TEENSY4.0】热成像仪之猜猜香烟有多少度

共1条 1/1 1 跳转至

【TEENSY4.0】热成像仪之猜猜香烟有多少度

高工
2026-02-15 20:00:28     打赏

【目标实现】

制作一个热成像仪,测测点燃的香烟有多少度!

【硬件】

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就可以下载他的驱动库到本地。

image.png

下载好驱动后,我们导航到他的官网,他的驱动也是非常之简单。根据他的示例,我移植好驱动如下:

#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的核心工作温度:

image.png

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

再看看我们的笔记本最高温度为35度

image.png

再测量一下本人头像:

image.png

可以后到我们的头像成像还是挺清晰的,表面温度最高31度左右。

再看看香烟的温度是多少:498度!

9fb7d51bd64997145a76aa8d5f6ba416.jpg

因此,烟草在高温下会产生非常多的有害物质,“吸烟有害健康”!

【总结】

Teensy开发板+MLX90640可以通过Arduino生态,可以快速的制作一个热成像仪,虽然制作成功有点高加起来成本上千了,但是他的FSP速度非常高,也非常小巧。




关键词: TEENSY4.0     热成像     MLX90640    

共1条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]