LittleVGL(LVGL) V8版本 干货入门教程一之移植到STM32并运行

LittleVGL(LVGL) V8版本 干货入门教程一之移植到STM32并运行

前言:

此文为我以前的教程“LittleVGL (LVGL)干货入门教程一之移植到stm32芯片”的LVGL V8版本入门教程,适用与LVGL的V8版本,因为V7与V8版本相差较大,所以专门出了这篇文章,以供参考。

阅读前,请确保你拥有以下条件:

  • Keil下的项目的基本创建能力。
  • stm32(或其他平台) 的开发经验。(不是这么重要,因为最低要求有画点函数就行)
  • 你已经实现了一个屏幕的驱动(至少要有画点函数,因为要对接接口)。

LVGL有三大种需要对接的API,可以互相独立使用

  1. 显示API(这个必须有,不然上lvgl就没意义了)
  2. 输入设备API(比如触摸屏、按键 等)
  3. 文件系统API(如FatFs等)

这篇文章只讲“显示API”的移植。

重要) 编译LVGL至少需要c99标准,Keil下编译需要选择AC6编译器。



一、LVGL源码下载方法

使用一个项目的前提就是:你必须获取到源码,那么你有两种方法下载到源码:
1、使用Git命令

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

2、直接从我提供的链接下载,版本为v8.0.3 dev版
LVGL v8.0.3 dev 点我下载

提醒:我推荐的使用方法是——不要增删更改任何的文件,获取到源码后保持原样,之所以这么做,是为了方便配合Git的使用,因为你可以通过Git实时获取到最新的源码,你只用添加文件到你的项目中即可。


二、认识项目目录

移植前理所应当的一件事当然是认识它的项目目录,知道每个目录是干什么的,有什么文件。

1)源码目录:
在源码目录下,所有的c文件和h文件都应该添加到你的项目中,这是最核心的东西。在src/extra/libs目录下的文件,作为第三方库使用,你以后可以使用这些第三方库,后面我会出文章讲,现在不用添加这些文件,不在本文赘述。
在这里插入图片描述
2)example目录:
这个目录也很重要,你可以在里面学习和使用里面的例程,但是在本文基础教程中,我们只使用以下红圈的文件:
在这里插入图片描述


三、 开始移植

好,现在我们已经简单的认识了一些移植必要的文件,那么现在可以开始移植了。
1)添加文件到项目

  1. 将所有src目录下的文件,按以上图片添加到你的项目
  2. 其中 lv_conf_template.h 文件名改为 lv_conf.h 并移动到上级目录。(必须)

port文件改名
Q:为什么要改文件名?
A:因为默认名有_tamplate后缀,当然不改也能用,编译器也能编,但代码是人看的,改掉比较好,把后缀_tamplate删除,文件内也要改。
默认名:
在这里插入图片描述
改后名:
在这里插入图片描述
使能port的使用 (重要

Q:如何使能?
A:在port文件中把“#if 0”改为“#if 1”,c文件和h文件都有这个,其实就是预处理。

整理好后的文件结构大概如下, 可以大致参考一下:
在这里插入图片描述

那么现在你可以尝试编译一下项目了,看看是否有报错,如找不到头文件、变量这类错误,请自行检查是否完全正确添加以上文件到项目。文件的添加较为繁琐,请仔细添加,并正确索引头文件目录。

2)显示API移植(重要)
首先,我们进入 “lv_port_disp.h” 文件,在注释块“MACROS”下添加👇

// 定义你的屏幕尺寸
#define SCREEN_HEIGHT 135  // 屏幕像素高度
#define SCREEN_WIDTH  240  // 屏幕像素宽度

现在找到函数 lv_port_disp_init,并在 “lv_port_disp.h” 文件中添加函数声明,他默认没声明。然后看我以下的改法👇,懒得改可以直接复制。

