基于littleVGL的双缓存机制,不使用GPU,改善刷新率问题
前言
在使用littleVGL作为图形库的时候,单缓冲的显示有类似拉窗帘的效果,不能用于实际产品中,littleVGL支持双缓存显示可以满足我们的显示要求
一、运行环境
当前项目使用的是MCU是STM32F429的片子,本身的资源非常丰富,使用的LTDC的外设驱动屏幕刷新,具体ltdc的外设等后面文章在记录;使用了外扩的SDRAM的前80048022字节作为显示缓冲区;屏幕800480,RGB565;
二、实现步骤
1.指定显示缓冲区
代码如下(示例):
/**
* @brief LCD显存和LVGL缓存定义
*/
uint16_t lcd_buffer_1[LCD_WIDTH * LCD_HIGH] __attribute__((at(SDRAM_LCD_ADDR_1)));
uint16_t lcd_buffer_2[LCD_WIDTH * LCD_HIGH] __attribute__((at(SDRAM_LCD_ADDR_2)));
2.指定ltdc显示buffer
代码如下(示例):pLayerCfg.FBStartAdress = (uint32_t)lcd_buffer_1;
/* LTDC init function */
void MX_LTDC_Init(void)
{
/* USER CODE BEGIN LTDC_Init 0 */
/* USER CODE END LTDC_Init 0 */
LTDC_LayerCfgTypeDef pLayerCfg = {0};
/* USER CODE BEGIN LTDC_Init 1 */
/* USER CODE END LTDC_Init 1 */
hltdc.Instance = LTDC;
hltdc.Init.HSPolarity = LTDC_HSPOLARITY_AL;
hltdc.Init.VSPolarity = LTDC_VSPOLARITY_AL;
hltdc.Init.DEPolarity = LTDC_DEPOLARITY_AL;
hltdc.Init.PCPolarity = LTDC_PCPOLARITY_IPC;
hltdc.Init.HorizontalSync = 1;
hltdc.Init.VerticalSync = 1;
hltdc.Init.AccumulatedHBP = 47;
hltdc.Init.AccumulatedVBP = 24;
hltdc.Init.AccumulatedActiveW = 847;
hltdc.Init.AccumulatedActiveH = 504;
hltdc.Init.TotalWidth = 1057;
hltdc.Init.TotalHeigh = 526;
hltdc.Init.Backcolor.Blue = 0;
hltdc.Init.Backcolor.Green = 0;
hltdc.Init.Backcolor.Red = 0;
if (HAL_LTDC_Init(&hltdc) != HAL_OK)
{
Error_Handler();
}
pLayerCfg.WindowX0 = 0;
pLayerCfg.WindowX1 = 800;
pLayerCfg.WindowY0 = 0;
pLayerCfg.WindowY1 = 480;
pLayerCfg.PixelFormat = LTDC_PIXEL_FORMAT_RGB565;
pLayerCfg.Alpha = 255;
pLayerCfg.Alpha0 = 0;
pLayerCfg.BlendingFactor1 = LTDC_BLENDING_FACTOR1_CA;
pLayerCfg.BlendingFactor2 = LTDC_BLENDING_FACTOR2_CA;
pLayerCfg.FBStartAdress = (uint32_t)lcd_buffer_1;
pLayerCfg.ImageWidth = 800;
pLayerCfg.ImageHeight = 480;
pLayerCfg.Backcolor.Blue = 0;
pLayerCfg.Backcolor.Green = 0;
pLayerCfg.Backcolor.Red = 0;
if (HAL_LTDC_ConfigLayer(&hltdc, &pLayerCfg, 0) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN LTDC_Init 2 */
/* USER CODE END LTDC_Init 2 */
}
3.配置littleVGL的初始化
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 your display drivers `flush_cb` to copy its content to your display.
* The buffer has to be greater than 1 display row
*
* There are three buffering configurations:
* 1. Create ONE buffer with some rows:
* LVGL will draw the display's content here and writes it to your display
*
* 2. Create TWO buffer with some rows:
* 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. Create TWO screen-sized buffer:
* Similar to 2) but the buffer have to be screen sized. When LVGL is ready it will give the
* whole frame to display. This way you only need to change the frame buffer's address instead of
* copying the pixels.
* */
static lv_disp_buf_t draw_buf_dsc_3;
lv_disp_buf_init(&draw_buf_dsc_3, lcd_buffer_2, lcd_buffer_1, LV_HOR_RES_MAX * LV_VER_RES_MAX);
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
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 = LV_HOR_RES_MAX;
disp_drv.ver_res = LV_VER_RES_MAX;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/
disp_drv.buffer = &draw_buf_dsc_3;
#if LV_USE_GPU
/*Optionally add functions to access the GPU. (Only in buffered mode, LV_VDB_SIZE != 0)*/
/*Blend two color array using opacity*/
disp_drv.gpu_blend_cb = gpu_blend;
/*Fill a memory array with a color*/
disp_drv.gpu_fill_cb = gpu_fill;
#endif
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
根据我们的需要创建2个显示缓冲区
3.更改lvgl刷新函数
/* Flush the content of the internal buffer the specific area on the display
* You can use DMA or any hardware acceleration to do this operation in the background but
* 'lv_disp_flush_ready()' has to be called when finished. */
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
HAL_LTDC_SetAddress_NoReload(&hltdc,(uint32_t)color_p,0);
HAL_LTDC_Reload(&hltdc,LTDC_RELOAD_VERTICAL_BLANKING);
/* IMPORTANT!!!
* Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
这里需要注意的是当lvgl需要刷新时,重新配置帧缓冲区地址而不重新加载,然后重新加载LTDC层配置, 有二中方式,一个是是立刻加载,一个是一帧结束加载,
LTDC_RELOAD_IMMEDIATE : Immediate Reload
LTDC_RELOAD_VERTICAL_BLANKING : Reload in the next Vertical Blanking
至此lvgl刷新的问题就可以解决了,测试拉帘效果消失,可以达到解决30ms一帧的显示效果,这是在没有使用DMA2D的情况下的效果,因为芯片限制,如果后面无法使用ST的片子,替换的片子不可能保证都有GPU的功能
总结
lvgl显示刷新问题基本上使用双缓冲区可以得到解决
本文介绍了如何通过littleVGL的双缓存机制,在不依赖GPU的情况下,改善STM32F429嵌入式系统的屏幕刷新率问题。详细阐述了配置显示缓冲区、ltdc设置、littleVGL初始化及刷新函数的修改步骤,最终实现了30ms一帧的显示效果。
1万+





