Migrating flush function from lvgl v6 to lvgl v9.2.2

Hi,

I am migrating from lvgl v6 to lvgl v9.2.2.

I registered two buffers, p_buffer_a points to buffer_A and p_buffer_b points to buffer_B.

But now lvgl made the variable lv_display_t private.
Maybe i can access in some other way, perhaps a public function ?!

Register Lcd and touch drivers:

// disp driver register   
    lv_display_t* display = lv_display_create(480, 854);
    lv_display_set_buffers(display, p_buffer_a, p_buffer_b, 480 * 854 * 2, LV_DISPLAY_RENDER_MODE_FULL);    
    lv_display_set_flush_cb(display, lcd_drv_display_flush);
// disp driver register    

// touch driver register    
    lv_indev_t* indev = lv_indev_create();
    lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
    lv_indev_set_read_cb(indev, touch_read);
// touch driver	register

Function to lvgl v9.2.2

void lcd_drv_display_flush( lv_display_t* drv, const lv_area_t* area, uint8_t* color_map )
{  
    uint32_t length = (uint32_t) ( 2 * ( lv_area_get_width(area) * lv_area_get_height(area) ) );  
        
    printf( "\nlength= %lu\n", length );

    if (color_map == p_buffer_a)
    { 
    
    #if lvgl_buffer_psram       
        Cache_WriteBack_Addr( (uint32_t)p_buffer_a, length );   
    #endif    

        lcd_drv_lcd_write_pixels_a( length, area );      
        lv_disp_flush_ready( drv );
    }
    else if(color_map == p_buffer_b )
    {

    #if lvgl_buffer_psram            
        Cache_WriteBack_Addr( (uint32_t)p_buffer_b, length );
    #endif

        lcd_drv_lcd_write_pixels_b( length, area );        
        lv_disp_flush_ready( drv );
    }
    else
    {
        printf("bug");
        return;
    }
}

Function to lvgl v6: Worked function

void lcd_drv_display_flush( lv_disp_drv_t* drv, const lv_area_t* area, lv_color_t* color_map ) 
{  
    (void) color_map;    

    uint32_t length = (uint32_t) ( 2 * ( lv_area_get_width(area) * lv_area_get_height(area) ) );  
        
    printf( "\nlength= %lu\n", length );

    if ( drv->buffer->buf_act == p_buffer_a )
    { 
    
    #if lvgl_buffer_psram       
        Cache_WriteBack_Addr( (uint32_t)p_buffer_a, length );   
    #endif    

        lcd_drv_lcd_write_pixels_a( length, area );      
        lv_disp_flush_ready( drv );
    }
    else if ( drv->buffer->buf_act == p_buffer_b )
    {

    #if lvgl_buffer_psram            
        Cache_WriteBack_Addr( (uint32_t)p_buffer_b, length );
    #endif

        lcd_drv_lcd_write_pixels_b( length, area );        
        lv_disp_flush_ready( drv );
    }
    else
    {
        printf("bug");
        return;
    }
}

Questions:
Will the new function to lvgl v9.2.2 work ?

Is there a way i can read “drv->buffer->buf_act” ?

Thank’s.

From what I tested, both options work:

void lcd_drv_display_flush( lv_display_t* drv, const lv_area_t* area, uint8_t* color_map )
{  
    uint32_t length = (uint32_t) ( 2 * ( lv_area_get_width(area) * lv_area_get_height(area) ) );  
        
    printf( "\nlength= %lu\n", length );

    if (color_map == (uint8_t*)p_buffer_a)
    { 
    
    #if lvgl_buffer_psram       
        Cache_WriteBack_Addr( (uint32_t)p_buffer_a, length );   
    #endif    

        lcd_drv_lcd_write_pixels_a( length, area );      
        lv_disp_flush_ready( drv );
    }
    else if(color_map == (uint8_t*)p_buffer_b )
    {

    #if lvgl_buffer_psram            
        Cache_WriteBack_Addr( (uint32_t)p_buffer_b, length );
    #endif

        lcd_drv_lcd_write_pixels_b( length, area );        
        lv_disp_flush_ready( drv );
    }
    else
    {
        printf("bug");
        return;
    }
}
void lcd_drv_display_flush( lv_display_t* drv, const lv_area_t* area, uint8_t* color_map )
{  
    (void)color_map;
	
	uint32_t length = (uint32_t) ( 2 * ( lv_area_get_width(area) * lv_area_get_height(area) ) ); 
        
    lv_draw_buf_t* p = lv_display_get_buf_active(drv); 
    uint8_t* p_buf_act = p->data;   
    
    printf( "\nlength= %lu\n", length );  // bytes

/*
    printf("\n\nPointers:\n");
    printf("color_map = %p\n", color_map);
    printf("p_buffer_a = %p\n", p_buffer_a);
    printf("p_buffer_b = %p\n", p_buffer_b);
    printf("p->data = %p\n", p->data);
    printf("p_buf_act = %p\n\n", p_buf_act);
*/    
    
    
    if (p_buf_act == (uint8_t*)p_buffer_a)
    { 
    
    #if lvgl_buffer_psram       
        Cache_WriteBack_Addr( (uint32_t)p_buffer_a, length );   
    #endif    

        lcd_drv_lcd_write_pixels_a( length, area );      
        lv_disp_flush_ready( drv );
    }
    else if(p_buf_act == (uint8_t*)p_buffer_b )
    {

    #if lvgl_buffer_psram            
        Cache_WriteBack_Addr( (uint32_t)p_buffer_b, length );
    #endif

        lcd_drv_lcd_write_pixels_b( length, area );        
        lv_disp_flush_ready( drv );
    }
    else
    {
        printf("bug");
        return;
    }
}

In “LV_DISPLAY_RENDER_MODE_FULL” work but with “LV_DISPLAY_RENDER_MODE_DIRECT” dont work, and i dont know why.


.

Hi,

Any suggestions?

Could it be a problem with the microcontroller cache or my display driver ?

@kisvegabor,

Some suggestion on why with LV_DISPLAY_RENDER_MODE_FULL works and with LV_DISPLAY_RENDER_MODE_DIRECT dont works ?

Using 1/2 display, two buffers with LV_DISPLAY_RENDER_MODE_PARTIAL, also dont work, same problem.
lv_display_set_buffers(display, p_buffer_a, p_buffer_b, 480 * 854, LV_DISPLAY_RENDER_MODE_PARTIAL);

OK so a description of what FULL, PARTIAL and DIRECT is for the rendering modes.

DIRECT is used if the display you are using doesn’t have internal RAM (GRAM) You can typically know if it does or doesn’t by having to set the memory location before writing to the display. If you do not need to set a memory location then the display will usually not have GRAM. These types of displays are typically connected to the MCU via an RGB “bus” which is 8, 16 or 24 lanes typically or with MIPI DSI (Display Serial Interface) which is 1, 2, 3 or 4 lanes and each lane is a differential set of connections to the MCU (2, 4, 6 or 8 pins used for the lanes). This requires a buffer size that is width * height * bytes per pixel

FULL is if the display does have GRAM but you are wanting the display to render the entire thing even if a small area gets updated. This requires the same buffer size as DIRECT. The difference between this and DIRECT is DIRECT keeps the buffers in synchronization when using 2 buffers. One buffer needs to get copied to the other. Where as FULL renders everything.

PARTIAL is just that. only the areas that have changed are what gets rendered. It might take several times of rendering to a partial buffer to update all of the areas that have changed on a display. This requires you to set the memory location of where to write the partial buffer to before you flush the buffer. The buffer size is typically set to width * height * bytes per pixel / 10. Using 2 buffers only helps performance wise if the MCU you are using supports DMA memory transfers which your MCU does.

If you provide me with the make and model of MCU you are using and also the display driver IC that is being used I will be able to provide you with a better solution. Without knowing those things it is going to be really hard to help you out. I also need to see the code you are using when you allocate the buffers. also what are the lcd_drv_lcd_write_pixels_* functions?

Hi @kdschlosser,

I know all about this.

I am using a ESP32-S3 with 32 MB flash with octal bus and 8 MB spiram com octal bus too.

I wrote the display driver(ili9806G), i wrote the lcd driver hardware module that stay inside esp32-s3 and i also wrote the GDMA module driver that stay intern to the esp32-s3. I do not use the drivers provided by esp-idf.

