ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LVGL 8.3.0嵌入式GUI开发实战:从STM32移植到HMI应用

LVGL 8.3.0嵌入式GUI开发实战:从STM32移植到HMI应用 简介LVGL 8.3.0嵌入式图形库官方源码包面向STM32、ESP32等微控制器平台的GUI开发者用于快速构建按钮、图表、滑动条、列表等交互界面解决嵌入式设备显示与触摸交互的开发需求。包内共1168个文件压缩后约24.53MB其中以449个C源码和208个头文件为核心覆盖全部库实现与API接口另含150个Python脚本、132个Markdown文档、55个RST文档以及字体、图片、示例工程等资源便于查阅文档、运行示例和二次开发。已有257人学习浏览适合希望深入理解LVGL内部机制或需要定制UI组件的开发者。源码包不仅提供完整库文件还附带演示程序、构建脚本与跨平台工程配置可直接集成到现有项目中通过阅读源码还能学习内存管理、绘图引擎和事件驱动等嵌入式GUI关键实现其开源社区与GitHub协作机制也为持续改进和问题排查提供了便利。 接手一个HMI项目屏幕分辨率480x272主控是M4内核要求界面带二级菜单、弹窗、实时数据刷新。第一反应是用之前的方案硬画但评估完工作量后还是决定换用LVGL 8.3.0这个版本的源码来做。为什么锁定在8.3.0而不是8.2或者更激进的9.x这篇文章就围绕这段时间用下来的真实体会展开覆盖源码结构、模拟器配置、对象体系、STM32移植、业务控件实现和版本迁移踩坑六个方面适合正在做嵌入式图形界面、准备把LVGL接到实际项目的朋友参考。1. LVGL 8.3.0源码包里的版本门道1.1 为什么偏偏是8.3.0LVGL的版本演进有个特点7.x和8.x的API差距巨大8.x和9.x之间也是大改。如果你打开官方仓库看master分支会发现代码结构跟8.3.0已经有不少区别。8.3.0恰好处于8.x系列的稳定成熟期新特性吸收了遗留BUG也修得差不多但还没有像9.x那样把底层渲染架构重排。对于做产品而不是追新的人来说这个版本是一个很稳的锚点。对比8.2.08.3.0的改动主要集中在事件系统lv_event_t参数调整、对象样式接口set_style系列函数、内存管理优化、部分控件行为修复。这些改动实践里感受最明显的是内存碎片问题比8.2好一些长时间运行刷新界面帧率波动变小。界面编辑器也基本都支持导出8.3格式的代码拿到手改起来顺手不用像老版本那样反复手动调整坐标。1.2 源码目录里哪些目录值得重点看拿到源码解压后第一反应是不要瞎翻。LVGL 8.3.0的目录结构其实非常清晰核心全部集中在src下面。我整理了一个实际会用到的目录对照表路径作用实际使用频率src/lv_core对象树、显示驱动、刷新核心lv_obj、lv_scr、lv_disp都在这里几乎每个项目都要改src/lv_hal硬件抽象层disp、indev、tick的接口定义移植必看src/lv_draw绘制引擎包括软件渲染和VDB缓冲大部分时候不用动src/lv_widgets控件库按钮、标签、列表、图表都在这天天打交道src/lv_font字体加载和内置字体换字体必看src/lv_misc内存分配、日志、定时器、任务调度遇到卡顿要看src/lv_themes主题系统可以快速换皮肤风格做美化时改lv_conf_template.h配置文件模板必须改成lv_conf.h并打开宏一个容易忽略的细节examples和demos目录不要整包编译进工程否则单片机根本塞不下。我见过太多新手把整个lvgl文件夹一股脑add到keil工程里编译直接爆ROM。正确做法是只编译src下的所有.c文件然后按需单独添加examples里的参考文件。1.3 版本来源建议不要直接从master分支拉代码LVGL的master经常处于开发状态API会临时变动。我建议在GitHub的release页面下载8.3.0的zip包或者打tag后clone。下载后先核对一下文件版本号打开lv_version.h里面应该有LVGL_VERSION_MAJOR 8、LVGL_VERSION_MINOR 3、LVGL_VERSION_PATCH 0这样的定义。移植时如果连不上外网也可以从国内镜像站拉但要注意拉完比对MD5防止下到被修改过的包。2. 先用模拟器把业务逻辑跑通再碰硬件2.1 为什么强烈建议模拟器先行LVGL的调试有个特性界面刷新和人机交互逻辑很难在单片机上下断点观察尤其遇到触摸坐标偏移、页面切换闪烁这类问题在硬件上排查非常痛苦。模拟器里有完善的日志输出和内存监测改完代码CtrlB就能看效果不用擦写Flash整个迭代效率能提升五倍以上。我的习惯是先在PC上把页面布局和业务逻辑全部跑通确认没有明显问题后再移植到开发板这样实际芯片调试周期会大幅缩短。2.2 Code::Blocks下的配置过程模拟器方式有很多官方推荐Code::Blocks MinGW我也用这个。步骤留意一下容易踩坑安装Code::BlocksMinGW组件勾选完整编译器。新建一个空控制台工程把lvgl目录拷贝到项目根目录下工程中只添加src目录下所有.c文件不要递归添加examples。同时添加几个官方模拟器专用文件lv_drivers/win32drv下的monitor.c、mouse.c还有lv_drivers/indev下的相关驱动源文件。将lv_conf_template.h复制到项目根目录并重命名为lv_conf.h同时把工程里的include路径加上项目根目录。最关键的是在编译选项里定义宏LV_CONF_INCLUDE_SIMPLE这样源码里#include lv_conf.h才能找到你的配置文件。添加链接库winmm、gdi32、user32。配置完成后跑一个最简单的demo在main函数里调用lv_init()创建屏幕分辨率然后循环调用lv_timer_handler()和monitor驱动刷新函数。运行起来如果黑屏先查分辨率设置是否和monitor.c里的一致再看颜色深度。2.3 模拟器里需要提前调的参数在lv_conf.h里LV_HOR_RES_MAX和LV_VER_RES_MAX改成目标屏的分辨率LV_COLOR_DEPTH根据屏的色深设置常见的是16或32。另外建议把LV_MEM_SIZE调大一点我一般设64*1024因为模拟器里如果内存池太小跑demo时出现莫名其妙的现象还以为是显示问题其实是内存不够。字体也是模拟器里容易拉闸的点内置的Montserrat 14字号偏小LCD屏上效果一般提前准备几个不同大小的字体文件能省不少后续折腾。3. 8.3.0的对象体系与事件交互逻辑3.1 对象树机制要往DOM方向上理解LVGL 8.3.0的UI结构本质上是一棵对象树。最顶层是屏幕lv_obj屏幕上可以创建容器容器里再放按钮、标签、列表等。每个控件就是一个节点父节点移动子节点跟着动父节点销毁子节点也没了。如果写过前端这个模型几乎和HTML的DOM节点一模一样——容器类似div按钮类似button标签就是span。页面切换的核心是活动屏幕。默认只有一个screen但你可以创建多个屏幕对象然后用lv_scr_load(new_scr)做切换。这是做多页面应用的基础。要创建一个新页面先lv_obj_create(NULL)获取一个屏幕级对象然后在上面铺控件最后在事件回调里调用lv_scr_load即可。注意创建屏幕时参数传NULL而不是传父对象否则就不是屏幕而是容器了。3.2 控件创建与事件绑定的标准步骤以创建一个按钮为例完整流程如下/* 在当前活动屏幕上创建一个按钮 */ 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, -40); 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, OK); lv_obj_center(label);事件回调里通过lv_event_get_target获取实际触发事件的控件再用user_data传递参数。这个设计比旧版的回调只带一个lv_obj_t参数要清晰很多因为同一套回调可以服务多个同类控件通过user_data区分是谁触发的。static void btn_event_cb(lv_event_t *e) { lv_obj_t *target lv_event_get_target(e); uint32_t id (uint32_t)lv_event_get_user_data(e); if (id 1) { /* 第一个按钮的逻辑 */ } else if (id 2) { /* 第二个按钮的逻辑 */ } }3.3 事件类型怎么选不迷路LVGL 8.3.0里事件类型非常多常用的就是LV_EVENT_CLICKED、LV_EVENT_VALUE_CHANGED、LV_EVENT_PRESSING、LV_EVENT_RELEASED、LV_EVENT_SCROLL_BEGIN等。对于滑块、开关这类有数值变化的控件用LV_EVENT_VALUE_CHANGED别用CLICKED否则你拿到的值永远是拖动前的旧值。对于下拉列表、滚动列表优先监听LV_EVENT_VALUE_CHANGED获取选中项。列表项点击的LV_EVENT_CLICKED事件实际是列表本身向上抛的如果你给列表项单独绑回调反而容易冲突。3.4 样式系统的一个常见误区8.3.0支持通过lv_obj_set_style_bg_color、lv_obj_set_style_border_color这类接口快速设置样式覆盖原有的低级API。新手容易犯的错是给不同状态加样式时直接改本地样式结果一按下去样式就丢失。正确做法是用LV_STATE_PRESSED状态修饰比如lv_obj_set_style_bg_color(btn, lv_color_hex(0x2196F3), LV_STATE_DEFAULT); lv_obj_set_style_bg_color(btn, lv_color_hex(0x1976D2), LV_STATE_PRESSED);第三个参数填入LV_STATE_*枚举控件在不同状态下会自动切换样式不用手动监听PRESS事件去改颜色。4. STM32移植适配要点三条接口线和一只内存拦路虎4.1 底层需要接的三件事移植LVGL 8.3.0到STM32说破天也就三件事显示驱动flush回调、输入设备触摸或按键、时基tick。显示驱动是核心。总流程是LVGL内部绘制完成后调用你注册的flush_cb函数把一块缓冲区的内容刷到屏幕。如果你的屏是SPI接口要重点注意DMA的使用——把LVGL的buf地址直接交给DMA发送发送完成后调用lv_disp_flush_ready通知LVGL这个buffer可以继续画了。如果发送还没结束LVGL就来要下一块缓冲就会出现撕裂或花屏。我采用双缓冲策略来规避这个问题Buffer1交给DMA发送时LVGL实际在画Buffer2两个区域交替使用帧率明显提升。输入设备不一定非要有触摸。用按键加焦点移动也能操作UI寄存器struct lv_indev_drv_t时type选LV_INDEV_TYPE_KEYPAD然后在回调里返回按键值即可。很多工控屏用物理按键导航也没问题。4.2 lv_conf.h里的关键配置移植时最容易出问题的配置项我直接整理成清单LV_COLOR_DEPTH务必和屏的色深一致16位颜色就设16否则颜色会严重失真。LV_MEM_SIZELVGL内置的内存池大小。F103C8T6这种64KB RAM的芯片我建议设到8KB到16KB界面简单够用F429这类大内存芯片可以设32KB以上。LV_DISP_DEF_REFR_PERIOD刷新周期单位毫秒默认30。追求流畅可以改到16但CPU占用会明显上升。LV_DPI_DEF默认DPI值影响控件尺寸换算屏的实际物理尺寸决定这个值。LV_FONT_MONTSERRAT_14默认字体开关小了打开省了没办法显示文本。4.3 小内存芯片的适配思路如果非要往F103C8T6这种小内存芯片上塞一定得砍功能。标准做法是关闭不用的控件宏比如LV_USE_CHART、LV_USE_CALENDAR、LV_USE_MSGBOX这些如果不用就关掉。能节省大量内存和Flash。另外把LV_FONT_MONTSERRAT_20和24等大字号关掉只保留一个常用字号。可以把LV_USE_LOG改成LV_LOG_LEVEL_WARN减少日志输出占用的资源。性能优化上LV_COLOR_DEPTH16比32渲染速度快很多因为每个像素少了一半数据量。局部刷新LV_INV_BUF_SIZE也很关键LVGL默认只刷新变化区域但如果你把LV_DISP_DEF_REFR_PERIOD设得太低会频繁触发整屏刷新反而变慢。实际优化效果需要上机验证我自己的经验是先从颜色深度和禁用不用的控件入手这两步收益最大。5. 三个高频业务控件弹窗、菜单、时间5.1 弹窗标准组件还是自定义容器LVGL 8.3.0里内置了lv_msgbox组件可以直接创建模态弹窗标题、内容、按钮全带。但实际用过几次后发现如果要自定义弹窗样式直接操作lv_msgbox常常不如自己用容器搭。推荐的做法是创建一个覆盖全屏的透明容器位置在屏幕最上层背景半透明遮罩中间放一个不透明的面板容器面板里放标题标签、正文标签、按钮。这样弹窗的样式变量全在你手里后续换肤、改动画都方便。弹窗关闭时注意对象销毁问题。如果弹窗是用动态创建的关闭回调里记得调用lv_obj_del并且把相关的user_data一并清理否则内存泄漏。5.2 菜单lv_list和lv_btnmatrix两种路线菜单是HMI最常见的结构。LVGL 8.3.0提供了lv_list用来做纵向列表非常方便每项可以带图标和文本点击事件用lv_obj_add_event_cb注册到list上通过lv_event_get_target判断具体点击了哪一行。lv_list的问题在于无法直接做九宫格样式的桌面式菜单这时用lv_btnmatrix更合适。btnmatrix靠一个字符串数组控制按钮布局维护起来很直观适合做4x2、3x3这种网格菜单。如果你需要侧滑返回上一级就得自己用lv_obj创建页面容器然后监听LV_EVENT_GESTURE做手势识别。8.3.0对手势事件支持不错向左滑动切换页面体验很顺手。5.3 时间显示LVGL没有内置时钟源很多新手以为LVGL自带当前时间控件实际上LVGL只负责显示不负责计时。你需要自己维护一个秒计数可以是RTC中断、定时器毫秒计数或者外部时间芯片。界面上用一个定时器lv_timer_create周期性刷新标签建议周期500ms或1000ms太频繁会造成不必要的重绘。代码思路很简单static void timer_cb(lv_timer_t *timer) { static char time_str[16]; /* 假设get_rtc_second()从RTC获取当前秒数 */ get_rtc_time(tm); lv_snprintf(time_str, sizeof(time_str), %02d:%02d:%02d, tm.hour, tm.min, tm.sec); lv_label_set_text(time_label, time_str); }需要注意的是lv_timer_create的回调需要尽量短不要在回调里做耗时的RTC读取、I2C访问否则会卡断LVGL的刷新循环。最好是在另外一个线程或中断里更新缓存变量LVGL定时器只负责读缓存和更新界面。6. 从旧版本迁移到8.3.0的踩坑复盘6.1 7.x升级到8.x的API大换血如果是从LVGL 7.x迁过来几乎等于重写界面代码。最典型的变化包括lv_disp_buf_init和lv_disp_drv_register的参数结构变了旧代码里lv_disp_drv_t的初始化方式完全失效样式从lv_style_list_t改为直接用函数设置事件回调从lv_event_cb_t(lv_obj_t*, lv_event_t)改为lv_event_cb_t(lv_event_t*)lv_scr_act()仍保留但行为类似。我用一个对照表辅助大家理解功能7.x写法8.3.0写法设置背景色lv_style_set_bg_color(style, LV_STATE_DEFAULT, color)lv_obj_set_style_bg_color(obj, color, 0)绑定点击事件lv_obj_set_event_cb(obj, cb)lv_obj_add_event_cb(obj, cb, LV_EVENT_CLICKED, NULL)创建容器lv_cont_create(parent, NULL)lv_obj_create(parent)页面切换lv_scr_load(scr)lv_scr_load(scr)如果项目代码量大建议先编译让编译器先报错再逐个函数查阅手册。编译报错才是最快定位API变更的方法。6.2 8.2到8.3的兼容性细节8.2到8.3相对平滑大方向上API基本不变。但有几个细节会影响编译LV_EVENT_*枚举的数值重排过如果你的代码里直接硬编码事件数字例如0x06一定要改成宏名另外lv_conf.h里有些宏被改名了比如LV_USE_THEME_DEFAULT的写法调整。升级前先diff一下自己用的lv_conf.h和官方8.3.0的lv_conf_template.h把废弃宏找出来改掉。如果你是从8.3.0升到8.3.x的小版本则注意目录结构没有变化直接替换源码包即可但lv_conf.h保留因为8.3系列兼容性很好。6.3 常见的性能误区界面卡顿不一定是LVGL本身慢反而最常见的原因是刷新缓冲配错了。单缓冲模式下如果flush_cb没有快速调用lv_disp_flush_ready会导致LVGL一直等待。改用双缓冲加DMA后刷新率会立竿见影。另一个常见坑是频繁调用lv_obj_align会导致重新布局计算在动画或数据刷新中要避免每帧都调用改用lv_obj_set_pos并配合LV_EVENT_SIZE_CHANGED做一次性对齐。还有一点容易被忽略LVGL的坐标和尺寸单位是像素但如果LV_DPI_DEF设置过大某些控件如lv_btnmatrix的默认间距会大得惊人。遇到了看布局诡异先查DPI。最后的体会是LVGL 8.3.0整体是一个非常成熟稳定的版本用好了在资源紧张的单片机上也能做出流畅的多页面交互界面。刚开始移植不要急着上复杂动画先把对象树、事件、样式、刷新机制搞透很多所谓奇怪问题其实都是这几个基础点没吃透导致的。想快速验证某个功能时我都是开着模拟器随手写个demo跑通了再拿去做硬件适配这个方法你们可以直接抄。本文还有配套的精品资源点击获取
返回列表