Page 1 of 1

Problems with i80 16 bits LVGL animations

Posted: Mon Apr 14, 2025 9:41 am
by ManelCS.
I don’t understand why, when I use the 8080 16-bit configuration, the LVGL animations show tearing, while using 8080 8-bit with the same display works perfectly.
I’m using the ST7789VI controller and ESP32-S3 module.

Static images look fine in both 8-bit and 16-bit modes.

This is how I have it configured:

Code: Select all

// i80_bus.c

#include "esp_timer.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "i80_bus.h"
#include "i80_config.h"

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"

#define EXAMPLE_LCD_PIXEL_CLOCK_HZ     (10 * 1000 * 1000)

#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL  1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL

#if CONFIG_LCD_I80_BUS_WIDTH_8
#define EXAMPLE_LCD_I80_BUS_WIDTH 8
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL  1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_DATA0          48
#define EXAMPLE_PIN_NUM_DATA1          45
#define EXAMPLE_PIN_NUM_DATA2          39
#define EXAMPLE_PIN_NUM_DATA3          38
#define EXAMPLE_PIN_NUM_DATA4          41
#define EXAMPLE_PIN_NUM_DATA5          40
#define EXAMPLE_PIN_NUM_DATA6          2
#define EXAMPLE_PIN_NUM_DATA7          42

#define EXAMPLE_PIN_NUM_PCLK           3  
#define EXAMPLE_PIN_NUM_CS             17  
#define EXAMPLE_PIN_NUM_DC             46   
#define EXAMPLE_PIN_NUM_RST            1    
#define EXAMPLE_PIN_NUM_BK_LIGHT       -1
#define PIN_NUM_RD                     -1
#define PIN_IM0                        -1
#define PIN_IM2                        -1

#elif CONFIG_LCD_I80_BUS_WIDTH_16
#define EXAMPLE_LCD_I80_BUS_WIDTH 16
#define EXAMPLE_PIN_NUM_DATA0          9    
#define EXAMPLE_PIN_NUM_DATA1          10   
#define EXAMPLE_PIN_NUM_DATA2          11   
#define EXAMPLE_PIN_NUM_DATA3          12   
#define EXAMPLE_PIN_NUM_DATA4          13   
#define EXAMPLE_PIN_NUM_DATA5          14   
#define EXAMPLE_PIN_NUM_DATA6          21   
#define EXAMPLE_PIN_NUM_DATA7          47   
#define EXAMPLE_PIN_NUM_DATA8          48   
#define EXAMPLE_PIN_NUM_DATA9          45   
#define EXAMPLE_PIN_NUM_DATA10         39   
#define EXAMPLE_PIN_NUM_DATA11         38   
#define EXAMPLE_PIN_NUM_DATA12         41   
#define EXAMPLE_PIN_NUM_DATA13         40   
#define EXAMPLE_PIN_NUM_DATA14         2    
#define EXAMPLE_PIN_NUM_DATA15         42

#define EXAMPLE_PIN_NUM_PCLK           3     
#define EXAMPLE_PIN_NUM_CS             17     
#define EXAMPLE_PIN_NUM_DC             46      
#define EXAMPLE_PIN_NUM_RST            1      
#define EXAMPLE_PIN_NUM_BK_LIGHT       4
#define PIN_NUM_RD                     -1
#define PIN_IM0                        -1
#define PIN_IM2                        -1
#endif

// The pixel number in horizontal and vertical
#define EXAMPLE_LCD_H_RES              320
#define EXAMPLE_LCD_V_RES              240
// Bit number used to represent command and parameter

#define EXAMPLE_LCD_CMD_BITS           8
#define EXAMPLE_LCD_PARAM_BITS         8


#define EXAMPLE_LVGL_TICK_PERIOD_MS    2
#define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500
#define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1
#define EXAMPLE_LVGL_TASK_STACK_SIZE   (4 * 1024)
#define EXAMPLE_LVGL_TASK_PRIORITY     2

#define EXAMPLE_DMA_BURST_SIZE         64 // 16, 32, 64. Higher burst size can improve the performance when the DMA buffer comes from PSRAM


static const char *TAG = "i80_bus";

static SemaphoreHandle_t lvgl_mux = NULL;

