-
video of raw data from sensor read of code
08/24/2019 at 01:37 • 0 commentsshows data ouput raw to terminal. there are 48 rows, and each row is a copy of an other.
this was a test with my hand across the front of the sensor. it is hard to remember what i was doing, but most likely it was a count from 1 to 5.
-
now works with about 5k of ram usage on teensy, *sort of works again uno 790 bytes ram
08/21/2019 at 18:54 • 0 commentsi've rewritten the memory management of the code for the mlx90640, so it uses about 5k of ram instead of 20k for teensy 3.1/3.2; for atmega and UNO it uses 790 bytes. functions don't use much ram.
https://github.com/jamesdanielv/thermal_cam_mlx90640/tree/master/Updates
look for code in updates folder. it should compile for teensy. if you want to compare to old method
change NEW_METHOD false in Z_MemManagment.h . i've upped the teensy i2c speed to 1mhz. (don't use above 1mhz or you could accidentally reprogram device.) so new method reads faster. it currently is slower to read at same i2c clock as normal, however this will be fixed soon. many of the ram calls are in sequential order, and a cache will speed up access by about 8times because of the overhead of individual requests over i2c bus.
as for the verified code working on the UNO, there is still some issues with the double floats used for original code for the 32 bit arm processor. with some simple tricks it will work on arduino calibrated, and with caching it should be almost as fast as arm.
another note to the fact that it can work on uno, is since i utilize storage on the sensor, data does not need to be moved around function to function, and stored and read from multiple times. this greatly reduces amount of work on the processor. also the code spent a lot of time waiting for large data transfes, and waited for data to be collected by sensor twice, so most of its time it was idle.
the new method requires less waiting, and less ram. a few optimizations are going to be added to deal with complex math
first is a simple replacement for pow, it is ok in this code case because base exponents are 2 and 3 and 4 only. so the time it takes to process is minimal and this configuration is efficient enough
float SimplePow(float base, uint8_t exponent)
{//we create or own low memory multiplierfloat tempbase=base;if (exponent >0){for (uint8_t i=1;i< exponent;i++){////we have base number,we start at 1 not 0, because 1 is already done
tempbase=tempbase*base;}}else{tempbase=1;}//we multiply unless exponent is 0 then result is 1
return tempbase;//we return result
}as for sqrt functions i'm switching over to the fast method:
float Q_rsqrt( float number ) //a good enough square root method.
{//https://en.wikipedia.org/wiki/Fast_inverse_square_root
long i;float x2, y;const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}as for pow for areas that use 2^1 to 2^16 i', switching to a table
these changes will be implemented once i fix the double float issues of the UNO.
UNO range is 4bytes of storage for floats, teensy 3.2 is 8bytes. the range can be compensated for, or i will create a library to process 64 bit floats. more to come!
-
beginning reduction of memory usage again. this time with verification
08/13/2019 at 12:31 • 0 commentsi have moved the feature of NEW_METHOD true to memCache.h folder, when it is set to true the code now uses 5k less of data here is how.
i have changed the eeprom data either method to get the data from flash memory. the old mem management still collects data into pixel matrix, of float, and int.
i removed the need for these variables so far. (i made code not use pointers, and i switched code that reads the array to a function that requests data from the cell. since data is in progmem it is almost as fast as a ram read.
float alpha[768];
int16_t offset[768]; //we dont need this anymoreill show the old method of getting offset (but after i removed the struct and pointer
void ExtractOffsetParameters( )
{
int occRow[24];
int occColumn[32];
int p = 0;
int16_t offsetRef;
uint8_t occRowScale;
uint8_t occColumnScale;
uint8_t occRemScale;
occRemScale = (eeDataGetStoredInLocalEPROM(16) & 0x000F);
occColumnScale = (eeDataGetStoredInLocalEPROM(16) & 0x00F0) >> 4;
occRowScale = (eeDataGetStoredInLocalEPROM(16) & 0x0F00) >> 8;
offsetRef = eeDataGetStoredInLocalEPROM(17);
if (offsetRef > 32767)
{
offsetRef = offsetRef - 65536;
}
for(int i = 0; i < 6; i++)
{
p = i * 4;
occRow[p + 0] = (eeDataGetStoredInLocalEPROM(18 +i) & 0x000F);
occRow[p + 1] = (eeDataGetStoredInLocalEPROM(18 +i) & 0x00F0) >> 4;
occRow[p + 2] = (eeDataGetStoredInLocalEPROM(18 +i) & 0x0F00) >> 8;
occRow[p + 3] = (eeDataGetStoredInLocalEPROM(18 +i) & 0xF000) >> 12;
}
for(int i = 0; i < 24; i++)
{
if (occRow[i] > 7)
{
occRow[i] = occRow[i] - 16;
}
}
for(int i = 0; i < 8; i++)
{
p = i * 4;
occColumn[p + 0] = (eeDataGetStoredInLocalEPROM(24 +i) & 0x000F);
occColumn[p + 1] = (eeDataGetStoredInLocalEPROM(24 +i)& 0x00F0) >> 4;
occColumn[p + 2] = (eeDataGetStoredInLocalEPROM(24 +i) & 0x0F00) >> 8;
occColumn[p + 3] = (eeDataGetStoredInLocalEPROM(24 +i) & 0xF000) >> 12;
}
for(int i = 0; i < 32; i ++)
{
if (occColumn[i] > 7)
{
occColumn[i] = occColumn[i] - 16;
}
}for(int i = 0; i < 24; i++)
{
for(int j = 0; j < 32; j ++)
{
p = 32 * i +j;
offset[p] = (eeDataGetStoredInLocalEPROM(64 +p) & 0xFC00) >> 10;
if (offset[p] > 31)
{
offset[p] = offset[p] - 64;
}
offset[p] = offset[p]*(1 << occRemScale);
offset[p] = (offsetRef + (occRow[i] << occRowScale) + (occColumn[j] << occColumnScale) + offset[p]);
}
}
}new method
int16_t ExtractOffsetParametersRawPerPixel(uint16_t value )
{//we get row and collumb data and process from there
uint8_t col= (value&31) ;//we take bottom 5 bits and add as we use 1-32
uint8_t row=(value&65504)>>5;;//this should have upper bits only
//int occRow[24];
//int occColumn[32];
//we only need to store one word each as conversion done on the fly
int occRow;
int occColumn;
uint8_t p = 0;
int16_t offsetRef;
uint8_t occRowScale;
uint8_t occColumnScale;
uint8_t occRemScale;
occRemScale = (eeDataGetStoredInLocalEPROM(16) & 0x000F);
occColumnScale = (eeDataGetStoredInLocalEPROM(16) & 0x00F0) >> 4;
occRowScale = (eeDataGetStoredInLocalEPROM(16) & 0x0F00) >> 8;
offsetRef = eeDataGetStoredInLocalEPROM(17);//11hex
if (offsetRef > 32767)
{
offsetRef = offsetRef - 65536;
}
//we have raw number for offset now
p=row&3;//we have 0-3 for data bits
uint8_t i=row>>2;
if (p==0) { occRow = (eeDataGetStoredInLocalEPROM(18 +i) & 0x000F);}
if (p==1) { occRow = (eeDataGetStoredInLocalEPROM(18 +i) & 0x00F0) >> 4;}
if (p==2) { occRow = (eeDataGetStoredInLocalEPROM(18 +i) & 0x0F00) >> 8;}
if (p==3) { occRow = (eeDataGetStoredInLocalEPROM(18 +i) & 0xF000) >> 12;}
if (occRow > 7)
{
occRow = occRow - 16;
}
p=col&3;//we have 0-3 for data bits
i=col>>2;
if (p==0) { occColumn = (eeDataGetStoredInLocalEPROM(24 +i) & 0x000F);}
if (p==1) { occColumn = (eeDataGetStoredInLocalEPROM(24 +i)& 0x00F0) >> 4;}
if (p==2) { occColumn = (eeDataGetStoredInLocalEPROM(24 +i) & 0x0F00) >> 8;}
if (p==3) { occColumn = (eeDataGetStoredInLocalEPROM(24 +i) & 0xF000) >> 12;}
if (occColumn > 7)
{
occColumn = occColumn- 16;
}
// for(int i = 0; i < 24; i++)
// {
// for(int j = 0; j < 32; j ++)
// {
// p = 32 * i +j;//this can be directly value
;//we have direct cell number
// offset[p] = (eeDataGetStoredInLocalEPROM(64 +p) & 0xFC00) >> 10;int16_t temp=(eeDataGetStoredInLocalEPROM(64 +value) & 0xFC00) >> 10;
if (temp > 31)
{
temp = temp- 64;
}
temp =temp*(1 << occRemScale);
temp = (offsetRef + (occRow << occRowScale) + (occColumn << occColumnScale) + temp);
// }
// }
return temp;
}the difference is now rather than pointing to a storeage in an array, i'm running a function that extracts the data from progmem.
-
double sample resolution of sensor to 64x48
08/08/2019 at 19:04 • 0 commentsi've been working on other projects, and now have time again to go back to this one. there are several things that i still need to address, and they all require verification with original functions.
i'm using a 3.2 teensy (arm with 64k or ram) to verify modified equations and methods and check them from the original. https://github.com/jamesdanielv/thermal_cam_mlx90640 look in updates folder. this code had more features than original code such as changing from continuous to static mode, and ability of changing hz to 64hz (however code seems to only work to 32hz, think it is limitation of speed of i2c, and method of data extraction)
i have a static value of NEWMETHOD true, or NEWMETHOD false to determine if code is to use new methods, or old methods. (it is currently being worked on and code is being migrated over )
one of the new features is the ability to output to terminal at 64x48 resolution. so you can test if sensor is working.
there are several reasons mlx90640 sensor dev has issues, primarily it is the typo's and errors in documentation, the documentation includes examples show hex swaps of wrong bytes, and addresses that even in the same sample are of different ram or rom locations for pulling specific values. also it is not clear that some of the uint16_t conversions are of data stored into a int, and the conversion to correct the data is in some cases inverted. so i am relying on testing of functional (but overly complex code) to compare all data sets to my code.
for example you can take cal data sore it in an uint16_t and move values into a float or a signed word
or take cal data and move it directly into a signed word, and modify values to get correct data
either method can get data from cal storage in eprom, just using the incorrect method will invert the answer.
i will eventually reduce memory usage down to about 1k of ram.
the way the original code processes data is overkill for simple processors.
my goal is to first get it to work on atmega 1280 with 8k of ram, and then on Arduino uno, and with 4x resolution! (128x96)
for example here is double resolution using the same amount of memory as original code.
-
optimizing temp readings of sensors. reading one line at a time a lot faster because of i2c overhead
12/16/2018 at 14:49 • 0 commentsi2c overhead is a lot per request of data from mlx90640 sensor
here is i2c protocol
from what i understand, is that after address is sent to sensor, and read mode sent, as long as no stop command is issued, each data space is read back one byte at a time and increments the address after each data byte transferred.
this means that requesting more than one byte at a time is how i can make more efficient use of the i2c bus, also it will work up to 800khz on arduino.
examples of efficiency are here
i need to request at least 10 memory addresses of data for each thermal pixel
overhead with no bytes is 3140 microseconds. i will work on reducing this. but it shows over head for requests
requesting 1 at a time is with total processing time of 4900-3140= 1760 microseconds per cell read
requesting 8 at a time is with total processing time of 9448-3140=6408/8= 801 microseconds
requesting 32 at a time is 28132-3140 =25092/32= 784 microseconds per cell read time
time of return diminishes after about 8 bytes at a time,
for example using an atmega chip with 8k of ram,
and reading all 768 cells for thermal data at a time
requesting 768 604568-3040=601528/768=783 microseconds
so reading up to 32 at a time for connivence is best, but 8 at a time is reasonable as well with reading only taking 801 microseconds.
there are several tricks to improve performance, this just shows that optimum reading of data peaks at about 32 sensors reading at a time. this is about 640 bytes of data and does not include calculations time, you can see that that overhead has been removed from calculations.
there are some efficiencies in the fact that at least 70% of calculations are applied to all sensor, and 20% are applied to at least 2 or 4 cells for calibration. leaving only 10% of math that needs to truly be done independently, and some of that is for noise and hysteresis.
i guess what im getting at is that 5000 microseconds read time per cell will be quickly reduced to at least half that, or more. it could be as low as 500microseconds per cell.
and the big kicker, we only need 3-5 cell reads to a tempurature, rest of data can be raw values without the To calculations to show color changes, we only need 3-5 temp readings for a reference so we can figure out color range for displaying images, and show temp values of high, low, and center.
-
array of 768 sensors seems to work with temp in celsius and calibrated
12/09/2018 at 19:30 • 0 commentshere is a 32x24 sensor read for terminal. seems to be calibrated, and using 808 bytes of ram and working on Arduino!
https://github.com/jamesdanielv/thermal_cam_mlx90640/blob/master/32x24calibratedsensorRead.zip
not all rows fit on screen but you can see the degrees in . currently each sensor read routine is about 5000 microseconds. this is far from optimized yet, it just seems to be working.
temp seem to be calibrated, will test and put it into a color map to verify.
the only wrench for this is i seem to only get these values when i have sensor run in continuous mode, not single shot mode. this causes a small error in gain calculations because vdd varies +/- 0.01 v between reads.
i will try to resolve why, because some of these measurements are a frame behind the others, however the rate of change for sensor may not mean it makes much difference, especially with multiple samples to reduce noise, but i will try to fix it either by getting single shot mode fixed (possible bit setting) or have code that figures out how old sensor data is and apply correct Vdd sample to calculate temp with more precision. the example i list above is with 19 bits of resolution so in theory with a resolution of
-40°C-300°C ~340 range, and 524288 (19bit) levels sensor is capable of sensing difference of 0.000648 degrees. amg8833 is 0.025 degrees. the noise suppression is superior in the mlx90640 sensor making it excellent for thermal imaging at a distance!
-
mlx90640 sensor reads degrees on Arduino! uses 852 bytes ram.
12/09/2018 at 17:57 • 0 commentshttps://github.com/jamesdanielv/thermal_cam_mlx90640/blob/master/MLX906040_1tempcell.zip
this is a test code setup that reads degrees c on arduino. sketch uses about 20k , and it uses 852 bytes of ram. code is not optimized or shrunk in size yet. it is just working. next i will get an array of sensor data to be output to a ram buffer so it will be compatible with my amg8833 code. the goal is to pretty much allow my drivers to replace the amg8833 code as a drop in replacement and build in features that allow more resolution detail.
a few things. this code is only a test code and it only converts 1 cell to a degree in temp, but it can do it for any cell.
CalculateTo(12,10); for example stores temp data in float value To, you can change CalculateTo(x,y) to any pixel value from 0,0 to 32,24 the full resolution of sensor.
CalculateTo does not return value yet, it is stored in To
there may be a higher amount of noise from this method as it reads and stores
1 cell at a time. this can be corrected in 64 samples per second mode, by taking 3 samples and averaging them over a short time.
don't worry i will reduce noise, and fix an errors eventually. this is just an update for those that want to try sensor on Arduino.
next code will have settings to change from 1cell to reading an array of cells for example code.
-
extract gain values done as well using few bytes
12/03/2018 at 08:44 • 0 commentsgain was easier, and it is across all pixels evenly.
i use a global variable SensorGaincommon to store value in it is a float.
void ExtractGain(){
worddata[0]= pgm_read_word_near(factoryCalData+0x0030);//we can get this value from eeprom, faster and more reliablyif (worddata[0]> 32767){worddata[0]=worddata[0]-65536;}//per document 11.2.2.4
SensorGaincommon=worddata[0];
MLX90640_I2CRead(MLX90640_address, 0x070A, 1, worddata);//we get from ramif ( worddata[0]> 32767){worddata[0]=worddata[0]-65536;}
SensorGaincommon= worddata[0]/SensorGaincommon;
}from terminal image you can see gain is calculated as well. this seems to not change much so i would say it needs to be read at least 1 time after changing analog resolution detail.
here is the math. this is same for all pixels. it also does not need to be updated that much
-
i now have ambient temperature of silicone measurements
12/03/2018 at 07:37 • 0 commentsi have the ambient temperature of silicone measurements. this is important for calibration of gain and using difference to cal a individual cells temp. so i'm getting closer. below i show code used, and an image of output to terminal. this is an important step to having sensor work on arduino.
i will also show the math required to get it working. i don't know why they made it so complex. it didn't need to be. i did also reference Melexis driver code to speed things along, so i'm grateful that they did document it. the Melexis code however uses heavy use of ram, and there is little reason to do so. most of the values are from calibration and several values are fixed once calculated. some calc and conversions are done on the compiler side and never done on Arduino.
to get ambient temp of silicone these values are needed to be calculated
KVPTAT
KTPTAT
VPTAT25
ALPHAPTAT
ptat
ptatArt
Vdd (we use the stored value we have for this already)
the sensor assumes its base temp to be +/- from 25 deg C so offsets to calculate it are from there.
here is my ruff code. i try to use reads from stored data from flash whenever possible but this code needed several reads. good news is that this code can run every frame or only twice per full scan of sensors.
void ExtractAmbientTemp()
{worddata[0]= pgm_read_word_near(factoryCalData+0x0032);//we can get this value from eeprom, faster and more reliably
worddata[0]=worddata[0] & 0xFC00;// as per 11.2.2.3
KVPTAT=worddata[0]>>10;
if (KVPTAT<31) {KVPTAT-=64;}
KVPTAT = KVPTAT/4096;
worddata[0]= pgm_read_word_near(factoryCalData+0x0032);//we can get this value from eeprom, faster and more reliablyKTPTAT = worddata[0]& 0x03FF;
if(KTPTAT > 511)
{
KTPTAT = KTPTAT - 1024;
}
KTPTAT= KTPTAT/8;VPTAT25= pgm_read_word_near(factoryCalData+0x0031);//we can get this value from eeprom, faster and more reliably
worddata[0]= pgm_read_word_near(factoryCalData+0x0010);//we can get this value from eeprom, faster and more reliably
ALPHAPTAT=(worddata[0] & 0xF000)/pow(2, (double)14) + 8.0f;//as per documentation. wil simplify later on
float ptat;
float ptatArt;
float vdd;
float ta;
vdd = Vdd;//Vdd has already been calculated or should have been
MLX90640_I2CRead(MLX90640_address, 0x0720, 1, worddata);//we read register memory
ptat= worddata[0];
if (ptat> 32767 ){ptat = ptat - 65536;}//documented method
MLX90640_I2CRead(MLX90640_address, 0x0700, 1, worddata);//we read register memory
ptatArt =worddata[0];
if(ptatArt > 32767){ptatArt = ptatArt - 65536;}
ptatArt = (ptat / (ptat * ALPHAPTAT + ptatArt)) * pow(2, 18);
ta = (ptatArt / (1 + KVPTAT * (vdd - 3.3)) - VPTAT25);
ta = ta / KTPTAT + 25;
AmbientTemp=ta;
}and the math
-
voltage calibration of sensor working.
12/02/2018 at 11:58 • 0 commentshere is the calibration routing i am using for the mlx90640 sensor
i have already stored values in eeprom, and only need to read ram value of voltage to sensors 1 time per pass. 2 passes to read all sensors. so two voltage values.
this code extracts the kVdd, and the Vdd25 which are stored values, and then applies calculations to get the VDD at time of sensor read. i use a 2 byte word worddata to get data when i need it from sensor. i'm trying to reduce the reads from the chip and use the stored values that are copied to eprom flash of arduino
void ExtractVDDParameters()
{
worddata[0]= pgm_read_word_near(factoryCalData+0x0033);//we can get this value from eeprom, faster and more reliably
kVdd =worddata[0];
kVdd =kVdd & 0xFF00;//we do some required conversion (11.2.2.2 in document)
kVdd =kVdd >>8 ;//we divide by 2^8
if (kVdd >127){kVdd -=256;}//if >127 we subtract 256 as per document
kVdd =kVdd *32 ;//we now multiply by 32
Vdd25=worddata[0];
Vdd25=Vdd25 & 0x00FF;
Vdd25= ((Vdd25 -256)<<5)-8192;
//we need to get this from ram on sensor because it changes all the time!
MLX90640_I2CRead(MLX90640_address, 0x072A, 1, worddata);//we get vdd used real time from senso
Vdd=worddata[0];
if (Vdd>32767 ){Vdd=Vdd-65536;}//we do this to normalize the value
uint16_t storedRes= pgm_read_word_near(factoryCalData+56);//we can get this value from eeprom, faster and more reliably
storedRes= storedRes& 0x3000;
storedRes= storedRes>>12;
//we need analog resolution
MLX90640_I2CRead(MLX90640_address, 0x800D, 1, worddata);//we read register memory
worddata[0]=(worddata[0]& 0x0C00)>>10;//we get ad resolution detail
float resolutionfix;
if (worddata[0]==0){resolutionfix=4.0;}
if (worddata[0]==1){resolutionfix=2.0;}
if (worddata[0]==2){resolutionfix=1.0;}
if (worddata[0]==3){resolutionfix=0.5;}
Vdd= (resolutionfix* Vdd - Vdd25) / kVdd + 3.3;
}here are the results in terminal. for current troubleshooting i am only using center 8x8 sensors (even though it is capable of 32x24). you can see the extracted vdd25 and kvdd values. also note that for each frame the voltage is different slightly. this will effect gain and proper reading if both values are not used.
the sensor reads currently are raw values and are currently meaningless except to know that they change values.
here is the calc references from documentation. seems simple but a lot of troubleshooting. also i found a better way to calc the different resolutions. at least one that does not need POW
now on to the next set of needed calc for getting temp from sensor.
here are the calculations in all that i need to work on getting coded for Arduino to work, now that i know the format and the offsets for eeprom data, and how the register on this product works, hopefully things will go quickly.