Hardfault when flushing display with DMA (v8/v7)

Description

I’ve built my own 8 bit parallel (8080) display driver that supports ILI9488/9486/9481 and R61529 on a Teensy MicroMod.

I have a very simple test sketch running lvgl v8 (the issue happens on v7.11 as well).
When I flush the display on a polling method, everything works fine and runs infinitely
But when I flush the display with DMA I get a hard fault on the following line of code:

DACCVIOL) Data Access Violation
(MMARVALID) Accessed Address: 0xC (nullptr)

The code is set up so that LVGL calls a pushPixle funciton in the lcd library that loads lv_color_t into DMA and transfers it to the GPIO.

Then, when the DMA transfer completes it triggers a callback function that will call lv_disp_flush_ready instead of getting stuck in some while loop while it waits for the DMA transfer to complete.

When using the standard blocking method (polling), I call lv_disp_flush_ready after it finishes the transfer and all works well.

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

Teensy Micromod (IMXRT1062)

What do you want to achieve?

Flush LVGL with DMA

What have you tried so far?

Code to reproduce

#include "ILI948x_t4_mm.h"
#include "lvgl.h"
ILI948x_t4_mm lcd = ILI948x_t4_mm(17, 16, 5); //(dc, cs, rst)
const int screenWidth = 480;
const int screenHeight = 320;

#define LVGL_TICK_PERIOD 5
/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;

/*Static or global buffer(s). The second buffer is optional*/
static lv_color_t buf_1[screenWidth * screenHeight /4];

IntervalTimer tick;
static void lv_tick_handler(void)
{
  lv_tick_inc(LVGL_TICK_PERIOD);
}



static lv_obj_t * meter;
static void set_value(void * indic, int32_t v)
{
    lv_meter_set_indicator_value(meter, indic, v);
}

/**
 * A simple meter
 */
void lv_example_meter_1(void)
{
    meter = lv_meter_create(lv_scr_act());
    lv_obj_center(meter);
    lv_obj_set_size(meter, 200, 200);

    /*Add a scale first*/
    lv_meter_scale_t * scale = lv_meter_add_scale(meter);
    lv_meter_set_scale_ticks(meter, scale, 41, 2, 10, lv_palette_main(LV_PALETTE_GREY));
    lv_meter_set_scale_major_ticks(meter, scale, 8, 4, 15, lv_color_black(), 10);

    lv_meter_indicator_t * indic;

    /*Add a blue arc to the start*/
    indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_BLUE), 0);
    lv_meter_set_indicator_start_value(meter, indic, 0);
    lv_meter_set_indicator_end_value(meter, indic, 20);

    /*Make the tick lines blue at the start of the scale*/
    indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_BLUE), lv_palette_main(LV_PALETTE_BLUE), false, 0);
    lv_meter_set_indicator_start_value(meter, indic, 0);
    lv_meter_set_indicator_end_value(meter, indic, 20);

    /*Add a red arc to the end*/
    indic = lv_meter_add_arc(meter, scale, 3, lv_palette_main(LV_PALETTE_RED), 0);
    lv_meter_set_indicator_start_value(meter, indic, 80);
    lv_meter_set_indicator_end_value(meter, indic, 100);

    /*Make the tick lines red at the end of the scale*/
    indic = lv_meter_add_scale_lines(meter, scale, lv_palette_main(LV_PALETTE_RED), lv_palette_main(LV_PALETTE_RED), false, 0);
    lv_meter_set_indicator_start_value(meter, indic, 80);
    lv_meter_set_indicator_end_value(meter, indic, 100);

    /*Add a needle line indicator*/
    indic = lv_meter_add_needle_line(meter, scale, 4, lv_palette_main(LV_PALETTE_GREY), -10);

    /*Create an animation to set the value*/
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_exec_cb(&a, set_value);
    lv_anim_set_var(&a, indic);
    lv_anim_set_values(&a, 0, 100);
    lv_anim_set_time(&a, 2000);
    lv_anim_set_repeat_delay(&a, 100);
    lv_anim_set_playback_time(&a, 500);
    lv_anim_set_playback_delay(&a, 100);
    lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
    lv_anim_start(&a);
}

