Display ESP32 - ESP32-8048S043 - Screen shake

Hi everyone,

I need some help.

I’m trying to get this display working in ESP-IDF with LVGL 8.3.

However, the screen keeps flickering…

I’ve already tried changing various settings… and I don’t know what to do.

I managed to get a similar display—the ESP32-4827S043—working.

It had the same problem.

However, applying the same settings doesn’t work because the resolution is different.

Does anyone have any suggestions?

Below is the code I am using:

PanelConfig.c:

#include "f_panelConfig.h"
#include "driver/gpio.h"

/* =========================
 * Sunton ESP32-8048S043C
 * 4.3" RGB 800x480, ST7262
 * ========================= */
#define LCD_H_RES              800
#define LCD_V_RES              480

#define LCD_PIXEL_CLOCK_HZ     (16 * 1000 * 1000)

#define LCD_HSYNC_GPIO         39
#define LCD_VSYNC_GPIO         41
#define LCD_DE_GPIO            40
#define LCD_PCLK_GPIO          42
#define LCD_DISP_GPIO          GPIO_NUM_NC

/* Ordem RGB565 física do painel */
#define LCD_DATA_R0            45
#define LCD_DATA_R1            48
#define LCD_DATA_R2            47
#define LCD_DATA_R3            21
#define LCD_DATA_R4            14

#define LCD_DATA_G0            5
#define LCD_DATA_G1            6
#define LCD_DATA_G2            7
#define LCD_DATA_G3            15
#define LCD_DATA_G4            16
#define LCD_DATA_G5            4

#define LCD_DATA_B0            8
#define LCD_DATA_B1            3
#define LCD_DATA_B2            46
#define LCD_DATA_B3            9
#define LCD_DATA_B4            1

/* Timing RGB usado pelo painel JC8048B043N_I / ST7262. */
#define LCD_HSYNC_PULSE_WIDTH  4
#define LCD_HSYNC_BACK_PORCH   8 //Estava em 16 e estava ruim.
#define LCD_HSYNC_FRONT_PORCH  8

#define LCD_VSYNC_PULSE_WIDTH  4
#define LCD_VSYNC_BACK_PORCH   8 //Estava em 10 e estava ruim.
#define LCD_VSYNC_FRONT_PORCH  8

esp_lcd_rgb_panel_config_t f_panelConfig_get(void) {
    const int data_gpio_nums[16] = {
        LCD_DATA_B0,
        LCD_DATA_B1,
        LCD_DATA_B2,
        LCD_DATA_B3,
        LCD_DATA_B4,
        LCD_DATA_G0,
        LCD_DATA_G1,
        LCD_DATA_G2,
        LCD_DATA_G3,
        LCD_DATA_G4,
        LCD_DATA_G5,
        LCD_DATA_R0,
        LCD_DATA_R1,
        LCD_DATA_R2,
        LCD_DATA_R3,
        LCD_DATA_R4
    };

    esp_lcd_rgb_panel_config_t panel_config = {
        .clk_src = LCD_CLK_SRC_PLL160M,
        .data_width = 16,
        .bits_per_pixel = 16,
        .num_fbs = 2, //Estava em 1 (Esse 0 é um teste para ver se o driver aloca 1 frame buffer por padrão, como documentado)
        .bounce_buffer_size_px = LCD_H_RES * 16,
        .sram_trans_align = 64,
        .psram_trans_align = 64,
        .hsync_gpio_num = LCD_HSYNC_GPIO,
        .vsync_gpio_num = LCD_VSYNC_GPIO,
        .de_gpio_num = LCD_DE_GPIO,
        .pclk_gpio_num = LCD_PCLK_GPIO,
        .disp_gpio_num = LCD_DISP_GPIO,
        .data_gpio_nums = {
            data_gpio_nums[0],  data_gpio_nums[1],  data_gpio_nums[2],  data_gpio_nums[3],
            data_gpio_nums[4],  data_gpio_nums[5],  data_gpio_nums[6],  data_gpio_nums[7],
            data_gpio_nums[8],  data_gpio_nums[9],  data_gpio_nums[10], data_gpio_nums[11],
            data_gpio_nums[12], data_gpio_nums[13], data_gpio_nums[14], data_gpio_nums[15]
        },
        .timings = {
            .pclk_hz = LCD_PIXEL_CLOCK_HZ,
            .h_res = LCD_H_RES,
            .v_res = LCD_V_RES,
            .hsync_pulse_width = LCD_HSYNC_PULSE_WIDTH,
            .hsync_back_porch = LCD_HSYNC_BACK_PORCH,
            .hsync_front_porch = LCD_HSYNC_FRONT_PORCH,
            .vsync_pulse_width = LCD_VSYNC_PULSE_WIDTH,
            .vsync_back_porch = LCD_VSYNC_BACK_PORCH,
            .vsync_front_porch = LCD_VSYNC_FRONT_PORCH,
            .flags = {
                .hsync_idle_low = 0,
                .vsync_idle_low = 0,
                .de_idle_high = 0,
                .pclk_active_neg = 1,
                .pclk_idle_high = 0,
            },
        },
        .flags = {
            .fb_in_psram = 1,
            .disp_active_low = 0,
        },
    };

    return panel_config;
}

