1. 为什么选择LVGL和GUI Guider?

如果你正在为一块小小的嵌入式屏幕发愁,想做出像手机App一样流畅、漂亮的界面,但又觉得从零写代码画像素点太折磨人,那你来对地方了。几年前我做第一个嵌入式UI项目时,用纯C语言在单片机上硬画按钮和滑块,光是处理触摸反馈和动画就掉了一大把头发。直到我遇到了LVGL,才真正体会到什么叫“解放生产力”。

简单来说,LVGL 是一个用C语言编写的开源图形库,专为资源受限的嵌入式设备而生。它五脏俱全,按钮、滑块、图表、动画、甚至复杂的3D效果都能搞定,而且对内存和CPU非常友好。而 GUI Guider 是恩智浦(NXP)推出的一款免费、拖拽式的UI设计工具,它就像嵌入式界的“Figma”或“Sketch”,让你用鼠标点点拖拖就能设计出界面,然后一键生成LVGL的C代码。这两者结合,相当于你既有了强大的发动机(LVGL),又有了好用的自动变速箱(GUI Guider),开发嵌入式UI就从“手搓零件”升级到了“组装整车”的体验。

我选择它们,最看重的就是效率美观。以前做一个复杂的设置页面,可能要写一周,现在用GUI Guider拖拽布局,半小时搞定视觉,再花点时间绑定事件逻辑,一天就能出原型。这对于产品快速迭代、验证想法至关重要。而且,LVGL的社区非常活跃,版本更新快,遇到问题很容易找到解决方案。接下来,我就带你从最基础的环境搭建开始,一步步走进这个高效又好看的嵌入式UI世界。

2. 搭建你的开发环境:稳扎稳打第一步

万事开头难,但把环境搭好,后面就一马平川了。这里我们分两步走:先是准备好LVGL的“运行环境”,然后是GUI Guider这个“设计工厂”。

2.1 获取与移植LVGL:让图形库在你的板子上跑起来

首先,你需要一块能跑LVGL的开发板。常见的STM32系列(如F4/F7/H7)、ESP32、甚至是树莓派Pico,只要Flash和RAM别太小(通常Flash>128KB, RAM>16KB就能跑基础功能),基本都支持。我手头用的是一块STM32F429 Discovery板,因为它自带TFT屏和SDRAM,玩UI很舒服。

