LVGL 优化帧率技巧

前文

LVGL——PC模拟器仿真模拟+VS2017
f429 discovery开发版 LVGL移植(带操作系统)
在F429平台上尝试LVGL过程中,总结出几种优化提高帧率的方法
这里我们直接用官方测试例程 benchmark做直观的帧率展示。

未优化版本

这里我以f429 discovery开发版 LVGL移植(带操作系统)这里的源代码做初始版本。

LVGL帧率限制

首先,LVGL是有一个帧率刷新周期的宏定义,LVGL会通过LVGL内部的tick,定时去刷屏幕,也就是说该宏定义限定了LVGL刷屏帧率的上限,默认满帧33帧。

#define LV_DISP_DEF_REFR_PERIOD 30 /[ms]/

这里我们直接设10ms刷新一次,满帧100帧。

刷屏方法效率

尽量使用dma之类的刷屏,不要使用画点函数,例如以下,效率很低。

    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            put_px(x, y, *color_p)
            color_p++;
        }
    }

我们初始版本使用DMA2D刷屏。

代码优化等级

CUBEMX生成的项目默认是arm compiler v5 + -O3 ,是性能较好的配置,不过大家自己移植lvgl未使用cubemx等代码生成工具,可能为了调试方便使用了O0优化等级,这个影响很大,429平台上帧率提升高达33%,-o3需要注意一下代码优化问题,善用volatile关键字。

arm compiler v5 + -O0 权重 FPS 332
在这里插入图片描述
arm compiler v5 + -O3 权重 FPS 446

在这里插入图片描述
帧率提升很明显!

编译器版本

本人试过arm compiler V6 + lvgl + freertos(freertos 需要改用 gcc的 port文件),效果很差。

  • LVGL栈使用设置需要比V5大,V5给2k就差不多了,v6只2k会栈溢出
  • 帧率拙计,同样benchmark测试帧率,权重FPS只有100出头

arm compiler v6 + -O3 权重 FPS 181
在这里插入图片描述
尽量用arm compiler5 吧,帧率损失有点大。

LVGL显存

LVGL其实提供了三种显存类型。三种分别对应不同环境吧,都可以试试 。

单buffer

画面会被拆分成buffer的size大小,分块刷新,当只有局部刷新时,比如说点击了一个按钮按钮变高亮,那么他就只会刷局部画面。

官方推荐大小 1/10 screen size,拆分太小,刷屏接口又慢的话,不仅帧率低,还会有感人的拉窗帘。

static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * LV_VER_RES_MAX / 10];                     /*Declare a buffer for 1/10 screen size*/
lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * LV_VER_RES_MAX / 10);    /*Initialize the display buffer*/

当然buffer全屏大小最好。
arm compiler v5 + -O3 + 全屏显存 权重 FPS 500在这里插入图片描述

非全尺寸双buffer

顾名思义,两个buffer,实际上就是PING-PONG模式,这种情况适合有dma或者其他显存加速设备的芯片,在一块buffer使用dma等外设后台刷屏时,lvgl可以再前台渲染显示进另一块显存,也就是说渲染和刷屏并发执行,理论是比单显存要好的,不过跟硬件和屏幕大小有关,具体要实测。

      static lv_disp_buf_t draw_buf_dsc_2;  //两个半屏缓存
      static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * LV_VER_RES_MAX/2];                        /*A buffer for 10 rows*/
      static lv_color_t draw_buf_2_2[LV_HOR_RES_MAX * LV_VER_RES_MAX/2];                        /*An other buffer for 10 rows*/
      lv_disp_buf_init(&draw_buf_dsc_2,draw_buf_2_2, draw_buf_2_1, LV_HOR_RES_MAX *LV_VER_RES_MAX/2);   /*Initialize the display buffer*/

