• 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,然后生成工程代码

    在这里插入图片描述

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

    在这里插入图片描述

    在keil中选择C99模式

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

    在这里插入图片描述

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

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

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

    打开会比较慢,多试几次

    在这里插入图片描述

    在这里插入图片描述

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

    在这里插入图片描述

    建立LVGL的文件夹

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

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    lvgl_driver:放显示和触摸的驱动

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

    在这里插入图片描述

    再打开porting文件夹

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    keil工程创建lvgl的文件夹

    在这里插入图片描述

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

    在这里插入图片描述

    添加完的lvgl文件夹如下

    在这里插入图片描述

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

    在这里插入图片描述

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    修改配置文件

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

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

    首先包含lvgl的头文件路径

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    修改显示驱动

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

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

    在这里插入图片描述

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

    在这里插入图片描述

    接下来修改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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    修改后的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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    修改触摸驱动

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    修改后的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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

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

    /*
    	* @name   Run
    	* @brief  系统运行
    	* @param  None
    	* @retval None      
    */
    static void Run()
    {		
    	//获取坐标板坐标
    	if(Touch.Scan() == TRUE)
    	{
    		//通过该标志位知道屏幕是否被触摸更新坐标
    		Touch.Touch_Flag = TRUE;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    包含lvgl的头文件

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

    在这里插入图片描述

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

    在这里插入图片描述

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

    在这里插入图片描述

    在这里插入图片描述

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

    在这里插入图片描述

    把前面触摸屏扫描函数中在LCD屏幕上显示触摸屏的坐标值的语句删除
    在这里插入图片描述

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

    在这里插入图片描述

  • 相关阅读:
    外卖点餐自取连锁多店小程序开发
    Linux find命令
    ESP8266-Arduino编程实例-PCF8575IO扩展器驱动
    【经验】通过跳板机远程连接内网服务器的相关配置
    Ranger功能验证
    学不会的线段树
    记一次JVM参数调优经历
    Android简易音乐重构MVVM Java版-BottomNavigationView+viewpager主界面结构(十一)
    torchtext中文文本预处理使用流程文档
    Zig实现Hello World
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/127972169