Simple and easy-to-use ESP32 lvgl display drive(esp-idf)

Hi, all

I wrote a general esp32 lvgl display drive display_100ask_drivers based on esp-idf. display_100ask_drivers works out of the box and supports two driver types: pure spi or iic or other protocol interfaces, and esp_lcd driver interface. You can switch flexibly in menuconfig. Currently, two driver interfaces, spi and esp_lcd, have been completed, and more will be added in the future! I will continue to make it easier to use, while providing better results.

Here is the LVGL demo on two sizes of SPI screens:

The usage is very simple, you can use the whole esp-idf-components as components, or extract display_100ask_driver and use it as a separate component. Either way, the final initialization code is the same:

static void guiTask(void *pvParameter)
{
    static lv_disp_draw_buf_t disp_buf;
    static lv_disp_drv_t disp_drv;      // contains callback functions

    (void)pvParameter;
    xGuiSemaphore = xSemaphoreCreateMutex();
 
    lv_init();
 
    /* Initialize display */
#if CONFIG_USE_100ASK_DISPLAY_SCREEN_ESP_LCD_PANEL
    esp_lcd_panel_handle_t panel_handle = NULL;
    panel_handle = display_100ask_hal_init(&disp_drv);
#else
    display_100ask_hal_init();
#endif

    /* Initialize key */
    //init_input_key();

    lv_color_t *buf1 = heap_caps_malloc(LV_DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
    if(buf1 == NULL)
        buf1 = malloc(LV_DISP_BUF_SIZE * sizeof(lv_color_t));
    
    assert(buf1 != NULL);
 
    /* Use double buffered when not working with monochrome displays */
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    lv_color_t *buf2 = heap_caps_malloc(LV_DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
    if (buf2 == NULL)
        buf2 = malloc(LV_DISP_BUF_SIZE * sizeof(lv_color_t));
    
    assert(buf2 != NULL);

#else
    static lv_color_t *buf2 = NULL;
#endif
 
    uint32_t size_in_px = LV_DISP_BUF_SIZE;
 
    /* Initialize the working buffer depending on the selected display.
     * NOTE: buf2 == NULL when using monochrome displays. */
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, size_in_px);
 
    lv_disp_drv_init(&disp_drv);
    disp_drv.flush_cb = display_100ask_hal_lvgl_flush;

#ifdef CONFIG_USE_100ASK_DISPLAY_SCREEN_ESP_LCD_PANEL
    disp_drv.user_data = panel_handle;
#endif
 
    /* When using a monochrome display we need to register the callbacks:
     * - rounder_cb
     * - set_px_cb */
#ifdef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    disp_drv.rounder_cb = disp_driver_rounder;
    disp_drv.set_px_cb = disp_driver_set_px;
#endif

    disp_drv.draw_buf = &disp_buf;
#if ((CONFIG_DISPLAY_SCREEN_100ASK_ROTATION == 0) || (CONFIG_DISPLAY_SCREEN_100ASK_ROTATION == 180))
    disp_drv.hor_res = CONFIG_DISPLAY_SCREEN_100ASK_WIDTH;
    disp_drv.ver_res = CONFIG_DISPLAY_SCREEN_100ASK_HEIGHT;
#elif ((CONFIG_DISPLAY_SCREEN_100ASK_ROTATION == 90) || (CONFIG_DISPLAY_SCREEN_100ASK_ROTATION == 270))
    disp_drv.hor_res = CONFIG_DISPLAY_SCREEN_100ASK_HEIGHT;
    disp_drv.ver_res = CONFIG_DISPLAY_SCREEN_100ASK_WIDTH;
#endif
    lv_disp_drv_register(&disp_drv);
 
    /* Register an input device when enabled on the menuconfig */
#if CONFIG_LV_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.read_cb = touch_driver_read;
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    lv_indev_drv_register(&indev_drv);
#endif


    /* Create and start a periodic timer interrupt to call lv_tick_inc */
    const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui",
    };
    esp_timer_handle_t periodic_timer;
    ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, LV_TICK_PERIOD_MS * 1000));
 
    /* Create the demo application */
    create_demo_application();
    
    while (1)
    {
        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */
        vTaskDelay(pdMS_TO_TICKS(10));
        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
        {
            lv_task_handler();
            xSemaphoreGive(xGuiSemaphore);
        }
    }
 
    /* A task should NEVER return */
    free(buf1);
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    free(buf2);
#endif
    vTaskDelete(NULL);
}

get display_100ask_drivers: https://github.com/100askTeam/esp-idf-components/tree/master/display_100ask_drivers

Hi, nice work! And you can use esp_lvgl_port (esp-bsp/components/esp_lvgl_port at master · espressif/esp-bsp · GitHub) for simple LCD and LVGL handling or esp_bsp (GitHub - espressif/esp-bsp: Board support components for Espressif development boards) for simple changing boards :slight_smile:

Thank you!

My intention in writing display_100ask_driver is to provide simpler, more efficient, easier to understand, and more scalable components. From a teaching perspective, first-time exposure to esp-idf can gradually deepen their understanding of more advanced usage. Once they become familiar with it, they can continue to use display_100ask_driver or other better components. It is important to enable them to learn the principles of methods more directly rather than just using APIs.

Can you elaborate a bit on why your library is “simpler” (than what exactly), for example by showing similar programs with and without it ? Is this only usable on the esp-idf framework or can it be used (and is it useful) for Arduino IDE users ? I read it and looked at it, but I don’t get the “simple” part. Maybe not simple enough :wink:

My target is Lilygo T-Display, and I’m currently using TFT-eSPI, but looking at LVGL for “simpler” and nicer GUI. Quite a newbie at UI design…

I edited the title to make it easier to understand. The goal of display_100ask_drivers is to provide an easy-to-use and powerful lvgl display driver framework…

is it possible to use this setup to drive a VGA monitor?

I have an arduinoEspidf project with , that I need to move across to native IDF. The current GL, though claims it works with IDF, doesn’t work with anything close to a modern version of IDF.

hi,
I haven’t tried it yet.