I am trying to code an Arduino Giga with a Giga Display Shield using LVGL, and I am encountering some really weird bugs.
The program is part of my PhD research, and it's several thousand lines of code, so I am not really able to share the full code (not to mention, this is a long enough post without adding all of the code). I'm trying to track down the issue to try make it easier to find the problem so that I can choose the relevant sections to share, but I am confused because there should be no connection between the two functions that are connected to the crashes. The most that I have seen for memory fragmentation is 2% and that's only briefly, and most times it is 0% before the crash happens (I don't use Strings either, but I just leared about using Serial.print(F() instead of Serial.print(), so I have to change that; and I just noticed that some of my arrays are not null terminated, so I am going to try that as well). I'm going to test to see if it has anything to do with using a mix of GFX and LVGL, but the only time that I use GFX is right at the start, and only because I was having trouble finding code to turn the screen black with white text for the startup sequence using LVGL. I have also tried using the LVGL logging, but that only seems to produce any output when I choose trace, and nothing shows up with the other options.
The main bug that I encounter is I am using the touchscreen to allow the user to make selections using radio buttons and to enter numbers using a number pad. Afterwards, it shows a summary on the screen and you have the option to press some buttons. The function that creates and displays the buttons is identical no matter what the user has chosen as their program, but some options result in the Arduino crashing and others work fine when the user presses one of the buttons.
Oddly enough, I found out what part of the problem is, and it has to do with the USB-A, but it has me confused. I want to use the USB to log data (I built it off of the data logger example provided by Arduino). However, if I remove the USB mass storage device setup code, the issues with crashing disappear, even though the code in that function has nothing to do with the USB. If I include even one msd.connect(); though, the code would have crashing problems on the summary screen. I will share these two sections below that I pulled out (sorry, I might have missed some of the variables that are global or local that get reused).
I was also having some weird things happen with the USB as well. The USB would power on and run, but then disconnect from the flash drive for some reason. I tried six different flash drives and only two of them would stay connected (both are over 10 years old, and the newer ones were disconnecting after about 20–30 seconds, no matter what I tried). I would also see if there were any error codes using this code that is part of the Arduino example (err is stored as an int, though it is also used to output text to the file as per the example):
err = usb.mount(&msd);
if (err) {
Serial.print("Error mounting USB device ");
Serial.println(err);
while (1)
;
}
And I would get error code -22, though it would still be able to open the file and write to it if I took out the while(1); line (I did see -5 one time as well, but that never repeated).
I am able to get past that part of the menu once the code for the USB is removed, but then it will either crash going to the next screen or not, again depending on what options the user has chosen, even though it should have no effect. The odd thing is, these crashes happen with the simplest set of options chosen by the user, but the most complex set of options works just fine.
On top of that, I want the screen to show a running update. I copied the code from the display screen in how it outputs the text on the screen, but for some reason, it won't space them out anymore like it does for the summary screen (it doesn't appear to be an error with the math, as I get it to output the math for the spacing to the serial monitor to double check). I get to this screen with the more complex set options provided by the user, but it crashes going to this screen if the user chooses the simplest options, even without the USB drive setup. I will share these two sections below after the USB and button sections (sorry, I might have missed copying some of the variables that are global or local that get initialized then later reused).
On top of all of this, for some reason, the Arduino IDE has made me add two extra } at the bottom of the code, even though they don't pair up with a { anywhere else in the code. It kept saying in the very last line of code that it needs a } after the ;, even though there was one that closed the function and there was only one { in that function (I have my code organized to easily collapse the functions and I can quickly scan to make sure that all { are paired with a }, but I also went through line by line to double check). It started with adding one }, then later I had to add a second one for some reason when it did it again.
I have asked a couple friends, but no one is really sure what is going on. I'm going to try to meet up with a friend later who is a developer, as he has more experience with Arduino than I do, but I figured that I would also ask here if anyone has any ideas about what might be going on or suggestions on how to troubleshoot this? Thanks.
Code for USB set up and button display
Code to initialize the USB (I use serialPrint as a bool to turn serial prints off and on, depending on what I want to do):
// Libraries
#include <Arduino_GigaDisplay.h>
#include <lvgl.h>
#include <Arduino_GigaDisplayTouch.h>
#include <ArduinoGraphics.h>
#include <Arduino_GigaDisplay_GFX.h>
#include <Arduino_USBHostMbed5.h>
#include <DigitalOut.h>
#include <FATFileSystem.h>
//Global variables
GigaDisplay_GFX DisplayGFX;
USBHostMSD msd;
mbed::FATFileSystem usb("usb");
int err;
void setup(){
// Enable the USB-A port
pinMode(PA_15, OUTPUT);
digitalWrite(PA_15, HIGH);
// Monitor USB stability
msd.attach_detected_callback(onDeviceDetected);
msd.attach_removed_callback(onDeviceRemoved);
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 USB for logging data
msd.connect();
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("Initializing memory");
yPosition += lineSpacing;
if (serialPrint)
Serial.print("Initializing memory");
retryTimeout = 0;
while (!msd.connected() && retryTimeout <= 3) {
msd.connect();
connectIterations = 0;
while (connectIterations <= 6 && !msd.connected()){
delay(1000);
DisplayGFX.print(".");
if (serialPrint)
Serial.print(".");
connectIterations++;
msd.connect();
}
if (serialPrint)
Serial.println("");
if(!msd.connected() && retryTimeout < 3) {
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("MSD not found. Retrying.");
yPosition += lineSpacing; // Move Y position down for the next line
if (serialPrint)
Serial.println("MSD not found. Retrying.");
}
retryTimeout++;
}
if(!msd.connected()) {
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("MSD not found. Failed.");
yPosition += lineSpacing; // Move Y position down for the next line
if (serialPrint)
Serial.println("MSD not found. Failed.");
}
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("Mounting USB device.");
yPosition += lineSpacing; // Move Y position down for the next line
if (serialPrint)
Serial.println("\n\nMounting USB device.");
err = usb.mount(&msd);
if (err) {
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("Error mounting USB device. Error code: ");
DisplayGFX.print(err);
yPosition += lineSpacing; // Move Y position down for the next line
if (serialPrint)
{
Serial.print("Error mounting USB device. Code: ");
Serial.println(err);
}
while (1); // Stop the startup
}
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("MSD read done.");
yPosition += lineSpacing; // Move Y position down for the next line
if (serialPrint)
Serial.println("MSD read done.");
if (!msd.connected()) {
DisplayGFX.setCursor(10, yPosition); // Set the cursor to the current position
DisplayGFX.print("USB device not detected. Stopping start up.");
yPosition += lineSpacing; // Move Y position down for the next line
if(serialPrint)
Serial.println("USB device not detected. Stopping start up.");
while(1);
}
if(serialPrint)
Serial.println("USB device connected.");
}
Code to display the buttons that are crashing sometimes:
// Global variables
lv_obj_t * button_grey_start = NULL;
lv_obj_t * label_grey_start = NULL;
lv_obj_t * button_red_pause = NULL;
lv_obj_t * label_button_red_pause = NULL;
lv_obj_t * button_red_countdown = NULL;
lv_obj_t * label_countdown = NULL;
static lv_style_t style_grey_button;
static lv_style_t style_red_button;
static int timerIteration = 0;
static int repeatPrime = 0;
void prime_tubing_handler(lv_event_t * e) {
setup_button_styles();
Serial.println("Start priming");
if(serialPrint)
Serial.println("Initialize variables");
long startTimer = millis();
static int timerIteration = 0;
if(serialPrint)
Serial.println("Creating grey button");
// Create an inactive grey button layered over the start button
button_grey_start = lv_btn_create(lv_scr_act());
lv_obj_set_size(button_grey_start, 200, 75);
lv_obj_align(button_grey_start, LV_ALIGN_CENTER, 120, 75);
lv_obj_add_style(button_grey_start, &style_grey_button, 0);
label_grey_start = lv_label_create(button_grey_start);
lv_label_set_text(label_grey_start, "Starting");
lv_obj_set_style_text_font(label_grey_start, &lv_font_montserrat_22, LV_PART_MAIN | LV_STATE_DEFAULT); // Set the label font
lv_obj_center(label_grey_start);
while(timerIteration < 3) {
if (button_red_countdown != NULL && lv_obj_is_valid(button_red_countdown)) {
lv_obj_del(button_red_countdown);
button_red_countdown = NULL; // Prevent dangling pointer
}
if (label_countdown != NULL && lv_obj_is_valid(label_countdown)) {
lv_obj_del(label_countdown);
label_countdown = NULL; // Prevent dangling pointer
}
if(serialPrint) {
Serial.println("Starting while");
Serial.print("millis() - startTimer: ");
Serial.println(millis() - startTimer);
Serial.print("timerIteration: ");
Serial.println(timerIteration);
}
// Create countdown button layered over the prime button
button_red_countdown = lv_btn_create(lv_scr_act());
lv_obj_set_size(button_red_countdown, 200, 75);
lv_obj_align(button_red_countdown, LV_ALIGN_CENTER, 120, -75);
lv_obj_add_style(button_red_countdown, &style_red_button, 0);
label_countdown = lv_label_create(button_red_countdown);
lv_obj_set_style_text_font(label_countdown, &lv_font_montserrat_22, LV_PART_MAIN | LV_STATE_DEFAULT); // Set the label font
lv_obj_center(label_countdown);
timerIteration++;
if(timerIteration == 1) {
lv_label_set_text(label_countdown, "3");
}
else if(timerIteration == 2) {
lv_label_set_text(label_countdown, "2");
}
else if(timerIteration == 3) {
lv_label_set_text(label_countdown, "1");
}
lv_refr_now(NULL);
//Serial.println("lv_obj_clean(button_red_countdown)");
lv_obj_clean(button_red_countdown);
delay(1000);
}
timerIteration = 0;
//if(serialPrint)
Serial.println("Stop priming");
//if(serialPrint)
Serial.println("Creating pause button");
button_red_pause = lv_btn_create(lv_scr_act());
lv_obj_set_size(button_red_pause, 200, 75);
lv_obj_align(button_red_pause, LV_ALIGN_CENTER, 120, -75);
lv_obj_add_style(button_red_pause, &style_red_button, 0);
//if(serialPrint)
Serial.println("Creating pause button label");
label_button_red_pause = lv_label_create(button_red_pause);
lv_obj_set_style_text_font(label_button_red_pause, &lv_font_montserrat_22, LV_PART_MAIN | LV_STATE_DEFAULT); // Set the label font
lv_label_set_text(label_button_red_pause, "Pause");
lv_obj_center(label_button_red_pause);
lv_obj_add_event_cb(button_red_pause, stop_priming, LV_EVENT_CLICKED, NULL);
lv_task_handler();
}
void setup_button_styles() {
if(repeatPrime == 0) {
// Initialize grey button style
lv_style_init(&style_grey_button);
lv_style_set_bg_opa(&style_grey_button, LV_OPA_COVER);
lv_style_set_bg_color(&style_grey_button, lv_palette_lighten(LV_PALETTE_GREY, 2));
lv_style_set_text_color(&style_grey_button, lv_palette_darken(LV_PALETTE_GREY, 1));
// Initialize red button style
lv_style_init(&style_red_button);
lv_style_set_bg_color(&style_red_button, lv_palette_main(LV_PALETTE_RED));
repeatPrime++;
}
}
Also, for some reason, label_countdown doesn't go to NULL after being set to that in the while loop.
Code for summary screen and update screen
Code to write the summary screen:
// Global variables for summary display screen
lv_obj_t * btn_prime = NULL;
lv_obj_t * label_prime = NULL;
lv_obj_t * btn_start = NULL;
lv_obj_t * label_start = NULL;.
const char* menuOptions[] = {"Option 1", "Option 2", "Option 3", "Option 4"};
const char* flowType[] = {"Steady", "Increasing", "Pulsatile"};
const char* summaryLabels[] = {"Menu option:", "Variable 1:", "Variable 2:", "Variable 3:", "Variable 4:", "Time 1:", "Variable 5:", "Variable 6:", "Time 2:"};
bool rememberSelections[9] = {false};
int summary_loop = 0;
void display_summary() {
// Clear the screen
lv_obj_clean(lv_scr_act());
lv_refr_now(NULL); // Force immediate screen refresh
// Display summary of selections
label_summary = lv_label_create(lv_scr_act());
lv_label_set_text(label_summary, "Summary of entries");
lv_obj_set_style_text_font(label_summary, &lv_font_montserrat_26, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_align(label_summary, LV_ALIGN_TOP_LEFT, 10, 10);
for (int i; i <= 8; i++){
if (rememberSelections[i] == true)
write_summary_screen(i);
}
summary_loop = 0;
// Prime Tubing Button
btn_prime = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn_prime, 200, 75);
lv_obj_align(btn_prime, LV_ALIGN_CENTER, 120, -75);
label_prime = lv_label_create(btn_prime);
lv_label_set_text(label_prime, "Prime");
lv_obj_set_style_text_font(label_prime, &lv_font_montserrat_22, LV_PART_MAIN | LV_STATE_DEFAULT); // Set the label font
lv_obj_center(label_prime);
lv_obj_add_event_cb(btn_prime, prime_tubing_handler, LV_EVENT_CLICKED, NULL);
// Start Perfusion Button
btn_start = lv_btn_create(lv_scr_act());
lv_obj_set_size(btn_start, 200, 75);
lv_obj_align(btn_start, LV_ALIGN_CENTER, 120, 75);
label_start = lv_label_create(btn_start);
lv_label_set_text(label_start, "Start");
lv_obj_set_style_text_font(label_start, &lv_font_montserrat_22, LV_PART_MAIN | LV_STATE_DEFAULT); // Set the label font
lv_obj_center(label_start);
lv_obj_add_event_cb(btn_start, start_perfusion, LV_EVENT_CLICKED, NULL);
}
void write_summary_screen(int i){
label_entered_option = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(label_entered_option, &lv_font_montserrat_18, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_align(label_entered_option, LV_ALIGN_TOP_LEFT, 10, 50 * (summary_loop + 1)); // Align top left with a y-offset
label_entered_datum = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(label_entered_datum, &lv_font_montserrat_18, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_align(label_entered_datum, LV_ALIGN_TOP_LEFT, 20, 20 + (50 * (summary_loop + 1))); // Align top left with a y-offset
switch (i) {
case 0:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text(label_entered_datum, menuOptions[menuSelection]);
lv_refr_now(NULL);
break;
case 1:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text(label_entered_datum, variable1[selection]);
lv_refr_now(NULL);
break;
case 2:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d", variable2);
lv_refr_now(NULL);
break;
case 3:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d", variable3);
lv_refr_now(NULL);
break;
case 4:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d", variable4);
lv_refr_now(NULL);
break;
case 5:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d:%02d", (time1/1000/60), (time1/1000%60)); // need to convert back to time
lv_refr_now(NULL);
break;
case 6:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d", variable5);
lv_refr_now(NULL);
break;
case 7:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d", varaible6);
lv_refr_now(NULL);
break;
case 8:
lv_label_set_text(label_entered_option, summaryLabels[i]);
lv_label_set_text_fmt(label_entered_datum, "%d:%02d", (time2/1000/60), time2/1000%60); // need to convert back to time
lv_refr_now(NULL);
break;
}
summary_loop++;
}
Compare this with the code for writing the update screen.
Code to write the update screen:
// Global variables for update screen
lv_obj_t * label_pause = NULL;
lv_obj_t * button_resume = NULL;
lv_obj_t * label_update_screen_option = NULL;
lv_obj_t * label_update_screen_datum = NULL;
const char* updateScreenLabels[] = {"Menu option:", "Variable 1:", "Update variable 1:", "Update variable 2:", "Update variable 3:", "Current runtime"};
bool rememberUpdateScreenSelections[6] = {false, false, false, false, false, true};
int updateScreenLoop = 0;
void update_screen() {
lv_obj_clean(lv_scr_act()); // Wipe the screen
lv_refr_now(NULL); // Force immediate screen refresh
for (int i = 0; i <= 5; i++){
if (rememberUpdateScreenSelections[i] == true)
write_update_screen(i);
}
updateScreenLoop = 0;
// Pause button
lv_obj_t * pauseButton = lv_btn_create(lv_scr_act()); // Create a button on the active screen
lv_obj_set_size(pauseButton, 150, 75); // Set button size (width, height)
lv_obj_align(pauseButton, LV_ALIGN_BOTTOM_MID, 0, -10); // Align button to the bottom middle
lv_obj_t * label_pauseButton = lv_label_create(pauseButton); // Create a label on the button
lv_label_set_text(label_pauseButton, "Pause"); // Set label text
// Set the font for the label
lv_obj_set_style_text_font(label_pauseButton, &lv_font_montserrat_22, LV_PART_MAIN | LV_STATE_DEFAULT); // Set the label font
lv_obj_center(label_pauseButton); // Center the label on the button
lv_obj_add_event_cb(pauseButton, pause_perfusion_handler, LV_EVENT_CLICKED, NULL); // Assign the event handler to the button
updateScreenTime = millis();
}
void write_update_screen(int i){
label_update_screen_option = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(label_entered_option, &lv_font_montserrat_30, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_align(label_entered_option, LV_ALIGN_TOP_LEFT, 10, 70 * (updateScreenLoop + 1)); // Align top left with a y-offset
label_update_screen_datum = lv_label_create(lv_scr_act());
lv_obj_set_style_text_font(label_entered_datum, &lv_font_montserrat_30, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_align(label_entered_datum, LV_ALIGN_TOP_LEFT, 20, 30 + (70 * (updateScreenLoop + 1))); // Align top left with a y-offset
switch (i) {
case 0:
lv_label_set_text(label_update_screen_option, updateScreenLabels[i]);
lv_label_set_text(label_update_screen_datum, menuOptions[menuSelection]);
lv_refr_now(NULL);
break;
case 1:
lv_label_set_text(label_update_screen_option, updateScreenLabels[i]);
lv_label_set_text(label_update_screen_datum, variable1[selection]);
lv_refr_now(NULL);
break;
case 2:
lv_label_set_text(label_update_screen_option, updateScreenLabels[i]);
lv_label_set_text_fmt(label_update_screen_datum, "%d", updateVariable1);
lv_refr_now(NULL);
break;
case 3:
lv_label_set_text(label_update_screen_option, updateScreenLabels[i]);
lv_label_set_text_fmt(label_update_screen_datum, "%d", updateVariable2);
lv_refr_now(NULL);
break;
case 4:
lv_label_set_text(label_update_screen_option, updateScreenLabels[i]);
lv_label_set_text_fmt(label_update_screen_datum, "%s", updateVariable3[number - 1]);
lv_refr_now(NULL);
break;
case 5:
run_time();
lv_label_set_text(label_update_screen_option, updateScreenLabels[i]);
lv_label_set_text_fmt(label_update_screen_datum, "%2d:%02d:%02d", currentHour, currentMinute, currentSecond);
lv_refr_now(NULL);
break;
}
updateScreenLoop++;
}
Code requiring }} at the end
And in case you are interested, here is the code at the end that requires to extra unpaired }} (it's copied from RTC / UDP / NTP Example (Timezone) example from Arduino with only minor changes related to calculating the time zone):
void onDeviceRemoved() {
Serial.print("USB device removed. ");
Serial.println(millis());
// Clear the screen
lv_obj_clean(lv_scr_act());
lv_refr_now(NULL); // Force immediate screen refresh
delay(200);
// Display summary of selections
lv_obj_t * label_USB_error = lv_label_create(lv_scr_act());
lv_label_set_text(label_USB_error, "USB drive disconnected.\nThis USB drive might not be compatible.\nPlease try a different USB drive if this persists.");
lv_obj_set_style_text_font(label_USB_error, &lv_font_montserrat_30, LV_PART_MAIN | LV_STATE_DEFAULT);
lv_obj_align(label_USB_error, LV_ALIGN_CENTER, 0, 0);
lv_refr_now(NULL);
while(1);
}}}