Skip to content

Instantly share code, notes, and snippets.

@polprog
Created December 17, 2025 14:06
Show Gist options
  • Select an option

  • Save polprog/35c74d9ff8ec02ac14f963b7b2ebf140 to your computer and use it in GitHub Desktop.

Select an option

Save polprog/35c74d9ff8ec02ac14f963b7b2ebf140 to your computer and use it in GitHub Desktop.
// =========================================================================
// GCYD35_Template.ino
// -------------------------------------------------------------------------
// The ESP32-3248S035C Template Project (G-Series)
// =========================================================================
//
// Original code author: Marvin A Cabico (Developer & Systems Integrator) + Gemini LLM
// https://github.com/mcabico-ict/GCYD35_Template/tree/main
// Ported to resistive touch CYD by polprog
//
// BOARD MODEL: ESP32-DA-WROOM Module
// HARDWARE: ST7796 Display Driver / XPT2046
// Board name: 3.5" LCD Display, ESP32-32E 320x480 Resistance Touch
#include <Arduino.h>
#include <SPI.h>
// LGFX Libraries (used for display and touch)
#define LGFX_USE_V1
#include <LovyanGFX.hpp>
// PIN DEFINITIONS & CONFIGURATION
// Display Pins
#define TFT_CS 15 // Chip Select
#define TFT_SCK 14 // SPI Clock
#define TFT_MOSI 13 // MOSI (Data)
#define TFT_MISO 12 // MISO (currently unused in this SPI 3-wire config, but defined for completeness)
#define TFT_DC 2 // Data/Command
#define TFT_BL 27 // Backlight PWM
// XPT2046 pins
#define TOUCH_CS 33
#define TOUCH_SCK 14
#define TOUCH_MOSI 13
#define TOUCH_MISO 12
#define TOUCH_INT 36
// HARDWARE CONFIGURATION (CYD 3.5-inch Standard Pinout)
class LGFX : public lgfx::LGFX_Device
{
lgfx::Panel_ST7796 _panel_instance;
lgfx::Bus_SPI _bus_instance;
lgfx::Light_PWM _light_instance;
lgfx::Touch_XPT2046 _touch_instance;
public:
LGFX(void)
{
// SPI Bus Setup
{
auto cfg = _bus_instance.config();
cfg.spi_host = HSPI_HOST;
cfg.spi_mode = 0;
cfg.freq_write = 80000000;
cfg.freq_read = 40000000;
cfg.spi_3wire = true;
cfg.use_lock = true;
cfg.dma_channel = SPI_DMA_CH_AUTO;
cfg.pin_sclk = TFT_SCK;
cfg.pin_mosi = TFT_MOSI;
cfg.pin_miso = TFT_MISO;
cfg.pin_dc = TFT_DC;
_bus_instance.config(cfg);
_panel_instance.setBus(&_bus_instance);
}
// Display Panel Setup
{
auto cfg = _panel_instance.config();
cfg.pin_cs = TFT_CS;
cfg.panel_width = 320;
cfg.panel_height = 480;
cfg.bus_shared = true;
_panel_instance.config(cfg);
}
// Backlight Setup
{
auto cfg = _light_instance.config();
cfg.pin_bl = TFT_BL;
cfg.invert = false;
cfg.freq = 1200;
cfg.pwm_channel = 7;
_light_instance.config(cfg);
_panel_instance.setLight(&_light_instance);
}
// Touch Setup (GT911)
{
auto cfg = _touch_instance.config();
cfg.spi_host = HSPI_HOST;
cfg.bus_shared = true;
cfg.pin_int = -1; //TOUCH_INT;
cfg.pin_sclk = TOUCH_SCK;
cfg.pin_mosi = TOUCH_MOSI;
cfg.pin_miso = TOUCH_MISO;
cfg.pin_cs = TOUCH_CS;
// Calibration coordinates for 320x480 panel (adjust if needed)
cfg.x_min = 200;
cfg.x_max = 3800;
cfg.y_min = 3900;
cfg.y_max = 300;
_touch_instance.config(cfg);
_panel_instance.setTouch(&_touch_instance);
}
setPanel(&_panel_instance);
}
};
LGFX tft;
// Define a structure to hold button properties
struct Button {
int x; // Top-left X coordinate
int y; // Top-left Y coordinate
int w; // Width
int h; // Height
const char* label;
uint16_t color;
uint16_t pressedColor;
bool isPressed;
};
// Array of Button definitions for a 480x320 landscape screen
Button buttons[] = {
// x, y, w, h, label, color (Light Gray), pressedColor (Black), isPressed
{30, 120, 80, 80, "LEFT", TFT_LIGHTGREY, TFT_BLACK, false},
{370, 120, 80, 80, "RIGHT", TFT_LIGHTGREY, TFT_BLACK, false},
{180, 30, 120, 60, "TOP", TFT_LIGHTGREY, TFT_BLACK, false},
{180, 230, 120, 60, "BOTTOM", TFT_LIGHTGREY, TFT_BLACK, false},
{180, 120, 120, 80, "CLEAR", TFT_LIGHTGREY, TFT_BLACK, false} // Label changed to CLEAR
};
const int NUM_BUTTONS = sizeof(buttons) / sizeof(buttons[0]);
void drawButton(const Button& btn, bool pressed);
bool isTouchInside(int touchX, int touchY, const Button& btn);
void drawButton(const Button& btn, bool pressed) {
uint16_t fillColor = pressed ? btn.pressedColor : btn.color;
uint16_t borderColor = TFT_WHITE;
uint16_t textColor = TFT_WHITE;
// Draw rounded rectangle button
tft.fillRoundRect(btn.x, btn.y, btn.w, btn.h, 8, fillColor);
tft.drawRoundRect(btn.x, btn.y, btn.w, btn.h, 8, borderColor);
// Draw label
tft.setFont(&fonts::Font2);
tft.setTextColor(textColor, fillColor);
tft.setTextDatum(middle_center);
int centerX = btn.x + (btn.w / 2);
int centerY = btn.y + (btn.h / 2);
tft.drawString(btn.label, centerX, centerY);
}
bool isTouchInside(int touchX, int touchY, const Button& btn) {
return (touchX >= btn.x && touchX <= (btn.x + btn.w) &&
touchY >= btn.y && touchY <= (btn.y + btn.h));
}
void repaint(){
tft.fillScreen(TFT_BLACK);
tft.setFont(&fonts::Font4);
tft.setTextColor(TFT_CYAN, TFT_BLACK);
tft.setTextDatum(top_center);
tft.drawString("GCYD35 Touch Test Template", tft.width() / 2, 5);
for (int i = 0; i < NUM_BUTTONS; i++) {
buttons[i].isPressed = false; // Ensure state is reset
drawButton(buttons[i], false);
}
}
void setup() {
Serial.begin(115200);
Serial.println("\n--- GCYD35 Start ---");
// 1. Initialize LGFX
tft.begin();
tft.setRotation(1); // Set to Landscape (480x320)
tft.setBrightness(255);
tft.fillScreen(TFT_BLACK);
repaint();
}
// --- MAIN LOOP ---
void loop() {
uint16_t x, y;
static uint16_t i = 0;
// Check for touch input
bool touched = tft.getTouchRaw(&x, &y); //Raw touch readout is useful to adjust the config
bool buttonWasHit = false;
if(touched) {
Serial.printf("touched!! x=%d y=%d\n", x, y);
}
touched = tft.getTouch(&x, &y);
// Iterate through all buttons
for (int i = 0; i < NUM_BUTTONS; i++) {
bool inside = isTouchInside(x, y, buttons[i]);
// Logic:
// If touched AND inside AND button was NOT already pressed -> change state to pressed
if (touched && inside && !buttons[i].isPressed) {
buttons[i].isPressed = true;
drawButton(buttons[i], true);
Serial.printf("Button '%s' Pressed at (%d, %d)\n", buttons[i].label, x, y);
// --- CLEAR ACTION LOGIC ---
if (strcmp(buttons[i].label, "CLEAR") == 0) {
repaint();
}
// --- END CLEAR ACTION LOGIC ---
buttonWasHit = true; // Button was hit (either the press or a hold)
}
// If NOT touched OR NOT inside AND button WAS pressed -> change state to released
else if ((!touched || !inside) && buttons[i].isPressed) {
buttons[i].isPressed = false;
drawButton(buttons[i], false);
Serial.printf("Button '%s' Released\n", buttons[i].label);
}
// If touched and inside (even if held), we stop the drawing function
if (touched && inside) {
buttonWasHit = true;
}
}
if (touched && !buttonWasHit) {
// Draw a small X at the touched pixel
tft.drawLine(x-3, y-3, x+3, y+3, TFT_RED);
tft.drawLine(x-3, y+3, x+3, y-3, TFT_RED);
}
delay(20);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment