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.
#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.
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.
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.
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.
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.
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.
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)
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
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.
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.