Getting LVGL v9.0 to work with LovyanGFX and ILI9488 screen

Description

Has anyone been able to get the new LVGL v.9.0 to work properly with LovyanGFX? In particular the ILI9488 screen. Due to the nature of my project’s design I have to utilize LovyanGFX. Unfortunately, I can’t even get the basic display callback to work on the new v9.0.

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

Arduino 2.2.1 and ESP32C3

What LVGL version are you using?

9.0

What do you want to achieve?

Just get the basic callback to work without the device crashing with a stack overflow error.

What have you tried so far?

I have tried porting over the display callback from working v8.3.11 and I tried the basic display callback outlined in the getting started. Neither works.

Code to reproduce

Add a code snippet which can run in the simulator. It should contain only the relevant code that compiles without errors when separated from your main code base.

The code block(s) should be formatted like:

// Display flushing 
void my_disp_flush( lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p )
{
   if (tft.getStartCount() == 0)
    {   // Processing if not yet started
        tft.startWrite();
    }
    tft.pushImageDMA( area->x1
                    , area->y1
                    , area->x2 - area->x1 + 1
                    , area->y2 - area->y1 + 1
                    , ( lgfx::swap565_t* )&color_p->full);
    lv_disp_flush_ready( disp );
}

// the above fails
// this also fails, but it fails during compilation.
void my_disp_flush(lv_display_t * disp, const lv_area_t * area,  void * px_map)
{
  uint16_t * buf16 = (uint16_t)px_map; /*Let's say it's a 16 bit (RGB565) display*/
    int32_t x, y;
    for(y = area->y1; y <= area->y2; y++) {
        for(x = area->x1; x <= area->x2; x++) {
            tft.drawPixel(x, y, *buf16);
            buf16++;
        }
    }
    lv_disp_flush_ready( disp );
}

Screenshot and/or video

Hello, does the disp flush get called at all? Do note that the setup and init of displays has also changed in V9.

I am currently also experimenting with it altough I am not using LovyanGFX. Can you show how you initialize everything?

Tinus,
Good question… below is my initialization code.

/Set to screen resolution/
#define TFT_HOR_RES 320
#define TFT_VER_RES 480

/LVGL draw into this buffer, 1/10 screen size usually works well. The size is in bytes/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))

IN SETUP…

uint8_t draw_buf[DRAW_BUF_SIZE];
lv_display_t * disp;

disp = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
lv_display_set_flush_cb(disp, my_disp_flush);
lv_display_set_buffers(disp, draw_buf, NULL, DRAW_BUF_SIZE, 
LV_DISPLAY_RENDER_MODE_PARTIAL);


//Initialize the input device driver
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); 
lv_indev_set_read_cb(indev, my_touchpad_read);

I do it like this:

const unsigned int screenWidth = 480;
const unsigned int screenHeight = 320;
const unsigned int lvBufferSize = screenWidth * screenHeight / 10 * (LV_COLOR_DEPTH / 8);
uint8_t lvBuffer[lvBufferSize];
...
void setup() {
//...
    static auto *lvDisplay = lv_display_create(screenWidth, screenHeight);
    lv_display_set_color_format(lvDisplay, LV_COLOR_FORMAT_RGB565);
    lv_display_set_flush_cb(lvDisplay, [](lv_display_t* display, const lv_area_t* area, unsigned char* data) {
            uint32_t w = lv_area_get_width(area);
            uint32_t h = lv_area_get_height(area);
            // swap does not help
            //lv_draw_sw_rgb565_swap(data, w*h);
            // printing the data content here indicates that the bytes are correct, e.g. 774D79967996 shows me that apart from a 1px border, the screen gets RGB565 color of 7996 (2 bytes in HEX) until the end of the line
            lcd.pushImage(area->x1, area->y1, w, h, data);
            lv_display_flush_ready(display);
        });
    lv_display_set_buffers(lvDisplay, lvBuffer, nullptr, lvBufferSize, LV_DISPLAY_RENDER_MODE_PARTIAL);
}
void loop()
{
    /* let the GUI do its work */
    lv_timer_handler(); 
    lv_tick_inc(5);
    delay(5);
}

However, when I want to display just the standard label:

    lv_obj_t *label = lv_label_create( lv_scr_act() );
    lv_label_set_text( label, "Hello Arduino, I'm LVGL!" );
    lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );

I do get a distorted image (the label is shifted half a screen to the right and the bottom half of the text is visible).

I know that the lcd is set up correctly, as I can display a JPEG with JPEGDEC (RGB565_BIG_ENDIAN pixel type) flawlessly:

int jpegDraw(JPEGDRAW* pDraw)
{
    lcd.pushImage(pDraw->x, pDraw->y, pDraw->iWidth, pDraw->iHeight, pDraw->pPixels);
    return 1;
}

Which does exactly the same as the draw-callback for lvgl.
What am I missing?
lv_conf.h is set to:

#define LV_COLOR_DEPTH 16
#define LV_USE_OS   LV_OS_NONE

I know it worked on the old lvgl 8.3 but I do not have the library or the lv_conf.h from three weeks ago.
My board is the Makerfabs 3.5" 320x480 TFT LCD driver is ILI9488, using 16bits parallel connection.
Can anyone spot the issue?

I now do have it working.
You can use this as a lvgl setup if you have LogyanGFX working:

const unsigned int screenWidth = 480;
const unsigned int screenHeight = 320;
const unsigned int lvBufferSize = screenWidth * screenHeight / 10 * (LV_COLOR_DEPTH / 8);
uint8_t lvBuffer[lvBufferSize];

static LGFX lcd;
void setup()
{
    lcd.init();
    lcd.setRotation(3);

    // setting up LVGL
    lv_init();
    
#if LV_USE_LOG != 0
    lv_log_register_print_cb([]( lv_log_level_t level, const char * buf ) {
      LV_UNUSED(level);
      Serial.println(buf);
      Serial.flush();
    } );
#endif
    
    static auto *lvDisplay = lv_display_create(screenWidth, screenHeight);
    //lv_display_set_color_format(lvDisplay, LV_COLOR_FORMAT_RGB565);
    lv_display_set_color_format(lvDisplay, LV_COLOR_FORMAT_RGB565);
    lv_display_set_flush_cb(lvDisplay, [](lv_display_t* display, const lv_area_t* area, unsigned char* data) {
            uint32_t w = lv_area_get_width(area);
            uint32_t h = lv_area_get_height(area);
            lv_draw_sw_rgb565_swap(data, w*h);
            lcd.pushImage(area->x1, area->y1, w, h, (uint16_t*)data);
            lv_display_flush_ready(display);
        });
    lv_display_set_buffers(lvDisplay, lvBuffer, nullptr, lvBufferSize, LV_DISPLAY_RENDER_MODE_PARTIAL);

    static auto *lvInput = lv_indev_create();
    lv_indev_set_type(lvInput, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(lvInput, [](lv_indev_t* dev, lv_indev_data_t* data) {
      uint16_t touchX, touchY;

      data->state = LV_INDEV_STATE_REL;

      if (lcd.getTouch(&touchX, &touchY))
      {
          data->state = LV_INDEV_STATE_PR;
          data->point.x = touchX;
          data->point.y = touchY;
      }
    });

    lv_obj_t *label = lv_label_create( lv_scr_act() );
    lv_label_set_text( label, "Hello Arduino, I'm LVGL!" );
    lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );
}

void loop()
{
    lv_task_handler(); 
    lv_tick_inc(5);
    delay(5);
}

My issue was that the draw callback had to use lcd.pushImage(..., (uint16_t*)data);
Hope this helps you as well!

danie1kr,

THANK YOU so much!!! You really made my day! You provided the necessary insights to allow me to get my code working with LVGL v9.0! Below is what I used which is slightly different from the code you presented. I wanted to share it with others for it may be helpful for those who faces challenges in getting LVGL v9.0 working with LovyanGFX and ILI9488 screens.

void my_disp_flush(lv_display_t* display, const lv_area_t* area, unsigned char* data) {

        uint32_t w = lv_area_get_width(area);
        uint32_t h = lv_area_get_height(area);
        lv_draw_sw_rgb565_swap(data, w*h);
       
        if (tft.getStartCount() == 0)
        {   // Processing if not yet started
             tft.startWrite();
        }
        tft.pushImageDMA( area->x1
                , area->y1
                , area->x2 - area->x1 + 1
                , area->y2 - area->y1 + 1
                ,(uint16_t*) data); 

        lv_display_flush_ready(display);
    }

/Read the touchpad/
void my_touchpad_read( lv_indev_t * indev, lv_indev_data_t * data )
{static uint16_t prev_x, prev_y;
if (ctp.touched()) {

 // Retrieve a point  
 TS_Point p = ctp.getPoint();
data->point.x = p.x;  
data->point.y = p.y;  
data->state = LV_INDEV_STATE_PR;
prev_x = data->point.x;
prev_y = data->point.y;
// the following is for debugging

// tft.drawPixel(prev_x,prev_y,TFT_RED);
} else {

data->state = LV_INDEV_STATE_REL;

}
}

void setup()
{
///LCD INIT CODE HERE

lv_init();

static auto *lvDisplay = lv_display_create(TFT_HOR_RES, TFT_VER_RES);
//lv_display_set_color_format(lvDisplay, LV_COLOR_FORMAT_RGB565);
lv_display_set_flush_cb(lvDisplay, my_disp_flush);
lv_display_set_buffers(lvDisplay, lvBuffer, nullptr, DRAW_BUF_SIZE, LV_DISPLAY_RENDER_MODE_PARTIAL);


//Initialize the input device driver
lv_indev_t * indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); //Touchpad should have POINTER type
lv_indev_set_read_cb(indev, my_touchpad_read);


lv_obj_t *label = lv_label_create( lv_scr_act() );
lv_label_set_text( label, "Hello Arduino, I'm LVGL!" );
lv_obj_align( label, LV_ALIGN_CENTER, 0, 0 );

}

3 Likes

@supremeneuron May I ask what hardware you are using as I have a few Esp32 c3 boards (seeed xiao… Espressif dev kits) and a ili9488 with a 40pin fpc connector.

I haven’t looked into the simplest way to have connect fpc lcd ribbon to my mcu… I will assume some kind of adapter?

R,

I actually have a custom made board. I am not using anything that is purchased from a marketplace. My design utilizes a ESP32C3 + FPC connector to a 3.5" LCD. I did this because I could not find a suitable mechanical interface board between the raw LCD. Also this allowed for my design to nicely fit in a custom 3D printed case.