栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Go语言

LVGL (7) 显示对接

Go语言 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

LVGL (7) 显示对接

目录

LVGL 显示缓冲区

LVGL 显示驱动

小结

lv_disp_drv_register 分析

lv_disp_drv_init 分析

lv_disp_drv_register 分析

注意


LVGL 是 GUI 的图形绘制库,既然是图形绘制,那么就需要考虑 2 点:

  1. 开辟绘制的 buffer;
  2. 对接底层实际绘制到屏幕的驱动;

LVGL 显示缓冲区

在 LVGL v8 上,用于绘制的 buffer,使用 lv_disp_draw_buf_t 结构体进行描述:

typedef struct _lv_disp_draw_buf_t {
    void * buf1; 
    void * buf2; 

    
    void * buf_act;
    uint32_t size; 
    
    volatile int flushing;
    
    volatile int flushing_last;
    volatile uint32_t last_area         : 1; 
    volatile uint32_t last_part         : 1; 
} 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)
{
    
    disp_init();

    

    

    
    static lv_disp_draw_buf_t draw_buf_dsc_1;
    static lv_color_t buf_1[MY_DISP_HOR_RES * 10];                          
    lv_disp_draw_buf_init(&draw_buf_dsc_1, buf_1, NULL, MY_DISP_HOR_RES * 10);   

//    
//    static lv_disp_draw_buf_t draw_buf_dsc_2;
//    static lv_color_t buf_2_1[MY_DISP_HOR_RES * 10];                        
//    static lv_color_t buf_2_2[MY_DISP_HOR_RES * 10];                        
//    lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * 10);   

//    
//    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];            
//    static lv_color_t buf_3_2[MY_DISP_HOR_RES * MY_DISP_VER_RES];            
//    lv_disp_draw_buf_init(&draw_buf_dsc_3, buf_3_1, buf_3_2, MY_DISP_VER_RES * LV_VER_RES_MAX);   

............
}

代码中可以看到,有 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 来表示一个显示驱动,它的定义如下:

typedef struct _lv_disp_drv_t {

    lv_coord_t hor_res;         
    lv_coord_t ver_res;         

    lv_coord_t
    physical_hor_res;     
    lv_coord_t
    physical_ver_res;     
    lv_coord_t
    offset_x;             
    lv_coord_t offset_y;             

    
    lv_disp_draw_buf_t * draw_buf;

    uint32_t direct_mode : 1;        
    uint32_t full_refresh : 1;       
    uint32_t sw_rotate : 1;          
    uint32_t antialiasing : 1;       
    uint32_t rotated : 2;            
    uint32_t screen_transp : 1;      

    uint32_t dpi : 10;              

    
    void (*flush_cb)(struct _lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

    
    void (*rounder_cb)(struct _lv_disp_drv_t * disp_drv, lv_area_t * area);

    
    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);


    
    void (*monitor_cb)(struct _lv_disp_drv_t * disp_drv, uint32_t time, uint32_t px);

    
    void (*wait_cb)(struct _lv_disp_drv_t * disp_drv);

    
    void (*clean_dcache_cb)(struct _lv_disp_drv_t * disp_drv);

    
    void (*drv_update_cb)(struct _lv_disp_drv_t * disp_drv);

    
    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; 
#endif

} lv_disp_drv_t;

在 lv_port_disp_init 函数中,会调用 lv_disp_drv_init 函数,来进行显示驱动部分的初始化:

void lv_port_disp_init(void)
{
.........

    

    static lv_disp_drv_t disp_drv;                         
    lv_disp_drv_init(&disp_drv);                    

    

    
    
    disp_drv.hor_res = 128;
    disp_drv.ver_res = 128;

    
    disp_drv.flush_cb = disp_flush;

    
    disp_drv.draw_buf = &draw_buf_dsc_1;

    
    //disp_drv.full_refresh = 1

    
    //disp_drv.gpu_fill_cb = gpu_fill;

    
    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)
{
    

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

//    LCD_Fill(area->x1, area->y1, area->x2, area->y2, *((uint16_t *)color_p));

    
    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;
    }

    
    // 这里没有指定 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; 
    
     // 创建一个用于 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); 
    disp->top_layer = lv_obj_create(NULL); 
    disp->sys_layer = lv_obj_create(NULL); 
    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; 
    if(disp_def == NULL) disp_def = disp; 

    lv_timer_ready(disp->refr_timer); 

    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 用于清理与显示相关的任何缓存的回调。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/991399.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号