1、前言
在之前的一篇文章LVGL在VSCode中安装模拟器,已经对LVGL进行了较为详细的介绍,本文将着重讲解如何移植适配LVGL,让这款图形化GUI库在STM32或其它类型的嵌入式MCU设备上运行起来。
LVGL在VScode中安装模拟器运行配置笔记教程_vscode lvgl-CSDN博客https://blog.csdn.net/weixin_49337111/article/details/136536375?spm=1001.2014.3001.5502
本文所使用的目标硬件设备为STM32F407VET6,LVGL版本为8.3。
提醒:LVGL移植时除了LCD液晶显示触摸屏的引脚初始化和硬件平台有关外,只有一个LVGL的心跳会和硬件平台及对应的库有关。因此本文的移植教程,适用于各类嵌入式MCU平台,标准库及HAL库等。
2、LVGL源码下载及加入工程
2.1 下载LVGL源码
进入LVGL的仓库,选择如图所示的v8.3分支下载。
项目目录预览 - lvgl - GitCodehttps://gitcode.com/lvgl/lvgl/tree/release%2Fv8.3?init=initRepo
2.2 LVGL工程结构完善
①、解压下载的LVGL库压缩包,复制如下图所示被选中的文件,(demos、examples、src、lv_conf_template.h、lvgl.h),其余的其它的文件可以根据需要进行选择是否去除。
将选中的文件复制到项目工程文件目录下的LVGL文件夹下,(没有则手动创建LVGL文件夹)。
②、如下图所示为移植LVGL工程时,所需要的文件。
③、将examples目录下的,porting文件夹复制到LVGL目录下,examples下其它文件可以直接删除。
④、修改文件名
为了标准化,规范化,将如下图所示,将porting文件夹中的所有文件的文件名去除 template,并在其对应的.c或.h文件中,将include包含了这些文件的代码进行修改。
将如下图所示的lv_conf_template.h的文件名修改为lv_conf.h,并将各文件中,include包含的文件名进行修改。
3、LVGL移植到MCU工程中
3.1、在工程中新建文件夹
LVGL_SRC //存放LVGL的源码文件
LVGL_PORTING //存放LVGL的移植文件
LVGL_APP //存放LVGL的用户APP文件
LVGL_DEMO //存放LVGL的Demo文件
3.2、将LVGL库文件加入到工程
添加LVGL源文件,将LVGL/src目录下的core、draw、font、hal、misc、widgets、extra文件夹下的所有文件全部添加进LVGL_SRC组中,如果文件夹内还有子文件夹,则需依次打开,将全部的.c源文件加入到工程中。
将LVGL/porting目录下的全部源文件加入到LVGL_PORTING组中
添加LVGL头文件
3.3、勾选C99模式和取消微库
3.4、编译整个工程
将全部的LVGL源文件和头文件正常加入到工程中,编译整个工程时,应该是没有错误的。如果存在报错,请检查未加入LVGL库时,工程编译是否存在错误。是否存在文件及头文件路径未加入工程。
4、LVGL显示屏移植适配
在完成上述操作后,就可以开始对显示屏进行移植适配操作。
4.1、修改lv_port_disp.c文件
打开lv_port_disp.c文件,并开启如图所示的宏定义
注释掉lv_port_disp.c文件中的这一部分内容,后面有需要再添加、修改
//注释掉lv_port_disp.c文件中的这一部分内容,后面有需要再添加、修改/* Example for 2) */static lv_disp_draw_buf_t draw_buf_dsc_2;static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*//* Example for 3) also set disp_drv.full_refresh = 1 below*/static lv_disp_draw_buf_t draw_buf_dsc_3;static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2,MY_DISP_VER_RES * LV_VER_RES_MAX); /*Initialize the display buffer*/
找到lv_pory_disp.c文件中的disp_flush 函数,注释掉如下图中框选中的内容,添加自己屏幕对应的像素填充函数,或者根据屏幕实际情况进行修改
//在指定区域内填充指定颜色块
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)
//color:要填充的颜色
void LCD_Color_Fill(u16 sx,u16 sy,u16 ex,u16 ey,u16 *color)
{ u16 height,width;u16 i,j;width=ex-sx+1; //得到填充的宽度height=ey-sy+1; //高度for(i=0;i<height;i++){setCursor(sx,sy+i); //设置光标位置 LCD->LCD_REG = 0x2C; //开始写入GRAMfor(j=0;j<width;j++){ LCD->LCD_RAM=color[i*width+j]; //写入数据 }}
}
4.2、修改lv_port_disp.h文件
打开lv_port_disp.h文件,并开启如下图所示的宏定义
#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
将图中的红框内的代码全部删除,换成
#include "lvgl.h"'
4.3、修改lv_conf.h文件
打开lv_conf.h文件,开启宏定义并加入屏幕分辨率信息
//设置屏幕的分辨率---需根据屏幕的实际情况进行修改
//注意、特别提醒:屏幕有横屏显示和竖屏显示之分,请仔细检查屏幕到底是横屏显示,还是竖屏显示,否则后面可能会出现显示界面不完整,或者显示有问题。
//如果遇到显示不全、有问题,可以尝试,将分辨率数据调换一下
#define MY_DISP_HOR_RES 240
#define MY_DISP_VER_RES 320
4.4、添加LVGL心跳
创建一个单片机1ms定时器,并在定时器的中断处理函数中加入LVGL心跳。创建和单片机型号和对应的库有关,请根据实际的目标平台进行修改处理,下面以STM32F4的标准库为例:
①、timer.c
#include "Timer.h"/*** @brief 定时器2配置(1ms触发一次定时器中断)* @param NONE* @retval NONE*/
void TIM2_Init(void)
{ /** 使能相关时钟 **/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 使能定时器时钟 /** 配置TIM2中断优先级 **/NVIC_InitTypeDef NVIC_InitStructure; // 声明优先级配置结构体NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 中断通道来源 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; // 子优先级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 使能通道NVIC_Init(&NVIC_InitStructure); // 配置写入寄存器/** 配置所用TIM的时基 **/TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; // 声明时基配置结构体TIM_TimeBaseStructure.TIM_Prescaler = 84-1; // 分频; 把接口时钟分频后给计数器使用, 即多少个接口脉冲,才产生一次计数器脉冲; 简单理解:计算每一计数器脉冲的时长;TIM_TimeBaseStructure.TIM_Period = 1000-1; // ARR, 自动重载值; 多少个计数器脉冲作为一周期; 注意:TIM2和5是32位的,其它TIM是16位的;TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; // 采样时钟分频TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; // 计数方式 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 初始化定时器,把配置写入寄存器//t = (arr+1) * (psc+1) / Freq//t = (84-1+1) * (1000-1+1) / 84 000 000//t = 0.001
s/** 配置中断 **/TIM_ClearFlag(TIM2, TIM_FLAG_Update); // 清除定时器更新中断标志位TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); // 开启定时器更新中断/** 使能定时器 **/TIM_Cmd(TIM2, ENABLE); // 使能定时器
}// 中断服务函数
void TIM2_IRQHandler (void)
{if ( TIM_GetITStatus( TIM2, TIM_IT_Update) != RESET ) { lv_tick_inc(1); //定时器中加入LVGL心跳TIM_ClearITPendingBit(TIM2 , TIM_IT_Update); }
}
②、timer.h
#ifndef __TIMER_H
#define __TIMER_H#include "stm32f4xx.h"#include "lvgl.h"void TIM2_Init(void);
void TIM2_IRQHandler (void);#endif
4.5、测试LVGL的显示功能
如下所示为测试LVGL移植功能的main.c,根据实际情况进行修改
#include "stm32f4xx.h"
#include "led.h"
#include "USART.h"
#include "Timer.h"
#include "LCD_ILI9341.h"
#include "XPT2046.h"#include "lvgl.h"
#include "lv_port_disp.h"/*** LVGL专业点灯Demo*/
void lv_example_led_1(void)
{/*Create a LED and switch it OFF*/lv_obj_t * led1 = lv_led_create(lv_scr_act());lv_obj_align(led1, LV_ALIGN_CENTER, -80, 0);lv_led_off(led1);/*Copy the previous LED and set a brightness*/lv_obj_t * led2 = lv_led_create(lv_scr_act());lv_obj_align(led2, LV_ALIGN_CENTER, 0, 0);lv_led_set_brightness(led2, 150);lv_led_set_color(led2, lv_palette_main(LV_PALETTE_RED));/*Copy the previous LED and switch it ON*/lv_obj_t * led3 = lv_led_create(lv_scr_act());lv_obj_align(led3, LV_ALIGN_CENTER, 80, 0);lv_led_on(led3);
}int main(void)
{ //其它外设初始化代码 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置系统中断优先级分组2USART1_Init(115200); // USART1 初始化,用于与串口软件通信; TX-PA9、RX-PA10, 115200-8-N-1,中断发送、接收TIM2_Init(); Led_Init(); // LED 初始化LED_RED_ON; LED_BLUE_ON;W25Q128_Init (); // 外部Flash 初始化,其内部存储有汉字字模数据//显示屏、触摸屏初始化代码 LCD_Init(); // LCD 初始化;XPT2046_Init(xLCD.width , xLCD.height, xLCD.dir); // 触摸 初始化XPT2046_Cmd(ENABLE); // 触摸使能:打开//LVGL初始化lv_init();lv_port_disp_init();//LVGL测试Demo lv_example_led_1();while (1) // while函数死循环,不能让main函数运行结束,否则会产生硬件错误{ lv_task_handler(); //LVGL事物处理,必须加到循环中}
}
LVGL移植正常,显示效果。
4.6、屏幕异常显示说明
程序编译下载到开发板后,如果出现有数据显示,但显示不正常,需要着重检查以下2个方面:
①、检查屏幕的显示方向,是横屏显示还是竖屏显示,可以和店家或厂家沟通咨询;
②、LVGL调用的屏幕数据刷新部分是否正常,即disp_flush函数的内容。
博主此前就是没有弄清楚显示屏的横屏、竖屏,导致程序烧录后,出现如下所示的显示不全。
如果可以查看到显示屏驱动源码,并且有设置屏幕显示方向的接口,那么也可以直接修改屏幕的显示方向。
注意:修改了屏幕的显示方向时,如果有用到触摸屏,也要一同修改触摸屏的方向!!!
5、LVGL触摸屏移植适配
5.1、修改lv_port_indev.h文件
打开lv_port_indev.h文件,开启该文件的宏定义,并包含触摸屏相关的头文件
5.2、修改lv_port_indev.c文件
①、打开lv_port_indev.c文件
如下图所示,开启该文件的宏定义,并修改包含的头文件为最新的文件名
②、修改lv_port_indev_init函数
因为本次移植,仅有一个触摸屏作为输入设备,因此直接屏蔽掉了Mouse、Keypad、Encoder和Button这些输入设备的配置代码。
③、修改LVGL触摸屏触摸和获取触摸坐标函数
打开LCD触摸屏板级驱动文件,添加触摸标志位,并查找获取LCD触摸屏坐标的方式。不同的触摸屏的驱动程序编写方式不同,可以咨询厂家或板级驱动文件开发者,如何获取触摸屏的坐标位置信息等。
通过查看触摸屏的驱动程序确定了触摸屏获取触摸坐标的接口
打开LVGL的输入设备文件lv_port_indev.c,根据实际情况修改touchpad_is_pressed()和touchpad_get_xy()函数。
5.3、修改定时器中断处理函数
打开上面移植LCD触摸屏时的定时器处理函数,在定时器的中断处理函数中,添加触摸屏的触摸扫描检测函数。
这个部分的操作,根据实际情况进行处理,有的触摸屏有单独的中断函数进行触控扫描操作
5.4、LVGL触摸测试
①、添加LVGL触摸Demo到工程
完成如下所示的操作后,还需要将该头文件包含到路径中
②、修改lv_cong.h
打开lv_conf.h文件,找到LV_USE_DEMO_KEYPAD_AND_ENCODER这个宏定义,并将其值改为1,以开启该宏定义
③、触摸测试
包含相应的触摸屏头文件及在代码中添加触摸屏初始化及demo调用的函数
#include "stm32f4xx.h"
#include "led.h"
#include "USART.h"
#include "Timer.h"
#include "LCD_ILI9341.h"
#include "XPT2046.h"#include "lvgl.h"
#include "lv_conf.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"#include "lv_demo_keypad_encoder.h"/*** LVGL专业点灯Demo*/
void lv_example_led_1(void)
{/*Create a LED and switch it OFF*/lv_obj_t * led1 = lv_led_create(lv_scr_act());lv_obj_align(led1, LV_ALIGN_CENTER, -80, 0);lv_led_off(led1);/*Copy the previous LED and set a brightness*/lv_obj_t * led2 = lv_led_create(lv_scr_act());lv_obj_align(led2, LV_ALIGN_CENTER, 0, 0);lv_led_set_brightness(led2, 150);lv_led_set_color(led2, lv_palette_main(LV_PALETTE_RED));/*Copy the previous LED and switch it ON*/lv_obj_t * led3 = lv_led_create(lv_scr_act());lv_obj_align(led3, LV_ALIGN_CENTER, 80, 0);lv_led_on(led3);
}int main(void)
{ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置系统中断优先级分组2USART1_Init(115200); // USART1 初始化,用于与串口软件通信; TX-PA9、RX-PA10, 115200-8-N-1,中断发送、接收TIM2_Init(); Led_Init(); // LED 初始化LED_RED_ON; LED_BLUE_ON;W25Q128_Init (); // 外部Flash 初始化,其内部存储有汉字字模数据LCD_Init(); // LCD 初始化;XPT2046_Init(xLCD.width , xLCD.height, xLCD.dir); // 触摸 初始化XPT2046_Cmd(ENABLE); // 触摸使能:打开LCD_Fill (0, 0, 240, 320, BLACK); // 整个背景填充白色LCD_String(15, 8, " LVGL移植测试 ", 24, BLACK, WHITE); // 本函数,可显示中英文字符串; 函数内自动选择font.h中的ASCII字模、Flash中的汉字字模; Flash中已存在放较为完成的汉字字模数据,四字字号,12号、16号、24号、32号,约6M大小lv_init();lv_port_disp_init();lv_port_indev_init();//lv_example_led_1();lv_demo_keypad_encoder();while (1) // while函数死循环,不能让main函数运行结束,否则会产生硬件错误{ lv_task_handler(); //LVGL事物处理}
}
最后提醒:如果移植过程中,出现内存大小报错,需要检查使用的MDK工程是否已经激活了;
如果烧录下载后,程序运行不起来,可以尝试修改启动文件中的,堆空间、栈空间的大小,检查芯片是否满足官方的移植性能要求。
本次移植Demo的程序源码已上传至资源下载区:【免费】LVGL移植到STM32MCU平台通用程序源码资源-CSDN文库https://download.csdn.net/download/weixin_49337111/89346775?spm=1001.2014.3001.5503