Lv_timer_handler does not run within xTaskCreate function - Shows Black Screen

Hi,
I am running a code on ESP IDE for ESP32 S3 board and WT32-SC01 Plus Touch LCD.
My lvgl code used to work fine when it was a single task and I had the main_app very simple as follow:

extern "C" void app_main(void)
{
    init_lvgl_lgfx();
    ui_init();

 while (true)
    {
        lv_timer_handler(); /* let the GUI do its work */
        vTaskDelay(1 / portTICK_PERIOD_MS);
    }
}

Then I had to add other tasks with different rates so I decided to use xTaskCreate function. The code builds with no error and the other two tasks work properly( SD card logging and ESP-Now). However, lv_timer_handler() do not seem to be running (I have made sure that the while loop runs with the handler inside it with a ESP_LOGI(TAG, “The loop runs”)). LCD shows black screen.

TaskHandle_t FreeRTOSTIDs[3];
SemaphoreHandle_t rtTaskSemaphoreList[3];
void tRate1(void *pvParameter)
{


	while (1) {
	    xSemaphoreTake(rtTaskSemaphoreList[1], portMAX_DELAY);

	    /* Set model inputs associated to subrate here */

	    /* Step the model for sample time for tid */
	    SW_step1();


	    /* Get model outputs here */

	  }
}

void tRate2(void *pvParameters)
{
  while (1) {
    xSemaphoreTake(rtTaskSemaphoreList[2], portMAX_DELAY);

    /* Set model inputs associated to subrate here */

    /* Step the model for sample time for tid */

    SW_step2();

    /* Get model outputs here */

  }
}

void tBaseRate(void *pvParameters)
{
  const TickType_t vPeriodicTaskPreiod = 10/portTICK_PERIOD_MS;
  TickType_t xLastWakeTime;
  xLastWakeTime = xTaskGetTickCount();

  /* For debuging the base rate semaphore release. Ignore */
  static int taskCounter[3] = { 0, 0, 0 };

  int i;
  while (1) {
    /* Set model base rate here: */
    vTaskDelayUntil(&xLastWakeTime, vPeriodicTaskPreiod);
    for (i = 1; i < 3; i++) {
      if (taskCounter[i] == 0) {
        xSemaphoreGive(rtTaskSemaphoreList[i]);
      }
    }

    taskCounter[1]++;
    if (taskCounter[1] == 100) {
      taskCounter[1]= 0;
    }

    taskCounter[2]++;
    if (taskCounter[2] == 200) {
      taskCounter[2]= 0;
    }

    /* Set model inputs associated with base rate here */

    /* Step the model for base rate */

  lv_timer_handler(); /* let the GUI do its work */
  ESP_LOGI(TAG, "The loop runs");

    /* Get model outputs here */
  }

  vSemaphoreDelete(rtTaskSemaphoreList[1]);
  vTaskDelete(FreeRTOSTIDs[1]);
  vSemaphoreDelete(rtTaskSemaphoreList[2]);
  vTaskDelete(FreeRTOSTIDs[2]);


  /* Delete the base-rate task */
  vTaskDelete(FreeRTOSTIDs[0]);
}

extern "C" void app_main(void)
{
  /* Initialize model */

	init_lvgl_lgfx();
        ui_init();


  UBaseType_t BaseRateTaskPriority = 2;
  rtTaskSemaphoreList[1] = xSemaphoreCreateBinary();
  xTaskCreate(tRate1, "tRate1", STACK_SIZE, NULL, 1, &FreeRTOSTIDs[1]);
  rtTaskSemaphoreList[2] = xSemaphoreCreateBinary();
  xTaskCreate(tRate2, "tRate2", STACK_SIZE, NULL, 0, &FreeRTOSTIDs[2]);
  xTaskCreate(tBaseRate, "tBaseRate", STACK_SIZE, NULL, BaseRateTaskPriority,&FreeRTOSTIDs[0]);
}

/* [EOF] */

I am not really sure about the difference but I have also tried “lv_task_handler()” function. No luck!

I really appreciate if anyone could help with this issue.

Hi @msh911 ,

There is one item I can see you should probably address.

If you are using freeRTOS I believe if your app_main() function is being called by xTaskCreate() somewhere you should terminate the function with a call to vTaskDelete(), I have personally experienced much undefined behaviour if this call is ommitted!

So taking your code I would do as follows:

extern "C" void app_main(void)
{
  /* Initialize model */

	init_lvgl_lgfx();
        ui_init();


  UBaseType_t BaseRateTaskPriority = 2;
  rtTaskSemaphoreList[1] = xSemaphoreCreateBinary();
  xTaskCreate(tRate1, "tRate1", STACK_SIZE, NULL, 1, &FreeRTOSTIDs[1]);
  rtTaskSemaphoreList[2] = xSemaphoreCreateBinary();
  xTaskCreate(tRate2, "tRate2", STACK_SIZE, NULL, 0, &FreeRTOSTIDs[2]);
  xTaskCreate(tBaseRate, "tBaseRate", STACK_SIZE, NULL, BaseRateTaskPriority,&FreeRTOSTIDs[0]);
  vTaskDelete(NULL);	 // This task does it's stuff and deletes itself
}

The other item to check is whether you are still calling lv_tick_inc() function somewhere as I can not see it mentioned in your code or description. If this is not called the lv_timer_handler() routine will not function.

I hope that helps.

Kind Regards,

Pete

Hi @pete-pjb

Thanks for your response.
I did try your suggestion, Still no luck.
I do have lv_tick_inc() function that its being called within init_lvgl_lgfx().

static void init_lvgl_lgfx()
{
    lcd.init();
    lv_init();
    lcd.setRotation(1);
    lcd.setBrightness(255);
    lcd.fillScreen(TFT_BLACK);

    lv_disp_draw_buf_init(&draw_buf, buf, buf2, screenWidth * LV_BUFFER_SIZE);

    /*** LVGL : Setup & Initialize the display device driver ***/
    static lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = screenWidth;
    disp_drv.ver_res = screenHeight;
    disp_drv.flush_cb = display_flush;
    disp_drv.draw_buf = &draw_buf;
    lv_disp_drv_register(&disp_drv);

    /*** LVGL : Setup & Initialize the input device driver ***/
    static lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type = LV_INDEV_TYPE_POINTER;
    indev_drv.read_cb = touchpad_read;
    lv_indev_drv_register(&indev_drv);


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



}



/*** Display callback to flush the buffer to screen ***/
static void display_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{

    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    lcd.startWrite();
    lcd.setAddrWindow(area->x1, area->y1, w, h);
    lcd.writePixels((uint16_t *)&color_p->full, w * h, true);
    lcd.endWrite();

    lv_disp_flush_ready(disp);
}

/*** Touchpad callback to read the touchpad ***/
static void touchpad_read(lv_indev_drv_t *indev_driver, lv_indev_data_t *data)
{
    uint16_t touchX, touchY;
    bool touched = lcd.getTouch(&touchX, &touchY);

    if (!touched)
    {
        data->state = LV_INDEV_STATE_REL;
    }
    else
    {
        data->state = LV_INDEV_STATE_PR;

        /*Set the coordinates*/
        data->point.x = touchX;
        data->point.y = touchY;
    }
}

/* Setting up tick task for lvgl*/
static void lv_tick_task(void *arg)
{
    (void)arg;
    lv_tick_inc(LV_TICK_PERIOD_MS);
}

Hi @msh911 ,

Your code looks okay this is very strange!

The next checks for me would be:

Can you confirm draw_buf, buf and buf2 are all static or global?

Can you place a break point or a log output in the lv_tick_task() to confirm it’s definitely being called.

Can you confirm the ESP_LOGI(TAG, "The loop runs"); entry is being called continuously?

Kind Regards,

Pete

Hi @pete-pjb

Thanks again for following this up.

  • Yes, draw_buf, buf and buf2 are all static and global.
  • Yes, ESP_LOGI(TAG, "The loop runs"); entry is being called continuously.
  • BUT:
    I put log output for lv_tick_task() and noticed that function is not called as you were concerned.

I am confused, How can lv_timer_handler() be called continuously but not lv_tick_task().
I thought every time lv_timer_handler() is called, it will call lv_tick_task() as well.

Hi @msh911 ,

The lv_tick_inc()/lv_tick_task() function needs to be called from a periodic interrupt or a timer independently from the lv_timer_task() so the esp_timer is most likely not working… I assume the value of LV_TICK_PERIOD_MS is not set to zero also, please check.

Looking at the code carefully there may be an issue here where the variables for the timer are going out of scope as the stack is being reclaimed after the termination of app_main() function so my next thing to suggest would be to modify the timer code as follows:

/* Create and start a periodic timer interrupt to call lv_tick_inc */

    static const esp_timer_create_args_t periodic_timer_args = {
        .callback = &lv_tick_task,
        .name = "periodic_gui"};
    static 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));

By making these variables static they will be around for the life of the application and not lost after the termination of the app_main() function.

