Pages: [1] 2 3 4   Go Down
Author Topic: Arduino and MLX90620 16X4 pixel IR thermal array  (Read 26465 times)
0 Members and 1 Guest are viewing this topic.
0
Offline Offline
Jr. Member
**
Karma: 1
Posts: 77
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello guys,
I want to share with you the code and wiring circuit to get the new MLX90620 working with the arduino. Like the well-known MLX90614, the sensor detects thermal radiation and measures temperatures without making contact with the object. The advantage of this sensor is that it can read 64 pixels at the same time VERY fast and accurate. For the price and size, it is a great thermal sensor.

Specifications of the sensor:
- Small size, cost effective 16X4 pixel, thermal array
- Active thermopile pixel technology for fast readout Easy to integrate
- Factory calibrated in wide temperature range: -40 to 85 °C for sensor temperature and -20 to 300 °C for object temperature. 2 Field Of View options: 60X15degrees or 40X10degrees
- High speed I2C digital interface for fast data transfer
- Programmable frame rate 0.5 to 64Hz.

Price:
45€ / 60$ for the 60° version -> http://www.futureelectronics.com/en/technologies/semiconductors/analog/sensors/temperature/Pages/5020660-MLX90620ESF-BAB-000-TU.aspx?IM=0
47€ / 65$ for the 40° version -> http://www.futureelectronics.com/en/Technologies/Product.aspx?ProductID=MLX90620ESFBAD000TUMELEXIS7020660&IM=0

Wiring:
You connect the MLX90620 to 3.3V over a diode, to break it down to the required 2.6V.
GND to GND, SCA to PIN 4 and SCL to PIN 5. Check out the official datasheet concerning the pin connections on the sensor.

Source Code - thanks to IlBaboomba for his various improvements:
Code:
/*
 * Attention! I commented out the alpha_ij array, so if you're going to compile the sketch you'll get for sure an error.
 * You should replace all 64 values with the alpha_ij calculated using the values stored in your MLX90620's EEPROM.
 * I suggest you to make an EEPROM dump, print it on the Serial port and store it in a file. From there, with the help of a spreadsheet (Libreoffice, Google Docs, Excel...) calculate your own alpha_ij values.
 * Please also pay attention to your emissivity value: since in my case it was equal to 1, to save SRAM i cut out that piece of calculation. You need to restore those lines if your emissivity value is not equal to 1.
 */

#include <i2cmaster.h>

int freq = 16;  //Set this value to your desired refresh frequency

int IRDATA[64];
byte CFG_LSB, CFG_MSB, PTAT_LSB, PTAT_MSB, CPIX_LSB, CPIX_MSB, PIX_LSB, PIX_MSB;
int PIX, v_th, CPIX;
float ta, to, emissivity, k_t1, k_t2;
float temperatures[64];
int count=0;
unsigned int PTAT;
int a_cp, b_cp, tgc, b_i_scale;

int a_ij[64];
int b_ij[64];
//float alpha_ij[64] = {1.591E-8, 1.736E-8, 1.736E-8, 1.620E-8, 1.783E-8, 1.818E-8, 1.992E-8, 1.748E-8, 1.864E-8, 2.056E-8, 2.132E-8, 2.033E-8, 2.097E-8, 2.324E-8, 2.388E-8, 2.161E-8, 2.155E-8, 2.394E-8, 2.353E-8, 2.068E-8, 2.353E-8, 2.633E-8, 2.708E-8, 2.394E-8, 2.499E-8, 2.778E-8, 2.731E-8, 2.580E-8, 2.539E-8, 2.796E-8, 2.871E-8, 2.598E-8, 2.586E-8, 2.801E-8, 2.830E-8, 2.633E-8, 2.609E-8, 2.894E-8, 2.924E-8, 2.633E-8, 2.464E-8, 2.778E-8, 2.894E-8, 2.673E-8, 2.475E-8, 2.737E-8, 2.796E-8, 2.679E-8, 2.394E-8, 2.708E-8, 2.714E-8, 2.644E-8, 2.347E-8, 2.563E-8, 2.493E-8, 2.388E-8, 2.179E-8, 2.440E-8, 2.504E-8, 2.295E-8, 2.033E-8, 2.283E-8, 2.295E-8, 2.155E-8};  //<-- REPLACE THIS VALUES WITH YOUR OWN!
//float v_ir_off_comp[64];  //I'm going to merge v_ir_off_comp calculation into v_ir_tgc_comp equation. It's not required anywhere else, so I'll save 256 bytes of SRAM doing this.
float v_ir_tgc_comp[64];
//float v_ir_comp[64]; //removed to save SRAM, in my case v_ir_comp == v_ir_tgc_comp



void config_MLX90620_Hz(int Hz){
  byte Hz_LSB;
  switch(Hz){
    case 0:
      Hz_LSB = B00001111;
      break;
    case 1:
      Hz_LSB = B00001110;
      break;
    case 2:
      Hz_LSB = B00001101;
      break;
    case 4:
      Hz_LSB = B00001100;
      break;
    case 8:
      Hz_LSB = B00001011;
      break;
    case 16:
      Hz_LSB = B00001010;
      break;
    case 32:
      Hz_LSB = B00001001;
      break;
    default:
      Hz_LSB = B00001110;
  }
  i2c_start_wait(0xC0);
  i2c_write(0x03);    
  i2c_write((byte)Hz_LSB-0x55);
  i2c_write(Hz_LSB);  
  i2c_write(0x1F);  
  i2c_write(0x74);  
  i2c_stop();
}

