Cheap Yellow Display ESP32-2432S028: sơ đồ chân, cài TFT_eSPI, cảm ứng XPT2046 và dự án đầu tiên
Cheap Yellow Display (ESP32-2432S028R) là gì, sơ đồ chân, hai bus SPI của màn hình và cảm ứng; cài TFT_eSPI, XPT2046_Touchscreen; dự án đầu tiên với nút bấm, LED RGB, LDR, hiệu chỉnh cảm ứng.
Cheap Yellow Display (CYD) là tên cộng đồng đặt cho bo ESP32-2432S028R: một mạch màu vàng gồm ESP32, màn hình TFT 2,8 inch 320 × 240 có cảm ứng, khe thẻ nhớ, LED RGB, cảm biến ánh sáng và chỗ cắm loa, giá rẻ hơn mua rời từng món. Bo này hợp để làm bảng điều khiển, đồng hồ, màn hình hiển thị cảm biến, giao diện cho thiết bị IoT. Bài này giới thiệu các khối trên bo, sơ đồ chân, cách cài TFT_eSPI và thư viện cảm ứng trong Arduino IDE, rồi làm dự án đầu tiên: ba nút trên màn hình bật LED RGB, thanh hiển thị độ sáng và vùng vẽ bằng ngón tay.
1. Cheap Yellow Display có gì

- ESP32-WROOM-32: lõi kép 240 MHz, Wi-Fi, Bluetooth, như ESP32 DevKit thông thường.
- Màn hình 2,8 inch 320 × 240 điểm ảnh, chip điều khiển ILI9341 (bản một cổng micro USB), nối bus SPI.
- Cảm ứng điện trở đọc bằng chip XPT2046, nối một bus SPI khác với màn hình.
- Khe thẻ microSD, LED RGB, cảm biến quang LDR, đầu cắm loa có mạch khuếch đại nhỏ.
- Mạch nạp CH340 và nút BOOT, RST: nạp chương trình qua cổng USB như ESP32 DevKit.
Có nhiều phiên bản: bản hai cổng (micro USB và USB-C) thường dùng chip màn hình ST7789 thay ILI9341, một số lô có màu bị đảo. Bản có chữ C ở cuối mã (2432S028C) dùng cảm ứng điện dung. Bài này viết cho bản phổ biến ESP32-2432S028R một cổng micro USB; mục 6 ghi cách sửa cho các bản khác.
2. Sơ đồ chân

Hầu hết chân GPIO đã nối vào các khối trên bo. Chân còn dùng được cho cảm biến ngoài chỉ có GPIO22, GPIO27 (đầu cắm CN1) và GPIO35 (đầu cắm P3, chỉ đọc). GPIO21 ở đầu cắm P3 trùng chân đèn nền màn hình: dùng chân này cho việc khác sẽ làm màn hình tối. Cần nhiều chân hơn thì dùng I2C trên GPIO22, GPIO27 với module mở rộng.

