Hello, I have a project where I use LVGL to display fancy graphics, I’ve been working with LVGL for quite a while and have successful projects with it.
This specific project uses an NRF24 transciever to receive data and display the numbers on the screen. Receive happens with an interrupt pin, when the pin is in FALLING state the function to print the receive is called. The MCU spends only 92 microseconds in the function so it’s almost no time at all. Right now I send from the transmitter every 250ms. So with that said, LVGL has all the time in the world to do it’s thing. I’m using a Teensy running 396mhz.
So I boot the device up but have the transmitter off, everything works fine, CPU usage at 3%. It can stay this way forever. I turn on the transmitter and data starts flowing in, after about 10 seconds CPU rises to 97% and the device is basically frozen. I turn of the transmitter but this high CPU usage stays the same until I reboot the device.
I am only displaying 2 numbers on the screen, and if I comment those out and only display the numbers in Serial.print, CPU usage still spikes.
I have no idea why this occurs. If I disable LVGL alltogether, this doesn’t happen at all. So I narrowed it down a bit more. If I comment out “lv_disp_drv_register(&disp_drv);” the CPU usage is nice and low.
I understand that this isn’t an LVGL fault or anything like that. But LVGL and the NRF24 doesn’t seem to like eachother.
Any ideas are appreciated as I don’t know what to do at this point.
EDIT: Further testing (this will blow your mind).
Crash occurs after about 10 seconds, so I activated the logging of LVGL and used TRACE to see everything. Now it can run for about 90 seconds until freeze. I thought it was just an odd coincidence so I reverted back to nog logging and once again freeze after 10 seconds. And so I activated the logging once again and saved the last part of it when it froze.
The NRF24 library and the Teensy core SPI library both have while loops in them. I think that LVGL is sensitive to these loops and if everything is not 100% right with timing, LVGL will freeze. I hope that someone knows a nice workaround for this issue.
Here is the log:
[Trace] (97.337, +0) lv_mem_buf_release: begin (address: 0x2000f648) (in lv_mem.c line #320)
[Trace] (97.337, +0) lv_mem_buf_get: begin, getting 40 bytes (in lv_mem.c line #266)
[Trace] (97.337, +0) lv_mem_buf_get: returning already allocated buffer (buffer id: 0, address: 0x2000f648) (in lv_mem.c line #289)
[Trace] (97.337, +0) lv_mem_buf_release: begin (address: 0x2000f648) (in lv_mem.c line #320)
[Trace] (97.337, +0) lv_mem_buf_get: begin, getting 64 bytes (in lv_mem.c line #266)
[Trace] (97.337, +0) lv_mem_buf_get: returning already allocated buffer (buffer id: 0, address: 0x2000f648) (in lv_mem.c line #289)
[Trace] (97.337, +0) lv_mem_buf_release: begin (address: 0x2000f648) (in lv_mem.c line #320)
[Trace] (97.337, +0) lv_mem_buf_get: begin, getting 99 bytes (in lv_mem.c line #266)
[Trace] (97.337, +0) lv_mem_buf_get: returning already allocated buffer (buffer id: 0, address: 0x2000f648) (in lv_mem.c line #289)
[Trace] (97.337, +0) lv_mem_buf_release: begin (address: 0x2000f648) (in lv_mem.c line #320)
[Trace] (97.337, +0) lv_mem_buf_get: begin, getting 9 bytes (in lv_mem.c line #266)
[Trace] (97.337, +0) lv_mem_buf_get: returning already allocated buffer (buffer id: 0, address: 0x2000f648) (in lv_mem.c line #289)
[Trace] (97.337, +0) lv_mem_buf_release: begin (address: 0x2000f648) (in lv_mem.c line #320)
[Trace] (97.337, +0) event_send_core: Sending event 22 to 0x2000f578 with 0x2004fe40 param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 23 to 0x2000f578 with 0x2004fe40 param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 24 to 0x2000f578 with 0x2004fe40 param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 44 to 0x2000f578 with 0x2004fbfc param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 44 to 0x2000f578 with 0x2004fbfc param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 25 to 0x2000f578 with 0x2004fe40 param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 23 to 0x2000e2a0 with 0x2004fe80 param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 24 to 0x2000e2a0 with 0x2004fe80 param (in lv_event.c line #405)
[Trace] (97.337, +0) event_send_core: Sending event 25 to 0x2000e2a0 with 0x2004fe80 param (in lv_event.c line #405)
[Trace] (97.337, +0) call_flush_cb: Calling flush_cb on (0;438)(138;479) area with 0x20225800 image pointer (in lv_refr.c line #979)
[Trace] (97.337, +0) lv_mem_free: freeing 0x2000f648 (in lv_mem.c line #161)
[Trace] (97.337, +0) _lv_disp_refr_timer: finished (in lv_refr.c line #316)
[Trace] (97.337, +0) lv_timer_exec: timer callback 0x7a21 finished (in lv_timer.c line #314)
[Trace] (97.337, +0) lv_timer_handler: finished (1 ms until the next timer call) (in lv_timer.c line #144)
Code to reproduce:
#include "Arduino.h"
#include <SPI.h>
#include <RF24.h>
#include "../lib/ILI948x_t4_mm/ILI948x_t4_mm.h"
#include "../lib/lvgl/lvgl.h"
int NRF_IRQ = 1;
long powerPressedStartTime = 0;
RF24 NRF24(0, 10); // Declare object from nRF24 library (Create your wireless SPI)
bool readyToRock = false;
void NRF24_InterruptReceive();
#define LVGL_TICK_PERIOD 1
IntervalTimer tick;
const uint16_t screenWidth = 320;
const uint16_t screenHeight = 480;
ILI948x_t4_mm lcd = ILI948x_t4_mm(17, 16, 5);
/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;
//LVGL two half screen sized buffers in DMA memory
DMAMEM static lv_color_t buf_1[(screenWidth * screenHeight)/2];
DMAMEM static lv_color_t buf_2[(screenWidth * screenHeight)/2];
static lv_disp_drv_t disp_drv;
const uint64_t pAddress = 0xB00B1E5000LL; //Create a pipe addresses for the 2 nodes to communicate over, the "LL" is for LongLong type
void my_log_cb(const char * buf) {
Serial.println(buf);
}
static void lv_tick_handler(void) {
lv_tick_inc(LVGL_TICK_PERIOD);
}
FASTRUN void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p) {
arm_dcache_flush((void*)buf_1, sizeof(buf_1)); // always flush cache after writing to DMAMEM variable that will be accessed by DMA
arm_dcache_flush((void*)buf_2, sizeof(buf_2)); // always flush cache after writing to DMAMEM variable that will be accessed by DMA
lcd.pushPixels16bitDMA((uint16_t*)(color_p),area->x1, area->y1, area->x2, area->y2);
}
FASTRUN void flushCB() {
lv_disp_flush_ready(&disp_drv);
}
void NRF24_Initiate() {
NRF24.begin(); // Start the nRF24 module
NRF24.maskIRQ(1,1,0); // mask all IRQ triggers except for receive (1 is mask, 0 is no mask)
NRF24.setPALevel(RF24_PA_LOW); // Set power level to low, won't work well at higher levels (interfer with receiver)
NRF24.openReadingPipe(1, pAddress); // open pipe o for recieving meassages with pipe address
NRF24.startListening(); // Start listening for messages
pinMode(NRF_IRQ, INPUT);
attachInterrupt(NRF_IRQ, NRF24_InterruptReceive, FALLING); //Create interrupt: 0 for pin 2 or 1 for pin 3, the name of the interrupt function or ISR, and condition to trigger interrupt
}
void NRF24_InterruptReceive() {
long int t1 = micros();
uint8_t payloadArray[12];
NRF24.read(&payloadArray, sizeof(payloadArray)); // RECEIVE DATA INTO BYTE ARRAY, THIS IS CAUSING FREEZE.
long int t2 = micros();
Serial.print("Time taken by the task: "); Serial.print(t2-t1); Serial.println(" microseconds");
}
void setup(){
Serial.begin(57600); //start serial to communicate process
Serial.print(CrashReport);
pinMode(7, OUTPUT); // RDX
digitalWrite(7, HIGH); // Pull RDX high
lcd.begin(24);
lcd.setFrameRate(68);
lcd.setRotation(2);
lcd.invertDisplay(true);
lcd.onCompleteCB(&flushCB);
pinMode(34, OUTPUT); // LCD BACKLIGHT
digitalWrite(34, HIGH); // LCD BACKLIGHT
lv_log_register_print_cb(my_log_cb); // LOGGING OF LVGL
lv_init();
lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, (screenWidth * screenHeight)/2);
lv_disp_drv_init(&disp_drv);
disp_drv.flush_cb = my_flush_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.hor_res = screenWidth;
disp_drv.ver_res = screenHeight;
lv_disp_drv_register(&disp_drv);
tick.begin(lv_tick_handler, LVGL_TICK_PERIOD * 1000); // Start ticker
}
const int loopDelay1 = 3;
unsigned long loopTimeNow1 = 0;
void loop() {
if (millis() > loopTimeNow1 + loopDelay1) {
lv_timer_handler();
loopTimeNow1 = millis();
}
if (!readyToRock && millis() > 5000) { // INITIATE AFTER EVERYTHING HAS BOOTED.
NRF24_Initiate();
readyToRock = true;
}
}