LVGL移植到STM32

移植LVGL到STM32

官方英文移植教程:

Getting LVGL(获取 LVGL) — LVGL 文档

韦东山中文移植教程:

Porting(移植) — LVGL 文档

源码获取

然后从github上下载源码:https://github.com/lvgl/lvgl

官方移植方法:https://docs.lvgl.io/master/details/integration/adding-lvgl-to-your-project/getting_lvgl.html

这是LVGL v9.3版本

image-20250810145619494

image-20250810145425207

移植

该笔记基于lvgl v9.x往上的,如果使用版本较低,就可能与本笔记中的内容不同

我们的只需要关注源码中需要移植的部分:

image-20250729204047343

以keil工程为例,我们找到项目根目录下Middlewares(没有就创建一个),然后再其中创建一个lvgl文件夹用于存放我们移植lvgl相关的内容

image-20250729200924599

然后在该lvgl目录下新建srcport文件夹

src文件夹:lvgl源码

port文件夹:存放我们自己的移植代码,比如显示驱动移植、输入设备(触摸/按键)接口移植等等

image-20250730140445796

src源码

将下载下来的lvgl库的lvgl/src源码文件夹替换刚刚创建的src文件夹

image-20250730140652464

image-20250730140756314

移植文件

再将lvgl/examples/porting下的文件复制到到我们的项目的lvgl_port中去,并进行一个规范命名的操作

相关文件介绍

lv_port_disp_template: LVGL显示驱动移植模板(通用)

lv_port_fs_template : LVGL文件系统接口移植模板

lv_port_indev_template: LVGL输入设备(触摸/按键)驱动移植模板

lv_port_lcd_stm32_template: STM32专用LCD显示驱动移植模板(这个和disp差不多,只不过是stm32专用的二选一即可)

image-20250729213653574

我们这里只有显示驱动触摸驱动所有只需要保留这两个到我们项目的port中,然后去掉template后缀规范命名

image-20250730140948907

image-20250730141004954

配置文件

主要就是lvgl库中的这三个文件需要移植:

image-20250729215427495

lv_conf_template.h/lv_cong: LVGL的配置头文件模板,里面包含了各种宏定义(比如颜色深度、是否开启某个控件/功能、内存大小等)。

lv_version.h: LVGL版本信息头文件

lvgl.h: 对源码src目录下所有头文件的包含,后续只需要包含整个文件就能使用全部API

将这三个文件复制到Middlewares/lvgl目录下,然后将配置文件更名为lv_conf

image-20250730141141231

工程管理

源文件添加

在keil工程中,新建这三个组srcportconf,然后分别添加对应的.c文件到项目

image-20250730141309276

conf组

把这lv_conf.hlv_version.hlvgl.h三个放到conf组中

image-20250730141336603

port组

将需要移植port文件夹中的的.c文件和.h文件添加到port组

image-20250731215648421

src组

第一种可以将需要添加的所有.c文件都添加到src组中

image-20250730155209348

第二种按照src文件夹目录结构,将src中的各个模块单独创建组添加,这样比较好找文件

image-20250730182427383

然后按照下方给的移植总览表格将对应模块所需要的.c文件添加到对应组中即可

移植总览表格

全部移植就是该文件夹下的所有.c文件,包括子文件夹,一个一个都要手动添加到组中,必须全部添加,否则编译会报错

只有driverother两个文件夹可以完全不用移植,如果使用到了再添加进去

名称 作用描述 必要性
core LVGL核心对象管理、事件、基础框架 全部移植
display 显示相关管理、显示设备适配 全部移植
font 字体管理和渲染 全部移植
indev 输入设备(触摸、按键等)抽象和管理 全部移植
layouts 控件布局系统(如grid、flex) 全部移植
misc 杂项工具和通用功能(如内存、链表等) 全部移植
tick LVGL定时任务调度与管理 全部移植
draw 绘图引擎,图形渲染 全部移植
osal 操作系统抽象适配(如RTOS接口) 全部移植
themes 主题系统,控件风格美化 全部移植
widgets 所有具体控件实现(如按钮、滑块、列表等) 全部移植
stdlib 标准库适配(为兼容不同C库/平台) 全部移植
libs 第三方库支持(如png/gif/qrcode等) 全部移植
drivers 官方外设驱动(如各种屏、触摸等) 按需移植(不用可以不移植)
others 其它杂项/实验性/不常用功能 按需移植(不用可以不移植)

下面是一些含有子目录的文件夹内容介绍

draw

image-20250730143244258

名称 作用 必要性
dma2d STM32 DMA2D 硬件绘图加速支持 按需移植(仅STM32带DMA2D且需加速时)
espresif ESP32/ESP系列芯片的硬件绘图加速接口 按需移植(仅ESP32等芯片使用时)
nema_gfx Think Silicon NEMA GPU 图形加速接口 按需移植(仅带NEMA GPU芯片用)
nxp 恩智浦NXP芯片硬件绘图加速 按需移植(NXP芯片带加速功能时)
opengles OpenGL ES 图形库加速接口 按需移植(PC/高端嵌入式平台用)
renesas 瑞萨Renesas芯片专用绘图加速 按需移植(瑞萨特定芯片用)
sdl SDL仿真/PC绘图后端,适合开发测试和演示 按需移植(PC仿真时可用)
sw 纯软件绘图(LVGL默认后端),所有平台可用 建议移植(几乎所有平台都可用,也可和硬件加速共存)
vg_lite VeriSilicon VG-Lite GPU 加速接口 按需移植(如NXP i.MX RT1170等带VG-Lite芯片)
layout

image-20250730145427122

名称 作用 必要性
flex Flex 布局引擎,类似于网页的 flex 布局,控件自适应排列 建议移植(用到 flex 布局或相关控件时必须)
grid Grid 布局引擎,类似于网页的 grid 布局,控件网格排列 建议移植(用到 grid 布局或相关控件时必须)
lv_layout.c 布局系统主入口和调度代码 必须移植
lv_layout.h 布局系统主头文件 必须移植
lv_layout_private.h 布局系统私有头文件 必须移植
libs

image-20250730150247181

名称 作用描述 必要性
barcode 条形码生成/解析支持 按需移植(如需条形码功能时才用)
bin_decoder 二进制图片格式解码 按需移植(如需支持二进制图片时才用)
bmp BMP图片格式解码 按需移植(如需BMP图片支持时才用)
expat XML解析库 按需移植(如需用到XML相关功能时才用)
ffmpeg FFmpeg视频/音频解码库适配 按需移植(如需视频/音频播放时才用)
freetype FreeType矢量字体引擎 按需移植(如需矢量字体/高级字体支持时)
fsdrv 文件系统驱动接口 按需移植(如需SD卡/U盘/文件系统支持时)
gif GIF图片格式解码 按需移植(如需GIF动画支持时才用)
libjpeg_turbo JPEG图片格式解码 按需移植(如需JPEG图片支持时才用)
libpng PNG图片格式解码 按需移植(如需PNG图片支持时才用)
lodepng 纯C实现PNG解码 按需移植(如需PNG且不用libpng时可选)
lz4 LZ4压缩算法支持 按需移植(如需LZ4压缩资源时才用)
qrcode 二维码(QR code)生成 按需移植(如需二维码生成功能时才用)
rle RLE压缩支持 按需移植(如需RLE图片资源支持时才用)
rlottie Lottie动画支持 按需移植(如需矢量动画时才用)
svg SVG矢量图形格式支持 按需移植(如需SVG图片/动画时才用)
thorvg ThorVG矢量图形库支持 按需移植(如需高级矢量渲染时才用)
tiny_ttf 轻量级TTF字体支持 按需移植(如需使用TTF字体时才用)
tjpgd Tiny JPEG解码库(轻量级JPEG解码) 按需移植(如需JPEG支持且资源有限时用)
misc

image-20250730150559802

名称 作用 必要性(必须移植、建议移植、按需移植)
cache 缓存相关功能,提升性能 按需移植(需要缓存机制时才用,默认可不加)
osal

image-20250730151354894

stdlib

image-20250730151834686

名称 作用描述 必要性
builtin LVGL 内置标准库实现(如内存、字符串等) 建议移植(大多数平台通用,默认推荐)
clib C 标准库适配层(如 malloc、memcpy 等) 按需移植(需适配特殊C库时才用)
micropython MicroPython 适配相关 按需移植(做 MicroPython 绑定时用)
rtthread RT-Thread 系统下标准库适配 按需移植(RT-Thread 系统用)
uefi UEFI 固件环境下标准库适配 按需移植(做固件开发才用)
themes

image-20250730152030943

名称 作用描述 必要性
default LVGL 默认主题(配色、控件样式等) 建议移植(默认主题常用,建议加)
mono 单色主题(灰度/黑白配色) 按需移植(如需单色风格时用)
simple 简化主题(简洁外观、较少样式) 按需移植(如需极简界面时用)
lv_theme.c 主题系统主实现文件 建议移植(使用主题功能时必须)
lv_theme.h 主题系统主头文件 建议移植(使用主题功能时必须)
lv_theme_private.h 主题系统私有头文件 建议移植
widgets

image-20250730152407221