void read_EEPROM_MLX90620(){
  byte EEPROM_DATA[256];
  i2c_start_wait(0xA0);    
  i2c_write(0x00);
  i2c_rep_start(0xA1);
  for(int i=0;i<=255;i++){
    EEPROM_DATA[i] = i2c_readAck();
  }
  i2c_stop();
  varInitialization(EEPROM_DATA);
  write_trimming_value(EEPROM_DATA[247]);
}

void write_trimming_value(byte val){
  i2c_start_wait(0xC0);
  i2c_write(0x04);
  i2c_write((byte)val-0xAA);
  i2c_write(val);  
  i2c_write(0x56);  
  i2c_write(0x00);  
  i2c_stop();
}

void calculate_TA(){
  ta = (-k_t1 + sqrt(square(k_t1) - (4 * k_t2 * (v_th - (float)PTAT))))/(2*k_t2) + 25; //it's much more simple now, isn't it? :)
}

void calculate_TO(){
  float v_cp_off_comp = (float) CPIX - (a_cp + (b_cp/pow(2, b_i_scale)) * (ta - 25)); //this is needed only during the to calculation, so I declare it here.
  
  for (int i=0; i<64; i++){
    v_ir_tgc_comp[i] = IRDATA[i] - (a_ij[i] + (float)(b_ij[i]/pow(2, b_i_scale)) * (ta - 25)) - (((float)tgc/32)*v_cp_off_comp);
    //v_ir_comp[i]= v_ir_tgc_comp[i] / emissivity; //removed to save SRAM, since emissivity in my case is equal to 1.
    //temperatures[i] = sqrt(sqrt((v_ir_comp[i]/alpha_ij[i]) + pow((ta + 273.15),4))) - 273.15;
    temperatures[i] = sqrt(sqrt((v_ir_tgc_comp[i]/alpha_ij[i]) + pow((ta + 273.15),4))) - 273.15; //edited to work with v_ir_tgc_comp instead of v_ir_comp
  }
}


void read_IR_ALL_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);      
  i2c_write(0x00);    
  i2c_write(0x01);      
  i2c_write(0x40);      
  i2c_rep_start(0xC1);
  for(int i=0;i<=63;i++){
    PIX_LSB = i2c_readAck();
    PIX_MSB = i2c_readAck();
    IRDATA[i] = (PIX_MSB << 8) + PIX_LSB;
  }
  i2c_stop();
}

void read_PTAT_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x90);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  PTAT_LSB = i2c_readAck();
  PTAT_MSB = i2c_readAck();
  i2c_stop();
  PTAT = ((unsigned int)PTAT_MSB << 8) + PTAT_LSB;
}

void read_CPIX_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x91);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CPIX_LSB = i2c_readAck();
  CPIX_MSB = i2c_readAck();
  i2c_stop();
  CPIX = (CPIX_MSB << 8) + CPIX_LSB;
}

void read_Config_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x92);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CFG_LSB = i2c_readAck();
  CFG_MSB = i2c_readAck();
  i2c_stop();
}

void check_Config_Reg_MLX90620(){
  read_Config_Reg_MLX90620();
  if ((!CFG_MSB & 0x04) == 0x04){
    config_MLX90620_Hz(freq);
  }
}

void varInitialization(byte EEPROM_DATA[]){
  v_th = (EEPROM_DATA[219] <<8) + EEPROM_DATA[218];
  k_t1 = ((EEPROM_DATA[221] <<8) + EEPROM_DATA[220])/1024.0;
  k_t2 =((EEPROM_DATA[223] <<8) + EEPROM_DATA[222])/1048576.0;
  
  a_cp = EEPROM_DATA[212];
  if(a_cp > 127){
    a_cp = a_cp - 256;
  }
  b_cp = EEPROM_DATA[213];
  if(b_cp > 127){
    b_cp = b_cp - 256;
  }
  tgc = EEPROM_DATA[216];
  if(tgc > 127){
    tgc = tgc - 256;
  }

  b_i_scale = EEPROM_DATA[217];

  emissivity = (((unsigned int)EEPROM_DATA[229] << 8) + EEPROM_DATA[228])/32768.0;

  for(int i=0;i<=63;i++){
    a_ij[i] = EEPROM_DATA[i];
    if(a_ij[i] > 127){
      a_ij[i] = a_ij[i] - 256;
    }
    b_ij[i] = EEPROM_DATA[64+i];
    if(b_ij[i] > 127){
      b_ij[i] = b_ij[i] - 256;
    }
  }
}

void Temperatures_Serial_Transmit(){
  for(int i=0;i<=63;i++){
    Serial.println(temperatures[i]);
  }
}

void setup(){
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  i2c_init();
  PORTC = (1 << PORTC4) | (1 << PORTC5);
  delay(5);
  read_EEPROM_MLX90620();
  config_MLX90620_Hz(freq);
}

void loop(){
  if(count ==0){ //TA refresh is slower than the pixel readings, I'll read the values and computate them not every loop.
    read_PTAT_Reg_MLX90620();
    calculate_TA();
    check_Config_Reg_MLX90620();
  }
  count++;
  if(count >=16){
    count = 0;
  }
  read_IR_ALL_MLX90620();
  read_CPIX_Reg_MLX90620();
  calculate_TO();
  Temperatures_Serial_Transmit();
}