void lv_port_disp_init(void)
{
    /*-------------------------
     * Initialize your display
     * -----------------------*/
    // 这里进行你的屏幕初始化
    disp_init();

    /*-----------------------------
     * Create a buffer for drawing
     *----------------------------*/

    /**
     * LVGL requires a buffer where it internally draws the widgets.
     * Later this buffer will passed to your display driver's `flush_cb` to copy its content to your display.
     * The buffer has to be greater than 1 display row
     *
     * There are 3 buffering configurations:
     * 1. Create ONE buffer:
     *      LVGL will draw the display's content here and writes it to your display
     *
     * 2. Create TWO buffer:
     *      LVGL will draw the display's content to a buffer and writes it your display.
     *      You should use DMA to write the buffer's content to the display.
     *      It will enable LVGL to draw the next part of the screen to the other buffer while
     *      the data is being sent form the first buffer. It makes rendering and flushing parallel.
     *
     * 3. Double buffering
     *      Set 2 screens sized buffers and set disp_drv.full_refresh = 1.
     *      This way LVGL will always provide the whole rendered screen in `flush_cb`
     *      and you only need to change the frame buffer's address.
     */
	 
	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 =  ST7735_WIDTH;
    disp_drv.ver_res = ST7735_HEIGHT;

    /*Used to copy the buffer's content to the display*/
    // disp_flush的函数实现看下面
    disp_drv.flush_cb = disp_flush;

    // 这里是LVGL画面渲染所使用的缓存空间分配,总共有三种方式
    // 你也可以改为malloc分配空间
#define BUFFER_METHOD 0
#if BUFFER_METHOD == 0
    /* Example for 1) */
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[ST7735_WIDTH * 10];                          /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, ST7735_WIDTH * 10);   /*Initialize the display buffer*/
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;
#elif
    /* Example for 2) */
    static lv_disp_draw_buf_t draw_buf_dsc_2;
    static lv_color_t buf_2_1[ST7735_WIDTH * 10];                        /*A buffer for 10 rows*/
    static lv_color_t buf_2_2[ST7735_WIDTH * 10];                        /*An other buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, ST7735_WIDTH * 10);   /*Initialize the display buffer*/
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_2;
#else
    /* Example for 3) also set disp_drv.full_refresh = 1 below*/
    static lv_disp_draw_buf_t draw_buf_dsc_3;
    static lv_color_t buf_3_1[ST7735_WIDTH * ST7735_HEIGHT];            /*A screen sized buffer*/
    static lv_color_t buf_3_2[ST7735_WIDTH * ST7735_HEIGHT];            /*An other screen sized buffer*/
    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, ST7735_WIDTH * ST7735_HEIGHT);   /*Initialize the display buffer*/
    /*Required for Example 3)*/
    disp_drv.full_refresh = 1
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_3;
#endif // 0

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    /* Fill a memory array with a color if you have GPU.
     * Note that, in lv_conf.h you can enable GPUs that has built-in support in LVGL.
     * But if you have a different GPU you can use with this callback.*/
    //disp_drv.gpu_fill_cb = gpu_fill;

    /*Finally register the driver*/
    lv_disp_drv_register(&disp_drv);
}

提醒:disp_drv.hor_res和disp_drv.ver_res的值要记得指定,不然它渲染的尺寸是不正确的,要和你的屏幕一样

disp_drv.hor_res = ST7735_WIDTH;
disp_drv.ver_res = ST7735_HEIGHT;

接下来找到函数 disp_flush,这个函数是用来刷新显示区域的,速度越快越好。

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*/

    lv_coord_t x, y;
    
    lv_coord_t x1 = area->x1;
    lv_coord_t y1 = area->y1;
    lv_coord_t x2 = area->x2;
    lv_coord_t y2 = area->y2;
    
    // 设置显示区域,函数是你自己实现的,这是我改后的
    // 例如ST7735的驱动就可以设置显示绘制区域
    set_region( x1, y1, x2, y2 );
    
    for(y = y1; y <= y2; y++) {
        for(x = x1; x <= x2; x++) {
            /* Put a pixel to the display. For example: */
            /* put_px(x, y, *color_p)*/
            // 画点函数,例如常见的一些屏用的是16位颜色,你把16位数据输出到屏幕即可
            send_pixel_dat( color_p->full );
            color_p++;
        }
    }

    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    // 这个很重要,不用改也不用删
    lv_disp_flush_ready(disp_drv);
}

“显示API” 的移植工作到此告一段落,那么接下来,可以开始运行LVGL了。


四、启动LVGL

启动LVGL需要调用以下函数👇

1)启动