I hope that makes sense and is the problem! Let me know how you get on…

Kind Regards,

Pete

Hi @pete-pjb

Sorry for the late response and thanks for helping me again.

LV_TICK_PERIOD_MS is not set to zero and I made those variables static and still does not work.
I’ve tried to move things around and at this point I’m out of ideas.

Thanks

Why you complicate this.
Primary tick is handled in lv_conf.h

/*Use a custom tick source that tells the elapsed time in milliseconds.
 *It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
    #define LV_TICK_CUSTOM_INCLUDE "Arduino.h"         /*Header for the system time function*/
    #define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis())    /*Expression evaluating to current system time in ms*/
#endif   /*LV_TICK_CUSTOM*/

And if you dont have multicore MCU, use threads for timing is waste of resources.
Simple effective way is in loop use if timer variable % num == x run period …

Hi @Marian_M

Thanks for your response.
I’m new to ESP32 and the concept of threads in general.
I’m just following on example I saw online for doing multi rate task operations.

I am just not undertaking why the tick function does not operate properly when defined the way I defined it.

Can you please elaborate on your “waste of resource” comment.
Also, I am using ESP IDE and not Arduino and there is no loop function. I understand I can use “while” instead but not sure if that does the exact same thing.

I really appreciate you taking the time

I prefer Platformio, and maybe your issue is, that xtaskcreate
Create a new task and add it to the list of tasks that are ready to run.
Not run it. For this you need The FreeRTOS vTaskStartScheduler() RTOS API function which is part of the RTOS scheduler control API. FreeRTOS is a professional grade, small footprint, open source RTOS for microcontrollers.

1 Like

Hi @msh911 ,

It is strange! I would say without seeing all the internals of the code etc. almost certainly the ESP timer is not running.

I have no experience with your hardware/software or development platform so it’s difficult to go much further for me. I can possibly do a bit more if you can you share which tools you are using, I have now download VS Code and the ESP IDF plug-in, in the hope I can get a glimpse of the ESP32 code and vendor implementation to see what is going on.

If you can tell me as much info as possible about your development setup I will try my best to duplicate your setup here (without your hardware of course) and I might be able to get a view of the big picture, which is missing for me right now.

I hope that makes sense.

Kind Regards,

Pete

Hi @msh911 ,

Just a quick follow up, did you find a solution to your problem?

Kind Regards,

Pete

I thought i would just add something that I am experiencing right now and maybe this is affecting you.

Just some details first:

ESP-IDF 5.1.2
LVGL 8.3
ESP32-C3
ST7735S Display

I have the display working with ‘hello world’ displaying. Seems all tick and lv tasks are running as expected.

however, my goal here was to read an I2C temperature sensor and take the output of that and display it.

I already had a working task for that so i just added the task with xTaskCreate. The temperature task got added, but the second (runs every 10 seconds) the temperature task runs, it blanks out my TFT, every time. Now there are no errors, and ive verfied all tasks still running (lv and otherwise).

If i disable the temperature task, the screen will display hello world indefinitely.

I was able to narrow it down a bit. If i remove all printf statements (was using them as markers and also to display the temperature and humidity on the console) then the lv tasks (tick and lv_task_handler) AND the temperature task will all run and get along… display continues to show hello world. The minute i add a printf statement anywhere in a running task, either my temperature gathering task or my lv_task_handler task, the display will display ‘hello world’ and go blank.

I cant figure out what’s causing this. But wondering if you have something similar going on since you have printf in there somewhere… just wanted to add.

SOLVED

Ok i am embarrassed to admit this one. I guess when i was wiring this thing up, i wanted to use IO0 for the Reset pin on my display. I thought i had wired the RST on my ST7735 display to IO0.

Well if you look at the pinout on the WSP-C3-32S Kit, on the other side of the board from IO0 is U0TX, a UART line. This is not the first time that ive looked at a pinout diagram that shows the board from one side vs the other. I got all the other pins wired correctly except i ended up wiring RST->U0TX. My guess is that as soon as something was set to the console, the U0TX was signal’d and it reset the display.

And one thing i should clarify. I believe i said the display went blank, not black. By blank, i meant white, just backlight only.

So i have fully rewired this device and fixed the RST line and everything is working. I’ve used printf commands and the display is not affected.

Im sorry for not catching this before. @Marian_M thank you very much for your offer to help.

hello all commenters,

Commenting in a hope to get some feedback on my post, please have a look at Usage of semaphore for all lv_.. apis and help me to correct my understanding

Thank you