You also need the I2CMaster library from here: http://www.cheap-thermocam.bplaced.net/software/I2Cmaster.rar (SCL_CLOCK has already been set to 400000)
This sketch transfers all 64 pixels to the computer over the serial window. For information about which pixel correspond to which position, check out the MLX90620 datasheet: http://www.melexis.com/Asset/Datasheet-IR-thermometer-16X4-sensor-array-MLX90620-DownloadLink-6099.aspx

I originally intended to integrate this sensor into my Cheap-Thermocam project www.cheap-thermocam.com), but it did not make it at the end.
« Last Edit: April 15, 2013, 07:26:47 pm by maxbot » Logged

0
Offline Offline
Full Member
***
Karma: 0
Posts: 134
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks for sharing,
I tried to order from there some time ago, but shipping cost to Europe was ridiculous, almost the price of the sensor itself.
Does anyone know an EU source of this?

Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 36
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello guys,
I want to share with you the code and wiring circuit to get the new MLX90620 working with the arduino. Like the well-known MLX90614, the sensor detects thermal radiation and measures temperatures without making contact with the object. The advantage of this sensor is that it can read 64 pixels at the same time VERY fast and accurate. For the price and size, it is a great thermal sensor.

Specifications of the sensor:
- Small size, cost effective 16X4 pixel, thermal array
- Active thermopile pixel technology for fast readout Easy to integrate
- Factory calibrated in wide temperature range: -40 to 85 °C for sensor temperature and -20 to 300 °C for object temperature. 2 Field Of View options: 60X15degrees or 40X10degrees
- High speed I2C digital interface for fast data transfer
- Programmable frame rate 0.5 to 64Hz.

Price: 75$ for the 60° version -> http://www.futureelectronics.com/en/technologies/semiconductors/analog/sensors/temperature/Pages/5020660-MLX90620ESF-BAB-000-TU.aspx?IM=0 (not at stock at the moment..)

Circuit diagram:


Source Code:
Code:
#include <i2cmaster.h>

int IRDATA[64];
word EEPROM_DATA[256];
byte CFG_LSB, CFG_MSB, PTAT_LSB, PTAT_MSB, CPIX_LSB, CPIX_MSB, PIX_LSB, PIX_MSB, EEDATA2;
int PIX, CPIX, PTAT, CFG;
double ta, to;
double temperatures[64];

void config_MLX90620_16Hz(){ //Sets the To-Refresh rate to 16Hz
  i2c_start_wait(0xC0);
  i2c_write(0x03);    
  i2c_write(0xB5);
  i2c_write(0x0A);  
  i2c_write(0x1F);  
  i2c_write(0x74);  
  i2c_stop();
}

void config_MLX90620_8Hz(){ //Sets the To-Refresh rate to 8Hz
  i2c_start_wait(0xC0);
  i2c_write(0x03);    
  i2c_write(0xB6);
  i2c_write(0x0B);  
  i2c_write(0x1F);  
  i2c_write(0x74);  
  i2c_stop();
}

void read_EEPROM_MLX90620(){
  i2c_start_wait(0xA0);    
  i2c_write(0x00);
  i2c_rep_start(0xA1);
  int i;
  for(i=0;i<=255;i++){
    EEPROM_DATA[i] = i2c_readAck();
  }
  i2c_stop();
}

void calculate_TA(){
  double v_th, k_t1, k_t2;
  v_th = (double) (256 * EEPROM_DATA[219]) + EEPROM_DATA[218];
  if(v_th > 32767){
    v_th = v_th - 65536;
  }
  k_t1 = (double) ((256 * EEPROM_DATA[221]) + EEPROM_DATA[220])/1024;
  if(k_t1 > 32767){
    k_t1 = k_t1 - 65536;
  }
  k_t2 = (double) ((256 * EEPROM_DATA[223]) + EEPROM_DATA[222])/1048576;
  if(k_t2 > 32767){
    k_t2 = k_t2 - 65536;
  }
  ta = (double) (-k_t1 + sqrt(square(k_t1) - (4 * k_t2 * (v_th - PTAT))))/(2*k_t2) + 25;
  Serial.println(ta);
}

void calculate_TO(){
  double v_ir_off_comp, v_ir_tgc_comp, v_ir_comp, v_cp_off_comp, emissivity, alpha_ij;
  int a_cp, b_cp, a_ij, b_ij, tgc;
  for(int i=0;i<=63;i++){
    if(IRDATA[i] > 32767){
      IRDATA[i] = IRDATA[i] - 65536;
    }
    a_cp = EEPROM_DATA[212];
    if(a_cp > 127){
      a_cp = a_cp - 256;
    }
    b_cp = EEPROM_DATA[213];
    if(b_cp > 127){
      b_cp = b_cp - 256;
    }
    v_cp_off_comp = (double) CPIX - (a_cp + (b_cp/pow(2,EEPROM_DATA[217])) * (ta - 25));
    a_ij = EEPROM_DATA[i];
    if(a_ij > 127){
      a_ij = a_ij - 256;
    }
    b_ij = EEPROM_DATA[64+i];
    if(b_ij > 127){
      b_ij = b_ij - 256;
    }
    v_ir_off_comp = (double) IRDATA[i] - (a_ij + (b_ij/pow(2,EEPROM_DATA[217])) * (ta - 25));
    tgc = EEPROM_DATA[216];
    if(tgc > 127){
      tgc = tgc - 256;
    }
    v_ir_tgc_comp = (double) v_ir_off_comp - ((tgc/32)*v_cp_off_comp);
    emissivity = (double) ((256*EEPROM_DATA[229]) + EEPROM_DATA[228])/32768;
    v_ir_comp = (double) v_ir_tgc_comp / emissivity;
    alpha_ij = 0.0000000232776;
    temperatures[i] = (double) sqrt(sqrt((v_ir_comp/alpha_ij) + pow((ta + 273.15),4))) - 273.15;
  }
}

void read_IR_ALL_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);      
  i2c_write(0x00);    
  i2c_write(0x01);      
  i2c_write(0x40);      
  i2c_rep_start(0xC1);
  int i;
  for(int i;i<=63;i++){
    PIX_LSB = i2c_readAck();
    PIX_MSB = i2c_readAck();
    IRDATA[i] = (PIX_MSB << 8) + PIX_LSB;
  }
  i2c_stop();
}

void read_PTAT_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x90);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  PTAT_LSB = i2c_readAck();
  PTAT_MSB = i2c_readAck();
  i2c_stop();
  PTAT = (PTAT_MSB << 8) + PTAT_LSB;
}

void read_CPIX_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x91);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CPIX_LSB = i2c_readAck();
  CPIX_MSB = i2c_readAck();
  i2c_stop();
  CPIX = (CPIX_MSB << 8) + CPIX_LSB;
  if(CPIX > 32767){
    CPIX = CPIX - 65536;
  }
}

void read_Config_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x92);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CFG_LSB = i2c_readAck();
  CFG_MSB = i2c_readAck();
  i2c_stop();
}

void check_Config_Reg_MLX90620(){
  read_Config_Reg_MLX90620();
  if ((CFG_MSB & 0x40) == 0){
    config_MLX90620_8Hz;
  }
}

void Temperatures_Serial_Transmit(){
  for(int i=0;i<=63;i++){
    Serial.println(temperatures[i]);
  }
}

void setup(){
  Serial.begin(115200);
  i2c_init();
  PORTC = (1 << PORTC4) | (1 << PORTC5);
  config_MLX90620_8Hz();
  //config_MLX90620_16Hz();
  read_EEPROM_MLX90620();
}

void loop(){
  read_PTAT_Reg_MLX90620();
  read_CPIX_Reg_MLX90620();
  read_IR_ALL_MLX90620();
  calculate_TA();
  calculate_TO();
  Temperatures_Serial_Transmit();
  check_Config_Reg_MLX90620();
  delay(500); //For 8Hz, the fastest refresh is 125ms, for 16Hz 65ms
}

You also need the I2CMaster library from here: http://www.cheap-thermocam.bplaced.net/software/I2Cmaster.rar
This sketch transfers all 64 pixels to the computer over the serial window. For information about which pixel correspond to which position, check out the MLX90620 datasheet: http://www.melexis.com/Asset/Datasheet-IR-thermometer-16X4-sensor-array-MLX90620-DownloadLink-6099.aspx

I originally intended to integrate this sensor into my Cheap-Thermocam project (www.cheap-thermocam.tk), but I have no time to finish it at the moment..
Maybe for somebody it is useful smiley-wink

Can you share sample of images? how fast does it do images? need computer or not?

Thanks a lot.

Jose
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

This is really nice, I was inspired by your first thermo cam to assemble my own version.  smiley

I evaluate it's uses as part of an Healthcare alarmsystem for elderly people.

Too bad those MLX90620 sensors are really hard to get, delivery lead times are now around 20 weeks.

Any good ideas were to get some within an acceptable timeframe ?
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I was just wondering if anyone had looked into using the grid-eye sensor from Panasonic?

It's an 8x8 Thermal Array Sensor and is currently in stock at http://www.digikey.com

Under $40 each
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 29
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hello!

First, i would like to thanks maxbot for sharing his experience with mlx 90620 and arduino. It has helped me a great deal with setting up my own communication.

I have a couple of questions though:

1. when calculating ta (sensor temp) coefficients i noticed that one should first do a sign check and then divide the result and not the other way around
2. it was mentioned that max refresh rate is 0.5 to 64 Hz.The documentation states that maximum refresh rate is 512 Hz.
3. when calculating the to (object temp) i noticed that alpha_ij is declared as a constant.where is this value from?this value should be calculated for each individual pixel.
4. could you be so kind to explain the check configuration register function  (CFG_MSB & 0x40) == 0?

Thank you again and best regards,
K

« Last Edit: December 03, 2012, 03:32:44 pm by Chimera » Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

The Grid-eye does look promising. It's in stock, and has the same field of view as the MLX990620, 60º.
http://www.digikey.com/product-detail/en/AMG8851/255-3507-1-ND/3461618

What it needs is a breakout board.
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 2
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I've never dabbled with hardware before so I'm a bit wary of buying the parts and messing it all up

There's a document here
http://pewa.panasonic.com/assets/pcsd/manuals/grid-eye/grid-eye-evalkit-reference-design.pdf
where they show how to hook it up to an arduino (including code) with an 8x8 LED grid to show the heat (low / med / high) but it's a bit basic - I'd prefer to change the 8x8 matrix from a simple 2 color one to an RGB to better represent temperatures.

