[st7123] add ST7123 touch controller component (M5Stack Tab5) (#12075)

Co-authored-by: Clyde Stubbs <2366188+clydebarrow@users.noreply.github.com>
This commit is contained in:
Sven Kocksch
2026-07-02 18:40:59 +10:00
committed by GitHub
co-authored by Clyde Stubbs
parent 06c7ac37d1
commit 792dfbcbbf
7 changed files with 222 additions and 0 deletions
+1
View File
@@ -501,6 +501,7 @@ esphome/components/ssd1331_base/* @kbx81
esphome/components/ssd1331_spi/* @kbx81
esphome/components/ssd1351_base/* @kbx81
esphome/components/ssd1351_spi/* @kbx81
esphome/components/st7123/* @miniskipper
esphome/components/st7567_base/* @latonita
esphome/components/st7567_i2c/* @latonita
esphome/components/st7567_spi/* @latonita
+6
View File
@@ -0,0 +1,6 @@
import esphome.codegen as cg
CODEOWNERS = ["@miniskipper"]
DEPENDENCIES = ["i2c"]
st7123_ns = cg.esphome_ns.namespace("st7123")
@@ -0,0 +1,32 @@
from esphome import pins
import esphome.codegen as cg
from esphome.components import i2c, touchscreen
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_RESET_PIN
from .. import st7123_ns
ST7123Touchscreen = st7123_ns.class_(
"ST7123Touchscreen",
touchscreen.Touchscreen,
i2c.I2CDevice,
)
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(ST7123Touchscreen),
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_schema,
cv.Optional(CONF_RESET_PIN): pins.gpio_output_pin_schema,
}
).extend(i2c.i2c_device_schema(0x55))
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await touchscreen.register_touchscreen(var, config)
await i2c.register_i2c_device(var, config)
if interrupt_pin := config.get(CONF_INTERRUPT_PIN):
cg.add(var.set_interrupt_pin(await cg.gpio_pin_expression(interrupt_pin)))
if reset_pin := config.get(CONF_RESET_PIN):
cg.add(var.set_reset_pin(await cg.gpio_pin_expression(reset_pin)))
@@ -0,0 +1,108 @@
#include "st7123_touchscreen.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::st7123 {
static const char *const TAG = "st7123.touchscreen";
void ST7123Touchscreen::setup() {
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false); // TP_RESX is active low, assert for at least tRSTW (2ms)
delay(5);
this->reset_pin_->digital_write(true);
// The controller needs up to 20ms to initialize after reset before it can be accessed.
this->setup_time_ = millis() + 30;
}
}
void ST7123Touchscreen::update() {
// check if setup is complete
if (this->setup_time_ != 0) {
if (this->setup_time_ > millis())
return;
uint8_t status;
if (this->read_register16(ST7123_REG_STATUS, &status, 1) != i2c::ERROR_OK) {
this->mark_failed(LOG_STR("Failed to read status register")); // will stop updates
return;
}
if ((status & 0x0F) == ST7123_STATUS_INIT) {
ESP_LOGD(TAG, "Controller still initializing");
return;
}
if (this->interrupt_pin_ != nullptr) {
this->interrupt_pin_->setup();
// INT is held high when idle and pulses low when touch data is ready.
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
}
ESP_LOGD(TAG, "Status is %X", status);
uint8_t data;
if (this->read_register16(ST7123_REG_MAX_TOUCHES, &data, 1) == i2c::ERROR_OK && data != 0 &&
data <= ST7123_MAX_TOUCHES) {
this->max_touches_ = data;
}
// If no calibration was supplied, read the native coordinate resolution from the controller.
if (this->x_raw_max_ == this->x_raw_min_ || this->y_raw_max_ == this->y_raw_min_) {
uint8_t res[4];
if (this->read_register16(ST7123_REG_MAX_X, res, sizeof(res)) == i2c::ERROR_OK) {
this->x_raw_max_ = encode_uint16(res[0] & ST7123_COORD_HIGH_MASK, res[1]);
this->y_raw_max_ = encode_uint16(res[2] & ST7123_COORD_HIGH_MASK, res[3]);
if (this->swap_x_y_)
std::swap(this->x_raw_max_, this->y_raw_max_);
} else {
this->mark_failed(LOG_STR("Failed to read calibration"));
return;
}
ESP_LOGD(TAG, "Read dimensions %d/%d", this->x_raw_max_, this->y_raw_max_);
}
this->setup_time_ = 0; // flag setup complete
}
Touchscreen::update();
}
void ST7123Touchscreen::update_touches() {
// Read the reporting table from the advanced touch info register through the last touch point.
// Reading from this register also clears the INT pin so the controller can report the next frame.
uint8_t data[(ST7123_REG_TOUCH_DATA - ST7123_REG_ADV_TOUCH_INFO) + ST7123_MAX_TOUCHES * ST7123_TOUCH_STRIDE];
const size_t len = (ST7123_REG_TOUCH_DATA - ST7123_REG_ADV_TOUCH_INFO) + this->max_touches_ * ST7123_TOUCH_STRIDE;
if (this->read_register16(ST7123_REG_ADV_TOUCH_INFO, data, len) != i2c::ERROR_OK) {
this->skip_update_ = true;
this->status_set_warning();
return;
}
this->status_clear_warning();
const uint8_t *points = data + (ST7123_REG_TOUCH_DATA - ST7123_REG_ADV_TOUCH_INFO);
for (uint8_t i = 0; i != this->max_touches_; i++) {
const uint8_t *p = points + i * ST7123_TOUCH_STRIDE;
if ((p[0] & ST7123_TOUCH_VALID) == 0)
continue;
uint16_t x = encode_uint16(p[0] & ST7123_COORD_HIGH_MASK, p[1]);
uint16_t y = encode_uint16(p[2] & ST7123_COORD_HIGH_MASK, p[3]);
uint8_t intensity = p[5];
ESP_LOGV(TAG, "Touch %u: x=%u, y=%u, intensity=%u", i, x, y, intensity);
this->add_raw_touch_position_(i, x, y, intensity);
}
}
void ST7123Touchscreen::dump_config() {
ESP_LOGCONFIG(TAG,
"ST7123 Touchscreen:\n"
" Max touches: %u\n"
" X Raw Min: %d, X Raw Max: %d\n"
" Y Raw Min: %d, Y Raw Max: %d",
this->max_touches_, this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_);
LOG_I2C_DEVICE(this);
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
LOG_PIN(" Reset Pin: ", this->reset_pin_);
}
} // namespace esphome::st7123
@@ -0,0 +1,48 @@
#pragma once
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/touchscreen/touchscreen.h"
#include "esphome/core/component.h"
#include "esphome/core/hal.h"
namespace esphome::st7123 {
// Sitronix ST7123 capacitive touch controller.
// Registers are addressed with a 16-bit big-endian address (sent MSB first).
static constexpr uint16_t ST7123_REG_STATUS = 0x0001; // [7:4] error code, [3:0] device status
static constexpr uint16_t ST7123_REG_MAX_X = 0x0005; // 0x0005..0x0006 X resolution, 0x0007..0x0008 Y resolution
static constexpr uint16_t ST7123_REG_MAX_TOUCHES = 0x0009;
static constexpr uint16_t ST7123_REG_ADV_TOUCH_INFO = 0x0010; // start of the reporting table
static constexpr uint16_t ST7123_REG_TOUCH_DATA = 0x0014; // first touch point
// Device status field of the status register.
static constexpr uint8_t ST7123_STATUS_INIT = 0x1;
// Each touch point occupies 7 bytes: X high, X low, Y high, Y low, area, intensity, reserved.
static constexpr uint8_t ST7123_TOUCH_STRIDE = 7;
// Bit 7 of the X high byte indicates a valid touch point.
static constexpr uint8_t ST7123_TOUCH_VALID = 0x80;
// The X and Y high bytes only use the low 6 bits.
static constexpr uint8_t ST7123_COORD_HIGH_MASK = 0x3F;
// The ST7123 can report at most 10 touch points.
static constexpr uint8_t ST7123_MAX_TOUCHES = 10;
class ST7123Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
public:
void setup() override;
void update() override;
void dump_config() override;
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; }
protected:
void update_touches() override;
InternalGPIOPin *interrupt_pin_{nullptr};
GPIOPin *reset_pin_{nullptr};
uint8_t max_touches_{ST7123_MAX_TOUCHES};
uint32_t setup_time_{1};
};
} // namespace esphome::st7123
+18
View File
@@ -0,0 +1,18 @@
display:
- platform: ssd1306_i2c
i2c_id: i2c_bus
id: st7123_ssd1306_i2c_display
model: SSD1306_128X64
reset_pin: ${display_reset_pin}
pages:
- id: st7123_page1
lambda: |-
it.rectangle(0, 0, it.get_width(), it.get_height());
touchscreen:
- platform: st7123
i2c_id: i2c_bus
id: st7123_touchscreen
display: st7123_ssd1306_i2c_display
interrupt_pin: ${interrupt_pin}
reset_pin: ${reset_pin}
@@ -0,0 +1,9 @@
substitutions:
display_reset_pin: "10"
interrupt_pin: "20"
reset_pin: "21"
packages:
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
<<: !include common.yaml