Điểm khác lớn nhất so với các màn hình TFT cảm ứng rời: cảm ứng XPT2046 không chung bus SPI với màn hình. Hàm cảm ứng có sẵn trong TFT_eSPI chỉ làm việc khi cảm ứng chung bus, nên trên CYD phải đọc cảm ứng bằng thư viện XPT2046_Touchscreen qua một bus SPI riêng. Khe thẻ SD nối vào chân mặc định của bus VSPI; muốn dùng cả thẻ SD và cảm ứng cùng lúc thì phải chia bus, phần đó nằm ngoài bài này.
3. Cài đặt trong Arduino IDE
- Cài gói ESP32 và chọn bo ESP32 Dev Module như bài cài ESP32 vào Arduino IDE 2. Cắm bo bằng dây USB có truyền dữ liệu; máy cần driver CH340.
- Library Manager: cài TFT_eSPI (Bodmer) và XPT2046_Touchscreen (Paul Stoffregen).
- Cấu hình TFT_eSPI cho CYD: mở thư mục thư viện TFT_eSPI (thường ở
Documents/Arduino/libraries/TFT_eSPI), sao lưu fileUser_Setup.h, rồi thay nội dung bằng file dưới đây.
// PYWORLD.VN - Cấu hình TFT_eSPI cho Cheap Yellow Display ESP32-2432S028R (ILI9341 320x240, bus HSPI)
// Chép nội dung này thay cho User_Setup.h trong thư mục thư viện TFT_eSPI (giữ bản cũ để dự phòng).
// Bản bo hai cổng USB thường dùng ST7789: đổi ILI9341_2_DRIVER thành ST7789_DRIVER, thêm TFT_INVERSION_ON nếu màu bị đảo.
#define USER_SETUP_ID 9001
#define ILI9341_2_DRIVER
#define TFT_WIDTH 240
#define TFT_HEIGHT 320
#define USE_HSPI_PORT // màn hình dùng HSPI; cảm ứng XPT2046 dùng VSPI riêng
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS 15
#define TFT_DC 2
#define TFT_RST -1 // chân RESET màn hình nối chung với EN của ESP32
#define TFT_BL 21 // đèn nền
#define TFT_BACKLIGHT_ON HIGH
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define SMOOTH_FONT
#define SPI_FREQUENCY 40000000
#define SPI_READ_FREQUENCY 20000000
#define DISABLE_ALL_LIBRARY_WARNINGS // cảm ứng đọc bằng thư viện XPT2046_Touchscreen nên không khai báo TOUCH_CS ở đây
TFT_eSPI đọc cấu hình từ thư mục thư viện, không phải từ thư mục sketch. Mỗi lần cập nhật thư viện TFT_eSPI, file User_Setup.h bị ghi đè về mặc định; cần chép lại. Cách cấu hình TFT_eSPI chi tiết (nhiều màn hình, file setup riêng) có ở bài cấu hình TFT_eSPI cùng cụm chủ đề.
4. Dự án đầu tiên: nút bấm, LED RGB, LDR, vẽ bằng tay

