Wärmebildkamera

Mit einem Arduino bzw. ESP32 kann man sogar eine Wärmebildkamera basteln. Bei der Variante 1 wird die Temperatur mit dem Sensor MLX90614 Pixel für Pixel bestimmt, indem man den Sensor mittels zweier Servomotoren in viele verschiedene Richtungen orientiert. Bei der Variante 2 mit dem Sensor MLX90640 ist dies nicht mehr nötig, da der Sensor bereits über 32×24 Pixel verfügt. Leider brachte ich den MLX90640 mit einem Arduino nicht zum Laufen. Daher musste ich auf einen ESP32 ausweichen.

Variante 1: Sensor MLX90614

Dieser Sensor verfügt lediglich über 1 Pixel. Daher muss zur Aufnahme eines kompletten Wärmebildes der Sensor Schritt für Schritt in viele Richtungen ausgerichtet werden. Dies bedingt natürlich eine sehr große Messzeit. Bei 1 Sekunde pro Messung und einem Bild mit 40×40 Pixel dauert der Messvorgang immerhin bereits fast 27 Minuten.

Die Temperaturwerte werden am Ende mit dem Grafikprogramm gnuplot eingefärbt, damit man ein anschauliches Wärmebild erhält.

 


Arduino-Code:

// =======================================================
// ====== Programm zur Erstellung eines Wärmebilds =======
// =======================================================


#include <Servo.h>           // Inkludiert die Servodateien 
#include <i2cmaster.h>       // Inkludiert die i2c-Bus-Dateien
 
Servo servo_horiz;           // Definiert den horizontalen servo
Servo servo_verti;           // Definiert den vertikalen servo
int taste;                   // Tastaturpin für Start des Scans
int horiz_start = 90;        // horizontaler Startwinkel
int verti_start = 20;        // vertikaler Startwinkel
int schritte = 40;           // horizontale bzw. vertikale Schritte


// ************************************
// ************** SETUP ***************
// ************************************

void setup() 
   { 
    Serial.begin(9600);
     
    servo_horiz.attach(9);    // Set horizontalen servo to digital pin 9
    servo_verti.attach(10);   // Set vertikalen servo to digital pin 10
    
    pinMode(13, OUTPUT);
    
    
    i2c_init();                              //Initialisiert den i2c-Bus
    PORTC = (1 << PORTC4) | (1 << PORTC5);   //enable pullups
    
   } 


// ********************************************
// ************** HAUPTSCHLEIFE ***************
// ********************************************
 
void loop() 
   { 
  
    taste = analogRead(A0);      // Abfrage, ob die Taste gedrückt wird und der Scan gestartet werden kann
    
    servo_horiz.write(horiz_start);
    servo_verti.write(verti_start);
    
    if (taste > 500)
       {        
        // ********** Messung gestartet ************ 
          
   
        digitalWrite(13, HIGH);   // set the LED on
        delay(1000);              // wait for a second
        digitalWrite(13, LOW);    // set the LED off
    
         
        for (int i = horiz_start; i >= horiz_start - schritte; i--)
          {
           servo_verti.write(verti_start);
                     
           delay(150);
                
           for (int j = verti_start; j <= verti_start + schritte; j++)
              { 
               servo_horiz.write(i);
               servo_verti.write(j);
           
               delay(250);
               
               
               int dev = 0x5A<<1;
               int data_low = 0;
               int data_high = 0;
               int pec = 0;
    
               i2c_start_wait(dev+I2C_WRITE);
               i2c_write(0x07);
    
               // Temperatur vom MLX90614 lesen
        
               i2c_rep_start(dev+I2C_READ);
               data_low = i2c_readAck();      // Read 1 byte and then send back
               data_high = i2c_readAck();     // Read 1 byte and then send back
               pec = i2c_readNak();
               i2c_stop();
    
               // This converts high and low bytes together and processes temperature, MSB is a error bit and is ignored for temps
        
               double tempFactor = 0.02;  // 0.02 degrees per LSB (measurement resolution of the MLX90614)
               double tempData = 0x0000;  // zero out the data
               int frac;                  // data past the decimal point
    
               // This masks off the error bit of the high byte, then moves it left 8 bits and adds the low byte.
        
               tempData = (double)(((data_high & 0x007F) << 8) + data_low);
               tempData = (tempData * tempFactor)-0.01;
    
               float celcius = tempData - 273.15;
    
               Serial.print(horiz_start - i);
               Serial.print(" ");
               Serial.print(j - verti_start);
               Serial.print(" ");
               Serial.println(celcius);
                  
              }           
          }
     
        // ********** Messung beendet ************  
          
        digitalWrite(13, HIGH);   // set the LED on
        delay(1000);              // wait for a second
        digitalWrite(13, LOW);    // set the LED off
        
        servo_horiz.write(horiz_start);
        servo_verti.write(verti_start);        
       }
         
   }

Variante 2: Sensor MLX90640

