From c1ce91f4ab86d78f4a2de9b502cca617703eac91 Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Wed, 17 Jun 2026 15:49:51 +0800 Subject: [PATCH 1/5] feat(rgb_lcd): support draw bitmap hook --- components/esp_lcd/dsi/esp_lcd_panel_dpi.c | 36 ++- .../esp_lcd/dsi/include/esp_lcd_mipi_dsi.h | 48 +--- components/esp_lcd/include/esp_lcd_types.h | 55 +++- components/esp_lcd/linker.lf | 28 ++ components/esp_lcd/rgb/esp_lcd_panel_rgb.c | 266 ++++++++++++++++-- .../esp_lcd/rgb/include/esp_lcd_panel_rgb.h | 48 +++- components/esp_lcd/rgb/rgb_lcd_rotation_sw.h | 78 +++-- .../mipi_dsi_lcd/main/test_mipi_dsi_panel.c | 15 +- .../test_apps/rgb_lcd/main/CMakeLists.txt | 2 +- .../test_apps/rgb_lcd/main/test_rgb_panel.c | 256 +++++++++++++++++ .../api-reference/peripherals/lcd/dsi_lcd.rst | 54 +++- .../api-reference/peripherals/lcd/rgb_lcd.rst | 70 +++++ .../api-reference/peripherals/lcd/dsi_lcd.rst | 54 +++- .../api-reference/peripherals/lcd/rgb_lcd.rst | 70 +++++ .../lcd/rgb_panel/main/Kconfig.projbuild | 8 + .../lcd/rgb_panel/main/rgb_lcd_example_main.c | 6 + 16 files changed, 972 insertions(+), 122 deletions(-) diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index b2a94a529d6..a9ba69dee4b 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -528,7 +528,7 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel) esp_lcd_panel_hooks_t hooks = { .draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook, }; - ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, dpi_panel->user_ctx), err, TAG, "register DMA2D draw bitmap hook failed"); + ESP_GOTO_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), err, TAG, "register DMA2D draw bitmap hook failed"); return ESP_OK; @@ -549,14 +549,19 @@ esp_err_t esp_lcd_dpi_panel_disable_dma2d(esp_lcd_panel_handle_t panel) // Check if built-in DMA2D draw bitmap hook is registered ESP_RETURN_ON_FALSE(dpi_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook not registered"); + // Clear the hook first so new draws stop entering the DMA2D path before uninstall. esp_lcd_panel_hooks_t hooks = { .draw_bitmap_hook = NULL, }; ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed"); - if (dpi_panel->fbcpy_handle) { - ESP_RETURN_ON_ERROR(esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed"); - dpi_panel->fbcpy_handle = NULL; + esp_err_t ret = esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle); + if (ret != ESP_OK) { + // Restore the hook so draw_bitmap_hook and fbcpy_handle stay consistent on failure + hooks.draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook; + esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "uninstall DMA2D failed"); } + dpi_panel->fbcpy_handle = NULL; dpi_panel->on_hook_end = NULL; return ESP_OK; @@ -586,14 +591,35 @@ static esp_err_t dpi_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, i size_t fb_size = dpi_panel->fb_size; size_t bits_per_pixel = dpi_panel->bits_per_pixel; - // clip to boundaries int h_res = dpi_panel->h_pixels; int v_res = dpi_panel->v_pixels; + + // save the original coordinates before clipping + int unclipped_x_start = x_start; + int unclipped_y_start = y_start; + int unclipped_x_end = x_end; + int unclipped_y_end = y_end; + + // clip to boundaries x_start = MAX(x_start, 0); x_end = MIN(x_end, h_res); y_start = MAX(y_start, 0); y_end = MIN(y_end, v_res); + // adjust the source coordinates to the clipped region + src_x_start += x_start - unclipped_x_start; + src_y_start += y_start - unclipped_y_start; + src_x_end -= unclipped_x_end - x_end; + src_y_end -= unclipped_y_end - y_end; + + if (x_start >= x_end || y_start >= y_end || src_x_start >= src_x_end || src_y_start >= src_y_end) { + // no valid region to draw, skip + if (dpi_panel->on_color_trans_done) { + dpi_panel->on_color_trans_done(&dpi_panel->base, NULL, dpi_panel->user_ctx); + } + return ESP_OK; + } + bool do_copy = false; uint8_t draw_buf_fb_index = 0; // check if the user draw buffer resides in any frame buffer's memory range diff --git a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h index e03b5cc6052..455c50b92d2 100644 --- a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h +++ b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h @@ -221,52 +221,6 @@ typedef struct { */ esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t dpi_panel, const esp_lcd_dpi_panel_event_callbacks_t *cbs, void *user_ctx); -/** - * @brief Type of draw bitmap hook data - */ -typedef struct { - void *dst_data; /*!< Destination buffer (usually frame buffer) */ - int dst_x_size; /*!< Destination bitmap width */ - int dst_y_size; /*!< Destination bitmap height */ - int dst_x_start; /*!< Destination start x coordinate */ - int dst_y_start; /*!< Destination start y coordinate */ - int dst_x_end; /*!< Destination end x coordinate (exclusive) */ - int dst_y_end; /*!< Destination end y coordinate (exclusive) */ - const void *src_data; /*!< Source bitmap data */ - int src_x_size; /*!< Source bitmap width */ - int src_y_size; /*!< Source bitmap height */ - int src_x_start; /*!< Source start x coordinate */ - int src_y_start; /*!< Source start y coordinate */ - int src_x_end; /*!< Source end x coordinate (exclusive) */ - int src_y_end; /*!< Source end y coordinate (exclusive) */ - int bits_per_pixel; /*!< Bits per pixel */ - bool (*on_hook_end)(esp_lcd_panel_handle_t panel); /*!< Callback to be invoked when the hook completes its operation */ -} esp_lcd_draw_bitmap_hook_data_t; - -/** - * @brief draw bitmap hook function type for custom pixel processing operations - * - * This hook allows users to implement custom operations like scaling, rotation, - * color space conversion, etc. using hardware accelerators like PPA or DMA2D. - * - * @note The hook should ensure the synchronization of draw operations on its own. - * - * @param[in] panel LCD panel handle - * @param[in] hook_data Hook data - * @param[in] hook_ctx Hook context - * @return - * - ESP_OK on success - * - Other error codes on failure - */ -typedef esp_err_t (*esp_lcd_panel_draw_bitmap_hook_t)(esp_lcd_panel_handle_t panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void* hook_ctx); - -/** - * @brief Type of LCD panel hooks - */ -typedef struct { - esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; /*!< Draw bitmap hook function */ -} esp_lcd_panel_hooks_t; - /** * @brief Register panel hooks to the DPI panel * @@ -286,7 +240,7 @@ esp_err_t esp_lcd_dpi_panel_register_hooks(esp_lcd_panel_handle_t dpi_panel, con /** * @brief Enable DMA2D for DPI panel * - * @note The function will register a built-in DMA2D draw bitmap hook to perform draw bitmap operations using DMA2D. + * @note The function will register a built-in DMA2D draw bitmap hook to perform bitmap copy using DMA2D. * * @param[in] dpi_panel LCD DPI panel handle, which is returned from esp_lcd_new_panel_dpi() * @return diff --git a/components/esp_lcd/include/esp_lcd_types.h b/components/esp_lcd/include/esp_lcd_types.h index ac58d2af67c..4d8b8231668 100644 --- a/components/esp_lcd/include/esp_lcd_types.h +++ b/components/esp_lcd/include/esp_lcd_types.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ #include #include "esp_assert.h" +#include "esp_err.h" #include "hal/lcd_types.h" #include "hal/gpio_types.h" #include "hal/color_types.h" @@ -72,6 +73,58 @@ typedef struct { lcd_yuv_conv_std_t conv_std; /*!< YUV conversion standard: BT601, BT709 */ } esp_lcd_color_conv_yuv_config_t; +/** + * @brief Type of draw bitmap hook data + */ +typedef struct { + void *dst_data; /*!< Destination buffer (usually frame buffer) */ + int dst_x_size; /*!< Destination bitmap width */ + int dst_y_size; /*!< Destination bitmap height */ + int dst_x_start; /*!< Destination start x coordinate */ + int dst_y_start; /*!< Destination start y coordinate */ + int dst_x_end; /*!< Destination end x coordinate (exclusive) */ + int dst_y_end; /*!< Destination end y coordinate (exclusive) */ + const void *src_data; /*!< Source bitmap data */ + int src_x_size; /*!< Source bitmap width */ + int src_y_size; /*!< Source bitmap height */ + int src_x_start; /*!< Source start x coordinate */ + int src_y_start; /*!< Source start y coordinate */ + int src_x_end; /*!< Source end x coordinate (exclusive) */ + int src_y_end; /*!< Source end y coordinate (exclusive) */ + int bits_per_pixel; /*!< Bits per pixel */ + bool (*on_hook_end)(esp_lcd_panel_handle_t panel); /*!< Callback to be invoked by an asynchronous hook after the custom draw + operation completes. This notifies the panel driver to finish the draw + transaction. If a color transfer done callback has been registered, it + also invokes that callback */ +} esp_lcd_draw_bitmap_hook_data_t; + +/** + * @brief draw bitmap hook function type for custom pixel processing operations + * + * This hook allows users to implement custom operations like scaling, rotation, + * color space conversion, etc. using hardware accelerators like PPA or DMA2D. + * + * @note For asynchronous operations, the hook should call hook_data->on_hook_end() after the operation is complete. + * The panel driver does not wait for a previous draw to finish; the hook must handle synchronization itself. + * The simplest approach is to serialize draws. To queue multiple transactions (e.g. via PPA), keep per-transaction + * hook_data, keep source buffers valid until completion, and handle overlapping destinations carefully. + * + * @param[in] panel LCD panel handle + * @param[in] hook_data Hook data + * @param[in] hook_ctx Hook context + * @return + * - ESP_OK on success + * - Other error codes on failure + */ +typedef esp_err_t (*esp_lcd_panel_draw_bitmap_hook_t)(esp_lcd_panel_handle_t panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void* hook_ctx); + +/** + * @brief Type of LCD panel hooks + */ +typedef struct { + esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; /*!< Draw bitmap hook function */ +} esp_lcd_panel_hooks_t; + #ifdef __cplusplus } #endif diff --git a/components/esp_lcd/linker.lf b/components/esp_lcd/linker.lf index f3f63a8dc1f..105896e1408 100644 --- a/components/esp_lcd/linker.lf +++ b/components/esp_lcd/linker.lf @@ -4,6 +4,15 @@ entries: if LCD_DSI_ISR_HANDLER_IN_IRAM = y: esp_lcd_panel_dpi: mipi_dsi_dma_trans_done_cb (noflash) esp_lcd_panel_dpi: mipi_dsi_bridge_isr_handler (noflash) + esp_lcd_panel_dpi: dpi_panel_draw_bitmap_hook_end (noflash) + if SOC_DMA2D_SUPPORTED = y: + esp_lcd_panel_dpi: async_fbcpy_done_cb (noflash) + +[mapping:esp_lcd_async_color_convert] +archive: libesp_driver_dma.a +entries: + if (LCD_DSI_ISR_HANDLER_IN_IRAM = y || LCD_RGB_ISR_IRAM_SAFE = y) && SOC_DMA2D_SUPPORTED = y: + async_color_convert_dma2d: async_color_convert_done_cb (noflash) [mapping:esp_lcd_dsi_dma] archive: libesp_driver_dma.a @@ -21,7 +30,26 @@ entries: if LCD_RGB_ISR_IRAM_SAFE = y: gdma: gdma_reset (noflash) gdma: gdma_start (noflash) + gdma: gdma_request_link_switch_event (noflash) gdma_link: gdma_link_get_head_addr (noflash) + gdma_link: gdma_link_concat (noflash) + +[mapping:esp_lcd_rgb_dma_hal] +archive: libesp_hal_dma.a +entries: + if LCD_RGB_ISR_IRAM_SAFE = y: + gdma_hal_top: gdma_hal_request_link_switch_event (noflash) + if SOC_AXI_GDMA_SUPPORTED = y: + gdma_hal_axi: gdma_axi_hal_request_link_switch_event (noflash) + +[mapping:esp_lcd_rgb] +archive: libesp_lcd.a +entries: + if LCD_RGB_ISR_IRAM_SAFE = y: + esp_lcd_panel_rgb: rgb_panel_update_dma_link (noflash) + esp_lcd_panel_rgb: rgb_panel_draw_bitmap_hook_end (noflash) + if SOC_DMA2D_SUPPORTED = y: + esp_lcd_panel_rgb: async_fbcpy_done_cb (noflash) [mapping:esp_lcd_rgb_hal_common] archive: libhal.a diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index 6b3dc3a9342..58684f12149 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -49,6 +49,7 @@ #include "hal/color_hal.h" #include "rgb_lcd_rotation_sw.h" #include "esp_private/sleep_retention.h" +#include "esp_async_color_convert.h" #if SOC_HAS(AXI_GDMA) #include "hal/axi_dma_ll.h" @@ -90,6 +91,8 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel); static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel); static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel); static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data); +static esp_err_t rgb_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data, + size_t src_x_size, size_t src_y_size, int src_x_start, int src_y_start, int src_x_end, int src_y_end); static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); @@ -167,6 +170,11 @@ struct esp_rgb_panel_t { uint32_t bb_behind_cache: 1; // Whether the bounce buffer is behind the cache uint32_t user_fb: 1; // Whether the frame buffer is provided by user } flags; + // hook fields + esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function + void* hook_ctx; // Hook context + bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation + async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy }; static esp_err_t lcd_rgb_panel_alloc_frame_buffers(esp_rgb_panel_t *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config) @@ -446,6 +454,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf rgb_panel->base.reset = rgb_panel_reset; rgb_panel->base.init = rgb_panel_init; rgb_panel->base.draw_bitmap = rgb_panel_draw_bitmap; + rgb_panel->base.draw_bitmap_2d = rgb_panel_draw_bitmap_2d; rgb_panel->base.disp_on_off = rgb_panel_disp_on_off; rgb_panel->base.invert_color = rgb_panel_invert_color; rgb_panel->base.mirror = rgb_panel_mirror; @@ -589,6 +598,10 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel) { esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); int panel_id = rgb_panel->panel_id; + // check if the panel is using DMA2D draw bitmap hook + if (rgb_panel->fbcpy_handle) { + ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_STATE, TAG, "please call `esp_lcd_rgb_panel_disable_dma2d()` before deleting the panel"); + } ESP_RETURN_ON_ERROR(lcd_rgb_panel_destroy(rgb_panel), TAG, "destroy rgb panel(%d) failed", panel_id); ESP_LOGD(TAG, "del rgb panel(%d)", panel_id); return ESP_OK; @@ -658,17 +671,73 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) } static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + size_t src_x_size = x_end - x_start; + size_t src_y_size = y_end - y_start; + size_t src_x_start = 0; + size_t src_y_start = 0; + size_t src_x_end = src_x_size; + size_t src_y_end = src_y_size; + ESP_RETURN_ON_ERROR(rgb_panel_draw_bitmap_2d(panel, x_start, y_start, x_end, y_end, color_data, src_x_size, src_y_size, src_x_start, src_y_start, src_x_end, src_y_end), + TAG, "draw bitmap failed"); + return ESP_OK; +} + +static void rgb_panel_update_dma_link(esp_rgb_panel_t *rgb_panel) +{ + if (!rgb_panel->bb_size && rgb_panel->flags.stream_mode) { + for (int i = 0; i < rgb_panel->num_fbs; i++) { + // Note, because of DMA prefetch, there's possibility that the old frame buffer might be sent out again + // it's hard to know the time when the new frame buffer starts + gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0); + } +#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT + gdma_request_link_switch_event(rgb_panel->dma_chan); +#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT + } +} + +static bool rgb_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel) +{ + esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); + rgb_panel_update_dma_link(rgb_panel); + if (rgb_panel->on_color_trans_done) { + return rgb_panel->on_color_trans_done(panel, NULL, rgb_panel->user_ctx); + } + return false; +} + +#if SOC_HAS(DMA2D) +static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args) +{ + bool need_yield = false; + esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)cb_args; + (void)conv_hdl; + (void)event; + + if (rgb_panel->on_hook_end) { + if (rgb_panel->on_hook_end(&rgb_panel->base)) { + need_yield = true; + } + } + return need_yield; +} +#endif // SOC_HAS(DMA2D) + +static esp_err_t rgb_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data, + size_t src_x_size, size_t src_y_size, int src_x_start, int src_y_start, int src_x_end, int src_y_end) { esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); ESP_RETURN_ON_FALSE(rgb_panel->num_fbs > 0, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed"); esp_lcd_rgb_panel_draw_buf_complete_cb_t cb = rgb_panel->on_color_trans_done; + uint8_t cur_fb_index = rgb_panel->cur_fb_index; + uint8_t *frame_buffer = rgb_panel->fbs[cur_fb_index]; uint8_t *draw_buffer = (uint8_t *)color_data; size_t fb_size = rgb_panel->fb_size; int h_res = rgb_panel->timings.h_res; int v_res = rgb_panel->timings.v_res; int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8; - uint32_t bytes_per_line = bytes_per_pixel * h_res; // adjust the flush window by adding extra gap x_start += rgb_panel->x_gap; @@ -676,6 +745,12 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int x_end += rgb_panel->x_gap; y_end += rgb_panel->y_gap; + // save the original coordinates before clipping + int unclipped_x_start = x_start; + int unclipped_y_start = y_start; + int unclipped_x_end = x_end; + int unclipped_y_end = y_end; + // clip to boundaries if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) { x_start = MAX(x_start, 0); @@ -696,6 +771,20 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int return ESP_OK; } + // adjust the source coordinates to the clipped region + src_x_start += x_start - unclipped_x_start; + src_y_start += y_start - unclipped_y_start; + src_x_end -= unclipped_x_end - x_end; + src_y_end -= unclipped_y_end - y_end; + + if (x_start >= x_end || y_start >= y_end || src_x_start >= src_x_end || src_y_start >= src_y_end) { + // no valid region to draw, skip + if (cb) { + cb(&rgb_panel->base, NULL, rgb_panel->user_ctx); + } + return ESP_OK; + } + // check if we want to copy the draw buffer to the internal frame buffer bool draw_buf_copy_to_fb = true; uint8_t draw_buf_fb_index = 0; @@ -707,17 +796,44 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int } } - if (draw_buf_copy_to_fb) { + if (rgb_panel->draw_bitmap_hook && draw_buf_copy_to_fb && !rgb_panel->rotate_mask) { // copy using draw bitmap hook + ESP_LOGV(TAG, "copy draw buffer by draw bitmap hook"); + // Note, whether the previous draw operation is finished should be ensured by the hook. + // For the built-in DMA2D hook, cache maintenance of the source and destination + // buffers is handled inside the async color convert driver. + + esp_lcd_draw_bitmap_hook_data_t hook_data = { + .dst_data = frame_buffer, + .dst_x_size = h_res, + .dst_y_size = v_res, + .dst_x_start = x_start, + .dst_y_start = y_start, + .dst_x_end = x_end, + .dst_y_end = y_end, + .src_data = draw_buffer, + .src_x_size = src_x_size, + .src_y_size = src_y_size, + .src_x_start = src_x_start, + .src_y_start = src_y_start, + .src_x_end = src_x_end, + .src_y_end = src_y_end, + .bits_per_pixel = rgb_panel->fb_bits_per_pixel, + .on_hook_end = rgb_panel_draw_bitmap_hook_end, + }; + + ESP_RETURN_ON_ERROR(rgb_panel->draw_bitmap_hook(panel, &hook_data, rgb_panel->hook_ctx), TAG, "draw_bitmap_hook failed"); + return ESP_OK; + } else if (draw_buf_copy_to_fb) { // copy by CPU // sync the draw buffer with the frame buffer by CPU copy ESP_LOGV(TAG, "copy draw buffer to frame buffer by CPU"); - uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index]; - size_t bytes_to_flush = v_res * bytes_per_line; - uint8_t *flush_ptr = fb; - - const uint8_t *from = (const uint8_t *)color_data; + uint8_t *fb = frame_buffer; + const uint8_t *from_base = draw_buffer; uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel; - size_t offset = y_start * copy_bytes_per_line + x_start * bytes_per_pixel; - uint8_t *to = fb; + uint32_t bytes_per_line = bytes_per_pixel * h_res; + uint32_t src_bytes_per_line = bytes_per_pixel * src_x_size; + size_t bytes_to_flush = 0; + uint8_t *flush_ptr = NULL; + if (1 == bytes_per_pixel) { COPY_PIXEL_CODE_BLOCK(8) } else if (2 == bytes_per_pixel) { @@ -726,22 +842,21 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int COPY_PIXEL_CODE_BLOCK(24) } // do memory sync only when the frame buffer is mounted to the DMA link list and behind the cache - if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) { + if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache && flush_ptr) { esp_cache_msync(flush_ptr, bytes_to_flush, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); } // after the draw buffer finished copying, notify the user to recycle the draw buffer if (cb) { cb(&rgb_panel->base, NULL, rgb_panel->user_ctx); } - } else { - ESP_LOGV(TAG, "draw buffer is part of the frame buffer"); - // the new frame buffer index is changed + } else { // no copy, just do cache memory write back + ESP_LOGV(TAG, "draw buffer is in frame buffer memory range, do cache write back only"); + // only write back the LCD lines that updated by the draw buffer rgb_panel->cur_fb_index = draw_buf_fb_index; - // when this function is called, the frame buffer already reflects the draw buffer changes - // if the frame buffer is also mounted to the DMA, we need to do the sync between them - if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) { - uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel; - size_t cache_sync_size = (y_end - y_start) * bytes_per_line; + uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel; + size_t cache_sync_size = (y_end - y_start) * h_res * bytes_per_pixel; + // the buffer to be flushed is still within the frame buffer, so even an unaligned address is OK + if (rgb_panel->flags.fb_behind_cache) { esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); } // after the draw buffer finished copying, notify the user to recycle the draw buffer @@ -750,18 +865,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int } } - if (!rgb_panel->bb_size) { - if (rgb_panel->flags.stream_mode) { - for (int i = 0; i < rgb_panel->num_fbs; i++) { - // Note, because of DMA prefetch, there's possibility that the old frame buffer might be sent out again - // it's hard to know the time when the new frame buffer starts - gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0); - } -#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT - ESP_RETURN_ON_ERROR(gdma_request_link_switch_event(rgb_panel->dma_chan), TAG, "request link switch event failed"); -#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT - } - } + rgb_panel_update_dma_link(rgb_panel); return ESP_OK; } @@ -1325,6 +1429,108 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args) } } +esp_err_t esp_lcd_rgb_panel_register_hooks(esp_lcd_panel_handle_t panel, const esp_lcd_panel_hooks_t *hooks, void *hook_ctx) +{ + ESP_RETURN_ON_FALSE(panel && hooks, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); + + rgb_panel->draw_bitmap_hook = hooks->draw_bitmap_hook; + rgb_panel->hook_ctx = hook_ctx; + + return ESP_OK; +} + +#if SOC_HAS(DMA2D) +static esp_err_t rgb_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void* hook_ctx) +{ + ESP_LOGV(TAG, "copy draw buffer by DMA2D"); + esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); + (void)hook_ctx; + + async_color_convert_request_t fbcpy_trans_config = { + .src_buffer = hook_data->src_data, + .dst_buffer = hook_data->dst_data, + .src_stride = hook_data->src_x_size, + .src_height = hook_data->src_y_size, + .dst_stride = hook_data->dst_x_size, + .dst_height = hook_data->dst_y_size, + .src_x = hook_data->src_x_start, + .src_y = hook_data->src_y_start, + .dst_x = hook_data->dst_x_start, + .dst_y = hook_data->dst_y_start, + .copy_width = hook_data->src_x_end - hook_data->src_x_start, + .copy_height = hook_data->src_y_end - hook_data->src_y_start, + // For this DMA2D hook we only do window copy from draw buffer to frame buffer. + // Source and destination color formats are intentionally set to the same value to disable CSC. + .src_color_format = rgb_panel->in_color_format, + .dst_color_format = rgb_panel->in_color_format, + }; + // The async color convert backend owns source/destination cache sync for the + // DMA2D copy path, so the LCD driver should not perform extra cache sync here. + // Save the completion callback and invoke it when the async frame buffer copy finishes. + rgb_panel->on_hook_end = hook_data->on_hook_end; + ESP_RETURN_ON_ERROR(esp_async_color_convert(rgb_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, rgb_panel), TAG, "async frame buffer copy failed"); + return ESP_OK; +} + +esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel) +{ + ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel"); + esp_err_t ret = ESP_OK; + esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); + + // Check if built-in DMA2D draw bitmap hook is registered + ESP_RETURN_ON_FALSE(!rgb_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook is already registered"); + + // Initialize the async color convert backend used by the built-in DMA2D copy hook. + // Use its default backlog to queue multiple frame buffer copy requests. + async_color_convert_config_t fbcpy_config = {}; + ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &rgb_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); + + // Register the DMA2D draw bitmap hook + esp_lcd_panel_hooks_t hooks = { + .draw_bitmap_hook = rgb_panel_draw_bitmap_dma2d_hook, + }; + ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL), err, TAG, "register DMA2D draw bitmap hook failed"); + + return ESP_OK; + +err: + if (rgb_panel->fbcpy_handle) { + esp_async_color_convert_uninstall(rgb_panel->fbcpy_handle); + rgb_panel->fbcpy_handle = NULL; + } + rgb_panel->on_hook_end = NULL; + return ret; +} + +esp_err_t esp_lcd_rgb_panel_disable_dma2d(esp_lcd_panel_handle_t panel) +{ + ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); + + // Check if built-in DMA2D draw bitmap hook is registered + ESP_RETURN_ON_FALSE(rgb_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook not registered"); + + // Clear the hook first so new draws stop entering the DMA2D path before uninstall. + esp_lcd_panel_hooks_t hooks = { + .draw_bitmap_hook = NULL, + }; + ESP_RETURN_ON_ERROR(esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed"); + esp_err_t ret = esp_async_color_convert_uninstall(rgb_panel->fbcpy_handle); + if (ret != ESP_OK) { + // Restore the hook so draw_bitmap_hook and fbcpy_handle stay consistent on failure + hooks.draw_bitmap_hook = rgb_panel_draw_bitmap_dma2d_hook; + esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL); + ESP_RETURN_ON_ERROR(ret, TAG, "uninstall DMA2D failed"); + } + rgb_panel->fbcpy_handle = NULL; + rgb_panel->on_hook_end = NULL; + + return ESP_OK; +} +#endif // SOC_HAS(DMA2D) + #if CONFIG_LCD_ENABLE_DEBUG_LOG __attribute__((constructor)) static void rgb_lcd_override_default_log_level(void) diff --git a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h index a55b50135e9..3b517f804fa 100644 --- a/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h +++ b/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -282,6 +282,52 @@ void *esp_lcd_rgb_alloc_draw_buffer(esp_lcd_panel_handle_t panel, size_t size, u */ esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, const esp_lcd_color_conv_yuv_config_t *config); +/** + * @brief Register panel hooks to the RGB panel + * + * @note You can register panel hooks to implement custom operations like scaling, rotation, color space conversion, etc. + * with hardware accelerators like PPA or DMA2D. + * The hook will be overridden when this function is called multiple times. + * @note If software rotation is enabled by mirror or swap_xy, the RGB panel driver bypasses the draw bitmap hook and + * falls back to the CPU copy path to apply the rotation transform. + * + * @param[in] panel LCD RGB panel handle, which is returned from esp_lcd_new_rgb_panel() + * @param[in] hooks Panel hooks + * @param[in] hook_ctx Hook context + * @return + * - ESP_OK: Register hooks successfully + * - Other error codes on failure + */ +esp_err_t esp_lcd_rgb_panel_register_hooks(esp_lcd_panel_handle_t panel, const esp_lcd_panel_hooks_t *hooks, void *hook_ctx); + +#if SOC_DMA2D_SUPPORTED +/** + * @brief Enable DMA2D for RGB panel + * + * @note The function will register a built-in DMA2D draw bitmap hook to perform bitmap copy using DMA2D. + * @note If software rotation is enabled by mirror or swap_xy, the RGB panel driver bypasses the built-in DMA2D hook and + * falls back to the CPU copy path, because the built-in DMA2D hook does not apply rotation transforms. + * + * @param[in] panel LCD RGB panel handle, which is returned from esp_lcd_new_rgb_panel() + * @return + * - ESP_OK: Enable DMA2D successfully + * - Other error codes on failure + */ +esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel); + +/** + * @brief Disable DMA2D for RGB panel + * + * @note The function will unregister the built-in DMA2D draw bitmap hook. + * + * @param[in] panel LCD RGB panel handle, which is returned from esp_lcd_new_rgb_panel() + * @return + * - ESP_OK: Disable DMA2D successfully + * - Other error codes on failure + */ +esp_err_t esp_lcd_rgb_panel_disable_dma2d(esp_lcd_panel_handle_t panel); +#endif // SOC_DMA2D_SUPPORTED + #endif // SOC_LCD_RGB_SUPPORTED #ifdef __cplusplus diff --git a/components/esp_lcd/rgb/rgb_lcd_rotation_sw.h b/components/esp_lcd/rgb/rgb_lcd_rotation_sw.h index 401786cbcbd..334ba6c2e9c 100644 --- a/components/esp_lcd/rgb/rgb_lcd_rotation_sw.h +++ b/components/esp_lcd/rgb/rgb_lcd_rotation_sw.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -44,71 +44,85 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from) *to++ = *from++; } +// Macro for draw_bitmap_2d that supports source image stride and offset +// Requires: from_base (source buffer base), src_x_start, src_y_start, src_bytes_per_line #define COPY_PIXEL_CODE_BLOCK(_bpp) \ switch (rgb_panel->rotate_mask) \ { \ case 0: \ { \ uint8_t *to = fb + (y_start * h_res + x_start) * bytes_per_pixel; \ + const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \ for (int y = y_start; y < y_end; y++) \ { \ memcpy(to, from, copy_bytes_per_line); \ to += bytes_per_line; \ - from += copy_bytes_per_line; \ + from += src_bytes_per_line; \ } \ bytes_to_flush = (y_end - y_start) * bytes_per_line; \ flush_ptr = fb + y_start * bytes_per_line; \ } \ break; \ case ROTATE_MASK_MIRROR_X: \ - for (int y = y_start; y < y_end; y++) \ { \ - uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \ - for (size_t x = x_start; x < x_end; x++) \ + const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \ + for (int y = y_start; y < y_end; y++) \ { \ - copy_pixel_##_bpp##bpp(to + index, from); \ - index -= bytes_per_pixel; \ - from += bytes_per_pixel; \ + uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \ + for (size_t x = x_start; x < x_end; x++) \ + { \ + copy_pixel_##_bpp##bpp(fb + index, from); \ + index -= bytes_per_pixel; \ + from += bytes_per_pixel; \ + } \ + from += src_bytes_per_line - copy_bytes_per_line; \ } \ + bytes_to_flush = (y_end - y_start) * bytes_per_line; \ + flush_ptr = fb + y_start * bytes_per_line; \ } \ - bytes_to_flush = (y_end - y_start) * bytes_per_line; \ - flush_ptr = fb + y_start * bytes_per_line; \ break; \ case ROTATE_MASK_MIRROR_Y: \ { \ uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + x_start) * bytes_per_pixel; \ + const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \ for (int y = y_start; y < y_end; y++) \ { \ - memcpy(to, from, copy_bytes_per_line); \ + memcpy(to, from, copy_bytes_per_line); \ to -= bytes_per_line; \ - from += copy_bytes_per_line; \ + from += src_bytes_per_line; \ } \ bytes_to_flush = (y_end - y_start) * bytes_per_line; \ flush_ptr = fb + (v_res - y_end) * bytes_per_line; \ } \ break; \ case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \ - for (int y = y_start; y < y_end; y++) \ { \ - uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \ - for (size_t x = x_start; x < x_end; x++) \ + const uint8_t *from = from_base + src_y_start * src_bytes_per_line + src_x_start * bytes_per_pixel; \ + for (int y = y_start; y < y_end; y++) \ { \ - copy_pixel_##_bpp##bpp(to + index, from); \ - index -= bytes_per_pixel; \ - from += bytes_per_pixel; \ + uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \ + for (size_t x = x_start; x < x_end; x++) \ + { \ + copy_pixel_##_bpp##bpp(fb + index, from); \ + index -= bytes_per_pixel; \ + from += bytes_per_pixel; \ + } \ + from += src_bytes_per_line - copy_bytes_per_line; \ } \ + bytes_to_flush = (y_end - y_start) * bytes_per_line; \ + flush_ptr = fb + (v_res - y_end) * bytes_per_line; \ } \ - bytes_to_flush = (y_end - y_start) * bytes_per_line; \ - flush_ptr = fb + (v_res - y_end) * bytes_per_line; \ break; \ case ROTATE_MASK_SWAP_XY: \ for (int y = y_start; y < y_end; y++) \ { \ for (int x = x_start; x < x_end; x++) \ { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ + uint32_t src_y = src_y_start + (y - y_start); \ + uint32_t src_x = src_x_start + (x - x_start); \ + uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \ uint32_t i = (x * h_res + y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ + copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \ } \ } \ bytes_to_flush = (x_end - x_start) * bytes_per_line; \ @@ -119,9 +133,11 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from) { \ for (int x = x_start; x < x_end; x++) \ { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ - uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ + uint32_t src_y = src_y_start + (y - y_start); \ + uint32_t src_x = src_x_start + (x - x_start); \ + uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \ + uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \ + copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \ } \ } \ bytes_to_flush = (x_end - x_start) * bytes_per_line; \ @@ -132,9 +148,11 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from) { \ for (int x = x_start; x < x_end; x++) \ { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ + uint32_t src_y = src_y_start + (y - y_start); \ + uint32_t src_x = src_x_start + (x - x_start); \ + uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \ uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ + copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \ } \ } \ bytes_to_flush = (x_end - x_start) * bytes_per_line; \ @@ -145,9 +163,11 @@ static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from) { \ for (int x = x_start; x < x_end; x++) \ { \ - uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \ + uint32_t src_y = src_y_start + (y - y_start); \ + uint32_t src_x = src_x_start + (x - x_start); \ + uint32_t src_j = src_y * src_bytes_per_line + src_x * bytes_per_pixel; \ uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \ - copy_pixel_##_bpp##bpp(to + i, from + j); \ + copy_pixel_##_bpp##bpp(fb + i, from_base + src_j); \ } \ } \ bytes_to_flush = (x_end - x_start) * bytes_per_line; \ diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c index 1c642b9ded6..1dc59637ac9 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c @@ -292,7 +292,6 @@ typedef struct { typedef struct { uint32_t count; - SemaphoreHandle_t draw_sem; } test_dpi_panel_color_trans_done_callback_ctx_t; IRAM_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx) @@ -307,6 +306,12 @@ IRAM_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_c } } + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(hook_ctx->draw_sem, &task_woken); + if (task_woken == pdTRUE) { + need_yield = true; + } + return need_yield; } @@ -353,11 +358,9 @@ static esp_err_t test_draw_bitmap_hook_ppa(esp_lcd_panel_handle_t panel, const e IRAM_ATTR static bool test_dpi_panel_color_trans_done_count_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) { - BaseType_t task_woken = pdFALSE; test_dpi_panel_color_trans_done_callback_ctx_t *color_trans_done_ctx = (test_dpi_panel_color_trans_done_callback_ctx_t *)user_ctx; color_trans_done_ctx->count++; - xSemaphoreGiveFromISR(color_trans_done_ctx->draw_sem, &task_woken); - return task_woken == pdTRUE; + return false; } TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]") @@ -437,7 +440,6 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]") }; test_dpi_panel_color_trans_done_callback_ctx_t color_trans_done_ctx = { - .draw_sem = draw_sem, .count = 0, }; TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &color_trans_done_ctx)); @@ -463,6 +465,7 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]") img, 200, 200, 0, 0, 200, 200); vTaskDelay(pdMS_TO_TICKS(10)); } + xSemaphoreTake(draw_sem, portMAX_DELAY); TEST_ASSERT_EQUAL_INT(100, color_trans_done_ctx.count); hooks.draw_bitmap_hook = NULL; @@ -472,7 +475,7 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]") TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel)); TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io)); TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus)); - vSemaphoreDelete(draw_sem); + vSemaphoreDeleteWithCaps(draw_sem); free(img); test_bsp_disable_dsi_phy_power(); diff --git a/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt b/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt index 66fd95ca6a7..7379bc71be0 100644 --- a/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt +++ b/components/esp_lcd/test_apps/rgb_lcd/main/CMakeLists.txt @@ -5,5 +5,5 @@ set(srcs "test_app_main.c" # In order for the cases defined by `TEST_CASE` to be linked into the final elf, # the component can be registered as WHOLE_ARCHIVE idf_component_register(SRCS ${srcs} - PRIV_REQUIRES esp_lcd unity esp_timer spi_flash + PRIV_REQUIRES esp_lcd unity esp_timer spi_flash esp_driver_ppa efuse WHOLE_ARCHIVE) diff --git a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c index 26f664f902d..3b836a5fe6c 100644 --- a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c +++ b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c @@ -9,6 +9,7 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "unity.h" +#include "soc/soc_caps.h" #include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_ops.h" #include "esp_random.h" @@ -17,6 +18,10 @@ #include "test_rgb_board.h" #include "esp_private/spi_flash_os.h" #include "esp_clk_tree.h" +#include "driver/ppa.h" +#include "esp_efuse.h" + +#define ALIGN_DOWN(num, align) ((num) & ~((align) - 1)) #if CONFIG_LCD_RGB_ISR_IRAM_SAFE #define TEST_LCD_CALLBACK_ATTR IRAM_ATTR @@ -382,3 +387,254 @@ TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]") free(img); } #endif // CONFIG_LCD_RGB_ISR_IRAM_SAFE + +TEST_CASE("lcd_rgb_panel_draw_bitmap_2d", "[lcd]") +{ + // Allocate a larger source image (200x200) for testing partial copy + size_t src_img_size = 200 * 200 * sizeof(uint16_t); + uint8_t *src_img = malloc(src_img_size); + TEST_ASSERT_NOT_NULL(src_img); + + printf("initialize RGB panel with stream mode\r\n"); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + + printf("Draw bitmap 2D by CPU - copy partial region from source to destination\r\n"); + for (int i = 0; i < 100; i++) { + int x_start = rand() % (TEST_LCD_H_RES - 100); + int y_start = rand() % (TEST_LCD_V_RES - 100); + // Fill source image with random pattern + uint8_t color_byte = rand() & 0xFF; + memset(src_img, color_byte, src_img_size / 2); + color_byte = rand() & 0xFF; + memset(src_img + src_img_size / 2, color_byte, src_img_size / 2); + + // Copy a 100x100 region from source (starting at 50,50) to destination at (x_start, y_start) + // Source image is 200x200, we copy region from (50,50) to (150,150) + esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + 100, y_start + 100, + src_img, 200, 200, 50, 50, 150, 150); + vTaskDelay(pdMS_TO_TICKS(10)); + } + vTaskDelay(pdMS_TO_TICKS(1000)); + + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + free(src_img); +} + +#if SOC_HAS(DMA2D) +TEST_CASE("lcd_rgb_panel_dma2d_hook", "[lcd]") +{ + // Allocate a larger source image (200x200) for testing partial copy + size_t src_img_size = 200 * 200 * sizeof(uint16_t); + size_t buffer_alignment = 1; + if (esp_efuse_is_flash_encryption_enabled()) { + buffer_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT; + } + uint8_t *src_img = heap_caps_aligned_calloc(buffer_alignment, 1, src_img_size, MALLOC_CAP_DMA | MALLOC_CAP_SPIRAM); + TEST_ASSERT_NOT_NULL(src_img); + + printf("initialize RGB panel with stream mode\r\n"); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + + printf("Draw bitmap 2D by CPU first\r\n"); + for (int i = 0; i < 50; i++) { + int x_start = rand() % (TEST_LCD_H_RES - 100); + int y_start = rand() % (TEST_LCD_V_RES - 100); + uint8_t color_byte = rand() & 0xFF; + memset(src_img, color_byte, src_img_size / 2); + color_byte = rand() & 0xFF; + memset(src_img + src_img_size / 2, color_byte, src_img_size / 2); + esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + 100, y_start + 100, + src_img, 200, 200, 50, 50, 150, 150); + vTaskDelay(pdMS_TO_TICKS(10)); + } + vTaskDelay(pdMS_TO_TICKS(1000)); + + size_t test_block_size = 100; + size_t start_alignment = 1; + size_t src_x_start = 50; + size_t src_y_start = 50; + if (esp_efuse_is_flash_encryption_enabled()) { + test_block_size = ALIGN_DOWN(test_block_size, SOC_MEMSPI_ENCRYPTION_ALIGNMENT); + start_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT; + src_x_start = ALIGN_DOWN(src_x_start, SOC_MEMSPI_ENCRYPTION_ALIGNMENT); + src_y_start = ALIGN_DOWN(src_y_start, SOC_MEMSPI_ENCRYPTION_ALIGNMENT); + } + + printf("Enable DMA2D draw bitmap hook\r\n"); + TEST_ESP_OK(esp_lcd_rgb_panel_enable_dma2d(panel_handle)); + printf("Draw bitmap 2D by DMA2D\r\n"); + for (int i = 0; i < 100; i++) { + int x_start = ALIGN_DOWN(rand() % (TEST_LCD_H_RES - test_block_size), start_alignment); + int y_start = ALIGN_DOWN(rand() % (TEST_LCD_V_RES - test_block_size), start_alignment); + uint8_t color_byte = rand() & 0xFF; + memset(src_img, color_byte, src_img_size / 2); + color_byte = rand() & 0xFF; + memset(src_img + src_img_size / 2, color_byte, src_img_size / 2); + esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + test_block_size, y_start + test_block_size, + src_img, 200, 200, src_x_start, src_y_start, src_x_start + test_block_size, src_y_start + test_block_size); + vTaskDelay(pdMS_TO_TICKS(10)); + } + vTaskDelay(pdMS_TO_TICKS(1000)); + + printf("Disable DMA2D draw bitmap hook\r\n"); + TEST_ESP_OK(esp_lcd_rgb_panel_disable_dma2d(panel_handle)); + vTaskDelay(pdMS_TO_TICKS(1000)); + + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + free(src_img); +} +#endif // SOC_HAS(DMA2D) + +#if SOC_HAS(PPA) +typedef struct { + ppa_client_handle_t ppa_srm_handle; + esp_lcd_draw_bitmap_hook_data_t hook_data; + SemaphoreHandle_t draw_sem; + esp_lcd_panel_handle_t panel; +} test_rgb_panel_draw_bitmap_hook_ctx_t; + +typedef struct { + uint32_t count; +} test_rgb_panel_color_trans_done_callback_ctx_t; + +TEST_LCD_CALLBACK_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx) +{ + bool need_yield = false; + test_rgb_panel_draw_bitmap_hook_ctx_t *hook_ctx = (test_rgb_panel_draw_bitmap_hook_ctx_t *)user_ctx; + esp_lcd_draw_bitmap_hook_data_t *hook_data = &hook_ctx->hook_data; + + if (hook_data->on_hook_end) { + if (hook_data->on_hook_end(hook_ctx->panel)) { + need_yield = true; + } + } + + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(hook_ctx->draw_sem, &task_woken); + if (task_woken == pdTRUE) { + need_yield = true; + } + + return need_yield; +} + +static esp_err_t test_draw_bitmap_hook_ppa(esp_lcd_panel_handle_t panel, const esp_lcd_draw_bitmap_hook_data_t *hook_data, void *user_ctx) +{ + test_rgb_panel_draw_bitmap_hook_ctx_t *hook_ctx = (test_rgb_panel_draw_bitmap_hook_ctx_t *)user_ctx; + ppa_client_handle_t ppa_srm_handle = hook_ctx->ppa_srm_handle; + xSemaphoreTake(hook_ctx->draw_sem, portMAX_DELAY); + memcpy(&hook_ctx->hook_data, hook_data, sizeof(esp_lcd_draw_bitmap_hook_data_t)); + ppa_srm_oper_config_t srm_config = { + .in.buffer = hook_data->src_data, + .in.pic_w = hook_data->src_x_size, + .in.pic_h = hook_data->src_y_size, + .in.block_w = hook_data->src_x_end - hook_data->src_x_start, + .in.block_h = hook_data->src_y_end - hook_data->src_y_start, + .in.block_offset_x = hook_data->src_x_start, + .in.block_offset_y = hook_data->src_y_start, + .in.srm_cm = PPA_SRM_COLOR_MODE_RGB565, + .out.buffer = hook_data->dst_data, + .out.buffer_size = hook_data->dst_x_size * hook_data->dst_y_size * hook_data->bits_per_pixel / 8, + .out.pic_w = hook_data->dst_x_size, + .out.pic_h = hook_data->dst_y_size, + .out.block_offset_x = hook_data->dst_x_start, + .out.block_offset_y = hook_data->dst_y_start, + .out.srm_cm = PPA_SRM_COLOR_MODE_RGB565, + .rotation_angle = PPA_SRM_ROTATION_ANGLE_90, + .scale_x = 0.5, + .scale_y = 0.5, + .rgb_swap = 0, + .byte_swap = 0, + .mode = PPA_TRANS_MODE_NON_BLOCKING, + .user_data = hook_ctx, + }; + + ppa_event_callbacks_t ppa_srm_event_callbacks = { + .on_trans_done = test_ppa_srm_trans_done_callback, + }; + TEST_ESP_OK(ppa_client_register_event_callbacks(ppa_srm_handle, &ppa_srm_event_callbacks)); + + TEST_ESP_OK(ppa_do_scale_rotate_mirror(ppa_srm_handle, &srm_config)); + + return ESP_OK; +} + +TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_color_trans_done_count_callback(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) +{ + test_rgb_panel_color_trans_done_callback_ctx_t *color_trans_done_ctx = (test_rgb_panel_color_trans_done_callback_ctx_t *)user_ctx; + color_trans_done_ctx->count++; + return false; +} + +TEST_CASE("lcd_rgb_panel_ppa_hook", "[lcd]") +{ + if (esp_efuse_is_flash_encryption_enabled()) { + TEST_PASS_MESSAGE("PPA SRM is not compatible with encrypted memory, skip this test"); + } + + // Allocate a larger source image (200x200) for testing + size_t src_img_size = 200 * 200 * sizeof(uint16_t); + uint8_t *src_img = malloc(src_img_size); + TEST_ASSERT_NOT_NULL(src_img); + + printf("initialize RGB panel with stream mode\r\n"); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + + SemaphoreHandle_t draw_sem = xSemaphoreCreateBinaryWithCaps(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + TEST_ASSERT_NOT_NULL(draw_sem); + xSemaphoreGive(draw_sem); + + // use PPA to scale and rotate the image in draw bitmap hook + ppa_client_handle_t ppa_srm_handle = NULL; + ppa_client_config_t ppa_srm_config = { + .oper_type = PPA_OPERATION_SRM, + .max_pending_trans_num = 1, + }; + TEST_ESP_OK(ppa_register_client(&ppa_srm_config, &ppa_srm_handle)); + + esp_lcd_rgb_panel_event_callbacks_t cbs = { + .on_color_trans_done = test_rgb_panel_color_trans_done_count_callback, + }; + + test_rgb_panel_color_trans_done_callback_ctx_t color_trans_done_ctx = { + .count = 0, + }; + TEST_ESP_OK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, &color_trans_done_ctx)); + + printf("Add PPA draw bitmap hook\r\n"); + esp_lcd_panel_hooks_t hooks = { + .draw_bitmap_hook = test_draw_bitmap_hook_ppa, + }; + test_rgb_panel_draw_bitmap_hook_ctx_t hook_ctx = { + .draw_sem = draw_sem, + .ppa_srm_handle = ppa_srm_handle, + .panel = panel_handle, + }; + TEST_ESP_OK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, &hook_ctx)); + + for (int i = 0; i < 100; i++) { + int x_start = rand() % (TEST_LCD_H_RES - 100); + int y_start = rand() % (TEST_LCD_V_RES - 100); + uint8_t color_byte = rand() & 0xFF; + memset(src_img, color_byte, src_img_size / 2); + color_byte = rand() & 0xFF; + memset(src_img + src_img_size / 2, color_byte, src_img_size / 2); + esp_lcd_panel_draw_bitmap_2d(panel_handle, x_start, y_start, x_start + 50, y_start + 50, + src_img, 200, 200, 0, 0, 200, 200); + vTaskDelay(pdMS_TO_TICKS(10)); + } + xSemaphoreTake(draw_sem, portMAX_DELAY); + TEST_ASSERT_EQUAL_INT(100, color_trans_done_ctx.count); + + hooks.draw_bitmap_hook = NULL; + TEST_ESP_OK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, NULL)); + TEST_ESP_OK(ppa_unregister_client(ppa_srm_handle)); + + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + vSemaphoreDeleteWithCaps(draw_sem); + free(src_img); +} +#endif // SOC_HAS(PPA) diff --git a/docs/en/api-reference/peripherals/lcd/dsi_lcd.rst b/docs/en/api-reference/peripherals/lcd/dsi_lcd.rst index 5d15966550e..6c7e689ee8e 100644 --- a/docs/en/api-reference/peripherals/lcd/dsi_lcd.rst +++ b/docs/en/api-reference/peripherals/lcd/dsi_lcd.rst @@ -86,7 +86,7 @@ MIPI DSI Interfaced LCD #. Configure draw bitmap hook function (optional) - If you want to use DMA2D to implement draw bitmap, the driver has already implemented the DMA2D draw bitmap hook function, you only need to call :func:`esp_lcd_dpi_panel_enable_dma2d` to enable it. + If you want to accelerate 2D bitmap copy with DMA2D, the driver already provides a built-in DMA2D bitmap copy hook. You only need to call :func:`esp_lcd_dpi_panel_enable_dma2d` to enable it. .. code-block:: c @@ -105,6 +105,58 @@ MIPI DSI Interfaced LCD }; ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx)); + If the custom hook is asynchronous — for example, the hook starts a PPA transfer and returns immediately while the hardware continues processing pixels in the background — call :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end` only after the hardware operation has actually finished. ``on_hook_end`` is implemented and filled into ``hook_data`` by the DPI panel driver; you do not need to write it yourself, only call it when the asynchronous operation completes to notify the driver that the draw transaction is finished. If a color transfer done callback has been registered, it is invoked at that time as well. + + The panel driver does not wait for a previous draw to finish; synchronization is the custom hook's responsibility. The simplest approach is to serialize draws (do not start a new one before the previous one completes), as shown in the example below. If you want to submit multiple draws concurrently using an accelerator transaction queue such as PPA's, keep a separate ``hook_data`` copy per transaction, keep the source buffer valid until hardware completion, and handle overlapping destination regions carefully. + + The following snippet shows an asynchronous custom hook: it starts PPA and returns immediately, then calls ``on_hook_end`` from the PPA completion callback. The example serializes draws with a semaphore so only one draw is in flight at a time: + + .. code-block:: c + + typedef struct { + esp_lcd_panel_handle_t panel; + esp_lcd_draw_bitmap_hook_data_t hook_data; + SemaphoreHandle_t draw_sem; + // ... other fields, e.g. ppa_client_handle_t + } draw_bitmap_hook_ctx_t; + + static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + bool need_yield = false; + + // on_hook_end is provided by the DPI panel driver; just call it when done + if (ctx->hook_data.on_hook_end) { + if (ctx->hook_data.on_hook_end(ctx->panel)) { + need_yield = true; + } + } + + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken); + if (task_woken == pdTRUE) { + need_yield = true; + } + return need_yield; + } + + static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel, + const esp_lcd_draw_bitmap_hook_data_t *hook_data, + void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + + // Simplest sync: wait until the previous draw finishes + xSemaphoreTake(ctx->draw_sem, portMAX_DELAY); + + // Save hook_data so the completion callback can call on_hook_end later + ctx->hook_data = *hook_data; + + // Start an asynchronous PPA transfer, then return immediately + // ppa_do_scale_rotate_mirror(...); + return ESP_OK; + } + Power Supply for MIPI DPHY -------------------------- diff --git a/docs/en/api-reference/peripherals/lcd/rgb_lcd.rst b/docs/en/api-reference/peripherals/lcd/rgb_lcd.rst index 63bc6a52b96..0321a2acd92 100644 --- a/docs/en/api-reference/peripherals/lcd/rgb_lcd.rst +++ b/docs/en/api-reference/peripherals/lcd/rgb_lcd.rst @@ -257,6 +257,76 @@ This mode is similar to :ref:`bounce_buffer_with_single_psram_frame_buffer`, but In a well-designed embedded application, situations where the DMA cannot deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can theoretically occur. In the {IDF_TARGET_NAME} hardware, this results in the LCD outputting dummy bytes while the DMA waits for data. If the DMA were to run in a continuous stream, it could cause a desynchronization between the LCD address from which the DMA reads data and the address from which the LCD peripheral outputs data, leading to a **permanently** shifted image. To prevent this, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, allowing the driver to automatically restart the DMA during the VBlank interrupt, or call :cpp:func:`esp_lcd_rgb_panel_restart` to manually restart the DMA. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted at the next VSYNC event. +Draw Bitmap Hook Function +------------------------- + +If you want to accelerate 2D bitmap copy with DMA2D, the driver already provides a built-in DMA2D bitmap copy hook. You only need to call :cpp:func:`esp_lcd_rgb_panel_enable_dma2d` to enable it. + +.. code-block:: c + + ESP_ERROR_CHECK(esp_lcd_rgb_panel_enable_dma2d(panel_handle)); + +If you need more advanced applications, you can add a custom hook for draw bitmap, such as using PPA to implement rotation, scaling, etc. + +.. code-block:: c + + esp_lcd_panel_hooks_t hooks = { + .draw_bitmap_hook = custom_draw_bitmap_hook, + }; + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, &user_ctx)); + +If the custom hook is asynchronous — for example, the hook starts a PPA transfer and returns immediately while the hardware continues processing pixels in the background — call :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end` only after the hardware operation has actually finished. ``on_hook_end`` is implemented and filled into ``hook_data`` by the RGB panel driver; you do not need to write it yourself, only call it when the asynchronous operation completes to notify the driver that the draw transaction is finished. If a color transfer done callback has been registered, it is invoked at that time as well. + +The panel driver does not wait for a previous draw to finish; synchronization is the custom hook's responsibility. The simplest approach is to serialize draws (do not start a new one before the previous one completes), as shown in the example below. If you want to submit multiple draws concurrently using an accelerator transaction queue such as PPA's, keep a separate ``hook_data`` copy per transaction, keep the source buffer valid until hardware completion, and handle overlapping destination regions carefully. + +The following snippet shows an asynchronous custom hook: it starts PPA and returns immediately, then calls ``on_hook_end`` from the PPA completion callback. The example serializes draws with a semaphore so only one draw is in flight at a time: + +.. code-block:: c + + typedef struct { + esp_lcd_panel_handle_t panel; + esp_lcd_draw_bitmap_hook_data_t hook_data; + SemaphoreHandle_t draw_sem; + // ... other fields, e.g. ppa_client_handle_t + } draw_bitmap_hook_ctx_t; + + static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + bool need_yield = false; + + // on_hook_end is provided by the RGB panel driver; just call it when done + if (ctx->hook_data.on_hook_end) { + if (ctx->hook_data.on_hook_end(ctx->panel)) { + need_yield = true; + } + } + + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken); + if (task_woken == pdTRUE) { + need_yield = true; + } + return need_yield; + } + + static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel, + const esp_lcd_draw_bitmap_hook_data_t *hook_data, + void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + + // Simplest sync: wait until the previous draw finishes + xSemaphoreTake(ctx->draw_sem, portMAX_DELAY); + + // Save hook_data so the completion callback can call on_hook_end later + ctx->hook_data = *hook_data; + + // Start an asynchronous PPA transfer, then return immediately + // ppa_do_scale_rotate_mirror(...); + return ESP_OK; + } + API Reference ------------- diff --git a/docs/zh_CN/api-reference/peripherals/lcd/dsi_lcd.rst b/docs/zh_CN/api-reference/peripherals/lcd/dsi_lcd.rst index 71a7733c4c2..b23118c5e64 100644 --- a/docs/zh_CN/api-reference/peripherals/lcd/dsi_lcd.rst +++ b/docs/zh_CN/api-reference/peripherals/lcd/dsi_lcd.rst @@ -86,7 +86,7 @@ MIPI DSI 接口的 LCD #. 配置绘制位图钩子函数(可选) - 若想使用 DMA2D 实现绘制位图,驱动程序内部已实现 DMA2D 绘制位图的钩子函数,用户只需调用 :func:`esp_lcd_dpi_panel_enable_dma2d` 即可。 + 若想使用 DMA2D 加速位图的复制,驱动程序内部已实现基于 DMA2D 的位图复制钩子函数,用户只需调用 :func:`esp_lcd_dpi_panel_enable_dma2d` 即可。 .. code-block:: c @@ -105,6 +105,58 @@ MIPI DSI 接口的 LCD }; ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_hooks(mipi_dpi_panel, &hooks, &user_ctx)); + 如果自定义钩子是异步的——例如钩子函数启动 PPA 后立即返回,真正的像素处理仍由硬件在后台执行——则必须在硬件操作真正完成后再调用 :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end`。该回调由 DPI 面板驱动实现并填入 ``hook_data``,用户无需自行编写,只需在异步操作完成时调用它,以通知驱动结束本次绘制事务;若已注册颜色传输完成回调,也会在此时被调用。 + + 面板驱动本身不会等待上一笔绘制结束,同步由自定义钩子自行负责。最简单的做法是串行执行(上一笔完成前不启动下一笔),如下面示例所示。若希望利用 PPA 等加速器的事务队列并发提交多笔绘制,则需要为每笔事务单独保存 ``hook_data``、保证源 buffer 在硬件完成前有效,并处理好目标区域重叠等问题。 + + 下面是一个异步自定义钩子的示意代码:钩子启动 PPA 后立即返回,并在 PPA 完成回调中调用 ``on_hook_end``。示例采用串行方式,用信号量保证同一时间只有一笔绘制在进行: + + .. code-block:: c + + typedef struct { + esp_lcd_panel_handle_t panel; + esp_lcd_draw_bitmap_hook_data_t hook_data; + SemaphoreHandle_t draw_sem; + // ... 其他字段,例如 ppa_client_handle_t + } draw_bitmap_hook_ctx_t; + + static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + bool need_yield = false; + + // on_hook_end 由 DPI 面板驱动提供,操作完成后直接调用即可 + if (ctx->hook_data.on_hook_end) { + if (ctx->hook_data.on_hook_end(ctx->panel)) { + need_yield = true; + } + } + + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken); + if (task_woken == pdTRUE) { + need_yield = true; + } + return need_yield; + } + + static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel, + const esp_lcd_draw_bitmap_hook_data_t *hook_data, + void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + + // 最简单的同步方式:等待上一笔完成后再启动本笔 + xSemaphoreTake(ctx->draw_sem, portMAX_DELAY); + + // 保存 hook_data,供完成回调稍后调用 on_hook_end + ctx->hook_data = *hook_data; + + // 启动异步 PPA 传输后立即返回 + // ppa_do_scale_rotate_mirror(...); + return ESP_OK; + } + 关于 MIPI DPHY 的供电 --------------------- diff --git a/docs/zh_CN/api-reference/peripherals/lcd/rgb_lcd.rst b/docs/zh_CN/api-reference/peripherals/lcd/rgb_lcd.rst index 63f6d4afb94..16540319de9 100644 --- a/docs/zh_CN/api-reference/peripherals/lcd/rgb_lcd.rst +++ b/docs/zh_CN/api-reference/peripherals/lcd/rgb_lcd.rst @@ -257,6 +257,76 @@ bounce buffer 与 PSRAM frame buffer 虽说在设计良好的嵌入式应用程序中, DMA 传递数据的速度不应该赶不上 LCD 读取数据的速度。但理论上,此种情况还是有可能出现的。在 {IDF_TARGET_NAME} 的硬件中,这种情况会导致 LCD 在 DMA 等待数据时单纯输出 dummy 字节。若以流式传输运行 DMA,则 DMA 会将读取到的数据传输到某个 LCD 地址,同时 LCD 也会将数据输出到某个 LCD 地址,但上述两个地址可能会不同步,导致图像 **永久** 偏移。 为防止类似情况发生,可以启用 :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` 选项,以便驱动程序在 VBlank 中断时自动重启 DMA;或者也可以调用 :cpp:func:`esp_lcd_rgb_panel_restart`,手动重启 DMA。请注意,调用 :cpp:func:`esp_lcd_rgb_panel_restart` 不会立即重启 DMA,DMA 只会在下一个 VSYNC 事件中重启。 +绘制位图钩子函数 +---------------- + +若想使用 DMA2D 加速位图的复制,驱动程序内部已实现基于 DMA2D 的位图复制钩子函数,用户只需调用 :cpp:func:`esp_lcd_rgb_panel_enable_dma2d` 即可。 + +.. code-block:: c + + ESP_ERROR_CHECK(esp_lcd_rgb_panel_enable_dma2d(panel_handle)); + +若需更高级的应用,用户可为绘制位图添加自定义钩子,例如通过 PPA 实现旋转、缩放等操作。 + +.. code-block:: c + + esp_lcd_panel_hooks_t hooks = { + .draw_bitmap_hook = custom_draw_bitmap_hook, + }; + ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_hooks(panel_handle, &hooks, &user_ctx)); + +如果自定义钩子是异步的——例如钩子函数启动 PPA 后立即返回,真正的像素处理仍由硬件在后台执行——则必须在硬件操作真正完成后再调用 :cpp:member:`esp_lcd_draw_bitmap_hook_data_t::on_hook_end`。该回调由 RGB 面板驱动实现并填入 ``hook_data``,用户无需自行编写,只需在异步操作完成时调用它,以通知驱动结束本次绘制事务;若已注册颜色传输完成回调,也会在此时被调用。 + +面板驱动本身不会等待上一笔绘制结束,同步由自定义钩子自行负责。最简单的做法是串行执行(上一笔完成前不启动下一笔),如下面示例所示。若希望利用 PPA 等加速器的事务队列并发提交多笔绘制,则需要为每笔事务单独保存 ``hook_data``、保证源 buffer 在硬件完成前有效,并处理好目标区域重叠等问题。 + +下面是一个异步自定义钩子的示意代码:钩子启动 PPA 后立即返回,并在 PPA 完成回调中调用 ``on_hook_end``。示例采用串行方式,用信号量保证同一时间只有一笔绘制在进行: + +.. code-block:: c + + typedef struct { + esp_lcd_panel_handle_t panel; + esp_lcd_draw_bitmap_hook_data_t hook_data; + SemaphoreHandle_t draw_sem; + // ... 其他字段,例如 ppa_client_handle_t + } draw_bitmap_hook_ctx_t; + + static bool ppa_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + bool need_yield = false; + + // on_hook_end 由 RGB 面板驱动提供,操作完成后直接调用即可 + if (ctx->hook_data.on_hook_end) { + if (ctx->hook_data.on_hook_end(ctx->panel)) { + need_yield = true; + } + } + + BaseType_t task_woken = pdFALSE; + xSemaphoreGiveFromISR(ctx->draw_sem, &task_woken); + if (task_woken == pdTRUE) { + need_yield = true; + } + return need_yield; + } + + static esp_err_t custom_draw_bitmap_hook(esp_lcd_panel_handle_t panel, + const esp_lcd_draw_bitmap_hook_data_t *hook_data, + void *user_ctx) + { + draw_bitmap_hook_ctx_t *ctx = (draw_bitmap_hook_ctx_t *)user_ctx; + + // 最简单的同步方式:等待上一笔完成后再启动本笔 + xSemaphoreTake(ctx->draw_sem, portMAX_DELAY); + + // 保存 hook_data,供完成回调稍后调用 on_hook_end + ctx->hook_data = *hook_data; + + // 启动异步 PPA 传输后立即返回 + // ppa_do_scale_rotate_mirror(...); + return ESP_OK; + } + API 参考 -------- diff --git a/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild b/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild index 5dc913d6da3..43eb3defd87 100644 --- a/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild +++ b/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild @@ -25,6 +25,14 @@ menu "Example Configuration" Allocate one draw buffer in LVGL. endchoice + config EXAMPLE_USE_DMA2D_COPY_FRAME + bool "Use DMA2D to copy draw buffer to frame buffer" + default y + depends on EXAMPLE_USE_SINGLE_FB && SOC_DMA2D_SUPPORTED + help + Enable this option, DMA2D will be used to copy the LVGL draw buffer to the target frame buffer. + This can save some CPU time and improve the performance. + choice EXAMPLE_LCD_DATA_LINES prompt "RGB LCD Data Lines" default EXAMPLE_LCD_DATA_LINES_16 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 4d31f9f5edb..deb66298611 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 @@ -271,6 +271,12 @@ void app_main(void) }; ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); +#if CONFIG_EXAMPLE_USE_DMA2D_COPY_FRAME + // use DMA2D to copy draw buffer to frame buffer + ESP_LOGI(TAG, "RGB panel added DMA2D draw bitmap hook"); + ESP_ERROR_CHECK(esp_lcd_rgb_panel_enable_dma2d(panel_handle)); +#endif + ESP_LOGI(TAG, "Initialize RGB LCD panel"); ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); From 7706587bb21a22461e947abb6bb1c20568ddfb1f Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Thu, 18 Jun 2026 11:11:37 +0800 Subject: [PATCH 2/5] fix(dma2d): improve async color convert performance --- .../src/async_color_convert_dma2d.c | 41 ++++++++++++++++--- components/esp_lcd/rgb/esp_lcd_panel_rgb.c | 4 +- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/components/esp_driver_dma/src/async_color_convert_dma2d.c b/components/esp_driver_dma/src/async_color_convert_dma2d.c index 976a962401e..5bb403c6ff1 100644 --- a/components/esp_driver_dma/src/async_color_convert_dma2d.c +++ b/components/esp_driver_dma/src/async_color_convert_dma2d.c @@ -164,9 +164,24 @@ static esp_err_t sync_if_cacheable(void *addr, size_t size, int flags) return esp_cache_get_line_size_by_addr(addr) > 0 ? esp_cache_msync(addr, size, flags) : ESP_OK; } -static size_t get_picture_size_bytes(uint32_t stride, uint32_t height, uint32_t bit_depth) +static void get_picture_window_bytes(uint32_t stride, + uint32_t x, + uint32_t y, + uint32_t window_width, + uint32_t window_height, + uint32_t bit_depth, + size_t *out_offset, + size_t *out_size) { - return (((size_t)stride * height * bit_depth) + 7) / 8; + size_t start_bit = ((size_t)y * stride + x) * bit_depth; + size_t end_bit = (((size_t)(y + window_height - 1) * stride + x + window_width) * bit_depth); + // Floor-divide start so the range begins at the first byte that contains start_bit. + // Ceil-divide end ((end_bit + 7) / 8) so any partial trailing byte is included. + size_t start_byte = start_bit / 8; + size_t end_byte = (end_bit + 7) / 8; + + *out_offset = start_byte; + *out_size = end_byte - start_byte; } static esp_err_t validate_request(const async_color_convert_request_t *request) @@ -371,14 +386,28 @@ static esp_err_t async_color_convert_dma2d_convert(async_color_convert_context_t uint32_t src_bpp = color_hal_pixel_format_fourcc_get_bit_depth(src_fourcc); uint32_t dst_bpp = color_hal_pixel_format_fourcc_get_bit_depth(dst_fourcc); - size_t src_total_size = get_picture_size_bytes(request->src_stride, request->src_height, src_bpp); - size_t dst_total_size = get_picture_size_bytes(request->dst_stride, request->dst_height, dst_bpp); + size_t src_window_offset = 0; + size_t src_window_size = 0; + get_picture_window_bytes(request->src_stride, request->src_x, request->src_y, + request->copy_width, request->copy_height, src_bpp, + &src_window_offset, &src_window_size); - ESP_GOTO_ON_ERROR(sync_if_cacheable((void *)request->src_buffer, src_total_size, + size_t dst_window_offset = 0; + size_t dst_window_size = 0; + get_picture_window_bytes(request->dst_stride, request->dst_x, request->dst_y, + request->copy_width, request->copy_height, dst_bpp, + &dst_window_offset, &dst_window_size); + + uint8_t *src_window_addr = (uint8_t *)request->src_buffer + src_window_offset; + uint8_t *dst_window_addr = (uint8_t *)request->dst_buffer + dst_window_offset; + + ESP_GOTO_ON_ERROR(sync_if_cacheable(src_window_addr, src_window_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED), recycle_and_out, TAG, "source cache sync failed"); - ESP_GOTO_ON_ERROR(sync_if_cacheable(request->dst_buffer, dst_total_size, + // UNALIGNED is safe here because C2M writes back partial cache lines before invalidating them + // Callers must not access the destination buffer until the async operation completes. + ESP_GOTO_ON_ERROR(sync_if_cacheable(dst_window_addr, dst_window_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE | ESP_CACHE_MSYNC_FLAG_UNALIGNED), recycle_and_out, TAG, "destination cache sync failed"); diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index 58684f12149..283a6d62c56 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -1484,7 +1484,9 @@ esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel) // Initialize the async color convert backend used by the built-in DMA2D copy hook. // Use its default backlog to queue multiple frame buffer copy requests. - async_color_convert_config_t fbcpy_config = {}; + async_color_convert_config_t fbcpy_config = { + .dma_burst_size = 128, // for better performance + }; ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &rgb_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); // Register the DMA2D draw bitmap hook From 7a02ede3dcd35618e69f24ae31f17d4c4d4d6c8a Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Thu, 13 Aug 2026 11:15:23 +0800 Subject: [PATCH 3/5] refactor(lcd): add async dma2d copy wrapper --- components/esp_driver_dma/CMakeLists.txt | 3 +- .../include/esp_private/async_memcpy_dma2d.h | 150 ++++++++++++++++++ .../esp_driver_dma/src/async_memcpy_dma2d.c | 67 ++++++++ components/esp_lcd/dsi/esp_lcd_panel_dpi.c | 41 ++--- components/esp_lcd/rgb/esp_lcd_panel_rgb.c | 41 ++--- 5 files changed, 253 insertions(+), 49 deletions(-) create mode 100644 components/esp_driver_dma/include/esp_private/async_memcpy_dma2d.h create mode 100644 components/esp_driver_dma/src/async_memcpy_dma2d.c diff --git a/components/esp_driver_dma/CMakeLists.txt b/components/esp_driver_dma/CMakeLists.txt index 3d520c3e9f4..46a442371ba 100644 --- a/components/esp_driver_dma/CMakeLists.txt +++ b/components/esp_driver_dma/CMakeLists.txt @@ -35,7 +35,8 @@ if(CONFIG_SOC_DW_GDMA_SUPPORTED) endif() if(CONFIG_SOC_DMA2D_SUPPORTED) - list(APPEND srcs "src/dma2d.c" "src/esp_async_color_convert.c" "src/async_color_convert_dma2d.c") + list(APPEND srcs "src/dma2d.c" "src/esp_async_color_convert.c" "src/async_color_convert_dma2d.c" + "src/async_memcpy_dma2d.c") endif() idf_component_register(SRCS ${srcs} diff --git a/components/esp_driver_dma/include/esp_private/async_memcpy_dma2d.h b/components/esp_driver_dma/include/esp_private/async_memcpy_dma2d.h new file mode 100644 index 00000000000..03e024f2651 --- /dev/null +++ b/components/esp_driver_dma/include/esp_private/async_memcpy_dma2d.h @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +// DO NOT USE THESE APIS IN ANY APPLICATIONS +// DMA2D async 2D memcpy is a private helper built on async color convert +// for same-format window copy (e.g. LCD frame buffer blit). + +#pragma once + +#include +#include +#include +#include "esp_err.h" +#include "hal/color_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Opaque handle of DMA2D async 2D memcpy driver instance + * + * @note Internally this is a thin wrapper over async color convert with + * source/destination formats forced to the same value (copy-only path). + */ +typedef struct async_memcpy_dma2d_t *async_memcpy_dma2d_handle_t; + +/** + * @brief DMA2D async 2D memcpy event data + */ +typedef struct { +} async_memcpy_dma2d_event_data_t; + +/** + * @brief DMA2D async 2D memcpy ISR callback + * + * @note This callback runs in ISR context. + * + * @param[in] mcp Driver handle that produced this event + * @param[in] edata Event data for the completed request + * @param[in] cb_args User context passed to :cpp:func:`esp_async_memcpy_dma2d` + * + * @return + * - true: a higher-priority task was woken and a yield is requested + * - false: no yield request + */ +typedef bool (*async_memcpy_dma2d_isr_cb_t)(async_memcpy_dma2d_handle_t mcp, + async_memcpy_dma2d_event_data_t *edata, + void *cb_args); + +/** + * @brief DMA2D async 2D memcpy driver configuration + */ +typedef struct { + uint32_t backlog; /*!< Number of in-flight/pending requests. 0 means driver default. */ + size_t dma_burst_size; /*!< DMA burst length in bytes. 0 means driver default. */ + uint32_t intr_priority; /*!< Interrupt priority. 0 means default low/medium priority. */ +} async_memcpy_dma2d_config_t; + +/** + * @brief DMA2D async 2D memcpy transaction descriptor + * + * Coordinates and size are in pixels. + * + * The source and destination windows are: + * - source: [src_x, src_x + copy_width) x [src_y, src_y + copy_height) + * - destination: [dst_x, dst_x + copy_width) x [dst_y, dst_y + copy_height) + * + * Both windows must be fully inside their corresponding image bounds. + * + * This API only performs same-format 2D window copy (no color conversion). + */ +typedef struct { + const void *src_buffer; /*!< Source picture base address */ + uint32_t src_stride; /*!< Source picture row stride in pixels */ + uint32_t src_height; /*!< Source picture height in pixels */ + uint32_t src_x; /*!< Source window x offset in pixels */ + uint32_t src_y; /*!< Source window y offset in pixels */ + + void *dst_buffer; /*!< Destination picture base address */ + uint32_t dst_stride; /*!< Destination picture row stride in pixels */ + uint32_t dst_height; /*!< Destination picture height in pixels */ + uint32_t dst_x; /*!< Destination window x offset in pixels */ + uint32_t dst_y; /*!< Destination window y offset in pixels */ + + uint32_t copy_width; /*!< Copy window width in pixels */ + uint32_t copy_height; /*!< Copy window height in pixels */ + + esp_color_fourcc_t pixel_format; /*!< Pixel format of both source and destination */ +} async_memcpy_dma2d_trans_desc_t; + +/** + * @brief Install DMA2D async 2D memcpy driver + * + * This is a thin wrapper over :cpp:func:`esp_async_color_convert_install_dma2d`. + * 2D window copy is implemented as a same-format color-convert request. + * + * @param[in] config Driver configuration + * @param[out] ret_hdl Returned driver handle + * + * @return + * - ESP_OK: Driver installed successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_NO_MEM: Out of memory + * - ESP_ERR_NOT_FOUND: Required DMA2D resource is unavailable + * - others: Error from lower-level driver + */ +esp_err_t esp_async_memcpy_install_dma2d(const async_memcpy_dma2d_config_t *config, + async_memcpy_dma2d_handle_t *ret_hdl); + +/** + * @brief Uninstall DMA2D async 2D memcpy driver + * + * @param[in] mcp Driver handle returned by :cpp:func:`esp_async_memcpy_install_dma2d` + * + * @return + * - ESP_OK: Driver uninstalled successfully + * - ESP_ERR_INVALID_ARG: Invalid argument + * - ESP_ERR_INVALID_STATE: There are pending requests in the queue + */ +esp_err_t esp_async_memcpy_uninstall_dma2d(async_memcpy_dma2d_handle_t mcp); + +/** + * @brief Submit an asynchronous 2D memory copy request via DMA2D + * + * The request is enqueued and completed later in DMA2D interrupt context. + * The callback can be NULL if no completion notification is needed. + * + * @param[in] mcp Driver handle returned by :cpp:func:`esp_async_memcpy_install_dma2d` + * @param[in] trans 2D memcpy transaction descriptor + * @param[in] cb_isr ISR callback invoked on copy completion, can be NULL + * @param[in] cb_args User context passed to @p cb_isr + * + * @return + * - ESP_OK: Request accepted + * - ESP_ERR_INVALID_ARG: Invalid argument or invalid request fields + * - ESP_ERR_INVALID_STATE: No free internal transaction slot (queue full) + * - others: Error from lower-level driver + */ +esp_err_t esp_async_memcpy_dma2d(async_memcpy_dma2d_handle_t mcp, + const async_memcpy_dma2d_trans_desc_t *trans, + async_memcpy_dma2d_isr_cb_t cb_isr, + void *cb_args); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_driver_dma/src/async_memcpy_dma2d.c b/components/esp_driver_dma/src/async_memcpy_dma2d.c new file mode 100644 index 00000000000..da24d8087da --- /dev/null +++ b/components/esp_driver_dma/src/async_memcpy_dma2d.c @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "esp_async_color_convert.h" +#include "esp_private/async_memcpy_dma2d.h" + +ESP_LOG_ATTR_TAG(TAG, "async_memcpy_dma2d"); + +/* + * DMA2D 2D window copy is a simplified same-format color-convert request. + * Keep this file as a thin naming/API wrapper so LCD and other drivers can + * express "2D memcpy" without calling color-convert APIs directly. + */ + +esp_err_t esp_async_memcpy_install_dma2d(const async_memcpy_dma2d_config_t *config, + async_memcpy_dma2d_handle_t *ret_hdl) +{ + ESP_RETURN_ON_FALSE(config && ret_hdl, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + async_color_convert_config_t conv_config = { + .backlog = config->backlog, + .dma_burst_size = config->dma_burst_size, + .intr_priority = config->intr_priority, + }; + return esp_async_color_convert_install_dma2d(&conv_config, (async_color_convert_handle_t *)ret_hdl); +} + +esp_err_t esp_async_memcpy_uninstall_dma2d(async_memcpy_dma2d_handle_t mcp) +{ + return esp_async_color_convert_uninstall((async_color_convert_handle_t)mcp); +} + +esp_err_t esp_async_memcpy_dma2d(async_memcpy_dma2d_handle_t mcp, + const async_memcpy_dma2d_trans_desc_t *trans, + async_memcpy_dma2d_isr_cb_t cb_isr, + void *cb_args) +{ + ESP_RETURN_ON_FALSE(mcp && trans, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + + async_color_convert_request_t request = { + .src_buffer = trans->src_buffer, + .dst_buffer = trans->dst_buffer, + .src_stride = trans->src_stride, + .src_height = trans->src_height, + .dst_stride = trans->dst_stride, + .dst_height = trans->dst_height, + .src_x = trans->src_x, + .src_y = trans->src_y, + .dst_x = trans->dst_x, + .dst_y = trans->dst_y, + .copy_width = trans->copy_width, + .copy_height = trans->copy_height, + // Same source/destination format disables CSC and performs 2D window copy only. + .src_color_format = trans->pixel_format, + .dst_color_format = trans->pixel_format, + }; + + // Callback prototypes are ABI-compatible: opaque handle pointer + empty event struct + user args. + return esp_async_color_convert((async_color_convert_handle_t)mcp, + &request, + (async_color_convert_isr_cb_t)cb_isr, + cb_args); +} diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index a9ba69dee4b..b9e901fca38 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -6,12 +6,12 @@ #include #include "esp_lcd_panel_interface.h" #include "esp_lcd_mipi_dsi.h" -#include "esp_async_color_convert.h" #include "esp_intr_alloc.h" #include "esp_clk_tree.h" #include "esp_cache.h" #include "mipi_dsi_priv.h" #include "esp_memory_utils.h" +#include "esp_private/async_memcpy_dma2d.h" #include "esp_private/dw_gdma.h" #include "hal/color_hal.h" @@ -44,7 +44,7 @@ struct esp_lcd_dpi_panel_t { esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function void* hook_ctx; // Hook context bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation - async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy + async_memcpy_dma2d_handle_t fbcpy_handle; // DMA2D async 2D memcpy handle used for same-format frame buffer copy #if CONFIG_PM_ENABLE esp_pm_lock_handle_t pm_lock; // Power management lock @@ -64,11 +64,11 @@ static bool dpi_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel) return false; } -static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args) +static bool async_fbcpy_done_cb(async_memcpy_dma2d_handle_t mcp, async_memcpy_dma2d_event_data_t *event, void *cb_args) { bool need_yield = false; esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)cb_args; - (void)conv_hdl; + (void)mcp; (void)event; if (dpi_panel->on_hook_end) { @@ -470,7 +470,9 @@ static esp_err_t dpi_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base); (void)hook_ctx; - async_color_convert_request_t fbcpy_trans_config = { + // Built-in DMA2D draw hook only needs same-format 2D window copy. + // Use the private DMA2D async memcpy wrapper (thin layer over color convert). + async_memcpy_dma2d_trans_desc_t fbcpy_trans_config = { .src_buffer = hook_data->src_data, .dst_buffer = hook_data->dst_data, .src_stride = hook_data->src_x_size, @@ -483,16 +485,13 @@ static esp_err_t dpi_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const .dst_y = hook_data->dst_y_start, .copy_width = hook_data->src_x_end - hook_data->src_x_start, .copy_height = hook_data->src_y_end - hook_data->src_y_start, - // For this DMA2D hook we only do window copy from draw buffer to frame buffer. - // Source and destination color formats are intentionally set to the same value to disable CSC. - .src_color_format = dpi_panel->in_color_format, - .dst_color_format = dpi_panel->in_color_format, + .pixel_format = dpi_panel->in_color_format, }; - // The async color convert backend owns source/destination cache sync for the - // DMA2D copy path, so the LCD driver should not perform extra cache sync here. + // The DMA2D async 2D memcpy backend owns source/destination cache sync for the + // copy path, so the LCD driver should not perform extra cache sync here. // Save the completion callback and invoke it when the async frame buffer copy finishes. dpi_panel->on_hook_end = hook_data->on_hook_end; - ESP_RETURN_ON_ERROR(esp_async_color_convert(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async frame buffer copy failed"); + ESP_RETURN_ON_ERROR(esp_async_memcpy_dma2d(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async frame buffer copy failed"); return ESP_OK; } @@ -517,12 +516,12 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel) // Check if built-in DMA2D draw bitmap hook is registered ESP_RETURN_ON_FALSE(!dpi_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook is already registered"); - // Initialize the async color convert backend used by the built-in DMA2D copy hook. + // Initialize the DMA2D async 2D memcpy backend used by the built-in copy hook. // Use its default backlog to queue multiple frame buffer copy requests. - async_color_convert_config_t fbcpy_config = { + async_memcpy_dma2d_config_t fbcpy_config = { .dma_burst_size = 128, // for better performance }; - ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &dpi_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); + ESP_RETURN_ON_ERROR(esp_async_memcpy_install_dma2d(&fbcpy_config, &dpi_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); // Register the DMA2D draw bitmap hook esp_lcd_panel_hooks_t hooks = { @@ -534,7 +533,7 @@ esp_err_t esp_lcd_dpi_panel_enable_dma2d(esp_lcd_panel_handle_t panel) err: if (dpi_panel->fbcpy_handle) { - esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle); + esp_async_memcpy_uninstall_dma2d(dpi_panel->fbcpy_handle); dpi_panel->fbcpy_handle = NULL; } dpi_panel->on_hook_end = NULL; @@ -554,13 +553,7 @@ esp_err_t esp_lcd_dpi_panel_disable_dma2d(esp_lcd_panel_handle_t panel) .draw_bitmap_hook = NULL, }; ESP_RETURN_ON_ERROR(esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed"); - esp_err_t ret = esp_async_color_convert_uninstall(dpi_panel->fbcpy_handle); - if (ret != ESP_OK) { - // Restore the hook so draw_bitmap_hook and fbcpy_handle stay consistent on failure - hooks.draw_bitmap_hook = dpi_panel_draw_bitmap_dma2d_hook; - esp_lcd_dpi_panel_register_hooks(panel, &hooks, NULL); - ESP_RETURN_ON_ERROR(ret, TAG, "uninstall DMA2D failed"); - } + ESP_RETURN_ON_ERROR(esp_async_memcpy_uninstall_dma2d(dpi_panel->fbcpy_handle), TAG, "uninstall DMA2D failed"); dpi_panel->fbcpy_handle = NULL; dpi_panel->on_hook_end = NULL; @@ -651,7 +644,7 @@ static esp_err_t dpi_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, i ESP_LOGV(TAG, "copy draw buffer by draw bitmap hook"); // Note, whether the previous draw operation is finished should be ensured by the hook. // For the built-in DMA2D hook, cache maintenance of the source and destination - // buffers is handled inside the async color convert driver. + // buffers is handled inside the DMA2D async 2D memcpy driver. esp_lcd_draw_bitmap_hook_data_t hook_data = { .dst_data = frame_buffer, diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index 283a6d62c56..cfe03db34be 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -49,7 +49,7 @@ #include "hal/color_hal.h" #include "rgb_lcd_rotation_sw.h" #include "esp_private/sleep_retention.h" -#include "esp_async_color_convert.h" +#include "esp_private/async_memcpy_dma2d.h" #if SOC_HAS(AXI_GDMA) #include "hal/axi_dma_ll.h" @@ -174,7 +174,7 @@ struct esp_rgb_panel_t { esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function void* hook_ctx; // Hook context bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation - async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy + async_memcpy_dma2d_handle_t fbcpy_handle; // DMA2D async 2D memcpy handle used for same-format frame buffer copy }; static esp_err_t lcd_rgb_panel_alloc_frame_buffers(esp_rgb_panel_t *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config) @@ -708,11 +708,11 @@ static bool rgb_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel) } #if SOC_HAS(DMA2D) -static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args) +static bool async_fbcpy_done_cb(async_memcpy_dma2d_handle_t mcp, async_memcpy_dma2d_event_data_t *event, void *cb_args) { bool need_yield = false; esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)cb_args; - (void)conv_hdl; + (void)mcp; (void)event; if (rgb_panel->on_hook_end) { @@ -800,7 +800,7 @@ static esp_err_t rgb_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, i ESP_LOGV(TAG, "copy draw buffer by draw bitmap hook"); // Note, whether the previous draw operation is finished should be ensured by the hook. // For the built-in DMA2D hook, cache maintenance of the source and destination - // buffers is handled inside the async color convert driver. + // buffers is handled inside the DMA2D async 2D memcpy driver. esp_lcd_draw_bitmap_hook_data_t hook_data = { .dst_data = frame_buffer, @@ -1447,7 +1447,9 @@ static esp_err_t rgb_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); (void)hook_ctx; - async_color_convert_request_t fbcpy_trans_config = { + // Built-in DMA2D draw hook only needs same-format 2D window copy. + // Use the private DMA2D async memcpy wrapper (thin layer over color convert). + async_memcpy_dma2d_trans_desc_t fbcpy_trans_config = { .src_buffer = hook_data->src_data, .dst_buffer = hook_data->dst_data, .src_stride = hook_data->src_x_size, @@ -1460,16 +1462,13 @@ static esp_err_t rgb_panel_draw_bitmap_dma2d_hook(esp_lcd_panel_t *panel, const .dst_y = hook_data->dst_y_start, .copy_width = hook_data->src_x_end - hook_data->src_x_start, .copy_height = hook_data->src_y_end - hook_data->src_y_start, - // For this DMA2D hook we only do window copy from draw buffer to frame buffer. - // Source and destination color formats are intentionally set to the same value to disable CSC. - .src_color_format = rgb_panel->in_color_format, - .dst_color_format = rgb_panel->in_color_format, + .pixel_format = rgb_panel->in_color_format, }; - // The async color convert backend owns source/destination cache sync for the - // DMA2D copy path, so the LCD driver should not perform extra cache sync here. + // The DMA2D async 2D memcpy backend owns source/destination cache sync for the + // copy path, so the LCD driver should not perform extra cache sync here. // Save the completion callback and invoke it when the async frame buffer copy finishes. rgb_panel->on_hook_end = hook_data->on_hook_end; - ESP_RETURN_ON_ERROR(esp_async_color_convert(rgb_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, rgb_panel), TAG, "async frame buffer copy failed"); + ESP_RETURN_ON_ERROR(esp_async_memcpy_dma2d(rgb_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, rgb_panel), TAG, "async frame buffer copy failed"); return ESP_OK; } @@ -1482,12 +1481,12 @@ esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel) // Check if built-in DMA2D draw bitmap hook is registered ESP_RETURN_ON_FALSE(!rgb_panel->fbcpy_handle, ESP_ERR_INVALID_STATE, TAG, "draw bitmap DMA2D hook is already registered"); - // Initialize the async color convert backend used by the built-in DMA2D copy hook. + // Initialize the DMA2D async 2D memcpy backend used by the built-in copy hook. // Use its default backlog to queue multiple frame buffer copy requests. - async_color_convert_config_t fbcpy_config = { + async_memcpy_dma2d_config_t fbcpy_config = { .dma_burst_size = 128, // for better performance }; - ESP_RETURN_ON_ERROR(esp_async_color_convert_install_dma2d(&fbcpy_config, &rgb_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); + ESP_RETURN_ON_ERROR(esp_async_memcpy_install_dma2d(&fbcpy_config, &rgb_panel->fbcpy_handle), TAG, "install async frame buffer copy backend failed"); // Register the DMA2D draw bitmap hook esp_lcd_panel_hooks_t hooks = { @@ -1499,7 +1498,7 @@ esp_err_t esp_lcd_rgb_panel_enable_dma2d(esp_lcd_panel_handle_t panel) err: if (rgb_panel->fbcpy_handle) { - esp_async_color_convert_uninstall(rgb_panel->fbcpy_handle); + esp_async_memcpy_uninstall_dma2d(rgb_panel->fbcpy_handle); rgb_panel->fbcpy_handle = NULL; } rgb_panel->on_hook_end = NULL; @@ -1519,13 +1518,7 @@ esp_err_t esp_lcd_rgb_panel_disable_dma2d(esp_lcd_panel_handle_t panel) .draw_bitmap_hook = NULL, }; ESP_RETURN_ON_ERROR(esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL), TAG, "unregister DMA2D draw bitmap hook failed"); - esp_err_t ret = esp_async_color_convert_uninstall(rgb_panel->fbcpy_handle); - if (ret != ESP_OK) { - // Restore the hook so draw_bitmap_hook and fbcpy_handle stay consistent on failure - hooks.draw_bitmap_hook = rgb_panel_draw_bitmap_dma2d_hook; - esp_lcd_rgb_panel_register_hooks(panel, &hooks, NULL); - ESP_RETURN_ON_ERROR(ret, TAG, "uninstall DMA2D failed"); - } + ESP_RETURN_ON_ERROR(esp_async_memcpy_uninstall_dma2d(rgb_panel->fbcpy_handle), TAG, "uninstall DMA2D failed"); rgb_panel->fbcpy_handle = NULL; rgb_panel->on_hook_end = NULL; From a081b698dff66549f13707936300033539730f3b Mon Sep 17 00:00:00 2001 From: Chen Jichang Date: Tue, 16 Jun 2026 17:55:55 +0800 Subject: [PATCH 4/5] feat(rgb_lcd): support convert rgb2rgb --- .../esp_hal_lcd/esp32p4/include/hal/lcd_ll.h | 43 +++++++- .../esp_hal_lcd/esp32s3/include/hal/lcd_ll.h | 5 +- components/esp_lcd/rgb/esp_lcd_panel_rgb.c | 25 ++++- .../test_apps/rgb_lcd/main/test_rgb_board.h | 26 +++++ .../test_apps/rgb_lcd/main/test_rgb_panel.c | 104 ++++++++++++++++-- 5 files changed, 182 insertions(+), 21 deletions(-) diff --git a/components/esp_hal_lcd/esp32p4/include/hal/lcd_ll.h b/components/esp_hal_lcd/esp32p4/include/hal/lcd_ll.h index 0956173d2f8..913377e24bc 100644 --- a/components/esp_hal_lcd/esp32p4/include/hal/lcd_ll.h +++ b/components/esp_hal_lcd/esp32p4/include/hal/lcd_ll.h @@ -17,10 +17,14 @@ #include "soc/hp_sys_clkrst_struct.h" #define LCD_LL_GET(_attr) LCD_LL_ ## _attr +#define LCD_LL_SUPPORT(_feat) LCD_LL_SUPPORT_ ## _feat #define LCD_LL_RGB_BUS_WIDTH 24 #define LCD_LL_RGB_PANEL_NUM 1 #define LCD_LL_I80_BUS_WIDTH 24 #define LCD_LL_I80_BUS_NUM 1 +#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300 +#define LCD_LL_SUPPORT_RGB2RGB_CONV 1 +#endif #ifdef __cplusplus extern "C" { @@ -230,14 +234,13 @@ static inline void lcd_ll_enable_color_convert(lcd_cam_dev_t *dev, bool en) } /** - * @brief Set convert data line width + * @brief Set convert input data line width for YUV<->RGB conversion * * @param dev LCD register base address * @param width data line width */ -static inline void lcd_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width) +static inline void lcd_ll_set_yuv_convert_input_data_width(lcd_cam_dev_t *dev, uint32_t width) { - HAL_ASSERT(width == 8 || width == 16); dev->lcd_rgb_yuv.lcd_conv_mode_8bits_on = (width == 8) ? 1 : 0; } @@ -286,6 +289,28 @@ static inline void lcd_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, lcd_yuv_conv_s } } +#if LCD_LL_SUPPORT(RGB2RGB_CONV) +/** + * @brief Set the converter mode: RGB to RGB + * + * @param dev LCD register base address + * @param in_color_format Input color format + * @param out_color_format Output color format + */ +static inline void lcd_ll_set_rgb2rgb_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format) +{ + dev->lcd_rgb_yuv.lcd_conv_trans_mode = 0; + dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3; + if (in_color_format == LCD_COLOR_FMT_RGB888 && out_color_format == LCD_COLOR_FMT_RGB565) { + dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 0; + } else if (in_color_format == LCD_COLOR_FMT_RGB565 && out_color_format == LCD_COLOR_FMT_RGB888) { + dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 1; + } else { + abort(); + } +} +#endif // LCD_LL_SUPPORT(RGB2RGB_CONV) + /** * @brief Set the converter mode: RGB to YUV * @@ -295,6 +320,9 @@ static inline void lcd_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, lcd_yuv_conv_s */ static inline void lcd_ll_set_rgb2yuv_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format) { +#if LCD_LL_SUPPORT(RGB2RGB_CONV) + dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 2; +#endif dev->lcd_rgb_yuv.lcd_conv_trans_mode = 1; dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3; switch (out_color_format) { @@ -318,6 +346,9 @@ static inline void lcd_ll_set_rgb2yuv_convert_mode(lcd_cam_dev_t *dev, lcd_color */ static inline void lcd_ll_set_yuv2rgb_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format) { +#if LCD_LL_SUPPORT(RGB2RGB_CONV) + dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 2; +#endif dev->lcd_rgb_yuv.lcd_conv_trans_mode = 0; dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3; switch (in_color_format) { @@ -341,6 +372,9 @@ static inline void lcd_ll_set_yuv2rgb_convert_mode(lcd_cam_dev_t *dev, lcd_color */ static inline void lcd_ll_set_yuv2yuv_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format) { +#if LCD_LL_SUPPORT(RGB2RGB_CONV) + dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 2; +#endif dev->lcd_rgb_yuv.lcd_conv_trans_mode = 1; switch (in_color_format) { case LCD_COLOR_FMT_YUV422_UYVY: @@ -424,9 +458,6 @@ static inline void lcd_ll_set_dma_read_stride(lcd_cam_dev_t *dev, uint32_t strid case 24: dev->lcd_user.lcd_byte_mode = 2; break; - case 32: - dev->lcd_user.lcd_byte_mode = 3; - break; default: abort(); } diff --git a/components/esp_hal_lcd/esp32s3/include/hal/lcd_ll.h b/components/esp_hal_lcd/esp32s3/include/hal/lcd_ll.h index 750f286f984..fc9d44fe177 100644 --- a/components/esp_hal_lcd/esp32s3/include/hal/lcd_ll.h +++ b/components/esp_hal_lcd/esp32s3/include/hal/lcd_ll.h @@ -16,6 +16,7 @@ #include "soc/system_struct.h" #define LCD_LL_GET(_attr) LCD_LL_ ## _attr +#define LCD_LL_SUPPORT(_feat) LCD_LL_SUPPORT_ ## _feat #define LCD_LL_RGB_BUS_WIDTH 16 #define LCD_LL_RGB_PANEL_NUM 1 #define LCD_LL_I80_BUS_WIDTH 16 @@ -201,12 +202,12 @@ static inline void lcd_ll_enable_color_convert(lcd_cam_dev_t *dev, bool en) } /** - * @brief Set convert data line width + * @brief Set convert input data line width for YUV<->RGB conversion * * @param dev LCD register base address * @param width data line width (8 or 16) */ -static inline void lcd_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width) +static inline void lcd_ll_set_yuv_convert_input_data_width(lcd_cam_dev_t *dev, uint32_t width) { HAL_ASSERT(width == 8 || width == 16); dev->lcd_rgb_yuv.lcd_conv_mode_8bits_on = (width == 8) ? 1 : 0; diff --git a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c index cfe03db34be..266b5c17238 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -357,6 +357,12 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf ESP_RETURN_ON_FALSE(in_color_format != 0, ESP_ERR_INVALID_ARG, TAG, "cannot determine input color format"); // if out_color_format is not specified, set it the same as in_color_format lcd_color_format_t out_color_format = rgb_panel_config->out_color_format ? rgb_panel_config->out_color_format : in_color_format; + if ((in_color_format == LCD_COLOR_FMT_RGB565 && out_color_format == LCD_COLOR_FMT_RGB888) || + (in_color_format == LCD_COLOR_FMT_RGB888 && out_color_format == LCD_COLOR_FMT_RGB565)) { +#if !LCD_LL_SUPPORT(RGB2RGB_CONV) + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "RGB to RGB conversion is not supported"); +#endif + } // calculate buffer size size_t fb_bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(in_color_format); @@ -575,8 +581,8 @@ esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, con // set color range lcd_ll_set_input_color_range(hal->dev, config->in_color_range); lcd_ll_set_output_color_range(hal->dev, config->out_color_range); - // set conversion data width - lcd_ll_set_convert_data_width(hal->dev, rgb_panel->data_width); + // set conversion input data width + lcd_ll_set_yuv_convert_input_data_width(hal->dev, rgb_panel->data_width); return ESP_OK; } @@ -630,7 +636,22 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel) lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg); // enable RGB mode and set data width lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true); +#if LCD_LL_SUPPORT(RGB2RGB_CONV) + if ((rgb_panel->in_color_format == LCD_COLOR_FMT_RGB888 && rgb_panel->out_color_format == LCD_COLOR_FMT_RGB565) || \ + (rgb_panel->in_color_format == LCD_COLOR_FMT_RGB565 && rgb_panel->out_color_format == LCD_COLOR_FMT_RGB888)) { + lcd_ll_set_rgb2rgb_convert_mode(rgb_panel->hal.dev, rgb_panel->in_color_format, rgb_panel->out_color_format); + // RGB2RGB converter always uses full color range + lcd_ll_set_input_color_range(rgb_panel->hal.dev, LCD_COLOR_RANGE_FULL); + lcd_ll_set_output_color_range(rgb_panel->hal.dev, LCD_COLOR_RANGE_FULL); + } +#endif + + // ESP32S3 dma stride is equal to the data width +#if CONFIG_IDF_TARGET_ESP32S3 lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->data_width); +#else + lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->fb_bits_per_pixel); +#endif // enable conversion if the input color format is different from the output color format lcd_ll_enable_color_convert(rgb_panel->hal.dev, rgb_panel->in_color_format != rgb_panel->out_color_format); // enable data phase only diff --git a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h index 3e65925a153..bbd412f7881 100644 --- a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h +++ b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_board.h @@ -68,6 +68,32 @@ extern "C" { #define TEST_LCD_DATA15_GPIO 19 // R4 #define TEST_LCD_DISP_EN_GPIO -1 +// GPIO definitions for 24-bit RGB888 interface (used by RGB565 to RGB888 conversion test) +#define TEST_LCD_24BIT_DATA0_GPIO 34 +#define TEST_LCD_24BIT_DATA1_GPIO 12 +#define TEST_LCD_24BIT_DATA2_GPIO 10 +#define TEST_LCD_24BIT_DATA3_GPIO 40 +#define TEST_LCD_24BIT_DATA4_GPIO 42 +#define TEST_LCD_24BIT_DATA5_GPIO 27 +#define TEST_LCD_24BIT_DATA6_GPIO 29 +#define TEST_LCD_24BIT_DATA7_GPIO 31 +#define TEST_LCD_24BIT_DATA8_GPIO 16 +#define TEST_LCD_24BIT_DATA9_GPIO 14 +#define TEST_LCD_24BIT_DATA10_GPIO 21 +#define TEST_LCD_24BIT_DATA11_GPIO 23 +#define TEST_LCD_24BIT_DATA12_GPIO 26 +#define TEST_LCD_24BIT_DATA13_GPIO 28 +#define TEST_LCD_24BIT_DATA14_GPIO 30 +#define TEST_LCD_24BIT_DATA15_GPIO 32 +#define TEST_LCD_24BIT_DATA16_GPIO 22 +#define TEST_LCD_24BIT_DATA17_GPIO 20 +#define TEST_LCD_24BIT_DATA18_GPIO 18 +#define TEST_LCD_24BIT_DATA19_GPIO 6 +#define TEST_LCD_24BIT_DATA20_GPIO 0 +#define TEST_LCD_24BIT_DATA21_GPIO 15 +#define TEST_LCD_24BIT_DATA22_GPIO 17 +#define TEST_LCD_24BIT_DATA23_GPIO 19 + #else #error "Unsupported target" #endif diff --git a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c index 3b836a5fe6c..f0a4fbc99d3 100644 --- a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c +++ b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c @@ -15,6 +15,7 @@ #include "esp_random.h" #include "esp_timer.h" #include "esp_attr.h" +#include "hal/lcd_ll.h" #include "test_rgb_board.h" #include "esp_private/spi_flash_os.h" #include "esp_clk_tree.h" @@ -31,13 +32,15 @@ #define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) -static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, lcd_color_format_t in_color_format, size_t bb_pixels, lcd_clock_source_t clk_src, bool refresh_on_demand, bool user_fb, +static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format, + size_t bb_pixels, lcd_clock_source_t clk_src, bool refresh_on_demand, bool user_fb, esp_lcd_rgb_panel_vsync_cb_t vsync_cb, void *user_data) { esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_rgb_panel_config_t panel_config = { .data_width = data_width, .in_color_format = in_color_format, + .out_color_format = out_color_format, .dma_burst_size = 64, .bounce_buffer_size_px = bb_pixels, .clk_src = clk_src, @@ -85,6 +88,38 @@ static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, l panel_config.user_fbs[0] = frame_buffer; } +#if LCD_LL_SUPPORT(RGB2RGB_CONV) + if (data_width == 24) { + const int data_gpios[24] = { + TEST_LCD_24BIT_DATA0_GPIO, + TEST_LCD_24BIT_DATA1_GPIO, + TEST_LCD_24BIT_DATA2_GPIO, + TEST_LCD_24BIT_DATA3_GPIO, + TEST_LCD_24BIT_DATA4_GPIO, + TEST_LCD_24BIT_DATA5_GPIO, + TEST_LCD_24BIT_DATA6_GPIO, + TEST_LCD_24BIT_DATA7_GPIO, + TEST_LCD_24BIT_DATA8_GPIO, + TEST_LCD_24BIT_DATA9_GPIO, + TEST_LCD_24BIT_DATA10_GPIO, + TEST_LCD_24BIT_DATA11_GPIO, + TEST_LCD_24BIT_DATA12_GPIO, + TEST_LCD_24BIT_DATA13_GPIO, + TEST_LCD_24BIT_DATA14_GPIO, + TEST_LCD_24BIT_DATA15_GPIO, + TEST_LCD_24BIT_DATA16_GPIO, + TEST_LCD_24BIT_DATA17_GPIO, + TEST_LCD_24BIT_DATA18_GPIO, + TEST_LCD_24BIT_DATA19_GPIO, + TEST_LCD_24BIT_DATA20_GPIO, + TEST_LCD_24BIT_DATA21_GPIO, + TEST_LCD_24BIT_DATA22_GPIO, + TEST_LCD_24BIT_DATA23_GPIO, + }; + memcpy(panel_config.data_gpio_nums, data_gpios, sizeof(data_gpios)); + } +#endif + TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); esp_lcd_rgb_panel_event_callbacks_t cbs = { @@ -103,7 +138,7 @@ TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]") TEST_ASSERT_NOT_NULL(img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); printf("flush random color block\r\n"); for (int i = 0; i < 200; i++) { uint8_t color_byte = esp_random() & 0xFF; @@ -125,7 +160,7 @@ TEST_CASE("lcd_rgb_panel_8bit_interface", "[lcd]") printf("initialize RGB panel with stream mode\r\n"); // bpp for RGB888 is 24 - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(8, LCD_COLOR_FMT_RGB888, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(8, LCD_COLOR_FMT_RGB888, LCD_COLOR_FMT_RGB888, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); uint8_t color_byte = esp_random() & 0xFF; printf("flush random color block 0x%x\r\n", color_byte); int x_start = esp_random() % (TEST_LCD_H_RES - 100); @@ -138,6 +173,53 @@ TEST_CASE("lcd_rgb_panel_8bit_interface", "[lcd]") free(img); } +#if LCD_LL_SUPPORT(RGB2RGB_CONV) +TEST_CASE("lcd_rgb_panel_rgb2rgb_conversion", "[lcd]") +{ + uint8_t *img = malloc(100 * 100 * 3); + TEST_ASSERT_NOT_NULL(img); + + printf("initialize RGB panel with RGB888 frame buffer and RGB565 output\r\n"); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB888, LCD_COLOR_FMT_RGB565, + 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + printf("flush random color block\r\n"); + for (int i = 0; i < 200; i++) { + uint8_t red = esp_random() & 0xFF; + uint8_t green = esp_random() & 0xFF; + uint8_t blue = esp_random() & 0xFF; + int x_start = esp_random() % (TEST_LCD_H_RES - 100); + int y_start = esp_random() % (TEST_LCD_V_RES - 100); + for (int j = 0; j < 100 * 100; j++) { + img[j * 3 + 0] = blue; + img[j * 3 + 1] = green; + img[j * 3 + 2] = red; + } + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + vTaskDelay(pdMS_TO_TICKS(10)); + } + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + + vTaskDelay(pdMS_TO_TICKS(500)); + + printf("initialize RGB panel with RGB565 frame buffer and RGB888 output\r\n"); + panel_handle = test_rgb_panel_initialization(24, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB888, + 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + printf("flush random color block\r\n"); + for (int i = 0; i < 200; i++) { + uint8_t color_byte = esp_random() & 0xFF; + int x_start = esp_random() % (TEST_LCD_H_RES - 100); + int y_start = esp_random() % (TEST_LCD_V_RES - 100); + memset(img, color_byte, TEST_IMG_SIZE); + esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); + vTaskDelay(pdMS_TO_TICKS(10)); + } + printf("delete RGB panel\r\n"); + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + free(img); +} +#endif + TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx) { TaskHandle_t task_to_notify = (TaskHandle_t)user_ctx; @@ -153,7 +235,7 @@ TEST_CASE("lcd_rgb_panel_refresh_on_demand", "[lcd]") TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); printf("initialize RGB panel with non-stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, true, false, test_rgb_panel_trans_done, cur_task); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, true, false, test_rgb_panel_trans_done, cur_task); printf("flush random color block\r\n"); for (int i = 0; i < 200; i++) { uint8_t color_byte = esp_random() & 0xFF; @@ -178,7 +260,7 @@ TEST_CASE("lcd_rgb_panel_bounce_buffer", "[lcd]") TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); printf("initialize RGB panel with non-stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 20 * TEST_LCD_H_RES, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_trans_done, cur_task); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 20 * TEST_LCD_H_RES, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_trans_done, cur_task); printf("flush random color block\r\n"); for (int i = 0; i < 200; i++) { uint8_t color_byte = esp_random() & 0xFF; @@ -201,7 +283,7 @@ TEST_CASE("lcd_rgb_panel_update_pclk", "[lcd]") TEST_ASSERT_NOT_NULL(img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); printf("flush one clock block to the LCD\r\n"); uint8_t color_byte = esp_random() & 0xFF; int x_start = esp_random() % (TEST_LCD_H_RES - 100); @@ -230,7 +312,7 @@ TEST_CASE("lcd_rgb_panel_restart", "[lcd]") TEST_ASSERT_NOT_NULL(img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); printf("flush one clock block to the LCD\r\n"); uint8_t color_byte = esp_random() & 0xFF; int x_start = esp_random() % (TEST_LCD_H_RES - 100); @@ -266,7 +348,7 @@ TEST_CASE("lcd_rgb_panel_rotate", "[lcd]") memset(img, color_byte, w * h * sizeof(uint16_t)); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); printf("Update the rotation of panel\r\n"); for (size_t i = 0; i < 8; i++) { @@ -291,7 +373,7 @@ TEST_CASE("lcd_rgb_panel_user_frame_buffer", "[lcd]") TEST_ASSERT_NOT_NULL(img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, true, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, true, NULL, NULL); printf("flush one clock block to the LCD\r\n"); uint8_t color_byte = esp_random() & 0xFF; @@ -325,7 +407,7 @@ TEST_CASE("lcd_rgb_panel_use_apll", "[lcd]") printf("APLL frequency: %"PRIu32" Hz\r\n", real_freq); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_APLL, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_APLL, false, false, NULL, NULL); printf("flush random color block\r\n"); for (int i = 0; i < 200; i++) { uint8_t color_byte = esp_random() & 0xFF; @@ -366,7 +448,7 @@ TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]") uint32_t callback_calls = 0; printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_count_in_callback, &callback_calls); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_count_in_callback, &callback_calls); printf("flush one clock block to the LCD\r\n"); uint8_t color_byte = esp_random() & 0xFF; int x_start = esp_random() % (TEST_LCD_H_RES - 100); From 2ccef0aa98642b5700ecb80d260302d6406be639 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 20 Aug 2026 10:06:21 +0800 Subject: [PATCH 5/5] fix(rgb_lcd): pass output color format in tests --- .../esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c index f0a4fbc99d3..15ebcbbbc41 100644 --- a/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c +++ b/components/esp_lcd/test_apps/rgb_lcd/main/test_rgb_panel.c @@ -331,7 +331,7 @@ TEST_CASE("lcd_rgb_panel_restart", "[lcd]") free(img); #else printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_lcd_rgb_panel_restart(panel_handle)); TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); #endif @@ -478,7 +478,7 @@ TEST_CASE("lcd_rgb_panel_draw_bitmap_2d", "[lcd]") TEST_ASSERT_NOT_NULL(src_img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); printf("Draw bitmap 2D by CPU - copy partial region from source to destination\r\n"); for (int i = 0; i < 100; i++) { @@ -516,7 +516,7 @@ TEST_CASE("lcd_rgb_panel_dma2d_hook", "[lcd]") TEST_ASSERT_NOT_NULL(src_img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); printf("Draw bitmap 2D by CPU first\r\n"); for (int i = 0; i < 50; i++) { @@ -662,7 +662,7 @@ TEST_CASE("lcd_rgb_panel_ppa_hook", "[lcd]") TEST_ASSERT_NOT_NULL(src_img); printf("initialize RGB panel with stream mode\r\n"); - esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); + esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL); SemaphoreHandle_t draw_sem = xSemaphoreCreateBinaryWithCaps(MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); TEST_ASSERT_NOT_NULL(draw_sem);