feat(display): 添加LVGL显示支持和PWM背光控制

添加了完整的LVGL集成支持,包括:

- 在app.overlay中配置显示设备树,添加背光别名和SPI3总线支持
- 集成PWM背光控制,通过pwm-leds子系统管理背光亮度
- 配置LVGL自动初始化和工作队列运行模式
- 实现显示模块的工作队列更新机制,包含UI创建和定时刷新
- 添加详细的LVGL移植说明文档,涵盖设备树配置、调试步骤和常见问题
- 调整分区配置以适应LVGL固件大小需求
- 启用MCUBoot bootloader支持OTA功能

该变更使得系统能够在ST7789V显示屏上正常运行LVGL界面,并通过PWM控制背光。
This commit is contained in:
2026-03-23 09:16:34 +08:00
parent 6ca70d2580
commit d02e33d97b
6 changed files with 916 additions and 39 deletions

View File

@@ -1,12 +1,10 @@
#include <stdio.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/led.h>
#include <zephyr/kernel.h>
#include <app_event_manager.h>
#include <lvgl.h>
#include <lvgl_zephyr.h>
@@ -16,14 +14,17 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(MODULE, LOG_LEVEL_INF);
#define DISPLAY_UPDATE_PERIOD_MS 10
#define DISPLAY_UPDATE_PERIOD_MS 1000
#define DISPLAY_BACKLIGHT_BRIGHTNESS 100
struct display_ctx {
const struct device *dev;
struct k_work_delayable refresh_work;
lv_obj_t *hello_label;
struct display_capabilities caps;
struct k_work_delayable update_work;
lv_obj_t *title_label;
lv_obj_t *count_label;
uint32_t tick_count;
bool ui_ready;
bool initialized;
};
@@ -31,10 +32,70 @@ static struct display_ctx disp = {
.dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)),
};
static void display_refresh_work_fn(struct k_work *work)
static const struct led_dt_spec display_backlight =
LED_DT_SPEC_GET(DT_NODELABEL(backlight));
static void display_schedule_update(k_timeout_t delay)
{
char count_str[12];
uint32_t wait_ms;
#ifdef CONFIG_LV_Z_RUN_LVGL_ON_WORKQUEUE
k_work_schedule_for_queue(lvgl_get_workqueue(), &disp.update_work, delay);
#else
k_work_reschedule(&disp.update_work, delay);
#endif
}
static int display_backlight_init(void)
{
int err;
if (!led_is_ready_dt(&display_backlight)) {
LOG_WRN("Display backlight device not ready");
return 0;
}
/*
* 背光亮度交给 pwm-leds 驱动管理,这样后面如果要做调光、呼吸灯或亮度档位,
* 都可以直接沿用 Zephyr 的 LED/PWM 接口,而不需要再单独碰 PWM 寄存器。
*/
err = led_set_brightness_dt(&display_backlight, DISPLAY_BACKLIGHT_BRIGHTNESS);
if (err) {
LOG_ERR("Failed to set backlight brightness: %d", err);
return err;
}
return 0;
}
static void display_create_ui_locked(void)
{
lv_obj_t *screen = lv_screen_active();
/*
* 先显式设置背景和文字颜色,避免把“有画面但颜色刚好看不见”误判为
* “LVGL 没有刷新”。这里使用高对比度配色,便于快速验证渲染链路。
*/
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, LV_PART_MAIN);
lv_obj_set_style_bg_color(screen, lv_color_hex(0x102A43), LV_PART_MAIN);
lv_obj_set_style_text_color(screen, lv_color_hex(0xF0F4F8), LV_PART_MAIN);
lv_obj_clean(screen);
disp.title_label = lv_label_create(screen);
lv_label_set_text(disp.title_label, "Zephyr LVGL running");
lv_obj_set_style_text_color(disp.title_label, lv_color_hex(0xF0F4F8), LV_PART_MAIN);
lv_obj_align(disp.title_label, LV_ALIGN_CENTER, 0, -16);
disp.count_label = lv_label_create(screen);
lv_label_set_text(disp.count_label, "tick 0");
lv_obj_set_style_text_color(disp.count_label, lv_color_hex(0xFFD166), LV_PART_MAIN);
lv_obj_align(disp.count_label, LV_ALIGN_CENTER, 0, 16);
disp.ui_ready = true;
}
static void display_update_work_fn(struct k_work *work)
{
char count_str[24];
lv_color_t bg_color;
ARG_UNUSED(work);
@@ -44,17 +105,21 @@ static void display_refresh_work_fn(struct k_work *work)
lvgl_lock();
if ((disp.tick_count % 100U) == 0U) {
snprintk(count_str, sizeof(count_str), "%u", disp.tick_count / 100U);
lv_label_set_text(disp.count_label, count_str);
if (!disp.ui_ready) {
display_create_ui_locked();
}
wait_ms = lv_timer_handler();
bg_color = ((disp.tick_count & 0x01u) == 0U) ? lv_color_hex(0x102A43) :
lv_color_hex(0x1F6F8B);
lv_obj_set_style_bg_color(lv_screen_active(), bg_color, LV_PART_MAIN);
snprintk(count_str, sizeof(count_str), "tick %u", disp.tick_count++);
lv_label_set_text(disp.count_label, count_str);
lv_obj_invalidate(lv_screen_active());
lvgl_unlock();
disp.tick_count++;
k_work_reschedule(&disp.refresh_work,
K_MSEC(MAX(wait_ms, DISPLAY_UPDATE_PERIOD_MS)));
display_schedule_update(K_MSEC(DISPLAY_UPDATE_PERIOD_MS));
}
static int display_demo_init(void)
@@ -66,22 +131,13 @@ static int display_demo_init(void)
return -ENODEV;
}
display_get_capabilities(disp.dev, &disp.caps);
LOG_INF("Display caps: %ux%u fmt=%d", disp.caps.x_resolution, disp.caps.y_resolution,
disp.caps.current_pixel_format);
k_work_init_delayable(&disp.update_work, display_update_work_fn);
disp.tick_count = 0U;
k_work_init_delayable(&disp.refresh_work, display_refresh_work_fn);
lvgl_lock();
lv_obj_clean(lv_screen_active());
disp.hello_label = lv_label_create(lv_screen_active());
lv_label_set_text(disp.hello_label, "LVGL demo running");
lv_obj_align(disp.hello_label, LV_ALIGN_CENTER, 0, -12);
disp.count_label = lv_label_create(lv_screen_active());
lv_label_set_text(disp.count_label, "0");
lv_obj_align(disp.count_label, LV_ALIGN_BOTTOM_MID, 0, -12);
lv_timer_handler();
lvgl_unlock();
disp.ui_ready = false;
err = display_blanking_off(disp.dev);
if (err) {
@@ -89,10 +145,15 @@ static int display_demo_init(void)
return err;
}
disp.initialized = true;
k_work_reschedule(&disp.refresh_work, K_MSEC(DISPLAY_UPDATE_PERIOD_MS));
err = display_backlight_init();
if (err) {
return err;
}
disp.initialized = true;
display_schedule_update(K_NO_WAIT);
LOG_INF("LVGL display demo initialized");
return 0;
}