hehung · 2022年12月13日 · 四川

【GD32F427开发板试用】7. 移植LVGL到GD32F427V

之前发帖

【GD32F427开发板试用】1. 串口实现scanf输入控制LED
【GD32F427开发板试用】2. RT-Thread标准版移植
【GD32F427开发板试用】3. 硬件IIC0驱动OLED显示中文
【GD32F427开发板试用】4. ADC采集摇杆模块移动量
【GD32F427开发板试用】5. SPI驱动TFTLCD屏幕
【GD32F427开发板试用】6. 定时器运用之精确定时1s

前言

LVGL是一个开源的轻量级图形显示库,非常适合资源比较少的单片机进行移植,上一次使用SPI驱动了一块TFTLCD屏幕,这一次就想着将LVGL移植上来,可以显示一些好看的界面。

本文实现了如下功能:

  1. 移植基于RT-Thread OS(使用RT_Thread OS移植起来十分的方便,都不需要自己去github下载源码了);
  2. 开发工具基于RT-Thread Stduio;
  3. 实现了LVGL功能移植,并使用label(标签)显示字符串;
  4. 如果没有使用RT-Thread OS,使用其他的系统或者是裸机,本文还是有一定的参考价值。

开发流程

环境准备

  1. 准备一个可以正常驱动LCD或者其他显示屏幕的工程,其中需要用到画点函数,画点函数是LVGL能够正常工作的基础,如果没有这个函数,需要先实现一个。 使用GD32F427V-START驱动TFT LCD的过程可以参考我之前发的帖子(本文最上部分);
  2. RT-Thread Studio开发环境,如果使用标准版RT-Thread OS,其他开发环境也是一样的操作,但是配置方面需要使用RT-Thread 提供的ENV工具;如果没有使用RT-Thread OS,使用其他的系统或者是裸机,本文还是有一定的参考价值;

移植过程

添加LVGL包

如下图,添加LVGL,如果使用ENV,则按照ENV的方式添加;
1.png
如果使用逻辑,需要在github上下载,电脑安装git,然后输入命令:

git clone --recursive https://github.com/lvgl/lvgl.git

创建显示文件

如下图,将这两个文件移植到application目录下,并更新名字,不然会导致编译错误。
注:还有四个文件根据实际情况选择是否需要复制到application,和文件系统以及触摸屏有关,我所使用的LCD屏幕不带触摸功能,所以没有移植,如果带有触摸功能,可以将最后两个文件一并移植到application文件夹下备用,关于触摸文件的修改,本文不做说明。
2.png

更新显示文件

修改头文件包含以及头文件内容

  1. 针对app_port_disp.c文件,修改头文件包含
    4.png
  2. 针对app_port_disp.h文件,修改如下部分
    5.png

移植(重要部分)

现在开始最重要的部分了,这部分是lvgl移植的核心,需要和我们的驱动文件配合起来。具体方式见下面:

  1. 修改显示屏的长和宽
    我所使用的显示屏是320X240的,所以做如下修改。
    如下图:
    6.png
  2. lv_port_disp_init函数修改
    定位到该函数,初始化buffer,官方提供了三种方式,并详细描述了每方式的区别,本文使用第一种方式,最为简单,我们将buffr扩大了一些,其实默认也是可以的。
    屏蔽不需要的其他两种方式。
    如下图:
    7.png
  3. disp_init函数修改
    定位到disp_init,这里面添加驱动函数的初始化函数即可。
    修改如下:
    其中,lcd_init是我的LCD屏幕的驱动函数;
    lcd_set_region是我的驱动函数中设置屏幕长宽的函数,这里根据自己的驱动函数实现逻辑添加即可,对于我的驱动代码,lcd_set_region是必须要加的,不然屏幕显示不正确。

    /*Initialize your display and the required peripherals.*/
    static void disp_init(void)
    {
     /*You code here*/
     /* Initialize lcd */
     extern void lcd_init(void);
     lcd_init();
     extern void lcd_set_region(uint16_t x_start,uint16_t y_start,uint16_t x_end,uint16_t y_end);
     lcd_set_region(0,0,MY_DISP_HOR_RES - 1,MY_DISP_VER_RES - 1);
    }
  4. 更新disp_flush函数
    该函数是LVGL显示的核心函数,需要将我们自己的驱动代码写到这里,主要是画一个实心矩形的驱动代码,官方提供了一个接口,我们可以直接在两个for循环内部加上我们的画点函数,或者是自己实现一个画实心矩形的逻辑。我修改的代码如下。
    其中,

    • LCD_CS_CLR与LCD_CS_SET是我的LCD特有的操作,不一定每个驱动代码都会这样写;
    • lcd_draw_point是我的画点函数,color_p是颜色变量,一般的颜色都是16位的,所以直接转换成了uint16即可,颜色的位数可以进行配置,后面再说。
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    if(disp_flush_enabled) {
        /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

        int32_t x;
        int32_t y;

        LCD_CS_CLR;
        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)*/
                extern void lcd_draw_point(uint16_t x,uint16_t y,uint16_t data);
                lcd_draw_point(x, y, (uint16_t)color_p->full);
                color_p++;
            }
        }
        LCD_CS_SET;
    }

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