lcd8048.c:

#include "f_lcd8048.h"
#include "f_panelConfig.h"

#include <stdint.h>
#include <stdbool.h>

#include "esp_log.h"
#include "esp_check.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_heap_caps.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"

static const char *TAG = "f_lcd8048";

static esp_lcd_panel_handle_t s_panel = NULL;
static uint16_t *s_framebuffer = NULL;

static SemaphoreHandle_t s_sem_vsync = NULL;
static volatile bool s_flush_pending = false;

static inline uint16_t rgb565(uint8_t r, uint8_t g, uint8_t b) {
    return (uint16_t)(((r & 0xF8) << 8) |
                      ((g & 0xFC) << 3) |
                      ((b & 0xF8) >> 3));
}

static bool IRAM_ATTR rgb_vsync_cb(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) {
        (void)panel;
        (void)edata;
        (void)user_ctx;

        if (!s_flush_pending || s_sem_vsync == NULL) {
            return false;
        }

        BaseType_t hp_task_woken = pdFALSE;
        xSemaphoreGiveFromISR(s_sem_vsync, &hp_task_woken);
        return (hp_task_woken == pdTRUE);
}

static void backlight_init(void) {
        const gpio_config_t io_conf = {
            .pin_bit_mask = 1ULL << LCD_BKLT_GPIO,
            .mode = GPIO_MODE_OUTPUT,
            .pull_up_en = GPIO_PULLUP_DISABLE,
            .pull_down_en = GPIO_PULLDOWN_DISABLE,
            .intr_type = GPIO_INTR_DISABLE
        };

        ESP_ERROR_CHECK(gpio_config(&io_conf));
        gpio_set_level(LCD_BKLT_GPIO, 0);
}

static void backlight_on(void) {
    gpio_set_level(LCD_BKLT_GPIO, 1);
}

static void fill_screen(uint16_t color) {
    if (s_framebuffer == NULL) {return;}
    const size_t total_pixels = LCD_H_RES * LCD_V_RES;
    for (size_t i = 0; i < total_pixels; i++) {
        s_framebuffer[i] = color;
    }
}

static void draw_test_pattern(void) {
        if (s_framebuffer == NULL) {
            return;
        }

        const uint16_t colors[] = {
            rgb565(255, 255, 255), // branco
            rgb565(255, 255,   0), // amarelo
            rgb565(  0, 255, 255), // ciano
            rgb565(  0, 255,   0), // verde
            rgb565(255,   0, 255), // magenta
            rgb565(255,   0,   0), // vermelho
            rgb565(  0,   0, 255), // azul
            rgb565(  0,   0,   0)  // preto
        };

        const int bar_count = sizeof(colors) / sizeof(colors[0]);
        const int bar_width = LCD_H_RES / bar_count;

        for (int y = 0; y < LCD_V_RES; y++) {
            for (int x = 0; x < LCD_H_RES; x++) {
                int idx = x / bar_width;
                if (idx >= bar_count) {
                    idx = bar_count - 1;
                }
                s_framebuffer[y * LCD_H_RES + x] = colors[idx];
            }
        }

        for (int x = 0; x < LCD_H_RES; x++) {
            s_framebuffer[x] = rgb565(255, 255, 255);
            s_framebuffer[(LCD_V_RES - 1) * LCD_H_RES + x] = rgb565(255, 255, 255);
        }

        for (int y = 0; y < LCD_V_RES; y++) {
            s_framebuffer[y * LCD_H_RES] = rgb565(255, 255, 255);
            s_framebuffer[y * LCD_H_RES + (LCD_H_RES - 1)] = rgb565(255, 255, 255);
        }
}

