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 Touch Interface 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<T> — concept-constrained adapter wrapping any concrete driver behind ITouchDriver.

  • espp::make_touch_driver(driver) — convenience factory returning a std::shared_ptr<ITouchDriver>.

API Reference

Header File

Classes

class St7123Touch : public espp::BasePeripheral<std::uint16_t>

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).

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].

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>(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<std::mutex> 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();

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.

Public Types

typedef std::function<bool(uint8_t)> probe_fn

Function to probe the peripheral

Param address:

The address to probe

Return:

True if the peripheral is found at the given address

Public Functions

inline explicit St7123Touch(const Config &config)

Constructor for the St7123Touch driver.

Parameters:

config – The configuration for the driver

inline bool update(std::error_code &ec)

Update the touch state by reading from the ST7123 over I2C.

Parameters:

ec – Error code to set if an I2C error occurs

Returns:

True when the read succeeded (regardless of whether a finger is actually touching), false on I2C error

inline uint8_t get_num_touch_points() const

Get the number of active touch points.

Returns:

Touch point count as of the last update() call

inline void get_touch_point(uint8_t *num_touch_points, uint16_t *x, uint16_t *y) const

Get the primary touch point coordinates.

Note

The values are cached from the last update() call.

Parameters:
  • num_touch_points – Output: number of active touch points

  • x – Output: X coordinate of the first active touch point

  • y – Output: Y coordinate of the first active touch point

inline bool get_home_button_state() const

Get the home-button state.

Returns:

Always false — the ST7123 does not expose a home button via I2C

inline bool probe(std::error_code &ec) const

Probe the peripheral

Note

This function is thread safe

Note

If the probe function is not set, this function will return false and set the error code to operation_not_supported

Note

This function is only available if UseAddress is true

Parameters:

ec – The error code to set if there is an error

Returns:

True if the peripheral is found

inline void set_address(uint8_t address)

Set the address of the peripheral

Note

This function is thread safe

Note

This function is only available if UseAddress is true

Parameters:

address – The address of the peripheral

inline void set_probe(const probe_fn &probe)

Set the probe function

Note

This function is thread safe

Note

This should rarely be used, as the probe function is usually set in the constructor. If you need to change the probe function, consider using the set_config function instead.

Note

This function is only available if UseAddress is true

Parameters:

probe – The probe function

inline void set_write(const write_fn &write)

Set the write function

Note

This function is thread safe

Note

This should rarely be used, as the write function is usually set in the constructor. If you need to change the write function, consider using the set_config function instead.

Parameters:

write – The write function

inline void set_read(const read_fn &read)

Set the read function

Note

This function is thread safe

Note

This should rarely be used, as the read function is usually set in the constructor. If you need to change the read function, consider using the set_config function instead.

Parameters:

read – The read function

inline void set_read_register(const read_register_fn &read_register)

Set the read register function

Note

This function is thread safe

Note

This should rarely be used, as the read register function is usually set in the constructor. If you need to change the read register function, consider using the set_config function instead.

Parameters:

read_register – The read register function

inline void set_write_then_read(const write_then_read_fn &write_then_read)

Set the write then read function

Note

This function is thread safe

Note

This should rarely be used, as the write then read function is usually set in the constructor. If you need to change the write then

Parameters:

write_then_read – The write then read function

inline void set_separate_write_then_read_delay(const std::chrono::milliseconds &delay)

Set the delay between the write and read operations in write_then_read

Note

This function is thread safe

Note

This should rarely be used, as the delay is usually set in the constructor. If you need to change the delay, consider using the set_config function instead.

Note

This delay is only used if the write_then_read function is not set to a custom function and the write and read functions are separate functions.

Parameters:

delay – The delay between the write and read operations in write_then_read

inline void set_config(const Config &config)

Set the configuration for the peripheral

Note

This function is thread safe

Note

The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.

Parameters:

config – The configuration for the peripheral

inline void set_config(Config &&config)

Set the configuration for the peripheral

Note

This function is thread safe

Note

The configuration should normally be set in the constructor, but this function can be used to change the configuration after the peripheral has been created - for instance if the peripheral could be found on different communications buses.

Parameters:

config – The configuration for the peripheral

inline const Config &config() const

Get the configuration for the peripheral

Returns:

The configuration for the peripheral

inline uint8_t address() const

Get the address of the peripheral

Returns:

The address of the peripheral

inline const std::string &get_name() const

Get the name of the component

Note

This is the tag of the logger

Returns:

A const reference to the name of the component

inline void set_log_tag(const std::string_view &tag)

Set the tag for the logger

Parameters:

tag – The tag to use for the logger

inline espp::Logger::Verbosity get_log_level() const

Get the log level for the logger

Returns:

The verbosity level of the logger

inline void set_log_level(espp::Logger::Verbosity level)

Set the log level for the logger

Parameters:

level – The verbosity level to use for the logger

inline void set_log_verbosity(espp::Logger::Verbosity level)

Set the log verbosity for the logger

See also

set_log_level

Note

This is a convenience method that calls set_log_level

Parameters:

level – The verbosity level to use for the logger

inline espp::Logger::Verbosity get_log_verbosity() const

Get the log verbosity for the logger

See also

get_log_level

Note

This is a convenience method that calls get_log_level

Returns:

The verbosity level of the logger

inline void set_log_rate_limit(std::chrono::duration<float> rate_limit)

Set the rate limit for the logger

Note

Only calls to the logger that have _rate_limit suffix will be rate limited

Parameters:

rate_limit – The rate limit to use for the logger

Public Static Attributes

static constexpr uint8_t DEFAULT_ADDRESS = 0x55

Default I2C address for the ST7123 touch interface.

struct Config

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).

Public Members

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.