RK3568 + LVGL + GUI Guider:嵌入式GUI开发实战指南
2026/9/24 2:10:30 网站建设 项目流程

拿到 RK3568 这块板子的那天,我其实没想太多,就是想把 GUI Guider 拖出来的界面,真正跑在 Linux 下的显示屏幕上。折腾了两三天,踩了一堆坑之后我把整个过程重新整理了一遍。写这篇文章的时候我特意把自己代回“第一次接触 LVGL 移植”的状态,确保你照着走就能跑通,而不是又一篇教你“抄完代码怎么编都编不过”的劝退文。

这不是从零开始的泛泛而谈,而是基于一系列明确选择的具体实操:硬件用 RK3568,图形库用 LVGL,界面用 GUI Guider 9.2 画。先说结论,这套方案每一步都有讲究:RK3568 是瑞芯微里做 HMI 的“万金油”,四核 A55 处理器加上 Mali-G52 GPU,跑 LVGL 这种轻量级 UI 绰绰有余;LVGL 是目前嵌入式 GUI 生态里最活跃的开源库之一,渲染开销低、控件丰富、中文字体和主题都很成熟;GUI Guider 9.2 又刚好是 NXP 官方持续更新的可视化拖拽工具,生成了可直接编译的项目骨架。三者组合起来的性价比,远高于在 STM32 上死磕或者抬出 Qt 这类重框架。

我默认你已经有一块能跑 Linux 系统的 RK3568 开发板(比如某宝常见的 RK3568 核心板+底板,或者香橙派 5 这类评估板),并且知道怎么给它烧录系统、用串口或者 ADB 登录。如果这些基础操作还不太熟,文中的命令也可以直接照抄,只要你板子的系统没改过,大概率差不了太多。

1. 项目整体思路:为什么是 RK3568 加 LVGL 加 GUI Guider

1.1 核心需求解析

这个项目的本质,是在一个带屏幕的嵌入式设备上,用最短时间做一个能看、能点的图形界面。对大多数做产品的团队来说,硬件选型通常早于界面开发,界面只是一个“能在上面跑”的目标。但选型如果太拉胯,界面流畅度、内存占用、配套生态都会让你后续痛苦。

先说 RK3568。它是一颗四核 Cortex-A55 的 SoC,主频最高 2.0 GHz,集成了 Mali-G52 GPU、VPU 编解码单元,还有很完整的显示控制器。比起在 STM32 上移植 LVGL,RK3568 跑 LVGL 完全是“杀鸡用牛刀”,但正因为性能余量大,你可以在界面里放心塞图片、动画、圆角阴影,甚至同时跑几个页面切换,不用担心帧率崩盘。

再说 LVGL。LVGL 目前版本已经到 9.x,API 变化比 v8 时代又大了一截。LVGL v9 的渲染架构做了重构,不再绑定特定的显示驱动接口,而是引入了lv_displaylv_event这一套对象模型,代码结构更干净。GUI Guider 9.2 正是围绕这个新版本设计的,所以你如果还拿 v8 时代的旧教程硬套,会发现接口对不上。

然后是 GUI Guider。这工具说白了就是“LVGL 的拖拽式界面设计器”,左边拖控件,右边调属性,底下写事件回调,最终自动生成一堆 C 代码。它最大的价值是让人把精力放在“界面长什么样”而不是“代码怎么组织”,对产品原型和中期开发都非常友好。9.2 版本生成的代码已经相当工程化,可以直接和我们的手动移植代码合并,不需要像老版本那样改到吐。

1.2 技术选型背后的考量

有人可能会问,RK3568 都上 Linux 了,为什么不直接用 Qt?这就得看使用场景了。Qt 的优势是复杂业务逻辑和桌面级交互,缺点是内存占用大、交叉编译麻烦、对图形驱动的要求高,而且商业授权在部分场景下不友好。LVGL 的定位是嵌入式专用,资源占用低一个量级,没有那么多运行时依赖,非常适合 IoT、智能面板、健身器材、家用电器这类“界面不需要太复杂但要稳”的产品。