I live in Florida and my electricity bill got up to $400 one month this summer so I'm quite motivated to put something together to see where all my air conditioned air is escaping to smiley
Logged

Offline Offline
Newbie
*
Karma: 0
Posts: 1
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi all from Italy!,i'm just starting to build my own "cheap thermocam" and this is my first Arduino project,i come from midibox-Sid project,a PIC based synth using the famous Sid chip from the commodore 64:this was a very beautiful project in my opinion and i take this chance to thank mr. Klose for his great work!!

http://www.ucapps.de/index.html?page=midibox_sid.html

Anyway i'm a newbie to electronics so i need it to be made as simple as possible...please :-)

My Questions are two:

A)as i want try to use the cheap thermocam in my daily work as an hydraulic(detecting pipes and losses behind walls)i think i need to get better graphic resolution and sensibility i can,so what sensor would be better to buy:
the single pixel MLX90614ESF-DCI-000-TU;
the newest 16x4 pixel MLX90620ESF-BAB-000-TU with wide FOV;
or the same model with medium FOV MLX90620ESF-BAD-000-TU?
{i think the last one would be the best solutuion becouse I seem to have realized that smaller FOV mean better precision but at the moment it's not in stock :-( }

B)How is it possible to combine more sensors to improve resolution?

Thank you

P.S.Special thanks go to maxbot and all the guys that work to give us this very powerful object!
« Last Edit: December 12, 2012, 06:42:41 am by Cisco85 » Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 24
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi guys,

i am also really interested in building my own cheap camera, but i cannot get any sensor here in Germany.
Is there any chance to get some? Any tips?

Thanks a lot.

Cu kami83
Logged

0
Offline Offline
Newbie
*
Karma: 1
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi maxbot,
thank you very much for your code, it's been a great starting point, but I found some bugs in it.
I'll share here everything I have found, hoping that it could help other people.
I'm going to quote every piece of code and comment it, and afterwards (next post, there's 9500 char limit per post!) I'll paste a similar code with all the corrections in it.
Please don't take this in the wrong way, I'm not trying to discredit your work, I'm just trying to help someone else who could experience the same issues I had.

Code:
#include <i2cmaster.h>

int IRDATA[64];
word EEPROM_DATA[256];
byte CFG_LSB, CFG_MSB, PTAT_LSB, PTAT_MSB, CPIX_LSB, CPIX_MSB, PIX_LSB, PIX_MSB, EEDATA2;
int PIX, CPIX, PTAT, CFG;
double ta, to;
double temperatures[64];
EEPROM_DATA is an array of bytes, not words.
Let's declare it as an array of bytes, we'll save 256 bytes (on 2048 available, it makes substantial difference!)
EEDATA2 and CFG are not used inside the code, so I'm going to get rid of them.
Double and float in arduino are the same, but some functions require only floats.

Code:

void config_MLX90620_16Hz(){ //Sets the To-Refresh rate to 16Hz
  i2c_start_wait(0xC0);
  i2c_write(0x03);    
  i2c_write(0xB5);
  i2c_write(0x0A);  
  i2c_write(0x1F);  
  i2c_write(0x74);  
  i2c_stop();
}

void config_MLX90620_8Hz(){ //Sets the To-Refresh rate to 8Hz
  i2c_start_wait(0xC0);
  i2c_write(0x03);    
  i2c_write(0xB6);
  i2c_write(0x0B);  
  i2c_write(0x1F);  
  i2c_write(0x74);  
  i2c_stop();
}

Nothing wrong here. It's however possible to declare a unique function and pass the refresh rate as a parameter.
It makes the code smaller and it allows to easily choose the frequency to work with.

Code:
void read_EEPROM_MLX90620(){
  i2c_start_wait(0xA0);    
  i2c_write(0x00);
  i2c_rep_start(0xA1);
  int i;
  for(i=0;i<=255;i++){
    EEPROM_DATA[i] = i2c_readAck();
  }
  i2c_stop();
}
ok.

Code:
void calculate_TA(){
  double v_th, k_t1, k_t2;
  v_th = (double) (256 * EEPROM_DATA[219]) + EEPROM_DATA[218];
  if(v_th > 32767){
    v_th = v_th - 65536;
  }
  k_t1 = (double) ((256 * EEPROM_DATA[221]) + EEPROM_DATA[220])/1024;
  if(k_t1 > 32767){
    k_t1 = k_t1 - 65536;
  }
  k_t2 = (double) ((256 * EEPROM_DATA[223]) + EEPROM_DATA[222])/1048576;
  if(k_t2 > 32767){
    k_t2 = k_t2 - 65536;
  }
  ta = (double) (-k_t1 + sqrt(square(k_t1) - (4 * k_t2 * (v_th - PTAT))))/(2*k_t2) + 25;
  Serial.println(ta);
}
Here you're declaring v_th, k_t1 and k_t2 and calculating them in every call of calculate_TA() function.
It is correct, but computationally inefficient. Those are constants, there's no need to calulate them in every loop iteration.
 
