Skip to content

Commit ad338b9

Browse files
committedJan 30, 2025
First commit.
1 parent af8c1d3 commit ad338b9

21 files changed

+11007
-31
lines changed
 

‎.gitignore

+6-31
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,7 @@
1-
# Prerequisites
2-
*.d
1+
# VSCODE
2+
*/.theia
3+
*/.vscode
34

4-
# Compiled Object files
5-
*.slo
6-
*.lo
7-
*.o
8-
*.obj
9-
10-
# Precompiled Headers
11-
*.gch
12-
*.pch
13-
14-
# Compiled Dynamic libraries
15-
*.so
16-
*.dylib
17-
*.dll
18-
19-
# Fortran module files
20-
*.mod
21-
*.smod
22-
23-
# Compiled Static libraries
24-
*.lai
25-
*.la
26-
*.a
27-
*.lib
28-
29-
# Executables
30-
*.exe
31-
*.out
32-
*.app
5+
# Mac special files
6+
*/.DS_store
7+
*/._*

‎MLX90640.ino

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*================================================================================
2+
* Thermograpy camera
3+
* Copyright (c) 2024 - 2025 embedded-kiddie
4+
* Released under the MIT license
5+
* https://opensource.org/license/mit
6+
*================================================================================*/
7+
#include <Arduino.h>
8+
#include "pin_assign.h"
9+
10+
#define DEBUG true
11+
#if DEBUG
12+
#define DBG_EXEC(x) x
13+
#else
14+
#define DBG_EXEC(x)
15+
#endif
16+
17+
/*--------------------------------------------------------------------------------
18+
* Step 1: Configure Operational settings
19+
*--------------------------------------------------------------------------------*/
20+
#define ENA_INTERPOLATION true // Enable interpolation
21+
#define ENA_MULTITASKING true // Enable multi-task by FreeRTOS
22+
#define ENA_OUTWARD_CAMERA false // Camera orientation (true: Outward, false: Selfie)
23+
24+
/*--------------------------------------------------------------------------------
25+
* Step 2: Select GFX Library
26+
*--------------------------------------------------------------------------------*/
27+
#if 1
28+
/*--------------------------------------------------------------------------------
29+
* LovyanGFX
30+
* Note: Currently 'AUTODETECT' only works for 'ESP32 2432S028R'.
31+
* For other boards you need to configure appropriately to fit your device.
32+
* See https://github.com/lovyan03/LovyanGFX/blob/master/src/lgfx/boards.hpp
33+
*--------------------------------------------------------------------------------*/
34+
#define USE_LOVYAN_GFX
35+
#define USE_AUTODETECT true
36+
37+
#else
38+
/*--------------------------------------------------------------------------------
39+
* TFT_eSPI
40+
* It does not allow the display and touch screen to be on different SPI buses.
41+
*--------------------------------------------------------------------------------*/
42+
#define USE_TFT_ESPI
43+
#ifdef CYD_TFT_CS
44+
#error Not yet supported // CYD needs XPT2046_Touchscreen library
45+
#endif
46+
#endif
47+
48+
/*--------------------------------------------------------------------------------
49+
* Step 3: Configure flash memory setting to save touch calibration data
50+
*--------------------------------------------------------------------------------*/
51+
#define USE_PREFERENCES true
52+
53+
/*--------------------------------------------------------------------------------
54+
* Step 4: Configure MLX90640 settings
55+
*--------------------------------------------------------------------------------*/
56+
#if ENA_INTERPOLATION
57+
#define INTERPOLATE_SCALE 8
58+
#define BOX_SIZE 1
59+
#define REFRESH_RATE (ENA_MULTITASKING ? MLX90640_32_HZ : MLX90640_16_HZ)
60+
#else
61+
#define INTERPOLATE_SCALE 1
62+
#define BOX_SIZE 8
63+
#define REFRESH_RATE (ENA_MULTITASKING ? MLX90640_32_HZ : MLX90640_16_HZ)
64+
#endif
65+
66+
/*--------------------------------------------------------------------------------
67+
* Heat map
68+
*--------------------------------------------------------------------------------*/
69+
#define N_HEATMAP 512 // 256 or 512 --> heatmap.h
70+
constexpr uint16_t heatmap[2][N_HEATMAP] = {
71+
#include "heatmap.h" // 0; Rainbow, 1: Inferno
72+
};
73+
74+
/*--------------------------------------------------------------------------------
75+
* Load modules
76+
*--------------------------------------------------------------------------------*/
77+
#include "mlx.hpp"
78+
#include "gfx.hpp"
79+
#include "task.hpp"
80+
#include "touch.hpp"
81+
#include "sdcard.hpp"
82+
#include "filter.hpp"
83+
#include "interpolation.hpp"
84+
#include "widget.hpp"
85+
86+
/*--------------------------------------------------------------------------------
87+
* Input process - Get thermal image from MLX90640
88+
*--------------------------------------------------------------------------------*/
89+
void ProcessInput(uint8_t bank) {
90+
// Refresh MLX90640 when refresh rate changes
91+
bool refresh = mlx_refresh();
92+
93+
// Get new image
94+
if (mlx.getFrame(src[bank]) != 0) {
95+
gfx_printf(TFT_WIDTH / 2 - FONT_WIDTH * 2, TFT_HEIGHT / 2 - FONT_HEIGHT * 4, "Failed");
96+
DBG_EXEC(printf("Failed\n"));
97+
delay(1000); // false = no new frame capture
98+
}
99+
100+
if (refresh) {
101+
// Avoid cluttered image
102+
for (int i = 0; i < MLX90640_COLS * MLX90640_ROWS; ++i) {
103+
src[bank][i] = (float)mlx_cnf.range_min;
104+
}
105+
} else {
106+
// Measure temperature of min/max/pickup
107+
filter_temperature(src[bank]);
108+
}
109+
}
110+
111+
/*--------------------------------------------------------------------------------
112+
* Output process - Interpolate thermal image and display on LCD.
113+
*--------------------------------------------------------------------------------*/
114+
void ProcessOutput(uint8_t bank, uint32_t inputStart, uint32_t inputFinish) {
115+
// Widget controller
116+
State_t state = widget_control();
117+
if (bank != NOT_UPDATED && (state == STATE_MAIN || state == STATE_THERMOGRAPH)) {
118+
static uint32_t outputFinish, outputPeriod, prevStart;
119+
uint32_t outputStart = millis();
120+
121+
const int dst_rows = mlx_cnf.interpolation * MLX90640_ROWS;
122+
const int dst_cols = mlx_cnf.interpolation * MLX90640_COLS;
123+
const int box_size = mlx_cnf.box_size;
124+
125+
GFX_EXEC(startWrite());
126+
GFX_FAST(createSprite(dst_cols * box_size, dst_rows * box_size));
127+
128+
interpolate_image(src[bank], MLX90640_ROWS, MLX90640_COLS, dst_rows, dst_cols);
129+
130+
if (mlx_cnf.marker_mode) {
131+
static uint32_t prevUpdate;
132+
if (outputStart - prevUpdate > 1000) {
133+
prevUpdate = outputStart;
134+
filter_update();
135+
}
136+
DrawTemperatureMarker();
137+
}
138+
139+
if (mlx_cnf.range_auto) {
140+
DrawTemperatureRange(2);
141+
}
142+
143+
if (state == STATE_MAIN) {
144+
gfx_printf(260 + FONT_WIDTH, LINE_HEIGHT * 3.5, "%4d", inputFinish - inputStart); // input processing time
145+
gfx_printf(260 + FONT_WIDTH, LINE_HEIGHT * 5.0, "%4d", outputPeriod); // output processing time
146+
147+
float v = 1000.0f / (float)(outputStart - prevStart);
148+
gfx_printf(260 + FONT_WIDTH, LINE_HEIGHT * 2.0, "%4.1f", v); // FPS
149+
150+
v = mlx.getTa(false);
151+
if (0.0f < v && v < 100.0f) {
152+
gfx_printf(260 + FONT_WIDTH, LINE_HEIGHT * 6.5, "%4.1f", v); // Sensor temperature
153+
}
154+
}
155+
156+
GFX_FAST(pushSprite(0, 0));
157+
GFX_FAST(deleteSprite());
158+
GFX_EXEC(endWrite());
159+
160+
// Save video
161+
if (mlx_cap.recording) {
162+
sdcard_record((uint8_t*)src[bank], sizeof(src[bank]), mlx_cap.filename);
163+
}
164+
165+
#if DEBUG
166+
// Capture screen
167+
else if (Serial.available()) {
168+
Serial.readStringUntil('\n');
169+
sdcard_save();
170+
}
171+
#endif
172+
173+
// Update processing time
174+
prevStart = outputStart;
175+
outputFinish = millis();
176+
outputPeriod = outputFinish - outputStart;
177+
}
178+
}
179+
180+
void setup() {
181+
// Prevent blocking when the USB cable is not connected to PC
182+
DBG_EXEC(Serial.begin(115200));
183+
DBG_EXEC(delay(500));
184+
185+
// Initialize peripherals
186+
mlx_setup();
187+
gfx_setup();
188+
sdcard_setup();
189+
touch_setup();
190+
widget_setup();
191+
192+
// Initialize interpolation
193+
interpolate_setup(mlx_cnf.interpolation);
194+
195+
// Start tasks
196+
#if ENA_MULTITASKING
197+
task_setup(ProcessInput, ProcessOutput);
198+
#endif
199+
}
200+
201+
void loop() {
202+
#if ENA_MULTITASKING
203+
delay(1000);
204+
#else
205+
uint32_t inputStart = millis();
206+
ProcessInput(0); // always use bank 0
207+
ProcessOutput(0, inputStart, millis());
208+
#endif
209+
}

‎User_Setup.h

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/*================================================================================
2+
* TFT_eSPI configuration
3+
* https://github.com/espressif/arduino-esp32/tree/master/variants
4+
*================================================================================*/
5+
#include "./pin_assign.h"
6+
7+
// variants/jczn_2432s028r/pins_arduino.h
8+
#if defined (CYD_TP_CS)
9+
// false: Panel driver: ILI9341 (micro-USB x 1 type)
10+
// true : Panel driver: ST7789 (micro-USB x 1 + USB-C x 1 type)
11+
#define DISPLAY_CYD_2USB true
12+
#include "./boards/Setup_CYD_2432S028R.h"
13+
14+
// variants/XIAO_ESP32S3/pins_arduino.h
15+
#elif defined (ARDUINO_XIAO_ESP32S3)
16+
#include "./boards/Setup_XIAO_ESP32S3_ST7789.h"
17+
#endif

