Thermal Alert

Thermal Alert

Introduction

The purpose of this project is to create  a device which measures the heat of the objects in front of it and if the temperatures are above the threshold, the chip Atmega328 will trigger an alarm. The alarm is a blink light and intermitent sound. The threshold is prefixed with a potentiometer. This object also has a ambient temperature sensor and a Bluetooth module to transmit the information to an Android Smartphone.
Figure 1: Front part of the temperature alarm.

Components

1 x Plastic Box
1 x MLX90621
1 x MCP9700
1 x Light of a bike
1 x speaker
1 x 10K Potentiometer
1 x Atmega328 3.3V 8MHz
1 x 8Mhz Cristal Oscillator
1x LED GREEN 5mm
2 x Capacitor 10uF
2 x Capacitor 100nF
1 x Capacitor 10nF
2 x Capacitor 22pF
2 x Resistor 4K7
1 x Resistor 10K
1 x Diode 1N4148
1 x Regulator TSR1-2405
1 x LM1117-3.3V
1x5 Male Strip pin, pitch:2.54mm
1x5, 1x6 :Female Strip Pin, pitch:2.54mm
1x Switch ON/OFF
Perfored Prototype board

Development

This device has been designed with a 8 bit microcontroller called Atmega328 and has been programmated using the Arduino IDE. The thermal  infrarred sensor MLX90621 is read by the I2C bus. It also has an analog  temperature sensor MCP9700. When the temperature measured by the MLX90621 reachs a prefixed threshold by the 10K potentiometer, a LED lamp is switched and the microcontrller plays an intermitent sound through the speaker that it is inside de box.

The prototype needs a input power supply between 24V and 6V. Two regulators step-down the input voltage to 5V and 3.3V. The box has a green LED that it light when the power supply input is connected to the box.
Figure 2: The interior of the box. 
To communicate the device with the PC it is needed to use an Arduino UNO without microcontroller.  VCC, GND, TX, RX and RESET of the Atmega328 are connected to Arduino. This board is used to communicate with the PC through the USB cable. Using these wires it is possible to reprogram the micro and see the sensor information through the serial port. For a better explanation visit
https://www.arduino.cc/en/Tutorial/ArduinoToBreadboard
Figure 3: The circuit has been built on a perforated board.
When the device it is not connected to de PC, this serial port  can be used to connect the HC-05 Bluetooth module. With this module we can use an Android Phone to see all the sensor data with applications such as Blueterm.

The Arduino Sketch has four files: 
  •  pitches.h: tones for producing the sound
  •  MLX90621.h and MLX90621.h: library for the infrared temperature sensor
  •  ThermalAlert.ino: it has the arduino code 
The codes used here are at the end of the page.
The library for MLX90621 has been downloaded from here:
https://github.com/robinvanemden/MLX90621_Arduino_Processing 
Figure 4: I use a wall power adapter to power this electronic device.

Schematic

  The value of XTAL is 8MHz.

Results

This device can be very useful to detect temperatures without contact. For instance, it can be colocated looking to a thermal radiator which we need to prevent very high temperatures  If the measureent is higher than the value prefixed with the potentiometer, the alarm will warn you that the radiator is extremely  hot.

In the next video you can see how woks this device:

References

Arduino Codes

ThermalAlert.ino
/*
* This project creates a small thermal camera using the MELEXIS MLX90621
*
* Adapted by Josh Long (https://github.com/longjos) Oct 2015
* Based on a https://github.com/robinvanemden/MLX90621_Arduino_Processing
*
* Original work by:
* 2013 by Felix Bonowski
* Based on a forum post by maxbot: http://forum.arduino.cc/index.php/topic,126244.msg949212.html#msg949212
* This code is in the public domain.
*/
#include <Arduino.h>
#include <Wire.h>
#include "MLX90621.h"
#include <Timer.h>

#define LUZ 11
#define BUZZER 9

MLX90621 sensor; // create an instance of the Sensor class
Timer luz;
Timer sonido;

boolean alert=false;

void setup()
{
 Serial.begin(9600);
 pinMode(LUZ,OUTPUT);
 pinMode(LUZ,OUTPUT);
 digitalWrite(LUZ,LOW);
 digitalWrite(BUZZER,LOW);
 sensor.initialise (16);
 delay(1000);
}

void loop()
{
 if(alert)
 {
    luz.update();
    sonido.update();
 }
 sensor.measure(true); //get new readings from the sensor
 for(int y=0;y<4;y++)
 {
 //go through all the rows
 for(int x=0;x<16;x++)
 {
  //go through all the columns
  int16_t valueAtXY= sensor.getTemperature(y+x*4); // extract the temperature at position x/y
  Serial.print(" "); Serial.print(valueAtXY);
 }
 Serial.println();
}
delay(1000);
Serial.print("MCP9700: "); Serial.println(MCP9700());
delay(1000);
int pot=Potenciometer();
Serial.print("Potenciometer: "); Serial.println(pot);
if(pot>500) alert=true;
else alert=false;
ParpadeoLUZ(alert);
delay(1000);
}

float MCP9700(void)
{
 float temp= analogRead(A0)*3.3/1024.0;
 temp = temp - 0.5;
 temp = temp / 0.01;
 return temp;
}

int Potenciometer(void)
{
 int pot=analogRead(A3);
 return pot;
}

void ParpadeoLUZ(boolean OnOff)
{
 if(OnOff)
 {
  luz.pulse(LUZ, 100, LOW);
  digitalWrite(LUZ,HIGH);//Proporciona voltajea los leds
 }
 else digitalWrite(LUZ,LOW);
}

void sound(boolean OnOff)
{
 if(OnOff)
 {
    luz.pulse(BUZZER,10, LOW);
 }
 else digitalWrite(BUZZER,LOW);
}

MLX90621.cpp
/*
* MLX90621.cpp
*
* Created on: 18.11.2013
* Author: Max
*
* Adapted by https://github.com/longjos
* Adapted for use with Arduino UNO
*/
#include "MLX90621.h"

void MLX90621::initialise(int refrate)
{
  refreshRate = refrate;
  Wire.begin();
  delay(5);
  readEEPROM();
  writeTrimmingValue();
  setConfiguration();
  preCalculateConstants();
}

void MLX90621::measure(bool calculate_temps)
{
  if (checkConfig())
  {
    readEEPROM();
    writeTrimmingValue();
    setConfiguration();
  }
  readPTAT();
  readIR();
  if(calculate_temps)
  {
    calculateTA();
    readCPIX();
    calculateTO();
  }
}

float MLX90621::getTemperature(int num)
{
  if ((num >= 0) && (num < 64))
  {
    return temperatures[num];
  }
  else
  {
    return 0;
  }
}

float MLX90621::getAmbient()
{
 return Tambient;
}

void MLX90621::setConfiguration()
{
  byte Hz_LSB;
  switch (refreshRate)
  {
    case 0:
      Hz_LSB = 0b00111111;
    break;
    case 1:
      Hz_LSB = 0b00111110;
    break;
    case 2:
      Hz_LSB = 0b00111101;
    break;
    case 4:
      Hz_LSB = 0b00111100;
    break;
    case 8:
      Hz_LSB = 0b00111011;
    break;
    case 16:
      Hz_LSB = 0b00111010;
    break;
    case 32:
      Hz_LSB = 0b00111001;
    break;
    default:
      Hz_LSB = 0b00111110;
 }
 byte defaultConfig_H = 0b01000110; //kmoto: See data sheet p.11 and 25
 Wire.beginTransmission(0x60);
 Wire.write(0x03);
 Wire.write((byte) Hz_LSB - 0x55);
 Wire.write(Hz_LSB);
 Wire.write(defaultConfig_H - 0x55);
 Wire.write(defaultConfig_H);
 Wire.endTransmission();

 //Read the resolution from the config register
 resolution = (readConfig() & 0x30) >> 4;
}

void MLX90621::readEEPROM() { // Read in blocks of 32 bytes to accomodate Wire library
for(int j=0;j<256;j+=32) {
Wire.beginTransmission(0x50);
Wire.write(j);
byte rc = Wire.endTransmission(false);
Wire.requestFrom(0x50, 32);
for (int i = 0; i < 32; i++) {
eepromData[j+i] = (uint8_t) Wire.read();
}
}
}

void MLX90621::writeTrimmingValue() {
Wire.beginTransmission(0x60);
Wire.write(0x04);
Wire.write((byte) eepromData[OSC_TRIM_VALUE] - 0xAA);
Wire.write(eepromData[OSC_TRIM_VALUE]);
Wire.write(0x56);
Wire.write(0x00);
Wire.endTransmission();
}