名称 作用描述 必要性
button 按钮控件 建议移植(常用控件)
image 图片控件 建议移植(常用控件)
label 文字标签控件 建议移植(常用控件)
bar 进度条控件 按需移植(常用,建议加)
arc 圆弧控件 按需移植
arclabel 沿圆弧排布的文字控件 按需移植
buttonmatrix 按钮矩阵控件(如键盘/九宫格) 按需移植
calendar 日历控件 按需移植
canvas 画布控件(可自绘像素) 按需移植
chart 图表控件(折线/柱状/饼图) 按需移植
checkbox 复选框控件 按需移植
dropdown 下拉框控件 按需移植
imagebutton 图片按钮控件 按需移植
keyboard 屏幕软键盘控件 按需移植
led LED指示灯控件 按需移植
line 线段控件 按需移植
list 列表控件 按需移植
lottie Lottie 动画控件 按需移植(需矢量动画时才用)
menu 菜单控件 按需移植
msgbox 消息框控件 按需移植
objx_templ LVGL对象扩展模板(自定义/开发用) 按需移植(开发自定义控件时用)
property 属性面板控件 按需移植
roller 转轮选择控件(如日期/时间选择器) 按需移植
scale 刻度尺/标尺控件 按需移植
slider 滑块控件(用于调节音量、亮度等数值) 按需移植
span 富文本/多样式文本片段控件 按需移植
spinbox 数字步进框控件(带加减按钮的数字输入) 按需移植
spinner 加载/等待动画控件 按需移植
switch 开关控件(类似手机设置中的开关) 按需移植
table 表格控件 按需移植
tabview 标签页控件(多页面切换) 按需移植
textarea 多行文本输入框控件 按需移植
tileview 平铺视图控件(多页面自由滑动切换) 按需移植
win 窗口控件(带标题栏和关闭按钮的窗口,仿桌面风格) 按需移植
3dtexture 三维纹理控件(罕用) 按需移植(需3D纹理时才用)
animimage 动图控件(如GIF/APNG序列) 按需移植(需显示动图时才用)
driver

这里面的驱动不使用的话直接都不移植即可

image-20250730144949080

名称 作用 必要性
display 各种显示屏驱动适配示例(PC/仿真/开发板用较多) 按需移植(STM32移植通常不用,可参考)
evdev Linux下输入设备(event device)驱动适配 按需移植(PC/Linux仿真环境用)
glfw GLFW库适配(PC端窗口/输入/显示/键鼠) 按需移植(PC端/仿真用)
libinput Linux下libinput输入库适配 按需移植(Linux桌面或嵌入式开发用)
nuttx NuttX RTOS平台驱动适配 按需移植(用NuttX RTOS才用)
qnx QNX操作系统下驱动适配 按需移植(用QNX才用)
sdl SDL库适配(PC端仿真、窗口、输入输出等) 按需移植(PC仿真/演示用)
uefi UEFI固件环境下驱动适配 按需移植(做固件开发才用)
wayland Linux Wayland窗口系统驱动适配 按需移植(Wayland桌面环境才用)
windows Windows平台下输入/显示驱动适配 按需移植(Windows仿真/开发才用)
x11 Linux X11窗口系统驱动适配 按需移植(X11桌面环境才用)
other

不用就都不移植

image-20250730151534228

名称 作用描述 必要性(必须移植、建议移植、按需移植)
file_explorer 文件浏览器相关功能/示例 按需移植(需文件管理功能时才用)
font_manager 字体管理扩展 按需移植(需高级字体管理时才用)
fragment 界面分片功能(类似Android Fragment) 按需移植(需动态界面切换时才用)
gridnav 网格导航控件 按需移植(需网格导航时才用)
ime 输入法扩展 按需移植(需自定义输入法时才用)
imgfont 图片字体支持 按需移植(需图片字体时才用)
monkey UI自动化测试工具 按需移植(需UI自动测试时才用)
observer 观察者模式实现 按需移植(需事件订阅/发布时才用)
snapshot 屏幕/控件快照功能 按需移植(需截图/快照时才用)
sysmon 系统监控(如内存/帧率等) 按需移植(需性能监控时才用)
test LVGL自带的测试代码 按需移植(开发调试或自测时才用)
translation 国际化/多语言支持 按需移植(需多语言界面时才用)
vg_lite_tvg VG-Lite格式矢量图形支持 按需移植(需VG-Lite矢量图时才用)
xml XML相关功能/解析 按需移植(需XML数据支持时才用)

头文件添加

最后添加lvgl库所有的头文件搜索路径即可,四个都要添加,不能缺少,否则可能会出现找不到头文件错误

image-20250730172424510

启用lv_conf.h

我们在lvgl配置文件lv_conf.h,将此处的0修改为1,启用该配置文件

image-20250730183943099

关于lv_conf.h:

lv_conf.h 是lvgl的配置文件,主要用于配置LVGL库要不要编译哪些功能、用多大内存、支不支持某些控件/主题/库、底层适配方式等。

这个文件就是在裁剪库的功能和适配你的硬件/需求

这里启用后就能进行编译了,如果编译报错可以看是否是下面的问题!

编译报错

找不到头文件

然后进行编译操作,可以发现有很多error,这些都是由于lv_conf_internal.h找不到../../lv_conf.h造成的。

image-20250730170902539

我们可以看到由于没有宏定义,lv_conf_internal.h包含lv_conf.h的路径的方法是第三种:../../lv_conf.h

然而我们的lv_conf.h的实际路径是../lv_conf.h,所以找不到

image-20250730171128450

解决方法

在预处理器添加宏定义LV_CONF_INCLUDE_SIMPLE,让lv_conf_internal.h包含头文件方式修改为自动查找头文件搜索路径

image-20250730171652670

关于移植的一些问题

移植的时候尽量全部移植,没事不要去裁剪,因为裁剪过后,如果进行了函数调用,找不到.c文件,会报链接错误。

