[ESP32] Hiệu chỉnh LVGL để chạy đa nhân trên ESP32 và FreeRTOS
Để hiệu chỉnh chương trình để chạy LVGL trên nhân số 1 của ESP32 với FreeRTOS, chúng ta cần thực hiện một số thay đổi. Những thay đổi này sẽ cho phép LVGL được xử lý trong một task riêng biệt, trong khi loop chính vẫn có thể thực hiện các tác vụ khác. Mutex được sử dụng để đảm bảo rằng không có xung đột khi truy cập vào LVGL từ các task khác nhau. Dưới đây là các bước và mã lệnh cần điều chỉnh:
1. Đầu tiên, thêm thư viện FreeRTOS:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
2. Tạo một task mới để chạy LVGL:
void lvgl_task(void *pvParameters) {
while (1) {
lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(5));
}
}
3. Thay đổi hàm setup()
:
void setup() {
initSerial();
initDisplay();
initLVGL();
xTaskCreatePinnedToCore(
lvgl_task, // Task function
"LVGL Task", // Task name
4096, // Stack size
NULL, // Task parameters
1, // Task priority
NULL, // Task handle
1 // Core where the task should run (1 for core 1)
);
Serial.println("Setup done");
}
4. Sửa đổi hàm loop()
:
void loop() {
// Không cần gọi lv_timer_handler() ở đây nữa
updateDisplay();
delay(5);
}
5. Thêm một mutex để bảo vệ truy cập vào LVGL:
SemaphoreHandle_t lvgl_mutex;
// Trong hàm initLVGL(), thêm:
lvgl_mutex = xSemaphoreCreateMutex();
// Trong lvgl_task():
void lvgl_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY) == pdTRUE) {
lv_timer_handler();
xSemaphoreGive(lvgl_mutex);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
// Trong updateDisplay():
void updateDisplay() {
if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY) == pdTRUE) {
#ifdef DIRECT_MODE
#if defined(CANVAS) || defined(RGB_PANEL)
gfx->flush();
#else
drawBitmap();
#endif
#else
#ifdef CANVAS
gfx->flush();
#endif
#endif
xSemaphoreGive(lvgl_mutex);
}
}
Mình sẽ giải thích chi tiết hơn về cơ chế bảo vệ với Mutex bên như sau:
Mutex (Mutual Exclusion) là một cơ chế đồng bộ hóa được sử dụng trong lập trình đa luồng để ngăn chặn việc truy cập đồng thời vào tài nguyên chia sẻ. Trong trường hợp của chúng ta, tài nguyên chia sẻ là thư viện LVGL và các hoạt động liên quan đến giao diện người dùng.
Tại sao cần Mutex:
- LVGL không được thiết kế để an toàn cho đa luồng. Điều này có nghĩa là nếu nhiều task cùng truy cập vào LVGL đồng thời, có thể xảy ra tình trạng race condition, dẫn đến lỗi không mong muốn hoặc crash.
- Với FreeRTOS và việc chạy LVGL trên một core riêng, chúng ta cần đảm bảo rằng chỉ có một task được phép truy cập vào LVGL tại một thời điểm.
Cách Mutex hoạt động:
- Khi một task muốn truy cập vào LVGL, nó phải "lấy" (take) Mutex trước.
- Nếu Mutex đang khả dụng (không bị task khác giữ), task sẽ lấy được Mutex và có thể tiếp tục thực hiện các hoạt động liên quan đến LVGL.
- Nếu Mutex đang bị giữ bởi task khác, task hiện tại sẽ bị block cho đến khi Mutex được giải phóng.
- Sau khi hoàn thành công việc với LVGL, task phải "trả" (give) Mutex để cho phép các task khác có thể sử dụng LVGL.
Triển khai trong code:
a. Khởi tạo Mutex:
SemaphoreHandle_t lvgl_mutex;
lvgl_mutex = xSemaphoreCreateMutex();
b. Sử dụng Mutex trong lvgl_task:
void lvgl_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY) == pdTRUE) {
lv_timer_handler();
xSemaphoreGive(lvgl_mutex);
}
vTaskDelay(pdMS_TO_TICKS(5));
}
}
c. Sử dụng Mutex trong các callback:
void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY) == pdTRUE) {
// Existing flush code here
xSemaphoreGive(lvgl_mutex);
}
lv_disp_flush_ready(disp_drv);
}
Lưu ý quan trọng:
portMAX_DELAY
được sử dụng để chỉ định rằng task sẽ đợi vô thời hạn nếu Mutex không khả dụng. Trong một số trường hợp, bạn có thể muốn sử dụng một timeout cụ thể.- Luôn đảm bảo rằng mỗi lần
xSemaphoreTake
thành công đều có mộtxSemaphoreGive
tương ứng để tránh deadlock. - Cần cẩn thận để không gọi bất kỳ hàm LVGL nào bên ngoài các khối được bảo vệ bởi Mutex.
Ưu điểm của việc sử dụng Mutex:
- Đảm bảo tính nhất quán của dữ liệu LVGL.
- Ngăn chặn các lỗi khó debug do race conditions.
- Cho phép sử dụng LVGL an toàn trong môi trường đa nhiệm.
Nhược điểm cần lưu ý:
- Có thể gây ra một chút overhead do cơ chế đồng bộ hóa.
- Nếu không cẩn thận, có thể dẫn đến deadlock (ví dụ: nếu quên giải phóng Mutex).
Bằng cách sử dụng Mutex, chúng ta đảm bảo rằng LVGL có thể hoạt động an toàn trong môi trường đa nhiệm của FreeRTOS, cho phép chúng ta tận dụng hiệu quả cả hai nhân của ESP32 mà không gây ra xung đột dữ liệu.
6. Đảm bảo rằng các callback của LVGL (như my_disp_flush
và my_touchpad_read
) cũng được bảo vệ bằng mutex:
void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p) {
if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY) == pdTRUE) {
// Existing flush code here
xSemaphoreGive(lvgl_mutex);
}
lv_disp_flush_ready(disp_drv);
}
void my_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) {
if (xSemaphoreTake(lvgl_mutex, portMAX_DELAY) == pdTRUE) {
// Existing touchpad read code here
xSemaphoreGive(lvgl_mutex);
}
}
Lưu ý rằng bạn có thể cần điều chỉnh kích thước ngăn xếp (stack size) và ưu tiên của task tùy thuộc vào yêu cầu cụ thể của ứng dụng của bạn.