void MLX90621::calculateTA(void)
{
 Tambient = ((-k_t1 + sqrt(sq(k_t1) - (4 * k_t2 * (v_th - (float) ptat))))/ (2 * k_t2)) + 21.0;
}

void MLX90621::preCalculateConstants()
{
  resolution_comp = pow(2.0, (3 - resolution));
  emissivity = unsigned_16(eepromData[CAL_EMIS_H], eepromData[CAL_EMIS_L]) / 32768.0;
  a_common = twos_16(eepromData[CAL_ACOMMON_H], eepromData[CAL_ACOMMON_L]);
  a_i_scale = (int16_t)(eepromData[CAL_AI_SCALE] & 0xF0) >> 4;
  b_i_scale = (int16_t) eepromData[CAL_BI_SCALE] & 0x0F;

  alpha_cp = unsigned_16(eepromData[CAL_alphaCP_H], eepromData[CAL_alphaCP_L]) /
  (pow(2.0, eepromData[CAL_A0_SCALE]) * resolution_comp);
  a_cp = (float) twos_16(eepromData[CAL_ACP_H], eepromData[CAL_ACP_L]) / resolution_comp;
  b_cp = (float) twos_8(eepromData[CAL_BCP]) / (pow(2.0, (float)b_i_scale) * resolution_comp);
  tgc = (float) twos_8(eepromData[CAL_TGC]) / 32.0;

  k_t1_scale = (int16_t) (eepromData[KT_SCALE] & 0xF0) >> 4;
  k_t2_scale = (int16_t) (eepromData[KT_SCALE] & 0x0F) + 10;
  v_th = (float) twos_16(eepromData[VTH_H], eepromData[VTH_L]);
  v_th = v_th / resolution_comp;
  k_t1 = (float) twos_16(eepromData[KT1_H], eepromData[KT1_L]);
  k_t1 /= (pow(2, k_t1_scale) * resolution_comp);
  k_t2 = (float) twos_16(eepromData[KT2_H], eepromData[KT2_L]);
  k_t2 /= (pow(2, k_t2_scale) * resolution_comp);
}

void MLX90621::calculateTO()
{
  float v_cp_off_comp = (float) cpix - (a_cp + b_cp * (Tambient - 25.0));
  tak4 = pow((float) Tambient + 273.15, 4.0);
  minTemp = NULL, maxTemp = NULL;
  for (int i = 0; i < 64; i++)
  {
    a_ij = ((float) a_common + eepromData[i] * pow(2.0, a_i_scale)) / resolution_comp;
    b_ij = (float) twos_8(eepromData[0x40 + i]) / (pow(2.0, b_i_scale) * resolution_comp);
    v_ir_off_comp = (float) irData[i] - (a_ij + b_ij * (Tambient - 25.0));
    v_ir_tgc_comp = (float) v_ir_off_comp - tgc * v_cp_off_comp;
    float alpha_ij = ((float) unsigned_16(eepromData[CAL_A0_H], eepromData[CAL_A0_L]) / pow(2.0, (float)     eepromData[CAL_A0_SCALE]));
    alpha_ij += ((float) eepromData[0x80 + i] / pow(2.0, (float) eepromData[CAL_DELTA_A_SCALE]));
    alpha_ij = alpha_ij / resolution_comp;
    //ksta = (float) twos_16(eepromData[CAL_KSTA_H], eepromData[CAL_KSTA_L]) / pow(2.0, 20.0);
    //alpha_comp = (1 + ksta * (Tambient - 25.0)) * (alpha_ij - tgc * alpha_cp);
    alpha_comp = (alpha_ij - tgc * alpha_cp); // For my MLX90621 the ksta calibrations were 0
    // so I can ignore them and save a few cycles
    v_ir_comp = v_ir_tgc_comp / emissivity;
    float temperature = pow((v_ir_comp / alpha_comp) + tak4, 1.0 / 4.0) - 274.15;
    temperatures[i] = temperature;
    if (minTemp == NULL || temperature < minTemp)
    {
     minTemp = temperature;
    }
    if (maxTemp == NULL || temperature > maxTemp)
    {
     maxTemp = temperature;
    }
 }
}

float MLX90621::getMinTemp()
{
 return minTemp;
}

float MLX90621::getMaxTemp()
{
 return maxTemp;
}


