App crashes when triggering screen change animation

ESP32 - Reference to needed screens goes NULL when going through screen change animation.

Having an issue regarding screen transition animations. I have a home screen that has a button that will change to a different screen. Simple concept. However, when pressing the button to trigger this change, everything falls apart, and I am left with a Guru Meditation - Load Prohibited exception. I have deduced through debugging that in the process of creating the animation, there is a point in the code that references a NULL pointer.

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

This project is using an ESP32-WROVER-I(16mb), on a custom board (however, board layout issues have been sufficiently ruled out for this particular issue), and compiling and linking using ESP-IDF v4.3.1-dirty. (edit) - Also using FreeRTOS on the ESP32.

What do you want to achieve?

To transition between screens using the stock animations, in this case specifically LV_SCR_LOAD_ANIM_MOVE_TOP, though this issue is reproduced when using any of the animation types.

What have you tried so far?

Specifically, here is where the NULL reference seems to be happening:
In ..\src\core\lv_disp.c :

static void scr_load_anim_start(lv_anim_t * a)
    lv_disp_t * d = lv_obj_get_disp(a->var);

    d->prev_scr = lv_scr_act();  ////<<<---THIS IS WHERE THE NULL ACCESS HAPPENS
    d->act_scr = a->var;

    lv_event_send(d->act_scr, LV_EVENT_SCREEN_LOAD_START, NULL);

With some print statements, I am seeing that d is pointing to address 0x00000000, bad news.

This is where d gets its values:
In ..\src\core\lv_obj_tree.c

lv_disp_t * lv_obj_get_disp(const lv_obj_t * obj)

    const lv_obj_t * scr;

    if (obj->parent == NULL) {
        scr = obj;  /*`obj` is a screen*/
        ESP_LOGW(TAG, "Obj is a screen");
    else { 
        scr = lv_obj_get_screen(obj);  /*get the screen of `obj`*/
        ESP_LOGW(TAG, "Getting the screen of obj");
    lv_disp_t * d;
    _LV_LL_READ(&LV_GC_ROOT(_lv_disp_ll), d) {
        ESP_LOGW(TAG, "for loop d: %d", (int)d);
        uint32_t i;
        for(i = 0; i < d->screen_cnt; i++) {
            if(d->screens[i] == scr) return d;

    //ESP_LOGW(TAG, "before exit d: %d", (int)d);
    //LV_LOG_WARN("No screen found");
    ESP_LOGW(TAG, "MAIN: No screen found");
    return NULL;

In that for loop, d->screens goes through and returns 3 valid screens, or something of that sort, but the last two elements are set to NULL.

Now, there should be no reason to have to modify these functions since they are part of the framwork, so below I share the rest of the user code. Most of it is boilerplate from examples, but also, I am not so confident that everything is initiallized correctly for v8.2

My main.c uses code from a v7.9 example, updated to v8.2 using the changelog guide, and my GUI code is generated by Squareline Studio v1.0.5

Code to reproduce

Here is a bare bones setup for reproducing the issue:
main.c -> app_main():

void app_main() {
    xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 0, NULL, 1);

    bool mem_leak_detected = false;
        if(heap_caps_get_free_size(MALLOC_CAP_INTERNAL) < 5000 && !mem_leak_detected){
            ESP_LOGW(TAGW, "Internal Memory Leak Possible: Internal RAM left %d", heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
            mem_leak_detected = true;
        if(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) < 50000 && !mem_leak_detected){
            ESP_LOGW(TAGW, "SPI Memory Leak Possible: SPI RAM left %d", heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
            mem_leak_detected = true;
        vTaskDelay(30000 / portTICK_PERIOD_MS);

SemaphoreHandle_t xGuiSemaphore;

static void guiTask(void *pvParameter) {

    (void) pvParameter;
    xGuiSemaphore = xSemaphoreCreateMutex();


    /* Initialize SPI or I2C bus used by the drivers */

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

    static lv_disp_draw_buf_t disp_buf;

    uint32_t size_in_px = DISP_BUF_SIZE;

#if defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_IL3820         \

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

    /* Initialize the working buffer depending on the selected display.
     * NOTE: buf2 == NULL when using monochrome displays. */
    /*JA NOTE: size_in_px is actually pixel count in bytes. multiply by 8 to get physical pixel count*/
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, size_in_px);

    static lv_disp_drv_t disp_drv;
    disp_drv.flush_cb = disp_driver_flush;
    disp_drv.hor_res = 480;                 /*Set the horizontal resolution in pixels*/
    disp_drv.ver_res = 320;                 /*Set the vertical resolution in pixels*/

    /* When using a monochrome display we need to register the callbacks:
     * - rounder_cb
     * - set_px_cb */
    disp_drv.rounder_cb = disp_driver_rounder;
    disp_drv.set_px_cb = disp_driver_set_px;

    disp_drv.draw_buf = &disp_buf;

    /* Register an input device when enabled on the menuconfig */
    static lv_indev_drv_t indev_drv;
    indev_drv.read_cb = &touch_driver_read;

    indev_drv.type = LV_INDEV_TYPE_POINTER;

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

    while (1) {
        /* Delay 1 tick (assumes FreeRTOS tick is 10ms */

        /* Try to take the semaphore, call lvgl related function on success */
        if (pdTRUE == xSemaphoreTake(xGuiSemaphore, portMAX_DELAY)) {

    /* A task should NEVER return */


static void create_demo_application(void)
    /* When using a monochrome display we only show "Hello World" centered on the
     * screen */

    /* use a pretty small demo for monochrome displays */
    /* Get the current screen  */
    lv_obj_t * scr = lv_disp_get_scr_act(NULL);

    /*Create a Label on the currently active screen*/
    lv_obj_t * label1 =  lv_label_create(scr, NULL);

    /*Modify the Label's text*/
    lv_label_set_text(label1, "Hello\nworld");

    /* Align the Label to the center
     * NULL means align on parent (which is the screen now)
     * 0, 0 at the end means an x, y offset after alignment*/
    lv_obj_align(label1, NULL, LV_ALIGN_CENTER, 0, 0);
    /* Otherwise we show the selected demo */


Code in ui.c :

// EDITOR VERSION: SquareLine Studio 1.0.5

#include "ui.h"
#include "ui_helpers.h"
#include "esp_log.h"
#include "esp_err.h"

#define TAG "UIINIT"

lv_obj_t * ui_Screen1;
lv_obj_t * ui_Button1;

lv_obj_t * ui_Screen2;
lv_obj_t * ui_Button5;

#if LV_COLOR_DEPTH != 16
    #error "LV_COLOR_DEPTH should be 16bit to match SquareLine Studio's settings"
#if LV_COLOR_16_SWAP !=0
    #error "#error LV_COLOR_16_SWAP should be 0 to match SquareLine Studio's settings"

void ui_event_Button1(lv_event_t * e)
    lv_event_code_t event = lv_event_get_code(e);
    lv_obj_t * ta = lv_event_get_target(e);
    if(event == LV_EVENT_CLICKED) {
        ESP_LOGW(TAG, "In Callback, sending ui_Screen2, %p", &ui_Screen2);
        _ui_screen_change(ui_Screen2, LV_SCR_LOAD_ANIM_MOVE_TOP, 500, 0);

void ui_Screen1_screen_init(void)

    // ui_Screen1

    ui_Screen1 = lv_obj_create(NULL);
    lv_obj_clear_flag(ui_Screen1, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_bg_color(ui_Screen1, lv_color_hex(0x8A0000), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Screen1, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_img_src(ui_Screen1, &ui_img_20201212_134643_png, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Button1

    ui_Button1 = lv_btn_create(ui_Screen1);

    lv_obj_set_width(ui_Button1, 220);
    lv_obj_set_height(ui_Button1, 120);

    lv_obj_set_x(ui_Button1, lv_pct(-25));
    lv_obj_set_y(ui_Button1, lv_pct(-12));

    lv_obj_set_align(ui_Button1, LV_ALIGN_CENTER);

    lv_obj_add_flag(ui_Button1, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
    lv_obj_clear_flag(ui_Button1, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_add_event_cb(ui_Button1, ui_event_Button1, LV_EVENT_ALL, NULL);
    lv_obj_set_style_bg_color(ui_Button1, lv_color_hex(0x4E4E4E), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Button1, 190, LV_PART_MAIN | LV_STATE_DEFAULT);

void ui_Screen2_screen_init(void)

    // ui_Screen2

    ui_Screen2 = lv_obj_create(NULL);

    lv_obj_clear_flag(ui_Screen2, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_bg_color(ui_Screen2, lv_color_hex(0x475464), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Screen2, 255, LV_PART_MAIN | LV_STATE_DEFAULT);

    // ui_Button5

    ui_Button5 = lv_btn_create(ui_Screen2);

    lv_obj_set_width(ui_Button5, 225);
    lv_obj_set_height(ui_Button5, 125);

    lv_obj_set_x(ui_Button5, -118);
    lv_obj_set_y(ui_Button5, lv_pct(-17));

    lv_obj_set_align(ui_Button5, LV_ALIGN_CENTER);

    lv_obj_add_flag(ui_Button5, LV_OBJ_FLAG_SCROLL_ON_FOCUS);
    lv_obj_clear_flag(ui_Button5, LV_OBJ_FLAG_SCROLLABLE);

    lv_obj_set_style_radius(ui_Button5, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_color(ui_Button5, lv_color_hex(0x000000), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(ui_Button5, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_border_side(ui_Button5, LV_BORDER_SIDE_FULL, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_color(ui_Button5, lv_color_hex(0x222222), LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_opa(ui_Button5, 255, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_width(ui_Button5, 5, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_spread(ui_Button5, -1, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_ofs_x(ui_Button5, 0, LV_PART_MAIN | LV_STATE_DEFAULT);
    lv_obj_set_style_shadow_ofs_y(ui_Button5, 3, LV_PART_MAIN | LV_STATE_DEFAULT);


void squareline_ui_init(void)
    lv_disp_t * dispp = lv_disp_get_default();
    lv_theme_t * theme = lv_theme_default_init(dispp, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_RED),
                                               true, LV_FONT_DEFAULT);
    lv_disp_set_theme(dispp, theme);


Screenshot of error:

Here is the basic dump. Upon sugestion, I can provide more detailed logs, and just tell me what settings to set up for that because I am still becoming familiar with LVGL’s logging system (as in, I have not really used it, just been using my own print statements inserted in key LVGL functions as to not be blind)

And here is a small log from the above pasted lv_obj_tree.c using my very sophisticated print statements :slightly_smiling_face:

This is my first time posting, so please let me know if I am missing anything. I’ve got plenty more details if needed. Thanks!


Well, Looks like I need to look into my drivers - I had code in my driver code for the ILI9488 that was doing some funky type conversions that shouldn’t have happened - namely assigning a lv_disp_t to a lv_disp_drv_t. I think this was causing all kind of weird things to happen, because my app has stopped crashing when I replaced this section of code with code from the develop branch of the ESP32 drivers.

Here is the code I replaced: (in ili9488.c)

static void IRAM_ATTR spi_ready(spi_transaction_t *trans)
     disp_spi_send_flag_t flags = (disp_spi_send_flag_t) trans->user;

     if (flags & DISP_SPI_SIGNAL_FLUSH) {
         static lv_disp_drv_t * disp = NULL;

         disp = _lv_refr_get_disp_refreshing();
 #else /* Before v7 */
         disp = lv_refr_get_disp_refreshing();


     if (chained_post_cb) {


static void IRAM_ATTR spi_ready(spi_transaction_t *trans)
    disp_spi_send_flag_t flags = (disp_spi_send_flag_t) trans->user;

    if (flags & DISP_SPI_SIGNAL_FLUSH) {
        lv_disp_t * disp = NULL;   ////<<<---NEW ASSIGNMENT

        disp = _lv_refr_get_disp_refreshing();
#else /* Before v7 */
        disp = lv_refr_get_disp_refreshing();



    if (chained_post_cb) {

I will be testing more to make sure this is a solid fix, and reviewing the rest of my driver code to make sure it is v8.2 compatible. Sorry if this post clutters up the forum with just me talking back and forth with myself, but I think this is a good lesson in making sure that you check your code to make sure absolutely all parts are updated to reflect v8 patterns. Will check in again if anything breaks moving forward.

1 Like