esp_err_t f_setup_lcd8048(void) {
        if (s_panel != NULL) {ESP_LOGW(TAG, "Display already initialized.");return ESP_OK;}
        backlight_init();
        
        esp_lcd_rgb_panel_config_t panel_config = f_panelConfig_get();
        ESP_RETURN_ON_ERROR(esp_lcd_new_rgb_panel(&panel_config, &s_panel), TAG, "esp_lcd_new_rgb_panel falhou");
        ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(s_panel), TAG, "esp_lcd_panel_reset falhou");
        ESP_RETURN_ON_ERROR(esp_lcd_panel_init(s_panel), TAG, "esp_lcd_panel_init falhou");

        if (s_sem_vsync == NULL) {
            s_sem_vsync = xSemaphoreCreateBinary();
            ESP_RETURN_ON_FALSE(s_sem_vsync != NULL, ESP_ERR_NO_MEM, TAG, "Falha ao criar s_sem_vsync");
        }
        s_flush_pending = false;
        xSemaphoreTake(s_sem_vsync, 0);

        esp_lcd_rgb_panel_event_callbacks_t cbs = {
            // .on_bounce_frame_finish = rgb_bounce_frame_finish_cb,
            .on_vsync = rgb_vsync_cb,
        };
        ESP_RETURN_ON_ERROR(esp_lcd_rgb_panel_register_event_callbacks(s_panel, &cbs, NULL), TAG, "esp_lcd_rgb_panel_register_event_callbacks falhou");
        void *fb0 = NULL;
        ESP_RETURN_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(s_panel, 1, &fb0), TAG,"esp_lcd_rgb_panel_get_frame_buffer falhou");
        s_framebuffer = (uint16_t *)fb0;
        backlight_on();
        fill_screen(rgb565(0, 0, 0));
        // draw_test_pattern();
        ESP_LOGI(TAG, "Display 4.3 initialized successfully (%dx%d).", LCD_H_RES, LCD_V_RES);
        return ESP_OK;
}

void f_display43_begin_flush_sync(void) {
    s_flush_pending = true;
    if (s_sem_vsync != NULL) {
        xSemaphoreTake(s_sem_vsync, 0);
    }
}

bool f_display43_wait_flush_release(TickType_t timeout_ticks) {
    if (s_sem_vsync == NULL) {
        return false;
    }
    if (xSemaphoreTake(s_sem_vsync, timeout_ticks) != pdTRUE) {
        return false;
    }
    s_flush_pending = false;
    return true;
}

esp_lcd_panel_handle_t f_display43_get_panel(void) {return s_panel;}
uint16_t *f_display43_get_framebuffer(void)        {return s_framebuffer; }

init-lvgl.c:

#include "f_init-lvgl.h"
#include "f_lcd8048.h"
#include "f_panelConfig.h"

#include <string.h>

#include "esp_log.h"
#include "esp_check.h"
#include "esp_timer.h"
#include "esp_heap_caps.h"

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

#include "esp_task_wdt.h"

static const char *TAG = "f_init_lvgl";

#define LVGL_TICK_PERIOD_MS      2  //Estava 10
#define LVGL_TASK_PERIOD_MS      20  // Estava 40
#define LVGL_BUF_LINES           160

static SemaphoreHandle_t s_lvgl_mutex = NULL;
static esp_timer_handle_t s_lvgl_tick_timer = NULL;
static TaskHandle_t s_lvgl_task_handle = NULL;

static lv_disp_draw_buf_t s_draw_buf;
static lv_color_t *s_buf1 = NULL;
static lv_color_t *s_buf2 = NULL;
static lv_disp_drv_t s_disp_drv;
static lv_disp_t *s_disp = NULL;

static bool s_lvgl_initialized = false;


static void lvgl_tick_cb(void *arg) {
    (void)arg;
    lv_tick_inc(LVGL_TICK_PERIOD_MS);
}

// static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) {
//         esp_lcd_panel_handle_t panel = f_display43_get_panel();
//         if (panel == NULL) { lv_disp_flush_ready(drv);return;}
//         f_display43_begin_flush_sync();
//         if (!f_display43_wait_flush_release(pdMS_TO_TICKS(100))) {ESP_LOGW(TAG, "Timeout waiting for VSYNC for flush");}
//         esp_err_t err = esp_lcd_panel_draw_bitmap(panel, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
//         if (err != ESP_OK) {ESP_LOGE(TAG, "esp_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));}
//         lv_disp_flush_ready(drv);
// }

