LVGL on Arduino Giga causing crashes

TL;DR: I’m trying to set up buffers, displays, and screens on an Arduino Giga, but I am getting crashes on upload, and I am not sure how to fix it.

I’m using an Arduino Giga R1 WiFi and a Giga Display Shield for this project, and this is the first time that I have ever worked with LVGL. I had the code working with a combination of Arduino_GigaDisplay_GFX.h and lvgl.h (v9.2.2), but I was having some weird crashes related to initializing the USB-A to connect a flash drive using Arduino_USBHostMbed5.h (either the USB-A would disconnect after 20–30 seconds with newer flash drives, or the Arduino simply crash with older flash drives that would stay connected long enough to progress through the menus, and I would get error -22 when verifying the USB connection with any of the drives, though it would still write a file).

For example of a crash, I have it so the user progresses through some menus to make selections with radio buttons and enter numbers with a button matrix, then show a summary with a couple buttons on the screen. Depending on what options the user had made, one of the buttons on the summary screen would act appropriately or simply crash, even though that code had nothing to do with the USB. In case you are wondering, I haven’t seen memory fragmentation above 2%, the heap stays relatively small, and there is still lots of memory left.

I decided to go back and restart with the LVGL and Arduino_H7_Video.h. I switched from outputting text with GFX to LVGL, and I am going to try using a display and separate screens as the foundation rather than whatever is the default active screen to see if I can get around the crashing problem.

I am trying to set up a buffer for flushing the screen to use for the screen updates and for loading screens. I don’t know if I really need the buffer (let alone two) as my screens are mostly static except for one that has a running update that changes every second, and another that flashes different colours if the user pauses things or if there is a fault somewhere, other than the button widgets of course. Also, the menus and the like seemed to load fine before without a buffer, an explicitly created display, or explicitly creating screens. I’m trying a few different things though to see if I can get rid of the crashes that are happening.

After implementing an explicit display, I had the code working more or less, though I was also having some new issues with white text not showing up on the black screen and crashes during the menus (though I hadn’t set up separate screens yet), but now that I am trying to add a buffer, the upload crashes as soon as it finishes and before the Arduino confirms a successful upload. I’ve tried figuring it out myself, I’ve talked with a few friends who are more familiar with Arduino than I am (though they aren’t familiar with the Giga and LVGL), and I have tried talking with ChatGPT’s Code Copilot to see if I can figure it out (though the latter keeps reverting to LVGL v8). I have been having trouble finding tutorials and examples for these things.

Because of a cover image at the start, the program memory space is 81% (normally 42% without the image), and the dynamic memory is 56% (normally 21% without the display buffers).

I’ve included the code below. Does anyone have any ideas about what is going on and where I am going wrong?

Description

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

Arduino Giga R1 WiFi with a Giga Display Shield, Arduino IDE 2.3.4

What do you want to achieve?

Get a display, screens, and buffer working.

What have you tried so far?

Reading the LVGL Documentation 9.0, searching forums, talking with friends, and asking ChatGPT Code Copilot if it can spot the problem.

Code to reproduce

As far as I remember, this is the code that was changed since the uploads last worked (the full code is several thousand lines long, so I don’t want to overwhelm people).

  // Libraries
  #include <Arduino_H7_Video.h>
  #include <lvgl.h>

  #include "cover_img.h"

  // Definitions
  #ifndef MY_DISP_HOR_RES
    #define MY_DISP_HOR_RES    800
  #endif

  #ifndef MY_DISP_VER_RES
    #define MY_DISP_VER_RES    480
  #endif

  #define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))

  // Instances
  Arduino_H7_Video DisplayAHV(MY_DISP_HOR_RES, MY_DISP_VER_RES, GigaDisplayShield);
  Arduino_GigaDisplayTouch touchDetector;

  // Global variables
  lv_display_t * display = lv_display_create(MY_DISP_HOR_RES, MY_DISP_VER_RES); 

  static uint8_t buf1[MY_DISP_HOR_RES * MY_DISP_VER_RES / 10 * BYTES_PER_PIXEL];
  static uint8_t buf2[MY_DISP_HOR_RES * MY_DISP_VER_RES / 10 * BYTES_PER_PIXEL]; 

void setup() {

  // Variables for self-test startup screen
  int yPosition = 10; // Initial Y-coordinate for the first line of text for startup screen
  const int lineSpacing = 20; // Space between lines of text
  
  // Initialize the display
  DisplayAHV.begin();

  // Initialize LVGL 
  lv_init();  

  lv_display_set_buffers(display, buf1, buf2, sizeof(buf1), LV_DISPLAY_RENDER_MODE_PARTIAL);
  lv_display_set_flush_cb(display, my_disp_flush);

  // Optionally set it as the default display
  lv_disp_set_default(display);
  
  cover_image();

  screen_black = lv_scr_act();
  lv_obj_set_style_bg_color(screen_black, lv_color_black(), LV_PART_MAIN);
  lv_obj_set_style_bg_opa(screen_black, LV_OPA_COVER, LV_PART_MAIN);

  // Create a label with white text
  lv_obj_t *label_startup = lv_label_create(screen_black);
  lv_obj_set_style_text_font(label_startup, &lv_font_montserrat_14, LV_PART_MAIN | LV_STATE_DEFAULT);
  lv_obj_set_style_text_color(label_startup, lv_color_white(), LV_PART_MAIN);

  lv_obj_align(label_startup, LV_ALIGN_TOP_LEFT, 10, yPosition);
  lv_label_set_text(label_startup, "LVGL screen initialized");
  lv_refr_now(NULL);
  yPosition += lineSpacing;
}

void my_disp_flush(lv_display_t *disp, const lv_area_t *area, uint8_t *color_p) {
  
    // Cast the buffer to lv_color_t for proper interpretation
    lv_color_t *color_map = (lv_color_t *)color_p;

    // Start drawing to the framebuffer
    DisplayAHV.beginDraw();

    // Loop through the area and set pixels
    for (int32_t y = area->y1; y <= area->y2; y++) {
        for (int32_t x = area->x1; x <= area->x2; x++) {
            // Extract RGB565 from lv_color_t
            uint16_t color = *((uint16_t *)color_map);

            // Convert RGB565 to R, G, B components
            uint8_t r = (color >> 11) & 0x1F;  // Extract 5 bits for red
            uint8_t g = (color >> 5) & 0x3F;   // Extract 6 bits for green
            uint8_t b = color & 0x1F;          // Extract 5 bits for blue

            // Scale to 8-bit values (0-255)
            r = (r * 255) / 31;
            g = (g * 255) / 63;
            b = (b * 255) / 31;

            // Set the pixel color
            DisplayAHV.set(x, y, r, g, b);

            // Advance to the next color in the buffer
            color_map++;
        }
    }

    // End drawing operation
    DisplayAHV.endDraw();

    lv_display_flush_ready(disp);
}

void cover_image() {

  LV_IMG_DECLARE(cover_img);
  lv_obj_t * img1 = lv_img_create(lv_scr_act());
  lv_img_set_src(img1, &cover_img);
  lv_obj_align(img1, LV_ALIGN_CENTER, 0, 0);
  lv_obj_set_size(img1, 800, 480);
  lv_timer_handler();
  delay(2000);

}

Screenshot and/or video

N/A as it is crashing before anything is shown on the screen

I also found that for including the cover image as cover_img.h, at the end of the pixel data, with using Arduino IDE, I wasn’t able to use this format because it kept giving me a compile error:

const lv_image_dsc_t cover_img = {
  .header.cf = LV_COLOR_FORMAT_RGB565,
  .header.magic = LV_IMAGE_HEADER_MAGIC,
  .header.w = 800,
  .header.h = 480,
  .data_size = 384000 * 2,
  .data = cover_img_map,
};

Instead, I had to use this format (though I haven’t had a chance to test it yet to see if it’s working and in the correct order, but it will compile):

const lv_image_dsc_t cover_img = {
  {
    480, // h
    800, // w
    0, // reserved:2
    0, // always_zero:3
    LV_COLOR_FORMAT_RGB565, // cf:5
  },
  384000 * 2, // data_size
  img_cover_img_map // data
};

I have been having all kinds of issues with getting the cover_img to display, and I finally got it working. Using this format:

const lv_img_dsc_t cover_img = {
  .header = {LV_COLOR_FORMAT_RGB565, 800, 480}, // Ensure the format and dimensions match
  .data_size = 800 * 480 * 2,                  // Width x Height x 2 bytes per pixel
  .data = cover_img_map                        // Pointer to the pixel data
};

I could get the data file to compile (the lv_img_dsc_t produced by the LVGL converter would cause compiler errors), but it wouldn’t show the image.

Finally, I just took the .c file from the LVGL converter without the edits for the Arduino IDE, put it in the same folder as the Arduino .ino file, and removed the line #include “cover_img.c” from the code for the libraries in the .ino file. The code compiled fine and cover_img showed up as it is supposed to.

I’m still confused about why this actually worked, but the main thing is that it is working.

Now I will have to go back and see if I can get the display code from earlier working.

It turns out that Arduino_H7_Video.h has it’s own code to create a display and buffers in LVGL (you have to include lvgl.h though for it to work). So I was creating a display onto a display, and inadvertently was naming it the same thing as Arduino_H7_Video.h did, which was creating a bunch of conflicts.

I still haven’t figured out the connection between why using both the WiFi and the USB is causing crashes in LVGL. I even restructured the menus to see if this would fix it, but the same menu option is still crashing, even though it’s in a different menu now. It’s almost like Arduino doesn’t like the word “Pressure” or something (though I did try changing it to a different word and that didn’t solve it, ha ha).