Add stable CYD firmware, USB calibration wizard and complete setup guide
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
from esphome import pins
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi, touchscreen
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_INTERRUPT_PIN, CONF_THRESHOLD
|
||||
|
||||
CODEOWNERS = ["@numo68", "@nielsnl68"]
|
||||
DEPENDENCIES = ["spi"]
|
||||
|
||||
XPT2046_ns = cg.esphome_ns.namespace("xpt2046")
|
||||
XPT2046Component = XPT2046_ns.class_(
|
||||
"XPT2046Component",
|
||||
touchscreen.Touchscreen,
|
||||
spi.SPIDevice,
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
touchscreen.touchscreen_schema(calibration_required=True)
|
||||
.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(XPT2046Component),
|
||||
cv.Optional(CONF_INTERRUPT_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
cv.Optional(CONF_THRESHOLD, default=400): cv.int_range(min=0, max=4095),
|
||||
},
|
||||
)
|
||||
)
|
||||
.extend(spi.spi_device_schema()),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await spi.register_spi_device(var, config)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
|
||||
cg.add(var.set_threshold(config[CONF_THRESHOLD]))
|
||||
|
||||
if CONF_INTERRUPT_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_irq_pin(pin))
|
||||
@@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
#include <cstdlib>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
|
||||
namespace esphome::xpt2046 {
|
||||
// Optional affine calibration corrects skew as well as scale/offset.
|
||||
// Identity by default; coefficients belong to an individual physical panel.
|
||||
class RawCorrection {
|
||||
public:
|
||||
void set(float xx, float xy, float xc, float yx, float yy, float yc) {
|
||||
xx_ = xx; xy_ = xy; xc_ = xc; yx_ = yx; yy_ = yy; yc_ = yc;
|
||||
}
|
||||
int16_t x(int raw_x, int raw_y) const { return bound(xx_ * raw_x + xy_ * raw_y + xc_); }
|
||||
int16_t y(int raw_x, int raw_y) const { return bound(yx_ * raw_x + yy_ * raw_y + yc_); }
|
||||
private:
|
||||
static int16_t bound(float v) { return static_cast<int16_t>(std::clamp(std::lround(v), 0L, 4095L)); }
|
||||
float xx_{1}, xy_{0}, xc_{0}, yx_{0}, yy_{1}, yc_{0};
|
||||
};
|
||||
|
||||
// XPT2046 ADC values can be wildly wrong as a finger first contacts/releases
|
||||
// the resistive sheet. Do not expose a lone sample as a new LVGL target.
|
||||
class TouchFilter {
|
||||
public:
|
||||
enum Result { HOLD, POSITION, RELEASE };
|
||||
Result sample(bool pressed, int16_t raw_x, int16_t raw_y) {
|
||||
if (!pressed) {
|
||||
candidate_count_ = 0;
|
||||
if (active_ && ++release_count_ < 2) return HOLD;
|
||||
active_ = false;
|
||||
release_count_ = 0;
|
||||
return RELEASE;
|
||||
}
|
||||
release_count_ = 0;
|
||||
if (active_ && near(raw_x, raw_y, x_, y_, 300)) {
|
||||
candidate_count_ = 0;
|
||||
x_ = (x_ + raw_x) / 2;
|
||||
y_ = (y_ + raw_y) / 2;
|
||||
return POSITION;
|
||||
}
|
||||
// Initial contact or a sudden jump: require three consistent samples.
|
||||
if (!candidate_count_ || !near(raw_x, raw_y, candidate_x_, candidate_y_, 100)) {
|
||||
candidate_x_ = raw_x;
|
||||
candidate_y_ = raw_y;
|
||||
candidate_count_ = 1;
|
||||
return HOLD;
|
||||
}
|
||||
candidate_x_ = (candidate_x_ * candidate_count_ + raw_x) / (candidate_count_ + 1);
|
||||
candidate_y_ = (candidate_y_ * candidate_count_ + raw_y) / (candidate_count_ + 1);
|
||||
if (++candidate_count_ < 3) return HOLD;
|
||||
x_ = candidate_x_;
|
||||
y_ = candidate_y_;
|
||||
active_ = true;
|
||||
candidate_count_ = 0;
|
||||
return POSITION;
|
||||
}
|
||||
int16_t x() const { return x_; }
|
||||
int16_t y() const { return y_; }
|
||||
private:
|
||||
static bool near(int x1, int y1, int x2, int y2, int distance) {
|
||||
return std::abs(x1 - x2) <= distance && std::abs(y1 - y2) <= distance;
|
||||
}
|
||||
int16_t x_{0}, y_{0}, candidate_x_{0}, candidate_y_{0};
|
||||
uint8_t candidate_count_{0}, release_count_{0};
|
||||
bool active_{false};
|
||||
};
|
||||
} // namespace esphome::xpt2046
|
||||
@@ -0,0 +1,117 @@
|
||||
#include "xpt2046.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace esphome::xpt2046 {
|
||||
|
||||
static const char *const TAG = "xpt2046";
|
||||
|
||||
void XPT2046Component::setup() {
|
||||
if (this->irq_pin_ != nullptr) {
|
||||
// The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state
|
||||
// while the channels are read and wiring it as an interrupt is not straightforward and would
|
||||
// need careful masking. A GPIO poll is cheap so we'll just use that.
|
||||
|
||||
this->irq_pin_->setup(); // INPUT
|
||||
this->irq_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->irq_pin_->setup();
|
||||
this->attach_interrupt_(this->irq_pin_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
}
|
||||
this->spi_setup();
|
||||
this->read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
|
||||
}
|
||||
|
||||
void XPT2046Component::update_touches() {
|
||||
int16_t data[6], x_raw, y_raw, z_raw;
|
||||
bool touch = false;
|
||||
|
||||
enable();
|
||||
|
||||
int16_t touch_pressure_1 = this->read_adc_(0xB1 /* touch_pressure_1 */);
|
||||
int16_t touch_pressure_2 = this->read_adc_(0xC1 /* touch_pressure_2 */);
|
||||
z_raw = touch_pressure_1 + 0xfff - touch_pressure_2;
|
||||
ESP_LOGVV(TAG, "Touchscreen Update z = %d", z_raw);
|
||||
touch = (z_raw >= this->threshold_);
|
||||
if (touch) {
|
||||
read_adc_(0xD1 /* X */); // dummy Y measure, 1st is always noisy
|
||||
data[0] = this->read_adc_(0x91 /* Y */);
|
||||
data[1] = this->read_adc_(0xD1 /* X */); // make 3 x-y measurements
|
||||
data[2] = this->read_adc_(0x91 /* Y */);
|
||||
data[3] = this->read_adc_(0xD1 /* X */);
|
||||
data[4] = this->read_adc_(0x91 /* Y */);
|
||||
}
|
||||
|
||||
data[5] = this->read_adc_(0xD0 /* X */); // Last X touch power down
|
||||
|
||||
disable();
|
||||
|
||||
if (touch) {
|
||||
x_raw = best_two_avg(data[1], data[3], data[5]);
|
||||
y_raw = best_two_avg(data[0], data[2], data[4]);
|
||||
|
||||
ESP_LOGD(TAG, "Touchscreen Update [%d, %d], z = %d", x_raw, y_raw, z_raw);
|
||||
|
||||
if (this->filter_.sample(true, x_raw, y_raw) == TouchFilter::POSITION) {
|
||||
this->add_raw_touch_position_(0,
|
||||
this->correction_.x(this->filter_.x(), this->filter_.y()),
|
||||
this->correction_.y(this->filter_.x(), this->filter_.y()), z_raw);
|
||||
} else {
|
||||
this->skip_update_ = true;
|
||||
}
|
||||
} else if (this->filter_.sample(false, 0, 0) == TouchFilter::HOLD) {
|
||||
this->skip_update_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void XPT2046Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"XPT2046:\n"
|
||||
" X min: %d\n"
|
||||
" X max: %d\n"
|
||||
" Y min: %d\n"
|
||||
" Y max: %d\n"
|
||||
" Swap X/Y: %s\n"
|
||||
" Invert X: %s\n"
|
||||
" Invert Y: %s\n"
|
||||
" threshold: %d",
|
||||
this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_, YESNO(this->swap_x_y_),
|
||||
YESNO(this->invert_x_), YESNO(this->invert_y_), this->threshold_);
|
||||
LOG_PIN(" IRQ Pin: ", this->irq_pin_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
// float XPT2046Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
int16_t XPT2046Component::best_two_avg(int16_t value1, int16_t value2, int16_t value3) {
|
||||
int16_t delta_a, delta_b, delta_c;
|
||||
int16_t reta = 0;
|
||||
|
||||
delta_a = (value1 > value2) ? value1 - value2 : value2 - value1;
|
||||
delta_b = (value1 > value3) ? value1 - value3 : value3 - value1;
|
||||
delta_c = (value3 > value2) ? value3 - value2 : value2 - value3;
|
||||
|
||||
if (delta_a <= delta_b && delta_a <= delta_c) {
|
||||
reta = (value1 + value2) >> 1;
|
||||
} else if (delta_b <= delta_a && delta_b <= delta_c) {
|
||||
reta = (value1 + value3) >> 1;
|
||||
} else {
|
||||
reta = (value2 + value3) >> 1;
|
||||
}
|
||||
|
||||
return reta;
|
||||
}
|
||||
|
||||
int16_t XPT2046Component::read_adc_(uint8_t ctrl) { // NOLINT
|
||||
uint8_t data[2];
|
||||
|
||||
this->write_byte(ctrl);
|
||||
delay(1);
|
||||
data[0] = this->read_byte();
|
||||
data[1] = this->read_byte();
|
||||
|
||||
return ((data[0] << 8) | data[1]) >> 3;
|
||||
}
|
||||
|
||||
} // namespace esphome::xpt2046
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
#include "touch_filter.h"
|
||||
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::xpt2046 {
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class XPT2046Component : public Touchscreen,
|
||||
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
|
||||
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> {
|
||||
public:
|
||||
void set_raw_correction(float xx, float xy, float xc, float yx, float yy, float yc) {
|
||||
correction_.set(xx, xy, xc, yx, yy, yc);
|
||||
}
|
||||
int16_t filtered_raw_x() const { return filter_.x(); }
|
||||
int16_t filtered_raw_y() const { return filter_.y(); }
|
||||
/// Set the threshold for the touch detection.
|
||||
void set_threshold(int16_t threshold) { this->threshold_ = threshold; }
|
||||
/// Set the pin used to detect the touch.
|
||||
void set_irq_pin(InternalGPIOPin *pin) { this->irq_pin_ = pin; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
// float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
TouchFilter filter_;
|
||||
RawCorrection correction_;
|
||||
static int16_t best_two_avg(int16_t value1, int16_t value2, int16_t value3);
|
||||
|
||||
int16_t read_adc_(uint8_t ctrl);
|
||||
|
||||
void update_touches() override;
|
||||
|
||||
int16_t threshold_;
|
||||
|
||||
InternalGPIOPin *irq_pin_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace esphome::xpt2046
|
||||
Reference in New Issue
Block a user