/*
* ============================================================
* ESP32-C6 × 5" TFT (ILI9488 / ST7796) + XPT2046 Touch
* GRAPHIC CAPABILITY TEST — WITH TOUCH INPUT SIMULATION
* ============================================================
* Author : Arvind Patil
* Project : AI Centre Nandurbar / Ysz4 / HansaScript
* Board : ESP32-C6 (RISC-V 160 MHz)
* Display : 5.0" TFT 800×480 ILI9488 / ST7796 SPI
* Touch : XPT2046 / NS2009 Resistive SPI
* IDE : Arduino IDE 2.x
* ============================================================
*
* LIBRARIES REQUIRED (Install via Library Manager):
* 1. TFT_eSPI by Bodmer
* 2. XPT2046_Touchscreen by Paul Stoffregen
* OR TFT_Touch (built-in with some displays)
*
* USER_SETUP_LOADED — pin config embedded here, no lib edit needed.
* ============================================================
*
* PIN WIRING (ESP32-C6 → 5" TFT + XPT2046 Touch)
* ┌─────────────────┬──────────────┬──────────────────────┐
* │ Signal │ ESP32-C6 GPIO│ Notes │
* ├─────────────────┼──────────────┼──────────────────────┤
* │ TFT MOSI │ GPIO 7 │ Shared SPI MOSI │
* │ TFT SCLK │ GPIO 6 │ Shared SPI CLK │
* │ TFT CS │ GPIO 10 │ TFT chip select │
* │ TFT DC/RS │ GPIO 11 │ Data/Command │
* │ TFT RST │ GPIO 22 │ Reset │
* │ TFT BL (PWM) │ GPIO 23 │ Backlight │
* │ TFT VCC │ 3.3V │ │
* │ TFT GND │ GND │ │
* ├─────────────────┼──────────────┼──────────────────────┤
* │ TOUCH MISO │ GPIO 2 │ XPT2046 MISO │
* │ TOUCH MOSI │ GPIO 7 │ Shared with TFT │
* │ TOUCH SCLK │ GPIO 6 │ Shared with TFT │
* │ TOUCH CS │ GPIO 9 │ Touch chip select │
* │ TOUCH IRQ │ GPIO 8 │ Interrupt (optional) │
* └─────────────────┴──────────────┴──────────────────────┘
*
* TOUCH TEST MODES (tap buttons on screen):
* [1] DRAW PAD — finger painting on TFT
* [2] BUTTON TEST — tap colored buttons, get response
* [3] DRAG DEMO — drag a block with finger
* [4] CALIBRATE — raw XY calibration crosshair
* [5] RIPPLE FX — tap anywhere → water ripple
* [6] KEYBOARD SIM — on-screen number pad
* ============================================================
*/
// ── Force TFT_eSPI user setup (no library file edit needed) ──
#define USER_SETUP_LOADED
#define ILI9488_DRIVER // or ST7796_DRIVER for ST7796
// TFT SPI pins — ESP32-C6
#define TFT_MOSI 7
#define TFT_SCLK 6
#define TFT_CS 10
#define TFT_DC 11
#define TFT_RST 22
#define TFT_BL 23
// Display size
#define TFT_WIDTH 800
#define TFT_HEIGHT 480
#define SPI_FREQUENCY 40000000 // 40 MHz TFT SPI
#define SPI_TOUCH_FREQUENCY 2500000 // 2.5 MHz Touch SPI
#define SPI_READ_FREQUENCY 20000000
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
#define SMOOTH_FONT
// ── Includes ──
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
// ── Touch pins ──
#define TOUCH_CS 9
#define TOUCH_IRQ 8
// ── Objects ──
TFT_eSPI tft = TFT_eSPI();
XPT2046_Touchscreen touch(TOUCH_CS, TOUCH_IRQ);
// ── Touch calibration values (adjust after running CALIBRATE mode) ──
// Raw ADC min/max from XPT2046 for your specific panel
#define TOUCH_X_MIN 350
#define TOUCH_X_MAX 3750
#define TOUCH_Y_MIN 280
#define TOUCH_Y_MAX 3800
#define TOUCH_SWAP_XY false // set true if axes are swapped
#define TOUCH_FLIP_X false
#define TOUCH_FLIP_Y false
// ── Colors (RGB565) ──
#define C_BLACK 0x0000
#define C_WHITE 0xFFFF
#define C_NAVY 0x000F
#define C_GOLD 0xFEA0
#define C_CYAN 0x07FF
#define C_GREEN 0x07E0
#define C_RED 0xF800
#define C_MAGENTA 0xF81F
#define C_ORANGE 0xFD20
#define C_DARKGREY 0x2104
#define C_PANEL 0x1082
// ── Backlight PWM ──
#define BL_CHANNEL 0
#define BL_FREQ 5000
#define BL_RES 8
// ── State ──
uint8_t currentMode = 0; // 0=menu, 1=draw, 2=buttons, 3=drag, 4=calib, 5=ripple, 6=keypad
uint16_t lastTX = 0, lastTY = 0;
bool touched = false;
uint32_t frameCount = 0;
uint32_t fpsTimer = 0;
float fps_val = 0;
// Drag block
int16_t blockX = 350, blockY = 190, blockW = 100, blockH = 60;
bool dragging = false;
int16_t dragOffX, dragOffY;
// Ripple
struct Ripple { int16_t x, y, r; bool active; uint16_t color; };
#define MAX_RIPPLES 6
Ripple ripples[MAX_RIPPLES];
// Keypad
char keypadBuffer[24] = "";
uint8_t keyLen = 0;
// ── Touch coordinate mapping ──
struct TouchPoint { uint16_t x, y; bool pressed; };
TouchPoint getTouch() {
TouchPoint pt = {0, 0, false};
if (!touch.tirqTouched() && !touch.touched()) return pt;
TS_Point raw = touch.getPoint();
if (raw.z < 400) return pt; // pressure threshold
// Map raw ADC → screen pixels
int16_t tx = map(raw.x, TOUCH_X_MIN, TOUCH_X_MAX, 0, TFT_WIDTH);
int16_t ty = map(raw.y, TOUCH_Y_MIN, TOUCH_Y_MAX, 0, TFT_HEIGHT);
if (TOUCH_SWAP_XY) { int16_t tmp=tx; tx=ty; ty=tmp; }
if (TOUCH_FLIP_X) tx = TFT_WIDTH - tx;
if (TOUCH_FLIP_Y) ty = TFT_HEIGHT - ty;
tx = constrain(tx, 0, TFT_WIDTH - 1);
ty = constrain(ty, 0, TFT_HEIGHT - 1);
pt.x = tx; pt.y = ty; pt.pressed = true;
return pt;
}
// ══════════════════════════════════════════════
// SETUP
// ══════════════════════════════════════════════
void setup() {
Serial.begin(115200);
delay(300);
Serial.println(F("\n=== ESP32-C6 × 5\" TFT Touch Test ==="));
Serial.println(F("Arvind Patil / AI Centre Nandurbar / Ysz4"));
// Backlight
ledcSetup(BL_CHANNEL, BL_FREQ, BL_RES);
ledcAttachPin(TFT_BL, BL_CHANNEL);
ledcWrite(BL_CHANNEL, 200);
// TFT init
tft.init();
tft.setRotation(1); // Landscape 800×480
tft.fillScreen(C_BLACK);
Serial.println(F("TFT OK — 800×480 ILI9488"));
// Touch init
SPI.begin(TFT_SCLK, TOUCH_CS, TFT_MOSI); // shared SPI bus
touch.begin();
touch.setRotation(1);
Serial.println(F("XPT2046 Touch OK"));
// Init ripples
for (uint8_t i = 0; i < MAX_RIPPLES; i++) ripples[i].active = false;
// Boot splash
bootSplash();
delay(2000);
// Show main menu
drawMenu();
fpsTimer = millis();
}
// ══════════════════════════════════════════════
// LOOP
// ══════════════════════════════════════════════
void loop() {
TouchPoint tp = getTouch();
// FPS counter
frameCount++;
if (millis() - fpsTimer >= 1000) {
fps_val = frameCount;
frameCount = 0;
fpsTimer = millis();
updateFpsBar();
}
if (currentMode == 0) {
if (tp.pressed) handleMenuTouch(tp.x, tp.y);
}
else if (currentMode == 1) { modeDraw(tp); }
else if (currentMode == 2) { modeButtons(tp); }
else if (currentMode == 3) { modeDrag(tp); }
else if (currentMode == 4) { modeCalibrate(tp); }
else if (currentMode == 5) { modeRipple(tp); }
else if (currentMode == 6) { modeKeypad(tp); }
delay(5);
}
// ══════════════════════════════════════════════
// BOOT SPLASH
// ══════════════════════════════════════════════
void bootSplash() {
tft.fillScreen(C_BLACK);
tft.setTextDatum(MC_DATUM);
// Gold title
tft.setFreeFont(&FreeSansBold18pt7b);
tft.setTextColor(C_GOLD, C_BLACK);
tft.drawString("ESP32-C6 x 5\" TFT", 400, 130);
tft.setFreeFont(&FreeSans12pt7b);
tft.setTextColor(C_CYAN, C_BLACK);
tft.drawString("TOUCH CAPABILITY TEST", 400, 185);
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextColor(C_GREEN, C_BLACK);
tft.drawString("ILI9488 | 800x480 | XPT2046 Touch", 400, 240);
tft.setTextColor(C_DARKGREY, C_BLACK);
tft.drawString("Arvind Patil / AI Centre Nandurbar / Ysz4", 400, 290);
// Progress bar
tft.drawRect(200, 330, 400, 18, C_GOLD);
for (int i = 0; i <= 400; i += 8) {
tft.fillRect(201, 331, i, 16, C_GOLD);
delay(6);
}
tft.setTextColor(C_WHITE, C_BLACK);
tft.setFreeFont(&FreeSans9pt7b);
tft.drawString("Touch Initialized — Loading Menu...", 400, 380);
Serial.println(F("Boot splash done."));
}
// ══════════════════════════════════════════════
// MAIN MENU
// ══════════════════════════════════════════════
struct MenuBtn {
uint16_t x, y, w, h;
const char* label;
uint16_t color;
uint8_t mode;
};
const MenuBtn menuBtns[] = {
{ 30, 80, 230, 70, "[1] DRAW PAD", 0x07E0, 1 },
{ 285, 80, 230, 70, "[2] BUTTON TEST", 0xFD20, 2 },
{ 540, 80, 230, 70, "[3] DRAG DEMO", 0x07FF, 3 },
{ 30,170, 230, 70, "[4] CALIBRATE", 0xF81F, 4 },
{ 285,170, 230, 70, "[5] RIPPLE FX", 0xFEA0, 5 },
{ 540,170, 230, 70, "[6] KEYPAD SIM", 0xFD20, 6 },
};
#define MENU_BTN_COUNT 6
void drawMenu() {
tft.fillScreen(C_BLACK);
// Header
tft.fillRect(0, 0, 800, 56, C_PANEL);
tft.setFreeFont(&FreeSans12pt7b);
tft.setTextColor(C_GOLD, C_PANEL);
tft.setTextDatum(ML_DATUM);
tft.drawString("ESP32-C6 x 5\" TFT Touch Test", 16, 28);
tft.setTextColor(C_DARKGREY, C_PANEL);
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextDatum(MR_DATUM);
tft.drawString("Arvind Patil / Ysz4", 784, 28);
// Menu buttons
for (uint8_t i = 0; i < MENU_BTN_COUNT; i++) {
const MenuBtn& b = menuBtns[i];
tft.fillRoundRect(b.x, b.y, b.w, b.h, 8, b.color);
tft.drawRoundRect(b.x, b.y, b.w, b.h, 8, C_WHITE);
tft.setTextColor(C_BLACK, b.color);
tft.setTextDatum(MC_DATUM);
tft.setFreeFont(&FreeSans9pt7b);
tft.drawString(b.label, b.x + b.w/2, b.y + b.h/2);
}
// Hint
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextColor(C_DARKGREY, C_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Tap a test to begin. Tap top-left corner to return to menu.", 400, 280);
drawFpsPanel();
currentMode = 0;
Serial.println(F("Menu drawn."));
}
void handleMenuTouch(uint16_t tx, uint16_t ty) {
for (uint8_t i = 0; i < MENU_BTN_COUNT; i++) {
const MenuBtn& b = menuBtns[i];
if (tx >= b.x && tx <= b.x+b.w && ty >= b.y && ty <= b.y+b.h) {
// Flash feedback
tft.fillRoundRect(b.x+2, b.y+2, b.w-4, b.h-4, 6, C_WHITE);
delay(80);
currentMode = b.mode;
Serial.printf("Mode: %d\n", currentMode);
launchMode(currentMode);
return;
}
}
}
void launchMode(uint8_t m) {
if (m == 1) setupDraw();
else if (m == 2) setupButtons();
else if (m == 3) setupDrag();
else if (m == 4) setupCalibrate();
else if (m == 5) setupRipple();
else if (m == 6) setupKeypad();
}
// ══════════════════════════════════════════════
// FPS / Status bar
// ══════════════════════════════════════════════
void drawFpsPanel() {
tft.fillRect(0, 430, 800, 50, C_PANEL);
tft.drawRect(0, 430, 800, 50, C_DARKGREY);
tft.setTextColor(C_GOLD, C_PANEL);
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextDatum(ML_DATUM);
tft.drawString("FPS:", 10, 455);
tft.setTextColor(C_GREEN, C_PANEL);
tft.drawString("TOUCH: XPT2046 SPI | TFT: ILI9488 SPI@40MHz | MCU: ESP32-C6 RISC-V", 200, 455);
}
void updateFpsBar() {
tft.fillRect(48, 436, 120, 28, C_PANEL);
tft.setTextColor(C_CYAN, C_PANEL);
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextDatum(ML_DATUM);
char buf[20]; sprintf(buf, "%.0f", fps_val);
tft.drawString(buf, 52, 455);
}
// ── Back button (top-left corner tap) ──
bool checkBack(uint16_t tx, uint16_t ty) {
if (tx < 60 && ty < 40) {
Serial.println(F("Back to menu."));
drawMenu();
return true;
}
return false;
}
void drawBackHint() {
tft.setTextColor(C_DARKGREY, C_BLACK);
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextDatum(ML_DATUM);
tft.drawString("[<BACK]", 4, 18);
}
// ══════════════════════════════════════════════
// MODE 1: DRAW PAD
// ══════════════════════════════════════════════
uint16_t drawColor = C_GREEN;
uint8_t penSize = 3;
const uint16_t palette[] = {
C_WHITE, C_RED, C_GREEN, C_CYAN, C_MAGENTA,
C_GOLD, C_ORANGE, 0xF81F, 0x07FF, 0xFFE0
};
#define PAL_COUNT 10
void setupDraw() {
tft.fillScreen(C_BLACK);
// Color palette bar
for (uint8_t i = 0; i < PAL_COUNT; i++) {
tft.fillRect(10 + i*38, 380, 34, 34, palette[i]);
tft.drawRect(10 + i*38, 380, 34, 34, C_WHITE);
}
// Pen size buttons
tft.fillRect(430, 380, 50, 34, C_PANEL);
tft.drawRect(430, 380, 50, 34, C_WHITE);
tft.setTextColor(C_WHITE, C_PANEL); tft.setTextDatum(MC_DATUM);
tft.setFreeFont(&FreeSans9pt7b);
tft.drawString("S", 455, 397);
tft.fillRect(490, 380, 50, 34, C_PANEL);
tft.drawRect(490, 380, 50, 34, C_WHITE);
tft.drawString("M", 515, 397);
tft.fillRect(550, 380, 50, 34, C_PANEL);
tft.drawRect(550, 380, 50, 34, C_WHITE);
tft.drawString("L", 575, 397);
// Clear button
tft.fillRect(620, 380, 80, 34, C_RED);
tft.setTextColor(C_WHITE, C_RED);
tft.drawString("CLEAR", 660, 397);
// Draw area border
tft.drawRect(0, 30, 800, 340, C_DARKGREY);
drawFpsPanel();
drawBackHint();
tft.setTextColor(C_DARKGREY, C_BLACK);
tft.setTextDatum(MC_DATUM);
tft.drawString("Draw with your finger!", 400, 200);
Serial.println(F("Mode 1: Draw Pad"));
}
void modeDraw(TouchPoint tp) {
if (!tp.pressed) { lastTX = 0; lastTY = 0; return; }
if (checkBack(tp.x, tp.y)) return;
// Palette select
if (tp.y >= 380 && tp.y <= 414) {
for (uint8_t i = 0; i < PAL_COUNT; i++) {
if (tp.x >= 10+i*38 && tp.x <= 44+i*38) { drawColor = palette[i]; return; }
}
if (tp.x >= 430 && tp.x <= 480) { penSize = 2; }
if (tp.x >= 490 && tp.x <= 540) { penSize = 5; }
if (tp.x >= 550 && tp.x <= 600) { penSize = 10; }
if (tp.x >= 620 && tp.x <= 700) {
tft.fillRect(1, 31, 798, 338, C_BLACK);
tft.drawString("Draw with your finger!", 400, 200);
}
return;
}
// Draw in canvas area
if (tp.y > 30 && tp.y < 370) {
if (lastTX && lastTY) {
tft.drawLine(lastTX, lastTY, tp.x, tp.y, drawColor);
if (penSize > 2) {
tft.fillCircle(tp.x, tp.y, penSize/2, drawColor);
}
} else {
tft.fillCircle(tp.x, tp.y, penSize/2, drawColor);
}
lastTX = tp.x; lastTY = tp.y;
}
}
// ══════════════════════════════════════════════
// MODE 2: BUTTON TEST
// ══════════════════════════════════════════════
struct TftButton {
uint16_t x, y, w, h;
const char* label;
uint16_t color;
uint8_t id;
};
TftButton tftBtns[] = {
{ 60, 80, 160, 60, "RED", C_RED, 1 },
{ 240, 80, 160, 60, "GREEN", C_GREEN, 2 },
{ 420, 80, 160, 60, "BLUE", 0x001F, 3 },
{ 600, 80, 160, 60, "YELLOW", 0xFFE0, 4 },
{ 60,170, 160, 60, "CYAN", C_CYAN, 5 },
{ 240,170, 160, 60, "MAGENTA", C_MAGENTA, 6 },
{ 420,170, 160, 60, "ORANGE", C_ORANGE, 7 },
{ 600,170, 160, 60, "GOLD", C_GOLD, 8 },
{ 180,270, 200, 60, "CLEAR", C_PANEL, 9 },
{ 420,270, 200, 60, "RANDOM", C_DARKGREY,10},
};
#define TFT_BTN_COUNT 10
uint8_t pressCount[TFT_BTN_COUNT+1] = {};
uint16_t lastBtnColor = C_BLACK;
void setupButtons() {
tft.fillScreen(C_BLACK);
tft.fillRect(0, 0, 800, 50, C_PANEL);
tft.setTextColor(C_GOLD, C_PANEL); tft.setFreeFont(&FreeSans12pt7b);
tft.setTextDatum(MC_DATUM);
tft.drawString("BUTTON TEST — tap buttons", 400, 28);
for (uint8_t i = 0; i < TFT_BTN_COUNT; i++) {
TftButton& b = tftBtns[i];
tft.fillRoundRect(b.x, b.y, b.w, b.h, 10, b.color);
tft.drawRoundRect(b.x, b.y, b.w, b.h, 10, C_WHITE);
tft.setTextColor(C_BLACK, b.color);
tft.setFreeFont(&FreeSans9pt7b);
tft.drawString(b.label, b.x+b.w/2, b.y+b.h/2);
}
tft.drawRect(10, 350, 780, 60, C_DARKGREY);
tft.setTextColor(C_DARKGREY, C_BLACK);
tft.drawString("Touch response shows here", 400, 380);
drawFpsPanel(); drawBackHint();
Serial.println(F("Mode 2: Button Test"));
}
void modeButtons(TouchPoint tp) {
if (!tp.pressed) return;
if (checkBack(tp.x, tp.y)) return;
for (uint8_t i = 0; i < TFT_BTN_COUNT; i++) {
TftButton& b = tftBtns[i];
if (tp.x>=b.x && tp.x<=b.x+b.w && tp.y>=b.y && tp.y<=b.y+b.h) {
pressCount[b.id]++;
// Flash
tft.fillRoundRect(b.x+3, b.y+3, b.w-6, b.h-6, 8, C_WHITE);
delay(60);
tft.fillRoundRect(b.x+3, b.y+3, b.w-6, b.h-6, 8, b.color);
// Response panel
if (b.id == 9) {
tft.fillRect(11,351,778,58,C_BLACK);
tft.setTextColor(C_WHITE,C_BLACK);
tft.setFreeFont(&FreeSans9pt7b); tft.setTextDatum(MC_DATUM);
tft.drawString("Screen cleared!", 400, 380);
lastBtnColor = C_BLACK;
} else if (b.id == 10) {
lastBtnColor = tft.color565(random(256),random(256),random(256));
tft.fillRect(11,351,778,58,lastBtnColor);
tft.setTextColor(C_BLACK, lastBtnColor);
tft.setFreeFont(&FreeSans9pt7b); tft.setTextDatum(MC_DATUM);
char buf[40]; sprintf(buf,"Random 0x%04X Count:%d", lastBtnColor, pressCount[10]);
tft.drawString(buf, 400, 380);
} else {
tft.fillRect(11,351,778,58,b.color);
tft.setTextColor(C_BLACK,b.color);
tft.setFreeFont(&FreeSans9pt7b); tft.setTextDatum(MC_DATUM);
char buf[50]; sprintf(buf,"%s pressed! Count: %d | X:%d Y:%d", b.label, pressCount[b.id], tp.x, tp.y);
tft.drawString(buf, 400, 380);
}
Serial.printf("BTN %s count=%d X=%d Y=%d\n", b.label, pressCount[b.id], tp.x, tp.y);
delay(120);
return;
}
}
}
// ══════════════════════════════════════════════
// MODE 3: DRAG DEMO
// ══════════════════════════════════════════════
void setupDrag() {
blockX=350; blockY=190;
tft.fillScreen(C_BLACK);
tft.fillRect(0,0,800,50,C_PANEL);
tft.setTextColor(C_GOLD,C_PANEL); tft.setFreeFont(&FreeSans12pt7b);
tft.setTextDatum(MC_DATUM);
tft.drawString("DRAG DEMO — drag the gold block", 400, 28);
// Grid
for (int x=0;x<800;x+=40) tft.drawFastVLine(x,50,360,0x1082);
for (int y=50;y<410;y+=40) tft.drawFastHLine(0,y,800,0x1082);
drawBlock(blockX, blockY, C_GOLD);
drawFpsPanel(); drawBackHint();
dragging = false;
Serial.println(F("Mode 3: Drag Demo"));
}
void drawBlock(int16_t x, int16_t y, uint16_t c) {
tft.fillRoundRect(x, y, blockW, blockH, 8, c);
tft.drawRoundRect(x, y, blockW, blockH, 8, C_WHITE);
tft.setTextColor(C_BLACK, c);
tft.setTextDatum(MC_DATUM);
tft.setFreeFont(&FreeSans9pt7b);
char buf[24]; sprintf(buf,"(%d,%d)", x, y);
tft.drawString(buf, x+blockW/2, y+blockH/2);
}
void modeDrag(TouchPoint tp) {
if (checkBack(tp.x, tp.y)) return;
if (tp.pressed) {
if (!dragging) {
// Check if touching block
if (tp.x>=blockX && tp.x<=blockX+blockW && tp.y>=blockY && tp.y<=blockY+blockH) {
dragging = true;
dragOffX = tp.x - blockX;
dragOffY = tp.y - blockY;
}
}
if (dragging) {
// Erase old block (redraw grid patch)
tft.fillRect(blockX-2, blockY-2, blockW+4, blockH+4, C_BLACK);
for (int x=(blockX/40)*40; x<blockX+blockW+40; x+=40)
tft.drawFastVLine(x, max(50,(int)blockY-5), blockH+10, 0x1082);
for (int y=(blockY/40)*40; y<blockY+blockH+40; y+=40)
tft.drawFastHLine(max(0,(int)blockX-5), y, blockW+10, 0x1082);
blockX = constrain(tp.x - dragOffX, 0, TFT_WIDTH - blockW);
blockY = constrain(tp.y - dragOffY, 50, 400 - blockH);
drawBlock(blockX, blockY, C_GOLD);
Serial.printf("Drag X=%d Y=%d\n", blockX, blockY);
}
} else {
dragging = false;
}
}
// ══════════════════════════════════════════════
// MODE 4: CALIBRATE
// ══════════════════════════════════════════════
struct CalPoint { uint16_t sx, sy; int32_t rx, ry; bool done; };
CalPoint calPts[4] = {
{40, 40, 0, 0, false},
{760, 40, 0, 0, false},
{760,440, 0, 0, false},
{40, 440, 0, 0, false},
};
uint8_t calIdx = 0;
void setupCalibrate() {
calIdx = 0;
for (uint8_t i=0;i<4;i++) calPts[i].done=false;
tft.fillScreen(C_BLACK);
tft.setTextColor(C_CYAN,C_BLACK); tft.setFreeFont(&FreeSans12pt7b);
tft.setTextDatum(MC_DATUM);
tft.drawString("TOUCH CALIBRATION", 400, 30);
tft.setFreeFont(&FreeSans9pt7b);
tft.setTextColor(C_DARKGREY,C_BLACK);
tft.drawString("Tap the crosshair at each corner", 400, 55);
drawCalTarget(calIdx);
drawFpsPanel(); drawBackHint();
Serial.println(F("Mode 4: Calibrate"));
}
void drawCalTarget(uint8_t i) {
uint16_t cx = calPts[i].sx, cy = calPts[i].sy;
tft.drawLine(cx-20,cy,cx+20,cy,C_GOLD);
tft.drawLine(cx,cy-20,cx,cy+20,C_GOLD);
tft.drawCircle(cx,cy,12,C_GOLD);
char buf[24]; sprintf(buf,"Point %d/4 →",i+1);
tft.fillRect(250,240,300,30,C_BLACK);
tft.setTextColor(C_WHITE,C_BLACK); tft.setTextDatum(MC_DATUM);
tft.setFreeFont(&FreeSans9pt7b);
tft.drawString(buf,400,255);
}
void modeCalibrate(TouchPoint tp) {
if (checkBack(tp.x, tp.y)) return;
if (!tp.pressed) return;
TS_Point raw = touch.getPoint();
calPts[calIdx].rx = raw.x;
calPts[calIdx].ry = raw.y;
calPts[calIdx].done = true;
tft.fillCircle(calPts[calIdx].sx, calPts[calIdx].sy, 5, C_GREEN);
Serial.printf("Cal[%d] raw X=%d Y=%d screen X=%d Y=%d\n",
calIdx, raw.x, raw.y, calPts[calIdx].sx, calPts[calIdx].sy);
calIdx++;
delay(300);
if (calIdx >= 4) {
// Print calibration values to Serial
tft.fillRect(80,150,640,180,C_PANEL);
tft.setTextColor(C_GREEN,C_PANEL); tft.setFreeFont(&FreeSans9pt7b);
tft.setTextDatum(MC_DATUM);
tft.drawString("CALIBRATION DONE! Copy to sketch:", 400, 175);
char buf[80];
int32_t xMin = min(calPts[0].rx, calPts[2].rx);
int32_t xMax = max(calPts[0].rx, calPts[2].rx);
int32_t yMin = min(calPts[0].ry, calPts[2].ry);
int32_t yMax = max(calPts[0].ry, calPts[2].ry);
sprintf(buf,"#define TOUCH_X_MIN %ld", xMin); tft.setTextColor(C_GOLD,C_PANEL); tft.drawString(buf,400,210);
sprintf(buf,"#define TOUCH_X_MAX %ld", xMax); tft.drawString(buf,400,235);
sprintf(buf,"#define TOUCH_Y_MIN %ld", yMin); tft.drawString(buf,400,260);
sprintf(buf,"#define TOUCH_Y_MAX %ld", yMax); tft.drawString(buf,400,285);
Serial.printf("\n-- CALIBRATION VALUES --\n");
Serial.printf("#define TOUCH_X_MIN %ld\n#define TOUCH_X_MAX %ld\n", xMin, xMax);
Serial.printf("#define TOUCH_Y_MIN %ld\n#define TOUCH_Y_MAX %ld\n", yMin, yMax);
Serial.println(F("Copy the above values into your sketch.\n"));
tft.setTextColor(C_WHITE,C_PANEL);
tft.drawString("Values printed to Serial Monitor", 400, 310);
calIdx = 0;
for (uint8_t i=0;i<4;i++) calPts[i].done=false;
} else {
drawCalTarget(calIdx);
}
}
// ══════════════════════════════════════════════
// MODE 5: RIPPLE FX
// ══════════════════════════════════════════════
void setupRipple() {
tft.fillScreen(C_BLACK);
tft.fillRect(0,0,800,50,C_PANEL);
tft.setTextColor(C_GOLD,C_PANEL); tft.setFreeFont(&FreeSans12pt7b);
tft.setTextDatum(MC_DATUM);
tft.drawString("RIPPLE FX — tap anywhere", 400, 28);
for (uint8_t i=0;i<MAX_RIPPLES;i++) ripples[i].active=false;
drawFpsPanel(); drawBackHint();
Serial.println(F("Mode 5: Ripple FX"));
}
void modeRipple(TouchPoint tp) {
if (checkBack(tp.x, tp.y)) return;
static bool wasPressed = false;
if (tp.pressed && !wasPressed) {
// Find free ripple slot
for (uint8_t i=0;i<MAX_RIPPLES;i++) {
if (!ripples[i].active) {
ripples[i] = { (int16_t)tp.x, (int16_t)tp.y, 0, true,
(uint16_t)tft.color565(random(100,256),random(100,256),random(100,256)) };
Serial.printf("Ripple @%d,%d\n", tp.x, tp.y);
break;
}
}
}
wasPressed = tp.pressed;
// Animate ripples
tft.fillRect(0,50,800,370,C_BLACK);
for (uint8_t i=0;i<MAX_RIPPLES;i++) {
if (!ripples[i].active) continue;
ripples[i].r += 8;
uint8_t alpha = map(ripples[i].r, 0, 250, 255, 0);
if (ripples[i].r > 250) { ripples[i].active=false; continue; }
// Draw 3 concentric circles per ripple
for (int8_t d=-2;d<=2;d++) {
uint16_t cr = constrain(ripples[i].r + d*10, 1, 260);
if (cr < 260)
tft.drawCircle(ripples[i].x, ripples[i].y, cr, ripples[i].color);
}
}
delay(12);
}
// ══════════════════════════════════════════════
// MODE 6: ON-SCREEN KEYPAD
// ══════════════════════════════════════════════
struct Key { uint16_t x,y,w,h; char label[4]; };
Key keys[] = {
{ 80,120,90,60,"1"}, {180,120,90,60,"2"}, {280,120,90,60,"3"},
{ 80,195,90,60,"4"}, {180,195,90,60,"5"}, {280,195,90,60,"6"},
{ 80,270,90,60,"7"}, {180,270,90,60,"8"}, {280,270,90,60,"9"},
{180,345,90,60,"0"}, { 80,345,90,60,"<"}, {280,345,90,60,"C"},
};
#define KEY_COUNT 12
void drawKeypad() {
for (uint8_t i=0;i<KEY_COUNT;i++){
Key& k=keys[i];
uint16_t c = (k.label[0]=='<')?C_ORANGE:(k.label[0]=='C')?C_RED:C_PANEL;
tft.fillRoundRect(k.x,k.y,k.w,k.h,8,c);
tft.drawRoundRect(k.x,k.y,k.w,k.h,8,C_GOLD);
tft.setTextColor(C_WHITE,c); tft.setTextDatum(MC_DATUM);
tft.setFreeFont(&FreeSans12pt7b);
tft.drawString(k.label, k.x+k.w/2, k.y+k.h/2);
}
}
void drawKeypadBuffer() {
tft.fillRect(80,60,600,44,C_BLACK);
tft.drawRect(80,60,600,44,C_GOLD);
tft.setTextColor(C_GOLD,C_BLACK);
tft.setFreeFont(&FreeSans12pt7b); tft.setTextDatum(ML_DATUM);
tft.drawString(keypadBuffer, 90, 87);
}
void setupKeypad() {
memset(keypadBuffer, 0, sizeof(keypadBuffer));
keyLen = 0;
tft.fillScreen(C_BLACK);
tft.fillRect(0,0,800,50,C_PANEL);
tft.setTextColor(C_GOLD,C_PANEL); tft.setFreeFont(&FreeSans12pt7b);
tft.setTextDatum(MC_DATUM);
tft.drawString("ON-SCREEN KEYPAD", 400, 28);
drawKeypadBuffer();
drawKeypad();
// Side display
tft.setTextColor(C_CYAN,C_BLACK); tft.setFreeFont(&FreeSans9pt7b);
tft.setTextDatum(ML_DATUM);
tft.drawString("Keypad result:", 450, 200);
tft.drawRect(440,215,300,60,C_DARKGREY);
drawFpsPanel(); drawBackHint();
Serial.println(F("Mode 6: Keypad"));
}
void modeKeypad(TouchPoint tp) {
if (!tp.pressed) return;
if (checkBack(tp.x, tp.y)) return;
static bool wasP = false;
if (wasP) { wasP=true; return; }
wasP = true;
for (uint8_t i=0;i<KEY_COUNT;i++){
Key& k=keys[i];
if (tp.x>=k.x && tp.x<=k.x+k.w && tp.y>=k.y && tp.y<=k.y+k.h) {
// Flash
tft.fillRoundRect(k.x+3,k.y+3,k.w-6,k.h-6,6,C_WHITE);
delay(50);
tft.fillRoundRect(k.x+3,k.y+3,k.w-6,k.h-6,6,
(k.label[0]=='<')?C_ORANGE:(k.label[0]=='C')?C_RED:C_PANEL);
tft.setTextColor(C_WHITE,
(k.label[0]=='<')?C_ORANGE:(k.label[0]=='C')?C_RED:C_PANEL);
tft.setTextDatum(MC_DATUM);
tft.setFreeFont(&FreeSans12pt7b);
tft.drawString(k.label,k.x+k.w/2,k.y+k.h/2);
if (k.label[0] == 'C') {
memset(keypadBuffer,0,sizeof(keypadBuffer)); keyLen=0;
} else if (k.label[0] == '<') {
if (keyLen>0) { keypadBuffer[--keyLen]='\0'; }
} else {
if (keyLen < 20) { keypadBuffer[keyLen++]=k.label[0]; }
}
drawKeypadBuffer();
// Side result
tft.fillRect(441,216,298,58,C_BLACK);
tft.setTextColor(C_GREEN,C_BLACK);
tft.setFreeFont(&FreeSans12pt7b); tft.setTextDatum(MC_DATUM);
tft.drawString(keypadBuffer, 590, 245);
Serial.printf("Key: %s Buffer: %s\n", k.label, keypadBuffer);
delay(100);
wasP = false;
return;
}
}
wasP = false;
}
// ══ END ══════════════════════════════════════
/*
* LIBRARY INSTALL (Arduino IDE Library Manager):
* Search: TFT_eSPI → Install (Bodmer)
* Search: XPT2046_Touchscreen → Install (Paul Stoffregen)
*
* BOARD MANAGER:
* Add URL: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
* Install: esp32 by Espressif Systems
* Select: ESP32C6 Dev Module
* CPU Freq: 160 MHz
* Flash: 4MB
*
* SERIAL MONITOR: 115200 baud
*
* TOUCH CALIBRATION:
* Run Mode 4 first → tap 4 corners → copy printed
* #define values to top of this sketch.
*
* Arvind Patil / AI Centre Nandurbar / Ysz4 / HansaScript
*/