目录
LVGL
LVGL是一个轻量、多功能的开源图形库。
官网:https://lvgl.io
移植要求:
16、32或64位微控制器或处理器
时钟速度>64MHz
RAM:4KB+150字节/小部件(约48KB的UI与几个屏幕)
Flash:大约100KB的LVGL(取决于启用的功能)
绘制缓冲区:>1/10屏幕大小的缓冲区用于渲染
帧缓冲区:显示控制器中至少有1个帧缓冲区,内部或外部RAM
编译器:C99或更高版本
demos LVGL官方演示代码 docs LVGL文献,解析部件的使用方法 env_support 环境支持(MDK、ESP、RTThread) examples 示例、输入输出设备接口文件 scripts LVGL手稿 src LVGL源文件 tests 官方人员的测试代码 lv_conf_template.h LVGL的剪裁文件 lvgl.h LVGL包含的头文件
LVGL移植(无操作系统)
LVGL移植第一步(基本配置):
1、裁剪LVGL库文件(保留官方demo,方便移植)
只保留LVGL文件:demos文件夹、examples文件夹、src文件夹、lv_conf_template.h文件、lvgl.h文件。
examples文件夹只保留porting文件夹(输入输出还有文件系统相关的接口文件)。
2、把lv_conf_template.h文件该为lv_conf.h。
3、打开lv_conf.h文件,修改条件编译指令,把#if 0修改成#if 1。
#if 1 /*Set it to "1" to enable content*/
4、以触摸屏实验为基础,添加定时器文件。
5、修改该目录结构:
工程目录
丨__ Middlewares文件夹
丨__ LVGL文件夹
丨__ GUI文件夹
丨__ lvgl文件夹
丨__ GUI_APP文件夹
6、复制examples文件夹、src文件夹、lv_conf_template.h文件、lvgl.h文件到lvgl文件夹。
7、复制demos文件夹到GUI_APP文件夹。(可选,用来做示例)
8、在工程中添加工程组和添加文件到工程组。
Middlewares/lvgl/example/porting 添加example/porting文件夹下的lv_port_disp_template.c和lv_port_indev_template.c文件 Middlewares/lvgl/src/core 添加src/core文件夹下的全部.c文件 Middlewares/lvgl/src/draw 添加src/draw文件夹下的全部.c文件(除了nxp_pxp、nxp_vglite、sdl和stm32_dma2d文件夹) Middlewares/lvgl/src/extra 添加src/extra文件夹下的全部.c文件 Middlewares/lvgl/src/font 添加src/font文件夹下的全部.c文件 Middlewares/lvgl/src/gpu 添加src/draw/stm32_dma2d和src/draw/sdl文件夹下的全部.c文件 Middlewares/lvgl/src/hal 添加src/hal文件夹下的全部.c文件 Middlewares/lvgl/src/misc 添加src/misc文件夹下的全部.c文件 Middlewares/lvgl/src/widgets 添加src/widgets文件夹下的全部.c文件 9、添加头文件路径。
..\..\Middlewares\LVGL\GUI ..\..\Middlewares\LVGL\GUI\lvgl ..\..\Middlewares\LVGL\GUI\lvgl\src
..\..\Middlewares\LVGL\GUI\lvgl\examples\porting 10、开启C99模式。
正常无报错有警告,如果有报错,取消勾选MicroLIB。
11、屏蔽MDK警告(慎用,非必须)。
在配置界面C/C++的Misc Controls栏中填入(不要有空格):--diag_suppress=68,111,188,223,546,1295
LVGL移植第二步(配置输出,如屏幕):
1、把lv_port_disp_template.c/h的条件编译指令#if 0修改成#if 1。
2、包含输出设备驱动头文件。(可以把它放到lv_port_disp_template.c/h文件中,默认.c中)
3、在disp_init函数中初始化屏幕设备,默认设置横屏(也可竖屏,无要求)。(如LCD_Init函数)
static void disp_init(void) { lcd_init(); // 屏幕初始化 lcd_display_dir(1); // 横屏 }
4、配置图像数据缓存模式(官方lv_port_disp_init()函数中提供三种,需要哪种就注释掉其它两种)。
第一种:单缓存模式,默认。如320*240分辨率,RGB565格式。则数组大小默认是320*10*2,默认一次10行。
第二种:双缓存模式,配合DMA。
第三种:全屏幕双缓冲,配合DMA。对SRAM占有过大,如320*240分辨率,RGB565格式。则内部SRAM数组大小默认是320*240*2*2。外部SRAM效果还不如第一种。
#ifndef MY_DISP_HOR_RES #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now. #define MY_DISP_HOR_RES 320 #endif #ifndef MY_DISP_VER_RES #warning Please define or replace the macro MY_DISP_VER_RES with the actual screen height, default value 240 is used for now. #define MY_DISP_VER_RES 240 #endif
5、设置屏幕尺寸(默认横屏)
disp_drv.hor_res = lcddev.width; disp_drv.ver_res = lcddev.height;
6、在disp_flush函数中配置打点输出(填充一块区域的颜色)。
/* 原文件内容,打点较慢 */ 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); } /* 自己修改文件内容 */ 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*/ //void lcd_color_fill(uint16_t sx, uint16_t sy, uint16_t ex, uint16_t ey, uint16_t *color); lcd_color_fill(area->x1, area->y1, area->x2, area->y2, (uint16_t)color); } /*IMPORTANT!!! *Inform the graphics library that you are ready with the flushing*/ lv_display_flush_ready(disp_drv); }
LVGL移植第三步(配置输入,如触摸屏):
1、把lv_port_indev_template.c/h的条件编译指令#if 0修改成#if 1。
2、按需裁剪输入设备(注释:触摸屏、鼠标、键盘、编码器、按键)。
3、包含输入设备驱动头文件。
4、在touchpad_init函数中初始化触摸屏。
static void touchpad_init(void) { tp_dev.init(); // 触摸屏初始化 }
5、配置触摸检测函数。
static bool touchpad_is_pressed(void) { /*Your code comes here*/ tp_dev.scan(0); // 0:屏幕扫描,1:物理坐标。如果按下会保存坐标轴,&tp_dev.x[0], &tp_dev.y[0] if(tp_dev.sta & TP_PRES_DOWN ) { return true; } return false; }
6、配置坐标获取函数。
static void touchpad_get_xy(int32_t * x, int32_t * y) { /*Your code comes here*/ (*x) = tp_dev.x[0]; (*y) = tp_dev.y[0]; }
LVGL移植第四步(配置时基,功能测试):
1、添加定时器驱动。
2、在定时器驱动.c文件中包含:#include "lvgl.h"。
3、在定时器中断函数(回调)中调用:lv_tick_inc(x);。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == BTIM_TIMX_INT) { lv_tick_inc(1); } }
4、初始化定时器时,需保证:进入中断的时间间隔 = x 毫秒。
5、在main.c文件中包含头文件。
#include "./BSP/TIMER/btim.h" #include "lvgl.h" #include "lv_port_disp_teemplate.h" #include "lv_port_indev_teemplate.h"
6、初始化定时器、LVGL库、输入输出设备。
btim_timx_int_init(10-1, 9000-1); // 以F429为例,基本定时器为90MHz,1ms。 lv_init(); lv_port_disp_init(); lv_port_indev_init();
7、在while中每隔5ms调用一次lv_timer_handler();。
lv_timer_handler()函数的中断优先级比lv_tick_inc()要低,所以lv_tick_inc()放到中断里,对时基要求较高。
while(1) { delay_ms(5); lv_timer_handler(); }
8、编写测试代码(while循环前调用即可)。
lv_obj_t *switch_obj = lv_switch_create(lv_scr_act()); lv_obj_set_size(switch_obj, 120, 60); lv_obj_align(switch_obj, LV_ALIGN_CENTER, 0, 0);
LVGL移植第五步(移植压力测试demos,可选):
1、把demos文件夹复制到Middlewares/LVGL/GUI_APP路径下。
2、添加头文件路径。
..\..\Middlewares\LVGL\GUI_APP\demos ..\..\Middlewares\LVGL\GUI_APP\demos\stress
3、打开lv_conf.h文件,找到宏定义LV_USE_DEMO_STRESS并设置为1。
4、新建Middlewares/LVGL/GUI_APP工程组,添加demos/stress文件夹下的lv_demo_stress.c文件。
5、main.c文件里包含头文件:#include "lv_demo_stress.h"。
6、初始化官方demo:lv_demo_stress();。
LVGL移植第六步(移植音乐播放器demos,可选):
1、把demos文件夹复制到Middlewares/LVGL/GUI_APP路径下。
2、添加头文件路径。
..\..\Middlewares\LVGL\GUI_APP\demos ..\..\Middlewares\LVGL\GUI_APP\demos\music
3、打开lv_conf.h文件,找到宏定义LV_USE_DEMO_MUSIC并设置为1。
4、新建Middlewares/LVGL/GUI_APP工程组,添加demos/music文件夹下的全部.c文件。
5、此时编译可能会报错,打开lv_conf.h文件,找到相应字体的宏并设置为1即可。
#define LV_FONT_MONTSERRAT_12 1 #define LV_FONT_MONTSERRAT_14 1 #define LV_FONT_MONTSERRAT_16 1
6、main.c文件里包含头文件:#include "lv_demo_music.h"。
7、初始化官方demo:lv_demo_music();。
LVGL移植(带操作系统)
在lv_conf.h文件中配置自定义时钟源,删除定时器提供时基的部分代码。
#define LV_TICK_CUSTOM 1 #if LV_TICK_CUSTOM #define LV_TICK_CUSTOM_INCLUDE "FreeRTOS.h" #define LV_TICK_CUSTOM_SYS_TIME_EXPR (xTaskGetTickCount()) #endif /*LV_TICK_CUSTOM*/
//btim_timx_int_init(10-1, 9000-1); // 以F429为例,基本定时器为90MHz,1ms。
编译时内存不够如何处理?
1、修改lv_conf.h,适当减小分配给LVGL管理的内存。
2、lv_port_disp_template.c,适当减小图形缓冲区的大小,同时需要兼顾运行效果。
3、FreeRTOSConfig.h,适当减小分配给FreeRTOS的内存,简单的工程,一般10~20k就够用了。
LVGL移植(外部SRAM)
Tips:内部SRAM够用的情况下不建议使用外部SRAM,因为会影响运行效果。
外部SRAM的使用场景:
场景1:把LVGL管理的内存空间放到外部SRAM(非常不推荐,因为一些小控件就是在LVGL管理的内存空间里进行分配的,放到外部SRAM中速度会变慢,会导致LVGL运行效果明显变差)
1、确定外部SRAM首地址,根据需求确定地址偏移。
2、在lv_conf.h中将LV_MEM_ADR定义到外部SRAM的地址(不一定是首地址)。
#define Bank5_SDRAM_ADDR ((uint32_t)(0xC0000000)) /* SDRAM开始地址 */ /* lv_conf.h */ /* MEMORY SETTING */ #define LV_MEM_ADR (0xC0000000 + 1280*800*2) /*0:unused*/
场景2:把绘图缓冲区放到外部SRAM(内部空间匮乏时可取)
1、确定外部SRAM首地址,根据需求确定地址偏移。
2、在lv_port_disp_template.c中创建全屏分辨率大小的数组,并将其定位到外部SRAM的地址。
#define Bank5_SDRAM_ADDR ((uint32_t)(0xC0000000)) /* SDRAM开始地址 */ /* 在lv_port_disp_template.c中,lv_port_disp_init函数中有定义了绘图缓冲区,需要注释掉 */ //static lv_color_t buf_1[800 * 60]; /* 在lv_port_disp_template.c中的开头加入内容 */ /* 为什么1280*800*2,因为这个工程有个帧缓存数组占用了0xC0000000,1280*800大小,16bit */ /* STATIC VARIABLES */ static lv_color_t buf_1[800 * 480] __attribute__((at(0xC0000000 + 1280*800*2))); /* 假如需要双缓冲区,可定义下面,lv_color_t类型为16位 */ static lv_color_t buf_2[800 * 480] __attribute__((at(0xC0000000 + 1280*800*2 + 800*480*2)));
LVGL移植(内存管理算法)
Tips:文中为正点原子自研内存管理算法。
移植步骤:
1、添加内存管理文件:Middlewares中添加MALLOC文件夹。
2、添加分组和.c文件:Middlewares/MALLOC--malloc.c
3、包含内存管理算法头文件:#include "./MALLOC/malloc.h"
LVGL中消耗内存的地方:
Tips:不同项目,内存分配的比例不同。一般分配给LVGL管理的内存为15~40k字节。
自研内存管理算法配置流程:
配置需要管理的内存池大小:
在malloc.h文件中通过相关的宏来定义,不同板子代码的宏定义不同。
/* mem1内存参数设定.mem1完全处于内部SRAM里面 */ #define MEM1_BLOCK_SIZE 64 #define MEM1_MAX_SIZE 40*1024 #define MEM1_ALLOC_TABLE_SIZE MEM1_MAX_SIZE / MEM1_BLOCK_SIZE /* mem2....CCM */ /* mem3....外部SRAM */
初始化需要管理的内存池:
调用my_mem_init函数对所需要管理的内存池进行初始化即可。
my_mem_init(SRAMIN);
适配自研的内存管理算法:
编写LVGL内存分配、内存释放、内存重新分配这三个函数,配置相关宏定义。
/*malloc.h*/ /** * @brief 分配内存(外部调用) * @param size:要分配的内存大小(字节) * @retval 分配到的内存首地址 */ void *lv_mymalloc(uint32_t size) { return (void*)mymalloc(SRAMIN, size); } /** * @brief 释放内存(外部调用) * @param ptr:内存首地址 * @retval 无 */ void *lv_myfree(void *ptr) { myfree(SRAMIN, ptr); } /** * @brief 重新分配内存(外部调用) * @param *ptr:旧内存首地址 * @param size:要分配的内存大小(字节) * @retval 新分配到的内存首地址. */ void *lv_myrealloc(void *ptr, uint32_t size) { return (void*)myrealloc(SRAMIN, ptr, size); } /*lv_conf.h*/ /* LV_MEM_CUSTOM。如果为1则使用自研内存管理算法;为0则使用内部内存管理算法 */ #define LV_MEM_CUSTOM 1 #define LV_MEM_CUSTOM_INCLUDE "./MALLOC/malloc.h" #define LV_MEM_CUSTOM_ALLOC lv_mymalloc #define LV_MEM_CUSTOM_FREE lv_myfree #define LV_MEM_CUSTOM_REALLOC lv_myrealloc /* 在lv_port_disp_template.c中的开头加入内容 */ /* 为什么要改?因为malloc也有用到sram的地址,地址冲突了,所以这里需要lvgl使用的sram进行偏移 */ /* STATIC VARIABLES */ static lv_color_t buf_1[800 * 480] __attribute__((at(0xC0000000 + 1280*800*2 + 10*1024 + 16*4))); /* 假如需要双缓冲区,可定义下面,lv_color_t类型为16位 */ static lv_color_t buf_2[800 * 480] __attribute__((at(0xC0000000 + 1280*800*2 + 800*480*2 + 10*1024 + 16*4)));
LVGL内部内存管理配置流程:
配置需要管理的内存池大小:
通过lv_conf.h中的LV_MEM_SIZE来定义,以字节为单位。
/*lv_conf.h*/ /* LV_MEM_CUSTOM。如果为1则使用自研内存管理算法;为0则使用内部内存管理算法 */ #define LV_MEM_CUSTOM 0 #define LV_MEM_SIZE (48U * 1024U)
创建需要管理的内存池:
有3种方式:大数组、地址段、内存分配函数。
LVGL移植(DMA2D)
Tips:只针对硬件支持DMA2D的板。
1、修改lv_conf.h中相关的宏定义,开启DMA2D。
#define LV_USE_GPU_STM32_DMA2D 1 #define LV_GPU_DMA2D_CMSIS_INCLUDE "stm32f429xx.h"
2、在MDK配置中添加对应芯片的定义。
3、配置DMA2D外设。
LVGL移植(配置文件介绍)
lv_conf.h文件内容介绍:
正点原子视频中带中文注释:第12讲 基础篇-LVGL移植(配置文件介绍1)_哔哩哔哩_bilibili
序号 板块介绍 功能描述 1 颜色 颜色深度、字节交换、屏幕透明等 2 内存 内存管理算法选择、内存分配大小等 3 硬件层 显示刷新周期、输入设备读取周期等 4 特征 绘图、日志、帧率显示等 5 编译器(不重要) 大数组前缀、内存对齐等 6 字体 开启系统字体、配置自定义字体等 7 文本(不重要) 字符编码、文本特性 8 核心部件 使能/失能核心部件 9 扩展功能 使能/失能扩展部件、第三方库等 10 实例 使能/失能LVGL官方的实例
文章评论