这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【STM32H7S78-DK评测】LVGL适配之LCD显示

共3条 1/1 1 跳转至

【STM32H7S78-DK评测】LVGL适配之LCD显示

工程师
2024-09-24 17:09:32   被打赏 50 分(兑奖)     打赏

简介:

         我们在之前的基础上已经适配了LTDC 的驱动,可以将framebuff 的数据刷新到屏幕上显示,在此基础上我们继续适配LVGL,LVGL 的适配网上的教程也很多在此就不重复造轮子了,LVGL 的代码相互之间的耦合没那么高,一致的重点主要是将代码加入到工程编译,并将底层的显示刷线接口对接到LCD显示就完成了移植适配过程,本地使用LVGL 8.3.10 的版本,最新的LVGL已经更新到了V9版本了,本地使用旧版本主要是为了后续使用Gui Guider(1.7.2) 生成LVGL的代码所以版本保持了一致。 

image.png


将下载的代码按照lvgl 的目录结构添加到IAR 工程:

image.png

修改lv_port_disp.c 对接lcd 显示,对应函数为disp_flash

/**
 * @file lv_port_disp_templ.c
 *
 */

/*Copy this file as "lv_port_disp.c" and set this value to "1" to enable content*/
#if 1

/*********************
 *      INCLUDES
 *********************/
#include "lv_port_disp.h"
#include <stdbool.h>

/*********************
 *      DEFINES
 *********************/
#define LCD_WIDTH  800
#define LCD_HEIGHT 480

uint16_t lcd_buff[LCD_HEIGHT][LCD_WIDTH] @ ".fb";
   
/**********************
 *      TYPEDEFS
 **********************/

/**********************
 *  STATIC PROTOTYPES
 **********************/
static void disp_init(void);

static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
//        const lv_area_t * fill_area, lv_color_t color);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

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

    /* Example for 1) */
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[LCD_WIDTH * 10];                               /*A buffer for 10 rows*/
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, LCD_WIDTH * 10);   /*Initialize the display buffer*/

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

    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 = LCD_WIDTH;
    disp_drv.ver_res = LCD_HEIGHT;

    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = disp_flush;

    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf_dsc_1;

    /*Required for Example 3)*/
    //disp_drv.full_refresh = 1;

    /* 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);
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
    /*You code here*/
}

volatile bool disp_flush_enabled = true;

/* Enable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_enable_update(void)
{
    disp_flush_enabled = true;
}

/* Disable updating the screen (the flushing process) when disp_flush() is called by LVGL
 */
void disp_disable_update(void)
{
    disp_flush_enabled = false;
}

/*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)
{
    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, *color_p)*/
                lcd_buff[y][x] = color_p->full;
                color_p++;
            }
        }
    }

    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

/*OPTIONAL: GPU INTERFACE*/

/*If your MCU has hardware accelerator (GPU) then you can use it to fill a memory with a color*/
//static void gpu_fill(lv_disp_drv_t * disp_drv, lv_color_t * dest_buf, lv_coord_t dest_width,
//                    const lv_area_t * fill_area, lv_color_t color)
//{
//    /*It's an example code which should be done by your GPU*/
//    int32_t x, y;
//    dest_buf += dest_width * fill_area->y1; /*Go to the first line*/
//
//    for(y = fill_area->y1; y <= fill_area->y2; y++) {
//        for(x = fill_area->x1; x <= fill_area->x2; x++) {
//            dest_buf[x] = color;
//        }
//        dest_buf+=dest_width;    /*Go to the next line*/
//    }
//}


#else /*Enable this file at the top*/

/*This dummy typedef exists purely to silence -Wpedantic.*/
typedef int keep_pedantic_happy;
#endif

对接好该函数后,更新lvgl_conf 本地使用RGB565 显示配置,并开启benchmark demo

/*====================
   Graphical settings
 *====================*/

/* Color depth:
 * - 1:  1 byte per pixel
 * - 8:  RGB332
 * - 16: RGB565
 * - 32: ARGB8888
 */
#define LV_COLOR_DEPTH     16 /* Can be 8 or 1. */

/* Swap the 2 bytes of RGB565 color.
 * Useful if the display has a 8 bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP   0

/*Enable features to draw on transparent background.
 *It's required if opa, and transform_* style properties are used.
 *Can be also used if the UI is above another layer, e.g. an OSD menu or video player.*//* 1: Enable screen transparency.
 * Useful for OSD or other overlapping GUIs.
 * Requires `LV_COLOR_DEPTH = 32` colors and the screen's style should be modified: `style.body.opa = ...`*/
#define LV_COLOR_SCREEN_TRANSP    0
/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER     0

/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK   1
#if LV_USE_DEMO_BENCHMARK
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/
#define LV_DEMO_BENCHMARK_RGB565A8 0
#endif

/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS      0

在freertos 任务中初始化lvgl,并调用benchmark  demo 程序