FASTRUN void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
     *`put_px` is just an example, it needs to implemented by you.*/
      lcd.pushPixels16bitDMA((uint16_t*)(color_p),area->x1, area->y1, area->x2, area->y2);
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    //lv_disp_flush_ready(disp_drv);
}

FASTRUN void flushCB()
{
  static lv_disp_drv_t * disp_drv;
  lv_disp_flush_ready(disp_drv);
  }

void setup() {
   
  Serial.begin(115200);
  delay(1000);
  Serial.print(CrashReport);
  
  pinMode(12, OUTPUT);
  pinMode(14, OUTPUT);
  digitalWrite(12, HIGH);
  digitalWrite(14, HIGH);
  
  lcd.begin(12);
  lcd.setRotation(3);
  lcd.onCompleteCB(&flushCB);
  
  
  lv_init();
  lv_disp_draw_buf_init(&disp_buf, buf_1, NULL, screenWidth * screenHeight /4);
  
  static lv_disp_drv_t disp_drv;          /*A variable to hold the drivers. Must be static or global.*/
  lv_disp_drv_init(&disp_drv);            /*Basic initialization*/
  disp_drv.draw_buf = &disp_buf;          /*Set an initialized buffer*/
  disp_drv.flush_cb = my_flush_cb;        /*Set a flush callback to draw to the display*/
  disp_drv.hor_res = screenWidth;                 /*Set the horizontal resolution in pixels*/
  disp_drv.ver_res = screenHeight;                 /*Set the vertical resolution in pixels*/
  lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/
  
  Serial.println("tick.begin");
  tick.begin(lv_tick_handler, LVGL_TICK_PERIOD * 1000);  // Start ticker
  lv_example_meter_1();
   
}

const int loopDelay1 = 5; // Make a request every 500ms
unsigned long timeNow1 = 0;
void loop() {
if (millis() > timeNow1 + loopDelay1)
    {
      timeNow1 = millis();
      lv_timer_handler(); /* let the GUI do its work */
    }

}

Screenshot and/or video

It loads the first 1/4 of the image and hard-faults when transferring with DMA.

When transferring polling method:

FASTRUN void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
     *`put_px` is just an example, it needs to implemented by you.*/
      lcd.pushPixels16bit((uint16_t*)(color_p),area->x1, area->y1, area->x2, area->y2);
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

The static disp_drv variable inside flushCB is never initialized and will always be 0, if I read your code correctly. I think that’s not what you intended. :slightly_smiling_face:

1 Like

It’s not what I intended :slight_smile:
I’ll try make it global and then test it again.

But I did get the same behavior in the past even when I had setup the code the same as the polling method (but calling the DMA transfer). It would still hard-fault at the same line.
That’s why I had implemented this method to register a callback when DMA is done transferring

FASTRUN void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
    /*The most simple case (but also the slowest) to put all pixels to the screen one-by-one
     *`put_px` is just an example, it needs to implemented by you.*/
      lcd.pushPixels16bitDMA((uint16_t*)(color_p),area->x1, area->y1, area->x2, area->y2);
    /* IMPORTANT!!!
     * Inform the graphics library that you are ready with the flushing*/
    lv_disp_flush_ready(disp_drv);
}

Well, @embeddedt, as usual - you were right :wink:
Now for some reason the FPS/CPU usage widget is not displaying… v8.1.0 (dev) - so I can’t compare DMA to Polling :frowning:

You’ve probably checked them already, but these are the two options that need to be enabled for that.

Indeed they have both been set to 1. No other dependencies ?

No, that should be it. Perhaps check that you’re editing the right lv_conf.h file, and/or do a full clean & rebuild of the project just in case it didn’t pick up the changes.

Still no luck. I’ve tried it all…

The CPU usage widget is drawn here, so if you have a debugger, you could set a breakpoint on this line and see if it gets there.

You do also need LV_USE_LABEL enabled (although I assume that is the case). I wasn’t aware of it till I read that code again.

1 Like

My only other guess would be that LVGL thinks your screen is bigger than it actually is, and thus the bottom right gets cut off. However, I highly doubt that is the case, otherwise your gauge wouldn’t be centered.

1 Like

It’s indeed a mystery.
I even tried to make a main screen object and resized it (made it smaller than 480x320) but it still didn’t show up.

I’ll try play around with it a little more, but it’s not urgent right now.