funpack12--wio terminal红外热成像仪
使用的是funpack第12期的机器wio terminal,搭配mlx90640,32X24像素,55°角
标签
嵌入式系统
lxb
更新2022-04-01
哈尔滨工业大学
1001

        Wio Terminal微型控制器可以控制大量数据采集和数据处理、预判,是一个足够强大、且足够低功耗的主控。另外,这个主控同时配备一个LCD 屏幕、支持microSD存储音频文件,并且具备一些用于指引操作的按钮。

      该WIO终端是基于SAMD51的微控制器与无线连接搭载瑞昱RTL8720DN这是与Arduino的和MicroPython兼容。目前,只有 Arduino 支持无线连接。它运行在120MHz(提升至 200MHz)、4MB外部闪存和192KB RAM。它支持蓝牙和 Wi-Fi,为物联网项目提供骨干。Wio 终端本身配备2.4 英寸 LCD 屏幕、板载 IMU(LIS3DHTR)、麦克风、蜂鸣器、microSD 卡插槽、光传感器和红外发射器(IR 940nm)。最重要的是,它还有两个用于Grove 生态系统的多功能 Grove 端口 和 40 个 Raspberry pi 兼容引脚 GPIO,用于更多附加组件。

     具体介绍链接:https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/

硬件概览:

    FqzgQcaYD2ucsB4q6atKU2wyoXfV

引脚图:

FsTST9otEkGSr61xIUPPC_cZ8Idw

底部Grove接口图:

FkkH_k-ZgXzUjd8eVPVdMZjwPyze

      这里再介绍一下mlx90640:

特性和优点:①小尺寸,低功耗, 32*24 像素红外阵列;②方便集成;③标准的 TO39 封装;④出厂校准;⑤噪声等效温差( NETD)0.1K RMS@1Hz 速率;⑥I2C 兼容数字接口;⑦可编程刷新速率 0.5Hz~60Hz;⑧供电电压 3.3V;⑨电流消耗:≤23mA;⑩两种视场角可选: 55° *30° 和 110° *75°;⒒工作温度: -45~85℃;⒓测温范围: -40~300℃
       MLX90640 是工业标准并经过完全校准的 32*24像素热红外阵列传感器,采用 4 脚 TO39 封装以及I2C 兼容的数字接口。
    MLX90640 包含 768 个热红外像素点。内嵌自身环境温度传感器和 VDD 电压检测 ADC。通过 I2C 接口,可以访问存储于内部 RAM 中的红外阵列、 环境温度以及 VDD 实时数据。

       MLX90640 有两个型号, A 型和 B 型,型号全称为:MLX90640ESF_BAA/BAB。
他们区别在于:①视场角不同: A 型为 110*75° , B 型为 55*35° ,通俗一点讲就是 A 型是广角,所以镜头矮一些,视野更宽,但对远处物体的捕捉能力更低, B 型更适于拍摄稍远的物体。
                          ②精度不同: A 型的噪声比 B 型大,所以 B 型的绝对温度和灵敏度都好一些。

       该模块将MCU与I2C接口连接起来。但是,它需要一个具有超过20000字节RAM的MCU来驱动相机。 

       调用学习方面Seeed有官方的库和例子,链接:https://github.com/Seeed-Studio/Seeed_Arduino_MLX9064x

        红外温度传感器阵列(mlx90640)的分辨率仅为32x24,在某些情况下已经足够好了。因此,在代码中使用线性插值来扩展到70 x 70(4900像素),以获得更好的指示。

         为了提高红外热像仪的性能和帧速率,可以将Wio终端CPU速度提高到200MHz。选择Tools -> CPU Speed -> 200MHz(Overclock),如下图所示:

Fvfuqs4ruQsvRm2Gs11LU69xFwqH

        Bodmer的屏幕截图示例,他创建了TFT_eSPI库- https://github.com/Bodmer/TFT_eSPI/tree/master/examples/Generic/TFT_Screen_Capture。此示例不会将图像写入 SD 卡,而是将串行流发送到主机 PC,并运行处理脚本来呈现和保存图像。我挪用了screenSaver功能和processing_sketch用于我的程序。我在处理草图时遇到了一些问题,因为最新版本将数据类型布尔更改为bool以修复弃用问题,并且该问题不适用于我正在使用的Processing(3.5.4)版本。我将数据类型恢复为布尔值,然后运行。

        源码如下:

#include <Wire.h>
#include "MLX90640_API.h"
#include "MLX9064X_I2C_Driver.h"
#include <TFT_eSPI.h>                // Include the graphics library (this includes the sprite functions)  


const byte MLX90640_address = 0x33; //Default 7-bit unshifted address of the MLX90640
#define TA_SHIFT 8 //Default shift for MLX90640 in open air
#define debug  Serial
uint16_t eeMLX90640[832];
float MLX90640To[768];
uint16_t MLX90640Frame[834];
paramsMLX90640 MLX90640;
int errorno = 0;


TFT_eSPI    tft = TFT_eSPI(); 
//TFT_eSprite Display = TFT_eSprite(&tft);  // Create Sprite object "img" with pointer to "tft" object
// the pointer is used by pushSprite() to push it onto the TFT


unsigned long CurTime;

uint16_t TheColor;
// start with some initial colors
uint16_t MinTemp = 25;
uint16_t MaxTemp = 38;


// variables for interpolated colors
byte red, green, blue;

// variables for row/column interpolation
byte i, j, k, row, col, incr;
float intPoint, val, a, b, c, d, ii;
byte aLow, aHigh;


// size of a display "pixel"
byte BoxWidth = 3;
byte BoxHeight = 3;


int x, y;
char buf[20];

// variable to toggle the display grid
int ShowGrid = -1;

// array for the interpolated array
float HDTemp[6400];


void setup() {
    Wire.begin();
    Wire.setClock(2000000); //Increase I2C clock speed to 2M
    debug.begin(115200); //Fast debug as possible
//    while (!debug);  // wait for terminal
    
    // start the display and set the background to black


    if (isConnected() == false) {
        debug.println("MLX90640 not detected at default I2C address. Please check wiring. Freezing.");
        while (1);
    }
    //Get device parameters - We only have to do this once
    int status;
    status = MLX90640_DumpEE(MLX90640_address, eeMLX90640);
    errorno = status;//MLX90640_CheckEEPROMValid(eeMLX90640);//eeMLX90640[10] & 0x0040;//
    
    if (status != 0) {
        debug.println("Failed to load system parameters");
       while(1);
    }


    status = MLX90640_ExtractParameters(eeMLX90640, &MLX90640);
    //errorno = status;
    if (status != 0) {
        debug.println("Parameter extraction failed");
        while(1);
    }


    //Once params are extracted, we can release eeMLX90640 array
    
   MLX90640_SetRefreshRate(MLX90640_address, 0x05); //Set rate to 16Hz
//    MLX90640_SetRefreshRate(MLX90640_address, 0x02); //Set rate to 2Hz


    tft.begin();
    tft.setRotation(3);
    tft.fillScreen(TFT_BLACK);
//    Display.createSprite(TFT_HEIGHT, TFT_WIDTH);
//    Display.fillSprite(TFT_BLACK); 


    // get the cutoff points for the color interpolation routines
    // note this function called when the temp scale is changed
    Getabcd();


    // draw a legend with the scale that matches the sensors max and min
    DrawLegend();  
    tft.fillRect(15, 15, 210, 210, TFT_BLUE);  
}
void loop() {


    // draw a large white border for the temperature area
//    tft.fillRect(10, 10, 210, 210, TFT_BLUE);
    for (byte x = 0 ; x < 2 ; x++) {
        int status = MLX90640_GetFrameData(MLX90640_address, MLX90640Frame);


        float vdd = MLX90640_GetVdd(MLX90640Frame, &MLX90640);
        float Ta = MLX90640_GetTa(MLX90640Frame, &MLX90640);


        float tr = Ta - TA_SHIFT; //Reflected temperature based on the sensor ambient temperature
        float emissivity = 0.95;


        MLX90640_CalculateTo(MLX90640Frame, &MLX90640, emissivity, tr, MLX90640To);
    }


    interpolate_image(MLX90640To,24,32,HDTemp,80,80);


    //display the 80 x 80 array
    DisplayGradient();
    
    //Crosshair in the middle of the screen
    tft.drawCircle(115, 115, 5, TFT_WHITE);
    tft.drawFastVLine(115, 105, 20, TFT_WHITE);
    tft.drawFastHLine(105, 115, 20, TFT_WHITE);
    //Displaying the temp at the middle of the Screen
     
    //Push the Sprite to the screen
//    Display.pushSprite(0, 0);


    tft.setRotation(3);
    tft.setTextColor(TFT_WHITE);
    tft.drawFloat(HDTemp[35 * 80 + 35], 2, 90, 20);        
    delay(500);
}
//Returns true if the MLX90640 is detected on the I2C bus
boolean isConnected() {
    Wire.beginTransmission((uint8_t)MLX90640_address);
    if (Wire.endTransmission() != 0) {
        return (false);    //Sensor did not ACK
    }
    return (true);
}
// function to display the results
void DisplayGradient() {

  tft.setRotation(4);

  // rip through 70 rows
  for (row = 0; row < 70; row ++) {

    // fast way to draw a non-flicker grid--just make every 10 MLX90640To 2x2 as opposed to 3x3
    // drawing lines after the grid will just flicker too much
    if (ShowGrid < 0) {
      BoxWidth = 3;
    }
    else {
      if ((row % 10 == 9) ) {
        BoxWidth = 2;
      }
      else {
        BoxWidth = 3;
      }
    }
    // then rip through each 70 cols
    for (col = 0; col < 70; col++) {

      // fast way to draw a non-flicker grid--just make every 10 MLX90640To 2x2 as opposed to 3x3
      if (ShowGrid < 0) {
        BoxHeight = 3;
      }
      else {
        if ( (col % 10 == 9)) {
          BoxHeight = 2;
        }
        else {
          BoxHeight = 3;
        }
      }
      // finally we can draw each the 70 x 70 points, note the call to get interpolated color
      tft.fillRect((row * 3) + 15, (col * 3) + 15, BoxWidth, BoxHeight, GetColor(HDTemp[row * 80 + col]));


    }
  }

}
// my fast yet effective color interpolation routine
uint16_t GetColor(float val) {

  /*
    pass in value and figure out R G B
    several published ways to do this I basically graphed R G B and developed simple linear equations
    again a 5-6-5 color display will not need accurate temp to R G B color calculation

    equations based on
    http://web-tech.ga-usa.com/2012/05/creating-a-custom-hot-to-cold-temperature-color-gradient-for-use-with-rrdtool/index.html

  */

  red = constrain(255.0 / (c - b) * val - ((b * 255.0) / (c - b)), 0, 255);

  if ((val > MinTemp) & (val < a)) {
    green = constrain(255.0 / (a - MinTemp) * val - (255.0 * MinTemp) / (a - MinTemp), 0, 255);
  }
  else if ((val >= a) & (val <= c)) {
    green = 255;
  }
  else if (val > c) {
    green = constrain(255.0 / (c - d) * val - (d * 255.0) / (c - d), 0, 255);
  }
  else if ((val > d) | (val < a)) {
    green = 0;
  }

  if (val <= b) {
    blue = constrain(255.0 / (a - b) * val - (255.0 * b) / (a - b), 0, 255);
  }
  else if ((val > b) & (val <= d)) {
    blue = 0;
  }
  else if (val > d) {
    blue = constrain(240.0 / (MaxTemp - d) * val - (d * 240.0) / (MaxTemp - d), 0, 240);
  }

  // use the displays color mapping function to get 5-6-5 color palet (R=5 bits, G=6 bits, B-5 bits)
  return tft.color565(red, green, blue);



}