我的疑问:如果全部移植,编译时间那么久,不会导致最终的固件体积增大吗

不会。因为添加到工程的所有.c文件在编译阶段时都会被单独编译成目标文件(.o/.obj)以供后续链接使用,这就是为什么编译时间久的原因。

然而,最终只有使用到的代码,才会被链接,最终算入到固件(.bin/.hex)体积中,固件体积并不会因为编译时间长而改变。

修改LVGL配置文件

lv_conf.h是LVGL的主配置文件,用于打开/关闭LVGL的各类功能、定义分辨率、颜色格式、内存策略、日志等。

在实际使用时我们必须对其进行配置,裁剪我们不需要的功能,优化固件大小等

下面是一些可能会修改的地方进行一个介绍

显示屏

颜色深度

我们LCD设置的是RGB565(16bit)表示像素的颜色深度,所有设置为16

image-20250731182134067

字节序

该配置项作用:设置16位颜色(RGB565)数据在内存中的高低字节顺序(大端/小端)。

LVGL v9.0之后已经移除了该配置,会自动根据你传给flush_cb的像素缓冲区格式和显示驱动的要求来处理字节顺序。

image-20250731203914835

内存管理

内存池介绍

LVGL在运行时会不断创建、销毁控件(比如按钮、标签、图片等),它需要用lv_malloc等函数动态分配内存。

为了适配嵌入式系统(有些没有标准库的malloc/free,或者用外部SRAM),LVGL自带一套内存管理机制,你可以把一块内存固定分配给LVGL,全部动态分配都在这块里面进行,互不干扰主程序其他部分。

分配内存池大小

lv_conf.h文件中这里可以看到这里可以设置供lv_malloc使用的内存大小,这个大小根据页面复杂程度控件多少进行分配,使用时根据使用内存大小自行修改即可

image-20250731183659020

这里分配的是64K,所有LVGL控件、动画、图片缓冲、内部结构体等动态分配的内存,都只能用这64KB,用完就lv_malloc失败,控件创建会报错!

假设使用的是STM32F411CEU6,他的SRAM是128KB,如果我们分配64KB给LVGL,那么剩下的64KB就是给主程序栈、全局变量等的空间,相当于把STM32的SRAM划分一部分专门给LVGL分配内存!

请注意分配给LVGL的内存不要浪费,如果剩下的空间较少的话,就会导致MCU其他程序分配空间不够,运行失败!

显示刷新与输入设备

刷新周期

该参数表示界面刷新的默认频率,单位ms,数值越小刷新越快,这里表示每33ms刷新一次,理论上帧率就是33FPS(1000/33)

我们想要60FPS的话,就设置16~17ms左右即可

image-20250731185124033

我们开发的时候需要兼顾刷新率和功耗等,所以应该实际测试并做调整!

刷新越快:动画更流畅,但是MCU负担大,功耗大

刷新越慢:动画卡顿,界面响应不及时,体验不好,但是更省资源,功耗更低

读取周期

用于设置输入设备(如触摸屏、按键)读取的默认周期,单位是ms。

LVGL 9.0之后删除了该配置项,输入设备读取周期请在注册驱动时用indev_drv.read_period成员单独设置。

image-20250731204209007

操作系统

这是LVGL v9.0及以后新加的配置项,用于指定你用的操作系统类型(比如FreeRTOS、CMSIS RTOS2、裸机等)。LVGL的多任务保护、互斥锁、延时等底层机制会根据这个选项自动适配对应的OS API。

这样LVGL才能正确地实现多线程安全和操作系统相关功能,比如保护GUI资源、防止多线程同时操作界面导致崩溃。

我这里使用的FreeRTOS的操作系统,所以修改为LV_OS_FREERTOS

image-20250731205226672

两种选择区别:

LV_OS_FREERTOS: LVGL 直接调用 FreeRTOS 的原生 API(如 xTaskCreate, xSemaphoreTake, vTaskDelay 等)。

LV_OS_CMSIS_RTOS2: LVGL 通过适配层访问操作系统功能(如任务、信号量等),调用的是 CMSIS-RTOS v2 的标准 API(如 osThreadNew, osSemaphoreAcquire)。

字体

这里是对启用的字体大小做配置,这里表示只启用了14号的Montserrat字体

image-20250731190039235

我们使用时将对应需要的字体大小的宏设置为1即可,该字体就会被编译进固件。

注意:只启用需要的字号,可以节省Flash空间,不要都开,因为启用的字体都会被编译进固件中,占用固件体积

控件启用(widget)

这里主要是对lvgl中各种控件(widgets)功能模块的开关和细节选项进行配置,以下是配置的作用:

  • 裁剪代码体积:只打开你项目实际需要使用的控件,未用的全部关闭,可以让固件更小、RAM占用更少,性能更高。

  • 定制控件行为:可以让某些控件有自带默认内容(比如按钮、标签、下拉框等),也可以自定义某些细节(如日历的星期名、月份名、密码明文显示时长等)。

  • 依赖关系:某些控件依赖于其它控件,比如滑块依赖进度条、下拉框依赖标签。关闭时要注意依赖。