‎boards/LGFX_CYD_2432S028R.hpp

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
#pragma once
2+
3+
#include <LovyanGFX.hpp>
4+
5+
// Example of settings for LovyanGFX on ESP32
6+
// https://github.com/espressif/arduino-esp32/blob/master/variants/jczn_2432s028r/pins_arduino.h
7+
8+
/*
9+
Copy this file, give it a new name, and change the settings to suit your environment.
10+
The created file can be used by including it in your user program.
11+
12+
The new file can also be placed in the "lgfx_user" folder of the library,
13+
but please note that in this case it may be deleted when the library is updated.
14+
15+
To operate safely, make a backup or place it in the user project folder.
16+
//*/
17+
18+
// false: Panel driver: ILI9341 (micro-USB x 1 type)
19+
// true : Panel driver: ST7789 (micro-USB x 1 + USB-C x 1 type)
20+
#ifndef DISPLAY_CYD_2USB
21+
#error DISPLAY_CYD_2USB should be defined.
22+
#endif
23+
24+
/// Create a class for your own settings by deriving from LGFX_Device.
25+
class LGFX : public lgfx::LGFX_Device
26+
{
27+
/*
28+
You can change the class name from "LGFX" to a different name.
29+
When using with AUTODETECT, "LGFX" is used, so change it to a name other than LGFX.
30+
Also, if you are using multiple panels at the same time, give each panel a different name.
31+
If you change the class name, you must also change the constructor name to the same name.
32+
33+
You can name it whatever you like, but in case the number of settings increases,
34+
for example, if you are setting up an SPI-connected ILI9341 with ESP32 DevKit-C,
35+
you can name it like LGFX_DevKitC_SPI_ILI9341 to match the file name and class name,
36+
which will make it less confusing when using it.
37+
//*/
38+
39+
// Select an instance that matches the type of panel.
40+
//lgfx::Panel_GC9A01 _panel_instance;
41+
//lgfx::Panel_GDEW0154M09 _panel_instance;
42+
//lgfx::Panel_HX8357B _panel_instance;
43+
//lgfx::Panel_HX8357D _panel_instance;
44+
//lgfx::Panel_ILI9163 _panel_instance;
45+
//lgfx::Panel_ILI9341 _panel_instance;
46+
//lgfx::Panel_ILI9342 _panel_instance;
47+
//lgfx::Panel_ILI9481 _panel_instance;
48+
//lgfx::Panel_ILI9486 _panel_instance;
49+
//lgfx::Panel_ILI9488 _panel_instance;
50+
//lgfx::Panel_IT8951 _panel_instance;
51+
//lgfx::Panel_RA8875 _panel_instance;
52+
//lgfx::Panel_SH110x _panel_instance; // SH1106, SH1107
53+
//lgfx::Panel_SSD1306 _panel_instance;
54+
//lgfx::Panel_SSD1327 _panel_instance;
55+
//lgfx::Panel_SSD1331 _panel_instance;
56+
//lgfx::Panel_SSD1351 _panel_instance; // SSD1351, SSD1357
57+
//lgfx::Panel_SSD1963 _panel_instance;
58+
//lgfx::Panel_ST7735 _panel_instance;
59+
//lgfx::Panel_ST7735S _panel_instance;
60+
//lgfx::Panel_ST7789 _panel_instance;
61+
//lgfx::Panel_ST7796 _panel_instance;
62+
63+
#if DISPLAY_CYD_2USB
64+
lgfx::Panel_ST7789 _panel_instance;
65+
#else
66+
lgfx::Panel_ILI9341 _panel_instance;
67+
#endif
68+
69+
// Select an instance that matches the type of bus for your panel.
70+
lgfx::Bus_SPI _bus_instance; // SPI bus instance
71+
//lgfx::Bus_I2C _bus_instance; // I2C bus instance (ESP32 only)
72+
//lgfx::Bus_Parallel8 _bus_instance; // 8-bit parallel bus instance (ESP32 only)
73+
74+
// If backlight control is possible, set an instance. (Delete if not needed)
75+
lgfx::Light_PWM _light_instance;
76+
77+
// Select an instance that matches the touch screen type. (Delete if not needed)
78+
//lgfx::Touch_CST816S _touch_instance;
79+
//lgfx::Touch_FT5x06 _touch_instance; // FT5206, FT5306, FT5406, FT6206, FT6236, FT6336, FT6436
80+
//lgfx::Touch_GSL1680E_800x480 _touch_instance; // GSL_1680E, 1688E, 2681B, 2682B
81+
//lgfx::Touch_GSL1680F_800x480 _touch_instance;
82+
//lgfx::Touch_GSL1680F_480x272 _touch_instance;
83+
//lgfx::Touch_GSLx680_320x320 _touch_instance;
84+
//lgfx::Touch_GT911 _touch_instance;
85+
//lgfx::Touch_STMPE610 _touch_instance;
86+
//lgfx::Touch_TT21xxx _touch_instance; // TT21100
87+
lgfx::Touch_XPT2046 _touch_instance;
88+
89+
public:
90+
91+
// Create a constructor and set various settings here.
92+
// When you change the class name, specify the same name for the constructor.
93+
LGFX(void)
94+
{
95+
{ //Sets the bus control.
96+
auto cfg = _bus_instance.config(); // Get the bus configuration structure.
97+
98+
// SPI bus settings
99+
cfg.spi_host = HSPI_HOST; // Select the SPI (ESP32-S2,C3: SPI2_HOST or SPI3_HOST / ESP32: VSPI_HOST or HSPI_HOST)
100+
// Due to the ESP-IDF version upgrade, the VSPI_HOST and HSPI_HOST are deprecated, so if an error occurs, use SPI2_HOST and SPI3_HOST instead.
101+
cfg.spi_mode = 0; // SPI communication mode (0 to 3)
102+
#if DISPLAY_CYD_2USB
103+
cfg.freq_write = 80000000; // SPI clock for transmit (Maximum 80MHz, rounded to an integer value of 80MHz)
104+
#else
105+
cfg.freq_write = 40000000; // SPI clock for transmit (Maximum 80MHz, rounded to an integer value of 80MHz)
106+
#endif
107+
cfg.freq_read = 16000000; // SPI clock for receive
108+
cfg.spi_3wire = false; // Set to true if receive on the MOSI pin
109+
cfg.use_lock = true; // Set to true if transaction lock is used
110+
cfg.dma_channel = SPI_DMA_CH_AUTO; // Set the DMA channel (0=DMA not used / 1=1ch / 2=2ch / SPI_DMA_CH_AUTO=automatic)
111+
// Due to the ESP-IDF version upgrade, SPI_DMA_CH_AUTO is recommended. Specifying 1ch or 2ch is no longer recommended.
112+
cfg.pin_sclk = CYD_TFT_SCK; // Set the SPI SCLK pin
113+
cfg.pin_mosi = CYD_TFT_MOSI; // Set the SPI MOSI pin
114+
cfg.pin_miso = CYD_TFT_MISO; // Set the SPI MISO pin (-1 = disable)
115+
cfg.pin_dc = CYD_TFT_DC; // Set the SPI D/C pin (-1 = disable)
116+
// When you use a common SPI bus with the SD card, be sure to set MISO and do not omit it.
117+
//*/
118+
/*
119+
// I2C bus settings
120+
cfg.i2c_port = 0; // Select the I2C port to use (0 or 1)
121+
cfg.freq_write = 400000; // Clock for transmit
122+
cfg.freq_read = 400000; // Clock for receive
123+
cfg.pin_sda = 21; // SDA pin number
124+
cfg.pin_scl = 22; // SCL pin number
125+
cfg.i2c_addr = 0x3C; // I2C device address
126+
//*/
127+
/*
128+
// 8-bit parallel bus settings
129+
cfg.i2s_port = I2S_NUM_0; // Select the I2S port to use (I2S_NUM_0 or I2S_NUM_1) (Use the I2S LCD mode of ESP32)
130+
cfg.freq_write = 20000000; // Clock for transmit (Maximum 20MHz, rounded to an integer division of 80MHz)
131+
cfg.pin_wr = 4; // WR pin number
132+
cfg.pin_rd = 2; // RD pin number
133+
cfg.pin_rs = 15; // RS(D/C) pin number
134+
cfg.pin_d0 = 12; // D0 pin number
135+
cfg.pin_d1 = 13; // D1 pin number
136+
cfg.pin_d2 = 26; // D2 pin number
137+
cfg.pin_d3 = 25; // D3 pin number
138+
cfg.pin_d4 = 17; // D4 pin number
139+
cfg.pin_d5 = 16; // D5 pin number
140+
cfg.pin_d6 = 27; // D6 pin number
141+
cfg.pin_d7 = 14; // D7 pin number
142+
//*/
143+
144+
_bus_instance.config(cfg); // Configure setting values in the bus.
145+
_panel_instance.setBus(&_bus_instance); // Set the bus on the panel.
146+
}
147+
148+
{ // Configure the display panel control settings.
149+
auto cfg = _panel_instance.config(); // Get the structure for display panel settings.
150+
151+
cfg.pin_cs = CYD_TFT_CS; // CS pin number (-1 = disable)
152+
cfg.pin_rst = -1; // RST pin number (-1 = disable) CYD_TFT_RS = CYD_TFT_CS, RESET is connected to board RST
153+
cfg.pin_busy = -1; // BUSY pin number (-1 = disable)
154+
155+
// The following are set to general values, so try commenting out if you are unsure of.
156+
157+
cfg.panel_width = 240; // Panel width
158+
cfg.panel_height = 320; // Panel height
159+
cfg.offset_x = 0; // Panel offset in X direction
160+
cfg.offset_y = 0; // Panel offset in Y direction
161+
#if DISPLAY_CYD_2USB
162+
cfg.offset_rotation = 0; // Rotation direction offset 0~7 (4~7 are upside down)
163+
cfg.dummy_read_pixel = 16; // Number of dummy read bits before pixel read
164+
#else
165+
cfg.offset_rotation = 2; // Rotation direction offset 0~7 (4~7 are upside down)
166+
cfg.dummy_read_pixel = 8; // Number of dummy read bits before pixel read
167+
#endif
168+
cfg.dummy_read_bits = 1; // Number of dummy read bits before reading non-pixel data
169+
cfg.readable = true; // Set to true if data can be read
170+
cfg.invert = false; // Set to true if the panel is inverted
171+
cfg.rgb_order = false; // Set to true if the red and blue of the panel are swapped
172+
cfg.dlen_16bit = false; // Set to true if the panel transmit data in 16-bit via 16-bit parallel or SPI
173+
cfg.bus_shared = false; // Set to true if the bus is shared with the SD card (The bus is controlled for drawJpg etc.)
174+
175+
// Set the following only if your display is misaligned, such as ST7735 or ILI9163, which have variable pixel counts.
176+
cfg.memory_width = 240; // Maximum width supported by driver IC
177+
cfg.memory_height = 320; // Maximum height supported by driver IC
178+
179+
_panel_instance.config(cfg);
180+
}
181+
182+
//*
183+
{ // Set the backlight control. (Delete if not required)
184+
auto cfg = _light_instance.config(); // Get the backlight setting structure
185+
186+
cfg.pin_bl = CYD_TFT_BL; // Backlight pin number
187+
cfg.invert = false; // Set to true if the backlight brightness is inverted
188+
cfg.freq = 12000; // Backlight PWM frequency
189+
cfg.pwm_channel = 7; // The PWM channel number
190+
191+
_light_instance.config(cfg);
192+
_panel_instance.setLight(&_light_instance); // Set the backlight on the panel.
193+
}
194+
//*/
195+
196+
//
197+
{ // Configure touch screen control (delete if not needed)
198+
auto cfg = _touch_instance.config();
199+
200+
cfg.x_min = 240; // Minimum X value (raw value) from touch screen
201+
cfg.x_max = 3800; // Maximum X value (raw value) from touch screen
202+
cfg.y_min = 3700; // Minimum Y value (raw value) from touch screen
203+
cfg.y_max = 200; // Maximum Y value (raw value) from touch screen
204+
cfg.pin_int = CYD_TP_IRQ; // Interrupt pin number
205+
cfg.bus_shared = false; // Set to true if the bus shared with the screen
206+
#if DISPLAY_CYD_2USB
207+
cfg.offset_rotation = 2; // Adjust when display and touch orientation do not match (0~7)
208+
#else
209+
cfg.offset_rotation = 0; // Adjust when display and touch orientation do not match (0~7)
210+
#endif
211+
212+
// For SPI connection
213+
cfg.spi_host = -1; // Select the SPI (HSPI_HOST or VSPI_HOST) or only XPT2046 can be set to -1.
214+
cfg.freq = 1000000; // Set the SPI clock
215+
cfg.pin_sclk = CYD_TP_CLK; // SCLK pin number
216+
cfg.pin_mosi = CYD_TP_MOSI; // MOSI pin number
217+
cfg.pin_miso = CYD_TP_MISO; // MISO pin number
218+
cfg.pin_cs = CYD_TP_CS; // CS pin number
219+
220+
// For I2C connection
221+
//cfg.i2c_port = 1; // Select the I2C (0 or 1)
222+
//cfg.i2c_addr = 0x38; // I2C device addres
223+
//cfg.pin_sda = 23; // SDA pin number
224+
//cfg.pin_scl = 32; // SCL pin number
225+
//cfg.freq = 400000; // Set the I2C clock
226+
227+
_touch_instance.config(cfg);
228+
_panel_instance.setTouch(&_touch_instance); // Set the touch screen on the panel.
229+
}
230+
//*/
231+
232+
setPanel(&_panel_instance); // Set the panel to be used.
233+
}
234+
};

