Files
thermostat-hw/main/ui/lvgl_port.c
T
2026-08-03 20:31:28 -07:00

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);
}