前言
本文基于 STM32F407ZET6 芯片及 2.8 寸 LCD 屏带触摸进行 LVGL 8.3 版本移植,从零开始详细介绍移植过程包括硬件接线。还详细解释了移植时修改的参数为什么要这么改。
如果你的硬件选型和我一样,只要跟着本文流程走最后一定会移植成功。如果不是问题也不大,只要满足 lvgl 最小资源要求的都可以,移植的步骤也是一样的。
设备清单
STM32F407ZET6 开发板
ZET6 和 ZGT6 的差别只是 Flash 的大小,这两个其他东西都一样无论用哪个都可以。只不过我买的板子上带了 TFT 的液晶屏接口方便我接线。
2.8 寸 TFT LCD 屏带触摸
2.8 寸 TFT-LCD 屏原理与应用
1️⃣基本参数
(此处省略图片)
2️⃣引脚说明
(此处省略图片)
3️⃣程序移植
参考 LCD Wiki 文档获取驱动代码:http://www.lcdwiki.com/zh/2.8inch_16BIT_Module_ILI9341_SKU:MRB2801#top
打开商家给的资料网站,找到程序并下载。
先下载该程序下来然后修改代码,确保该屏幕可用于我们买的 STM32F407ZET6 最小开发板,但是由上面的产品介绍我们可以知道该屏幕是兼容正点原子的探索者开发板的,那应该问题不大,最多就是管脚不太一样。
下载好的压缩包解压后如图所示,一路点击找到对应代码。
打开后 main 函数代码如下所示。
上面清楚注释说明用到什么管脚,此时对照我们开发板的原理图进行对比接线,在进行此步前我们发现我的是 ZET6,而正点原子的是 ZGT6,为了避免潜在错误,我们最好改一下 Device,其实 ZET6 和 ZGT6 的区别就是 ZGT6 的 Flash 更大,不改理论上也没事。
修改步骤如下:
(此处省略图片)
4️⃣硬件接线
此时我们要查看如何接线到开发板上最好就是看 main 函数可以找到一个 LCD_Init() 函数,右键跳转到定义,发现还有一个 LCD_GPIOInit() 这个就是管脚定义的地方,通过阅读代码可以分析出来具体每个引脚是接到了哪里,但是这个移植程序 main 函数已经标注清楚了我们要用的什么引脚直接找到原理图对着接线即可。
而我们买的板子上也有对应的扩展 TFT 插槽,如下图,对应进行接线即可。
(此处省略图片)
下面为引脚接线汇总表格,巧的是这个插槽名称和正点原子的插槽名称一模一样。
| stm32 开发板 TFT 插槽丝印名 | LCD 引脚 |
|---|---|
| D00 (PD14) | DB0 |
| D01 (PD15) | DB1 |
| D02 (PD0) | DB2 |
| D03 (PD3) | DB3 |
| D04 (PE7) | DB4 |
| D05 (PE8) | DB5 |
| D06 (PE9) | DB6 |
| D07 (PE10) | DB7 |
| D08 (PE11) | DB8 |
| D09 (PE12) | DB9 |
| D10 (PE13) | DB10 |
| D11 (PE14) | DB11 |
| D12 (PE15) | DB12 |
| D13 (PD8) | DB13 |
| D14 (PD9) | DB14 |
| D15 (PD10) | DB15 |
液晶屏控制线
| stm32 开发板 TFT 插槽丝印名 | LCD 引脚 |
|---|---|
| WE (PD5) | WR |
| RD (PD4) | RD |
| RST (复位引脚) | RST |
| RS (PF12) | RS |
| CS (PG12) | CS |
| 3v3 | BL |
在我这个开发板中,背光是由一个 PNP 晶体管 Q2 控制的:
(此处省略图片)
- PB15 控制 Q2 导通,Q2 输出才给 LCD 背光部分送电
- 如果 PB15 没配置,或者是高电平,Q2 不导通 → 背光不亮,屏幕黑
因此我为了省事直接将 LCD 上的 BL 的线接到随便一个 3v3 即可,默认背光。
触摸屏控制线,没有触摸功能可以不接
| stm32 开发板 TFT 插槽丝印名 | LCD 引脚 |
|---|---|
| TIRQ (PB1) | PEN |
| TSO (PB2) | MISO |
| TSI (PF11) | MOSI |
| TCS (PC13) | T_CS |
| TSCK (PB0) | CLK |
电源接线
| stm32 开发板 TFT 插槽丝印名 | LCD 引脚 |
|---|---|
| GND | LCD 上有两个 GND,开发板上的插槽有 3 三个 GND,将 LCD 上的两个 GND 接到 开发板上的任意两个 GND 即可 |
| VDD | LCD 上有两个 VDD,将其中 一个接到开发板上的 3v3 即可,两个一起接也没事,因为都是并联的,接其中一个即可 |
LVGL8.3 移植流程
1️⃣硬件及平台要求
(此处省略图片)
2️⃣版本说明
在当前主流版本中,LVGL 已发展到 V9,但在将其移植到资源受限的 STM32F407ZET6 上时,更推荐使用 LVGL 8.3。原因如下:
① 资源适配性优先考虑
- STM32F407 系列 MCU 拥有 2MB Flash 和 192KB RAM,在嵌入式系统中算是中高端配置,但相比更高级别的处理器依然资源有限。LVGL 8 相较于 LVGL 9 占用资源更少,对 RAM 和 Flash 的要求更低,尤其适合资源敏感型设备。
对于像 F407 这样 RAM 空间接近边缘的平台,LVGL 8 的轻量级特性可以显著降低内存压力,避免系统运行时频繁溢出或卡顿。
② 成熟稳定,社区支持良好
- 作为一个经过时间验证的版本,LVGL 8 已广泛应用于实际项目,生态成熟、社区活跃、文档全面。相比刚发布不久的 LVGL 9,LVGL 8 更适合希望快速搭建 UI 原型或需要高稳定性的工业项目。
选择一个成熟版本,可以有效规避移植中可能出现的兼容性问题和 bug,提升开发效率与项目成功率。
3️⃣源码下载
进入 github 源码页面后,选择要下载的源码版本,目前稳定版为 8.3.11,选中 release/v8.3 即可。
选中源码版本为 8.3 后,点击右侧的【< >code】按钮执行源码下载,选择下载 ZIP 格式即可。
4️⃣源码移植
1> 打开移植好的 2.8 英寸电阻屏的工程,在该工程目录下创建 LVGL 文件夹,并将 LVGL 所有源码解压到该文件夹。
2> 将带有_template 后缀的 c 与 h 文件,删除 _template 文字,如 lv_conf_template.h 的名字修改为 lv_conf.h,详细如下:
| 路径 | 文件名 | 修改后的文件名 | 作用说明 |
|---|---|---|---|
| LVGL | lv_conf_template.h | lv_conf.h | LVGL 的全局配置模板,控制颜色深度、启用组件、缓冲大小等,需复制为 lv_conf.h 后由用户自行配置。 |
| LVGL\examples\porting\ | lv_port_disp_template.c | lv_port_disp.c | 显示驱动移植模板,实现如 flush_cb 等函数,将 LVGL 的绘图缓冲内容输出到 LCD 屏幕。 |
| lv_port_disp_template.h | lv_port_disp.h | 配套头文件,声明显示接口函数,供主程序或其他模块调用。 | |
| lv_port_fs_template.c | lv_port_fs.c | 文件系统适配模板,封装如 FatFS 接口,用于从 SD 卡或 Flash 加载字体、图片等资源。 | |
| lv_port_fs_template.h | lv_port_fs.h | 配套头文件,声明文件系统接口函数。 | |
| lv_port_indev_template.c | lv_port_indev.c | 输入设备移植模板,用于对接电阻屏、电容屏、按键、编码器等,将输入事件传递给 LVGL。 | |
| lv_port_indev_template.h | lv_port_indev.h | 配套头文件,声明输入设备初始化和读取函数。 |
这些 *_template 文件是官方提供的移植/配置参考模板。要在项目中生效,必须复制出来并改名,以便参与编译并能让 LVGL 找到你定制的配置与驱动。
3> 添加分组
- 进入管理项目项界面,选中【Project Items】标签页,创建 LVGL_CORE、LVGL_DRAW….LVGL_DEMOKEYPAD 等多个 Group(组),并为每个组添加对应的 c 文件,详细如下图:
添加 lvgl/src/core 下所有 .c 文件
添加 lvgl/src/draw、lvgl/src/draw/子文件夹 的所有.c 文件
添加 lvgl/src/extra、lvgl/src/extra/子文件夹 的所有.c 文件
添加 lvgl/src/font 的所有.c 文件
添加 lvgl/src/hal 的所有.c 文件
添加 lvgl/src/misc 的所有.c 文件
添加 lvgl/src/widgets 的所有.c 文件
添加 lvgl/src/porting 的所有.c 文件
添加 lvgl/ 的所有.h 文件
添加 lvgl/demos/stress 的所有.c 文件
添加 lvgl/demos/widgets、lvgl/demos/widgets/子文件夹 的所有.c 文件
添加 lvgl/demos/keypad_encoder 的所有.c 文件
综合汇总表格
| Groups | 文件路径 | 分组说明 |
|---|---|---|
| LVGL_CORE | lvgl/src/core | 核心模块:包含 LVGL 的核心功能,如对象系统、事件处理等。 |
| LVGL_DRAW | lvgl/src/draw lvgl/src/draw/子文件夹 | 绘图模块:绘制图形、图像、颜色混合等相关实现。 |
| LVGL_EXTRA | lvgl/src/extra lvgl/src/extra/子文件夹 | 扩展组件:包含额外功能,如图表、加载动画、滑块等 UI 控件扩展。 |
| LVGL_FONT | lvgl/src/font | 字体模块:内置字体处理与注册支持。 |
| LVGL_HAL | lvgl/src/hal | 硬件抽象层:与显示、输入等硬件接口交互的封装。 |
| LVGL_MISC | lvgl/src/misc | 杂项工具:如日志、内存管理、算法等辅助功能模块。 |
| LVGL_WIDGETS | lvgl/src/widgets | 基本控件:按钮、标签、滑块、列表等常规 UI 元素实现。 |
| LVGL_PORTING | lvgl/examples/porting | 用户移植代码:显示、输入、文件系统等用户移植的接口代码。 |
| LVGL_CONFIG | lvgl/lvgl.h lvgl/lvgl_conf.h | 配置接口:LVGL 主入口头文件及用户配置文件。 |
| LVGL_DEMO_BENCHMARK | lvgl/demos/benchmark lvgl/demos/benchmark/子文件夹 | 性能测试 Demo:用于测试绘图、控件刷新效率。 |
| LVGL_DEMO_STRESS | lvgl/demos/stress | 稳定性测试 Demo:用于运行各种控件与动画的压力测试。 |
| LVGL_DEMO_WIDGETS | lvgl/demos/widgets lvgl/demos/widgets/子文件夹 | 控件演示:展示各种 UI 控件的外观、交互和组合使用方式。 |
| LVGL_DEMO_KEYPAD | lvgl/demos/keypad_encoder | 输入演示:模拟键盘/编码器输入设备的操作与交互逻辑。 |
所有步骤完成后项目结构如下图所示。
工程配置
步骤 一:修改启动文件中的堆栈大小(Stack_Size 至少设为 0x1000*(4KB)**)
- 在 STM32 的启动汇编文件
startup_stm32f40_41xxx.s中,Stack_Size决定了程序运行时主栈的大小。LVGL 是一个图形库,使用了大量的局部变量、栈内存分配(如控件结构体、绘图缓冲处理等),栈空间不足将导致程序运行异常,如 HardFault、死机、显示异常。
步骤 二:在宏定义中添加 LV_LVGL_H_INCLUDE_SIMPLE 并启用 C99 模式
- 添加此宏后,LVGL 的头文件如
#include "lvgl.h"会直接从你当前工程路径或 include 路径中寻找; - 否则 LVGL 会尝试去
#include "lvgl/lvgl.h",这在移植过程中可能因路径不一致导致头文件找不到; - LVGL 源码的编译需要 C99 模式的支持,不然会出现大量的报错,所以大家需要启用 C99 模式。
包含头文件路径,点击'魔术棒',然后选择 C/C++,在 Include Paths 添加新的头文件路径,详细如下:
修改配置文件
准备工作已经完成,接下来要修改一些参数使 LVGL 能适配现在的 407 芯片及屏幕。
1️⃣lvgl_config.h
-
启用
lvgl/lv_conf.h这个头文件,因为默认该文件是不参与编译,必须将#if 0修改为#if 1。 -
根据当前显示设备支持的颜色深度设置宏 LV_COLOR_DEPTH,对于 SPI TFT 屏且每 8 位的传输,确定是否要交换 16 位色的高低字节,当前显示设备是高字节优先传输,若使能了 DMA 传输,要设置宏 LV_COLOR_16_SWAP 为 1。
注意:由 2.8 寸屏的产品参数可知色深为 16 位,即 RGB565
- 需要使能 LVGL 配置文件中的宏定义 LV_USE_DEMO_WIDGETS,这样可以使用 LVGL 提供的 demo 验证显示效果。
2️⃣适配屏幕驱动
-
启用
LVGL/examples/porting/lv_port_disp.c,默认该文件是不参与编译,必须将#if 0修改为#if 1,另外,记得'lv_port_disp_template.h'要修改为'lv_port_disp.h'。 -
当前文件会涉及到显示设备的初始化、颜色填充等函数,需要引入相关支撑 tft 函数的头文件,并补充增加"lvgl.h"头文件。
注意:这个 tft.h 是我自己把这个显示屏的驱动代码和触摸代码封装到同一个头文件里面了,这里要替换成自己显示屏的默认驱动文件,比如我用的这个 2.8 寸屏幕,上文不是已经移植了商家给的兼容正点原子的代码嘛,里面就自带了一个 lcd.h,这个文件就是 2.8 寸屏幕的驱动,因此我们将这个 tft.h 替换成 lcd.h,然后在下面写上对应屏幕初始化函数即可,下文会提及。
-
启用
LVGL/examples/porting/lv_port_disp.h,默认该文件是不参与编译,必须将#if 0修改为#if 1。 -
修改分辨率,根据用到的屏幕分辨率修改宏 MY_DISP_HOR_RES(屏幕宽度)与 MY_DISP_VER_RES(屏幕高度)。
-
修改
lv_port_disp_init函数,lv_port_disp_init函数是 LVGL 在特定平台或模拟器上初始化显示驱动的接口函数。在实际嵌入式项目中,需要根据硬件平台编写或修改这个函数来适配显示控制器。
此函数通常包含以下内容:初始化硬件:配置和打开与显示屏连接的硬件接口(disp_init)。创建显示器描述符:定义一个 lv_disp_drv_t 结构体实例,该结构体包含了 LVGL 需要的所有关于显示控制器的信息,例如:一个回调函数用于写像素数据到屏幕(disp_flush)。注册显示器驱动:调用 lv_disp_drv_register 函数,将创建好的显示器驱动结构体注册给 LVGL 核心库,使其能够通过该驱动与实际硬件进行通信。
右键跳转到定义。
查找 main 函数,将以下代码剪切复制到 disp_init() 即可,你们的代码不一定和我一样因为每个人用的屏幕和商家给的代码函数名封装不一样,视具体情况来即可。
- 开启 LVGL 缓冲区
LVGL 有三种缓冲配置
单缓冲/行缓冲(Single Buffer)(移植时使用该缓冲配置)
- LVGL 将在此处绘制显示器的内容,并将其写入显示器。
- 较省内存,但 CPU 和 LCD 显存'争用'这块内存
- 如果刷新不及时,可能会卡顿
双缓冲(Double Buffer / Flushing)(有 DMA 外设则使用该缓冲配置,能大幅提高显示帧率)
- 两块缓冲轮流用,一块画、一块刷,无闪烁,更平滑,内存开销是单缓冲的 2 倍
- LVGL 将绘制显示的内容到缓冲区并将其写入显示,并使用 DMA 将缓冲区的内容写入显示器,它将使 LVGL 绘制屏幕的下一部分到另一个缓冲区同时数据从第一个缓冲区发送。它使渲染和并行刷新。
全帧缓冲(Full Frame Buffer)(有 DMA 且大内存则推荐使用该缓冲配置,进一步提高显示帧率)
- 把整个屏幕一帧都缓存在内存中
- 设置 2 个屏幕大小的缓冲区,并设置 disp_drv.full_refresh=1。这样,LVGL 将始终在 flush_cb 中提供整个渲染屏幕,并且只需要更改帧缓冲区的地址。
- 优点:一次刷新整屏,最大兼容性(动画、透明、旋转等最好)
- 缺点:占用大约 153.6KB(320x240x2 字节)内存
STM32F407 的 SRAM 要用 CCM、SRAM1+2 才能凑够这么多
| MCU 资源 | 建议缓冲方式 |
|---|---|
| STM32F1/F0 类低内存(< 64KB) | 单缓冲,局部刷(8 行~20 行) |
| STM32F4/F7 有 192KB+ 内存 | 双缓冲(推荐)或小全帧缓冲 |
| STM32H7 + SDRAM | 全帧缓冲最佳(800x480 等高分屏) |
概念介绍完毕,接下来我们看到 lv_port_disp_init(void) 里面官方提供了三种方式的示例,我们把二和三示例注释掉即可。
- 编写
disp_flush函数,将内部缓冲区的内容刷新到显示器上的特定区域,并被 disp_drv.flush_cb 进行回调。
disp_flush 的作用
将 LVGL 内部渲染缓冲区的数据,通过你自己的显示驱动(LCD 驱动),刷新到真实屏幕的指定区域上。
LVGL 是一个图形库,它自己不会直接操作你的屏幕,而是:把界面内容'画'到一个内存缓冲区里(叫 draw buffer);然后回调你注册的 flush_cb 函数,也就是 disp_flush();把这部分缓冲数据由你来刷到屏幕上去(你写的 LCD 显示函数来做);最后你要调用 lv_disp_flush_ready() 告诉 LVGL:'我刷完了'。
而这个 LCD 刷屏函数在商家给的移植代码中已经帮我们实现了,直接调用就好。
3️⃣配置输入设备 (触摸功能)
- 启用
examples/porting/lv_port_indev.c,默认该文件是不参与编译,必须将#if 0修改为#if 1,另外,必须把'lv_port_indev_template.h'要修改为'lv_port_indev.h'。
另外额外增加以下头文件,支持触摸检测。
#include "touch.h"
#include "tft.h"
-
启用
examples/porting/lv_port_indev.h,默认该文件是不参与编译,必须将#if 0修改为#if 1,且把该头文件中的#include "lvgl/lvgl.h"改成#include "lvgl.h",因为在前面几步中已经配置好了 LVGL 的宏定义。 -
在
lv_port_indev_init函数中屏蔽鼠标、键盘、编码器等相关代码,只保留 Touchpad(触摸板)设备。 -
修改
touchpad_init函数,该函数会被lv_port_indev_init调用,并需增加初始化触摸板设备的代码。 -
修改
touchpad_is_pressed函数,该函数会被touchpad_read调用,并需增加触摸板设备按压检测的代码。 -
修改
touchpad_get_xy函数,该函数会被touchpad_read调用,并需增加触摸板设备获取 x、y 坐标值的代码。
提供心跳
lv_tick_inc() 是 LVGL 中的一个函数,主要用于模拟系统时钟的滴答(tick)更新。在 LVGL 中,系统时钟被用于动画、延时处理等定时任务。
- LVGL 所有动画、过渡效果、事件延迟、定时器等功能,都依赖内部 tick
- LVGL 不会自己生成时间,你必须提供
- 该函数的主要作用是将 LVGL 内部维护的系统 tick 计数器加 1,表示系统时间已经向前推进了一个单位的时间间隔(通常是毫秒级别)。在实际使用中,用户需要在合适的时机(例如硬件定时器中断服务程序中如 TIM3_IRQHandler)调用此函数,以便 LVGL 能够正确地进行定时相关的操作,参考代码如下:
初始化 TIM3 使其每 1ms 进入一次中断:
void TIM3_Int_Init(uint16_t arr, uint16_t psc) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr; // 自动重装值
TIM_TimeBaseStructure.TIM_Prescaler = psc; // 预分频系数
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 高优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM3, ENABLE);
}
配置参数让它每 1ms 中断一次
// 放到 main 函数中
TIM3_Int_Init(999, 83); // 84MHz / (83+1) = 1MHz,每 1000 个数 1ms
TIM3 中断处理函数
void TIM3_IRQHandler(void) {
/* 检测时间更新中断的标志位是否置位 */
if (TIM_GetITStatus(TIM3, TIM_IT_Update) == SET) {
lv_tick_inc(1); // 告诉 LVGL:时间过了 1ms
/* 清空标志位 */
TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
}
}
不写这个,LVGL 动画不会动,界面事件反应异常。
任务处理
要处理 LVGL 的任务,我们需要定期通过以下方式之一调用 lv_task_handler()。
lv_task_handler() 是调度器和刷新器
- LVGL 不像 RTOS 那样自动运行任务,它需要你周期性手动调用
- 你每 5~10ms 调用一次,它就处理定时任务、界面刷新等
有如下几种方案:
(1)最简单的是在 main 函数的 while(1) 死循环里面定时调用。
while(1) {
lv_task_handler();
delay_ms(5);
}
(2)定期定时中断 (低优先级然后是 lv_tick_inc()) 中调用
lv_tick_inc()是时间推进核心,不能被中断,否则时间计算会出错;lv_task_handler()是刷新逻辑,可以稍微延后一点;- 实现代码如下:
初始化 Tim2 定时器
void TIM2_Int_Init(uint16_t arr, uint16_t psc) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 比 TIM3 低
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
配置定时器 10ms 中断一次
// 放到 main 函数中
TIM2_Int_Init(9999, 83); // 84MHz / (83+1) = 1MHz,每 10000 个数 10ms
TIM2 中断服务函数
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
lv_task_handler(); // 刷新 LVGL 任务
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
(3)你还可以在 RTOS 任务中定时调用。(上了 LVGL 都推荐使用 RTOS)
void lvgl_task(void* pvParameters) {
while(1) {
lv_task_handler(); // 处理 LVGL 任务
vTaskDelay(pdMS_TO_TICKS(5)); // 任务延时 5ms(不占用 CPU)
}
}
int main() {
// 先在 main 函数中创建任务 xTaskCreate(lvgl_task,"LVGL",512,NULL,2,NULL);
// 启动任务调度器
vTaskStartScheduler();
}
基于我们是移植阶段,使用最简单的第一种方式在 main 函数里面使用即可,确保能把程序跑起来先。
工程演示
-
在工程中创建
main.h头文件,在该头文件中包含以下 lvgl 相关头文件。 -
在 main 函数中,调用
lv_init、lv_port_disp_init、lv_port_indev_init、lv_demo_widgets、tim3_init等相关初始化函数,在 while(1) 不可退出循环中添加lv_task_handler函数,该函数为图形库中的一个核心函数,它负责处理所有 LVGL 的任务和事件。
#include "main.h"
// 主函数
int main(void) {
// 初始化 lvgllv_init();
// 初始化 lvgl 显示设备
lv_port_disp_init();
// 初始化 lvgl 输入设备
lv_port_indev_init();
// 初始化 lvgl demo
lv_demo_widgets();
// tim3 初始化,定时周期为 1ms
TIM3_Int_Init(999, 83);
while(1) {
lv_task_handler();
}
return 0;
}
- 将编译好的程序下载到开发板,可以看到开发板的 LCD 屏上成功显示 LVGL 提供的 demo。
至此 LVGL 移植已经结束了,但我们在移植过程中把所有控件都移植进去了,实际上我们是用不到这么多控件的,加上单片机本来资源就受限,因此我们还得进行裁剪,将没用到的控件、字体等删除,节省空间。后面会单独出一篇文章介绍一下。


