STM32第二十二课:LVGL的移植和屏幕创建(7.11版本)


需求

1.将LVGL7.11移植到项目工程中。
2.创建一个LVGL任务,在任务中创建两个屏幕,开始运行时加载第一个屏幕。
3.设计一个按键控制,通过按压不用的按键来切换不同的屏幕。


一、LVGL概要

  LVGL就是一款免费开源的嵌入式图形库,可为任何MCU、MPU和显示器类型设计好看的用户界面。其实本质上就是一个图形库。开发者通过代码编写调用LVGL库来创建GUI界面。它包含一个HAL(硬件抽象层)接口,用于注册显示和输入设备驱动程序
  驱动程序除特定的驱动程序外,它还有其他的功能,可驱动显示器到GPU(可选)、读取触摸板或按钮的输入。

Lvgl官网:官网地址
Lvgl官方文档:官方文档
Lvgl源码网址:源码地址

个人理解
  LVGL屏幕交互设计就是制作PPT,每个屏幕的创建就是每页PPT的创建。在PPT中你可以制作多个PPT页面,但显示的屏幕只有一个,所以我们通过鼠标进行页面切换。在LVGL中也一样,你可以创建多个屏幕,但给客户显示的屏幕只有一个,所以可以通过代码逻辑来进行按需切换。

二、移植步骤

移植前准备:
1.想要移植的目标工程。
2.下载好的LVGL源码(版本按需选择,版本越高功能越多,占用空间也就越大),本例由于芯片资源有限,所以选择的是LVGL7.11版本。

1.源码复制

在想要移植的目标工程中创建一个LVGL文件夹
在这里插入图片描述

在建好的LVGL文件夹中新建app,porting,src文件夹
app:放置以后自己编写的LVGL工程
porting:LVGL接口文件
src:LVGL源码文件
在这里插入图片描述
打开下载好的LVGL源码文件夹,将LVGL源码文件夹中的src文件夹中的内容全部copy到上图目标工程的src文件夹中。
在这里插入图片描述
打开LVGL源码文件中的examples文件夹
在这里插入图片描述
找到该文件夹中的porting接口文件,将该文件中的内容同样复制到目标工程中我们自己创建的porting文件夹中。
在这里插入图片描述
在源码文件中找到lvgl/lvgl.h文件以及lvgl/lv_conf_template.h文件,将其复制到目标工程中的LVGL文件中
在这里插入图片描述
复制过来后改个名字,将lv_conf_template.h文件更名为lv_conf.h
在这里插入图片描述
将porting文件夹中的显示接口文件改名
lv_port_disp_template为屏幕驱动。
lv_port_fs_template为文件系统驱动。
lv_port_indev_template为输入驱动
本项目中我们只使用了屏幕的显示功能,因此我们只修改屏幕驱动的文件名字。
在这里插入图片描述

2.添加到工程中

用keil5打开目标工程项目。
在工程目录下新建三个分组,分别为Lvgl/app、Lvgl/porting、Lvgl/src三个目录。
在这里插入图片描述
添加文件到工程目录中
porting目录下只添加lv_port_disp.c,以及Lvgl目录下的lv_conf.h文件,这两个文件后面需要修改。
src目录下,添加Lvgl/src目录下除去gpu文件夹外的所有文件夹内的c文件。
在这里插入图片描述
之后,点击配置选择
在这里插入图片描述
点击C/C++添加头文件路径,把Lvgl文件夹下所有包含h文件的路径全部添加。
在这里插入图片描述

3.配置文件的修改

打开lv_port_disp.c/.h文件,将所有的#include "lvgl\lvgl.h"替换为#include “lvgl.h”。
将if置1,开启屏幕接口和lcd初始化
在这里插入图片描述
修改lv_conf.h文件如下图所示
在这里插入图片描述
编译修改后的代码,如果没有error就代表以上配置,配置成功了。

4.适配屏幕接口

  修改lv_conf.h内的宏定义,通过它可以设置库的基本行为,裁剪不需要模块和功能,在编译时调整内存缓冲区的大小等等。