也有朋友纠结“直接用官方 BSP 里现成的 buildroot,能不能省事”,这当然可以。但如果你要做的是自己的终端产品,用 GUI Guider 生成界面代码、手动交叉编译,反而更容易把控整个软件栈。这句话得展开讲一讲:官方 SDK 常年把 LVGL 作为示例应用编译进 rootfs,可它往往是固定版本、固定分辨率、固定触摸节点,你改一个小地方就得整个镜像重编。独立工程的方式则灵活得多,界面代码和业务逻辑分离,后续改 UI 也不用重刷系统。

还有一个很多人前期不重视、后期会后悔的点:分辨率。RK3568 的显示控制器支持 LVDS、MIPI DSI、eDP、HDMI 多种输出。在做界面之前,必须先想清楚你的屏幕接口和分辨率是多少。LVGL 是用户态程序,它不会自己初始化显示控制器,而是通过文件接口去写屏,所以你的屏幕能不能点亮、用什么驱动,直接决定了跑 LVGL 的方式。我建议不管最终产品用哪种屏幕,前期先用带 HDMI 的显示器或者一个已知驱动正常的 MIPI 屏幕做验证,别一上来就调新屏。

1.3 RK3568 与 RK3566 的差别

标题写的是 RK3568,但很多人手上可能是 RK3566,也经常有人问这俩到底啥区别。这里先花一小段讲清楚,避免你照着文章结果板子不同、配置差异大到怀疑人生。

项目RK3568RK3566
CPU四核 Cortex-A55,最高 2.0GHz四核 Cortex-A55,最高 1.8GHz
GPUMali-G52 2EEMali-G52 1EE
PCIe支持 PCIe 3.0不支持 PCIe
显示支持 4K 显示主要面向 2K 以下
典型场景中高端 HMI、边缘计算、NAS平板、低端盒子、简单面板

如果你的板子是 RK3566,这篇文章的 LVGL 移植思路完全通用,只是性能上限和显示能力略有差别。界面本身对小核数不敏感,LVGL 主要是单线程渲染,A55 的 IPC 足够。所以别纠结太多,先跑通才是王道。

2. 搭建开发环境:交叉编译工具链和板端系统准备

2.1 开发机上需要准备的东西

这里假设你用的开发机是 Ubuntu(我实测 18.04 和 20.04、22.04 都没啥问题),Windows 下也可以用 WSL 或者装交叉编译工具后配合 IDE,但我建议还是 Linux 省心。

首先要确认目标系统架构。RK3568 是 64 位的,板子上的 Linux rootfs 一般是aarch64架构,所以我们需要用 aarch64 交叉编译器。

如果你的开发板厂商提供了完整的 SDK 和工具链,优先用厂商的,因为它的 glibc 版本和板端系统是匹配的。以瑞芯微官方 SDK 为例,你通常会拿到一个包含 buildroot 的环境,里面已经装好了工具链。但我这里讲的是不依赖整套 SDK 的裸交叉编译方式,所以需要自己装:

sudo apt update sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu make cmake

装完验证一下:

aarch64-linux-gnu-gcc --version

如果输出中能看到gcc version 9.x10.x之类,说明交叉编译器可用。后面我们写 Makefile 时会用到CROSS_COMPILE ?= aarch64-linux-gnu-这样的前缀。

另外还需要保证板子上能方便地接收你编译好的可执行文件。最省事的方式是 ADB 或者 SSH。瑞芯微的开发板一般默认开启 ADB,USB 连上之后用adb devices能看到设备。如果你习惯用网络,也可以ifconfig查到板子的 IP,然后ssh root@<IP>登录,默认密码通常是rockchip或者你自己设置的。

2.2 板端系统与显示设备确认

在跑 LVGL 之前,必须先确认两件事:系统能启动,屏幕能点亮。