// function to get the cutoff points in the temp vs RGB graph
void Getabcd() {

  a = MinTemp + (MaxTemp - MinTemp) * 0.2121;
  b = MinTemp + (MaxTemp - MinTemp) * 0.3182;
  c = MinTemp + (MaxTemp - MinTemp) * 0.4242;
  d = MinTemp + (MaxTemp - MinTemp) * 0.8182;

}
float get_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y)
{
    if (x < 0)
    {
        x = 0;
    }
    if (y < 0)
    {
        y = 0;
    }
    if (x >= cols)
    {
        x = cols - 1;
    }
    if (y >= rows)
    {
        y = rows - 1;
    }
    return p[y * cols + x];
}


void set_point(float *p, uint8_t rows, uint8_t cols, int8_t x, int8_t y, float f)
{
    if ((x < 0) || (x >= cols))
    {
        return;
    }
    if ((y < 0) || (y >= rows))
    {
        return;
    }
    p[y * cols + x] = f;
}


// src is a grid src_rows * src_cols
// dest is a pre-allocated grid, dest_rows*dest_cols
void interpolate_image(float *src, uint8_t src_rows, uint8_t src_cols,
                       float *dest, uint8_t dest_rows, uint8_t dest_cols)
{
    float mu_x = (src_cols - 1.0) / (dest_cols - 1.0);
    float mu_y = (src_rows - 1.0) / (dest_rows - 1.0);


    float adj_2d[16]; // matrix for storing adjacents


    for (uint8_t y_idx = 0; y_idx < dest_rows; y_idx++)
    {
        for (uint8_t x_idx = 0; x_idx < dest_cols; x_idx++)
        {
            float x = x_idx * mu_x;
            float y = y_idx * mu_y;
            get_adjacents_2d(src, adj_2d, src_rows, src_cols, x, y);


            float frac_x = x - (int)x; // we only need the ~delta~ between the points
            float frac_y = y - (int)y; // we only need the ~delta~ between the points
            float out = bicubicInterpolate(adj_2d, frac_x, frac_y);
            set_point(dest, dest_rows, dest_cols, x_idx, y_idx, out);
        }
    }
}


