STM32F7xx移植LVGL记录

1 简介

项目中要用到屏幕,也是工作多年首次进行彩屏的开发。之前的项目大多是不需要用户界面的,或者是单色屏。GUI的开发,也就是使用过u8g2,这是个不熟悉的领域。但是也一直想要尝试彩屏的,这就有了机会。

STM32就不多说了。简单说一下LVGL,在这之前也是听过LVGL这个开源图形库的鼎鼎大名,项目开始的时候,对LVGL做了简单的了解,简单的label,button等自不必说,还有优美的动画效果,那是一定要用在项目中。文档可以看LVGL的官网或者百问网上的中文翻译。个人感觉内容不是很详细,好在LVGL上手也简单,从一个小项目开始也是足够了。

2 移植

2.1 环境说明

在Windows使用MDK进行STM32的开发,MDK版本5.30+,编译器使用AC6+,选择C99(很多地方看到LVGL需要支持C99以上)。LVGL版本使用的V9.2。

2.2 移植准备

  • 下载LVGL源码,GitHub:https://github.com/lvgl/lvgl.git
  • 创建STM32的Keil工程
  • 在工程中添加LVGL源码,这是个比较繁琐的步骤,源码中的文件放在不同的文件夹中,而且新手会不清楚哪些需要,哪些可以不用(我就是这样的,不想把不需要的添加到工程中,但是我又不了解)。可以参考正点原子的视频或文档,但是正点的使用的是V8.3的版本,还是有不一样的地方。
  • 或者使用Keil的pack,一键添加到工程中,操作简单。LVGL的pack在源码中就有,在lvgl\env_support\cmsis-pack\LVGL.lvgl.9.2.0.pack,先进行安装。在项目中点击Manage Run-Time Environment,选择LVGL,需要勾选Essential(核心部分,必选),在不用其他的情况下,勾选Porting中的Display,使用显示的移植模板。当然还可以选择File System和Input的移植模板,我是没有用到。

准备工作差不多就完成了,下面开始移植部分。

2.3 移植步骤

移植,也就是接口对接。使其代码库能在现有的硬件平台上运行。图形库,其工作就是在屏幕上显示图形,那就一定需要我们为其提供屏幕显示的接口;然后,LVGL需要我们提供类似“心跳”的功能;最后,在程序中怎么去调用,来实现屏幕显示和刷新呢?需要我们在主循环中调用lv_timer_handler()。LVGL就可以简单运行起来了。说明一下,我使用的是裸机。

屏幕驱动

屏幕驱动用于屏幕的初始化和屏幕指定区域的数据填充。

主要实现三个函数供LVGL调用。

屏幕初始化

这一步,点亮屏幕。需要根据自己用的屏幕,和所用的屏幕驱动芯片,以及MCU用到的IO进行编写。

屏幕刷新区域设置

屏幕驱动IC根据其手册,在刷新数据前先设置刷新区域,我所使用的屏幕根据起始的xy坐标和结束的xy坐标进行区域选择,这恰好也是LVGL所给出的区域参数。驱动IC有专门的指令进行区域设置,编码实现这个功能也是很简单的。

屏幕数据刷新

设置好刷新区域,紧跟着输入要显示的数据,即可实现屏幕的区域刷新。这里的数据就涉及到颜色了。我使用的屏幕支持的颜色是RGB565的,即一个点的像素使用16bit表示,R:G:B=5:6:5。刷新N个像素点,就需要发送N*2字节的数据。实际根据自己的屏幕情况,来进行数据发送。

网上有些是吧屏幕刷新区域设置和数据刷新合为一个,都是可以的。最主要就是实现区域的刷新就OK

驱动对接LVGL

驱动的对接,有个lv_port_disp_template.c文件,这个文件就是LVGL提供的移植模板,其中有个disp_init(void)的函数,在里面调用自己的屏幕初始化函数。

/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
    /*You code here*/
}

还有个区域刷新的函数,在里面调用自己的屏幕刷新函数。其中的put_px(x, y, *px_map)可以替换为自己的数据发送函数。循环前需设定刷新区域,这里没有,要自己添加。以16bit的颜色数据为例,要注意的是,最后面的数据入参是uint8_t *类型,所以在循环中发送数据时,可以每次发送1字节,一次循环发送两次,同样px_map++也要有两个。也可以在循环中发送一次2字节的数据,在循环之前,定义16bit数据的指针uint16_t* px = (uint16_t*)px_map,循环中也不再使用px_map++,而是px++了,同样,要发送的也是px所指向的数据。

/*Flush the content of the internal buffer the specific area on the display.
 *`px_map` contains the rendered image as raw pixel map and it should be copied to `area` on the display.
 *You can use DMA or any hardware acceleration to do this operation in the background but
 *'lv_display_flush_ready()' has to be called when it's finished.*/
