想學習 ESP32-S3 圖形介面與人機介面(HMI)開發嗎?本篇教學將帶您使用高效能的 ESP32-S3 開發板 搭配 2.8 吋 ILI9341 TFT 觸控螢幕,從零開始打造一款經典好玩的「打地鼠遊戲(Whack-a-Mole)」。
透過本篇完整教學,您將學會以下核心技術:
-
ESP32-S3 SPI TFT 螢幕驅動與 ILI9341 顯示器設定
-
XPT2046 觸控控制器應用與獨立 SPI 架構
-
TFT_eSPI高效能圖形函式庫使用技巧 -
觸控座標即時校正與數據對應
-
遊戲介面繪製、觸控事件判斷與計分邏輯
-
Arduino IDE 開發環境設定與常見問題排解
本次使用硬體與購買連結
在開始動手實作前,請先準備好以下硬體元件:
-
ESP32-S3 N16R8 開發板
-
特色:搭載雙核心 Xtensa LX7 微控制器,支援 WiFi 與藍牙雙模,內建 16MB Flash 與 8MB PSRAM,非常適合處理圖形介面(GUI)與物聯網(IoT)專案。
-
-
2.8 吋 ILI9341 TFT 觸控螢幕
-
規格:解析度 240×320,採用 ILI9341 顯示驅動 IC 與 XPT2046 觸控 IC,透過 SPI 介面通訊,並支援 MicroSD 卡擴充。
-
專案成果展示
完成本教學的程式碼與硬體組裝後,您將實現以下互動功能:
-
隨機洞穴位置出現地鼠
-
觸控螢幕直接擊打地鼠得分
-
30 秒遊戲倒數計時機制
-
遊戲結束畫面與最終總分統計
第一階段:Arduino IDE 環境配置與程式庫安裝
Step 1:安裝 Arduino IDE 與 ESP32 核心
建議使用以下穩定版本進行開發:
-
Arduino IDE:1.8.19(或相容版本)
-
ESP32 Core:2.0.13
Step 2:安裝必要函式庫
在編寫程式前,需透過 Arduino IDE 安裝專門處理顯示與觸控的第三方程式庫:
-
TFT_eSPI(建議版本:2.4.79)
-
開啟 Arduino IDE,點選左側「程式庫管理員」(或至選單
工具>管理程式庫)。 -
搜尋
TFT_eSPI,找到指定版本並點擊安裝。
-
-
XPT2046_Touchscreen(建議版本:1.4)
-
同樣透過程式庫管理員搜尋
XPT2046_Touchscreen,找到指定版本並點擊安裝。
-
第二階段:ESP32-S3 與 ILI9341 接線圖解
為了確保顯示流暢並避免訊號干擾,建議將 TFT 顯示器與 XPT2046 觸控晶片採用獨立的 SPI 腳位連接方式。
1. TFT 顯示器接線表
|
TFT 模組腳位 |
ESP32-S3腳位 |
|---|---|
|
VCC |
3V3 |
|
GND |
GND |
|
LCD_CS |
GPIO10 |
|
LCD_RST |
GPIO14 |
|
LCD_RS(DC) |
GPIO9 |
|
SDI(MOSI) |
GPIO11 |
|
SCK |
GPIO12 |
|
SDO(MISO) |
GPIO13 |
|
LED |
3V3 |
2. XPT2046 觸控接線表(獨立 SPI)
|
TOUCH腳位 |
ESP32-S3腳位 |
|---|---|
|
T_CS |
GPIO16 |
|
T_CLK |
GPIO6 |
|
T_DIN |
GPIO4 |
|
T_DO |
GPIO5 |
|
T_IRQ |
不接 |

