I want to create a line_select state consisting of 10 boxes, namely 1A, 1B, 2A, 2B, 3A, 3B, 4A, 4B, 5A, and 5B, the coding was successfully executed and displayed the display as I wanted, but why does it mirror when clicked?
So when I click 1A, it'll be pressed on 2B. If I press 1B, it'll be pressed on 2A. And so on.
I've tried various ways to fix it, but it still doesn't work. Can anyone help?
// =====================================================================
// === KODE FINAL V10.19 - SOLUSI GEOMETRI MATEMATIS & KOREKSI URUTAN DISPLAY ===
// =====================================================================
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
// --- Definisi Warna ---
#define BLACK 0x0000
#define BLUE 0x001F
#define GREEN 0x07E0
#define WHITE 0xFFFF
#define RED 0xF800
#define YELLOW 0xFFE0
// --- 1. DEFINISI PIN TOUCH ---
#define TOUCH_CS 4
#define TOUCH_IRQ 5
TFT_eSPI tft = TFT_eSPI();
XPT2046_Touchscreen ts(TOUCH_CS);
// --- 2. DEFINISI STATE MACHINE & DATA ---
enum State {
STATE_LINE_SELECT,
STATE_PART_SELECT
};
State currentState = STATE_LINE_SELECT;
// Array Line Names (Index 0-3 adalah Baris 1/Atas, Index 8-9 adalah Baris 3/Bawah)
const char* lineNames[] = {
"1A", "1B", "2A", "2B", // Index 0-3 (Baris 1/ATAS)
"3A", "3B", "4A", "4B", // Index 4-7 (Baris 2/TENGAH)
"5A", "5B" // Index 8-9 (Baris 3/BAWAH)
};
const int TOTAL_LINES = 10;
// =======================================================
// === NILAI KALIBRASI RAW (Dipertahankan dari V10.18) ===
// =======================================================
// Nilai ini harus diverifikasi. Jika sentuhan melenceng, tukar/balik nilainya.
#define TS_LEFT 3946 // RAW X MIN
#define TS_RT 190 // RAW X MAX
#define TS_TOP 219 // RAW Y MIN
#define TS_BOT 3935 // RAW Y MAX
#define X_CAL 480 // LEBAR PIKSEL LAYAR (Rotasi 1)
#define Y_CAL 320 // TINGGI PIKSEL LAYAR (Rotasi 1)
// Variabel Kontrol
bool touch_initialized = false;
unsigned long last_touch_time = 0;
const unsigned long DEBOUNCE_DELAY = 300;
#define TOUCH_PRESSURE_THRESHOLD 500
// --- Konstanta Geometri Tombol ---
const int BTN_W = 113;
const int BTN_H = 80;
const int GAP_H = 5;
const int GAP_V = 15;
const int START_X = 6;
const int START_Y = 45;
// ===============================================
// === FUNGSI UTAMA DISPLAY & STATE MACHINE ===
// ===============================================
void drawButton(int x, int y, int w, int h, const char* label, int textSize, uint16_t bgColor, uint16_t fgColor) {
tft.fillRect(x, y, w, h, bgColor);
tft.drawRect(x, y, w, h, fgColor);
tft.setTextColor(fgColor, bgColor);
tft.setTextSize(textSize);
uint16_t text_w = tft.textWidth(label);
uint16_t text_h = 8 * textSize;
int label_x = x + (w - text_w) / 2;
int label_y = y + (h - text_h) / 2;
tft.setCursor(label_x, label_y);
tft.print(label);
}
/**
* @brief Menggambar menu pemilihan baris dengan urutan yang BENAR (1A di Atas).
*/
void drawLineSelectMenu() {
tft.fillScreen(BLACK);
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(3);
uint16_t title_width = tft.textWidth("SELECT LINE");
tft.setCursor(tft.width() / 2 - title_width / 2, 10);
tft.print("PILIH LINE");
// i: Baris Display (0 = Atas, 2 = Bawah)
for(int i = 0; i < 3; i++) {
// j: Kolom Display (0 = Kiri, 3 = Kanan)
for(int j = 0; j < 4; j++) {
// KOREKSI URUTAN DISPLAY:
// i=0 (Baris Atas) -> line_idx 0-3 (1A, 1B, 2A, 2B)
// i=1 (Baris Tengah) -> line_idx 4-7 (3A, 3B, 4A, 4B)
// i=2 (Baris Bawah) -> line_idx 8-11 (5A, 5B, [kosong], [kosong])
int line_idx = i * 4 + j;
if (line_idx < TOTAL_LINES) {
int x = START_X + j * (BTN_W + GAP_H);
int y = START_Y + i * (BTN_H + GAP_V);
drawButton(x, y, BTN_W, BTN_H, lineNames[line_idx], 2, BLUE, WHITE);
}
}
}
}
void drawPartSelectMenu(const char* selectedLine) {
tft.fillScreen(BLACK);
tft.setTextColor(GREEN, BLACK);
tft.setTextSize(3);
uint16_t title_width = tft.textWidth("MENU PART SELECT");
tft.setCursor(tft.width() / 2 - title_width / 2, 100);
tft.print("MENU PART SELECT");
tft.setTextColor(WHITE, BLACK);
tft.setTextSize(5);
tft.setCursor(tft.width() / 2 - tft.textWidth(selectedLine) / 2, 150);
tft.print(selectedLine);
// Tombol Back
drawButton(190, 250, 100, 50, "KEMBALI", 2, RED, WHITE);
}
void changeState(State newState, const char* selectedLine = "") {
currentState = newState;
tft.fillScreen(BLACK);
switch(currentState) {
case STATE_LINE_SELECT:
drawLineSelectMenu();
Serial.println("State: LINE_SELECT");
break;
case STATE_PART_SELECT:
drawPartSelectMenu(selectedLine);
Serial.print("State: PART_SELECT, Line: ");
Serial.println(selectedLine);
break;
}
}
// ===============================================
// === FUNGSI HANDLE TOUCH - KOREKSI AXIS & GEOMETRI V10.19 ===
// ===============================================
void handleTouch() {
if (millis() - last_touch_time < DEBOUNCE_DELAY) {
return;
}
if (ts.touched()) {
last_touch_time = millis();
TS_Point p = ts.getPoint();
if (p.z < TOUCH_PRESSURE_THRESHOLD) {
return;
}
// --- PEMETAAN PIKSEL (HIPOTESIS KOREKSI AXIS V10.19) ---
int px = map(p.x, TS_LEFT, TS_RT, X_CAL, 0); // X Display (0-480) <- Raw X, Inverted
int py = map(p.y, TS_TOP, TS_BOT, Y_CAL, 0); // Y Display (0-320) <- Raw Y, Inverted
// Batasan: memastikan koordinat berada di dalam rentang layar
px = max(0, min(px, X_CAL - 1));
py = max(0, min(py, Y_CAL - 1));
// --- DEBUG VISUAL (Titik Kuning) ---
tft.fillCircle(px, py, 5, YELLOW);
Serial.print("Touch FINAL Pixels: (");
Serial.print(px);
Serial.print(", ");
Serial.print(py);
Serial.println(")");
// --- Logika Deteksi Tombol Berbasis Perhitungan Geometri Langsung ---
if (currentState == STATE_LINE_SELECT) {
int i = -1; // Baris Display (0=Atas, 2=Bawah)
int j = -1; // Kolom Display (0=Kiri, 3=Kanan)
// 1. Hitung Kolom j (0 hingga 3)
if (px >= START_X && px < (START_X + 4*BTN_W + 3*GAP_H)) {
int block_x = px - START_X;
j = block_x / (BTN_W + GAP_H);
// Pengecekan Gap
int button_start_x = START_X + j * (BTN_W + GAP_H);
if (px > (button_start_x + BTN_W) && j < 4) {
j = -1; // Berada di GAP
}
}
// 2. Hitung Baris i (0 hingga 2)
if (py >= START_Y && py < (START_Y + 3*BTN_H + 2*GAP_V)) {
int block_y = py - START_Y;
i = block_y / (BTN_H + GAP_V);
// Pengecekan Gap
int button_start_y = START_Y + i * (BTN_H + GAP_V);
if (py > (button_start_y + BTN_H) && i < 3) {
i = -1; // Berada di GAP
}
}
// 3. Jika i dan j valid, hitung line_idx
if (i != -1 && j != -1) {
// KOREKSI INDEX V10.19: Baris i (0=Atas) langsung memetakan ke line_idx 0-3.
int line_idx = i * 4 + j;
if (line_idx < TOTAL_LINES) {
Serial.print("SUCCESS! Baris:");
Serial.print(i);
Serial.print(", Kolom:");
Serial.print(j);
Serial.print(", Line Index:");
Serial.print(line_idx);
Serial.print(" -> ");
Serial.println(lineNames[line_idx]);
// Menonaktifkan Titik Kuning di Area Tombol yang Berhasil Ditekan
tft.fillCircle(px, py, 5, BLACK);
changeState(STATE_PART_SELECT, lineNames[line_idx]);
return;
}
} else {
// Menonaktifkan Titik Kuning di Area yang Tidak Ada Tombol (untuk feedback visual)
tft.fillCircle(px, py, 5, BLACK);
}
}
// --- Logika Tombol Kembali (BACK) ---
else if (currentState == STATE_PART_SELECT) {
// Area Tombol KEMBALI: x=190, y=250, w=100, h=50
if (px >= 190 && px <= 290 && py >= 250 && py <= 300) {
Serial.println("Tombol KEMBALI ditekan");
changeState(STATE_LINE_SELECT);
return;
}
tft.fillCircle(px, py, 5, BLACK); // Hapus titik kuning setelah cek
}
}
}
// ===============================================
// === SETUP DAN LOOP ===
// ===============================================
void setup() {
Serial.begin(115200);
Serial.println("TFT/Touch V10.19 Started - FINAL GEOMETRY & ORDER FIX");
tft.init();
tft.setRotation(1); // Mode Landscape (480x320)
tft.fillScreen(BLACK);
changeState(STATE_LINE_SELECT);
}
void loop() {
if (!touch_initialized) {
ts.begin();
touch_initialized = true;
}
handleTouch();
}

