I'm having issues with the spinner not displaying in LVGL 8.1

Description

I need to display a spinner, then run a time consuming task, then stop displaying the spinner.

I have created a small piece of test code, that I wrapped in the xGuiSemaphore, just in case that was the reason the spinner was not displaying.

What MCU/Processor/Board and compiler are you using?

ESP32, ili9488

What LVGL version are you using?

LVGL 8.1

What do you want to achieve?

Display a spinner when performing time consuming task eg: drawing new screen / loading data.
Display a spinner after a menu button click before branching to a new screen, while the screen is drawing and loading.

What have you tried so far?

Migrated to LVGL 8.1 from 7, and now just cannot get the spinner to work properly in the circumstances mentioned above

Code to reproduce

I have added a RTOS task to create a spinner, then pause 5 seconds, then display the spinner for 1 second every 5 seconds as a test.
After my home screen is displayed, I start the RTOS task.

LVGL is running on Core1 of the ESP32

/**
 * Do the home screen layout ... then ...
 * (I also run the RTOS task on the same core.) 
 */
xTaskCreatePinnedToCore(vShowSpinnerTask, "ShowSpinnerTask", 2048, NULL, 2, NULL, 1);
static void vShowSpinnerTask(void *pvParameters)
{
    lv_obj_t *busy_spinner = lv_spinner_create(lv_layer_top(), 1000, 60);
    lv_obj_set_size(busy_spinner, 100, 100);
    lv_obj_align(busy_spinner, LV_ALIGN_CENTER, 0, 0);
    lv_obj_add_flag(busy_spinner, LV_OBJ_FLAG_HIDDEN);
    vTaskDelay(pdMS_TO_TICKS(5000));

  for(;;)
  {
  xSemaphoreTake(xGuiSemaphore, portMAX_DELAY);
  ESP_LOGW(TAG, "Showing Spinner ............................");
  lv_obj_clear_flag(busy_spinner, LV_OBJ_FLAG_HIDDEN);
  vTaskDelay(pdMS_TO_TICKS(1000));
  lv_obj_add_flag(busy_spinner, LV_OBJ_FLAG_HIDDEN);
  xSemaphoreGive(xGuiSemaphore);

  vTaskDelay(pdMS_TO_TICKS(5000));
  }
  vTaskDelete(NULL);
}

Result

I can see that the task is executed repeatedly, as I can se the ESP_LOGW output in the console.
The home page displays, but the spinner never shows up.

The GUI task is implemented in the following manner

/**
 * Gui task for setup of LVGL TFT and Touch drivers and buffers.
 * @note 2021-06-30 15:00 This has been modified to be compatible with LVGL 8.0
 * 
 */ 
static void guiTask(void *pvParameter)
{

    (void)pvParameter;

    loadassist_backlight_on = 1;
    xGuiSemaphore = xSemaphoreCreateMutex();

    lv_init();

    /* Initialize SPI or I2C bus used by the drivers */
    lvgl_driver_init();

    lv_color_t *buf1 = heap_caps_malloc(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    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(DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    assert(buf2 != NULL);
#else
    static lv_color_t *buf2 = NULL;
#endif

    static lv_disp_draw_buf_t disp_buf;

    uint32_t size_in_px = DISP_BUF_SIZE;

#if defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820 || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_JD79653A || defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_UC8151D

    /* Actual size in pixels, not bytes. */
    size_in_px *= 8;
#endif

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

    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.flush_cb = disp_driver_flush;

    /* 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.hor_res = CONFIG_APP_LV_DISPLAY_HOR_RES; 
    disp_drv.ver_res = CONFIG_APP_LV_DISPLAY_VER_RES;
    disp_drv.full_refresh = 1;
    disp_drv.draw_buf = &disp_buf;
    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 default application */
    create_default_application();

    while (1)
    {
        /**
         *  Delay 1 tick (assumes FreeRTOS tick is 10ms)
         *  The timing is not critical but it should be about 5 milliseconds to keep the system responsive.
         *  https://docs.lvgl.io/latest/en/html/porting/task-handler.html
         */
        vTaskDelay(pdMS_TO_TICKS(10));

        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY))
        {
            /**
             * @note if app_backlight_on is False, 
             * then don't handle any LVGL tasks, as there should be none
             */
            if (app_backlight_on)
            {
                lv_task_handler();
            }
            xSemaphoreGive(xGuiSemaphore);
        }
    }

    /* A task should NEVER return */
    free(buf1);
#ifndef CONFIG_LV_TFT_DISPLAY_MONOCHROME
    free(buf2);
#endif
    vTaskDelete(NULL);
}