第一步:获取LVGL源码。 最稳妥的方式是去LVGL的GitHub仓库(https://github.com/lvgl/lvgl)下载最新稳定版。我建议直接下载ZIP包,或者用git克隆。记住,别用太老的版本,新版本(如v8.3以上)的API更友好,功能也更强大。

第二步:将LVGL源码加入你的工程。 这是“移植”的核心。无论你用的是Keil、IAR、还是STM32CubeIDE,步骤都类似:

  1. 在你的工程目录下(比如 DriversMiddlewares 同级),新建一个文件夹,就叫 lvgl
  2. 把下载的LVGL源码包里所有文件和文件夹(除了 examplesdemos,初期可以不要)都拷贝到这个 lvgl 文件夹里。
  3. 在你的IDE中,将这些源文件(主要是 lvgl/src 下的所有 .c 文件)添加到工程的项目树中。别忘了把头文件路径(lvgllvgl/src 等)也添加到编译器的包含路径(Include Paths)里。

第三步:配置显示和输入驱动。 LVGL本身不直接操作硬件,它需要你提供几个“端口”函数。幸运的是,LVGL提供了模板。在 lvgl/examples/porting 目录下,找到 lv_port_disp.c(显示)、lv_port_indev.c(输入设备,如触摸屏)等文件。把它们也拷贝到你的工程里,然后根据你的硬件修改。 以显示驱动为例,你需要修改 lv_port_disp.c 里的一个关键函数:disp_flush。这个函数的作用是把LVGL画好的一块内存区域(buffer)里的颜色数据,搬运到你的屏幕显存里。如果你用的是STM32和LTDC(液晶控制器),可能只需要调用HAL库的DMA2D填充函数。如果你用的是SPI屏,那就需要实现一个写显存数据的SPI发送函数。

这里有个我踩过的坑:双缓冲(Double Buffering)。在 lv_conf.h(LVGL的配置文件)里,你可以设置 LV_USE_DOUBLE_BUFFER 为1。这会让LVGL使用两块绘图缓冲区,当它在后台缓冲区绘制下一帧时,前台缓冲区的内容正在被发送到屏幕,这样能有效避免屏幕撕裂,让动画更流畅。但前提是你的RAM足够放下两块缓冲区(比如320*240的RGB565屏幕,一块缓冲区就要150KB!)。如果RAM紧张,就用单缓冲,或者减小缓冲区大小(LV_DISP_BUF_SIZE),LVGL会自动分块刷新。

2.2 安装与配置GUI Guider:启动你的可视化设计器

LVGL在板子上跑通后,我们再来布置设计端。GUI Guider是图形化设计工具,它依赖于Java环境。

第一步:安装Java JDK。 去Oracle官网或OpenJDK网站下载JDK 11或16(版本别太老,也别用最新的预览版,求稳)。下载后运行安装程序,一路下一步就行。安装完成后,需要设置环境变量,这是很多新手会卡住的地方。

  • 新建系统变量 JAVA_HOME,变量值是你的JDK安装路径,比如 C:\Program Files\Java\jdk-11.0.15
  • 编辑系统变量 Path,添加一条新条目:%JAVA_HOME%\bin。 验证是否成功:打开命令行(CMD),输入 java -version,如果能看到版本号信息,就说明配置对了。

第二步:安装并运行GUI Guider。 去NXP官网找到GUI Guider的下载页面,选择对应你操作系统的安装包(Windows通常是.exe)。安装过程很简单。第一次运行时,它会让你选择一个工作空间(Workspace)目录,用来存放你的项目文件,选个顺眼的位置就行。

第一次打开GUI Guider,你可能会觉得界面有点复杂,别慌。中间最大的区域是画布(Canvas),你在这里拖放控件。左边是控件工具箱(Widgets),各种按钮、标签、滑块都在这里。右边是属性面板(Properties)和项目资源管理器(Project Explorer)。我们先熟悉一下,下一章再开始动手设计。

3. 搞定中文字库:让你的界面“说中文”

默认的LVGL只包含西文字符,显示中文就是一堆方框。添加中文字库是中文项目必经的一步。网上有些教程比较老,用起来麻烦,现在LVGL官方提供了非常方便的在线工具,我们用它来生成字库。

3.1 使用官方在线工具生成字库

LVGL官方有一个“在线字体转换器”(LVGL Online Font Converter),地址在LVGL官网能找到。这个工具是神器,完全免费。

  1. 准备字体文件:你需要一个 .ttf.otf 格式的中文字体文件。注意版权!如果是商业项目,务必使用开源字体或购买商用授权。个人学习和测试,可以用系统自带的,或者像我一样,喜欢用“阿里巴巴普惠体”,它是阿里巴巴发布的免费可商用字体,字形现代清晰。
  2. 打开转换器网页:你会看到一个表单。
    • Name:给你的字库起个名字,比如 my_font_16_cn
    • Size:字体大小(像素高度),比如16。嵌入式设备上,16px是阅读比较舒服的常用大小。
    • Bpp (Bits per pixel):抗锯齿位数。选1就是黑白,选2或4会有灰度抗锯齿,显示更平滑,但生成的字体文件体积会变大,渲染也更耗CPU。对于小字号(16px以下),我实测用 Bpp=1 效果完全可以接受,先保证性能。
  3. 选择字符范围:这是关键!你不能把整个中文字库(好几万个字)都塞进去,设备存不下。有两个方法:
    • Range:输入Unicode范围。比如常用汉字大概在 0x4E000x9FA5 之间,但这样也有好几万。不推荐。
    • Symbols强烈推荐这个方法。在这里直接输入你项目中所有会用到的汉字和标点。比如,你的界面有“设置”、“确定”、“取消”、“温度:25℃”这些文字,你就把“设置确定取消温度:℃”这些字符全部粘贴进去。工具只会生成这些用到的字形,体积小到惊人。我做过一个包含几十个控件的界面,所有中文字符加起来不到200个,生成的字体文件才30多KB。
  4. 上传字体文件并转换:选择你的中文字体文件,点击“Convert”按钮。稍等片刻,浏览器就会下载一个 .c 文件(比如 my_font_16_cn.c)。

3.2 将字库集成到LVGL工程

生成好字库文件后,如何让它生效呢?

  1. 放入工程:把下载的 my_font_16_cn.c 文件,放到你的工程目录里,我习惯放在 lvgl 文件夹下,或者新建一个 fonts 文件夹。
  2. 声明字体:打开LVGL的核心配置文件 lv_conf.h。用编辑器搜索 LV_FONT_CUSTOM_DECLARE。你会看到类似这样的宏:
    #define LV_FONT_CUSTOM_DECLARE
    
    把它修改为声明你的字体:
    #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_16_cn)
    
    这样,LVGL在编译时就知道有这个自定义字体了。
  3. 在代码中使用:现在你可以在任何需要显示中文的地方,指定使用这个字体了。有两种方式:
    • 全局默认字体:在 lv_conf.h 中,找到 LV_FONT_DEFAULT 的定义,可以把它改成 &my_font_16_cn。这样所有文本默认都会用这个中文字体(但西文也会用它,可能不好看)。更常见的做法是混合字体,但稍复杂。
    • 局部设置字体(推荐):在创建控件后,通过样式(Style)来设置。这是LVGL最灵活的方式。
    /* 创建一个标签 */
    lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_text(label, "你好,世界!");
    
    /* 创建一个样式并设置字体 */
    static lv_style_t style_cn;
    lv_style_init(&style_cn);
    lv_style_set_text_font(&style_cn, &my_font_16_cn); // 关键:绑定中文字体
    
    /* 将样式应用到标签 */
    lv_obj_add_style(label, &style_cn, LV_PART_MAIN);
    
    编译下载,你的中文应该就能完美显示了!这一步的成功,会让你成就感满满。

4. GUI Guider实战:拖拽出你的第一个界面

环境和中文字库都准备好了,现在进入最有趣的部分——用GUI Guider设计界面。我们目标是做一个简单的智能家居控制面板,上面有温度显示、灯光开关滑块和模式切换按钮。

4.1 创建新项目与基础布局

打开GUI Guider,点击“Create New Project”。

  • Display:选择你的屏幕分辨率,比如 320x240。
  • Color depth:选16-bit(RGB565),这是嵌入式屏最常用的格式,兼顾色彩和内存。
  • Simulator:选“None”或你对应的开发板型号(如果有),这影响模拟时的驱动。
  • LVGL Version:一定要选和你工程里移植的版本一致的,比如v8.3.x。不一致会导致生成的代码编译不过。

点击创建后,你会看到一个空白的画布。我们先从整体布局开始:

  1. 添加一个背景(Background):从控件箱里拖一个“Arc”控件?不对,背景其实可以用一个简单的“Panel”或者直接设置屏幕样式。更简单的是,在左侧项目树的“Screen”上右键,选择“Set Style”。在样式编辑器里,你可以设置背景颜色、渐变等。我习惯先设一个浅灰色背景。
  2. 使用容器(Container):好的UI是规划出来的。拖一个“Container”(容器)控件到画布上。容器就像HTML里的div,用来分组和布局其他控件。你可以调整它的大小和位置,用来作为标题栏、内容区、底部导航栏。利用右侧属性面板的“Layout”设置,比如“Flex”布局,可以轻松实现子控件的自动排列。

4.2 添加与配置控件:温度、滑块和按钮

现在开始摆放具体控件:

  1. 温度显示
    • 拖一个“Label”控件到画布上,放在顶部作为标题,比如“客厅环境”。
    • 再拖一个大的“Label”控件到中间,用来显示温度数值。在它的属性面板里,把“Text”改成“25.5℃”(先写个示例文本)。然后,把字体改成我们之前添加的中文字体。在属性面板找到“Style”部分,展开“Text”属性,点击“Font”旁边的编辑按钮,在弹出的字体管理器中,你应该能看到 my_font_16_cn(如果你在GUI Guider的项目设置里导入了该字体文件)。选中它,这个标签就会用中文显示了。你还可以调大字号,改变颜色。
  2. 灯光控制滑块
    • 拖一个“Slider”控件到画布上。在属性面板,你可以设置它的范围(Range,比如0到100)、当前值(Value)、以及方向(垂直或水平)。
    • 在滑块旁边放一个Label,文本设为“灯光亮度”。再放一个Label,绑定显示滑块的当前值。这个“绑定”是GUI Guider的精华:选中显示值的Label,在属性面板找到“Text”属性,不要直接输入,点击右边的小闪电图标(“Add Event”),选择“Value Changed”事件,然后选择数据源为刚才那个Slider控件。这样,当滑块滑动时,这个Label的文本会自动更新为滑块的值。这就是数据绑定,省去了你手动写回调函数去同步的麻烦。
  3. 模式切换按钮
    • 拖一个“Button Matrix”控件(按钮矩阵)或者几个普通的“Button”。我们用Button Matrix来做模式切换,比如“自动”、“制冷”、“制热”。
    • 选中Button Matrix,在属性面板的“Map”里,输入 "自动", "制冷", "制热"(注意英文引号和逗号)。它会自动生成三个并排的按钮。
    • 我们需要让它有单选效果。在“Control”属性里,勾选“One Checked”。现在点击这三个按钮,它们就会有选中/取消选中的状态了。

4.3 生成代码并与你的工程联调

设计得差不多了,点击GUI Guider顶部工具栏的“Generate Code”按钮(或者按F5)。它会让你选择代码输出路径。关键一步来了:不要覆盖你的整个工程! GUI Guider会生成一个完整的、包含 main.c 的工程框架,但我们已经有自己的工程了。我们只需要它生成的UI相关文件。

  • 通常,生成的代码会放在 guider_project -> generated 文件夹下。里面最重要的文件是 gui_guider.cgui_guider.h,以及一个 events_init.c 可能包含了事件回调的框架。
  • 我们的策略是:只把这些生成的文件(夹)拷贝到我们自己的STM32(或其他平台)工程里。具体来说:
    1. 在你的IDE工程目录下,新建一个 gui 文件夹。
    2. generated 文件夹里的所有 .c.h 文件拷贝到 gui 里。
    3. 在你的IDE中,将这些新 .c 文件添加到项目,并把 gui 目录添加到头文件包含路径。
    4. 在你自己的 main.c 里,初始化LVGL后,调用生成的UI初始化函数。通常这个函数叫 setup_ui(&guider_ui),你可以在 gui_guider.h 里找到声明。
    #include "gui_guider.h"
    lv_ui guider_ui; // 创建一个UI实例
    
    int main(void) {
        // ... 你的硬件初始化(时钟、GPIO、LCD等)
        lv_init(); // 初始化LVGL
        lv_port_disp_init(); // 初始化显示驱动
        lv_port_indev_init(); // 初始化输入驱动
    
        setup_ui(&guider_ui); // 初始化GUI Guider生成的界面
    
        while(1) {
            lv_timer_handler(); // LVGL定时器任务处理,必须周期性调用(5ms左右一次)
            lv_tick_inc(5); // 告诉LVGL时间过去了5ms(用于动画等)
            delay_ms(5); // 你的延时函数
        }
    }
    
    编译、下载到开发板。如果一切顺利,你就能在屏幕上看到和GUI Guider设计器里一模一样的界面了!滑动滑块,按钮切换,都能正常工作。这种“所见即所得”的体验,对于嵌入式开发来说,真的太爽了。

5. 进阶技巧与避坑指南

当你能做出静态界面后,肯定会想让界面“活”起来,处理更复杂的逻辑。这里分享几个我实战中总结的进阶技巧和常见坑点。

5.1 处理用户交互与自定义事件

GUI Guider生成的事件代码框架在 events_init.c 里。它为你添加的控件自动创建了一些空的事件回调函数。比如,你给一个按钮设置了“Clicked”事件,这里就会有一个 btn_clicked_event_handler 函数。 但是,这些生成的函数里只有一句 LV_LOG_USER("Clicked"),也就是仅仅打印日志。你需要在这里添加你自己的业务逻辑。

static void btn_auto_clicked_event_handler(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);
    lv_obj_t *obj = lv_event_get_target(e);
    if(code == LV_EVENT_CLICKED) {
        // 在这里写你的逻辑
        // 例如,发送消息给其他任务,改变全局变量,或者控制硬件
        set_system_mode(MODE_AUTO);
        // 也可以更新其他UI控件
        lv_label_set_text(guider_ui.screen_label_status, "模式:自动");
    }
}

