From 6fa51dad15c96e2c342417c8cd8337855be71168 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 03:33:34 +0000 Subject: [PATCH 01/10] Initial plan From 6fe093564d5f433c13730b70247cb6c686f91a77 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 03:49:24 +0000 Subject: [PATCH 02/10] Add ST7123 integrated touch support to M5Stack Tab5 Agent-Logs-Url: https://github.com/esp-cpp/espp/sessions/3ad85902-651b-43d5-8f1d-d4c119604286 Co-authored-by: finger563 <213467+finger563@users.noreply.github.com> --- .github/workflows/upload_components.yml | 1 + components/m5stack-tab5/CMakeLists.txt | 2 +- components/m5stack-tab5/idf_component.yml | 1 + .../m5stack-tab5/include/m5stack-tab5.hpp | 9 +- components/m5stack-tab5/src/touchpad.cpp | 103 +++++++---- components/st7123touch/CMakeLists.txt | 4 + components/st7123touch/README.md | 59 +++++++ components/st7123touch/idf_component.yml | 22 +++ .../st7123touch/include/st7123touch.hpp | 164 ++++++++++++++++++ doc/Doxyfile | 1 + doc/en/input/index.rst | 1 + doc/en/input/st7123touch.rst | 24 +++ 12 files changed, 355 insertions(+), 36 deletions(-) create mode 100644 components/st7123touch/CMakeLists.txt create mode 100644 components/st7123touch/README.md create mode 100644 components/st7123touch/idf_component.yml create mode 100644 components/st7123touch/include/st7123touch.hpp create mode 100644 doc/en/input/st7123touch.rst diff --git a/.github/workflows/upload_components.yml b/.github/workflows/upload_components.yml index 7f2ef95a0..85cc2818c 100755 --- a/.github/workflows/upload_components.yml +++ b/.github/workflows/upload_components.yml @@ -113,6 +113,7 @@ jobs: components/serialization components/socket components/st25dv + components/st7123touch components/state_machine components/t_keyboard components/t-deck diff --git a/components/m5stack-tab5/CMakeLists.txt b/components/m5stack-tab5/CMakeLists.txt index 20e5c74f3..09ff66bc8 100644 --- a/components/m5stack-tab5/CMakeLists.txt +++ b/components/m5stack-tab5/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES driver esp_lcd fatfs base_component bmi270 codec display display_drivers gt911 i2c ina226 input_drivers interrupt pi4ioe5v rx8130ce task + REQUIRES driver esp_lcd fatfs base_component bmi270 codec display display_drivers gt911 i2c ina226 input_drivers interrupt pi4ioe5v rx8130ce st7123touch task REQUIRED_IDF_TARGETS "esp32p4" ) diff --git a/components/m5stack-tab5/idf_component.yml b/components/m5stack-tab5/idf_component.yml index bef038214..e0c303167 100644 --- a/components/m5stack-tab5/idf_component.yml +++ b/components/m5stack-tab5/idf_component.yml @@ -28,6 +28,7 @@ dependencies: espp/interrupt: ">=1.0" espp/pi4ioe5v: ">=1.0" espp/rx8130ce: ">=1.0" + espp/st7123touch: ">=1.0" espp/task: ">=1.0" targets: - esp32p4 diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp index 06567fbc5..c7fea3bd3 100644 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -33,6 +33,7 @@ #include "es8388.hpp" #include "gt911.hpp" #include "i2c.hpp" +#include "st7123touch.hpp" #include "ili9881.hpp" #include "ina226.hpp" #include "interrupt.hpp" @@ -104,9 +105,12 @@ class M5StackTab5 : public BaseComponent { } } - /// Alias for the GT911 touch controller used by the Tab5 + /// Alias for the GT911 touch controller used by the Tab5 (ILI9881 variant) using TouchDriver = espp::Gt911; + /// Alias for the ST7123 integrated touch controller (ST7123 variant) + using St7123TouchDriver = espp::St7123Touch; + /// Alias for the touchpad data used by the Tab5 touchpad using TouchpadData = espp::TouchpadData; @@ -678,7 +682,8 @@ class M5StackTab5 : public BaseComponent { .stack_size_bytes = CONFIG_M5STACK_TAB5_INTERRUPT_STACK_SIZE}}}; // Component instances - std::shared_ptr touch_driver_; + std::shared_ptr touch_driver_; ///< GT911 touch driver (ILI9881 variant) + std::shared_ptr st7123_touch_driver_; ///< ST7123 integrated touch (ST7123 variant) std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; TouchpadData touchpad_data_; diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp index 3c51469b2..51d858fb4 100644 --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -3,33 +3,57 @@ namespace espp { bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { - if (touch_driver_) { + if (touch_driver_ || st7123_touch_driver_) { logger_.warn("Touch driver already initialized"); return true; } - logger_.info("Initializing multi-touch controller"); + logger_.info("Initializing touch controller"); touch_callback_ = callback; // add touch interrupt interrupts_.add_interrupt(touch_interrupt_pin_); - // Reset touch controller via expander if available - touch_reset(true); + // Determine which touch controller is present based on the detected display. + // If the LCD has already been initialised, reuse the cached result; otherwise + // run the I2C probe now. + auto controller = display_controller_; + if (controller == DisplayController::UNKNOWN) { + controller = detect_display_controller(); + } + using namespace std::chrono_literals; - std::this_thread::sleep_for(10ms); - touch_reset(false); - std::this_thread::sleep_for(50ms); - - // Create touch driver instance - touch_driver_ = std::make_shared( - TouchDriver::Config{.write = std::bind_front(&I2c::write, &internal_i2c_), - .read = std::bind_front(&I2c::read, &internal_i2c_), - .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 address - .log_level = espp::Logger::Verbosity::WARN}); - - // Create touchpad input wrapper + + if (controller == DisplayController::ST7123) { + // The ST7123 is a TDDI chip: its touch engine is enabled by LCD_RST, which + // was already pulsed during initialize_lcd(). Toggling TP_RST here is + // harmful — on some boards it takes the touch I2C endpoint offline. + logger_.info("ST7123 variant detected — using integrated touch controller (skipping TP_RST)"); + + st7123_touch_driver_ = std::make_shared(St7123TouchDriver::Config{ + .write = std::bind_front(&I2c::write, &internal_i2c_), + .read = std::bind_front(&I2c::read, &internal_i2c_), + .address = St7123TouchDriver::DEFAULT_ADDRESS, + .log_level = espp::Logger::Verbosity::WARN}); + } else { + // ILI9881 (and UNKNOWN fallback) use a standalone GT911 touch controller + // that requires a hardware reset via the IO expander before being used. + logger_.info("ILI9881/default variant — using GT911 touch controller"); + + touch_reset(true); + std::this_thread::sleep_for(10ms); + touch_reset(false); + std::this_thread::sleep_for(50ms); + + touch_driver_ = std::make_shared( + TouchDriver::Config{.write = std::bind_front(&I2c::write, &internal_i2c_), + .read = std::bind_front(&I2c::read, &internal_i2c_), + .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 + .log_level = espp::Logger::Verbosity::WARN}); + } + + // Create touchpad input wrapper (identical for both drivers) touchpad_input_ = std::make_shared( TouchpadInput::Config{.touchpad_read = std::bind_front(&M5StackTab5::touchpad_read, this), .swap_xy = false, @@ -43,27 +67,40 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { bool M5StackTab5::update_touch() { logger_.debug("Updating touch data"); - if (!touch_driver_) { - logger_.error("Touch driver not initialized"); - return false; - } - // get the latest data from the device std::error_code ec; - bool new_data = touch_driver_->update(ec); - if (ec) { - logger_.error("could not update touch_driver: {}", ec.message()); - std::lock_guard lock(touchpad_data_mutex_); - touchpad_data_ = {}; - return false; - } - if (!new_data) { + TouchpadData temp_data; + bool new_data = false; + + if (st7123_touch_driver_) { + new_data = st7123_touch_driver_->update(ec); + if (ec) { + logger_.error("could not update ST7123 touch driver: {}", ec.message()); + std::lock_guard lock(touchpad_data_mutex_); + touchpad_data_ = {}; + return false; + } + if (!new_data) + return false; + st7123_touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); + temp_data.btn_state = st7123_touch_driver_->get_home_button_state(); + } else if (touch_driver_) { + new_data = touch_driver_->update(ec); + if (ec) { + logger_.error("could not update touch driver: {}", ec.message()); + std::lock_guard lock(touchpad_data_mutex_); + touchpad_data_ = {}; + return false; + } + if (!new_data) + return false; + touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); + temp_data.btn_state = touch_driver_->get_home_button_state(); + } else { + logger_.error("No touch driver initialized"); return false; } - // get the latest data from the touchpad - TouchpadData temp_data; - touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); - temp_data.btn_state = touch_driver_->get_home_button_state(); + // update the touchpad data std::lock_guard lock(touchpad_data_mutex_); touchpad_data_ = temp_data; diff --git a/components/st7123touch/CMakeLists.txt b/components/st7123touch/CMakeLists.txt new file mode 100644 index 000000000..d43ade275 --- /dev/null +++ b/components/st7123touch/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + INCLUDE_DIRS "include" + REQUIRES "base_peripheral" + ) diff --git a/components/st7123touch/README.md b/components/st7123touch/README.md new file mode 100644 index 000000000..e45fb3e82 --- /dev/null +++ b/components/st7123touch/README.md @@ -0,0 +1,59 @@ +# ST7123 Touch Component + +Driver for the touch controller integrated into the **Sitronix ST7123** TDDI +(Touch and Display Driver Integration) chip. + +## Overview + +The ST7123 combines a MIPI-DSI display driver and a capacitive multi-touch +controller in a single IC. This component exposes the touch side only; the +display initialization sequence is handled by the `display_drivers` component. + +Touch data is retrieved over I2C at the chip's address (default **0x55**) using +the following register map: + +| Register | Width | Description | +|----------|-------|-------------| +| `0x0009` | 1 B | Max simultaneous touch count (firmware configured) | +| `0x0010` | 1 B | Advanced info — bit 3 = `with_coord` flag | +| `0x0014` | 7 B × N | Touch coordinate reports (N = max touches) | + +Each 7-byte touch report packs: + +``` +Byte 0: [valid(7)] [reserved(6)] [x_h(5:0)] +Byte 1: x_l +Byte 2: y_h +Byte 3: y_l +Byte 4: contact area +Byte 5: intensity +Byte 6: reserved +``` + +### Important: TP_RST must NOT be toggled for ST7123 + +The ST7123's touch engine is enabled by the **LCD_RST** pulse that is issued +during display initialization. Toggling a separate `TP_RST` line (as used by +standalone controllers like the GT911) has no effect and, on some boards +(e.g. M5Stack Tab5), will take the touch I2C endpoint offline. + +## Usage + +```cpp +#include "st7123touch.hpp" + +espp::St7123Touch touch({ + .write = std::bind_front(&espp::I2c::write, &i2c), + .read = std::bind_front(&espp::I2c::read, &i2c), +}); + +std::error_code ec; +touch.update(ec); +uint8_t num = 0; +uint16_t x = 0, y = 0; +touch.get_touch_point(&num, &x, &y); +``` + +## Dependencies + +- `espp/base_peripheral` diff --git a/components/st7123touch/idf_component.yml b/components/st7123touch/idf_component.yml new file mode 100644 index 000000000..bb51d9084 --- /dev/null +++ b/components/st7123touch/idf_component.yml @@ -0,0 +1,22 @@ +## IDF Component Manager Manifest File +license: "MIT" +description: "ST7123 TDDI integrated touch controller driver component for ESP-IDF" +url: "https://github.com/esp-cpp/espp/tree/main/components/st7123touch" +repository: "git://github.com/esp-cpp/espp.git" +maintainers: + - William Emfinger +documentation: "https://esp-cpp.github.io/espp/input/st7123touch.html" +tags: + - cpp + - Component + - ST7123 + - Touch + - Touchscreen + - Input + - I2C + - Peripheral + - TDDI +dependencies: + idf: + version: '>=5.0' + espp/base_peripheral: '>=1.0' diff --git a/components/st7123touch/include/st7123touch.hpp b/components/st7123touch/include/st7123touch.hpp new file mode 100644 index 000000000..bc6fff7d0 --- /dev/null +++ b/components/st7123touch/include/st7123touch.hpp @@ -0,0 +1,164 @@ +#pragma once + +#include +#include + +#include "base_peripheral.hpp" + +namespace espp { +/// @brief Driver for the ST7123 integrated touch controller +/// +/// The ST7123 is a TDDI (Touch and Display Driver Integration) chip that +/// includes both a MIPI-DSI display driver and a capacitive multi-touch +/// controller. The touch data is accessed via I2C at the chip's address +/// (default 0x55). +/// +/// @note The ST7123's touch engine is gated by the LCD reset (LCD_RST) line, +/// NOT the TP_RST line used by standalone touch controllers such as the +/// GT911. When used in a system that has a separate TP_RST signal +/// (e.g. M5Stack Tab5), do NOT toggle TP_RST for this chip — doing so +/// may knock the touch I2C endpoint offline. +/// +/// Touch data reading sequence (based on ST7123 TDDI Interface Protocol): +/// 1. Read 1 byte from register 0x0010 (advanced info). Bit 3 = with_coord. +/// 2. If with_coord is set: +/// a. Read 1 byte from register 0x0009 (max touch count). +/// b. Read (max_touches × 7) bytes from register 0x0014 (touch reports). +/// c. For each 7-byte report: bit 7 of byte[0] = valid flag, +/// x = ((byte[0] & 0x3F) << 8) | byte[1], +/// y = (byte[2] << 8) | byte[3]. +/// +/// \section st7123touch_ex1 Example +/// (No standalone example; used internally by the M5Stack Tab5 BSP component.) +class St7123Touch : public BasePeripheral { +public: + /// Default I2C address for the ST7123 touch interface + static constexpr uint8_t DEFAULT_ADDRESS = 0x55; + + /// @brief Configuration for the St7123Touch driver + struct Config { + BasePeripheral::write_fn write; ///< Function for writing to the ST7123 + BasePeripheral::read_fn read; ///< Function for reading from the ST7123 + uint8_t address = DEFAULT_ADDRESS; ///< I2C address of the chip + espp::Logger::Verbosity log_level{ + espp::Logger::Verbosity::WARN}; ///< Log verbosity for the driver + }; + + /// @brief Constructor for the St7123Touch driver + /// @param config The configuration for the driver + explicit St7123Touch(const Config &config) + : BasePeripheral({.address = config.address, .write = config.write, .read = config.read}, + "St7123Touch", config.log_level) {} + + /// @brief Update the touch state by reading from the ST7123 over I2C + /// @param ec Error code to set if an I2C error occurs + /// @return True when the read succeeded (regardless of whether a finger is + /// actually touching), false on I2C error + bool update(std::error_code &ec) { + std::lock_guard lock(base_mutex_); + + // Read advanced info byte: bit 3 (with_coord) indicates coordinate data + uint8_t adv_info = 0; + read_many_from_register(static_cast(Registers::ADV_INFO), &adv_info, 1, ec); + if (ec) + return false; + + if (!(adv_info & ADV_INFO_WITH_COORD)) { + // No coordinate data in this interrupt — clear touch state so LVGL sees + // the finger as lifted. + num_touch_points_ = 0; + x_ = 0; + y_ = 0; + return true; + } + + // Read the firmware-configured maximum number of simultaneous touches + uint8_t max_touches = 0; + read_many_from_register(static_cast(Registers::MAX_TOUCHES), &max_touches, 1, ec); + if (ec) + return false; + + if (max_touches == 0) { + num_touch_points_ = 0; + x_ = 0; + y_ = 0; + return true; + } + + // Clamp to our local buffer size + if (max_touches > MAX_TOUCH_POINTS) { + max_touches = MAX_TOUCH_POINTS; + } + + // Read all touch reports in one transaction + uint8_t data[MAX_TOUCH_POINTS * TOUCH_REPORT_SIZE] = {}; + read_many_from_register(static_cast(Registers::REPORT_COORD_0), data, + TOUCH_REPORT_SIZE * max_touches, ec); + if (ec) + return false; + + // Parse reports; record first valid point for single-touch consumers + uint8_t count = 0; + uint16_t first_x = 0; + uint16_t first_y = 0; + for (int i = 0; i < max_touches; i++) { + const uint8_t *p = &data[i * TOUCH_REPORT_SIZE]; + const bool valid = (p[0] & 0x80) != 0; + if (!valid) + continue; + const uint16_t px = static_cast((p[0] & 0x3F) << 8) | p[1]; + const uint16_t py = static_cast(p[2] << 8) | p[3]; + if (count == 0) { + first_x = px; + first_y = py; + } + count++; + } + + num_touch_points_ = count; + x_ = first_x; + y_ = first_y; + logger_.debug("Touch: {} point(s) at ({}, {})", count, first_x, first_y); + return true; + } + + /// @brief Get the number of active touch points + /// @return Touch point count as of the last update() call + uint8_t get_num_touch_points() const { return num_touch_points_; } + + /// @brief Get the primary touch point coordinates + /// @param num_touch_points Output: number of active touch points + /// @param x Output: X coordinate of the first active touch point + /// @param y Output: Y coordinate of the first active touch point + /// @note The values are cached from the last update() call. + void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { + *num_touch_points = get_num_touch_points(); + if (*num_touch_points != 0) { + *x = x_; + *y = y_; + } + } + + /// @brief Get the home-button state + /// @return Always false — the ST7123 does not expose a home button via I2C + bool get_home_button_state() const { return false; } + +protected: + /// Maximum number of simultaneous touches the driver will parse + static constexpr int MAX_TOUCH_POINTS = 10; + /// Bytes per touch report in the coordinate register block + static constexpr int TOUCH_REPORT_SIZE = 7; + /// Bit mask for the with_coord flag in the advanced-info register + static constexpr uint8_t ADV_INFO_WITH_COORD = (1 << 3); + + enum class Registers : uint16_t { + ADV_INFO = 0x0010, ///< Advanced info byte; bit 3 = with_coord + MAX_TOUCHES = 0x0009, ///< Firmware-configured maximum touch count + REPORT_COORD_0 = 0x0014, ///< First touch coordinate report (7 bytes each) + }; + + std::atomic num_touch_points_{0}; + std::atomic x_{0}; + std::atomic y_{0}; +}; // class St7123Touch +} // namespace espp diff --git a/doc/Doxyfile b/doc/Doxyfile index 404591115..ba7f8da70 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -323,6 +323,7 @@ INPUT = \ $(PROJECT_PATH)/components/socket/include/udp_socket.hpp \ $(PROJECT_PATH)/components/socket/include/tcp_socket.hpp \ $(PROJECT_PATH)/components/st25dv/include/st25dv.hpp \ + $(PROJECT_PATH)/components/st7123touch/include/st7123touch.hpp \ $(PROJECT_PATH)/components/state_machine/include/deep_history_state.hpp \ $(PROJECT_PATH)/components/state_machine/include/shallow_history_state.hpp \ $(PROJECT_PATH)/components/state_machine/include/state_base.hpp \ diff --git a/doc/en/input/index.rst b/doc/en/input/index.rst index acbeefa9b..4b86e254e 100644 --- a/doc/en/input/index.rst +++ b/doc/en/input/index.rst @@ -8,6 +8,7 @@ Input APIs cst816 ft5x06 gt911 + st7123touch tt21100 t_keyboard encoder_input diff --git a/doc/en/input/st7123touch.rst b/doc/en/input/st7123touch.rst new file mode 100644 index 000000000..11b76fd1a --- /dev/null +++ b/doc/en/input/st7123touch.rst @@ -0,0 +1,24 @@ +ST7123 Touch Controller +*********************** + +The `St7123Touch` class provides an interface to the capacitive touch controller +integrated in the **Sitronix ST7123** TDDI (Touch and Display Driver Integration) +chip. + +The ST7123 combines a MIPI-DSI display driver and a multi-touch capacitive +controller in a single IC. This driver accesses the touch side over I2C +(default address **0x55**). + +.. note:: + + The ST7123's touch engine is enabled by the **LCD_RST** pulse issued during + display initialization — do *not* toggle the ``TP_RST`` line used by + standalone controllers such as the GT911, as this can take the touch I2C + endpoint offline. + +.. ---------------------------- API Reference ---------------------------------- + +API Reference +------------- + +.. include-build-file:: inc/st7123touch.inc From 0b05c2ce6a9736c96522da5ab17b9d51c9148b7c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 11:46:10 +0000 Subject: [PATCH 03/10] Add st7123touch example, CI, docs, and C++23 concept-based touch driver interface Agent-Logs-Url: https://github.com/esp-cpp/espp/sessions/7358df43-1e1a-4119-bdc3-37852bfec05e Co-authored-by: finger563 <213467+finger563@users.noreply.github.com> --- .github/workflows/build.yml | 2 + .../m5stack-tab5/include/m5stack-tab5.hpp | 3 +- components/m5stack-tab5/src/touchpad.cpp | 54 +++++-------- components/st7123touch/example/CMakeLists.txt | 22 +++++ components/st7123touch/example/README.md | 52 ++++++++++++ .../st7123touch/example/main/CMakeLists.txt | 2 + .../example/main/Kconfig.projbuild | 17 ++++ .../example/main/st7123touch_example.cpp | 80 +++++++++++++++++++ .../st7123touch/example/sdkconfig.defaults | 6 ++ .../st7123touch/include/st7123touch.hpp | 80 ++++++++++++++++++- doc/Doxyfile | 1 + doc/en/input/st7123touch.rst | 16 ++++ doc/en/input/st7123touch_example.md | 2 + 13 files changed, 301 insertions(+), 36 deletions(-) create mode 100644 components/st7123touch/example/CMakeLists.txt create mode 100644 components/st7123touch/example/README.md create mode 100644 components/st7123touch/example/main/CMakeLists.txt create mode 100644 components/st7123touch/example/main/Kconfig.projbuild create mode 100644 components/st7123touch/example/main/st7123touch_example.cpp create mode 100644 components/st7123touch/example/sdkconfig.defaults create mode 100644 doc/en/input/st7123touch_example.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 82595040e..c5fc60a1e 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -185,6 +185,8 @@ jobs: target: esp32 - path: 'components/st25dv/example' target: esp32s3 + - path: 'components/st7123touch/example' + target: esp32s3 - path: 'components/state_machine/example' target: esp32 - path: 'components/t-deck/example' diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp index c7fea3bd3..3e4a356c2 100644 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -682,8 +682,7 @@ class M5StackTab5 : public BaseComponent { .stack_size_bytes = CONFIG_M5STACK_TAB5_INTERRUPT_STACK_SIZE}}}; // Component instances - std::shared_ptr touch_driver_; ///< GT911 touch driver (ILI9881 variant) - std::shared_ptr st7123_touch_driver_; ///< ST7123 integrated touch (ST7123 variant) + std::shared_ptr touch_driver_; ///< Concept-erased touch driver (GT911 or ST7123) std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; TouchpadData touchpad_data_; diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp index 51d858fb4..08d88eb87 100644 --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -3,7 +3,7 @@ namespace espp { bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { - if (touch_driver_ || st7123_touch_driver_) { + if (touch_driver_) { logger_.warn("Touch driver already initialized"); return true; } @@ -31,11 +31,12 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { // harmful — on some boards it takes the touch I2C endpoint offline. logger_.info("ST7123 variant detected — using integrated touch controller (skipping TP_RST)"); - st7123_touch_driver_ = std::make_shared(St7123TouchDriver::Config{ + auto driver = std::make_shared(St7123TouchDriver::Config{ .write = std::bind_front(&I2c::write, &internal_i2c_), .read = std::bind_front(&I2c::read, &internal_i2c_), .address = St7123TouchDriver::DEFAULT_ADDRESS, .log_level = espp::Logger::Verbosity::WARN}); + touch_driver_ = espp::make_touch_driver(std::move(driver)); } else { // ILI9881 (and UNKNOWN fallback) use a standalone GT911 touch controller // that requires a hardware reset via the IO expander before being used. @@ -46,11 +47,12 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { touch_reset(false); std::this_thread::sleep_for(50ms); - touch_driver_ = std::make_shared( + auto driver = std::make_shared( TouchDriver::Config{.write = std::bind_front(&I2c::write, &internal_i2c_), .read = std::bind_front(&I2c::read, &internal_i2c_), .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 .log_level = espp::Logger::Verbosity::WARN}); + touch_driver_ = espp::make_touch_driver(std::move(driver)); } // Create touchpad input wrapper (identical for both drivers) @@ -68,40 +70,26 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { bool M5StackTab5::update_touch() { logger_.debug("Updating touch data"); - std::error_code ec; - TouchpadData temp_data; - bool new_data = false; - - if (st7123_touch_driver_) { - new_data = st7123_touch_driver_->update(ec); - if (ec) { - logger_.error("could not update ST7123 touch driver: {}", ec.message()); - std::lock_guard lock(touchpad_data_mutex_); - touchpad_data_ = {}; - return false; - } - if (!new_data) - return false; - st7123_touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); - temp_data.btn_state = st7123_touch_driver_->get_home_button_state(); - } else if (touch_driver_) { - new_data = touch_driver_->update(ec); - if (ec) { - logger_.error("could not update touch driver: {}", ec.message()); - std::lock_guard lock(touchpad_data_mutex_); - touchpad_data_ = {}; - return false; - } - if (!new_data) - return false; - touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); - temp_data.btn_state = touch_driver_->get_home_button_state(); - } else { + if (!touch_driver_) { logger_.error("No touch driver initialized"); return false; } - // update the touchpad data + std::error_code ec; + bool new_data = touch_driver_->update(ec); + if (ec) { + logger_.error("could not update touch driver: {}", ec.message()); + std::lock_guard lock(touchpad_data_mutex_); + touchpad_data_ = {}; + return false; + } + if (!new_data) + return false; + + TouchpadData temp_data; + touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); + temp_data.btn_state = touch_driver_->get_home_button_state(); + std::lock_guard lock(touchpad_data_mutex_); touchpad_data_ = temp_data; return true; diff --git a/components/st7123touch/example/CMakeLists.txt b/components/st7123touch/example/CMakeLists.txt new file mode 100644 index 000000000..dd0e96558 --- /dev/null +++ b/components/st7123touch/example/CMakeLists.txt @@ -0,0 +1,22 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.20) + +set(ENV{IDF_COMPONENT_MANAGER} "0") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# add the component directories that we want to use +set(EXTRA_COMPONENT_DIRS + "../../../components/" +) + +set( + COMPONENTS + "main esptool_py task st7123touch i2c" + CACHE STRING + "List of components to include" + ) + +project(st7123touch_example) + +set(CMAKE_CXX_STANDARD 23) diff --git a/components/st7123touch/example/README.md b/components/st7123touch/example/README.md new file mode 100644 index 000000000..572e3b4da --- /dev/null +++ b/components/st7123touch/example/README.md @@ -0,0 +1,52 @@ +# ST7123Touch Example + +This example shows how to use the ST7123 integrated touch controller driver. +It demonstrates: + +- Probing the I2C bus for the ST7123 touch interface (default address 0x55) +- Creating an `espp::St7123Touch` instance +- Wrapping it as `std::shared_ptr` using the + `espp::make_touch_driver` helper and the `espp::TouchDriverConcept` +- Polling for touch events via the type-erased interface in a background task + +## How to use example + +### Hardware Required + +Any ESP32 board connected to an ST7123 TDDI panel via I2C. + +> **Note:** The ST7123's touch engine is enabled by the **LCD_RST** pulse that +> is issued during display initialization. Do **not** toggle a separate +> `TP_RST` line — doing so may take the touch I2C endpoint offline. + +### Configure + +``` +idf.py menuconfig +``` + +Set the `I2C SDA` and `I2C SCL` GPIO numbers for your board under +**Example Configuration**. + +### Build and Flash + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type `Ctrl-]`.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to +build projects. + +## Example Output + +``` +Starting st7123touch example +ST7123 touch probe: 1 + address: 0x55 +num_touch_points: 1, x: 320, y: 240 +num_touch_points: 0, x: 0, y: 0 +``` diff --git a/components/st7123touch/example/main/CMakeLists.txt b/components/st7123touch/example/main/CMakeLists.txt new file mode 100644 index 000000000..a941e22ba --- /dev/null +++ b/components/st7123touch/example/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS ".") diff --git a/components/st7123touch/example/main/Kconfig.projbuild b/components/st7123touch/example/main/Kconfig.projbuild new file mode 100644 index 000000000..177fea534 --- /dev/null +++ b/components/st7123touch/example/main/Kconfig.projbuild @@ -0,0 +1,17 @@ +menu "Example Configuration" + + config EXAMPLE_I2C_SCL_GPIO + int "SCL GPIO Num" + range 0 56 + default 22 + help + GPIO number for I2C Master clock line. + + config EXAMPLE_I2C_SDA_GPIO + int "SDA GPIO Num" + range 0 56 + default 21 + help + GPIO number for I2C Master data line. + +endmenu diff --git a/components/st7123touch/example/main/st7123touch_example.cpp b/components/st7123touch/example/main/st7123touch_example.cpp new file mode 100644 index 000000000..8ec57d83d --- /dev/null +++ b/components/st7123touch/example/main/st7123touch_example.cpp @@ -0,0 +1,80 @@ +#include +#include + +#include "i2c.hpp" +#include "st7123touch.hpp" +#include "task.hpp" + +using namespace std::chrono_literals; + +extern "C" void app_main(void) { + { + fmt::print("Starting st7123touch example\n"); + //! [st7123touch example] + // make the I2C that we'll use to communicate + espp::I2c i2c({ + .port = I2C_NUM_0, + .sda_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SDA_GPIO, + .scl_io_num = (gpio_num_t)CONFIG_EXAMPLE_I2C_SCL_GPIO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .timeout_ms = 100, + .clk_speed = 400 * 1000, + }); + + bool has_st7123 = i2c.probe_device(espp::St7123Touch::DEFAULT_ADDRESS); + fmt::print("ST7123 touch probe: {}\n", has_st7123); + fmt::print(" address: {:#02x}\n", espp::St7123Touch::DEFAULT_ADDRESS); + + // Create the ST7123 touch driver + espp::St7123Touch touch({ + .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3), + .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3), + .log_level = espp::Logger::Verbosity::WARN, + }); + + // Wrap the driver in a concept-erased ITouchDriver pointer. + // Any type satisfying espp::TouchDriverConcept can be wrapped this way. + auto driver = espp::make_touch_driver(std::make_shared(std::move(touch))); + + // Poll for touch events using the type-erased interface + auto task_fn = [&driver](std::mutex &m, std::condition_variable &cv) { + std::error_code ec; + bool new_data = driver->update(ec); + if (ec) { + fmt::print("Could not update state\n"); + return false; + } + if (!new_data) { + return false; // don't stop the task + } + uint8_t num_touch_points = 0; + uint16_t x = 0, y = 0; + driver->get_touch_point(&num_touch_points, &x, &y); + fmt::print("num_touch_points: {}, x: {}, y: {}\n", num_touch_points, x, y); + // NOTE: sleeping in this way allows the sleep to exit early when the + // task is being stopped / destroyed + { + std::unique_lock lk(m); + cv.wait_for(lk, 50ms); + } + return false; // don't stop the task + }; + auto task = espp::Task({.callback = task_fn, + .task_config = {.name = "St7123Touch Task"}, + .log_level = espp::Logger::Verbosity::WARN}); + task.start(); + //! [st7123touch example] + while (true) { + std::this_thread::sleep_for(100ms); + } + } + + fmt::print("St7123Touch example complete!\n"); + + while (true) { + std::this_thread::sleep_for(1s); + } +} diff --git a/components/st7123touch/example/sdkconfig.defaults b/components/st7123touch/example/sdkconfig.defaults new file mode 100644 index 000000000..482d3bb8c --- /dev/null +++ b/components/st7123touch/example/sdkconfig.defaults @@ -0,0 +1,6 @@ +CONFIG_IDF_TARGET="esp32s3" + +# Common ESP-related +# +CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096 +CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192 diff --git a/components/st7123touch/include/st7123touch.hpp b/components/st7123touch/include/st7123touch.hpp index bc6fff7d0..b0c6b6d6d 100644 --- a/components/st7123touch/include/st7123touch.hpp +++ b/components/st7123touch/include/st7123touch.hpp @@ -1,11 +1,89 @@ #pragma once #include +#include #include +#include +#include #include "base_peripheral.hpp" namespace espp { + +/// @brief Concept satisfied by any touch-controller driver that exposes the +/// standard espp touch-driver interface. +/// +/// A type T satisfies TouchDriverConcept if it provides: +/// - `bool T::update(std::error_code &)` — read new touch data from hardware +/// - `void T::get_touch_point(uint8_t *, uint16_t *, uint16_t *) const` — retrieve coordinates +/// - `bool T::get_home_button_state() const` — return home-button state +/// +/// Both `espp::Gt911` and `espp::St7123Touch` satisfy this concept. +template +concept TouchDriverConcept = requires(T &t, std::error_code &ec, uint8_t *n, uint16_t *x, + uint16_t *y) { + { t.update(ec) } -> std::convertible_to; + { t.get_touch_point(n, x, y) }; + { t.get_home_button_state() } -> std::convertible_to; +}; + +/// @brief Abstract type-erased interface for a touch driver. +/// +/// Used together with `TouchDriverAdapter` to allow the M5Stack Tab5 BSP +/// (and any other consumer) to store a single `std::shared_ptr` +/// regardless of which concrete driver (Gt911, St7123Touch, …) is in use at +/// runtime. +struct ITouchDriver { + virtual ~ITouchDriver() = default; + + /// @brief Read new touch data from the hardware. + /// @param ec Set on I2C error; cleared on success. + /// @return True when new coordinate data is available. + virtual bool update(std::error_code &ec) = 0; + + /// @brief Retrieve the primary touch point. + /// @param num_touch_points Output: number of active touch points. + /// @param x Output: X coordinate. + /// @param y Output: Y coordinate. + virtual void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const = 0; + + /// @brief Return the home-button pressed state. + virtual bool get_home_button_state() const = 0; +}; + +/// @brief Concept-constrained adapter that wraps any concrete touch driver +/// satisfying `TouchDriverConcept` behind the `ITouchDriver` interface. +/// +/// Usage: +/// @code +/// auto driver = std::make_shared(...); +/// std::shared_ptr touch = +/// std::make_shared>(driver); +/// @endcode +template +struct TouchDriverAdapter : ITouchDriver { + /// Underlying concrete driver + std::shared_ptr driver; + + explicit TouchDriverAdapter(std::shared_ptr d) : driver(std::move(d)) {} + + bool update(std::error_code &ec) override { return driver->update(ec); } + + void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const override { + driver->get_touch_point(num_touch_points, x, y); + } + + bool get_home_button_state() const override { return driver->get_home_button_state(); } +}; + +/// @brief Convenience factory: wrap a shared_ptr to a concrete touch driver in +/// a `TouchDriverAdapter` and return it as `std::shared_ptr`. +/// @tparam T Concrete driver type — must satisfy `TouchDriverConcept`. +template +std::shared_ptr make_touch_driver(std::shared_ptr driver) { + return std::make_shared>(std::move(driver)); +} + /// @brief Driver for the ST7123 integrated touch controller /// /// The ST7123 is a TDDI (Touch and Display Driver Integration) chip that @@ -29,7 +107,7 @@ namespace espp { /// y = (byte[2] << 8) | byte[3]. /// /// \section st7123touch_ex1 Example -/// (No standalone example; used internally by the M5Stack Tab5 BSP component.) +/// \snippet st7123touch_example.cpp st7123touch example class St7123Touch : public BasePeripheral { public: /// Default I2C address for the ST7123 touch interface diff --git a/doc/Doxyfile b/doc/Doxyfile index ba7f8da70..84855dd65 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -146,6 +146,7 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/seeed-studio-round-display/example/main/seeed_studio_round_display_example.cpp \ $(PROJECT_PATH)/components/socket/example/main/socket_example.cpp \ $(PROJECT_PATH)/components/st25dv/example/main/st25dv_example.cpp \ + $(PROJECT_PATH)/components/st7123touch/example/main/st7123touch_example.cpp \ $(PROJECT_PATH)/components/state_machine/example/main/hfsm_example.cpp \ $(PROJECT_PATH)/components/tabulate/example/main/tabulate_example.cpp \ $(PROJECT_PATH)/components/t-deck/example/main/t_deck_example.cpp \ diff --git a/doc/en/input/st7123touch.rst b/doc/en/input/st7123touch.rst index 11b76fd1a..7dd5f3b70 100644 --- a/doc/en/input/st7123touch.rst +++ b/doc/en/input/st7123touch.rst @@ -16,6 +16,22 @@ controller in a single IC. This driver accesses the touch side over I2C standalone controllers such as the GT911, as this can take the touch I2C endpoint offline. +The driver also provides: + +- ``espp::TouchDriverConcept`` — C++23 concept satisfied by any touch driver + exposing ``update()``, ``get_touch_point()``, and ``get_home_button_state()``. +- ``espp::ITouchDriver`` — abstract type-erased interface backed by the concept. +- ``espp::TouchDriverAdapter`` — concept-constrained adapter wrapping any + concrete driver behind ``ITouchDriver``. +- ``espp::make_touch_driver(driver)`` — convenience factory returning a + ``std::shared_ptr``. + +.. ------------------------------- Example ------------------------------------- + +.. toctree:: + + st7123touch_example + .. ---------------------------- API Reference ---------------------------------- API Reference diff --git a/doc/en/input/st7123touch_example.md b/doc/en/input/st7123touch_example.md new file mode 100644 index 000000000..2370a7250 --- /dev/null +++ b/doc/en/input/st7123touch_example.md @@ -0,0 +1,2 @@ +```{include} ../../../components/st7123touch/example/README.md +``` From 6ea93f1622bff7176c2c5d0c50c766319e29b3e7 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 23 May 2026 22:28:10 -0500 Subject: [PATCH 04/10] improve touch interface rework --- .github/workflows/build.yml | 2 + .github/workflows/upload_components.yml | 1 + components/chsc6x/CMakeLists.txt | 2 +- components/chsc6x/idf_component.yml | 1 + components/chsc6x/include/chsc6x.hpp | 42 ++++--- components/cst816/CMakeLists.txt | 2 +- components/cst816/idf_component.yml | 1 + components/cst816/include/cst816.hpp | 40 ++++--- components/esp-box/CMakeLists.txt | 2 +- components/esp-box/idf_component.yml | 1 + components/esp-box/include/esp-box.hpp | 6 +- components/esp-box/src/touchpad.cpp | 61 ++-------- components/ft5x06/CMakeLists.txt | 2 +- components/ft5x06/README.md | 4 +- .../ft5x06/example/main/ft5x06_example.cpp | 16 ++- components/ft5x06/idf_component.yml | 1 + components/ft5x06/include/ft5x06.hpp | 72 +++++++++--- components/gt911/CMakeLists.txt | 2 +- components/gt911/idf_component.yml | 1 + components/gt911/include/gt911.hpp | 66 ++++++----- components/input_drivers/CMakeLists.txt | 2 +- components/input_drivers/idf_component.yml | 1 + .../input_drivers/include/touchpad_input.hpp | 14 +-- .../m5stack-tab5/example/CMakeLists.txt | 2 + components/touch/CMakeLists.txt | 4 + components/touch/README.md | 13 +++ components/touch/example/CMakeLists.txt | 22 ++++ components/touch/example/README.md | 18 +++ components/touch/example/main/CMakeLists.txt | 2 + .../touch/example/main/touch_example.cpp | 48 ++++++++ components/touch/idf_component.yml | 19 ++++ components/touch/include/touch.hpp | 106 ++++++++++++++++++ components/touch/src/touch.cpp | 1 + components/tt21100/CMakeLists.txt | 2 +- components/tt21100/idf_component.yml | 1 + components/tt21100/include/tt21100.hpp | 62 ++++++---- doc/Doxyfile | 3 +- doc/en/input/ft5x06.rst | 2 + doc/en/input/index.rst | 3 +- doc/en/input/touch.rst | 19 ++++ doc/en/input/touch_example.md | 2 + 41 files changed, 485 insertions(+), 186 deletions(-) create mode 100644 components/touch/CMakeLists.txt create mode 100644 components/touch/README.md create mode 100644 components/touch/example/CMakeLists.txt create mode 100644 components/touch/example/README.md create mode 100644 components/touch/example/main/CMakeLists.txt create mode 100644 components/touch/example/main/touch_example.cpp create mode 100644 components/touch/idf_component.yml create mode 100644 components/touch/include/touch.hpp create mode 100644 components/touch/src/touch.cpp create mode 100644 doc/en/input/touch.rst create mode 100644 doc/en/input/touch_example.md diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c5fc60a1e..f5cc0b092 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -203,6 +203,8 @@ jobs: target: esp32 - path: 'components/timer/example' target: esp32 + - path: 'components/touch/example' + target: esp32 - path: 'components/tla2528/example' target: esp32 - path: 'components/tt21100/example' diff --git a/.github/workflows/upload_components.yml b/.github/workflows/upload_components.yml index 85cc2818c..a59cd3e20 100755 --- a/.github/workflows/upload_components.yml +++ b/.github/workflows/upload_components.yml @@ -122,6 +122,7 @@ jobs: components/task components/thermistor components/timer + components/touch components/tla2528 components/tt21100 components/utils diff --git a/components/chsc6x/CMakeLists.txt b/components/chsc6x/CMakeLists.txt index d43ade275..220bf7d39 100644 --- a/components/chsc6x/CMakeLists.txt +++ b/components/chsc6x/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "base_peripheral" + REQUIRES "base_peripheral" "touch" ) diff --git a/components/chsc6x/idf_component.yml b/components/chsc6x/idf_component.yml index 6090e2b91..c9ae997ff 100644 --- a/components/chsc6x/idf_component.yml +++ b/components/chsc6x/idf_component.yml @@ -19,3 +19,4 @@ dependencies: idf: version: '>=5.0' espp/base_peripheral: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/chsc6x/include/chsc6x.hpp b/components/chsc6x/include/chsc6x.hpp index 864eb2b82..9db1d8968 100644 --- a/components/chsc6x/include/chsc6x.hpp +++ b/components/chsc6x/include/chsc6x.hpp @@ -1,16 +1,16 @@ #pragma once -#include #include #include "base_peripheral.hpp" +#include "touch.hpp" namespace espp { /// @brief Driver for the Chsc6x touch controller /// /// \section chsc6x_ex1 Example /// \snippet chsc6x_example.cpp chsc6x example -class Chsc6x : public BasePeripheral<> { +class Chsc6x : public BasePeripheral<>, public ITouchDevice { public: /// Default address for the CHSC6X chip static constexpr uint8_t DEFAULT_ADDRESS = 0x2E; @@ -34,6 +34,7 @@ class Chsc6x : public BasePeripheral<> { /// @param ec Error code to set if an error occurs /// @return True if the CHSC6X has new data, false otherwise bool update(std::error_code &ec) { + TouchState state{}; static constexpr size_t DATA_LEN = 5; static uint8_t data[DATA_LEN]; read_many_from_register(0, data, DATA_LEN, ec); @@ -42,22 +43,29 @@ class Chsc6x : public BasePeripheral<> { // first byte is non-zero when touched, 3rd byte is x, 5th byte is y if (data[0] == 0) { - x_ = 0; - y_ = 0; - num_touch_points_ = 0; + std::lock_guard lock(base_mutex_); + touch_state_ = state; return true; } - x_ = data[2]; - y_ = data[4]; - num_touch_points_ = 1; - logger_.debug("Touch at ({}, {})", x_, y_); + state.num_touch_points = 1; + state.points[0] = {.x = data[2], .y = data[4]}; + logger_.debug("Touch at ({}, {})", state.points[0].x, state.points[0].y); + std::lock_guard lock(base_mutex_); + touch_state_ = state; return true; } + /// @brief Get the cached touch state. + /// @return The cached touch state as of the last update() call. + TouchState touch_state() const override { + std::lock_guard lock(base_mutex_); + return touch_state_; + } + /// @brief Get the number of touch points /// @return The number of touch points as of the last update /// @note This is a cached value from the last update() call - uint8_t get_num_touch_points() const { return num_touch_points_; } + uint8_t get_num_touch_points() const { return touch_state().num_touch_points; } /// @brief Get the touch point data /// @param num_touch_points The number of touch points as of the last update @@ -65,16 +73,14 @@ class Chsc6x : public BasePeripheral<> { /// @param y The y coordinate of the touch point /// @note This is a cached value from the last update() call void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { - *num_touch_points = get_num_touch_points(); - if (*num_touch_points != 0) { - *x = x_; - *y = y_; - } + auto state = touch_state(); + auto point = state.primary_point(); + *num_touch_points = state.num_touch_points; + *x = point.x; + *y = point.y; } protected: - std::atomic num_touch_points_; - std::atomic x_; - std::atomic y_; + TouchState touch_state_; }; // class Chsc6x } // namespace espp diff --git a/components/cst816/CMakeLists.txt b/components/cst816/CMakeLists.txt index d43ade275..220bf7d39 100644 --- a/components/cst816/CMakeLists.txt +++ b/components/cst816/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "base_peripheral" + REQUIRES "base_peripheral" "touch" ) diff --git a/components/cst816/idf_component.yml b/components/cst816/idf_component.yml index d028ba4bd..729638110 100644 --- a/components/cst816/idf_component.yml +++ b/components/cst816/idf_component.yml @@ -20,3 +20,4 @@ dependencies: idf: version: '>=5.0' espp/base_peripheral: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/cst816/include/cst816.hpp b/components/cst816/include/cst816.hpp index a3ac94e72..11af902c7 100644 --- a/components/cst816/include/cst816.hpp +++ b/components/cst816/include/cst816.hpp @@ -1,9 +1,9 @@ #pragma once -#include #include #include "base_peripheral.hpp" +#include "touch.hpp" namespace espp { /// @brief Driver for the CST816 touch controller @@ -18,7 +18,7 @@ namespace espp { /// /// \section cst816_ex1 Example /// \snippet cst816_example.cpp cst816 example -class Cst816 : public BasePeripheral { +class Cst816 : public BasePeripheral, public ITouchDevice { public: /// Default address for the CST816 chip static constexpr uint8_t DEFAULT_ADDRESS = 0x15; @@ -42,6 +42,7 @@ class Cst816 : public BasePeripheral { /// @param ec Error code to set if an error occurs /// @return True if the CST816 has new data, false otherwise bool update(std::error_code &ec) { + TouchState state{}; bool new_data = false; Data data{}; read_many_from_register(static_cast(Registers::DATA_START), @@ -49,18 +50,26 @@ class Cst816 : public BasePeripheral { if (ec) return false; - num_touch_points_ = data.num; - x_ = (data.x_h << 8) | data.x_l; - y_ = (data.y_h << 8) | data.y_l; - home_button_pressed_ = false; + state.num_touch_points = data.num; + state.points[0] = {.x = static_cast((data.x_h << 8) | data.x_l), + .y = static_cast((data.y_h << 8) | data.y_l)}; new_data = true; + std::lock_guard lock(base_mutex_); + touch_state_ = state; return new_data; } + /// @brief Get the cached touch state. + /// @return The cached touch state as of the last update() call. + TouchState touch_state() const override { + std::lock_guard lock(base_mutex_); + return touch_state_; + } + /// @brief Get the number of touch points /// @return The number of touch points as of the last update /// @note This is a cached value from the last update() call - uint8_t get_num_touch_points() const { return num_touch_points_; } + uint8_t get_num_touch_points() const { return touch_state().num_touch_points; } /// @brief Get the touch point data /// @param num_touch_points The number of touch points as of the last update @@ -68,17 +77,17 @@ class Cst816 : public BasePeripheral { /// @param y The y coordinate of the touch point /// @note This is a cached value from the last update() call void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { - *num_touch_points = get_num_touch_points(); - if (*num_touch_points != 0) { - *x = x_; - *y = y_; - } + auto state = touch_state(); + auto point = state.primary_point(); + *num_touch_points = state.num_touch_points; + *x = point.x; + *y = point.y; } /// @brief Get the home button state /// @return True if the home button is pressed, false otherwise /// @note This is a cached value from the last update() call - bool get_home_button_state() const { return home_button_pressed_; } + bool get_home_button_state() const { return touch_state().btn_state; } protected: static constexpr int MAX_CONTACTS = 1; @@ -98,9 +107,6 @@ class Cst816 : public BasePeripheral { CHIP_ID = 0xA7, }; - std::atomic home_button_pressed_{false}; - std::atomic num_touch_points_; - std::atomic x_; - std::atomic y_; + TouchState touch_state_; }; } // namespace espp diff --git a/components/esp-box/CMakeLists.txt b/components/esp-box/CMakeLists.txt index 09c5f20d8..8d19cc590 100644 --- a/components/esp-box/CMakeLists.txt +++ b/components/esp-box/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES driver esp_driver_i2s base_component codec display display_drivers i2c input_drivers interrupt gt911 task tt21100 icm42607 + REQUIRES driver esp_driver_i2s base_component codec display display_drivers i2c input_drivers interrupt gt911 task touch tt21100 icm42607 REQUIRED_IDF_TARGETS "esp32s3" ) diff --git a/components/esp-box/idf_component.yml b/components/esp-box/idf_component.yml index af876f5d6..c281f874a 100644 --- a/components/esp-box/idf_component.yml +++ b/components/esp-box/idf_component.yml @@ -26,6 +26,7 @@ dependencies: espp/interrupt: '>=1.0' espp/gt911: '>=1.0' espp/task: '>=1.0' + espp/touch: '>=1.0' espp/tt21100: '>=1.0' espp/icm42607: '>=1.0' targets: diff --git a/components/esp-box/include/esp-box.hpp b/components/esp-box/include/esp-box.hpp index 02e8cdece..cd674b24c 100644 --- a/components/esp-box/include/esp-box.hpp +++ b/components/esp-box/include/esp-box.hpp @@ -23,6 +23,7 @@ #include "icm42607.hpp" #include "interrupt.hpp" #include "st7789.hpp" +#include "touch.hpp" #include "touchpad_input.hpp" #include "tt21100.hpp" @@ -359,8 +360,6 @@ class EspBox : public BaseComponent { bool initialize_codec(); bool initialize_i2s(uint32_t default_audio_rate); bool update_touch(); - bool update_gt911(); - bool update_tt21100(); void update_volume_output(); bool audio_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified); void lcd_wait_lines(); @@ -509,8 +508,7 @@ class EspBox : public BaseComponent { button_callback_t mute_button_callback_{nullptr}; // touch - std::shared_ptr gt911_; // only used on ESP32-S3-BOX-3 - std::shared_ptr tt21100_; // only used on ESP32-S3-BOX + std::shared_ptr touch_driver_; std::shared_ptr touchpad_input_; std::recursive_mutex touchpad_data_mutex_; TouchpadData touchpad_data_; diff --git a/components/esp-box/src/touchpad.cpp b/components/esp-box/src/touchpad.cpp index 5817b4394..3bded6704 100644 --- a/components/esp-box/src/touchpad.cpp +++ b/components/esp-box/src/touchpad.cpp @@ -7,7 +7,7 @@ using namespace espp; //////////////////////// bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { - if (gt911_ || tt21100_) { + if (touch_driver_) { logger_.warn("Touch already initialized, not initializing again!"); return false; } @@ -15,7 +15,7 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { switch (box_type_) { case BoxType::BOX3: logger_.info("Initializing GT911"); - gt911_ = std::make_unique(espp::Gt911::Config{ + touch_driver_ = std::make_shared(espp::Gt911::Config{ .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, @@ -24,7 +24,7 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { break; case BoxType::BOX: logger_.info("Initializing TT21100"); - tt21100_ = std::make_unique(espp::Tt21100::Config{ + touch_driver_ = std::make_shared(espp::Tt21100::Config{ .write = std::bind(&espp::I2c::write, &internal_i2c_, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), .read = std::bind(&espp::I2c::read, &internal_i2c_, std::placeholders::_1, @@ -44,43 +44,14 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { return true; } -bool EspBox::update_gt911() { - // ensure the gt911 is initialized - if (!gt911_) { - return false; - } - // get the latest data from the device - std::error_code ec; - bool new_data = gt911_->update(ec); - if (ec) { - logger_.error("could not update gt911: {}\n", ec.message()); - std::lock_guard lock(touchpad_data_mutex_); - touchpad_data_ = {}; - return false; - } - if (!new_data) { - return false; - } - // get the latest data from the touchpad - TouchpadData temp_data; - gt911_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); - temp_data.btn_state = gt911_->get_home_button_state(); - // update the touchpad data - std::lock_guard lock(touchpad_data_mutex_); - touchpad_data_ = temp_data; - return true; -} - -bool EspBox::update_tt21100() { - // ensure the tt21100 is initialized - if (!tt21100_) { +bool EspBox::update_touch() { + if (!touch_driver_) { return false; } - // get the latest data from the device std::error_code ec; - bool new_data = tt21100_->update(ec); + bool new_data = touch_driver_->update(ec); if (ec) { - logger_.error("could not update tt21100: {}\n", ec.message()); + logger_.error("could not update touch driver: {}\n", ec.message()); std::lock_guard lock(touchpad_data_mutex_); touchpad_data_ = {}; return false; @@ -88,27 +59,11 @@ bool EspBox::update_tt21100() { if (!new_data) { return false; } - // get the latest data from the touchpad - TouchpadData temp_data; - tt21100_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); - temp_data.btn_state = tt21100_->get_home_button_state(); - // update the touchpad data std::lock_guard lock(touchpad_data_mutex_); - touchpad_data_ = temp_data; + touchpad_data_ = touch_driver_->touchpad_data(); return true; } -bool EspBox::update_touch() { - switch (box_type_) { - case BoxType::BOX3: - return update_gt911(); - case BoxType::BOX: - return update_tt21100(); - default: - return false; - } -} - void EspBox::touchpad_read(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, uint8_t *btn_state) { std::lock_guard lock(touchpad_data_mutex_); diff --git a/components/ft5x06/CMakeLists.txt b/components/ft5x06/CMakeLists.txt index d43ade275..220bf7d39 100644 --- a/components/ft5x06/CMakeLists.txt +++ b/components/ft5x06/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "base_peripheral" + REQUIRES "base_peripheral" "touch" ) diff --git a/components/ft5x06/README.md b/components/ft5x06/README.md index 558143d70..c76de3dee 100644 --- a/components/ft5x06/README.md +++ b/components/ft5x06/README.md @@ -2,7 +2,9 @@ [![Badge](https://components.espressif.com/components/espp/ft5x06/badge.svg)](https://components.espressif.com/components/espp/ft5x06) -The FT5x06 is a capacitive touch controller that supports up to 5 touch points. +The FT5x06 is a capacitive touch controller that supports up to 5 touch points +and now exposes the same cached touch interface as the other ESPP touch +controllers. ## Example diff --git a/components/ft5x06/example/main/ft5x06_example.cpp b/components/ft5x06/example/main/ft5x06_example.cpp index def438e89..f09d38ea4 100644 --- a/components/ft5x06/example/main/ft5x06_example.cpp +++ b/components/ft5x06/example/main/ft5x06_example.cpp @@ -31,14 +31,20 @@ extern "C" void app_main(void) { // the state auto task_fn = [&ft5x06](std::mutex &m, std::condition_variable &cv) { std::error_code ec; - // get the state - uint8_t num_touch_points = 0; - uint16_t x = 0, y = 0; - ft5x06.get_touch_point(&num_touch_points, &x, &y, ec); + // update the cached touch state + bool new_data = ft5x06.update(ec); if (ec) { - fmt::print("Could not get touch point\n"); + fmt::print("Could not update state\n"); return false; } + if (!new_data) { + return false; + } + auto state = ft5x06.touch_state(); + auto point = state.primary_point(); + uint8_t num_touch_points = state.num_touch_points; + uint16_t x = point.x; + uint16_t y = point.y; fmt::print("num_touch_points: {}, x: {}, y: {}\n", num_touch_points, x, y); // NOTE: sleeping in this way allows the sleep to exit early when the // task is being stopped / destroyed diff --git a/components/ft5x06/idf_component.yml b/components/ft5x06/idf_component.yml index 7b8fb73e6..42107b879 100644 --- a/components/ft5x06/idf_component.yml +++ b/components/ft5x06/idf_component.yml @@ -19,3 +19,4 @@ dependencies: idf: version: '>=5.0' espp/base_peripheral: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/ft5x06/include/ft5x06.hpp b/components/ft5x06/include/ft5x06.hpp index 97b13ed27..53265070a 100644 --- a/components/ft5x06/include/ft5x06.hpp +++ b/components/ft5x06/include/ft5x06.hpp @@ -1,8 +1,10 @@ #pragma once +#include #include #include "base_peripheral.hpp" +#include "touch.hpp" namespace espp { /// @brief The FT5x06 touch controller. @@ -10,7 +12,7 @@ namespace espp { /// /// \section ft5x06_ex1 Example /// \snippet ft5x06_example.cpp ft5x06 example -class Ft5x06 : public BasePeripheral<> { +class Ft5x06 : public BasePeripheral<>, public ITouchDevice { public: /// @brief The default I2C address for the FT5x06. static constexpr uint8_t DEFAULT_ADDRESS = (0x38); @@ -48,11 +50,53 @@ class Ft5x06 : public BasePeripheral<> { } } + /// @brief Update the cached touch state. + /// @param ec The error code if the function fails. + /// @return True if new data was read successfully. + bool update(std::error_code &ec) override { + TouchState state{}; + auto tp = read_u8_from_register((uint8_t)Registers::TOUCH_POINTS, ec); + if (ec) { + return false; + } + state.num_touch_points = std::min(tp, TouchState::MAX_TOUCH_POINTS); + if (state.num_touch_points > 0) { + static constexpr size_t BYTES_PER_TOUCH = 6; + uint8_t data[TouchState::MAX_TOUCH_POINTS * BYTES_PER_TOUCH]; + read_many_from_register((uint8_t)Registers::TOUCH1_XH, data, + state.num_touch_points * BYTES_PER_TOUCH, ec); + if (ec) { + return false; + } + for (size_t i = 0; i < state.num_touch_points; i++) { + size_t offset = i * BYTES_PER_TOUCH; + state.points[i] = { + .x = static_cast(((data[offset + 0] & 0x0f) << 8) + data[offset + 1]), + .y = static_cast(((data[offset + 2] & 0x0f) << 8) + data[offset + 3]), + }; + } + logger_.info("Got touch ({}, {})", state.points[0].x, state.points[0].y); + } + { + std::lock_guard lock(base_mutex_); + touch_state_ = state; + } + return true; + } + + /// @brief Get the cached touch state. + /// @return The cached touch state as of the last update() call. + TouchState touch_state() const override { + std::lock_guard lock(base_mutex_); + return touch_state_; + } + /// @brief Get the number of touch points. /// @param ec The error code if the function fails. /// @return The number of touch points. uint8_t get_num_touch_points(std::error_code &ec) { - return read_u8_from_register((uint8_t)Registers::TOUCH_POINTS, ec); + ec.clear(); + return touch_state().num_touch_points; } /// @brief Get the touch point. @@ -61,22 +105,12 @@ class Ft5x06 : public BasePeripheral<> { /// @param y The y coordinate of the touch point. /// @param ec The error code if the function fails. void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, std::error_code &ec) { - std::lock_guard lock(base_mutex_); - auto tp = get_num_touch_points(ec); - if (ec) { - return; - } - *num_touch_points = tp; - if (*num_touch_points != 0) { - uint8_t data[4]; - read_many_from_register((uint8_t)Registers::TOUCH1_XH, data, 4, ec); - if (ec) { - return; - } - *x = ((data[0] & 0x0f) << 8) + data[1]; - *y = ((data[2] & 0x0f) << 8) + data[3]; - logger_.info("Got touch ({}, {})", *x, *y); - } + ec.clear(); + auto state = touch_state(); + auto point = state.primary_point(); + *num_touch_points = state.num_touch_points; + *x = point.x; + *y = point.y; } /// @brief Get the gesture that was detected. @@ -181,5 +215,7 @@ class Ft5x06 : public BasePeripheral<> { ID_G_FT5201ID = 0xA8, ID_G_ERR = 0xA9, }; + + TouchState touch_state_{}; }; } // namespace espp diff --git a/components/gt911/CMakeLists.txt b/components/gt911/CMakeLists.txt index d43ade275..220bf7d39 100644 --- a/components/gt911/CMakeLists.txt +++ b/components/gt911/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "base_peripheral" + REQUIRES "base_peripheral" "touch" ) diff --git a/components/gt911/idf_component.yml b/components/gt911/idf_component.yml index 775774d3b..779bef15f 100644 --- a/components/gt911/idf_component.yml +++ b/components/gt911/idf_component.yml @@ -21,3 +21,4 @@ dependencies: idf: version: '>=5.0' espp/base_peripheral: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/gt911/include/gt911.hpp b/components/gt911/include/gt911.hpp index fc5ebb1ad..a798445a0 100644 --- a/components/gt911/include/gt911.hpp +++ b/components/gt911/include/gt911.hpp @@ -1,16 +1,17 @@ #pragma once -#include +#include #include #include "base_peripheral.hpp" +#include "touch.hpp" namespace espp { /// @brief Driver for the GT911 touch controller /// /// \section gt911_ex1 Example /// \snippet gt911_example.cpp gt911 example -class Gt911 : public BasePeripheral { +class Gt911 : public BasePeripheral, public ITouchDevice { public: /// Default address for the GT911 chip, if the interrupt pin is low on power on static constexpr uint8_t DEFAULT_ADDRESS_1 = 0x5D; @@ -39,6 +40,7 @@ class Gt911 : public BasePeripheral { bool new_data = false; static constexpr size_t DATA_LEN = CONTACT_SIZE * MAX_CONTACTS; static uint8_t data[DATA_LEN]; + TouchState state{}; std::lock_guard lock(base_mutex_); read_many_from_register((uint16_t)Registers::POINT_INFO, data, 1, ec); if (ec) @@ -55,27 +57,27 @@ class Gt911 : public BasePeripheral { if (ec) return false; // set the button state - home_button_pressed_ = data[0]; - logger_.debug("Home button is {}", home_button_pressed_ ? "pressed" : "released"); + state = touch_state_; + state.btn_state = data[0]; + logger_.debug("Home button is {}", state.btn_state ? "pressed" : "released"); + touch_state_ = state; new_data = true; } else if ((data[0] & 0x80) == 0x80) { - // clear the home button state - home_button_pressed_ = false; - // touch data is available - num_touch_points_ = data[0] & 0x0f; - logger_.debug("Got {} touch points", num_touch_points_); - if (num_touch_points_ > 0) { - read_many_from_register((uint16_t)Registers::POINTS, data, CONTACT_SIZE * num_touch_points_, - ec); + state.num_touch_points = std::min(data[0] & 0x0f, TouchState::MAX_TOUCH_POINTS); + logger_.debug("Got {} touch points", state.num_touch_points); + if (state.num_touch_points > 0) { + read_many_from_register((uint16_t)Registers::POINTS, data, + CONTACT_SIZE * state.num_touch_points, ec); if (ec) return false; - // convert the data pointer to a GTPoint* - const auto *point = reinterpret_cast(&data[0]); - x_ = point->x; - y_ = point->y; - logger_.debug("Touch at ({}, {})", x_, y_); + const auto *points = reinterpret_cast(&data[0]); + for (size_t i = 0; i < state.num_touch_points; i++) { + state.points[i] = {.x = points[i].x, .y = points[i].y}; + } + logger_.debug("Touch at ({}, {})", state.points[0].x, state.points[0].y); } + touch_state_ = state; new_data = true; } @@ -85,10 +87,21 @@ class Gt911 : public BasePeripheral { return new_data; } + /// @brief Get the cached touch state. + /// @return The cached touch state as of the last update() call. + TouchState touch_state() const override { + std::lock_guard lock(base_mutex_); + return touch_state_; + } + + /// @brief Whether the controller exposes a home button. + /// @return True. + bool has_home_button() const override { return true; } + /// @brief Get the number of touch points /// @return The number of touch points as of the last update /// @note This is a cached value from the last update() call - uint8_t get_num_touch_points() const { return num_touch_points_; } + uint8_t get_num_touch_points() const { return touch_state().num_touch_points; } /// @brief Get the touch point data /// @param num_touch_points The number of touch points as of the last update @@ -96,17 +109,17 @@ class Gt911 : public BasePeripheral { /// @param y The y coordinate of the touch point /// @note This is a cached value from the last update() call void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { - *num_touch_points = get_num_touch_points(); - if (*num_touch_points != 0) { - *x = x_; - *y = y_; - } + auto state = touch_state(); + auto point = state.primary_point(); + *num_touch_points = state.num_touch_points; + *x = point.x; + *y = point.y; } /// @brief Get the home button state /// @return True if the home button is pressed, false otherwise /// @note This is a cached value from the last update() call - bool get_home_button_state() const { return home_button_pressed_; } + bool get_home_button_state() const { return touch_state().btn_state; } protected: static constexpr int CONTACT_SIZE = 8; @@ -256,9 +269,6 @@ class Gt911 : public BasePeripheral { #pragma pack(pop) - std::atomic home_button_pressed_{false}; - std::atomic num_touch_points_; - std::atomic x_; - std::atomic y_; + TouchState touch_state_; }; } // namespace espp diff --git a/components/input_drivers/CMakeLists.txt b/components/input_drivers/CMakeLists.txt index e2dd8771d..b2746a478 100644 --- a/components/input_drivers/CMakeLists.txt +++ b/components/input_drivers/CMakeLists.txt @@ -1,3 +1,3 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES base_component ) + REQUIRES base_component touch ) diff --git a/components/input_drivers/idf_component.yml b/components/input_drivers/idf_component.yml index 066e4556f..3fccc8498 100644 --- a/components/input_drivers/idf_component.yml +++ b/components/input_drivers/idf_component.yml @@ -22,3 +22,4 @@ dependencies: lvgl/lvgl: version: '>=9.2.2' espp/base_component: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/input_drivers/include/touchpad_input.hpp b/components/input_drivers/include/touchpad_input.hpp index 688a81eea..4cf1a8910 100644 --- a/components/input_drivers/include/touchpad_input.hpp +++ b/components/input_drivers/include/touchpad_input.hpp @@ -7,22 +7,10 @@ #include "sdkconfig.h" #include "base_component.hpp" +#include "touch.hpp" namespace espp { -/// The data structure for the touchpad -struct TouchpadData { - uint8_t num_touch_points = 0; ///< The number of touch points - uint16_t x = 0; ///< The x coordinate - uint16_t y = 0; ///< The y coordinate - uint8_t btn_state = 0; ///< The button state (0 = button released, 1 = button pressed) - - /// @brief Compare two TouchpadData objects for equality - /// @param rhs The right hand side of the comparison - /// @return true if the two TouchpadData objects are equal, false otherwise - bool operator==(const TouchpadData &rhs) const = default; -}; - /** * @brief Light wrapper around LVGL input device driver, specifically * designed for touchpads with optional home buttons. diff --git a/components/m5stack-tab5/example/CMakeLists.txt b/components/m5stack-tab5/example/CMakeLists.txt index c60031698..9921ee88c 100644 --- a/components/m5stack-tab5/example/CMakeLists.txt +++ b/components/m5stack-tab5/example/CMakeLists.txt @@ -31,7 +31,9 @@ set(EXTRA_COMPONENT_DIRS "../../../components/math" "../../../components/rx8130ce" "../../../components/pi4ioe5v" + "../../../components/st7123touch" "../../../components/task" + "../../../components/touch" ) set( diff --git a/components/touch/CMakeLists.txt b/components/touch/CMakeLists.txt new file mode 100644 index 000000000..6c32defac --- /dev/null +++ b/components/touch/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" + ) diff --git a/components/touch/README.md b/components/touch/README.md new file mode 100644 index 000000000..1f6ccdbe5 --- /dev/null +++ b/components/touch/README.md @@ -0,0 +1,13 @@ +# Touch Interface Component + +[![Badge](https://components.espressif.com/components/espp/touch/badge.svg)](https://components.espressif.com/components/espp/touch) + +The `touch` component defines shared touch data types and the runtime interface +used by ESPP touch controller drivers. It standardizes cached touch state, +single-point compatibility helpers, and runtime-polymorphic access for BSPs that +need to select between multiple touch controllers. + +## Example + +The [example](./example) shows how to implement and consume the shared +`ITouchDevice` interface. diff --git a/components/touch/example/CMakeLists.txt b/components/touch/example/CMakeLists.txt new file mode 100644 index 000000000..09d27623d --- /dev/null +++ b/components/touch/example/CMakeLists.txt @@ -0,0 +1,22 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.20) + +set(ENV{IDF_COMPONENT_MANAGER} "0") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# add the component directories that we want to use +set(EXTRA_COMPONENT_DIRS + "../../../components/" +) + +set( + COMPONENTS + "main esptool_py touch" + CACHE STRING + "List of components to include" + ) + +project(touch_example) + +set(CMAKE_CXX_STANDARD 20) diff --git a/components/touch/example/README.md b/components/touch/example/README.md new file mode 100644 index 000000000..5c362e4b8 --- /dev/null +++ b/components/touch/example/README.md @@ -0,0 +1,18 @@ +# Touch Interface Example + +This example shows how to expose a touch controller through the shared +`espp::ITouchDevice` runtime interface. + +## How to use example + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) diff --git a/components/touch/example/main/CMakeLists.txt b/components/touch/example/main/CMakeLists.txt new file mode 100644 index 000000000..a941e22ba --- /dev/null +++ b/components/touch/example/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS ".") diff --git a/components/touch/example/main/touch_example.cpp b/components/touch/example/main/touch_example.cpp new file mode 100644 index 000000000..5babe083d --- /dev/null +++ b/components/touch/example/main/touch_example.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include "touch.hpp" + +using namespace std::chrono_literals; + +namespace { +class FakeTouchDevice : public espp::ITouchDevice { +public: + bool update(std::error_code &ec) override { + ec.clear(); + state_.num_touch_points = state_.num_touch_points == 0 ? 1 : 0; + state_.btn_state = state_.num_touch_points; + state_.points[0] = {.x = 120, .y = 64}; + return true; + } + + espp::TouchState touch_state() const override { return state_; } + + bool has_home_button() const override { return true; } + +protected: + espp::TouchState state_; +}; +} // namespace + +extern "C" void app_main(void) { + { + std::printf("Starting touch example\n"); + //! [touch example] + FakeTouchDevice touch; + std::error_code ec; + touch.update(ec); + auto state = touch.touch_state(); + auto primary = state.primary_point(); + std::printf("num_touch_points=%u x=%u y=%u btn_state=%u\n", state.num_touch_points, primary.x, + primary.y, state.btn_state); + //! [touch example] + } + + std::printf("Touch example complete!\n"); + + while (true) { + std::this_thread::sleep_for(1s); + } +} diff --git a/components/touch/idf_component.yml b/components/touch/idf_component.yml new file mode 100644 index 000000000..e81439a6e --- /dev/null +++ b/components/touch/idf_component.yml @@ -0,0 +1,19 @@ +## IDF Component Manager Manifest File +license: "MIT" +description: "Shared touch types and runtime interface component for ESP-IDF" +url: "https://github.com/esp-cpp/espp/tree/main/components/touch" +repository: "git://github.com/esp-cpp/espp.git" +maintainers: + - William Emfinger +documentation: "https://esp-cpp.github.io/espp/input/touch.html" +examples: + - path: example +tags: + - cpp + - Component + - Touch + - Touchscreen + - Input +dependencies: + idf: + version: '>=5.0' diff --git a/components/touch/include/touch.hpp b/components/touch/include/touch.hpp new file mode 100644 index 000000000..b11a1f16b --- /dev/null +++ b/components/touch/include/touch.hpp @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +namespace espp { +/// The data structure for a single touch point. +struct TouchPoint { + uint16_t x = 0; ///< The x coordinate. + uint16_t y = 0; ///< The y coordinate. + + /// @brief Compare two TouchPoint objects for equality. + /// @param rhs The right hand side of the comparison. + /// @return true if the two TouchPoint objects are equal, false otherwise. + bool operator==(const TouchPoint &rhs) const = default; +}; + +/// The data structure for the primary touch point used by single-pointer consumers. +struct TouchpadData { + uint8_t num_touch_points = 0; ///< The number of touch points. + uint16_t x = 0; ///< The primary touch x coordinate. + uint16_t y = 0; ///< The primary touch y coordinate. + uint8_t btn_state = 0; ///< The button state (0 = released, 1 = pressed). + + /// @brief Compare two TouchpadData objects for equality. + /// @param rhs The right hand side of the comparison. + /// @return true if the two TouchpadData objects are equal, false otherwise. + bool operator==(const TouchpadData &rhs) const = default; +}; + +/// Shared cached touch state used by touch drivers. +struct TouchState { + static constexpr std::size_t MAX_TOUCH_POINTS = 5; ///< Maximum number of cached touch points. + + std::array points{}; ///< Cached touch points. + uint8_t num_touch_points = 0; ///< Number of valid touch points. + uint8_t btn_state = 0; ///< Optional button state. + + /// @brief Compare two TouchState objects for equality. + /// @param rhs The right hand side of the comparison. + /// @return true if the two TouchState objects are equal, false otherwise. + bool operator==(const TouchState &rhs) const = default; + + /// @brief Get the primary touch point. + /// @return The first valid touch point, or `{0, 0}` if there is no touch. + TouchPoint primary_point() const { + if (num_touch_points == 0) { + return {}; + } + return points[0]; + } + + /// @brief Convert the cached multi-touch state to the single-point touchpad view. + /// @return The primary-point view of the cached state. + TouchpadData touchpad_data() const { + auto point = primary_point(); + return { + .num_touch_points = num_touch_points, .x = point.x, .y = point.y, .btn_state = btn_state}; + } +}; + +/// Runtime interface implemented by touch controller drivers. +class ITouchDevice { +public: + virtual ~ITouchDevice() = default; + + /// @brief Update the cached touch state from the hardware. + /// @param ec Error code to set if an error occurs. + /// @return True if the device reported new data, false otherwise. + virtual bool update(std::error_code &ec) = 0; + + /// @brief Get the cached touch state. + /// @return The cached touch state. + virtual TouchState touch_state() const = 0; + + /// @brief Whether the controller exposes a touch-adjacent home button. + /// @return True if the controller exposes a home button, false otherwise. + virtual bool has_home_button() const { return false; } + + /// @brief Get the number of cached touch points. + /// @return The number of cached touch points. + uint8_t get_num_touch_points() const { return touch_state().num_touch_points; } + + /// @brief Get the primary cached touch point. + /// @param num_touch_points The number of touch points as of the last update. + /// @param x The x coordinate of the primary touch point. + /// @param y The y coordinate of the primary touch point. + void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { + auto state = touch_state(); + auto point = state.primary_point(); + *num_touch_points = state.num_touch_points; + *x = point.x; + *y = point.y; + } + + /// @brief Get the cached home button state. + /// @return The cached home button state. + uint8_t get_home_button_state() const { return touch_state().btn_state; } + + /// @brief Get the cached single-point touchpad view. + /// @return The primary touchpad view of the cached state. + TouchpadData touchpad_data() const { return touch_state().touchpad_data(); } +}; +} // namespace espp diff --git a/components/touch/src/touch.cpp b/components/touch/src/touch.cpp new file mode 100644 index 000000000..cb4e5b475 --- /dev/null +++ b/components/touch/src/touch.cpp @@ -0,0 +1 @@ +#include "touch.hpp" diff --git a/components/tt21100/CMakeLists.txt b/components/tt21100/CMakeLists.txt index d43ade275..220bf7d39 100644 --- a/components/tt21100/CMakeLists.txt +++ b/components/tt21100/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "base_peripheral" + REQUIRES "base_peripheral" "touch" ) diff --git a/components/tt21100/idf_component.yml b/components/tt21100/idf_component.yml index d562dfa5d..3d1bca17a 100644 --- a/components/tt21100/idf_component.yml +++ b/components/tt21100/idf_component.yml @@ -20,3 +20,4 @@ dependencies: idf: version: '>=5.0' espp/base_peripheral: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/tt21100/include/tt21100.hpp b/components/tt21100/include/tt21100.hpp index 6658d2824..7648b57dd 100644 --- a/components/tt21100/include/tt21100.hpp +++ b/components/tt21100/include/tt21100.hpp @@ -1,15 +1,17 @@ #pragma once +#include #include #include "base_peripheral.hpp" +#include "touch.hpp" namespace espp { /// @brief Driver for the Tt21100 touch controller /// /// \section tt21100_ex1 Example /// \snippet tt21100_example.cpp tt21100 example -class Tt21100 : public BasePeripheral<> { +class Tt21100 : public BasePeripheral<>, public ITouchDevice { public: /// @brief The default i2c address static constexpr uint8_t DEFAULT_ADDRESS = (0x24); @@ -39,6 +41,7 @@ class Tt21100 : public BasePeripheral<> { bool update(std::error_code &ec) { static uint16_t data_len; static uint8_t data[256]; + TouchState state{}; // NOTE: this chip is weird, and even though we're reading a u16, we can't // use the read_u16 since that function assumes the data is in little @@ -77,37 +80,59 @@ class Tt21100 : public BasePeripheral<> { case 7: case 17: case 27: { - // touch event - NOTE: this only gets the first touch record + // touch event const auto report_data = reinterpret_cast(data); - const auto touch_data = reinterpret_cast(&report_data->touch_record[0]); - x_ = touch_data->x; - y_ = touch_data->y; - num_touch_points_ = (data_len - sizeof(TouchReport)) / sizeof(TouchRecord); - logger_.debug("Touch event: #={}, [0]=({}, {})", num_touch_points_, x_, y_); + const auto *touch_records = reinterpret_cast(data + sizeof(TouchReport)); + state = touch_state(); + state.num_touch_points = + std::min(report_data->record_num, TouchState::MAX_TOUCH_POINTS); + for (size_t i = 0; i < state.num_touch_points; i++) { + state.points[i] = {.x = touch_records[i].x, .y = touch_records[i].y}; + } + if (state.num_touch_points > 0) { + logger_.debug("Touch event: #={}, [0]=({}, {})", state.num_touch_points, state.points[0].x, + state.points[0].y); + } new_data = true; break; } case 14: { // button event const auto button_data = reinterpret_cast(data); - home_button_pressed_ = button_data->btn_val; + state = touch_state(); + state.btn_state = button_data->btn_val; auto btn_signal = button_data->btn_signal[0]; logger_.debug("Button event({}): {}, {}", static_cast(button_data->length), - home_button_pressed_, btn_signal); + state.btn_state, btn_signal); new_data = true; break; } default: break; } + if (new_data) { + std::lock_guard lock(base_mutex_); + touch_state_ = state; + } return new_data; } + /// @brief Get the cached touch state. + /// @return The cached touch state as of the last update() call. + TouchState touch_state() const override { + std::lock_guard lock(base_mutex_); + return touch_state_; + } + + /// @brief Whether the controller exposes a home button. + /// @return True. + bool has_home_button() const override { return true; } + /// @brief Get the number of touch points /// @note This is the number of touch points that were present when the last /// update() was called /// @return The number of touch points - uint8_t get_num_touch_points() const { return num_touch_points_; } + uint8_t get_num_touch_points() const { return touch_state().num_touch_points; } /// @brief Get the touch point data /// @note This is the touch point data that was present when the last @@ -116,18 +141,18 @@ class Tt21100 : public BasePeripheral<> { /// @param x The x position of the touch point /// @param y The y position of the touch point void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { - *num_touch_points = get_num_touch_points(); - if (*num_touch_points != 0) { - *x = x_; - *y = y_; - } + auto state = touch_state(); + auto point = state.primary_point(); + *num_touch_points = state.num_touch_points; + *x = point.x; + *y = point.y; } /// @brief Get the state of the home button /// @note This is the state of the home button when the last update() was /// called /// @return True if the home button is pressed - uint8_t get_home_button_state() const { return home_button_pressed_; } + uint8_t get_home_button_state() const { return touch_state().btn_state; } protected: void init(std::error_code &ec) { @@ -195,9 +220,6 @@ class Tt21100 : public BasePeripheral<> { #pragma pack(pop) - std::atomic home_button_pressed_{false}; - std::atomic num_touch_points_; - std::atomic x_; - std::atomic y_; + TouchState touch_state_; }; } // namespace espp diff --git a/doc/Doxyfile b/doc/Doxyfile index 84855dd65..132860091 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -155,6 +155,7 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/task/example/main/task_example.cpp \ $(PROJECT_PATH)/components/thermistor/example/main/thermistor_example.cpp \ $(PROJECT_PATH)/components/timer/example/main/timer_example.cpp \ + $(PROJECT_PATH)/components/touch/example/main/touch_example.cpp \ $(PROJECT_PATH)/components/tla2528/example/main/tla2528_example.cpp \ $(PROJECT_PATH)/components/tt21100/example/main/tt21100_example.cpp \ $(PROJECT_PATH)/components/vl53l/example/main/vl53l_example.cpp \ @@ -338,6 +339,7 @@ INPUT = \ $(PROJECT_PATH)/components/thermistor/include/thermistor.hpp \ $(PROJECT_PATH)/components/timer/include/high_resolution_timer.hpp \ $(PROJECT_PATH)/components/timer/include/timer.hpp \ + $(PROJECT_PATH)/components/touch/include/touch.hpp \ $(PROJECT_PATH)/components/tla2528/include/tla2528.hpp \ $(PROJECT_PATH)/components/tt21100/include/tt21100.hpp \ $(PROJECT_PATH)/components/vl53l/include/vl53l.hpp \ @@ -380,4 +382,3 @@ HAVE_DOT = NO GENERATE_LATEX = YES GENERATE_MAN = NO GENERATE_RTF = NO - diff --git a/doc/en/input/ft5x06.rst b/doc/en/input/ft5x06.rst index 875656a33..6a931f64f 100644 --- a/doc/en/input/ft5x06.rst +++ b/doc/en/input/ft5x06.rst @@ -2,6 +2,8 @@ FT5x06 Touch Controller *********************** The FT5x06 is a capacitive touch controller that supports up to 5 touch points. +It also implements the shared `ITouchDevice` runtime interface so it can be +used interchangeably with the other ESPP touch drivers. .. ------------------------------- Example ------------------------------------- diff --git a/doc/en/input/index.rst b/doc/en/input/index.rst index 4b86e254e..dc6d2e749 100644 --- a/doc/en/input/index.rst +++ b/doc/en/input/index.rst @@ -9,8 +9,9 @@ Input APIs ft5x06 gt911 st7123touch - tt21100 t_keyboard + touch + tt21100 encoder_input keypad_input pointer_input diff --git a/doc/en/input/touch.rst b/doc/en/input/touch.rst new file mode 100644 index 000000000..0bd9ea9a9 --- /dev/null +++ b/doc/en/input/touch.rst @@ -0,0 +1,19 @@ +Touch Interface +*************** + +The `touch` component provides the shared `TouchPoint`, `TouchState`, +`TouchpadData`, and `ITouchDevice` types used by ESPP touch controller drivers +and BSPs. + +.. ------------------------------- Example ------------------------------------- + +.. toctree:: + + touch_example + +.. ---------------------------- API Reference ---------------------------------- + +API Reference +------------- + +.. include-build-file:: inc/touch.inc diff --git a/doc/en/input/touch_example.md b/doc/en/input/touch_example.md new file mode 100644 index 000000000..26fa8c92d --- /dev/null +++ b/doc/en/input/touch_example.md @@ -0,0 +1,2 @@ +```{include} ../../../components/touch/example/README.md +``` From 003d808c9d7599b005031bd7cd6cde4693fced11 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 23 May 2026 23:28:34 -0500 Subject: [PATCH 05/10] fix bug in tt21100 impl --- components/tt21100/include/tt21100.hpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/components/tt21100/include/tt21100.hpp b/components/tt21100/include/tt21100.hpp index 7648b57dd..946053a2b 100644 --- a/components/tt21100/include/tt21100.hpp +++ b/components/tt21100/include/tt21100.hpp @@ -201,13 +201,12 @@ class Tt21100 : public BasePeripheral<>, public ITouchDevice { uint16_t data_len; uint8_t report_id; uint16_t time_stamp; - uint8_t : 2; - uint8_t large_object : 1; uint8_t record_num : 5; + uint8_t large_object : 1; uint8_t report_counter : 2; - uint8_t : 3; + uint8_t : 2; uint8_t noise_efect : 3; - TouchRecord touch_record[0]; + uint8_t : 3; }; struct ButtonRecord { From ea70e9a30c1cf57695098a93fe60fba42ff9d00d Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 23 May 2026 23:31:55 -0500 Subject: [PATCH 06/10] fix sa --- components/ft5x06/include/ft5x06.hpp | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/components/ft5x06/include/ft5x06.hpp b/components/ft5x06/include/ft5x06.hpp index 53265070a..7a673a775 100644 --- a/components/ft5x06/include/ft5x06.hpp +++ b/components/ft5x06/include/ft5x06.hpp @@ -92,20 +92,18 @@ class Ft5x06 : public BasePeripheral<>, public ITouchDevice { } /// @brief Get the number of touch points. - /// @param ec The error code if the function fails. + /// @note This is the number of touch points that were present when the last + /// update() was called /// @return The number of touch points. - uint8_t get_num_touch_points(std::error_code &ec) { - ec.clear(); - return touch_state().num_touch_points; - } + uint8_t get_num_touch_points() const { return touch_state().num_touch_points; } - /// @brief Get the touch point. + /// @brief Get the touch point data. + /// @note This is the touch point data that was present when the last + /// update() was called /// @param num_touch_points The number of touch points. /// @param x The x coordinate of the touch point. /// @param y The y coordinate of the touch point. - /// @param ec The error code if the function fails. - void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y, std::error_code &ec) { - ec.clear(); + void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const { auto state = touch_state(); auto point = state.primary_point(); *num_touch_points = state.num_touch_points; From c58cefb4022433b5189092e3a00d61f4ac385611 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 24 Jun 2026 10:00:04 -0500 Subject: [PATCH 07/10] fix examples and clean up branch some --- components/m5stack-tab5/CMakeLists.txt | 2 +- .../src/smartpanlee-sc01-plus.cpp | 12 ++- components/st7123touch/CMakeLists.txt | 2 +- .../example/main/st7123touch_example.cpp | 8 +- components/st7123touch/idf_component.yml | 1 + .../st7123touch/include/st7123touch.hpp | 78 +------------------ components/touch/README.md | 6 ++ components/touch/include/touch.hpp | 76 ++++++++++++++++++ doc/en/input/st7123touch.rst | 5 +- doc/en/input/touch.rst | 11 +++ 10 files changed, 116 insertions(+), 85 deletions(-) mode change 100644 => 100755 components/m5stack-tab5/CMakeLists.txt mode change 100644 => 100755 components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp mode change 100644 => 100755 components/st7123touch/CMakeLists.txt mode change 100644 => 100755 components/st7123touch/example/main/st7123touch_example.cpp mode change 100644 => 100755 components/st7123touch/idf_component.yml mode change 100644 => 100755 components/st7123touch/include/st7123touch.hpp mode change 100644 => 100755 components/touch/README.md mode change 100644 => 100755 doc/en/input/st7123touch.rst mode change 100644 => 100755 doc/en/input/touch.rst diff --git a/components/m5stack-tab5/CMakeLists.txt b/components/m5stack-tab5/CMakeLists.txt old mode 100644 new mode 100755 index fc2b4b29d..d75353614 --- a/components/m5stack-tab5/CMakeLists.txt +++ b/components/m5stack-tab5/CMakeLists.txt @@ -1,6 +1,6 @@ idf_component_register( INCLUDE_DIRS "include" SRC_DIRS "src" - REQUIRES driver esp_driver_i2s esp_driver_sdmmc esp_driver_spi esp_lcd fatfs base_component bmi270 codec display display_drivers gt911 i2c ina226 input_drivers interrupt pi4ioe5v rx8130ce st7123touch task + REQUIRES driver esp_driver_i2s esp_driver_sdmmc esp_driver_spi esp_lcd fatfs base_component bmi270 codec display display_drivers gt911 i2c ina226 input_drivers interrupt pi4ioe5v rx8130ce st7123touch task touch REQUIRED_IDF_TARGETS "esp32p4" ) diff --git a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp old mode 100644 new mode 100755 index 7c2638e2a..ee88b5b53 --- a/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp +++ b/components/smartpanlee-sc01-plus/src/smartpanlee-sc01-plus.cpp @@ -493,15 +493,23 @@ bool SmartPanleeSc01Plus::update_touch() { return false; } + // Read fresh data from the controller, then pull the cached touch point out of + // the driver via the espp touch-device interface (update() + get_touch_point()). std::error_code ec; - TouchpadData temp_data; - touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y, ec); + bool new_data = touch_driver_->update(ec); if (ec) { logger_.error("Could not update touch driver: {}", ec.message()); std::lock_guard lock(touchpad_data_mutex_); touchpad_data_ = {}; return false; } + if (!new_data) { + return false; + } + + TouchpadData temp_data; + touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); + temp_data.btn_state = touch_driver_->get_home_button_state(); std::lock_guard lock(touchpad_data_mutex_); bool changed = temp_data != touchpad_data_; diff --git a/components/st7123touch/CMakeLists.txt b/components/st7123touch/CMakeLists.txt old mode 100644 new mode 100755 index d43ade275..220bf7d39 --- a/components/st7123touch/CMakeLists.txt +++ b/components/st7123touch/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register( INCLUDE_DIRS "include" - REQUIRES "base_peripheral" + REQUIRES "base_peripheral" "touch" ) diff --git a/components/st7123touch/example/main/st7123touch_example.cpp b/components/st7123touch/example/main/st7123touch_example.cpp old mode 100644 new mode 100755 index 8ec57d83d..9e839b8de --- a/components/st7123touch/example/main/st7123touch_example.cpp +++ b/components/st7123touch/example/main/st7123touch_example.cpp @@ -26,8 +26,10 @@ extern "C" void app_main(void) { fmt::print("ST7123 touch probe: {}\n", has_st7123); fmt::print(" address: {:#02x}\n", espp::St7123Touch::DEFAULT_ADDRESS); - // Create the ST7123 touch driver - espp::St7123Touch touch({ + // Create the ST7123 touch driver. It owns a recursive_mutex and atomics, so + // it is non-copyable and non-movable — construct it directly inside the + // shared_ptr rather than moving a stack instance into it. + auto touch = std::make_shared(espp::St7123Touch::Config{ .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, @@ -37,7 +39,7 @@ extern "C" void app_main(void) { // Wrap the driver in a concept-erased ITouchDriver pointer. // Any type satisfying espp::TouchDriverConcept can be wrapped this way. - auto driver = espp::make_touch_driver(std::make_shared(std::move(touch))); + auto driver = espp::make_touch_driver(touch); // Poll for touch events using the type-erased interface auto task_fn = [&driver](std::mutex &m, std::condition_variable &cv) { diff --git a/components/st7123touch/idf_component.yml b/components/st7123touch/idf_component.yml old mode 100644 new mode 100755 index bb51d9084..1a1eb4247 --- a/components/st7123touch/idf_component.yml +++ b/components/st7123touch/idf_component.yml @@ -20,3 +20,4 @@ dependencies: idf: version: '>=5.0' espp/base_peripheral: '>=1.0' + espp/touch: '>=1.0' diff --git a/components/st7123touch/include/st7123touch.hpp b/components/st7123touch/include/st7123touch.hpp old mode 100644 new mode 100755 index b0c6b6d6d..8ddeaa5ab --- a/components/st7123touch/include/st7123touch.hpp +++ b/components/st7123touch/include/st7123touch.hpp @@ -1,89 +1,13 @@ #pragma once #include -#include -#include -#include #include #include "base_peripheral.hpp" +#include "touch.hpp" namespace espp { -/// @brief Concept satisfied by any touch-controller driver that exposes the -/// standard espp touch-driver interface. -/// -/// A type T satisfies TouchDriverConcept if it provides: -/// - `bool T::update(std::error_code &)` — read new touch data from hardware -/// - `void T::get_touch_point(uint8_t *, uint16_t *, uint16_t *) const` — retrieve coordinates -/// - `bool T::get_home_button_state() const` — return home-button state -/// -/// Both `espp::Gt911` and `espp::St7123Touch` satisfy this concept. -template -concept TouchDriverConcept = requires(T &t, std::error_code &ec, uint8_t *n, uint16_t *x, - uint16_t *y) { - { t.update(ec) } -> std::convertible_to; - { t.get_touch_point(n, x, y) }; - { t.get_home_button_state() } -> std::convertible_to; -}; - -/// @brief Abstract type-erased interface for a touch driver. -/// -/// Used together with `TouchDriverAdapter` to allow the M5Stack Tab5 BSP -/// (and any other consumer) to store a single `std::shared_ptr` -/// regardless of which concrete driver (Gt911, St7123Touch, …) is in use at -/// runtime. -struct ITouchDriver { - virtual ~ITouchDriver() = default; - - /// @brief Read new touch data from the hardware. - /// @param ec Set on I2C error; cleared on success. - /// @return True when new coordinate data is available. - virtual bool update(std::error_code &ec) = 0; - - /// @brief Retrieve the primary touch point. - /// @param num_touch_points Output: number of active touch points. - /// @param x Output: X coordinate. - /// @param y Output: Y coordinate. - virtual void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const = 0; - - /// @brief Return the home-button pressed state. - virtual bool get_home_button_state() const = 0; -}; - -/// @brief Concept-constrained adapter that wraps any concrete touch driver -/// satisfying `TouchDriverConcept` behind the `ITouchDriver` interface. -/// -/// Usage: -/// @code -/// auto driver = std::make_shared(...); -/// std::shared_ptr touch = -/// std::make_shared>(driver); -/// @endcode -template -struct TouchDriverAdapter : ITouchDriver { - /// Underlying concrete driver - std::shared_ptr driver; - - explicit TouchDriverAdapter(std::shared_ptr d) : driver(std::move(d)) {} - - bool update(std::error_code &ec) override { return driver->update(ec); } - - void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const override { - driver->get_touch_point(num_touch_points, x, y); - } - - bool get_home_button_state() const override { return driver->get_home_button_state(); } -}; - -/// @brief Convenience factory: wrap a shared_ptr to a concrete touch driver in -/// a `TouchDriverAdapter` and return it as `std::shared_ptr`. -/// @tparam T Concrete driver type — must satisfy `TouchDriverConcept`. -template -std::shared_ptr make_touch_driver(std::shared_ptr driver) { - return std::make_shared>(std::move(driver)); -} - /// @brief Driver for the ST7123 integrated touch controller /// /// The ST7123 is a TDDI (Touch and Display Driver Integration) chip that diff --git a/components/touch/README.md b/components/touch/README.md old mode 100644 new mode 100755 index 1f6ccdbe5..7f0c84a47 --- a/components/touch/README.md +++ b/components/touch/README.md @@ -7,6 +7,12 @@ used by ESPP touch controller drivers. It standardizes cached touch state, single-point compatibility helpers, and runtime-polymorphic access for BSPs that need to select between multiple touch controllers. +In addition to the `ITouchDevice` interface, the component provides the +type-erasure helpers `TouchDriverConcept`, `ITouchDriver`, `TouchDriverAdapter`, +and the `make_touch_driver()` factory. These let a BSP wrap any concrete driver +(e.g. `Gt911` or `St7123Touch`) in a single `std::shared_ptr` +regardless of which controller is present at runtime. + ## Example The [example](./example) shows how to implement and consume the shared diff --git a/components/touch/include/touch.hpp b/components/touch/include/touch.hpp index b11a1f16b..1ed1a8234 100644 --- a/components/touch/include/touch.hpp +++ b/components/touch/include/touch.hpp @@ -1,8 +1,10 @@ #pragma once #include +#include #include #include +#include #include namespace espp { @@ -103,4 +105,78 @@ class ITouchDevice { /// @return The primary touchpad view of the cached state. TouchpadData touchpad_data() const { return touch_state().touchpad_data(); } }; + +/// @brief Concept satisfied by any touch-controller driver that exposes the +/// standard espp touch-driver interface. +/// +/// A type T satisfies TouchDriverConcept if it provides: +/// - `bool T::update(std::error_code &)` — read new touch data from hardware +/// - `void T::get_touch_point(uint8_t *, uint16_t *, uint16_t *) const` — retrieve coordinates +/// - `bool T::get_home_button_state() const` — return home-button state +/// +/// Both `espp::Gt911` and `espp::St7123Touch` satisfy this concept. +template +concept TouchDriverConcept = requires(T &t, std::error_code &ec, uint8_t *n, uint16_t *x, + uint16_t *y) { + { t.update(ec) } -> std::convertible_to; + {t.get_touch_point(n, x, y)}; + { t.get_home_button_state() } -> std::convertible_to; +}; + +/// @brief Abstract type-erased interface for a touch driver. +/// +/// Used together with `TouchDriverAdapter` to allow a BSP (such as the +/// M5Stack Tab5) or any other consumer to store a single +/// `std::shared_ptr` regardless of which concrete driver +/// (Gt911, St7123Touch, …) is in use at runtime. +struct ITouchDriver { + virtual ~ITouchDriver() = default; + + /// @brief Read new touch data from the hardware. + /// @param ec Set on I2C error; cleared on success. + /// @return True when new coordinate data is available. + virtual bool update(std::error_code &ec) = 0; + + /// @brief Retrieve the primary touch point. + /// @param num_touch_points Output: number of active touch points. + /// @param x Output: X coordinate. + /// @param y Output: Y coordinate. + virtual void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const = 0; + + /// @brief Return the home-button pressed state. + virtual bool get_home_button_state() const = 0; +}; + +/// @brief Concept-constrained adapter that wraps any concrete touch driver +/// satisfying `TouchDriverConcept` behind the `ITouchDriver` interface. +/// +/// Usage: +/// @code +/// auto driver = std::make_shared(...); +/// std::shared_ptr touch = +/// std::make_shared>(driver); +/// @endcode +template struct TouchDriverAdapter : ITouchDriver { + /// Underlying concrete driver + std::shared_ptr driver; + + explicit TouchDriverAdapter(std::shared_ptr d) + : driver(std::move(d)) {} + + bool update(std::error_code &ec) override { return driver->update(ec); } + + void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const override { + driver->get_touch_point(num_touch_points, x, y); + } + + bool get_home_button_state() const override { return driver->get_home_button_state(); } +}; + +/// @brief Convenience factory: wrap a shared_ptr to a concrete touch driver in +/// a `TouchDriverAdapter` and return it as `std::shared_ptr`. +/// @tparam T Concrete driver type — must satisfy `TouchDriverConcept`. +template +std::shared_ptr make_touch_driver(std::shared_ptr driver) { + return std::make_shared>(std::move(driver)); +} } // namespace espp diff --git a/doc/en/input/st7123touch.rst b/doc/en/input/st7123touch.rst old mode 100644 new mode 100755 index 7dd5f3b70..f60fb0c26 --- a/doc/en/input/st7123touch.rst +++ b/doc/en/input/st7123touch.rst @@ -16,7 +16,10 @@ controller in a single IC. This driver accesses the touch side over I2C standalone controllers such as the GT911, as this can take the touch I2C endpoint offline. -The driver also provides: +``St7123Touch`` satisfies the ``espp::TouchDriverConcept``, so it can be wrapped +in the type-erased ``espp::ITouchDriver`` interface via +``espp::make_touch_driver()``. These shared type-erasure helpers now live in the +:doc:`touch` component and are reused by every espp touch driver and BSP: - ``espp::TouchDriverConcept`` — C++23 concept satisfied by any touch driver exposing ``update()``, ``get_touch_point()``, and ``get_home_button_state()``. diff --git a/doc/en/input/touch.rst b/doc/en/input/touch.rst old mode 100644 new mode 100755 index 0bd9ea9a9..10d0c322a --- a/doc/en/input/touch.rst +++ b/doc/en/input/touch.rst @@ -5,6 +5,17 @@ The `touch` component provides the shared `TouchPoint`, `TouchState`, `TouchpadData`, and `ITouchDevice` types used by ESPP touch controller drivers and BSPs. +It also provides the type-erasure helpers used to store any touch driver behind +a single runtime handle: + +- ``espp::TouchDriverConcept`` — C++23 concept satisfied by any touch driver + exposing ``update()``, ``get_touch_point()``, and ``get_home_button_state()``. +- ``espp::ITouchDriver`` — abstract type-erased interface backed by the concept. +- ``espp::TouchDriverAdapter`` — concept-constrained adapter wrapping any + concrete driver (e.g. ``Gt911`` or ``St7123Touch``) behind ``ITouchDriver``. +- ``espp::make_touch_driver(driver)`` — convenience factory returning a + ``std::shared_ptr``. + .. ------------------------------- Example ------------------------------------- .. toctree:: From da4244c77d45fea600a34a2bcf48d665d38da51a Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Wed, 24 Jun 2026 21:57:28 -0500 Subject: [PATCH 08/10] working touch on st7123 m5stack tab5 --- .../example/main/m5stack_tab5_example.cpp | 4 +- .../m5stack-tab5/include/m5stack-tab5.hpp | 10 +-- components/m5stack-tab5/src/audio.cpp | 35 ++++------ components/m5stack-tab5/src/touchpad.cpp | 66 +++++++++++++++---- components/m5stack-tab5/src/video.cpp | 7 +- .../example/main/st7123touch_example.cpp | 11 ++-- .../st7123touch/include/st7123touch.hpp | 13 +++- 7 files changed, 95 insertions(+), 51 deletions(-) mode change 100644 => 100755 components/m5stack-tab5/src/video.cpp mode change 100755 => 100644 components/st7123touch/example/main/st7123touch_example.cpp diff --git a/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp b/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp index fcadfe6fb..6efab66df 100644 --- a/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp +++ b/components/m5stack-tab5/example/main/m5stack_tab5_example.cpp @@ -43,7 +43,7 @@ static bool load_audio(size_t &out_size, size_t &out_sample_rate); static void play_click(espp::M5StackTab5 &tab5); extern "C" void app_main(void) { - espp::Logger logger({.tag = "M5Stack Tab5 Example", .level = espp::Logger::Verbosity::DEBUG}); + espp::Logger logger({.tag = "M5Stack Tab5 Example", .level = espp::Logger::Verbosity::INFO}); logger.info("Starting example!"); //! [m5stack tab5 example] @@ -92,7 +92,7 @@ extern "C" void app_main(void) { static auto previous_touchpad_data = tab5.touchpad_convert(touch); auto touchpad_data = tab5.touchpad_convert(touch); if (touchpad_data != previous_touchpad_data) { - logger.info("Touch: {}", touchpad_data); + logger.debug("Touch: {}", touchpad_data); previous_touchpad_data = touchpad_data; // if the button is pressed, clear the circles if (touchpad_data.btn_state) { diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp index 2389da739..ceef5fe92 100644 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -33,7 +33,6 @@ #include "es8388.hpp" #include "gt911.hpp" #include "i2c.hpp" -#include "st7123touch.hpp" #include "ili9881.hpp" #include "ina226.hpp" #include "interrupt.hpp" @@ -41,6 +40,7 @@ #include "pi4ioe5v.hpp" #include "rx8130ce.hpp" #include "st7123.hpp" +#include "st7123touch.hpp" #include "touchpad_input.hpp" // #include "wifi_ap.hpp" // #include "wifi_sta.hpp" @@ -701,8 +701,11 @@ class M5StackTab5 : public BaseComponent { // Component instances std::shared_ptr> touch_i2c_device_; - std::shared_ptr touch_driver_; ///< Concept-erased touch driver (GT911 or ST7123) + std::shared_ptr + touch_driver_; ///< Concept-erased touch driver (GT911 or ST7123) std::shared_ptr touchpad_input_; + std::unique_ptr + touch_poll_task_; ///< Polls the touch driver when it does not drive TP_INT (e.g. ST7123) std::recursive_mutex touchpad_data_mutex_; TouchpadData touchpad_data_; touch_callback_t touch_callback_{nullptr}; @@ -723,13 +726,10 @@ class M5StackTab5 : public BaseComponent { i2s_chan_handle_t audio_tx_handle{nullptr}; i2s_chan_handle_t audio_rx_handle{nullptr}; i2s_std_config_t audio_std_cfg{}; - i2s_event_callbacks_t audio_tx_callbacks_{}; - i2s_event_callbacks_t audio_rx_callbacks_{}; std::vector audio_tx_buffer; std::vector audio_rx_buffer; StreamBufferHandle_t audio_tx_stream; StreamBufferHandle_t audio_rx_stream; - std::atomic has_sound{false}; std::atomic recording_{false}; std::function audio_rx_callback_{nullptr}; diff --git a/components/m5stack-tab5/src/audio.cpp b/components/m5stack-tab5/src/audio.cpp index 60be9ecea..62d64b4ef 100644 --- a/components/m5stack-tab5/src/audio.cpp +++ b/components/m5stack-tab5/src/audio.cpp @@ -4,15 +4,6 @@ // Audio Functions // //////////////////////// -static TaskHandle_t play_audio_task_handle_ = NULL; - -static bool IRAM_ATTR audio_tx_sent_callback(i2s_chan_handle_t handle, i2s_event_data_t *event, - void *user_ctx) { - // notify the main task that we're done - vTaskNotifyGiveFromISR(play_audio_task_handle_, NULL); - return true; -} - namespace espp { bool M5StackTab5::initialize_audio(uint32_t sample_rate, @@ -62,10 +53,13 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &audio_tx_handle, &audio_rx_handle)); - // Configure I2S for stereo output (needed for proper ES8388 operation) + // Configure I2S for stereo output (needed for proper ES8388 operation). The + // playback data is interleaved 16-bit stereo, so the slot mode MUST be stereo + // — a mono slot plays interleaved stereo at the wrong rate and garbles L/R. audio_std_cfg = { .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate), - .slot_cfg = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), + .slot_cfg = + I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), .gpio_cfg = {.mclk = audio_mclk_io, .bclk = audio_sclk_io, .ws = audio_lrck_io, @@ -157,17 +151,10 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, }; ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(audio_rx_handle, &tdm_cfg)); - // Register TX done callback - memset(&audio_tx_callbacks_, 0, sizeof(audio_tx_callbacks_)); - audio_tx_callbacks_.on_sent = audio_tx_sent_callback; - i2s_channel_register_event_callback(audio_tx_handle, &audio_tx_callbacks_, NULL); - // now enable both channels ESP_ERROR_CHECK(i2s_channel_enable(audio_tx_handle)); ESP_ERROR_CHECK(i2s_channel_enable(audio_rx_handle)); - play_audio_task_handle_ = xTaskGetCurrentTaskHandle(); - // Stream buffers and task auto tx_buf_size = calc_audio_buffer_size(sample_rate); audio_tx_buffer.resize(tx_buf_size); @@ -216,11 +203,10 @@ void M5StackTab5::play_audio(const uint8_t *data, uint32_t num_bytes) { if (!audio_initialized_ || !data || num_bytes == 0) { return; } - if (has_sound) { - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - } + // Don't block here: append what fits into the stream buffer and return + // immediately. The audio task drains it to the I2S peripheral. Matches the + // esp-box / t-deck playback path. xStreamBufferSendFromISR(audio_tx_stream, data, num_bytes, NULL); - has_sound = true; } void M5StackTab5::play_audio(std::span data) { @@ -247,6 +233,9 @@ void M5StackTab5::stop_audio_recording() { bool M5StackTab5::audio_task_callback(std::mutex &m, std::condition_variable &cv, bool &task_notified) { // Playback: write next buffer worth of audio from stream buffer + // Queue the next I2S out frame to write (matches the esp-box / t-deck path): + // always write a full, frame-aligned buffer with the queued samples zero- + // padded up to buffer_size so the I2S DMA is fed at a constant cadence. uint16_t available = xStreamBufferBytesAvailable(audio_tx_stream); int buffer_size = audio_tx_buffer.size(); available = std::min(available, buffer_size); @@ -256,7 +245,7 @@ bool M5StackTab5::audio_task_callback(std::mutex &m, std::condition_variable &cv i2s_channel_write(audio_tx_handle, tx_buf, buffer_size, NULL, portMAX_DELAY); } else { xStreamBufferReceive(audio_tx_stream, tx_buf, available, 0); - i2s_channel_write(audio_tx_handle, tx_buf, available, NULL, portMAX_DELAY); + i2s_channel_write(audio_tx_handle, tx_buf, buffer_size, NULL, portMAX_DELAY); } // Recording: read from RX channel and invoke callback diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp index 89da47eb9..752e6b78f 100644 --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -12,9 +12,6 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { touch_callback_ = callback; - // add touch interrupt - interrupts_.add_interrupt(touch_interrupt_pin_); - // Determine which touch controller is present based on the detected display. // If the LCD has already been initialised, reuse the cached result; otherwise // run the I2C probe now. @@ -24,12 +21,19 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { } using namespace std::chrono_literals; - + std::error_code ec; touch_i2c_device_ = internal_i2c_.add_device( { - .device_address = controller == DisplayController::ST7123 ? St7123TouchDriver::DEFAULT_ADDRESS : TouchDriver::DEFAULT_ADDRESS_2, - .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .device_address = controller == DisplayController::ST7123 + ? St7123TouchDriver::DEFAULT_ADDRESS + : TouchDriver::DEFAULT_ADDRESS_2, + // Keep the touch transaction timeout short. A touch read normally + // completes in well under 1 ms; the bus default (200 ms, sized for the + // IMU's large config write) would hold the shared internal I2C bus for + // a fifth of a second whenever the ST7123 stalls, starving the IMU/RTC/ + // battery reads. Fail fast and let the next poll retry instead. + .timeout_ms = 10, .scl_speed_hz = internal_i2c_.config().clk_speed, .log_level = espp::Logger::Verbosity::WARN, }, @@ -40,17 +44,46 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { } if (controller == DisplayController::ST7123) { - // The ST7123 is a TDDI chip: its touch engine is enabled by LCD_RST, which - // was already pulsed during initialize_lcd(). Toggling TP_RST here is - // harmful — on some boards it takes the touch I2C endpoint offline. - logger_.info("ST7123 variant detected — using integrated touch controller (skipping TP_RST)"); + // The pin we historically called "TP_RST" (IO expander 0x43 P5) is in fact + // the ST7123 touch ENABLE line (esp-bsp's BSP_TOUCH_EN). The touch engine + // needs a clean disable→enable (low→high) edge to boot its firmware and + // start scanning; without it the chip answers I2C and reports correct + // firmware/config registers but never produces coordinates. Pulse it here. + logger_.info( + "ST7123 variant detected — pulsing touch enable (0x43 P5) to start the touch engine"); + touch_reset(true); // drive 0x43 P5 low (disable / reset touch) + std::this_thread::sleep_for(20ms); + touch_reset(false); // drive 0x43 P5 high (enable touch) + std::this_thread::sleep_for(50ms); auto driver = std::make_shared(St7123TouchDriver::Config{ - .write = espp::make_i2c_addressed_write(touch_i2c_device_), - .read = espp::make_i2c_addressed_read(touch_i2c_device_), + // The ST7123 only holds its register pointer within a single + // transaction, so reads must be a repeated-START write-then-read. + .write_then_read = espp::make_i2c_addressed_write_then_read(touch_i2c_device_), .address = St7123TouchDriver::DEFAULT_ADDRESS, .log_level = espp::Logger::Verbosity::WARN}); touch_driver_ = espp::make_touch_driver(std::move(driver)); + + // The ST7123's TP_INT line is not asserted like a standalone GT911's, so an + // interrupt-driven read never fires. Poll the controller from a background + // task instead (the same update()/get_touch_point() flow the standalone + // st7123touch example uses). + touch_poll_task_ = std::make_unique(espp::Task::Config{ + .callback = [this](std::mutex &m, std::condition_variable &cv) -> bool { + auto now = std::chrono::high_resolution_clock::now(); + if (update_touch() && touch_callback_) { + touch_callback_(touchpad_data()); + } + std::unique_lock lock(m); + // ~33 Hz is plenty for responsive touch and is gentle on the + // shared internal I2C bus. Polling the TDDI ST7123 at 100 Hz while + // it time-shares scanning with the display stresses it into I2C + // stalls and eventually wedges its touch engine. + cv.wait_until(lock, now + std::chrono::milliseconds(20)); + return false; // keep running + }, + .task_config = {.name = "tab5 touch poll", .stack_size_bytes = 4 * 1024}}); + touch_poll_task_->start(); } else { // ILI9881 (and UNKNOWN fallback) use a standalone GT911 touch controller // that requires a hardware reset via the IO expander before being used. @@ -67,8 +100,11 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 .log_level = espp::Logger::Verbosity::WARN}); touch_driver_ = espp::make_touch_driver(std::move(driver)); + + // The GT911 drives TP_INT, so read it from the touch interrupt. + interrupts_.add_interrupt(touch_interrupt_pin_); } - + touchpad_input_ = std::make_shared( TouchpadInput::Config{.touchpad_read = std::bind_front(&M5StackTab5::touchpad_read, this), .swap_xy = false, @@ -96,6 +132,7 @@ bool M5StackTab5::update_touch() { touchpad_data_ = {}; return false; } + logger_.debug("Touch driver update returned new_data={}", new_data); if (!new_data) return false; @@ -103,6 +140,9 @@ bool M5StackTab5::update_touch() { touch_driver_->get_touch_point(&temp_data.num_touch_points, &temp_data.x, &temp_data.y); temp_data.btn_state = touch_driver_->get_home_button_state(); + logger_.debug("Touch data: num_touch_points={}, x={}, y={}, btn_state={}", + temp_data.num_touch_points, temp_data.x, temp_data.y, temp_data.btn_state); + std::lock_guard lock(touchpad_data_mutex_); touchpad_data_ = temp_data; return true; diff --git a/components/m5stack-tab5/src/video.cpp b/components/m5stack-tab5/src/video.cpp old mode 100644 new mode 100755 index 2d9185b92..665f0035b --- a/components/m5stack-tab5/src/video.cpp +++ b/components/m5stack-tab5/src/video.cpp @@ -162,7 +162,12 @@ bool M5StackTab5::initialize_lcd() { } 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; + // The ST7123 is a TDDI part: its touch engine scans during the display + // blanking interval and is timed against the pixel clock the vendor init + // table was tuned for. Running the panel faster than the reference 70 MHz + // (M5Stack/esp-bsp value) shrinks the blanking window and desyncs the touch + // scan, so the panel shows but touch never reports. Keep this at 70 MHz. + dpi_cfg.dpi_clock_freq_mhz = 70; #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) dpi_cfg.in_color_format = LCD_COLOR_FMT_RGB565; dpi_cfg.out_color_format = LCD_COLOR_FMT_RGB565; diff --git a/components/st7123touch/example/main/st7123touch_example.cpp b/components/st7123touch/example/main/st7123touch_example.cpp old mode 100755 new mode 100644 index 9e839b8de..c8f389358 --- a/components/st7123touch/example/main/st7123touch_example.cpp +++ b/components/st7123touch/example/main/st7123touch_example.cpp @@ -30,10 +30,13 @@ extern "C" void app_main(void) { // it is non-copyable and non-movable — construct it directly inside the // shared_ptr rather than moving a stack instance into it. auto touch = std::make_shared(espp::St7123Touch::Config{ - .write = std::bind(&espp::I2c::write, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), - .read = std::bind(&espp::I2c::read, &i2c, std::placeholders::_1, std::placeholders::_2, - std::placeholders::_3), + // The ST7123 only holds its register pointer within a single I2C + // transaction, so register reads must be a repeated-START + // write-then-read rather than a separate write + read. + .write_then_read = [&i2c](uint8_t addr, const uint8_t *wdata, size_t wlen, uint8_t *rdata, + size_t rlen) -> bool { + return i2c.write_read(addr, wdata, wlen, rdata, rlen); + }, .log_level = espp::Logger::Verbosity::WARN, }); diff --git a/components/st7123touch/include/st7123touch.hpp b/components/st7123touch/include/st7123touch.hpp index 8ddeaa5ab..80e4b2668 100755 --- a/components/st7123touch/include/st7123touch.hpp +++ b/components/st7123touch/include/st7123touch.hpp @@ -38,9 +38,16 @@ class St7123Touch : public BasePeripheral { static constexpr uint8_t DEFAULT_ADDRESS = 0x55; /// @brief Configuration for the St7123Touch driver + /// @note The ST7123 latches its register pointer only for the duration of a + /// single I2C transaction. Register reads MUST therefore use a combined + /// repeated-START write-then-read; issuing the register-pointer write + /// and the data read as two separate transactions (with a STOP in + /// between) makes the chip return register 0x00 on every read. For that + /// reason the driver takes a `write_then_read` function rather than + /// separate write/read functions. struct Config { - BasePeripheral::write_fn write; ///< Function for writing to the ST7123 - BasePeripheral::read_fn read; ///< Function for reading from the ST7123 + BasePeripheral::write_then_read_fn + write_then_read; ///< Combined (repeated-START) write-then-read for the ST7123 uint8_t address = DEFAULT_ADDRESS; ///< I2C address of the chip espp::Logger::Verbosity log_level{ espp::Logger::Verbosity::WARN}; ///< Log verbosity for the driver @@ -49,7 +56,7 @@ class St7123Touch : public BasePeripheral { /// @brief Constructor for the St7123Touch driver /// @param config The configuration for the driver explicit St7123Touch(const Config &config) - : BasePeripheral({.address = config.address, .write = config.write, .read = config.read}, + : BasePeripheral({.address = config.address, .write_then_read = config.write_then_read}, "St7123Touch", config.log_level) {} /// @brief Update the touch state by reading from the ST7123 over I2C From b6493aac8260b017300d658f327450c2fa6e52f0 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 25 Jun 2026 09:43:42 -0500 Subject: [PATCH 09/10] improve functionality and ensure touch works --- .../m5stack-tab5/include/m5stack-tab5.hpp | 15 +- components/m5stack-tab5/src/audio.cpp | 137 +++++++++--------- components/m5stack-tab5/src/imu.cpp | 7 +- components/m5stack-tab5/src/touchpad.cpp | 40 ++--- .../st7123touch/include/st7123touch.hpp | 24 +-- 5 files changed, 116 insertions(+), 107 deletions(-) mode change 100644 => 100755 components/m5stack-tab5/include/m5stack-tab5.hpp mode change 100644 => 100755 components/m5stack-tab5/src/imu.cpp mode change 100644 => 100755 components/m5stack-tab5/src/touchpad.cpp diff --git a/components/m5stack-tab5/include/m5stack-tab5.hpp b/components/m5stack-tab5/include/m5stack-tab5.hpp old mode 100644 new mode 100755 index ceef5fe92..dde9de2f3 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -664,8 +664,17 @@ class M5StackTab5 : public BaseComponent { .scl_io_num = internal_i2c_scl, .sda_pullup_en = GPIO_PULLUP_ENABLE, .scl_pullup_en = GPIO_PULLUP_ENABLE, - .timeout_ms = 200, // needs to be long enough for writing imu config file (8k) - .clk_speed = 400'000}}; + .timeout_ms = 200, + // Standard-mode (100 kHz) shared internal bus. At 400 kHz the + // bus is marginal with this many devices on it — the ST7123 + // touch reads time out and a hung transaction corrupts a + // concurrent BMI270 read (correlated touch I/O errors + IMU + // vector jumps while dragging). Per-device speed mixing + // (touch @100k, rest @400k) did NOT help: any 400 kHz traffic + // disturbs the bus. 100 kHz everywhere is clean. The IMU's + // 8 KB config upload is chunked (see initialize_imu) so it + // still fits the 200 ms transaction timeout at this speed. + .clk_speed = 100'000}}; // Interrupt configurations espp::Interrupt::PinConfig button_interrupt_pin_{ @@ -704,8 +713,6 @@ class M5StackTab5 : public BaseComponent { std::shared_ptr touch_driver_; ///< Concept-erased touch driver (GT911 or ST7123) std::shared_ptr touchpad_input_; - std::unique_ptr - touch_poll_task_; ///< Polls the touch driver when it does not drive TP_INT (e.g. ST7123) std::recursive_mutex touchpad_data_mutex_; TouchpadData touchpad_data_; touch_callback_t touch_callback_{nullptr}; diff --git a/components/m5stack-tab5/src/audio.cpp b/components/m5stack-tab5/src/audio.cpp index 62d64b4ef..621e82f32 100644 --- a/components/m5stack-tab5/src/audio.cpp +++ b/components/m5stack-tab5/src/audio.cpp @@ -15,6 +15,74 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, return true; } + // I2S standard channel for TX (playback) + logger_.info("Creating I2S channel for playback (TX)"); + i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); + chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &audio_tx_handle, &audio_rx_handle)); + + // Configure I2S for stereo output (needed for proper ES8388 operation). The + // playback data is interleaved 16-bit stereo, so the slot mode MUST be stereo + // — a mono slot plays interleaved stereo at the wrong rate and garbles L/R. + audio_std_cfg = { + .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate), + .slot_cfg = + I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), + .gpio_cfg = {.mclk = audio_mclk_io, + .bclk = audio_sclk_io, + .ws = audio_lrck_io, + .dout = audio_dsdin_io, // ES8388 DSDIN (playback data input) + .din = audio_asdout_io, // ES7210 ASDOUT (record data output) + .invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}, + }; + logger_.info("Configuring I2S standard mode with sample rate {} Hz", sample_rate); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(audio_tx_handle, &audio_std_cfg)); + + // Optional RX channel for recording (ES7210) + logger_.info("Creating I2S channel for recording (RX)"); + i2s_tdm_config_t tdm_cfg = { + .clk_cfg = + { + .sample_rate_hz = (uint32_t)48000, + .clk_src = I2S_CLK_SRC_DEFAULT, + .ext_clk_freq_hz = 0, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + .bclk_div = 8, + }, + .slot_cfg = {.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = (i2s_tdm_slot_mask_t)(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | + I2S_TDM_SLOT2 | I2S_TDM_SLOT3), + .ws_width = I2S_TDM_AUTO_WS_WIDTH, + .ws_pol = false, + .bit_shift = true, + .left_align = false, + .big_endian = false, + .bit_order_lsb = false, + .skip_mask = false, + .total_slot = I2S_TDM_AUTO_SLOT_NUM}, + .gpio_cfg = {.mclk = audio_mclk_io, + .bclk = audio_sclk_io, + .ws = audio_lrck_io, + .dout = audio_dsdin_io, // ES8388 DSDIN (playback data input) + .din = audio_asdout_io, // ES7210 ASDOUT (record data output) + .invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}, + }; + ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(audio_rx_handle, &tdm_cfg)); + + // Stream buffers and task + auto tx_buf_size = calc_audio_buffer_size(sample_rate); + audio_tx_buffer.resize(tx_buf_size); + audio_tx_stream = xStreamBufferCreate(tx_buf_size * 4, 0); + xStreamBufferReset(audio_tx_stream); + // RX buffer for recording + audio_rx_buffer.resize(tx_buf_size); + + // now enable both channels + ESP_ERROR_CHECK(i2s_channel_enable(audio_tx_handle)); + ESP_ERROR_CHECK(i2s_channel_enable(audio_rx_handle)); + std::error_code ec; es8388_i2c_device_ = internal_i2c_.add_device( { @@ -47,29 +115,6 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, set_es7210_write(espp::make_i2c_addressed_write(es7210_i2c_device_)); set_es7210_read(espp::make_i2c_addressed_read_register(es7210_i2c_device_)); - // I2S standard channel for TX (playback) - logger_.info("Creating I2S channel for playback (TX)"); - i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER); - chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &audio_tx_handle, &audio_rx_handle)); - - // Configure I2S for stereo output (needed for proper ES8388 operation). The - // playback data is interleaved 16-bit stereo, so the slot mode MUST be stereo - // — a mono slot plays interleaved stereo at the wrong rate and garbles L/R. - audio_std_cfg = { - .clk_cfg = I2S_STD_CLK_DEFAULT_CONFIG(sample_rate), - .slot_cfg = - I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO), - .gpio_cfg = {.mclk = audio_mclk_io, - .bclk = audio_sclk_io, - .ws = audio_lrck_io, - .dout = audio_dsdin_io, // ES8388 DSDIN (playback data input) - .din = audio_asdout_io, // ES7210 ASDOUT (record data output) - .invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}, - }; - logger_.info("Configuring I2S standard mode with sample rate {} Hz", sample_rate); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(audio_tx_handle, &audio_std_cfg)); - // ES8388 DAC playback config audio_hal_codec_config_t es8388_cfg{}; es8388_cfg.codec_mode = AUDIO_HAL_CODEC_MODE_DECODE; @@ -118,51 +163,7 @@ bool M5StackTab5::initialize_audio(uint32_t sample_rate, } es7210_adc_ctrl_state(AUDIO_HAL_CODEC_MODE_ENCODE, AUDIO_HAL_CTRL_START); - // Optional RX channel for recording (ES7210) - logger_.info("Creating I2S channel for recording (RX)"); - i2s_tdm_config_t tdm_cfg = { - .clk_cfg = - { - .sample_rate_hz = (uint32_t)48000, - .clk_src = I2S_CLK_SRC_DEFAULT, - .ext_clk_freq_hz = 0, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - .bclk_div = 8, - }, - .slot_cfg = {.data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = (i2s_tdm_slot_mask_t)(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | - I2S_TDM_SLOT2 | I2S_TDM_SLOT3), - .ws_width = I2S_TDM_AUTO_WS_WIDTH, - .ws_pol = false, - .bit_shift = true, - .left_align = false, - .big_endian = false, - .bit_order_lsb = false, - .skip_mask = false, - .total_slot = I2S_TDM_AUTO_SLOT_NUM}, - .gpio_cfg = {.mclk = audio_mclk_io, - .bclk = audio_sclk_io, - .ws = audio_lrck_io, - .dout = audio_dsdin_io, // ES8388 DSDIN (playback data input) - .din = audio_asdout_io, // ES7210 ASDOUT (record data output) - .invert_flags = {.mclk_inv = false, .bclk_inv = false, .ws_inv = false}}, - }; - ESP_ERROR_CHECK(i2s_channel_init_tdm_mode(audio_rx_handle, &tdm_cfg)); - - // now enable both channels - ESP_ERROR_CHECK(i2s_channel_enable(audio_tx_handle)); - ESP_ERROR_CHECK(i2s_channel_enable(audio_rx_handle)); - - // Stream buffers and task - auto tx_buf_size = calc_audio_buffer_size(sample_rate); - audio_tx_buffer.resize(tx_buf_size); - audio_tx_stream = xStreamBufferCreate(tx_buf_size * 4, 0); - xStreamBufferReset(audio_tx_stream); - // RX buffer for recording - audio_rx_buffer.resize(tx_buf_size); - + // now make the audio task logger_.info("Creating audio task for playback and recording"); using namespace std::placeholders; audio_task_ = espp::Task::make_unique( diff --git a/components/m5stack-tab5/src/imu.cpp b/components/m5stack-tab5/src/imu.cpp old mode 100644 new mode 100755 index 9b38c8a5c..0bec65995 --- a/components/m5stack-tab5/src/imu.cpp +++ b/components/m5stack-tab5/src/imu.cpp @@ -14,7 +14,7 @@ bool M5StackTab5::initialize_imu(const Imu::filter_fn &orientation_filter) { imu_i2c_device_ = internal_i2c_.add_device( { .device_address = Imu::DEFAULT_ADDRESS, - .timeout_ms = static_cast(internal_i2c_.config().timeout_ms), + .timeout_ms = 200, .scl_speed_hz = internal_i2c_.config().clk_speed, .log_level = espp::Logger::Verbosity::WARN, }, @@ -39,6 +39,11 @@ bool M5StackTab5::initialize_imu(const Imu::filter_fn &orientation_filter) { .gyroscope_performance_mode = Imu::GyroscopePerformanceMode::PERFORMANCE_OPTIMIZED, }, .orientation_filter = orientation_filter, + // Upload the ~8 KB config file in 256-byte bursts rather than one big + // transaction. The shared internal bus runs at 100 kHz, where a single + // 8 KB write (~740 ms) would blow the 200 ms transaction timeout; each + // 256-byte chunk takes ~23 ms, well within it. + .burst_write_size = 256, .auto_init = true, .log_level = espp::Logger::Verbosity::WARN}); diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp old mode 100644 new mode 100755 index 752e6b78f..753bd9bde --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -32,8 +32,8 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { // completes in well under 1 ms; the bus default (200 ms, sized for the // IMU's large config write) would hold the shared internal I2C bus for // a fifth of a second whenever the ST7123 stalls, starving the IMU/RTC/ - // battery reads. Fail fast and let the next poll retry instead. - .timeout_ms = 10, + // battery reads. Fail fast and let the next read retry instead. + .timeout_ms = 20, .scl_speed_hz = internal_i2c_.config().clk_speed, .log_level = espp::Logger::Verbosity::WARN, }, @@ -57,33 +57,23 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { std::this_thread::sleep_for(50ms); auto driver = std::make_shared(St7123TouchDriver::Config{ - // The ST7123 only holds its register pointer within a single - // transaction, so reads must be a repeated-START write-then-read. - .write_then_read = espp::make_i2c_addressed_write_then_read(touch_i2c_device_), + // Use two separate transactions (write the register pointer, then read) + // rather than a combined repeated-START. The longer combined transaction + // is more prone to I/O errors when reads run from the touch interrupt + // handler on this board. + .write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), .address = St7123TouchDriver::DEFAULT_ADDRESS, .log_level = espp::Logger::Verbosity::WARN}); touch_driver_ = espp::make_touch_driver(std::move(driver)); - // The ST7123's TP_INT line is not asserted like a standalone GT911's, so an - // interrupt-driven read never fires. Poll the controller from a background - // task instead (the same update()/get_touch_point() flow the standalone - // st7123touch example uses). - touch_poll_task_ = std::make_unique(espp::Task::Config{ - .callback = [this](std::mutex &m, std::condition_variable &cv) -> bool { - auto now = std::chrono::high_resolution_clock::now(); - if (update_touch() && touch_callback_) { - touch_callback_(touchpad_data()); - } - std::unique_lock lock(m); - // ~33 Hz is plenty for responsive touch and is gentle on the - // shared internal I2C bus. Polling the TDDI ST7123 at 100 Hz while - // it time-shares scanning with the display stresses it into I2C - // stalls and eventually wedges its touch engine. - cv.wait_until(lock, now + std::chrono::milliseconds(20)); - return false; // keep running - }, - .task_config = {.name = "tab5 touch poll", .stack_size_bytes = 4 * 1024}}); - touch_poll_task_->start(); + // Now that the touch engine is actually scanning (it needed the correct DPI + // pixel clock to run), the ST7123 drives its TP_INT line low on each new + // touch report — just like the GT911 — so read it from the touch interrupt + // rather than polling. Reading only when a frame is ready avoids hammering + // the shared internal I2C bus and the mid-scan I2C stalls that aggressive + // polling provoked (which eventually wedged the touch engine). + interrupts_.add_interrupt(touch_interrupt_pin_); } else { // ILI9881 (and UNKNOWN fallback) use a standalone GT911 touch controller // that requires a hardware reset via the IO expander before being used. diff --git a/components/st7123touch/include/st7123touch.hpp b/components/st7123touch/include/st7123touch.hpp index 80e4b2668..635b08669 100755 --- a/components/st7123touch/include/st7123touch.hpp +++ b/components/st7123touch/include/st7123touch.hpp @@ -38,16 +38,19 @@ class St7123Touch : public BasePeripheral { static constexpr uint8_t DEFAULT_ADDRESS = 0x55; /// @brief Configuration for the St7123Touch driver - /// @note The ST7123 latches its register pointer only for the duration of a - /// single I2C transaction. Register reads MUST therefore use a combined - /// repeated-START write-then-read; issuing the register-pointer write - /// and the data read as two separate transactions (with a STOP in - /// between) makes the chip return register 0x00 on every read. For that - /// reason the driver takes a `write_then_read` function rather than - /// separate write/read functions. + /// @note Provide EITHER a combined `write_then_read` (a repeated-START + /// write-then-read, no STOP between the register-pointer write and the + /// data read) OR a separate `write` + `read` pair. If `write_then_read` + /// is set it takes precedence; otherwise the driver writes the register + /// pointer and reads the data as two separate transactions. Some boards + /// are happier with the separate form (e.g. when reads run from an + /// interrupt handler and the longer combined transaction is more prone + /// to I/O errors). struct Config { + BasePeripheral::write_fn write; ///< Write function (paired with `read` for separate reads) + BasePeripheral::read_fn read; ///< Read function (paired with `write` for separate reads) BasePeripheral::write_then_read_fn - write_then_read; ///< Combined (repeated-START) write-then-read for the ST7123 + write_then_read; ///< Combined (repeated-START) write-then-read; takes precedence if set uint8_t address = DEFAULT_ADDRESS; ///< I2C address of the chip espp::Logger::Verbosity log_level{ espp::Logger::Verbosity::WARN}; ///< Log verbosity for the driver @@ -56,7 +59,10 @@ class St7123Touch : public BasePeripheral { /// @brief Constructor for the St7123Touch driver /// @param config The configuration for the driver explicit St7123Touch(const Config &config) - : BasePeripheral({.address = config.address, .write_then_read = config.write_then_read}, + : BasePeripheral({.address = config.address, + .write = config.write, + .read = config.read, + .write_then_read = config.write_then_read}, "St7123Touch", config.log_level) {} /// @brief Update the touch state by reading from the ST7123 over I2C From 712dddc524b83750b1ae25832b5d7e0516c859c4 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Thu, 25 Jun 2026 10:07:19 -0500 Subject: [PATCH 10/10] low log level of io expander --- components/m5stack-tab5/src/m5stack-tab5.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/m5stack-tab5/src/m5stack-tab5.cpp b/components/m5stack-tab5/src/m5stack-tab5.cpp index feb62f1c4..b7fb4e038 100644 --- a/components/m5stack-tab5/src/m5stack-tab5.cpp +++ b/components/m5stack-tab5/src/m5stack-tab5.cpp @@ -49,7 +49,7 @@ bool M5StackTab5::initialize_io_expanders() { .pull_down_mask = IOX_0x43_PULL_DOWNS, .write = espp::make_i2c_addressed_write(ioexp_0x43_i2c_device_), .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x43_i2c_device_), - .log_level = Logger::Verbosity::INFO}); + .log_level = Logger::Verbosity::WARN}); ioexp_0x44_ = std::make_shared(IoExpander::Config{ .device_address = 0x44, .direction_mask = IOX_0x44_DIRECTION_MASK, @@ -59,7 +59,7 @@ bool M5StackTab5::initialize_io_expanders() { .pull_down_mask = IOX_0x44_PULL_DOWNS, .write = espp::make_i2c_addressed_write(ioexp_0x44_i2c_device_), .write_then_read = espp::make_i2c_addressed_write_then_read(ioexp_0x44_i2c_device_), - .log_level = Logger::Verbosity::INFO}); + .log_level = Logger::Verbosity::WARN}); logger_.info("IO expanders initialized"); return true;