Hi @wreyford,

Can I refer you to this post, hopefully this will give you and idea of how to best structure your application to achieve your goal. This was for version 7.0 but it should also work fine for version 8.

Kind Regards,

Pete

Thx, I think I have that part working. I did read through the post, and appears that is exactly what I am doing. See my GUI implementation.
Note that lv_tasks have been dropped in LVGL8.1, so I have converted all those I used in 7.0 to RTOS, and placed them all on the same core as LVGL.

My issue is with the spinner…

Hi @wreyford,

The issue is you can not call blocking functions like vTaskDelay() from within the GUI thread of execution. So if you look at my previous link, in the process_msg_q() function you will need a case to create the spinner and a case to destroy the spinner. You can then queue a message to create the spinner at the beginning of your lengthy task and then queue another message to destroy the spinner at the end of the task.

lv_tasks are indeed removed in version 8.1 you can replace that with an lv_timer instead…

I hope that makes sense?

Kind Regards,

Pete

Aha, Thx for being patient with me @pete-pjb.

I now see what you are trying to tell me.
I’m going to give it a bash, and will post my results here.

1 Like

No worries,

Hope it goes well for you.

Cheers,

Pete

Note the approach I have described also negates any need for semaphores as all LVGL activity is confined to one task…

Cheers,

Pete

I did as recommended and placed my spinner into a task with a queue to determine it’s state. This is a non blocking RTOS task.
i.e. if (xQueueReceive(xSpinnerQueue, (void *)&spinnerState, (portTickType)portMAX_DELAY) == pdTRUE) is non blocking.

void vShowSpinnerTask(void *pvParameters)
{
  int spinnerState;

  lv_obj_t *busy_spinner = lv_spinner_create(lv_layer_top(), 1000, 60);
  lv_obj_set_size(busy_spinner, 100, 100);
  lv_obj_align(busy_spinner, LV_ALIGN_CENTER, 0, 0);
  lv_obj_add_flag(busy_spinner, LV_OBJ_FLAG_HIDDEN);

  for (;;)
  {
    if (xQueueReceive(xSpinnerQueue, (void *)&spinnerState, (portTickType)portMAX_DELAY) == pdTRUE)
    {
      switch (spinnerState)
      {
      case APP_SPINNER_VISIBLE:
        lv_obj_clear_flag(busy_spinner, LV_OBJ_FLAG_HIDDEN);
        //lv_refr_now(lv_disp_get_default());
        break;
      case APP_SPINNER_INVISIBLE:
        lv_obj_add_flag(busy_spinner, LV_OBJ_FLAG_HIDDEN);
        //lv_refr_now(lv_disp_get_default());
        break;
      default:
        break;
      }
    }
  }
  /* Tasks must not attempt to return from their implementing
        function or otherwise exit.  In newer FreeRTOS port
        attempting to do so will result in an configASSERT() being
        called if it is defined.  If it is necessary for a task to
        exit then have the task call vTaskDelete( NULL ) to ensure
        its exit is clean. */
  vTaskDelete(NULL);
}

This did allow the spinner to show and spin momentarily, but then it would stop spinning, and it would appear as if the system has hanged, only to show the new screen a few seconds later.
The REAL problem, was actually ME loading a lot of data from LittleFS, to populate certain dropdowns, and set params required by the application.
I separated these from the Screen layout, and put them in their own RTOS task, and importantly, on the CORE 0, not the same core as LVGL (CORE1).
This eventually gave the desired effect.

I’m still open to input for improving my obviously poor handling of the code for optimal use of LVGL and RTOS together.