默认内容开启

LV_WIDGETS_HAS_DEFAULT_VALUE:该选项可是否让控件自动带有默认内容,比如按钮带默认标签、下拉框带默认选项等

设置为0: 比如新建按钮矩阵会自带 “Btn1”, “Btn2”… 这些默认项,方便测试和快速开发。

设置为1: 所有控件默认内容为空,需要你手动设置内容,更加“干净”。

image-20250731195733241

控件使能

这些都是控制是否编译进该控件类型支持的宏。如果不用某个控件,建议关掉(设为0),可以减少固件体积和内存消耗

image-20250731200038636

下面是所有的配置项,根据需求开关即可

配置项 作用说明
LV_WIDGETS_HAS_DEFAULT_VALUE 控件创建时自动带有默认内容(如按钮有默认标签,日历有默认选项等)
LV_USE_ANIMIMG 启用动画图片控件(可播放帧动画图片)
LV_USE_ARC 启用弧形控件(用于仪表盘等圆弧显示)
LV_USE_ARCLABEL 启用弧形文字控件(文字沿圆弧排列显示)
LV_USE_BAR 启用条形/进度条控件
LV_USE_BUTTON 启用普通按钮控件
LV_USE_BUTTONMATRIX 启用按钮矩阵控件(如数字键盘)
LV_USE_CALENDAR 启用日历控件(显示日期、支持周起始日、月份名等设置)
LV_USE_CANVAS 启用画布控件(可自定义绘图)
LV_USE_CHART 启用图表控件(显示折线、柱状等数据图)
LV_USE_CHECKBOX 启用复选框控件
LV_USE_DROPDOWN 启用下拉框控件(依赖label)
LV_USE_IMAGE 启用图片控件(依赖label)
LV_USE_IMAGEBUTTON 启用图片按钮控件
LV_USE_KEYBOARD 启用虚拟键盘控件
LV_USE_LABEL 启用标签(文本)控件
LV_USE_LED 启用LED指示灯控件
LV_USE_LINE 启用线段控件
LV_USE_LIST 启用列表控件
LV_USE_LOTTIE 启用Lottie动画控件(动画矢量图,依赖canvas和thorvg库)
LV_USE_MENU 启用菜单控件
LV_USE_MSGBOX 启用消息框控件
LV_USE_ROLLER 启用滚轮选择器控件(依赖label)
LV_USE_SCALE 启用刻度尺控件
LV_USE_SLIDER 启用滑块控件(依赖bar)
LV_USE_SPAN 启用富文本控件(支持一行多样式文本)
LV_USE_SPINBOX 启用数字微调控件
LV_USE_SPINNER 启用加载等待动画控件
LV_USE_SWITCH 启用开关控件(类似手机设置中的滑动开关)
LV_USE_TABLE 启用表格控件
LV_USE_TABVIEW 启用分页栏控件(多标签切换,如微信顶部栏目)
LV_USE_TEXTAREA 启用多行文本输入框(依赖label,可用于输入、密码框等)
LV_USE_TILEVIEW 启用多页面平铺控件(可滑动切换页面)
LV_USE_WIN 启用窗口控件(带标题栏、按钮的窗口)
LV_USE_3DTEXTURE 启用3D纹理控件(一般很少用,默认关闭)

主题

LVGL主题(Theme)相关的选项,用于设置界面整体风格、色彩、动效等。

image-20250731201508903

配置项 作用说明
LV_USE_THEME_DEFAULT 启用“默认主题”,一个功能完整、效果美观的主题。
LV_THEME_DEFAULT_DARK 配合默认主题:0为浅色模式(Light),1为深色模式(Dark)。
LV_THEME_DEFAULT_GROW 按钮等控件按下时是否“放大”动效(1=有动态放大,0=无)。
LV_THEME_DEFAULT_TRANSITION_TIME 主题内控件切换动画的默认时长,单位毫秒(ms)。
LV_USE_THEME_SIMPLE 启用“简单主题”,样式很简洁、适合自定义二次开发。
LV_USE_THEME_MONO 启用“单色主题”,为黑白/单色屏显示设计,使用彩屏时应设置为0

布局

LVGL布局系统的配置项,决定你的界面控件能否使用更高级的自动布局方式,不使用都设置为0即可

image-20250731201755812

配置项 作用说明
LV_USE_FLEX 启用Flex布局,类似CSS的Flexbox,支持弹性排列、自动换行、对齐等。适合动态、响应式界面。
LV_USE_GRID 启用Grid布局,类似CSS的Grid,支持复杂的网格(行/列)布局,适合表单、面板等场景。

其他

还有一些其他的配置,使用的时候再看即可

  • 启用性能监控器以查看 CPU 和帧率:

image-20250731204544393

编译报错

如果发现关闭某些控件后,出现编译报错的现象,很大概率是因为部分控件之间有相互依赖关系,必须同时开启或关闭,如果一个开启一个关闭就可能会导致编译失败