关键点:如何获取界面上其他控件?GUI Guider生成的 lv_ui 结构体(例子里的 guider_ui)包含了所有你在设计器里命名的控件。你可以在 gui_guider.h 里看到它的定义,里面有像 screen_slider_lightscreen_btnm_mode 这样的成员。在你的事件回调里,通过 guider_ui.控件名 就能直接访问和操作它们,非常方便。

5.2 内存与性能优化:让界面流畅的关键

嵌入式资源紧张,UI又是吃资源大户,优化必不可少。

  1. 监控内存使用:LVGL提供了 LV_MEM_SIZE 宏来定义堆内存大小。在 lv_conf.h 里设置它。你可以通过调用 lv_mem_get_size()lv_mem_get_free_size() 在运行时监控。如果频繁分配失败,考虑增大这个值,或者检查是否有内存泄漏(比如创建了对象但没删除)。
  2. 减少重绘区域:LVGL是局部重绘的,只刷新变化的部分。但如果你频繁改变整个屏幕的大面积区域,还是会卡。优化方法是:
    • 避免在 lv_timer_handler 调用的主循环里进行大量、频繁的UI更新。
    • 使用 lv_obj_mark_layout_as_dirty(obj)lv_obj_invalidate(obj) 来精确标记需要重绘的控件,而不是简单地改变属性。
  3. 图片和字体优化
    • 图片:尽量使用LVGL内置的“符号字体”(Symbol)来代替小图标,它体积小,是矢量缩放。如果必须用图片,一定要用GUI Guider或LVGL提供的图像转换工具,转换成C数组格式(LV_IMG_CF_RAW)或索引格式(LV_IMG_CF_INDEXED_1/2/4/8BIT),后者能极大压缩体积。切忌直接扔一个未经处理的BMP或PNG文件进去。
    • 字体:如前所述,用“Symbols”方式生成最小字库。对于不同大小的字体,分别生成字库文件,而不是用一个超大字库缩放。
  4. 使用LVGL的异步任务:对于耗时的操作(比如从网络获取数据、读写大容量Flash),千万不要在UI事件回调里阻塞等待。应该创建一个RTOS任务(如果用了RTOS),或者用LVGL的 lv_async_call(my_task, &data) 函数,将耗时任务抛到后台,等完成后在回调里再更新UI。

