From a374068b996b77d9df71a1e15a0fa270ea24c755 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 25 Jun 2026 12:54:08 +0800 Subject: [PATCH] refactor(rgb_panel): localize panel setup helpers Move the RGB panel initialization out of the shared example component and into example-local files so the LCD setup flow and Kconfig stay close to the example. Rename the helper APIs and sync the log expectations so the teaching-oriented example remains easier to follow. --- examples/peripherals/lcd/rgb_panel/README.md | 32 ++++--- .../rgb_panel_init/CMakeLists.txt | 4 - .../lcd/rgb_panel/main/CMakeLists.txt | 4 +- .../rgb_panel_init => main}/Kconfig.projbuild | 0 .../example_rgb_lcd_panel.c} | 85 +++++++++---------- .../example_rgb_lcd_panel.h} | 22 ++--- .../lcd/rgb_panel/main/idf_component.yml | 2 - .../lcd/rgb_panel/main/rgb_lcd_example_main.c | 62 +++++++------- .../lcd/rgb_panel/pytest_rgb_panel_lvgl.py | 6 +- 9 files changed, 109 insertions(+), 108 deletions(-) delete mode 100644 examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/CMakeLists.txt rename examples/peripherals/lcd/rgb_panel/{common_components/rgb_panel_init => main}/Kconfig.projbuild (100%) rename examples/peripherals/lcd/rgb_panel/{common_components/rgb_panel_init/example_rgb_panel_init.c => main/example_rgb_lcd_panel.c} (55%) rename examples/peripherals/lcd/rgb_panel/{common_components/rgb_panel_init/include/example_rgb_panel_init.h => main/example_rgb_lcd_panel.h} (85%) diff --git a/examples/peripherals/lcd/rgb_panel/README.md b/examples/peripherals/lcd/rgb_panel/README.md index 3c2c0fdf97d..3550b0c2e80 100644 --- a/examples/peripherals/lcd/rgb_panel/README.md +++ b/examples/peripherals/lcd/rgb_panel/README.md @@ -3,9 +3,15 @@ # RGB LCD Panel Example -[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd/rgb_lcd.html) supports RGB interfaced LCD panel, with multiple buffer modes. This example shows the general process of installing an RGB panel driver, and displays a scatter chart on the screen based on the LVGL library. +[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd/rgb_lcd.html) supports RGB interfaced LCD panels with multiple buffering modes. This example shows how to create an RGB panel driver, connect it to LVGL, and display a simple demo UI. -This example uses the [esp_timer](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html) to generate the ticks needed by LVGL and uses a dedicated task to run the `lv_timer_handler()`. Since the LVGL APIs are not thread-safe, this example uses a mutex which be invoked before the call of `lv_timer_handler()` and released after it. The same mutex needs to be used in other tasks and threads around every LVGL (lv_...) related function call and code. +This example uses the [esp_timer](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/esp_timer.html) to generate LVGL ticks, and a dedicated task to run `lv_timer_handler()`. Since LVGL APIs are not thread-safe, the example also uses a mutex around every LVGL call. + +If you are new to this example, the easiest reading order is: + +1. `main/rgb_lcd_example_main.c`: overall initialization flow +2. `main/example_rgb_lcd_panel.h`: LCD timing and GPIO definitions +3. `main/example_rgb_lcd_panel.c`: RGB panel creation and initialization helpers This example uses 3 kinds of **buffering mode**: @@ -55,15 +61,17 @@ The connection between ESP Board and the LCD is as follows: Run `idf.py menuconfig` and go to `Example Configuration`: -1. `Use single frame buffer`: The RGB LCD driver allocates one frame buffer and mount it to the DMA. The example also allocates one draw buffer for the LVGL library. The draw buffer contents are copied to the frame buffer by the CPU. -2. `Use double frame buffer`: The RGB LCD driver allocates two frame buffers and mount them to the DMA. The LVGL library draws directly to the offline frame buffer while the online frame buffer is displayed by the RGB LCD controller. -3. `Use bounce buffer`: The RGB LCD driver allocates one frame buffer and two bounce buffers. The bounce buffers are mounted to the DMA. The frame buffer contents are copied to the bounce buffers by the CPU. The example also allocates one draw buffer for the LVGL library. The draw buffer contents are copied to the frame buffer by the CPU. -4. Choose the number of LCD data lines in `RGB LCD Data Lines` -5. Set the GPIOs used by RGB LCD peripheral in `GPIO assignment`, e.g. the synchronization signals (HSYNC, VSYNC, DE) and the data lines +1. `Use single frame buffer`: The driver allocates one full-screen frame buffer. LVGL renders into a smaller draw buffer, and the CPU copies the dirty area into the frame buffer. +2. `Use double frame buffer`: The driver allocates two full-screen frame buffers. LVGL renders directly into the offline frame buffer while the RGB LCD controller scans out the online frame buffer. +3. `Use bounce buffer`: The driver allocates one full-screen frame buffer plus two internal bounce buffers. This can help when PSRAM bandwidth is not enough, at the cost of extra CPU usage and internal RAM. +4. Choose the number of LCD data lines in `RGB LCD Data Lines`. +5. Set the RGB GPIOs in `RGB LCD GPIO assignment`, including `PCLK`, `HSYNC`, `VSYNC`, `DE`, and the data lines. + +You will usually need to update the timing values in [example_rgb_lcd_panel.h](main/example_rgb_lcd_panel.h) to match your LCD datasheet. ### Build and Flash -Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A scatter chart will show up on the LCD as expected. +Run `idf.py -p PORT build flash monitor` to build, flash, and monitor the project. If the timing and GPIO settings match your hardware, the LCD should show the LVGL demo UI. The first time you run `idf.py` for the example will cost extra time as the build system needs to address the component dependencies and downloads the missing components from the ESP Component Registry into `managed_components` folder. @@ -78,8 +86,9 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l I (872) main_task: Started on CPU0 I (882) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations I (882) main_task: Calling app_main() +I (892) example: Initialize LCD backlight I (892) example: Turn off LCD backlight -I (892) example: Install RGB LCD panel driver +I (892) example: Create RGB LCD panel I (922) example: Initialize RGB LCD panel I (922) example: Turn on LCD backlight I (922) example: Initialize LVGL library @@ -96,7 +105,8 @@ I (1102) main_task: Returned from app_main() ## Troubleshooting * Why the LCD doesn't light up? - * Please pay attention to the level used to turn on the LCD backlight, some LCD module needs a low level to turn it on, while others take a high level. You can change the backlight level macro `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL` in [lvgl_example_main.c](main/rgb_lcd_example_main.c). + * Please pay attention to the level used to turn on the LCD backlight, some LCD module needs a low level to turn it on, while others take a high level. You can change the backlight level macro `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL` in [example_rgb_lcd_panel.h](main/example_rgb_lcd_panel.h). + * Some LCD modules also require a separate initialization sequence over SPI or I2C before they can accept RGB data. This example only covers the RGB data path itself. * Where to allocate the frame buffer? * The frame buffer of RGB panel is located in ESP side (unlike other controller based LCDs, where the frame buffer is located in external chip). As the frame buffer usually consumes much RAM (depends on the LCD resolution and color depth), we recommend to put the frame buffer into PSRAM (like what we do in this example). However, putting frame buffer in PSRAM will limit the maximum PCLK due to the bandwidth of **SPI0**. * Why LCD screen drifts? @@ -107,6 +117,6 @@ I (1102) main_task: Returned from app_main() * Enable `CONFIG_EXAMPLE_USE_BOUNCE_BUFFER`, which will make the LCD controller fetch data from internal SRAM (instead of the PSRAM), but at the cost of increasing CPU usage. * Enable `CONFIG_SPIRAM_XIP_FROM_PSRAM` can also help if the you're not using the bounce buffer mode. These two configurations can save some **SPI0** bandwidth from being consumed by ICache. * Why the RGB timing is correct but the LCD doesn't show anything? - * Please read the datasheet of the IC used by your LCD module, and check if it needs a special initialization sequence. The initialization is usually done by sending some specific SPI commands and parameters to the IC. After the initialization, the LCD will be ready to receive RGB data. For simplicity, this example only works out of the box for those LCD modules which don't need extra initialization. + * Please read the datasheet of the IC used by your LCD module, and check if it needs a special initialization sequence. The initialization is usually done by sending specific commands and parameters over SPI or I2C. After that sequence, the LCD will be ready to receive RGB data. For simplicity, this example only works out of the box for panels that do not need extra initialization. For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/CMakeLists.txt b/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/CMakeLists.txt deleted file mode 100644 index d91a161dff5..00000000000 --- a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -idf_component_register(SRCS "example_rgb_panel_init.c" - INCLUDE_DIRS "include" - REQUIRES esp_lcd esp_driver_gpio - ) diff --git a/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt b/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt index 2c95073c568..c44eb1c388e 100644 --- a/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt +++ b/examples/peripherals/lcd/rgb_panel/main/CMakeLists.txt @@ -1,3 +1,3 @@ -idf_component_register(SRCS "rgb_lcd_example_main.c" "lvgl_demo_ui.c" - PRIV_REQUIRES esp_lcd esp_timer +idf_component_register(SRCS "rgb_lcd_example_main.c" "lvgl_demo_ui.c" "example_rgb_lcd_panel.c" + PRIV_REQUIRES esp_lcd esp_timer esp_driver_gpio INCLUDE_DIRS ".") diff --git a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/Kconfig.projbuild b/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild similarity index 100% rename from examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/Kconfig.projbuild rename to examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild diff --git a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/example_rgb_panel_init.c b/examples/peripherals/lcd/rgb_panel/main/example_rgb_lcd_panel.c similarity index 55% rename from examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/example_rgb_panel_init.c rename to examples/peripherals/lcd/rgb_panel/main/example_rgb_lcd_panel.c index 305e73180f8..b6aac9afb43 100644 --- a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/example_rgb_panel_init.c +++ b/examples/peripherals/lcd/rgb_panel/main/example_rgb_lcd_panel.c @@ -4,46 +4,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -#include "example_rgb_panel_init.h" -#include "driver/gpio.h" #include "esp_check.h" -#include "esp_lcd_panel_rgb.h" +#include "driver/gpio.h" +#include "esp_lcd_panel_ops.h" +#include "example_rgb_lcd_panel.h" -static const char *TAG = "rgb_panel_init"; +static const char *TAG = "example"; -static void s_fill_data_gpio_nums(gpio_num_t data_gpio_nums[ESP_LCD_RGB_BUS_WIDTH_MAX]) -{ - data_gpio_nums[0] = EXAMPLE_PIN_NUM_DATA0; - data_gpio_nums[1] = EXAMPLE_PIN_NUM_DATA1; - data_gpio_nums[2] = EXAMPLE_PIN_NUM_DATA2; - data_gpio_nums[3] = EXAMPLE_PIN_NUM_DATA3; - data_gpio_nums[4] = EXAMPLE_PIN_NUM_DATA4; - data_gpio_nums[5] = EXAMPLE_PIN_NUM_DATA5; - data_gpio_nums[6] = EXAMPLE_PIN_NUM_DATA6; - data_gpio_nums[7] = EXAMPLE_PIN_NUM_DATA7; - data_gpio_nums[8] = EXAMPLE_PIN_NUM_DATA8; - data_gpio_nums[9] = EXAMPLE_PIN_NUM_DATA9; - data_gpio_nums[10] = EXAMPLE_PIN_NUM_DATA10; - data_gpio_nums[11] = EXAMPLE_PIN_NUM_DATA11; - data_gpio_nums[12] = EXAMPLE_PIN_NUM_DATA12; - data_gpio_nums[13] = EXAMPLE_PIN_NUM_DATA13; - data_gpio_nums[14] = EXAMPLE_PIN_NUM_DATA14; - data_gpio_nums[15] = EXAMPLE_PIN_NUM_DATA15; -#if CONFIG_EXAMPLE_LCD_DATA_LINES > 16 - data_gpio_nums[16] = EXAMPLE_PIN_NUM_DATA16; - data_gpio_nums[17] = EXAMPLE_PIN_NUM_DATA17; - data_gpio_nums[18] = EXAMPLE_PIN_NUM_DATA18; - data_gpio_nums[19] = EXAMPLE_PIN_NUM_DATA19; - data_gpio_nums[20] = EXAMPLE_PIN_NUM_DATA20; - data_gpio_nums[21] = EXAMPLE_PIN_NUM_DATA21; - data_gpio_nums[22] = EXAMPLE_PIN_NUM_DATA22; - data_gpio_nums[23] = EXAMPLE_PIN_NUM_DATA23; -#endif -} - -esp_err_t example_rgb_panel_init_backlight(void) +esp_err_t example_rgb_lcd_backlight_init(void) { #if EXAMPLE_PIN_NUM_BK_LIGHT >= 0 + // The RGB panel data interface usually does not control the backlight itself, + // so drive the dedicated backlight GPIO separately when the board provides one. gpio_config_t bk_gpio_config = { .mode = GPIO_MODE_OUTPUT, .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT, @@ -54,7 +26,7 @@ esp_err_t example_rgb_panel_init_backlight(void) return ESP_OK; } -void example_rgb_panel_set_backlight(bool on) +void example_rgb_lcd_backlight_set(bool on) { if (EXAMPLE_PIN_NUM_BK_LIGHT < 0) { return; @@ -63,10 +35,11 @@ void example_rgb_panel_set_backlight(bool on) gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, on ? EXAMPLE_LCD_BK_LIGHT_ON_LEVEL : EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL); } -esp_err_t example_rgb_panel_new(esp_lcd_panel_handle_t *panel_handle) +esp_err_t example_rgb_lcd_panel_new(esp_lcd_panel_handle_t *panel_handle) { ESP_RETURN_ON_FALSE(panel_handle, ESP_ERR_INVALID_ARG, TAG, "panel handle is null"); + // Fill one esp_lcd_rgb_panel_config_t in one place: bus width, GPIO routing, timing, and frame buffers. esp_lcd_rgb_panel_config_t panel_config = { .data_width = CONFIG_EXAMPLE_LCD_DATA_LINES, .dma_burst_size = 64, @@ -80,6 +53,34 @@ esp_err_t example_rgb_panel_new(esp_lcd_panel_handle_t *panel_handle) .vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC, .hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC, .de_gpio_num = EXAMPLE_PIN_NUM_DE, + .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, + 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, +#if CONFIG_EXAMPLE_LCD_DATA_LINES > 16 + EXAMPLE_PIN_NUM_DATA16, + EXAMPLE_PIN_NUM_DATA17, + EXAMPLE_PIN_NUM_DATA18, + EXAMPLE_PIN_NUM_DATA19, + EXAMPLE_PIN_NUM_DATA20, + EXAMPLE_PIN_NUM_DATA21, + EXAMPLE_PIN_NUM_DATA22, + EXAMPLE_PIN_NUM_DATA23, +#endif + }, .timings = { .pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ, .h_res = EXAMPLE_LCD_H_RES, @@ -96,20 +97,14 @@ esp_err_t example_rgb_panel_new(esp_lcd_panel_handle_t *panel_handle) }, .flags.fb_in_psram = true, }; - s_fill_data_gpio_nums(panel_config.data_gpio_nums); return esp_lcd_new_rgb_panel(&panel_config, panel_handle); } -esp_err_t example_rgb_panel_init(esp_lcd_panel_handle_t panel_handle) +esp_err_t example_rgb_lcd_panel_init(esp_lcd_panel_handle_t panel_handle) { ESP_RETURN_ON_FALSE(panel_handle, ESP_ERR_INVALID_ARG, TAG, "panel handle is null"); + // reset() applies the panel reset sequence, while init() starts the RGB output engine. ESP_RETURN_ON_ERROR(esp_lcd_panel_reset(panel_handle), TAG, "reset RGB panel failed"); ESP_RETURN_ON_ERROR(esp_lcd_panel_init(panel_handle), TAG, "init RGB panel failed"); return ESP_OK; } - -esp_err_t example_rgb_panel_deinit(esp_lcd_panel_handle_t panel_handle) -{ - ESP_RETURN_ON_FALSE(panel_handle, ESP_ERR_INVALID_ARG, TAG, "panel handle is null"); - return esp_lcd_panel_del(panel_handle); -} diff --git a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/include/example_rgb_panel_init.h b/examples/peripherals/lcd/rgb_panel/main/example_rgb_lcd_panel.h similarity index 85% rename from examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/include/example_rgb_panel_init.h rename to examples/peripherals/lcd/rgb_panel/main/example_rgb_lcd_panel.h index 031f1319f98..63453180945 100644 --- a/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init/include/example_rgb_panel_init.h +++ b/examples/peripherals/lcd/rgb_panel/main/example_rgb_lcd_panel.h @@ -7,9 +7,9 @@ #pragma once #include -#include "esp_err.h" -#include "esp_lcd_panel_ops.h" #include "sdkconfig.h" +#include "esp_err.h" +#include "esp_lcd_panel_rgb.h" #ifdef __cplusplus extern "C" { @@ -18,7 +18,8 @@ extern "C" { //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Refresh Rate = 18000000/(1+40+20+800)/(1+10+5+480) = 42Hz +// Example timing: +// Refresh rate = 18 MHz / (HSYNC + HBP + H_RES + HFP) / (VSYNC + VBP + V_RES + VFP) = about 42 Hz #define EXAMPLE_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000) #define EXAMPLE_LCD_H_RES 800 #define EXAMPLE_LCD_V_RES 480 @@ -31,6 +32,7 @@ extern "C" { #define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1 #define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL +// Set these two GPIOs to -1 when the panel backlight or display-enable pin is fixed on the board. #define EXAMPLE_PIN_NUM_BK_LIGHT -1 #define EXAMPLE_PIN_NUM_DISP_EN -1 @@ -72,17 +74,17 @@ extern "C" { #define EXAMPLE_RGB_PANEL_NUM_FBS 1 #endif +// One RGB565 pixel uses 2 bytes, one RGB888 pixel uses 3 bytes. #if CONFIG_EXAMPLE_LCD_DATA_LINES_16 -#define EXAMPLE_PIXEL_SIZE 2 +#define EXAMPLE_PIXEL_SIZE 2 #elif CONFIG_EXAMPLE_LCD_DATA_LINES_24 -#define EXAMPLE_PIXEL_SIZE 3 +#define EXAMPLE_PIXEL_SIZE 3 #endif -esp_err_t example_rgb_panel_init_backlight(void); -void example_rgb_panel_set_backlight(bool on); -esp_err_t example_rgb_panel_new(esp_lcd_panel_handle_t *panel_handle); -esp_err_t example_rgb_panel_init(esp_lcd_panel_handle_t panel_handle); -esp_err_t example_rgb_panel_deinit(esp_lcd_panel_handle_t panel_handle); +esp_err_t example_rgb_lcd_backlight_init(void); +void example_rgb_lcd_backlight_set(bool on); +esp_err_t example_rgb_lcd_panel_new(esp_lcd_panel_handle_t *panel_handle); +esp_err_t example_rgb_lcd_panel_init(esp_lcd_panel_handle_t panel_handle); #ifdef __cplusplus } diff --git a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml index 3cee1989669..0a082a3aeca 100644 --- a/examples/peripherals/lcd/rgb_panel/main/idf_component.yml +++ b/examples/peripherals/lcd/rgb_panel/main/idf_component.yml @@ -1,4 +1,2 @@ dependencies: lvgl/lvgl: "9.5.0" - rgb_panel_init: - path: ${IDF_PATH}/examples/peripherals/lcd/rgb_panel/common_components/rgb_panel_init diff --git a/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c b/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c index c09e135a217..fe95036e4dd 100644 --- a/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c +++ b/examples/peripherals/lcd/rgb_panel/main/rgb_lcd_example_main.c @@ -12,16 +12,15 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_timer.h" -#include "example_rgb_panel_init.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_rgb.h" #include "esp_err.h" #include "esp_log.h" +#include "esp_lcd_panel_ops.h" #include "lvgl.h" +#include "example_rgb_lcd_panel.h" static const char *TAG = "example"; -// Keep the LVGL draw format local to this example. The panel bus format is configured separately. +// LVGL draw buffers must use the same pixel format as the RGB panel output. #if CONFIG_EXAMPLE_LCD_DATA_LINES_16 #define EXAMPLE_LV_COLOR_FORMAT LV_COLOR_FORMAT_RGB565 #elif CONFIG_EXAMPLE_LCD_DATA_LINES_24 @@ -30,10 +29,6 @@ static const char *TAG = "example"; #error "Unsupported LVGL color format" #endif -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////// Please update the following configuration according to your Application /////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - #define EXAMPLE_LVGL_DRAW_BUF_LINES 50 // number of display lines in each draw buffer #define EXAMPLE_LVGL_TICK_PERIOD_MS 2 #define EXAMPLE_LVGL_TASK_STACK_SIZE (5 * 1024) @@ -41,7 +36,8 @@ static const char *TAG = "example"; #define EXAMPLE_LVGL_TASK_MAX_DELAY_MS 500 #define EXAMPLE_LVGL_TASK_MIN_DELAY_MS 1000 / CONFIG_FREERTOS_HZ -// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it +// LVGL is not thread-safe. In this example both app_main() and the LVGL task touch +// the LVGL object tree, so guard every LVGL call with the same lock. static _lock_t lvgl_api_lock; static TaskHandle_t lvgl_task_handle; @@ -63,9 +59,9 @@ static bool example_on_frame_buf_complete(esp_lcd_panel_handle_t panel, const es static void example_lvgl_flush_wait_cb(lv_display_t *disp) { - // The flush callback only submits the rendered buffer to the LCD driver. - // With direct-mode double buffering, LVGL must wait until the RGB panel has - // switched away from the previous frame buffer before rendering into it again. + // In direct-mode double buffering, lv_display_flush_cb() only tells the driver + // which frame buffer should be displayed next. LVGL must then wait until the + // driver finishes switching buffers before it renders the next frame. if (lv_display_flush_is_last(disp)) { // Wait until the previous frame buffer is no longer referenced by DMA. ulTaskNotifyTake(pdTRUE, portMAX_DELAY); @@ -90,11 +86,11 @@ static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uin int offsety2 = area->y2; #if CONFIG_EXAMPLE_USE_DOUBLE_FB if (!lv_display_flush_is_last(disp)) { + // LVGL may split one frame into several dirty rectangles. In direct mode, + // switch the hardware frame buffer only after the last rectangle is done. lv_display_flush_ready(disp); return; } - // In direct mode, LVGL may flush multiple dirty areas. Switch the RGB panel to the new - // frame buffer only after the last dirty area has been rendered. offsetx1 = 0; offsety1 = 0; offsetx2 = EXAMPLE_LCD_H_RES - 1; @@ -117,6 +113,7 @@ static void example_lvgl_port_task(void *arg) ESP_LOGI(TAG, "Starting LVGL task"); uint32_t time_till_next_ms = 0; while (1) { + // lv_timer_handler() runs animations, input handling, and screen refresh scheduling. _lock_acquire(&lvgl_api_lock); time_till_next_ms = lv_timer_handler(); _lock_release(&lvgl_api_lock); @@ -130,66 +127,67 @@ static void example_lvgl_port_task(void *arg) void app_main(void) { + // Keep the backlight off while the RGB panel and LVGL are being configured. + ESP_ERROR_CHECK(example_rgb_lcd_backlight_init()); + ESP_LOGI(TAG, "Initialize LCD backlight"); ESP_LOGI(TAG, "Turn off LCD backlight"); - ESP_ERROR_CHECK(example_rgb_panel_init_backlight()); - example_rgb_panel_set_backlight(false); + example_rgb_lcd_backlight_set(false); - ESP_LOGI(TAG, "Install RGB LCD panel driver"); + // Create the RGB panel object from the GPIO/timing configuration in + // example_rgb_lcd_panel.{h,c}, then reset and start the hardware. + ESP_LOGI(TAG, "Create RGB LCD panel"); esp_lcd_panel_handle_t panel_handle = NULL; - ESP_ERROR_CHECK(example_rgb_panel_new(&panel_handle)); + ESP_ERROR_CHECK(example_rgb_lcd_panel_new(&panel_handle)); ESP_LOGI(TAG, "Initialize RGB LCD panel"); - ESP_ERROR_CHECK(example_rgb_panel_init(panel_handle)); + ESP_ERROR_CHECK(example_rgb_lcd_panel_init(panel_handle)); ESP_LOGI(TAG, "Turn on LCD backlight"); - example_rgb_panel_set_backlight(true); + example_rgb_lcd_backlight_set(true); + // Create one LVGL display that uses the RGB panel as its flush target. ESP_LOGI(TAG, "Initialize LVGL library"); lv_init(); - // create a lvgl display lv_display_t *display = lv_display_create(EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES); - // associate the rgb panel handle to the display lv_display_set_user_data(display, panel_handle); - // set color depth lv_display_set_color_format(display, EXAMPLE_LV_COLOR_FORMAT); - // create draw buffers void *buf1 = NULL; void *buf2 = NULL; #if CONFIG_EXAMPLE_USE_DOUBLE_FB ESP_LOGI(TAG, "Use frame buffers as LVGL draw buffers"); ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2)); - // set LVGL draw buffers and direct mode + // Direct mode lets LVGL render straight into the hardware frame buffers. lv_display_set_buffers(display, buf1, buf2, EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * EXAMPLE_PIXEL_SIZE, LV_DISPLAY_RENDER_MODE_DIRECT); #else ESP_LOGI(TAG, "Allocate LVGL draw buffers"); - // it's recommended to allocate the draw buffer from internal memory, for better performance + // Partial mode uses a smaller draw buffer and copies only the dirty area to the panel. + // Allocate this buffer from internal RAM for better DMA and CPU access performance. size_t draw_buffer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LVGL_DRAW_BUF_LINES * EXAMPLE_PIXEL_SIZE; buf1 = esp_lcd_rgb_alloc_draw_buffer(panel_handle, draw_buffer_sz, 0); assert(buf1); - // set LVGL draw buffers and partial mode lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL); #endif // CONFIG_EXAMPLE_USE_DOUBLE_FB - // set the callback which can copy the rendered image to an area of the display + // Connect LVGL's flush path to the RGB panel driver. lv_display_set_flush_cb(display, example_lvgl_flush_cb); #if CONFIG_EXAMPLE_USE_DOUBLE_FB - // The wait callback keeps LVGL from reusing a frame buffer until the panel driver - // reports that the buffer has finished refreshing. lv_display_set_flush_wait_cb(display, example_lvgl_flush_wait_cb); #endif ESP_LOGI(TAG, "Register event callbacks"); esp_lcd_rgb_panel_event_callbacks_t cbs = { #if CONFIG_EXAMPLE_USE_DOUBLE_FB + // Signal LVGL when the panel has switched to the new frame buffer. .on_frame_buf_complete = example_on_frame_buf_complete, #else + // Signal LVGL when the driver finishes copying the dirty area. .on_color_trans_done = example_notify_lvgl_flush_ready, #endif }; ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, display)); ESP_LOGI(TAG, "Install LVGL tick timer"); - // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) + // Feed LVGL with a periodic tick so it can keep time for animations and timers. const esp_timer_create_args_t lvgl_tick_timer_args = { .callback = &example_increase_lvgl_tick, .name = "lvgl_tick" @@ -201,8 +199,8 @@ void app_main(void) ESP_LOGI(TAG, "Create LVGL task"); xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, &lvgl_task_handle); + // Build the demo UI once the display pipeline is ready. ESP_LOGI(TAG, "Display LVGL UI"); - // Lock the mutex due to the LVGL APIs are not thread-safe _lock_acquire(&lvgl_api_lock); example_lvgl_demo_ui(display); _lock_release(&lvgl_api_lock); diff --git a/examples/peripherals/lcd/rgb_panel/pytest_rgb_panel_lvgl.py b/examples/peripherals/lcd/rgb_panel/pytest_rgb_panel_lvgl.py index db6538f6c69..3d7eb4420d0 100644 --- a/examples/peripherals/lcd/rgb_panel/pytest_rgb_panel_lvgl.py +++ b/examples/peripherals/lcd/rgb_panel/pytest_rgb_panel_lvgl.py @@ -17,8 +17,9 @@ from pytest_embedded_idf.utils import idf_parametrize ) @idf_parametrize('target', ['esp32s3'], indirect=['target']) def test_rgb_lcd_lvgl_esp32s3(dut: Dut) -> None: + dut.expect_exact('example: Initialize LCD backlight') dut.expect_exact('example: Turn off LCD backlight') - dut.expect_exact('example: Install RGB LCD panel driver') + dut.expect_exact('example: Create RGB LCD panel') dut.expect_exact('example: Initialize RGB LCD panel') dut.expect_exact('example: Turn on LCD backlight') dut.expect_exact('example: Initialize LVGL library') @@ -39,8 +40,9 @@ def test_rgb_lcd_lvgl_esp32s3(dut: Dut) -> None: ) @idf_parametrize('target', ['esp32p4', 'esp32s31'], indirect=['target']) def test_rgb_lcd_lvgl(dut: Dut) -> None: + dut.expect_exact('example: Initialize LCD backlight') dut.expect_exact('example: Turn off LCD backlight') - dut.expect_exact('example: Install RGB LCD panel driver') + dut.expect_exact('example: Create RGB LCD panel') dut.expect_exact('example: Initialize RGB LCD panel') dut.expect_exact('example: Turn on LCD backlight') dut.expect_exact('example: Initialize LVGL library')