比如:

1
2
3
4
5
6
7
#define LV_USE_SCALE 1
#define LV_USE_LINE 1
// 必须一起开

#define LV_USE_SCALE 1
#define LV_USE_LINE 0
// 这样就会导致lv_scale.c中用到Line相关API时就会报错

修改LVGL移植层

移植层文件启用

编译通过,修改LVGL配置文件过后,就该开始适配对应的显示驱动触摸驱动文件系统

我们移植过来时的移植层文件中,由于规范了命名(去掉了template后缀),所以还需要对对应的.c和.h文件进行修改启用

image-20250731214229853

这里我的项目中只使用了显示驱动适配输入设备适配

image-20250731215517535

移植层中所有我们需要使用的所有.c文件中和对应的.h文件,都要启用和修改,具体每个文件修改位置如下:

lv_port_xxx.h:

image-20250731215233116

lv_port_xxx.c:

image-20250731214510730

移植显示驱动

lv_port_disp.c文件中,主要注意的就只有lv_port_disp_initdisp_flush,以下对这两个分别进行介绍

设置分辨率

lv_port_disp.c中由这几个宏定义,我们需要修改为自己的大小(lvgl v9.0之后才有,更低版本没有需要自己定义)

image-20250802161130708

我使用的是240*280的屏幕

MY_DISP_HOR_RES: 水平分辨率,我设置为240

MY_DISP_VER_RES: 垂直分辨率,我设置为280

BYTE_PER_PIXEL: 每个像素所占字节数,我们LCD设置的是rgb565(16bit),所以代表2字节

lv_port_disp_init

lvgl v9.x版本往上流程具有一定的修改,和老版本使用的方式不同!

这个函数主要是三部分组成:

  1. 初始化显示屏
  2. 创建显示对象并设置刷新回调
  3. 显示缓冲区和缓冲配置示例

image-20250801223651638

初始化显示屏

这里主要是我们完成底层硬件显示屏的初始化,初始化SPI、LCD控制器、设置背光等

但是这里我们一般不使用,因为在我们的项目中会有集中初始化的地方

image-20250801224346896

创建显示对象并设置刷新回调

这里是在LVGL内部创建一个显示对象,并告诉LVGL如何把渲染好的像素数据刷新到屏幕上。

  • lv_display_create:创建一个LVGL显示驱动实例,传参就是我们设置的分辨率的宏定义。

  • lv_display_set_flush_cb:设置刷新回调函数,LVGL每次有画面要显示时会调用你写的disp_flush函数,把一个区域的数据通过SPI等方式传给屏幕。

image-20250801224604611

显示缓冲区

显示缓冲区介绍

LVGL显示缓冲区使用的是RAM空间,LVGL把要渲染的图形的像素内容先放到这个缓冲区,然后我们在flush_cb回调中把这块内容传给实际的LCD屏幕。

为什么要用缓冲区?

  • 解耦渲染和传输:LVGL的图形引擎在内存中先完成绘制,不直接操作屏幕——这样可以用复杂算法、动画、混合等技术。
  • 提升效率:你可以用DMA等高效机制将缓冲区内容一次性传输到LCD,而不是逐像素刷屏。
  • 节约资源:缓冲区可以只分配部分区域(比如10行),不用整个屏幕都占RAM,适合资源受限的MCU。
  • 支持双缓冲/全屏刷新:可实现并行渲染和刷屏、无撕裂动画。

lvgl在为我们提供了三种显示缓冲的方式:单缓冲区双缓冲区全屏双缓冲,使用时在lv_port_disp_init注释掉另外两种的代码或使用条件编译控制使用的方法即可

下面将介绍工作原理与各方式的优劣

LVGL显示工作原理
  1. 渲染阶段:LVGL首先在内存中的显示缓冲区里渲染出一块区域的像素数据(比如10行),将所有控件、图片、文字等按照你的界面需求画在缓冲区里。
  2. 刷新阶段:LVGL完成这一块的渲染后,调用你的flush_cb回调,把刚刚渲染好的像素数据交给你。
  3. 数据传输:你在flush_cb里,使用DMA或CPU把这块缓冲区的像素数据“刷新”到LCD屏幕的对应位置,让实际屏幕内容发生变化。
  4. 通知完成:当数据传输(刷新)完成后,你通知LVGL(通常调用lv_disp_flush_ready()),告知上一块区域刷屏已结束。
  5. 循环过程:LVGL继续在缓冲区渲染下一块区域,然后重复上述渲染→刷新过程,直到整帧内容全部显示到屏幕上。
单缓冲区(部分缓冲)

buf_1_1:分配的缓冲区,一般至少分配屏幕的10行像素数据

image-20250802162108852

  • 优点:占用内存最少,适合小RAM MCU。

  • 缺点:刷屏期间LVGL必须等传输完才能画下一块,渲染和刷屏不能并行,效率略低

单缓冲区适合内存极度紧张的MCU,界面简单、刷新要求不高!

双缓冲区(部分缓冲)

