LVGL的学习

前言

提示:本博客中的大部分学习内容参考正点原子LGVL开发指南以及一些他人博客内容,相关文件可以去正点原子官方网站下载或者CSDN自查,请大家在使用时:自用为好,切勿商用
正点原子资料下载中心(LVGL新版学习资料)

编写本博客主要是回顾在个人项目中对于LVGL的学习历程,并且对于学习LVGL这一新技术进行总结。


一、LVGL是什么

简单来说,LVGL就是一款免费的轻量级开源图形库。我个人的看法就是对于嵌入式开发,可以利用LVGL设计出自己所想要的显示控制界面,能够满足自己的需求与创意想法。而其所具备的一系列优缺点,都可以在官网或者其他论坛中找到,此处就不做赘述。

二、LVGL的移植

在进行移植之前,我们需要准备一些文件:

1、LVGL的源码。LVGL 相关的源码和工程都是存放在 GitHub 远程仓库中,该 GitHub 远程仓库地址如下, 用户可以在该仓库中下载 LVGL 图形库的源码。 以下文档中我以lvgl-release-v8.3版本为例。
LVGL源代码

2、基础工程:LCD显示驱动、触摸屏显示驱动、基本定时器驱动。我使用的是正点原子官方探索者STM32F407开发板的官方例程《触摸屏实验》以及《定时器中断实验》。

3、将《定时器中断实验》中定时器中断实验\HARDWARE路径下的TIMER文件夹复制到《触摸屏实验》的HARDWARE文件夹下,并且修改《触摸屏实验》的文件名,以下以《LVGL》文件为例。

1、无操作系统

1.前期准备

1、解压从官网下载的源代码压缩包,lvgl-release-v8.3解压后如下图:
解压后的文件内容

2、在源代码文件中我们需要的只有examples文件夹src文件夹lv_conf_template.h文件lvgl.h文件,将lv_conf_template.h文件重命名为lv_conf.h其他文件及文件夹均与移植无关,但是其中的demos文件夹存放的是官方提供的演示例程,需要者可以保留。精简后如下图:
精简后文件夹

3、打开examples文件夹,仅保留porting文件夹,其余的都删除掉。


2.源码移植

1、打开刚才准备好的《LVLG》文件夹,新建LVGL文件夹,打开,新建GUI文件夹GUI_APP文件夹,其中GUI文件夹用于存放lvgl官方源代码,GUI_APP文件夹用于存放后期自己编写的界面代码。

2、将刚才精简后的 lvgl-release-v8.3文件夹 移至 GUI文件夹 下,并且重命名为 lvgl
此处说明以下为何要如此命名文件夹路径:为了兼容 LVGL源码中包含头文件的格式,不然在后期对LVGL源码进行修改时会很繁琐,需要修改多个文件内容

3、以上步骤做完、文件路径如下图:
文件路径

4、打开《lvgl》工程,填加以下如图工程分组:
工程分组
其实该工程分组对应了LVGL源码中src子目录的文件路径:
src文件目录

5、按照以下步骤,将相关路径将文件添加至对应分组中:

  • 往 LVGL/GUI/lvgl/src/core 分组中添加 core 文件夹下的全部.c 文件
  • 往 LVGL/GUI/lvgl/src/draw 组中添加 draw 文件夹下除 nxp_pxpnxp_vglitesdlstm32_dma2d 文件夹之外的全部.c 文件
  • 往 LVGL/GUI/lvgl/src/extra 组中添加 extra 文件夹下除了 lib 文件夹之外的全部.c 文件
  • 往 LVGL/GUI/lvgl/src/font 组中添加 font 文件夹下的全部.c 文件
  • 往 LVGL/GUI/lvgl/src/gpu 组中添加 draw/stm32_dma2ddraw/sdl文件夹下的全部.c文件
  • 往 LVGL/GUI/lvgl/src/hal 组中添加 hal 文件夹下的全部.c 文件
  • 往 LVGL/GUI/lvgl/src/misc 组中添加 misc 文件夹下的全部.c 文件
  • 往 LVGL/GUI/lvgl/src/widgets 组中添加 widgets 文件夹下的全部.c 文件
  • 往 LVGL/GUI/lvgl/examples/porting 组添加 LVGL/GUI/lvgl/examples/porting目录下的lv_port_disp_template.clv_port_indev_template.c 文件

6、在工程中填加对应头文件路径
头文件路径

7、往 HARDWARE 分组填加定时器驱动文件 timer.c ,并且填加对应的头文件路径
提醒,在填加定时器驱动文件之后,需要在 FWLIB 分组下填加stm32f4xx_tim.c文件,否则工程会对定时器进行报错

