LVGL UI应用开发说明¶
目录¶
- 概述
- 快速入门
- 实现原理
- 资源生成方式
- 图片资源
- 字体资源
- 文件系统资源
- 加载方式
- 初始化流程
- 显示驱动
- 图片加载
- 字体加载
- 示例工程
- demo_lvgl
- ai_xiaozhi_wakeup_lvgl
- 附录
- 文件结构
- 依赖模块
概述¶
LVGL(Light and Versatile Graphics Library)是一款开源的嵌入式图形库,专为资源受限的 MCU 设计,占用内存小、运行效率高、控件丰富,广泛用于模组 LCD 屏的界面开发。目前使用的LVGL9.4.0版本。
版本信息¶
| 项目 | 说明 |
|---|---|
| LVGL 版本 | 9.4.0 |
| 组件路径 | components/lvgl-9.4.0 |
| 颜色深度 | 16 bit(RGB565,LV_COLOR_DEPTH 16) |
| 操作系统集成 | LV_USE_OS = LV_OS_CUSTOM(对接 YOpen OS 抽象层) |
| 运行内存 | LV_MEM_SIZE 64 KB |
| 配置文件 | components/lvgl-9.4.0/lv_conf.h |
版本号定义在 components/lvgl-9.4.0/lv_version.h:
#define LVGL_VERSION_MAJOR 9
#define LVGL_VERSION_MINOR 4
#define LVGL_VERSION_PATCH 0
支持功能¶
| 功能 | 说明 |
|---|---|
| 控件 | label / image / button / list 等 LVGL 内置控件 |
| 字体 | Montserrat、FontAwesome5、思源黑体(Source Han Sans SC,含中文) |
| 图片解码 | JPEG(yopen_jpeg)、BMP、GIF、PNG(可选) |
| 显示驱动 | ST7789(240×320)、JD9853(240×320) |
| 文件系统 | LittleFS(LV_USE_FS) |
快速入门¶
以 demo_lvgl 为例,展示一个最小 LVGL 应用从放置图片到显示的最短路径。
使用步骤¶
- 准备图片:将图片放入
components/demo/lvgl/fs/root/(如ycom.jpg),预制文件系统会将其打包进 LittleFS 镜像。 - 编译项目:执行构建命令。
- 运行显示:上电后自动初始化 LVGL 并在屏幕上显示文字与图片。
编译命令¶
.\build.bat YM310_W09C.A60 lvgl
<bsp_module> 为模组型号,如 YM310_X09S.U62、YM310_W09C.A60、YM310_W09S.H60 等;lvgl 对应 project/lvgl.cmake 工程。
实现原理¶
整体架构¶
LVGL 在 YOpen SDK 中的调用层次自上而下为:
业务/示例 → LVGL 核心(
lvgl-9.4.0)→ 显示驱动抽象(components/lcd)→ SPI LCD 硬件。
- 业务/示例层:
demo_lvgl.c、ai_lvgl.c等,负责创建控件、处理消息。 - LVGL 核心层:
components/lvgl-9.4.0,负责控件渲染、事件、定时器、字体/图片解码。 - 显示驱动抽象层:
components/lcd的lcdDrv.h(lcdOpen/lcdSetWindow/lcdFill/lcdBackLight)。 - 硬件层:ST7789、JD9853 等 SPI 屏幕。
资源生成与加载流程¶
编译阶段:
图片文件(.jpg/.png/.gif) ──→ LVGLImage.py ──→ C 数组(.c)
字体文件(.ttf/.otf/.woff) ──→ lv_font_conv ──→ C 文件(.c)
任意文件(图片/音频等) ──→ 预制文件系统 ──→ LittleFS 镜像
运行阶段:
lv_init() → 初始化 LVGL
lv_port_disp_init() → 创建显示设备并注册 flush 回调
lv_image_set_src() → 设置图片源(C 数组或文件路径)
lv_obj_set_style_text_font() → 设置字体
lv_timer_handler() → 主循环周期刷新
技术要点¶
- 编译时转换:图片和字体在编译阶段被转换为 C 数据,随固件烧录,运行时无需解析源文件。
- 两种图片加载:可加载为 C 数组(
LV_IMAGE_DECLARE),也可通过文件系统路径加载(如/ycom.jpg)。 - 按需解码:JPEG / GIF 等格式由 LVGL 在显示时自动调用对应解码器解码。
- 消息驱动 UI:采用「消息队列 + 主循环」模型,业务线程投递消息,LVGL 任务统一处理刷新。
资源生成方式¶
图片资源¶
图片通过 LVGL 官方工具 LVGLImage.py 转换为 C 数组。在工程 CMakeLists.txt 中配置:
# 查找 resource/image 目录下的图片文件
file(GLOB IMAGE_FILES "${CURRENT_CMAKE_DIR}/resource/image/*.jpg")
foreach(IMAGE_FILE ${IMAGE_FILES})
get_filename_component(IMAGE_NAME ${IMAGE_FILE} NAME_WE)
set(OUTPUT_C_FILE "${PROJECT_BINARY_DIR}/${IMAGE_NAME}.c")
set(LVGL_IMAGE_TOOL ${YOPEN_TOP_DIR}/components/lvgl-9.4.0/scripts/LVGLImage.py)
add_custom_command(
OUTPUT ${OUTPUT_C_FILE}
COMMAND python ${LVGL_IMAGE_TOOL} --cf RAW --ofmt C -o ${PROJECT_BINARY_DIR} --name ${IMAGE_NAME} ${IMAGE_FILE}
MAIN_DEPENDENCY ${IMAGE_FILE}
COMMENT "Converting image: ${IMAGE_FILE}"
)
target_sources(${target} PRIVATE ${OUTPUT_C_FILE})
target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR})
endforeach()
参数说明:
| 参数 | 值 | 说明 |
|---|---|---|
--cf |
RAW |
颜色格式,保留原始图片数据(JPEG 用 RAW,带透明用 RAW_ALPHA) |
--ofmt |
C |
输出格式为 C 文件 |
--name |
{IMAGE_NAME} |
生成的变量名(与文件名对应) |
详细 JPEG 转换与显示流程见 LVGL JPEG 解码说明。GIF 图片同样使用
LVGLImage.py转换,配合src/libs/gif解码器使用(参考app/xiaozhi、app/cyber的CMakeLists.txt)。
字体资源¶
字体通过 LVGL 工具 lv_font_conv 转换为 C 文件。SDK 提供了封装脚本 built_in_font_gen.py(位于 components/lvgl-9.4.0/scripts/built_in_font/)。
工具安装(lv_font_conv 依赖 Node.js):
- 安装 Node.js。
- 下载 lv_font_conv 代码。
- 全局安装:
npm install -g lv_font_conv
- 将 Node.js 全局 bin 目录添加到系统环境变量
PATH。
生成命令(以思源黑体中文 16 号为例):
python ./built_in_font_gen.py --size 16 -o lv_font_source_han_sans_sc_16_cjk.c \
--bpp 1 --font SourceHanSansSC-Normal.otf \
-r 0x20-0xbf,0x3000-0x303F,0xFF00-0xFFEF --symbols 啊阿埃挨哎唉哀.....
参数说明:
| 参数 | 说明 |
|---|---|
--size |
字体像素尺寸 |
--bpp |
每个像素的位深度(1/2/4,中文字体通常用 1) |
-r |
包含的字符 Unicode 范围(如 0x20-0xbf ASCII、0x3000-0x303F CJK 标点、0xFF00-0xFFEF 全角) |
--symbols |
额外指定的字符(逐个列出需要的中文字符) |
--font |
源字体文件(TTF / OTF / WOFF,如 SourceHanSansSC-Normal.otf) |
-o |
输出的 C 文件名 |
SDK 已内置常用字体(components/lvgl-9.4.0/src/font/),可直接使用:
| 字体变量 | 说明 |
|---|---|
lv_font_montserrat_24 |
英文/数字(lv_conf.h 中已启用 24 号) |
lv_font_awesome5_16 |
图标字体(电池/信号/表情等符号) |
lv_font_source_han_sans_sc_14_cjk |
思源黑体中文 14 号 |
lv_font_source_han_sans_sc_16_cjk |
思源黑体中文 16 号 |
lv_font_source_han_sans_sc_24_cjk |
思源黑体中文 24 号 |
文件系统资源¶
任意文件(图片、音频等)可放入预制文件系统目录,编译时打包成 LittleFS 镜像,运行时通过文件路径访问:
# 预制文件系统根目录,该路径下所有文件打包成 LittleFS 镜像
set(YOPEN_PROJECT_FS_DIR ${YOPEN_TOP_DIR}/components/demo/lvgl/fs/root)
例如 demo_lvgl 的 fs/root/ 下放置了 ycom.jpg、test.bin,运行时通过 /ycom.jpg、/test.bin 访问。
加载方式¶
初始化流程¶
LVGL 应用入口通常在一个独立任务中完成初始化并进入主循环:
void demo_lcd_task(void *argv)
{
// 1. 初始化 LVGL
lv_init();
// 2. 初始化显示设备
lv_port_disp_init();
// 3. 创建界面
show_text_demo();
show_image_demo();
// 4. 主循环:周期调用 lv_timer_handler 驱动刷新
while (1) {
uint32_t time_till_next = lv_timer_handler();
if (time_till_next == LV_NO_TIMER_READY) {
time_till_next = 20;
}
yopen_rtos_task_sleep_ms(time_till_next);
}
}
application_init(demo_lcd_task, "COMP.LCD", 4, 4, NULL);
显示驱动¶
LVGL 不直接操作硬件,而是通过「flush 回调」把渲染结果交给 LCD 驱动。核心流程:
lv_display_create()创建显示设备;lv_display_set_flush_cb()注册刷新回调;lv_display_set_buffers()设置渲染缓冲区;- 在
flush回调中调用lcdSetWindow/lcdFill把像素数据刷到屏幕,最后调用lv_display_flush_ready()。
static void disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
if (disp_flush_enabled) {
disp_update_window(area->x1, area->y1, area->x2, area->y2, (uint16_t *)px_map);
}
lv_display_flush_ready(disp);
}
屏幕参数集中在 components/demo/lvgl/demo_lcd_conf.h,换屏只需修改该文件:
| LCD | 驱动 ID | 分辨率 |
|---|---|---|
| ST7789 | ST7789_ID(0x7789) |
240 × 320 |
| JD9853 | JD9853_ID(0x9853) |
240 × 320 |
图片加载¶
支持两种方式:
方式一:C 数组(编译时转换)
// 声明编译时生成的图片变量(变量名与图片文件名对应)
LV_IMAGE_DECLARE(your_image);
lv_obj_t *img = lv_image_create(lv_screen_active());
lv_image_set_src(img, &your_image);
方式二:文件系统路径(运行时从 LittleFS 加载)
// 先注册 JPEG 解码器
extern void lv_yopen_jpeg_init(void);
lv_yopen_jpeg_init();
lv_obj_t *img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "/ycom.jpg"); // JPG 格式
// lv_image_set_src(img, "/test.bin"); // LVGL 工具转成的 BIN 格式
字体加载¶
通过 lv_obj_set_style_text_font 为控件设置字体:
lv_obj_t *label = lv_label_create(lv_screen_active());
// 中文使用思源黑体
lv_obj_set_style_text_font(label, &lv_font_source_han_sans_sc_24_cjk, LV_PART_MAIN);
// 图标使用 FontAwesome5
lv_obj_set_style_text_font(label, &lv_font_awesome5_16, LV_PART_MAIN);
示例工程¶
demo_lvgl¶
最简 LVGL 示例,演示文字和图片显示,适合作为新建 LVGL 应用的起点。
| 项目 | 说明 |
|---|---|
| 工程文件 | project/lvgl.cmake |
| 产品名 | LVGL_DEMO |
| 入口任务 | demo_lcd_task(application_init(..., "COMP.LCD", 4, 4, NULL)) |
| 功能 | 显示文字标签 + 定时刷新计数 + 显示 JPG 图片 |
核心源码:
// components/demo/lvgl/demo_lvgl.c
lv_init();
lv_port_disp_init();
show_text_demo();
show_image_demo();
// 文字显示
s_label1 = lv_label_create(lv_scr_act());
lv_label_set_text(s_label1, "Hello LVGL 9.4.0");
lv_obj_set_pos(s_label1, 0, 10);
// 图片显示
lv_obj_t *img = lv_image_create(lv_screen_active());
lv_image_set_src(img, "/ycom.jpg");
编译命令:
.\build.bat YM310_W09C.A60 lvgl
ai_xiaozhi_wakeup_lvgl¶
小智 AI(唤醒版)的 LVGL 界面,采用消息驱动模型,展示电量、信号、表情、状态文字与对话气泡。
| 项目 | 说明 |
|---|---|
| 工程文件 | project/ai_xiaozhi_wakeup_lvgl.cmake |
| 产品名 | YM310_W09C_OPEN.AI.XIAOZHI.LVGL |
| 入口任务 | ai_lvgl_task(application_init(..., "ai.lvgl", 6, APP_PRIORITY_LOW, NULL)) |
| 屏幕 | ST7789(240 × 320) |
| 功能 | 电量、信号、表情、状态文字、对话气泡、打字机效果 |
界面各元素通过消息队列驱动,业务线程调用接口投递消息,LVGL 任务统一处理:
// components/demo/ai_xiaozhi/ai_lvgl.h 中提供的对外接口
void ai_lvgl_signal_set(ai_lvgl_signal_level_t level, bool pdpActive); // 信号
void ai_lvgl_vbat_set(int vol, bool isCharging); // 电量
void ai_lvgl_emoji_set(bool startStop); // 表情
void ai_lvgl_status_text_set(const char *text, int delay); // 状态文字
void ai_lvgl_dialog_text_set(const char *text, int delay, bool send); // 对话文字
主循环在 lv_timer_handler() 的基础上,同时等待消息队列:
while (1) {
uint32_t time_till_next = lv_timer_handler();
if (time_till_next == LV_NO_TIMER_READY) {
if (!s_lvgl_enter_sleep) time_till_next = 20;
}
// 等待消息并分发处理
ret = yopen_rtos_queue_wait(s_lvgl_queue, (uint8_t *)&msg, sizeof(msg), time_till_next);
if (ret == YOPEN_NO_ERROR) {
switch (msg.type) {
case AI_LVGL_MSG_TYPE_VBAT: lvgl_vbat_set(...); break;
case AI_LVGL_MSG_TYPE_EMOJI: lvgl_emoji_set(...); break;
case AI_LVGL_MSG_TYPE_STATUS_TEXT: lvgl_status_text_set(...); break;
case AI_LVGL_MSG_TYPE_DIALOG_TEXT: lvgl_dialog_text_set(...); break;
case AI_LVGL_MSG_TYPE_SIGNAL: lvgl_signal_set(...); break;
}
}
}
对话气泡在 components/demo/ai_xiaozhi/ai_lvgl_dialog.c 中实现,使用 lv_font_source_han_sans_sc_16_cjk 中文字体。
编译命令:
.\build.bat YM310_W09C.A60 ai_xiaozhi_wakeup_lvgl
附录¶
文件结构¶
components/
├── lvgl-9.4.0/ # LVGL 核心库(9.4.0)
│ ├── lv_conf.h # LVGL 配置(颜色深度、内存、解码器开关等)
│ ├── lv_version.h # 版本号定义
│ ├── scripts/
│ │ ├── LVGLImage.py # 图片转换工具
│ │ └── built_in_font/ # 字体生成脚本
│ │ └── built_in_font_gen.py
│ └── src/
│ ├── font/ # 内置字体(含思源黑体、FontAwesome5)
│ └── libs/
│ ├── yopen_jpeg/ # JPEG 解码器
│ ├── bmp/ # BMP 解码器
│ ├── gif/ # GIF 解码器
│ └── qrcode/ # 二维码
├── lcd/ # LCD 驱动抽象层
│ └── inc/
│ ├── lcdDrv.h # LCD 驱动接口
│ ├── lcdDev_7789.h # ST7789 驱动(240×320)
│ └── lcdDev_jd9853.h # JD9853 驱动(240×320)
└── demo/
├── lvgl/ # demo_lvgl 示例
│ ├── demo_lvgl.c # LVGL 应用入口
│ ├── demo_lvgl_disp.c # 显示驱动
│ ├── demo_lvgl_disp.h
│ ├── demo_lcd_conf.h # 屏幕参数配置
│ └── fs/root/ # 预制文件系统(ycom.jpg、test.bin)
└── ai_xiaozhi/ # ai_xiaozhi_wakeup_lvgl 示例
├── ai_lvgl.c # LVGL 界面入口(消息驱动)
├── ai_lvgl.h # 对外接口与消息定义
├── ai_lvgl_dialog.c # 对话气泡 UI
└── fs/root/ # 预制文件系统(提示音 mp3 等)
project/
├── lvgl.cmake # demo_lvgl 工程配置
└── ai_xiaozhi_wakeup_lvgl.cmake # ai_xiaozhi_wakeup_lvgl 工程配置
依赖模块¶
| 模块 | 路径 | 说明 |
|---|---|---|
| lvgl | components/lvgl-9.4.0 |
LVGL 图形库核心 |
| lcd | components/lcd |
LCD 显示驱动抽象 |
| yopen_jpeg | components/lvgl-9.4.0/src/libs/yopen_jpeg |
JPEG 图片解码器 |
| font | components/font |
独立点阵字库(宋体,非 LVGL 字体,可选) |