void MLX90621::readIR()
{
 for (int j = 0; j < 64; j += 16)
 {
    // Read in blocks of 32 bytes to overcome Wire buffer limit
    Wire.beginTransmission(0x60);
    Wire.write(0x02);
    Wire.write(j);
    Wire.write(0x01);
    Wire.write(0x20);
    Wire.endTransmission(false);
   Wire.requestFrom(0x60, 32);
    for (int i = 0; i < 16; i++)
    {
     uint8_t pixelDataLow = (uint8_t) Wire.read();
     uint8_t pixelDataHigh = (uint8_t) Wire.read();
     irData[j + i] = twos_16(pixelDataHigh, pixelDataLow);
    }
  }
}

void MLX90621::readPTAT()
{
  Wire.beginTransmission(0x60);
  Wire.write(0x02);
  Wire.write(0x40);
  Wire.write(0x00);
  Wire.write(0x01);
  Wire.endTransmission(false);
  Wire.requestFrom(0x60, 2);
  byte ptatLow = Wire.read();
  byte ptatHigh = Wire.read();
  ptat = (ptatHigh * 256) + ptatLow;
}

void MLX90621::readCPIX()
{
  Wire.beginTransmission(0x60);
  Wire.write(0x02);
  Wire.write(0x41);
  Wire.write(0x00);
  Wire.write(0x01);
  Wire.endTransmission(false);
  Wire.requestFrom(0x60, 2);
  byte cpixLow = Wire.read();
  byte cpixHigh = Wire.read();
  cpix = twos_16(cpixHigh, cpixLow);
}

int16_t MLX90621::twos_16(uint8_t highByte, uint8_t lowByte)
{
  uint16_t combined_word = 256 * highByte + lowByte;
  if (combined_word > 32767)
     return (int16_t) (combined_word - 65536);
  return (int16_t) combined_word;
}

int8_t MLX90621::twos_8(uint8_t byte)
{
 if (byte > 127)
   return (int8_t) byte - 256;
 return (int8_t) byte;
}

uint16_t MLX90621::unsigned_16(uint8_t highByte, uint8_t lowByte)
{
  return (highByte << 8) | lowByte;
}

uint16_t MLX90621::readConfig()
{
  Wire.beginTransmission(0x60);
  Wire.write(0x02);
  Wire.write(0x92);
  Wire.write(0x00);
  Wire.write(0x01);
  Wire.endTransmission(false);
  Wire.requestFrom(0x60, 2);
  byte configLow = Wire.read();
  byte configHigh = Wire.read();
  uint16_t config = ((uint16_t) (configHigh << 8) | configLow);
  return config;
}

//Poll the MLX90621 for its current status
//Returns true if the POR/Brown out bit is set
boolean MLX90621::checkConfig()
{
  bool check = !((readConfig() & 0x0400) >> 10);
  return check;
}



MLX90621.h
/*
* MLX90621.h
*
* Created on: 08.07.2014
* Author: Max Ritter
*
* Adapted by https://github.com/longjos
* Adapted for use with Arduino UNO
*/

#ifndef MLX90621_H_
#define MLX90621_H_
//lalala
#ifdef __cplusplus

//Libraries to be included
#include <Arduino.h>
#include <Wire.h>

//Begin registers
#define CAL_ACOMMON_L 0xD0
#define CAL_ACOMMON_H 0xD1
#define CAL_ACP_L 0xD3
#define CAL_ACP_H 0xD4
#define CAL_BCP 0xD5
#define CAL_alphaCP_L 0xD6
#define CAL_alphaCP_H 0xD7
#define CAL_TGC 0xD8
#define CAL_AI_SCALE 0xD9
#define CAL_BI_SCALE 0xD9


#define VTH_L 0xDA
#define VTH_H 0xDB
#define KT1_L 0xDC
#define KT1_H 0xDD
#define KT2_L 0xDE
#define KT2_H 0xDF
#define KT_SCALE 0xD2

//Common sensitivity coefficients
#define CAL_A0_L 0xE0
#define CAL_A0_H 0xE1
#define CAL_A0_SCALE 0xE2
#define CAL_DELTA_A_SCALE 0xE3
#define CAL_EMIS_L 0xE4
#define CAL_EMIS_H 0xE5
#define CAL_KSTA_L 0xE6
#define CAL_KSTA_H 0xE7


//Config register = 0xF5-F6
#define OSC_TRIM_VALUE 0xF7

//Bits within configuration register 0x92
#define POR_TEST 10

class MLX90621
{
 private:
 /* Variables */
 byte refreshRate; //Set this value to your desired refresh frequency
 float temperatures[64]; //Contains the calculated temperatures of each pixel in the array
 float Tambient; //Tracks the changing ambient temperature of the sensor
 //byte loopCount = 0; //Used in main loop