My driver is intel 8080 mode and the display has internal memory, ie, not a dumb display.
The hardware( pcb and so on) i designed.
Now i am using 8 bit bus, but the hardware designed, can use 16 bits bus( i need change a jumper in hardware and change something in display drivers).

I am using two full display buffers(854*480*2 bytes each buffer) allocated in external spiram, when i use LV_DISPLAY_RENDER_MODE_FULL.

With LV_DISPLAY_RENDER_MODE_FULL works ok, but It gets a bit heavy for the ESP32-S3, because when I press a simple button in screen, the lib lvgl sends 4 full screen buffers( 854*480*2 bytes ) to the display glass.

My question is why it works with LV_DISPLAY_RENDER_MODE_FULL mode and in other two modes it doesn’t work.

Could it be an error in my drivers ?

Thank’s.

Without see the code it is hard to know what is happening.

If you have internal GRAM it is going to be much better from a performance standpoint to use partial buffers and not full buffers. your buffers should be 1/10th the size of a full buffer. When you do this you would be able to get both buffers to fit into SRAM which will greatly improve performance.

Out of question, what is the reason why you are not using the I8080 driver that is included in the esp_lcd component? Is there some feature that you need that it is missing?

It looks like that display IC uses I8080 and also SPI 3 wire. the SPI 3 wire handles the commands and the I8080 is used only for transmitting of the buffer. Is that correct?

Are you able to compile and load a firmware for it to test to see if it functions properly without using your code? You will be able to view the code that is to be compiled as the code is open source. I can add the display driver to the code if you are able to supply me with the initialization commands you use. I would also need to know if SPI 3 wire is being used to pass the commands to the display.

If it works properly then we know there is something in your code that would be mucking it up. if we see the same behavior then we know there is something that needs to be done at the hardware level that is being missed. It narrows down where we actually need to look for the problem.

I have written many display drivers and there are a lot of off the wall things that need to be done to write the buffer data with some of the IC’s. as an example. a display IC that uses one command to set the memory address for the first chunk that is being sent and then uses a second command for all other chunks. The first command but always start at pixel 0, 0. and the entire display must be sent even tho the display IC has GRAM. This is the kind of setup where setting LVGL to use the “FULL” rendering is needed.

If you could share the code it would help out greatly that’s for sure. It would be easier for me to locate any possible issues that way. If you do not want the code to be publicly seen you can zip the code up and send it in a PM. You might have to add “.txt” to the end of the filename to be able to send it in a PM. I don’t remember if you have to do that or not.

Some questions answered above.

If you are willing to give this a shot. Clone and compile this library

You can compile it using a POSIX OS, Linux, MacOS… If you are running on Windows you will need to use WSL to do it.

You need to have some requirements installed.
* build-essential
* cmake
* ninja-build
* python
* libusb-1.0-0-dev

The command you will want to use to compile is the following…

python3 make.py esp32 BOARD=ESP32_GENERIC_S3 BOARD_VARIANT=SPIRAM_OCT --flash-size=32 --octal-flash DISPLAY=ili9488 INDEV=?

The INDEV you can set to what your touch IC is. If the driver is available it will compile. If it is not then it will fail.

You are going to need to add some additional commands depending on how you have the ESP32 set up for the serial connection.

If you are using jtag add the following to the build command

--enable-jtag-repl=y --enable-cdc-repl=n --enable-uart-repl=n

If you are using CDC add the following commands.

--enable-jtag-repl=n --enable-cdc-repl=y --enable-uart-repl=n

and the last is if you are using the UART

--enable-jtag-repl=n --enable-cdc-repl=n --enable-uart-repl=y

all other requirements including the ESP-IDF are collected by the build script. When the build is completed it will tell you what you need to do for the next step to flash the firmware.

Once you have the firmware flashed I will write the code to get the display fired up and running. This will let you know if everything is running properly with your board.

If everything is running like it should then we can flip back over to your code and go from there to see what is happening that is mucking it up.

Really need to isolate if it is a hardware issue or if it is a software issues and this is going to be the easiest way of doing that.

If it is a software problem it is going to be really hard to narrow down where the issue is without looking at the code. My suggestion is going to be to use the esp_lcd component that is built into the IDF to handle the communications with the display. Writing the panel driver is not that hard to do since there is not one available for your display on the component registry.