/*
PYWORLD.VN - Dự án đầu tiên với Cheap Yellow Display (ESP32-2432S028R): màn hình ILI9341 320x240, cảm ứng XPT2046
PYWORLD.VN - Ba nút trên màn hình bật/tắt LED RGB, thanh hiển thị số đọc cảm biến quang LDR, vẽ chấm ở chỗ chạm
PYWORLD.VN - Thư viện TFT_eSPI (cấu hình User_Setup_CYD.h trong bài) và XPT2046_Touchscreen; cảm ứng dùng bus SPI riêng
*/
#include <SPI.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>
// Cảm ứng XPT2046 nằm trên bus SPI riêng (VSPI), không chung với màn hình
#define TOUCH_IRQ 36
#define TOUCH_MOSI 32
#define TOUCH_MISO 39
#define TOUCH_CLK 25
#define TOUCH_CS 33
// LED RGB trên bo, kích mức thấp; cảm biến quang LDR ở GPIO34
#define LED_DO 4
#define LED_XANH_LA 16
#define LED_XANH_DUONG 17
#define CHAN_LDR 34
// Số đọc thô của cảm ứng ở mép màn hình: chạm bốn góc, xem Serial Monitor rồi sửa cho khớp bo của bạn
const int CHAM_X_MIN = 300, CHAM_X_MAX = 3800;
const int CHAM_Y_MIN = 250, CHAM_Y_MAX = 3800;
SPIClass spiCamUng(VSPI);
XPT2046_Touchscreen camUng(TOUCH_CS, TOUCH_IRQ);
TFT_eSPI tft;
struct Nut {
int x, y, w, h;
const char *ten;
uint16_t mau;
int chanLed;
bool bat;
};
Nut nut[3] = {
{8, 46, 98, 48, "DO", TFT_RED, LED_DO, false},
{111, 46, 98, 48, "XANH LA", TFT_GREEN, LED_XANH_LA, false},
{214, 46, 98, 48, "XANH DUONG", TFT_BLUE, LED_XANH_DUONG, false},
};
const int VUNG_VE_Y = 140; // phía dưới đường này là vùng vẽ chấm
void veNut(const Nut &n) {
tft.fillRoundRect(n.x, n.y, n.w, n.h, 8, n.bat ? n.mau : TFT_DARKGREY);
tft.drawRoundRect(n.x, n.y, n.w, n.h, 8, TFT_WHITE);
tft.setTextColor(TFT_WHITE);
tft.setTextDatum(MC_DATUM);
tft.drawString(n.ten, n.x + n.w / 2, n.y + n.h / 2, 2);
}
void veManHinh() {
tft.fillScreen(TFT_BLACK);
tft.setTextColor(TFT_YELLOW, TFT_BLACK);
tft.setTextDatum(TL_DATUM);
tft.drawString("PYWORLD.VN - CYD", 8, 8, 4);
for (const Nut &n : nut) veNut(n);
tft.drawFastHLine(0, VUNG_VE_Y, 320, TFT_DARKGREY);
tft.setTextColor(TFT_LIGHTGREY, TFT_BLACK);
tft.drawString("Cham vao day de ve", 8, VUNG_VE_Y + 6, 2);
}
void datLed(int chan, bool bat) {
digitalWrite(chan, bat ? LOW : HIGH); // kích mức thấp
}
void veThanhLdr(int giaTri) {
int dai = map(giaTri, 0, 4095, 0, 200);
tft.fillRect(110, 108, 200, 16, TFT_BLACK);
tft.fillRect(110, 108, dai, 16, TFT_CYAN);
tft.drawRect(110, 108, 200, 16, TFT_WHITE);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.setTextDatum(TL_DATUM);
tft.drawString("LDR " + String(giaTri) + " ", 8, 108, 2);
}
void setup() {
Serial.begin(115200);
for (int chan : {LED_DO, LED_XANH_LA, LED_XANH_DUONG}) {
pinMode(chan, INPUT); // gói esp32 3.x: ghi sẵn mức tắt rồi mới chuyển sang ngõ ra
datLed(chan, false);
pinMode(chan, OUTPUT);
}
analogSetPinAttenuation(CHAN_LDR, ADC_11db);
spiCamUng.begin(TOUCH_CLK, TOUCH_MISO, TOUCH_MOSI, TOUCH_CS);
camUng.begin(spiCamUng);
camUng.setRotation(1);
tft.init();
tft.setRotation(1); // nằm ngang 320 x 240, cổng USB bên phải
veManHinh();
}
void loop() {
static bool dangCham = false;
static uint32_t lucDocLdr = 0;
if (camUng.tirqTouched() && camUng.touched()) {
TS_Point p = camUng.getPoint();
int x = constrain(map(p.x, CHAM_X_MIN, CHAM_X_MAX, 0, 319), 0, 319);
int y = constrain(map(p.y, CHAM_Y_MIN, CHAM_Y_MAX, 0, 239), 0, 239);
if (!dangCham) { // chỉ xử lý nút ở lần chạm đầu, không lặp khi giữ tay
Serial.printf("Cham tho x=%d y=%d z=%d -> man hinh x=%d y=%d\n", p.x, p.y, p.z, x, y);
for (Nut &n : nut) {
if (x >= n.x && x < n.x + n.w && y >= n.y && y < n.y + n.h) {
n.bat = !n.bat;
datLed(n.chanLed, n.bat);
veNut(n);
}
}
}
if (y > VUNG_VE_Y + 4) tft.fillCircle(x, y, 3, TFT_YELLOW);
dangCham = true;
} else {
dangCham = false;
}
if (millis() - lucDocLdr >= 300) {
lucDocLdr = millis();
veThanhLdr(analogRead(CHAN_LDR));
}
}
Chương trình làm các việc sau:
- Khởi động bus SPI riêng cho cảm ứng (
SPIClass spiCamUng(VSPI)với chân 25, 39, 32, 33), rồi đặt hướng xoay giống màn hình (setRotation(1), nằm ngang). - Vẽ tiêu đề, ba nút, thanh LDR và vùng vẽ.
- Mỗi vòng lặp kiểm tra chân IRQ (
tirqTouched()) trước khi đọc SPI, nên không tốn thời gian khi không có ai chạm. - Khi chạm: đổi số đọc thô ra tọa độ màn hình bằng
map(). Ở lần chạm đầu, nếu tọa độ nằm trong một nút thì đảo trạng thái LED tương ứng. Trong vùng vẽ thì tô một chấm vàng. - Mỗi 300 ms đọc LDR (GPIO34) và vẽ lại thanh.
LED RGB trên bo kích mức thấp: ghi LOW là sáng. Chương trình ghi sẵn mức tắt trước khi chuyển chân sang ngõ ra để LED không chớp lúc khởi động.
5. Hiệu chỉnh cảm ứng
Cảm ứng điện trở trả về số thô khoảng 200–3900 theo mỗi trục, mỗi bo lệch một ít. Cách chỉnh:
- Nạp chương trình, mở Serial Monitor ở 115200 baud.
- Chạm lần lượt sát bốn góc màn hình bằng đầu bút nhựa. Mỗi lần chạm, Serial in ra số thô
x,y. - Lấy số
xnhỏ nhất, lớn nhất điền vàoCHAM_X_MIN,CHAM_X_MAX; tương tự vớiy. - Nếu chạm bên trái mà chấm hiện bên phải, đổi chỗ hai giá trị MIN và MAX của trục đó.
Cảm ứng điện trở cần ấn hơi mạnh, chính xác nhất khi dùng bút nhựa. Giá trị z là lực ấn: chạm quá nhẹ thì z nhỏ và tọa độ nhảy.
6. Lỗi hay gặp
| Hiện tượng | Nguyên nhân thường gặp | Cách xử lý |
|---|---|---|
| Màn hình trắng, đèn nền sáng | TFT_eSPI chưa đúng cấu hình CYD | Kiểm tra User_Setup.h trong thư mục thư viện; có USE_HSPI_PORT và đúng chân |
| Màu bị đảo (nền đen thành trắng) | Bản bo dùng màn hình khác | Gọi tft.invertDisplay(true) sau tft.init(), hoặc thêm TFT_INVERSION_ON |
| Hình lệch, sọc, chỉ hiện một phần | Bo hai cổng USB dùng ST7789 | Đổi ILI9341_2_DRIVER thành ST7789_DRIVER |
| Chạm không có tác dụng | Dùng hàm cảm ứng của TFT_eSPI, hoặc sai chân bus cảm ứng | Dùng XPT2046_Touchscreen với SPIClass riêng như chương trình mẫu |
| Chạm một chỗ, chấm hiện chỗ khác | Chưa hiệu chỉnh, khác hướng xoay | Làm theo mục 5; setRotation của màn hình và cảm ứng phải giống nhau |
| Màn hình tối sau khi dùng chân P3 | GPIO21 là chân đèn nền | Dùng GPIO22, 27, 35 cho thiết bị ngoài |
| Nạp code báo lỗi kết nối | Dây USB chỉ sạc, thiếu driver CH340 | Đổi dây có dữ liệu, cài driver, giữ BOOT khi bắt đầu nạp nếu cần |
7. Bước tiếp theo
- Vẽ đồ thị dữ liệu cảm biến theo thời gian: xem bài màn hình TFT SPI ILI9341 với ESP32: vẽ chữ và đồ thị, cùng chip màn hình với CYD.
- Giao diện có nút, thanh trượt, biểu đồ đẹp hơn: dùng thư viện đồ họa LVGL trên nền TFT_eSPI (bài LVGL là gì cùng cụm chủ đề).
- Kết nối Wi-Fi để hiện thời tiết, giá, trạng thái thiết bị trong nhà: CYD là ESP32 đầy đủ, mọi chương trình Wi-Fi của ESP32 đều chạy được.
8. Xem thêm
- Màn hình TFT SPI ILI9341 với ESP32: nối dây, lệnh khởi động, vẽ chữ và đồ thị
- Cài ESP32 vào Arduino IDE 2: thêm bo mạch, chọn ESP32 Dev Module
- ESP32 CH340 hay CP2102: cài driver trên Windows
Bài cùng cụm chủ đề: cấu hình thư viện TFT_eSPI; LVGL là gì, giao diện cho màn hình ESP32; màn hình ST7789 với ESP32.
Xem thêm