From d0a1e8c80fd170e995af526e87c865b5583f9c1a Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Sat, 24 Jan 2026 18:24:41 +0800 Subject: [PATCH] feat: enhance EInk display handler with improved resource management and non-blocking SPI transactions --- main/display/eink_display_handler.cpp | 149 +++++++++++++++++--------- main/display/eink_display_handler.h | 1 + 2 files changed, 100 insertions(+), 50 deletions(-) diff --git a/main/display/eink_display_handler.cpp b/main/display/eink_display_handler.cpp index 164efd2..2f9a7b5 100644 --- a/main/display/eink_display_handler.cpp +++ b/main/display/eink_display_handler.cpp @@ -22,7 +22,12 @@ EInkDisplayHandler::~EInkDisplayHandler() { vTaskDelete(_touch_task_handle); } if (_lvgl_display != nullptr) { - lvgl_port_remove_disp(_lvgl_display); + lv_display_delete(_lvgl_display); + _lvgl_display = nullptr; + if (_lvgl_draw_buf != nullptr) { + lv_draw_buf_destroy(_lvgl_draw_buf); + _lvgl_draw_buf = nullptr; + } } if (_lvgl_touch_indev != nullptr) { lvgl_port_remove_touch(_lvgl_touch_indev); @@ -73,7 +78,7 @@ void EInkDisplayHandler::init() { devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz (max for GDEY075T7) devcfg.mode = 0; // SPI mode 0 devcfg.spics_io_num = PIN_CS; - devcfg.queue_size = 1; + devcfg.queue_size = 7; // Queue size for non-blocking transactions devcfg.pre_cb = nullptr; ret = spi_bus_add_device(SPI2_HOST, &devcfg, &_spi); @@ -102,57 +107,58 @@ void EInkDisplayHandler::init() { } memset(_framebuffer, 0xFF, DISPLAY_BUFFER_SIZE); // Initialize to white - // Create LVGL display driver - lvgl_port_display_cfg_t disp_cfg = {}; - - disp_cfg.io_handle = nullptr; - disp_cfg.panel_handle = nullptr; - disp_cfg.buffer_size = DISPLAY_WIDTH * 40; // 40 lines buffer - disp_cfg.double_buffer = false; - disp_cfg.hres = DISPLAY_WIDTH; - disp_cfg.vres = DISPLAY_HEIGHT; - disp_cfg.monochrome = true; - - disp_cfg.rotation.swap_xy = false; - disp_cfg.rotation.mirror_x = false; - disp_cfg.rotation.mirror_y = false; - - disp_cfg.flags.buff_dma = _framebuffer_in_psram ? false : true; - disp_cfg.flags.buff_spiram = _framebuffer_in_psram; - disp_cfg.flags.swap_bytes = false; - disp_cfg.flags.full_refresh = false; - disp_cfg.flags.direct_mode = false; - - _lvgl_display = lvgl_port_add_disp(&disp_cfg); - if (_lvgl_display == nullptr) { + // Create LVGL display manually (no esp_lcd panel for e-paper) + lv_display_t* disp = lv_display_create(DISPLAY_WIDTH, DISPLAY_HEIGHT); + if (disp == nullptr) { ESP_LOGE(TAG, "Failed to create LVGL display"); return; } - // Set custom flush callback - lv_display_set_flush_cb(_lvgl_display, _lvgl_flush_cb); - lv_display_set_user_data(_lvgl_display, this); + /* 1-bit e-paper display */ + lv_display_set_color_format(disp, LV_COLOR_FORMAT_I1); - ESP_LOGI(TAG, "LVGL display registered"); - - // Register GT911 touch input with LVGL - const lvgl_port_touch_cfg_t touch_cfg = { - .disp = _lvgl_display, - .handle = get_touch_handle(), - .scale = {}, // Default scaling - }; - - _lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg); - if (_lvgl_touch_indev == nullptr) { - ESP_LOGE(TAG, "Failed to register LVGL touch input"); + /* Create a draw buffer covering ~40 lines */ + _lvgl_draw_buf = lv_draw_buf_create(DISPLAY_WIDTH, DISPLAY_HEIGHT, LV_COLOR_FORMAT_I1, LV_STRIDE_AUTO); + if (_lvgl_draw_buf == nullptr) { + ESP_LOGE(TAG, "Failed to create LVGL draw buffer"); + lv_display_delete(disp); return; } - // Override touch read callback to check BUSY pin - lv_indev_set_read_cb(_lvgl_touch_indev, _lvgl_touch_read_cb); - lv_indev_set_user_data(_lvgl_touch_indev, this); + lv_display_set_draw_buffers(disp, _lvgl_draw_buf, NULL); + lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT); - ESP_LOGI(TAG, "LVGL touch input registered"); + // Set custom flush callback and user data + lv_display_set_flush_cb(disp, _lvgl_flush_cb); + lv_display_set_user_data(disp, this); + + _lvgl_display = disp; + + ESP_LOGI(TAG, "LVGL display registered"); + + // Register GT911 touch input with LVGL, only if touch handle is valid + esp_lcd_touch_handle_t tp_handle = get_touch_handle(); + if (tp_handle == nullptr) { + ESP_LOGE(TAG, "Touch handle is NULL — touch initialization failed; skipping LVGL touch registration"); + } else { + const lvgl_port_touch_cfg_t touch_cfg = { + .disp = _lvgl_display, + .handle = tp_handle, + .scale = {}, // Default scaling + }; + + _lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg); + if (_lvgl_touch_indev == nullptr) { + ESP_LOGE(TAG, "Failed to register LVGL touch input"); + return; + } + + // Override touch read callback to check BUSY pin + lv_indev_set_read_cb(_lvgl_touch_indev, _lvgl_touch_read_cb); + lv_indev_set_user_data(_lvgl_touch_indev, this); + + ESP_LOGI(TAG, "LVGL touch input registered"); + } // Perform initial full refresh to clear display ESP_LOGI(TAG, "Performing initial display clear..."); @@ -302,19 +308,40 @@ void EInkDisplayHandler::_perform_full_refresh(const uint8_t* framebuffer) { _wait_for_busy(); + spi_transaction_t* rtrans; // Declare once for entire function + // Step 1: Write old data (0x10) - typically all zeros for full refresh epd_write_cmd(0x10); xSemaphoreTake(_spi_mutex, portMAX_DELAY); gpio_set_level(PIN_DC, 1); // Data mode + // Use queued transactions to allow task switching + static uint8_t zero_byte = 0x00; // Static to persist across queue operations for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; i++) { spi_transaction_t t = {}; t.length = 8; - uint8_t byte = 0x00; // Old data (cleared screen) - t.tx_buffer = &byte; - spi_device_polling_transmit(_spi, &t); + t.tx_buffer = &zero_byte; + + esp_err_t ret = spi_device_queue_trans(_spi, &t, portMAX_DELAY); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to queue SPI transaction: %s", esp_err_to_name(ret)); + break; + } + + // Retrieve result every 100 bytes to prevent queue overflow and allow yielding + if (i % 100 == 99) { + for (int j = 0; j < 100; j++) { + spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY); + } + } } + + // Get remaining results + for (size_t i = 0; i < (DISPLAY_BUFFER_SIZE % 100); i++) { + spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY); + } + xSemaphoreGive(_spi_mutex); // Step 2: Write new data (0x13) with data inversion @@ -323,13 +350,35 @@ void EInkDisplayHandler::_perform_full_refresh(const uint8_t* framebuffer) { xSemaphoreTake(_spi_mutex, portMAX_DELAY); gpio_set_level(PIN_DC, 1); // Data mode + // Use queued transactions with inverted framebuffer data + static uint8_t tx_buffer[100]; // Buffer for batch of inverted bytes for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; i++) { + size_t buf_idx = i % 100; + tx_buffer[buf_idx] = ~framebuffer[i]; // Invert data per manufacturer spec + spi_transaction_t t = {}; t.length = 8; - uint8_t byte = ~framebuffer[i]; // Invert data per manufacturer spec - t.tx_buffer = &byte; - spi_device_polling_transmit(_spi, &t); + t.tx_buffer = &tx_buffer[buf_idx]; + + esp_err_t ret = spi_device_queue_trans(_spi, &t, portMAX_DELAY); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to queue SPI transaction: %s", esp_err_to_name(ret)); + break; + } + + // Retrieve result every 100 bytes to prevent queue overflow and allow yielding + if (buf_idx == 99) { + for (int j = 0; j < 100; j++) { + spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY); + } + } } + + // Get remaining results + for (size_t i = 0; i < (DISPLAY_BUFFER_SIZE % 100); i++) { + spi_device_get_trans_result(_spi, &rtrans, portMAX_DELAY); + } + xSemaphoreGive(_spi_mutex); // Step 3: Trigger display refresh (DRF) diff --git a/main/display/eink_display_handler.h b/main/display/eink_display_handler.h index 4c5b53a..b49cf1d 100644 --- a/main/display/eink_display_handler.h +++ b/main/display/eink_display_handler.h @@ -28,6 +28,7 @@ private: // LVGL display and input device handles lv_display_t* _lvgl_display = nullptr; lv_indev_t* _lvgl_touch_indev = nullptr; + lv_draw_buf_t* _lvgl_draw_buf = nullptr; // Framebuffer uint8_t* _framebuffer = nullptr;