简介
在上一篇帖子(【STM32H745I-DISCO GuiDemo 】GuiDemo 适配LTDC驱动)已经适配了LTDC 的驱动,可以将framebuff 的数据刷新到屏幕上显示,在此基础上我们继续适配LVGL,LVGL 的适配网上的教程也很多在此不做额外的赘述,LVGL 的代码相互之间的耦合没那么高,移植的重点主要是将代码加入到工程编译,并将底层的显示刷线接口对接到LCD显示就完成了移植适配过程,本地使用LVGL 8.3.10 的版本。
Frame_buffer 配置
开发环境本地使用的IAR,将LVGL 代码加入工程编译通过后,我们需要先对接LVGL显示相关的接口,适配显示相关的接口需要配置显存,之前的LTDC驱动程序是将Framebuffer 映射到了flash 空间,默认工程M7 使用的RAM大小为128K,屏幕为480*272 显示使用的RGB565的数据格式,一帧Framebuffer 数据大小
显示缓冲区大小 = 480 x 272 x 16 / 8 = 261120 字节 ≈ 255KB
显然内存资源是不够开发LVGL应用,修改linkfile 将SRAM1 512K空间作为,FrameBuffer 空间使用,定义section “.fb”用来分配显存空间。

添加如下代码适配LCD显示接口
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 * LCD_HEIGHT] @ ".fb"; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, LCD_WIDTH * LCD_HEIGHT); /*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);
}
添加完LTDC 的frame 操作方式后,我们需要开启FreeRtos 的tick hook 来驱动LVGL 的运行时间,cubmx 配置如下

在对应的hook函数中增加lvgl 的计数器
/* USER CODE BEGIN 3 */
void vApplicationTickHook( void )
{
/* This function will be called by each tick interrupt if
configUSE_TICK_HOOK is set to 1 in FreeRTOSConfig.h. User code can be
added here, but the tick hook is called from an interrupt context, so
code must not attempt to block, and only the interrupt safe FreeRTOS API
functions can be used (those that end in FromISR()). */
lv_tick_inc(1);
}
板子验证
LVGL TASK 中添加lvgl 的music demo 代码
/* USER CODE END Header_StartDefaultTask */
void StartDefaultTask(void *argument)
{
/* USER CODE BEGIN 5 */
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;
extern void lv_demo_music(void);
lv_demo_music();
//void lv_demo_keypad_encoder(void);
//lv_demo_keypad_encoder();
for (;;)
{
lv_task_handler();
vTaskDelay(5);
}
/* USER CODE END 5 */
}
烧写到板子后,LVGL DEMO 已经按照预期的运行起来,后续可以基于LVGL开发 GUI 应用了
