ArduinoVN
Đăng nhập Tham gia
Thư viện Code /u/linucat /27/04/2026

Thư viện LVGL: UI đẹp như smartphone trên TFT ESP32

Thảo luận

LVGL (Light and Versatile Graphics Library) là engine UI hàng đầu cho embedded — render giao diện đẹp như mobile app trên TFT ESP32. Có button, slider, chart, list scroll mượt, theme đen/sáng, anti-aliasing. Bài này hướng dẫn cài, setup, và tạo dashboard đầu tiên.

1. LVGL là gì?

LVGL là graphics framework open-source (MIT) viết bằng C — chạy trên bất kỳ MCU nào có 64KB+ RAM. ESP32 (320KB RAM) dư sức. Tính năng:

  • 30+ widget có sẵn: button, slider, switch, chart, gauge, calendar, keyboard.
  • Anti-aliased rendering — chữ và border mượt.
  • Animation engine — fade, slide, zoom.
  • Theme đen/sáng, customize màu.
  • Touch input handling.
  • Layout flex/grid như CSS.

2. So với Adafruit_GFX và TFT_eSPI

Tiêu chíGFX/eSPILVGL
Mức độVẽ pixel/shapeWidget UI cấp cao
Setup5 phút1-2 giờ
RAM~5KB~40KB+
ĐẹpBasicSmartphone-level
Touch handlingTự codeBuilt-in
AnimationTự codeBuilt-in

Quy tắc: project hiển thị đơn giản (3 chỉ số sensor) → eSPI. Project UI phức tạp (menu, settings, dashboard nhiều page) → LVGL.

3. Cài đặt

Library Manager → "lvgl" by kisvegabor. Cài cùng TFT_eSPI (driver TFT cho LVGL).

Setup phức tạp hơn so với thư viện khác — phải sửa nhiều file. Khuyến nghị: dùng PlatformIO để dễ quản lý dependency và config.

4. Cấu hình TFT_eSPI

Vào thư viện TFT_eSPI/User_Setup.h, sửa:

#define ILI9341_DRIVER
#define TFT_WIDTH  240
#define TFT_HEIGHT 320

#define TFT_MOSI 23
#define TFT_SCLK 18
#define TFT_CS   15
#define TFT_DC   2
#define TFT_RST  4

#define LOAD_GLCD
#define SMOOTH_FONT
#define SPI_FREQUENCY 40000000

5. Cấu hình LVGL — lv_conf.h

Copy lv_conf_template.h trong thư mục library → lv_conf.h đặt cạnh thư mục lvgl. Enable:

#define LV_CONF_INCLUDE_SIMPLE
#define LV_COLOR_DEPTH 16
#define LV_TICK_CUSTOM 1
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h"
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_24 1
#define LV_USE_PERF_MONITOR 1

6. Setup cơ bản — hello world

#include 
#include 

TFT_eSPI tft;
static lv_disp_draw_buf_t draw_buf;
static lv_color_t buf[320 * 10];   // buffer 10 dòng

void my_disp_flush(lv_disp_drv_t* disp, const lv_area_t* area, lv_color_t* color_p) {
  uint32_t w = (area->x2 - area->x1 + 1);
  uint32_t h = (area->y2 - area->y1 + 1);
  tft.startWrite();
  tft.setAddrWindow(area->x1, area->y1, w, h);
  tft.pushColors((uint16_t*)&color_p->full, w * h, true);
  tft.endWrite();
  lv_disp_flush_ready(disp);
}