‎boards/LGFX_XIAO_ESP32S3_ST7789.hpp

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
#pragma once
2+
3+
#define LGFX_USE_V1
4+
#include <LovyanGFX.hpp>
5+
6+
// Configuration to share the SPI bus host with SD
7+
// https://github.com/espressif/esp-idf/blob/master/components/esp_driver_sdspi/include/driver/sdspi_host.h#L23-L29
8+
#include <driver/sdspi_host.h>
9+
#define SPI_HOST SDSPI_DEFAULT_HOST
10+
11+
// ESP32でLovyanGFXを独自設定で利用する場合の設定例
12+
13+
/*
14+
このファイルを複製し、新しい名前を付けて、環境に合わせて設定内容を変更してください。
15+
作成したファイルをユーザープログラムからincludeすることで利用可能になります。
16+
17+
複製したファイルはライブラリのlgfx_userフォルダに置いて利用しても構いませんが、
18+
その場合はライブラリのアップデート時に削除される可能性があるのでご注意ください。
19+
20+
安全に運用したい場合はバックアップを作成しておくか、ユーザープロジェクトのフォルダに置いてください。
21+
//*/
22+
23+
24+
/// 独自の設定を行うクラスを、LGFX_Deviceから派生して作成します。
25+
class LGFX : public lgfx::LGFX_Device
26+
{
27+
/*
28+
クラス名は"LGFX"から別の名前に変更しても構いません。
29+
AUTODETECTと併用する場合は"LGFX"は使用されているため、LGFX以外の名前に変更してください。
30+
また、複数枚のパネルを同時使用する場合もそれぞれに異なる名前を付けてください。
31+
※ クラス名を変更する場合はコンストラクタの名前も併せて同じ名前に変更が必要です。
32+
33+
名前の付け方は自由に決めて構いませんが、設定が増えた場合を想定し、
34+
例えばESP32 DevKit-CでSPI接続のILI9341の設定を行った場合、
35+
LGFX_DevKitC_SPI_ILI9341
36+
のような名前にし、ファイル名とクラス名を一致させておくことで、利用時に迷いにくくなります。
37+
//*/
38+
39+
40+
// 接続するパネルの型にあったインスタンスを用意します。
41+
//lgfx::Panel_GC9A01 _panel_instance;
42+
//lgfx::Panel_GDEW0154M09 _panel_instance;
43+
//lgfx::Panel_HX8357B _panel_instance;
44+
//lgfx::Panel_HX8357D _panel_instance;
45+
//lgfx::Panel_ILI9163 _panel_instance;
46+
//lgfx::Panel_ILI9341 _panel_instance;
47+
//lgfx::Panel_ILI9342 _panel_instance;
48+
//lgfx::Panel_ILI9481 _panel_instance;
49+
//lgfx::Panel_ILI9486 _panel_instance;
50+
//lgfx::Panel_ILI9488 _panel_instance;
51+
//lgfx::Panel_IT8951 _panel_instance;
52+
//lgfx::Panel_RA8875 _panel_instance;
53+
//lgfx::Panel_SH110x _panel_instance; // SH1106, SH1107
54+
//lgfx::Panel_SSD1306 _panel_instance;
55+
//lgfx::Panel_SSD1327 _panel_instance;
56+
//lgfx::Panel_SSD1331 _panel_instance;
57+
//lgfx::Panel_SSD1351 _panel_instance; // SSD1351, SSD1357
58+
//lgfx::Panel_SSD1963 _panel_instance;
59+
//lgfx::Panel_ST7735 _panel_instance;
60+
//lgfx::Panel_ST7735S _panel_instance;
61+
lgfx::Panel_ST7789 _panel_instance;
62+
//lgfx::Panel_ST7796 _panel_instance;
63+
64+
65+
// パネルを接続するバスの種類にあったインスタンスを用意します。
66+
lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス
67+
//lgfx::Bus_I2C _bus_instance; // I2Cバスのインスタンス (ESP32のみ)
68+
//lgfx::Bus_Parallel8 _bus_instance; // 8ビットパラレルバスのインスタンス (ESP32のみ)
69+
70+
// バックライト制御が可能な場合はインスタンスを用意します。(必要なければ削除)
71+
//lgfx::Light_PWM _light_instance;
72+
73+
// タッチスクリーンの型にあったインスタンスを用意します。(必要なければ削除)
74+
//lgfx::Touch_CST816S _touch_instance;
75+
//lgfx::Touch_FT5x06 _touch_instance; // FT5206, FT5306, FT5406, FT6206, FT6236, FT6336, FT6436
76+
//lgfx::Touch_GSL1680E_800x480 _touch_instance; // GSL_1680E, 1688E, 2681B, 2682B
77+
//lgfx::Touch_GSL1680F_800x480 _touch_instance;
78+
//lgfx::Touch_GSL1680F_480x272 _touch_instance;
79+
//lgfx::Touch_GSLx680_320x320 _touch_instance;
80+
//lgfx::Touch_GT911 _touch_instance;
81+
//lgfx::Touch_STMPE610 _touch_instance;
82+
//lgfx::Touch_TT21xxx _touch_instance; // TT21100
83+
lgfx::Touch_XPT2046 _touch_instance;
84+
85+
public:
86+
87+
// コンストラクタを作成し、ここで各種設定を行います。
88+
// クラス名を変更した場合はコンストラクタも同じ名前を指定してください。
89+
LGFX(void)
90+
{
91+
{ // バス制御の設定を行います。
92+
auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。
93+
94+
// SPIバスの設定
95+
cfg.spi_host = SPI_HOST; // 使用するSPIを選択 ESP32-S2,C3 : SPI2_HOST or SPI3_HOST / ESP32 : VSPI_HOST or HSPI_HOST
96+
// ※ ESP-IDFバージョンアップに伴い、VSPI_HOST , HSPI_HOSTの記述は非推奨になるため、エラーが出る場合は代わりにSPI2_HOST , SPI3_HOSTを使用してください。
97+
cfg.spi_mode = SPI_MODE; // SPI通信モードを設定 (0 ~ 3)
98+
cfg.freq_write = SPI_FREQUENCY; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
99+
cfg.freq_read = SPI_READ_FREQUENCY;// 受信時のSPIクロック
100+
cfg.spi_3wire = false; // 受信をMOSIピンで行う場合はtrueを設定
101+
cfg.use_lock = true; // トランザクションロックを使用する場合はtrueを設定
102+
cfg.dma_channel = SPI_DMA_CH_AUTO; // 使用するDMAチャンネルを設定 (0=DMA不使用 / 1=1ch / 2=ch / SPI_DMA_CH_AUTO=自動設定)
103+
// ※ ESP-IDFバージョンアップに伴い、DMAチャンネルはSPI_DMA_CH_AUTO(自動設定)が推奨になりました。1ch,2chの指定は非推奨になります。
104+
cfg.pin_sclk = TFT_SCLK; // SPIのSCLKピン番号を設定
105+
cfg.pin_mosi = TFT_MOSI; // SPIのMOSIピン番号を設定
106+
cfg.pin_miso = TFT_MISO; // SPIのMISOピン番号を設定 (-1 = disable)
107+
cfg.pin_dc = TFT_DC; // SPIのD/Cピン番号を設定 (-1 = disable)
108+
// SDカードと共通のSPIバスを使う場合、MISOは省略せず必ず設定してください。
109+
//*/
110+
/*
111+
// I2Cバスの設定
112+
cfg.i2c_port = 0; // 使用するI2Cポートを選択 (0 or 1)
113+
cfg.freq_write = 400000; // 送信時のクロック
114+
cfg.freq_read = 400000; // 受信時のクロック
115+
cfg.pin_sda = 21; // SDAを接続しているピン番号
116+
cfg.pin_scl = 22; // SCLを接続しているピン番号
117+
cfg.i2c_addr = 0x3C; // I2Cデバイスのアドレス
118+
//*/
119+
/*
120+
// 8ビットパラレルバスの設定
121+
cfg.i2s_port = I2S_NUM_0; // 使用するI2Sポートを選択 (I2S_NUM_0 or I2S_NUM_1) (ESP32のI2S LCDモードを使用します)
122+
cfg.freq_write = 20000000; // 送信クロック (最大20MHz, 80MHzを整数で割った値に丸められます)
123+
cfg.pin_wr = 4; // WR を接続しているピン番号
124+
cfg.pin_rd = 2; // RD を接続しているピン番号
125+
cfg.pin_rs = 15; // RS(D/C)を接続しているピン番号
126+
cfg.pin_d0 = 12; // D0を接続しているピン番号
127+
cfg.pin_d1 = 13; // D1を接続しているピン番号
128+
cfg.pin_d2 = 26; // D2を接続しているピン番号
129+
cfg.pin_d3 = 25; // D3を接続しているピン番号
130+
cfg.pin_d4 = 17; // D4を接続しているピン番号
131+
cfg.pin_d5 = 16; // D5を接続しているピン番号
132+
cfg.pin_d6 = 27; // D6を接続しているピン番号
133+
cfg.pin_d7 = 14; // D7を接続しているピン番号
134+
//*/
135+
136+
_bus_instance.config(cfg); // 設定値をバスに反映します。
137+
_panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
138+
}
139+
140+
{ // 表示パネル制御の設定を行います。
141+
auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。
142+
143+
cfg.pin_cs = TFT_CS; // CSが接続されているピン番号 (-1 = disable)
144+
cfg.pin_rst = TFT_RST; // RSTが接続されているピン番号 (-1 = disable)
145+
cfg.pin_busy = -1; // BUSYが接続されているピン番号 (-1 = disable)
146+
147+
// ※ 以下の設定値はパネル毎に一般的な初期値が設定されていますので、不明な項目はコメントアウトして試してみてください。
148+
149+
cfg.panel_width = TFT_WIDTH; // 実際に表示可能な幅
150+
cfg.panel_height = TFT_HEIGHT; // 実際に表示可能な高さ
151+
cfg.offset_x = 0; // パネルのX方向オフセット量
152+
cfg.offset_y = 0; // パネルのY方向オフセット量
153+
cfg.offset_rotation = 0; // 回転方向の値のオフセット 0~7 (4~7は上下反転)
154+
cfg.dummy_read_pixel = 8; // ピクセル読出し前のダミーリードのビット数
155+
cfg.dummy_read_bits = 1; // ピクセル以外のデータ読出し前のダミーリードのビット数
156+
cfg.readable = true; // データ読出しが可能な場合 trueに設定
157+
cfg.invert = false; // パネルの明暗が反転してしまう場合 trueに設定
158+
cfg.rgb_order = false; // パネルの赤と青が入れ替わってしまう場合 trueに設定
159+
cfg.dlen_16bit = false; // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
160+
cfg.bus_shared = true; // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)
161+
162+
// 以下はST7735やILI9163のようにピクセル数が可変のドライバで表示がずれる場合にのみ設定してください。
163+
// cfg.memory_width = 240; // ドライバICがサポートしている最大の幅
164+
// cfg.memory_height = 320; // ドライバICがサポートしている最大の高さ
165+
166+
_panel_instance.config(cfg);
167+
}
168+
169+
/*
170+
{ // バックライト制御の設定を行います。(必要なければ削除)
171+
auto cfg = _light_instance.config(); // バックライト設定用の構造体を取得します。
172+
173+
cfg.pin_bl = 32; // バックライトが接続されているピン番号
174+
cfg.invert = false; // バックライトの輝度を反転させる場合 true
175+
cfg.freq = 44100; // バックライトのPWM周波数
176+
cfg.pwm_channel = 7; // 使用するPWMのチャンネル番号
177+
178+
_light_instance.config(cfg);
179+
_panel_instance.setLight(&_light_instance); // バックライトをパネルにセットします。
180+
}
181+
*/
182+
183+
//*
184+
{ // タッチスクリーン制御の設定を行います。(必要なければ削除)
185+
auto cfg = _touch_instance.config();
186+
187+
cfg.x_min = 240; // タッチスクリーンから得られる最小のX値(生の値)
188+
cfg.x_max = 3800; // タッチスクリーンから得られる最大のX値(生の値)
189+
cfg.y_min = 3700; // タッチスクリーンから得られる最小のY値(生の値)
190+
cfg.y_max = 200; // タッチスクリーンから得られる最大のY値(生の値)
191+
cfg.pin_int = TOUCH_IRQ; // INTが接続されているピン番号
192+
cfg.bus_shared = true; // 画面と共通のバスを使用している場合 trueを設定
193+
cfg.offset_rotation = 0; // 表示とタッチの向きのが一致しない場合の調整 0~7の値で設定
194+
195+
// SPI接続の場合
196+
cfg.spi_host = SPI_HOST; // 使用するSPIを選択 (HSPI_HOST or VSPI_HOST)
197+
cfg.freq = SPI_TOUCH_FREQUENCY; // SPIクロックを設定
198+
cfg.pin_sclk = TFT_SCLK; // SCLKが接続されているピン番号
199+
cfg.pin_mosi = TFT_MOSI; // MOSIが接続されているピン番号
200+
cfg.pin_miso = TFT_MISO; // MISOが接続されているピン番号
201+
cfg.pin_cs = TOUCH_CS; // CSが接続されているピン番号
202+
203+
// I2C接続の場合
204+
// cfg.i2c_port = 1; // 使用するI2Cを選択 (0 or 1)
205+
// cfg.i2c_addr = 0x38; // I2Cデバイスアドレス番号
206+
// cfg.pin_sda = 23; // SDAが接続されているピン番号
207+
// cfg.pin_scl = 32; // SCLが接続されているピン番号
208+
// cfg.freq = 400000; // I2Cクロックを設定
209+
210+
_touch_instance.config(cfg);
211+
_panel_instance.setTouch(&_touch_instance); // タッチスクリーンをパネルにセットします。
212+
}
213+
//*/
214+
215+
setPanel(&_panel_instance); // 使用するパネルをセットします。
216+
}
217+
};

‎boards/Setup_CYD_2432S028R.h

+409
Large diffs are not rendered by default.

‎boards/Setup_XIAO_ESP32S3_ST7789.h

+412
Large diffs are not rendered by default.

‎draw.hpp

+527
Large diffs are not rendered by default.

‎filter.hpp

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*================================================================================
2+
* Multitasking helper functions
3+
*================================================================================*/
4+
#define TIME_CONSTANT 3.0f // [sec]
5+
6+
/*--------------------------------------------------------------------------------
7+
* Low pass filter (x: input, T: sampling time [sec])
8+
*--------------------------------------------------------------------------------*/
9+
typedef struct {
10+
float y;
11+
float filter(float x, const float T) {
12+
return (y += (T / (T + TIME_CONSTANT) * (x - y)));
13+
}
14+
} LowPass_t;
15+
16+
static LowPass_t lp_min, lp_max, lp_pick;
17+
18+
/*--------------------------------------------------------------------------------
19+
* Set the initial value for the low-pass filter
20+
*--------------------------------------------------------------------------------*/
21+
void filter_reset(void) {
22+
lp_min.y = MINTEMP;
23+
lp_max.y = MAXTEMP;
24+
lp_pick.y = (MINTEMP + MAXTEMP) / 2;
25+
}
26+
27+
/*--------------------------------------------------------------------------------
28+
* Measure temperature and update it's range & min/max points
29+
*--------------------------------------------------------------------------------*/
30+
#if false
31+
#define CHECK_VALUE
32+
#include <math.h>
33+
#endif
34+
35+
typedef struct {
36+
uint16_t x, y;
37+
float v;
38+
} Temperature_t;
39+
40+
static Temperature_t te_min, te_max, _te_min, _te_max, te_pick;
41+
42+
void filter_update(void) {
43+
_te_min = te_min;
44+
_te_max = te_max;
45+
}
46+
47+
void filter_temperature(float *src) {
48+
// Measure the temperature at the picked up point
49+
if (te_pick.x != 0 || te_pick.y != 0) {
50+
te_pick.v = src[te_pick.x + (te_pick.y * MLX90640_COLS)];
51+
te_pick.v = lp_pick.filter(te_pick.v, mlx_cnf.sampling_period);
52+
}
53+
54+
// Measure min/max temperature
55+
te_min.v = 999.0f;
56+
te_max.v = -999.0f;
57+
58+
for (uint16_t y = 0; y < MLX90640_ROWS; ++y) {
59+
for (uint16_t x = 0; x < MLX90640_COLS; ++x, ++src) {
60+
float t = *src;
61+
#ifdef CHECK_VALUE
62+
if (isinf(t) || isnan(t) || t < -30.0f || 300.0f < t) {
63+
continue;
64+
}
65+
#endif
66+
if (t < te_min.v) { te_min.x = x; te_min.y = y; te_min.v = t; } else
67+
if (t > te_max.v) { te_max.x = x; te_max.y = y; te_max.v = t; }
68+
}
69+
}
70+
71+
if (mlx_cnf.range_auto) {
72+
#define RANGE_STEP (1) // --> 'TERMOGRAPH_STEP' in widgets.hpp
73+
mlx_cnf.range_min = ((int)((float)lp_min.filter(te_min.v, mlx_cnf.sampling_period) / (float)RANGE_STEP) + 0.5f) * RANGE_STEP;
74+
mlx_cnf.range_max = ((int)((float)lp_max.filter(te_max.v, mlx_cnf.sampling_period) / (float)RANGE_STEP) + 0.5f) * RANGE_STEP;
75+
76+
// debug for serial ploter
77+
// DBG_EXEC(printf("%4.1f, %4.1f, %4.1f, %4.1f\n", te_min.v, te_max.v, lp_min.y, lp_max.y));
78+
}
79+
}

‎gfx.hpp

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*================================================================================
2+
* GFX Library
3+
* For the 'drawBmpFile()' function to work correctly, LovyanGFX requires
4+
* that the SD library header file be included before '<LovyanGFX.hpp>`.
5+
*================================================================================*/
6+
#include <SD.h>
7+
#include <SPI.h>
8+
9+
/*--------------------------------------------------------------------------------
10+
* LovyanGFX
11+
* https://github.com/lovyan03/LovyanGFX
12+
*--------------------------------------------------------------------------------*/
13+
#ifdef USE_LOVYAN_GFX
14+
15+
#define SCREEN_ROTATION 3
16+
#define GFX_EXEC(x) lcd.x
17+
#define GFX_FAST(x) lcd_sprite.x
18+
19+
/*--------------------------------------------------
20+
* variants/jczn_2432s028r/pins_arduino.h
21+
*--------------------------------------------------*/
22+
#if defined (CYD_TP_CS)
23+
#if USE_AUTODETECT
24+
#define LGFX_AUTODETECT
25+
#include <LovyanGFX.h>
26+
#else
27+
#include <LovyanGFX.h>
28+
// false: (micro-USB x 1 type, ILI9341)
29+
// true : (micro-USB x 1 + USB-C x 1 type, ST7789)
30+
#define DISPLAY_CYD_2USB true
31+
#include "./boards/LGFX_CYD_2432S028R.hpp"
32+
#endif
33+
34+
/*--------------------------------------------------
35+
* variants/XIAO_ESP32S3/pins_arduino.h
36+
*--------------------------------------------------*/
37+
#elif defined (ARDUINO_XIAO_ESP32S3)
38+
#include <LovyanGFX.h>
39+
#include "./boards/LGFX_XIAO_ESP32S3_ST7789.hpp"
40+
41+
/*--------------------------------------------------
42+
* variants/your_board_type/pins_arduino.h
43+
*--------------------------------------------------*/
44+
#else
45+
#endif
46+
47+
/*--------------------------------------------------------------------------------
48+
* TFT_eSPI
49+
* https://github.com/Bodmer/TFT_eSPI
50+
*--------------------------------------------------------------------------------*/
51+
#elif defined (USE_TFT_ESPI)
52+
#include <TFT_eSPI.h>
53+
#endif
54+
55+
/*--------------------------------------------------------------------------------
56+
* Definitions of graphics helpers
57+
*--------------------------------------------------------------------------------*/
58+
// Font size for setTextSize(2)
59+
#define FONT_WIDTH 12 // [px] (Device coordinate system)
60+
#define FONT_HEIGHT 16 // [px] (Device coordinate system)
61+
#define LINE_HEIGHT 18 // [px] (FONT_HEIGHT + margin)
62+
63+
uint16_t lcd_width;
64+
uint16_t lcd_height;
65+
66+
/*--------------------------------------------------------------------------------
67+
* LovyanGFX
68+
*--------------------------------------------------------------------------------*/
69+
#if defined (LOVYANGFX_HPP_)
70+
71+
LGFX lcd;
72+
LGFX_Sprite lcd_sprite(&lcd);
73+
74+
void gfx_setup(void) {
75+
GFX_EXEC(init());
76+
GFX_EXEC(initDMA());
77+
GFX_EXEC(clear(TFT_BLACK));
78+
GFX_EXEC(setBrightness(128)); // 0 ~ 255
79+
GFX_EXEC(setRotation(SCREEN_ROTATION));
80+
GFX_EXEC(setTextColor(TFT_WHITE, TFT_BLACK));
81+
lcd_width = GFX_EXEC(width());
82+
lcd_height = GFX_EXEC(height());
83+
}
84+
85+
/*--------------------------------------------------------------------------------
86+
* TFT_eSPI
87+
*--------------------------------------------------------------------------------*/
88+
#elif defined (_TFT_eSPIH_)
89+
90+
TFT_eSPI tft = TFT_eSPI();
91+
TFT_eSprite tft_sprite(&tft);
92+
93+
#define SCREEN_ROTATION 3
94+
#define GFX_EXEC(x) tft.x
95+
#define GFX_FAST(x) tft_sprite.x
96+
#define setClipRect setViewport
97+
#define clearClipRect resetViewport
98+
99+
void gfx_setup(void) {
100+
GFX_EXEC(init());
101+
GFX_EXEC(initDMA(false)); // 'true' breaks SD card function
102+
GFX_EXEC(fillScreen(0));
103+
GFX_EXEC(setRotation(SCREEN_ROTATION));
104+
GFX_EXEC(setTextColor(TFT_WHITE, TFT_BLACK));
105+
lcd_width = GFX_EXEC(width());
106+
lcd_height = GFX_EXEC(height());
107+
}
108+
#endif // LovyanGFX or TFT_eSPI
109+
110+
/*--------------------------------------------------------------------------------
111+
* Common function
112+
*--------------------------------------------------------------------------------*/
113+
#include <stdio.h>
114+
#include <stdarg.h>
115+
116+
#ifndef BUF_SIZE
117+
#define BUF_SIZE 64
118+
#endif
119+
120+
void gfx_printf(uint16_t x, uint16_t y, const char* fmt, ...) {
121+
int len = 0;
122+
char buf[BUF_SIZE];
123+
124+
va_list arg_ptr;
125+
va_start(arg_ptr, fmt);
126+
len = vsnprintf(buf, sizeof(buf), fmt, arg_ptr);
127+
va_end(arg_ptr);
128+
129+
// use the followings:
130+
// setTextColor(foreground_color, background_color);
131+
// setTextDatum(textdatum_t::...);
132+
GFX_EXEC(drawString(buf, x, y));
133+
}

