diff --git a/main/display/eink_display_handler.cpp b/main/display/eink_display_handler.cpp index 8f44eb3..1bbb7fa 100644 --- a/main/display/eink_display_handler.cpp +++ b/main/display/eink_display_handler.cpp @@ -11,45 +11,30 @@ #define DISPLAY_BUFFER_SIZE (EINK_HEIGHT* EINK_WIDTH) / 8 // 1 bit per pixels #define MINIMUM_PIN_SETUP_DELAY_MS 10 #define MINIMUM_POWER_ON_DELAY_MS 100 -#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low -#define BUSY_INACTIVE_LEVEL 1 -#define DMA_TRANSFER_CHUNK_SIZE 4096 // 4KB chunk size for DMA transfers 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 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 + 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) draw_buffer_ = DRAW_BUFFER; - spi_mutex_ = xSemaphoreCreateMutex(); - if (spi_mutex_ == nullptr) { - ESP_LOGE(TAG, "Failed to create SPI mutex"); - } - spi_transaction_mutex_ = xSemaphoreCreateMutex(); - if (spi_transaction_mutex_ == nullptr) { - ESP_LOGE(TAG, "Failed to create SPI transaction mutex"); - } + old_buffer_ = OLD_DRAW_BUFFER; + refresh_mutex_ = xSemaphoreCreateMutex(); if (refresh_mutex_ == nullptr) { ESP_LOGE(TAG, "Failed to create refresh mutex"); } } + EInkDisplayHandler::~EInkDisplayHandler() { - if (spi_mutex_ != nullptr) { - vSemaphoreDelete(spi_mutex_); - } - if (spi_transaction_mutex_ != nullptr) { - vSemaphoreDelete(spi_transaction_mutex_); - } if (refresh_mutex_ != nullptr) { vSemaphoreDelete(refresh_mutex_); } - if (spi_ != nullptr) { - spi_bus_remove_device(spi_); - } if (tp_handle_ != nullptr) { esp_lcd_touch_del(tp_handle_); } @@ -66,26 +51,26 @@ esp_err_t EInkDisplayHandler::deep_sleep_display(void) { } { esp_err_t err = ESP_OK; - TransactionGuard transaction_guard(*this); + TransactionGuard transaction_guard(this->epd_handler_); err = transaction_guard.begin(pdMS_TO_TICKS(5000)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin transaction for deep sleep: %s", esp_err_to_name(err)); return err; } - wait_for_idle(); + epd_handler_.wait_for_idle(); - err = epd_write_cmd(0x02, transaction_guard.transaction_id()); // power off + err = epd_handler_.epd_write_cmd(0x02, transaction_guard.transaction_id()); // power off if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send power off command: %s", esp_err_to_name(err)); return err; } - wait_for_idle(); - err = epd_write_cmd(0x07, transaction_guard.transaction_id()); //deep sleep + epd_handler_.wait_for_idle(); + err = epd_handler_.epd_write_cmd(0x07, transaction_guard.transaction_id()); //deep sleep if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send deep sleep command: %s", esp_err_to_name(err)); return err; } - err = epd_write_data(0xA5, transaction_guard.transaction_id()); + err = epd_handler_.epd_write_data(0xA5, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send deep sleep data: %s", esp_err_to_name(err)); return err; @@ -98,32 +83,32 @@ esp_err_t EInkDisplayHandler::deep_sleep_display(void) { esp_err_t EInkDisplayHandler::refresh_display() { esp_err_t err = ESP_OK; - if (is_deep_sleep_) { - epd_init_(); - } - { ESP_LOGI(TAG, "Waiting for display to be idle..."); - TransactionGuard transaction_guard(*this); + TransactionGuard transaction_guard(this->epd_handler_); err = transaction_guard.begin(pdMS_TO_TICKS(10000)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin transaction for display refresh: %s", esp_err_to_name(err)); return err; } - wait_for_idle(); + if (is_deep_sleep_) { + epd_init_internal_(transaction_guard.transaction_id()); + } + + epd_handler_.wait_for_idle(); ESP_LOGI(TAG, "Starting display refresh..."); - err = epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode + err = epd_handler_.epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err)); return err; } - err = epd_write_cmd(0x12, transaction_guard.transaction_id()); // display refresh + err = epd_handler_.epd_write_cmd(0x12, transaction_guard.transaction_id()); // display refresh if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send display refresh command: %s", esp_err_to_name(err)); return err; } vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay - wait_for_idle(); + epd_handler_.wait_for_idle(); } { @@ -136,12 +121,6 @@ esp_err_t EInkDisplayHandler::refresh_display() { force_full_refresh_ = false; } - err = deep_sleep_display(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to enter deep sleep after refresh: %s", esp_err_to_name(err)); - return err; - } - ESP_LOGI(TAG, "Refresh complete"); return ESP_OK; } @@ -150,35 +129,35 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool ESP_LOGI(TAG, "Starting full refresh (3 seconds)..."); esp_err_t err = ESP_OK; - write_to_buffer_(framebuffer, RefreshArea { 0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1 }); - - if (is_deep_sleep_) { - epd_init_(); - } { - TransactionGuard transaction_guard(*this); + TransactionGuard transaction_guard(this->epd_handler_); err = transaction_guard.begin(pdMS_TO_TICKS(10000)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin transaction for full refresh: %s", esp_err_to_name(err)); return err; } + if (is_deep_sleep_) { + epd_init_internal_(transaction_guard.transaction_id()); + } - wait_for_idle(); + write_to_buffer_(framebuffer, RefreshArea { 0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1 }); + + epd_handler_.wait_for_idle(); // Step 0: Enter normal mode - err = epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode + err = epd_handler_.epd_write_cmd(0x92, transaction_guard.transaction_id()); // enter normal mode if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err)); return err; } // Step 1: Write old data (0x10) - Arduino uses 0xFF (all white) for base map { - err = epd_write_cmd(0x10, transaction_guard.transaction_id()); + err = epd_handler_.epd_write_cmd(0x10, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send old data command: %s", esp_err_to_name(err)); return err; } - err = 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; @@ -187,20 +166,20 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool // Step 2: Write new data (0x13) { - err = epd_write_cmd(0x13, transaction_guard.transaction_id()); + err = epd_handler_.epd_write_cmd(0x13, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send new data command: %s", esp_err_to_name(err)); return err; } - err = transfer_spi_data(draw_buffer_, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data + err = epd_handler_.transfer_spi_data(draw_buffer_, DISPLAY_BUFFER_SIZE, transaction_guard.transaction_id()); // Send new framebuffer data if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send framebuffer data for new data: %s", esp_err_to_name(err)); return err; } } // Step 3: Trigger display refresh (DRF) - err = epd_write_cmd(0x12, transaction_guard.transaction_id()); + err = epd_handler_.epd_write_cmd(0x12, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send display refresh command: %s", esp_err_to_name(err)); return err; @@ -210,7 +189,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool ESP_LOGI(TAG, "Display refresh triggered, BUSY pin: %d", gpio_get_level(PIN_BUSY)); // Wait for refresh to complete - wait_for_idle(); + epd_handler_.wait_for_idle(); } err = deep_sleep_display(); @@ -220,6 +199,7 @@ esp_err_t EInkDisplayHandler::full_write(const uint8_t* framebuffer, const bool } refresh_area_.reset(); + memcpy(old_buffer_, draw_buffer_, DISPLAY_BUFFER_SIZE); ESP_LOGI(TAG, "Full refresh complete"); return ESP_OK; @@ -229,49 +209,14 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr ESP_LOGI(TAG, "Starting partial refresh (0.3 seconds)..."); esp_err_t err = ESP_OK; - write_to_buffer_(incoming_partial_framebuffer, incoming_area); - - if (!is_last_partial_update) { - ESP_LOGI(TAG, "Partial refresh skipped (not last partial update)"); - refresh_area_.expand_to_include(incoming_area); - return ESP_OK; - } - - RefreshArea area = refresh_area_; - if (area.x1 % 8 != 0 || area.x2 % 8 != 7) { - ESP_LOGE(TAG, "Partial refresh area x1 and x2 must be byte-aligned (x1 %% 8 == 0 and x2 %% 8 == 7)"); - ESP_LOGI(TAG, "Given area: x1=%d, x2=%d", area.x1, area.x2); - return ESP_ERR_INVALID_ARG; - } - - // Calculate partial buffer size based on the refresh area - const uint32_t area_width_bytes = (area.x2 - area.x1 + 1) / 8; - 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]; - if (partial_buffer == nullptr) { - ESP_LOGE(TAG, "Failed to allocate partial buffer for partial refresh"); - return ESP_ERR_NO_MEM; - } - // Copy the relevant area from draw_buffer_ to partial_buffer - for (int32_t row = 0; row < area_height; ++row) { - uint32_t fb_y = area.y1 + row; - uint32_t fb_x_byte_start = area.x1 / 8; - uint8_t* fb_ptr = &draw_buffer_[fb_y * (DISPLAY_WIDTH / 8) + fb_x_byte_start]; - uint8_t* dest_ptr = &partial_buffer[row * area_width_bytes]; - memcpy(dest_ptr, fb_ptr, area_width_bytes); - } - { - TransactionGuard transaction_guard(*this); + TransactionGuard transaction_guard(this->epd_handler_); err = transaction_guard.begin(pdMS_TO_TICKS(5000)); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to begin transaction for partial refresh: %s", esp_err_to_name(err)); return err; } - // 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()); @@ -281,17 +226,54 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr } } - wait_for_idle(); + 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) { + ESP_LOGE(TAG, "Partial refresh area x1 and x2 must be byte-aligned (x1 %% 8 == 0 and x2 %% 8 == 7)"); + ESP_LOGI(TAG, "Given area: x1=%d, x2=%d", area.x1, area.x2); + return ESP_ERR_INVALID_ARG; + } + + // Calculate partial buffer size based on the refresh area + const uint32_t area_width_bytes = (area.x2 - area.x1 + 1) / 8; + 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]; + if (partial_buffer == nullptr) { + ESP_LOGE(TAG, "Failed to allocate partial buffer for partial refresh"); + return ESP_ERR_NO_MEM; + } + // Copy the relevant area from draw_buffer_ to partial_buffer + for (int32_t row = 0; row < area_height; ++row) { + uint32_t fb_y = area.y1 + row; + uint32_t fb_x_byte_start = area.x1 / 8; + uint8_t* fb_ptr = &draw_buffer_[fb_y * (DISPLAY_WIDTH / 8) + fb_x_byte_start]; + uint8_t* dest_ptr = &partial_buffer[row * area_width_bytes]; + memcpy(dest_ptr, fb_ptr, area_width_bytes); + } + + + epd_handler_.wait_for_idle(); // Step 1 VCOM setting std::vector vcom_data = { 0xA9, 0x07 }; - err = epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM for partial refresh + err = epd_handler_.epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM for partial refresh if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set VCOM for partial refresh: %s", esp_err_to_name(err)); return err; } // Step 2: Enter partial refresh mode - err = epd_write_cmd(0x91, transaction_guard.transaction_id()); // Enter partial mode + err = epd_handler_.epd_write_cmd(0x91, transaction_guard.transaction_id()); // Enter partial mode if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to enter partial refresh mode: %s", esp_err_to_name(err)); return err; @@ -335,33 +317,38 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr ESP_LOGI(TAG, "Partial window data: %02X %02X %02X %02X %02X %02X %02X %02X", window_data[0], window_data[1], window_data[2], window_data[3], window_data[4], window_data[5], window_data[6], window_data[7]); - err = epd_write_cmd_with_data(0x90, window_data, transaction_guard.transaction_id()); // Set partial window + err = epd_handler_.epd_write_cmd_with_data(0x90, window_data, transaction_guard.transaction_id()); // Set partial window if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send set partial window command: %s", esp_err_to_name(err)); return err; } } - // Step 4: Write new data (0x13) + // Step 5: Write new data (0x13) { - err = epd_write_cmd(0x13, transaction_guard.transaction_id()); + 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; return err; } // Send only the partial area data, not the full display buffer - ESP_LOGI(TAG, "Sending partial buffer: %zu bytes (area: %dx%d)", + ESP_LOGI(TAG, "Sending new partial buffer: %zu bytes (area: %dx%d)", partial_buffer_size, area_width_bytes * 8, area_height); - err = transfer_spi_data(partial_buffer, partial_buffer_size, transaction_guard.transaction_id()); + 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; return err; } } - // Step 5: Trigger partial display refresh (DRF) by ending the data write - err = epd_write_cmd(0x11, transaction_guard.transaction_id()); + // Clean up partial buffer + delete[] partial_buffer; + + // Step 6: Trigger partial display refresh (DRF) + err = epd_handler_.epd_write_cmd(0x11, 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; @@ -369,9 +356,9 @@ esp_err_t EInkDisplayHandler::partial_refresh(const uint8_t* incoming_partial_fr vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // at least 200us delay - wait_for_idle(); - // Step 6: Exit partial mode - err = epd_write_cmd(0x92, transaction_guard.transaction_id()); + epd_handler_.wait_for_idle(); + // Step 7: Exit partial mode + err = epd_handler_.epd_write_cmd(0x92, transaction_guard.transaction_id()); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to exit partial refresh mode: %s", esp_err_to_name(err)); return err; @@ -411,6 +398,8 @@ 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(); return ESP_OK; @@ -454,26 +443,6 @@ void EInkDisplayHandler::request_full_refresh(void) { } } -// Check if display is busy (refreshing) -bool EInkDisplayHandler::is_busy(void) const { - return gpio_get_level(PIN_BUSY) != BUSY_ACTIVE_LEVEL; // BUSY is active LOW -} -void EInkDisplayHandler::wait_for_idle(void) const { - ESP_LOGI(TAG, "Waiting for display ready (BUSY pin)..."); - int initial_level = gpio_get_level(PIN_BUSY); - ESP_LOGI(TAG, "Initial BUSY pin level: %d (0=BUSY, 1=FREE)", initial_level); - - // If already free, no need to wait - if (initial_level == BUSY_INACTIVE_LEVEL) { - ESP_LOGI(TAG, "Display already ready (BUSY pin = 1)"); - return; - } - while (gpio_get_level(PIN_BUSY) != BUSY_INACTIVE_LEVEL) { - vTaskDelay(pdMS_TO_TICKS(10)); - } - ESP_LOGI(TAG, "Display is now ready (BUSY pin = 1)"); -} - esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group) { esp_err_t err; @@ -482,9 +451,9 @@ esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group ESP_LOGE(TAG, "Failed to initialize display pins: %s", esp_err_to_name(err)); return err; } - err = epd_init_(); + err = this->epd_handler_.init(); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize EPD: %s", esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to initialize EPD handler: %s", esp_err_to_name(err)); return err; } err = init_touch_(); @@ -492,12 +461,6 @@ esp_err_t EInkDisplayHandler::init_devices(EventGroupHandle_t system_event_group ESP_LOGE(TAG, "Failed to initialize touch: %s", esp_err_to_name(err)); return err; } - err = deep_sleep_display(); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to put display into deep sleep: %s", esp_err_to_name(err)); - return err; - } - // if system_event_group is provided, set display ready bits if (system_event_group != nullptr) { // Indicate that display is ready @@ -536,147 +499,88 @@ esp_err_t EInkDisplayHandler::init_display_pins_(void) { return ret; } - // Initialize SPI bus - spi_bus_config_t buscfg = {}; - buscfg.mosi_io_num = 11; // MOSI pin - buscfg.miso_io_num = -1; // No MISO for e-paper - buscfg.sclk_io_num = 12; // SCK pin - buscfg.quadwp_io_num = -1; - buscfg.quadhd_io_num = -1; - buscfg.max_transfer_sz = DISPLAY_BUFFER_SIZE; - - ret = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret)); - return ret; - } - - // Add SPI device - spi_device_interface_config_t devcfg = {}; - devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz - devcfg.mode = 0; // SPI mode 0 - devcfg.spics_io_num = PIN_CS; - devcfg.queue_size = 7; // Queue size for non-blocking transactions - devcfg.pre_cb = nullptr; - - ret = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(ret)); - return ret; - } return ESP_OK; } - -// required to be called by inheriting class after SPI device is created -esp_err_t EInkDisplayHandler::epd_init_(void) { +esp_err_t EInkDisplayHandler::epd_init_internal_(uint32_t transaction_id) { ESP_LOGI(TAG, "Initializing EPD..."); esp_err_t err; - { - TransactionGuard transaction_guard(*this); - esp_err_t begin_err = transaction_guard.begin(); - if (begin_err != ESP_OK) { - ESP_LOGE(TAG, "Failed to begin transaction: %s", esp_err_to_name(begin_err)); - return begin_err; - } - // 1. Hardware Reset - err = gpio_set_level(PIN_RST, 0); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set PIN_RST low: %s", esp_err_to_name(err)); - return err; - } - vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); - err = gpio_set_level(PIN_RST, 1); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to set PIN_RST high: %s", esp_err_to_name(err)); - return err; - } - vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); - - // 2. Initialization Sequence - std::vector panel_setting_data = { 0x1F }; - err = epd_write_cmd_with_data(0x00, panel_setting_data, transaction_guard.transaction_id()); // Panel Setting - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err)); - return err; - } - vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); - std::vector vcom_data = { 0x10, 0x07 }; - err = epd_write_cmd_with_data(0x50, vcom_data, transaction_guard.transaction_id()); // VCOM - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send VCOM command: %s", esp_err_to_name(err)); - return err; - } - vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); - err = epd_write_cmd(0x04, transaction_guard.transaction_id()); // Power ON - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err)); - return err; - } - vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); // Wait for power on - - // Check BUSY pin with detailed logging - ESP_LOGI(TAG, "Waiting for EPD to be ready after power on..."); - ESP_LOGI(TAG, "BUSY pin level after power on: %d (0=BUSY, 1=FREE)", gpio_get_level(PIN_BUSY)); - - int busy_timeout = 0; - while (gpio_get_level(PIN_BUSY) == BUSY_ACTIVE_LEVEL) { // BUSY is active LOW - vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); - busy_timeout++; - if (busy_timeout > 500) { // 5 second timeout - ESP_LOGE(TAG, "EPD power on timeout! BUSY pin stuck at 0"); - return ESP_ERR_TIMEOUT; - } - if (busy_timeout % 50 == 0) { // Log every 500ms - ESP_LOGW(TAG, "Still waiting for EPD power on, timeout: %d/500", busy_timeout); - } - } - ESP_LOGI(TAG, "EPD power on complete after %d * 10ms, BUSY pin: %d", busy_timeout, gpio_get_level(PIN_BUSY)); - std::vector booster_data = { 0x27, 0x27, 0x18, 0x17 }; - err = epd_write_cmd_with_data(0x06, booster_data, transaction_guard.transaction_id()); // Booster Soft Start - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send Booster Soft Start command: %s", esp_err_to_name(err)); - return err; - } - vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); - - // Enhanced display drive commands - std::vector e0_data = { 0x02 }; - err = epd_write_cmd_with_data(0xE0, e0_data, transaction_guard.transaction_id()); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err)); - return err; - } - std::vector e5_data = { 0x5A }; - err = epd_write_cmd_with_data(0xE5, e5_data, transaction_guard.transaction_id()); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err)); - return err; - } - - is_deep_sleep_ = false; - - err = refresh_old_buffer_(transaction_guard.transaction_id()); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to refresh old buffer during init: %s", esp_err_to_name(err)); - return err; - } + // 1. Hardware Reset + err = gpio_set_level(PIN_RST, 0); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set PIN_RST low: %s", esp_err_to_name(err)); + return err; } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + err = gpio_set_level(PIN_RST, 1); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to set PIN_RST high: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + + // 2. Initialization Sequence + std::vector panel_setting_data = { 0x1F }; + err = epd_handler_.epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id); // Panel Setting + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + std::vector vcom_data = { 0x10, 0x07 }; + err = epd_handler_.epd_write_cmd_with_data(0x50, vcom_data, transaction_id); // VCOM + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send VCOM command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + err = epd_handler_.epd_write_cmd(0x04, transaction_id); // Power ON + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); // Wait for power on + + // Check BUSY pin with detailed logging + ESP_LOGI(TAG, "Waiting for EPD to be ready after power on..."); + ESP_LOGI(TAG, "BUSY pin level after power on: %d (0=BUSY, 1=FREE)", gpio_get_level(PIN_BUSY)); + + epd_handler_.wait_for_idle(); + std::vector booster_data = { 0x27, 0x27, 0x18, 0x17 }; + err = epd_handler_.epd_write_cmd_with_data(0x06, booster_data, transaction_id); // Booster Soft Start + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Booster Soft Start command: %s", esp_err_to_name(err)); + return err; + } + vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); + + // Enhanced display drive commands + std::vector e0_data = { 0x02 }; + err = epd_handler_.epd_write_cmd_with_data(0xE0, e0_data, transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err)); + return err; + } + std::vector e5_data = { 0x5A }; + err = epd_handler_.epd_write_cmd_with_data(0xE5, e5_data, transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command: %s", esp_err_to_name(err)); + return err; + } + + is_deep_sleep_ = false; + + err = refresh_old_buffer_(transaction_id); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to refresh old buffer during init: %s", esp_err_to_name(err)); + return err; + } + return err; } -esp_err_t EInkDisplayHandler::epd_init_partial_(void) { - TransactionGuard transaction_guard(*this); - esp_err_t begin_err = transaction_guard.begin(); - if (begin_err != ESP_OK) { - ESP_LOGE(TAG, "Failed to begin transaction: %s", esp_err_to_name(begin_err)); - return begin_err; - } - return epd_init_partial_internal_(transaction_guard.transaction_id()); -} - // Internal version that uses an existing transaction (no separate TransactionGuard) esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id) { ESP_LOGI(TAG, "Initializing EPD for partial refresh (internal)..."); @@ -698,7 +602,7 @@ esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id // 2. Panel Setting std::vector panel_setting_data = { 0x1F }; - err = epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id); + err = epd_handler_.epd_write_cmd_with_data(0x00, panel_setting_data, transaction_id); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send Panel Setting command: %s", esp_err_to_name(err)); return err; @@ -706,24 +610,24 @@ esp_err_t EInkDisplayHandler::epd_init_partial_internal_(uint32_t transaction_id vTaskDelay(pdMS_TO_TICKS(MINIMUM_PIN_SETUP_DELAY_MS)); // 3. Power ON - err = epd_write_cmd(0x04, transaction_id); + err = epd_handler_.epd_write_cmd(0x04, transaction_id); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send Power ON command: %s", esp_err_to_name(err)); return err; } vTaskDelay(pdMS_TO_TICKS(MINIMUM_POWER_ON_DELAY_MS)); - wait_for_idle(); + epd_handler_.wait_for_idle(); // 4. Partial initialization sequence - Enhanced Display Drive std::vector e0_data = { 0x02 }; - err = epd_write_cmd_with_data(0xE0, e0_data, transaction_id); + err = epd_handler_.epd_write_cmd_with_data(0xE0, e0_data, transaction_id); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command (E0): %s", esp_err_to_name(err)); return err; } std::vector e5_data = { 0x6E }; - err = epd_write_cmd_with_data(0xE5, e5_data, transaction_id); + err = epd_handler_.epd_write_cmd_with_data(0xE5, e5_data, transaction_id); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to send Enhanced Display Drive command (E5): %s", esp_err_to_name(err)); return err; @@ -804,221 +708,43 @@ esp_err_t EInkDisplayHandler::init_touch_() { } esp_err_t EInkDisplayHandler::refresh_old_buffer_(uint32_t transaction_id) { - ESP_LOGI(TAG, "Refreshing old buffer to match current draw buffer..."); + ESP_LOGI(TAG, "Refreshing display SRAM to match current buffers..."); esp_err_t err; - // Send command to write old data - err = epd_write_cmd(0x10, transaction_id); // Command to write old data + err = epd_handler_.epd_write_cmd(0x92, transaction_id); // enter normal mode + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to enter normal mode: %s", esp_err_to_name(err)); + return err; + } + // Send command to write old data (0x10) + 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)); return err; } - // Send the current draw buffer as old data - err = transfer_spi_data(draw_buffer_, DISPLAY_BUFFER_SIZE, 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)); return err; } - ESP_LOGI(TAG, "Old buffer refreshed successfully"); - return ESP_OK; -} - -esp_err_t EInkDisplayHandler::epd_write_cmd(const uint8_t cmd, uint32_t transaction_id) { - ESP_LOGI(TAG, "epd_write_cmd: waiting to send 0x%02X", cmd); - - SemaphoreGuard transaction_guard(spi_transaction_mutex_); - esp_err_t err = - wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + // Also write to new data register (0x13) to ensure consistent baseline + // This prevents undefined state after hardware reset + err = epd_handler_.epd_write_cmd(0x13, transaction_id); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s", - cmd, esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to send new data command: %s", esp_err_to_name(err)); return err; } - SemaphoreGuard guard(spi_mutex_); - if (!guard.take(pdMS_TO_TICKS(5000))) { - ESP_LOGE(TAG, "SPI mutex timeout for cmd 0x%02X", cmd); - return ESP_ERR_TIMEOUT; - } - err = dangerous_epd_write_cmd_without_lock_(cmd); - ESP_LOGI(TAG, "epd_write_cmd: 0x%02X done", cmd); - return err; -} - -esp_err_t EInkDisplayHandler::epd_write_data(const uint8_t data, uint32_t transaction_id) { - ESP_LOGI(TAG, "epd_write_data: waiting to send 0x%02X", data); - SemaphoreGuard transaction_guard(spi_transaction_mutex_); - esp_err_t err = - wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + // Send the draw buffer as new data (should match old_buffer_ after full refresh) + err = epd_handler_.transfer_spi_data(old_buffer_, DISPLAY_BUFFER_SIZE, transaction_id); if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data 0x%02X: %s", - data, esp_err_to_name(err)); - return err; - } - SemaphoreGuard guard(spi_mutex_); - if (!guard.take(pdMS_TO_TICKS(5000))) { - ESP_LOGE(TAG, "SPI mutex timeout for data 0x%02X", data); - return ESP_ERR_TIMEOUT; - } - err = dangerous_epd_write_data_without_lock_(data); - ESP_LOGI(TAG, "epd_write_data: 0x%02X done", data); - return err; -} - -esp_err_t EInkDisplayHandler::epd_write_cmd_with_data(const uint8_t cmd, std::vector& data, uint32_t transaction_id) { - const size_t data_len = data.size(); - ESP_LOGI(TAG, "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, data_len); - - SemaphoreGuard transaction_guard(spi_transaction_mutex_); - esp_err_t err = - wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s, with data", - cmd, esp_err_to_name(err)); + ESP_LOGE(TAG, "Failed to send draw buffer data: %s", esp_err_to_name(err)); return err; } - SemaphoreGuard guard(spi_mutex_); - if (!guard.take(pdMS_TO_TICKS(5000))) { - ESP_LOGE(TAG, "SPI mutex timeout for cmd with data 0x%02X", cmd); - return ESP_ERR_TIMEOUT; - } - err = dangerous_epd_write_cmd_without_lock_(cmd); - if (err != ESP_OK) { - return err; - }; - for (size_t i = 0; i < data_len; ++i) { - err = dangerous_epd_write_data_without_lock_(data[i]); - if (err != ESP_OK) { - return err; - } - } - ESP_LOGI(TAG, "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, data_len); + ESP_LOGI(TAG, "Display SRAM refreshed successfully"); return ESP_OK; } - - -esp_err_t EInkDisplayHandler::dangerous_epd_write_cmd_without_lock_(const uint8_t cmd) { - ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: sending 0x%02X", cmd); - gpio_set_level(PIN_DC, 0); // Command mode - spi_transaction_t t {}; - t.length = 8;t.tx_buffer = &cmd; - esp_err_t err = spi_device_polling_transmit(spi_, &t); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send data 0x%02X", cmd); - } else { - ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: 0x%02X sent", cmd); - } - return err; -} - -esp_err_t EInkDisplayHandler::dangerous_epd_write_data_without_lock_(const uint8_t data) { - ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: sending 0x%02X", data); - gpio_set_level(PIN_DC, 1); // Data mode - spi_transaction_t t = { }; - t.length = 8; t.tx_buffer = &data; - esp_err_t err = spi_device_polling_transmit(spi_, &t); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to send data 0x%02X", data); - } else { - ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: 0x%02X sent", data); - } - return err; -} - -esp_err_t EInkDisplayHandler::transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id) { - ESP_LOGI(TAG, "transfer_spi_data: waiting to send %zu bytes of data", length); - - SemaphoreGuard transaction_guard(spi_transaction_mutex_); - esp_err_t err = - wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data of %zu bytes: %s", - length, esp_err_to_name(err)); - return err; - } - SemaphoreGuard guard(spi_mutex_); - if (!guard.take(pdMS_TO_TICKS(5000))) { - ESP_LOGE(TAG, "SPI mutex timeout for data transfer of %zu bytes", length); - return ESP_ERR_TIMEOUT; - } - ESP_LOGI(TAG, "transfer_spi_data: starting to send %zu bytes of data", length); - - size_t offset = 0; - size_t remaining = length; - gpio_set_level(PIN_DC, 1); // Data mode - while (remaining > 0) { - size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE; - - spi_transaction_t t = {}; - t.length = transfer_size * 8; // Length in bits - t.tx_buffer = data + offset; - - 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)); - return ret; - } - - remaining -= transfer_size; - offset += transfer_size; - - // Yield every 16KB to prevent watchdog timeout - if (offset % (16 * 1024) == 0) { - ESP_LOGI(TAG, "New data progress: %zu/%zu bytes sent, yielding...", offset, length); - vTaskDelay(pdMS_TO_TICKS(1)); - } - } - - ESP_LOGI(TAG, "transfer_spi_data: completed sending %zu bytes of data", length); - return ESP_OK; -} - -esp_err_t EInkDisplayHandler::begin_transaction_(TickType_t timeout, uint32_t& out_id) { - ESP_LOGI(TAG, "begin_transaction_: waiting to obtain transaction mutex"); - if (xSemaphoreTake(spi_transaction_mutex_, timeout) != pdTRUE) { - ESP_LOGE(TAG, "begin_transaction_: transaction mutex timeout"); - return ESP_ERR_TIMEOUT; - } - - out_id = ++spi_transaction_id; - ESP_LOGI(TAG, "begin_transaction_: transaction mutex obtained"); - return ESP_OK; -} - -esp_err_t EInkDisplayHandler::end_transaction_(void) { - ESP_LOGI(TAG, "end_transaction_: releasing transaction mutex"); - if (xSemaphoreGive(spi_transaction_mutex_) != pdTRUE) { - ESP_LOGE(TAG, "end_transaction_: failed to release transaction mutex"); - return ESP_FAIL; - } - - ESP_LOGI(TAG, "end_transaction_: transaction mutex released"); - return ESP_OK; -} - -esp_err_t EInkDisplayHandler::wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard) { - // Validate transaction ID if provided - if (awaiting_transaction_id != 0 && awaiting_transaction_id != spi_transaction_id) { - // Invalid transaction ID - ESP_LOGE(TAG, "Invalid transaction ID 0x%08X while waiting, current transaction ID: 0x%08X", - awaiting_transaction_id, spi_transaction_id); - return ESP_ERR_INVALID_ARG; - } - SemaphoreGuard transaction_guard(spi_transaction_mutex_); - if (awaiting_transaction_id == 0) { - // wait for current transaction to complete - ESP_LOGV(TAG, "Waiting for current transaction 0x%08X to complete", - spi_transaction_id); - // take the mutex to ensure no transaction is active - if (!transaction_guard.take(timeout)) { - ESP_LOGE(TAG, "SPI transaction mutex timeout while waiting for transaction end"); - return ESP_ERR_TIMEOUT; - } - } - // awaited_transaction_id is valid and matches current transaction ID or 0 - out_transaction_guard = std::move(transaction_guard); - return ESP_OK; -} \ No newline at end of file diff --git a/main/display/eink_display_handler.h b/main/display/eink_display_handler.h index a0330ec..a115d8f 100644 --- a/main/display/eink_display_handler.h +++ b/main/display/eink_display_handler.h @@ -5,6 +5,7 @@ #include "common/semaphore_guard.h" #include #include +#include "epd_handler.h" // Refresh mode configuration #define PARTIAL_REFRESH_THRESHOLD 10 // Full refresh every N partial refreshes @@ -62,74 +63,37 @@ public: // Request a full refresh on next flush void request_full_refresh(void); - // Check if display is busy (refreshing) - bool is_busy(void) const; - void wait_for_idle(void) const; + bool is_busy() { + return epd_handler_.is_busy(); + } esp_lcd_touch_handle_t get_touch_handle() const { return tp_handle_; } -protected: - esp_err_t epd_write_cmd(const uint8_t cmd, uint32_t transaction_id); - esp_err_t epd_write_data(const uint8_t data, uint32_t transaction_id); - esp_err_t epd_write_cmd_with_data(const uint8_t cmd, std::vector& data, uint32_t transaction_id); - esp_err_t transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id); - private: esp_err_t init_display_pins_(void); - esp_err_t epd_init_(void); // full fast refresh init - esp_err_t epd_init_partial_(void); // partial refresh init (standalone) + esp_err_t epd_init_internal_(uint32_t transaction_id); // full fast refresh init esp_err_t epd_init_partial_internal_(uint32_t transaction_id); // partial refresh init (within existing transaction) esp_err_t init_touch_(void); - esp_err_t dangerous_epd_write_cmd_without_lock_(const uint8_t cmd); - esp_err_t dangerous_epd_write_data_without_lock_(const uint8_t data); // write to the internal draw buffer void write_to_buffer_(const uint8_t* src, const RefreshArea& area); // write the internal draw buffer to the display's old sram esp_err_t refresh_old_buffer_(uint32_t transaction_id); - esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id); - esp_err_t end_transaction_(void); - // given a transaction ID, wait for current transaction to complete. The transaction ID will determine if the wait is needed. - esp_err_t wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard); - - friend class TransactionGuard; + EPDHandler epd_handler_; uint32_t partial_refresh_count_ = 0; bool force_full_refresh_ = false; std::atomic is_deep_sleep_ { false }; - SemaphoreHandle_t spi_mutex_ = nullptr; - SemaphoreHandle_t spi_transaction_mutex_ = nullptr; SemaphoreHandle_t refresh_mutex_ = nullptr; - uint32_t spi_transaction_id = 0; // For tracking SPI transactions - spi_device_handle_t spi_ = nullptr; 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) uint8_t* draw_buffer_ = nullptr; + uint8_t* old_buffer_ = nullptr; RefreshArea refresh_area_ = { 0, 0, 0, 0 }; }; - -class TransactionGuard { -public: - TransactionGuard(EInkDisplayHandler& handler, TickType_t timeout = portMAX_DELAY) - : handler_(handler) { } - ~TransactionGuard() { if (transaction_id_) handler_.end_transaction_(); } - - esp_err_t begin(TickType_t timeout = portMAX_DELAY) { - esp_err_t err = handler_.begin_transaction_(timeout, transaction_id_); - return err; - } - uint32_t transaction_id() const { return transaction_id_; } - bool is_active() const { return transaction_id_ != 0; } -private: - // delete copy constructor and assignment operator - TransactionGuard(const TransactionGuard&) = delete; - TransactionGuard& operator=(const TransactionGuard&) = delete; - EInkDisplayHandler& handler_; - uint32_t transaction_id_ = 0; -}; diff --git a/main/display/epd_handler.cpp b/main/display/epd_handler.cpp new file mode 100644 index 0000000..ad91d45 --- /dev/null +++ b/main/display/epd_handler.cpp @@ -0,0 +1,287 @@ +#include "display/epd_handler.h" +#include "esp_log.h" +#include "display/constants.h" +#include "common/constants.h" +#include "esp_lcd_touch_gt911.h" +#include +#define TAG "EPDHandler" + +#define BUSY_ACTIVE_LEVEL 0 // BUSY pin is active low +#define BUSY_INACTIVE_LEVEL 1 +#define DMA_TRANSFER_CHUNK_SIZE 4096 // 4KB chunk size for DMA transfers + +EPDHandler::EPDHandler() { + spi_mutex_ = xSemaphoreCreateMutex(); + if (spi_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create SPI mutex"); + } + spi_transaction_mutex_ = xSemaphoreCreateMutex(); + if (spi_transaction_mutex_ == nullptr) { + ESP_LOGE(TAG, "Failed to create SPI transaction mutex"); + } +} + +EPDHandler::~EPDHandler() { + if (spi_mutex_ != nullptr) { + vSemaphoreDelete(spi_mutex_); + } + if (spi_transaction_mutex_ != nullptr) { + vSemaphoreDelete(spi_transaction_mutex_); + } + if (spi_ != nullptr) { + spi_bus_remove_device(spi_); + } +} + +esp_err_t EPDHandler::init() { + esp_err_t err; + + // Initialize SPI bus + spi_bus_config_t buscfg = {}; + buscfg.mosi_io_num = 11; // MOSI pin + buscfg.miso_io_num = -1; // No MISO for e-paper + buscfg.sclk_io_num = 12; // SCK pin + buscfg.quadwp_io_num = -1; + buscfg.quadhd_io_num = -1; + buscfg.max_transfer_sz = DMA_TRANSFER_CHUNK_SIZE; + + err = spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(err)); + return err; + } + + // Add SPI device + spi_device_interface_config_t devcfg = {}; + devcfg.clock_speed_hz = 10 * 1000 * 1000; // 10 MHz + devcfg.mode = 0; // SPI mode 0 + devcfg.spics_io_num = PIN_CS; + devcfg.queue_size = 7; // Queue size for non-blocking transactions + devcfg.pre_cb = nullptr; + + err = spi_bus_add_device(SPI2_HOST, &devcfg, &spi_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to add SPI device: %s", esp_err_to_name(err)); + return err; + } + + return ESP_OK; +} + +// Check if display is busy (refreshing) +bool EPDHandler::is_busy(void) const { + return gpio_get_level(PIN_BUSY) != BUSY_ACTIVE_LEVEL; // BUSY is active LOW +} +void EPDHandler::wait_for_idle(void) const { + ESP_LOGI(TAG, "Waiting for display ready (BUSY pin)..."); + int initial_level = gpio_get_level(PIN_BUSY); + ESP_LOGI(TAG, "Initial BUSY pin level: %d (0=BUSY, 1=FREE)", initial_level); + + // If already free, no need to wait + if (initial_level == BUSY_INACTIVE_LEVEL) { + ESP_LOGI(TAG, "Display already ready (BUSY pin = 1)"); + return; + } + while (gpio_get_level(PIN_BUSY) != BUSY_INACTIVE_LEVEL) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + ESP_LOGI(TAG, "Display is now ready (BUSY pin = 1)"); +} + +esp_err_t EPDHandler::epd_write_cmd(const uint8_t cmd, uint32_t transaction_id) { + ESP_LOGI(TAG, "epd_write_cmd: waiting to send 0x%02X", cmd); + + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s", + cmd, esp_err_to_name(err)); + return err; + } + + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for cmd 0x%02X", cmd); + return ESP_ERR_TIMEOUT; + } + err = dangerous_epd_write_cmd_without_lock_(cmd); + ESP_LOGI(TAG, "epd_write_cmd: 0x%02X done", cmd); + return err; +} + +esp_err_t EPDHandler::epd_write_data(const uint8_t data, uint32_t transaction_id) { + ESP_LOGI(TAG, "epd_write_data: waiting to send 0x%02X", data); + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data 0x%02X: %s", + data, esp_err_to_name(err)); + return err; + } + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for data 0x%02X", data); + return ESP_ERR_TIMEOUT; + } + err = dangerous_epd_write_data_without_lock_(data); + ESP_LOGI(TAG, "epd_write_data: 0x%02X done", data); + return err; +} + +esp_err_t EPDHandler::epd_write_cmd_with_data(const uint8_t cmd, std::vector& data, uint32_t transaction_id) { + const size_t data_len = data.size(); + ESP_LOGI(TAG, "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, data_len); + + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending cmd 0x%02X: %s, with data", + cmd, esp_err_to_name(err)); + return err; + } + + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for cmd with data 0x%02X", cmd); + return ESP_ERR_TIMEOUT; + } + err = dangerous_epd_write_cmd_without_lock_(cmd); + if (err != ESP_OK) { + return err; + }; + for (size_t i = 0; i < data_len; ++i) { + err = dangerous_epd_write_data_without_lock_(data[i]); + if (err != ESP_OK) { + return err; + } + } + ESP_LOGI(TAG, "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, data_len); + return ESP_OK; +} + + +esp_err_t EPDHandler::dangerous_epd_write_cmd_without_lock_(const uint8_t cmd) { + ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: sending 0x%02X", cmd); + gpio_set_level(PIN_DC, 0); // Command mode + spi_transaction_t t {}; + t.length = 8;t.tx_buffer = &cmd; + esp_err_t err = spi_device_polling_transmit(spi_, &t); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send data 0x%02X", cmd); + } else { + ESP_LOGI(TAG, "dangerous_epd_write_cmd_without_lock_: 0x%02X sent", cmd); + } + return err; +} + +esp_err_t EPDHandler::dangerous_epd_write_data_without_lock_(const uint8_t data) { + ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: sending 0x%02X", data); + gpio_set_level(PIN_DC, 1); // Data mode + spi_transaction_t t = { }; + t.length = 8; t.tx_buffer = &data; + esp_err_t err = spi_device_polling_transmit(spi_, &t); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to send data 0x%02X", data); + } else { + ESP_LOGI(TAG, "dangerous_epd_write_data_without_lock_: 0x%02X sent", data); + } + return err; +} + +esp_err_t EPDHandler::transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id) { + ESP_LOGI(TAG, "transfer_spi_data: waiting to send %zu bytes of data", length); + + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + esp_err_t err = + wait_for_transaction_end_(pdMS_TO_TICKS(5000), transaction_id, transaction_guard); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to wait for previous transaction end before sending data of %zu bytes: %s", + length, esp_err_to_name(err)); + return err; + } + SemaphoreGuard guard(spi_mutex_); + if (!guard.take(pdMS_TO_TICKS(5000))) { + ESP_LOGE(TAG, "SPI mutex timeout for data transfer of %zu bytes", length); + return ESP_ERR_TIMEOUT; + } + ESP_LOGI(TAG, "transfer_spi_data: starting to send %zu bytes of data", length); + + size_t offset = 0; + size_t remaining = length; + gpio_set_level(PIN_DC, 1); // Data mode + while (remaining > 0) { + size_t transfer_size = (remaining < DMA_TRANSFER_CHUNK_SIZE) ? remaining : DMA_TRANSFER_CHUNK_SIZE; + + spi_transaction_t t = {}; + t.length = transfer_size * 8; // Length in bits + t.tx_buffer = data + offset; + + 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)); + return ret; + } + + remaining -= transfer_size; + offset += transfer_size; + + // Yield every 16KB to prevent watchdog timeout + if (offset % (16 * 1024) == 0) { + ESP_LOGI(TAG, "New data progress: %zu/%zu bytes sent, yielding...", offset, length); + vTaskDelay(pdMS_TO_TICKS(1)); + } + } + + ESP_LOGI(TAG, "transfer_spi_data: completed sending %zu bytes of data", length); + return ESP_OK; +} + +esp_err_t EPDHandler::begin_transaction_(TickType_t timeout, uint32_t& out_id) { + ESP_LOGI(TAG, "begin_transaction_: waiting to obtain transaction mutex"); + if (xSemaphoreTake(spi_transaction_mutex_, timeout) != pdTRUE) { + ESP_LOGE(TAG, "begin_transaction_: transaction mutex timeout"); + return ESP_ERR_TIMEOUT; + } + + out_id = ++spi_transaction_id; + ESP_LOGI(TAG, "begin_transaction_: transaction mutex obtained"); + return ESP_OK; +} + +esp_err_t EPDHandler::end_transaction_(void) { + ESP_LOGI(TAG, "end_transaction_: releasing transaction mutex"); + if (xSemaphoreGive(spi_transaction_mutex_) != pdTRUE) { + ESP_LOGE(TAG, "end_transaction_: failed to release transaction mutex"); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "end_transaction_: transaction mutex released"); + return ESP_OK; +} + +esp_err_t EPDHandler::wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard) { + // Validate transaction ID if provided + if (awaiting_transaction_id != 0 && awaiting_transaction_id != spi_transaction_id) { + // Invalid transaction ID + ESP_LOGE(TAG, "Invalid transaction ID 0x%08X while waiting, current transaction ID: 0x%08X", + awaiting_transaction_id, spi_transaction_id); + return ESP_ERR_INVALID_ARG; + } + SemaphoreGuard transaction_guard(spi_transaction_mutex_); + if (awaiting_transaction_id == 0) { + // wait for current transaction to complete + ESP_LOGV(TAG, "Waiting for current transaction 0x%08X to complete", + spi_transaction_id); + // take the mutex to ensure no transaction is active + if (!transaction_guard.take(timeout)) { + ESP_LOGE(TAG, "SPI transaction mutex timeout while waiting for transaction end"); + return ESP_ERR_TIMEOUT; + } + } + // awaited_transaction_id is valid and matches current transaction ID or 0 + out_transaction_guard = std::move(transaction_guard); + return ESP_OK; +} \ No newline at end of file diff --git a/main/display/epd_handler.h b/main/display/epd_handler.h new file mode 100644 index 0000000..0a32e5e --- /dev/null +++ b/main/display/epd_handler.h @@ -0,0 +1,41 @@ +#pragma once +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "driver/spi_master.h" +#include "common/semaphore_guard.h" +#include +#include "display/transaction_guard.h" + +class EPDHandler : public WithTransaction { +public: + EPDHandler(); + ~EPDHandler(); + esp_err_t init(); + + esp_err_t epd_write_cmd(const uint8_t cmd, uint32_t transaction_id); + esp_err_t epd_write_data(const uint8_t data, uint32_t transaction_id); + esp_err_t epd_write_cmd_with_data(const uint8_t cmd, std::vector& data, uint32_t transaction_id); + esp_err_t transfer_spi_data(const uint8_t* data, const size_t& length, uint32_t transaction_id); + + bool is_busy(void) const; + void wait_for_idle(void) const; + +private: + + esp_err_t dangerous_epd_write_cmd_without_lock_(const uint8_t cmd); + esp_err_t dangerous_epd_write_data_without_lock_(const uint8_t data); + + esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id) override; + esp_err_t end_transaction_(void) override; + // given a transaction ID, wait for current transaction to complete. The transaction ID will determine if the wait is needed. + esp_err_t wait_for_transaction_end_(TickType_t timeout, uint32_t awaiting_transaction_id, SemaphoreGuard& out_transaction_guard); + + spi_device_handle_t spi_ = nullptr; + + SemaphoreHandle_t spi_mutex_ = nullptr; + SemaphoreHandle_t spi_transaction_mutex_ = nullptr; + + uint32_t spi_transaction_id = 0; // For tracking SPI transactions + + friend class TransactionGuard; +}; diff --git a/main/display/lvgl_handler.cpp b/main/display/lvgl_handler.cpp index 775c87a..2be8eef 100644 --- a/main/display/lvgl_handler.cpp +++ b/main/display/lvgl_handler.cpp @@ -103,6 +103,10 @@ void LVGLHandler::flush_cb_(lv_display_t* disp, const lv_area_t* area, uint8_t* int32_t area_w = lv_area_get_width(area); int32_t area_h = lv_area_get_height(area); if (area->x1 == 0 && area->y1 == 0 && area_w == DISPLAY_WIDTH && area_h == DISPLAY_HEIGHT) { + // revert the pixel data for e-ink (LVGL: 1=white, 0=black; E-Ink: 1=black, 0=white) + for (size_t i = 0; i < DISPLAY_BUFFER_SIZE; ++i) { + pixel_data[i] = ~pixel_data[i]; + } esp_err_t err = handler->display_handler_->full_write( pixel_data, true // white basemap diff --git a/main/display/transaction_guard.h b/main/display/transaction_guard.h new file mode 100644 index 0000000..d8a8581 --- /dev/null +++ b/main/display/transaction_guard.h @@ -0,0 +1,33 @@ +#pragma once +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include +#include + +class WithTransaction { +protected: + virtual esp_err_t end_transaction_() = 0; + virtual esp_err_t begin_transaction_(TickType_t timeout, uint32_t& out_id) = 0; + + friend class TransactionGuard; +}; + +class TransactionGuard { +public: + TransactionGuard(WithTransaction& handler, TickType_t timeout = portMAX_DELAY) + : handler_(handler) { } + ~TransactionGuard() { if (transaction_id_) handler_.end_transaction_(); } + + esp_err_t begin(TickType_t timeout = portMAX_DELAY) { + esp_err_t err = handler_.begin_transaction_(timeout, transaction_id_); + return err; + } + uint32_t transaction_id() const { return transaction_id_; } + bool is_active() const { return transaction_id_ != 0; } +private: + // delete copy constructor and assignment operator + TransactionGuard(const TransactionGuard&) = delete; + TransactionGuard& operator=(const TransactionGuard&) = delete; + WithTransaction& handler_; + uint32_t transaction_id_ = 0; +}; diff --git a/main/main.cpp b/main/main.cpp index 3852f7c..cf5435a 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -44,252 +44,6 @@ void init_queues( -void random_draw_rect( - LVGLHandler* lvgl_handler -) { - // Draw a random rectangle on the display using LVGL every 2 seconds until 100 rectangles have been drawn - static int rect_count = 0; - do { - - rect_count++; - LVGLHandler* handler = lvgl_handler; - if (handler == nullptr) { - return; - } - lvgl_port_lock(pdMS_TO_TICKS(5000)); - // Create a random rectangle - lv_obj_t* rect = lv_obj_create(lv_scr_act()); - int x = esp_random() % (DISPLAY_WIDTH - 50); - int y = esp_random() % (DISPLAY_HEIGHT - 50); - lv_obj_set_pos(rect, x, y); - int w = 20 + (esp_random() % 100); - int h = 20 + (esp_random() % 100); - lv_obj_set_size(rect, w, h); - // white or black fill for the rect - bool is_white = (esp_random() % 2 == 0); - lv_obj_set_style_bg_color(rect, is_white ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000), 0); - - lvgl_port_unlock(); - ESP_LOGI(TAG, "Drawn %s rectangle %d at (%d,%d) size (%d x %d)", - is_white ? "white" : "black", - rect_count, x, y, w, h); - // Schedule next rectangle draw - vTaskDelay(pdMS_TO_TICKS(2000)); - } while (rect_count < 100); -} - -void EInk_Checkerboard( - EInkDisplayHandler* display_handler -) { - struct CheckerboardTaskParams { - EInkDisplayHandler* display_handler; - }; - auto checkerboard_task_fn = [](void* pvParameters) { - CheckerboardTaskParams* params = static_cast(pvParameters); - if (params != nullptr && params->display_handler != nullptr) { - // Add this task to the watchdog timer - esp_err_t wdt_err = esp_task_wdt_add(NULL); - if (wdt_err != ESP_OK) { - ESP_LOGW(TAG, "Failed to add checkerboard task to watchdog: %s", esp_err_to_name(wdt_err)); - } - - EInkDisplayHandler* display_handler = params->display_handler; - const size_t DISPLAY_BUFFER_SIZE = DISPLAY_WIDTH * DISPLAY_HEIGHT / 8; - uint8_t* framebuffer = new uint8_t[DISPLAY_BUFFER_SIZE]; - if (framebuffer == nullptr) { - ESP_LOGE(TAG, "Failed to allocate framebuffer for checkerboard task"); - if (wdt_err == ESP_OK) { - esp_task_wdt_delete(NULL); - } - vTaskDelete(NULL); - return; - } - // Create checkerboard pattern - for (size_t y = 0; y < DISPLAY_HEIGHT; y++) { - for (size_t x = 0; x < DISPLAY_WIDTH; x++) { - size_t byte_index = (y * DISPLAY_WIDTH + x) / 8; - size_t bit_index = 7 - (x % 8); - bool is_white = ((x / 20) % 2) == ((y / 20) % 2); - if (is_white) { - framebuffer[byte_index] |= (1 << bit_index); // Set bit to 1 for white - } else { - framebuffer[byte_index] &= ~(1 << bit_index); // Clear bit to 0 for black - } - } - // Yield and reset watchdog periodically - const size_t YIELD_INTERVAL = 16; - if (y % YIELD_INTERVAL == 0) { - if (wdt_err == ESP_OK) { - esp_task_wdt_reset(); - } - vTaskDelay(1 / portTICK_PERIOD_MS); - // partial refresh to show progress - int32_t y_start = static_cast((y >= YIELD_INTERVAL - 1) ? (y - (YIELD_INTERVAL - 1)) : 0); - int32_t y_end = static_cast(y); - // get the partial framebuffer for this area - uint8_t* partial_framebuffer = &framebuffer[y_start * (DISPLAY_WIDTH / 8)]; - esp_err_t err = display_handler->partial_refresh(partial_framebuffer, RefreshArea { 0, y_start, DISPLAY_WIDTH - 1, y_end }); - if (err != ESP_OK) { - ESP_LOGE(TAG, "Partial refresh failed at y=%d: %s", y, esp_err_to_name(err)); - } - // wait for 4 seconds to prevent spamming the display - // vTaskDelay(2000 / portTICK_PERIOD_MS); - } - } - // Perform full write to display - // esp_err_t err = display_handler->full_write(framebuffer); - // if (err != ESP_OK) { - // ESP_LOGE(TAG, "Checkerboard full write failed: %s", esp_err_to_name(err)); - // } else { - // ESP_LOGI(TAG, "Checkerboard pattern displayed successfully."); - // } - delete[] framebuffer; - - // Remove task from watchdog before deletion - if (wdt_err == ESP_OK) { - esp_task_wdt_delete(NULL); - } - } else { - ESP_LOGE(TAG, "Invalid parameters for checkerboard task"); - } - vTaskDelete(NULL); - }; - CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); - checker_params->display_handler = display_handler; - BaseType_t res = xTaskCreate( - checkerboard_task_fn, - "checkerboard_task", - 8192, - static_cast(checker_params), - tskIDLE_PRIORITY + 1, - NULL - ); - - if (res != pdPASS) { - ESP_LOGE(TAG, "Failed to create checkerboard task"); - delete checker_params; - } -} - -void LVGL_Checkerboard( - LVGLHandler* lvgl_handler -) { - struct CheckerboardTaskParams { - LVGLHandler* lvgl_handler; - }; - auto checkerboard_task_fn = [](void* pvParameters) { - CheckerboardTaskParams* params = static_cast(pvParameters); - if (params == nullptr || params->lvgl_handler == nullptr) { - ESP_LOGE(TAG, "Invalid parameters for LVGL checkerboard task"); - delete params; - vTaskDelete(NULL); - return; - } - auto* handler = static_cast(params->lvgl_handler); - - // Add safety checks - if (!handler) { - ESP_LOGE("LVGL", "Handler is null!"); - delete params; - vTaskDelete(NULL); - return; - } - - ESP_LOGI("HEAP", "Free: %d", esp_get_free_heap_size()); - - // Wait for LVGL system to fully initialize - vTaskDelay(pdMS_TO_TICKS(200)); - - // Acquire LVGL lock with proper timeout - if (!lvgl_port_lock(pdMS_TO_TICKS(5000))) { - ESP_LOGE(TAG, "Failed to acquire LVGL lock for checkerboard"); - delete params; - vTaskDelete(NULL); - return; - } - - // Verify LVGL is properly initialized - if (lv_display_get_default() == nullptr) { - ESP_LOGE(TAG, "LVGL default display not available"); - lvgl_port_unlock(); - delete params; - vTaskDelete(NULL); - return; - } - - // Create LVGL objects for checkerboard - lv_obj_t* scr = lv_scr_act(); - if (scr == nullptr) { - ESP_LOGE(TAG, "Failed to get active LVGL screen"); - lvgl_port_unlock(); - delete params; - vTaskDelete(NULL); - return; - } - lv_obj_t* checkerboard = lv_obj_create(scr); - if (checkerboard == nullptr) { - ESP_LOGE(TAG, "Failed to create LVGL checkerboard object"); - lvgl_port_unlock(); - delete params; - vTaskDelete(NULL); - return; - } - lv_obj_set_size(checkerboard, DISPLAY_WIDTH, DISPLAY_HEIGHT); - // remove border and padding - lv_obj_set_style_pad_all(checkerboard, 0, 0); - lv_obj_set_style_border_width(checkerboard, 0, 0); - const int CELL_SIZE = 40; - lvgl_port_unlock(); - // Create checkerboard pattern using LVGL - for (int y = 0; y < DISPLAY_HEIGHT; y += CELL_SIZE) { - lvgl_port_lock(pdMS_TO_TICKS(1000)); - for (int x = 0; x < DISPLAY_WIDTH; x += CELL_SIZE) { - lv_color_t color = (((x / CELL_SIZE) % 2) == ((y / CELL_SIZE) % 2)) ? lv_color_hex(0xFFFFFF) : lv_color_hex(0x000000); - lv_obj_t* cell = lv_obj_create(checkerboard); - if (cell == nullptr) { - ESP_LOGE(TAG, "Failed to create LVGL checkerboard cell"); - lvgl_port_unlock(); - continue; - } - lv_obj_set_size(cell, CELL_SIZE, CELL_SIZE); - lv_obj_set_style_bg_color(cell, color, 0); - lv_obj_set_pos(cell, x, y); - // remove border and padding - lv_obj_set_style_pad_all(cell, 0, 0); - lv_obj_set_style_border_width(cell, 0, 0); - lv_obj_t* label = lv_label_create(cell); - if (label != nullptr) { - lv_label_set_text_fmt(label, "(%d,%d)", x, y); - lv_obj_center(label); - } - } - lvgl_port_unlock(); - // Yield to allow LVGL to process rendering - vTaskDelay(500 / portTICK_PERIOD_MS); - } - - - - ESP_LOGI(TAG, "LVGL Checkerboard pattern displayed successfully."); - delete params; - vTaskDelete(NULL); - }; - CheckerboardTaskParams* checker_params = new CheckerboardTaskParams(); - checker_params->lvgl_handler = lvgl_handler; - BaseType_t res = xTaskCreate( - checkerboard_task_fn, - "lvgl_checkerboard_task0", - 8192, - static_cast(checker_params), - tskIDLE_PRIORITY + 1, - NULL - ); - if (res != pdPASS) { - ESP_LOGE(TAG, "Failed to create LVGL checkerboard task"); - delete checker_params; - } -} - void app_main(void) { display_chip_info(); @@ -306,14 +60,14 @@ void app_main(void) { ESP_LOGI(TAG, "Queues initialized.\n"); // - KVStorageHandler* kv_storage_handler = new NVSStorageHandler( - DEFAULT_STORAGE_NAMESPACE - ); + // KVStorageHandler* kv_storage_handler = new NVSStorageHandler( + // DEFAULT_STORAGE_NAMESPACE + // ); - auto wifi_handler = std::make_unique( - std::unique_ptr(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)) - ); - NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler)); + // auto wifi_handler = std::make_unique( + // std::unique_ptr(new NVSStorageHandler(WIFI_CREDENTIALS_STORAGE_NAMESPACE)) + // ); + // NetworkHandler* network_handler = new NetworkHandler(std::move(wifi_handler)); EInkDisplayHandler* display_handler = new EInkDisplayHandler(); // Initialize display and touch // display_handler->init_devices(system_event_group); @@ -330,8 +84,8 @@ void app_main(void) { } // - kv_storage_handler->init(system_event_group); - network_handler->init(system_event_group); + // kv_storage_handler->init(system_event_group); + // network_handler->init(system_event_group); // ESP_LOGI(TAG, "Waiting for system to be ready...\n"); @@ -349,36 +103,60 @@ void app_main(void) { // Allow LVGL system to stabilize before creating objects vTaskDelay(pdMS_TO_TICKS(100)); - // Show checkerboard pattern on display for testing - // EInk_Checkerboard(display_handler); - // LVGL_Checkerboard(&lvgl_handler); + // Create main screen and button for random rectangle demo + lv_obj_t* scr = lv_scr_act(); - // Register apps with AppRegistry by creating their descriptors - // Each descriptor will create and register the app instance - // DemoAppDescriptor* demo_descriptor = new DemoAppDescriptor(); - // ShutdownAppDescriptor* shutdown_descriptor = new ShutdownAppDescriptor(); - DiscordAppDescriptor::instance(); // Use singleton pattern for Discord app - // MtrAppDescriptor* mtr_descriptor = new MtrAppDescriptor(); + // Create a button + lv_obj_t* btn = lv_btn_create(scr); + lv_obj_set_size(btn, 200, 60); + lv_obj_align(btn, LV_ALIGN_TOP_MID, 0, 20); + lv_obj_set_style_border_width(btn, 2, 0); + lv_obj_set_style_border_color(btn, lv_color_make(0, 0, 0), 0); - // Pass network handler to MtrApp so it can fetch arrival data - // MtrApp* mtr_app = dynamic_cast(mtr_descriptor->get_app_instance()); - // if (mtr_app) { - // mtr_app->set_network_handler(network_handler); - // } + // Add label to button + lv_obj_t* label = lv_label_create(btn); + lv_label_set_text(label, "Create Random Rect"); + lv_obj_center(label); + lv_obj_set_style_text_color(label, lv_color_make(0, 0, 0), 0); - ESP_LOGI(TAG, "Apps registered with AppRegistry\n"); + // Event handler for button - creates random rectangles + auto btn_event_cb = [](lv_event_t* e) { + lv_obj_t* scr = lv_scr_act(); - // Initialize UI Handler (will render app icons from registry) - UIHandler ui_handler; - if (ui_handler.init() != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize UI handler"); - vTaskDelay(5000 / portTICK_PERIOD_MS); - return esp_restart(); - } - ESP_LOGI(TAG, "UI handler initialized successfully\n"); - ESP_LOGI(TAG, "Main screen displayed with app icons. Tap an icon to launch an app.\n"); + // Create a random rectangle + lv_obj_t* rect = lv_obj_create(scr); + // Random size (30-100 pixels) + lv_coord_t width = 30 + (esp_random() % 70); + lv_coord_t height = 30 + (esp_random() % 70); + lv_obj_set_size(rect, width, height); + // Random position (avoid top 100px where button is) + lv_coord_t x = esp_random() % (LV_HOR_RES - width); + lv_coord_t y = 100 + (esp_random() % (LV_VER_RES - 100 - height)); + lv_obj_set_pos(rect, x, y); + + lv_obj_set_style_bg_color(rect, lv_color_make(0, 0, 0), 0); + lv_obj_set_style_bg_opa(rect, LV_OPA_COVER, 0); + + // Make rectangle clickable + lv_obj_add_flag(rect, LV_OBJ_FLAG_CLICKABLE); + + // Event handler to delete rectangle when clicked + auto rect_event_cb = [](lv_event_t* e) { + lv_obj_t* rect = static_cast(lv_event_get_target(e)); + lv_obj_del(rect); + ESP_LOGI(TAG, "Rectangle deleted"); + }; + + lv_obj_add_event_cb(rect, rect_event_cb, LV_EVENT_CLICKED, NULL); + + ESP_LOGI(TAG, "Created rectangle at (%d, %d) with size %dx%d", x, y, width, height); + }; + + lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL); + + ESP_LOGI(TAG, "Random rectangle demo initialized. Tap button to create rectangles.\n"); // wait for shutdown signal ESP_LOGI(TAG, "Waiting for shutdown signal...\n");