Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added a Python Real-Time Heatmap Visualization for MLX90640 using Serial #28

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions Firmware/Example4_OutputToPython/Example2_OutputToProcessing.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Output the temperature readings to all pixels to be read by a Processing visualizer
By: Nathan Seidle
SparkFun Electronics
Date: May 22nd, 2018
License: MIT. See license file for more information but you can
basically do whatever you want with this code.
Feel like supporting open source hardware?
Buy a board from SparkFun! https://www.sparkfun.com/products/14769
This example outputs 768 temperature values as fast as possible. Use this example
in conjunction with our Processing visualizer.
This example will work with a Teensy 3.1 and above. The MLX90640 requires some
hefty calculations and larger arrays. You will need a microcontroller with 20,000
bytes or more of RAM.
This relies on the driver written by Melexis and can be found at:
https://github.com/melexis/mlx90640-library
Hardware Connections:
Connect the SparkFun Qwiic Breadboard Jumper (https://www.sparkfun.com/products/14425)
to the Qwiic board
Connect the male pins to the Teensy. The pinouts can be found here: https://www.pjrc.com/teensy/pinout.html
Open the serial monitor at 115200 baud to see the output
*/

#include <Wire.h>

#include "MLX90640_API.h"
#include "MLX90640_I2C_Driver.h"

const byte MLX90640_address = 0x33; //Default 7-bit unshifted address of the MLX90640

#define TA_SHIFT 8 //Default shift for MLX90640 in open air

float mlx90640To[768];
paramsMLX90640 mlx90640;

void setup()
{
Wire.begin();
Wire.setClock(400000); //Increase I2C clock speed to 400kHz

Serial.begin(115200); //Fast serial as possible

while (!Serial); //Wait for user to open terminal
//Serial.println("MLX90640 IR Array Example");

if (isConnected() == false)
{
Serial.println("MLX90640 not detected at default I2C address. Please check wiring. Freezing.");
while (1);
}

//Get device parameters - We only have to do this once
int status;
uint16_t eeMLX90640[832];
status = MLX90640_DumpEE(MLX90640_address, eeMLX90640);
if (status != 0)
Serial.println("Failed to load system parameters");

status = MLX90640_ExtractParameters(eeMLX90640, &mlx90640);
if (status != 0)
Serial.println("Parameter extraction failed");

//Once params are extracted, we can release eeMLX90640 array

//MLX90640_SetRefreshRate(MLX90640_address, 0x02); //Set rate to 2Hz
MLX90640_SetRefreshRate(MLX90640_address, 0x03); //Set rate to 4Hz
//MLX90640_SetRefreshRate(MLX90640_address, 0x07); //Set rate to 64Hz
}

void loop()
{
long startTime = millis();
for (byte x = 0 ; x < 2 ; x++)
{
uint16_t mlx90640Frame[834];
int status = MLX90640_GetFrameData(MLX90640_address, mlx90640Frame);

float vdd = MLX90640_GetVdd(mlx90640Frame, &mlx90640);
float Ta = MLX90640_GetTa(mlx90640Frame, &mlx90640);

float tr = Ta - TA_SHIFT; //Reflected temperature based on the sensor ambient temperature
float emissivity = 0.95;

MLX90640_CalculateTo(mlx90640Frame, &mlx90640, emissivity, tr, mlx90640To);
}
long stopTime = millis();

for (int x = 0 ; x < 768 ; x++)
{
//if(x % 8 == 0) Serial.println();
Serial.print(mlx90640To[x], 2);
Serial.print(",");
}
Serial.println("");
}

//Returns true if the MLX90640 is detected on the I2C bus
boolean isConnected()
{
Wire.beginTransmission((uint8_t)MLX90640_address);
if (Wire.endTransmission() != 0)
return (false); //Sensor did not ACK
return (true);
}

1,183 changes: 1,183 additions & 0 deletions Firmware/Example4_OutputToPython/MLX90640_API.cpp

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions Firmware/Example4_OutputToPython/MLX90640_API.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @copyright (C) 2017 Melexis N.V.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef _MLX640_API_H_
#define _MLX640_API_H_

typedef struct
{
int16_t kVdd;
int16_t vdd25;
float KvPTAT;
float KtPTAT;
uint16_t vPTAT25;
float alphaPTAT;
int16_t gainEE;
float tgc;
float cpKv;
float cpKta;
uint8_t resolutionEE;
uint8_t calibrationModeEE;
float KsTa;
float ksTo[4];
int16_t ct[4];
float alpha[768];
int16_t offset[768];
float kta[768];
float kv[768];
float cpAlpha[2];
int16_t cpOffset[2];
float ilChessC[3];
uint16_t brokenPixels[5];
uint16_t outlierPixels[5];
} paramsMLX90640;

int MLX90640_DumpEE(uint8_t slaveAddr, uint16_t *eeData);
int MLX90640_GetFrameData(uint8_t slaveAddr, uint16_t *frameData);
int MLX90640_ExtractParameters(uint16_t *eeData, paramsMLX90640 *mlx90640);
float MLX90640_GetVdd(uint16_t *frameData, const paramsMLX90640 *params);
float MLX90640_GetTa(uint16_t *frameData, const paramsMLX90640 *params);
void MLX90640_GetImage(uint16_t *frameData, const paramsMLX90640 *params, float *result);
void MLX90640_CalculateTo(uint16_t *frameData, const paramsMLX90640 *params, float emissivity, float tr, float *result);
int MLX90640_SetResolution(uint8_t slaveAddr, uint8_t resolution);
int MLX90640_GetCurResolution(uint8_t slaveAddr);
int MLX90640_SetRefreshRate(uint8_t slaveAddr, uint8_t refreshRate);
int MLX90640_GetRefreshRate(uint8_t slaveAddr);
int MLX90640_GetSubPageNumber(uint16_t *frameData);
int MLX90640_GetCurMode(uint8_t slaveAddr);
int MLX90640_SetInterleavedMode(uint8_t slaveAddr);
int MLX90640_SetChessMode(uint8_t slaveAddr);

#endif
109 changes: 109 additions & 0 deletions Firmware/Example4_OutputToPython/MLX90640_I2C_Driver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
@copyright (C) 2017 Melexis N.V.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/


#include <Wire.h>

#include "MLX90640_I2C_Driver.h"

void MLX90640_I2CInit()
{

}

//Read a number of words from startAddress. Store into Data array.
//Returns 0 if successful, -1 if error
int MLX90640_I2CRead(uint8_t _deviceAddress, unsigned int startAddress, unsigned int nWordsRead, uint16_t *data)
{

//Caller passes number of 'unsigned ints to read', increase this to 'bytes to read'
uint16_t bytesRemaining = nWordsRead * 2;

//It doesn't look like sequential read works. Do we need to re-issue the address command each time?

uint16_t dataSpot = 0; //Start at beginning of array

//Setup a series of chunked I2C_BUFFER_LENGTH byte reads
while (bytesRemaining > 0)
{
Wire.beginTransmission(_deviceAddress);
Wire.write(startAddress >> 8); //MSB
Wire.write(startAddress & 0xFF); //LSB
if (Wire.endTransmission(false) != 0) //Do not release bus
{
Serial.println("No ack read");
return (0); //Sensor did not ACK
}

uint16_t numberOfBytesToRead = bytesRemaining;
if (numberOfBytesToRead > I2C_BUFFER_LENGTH) numberOfBytesToRead = I2C_BUFFER_LENGTH;

Wire.requestFrom((uint8_t)_deviceAddress, numberOfBytesToRead);
if (Wire.available())
{
for (uint16_t x = 0 ; x < numberOfBytesToRead / 2; x++)
{
//Store data into array
data[dataSpot] = Wire.read() << 8; //MSB
data[dataSpot] |= Wire.read(); //LSB

dataSpot++;
}
}

bytesRemaining -= numberOfBytesToRead;

startAddress += numberOfBytesToRead / 2;
}

return (0); //Success
}

//Set I2C Freq, in kHz
//MLX90640_I2CFreqSet(1000) sets frequency to 1MHz
void MLX90640_I2CFreqSet(int freq)
{
//i2c.frequency(1000 * freq);
Wire.setClock((long)1000 * freq);
}

//Write two bytes to a two byte address
int MLX90640_I2CWrite(uint8_t _deviceAddress, unsigned int writeAddress, uint16_t data)
{
Wire.beginTransmission((uint8_t)_deviceAddress);
Wire.write(writeAddress >> 8); //MSB
Wire.write(writeAddress & 0xFF); //LSB
Wire.write(data >> 8); //MSB
Wire.write(data & 0xFF); //LSB
if (Wire.endTransmission() != 0)
{
//Sensor did not ACK
Serial.println("Error: Sensor did not ack");
return (-1);
}

uint16_t dataCheck;
MLX90640_I2CRead(_deviceAddress, writeAddress, 1, &dataCheck);
if (dataCheck != data)
{
//Serial.println("The write request didn't stick");
return -2;
}

return (0); //Success
}

51 changes: 51 additions & 0 deletions Firmware/Example4_OutputToPython/MLX90640_I2C_Driver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
@copyright (C) 2017 Melexis N.V.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#ifndef _MLX90640_I2C_Driver_H_
#define _MLX90640_I2C_Driver_H_

#include <stdint.h>

//Define the size of the I2C buffer based on the platform the user has
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)

