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