Pass a uint32_t buffer to the driver write uint16_t pixel in unconventional positions

Hi,

Description

I am trying to write a driver for the esp32 i2s module a while already.
I want to use i2s module in lcd parallel mode (8bits) with dma and two alterneted buffers.

The espressif team replied to me that it is not possible to pass a buffer of uint8_t directly to the i2s module using dma.
link: “https://www.esp32.com/viewtopic.php?f=13&t=12905&start=30”.
"Hi, Baldhead

Thank you for your suggestion. Due to hardware limitations, the DMA transfer data size is at least one word long. Can you provide an LCD data sheet, maybe we can use 16-bit mode to drive it.

thanks !!"

I started the studies by the two links below a long time ago:

https://blog.littlevgl.com/2019-02-02/use-ipod-nano6-lcd-for-littlevgl
https://github.com/espressif/esp-iot-solution/tree/master/components/i2s_devices/lcd_common

The problem is that i need to copy a “lv_color_t” buffer (in my case uint16_t each pixel), to a uint32_t buffer byte by byte.
This way there is a big waste of memory and time to copy the buffer ( unfortunately it’s the only way ).
I studied the link “https://github.com/espressif/esp-iot-solution/tree/master/components/i2s_devices/lcd_common”, of espressif and that’s how it’s done using dynamically allocated memory. My driver is using statically allocated memory.

example:

#define buffer_size 30720 // in bytes

DMA_ATTR lv_color_t buffer_a[buffer_size/2]; // littlevgl buffer. // buffer_size/2 = 15360 uint16_t.
DMA_ATTR lv_color_t buffer_b[buffer_size/2]; // littlevgl buffer.

static DMA_ATTR uint32_t buf_a[buffer_size/4]; // dma buffer. // buffer_size/4 = 7680 uint32_t.
static DMA_ATTR uint32_t buf_b[buffer_size/4]; // dma buffer.

I need to copy the littlevgl buffer(buffer_a) to dma buffer(buf_a) byte by byte using a single position in uint32_t buffer to store only one single byte like:

ptr = (uint8_t *) &buffer_a;    // buffer_a are uint16_t.

for ( i = 0 ; i < length ; i = i + 2 )    // length <= 7680 bytes.  loop max 7680/2 = 3840 times.
{        
    buf_a[ i ] = ptr[ i ];                // low pixel byte. 
    buf_a[ i + 1 ] = ptr[ i + 1 ];    // high pixel byte.        

 // the buf_a address inc in 4 bytes while the buffer_a(ptr) address inc in 1 byte.
}    

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

MCU: ESP32.
Framework: esp32-idf.

What do you want to achieve?

I would like to pass the littlevgl buffer directly to i2s module using dma.

I would like that littlevgl write directly in buffer uint32_t like so (if possible obviously):

uint16_t pixel0, pixel1, pixel2, pixel3, …, pixel N.
uint32_t buf_a[ ];

buf_a[0] = pixel0 low byte.
buf_a[1] = pixel0 high byte.

buf_a[2] = pixel1 low byte.
buf_a[3] = pixel1 high byte.

buf_a[4] = pixel2 low byte.
buf_a[5] = pixel2 high byte.

and so on.

This way i only declares 2 buffers uint32_t and the littlevgl write directly in that buffers.
This saves a little memory (2 buffers instead of 4 buffers) and also saves time to copy one buffer to another.

What have you tried so far?

Copy the buffer directly in my i2s driver.

Thank’s.

Just to be sure I understood well:

  1. You have 16 bit color depth
  2. You want the 2 bytes of pixel to be written in an uint32_t byte buffer (8 bytes/px)

Is that correct?

Hi Kisvegabor,

Yes.

Yes. Like the link:
https://github.com/espressif/esp-iot-solution/blob/40cec135eace36cf11ab72988e27546a2c0d025b/components/i2s_devices/lcd_common/i2s_lcd.c#L351”.

Thank’s.

Hi,

Only correcting the lvgl buffer size ( wrong value “buffer_size/2” = 15360 uint16_t = 30720 bytes ).

Now lvgl buffers, buffer_a and buffer_b, are 3840 uint16_t ( or 7680 bytes ).

( 3840 = 30720 / 8 ) and not ( 15360 = 30720 / 2 ).

#define pixels_size 3840

#define pixels_size_in_bytes ( pixels_size * sizeof(lv_color_t) )     // 7680 bytes

***********************************************************************************
// lvgl graphic buffers.
DMA_ATTR lv_color_t buffer_a[ pixels_size ];    // 3840 pixels = 7680 bytes. 
DMA_ATTR lv_color_t buffer_b[ pixels_size ];    // 3840 pixels = 7680 bytes. 

***********************************************************************************
// dma buffers.
static DMA_ATTR uint32_t buf_a[ pixels_size_in_bytes ];    // pixels_size_in_bytes = 7680 bytes.  uint32_t buf_a -> 30720 bytes ( 7680 * 4 ).
static DMA_ATTR uint32_t buf_b[ pixels_size_in_bytes ];

***********************************************************************************

// #define descriptor_size ( ( pixels_size_in_bytes * 4 ) / 4092 ) + 1 
// static DMA_ATTR lldesc_t dma_desc_buf_a[ descriptor_size ]; 

static DMA_ATTR lldesc_t dma_desc_buf_a[ 8 ];  
static DMA_ATTR lldesc_t dma_desc_buf_b[ 8 ];

Thank’s.

Hi,

Based on what expressif told me, i can only write uint8_t on the i2s parallel port (8 outputs pins) using a word (uint32_t).
This using dma.

