mirror of
https://github.com/esphome/esphome.git
synced 2026-10-07 00:57:26 +08:00
[gsl3670] Add new touchscreen component (#16285)
This commit is contained in:
@@ -209,6 +209,7 @@ esphome/components/gree/switch/* @nagyrobi
|
||||
esphome/components/grove_gas_mc_v2/* @YorkshireIoT
|
||||
esphome/components/grove_tb6612fng/* @max246
|
||||
esphome/components/growatt_solar/* @leeuwte
|
||||
esphome/components/gsl3670/* @clydebarrow
|
||||
esphome/components/gt911/* @clydebarrow @jesserockz
|
||||
esphome/components/haier/* @paveldn
|
||||
esphome/components/haier/binary_sensor/* @paveldn
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@clydebarrow"]
|
||||
@@ -0,0 +1,167 @@
|
||||
#include "gsl3670_touchscreen.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::gsl3670 {
|
||||
|
||||
static const char *const TAG = "gsl3670.touchscreen";
|
||||
static const size_t MAX_TOUCHES = 3;
|
||||
// ---------------------------------------------------------------------------
|
||||
// setup() – mirrors esp_lcd_touch_gsl3670_init() in the Seeed BSP:
|
||||
// clear_reg → reset → load_fw → startup_chip → reset → startup_chip
|
||||
// ---------------------------------------------------------------------------
|
||||
void GSL3670Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up GSL3670 touchscreen...");
|
||||
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
this->reset_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup();
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
if (this->x_raw_max_ == this->x_raw_min_) {
|
||||
this->x_raw_max_ = this->display_->get_native_width();
|
||||
}
|
||||
if (this->y_raw_max_ == this->y_raw_min_) {
|
||||
this->y_raw_max_ = this->display_->get_native_height();
|
||||
}
|
||||
|
||||
this->clear_reg_();
|
||||
this->reset_();
|
||||
this->load_firmware_();
|
||||
this->startup_chip_();
|
||||
this->reset_();
|
||||
this->startup_chip_();
|
||||
|
||||
ESP_LOGCONFIG(TAG, "GSL3670 initialised OK");
|
||||
}
|
||||
|
||||
void GSL3670Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"GSL3670 Touchscreen:\n"
|
||||
" X-raw-max: %d\n"
|
||||
" Y-raw-max: %d\n",
|
||||
this->x_raw_max_, this->y_raw_max_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
ESP_LOGCONFIG(TAG, " Firmware records: %zu", this->firmware_len_);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// update_touches() – mirrors esp_lcd_touch_gsl3670_read_data() in Seeed BSP
|
||||
// ---------------------------------------------------------------------------
|
||||
void GSL3670Touchscreen::update_touches() {
|
||||
uint8_t buf[44] = {};
|
||||
auto err = this->read_register(0x80, buf, sizeof(buf));
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "I2C read failed (%d)", err);
|
||||
return;
|
||||
}
|
||||
uint8_t finger_num = clamp_at_most(buf[0], MAX_TOUCHES);
|
||||
|
||||
// Build gsl_touch_info exactly as the Seeed driver does
|
||||
for (uint8_t j = 0; j != finger_num; j++) {
|
||||
// buf[(j+1)*4 + 0..3]: byte0=y_lo, byte1=y_hi, byte2=x_lo, byte3=id|x_hi
|
||||
auto x = (uint16_t) (((buf[(j + 1) * 4 + 3] & 0x0f) << 8) | buf[(j + 1) * 4 + 2]);
|
||||
auto y = (uint16_t) ((buf[(j + 1) * 4 + 1] << 8) | buf[(j + 1) * 4 + 0]);
|
||||
auto id = (buf[(j + 1) * 4 + 3] >> 4) & 0x0f;
|
||||
ESP_LOGV(TAG, "Touch id=%u, x=%u y=%u", id, x, y);
|
||||
if (x <= 8192 && y <= 8192)
|
||||
this->add_raw_touch_position_(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// clear_reg_() – mirrors esp_lcd_touch_gsl3670_clear_reg()
|
||||
// GPIO reset → write 0x01 to 0x88 → write 0x04 to 0xe4 → write 0x00 to 0xe0
|
||||
// ---------------------------------------------------------------------------
|
||||
void GSL3670Touchscreen::clear_reg_() {
|
||||
ESP_LOGD(TAG, "clear_reg");
|
||||
|
||||
// GPIO reset pulse
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
}
|
||||
|
||||
this->write_reg8_(0x88, 0x01);
|
||||
// delay(5);
|
||||
this->write_reg8_(0xe4, 0x04);
|
||||
// delay(5);
|
||||
this->write_reg8_(0xe0, 0x00);
|
||||
// delay(5);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// reset_() – mirrors touch_gsl3670_reset()
|
||||
// GPIO reset → write 0x04 to 0xe4 → write 4×0x00 to 0xbc
|
||||
// ---------------------------------------------------------------------------
|
||||
void GSL3670Touchscreen::reset_() {
|
||||
ESP_LOGD(TAG, "reset");
|
||||
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(1);
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(5);
|
||||
}
|
||||
|
||||
this->write_reg8_(0xe4, 0x04);
|
||||
|
||||
uint8_t zeros[4] = {0, 0, 0, 0};
|
||||
this->write_reg_(0xbc, zeros, 4);
|
||||
}
|
||||
|
||||
void GSL3670Touchscreen::load_firmware_() {
|
||||
if (firmware_ == nullptr || firmware_len_ == 0) {
|
||||
ESP_LOGW(TAG, "No firmware supplied – skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Loading firmware (%zu blocks)...", firmware_len_);
|
||||
|
||||
static constexpr size_t FW_BLK_SIZE = 128 + 4;
|
||||
|
||||
for (size_t i = 0; i != this->firmware_len_; i++) {
|
||||
auto offset = i * FW_BLK_SIZE;
|
||||
uint8_t val = this->firmware_[offset + 0];
|
||||
ESP_LOGV(TAG, "Firmware address 0x%02X", val);
|
||||
this->write_reg_(0xf0, &val, 1);
|
||||
this->write_reg_(0, this->firmware_ + offset + 4, 128);
|
||||
}
|
||||
ESP_LOGD(TAG, "Firmware load complete");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// startup_chip_() – mirrors esp_lcd_touch_gsl3670_startup_chip()
|
||||
// write 0x00 to 0xe0
|
||||
// ---------------------------------------------------------------------------
|
||||
void GSL3670Touchscreen::startup_chip_() {
|
||||
ESP_LOGD(TAG, "startup_chip");
|
||||
this->write_reg8_(0xe0, 0x00);
|
||||
delay(5);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// I2C helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
bool GSL3670Touchscreen::write_reg_(uint8_t reg, const uint8_t *data, size_t len) {
|
||||
auto err = this->write_register(reg, data, len);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "I2C write reg 0x%02X len %zu failed (%d)", reg, len, err);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSL3670Touchscreen::write_reg8_(uint8_t reg, uint8_t val) { return write_reg_(reg, &val, 1); }
|
||||
|
||||
} // namespace esphome::gsl3670
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::gsl3670 {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GSL3670 touchscreen ESPHome component
|
||||
// ---------------------------------------------------------------------------
|
||||
class GSL3670Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
/// Supply the firmware table (generated by codegen from the YAML)
|
||||
void set_firmware(const uint8_t *fw, size_t len) {
|
||||
this->firmware_ = fw;
|
||||
this->firmware_len_ = len;
|
||||
}
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { interrupt_pin_ = pin; }
|
||||
void set_reset_pin(GPIOPin *pin) { reset_pin_ = pin; }
|
||||
|
||||
// touchscreen::Touchscreen / Component interface
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void update_touches() override;
|
||||
|
||||
private:
|
||||
// ---------- init steps (mirrors esp_lcd_touch_gsl3670_init) ----------
|
||||
void clear_reg_(); // GPIO reset + 0x88/0xe4/0xe0 sequence
|
||||
void reset_(); // GPIO reset + 0xe4/0xbc sequence
|
||||
void load_firmware_(); // write GSLX670_FW table
|
||||
void startup_chip_(); // 0x00→0xe0 + gsl_DataInit
|
||||
|
||||
// ---------- I2C helpers ----------
|
||||
bool write_reg_(uint8_t reg, const uint8_t *data, size_t len);
|
||||
bool write_reg8_(uint8_t reg, uint8_t val);
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
|
||||
const uint8_t *firmware_{nullptr};
|
||||
size_t firmware_len_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::gsl3670
|
||||
File diff suppressed because it is too large
Load Diff
@@ -60,40 +60,79 @@ def validate_calibration(calibration_config):
|
||||
return calibration_config
|
||||
|
||||
|
||||
CALIBRATION_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_X_MIN): cv.int_range(min=0, max=4095),
|
||||
cv.Required(CONF_X_MAX): cv.int_range(min=0, max=4095),
|
||||
cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=4095),
|
||||
cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=4095),
|
||||
}
|
||||
),
|
||||
validate_calibration,
|
||||
)
|
||||
def option_with_default(option: str, defaults: dict, required: bool = False):
|
||||
if option in defaults or not required:
|
||||
return cv.Optional(option, default=defaults.get(option, cv.UNDEFINED))
|
||||
return cv.Required(option)
|
||||
|
||||
|
||||
def touchscreen_schema(default_touch_timeout=cv.UNDEFINED, calibration_required=False):
|
||||
calibration = (
|
||||
cv.Required(CONF_CALIBRATION)
|
||||
if calibration_required
|
||||
else cv.Optional(CONF_CALIBRATION)
|
||||
)
|
||||
_CALIBRATION_KEYS = {CONF_X_MIN, CONF_X_MAX, CONF_Y_MIN, CONF_Y_MAX}
|
||||
_TRANSFORM_KEYS = {CONF_SWAP_XY, CONF_MIRROR_X, CONF_MIRROR_Y}
|
||||
|
||||
|
||||
def _calibration_schema(defaults: dict, required: bool) -> dict:
|
||||
"""
|
||||
Generate Calibration schema. If defaults are provided for all suboptions,
|
||||
the entire calibration config is optional with a populated default value.
|
||||
Otherwise, it's optional or required as specified.
|
||||
"""
|
||||
if _CALIBRATION_KEYS.issubset(defaults):
|
||||
key = cv.Optional(
|
||||
CONF_CALIBRATION,
|
||||
default={k: v for k, v in defaults.items() if k in _CALIBRATION_KEYS},
|
||||
)
|
||||
elif required:
|
||||
key = cv.Required(CONF_CALIBRATION)
|
||||
else:
|
||||
key = cv.Optional(CONF_CALIBRATION)
|
||||
return {
|
||||
key: cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
option_with_default(x, defaults, True): cv.int_range(
|
||||
min=0, max=4095
|
||||
)
|
||||
for x in _CALIBRATION_KEYS
|
||||
}
|
||||
),
|
||||
validate_calibration,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def _transform_schema(defaults: dict) -> dict:
|
||||
if _TRANSFORM_KEYS.issubset(defaults):
|
||||
key = cv.Optional(
|
||||
CONF_TRANSFORM,
|
||||
default={k: v for k, v in defaults.items() if k in _TRANSFORM_KEYS},
|
||||
)
|
||||
else:
|
||||
key = cv.Optional(CONF_TRANSFORM)
|
||||
return {
|
||||
key: cv.Schema(
|
||||
{
|
||||
cv.Optional(x, default=defaults.get(x, False)): cv.boolean
|
||||
for x in _TRANSFORM_KEYS
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def touchscreen_schema(
|
||||
default_touch_timeout=cv.UNDEFINED,
|
||||
calibration_required=False,
|
||||
defaults: dict = None,
|
||||
) -> cv.Schema:
|
||||
defaults = defaults or {}
|
||||
return cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DISPLAY): cv.use_id(display.Display),
|
||||
cv.Optional(CONF_TRANSFORM): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_SWAP_XY, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_X, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIRROR_Y, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_TOUCH_TIMEOUT, default=default_touch_timeout): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=cv.TimePeriod(milliseconds=65535)),
|
||||
),
|
||||
calibration: CALIBRATION_SCHEMA,
|
||||
**_transform_schema(defaults),
|
||||
**_calibration_schema(defaults, calibration_required),
|
||||
cv.Optional(CONF_ON_TOUCH): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_UPDATE): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(single=True),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,28 @@
|
||||
packages:
|
||||
i2c: !include ../../test_build_components/common/i2c/esp32-idf.yaml
|
||||
spi: !include ../../test_build_components/common/spi/esp32-s3-idf.yaml
|
||||
|
||||
xl9535:
|
||||
id: expander
|
||||
|
||||
display:
|
||||
- platform: mipi_spi
|
||||
spi_id: spi_bus
|
||||
model: t-display-s3-pro
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
|
||||
touchscreen:
|
||||
# Firmware downloaded from the model's default release URL and cached.
|
||||
- platform: gsl3670
|
||||
model: seeed-reterminal-d1001
|
||||
interrupt_pin: 18
|
||||
# Explicit firmware URL + SHA-256 override.
|
||||
- platform: gsl3670
|
||||
model: seeed-reterminal-d1001
|
||||
reset_pin: 10
|
||||
interrupt_pin: 11
|
||||
firmware:
|
||||
url: https://github.com/esphome-libs/gsl3670-firmware/releases/download/v1.0.0/seeed-d1001-fw.bin
|
||||
sha256: 2e50501ad83656fb6fa3d92591f9f31add4d442c8e8a79f29f5c4d335bd127a4
|
||||
Reference in New Issue
Block a user