lv_init();                  // lvgl初始化,如果这个没有初始化,那么下面的初始化会崩溃
lv_port_disp_init();        // 显示器初始化
lv_port_indev_init();       // 输入设备初始化(如果没有实现就注释掉)
lv_port_fs_init();          // 文件系统设备初始化(如果没有实现就注释掉)

当然,这样还是无法使用LVGL,因为它没有 “心跳”。

2)让它心跳
让它心跳需要调用两个函数

lv_tick_inc(LVGL_TICK);		// tick 提供时钟,LVGL_TICK 一般为 5ms 即可
lv_timer_handler(); 		// 运行所有lvgl的timer

有两种方式让它心跳👇
1、不使用OS(裸机)

#include <lvgl.h>
#include "lv_port_disp.h"

#define LVGL_TICK 	5

static void lvgl_init( void ) 
{
    lv_init();
    lv_port_disp_init();        // 显示器初始化
    // lv_port_indev_init();       // 输入设备初始化
    // lv_port_fs_init();          // 文件系统设备初始化
}

int main()
{
	lvgl_init();

	while(1) {
		// 先调用 lv_tick_inc 再调用 lv_timer_handler
		lv_tick_inc(LVGL_TICK);
		lv_timer_handler();
		HAL_Delay(LVGL_TICK);
	}
}

2、使用OS(以STM32下的 CMSIS-FreeRTOS 为例)
需要注意的一点是,lv_tick_inc 的优先级必须高于 lv_timer_handler
以下代码较长,想要测试的,可以在文章末尾获取例程。

#include <lv_port_disp.h>
#include <FreeRTOS.h>
#include <lvgl.h>

#define LVGL_TICK 5	// ms

/* lvgl_tick 任务 */
static osThreadId_t lvgl_tick_task_handle;
const osThreadAttr_t lvgl_tick_task_attrs  = {
  .name = "lvgl_tick",
  .stack_size = 1024,
  .priority = (osPriority_t) osPriorityLow1,
};
static void lvgl_tick_task()
{
	while(1) {
		lv_tick_inc(LVGL_TICK);
		osDelay(LVGL_TICK);
	}
}

/* lvgl 任务 */
static osThreadId_t lvgl_task_handle;
const osThreadAttr_t lvgl_task_attrs  = {
  .name = "lvgl",
  .stack_size = 4096,
  .priority = (osPriority_t) osPriorityLow,
};
static void lvgl_task()
{
	while(1) {
		lv_timer_handler();
		osDelay(LVGL_TICK);
	}
}

static void lvgl_task_init()
{
	lvgl_tick_task_handle = osThreadNew(lvgl_tick_task, NULL, &lvgl_tick_task_attrs);
	lvgl_task_handle = osThreadNew(lvgl_task, NULL, &lvgl_task_attrs);
}

static void lv_ex_label(void)
{
	static char* github_addr = "Hotakus/stm32f405_lvgl_example"";
	lv_obj_t * label = lv_label_create(lv_scr_act());
    lv_label_set_recolor(label, true);
    lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/
    lv_obj_set_width(label, 120);
    lv_label_set_text_fmt(label, "#ff0000 Github: %s#", github_addr);
    lv_obj_align(label, LV_ALIGN_CENTER, 0, 10);
	
    lv_obj_t * label2 = lv_label_create(lv_scr_act());
    lv_label_set_recolor(label2, true);
    lv_label_set_long_mode(label2, LV_LABEL_LONG_SCROLL_CIRCULAR); /*Circular scroll*/
    lv_obj_set_width(label2, 120);
    lv_label_set_text_fmt(label2, "#ff0000 Hello# #00ff00 world ! Trisuborn.#");
    lv_obj_align(label2, LV_ALIGN_CENTER, 0, -10);
}

static void lvgl_init()
{
	lv_init();

	/* port文件初始化 */
    lv_port_disp_init();        // 显示器初始化

    /* 简单例程 */
	lv_ex_label();

	/* 创建LVGL相关任务 */
	lvgl_task_init();
}

int main()
{
	HAL_Init();
  	SystemClock_Config();

	/* Init scheduler */
  	osKernelInitialize();  /* Call init function for freertos objects (in freertos.c) */
  	MX_FREERTOS_Init();
  	lvgl_init();
  	/* Start scheduler */
  	osKernelStart();
}