  /* Methods */
  void readEEPROM();
  void setConfiguration();
  void writeTrimmingValue();
  void calculateTA();
  void readPTAT();
  void calculateTO();
  void readIR();
  void readCPIX();
  void preCalculateConstants();
  int16_t twos_16(uint8_t highByte, uint8_t lowByte);
  int8_t twos_8(uint8_t byte);
  uint16_t unsigned_16(uint8_t highByte, uint8_t lowByte);
  uint16_t readConfig();
  boolean checkConfig();
  float v_ir_off_comp, ksta, v_ir_tgc_comp, v_ir_comp, alpha_comp;
  float tak4, resolution_comp;
  int16_t a_common, a_i_scale, b_i_scale, k_t1_scale, k_t2_scale, resolution;
  uint8_t eepromData[256]; //Contains the full EEPROM reading from the MLX90621
  float k_t1, k_t2, emissivity, tgc, alpha_cp, a_cp, b_cp, v_th;
  uint16_t ptat;
  int16_t cpix;
  float a_ij, b_ij, alpha_ij;
  float minTemp, maxTemp;
  public:
    int16_t irData[64]; //Contains the raw IR data from the sensor
    void initialise(int);
    void measure(bool);
    float getTemperature(int num);
    float getAmbient();
    float getMinTemp();
    float getMaxTemp();
};
#endif
#endif

Pitches.h
*************************************************
* Public Constants
*************************************************/
#define NOTE_B0 31
#define NOTE_C1 33
#define NOTE_CS1 35
#define NOTE_D1 37
#define NOTE_DS1 39
#define NOTE_E1 41
#define NOTE_F1 44
#define NOTE_FS1 46
#define NOTE_G1 49
#define NOTE_GS1 52
#define NOTE_A1 55
#define NOTE_AS1 58
#define NOTE_B1 62
#define NOTE_C2 65
#define NOTE_CS2 69
#define NOTE_D2 73
#define NOTE_DS2 78
#define NOTE_E2 82
#define NOTE_F2 87
#define NOTE_FS2 93
#define NOTE_G2 98
#define NOTE_GS2 104
#define NOTE_A2 110
#define NOTE_AS2 117
#define NOTE_B2 123
#define NOTE_C3 131
#define NOTE_CS3 139
#define NOTE_D3 147
#define NOTE_DS3 156
#define NOTE_E3 165
#define NOTE_F3 175
#define NOTE_FS3 185
#define NOTE_G3 196
#define NOTE_GS3 208
#define NOTE_A3 220
#define NOTE_AS3 233
#define NOTE_B3 247
#define NOTE_C4 262
#define NOTE_CS4 277
#define NOTE_D4 294
#define NOTE_DS4 311
#define NOTE_E4 330
#define NOTE_F4 349
#define NOTE_FS4 370
#define NOTE_G4 392
#define NOTE_GS4 415
#define NOTE_A4 440
#define NOTE_AS4 466
#define NOTE_B4 494
#define NOTE_C5 523
#define NOTE_CS5 554
#define NOTE_D5 587
#define NOTE_DS5 622
#define NOTE_E5 659
#define NOTE_F5 698
#define NOTE_FS5 740
#define NOTE_G5 784
#define NOTE_GS5 831
#define NOTE_A5 880
#define NOTE_AS5 932
#define NOTE_B5 988
#define NOTE_C6 1047
#define NOTE_CS6 1109
#define NOTE_D6 1175
#define NOTE_DS6 1245
#define NOTE_E6 1319
#define NOTE_F6 1397
#define NOTE_FS6 1480
#define NOTE_G6 1568
#define NOTE_GS6 1661
#define NOTE_A6 1760
#define NOTE_AS6 1865
#define NOTE_B6 1976
#define NOTE_C7 2093
#define NOTE_CS7 2217
#define NOTE_D7 2349
#define NOTE_DS7 2489
#define NOTE_E7 2637
#define NOTE_F7 2794
#define NOTE_FS7 2960
#define NOTE_G7 3136
#define NOTE_GS7 3322
#define NOTE_A7 3520
#define NOTE_AS7 3729
#define NOTE_B7 3951
#define NOTE_C8 4186
#define NOTE_CS8 4435
#define NOTE_D8 4699
#define NOTE_DS8 4978