Hand-built handheld game console with TFT screen, joystick, and exposed ESP32-S3 wiring

ขั้นสูง

สร้างเครื่องเล่นเกมพกพาสไตล์เรโทร

จอแสดงผลเซ็นเซอร์

สินค้าในหมวดหมู่ใหม่สำหรับร้านนี้: เครื่องเล่นเกมพกพาขนาดเล็กที่มีหน้าจอสีจริงและปุ่มควบคุมจริง ซึ่งเล่นเกมเรียบง่ายที่คุณเขียนขึ้นเองได้ โดยยึดหลักการพื้นฐานเช่นเดียวกับเครื่องเล่นเกมพกพารุ่นคลาสสิกทุกเครื่อง

รวมชิ้นส่วนจริง 4 รายการ

ราคารวมชุดนี้: 1,271.00 ฿

ยังไม่รวมส่วนลดคำสั่งซื้อที่คุณอาจได้รับตอนชำระเงิน

สิ่งที่คุณต้องใช้

เป้าหมายของโปรเจกต์

นี่คือการสร้างที่ซับซ้อนที่สุดในแค็ตตาล็อก: อุปกรณ์เกมพกพาจริงที่มีหน้าจอสี, ก้านควบคุมทางกายภาพ และเสียง ที่รันเกมที่คุณเขียนเองทั้งหมด มันรวบรวมกราฟิกหน้าจอ, อินพุตแบบอนาล็อก และเสียงเข้าไว้ในโปรเจกต์เดียว ซึ่งเป็นส่วนประกอบสามอย่างเดียวกันที่อยู่เบื้องหลังเครื่องเล่นเกมพกพารุ่นคลาสสิกทุกรุ่น

คอนโทรลเลอร์ทำอะไร

มันรันลูปเกมอย่างต่อเนื่อง: อ่านจอยสติ๊ก, อัปเดตสถานะเกมตามอินพุตนั้น, วาดหน้าจอใหม่, ตรวจสอบเงื่อนไขการชนะหรือแพ้, และทำซ้ำหลายสิบครั้งต่อวินาที ทุกสิ่งที่เกมเป็นจริง ๆ ล้วนมาจากลูปเดียวที่ทำงานเร็วและสอดคล้องกัน

หน้าจอทำอะไร

TFT สีจะวาดภาพเกมใหม่ทุกเฟรม: ตำแหน่งผู้เล่น, คะแนน, อุปสรรค, หรืออะไรก็ตามที่เกมต้องการแสดงผล สีเต็มรูปแบบเปิดโอกาสให้ภาพที่เหมือนเกมจริง ๆ ซึ่ง OLED แบบขาวดำขนาดเล็กไม่สามารถเทียบได้

จอยสติ๊กทำอะไร

มันรายงานตำแหน่งทางกายภาพเป็นค่าอนาล็อกง่าย ๆ สองค่า บวกกับการคลิกปุ่มดิจิทัล ซึ่งเป็นรูปแบบอินพุตเดียวกับคอนโทรลเลอร์เกมจริง ทำให้คอนโทรลเลอร์มีทุกสิ่งที่จำเป็นในการเคลื่อนย้ายตัวละครผู้เล่นด้วยการควบคุมที่สมจริงและค่อยเป็นค่อยไป แทนที่จะเป็นแค่ทิศทางเปิด/ปิด

Buzzer ทำอะไร

มันให้เอฟเฟกต์เสียงง่าย ๆ และเสียงบี๊บในเหตุการณ์สำคัญของเกม เช่น การทำคะแนน, การชน, เกมโอเวอร์ ซึ่งเป็นสัญญาณเสียงเล็ก ๆ ที่ทำให้เกมรู้สึกตอบสนองอย่างแท้จริง

แนวคิดซอฟต์แวร์และไลบรารี

เริ่มต้นง่ายกว่าเกมเต็มรูปแบบมาก: วาดรูปทรงคงที่หนึ่งรูปบนหน้าจอ, จากนั้นทำให้มันเคลื่อนที่ตามจอยสติ๊ก, จากนั้นเพิ่มวัตถุที่สองและการตรวจจับการชนแบบง่าย ๆ เกมที่สมบูรณ์ก็แค่ลูปเดียวกันนั้นที่มีกฎเพิ่มเติมซ้อนทับกัน เริ่มต้นด้วยอะไรที่ง่าย ๆ เช่น จุดเดียวที่เคลื่อนที่ได้ก่อนที่จะพยายามทำอะไรที่มีกราฟิกจริงจัง

การทดสอบและการแก้ไขปัญหา

  • การวาดใหม่ดูสั่นไหวหรือช้า หลีกเลี่ยงการล้างและวาดหน้าจอทั้งหมดใหม่ทุกเฟรม ให้วาดใหม่เฉพาะพิกเซลที่เปลี่ยนแปลงจริง ๆ เท่านั้น
  • การอ่านค่าจอยสติ๊กกระโดดหรือไม่กลับไปที่ศูนย์อย่างสมบูรณ์ ให้อ่านค่าที่หยุดนิ่งหนึ่งครั้งตอนเริ่มต้นและถือว่าเป็นศูนย์ แทนที่จะสันนิษฐานว่าเป็นจุดกึ่งกลางที่สมบูรณ์แบบ
  • นี่คือรายการส่วนประกอบที่มีปริมาณน้อยกว่าส่วนใหญ่ในแค็ตตาล็อก หากคุณสังเกตเห็นสิ่งผิดปกติเกี่ยวกับจอยสติ๊กที่เพิ่งแกะกล่อง ควรส่งข้อความถึงซัพพลายเออร์อย่างรวดเร็วแทนที่จะคิดว่าเป็นเรื่องปกติ

การอัปเกรดเสริม

เพิ่มโมดูล SD การ์ดจากส่วน Data Logger เพื่อบันทึกคะแนนสูงสุดที่คงอยู่แม้ปิดเครื่อง ซึ่งเป็นไฟล์บันทึกจริงแทนที่จะเป็นคะแนนที่รีเซ็ตทุกครั้ง

การเดินสายไฟ

  • ST7789 VCC → ESP32-S3 3.3V
  • ST7789 GND → ESP32-S3 GND
  • ST7789 SCK → ESP32-S3 GPIO12
  • ST7789 SDA (MOSI) → ESP32-S3 GPIO11
  • ST7789 RES (RST) → ESP32-S3 GPIO8
  • ST7789 DC → ESP32-S3 GPIO9
  • ST7789 CS → ESP32-S3 GPIO10
  • ST7789 BLK → ESP32-S3 GPIO13 หรือต่อตรงเข้ากับ 3.3V เพื่อให้ไฟแบ็คไลท์ติดตลอดเวลา
  • Joystick VRx → ESP32-S3 GPIO1 (ADC)
  • Joystick VRy → ESP32-S3 GPIO2 (ADC)
  • Joystick SW → ESP32-S3 GPIO3 (INPUT_PULLUP, active LOW)
  • Joystick VCC/GND → ESP32-S3 3.3V/GND
  • Buzzer signal → ESP32-S3 GPIO4
  • Buzzer GND → ESP32-S3 GND

แผง ST7789 ขนาด 2.4 นิ้ว มักจะเป็น 240x320 แต่แตกต่างกันไปตามซัพพลายเออร์ — ตรวจสอบของคุณและปรับ `SCREEN_WIDTH`/`SCREEN_HEIGHT` และ `setRotation()` หากภาพถูกสะท้อนหรือถูกตัด นี่คือ active buzzer ตามรายการชิ้นส่วน ดังนั้นจึงสามารถเปิด/ปิดได้เท่านั้น — เหมาะสำหรับเสียงบี๊บเมื่อชนขอบเขต แต่ไม่สามารถเล่นเพลงหรือปรับระดับเสียงได้ (ดูโปรเจกต์ Theremin สำหรับการสร้างโทนเสียง ซึ่งต้องใช้ passive buzzer แทน)