到目前为止,基本上以及算是移植完成了显示部分了,但是现在还是不能使用,因为还没有进行配置,接下来描述这部分。

RT-Thread配置

如果没有使用RT-Thread OS可以忽略这步骤,后面的步骤(lv_conf.h文件中)也可以对这部分内容进行配置。
打开RT-Thread配置页面,配置lvgl相关参数,配置想很少,根据实际情况配置。
第一项配置优先级;
第二项配置LVGL使用RT-Thread任务的栈空间;
第三项配置,用于配置屏幕刷新速率,单位是ms。默认是5。其实没必要,因为SPI总线的刷新速度跟不上,对于GD32F427V,SPI最快也要二分频,连接到了APB1总线,所以最高频率为25MHz。这里设置20或者30都是可以的,本来刷新速率也跟不上。
8.png

lv_conf.h文件创建

lv_conf.h文件是用于配置LVGL中的一些功能是否使能的。模板中已经提供了,我们只需要复制过来,更新一下名字即可。
9.png

  1. 使能该文件
    90.png
  2. 设置配置
    默认配置就能够使用了,但是很多选择项都是打开的,很占用资源,可以对其中一些不使用的显示模块进行禁用,本文就不做过多说明。
    注: 在编译的时候还是会报一些警告或者是编译错误,直接将lv_conf.h中报错的宏定义禁掉就可以了,因为很多都是重定义,在其他地方已经定义过了,这里无需再次定义,然后在此编译就不会报错了。
    举例如下:
    91.png

关于宏LV_DPI_DEF的计算公式如下:
DPI = 根号下(长边像素的平方+短边像素的平方)/英寸。
对于我的LCD,2.2寸,320X240,所以计算公式为
DPI = 根号下(320320 + 240 240) / 2.2 ≈ 182.

到目前为止,整个移植过程都结束了,接下来就是写测试用例来测试移植是否成功了。

测试代码

如果用的是RT-Thread OS,官方已经提供了测试代码,如下,并且会自动参与编译,我们就无须手动创建了,没有使用RT-Thread需要手动创建一个文件,本文不做说明:
92.png

官方已经帮我们创建好了显示任务,我们不需要再次创建,只需要往里面添加显示内容即可。
禁掉其中无用的内容。
只需要修改lvgl_thread_entry文件。
禁掉lv_port_indev_init以及lv_user_gui_init;
lv_user_gui_init起止可以不用禁掉,在里面写我们的代码,但是不知道为什么我这边总是编译不通过,所以就禁掉了,然后添加了函数
Gui_LvglEntry,这个函数需要我们自己实现。

static void lvgl_thread_entry(void *parameter)
{
#if LV_USE_LOG
    lv_log_register_print_cb(lv_rt_log);
#endif /* LV_USE_LOG */
    lv_init();
    lv_port_disp_init();
//    lv_port_indev_init();
//    lv_user_gui_init();

    extern void Gui_LvglEntry(void);
    Gui_LvglEntry();

    /* handle the tasks of LVGL */
    while(1)
    {
        lv_task_handler();
        rt_thread_mdelay(PKG_LVGL_DISP_REFR_PERIOD);
    }
}

实现Gui_LvglEntry

在application里面新建一个文件app_gui.c,然后新建一个函数Gui_lvgl_Test,内容如下:
该函数我实现了显示不同颜色的字符串;见效果展示。

/*
 * @hehung
 * 2022-12-13
 *  转载请注明出处
 * */

#include "lvgl.h"

static void Gui_lvgl_Test(void);

/* My GUI entry */
void Gui_LvglEntry(void)
{
    Gui_lvgl_Test();
}

/* Lvgl test for porting to GD32F427V  */
static void Gui_lvgl_Test(void)
{
    lv_obj_t * label;

    /*Create a label below the slider*/
    label = lv_label_create(lv_scr_act());
    lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP);     /*Break the long lines*/
    lv_label_set_recolor(label, true);                      /*Enable re-coloring by commands in the text*/
    lv_label_set_text(label, "#0000ff GD32F427V-START#\n"
                             "#ff00ff aijishu.com#\n "
                             "#ff0000 Lvgl Porting Test#\n"
                             "         --hehung");
    lv_obj_set_width(label, 150);  /*Set smaller width to make the lines wrap*/
    lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);
}

至此,整个LVGL移植过程结束。下面试验证。

验证

效果见下方。
试了一下刷新,速度还是比较慢,后期还需要优化,使用DMA应该会快一些。
93.png

94.png

推荐阅读
关注数
10711
内容数
187
中国高性能通用微控制器领域的领跑者兆易创新GD系列芯片技术专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息