分配两块同样大小的缓冲区。LVGL在一块缓冲区渲染的同时,用DMA把另一块缓冲区的数据刷新到LCD。渲染和刷屏可以并行,效率高,适合使用DMA传输的情况。

我们通常就是使用这种方式配合DMA进行传输,在使用时可以根据RAM的大小,适当提高缓冲区的大小,但是最好设置为一次刷新>=10行!

image-20250802164830328

  • 优点:效率高,动画流畅,推荐DMA场景使用。

  • 缺点:比单缓冲多占用一份缓冲区内存,但一般MCU都能承受。

双缓冲区适合有DMA的MCU,RAM中等,界面需要流畅滚动、动画等。

全屏双缓冲(全屏缓冲)

分配两块全屏大小的缓冲区。LVGL每次都将整屏内容渲染到缓冲区,然后一次性刷到LCD。适合直接切换帧缓地址的显示器(如外接显存或高级MCU)。

image-20250802165339302

优点:画面最流畅,无撕裂、无闪烁,支持复杂动画。

缺点:占用内存极大(如240x280x2=134KB一块),普通MCU很难承受。

全屏双缓冲适合高端MCU、带外部RAM、智能屏等,动画复杂、全屏刷新需求高!

API
  • 缓冲区设置相关函数
1
2
3
4
5
6
7
void lv_display_set_buffers(
lv_display_t * disp, // 显示对象
void * buf1, // 第一块缓冲区指针
void * buf2, // 第二块缓冲区指针(可为NULL)
size_t size_in_bytes, // 第一块缓冲区的字节数
lv_display_render_mode_t render_mode // 渲染模式
);
  • disp:由 lv_display_create 创建的显示对象指针

  • buf1:第一块缓冲区地址(如 uint8_t buf[宽*高*字节/像素]

  • buf2:第二块缓冲区地址(如需双缓冲,否则可传 NULL)

  • size_in_bytes:缓冲区的实际字节数(不是像素数!)

  • render_mode

    • LV_DISPLAY_RENDER_MODE_PARTIAL:部分渲染(如只分配10行缓冲区,适合RAM小)

    • LV_DISPLAY_RENDER_MODE_DIRECT:全屏双缓冲(适合RAM充足,支持无闪烁动画)

disp_flush

image-20250802171013290

在初始化的过程中我们使用 lv_display_set_flush_cb函数设置了刷新回调函数,现在就该对我们的刷新回调函数进行一个实现了

函数原型:

1
static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
  • disp_drv:LVGL的显示驱动结构体,里面有各种屏幕配置参数(一般用不到)。
  • area:定义了需要刷新的屏幕矩形区域(起始坐标x1,y1,结束坐标x2,y2)。
    • image-20250802171749963
  • px_map:指向这块区域的像素数据,格式取决于我们配置的色深(如RGB565 2字节/像素)。

示例程序

我们移植过来的时候,lvgl给出的示例回调函数如下,使用的是最简单也是最耗时的方式,逐个将所有像素显示到屏幕上

image-20250802171155166

我们将对其流程进行一个拆解

判断是否允许刷新

lvgl v9.x新增内容,这个变量定义在上方

image-20250802172103951

image-20250802172042347

比如某些时刻禁止刷屏时可以使用

像素刷新逻辑

image-20250802172221877

工作原理:遍历area定义的区域,把px_map里的每个像素输送到对应的屏幕坐标。

  • 这里的put_px(x, y, *px_map)是示例,实际应替换成我们的LCD底层驱动的写像素的接口。
  • px_map++是指向下一像素(如果RGB565则每次应加2字节)。
刷新完成通知

LVGL要求我们务必在数据送完屏幕后调用它,告诉图形库我们准备好下次刷新了,否则界面不会继续渲染后续内容。

image-20250802172350597

这个函数非常重要

  • 如果我们使用DMA异步传输,应该在DMA完成中断里调用,而不是在这里立即调用。

常用做法

上面的示例是最简单,但最耗时的做法,我们实际开发过程中并不会这么做,以下就介绍一下实际开发过程中的做法

DMA批量传输

我们可以使用SPI的DMA方式直接将所有的像素数据批量传输给LCD,然后在DMA传输完成后的SPI传输完成的中断回调函数中执行刷新完成通知

不再逐点的去写,这样就可以由很快的速度了

具体可以看逻辑开发的使用DMA驱动SPI显示屏笔记

image-20250803231209441

移植触摸驱动

lv_port_indev.c文件中,有多种触摸的方式触摸屏鼠标键盘编码器按钮,我们通常只会选择一种进行移植,以下介绍移植触摸屏相关的内容

lv_port_indev_init

lvgl v9.x版本往上流程具有一定的修改,和老版本使用的方式不同!

这个函数主要是三部分组成:

  1. 初始化输入设备
  2. 创建输入对象
  3. 设置刷新回调函数

lvgl将所有的输入设备初始化都放进去了,我们只需要保留我们需要输入设备即可

image-20250807173520613

这里我们只需要触摸屏,所以仅保留触摸屏的初始化,最终效果如下:

image-20250807173712546

还有就是,前面的函数声明和变量定义也仅保留需要的输入设备,我这里只剩下了触摸屏的

image-20250807173840403

touchpad_init

触摸屏的初始化函数,可以在这里放触摸的初始化函数,也可以自己安排初始化(保证在lvgl初始化前)

image-20250807174123810

touchpad_read

该函数就是我们之前注册的回调函数,由lvgl调用读取触摸屏输入

image-20250807174509121

这里主要会使用touchpad_is_pressed先判断触摸是被是否按下,确认按下后,在调用touchpad_get_xy获取触摸点的坐标

我们需要移植的也就是这两个函数

touchpad_is_pressed

该函数用于判断触摸是否被按下,初始是这样的:

image-20250807174755432

我们移植的话,只需要调用触摸驱动对应的API获取按下的手指个数,进行判断是否被按下(个数>0就按下了),然后返回true即可

image-20250807174834467

touchpad_get_xy

该函数用于触摸按下后,获取按下位置的坐标x和y,我们需要做的就是将触摸获取到的坐标赋值给xy,供lvgl使用

初始是这样的

image-20250807175204575

我们调用触摸芯片获取触摸点坐标的函数CST816T_Get_Postiton,该函数会将xy坐标存储在该类型的结构体CST816T_Get_Position

image-20250807175758084

然后我们将坐标赋值给x,y变量即可

image-20250807175259909

配置和启动LVGL

以上移植完成后,我们还需要配置LVGL的时基,同时周期性调用LVGL的任务处理函数lv_task_handler()使LVGL运行起来

配置时基

时基(LV_TICK)作为LVGL的核心时间管理机制,用于驱动动画、任务(lv_task)、输入处理等功能,详细介绍见LVGL调度笔记

FreeRTOS

使用FreeRTOS的钩子函数

FreeRTOS的钩子函数vApplicationTickHook()会在每次系统时钟节拍(tick)中断时被调用(通常是SysTick中断),详细介绍见笔记或官方文档

下面我们来介绍如何进行配置

1.在FreeRTOSConfig.h中配置configUSE_IDLE_HOOK1,启用钩子函数

image-20250807222131884

运行周期:由configTICK_RATE_HZ决定, 一般设置为1000,也就是1ms

image-20250807222225838

  1. 自己实现vApplicationTickHook,里面调用lv_tick_inc(1),告诉LVGL已经过去了1ms
1
2
3
4
5
void vApplicationTickHook()
{
// 告诉lvgl已经过去了1毫秒
lv_tick_inc(1);
}

注意:vApplicationTickHook() 从ISR内执行,因此必须非常短,且不能被阻塞!

单任务控制

单独创建一个任务,每过10ms就调用一次lv_tick_inc(10)

1
2
3
4
5
6
7
8
9
/* LVGL 时间源 */
void LVGLTick(void const * argument)
{
for(;;)
{
lv_tick_inc(10);
osDelay(10);
}
}

裸机

使用SysTick中断

确保HAL库使用SysTick作为时基,在**SysTick_Handler()**中调用即可lv_tick_inc(1)

定时器

自己配置一个1ms中断的定时器,在内部调用lv_tick_inc(1)即可

周期性调用lvgl任务处理

我们必须在循环中间隔5~20ms持续调用lv_task_handler() 来处理LVGL的所有任务,否则所有UI刷新、动画、事件分发等任务操作都不会执行,也就不会更新

  • 在使用LVGL时,通常伴随RTOS的使用,所以我们都是将lvgl任务处理单独由一个任务执行,保证lv_task_handler()周期性的被调用处理所有的事件(UI刷新,输入检测等)
1
2
3
4
5
6
7
8
9
10
/* LVGL Handler task,驱动LVGL运行 */
void LvglHandlerTask(void *argument)
{
while(1)
{
lv_task_handler(); // 调用lvgl的事务处理
osDelay(5);
}
}

  • 或者在主循环中调用也可以
1
2
3
4
5
while (1)								
{
lv_timer_handler(); //调用lvgl的事务处理
delay_ms(1);
}

移植成功与否测试

显示驱动测试

随便在一个任务或者main函数中进行:

1.LCD初始化

2.LVGL初始化

3.画一个按钮

4.启动LVGL的任务处理

image-20250805141614519

然后下载到MCU中,看看是否显示正常,触摸正常

image-20250807230309201

触摸驱动测试

触摸必须保证配置了LVGL时基,否则不会生效

和显示测试一样,随便在一个任务或者main函数中进行:

1.LCD初始化

2.LVGL初始化

3.画一个按钮

4.启动LVGL的任务处理

image-20250805141614519

点击按钮,按钮启用即证明触摸移植成功

image-20250807230014498

移植遇到的问题

LVGL触摸回调函数不触发

首先检查没有lvgl的时候,触摸驱动是否正常,如果正常,多半是LVGL配置的时基出了问题,检查是否调用lv_tick_inc(1)

没有调用,没有通知LVGL时间流逝,即使调用了lv_task_handler(),也不会执行所有的任务处理