/* * Minimal LVGL 9 port layer: tick source, service task, and a recursive lock. * * LVGL needs three things the application must supply: a millisecond tick, a * periodic call to lv_timer_handler(), and mutual exclusion around its API. */ #include "esp_check.h" #include "esp_log.h" #include "esp_timer.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "lvgl.h" #include "lvgl_port.h" static const char *TAG = "lvgl_port"; #define LVGL_TICK_PERIOD_MS 2 #define LVGL_TASK_STACK_SIZE 8192 #define LVGL_TASK_PRIORITY 4 #define LVGL_TASK_MAX_DELAY_MS 500 #define LVGL_TASK_MIN_DELAY_MS 1 static SemaphoreHandle_t lvgl_mutex = NULL; static esp_timer_handle_t tick_timer = NULL; static volatile bool paused = false; static void lvgl_tick_cb(void *arg) { lv_tick_inc(LVGL_TICK_PERIOD_MS); } static void lvgl_task(void *arg) { ESP_LOGI(TAG, "LVGL task started"); while (1) { uint32_t delay_ms = LVGL_TASK_MAX_DELAY_MS; if (paused) { /* Leave the framebuffer untouched -- the panel keeps scanning it * out, so the last frame stays on screen. */ vTaskDelay(pdMS_TO_TICKS(20)); continue; } if (lvgl_port_lock(-1)) { delay_ms = lv_timer_handler(); lvgl_port_unlock(); } if (delay_ms > LVGL_TASK_MAX_DELAY_MS) { delay_ms = LVGL_TASK_MAX_DELAY_MS; } else if (delay_ms < LVGL_TASK_MIN_DELAY_MS) { delay_ms = LVGL_TASK_MIN_DELAY_MS; } vTaskDelay(pdMS_TO_TICKS(delay_ms)); } } esp_err_t lvgl_port_init(void) { /* Recursive: lv_timer_handler() runs callbacks that may re-enter code * which also takes the lock. */ lvgl_mutex = xSemaphoreCreateRecursiveMutex(); ESP_RETURN_ON_FALSE(lvgl_mutex != NULL, ESP_ERR_NO_MEM, TAG, "mutex alloc failed"); lv_init(); ESP_LOGI(TAG, "LVGL %d.%d.%d initialized", lv_version_major(), lv_version_minor(), lv_version_patch()); return ESP_OK; } esp_err_t lvgl_port_start(void) { const esp_timer_create_args_t timer_args = { .callback = lvgl_tick_cb, .name = "lvgl_tick", }; ESP_RETURN_ON_ERROR(esp_timer_create(&timer_args, &tick_timer), TAG, "tick timer create failed"); ESP_RETURN_ON_ERROR(esp_timer_start_periodic(tick_timer, LVGL_TICK_PERIOD_MS * 1000), TAG, "tick timer start failed"); const BaseType_t ok = xTaskCreate(lvgl_task, "lvgl", LVGL_TASK_STACK_SIZE, NULL, LVGL_TASK_PRIORITY, NULL); ESP_RETURN_ON_FALSE(ok == pdPASS, ESP_ERR_NO_MEM, TAG, "LVGL task create failed"); return ESP_OK; } void lvgl_port_pause(void) { if (paused) { return; } /* * Take the lock before setting the flag so we cannot stop the task * mid-render, then release it -- the flag alone keeps the task idle and * callers stay free to mutate widgets while paused. */ if (lvgl_port_lock(1000)) { paused = true; lvgl_port_unlock(); } else { ESP_LOGW(TAG, "pause: lock timed out, pausing anyway"); paused = true; } } void lvgl_port_resume(void) { paused = false; } bool lvgl_port_is_paused(void) { return paused; } bool lvgl_port_lock(int timeout_ms) { const TickType_t ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); return xSemaphoreTakeRecursive(lvgl_mutex, ticks) == pdTRUE; } void lvgl_port_unlock(void) { xSemaphoreGiveRecursive(lvgl_mutex); }