//I2C_BUFFER_LENGTH is defined in Wire.H
#define I2C_BUFFER_LENGTH BUFFER_LENGTH

#elif defined(__SAMD21G18A__)

//SAMD21 uses RingBuffer.h
#define I2C_BUFFER_LENGTH SERIAL_BUFFER_SIZE

#elif __MK20DX256__
//Teensy 3.2
#define I2C_BUFFER_LENGTH 32

#else

//The catch-all default is 32
#define I2C_BUFFER_LENGTH 32

#endif
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=


void MLX90640_I2CInit(void);
int MLX90640_I2CRead(uint8_t slaveAddr, unsigned int startAddress, unsigned int nWordsRead, uint16_t *data);
int MLX90640_I2CWrite(uint8_t slaveAddr, unsigned int writeAddress, uint16_t data);
void MLX90640_I2CFreqSet(int freq);
#endif
62 changes: 62 additions & 0 deletions Firmware/Example4_OutputToPython/MLXHeatCam/MLXHeatCam.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import serial
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# Serial port configuration
serial_port = 'COM3'
baud_rate = 115200

# Initialize serial port
ser = serial.Serial(serial_port, baud_rate, timeout=1)
ser.flush()

# Initialize temperature array
temps = np.zeros(768)
max_temp = 0
min_temp = 500

# Create the figure for plotting
fig, ax = plt.subplots()
heatmap = ax.imshow(np.zeros((24, 32)), cmap='hsv', vmin=160, vmax=360)
plt.colorbar(heatmap)

def read_serial_data():
global max_temp, min_temp
if ser.in_waiting > 5000:
line = ser.read_until(b'\r')
if len(line) > 4608:
line = line[:4608]
split_string = line.decode().strip().split(',')

# Update min and max temperatures
max_temp = 0
min_temp = 500

for q in range(768):
try:
value = float(split_string[q])
if value > max_temp:
max_temp = value
if value < min_temp:
min_temp = value
except (ValueError, IndexError):
pass

# Map temperatures to colors
for q in range(768):
try:
value = float(split_string[q])
mapped_value = np.clip(np.interp(value, [min_temp, max_temp], [180, 360]), 160, 360)
temps[q] = mapped_value
except (ValueError, IndexError):
temps[q] = 0

def update_heatmap(*args):
read_serial_data()
heatmap.set_array(temps.reshape((24, 32)))
return heatmap,

ani = animation.FuncAnimation(fig, update_heatmap, interval=100)

plt.show()