ESP32 + Màn hình ILI9341 / ST7789 TFT: Vẽ đồ thị realtime từ cảm biến
Trung cấp1/8/2026- Tác giả: IoTlabs Maker

ESP32 + Màn hình ILI9341 / ST7789 TFT: Vẽ đồ thị realtime từ cảm biến

Kết nối ESP32 DevKit với màn hình TFT màu SPI để vẽ đồ thị đường (line graph) cập nhật realtime từ dữ liệu cảm biến, dùng thư viện Adafruit_GFX.

ESP32TFTILI9341SPIDisplayDo thi realtime

Vẽ đồ thị đường (line graph) realtime trên màn hình TFT màu SPI là kỹ thuật hữu ích để trực quan hoá xu hướng dữ liệu cảm biến (nhiệt độ, độ ẩm, ánh sáng...) ngay trên thiết bị mà không cần dashboard ngoài.

Bài viết hướng dẫn đấu nối ESP32 DevKit với TFT SPI màu và viết chương trình lấy mẫu dữ liệu định kỳ, vẽ từng điểm nối tiếp thành đường đồ thị cuộn (scrolling graph).

Ghi chú thay thế linh kiện:

  • Code trong bài này dùng ILI9341 240x320 vì cùng chuẩn giao tiếp SPI với bộ chân tương đương (VCC, GND, CLK, MOSI, MISO, CS, DC, RST, BL)

  • Sơ đồ đấu nối và logic mạch giữ nguyên, chỉ khác thư viện driver (Adafruit_ILI9341 thay vì Adafruit_ST7789).

Hướng dẫn chi tiết

Kết nối ESP32 DevKit với TFT 2.4" SPI ILI9341 (thay thế ST7789 do hardware_catalog chưa có) và vẽ đồ thị cảm biến realtime bằng Adafruit_GFX.

1. Giới thiệu

Vẽ đồ thị đường (line graph) realtime trên màn hình TFT màu SPI là kỹ thuật hữu ích để trực quan hoá xu hướng dữ liệu cảm biến (nhiệt độ, độ ẩm, ánh sáng...) ngay trên thiết bị mà không cần dashboard ngoài. Bài viết hướng dẫn đấu nối ESP32 DevKit với TFT SPI màu và viết chương trình lấy mẫu dữ liệu định kỳ, vẽ từng điểm nối tiếp thành đường đồ thị cuộn (scrolling graph).

Ghi chú thay thế linh kiện (quan trọng): Bài viết gốc dự kiến dùng màn TFT ST7789, nhưng bài viết này dùng ILI9341 240x320 vì cùng chuẩn giao tiếp SPI với bộ chân tương đương (VCC, GND, CLK, MOSI, MISO, CS, DC, RST, BL), sơ đồ đấu nối và logic mạch giữ nguyên, chỉ khác thư viện driver (Adafruit_ILI9341 thay vì Adafruit_ST7789). Nếu bạn có sẵn màn ST7789 thật, chỉ cần đổi thư viện và class khởi tạo, chân cắm vẫn tương thích.

2. Linh kiện cần có

Linh kiện

Số lượng

Giá tham khảo

ESP32 DevKit V4 (30/38 pin)

1

~75.000 - 120.000đ

TFT 2.4" 240x320 SPI (ILI9341)

1

~90.000 - 150.000đ

Dây jumper cái-cái (9 sợi)

1 bộ

~15.000đ

3. Sơ đồ kết nối

TFT SPI dùng VSPI hardware của ESP32 (SCK=GPIO18, MOSI=GPIO23, MISO=GPIO19) cộng thêm 3 chân điều khiển GPIO thường (CS, DC, RST) và 1 chân backlight.

TFT ILI9341

ESP32 DevKit

Ghi chú

VCC

3V3

Nguồn 3.3V — TFT màu SPI luôn dùng logic 3.3V, KHÔNG cấp 5V

GND

GND

Mass chung

CLK

GPIO18

VSPI SCK (chuẩn ESP32)

MOSI

GPIO23

VSPI MOSI

MISO

GPIO19

VSPI MISO (đọc ID, không bắt buộc khi chỉ vẽ)

CS

GPIO5

Chip Select

DC

GPIO2

Data/Command

RST

GPIO4

Reset

BL

GPIO15

Backlight — điều khiển bằng GPIO, giữ HIGH liên tục trong code

An toàn điện áp: Module TFT SPI màu (ILI9341/ST7789/ST7735) hầu như luôn có mạch logic 3.3V on-board, cấp thẳng 3V3 từ ESP32 là đúng chuẩn — không cần level shifter.

4. Code mẫu đầy đủ

Chương trình khởi tạo TFT, bật backlight qua GPIO15, sau đó mỗi 300ms lấy 1 mẫu dữ liệu cảm biến (mô phỏng bằng hàm sin biến thiên theo thời gian) và vẽ nối điểm liên tiếp thành đường đồ thị; khi đồ thị đầy chiều rộng màn hình, khung được xoá và vẽ lại từ đầu (cuộn theo khung).

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

#define TFT_CS 5
#define TFT_DC 2
#define TFT_RST 4
#define TFT_BL 15

Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST);

const int GRAPH_X = 0;
const int GRAPH_Y = 40;
const int GRAPH_W = 240;
const int GRAPH_H = 180;
const int MAX_POINTS = GRAPH_W;

int16_t history[MAX_POINTS];
int historyCount = 0;
unsigned long lastSample = 0;
const unsigned long SAMPLE_INTERVAL_MS = 300;
bool tftOk = false;

