diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index 113bdc959e9..ffe633869d3 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -542,7 +542,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; @@ -563,14 +563,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; @@ -600,14 +605,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 ef68a71565a..24d7a2f6db5 100644 --- a/components/esp_lcd/rgb/esp_lcd_panel_rgb.c +++ b/components/esp_lcd/rgb/esp_lcd_panel_rgb.c @@ -48,6 +48,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" @@ -89,6 +90,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); @@ -172,6 +175,11 @@ struct esp_rgb_panel_t { uint32_t user_fb: 1; // Whether the frame buffer is provided by user uint32_t core_clk_enabled: 1; // Whether the LCD core clock source was enabled for this panel instance } 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) @@ -476,6 +484,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; @@ -626,6 +635,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; @@ -695,17 +708,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; @@ -713,6 +782,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); @@ -733,6 +808,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; @@ -744,17 +833,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) { @@ -763,22 +879,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 @@ -787,18 +902,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; } @@ -1424,6 +1528,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 a4961d0c48d..f513ee1290a 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 @@ -290,7 +290,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) @@ -305,6 +304,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; } @@ -351,11 +356,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]") @@ -435,7 +438,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)); @@ -461,6 +463,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; @@ -470,7 +473,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 0816b07ee06..c18208890ca 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_timer.h" @@ -16,6 +17,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 @@ -381,3 +386,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/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in index 95d1ac71004..a715f40c403 100644 --- a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in @@ -1223,6 +1223,10 @@ config SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV bool default y +config SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION + bool + default y + config SOC_SECURE_BOOT_V2_RSA bool default y diff --git a/components/soc/esp32s31/include/soc/soc_caps.h b/components/soc/esp32s31/include/soc/soc_caps.h index 76d1367b69f..5df778b30ae 100644 --- a/components/soc/esp32s31/include/soc/soc_caps.h +++ b/components/soc/esp32s31/include/soc/soc_caps.h @@ -456,6 +456,9 @@ /*--------------------------- CAM ---------------------------------*/ #define SOC_LCDCAM_CAM_SUPPORT_RGB_YUV_CONV (1) +/*--------------------------- LCD ---------------------------------*/ +#define SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION (1) /*!< Support back up registers before sleep */ + /*-------------------------- Secure Boot CAPS----------------------------*/ #define SOC_SECURE_BOOT_V2_RSA 1 #define SOC_SECURE_BOOT_V2_ECC 0 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 f95526ea35a..52dfa277526 100644 --- a/docs/en/api-reference/peripherals/lcd/rgb_lcd.rst +++ b/docs/en/api-reference/peripherals/lcd/rgb_lcd.rst @@ -312,6 +312,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 :menuitem:`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 2e85e0e772c..300c4e9aa04 100644 --- a/docs/zh_CN/api-reference/peripherals/lcd/rgb_lcd.rst +++ b/docs/zh_CN/api-reference/peripherals/lcd/rgb_lcd.rst @@ -312,6 +312,76 @@ bounce buffer 与 PSRAM frame buffer 虽说在设计良好的嵌入式应用程序中, DMA 传递数据的速度不应该赶不上 LCD 读取数据的速度。但理论上,此种情况还是有可能出现的。在 {IDF_TARGET_NAME} 的硬件中,这种情况会导致 LCD 在 DMA 等待数据时单纯输出 dummy 字节。若以流式传输运行 DMA,则 DMA 会将读取到的数据传输到某个 LCD 地址,同时 LCD 也会将数据输出到某个 LCD 地址,但上述两个地址可能会不同步,导致图像 **永久** 偏移。 为防止类似情况发生,可以启用 :menuitem:`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 df821a316f3..39e0991aa7c 100644 --- a/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild +++ b/examples/peripherals/lcd/rgb_panel/main/Kconfig.projbuild @@ -25,6 +25,14 @@ menu "RGB Panel 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 fe95036e4dd..498b1a82f71 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 @@ -139,6 +139,12 @@ void app_main(void) esp_lcd_panel_handle_t panel_handle = NULL; ESP_ERROR_CHECK(example_rgb_lcd_panel_new(&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(example_rgb_lcd_panel_init(panel_handle));