static bool example_notify_lvgl_flush_ready(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
    lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
    lv_disp_flush_ready(disp_driver);
    return false;
}

static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
    esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
    int offsetx1 = area->x1;
    int offsetx2 = area->x2;
    int offsety1 = area->y1;
    int offsety2 = area->y2;
    // copy a buffer's content to a specific area of the display
    esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}

bool lvgl_lock(int timeout_ms)
{
    // Convert timeout in milliseconds to FreeRTOS ticks
    // If `timeout_ms` is set to -1, the program will block until the condition is met
    const TickType_t timeout_ticks = (timeout_ms == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms);
    return xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE;
}

void lvgl_unlock(void)
{
    xSemaphoreGiveRecursive(lvgl_mux);
}

static void example_lvgl_port_task(void *arg)
{
    ESP_LOGI(TAG, "Starting LVGL task");
    uint32_t task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
    while (1) {
        // Lock the mutex due to the LVGL APIs are not thread-safe
        if (lvgl_lock(-1)) {
            task_delay_ms = lv_timer_handler();
            // Release the mutex
            lvgl_unlock();
        }
        if (task_delay_ms > EXAMPLE_LVGL_TASK_MAX_DELAY_MS) {
            task_delay_ms = EXAMPLE_LVGL_TASK_MAX_DELAY_MS;
        } else if (task_delay_ms < EXAMPLE_LVGL_TASK_MIN_DELAY_MS) {
            task_delay_ms = EXAMPLE_LVGL_TASK_MIN_DELAY_MS;
        }
        vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
    }
}

static void example_increase_lvgl_tick(void *arg)
{
    /* Tell LVGL how many milliseconds has elapsed */
    lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}

void example_init_i80_bus(esp_lcd_panel_io_handle_t *io_handle, void *user_ctx)
{
    ESP_LOGI(TAG, "Initialize Intel 8080 bus");

    esp_lcd_i80_bus_handle_t i80_bus = NULL;
    esp_lcd_i80_bus_config_t bus_config = {
        .clk_src = LCD_CLK_SRC_DEFAULT,
        .dc_gpio_num = EXAMPLE_PIN_NUM_DC,
        .wr_gpio_num = EXAMPLE_PIN_NUM_PCLK,
        .data_gpio_nums = {
            EXAMPLE_PIN_NUM_DATA0, EXAMPLE_PIN_NUM_DATA1, EXAMPLE_PIN_NUM_DATA2,
            EXAMPLE_PIN_NUM_DATA3, EXAMPLE_PIN_NUM_DATA4, EXAMPLE_PIN_NUM_DATA5,
            EXAMPLE_PIN_NUM_DATA6, EXAMPLE_PIN_NUM_DATA7,
#if CONFIG_LCD_I80_BUS_WIDTH_16
            EXAMPLE_PIN_NUM_DATA8, EXAMPLE_PIN_NUM_DATA9, EXAMPLE_PIN_NUM_DATA10,
            EXAMPLE_PIN_NUM_DATA11, EXAMPLE_PIN_NUM_DATA12, EXAMPLE_PIN_NUM_DATA13,
            EXAMPLE_PIN_NUM_DATA14, EXAMPLE_PIN_NUM_DATA15,
#endif
        },
        .bus_width = EXAMPLE_LCD_I80_BUS_WIDTH,
        .max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t),
        .dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
    };
    ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));

    esp_lcd_panel_io_i80_config_t io_config = {
        .cs_gpio_num = EXAMPLE_PIN_NUM_CS,
        .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
        .trans_queue_depth = 10,
        .dc_levels = {
            .dc_idle_level = 0,
            .dc_cmd_level = 0,
            .dc_dummy_level = 0,
            .dc_data_level = 1,
        },
        .flags = {
            .swap_color_bytes = !LV_COLOR_16_SWAP, // Swap can be done in LvGL (default) or DMA
        },
        .on_color_trans_done = example_notify_lvgl_flush_ready,
        .user_ctx = user_ctx,
        .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
        .lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, io_handle));
}

