X-TRACK 中的 LVGL 8.3 图形库:从嵌入式 GUI 特性到模拟器与移植实战
X-TRACK 中的 LVGL 8.3 图形库:从嵌入式 GUI 特性到模拟器与移植实战
导读
LVGL(Light and Versatile Graphics Library)是一个用 C 编写、面向嵌入式系统的开源图形库,它以"轻量、通用、可裁剪"著称,为微控制器设备提供按钮、图表、列表、滑动条等完整控件体系与动画、抗锯齿等高级渲染能力。本仓库 Software/X-Track/Simulator/LVGL.Simulator/lvgl/README.md 是随 X-TRACK 项目一同携带的 LVGL 官方文档,正文将以其为核心骨架,完整继承其特性清单、硬件需求、平台支持与示例代码,并结合 X-TRACK 仓库中真实的 lv_conf 配置、Windows 桌面模拟器入口、Linux framebuffer 移植代码以及 Dialplate 页面实现,带你从"读文档"走向"看真实工程如何落地"。
一、仓库中 LVGL 的版本与文档定位
X-TRACK 将 LVGL 以源码方式内置在模拟器工程与各移植目标中。本关联文档所在目录为 Software/X-Track/Simulator/LVGL.Simulator/lvgl,同目录下还配有中文版本 README_zh.md,两份文档互为镜像。
从版本头文件可以确认仓库携带的精确版本号:
#define LVGL_VERSION_MAJOR 8
#define LVGL_VERSION_MINOR 3
#define LVGL_VERSION_PATCH 0
见 lvgl.h。也就是说,本文所讨论的 LVGL 功能、宏与 API 均以 v8.3.0 为准。项目根目录 README.md 的更新日志也印证了这条升级路径:v2.4 版本"更新 lvgl v8.3.0 dev",v2.7 版本继续"同步 commit 941d95b0…"保持与主线对齐。
二、核心特性:一套为嵌入式场景设计的 GUI 能力
原文档对 LVGL 的特性做了系统归纳,以下逐条继承并结合仓库配置佐证:
- 丰富的模块化图形组件:按钮(button)、图表(chart)、列表(list)、滑动条(slider)、图片(image)等一应俱全。在 X-Track 的 lv_conf.h 中,
LV_USE_BTN、LV_USE_CHART、LV_USE_SLIDER、LV_USE_IMG、LV_USE_LABEL、LV_USE_ROLLER、LV_USE_SWITCH、LV_USE_TEXTAREA、LV_USE_TABLE、LV_USE_ARC、LV_USE_MENU、LV_USE_METER、LV_USE_SPINNER等均置为1,几乎全量启用,供表盘与系统信息页使用。 - 高级图形引擎:动画(animation)、抗锯齿(anti-aliasing)、透明度(opacity)、平滑滚动、图层混合。X-Track 表盘页面正是用
lv_anim_timeline编排入场动画(详见下文第四节)。 - 多种输入设备:触摸屏、鼠标、键盘、编码器、按键等。X-Track 的 Windows 模拟器调用
lv_win32_add_all_input_devices_to_group(NULL)一次性挂载全部输入;Linux 移植则用evdev接入指针设备(见 main.cpp)。 - 多显示设备支持:单库可驱动多个 display。
- 硬件无关性:可运行于任意满足要求的 MCU 与显示器。仓库实际落地在国产 AT32F403A / AT32F435(见 MDK-ARM_F403A 与 MDK-ARM_F435)、Linux framebuffer(Linux)以及 Windows 桌面模拟器(LVGL.Simulator)四类目标上。
- 低资源可裁剪:最低 64 kB Flash、16 kB RAM 即可运行。X-Track 在 lv_conf.h 中将内置内存池
LV_MEM_SIZE配置为72U * 1024U字节(72 KB),并针对 MCU 端与 PC 端分别选用内置分配器或系统malloc/free。 - 多语言与 UTF-8:支持中文、日文、韩文、阿拉伯文等。仓库配置
LV_TXT_ENC LV_TXT_ENC_UTF8(lv_conf.h),X-Track 的界面即包含中文场景。 - 类 CSS 样式与布局:支持 Flexbox 与 Grid 布局。仓库在 lv_conf.h 中同时开启
LV_USE_FLEX 1与LV_USE_GRID 1;Dialplate 页面的底部信息区就用LV_FLEX_FLOW_ROW_WRAP实现了信息卡片自动换行排列(见下文)。 - OS / 外部内存 / GPU 可选:内建支持 STM32 DMA2D、SWM341 DMA2D、NXP PXP 与 VGLite;X-Track 的 lv_conf 中这些加速项均为
0(未启用),属于"可选不必须"的典型用法。 - 单帧缓冲也能平滑渲染:X-Track 的 Linux 移植即单缓冲方案(详见第四节)。
- 纯 C 实现、兼容 C++:X-Track 的应用层是 C++ 工程,直接
#include "lvgl/lvgl.h"即可无缝互操作。 - MicroPython 绑定:可通过 Python 调用 LVGL API。
- PC 模拟器:无硬件即可开发调试,X-Track 的 LVGL.Simulator 工程 正是这一特性的工程化产物。
- 丰富例程与文档:lvgl 目录内自带 examples(415+ 个示例文件)与 docs 文档树,可离线查阅。
三、硬件需求与支持平台
3.1 硬件需求表(继承原文)
原文档给出了一份权威的最低/推荐配置表,这是评估"我的芯片能否跑 LVGL"的第一依据:
| 项目 | 最低要求 | 推荐配置 |
|---|---|---|
| 架构 | 16/32/64 位微控制器或微处理器 | 同左 |
| 时钟 | > 16 MHz | > 48 MHz |
| Flash/ROM | > 64 kB | > 180 kB |
| 静态 RAM | > 16 kB | > 48 kB |
| Draw buffer(绘制缓冲) | > 1 × 水平分辨率像素 | > 1/10 屏幕大小 |
| 编译器 | C99 或更新 | 同左 |
注意:实际内存占用会随架构、编译器与编译选项变化,表中数值仅供参考。X-Track 在 240×240、RGB565(每像素 2 字节)的显示方案下,将 LVGL 内存池设为 72 KB,并提供 StackInfo 与
HAL::Memory_DumpInfo()等工具用于实测栈与堆占用,正是"按需调整、实测为准"的工程实践。
3.2 支持平台(继承原文)
LVGL 平台无关,只要满足上表即可运行,原文档列举的平台包括:
- NXP:Kinetis、LPC、iMX、iMX RT
- STM32F1 / F3 / F4 / F7 / L4 / L5 / H7 系列
- Microchip:dsPIC33、PIC24、PIC32MX、PIC32MZ
- Linux frame buffer(
/dev/fb) - Raspberry Pi、Espressif ESP32、Infineon Aurix、Nordic NRF52、Quectel 模组、SYNWIT SWM341 等
LVGL 同时还以以下形式分发:Arduino 库、PlatformIO 包、Zephyr 库、ESP32 组件、NXP MCUXpresso 组件、NuttX 库、RT-Thread RTOS 包等。
仓库实例对照:X-Track 虽然没有直接使用上述商业平台,但完美印证了"平台无关"这一主张——同一套 LVGL v8.3 同时跑在国产 AT32F403A/AT32F435 裸机(MDK 工程)、Linux framebuffer(Software/X-Track/Linux)和 Windows Win32(LVGL.Simulator.cpp)上,应用层代码零改动复用。
四、快速入门:官方 10 步学习路径与仓库落地对照
原文档给出了官方推荐的学习顺序,X-Track 仓库可以逐条"对号入座":
- 看在线演示体验 LVGL —— 对 X-Track 而言,最直接的体验方式是直接构建运行仓库的 LVGL.Simulator.sln 工程;
- 读文档 Introduction —— 仓库内 lvgl/docs 提供了完整文档副本;
- 看 Quick overview 熟悉基础 —— 对应 lvgl 源码中
lv_obj、lv_style等核心对象模型; - 搭建 PC 模拟器 —— X-Track 的 LVGL.Simulator 就是现成答案,基于
lv_drivers的 win32drv 实现,240×240 窗口模拟真实表盘分辨率(见 LVGL.Simulator.cpp); - 动手实践例程 —— lvgl/examples 内置 100+ 示例;
- 参考移植指南并移植到开发板 —— X-Track 的 lv_port、lv_port_fs_sdfat.cpp、lv_port_indev.cpp 以及 MDK-ARM_F403A/Platform/HAL/HAL_Display.cpp 就是一套完整的移植范例;
- 细读 Overview 深入理解 —— 源码 lvgl/src 按 core / draw / widgets / layouts / misc 分层组织,是研读内部机制的入口;
- 查阅 Widgets 文档 —— lvgl/docs/widgets 在仓库内可离线浏览;
- 有问题上论坛提问;
- 阅读贡献指南参与社区。
4.1 Windows 模拟器主循环(仓库实证)
X-Track 的模拟器入口展示了 LVGL 应用的标准骨架(LVGL.Simulator.cpp):
int main()
{
lv_init();
lv_fs_if_init();
if (!lv_win32_init(GetModuleHandleW(NULL), SW_SHOW,
SCREEN_HOR_RES, SCREEN_VER_RES, ...)) return -1;
lv_win32_add_all_input_devices_to_group(NULL);
HAL::HAL_Init();
App_Init();
while (!lv_win32_quit_signal)
{
lv_timer_handler(); /* 驱动 LVGL 定时器与重绘 */
HAL::HAL_Update(); /* 刷新传感器/GPS 等外设数据 */
Sleep(1);
}
App_Uninit();
return 0;
}
lv_timer_handler() 是 LVGL 的事件引擎核心:所有动画、刷新、输入处理都在其中推进。
4.2 Linux framebuffer 移植要点(仓库实证)
main.cpp 给出了一个教科书级的 LVGL 显示/输入移植流程:
/* 1. 初始化 fbdev,取屏幕尺寸 */
fbdev_init();
fbdev_get_sizes(&width, &height);
/* 2. 分配绘制缓冲(单缓冲方案) */
lv_color_t* buf = (lv_color_t*)malloc(disp_buf_size * sizeof(lv_color_t));
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf, NULL, disp_buf_size);
/* 3. 注册显示驱动:flush 回调指向 fbdev_flush */
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = fbdev_flush;
disp_drv.hor_res = width;
disp_drv.ver_res = height;
lv_disp_drv_register(&disp_drv);
/* 4. 注册指针输入设备:evdev 读取鼠标/触摸 */
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = evdev_read;
lv_indev_register(&indev_drv);
同时,Linux 端通过 lv_conf.h 中的 LV_TICK_CUSTOM 1 使用自定义时钟源,并在 main.cpp 末尾实现了 custom_tick_get()(基于 gettimeofday),省去了手动调用 lv_tick_inc() 的麻烦。
五、第一个控件:C 与 MicroPython 官方示例(完整继承)
原文档给出的按钮 + 标签示例是理解 LVGL API 的最佳起点,此处完整保留并补充注释:
5.1 C 语言版本
lv_obj_t * btn = lv_btn_create(lv_scr_act()); /*在活动屏幕创建按钮*/
lv_obj_set_pos(btn, 10, 10); /*设置位置 (x, y)*/
lv_obj_set_size(btn, 100, 50); /*设置尺寸 (宽, 高)*/
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED, NULL); /*绑定点击事件回调*/
lv_obj_t * label = lv_label_create(btn); /*在按钮上创建标签*/
lv_label_set_text(label, "Button"); /*设置标签文本*/
lv_obj_center(label); /*标签水平垂直居中*/
...
void btn_event_cb(lv_event_t * e)
{
printf("Clicked\n");
}
要点解读:lv_scr_act() 返回当前活动屏幕对象;lv_obj_* 系列是通用对象 API;事件回调通过 lv_obj_add_event_cb 注册,LV_EVENT_CLICKED 表示点击释放事件——这是 LVGL v8 的事件模型(v7 之前是 lv_obj_set_event_cb 直接赋回调)。
5.2 MicroPython 版本
def btn_event_cb(e):
print("Clicked")
# 创建按钮与标签
btn = lv.btn(lv.scr_act())
btn.set_pos(10, 10)
btn.set_size(100, 50)
btn.add_event_cb(btn_event_cb, lv.EVENT.CLICKED, None)
label = lv.label(btn)
label.set_text("Button")
label.center()
MicroPython 绑定把 C API 按"类方法"风格暴露:lv.btn、lv.label 对应 C 的 lv_btn_create、lv_label_create,事件常量位于 lv.EVENT 命名空间下。
5.3 仓库实战:Dialplate 表盘页的真实 UI 代码
官方示例演示了"创建—定位—绑定事件"三步,X-Track 的 DialplateView.cpp 则展示了同一套 API 在真实产品中的组合用法——创建容器、移除默认样式、设置圆角与背景色、加载自定义字体、创建并居中对齐标签:
lv_obj_t* cont = lv_obj_create(par);
lv_obj_remove_style_all(cont); /*清空默认样式,便于完全自定义*/
lv_obj_set_size(cont, LV_HOR_RES, 142); /*宽度铺满屏幕*/
lv_obj_set_style_bg_opa(cont, LV_OPA_COVER, 0); /*不透明背景*/
lv_obj_set_style_bg_color(cont, lv_color_hex(0x333333), 0);
lv_obj_set_style_radius(cont, 27, 0); /*圆角半径 27*/
lv_obj_set_y(cont, -36);
lv_obj_t* label = lv_label_create(cont);
lv_obj_set_style_text_font(label, ResourcePool::GetFont("bahnschrift_65"), 0); /*自定义大号数字字体*/
lv_obj_set_style_text_color(label, lv_color_white(), 0);
lv_label_set_text(label, "00"); /*速度初始值*/
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 63); /*顶部居中*/
同一个页面还用 lv_anim_timeline 编排了 500ms 缓出(ease_out)的入场动画(DialplateView.cpp),对应官方特性清单中的"动画引擎";用 LV_FLEX_FLOW_ROW_WRAP 让 4 个信息卡片自动换行(DialplateView.cpp),对应"Flexbox 布局"。可以看出:官方文档描述的每一项能力,在 X-Track 中都有对应的真实调用点。
六、lv_conf.h 关键配置解析(仓库实证)
LVGL 通过 lv_conf.h 进行编译期裁剪。仓库将配置文件放在 Software/X-Track/Simulator/LVGL.Simulator/lv_conf.h,MCU 端也各自维护一份(见 MDK-ARM_F435/Platform/lv_port 目录下的配置链)。以下摘取与本项目强相关的关键项:
| 配置宏 | 仓库取值 | 含义与影响 |
|---|---|---|
LV_COLOR_DEPTH |
16 |
RGB565 色彩深度,2 字节/像素,与 240×240 ST7789 屏匹配;SPI 8 位接口场景下配合 LV_COLOR_16_SWAP 1 交换高低字节 |
LV_MEM_SIZE |
72U * 1024U |
内置内存池大小(字节);LV_MEM_CUSTOM 在 Arduino 端为 0、PC 端为 1,PC 直接使用 malloc/free |
LV_DISP_DEF_REFR_PERIOD |
16 |
显示刷新周期(ms),对应约 60 FPS 的刷新节奏;更新日志显示从 20ms 优化到 16ms |
LV_INDEV_DEF_READ_PERIOD |
30 |
输入设备轮询周期(ms),适合编码器/按键场景 |
LV_TICK_CUSTOM |
1 |
自定义时基,Linux 用 custom_tick_get()、Win32 用 timeGetTime()、Arduino 用 millis() |
LV_DPI_DEF |
130 |
默认 DPI,用于推算控件默认尺寸与内边距 |
LV_USE_FLEX / LV_USE_GRID |
1 / 1 |
启用 Flexbox 与 Grid 布局引擎 |
LV_TXT_ENC |
LV_TXT_ENC_UTF8 |
UTF-8 编码,支持中文等多语言文本 |
LV_FONT_MONTSERRAT_10 |
1 |
内置 Montserrat 10 号字体,且 LV_FONT_DEFAULT 指向它;其余内置字体全部关闭以省 Flash,界面改用 ResourcePool 加载的自定义字体(如 bahnschrift) |
LV_USE_LOG |
Arduino 端 0、PC 端 1 |
日志开关,PC 端通过 printf 输出,便于调试 |
LV_USE_PNG |
Arduino 端 0、PC 端 1 |
PNG 解码器按平台使能——更新日志 v2.2 明确指出"默认开启 PNG 解码器,受限于内存 MCU 无法使用" |
LV_USE_GPU_*(STM32 DMA2D、SWM341、NXP PXP/VG-Lite、SDL)在仓库中全部为 0,与官方"GPU 可选、不必须"的描述一致;LV_BUILD_EXAMPLES、各 LV_USE_DEMO_* 均为 0,说明 X-Track 将 LVGL 作为库裁剪集成、不携带官方 demo,这也呼应了更新日志 v1.6"去除 lv_examples,减少工程体积"的做法。
七、周边生态:文件系统与扩展组件
LVGL 的可用性还依赖周边配套,仓库中同样有据可查:
- 文件系统抽象(lv_fs_if):X-Track 的 Windows 模拟器调用
lv_fs_if_init()(LVGL.Simulator.cpp),并通过 lv_fs_pc.c 将 PC 目录映射为 LVGL 驱动器,用于读取离线地图瓦片;MCU 端则用 lv_port_fs_sdfat.cpp 对接 SdFat,实现 SD 卡轨迹记录与地图读取。 - 输入设备封装(lv_port_indev):lv_port_indev.cpp 为编码器创建全局默认 group(
lv_group_get_default()),让 Dialplate、LiveMap、SystemInfos 等页面共享同一焦点管理——对应官方"encoder 支持"特性。 - PNG 解码:PC 端通过
lv_png_init启用内置 PNG 解码器(lv_conf.h),配合 lv_img_png 组件在 LiveMap 页面渲染 PNG 格式地图瓦片。
八、阅读路径速查
围绕本文主题,建议按以下顺序深入仓库:
- 文档本体:Software/X-Track/Simulator/LVGL.Simulator/lvgl/README.md(中文版见 README_zh.md)
- 版本头:Software/X-Track/Simulator/LVGL.Simulator/lvgl/lvgl.h
- 裁剪配置:Software/X-Track/Simulator/LVGL.Simulator/lv_conf.h
- 官方示例:Software/X-Track/Simulator/LVGL.Simulator/lvgl/examples
- 离线文档:Software/X-Track/Simulator/LVGL.Simulator/lvgl/docs
- Windows 模拟器入口:LVGL.Simulator.cpp
- Linux framebuffer 移植:Software/X-Track/Linux/main.cpp
- 应用层 UI 实践:DialplateView.cpp、LiveMapView.cpp、StartUpView.cpp
- 移植封装:lv_port.cpp、lv_port_indev.cpp、lv_port_fs_sdfat.cpp
- 更新日志(版本演进线索):README.md
综上:LVGL 文档回答"这个库能做什么、门槛多高、怎么入门",而 X-Track 仓库则回答"在真实的嵌入式骑行码表上它被怎样配置、移植和组合使用"。两者互为印证,是学习嵌入式 GUI 开发的一条高质量完整链路。