From 49d4bfe16f16e801436e77f25c3d201f93dbd971 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 17 Dec 2025 21:43:02 -0600 Subject: [PATCH 1/4] feat(m5tab5): Add support for querying and automatically determining the appropriate display driver closes #573 --- components/display_drivers/include/st7123.hpp | 186 ++++++++++++++++++ components/m5stack-tab5/README.md | 23 +++ .../m5stack-tab5/include/m5stack-tab5.hpp | 25 ++- components/m5stack-tab5/src/video.cpp | 32 ++- 4 files changed, 258 insertions(+), 8 deletions(-) create mode 100644 components/display_drivers/include/st7123.hpp diff --git a/components/display_drivers/include/st7123.hpp b/components/display_drivers/include/st7123.hpp new file mode 100644 index 000000000..816c9bcaa --- /dev/null +++ b/components/display_drivers/include/st7123.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include + +#include "display_drivers.hpp" + +namespace espp { +/** + * @brief Display driver for the ST7123 display controller (DSI/DCS style). + * + * This follows the same interface as the other display drivers and relies on a + * lower-level transport to execute write_command and bulk color transfers. + * + * The initialization sequence is compatible with M5Stack Tab5 displays using + * the ST7123 controller chip. + */ +class St7123 { + static constexpr uint8_t LA_BIT = 1 << 0; ///< Row Address Order (LA) + static constexpr uint8_t CA_BIT = 1 << 1; ///< Column Address Order (CA) + static constexpr uint8_t BGR_BIT = 1 << 3; ///< BGR Order + +public: + enum class Command : uint8_t { + // Standard DCS Commands + nop = 0x00, ///< No Operation + swreset = 0x01, ///< Software Reset + rddid = 0x04, ///< Read Display ID + sleep_in = 0x10, ///< Sleep In + sleep_out = 0x11, ///< Sleep Out + partial_on = 0x12, ///< Partial Mode On + normal_on = 0x13, ///< Normal Display Mode On + invert_off = 0x20, ///< Display Inversion Off + invert_on = 0x21, ///< Display Inversion On + display_off = 0x28, ///< Display Off + display_on = 0x29, ///< Display On + caset = 0x2A, ///< Column Address Set + raset = 0x2B, ///< Row Address Set + ramwr = 0x2C, ///< Memory Write + ramrd = 0x2E, ///< Memory Read + partial_area = 0x30, ///< Partial Area + vscrdef = 0x33, ///< Vertical Scrolling Definition + te_off = 0x34, ///< Tearing Effect Line Off + te_on = 0x35, ///< Tearing Effect Line On + madctl = 0x36, ///< Memory Data Access Control + vscsad = 0x37, ///< Vertical Scroll Start Address of RAM + idle_off = 0x38, ///< Idle Mode Off + idle_on = 0x39, ///< Idle Mode On + colmod = 0x3A, ///< Pixel Format Set + + // ST7123 Specific Commands + page_select = 0xB9, ///< Set EXTC command (0xB9 for ST7123) + }; + + /** + * @brief Store config and send initialization commands to the controller. + * @param config display_drivers::Config + * @return true if initialization succeeded, false otherwise + */ + static bool initialize(const display_drivers::Config &config) { + write_command_ = config.write_command; + read_command_ = config.read_command; + lcd_send_lines_ = config.lcd_send_lines; + reset_pin_ = config.reset_pin; + dc_pin_ = config.data_command_pin; + offset_x_ = config.offset_x; + offset_y_ = config.offset_y; + mirror_x_ = config.mirror_x; + mirror_y_ = config.mirror_y; + mirror_portrait_ = config.mirror_portrait; + swap_xy_ = config.swap_xy; + swap_color_order_ = config.swap_color_order; + + // Initialize display pins + display_drivers::init_pins(reset_pin_, dc_pin_, config.reset_value); + + uint8_t madctl = 0x00; + if (swap_color_order_) { + madctl |= BGR_BIT; + } + if (mirror_x_) { + madctl |= CA_BIT; + } + if (mirror_y_) { + madctl |= LA_BIT; + } + if (swap_xy_) { + // Row/column exchange - ST7123 doesn't have explicit bit for this in basic MADCTL + madctl |= 0; + } + + uint8_t colmod = 0x55; // default to 16 bits per pixel + switch (config.bits_per_pixel) { + case 16: // RGB565 + colmod = 0x55; + break; + case 18: // RGB666 + colmod = 0x66; + break; + case 24: // RGB888 + colmod = 0x77; + break; + default: + break; + } + + // Try to read the ID if we have a read_command function + if (config.read_command) { + uint8_t id[3] = {0}; + // Read ID registers (RDDID returns 0xFF for first byte, then ID1, ID2) + read_command_(static_cast(Command::rddid), {id, 3}, 0); + + // First byte is 0xFF, check remaining bytes for ST7123 signature + // Note: Exact ID values depend on panel manufacturer + // Skip validation for now to allow display to work with different panels + // if (id[0] != 0xFF) { + // return false; + // } + } + + // ST7123 initialization sequence + // Minimal initialization based on ST7123 datasheet + auto init_commands = std::to_array>({ + // Software reset + {static_cast(Command::swreset), {}, 120}, + + // Sleep Out - must wait 120ms after this + {static_cast(Command::sleep_out), {}, 120}, + + // Memory access control + {static_cast(Command::madctl), {madctl}, 0}, + + // Pixel format (color mode) + {static_cast(Command::colmod), {colmod}, 0}, + + // Normal display mode + {static_cast(Command::normal_on), {}, 0}, + + // Display On + {static_cast(Command::display_on), {}, 20}, + }); + + for (auto &cmd : init_commands) { + write_command_(cmd.command, std::span(cmd.parameters), 0); + if (cmd.delay_ms > 0) { + std::this_thread::sleep_for(std::chrono::milliseconds(cmd.delay_ms)); + } + } + + return true; + } + + /** + * @brief Get the display controller ID + * @return ID string for ST7123 + */ + static constexpr const char *id() { return "ST7123"; } + +protected: + static display_drivers::write_command_fn write_command_; + static display_drivers::read_command_fn read_command_; + static display_drivers::send_lines_fn lcd_send_lines_; + static gpio_num_t reset_pin_; + static gpio_num_t dc_pin_; + static int offset_x_; + static int offset_y_; + static bool swap_xy_; + static bool mirror_x_; + static bool mirror_y_; + static bool mirror_portrait_; + static bool swap_color_order_; +}; + +inline display_drivers::write_command_fn St7123::write_command_{nullptr}; +inline display_drivers::read_command_fn St7123::read_command_{nullptr}; +inline display_drivers::send_lines_fn St7123::lcd_send_lines_{nullptr}; +inline gpio_num_t St7123::reset_pin_{GPIO_NUM_NC}; +inline gpio_num_t St7123::dc_pin_{GPIO_NUM_NC}; +inline int St7123::offset_x_{0}; +inline int St7123::offset_y_{0}; +inline bool St7123::swap_xy_{false}; +inline bool St7123::mirror_x_{false}; +inline bool St7123::mirror_y_{false}; +inline bool St7123::mirror_portrait_{false}; +inline bool St7123::swap_color_order_{false}; + +} // namespace espp diff --git a/components/m5stack-tab5/README.md b/components/m5stack-tab5/README.md index 47de5a714..fa9ed6cba 100644 --- a/components/m5stack-tab5/README.md +++ b/components/m5stack-tab5/README.md @@ -10,6 +10,7 @@ The `espp::M5StackTab5` component provides a singleton hardware abstraction for ### Display & Touch - 5″ 1280 × 720 IPS TFT screen via MIPI-DSI +- **Automatic display controller detection** (supports ILI9881 or ST7123) - GT911 multi-touch controller (I²C) for smooth interaction - Adjustable backlight brightness control @@ -65,6 +66,28 @@ The `espp::M5StackTab5` component provides a singleton hardware abstraction for | Battery | NP-F550 2000mAh removable | | Expansion | Grove, M5-Bus, STAMP pads, GPIO headers | +## Display Controller Auto-Detection + +The M5Stack Tab5 hardware can be manufactured with one of two different MIPI-DSI display controllers: +- **ILI9881** (earlier hardware revisions) +- **ST7123** (newer hardware revisions) + +The BSP automatically detects which display controller is present during initialization by: +1. Attempting to initialize with the ILI9881 driver first +2. If ILI9881 detection fails, falling back to ST7123 initialization +3. Logging the detected controller type for debugging + +This means your application code works seamlessly across both hardware variants without any code changes. You can optionally query the detected controller type: + +```cpp +auto& tab5 = espp::M5StackTab5::get(); +tab5.initialize_lcd(); + +// Query the detected controller +auto controller_type = tab5.get_display_controller(); +const char* controller_name = tab5.get_display_controller_name(); +``` + ## Example The [example](./example) shows how to use the `espp::M5StackTab5` hardware abstraction component to initialize and use various subsystems of the Tab5. diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp index df71d89df..294a6672a 100644 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -39,6 +39,7 @@ #include "led.hpp" #include "pi4ioe5v.hpp" #include "rx8130ce.hpp" +#include "st7123.hpp" #include "touchpad_input.hpp" // #include "wifi_ap.hpp" // #include "wifi_sta.hpp" @@ -75,8 +76,25 @@ class M5StackTab5 : public BaseComponent { /// Alias for the pixel type used by the Tab5 display using Pixel = lv_color16_t; - /// Alias for the display driver used by the Tab5 - using DisplayDriver = espp::Ili9881; + /// Enum for display controller type + enum class DisplayController { UNKNOWN, ILI9881, ST7123 }; + + /// Get the detected display controller type + /// \return The display controller type + DisplayController get_display_controller() const { return display_controller_; } + + /// Get a string name for the display controller + /// \return String name of the controller + const char *get_display_controller_name() const { + switch (display_controller_) { + case DisplayController::ILI9881: + return "ILI9881"; + case DisplayController::ST7123: + return "ST7123"; + default: + return "Unknown"; + } + } /// Alias for the GT911 touch controller used by the Tab5 using TouchDriver = espp::Gt911; @@ -710,6 +728,9 @@ class M5StackTab5 : public BaseComponent { esp_lcd_panel_handle_t panel{nullptr}; // color handle } lcd_handles_{}; + // Display controller detection + DisplayController display_controller_{DisplayController::UNKNOWN}; + // original function pointer for the panel del, init esp_err_t (*original_panel_del_)(esp_lcd_panel_t *panel){nullptr}; esp_err_t (*original_panel_init_)(esp_lcd_panel_t *panel){nullptr}; diff --git a/components/m5stack-tab5/src/video.cpp b/components/m5stack-tab5/src/video.cpp index 588bcc535..e870d1324 100644 --- a/components/m5stack-tab5/src/video.cpp +++ b/components/m5stack-tab5/src/video.cpp @@ -13,8 +13,7 @@ using namespace std::chrono_literals; namespace espp { bool M5StackTab5::initialize_lcd() { - logger_.info("Initializing M5Stack Tab5 LCD (MIPI-DSI, ILI9881C, {}x{})", display_width_, - display_height_); + logger_.info("Initializing M5Stack Tab5 LCD (MIPI-DSI, {}x{})", display_width_, display_height_); if (!ioexp_0x43_) { if (!initialize_io_expanders()) { @@ -127,10 +126,11 @@ bool M5StackTab5::initialize_lcd() { } } - // Now initialize DisplayDriver for any additional configuration - logger_.info("Initializing DisplayDriver with DSI configuration"); + // Detect and initialize the appropriate display controller + logger_.info("Detecting display controller type"); using namespace std::placeholders; - DisplayDriver::initialize(espp::display_drivers::Config{ + + espp::display_drivers::Config display_config{ .write_command = std::bind_front(&M5StackTab5::dsi_write_command, this), .read_command = std::bind_front(&M5StackTab5::dsi_read_command, this), .lcd_send_lines = nullptr, @@ -145,7 +145,27 @@ bool M5StackTab5::initialize_lcd() { .mirror_x = mirror_x, .mirror_y = mirror_y, .mirror_portrait = false, - }); + }; + + // Try ILI9881 first + logger_.info("Attempting to initialize as ILI9881"); + if (espp::Ili9881::initialize(display_config)) { + logger_.info("Successfully detected ILI9881 display controller"); + display_controller_ = DisplayController::ILI9881; + } + // If ILI9881 fails, try ST7123 + else { + logger_.info("ILI9881 detection failed, attempting ST7123"); + if (espp::St7123::initialize(display_config)) { + logger_.info("Successfully detected ST7123 display controller"); + display_controller_ = DisplayController::ST7123; + } else { + logger_.error("Failed to detect display controller (tried ILI9881 and ST7123)"); + return false; + } + } + + logger_.info("Display controller: {}", get_display_controller_name()); // call init on the panel logger_.info("Calling low-level panel init"); From e1eae2379885d8052e18a4ccef25f9f255b423ed Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 18 Dec 2025 10:49:10 -0600 Subject: [PATCH 2/4] updated with fixes and improvements from @CarbonNeuron (thanks!) --- .../example/main/m5stack_tab5_example.cpp | 9 +- .../m5stack-tab5/include/m5stack-tab5.hpp | 10 ++- components/m5stack-tab5/src/video.cpp | 85 ++++++++++++++----- 3 files changed, 80 insertions(+), 24 deletions(-) diff --git a/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp b/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp index 141f5c97c..281b7209e 100644 --- a/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp +++ b/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp @@ -307,17 +307,20 @@ extern "C" void app_main(void) { // start a simple thread to do the lv_task_handler every 16ms logger.info("Starting LVGL task..."); espp::Task lv_task({.callback = [](std::mutex &m, std::condition_variable &cv) -> bool { + auto start_time = std::chrono::high_resolution_clock::now(); { std::lock_guard lock(lvgl_mutex); lv_task_handler(); } std::unique_lock lock(m); - cv.wait_for(lock, 16ms); + cv.wait_until(lock, start_time + 16ms, []() { return false; }); return false; }, .task_config = { .name = "lv_task", .stack_size_bytes = 10 * 1024, + .priority = 20, + .core_id = 1, }}); lv_task.start(); @@ -348,7 +351,7 @@ extern "C" void app_main(void) { // sleep first in case we don't get IMU data and need to exit early { std::unique_lock lock(m); - cv.wait_for(lock, 10ms); + cv.wait_for(lock, 20ms); } static auto &tab5 = espp::M5StackTab5::get(); static auto imu = tab5.imu(); @@ -483,7 +486,7 @@ extern "C" void app_main(void) { .name = "Data Display Task", .stack_size_bytes = 6 * 1024, .priority = 10, - .core_id = 0, + .core_id = 1, }}); imu_task.start(); diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp index 294a6672a..06567fbc5 100644 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -79,6 +79,8 @@ class M5StackTab5 : public BaseComponent { /// Enum for display controller type enum class DisplayController { UNKNOWN, ILI9881, ST7123 }; + DisplayController detect_display_controller(); + /// Get the detected display controller type /// \return The display controller type DisplayController get_display_controller() const { return display_controller_; } @@ -86,7 +88,13 @@ class M5StackTab5 : public BaseComponent { /// Get a string name for the display controller /// \return String name of the controller const char *get_display_controller_name() const { - switch (display_controller_) { + return get_display_controller_name(display_controller_); + } + + /// Get a string name for the display controller + /// \return String name of the controller + const char *get_display_controller_name(DisplayController controller) const { + switch (controller) { case DisplayController::ILI9881: return "ILI9881"; case DisplayController::ST7123: diff --git a/components/m5stack-tab5/src/video.cpp b/components/m5stack-tab5/src/video.cpp index e870d1324..06709e08f 100644 --- a/components/m5stack-tab5/src/video.cpp +++ b/components/m5stack-tab5/src/video.cpp @@ -12,6 +12,24 @@ using namespace std::chrono_literals; namespace espp { +M5StackTab5::DisplayController M5StackTab5::detect_display_controller() { + auto &i2c = internal_i2c(); + + // Probe for the GT911 touch controller, if it exists we have an ILI9881 display + bool exists = i2c.probe_device(0x14); + if (exists) { + return M5StackTab5::DisplayController::ILI9881; + } + + // Probe for the ST7123 display controller + exists = i2c.probe_device(0x55); + if (exists) { + return M5StackTab5::DisplayController::ST7123; + } + + // Unknown display controller + return M5StackTab5::DisplayController::UNKNOWN; +} bool M5StackTab5::initialize_lcd() { logger_.info("Initializing M5Stack Tab5 LCD (MIPI-DSI, {}x{})", display_width_, display_height_); @@ -65,6 +83,10 @@ bool M5StackTab5::initialize_lcd() { lcd_reset(false); // Release reset std::this_thread::sleep_for(120ms); + // Detect and initialize the appropriate display controller + logger_.info("Detecting display controller type"); + auto detected_controller = detect_display_controller(); + // create MIPI DSI bus first, it will initialize the DSI PHY as well if (lcd_handles_.mipi_dsi_bus == nullptr) { logger_.info("Creating MIPI DSI bus"); @@ -72,7 +94,8 @@ bool M5StackTab5::initialize_lcd() { .bus_id = 0, .num_data_lanes = 2, .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, - .lane_bit_rate_mbps = 730, + .lane_bit_rate_mbps = + (uint32_t)(detected_controller == DisplayController::ILI9881 ? 730 : 965), }; ret = esp_lcd_new_dsi_bus(&bus_config, &lcd_handles_.mipi_dsi_bus); if (ret != ESP_OK) { @@ -97,11 +120,18 @@ bool M5StackTab5::initialize_lcd() { } } - // Create DPI panel with M5Stack Tab5 official ILI9881 timing parameters - if (lcd_handles_.panel == nullptr) { + if (detected_controller == DisplayController::UNKNOWN) { + logger_.error("Unable to detect display controller"); + return false; + } + logger_.info("Detected display controller: {}", get_display_controller_name(detected_controller)); + + esp_lcd_dpi_panel_config_t dpi_cfg{}; + memset(&dpi_cfg, 0, sizeof(dpi_cfg)); + + if (detected_controller == DisplayController::ILI9881 && lcd_handles_.panel == nullptr) { + // Create DPI panel with M5Stack Tab5 official ILI9881 timing parameters logger_.info("Creating MIPI DSI DPI panel with M5Stack Tab5 ILI9881 configuration"); - esp_lcd_dpi_panel_config_t dpi_cfg{}; - memset(&dpi_cfg, 0, sizeof(dpi_cfg)); dpi_cfg.virtual_channel = 0; dpi_cfg.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT; dpi_cfg.dpi_clock_freq_mhz = 60; @@ -117,6 +147,24 @@ bool M5StackTab5::initialize_lcd() { dpi_cfg.video_timing.vsync_front_porch = 20; dpi_cfg.flags.use_dma2d = true; + } else if (detected_controller == DisplayController::ST7123 && lcd_handles_.panel == nullptr) { + dpi_cfg.virtual_channel = 0; + dpi_cfg.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT; + dpi_cfg.dpi_clock_freq_mhz = 100; + dpi_cfg.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565; + dpi_cfg.num_fbs = 1; + dpi_cfg.video_timing.h_size = display_width_; + dpi_cfg.video_timing.v_size = display_height_; + dpi_cfg.video_timing.hsync_back_porch = 40; + dpi_cfg.video_timing.hsync_pulse_width = 2; + dpi_cfg.video_timing.hsync_front_porch = 40; + dpi_cfg.video_timing.vsync_back_porch = 8; + dpi_cfg.video_timing.vsync_pulse_width = 2; + dpi_cfg.video_timing.vsync_front_porch = 220; + dpi_cfg.flags.use_dma2d = true; + } + + if (lcd_handles_.panel == nullptr) { logger_.info("Creating DPI panel with resolution {}x{}", dpi_cfg.video_timing.h_size, dpi_cfg.video_timing.v_size); ret = esp_lcd_new_panel_dpi(lcd_handles_.mipi_dsi_bus, &dpi_cfg, &lcd_handles_.panel); @@ -126,8 +174,6 @@ bool M5StackTab5::initialize_lcd() { } } - // Detect and initialize the appropriate display controller - logger_.info("Detecting display controller type"); using namespace std::placeholders; espp::display_drivers::Config display_config{ @@ -147,22 +193,21 @@ bool M5StackTab5::initialize_lcd() { .mirror_portrait = false, }; - // Try ILI9881 first - logger_.info("Attempting to initialize as ILI9881"); - if (espp::Ili9881::initialize(display_config)) { - logger_.info("Successfully detected ILI9881 display controller"); - display_controller_ = DisplayController::ILI9881; - } - // If ILI9881 fails, try ST7123 - else { - logger_.info("ILI9881 detection failed, attempting ST7123"); + if (detected_controller == DisplayController::ILI9881) { + logger_.info("Initializing as ILI9881"); + if (espp::Ili9881::initialize(display_config)) { + logger_.info("Successfully initialized ILI9881 display controller"); + display_controller_ = DisplayController::ILI9881; + } + } else if (detected_controller == DisplayController::ST7123) { + logger_.info("Initializing as ST7123"); if (espp::St7123::initialize(display_config)) { - logger_.info("Successfully detected ST7123 display controller"); + logger_.info("Successfully initialized ST7123 display controller"); display_controller_ = DisplayController::ST7123; - } else { - logger_.error("Failed to detect display controller (tried ILI9881 and ST7123)"); - return false; } + } else { + logger_.error("Failed to detect display controller"); + return false; } logger_.info("Display controller: {}", get_display_controller_name()); From 604a30acff3d4b046039b1e822902dd3da0958d5 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 18 Dec 2025 11:20:33 -0600 Subject: [PATCH 3/4] updated st7123 display driver --- components/display_drivers/include/class.hpp | 51 ++++ components/display_drivers/include/st7123.hpp | 222 +++++++++--------- 2 files changed, 167 insertions(+), 106 deletions(-) create mode 100644 components/display_drivers/include/class.hpp diff --git a/components/display_drivers/include/class.hpp b/components/display_drivers/include/class.hpp new file mode 100644 index 000000000..88fc4e874 --- /dev/null +++ b/components/display_drivers/include/class.hpp @@ -0,0 +1,51 @@ + +/** + * @brief Base class for display drivers. + */ +class DisplayDriver { +public: + /// @brief Construct a DisplayDriver + /// @param config Configuration for the display driver + explicit DisplayDriver(const Config &config) + : config_(config) {} + + /// @brief Initialize the display driver + /// @param write Write function for sending commands to the display + /// @param send_lines Function for sending pixel data to the display + /// @param width Width of the display in pixels + /// @param height Height of the display in pixels + /// @return True if initialization was successful, false otherwise + virtual bool initialize(write_command_fn write, send_lines_fn send_lines, size_t width, + size_t height) { + write_command_ = write; + send_lines_ = send_lines; + width_ = width; + height_ = height; + return true; + } + + /// @brief Reset the display + virtual void reset() { + std::scoped_lock lk(config_mutex_); + if (config_.reset_pin != GPIO_NUM_NC) { + gpio_set_level(config_.reset_pin, config_.reset_value); + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + gpio_set_level(config_.reset_pin, !config_.reset_value); + std::this_thread::sleep_for(std::chrono::milliseconds(120)); + } + } + + /// @brief Get the width of the display + /// @return Width in pixels + size_t width() const { return width_; } + + /// @brief Get the height of the display + /// @return Height in pixels + size_t height() const { return height_; } + +protected: + std::mutex config_mutex_; + Config config_; + size_t width_{0}; + size_t height_{0}; +}; diff --git a/components/display_drivers/include/st7123.hpp b/components/display_drivers/include/st7123.hpp index 816c9bcaa..4358537d7 100644 --- a/components/display_drivers/include/st7123.hpp +++ b/components/display_drivers/include/st7123.hpp @@ -1,61 +1,40 @@ -#pragma once -#include +#pragma once #include "display_drivers.hpp" +#include +#include +#include namespace espp { -/** - * @brief Display driver for the ST7123 display controller (DSI/DCS style). - * - * This follows the same interface as the other display drivers and relies on a - * lower-level transport to execute write_command and bulk color transfers. - * - * The initialization sequence is compatible with M5Stack Tab5 displays using - * the ST7123 controller chip. - */ + class St7123 { - static constexpr uint8_t LA_BIT = 1 << 0; ///< Row Address Order (LA) - static constexpr uint8_t CA_BIT = 1 << 1; ///< Column Address Order (CA) - static constexpr uint8_t BGR_BIT = 1 << 3; ///< BGR Order + // MADCTL bits (see Espressif driver) + static constexpr uint8_t GS_BIT = 1 << 0; // Row mirror (Y) + static constexpr uint8_t SS_BIT = 1 << 1; // Column mirror (X) + static constexpr uint8_t BGR_BIT = 1 << 3; public: enum class Command : uint8_t { - // Standard DCS Commands - nop = 0x00, ///< No Operation - swreset = 0x01, ///< Software Reset - rddid = 0x04, ///< Read Display ID - sleep_in = 0x10, ///< Sleep In - sleep_out = 0x11, ///< Sleep Out - partial_on = 0x12, ///< Partial Mode On - normal_on = 0x13, ///< Normal Display Mode On - invert_off = 0x20, ///< Display Inversion Off - invert_on = 0x21, ///< Display Inversion On - display_off = 0x28, ///< Display Off - display_on = 0x29, ///< Display On - caset = 0x2A, ///< Column Address Set - raset = 0x2B, ///< Row Address Set - ramwr = 0x2C, ///< Memory Write - ramrd = 0x2E, ///< Memory Read - partial_area = 0x30, ///< Partial Area - vscrdef = 0x33, ///< Vertical Scrolling Definition - te_off = 0x34, ///< Tearing Effect Line Off - te_on = 0x35, ///< Tearing Effect Line On - madctl = 0x36, ///< Memory Data Access Control - vscsad = 0x37, ///< Vertical Scroll Start Address of RAM - idle_off = 0x38, ///< Idle Mode Off - idle_on = 0x39, ///< Idle Mode On - colmod = 0x3A, ///< Pixel Format Set - - // ST7123 Specific Commands - page_select = 0xB9, ///< Set EXTC command (0xB9 for ST7123) + NOP = 0x00, + SWRESET = 0x01, + RDDID = 0x04, + SLPIN = 0x10, + SLPOUT = 0x11, + NORON = 0x13, + INVOFF = 0x20, + INVON = 0x21, + DISPOFF = 0x28, + DISPON = 0x29, + CASET = 0x2A, + RASET = 0x2B, + RAMWR = 0x2C, + RAMRD = 0x2E, + MADCTL = 0x36, + COLMOD = 0x3A, + // ... add more as needed ... }; - /** - * @brief Store config and send initialization commands to the controller. - * @param config display_drivers::Config - * @return true if initialization succeeded, false otherwise - */ static bool initialize(const display_drivers::Config &config) { write_command_ = config.write_command; read_command_ = config.read_command; @@ -73,86 +52,117 @@ class St7123 { // Initialize display pins display_drivers::init_pins(reset_pin_, dc_pin_, config.reset_value); - uint8_t madctl = 0x00; - if (swap_color_order_) { + // MADCTL value + uint8_t madctl = 0; + if (mirror_x_) + madctl |= GS_BIT; + if (mirror_y_) + madctl |= SS_BIT; + if (swap_color_order_) madctl |= BGR_BIT; - } - if (mirror_x_) { - madctl |= CA_BIT; - } - if (mirror_y_) { - madctl |= LA_BIT; - } - if (swap_xy_) { - // Row/column exchange - ST7123 doesn't have explicit bit for this in basic MADCTL - madctl |= 0; - } + // Note: swap_xy_ not supported by ST7123 MADCTL - uint8_t colmod = 0x55; // default to 16 bits per pixel + // COLMOD value + uint8_t colmod = 0x55; // 16bpp default switch (config.bits_per_pixel) { - case 16: // RGB565 + case 16: colmod = 0x55; break; - case 18: // RGB666 + case 18: colmod = 0x66; break; - case 24: // RGB888 + case 24: colmod = 0x77; break; default: break; } - - // Try to read the ID if we have a read_command function - if (config.read_command) { - uint8_t id[3] = {0}; - // Read ID registers (RDDID returns 0xFF for first byte, then ID1, ID2) - read_command_(static_cast(Command::rddid), {id, 3}, 0); - - // First byte is 0xFF, check remaining bytes for ST7123 signature - // Note: Exact ID values depend on panel manufacturer - // Skip validation for now to allow display to work with different panels - // if (id[0] != 0xFF) { - // return false; - // } - } - - // ST7123 initialization sequence - // Minimal initialization based on ST7123 datasheet - auto init_commands = std::to_array>({ - // Software reset - {static_cast(Command::swreset), {}, 120}, - - // Sleep Out - must wait 120ms after this - {static_cast(Command::sleep_out), {}, 120}, - - // Memory access control - {static_cast(Command::madctl), {madctl}, 0}, - - // Pixel format (color mode) - {static_cast(Command::colmod), {colmod}, 0}, - - // Normal display mode - {static_cast(Command::normal_on), {}, 0}, - - // Display On - {static_cast(Command::display_on), {}, 20}, - }); - - for (auto &cmd : init_commands) { + // ST7123 vendor-specific init sequence (from Espressif driver) + using Cmd = display_drivers::DisplayInitCmd<>; + std::array init_cmds = {{ + {0x60, {0x71, 0x23, 0xa2}, 0}, + {0x60, {0x71, 0x23, 0xa3}, 0}, + {0x60, {0x71, 0x23, 0xa4}, 0}, + {0xA4, {0x31}, 0}, + {0xD7, {0x10, 0x0A, 0x10, 0x2A, 0x80, 0x80}, 0}, + {0x90, {0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09}, 0}, + {0xA3, + {0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00, + 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, + 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, 0x00, 0x00, 0x00, 0xFF}, + 0}, + {0xA6, + {0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, + 0x55, 0x38, 0x00, 0x37, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x11, 0x00, 0x00, + 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0xEC, 0x11, 0x00, 0x03, 0x00, 0x03, 0x6E, + 0x6E, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00}, + 0}, + {0xA7, + {0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6E, 0x6E, 0x91, 0xFF, + 0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02, 0x01, 0x6E, 0x6E, 0x91, 0xFF, 0x08, + 0x80, 0x64, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80, + 0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6E, 0x6E, 0x84, 0xFF, 0x08, 0x80, 0x44}, + 0}, + {0xAC, + {0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08, 0x0A, 0x0A, 0x1C, + 0x1C, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19, 0x19, 0x18, 0x18, 0x06, 0x12, 0x12, + 0x10, 0x10, 0x09, 0x09, 0x0B, 0x0B, 0x1C, 0x1C, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01}, + 0}, + {0xAD, + {0xF0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xFF, 0xFF, 0xF0, 0x40, 0x06, 0x01, + 0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF}, + 0}, + {0xAE, {0xFE, 0x3F, 0x3F, 0xFE, 0x3F, 0x3F, 0x00}, 0}, + {0xB2, + {0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20, + 0xC0, 0x00}, + 0}, + {0xE8, + {0x20, 0x6F, 0x04, 0x97, 0x97, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E}, + 0}, + {0x75, {0x03, 0x04}, 0}, + {0xE7, + {0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, 0xF0, 0xB1, 0x50, 0x00, + 0x50, 0xB1, 0x50, 0xB1, 0x50, 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45, + 0xC9, 0x6A, 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x77}, + 0}, + {0xEA, {0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C}, 0}, + {0xB0, {0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43}, 0}, + {0xB7, {0x00, 0x00, 0x73, 0x73}, 0}, + {0xBF, {0xA6, 0xAA}, 0}, + {0xA9, {0x00, 0x00, 0x73, 0xFF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03}, 0}, + {0xC8, + {0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, + 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, + 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF}, + 0}, + {0xC9, + {0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06, + 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32, + 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF}, + 0}, + {0x11, {0x00}, 100}, + {0x29, {0x00}, 0}, + {0x35, {0x00}, 100}, + }}; + + // Send vendor-specific init sequence + for (const auto &cmd : init_cmds) { write_command_(cmd.command, std::span(cmd.parameters), 0); if (cmd.delay_ms > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(cmd.delay_ms)); } + std::this_thread::sleep_for(std::chrono::milliseconds(5)); } + // Set MADCTL (mirror/color order) + write_command_(static_cast(Command::MADCTL), std::span(&madctl, 1), 0); + // Set COLMOD (color depth) + write_command_(static_cast(Command::COLMOD), std::span(&colmod, 1), 0); + return true; } - /** - * @brief Get the display controller ID - * @return ID string for ST7123 - */ static constexpr const char *id() { return "ST7123"; } protected: From 899045ac1616f5406458b048440a1ec790974d1f Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 18 Dec 2025 12:40:13 -0600 Subject: [PATCH 4/4] minor update --- components/display_drivers/include/st7123.hpp | 5 ++--- components/m5stack-tab5/src/video.cpp | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/components/display_drivers/include/st7123.hpp b/components/display_drivers/include/st7123.hpp index 4358537d7..b1ed6ea90 100644 --- a/components/display_drivers/include/st7123.hpp +++ b/components/display_drivers/include/st7123.hpp @@ -1,11 +1,11 @@ - #pragma once -#include "display_drivers.hpp" #include #include #include +#include "display_drivers.hpp" + namespace espp { class St7123 { @@ -32,7 +32,6 @@ class St7123 { RAMRD = 0x2E, MADCTL = 0x36, COLMOD = 0x3A, - // ... add more as needed ... }; static bool initialize(const display_drivers::Config &config) { diff --git a/components/m5stack-tab5/src/video.cpp b/components/m5stack-tab5/src/video.cpp index 06709e08f..872c73b0a 100644 --- a/components/m5stack-tab5/src/video.cpp +++ b/components/m5stack-tab5/src/video.cpp @@ -174,8 +174,6 @@ bool M5StackTab5::initialize_lcd() { } } - using namespace std::placeholders; - espp::display_drivers::Config display_config{ .write_command = std::bind_front(&M5StackTab5::dsi_write_command, this), .read_command = std::bind_front(&M5StackTab5::dsi_read_command, this),