如果你用 HDMI 连接显示器,插上后进入系统命令行,用如下命令可以看到 DRM 相关的输出信息:

dmesg | grep -i drm cat /sys/class/drm/card0-*/status cat /sys/class/drm/card0-*/modes

正常情况下,你会看到某个 connector 的状态是connected,并且有具体分辨率模式,比如1920x1080。如果你的屏幕是 MIPI DSI 或者 RGB 屏,那么应该是对应的 connector 显示为 connected,前提是设备树已经正确配置。

LVGL 跑在 Linux 用户态,不需要真正的显卡加速也能工作,它把显存看作一块普通的内存区域,刷屏时直接往这块内存写像素。所以我们的第一个任务,就是找到一个“能写像素的显示设备节点”。RK3568 的 Linux 内核通常会提供两种路径:

  • /dev/fb0,传统 framebuffer 设备
  • DRM/KMS 设备/dev/dri/card0

LVGL 官方维护的驱动代码里,两种都支持。我个人建议优先使用 framebuffer 方式,因为代码路径短、好排查。虽然 DRM 是最终推荐方案,但在“先跑通”阶段,framebuffer 的报错信息友好得多。

如果ls /dev/fb0看不到任何 fb 设备,也不用急,去检查内核配置CONFIG_FB_ROCKCHIP。有些官方内核默认没开 framebuffer,只开了 DRM。这种情况下,有两个选择:一是重新配置内核打开CONFIG_FB_ROCKCHIP并编译烧录,二是直接用 DRM/KMS 驱动。为了减少前期折腾,我强烈建议你打开 framebuffer。瑞芯微原厂内核的 framebuffer 和 DRM 是可以共存的,后者不冲突。

触摸方面,确认一下系统里 event 设备:

cat /proc/bus/input/devices

会看到event0event1之类的输入设备。如果你的屏幕是触摸屏,通常会出现一个名字类似GoodixGslX680eGalax的设备。先用evtest工具验证有没有触摸中断上报:

sudo evtest /dev/input/event1

手指点屏幕,如果终端有输出,说明触摸设备是好的。这一步很重要,否则你界面能显示但不能点,会让人怀疑人生。

2.3 获取 LVGL 及显示驱动源码

接下来要把 LVGL 源码和官方驱动拉到本地。这里有一个非常容易踩坑的点:GUI Guider 9.2 生成代码依赖的 LVGL 版本是 9.x,如果你直接从网上下一个最新 master,接口可能和 GUI Guider 生成的代码不匹配。最好的做法是下载官方 release 分支,版本选择 9.2 附近的稳定版。

我的建议是:

git clone --branch v9.2.0 --depth 1 https://github.com/lvgl/lvgl.git git clone --branch v9.2.0 --depth 1 https://github.com/lvgl/lv_drivers.git

这里单独解释一下lv_drivers是什么。LVGL 本身不直接操作硬件,它只管把控件画到内存里,然后把更新区域交给显示驱动函数去刷。lv_drivers是 LVGL 官方仓库,里面提供了常见的 Linux 显示驱动、输入设备驱动,比如 framebuffer、DRM、evdev,代码小而清晰,我们只需要把它编译进去,然后做少量配置就行。

如果你在编译时发现 LVGL 源码中有lv_display.h但缺少某些定义,可以检查一下仓库的版本标签。v9.2 是 GUI Guider 9.2 的原配版本,别乱升,也千万别用 v8 去凑。

3. GUI Guider 9.2:设计你的第一个界面

3.1 软件安装与新建工程

GUI Guider 9.2 是跨平台软件,Windows、Ubuntu、macOS 都有安装包。直接去 NXP 官网下载页面找对应版本,安装很简单,一路下一步就可以。装完打开,会出现创建工程的引导页面。

