diff --git a/main/display/constants.h b/main/display/constants.h new file mode 100644 index 0000000..da77ac1 --- /dev/null +++ b/main/display/constants.h @@ -0,0 +1,11 @@ +#pragma once +#include "driver/spi_master.h" +#include "driver/gpio.h" + +#define PIN_TOUCH_IRQ GPIO_NUM_4 +#define PIN_TOUCH_SDA GPIO_NUM_5 +#define PIN_TOUCH_SCL GPIO_NUM_6 +#define PIN_BUSY GPIO_NUM_7 +#define PIN_RST GPIO_NUM_8 +#define PIN_DC GPIO_NUM_9 +#define PIN_CS GPIO_NUM_10 diff --git a/main/display/display.cpp b/main/display/display.cpp index 738eefe..961e21b 100644 --- a/main/display/display.cpp +++ b/main/display/display.cpp @@ -1,62 +1,157 @@ -#include "display.h" +#include "display/display.h" #include "common/constants.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" -// TODO: implement actual display functionality +#include "esp_log.h" -DisplayHandler::DisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex) { - (void)touch_queue; - (void)lvgl_mutex; -} - -DisplayHandler::~DisplayHandler() { } - -EInkDisplayHandler::EInkDisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex) - : DisplayHandler(touch_queue, lvgl_mutex) { } - -EInkDisplayHandler::~EInkDisplayHandler() { } - -void EInkDisplayHandler::init(EventGroupHandle_t system_event_group) { - if (system_event_group != NULL) { - xEventGroupSetBits(system_event_group, DISPLAY_READY_BIT); +DisplayHandler::~DisplayHandler() { + if (_spi_mutex != nullptr) { + vSemaphoreDelete(_spi_mutex); + } + if (_spi != nullptr) { + spi_bus_remove_device(_spi); + } + if (_tp_handle != nullptr) { + esp_lcd_touch_del(_tp_handle); + } + if (_tp_io_handle != nullptr) { + esp_lcd_panel_io_del(_tp_io_handle); } } -void EInkDisplayHandler::start_event_loop() { - // Minimal background task to represent display processing - xTaskCreate( - // use the static adapter and pass `this` as the task parameter - EInkDisplayHandler::task_adapter, - "display_task", - 2048, - this, - tskIDLE_PRIORITY + 1, - nullptr - ); + +void DisplayHandler::init_devices(bool set_display_ready /*= true*/) { + ESP_LOGI("DisplayHandler", "Initializing display and touch..."); + _epd_init(); + _touch_init(); + ESP_LOGI("DisplayHandler", "Display and touch initialized."); + if (set_display_ready) { + ESP_LOGI("DisplayHandler", "Setting display ready bit."); + xEventGroupSetBits(_system_event_group, DISPLAY_READY_BIT | TOUCH_CALIBRATED_BIT); + } } -// static -void EInkDisplayHandler::task_adapter(void* arg) { - EInkDisplayHandler* self = static_cast(arg); - if (self) { - self->run_event_loop(); + +void DisplayHandler::epd_write_cmd(uint8_t cmd) { + ESP_LOGI("DisplayHandler", "epd_write_cmd: waiting to send 0x%02X", cmd); + xSemaphoreTake(_spi_mutex, portMAX_DELAY); + _dangerous_epd_write_cmd_without_lock(cmd); + xSemaphoreGive(_spi_mutex); + ESP_LOGI("DisplayHandler", "epd_write_cmd: 0x%02X done", cmd); +} + +void DisplayHandler::epd_write_data(uint8_t data) { + ESP_LOGI("DisplayHandler", "epd_write_data: waiting to send 0x%02X", data); + xSemaphoreTake(_spi_mutex, portMAX_DELAY); + _dangerous_epd_write_data_without_lock(data); + xSemaphoreGive(_spi_mutex); + ESP_LOGI("DisplayHandler", "epd_write_data: 0x%02X done", data); +} + +void DisplayHandler::epd_write_cmd_with_data(uint8_t cmd, const uint8_t* data, size_t data_len) { + ESP_LOGI("DisplayHandler", "epd_write_cmd_with_data: waiting to send cmd 0x%02X with %u bytes of data", cmd, (unsigned)data_len); + xSemaphoreTake(_spi_mutex, portMAX_DELAY); + _dangerous_epd_write_cmd_without_lock(cmd); + for (size_t i = 0; i < data_len; ++i) { + _dangerous_epd_write_data_without_lock(data[i]); + } + xSemaphoreGive(_spi_mutex); + ESP_LOGI("DisplayHandler", "epd_write_cmd_with_data: cmd 0x%02X with %u bytes of data done", cmd, (unsigned)data_len); +} + +// +// Private methods +// + +void DisplayHandler::_dangerous_epd_write_cmd_without_lock(uint8_t cmd) { + ESP_LOGI("DisplayHandler", "_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("DisplayHandler", "Failed to send data 0x%02X", cmd); } else { - printf("EInkDisplayHandler::task_adapter received null pointer\n"); - } - // If run_event_loop ever returns, delete the task. - vTaskDelete(NULL); -} - -void EInkDisplayHandler::run_event_loop() { - for (;;) { - vTaskDelay(pdMS_TO_TICKS(1000)); + ESP_LOGI("DisplayHandler", "_dangerous_epd_write_cmd_without_lock: 0x%02X sent", cmd); } } -shutdown_display_handlerFunc EInkDisplayHandler::get_shutdown_display_handler() { - return nullptr; +void DisplayHandler::_dangerous_epd_write_data_without_lock(uint8_t data) { + ESP_LOGI("DisplayHandler", "_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("DisplayHandler", "Failed to send data 0x%02X", data); + } else { + ESP_LOGI("DisplayHandler", "_dangerous_epd_write_data_without_lock: 0x%02X sent", data); + } } -restart_display_handlerFunc EInkDisplayHandler::get_restart_display_handler() { - return nullptr; +// required to be called by inheriting class after SPI device is created +void DisplayHandler::_epd_init(void) { + ESP_LOGI("DisplayHandler", "Initializing EPD..."); + // 1. Hardware Reset + gpio_set_level(PIN_RST, 0); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(PIN_RST, 1); + vTaskDelay(pdMS_TO_TICKS(10)); + + // 2. Initialization Sequence + const uint8_t panel_setting_data[] = { 0x1F }; + epd_write_cmd_with_data(0x00, panel_setting_data, 1); // Panel Setting + vTaskDelay(pdMS_TO_TICKS(10)); + const uint8_t vcom_data[] = { 0x10, 0x07 }; + epd_write_cmd_with_data(0x50, vcom_data, 2); // VCOM + vTaskDelay(pdMS_TO_TICKS(10)); + epd_write_cmd(0x04); // Power ON + vTaskDelay(pdMS_TO_TICKS(100)); // Wait for power on + + // Check BUSY pin + ESP_LOGI("DisplayHandler", "Waiting for EPD to be ready..."); + while (gpio_get_level(PIN_BUSY) == 1) { + vTaskDelay(pdMS_TO_TICKS(10)); + } + ESP_LOGI("DisplayHandler", "EPD is ready."); + const uint8_t booster_data[] = { 0x27, 0x27 }; + epd_write_cmd_with_data(0x06, booster_data, 2); // Booster Soft Start + vTaskDelay(pdMS_TO_TICKS(10)); +} + +void DisplayHandler::_touch_init(void) { + ESP_LOGI("DisplayHandler", "Initializing touch..."); + // 1. Initialize I2C Bus + i2c_config_t conf = {}; + conf.mode = I2C_MODE_MASTER; + conf.sda_io_num = PIN_TOUCH_SDA; + conf.scl_io_num = PIN_TOUCH_SCL; + conf.sda_pullup_en = GPIO_PULLUP_ENABLE; + conf.scl_pullup_en = GPIO_PULLUP_ENABLE; + conf.master.clk_speed = 400000; + + i2c_param_config(I2C_NUM_0, &conf); + i2c_driver_install(I2C_NUM_0, I2C_MODE_MASTER, 0, 0, 0); + ESP_LOGI("DisplayHandler", "I2C driver installed"); + + // 2. Initialize GT911 + ESP_LOGI("DisplayHandler", "Initializing GT911 touch controller..."); + esp_lcd_panel_io_i2c_config_t tp_io_config = {}; + // temporarily disable -Wmissing-field-initializers, as ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG macro does not set all fields +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + esp_lcd_panel_io_i2c_config_t default_tp_io_config = ESP_LCD_TOUCH_IO_I2C_GT911_CONFIG(); +#pragma GCC diagnostic pop + tp_io_config.dev_addr = default_tp_io_config.dev_addr; + tp_io_config.control_phase_bytes = default_tp_io_config.control_phase_bytes; + tp_io_config.dc_bit_offset = default_tp_io_config.dc_bit_offset; + tp_io_config.lcd_cmd_bits = default_tp_io_config.lcd_cmd_bits; + tp_io_config.flags = default_tp_io_config.flags; + esp_lcd_new_panel_io_i2c(I2C_NUM_0, &tp_io_config, &_tp_io_handle); + + esp_lcd_touch_config_t tp_cfg = {}; + tp_cfg.x_max = 800; + tp_cfg.y_max = 480; + tp_cfg.rst_gpio_num = PIN_RST; + tp_cfg.int_gpio_num = PIN_TOUCH_IRQ; + + esp_lcd_touch_new_i2c_gt911(_tp_io_handle, &tp_cfg, &_tp_handle); + ESP_LOGI("DisplayHandler", "GT911 touch controller initialized"); } diff --git a/main/display/display.h b/main/display/display.h index b31efe7..729a6ee 100644 --- a/main/display/display.h +++ b/main/display/display.h @@ -1,48 +1,38 @@ #pragma once - -#include -#include +#include "driver/spi_master.h" +#include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" -#include "esp_system.h" - -typedef void (*shutdown_display_handlerFunc)(void); -typedef void (*restart_display_handlerFunc)(void); +#include "esp_lcd_touch_gt911.h" +#include "display/constants.h" +#include class DisplayHandler { public: - DisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex); - // the system_event_group is used to set display-ready bit - virtual void init(EventGroupHandle_t system_event_group) = 0; - virtual void start_event_loop() = 0; - // get a handler to perform display shutdown cleanup, this is called after event loop ends and DisplayHandler is deleted - virtual shutdown_display_handlerFunc get_shutdown_display_handler() = 0; - virtual restart_display_handlerFunc get_restart_display_handler() = 0; - virtual ~DisplayHandler() = 0; + DisplayHandler( + EventGroupHandle_t system_event_group + ) : _system_event_group(system_event_group) { } + ~DisplayHandler(); + + // required to be called by inheriting class after SPI device is created + // set set_display_ready to false if further initialization is needed before marking display ready + void init_devices(bool set_display_ready = true); + + void epd_write_cmd(uint8_t cmd); + void epd_write_data(uint8_t data); + + void epd_write_cmd_with_data(uint8_t cmd, const uint8_t* data, size_t data_len); private: - DisplayHandler(const DisplayHandler&) = delete; - DisplayHandler& operator=(const DisplayHandler&) = delete; -}; - -class EInkDisplayHandler : public DisplayHandler { -public: - EInkDisplayHandler(QueueHandle_t touch_queue, SemaphoreHandle_t lvgl_mutex); - void init(EventGroupHandle_t system_event_group) override; - void start_event_loop() override; - shutdown_display_handlerFunc get_shutdown_display_handler() override; - restart_display_handlerFunc get_restart_display_handler() override; - ~EInkDisplayHandler() override; - -private: - // Task adapter used for FreeRTOS task creation. It forwards to the - // instance `run_event_loop()` method using the `this` pointer passed - // as the task parameter. - static void task_adapter(void* arg); - - // Instance method that implements the display task loop. - void run_event_loop(); - // prevent copying - EInkDisplayHandler(const EInkDisplayHandler&) = delete; - EInkDisplayHandler& operator=(const EInkDisplayHandler&) = delete; + SemaphoreHandle_t _spi_mutex = xSemaphoreCreateMutex(); + spi_device_handle_t _spi = nullptr; + EventGroupHandle_t _system_event_group = nullptr; + esp_lcd_panel_io_handle_t _tp_io_handle = nullptr; + esp_lcd_touch_handle_t _tp_handle = nullptr; + + void _dangerous_epd_write_cmd_without_lock(uint8_t cmd); + void _dangerous_epd_write_data_without_lock(uint8_t data); + + void _epd_init(void); + void _touch_init(void); };