Created
December 17, 2025 14:06
-
-
Save polprog/35c74d9ff8ec02ac14f963b7b2ebf140 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ========================================================================= | |
| // 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