第三階段:TFT_eSPI 函式庫設定
安裝完成後,必須修改 TFT_eSPI 的設定檔以對應您的硬體腳位。
找到以下路徑的設定檔: Documents/Arduino/libraries/TFT_eSPI/User_Setup.h
請根據上方的接線對應修改 User_Setup.h 內容:
#define ILI9341_DRIVER
#define TFT_MISO 13
#define TFT_MOSI 11
#define TFT_SCLK 12
#define TFT_CS 10
#define TFT_DC 9
#define TFT_RST 14
#define SPI_FREQUENCY 40000000
#define SPI_READ_FREQUENCY 20000000
第四階段:觸控座標校正實作
由於每片觸控面板的物理特性略有差異,使用前必須透過校正程式取得對應的原始數值(Raw Data)。
觸控校正程式碼
#include <SPI.h>
#include <XPT2046_Touchscreen.h>
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI();
// Touch SPI
SPIClass touchSPI(FSPI);
#define TOUCH_SCLK 6
#define TOUCH_MISO 5
#define TOUCH_MOSI 4
#define TOUCH_CS 16
XPT2046_Touchscreen ts(TOUCH_CS);
void waitTouchRelease()
{
while(ts.touched())
{
delay(10);
}
}
void getCalibrationPoint(
const char* text,
int px,
int py,
uint16_t color,
int &rawX,
int &rawY
)
{
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_WHITE);
tft.setTextSize(2);
tft.drawString(text, 20, 20);
tft.fillCircle(px, py, 10, color);
while(!ts.touched())
{
delay(10);
}
TS_Point p = ts.getPoint();
rawX = p.x;
rawY = p.y;
Serial.printf(
"%s : X=%d Y=%d Z=%d\n",
text,
p.x,
p.y,
p.z
);
waitTouchRelease();
delay(500);
}
void setup()
{
Serial.begin(115200);
tft.init();
tft.setRotation(1);
touchSPI.begin(
TOUCH_SCLK,
TOUCH_MISO,
TOUCH_MOSI,
TOUCH_CS
);
ts.begin(touchSPI);
int x1,y1;
int x2,y2;
int x3,y3;
int x4,y4;
getCalibrationPoint(
"Touch TOP LEFT",
20,
20,
TFT_RED,
x1,
y1
);
getCalibrationPoint(
"Touch TOP RIGHT",
300,
20,
TFT_GREEN,
x2,
y2
);
getCalibrationPoint(
"Touch BOTTOM LEFT",
20,
220,
TFT_BLUE,
x3,
y3
);
getCalibrationPoint(
"Touch BOTTOM RIGHT",
300,
220,
TFT_YELLOW,
x4,
y4
);
tft.fillScreen(TFT_BLACK);
Serial.println();
Serial.println("===== Calibration Result =====");
Serial.printf("TOP LEFT : X=%d Y=%d\n", x1, y1);
Serial.printf("TOP RIGHT : X=%d Y=%d\n", x2, y2);
Serial.printf("BOTTOM LEFT : X=%d Y=%d\n", x3, y3);
Serial.printf("BOTTOM RIGHT : X=%d Y=%d\n", x4, y4);
Serial.println("==============================");
tft.drawString("Calibration Done", 50, 100);
}
void loop()
{
}
依照畫面指示依序點擊螢幕四角:
-
TOP LEFT(左上) -
TOP RIGHT(右上) -
BOTTOM LEFT(左下) -
BOTTOM RIGHT(右下)
取得校正數值與座標對應
透過序列埠監視視窗取得實測結果後,定義您的極值並進行座標對應轉換:
#define TS_MINX 608
#define TS_MAXX 3714
#define TS_MINY 544
#define TS_MAXY 3545
轉換為螢幕座標:
int tx = map(
p.x,
TS_MAXX,
TS_MINX,
0,
320
);
int ty = map(
p.y,
TS_MAXY,
TS_MINY,
0,
240
);
第五階段:打地鼠遊戲核心功能實作
在主程式中,我們透過繪圖函式與邏輯判斷完成遊戲互動機制。
程式載點:Whack A Mole Code
1. 洞穴與地鼠繪製
利用圓形繪製函式在地畫出地鼠洞與隨機出現的地鼠:
洞穴繪製:tft.fillCircle(x, y, 35, TFT_DARKGREY);
地鼠繪製:tft.fillCircle(x, y, 28, TFT_BROWN);
2. 擊中判斷邏輯
透過計算觸控座標與地鼠中心點的幾何距離,判定玩家是否成功擊中目標:
int dx = tx - holes[moleIndex].x;
int dy = ty - holes[moleIndex].y;
if ((dx * dx + dy * dy) < 900)
{
score++;
}
3. 倒數計時與遊戲結束控制
利用系統時間差計算剩餘秒數,時間歸零時觸發結算畫面:
int remain =
30 - ((millis() - gameStart) / 1000);
if ((millis() - gameStart) >= 30000)
{
showGameOver();
}
常見問題與疑難排解 (FAQ)
Q1:ILI9341 螢幕沒有顯示畫面?
請確認以下項目:
-
TFT_CS與TFT_DC實體接線與程式碼定義是否完全正確。 -
TFT_eSPI的User_Setup.h設定檔是否已正確儲存並重新編譯。 -
SPI 通訊頻率是否設定過高(建議初始測試使用 40MHz 以下)。
Q2:XPT2046 觸控螢幕完全沒有反應?
請確認以下項目:
-
觸控晶片的
T_CS腳位連接是否正確。 -
是否已執行並完成上文的觸控校正程式。
-
觸控 SPI 是否採用獨立的腳位設定,避免與顯示器資料線衝突。
Q3:為什麼 ESP32-S3 與 XPT2046 建議要分開獨立 SPI?
部分市售的 ILI9341 模組若讓顯示與觸控共用同一組 SPI 匯流排,在高頻率存取時容易發生以下狀況:
-
螢幕畫面閃爍
-
觸控功能突然失效
-
座標偏移或飄移
採用獨立 SPI 腳位能夠有效隔離訊號,大幅提升系統穩定度。
結語
透過本篇完整的實作教學,您已經成功學會使用 ESP32-S3 搭配 ILI9341 觸控螢幕 開發互動式遊戲。這項技術不僅能應用於趣味小遊戲,未來更能廣泛延伸至智慧家居控制面板、物聯網(IoT)狀態顯示器、教育互動設備以及各類 AI 人機介面專案中。
歡迎持續關注米羅科技 Mirotek,探索更多關於 ESP32、Arduino、樹莓派與電子模組的實用教學與硬體應用!
延伸閱讀與推薦課程
- 上一篇教學:【ESP32-S3 教學】開始使用 Arduino IDE 編寫程式-Serial 序列埠通信與 UART 入門實作
- 下一篇推薦:【ESP32教學】第三十六篇:專題ESP32氣象站+電子時鐘 OpenWeather 天氣預報搭配 ILI9341顯示螢幕
- 返回教學列表:米羅科技文創學院教學部落格
- 教學總索引:ESP32 AIoT 開發指南 – 快速索引目錄