‎heatmap.h

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#if (N_HEATMAP == 256)
2+
{
3+
/* Railbow */
4+
0x480F,0x400F,0x400F,0x400F,0x4010,0x3810,0x3810,0x3810,0x3810,0x3010,0x3010,0x3010,0x2810,0x2810,0x2810,0x2810,
5+
0x2010,0x2010,0x2010,0x1810,0x1810,0x1811,0x1811,0x1011,0x1011,0x1011,0x0811,0x0811,0x0811,0x0011,0x0011,0x0011,
6+
0x0011,0x0011,0x0031,0x0031,0x0051,0x0072,0x0072,0x0092,0x00B2,0x00B2,0x00D2,0x00F2,0x00F2,0x0112,0x0132,0x0152,
7+
0x0152,0x0172,0x0192,0x0192,0x01B2,0x01D2,0x01F3,0x01F3,0x0213,0x0233,0x0253,0x0253,0x0273,0x0293,0x02B3,0x02D3,
8+
0x02D3,0x02F3,0x0313,0x0333,0x0333,0x0353,0x0373,0x0394,0x03B4,0x03D4,0x03D4,0x03F4,0x0414,0x0434,0x0454,0x0474,
9+
0x0474,0x0494,0x04B4,0x04D4,0x04F4,0x0514,0x0534,0x0534,0x0554,0x0554,0x0574,0x0574,0x0573,0x0573,0x0573,0x0572,
10+
0x0572,0x0572,0x0571,0x0591,0x0591,0x0590,0x0590,0x058F,0x058F,0x058F,0x058E,0x05AE,0x05AE,0x05AD,0x05AD,0x05AD,
11+
0x05AC,0x05AC,0x05AB,0x05CB,0x05CB,0x05CA,0x05CA,0x05CA,0x05C9,0x05C9,0x05C8,0x05E8,0x05E8,0x05E7,0x05E7,0x05E6,
12+
0x05E6,0x05E6,0x05E5,0x05E5,0x0604,0x0604,0x0604,0x0603,0x0603,0x0602,0x0602,0x0601,0x0621,0x0621,0x0620,0x0620,
13+
0x0620,0x0620,0x0E20,0x0E20,0x0E40,0x1640,0x1640,0x1E40,0x1E40,0x2640,0x2640,0x2E40,0x2E60,0x3660,0x3660,0x3E60,
14+
0x3E60,0x3E60,0x4660,0x4660,0x4E60,0x4E80,0x5680,0x5680,0x5E80,0x5E80,0x6680,0x6680,0x6E80,0x6EA0,0x76A0,0x76A0,
15+
0x7EA0,0x7EA0,0x86A0,0x86A0,0x8EA0,0x8EC0,0x96C0,0x96C0,0x9EC0,0x9EC0,0xA6C0,0xAEC0,0xAEC0,0xB6E0,0xB6E0,0xBEE0,
16+
0xBEE0,0xC6E0,0xC6E0,0xCEE0,0xCEE0,0xD6E0,0xD700,0xDF00,0xDEE0,0xDEC0,0xDEA0,0xDE80,0xDE80,0xE660,0xE640,0xE620,
17+
0xE600,0xE5E0,0xE5C0,0xE5A0,0xE580,0xE560,0xE540,0xE520,0xE500,0xE4E0,0xE4C0,0xE4A0,0xE480,0xE460,0xEC40,0xEC20,
18+
0xEC00,0xEBE0,0xEBC0,0xEBA0,0xEB80,0xEB60,0xEB40,0xEB20,0xEB00,0xEAE0,0xEAC0,0xEAA0,0xEA80,0xEA60,0xEA40,0xF220,
19+
0xF200,0xF1E0,0xF1C0,0xF1A0,0xF180,0xF160,0xF140,0xF100,0xF0E0,0xF0C0,0xF0A0,0xF080,0xF060,0xF040,0xF020,0xF800,
20+
},
21+
{
22+
/* Inferno */
23+
0x304A,0x304A,0x304A,0x304A,0x304B,0x304B,0x384B,0x384B,0x384B,0x384B,0x384C,0x384C,0x384C,0x406C,0x406C,0x406C,
24+
0x406C,0x406D,0x406D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,0x506E,0x506E,0x506E,0x506E,0x508E,0x508E,
25+
0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x608E,0x608E,0x608E,0x608E,0x608E,0x60AE,0x60AE,0x68AE,0x68AE,0x68AE,
26+
0x68AE,0x68AE,0x68AE,0x70AE,0x70AE,0x70AE,0x70CE,0x70CE,0x70CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x80CE,
27+
0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x88EE,0x88ED,0x890D,0x890D,0x890D,0x890D,0x910D,0x910D,0x910D,0x910D,
28+
0x912D,0x912D,0x992D,0x992D,0x992C,0x992C,0x992C,0x994C,0x994C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA16B,0xA16B,
29+
0xA96B,0xA96B,0xA96B,0xA96B,0xA98B,0xA98B,0xB18B,0xB18A,0xB18A,0xB18A,0xB1AA,0xB1AA,0xB1AA,0xB1AA,0xB9AA,0xB9A9,
30+
0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xC1E9,0xC1E9,0xC1E8,0xC1E8,0xC1E8,0xC1E8,0xC208,0xCA08,0xCA08,0xCA08,0xCA07,
31+
0xCA27,0xCA27,0xCA27,0xCA27,0xCA47,0xD247,0xD247,0xD247,0xD246,0xD266,0xD266,0xD266,0xD266,0xDA86,0xDA86,0xDA86,
32+
0xDA85,0xDA85,0xDAA5,0xDAA5,0xDAA5,0xDAA5,0xE2C5,0xE2C5,0xE2C5,0xE2C5,0xE2E4,0xE2E4,0xE2E4,0xE2E4,0xE304,0xE304,
33+
0xE304,0xEB24,0xEB24,0xEB24,0xEB24,0xEB43,0xEB43,0xEB43,0xEB43,0xEB63,0xEB63,0xEB63,0xEB83,0xF383,0xF383,0xF383,
34+
0xF3A3,0xF3A3,0xF3A3,0xF3C3,0xF3C3,0xF3C2,0xF3E2,0xF3E2,0xF3E2,0xF3E2,0xF402,0xF402,0xF402,0xF422,0xFC22,0xFC22,
35+
0xFC42,0xFC42,0xFC42,0xFC62,0xFC62,0xFC62,0xFC82,0xFC82,0xFC82,0xFCA2,0xFCA2,0xFCA2,0xFCC2,0xFCC2,0xFCC2,0xFCE3,
36+
0xFCE3,0xFD03,0xFD03,0xFD03,0xFD23,0xFD23,0xFD23,0xFD43,0xFD43,0xFD43,0xFD63,0xFD63,0xFD84,0xFD84,0xFD84,0xFDA4,
37+
0xFDA4,0xFDC4,0xFDC4,0xFDC4,0xFDE4,0xFDE5,0xFE05,0xFE05,0xFE05,0xFE25,0xFE25,0xFE46,0xFE46,0xFE66,0xFE66,0xFE66,
38+
0xFE87,0xFE87,0xF6A7,0xF6A7,0xF6C7,0xF6C8,0xF6C8,0xF6E8,0xF6E8,0xF709,0xF709,0xF729,0xF729,0xF74A,0xF74A,0xF74A,
39+
},
40+
#elif (N_HEATMAP == 512)
41+
{
42+
/* Railbow */
43+
44+
0x480E,0x400F,0x400F,0x400F,0x400F,0x400F,0x4010,0x4010,0x4010,0x4010,0x4010,0x3811,0x3811,0x3811,0x3811,0x3811,
45+
0x3812,0x3812,0x3812,0x3812,0x3012,0x3013,0x3013,0x3013,0x3013,0x3013,0x3014,0x3014,0x3014,0x3014,0x2814,0x2815,
46+
0x2815,0x2815,0x2815,0x2815,0x2816,0x2816,0x2816,0x2016,0x2016,0x2017,0x2017,0x2017,0x2017,0x2017,0x2018,0x2018,
47+
0x2018,0x1818,0x1818,0x1819,0x1819,0x1819,0x1819,0x1819,0x181A,0x181A,0x101A,0x101A,0x101A,0x101B,0x101B,0x101B,
48+
0x101B,0x101B,0x101C,0x081C,0x081C,0x081C,0x081C,0x081D,0x081D,0x081D,0x081D,0x081D,0x081E,0x001E,0x001E,0x001E,
49+
0x001E,0x001F,0x001F,0x001F,0x001F,0x001F,0x001F,0x003F,0x003F,0x005F,0x005F,0x007F,0x009F,0x009F,0x00BF,0x00BF,
50+
0x00DF,0x00FF,0x00FF,0x011F,0x011F,0x013F,0x015F,0x015F,0x017F,0x017F,0x019F,0x01BF,0x01BF,0x01DF,0x01DF,0x01FF,
51+
0x021F,0x021F,0x023F,0x023F,0x025F,0x027F,0x027F,0x029F,0x029F,0x02BF,0x02DF,0x02DF,0x02FF,0x02FF,0x031F,0x033F,
52+
0x033F,0x035F,0x035F,0x037F,0x039F,0x039F,0x03BF,0x03BF,0x03DF,0x03FF,0x03FF,0x041F,0x041F,0x043F,0x043F,0x045F,
53+
0x047F,0x047F,0x049F,0x049F,0x04BF,0x04DF,0x04DF,0x04FF,0x04FF,0x051F,0x053F,0x053F,0x055F,0x055F,0x057F,0x059F,
54+
0x059F,0x05BF,0x05BF,0x05DF,0x05FF,0x05FF,0x061F,0x061F,0x063F,0x065F,0x065F,0x067F,0x067F,0x069F,0x06BF,0x06BF,
55+
0x06DF,0x06DF,0x06FF,0x071F,0x071F,0x073F,0x073F,0x075F,0x077F,0x077F,0x079F,0x079F,0x07BF,0x07DF,0x07DF,0x07FF,
56+
0x07FF,0x07FF,0x07FF,0x07FF,0x07FE,0x07FE,0x07FE,0x07FD,0x07FD,0x07FD,0x07FC,0x07FC,0x07FC,0x07FC,0x07FB,0x07FB,
57+
0x07FB,0x07FA,0x07FA,0x07FA,0x07F9,0x07F9,0x07F9,0x07F9,0x07F8,0x07F8,0x07F8,0x07F7,0x07F7,0x07F7,0x07F6,0x07F6,
58+
0x07F6,0x07F6,0x07F5,0x07F5,0x07F5,0x07F4,0x07F4,0x07F4,0x07F3,0x07F3,0x07F3,0x07F3,0x07F2,0x07F2,0x07F2,0x07F1,
59+
0x07F1,0x07F1,0x07F0,0x07F0,0x07F0,0x07F0,0x07EF,0x07EF,0x07EF,0x07EE,0x07EE,0x07EE,0x07EE,0x07ED,0x07ED,0x07ED,
60+
0x07EC,0x07EC,0x07EC,0x07EB,0x07EB,0x07EB,0x07EB,0x07EA,0x07EA,0x07EA,0x07E9,0x07E9,0x07E9,0x07E8,0x07E8,0x07E8,
61+
0x07E8,0x07E7,0x07E7,0x07E7,0x07E6,0x07E6,0x07E6,0x07E5,0x07E5,0x07E5,0x07E5,0x07E4,0x07E4,0x07E4,0x07E3,0x07E3,
62+
0x07E3,0x07E2,0x07E2,0x07E2,0x07E2,0x07E1,0x07E1,0x07E1,0x07E0,0x07E0,0x07E0,0x07E0,0x07E0,0x07E0,0x0FE0,0x0FE0,
63+
0x0FE0,0x0FE0,0x17E0,0x17E0,0x17E0,0x1FE0,0x1FE0,0x1FE0,0x27E0,0x27E0,0x27E0,0x27E0,0x2FE0,0x2FE0,0x2FE0,0x37E0,
64+
0x37E0,0x37E0,0x3FE0,0x3FE0,0x3FE0,0x3FE0,0x47E0,0x47E0,0x47E0,0x4FE0,0x4FE0,0x4FE0,0x57E0,0x57E0,0x57E0,0x57E0,
65+
0x5FE0,0x5FE0,0x5FE0,0x67E0,0x67E0,0x67E0,0x6FE0,0x6FE0,0x6FE0,0x6FE0,0x77E0,0x77E0,0x77E0,0x7FE0,0x7FE0,0x7FE0,
66+
0x87E0,0x87E0,0x87E0,0x87E0,0x8FE0,0x8FE0,0x8FE0,0x97E0,0x97E0,0x97E0,0x97E0,0x9FE0,0x9FE0,0x9FE0,0xA7E0,0xA7E0,
67+
0xA7E0,0xAFE0,0xAFE0,0xAFE0,0xAFE0,0xB7E0,0xB7E0,0xB7E0,0xBFE0,0xBFE0,0xBFE0,0xC7E0,0xC7E0,0xC7E0,0xC7E0,0xCFE0,
68+
0xCFE0,0xCFE0,0xD7E0,0xD7E0,0xD7E0,0xDFE0,0xDFE0,0xDFE0,0xDFE0,0xE7E0,0xE7E0,0xE7E0,0xEFE0,0xEFE0,0xEFE0,0xF7E0,
69+
0xF7E0,0xF7E0,0xF7E0,0xFFE0,0xFFE0,0xFFE0,0xFFE0,0xFFC0,0xFFC0,0xFFA0,0xFFA0,0xFF80,0xFF60,0xFF60,0xFF40,0xFF40,
70+
0xFF20,0xFF00,0xFF00,0xFEE0,0xFEE0,0xFEC0,0xFEA0,0xFEA0,0xFE80,0xFE80,0xFE60,0xFE40,0xFE40,0xFE20,0xFE20,0xFE00,
71+
0xFDE0,0xFDE0,0xFDC0,0xFDC0,0xFDA0,0xFD80,0xFD80,0xFD60,0xFD60,0xFD40,0xFD20,0xFD20,0xFD00,0xFD00,0xFCE0,0xFCC0,
72+
0xFCC0,0xFCA0,0xFCA0,0xFC80,0xFC60,0xFC60,0xFC40,0xFC40,0xFC20,0xFC00,0xFC00,0xFBE0,0xFBE0,0xFBC0,0xFBC0,0xFBA0,
73+
0xFB80,0xFB80,0xFB60,0xFB60,0xFB40,0xFB20,0xFB20,0xFB00,0xFB00,0xFAE0,0xFAC0,0xFAC0,0xFAA0,0xFAA0,0xFA80,0xFA60,
74+
0xFA60,0xFA40,0xFA40,0xFA20,0xFA00,0xFA00,0xF9E0,0xF9E0,0xF9C0,0xF9A0,0xF9A0,0xF980,0xF980,0xF960,0xF940,0xF940,
75+
0xF920,0xF920,0xF900,0xF8E0,0xF8E0,0xF8C0,0xF8C0,0xF8A0,0xF880,0xF880,0xF860,0xF860,0xF840,0xF820,0xF820,0xF800,
76+
},
77+
{
78+
/* Inferno */
79+
0x304A,0x304A,0x304A,0x304A,0x304A,0x304A,0x304A,0x304B,0x304B,0x304B,0x304B,0x304B,0x384B,0x384B,0x384B,0x384B,
80+
0x384B,0x384B,0x384B,0x384C,0x384C,0x384C,0x384C,0x384C,0x384C,0x404C,0x406C,0x406C,0x406C,0x406C,0x406C,0x406C,
81+
0x406C,0x406D,0x406D,0x406D,0x406D,0x406D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,0x486D,
82+
0x486D,0x486D,0x486D,0x506D,0x506E,0x506E,0x506E,0x506E,0x506E,0x506E,0x506E,0x506E,0x508E,0x508E,0x508E,0x588E,
83+
0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x588E,0x608E,0x608E,0x608E,0x608E,
84+
0x608E,0x608E,0x608E,0x608E,0x608E,0x608E,0x60AE,0x60AE,0x60AE,0x68AE,0x68AE,0x68AE,0x68AE,0x68AE,0x68AE,0x68AE,
85+
0x68AE,0x68AE,0x68AE,0x68AE,0x68AE,0x70AE,0x70AE,0x70AE,0x70AE,0x70AE,0x70AE,0x70AE,0x70CE,0x70CE,0x70CE,0x70CE,
86+
0x70CE,0x70CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x78CE,0x80CE,0x80EE,
87+
0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x80EE,0x88EE,0x88EE,0x88EE,0x88ED,0x88ED,
88+
0x890D,0x890D,0x890D,0x890D,0x890D,0x890D,0x890D,0x910D,0x910D,0x910D,0x910D,0x910D,0x910D,0x910D,0x910D,0x910D,
89+
0x912D,0x912D,0x912D,0x912D,0x992D,0x992D,0x992D,0x992C,0x992C,0x992C,0x992C,0x992C,0x992C,0x992C,0x994C,0x994C,
90+
0x994C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14C,0xA14B,0xA16B,0xA16B,0xA16B,0xA96B,
91+
0xA96B,0xA96B,0xA96B,0xA96B,0xA96B,0xA96B,0xA96B,0xA96B,0xA98B,0xA98B,0xA98B,0xA98B,0xB18B,0xB18A,0xB18A,0xB18A,
92+
0xB18A,0xB18A,0xB18A,0xB18A,0xB1AA,0xB1AA,0xB1AA,0xB1AA,0xB1AA,0xB1AA,0xB1AA,0xB9AA,0xB9AA,0xB9AA,0xB9A9,0xB9A9,
93+
0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xB9C9,0xC1C9,0xC1E9,0xC1E9,0xC1E9,0xC1E9,0xC1E8,0xC1E8,
94+
0xC1E8,0xC1E8,0xC1E8,0xC1E8,0xC1E8,0xC208,0xC208,0xC208,0xCA08,0xCA08,0xCA08,0xCA08,0xCA08,0xCA08,0xCA07,0xCA27,
95+
0xCA27,0xCA27,0xCA27,0xCA27,0xCA27,0xCA27,0xCA27,0xCA27,0xCA47,0xD247,0xD247,0xD247,0xD247,0xD247,0xD247,0xD246,
96+
0xD246,0xD266,0xD266,0xD266,0xD266,0xD266,0xD266,0xD266,0xD266,0xD266,0xDA86,0xDA86,0xDA86,0xDA86,0xDA86,0xDA86,
97+
0xDA85,0xDA85,0xDA85,0xDAA5,0xDAA5,0xDAA5,0xDAA5,0xDAA5,0xDAA5,0xDAA5,0xDAA5,0xDAC5,0xE2C5,0xE2C5,0xE2C5,0xE2C5,
98+
0xE2C5,0xE2C5,0xE2C5,0xE2E4,0xE2E4,0xE2E4,0xE2E4,0xE2E4,0xE2E4,0xE2E4,0xE2E4,0xE304,0xE304,0xE304,0xE304,0xE304,
99+
0xE304,0xEB04,0xEB24,0xEB24,0xEB24,0xEB24,0xEB24,0xEB24,0xEB24,0xEB23,0xEB43,0xEB43,0xEB43,0xEB43,0xEB43,0xEB43,
100+
0xEB43,0xEB63,0xEB63,0xEB63,0xEB63,0xEB63,0xEB63,0xEB63,0xEB83,0xEB83,0xF383,0xF383,0xF383,0xF383,0xF383,0xF3A3,
101+
0xF3A3,0xF3A3,0xF3A3,0xF3A3,0xF3A3,0xF3A3,0xF3C3,0xF3C3,0xF3C3,0xF3C3,0xF3C2,0xF3C2,0xF3E2,0xF3E2,0xF3E2,0xF3E2,
102+
0xF3E2,0xF3E2,0xF3E2,0xF402,0xF402,0xF402,0xF402,0xF402,0xF402,0xF422,0xF422,0xF422,0xFC22,0xFC22,0xFC22,0xFC42,
103+
0xFC42,0xFC42,0xFC42,0xFC42,0xFC42,0xFC62,0xFC62,0xFC62,0xFC62,0xFC62,0xFC62,0xFC82,0xFC82,0xFC82,0xFC82,0xFC82,
104+
0xFC82,0xFCA2,0xFCA2,0xFCA2,0xFCA2,0xFCA2,0xFCA2,0xFCC2,0xFCC2,0xFCC2,0xFCC2,0xFCC2,0xFCC2,0xFCE3,0xFCE3,0xFCE3,
105+
0xFCE3,0xFCE3,0xFD03,0xFD03,0xFD03,0xFD03,0xFD03,0xFD03,0xFD23,0xFD23,0xFD23,0xFD23,0xFD23,0xFD43,0xFD43,0xFD43,
106+
0xFD43,0xFD43,0xFD43,0xFD63,0xFD63,0xFD63,0xFD63,0xFD63,0xFD84,0xFD84,0xFD84,0xFD84,0xFD84,0xFDA4,0xFDA4,0xFDA4,
107+
0xFDA4,0xFDA4,0xFDC4,0xFDC4,0xFDC4,0xFDC4,0xFDC4,0xFDE4,0xFDE4,0xFDE5,0xFDE5,0xFDE5,0xFE05,0xFE05,0xFE05,0xFE05,
108+
0xFE05,0xFE25,0xFE25,0xFE25,0xFE25,0xFE26,0xFE46,0xFE46,0xFE46,0xFE46,0xFE66,0xFE66,0xFE66,0xFE66,0xFE66,0xFE86,
109+
0xFE87,0xFE87,0xFE87,0xF687,0xF6A7,0xF6A7,0xF6A7,0xF6A7,0xF6C7,0xF6C8,0xF6C8,0xF6C8,0xF6C8,0xF6E8,0xF6E8,0xF6E8,
110+
0xF6E8,0xF708,0xF709,0xF709,0xF709,0xF709,0xF729,0xF729,0xF729,0xF72A,0xF74A,0xF74A,0xF74A,0xF74A,0xF74A,0xF76A,
111+
},
112+
#else
113+
#error 'N_HEATMAP' in 'MLX90649' needs to be set correctly
114+
#endif

