ESP-S3 issue with horizontal banding on LCD ili9488

I’ve ported my code to compile and run on a ESP32 S3
Using LVGL 8.1, ESP-IDF, VS Code Insiders

I have tried in two ways:
1). Using a modified lv_port_esp32 that will compile with LVGL8.1 and also a modified driver of ili9488.
2). Using ESP-LCD with a driver that I wrote for the ili9488, and LVGL8.1.

When I compile and run both 1 and 2 on an ESP32 using the same LCD (BuyDisplay) 480 X 320
and driven using SPI, it works perfectly.

The same code running on the S3 results in Banding of the LCD.
The LCD properly initializes, but I suspect something to do with the SPI bus on the S3 that results in this artefact.
I’ve been at it for days, but just cannot resolve the issue.

I’m hoping someone can perhaps point me in the right direction, or at least give some sort of advice.

The screen appears as follows

Also notice that small areas that are updated are rendered perfectly, eg memory and FPS, and also the time. See video attached as a zip file. (1.9 MB)

I hope that somebody will have the solution, or point me in the correct direction

I have altered the flush routine, to only display the i.e… 480 X 40 buffer to the first 0,479,0,39 memory region, and alternately flush to the next consecutive memory area 0,479,40,79 and instead of them being directly below each other on the screen, there is a empty band of 40 lines in-between.
This only happens with S3 port.
See image below.
Hoping somebody will have an idea, as to the cause.


Just guessing but it seems like the SPI can’t send that large buffers and the end of the data is lost.

You can try it out by simply lowering the draw buffer size.

1 Like

Thx @kisvegabor . As simple as that. Reduced from 40 to 20 lines, and sorted.

1 Like

Actually that worked but just didn’t make sense why it should be an issue on the ESP32-S3, and not on the S3.

Kept digging and in my S3 port I forgot to increase the spi_max_transfer_size to to actual screen pixels * 3 for the ili9488 and still had it at * 2, but the ili9488 only supports 18bit color for spi and thus needs 3 * bytes.

.max_transfer_sz = LCD_WIDTH * LCD_HEIGHT * sizeof(uint8_t) * 3

Im actually thinking of getting another lcd display with capacitive touch in 3.5" 320*480 that actually supports 16bit color RGB565, as that will save me a lot of DMA memory, required for the conversion from 16 bit to 18bits. Currently using ER-TFTM035-6 with Capacitive Touch and SPI.

Glad the you have found the root of the issue! :slight_smile:

Actually I’m embarrassed to say that that was not the solution. I was so excited, I didn’t notice that I was flashing the ESP32. Well when I eventually noticed, I had to reduce the the buffer line height back to 20, as in the code below.

When I set to theme dark mode, I uncovered another possibly related problem.

static void loadassist_lv_port_disp_init(void)
    static lv_disp_draw_buf_t draw_buf_dsc; // contains internal graphic buffer(s) called draw buffer(s)
    size_t disp_buf_height = 20;

    /* Using static space for display buffer */
     * @brief OK 2022-01-06 After >16 hours of debugging and changing parameters,
     * it appears that on the S3, if the disp_buf_height is set to 40, only every second
     * buffer is transmitted to the LCD RAM. Setting to 20 solves the banding issue.
     * Thx to Kisvegabor for pointing that out.
    static lv_color_t p_disp_buf1[LCD_WIDTH * 20];
    static lv_color_t p_disp_buf2[LCD_WIDTH * 20];

    /* Initialize display buffer */
    lv_disp_draw_buf_init(&draw_buf_dsc, p_disp_buf1, p_disp_buf2, LCD_WIDTH * disp_buf_height);

    ESP_LOGI(TAG, "Register display driver to LVGL");
    disp_drv.full_refresh = 0;
    /*Set the resolution of the display*/
    disp_drv.hor_res = LCD_WIDTH;
    disp_drv.ver_res = LCD_HEIGHT;
    /* Used to copy the buffer's content to the display */
    disp_drv.flush_cb = disp_flush;
    disp_drv.draw_buf = &draw_buf_dsc;
    /* Use lcd_trans_done_cb to inform the graphics library that flush already done */
    loadassist_lcd_set_cb(loadassist_lv_port_flush_ready, NULL);
    /*Finally register the driver*/

It appears that the last pixel of the flushed buffer area is not set. This results in dots at the far right of the screen spaced at exactly every 20 lines.
Also there are dots, at the bottom right corner of each refreshed smaller area on the screen.

