TFT-LCD移植LVGL详细过程记录

TFT-LCD移植LVGL

LVGL(轻量级和通用图形库)是一个免费和开源的图形库,它提供了创建嵌入式GUI所需的一切,具有易于使用的图形元素,美丽的视觉效果和低内存占用。

LVGL更多介绍:https://zhuanlan.zhihu.com/p/406294618

本次实验将LVGL移植到STM32F103ZET6中,并编译通过,记录一下移植过程

在CubeMX中修改栈大小

因为LVGL要求MCU的堆栈最少是2K,2K = 2048 = 0x800,所以要在CubeMX中修改堆栈大小,为什么不直接在代码中修改呢,因为在代码中修改的话,而CubeMX中没有修改,下一次用CubeMX生成工程后又会覆盖掉代码中修改的地方,所以需要在CubeMX中修改

使用上次TFT-LCD触摸的文件,打开后在下图位置中修改,最小堆栈大小设置为0x800,然后生成工程代码

TFT-LCD移植LVGL详细过程记录_第1张图片

在keil工程里也可查看修改的位置

TFT-LCD移植LVGL详细过程记录_第2张图片

在keil中选择C99模式

LVGL要求开启C99模式,在C/C++选项中勾选即可

TFT-LCD移植LVGL详细过程记录_第3张图片

去官网或Github下载LVGL的源码和例程

官网地址:https://lvgl.io/

Github下载地址:https://github.com/lvgl

打开会比较慢,多试几次

TFT-LCD移植LVGL详细过程记录_第4张图片

TFT-LCD移植LVGL详细过程记录_第5张图片

下载好源码和例程,这里用V7版本的

TFT-LCD移植LVGL详细过程记录_第6张图片

建立LVGL的文件夹

LVGL官方代码里是相对路径,所以一定要按步骤建好文件夹

在工程目录下新建一个GUI的文件夹

TFT-LCD移植LVGL详细过程记录_第7张图片

GUI文件夹里再建三个子文件夹

TFT-LCD移植LVGL详细过程记录_第8张图片

lvgl:放源码,将下载的源码 lvgl-release-v7压缩包复制到该文件夹,并解压缩

TFT-LCD移植LVGL详细过程记录_第9张图片

lvgl_driver:放显示和触摸的驱动

在解压完的源码文件中,有个examples的文件夹,点击打开

TFT-LCD移植LVGL详细过程记录_第10张图片

再打开porting文件夹

TFT-LCD移植LVGL详细过程记录_第11张图片

这些就是显示和触摸的驱动文件

TFT-LCD移植LVGL详细过程记录_第12张图片

将显示和触摸的.c和.h文件拷贝到 lvgl_driver文件夹中,因为没有用到文件系统,所以可以不用拷贝,需要将文件名的_template去掉,不然后面加入keil工程后编译会出错

TFT-LCD移植LVGL详细过程记录_第13张图片

lvgl_example:放例程,将官方例程 lv_examples-release-v7复制到该文件夹并解压缩

TFT-LCD移植LVGL详细过程记录_第14张图片

将配置文件剪切到GUI根目录

打开lvgl的文件夹,找到 lv_conf_template.h文件

TFT-LCD移植LVGL详细过程记录_第15张图片

剪切粘贴到GUI文件夹的目录下,并修改文件名,将_template去掉

TFT-LCD移植LVGL详细过程记录_第16张图片

然后打开 lv_conf.h 文件,修改预编译选项,将0改为1,然后保存

TFT-LCD移植LVGL详细过程记录_第17张图片

打开 lvgl_examples 的文件夹,找到 lv_ex_conf_template.h 文件

TFT-LCD移植LVGL详细过程记录_第18张图片

剪切粘贴到GUI文件夹的目录下,同样要修改文件名,将_template去掉

TFT-LCD移植LVGL详细过程记录_第19张图片

同理,将 lv_ex_conf.h 的预编译选项的0改为1,保存

TFT-LCD移植LVGL详细过程记录_第20张图片

至此,LVGL的工程文件夹已经创建好

TFT-LCD移植LVGL详细过程记录_第21张图片

keil工程创建lvgl的文件夹

TFT-LCD移植LVGL详细过程记录_第22张图片

打开lvgl->src,将下面文件夹里的.c文件全都添加到keil工程的lvgl文件夹中

TFT-LCD移植LVGL详细过程记录_第23张图片

添加完的lvgl文件夹如下

TFT-LCD移植LVGL详细过程记录_第24张图片

然后编译一次,没有出现错误,发现有警告,这些警告是源码里的,尽量不要去修改源码

TFT-LCD移植LVGL详细过程记录_第25张图片

在这里插入图片描述

发现是111的警告,可以在keil里设置,屏蔽这些警告,需要在C/C++选项中的下图位置添加这条语句:–diag_suppress=111

TFT-LCD移植LVGL详细过程记录_第26张图片

然后再次编译,发现没有出现警告