‎interpolation.hpp

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*================================================================================
2+
* Pixel interpolation
3+
*================================================================================*/
4+
inline float get_point(float *p, const int rows, const int cols, int x, int y) __attribute__((always_inline));
5+
inline float get_point(float *p, const int rows, const int cols, int x, int y) {
6+
//if (x < 0) { x = 0; } else
7+
if (x >= cols) { x = cols - 1; }
8+
//if (y < 0) { y = 0; } else
9+
if (y >= rows) { y = rows - 1; }
10+
return p[y * cols + x];
11+
}
12+
13+
/*--------------------------------------------------------------------------------------------
14+
* Bilinear interpolation
15+
* https://algorithm.joho.info/image-processing/bi-linear-interpolation/
16+
*--------------------------------------------------------------------------------------------*/
17+
static float table_ratio[INTERPOLATE_SCALE][2];
18+
19+
void interpolate_setup(const int scale) {
20+
for (int i = 0; i < scale; ++i) {
21+
table_ratio[i][0] = (float)i / (float)scale;
22+
table_ratio[i][1] = 1.0f - table_ratio[i][0];
23+
}
24+
}
25+
26+
#define FURTHER_OPTIMIZATION true // Is the compiler smart enough?
27+
28+
void interpolate_image(float *src, const int src_rows, const int src_cols, const int dst_rows, const int dst_cols) {
29+
int X, Y;
30+
float X0Y0, X1Y0, X0Y1, X1Y1;
31+
float x_ratio_lo, x_ratio_hi;
32+
float y_ratio_lo, y_ratio_hi;
33+
const int scale = dst_rows / src_rows;
34+
#if FURTHER_OPTIMIZATION
35+
float v0, v1, w0, w1;
36+
#endif
37+
const int box_size = mlx_cnf.box_size;
38+
const uint16_t *hm = heatmap[mlx_cnf.color_scheme];
39+
40+
// Bilinear interpolation
41+
for (int y = 0; y < src_rows; ++y) {
42+
Y = y * scale;
43+
44+
for (int x = 0; x < src_cols; ++x) {
45+
X = x * scale;
46+
47+
X0Y0 = get_point(src, src_rows, src_cols, x, y );
48+
X1Y0 = get_point(src, src_rows, src_cols, x + 1, y );
49+
X0Y1 = get_point(src, src_rows, src_cols, x, y + 1);
50+
X1Y1 = get_point(src, src_rows, src_cols, x + 1, y + 1);
51+
52+
for (int j = 0; j < scale; ++j) {
53+
y_ratio_lo = table_ratio[j][0];
54+
y_ratio_hi = table_ratio[j][1];
55+
56+
#if FURTHER_OPTIMIZATION
57+
v0 = y_ratio_hi * X0Y0;
58+
v1 = y_ratio_hi * X1Y0;
59+
w0 = y_ratio_lo * X0Y1;
60+
w1 = y_ratio_lo * X1Y1;
61+
#endif
62+
for (int i = 0; i < scale; ++i) {
63+
x_ratio_lo = table_ratio[i][0];
64+
x_ratio_hi = table_ratio[i][1];
65+
66+
#if FURTHER_OPTIMIZATION
67+
float t = v0 * x_ratio_hi + v1 * x_ratio_lo +
68+
w0 * x_ratio_hi + w1 * x_ratio_lo;
69+
#else
70+
float t = y_ratio_hi * (x_ratio_hi * X0Y0 + x_ratio_lo * X1Y0) +
71+
y_ratio_lo * (x_ratio_hi * X0Y1 + x_ratio_lo * X1Y1);
72+
#endif
73+
t = min((int)t, (int)mlx_cnf.range_max);
74+
t = max((int)t, (int)mlx_cnf.range_min);
75+
76+
int colorIndex = map(t, mlx_cnf.range_min, mlx_cnf.range_max, 0, N_HEATMAP - 1);
77+
78+
#if ENA_OUTWARD_CAMERA
79+
if (box_size == 1) {
80+
GFX_FAST(drawPixel(dst_cols - 1 - (X + i), (Y + j), hm[colorIndex]));
81+
} else {
82+
GFX_FAST(fillRect((dst_cols - 1 - (X + i)) * box_size, (Y + j) * box_size, box_size, box_size, hm[colorIndex]));
83+
}
84+
#else // Selfie Camera
85+
if (box_size == 1) {
86+
GFX_FAST(drawPixel(X + i, Y + j, hm[colorIndex]));
87+
} else {
88+
GFX_FAST(fillRect((X + i) * box_size, (Y + j) * box_size, box_size, box_size, hm[colorIndex]));
89+
}
90+
#endif
91+
}
92+
}
93+
}
94+
}
95+
}

