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

MLX90620.cpp

Committer:
loopsva
Date:
2013-07-29
Revision:
0:a86b8d0294ee

File content as of revision 0:a86b8d0294ee:

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