element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Personal Blogs
  • Community Hub
  • More
Personal Blogs
Ralph Yamamoto's Blog MLX90640 Thermal Camera on Wio Terminal
  • Blog
  • Documents
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: ralphjy
  • Date Created: 10 Nov 2021 3:41 PM Date Created
  • Views 4816 views
  • Likes 1 like
  • Comments 4 comments
  • melexis
  • tft_espi
  • thermal camera
  • mlx90640
  • wio terminal
Related
Recommended

MLX90640 Thermal Camera on Wio Terminal

ralphjy
ralphjy
10 Nov 2021

I'm want to use the MLX90640 Thermal Camera module from Adafruit with my Wio Terminal to make a diagnostic tool that I can use for thermal measurements.  I tried out a module with a 55 degree FOV lens with Jupyter Notebooks on my PC Using the MLX90640 Thermal Camera with Jupyter Notebooks .

 

Now I'm going to try out a module with a wide angle 110 degree FOV with the Wio Terminal.  I'm using a STEMMA-QT to Grove cable to connect the modules, so I can easily swap modules to change the FOV.  I also need to 3D print a holder so that I can attach the module to the back of the Wio Terminal (I'm going to create a dummy header to plug into the RPi connector).

 

First, I need to try out some software (I'm using the Arduino IDE).  It seems that each board manufacturer has their own variant of the sensor library and different examples are included.  I had used the Adafruit MLX90640 library with the example that I used with Jupyter Notebooks.  I decided to try the SparkFun library next.  That library has an example that sends the sensor data as a serial stream over USB to a Processing app to visualize the data.

 

Here is base setup (I still need to figure out how to mount the camera module, but it's easier to take selfies with the camera loose).

imageimage

 

And a short video of the data visualization.  I wearing my glasses in this one.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

 

 

Of course, the whole purpose of using the Wio Terminal is to show the data on its built-in LCD display. So, I switched to using the Seeed Studio library which supports both the MLX90640 and MLX90641.  The MLX90640 has a 32x24 pixel array and the MLX90641 has a 16x12 pixel array.  The MLX90640 is spec'ed for a commercial temperature range and the MLX90641 for industrial temperature range.

image

 

The Seeed library has an example of using a Grove MLX90641 module with the Wio Terminal.  I needed to adjust array sizes to handle the larger sensor of the MLX90640 and also corrected a mistake in the Celsius to Fahrenheit conversion.  The one thing that I could not get working was using sprites with the display.  I assume the code did work with an MLX90641.  It could be that I'm encountering a memory issue with the larger arrays.  Anyway, I'm doing it without using sprites - reduced the refresh rate and inserted a little delay in the loop.  I'll try to figure this out later.

 

Wio_Terminal_MLX90640_noSprite.ino

#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);

}

 

And another short video.  The temperature displayed at the top is the temperature of the pixels in the crosshairs in the center of the image.  In this video I don't have my glasses on.

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

 

 

 

Next, I need to add the battery pack and mount the camera to the side opposite the LCD and I will have a standalone thermal camera image.  I'll post when that's done.

Here is the link to the finished project: Wio Terminal Thermal Camera

  • Sign in to reply
Parents
  • tony1tf
    tony1tf over 2 years ago

    Hi Ralph

    Great project - thank you - I just got a Wio-terminal and had a MLX90640 Pimoroni breakout board. So I was looking into editing the example on the Seeed Studio page, when I came across this blog where you had already done the work. I've just modified your code to auto-scale on the max temperature, and post it here.

    Tony

    // Code from Wio_Terminal_MLX90640_noSprite.ino by Ralph Yamamoto on Element14 personal blogs
    // edit for Max temp tracking - Tony 26 Sept 2022
    #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;
    float centretemp;
    
    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 = 15;
    uint16_t MaxTemp = 20;
    
    
    // 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);
        centretemp=HDTemp[35 * 80 + 35], 2, 90, 20;
        tft.drawFloat(centretemp,2,90,20);
     //   tft.drawFloat(HDTemp[35 * 80 + 35], 2, 90, 20); 
     // Code added by Tony 26 Sep 2022 for auto scaling of max temp
        if (centretemp > MaxTemp) {
        MaxTemp=centretemp; 
        Getabcd();
        DrawLegend(); 
        }  
        delay(400);
    }
    //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);
    
    }

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • tony1tf
    tony1tf over 2 years ago

    Hi Ralph

    Great project - thank you - I just got a Wio-terminal and had a MLX90640 Pimoroni breakout board. So I was looking into editing the example on the Seeed Studio page, when I came across this blog where you had already done the work. I've just modified your code to auto-scale on the max temperature, and post it here.

    Tony

    // Code from Wio_Terminal_MLX90640_noSprite.ino by Ralph Yamamoto on Element14 personal blogs
    // edit for Max temp tracking - Tony 26 Sept 2022
    #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;
    float centretemp;
    
    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 = 15;
    uint16_t MaxTemp = 20;
    
    
    // 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);
        centretemp=HDTemp[35 * 80 + 35], 2, 90, 20;
        tft.drawFloat(centretemp,2,90,20);
     //   tft.drawFloat(HDTemp[35 * 80 + 35], 2, 90, 20); 
     // Code added by Tony 26 Sep 2022 for auto scaling of max temp
        if (centretemp > MaxTemp) {
        MaxTemp=centretemp; 
        Getabcd();
        DrawLegend(); 
        }  
        delay(400);
    }
    //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);
    
    }

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
No Data
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2025 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube