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)
{
LV_ASSERT_OBJ(obj, MY_CLASS);
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():
/**********************
* APPLICATION MAIN
**********************/
void app_main() {
xTaskCreatePinnedToCore(guiTask, "gui", 4096*2, NULL, 0, NULL, 1);
bool mem_leak_detected = false;
while(1){
heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
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();
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 \
|| defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_SSD1306
/* 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. */
/*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;
lv_disp_drv_init(&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 */
#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;
lv_disp_drv_register(&disp_drv);
/* Register an input device when enabled on the menuconfig */
#if CONFIG_LV_TOUCH_CONTROLLER != TOUCH_CONTROLLER_NONE
static 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);
}
static void create_demo_application(void)
{
/* When using a monochrome display we only show "Hello World" centered on the
* screen */
#if defined CONFIG_LV_TFT_DISPLAY_MONOCHROME || \
defined CONFIG_LV_TFT_DISPLAY_CONTROLLER_ST7735S
/* 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);
#else
/* Otherwise we show the selected demo */
squareline_ui_init();
#endif
}
Code in ui.c :
// SquareLine LVGL GENERATED FILE
// EDITOR VERSION: SquareLine Studio 1.0.5
// LVGL VERSION: 8.2
#include "ui.h"
#include "ui_helpers.h"
#include "esp_log.h"
#include "esp_err.h"
//DEFINES
#define TAG "UIINIT"
//VARIABLES
lv_obj_t * ui_Screen1;
lv_obj_t * ui_Button1;
lv_obj_t * ui_Screen2;
lv_obj_t * ui_Button5;
//TEST SETTINGS
#if LV_COLOR_DEPTH != 16
#error "LV_COLOR_DEPTH should be 16bit to match SquareLine Studio's settings"
#endif
#if LV_COLOR_16_SWAP !=0
#error "#error LV_COLOR_16_SWAP should be 0 to match SquareLine Studio's settings"
#endif
//FUNCTIONS
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);
}
}
//SCREENS
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);
ui_Screen1_screen_init();
ui_Screen2_screen_init();
lv_disp_load_scr(ui_Screen1);
}
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 
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!
-Joseph

