news 2026/4/18 12:04:50

LVGL新手教程:从零实现一个简单按钮界面

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LVGL新手教程:从零实现一个简单按钮界面

从零开始用 LVGL 搭出一个能点的按钮:新手实战指南

你有没有过这样的经历?手头一块 STM32 或 ESP32 开发板,接了个小屏幕,想做个带“点击”功能的界面——比如按一下启动某个功能。但一查资料发现,GUI 太重跑不动,裸机画图又太麻烦?

别急,LVGL就是为这种场景而生的。

它轻得惊人(最低只要 16KB RAM),功能却很完整:按钮、滑动条、动画、主题全都有,还支持触摸输入。最关键的是——它是开源免费的,社区活跃,文档齐全。

今天我们就来手把手实现一个可点击、会变文字的按钮界面,不讲虚的,只写你能直接用的代码,帮你跨过那道“看得懂但做不出来”的坎。


先搞清楚:LVGL 到底是怎么工作的?

在动手前,先建立一个清晰的认知模型。你可以把 LVGL 想象成一个“嵌入式世界的网页引擎”。

  • 它不直接控制屏幕或触摸芯片;
  • 而是通过你提供的“回调函数”,间接和硬件打交道;
  • 你在上面创建各种控件(像 HTML 标签一样);
  • 用户操作时,它自动找到目标控件并通知你。

整个过程非阻塞,可以轻松集成进主循环或者 RTOS 中。

核心三步走:

  1. 初始化 LVGL 内核
  2. 注册显示和输入驱动
  3. 创建 UI + 绑定事件
  4. 循环调用任务处理函数

下面我们一步步拆解。


第一步:初始化 LVGL ——lv_init()必须最先调

所有 LVGL 程序都从这句开始:

#include "lvgl.h" void app_main(void) { lv_init(); // ← 所有 LVGL API 的起点! }

就这么简单?没错。

但这背后做了不少事:
- 初始化内存池(用于分配对象)
- 准备样式系统与动画引擎
- 设置默认日志输出

⚠️ 注意:lv_init()只负责软件初始化,不涉及任何硬件操作。真正的硬件对接要靠后续的驱动注册。

这个函数依赖一个配置文件lv_conf.h,你需要确保工程中已包含它,并根据你的 MCU 资源调整关键参数,例如:

#define LV_MEM_SIZE (32U * 1024U) // 分配 32KB 内存池 #define LV_COLOR_DEPTH 16 // 使用 RGB565 颜色格式 #define LV_HOR_RES_MAX 320 #define LV_VER_RES_MAX 240

没有这个文件?去 LVGL GitHub 下载源码,复制一份lv_conf_template.h改名为lv_conf.h即可。


第二步:连接屏幕 —— 注册显示驱动

LVGL 不知道你怎么驱动 ILI9341 或 ST7789,但它提供了一个标准接口:只要你告诉它“如何把像素数据刷到屏幕上”,它就能自己算好该画哪一块区域。

这就是显示驱动(Display Driver)的作用。

关键结构体:lv_disp_drv_t

你需要做三件事:
1. 定义一块绘制缓冲区(Draw Buffer)
2. 实现刷新回调flush_cb
3. 填充分辨率等信息并注册

来看代码:

// 缓冲区大小建议至少一行宽度 × 10 行像素 static lv_color_t disp_buf_area[320 * 10]; static lv_disp_draw_buf_t draw_buf; // 刷新回调:将 LVGL 计算好的脏区域写入 LCD void my_flush_cb(lv_disp_drv_t * disp_drv, 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驱动函数写入指定区域 lcd_write_pixels(area->x1, area->y1, w, h, (uint16_t *)color_p); // ✅ 必须调用!否则 LVGL 会认为屏幕还在忙 lv_disp_flush_ready(disp_drv); } // 注册显示驱动 void register_display_driver(void) { static lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.hor_res = 320; disp_drv.ver_res = 240; disp_drv.flush_cb = my_flush_cb; disp_drv.draw_buf = &draw_buf; // 初始化绘制缓冲区 lv_disp_draw_buf_init(&draw_buf, disp_buf_area, NULL, 320 * 10); // 向 LVGL 注册驱动 lv_disp_drv_register(&disp_drv); }

📌重点提醒
-flush_cb是核心,必须尽快完成传输;
- 如果使用 DMA,记得在 DMA 中断里调用lv_disp_flush_ready()
-忘记调lv_disp_flush_ready()是导致界面卡死最常见的原因!