Code:
void calculate_TO(){
  double v_ir_off_comp, v_ir_tgc_comp, v_ir_comp, v_cp_off_comp, emissivity, alpha_ij;
  int a_cp, b_cp, a_ij, b_ij, tgc;
  for(int i=0;i<=63;i++){
    if(IRDATA[i] > 32767){
      IRDATA[i] = IRDATA[i] - 65536;
    }
    a_cp = EEPROM_DATA[212];
    if(a_cp > 127){
      a_cp = a_cp - 256;
    }
    b_cp = EEPROM_DATA[213];
    if(b_cp > 127){
      b_cp = b_cp - 256;
    }
    v_cp_off_comp = (double) CPIX - (a_cp + (b_cp/pow(2,EEPROM_DATA[217])) * (ta - 25));
    a_ij = EEPROM_DATA[i];
    if(a_ij > 127){
      a_ij = a_ij - 256;
    }
    b_ij = EEPROM_DATA[64+i];
    if(b_ij > 127){
      b_ij = b_ij - 256;
    }
    v_ir_off_comp = (double) IRDATA[i] - (a_ij + (b_ij/pow(2,EEPROM_DATA[217])) * (ta - 25));
    tgc = EEPROM_DATA[216];
    if(tgc > 127){
      tgc = tgc - 256;
    }
    v_ir_tgc_comp = (double) v_ir_off_comp - ((tgc/32)*v_cp_off_comp);
    emissivity = (double) ((256*EEPROM_DATA[229]) + EEPROM_DATA[228])/32768;
    v_ir_comp = (double) v_ir_tgc_comp / emissivity;
    alpha_ij = 0.0000000232776;
    temperatures[i] = (double) sqrt(sqrt((v_ir_comp/alpha_ij) + pow((ta + 273.15),4))) - 273.15;
  }
}
Like the piece of code above, there's no need to declare and calculate a_cp, b_cp, a_ij, b_ij and emissivity on every call.
It can be done once for all in the setup() function.
I don't understand why you set alpha_ij to a constant value, it should be calculated for every pixel like a_ij and b_ij.
I see that the arduino is not capable to do calculation with such small numbers, but I really suggest to do them by hand (or with the help of a spreadsheet) and paste them into the code
This parameter influences a lot the results of the readings.


Code:
void read_IR_ALL_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);      
  i2c_write(0x00);    
  i2c_write(0x01);      
  i2c_write(0x40);      
  i2c_rep_start(0xC1);
  int i;
  for(int i;i<=63;i++){
    PIX_LSB = i2c_readAck();
    PIX_MSB = i2c_readAck();
    IRDATA[i] = (PIX_MSB << 8) + PIX_LSB;
  }
  i2c_stop();
}

void read_PTAT_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x90);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  PTAT_LSB = i2c_readAck();
  PTAT_MSB = i2c_readAck();
  i2c_stop();
  PTAT = (PTAT_MSB << 8) + PTAT_LSB;
}

void read_CPIX_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x91);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CPIX_LSB = i2c_readAck();
  CPIX_MSB = i2c_readAck();
  i2c_stop();
  CPIX = (CPIX_MSB << 8) + CPIX_LSB;
  if(CPIX > 32767){
    CPIX = CPIX - 65536;
  }
}

void read_Config_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x92);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CFG_LSB = i2c_readAck();
  CFG_MSB = i2c_readAck();
  i2c_stop();
}
PTAT calculation needs a fix. You declared it as an int, and arduino stores ints as two's complement, so you'll get a negative value if PTAT goes over 32767.
For the same reason, there's no need for the if statement after CPIX first assignment.


Code:
void check_Config_Reg_MLX90620(){
  read_Config_Reg_MLX90620();
  if ((CFG_MSB & 0x40) == 0){
    config_MLX90620_8Hz;
  }
}
I really didn't understand this part.
You are checking if the MSB of the 0x92 config register has the ADC reference set to HIGH, and in that case you're rewriting the configuration.
Maybe it's just a typo, in the datasheet is reported to check whether a POR occurred, and in that case, rewrite the configuration.
If you meant to write that, the if condition should be: if ((!CFG_MSB & 0x04) == 0x04)

Code:
void Temperatures_Serial_Transmit(){
  for(int i=0;i<=63;i++){
    Serial.println(temperatures[i]);
  }
}
it's ok, I don't know your intended application, but this instruction takes a lot(~32ms) and it's the bottleneck of the sketch, even with 115200 baud rate

Code:
void setup(){
  Serial.begin(115200);
  i2c_init();
  PORTC = (1 << PORTC4) | (1 << PORTC5);
  config_MLX90620_8Hz();
  //config_MLX90620_16Hz();
  read_EEPROM_MLX90620();
}
Big missing here.
You forgot to write the trimming oscillator value to the 0x93 register, after I put it into the code, my readings improved a lot!
I also put the read_EEPROM_MLX90620() function before the config_MLX90620_xxHz() functions, as suggested in the datasheet.

Code:
void loop(){
  read_PTAT_Reg_MLX90620();
  read_CPIX_Reg_MLX90620();
  read_IR_ALL_MLX90620();
  calculate_TA();
  calculate_TO();
  Temperatures_Serial_Transmit();
  check_Config_Reg_MLX90620();
  delay(500); //For 8Hz, the fastest refresh is 125ms, for 16Hz 65ms
}

I find the delay useless and counterproducting.
I tested your code and without the delay it goes at 12Hz, about 82ms per cycle, so even at 8Hz if you take off the delay, you'll get small quantities of double readings.
A good and quick improvement in the code speed is obtained setting the I2C rate at 400KHz. By default in I2Cmaster is set at 50Khz.
To do this, just go in the twimaster.cpp file and change #define SCL_CLOCK 50000L to #define SCL_CLOCK 400000L. The code now loops at 17.15Hz, enough to read the data even at 16Hz.
Logged

