Esp32 rebooting after after changing lvgl v8.2 to 9.2, uint32_t lv_timer_handler(void)

after changing lvgl v8.2 to 9.2, esp32 is rebooting. it is happening inside the uint32_t lv_timer_handler(void)
I am using LVGL with ESP32 Cheap Yellow Display Board (ESP32-2432S028R). added comments in below function were i fond issue, commented like this
/////-----------------------this loop making issue///if is add while loop it not rebooting-------------------///

LV_ATTRIBUTE_TIMER_HANDLER uint32_t lv_timer_handler(void)
{
LV_TRACE_TIMER(“begin”);

lv_timer_state_t * state_p = &state;
/*Avoid concurrent running of the timer handler*/
if(state_p->already_running) {
    LV_TRACE_TIMER("already running, concurrent calls are not allow, returning");
    return 1;
}
state_p->already_running = true;

if(state_p->lv_timer_run == false) {
    state_p->already_running = false; /*Release mutex*/
    return 1;
}

LV_PROFILER_BEGIN;
lv_lock();

uint32_t handler_start = lv_tick_get();

if(handler_start == 0) {
    state.run_cnt++;
    if(state.run_cnt > 100) {
        state.run_cnt = 0;
        LV_LOG_WARN("It seems lv_tick_inc() is not called.");
    }
}

/*Run all timer from the list*/
lv_timer_t * next;
lv_timer_t * timer_active;
lv_ll_t * timer_head = timer_ll_p;
do {
    state_p->timer_deleted             = false;
    state_p->timer_created             = false;

    timer_active = lv_ll_get_head(timer_head);

    while(timer_active) {
        /*The timer might be deleted if it runs only once ('repeat_count = 1')
         *So get next element until the current is surely valid*/
        next = lv_ll_get_next(timer_head, timer_active);

        if(lv_timer_exec(timer_active)) {
            /*If a timer was created or deleted then this or the next item might be corrupted*/
            if(state_p->timer_created || state_p->timer_deleted) {
                LV_TRACE_TIMER("Start from the first timer again because a timer was created or deleted");
                break;
            }
        }

        timer_active = next; /*Load the next timer*/

/////-----------------------this loop making issue///if is add while loop it not rebooting-------------------///
}
} while(timer_active);

uint32_t time_until_next = LV_NO_TIMER_READY;
next = lv_ll_get_head(timer_head);
while(next) {
    if(!next->paused) {
        uint32_t delay = lv_timer_time_remaining(next);
        if(delay < time_until_next)
            time_until_next = delay;
    }

    next = lv_ll_get_next(timer_head, next); /*Find the next timer*/

}

state_p->busy_time += lv_tick_elaps(handler_start);
uint32_t idle_period_time = lv_tick_elaps(state_p->idle_period_start);
if(idle_period_time >= IDLE_MEAS_PERIOD) {
    state_p->idle_last         = (state_p->busy_time * 100) / idle_period_time;  /*Calculate the busy percentage*/
    state_p->idle_last         = state_p->idle_last > 100 ? 0 : 100 - state_p->idle_last; /*But we need idle time*/
    state_p->busy_time         = 0;
    state_p->idle_period_start = lv_tick_get();
}

state_p->timer_time_until_next = time_until_next;
state_p->already_running = false; /*Release the mutex*/

LV_TRACE_TIMER("finished (%" LV_PRIu32 " ms until the next timer call)", time_until_next);
lv_unlock();

LV_PROFILER_END;

return time_until_next;

}

please help. I have added complete project file
Thank
Jolly

LVGL_HelloWorld.zip (63.5 KB)

need to see the output data when it reboots. There is going to be a core dump and in that core core dump is going to be a back trace. if you decode that back trace it will tell you exactly where the issue is coming from.

also please wrap code bloacks in ```… Like this…

```

Your code Here