แผนภาพการต่อสาย

VCC → 3.3V GND SCK → GPIO12 SDA → GPIO11 RES → GPIO8 DC → GPIO9 CS → GPIO10 BLK → GPIO13 GPIO1 GPIO2 SW → GPIO3 VCC/GND → 3.3V/GND signal → GPIO4 GND ESP32-S3 ST7789 Joystick VRx Joystick VRy Joystick Buzzer

โค้ดเริ่มต้น

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>

// --- Pin definitions: adjust to match your ESP32-S3 board's silkscreen if different ---
#define TFT_CS   10
#define TFT_DC   9
#define TFT_RST  8
#define TFT_SCK  12
#define TFT_MOSI 11
#define TFT_BLK  13   // backlight - tie to 3.3V directly, or drive as PWM for dimming

#define VRX_PIN  1    // joystick X (ADC-capable pin)
#define VRY_PIN  2    // joystick Y (ADC-capable pin)
#define SW_PIN   3    // joystick push button, active LOW
#define BUZZER_PIN 4  // active buzzer signal

// 2.4" ST7789 panels are commonly 240x320 - adjust if yours differs
#define SCREEN_WIDTH  240
#define SCREEN_HEIGHT 320

SPIClass tftSPI(FSPI);
Adafruit_ST7789 tft = Adafruit_ST7789(&tftSPI, TFT_CS, TFT_DC, TFT_RST);

int paddleX, paddleY;
const int PADDLE_SIZE = 20;
const int MOVE_SPEED = 4;

void beep(int durationMs) {
  digitalWrite(BUZZER_PIN, HIGH);
  delay(durationMs);
  digitalWrite(BUZZER_PIN, LOW);
}

void setup() {
  Serial.begin(115200);
  pinMode(SW_PIN, INPUT_PULLUP);
  pinMode(BUZZER_PIN, OUTPUT);
  digitalWrite(BUZZER_PIN, LOW);

  pinMode(TFT_BLK, OUTPUT);
  digitalWrite(TFT_BLK, HIGH); // backlight on

  tftSPI.begin(TFT_SCK, -1, TFT_MOSI, TFT_CS);
  tft.init(SCREEN_WIDTH, SCREEN_HEIGHT);
  tft.setRotation(2);
  tft.fillScreen(ST77XX_BLACK);

  paddleX = SCREEN_WIDTH / 2;
  paddleY = SCREEN_HEIGHT / 2;
}

void loop() {
  int rawX = analogRead(VRX_PIN); // 0-4095 on ESP32 ADC
  int rawY = analogRead(VRY_PIN);

  int dx = map(rawX, 0, 4095, -MOVE_SPEED, MOVE_SPEED);
  int dy = map(rawY, 0, 4095, -MOVE_SPEED, MOVE_SPEED);

  // erase old paddle
  tft.fillRect(paddleX, paddleY, PADDLE_SIZE, PADDLE_SIZE, ST77XX_BLACK);

  paddleX += dx;
  paddleY += dy;

  bool hitEdge = false;
  if (paddleX < 0) { paddleX = 0; hitEdge = true; }
  if (paddleX > SCREEN_WIDTH - PADDLE_SIZE) { paddleX = SCREEN_WIDTH - PADDLE_SIZE; hitEdge = true; }
  if (paddleY < 0) { paddleY = 0; hitEdge = true; }
  if (paddleY > SCREEN_HEIGHT - PADDLE_SIZE) { paddleY = SCREEN_HEIGHT - PADDLE_SIZE; hitEdge = true; }

  if (hitEdge) beep(40);

  // draw new paddle
  tft.fillRect(paddleX, paddleY, PADDLE_SIZE, PADDLE_SIZE, ST77XX_GREEN);

  if (digitalRead(SW_PIN) == LOW) {
    beep(100); // button press feedback
  }

  delay(20);
}

ไม่แน่ใจขั้นตอนไหน? ถามเรา