void start_task1(void *pvParameters)
{
    printf("lvgl benchmark demo started\r\n");

    //lv_port_pre_init();
    lv_init();
    lv_port_disp_init();
    //lv_port_indev_init();

    //s_lvgl_initialized = true;

    lv_demo_benchmark();

    for (;;)
    {
        lv_task_handler();
        vTaskDelay(5);
    }
}

运行验证:

20240924-170648.gif

上述的显示会存在花屏幕的现象,需要进一步排查原因,本地配置未使用DMA2D加速搬运像素数据,只使用了单buff 后续继续研究提升性能。

花屏问题调查:

我们继续解决上述试验花屏的问题,初步认为是MCU 写frame buff 的时候LTDC 也在读取frame buff 数据造成的并发访问的异常问题,针对该思路本地使用DMA2D来搬运大块数据的效率会高于MCU搬运的效率,理论上虽然不能解决并发访问冲突的问题,但是会降低花屏的现象。

LTDC 使用DMA2D 搬运数据:

STM32CubeMX 配置开启DMA2D,本地LTDC配置数据格式为RGB565:

image.png

生成DMA2D 初始化代码

/**
  * @brief DMA2D Initialization Function
  * @param None
  * @retval None
  */
static void MX_DMA2D_Init(void)
{

  /* USER CODE BEGIN DMA2D_Init 0 */

  /* USER CODE END DMA2D_Init 0 */

  /* USER CODE BEGIN DMA2D_Init 1 */

  /* USER CODE END DMA2D_Init 1 */
  hdma2d.Instance = DMA2D;
  hdma2d.Init.Mode = DMA2D_M2M;
  hdma2d.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset = 0;
  hdma2d.LayerCfg[1].InputOffset = 0;
  hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
  hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
  hdma2d.LayerCfg[1].InputAlpha = 0;
  hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA;
  hdma2d.LayerCfg[1].RedBlueSwap = DMA2D_RB_REGULAR;
  hdma2d.LayerCfg[1].ChromaSubSampling = DMA2D_NO_CSS;
  if (HAL_DMA2D_Init(&hdma2d) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_DMA2D_ConfigLayer(&hdma2d, 1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN DMA2D_Init 2 */

  /* USER CODE END DMA2D_Init 2 */

}

在上述初始化代码的基础上,添加如下测试代码验证DMA2D的功能。

#define USED_DMA2D_TEST_CODE  1

#if (1 == USED_DMA2D_TEST_CODE)
static void LL_ConvertLineToRGB(uint32_t *pSrc, uint32_t *pDst, uint32_t w,uint32_t h)
{
  /* Configure the DMA2D Mode, Color Mode and output offset */
  hdma2d.Init.Mode         = DMA2D_M2M_PFC;
  hdma2d.Init.ColorMode    = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset = 800 - w;

  /* Foreground Configuration */
  hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
  hdma2d.LayerCfg[1].InputAlpha = 0xFF;
  hdma2d.LayerCfg[1].InputColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.LayerCfg[1].InputOffset = 0;
  hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA;

  hdma2d.Instance = DMA2D;

  /* DMA2D Initialization */
  if (HAL_DMA2D_Init(&hdma2d) == HAL_OK)
  {
    if (HAL_DMA2D_ConfigLayer(&hdma2d, 1) == HAL_OK)
    {
      if (HAL_DMA2D_Start(&hdma2d, (uint32_t)pSrc, (uint32_t)pDst, w, h) == HAL_OK)
      {
        /* Polling For DMA transfer */
        (void)HAL_DMA2D_PollForTransfer(&hdma2d, 50);
      }
    }
  }
}

int32_t BSP_LCD_FillBuff( uint32_t Xpos, uint32_t Ypos, uint32_t Width, uint32_t Height, uint32_t Color)
{
  uint32_t  Xaddress;

  /* Get the rectangle start address */
  Xaddress = (hltdc.LayerCfg[0].FBStartAdress) + 2*(800*Ypos + Xpos);

  /* Fill the rectangle */
  LL_ConvertLineToRGB((uint32_t *)_acrgb565data,(uint32_t *)Xaddress, Width, Height);

  return BSP_ERROR_NONE;
}

static void LL_FillBuffer(uint32_t *pDst, uint32_t xSize, uint32_t ySize, uint32_t OffLine, uint32_t Color)
{
  /* Register to memory mode with ARGB8888 as color Mode */
  hdma2d.Init.Mode         = DMA2D_R2M;
  hdma2d.Init.ColorMode    = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset = OffLine;

  hdma2d.Instance = DMA2D;

  /* DMA2D Initialization */
  if (HAL_DMA2D_Init(&hdma2d) == HAL_OK)
  {
    if (HAL_DMA2D_Start(&hdma2d, Color, (uint32_t)pDst, xSize, ySize) == HAL_OK)
    {
      /* Polling For DMA transfer */
      (void)HAL_DMA2D_PollForTransfer(&hdma2d, 50);
    }
  }
}

调用上述接口,使用DMA2D搬运数据

    BSP_LCD_FillBuff(0,0,200,200,0);
    
    vTaskDelay(5000); 
    for (;;)
    {
        //lv_task_handler();
    BSP_LCD_FillRect(0,0,100,300,0x55aa);
    vTaskDelay(5);
    BSP_LCD_FillRect(200,0,100,200,0xaa55);
    vTaskDelay(5);
    
    BSP_LCD_FillRect(300,0,100,300,0x55aa);
    vTaskDelay(5);
    BSP_LCD_FillRect(500,0,100,200,0xaa55);
    }

运行后结果跟预期的一致说明DMA2D    已经工作并搬运数据至LTDC framebuff

image.png

将上述DMA2D   功能验证通过代码对接到LVGL 刷新LCD显示接口函数中。

extern  DMA2D_HandleTypeDef hdma2d;
static void dma2d_transfer(uint32_t data, uint16_t x, uint16_t y, uint16_t width, uint16_t height)
{
  /* Configure the DMA2D Mode, Color Mode and output offset */
  hdma2d.Init.Mode         = DMA2D_M2M_PFC;
  hdma2d.Init.ColorMode    = DMA2D_OUTPUT_RGB565;
  hdma2d.Init.OutputOffset = LCD_WIDTH - width;

  /* Foreground Configuration */
  hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
  hdma2d.LayerCfg[1].InputAlpha = 0xFF;
  hdma2d.LayerCfg[1].InputColorMode = DMA2D_OUTPUT_RGB565;
  hdma2d.LayerCfg[1].InputOffset = 0;
  hdma2d.LayerCfg[1].AlphaInverted = DMA2D_REGULAR_ALPHA;
  
  hdma2d.Instance = DMA2D;

  uint32_t fb_offset = (uint32_t)lcd_buff + (x + y * LCD_WIDTH) * 2;
   
      /* DMA2D Initialization */
  if (HAL_DMA2D_Init(&hdma2d) == HAL_OK)
  {
    if (HAL_DMA2D_ConfigLayer(&hdma2d, 1) == HAL_OK)
    {
      if (HAL_DMA2D_Start(&hdma2d, (uint32_t)data, (uint32_t)fb_offset, width, height) == HAL_OK)
      {
        /* Polling For DMA transfer */
        (void)HAL_DMA2D_PollForTransfer(&hdma2d, 50);
      }
    }
  }
}

int32_t BSP_LCD_FillBuff( uint32_t Xpos, uint32_t Ypos, uint32_t Width, uint32_t Height, uint32_t Color);

/*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)
{
#if (LV_USE_GPU_STM32_DMA2D == 0)
  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, *color_p)*/
                lcd_buff[y][x] = color_p->full;
                color_p++;
            }
        }
    }