‎marker.h

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// icon-icon_marker0.png
2+
// https://lang-ship.com/tools/image2data/
3+
// RAW File Dump
4+
static constexpr unsigned char icon_marker0[230] = {
5+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
6+
0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x0e, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x23, 0x72,
7+
0x0d, 0x00, 0x00, 0x00, 0xad, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x05, 0xc1, 0x3b, 0xd2, 0xc1,
8+
0x50, 0x00, 0x86, 0xe1, 0x37, 0xc1, 0x98, 0x64, 0x05, 0xfe, 0x8b, 0x0a, 0x85, 0x05, 0x30, 0x24,
9+
0x8d, 0x45, 0x88, 0xc9, 0x71, 0x06, 0x67, 0x23, 0x34, 0xd6, 0xa0, 0x61, 0x3f, 0x32, 0x6e, 0x05,
10+
0x85, 0xb0, 0x0a, 0x44, 0xef, 0xf3, 0x3c, 0x00, 0x54, 0x3b, 0xc6, 0x74, 0xab, 0x00, 0x00, 0xc9,
11+
0x59, 0x92, 0x2e, 0x09, 0x00, 0xcc, 0x75, 0x72, 0xed, 0xf6, 0xec, 0xa8, 0x05, 0xc0, 0x50, 0x9b,
12+
0x4a, 0xcd, 0x98, 0x5a, 0x79, 0xad, 0x04, 0x82, 0xeb, 0xa9, 0x92, 0x3c, 0xa4, 0xc7, 0xa8, 0x7c,
13+
0xcc, 0x03, 0x7a, 0xb2, 0x3f, 0xcf, 0xc3, 0x60, 0xb0, 0x7f, 0xfd, 0x8e, 0xd5, 0x63, 0xa2, 0xd6,
14+
0x44, 0x31, 0x44, 0x9a, 0x36, 0x35, 0xf5, 0x3d, 0xe4, 0xf1, 0x81, 0x0f, 0x9e, 0x80, 0xbe, 0xcc,
15+
0x5f, 0x91, 0xc5, 0xd1, 0xb6, 0xf8, 0x4f, 0x15, 0x11, 0xdc, 0xf6, 0x25, 0x5b, 0x48, 0x6f, 0x5b,
16+
0xda, 0xdd, 0x43, 0x48, 0xb5, 0xf2, 0xeb, 0xce, 0xd5, 0xfd, 0x95, 0x52, 0x80, 0xa5, 0x32, 0xd3,
17+
0x68, 0xa4, 0x99, 0x96, 0x00, 0x60, 0x73, 0x49, 0xca, 0x2d, 0x00, 0x40, 0x18, 0x3b, 0x17, 0x87,
18+
0x00, 0x5f, 0x62, 0xd3, 0x4c, 0x80, 0x38, 0x4d, 0x80, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45,
19+
0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, };
20+
21+
// icon-icon_marker1.png
22+
// https://lang-ship.com/tools/image2data/
23+
// RAW File Dump
24+
static constexpr unsigned char icon_marker1[270] = {
25+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
26+
0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x98, 0xa0,
27+
0xbd, 0x00, 0x00, 0x00, 0xd5, 0x49, 0x44, 0x41, 0x54, 0x78, 0x01, 0x35, 0xce, 0x03, 0x52, 0x45,
28+
0x01, 0x00, 0x86, 0xd1, 0x2f, 0x73, 0x09, 0xcf, 0xd6, 0x30, 0xaf, 0x24, 0xd7, 0x4e, 0xb2, 0x07,
29+
0xd9, 0xe6, 0x20, 0xad, 0x23, 0x7b, 0x94, 0x5b, 0xc2, 0x33, 0xef, 0x9f, 0xcf, 0x0a, 0x0e, 0xdf,
30+
0xb0, 0x37, 0x8d, 0x8d, 0x35, 0xd9, 0xf9, 0x67, 0x59, 0x8e, 0xe8, 0x4b, 0x64, 0xd9, 0x02, 0xdf,
31+
0xaa, 0x9f, 0xf4, 0xb0, 0x96, 0xc9, 0xac, 0xdd, 0xeb, 0xb9, 0x1a, 0xc0, 0xfc, 0xa2, 0xc1, 0x4a,
32+
0x57, 0x26, 0xe3, 0xaa, 0x1c, 0xd4, 0x8b, 0x05, 0x58, 0xd5, 0x20, 0x04, 0xa5, 0x20, 0x0c, 0x6a,
33+
0x0d, 0xec, 0xd1, 0xfb, 0x62, 0x28, 0x9b, 0x9b, 0x2b, 0x83, 0xe2, 0xbb, 0x98, 0x9d, 0x26, 0x2d,
34+
0x7a, 0xdc, 0x45, 0x00, 0x45, 0x6e, 0xcf, 0x82, 0x9a, 0xd8, 0x56, 0x2a, 0x99, 0x98, 0x06, 0x98,
35+
0x4e, 0x24, 0x53, 0x3a, 0x60, 0x4f, 0x99, 0x4c, 0x7a, 0x01, 0x60, 0x21, 0x9d, 0xc9, 0xe8, 0x98,
36+
0x66, 0x2d, 0x85, 0x82, 0x25, 0x00, 0x25, 0xc1, 0xd0, 0x92, 0x9a, 0x71, 0xc6, 0x6f, 0x0a, 0xa1,
37+
0x74, 0x62, 0xa2, 0x14, 0x0a, 0xaf, 0xe3, 0x4e, 0xd8, 0x54, 0x0f, 0x04, 0xa4, 0x00, 0xf4, 0x68,
38+
0x0b, 0xb0, 0xbd, 0xab, 0xbb, 0xec, 0x3b, 0x56, 0xd6, 0xad, 0x0f, 0x1b, 0x40, 0xfd, 0xbb, 0xae,
39+
0x16, 0x32, 0x99, 0x85, 0x2b, 0xbd, 0x37, 0xc0, 0x37, 0xc7, 0x66, 0x42, 0x5f, 0x12, 0x5b, 0x0e,
40+
0xfe, 0x79, 0x3a, 0x4e, 0x4f, 0x3b, 0x3d, 0x7c, 0xfb, 0x04, 0x7f, 0xfb, 0x69, 0x12, 0x9f, 0xf2,
41+
0x94, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82, };

‎mlx.hpp

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#include <atomic>
2+
/*--------------------------------------------------------------------------------
3+
* MLX90640 instance
4+
*--------------------------------------------------------------------------------*/
5+
#include <Wire.h>
6+
#include <Adafruit_MLX90640.h>
7+
Adafruit_MLX90640 mlx;
8+
9+
/*--------------------------------------------------------------------------------
10+
* The size of thermal image (max INTERPOLATE_SCALE = 8)
11+
*--------------------------------------------------------------------------------*/
12+
#define MLX90640_COLS 32
13+
#define MLX90640_ROWS 24
14+
#define INTERPOLATED_COLS (MLX90640_COLS * 8)
15+
#define INTERPOLATED_ROWS (MLX90640_ROWS * 8)
16+
17+
/*--------------------------------------------------------------------------------
18+
* Thermography image
19+
*--------------------------------------------------------------------------------*/
20+
#if ENA_MULTITASKING
21+
float src[2][MLX90640_ROWS * MLX90640_COLS];
22+
#else
23+
float src[1][MLX90640_ROWS * MLX90640_COLS];
24+
#endif
25+
26+
/*--------------------------------------------------------------------------------
27+
* Initial values for range of temperature
28+
*--------------------------------------------------------------------------------*/
29+
#define MINTEMP 20 // Low temperature range
30+
#define MAXTEMP 35 // High temperature range
31+
32+
/*--------------------------------------------------------------------------------
33+
* MLX90640 control configuration
34+
*--------------------------------------------------------------------------------*/
35+
typedef struct MLXConfig {
36+
// Member Variables
37+
uint8_t interpolation; // 1, 2, 4, 6, 8
38+
uint8_t box_size; // 1, 2, 4, 8
39+
uint8_t refresh_rate; // sampline frequency (mlx90640_refreshrate_t)
40+
uint8_t color_scheme; // 0: rainbow, 1: inferno
41+
uint8_t marker_mode; // 0: off, 1: min/max, 2: picked up by user
42+
bool range_auto; // automatic measurement of temperature min/max
43+
int16_t range_min; // minimum temperature
44+
int16_t range_max; // maximum temperature
45+
float sampling_period; // sampling period [sec]
46+
47+
// Comparison Operators
48+
bool operator >= (const MLXConfig &RHS) {
49+
return (
50+
(interpolation != RHS.interpolation) ||
51+
(box_size != RHS.box_size ) ||
52+
(refresh_rate != RHS.refresh_rate )
53+
);
54+
}
55+
bool operator != (const MLXConfig &RHS) {
56+
return (
57+
(color_scheme != RHS.color_scheme ) ||
58+
(marker_mode != RHS.marker_mode ) ||
59+
(range_auto != RHS.range_auto ) ||
60+
(range_min != RHS.range_min ) ||
61+
(range_max != RHS.range_max )
62+
);
63+
}
64+
// Copy some members
65+
MLXConfig operator <<= (const MLXConfig &RHS) {
66+
color_scheme = RHS.color_scheme;
67+
marker_mode = RHS.marker_mode;
68+
range_auto = RHS.range_auto;
69+
range_min = RHS.range_min;
70+
range_max = RHS.range_max;
71+
return RHS;
72+
};
73+
74+
// Setup refresh_rate according to INTERPOLATE_SCALE and BOX_SIZE
75+
void init(void) {
76+
sampling_period = 2.0f / pow(2.0f, (float)(refresh_rate - 1));
77+
78+
void filter_reset(void);
79+
filter_reset();
80+
}
81+
} MLXConfig_t;
82+
83+
constexpr MLXConfig_t mlx_ini = {
84+
.interpolation = INTERPOLATE_SCALE, // 1, 2, 4, 6, 8
85+
.box_size = BOX_SIZE, // 1, 2, 4, 8
86+
.refresh_rate = REFRESH_RATE, // sampline frequency (32Hz, 16Hz, 8Hz, 4Hz, 2Hz)
87+
.color_scheme = 0, // 0: rainbow, 1: inferno
88+
.marker_mode = 0, // 0: off, 1: min/max, 2: picked up by user
89+
.range_auto = false, // automatic measurement of temperature min/max
90+
.range_min = MINTEMP, // minimum temperature (20 deg)
91+
.range_max = MAXTEMP, // maximum temperature (35 deg)
92+
};
93+
94+
MLXConfig_t mlx_cnf = mlx_ini;
95+
96+
/*--------------------------------------------------------------------------------
97+
* MLX90640 capture control
98+
*--------------------------------------------------------------------------------*/
99+
typedef struct MLXCapture {
100+
uint8_t capture_mode; // 0: camera, 1: video
101+
bool recording; // false: stop, true: recording
102+
char filename[30]; // "/MLX90640/mlx%04d.raw"
103+
} MLXCapture_t;
104+
105+
MLXCapture_t mlx_cap = {
106+
.capture_mode = 0, // 0: camera, 1: video
107+
.recording = false, // false: stop, true: recording
108+
};
109+
110+
/*--------------------------------------------------------------------------------
111+
* Set MLX90640 refresh rate
112+
*--------------------------------------------------------------------------------*/
113+
bool mlx_refresh(void) {
114+
// The initial value 64Hz is out of range
115+
static uint8_t refreshRate = MLX90640_64_HZ;
116+
117+
// Refresh MLX90640 when refresh rate changes
118+
if (refreshRate != mlx_cnf.refresh_rate) {
119+
mlx.setRefreshRate((mlx90640_refreshrate_t)(refreshRate = mlx_cnf.refresh_rate));
120+
mlx_cnf.init();
121+
delay(100); // Wait a bit for the change to take effect
122+
return true;
123+
} else {
124+
return false;
125+
}
126+
}
127+
128+
/*--------------------------------------------------------------------------------
129+
* MLX90640 initializing
130+
*--------------------------------------------------------------------------------*/
131+
void mlx_setup(void) {
132+
#ifdef MLX_I2C_SDA
133+
Wire.begin(MLX_I2C_SDA, MLX_I2C_SCL);
134+
#else
135+
Wire.begin();
136+
#endif
137+
138+
// Initialize MLX90640 with I2C
139+
if (!mlx.begin(MLX90640_I2CADDR_DEFAULT, &Wire)) {
140+
DBG_EXEC(printf("MLX90640 not found!\n"));
141+
} else {
142+
DBG_EXEC(printf("MLX90640 found at I2C address 0x%X.\n", MLX90640_I2CADDR_DEFAULT));
143+
DBG_EXEC(printf("Serial number: %04X%04X%04X\n", mlx.serialNumber[0], mlx.serialNumber[1], mlx.serialNumber[2]));
144+
}
145+
146+
// I2C bus clock for MLX90640 (Officially, the ESP32S3 supports up to 800KHz)
147+
Wire.setClock(1000000); // 400 KHz (Sm) or 1 MHz (Fm+)
148+
149+
// Set MLX90640 operating mode
150+
mlx.setMode(MLX90640_CHESS); // or MLX90640_INTERLEAVED
151+
mlx.setResolution(MLX90640_ADC_18BIT); // 16BIT, 17BIT, 18BIT (default) or 19BIT
152+
mlx_refresh();
153+
}

‎pin_assign.h

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*================================================================================
2+
* I2C / SPI pin assign
3+
* https://github.com/espressif/arduino-esp32/tree/master/variants
4+
*================================================================================*/
5+
#ifndef _PIN_ASSIGN_H_
6+
#define _PIN_ASSIGN_H_
7+
8+
/*--------------------------------------------------------------------------------
9+
* I2C pin assignment
10+
*--------------------------------------------------------------------------------*/
11+
// variants/jczn_2432s028r/pins_arduino.h
12+
#if defined (CYD_TP_CS)
13+
#define MLX_I2C_SDA 22
14+
#define MLX_I2C_SCL 27
15+
#endif
16+
17+
/*--------------------------------------------------------------------------------
18+
* Display panel resolution (defalult orientation: portrait)
19+
*--------------------------------------------------------------------------------*/
20+
#define TFT_WIDTH 240
21+
#define TFT_HEIGHT 320
22+
23+
/*--------------------------------------------------------------------------------
24+
* SPI pin assignment
25+
*--------------------------------------------------------------------------------*/
26+
// variants/jczn_2432s028r/pins_arduino.h
27+
#if defined (CYD_SD_SS)
28+
#define TFT_DC CYD_TFT_DC // 2
29+
#define TFT_MISO CYD_TFT_MISO // 12
30+
#define TFT_MOSI CYD_TFT_MOSI // 13
31+
#define TFT_SCLK CYD_TFT_SCK // 14
32+
#define TFT_CS CYD_TFT_CS // 15
33+
#define TFT_RST -1 // 2 (CYD_TFT_RS = CYD_TFT_DC)
34+
#define TFT_BL CYD_TFT_BL // 21
35+
#define TFT_SPI_BUS CYD_TFT_SPI_BUS // HSPI
36+
37+
#define TOUCH_IRQ CYD_TP_IRQ // 36
38+
#define TOUCH_MOSI CYD_TP_MOSI // 32
39+
#define TOUCH_MISO CYD_TP_MISO // 39
40+
#define TOUCH_CLK CYD_TP_CLK // 25
41+
#define TOUCH_CS CYD_TP_CS // 33
42+
#define TOUCH_SPI_BUS -1 // CYD_TP_SPI_BUS (VSPI)
43+
44+
#define SD_CS CYD_SD_SS // 5
45+
#define SD_MOSI CYD_SD_MOSI // 23
46+
#define SD_MISO CYD_SD_MISO // 19
47+
#define SD_SCK CYD_SD_SCK // 18
48+
#define SD_SPI_BUS CYD_SD_SPI_BUS // VSPI
49+
#define SPI_MODE SPI_MODE0 // SPI_MODE0, SPI_MODE2 or SPI_MODE3
50+
51+
// false: Panel driver: ILI9341 (micro-USB x 1 type)
52+
// true : Panel driver: ST7789 (micro-USB x 1 + USB-C x 1 type)
53+
#if DISPLAY_CYD_2USB
54+
#define SPI_FREQUENCY 80000000 // 80 MHz (ST7789)
55+
#else
56+
#define SPI_FREQUENCY 40000000 // 40 MHz (ILI9341)
57+
#endif
58+
#define SPI_SD_FREQUENCY 50000000 // 50 MHz
59+
#define SPI_READ_FREQUENCY 16000000 // 16 MHz
60+
#define SPI_TOUCH_FREQUENCY 1000000 // 1 MHz
61+
62+
// variants/XIAO_ESP32S3/pins_arduino.h
63+
#elif defined (ARDUINO_XIAO_ESP32S3)
64+
#define TFT_SCLK SCK // D8 = 7
65+
#define TFT_MISO MISO // D9 = 8
66+
#define TFT_MOSI MOSI // D10 = 9
67+
#define TFT_CS D2 // 3
68+
#define TFT_RST D1 // 2
69+
#define TFT_DC D0 // 1
70+
#define TOUCH_CS D3 // 4
71+
#define TOUCH_IRQ D7 // 44
72+
#define SD_CS D6 // 43
73+
#define SPI_MODE SPI_MODE3 // SPI_MODE0, SPI_MODE2 or SPI_MODE3
74+
75+
#define SPI_FREQUENCY 80000000 // 80 MHz
76+
#define SPI_SD_FREQUENCY 50000000 // 50 MHz
77+
#define SPI_READ_FREQUENCY 4000000 // 4 MHz
78+
#define SPI_TOUCH_FREQUENCY 250000 // 250KHz
79+
80+
#else
81+
#error Pin assignment required depending on your board
82+
#endif
83+
84+
#endif // _PIN_ASSIGN_H_