修改屏幕分辨率
在这里插入图片描述
修改屏幕DPI
DPI 是将屏幕分辨率屏幕尺寸结合起来的度量,用于描述在每英寸上的像素密度。
屏幕分辨率:指的是屏幕上横向和纵向像素的总数,通常表示为宽×高的像素数。
屏幕尺寸:指的是屏幕的对角线长度,通常以英寸(inch)为单位。
在这里插入图片描述
设置占用内存,最少也要2kb,根据芯片的内存来决定,越大越好。
本例使用的芯片:STM32F103ZET6
512 KB闪存(用于程序存储)。
64 KB SRAM(用于数据存储)
在这里插入图片描述
由于该芯片没有GPU,所以还需要把GPU选项关掉
在这里插入图片描述
继续适配屏幕接口到lvgl上、修改lv_port_disp.c文件中的显示接口函数,用于适配我们的屏幕与lvgl,包含lcd屏幕显示的头文件。
在这里插入图片描述
修改屏幕显示初始化函数lv_port_disp_init,我们用方法一显示,同时修改屏幕的大小。
在这里插入图片描述
  修改disp_init函数,该函数一般将我们的屏幕初始化放进去,也可以在硬件层初始化屏幕,这里就可以不写底层屏幕的初始化。
在这里插入图片描述
修改disp_flush函数,该函数是lvgl绘制界面的关键函数,是用于绘制界面的最基本的函数,也是lvgl与底层屏幕的绘制适配接口函数。

LVGL给的例程中是想让我们使用画点方式进行实现,但画点方式效率比较低,所以此处我们使用LCD_Color_Fill函数实现,该函数通常支持一次性填充整个屏幕或者特定区域,可以大大提高cpu的运行效率
在这里插入图片描述

三、需求实现

1.初始化LVGL

为了给LVGL提供时间基准,告诉LVGL现在过了多久,我们需要写一个系统心跳钩子函数。
如果使用的是裸机开发,那么直接将这个函数放到硬件定时器的1ms中断服务函数内。
此时我们使用的是操作系统,所以我们可以放到系统基础节拍的钩子函数内。
在这里插入图片描述
为了使屏幕定期刷新我们还需要每隔x毫秒调用lv_task_handler()用以处理与lvgl相关的任务。
如果是裸机开发,那么我们可以在while(1)中做一个时间点,1ms或者10ms的调用一次这个函数。
此时我们使用的是操作系统,那么我们可以将lv_task_handler()函数放到任务中的while(1)中定期调用。
在这里插入图片描述
搞定LVGL时间基准和定期刷新屏幕后,想要进行屏幕的创建和编写,此时我们还需要进行LCD初始化LVGL初始化以及LVGL接口的初始化。
在这里插入图片描述

2.创建屏幕

子类和父类的概念
在LVGL中,有子类和父类的概念,简单来说就是,一个屏幕上的所有部分都是这个屏幕的子类。
在LVGL中,所有的屏幕都没有父类,不需要依赖任何东西。
可以同时创建多个屏幕,在LVGL初始化中,会在后台加载所有的屏幕。想要那个屏幕显示时,只需调用屏幕加载函数调用即可。
了解以上概念后,我们就可以开始创建屏幕了。

先创建lv_obj_t类型的变量。
lv_obj_t类型用来表示图形界面库中的各种对象,如屏幕(screen)、按钮(button)、标签(label)等。

lv_obj_t * scr1;
lv_obj_t * scr2;
lv_obj_t * obj;
lv_obj_t * obj1;
lv_obj_t * label1;

进行屏幕的创建以及其他部件的创建。

	//测试代码--显示一个label
	scr1 = lv_obj_create(NULL, NULL);
	obj = lv_obj_create(scr1, NULL);
	obj1 = lv_obj_create(obj, NULL);
	lv_obj_set_size(obj, 50, 50);	 /*Button size*/
	lv_obj_set_pos(obj, 0,0);
	//
	scr2 = lv_obj_create(NULL, NULL);
	label1 = lv_label_create(scr2, NULL);
	lv_label_set_long_mode(label1, LV_LABEL_LONG_BREAK);     
	lv_label_set_recolor(label1, true);                      
	lv_label_set_align(label1, LV_LABEL_ALIGN_CENTER);
	lv_label_set_text(label1, "#0000ff Re-color# #ff00ff words# #ff0000 of a# label "
														"and  wrap long text automatically.");
	lv_obj_set_width(label1, 150);
	lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, -30);
	lv_scr_load(scr1);

3.按键控制

void KEY_Task(void *p)
{
	while(1)
	{
		switch(key_getvalue())
		{
			case 1:lv_scr_load_anim(scr2,LV_SCR_LOAD_ANIM_MOVE_LEFT,1000,0,0);break;
			case 2:lv_scr_load_anim(scr1,LV_SCR_LOAD_ANIM_MOVE_LEFT,1000,0,0);break;
			case 3:;break;
			case 4:break;
		}
		vTaskDelay(10);//MS级别的延时,带有阻塞性质,任务会从运行态变为阻塞态
	}
}	

本质上就是通过代码控制不同屏幕的加载,后加载的屏幕会覆盖掉前面加载的屏幕。可以理解为PPT的幻灯片。
lv_scr_load()和lv_scr_load_anim()函数都是屏幕加载,只不过前者是直接加载,后者是动画加载。