```

it ends up keeping the formatting and will look like this…


Your code Here

You code is overly complicated that’s for sure. You really do not need to use a hardware timer to handle the tick increment.

I also recommend using DMA transfers for the display it will improve performance.

It is hard to understand what is happening in your code because of the functions being nested as they are. You should be initializing the display driver, indev driver, LVGL display driver, LVGL indev driver and 2 tasks one for the main app and the other to increment the tick. This all should be done in the app_main function. You can do them in separate functions but only one level not the nested functions you have going on.

Give me a bit and I will make some changed to your code so you get a better idea…


here is the output , please help

Ok, I will try, I am not familiar esp32 , lvgl and RTOS coding, could you help me to cleanup the code? i will try as you suggested

thanks
Jolly

I think this will help.

/*
 * display_mng.c
 *
 *  Created on: Jan 26, 2024
 *      Author: xpress_embedo
 */

#include "esp_heap_caps.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "ili9341.h"

#include "lvgl.h"
#include "display_mng.h"

// Defines
#define LV_TICK_PERIOD_MS           (2)
#define DISP_BUFFER_SIZE            (TFT_BUFFER_SIZE)
#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))

// Private Function Declarations
static void display_mng(void *pvParameter);
static void display_flush_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map);
static void display_flush_slow_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map);
static void display_flush_swap_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map);
static void display_input_read(lv_indev_t * indev, lv_indev_data_t*data);
static void lvgl_tick(void *arg);

// Private Variables
// Creates a semaphore to handle concurrent call to lvgl stuff, If we wish to
// call *any* lvgl function from other threads/tasks we should lock on the very
// same semaphore!
SemaphoreHandle_t lvgl_semaphore;


// Public Function Definitions
/**
 * @brief Display Initialize
 *        Initialize the Display Controller, Touch, and Initialize LVGL Library
 *        Also link draw buffer.
 * @param  None
 */
void display_init( void )
{
  // for LVGL 8.3.11
 // static lv_disp_draw_buf_t draw_buf; // contains internal graphics buffer called draw buffer
 // static lv_disp_drv_t disp_drv;      // contains callback functions
 // static lv_indev_drv_t indev_drv;    // input device drivers
 tft_init();
 xpt2046_init();
 lv_init();

 // lvgl 9.2

  lv_display_t * disp = lv_display_create(TFT_HOR_RES_MAX, TFT_VER_RES_MAX);
  lv_display_set_flush_cb(disp, display_flush_cb);
  lv_display_set_flush_wait_cb(disp, NULL);
 
  size_t DRAW_BUF_SIZE = 0;
  DRAW_BUF_SIZE =  TFT_HOR_RES_MAX * TFT_VER_RES_MAX / 10  * sizeof(lv_color_t);
  lv_color_t * drawBuf1[DRAW_BUF_SIZE / 4];
  lv_display_set_buffers(disp, drawBuf1, NULL, DRAW_BUF_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);

  // configuring input devices
  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
 lv_indev_set_long_press_time(indev, 150);
  lv_indev_set_read_cb(indev, display_input_read);

  // initialize the lvgl library
  



  // Tick Interface for LVGL using esp_timer to generate 2ms periodic event
  lvgl_semaphore = xSemaphoreCreateMutex();
  const esp_timer_create_args_t lvgl_tick_timer_args =
  {
    .callback = &lvgl_tick,
    .name = "lvgl_tick"
  };
  esp_timer_handle_t lvgl_tick_timer;
  ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
  ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, LV_TICK_PERIOD_MS * 1000));  // here time is in micro seconds
 
  
  //xTaskCreatePinnedToCore(&display_mng, "display mng", 4096*4, NULL, 5, NULL, 0);
  xTaskCreatePinnedToCore(&display_mng, "display mng", 4096*4, NULL, 5, NULL, 1);
  // NOTE: I checked the flush timing with pinning and without pinning to core is same
}

// Private Function Definitions
/**
 * @brief Display Manager Function which calls the lvgl timer handler function
 * @param *pvParameter  task parameter
 */
static int max_flushing_time = 0;
static void display_mng(void *pvParameter)
{
  int64_t start_time = 0;

  while(1)
  {
    vTaskDelay(pdMS_TO_TICKS(20));
    if( pdTRUE == xSemaphoreTake(lvgl_semaphore, portMAX_DELAY) )
    {
      start_time = esp_timer_get_time();
      lv_timer_handler();

      // Semaphore is released when flushing is completed, this is checked using
      // tft_flush_status function, and then we release the semaphore
      // xSemaphoreGive(lvgl_semaphore);
    }

    // check flushing status
    if( tft_flush_status() == true )
    {
      // printf("Flushing Time: %d" PRId64 ", %" PRId64 "\n", esp_timer_get_time(), start_time);
      int time_taken = (int32_t)((esp_timer_get_time() - start_time)/1000);
      if( time_taken > max_flushing_time )
      {
        max_flushing_time = time_taken;
        printf("Flushing Time: %d ms\n", max_flushing_time );
      }
      xSemaphoreGive(lvgl_semaphore);
    }
  }
}

/**
 * @brief Flush the data to the display controller
 *        This function is a fast function, further improvements can be done in
 *        the SPI driver layer.
 * @note  The ILI9341 is working in 8-bit SPI mode, and hence in LVGL configuration
 *        Data SWAP must be enabled, else this function will not display data
 *        properly, and if u want that use the flush_swap function, but for sure
 *        the flush_swap function is a slow function.
 * @param drv         lvgl display drivers
 * @param area        lvgl area to be updated
 * @param color_map   pixel information
 */
static void display_flush_cb(lv_display_t * disp, const lv_area_t *area, lv_color_t *color_map)
{
  size_t width = (area->x2 - area->x1 + 1);
  size_t height = (area->y2 - area->y1 + 1);
  size_t len = width * height * 2;

  ili9341_set_window(area->x1, area->y1, area->x2, area->y2);

  // transfer frame buffer
  // the problem with this command is that it uses the polling method
  // tft_send_cmd(ILI9341_GRAM, (uint8_t*)color_map, len);

  // while here the first command i.e. ILI9341_GRAM using polling method
  // while the send data method using spi_device_transmit function
  tft_send_cmd(ILI9341_GRAM, 0, 0);
  tft_send_data((uint8_t*)color_map, len);

  lv_disp_flush_ready(disp);
}


/**
 * @brief Flush the data to the display controller
 *        This function is so slow that it will create a watchdog reset.
 *        This function is just written to test some stuff
 * @param drv         lvgl display drivers
 * @param area        lvgl area to be updated
 * @param color_map   pixel information
 */
static void display_flush_slow_cb(lv_display_t *disp, const lv_area_t *area, lv_color_t *color_map)
{
  uint16_t x, y;
  lv_color_t temp;
  for(y = area->y1; y <= area->y2; y++)
  {
    for(x = area->x1; x <= area->x2; x++)
    {
      temp = *color_map;
      ili9341_draw_pixel(x, y, lv_color_to_u16(temp));
      color_map++;
    }
  }
  lv_disp_flush_ready(disp);
}

/**
 * @brief Flush the data to the display controller
 *        This function is swaps the data before transmitting unlike the above 
 *        flush function, hence it is slow, it is recommended to use the above
 *        function and not this one.
 * @param drv         lvgl display drivers
 * @param area        lvgl area to be updated
 * @param color_map   pixel information
 */
static void display_flush_swap_cb(lv_display_t *disp, const lv_area_t *area, lv_color_t *color_map)
{
  size_t idx = 0;
  size_t width = (area->x2 - area->x1 + 1);
  size_t height = (area->y2 - area->y1 + 1);
  size_t len = width * height;
  uint8_t data[2];
  uint16_t temp;

  ili9341_set_window(area->x1, area->y1, area->x2, area->y2);

  // transfer frame buffer
  tft_send_cmd(ILI9341_GRAM, 0, 0);
  for( idx = 0; idx < len; idx++ )
  {
   // temp = *color_map;
    temp = lv_color_to_u16(*color_map);
    data[0] = (temp)>>8;
    data[1] = (temp) & 0xFF;
    tft_send_data(data, 2);
    color_map++;
  }

  lv_disp_flush_ready(disp);
}

/**
 * @brief Read the touch coordinates from the touch controller
 * @param drv   pointer to input device driver structure
 * @param data  pointer to data
 */
static void display_input_read(lv_indev_t * indev, lv_indev_data_t * data)
{
  static int16_t x = 0;
  static int16_t y = 0;
  // check if we have a touch detected or not
  if( xpt2046_read(&x, &y) )
  {
    // we are here means touch is detected
    data->point.x = x;
    data->point.y = y;
    data->state = LV_INDEV_STATE_PRESSED;
  }
  else
  {
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

/**
 * @brief LVGL Tick Function Hook
 *        LVGL need to call function lv_tick_inc periodically @ LV_TICK_PERIOD_MS
 *        to keep timing information.
 * @param arg 
 */
static void lvgl_tick(void *arg)
{
  (void) arg;

  lv_tick_inc(LV_TICK_PERIOD_MS);
}

main.c

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "lvgl.h"
#include "display_mng.h"

// Private Variables
static uint8_t button_counter = 0;
static lv_obj_t * count_label;

// Private Function Declarations
void lv_button_demo(void);
static void event_handler(lv_event_t * e);


void app_main(void)
{
  display_init();

  lv_button_demo();

  while (true)
  {
    sleep(3);
     printf("LVGL Demo Running\n");
  }
}


// Private Function Definitions
/**
 * @brief Simple Button Screen containing two labels and two buttons
 * @param  None
 */
void lv_button_demo(void)
{
  lv_obj_t * label;

  lv_obj_t * btn1 = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(btn1, event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(btn1, LV_ALIGN_CENTER, 0, -10);

  label = lv_label_create(btn1);
  lv_label_set_text(label, "Button");
  lv_obj_center(label);

  count_label = lv_label_create(lv_scr_act());
  lv_obj_align(count_label, LV_ALIGN_CENTER, 0, -60);
  lv_label_set_text(count_label, "Counts: 0");

  lv_obj_t * btn2 = lv_btn_create(lv_scr_act());
  lv_obj_add_event_cb(btn2, event_handler, LV_EVENT_ALL, NULL);
  lv_obj_align(btn2, LV_ALIGN_CENTER, 0, 40);
  lv_obj_add_flag(btn2, LV_OBJ_FLAG_CHECKABLE);
  lv_obj_set_height(btn2, LV_SIZE_CONTENT);

  label = lv_label_create(btn2);
  lv_label_set_text(label, "Toggle");
  lv_obj_center(label);
}

/**
 * @brief Event Handler Function for Button Clicked Events
 * @param e lvgl event
 */
static void event_handler(lv_event_t * e)
{
  lv_event_code_t code = lv_event_get_code(e);

  if( (code == LV_EVENT_CLICKED) || (code ==  LV_EVENT_LONG_PRESSED_REPEAT) )
  {
    if ( code == LV_EVENT_CLICKED)
      LV_LOG_USER("Click Event");
    else if( code == LV_EVENT_LONG_PRESSED_REPEAT )
      LV_LOG_USER("Press and Hold Event");
    button_counter++;
    lv_label_set_text_fmt(count_label, "Count: %d", button_counter);
  }
  else if(code == LV_EVENT_VALUE_CHANGED)
  {
    LV_LOG_USER("Toggle Event");
  }
}

I am actually working on it now. I have gotten rid of most of the code you have and removed the driver code and I am instead using the drivers that are in the component registry for the ESP32.

There is a repository for the ESP32 called the “component registry” It has all kinds of different helpful bits of code in there. It is actually where your project was collecting LVGL from. There is a file named idf_component.yml which is where you list dependencies on other components your project has. That will tell the esp-idf build script to go and collect those needed components so you can use them.

I also wrote the menuconfig script so you can configure the pins and pin states using idf.py menuconfig It gives you a nice little UI with menus and what have you to make changes to config settings for the ESP32 and also for the project your are compiling (if a script has been written for the project)…

can you give me some information about the MCU you are using?..

  • How much flash memory
  • Type of SPI used for the flash, quad or octal
  • Type of SPI used for external RAM (SPIRAM/PSRAM), quad or octal
  • version of the ESP32 you are using, C3, C6, S2, S3, original, etc…
  • display width
  • display height

I will be able to further optimize the code if I know those things as it will tell me the number of processor cores and how much DMA memory is available and where I should place the frame buffers.

also… This line of code is wrong.

lv_color_t * drawBuf1[DRAW_BUF_SIZE / 4]

You are creating an array of pointers with that code and in all honesty I am not sure why it would even be compiling because you are not initializing the array.

you do not want to create a frame buffer full of lv_color_t objects because in LVGL version 9 the size of lv_color_t is always going to be 4 bytes. the frame buffer size you want to set as 1/10 of the total number of bytes it would take to draw the entire screen. that math is width * height * bytes_per_pixel / 10. In your cause you are using RGB565 which is 16bit color so there are 2 bytes per pixel.

You are also allocating the frame buffer on the stack which you do not want to do. You need to allocate the buffer in the heap. To do that you would use malloc, better is to use calloc because that will zero out the memory after it allocates it. You can go one step further and use the ESP32 specific heap functions heap_caps_malloc and heap_caps_calloc. Difference here is the ability to instruct what kind of memory you want the buffer allocated in.

uint8_t * fb = (uint8_t *)heap_caps_calloc(1, buffer_size, MALLOC_CAPS_INTERNAL | MALLOC_CAPS_DMA)

or the following if you want to allocate the buffer in external DMA memory.

uint8_t * fb = heap_caps_calloc(1, buffer_size, MALLOC_CAPS_SPIRAM | MALLOC_CAPS_DMA)

If the MCU has enough DMA memory you want to create 2 frame buffers in that memory. Internal memory is faster than external but either is going to be a lot faster than only having a single frame buffer. Using DMA memory allows the frame buffer to be transmitted to the display with only using the smallest amount of CPU time which is in the form of interrupts to put together the next transaction to be sent. So while one buffer is being transmitted the CPU is free to allow LVGL to render to the buffer that is not transmitting. When LVGL is done rendering it calls the flush callback and the just rendered buffer gets queued up to be sent. When the buffer that is sending finishes a callback is made so that LVGL can be notified that it is OK to render to the buffer that has just finished transmitting. Using double buffering and DMA memory gives almost double the speed (frame rate). It is worth doing if the memory is available for sure.

Here is requsted information

  • How much flash memory : 4MB
  • Type of SPI used for the flash, quad or octal : Qaud
  • Type of SPI used for external RAM (SPIRAM/PSRAM), quad or octal : No external RAM
  • version of the ESP32 you are using, C3, C6, S2, S3, original, etc… : ESP32­WROOM­32
  • display width : 320
  • display height : 240
  • Display Driver : ILI9341, SPI
  • Touch : xpt2046
    Thanks
    Jolly

I hope the UI you are writing isn’t all that complex. There isn’t a whole lot of memory when you don’t have any external RAM. It’s only around 380K or so. There is going to be 30,720 bytes of that used for the frame buffers.

I am still stuck, no luck for fixing reboot issue.