Dual frame buffers (full size) + imxrt1062 LCDIF = garbage on the screen

Description

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

Custom Teensy Micromod with SDRAM (aka Dev board V4) - experimental

What LVGL version are you using?

8.3.3 / 8.3.11

What do you want to achieve?

Use two full screen sized frame buffers in SDRAM

What have you tried so far?

See code below
I have set my flush CB to simpley set the next buffer address for the eLCDIF.
I have added a callback to the eLCDIF frame done interrupt that calls lv_disp_flush_ready and tells LVGL it can transmit the next frame.

I do see some rendering on the screen, but mostly garbage.

I know the library works as I have displayed a c array image copied into SDRAM and pointed to the LCDIF next buffer register (without LVGL)

Is there a better way to do this that is non-blocking?

Code to reproduce

my main.cpp code.
Im using an eLCDIF_t4 library that I put together earlier this week, alongside a SDRAM_t4 library that was written over the past 6 weeks by senior forum members on PJRC

#include "SDRAM_t4.h"
#include "eLCDIF_t4.h"
#include "wallpaper.c"
#include "lvgl.h"

SDRAM_t4 sdram;
eLCDIF_t4 lcd;
eLCDIF_t4_config lcd_config = {480, 8, 4, 4, 800, 8, 4, 4, 25, 24, 0, 0};


static lv_disp_drv_t disp_drv;          /*A variable to hold the drivers. Must be static or global.*/
EXTMEM lv_color_t lcdBuffer1[800*480] __attribute__((aligned(64)));
EXTMEM lv_color_t lcdBuffer2[800*480] __attribute__((aligned(64)));

/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;

void my_flush_cb(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p){
  static bool runLCD = false;
  if(!runLCD){
    lcd.runLCD(); // Turn on the LCDIF when the 1st frame is ready to be displayed
    runLCD = true;
  }
  lcd.setNextBufferAddress((uint32_t*)color_p);
}

void lcdCallback(){
  lv_disp_flush_ready(&disp_drv);
}


static void anim_x_cb(void * var, int32_t v)
{
    lv_obj_set_x(var, v);
}

static void anim_size_cb(void * var, int32_t v)
{
    lv_obj_set_size(var, v, v);
}

/**
 * Create a playback animation
 */
void lv_example_anim_2(void)
{
  lv_obj_t * obj = lv_obj_create(lv_scr_act());
  lv_obj_set_style_bg_color(obj, lv_palette_main(LV_PALETTE_RED), 0);
  lv_obj_set_style_radius(obj, LV_RADIUS_CIRCLE, 0);

  lv_obj_align(obj, LV_ALIGN_LEFT_MID, 10, 0);

  lv_anim_t a;
  lv_anim_init(&a);
  lv_anim_set_var(&a, obj);
  lv_anim_set_values(&a, 10, 50);
  lv_anim_set_time(&a, 1000);
  lv_anim_set_playback_delay(&a, 100);
  lv_anim_set_playback_time(&a, 300);
  lv_anim_set_repeat_delay(&a, 500);
  lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
  lv_anim_set_path_cb(&a, lv_anim_path_ease_in_out);

  lv_anim_set_exec_cb(&a, anim_size_cb);
  lv_anim_start(&a);
  lv_anim_set_exec_cb(&a, anim_x_cb);
  lv_anim_set_values(&a, 10, 240);
  lv_anim_start(&a);
}

IntervalTimer lv_tick;
#define LVGL_TICK_PERIOD 3
static void lv_tick_handler(void)
{
  lv_tick_inc(LVGL_TICK_PERIOD);
}

void setup() {
  Serial.begin(115200);
  Serial.print(CrashReport);
  
  //Start the SDRAM at 133Mhz
  if (!sdram.begin(32, 133,0)){
    Serial.println("SDRAM init fail :( ...");
  }
  else {
    Serial.println("SDRAM init succcess ;)");
    // Start the eLCFIF and set the current and next buffers (same source for this test)
    lcd.begin(BUS_24BIT, WORD_24BIT, lcd_config);
    lcd.onCompleteCallback(lcdCallback);
    lcd.setCurrentBufferAddress(lcdBuffer1);
    lcd.setNextBufferAddress(lcdBuffer1);
    //Once everyhing is ready, set the run bit on the eLCDIF to start pushing data to the display
    lv_init();
    /*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
    lv_disp_draw_buf_init(&disp_buf, lcdBuffer1, lcdBuffer2, 800*480);
    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 = 800;                 /*Set the horizontal resolution in pixels*/
    disp_drv.ver_res = 480;                 /*Set the vertical resolution in pixels*/
    disp_drv.full_refresh = 1;
    disp_drv.direct_mode = 0;
    lv_disp_t * disp = lv_disp_drv_register(&disp_drv); /*Register the driver and save the created display objects*/

  
    lv_tick.begin(lv_tick_handler, LVGL_TICK_PERIOD * 1000);  // Start ticker
    lv_example_anim_2();
  }
}


unsigned long previousMillis = 0;
void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= 5) {
    // Save the last time lv_timer_handler was called
    previousMillis = currentMillis;

    // Call lv_timer_handler
    lv_timer_handler();
  }
  //Serial.println("loop..");
}



Screenshot and/or video

If possible, add screenshots and/or videos about the current state.

@kisvegabor any chance you could take a peek at this?

Are you sure that you use elcdif in correct way?

As far as I understand lcd.setNextBufferAddress() should point to buffer which should be displayed. And while one buffer outputed onto the lcd another buffer may be fillled with new data. And when buffer successfully filled you point elcdif to another buffer etc. But my_flush_cb() called by lvgl and point to area that could refreshed with new data:

  • const lv_area_t * area - size of area size and coord
  • lv_color_t * color_p - data array should be filled on this area
    In this case lvgl buffer & elcdif frame buffer is different things.

So I think your need:

  • define 2 frame buffers for elcdif
  • point onto first of them in init function
  • in flush_cb() copy currently active buffer into inactive; and then fill area in inactive buffer with new data; after that switch elcdif onto currently inactive buffer
1 Like

nice example of using LVGL + mimxrt eLCDIF implemented in Zephyrproject RTOS, IMHO

I implemented a similar logic to the lvgl imxrt1064 demo, but instead of waiting for the s_framePending bool to change, and then tell lvgl that the flush is done, I tell lvgl that the flush is done though the LCDIF frame complete ISR.
You can see the setup code here:
https://github.com/lvgl/lv_port_nxp_mimxrt1064-evk/blob/master/hal/disp.c

Your suggestions actually requires a 3rd buffer, which will slow things down even more as all three will be in SDRAM

Here is a video of the issue:
https://drive.google.com/file/d/1RIstRCfK3c7m7GeE7aTyA8Zx08rUN4aR/view?usp=sharing

First, please verify if the driver workes well without LVGL. Just draw some colored rectangles to the frame buffer, swap the buffers, draw rectangles again, etc.

@kisvegabor I went with your suggestions and tried to do some moving color bars and it did not work well

I did some research and found that I was not setting one of the control registers for throttling the eLCDIF controller.


So now I can get the scrolling color bars to work without LVGL.

But with LVGL I don’t get the flashing and noise, but I don’t get a clear image either:

More updates:

Upgraded to v8.3.11 and see slightly better performance, but still seems like there is a caching issue (which makes no sense as it works fine with an independent fill buffer function, and I call a function to clear the cache before writing)

Figured this out - and was per my latest suspicion - the buffers were in a cached region of SDRAM and they need to be in a non cached region.
I added a 4MB no cache allocation to the last 4MB of SDRAM via the MPU config in the RT1062 and that fixed the issue

Getting a stable 58FPS right now with the tests.

What still baffles me though is why it barley worked on V8.3.3 and worked alot better with the exact same config on v8.3.11

1 Like