文章目录[隐藏]
LittleVGL(LVGL) V8版本 干货入门教程一之移植到STM32并运行
前言:
此文为我以前的教程“LittleVGL (LVGL)干货入门教程一之移植到stm32芯片”的LVGL V8版本入门教程,适用与LVGL的V8版本,因为V7与V8版本相差较大,所以专门出了这篇文章,以供参考。
阅读前,请确保你拥有以下条件:
- Keil下的项目的基本创建能力。
- stm32(或其他平台) 的开发经验。(不是这么重要,因为最低要求有画点函数就行)
- 你已经实现了一个屏幕的驱动(至少要有画点函数,因为要对接接口)。
LVGL有三大种需要对接的API,可以互相独立使用
- 显示API(这个必须有,不然上lvgl就没意义了)
- 输入设备API(比如触摸屏、按键 等)
- 文件系统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)添加文件到项目
- 将所有src目录下的文件,按以上图片添加到你的项目
- 其中 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例程
我提供了简单的STM32的LVGL使用例程再Github中,有需要的自取👇
STM32F405RG的LVGL例程
版权声明:本文为CSDN博主「Trisuborn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_26106317/article/details/120610353
暂无评论