ESPHome  2022.6.3
xpt2046.cpp
Go to the documentation of this file.
1 #include "xpt2046.h"
2 #include "esphome/core/log.h"
3 #include "esphome/core/helpers.h"
4 
5 #include <algorithm>
6 
7 namespace esphome {
8 namespace xpt2046 {
9 
10 static const char *const TAG = "xpt2046";
11 
13  if (this->irq_pin_ != nullptr) {
14  // The pin reports a touch with a falling edge. Unfortunately the pin goes also changes state
15  // while the channels are read and wiring it as an interrupt is not straightforward and would
16  // need careful masking. A GPIO poll is cheap so we'll just use that.
17  this->irq_pin_->setup(); // INPUT
18  }
19  spi_setup();
20  read_adc_(0xD0); // ADC powerdown, enable PENIRQ pin
21 }
22 
24  if (this->irq_pin_ != nullptr) {
25  // Force immediate update if a falling edge (= touched is seen) Ignore if still active
26  // (that would mean that we missed the release because of a too long update interval)
27  bool val = this->irq_pin_->digital_read();
28  if (!val && this->last_irq_ && !this->touched) {
29  ESP_LOGD(TAG, "Falling penirq edge, forcing update");
30  update();
31  }
32  this->last_irq_ = val;
33  }
34 }
35 
37  int16_t data[6];
38  bool touch = false;
39  uint32_t now = millis();
40 
41  this->z_raw = 0;
42 
43  // In case the penirq pin is present only do the SPI transaction if it reports a touch (is low).
44  // The touch has to be also confirmed with checking the pressure over threshold
45  if (this->irq_pin_ == nullptr || !this->irq_pin_->digital_read()) {
46  enable();
47 
48  int16_t z1 = read_adc_(0xB1 /* Z1 */);
49  int16_t z2 = read_adc_(0xC1 /* Z2 */);
50 
51  this->z_raw = z1 + 4095 - z2;
52 
53  touch = (this->z_raw >= this->threshold_);
54  if (touch) {
55  read_adc_(0x91 /* Y */); // dummy Y measure, 1st is always noisy
56  data[0] = read_adc_(0xD1 /* X */);
57  data[1] = read_adc_(0x91 /* Y */); // make 3 x-y measurements
58  data[2] = read_adc_(0xD1 /* X */);
59  data[3] = read_adc_(0x91 /* Y */);
60  data[4] = read_adc_(0xD1 /* X */);
61  }
62 
63  data[5] = read_adc_(0x90 /* Y */); // Last Y touch power down
64 
65  disable();
66  }
67 
68  if (touch) {
69  this->x_raw = best_two_avg(data[0], data[2], data[4]);
70  this->y_raw = best_two_avg(data[1], data[3], data[5]);
71  } else {
72  this->x_raw = this->y_raw = 0;
73  }
74 
75  ESP_LOGV(TAG, "Update [x, y] = [%d, %d], z = %d%s", this->x_raw, this->y_raw, this->z_raw, (touch ? " touched" : ""));
76 
77  if (touch) {
78  // Normalize raw data according to calibration min and max
79 
80  int16_t x_val = normalize(this->x_raw, this->x_raw_min_, this->x_raw_max_);
81  int16_t y_val = normalize(this->y_raw, this->y_raw_min_, this->y_raw_max_);
82 
83  if (this->swap_x_y_) {
84  std::swap(x_val, y_val);
85  }
86 
87  if (this->invert_x_) {
88  x_val = 0x7fff - x_val;
89  }
90 
91  if (this->invert_y_) {
92  y_val = 0x7fff - y_val;
93  }
94 
95  x_val = (int16_t)((int) x_val * this->x_dim_ / 0x7fff);
96  y_val = (int16_t)((int) y_val * this->y_dim_ / 0x7fff);
97 
98  if (!this->touched || (now - this->last_pos_ms_) >= this->report_millis_) {
99  ESP_LOGD(TAG, "Raw [x, y] = [%d, %d], transformed = [%d, %d]", this->x_raw, this->y_raw, x_val, y_val);
100 
101  this->x = x_val;
102  this->y = y_val;
103  this->touched = true;
104  this->last_pos_ms_ = now;
105 
106  this->on_state_trigger_->process(this->x, this->y, true);
107  for (auto *button : this->buttons_)
108  button->touch(this->x, this->y);
109  }
110  } else {
111  if (this->touched) {
112  ESP_LOGD(TAG, "Released [%d, %d]", this->x, this->y);
113 
114  this->touched = false;
115 
116  this->on_state_trigger_->process(this->x, this->y, false);
117  for (auto *button : this->buttons_)
118  button->release();
119  }
120  }
121 }
122 
123 void XPT2046Component::set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
124  this->x_raw_min_ = std::min(x_min, x_max);
125  this->x_raw_max_ = std::max(x_min, x_max);
126  this->y_raw_min_ = std::min(y_min, y_max);
127  this->y_raw_max_ = std::max(y_min, y_max);
128  this->invert_x_ = (x_min > x_max);
129  this->invert_y_ = (y_min > y_max);
130 }
131 
133  ESP_LOGCONFIG(TAG, "XPT2046:");
134 
135  LOG_PIN(" IRQ Pin: ", this->irq_pin_);
136  ESP_LOGCONFIG(TAG, " X min: %d", this->x_raw_min_);
137  ESP_LOGCONFIG(TAG, " X max: %d", this->x_raw_max_);
138  ESP_LOGCONFIG(TAG, " Y min: %d", this->y_raw_min_);
139  ESP_LOGCONFIG(TAG, " Y max: %d", this->y_raw_max_);
140  ESP_LOGCONFIG(TAG, " X dim: %d", this->x_dim_);
141  ESP_LOGCONFIG(TAG, " Y dim: %d", this->y_dim_);
142  if (this->swap_x_y_) {
143  ESP_LOGCONFIG(TAG, " Swap X/Y");
144  }
145  ESP_LOGCONFIG(TAG, " threshold: %d", this->threshold_);
146  ESP_LOGCONFIG(TAG, " Report interval: %u", this->report_millis_);
147 
148  LOG_UPDATE_INTERVAL(this);
149 }
150 
152 
153 int16_t XPT2046Component::best_two_avg(int16_t x, int16_t y, int16_t z) {
154  int16_t da, db, dc;
155  int16_t reta = 0;
156 
157  da = (x > y) ? x - y : y - x;
158  db = (x > z) ? x - z : z - x;
159  dc = (z > y) ? z - y : y - z;
160 
161  if (da <= db && da <= dc) {
162  reta = (x + y) >> 1;
163  } else if (db <= da && db <= dc) {
164  reta = (x + z) >> 1;
165  } else {
166  reta = (y + z) >> 1;
167  }
168 
169  return reta;
170 }
171 
172 int16_t XPT2046Component::normalize(int16_t val, int16_t min_val, int16_t max_val) {
173  int16_t ret;
174 
175  if (val <= min_val) {
176  ret = 0;
177  } else if (val >= max_val) {
178  ret = 0x7fff;
179  } else {
180  ret = (int16_t)((int) 0x7fff * (val - min_val) / (max_val - min_val));
181  }
182 
183  return ret;
184 }
185 
186 int16_t XPT2046Component::read_adc_(uint8_t ctrl) {
187  uint8_t data[2];
188 
189  write_byte(ctrl);
190  data[0] = read_byte();
191  data[1] = read_byte();
192 
193  return ((data[0] << 8) | data[1]) >> 3;
194 }
195 
196 void XPT2046OnStateTrigger::process(int x, int y, bool touched) { this->trigger(x, y, touched); }
197 
198 void XPT2046Button::touch(int16_t x, int16_t y) {
199  bool touched = (x >= this->x_min_ && x <= this->x_max_ && y >= this->y_min_ && y <= this->y_max_);
200 
201  if (touched) {
202  this->publish_state(true);
203  this->state_ = true;
204  } else {
205  release();
206  }
207 }
208 
210  if (this->state_) {
211  this->publish_state(false);
212  this->state_ = false;
213  }
214 }
215 
216 } // namespace xpt2046
217 } // namespace esphome
const float DATA
For components that import data from directly connected sensors like DHT.
Definition: component.cpp:18
int16_t read_adc_(uint8_t ctrl)
Definition: xpt2046.cpp:186
static int16_t normalize(int16_t val, int16_t min_val, int16_t max_val)
Definition: xpt2046.cpp:172
void touch(int16_t x, int16_t y)
Definition: xpt2046.cpp:198
XPT2046OnStateTrigger * on_state_trigger_
Definition: xpt2046.h:119
virtual void setup()=0
void set_calibration(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max)
Set the coordinates for the touch screen edges.
Definition: xpt2046.cpp:123
uint32_t IRAM_ATTR HOT millis()
Definition: core.cpp:26
float get_setup_priority() const override
Definition: xpt2046.cpp:151
bool touched
True if the component currently detects the touch.
Definition: xpt2046.h:91
virtual bool digital_read()=0
void process(int x, int y, bool touched)
Definition: xpt2046.cpp:196
void swap(optional< T > &x, optional< T > &y)
Definition: optional.h:210
static int16_t best_two_avg(int16_t x, int16_t y, int16_t z)
Definition: xpt2046.cpp:153
void update() override
Read and process the values from the hardware.
Definition: xpt2046.cpp:36
Definition: a4988.cpp:4
std::vector< XPT2046Button * > buttons_
Definition: xpt2046.h:120
int16_t x
Coordinates of the touch position.
Definition: xpt2046.h:87
uint32_t val
Definition: datatypes.h:85
int16_t x_raw
Raw sensor values of the coordinates and the pressure.
Definition: xpt2046.h:98
void loop() override
Detect the touch if the irq pin is specified.
Definition: xpt2046.cpp:23