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

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

修改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);
}
}运行验证:

上述的显示会存在花屏幕的现象,需要进一步排查原因,本地配置未使用DMA2D加速搬运像素数据,只使用了单buff 后续继续研究提升性能。
花屏问题调查:
我们继续解决上述试验花屏的问题,初步认为是MCU 写frame buff 的时候LTDC 也在读取frame buff 数据造成的并发访问的异常问题,针对该思路本地使用DMA2D来搬运大块数据的效率会高于MCU搬运的效率,理论上虽然不能解决并发访问冲突的问题,但是会降低花屏的现象。
LTDC 使用DMA2D 搬运数据:
STM32CubeMX 配置开启DMA2D,本地LTDC配置数据格式为RGB565:

生成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

将上述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 之前花屏的更厉害了

经过几天的排查发现花屏的原因为本地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

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

我要赚赏金
