// The following touch screen support code by maxpautsch was merged 1/10/17 // https://github.com/maxpautsch // Define TOUCH_CS is the user setup file to enable this code // A demo is provided in examples Generic folder // Additions by Bodmer to double sample, use Z value to improve detection reliability // and to correct rotation handling // See license in root directory. /*************************************************************************************** ** Function name: begin_touch_read_write - was spi_begin_touch ** Description: Start transaction and select touch controller ***************************************************************************************/ // The touch controller has a low SPI clock rate inline void TFT_eSPI::begin_touch_read_write(void) { DMA_BUSY_CHECK; CS_H; // Just in case it has been left low #if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS) if (locked) { locked = false; spi.beginTransaction(SPISettings(SPI_TOUCH_FREQUENCY, MSBFIRST, SPI_MODE0)); } #else spi.setFrequency(SPI_TOUCH_FREQUENCY); #endif SET_BUS_READ_MODE; T_CS_L; } /*************************************************************************************** ** Function name: end_touch_read_write - was spi_end_touch ** Description: End transaction and deselect touch controller ***************************************************************************************/ inline void TFT_eSPI::end_touch_read_write(void) { T_CS_H; #if defined(SPI_HAS_TRANSACTION) && defined(SUPPORT_TRANSACTIONS) if (!inTransaction) { if (!locked) { locked = true; spi.endTransaction(); } } #else spi.setFrequency(SPI_FREQUENCY); #endif SET_BUS_WRITE_MODE; } /*************************************************************************************** ** Function name: Legacy - deprecated ** Description: Start/end transaction ***************************************************************************************/ void TFT_eSPI::spi_begin_touch() { begin_touch_read_write(); } void TFT_eSPI::spi_end_touch() { end_touch_read_write(); } void TFT_eSPI::set_touch_rotation(uint8_t rotation) { _rotation = rotation; } /*************************************************************************************** ** Function name: getTouchRaw ** Description: read raw touch position. Always returns true. ***************************************************************************************/ uint8_t TFT_eSPI::getTouchRaw(uint16_t* x, uint16_t* y) { uint16_t tmp1, tmp2; begin_touch_read_write(); // Start YP sample request for x position, read 4 times and keep last sample spi.transfer(0xd0); // Start new YP conversion spi.transfer(0); // Read first 8 bits spi.transfer(0xd0); // Read last 8 bits and start new YP conversion spi.transfer(0); // Read first 8 bits spi.transfer(0xd0); // Read last 8 bits and start new YP conversion spi.transfer(0); // Read first 8 bits spi.transfer(0xd0); // Read last 8 bits and start new YP conversion tmp1 = spi.transfer(0); // Read first 8 bits tmp1 = tmp1 << 5; tmp1 |= 0x1f & (spi.transfer(0x90) >> 3); // Read last 8 bits and start new XP conversion //*x = tmp; // Start XP sample request for y position, read 4 times and keep last sample spi.transfer(0); // Read first 8 bits spi.transfer(0x90); // Read last 8 bits and start new XP conversion spi.transfer(0); // Read first 8 bits spi.transfer(0x90); // Read last 8 bits and start new XP conversion spi.transfer(0); // Read first 8 bits spi.transfer(0x90); // Read last 8 bits and start new XP conversion tmp2 = spi.transfer(0); // Read first 8 bits tmp2 = tmp2 << 5; tmp2 |= 0x1f & (spi.transfer(0) >> 3); // Read last 8 bits //*y = tmp; switch (_rotation) { case 0: *x = 4095 - tmp2; *y = tmp1; break; case 1: *x = tmp1; *y = tmp2; break; case 2: *x = tmp2; *y = 4095 - tmp1; break; default: // 3 *x = 4095 - tmp1; *y = 4095 - tmp2; } end_touch_read_write(); return true; } /*************************************************************************************** ** Function name: getTouchRawZ ** Description: read raw pressure on touchpad and return Z value. ***************************************************************************************/ uint16_t TFT_eSPI::getTouchRawZ(void) { begin_touch_read_write(); // Z sample request int16_t tz = 0xFFF; spi.transfer(0xb0); // Start new Z1 conversion tz += spi.transfer16(0xc0) >> 3; // Read Z1 and start Z2 conversion tz -= spi.transfer16(0x00) >> 3; // Read Z2 end_touch_read_write(); return (uint16_t) tz; } /*************************************************************************************** ** Function name: validTouch ** Description: read validated position. Return false if not pressed. ***************************************************************************************/ #define _RAWERR 20 // Deadband error allowed in successive position samples uint8_t TFT_eSPI::validTouch(uint16_t* x, uint16_t* y, uint16_t threshold) { uint16_t x_tmp, y_tmp, x_tmp2, y_tmp2; // Wait until pressure stops increasing to debounce pressure uint16_t z1 = 1; uint16_t z2 = 0; while (z1 > z2) { z2 = z1; z1 = getTouchRawZ(); delay(1); } // Serial.print("Z = ");Serial.println(z1); if (z1 <= threshold) return false; getTouchRaw(&x_tmp, &y_tmp); // Serial.print("Sample 1 x,y = "); Serial.print(x_tmp);Serial.print(",");Serial.print(y_tmp); // Serial.print(", Z = ");Serial.println(z1); delay(1); // Small delay to the next sample if (getTouchRawZ() <= threshold) return false; delay(2); // Small delay to the next sample getTouchRaw(&x_tmp2, &y_tmp2); // Serial.print("Sample 2 x,y = "); Serial.print(x_tmp2);Serial.print(",");Serial.println(y_tmp2); // Serial.print("Sample difference = ");Serial.print(abs(x_tmp - x_tmp2));Serial.print(",");Serial.println(abs(y_tmp - y_tmp2)); if (abs(x_tmp - x_tmp2) > _RAWERR) return false; if (abs(y_tmp - y_tmp2) > _RAWERR) return false; *x = x_tmp; *y = y_tmp; return true; } /*************************************************************************************** ** Function name: getTouch ** Description: read callibrated position. Return false if not pressed. ***************************************************************************************/ #define Z_THRESHOLD 350 // Touch pressure threshold for validating touches uint8_t TFT_eSPI::getTouch(uint16_t* x, uint16_t* y, uint16_t threshold) { uint16_t x_tmp, y_tmp; if (threshold < 20) threshold = 20; if (_pressTime > millis()) threshold = 20; uint8_t n = 5; uint8_t valid = 0; while (n--) { if (validTouch(&x_tmp, &y_tmp, threshold)) valid++; ; } if (valid < 1) { _pressTime = 0; return false; } _pressTime = millis() + 50; convertRawXY(&x_tmp, &y_tmp); if (x_tmp >= _width || y_tmp >= _height) return false; _pressX = x_tmp; _pressY = y_tmp; *x = _pressX; *y = _pressY; return valid; } /*************************************************************************************** ** Function name: convertRawXY ** Description: convert raw touch x,y values to screen coordinates ***************************************************************************************/ void TFT_eSPI::convertRawXY(uint16_t* x, uint16_t* y) { uint16_t x_tmp = *x, y_tmp = *y, xx, yy; if (!touchCalibration_rotate) { xx = (x_tmp - touchCalibration_x0) * _width / touchCalibration_x1; yy = (y_tmp - touchCalibration_y0) * _height / touchCalibration_y1; if (touchCalibration_invert_x) xx = _width - xx; if (touchCalibration_invert_y) yy = _height - yy; } else { xx = (y_tmp - touchCalibration_x0) * _width / touchCalibration_x1; yy = (x_tmp - touchCalibration_y0) * _height / touchCalibration_y1; if (touchCalibration_invert_x) xx = _width - xx; if (touchCalibration_invert_y) yy = _height - yy; } *x = xx; *y = yy; } /*************************************************************************************** ** Function name: calibrateTouch ** Description: generates calibration parameters for touchscreen. ***************************************************************************************/ void TFT_eSPI::calibrateTouch(uint16_t* parameters, uint32_t color_fg, uint32_t color_bg, uint8_t size) { int16_t values[] = {0, 0, 0, 0, 0, 0, 0, 0}; uint16_t x_tmp, y_tmp; for (uint8_t i = 0; i < 4; i++) { fillRect(0, 0, size + 1, size + 1, color_bg); fillRect(0, _height - size - 1, size + 1, size + 1, color_bg); fillRect(_width - size - 1, 0, size + 1, size + 1, color_bg); fillRect(_width - size - 1, _height - size - 1, size + 1, size + 1, color_bg); if (i == 5) break; // used to clear the arrows switch (i) { case 0: // up left drawLine(0, 0, 0, size, color_fg); drawLine(0, 0, size, 0, color_fg); drawLine(0, 0, size, size, color_fg); break; case 1: // bot left drawLine(0, _height - size - 1, 0, _height - 1, color_fg); drawLine(0, _height - 1, size, _height - 1, color_fg); drawLine(size, _height - size - 1, 0, _height - 1, color_fg); break; case 2: // up right drawLine(_width - size - 1, 0, _width - 1, 0, color_fg); drawLine(_width - size - 1, size, _width - 1, 0, color_fg); drawLine(_width - 1, size, _width - 1, 0, color_fg); break; case 3: // bot right drawLine(_width - size - 1, _height - size - 1, _width - 1, _height - 1, color_fg); drawLine(_width - 1, _height - 1 - size, _width - 1, _height - 1, color_fg); drawLine(_width - 1 - size, _height - 1, _width - 1, _height - 1, color_fg); break; } // user has to get the chance to release if (i > 0) delay(1000); for (uint8_t j = 0; j < 8; j++) { // Use a lower detect threshold as corners tend to be less sensitive while (!validTouch(&x_tmp, &y_tmp, Z_THRESHOLD / 2)) ; values[i * 2] += x_tmp; values[i * 2 + 1] += y_tmp; } values[i * 2] /= 8; values[i * 2 + 1] /= 8; } // from case 0 to case 1, the y value changed. // If the measured delta of the touch x axis is bigger than the delta of the y axis, the touch and TFT axes are switched. touchCalibration_rotate = false; if (abs(values[0] - values[2]) > abs(values[1] - values[3])) { touchCalibration_rotate = true; touchCalibration_x0 = (values[1] + values[3]) / 2; // calc min x touchCalibration_x1 = (values[5] + values[7]) / 2; // calc max x touchCalibration_y0 = (values[0] + values[4]) / 2; // calc min y touchCalibration_y1 = (values[2] + values[6]) / 2; // calc max y } else { touchCalibration_x0 = (values[0] + values[2]) / 2; // calc min x touchCalibration_x1 = (values[4] + values[6]) / 2; // calc max x touchCalibration_y0 = (values[1] + values[5]) / 2; // calc min y touchCalibration_y1 = (values[3] + values[7]) / 2; // calc max y } // in addition, the touch screen axis could be in the opposite direction of the TFT axis touchCalibration_invert_x = false; if (touchCalibration_x0 > touchCalibration_x1) { values[0] = touchCalibration_x0; touchCalibration_x0 = touchCalibration_x1; touchCalibration_x1 = values[0]; touchCalibration_invert_x = true; } touchCalibration_invert_y = false; if (touchCalibration_y0 > touchCalibration_y1) { values[0] = touchCalibration_y0; touchCalibration_y0 = touchCalibration_y1; touchCalibration_y1 = values[0]; touchCalibration_invert_y = true; } // pre calculate touchCalibration_x1 -= touchCalibration_x0; touchCalibration_y1 -= touchCalibration_y0; if (touchCalibration_x0 == 0) touchCalibration_x0 = 1; if (touchCalibration_x1 == 0) touchCalibration_x1 = 1; if (touchCalibration_y0 == 0) touchCalibration_y0 = 1; if (touchCalibration_y1 == 0) touchCalibration_y1 = 1; // export parameters, if pointer valid if (parameters != NULL) { parameters[0] = touchCalibration_x0; parameters[1] = touchCalibration_x1; parameters[2] = touchCalibration_y0; parameters[3] = touchCalibration_y1; parameters[4] = touchCalibration_rotate | (touchCalibration_invert_x << 1) | (touchCalibration_invert_y << 2); } } /*************************************************************************************** ** Function name: setTouch ** Description: imports calibration parameters for touchscreen. ***************************************************************************************/ void TFT_eSPI::setTouch(uint16_t* parameters) { touchCalibration_x0 = parameters[0]; touchCalibration_x1 = parameters[1]; touchCalibration_y0 = parameters[2]; touchCalibration_y1 = parameters[3]; if (touchCalibration_x0 == 0) touchCalibration_x0 = 1; if (touchCalibration_x1 == 0) touchCalibration_x1 = 1; if (touchCalibration_y0 == 0) touchCalibration_y0 = 1; if (touchCalibration_y1 == 0) touchCalibration_y1 = 1; touchCalibration_rotate = parameters[4] & 0x01; touchCalibration_invert_x = parameters[4] & 0x02; touchCalibration_invert_y = parameters[4] & 0x04; }