From 36776fecf9103cacfd295f1f9a3831c0a910219c Mon Sep 17 00:00:00 2001 From: Gustavo Cateim Date: Fri, 10 Jul 2026 16:19:11 -0300 Subject: [PATCH 1/5] input: release touch handles before deleting the i2c bus Since ESP-IDF v5.5 (espressif/esp-idf@e73d78ba) i2c_del_master_bus() refuses to delete a bus that still has devices attached and returns ESP_ERR_INVALID_STATE. The touchscreen task registers a panel IO device on the bus but never releases it, so under IDF 5.5.4 the first touchscreen_deinit() fails and aborts via ESP_ERROR_CHECK. On boards that panic with PRINT_HALT, such as the Waveshare S3 Touch LCD 2, this freezes the device on the boot splash screen while gathering camera entropy, and also hangs the QR scanner and the camera exit path, since the touchscreen is restarted around every camera start/stop (see #306). Releasing the touch handle and the panel IO device (which removes it from the bus) before deleting the bus restores the deinit/init cycle, and also fixes a heap leak present under IDF 5.4 where both handles leaked on every cycle. Co-authored-by: oroderico --- main/input/touchscreen.inc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/input/touchscreen.inc b/main/input/touchscreen.inc index 7607db01..4f1540f1 100644 --- a/main/input/touchscreen.inc +++ b/main/input/touchscreen.inc @@ -91,6 +91,9 @@ static void touchscreen_task(void* ignored) } vTaskDelay(20 / portTICK_PERIOD_MS); } + // Since IDF 5.5 the i2c bus cannot be deleted while devices are still attached + ESP_ERROR_CHECK(esp_lcd_touch_del(ret_touch)); + ESP_ERROR_CHECK(esp_lcd_panel_io_del(tp_io_handle)); ESP_ERROR_CHECK(_i2c_deinit(touch_i2c_handle)); shutdown_finished = true; vTaskDelete(NULL); From fb3a009ad60fbbaa097f2c6c56471fc5fa154312 Mon Sep 17 00:00:00 2001 From: Gustavo Cateim Date: Sat, 11 Jul 2026 13:09:24 -0300 Subject: [PATCH 2/5] power: release PMU devices before deleting the i2c bus on M5CoreS3 Since ESP-IDF v5.5 i2c_del_master_bus() refuses to delete a bus that still has devices attached and returns ESP_ERR_INVALID_STATE. On the M5CoreS3, power_init() attaches the axp2101 and aw9523 devices and then deletes the bus without releasing them, so under IDF 5.5.4 the deinit fails, power_init() propagates the error and the JADE_ASSERT in main.c aborts the boot. Same root cause as the touchscreen fix in the previous commit (#306). Remove both devices from the bus before deleting it. Not tested on hardware (none available); the change mirrors the touchscreen fix validated on the Waveshare S3 Touch LCD 2. --- main/power/m5stackcores3.inc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/main/power/m5stackcores3.inc b/main/power/m5stackcores3.inc index 45c900a2..390fd13b 100644 --- a/main/power/m5stackcores3.inc +++ b/main/power/m5stackcores3.inc @@ -48,6 +48,9 @@ esp_err_t power_init(void) axp2101_init(); vTaskDelay(100 / portTICK_PERIOD_MS); + // Since IDF 5.5 the i2c bus cannot be deleted while devices are still attached + I2C_CHECK_RET(i2c_master_bus_rm_device(axp2101)); + I2C_CHECK_RET(i2c_master_bus_rm_device(aw9523)); I2C_CHECK_RET(_i2c_deinit(NULL)); return ESP_OK; From cfce08ced9f58ed0244e516f1d7f0c61a82889ee Mon Sep 17 00:00:00 2001 From: Jon Griffiths Date: Sun, 12 Jul 2026 04:16:26 +1200 Subject: [PATCH 3/5] wally: update wally to release 1.5.6 --- components/libwally-core/upstream | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/libwally-core/upstream b/components/libwally-core/upstream index c5591834..0c41f38f 160000 --- a/components/libwally-core/upstream +++ b/components/libwally-core/upstream @@ -1 +1 @@ -Subproject commit c5591834b3ae4ee4c7db9e537a9c19104ab4bf0c +Subproject commit 0c41f38fb1c201786e9c3ac9eae4f5f80c051399 From b69d1d7b06b4ba4164afa568ec73b4bdc9a0c381 Mon Sep 17 00:00:00 2001 From: oroderico Date: Sat, 11 Jul 2026 09:53:00 -0300 Subject: [PATCH 4/5] display: redraw touch navbar on orientation flip On boards with a virtual touch navbar (TTGO TWatchS3, M5 CoreS3, Waveshare S3 Touch LCD 2) the prev/OK/next buttons are drawn only once at startup, inside display_init(). Flipping the display orientation remaps the panel scan direction, so the navbar was left stale and upside down at the wrong edge of the panel. Extract the drawing code into display_touch_navbar_redraw() and call it from gui_set_flipped_orientation() whenever the orientation actually changes. Boards without a navbar get an inline no-op stub. The TOUCH_BUTTON_AREA constant moves to display.h so the touch input code can share it. The two 50 ms settle delays stay in display_init(), as flipping does not need them. Co-authored-by: Gustavo Cateim --- main/display.c | 68 +++++++++++++++++++++++++++----------------------- main/display.h | 11 ++++++++ main/gui.c | 7 +++++- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/main/display.c b/main/display.c index 59125561..3794e271 100644 --- a/main/display.c +++ b/main/display.c @@ -230,41 +230,19 @@ static void display_clear(void) CONFIG_DISPLAY_OFFSET_X, CONFIG_DISPLAY_OFFSET_Y, CONFIG_DISPLAY_WIDTH, CONFIG_DISPLAY_HEIGHT, TFT_BLACK); } -void display_init(TaskHandle_t* gui_h) -{ - JADE_LOGI("display/screen init"); - JADE_ASSERT(gui_h); -#ifdef CONFIG_LIBJADE - if (*gui_h) { - return; // Already initialized - } -#endif - JADE_ASSERT(!*gui_h); - - JADE_ASSERT(power_screen_on() == ESP_OK); - vTaskDelay(100 / portTICK_PERIOD_MS); - -#if defined(CONFIG_ETH_USE_OPENETH) -#if defined(CONFIG_HAS_CAMERA) - qemu_display_init(); -#endif -#else - JADE_ASSERT(gui_h); - display_hw_init(gui_h); - #if defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) || defined(CONFIG_BOARD_TYPE_M5_CORES3) \ || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) -#define TOUCH_BUTTON_AREA 40 #define TOUCH_BUTTON_MARGIN 5 #define TOUCH_BUTTON_WIDTH 40 - /* The TwatchS3 and core s3 don't have buttons that can be used (just power and - reset) - but it has a touch panel, we use the bottom 40 pixels worth of height - to display 3 buttons (prev, OK, next), we handle this here rather than - in display_hw because we want to draw text inside the virtual buttons */ - - vTaskDelay(50 / portTICK_PERIOD_MS); - +/* The TwatchS3 and core s3 don't have buttons that can be used (just power and + reset) + but it has a touch panel, we use the bottom 40 pixels worth of height + to display 3 buttons (prev, OK, next), we handle this here rather than + in display_hw because we want to draw text inside the virtual buttons. + Called at startup and again whenever the display orientation is flipped, + as flipping remaps the panel and the navbar must be redrawn in place. */ +void display_touch_navbar_redraw(void) +{ /* blank the bottom of the display with black */ uint16_t line[CONFIG_DISPLAY_WIDTH] = { TFT_BLACK }; for (int16_t i = 0; i < TOUCH_BUTTON_AREA; ++i) { @@ -287,7 +265,35 @@ void display_init(TaskHandle_t* gui_h) disp_win_virtual_buttons.x2 = (CONFIG_DISPLAY_WIDTH - TOUCH_BUTTON_MARGIN) + CONFIG_DISPLAY_OFFSET_X; display_print_in_area("I", CENTER, CENTER, disp_win_virtual_buttons, 0); display_set_font(DEFAULT_FONT); +} +#endif + +void display_init(TaskHandle_t* gui_h) +{ + JADE_LOGI("display/screen init"); + JADE_ASSERT(gui_h); +#ifdef CONFIG_LIBJADE + if (*gui_h) { + return; // Already initialized + } +#endif + JADE_ASSERT(!*gui_h); + + JADE_ASSERT(power_screen_on() == ESP_OK); + vTaskDelay(100 / portTICK_PERIOD_MS); +#if defined(CONFIG_ETH_USE_OPENETH) +#if defined(CONFIG_HAS_CAMERA) + qemu_display_init(); +#endif +#else + JADE_ASSERT(gui_h); + display_hw_init(gui_h); + +#if defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) || defined(CONFIG_BOARD_TYPE_M5_CORES3) \ + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) + vTaskDelay(50 / portTICK_PERIOD_MS); + display_touch_navbar_redraw(); vTaskDelay(50 / portTICK_PERIOD_MS); #endif #endif diff --git a/main/display.h b/main/display.h index a625da45..528ca16b 100644 --- a/main/display.h +++ b/main/display.h @@ -91,6 +91,17 @@ extern const color_t TFT_PINK; void display_init(TaskHandle_t* gui_h); bool display_flip_orientation(bool flipped_orientation); + +// Height in pixels of the touch area reserved below the main display for the +// virtual navigation buttons (or the fixed capacitive strip on the M5 Core2) +#define TOUCH_BUTTON_AREA 40 + +#if defined(CONFIG_BOARD_TYPE_TTGO_TWATCHS3) || defined(CONFIG_BOARD_TYPE_M5_CORES3) \ + || defined(CONFIG_BOARD_TYPE_WS_TOUCH_LCD2) +void display_touch_navbar_redraw(void); +#else +static inline void display_touch_navbar_redraw(void) {} +#endif Icon* get_icon(const uint8_t* start, const uint8_t* end); Picture* get_picture(const uint8_t* start, const uint8_t* end); void display_picture(const Picture* imgbuf, int x, int y, dispWin_t area); diff --git a/main/gui.c b/main/gui.c index 4c6b3ef2..dfeb1315 100644 --- a/main/gui.c +++ b/main/gui.c @@ -263,7 +263,12 @@ bool gui_get_flipped_orientation(void) { return gui_orientation_flipped; } bool gui_set_flipped_orientation(const bool flipped_orientation) { - gui_orientation_flipped = display_flip_orientation(flipped_orientation); + const bool new_orientation = display_flip_orientation(flipped_orientation); + if (gui_orientation_flipped != new_orientation) { + gui_orientation_flipped = new_orientation; + // Redraw the virtual navbar buttons (if any) at their new position + display_touch_navbar_redraw(); + } return gui_orientation_flipped; } From 0ed220f16ebce7f6afc6141aec44927999b5c523 Mon Sep 17 00:00:00 2001 From: Gustavo Cateim Date: Sat, 11 Jul 2026 09:53:00 -0300 Subject: [PATCH 5/5] input: mirror touch coordinates on display orientation flip The touch controllers keep reporting physical panel coordinates after the display orientation is flipped, so the virtual navbar buttons stop lining up: taps land on the stale y band and prev/next act swapped left/right (the FIXME removed here described exactly this). Instead of teaching the button handler about orientation, apply esp_lcd_touch_set_mirror_x/_y from the touchscreen task whenever the gui orientation changes. The esp_lcd_touch component mirrors the coordinates in software (x_max - x, y_max - y) for drivers without hardware mirror support, which covers both the CST816S and the FT5X06, so touch input always matches the display coordinate space on every board with a navbar. The M5 Core2 is excluded: its buttons are silk-screened on a fixed capacitive strip below the display and must not follow the flip. For the software mirror to be correct y_max must span the full touch panel, including the navbar area below the main display, so it gains TOUCH_BUTTON_AREA. The navbar band check also replaces the hardcoded touch_y > 200 with the board display height: on displays taller than 200 pixels, such as the Waveshare S3 Touch LCD 2 (280 pixels), the old value made taps on the bottom of the GUI area trigger navigation. Co-authored-by: oroderico --- main/input/touchscreen.inc | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/main/input/touchscreen.inc b/main/input/touchscreen.inc index 4f1540f1..5c541e97 100644 --- a/main/input/touchscreen.inc +++ b/main/input/touchscreen.inc @@ -29,9 +29,11 @@ static void touchscreen_task(void* ignored) { esp_lcd_touch_handle_t ret_touch = NULL; // FIXME: check mirror flags? + // NOTE: the touch panel extends below the main display over the navigation button + // area - y_max must cover it, as it is used when mirroring coordinates on flip const esp_lcd_touch_config_t tp_cfg = { .x_max = CONFIG_DISPLAY_WIDTH + CONFIG_DISPLAY_OFFSET_X, - .y_max = CONFIG_DISPLAY_HEIGHT + CONFIG_DISPLAY_OFFSET_Y, + .y_max = CONFIG_DISPLAY_HEIGHT + CONFIG_DISPLAY_OFFSET_Y + TOUCH_BUTTON_AREA, .rst_gpio_num = GPIO_NUM_NC, .int_gpio_num = GPIO_NUM_NC, .levels = { @@ -65,17 +67,30 @@ static void touchscreen_task(void* ignored) uint16_t touch_strength[1]; uint8_t touch_cnt = 10; +#if !defined(CONFIG_BOARD_TYPE_M5_CORE2) + // Track the display orientation and mirror the touch coordinates to match, so the + // virtual navbar buttons keep working when the display is flipped. Not on the M5 + // Core2, where the buttons are silk-screened on a fixed strip below the display. + bool touch_mirrored = false; +#endif + // FIXME: don't allow multiple touches within 300 ms? - // FIXME: this doesn't currently work with Display -> Flip Orientation feature - // but it could by changing the touch_y[0] > 200 logic with < 40 and inverting prev with next and viceversa while (!shutdown_requested) { +#if !defined(CONFIG_BOARD_TYPE_M5_CORE2) + const bool flipped = gui_get_flipped_orientation(); + if (touch_mirrored != flipped) { + ESP_ERROR_CHECK(esp_lcd_touch_set_mirror_x(ret_touch, flipped)); + ESP_ERROR_CHECK(esp_lcd_touch_set_mirror_y(ret_touch, flipped)); + touch_mirrored = flipped; + } +#endif if (esp_lcd_touch_read_data(ret_touch) == ESP_OK) { bool touchpad_pressed = esp_lcd_touch_get_coordinates(ret_touch, touch_x, touch_y, touch_strength, &touch_cnt, 1); if (touchpad_pressed) { const uint16_t first_third_end = CONFIG_DISPLAY_WIDTH / 3; const uint16_t middle_thirds_end = (CONFIG_DISPLAY_WIDTH * 2) / 3; - if (touch_y[0] > 200) { + if (touch_y[0] > CONFIG_DISPLAY_HEIGHT + CONFIG_DISPLAY_OFFSET_Y) { if (touch_x[0] <= first_third_end) { gui_prev(); } else if (touch_x[0] > first_third_end && touch_x[0] < middle_thirds_end) {