TFT-LCD移植LVGL详细过程记录_第27张图片

修改配置文件

打开lv_disp.c源文件,找到 lv_conf.h文件,这个就是配置文件,要修改屏幕的最大分辨率,因为手上屏幕是240x320的,所以水平分辨率改为240,垂直分辨率保持默认

TFT-LCD移植LVGL详细过程记录_第28张图片

往下一点就是颜色格式设置和总线位数设置,颜色格式默认RGB565的,所以不用改,手上屏幕是16位总线的,所以总线位数也不用改,这需要根据具体屏幕硬件来设置

TFT-LCD移植LVGL详细过程记录_第29张图片

LVGL显示界面需要内存,如果显示的东西多,则需要的内存就大,下面这里就是设置分给LVGL内存的大小,如果显示的内容多,则32可改为其他值,这里使用默认

在这里插入图片描述

191行的是设置是否使用GPU,默认是1开启,改为0,选择不使用

在这里插入图片描述

211行是设置是否使用文件系统,默认是1使用,改为0,选择不使用

在这里插入图片描述

最后编译一次,没有错误则进行下一步

定时器中断回调函数中调用 LVGL 心跳函数 lv_tick_inc

首先包含lvgl的头文件路径

TFT-LCD移植LVGL详细过程记录_第30张图片

在MyApplication.h头文件中添加 lvgl.h 头文件

TFT-LCD移植LVGL详细过程记录_第31张图片

在定时器中断回调函数中调用lvgl的心跳函数 lv_tick_inc(),定时器每隔一定时间就调用该函数,控制 lvgl 刷新界面,lv_tick_inc函数需要传入参数,参数就是定时器定时时间,比如定时5ms,那就传入5,定时1ms,那就传入1

TFT-LCD移植LVGL详细过程记录_第32张图片

修改显示驱动

在keil工程中新创建一个驱动的文件夹 lvgl_driver,并添加显示驱动源文件 lv_port_disp.c,

TFT-LCD移植LVGL详细过程记录_第33张图片

打开lv_port_disp.c文件,修改预编译选项,0改为1,修改引入头文件的名称,lv_port_disp_template.h 改为 lv_port_disp.h

TFT-LCD移植LVGL详细过程记录_第34张图片

打开 lv_port_disp.h头文件,预编译0改为1,"lvgl/lvgl.h"改为 “…/lvgl/lvgl.h”,编译器才能找到该头文件路径

TFT-LCD移植LVGL详细过程记录_第35张图片

再回到 lv_port_disp.c 源文件中,找到 disp_init() 函数,这里可以放自己写的TFT-LCD屏幕驱动

TFT-LCD移植LVGL详细过程记录_第36张图片

当然使用自己的函数需要引入对应的头文件

TFT-LCD移植LVGL详细过程记录_第37张图片

执行完disp_init函数后,会进行缓存的定义,lvgl给出了三种定义的方法,如下图,方法1最简单,方法2使用了双重缓存,方法3是根据屏幕大小定义缓存,这次使用比较简单的第1种方法,把另外两种注释掉即可

第一种方法中的缓存大小 LV_HOR_RES_MAX * 10 改为 LV_HOR_RES_MAX * LV_HOR_RES_MAX / 10,设置缓存大一点

TFT-LCD移植LVGL详细过程记录_第38张图片

再往下就是设置当前屏幕的尺寸大小的,将水平的480改为240,跟屏幕匹配;前面源码里设置的是屏幕最大的分辨率,这里设置的尺寸大小不能大于前面设置的最大分辨率

TFT-LCD移植LVGL详细过程记录_第39张图片

接下来修改disp_flush函数,下面是该函数没有被修改过的,可以看出该函数的功能就是设置一个窗口,然后往窗口里写入像素点的值,写入的操作默认被注释掉了,写完一个像素点后,像素点指针加1,继续写下一个像素点

/* Flush the content of the internal buffer the specific area on the display
 * You can use DMA or any hardware acceleration to do this operation in the background but
 * 'lv_disp_flush_ready()' has to be called when finished. */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    int32_t x;
    int32_t y;
    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            /* Put a pixel to the display. For example: */
            /* put_px(x, y, *color_p)*/			//写入像素点数据
            color_p++;						   //像素点指针加1
        }
    }

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

修改后的disp_flush函数如下,挂起STM32的systick时钟是为了提高GUI的刷新速度,在写完像素点数据后,再开启systick时钟

/* Flush the content of the internal buffer the specific area on the display
 * You can use DMA or any hardware acceleration to do this operation in the background but
 * 'lv_disp_flush_ready()' has to be called when finished. */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    uint16_t x,y;
	
	//挂起systick,提高GUI刷新速率
	HAL_SuspendTick();
	
	//设置窗口,参数:X轴起始位,Y轴起始位,长度,宽度
	TFT_LCD.LCD_SetWindows(area->x1,area->y1,area->x2-area->x1+1,area->y2-area->y1+1); 
	
    for(y = area->y1; y <= area->y2; y++) 
	{
      for(x = area->x1; x <= area->x2; x++) 
	  {
            /* Put a pixel to the display. For example: */
            /* put_px(x, y, *color_p)*/
		   LCD_Write_DATA(color_p->full);		//调用函数写入像素点数据
            color_p++;
      }
    }

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
		
	//恢复systick
	HAL_ResumeTick();
}

