Driver for the Melexis MLX90620 infrared temperature sensor array. 64 pixels in a 16 x 4 format. Updated driver for KL25Z

Dependents:   lpc812_mlx90620

Melexis MLX90620 home page:

Note: I had to delete the original repository and start a new one due to issues I had with publishing. It should be at Rev 2, but it starts fresh at Rev 0. The reason for the new revision was basic ic2 command cleanup in order to work with the KL25Z board.

The datasheet for the MLX90620 can be found here: http://www.melexis.com/MLX90620

Note that the conversion-start process that is used in the mbed driver, the "Step" mode in section 9.3.2, is not longer in the datasheet. It was last described in the datasheet dated 20120620. Step mode was bit 6 of the Configuration Register 0x92, section 8.2.2.1, Table 10. Melexis claims that the temperature readings were not as accurate as the back-to-back-to-back "Normal" continous conversion method.

However, in my main.cpp code, I do start another conversion immediately after the current conversion completes and all of the sensor array RAM values have been read. Then, I perform all of the floating point math and update the display while the new conversion is taking place. At 4 conversions per second using the KL25Z board, there is about 110mS of idle time before the new conversion is ready to be read out. Reading bits 8 and 9 in the Configuration Register tell you when it is time to read the sensor array RAM values.

The datasheet says the the MLX90620 needs to operate at 2.6V for best performance (certain versions can operate up to 3.0V). Use an LDO from 3.3V to generate 2.6V. Though you could use a regular diode to drop 3.3V to 2.6V, it is not consistent from diode-to-diode and the voltage drop will vary with temperature. I highly recommend using an LDO. And don't forget to use bypass caps!!! BTW: Though the device VCC is limited to 2.6V, the i2c I/O pins can operate at 3.3V.

With an mbed1768, the MLX90620 operates with an i2c frequency of 400KHz and 4 feet of 4-wire cable. I have not tried 4 feet on the KL25Z, but it does work at 1 foot. The i2c pullup resistors are 2.2k.

In the overall program that I use for the MLX90620 (to be published), an mbed1768 can run with a USB Serial output of 921600 baud, whereas the KL25Z is limited to 115200 baud. Anything faster on the KL25Z, output characters get dropped.

...kevin