新建工程时几个关键点:

  • Target 选择:里面有很多 NXP 的评估板型号,可能没有 RK3568。没关系,选一个通用的Generic或者自带模拟器的空模板即可。GUI Guider 最终输出的是纯 C 代码,不绑定具体硬件,这里的选择只决定初始模板内容。
  • 屏幕分辨率:这一步必须和你实际屏幕一致。比如我用的是 1024x600 的屏幕,就填1024600。分辨率不对的话,后面生成的代码坐标全是错的,调起来很痛苦。
  • 颜色深度:LVGL 支持 1/8/16/24/32 位颜色。RK3568 的 framebuffer 常见配置是 RGB565(16位)或 ARGB8888(32位)。如果你不确定,先用 16 位 RGB565,兼容性最好。改成 32 位也行,但注意显存占用翻倍。

新建完成后,GUI Guider 会给你一个空白页面,类似普通设计器。左侧是控件库,中间是画布,右侧是属性面板,底部有图层。这个界面熟悉一下即可,操作逻辑和 PS、Figma 有点类似。

3.2 从空白页到一个能看的仪表盘

现在我们来做点实际的东西。假设你的产品要做一个简单的温湿度监控面板,那界面可以长这样:顶部一张图片或者一个圆角矩形标题栏,中间一个大数字显示温度,底下两个按钮用来切换页面。

在左侧控件库里,依次拖入:

  • 一个Arc圆弧控件,用来模拟仪表盘指针
  • 一个Label标签,用来显示温度数字
  • 一个Button按钮,用来触发事件

把这些控件摆放好后,右侧属性面板可以设置位置、大小、背景颜色、圆角、字体、文字颜色。GUI Guider 支持实时预览,你可以直接在当前工程里运行模拟器看看效果,虽然模拟器上的观感和真机略有差别,但布局基本是一致的。

写了这些之后,我想强调一个新手最容易漏掉的操作:给每个控件改一个有意义的名字。双击控件名,把screen_1_btn_1改成temp_up_btn,把screen_1_label_1改成temp_label。看似不起眼,但等你生成代码后,搜索和回调处理会省下一大把时间。

3.3 事件绑定与代码生成

在 GUI Guider 里选中按钮,右侧有一个“事件”标签,点击添加事件,下拉框里有各种各样的事件类型,比如点击按下释放。添加之后,右侧会有一个代码编辑窗口。这里可以直接写回调函数体。GUI Guider 会自动帮你把事件注册好,你只需要写按钮点击之后要干什么。

例如,给按钮添加一个点击事件,在回调里把温度标签的数字加一:

void temp_up_btn_event_handler(lv_event_t *e) { static int temp = 25; temp++; lv_label_set_text_fmt(temp_label, "%d°C", temp); }

注意这里temp_label变量是生成的全局变量名,它在gui_guider.h或者对应的setup_scr_screen_1.c里被声明。GUI Guider 生成的多屏代码中,每个控件通常是一个全局指针变量,跨文件可直接访问。

事件写好之后,点击右上角的 “Generate Code”。这时会在你指定的工程输出目录下生成一堆文件,通常包括:

  • gui_guider.h
  • gui_guider.c
  • setup_scr_screen_1.c
  • setup_scr_screen_1.h
  • events_init.c
  • events_init.h
  • custom.c
  • custom.h

打开gui_guider.c,能看到初始化函数setup_ui()setup_scr_screen_1()这类入口。我们的核心目标,就是把setup_ui()调用到 LVGL 初始化之后,然后让它进入事件循环。

GUI Guider 生成的代码默认是为 “MCU 环境下自行编译” 准备的,它不包含main函数,也不包含 LVGL 的初始化。这些需要我们自己写,这正是下一章的核心内容。

3.4 中文字体与图片资源的处理

很多人在这一步栽了跟头:GUI Guider 里明明写了中文,生成代码后烧到板子上,显示出来全是方框。原因是 LVGL 默认字体只包含 ASCII 字符,不含中文点阵,必须额外加入中文字体,并把要用到的汉字子集编译进字库。

