LVGL移植到STM32

LVGL移植到STM32
THEDI移植LVGL到STM32
官方英文移植教程:
Getting LVGL(获取 LVGL) — 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版本
移植
该笔记基于lvgl v9.x
往上的,如果使用版本较低,就可能与本笔记中的内容不同
我们的只需要关注源码中需要移植的部分:
以keil工程为例,我们找到项目根目录下Middlewares
(没有就创建一个),然后再其中创建一个lvgl文件夹用于存放我们移植lvgl相关的内容
然后在该lvgl目录下
新建src
和port
文件夹
src文件夹
:lvgl源码
port文件夹
:存放我们自己的移植代码,比如显示驱动移植、输入设备(触摸/按键)接口移植等等
src源码
将下载下来的lvgl库的lvgl/src
源码文件夹替换刚刚创建的src文件夹
移植文件
再将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专用的二选一即可)
我们这里只有显示驱动
和触摸驱动
所有只需要保留这两个到我们项目的port
中,然后去掉template后缀规范命名
配置文件
主要就是lvgl库中的这三个文件需要移植:
lv_conf_template.h/lv_cong
: LVGL的配置头文件模板,里面包含了各种宏定义(比如颜色深度、是否开启某个控件/功能、内存大小等)。
lv_version.h
: LVGL版本信息头文件
lvgl.h
: 对源码src
目录下所有头文件的包含,后续只需要包含整个文件就能使用全部API
将这三个文件复制到Middlewares/lvgl
目录下,然后将配置文件更名为lv_conf
工程管理
源文件添加
在keil工程中,新建这三个组src
、port
、conf
,然后分别添加对应的.c文件到项目
conf组
把这lv_conf.h
、lv_version.h
、lvgl.h
三个放到conf组中
port组
将需要移植port文件夹中的的.c文件和.h文件添加到port组
src组
第一种
:可以将需要添加的所有.c文件都添加到src组中
第二种
:按照src文件夹目录结构,将src中的各个模块单独创建组添加,这样比较好找文件
然后按照下方给的移植总览表格
将对应模块所需要的.c文件添加到对应组中即可
移植总览表格
全部移植
就是该文件夹下的所有.c文件
,包括子文件夹
,一个一个都要手动
添加到组中,必须全部添加,否则编译会报错
只有driver和other两个文件夹可以完全不用移植,如果使用到了再添加进去
名称 | 作用描述 | 必要性 |
---|---|---|
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
名称 | 作用 | 必要性 |
---|---|---|
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
名称 | 作用 | 必要性 |
---|---|---|
flex | Flex 布局引擎,类似于网页的 flex 布局,控件自适应排列 | 建议移植(用到 flex 布局或相关控件时必须) |
grid | Grid 布局引擎,类似于网页的 grid 布局,控件网格排列 | 建议移植(用到 grid 布局或相关控件时必须) |
lv_layout.c | 布局系统主入口和调度代码 | 必须移植 |
lv_layout.h | 布局系统主头文件 | 必须移植 |
lv_layout_private.h | 布局系统私有头文件 | 必须移植 |
libs
名称 | 作用描述 | 必要性 |
---|---|---|
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
名称 | 作用 | 必要性(必须移植、建议移植、按需移植) |
---|---|---|
cache | 缓存相关功能,提升性能 | 按需移植(需要缓存机制时才用,默认可不加) |
osal
stdlib
名称 | 作用描述 | 必要性 |
---|---|---|
builtin | LVGL 内置标准库实现(如内存、字符串等) | 建议移植(大多数平台通用,默认推荐) |
clib | C 标准库适配层(如 malloc、memcpy 等) | 按需移植(需适配特殊C库时才用) |
micropython | MicroPython 适配相关 | 按需移植(做 MicroPython 绑定时用) |
rtthread | RT-Thread 系统下标准库适配 | 按需移植(RT-Thread 系统用) |
uefi | UEFI 固件环境下标准库适配 | 按需移植(做固件开发才用) |
themes
名称 | 作用描述 | 必要性 |
---|---|---|
default | LVGL 默认主题(配色、控件样式等) | 建议移植(默认主题常用,建议加) |
mono | 单色主题(灰度/黑白配色) | 按需移植(如需单色风格时用) |
simple | 简化主题(简洁外观、较少样式) | 按需移植(如需极简界面时用) |
lv_theme.c | 主题系统主实现文件 | 建议移植(使用主题功能时必须) |
lv_theme.h | 主题系统主头文件 | 建议移植(使用主题功能时必须) |
lv_theme_private.h | 主题系统私有头文件 | 建议移植 |
widgets
名称 | 作用描述 | 必要性 |
---|---|---|
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
这里面的驱动不使用的话直接都不移植即可
名称 | 作用 | 必要性 |
---|---|---|
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
不用就都不移植
名称 | 作用描述 | 必要性(必须移植、建议移植、按需移植) |
---|---|---|
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库所有的头文件搜索路径即可,四个都要添加,不能缺少,否则可能会出现找不到头文件错误
启用lv_conf.h
我们在lvgl配置文件lv_conf.h,将此处的0
修改为1
,启用该配置文件
关于lv_conf.h:
lv_conf.h
是lvgl的配置文件,主要用于配置LVGL库要不要编译哪些功能、用多大内存、支不支持某些控件/主题/库、底层适配方式等。这个文件就是在
裁剪
库的功能和适配你的硬件/需求。
这里启用后就能进行编译了,如果编译报错可以看是否是下面的问题!
编译报错
找不到头文件
然后进行编译操作,可以发现有很多error,这些都是由于lv_conf_internal.h
找不到../../lv_conf.h
造成的。
我们可以看到由于没有宏定义,lv_conf_internal.h
包含lv_conf.h
的路径的方法是第三种:../../lv_conf.h
然而我们的lv_conf.h的实际路径是../lv_conf.h
,所以找不到
解决方法:
在预处理器添加宏定义LV_CONF_INCLUDE_SIMPLE
,让lv_conf_internal.h
包含头文件方式修改为自动查找头文件搜索路径
关于移植的一些问题
移植的时候尽量全部移植,没事不要去裁剪,因为裁剪过后,如果进行了函数调用,找不到.c文件,会报链接错误。
我的疑问:如果全部移植,编译时间那么久,不会导致最终的固件体积增大吗?
不会
。因为添加到工程的所有.c文件在编译阶段时都会被单独编译成目标文件(.o/.obj)以供后续链接使用,这就是为什么编译时间久的原因。
然而,最终只有使用到的代码,才会被链接,最终算入到固件(.bin/.hex)体积中,固件体积并不会因为编译时间长而改变。
修改LVGL配置文件
lv_conf.h
是LVGL的主配置文件,用于打开/关闭LVGL的各类功能、定义分辨率、颜色格式、内存策略、日志等。
在实际使用时我们必须对其进行配置,裁剪我们不需要的功能,优化固件大小等
下面是一些可能会修改的地方进行一个介绍
显示屏
颜色深度
我们LCD设置的是RGB565(16bit)表示像素的颜色深度,所有设置为16
字节序
该配置项作用:设置16位颜色(RGB565)数据在内存中的高低字节顺序(大端/小端)。
在LVGL v9.0
之后已经移除了该配置,会自动根据你传给flush_cb
的像素缓冲区格式和显示驱动的要求来处理字节顺序。
内存管理
内存池介绍
LVGL在运行时会不断创建、销毁控件(比如按钮、标签、图片等),它需要用lv_malloc
等函数动态分配内存。
为了适配嵌入式系统(有些没有标准库的malloc/free
,或者用外部SRAM),LVGL自带一套内存管理机制,你可以把一块内存固定分配给LVGL,全部动态分配都在这块里面进行,互不干扰主程序其他部分。
分配内存池大小
lv_conf.h文件中这里可以看到这里可以设置供lv_malloc
使用的内存大小,这个大小根据页面复杂程度
和控件多少
进行分配,使用时根据使用内存大小自行修改
即可
这里分配的是64K
,所有LVGL控件、动画、图片缓冲、内部结构体等动态分配的内存,都只能用这64KB,用完就lv_malloc
失败,控件创建会报错!
假设使用的是STM32F411CEU6,他的SRAM是128KB,如果我们分配64KB给LVGL,那么剩下的64KB就是给主程序栈、全局变量等的空间,相当于把STM32的SRAM划分一部分专门给LVGL分配内存!
请注意分配给LVGL的内存不要浪费,如果剩下的空间较少的话,就会导致MCU其他程序分配空间不够,运行失败!
显示刷新与输入设备
刷新周期
该参数表示界面刷新的默认频率
,单位ms,数值越小刷新越快,这里表示每33ms刷新一次,理论上帧率就是33FPS(1000/33)
我们想要60FPS的话,就设置16~17ms左右即可
我们开发的时候需要兼顾刷新率和功耗等,所以应该实际测试并做调整!
刷新越快
:动画更流畅,但是MCU负担大,功耗大
刷新越慢
:动画卡顿,界面响应不及时,体验不好,但是更省资源,功耗更低
读取周期
用于设置输入设备(如触摸屏、按键)读取的默认周期,单位是ms。
在LVGL 9.0
之后删除了该配置项,输入设备读取周期请在注册驱动时用indev_drv.read_period
成员单独设置。
操作系统
这是LVGL v9.0及以后新加的配置项,用于指定你用的操作系统类型(比如FreeRTOS、CMSIS RTOS2、裸机等)。LVGL的多任务保护、互斥锁、延时等底层机制会根据这个选项自动适配对应的OS API。
这样LVGL才能正确地实现多线程安全和操作系统相关功能,比如保护GUI资源、防止多线程同时操作界面导致崩溃。
我这里使用的FreeRTOS的操作系统,所以修改为LV_OS_FREERTOS
两种选择区别:
LV_OS_FREERTOS: LVGL 直接调用 FreeRTOS 的原生 API(如
xTaskCreate
,xSemaphoreTake
,vTaskDelay
等)。LV_OS_CMSIS_RTOS2: LVGL 通过适配层访问操作系统功能(如任务、信号量等),调用的是 CMSIS-RTOS v2 的标准 API(如
osThreadNew
,osSemaphoreAcquire
)。
字体
这里是对启用的字体大小做配置,这里表示只启用了14号的Montserrat
字体
我们使用时将对应需要的字体大小的宏设置为1即可,该字体就会被编译进固件。
注意:只启用需要的字号,可以节省Flash空间,不要都开,因为启用的字体都会被编译进固件中,占用固件体积
控件启用(widget)
这里主要是对lvgl中各种控件(widgets)功能模块的开关和细节选项进行配置,以下是配置的作用:
裁剪代码体积
:只打开你项目实际需要使用的控件,未用的全部关闭,可以让固件更小、RAM占用更少,性能更高。定制控件行为:可以让某些控件有自带默认内容(比如按钮、标签、下拉框等),也可以自定义某些细节(如日历的星期名、月份名、密码明文显示时长等)。
依赖关系:某些控件依赖于其它控件,比如滑块依赖进度条、下拉框依赖标签。关闭时要注意依赖。
默认内容开启
LV_WIDGETS_HAS_DEFAULT_VALUE
:该选项可是否让控件自动带有默认内容,比如按钮带默认标签、下拉框带默认选项等
设置为0: 比如新建按钮矩阵会自带 “Btn1”, “Btn2”… 这些默认项,方便测试和快速开发。
设置为1: 所有控件默认内容为空,需要你手动设置内容,更加“干净”。
控件使能
这些都是控制是否编译进该控件类型支持的宏。如果不用某个控件,建议关掉(设为0),可以减少固件体积和内存消耗
下面是所有的配置项,根据需求开关即可
配置项 | 作用说明 |
---|---|
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)相关的选项,用于设置界面整体风格、色彩、动效等。
配置项 | 作用说明 |
---|---|
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
即可
配置项 | 作用说明 |
---|---|
LV_USE_FLEX | 启用Flex布局,类似CSS的Flexbox,支持弹性排列、自动换行、对齐等。适合动态、响应式界面。 |
LV_USE_GRID | 启用Grid布局,类似CSS的Grid,支持复杂的网格(行/列)布局,适合表单、面板等场景。 |
其他
还有一些其他的配置,使用的时候再看即可
- 启用性能监控器以查看 CPU 和帧率:
编译报错
如果发现关闭某些控件
后,出现编译报错的现象,很大概率是因为部分控件之间有相互依赖关系,必须同时开启或关闭
,如果一个开启一个关闭就可能会导致编译失败
比如:
1 |
|
修改LVGL移植层
移植层文件启用
编译通过,修改LVGL配置文件过后,就该开始适配对应的显示驱动
、触摸驱动
、文件系统
等
我们移植过来时的移植层文件中,由于规范了命名(去掉了template后缀),所以还需要对对应的.c和.h文件进行修改
和启用
这里我的项目中只使用了显示驱动适配
、输入设备适配
移植层中所有我们需要使用的所有.c
文件中和对应的.h
文件,都要启用和修改,具体每个文件修改位置如下:
lv_port_xxx.h
:
lv_port_xxx.c
:
移植显示驱动
在lv_port_disp.c
文件中,主要注意的就只有lv_port_disp_init
和disp_flush
,以下对这两个分别进行介绍
设置分辨率
在lv_port_disp.c
中由这几个宏定义,我们需要修改为自己的大小(lvgl v9.0之后才有,更低版本没有需要自己定义)
我使用的是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版本往上流程具有一定的修改,和老版本使用的方式不同!
这个函数主要是三部分组成:
- 初始化显示屏
- 创建显示对象并设置刷新回调
- 显示缓冲区和缓冲配置示例
初始化显示屏
这里主要是我们完成底层硬件显示屏的初始化,初始化SPI、LCD控制器、设置背光等
但是这里我们一般不使用,因为在我们的项目中会有集中初始化的地方
创建显示对象并设置刷新回调
这里是在LVGL内部创建一个显示对象
,并告诉LVGL如何把渲染好的像素数据刷新
到屏幕上。
lv_display_create
:创建一个LVGL显示驱动实例,传参就是我们设置的分辨率的宏定义。lv_display_set_flush_cb
:设置刷新回调函数
,LVGL每次有画面要显示时会调用你写的disp_flush
函数,把一个区域的数据通过SPI等方式传给屏幕。
显示缓冲区
显示缓冲区介绍
LVGL显示缓冲区使用的是RAM空间,LVGL把要渲染的图形的像素内容先放到这个缓冲区,然后我们在flush_cb
回调中把这块内容传给实际的LCD屏幕。
为什么要用缓冲区?
- 解耦渲染和传输:LVGL的图形引擎在内存中先完成绘制,不直接操作屏幕——这样可以用复杂算法、动画、混合等技术。
- 提升效率:你可以用DMA等高效机制将缓冲区内容一次性传输到LCD,而不是逐像素刷屏。
- 节约资源:缓冲区可以只分配部分区域(比如10行),不用整个屏幕都占RAM,适合资源受限的MCU。
- 支持双缓冲/全屏刷新:可实现并行渲染和刷屏、无撕裂动画。
lvgl在为我们提供了三种显示缓冲的方式:单缓冲区
、双缓冲区
、全屏双缓冲
,使用时在lv_port_disp_init
注释掉另外两种的代码或使用条件编译控制使用的方法即可
下面将介绍工作原理与各方式的优劣
LVGL显示工作原理
- 渲染阶段:LVGL首先在内存中的显示缓冲区里
渲染
出一块区域的像素数据(比如10行),将所有控件、图片、文字等按照你的界面需求画在缓冲区里。 - 刷新阶段:LVGL完成这一块的渲染后,调用你的
flush_cb
回调,把刚刚渲染好的像素数据交给你。 - 数据传输:你在
flush_cb
里,使用DMA或CPU把这块缓冲区的像素数据“刷新”到LCD屏幕的对应位置,让实际屏幕内容发生变化。 - 通知完成:当数据传输(刷新)完成后,你通知LVGL(通常调用
lv_disp_flush_ready()
),告知上一块区域刷屏已结束。 - 循环过程:LVGL继续在缓冲区
渲染
下一块区域,然后重复上述渲染→刷新
过程,直到整帧内容全部显示到屏幕上。
单缓冲区(部分缓冲)
buf_1_1:分配的缓冲区,一般至少分配屏幕的10行像素数据
优点
:占用内存最少,适合小RAM MCU。缺点
:刷屏期间LVGL必须等传输完才能画下一块,渲染和刷屏不能并行,效率略低
单缓冲区适合内存极度紧张的MCU,界面简单、刷新要求不高!
双缓冲区(部分缓冲)
分配两块同样大小的缓冲区。LVGL在一块缓冲区渲染
的同时,用DMA把另一块缓冲区的数据刷新
到LCD。渲染和刷屏可以并行
,效率高,适合使用DMA传输的情况。
我们通常就是使用这种方式配合DMA进行传输,在使用时可以根据RAM的大小,适当提高缓冲区的大小,但是最好设置为一次刷新>=10行!
优点
:效率高,动画流畅,推荐DMA场景使用。缺点
:比单缓冲多占用一份缓冲区内存,但一般MCU都能承受。
双缓冲区适合有DMA的MCU,RAM中等,界面需要流畅滚动、动画等。
全屏双缓冲(全屏缓冲)
分配两块全屏大小
的缓冲区。LVGL每次都将整屏内容渲染到缓冲区,然后一次性刷到LCD。适合直接切换帧缓地址的显示器(如外接显存或高级MCU)。
优点
:画面最流畅,无撕裂、无闪烁,支持复杂动画。
缺点
:占用内存极大(如240x280x2=134KB一块),普通MCU很难承受。
全屏双缓冲适合高端MCU、带外部RAM、智能屏等,动画复杂、全屏刷新需求高!
API
- 缓冲区设置相关函数
1 | void lv_display_set_buffers( |
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
在初始化的过程中我们使用 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
)。px_map
:指向这块区域的像素数据,格式取决于我们配置的色深(如RGB565 2字节/像素)。
示例程序
我们移植过来的时候,lvgl给出的示例回调函数如下,使用的是最简单
也是最耗时
的方式,逐个将所有像素显示到屏幕上
我们将对其流程进行一个拆解
判断是否允许刷新
lvgl v9.x新增内容,这个变量定义在上方
比如某些时刻禁止刷屏时可以使用
像素刷新逻辑
工作原理:遍历area
定义的区域,把px_map
里的每个像素输送到对应的屏幕坐标。
- 这里的
put_px(x, y, *px_map)
是示例,实际应替换成我们的LCD底层驱动
的写像素的接口。 px_map++
是指向下一像素(如果RGB565则每次应加2字节)。
刷新完成通知
LVGL要求我们务必在数据送完屏幕后调用它,告诉图形库我们准备好下次刷新了,否则界面不会继续渲染后续内容。
这个函数非常重要
- 如果我们使用DMA异步传输,应该在DMA完成中断里调用,而不是在这里立即调用。
常用做法
上面的示例是最简单,但最耗时的做法,我们实际开发过程中并不会这么做,以下就介绍一下实际开发过程中的做法
DMA批量传输
我们可以使用SPI的DMA方式直接将所有的像素数据批量传输给LCD,然后在DMA传输完成后的SPI传输完成的中断回调函数中执行刷新完成通知
不再逐点的去写,这样就可以由很快的速度了
具体可以看逻辑开发的使用DMA驱动SPI显示屏
笔记
移植触摸驱动
在lv_port_indev.c
文件中,有多种触摸的方式触摸屏
、鼠标
、键盘
、编码器
、按钮
,我们通常只会选择一种进行移植,以下介绍移植触摸屏相关的内容
lv_port_indev_init
lvgl v9.x版本往上流程具有一定的修改,和老版本使用的方式不同!
这个函数主要是三部分组成:
- 初始化输入设备
- 创建输入对象
- 设置刷新回调函数
lvgl将所有的输入设备初始化都放进去了,我们只需要保留我们需要输入设备即可
这里我们只需要触摸屏,所以仅保留触摸屏的初始化,最终效果如下:
还有就是,前面的函数声明和变量定义也仅保留需要的输入设备,我这里只剩下了触摸屏的
touchpad_init
触摸屏的初始化函数,可以在这里放触摸的初始化函数,也可以自己安排初始化(保证在lvgl初始化前)
touchpad_read
该函数就是我们之前注册的回调函数,由lvgl调用读取触摸屏输入
这里主要会使用touchpad_is_pressed
先判断触摸是被是否按下,确认按下后,在调用touchpad_get_xy
获取触摸点的坐标
我们需要移植的也就是这两个函数
touchpad_is_pressed
该函数用于判断触摸是否被按下,初始是这样的:
我们移植的话,只需要调用触摸驱动对应的API获取按下的手指个数,进行判断是否被按下(个数>0就按下了),然后返回true
即可
touchpad_get_xy
该函数用于触摸按下后,获取按下位置的坐标x和y,我们需要做的就是将触摸获取到的坐标赋值给xy,供lvgl使用
初始是这样的
我们调用触摸芯片获取触摸点坐标的函数CST816T_Get_Postiton
,该函数会将xy坐标存储在该类型的结构体CST816T_Get_Position
中
然后我们将坐标赋值给x,y变量即可
配置和启动LVGL
以上移植完成后,我们还需要配置LVGL的时基
,同时周期性调用LVGL的任务处理函数lv_task_handler()
使LVGL运行起来
配置时基
时基(LV_TICK)作为LVGL的核心时间管理机制,用于驱动动画、任务(lv_task)、输入处理等功能,详细介绍见LVGL调度
笔记
FreeRTOS
使用FreeRTOS的钩子函数
FreeRTOS的钩子函数vApplicationTickHook()
会在每次系统时钟节拍(tick)中断时被调用(通常是SysTick中断),详细介绍见笔记或官方文档
下面我们来介绍如何进行配置
1.在FreeRTOSConfig.h
中配置configUSE_IDLE_HOOK
为 1
,启用钩子函数
运行周期:由configTICK_RATE_HZ
决定, 一般设置为1000,也就是1ms
- 自己实现
vApplicationTickHook
,里面调用lv_tick_inc(1)
,告诉LVGL已经过去了1ms
1 | void vApplicationTickHook() |
注意:vApplicationTickHook() 从ISR内执行,因此必须非常短,且不能被阻塞!
单任务控制
单独创建一个任务,每过10ms就调用一次lv_tick_inc(10)
1 | /* LVGL 时间源 */ |
裸机
使用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 | /* LVGL Handler task,驱动LVGL运行 */ |
- 或者在主循环中调用也可以
1 | while (1) |
移植成功与否测试
显示驱动测试
随便在一个任务或者main函数中进行:
1.LCD初始化
2.LVGL初始化
3.画一个按钮
4.启动LVGL的任务处理
然后下载到MCU中,看看是否显示正常,触摸正常
触摸驱动测试
触摸必须保证配置了LVGL时基,否则不会生效
和显示测试一样,随便在一个任务或者main函数中进行:
1.LCD初始化
2.LVGL初始化
3.画一个按钮
4.启动LVGL的任务处理
点击按钮,按钮启用即证明触摸移植成功
移植遇到的问题
LVGL触摸回调函数不触发
首先检查没有lvgl的时候,触摸驱动是否正常,如果正常,多半是LVGL配置的时基出了问题,检查是否调用lv_tick_inc(1)
。
没有调用,没有通知LVGL时间流逝,即使调用了lv_task_handler()
,也不会执行所有的任务处理