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 1/6] 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 2/6] 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 3/6] 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 4/6] 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 5/6] 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 6/6] 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;