另外disp_flush刷屏接口也要改成中断回调的形式,如果poll死等dma1传输结束,那就没有dma不占cpu的意义了。

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*/
    int16_t w = (area->x2 - area->x1 + 1);
    int16_t h = (area->y2 - area->y1 + 1);
    uint32_t size = w * h;
   disp_drv_p = disp_drv;
  /*##-1- Configure the DMA2D Mode, Color Mode and output offset #############*/ 
   hdma2d.Init.Mode         = DMA2D_M2M;
   hdma2d.Init.ColorMode    = DMA2D_OUTPUT_RGB565;
   hdma2d.Init.OutputOffset = (LV_HOR_RES_MAX-w);     
  
   /*##-3- Foreground Configuration ###########################################*/
   hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
   hdma2d.LayerCfg[1].InputAlpha = 0xFF;
   hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565 ;
   hdma2d.LayerCfg[1].InputOffset = 0;

   hdma2d.Instance          = DMA2D; 
  
   /*##-4- DMA2D Initialization ###############################################*/
   if(HAL_DMA2D_Init(&hdma2d) != HAL_OK) 
   {
     /* Initialization Error */
     Error_Handler(); 
   }
  
     if(HAL_DMA2D_ConfigLayer(&hdma2d, 1) != HAL_OK) 
   {
     /* Initialization Error */
     Error_Handler(); 
   }
  
   /*##-5- Start DMA2D transfer ###############################################*/  
  
   if(HAL_DMA2D_Start_IT(&hdma2d, (uint32_t)color_p, (uint32_t) (hltdc.LayerCfg[0].FBStartAdress) + 2*(LV_HOR_RES_MAX *area->y1 + area->x1), w, h) != HAL_OK)
   {
     /* Initialization Error */
     Error_Handler(); 
   }
//	HAL_DMA2D_PollForTransfer(&hdma2d,100);
//    lv_disp_flush_ready( disp_drv_p);

}
void DMA2D_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2D_IRQn 0 */
  lv_disp_flush_ready( disp_drv_p);
  /* USER CODE END DMA2D_IRQn 0 */
  HAL_DMA2D_IRQHandler(&hdma2d);
  /* USER CODE BEGIN DMA2D_IRQn 1 */

  /* USER CODE END DMA2D_IRQn 1 */
}

arm compiler v5 + -O3 + 双半屏显存 权重 FPS 502 对于429开发版的屏幕而言和单全屏显存区别不大。
在这里插入图片描述

全尺寸双buffer

两个全尺寸buffer,虽然直觉看上去和非全尺寸双buffer应该区别不大,但实际上,这个是专门为带TFT驱动的MCU准备的。双全尺寸与前两种不同,在任何时候,给出的buffer都是全屏大小的buffer,而不是局部刷新只提供刷新部分的buffer窗口,这么做的的好处是刷屏,只需要更改TFT驱动的显存地址,而不需要将LVGL的buffer再搬运到TFT驱动的显存,就可完成刷屏动作。

F429带LTDC,也就是TFT驱动,理论上应当使用该类型。
不过实测帧没有提升反而大幅下降。。。。有点奇怪。

刷屏修改LTDC的显存地址, 当LTDC刷到最后一行,中断回调提示LVGL屏幕刷完。代码如下

      static lv_disp_buf_t draw_buf_dsc_2;  //两个全屏缓存
      static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * LV_VER_RES_MAX];                        /*A buffer for 10 rows*/
      static lv_color_t draw_buf_2_2[LV_HOR_RES_MAX * LV_VER_RES_MAX];                        /*An other buffer for 10 rows*/
      lv_disp_buf_init(&draw_buf_dsc_2,draw_buf_2_2, draw_buf_2_1, LV_HOR_RES_MAX *LV_VER_RES_MAX);   /*Initialize the display buffer*/
extern LTDC_HandleTypeDef hltdc;
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{

   disp_drv_p = disp_drv;
  HAL_LTDC_SetAddress(&hltdc,(uint32_t)color_p,0);
  HAL_LTDC_ProgramLineEvent(&hltdc,LV_VER_RES_MAX -1);
}
void LTDC_IRQHandler(void)
{
  /* USER CODE BEGIN LTDC_IRQn 0 */
  lv_disp_flush_ready(disp_drv_p); /* tell lvgl that flushing is done */
  /* USER CODE END LTDC_IRQn 0 */
  HAL_LTDC_IRQHandler(&hltdc);
  /* USER CODE BEGIN LTDC_IRQn 1 */
 __HAL_LTDC_DISABLE_IT(&hltdc, LTDC_IT_LI);
  /* USER CODE END LTDC_IRQn 1 */
}

总之以上在项目中三种显存模式都试一试。不同平台,帧率表现可能不太一样。

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

前文

LVGL——PC模拟器仿真模拟+VS2017
f429 discovery开发版 LVGL移植(带操作系统)
在F429平台上尝试LVGL过程中,总结出几种优化提高帧率的方法
这里我们直接用官方测试例程 benchmark做直观的帧率展示。

未优化版本

这里我以f429 discovery开发版 LVGL移植(带操作系统)这里的源代码做初始版本。

LVGL帧率限制

首先,LVGL是有一个帧率刷新周期的宏定义,LVGL会通过LVGL内部的tick,定时去刷屏幕,也就是说该宏定义限定了LVGL刷屏帧率的上限,默认满帧33帧。

#define LV_DISP_DEF_REFR_PERIOD 30 /[ms]/

