
在嵌入式图形用户界面(GUI)开发领域,LVGL(Light and Versatile Graphics Library)凭借其开源、轻量、高画质和丰富的控件集,已成为工程师构建交互式界面的首选方案。然而,面对不同的硬件平台和开发阶段,如何高效地进行模拟验证和最终移植,是每个初学者必须跨越的门槛。本文将从模拟开发环境搭建、硬件移植流程两个核心维度,结合代码示例,系统讲解LVGL的基础开发实践。
LVGL是一款用C语言编写的开源图形库,支持16位以上颜色深度,并提供按钮、滑块、图表、键盘等30余种基础控件。其核心优势在于:
开发流程通常分为两步:先在PC上使用模拟器进行界面逻辑设计和验证,再移植到目标硬件平台进行最终调试。
推荐使用Linux或Windows + MSYS2,配合SDL2作为显示后端。以下是基于Ubuntu的搭建步骤:
sudo apt-get install libsdl2-devgit clone https://github.com/lvgl/lvgl.git
git clone https://github.com/lvgl/lv_drivers.git
git clone https://github.com/lvgl/lv_port_pc_eclipse.git # 或使用lv_port_linux_frame_buffer典型的模拟器项目包含以下文件:
main.c:主循环和初始化。lv_conf.h:LVGL核心配置。lv_drv_conf.h:驱动配置。main.c 初始化流程:
#include "lvgl/lvgl.h"
#include "lv_drivers/display/monitor.h"
#include "lv_drivers/indev/mouse.h"
int main(void) {
/* 初始化LVGL */
lv_init();
/* 初始化SDL显示驱动(分辨率为800x480) */
monitor_init();
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.flush_cb = monitor_flush;
disp_drv.hor_res = 800;
disp_drv.ver_res = 480;
lv_disp_drv_register(&disp_drv);
/* 初始化鼠标输入驱动 */
mouse_init();
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = mouse_read;
lv_indev_drv_register(&indev_drv);
/* 创建示例界面(按钮) */
lv_obj_t *btn = lv_btn_create(lv_scr_act());
lv_obj_set_pos(btn, 100, 100);
lv_obj_set_size(btn, 120, 50);
lv_obj_t *label = lv_label_create(btn);
lv_label_set_text(label, "Hello LVGL");
/* 主循环 */
while (1) {
lv_timer_handler(); /* 处理LVGL任务 */
lv_tick_inc(5); /* 提供5ms心跳 */
usleep(5000); /* 5ms延时 */
}
return 0;
}lv_conf.h 关键配置:
#define LV_COLOR_DEPTH 32 // 颜色深度
#define LV_MEM_CUSTOM 0 // 使用内置内存管理
#define LV_HOR_RES_MAX 800 // 最大水平分辨率
#define LV_VER_RES_MAX 480 // 最大垂直分辨率
#define LV_USE_PERF_MONITOR 1 // 开启性能监控(调试用)编译运行后,即可看到带有“Hello LVGL”按钮的窗口,支持鼠标点击交互。
lvgl/src至工程。LVGL通过lv_disp_drv_t结构体与显示硬件交互,核心为flush_cb回调。以下是SPI屏幕的刷新函数实现:
/* 显示缓冲区(双缓冲) */
#define DISP_BUF_SIZE (800 * 480)
static lv_color_t buf_1[DISP_BUF_SIZE];
static lv_color_t buf_2[DISP_BUF_SIZE];
void lv_port_disp_init(void) {
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
/* 分配缓冲区 */
lv_disp_buf_t *disp_buf = malloc(sizeof(lv_disp_buf_t));
lv_disp_buf_init(disp_buf, buf_1, buf_2, DISP_BUF_SIZE);
disp_drv.buffer = disp_buf;
disp_drv.flush_cb = disp_flush_cb;
disp_drv.hor_res = 800;
disp_drv.ver_res = 480;
lv_disp_drv_register(&disp_drv);
}
/* 刷新回调:将LVGL渲染好的像素数据发送到屏幕 */
static void disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) {
uint16_t w = area->x2 - area->x1 + 1;
uint16_t h = area->y2 - area->y1 + 1;
/* 设置ILI9341的写入窗口 */
ili9341_set_window(area->x1, area->y1, w, h);
/* 发送像素数据(假设SPI DMA方式) */
ili9341_send_color_data((uint16_t *)color_p, w * h);
/* 通知LVGL刷新完成 */
lv_disp_flush_ready(drv);
}LVGL通过lv_indev_drv_t读取输入事件。以XPT2046为例:
void lv_port_indev_init(void) {
static lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = touchpad_read;
lv_indev_drv_register(&indev_drv);
}
static bool touchpad_read(lv_indev_drv_t *drv, lv_indev_data_t *data) {
uint16_t x, y;
uint8_t pressed = xpt2046_read(&x, &y); // 读取原始坐标
if (pressed) {
/* 坐标校准(分辨率映射) */
data->point.x = map(x, TOUCH_X_MIN, TOUCH_X_MAX, 0, 800);
data->point.y = map(y, TOUCH_Y_MIN, TOUCH_Y_MAX, 0, 480);
data->state = LV_INDEV_STATE_PR;
} else {
data->state = LV_INDEV_STATE_REL;
}
return false; // 无需更多数据
}LVGL需要周期性调用lv_tick_inc(ms)以处理动画和超时。在STM32上通常使用SysTick或硬件定时器中断:
/* SysTick中断服务函数(每1ms调用) */
void SysTick_Handler(void) {
static uint32_t tick_cnt = 0;
tick_cnt++;
if (tick_cnt >= 5) { // 每5ms提供一次心跳
lv_tick_inc(5);
tick_cnt = 0;
}
}在main()函数的while(1)中,定期调用lv_timer_handler():
while (1) {
lv_timer_handler();
delay_ms(5);
}LV_COLOR_DEPTH与硬件匹配。LV_MEM_CUSTOM,改用外部PSRAM。LV_USE_GPU选项(若有硬件加速)。模拟开发与硬件移植是LVGL学习的两大支柱。通过PC模拟,开发者可以快速验证界面设计;通过细致移植,将成果真正落地到嵌入式设备。本文提供的代码框架已在实际项目中多次验证,读者可根据自身硬件调整驱动细节。随着LVGL 9.0版本的发布,其支持更高效的渲染管线、矢量图形和更多输入设备,未来嵌入式GUI开发将更加便捷高效。掌握这些基础,你将能够从容应对从智能家居到工业HMI的多样化界面开发需求。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。