#include <Arduino.h>
#include <TFT_eSPI.h>

// ================= 屏幕基础配置 =================
#define SCREEN_WIDTH  240
#define SCREEN_HEIGHT 320
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_CS   10
#define TFT_DC    9
#define TFT_RST   8
#define TFT_BL    7

// ================= 系统配置 =================
#define REPORT_INTERVAL 1000
#define TEST_PIN 42
#define BEEP_DURATION 200
#define UI_REFRESH_INTERVAL 25

// ================= 引脚定义 =================
#define JOY_X1  14
#define JOY_Y1  15
#define JOY_X2  1
#define JOY_Y2  2
#define KEY1    19
#define KEY2    20

// ================= 需隔离的旧引脚 =================
#define OLD_PIN_35 35
#define OLD_PIN_36 36
#define OLD_PIN_37 37
#define OLD_PIN_38 38

// ================= ADC配置 =================
#define ADC_MIN 0
#define ADC_MAX 4095
#define ADC_DEAD_ZONE 200

// ================= 颜色定义 =================
#define COLOR_BG            0x1042
#define COLOR_JOY_BG        0x18C3
#define COLOR_JOY_FRAME     0x3186
#define COLOR_JOY_CROSS     0x2945
#define COLOR_JOY_DOT       0x07E0
#define COLOR_JOY_DOT_BORDER 0xFFFF
#define COLOR_KEY_IDLE      0x6000
#define COLOR_KEY_ACT       0xF800
#define COLOR_SYS_FRAME     0x39E7
#define COLOR_SYS_TEXT      0xC618
#define COLOR_WHITE         0xFFFF

// ================= UI区域定义 (已上移,无标题栏) =================
typedef struct {
  int16_t x;
  int16_t y;
  int16_t w;
  int16_t h;
  const char* title;
  int16_t joy_inner_x;
  int16_t joy_inner_y;
  int16_t joy_inner_w;
  int16_t joy_inner_h;
  int16_t key_x;
  int16_t key_y;
  int16_t key_w;
  int16_t key_h;
} JoyUI_Config;

// 左摇杆 (整体上移15像素)
JoyUI_Config leftJoy = {
  .x = 10, .y = 10, .w = 105, .h = 140, .title = "LEFT",
  .joy_inner_x = 15, .joy_inner_y = 30, .joy_inner_w = 95, .joy_inner_h = 60,
  .key_x = 32, .key_y = 100, .key_w = 60, .key_h = 18
};

// 右摇杆
JoyUI_Config rightJoy = {
  .x = 125, .y = 10, .w = 105, .h = 140, .title = "RIGHT",
  .joy_inner_x = 130, .joy_inner_y = 30, .joy_inner_w = 95, .joy_inner_h = 60,
  .key_x = 147, .key_y = 100, .key_w = 60, .key_h = 18
};

// 系统信息区域 (上移)
#define SYS_INFO_X 10
#define SYS_INFO_Y 160
#define SYS_INFO_W 220
#define SYS_INFO_H 150

TFT_eSPI tft = TFT_eSPI();

int adcLX = 2048, adcLY = 2048;
int adcRX = 2048, adcRY = 2048;
bool key1State = false, key2State = false;
uint32_t lastScreenUpdate = 0;

void initTFT();
void drawBootAnimation();
void drawMainUIFrame();
void readAllInputs();
void updateJoyDisplay(JoyUI_Config config, int adcX, int adcY, bool keyState);
void updateSysInfo();
void testBeep();
void drawCenteredText(const char* text, int y, uint16_t color, uint8_t size);
void drawRightAlignedText(const char* text, int x, int y, uint16_t color, uint8_t size);

void setup() {
  Serial.begin(115200);
  delay(500);

  pinMode(OLD_PIN_35, INPUT);
  pinMode(OLD_PIN_36, INPUT);
  pinMode(OLD_PIN_37, INPUT);
  pinMode(OLD_PIN_38, INPUT);
  pinMode(JOY_X1, INPUT);
  pinMode(JOY_Y1, INPUT);
  pinMode(JOY_X2, INPUT);
  pinMode(JOY_Y2, INPUT);
  pinMode(KEY1, INPUT_PULLUP);
  pinMode(KEY2, INPUT_PULLUP);
  pinMode(TEST_PIN, OUTPUT);
  digitalWrite(TEST_PIN, LOW);

  initTFT();
  drawBootAnimation();
  drawMainUIFrame();
  testBeep();
}

void loop() {
  readAllInputs();

  if (millis() - lastScreenUpdate >= UI_REFRESH_INTERVAL) {
    lastScreenUpdate = millis();
    updateJoyDisplay(leftJoy, adcLX, adcLY, key1State);
    updateJoyDisplay(rightJoy, adcRX, adcRY, key2State);
  }

  static uint32_t lastSysReport = 0;
  if (millis() - lastSysReport >= REPORT_INTERVAL) {
    lastSysReport = millis();
    updateSysInfo();
  }

  
}

