1. 屏幕驱动初始化

屏幕驱动初始化函数位于 lv_port_disp.c 这个文件。

lvgl_display_init.jpg

上图中完整屏幕驱动初始化函数内容为:

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

// ----------------------- modify by zhbi --------------------------------------------------------------------
    /* 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_1[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_1, 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_1[MY_DISP_HOR_RES * MY_DISP_VER_RES];            /*An other 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*/
// -----------------------------------------------------------------------------------------------------------
    /*-----------------------------------
     * 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*/
    // ---------- modify by zhbi ---------
    disp_drv.hor_res = 320;
    disp_drv.ver_res = 240;
    // -----------------------------------

    /*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);
}
  • 选择图形缓存模式,即使用一个缓存还是两个缓存或是使用三个缓存,这里只使用一个缓存。
  • 初始化显示驱动结构。
  • 设置屏幕分辨率尺寸,比如这里用到的屏幕为 320X240即宽 320 像素,高 240 像素。
  • 指定刷新调用函数,这里使用官方例子默认的 disp_flush()函数,这个函数也是需要我们自己去编写完善的,主要编写屏幕绘图函数。
  • 指定第一步选择的屏幕显存模式,这里选择 draw_buf_dsc_1

2. 屏幕绘图驱动对接(完善刷新函数)

屏幕绘图驱动函数位于 lv_port_disp.c 这个文件。

第一种方法:是直接向 lvgl 提供绘制单个像素的屏幕驱动接口,好处是易懂,但是刷图缓慢。
20220108200233.jpg

第二种方法:是向 lvgl 提供开辟一个屏幕缓存空间的接口,填充像素数据接口,这样好处是刷图快速,刷图帧率显著提升。图中 ili9341_display_region() 这个函数就是用于告诉屏幕需要以那个坐标为起点开辟一个多大的显示区域,然后往这个区域填充数量相等的颜色数据即可
lvgl_display_flush.jpg

第三种方法:与第二种方法相同,主要做法为将官方的双层 for 循环简化为单层 for 循环,这里就需要通过 lvgl 提供的坐标计算出需要刷新的宽和高再通过宽和高计算出总共需要刷新的像素数
20220108200046.jpg

上图中完整刷屏函数内容为:

/*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)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one*/

    int32_t x;
    int32_t y;

// --------------- add by zhbi ----------------------------------------------------------------------
    ili9341_display_region(area->y1, area->x1, (area->x2 - area->x1) + 1, (area->y2 - area->y1) + 1);
    GPIO_SetBits(ILI9341_RS_GPIO, ILI9341_RS_PIN);
// --------------------------------------------------------------------------------------------------

//     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)*/
// // --------------- add by zhbi --------------------------------------
//             // display_pixel(y, x, color_p->full);
//             // ili9341_write_data(color_p->full);
//             *(__IO uint16_t *)(ILI9341_WRITE_DATA) = color_p->full;
// // ------------------------------------------------------------------
//             color_p++;
//         }
//     }

// --------------- add by zhbi --------------------------------------
    unsigned int w = (area->x2 - area->x1 + 1);
    unsigned int h = (area->y2 - area->y1 + 1);
    unsigned int pixel_count = w * h;

    for (unsigned int i = 0; i < pixel_count; i++) {
        *(__IO uint16_t *)(ILI9341_WRITE_DATA) = color_p->full;
        color_p++;
    }
// ------------------------------------------------------------------

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

3. 配置 lvgl 节拍

因为 lvgl 需要计算刷新率以及支持动画等,所以 lvgl 需要计算时间,所以需要给 lvgl 提供一个较为稳定的运行节拍,这个节拍可以使用系统滴答定时器,也可以使用其它定时器。这里的配置如下:

timers_init(50, 1680);

void CURRENT_TIMER_IRQHANDLER()
{
    if (TIM_GetITStatus(CURRENT_TIMER, TIM_IT_Update) != RESET) {
        lv_tick_inc(1);
        TIM_ClearITPendingBit(CURRENT_TIMER, TIM_IT_Update);
    }
}

4. 调用处理函数

如果没有使用 RTOS 则在在主函数调用,如果使用 RTOS 可以在一个任务中调用该处理函数。

int main()
{
    lv_init();
    lv_port_disp_init();

    for (;;) {
        lv_task_handler();
    }
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