From c5d6cfcd22fbbccd9f5920dfae87f750c033ba62 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Mon, 20 Apr 2026 15:11:48 +0800 Subject: [PATCH] feat(display): Implement singleton pattern for EInkDisplayHandler and enhance buffer allocation checks --- main/display/eink_display_handler.cpp | 62 ++++++++++++++++----------- main/display/eink_display_handler.h | 4 +- main/display/epd_handler.cpp | 46 +++++++++++++------- 3 files changed, 72 insertions(+), 40 deletions(-) diff --git a/main/display/eink_display_handler.cpp b/main/display/eink_display_handler.cpp index 40426ad..edffee3 100644 --- a/main/display/eink_display_handler.cpp +++ b/main/display/eink_display_handler.cpp @@ -13,22 +13,31 @@ #define MINIMUM_POWER_ON_DELAY_MS 100 #define PARTIAL_REFRESH_THRESHOLD 5 // Full refresh every N partial refreshes -static uint8_t* DRAW_BUFFER; // 1 bit per pixel -static uint8_t* OLD_DRAW_BUFFER; // 1 bit per pixel -static uint8_t* black_data; -static uint8_t* white_data; +// Static flag to prevent multiple instances (these buffers are large, only one display allowed) +static bool display_instance_exists = false; EInkDisplayHandler::EInkDisplayHandler() { - black_data = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); - white_data = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); - DRAW_BUFFER = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); - OLD_DRAW_BUFFER = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); - memset(black_data, 0xFF, DISPLAY_BUFFER_SIZE); // eink uses 1 for black - memset(white_data, 0x00, DISPLAY_BUFFER_SIZE); - memset(DRAW_BUFFER, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink) - memset(OLD_DRAW_BUFFER, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink) - draw_buffer_ = DRAW_BUFFER; - old_buffer_ = OLD_DRAW_BUFFER; + if (display_instance_exists) { + ESP_LOGE(TAG, "Only one EInkDisplayHandler instance allowed!"); + return; + } + display_instance_exists = true; + + black_data_ = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); + white_data_ = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); + draw_buffer_ = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); + old_buffer_ = static_cast(heap_caps_malloc(DISPLAY_BUFFER_SIZE, MALLOC_CAP_SPIRAM)); + + // Check for allocation failures + if (!black_data_ || !white_data_ || !draw_buffer_ || !old_buffer_) { + ESP_LOGE(TAG, "Failed to allocate display buffers!"); + return; + } + + memset(black_data_, 0xFF, DISPLAY_BUFFER_SIZE); // eink uses 1 for black + memset(white_data_, 0x00, DISPLAY_BUFFER_SIZE); + memset(draw_buffer_, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink) + memset(old_buffer_, 0x00, DISPLAY_BUFFER_SIZE); // start with all white (0 = white in e-ink) refresh_mutex_ = xSemaphoreCreateMutex(); if (refresh_mutex_ == nullptr) { @@ -46,18 +55,23 @@ EInkDisplayHandler::~EInkDisplayHandler() { if (tp_io_handle_ != nullptr) { esp_lcd_panel_io_del(tp_io_handle_); } - if (black_data != nullptr) { - heap_caps_free(black_data); + if (black_data_ != nullptr) { + heap_caps_free(black_data_); + black_data_ = nullptr; } - if (white_data != nullptr) { - heap_caps_free(white_data); + if (white_data_ != nullptr) { + heap_caps_free(white_data_); + white_data_ = nullptr; } - if (DRAW_BUFFER != nullptr) { - heap_caps_free(DRAW_BUFFER); + if (draw_buffer_ != nullptr) { + heap_caps_free(draw_buffer_); + draw_buffer_ = nullptr; } - if (OLD_DRAW_BUFFER != nullptr) { - heap_caps_free(OLD_DRAW_BUFFER); + if (old_buffer_ != nullptr) { + heap_caps_free(old_buffer_); + old_buffer_ = nullptr; } + display_instance_exists = false; } esp_err_t EInkDisplayHandler::deep_sleep_display(void) { @@ -185,7 +199,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err)); return err; } - err = epd_handler_.transfer_spi_data(white_basemap ? black_data : white_data, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF) + err = epd_handler_.transfer_spi_data(white_basemap ? black_data_ : white_data_, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send all white data (0xFF) if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send all white data for old data: %s", esp_err_to_name(err)); return err; @@ -449,7 +463,7 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr esp_err_t EInkDisplayHandler::clear_display(void) { ESP_LOGV(TAG, "Clearing display to all white..."); - esp_err_t err = full_write(white_data, false); + esp_err_t err = full_write(white_data_, false); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to clear display: %s", esp_err_to_name(err)); return err; diff --git a/main/display/eink_display_handler.h b/main/display/eink_display_handler.h index 16127de..43010c7 100644 --- a/main/display/eink_display_handler.h +++ b/main/display/eink_display_handler.h @@ -90,9 +90,11 @@ private: esp_lcd_panel_io_handle_t tp_io_handle_ = nullptr; esp_lcd_touch_handle_t tp_handle_ = nullptr; - // this buffer reflects the current display state (1=black, 0=white) + // Display buffers (1=black, 0=white) uint8_t* draw_buffer_ = nullptr; uint8_t* old_buffer_ = nullptr; + uint8_t* black_data_ = nullptr; // All 0xFF (black pattern) + uint8_t* white_data_ = nullptr; // All 0x00 (white pattern) RefreshArea refresh_area_ = { 0, 0, 0, 0 }; }; diff --git a/main/display/epd_handler.cpp b/main/display/epd_handler.cpp index 4fd7429..83aea33 100644 --- a/main/display/epd_handler.cpp +++ b/main/display/epd_handler.cpp @@ -4,6 +4,7 @@ #include "common/constants.h" #include "esp_lcd_touch_gt911.h" #include +#include #define TAG "EPDHandler" #define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low @@ -213,10 +214,29 @@ esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& lengt size_t remaining = length; gpio_set_level(PIN_DC, 1); // Data mode - // Allocate a temporary buffer for inverted data (only if inverted) + // Check if data is in PSRAM (needs cache sync and staging buffer) + bool data_in_psram = (esp_ptr_external_ram((void*)data) != 0); + + if (data_in_psram) { + // Flush cache to ensure DMA sees the latest data in PSRAM + esp_err_t cache_err = esp_cache_msync((void*)data, length, ESP_CACHE_MSYNC_FLAG_DIR_C2M); + if (cache_err != ESP_OK) { + ESP_LOGW(TAG, "Cache sync failed: %s", esp_err_to_name(cache_err)); + } + } + + // Use staging buffer in internal DMA-capable RAM + // PSRAM cannot be allocated with MALLOC_CAP_DMA, so we always use a staging buffer + uint8_t* staging_buffer = (uint8_t*)heap_caps_malloc(DMA_TRANSFER_CHUNK_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); + if (staging_buffer == nullptr) { + ESP_LOGE(TAG, "Failed to allocate DMA staging buffer"); + return ESP_ERR_NO_MEM; + } + + // Additional buffer needed only for inverted data uint8_t* temp_transfer_buffer = nullptr; if (inverted) { - temp_transfer_buffer = (uint8_t*)heap_caps_malloc(DMA_TRANSFER_CHUNK_SIZE, MALLOC_CAP_DMA); + temp_transfer_buffer = (uint8_t*)heap_caps_malloc(DMA_TRANSFER_CHUNK_SIZE, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL); if (temp_transfer_buffer == nullptr) { ESP_LOGE(TAG, "Failed to allocate memory for inverted data transfer buffer"); ESP_LOGI(TAG, "Current free heap size: %u bytes", esp_get_free_heap_size()); @@ -229,31 +249,27 @@ esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& lengt while (remaining > 0) { size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE; - const uint8_t* transfer_buffer = nullptr; + // Copy data to DMA-capable staging buffer + // Required because PSRAM cannot be allocated with MALLOC_CAP_DMA if (inverted) { - // Invert only the current chunk into the temporary buffer + // Invert while copying for (size_t i = 0; i < transfer_size; ++i) { - temp_transfer_buffer[i] = ~data[offset + i]; + staging_buffer[i] = ~data[offset + i]; } - transfer_buffer = temp_transfer_buffer; } else { - transfer_buffer = data + offset; + // Straight copy from PSRAM to internal DMA buffer + memcpy(staging_buffer, data + offset, transfer_size); } spi_transaction_t t = {}; t.length = transfer_size * 8; // Length in bits - t.tx_buffer = transfer_buffer; + t.tx_buffer = staging_buffer; esp_err_t ret = spi_device_polling_transmit(spi_, &t); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to send SPI chunk at offset %zu: %s", offset, esp_err_to_name(ret)); - if (ret == ESP_ERR_NO_MEM) { - ESP_LOGE(TAG, "Current free heap size: %u bytes", esp_get_free_heap_size()); - ESP_LOGE(TAG, "Current free DMA-capable memory size: %u bytes", - heap_caps_get_free_size(MALLOC_CAP_DMA)); - } + heap_caps_free(staging_buffer); if (inverted && temp_transfer_buffer != nullptr) { - // Free the temporary inverted buffer heap_caps_free(temp_transfer_buffer); } return ret; @@ -269,8 +285,8 @@ esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& lengt } } + heap_caps_free(staging_buffer); if (inverted && temp_transfer_buffer != nullptr) { - // Free the temporary inverted buffer heap_caps_free(temp_transfer_buffer); }