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ì

Hình minh họa mặt sau bo ESP32-2432S028R: module ESP32-WROOM-32, USB CH340, khe thẻ microSD, LED RGB, cảm biến ánh sáng LDR, đầu cắm loa P4, nút BOOT và RST, đầu cắm P1 nguồn và UART, P3 GPIO35 22 21, CN1 GPIO22 27
Hình minh họa vị trí các khối, không theo tỉ lệ.
  • 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

Bảng sơ đồ chân ESP32-2432S028R: màn hình ILI9341 HSPI MOSI 13 MISO 12 SCK 14 CS 15 DC 2 đèn nền 21; cảm ứng XPT2046 CLK 25 MOSI 32 MISO 39 CS 33 IRQ 36; thẻ microSD 18 19 23 CS 5; LED RGB 4 16 17; LDR GPIO34; loa GPIO26; chân trống GPIO35 22 27
Gần như mọi chân của ESP32 đã dùng cho các khối trên bo.

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.

Sơ đồ hai bus SPI: màn hình ILI9341 trên HSPI chân 13 12 14 CS 15 DC 2, cảm ứng XPT2046 trên VSPI chân 25 39 32 CS 33, thẻ microSD chân 18 19 23 CS 5 chưa dùng
Màn hình và cảm ứng nằm trên hai bus SPI khác nhau.

Đ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

  1. 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.
  2. Library Manager: cài TFT_eSPI (Bodmer) và XPT2046_Touchscreen (Paul Stoffregen).
  3. 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 file User_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

Màn hình chương trình mẫu: tiêu đề PYWORLD.VN CYD màu vàng, ba nút DO, XANH LA đang bật màu xanh lá, XANH DUONG, thanh LDR màu xanh lơ, vùng vẽ phía dưới có nét chấm vàng
Bố cục theo đúng tọa độ trong chương trình.
/*
  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:

  1. Nạp chương trình, mở Serial Monitor ở 115200 baud.
  2. 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.
  3. Lấy số x nhỏ nhất, lớn nhất điền vào CHAM_X_MIN, CHAM_X_MAX; tương tự với y.
  4. 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ượngNguyên nhân thường gặpCách xử lý
Màn hình trắng, đèn nền sángTFT_eSPI chưa đúng cấu hình CYDKiể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ácGọ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ầnBo hai cổng USB dùng ST7789Đổi ILI9341_2_DRIVER thành ST7789_DRIVER
Chạm không có tác dụngDùng hàm cảm ứng của TFT_eSPI, hoặc sai chân bus cảm ứngDù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ácChưa hiệu chỉnh, khác hướng xoayLà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 P3GPIO21 là chân đèn nềnDùng GPIO22, 27, 35 cho thiết bị ngoài
Nạp code báo lỗi kết nốiDâ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

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.


Tin tức liên quan

Đèn LED nháy theo nhạc với ESP32: micro INMP441, FFT tự viết, dải WS2812B, bắt nhịp trống
Đèn LED nháy theo nhạc với ESP32: micro INMP441, FFT tự viết, dải WS2812B, bắt nhịp trống

Làm đèn LED nháy theo nhạc với ESP32: micro INMP441 thu tiếng, FFT 512 điểm chia 6 dải tần, bắt nhịp trống tính BPM, dải WS2812B với ba chế độ phổ, nhịp và VU.

Cảm biến lưu lượng nước YF-S201 với ESP32: nối dây 3,3 V, đếm xung, lít/phút, tổng lít, hiệu chuẩn
Cảm biến lưu lượng nước YF-S201 với ESP32: nối dây 3,3 V, đếm xung, lít/phút, tổng lít, hiệu chuẩn

Dùng cảm biến lưu lượng YF-S201 với ESP32: nguyên lý tua bin Hall, F = 7,5 × Q, nối dây an toàn 3,3 V bằng diode, đếm xung bằng ngắt, tính lít/phút, tổng lít lưu flash, hiệu chuẩn bằng xô đo.

GPS NEO-6M với ESP32: đọc câu NMEA, lấy tọa độ, giờ Việt Nam, tốc độ không cần thư viện
GPS NEO-6M với ESP32: đọc câu NMEA, lấy tọa độ, giờ Việt Nam, tốc độ không cần thư viện

GPS làm việc thế nào, module NEO-6M, nối với ESP32 qua UART2, đọc câu NMEA RMC và GGA, kiểm checksum, đổi tọa độ, giờ Việt Nam, tốc độ, tính khoảng cách, không cần TinyGPS.

Hướng dẫn dùng ngắt GPIO trên ESP32: đọc nút nhấn, chống dội và đếm xung
Hướng dẫn dùng ngắt GPIO trên ESP32: đọc nút nhấn, chống dội và đếm xung

Hướng dẫn dùng ngắt GPIO trên ESP32 với Arduino IDE: attachInterrupt, các kiểu kích ngắt, chống dội nút nhấn, đếm xung đo tần số và quy tắc viết hàm ngắt an toàn.

Cân IoT theo dõi bình gas bằng ESP32 và HX711: còn bao nhiêu kg, ước tính ngày hết, báo qua Telegram
Cân IoT theo dõi bình gas bằng ESP32 và HX711: còn bao nhiêu kg, ước tính ngày hết, báo qua Telegram

Làm cân IoT đặt dưới bình gas: ESP32 đọc load cell qua HX711, trừ vỏ bình ra số kg gas còn lại, hồi quy tính mức dùng mỗi ngày, ước tính ngày hết và nhắn Telegram khi sắp hết.

RFID RC522 với ESP32: đọc mã thẻ không cần thư viện, lưu thẻ hợp lệ, mở khóa bằng relay
RFID RC522 với ESP32: đọc mã thẻ không cần thư viện, lưu thẻ hợp lệ, mở khóa bằng relay

Nối RC522 với ESP32 qua SPI, hiểu REQA, chống va chạm, SELECT, HLTA, đọc UID 4 và 7 byte không cần thư viện, lưu thẻ bằng Preferences, đóng relay khi quẹt đúng thẻ.

Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp
Hướng dẫn đọc ADC trên ESP32 và đo điện áp pin 18650 bằng cầu phân áp

Hướng dẫn đọc điện áp analog bằng ADC của ESP32 trên Arduino IDE, dùng analogReadMilliVolts, làm cầu phân áp đo pin Li-ion 18650 và ước lượng phần trăm pin còn lại.

Hướng dẫn ESP-NOW trên ESP32: truyền dữ liệu giữa hai mạch không cần router Wi-Fi
Hướng dẫn ESP-NOW trên ESP32: truyền dữ liệu giữa hai mạch không cần router Wi-Fi

Hướng dẫn dùng ESP-NOW để hai mạch ESP32 truyền dữ liệu trực tiếp không cần router: xem địa chỉ MAC, chương trình gửi, chương trình nhận, đọc RSSI và xử lý lỗi thường gặp.


Bình luận
Đã thêm vào giỏ hàng