‎sdcard.hpp

+432
Large diffs are not rendered by default.

‎task.hpp

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/*================================================================================
2+
* Multitasking helper functions
3+
*================================================================================*/
4+
#define TASK1_CORE 1
5+
#define TASK2_CORE 0
6+
7+
#define TASK1_STACK 8192
8+
#define TASK2_STACK 8192
9+
10+
#define TASK1_PRIORITY 2
11+
#define TASK2_PRIORITY 1
12+
13+
// Message queue sent from task 1 to task 2
14+
typedef struct {
15+
uint8_t bank; // Exclusive bank numbers for Task 1 and Task 2
16+
uint32_t start; // Task 1 start time
17+
uint32_t finish; // Task 1 Finish Time
18+
} MessageQueue_t;
19+
20+
// Define two tasks on the core
21+
void Task1(void *pvParameters);
22+
void Task2(void *pvParameters);
23+
24+
// Define pointers to the tasks
25+
static void (*Process1)(uint8_t bank);
26+
static void (*Process2)(uint8_t bank, uint32_t start, uint32_t finish);
27+
28+
// Message queues and semaphores for handshaking
29+
static TaskHandle_t taskHandle[2];
30+
static QueueHandle_t queHandle;
31+
static SemaphoreHandle_t semHandle;
32+
33+
#define HALT() { for(;;) delay(1000); }
34+
#define WAIT_QUEUE portTICK_PERIOD_MS // portMAX_DELAY
35+
#define NOT_UPDATED 0xFF
36+
37+
// The setup function runs once when press reset or power on the board
38+
void task_setup(void (*task1)(uint8_t), void (*task2)(uint8_t, uint32_t, uint32_t)) {
39+
// Pointers to the tasks to be executed.
40+
Process1 = task1;
41+
Process2 = task2;
42+
43+
// Task1 start immediately --> Process1 --> Task1 send queue --> Task1 wait for semaphre
44+
// --> Task2 receive queue --> Process2 --> Task2 give semaphre --> Task2 wait queue
45+
// --> Task1 take sepahore --> Process1 --> ...
46+
semHandle = xSemaphoreCreateCounting(1, TASK1_CORE != TASK2_CORE ? 0 : 0);
47+
queHandle = xQueueCreate(1, sizeof(MessageQueue_t));
48+
49+
// Check if the queue or the semaphore was successfully created
50+
if (queHandle == NULL || semHandle == NULL) {
51+
DBG_EXEC(printf("Can't create queue or semaphore.\n"));
52+
HALT();
53+
}
54+
55+
// Set up sender task in core 1 and start immediately
56+
xTaskCreatePinnedToCore(
57+
Task1, "Task1",
58+
TASK1_STACK, // The stack size
59+
NULL, // Pass reference to a variable describing the task number
60+
TASK1_PRIORITY, // priority
61+
&taskHandle[0], // Pass reference to task handle
62+
TASK1_CORE
63+
);
64+
65+
// Set up receiver task on core 0 and start immediately
66+
xTaskCreatePinnedToCore(
67+
Task2, "Task2",
68+
TASK2_STACK, // The stack size
69+
NULL, // Pass reference to a variable describing the task number
70+
TASK2_PRIORITY, // priority
71+
&taskHandle[1], // Pass reference to task handle
72+
TASK2_CORE
73+
);
74+
}
75+
76+
/*--------------------------------------------------*/
77+
/*------------------- Handshake --------------------*/
78+
/*--------------------------------------------------*/
79+
uint8_t SendQueue(uint8_t bank, uint32_t start, uint32_t finish) {
80+
MessageQueue_t queue = {
81+
bank, start, finish
82+
};
83+
84+
if (xQueueSend(queHandle, &queue, portMAX_DELAY) == pdTRUE) {
85+
// DBG_EXEC(printf("Give queue: %d\n", queue.bank));
86+
} else {
87+
DBG_EXEC(printf("unable to send queue\n"));
88+
}
89+
90+
return !bank;
91+
}
92+
93+
MessageQueue_t ReceiveQueue() {
94+
MessageQueue_t queue;
95+
96+
if (xQueueReceive(queHandle, &queue, portMAX_DELAY) == pdTRUE) {
97+
// DBG_EXEC(printf("Take queue: %d\n", queue.bank));
98+
} else {
99+
DBG_EXEC(printf("Unable to receive queue.\n"));
100+
}
101+
102+
return queue;
103+
}
104+
105+
bool ScanQueue(MessageQueue_t &queue) {
106+
if (xQueueReceive(queHandle, &queue, WAIT_QUEUE) == pdTRUE) {
107+
return true;
108+
} else {
109+
return false;
110+
}
111+
}
112+
113+
void TakeSemaphore(void) {
114+
if (xSemaphoreTake(semHandle, portMAX_DELAY) == pdTRUE) {
115+
// DBG_EXEC(printf("Take semaphore.\n"));
116+
} else {
117+
DBG_EXEC(printf("Unable to take semaphore.\n"));
118+
}
119+
}
120+
121+
void GiveSemaphore(void) {
122+
if (xSemaphoreGive(semHandle) == pdTRUE) {
123+
// DBG_EXEC(printf("Give semaphore.\n"));
124+
} else {
125+
DBG_EXEC(printf("Unable to give semaphore.\n"));
126+
}
127+
}
128+
129+
/*--------------------------------------------------*/
130+
/*--------------------- Tasks ----------------------*/
131+
/*--------------------------------------------------*/
132+
void Task1(void *pvParameters) {
133+
uint8_t bank = 0;
134+
135+
while (true) {
136+
uint32_t start = millis();
137+
138+
Process1(bank);
139+
140+
bank = SendQueue(bank, start, millis());
141+
142+
TakeSemaphore();
143+
}
144+
}
145+
146+
void Task2(void *pvParameters) {
147+
MessageQueue_t queue;
148+
149+
while (true) {
150+
if (ScanQueue(queue)) {
151+
GiveSemaphore();
152+
} else {
153+
queue.bank = NOT_UPDATED;
154+
}
155+
156+
Process2(queue.bank, queue.start, queue.finish);
157+
158+
yield(); // Prevent the watchdog from firing
159+
}
160+
}

‎touch.hpp

+354
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
/*================================================================================
2+
* Touch event manager
3+
*================================================================================*/
4+
5+
/*--------------------------------------------------------------------------------
6+
* Touch panel calibration configuration
7+
*--------------------------------------------------------------------------------*/
8+
typedef struct TouchConfig {
9+
// Member variables
10+
uint16_t cal[8];
11+
int8_t offset[2];
12+
13+
// Comparison operator
14+
bool operator >= (TouchConfig &RHS) {
15+
return !bcmp(cal, RHS.cal, sizeof(cal));
16+
}
17+
bool operator <= (TouchConfig &RHS) {
18+
return (offset[0] != RHS.offset[0]) || (offset[1] != RHS.offset[1]);
19+
}
20+
bool operator != (TouchConfig &RHS) {
21+
return bcmp(cal, RHS.cal, sizeof(cal)) || (offset[0] != RHS.offset[0]) || (offset[1] != RHS.offset[1]);
22+
}
23+
} TouchConfig_t;
24+
25+
/*--------------------------------------------------------------------------------
26+
* Touch panel calibration parameters
27+
* When 'USE_PREFERENCES' is 'false', the calibration result must be embedded.
28+
*--------------------------------------------------------------------------------*/
29+
TouchConfig_t tch_cnf = {
30+
#if defined (LOVYANGFX_HPP_)
31+
.cal = { 0, 0, 0, 0, 0, 0, 0, 0 },
32+
#elif defined (_TFT_eSPIH_)
33+
.cal = { 0, 0, 0, 0, 0, },
34+
#else // defined (_XPT2046_Touchscreen_h_)
35+
.cal = { 0, },
36+
#endif
37+
.offset = { 0, },
38+
};
39+
40+
/*--------------------------------------------------------------------------------
41+
* Event definition
42+
*--------------------------------------------------------------------------------*/
43+
#define PERIOD_DEBOUNCE 25 // [msec]
44+
#define PERIOD_TOUCHED 50 // [msec]
45+
#define PERIOD_TAP2 200 // [msec]
46+
#define PERIOD_CLEAR_EVENT 100 // [msec]
47+
48+
typedef enum {
49+
EVENT_NONE = (0x00), // considered as 'HIGH'
50+
EVENT_RISING = (0x01), // touch --> untouch
51+
EVENT_FALLING = (0x02), // untouch --> touch
52+
EVENT_TOUCHED = (0x04), // touch --> touch
53+
EVENT_TAP2 = (0x08), // double tap
54+
EVENT_WATCH = (0x10), // execute a callback every watch cycle
55+
56+
// alias
57+
EVENT_INIT = (EVENT_NONE),
58+
EVENT_UP = (EVENT_RISING),
59+
EVENT_DOWN = (EVENT_FALLING),
60+
EVENT_DRAG = (EVENT_FALLING | EVENT_TOUCHED),
61+
EVENT_TAP = (EVENT_FALLING | EVENT_RISING),
62+
EVENT_CLICK = (EVENT_FALLING | EVENT_RISING),
63+
EVENT_CHANGE = (EVENT_FALLING | EVENT_RISING),
64+
EVENT_SELECT = (EVENT_FALLING | EVENT_TAP2),
65+
EVENT_ALL = (EVENT_FALLING | EVENT_RISING | EVENT_TOUCHED),
66+
} Event_t;
67+
68+
typedef struct Touch {
69+
Event_t event; // Detected event
70+
uint16_t x, y; // The coordinates where the event fired
71+
} Touch_t;
72+
73+
/*--------------------------------------------------------------------------------
74+
* Function prototyping
75+
*--------------------------------------------------------------------------------*/
76+
bool touch_setup(void);
77+
bool touch_event(Touch_t &touch);
78+
void touch_clear(void);
79+
void touch_calibrate(TouchConfig_t *config);
80+
bool touch_save(TouchConfig_t *config);
81+
bool touch_load(TouchConfig_t *config);
82+
83+
/*--------------------------------------------------------------------------------
84+
* Simple touch point correction
85+
*--------------------------------------------------------------------------------*/
86+
extern uint16_t lcd_width;
87+
extern uint16_t lcd_height;
88+
89+
/*--------------------------------------------------------------------------------
90+
* Setup touch manager
91+
*--------------------------------------------------------------------------------*/
92+
bool touch_setup(void) {
93+
#if USE_PREFERENCES
94+
// Load calibration parameters from FLASH
95+
if (touch_load(&tch_cnf) == false) {
96+
touch_calibrate(&tch_cnf);
97+
touch_save(&tch_cnf);
98+
}
99+
#else
100+
if (tch_cnf.cal[0] == 0) {
101+
touch_calibrate(&tch_cnf);
102+
}
103+
#endif
104+
105+
#if defined (LOVYANGFX_HPP_)
106+
107+
// https://github.com/lovyan03/LovyanGFX/discussions/539
108+
GFX_EXEC(setTouchCalibrate(tch_cnf.cal));
109+
return true;
110+
111+
#elif defined (_TFT_eSPIH_)
112+
113+
// https://github.com/Bodmer/TFT_eSPI/tree/master/examples/Generic/Touch_calibrate
114+
GFX_EXEC(setTouch(tch_cnf.cal));
115+
return true;
116+
117+
else // defined (_XPT2046_Touchscreen_h_)
118+
119+
if (ts.begin()) {
120+
ts.setRotation(SCREEN_ROTATION);
121+
return true;
122+
} else {
123+
return false;
124+
}
125+
126+
#endif
127+
}
128+
129+
/*--------------------------------------------------------------------------------
130+
* Touch event manager
131+
*--------------------------------------------------------------------------------*/
132+
bool touch_event(Touch_t &touch) {
133+
uint32_t time = millis();
134+
static uint32_t prev_time;
135+
static uint16_t x, y;
136+
static bool prev_stat;
137+
static uint8_t count;
138+
Event_t event = EVENT_NONE;
139+
140+
#if defined (_XPT2046_Touchscreen_h_)
141+
142+
bool stat = ts.touched();
143+
if (stat) {
144+
TS_Point p = ts.getPoint();
145+
x = p.x;
146+
y = p.y;
147+
}
148+
149+
#else // LovyanGFX || TFT_eSPI
150+
151+
bool stat = GFX_EXEC(getTouch(&x, &y));
152+
153+
#endif // _XPT2046_Touchscreen_h_
154+
155+
// when state changes, check again after a certain period of time
156+
uint32_t dt = time - prev_time;
157+
if (stat != prev_stat) {
158+
if (dt < PERIOD_DEBOUNCE) {
159+
return false;
160+
} else {
161+
// update the time when state changes
162+
prev_time = time;
163+
164+
// reset double tap counter
165+
if (dt > PERIOD_TAP2 || count >= 4) {
166+
count = 0;
167+
}
168+
}
169+
}
170+
171+
// untouch --> touch
172+
if (prev_stat == false && stat == true) {
173+
event = EVENT_FALLING;
174+
count = (count == 0 ? count + 1 : (dt <= PERIOD_TAP2 ? count + 1 : 0));
175+
} else
176+
177+
// touch --> untouch
178+
if (prev_stat == true && stat == false) {
179+
event = EVENT_RISING;
180+
count = dt <= PERIOD_TAP2 ? count + 1 : 0;
181+
} else
182+
183+
// touch --> touch
184+
if (prev_stat == true && stat == true) {
185+
event = EVENT_TOUCHED;
186+
}
187+
188+
// update state
189+
prev_stat = stat;
190+
191+
if (event != EVENT_NONE) {
192+
if (stat) {
193+
x += tch_cnf.offset[0];
194+
y += tch_cnf.offset[1];
195+
x = constrain(x, 0, lcd_width - 1);
196+
y = constrain(y, 0, lcd_height - 1);
197+
}
198+
199+
// TAP2 = FALLING --> RISING --> FALLING --> RISING
200+
touch.event = (Event_t)(event | (count >= 4 ? EVENT_TAP2 : EVENT_NONE));
201+
touch.x = x;
202+
touch.y = y;
203+
204+
// DBG_EXEC(printf("event: %d, x: %d, y: %d, dt: %d, count: %d\n", touch.event, x, y, dt, count));
205+
return true;
206+
}
207+
208+
return false;
209+
}
210+
211+
/*--------------------------------------------------------------------------------
212+
* Wait until there are no more touch events
213+
*--------------------------------------------------------------------------------*/
214+
void touch_clear(void) {
215+
Touch_t touch;
216+
delay(PERIOD_CLEAR_EVENT);
217+
while(touch_event(touch));
218+
}
219+
220+
/*--------------------------------------------------------------------------------
221+
* Calibrating the touch panel
222+
*--------------------------------------------------------------------------------*/
223+
void touch_calibrate(TouchConfig_t *config) {
224+
#if defined (_XPT2046_Touchscreen_h_)
225+
226+
#elif defined (LOVYANGFX_HPP_)
227+
228+
// https://github.com/lovyan03/LovyanGFX/tree/master/examples/HowToUse/2_user_setting
229+
GFX_EXEC(clear(0));
230+
GFX_EXEC(setTextSize(2));
231+
GFX_EXEC(setTextDatum(textdatum_t::middle_center));
232+
GFX_EXEC(drawString("touch the arrow marker.", lcd_width >> 1, lcd_height >> 1));
233+
GFX_EXEC(setTextDatum(textdatum_t::top_left));
234+
GFX_EXEC(calibrateTouch(config->cal, TFT_WHITE, TFT_BLACK, std::max(lcd_width, lcd_height) >> 3));
235+
236+
DBG_EXEC({
237+
printf("\n// LovyanGFX\n");
238+
printf(".cal = { ");
239+
for (uint8_t i = 0; i < 8; ++i) {
240+
printf("%d\n", config->cal[i]);
241+
printf(i < 7 ? ", " : " },\n");
242+
}
243+
});
244+
245+
#elif defined (_TFT_eSPIH_)
246+
247+
// https://github.com/Bodmer/TFT_eSPI/tree/master/examples/Generic/Touch_calibrate
248+
GFX_EXEC(fillScreen(TFT_BLACK));
249+
GFX_EXEC(setCursor(20, 20));
250+
GFX_EXEC(setTextSize(2));
251+
GFX_EXEC(setTextColor(TFT_WHITE, TFT_BLACK));
252+
GFX_EXEC(println("Touch corners in order"));
253+
GFX_EXEC(calibrateTouch(config->cal, TFT_MAGENTA, TFT_BLACK, 15));
254+
255+
DBG_EXEC({
256+
printf("\n// TFT_eSPI\n");
257+
printf(".cal = { ");
258+
for (uint8_t i = 0; i < 5; ++i) {
259+
printf("%d\n", config->cal[i]);
260+
printf(i < 4 ? ", " : "0, },\n");
261+
}
262+
});
263+
264+
#endif
265+
266+
// clear offset
267+
config->offset[0] = config->offset[1] = 0;
268+
}
269+
270+
/*--------------------------------------------------------------------------------
271+
* Saving and loading calibration configurations using preferences.h
272+
* https://docs.espressif.com/projects/arduino-esp32/en/latest/tutorials/preferences.html
273+
* https://github.com/espressif/arduino-esp32/tree/master/libraries/Preferences
274+
*--------------------------------------------------------------------------------*/
275+
#include <Preferences.h>
276+
277+
#define RW_MODE false
278+
#define RO_MODE true
279+
#define INIT_KEY "initialized"
280+
281+
#if defined (_XPT2046_Touchscreen_h_)
282+
#define PREF_KEY "XPT2046"
283+
#elif defined (LOVYANGFX_HPP_)
284+
#define PREF_KEY "LovyanGFX"
285+
#elif defined (_TFT_eSPIH_)
286+
#define PREF_KEY "TFT_eSPI"
287+
#endif
288+
289+
bool touch_save(TouchConfig_t *config) {
290+
#if USE_PREFERENCES
291+
Preferences touchPref;
292+
293+
if (touchPref.begin(PREF_KEY, RW_MODE) == false) {
294+
DBG_EXEC(printf("Preferences: begin(%s) failed.\n", PREF_KEY));
295+
return false;
296+
}
297+
298+
if (touchPref.putBytes("cal", static_cast<const void*>(config->cal), sizeof(config->cal)) != sizeof(config->cal)) {
299+
DBG_EXEC(printf("Preferences: putBytes(cal) failed.\n"));
300+
touchPref.end();
301+
return false;
302+
}
303+
304+
if (touchPref.putBytes("offset", static_cast<const void*>(config->offset), sizeof(config->offset)) != sizeof(config->offset)) {
305+
DBG_EXEC(printf("Preferences: putBytes(offset) failed.\n"));
306+
touchPref.end();
307+
return false;
308+
}
309+
310+
if (touchPref.putBool(INIT_KEY, true) != sizeof(true)) {
311+
DBG_EXEC(printf("Preferences: putBool(%s) failed.\n", INIT_KEY));
312+
touchPref.end();
313+
return false;
314+
}
315+
316+
touchPref.end();
317+
#endif
318+
return true;
319+
}
320+
321+
bool touch_load(TouchConfig_t *config) {
322+
#if USE_PREFERENCES
323+
Preferences touchPref;
324+
TouchConfig_t c;
325+
326+
if (touchPref.begin(PREF_KEY, RO_MODE) == false) {
327+
DBG_EXEC(printf("Preferences: %s does not exist.\n", PREF_KEY));
328+
return false;
329+
}
330+
331+
// Check if it already exists
332+
if (touchPref.isKey(INIT_KEY) == false) {
333+
DBG_EXEC(printf("Preferences: %s does not exist.\n", INIT_KEY));
334+
touchPref.end();
335+
return false;
336+
}
337+
338+
if (touchPref.getBytes("cal", static_cast<void*>(&c.cal), sizeof(c.cal)) != sizeof(c.cal)) {
339+
DBG_EXEC(printf("Preferences: getBytes(cal) failed.\n"));
340+
touchPref.end();
341+
return false;
342+
}
343+
344+
if (touchPref.getBytes("offset", static_cast<void*>(&c.offset), sizeof(c.offset)) != sizeof(c.offset)) {
345+
DBG_EXEC(printf("Preferences: getBytes(offset) failed.\n"));
346+
touchPref.end();
347+
return false;
348+
}
349+
350+
*config = c;
351+
touchPref.end();
352+
#endif
353+
return true;
354+
}