static void lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) {
    esp_lcd_panel_handle_t panel = f_display43_get_panel();
    if (panel == NULL) {
        lv_disp_flush_ready(drv);
        return;
    }

    esp_err_t err = esp_lcd_panel_draw_bitmap(panel, area->x1, area->y1, area->x2 + 1, area->y2 + 1, color_map);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "esp_lcd_panel_draw_bitmap failed: %s", esp_err_to_name(err));
    }
    lv_disp_flush_ready(drv);
}

static void lvgl_port_task(void *arg) {
    (void)arg;

    esp_task_wdt_delete(NULL);

    while (1) {
        lock();
            lv_timer_handler();
        unlock();
        vTaskDelay(pdMS_TO_TICKS(LVGL_TASK_PERIOD_MS));
    }
}

void lock(void) { if (s_lvgl_mutex == NULL) { return; } xSemaphoreTake(s_lvgl_mutex, portMAX_DELAY); }
void unlock(void) { if (s_lvgl_mutex != NULL) { xSemaphoreGive(s_lvgl_mutex); } }

lv_disp_t *f_lvgl_get_display(void) { return s_disp;}

esp_err_t f_init_lvgl(void) {
        if (s_lvgl_initialized) {
            ESP_LOGW(TAG, "LVGL already initialized.");
            return ESP_OK;
        }
    
        //ESP_RETURN_ON_ERROR(f_setupDisplay43(), TAG, "f_setupDisplay43 falhou");

        if (s_lvgl_mutex == NULL) {
            s_lvgl_mutex = xSemaphoreCreateMutex();
            ESP_RETURN_ON_FALSE(s_lvgl_mutex != NULL, ESP_ERR_NO_MEM, TAG, "Failed to create LVGL mutex");
        }

        lv_init();

        const size_t buf_pixels = LCD_H_RES * LVGL_BUF_LINES;
        const size_t buf_bytes = buf_pixels * sizeof(lv_color_t);

        s_buf1 = heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
        s_buf2 = heap_caps_malloc(buf_bytes, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);

        ESP_RETURN_ON_FALSE(s_buf1 != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate s_buf1");
        ESP_RETURN_ON_FALSE(s_buf2 != NULL, ESP_ERR_NO_MEM, TAG, "Failed to allocate s_buf2");

        memset(s_buf1, 0, buf_bytes);
        memset(s_buf2, 0, buf_bytes);

        lv_disp_draw_buf_init(&s_draw_buf, s_buf1, s_buf2, buf_pixels);

        lv_disp_drv_init(&s_disp_drv);
        s_disp_drv.hor_res = LCD_H_RES;
        s_disp_drv.ver_res = LCD_V_RES;
        s_disp_drv.flush_cb = lvgl_flush_cb;
        s_disp_drv.draw_buf = &s_draw_buf;
        s_disp_drv.full_refresh = 0;

        s_disp = lv_disp_drv_register(&s_disp_drv);
        ESP_RETURN_ON_FALSE(s_disp != NULL, ESP_FAIL, TAG, "Failed to register display in LVGL");

        const esp_timer_create_args_t tick_args = {
            .callback = lvgl_tick_cb,
            .arg = NULL,
            .dispatch_method = ESP_TIMER_TASK,
            .name = "lvgl_tick"
        };

        ESP_RETURN_ON_ERROR(esp_timer_create(&tick_args, &s_lvgl_tick_timer), TAG, "esp_timer_create failed");
        ESP_RETURN_ON_ERROR(esp_timer_start_periodic(s_lvgl_tick_timer, LVGL_TICK_PERIOD_MS * 1000), TAG, "esp_timer_start_periodic failed");

        BaseType_t task_ok = xTaskCreatePinnedToCore(lvgl_port_task, "t_lvgl", 4000, NULL, tskIDLE_PRIORITY+5, &s_lvgl_task_handle, 1);
        
        ESP_RETURN_ON_FALSE(task_ok == pdPASS, ESP_FAIL, TAG, "Failed to create LVGL task");

        s_lvgl_initialized = true;

        ESP_LOGI(TAG, "LVGL initialized successfully.");
        ESP_LOGI(TAG, "Resolution: %dx%d", LCD_H_RES, LCD_V_RES);
        ESP_LOGI(TAG, "Buffer lines: %d", LVGL_BUF_LINES);
        ESP_LOGI(TAG, "Heap internal livre: %u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_INTERNAL));
        ESP_LOGI(TAG, "Heap PSRAM livre: %u", (unsigned)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));

        return ESP_OK;
}

image

image

@kdschlosser @kisvegabor