GUI Guider 的字体管理里可以点击“文本”设置,选择“添加字体”,然后从本机导入一个 TTF 字体文件。这里有个小细节:不要整个字体全量嵌入,LVGL 是按 Unicode 码位裁剪子集的。在 GUI Guider 中,你可以在“字体属性”里设置要包含的字符范围,比如添加0x4E00-0x9FA5范围,这样基本覆盖常用汉字。同时可以勾选“自动收集用到的汉字”,让它只打包你在界面里写过的那些汉字,这样生成的 bin/fnt 文件体积会小很多。

图片资源同理。GUI Guider 支持 PNG、JPG、SVG,但最终会转成 C 数组或者 LVGL 的 bin 格式。一般建议图标和素材直接用 PNG,颜色不要太复杂,这样输出体积可控。如果你做的是大尺寸背景图,建议先压缩到和目标分辨率完全一致,不要在运行时缩放,否则内存开销翻倍,性能也难看。

4. LVGL 移植到 RK3568:从源码到可执行文件

4.1 整理工程目录结构

现在我们把所有文件组织成一个真正能编译的 Linux 用户态程序。我习惯的目录结构如下:

lvgl_rk3568_demo/ ├── lvgl/ ├── lv_drivers/ ├── gui_guider/ │ ├── gui_guider.h │ ├── gui_guider.c │ ├── setup_scr_screen_1.c │ ├── setup_scr_screen_1.h │ ├── events_init.c │ ├── events_init.h │ ├── custom.c │ ├── custom.h │ └── images/ (存放生成的图片资源) ├── src/ │ └── main.c ├── Makefile └── lv_conf.h

将 GUI Guider 生成的generated目录整体拷贝到工程里改名为gui_guider。然后把下载好的lvgllv_drivers仓库放进来。lv_conf.h是 LVGL 的配置文件,在 LVGL 仓库的lv_conf_template.h基础上修改,复制一份到工程根目录即可。

lv_conf.h里有一些必须设置的关键项:

#define LV_COLOR_DEPTH 16 #define LV_USE_LOG 1 #define LV_LOG_LEVEL LV_LOG_LEVEL_WARN #define LV_MEM_SIZE (64U * 1024U)

LV_MEM_SIZE是 LVGL 的动态内存池大小。RK3568 有充足内存,但 LVGL 的默认内存池是内部静态数组,和系统内存没关系,所以分配多少它就用多少。先设 64KB 够跑简单界面,如果后面发现控件多到内存不足(会报lv_mem_alloc失败),再往上调。

4.2 编写 main.c:显示初始化与事件循环

现在写核心的main.c。这段代码要做四件事:初始化 framebuffer 显示设备、初始化触摸输入设备、初始化 LVGL 库、进入无限事件循环。

完整代码如下:

#include <stdio.h> #include <unistd.h> #include "lvgl.h" #include "lv_drivers/display/fbdev.h" #include "lv_drivers/indev/evdev.h" #include "gui_guider.h" #define HOR_RES 1024 #define VER_RES 600 int main(void) { lv_init(); fbdev_init(); fbdev_set_resolution(HOR_RES, VER_RES); evdev_init(); // 设置触摸设备节点,默认是 /dev/input/event1,根据实际调试 evdev_set_file("/dev/input/event1"); lv_display_t *disp = lv_display_create(HOR_RES, VER_RES); lv_display_set_flush_cb(disp, fbdev_flush); lv_display_flush_ready(disp); lv_indev_t *indev = lv_indev_create(); lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(indev, evdev_read); setup_ui(&guider_ui); while (1) { lv_timer_handler(); usleep(5000); } return 0; }

这里解释一下关键点。fbdev_init()会打开/dev/fb0并做mmap把显存映射到用户态地址。lv_display_create创建了一个与屏幕尺寸一致的显示对象,lv_display_set_flush_cb把 LVGL 的 flush 回调指向fbdev_flush——这个函数在lv_drivers/display/fbdev.c里实现,负责把渲染好的像素拷贝到 framebuffer。evdev_initevdev_read同理,读取触摸事件传给 LVGL。