‎widget.hpp

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*================================================================================
2+
* Wedget manager
3+
*================================================================================*/
4+
#if false // Check widget area by drawing with RED box
5+
#define CHECK_WIDGET_AREA(x) GFX_EXEC(x)
6+
#else
7+
#define CHECK_WIDGET_AREA(x)
8+
#endif
9+
10+
/*--------------------------------------------------------------------------------
11+
* State of the screen
12+
*--------------------------------------------------------------------------------*/
13+
typedef enum {
14+
STATE_ON = 0,
15+
STATE_MAIN,
16+
STATE_CONFIGURATION,
17+
STATE_RESOLUTION,
18+
STATE_THERMOGRAPH,
19+
STATE_FILE_MANAGER,
20+
STATE_FILE_CONFIRM,
21+
STATE_CAPTURE_MODE,
22+
STATE_CALIBRATION,
23+
STATE_ADJUST_OFFSET,
24+
STATE_INFORMATION,
25+
} State_t;
26+
27+
static State_t state = STATE_ON;
28+
29+
/*--------------------------------------------------------------------------------
30+
* Widget data definition
31+
*--------------------------------------------------------------------------------*/
32+
typedef struct {
33+
const uint8_t *data; // pointer to the image
34+
const size_t size; // size of image data (only for TFT_eSPI with bitbank2/PNGdec)
35+
} Image_t;
36+
37+
typedef struct Widget {
38+
const uint16_t x, y; // The top left coordinate of the widget
39+
const uint16_t w, h; // Widget width and height
40+
const Image_t *image; // Widget image data
41+
const Event_t event; // The touch event to detect
42+
void (*callback)(const struct Widget *widget, const Touch_t &touch); // Event handler
43+
} Widget_t;
44+
45+
#define N_WIDGETS(w) (sizeof(w) / sizeof(w[0]))
46+
47+
/*--------------------------------------------------------------------------------
48+
* Focused widget
49+
*--------------------------------------------------------------------------------*/
50+
static Widget_t const *focus = NULL;
51+
52+
/*--------------------------------------------------------------------------------
53+
* Event message to command initialization
54+
*--------------------------------------------------------------------------------*/
55+
static constexpr Touch_t doInit = { EVENT_INIT, 0, 0 };
56+
57+
/*--------------------------------------------------------------------------------
58+
* Functions prototyping
59+
*--------------------------------------------------------------------------------*/
60+
void widget_setup(State_t screen = STATE_ON);
61+
void widget_state(State_t screen = STATE_ON);
62+
State_t widget_control(void);
63+
64+
/*--------------------------------------------------------------------------------
65+
* Widgets
66+
*--------------------------------------------------------------------------------*/
67+
#include "draw.hpp"
68+
#include "widgets.hpp"
69+
70+
/*--------------------------------------------------------------------------------
71+
* Get widget table
72+
*--------------------------------------------------------------------------------*/
73+
static bool widget_get(State_t screen, Widget_t const **widget, int *n) {
74+
switch (screen) {
75+
case STATE_MAIN:
76+
*widget = widget_main;
77+
*n = N_WIDGETS(widget_main);
78+
return true;
79+
80+
case STATE_CONFIGURATION:
81+
*widget = widget_configuration;
82+
*n = N_WIDGETS(widget_configuration);
83+
return true;
84+
85+
case STATE_RESOLUTION:
86+
*widget = widget_resolution;
87+
*n = N_WIDGETS(widget_resolution);
88+
return true;
89+
90+
case STATE_THERMOGRAPH:
91+
*widget = widget_thermograph;
92+
*n = N_WIDGETS(widget_thermograph);
93+
return true;
94+
95+
case STATE_FILE_MANAGER:
96+
*widget = widget_file_manager;
97+
*n = N_WIDGETS(widget_file_manager);
98+
return true;
99+
100+
case STATE_FILE_CONFIRM:
101+
*widget = widget_file_confirm;
102+
*n = N_WIDGETS(widget_file_confirm);
103+
return true;
104+
105+
case STATE_CAPTURE_MODE:
106+
*widget = widget_capture_mode;
107+
*n = N_WIDGETS(widget_capture_mode);
108+
return true;
109+
110+
case STATE_CALIBRATION:
111+
*widget = widget_calibration;
112+
*n = N_WIDGETS(widget_calibration);
113+
return true;
114+
115+
case STATE_ADJUST_OFFSET:
116+
*widget = widget_adjust_offset;
117+
*n = N_WIDGETS(widget_adjust_offset);
118+
return true;
119+
120+
case STATE_INFORMATION:
121+
*widget = widget_information;
122+
*n = N_WIDGETS(widget_information);
123+
return true;
124+
125+
case STATE_ON:
126+
default:
127+
return false;
128+
}
129+
}
130+
131+
/*--------------------------------------------------------------------------------
132+
* Handle the widget events on screen
133+
*--------------------------------------------------------------------------------*/
134+
static bool widget_event(const Widget_t *widgets, const size_t n_widgets, Touch_t &touch) {
135+
Event_t event = touch.event;
136+
137+
if (focus == NULL) {
138+
for (int i = 0; i < n_widgets; ++i) {
139+
// In case the touch events to be handled
140+
if ((widgets[i].event & touch.event) && widgets[i].callback) {
141+
// Focus the widget where the event fired
142+
if (widgets[i].x <= touch.x && touch.x <= widgets[i].x + widgets[i].w &&
143+
widgets[i].y <= touch.y && touch.y <= widgets[i].y + widgets[i].h) {
144+
focus = &widgets[i];
145+
break;
146+
}
147+
}
148+
}
149+
}
150+
151+
// execute the callback function for the focused widget
152+
if (focus && (focus->event & touch.event)) {
153+
// DBG_EXEC(printf("event = %d(%d), x = %d, y = %d\n", touch.event, focus->event, touch.x, touch.y));
154+
touch.event = (Event_t)(touch.event & focus->event); // mask by target event
155+
focus->callback(focus, touch);
156+
}
157+
158+
// reset focus when touch leaves screen
159+
focus = (event & EVENT_RISING ? NULL : focus);
160+
161+
return (focus != NULL);
162+
}
163+
164+
/*--------------------------------------------------------------------------------
165+
* Watch events on a specific screen
166+
*--------------------------------------------------------------------------------*/
167+
static bool widget_watch(const Widget_t *widgets, const size_t n_widgets) {
168+
Touch_t touch;
169+
170+
if (touch_event(touch)) {
171+
// Returns true when the touch event is handled by a widget
172+
if (widget_event(widgets, n_widgets, touch) == true) {
173+
return true;
174+
}
175+
}
176+
177+
// Execute a special callback every watch cycle
178+
else if (widgets[n_widgets-1].event == EVENT_WATCH) {
179+
widgets[n_widgets-1].callback(&widgets[n_widgets-1], doInit);
180+
return true;
181+
}
182+
183+
return false;
184+
}
185+
186+
/*--------------------------------------------------------------------------------
187+
* Draw all widgets at the start of each state
188+
*--------------------------------------------------------------------------------*/
189+
void widget_setup(State_t screen /* = STATE_ON */) {
190+
int n;
191+
Widget_t const *widget;
192+
193+
// reset focused widget
194+
focus = NULL;
195+
196+
if (widget_get(screen, &widget, &n)) {
197+
for (int i = 0; i < n; ++i, ++widget) {
198+
if (widget->callback) {
199+
widget->callback(widget, doInit);
200+
}
201+
CHECK_WIDGET_AREA(drawRect(widget->x, widget->y, widget->w, widget->h, TFT_RED));
202+
}
203+
}
204+
}
205+
206+
/*--------------------------------------------------------------------------------
207+
* Change state
208+
*--------------------------------------------------------------------------------*/
209+
void widget_state(State_t screen /*= STATE_ON */) {
210+
touch_clear();
211+
widget_setup(state = screen);
212+
}
213+
214+
/*--------------------------------------------------------------------------------
215+
* Finite State Machines
216+
*--------------------------------------------------------------------------------*/
217+
State_t widget_control(void) {
218+
int n;
219+
Widget_t const *widget;
220+
221+
switch (state) {
222+
case STATE_MAIN:
223+
case STATE_THERMOGRAPH:
224+
if (widget_get(state, &widget, &n)) {
225+
widget_watch(widget, n);
226+
}
227+
break;
228+
229+
case STATE_CONFIGURATION:
230+
case STATE_RESOLUTION:
231+
case STATE_FILE_MANAGER:
232+
case STATE_FILE_CONFIRM:
233+
case STATE_CAPTURE_MODE:
234+
case STATE_CALIBRATION:
235+
case STATE_ADJUST_OFFSET:
236+
case STATE_INFORMATION:
237+
if (widget_get(state, &widget, &n)) {
238+
State_t initial = state;
239+
do {
240+
widget_watch(widget, n);
241+
delay(1); // reset wdt
242+
} while (state == initial);
243+
}
244+
break;
245+
246+
case STATE_ON:
247+
default:
248+
widget_state(STATE_MAIN);
249+
break;
250+
}
251+
252+
return state;
253+
}

‎widgets.h

+5,431
Large diffs are not rendered by default.

‎widgets.hpp

+1,647
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.