Dieser Sensor verfügt wie schon erwähnt bereits über 32×24 Pixel. Deshalb können ca. 2 Bilder pro Sekunde erstellt/angezeigt werden. Das Display verfügt über 320×240 Pixel. Angezeigt werden Minimaltemperatur, Maximaltemperatur und Temperatur des mit einem Kreuz versehenen Pixels in der Bildmitte.


Arduino-Code:

#include <Wire.h>
#include "MLX90640_API.h"
#include "MLX90640_I2C_Driver.h"
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

// For the ESP-WROVER_KIT, these are the default.
#define TFT_CS   15 
#define TFT_DC    2
#define TFT_MOSI 13
#define TFT_CLK  14
#define TFT_RST  26
#define TFT_MISO 12
#define TFT_LED  27

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

const byte MLX90640_address = 0x33; //Default 7-bit unshifted address of the MLX90640

#define TA_SHIFT 8 //Default shift for MLX90640 in open air

static float mlx90640To[768];
paramsMLX90640 mlx90640;

int xPos, yPos;                                // Abtastposition
int R_colour, G_colour, B_colour;              // RGB-Farbwert
int i, j;                                      // Zählvariable
float T_max, T_min;                            // maximale bzw. minimale gemessene Temperatur
float T_center;                                // Temperatur in der Bildschirmmitte




// ***************************************
// **************** SETUP ****************
// ***************************************

void setup()
   {
    Serial.begin(115200);
    
    Wire.begin();
    Wire.setClock(400000); //Increase I2C clock speed to 400kHz

    while (!Serial); //Wait for user to open terminal
    
    Serial.println("MLX90640 IR Array Example");

    if (isConnected() == false)
       {
        Serial.println("MLX90640 not detected at default I2C address. Please check wiring. Freezing.");
        while (1);
       }
       
    Serial.println("MLX90640 online!");

    //Get device parameters - We only have to do this once
    int status;
    uint16_t eeMLX90640[832];
    
    status = MLX90640_DumpEE(MLX90640_address, eeMLX90640);
  
    if (status != 0)
       Serial.println("Failed to load system parameters");

    status = MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
  
    if (status != 0)
       {
        Serial.println("Parameter extraction failed");
        Serial.print(" status = ");
        Serial.println(status);
       }

    //Once params are extracted, we can release eeMLX90640 array

    MLX90640_I2CWrite(0x33, 0x800D, 6401);    // writes the value 1901 (HEX) = 6401 (DEC) in the register at position 0x800D to enable reading out the temperatures!!!
    // ===============================================================================================================================================================

    //MLX90640_SetRefreshRate(MLX90640_address, 0x00); //Set rate to 0.25Hz effective - Works
    //MLX90640_SetRefreshRate(MLX90640_address, 0x01); //Set rate to 0.5Hz effective - Works
    //MLX90640_SetRefreshRate(MLX90640_address, 0x02); //Set rate to 1Hz effective - Works
    //MLX90640_SetRefreshRate(MLX90640_address, 0x03); //Set rate to 2Hz effective - Works
    MLX90640_SetRefreshRate(MLX90640_address, 0x04); //Set rate to 4Hz effective - Works
    //MLX90640_SetRefreshRate(MLX90640_address, 0x05); //Set rate to 8Hz effective - Works at 800kHz
    //MLX90640_SetRefreshRate(MLX90640_address, 0x06); //Set rate to 16Hz effective - Works at 800kHz
    //MLX90640_SetRefreshRate(MLX90640_address, 0x07); //Set rate to 32Hz effective - fails

       
    pinMode(TFT_LED, OUTPUT);
    digitalWrite(TFT_LED, HIGH);

    tft.begin();

    tft.setRotation(1);

    tft.fillScreen(ILI9341_BLACK);
    tft.fillRect(0, 0, 319, 13, tft.color565(255, 0, 10));
    tft.setCursor(100, 3);
    tft.setTextSize(1);
    tft.setTextColor(ILI9341_YELLOW, tft.color565(255, 0, 10));
    tft.print("Thermographie - stoppi");    

    tft.drawLine(250, 210 - 0, 258, 210 - 0, tft.color565(255, 255, 255));
    tft.drawLine(250, 210 - 30, 258, 210 - 30, tft.color565(255, 255, 255));
    tft.drawLine(250, 210 - 60, 258, 210 - 60, tft.color565(255, 255, 255));
    tft.drawLine(250, 210 - 90, 258, 210 - 90, tft.color565(255, 255, 255));
    tft.drawLine(250, 210 - 120, 258, 210 - 120, tft.color565(255, 255, 255));
    tft.drawLine(250, 210 - 150, 258, 210 - 150, tft.color565(255, 255, 255));
    tft.drawLine(250, 210 - 180, 258, 210 - 180, tft.color565(255, 255, 255));

    tft.setCursor(80, 220);
    tft.setTextColor(ILI9341_WHITE, tft.color565(0, 0, 0));
    tft.print("T+ = ");    


    // drawing the colour-scale
    // ========================
 
    for (i = 0; i < 181; i++)
       {
        getColour(i);
        tft.drawLine(240, 210 - i, 250, 210 - i, tft.color565(R_colour, G_colour, B_colour));
       } 

   } 



