STM32第二十二课:LVGL图形库移植与屏幕创建详解(基于7.11版本)

目录

  • 需求
  • 一、LVGL概要
  • 二、移植步骤
  • 1.源码复制
  • 2.添加到工程中
  • 3.配置文件的修改
  • 4.适配屏幕接口
  • 三、需求实现
  • 1.初始化LVGL
  • 2.创建屏幕
  • 3.按键控制
  • 完整代码

  • 需求

    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
    }
    
    

    作者:小白橘颂

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32第二十二课:LVGL图形库移植与屏幕创建详解(基于7.11版本)

    发表回复