See images:
This problem is present on both the ESP32-S3 and ESP32.
ESP32 (just the 1st flushed buffer. Note last pixel - flush size 40 X 480):

ESP32 (full screen with all the artefacts. Notice the smaller updated area).

And then the ESP32-S3 (The full screen with buffer set to 20 lines, thus dots more frequently spaced).

Flush routine as follows:

static esp_err_t panel_ili9488_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
    //!ESP_LOGI(TAG, "Draw the bitmap to the screen.....");
    size_t len = (x_end - x_start) * (y_end - y_start);
    ESP_LOGI(TAG, "LEN: %d x_end: %d x_start: %d y_end: %d y_start: %d", len, x_end, x_start, y_end, y_start);

    //!ESP_LOGI(TAG, "Allocate DMA Memory for the 16Bit to 18Bit conversion %d.....", 3 * len * sizeof(uint8_t));
    lv_color16_t *buffer_16bit = (lv_color16_t *) color_data;
    uint8_t *mybuf;
    do {
	    mybuf = (uint8_t *) heap_caps_malloc(3 * len * sizeof(uint8_t), MALLOC_CAP_DMA);
        if (mybuf == NULL)  ESP_LOGE(TAG, "Could not allocate enough DMA memory for conversion 16bits to 18 bits! %d", 3 * len * sizeof(uint8_t));
    } while (mybuf == NULL);

    uint32_t LD = 0;
    uint32_t j = 0;

    for (uint32_t i = 0; i < len; i++) {
        LD = buffer_16bit[i].full;
        mybuf[j] = (uint8_t) (((LD & 0xF800) >> 8) | ((LD & 0x8000) >> 13));
        mybuf[j] = (uint8_t) ((LD & 0x07E0) >> 3);
        mybuf[j] = (uint8_t) (((LD & 0x001F) << 3) | ((LD & 0x0010) >> 2));

    //!ESP_LOGI(TAG, "Converted 16bits color to 18 bits color!");

    ili9488_panel_t *ili9488 = __containerof(panel, ili9488_panel_t, base);
    assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
    esp_lcd_panel_io_handle_t io = ili9488->io;

    x_start += ili9488->x_gap;
    x_end += ili9488->x_gap;
    y_start += ili9488->y_gap;
    y_end += ili9488->y_gap;

    //define an area of frame memory where MCU can access
    esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) {
        (x_start >> 8) & 0xFF,
        x_start & 0xFF,
        ((x_end - 1) >> 8) & 0xFF,
        (x_end - 1) & 0xFF,
    }, 4);
    esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET , (uint8_t[]) {
        (y_start >> 8) & 0xFF,
        y_start & 0xFF,
        ((y_end - 1) >> 8) & 0xFF,
        (y_end - 1) & 0xFF,
    }, 4);

    // transfer frame buffer
    //ili9488_send_color((void *) mybuf, size * 3);

    //!ESP_LOGI(TAG, "Writing to memory....");
    //size_t len = (x_end - x_start) * (y_end - y_start) * ili9488->bits_per_pixel / 8; This only supports 8 and 16bit SPI
    esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, (void *) mybuf, len * 3); //need 3 X uint8_t for converted 18 bits.

    return ESP_OK;

For the missing last pixel: what if you put together a small image manually (e.g. 2x2 px) and send it manually with your flush_cb?

1 Like

did you figure out the problem with the last missing pixels?

Sorry, no @engineering-spirit I kept on getting a hot pixel, that coincided with my last pixel of the buffer flush.
I do not own an oscilloscope, and had spent way to much time on this.

I decided to offer up a few GPIO pins, and worked on a Parallel Driver for ili9488 and ran 8bit parallel.
I used Espressif’s LCD Parallel i8080 8 bit 16 color mode and modified slightly.
I also switched to using LVGL from and this simplified my entire integration in visual studio code insiders using ESP-IDF 4.4.1 and LVGL 8.3.0 and dropped the official port, making life much simpler.

I’m happy with the outcome:

thanks for your reply.

I have narrowed it down to the DMA transfer.
If I do the transfer in polling mode, there is no glitch.

was looking for some answers in the ESP32­S3 Technical Reference Manual but the SPI chapter is not yet published.

My suspicion is that there is a bug in the DMA, that the buffer pointer is incremented but not clocked out of the SPI. (as the glitch data is the same as the memory directly after the display buffer)

I can’t do parallel interface with the current board, as there are no enough free pins on the wrover module in my application ( only 2 free pins on the module currently) .

Also will have a look at using the LVGL from