From 2ff5d462c87bfd95676534a2d9279a19089a24d5 Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 29 Nov 2021 18:08:11 +0000 Subject: [PATCH] MLX90640: Driver and 32x32 LED matrix example --- .gitmodules | 3 + drivers/CMakeLists.txt | 3 +- drivers/mlx90640/CMakeLists.txt | 1 + .../mlx90640/MLX90640_RP2040_I2C_Driver.cpp | 50 +++++++++ drivers/mlx90640/mlx90640.cmake | 13 +++ drivers/mlx90640/mlx90640.cpp | 92 ++++++++++++++++ drivers/mlx90640/mlx90640.hpp | 33 ++++++ drivers/mlx90640/src | 1 + examples/CMakeLists.txt | 1 + examples/breakout_mlx90640/CMakeLists.txt | 12 ++ examples/breakout_mlx90640/demo.cpp | 104 ++++++++++++++++++ 11 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 drivers/mlx90640/CMakeLists.txt create mode 100644 drivers/mlx90640/MLX90640_RP2040_I2C_Driver.cpp create mode 100644 drivers/mlx90640/mlx90640.cmake create mode 100644 drivers/mlx90640/mlx90640.cpp create mode 100644 drivers/mlx90640/mlx90640.hpp create mode 160000 drivers/mlx90640/src create mode 100644 examples/breakout_mlx90640/CMakeLists.txt create mode 100644 examples/breakout_mlx90640/demo.cpp diff --git a/.gitmodules b/.gitmodules index a9b26b06..b1f258d2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ path = drivers/vl53l5cx/src url = https://github.com/ST-mirror/VL53L5CX_ULD_driver branch = no-fw/lite/en +[submodule "drivers/mlx90640/src"] + path = drivers/mlx90640/src + url = https://github.com/melexis/mlx90640-library diff --git a/drivers/CMakeLists.txt b/drivers/CMakeLists.txt index d1978c9d..fb66d2a5 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -42,4 +42,5 @@ add_subdirectory(sh1107) add_subdirectory(st7567) add_subdirectory(psram_display) add_subdirectory(inky73) -add_subdirectory(shiftregister) \ No newline at end of file +add_subdirectory(shiftregister) +add_subdirectory(mlx90640) diff --git a/drivers/mlx90640/CMakeLists.txt b/drivers/mlx90640/CMakeLists.txt new file mode 100644 index 00000000..a1011492 --- /dev/null +++ b/drivers/mlx90640/CMakeLists.txt @@ -0,0 +1 @@ +include(mlx90640.cmake) \ No newline at end of file diff --git a/drivers/mlx90640/MLX90640_RP2040_I2C_Driver.cpp b/drivers/mlx90640/MLX90640_RP2040_I2C_Driver.cpp new file mode 100644 index 00000000..214eeafe --- /dev/null +++ b/drivers/mlx90640/MLX90640_RP2040_I2C_Driver.cpp @@ -0,0 +1,50 @@ +#include "src/headers/MLX90640_I2C_Driver.h" +#include "mlx90640.hpp" + +#include "stdio.h" + + +static pimoroni::I2C *i2c; + +void MLX90640_I2CConfigure(pimoroni::I2C *i2c_instance) { + i2c = i2c_instance; +} + +void MLX90640_I2CInit() +{ + // i2c->init(); // Called in constructor +} + +int MLX90640_I2CGeneralReset(void) +{ + return 0; +} + +int MLX90640_I2CRead(uint8_t slaveAddr, uint16_t startAddress, uint16_t nMemAddressRead, uint16_t *data) +{ + uint8_t cmd[2] = {(char)(startAddress >> 8), (char)(startAddress & 0xFF)}; + + // Set 16-bit register pointer + i2c->write_blocking(slaveAddr, cmd, sizeof(cmd), true); + // Read result + i2c->read_blocking(slaveAddr, (uint8_t*)data, nMemAddressRead * sizeof(uint16_t), false); + + for(auto n = 0u; n < nMemAddressRead; n++) { + data[n] = __builtin_bswap16(data[n]); + } + + return 0; +} + +void MLX90640_I2CFreqSet(int freq) +{ + // We can't assume we own the I2C instance and can wiggle the baudrate ad-hoc +} + +int MLX90640_I2CWrite(uint8_t slaveAddr, uint16_t writeAddress, uint16_t data) +{ + uint8_t cmd[4] = {(char)(writeAddress >> 8), (char)(writeAddress & 0x00FF), (char)(data >> 8), (char)(data & 0x00FF)}; + i2c->write_blocking(slaveAddr, cmd, sizeof(cmd), false); + return 0; +} + diff --git a/drivers/mlx90640/mlx90640.cmake b/drivers/mlx90640/mlx90640.cmake new file mode 100644 index 00000000..52bc8ec6 --- /dev/null +++ b/drivers/mlx90640/mlx90640.cmake @@ -0,0 +1,13 @@ +set(DRIVER_NAME mlx90640) +add_library(${DRIVER_NAME} INTERFACE) + +target_sources(${DRIVER_NAME} INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/src/functions/MLX90640_API.cpp + ${CMAKE_CURRENT_LIST_DIR}/MLX90640_RP2040_I2C_Driver.cpp + ${CMAKE_CURRENT_LIST_DIR}/mlx90640.cpp +) + +target_link_libraries(${DRIVER_NAME} INTERFACE pico_stdlib hardware_i2c pimoroni_i2c) + +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}) +target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}/src/headers) diff --git a/drivers/mlx90640/mlx90640.cpp b/drivers/mlx90640/mlx90640.cpp new file mode 100644 index 00000000..3bcc79d0 --- /dev/null +++ b/drivers/mlx90640/mlx90640.cpp @@ -0,0 +1,92 @@ +#include +#include +#include +#include +#include +#include +#include +#include "src/headers/MLX90640_API.h" +#include "mlx90640.hpp" + + + +namespace pimoroni { + MLX90640::MLX90640_Error MLX90640::setup(int fps){ + MLX90640_I2CConfigure(i2c_instance); + //MLX90640_SetDeviceMode(i2c_address, 0); + //MLX90640_SetSubPageRepeat(i2c_address, 0); + + switch(fps){ + case 1: + MLX90640_SetRefreshRate(i2c_address, 0b001); + break; + case 2: + MLX90640_SetRefreshRate(i2c_address, 0b010); + break; + case 4: + MLX90640_SetRefreshRate(i2c_address, 0b011); + break; + case 8: + MLX90640_SetRefreshRate(i2c_address, 0b100); + break; + case 16: + MLX90640_SetRefreshRate(i2c_address, 0b101); + if(i2c_instance->get_baudrate() < 1000000) { + return INVALID_BAUDRATE; + } + break; + case 32: + MLX90640_SetRefreshRate(i2c_address, 0b110); + if(i2c_instance->get_baudrate() < 1000000) { + return INVALID_BAUDRATE; + } + break; + case 64: + MLX90640_SetRefreshRate(i2c_address, 0b111); + if(i2c_instance->get_baudrate() < 1000000) { + return INVALID_BAUDRATE; + } + break; + default: +#ifdef DEBUG + printf("Unsupported framerate: %d", fps); +#endif + return INVALID_FPS; + } + //MLX90640_SetChessMode(i2c_address); + MLX90640_SetInterleavedMode(i2c_address); + //MLX90640_SetResolution(i2c_address, 0); + MLX90640_DumpEE(i2c_address, eeMLX90640); + MLX90640_ExtractParameters(eeMLX90640, &mlx90640); + + return OK; + } + + int MLX90640::get_image(void){ + MLX90640_I2CConfigure(i2c_instance); + + MLX90640_GetFrameData(i2c_address, frame0); + sleep_us(1000); + MLX90640_GetFrameData(i2c_address, frame1); + + MLX90640_GetImage(frame0, &mlx90640, mlx90640To); + MLX90640_GetImage(frame1, &mlx90640, mlx90640To); + + return 0; + } + + int MLX90640::get_frame(void){ + MLX90640_I2CConfigure(i2c_instance); + + MLX90640_GetFrameData(i2c_address, frame0); + sleep_us(1000); + MLX90640_GetFrameData(i2c_address, frame1); + + int tr0 = MLX90640_GetTa(frame0, &mlx90640) - reflected_temperature; + MLX90640_CalculateTo(frame0, &mlx90640, emissivity, tr0, mlx90640To); + int tr1 = MLX90640_GetTa(frame1, &mlx90640) - reflected_temperature; + MLX90640_CalculateTo(frame1, &mlx90640, emissivity, tr1, mlx90640To); + + return 0; + } +} \ No newline at end of file diff --git a/drivers/mlx90640/mlx90640.hpp b/drivers/mlx90640/mlx90640.hpp new file mode 100644 index 00000000..69a20b4c --- /dev/null +++ b/drivers/mlx90640/mlx90640.hpp @@ -0,0 +1,33 @@ +#include "src/headers/MLX90640_API.h" +#include "common/pimoroni_i2c.hpp" + +void MLX90640_I2CConfigure(pimoroni::I2C *i2c_instance); + +#define MLX90640_DEFAULT_I2C_ADDRESS 0x33 + +namespace pimoroni { + class MLX90640 { + public: + enum MLX90640_Error { + OK = 0, + INVALID_BAUDRATE = 1, + INVALID_FPS = 2, + }; + + float mlx90640To[768] = {0.0f}; + float emissivity = 1.0f; + float reflected_temperature = 8.0f; + + MLX90640(pimoroni::I2C *i2c_instance, uint i2c_address=MLX90640_DEFAULT_I2C_ADDRESS) : i2c_instance(i2c_instance), i2c_address(i2c_address) {}; + MLX90640_Error setup(int fps); + int get_image(void); + int get_frame(void); + private: + pimoroni::I2C *i2c_instance; + uint i2c_address = MLX90640_DEFAULT_I2C_ADDRESS; + paramsMLX90640 mlx90640; + uint16_t eeMLX90640[832] = {0}; + uint16_t frame0[834] = {0}; + uint16_t frame1[834] = {0}; + }; +} \ No newline at end of file diff --git a/drivers/mlx90640/src b/drivers/mlx90640/src new file mode 160000 index 00000000..4dbbc569 --- /dev/null +++ b/drivers/mlx90640/src @@ -0,0 +1 @@ +Subproject commit 4dbbc56957b6cb7fce2f7ba2d6351c3f5446f0cb diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 24262ad6..1ba0f213 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -24,6 +24,7 @@ add_subdirectory(breakout_scd41) add_subdirectory(breakout_vl53l5cx) add_subdirectory(breakout_pms5003) add_subdirectory(breakout_oled_128x128) +add_subdirectory(breakout_mlx90640) add_subdirectory(pico_display) add_subdirectory(pico_display_2) diff --git a/examples/breakout_mlx90640/CMakeLists.txt b/examples/breakout_mlx90640/CMakeLists.txt new file mode 100644 index 00000000..c2fa2f03 --- /dev/null +++ b/examples/breakout_mlx90640/CMakeLists.txt @@ -0,0 +1,12 @@ +set(OUTPUT_NAME mlx90460_demo) + +add_executable( + ${OUTPUT_NAME} + demo.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib mlx90640 hub75_legacy hardware_vreg) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_mlx90640/demo.cpp b/examples/breakout_mlx90640/demo.cpp new file mode 100644 index 00000000..9da4b65c --- /dev/null +++ b/examples/breakout_mlx90640/demo.cpp @@ -0,0 +1,104 @@ +#include "pico/stdlib.h" +#include "hardware/vreg.h" +#include "stdio.h" + +#include +#include + +#include "mlx90640.hpp" + +#include "hub75.hpp" + +using namespace pimoroni; + +// Display size in pixels +// Should be either 64x64 or 32x32 but perhaps 64x32 an other sizes will work. +// Note: this example uses only 5 address lines so it's limited to 32*2 pixels. +const uint8_t WIDTH = 32; +const uint8_t HEIGHT = 32; + +// min and max temperature range (in degrees C) for scaling false colour +const float temp_min = 18.0f; +const float temp_max = 38.0f; + +// colour brightness - crushes dynamic range so use wisely! +const float brightness = 0.5f; + +Hub75 hub75(WIDTH, HEIGHT, nullptr); + +// Dirty hack to overclock the Pico before class initialisation takes place +// since i2c uses the current clock frequency when determining baudrate. +class OC { + public: + OC(uint32_t freq_khz, vreg_voltage voltage) { + vreg_set_voltage(voltage); + sleep_us(100); + set_sys_clock_khz(freq_khz, true); + } +}; + +OC oc(266000, VREG_VOLTAGE_1_20); + +// 1MHz i2c for higher framerates +I2C i2c(20, 21, 1000000UL); + +MLX90640 mlx90640(&i2c); + +void __isr dma_complete() { + hub75.dma_complete(); +} + +void set_pixel_false_colour(int x, int y, float v) { + const int colours = 8; + static float color[colours][3] = { + {0, 0, 0}, + {0, 0, 255.0f}, + {0, 255.0f,255.0f}, + {0, 255.0f,0}, + {255.0f,255.0f,0}, + {255.0f,0, 0}, + {255.0f,0, 255.0f}, + {255.0f,255.0f,255.0f} + }; + int idx1, idx2; + float blend = 0.0f; + const float temp_range = temp_max - temp_min; + v -= temp_min; + v /= temp_range; + if(v <= 0) {idx1 = idx2 = 0;} + else if(v >= 1) {idx1 = idx2 = colours - 1;} + else + { + v *= (colours - 1); + idx1 = std::floor(v); + idx2 = idx1 + 1; + blend = v - float(idx1); + } + + int r = (int)((((color[idx2][0] - color[idx1][0]) * blend) + color[idx1][0]) * brightness); + int g = (int)((((color[idx2][1] - color[idx1][1]) * blend) + color[idx1][1]) * brightness); + int b = (int)((((color[idx2][2] - color[idx1][2]) * blend) + color[idx1][2]) * brightness); + + hub75.set_rgb(x, y, r, g, b); +} + + int main() { + stdio_init_all(); + hub75.start(dma_complete); + + mlx90640.setup(32); + + while(true) { + mlx90640.get_frame(); + for(auto y = 0u; y < 24; y++) { + for(auto x = 0u; x < 32; x++) { + int offset = y * 32 + x; + float v = mlx90640.mlx90640To[offset]; // / 30000.0f; + set_pixel_false_colour(x, y, v); + } + } + hub75.flip(); + } + + return 0; +}