float readSensorValue() {
  // Demo: gia lap tin hieu cam bien (vd ADC/nhiet do). Thay bang analogRead(pin)
  // hoac doc cam bien that (vd LM35, NTC) khi trien khai thuc te.
  float t = millis() / 1000.0f;
  float simulated = 50.0f + 30.0f * sinf(t * 0.7f) + (float)((millis() % 7) - 3);
  return simulated;
}

int valueToY(float v) {
  float vMin = 0, vMax = 100;
  if (v < vMin) v = vMin;
  if (v > vMax) v = vMax;
  int y = GRAPH_Y + GRAPH_H - (int)((v - vMin) / (vMax - vMin) * GRAPH_H);
  return y;
}

void drawGraphFrame() {
  tft.fillRect(GRAPH_X, GRAPH_Y, GRAPH_W, GRAPH_H, ILI9341_BLACK);
  tft.drawRect(GRAPH_X, GRAPH_Y, GRAPH_W, GRAPH_H, ILI9341_WHITE);
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println(F("[BOOT] ESP32 TFT ILI9341 realtime graph demo starting..."));

  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH); // bat den nen, giu sang lien tuc

  tft.begin();
  uint16_t id = tft.readcommand8(ILI9341_RDMODE);
  tftOk = true; // tft.begin() khong tra ve trang thai loi; danh dau san sang de ve.

  tft.setRotation(1);
  tft.fillScreen(ILI9341_BLACK);
  tft.setTextColor(ILI9341_WHITE);
  tft.setTextSize(2);
  tft.setCursor(0, 5);
  tft.println("IoTLabs - Realtime Graph");
  drawGraphFrame();

  Serial.println(F("[OK] TFT ILI9341 khoi tao xong, bat dau ve do thi."));
}

void loop() {
  unsigned long now = millis();
  if (now - lastSample >= SAMPLE_INTERVAL_MS) {
    lastSample = now;
    float v = readSensorValue();
    Serial.printf("[DATA] sensor=%.2f\n", v);

    if (tftOk) {
      if (historyCount >= MAX_POINTS) {
        // Cuon trai: xoa va ve lai khung khi day man hinh
        drawGraphFrame();
        historyCount = 0;
      }

      int x = GRAPH_X + historyCount;
      int y = valueToY(v);
      tft.drawPixel(x, y, ILI9341_GREEN);
      if (historyCount > 0) {
        int xPrev = GRAPH_X + historyCount - 1;
        int yPrev = valueToY(history[historyCount - 1]);
        tft.drawLine(xPrev, yPrev, x, y, ILI9341_GREEN);
      }

      history[historyCount] = (int16_t)v;
      historyCount++;
    } else {
      Serial.println(F("[WARN] TFT khong san sang, chi in Serial."));
    }
  }
}

Kết quả compile: Sketch uses 343772 bytes (26%) of program storage space. Global variables use 24188 bytes (7%) of dynamic memory. FQBN: esp32:esp32:esp32.

5. Giải thích code chi tiết

  • tft.begin() khởi tạo giao tiếp SPI + gửi chuỗi lệnh cấu hình chuẩn cho driver ILI9341; không trả về mã lỗi nên chương trình đánh dấu tftOk = true ngay sau khi gọi (khác OLED I2C có ACK để kiểm tra).

  • valueToY() ánh xạ giá trị cảm biến (0-100) sang toạ độ Y trong vùng vẽ GRAPH_H pixel — đảo chiều vì toạ độ màn hình tăng xuống dưới trong khi giá trị cao nên vẽ lên trên.

  • tft.drawLine() nối điểm hiện tại với điểm trước đó để tạo đường liền thay vì các chấm rời rạc.

  • Khi historyCount chạm MAX_POINTS (hết chiều rộng khung đồ thị), chương trình xoá và vẽ lại khung — mô phỏng hiệu ứng "cuộn" đơn giản không cần dịch chuyển toàn bộ pixel.

6. Ứng dụng thực tế / hướng mở rộng

Có thể mở rộng bằng cách: (1) thay hàm giả lập bằng analogRead() đọc cảm biến ánh sáng/nhiệt độ thật; (2) dùng thư viện TFT_eSPI (cấu hình qua User_Setup.h) để tăng tốc độ vẽ đáng kể so với Adafruit_GFX; (3) thêm trục số và lưới toạ độ để dễ đọc giá trị; (4) nếu có màn ST7789 thật, chỉ cần đổi Adafruit_ILI9341 thành Adafruit_ST7789 và class khởi tạo tương ứng — toàn bộ logic vẽ đồ thị giữ nguyên.

7. Lỗi thường gặp

Lỗi

Nguyên nhân

Cách sửa

Màn hình trắng/không hiện gì dù cấp nguồn đúng

Sai chân CS/DC/RST hoặc thiếu digitalWrite(TFT_BL, HIGH)

Kiểm tra lại 3 chân điều khiển và đảm bảo backlight đã được bật

Compile lỗi "Adafruit_ILI9341.h: No such file"

Chưa cài thư viện

arduino-cli lib install "Adafruit ILI9341"

Hình ảnh bị lật/xoay sai hướng

Giá trị setRotation() không khớp hướng lắp module

Thử các giá trị 0-3 cho tft.setRotation() tới khi đúng hướng

Đồ thị vẽ chậm, giật

Adafruit_GFX vẽ từng pixel qua SPI tốc độ mặc định

Chuyển sang thư viện TFT_eSPI (SPI tốc độ cao hơn, hỗ trợ DMA)

8. Tổng kết

TFT màu SPI (ILI9341, tương thích chân với ST7789/ST7735) cho phép ESP32 vẽ đồ thị dữ liệu cảm biến realtime ngay trên thiết bị — hữu ích cho các trạm quan trắc di động không cần kết nối dashboard ngoài.