8、屏蔽 MDK 的警告(引用正点原子官方教程
移植至此,如果我们编译代码,则会出现很多警告,这些警告都是 LVGL源码所带来的,大家如果想屏幕这些警告,可以采用以下方法(非必须,慎用):点击 图标,选中C/C++选项 卡 , 在 Misc Controls 框 中 填 入 以 下 内 容 : --diag_suppress=68 --diag_suppress=111 --diag_suppress=188 --diag_suppress=223 --diag_suppress=546 --diag_suppress=1295。


3.修改相关文件代码

(1)为LVGL提供时基(即心跳)
  • 打开timer.c文件,声明LVGL头文件#include “lvgl.h”
  • 修改定时器3中断服务函数
void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
	{
		LED1=!LED1;//DS1翻转
		lv_tick_inc(1); //为lvgl提供1ms的心跳
	}
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);  //清除中断标志位
}
  • 打开main.c文件,在初始化中填加定时器的初始化代码,本人使用的是stm32f407开发板,代码为
TIM3_Int_Init(1000-1,84-1); //提供1ms的定时

(2)配置显示屏、触摸输入驱动
  • 先将 LVGL/GUI/lvgl/examples/porting 分组下的两个文件及对应头文件中的条件编译指令#if 0 都修改成#if 1

  • 1.对lv_port_disp_template.c进行修改

    该文件用于配置显示屏,它可以将用户的底层显示驱动与 LVGL的显示驱动衔接起来。
    主要需要修改的地方为

    • 包含lcd驱动头文件 #include “lcd.h”
    • 设置自己的屏幕宽度 #define MY_DISP_HOR_RES 320
    • 设置自己的屏幕高度 #define MY_DISP_VER_RES 480
    • 根据需求选择缓冲方式,然后将其他不需要的配置注释或者删除
    • static void disp_init(void)函数中填加自己的lcd初始化函数
    • 修改static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)函数
      static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
      {
          /* 重要!!!将源码的LCD 驱动函数更换为自己lcd.c文件中在指定区域内填充指定颜色块的函数 */
          LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(uint16_t*)color_p);
          lv_disp_flush_ready(disp_drv);
      }
    

    精简之后的lv_port_disp_template.c代码如下,如果所使用的开发板是相同的可以直接复制粘贴使用

    #if 1
    
    #include "lv_port_disp_template.h"
    #include <stdbool.h>
    #include "lcd.h"    /*包含lcd驱动头文件*/
    
    #ifndef MY_DISP_HOR_RES
        #define MY_DISP_HOR_RES    320  /*设置自己的屏幕宽度*/
    #endif
    
    #ifndef MY_DISP_VER_RES
        #define MY_DISP_VER_RES    480  /*设置自己的屏幕高度*/
    #endif
    
    /*显示设备初始化函数*/
    static void disp_init(void);    
    
    /* 显示设备刷新函数 */
    static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
    
    void lv_port_disp_init(void)
    {
        /*-------------------------
        * 初始化显示设备
        * -----------------------*/
        disp_init();
        /*-----------------------------
    * 创建一个绘图缓冲区
    *----------------------------*/
    /**
     * 1. 单缓冲区:
     * LVGL 会将显示设备的内容绘制到这里,并将他写入显示设备。
    
        /* Example for 1) */
        static lv_disp_draw_buf_t draw_buf_dsc_1;
        static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          /*A buffer for 10 rows*/
        lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   /*Initialize the display buffer*/
    
        static lv_disp_drv_t disp_drv;                         /*Descriptor of a display driver*/
        lv_disp_drv_init(&disp_drv);                    /*Basic initialization*/
    
        /*Set up the functions to access to your display*/
    
        /*Set the resolution of the display*/
        disp_drv.hor_res = MY_DISP_HOR_RES;
        disp_drv.ver_res = MY_DISP_VER_RES;
    
        /*Used to copy the buffer's content to the display*/
        disp_drv.flush_cb = disp_flush;
    
        /*Set a display buffer*/
        disp_drv.draw_buf = &draw_buf_dsc_1;
    
        lv_disp_drv_register(&disp_drv);
    }
    
    /*Initialize your display and the required peripherals.*/
    static void disp_init(void)
    {
        /*You code here*/
        LCD_Init(); /* 初始化 LCD ,填加自己的lcd初始化函数*/
    }
    
    volatile bool disp_flush_enabled = true;
    
    /* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
    */
    void disp_enable_update(void)
    {
        disp_flush_enabled = true;
    }
    
    /* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
    */
    void disp_disable_update(void)
    {
        disp_flush_enabled = false;
    }
    
    /*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)
    {
        /* 重要!!!将源码的LCD 驱动函数更换为自己lcd.c文件中在指定区域内填充指定颜色块的函数 */
        LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(uint16_t*)color_p);
        /*IMPORTANT!!!
        *Inform the graphics library that you are ready with the flushing*/
        lv_disp_flush_ready(disp_drv);
    }
    
    #else /*Enable this file at the top*/
    /*This dummy typedef exists purely to silence -Wpedantic.*/
    typedef int keep_pedantic_happy;
    #endif
    

  • 2.对lv_port_indev_template.c进行修改
    该文件用于配置输入设备,例如:触摸屏、鼠标、键盘、编码器、按键等,它可以将用户的底层输入设备驱动与 LVGL的输入驱动衔接起来。
    主要需要修改的地方为
    • 包含lcd驱动头文件 #include “lcd.h”
    • 包含触摸驱动头文件 #include “touch.h”
    • 包含按键驱动头文件 #include “key.h”
    • 删除不需要的输入设备相关代码,本人只使用到了触摸屏,如需使用其他的输入设备请浏览官网相关文档
    • static void touchpad_init(void)函数中插入自己使用的触摸屏驱动初始化函数
      tp_dev.init();  //触摸驱动函数
      if (KEY_Scan(0) == KEY0_PRES) /* KEY0 按下,则执行校准程序 */
      {
          LCD_Clear(WHITE); /* 清屏 */
          TP_Adjust(); /* 屏幕校准 */
          TP_Save_Adjdata();
      }
    
    • static bool touchpad_is_pressed(void)函数中编写获取触摸屏设备的状态,返回 true 是触摸板被按下
    static bool touchpad_is_pressed(void)
      {
          /*Your code comes here*/
    
          tp_dev.scan(0);
          if (tp_dev.sta & TP_PRES_DOWN)
          {
              return true;
          }
          return false;
      }
    
    • static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)函数中编写在触摸屏被按下的时候读取 x、y 坐标
    static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
      {
          /*Your code comes here*/
    
          (*x) = tp_dev.x[0];
          (*y) = tp_dev.y[0];
      }
    
    精简之后的lv_port_indev_template.c代码如下,如果所使用的开发板是相同的可以直接复制粘贴使用
    #if 1
    
    /*********************
    *      INCLUDES
    *********************/
    #include "lv_port_indev_template.h"
    #include "../../lvgl.h"
    #include "touch.h"  //填加自己的屏幕触摸驱动头文件 
    #include "lcd.h"    //填加自己的屏幕驱动头文件 
    #include "key.h"    //填加自己的按键驱动头文件 
    
    static void touchpad_init(void);
    static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
    static bool touchpad_is_pressed(void);
    static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y);
    
    lv_indev_t * indev_touchpad;
    
    void lv_port_indev_init(void)
    {
        static lv_indev_drv_t indev_drv;
    
        /*------------------
        * Touchpad
        * -----------------*/
    
        
        touchpad_init();    /* 第一步 初始化触摸屏 */
    
        /*Register a touchpad input device*/
        lv_indev_drv_init(&indev_drv);  /* 第二步 注册触摸屏输入设备 */
        indev_drv.type = LV_INDEV_TYPE_POINTER; /* 第三步 选择输入设备类型:触摸屏 */
        indev_drv.read_cb = touchpad_read;  /* 第四步 设置触摸坐标读取回调函数 */
        indev_touchpad = lv_indev_drv_register(&indev_drv); /* 注册输入设备 */
    }
    
    /*插入自己使用的触摸屏驱动初始化函数*/
    static void touchpad_init(void)
    {
        tp_dev.init();  //触摸驱动函数
        
        if (KEY_Scan(0) == KEY0_PRES) /* KEY0 按下,则执行校准程序 */
        {
            LCD_Clear(WHITE); /* 清屏 */
            TP_Adjust(); /* 屏幕校准 */
            TP_Save_Adjdata();
        }
        /*Your code comes here*/
    }
    
    /*Will be called by the library to read the touchpad*/
    static void touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
    {
        static lv_coord_t last_x = 0;
        static lv_coord_t last_y = 0;
    
        /*Save the pressed coordinates and the state*/
        if(touchpad_is_pressed()) {
            touchpad_get_xy(&last_x, &last_y);
            data->state = LV_INDEV_STATE_PR;
        }
        else {
            data->state = LV_INDEV_STATE_REL;
        }
    
        /*Set the last pressed coordinates*/
        data->point.x = last_x;
        data->point.y = last_y;
    }
    
    /*获取触摸屏设备的状态,返回 true 是触摸板被按下*/
    static bool touchpad_is_pressed(void)
    {
        /*Your code comes here*/
    
        tp_dev.scan(0);
        if (tp_dev.sta & TP_PRES_DOWN)
        {
            return true;
        }
        return false;
    }
    
    /*在触摸屏被按下的时候读取 x、y 坐标*/
    static void touchpad_get_xy(lv_coord_t * x, lv_coord_t * y)
    {
        /*Your code comes here*/
    
        (*x) = tp_dev.x[0];
        (*y) = tp_dev.y[0];
    }
    
    #else /*Enable this file at the top*/
    #endif
    

