136 lines
3.5 KiB
C
136 lines
3.5 KiB
C
/*
|
|
* 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);
|
|
}
|