5.3 项目结构与代码管理建议

当项目变大,UI页面变多时,好的代码结构能让你后期维护更轻松。

  • 一个屏幕对应一组文件:不要把所有UI代码都堆在 gui_guider.c 里。GUI Guider支持多屏幕(Screen)设计。你可以为“主界面”、“设置界面”、“关于界面”分别创建一个Screen。生成代码后,每个Screen的控件会放在不同的结构体里,事件回调也可以分开文件管理。
  • 业务逻辑与UI分离:这是软件工程的黄金法则。不要直接在事件回调里写满硬件操作和复杂算法。应该抽象出一个“业务逻辑层”或“模型(Model)”。UI层(事件回调)只负责调用业务层的接口,并监听业务层的数据变化来更新显示。这样,即使以后换一个UI库,你的核心业务代码也不用大改。
  • 善用版本控制:GUI Guider的项目文件(.guider)是文本格式的(实际上是JSON),完全可以纳入Git等版本控制系统。这样,UI设计的每一次修改都有迹可循,可以回滚,也可以和固件代码的版本对应起来。

走完这一整套流程,从环境搭建、中文字库处理,到GUI Guider拖拽设计,最后代码集成与优化,你应该已经能够独立开发出功能完整、界面美观的嵌入式UI了。LVGL和GUI Guider这个组合,极大地降低了嵌入式图形开发的门槛,把创造力从繁琐的底层驱动中释放出来。剩下的,就是发挥你的想象力,去打造更多惊艳的产品界面了。如果在实际动手过程中遇到具体问题,多翻翻LVGL的官方文档和论坛,社区的智慧总能帮你找到答案。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