0
Offline Offline
Newbie
*
Karma: 1
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Now I'm going to paste here your code modified, with some comments here and there.
It's far from being perfect, but now it seems to work a lot better, at least for me  smiley

Code:
/*
 * Attention! I commented out the alpha_ij array, so if you're going to compile the sketch you'll get for sure an error.
 * You should replace all 64 values with the alpha_ij calculated using the values stored in your MLX90620's EEPROM.
 * I suggest you to make an EEPROM dump, print it on the Serial port and store it in a file. From there, with the help of a spreadsheet (Libreoffice, Google Docs, Excel...) calculate your own alpha_ij values.
 * Please also pay attention to your emissivity value: since in my case it was equal to 1, to save SRAM i cut out that piece of calculation. You need to restore those lines if your emissivity value is not equal to 1.
 */

#include <i2cmaster.h>

int freq = 16;  //Set this value to your desired refresh frequency

int IRDATA[64];
byte CFG_LSB, CFG_MSB, PTAT_LSB, PTAT_MSB, CPIX_LSB, CPIX_MSB, PIX_LSB, PIX_MSB;
int PIX, v_th, CPIX;
float ta, to, emissivity, k_t1, k_t2;
float temperatures[64];
int count=0;
unsigned int PTAT;
int a_cp, b_cp, tgc, b_i_scale;

int a_ij[64];
int b_ij[64];
//float alpha_ij[64] = {1.591E-8, 1.736E-8, 1.736E-8, 1.620E-8, 1.783E-8, 1.818E-8, 1.992E-8, 1.748E-8, 1.864E-8, 2.056E-8, 2.132E-8, 2.033E-8, 2.097E-8, 2.324E-8, 2.388E-8, 2.161E-8, 2.155E-8, 2.394E-8, 2.353E-8, 2.068E-8, 2.353E-8, 2.633E-8, 2.708E-8, 2.394E-8, 2.499E-8, 2.778E-8, 2.731E-8, 2.580E-8, 2.539E-8, 2.796E-8, 2.871E-8, 2.598E-8, 2.586E-8, 2.801E-8, 2.830E-8, 2.633E-8, 2.609E-8, 2.894E-8, 2.924E-8, 2.633E-8, 2.464E-8, 2.778E-8, 2.894E-8, 2.673E-8, 2.475E-8, 2.737E-8, 2.796E-8, 2.679E-8, 2.394E-8, 2.708E-8, 2.714E-8, 2.644E-8, 2.347E-8, 2.563E-8, 2.493E-8, 2.388E-8, 2.179E-8, 2.440E-8, 2.504E-8, 2.295E-8, 2.033E-8, 2.283E-8, 2.295E-8, 2.155E-8};  //<-- REPLACE THIS VALUES WITH YOUR OWN!
//float v_ir_off_comp[64];  //I'm going to merge v_ir_off_comp calculation into v_ir_tgc_comp equation. It's not required anywhere else, so I'll save 256 bytes of SRAM doing this.
float v_ir_tgc_comp[64];
//float v_ir_comp[64]; //removed to save SRAM, in my case v_ir_comp == v_ir_tgc_comp



void config_MLX90620_Hz(int Hz){
  byte Hz_LSB;
  switch(Hz){
    case 0:
      Hz_LSB = B00001111;
      break;
    case 1:
      Hz_LSB = B00001110;
      break;
    case 2:
      Hz_LSB = B00001101;
      break;
    case 4:
      Hz_LSB = B00001100;
      break;
    case 8:
      Hz_LSB = B00001011;
      break;
    case 16:
      Hz_LSB = B00001010;
      break;
    case 32:
      Hz_LSB = B00001001;
      break;
    default:
      Hz_LSB = B00001110;
  }
  i2c_start_wait(0xC0);
  i2c_write(0x03);    
  i2c_write((byte)Hz_LSB-0x55);
  i2c_write(Hz_LSB);  
  i2c_write(0x1F);  
  i2c_write(0x74);  
  i2c_stop();
}

void read_EEPROM_MLX90620(){
  byte EEPROM_DATA[256];
  i2c_start_wait(0xA0);    
  i2c_write(0x00);
  i2c_rep_start(0xA1);
  for(int i=0;i<=255;i++){
    EEPROM_DATA[i] = i2c_readAck();
  }
  i2c_stop();
  varInitialization(EEPROM_DATA);
  write_trimming_value(EEPROM_DATA[247]);
}

void write_trimming_value(byte val){
  i2c_start_wait(0xC0);
  i2c_write(0x04);
  i2c_write((byte)val-0xAA);
  i2c_write(val);  
  i2c_write(0x56);  
  i2c_write(0x00);  
  i2c_stop();
}

void calculate_TA(){
  ta = (-k_t1 + sqrt(square(k_t1) - (4 * k_t2 * (v_th - (float)PTAT))))/(2*k_t2) + 25; //it's much more simple now, isn't it? :)
}