第三步:接入触摸屏 —— 注册输入设备

现在屏幕能画了,但用户没法交互。我们需要让 LVGL “知道”手指点在哪。

LVGL 把触摸屏归类为“指针型设备”(Pointer Device),只需要你实现一个读取回调即可。

static lv_indev_drv_t indev_drv; void my_touch_read_cb(lv_indev_drv_t * drv, lv_indev_data_t * data) { if (touch_pressed()) { // 检测是否按下 >// 创建按钮,父对象是当前屏幕 lv_obj_t * btn = lv_btn_create(lv_scr_act()); lv_obj_set_size(btn, 120, 50); lv_obj_align(btn, LV_ALIGN_CENTER, 0, 0); // 居中对齐 // 创建标签作为按钮的子对象 lv_obj_t * label = lv_label_create(btn); lv_label_set_text(label, "Click Me"); lv_obj_center(label); // 自动居中标签

💡 解释一下:
-lv_scr_act()返回当前活动屏幕,相当于 HTML 中的<body>
- 按钮 (lv_btn) 本质是一个容器,标签 (lv_label) 作为其子元素会随父级移动;
-lv_obj_align(..., LV_ALIGN_CENTER, ...)是 LVGL 提供的强大布局工具,免去手动计算坐标。

此时运行程序,你应该已经看到屏幕上出现了一个灰色矩形按钮。


第五步:让按钮“活起来”——绑定点击事件

光有按钮不够,我们希望点击后能计数并更新文字。

这就需要用到事件机制

如何监听点击?

给按钮添加一个事件回调函数:

void btn_event_cb(lv_event_t * e) { lv_event_code_t code = lv_event_get_code(e); // 获取事件类型 lv_obj_t * btn = lv_event_get_target(e); // 获取触发事件的对象 static uint8_t click_count = 0; if (code == LV_EVENT_CLICKED) { click_count++; printf("按钮被点击了 %d 次\n", click_count); // 修改按钮上的文字 lv_obj_t * label = lv_obj_get_child(btn, 0); // 获取第一个子对象(即 label) char buf[32]; sprintf(buf, "Clicked %d", click_count); lv_label_set_text(label, buf); } }

然后把这个回调“挂上去”:

lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL);

📌 小贴士:
-LV_EVENT_ALL会监听所有事件,适合调试;
- 正式项目建议改为LV_EVENT_CLICKED,减少不必要的回调开销;
- 事件是在lv_task_handler()中被检测和分发的,所以必须周期性调用它。


最后一步:启动 GUI 引擎 ——lv_task_handler()

LVGL 不是实时响应中断的系统,而是基于定时轮询的方式工作。

你需要在一个循环中定期调用:

while(1) { lv_task_handler(); // ← GUI 的“心跳” lv_tick_inc(5); // 手动递增系统滴答(每5ms一次) vTaskDelay(pdMS_TO_TICKS(5)); }

📌lv_tick_inc()替代了硬件定时器中断的作用,告诉 LVGL 时间过去了多少毫秒,用于动画、长按判定等功能。

如果你用了 FreeRTOS,可以把lv_task_handler()放在一个低优先级任务中执行。


整体流程串一遍

回顾一下完整流程:

int main(void) { system_init(); // 初始化 MCU、时钟等 lv_init(); // 1. 初始化 LVGL register_display_driver(); // 2. 注册显示驱动 register_touch_driver(); // 3. 注册触摸驱动 create_ui(); // 4. 创建按钮和标签 // 5. 绑定事件(在 create_ui 内完成) while(1) { lv_task_handler(); // 6. 处理 GUI 任务 lv_tick_inc(5); delay_ms(5); } }

只要这几步都走通,你的按钮就能正常响应点击,文字也会跟着变化!


新手常踩的坑 & 实战建议

❌ 常见问题一:界面卡死不动

  • 原因:没调lv_disp_flush_ready()
  • 解决:确保在flush_cb结束前调用该函数,尤其是使用 DMA 时要在传输完成中断中调。

❌ 常见问题二:触摸不准或无反应

  • 检查点
  • read_cb是否正确返回坐标?
  • 触摸 IC 驱动是否正常工作?
  • 是否需要校准坐标系?(某些模块 X/Y 轴可能翻转)

✅ 性能优化建议

  • 绘制缓冲区大小:建议至少一行宽 × 10 行高。太小会导致频繁重绘,太大吃内存。
  • 避免在事件回调中干重活:如开启电机、发网络请求。应设标志位,由主循环处理。
  • 调用频率lv_task_handler()每 5~10ms 调一次最佳,太快浪费 CPU,太慢动画卡顿。

✅ 进阶方向

学会了基础按钮,下一步你可以尝试:
- 添加多个页面切换(用lv_obj_clean(lv_scr_act())清屏)
- 使用lv_label_set_text_fmt()显示变量值
- 引入主题美化界面(lv_theme_default_init()
- 创建自定义控件组合(如带图标+文字的按钮)


写在最后:为什么你应该学 LVGL?

在这个万物互联的时代,哪怕是最简单的设备,也越来越多地配备了显示屏和触控能力。无论是智能插座、温控面板,还是工业 HMI,图形界面正成为标配。

LVGL正是以极低门槛,让你在资源有限的 MCU 上也能做出专业级交互体验的利器。

它不像 TouchGFX 那样依赖特定硬件,也不像 Qt 那样臃肿。它的设计理念就是:简单、灵活、可裁剪、跨平台

今天我们只做了一个按钮,但你已经掌握了 LVGL 的核心脉络:
- 初始化 → 驱动对接 → 控件创建 → 事件响应 → 主循环驱动

这就像搭好了骨架,剩下的肌肉和皮肤,都可以一步步加上去。


如果你正在做毕业设计、产品原型,或是单纯想玩转彩色屏,不妨试试照着这篇教程跑一遍。
当你亲手看到那个按钮随着点击不断改变数字时,那种“我真的做出来了”的成就感,绝对值得。

💬 动手过程中遇到问题?欢迎留言交流。一起把嵌入式图形界面玩明白!

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

零基础学RS485通讯:全面讲解总线拓扑结构

零基础也能搞懂RS485&#xff1a;拓扑结构决定通信成败你有没有遇到过这样的情况&#xff1f;明明代码写得没问题&#xff0c;Modbus协议解析也对&#xff0c;可就是有些从站时不时“失联”、数据乱码&#xff0c;换根线又好了——结果第二天故障重现。折腾半天&#xff0c;最后…

作者头像 李华
网站建设 2026/4/18 8:15:19

screen命令在断网环境下的调试应用操作指南

断网不断程&#xff1a;用screen构建高可用远程调试环境你有没有过这样的经历&#xff1f;深夜正在远程烧录固件&#xff0c;眼看着进度条走到 90%&#xff0c;突然 Wi-Fi 切换、4G 信号丢失&#xff0c;SSH 连接一断&#xff0c;终端里的任务瞬间“消失”。刷新会话后发现&…

作者头像 李华
网站建设 2026/4/18 8:49:01

SCAU期末笔记 - 计算机网络雨课堂习题整理

我以为只有PDF题库的 怎么还有个雨课堂习题要复 算了算了开始整理吧一百多道题我搞不动了&#xff0c;主要精力还是准备留给pdf题库&#xff0c;这个就全靠豆包大人发力了 1.多选题 计算机网络的时延由&#xff08; &#xff09;组成。A.传播时延B.发送时延C.排队时延D.处理时延…

作者头像 李华
网站建设 2026/4/18 11:04:27

深度解析|当 Prometheus 遇见大模型:解密下一代智能监控体系

导读在云原生时代&#xff0c;Prometheus Alertmanager 虽然解决了“看得见”的问题&#xff0c;却无法解决“看得懂”和“看得早”的难题。运维团队往往陷入“故障发生->收到告警->紧急救火”的被动循环。 本文将探讨如何利用 AI 大模型技术赋能现有监控体系&#xff0…

作者头像 李华
网站建设 2026/4/18 8:28:24

L298N典型应用电路搭建手把手教程

手把手教你用L298N驱动直流电机&#xff1a;从零搭建稳定控制电路你有没有遇到过这样的情况&#xff1f;写好了Arduino程序&#xff0c;信心满满地给小车通电&#xff0c;结果电机纹丝不动——或者只转一个方向&#xff0c;还“嗡嗡”发热。别急&#xff0c;问题很可能出在电机…

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

Java Web 车辆管理系统系统源码-SpringBoot2+Vue3+MyBatis-Plus+MySQL8.0【含文档】

摘要 随着城市化进程的加快和私家车保有量的持续增长&#xff0c;车辆管理成为城市治理的重要课题。传统车辆管理方式依赖人工登记和纸质档案&#xff0c;存在效率低下、数据易丢失、查询困难等问题。信息化技术的普及为车辆管理提供了新的解决方案&#xff0c;通过构建智能化的…

作者头像 李华