那么现在,如果LVGL正常运行,那么你的屏幕将会显示为白色,这是默认颜色,可以调用以下代码更改默认背景颜色👇

lv_color_t bg_color = lv_color_make(0xF8, 0x00, 0x00); 	// 分别为R G B,如 0xF8, 0x00, 0x00 为正红色
lv_obj_set_style_bg_color(lv_scr_act(), bg_color, 0); 	// 改为正红色

四、LVGL的配置文件

好,那么经历了上面的移植和使用,你应该对LVGL的启动流程与运行机制有初步理解了 ,当然,LVGL不仅如此,你甚至可以自由配置,优化LVGL。现在我们打开文件 “lv_conf.h” 来看看LVGL的配置文件有哪些是值得你注意的👇。

1)内存分配定义
找到宏定义:LV_MEM_CUSTOM ,在这里,你可以指定LV_MEM_SIZE大小的值,它将决定LVGL运行所使用的RAM大小,你也可以指定动态内存分配的函数。如果你的LVGL运行有问题,那么请先检查以下宏定义。

#define LV_MEM_CUSTOM      0
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
#  define LV_MEM_SIZE    (32U * 1024U)          /*[bytes]*/

/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#  define LV_MEM_ADR          0     /*0: unused*/
#else       /*LV_MEM_CUSTOM*/
#  define LV_MEM_CUSTOM_INCLUDE   <stdlib.h>   /*Header for the dynamic memory function*/
#  define LV_MEM_CUSTOM_ALLOC     malloc
#  define LV_MEM_CUSTOM_FREE      free
#  define LV_MEM_CUSTOM_REALLOC   realloc
#endif     /*LV_MEM_CUSTOM*/

2)底层设置

#define LV_DISP_DEF_REFR_PERIOD     30      /*[ms] 刷新周期*/
#define LV_INDEV_DEF_READ_PERIOD    300      /*[ms] 输入设备读取周期*/

/*Use a custom tick source that tells the elapsed time in milliseconds.
 *It removes the need to manually update the tick with `lv_tick_inc()`)*/
 /* 时钟源提供器,如果LV_TICK_CUSTOM==1,那么你就不用lv_tick_inc()提供时钟了 */
#define LV_TICK_CUSTOM     0
#if LV_TICK_CUSTOM
/* ↓ 这里可以指定时钟源提供器,例如STM32的HAL库的HAL_GetTick() */
#define LV_TICK_CUSTOM_INCLUDE  "stm32f4xx_hal.h"     /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (HAL_GetTick()) 
#endif   /*LV_TICK_CUSTOM*/
 /* 
  * LV_DPI_DEF 注意这里,虽然LVGL的作者说这个没这么重要,但他会严重影响到LVGL的动画效果
  * 你应该进行DPI的手动计算,例如128x128分辨率1.44英寸的屏幕,那么 DPI = ((√128*128) / 1.44) ≈ 89
  */
#define LV_DPI_DEF                  89     /*[px/inch]*/

3)监控设置
顾名思义,你可以打开这些配置,提高开发时的Debug效率

/*1: Show CPU usage and FPS count in the right bottom corner*/
#define LV_USE_PERF_MONITOR     1
/*1: Show the used memory and the memory fragmentation  in the left bottom corner
 * Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR      0

当你进行LVGL的移植配置时,以上的宏定义会直接影响到LVGL的运行效率和使用体验,所以应该仔细配置。
当然,除此之外,还有很多配置项,但几乎都是一些功能的选配,并不是那么重要,需要再看即可。


五、LVGL简单的运行效果图

lvgl


六 、LVGL例程

我提供了简单的STM32的LVGL使用例程再Github中,有需要的自取👇
STM32F405RG的LVGL例程


版权声明:本文为CSDN博主「Trisuborn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26106317/article/details/120610353

生成海报
点赞 0

Trisuborn

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

PCA9555详细学习

2022.01.27 控制寄存器和命令寄存器,及英文理解 一般8位作为地址,其中前四位时固定的,中间三位为地址配置,最后一位是读写位,即读是1,写是0 当地址

stm32的 按键 消抖

一、什么是抖动 a、较为官方的解释: 在机械按键的触点闭合和断开时,都会产生抖动,为了保证系统能正确识别按键的开关,就必须对按键的抖动进行处理。         按键的抖动对于人类来说是感