ESP32 + E-Paper Display: Màn hình tiết kiệm điện cho thiết bị IoT
Trung cấp1/8/2026- Tác giả: IoTlabs Maker

ESP32 + E-Paper Display: Màn hình tiết kiệm điện cho thiết bị IoT

Kết nối ESP32 DevKit với màn hình e-paper (e-ink) SPI 2.13" SSD1680 và hiển thị dữ liệu cập nhật định kỳ bằng thư viện GxEPD2, khai thác ưu điểm giữ hình ảnh vĩnh viễn không cần cấp điện liên tục — lý tưởng cho thiết bị IoT chạy pin.

ESP32E-PaperE-InkSSD1680SPITiet kiem dienLow Power

Màn hình e-paper (e-ink) có đặc tính khác biệt hoàn toàn so với LCD/OLED: chỉ tiêu thụ dòng điện đáng kể trong lúc vẽ/cập nhật hình ảnh (vài chục mA trong 1-2 giây), sau đó KHÔNG cần nguồn để giữ nội dung hiển thị, pixel vật lý giữ trạng thái vĩnh viễn cho tới lần cập nhật kế tiếp.

Đặc tính này khiến e-paper trở thành lựa chọn lý tưởng cho các thiết bị IoT chạy pin cần hiển thị dữ liệu ít thay đổi (nhãn giá điện tử, trạm đo môi trường ngoài trời, bảng thông báo). Bài viết hướng dẫn đấu nối ESP32 với e-paper SPI 2.13" (driver SSD1680) và viết chương trình cập nhật định kỳ bằng thư viện GxEPD2, kèm giải thích cách kết hợp deep sleep để tối đa hoá tuổi thọ pin.

Hướng dẫn chi tiết

Kết nối ESP32 DevKit với e-paper SPI 2.13" SSD1680, cập nhật dữ liệu định kỳ bằng GxEPD2, khai thác ưu điểm giữ hình không cần cấp điện liên tục.

1. Giới thiệu

Màn hình e-paper (e-ink) có đặc tính khác biệt hoàn toàn so với LCD/OLED: chỉ tiêu thụ dòng điện đáng kể trong lúc vẽ/cập nhật hình ảnh (vài chục mA trong 1-2 giây), sau đó KHÔNG cần nguồn để giữ nội dung hiển thị — pixel vật lý giữ trạng thái vĩnh viễn cho tới lần cập nhật kế tiếp. Đặc tính này khiến e-paper trở thành lựa chọn lý tưởng cho các thiết bị IoT chạy pin cần hiển thị dữ liệu ít thay đổi (nhãn giá điện tử, trạm đo môi trường ngoài trời, bảng thông báo). Bài viết hướng dẫn đấu nối ESP32 với e-paper SPI 2.13" (driver SSD1680) và viết chương trình cập nhật định kỳ bằng thư viện GxEPD2.

Ghi chú linh kiện: Module e-paper SPI 2.13" có sẵn trong IoTLabs Workspace pinout chuẩn theo các module e-paper SPI phổ biến trên thị trường: VCC, GND, DIN, CLK, CS, DC, RST, BUSY.

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đ

E-Paper 2.13" SPI (SSD1680)

1

~180.000 - 280.000đ

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

1 bộ

~15.000đ

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

E-Paper SSD1680

ESP32 DevKit

Ghi chú

VCC

3V3

Nguồn 3.3V

GND

GND

Mass chung

DIN

GPIO23

SPI MOSI (VSPI chuẩn ESP32)

CLK

GPIO18

SPI SCK

CS

GPIO5

Chip Select

DC

GPIO17

Data/Command

RST

GPIO16

Reset

BUSY

GPIO4

Chân input báo module đang bận xử lý cập nhật (chờ trước khi gửi lệnh mới)

An toàn điện áp: Toàn bộ module e-paper SPI phổ biến (Waveshare, Good Display) đều dùng 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 e-paper qua GxEPD2, vẽ màn hình trạng thái (điện áp pin mô phỏng + số lần cập nhật) ngay khi boot, sau đó cập nhật lại mỗi 60 giây (rút ngắn để dễ quan sát trong demo — triển khai thực tế nên đặt vài phút đến vài giờ).

#include <SPI.h>
#define ENABLE_GxEPD2_display 1
#include <GxEPD2_BW.h>

#define EPD_CS 5
#define EPD_DC 17
#define EPD_RST 16
#define EPD_BUSY 4

GxEPD2_BW<GxEPD2_213_B74, GxEPD2_213_B74::HEIGHT> display(
    GxEPD2_213_B74(EPD_CS, EPD_DC, EPD_RST, EPD_BUSY));

unsigned long lastUpdate = 0;
const unsigned long UPDATE_INTERVAL_MS = 60000; // cap nhat moi 60s (demo); thuc te co the vai phut/gio
bool epdOk = false;
int readingCount = 0;

float readBatteryVoltage() {
  // Demo: gia lap dien ap pin. Thay bang analogRead(ADC_PIN) * he so chia ap thuc te.
  return 3.85f - (float)(readingCount % 10) * 0.01f;
}