修改触摸驱动

在lvgl_driver文件夹中,添加触摸驱动源文件 lv_port_indev.c

TFT-LCD移植LVGL详细过程记录_第40张图片

然后与显示驱动一样,修改源文件的预编译选项,修改引入头文件的名称

TFT-LCD移植LVGL详细过程记录_第41张图片

lv_port_indev.h头文件也是修改预编译选项,修改引入的头文件路径

TFT-LCD移植LVGL详细过程记录_第42张图片

然后回到 lv_port_indev.c源文件中,里面是一些功能的驱动函数,如触摸、鼠标、键盘、编码器和按钮功能,需要什么功能根据实际情况选择,本次移植使用简单点的触摸功能,其他功能函数都可删掉

TFT-LCD移植LVGL详细过程记录_第43张图片

修改后的lv_port_indev.c源文件,用lvgl自带的初始化函数,然后对touchpad_read函数进行修改

/**
 * @file lv_port_indev_templ.c
 *
 */

 /*Copy this file as "lv_port_indev.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_indev.h"
#include "MyApplication.h"

static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);

void lv_port_indev_init(void)
{
    /* Here you will find example implementation of input devices supported by LittelvGL:
     *  - Touchpad
     *  - Mouse (with cursor support)
     *  - Keypad (supports GUI usage only with key)
     *  - Encoder (supports GUI usage only with: left, right, push)
     *  - Button (external buttons to press points on the screen)
     *
     *  The `..._read()` function are only examples.
     *  You should shape them according to your hardware
     */

    lv_indev_drv_t indev_drv;

    /*------------------
     * Touchpad
     * -----------------*/

    /*Register a touchpad input device*/
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    lv_indev_drv_register(&indev_drv);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/* Will be called by the library to read the touchpad */
static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
	//当前坐标
    static uint16_t last_x = 0;
	static uint16_t last_y = 0;
	
	//如果触摸导致坐标更新
	if(Touch.Touch_Flag == TRUE)
	{		
		Touch.Touch_Flag = FALSE;
		
		//把新坐标Touch.LCD_X和Touch.LCD_Y赋给lvgl的坐标结构体的x和y
		data->point.x = Touch.LCD_X;
		data->point.y = Touch.LCD_Y;
		//更改状态,lvgl获取坐标
		data->state = LV_INDEV_STATE_PR;
		
		//更新当前坐标
		last_x = data->point.x;
		last_y = data->point.y;
	}
	else	//如果没有触摸
	{
		//把上一次坐标赋给lvgl坐标结构体的x和y
		data->point.x = last_x;
		data->point.y = last_y;
		//更改状态,lvgl获取坐标
		data->state = LV_INDEV_STATE_REL;
	}
	
	return false;
}

#else /* Enable this file at the top */

/* This dummy typedef exists purely to silence -Wpedantic. */
typedef int keep_pedantic_happy;
#endif

系统运行主函数中判断屏幕是否被触摸,如果触摸了,则更新标志位

/*
	* @name   Run
	* @brief  系统运行
	* @param  None
	* @retval None      
*/
static void Run()
{		
	//获取坐标板坐标
	if(Touch.Scan() == TRUE)
	{
		//通过该标志位知道屏幕是否被触摸更新坐标
		Touch.Touch_Flag = TRUE;
	}
}

包含lvgl的头文件

在自己工程的公共头文件MyApplication.h中添加lvgl的头文件

TFT-LCD移植LVGL详细过程记录_第44张图片

在设置中添加头文件路径,包括源文件路径和驱动路径

TFT-LCD移植LVGL详细过程记录_第45张图片

显示驱动和触摸驱动都是有初始化函数的,但它们的头文件都没有声明初始化函数,如果要调用这些函数的话是会有警告的,所以要先进行声明

TFT-LCD移植LVGL详细过程记录_第46张图片

TFT-LCD移植LVGL详细过程记录_第47张图片

在自己的初始化函数中调用lvgl的初始化函数lv_init(),显示驱动函数lv_port_disp_init(),触摸驱动函数lv_port_indev_init()

TFT-LCD移植LVGL详细过程记录_第48张图片

把前面触摸屏扫描函数中在LCD屏幕上显示触摸屏的坐标值的语句删除
TFT-LCD移植LVGL详细过程记录_第49张图片

移植基本完成,进行编译,没有报错

TFT-LCD移植LVGL详细过程记录_第50张图片

你可能感兴趣的:(LVGL,单片机,stm32,嵌入式硬件,LVGL,TFT-LCD)