void initTFT() {
  pinMode(TFT_RST, OUTPUT);
  digitalWrite(TFT_RST, LOW);
  delay(100);
  digitalWrite(TFT_RST, HIGH);
  delay(200);
  tft.init();
  tft.setRotation(2);
  tft.fillScreen(COLOR_BG);
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
}

void drawBootAnimation() {
  for (int y = 0; y < SCREEN_HEIGHT; y++) {
    uint8_t blue = map(y, 0, SCREEN_HEIGHT, 10, 70);
    tft.drawFastHLine(0, y, SCREEN_WIDTH, tft.color565(0, 0, blue));
  }
  drawCenteredText("ESP32-S3", 80, COLOR_WHITE, 4);
  delay(300);
  drawCenteredText("Joystick Monitor", 130, COLOR_JOY_DOT, 2);
  delay(500);
  tft.fillRoundRect(30, 180, 180, 20, 10, COLOR_JOY_FRAME);
  for (int i = 0; i <= 176; i += 4) {
    tft.fillRoundRect(32, 182, i, 16, 8, COLOR_JOY_DOT);
    delay(12);
  }
  drawCenteredText("System Ready!", 220, COLOR_WHITE, 2);
  delay(800);
  tft.fillScreen(COLOR_BG);
}

// ================= 主界面:已移除顶部标题栏 =================
void drawMainUIFrame() {
  // 左摇杆
  tft.fillRoundRect(leftJoy.x, leftJoy.y, leftJoy.w, leftJoy.h, 5, COLOR_JOY_BG);
  tft.drawRoundRect(leftJoy.x, leftJoy.y, leftJoy.w, leftJoy.h, 5, COLOR_JOY_FRAME);
  tft.setTextColor(COLOR_WHITE);
  tft.setTextSize(1);
  tft.setCursor(leftJoy.x + 8, leftJoy.y + 8);
  tft.println(leftJoy.title);

  // 右摇杆
  tft.fillRoundRect(rightJoy.x, rightJoy.y, rightJoy.w, rightJoy.h, 5, COLOR_JOY_BG);
  tft.drawRoundRect(rightJoy.x, rightJoy.y, rightJoy.w, rightJoy.h, 5, COLOR_JOY_FRAME);
  tft.setCursor(rightJoy.x + 8, rightJoy.y + 8);
  tft.println(rightJoy.title);

  // 系统信息
  tft.drawRoundRect(SYS_INFO_X, SYS_INFO_Y, SYS_INFO_W, SYS_INFO_H, 5, COLOR_SYS_FRAME);
  tft.setTextColor(COLOR_SYS_TEXT);
  tft.setTextSize(1);
  tft.setCursor(SYS_INFO_X + 10, SYS_INFO_Y + 8);
  tft.println("System Information");
}

void readAllInputs() {
  adcLX = analogRead(JOY_X1);
  adcLY = analogRead(JOY_Y1);
  adcRX = analogRead(JOY_X2);
  adcRY = analogRead(JOY_Y2);
  key1State = (digitalRead(KEY1) == LOW);
  key2State = (digitalRead(KEY2) == LOW);
}