(3)检测代码是否移植成功

1.在进行检测之前,需要将kiel开启C99模式,否则编译代码会出现很多报错,C99 模式的方法如下图所示:
C99模式开启

2.开启 C99模式之后,接下来就可以在 main.c中编写测试代码,检测 LVGL是否移植成功,具体源码如下:

#include "sys.h"
#include "delay.h"    
#include "usart.h"  
#include "led.h"
#include "lcd.h"
#include "key.h"  
#include "touch.h" 
#include "timer.h"

#include "lvgl.h"
#include "lv_port_disp.h"
#include "lv_port_indev.h"

int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
	delay_init(168);  //初始化延时函数
	uart_init(115200);		//初始化串口波特率为115200
	
	LED_Init();					//初始化LED 
 	LCD_Init();					//LCD初始化 
	KEY_Init(); 				//按键初始化  
	tp_dev.init();				//触摸屏初始化
	TIM3_Int_Init(1000-1,84-1); //初始化定时器1ms

	lv_init();			  // lvgl系统初始化
	lv_port_disp_init();  // lvgl显示接口初始化,放在lv_init()的后面
	lv_port_indev_init(); // lvgl输入接口初始化,放在lv_init()的后面
	
	lv_obj_t *label = lv_label_create(lv_scr_act());
    lv_label_set_text(label,"Hello Alientek!!!");
    lv_obj_center(label);

	while (1)
	{
		lv_task_handler(); // lvgl的事务处理
        delay_ms(5);
	}
}

