Touchscreen driver POC
Example documentation
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@andrewmv"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
icnt86x_ns = cg.esphome_ns.namespace("icnt86x")
|
||||
@@ -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 icnt86x_ns
|
||||
|
||||
ICNT86XTouchscreen = icnt86x_ns.class_(
|
||||
"ICNT86XTouchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ICNT86XTouchscreen),
|
||||
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(0x48))
|
||||
|
||||
|
||||
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,131 @@
|
||||
#include "icnt86x_touchscreen.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace icnt86x {
|
||||
|
||||
static const char *const TAG = "icnt86x.touchscreen";
|
||||
|
||||
static const uint16_t REG_VERSION = 0x000A;
|
||||
static const uint16_t REG_TOUCH_CNT = 0x1001;
|
||||
static const uint16_t REG_TOUCH_DAT = 0x1002;
|
||||
|
||||
static const uint8_t MAX_TOUCHES = 2;
|
||||
static const uint8_t BYTES_PER_TOUCH = 7;
|
||||
|
||||
// Observed event codes from protocol analysis
|
||||
static const uint8_t EVENT_MOVE = 0x02;
|
||||
static const uint8_t EVENT_DOWN = 0x03;
|
||||
// 0x04 = idle/no-contact — chip reports count=1 with this code when untouched
|
||||
|
||||
void ICNT86XTouchscreen::setup() {
|
||||
if (this->reset_pin_ != nullptr) {
|
||||
this->reset_pin_->setup();
|
||||
// Drive INT low before and during reset release. Many touch controllers
|
||||
// sample INT at reset release to select interrupt output mode; if INT is
|
||||
// left pulled high by the level shifter the chip comes up with INT disabled.
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->interrupt_pin_->digital_write(false);
|
||||
}
|
||||
this->reset_pin_->digital_write(true);
|
||||
delay(100);
|
||||
this->reset_pin_->digital_write(false);
|
||||
delay(100);
|
||||
this->reset_pin_->digital_write(true); // INT still driven low at release
|
||||
}
|
||||
|
||||
// Allow chip 100ms to boot, then reconfigure INT as input before attaching ISR
|
||||
this->set_timeout(100, [this]() { this->setup_internal_(); });
|
||||
}
|
||||
|
||||
void ICNT86XTouchscreen::setup_internal_() {
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->interrupt_pin_->setup(); // reconfigure as input after driving low during reset
|
||||
}
|
||||
|
||||
uint8_t ver[4];
|
||||
if (!this->read_reg16_(REG_VERSION, ver, sizeof(ver))) {
|
||||
this->mark_failed();
|
||||
ESP_LOGE(TAG, "Failed to communicate with ICNT86X at 0x%02X", this->address_);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "ICNT86X IC version: %02X%02X, FW version: %02X%02X",
|
||||
ver[0], ver[1], ver[2], ver[3]);
|
||||
|
||||
if (this->interrupt_pin_ != nullptr) {
|
||||
this->attach_interrupt_(this->interrupt_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
|
||||
this->setup_done_ = true;
|
||||
}
|
||||
|
||||
void ICNT86XTouchscreen::update_touches() {
|
||||
// In polling mode loop() never runs, so is_touched_ is never reset between
|
||||
// cycles. Reset it here so send_touches_() can detect the press→release
|
||||
// transition via !is_touched_ && was_touched_.
|
||||
this->is_touched_ = false;
|
||||
|
||||
if (!this->setup_done_) {
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t count = 0;
|
||||
if (!this->read_reg16_(REG_TOUCH_CNT, &count, 1)) {
|
||||
this->status_set_warning();
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_reg16_(REG_TOUCH_CNT, 0x00);
|
||||
|
||||
// Always allow send_touches_() to run — even with zero touches it needs to
|
||||
// fire to propagate the release event when !is_touched_ && was_touched_.
|
||||
this->skip_update_ = false;
|
||||
|
||||
if (count == 0 || count > MAX_TOUCHES)
|
||||
return;
|
||||
|
||||
uint8_t data[MAX_TOUCHES * BYTES_PER_TOUCH];
|
||||
if (!this->read_reg16_(REG_TOUCH_DAT, data, count * BYTES_PER_TOUCH)) {
|
||||
this->status_set_warning();
|
||||
this->skip_update_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < count; i++) {
|
||||
// Per-point layout (7 bytes): [id, x_lo, x_hi, y_lo, y_hi, pressure, event]
|
||||
const uint8_t *p = &data[i * BYTES_PER_TOUCH];
|
||||
uint8_t event = p[6];
|
||||
if (event != EVENT_DOWN && event != EVENT_MOVE)
|
||||
continue;
|
||||
uint8_t id = p[0];
|
||||
int16_t x = ((uint16_t)p[2] << 8) | p[1];
|
||||
int16_t y = ((uint16_t)p[4] << 8) | p[3];
|
||||
this->add_raw_touch_position_(id, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
bool ICNT86XTouchscreen::read_reg16_(uint16_t reg, uint8_t *data, size_t len) {
|
||||
uint8_t reg_buf[2] = {(uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF)};
|
||||
if (this->write(reg_buf, 2) != i2c::ERROR_OK)
|
||||
return false;
|
||||
return this->read(data, len) == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
bool ICNT86XTouchscreen::write_reg16_(uint16_t reg, uint8_t value) {
|
||||
uint8_t buf[3] = {(uint8_t)(reg >> 8), (uint8_t)(reg & 0xFF), value};
|
||||
return this->write(buf, 3) == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
void ICNT86XTouchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "ICNT86X Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
LOG_PIN(" Reset Pin: ", this->reset_pin_);
|
||||
}
|
||||
|
||||
} // namespace icnt86x
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,33 @@
|
||||
#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 {
|
||||
namespace icnt86x {
|
||||
|
||||
class ICNT86XTouchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
bool can_proceed() override { return this->setup_done_; }
|
||||
|
||||
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;
|
||||
void setup_internal_();
|
||||
|
||||
bool read_reg16_(uint16_t reg, uint8_t *data, size_t len);
|
||||
bool write_reg16_(uint16_t reg, uint8_t value);
|
||||
|
||||
InternalGPIOPin *interrupt_pin_{nullptr};
|
||||
GPIOPin *reset_pin_{nullptr};
|
||||
bool setup_done_{false};
|
||||
};
|
||||
|
||||
} // namespace icnt86x
|
||||
} // namespace esphome
|
||||
Reference in New Issue
Block a user