// **********************************
// ************** LOOP **************
// **********************************

void loop()
   {
    for (byte x = 0 ; x < 2 ; x++) //Read both subpages
       {
        uint16_t mlx90640Frame[834];
        int status = MLX90640_GetFrameData(MLX90640_address, mlx90640Frame);
    
        if (status < 0)
           {
            Serial.print("GetFrame Error: ");
            Serial.println(status);
           }

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

       
    // determine T_min and T_max and eliminate error pixels
    // ====================================================

    mlx90640To[1*32 + 21] = 0.5 * (mlx90640To[1*32 + 20] + mlx90640To[1*32 + 22]);    // eliminate the error-pixels
    mlx90640To[4*32 + 30] = 0.5 * (mlx90640To[4*32 + 29] + mlx90640To[4*32 + 31]);    // eliminate the error-pixels
    
    T_min = mlx90640To[0];
    T_max = mlx90640To[0];

    for (i = 1; i < 768; i++)
       {
        if((mlx90640To[i] > -41) && (mlx90640To[i] < 301))
           {
            if(mlx90640To[i] < T_min)
               {
                T_min = mlx90640To[i];
               }

            if(mlx90640To[i] > T_max)
               {
                T_max = mlx90640To[i];
               }
           }
        else if(i > 0)   // temperature out of range
           {
            mlx90640To[i] = mlx90640To[i-1];
           }
        else
           {
            mlx90640To[i] = mlx90640To[i+1];
           }
       }


    // determine T_center
    // ==================

    T_center = mlx90640To[11* 32 + 15];    

    // drawing the picture
    // ===================

    for (i = 0 ; i < 24 ; i++)
       {
        for (j = 0; j < 32; j++)
           {
            mlx90640To[i*32 + j] = 180.0 * (mlx90640To[i*32 + j] - T_min) / (T_max - T_min);
                       
            getColour(mlx90640To[i*32 + j]);
            
            tft.fillRect(217 - j * 7, 35 + i * 7, 7, 7, tft.color565(R_colour, G_colour, B_colour));
           }
       }
    
    tft.drawLine(217 - 15*7 + 3.5 - 5, 11*7 + 35 + 3.5, 217 - 15*7 + 3.5 + 5, 11*7 + 35 + 3.5, tft.color565(255, 255, 255));
    tft.drawLine(217 - 15*7 + 3.5, 11*7 + 35 + 3.5 - 5, 217 - 15*7 + 3.5, 11*7 + 35 + 3.5 + 5,  tft.color565(255, 255, 255));
 
    tft.fillRect(260, 25, 37, 10, tft.color565(0, 0, 0));
    tft.fillRect(260, 205, 37, 10, tft.color565(0, 0, 0));    
    tft.fillRect(115, 220, 37, 10, tft.color565(0, 0, 0));    

    tft.setTextColor(ILI9341_WHITE, tft.color565(0, 0, 0));
    tft.setCursor(265, 25);
    tft.print(T_max, 1);
    tft.setCursor(265, 205);
    tft.print(T_min, 1);
    tft.setCursor(120, 220);
    tft.print(T_center, 1);

    tft.setCursor(300, 25);
    tft.print("C");
    tft.setCursor(300, 205);
    tft.print("C");
    tft.setCursor(155, 220);
    tft.print("C");
    
    //delay(20);
   }
   


// ===============================
// ===== determine the colour ====
// ===============================

void getColour(int j)
   {
    if (j >= 0 && j < 30)
       {
        R_colour = 0;
        G_colour = 0;
        B_colour = 20 + (120.0/30.0) * j;
       }
    
    if (j >= 30 && j < 60)
       {
        R_colour = (120.0 / 30) * (j - 30.0);
        G_colour = 0;
        B_colour = 140 - (60.0/30.0) * (j - 30.0);
       }

    if (j >= 60 && j < 90)
       {
        R_colour = 120 + (135.0/30.0) * (j - 60.0);
        G_colour = 0;
        B_colour = 80 - (70.0/30.0) * (j - 60.0);
       }

    if (j >= 90 && j < 120)
       {
        R_colour = 255;
        G_colour = 0 + (60.0/30.0) * (j - 90.0);
        B_colour = 10 - (10.0/30.0) * (j - 90.0);
       }

    if (j >= 120 && j < 150)
       {
        R_colour = 255;
        G_colour = 60 + (175.0/30.0) * (j - 120.0);
        B_colour = 0;
       }

    if (j >= 150 && j <= 180)
       {
        R_colour = 255;
        G_colour = 235 + (20.0/30.0) * (j - 150.0);
        B_colour = 0 + 255.0/30.0 * (j - 150.0);
       }

   }
   
   
//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);
   }