void renderStatusScreen(float batteryV, int count) {
  display.setRotation(1);
  display.setFont(nullptr);
  display.setTextColor(GxEPD_BLACK);
  display.firstPage();
  do {
    display.fillScreen(GxEPD_WHITE);
    display.setCursor(5, 15);
    display.print("IoTLabs - E-Paper");
    display.setCursor(5, 35);
    display.print("Power-save Node");
    display.setCursor(5, 60);
    display.print("Battery: ");
    display.print(batteryV, 2);
    display.print("V");
    display.setCursor(5, 80);
    display.print("Update #: ");
    display.print(count);
  } while (display.nextPage());
}

void setup() {
  Serial.begin(115200);
  delay(200);
  Serial.println(F("[BOOT] ESP32 E-Paper power-save demo starting..."));

  display.init(115200, true, 2, false);
  epdOk = true;
  Serial.println(F("[OK] E-Paper SSD1680 khoi tao thanh cong."));

  float v = readBatteryVoltage();
  readingCount++;
  renderStatusScreen(v, readingCount);
  Serial.printf("[DATA] battery=%.2fV update=%d (man hinh giu hinh khong can dien)\n", v, readingCount);
}

void loop() {
  unsigned long now = millis();
  if (now - lastUpdate >= UPDATE_INTERVAL_MS) {
    lastUpdate = now;

    if (epdOk) {
      float v = readBatteryVoltage();
      readingCount++;
      renderStatusScreen(v, readingCount);
      Serial.printf("[DATA] battery=%.2fV update=%d\n", v, readingCount);
    } else {
      Serial.println(F("[WARN] E-Paper khong san sang, chi in Serial."));
    }
    // Trien khai thuc te: goi esp_deep_sleep(UPDATE_INTERVAL_MS * 1000ULL) o day
    // de tat toan bo MCU giua 2 lan cap nhat, chi e-paper "giu" hinh anh.
  }
}

Kết quả compile: Sketch uses 425232 bytes (32%) of program storage space. Global variables use 27708 bytes (8%) of dynamic memory. FQBN: esp32:esp32:esp32.

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

  • GxEPD2_BW<GxEPD2_213_B74, ...> — chọn đúng class driver ứng với kích thước/chip điều khiển thật của module (SSD1680, 2.13", 122x250px); GxEPD2 hỗ trợ hàng chục loại e-paper khác nhau qua các class tương tự.

  • display.firstPage()/nextPage() — mô hình vẽ "paged" đặc trưng của GxEPD2: toàn bộ nội dung được vẽ lại trong vòng lặp do-while để tối ưu RAM khi framebuffer không đủ chứa toàn màn hình cùng lúc.

  • Chân EPD_BUSY được thư viện tự động đọc để chờ module hoàn tất chu trình cập nhật vật lý (thường 1-2 giây với chế độ full refresh) trước khi cho phép lệnh vẽ tiếp theo.

  • Đoạn comment cuối loop() gợi ý dùng esp_deep_sleep() — đây là bước tối ưu quan trọng nhất cho ứng dụng thực tế: ESP32 tắt hoàn toàn giữa các lần cập nhật, chỉ e-paper "nhớ" hình ảnh, giúp thiết bị chạy pin CR2032/18650 trong nhiều tháng.

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

Có thể mở rộng bằng cách: (1) thay readBatteryVoltage() giả lập bằng analogRead() qua mạch chia áp đọc pin thật; (2) thêm esp_sleep_enable_timer_wakeup() + esp_deep_sleep_start() để MCU ngủ sâu giữa các lần cập nhật (giảm dòng tiêu thụ từ ~80mA xuống ~10µA khi idle); (3) dùng display.setPartialWindow() để cập nhật một phần màn hình nhanh hơn (partial refresh) thay vì full refresh mỗi lần.

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

Lỗi

Nguyên nhân

Cách sửa

Màn hình không đổi nội dung, chỉ nhấp nháy đen trắng

Bình thường — đây là hiệu ứng "flash" đặc trưng của full refresh e-paper để xoá bóng ma (ghosting)

Không phải lỗi; nếu muốn tránh nhấp nháy dùng partial refresh cho các cập nhật nhỏ

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

Chưa cài thư viện GxEPD2

arduino-cli lib install "GxEPD2"

Chương trình treo ở display.init()

Sai chân BUSY hoặc RST, module không phản hồi

Kiểm tra lại 4 chân điều khiển (CS/DC/RST/BUSY), đảm bảo đúng thứ tự tham số khởi tạo class

8. Tổng kết

E-paper là lựa chọn hiển thị tiết kiệm điện bậc nhất cho thiết bị IoT chạy pin nhờ đặc tính giữ hình ảnh không cần nguồn nuôi liên tục.

Bài viết đã trình bày cách đấu nối ESP32 với e-paper SPI SSD1680, kèm hướng dẫn kết hợp deep sleep để tối đa hoá tuổi thọ pin trong triển khai thực tế.