diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 06b8cce8a..ba04d0aa5 100755 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -203,6 +203,8 @@ jobs: target: esp32s3 - path: 'components/st25dv/example' target: esp32s3 + - path: 'components/st7123touch/example' + target: esp32s3 - path: 'components/state_machine/example' target: esp32 - path: 'components/t-deck/example' @@ -219,6 +221,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 bfa0e014f..c386e8670 100755 --- a/.github/workflows/upload_components.yml +++ b/.github/workflows/upload_components.yml @@ -122,6 +122,7 @@ jobs: components/socket components/spi components/st25dv + components/st7123touch components/state_machine components/t_keyboard components/t-deck @@ -130,6 +131,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 8cb4c5a20..37c517681 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 spi task tt21100 icm42607 + REQUIRES driver esp_driver_i2s base_component codec display display_drivers i2c input_drivers interrupt gt911 spi 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 7c74a8942..d971f8f49 100644 --- a/components/esp-box/idf_component.yml +++ b/components/esp-box/idf_component.yml @@ -27,6 +27,7 @@ dependencies: espp/gt911: '>=1.0' espp/spi: '>=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 2a8ac26d4..276a5f92a 100644 --- a/components/esp-box/include/esp-box.hpp +++ b/components/esp-box/include/esp-box.hpp @@ -24,6 +24,7 @@ #include "interrupt.hpp" #include "spi.hpp" #include "st7789.hpp" +#include "touch.hpp" #include "touchpad_input.hpp" #include "tt21100.hpp" @@ -355,8 +356,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(); @@ -506,8 +505,7 @@ class EspBox : public BaseComponent { // touch std::shared_ptr> touch_i2c_device_; - 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 b6d6ff597..f023f79c1 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; } @@ -28,7 +28,7 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { return false; } logger_.info("Initializing GT911"); - gt911_ = std::make_unique( + touch_driver_ = std::make_shared( espp::Gt911::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), .read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); @@ -48,7 +48,7 @@ bool EspBox::initialize_touch(const EspBox::touch_callback_t &callback) { return false; } logger_.info("Initializing TT21100"); - tt21100_ = std::make_unique( + touch_driver_ = std::make_shared( espp::Tt21100::Config{.read = espp::make_i2c_addressed_read(touch_i2c_device_), .log_level = espp::Logger::Verbosity::WARN}); } break; @@ -65,43 +65,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; @@ -109,27 +80,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 c2c3379cc..d5e0f09ea 100644 --- a/components/ft5x06/example/main/ft5x06_example.cpp +++ b/components/ft5x06/example/main/ft5x06_example.cpp @@ -39,14 +39,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..7a673a775 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,35 +50,65 @@ class Ft5x06 : public BasePeripheral<> { } } - /// @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); - } - - /// @brief Get the touch point. - /// @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. + /// @brief Update the cached touch state. /// @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); + /// @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; + return false; } - *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); + 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; + 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]), + }; } - *x = ((data[0] & 0x0f) << 8) + data[1]; - *y = ((data[2] & 0x0f) << 8) + data[3]; - logger_.info("Got touch ({}, {})", *x, *y); + 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. + /// @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 touch_state().num_touch_points; } + + /// @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. + 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 gesture that was detected. @@ -181,5 +213,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/CMakeLists.txt b/components/m5stack-tab5/CMakeLists.txt old mode 100644 new mode 100755 index 6342405f1..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 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/m5stack-tab5/example/CMakeLists.txt b/components/m5stack-tab5/example/CMakeLists.txt index 51a119d09..ca930545a 100644 --- a/components/m5stack-tab5/example/CMakeLists.txt +++ b/components/m5stack-tab5/example/CMakeLists.txt @@ -32,7 +32,9 @@ set(EXTRA_COMPONENT_DIRS "../../../components/rx8130ce" "../../../components/pi4ioe5v" "../../../components/spi" + "../../../components/st7123touch" "../../../components/task" + "../../../components/touch" ) set( 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/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 old mode 100644 new mode 100755 index 581e7c861..dde9de2f3 --- a/components/m5stack-tab5/include/m5stack-tab5.hpp +++ b/components/m5stack-tab5/include/m5stack-tab5.hpp @@ -40,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" @@ -107,9 +108,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; @@ -660,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_{ @@ -697,7 +710,8 @@ class M5StackTab5 : public BaseComponent { // Component instances std::shared_ptr> touch_i2c_device_; - std::shared_ptr touch_driver_; + 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_; @@ -719,13 +733,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..621e82f32 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, @@ -24,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( { @@ -56,26 +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) - 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), - .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; @@ -124,58 +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)); - - // 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); - 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( @@ -216,11 +204,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 +234,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 +246,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/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/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; diff --git a/components/m5stack-tab5/src/touchpad.cpp b/components/m5stack-tab5/src/touchpad.cpp old mode 100644 new mode 100755 index 71612df76..753bd9bde --- a/components/m5stack-tab5/src/touchpad.cpp +++ b/components/m5stack-tab5/src/touchpad.cpp @@ -8,25 +8,32 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { 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_); + // 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(); + } - // Reset touch controller via expander if available - touch_reset(true); using namespace std::chrono_literals; - std::this_thread::sleep_for(10ms); - touch_reset(false); - std::this_thread::sleep_for(50ms); std::error_code ec; touch_i2c_device_ = internal_i2c_.add_device( { - .device_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 read retry instead. + .timeout_ms = 20, .scl_speed_hz = internal_i2c_.config().clk_speed, .log_level = espp::Logger::Verbosity::WARN, }, @@ -36,14 +43,58 @@ bool M5StackTab5::initialize_touch(const touch_callback_t &callback) { return false; } - // Create touch driver instance - touch_driver_ = std::make_shared( - TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), - .read = espp::make_i2c_addressed_read(touch_i2c_device_), - .address = TouchDriver::DEFAULT_ADDRESS_2, // GT911 0x14 address - .log_level = espp::Logger::Verbosity::WARN}); + if (controller == DisplayController::ST7123) { + // 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{ + // 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)); + + // 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. + 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); + + auto driver = std::make_shared( + TouchDriver::Config{.write = espp::make_i2c_addressed_write(touch_i2c_device_), + .read = espp::make_i2c_addressed_read(touch_i2c_device_), + .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_); + } - // Create touchpad input wrapper touchpad_input_ = std::make_shared( TouchpadInput::Config{.touchpad_read = std::bind_front(&M5StackTab5::touchpad_read, this), .swap_xy = false, @@ -57,28 +108,31 @@ 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"); + logger_.error("No touch driver 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()); + logger_.error("could not update touch driver: {}", ec.message()); std::lock_guard lock(touchpad_data_mutex_); touchpad_data_ = {}; return false; } - if (!new_data) { + logger_.debug("Touch driver update returned new_data={}", new_data); + if (!new_data) 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 + + 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/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 new file mode 100755 index 000000000..220bf7d39 --- /dev/null +++ b/components/st7123touch/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + INCLUDE_DIRS "include" + REQUIRES "base_peripheral" "touch" + ) 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/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..c8f389358 --- /dev/null +++ b/components/st7123touch/example/main/st7123touch_example.cpp @@ -0,0 +1,85 @@ +#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. 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{ + // 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, + }); + + // 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(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/idf_component.yml b/components/st7123touch/idf_component.yml new file mode 100755 index 000000000..1a1eb4247 --- /dev/null +++ b/components/st7123touch/idf_component.yml @@ -0,0 +1,23 @@ +## 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' + espp/touch: '>=1.0' diff --git a/components/st7123touch/include/st7123touch.hpp b/components/st7123touch/include/st7123touch.hpp new file mode 100755 index 000000000..635b08669 --- /dev/null +++ b/components/st7123touch/include/st7123touch.hpp @@ -0,0 +1,179 @@ +#pragma once + +#include +#include + +#include "base_peripheral.hpp" +#include "touch.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 +/// \snippet st7123touch_example.cpp st7123touch example +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 + /// @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; 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 + }; + + /// @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, + .write_then_read = config.write_then_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/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 100755 index 000000000..7f0c84a47 --- /dev/null +++ b/components/touch/README.md @@ -0,0 +1,19 @@ +# 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. + +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 +`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..1ed1a8234 --- /dev/null +++ b/components/touch/include/touch.hpp @@ -0,0 +1,182 @@ +#pragma once + +#include +#include +#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(); } +}; + +/// @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/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..946053a2b 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) { @@ -176,13 +201,12 @@ class Tt21100 : public BasePeripheral<> { 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 { @@ -195,9 +219,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 9c97c3f3a..06ebe3bd2 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -159,6 +159,7 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/socket/example/main/socket_example.cpp \ $(PROJECT_PATH)/components/spi/example/main/spi_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 \ @@ -167,6 +168,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 \ @@ -360,6 +362,7 @@ INPUT = \ $(PROJECT_PATH)/components/socket/include/tcp_socket.hpp \ $(PROJECT_PATH)/components/spi/include/spi.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 \ @@ -373,6 +376,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 \ 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 acbeefa9b..dc6d2e749 100644 --- a/doc/en/input/index.rst +++ b/doc/en/input/index.rst @@ -8,8 +8,10 @@ Input APIs cst816 ft5x06 gt911 - tt21100 + st7123touch t_keyboard + touch + tt21100 encoder_input keypad_input pointer_input diff --git a/doc/en/input/st7123touch.rst b/doc/en/input/st7123touch.rst new file mode 100755 index 000000000..f60fb0c26 --- /dev/null +++ b/doc/en/input/st7123touch.rst @@ -0,0 +1,43 @@ +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. + +``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()``. +- ``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 +------------- + +.. include-build-file:: inc/st7123touch.inc 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 +``` diff --git a/doc/en/input/touch.rst b/doc/en/input/touch.rst new file mode 100755 index 000000000..10d0c322a --- /dev/null +++ b/doc/en/input/touch.rst @@ -0,0 +1,30 @@ +Touch Interface +*************** + +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:: + + 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 +```