news 2026/4/23 12:59:16

新手教程:完成LVGL移植并运行第一个GUI界面

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
新手教程:完成LVGL移植并运行第一个GUI界面

从零开始点亮LVGL:手把手教你完成移植并跑通第一个界面

你有没有遇到过这样的场景?项目需要一个带触摸操作的彩色屏幕,客户希望界面流畅、美观,最好还能有点动画效果。可你的主控只是个几百K Flash的STM32F407,连操作系统都没上,怎么办?

别慌——LVGL(Light and Versatile Graphics Library)就是为此而生的。

它不是那种动辄上百MB内存占用的桌面GUI框架,而是一个专为资源受限MCU设计的轻量级图形库。用得好,哪怕是一块2.8寸TFT屏,也能做出媲美智能设备的操作体验。

今天我们就来干一件实在事:不讲虚的,直接带你从零完成LVGL移植,点亮第一行“Hello LVGL!”文字。整个过程清晰、可复现,适合所有刚接触嵌入式GUI开发的新手。


为什么是LVGL?和其他GUI比有什么不一样?

在选型之前,很多人会纠结:到底该用TouchGFX、emWin还是LVGL?

简单说:

  • TouchGFX:好看,但贵,且绑定STM32硬件;
  • emWin:成熟稳定,但商业授权费用高;
  • LVGL:开源免费 + 模块化裁剪 + 社区活跃 —— 对于初创项目或学习者来说,几乎是唯一选择。

更重要的是,LVGL的设计哲学非常“嵌入式友好”:

  • 不依赖操作系统(裸机也能跑)
  • 支持双缓冲、脏区域刷新,极大降低CPU负载
  • 控件丰富(按钮、滑块、图表、列表…应有尽有)
  • 配置灵活,RAM/Flash占用可精确控制

换句话说:小资源,大功能

我们接下来要做的,就是把这套强大的图形引擎,“嫁接”到你的MCU系统中。


LVGL是怎么工作的?先搞懂它的底层逻辑

在动手前,得明白一件事:LVGL本身并不知道你是用SPI驱动ILI9341,还是用FSMC接的RGB屏。它只负责“画图”,怎么把图画到屏幕上,是你作为开发者必须告诉它的。

这就引出了LVGL的核心架构思想——抽象层解耦

它的运行流程其实很简单:

  1. 你在代码里创建一个按钮:lv_btn_create(...)
  2. LVGL内核计算这个按钮的位置、颜色、是否需要重绘
  3. 渲染器生成对应的像素数据(比如一段RGB565数组)
  4. 调用你事先注册的flush_cb函数,把这段数据“刷”进屏幕
  5. 同时,定时器不断推进时间轴,让动画和事件响应持续生效

整个过程就像一个流水线,而你要做的关键工作只有两个:

✅ 实现显示输出函数(即flush_cb
✅ 每隔1ms上报一次时间戳(tick)

只要这两步打通,LVGL就能自己跑起来。


第一步:把LVGL源码接入工程

最简单的办法是从 GitHub官方仓库 下载最新版本,将lvgl/文件夹整体复制到你的项目路径下,比如放在middleware/gui/lvgl

然后,在IDE中添加头文件搜索路径:

./middleware/gui/lvgl

接着,必须创建一个名为lv_conf.h的配置文件。这是强制要求!如果没有这个文件,LVGL会使用默认配置,极有可能导致内存溢出或编译失败。

你可以从lvgl/examples/templates/lv_conf_template.h复制一份改名即可,关键参数如下:

#define LV_COLOR_DEPTH 16 // 使用RGB565格式 #define LV_HOR_RES_MAX 320 // 最大水平分辨率 #define LV_VER_RES_MAX 240 // 最大垂直分辨率 #define LV_USE_LOG 1 // 开启日志输出(调试时很有用) #define LV_LOG_LEVEL LV_LOG_LEVEL_INFO #define LV_MEM_SIZE (32U * 1024U) // 动态内存池大小,根据板子调整

⚠️ 特别提醒:一定要在C/C++ defines中定义宏LV_CONF_H,否则LVGL不会识别该配置文件!

例如在Keil或STM32CubeIDE中添加:

LV_CONF_H="lv_conf.h"

这一步看似简单,却是后续一切正常运行的基础。


第二步:实现显示驱动——让图像真正出现在屏幕上

这是整个移植过程中最关键的一步:如何把LVGL生成的画面送到LCD上?

LVGL提供了一个结构体lv_disp_drv_t,你需要填充其中的回调函数,尤其是flush_cb

先看代码实现:

static void display_flush(lv_disp_drv_t * disp, const lv_area_t * area, lv_color_t * color_p) { uint32_t w = (area->x2 - area->x1 + 1); uint32_t h = (area->y2 - area->y1 + 1); // 调用底层LCD驱动,写入指定区域的RGB数据 lcd_write_frame_buffer(area->x1, area->y1, w, h, (uint8_t *)color_p); // 必须调用此函数通知LVGL:本次刷新已完成 lv_disp_flush_ready(disp); }
关键点解析:
  • area是待刷新的矩形区域(可能是全屏,也可能是局部变动)
  • color_p是指向像素数据的指针,格式由LV_COLOR_DEPTH决定(通常是RGB565)
  • lcd_write_frame_buffer()是你自己封装的LCD写函数(可通过FSMC、SPI+DMA等方式实现)

⚠️ 注意:如果你用的是慢速SPI屏,请务必启用DMA传输,并确保flush_cb不阻塞太久,否则帧率会严重下降。


第三步:注册显示设备并分配绘图缓冲区

有了刷新函数还不够,还需要一块“画布”供LVGL绘图使用——这就是绘图缓冲区(draw buffer)

它可以是单缓冲、双缓冲,也可以是行缓冲模式。对于性能有限的MCU,推荐使用“半屏缓冲”策略:

static lv_color_t draw_buf_1[LV_HOR_RES_MAX * 50]; // 约占屏幕高度1/5 static lv_color_t draw_buf_2[LV_HOR_RES_MAX * 50]; static lv_disp_buf_t disp_buf; lv_disp_buf_init(&disp_buf, draw_buf_1, draw_buf_2, LV_HOR_RES_MAX * 50);

然后初始化驱动结构体并注册:

lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.buffer = &disp_buf; disp_drv.flush_cb = display_flush; disp_drv.hor_res = 320; disp_drv.ver_res = 240; lv_disp_t * disp = lv_disp_drv_register(&disp_drv);

📌 小贴士:
- 缓冲区越大,绘图效率越高(减少重复绘制),但占用RAM越多
- 若RAM紧张,可设为每行缓冲(如上面的50行),LVGL会自动分块渲染


第四步:启动定时器服务——让GUI“活”起来

LVGL内部有许多异步任务:动画播放、输入去抖、窗口超时等等。这些都依赖一个核心机制:时间滴答(tick)

你需要保证每1ms向LVGL上报一次时间增量。

常见做法是在SysTick中断中调用:

void SysTick_Handler(void) { HAL_IncTick(); lv_tick_inc(1); // 告诉LVGL过去1ms }

或者在FreeRTOS中创建一个低优先级任务:

void lvgl_timer_task(void *pvParameters) { while(1) { lv_timer_handler(); // 推进LVGL内部定时器 vTaskDelay(pdMS_TO_TICKS(5)); } }

❗ 切记:如果忘了调用lv_timer_handler(),你会发现按钮按不动、动画卡住、触摸无反应——因为整个GUI系统“停摆”了。


第五步:创建你的第一个GUI界面

万事俱备,现在可以写UI代码了!

void create_first_gui(void) { // 创建一个标签对象 lv_obj_t * label = lv_label_create(lv_scr_act()); // 设置文本内容 lv_label_set_text(label, "Hello LVGL!"); // 居中对齐 lv_obj_align(label, LV_ALIGN_CENTER, 0, 0); }

就这么三行代码,就能在屏幕中央显示一行文字。

运行效果如下:

+----------------------------+ | | | | | Hello LVGL! | | | | | +----------------------------+

是不是很激动?恭喜你,已经成功迈出了嵌入式GUI开发的第一步!


常见问题排查指南(新手必看)

❓ 屏幕黑屏 or 花屏?

检查以下几点:

  • flush_cb是否正确调用了LCD写函数?
  • 颜色格式是否一致?LVGL输出的是RGB565吗?LCD控制器设置是否匹配?
  • 绘图缓冲区是否位于可访问内存?(例如SDRAM需提前使能时钟和初始化)

❓ 界面卡顿、刷新慢?

  • 提高绘图缓冲区大小(建议至少半屏)
  • 使用DMA传输代替CPU搬运数据(特别是SPI屏)
  • 检查flush_cb是否耗时过长(避免延时函数)

❓ 按钮无法点击?触摸没反应?

那是因为你还没有实现输入设备驱动

LVGL支持多种输入方式,最常见的就是触摸屏(TP)。只需再加几行代码:

static void touch_read(lv_indev_drv_t * drv, lv_indev_data_t * data) { if (touch_pressed()) { >
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 9:58:45

MyBatisPlus分页查询IndexTTS2用户生成记录数据表

MyBatisPlus分页查询IndexTTS2用户生成记录数据表 在AI语音合成系统日益普及的今天,如何高效管理海量用户操作日志,成为后端架构设计中不可忽视的一环。以“科哥”团队研发的新一代中文TTS系统IndexTTS2为例,其V23版本不仅在情感表达和自然度…

作者头像 李华
网站建设 2026/4/23 11:34:31

树莓派SPI总线应用手把手教程:驱动OLED显示屏从零实现

树莓派SPI驱动OLED实战:从点亮屏幕到图形界面的完整路径你有没有过这样的经历?买了一块OLED屏,插上树莓派,查了一堆资料,却卡在“为什么屏幕没反应”这一步。命令发了、接线对了、代码也跑通了——可屏幕就是黑的。别急…

作者头像 李华
网站建设 2026/4/23 12:43:58

通过逻辑分析仪观察奇偶校验时序:实操指南

用逻辑分析仪“看见”奇偶校验:从波形到协议的深度实战 你有没有遇到过这样的情况?系统偶尔传回一串乱码,日志里突然冒出几个“校验错误”,但示波器上看波形又“差不多正常”。这时候,传统的电压观测已经不够用了——…

作者头像 李华
网站建设 2026/4/23 11:34:03

LLM 推理中的数值非确定性与 RL 训推不一致的系统性解法

主题LLM 推理中的数值非确定性与 RL 训推不一致的系统性解法文末进群~时间2026.1.4 11:00 北京时间2026.1.3 22:00 美东时间直播预约🎙本次分享为全英文讲座!🌍视频号b站Youtube:https://youtube.com/live/4ay81dNuSR4内容即使将温…

作者头像 李华
网站建设 2026/4/23 11:25:55

Typora官网主题美化教程打造个性化写作空间

Typora 主题美化实战:打造专属的沉浸式写作环境 在数字创作愈发普及的今天,写作早已不只是内容的堆砌,而是一种思维与审美的双重表达。一款好的编辑器,不仅要“好用”,更要“顺眼”。Typora 作为 Markdown 编辑器中的…

作者头像 李华
网站建设 2026/4/23 14:50:08

通信信号处理流水线中的BRAM使用技巧:实战分享

通信信号处理流水线中的BRAM实战技巧:从原理到性能跃升在高速通信系统设计中,我们常常面临一个看似简单却极具挑战的问题:如何让数据“刚刚好”地到达下一个处理模块?想象这样一个场景——你正在设计一款5G毫米波接收机&#xff0…

作者头像 李华