As 1 pixel = 2 bytes, then…

Thank’s.

I can imagine this:
Declare DMA_ATTR lv_color_t buffer_a[ pixels_size * 4]. But still, tell LittlevGL you have pixels_size buffers. So lvgl will use the 1/4 of the buffer. In flush_cb you can rearrange the bytes into the same buffer with a for cycle (starting from the end to not overwrite the content). Obviously it has a performance impact. A 320x240 display has 75k pixels. If this operation takes 10 clock cycles per pixel then on 200 MHz it’s 3,8 ms.

Making LittlevGL support such a special format is possible but not trivial.

Hi kisvegabor,

I thought it was relatively simple.

Thank’s.

Hi kisvegabor,

I did a test with a buffer copy but it did almost 800 us to copy 7680 pixels (15360 bytes).

And this corresponds to 1/20 of the display size.

So if i want to reach 30 frames per second on full display size: 20 * 30 = 600 copy / second.

600 * 800us = 480 ms.

Apparently would work but it’s a grand overhead.

Could you give me some tips on how to change the driver to write directly to a 32 bit buffer byte by byte ?

I think it would look something like this:

file: i2s_parallel_driver.c

DMA_ATTR uint32_t buf_a[ pixels_size_in_bytes ];  // pixels_size_in_bytes = 15360 bytes. 
DMA_ATTR uint32_t buf_b[ pixels_size_in_bytes ];

********************************************************************************
file: my_app.c 

static lv_disp_buf_t disp_buf;
     
lv_disp_buf_init ( &disp_buf,  ( uint32_t* ) buf_a, ( uint32_t* ) buf_b, 7680 );  // 7680 pixels = 15360 bytes. pixel size = lv_color_t = 2 bytes / pixel.

********************************************************************************
file: lvgl_driver.c 

uint8_t* ptr; 

ptr = (uint8_t *) &pixel0;    // pixel0 provided by the littlevgl driver library. pixel0 are of type lv_color_t = uint16_t.
 
for ( uint32_t i = 0 ; i < length ; i = i + 2 )    // length <= 15360 bytes.  loop max 15360/2 = 7680 times.
{      
    buf_a[ i ] = ptr[ i ];   
    buf_a[ i + 1 ] = ptr[ i + 1 ];
} 

Thank’s for your help.

I made some test and I was able to hack 64 bit color support into the library. However, to use images the the image converter also need to be updated and the images will use also 8 byte/pixel.

To send a patch to you I need to know that what is the exact color format you use. Is the similar to the format with LV_COLOR_DETPH == 16, or do you also enabled LV_COLOR_16_SWAP?

Hi kisvegabor,

I am not wiring lcd yet.
I am only working in the i2s_parallel_driver.

color format: 16 bits / pixel (rgb565).

“Is the similar to the format with LV_COLOR_DETPH == 16”
I think yes.

“or do you also enabled LV_COLOR_16_SWAP?”
I think yes.

In anex follow lcd controller datasheet.
In page 62:
4. 8-Bit Bus Interface [IM3-0 = 0100]
In page 64:
(f) 16bpp Frame Memory Write (Option 1)

I think that are equal littlevgl format
LV_COLOR_16_SWAP == 1.

typedef union
{
    struct
    {
#if LV_COLOR_16_SWAP == 0
        uint16_t blue : 5;
        uint16_t green : 6;
        uint16_t red : 5;
#else
        uint16_t green_h : 3;
        uint16_t red : 5;
        uint16_t blue : 5;
        uint16_t green_l : 3;
#endif
    } ch;
    uint16_t full;
} lv_color16_t;

I dont know if i need swap the 2 bytes(low and high bytes) of each pixel to send to lcd controller.

I could not attach the lcd datasheet because the forum limit size of 4 MB.
The datasheet size are almost 5 MB.

Datasheet: R61529_rev0.02.pdf

Thank’s.

Could you compress it into a ZIP file, send a link to the page you downloaded it from, or upload it to Google Drive and provide a link here?

Hi embeddedt,

Datasheet link:

Thank’s for the help.

I’ve uploaded the modified version here: https://github.com/littlevgl/lvgl/tree/c64-test/src
It uses LV_COLOR_16_SWAP.

Note that, it’s a quite hacky solution and it won’t be maintained or merged to upstream. I’ll remove that branch if you confirm it’s working.

Hi Kisvegabor,

I need to change all files of littlevgl lib ?

Could you just tell me which files were modified ?
This way i just change these files.

Because i integrated littlevgl lib with esp-idf with cmake which use “CMakeLists.txt” and i modified all include of all files and folder organization too to follow the espressif “pattern”.

I posted the project here in the forum in another discussion few months ago:

I have that vesion of lib:

file: lv_version.h 

/*********************
 *      INCLUDES
 *********************/

/*Current version of LittlevGL*/
#define LVGL_VERSION_MAJOR   6
#define LVGL_VERSION_MINOR   0
#define LVGL_VERSION_PATCH   0
#define LVGL_VERSION_INFO    ""

Has the storage of images also changed with these new changes ?

Thank’s.

It’s on a git branch branched off of v6.1 (the latest version).

The commit diff shows you all of the changes made.

You may be required to reconvert images for 6.1; I can’t remember.

Since you’ve modified the directory structure of your previous version of LittlevGL you will need to do some work to upgrade. This is why it’s easier to leave the existing directory structure in place.

1 Like

Yes. You should modify the image converter to generate 64 bit images instead of 16 bit.
The image converter can run offline too.