补充一句:很多旧教程里会看到lv_disp_drv_registerlv_indev_drv_register,这些 API 在 LVGL 9 中已经改名了,改成lv_display_create加回调函数的方式。如果你照抄老代码,编译器会直接报implicit declaration of function之类,所以一定要确认 API 版本。

4.3 Makefile 编写与交叉编译

整个工程的编译依赖比较多,最简单的方式是手写一个 Makefile,把 LVGL 和 LV_Drivers 的源码都编进来。

CROSS_COMPILE ?= aarch64-linux-gnu- CC := $(CROSS_COMPILE)gcc LVGL_DIR := lvgl LVDRV_DIR := lv_drivers CFLAGS := -O2 -Wall -I$(LVGL_DIR) -I$(LVGL_DIR)/src -I$(LVDRV_DIR) -I. CFLAGS += -DLV_CONF_INCLUDE_SIMPLE CFLAGS += -D_GNU_SOURCE # 收集 LVGL 源码 LVGL_SRC := $(wildcard $(LVGL_DIR)/src/*.c) LVGL_SRC += $(wildcard $(LVGL_DIR)/src/**/*.c) LVGL_SRC += $(wildcard $(LVGL_DIR)/src/**/**/*.c) # 收集 lv_drivers 源码 LVDRV_SRC := $(wildcard $(LVDRV_DIR)/display/*.c) LVDRV_SRC += $(wildcard $(LVDRV_DIR)/indev/*.c) # GUI Guider 生成代码 GUI_SRC := $(wildcard gui_guider/*.c) SRCS := src/main.c $(LVGL_SRC) $(LVDRV_SRC) $(GUI_SRC) OBJS := $(SRCS:.c=.o) TARGET := lvgl_demo all: $(TARGET) $(TARGET): $(OBJS) $(CC) -o $@ $^ -lm -lpthread %.o: %.c $(CC) $(CFLAGS) -c -o $@ $< clean: rm -rf $(OBJS) $(TARGET)

这里有个坑:LVGL v9 的源码组织比较深,wildcard src/**/*.c这种写法在 Make 的默认 glob 模式下不会递归匹配所有子目录,所以保险起见我写了好几层 wildcard 来覆盖多层子目录。如果你的源码树更深,再补一层即可。更省心的办法是用find命令动态生成,但为了新手好理解,这里先用显式多层。

编译一行命令:

make

如果一切正常,几分钟后会生成一个名为lvgl_demo的 aarch64 可执行文件。用file lvgl_demo查看,应该能看到ELF 64-bit LSB executable, ARM aarch64字样。

4.4 显存与绘制性能的初步调优

跑通是一回事,跑得流畅又是另一回事。LVGL 在 Linux framebuffer 上默认是全量刷新或者脏矩形刷新,如果你发现界面卡顿,可以调整两个地方。

第一是LV_DISP_ROT和屏幕方向的配置。如果屏幕物理方向不对,不要用 LVGL 做旋转,最好在内核的设备树里把 DRM 的 rotation 属性配好,或者调整 fb 的 pan。软件旋转会显著增加 CPU 负载。

第二是脏矩形刷新。LVGL 内置的刷新机制本身只会重绘变化区域,但 framebuffer 驱动层的fbdev_flush函数默认是把整个区域拷贝过去。如果你的屏幕分辨率是 1080p,每帧全屏刷新一次 RGB565 数据大约是 4MB,CPU 拷贝开销不小。可以把 framebuffer 的fbdev_set_resolution分辨率设小一点(匹配实际屏幕),或者在后期的性能优化中使用LV_USE_PERF_MONITOR来观察 FPS:

#define LV_USE_PERF_MONITOR 1

打开这个宏之后,界面上会有一个小的 CPU/帧率显示条,方便量化优化效果。

5. 部署运行、踩坑记录与后续扩展

5.1 拷贝可执行文件到开发板

编译完成后,把lvgl_demo文件拷贝到 RK3568 的 Linux 系统里。最简单的办法:

adb push lvgl_demo /userdata/ adb shell chmod +x /userdata/lvgl_demo adb shell /userdata/lvgl_demo

如果你的系统用 SSH,也可以scp发送。运行之后,屏幕上应该会出现你刚才在 GUI Guider 里画的界面。

如果运行后屏幕黑屏或者花屏,不要慌,最可能有问题的不是 LVGL 代码,而是 framebuffer 的颜色格式不匹配。lv_conf.hLV_COLOR_DEPTH如果是 16,而 fb 设备是 32 位深,就会显示花屏。先确认一下:

fbset -s /dev/fb0

看到rgba 8/8/8/8或者rgb 5/6/5之类的信息后,再去改LV_COLOR_DEPTH重新编译。一般来说,常见 RGB 屏是 RGB565,HDMI 显示器多数能接收 RGB565,但也有些显示器强制要求 32 位,具体以实际画面为准。

5.2 常见问题与排查思路实录

这一节我把实际操作中遇到的高频问题列成速查表,按现象、原因、解决办法三列组织:

现象可能原因解决办法
运行时报错Failed to open /dev/fb0内核没开 framebuffer检查内核配置CONFIG_FB_ROCKCHIP,重新编译烧录
屏幕黑屏但不报错显示驱动没刷新,或 LVGL 没进入 timer 循环检查 main.c 是否在 while 里调用lv_timer_handler()
花屏、颜色偏色LV_COLOR_DEPTH与 fb 格式不一致fbset -s /dev/fb0查格式,匹配修改
触摸没反应evdev 设备节点不对cat /proc/bus/input/devices查触摸设备,改evdev_set_file
中文显示方框字体没嵌入中文字库GUI Guider 里导入 TTF 并裁剪字符集
界面卡顿CPU 渲染过重或刷新区域过大打开LV_USE_PERF_MONITOR观察,缩小控件阴影/圆角使用
编译时lv_conf.h找不到编译宏没指对头文件Makefile 增加-DLV_CONF_INCLUDE_SIMPLE,确认 lv_conf.h 在根目录

有一个问题非常隐蔽,我特意单独拎出来说。如果你在 GUI Guider 里使用了动态图片或 GIF 资源,生成代码里可能会引用lv_gif.hlv_png.h这些外部库。这些不是 LVGL 核心源码,需要额外编译对应的 libpng、libgif 库,或者在你下载 LVGL 时把lv_lib_png等仓库一同拉进来。编译报错如果看到undefined reference to lv_png_init,别怀疑 LVGL 的问题,就是缺依赖库。

5.3 性能优化思路:从 framebuffer 到 DRM/Mali GPU 渲染

前面几节都是基于 framebuffer 的软渲染方案,优点是简单直接,缺点是 GPU 没有真正参与渲染。RK3568 的 Mali-G52 本来可以加速部分渲染,但如果你用 fbdev 作为后端,GPU 加速效率会受到限制。

跑通第一个界面之后,如果你要把它做成真正的产品,建议走 DRM/KMS 后端。LVGL 官方的lv_drivers里有一个drm驱动,它通过 DRM 的 dumb buffer 创建显存,然后编程填充到内存。用 DRM 的好处是可以利用 KMS 的自动刷新机制,避免手动 memcpy,而且在一些场景下还能配合 Mali 的 GPU 做合成。

DRM 驱动的初始化比 fbdev 复杂一些,代码示例大概是:

lv_display_t *disp = lv_drm_display_create(CONNECTOR_ID, HOR_RES, VER_RES);

其中CONNECTOR_ID要自己通过drmModeGetConnector去遍历。这部分代码我已经在另一个项目里写过,但放进来会把这篇文章拖太长,这里只点明方向:先把软件渲染跑顺,再研究 DRM。