void calculate_TO(){
  float v_cp_off_comp = (float) CPIX - (a_cp + (b_cp/pow(2, b_i_scale)) * (ta - 25)); //this is needed only during the to calculation, so I declare it here.
  
  for (int i=0; i<64; i++){
    v_ir_tgc_comp[i] = IRDATA[i] - (a_ij[i] + (float)(b_ij[i]/pow(2, b_i_scale)) * (ta - 25)) - (((float)tgc/32)*v_cp_off_comp);
    //v_ir_comp[i]= v_ir_tgc_comp[i] / emissivity; //removed to save SRAM, since emissivity in my case is equal to 1.
    //temperatures[i] = sqrt(sqrt((v_ir_comp[i]/alpha_ij[i]) + pow((ta + 273.15),4))) - 273.15;
    temperatures[i] = sqrt(sqrt((v_ir_tgc_comp[i]/alpha_ij[i]) + pow((ta + 273.15),4))) - 273.15; //edited to work with v_ir_tgc_comp instead of v_ir_comp
  }
}


void read_IR_ALL_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);      
  i2c_write(0x00);    
  i2c_write(0x01);      
  i2c_write(0x40);      
  i2c_rep_start(0xC1);
  for(int i=0;i<=63;i++){
    PIX_LSB = i2c_readAck();
    PIX_MSB = i2c_readAck();
    IRDATA[i] = (PIX_MSB << 8) + PIX_LSB;
  }
  i2c_stop();
}

void read_PTAT_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x90);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  PTAT_LSB = i2c_readAck();
  PTAT_MSB = i2c_readAck();
  i2c_stop();
  PTAT = ((unsigned int)PTAT_MSB << 8) + PTAT_LSB;
}

void read_CPIX_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x91);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CPIX_LSB = i2c_readAck();
  CPIX_MSB = i2c_readAck();
  i2c_stop();
  CPIX = (CPIX_MSB << 8) + CPIX_LSB;
}

void read_Config_Reg_MLX90620(){
  i2c_start_wait(0xC0);
  i2c_write(0x02);
  i2c_write(0x92);
  i2c_write(0x00);
  i2c_write(0x01);
  i2c_rep_start(0xC1);
  CFG_LSB = i2c_readAck();
  CFG_MSB = i2c_readAck();
  i2c_stop();
}

void check_Config_Reg_MLX90620(){
  read_Config_Reg_MLX90620();
  if ((!CFG_MSB & 0x04) == 0x04){
    config_MLX90620_Hz(freq);
  }
}

void varInitialization(byte EEPROM_DATA[]){
  v_th = (EEPROM_DATA[219] <<8) + EEPROM_DATA[218];
  k_t1 = ((EEPROM_DATA[221] <<8) + EEPROM_DATA[220])/1024.0;
  k_t2 =((EEPROM_DATA[223] <<8) + EEPROM_DATA[222])/1048576.0;
  
  a_cp = EEPROM_DATA[212];
  if(a_cp > 127){
    a_cp = a_cp - 256;
  }
  b_cp = EEPROM_DATA[213];
  if(b_cp > 127){
    b_cp = b_cp - 256;
  }
  tgc = EEPROM_DATA[216];
  if(tgc > 127){
    tgc = tgc - 256;
  }

  b_i_scale = EEPROM_DATA[217];

  emissivity = (((unsigned int)EEPROM_DATA[229] << 8) + EEPROM_DATA[228])/32768.0;

  for(int i=0;i<=63;i++){
    a_ij[i] = EEPROM_DATA[i];
    if(a_ij[i] > 127){
      a_ij[i] = a_ij[i] - 256;
    }
    b_ij[i] = EEPROM_DATA[64+i];
    if(b_ij[i] > 127){
      b_ij[i] = b_ij[i] - 256;
    }
  }
}

void Temperatures_Serial_Transmit(){
  for(int i=0;i<=63;i++){
    Serial.println(temperatures[i]);
  }
}

void setup(){
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  i2c_init();
  PORTC = (1 << PORTC4) | (1 << PORTC5);
  delay(5);
  read_EEPROM_MLX90620();
  config_MLX90620_Hz(freq);
}

void loop(){
  if(count ==0){ //TA refresh is slower than the pixel readings, I'll read the values and computate them not every loop.
    read_PTAT_Reg_MLX90620();
    calculate_TA();
    check_Config_Reg_MLX90620();
  }
  count++;
  if(count >=16){
    count = 0;
  }
  read_IR_ALL_MLX90620();
  read_CPIX_Reg_MLX90620();
  calculate_TO();
  Temperatures_Serial_Transmit();
}



These small modifications made a substantial difference in the final results. I had a lot of noise before, and now it's much more precise in every reading, almost perfect!
I also got a substantial framerate improvement, from the initial 12Hz (with 50KHz I2C) to 20 Hz now.
It is possible to speed up everything by making a computer do all the calculation, and passing just the IRDATA + CPIX and PTAT info. I reached 87Hz that way!
I think this is everything, I hope it will be useful for at least someone!
Bye,
Alessandro.
Logged

0
Offline Offline
Jr. Member
**
Karma: 1
Posts: 77
Arduino rocks
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Thanks a lot for your work, IlBaboomba !
I finished my code in a short time and all the recommendations you made are completely right.
Am I allowed to replace the code in my first post with yours ?
Logged

0
Offline Offline
Newbie
*
Karma: 1
Posts: 12
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

yes, of course smiley
Logged

0
Offline Offline
Newbie
*
Karma: 0
Posts: 24
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

HI,

i have found an distributor for Germany. Which type of sensor of the MLX 90620 should i use the 60° or 40°?

Thanks a lot.

Cu kami
Logged

Pages: [1] 2 3 4   Go Up
Jump to: