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:
- Switching functions on the ESP32S3 display.
- 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);
}
}
}