#pragma once #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "esp_lcd_touch_gt911.h" #include "common/semaphore_guard.h" #include // Refresh mode configuration #define PARTIAL_REFRESH_THRESHOLD 10 // Full refresh every N partial refreshes #define DISPLAY_WIDTH 800 #define DISPLAY_HEIGHT 480 // forward declarations class EInkDisplayHandler; struct RefreshArea { public: RefreshArea(int32_t x_start, int32_t y_start, int32_t x_end, int32_t y_end) : x1(x_start), y1(y_start), x2(x_end), y2(y_end) { } int32_t x1; int32_t y1; int32_t x2; int32_t y2; }; class EInkDisplayHandler { public: EInkDisplayHandler(); virtual ~EInkDisplayHandler(); esp_err_t init_devices(EventGroupHandle_t system_event_group = nullptr); esp_err_t refresh_display(void); esp_err_t full_write(const uint8_t* framebuffer); esp_err_t partial_refresh(const uint8_t* framebuffer, const RefreshArea& area); esp_err_t clear_display(void); // 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; 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); 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); 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; uint32_t partial_refresh_count_ = 0; bool force_full_refresh_ = 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; }; 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; };