GT911 I2C touch connects to ESP32S3 screen

Hi everyone,

I’m working on a project using the ESP32-S3-Touch-LCD-1.28 from Waveshare and an A061VW01 trackpad with the GT911 chip.

I’ve successfully implemented separate codes for:

  1. Switching functions on the ESP32S3 display.
  2. Using the trackpad to control multiple LED beads, which successfully works without involving the display.

However, when I try to merge these two codes, the trackpad stops working entirely.

I am confused if I’m using the Wire library for I2C communication in the wrong way, but I couldn’t find the problem.

Has anyone experienced a similar issue or have suggestions for troubleshooting? Any help would be greatly appreciated!

The current code is below

#include <Arduino.h>
#include <TFT_eSPI.h>
#include "LCD_Test.h"  //觸控庫
#include <SPI.h>
#include <stdint.h>
#include <AnimatedGIF.h>
#include "ImageData.h"    // 圖片
#include "images/siri.h"  // GIF
#include <Wire.h>
#include <Adafruit_NeoPixel.h>

// I²C
#define SDA_PIN 15
#define SCL_PIN 16
#define RST_PIN 18
#define I2C_DEVICE_ADDRESS 0x14

#define GT911_TOUCH_STATUS 0x814E
#define GT911_FIRST_POINT 0x8150

