lvgl8.x docking screen driver

Posted by s1m0n on Fri, 18 Feb 2022 08:47:40 +0100

1. Screen driver initialization

The screen driver initialization function is located in lv_port_disp.c this file.

The contents of the full screen driver initialization function in the figure above are:

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);
}
  • Select the graphics cache mode, that is, use one cache, two caches or three caches. Only one cache is used here.
  • Initialize the display driver structure.
  • Set the screen resolution size. For example, the screen used here is 320X240, that is, 320 pixels wide and 240 pixels high.
  • Specify the refresh calling function. Here, the default disp of the official example is used_ Flush () function, which also needs to be written and improved by ourselves, mainly writing screen drawing functions.
  • Specify the screen memory mode selected in the first step, and select draw here_ buf_ dsc_ 1.

2. Screen drawing driver docking (perfect refresh function)

The screen drawing driver function is located in lv_port_disp.c this file.

The first method is to directly provide lvgl with a screen driver interface for drawing a single pixel. The advantage is easy to understand, but the drawing is slow.

The second method is to provide lvgl with an interface to open up a screen cache space and fill the pixel data interface. This has the advantage of fast image brushing and significantly improved image brushing frame rate. Ili9341 in the figure_ display_ The region () function is used to tell the screen how large the display area needs to be opened from that coordinate, and then fill this area with an equal amount of color data.

The third method is the same as the second method. The main method is to simplify the official double-layer for loop into a single-layer for loop. Here, you need to calculate the width and height to be refreshed through the coordinates provided by lvgl, and then calculate the total number of pixels to be refreshed through the width and height.

The complete screen swiping function in the figure above is as follows:

/*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. Configure lvgl beat

Because lvgl needs to calculate the refresh rate and support animation, lvgl needs to calculate the time, so it needs to provide lvgl with a relatively stable running beat. This beat can use the system tick timer or other timers. The configuration here is as follows:

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. Call processing function

If RTOS is not used, it is called in the main function. If RTOS is used, it can be invoked in a task.

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

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

Topics: stm32 LVGL