这里我们直接设10ms刷新一次,满帧100帧。

刷屏方法效率

尽量使用dma之类的刷屏,不要使用画点函数,例如以下,效率很低。

    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            put_px(x, y, *color_p)
            color_p++;
        }
    }

我们初始版本使用DMA2D刷屏。

代码优化等级

CUBEMX生成的项目默认是arm compiler v5 + -O3 ,是性能较好的配置,不过大家自己移植lvgl未使用cubemx等代码生成工具,可能为了调试方便使用了O0优化等级,这个影响很大,429平台上帧率提升高达33%,-o3需要注意一下代码优化问题,善用volatile关键字。

arm compiler v5 + -O0 权重 FPS 332
在这里插入图片描述
arm compiler v5 + -O3 权重 FPS 446

在这里插入图片描述
帧率提升很明显!

编译器版本

本人试过arm compiler V6 + lvgl + freertos(freertos 需要改用 gcc的 port文件),效果很差。

  • LVGL栈使用设置需要比V5大,V5给2k就差不多了,v6只2k会栈溢出
  • 帧率拙计,同样benchmark测试帧率,权重FPS只有100出头

arm compiler v6 + -O3 权重 FPS 181
在这里插入图片描述
尽量用arm compiler5 吧,帧率损失有点大。

LVGL显存

LVGL其实提供了三种显存类型。三种分别对应不同环境吧,都可以试试 。

单buffer

画面会被拆分成buffer的size大小,分块刷新,当只有局部刷新时,比如说点击了一个按钮按钮变高亮,那么他就只会刷局部画面。

官方推荐大小 1/10 screen size,拆分太小,刷屏接口又慢的话,不仅帧率低,还会有感人的拉窗帘。

static lv_disp_buf_t disp_buf;
static lv_color_t buf[LV_HOR_RES_MAX * LV_VER_RES_MAX / 10];                     /*Declare a buffer for 1/10 screen size*/
lv_disp_buf_init(&disp_buf, buf, NULL, LV_HOR_RES_MAX * LV_VER_RES_MAX / 10);    /*Initialize the display buffer*/

当然buffer全屏大小最好。
arm compiler v5 + -O3 + 全屏显存 权重 FPS 500在这里插入图片描述

非全尺寸双buffer

顾名思义,两个buffer,实际上就是PING-PONG模式,这种情况适合有dma或者其他显存加速设备的芯片,在一块buffer使用dma等外设后台刷屏时,lvgl可以再前台渲染显示进另一块显存,也就是说渲染和刷屏并发执行,理论是比单显存要好的,不过跟硬件和屏幕大小有关,具体要实测。

      static lv_disp_buf_t draw_buf_dsc_2;  //两个半屏缓存
      static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * LV_VER_RES_MAX/2];                        /*A buffer for 10 rows*/
      static lv_color_t draw_buf_2_2[LV_HOR_RES_MAX * LV_VER_RES_MAX/2];                        /*An other buffer for 10 rows*/
      lv_disp_buf_init(&draw_buf_dsc_2,draw_buf_2_2, draw_buf_2_1, LV_HOR_RES_MAX *LV_VER_RES_MAX/2);   /*Initialize the display buffer*/

另外disp_flush刷屏接口也要改成中断回调的形式,如果poll死等dma1传输结束,那就没有dma不占cpu的意义了。

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*/
    int16_t w = (area->x2 - area->x1 + 1);
    int16_t h = (area->y2 - area->y1 + 1);
    uint32_t size = w * h;
   disp_drv_p = disp_drv;
  /*##-1- Configure the DMA2D Mode, Color Mode and output offset #############*/ 
   hdma2d.Init.Mode         = DMA2D_M2M;
   hdma2d.Init.ColorMode    = DMA2D_OUTPUT_RGB565;
   hdma2d.Init.OutputOffset = (LV_HOR_RES_MAX-w);     
  
   /*##-3- Foreground Configuration ###########################################*/
   hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
   hdma2d.LayerCfg[1].InputAlpha = 0xFF;
   hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565 ;
   hdma2d.LayerCfg[1].InputOffset = 0;

   hdma2d.Instance          = DMA2D; 
  
   /*##-4- DMA2D Initialization ###############################################*/
   if(HAL_DMA2D_Init(&hdma2d) != HAL_OK) 
   {
     /* Initialization Error */
     Error_Handler(); 
   }
  
     if(HAL_DMA2D_ConfigLayer(&hdma2d, 1) != HAL_OK) 
   {
     /* Initialization Error */
     Error_Handler(); 
   }
  
   /*##-5- Start DMA2D transfer ###############################################*/  
  
   if(HAL_DMA2D_Start_IT(&hdma2d, (uint32_t)color_p, (uint32_t) (hltdc.LayerCfg[0].FBStartAdress) + 2*(LV_HOR_RES_MAX *area->y1 + area->x1), w, h) != HAL_OK)
   {
     /* Initialization Error */
     Error_Handler(); 
   }
