From adea6678a560222d3c1da48ee7b2b8f9c86e62eb Mon Sep 17 00:00:00 2001 From: NellowTCS Date: Wed, 29 Oct 2025 13:12:46 -0600 Subject: [PATCH] New board: T-Display S3 (no touch) (#398) --- .github/workflows/build-firmware.yml | 1 + Boards/lilygo-tdisplay-s3/CMakeLists.txt | 7 + .../Source/Configuration.cpp | 40 ++ Boards/lilygo-tdisplay-s3/Source/Init.cpp | 47 +++ .../Source/devices/Display.cpp | 30 ++ .../Source/devices/Display.h | 26 ++ .../Source/devices/Power.cpp | 12 + .../lilygo-tdisplay-s3/Source/devices/Power.h | 9 + Drivers/ST7789-i8080/CMakeLists.txt | 5 + Drivers/ST7789-i8080/README.md | 3 + .../Source/St7789i8080Display.cpp | 345 ++++++++++++++++++ .../ST7789-i8080/Source/St7789i8080Display.h | 139 +++++++ sdkconfig.board.lilygo-tdisplay-s3 | 60 +++ 13 files changed, 724 insertions(+) create mode 100644 Boards/lilygo-tdisplay-s3/CMakeLists.txt create mode 100644 Boards/lilygo-tdisplay-s3/Source/Configuration.cpp create mode 100644 Boards/lilygo-tdisplay-s3/Source/Init.cpp create mode 100644 Boards/lilygo-tdisplay-s3/Source/devices/Display.cpp create mode 100644 Boards/lilygo-tdisplay-s3/Source/devices/Display.h create mode 100644 Boards/lilygo-tdisplay-s3/Source/devices/Power.cpp create mode 100644 Boards/lilygo-tdisplay-s3/Source/devices/Power.h create mode 100644 Drivers/ST7789-i8080/CMakeLists.txt create mode 100644 Drivers/ST7789-i8080/README.md create mode 100644 Drivers/ST7789-i8080/Source/St7789i8080Display.cpp create mode 100644 Drivers/ST7789-i8080/Source/St7789i8080Display.h create mode 100644 sdkconfig.board.lilygo-tdisplay-s3 diff --git a/.github/workflows/build-firmware.yml b/.github/workflows/build-firmware.yml index c859fd14..4eb1640f 100644 --- a/.github/workflows/build-firmware.yml +++ b/.github/workflows/build-firmware.yml @@ -31,6 +31,7 @@ jobs: { id: elecrow-crowpanel-basic-50, arch: esp32s3 }, { id: lilygo-tdeck, arch: esp32s3 }, { id: lilygo-tdongle-s3, arch: esp32s3 }, + { id: lilygo-tdisplay-s3, arch: esp32s3 }, { id: lilygo-tlora-pager, arch: esp32s3 }, { id: m5stack-cardputer, arch: esp32s3 }, { id: m5stack-cardputer-adv, arch: esp32s3 }, diff --git a/Boards/lilygo-tdisplay-s3/CMakeLists.txt b/Boards/lilygo-tdisplay-s3/CMakeLists.txt new file mode 100644 index 00000000..3029aa07 --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/CMakeLists.txt @@ -0,0 +1,7 @@ +file(GLOB_RECURSE SOURCE_FILES Source/*.c*) + +idf_component_register( + SRCS ${SOURCE_FILES} + INCLUDE_DIRS "Source" + REQUIRES Tactility ButtonControl PwmBacklight EstimatedPower ST7789-i8080 +) diff --git a/Boards/lilygo-tdisplay-s3/Source/Configuration.cpp b/Boards/lilygo-tdisplay-s3/Source/Configuration.cpp new file mode 100644 index 00000000..64e76a15 --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/Source/Configuration.cpp @@ -0,0 +1,40 @@ +#include "devices/Display.h" +#include "devices/Power.h" + +#include +#include +#include + +bool initBoot(); + +using namespace tt::hal; + +static std::vector> createDevices() { + return { + createPower(), + createDisplay(), + ButtonControl::createTwoButtonControl(0, 14), + }; +} + +extern const Configuration hardwareConfiguration = { + .initBoot = initBoot, + .createDevices = createDevices, + .i2c = {}, + .spi { + spi::Configuration { + .device = SPI2_HOST, + .dma = SPI_DMA_CH_AUTO, + .config = { + .mosi_io_num = GPIO_NUM_7, + .miso_io_num = GPIO_NUM_NC, + .sclk_io_num = GPIO_NUM_6, + .max_transfer_sz = DISPLAY_HORIZONTAL_RESOLUTION * DISPLAY_VERTICAL_RESOLUTION * 2, + .flags = 0 + }, + .initMode = spi::InitMode::ByTactility, + .isMutable = false, + .lock = tt::lvgl::getSyncLock() + } + } +}; diff --git a/Boards/lilygo-tdisplay-s3/Source/Init.cpp b/Boards/lilygo-tdisplay-s3/Source/Init.cpp new file mode 100644 index 00000000..4e25f1c0 --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/Source/Init.cpp @@ -0,0 +1,47 @@ +#include "devices/Power.h" +#include "devices/Display.h" + +#include "PwmBacklight.h" +#include "Tactility/kernel/SystemEvents.h" +#include +#include + +#define TAG "tdisplay-s3" + +// Power on + + +static bool powerOn() { + gpio_config_t power_signal_config = { + .pin_bit_mask = BIT64(TDISPLAY_S3_POWERON_GPIO), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + + if (gpio_config(&power_signal_config) != ESP_OK) { + return false; + } + + if (gpio_set_level(TDISPLAY_S3_POWERON_GPIO, 1) != ESP_OK) { + return false; + } + + return true; +} + +bool initBoot() { + ESP_LOGI(TAG, "Powering on the board..."); + if (!powerOn()) { + ESP_LOGE(TAG, "Failed to power on the board."); + return false; + } + + if (!driver::pwmbacklight::init(DISPLAY_BL, 30000)) { + ESP_LOGE(TAG, "Failed to initialize backlight."); + return false; + } + + return true; +} \ No newline at end of file diff --git a/Boards/lilygo-tdisplay-s3/Source/devices/Display.cpp b/Boards/lilygo-tdisplay-s3/Source/devices/Display.cpp new file mode 100644 index 00000000..eeb902e6 --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/Source/devices/Display.cpp @@ -0,0 +1,30 @@ +#include "Display.h" + +#include "St7789i8080Display.h" +#include "PwmBacklight.h" + +std::shared_ptr createDisplay() { + // Create configuration + auto config = St7789i8080Display::Configuration( + DISPLAY_CS, // CS + DISPLAY_DC, // DC + DISPLAY_WR, // WR + DISPLAY_RD, // RD + { DISPLAY_I80_D0, DISPLAY_I80_D1, DISPLAY_I80_D2, DISPLAY_I80_D3, + DISPLAY_I80_D4, DISPLAY_I80_D5, DISPLAY_I80_D6, DISPLAY_I80_D7 }, // D0..D7 + DISPLAY_RST, // RST + DISPLAY_BL // BL + ); + + // Set resolution explicitly + config.horizontalResolution = DISPLAY_HORIZONTAL_RESOLUTION; + config.verticalResolution = DISPLAY_VERTICAL_RESOLUTION; + config.backlightDutyFunction = driver::pwmbacklight::setBacklightDuty; + + // Adjust other settings as needed + config.gapX = 35; // ST7789 has a 35 pixel gap on X axis + config.pixelClockFrequency = 10 * 1000 * 1000; // 10MHz for better stability + + auto display = std::make_shared(config); + return display; +} diff --git a/Boards/lilygo-tdisplay-s3/Source/devices/Display.h b/Boards/lilygo-tdisplay-s3/Source/devices/Display.h new file mode 100644 index 00000000..4dbf9a6c --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/Source/devices/Display.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +class St7789i8080Display; + +constexpr auto DISPLAY_CS = GPIO_NUM_6; +constexpr auto DISPLAY_DC = GPIO_NUM_7; +constexpr auto DISPLAY_WR = GPIO_NUM_8; +constexpr auto DISPLAY_RD = GPIO_NUM_9; +constexpr auto DISPLAY_RST = GPIO_NUM_5; +constexpr auto DISPLAY_BL = GPIO_NUM_38; +constexpr auto DISPLAY_I80_D0 = GPIO_NUM_39; +constexpr auto DISPLAY_I80_D1 = GPIO_NUM_40; +constexpr auto DISPLAY_I80_D2 = GPIO_NUM_41; +constexpr auto DISPLAY_I80_D3 = GPIO_NUM_42; +constexpr auto DISPLAY_I80_D4 = GPIO_NUM_45; +constexpr auto DISPLAY_I80_D5 = GPIO_NUM_46; +constexpr auto DISPLAY_I80_D6 = GPIO_NUM_47; +constexpr auto DISPLAY_I80_D7 = GPIO_NUM_48; +constexpr auto DISPLAY_HORIZONTAL_RESOLUTION = 170; +constexpr auto DISPLAY_VERTICAL_RESOLUTION = 320; + +// Factory function for registration +std::shared_ptr createDisplay(); diff --git a/Boards/lilygo-tdisplay-s3/Source/devices/Power.cpp b/Boards/lilygo-tdisplay-s3/Source/devices/Power.cpp new file mode 100644 index 00000000..febacad1 --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/Source/devices/Power.cpp @@ -0,0 +1,12 @@ +#include "Power.h" + +#include +#include + +std::shared_ptr createPower() { + ChargeFromAdcVoltage::Configuration configuration; + // 2.0 ratio, but +.11 added as display voltage sag compensation. + configuration.adcMultiplier = 2.11; + + return std::make_shared(configuration); +} \ No newline at end of file diff --git a/Boards/lilygo-tdisplay-s3/Source/devices/Power.h b/Boards/lilygo-tdisplay-s3/Source/devices/Power.h new file mode 100644 index 00000000..f2e39246 --- /dev/null +++ b/Boards/lilygo-tdisplay-s3/Source/devices/Power.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include +#include + +constexpr auto TDISPLAY_S3_POWERON_GPIO = GPIO_NUM_15; + +std::shared_ptr createPower(); diff --git a/Drivers/ST7789-i8080/CMakeLists.txt b/Drivers/ST7789-i8080/CMakeLists.txt new file mode 100644 index 00000000..7cd677c7 --- /dev/null +++ b/Drivers/ST7789-i8080/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "Source" + INCLUDE_DIRS "Source" + REQUIRES Tactility driver EspLcdCompat +) diff --git a/Drivers/ST7789-i8080/README.md b/Drivers/ST7789-i8080/README.md new file mode 100644 index 00000000..753ffb45 --- /dev/null +++ b/Drivers/ST7789-i8080/README.md @@ -0,0 +1,3 @@ +# ST7789 + +A basic ESP32 LVGL driver for ST7789 parallel i8080 displays. diff --git a/Drivers/ST7789-i8080/Source/St7789i8080Display.cpp b/Drivers/ST7789-i8080/Source/St7789i8080Display.cpp new file mode 100644 index 00000000..d345b9a0 --- /dev/null +++ b/Drivers/ST7789-i8080/Source/St7789i8080Display.cpp @@ -0,0 +1,345 @@ +#include "St7789i8080Display.h" +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr auto TAG = "St7789i8080Display"; +static St7789i8080Display* g_display_instance = nullptr; + +// ST7789 initialization commands +typedef struct { + uint8_t cmd; + uint8_t data[14]; + uint8_t len; +} lcd_init_cmd_t; + +static const lcd_init_cmd_t st7789_init_cmds[] = { + {0x11, {0}, 0 | 0x80}, + {0x36, {0x08}, 1}, + {0x3A, {0X05}, 1}, + {0x20, {0}, 0}, + {0xB2, {0X0B, 0X0B, 0X00, 0X33, 0X33}, 5}, + {0xB7, {0X75}, 1}, + {0xBB, {0X28}, 1}, + {0xC0, {0X2C}, 1}, + {0xC2, {0X01}, 1}, + {0xC3, {0X1F}, 1}, + {0xC6, {0X13}, 1}, + {0xD0, {0XA7}, 1}, + {0xD0, {0XA4, 0XA1}, 2}, + {0xD6, {0XA1}, 1}, + {0xE0, {0XF0, 0X05, 0X0A, 0X06, 0X06, 0X03, 0X2B, 0X32, 0X43, 0X36, 0X11, 0X10, 0X2B, 0X32}, 14}, + {0xE1, {0XF0, 0X08, 0X0C, 0X0B, 0X09, 0X24, 0X2B, 0X22, 0X43, 0X38, 0X15, 0X16, 0X2F, 0X37}, 14}, +}; + +// Callback when color transfer is done +static bool notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, + esp_lcd_panel_io_event_data_t *edata, + void *user_ctx) { + lv_display_t *disp = (lv_display_t *)user_ctx; + lv_display_flush_ready(disp); + return false; +} + +St7789i8080Display::St7789i8080Display(const Configuration& config) + : configuration(config), lock(std::make_shared()) { + + // Validate configuration + if (!configuration.isValid()) { + TT_LOG_E(TAG, "Invalid configuration: resolution must be set"); + return; + } +} + +bool St7789i8080Display::createI80Bus() { + TT_LOG_I(TAG, "Creating I80 bus"); + + // Create I80 bus configuration + esp_lcd_i80_bus_config_t bus_cfg = { + .dc_gpio_num = configuration.dcPin, + .wr_gpio_num = configuration.wrPin, + .clk_src = LCD_CLK_SRC_DEFAULT, + .data_gpio_nums = { + configuration.dataPins[0], configuration.dataPins[1], + configuration.dataPins[2], configuration.dataPins[3], + configuration.dataPins[4], configuration.dataPins[5], + configuration.dataPins[6], configuration.dataPins[7], + }, + .bus_width = configuration.busWidth, + .max_transfer_bytes = configuration.bufferSize * sizeof(uint16_t), + .psram_trans_align = 64, + .sram_trans_align = 4 + }; + + esp_err_t ret = esp_lcd_new_i80_bus(&bus_cfg, &i80BusHandle); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to create I80 bus: %s", esp_err_to_name(ret)); + return false; + } + + return true; +} + +bool St7789i8080Display::createPanelIO() { + TT_LOG_I(TAG, "Creating panel IO"); + + // Create panel IO with proper callback + esp_lcd_panel_io_i80_config_t io_cfg = { + .cs_gpio_num = configuration.csPin, + .pclk_hz = configuration.pixelClockFrequency, + .trans_queue_depth = configuration.transactionQueueDepth, + .on_color_trans_done = notify_lvgl_flush_ready, // Use proper callback + .user_ctx = nullptr, // Will be set when LVGL display is created + .lcd_cmd_bits = configuration.lcdCmdBits, + .lcd_param_bits = configuration.lcdParamBits, + .dc_levels = { + .dc_idle_level = configuration.dcLevels.dcIdleLevel, + .dc_cmd_level = configuration.dcLevels.dcCmdLevel, + .dc_dummy_level = configuration.dcLevels.dcDummyLevel, + .dc_data_level = configuration.dcLevels.dcDataLevel, + }, + .flags = { + .cs_active_high = configuration.flags.csActiveHigh, + .reverse_color_bits = configuration.flags.reverseColorBits, + .swap_color_bytes = configuration.flags.swapColorBytes, + .pclk_active_neg = configuration.flags.pclkActiveNeg, + .pclk_idle_low = configuration.flags.pclkIdleLow + } + }; + + esp_err_t ret = esp_lcd_new_panel_io_i80(i80BusHandle, &io_cfg, &ioHandle); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to create panel IO: %s", esp_err_to_name(ret)); + return false; + } + + return true; +} + +bool St7789i8080Display::createPanel() { + TT_LOG_I(TAG, "Configuring panel"); + + // Create ST7789 panel + esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = configuration.resetPin, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .data_endian = LCD_RGB_DATA_ENDIAN_LITTLE, + .bits_per_pixel = 16, + .flags = { + .reset_active_high = false + }, + .vendor_config = nullptr + }; + + esp_err_t ret = esp_lcd_new_panel_st7789(ioHandle, &panel_config, &panelHandle); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to create ST7789 panel: %s", esp_err_to_name(ret)); + return false; + } + + // Reset panel + ret = esp_lcd_panel_reset(panelHandle); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to reset panel: %s", esp_err_to_name(ret)); + return false; + } + + // Initialize panel + ret = esp_lcd_panel_init(panelHandle); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to init panel: %s", esp_err_to_name(ret)); + return false; + } + + // Set gap + ret = esp_lcd_panel_set_gap(panelHandle, configuration.gapX, configuration.gapY); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel gap: %s", esp_err_to_name(ret)); + return false; + } + + // Set inversion + ret = esp_lcd_panel_invert_color(panelHandle, configuration.invertColor); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel inversion: %s", esp_err_to_name(ret)); + return false; + } + + // Set mirror + ret = esp_lcd_panel_mirror(panelHandle, configuration.mirrorX, configuration.mirrorY); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to set panel mirror: %s", esp_err_to_name(ret)); + return false; + } + + // Turn on display + ret = esp_lcd_panel_disp_on_off(panelHandle, true); + if (ret != ESP_OK) { + TT_LOG_E(TAG, "Failed to turn display on: %s", esp_err_to_name(ret)); + return false; + } + + return true; +} + +void St7789i8080Display::sendInitCommands() { + TT_LOG_I(TAG, "Sending ST7789 init commands"); + for (const auto& cmd : st7789_init_cmds) { + esp_lcd_panel_io_tx_param(ioHandle, cmd.cmd, cmd.data, cmd.len & 0x7F); + if (cmd.len & 0x80) { + vTaskDelay(pdMS_TO_TICKS(120)); + } + } +} + +bool St7789i8080Display::start() { + TT_LOG_I(TAG, "Initializing I8080 ST7789 Display hardware..."); + + // Configure RD pin if needed + if (configuration.rdPin != GPIO_NUM_NC) { + gpio_config_t rd_gpio_config = { + .pin_bit_mask = (1ULL << static_cast(configuration.rdPin)), + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&rd_gpio_config); + gpio_set_level(configuration.rdPin, 1); + } + + // Calculate buffer size if needed + configuration.calculateBufferSize(); + + // Allocate buffer based on resolution + size_t buffer_size = configuration.bufferSize * LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565); + buf1 = (uint8_t*)heap_caps_malloc(buffer_size, MALLOC_CAP_DMA); + if (!buf1) { + TT_LOG_E(TAG, "Failed to allocate display buffer"); + return false; + } + + // Create I80 bus + if (!createI80Bus()) { + return false; + } + + // Create panel IO + if (!createPanelIO()) { + return false; + } + + // Create panel + if (!createPanel()) { + return false; + } + + TT_LOG_I(TAG, "Display hardware initialized"); + return true; +} + +bool St7789i8080Display::stop() { + // Turn off display + if (panelHandle) { + esp_lcd_panel_disp_on_off(panelHandle, false); + } + + // Destroy in reverse order: panel, IO, bus + if (panelHandle) { + esp_lcd_panel_del(panelHandle); + panelHandle = nullptr; + } + if (ioHandle) { + esp_lcd_panel_io_del(ioHandle); + ioHandle = nullptr; + } + if (i80BusHandle) { + esp_lcd_del_i80_bus(i80BusHandle); + i80BusHandle = nullptr; + } + + // Free buffer + if (buf1) { + heap_caps_free(buf1); + buf1 = nullptr; + } + + // Turn off backlight + if (configuration.backlightDutyFunction) { + configuration.backlightDutyFunction(0); + } + + return true; +} + +bool St7789i8080Display::startLvgl() { + TT_LOG_I(TAG, "Initializing LVGL for ST7789 display"); + + // Don't reinitialize hardware if it's already done + if (!ioHandle) { + TT_LOG_I(TAG, "Hardware not initialized, calling start()"); + if (!start()) { + TT_LOG_E(TAG, "Hardware initialization failed"); + return false; + } + } else { + TT_LOG_I(TAG, "Hardware already initialized, skipping"); + } + + // Create LVGL display using lvgl_port + lvgl_port_display_cfg_t display_cfg = { + .io_handle = ioHandle, + .panel_handle = panelHandle, + .control_handle = nullptr, + .buffer_size = configuration.bufferSize, + .double_buffer = false, + .trans_size = 0, + .hres = configuration.horizontalResolution, + .vres = configuration.verticalResolution, + .monochrome = false, + .rotation = { + .swap_xy = configuration.swapXY, + .mirror_x = configuration.mirrorX, + .mirror_y = configuration.mirrorY, + }, + .color_format = LV_COLOR_FORMAT_RGB565, + .flags = { + .buff_dma = true, + .buff_spiram = false, + .sw_rotate = false, + .swap_bytes = true, + .full_refresh = false, + .direct_mode = false + } + }; + + // Create the LVGL display + lvglDisplay = lvgl_port_add_disp(&display_cfg); + if (!lvglDisplay) { + TT_LOG_E(TAG, "Failed to create LVGL display"); + return false; + } + + // Register the callback for color transfer completion + esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = notify_lvgl_flush_ready, + }; + esp_lcd_panel_io_register_event_callbacks(ioHandle, &cbs, lvglDisplay); + + g_display_instance = this; + TT_LOG_I(TAG, "LVGL display created successfully"); + return true; +} + +bool St7789i8080Display::stopLvgl() { + if (lvglDisplay) { + lvgl_port_remove_disp(lvglDisplay); + lvglDisplay = nullptr; + } + return true; +} diff --git a/Drivers/ST7789-i8080/Source/St7789i8080Display.h b/Drivers/ST7789-i8080/Source/St7789i8080Display.h new file mode 100644 index 00000000..575dfbd5 --- /dev/null +++ b/Drivers/ST7789-i8080/Source/St7789i8080Display.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class St7789i8080Display : public tt::hal::display::DisplayDevice { +public: + struct Configuration { + // Pin configuration + gpio_num_t csPin; + gpio_num_t dcPin; + gpio_num_t wrPin; + gpio_num_t rdPin; + std::array dataPins; + gpio_num_t resetPin; + gpio_num_t backlightPin; + + // Display resolution configuration + uint16_t horizontalResolution; + uint16_t verticalResolution; + + // Bus configuration + unsigned int pixelClockFrequency = 16 * 1000 * 1000; // 16MHz default + size_t busWidth = 8; // 8-bit bus + size_t transactionQueueDepth = 40; + size_t bufferSize = 0; // Will be calculated if 0 + + // LCD command/parameter configuration + int lcdCmdBits = 8; + int lcdParamBits = 8; + + // DC line level configuration + struct { + bool dcIdleLevel = 0; + bool dcCmdLevel = 0; + bool dcDummyLevel = 0; + bool dcDataLevel = 1; + } dcLevels; + + // Bus flags + struct { + bool csActiveHigh = false; + bool reverseColorBits = false; + bool swapColorBytes = true; + bool pclkActiveNeg = false; + bool pclkIdleLow = false; + } flags; + + // Display configuration + int gapX = 0; + int gapY = 0; + bool swapXY = false; + bool mirrorX = false; + bool mirrorY = false; + bool invertColor = true; + + // Additional features + std::shared_ptr touch = nullptr; + std::function backlightDutyFunction = nullptr; + + // Basic constructor - requires resolution to be set separately + Configuration(gpio_num_t cs, gpio_num_t dc, gpio_num_t wr, gpio_num_t rd, + std::array data, gpio_num_t rst, gpio_num_t bl) + : csPin(cs), dcPin(dc), wrPin(wr), rdPin(rd), + dataPins(data), resetPin(rst), backlightPin(bl), + horizontalResolution(0), verticalResolution(0) {} // Initialize to 0 + + // Method to calculate buffer size after resolution is set + void calculateBufferSize() { + if (bufferSize == 0 && horizontalResolution > 0 && verticalResolution > 0) { + bufferSize = horizontalResolution * verticalResolution / 10; + } + } + + // Validation method + bool isValid() const { + return horizontalResolution > 0 && verticalResolution > 0; + } + }; + +private: + Configuration configuration; + esp_lcd_i80_bus_handle_t i80BusHandle = nullptr; + esp_lcd_panel_io_handle_t ioHandle = nullptr; + esp_lcd_panel_handle_t panelHandle = nullptr; + lv_display_t* lvglDisplay = nullptr; + std::shared_ptr lock; + uint8_t* buf1 = nullptr; + + // Internal initialization methods + void sendInitCommands(); + bool createI80Bus(); + bool createPanelIO(); + bool createPanel(); + +public: + explicit St7789i8080Display(const Configuration& config); + lv_display_t* getLvglDisplay() const override { return lvglDisplay; } + std::string getName() const override { return "I8080 ST7789"; } + std::string getDescription() const override { return "I8080-based ST7789 display"; } + + // Lifecycle + bool start() override; + bool stop() override; + bool startLvgl() override; + bool stopLvgl() override; + + // Capabilities + bool supportsLvgl() const override { return true; } + bool supportsDisplayDriver() const override { return false; } + bool supportsBacklightDuty() const override { return configuration.backlightDutyFunction != nullptr; } + + // Touch and backlight + std::shared_ptr getTouchDevice() override { return configuration.touch; } + std::shared_ptr getDisplayDriver() override { return nullptr; } + + void setBacklightDuty(uint8_t backlightDuty) override { + if (configuration.backlightDutyFunction != nullptr) { + configuration.backlightDutyFunction(backlightDuty); + } + } + + // Hardware access methods + esp_lcd_panel_io_handle_t getIoHandle() const { return ioHandle; } + esp_lcd_panel_handle_t getPanelHandle() const { return panelHandle; } + + // Resolution access methods + uint16_t getHorizontalResolution() const { return configuration.horizontalResolution; } + uint16_t getVerticalResolution() const { return configuration.verticalResolution; } +}; + +// Factory function for registration +std::shared_ptr createDisplay(); \ No newline at end of file diff --git a/sdkconfig.board.lilygo-tdisplay-s3 b/sdkconfig.board.lilygo-tdisplay-s3 new file mode 100644 index 00000000..780c2ce2 --- /dev/null +++ b/sdkconfig.board.lilygo-tdisplay-s3 @@ -0,0 +1,60 @@ +# Software defaults +# Increase stack size for WiFi (fixes crash after scan) +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=3072 +CONFIG_LV_FONT_MONTSERRAT_14=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_USE_FS_STDIO=y +CONFIG_LV_FS_STDIO_LETTER=65 +CONFIG_LV_FS_STDIO_PATH="" +CONFIG_LV_FS_STDIO_CACHE_SIZE=4096 +CONFIG_LV_USE_LODEPNG=y +CONFIG_LV_USE_BUILTIN_MALLOC=n +CONFIG_LV_USE_CLIB_MALLOC=y +CONFIG_LV_USE_MSGBOX=n +CONFIG_LV_USE_SPINNER=n +CONFIG_LV_USE_WIN=n +CONFIG_LV_USE_SNAPSHOT=y +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=2 +CONFIG_FREERTOS_SMP=n +CONFIG_FREERTOS_UNICORE=n +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=5120 +CONFIG_FREERTOS_USE_TRACE_FACILITY=y +CONFIG_FATFS_LFN_HEAP=y +CONFIG_FATFS_VOLUME_COUNT=3 +CONFIG_FATFS_SECTOR_512=y +CONFIG_WL_SECTOR_SIZE_512=y +CONFIG_WL_SECTOR_SIZE=512 +CONFIG_WL_SECTOR_MODE_SAFE=y +CONFIG_WL_SECTOR_MODE=1 + +# Hardware: Main +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions-16mb.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions-16mb.csv" +CONFIG_TT_BOARD_LILYGO_TDISPLAYS3=y +CONFIG_TT_BOARD_NAME="LilyGo T-Display S3" +CONFIG_TT_BOARD_ID="lilygo-tdisplay-s3" +CONFIG_IDF_EXPERIMENTAL_FEATURES=y +CONFIG_IDF_TARGET="esp32s3" +CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y +CONFIG_FLASHMODE_QIO=y +# Hardware: SPI RAM +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_SPEED_120M=y +CONFIG_SPIRAM_USE_MALLOC=y +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +# SPI Flash (can set back to 80MHz after ESP-IDF bug is resolved) +CONFIG_ESPTOOLPY_FLASHFREQ_120M=y +# LVGL +CONFIG_LV_DPI_DEF=139 +CONFIG_LV_DISP_DEF_REFR_PERIOD=10 +CONFIG_LV_THEME_DEFAULT_DARK=y +CONFIG_LV_USE_ST7789=y +# USB +CONFIG_TINYUSB_MSC_ENABLED=y +CONFIG_TINYUSB_MSC_MOUNT_PATH="/sdcard"