LVGL_Port
LVGL-8.3.0 移植到 STM32F407ZGT6
学习笔记,仅供参考
在成功驱动 TFTLCD 显示屏后,就尝试学习 LVGL GUI 以便更好地制作图形界面,俗话说万事开头难,移植就是学习 LVGL 的第一步,记录我的移植过程以及遇到的错误
LVGL 简介
LVGL - Light and Versatile Graphics Library(轻量级通用型图形库),它是在 MCU/MPU 上创建绚丽 UI 的开源嵌入式图形库。另外可使用官方提供的 SquareLine Studio 拖拽式 UI 编辑器简化开发过程。可通过 LVGL 官方教程 进一步了解,下面是 LVGL Github 代码库,可在右侧找到所发布的版本并进行下载。本次使用的版本是 8.3.0
LVGL 项目目录
在下载解压 LVGL 源码后,可见到如下目录结构
图中被红框选中的就是项目移植要用到的文件
examples 目录提供学习和使用的例程,本文中只使用其 porting 子目录的 .c 和 .h 文件
src 目录就是 LVGL 的核心源码,其中所有的 .c 和 .h 文件均是这次移植要用到的,下图是 src 目录的解释来自 CSDN@Trisuborn
而 lvgl.h 和 lv_conf_template.h 则分别是用来在 main.c 中引用和配置 LVGL
LVGL 移植
项目创建
首先先在合适目录下创建 STM32 项目目录,并在该项目目录下新建 LVGL 目录并将上面 LVGL 源码中红框所标的目录及文件复制到新建 LVGL 目录下。另外将 lv_conf_template.h
改名为 lv_conf.h
Keil 工程添加文件
打开 Keil,点击 options for target
图标,在 C/C++ 设置栏下勾选 C99。这是因为 LVGL 要用 C99 以上的标准来编译,否则会出错
然后在工程目录中添加 LVGL 组,并将 src 目录下 core, draw, font, hal, misc, widgets 文件夹下所有的 .c 文件放在 Src 组中;将 examples/porting 下的 .c 文件放入 Port 组中(由于未使用文件系统,故未放 lv_port_fs_template.c)。另外要注意的是有些目录中仍存在子目录,所有要一层层打开添加,文件比较多保持耐心慢慢添加
同样再将 src/extra/ 下的 layouts, themes, widgets, lv_extra.c 所有文件加到 LVGL/Src 组中
源文件添加到工程后就可以将对应的头文件加入到工程路径中,.\LVGL 就是包含 lvgl.h 和 lv_conf.h,.\LVGL\Src 包含 Src 下的所有头文件,.\LVGL\Port 就是包含 Porting 下的头文件
修改文件内容
先打开工程目录下的 lv_port_disp_template.h
,将开头的 #if 0 改为 #if 1,即开启使用此 port,并且修改 include 引入的头文件
同样打开 lv_port_disp_template.c
,将开头的 0 改为 1
接着打开 lv_conf.h
,开头 0 改为 1,且在 color setting 注释下添加显示屏宽和高的宏定义,可直接复制下面的代码
/* 在移植过程中看到在 LVGL V8 之前要必须手动在此添加屏幕宏定义
而在 V8 之后添加了条件编译,若不定义就要去修改官方所默认的 320x240 的宏 */
#define MY_DISP_HOR_RES 240 // Horizontal 水平宽度
#define MY_DISP_VER_RES 320 // Vertival 竖直高度
再接着修改显示所有用到的必要文件 lv_port_disp_template.c
,只修改两处地方
- 找到 lv_port_disp_init() 函数中 create buffer 部分,官方这里给出三种创建显示缓存 buffer 的策略,为降低难度就是要第一种策略,即将二、三的代码注释掉避免编译时报错。编译时可在 C/C++ 栏的 Misc Control 中添加
--diag_suppress=188,546,68,111
来屏蔽源码所带来的 warning 警告
- 找到 disp_flush() 函数,在 for 循环中添加 lcd 的画点函数,这样就可使用 LVGL 接口实现画图,别忘记在开头添加
lcd.h
到此 LVGL 代码移植部分就结束了,主要是项目文件的添加和修改文件内容
LVGL 功能测试
先在 main.c 中添加 lvgl.h
和 lv_port_disp_template.h
,随后在 lcd 初始化之后添加 lv_init() 和 lv_port_disp_init() 初始化 lvgl 和对应的显示端口,另外 lvgl 还需在定时器中断里添加一个跳动的”心脏”进行刷新,还要在 while(1) 中添加 lv_task_handler() 执行 lvgl 的 timer_handler
下面就是整个 main.c 的代码示例
#include "main.h"
#include "usart.h"
#include "gpio.h"
#include "fsmc.h"
#include "tim.h"
/* Private includes ----------------------------------------------------------*/
#include "bsp_tftlcd.h"
/* Include LVGL header file */
#include "lvgl.h"
#include "lv_port_disp_template.h"
/* function declaration */
void SystemClock_Config(void);
/* Private user code ---------------------------------------------------------*/
/* printf 重定向 */
int fputc(int ch, FILE *f)
{
uint8_t temp[1] = {ch};
HAL_UART_Transmit(&huart1, temp, 1, 2);
return ch;
}
/* LVGL 功能测试函数 */
void lv_ex_label(void)
{
char *github_addr = "https://github.com";
lv_obj_t *label = lv_label_create(lv_scr_act());
lv_label_set_recolor(label, true);
lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(label, 240);
lv_label_set_text_fmt(label, "#ff0000 Github: %s#", github_addr);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 10);
lv_obj_t *label2 = lv_label_create(lv_scr_act());
lv_label_set_recolor(label2, true);
lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR);
lv_obj_set_width(label2, 240);
lv_label_set_text_fmt(label2, "#ff0000 Hello# #00ff00 world!# #0000ff 12345#");
lv_obj_align(label2, LV_ALIGN_CENTER, 0, -10);
}
int main(void)
{
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_FSMC_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim6); // 开启定时器 TIM6
lcd_init(); // 初始化 LCD 显示屏
lv_init(); // 在 lcd 初始化后添加 LVGL 初始化
lv_port_disp_init(); // 显示器初始化
// 若还需输入控制、文件系统则需在下面添加:
// lv_port_indev_init(); // 输入设备初始化
// lv_port_fs_init(); // 文件系统设备初始化
lv_ex_label(); // 运行测试函数
while (1) {
lv_task_handler(); // 运行所有 lvgl 的 timer
HAL_Delay(10);
}
}
/* 定时器中断回调函数,每 1ms 调用一次 lv_tick_inc() */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == htim6.Instance) {
lv_tick_inc(5); // lvgl "心跳"函数
}
}
问题记录
在照着上述步骤移植完 LVGL 后,兴奋地开始编译烧录,但却只显示白屏,在经验证后排除硬件连线问题,那问题就出现在程序上。经过单步调试发现程序在调用 lv_task_handler() 会进入 HardFault 死循环,经搜查发现,HardFault 问题跟内存泄漏有关,于是将问题缩小到程序内存设置上,通过 此篇博客 发现可在启动文件,点击下方Configuration Wizard,可在 Option 的设置框中设置堆栈空间的大小。于是将栈大小设置为 0x0000_1000 成功显示出文字图形
另外,可能是在 CubeMX 生成定时器程序是卡死的原因或配置有问题,导致 TIM6 定时器并未正常工作,即未执行到心跳函数,显示图形未被刷新,于是就把心跳函数放在 while(1) lv_task_handler() 之前,重新烧录后文字会循环滚动显示,最终达到代码所要实现的效果。由此也可认为心跳函数的时间参数或定时器中断时间间隔会影响到图形刷新的效果