#else
  if(disp_flush_enabled) {
    uint16_t act_x1, act_x2, act_y1, act_y2;
 
	if(area->x2 < 0 || area->y2 < 0)
        return;
 
	if(area->x1 > (LCD_WIDTH - 1) || area->y1 > (LCD_HEIGHT - 1))
        return;
 
	act_x1 = area->x1 < 0 ? 0 : area->x1;
	act_y1 = area->y1 < 0 ? 0 : area->y1;
	act_x2 = area->x2 > LCD_WIDTH - 1 ? LCD_WIDTH - 1 : area->x2;
	act_y2 = area->y2 > LCD_HEIGHT - 1 ? LCD_HEIGHT - 1 : area->y2;
 
	dma2d_transfer((uint32_t)color_p, act_x1, act_y1, act_x2 - act_x1 + 1, act_y2 - act_y1 + 1); 
  }
#endif
    /*IMPORTANT!!!
     *Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

运行验证,不出意外的果然出意外了,跑起来比未使用DMA2D 之前花屏的更厉害了

20240926-220258.gif


经过几天的排查发现花屏的原因为本地framebuff 存储在外部PSRAM上,这段RAM 在MPU 初始化时配置的属性为MPU_ACCESS_CACHEABLE & MPU_ACCESS_BUFFERABLE 造成花屏的原因为LTDC读取的物理内存,我们写framebuff 时数据并没有及时被刷新到物理内存而是先保存在cache中,知道原因后我们可以有多种方式进行修改

1   修改外部PSRAM 为MPU_ACCESS_NOT_CACHEABLE & MPU_ACCESS_NOT_BUFFERABLE 这样就不会造成上述的数据不一致问题。

2  LVGL 底层更新frame buff前disable cache 更新后在 enable cache ,这种修改方式不好的一点会造成已经在cache 中的其他数据强制丢弃,在此访问造成cahce miss 

3  划分出一段内存将frame buff 存放其中,配置为no cahce 属性,此方法跟方法1 基本时一致的。

我们按照方法一的修改修改这段内存的属性为不可cache 和 buffed

image.png

修改上述MPU属性后验证,花屏幕问题终于得到解决了。

 20240926-222019.gif



助工
2024-10-07 10:07:48     打赏
2楼

代码包?


院士
2024-10-15 15:39:34     打赏
3楼

谢谢分享,学习了。


共3条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]