From 3991684d9fc3c836bd0d4d824a68cb590e51732f Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Mon, 29 Nov 2021 18:08:11 +0000 Subject: [PATCH 1/6] MLX90640: Driver and 32x32 LED matrix example --- .gitmodules | 3 + drivers/CMakeLists.txt | 1 + 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, 311 insertions(+) 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 bcc2769df4..e4cebd9bea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -21,3 +21,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 bb474e1dc4..c985b7f7e1 100644 --- a/drivers/CMakeLists.txt +++ b/drivers/CMakeLists.txt @@ -38,3 +38,4 @@ add_subdirectory(vl53l5cx) add_subdirectory(pcf85063a) add_subdirectory(pms5003) add_subdirectory(sh1107) +add_subdirectory(mlx90640) diff --git a/drivers/mlx90640/CMakeLists.txt b/drivers/mlx90640/CMakeLists.txt new file mode 100644 index 0000000000..a1011492e2 --- /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 0000000000..214eeafe73 --- /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 0000000000..52bc8ec6d5 --- /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 0000000000..3bcc79d077 --- /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 0000000000..69a20b4c8c --- /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 0000000000..4dbbc56957 --- /dev/null +++ b/drivers/mlx90640/src @@ -0,0 +1 @@ +Subproject commit 4dbbc56957b6cb7fce2f7ba2d6351c3f5446f0cb diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c7de23d121..f3aa6274fe 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 0000000000..1fdedbc4bc --- /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 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 0000000000..9da4b65cc1 --- /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; +} From a8f3f2f3bab88da8731b31cb5c96bfbe9278cf8c Mon Sep 17 00:00:00 2001 From: Phil Howard Date: Tue, 23 Aug 2022 10:36:50 +0100 Subject: [PATCH 2/6] MLX90640: MicroPython module. --- drivers/mlx90640/mlx90640.hpp | 6 +- .../breakout_mlx90640/micropython.cmake | 21 ++++++ .../modules/breakout_mlx90640/mlx90640.c | 35 ++++++++++ .../modules/breakout_mlx90640/mlx90640.cpp | 65 +++++++++++++++++++ .../modules/breakout_mlx90640/mlx90640.h | 7 ++ micropython/modules/micropython-common.cmake | 1 + 6 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 micropython/modules/breakout_mlx90640/micropython.cmake create mode 100644 micropython/modules/breakout_mlx90640/mlx90640.c create mode 100644 micropython/modules/breakout_mlx90640/mlx90640.cpp create mode 100644 micropython/modules/breakout_mlx90640/mlx90640.h diff --git a/drivers/mlx90640/mlx90640.hpp b/drivers/mlx90640/mlx90640.hpp index 69a20b4c8c..9133c3c21a 100644 --- a/drivers/mlx90640/mlx90640.hpp +++ b/drivers/mlx90640/mlx90640.hpp @@ -1,3 +1,4 @@ +#include #include "src/headers/MLX90640_API.h" #include "common/pimoroni_i2c.hpp" @@ -8,13 +9,16 @@ void MLX90640_I2CConfigure(pimoroni::I2C *i2c_instance); namespace pimoroni { class MLX90640 { public: + static const int WIDTH = 32; + static const int HEIGHT = 24; + enum MLX90640_Error { OK = 0, INVALID_BAUDRATE = 1, INVALID_FPS = 2, }; - float mlx90640To[768] = {0.0f}; + float mlx90640To[WIDTH * HEIGHT] = {0.0f}; float emissivity = 1.0f; float reflected_temperature = 8.0f; diff --git a/micropython/modules/breakout_mlx90640/micropython.cmake b/micropython/modules/breakout_mlx90640/micropython.cmake new file mode 100644 index 0000000000..0f2875b40a --- /dev/null +++ b/micropython/modules/breakout_mlx90640/micropython.cmake @@ -0,0 +1,21 @@ +add_library(usermod_mlx90640 INTERFACE) + +target_sources(usermod_mlx90640 INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/mlx90640.c + ${CMAKE_CURRENT_LIST_DIR}/mlx90640.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/mlx90640/mlx90640.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/mlx90640/src/functions/MLX90640_API.cpp + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/mlx90640/MLX90640_RP2040_I2C_Driver.cpp +) + +target_include_directories(usermod_mlx90640 INTERFACE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/mlx90640/ + ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/mlx90640/src/headers/ +) + +target_compile_definitions(usermod_mlx90640 INTERFACE + MODULE_MLX90640_ENABLED=1 +) + +target_link_libraries(usermod INTERFACE usermod_mlx90640) \ No newline at end of file diff --git a/micropython/modules/breakout_mlx90640/mlx90640.c b/micropython/modules/breakout_mlx90640/mlx90640.c new file mode 100644 index 0000000000..81593f529d --- /dev/null +++ b/micropython/modules/breakout_mlx90640/mlx90640.c @@ -0,0 +1,35 @@ +#include "mlx90640.h" + + +MP_DEFINE_CONST_FUN_OBJ_2(MLX90640_setup_obj, MLX90640_setup); +MP_DEFINE_CONST_FUN_OBJ_1(MLX90640_get_frame_obj, MLX90640_get_frame); + +STATIC const mp_rom_map_elem_t MLX90640_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_setup), MP_ROM_PTR(&MLX90640_setup_obj) }, + { MP_ROM_QSTR(MP_QSTR_get_frame), MP_ROM_PTR(&MLX90640_get_frame_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(MLX90640_locals_dict, MLX90640_locals_dict_table); + +const mp_obj_type_t MLX90640_MLX90640_type = { + { &mp_type_type }, + .name = MP_QSTR_MLX90640, + .make_new = MLX90640_make_new, + .locals_dict = (mp_obj_dict_t*)&MLX90640_locals_dict, +}; + +STATIC const mp_map_elem_t MLX90640_globals_table[] = { + { MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_msa301) }, + { MP_OBJ_NEW_QSTR(MP_QSTR_MLX90640), (mp_obj_t)&MLX90640_type }, +}; +STATIC MP_DEFINE_CONST_DICT(mp_module_MLX90640_globals, MLX90640_globals_table); + +const mp_obj_module_t MLX90640_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&mp_module_MLX90640_globals, +}; + +#if MICROPY_VERSION <= 70144 +MP_REGISTER_MODULE(MP_QSTR_MLX90640, MLX90640_user_cmodule, MODULE_MLX90640_ENABLED); +#else +MP_REGISTER_MODULE(MP_QSTR_MLX90640, MLX90640_user_cmodule); +#endif \ No newline at end of file diff --git a/micropython/modules/breakout_mlx90640/mlx90640.cpp b/micropython/modules/breakout_mlx90640/mlx90640.cpp new file mode 100644 index 0000000000..3693770248 --- /dev/null +++ b/micropython/modules/breakout_mlx90640/mlx90640.cpp @@ -0,0 +1,65 @@ +#include "drivers/mlx90640/mlx90640.hpp" + +#include "micropython/modules/util.hpp" + +using namespace pimoroni; + +extern "C" { +#include "mlx90640.h" +#include "pimoroni_i2c.h" + +typedef struct _ModMLX90640_obj_t { + mp_obj_base_t base; + MLX90640 *breakout; + _PimoroniI2C_obj_t *i2c; + int address; +} ModMLX90640_obj_t; + +mp_obj_t MLX90640_setup(mp_obj_t self_in, mp_obj_t fps) { + _ModMLX90640_obj_t *self = MP_OBJ_TO_PTR2(self_in, _ModMLX90640_obj_t); + + self->breakout->setup(mp_obj_get_int(fps)); + + return mp_const_none; +} + +mp_obj_t MLX90640_get_frame(mp_obj_t self_in) { + _ModMLX90640_obj_t *self = MP_OBJ_TO_PTR2(self_in, _ModMLX90640_obj_t); + + self->breakout->get_frame(); + + mp_obj_list_t *list = MP_OBJ_TO_PTR2(mp_obj_new_list(MLX90640::WIDTH * MLX90640::HEIGHT, NULL), mp_obj_list_t); + + for(auto y = 0u; y < MLX90640::HEIGHT; y++) { + for(auto x = 0u; x < MLX90640::WIDTH; x++) { + int offset = y * MLX90640::WIDTH + x; + float v = self->breakout->mlx90640To[offset]; + list->items[offset] = mp_obj_new_float(v); + } + } + + return list; +} + +mp_obj_t MLX90640_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + enum { ARG_i2c, ARG_address }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_i2c, MP_ARG_OBJ, {.u_obj = nullptr} }, + { MP_QSTR_address, MP_ARG_INT, {.u_int = MLX90640_DEFAULT_I2C_ADDRESS}} + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + _ModMLX90640_obj_t *self = m_new_obj_with_finaliser(_ModMLX90640_obj_t); + self->base.type = &MLX90640_type; + + self->i2c = PimoroniI2C_from_machine_i2c_or_native(args[ARG_i2c].u_obj); + self->address = args[ARG_address].u_int; + + self->breakout = m_new_class(MLX90640, (pimoroni::I2C *)(self->i2c->i2c), self->address); + + return self; +} + +} \ No newline at end of file diff --git a/micropython/modules/breakout_mlx90640/mlx90640.h b/micropython/modules/breakout_mlx90640/mlx90640.h new file mode 100644 index 0000000000..3bf4bc0f98 --- /dev/null +++ b/micropython/modules/breakout_mlx90640/mlx90640.h @@ -0,0 +1,7 @@ +#include "py/runtime.h" + +extern const mp_obj_type_t MLX90640_type; + +extern mp_obj_t MLX90640_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args); +extern mp_obj_t MLX90640_get_frame(mp_obj_t self_in); +extern mp_obj_t MLX90640_setup(mp_obj_t self_in, mp_obj_t fps_in); \ No newline at end of file diff --git a/micropython/modules/micropython-common.cmake b/micropython/modules/micropython-common.cmake index 29ec616dd4..54872b2d2b 100644 --- a/micropython/modules/micropython-common.cmake +++ b/micropython/modules/micropython-common.cmake @@ -22,6 +22,7 @@ include(breakout_bmp280/micropython) include(breakout_icp10125/micropython) include(breakout_scd41/micropython) include(breakout_vl53l5cx/micropython) +include(breakout_mlx90640/micropython) include(pico_scroll/micropython) include(pico_rgb_keypad/micropython) From 4624b39112d24e2887356996070e52eefbfc1ef7 Mon Sep 17 00:00:00 2001 From: thinkier <26728734+thinkier@users.noreply.github.com> Date: Mon, 2 Jan 2023 00:32:38 +1100 Subject: [PATCH 3/6] pulled out from demo / non-modular CMakeList --- examples/breakout_mlx90640/CMakeLists.txt | 13 +------------ examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake | 12 ++++++++++++ .../{demo.cpp => mlx90640_rgbmatrix.cpp} | 0 3 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake rename examples/breakout_mlx90640/{demo.cpp => mlx90640_rgbmatrix.cpp} (100%) diff --git a/examples/breakout_mlx90640/CMakeLists.txt b/examples/breakout_mlx90640/CMakeLists.txt index 1fdedbc4bc..54e4fa8a28 100644 --- a/examples/breakout_mlx90640/CMakeLists.txt +++ b/examples/breakout_mlx90640/CMakeLists.txt @@ -1,12 +1 @@ -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 hardware_vreg) - -# create map/bin/hex file etc. -pico_add_extra_outputs(${OUTPUT_NAME}) +include(mlx90640_rgbmatrix.cmake) diff --git a/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake b/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake new file mode 100644 index 0000000000..7a29260481 --- /dev/null +++ b/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake @@ -0,0 +1,12 @@ +set(OUTPUT_NAME mlx90460_rgbmatrix) + +add_executable( + ${OUTPUT_NAME} + mlx90640_rgbmatrix.cpp +) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} pico_stdlib mlx90640 hub75 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/mlx90640_rgbmatrix.cpp similarity index 100% rename from examples/breakout_mlx90640/demo.cpp rename to examples/breakout_mlx90640/mlx90640_rgbmatrix.cpp From 5560cd1d5d0bfeb77678c663d8971a1020e94942 Mon Sep 17 00:00:00 2001 From: thinkier <26728734+thinkier@users.noreply.github.com> Date: Mon, 2 Jan 2023 01:26:19 +1100 Subject: [PATCH 4/6] typo --- examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake b/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake index 7a29260481..8e65a87941 100644 --- a/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake +++ b/examples/breakout_mlx90640/mlx90640_rgbmatrix.cmake @@ -1,4 +1,4 @@ -set(OUTPUT_NAME mlx90460_rgbmatrix) +set(OUTPUT_NAME mlx90640_rgbmatrix) add_executable( ${OUTPUT_NAME} From 4d9768bd058a38f753813e0974cf65ff4c32d7a7 Mon Sep 17 00:00:00 2001 From: thinkier <26728734+thinkier@users.noreply.github.com> Date: Mon, 2 Jan 2023 12:01:53 +1100 Subject: [PATCH 5/6] datasheet strongly recommends checkerboard read pattern --- drivers/mlx90640/mlx90640.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/mlx90640/mlx90640.cpp b/drivers/mlx90640/mlx90640.cpp index 3bcc79d077..bf0ad47437 100644 --- a/drivers/mlx90640/mlx90640.cpp +++ b/drivers/mlx90640/mlx90640.cpp @@ -53,8 +53,8 @@ namespace pimoroni { #endif return INVALID_FPS; } - //MLX90640_SetChessMode(i2c_address); - MLX90640_SetInterleavedMode(i2c_address); + MLX90640_SetChessMode(i2c_address); + //MLX90640_SetInterleavedMode(i2c_address); //MLX90640_SetResolution(i2c_address, 0); MLX90640_DumpEE(i2c_address, eeMLX90640); MLX90640_ExtractParameters(eeMLX90640, &mlx90640); From 0b2e34eceb2342db7d9820fdc83de82237265909 Mon Sep 17 00:00:00 2001 From: thinkier <26728734+thinkier@users.noreply.github.com> Date: Mon, 2 Jan 2023 12:05:36 +1100 Subject: [PATCH 6/6] added basic lcd demo --- examples/breakout_mlx90640/CMakeLists.txt | 1 + .../breakout_mlx90640/mlx90640_st7789.cmake | 13 ++ .../breakout_mlx90640/mlx90640_st7789.cpp | 141 ++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 examples/breakout_mlx90640/mlx90640_st7789.cmake create mode 100644 examples/breakout_mlx90640/mlx90640_st7789.cpp diff --git a/examples/breakout_mlx90640/CMakeLists.txt b/examples/breakout_mlx90640/CMakeLists.txt index 54e4fa8a28..867b7b8aef 100644 --- a/examples/breakout_mlx90640/CMakeLists.txt +++ b/examples/breakout_mlx90640/CMakeLists.txt @@ -1 +1,2 @@ include(mlx90640_rgbmatrix.cmake) +include(mlx90640_st7789.cmake) diff --git a/examples/breakout_mlx90640/mlx90640_st7789.cmake b/examples/breakout_mlx90640/mlx90640_st7789.cmake new file mode 100644 index 0000000000..3888ade2d3 --- /dev/null +++ b/examples/breakout_mlx90640/mlx90640_st7789.cmake @@ -0,0 +1,13 @@ +set(OUTPUT_NAME mlx90640_st7789) + +add_executable( + ${OUTPUT_NAME} + mlx90640_st7789.cpp +) + +# Pull in pico libraries that we need +pico_enable_stdio_usb(${OUTPUT_NAME} 1) +target_link_libraries(${OUTPUT_NAME} pico_stdlib pico_stdio mlx90640 pico_graphics st7789) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/breakout_mlx90640/mlx90640_st7789.cpp b/examples/breakout_mlx90640/mlx90640_st7789.cpp new file mode 100644 index 0000000000..e61e101dc0 --- /dev/null +++ b/examples/breakout_mlx90640/mlx90640_st7789.cpp @@ -0,0 +1,141 @@ +#include "pico/stdlib.h" + +#include +#include +#include +#include + +#include "mlx90640.hpp" +#include "st7789.hpp" + +using namespace std; +using namespace pimoroni; + +I2C i2c(BOARD::BREAKOUT_GARDEN); + +MLX90640 mlx90640(&i2c); +ST7789 st7789(240, 240, pimoroni::ROTATE_90, false, get_spi_pins(BG_SPI_FRONT)); +PicoGraphics_PenRGB565 graphics(st7789.width, st7789.height, nullptr); +Pen BLACK = graphics.create_pen(0, 0, 0); + +const uint8_t PIXEL_SIZE = 7; +const uint8_t X_MARGIN = 8; +const uint8_t Y_MARGIN = 36; + +struct Range { + float min; + float max; +}; + +void write_pixel(uint8_t cam_x, uint8_t cam_y) { + auto x_base = cam_x * PIXEL_SIZE + X_MARGIN; + auto y_base = cam_y * PIXEL_SIZE + Y_MARGIN; + + for (auto x = x_base; x < x_base + PIXEL_SIZE; x++) { + for (auto y = y_base; y < y_base + PIXEL_SIZE; y++) { + graphics.set_pixel(Point(x, y)); + } + } +} + +Range get_frame_temp_range(float frame[24 * 32]) { + auto min = frame[0]; + auto max = frame[0]; + + for (auto i = 1; i < 768; i++) { + min = std::min(min, frame[i]); + max = std::max(max, frame[i]); + } + + return {min, max}; +} + +Range get_abs_range(deque buf) { + auto min = buf[0].min; + auto max = buf[0].max; + + for (uint8_t i = 1; i < buf.size(); i++) { + min = floor(std::min(min, buf[i].min)); + max = ceil(std::max(max, buf[i].max)); + } + + return {min, max}; +} + +float get_temp_gradient(float temp, Range range) { + return (temp - range.min) / (range.max - range.min); +} + +void set_pen_grayscale(float g) { + auto v = g * 255; + + graphics.set_pen(v, v, v); +} + +void set_pen_hue(float h) { + h = std::fmod(h, 1.0f); + if (h < 0) { + h += 1.0f; + } + h *= 6; + + float kr = fmod(5 + h, 6); + float kg = fmod(3 + h, 6); + float kb = fmod(1 + h, 6); + + auto r = 1 - max(0.0f, min(1.0f, min(4 - kr, kr))); + auto g = 1 - max(0.0f, min(1.0f, min(4 - kg, kg))); + auto b = 1 - max(0.0f, min(1.0f, min(4 - kb, kb))); + + graphics.set_pen(r * 255, g * 255, b * 255); +} + +int main() { + stdio_init_all(); + st7789.set_backlight(128); + + // Any higher framerate and it will require weird Pico overclocking + mlx90640.setup(8); + +// while (!stdio_usb_connected()) { +// sleep_ms(1); +// } +// cout << "--- Connected ---" << endl; + sleep_ms(500); // Don't read the initial frames + + deque ranges; + + while (true) { + mlx90640.get_frame(); + graphics.set_pen(BLACK); + graphics.clear(); + + + // Circular buffer to keep track of the temp range + ranges.push_back(get_frame_temp_range(mlx90640.mlx90640To)); + while (ranges.size() > 64) { + ranges.pop_front(); + } + auto range = get_abs_range(ranges); + + for (auto y = 0u; y < 24; y++) { + for (auto x = 0u; x < 32; x++) { + // Usable temperature values as-is + uint16_t offset = y * 32 + x; + float temp = mlx90640.mlx90640To[offset]; + + // Convert value to within the range of [0, 1] + auto g = get_temp_gradient(temp, range); + // Hacky way to flip the spectrum and then discard the part where it transitions from blue to red + set_pen_hue(2 * (1 - g) / 3); + // set_pen_grayscale(g); + + // Convert cam-pixel to display-pixel + write_pixel(x, y); + } + } + + st7789.update(&graphics); + } +} +