3.移植成功的效果如下:
移植成功效果
如果编译工程时出现如图 的报错(不一定出现),则说明内存不足,用户可以尝试在 startup_stm32f40_41xxx.s 文件中修改这两个参数来增加栈空间,注意要选择合适的参数,

Stack_Size      EQU     0x00000400
Heap_Size       EQU     0x00000200

内存不足报错

三、GUI-Guider创建代码的移植

GUI-Guider是恩智浦为LVGL开发了一个上位机GUI设计工具,可以通过拖放控件的方式设计LVGL GUI页面,加速GUI的设计。设计完成的GUI页面可以在PC上仿真运行,确认设计完毕之后可以生成C代码,再整合到MCU项目中。关于GUI-Guider的使用,后期有时间的话会再出一个博客,本篇博客中只介绍如何将生成的代码移植到自己的工程里面。我使用的是当前GUI-Guider最新版本1.7.2版本。以下以我自己生成的工程为例:

1、GUI-Guider代码生成

  • 打开自己的GUI项目,按照自己的需求设计相关界面及控件事件。完成之后,点击运行-编译部署C或者点击下图箭头所指即可
    GUi-Guider代码部署
    ——
  • 打开自己保存该项目的文件夹,文件夹内容如下图所示,我们所需要的只有customgenerated两个文件夹,其余的不用管:
    GUI-Guider文件内容

2、移植工作

  • customgenerated两个文件夹复制粘贴到刚才移植成功的工程的GUI_APP文件下
  • 打开该工程
  • customgenerated两个文件夹下的所有**.c**文件全部都填加到LVGL/GUI_APP分组下
  • 填加customgenerated两个文件夹的文件路径,如下图所示:
    头文件路径
  • 在main.c文件下填加两个头文件
    #include "events_init.h"  //自己设计的一系列事件都存放在该文件对应的.c文件中
    #include "gui_guider.h"   //是对自己所设计的所有控件的引用
    
  • 在头文件引用下方定义lv_ui的一个变量 lv_ui guider_ui;
  • 在main函数中,while(1)之前填加两行代码:
    setup_ui(&guider_ui);
    events_init(&guider_ui);
    
  • 编译代码,判断是否移植成功,无错误烧录到单片机上观察效果即可。
  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值