目录
LVGL 是 GUI 的图形绘制库,既然是图形绘制,那么就需要考虑 2 点:
- 开辟绘制的 buffer;
- 对接底层实际绘制到屏幕的驱动;
LVGL 显示缓冲区
在 LVGL v8 上,用于绘制的 buffer,使用 lv_disp_draw_buf_t 结构体进行描述:
/**
* Structure for holding display buffer information.
*/
typedef struct _lv_disp_draw_buf_t {
void * buf1; /**< First display buffer.*/
void * buf2; /**< Second display buffer.*/
/*Internal, used by the library*/
void * buf_act;
uint32_t size; /*In pixel count*/
/*1: flushing is in progress. (It can't be a bit field because when it's cleared from IRQ Read-Modify-Write issue might occur)*/
volatile int flushing;
/*1: It was the last chunk to flush. (It can't be a bit field because when it's cleared from IRQ Read-Modify-Write issue might occur)*/
volatile int flushing_last;
volatile uint32_t last_area : 1; /*1: the last area is being rendered*/
volatile uint32_t last_part : 1; /*1: the last part of the current area is being rendered*/
} lv_disp_draw_buf_t;
绘制缓冲区是 LVGL 用来渲染屏幕内容的简单数组。 一旦渲染准备就绪,绘制缓冲区的内容将使用显示驱动程序中设置的 flush_cb 函数发送到显示器;
在 lv_port_disp.c 文件中 (xxx_port_xxx 这种文件,都是需要根据实际的芯片、板卡、和外设来进行移植),有一个函数 lv_port_disp_init;用于初始化显示相关的内容,可以在里面进行绘制缓冲区的配置:
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[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
// /* Example for 2) */
// static lv_disp_draw_buf_t draw_buf_dsc_2;
// static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10]; /*A buffer for 10 rows*/
// static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10]; /*An other buffer for 10 rows*/
// lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10); /*Initialize the display buffer*/
// /* Example for 3) also set disp_drv.full_refresh = 1 below*/
// static lv_disp_draw_buf_t draw_buf_dsc_3;
// static lv_color_t buf_3_1[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*A screen sized buffer*/
// static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES]; /*Another screen sized buffer*/
// lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * LV_VER_RES_MAX); /*Initialize the display buffer*/
............
}
代码中可以看到,有 3 种配置,这里选了第 1 种,配置的绘图缓冲区为:MY_DISP_HOR_RES * 10;也就是 10 行数据;
个 lv_port_disp_init 函数,是 LVGL 在初始化的时候,需要显式调用的;
注意,这里的 lv_disp_draw_buf_t ,是 static 的,也就是说它是静态的,全局的变量,不能也不要被销毁;
绘制缓冲区可以小于屏幕。在这种情况下,较大的区域将被重新绘制为适合绘制缓冲区的较小部分。 如果只有一个小区域发生变化(例如按下按钮),则只会刷新该区域。
更大的缓冲区会导致更好的性能,但超过 1/10 屏幕大小的缓冲区没有显着的性能改进。 因此,建议选择绘制缓冲区的大小至少为屏幕大小的 1/10。
如果只使用一个缓冲区,LVGL 将屏幕内容绘制到该绘制缓冲区中并将其发送到显示器。 这样 LVGL 需要等到缓冲区的内容发送到显示器,然后再在其中绘制新内容。
如果使用两个缓冲区,LVGL 可以绘制到一个缓冲区中,而另一个缓冲区的内容被发送到后台显示。 应使用 DMA 或其他硬件将数据传输到显示器,让 MCU 同时绘制。 这样,显示的渲染和刷新变得并行。
LVGL 显示驱动
LVGL 使用 lv_disp_drv_t 来表示一个显示驱动,它的定义如下:
/**
* Display Driver structure to be registered by HAL.
* Only its pointer will be saved in `lv_disp_t` so it should be declared as
* `static lv_disp_drv_t my_drv` or allocated dynamically.
*/
typedef struct _lv_disp_drv_t {
lv_coord_t hor_res; /**< Horizontal resolution.*/
lv_coord_t ver_res; /**< Vertical resolution.*/
lv_coord_t
physical_hor_res; /**< Horizontal resolution of the full / physical display. Set to -1 for fullscreen mode.*/
lv_coord_t
physical_ver_res; /**< Vertical resolution of the full / physical display. Set to -1 for fullscreen mode.*/
lv_coord_t
offset_x; /**< Horizontal offset from the full / physical display. Set to 0 for fullscreen mode.*/
lv_coord_t offset_y; /**< Vertical offset from the full / physical display. Set to 0 for fullscreen mode.*/
/** Pointer to a buffer initialized with `lv_disp_draw_buf_init()`.
* LVGL will use this buffer(s) to draw the screens contents*/
lv_disp_draw_buf_t * draw_buf;
uint32_t direct_mode : 1; /**< 1: Use screen-sized buffers and draw to absolute coordinates*/
uint32_t full_refresh : 1; /**< 1: Always make the whole screen redrawn*/
uint32_t sw_rotate : 1; /**< 1: use software rotation (slower)*/
uint32_t antialiasing : 1; /**< 1: anti-aliasing is enabled on this display.*/
uint32_t rotated : 2; /**< 1: turn the display by 90 degree. @warning Does not update coordinates for you!*/
uint32_t screen_transp : 1; /**Handle if the screen doesn't have a solid (opa == LV_OPA_COVER) background.
* Use only if required because it's slower.*/
uint32_t dpi : 10; /** DPI (dot per inch) of the display. Default value is `LV_DPI_DEF`.*/
/** MANDATORY: Write the internal buffer (draw_buf) to the display. 'lv_disp_flush_ready()' has to be
* called when finished*/
void (*flush_cb)(struct _lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);
/** OPTIONAL: Extend the invalidated areas to match with the display drivers requirements
* E.g. round `y` to, 8, 16 ..) on a monochrome display*/
void (*rounder_cb)(struct _lv_disp_drv_t * disp_drv, lv_area_t * area);
/** OPTIONAL: Set a pixel in a buffer according to the special requirements of the display
* Can be used for color format not supported in LittelvGL. E.g. 2 bit -> 4 gray scales
* @note Much slower then drawing with supported color formats.*/
void (*set_px_cb)(struct _lv_disp_drv_t * disp_drv, uint8_t * buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y,
lv_color_t color, lv_opa_t opa);
void (*clear_cb)(struct _lv_disp_drv_t * disp_drv, uint8_t * buf, uint32_t size);
/** OPTIONAL: Called after every refresh cycle to tell the rendering and flushing time + the
* number of flushed pixels*/
void (*monitor_cb)(struct _lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px);
/** OPTIONAL: Called periodically while lvgl waits for operation to be completed.
* For example flushing or GPU
* User can execute very simple tasks here or yield the task*/
void (*wait_cb)(struct _lv_disp_drv_t * disp_drv);
/** OPTIONAL: Called when lvgl needs any CPU cache that affects rendering to be cleaned*/
void (*clean_dcache_cb)(struct _lv_disp_drv_t * disp_drv);
/** OPTIONAL: called when driver parameters are updated */
void (*drv_update_cb)(struct _lv_disp_drv_t * disp_drv);
/** On CHROMA_KEYED images this color will be transparent.
* `LV_COLOR_CHROMA_KEY` by default. (lv_conf.h)*/
lv_color_t color_chroma_key;
lv_draw_ctx_t * draw_ctx;
void (*draw_ctx_init)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx);
void (*draw_ctx_deinit)(struct _lv_disp_drv_t * disp_drv, lv_draw_ctx_t * draw_ctx);
size_t draw_ctx_size;
#if LV_USE_USER_DATA
void * user_data; /**< Custom display driver user data*/
#endif
} lv_disp_drv_t;
在 lv_port_disp_init 函数中,会调用 lv_disp_drv_init 函数,来进行显示驱动部分的初始化:
void lv_port_disp_init(void)
{
.........
/*-----------------------------------
* 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*/
/* NOTES : StephenZhou modified, to adapt ST7735S 128x128 Screen */
disp_drv.hor_res = 128;
disp_drv.ver_res = 128;
/*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);
}
这里可以看到,定义了一个 disp_drv 的 driver,而且是静态的;传入到 lv_disp_drv_init 函数进行最基本的结构体初始化:
void lv_disp_drv_init(lv_disp_drv_t * driver)
{
lv_memset_00(driver, sizeof(lv_disp_drv_t));
driver->hor_res = 320;
driver->ver_res = 240;
driver->physical_hor_res = -1;
driver->physical_ver_res = -1;
driver->offset_x = 0;
driver->offset_y = 0;
driver->antialiasing = LV_COLOR_DEPTH > 8 ? 1 : 0;
driver->screen_transp = LV_COLOR_SCREEN_TRANSP;
driver->dpi = LV_DPI_DEF;
driver->color_chroma_key = LV_COLOR_CHROMA_KEY;
#if LV_USE_GPU_STM32_DMA2D
driver->draw_ctx_init = lv_draw_stm32_dma2d_ctx_init;
driver->draw_ctx_deinit = lv_draw_stm32_dma2d_ctx_init;
driver->draw_ctx_size = sizeof(lv_draw_stm32_dma2d_ctx_t);
#elif LV_USE_GPU_NXP_PXP
driver->draw_ctx_init = lv_draw_nxp_pxp_init;
driver->draw_ctx_deinit = lv_draw_nxp_pxp_init;
driver->draw_ctx_size = sizeof(lv_draw_nxp_pxp_t);
#elif LV_USE_GPU_NXP_VG_LITE
driver->draw_ctx_init = lv_draw_nxp_vglite_init;
driver->draw_ctx_deinit = lv_draw_nxp_vglite_init;
driver->draw_ctx_size = sizeof(lv_draw_nxp_vglite_t);
#elif LV_USE_GPU_SDL
driver->draw_ctx_init = lv_draw_sdl_init_ctx;
driver->draw_ctx_deinit = lv_draw_sdl_deinit_ctx;
driver->draw_ctx_size = sizeof(lv_draw_sdl_ctx_t);
#else
driver->draw_ctx_init = lv_draw_sw_init_ctx;
driver->draw_ctx_deinit = lv_draw_sw_init_ctx;
driver->draw_ctx_size = sizeof(lv_draw_sw_ctx_t);
#endif
}
接着,在外部,显示的配置了 hor_res 和 ver_res(这里,我使用的屏是 128x128 的);
接着配置了 disp_drv.flush_cb = disp_flush; 这个是用于对接到 platform 的刷新数据的函数,(这个函数需要自行实现);
然后再将前面初始化完成的 draw_buf 挂接到 display 上:disp_drv.draw_buf = &draw_buf_dsc_1;
最后调用 lv_disp_drv_register,来将初始化完毕的 display driver 注册给 LVGL;
这里需要注意的是,disp_drv.flush_cb = disp_flush; 函数的移植,是需要我们自行根据屏来实现的:
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*/
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_DrawPoint(x, y, color_p->full);
color_p++;
}
}
// LCD_Fill(area->x1, area->y1, area->x2, area->y2, *((uint16_t *)color_p));
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
这里的 LCD_DrawPoint 就是自行实现的,这里使用 STM32 + ST7735S(SPI方式驱屏),所以这里的 LCD_DrawPoint 是对接的这部分驱动实现:
void LCD_DrawPoint(u16 x,u16 y,u16 color)
{
LCD_Address_Set(x,y,x,y);//设置光标位置
LCD_WriteData_16Bit(color);
}
小结
应用层调用 lv_init,在调用 lv_port_disp_init 函数,来完成 LVGL 的初始化动作:
application
|-------> lv_init()
|-------> lv_port_disp_init()
|------------------> lv_disp_draw_buf_init() 配置绘制 buffer;
|------------------> lv_disp_drv_init() 配置底层驱动,挂接绘制函数 flush_cb;
|-------------------------------> lv_disp_drv_register() 注册驱动;
lv_disp_drv_register 分析
lv_disp_drv_init 分析
要说 lv_disp_drv_register,那么就要先说在他之前调用的 lv_disp_drv_init:
void lv_disp_drv_init(lv_disp_drv_t * driver)
{
lv_memset_00(driver, sizeof(lv_disp_drv_t));
driver->hor_res = 320;
driver->ver_res = 240;
driver->physical_hor_res = -1;
driver->physical_ver_res = -1;
driver->offset_x = 0;
driver->offset_y = 0;
driver->antialiasing = LV_COLOR_DEPTH > 8 ? 1 : 0;
driver->screen_transp = LV_COLOR_SCREEN_TRANSP;
driver->dpi = LV_DPI_DEF;
driver->color_chroma_key = LV_COLOR_CHROMA_KEY;
#if LV_USE_GPU_STM32_DMA2D
driver->draw_ctx_init = lv_draw_stm32_dma2d_ctx_init;
driver->draw_ctx_deinit = lv_draw_stm32_dma2d_ctx_init;
driver->draw_ctx_size = sizeof(lv_draw_stm32_dma2d_ctx_t);
#elif LV_USE_GPU_NXP_PXP
driver->draw_ctx_init = lv_draw_nxp_pxp_init;
driver->draw_ctx_deinit = lv_draw_nxp_pxp_init;
driver->draw_ctx_size = sizeof(lv_draw_nxp_pxp_t);
#elif LV_USE_GPU_NXP_VG_LITE
driver->draw_ctx_init = lv_draw_nxp_vglite_init;
driver->draw_ctx_deinit = lv_draw_nxp_vglite_init;
driver->draw_ctx_size = sizeof(lv_draw_nxp_vglite_t);
#elif LV_USE_GPU_SDL
driver->draw_ctx_init = lv_draw_sdl_init_ctx;
driver->draw_ctx_deinit = lv_draw_sdl_deinit_ctx;
driver->draw_ctx_size = sizeof(lv_draw_sdl_ctx_t);
#else
driver->draw_ctx_init = lv_draw_sw_init_ctx;
driver->draw_ctx_deinit = lv_draw_sw_init_ctx;
driver->draw_ctx_size = sizeof(lv_draw_sw_ctx_t);
#endif
}
初始化了一些关键的结构,这里,关系最后几个:
- draw_ctx_init;
- draw_ctx_deini;
- draw_ctx_size;
我们没定义 STM32_DMA2D 以及其他的,所以默认就走了最后一个,也就是纯软件的 draw context;
void lv_disp_drv_init(lv_disp_drv_t * driver)
{
......
driver->draw_ctx_init = lv_draw_sw_init_ctx;
driver->draw_ctx_deinit = lv_draw_sw_init_ctx;
driver->draw_ctx_size = sizeof(lv_draw_sw_ctx_t);
......
}
OK,看起来只有这个 lv_draw_sw_init_ctx,这个,记住他;
lv_disp_drv_register 分析
lv_disp_t * lv_disp_drv_register(lv_disp_drv_t * driver)
{
// 首先生成一个 _lv_dis_ll 链表的头,disp
lv_disp_t * disp = _lv_ll_ins_head(&LV_GC_ROOT(_lv_disp_ll));
if(!disp) {
LV_ASSERT_MALLOC(disp);
return NULL;
}
/*Create a draw context if not created yet*/
// 这里没有指定 draw_ctx
if(driver->draw_ctx == NULL) {
// 分配一个 draw_ctx
lv_draw_ctx_t * draw_ctx = lv_mem_alloc(driver->draw_ctx_size);
LV_ASSERT_MALLOC(draw_ctx);
if(draw_ctx == NULL) return NULL;
// 调用 draw_ctx_init,也就是 lv_disp_drv_init 函数挂接的 lv_draw_sw_init_ctx
driver->draw_ctx_init(driver, draw_ctx);
// 将 draw_ctx 挂接到 drvier->draw_ctx
driver->draw_ctx = draw_ctx;
}
lv_memset_00(disp, sizeof(lv_disp_t));
// 将 driver 挂接到 disp->drvier
disp->driver = driver;
// 至此
disp 结构,已经包含的几乎所有的 port 相关的操作;
// 包含 flush_cb 即,直接绘制到屏的驱动,和平台相关;
// 包含绘制相关的接口,在 draw_ctx 中;
// 赋值给全局变量 disp_def
lv_disp_t * disp_def_tmp = disp_def;
disp_def = disp; /*Temporarily change the default screen to create the default screens on the
new display*/
/*Create a refresh timer*/
// 创建一个用于 refresh 的 lv_timer,使用默认的 LV_DISP_DEF_REFR_PERIOD=30ms 为周期,lv_timer 的回调函数为 _lv_disp_refr_timer
disp->refr_timer = lv_timer_create(_lv_disp_refr_timer, LV_DISP_DEF_REFR_PERIOD, disp);
LV_ASSERT_MALLOC(disp->refr_timer);
if(disp->refr_timer == NULL) {
lv_mem_free(disp);
return NULL;
}
// 判断分配的 buffer 是否可以支持全刷新模式
if(driver->full_refresh && driver->draw_buf->size < (uint32_t)driver->hor_res * driver->ver_res) {
driver->full_refresh = 0;
LV_LOG_WARN("full_refresh requires at least screen sized draw buffer(s)");
}
// 设置背景颜色为白色
disp->bg_color = lv_color_white();
// 设置不透明度
#if LV_COLOR_SCREEN_TRANSP
disp->bg_opa = LV_OPA_TRANSP;
#else
disp->bg_opa = LV_OPA_COVER;
#endif
#if LV_USE_THEME_DEFAULT
//
设置 theme
if(lv_theme_default_is_inited() == false) {
disp->theme = lv_theme_default_init(disp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
LV_THEME_DEFAULT_DARK, LV_FONT_DEFAULT);
}
else {
disp->theme = lv_theme_default_get();
}
#endif
//
创建系统的 3 个 screen
disp->act_scr = lv_obj_create(NULL); /*Create a default screen on the display*/
disp->top_layer = lv_obj_create(NULL); /*Create top layer on the display*/
disp->sys_layer = lv_obj_create(NULL); /*Create sys layer on the display*/
lv_obj_remove_style_all(disp->top_layer);
lv_obj_remove_style_all(disp->sys_layer);
lv_obj_clear_flag(disp->top_layer, LV_OBJ_FLAG_CLICKABLE);
lv_obj_clear_flag(disp->sys_layer, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_scrollbar_mode(disp->top_layer, LV_SCROLLBAR_MODE_OFF);
lv_obj_set_scrollbar_mode(disp->sys_layer, LV_SCROLLBAR_MODE_OFF);
lv_obj_invalidate(disp->act_scr);
disp_def = disp_def_tmp; /*Revert the default display*/
if(disp_def == NULL) disp_def = disp; /*Initialize the default display*/
lv_timer_ready(disp->refr_timer); /*Be sure the screen will be refreshed immediately on start up*/
return disp;
}
lv_draw_sw_init_ctx 函数实现,其中包含了绘制 rect,line ,background 等等;
void lv_draw_sw_init_ctx(lv_disp_drv_t * drv, lv_draw_ctx_t * draw_ctx)
{
LV_UNUSED(drv);
lv_draw_sw_ctx_t * draw_sw_ctx = (lv_draw_sw_ctx_t *) draw_ctx;
lv_memset_00(draw_sw_ctx, sizeof(lv_draw_sw_ctx_t));
draw_sw_ctx->base_draw.draw_arc = lv_draw_sw_arc;
draw_sw_ctx->base_draw.draw_rect = lv_draw_sw_rect;
draw_sw_ctx->base_draw.draw_bg = lv_draw_sw_bg;
draw_sw_ctx->base_draw.draw_letter = lv_draw_sw_letter;
draw_sw_ctx->base_draw.draw_img_decoded = lv_draw_sw_img_decoded;
draw_sw_ctx->base_draw.draw_line = lv_draw_sw_line;
draw_sw_ctx->base_draw.draw_polygon = lv_draw_sw_polygon;
draw_sw_ctx->base_draw.wait_for_finish = lv_draw_sw_wait_for_finish;
draw_sw_ctx->blend = lv_draw_sw_blend_basic;
}
相关结构体和函数的关系如下所示:
如图所示, lv_disp_t 结构几乎包含了所有和显示相关的内容(直接或者间接的),包括 draw_buffer,flush_cb,绘制接口,refresh timer,和 LVGL 的几层基本 Layer;
注意
适配 lv_disp_drv_t 的时候,有些必须要指定的结构:
- 指向初始化的 lv_disp_draw_buf_t 变量的 draw_buf 指针;
- hor_res 显示器的水平分辨率(以像素为单位)
- ver_res 显示器的垂直分辨率(以像素为单位)
- flush_cb 一个回调函数,用于将缓冲区的内容复制到显示器的特定区域
lv_disp_flush_ready(&disp_drv) 需要在刷新准备好时调用。 LVGL 可能会以多个块呈现屏幕,因此多次调用 flush_cb。要查看当前是否是渲染的最后一个块,请使用 lv_disp_flush_is_last(&disp_drv)。
一些其他可选的回调,使处理单色、灰度或其他非标准 RGB 显示器更容易、更优化:
- rounder_cb 四舍五入要重绘的区域的坐标。例如。 2x2 px 可以转换为 2x8。 如果显示控制器只能刷新具有特定高度或宽度的区域(单色显示器通常为 8 像素高度),则可以使用它。
- set_px_cb 一个自定义函数来写入绘制缓冲区。如果显示器具有特殊的颜色格式,它可用于将像素更紧凑地存储在绘图缓冲区中。 (例如 1 位单色、2 位灰度等) 这样,lv_disp_draw_buf_t 中使用的缓冲区可以更小,以仅容纳给定区域大小所需的位数。请注意,使用set_px_cb 渲染比普通渲染慢。
- monitor_cb 一个回调函数,告诉我们在多长时间内刷新了多少像素。当最后一个块被渲染并发送到显示器时调用。
- clean_dcache_cb 用于清理与显示相关的任何缓存的回调。