完整代码

main.c

#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "delay.h"
#include "string.h"
#include "pwm.h"
#include "adc.h"
#include "su03t.h"
#include "dht11.h"
#include "kqm.h"
#include "usart.h"
#include "key.h"
#include "aliot.h"
#include "lvgl.h"
#include "lv_port_disp.h"
//使用FreeRtos相关头文件之前,一定要先包含这个#include "FreeRtos.h"
#include "FreeRtos.h"
#include "task.h"
#include "semphr.h"
#include "queue.h"
#include "bsp_lcd.h"
#include "RTC.h"
#include "time.h"

TaskHandle_t Deal_TaskHandle;//数据处理
TaskHandle_t Su03_TaskHandle;//语音播报
TaskHandle_t Ali_pub_TaskHandle;//阿里发布
TaskHandle_t Lcd_TaskHandle;
TaskHandle_t Lvgl_TaskHandle;
TaskHandle_t KEY_TaskHandle;//按键任务

char D_wen[20];
char D_shi[20];
char D_time[20];
struct tm *info;
extern const unsigned char gImage_hengliu[153600];
uint32_t sec=0;
lv_obj_t * scr1;
lv_obj_t * scr2;
lv_obj_t * obj;
lv_obj_t * obj1;
lv_obj_t * label1;
void Lvgl_Task(void *p)
{
  LCD_Init();
	lv_init();
	lv_port_disp_init();
	//测试代码--显示一个label
	scr1 = lv_obj_create(NULL, NULL);
	obj = lv_obj_create(scr1, NULL);
	obj1 = lv_obj_create(obj, NULL);
	lv_obj_set_size(obj, 50, 50);	 /*Button size*/
	lv_obj_set_pos(obj, 0,0);
	//
	scr2 = lv_obj_create(NULL, NULL);
	label1 = lv_label_create(scr2, NULL);
	lv_label_set_long_mode(label1, LV_LABEL_LONG_BREAK);     
	lv_label_set_recolor(label1, true);                      
	lv_label_set_align(label1, LV_LABEL_ALIGN_CENTER);
	lv_label_set_text(label1, "#0000ff Re-color# #ff00ff words# #ff0000 of a# label "
														"and  wrap long text automatically.");
	lv_obj_set_width(label1, 150);
	lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, -30);
	lv_scr_load(scr1);
	//
	while(1) 
	{
		lv_task_handler();
		vTaskDelay(5);
	}
}

void KEY_Task(void *p)
{
	while(1)
	{
		switch(key_getvalue())
		{
			case 1:lv_scr_load_anim(scr2,LV_SCR_LOAD_ANIM_MOVE_LEFT,1000,0,0);break;
			case 2:lv_scr_load_anim(scr1,LV_SCR_LOAD_ANIM_MOVE_LEFT,1000,0,0);break;
			case 3:;break;
			case 4:break;
		}
		vTaskDelay(10);//MS级别的延时,带有阻塞性质,任务会从运行态变为阻塞态
	}
}	

int main()
{
	RGBpwm_Config();
  Kqm_U4Config();
  Usart1_Config();
  Su03t_U5Config();
	DHT11_Config();	 
	Adc_Config();
	Led_Init();
	key_Init();
	BaseType_t Ret = pdPASS;
	Ret = xTaskCreate(Lvgl_Task,"Lvgl_Task",500,NULL,2,&Lvgl_TaskHandle);	
	if(Ret==pdPASS)
	{
		printf("Lvgl运行成功!\r\n");
	}
	Ret = xTaskCreate(KEY_Task, //创建任务的任务函数名
                    "KEY_Task",//任务名字
                    100,//任务栈深度。32位单片机*4
                    NULL,//创建任务时传递参数,没有就给NULL
                    2,//任务优先级
										&KEY_TaskHandle);//任务的句柄,用于后边删除,挂起任务
	if(Ret == pdPASS){
	printf("KEY_Task创建完成\r\n");
	}
	printf("开始调度!\r\n");
	vTaskStartScheduler();
	while(1)
	{
		
	}
}

void vApplicationStackOverflowHook( TaskHandle_t xTask,char *pcTaskName )
{
	printf("任务:%s->栈溢出\r\n",pcTaskName);
	printf("任务剩余空间:%d\r\n",(int)uxTaskGetStackHighWaterMark(xTask));
	while(1)//栈溢出时卡死到钩子函数中
	{}
}

//系统心跳钩子函数
void vApplicationTickHook(void)
{
	lv_tick_inc(1);//告诉LVGL时间过了1Ms
}

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值