Refactor LVGL code into kernel module (#472)
* **New Features** * Added a standalone LVGL module and enabled LVGL support in the simulator for richer local UI testing. * **Refactor** * HAL and LVGL split into distinct modules; startup and device attach/detach flows reorganized for clearer lifecycle management. * Public APIs tightened with clearer nullability/documentation. * **Bug Fixes** * More consistent LVGL start/stop and device attach/detach behavior for improved stability.
This commit is contained in:
committed by
GitHub
parent
3fe1dc0312
commit
9f721e6655
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
#include <esp_lvgl_port.h>
|
||||
|
||||
#include <tactility/time.h>
|
||||
#include <tactility/error.h>
|
||||
#include <tactility/lvgl_module.h>
|
||||
|
||||
extern struct LvglModuleConfig lvgl_module_config;
|
||||
|
||||
static bool initialized = false;
|
||||
|
||||
bool lvgl_lock(void) {
|
||||
if (!initialized) return false;
|
||||
return lvgl_port_lock(portMAX_DELAY);
|
||||
}
|
||||
|
||||
bool lvgl_try_lock_timed(uint32_t timeout) {
|
||||
if (!initialized) return false;
|
||||
return lvgl_port_lock(millis_to_ticks(timeout));
|
||||
}
|
||||
|
||||
void lvgl_unlock(void) {
|
||||
if (!initialized) return;
|
||||
lvgl_port_unlock();
|
||||
}
|
||||
|
||||
error_t lvgl_arch_start() {
|
||||
const lvgl_port_cfg_t lvgl_cfg = {
|
||||
.task_priority = lvgl_module_config.task_priority,
|
||||
.task_stack = lvgl_module_config.task_stack_size,
|
||||
.task_affinity = lvgl_module_config.task_affinity,
|
||||
.task_max_sleep_ms = 500,
|
||||
.timer_period_ms = 5
|
||||
};
|
||||
|
||||
if (lvgl_port_init(&lvgl_cfg) != ESP_OK) {
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
// We must have the state set to "initialized" so that the lvgl lock works
|
||||
// when we call listener functions. These functions could create new
|
||||
// devices and services. The latter might start adding widgets immediately.
|
||||
initialized = true;
|
||||
|
||||
if (lvgl_module_config.on_start) lvgl_module_config.on_start();
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t lvgl_arch_stop() {
|
||||
if (lvgl_module_config.on_stop) lvgl_module_config.on_stop();
|
||||
|
||||
if (lvgl_port_deinit() != ESP_OK) {
|
||||
// Call on_start again to recover
|
||||
if (lvgl_module_config.on_start) lvgl_module_config.on_start();
|
||||
return ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
initialized = false;
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,148 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
#ifndef ESP_PLATFORM
|
||||
|
||||
#include <tactility/check.h>
|
||||
#include <tactility/delay.h>
|
||||
#include <tactility/concurrent/recursive_mutex.h>
|
||||
#include <tactility/concurrent/thread.h>
|
||||
#include <tactility/freertos/task.h>
|
||||
#include <tactility/time.h>
|
||||
|
||||
#include <lvgl.h>
|
||||
|
||||
#include "tactility/lvgl_module.h"
|
||||
|
||||
extern struct LvglModuleConfig lvgl_module_config;
|
||||
|
||||
// Mutex for LVGL drawing
|
||||
static struct RecursiveMutex lvgl_mutex;
|
||||
static bool lvgl_mutex_initialised = false;
|
||||
static struct RecursiveMutex task_mutex;
|
||||
static bool task_mutex_initialised = false;
|
||||
|
||||
static uint32_t task_max_sleep_ms = 10;
|
||||
static TaskHandle_t lvgl_task_handle = NULL;
|
||||
static bool lvgl_task_interrupt_requested = false;
|
||||
|
||||
#define LVGL_STOP_POLL_INTERVAL 10
|
||||
#define LVGL_STOP_TIMEOUT 5000
|
||||
|
||||
static void task_lock(void) {
|
||||
if (!task_mutex_initialised) return;
|
||||
recursive_mutex_lock(&task_mutex);
|
||||
}
|
||||
|
||||
static void task_unlock(void) {
|
||||
if (!task_mutex_initialised) return;
|
||||
recursive_mutex_unlock(&task_mutex);
|
||||
}
|
||||
|
||||
bool lvgl_lock(void) {
|
||||
if (!lvgl_mutex_initialised) return false;
|
||||
recursive_mutex_lock(&lvgl_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool lvgl_try_lock_timed(uint32_t timeout) {
|
||||
if (!lvgl_mutex_initialised) return false;
|
||||
return recursive_mutex_try_lock_timed(&lvgl_mutex, millis_to_ticks(timeout));
|
||||
}
|
||||
|
||||
void lvgl_unlock(void) {
|
||||
if (!lvgl_mutex_initialised) return;
|
||||
recursive_mutex_unlock(&lvgl_mutex);
|
||||
}
|
||||
|
||||
static void lvgl_task_set_interrupted(bool interrupted) {
|
||||
task_lock();
|
||||
lvgl_task_interrupt_requested = interrupted;
|
||||
task_unlock();
|
||||
}
|
||||
|
||||
static bool lvgl_task_is_interrupt_requested() {
|
||||
task_lock();
|
||||
bool result = lvgl_task_interrupt_requested;
|
||||
task_unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
static void lvgl_task(void* arg) {
|
||||
uint32_t task_delay_ms = task_max_sleep_ms;
|
||||
|
||||
check(!lvgl_task_is_interrupt_requested());
|
||||
|
||||
// on_start must be called from the task, otherwise the display doesn't work
|
||||
if (lvgl_module_config.on_start) lvgl_module_config.on_start();
|
||||
|
||||
while (!lvgl_task_is_interrupt_requested()) {
|
||||
if (lvgl_try_lock_timed(10)) {
|
||||
task_delay_ms = lv_timer_handler();
|
||||
lvgl_unlock();
|
||||
}
|
||||
if ((task_delay_ms > task_max_sleep_ms) || (1 == task_delay_ms)) {
|
||||
task_delay_ms = task_max_sleep_ms;
|
||||
} else if (task_delay_ms < 1) {
|
||||
task_delay_ms = 1;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
|
||||
}
|
||||
|
||||
if (lvgl_module_config.on_stop) lvgl_module_config.on_stop();
|
||||
|
||||
task_lock();
|
||||
lvgl_task_handle = NULL;
|
||||
task_unlock();
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
error_t lvgl_arch_start() {
|
||||
if (!lvgl_mutex_initialised) {
|
||||
recursive_mutex_construct(&lvgl_mutex);
|
||||
lvgl_mutex_initialised = true;
|
||||
}
|
||||
|
||||
if (!task_mutex_initialised) {
|
||||
recursive_mutex_construct(&task_mutex);
|
||||
task_mutex_initialised = true;
|
||||
}
|
||||
|
||||
lvgl_task_set_interrupted(false);
|
||||
|
||||
lv_init();
|
||||
|
||||
// Create the main app loop, like ESP-IDF
|
||||
BaseType_t task_result = xTaskCreate(
|
||||
lvgl_task,
|
||||
"lvgl",
|
||||
lvgl_module_config.task_stack_size,
|
||||
NULL,
|
||||
lvgl_module_config.task_priority,
|
||||
&lvgl_task_handle
|
||||
);
|
||||
|
||||
return (task_result == pdTRUE) ? ERROR_NONE : ERROR_RESOURCE;
|
||||
}
|
||||
|
||||
error_t lvgl_arch_stop() {
|
||||
TickType_t start_ticks = get_ticks();
|
||||
lvgl_task_set_interrupted(true);
|
||||
while (true) {
|
||||
task_lock();
|
||||
bool done = (lvgl_task_handle == NULL);
|
||||
task_unlock();
|
||||
if (done) break;
|
||||
|
||||
delay_ticks(LVGL_STOP_POLL_INTERVAL);
|
||||
|
||||
if (get_ticks() - start_ticks > LVGL_STOP_TIMEOUT) {
|
||||
return ERROR_TIMEOUT;
|
||||
}
|
||||
}
|
||||
|
||||
lv_deinit();
|
||||
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
#include <lvgl.h>
|
||||
#include <string.h>
|
||||
#include <tactility/module.h>
|
||||
#include <tactility/lvgl_module.h>
|
||||
|
||||
|
||||
error_t lvgl_arch_start();
|
||||
error_t lvgl_arch_stop();
|
||||
|
||||
static bool is_running = false;
|
||||
static bool is_configured = false;
|
||||
|
||||
struct LvglModuleConfig lvgl_module_config = {
|
||||
NULL,
|
||||
NULL,
|
||||
0,
|
||||
0,
|
||||
#ifdef ESP_PLATFORM
|
||||
0,
|
||||
#endif
|
||||
};
|
||||
|
||||
void lvgl_module_configure(const struct LvglModuleConfig config) {
|
||||
is_configured = true;
|
||||
memcpy(&lvgl_module_config, &config, sizeof(struct LvglModuleConfig));
|
||||
}
|
||||
|
||||
static error_t start() {
|
||||
if (!is_configured) {
|
||||
return ERROR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (is_running) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t result = lvgl_arch_start();
|
||||
if (result == ERROR_NONE) {
|
||||
is_running = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static error_t stop() {
|
||||
if (!is_running) {
|
||||
return ERROR_NONE;
|
||||
}
|
||||
|
||||
error_t error = lvgl_arch_stop();
|
||||
if (error == ERROR_NONE) {
|
||||
is_running = false;
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
bool lvgl_is_running() {
|
||||
return is_running;
|
||||
}
|
||||
|
||||
struct Module lvgl_module = {
|
||||
.name = "lvgl",
|
||||
.start = start,
|
||||
.stop = stop
|
||||
};
|
||||
Reference in New Issue
Block a user