ESPHome 2026.11.0-dev
Loading...
Searching...
No Matches
spd2010_touchscreen.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
2// SPDX-License-Identifier: Apache-2.0
3// Protocol adapted from esp_lcd_touch_spd2010 2.0.1 for ESPHome.
4// https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch_spd2010
5
7
9#include "esphome/core/log.h"
10
12
13ESPHOME_LOG_TAG(TAG, "spd2010.touchscreen");
14
15static constexpr uint16_t REG_CLEAR_INTERRUPT = 0x0002;
16static constexpr uint16_t REG_CPU_START = 0x0004;
17static constexpr uint16_t REG_STATUS = 0x0020;
18static constexpr uint16_t REG_VERSION = 0x0026;
19static constexpr uint16_t REG_TOUCH_START = 0x0046;
20static constexpr uint16_t REG_POINT_MODE = 0x0050;
21static constexpr uint16_t REG_HDP_STATUS = 0x02FC;
22static constexpr uint16_t REG_HDP = 0x0300;
23
24// Low status byte
25static constexpr uint8_t STATUS_POINT = 0x01;
26static constexpr uint8_t STATUS_GESTURE = 0x02;
27static constexpr uint8_t STATUS_AUX = 0x08;
28// High status byte
29static constexpr uint8_t STATUS_BIOS = 0x40;
30static constexpr uint8_t STATUS_CPU = 0x20;
31static constexpr uint8_t STATUS_RUNNING = 0x08;
32
33static constexpr uint8_t HDP_STATUS_DONE = 0x82;
34static constexpr uint8_t HDP_STATUS_MORE = 0x00;
35
36static constexpr uint8_t MAX_CONTACT_ID = 0x0A;
37static constexpr uint8_t GESTURE_REPORT_ID = 0xF6;
38static constexpr size_t REPORT_HEADER_LENGTH = 4;
39static constexpr size_t CONTACT_LENGTH = 6;
40static constexpr size_t MAX_REPORT_LENGTH = REPORT_HEADER_LENGTH + 10 * CONTACT_LENGTH;
41static constexpr uint8_t MAX_DRAIN_READS = 8;
42
43static constexpr uint32_t RESET_PULSE_WIDTH =
44 20; // longer than the datasheet says, but the controller seems to need it
45static constexpr uint32_t RESET_DELAY = 20;
46
47bool SPD2010Touchscreen::read_register_(uint16_t reg, uint8_t *data, size_t length) const {
48 const uint8_t command[] = {static_cast<uint8_t>(reg), static_cast<uint8_t>(reg >> 8u)};
49 if (this->write(command, sizeof(command)) != i2c::ERROR_OK)
50 return false;
51 // The controller needs a STOP and a delay before a commandless receive.
53 return this->read(data, length) == i2c::ERROR_OK;
54}
55
56bool SPD2010Touchscreen::write_command_(uint16_t reg, uint16_t value) const {
57 const uint8_t command[]{
58 static_cast<uint8_t>(reg),
59 static_cast<uint8_t>(reg >> 8u),
60 static_cast<uint8_t>(value),
61 static_cast<uint8_t>(value >> 8u),
62 };
63 return this->write(command, sizeof(command)) == i2c::ERROR_OK;
64}
65
66bool SPD2010Touchscreen::clear_interrupt_() const { return this->write_command_(REG_CLEAR_INTERRUPT, 1); }
67
69 if (this->x_raw_max_ == this->x_raw_min_)
70 this->x_raw_max_ = this->display_->get_native_width() - 1;
71 if (this->y_raw_max_ == this->y_raw_min_)
72 this->y_raw_max_ = this->display_->get_native_height() - 1;
73 if (this->reset_pin_ != nullptr) {
74 this->reset_pin_->setup();
75 this->reset_pin_->digital_write(false);
76 this->set_timeout(RESET_PULSE_WIDTH, [this] {
77 this->reset_pin_->digital_write(true);
78 this->set_timeout(RESET_DELAY, [this] { this->initialize_(); });
79 });
80 } else {
81 this->initialize_();
82 }
83}
84
86 uint8_t version[18];
87 if (!this->read_register_(REG_VERSION, version, sizeof(version))) {
88 this->mark_failed(LOG_STR("Failed to read firmware version"));
89 return;
90 }
91 ESP_LOGD(TAG, "Firmware version: 0x%04X", encode_uint16(version[5], version[4]));
92 if (this->interrupt_pin_ != nullptr) {
93 this->interrupt_pin_->setup();
95 }
96 this->ready_ = true;
97}
98
100 // A corrupt status must not cause an unbounded drain loop or buffer overflow.
101 for (uint8_t attempt = 0; attempt != MAX_DRAIN_READS; attempt++) {
102 uint8_t status[8];
103 if (!this->read_register_(REG_HDP_STATUS, status, sizeof(status)))
104 return false;
105 if (status[5] == HDP_STATUS_DONE)
106 return this->clear_interrupt_();
107 const uint16_t length = encode_uint16(status[3], status[2]);
108 if (status[5] != HDP_STATUS_MORE || length == 0 || length > MAX_REPORT_LENGTH)
109 break;
110 uint8_t remaining[MAX_REPORT_LENGTH];
111 if (!this->read_register_(REG_HDP, remaining, length))
112 return false;
113 }
114 ESP_LOGW(TAG, "Invalid or unfinished report");
115 (void) this->clear_interrupt_();
116 return false;
117}
118
119// Returns true only when touch data was read correctly and should be published.
121 uint8_t status[4];
122 if (!this->read_register_(REG_STATUS, status, sizeof(status))) {
123 this->status_set_warning(LOG_STR("Failed to read status"));
124 return false;
125 }
126 const uint8_t status_low = status[0];
127 const uint8_t status_high = status[1];
128 const uint16_t length = encode_uint16(status[3], status[2]);
129
130 if (status_high & STATUS_BIOS) {
131 this->status_set_warning(LOG_STR("BIOS status; start CPU"));
132 (void) (this->clear_interrupt_() && this->write_command_(REG_CPU_START, 1));
133 return false;
134 }
135 if (status_high & STATUS_CPU) {
136 this->status_set_warning(LOG_STR("CPU status; stop point mode, start touch"));
137 (void) (this->write_command_(REG_POINT_MODE, 0) && this->write_command_(REG_TOUCH_START, 0) &&
138 this->clear_interrupt_());
139 return false;
140 }
141 if ((status_high & STATUS_RUNNING) && length == 0) {
142 if (!this->clear_interrupt_()) {
143 this->status_set_warning(LOG_STR("Failed to clear interrupt"));
144 return false;
145 }
146 this->status_clear_warning();
147 return true;
148 }
149 if (!(status_low & (STATUS_POINT | STATUS_GESTURE))) {
150 if ((status_high & STATUS_RUNNING) && (status_low & STATUS_AUX) && !this->clear_interrupt_()) {
151 this->status_set_warning(LOG_STR("Failed to clear interrupt"));
152 return false;
153 }
154 this->status_clear_warning();
155 return false;
156 }
157 if (length < REPORT_HEADER_LENGTH || length > MAX_REPORT_LENGTH ||
158 (length - REPORT_HEADER_LENGTH) % CONTACT_LENGTH != 0) {
159 ESP_LOGW(TAG, "Invalid report length: %u", length);
160 this->status_set_warning(LOG_STR("Invalid report length"));
161 (void) this->clear_interrupt_();
162 return false;
163 }
164 uint8_t report[MAX_REPORT_LENGTH];
165 if (!this->read_register_(REG_HDP, report, length) || !this->finish_report_()) {
166 this->status_set_warning(LOG_STR("Failed to read report"));
167 return false;
168 }
169 // Gesture reports are not used.
170 if (!(status_low & STATUS_POINT) ||
171 (length > REPORT_HEADER_LENGTH && report[REPORT_HEADER_LENGTH] == GESTURE_REPORT_ID)) {
172 this->status_clear_warning();
173 return false;
174 }
175 // Check every ID before adding any point, so a bad report leaves the stored touches unchanged.
176 for (size_t offset = REPORT_HEADER_LENGTH; offset < length; offset += CONTACT_LENGTH) {
177 if (report[offset] > MAX_CONTACT_ID) {
178 this->status_set_warning(LOG_STR("Invalid contact ID"));
179 return false;
180 }
181 }
182 for (size_t offset = REPORT_HEADER_LENGTH; offset < length; offset += CONTACT_LENGTH) {
183 const uint8_t *contact = report + offset;
184 // Zero strength marks a released contact.
185 if (contact[4] == 0)
186 continue;
187 const uint16_t x = ((contact[3] & 0xF0u) << 4u) | contact[1];
188 const uint16_t y = ((contact[3] & 0x0Fu) << 8u) | contact[2];
189 this->add_raw_touch_position_(contact[0], x, y, contact[4]);
190 }
191 this->status_clear_warning();
192 return true;
193}
194
196 if (!this->ready_ || !this->read_data_())
197 this->skip_update_ = true;
198}
199
201 ESP_LOGCONFIG(TAG,
202 "SPD2010 Touchscreen:\n"
203 " Raw X range: %d-%d\n"
204 " Raw Y range: %d-%d",
205 this->x_raw_min_, this->x_raw_max_, this->y_raw_min_, this->y_raw_max_);
206 LOG_I2C_DEVICE(this);
207 LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
208 LOG_PIN(" Reset Pin: ", this->reset_pin_);
209}
210
211} // namespace esphome::spd2010
uint8_t status
Definition bl0942.h:8
void mark_failed()
Mark this component as failed.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
Definition component.cpp:96
void status_clear_warning()
Definition component.h:291
virtual void setup()=0
virtual void digital_write(bool value)=0
int get_native_width()
Get the native (original) width of the display in pixels.
Definition display.h:338
int get_native_height()
Get the native (original) height of the display in pixels.
Definition display.h:340
ErrorCode write(const uint8_t *data, size_t len) const
writes an array of bytes to a device using an I2CBus
Definition i2c.h:183
ErrorCode read(uint8_t *data, size_t len) const
reads an array of bytes from the device using an I2CBus
Definition i2c.h:163
I2CRegister reg(uint8_t a_register)
calls the I2CRegister constructor
Definition i2c.h:152
bool read_register_(uint16_t reg, uint8_t *data, size_t length) const
bool write_command_(uint16_t reg, uint16_t value) const
void attach_interrupt_(InternalGPIOPin *irq_pin, esphome::gpio::InterruptType type)
Call this function to send touch points to the on_touch listener and the binary_sensors.
void add_raw_touch_position_(uint8_t id, int16_t x_raw, int16_t y_raw, int16_t z_raw=0)
@ INTERRUPT_FALLING_EDGE
Definition gpio_pin.h:53
@ ERROR_OK
No error found during execution of method.
Definition i2c_bus.h:15
ESPHOME_LOG_TAG(TAG, "spd2010.touchscreen")
void delay_microseconds_safe(uint32_t us)
Delay for the given amount of microseconds, possibly yielding to other processes during the wait.
Definition helpers.cpp:889
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
Definition helpers.h:980
static void uint32_t
uint16_t length
Definition tt21100.cpp:0
uint16_t x
Definition tt21100.cpp:5
uint16_t y
Definition tt21100.cpp:6