void setup() {
  Serial.begin(115200);
  tft.begin();
  tft.setRotation(1);
  lv_init();
  lv_disp_draw_buf_init(&draw_buf, buf, NULL, 320 * 10);

  static lv_disp_drv_t disp_drv;
  lv_disp_drv_init(&disp_drv);
  disp_drv.hor_res = 320;
  disp_drv.ver_res = 240;
  disp_drv.flush_cb = my_disp_flush;
  disp_drv.draw_buf = &draw_buf;
  lv_disp_drv_register(&disp_drv);

  // Tao label
  lv_obj_t* label = lv_label_create(lv_scr_act());
  lv_label_set_text(label, "Hello LVGL");
  lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

void loop() {
  lv_timer_handler();
  delay(5);
}

7. Widget — button có callback

static void btn_event_cb(lv_event_t* e) {
  Serial.println("Clicked!");
}

lv_obj_t* btn = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn, 120, 50);
lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0);
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL);

lv_obj_t* btn_label = lv_label_create(btn);
lv_label_set_text(btn_label, "Bam vao");
lv_obj_center(btn_label);

8. Slider + chart real-time

// Slider
lv_obj_t* slider = lv_slider_create(lv_scr_act());
lv_obj_set_width(slider, 200);
lv_obj_align(slider, LV_ALIGN_TOP_MID, 0, 30);
lv_slider_set_range(slider, 0, 100);
lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);

// Chart
lv_obj_t* chart = lv_chart_create(lv_scr_act());
lv_obj_set_size(chart, 300, 150);
lv_obj_align(chart, LV_ALIGN_BOTTOM_MID, 0, -20);
lv_chart_set_type(chart, LV_CHART_TYPE_LINE);
lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, 0, 100);
lv_chart_series_t* ser = lv_chart_add_series(chart, lv_palette_main(LV_PALETTE_BLUE), LV_CHART_AXIS_PRIMARY_Y);

// Update mỗi giây
for (uint16_t i = 0; i < 10; i++) {
  lv_chart_set_next_value(chart, ser, random(0, 100));
  delay(500);
  lv_timer_handler();
}

9. Theme và styling

// Theme tối
lv_theme_t* theme = lv_theme_default_init(NULL, lv_palette_main(LV_PALETTE_BLUE),
  lv_palette_main(LV_PALETTE_PINK), true /*dark*/, LV_FONT_DEFAULT);
lv_disp_set_theme(NULL, theme);

// Style custom
static lv_style_t style_btn;
lv_style_init(&style_btn);
lv_style_set_bg_color(&style_btn, lv_color_hex(0xff5722));
lv_style_set_radius(&style_btn, 20);
lv_obj_add_style(btn, &style_btn, 0);

10. Layout flex/grid

// Flex container
lv_obj_t* cont = lv_obj_create(lv_scr_act());
lv_obj_set_size(cont, 300, 200);
lv_obj_set_flex_flow(cont, LV_FLEX_FLOW_ROW_WRAP);
lv_obj_set_flex_align(cont, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);

// Thêm 6 button - tự xếp ngang theo wrap
for (int i = 0; i < 6; i++) {
  lv_obj_t* b = lv_btn_create(cont);
  lv_obj_set_size(b, 60, 60);
}

Cú pháp giống CSS flex — không phải tính toạ độ thủ công.

11. Hiệu năng

  • ESP32 + ILI9341 SPI 40MHz: ~30 FPS với LVGL.
  • ESP32-S3 + ILI9488 RGB parallel: 60 FPS.
  • Buffer lớn (160 dòng) → ít flush, smooth hơn nhưng tốn RAM.
  • Enable PSRAM (ESP32-WROVER) cho UI phức tạp.

12. Ứng dụng

  • Smart home control panel.
  • Đồng hồ digital UI đẹp.
  • Weather station với chart history.
  • Settings UI cho thiết bị IoT.
  • POS terminal mini.
  • Game đơn giản (Tetris, Snake).

Liên quan

Đọc TFT_eSPI: thư viện TFT tốc độ cao để hiểu driver bên dưới. Áp dụng vào project Smart mirror Raspberry Pi hoặc smart home controller riêng.

Thảo luận (0)

Đăng nhập để tham gia thảo luận.
Chưa có bình luận nào. Hãy là người đầu tiên!