static void disp_flush(lv_display_t * disp_drv, const lv_area_t * area, uint8_t * px_map)
{
    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;
        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, *px_map)*/
                px_map++;
            }
        }
    }

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

LVGL配置

通常,LVGL的配置使用lv_conf.h文件,使用宏定义进行参数的配置和功能的开关。使用MDK的RTE环境配置的LVGL项目,其配置文件是lv_conf_cmsis.h。配置的重要几个点,屏幕分辨率,堆大小,色彩格式,字体,使用到的组件等。

  • 屏幕分辨率
    分辨率在生成的文件中是没有定义的,可以自己添加进去。例如:
#define MY_DISP_HOR_RES    480
#define MY_DISP_VER_RES    320
  • 堆大小
    堆大小默认配置是128kB,默认使用的是LVGL内建的malloc。宏定义如下:
/* Possible values
 * - LV_STDLIB_BUILTIN:     LVGL's built in implementation
 * - LV_STDLIB_CLIB:        Standard C functions, like malloc, strlen, etc
 * - LV_STDLIB_MICROPYTHON: MicroPython implementation
 * - LV_STDLIB_RTTHREAD:    RT-Thread implementation
 * - LV_STDLIB_CUSTOM:      Implement the functions externally
 */
#define LV_USE_STDLIB_MALLOC    LV_STDLIB_BUILTIN
#define LV_USE_STDLIB_STRING    LV_STDLIB_BUILTIN
#define LV_USE_STDLIB_SPRINTF   LV_STDLIB_BUILTIN

/*Size of the memory available for `lv_malloc()` in bytes (>= 2kB)*/
    #define LV_MEM_SIZE (128 * 1024U)          /*[bytes]*/

这里我做了修改,改为LV_STDLIB_CLIB,使用标准C库的相关函数,这样LV_MEM_SIZE的定义就不起作用,需要增大startup_stm32f407xx.s中的堆空间大小,我配置的是0x20000。同时,实测LV_USE_STDLIB_SPRINTF需要使用LV_STDLIB_CLIB,label组件的格式化输出才能正确显示。

  • 色彩格式
    之前提到我用的屏幕支持RGB565的色彩,所以我这里配置只使用RGB565。如下,其他的颜色都配置为0。
#define LV_DRAW_SW_SUPPORT_RGB565		1
  • 字体
    LVGL有内置的字体文件,定义的是const数组,占用Flash空间,对于资源较少的硬件,不是很友好。字体启用在配置头文件中有如下的宏定义列表。同时需要配置默认的字体。字体这里,有一点需要注意的是,源文件需要utf-8编码,因为LVGL在对字体解码时是使用utf-8的算法,主要是针对非ASCII编码的符号。对于汉字的显示,LVGL也是可以很好支持,不过需要自己提供字体文件。我使用了字库IC,所以这里就不需要额外的字体c文件,不过要自己实现字体获取的函数,之后会单独出个文档进行LVGL加入字库IC的说明。
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
 *https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8  0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_14 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 1
#define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 0
#define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0
#define LV_FONT_MONTSERRAT_40 0
#define LV_FONT_MONTSERRAT_42 0
#define LV_FONT_MONTSERRAT_44 0
#define LV_FONT_MONTSERRAT_46 0
#define LV_FONT_MONTSERRAT_48 0

/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0  /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0  /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_14_CJK            0  /*1000 most common CJK radicals*/
#define LV_FONT_SIMSUN_16_CJK            0  /*1000 most common CJK radicals*/

/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8  0
#define LV_FONT_UNSCII_16 0

/*Optionally declare custom fonts here.
 *You can use these fonts as default font too and they will be available globally.
 *E.g. #define LV_FONT_CUSTOM_DECLARE   LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
#define LV_FONT_CUSTOM_DECLARE

/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_14
  • 组件启用
    使用不到的组件,可以不启用,以减小编译输出的大小。例如不使用table,配置#define LV_USE_TABLE 0就行了。根据自己项目需求自己配置。

LVGL初始化

经过以上的步骤,移植基本完成。再加入“心跳”,LVGL就可以运行了。我这边加到了systick的中断中:

lv_tick_inc(1); // 在中断中调用,表示增加的心跳为1ms

最后,在main()函数中,初始化时进行如下调用:

lv_init();      // LVGL初始化
lv_port_disp_init();  // 显示初始化

在大循环中调用:

lv_timer_handler();  // LVGL的定时器处理

到此,程序完成,可以编译运行啦。

posted @ 2024-10-26 18:17  河东码士  阅读(127)  评论(0编辑  收藏  举报