//	HAL_DMA2D_PollForTransfer(&hdma2d,100);
//    lv_disp_flush_ready( disp_drv_p);

}
void DMA2D_IRQHandler(void)
{
  /* USER CODE BEGIN DMA2D_IRQn 0 */
  lv_disp_flush_ready( disp_drv_p);
  /* USER CODE END DMA2D_IRQn 0 */
  HAL_DMA2D_IRQHandler(&hdma2d);
  /* USER CODE BEGIN DMA2D_IRQn 1 */

  /* USER CODE END DMA2D_IRQn 1 */
}

arm compiler v5 + -O3 + 双半屏显存 权重 FPS 502 对于429开发版的屏幕而言和单全屏显存区别不大。
在这里插入图片描述

全尺寸双buffer

两个全尺寸buffer,虽然直觉看上去和非全尺寸双buffer应该区别不大,但实际上,这个是专门为带TFT驱动的MCU准备的。双全尺寸与前两种不同,在任何时候,给出的buffer都是全屏大小的buffer,而不是局部刷新只提供刷新部分的buffer窗口,这么做的的好处是刷屏,只需要更改TFT驱动的显存地址,而不需要将LVGL的buffer再搬运到TFT驱动的显存,就可完成刷屏动作。

F429带LTDC,也就是TFT驱动,理论上应当使用该类型。
不过实测帧没有提升反而大幅下降。。。。有点奇怪。

刷屏修改LTDC的显存地址, 当LTDC刷到最后一行,中断回调提示LVGL屏幕刷完。代码如下

      static lv_disp_buf_t draw_buf_dsc_2;  //两个全屏缓存
      static lv_color_t draw_buf_2_1[LV_HOR_RES_MAX * LV_VER_RES_MAX];                        /*A buffer for 10 rows*/
      static lv_color_t draw_buf_2_2[LV_HOR_RES_MAX * LV_VER_RES_MAX];                        /*An other buffer for 10 rows*/
      lv_disp_buf_init(&draw_buf_dsc_2,draw_buf_2_2, draw_buf_2_1, LV_HOR_RES_MAX *LV_VER_RES_MAX);   /*Initialize the display buffer*/
extern LTDC_HandleTypeDef hltdc;
static void disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
{

   disp_drv_p = disp_drv;
  HAL_LTDC_SetAddress(&hltdc,(uint32_t)color_p,0);
  HAL_LTDC_ProgramLineEvent(&hltdc,LV_VER_RES_MAX -1);
}
void LTDC_IRQHandler(void)
{
  /* USER CODE BEGIN LTDC_IRQn 0 */
  lv_disp_flush_ready(disp_drv_p); /* tell lvgl that flushing is done */
  /* USER CODE END LTDC_IRQn 0 */
  HAL_LTDC_IRQHandler(&hltdc);
  /* USER CODE BEGIN LTDC_IRQn 1 */
 __HAL_LTDC_DISABLE_IT(&hltdc, LTDC_IT_LI);
  /* USER CODE END LTDC_IRQn 1 */
}

总之以上在项目中三种显存模式都试一试。不同平台,帧率表现可能不太一样。

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

生成海报
点赞 0

无人等人

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

暂无评论

发表评论

相关推荐

LVGL 优化帧率技巧

前文 LVGL——PC模拟器仿真模拟VS2017 f429 discovery开发版 LVGL移植(带操作系统) 在F429平台上尝试LVGL过程中,总结出几种优化提高帧率的方法 这里我们直接用官方测

基于STM32的指纹密码锁

设计简介: 本设计是基于单片机的指纹密码锁,主要实现以下功能: 矩阵按键输入密码,并通过按键显示*号可通过按键或手机开门密码可通过按键进行开门可通过蓝牙模块连接手机进行开门可通过指纹进

定时器触发STM32 ADC的采样转换示例

开发板:STM32F446 Nucleo开发板IDE:  keil MDK初始化配置工具:stm32cubeMx例程内容:通过定时器触发ADC规则通道及注入通道的模数转换工作。下面基于STM32CubeMx进行些必要

STM32串口发送接收数据

1.串口通信 我用的32是stm32f10x最小系统没有UART4和UART5 USART : 通用同步异步收发器 UART : 通用异步收发器 nRTS : 请求发送 nCTS : 请求接收 区别:USART指单片机的一个IO端