void example_init_lcd_panel(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t *panel)
{
    esp_lcd_panel_handle_t panel_handle = NULL;
#if CONFIG_EXAMPLE_LCD_I80_CONTROLLER_ST7789
    ESP_LOGI(TAG, "Install LCD driver of st7789");
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = EXAMPLE_PIN_NUM_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
        .bits_per_pixel = 16,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));

    esp_lcd_panel_reset(panel_handle);
    esp_lcd_panel_init(panel_handle);
    // Set inversion, x/y coordinate order, x/y mirror according to your LCD module spec
    // the gap is LCD panel specific, even panels with the same driver IC, can have different gap value
    esp_lcd_panel_swap_xy(panel_handle, true);
    esp_lcd_panel_mirror(panel_handle, true, false);
    esp_lcd_panel_invert_color(panel_handle, true);
    esp_lcd_panel_set_gap(panel_handle, 0, 0);
#elif CONFIG_EXAMPLE_LCD_I80_CONTROLLER_NT35510
    ESP_LOGI(TAG, "Install LCD driver of nt35510");
    esp_lcd_panel_dev_config_t panel_config = {
        .reset_gpio_num = EXAMPLE_PIN_NUM_RST,
        .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
        .bits_per_pixel = 16,
    };
    ESP_ERROR_CHECK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle));

    esp_lcd_panel_reset(panel_handle);
    esp_lcd_panel_init(panel_handle);
    // Set inversion, x/y coordinate order, x/y mirror according to your LCD module spec
    esp_lcd_panel_swap_xy(panel_handle, true);
    esp_lcd_panel_mirror(panel_handle, true, false);
#endif
    *panel = panel_handle;
}

lv_disp_t * Start_Display()
{
    static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
    static lv_disp_drv_t disp_drv;      // contains callback functions

    /* -------------------- START PINS -------------------- */
    #if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
        ESP_LOGI(TAG, "Turn off LCD backlight");
        gpio_config_t bk_gpio_config = {
            .mode = GPIO_MODE_OUTPUT,
            .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
        };
        ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
        gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
    #endif // EXAMPLE_PIN_NUM_BK_LIGHT >= 0

    // gpio_config_t rd_config = {
    //     .mode = GPIO_MODE_OUTPUT,
    //     .pin_bit_mask = 1ULL << PIN_NUM_RD
    // };
    // ESP_ERROR_CHECK(gpio_config(&rd_config));
    // gpio_set_level(PIN_NUM_RD, 1);

    // gpio_config_t im0_config = {
    //     .mode = GPIO_MODE_OUTPUT,
    //     .pin_bit_mask = 1ULL << PIN_IM0
    // };
    // ESP_ERROR_CHECK(gpio_config(&im0_config));
    // gpio_set_level(PIN_IM0, 1);

    // gpio_config_t im2_config = {
    //     .mode = GPIO_MODE_OUTPUT,
    //     .pin_bit_mask = 1ULL << PIN_IM2
    // };
    // ESP_ERROR_CHECK(gpio_config(&im2_config));
    // gpio_set_level(PIN_IM2, 0);

    /* -------------------- START i80 BUS -------------------- */
    esp_lcd_panel_io_handle_t io_handle = NULL;
    example_init_i80_bus(&io_handle, &disp_drv); /* Structs already pointers */
    
    esp_lcd_panel_handle_t panel_handle = NULL;
    example_init_lcd_panel(io_handle, &panel_handle);

    // Stub: user can flush pre-defined pattern to the screen before we turn on the screen or backlight

    ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));

    /* -------------------- BACKLIGHT ON -------------------- */

    #if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
        ESP_LOGI(TAG, "Turn on LCD backlight");
        gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
    #endif // EXAMPLE_PIN_NUM_BK_LIGHT >= 0

    /* -------------------- START LVGL -------------------- */

    ESP_LOGI(TAG, "Initialize LVGL library");
    lv_init();
    // alloc draw buffers used by LVGL
    // it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized
    uint32_t draw_buf_alloc_caps = 0;
    #if CONFIG_EXAMPLE_LCD_I80_COLOR_IN_PSRAM
    draw_buf_alloc_caps |= MALLOC_CAP_SPIRAM;
    #endif
    lv_color_t *buf1 = esp_lcd_i80_alloc_draw_buffer(io_handle, EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), draw_buf_alloc_caps);
    lv_color_t *buf2 = esp_lcd_i80_alloc_draw_buffer(io_handle, EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), draw_buf_alloc_caps);
    assert(buf1);
    assert(buf2);
    ESP_LOGI(TAG, "buf1@%p, buf2@%p", buf1, buf2);
    // initialize LVGL draw buffers
    lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 100);

    ESP_LOGI(TAG, "Register display driver to LVGL");
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res = EXAMPLE_LCD_H_RES;
    disp_drv.ver_res = EXAMPLE_LCD_V_RES;
    disp_drv.flush_cb = example_lvgl_flush_cb;
    disp_drv.draw_buf = &disp_buf;
    disp_drv.user_data = panel_handle;

    lv_disp_t *disp = lv_disp_drv_register(&disp_drv);

    ESP_LOGI(TAG, "Install LVGL tick timer");
    // Tick interface for LVGL (using esp_timer to generate 2ms periodic event)
    const esp_timer_create_args_t lvgl_tick_timer_args = {
        .callback = &example_increase_lvgl_tick,
        .name = "lvgl_tick"
    };
    esp_timer_handle_t lvgl_tick_timer = NULL;
    ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer));
    ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));

    lvgl_mux = xSemaphoreCreateRecursiveMutex();
    assert(lvgl_mux);
    ESP_LOGI(TAG, "Create LVGL task");
    xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);

    return disp;
}