@kdschlosser,

Sorry, but I didn’t understand what you proposed.

I ended up checking that the coordinates( area->x1, area->x2, area->y1, area->y2 ) passed by the lvgl lib are correct, but the display is not reproducing the image correctly.

Or my driver it’s bugged or the ili9806G init are not corret ( maybe 36h command(MADCTL), my, mx, mv and so on ).

But it’s very strange that if it’s in the mode( LV_DISPLAY_RENDER_MODE_FULL ) that sends the full screen buffer it works.

I tested the hardware/software of ESP32-S3 without using lvgl library, and the results are ok.

tile000 image was generated in Image Converter — LVGL with LVGL v9 and RGB565 options.

{
    lv_area_t ar;
    
    ar.x1 = 198;
    ar.x2 = 281;  // 479;
    ar.y1 = 386;
    ar.y2 = 469;  // 853;

    uint32_t pix = (uint32_t) (ar.x2 - ar.x1 + 1) * (ar.y2 - ar.y1 + 1); 
    //uint32_t len = pix * 2;
         
    if(p_buffer_a == NULL)
    {
        ESP_LOGE(MY_APP_TAG, "Deu mherda: p_buffer_a = NULL");
    }
   

    //lv_color16_t cor = rgb888_to_lv_color16_t(0x00, 0xFF, 0x00);


    uint8_t* rgb565_array = tile000.data;


    for( uint32_t i = 0 ; i < pix ; i++ )
    {
        // Combinar os dois bytes para formar um valor uint16_t RGB565
        uint16_t rgb565 = (rgb565_array[i * 2 + 1] << 8) | rgb565_array[i * 2];

        // Extrair os componentes vermelho, verde e azul
        lv_color16_t color16;
        color16.red = (rgb565 >> 11) & 0x1F;    // Extrair 5 bits de vermelho
        color16.green = (rgb565 >> 5) & 0x3F;  // Extrair 6 bits de verde
        color16.blue = rgb565 & 0x1F;               // Extrair 5 bits de azul

        // Atribuir a cor convertida ao array lv_color16_t
        p_buffer_a[i] = color16;


        //p_buffer_a[i] = cor;


        /*
        p_buffer_a[i].red = 0x1F; 
        p_buffer_a[i].green = 0x3F;
        p_buffer_a[i].blue = 0x1F;
        */  
    }

    ili9806g_drv_display_flush(NULL, &ar, (uint8_t*)p_buffer_a );
}