// p is a list of 4 points, 2 to the left, 2 to the right
float cubicInterpolate(float p[], float x)
{
    float r = p[1] + (0.5 * x * (p[2] - p[0] + x * (2.0 * p[0] - 5.0 * p[1] + 4.0 * p[2] - p[3] + x * (3.0 * (p[1] - p[2]) + p[3] - p[0]))));
    return r;
}


// p is a 16-point 4x4 array of the 2 rows & columns left/right/above/below
float bicubicInterpolate(float p[], float x, float y)
{
    float arr[4] = {0, 0, 0, 0};
    arr[0] = cubicInterpolate(p + 0, x);
    arr[1] = cubicInterpolate(p + 4, x);
    arr[2] = cubicInterpolate(p + 8, x);
    arr[3] = cubicInterpolate(p + 12, x);
    return cubicInterpolate(arr, y);
}


// src is rows*cols and dest is a 4-point array passed in already allocated!
void get_adjacents_1d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y)
{
    // pick two items to the left
    dest[0] = get_point(src, rows, cols, x - 1, y);
    dest[1] = get_point(src, rows, cols, x, y);
    // pick two items to the right
    dest[2] = get_point(src, rows, cols, x + 1, y);
    dest[3] = get_point(src, rows, cols, x + 2, y);
}


// src is rows*cols and dest is a 16-point array passed in already allocated!
void get_adjacents_2d(float *src, float *dest, uint8_t rows, uint8_t cols, int8_t x, int8_t y)
{
    float arr[4];
    for (int8_t delta_y = -1; delta_y < 3; delta_y++)
    {                                          // -1, 0, 1, 2
        float *row = dest + 4 * (delta_y + 1); // index into each chunk of 4
        for (int8_t delta_x = -1; delta_x < 3; delta_x++)
        { // -1, 0, 1, 2
            row[delta_x + 1] = get_point(src, rows, cols, x + delta_x, y + delta_y);
        }
    }
}


// function to draw a legend
void DrawLegend() {

  //color legend with max and min text
  j = 0;

  float inc = (MaxTemp - MinTemp ) / 160.0;

  for (ii = MinTemp; ii < MaxTemp; ii += inc) {
    tft.drawFastHLine(260, 200 - j++, 30, GetColor(ii));
  }

  tft.setTextSize(2);
  tft.setCursor(245, 20);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  sprintf(buf, "%2d/%2d", MaxTemp, (int) (MaxTemp * 1.8) + 32);
  tft.print(buf);

  tft.setTextSize(2);
  tft.setCursor(245, 210);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  sprintf(buf, "%2d/%2d", MinTemp, (int) (MinTemp * 1.8) + 32);
  tft.print(buf);

}

困难:

       就是对mlx90640的学习理解吧,毕竟资料还是相对较少,且上手不易(好在有官方例程)

心得体会:

       本来就参加过funpack12期,对wio的使用算是入门了吧,本身arduino编程也是非常友好的。缺点就是个人觉得不够流畅吧,不过也没找到很好的办法。后续想再加入一个截图的功能,看看有没有什么办法实现。

        对热成像原理及其仪器感兴趣的可以去看一看《无线电》杂志的22年的第2期。

参考链接:

https://community.element14.com/members-area/personalblogs/b/ralph-yamamoto-s-blog/posts/mlx90640-thermal-camera-on-wio-terminal

https://community.element14.com/members-area/personalblogs/b/ralph-yamamoto-s-blog/posts/wio-terminal-thermal-camera

Build a IR Thermal Imaging Camera using Wio Terminal

 

 

附件下载
sketch_mar24b.ino
MLX90640-Datasheet-Melexis.pdf
Seeed_Arduino_MLX9064x-master.zip
团队介绍
电子信息学院大三
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号