diff --git a/main/display/eink_display_handler.cpp b/main/display/eink_display_handler.cpp index 05a02f7..c9fa818 100644 --- a/main/display/eink_display_handler.cpp +++ b/main/display/eink_display_handler.cpp @@ -12,16 +12,20 @@ #define MINIMUM_PIN_SETUP_DELAY_MS 10 #define MINIMUM_POWER_ON_DELAY_MS 100 -static uint8_t black_data[DISPLAY_BUFFER_SIZE]; // all black data -static uint8_t white_data[DISPLAY_BUFFER_SIZE]; // all white data -static uint8_t DRAW_BUFFER[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8]; // 1 bit per pixel -static uint8_t OLD_DRAW_BUFFER[DISPLAY_WIDTH * DISPLAY_HEIGHT / 8]; // 1 bit per pixel +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; EInkDisplayHandler::EInkDisplayHandler() { - memset(black_data, 0xFF, sizeof(black_data)); // eink uses 1 for black - memset(white_data, 0x00, sizeof(white_data)); - memset(DRAW_BUFFER, 0x00, sizeof(DRAW_BUFFER)); // start with all white (0 = white in e-ink) - memset(OLD_DRAW_BUFFER, 0x00, sizeof(OLD_DRAW_BUFFER)); // start with all white (0 = white in e-ink) + 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; @@ -41,6 +45,18 @@ 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 (white_data != nullptr) { + heap_caps_free(white_data); + } + if (DRAW_BUFFER != nullptr) { + heap_caps_free(DRAW_BUFFER); + } + if (OLD_DRAW_BUFFER != nullptr) { + heap_caps_free(OLD_DRAW_BUFFER); + } } esp_err_t EInkDisplayHandler::deep_sleep_display(void) { @@ -205,11 +221,20 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool return ESP_OK; } +// TODO: Partial refresh is inverted in color esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_framebuffer, const RefreshArea& incoming_area, const bool is_last_partial_update) { ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)..."); esp_err_t err = ESP_OK; + write_to_buffer_(incoming_partial_framebuffer, incoming_area); + // Always expand refresh_area_ to include incoming_area + refresh_area_.expand_to_include(incoming_area); + + if (!is_last_partial_update) { + ESP_LOGI(TAG, "Partial refresh skipped (not last partial update)"); + return ESP_OK; + } { TransactionGuard transaction_guard(this->epd_handler_); err = transaction_guard.begin(pdMS_TO_TICKS(5000)); @@ -219,22 +244,18 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr } // Wake display from deep sleep INSIDE the transaction to prevent race conditions if (is_deep_sleep_) { - err = epd_init_partial_internal_(transaction_guard.transaction_id()); + err = epd_init_internal_(transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to initialize EPD for partial refresh: %s", esp_err_to_name(err)); return err; } + err = refresh_old_buffer_(transaction_guard.transaction_id()); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to refresh old buffer during partial refresh init: %s", esp_err_to_name(err)); + return err; + } } - write_to_buffer_(incoming_partial_framebuffer, incoming_area); - - // Always expand refresh_area_ to include incoming_area - refresh_area_.expand_to_include(incoming_area); - - if (!is_last_partial_update) { - ESP_LOGI(TAG, "Partial refresh skipped (not last partial update)"); - return ESP_OK; - } RefreshArea area = refresh_area_; if (area.x1 % 8 != 0 || area.x2 % 8 != 7) { @@ -248,7 +269,9 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr const uint32_t area_height = area.y2 - area.y1 + 1; const size_t partial_buffer_size = area_width_bytes * area_height; - uint8_t* partial_buffer = new uint8_t[partial_buffer_size]; + // uint8_t* partial_buffer = new uint8_t[partial_buffer_size]; + uint8_t* partial_buffer = static_cast(heap_caps_malloc(partial_buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL)); + if (partial_buffer == nullptr) { ESP_LOGE(TAG, "Failed to allocate partial buffer for partial refresh"); return ESP_ERR_NO_MEM; @@ -329,7 +352,7 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr err = epd_handler_.epd_write_cmd(0x13, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send new data command for partial refresh: %s", esp_err_to_name(err)); - delete[] partial_buffer; + heap_caps_free(partial_buffer); return err; } @@ -339,16 +362,19 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr err = epd_handler_.transfer_spi_data(partial_buffer, partial_buffer_size, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send partial_buffer data for partial refresh: %s", esp_err_to_name(err)); - delete[] partial_buffer; + heap_caps_free(partial_buffer); return err; } + + memcpy(old_buffer_, draw_buffer_, DISPLAY_BUFFER_SIZE); } // Clean up partial buffer - delete[] partial_buffer; + heap_caps_free(partial_buffer); // Step 6: Trigger partial display refresh (DRF) - err = epd_handler_.epd_write_cmd(0x11, transaction_guard.transaction_id()); + // Use 0x12 (Display Update) command - same as full refresh, per sample code + err = epd_handler_.epd_write_cmd(0x12, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send display refresh command for partial refresh: %s", esp_err_to_name(err)); return err; @@ -398,7 +424,6 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr return err; } - memcpy(old_buffer_, draw_buffer_, DISPLAY_BUFFER_SIZE); refresh_area_.reset(); @@ -702,7 +727,7 @@ esp_err_t EInkDisplayHandler::init_touch_() { } esp_err_t EInkDisplayHandler::refresh_old_buffer_(uint32_t transaction_id) { - ESP_LOGI(TAG, "Refreshing display SRAM to match current buffers..."); + ESP_LOGI(TAG, "Refreshing display SRAM to restore state after wake..."); esp_err_t err; err = epd_handler_.epd_write_cmd(0x92, transaction_id); // enter normal mode @@ -710,7 +735,11 @@ esp_err_t EInkDisplayHandler::refresh_old_buffer_(uint32_t transaction_id) { ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err)); return err; } - // Send command to write old data (0x10) + + // Write OLD data (0x10) as all 0x00 (white in e-ink terms) + // This tells the controller: "assume display was all white" + // Matches sample's EPD_WhiteScreen_ALL() which uses 0x00 for old SRAM + // The differential refresh: old=0 + new=0 → stay white, old=0 + new=1 → drive to black err = epd_handler_.epd_write_cmd(0x10, transaction_id); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err)); @@ -720,25 +749,25 @@ esp_err_t EInkDisplayHandler::refresh_old_buffer_(uint32_t transaction_id) { // Send the old buffer as old data err = epd_handler_.transfer_spi_data(old_buffer_, DISPLAY_BUFFER_SIZE, transaction_id); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send old buffer data: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to send white baseline to old SRAM: %s", esp_err_to_name(err)); return err; } - // Also write to new data register (0x13) to ensure consistent baseline - // This prevents undefined state after hardware reset + // Write NEW data (0x13) with the actual display content + // This restores the display to show old_buffer_ content err = epd_handler_.epd_write_cmd(0x13, transaction_id); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send new data command: %s", esp_err_to_name(err)); return err; } - // Send the draw buffer as new data (should match old_buffer_ after full refresh) + // Send the last displayed content to new SRAM err = epd_handler_.transfer_spi_data(old_buffer_, DISPLAY_BUFFER_SIZE, transaction_id); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send draw buffer data: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to send display content to new SRAM: %s", esp_err_to_name(err)); return err; } - ESP_LOGI(TAG, "Display SRAM refreshed successfully"); + ESP_LOGI(TAG, "Display SRAM restored successfully"); return ESP_OK; }