Revision:
0:a86b8d0294ee
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MLX90620.cpp	Mon Jul 29 23:32:09 2013 +0000
@@ -0,0 +1,262 @@
+//NOTE:  "Step Measurement Mode" was removed from new MLX90620 data sheet, page 22 dated Sept 19 2012
+//       which is used in this implementation
+
+#include "mbed.h"
+#include "MLX90620.h"
+
+extern char* EEbuf;
+extern char* RamBuf;
+extern char* RamCmmd;                       //must reside in main.cpp
+
+const int PTATSENS  = 0x90;                 //ram offset = 0x90, PTAT sensor reading, 16b
+const int TGCSENS   = 0x91;                 //ram offset = 0x91, TGC sensor reading, 16b
+const int MLXCONFIG = 0x92;                 //ram offset = 0x92, config register, 16b
+const int MLXTRIM   = 0x93;                 //ram offset = 0x93, oscillator trim, lsb>6b of 16b
+const int EETRIM    = 0xf7;                 //eep offset = 0xf0, 1 byte, oscillator trim value
+
+unsigned short Config = 0;                  //MLX90620 configuration register
+unsigned short OscTrim = 0;                 //MLX90620 oscillator trim register
+unsigned short PtatD = 0;                   //MLX90620 PTAT data register
+short VCP = 0;                              //VCP / TGC
+short Vth25X = 0;
+float TaXX = 0.0;
+
+//For To
+signed char AcpX = 0;
+signed char BcpX = 0;
+float Kt1fX = 0.0;
+float Kt2fX = 0.0;
+signed char TGCX = 0;
+char BiScaleX = 0;
+unsigned short theta0X = 0;
+char theta0ScaleX = 0;
+char deltaThetaScaleX = 0;
+unsigned short elipsonX = 0;
+signed char AiPixelX = 0;                   //eeprom address range 0x00 - 0x3f
+signed char BiPixelX = 0;                   //eeprom address range 0x40 - 0x7f
+char dThetaPixelX = 0;                      //eeprom address range 0x80 - 0xbf
+short VirPixelX = 0;
+double TempPxlX = 0;
+const int TOINDEX =  0xd4;                  //eep offset = 0xD4 and 0xE0 (0xD4 + 0x0C), 6 bytes + 6 bytes
+const int TAINDEX =  0xda;                  //eep offset = 0xDA, 6 bytes
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+// Constructor
+
+MLX90620::MLX90620(PinName sda, PinName scl, const char* name) : _i2c(sda, scl){
+    _i2c.frequency(400000);                 //set up i2c speed
+    _i2c.stop();
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//copy contents of EEPROM inside the MLX90620 into a local buffer.  Data is used for lookup tables and parameters
+
+int MLX90620::LoadEEPROM() {
+    //clear out buffer first
+    for(int i = 0; i < 256; i++) {
+        EEbuf[i] = 0;
+    }
+    
+    //load the entire EEPROM
+    EEbuf[0] = 0;
+    if(!_i2c.write(0xa0, EEbuf, 1, true)) {                //0 returned is ok
+        _i2c.read(0xa0, EEbuf, 256);                       //load contents of EEPROM
+    } else {
+        _i2c.stop();
+        return(1);
+    }
+    return(0);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//copy oscillator offset from EEbuf to MLX90620 (MS byte = 0)
+
+int MLX90620::SetOscTrimReg() { 
+    RamCmmd[0] = 4;                          //command
+    RamCmmd[1] = EEbuf[EETRIM] - 0xaa;       //LS byte check
+    RamCmmd[2] = EEbuf[EETRIM];              //oscillator trim value
+    RamCmmd[3] = 0x100 - 0xaa;                   //MS byte check
+    RamCmmd[4] = 0;                          //MS byte = 0
+    int r = _i2c.write(0xc0, RamCmmd, 5, false);
+    return(r);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//get oscillator offset register from MLX90620
+
+unsigned short MLX90620::GetOscTrimReg() { 
+    RamCmmd[0] = 2;                          //command
+    RamCmmd[1] = MLXTRIM;                    //address of register
+    RamCmmd[2] = 0;                          //address step
+    RamCmmd[3] = 1;                          //# of reads
+    _i2c.write(0xc0, RamCmmd, 4, true);  
+    _i2c.read(0xc0, RamCmmd, 2);
+    OscTrim = (RamCmmd[1] << 8) + RamCmmd[0];
+    return(OscTrim);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//initialize the configuration register
+//******* NOTE: Step measurement mode was removed from new data sheet dated   Sept 19 2012
+
+int MLX90620::SetConfigReg() {   
+    RamCmmd[0] = 3;                          //command
+    RamCmmd[1] = 0x14c - 0x55;                //LS byte check
+    RamCmmd[2] = 0x4c;                       //LS config value, step meas mode, 4Hz array  *******
+    RamCmmd[3] = 0x5c - 0x55;                //MS byte check
+    RamCmmd[4] = 0x5c;                       //MS config value, 8Hz Ta, 400k i2c
+    int r = _i2c.write(0xc0, RamCmmd, 5, false);
+    return(r);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//get configuration register from MLX90620
+
+unsigned short MLX90620::GetConfigReg() { 
+    RamCmmd[0] = 2;                          //command
+    RamCmmd[1] = MLXCONFIG;                  //address of register
+    RamCmmd[2] = 0;                          //address step
+    RamCmmd[3] = 1;                          //# of reads
+    _i2c.write(0xc0, RamCmmd, 4, true); 
+    _i2c.read(0xc0, RamCmmd, 2);
+    Config = (RamCmmd[1] << 8) + RamCmmd[0];
+    return(Config);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//get PTAT register from MLX90620
+
+unsigned short MLX90620::GetPTATReg() { 
+    RamCmmd[0] = 2;                          //command
+    RamCmmd[1] = PTATSENS;                   //address of register
+    RamCmmd[2] = 0;                          //address step
+    RamCmmd[3] = 1;                          //# of reads
+    _i2c.write(0xc0, RamCmmd, 4, true); 
+    _i2c.read(0xc0, RamCmmd, 2);
+    PtatD = (RamCmmd[1] << 8) + RamCmmd[0];
+    return(PtatD);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//get VCP / TGC register from MLX90620
+
+short MLX90620::GetTGCReg() { 
+    RamCmmd[0] = 2;                          //command
+    RamCmmd[1] = TGCSENS;                    //address of register
+    RamCmmd[2] = 0;                          //address step
+    RamCmmd[3] = 1;                          //# of reads
+    _i2c.write(0xc0, RamCmmd, 4, true);
+    _i2c.read(0xc0, RamCmmd, 2); 
+    VCP = (RamCmmd[1] << 8) + RamCmmd[0];
+    return(VCP);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//get RAM dump from MLX90620
+bool firstDump = false;
+
+void MLX90620::LoadMLXRam() { 
+    RamCmmd[0] = 2;                          //command
+    RamCmmd[1] = 0;                          //start address
+    RamCmmd[2] = 1;                          //address step
+    RamCmmd[3] = 0x40;                       //# of reads
+    _i2c.write(0xc0, RamCmmd, 4, true);
+    _i2c.read(0xc0, RamBuf, 0x80); 
+    PtatD = MLX90620::GetPTATReg();
+    VCP = MLX90620::GetTGCReg();
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+//start measurement MLX90620
+
+int MLX90620::StartMeasurement() {   
+    RamCmmd[0] = 1;                         //command
+    RamCmmd[1] = 8;                         //address of config register
+
+    int r = _i2c.write(0xc0, RamCmmd, 2, false);
+    return(r);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+// Initial Calculations for Ta and To
+
+float MLX90620::GetDieTemp() {
+    PtatD = MLX90620::GetPTATReg();
+    float TaX = (-Kt1fX + sqrtf(powf(Kt1fX, 2.0) - 4.0 * Kt2fX * (Vth25X - PtatD)))/(2.0 * Kt2fX) + 25.0;
+    return(TaX);
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+// Initial Calculations for Ta and To
+
+void MLX90620::CalcTa_To() {
+    //Calculate Ta first
+    Vth25X = (EEbuf[TAINDEX + 1] << 8) + EEbuf[TAINDEX + 0];
+    short Kt1   = (EEbuf[TAINDEX + 3] << 8) + EEbuf[TAINDEX + 2];
+    short Kt2   = (EEbuf[TAINDEX + 5] << 8) + EEbuf[TAINDEX + 4];
+    Kt1fX = Kt1 / 1024.0;
+    Kt2fX = Kt2 / 1048576.0;
+    TaXX = MLX90620::GetDieTemp();
+    
+    //Calculate To
+    AcpX = EEbuf[TOINDEX + 0];
+    BcpX = EEbuf[TOINDEX + 1];
+//    unsigned short thetaCPX = (EEbuf[TOINDEX + 3] << 8) + EEbuf[TOINDEX + 2];
+    TGCX = EEbuf[TOINDEX + 4];
+    BiScaleX = EEbuf[TOINDEX + 5];
+    theta0X = (EEbuf[TOINDEX + 13] << 8) + EEbuf[TOINDEX + 12];
+    theta0ScaleX = EEbuf[TOINDEX + 14];
+    deltaThetaScaleX = EEbuf[TOINDEX + 15];
+    elipsonX = (EEbuf[TOINDEX + 17] << 8) + EEbuf[TOINDEX + 16];
+/*
+        printf("Vth(25) = %6d 0x%x\nTa1     = %6d 0x%x\nTa2     = %6d 0x%x\n", Vth25X, Vth25X, Kt1, Kt1, Kt2, Kt2);
+        printf("Kt1fX   = %f\nKt2fX   = %f\nTaXX    = %f\n\n", Kt1fX, Kt2fX, TaXX);
+        printf("Acp     = %6d 0x%x\nBcp     = %6d 0x%x\nThCP    = %6d 0x%x\n", AcpX, AcpX, BcpX, BcpX, thetaCPX, thetaCPX); 
+        printf("TGC     = %6d 0x%x\nBiS     = %6d 0x%x\nTh0     = %6d 0x%x\n", TGCX, TGCX, BiScaleX, BiScaleX, theta0X, theta0X);   
+        printf("T0s     = %6d 0x%x\nDts     = %6d 0x%x\nelip    = %6d 0x%x\n\n", theta0ScaleX, theta0ScaleX, deltaThetaScaleX, deltaThetaScaleX, elipsonX, elipsonX);   
+*/
+}
+
+//--------------------------------------------------------------------------------------------------------------------------------------//
+// Pixel Temperature Calculation
+
+double MLX90620::CalcPixel(int Pixel) {
+    AiPixelX = EEbuf[Pixel];                                        //eeprom address range 0x00 - 0x3f
+    BiPixelX = EEbuf[Pixel + 0x40];                                 //eeprom address range 0x40 - 0x7f
+    dThetaPixelX = EEbuf[Pixel + 0x80];                             //eeprom address range 0x08 - 0xbf
+    VirPixelX = (RamBuf[Pixel * 2 + 1] << 8) + RamBuf[Pixel * 2];   //ram address range 0x000 - 0x08f, 16b
+    float Vcp_off_comp = VCP - (AcpX + BcpX / powf(2.0,BiScaleX) * (TaXX - 25.0));
+    float VirPixel_off_comp = VirPixelX - (AiPixelX + BiPixelX / powf(2.0,BiScaleX) * (TaXX - 25.0));
+    float VirPixel_off_comp2 = (float(AiPixelX) + float(BiPixelX) / float(1 << BiScaleX) * (TaXX - 25.0));
+    VirPixel_off_comp2 = VirPixelX - VirPixel_off_comp2;
+    float VirPixel_tgc_comp = VirPixel_off_comp - TGCX / 32.0 * Vcp_off_comp;
+    float elipsonf = elipsonX / 32768.0;
+    float VirPixel_comp = VirPixel_tgc_comp / elipsonf;
+    double theta28 = theta0X / powf(2.0, theta0ScaleX) + dThetaPixelX / powf(2.0, deltaThetaScaleX);
+    double TempPxl = powf((VirPixel_comp / theta28 + powf((TaXX + 273.15), 4.0)), (1.0 / 4.0)) - 273.15;
+/*
+        printf("pixel = %d\n", Pixel);
+        printf("Acp   = %d\nBcp   = %d\nBiS   = %d\n", AcpX, BcpX, BiScaleX);
+        printf("Vcp   = %d\neps   = %d\nTGC   = %d\n", VCP, elipsonX, TGCX);
+        printf("Vcp_off_comp      = %f\n", Vcp_off_comp);
+        printf("VirPixel_off_comp        = %f\n", VirPixel_off_comp);
+        printf("VirPixel                 = %d\n", VirPixelX);
+        printf("AiPixel                  = %d\n", AiPixelX);
+        printf("BiPixel                  = %d\n", BiPixelX);
+        printf("BiScale                  = %d\n", BiScaleX);
+        printf("2^BiScale                = %f\n", (powf(2.0,BiScaleX)));
+        printf("1 << BiScale             = %d\n", (1 << BiScaleX));
+        printf("Ta-25.0                  = %f\n", (TaXX - 25.0));
+        printf("BiPix/2^BiScale          = %f\n", (BiPixelX / powf(2.0,BiScaleX)));
+        printf("AiP+BiP/2^BiScale)*(Ta-25= %f\n", (AiPixelX + BiPixelX / powf(2.0,BiScaleX) * (TaXX - 25.0)));
+        printf("VirPixel_off_comp again  = %f\n", (VirPixelX - (AiPixelX + BiPixelX / powf(2.0,BiScaleX) * (TaXX - 25.0))));
+        printf("VirPixel_off_comp2 step  = %f\n", VirPixel_off_comp2);
+        printf("VirPixel_tgc_comp        = %f\n",  VirPixel_tgc_comp);
+        printf("elipsonf                 = %f\n", elipsonf);
+        printf("VirPixel_comp            = %f\n",  VirPixel_comp);
+        printf("theta28                  = %f  << double print problem\n", (theta28 * 100000000.0));  //<<< can't print a double
+        printf("TempPxl                  = %f\n",  TempPxl);
+*/
+    return(TempPxl);
+}
+