// LED
#define LED_PIN 21
#define NUM_LEDS 28
Adafruit_NeoPixel strip(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

TFT_eSPI tft = TFT_eSPI();
CST816S touch(6, 7, 13, 5);  // SDA, SCL, RST, IRQ
AnimatedGIF gif;

enum DisplayMode {
  GIF_DISPLAY,
  SINGLE_IMAGE,
  IMAGE_SLIDER, 
  CAMERA_CONTROL,
  TIME_SELECTOR
};

DisplayMode currentMode = GIF_DISPLAY;
#define GIF_IMAGE siri

const uint16_t* imageArray[7] = { gImage_5, gImage_1, gImage_2, gImage_3, gImage_4, gImage_5, gImage_1 };
const uint16_t* imageArrayCam[6] = { gImage_zoom, gImage_snap, gImage_flip, gImage_brightness, gImage_zoom, gImage_snap };
int currentIndex = 1;
int16_t currentOffset = 240;
int16_t targetOffset = 240;
bool isTouching = false;
int16_t touchX_start, touchX;
int touchDelta = 0;

uint16_t TOUCH_WIDTH = 700;
uint16_t TOUCH_HEIGHT = 800;

bool ledStates[NUM_LEDS] = { false };
bool fading = false;

#define CENTER_X 120
#define CENTER_Y 120
#define RADIUS 100
#define RING_WIDTH 15
#define RADIUS_LIMIT 65

float currentAngle = 0;
float lastAngle = 0;
int hours = 0;
int minutes = 0;
bool firstTouch = true;

#define TOUCH_MOVE_THRESHOLD 20
#define SWIPE_THRESHOLD 50
#define JUMP_DELAY 500

int16_t touchY_start;
bool isVerticalSwipe = false;
unsigned long lastTouchReleaseTime = 0;
unsigned long lastTouchStateChange = 0;

void setup() {
  Serial.begin(115200);
  Wire.begin(SDA_PIN, SCL_PIN);
  pinMode(RST_PIN, OUTPUT);

  resetTouchPanel();
  Serial.println("Touch panel initialized.");

  strip.begin();
  strip.show();
  Serial.println("RGB LED strip initialized.");

  tft.init();
  tft.setRotation(2);
  tft.fillScreen(TFT_BLACK);
  touch.begin();
  gif.begin(BIG_ENDIAN_PIXELS);
  initGifDisplay();
}

void loop() {
  handleTFTTouch();
  handleTouchPanel();
}

void handleTFTTouch() {
  static uint32_t lastDebugTime = 0;
  if (millis() - lastDebugTime > 1000) {
    lastDebugTime = millis();

    if (isTouching && (millis() - lastTouchStateChange > 3000)) {
      isTouching = false;
      isVerticalSwipe = false;
      lastTouchStateChange = millis();
    }
  }

  static DisplayMode lastMode = currentMode;
  switch (currentMode) {
    case GIF_DISPLAY:
      handleGifDisplay();
      handleGifTouch();
      break;
    case SINGLE_IMAGE:
      handleSingleImageTouch();
      break;
    case IMAGE_SLIDER:
      handleImageSliderTouch();
      if (!isVerticalSwipe) {
        updateDisplay();
        handleEdgeJump();
      }
      break;
    case CAMERA_CONTROL:
      handleCameraTouch();
      if (!isVerticalSwipe) {
        updateDisplayCam();
        handleEdgeJumpCam();
      }
      break;
    case TIME_SELECTOR:
      handleTimeSelectorTouch();
      break;
  }
}

void handleTouchPanel() {
  uint8_t touchStatus = readRegister8(GT911_TOUCH_STATUS);
  if (touchStatus & 0x80) {
    fading = false;
    uint16_t touchX = readRegister16(GT911_FIRST_POINT);
    uint16_t touchY = readRegister16(GT911_FIRST_POINT + 2);

    if (touchX > TOUCH_WIDTH) TOUCH_WIDTH = touchX;
    if (touchY > TOUCH_HEIGHT) TOUCH_HEIGHT = touchY;

    uint16_t newX = TOUCH_WIDTH - touchX;
    uint16_t newY = TOUCH_HEIGHT - touchY;

    int ledIndexX = map(newX, 20, 700, 16, 0);
    int ledIndexY = map(newY, 250, 750, 17, 27);

    ledIndexX = constrain(ledIndexX, 0, 16);
    ledIndexY = constrain(ledIndexY, 17, 27);

    ledStates[ledIndexX] = true;
    ledStates[ledIndexY] = true;

    for (int i = 0; i < NUM_LEDS; i++) {
      if (ledStates[i]) {
        strip.setPixelColor(i, strip.Color(255, 0, 255));
      } else {
        strip.setPixelColor(i, strip.Color(0, 0, 255));
      }
    }
    strip.show();

    writeRegister8(GT911_TOUCH_STATUS, 0);
  } else if (!fading) {
    fading = true;
    fadeOutLights();
  }
  delay(50);
}

void fadeOutLights() {
  for (int brightness = 255; brightness >= 0; brightness -= 10) {
    for (int i = 0; i < NUM_LEDS; i++) {
      if (ledStates[i]) {
        uint8_t r = (255 * brightness) / 255;
        uint8_t g = (0 * brightness) / 255;
        uint8_t b = (255 * brightness) / 255;
        strip.setPixelColor(i, strip.Color(r, g, b));
      } else {
        uint8_t r = (0 * brightness) / 255;
        uint8_t g = (0 * brightness) / 255;
        uint8_t b = (255 * brightness) / 255;
        strip.setPixelColor(i, strip.Color(r, g, b));
      }
    }
    strip.show();
    delay(15);
  }

  clearLEDs();
  strip.show();
}

void resetTouchPanel() {
  digitalWrite(RST_PIN, LOW);
  delay(100);
  digitalWrite(RST_PIN, HIGH);
  delay(200);
}

void clearLEDs() {
  for (int i = 0; i < NUM_LEDS; i++) {
    strip.setPixelColor(i, 0);
    ledStates[i] = false;
  }
}

uint8_t readRegister8(uint16_t reg) {
  Wire.beginTransmission(I2C_DEVICE_ADDRESS);
  Wire.write(highByte(reg));
  Wire.write(lowByte(reg));
  Wire.endTransmission();

  Wire.requestFrom(I2C_DEVICE_ADDRESS, 1);
  if (Wire.available()) {
    return Wire.read();
  }
  return 0;
}

uint16_t readRegister16(uint16_t reg) {
  Wire.beginTransmission(I2C_DEVICE_ADDRESS);
  Wire.write(highByte(reg));
  Wire.write(lowByte(reg));
  Wire.endTransmission();

  Wire.requestFrom(I2C_DEVICE_ADDRESS, 2);
  uint16_t value = 0;
  if (Wire.available()) {
    value = Wire.read();
    value |= (Wire.read() << 8);
  }
  return value;
}

void writeRegister8(uint16_t reg, uint8_t value) {
  Wire.beginTransmission(I2C_DEVICE_ADDRESS);
  Wire.write(highByte(reg));
  Wire.write(lowByte(reg));
  Wire.write(value);
  Wire.endTransmission();
}

void handleGifTouch() {
  if (touch.available()) {
    int16_t touchY = touch.data.y;
    if (!isTouching) {
      touchY_start = touchY;
      isTouching = true;
      lastTouchStateChange = millis();
    }

    int16_t verticalDelta = touchY - touchY_start;
    if (abs(verticalDelta) > SWIPE_THRESHOLD) {
      if (verticalDelta < 0) {
        currentMode = SINGLE_IMAGE;
        gif.close();
        tft.endWrite();
        tft.fillScreen(TFT_BLACK);
        tft.pushImage(0, 0, 240, 240, gImage_black);

        isTouching = false;
        isVerticalSwipe = false;
        delay(50);
      }
    }
  } else {
    isTouching = false;
  }
}

void handleSingleImageTouch() {
  if (millis() - lastTouchStateChange < 200) {
    return;
  }

  if (touch.available()) {
    int16_t touchY = touch.data.y;

    if (!isTouching) {
      touchY_start = touchY;
      isTouching = true;
      lastTouchStateChange = millis();
    }

    int16_t verticalDelta = touchY - touchY_start;
    if (abs(verticalDelta) > SWIPE_THRESHOLD) {
      if (verticalDelta < 0) {
        currentMode = IMAGE_SLIDER;
        tft.fillScreen(TFT_BLACK);
        updateDisplay();

        isTouching = false;
        isVerticalSwipe = false;
        lastTouchStateChange = millis();
        return;
      } else if (verticalDelta > 0) {
        currentMode = GIF_DISPLAY;
        tft.fillScreen(TFT_BLACK);
        initGifDisplay();

        isTouching = false;
        isVerticalSwipe = false;
        lastTouchStateChange = millis();
        return;
      }
    }
  } else {
    if (isTouching) {
      isTouching = false;
      lastTouchStateChange = millis();
    }
  }
}

void handleImageSliderTouch() {
  if (touch.available()) {
    touchX = touch.data.x;
    int16_t touchY = touch.data.y;

    if (!isTouching) {
      touchX_start = touchX;
      touchY_start = touchY;
      isTouching = true;
      isVerticalSwipe = false;
    }

    int16_t verticalDelta = touchY - touchY_start;
    if (abs(verticalDelta) > SWIPE_THRESHOLD && !isVerticalSwipe) {
      if (verticalDelta < 0) {
        currentMode = CAMERA_CONTROL;
        tft.fillScreen(TFT_BLACK);
        updateDisplayCam();
        currentIndex = 1;
        currentOffset = targetOffset = currentIndex * 240;
        touchDelta = 0;

        isTouching = false;
        isVerticalSwipe = false;
        lastTouchStateChange = millis();
        return;
      } else if (verticalDelta > 0) {
        currentMode = SINGLE_IMAGE;
        isTouching = false;
        isVerticalSwipe = true;
        lastTouchStateChange = millis();
        tft.fillScreen(TFT_BLACK);
        tft.pushImage(0, 0, 240, 240, gImage_black);

        currentIndex = 1;
        currentOffset = targetOffset = currentIndex * 240;
        touchDelta = 0;
        return;
      }
      isVerticalSwipe = true;
    }

    if (!isVerticalSwipe) {
      touchDelta = touchX_start - touchX;
      currentOffset = targetOffset - touchDelta;
    }
  } else if (isTouching) {
    isTouching = false;
    int16_t totalDelta = touchX - touchX_start;
    lastTouchReleaseTime = millis();

    if (abs(totalDelta) > TOUCH_MOVE_THRESHOLD) {
      if (totalDelta < 0) {
        currentIndex--;
        if (currentIndex < 0) currentIndex = 6;
      } else {
        currentIndex++;
        if (currentIndex > 6) currentIndex = 0;
      }
      targetOffset = currentIndex * 240;
    } else {
      targetOffset = currentIndex * 240;
    }
  }
}

void handleCameraTouch() {
  if (touch.available()) {
    touchX = touch.data.x;
    int16_t touchY = touch.data.y;

    if (!isTouching) {
      touchX_start = touchX;
      touchY_start = touchY;
      isTouching = true;
      isVerticalSwipe = false;
    }

    int16_t verticalDelta = touchY - touchY_start;
    if (abs(verticalDelta) > SWIPE_THRESHOLD && !isVerticalSwipe) {
      if (verticalDelta < 0) {
        currentMode = TIME_SELECTOR;
        isTouching = false;
        isVerticalSwipe = true;
        tft.fillScreen(TFT_BLACK);
        currentIndex = 1;
        currentOffset = targetOffset = currentIndex * 240;
        drawBackgroundRing();
        drawTimeRing(0);
        displayTime(0, 0);
        return;
      } else if (verticalDelta > 0) {
        currentMode = IMAGE_SLIDER;
        tft.fillScreen(TFT_BLACK);
        updateDisplay();
        currentIndex = 1;
        currentOffset = targetOffset = currentIndex * 240;
        touchDelta = 0;

        isTouching = false;
        isVerticalSwipe = false;
        lastTouchStateChange = millis();
        return;
      }
      isVerticalSwipe = true;
    }

    if (!isVerticalSwipe) {
      touchDelta = touchX_start - touchX;
      currentOffset = targetOffset - touchDelta;
    }
  } else if (isTouching) {
    isTouching = false;
    int16_t totalDelta = touchX - touchX_start;
    lastTouchReleaseTime = millis();

    if (abs(totalDelta) > TOUCH_MOVE_THRESHOLD) {
      if (totalDelta < 0) {
        currentIndex--;
        if (currentIndex < 0) currentIndex = 6;
      } else {
        currentIndex++;
        if (currentIndex > 6) currentIndex = 0;
      }
      targetOffset = currentIndex * 240;
    } else {
      targetOffset = currentIndex * 240;
    }
  }
}

void handleTimeSelectorTouch() {
  unsigned long currentTime = millis();
  if (currentMode == TIME_SELECTOR && currentTime - lastTouchStateChange > 5000) {
    currentMode = GIF_DISPLAY;
    tft.fillScreen(TFT_BLACK);
    initGifDisplay();

    currentAngle = 0;
    lastAngle = 0;
    hours = 0;
    minutes = 0;
    isTouching = false;
    firstTouch = true;
    return;
  }

  if (touch.available()) {
    uint16_t x = touch.data.x;
    uint16_t y = touch.data.y;

    if (!isTouching) {
      touchY_start = y;
      isTouching = true;
      isVerticalSwipe = false;
      lastTouchStateChange = millis();
    }

    float distance = sqrt(pow(x - CENTER_X, 2) + pow(y - CENTER_Y, 2));

    if (distance <= RADIUS_LIMIT) {
      int16_t verticalDelta = y - touchY_start;
      if (abs(verticalDelta) > SWIPE_THRESHOLD && !isVerticalSwipe) {
        if (verticalDelta > 50) {
          currentMode = CAMERA_CONTROL;
          tft.fillScreen(TFT_BLACK);
          updateDisplayCam();
          currentAngle = 0;
          lastAngle = 0;
          hours = 0;
          minutes = 0;

          isTouching = false;
          isVerticalSwipe = false;
          lastTouchStateChange = millis();
          return;
        } else if (verticalDelta < 50) {
          currentMode = GIF_DISPLAY;
          tft.fillScreen(TFT_BLACK);
          initGifDisplay();
          currentAngle = 0;
          lastAngle = 0;
          hours = 0;
          minutes = 0;

          isTouching = false;
          isVerticalSwipe = false;
          lastTouchStateChange = millis();
          return;
        }
      }
    } else {
      if (isTouching) {
        isTouching = false;
        lastTouchStateChange = millis();
      }
    }

    if (distance > RADIUS_LIMIT) {
      if (firstTouch) {
        touchY_start = y;
        lastAngle = atan2(y - CENTER_Y, x - CENTER_X) * 180 / PI - 90;
        firstTouch = false;
      }

      float touchAngle = atan2(y - CENTER_Y, x - CENTER_X) * 180 / PI - 90;
      touchAngle = fmod(touchAngle + 360, 360);

      float angleDiff = touchAngle - lastAngle;
      if (angleDiff > 180) angleDiff -= 360;
      if (angleDiff < -180) angleDiff += 360;

      currentAngle += angleDiff;
      if (currentAngle > 360) currentAngle = 360;
      if (currentAngle < 0) currentAngle = 0;

      float totalMinutes = (currentAngle / 360) * 360;
      hours = int(totalMinutes) / 60;
      minutes = int(totalMinutes) % 60;

      drawTimeRing(currentAngle);
      displayTime(hours, minutes);

      lastAngle = touchAngle;
    }
  } else {
    firstTouch = true;
  }
}


void initGifDisplay() {
  if (gif.open((uint8_t*)GIF_IMAGE, sizeof(GIF_IMAGE), GIFDraw)) {
    tft.startWrite();
  }
}

void handleGifDisplay() {
  if (gif.playFrame(true, NULL)) {
    yield();
  } else {
    gif.close();
    tft.endWrite();
    initGifDisplay();
  }
}

void handleEdgeJump() {
  if ((currentIndex == 0 || currentIndex == 6) && (millis() - lastTouchReleaseTime >= JUMP_DELAY)) {
    if (currentIndex == 0) {
      currentIndex = 5;
    } else if (currentIndex == 6) {
      currentIndex = 1;
    }
    currentOffset = targetOffset = currentIndex * 240;
    lastTouchReleaseTime = 0;
  }
}

void handleEdgeJumpCam() {
  if ((currentIndex == 0 || currentIndex == 5) && (millis() - lastTouchReleaseTime >= JUMP_DELAY)) {
    if (currentIndex == 0) {
      currentIndex = 4;
    } else if (currentIndex == 5) {
      currentIndex = 1;
    }
    currentOffset = targetOffset = currentIndex * 240;
    lastTouchReleaseTime = 0;
  }
}

void updateDisplay() {
  if (!isTouching && currentOffset != targetOffset) {
    int delta = targetOffset - currentOffset;
    int speed = max(abs(delta) / 4, 8);
    if (delta > 0) {
      currentOffset += speed;
      if (currentOffset > targetOffset) currentOffset = targetOffset;
    } else {
      currentOffset -= speed;
      if (currentOffset < targetOffset) currentOffset = targetOffset;
    }
  }

  int offset = currentOffset;
  while (offset < 0) offset += 240 * 7;
  offset = offset % (240 * 7);

  int displayIndex = offset / 240;

  tft.pushImage(-offset % 240, 0, 240, 240, imageArray[displayIndex]);

  if (offset % 240 != 0) {
    int nextIndex = (displayIndex + 1) % 7;
    tft.pushImage(240 - (offset % 240), 0, 240, 240, imageArray[nextIndex]);

    int prevIndex = (displayIndex - 1 + 7) % 7;
    if (offset % 240 != 0) {
      tft.pushImage(-240 - (offset % 240), 0, 240, 240, imageArray[prevIndex]);
    }
  }
}

void updateDisplayCam() {
  if (!isTouching && currentOffset != targetOffset) {
    int delta = targetOffset - currentOffset;
    int speed = max(abs(delta) / 4, 8);
    if (delta > 0) {
      currentOffset += speed;
      if (currentOffset > targetOffset) currentOffset = targetOffset;
    } else {
      currentOffset -= speed;
      if (currentOffset < targetOffset) currentOffset = targetOffset;
    }
  }

  int offset = currentOffset;
  while (offset < 0) offset += 240 * 6;
  offset = offset % (240 * 6);

  int displayIndex = offset / 240;

  tft.pushImage(-offset % 240, 0, 240, 240, imageArrayCam[displayIndex]);

  if (offset % 240 != 0) {
    int nextIndex = (displayIndex + 1) % 6;
    tft.pushImage(240 - (offset % 240), 0, 240, 240, imageArrayCam[nextIndex]);

    int prevIndex = (displayIndex - 1 + 6) % 6;
    if (offset % 240 != 0) {
      tft.pushImage(-240 - (offset % 240), 0, 240, 240, imageArrayCam[prevIndex]);
    }
  }
}

void drawBackgroundRing() {
  tft.fillCircle(CENTER_X, CENTER_Y, RADIUS, TFT_DARKGREY);
  tft.fillCircle(CENTER_X, CENTER_Y, RADIUS - RING_WIDTH, TFT_BLACK);
}

void displayTime(int h, int m) {
  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.fillRect(CENTER_X - 50, CENTER_Y - 15, 100, 30, TFT_BLACK);

  char timeStr[6];
  sprintf(timeStr, "%02d:%02d", h, m);

  int textWidth = tft.textWidth(timeStr);
  int textHeight = 16;

  int x = CENTER_X - (textWidth / 2);
  int y = CENTER_Y - (textHeight / 2);

  tft.drawString(timeStr, x, y);
}

void drawTimeRing(float angle) {
  for (int i = -90; i < 270; i++) {
    float radian = i * PI / 180;
    int x = CENTER_X + (RADIUS - RING_WIDTH / 2) * cos(radian);
    int y = CENTER_Y + (RADIUS - RING_WIDTH / 2) * sin(radian);

    if (i >= angle - 90 || angle <= 0) {
      tft.fillCircle(x, y, RING_WIDTH / 2, TFT_DARKGREY);
    }
  }

  if (angle > 0) {
    for (int i = -90; i < angle - 90; i++) {
      float radian = i * PI / 180;
      int x = CENTER_X + (RADIUS - RING_WIDTH / 2) * cos(radian);
      int y = CENTER_Y + (RADIUS - RING_WIDTH / 2) * sin(radian);
      tft.fillCircle(x, y, RING_WIDTH / 2, TFT_WHITE);
    }
  }
}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.