Merge branch 'feat/support_rgb_lcd_dma2d' into 'master'

feat(rgb_lcd): support draw bitmap using dma2d

Closes IDF-15652 and IDFGH-11807

See merge request espressif/esp-idf!48510
This commit is contained in:
morris
2026-08-14 14:03:36 +08:00
24 changed files with 1239 additions and 152 deletions

View File

@@ -39,7 +39,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")
if(CONFIG_SOC_PAU_SUPPORTED)
list(APPEND srcs "src/${target}/dma2d_retention.c")
endif()

View File

@@ -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 <stddef.h>
#include <stdbool.h>
#include <stdint.h>
#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

View File

@@ -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");

View File

@@ -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);
}

View File

@@ -178,7 +178,7 @@ revert:
*
* Passing a NULL `expected` never claims (there is nothing to tear down).
*/
static inline bool claim_rx_transaction(dma2d_group_t *group, dma2d_rx_channel_t *rx_chan, dma2d_trans_t *expected)
FORCE_INLINE_ATTR bool claim_rx_transaction(dma2d_group_t *group, dma2d_rx_channel_t *rx_chan, dma2d_trans_t *expected)
{
bool claimed = false;
esp_os_enter_critical_safe(&group->spinlock);

View File

@@ -6,12 +6,12 @@
#include <sys/param.h>
#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"
@@ -45,7 +45,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
@@ -65,11 +65,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) {
@@ -484,7 +484,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,
@@ -497,16 +499,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;
}
@@ -531,24 +530,24 @@ 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 = {
.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;
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;
@@ -563,14 +562,13 @@ 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_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;
return ESP_OK;
@@ -600,14 +598,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
@@ -639,7 +658,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,

View File

@@ -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

View File

@@ -11,7 +11,6 @@
#include "hal/cache_ll.h"
#include "hal/cache_hal.h"
#include "esp_private/sleep_retention.h"
#include "esp_sleep.h"
// Use retention link only when the target supports sleep retention is enabled
#define I80_USE_RETENTION_LINK (SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION && CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)
@@ -133,9 +132,6 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
TAG, "invalid bus width:%d", bus_config->bus_width);
#if !SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION
ESP_RETURN_ON_FALSE(bus_config->flags.allow_pd == 0, ESP_ERR_NOT_SUPPORTED, TAG, "register back up is not supported");
#if SOC_PM_SUPPORT_TOP_PD
esp_sleep_pd_config(ESP_PD_DOMAIN_TOP, ESP_PD_OPTION_ON); //IDF-15652
#endif
#endif // SOC_LCDCAM_LCD_SUPPORT_SLEEP_RETENTION
// allocate i80 bus memory

View File

@@ -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 <stdbool.h>
#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

View File

@@ -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

View File

@@ -48,6 +48,7 @@
#include "hal/color_hal.h"
#include "rgb_lcd_rotation_sw.h"
#include "esp_private/sleep_retention.h"
#include "esp_private/async_memcpy_dma2d.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_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)
@@ -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_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)mcp;
(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 DMA2D async 2D memcpy 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,103 @@ 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;
// 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,
.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,
.pixel_format = rgb_panel->in_color_format,
};
// 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_memcpy_dma2d(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 DMA2D async 2D memcpy backend used by the built-in copy hook.
// Use its default backlog to queue multiple frame buffer copy requests.
async_memcpy_dma2d_config_t fbcpy_config = {
.dma_burst_size = 128, // for better performance
};
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 = {
.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_memcpy_uninstall_dma2d(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_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;
return ESP_OK;
}
#endif // SOC_HAS(DMA2D)
#if CONFIG_LCD_ENABLE_DEBUG_LOG
__attribute__((constructor))
static void rgb_lcd_override_default_log_level(void)

View File

@@ -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

View File

@@ -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; \

View File

@@ -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();

View File

@@ -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)

View File

@@ -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,9 @@
#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"
#include "esp_macros.h"
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
#define TEST_LCD_CALLBACK_ATTR IRAM_ATTR
@@ -381,3 +385,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 = ESP_ALIGN_DOWN(test_block_size, SOC_MEMSPI_ENCRYPTION_ALIGNMENT);
start_alignment = SOC_MEMSPI_ENCRYPTION_ALIGNMENT;
src_x_start = ESP_ALIGN_DOWN(src_x_start, SOC_MEMSPI_ENCRYPTION_ALIGNMENT);
src_y_start = ESP_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 = ESP_ALIGN_DOWN(rand() % (TEST_LCD_H_RES - test_block_size), start_alignment);
int y_start = ESP_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)

View File

@@ -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

View File

@@ -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

View File

@@ -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
--------------------------

View File

@@ -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
-------------

View File

@@ -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 的供电
---------------------

View File

@@ -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` 不会立即重启 DMADMA 只会在下一个 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 参考
--------

View File

@@ -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

View File

@@ -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));