// ================= 核心:优化版摇杆显示 =================
void updateJoyDisplay(JoyUI_Config config, int adcX, int adcY, bool keyState) {
  // 1. 安全清除整个内部区域 (稍微扩大一点,防止边缘残留)
  tft.fillRect(config.x + 1, config.y + 18, config.w - 2, config.h - 22, COLOR_JOY_BG);

  // 2. 定义活动区域
  int innerX = config.x + 10;
  int innerY = config.y + 28; // 稍微下移,给标题留空间
  int innerW = config.w - 20;
  int innerH = config.h - 55;

  // =========================================================
  // 关键修复1:左右方向反转
  // 如果你觉得左右反了,就把下面这行的 map 参数调换顺序
  // =========================================================
  // 原逻辑:int dotX = map(adcY, ADC_MIN, ADC_MAX, innerX + 8, innerX + innerW - 8);
  // 反转后:(把 innerX + innerW - 8 和 innerX + 8 换位置)
  int dotX = map(adcY, ADC_MIN, ADC_MAX, innerX + innerW - 8, innerX + 8); 
  
  // 上下方向 (保持原样,如果你觉得上下也反了,同理调换)
  int dotY = map(adcX, ADC_MIN, ADC_MAX, innerY + innerH - 8, innerY + 8);

  // 死区处理
  if (abs(adcX - 2048) < ADC_DEAD_ZONE) dotY = innerY + innerH/2;
  if (abs(adcY - 2048) < ADC_DEAD_ZONE) dotX = innerX + innerW/2;

  // 严格限制范围
  dotX = constrain(dotX, innerX + 8, innerX + innerW - 8);
  dotY = constrain(dotY, innerY + 8, innerY + innerH - 8);

  // 3. 绘制十字准星
  int centerX = innerX + innerW/2;
  int centerY = innerY + innerH/2;
  tft.drawFastVLine(centerX, innerY, innerH, COLOR_JOY_CROSS);
  tft.drawFastHLine(innerX, centerY, innerW, COLOR_JOY_CROSS);
  tft.fillCircle(centerX, centerY, 3, COLOR_JOY_CROSS);

  // 4. 绘制摇杆圆点
  tft.fillCircle(dotX, dotY, 7, COLOR_JOY_DOT);
  tft.drawCircle(dotX, dotY, 7, COLOR_JOY_DOT_BORDER);

  // =========================================================
  // 关键修复2:按键文字绝对居中
  // =========================================================
  int keyBoxX = config.x + (config.w - 50) / 2; // 按键框居中
  int keyBoxY = config.y + config.h - 22;
  
  // 先清除按键区域背景
  tft.fillRect(keyBoxX - 2, keyBoxY - 2, 54, 20, COLOR_JOY_BG);
  // 绘制按键框
  tft.fillRoundRect(keyBoxX, keyBoxY, 50, 16, 3, keyState ? COLOR_KEY_ACT : COLOR_KEY_IDLE);
  tft.drawRoundRect(keyBoxX, keyBoxY, 50, 16, 3, COLOR_WHITE);
  
  // 绘制文字 "SW" (居中算法)
  tft.setTextColor(COLOR_WHITE);
  tft.setTextSize(1);
  const char* keyLabel = "SW";
  int textWidth = strlen(keyLabel) * 6; // 6 pixels per char
  int textX = keyBoxX + (50 - textWidth) / 2;
  int textY = keyBoxY + 4;
  tft.setCursor(textX, textY);
  tft.print(keyLabel);

  // =========================================================
  // 关键修复3:ADC数值坐标居中显示
  // =========================================================
  char coordStr[16];
  sprintf(coordStr, "%d,%d", adcX, adcY);
  int coordWidth = strlen(coordStr) * 6;
  int coordX = config.x + (config.w - coordWidth) / 2;
  tft.setTextSize(1);
  tft.setCursor(coordX, config.y + config.h - 35);
  tft.print(coordStr);
}

void updateSysInfo() {
  tft.fillRect(SYS_INFO_X + 10, SYS_INFO_Y + 20, SYS_INFO_W - 20, SYS_INFO_H - 30, COLOR_BG);

  tft.setTextColor(COLOR_SYS_TEXT);
  tft.setTextSize(1);
  char buffer[48];

  sprintf(buffer, "CPU Frequency");
  tft.setCursor(SYS_INFO_X + 15, SYS_INFO_Y + 25);
  tft.print(buffer);
  sprintf(buffer, "%d MHz", ESP.getCpuFreqMHz());
  drawRightAlignedText(buffer, SYS_INFO_X + SYS_INFO_W - 15, SYS_INFO_Y + 25, COLOR_WHITE, 1);

  sprintf(buffer, "Memory Usage");
  tft.setCursor(SYS_INFO_X + 15, SYS_INFO_Y + 45);
  tft.print(buffer);
  uint32_t heapUsed = (ESP.getHeapSize() - ESP.getFreeHeap()) * 100 / ESP.getHeapSize();
  sprintf(buffer, "%d %%", heapUsed);
  drawRightAlignedText(buffer, SYS_INFO_X + SYS_INFO_W - 15, SYS_INFO_Y + 45, COLOR_WHITE, 1);

  sprintf(buffer, "Free Memory");
  tft.setCursor(SYS_INFO_X + 15, SYS_INFO_Y + 65);
  tft.print(buffer);
  sprintf(buffer, "%d KB", ESP.getFreeHeap() / 1024);
  drawRightAlignedText(buffer, SYS_INFO_X + SYS_INFO_W - 15, SYS_INFO_Y + 65, COLOR_WHITE, 1);

  sprintf(buffer, "System Uptime");
  tft.setCursor(SYS_INFO_X + 15, SYS_INFO_Y + 85);
  tft.print(buffer);
  sprintf(buffer, "%lu s", millis() / 1000);
  drawRightAlignedText(buffer, SYS_INFO_X + SYS_INFO_W - 15, SYS_INFO_Y + 85, COLOR_WHITE, 1);
}

void drawCenteredText(const char* text, int y, uint16_t color, uint8_t size) {
  tft.setTextSize(size);
  tft.setTextColor(color);
  int16_t x = (SCREEN_WIDTH - strlen(text) * 6 * size) / 2;
  tft.setCursor(x, y);
  tft.print(text);
}

void drawRightAlignedText(const char* text, int x, int y, uint16_t color, uint8_t size) {
  tft.setTextSize(size);
  tft.setTextColor(color);
  int16_t textWidth = strlen(text) * 6 * size;
  tft.setCursor(x - textWidth, y);
  tft.print(text);
}

void testBeep() {
  uint32_t end = millis() + BEEP_DURATION;
  while (millis() < end) {
    digitalWrite(TEST_PIN, HIGH);
    delayMicroseconds(250);
    digitalWrite(TEST_PIN, LOW);
    delayMicroseconds(250);
  }
  digitalWrite(TEST_PIN, LOW);
}

更多推荐