void ili9806g_drv_display_flush( lv_display_t* drv, const lv_area_t* area, uint8_t* color_map )
{  
    (void)drv;   
            
    uint32_t length = (uint32_t) (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * 2;  
    
    printf( "\nlength = %lu\n", length );


    #if lvgl_buffer_psram       
        //Cache_WriteBack_Addr( (uint32_t)color_map, length ); 
        
        // flush data from cache to the physical memory
        esp_cache_msync( (void*)color_map, length, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_TYPE_DATA | ESP_CACHE_MSYNC_FLAG_UNALIGNED );
    #endif    

    ili9806g_drv_lcd_write_pixels_a( length, area );      
}


static lv_color16_t rgb888_to_lv_color16_t(uint8_t r, uint8_t g, uint8_t b)
{
    lv_color16_t color16;

    // Converter os componentes RGB888 para RGB565
    color16.red = r >> 3;       // Reduzir 8 bits para 5 bits
    color16.green = g >> 2;  // Reduzir 8 bits para 6 bits
    color16.blue = b >> 3;    // Reduzir 8 bits para 5 bits

    return color16;
}





tile000.c (83.7 KB)
tile000

I changed compiler option to:
Optimization Level: Debug without optimization (-O0)

Mode: LV_DISPLAY_RENDER_MODE_DIRECT

What I noticed is that the buffers don’t always alternate, is this normal ?

In fact, it is even alternating little between the 2 buffers, it keeps sending to buffer B several times, then from time to time it sends to buffer A

length= 256366 bytes
width = 473
height = 271
x1= 4
x2= 476
y1= 576
y2= 846


Pointers:
color_map = 0x3c280900
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c280900
p_buf_act = 0x3c280900


length= 256366 bytes
width = 473
height = 271
x1= 4
x2= 476
y1= 576
y2= 846


Pointers:
color_map = 0x3c348bc0
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c348bc0
p_buf_act = 0x3c348bc0


length= 26728 bytes
width = 257
height = 52
x1= 2
x2= 258
y1= 544
y2= 595


Pointers:
color_map = 0x3c348bc0
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c348bc0
p_buf_act = 0x3c348bc0


length= 256366 bytes
width = 473
height = 271
x1= 4
x2= 476
y1= 576
y2= 846


Pointers:
color_map = 0x3c280900
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c280900
p_buf_act = 0x3c280900


length= 253398 bytes
width = 471
height = 269
x1= 5
x2= 475
y1= 577
y2= 845


Pointers:
color_map = 0x3c348bc0
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c348bc0
p_buf_act = 0x3c348bc0


length= 256366 bytes
width = 473
height = 271
x1= 4
x2= 476
y1= 576
y2= 846


Pointers:
color_map = 0x3c280900
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c280900
p_buf_act = 0x3c280900


length= 253398 bytes
width = 471
height = 269
x1= 5
x2= 475
y1= 577
y2= 845


Pointers:
color_map = 0x3c348bc0
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c348bc0
p_buf_act = 0x3c348bc0


length= 26728 bytes
width = 257
height = 52
x1= 2
x2= 258
y1= 544
y2= 595


Pointers:
color_map = 0x3c348bc0
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c348bc0
p_buf_act = 0x3c348bc0


length= 253398 bytes
width = 471
height = 269
x1= 5
x2= 475
y1= 577
y2= 845


Pointers:
color_map = 0x3c280900
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c280900
p_buf_act = 0x3c280900


length= 250446 bytes
width = 469
height = 267
x1= 6
x2= 474
y1= 578
y2= 844


Pointers:
color_map = 0x3c348bc0
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c348bc0
p_buf_act = 0x3c348bc0


length= 256366 bytes
width = 473
height = 271
x1= 4
x2= 476
y1= 576
y2= 846


Pointers:
color_map = 0x3c280900
p_buffer_a = 0x3c280900
p_buffer_b = 0x3c348bc0
p->data = 0x3c280900
p_buf_act = 0x3c280900

Result:

The positions of the updates on the display seem correct to me.
The problem is that this partial updates are not understandable.

I changed compiler option to:
Optimization Level: optimize for performance (-O2)

Mode: LV_DISPLAY_RENDER_MODE_DIRECT

Same problema that before:

piece of my driver code:
while( hw_lcd_transfer_done_status() );

But if a put this in same piece of my driver code:

while( hw_lcd_transfer_done_status() )
{
    ets_delay_us(1);
}

I noticed that the compiler must be optimizing in some way that is changing the order of my driver instructions, but at least now the partial lcd updates are going to the correct position.

The problem is that this partial updates are not understandable.

Some sugestion @kdschlosser or @kisvegabor ???

May be some LVGL config that i have missing ?

I am using with real time operating system ( FreeRtos ), can be that i didnt config LVGL in correct way to work with a rtos ?

With this two configuration below, works Ok.

I think the LV_DISPLAY_RENDER_MODE_DIRECT option in the LVGL library is bugged.

lv_display_set_buffers(display, p_buffer_a, p_buffer_b, pixels_size_in_bytes, LV_DISPLAY_RENDER_MODE_PARTIAL );

lv_display_set_buffers(display, p_buffer_a, p_buffer_b, pixels_size_in_bytes / 8, LV_DISPLAY_RENDER_MODE_PARTIAL );

No one ?

Hi,

I think what you are missing is if(lv_display_flush_is_last(disp) == false) return;. In Direct mode the flush_cb is called for each area that LVGL renders. But probably you want to do anything only after the last area is also rendered.

Sorry @kisvegabor, but I didn’t understand.

In direct mode LVGL renderes all the dirty areas into the the frame buffer directly. flush_cb is called after each area, however you need to sync (that is swap the buffers, flush cache, etc) only when the last area is also rendered.

You can use lv_display_flush_is_last() to check if the last area was sent to the flush_cb.

But shouldn’t the LVGL library call flush callback only when all areas are rendered ?