Re: Problems with i80 16 bits LVGL animations

Posted: Tue Apr 15, 2025 12:16 am
by Sprite
Probably a speed issue. Tearing happens when the vsync and the display write overlap, but how visible that is in an animation depends on where the tearing happens in all the different frames: if it's more-or-less in the same location, the tearing is really obvious. Could be that the slower 8-bit mode differs in speed by just enough to more effectively hide the tear. (Note that unless you have a vsync or TE output from the display back to the ESP, some tearing is inevitable.)

Re: Problems with i80 16 bits LVGL animations

Posted: Wed Apr 16, 2025 12:16 pm
by ManelCS.
Probably a speed issue. Tearing happens when the vsync and the display write overlap, but how visible that is in an animation depends on where the tearing happens in all the different frames: if it's more-or-less in the same location, the tearing is really obvious. Could be that the slower 8-bit mode differs in speed by just enough to more effectively hide the tear. (Note that unless you have a vsync or TE output from the display back to the ESP, some tearing is inevitable.)
The tearing happens more-or-less in the same location. Without using TE I understand that it cannot be solved?

Re: Problems with i80 16 bits LVGL animations

Posted: Wed Apr 16, 2025 11:25 pm
by teletypeguy
Hi:

I can't comment on animation issues as my gui stuff is just infrequent as-needed updates, but I did use i80 mode for one project. I was using an Elecrow DLC35010R display/esp32s3 module with 3.5" 320x480 (ILI9488) and cap touch (FT6236-i2c). The ili9488 was configured for i80-16. I had a frustrating experience trying to get it running, and finally after probing all the signals with a scope I realized the ili9488 read pin was floating. This pin was not defined or driven by the esp_lcd driver, and once I added a couple of lines of code to manually set it high my display finally started working. I was also able to modify the ili9488 config pins to select i80 8-bit mode, which worked well and freed up 8 pins on the s3 for me to use.

What display are you using with the st7789 in i80 mode? I have only seen this one elecrow display that uses i80.

Re: Problems with i80 16 bits LVGL animations

Posted: Tue Apr 22, 2025 9:17 am
by ManelCS.
Hi:

I can't comment on animation issues as my gui stuff is just infrequent as-needed updates, but I did use i80 mode for one project. I was using an Elecrow DLC35010R display/esp32s3 module with 3.5" 320x480 (ILI9488) and cap touch (FT6236-i2c). The ili9488 was configured for i80-16. I had a frustrating experience trying to get it running, and finally after probing all the signals with a scope I realized the ili9488 read pin was floating. This pin was not defined or driven by the esp_lcd driver, and once I added a couple of lines of code to manually set it high my display finally started working. I was also able to modify the ili9488 config pins to select i80 8-bit mode, which worked well and freed up 8 pins on the s3 for me to use.

What display are you using with the st7789 in i80 mode? I have only seen this one elecrow display that uses i80.
I'm using this display: https://newhavendisplay.com/content/spe ... F-CSXP.pdf