还有一个方向是使用 LVGL 的lv_draw_sw高性能渲染功能。LVGL 9 默认使用软件渲染器,但在编译时可以开启LV_DRAW_SW_SUPPORT下的 NEON 优化。A55 支持 ARM NEON 指令,把优化开关打开之后,颜色填充和位块搬运性能可以提升 30% 左右,代价只是编译指令集打开,几乎零成本。

#define LV_DRAW_SW_NEON 1

记得在编译参数加-mfpu=neon -mfloat-abi=softfp,或者干脆用-mcpu=cortex-a55让编译器自动优化。实测下来,简单界面 FPS 从 40 多帧能到 60 帧,效果明显。

5.4 从单屏到多页面与业务代码的对接

GUI Guider 可以创建多个 screen,然后通过事件切换屏幕。它生成的代码里有lv_scr_load_anim这类动画切换函数,你可以直接调用。例如在按钮事件里:

void next_screen_btn_event_handler(lv_event_t *e) { lv_scr_load_anim(guider_ui.screen_2, LV_SCR_LOAD_ANIM_MOVE_LEFT, 300, 0, false); }

这里的guider_ui是全局结构体,GUI Guider 在gui_guider.c里已经初始化好所有屏幕指针。多屏切换的内存分配和释放要特别注意:lv_scr_load_anim会自动卸载旧屏幕,但如果你用到自定义的临时对象,要手动删除,避免内存泄漏。

业务代码对接方面,建议把 LVGL 事件回调当成一个薄薄的消息入口,不要在回调里执行耗时操作。比如按钮触发网络请求,正确做法是在回调里置一个标志位,然后在while(1)循环中(或者单独线程)处理真正的业务逻辑。LVGL 的回调是在lv_timer_handler()上下文中执行的,如果你在回调里卡 200ms,整个 UI 就卡 200ms,体验非常糟糕。

5.5 触摸设备封装与实际项目中的输入兼容

最后补一点关于触摸的小经验。不同屏厂的触摸芯片不一样,有的叫Goodix,有的叫GslX680,有的叫Hynitron。LVGL 的evdev_read只读取标准的struct input_event结构,所以理论上所有 evdev 设备都行,但触摸坐标上报的方式有差异。

大多数触摸屏在 evdev 里报ABS_XABS_YBTN_TOUCH,这没问题。但也有舞台投影触摸框或者红外触摸框只报BTN_LEFT,不报BTN_TOUCH,这种情况下 LVGL 可能识别不到按压。解决方式是在evdev_read里稍微改动一小段解析逻辑,把BTN_LEFT映射成LV_INDEV_STATE_PRESSED

调试触摸时可以用evtest查看原始事件,如果发现坐标范围是 0~4095 而你的屏幕是 1024x600,LVGL 会默认按绝对坐标直接映射,这个一般是对的。如果系统里的触摸驱动校准过坐标,比如上报范围已经是屏幕像素范围,那就不用额外做坐标变换。

写在最后的一点经验

这篇文章从方案选型、环境搭建、GUI 设计、代码生成、交叉编译到最终部署,把一条从零到一的路完整走了一遍。我特意没有用厂商 SDK 里的现成 LVGL 示例,而是采用“源码直接编译、手动适配驱动”的方式,因为这种灵活度才符合真实做产品的需求。

基于我个人的实操体会,如果你第一次搞,一定要按顺序来:先点亮屏幕,再验证触摸,最后才和 GUI Guider 生成的代码合并。很多人一上来就急着把整个工程编好,结果发现屏幕没亮,后面所有调试都是空中楼阁。

还有一个小技巧分享给大家:在main.c最开始加一个lv_log_register_print_cb回调,把 LVGL 的日志重定向到串口或文件,调试时能救大命。LVGL 在内存不足、颜色格式不匹配、资源加载失败时都有明确的错误输出,只是默认固定在 stdout,有时候你没注意到。

LVGL 的东西远不止一篇文章能讲完,但跑通第一个界面的过程是相通的。后面无论是接真实的传感器数据、做多语言、还是上 DRM 硬件加速,都是从这条基线出发。祝你们一次点亮。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询