Merge branch 'feat/support_rgb_lcd_dma2d_v6.0' into 'release/v6.0'

feat(rgb_lcd): support draw bitmap using dma2d (v6.0)

See merge request espressif/esp-idf!51980
This commit is contained in:
morris
2026-09-17 15:36:32 +08:00
23 changed files with 1415 additions and 169 deletions
+2 -1
View File
@@ -35,7 +35,8 @@ if(CONFIG_SOC_DW_GDMA_SUPPORTED)
endif()
if(CONFIG_SOC_DMA2D_SUPPORTED)
list(APPEND srcs "src/dma2d.c" "src/esp_async_color_convert.c" "src/async_color_convert_dma2d.c")
list(APPEND srcs "src/dma2d.c" "src/esp_async_color_convert.c" "src/async_color_convert_dma2d.c"
"src/async_memcpy_dma2d.c")
endif()
idf_component_register(SRCS ${srcs}
@@ -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
@@ -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");
@@ -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);
}
@@ -17,10 +17,14 @@
#include "soc/hp_sys_clkrst_struct.h"
#define LCD_LL_GET(_attr) LCD_LL_ ## _attr
#define LCD_LL_SUPPORT(_feat) LCD_LL_SUPPORT_ ## _feat
#define LCD_LL_RGB_BUS_WIDTH 24
#define LCD_LL_RGB_PANEL_NUM 1
#define LCD_LL_I80_BUS_WIDTH 24
#define LCD_LL_I80_BUS_NUM 1
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
#define LCD_LL_SUPPORT_RGB2RGB_CONV 1
#endif
#ifdef __cplusplus
extern "C" {
@@ -230,14 +234,13 @@ static inline void lcd_ll_enable_color_convert(lcd_cam_dev_t *dev, bool en)
}
/**
* @brief Set convert data line width
* @brief Set convert input data line width for YUV<->RGB conversion
*
* @param dev LCD register base address
* @param width data line width
*/
static inline void lcd_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width)
static inline void lcd_ll_set_yuv_convert_input_data_width(lcd_cam_dev_t *dev, uint32_t width)
{
HAL_ASSERT(width == 8 || width == 16);
dev->lcd_rgb_yuv.lcd_conv_mode_8bits_on = (width == 8) ? 1 : 0;
}
@@ -286,6 +289,28 @@ static inline void lcd_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, lcd_yuv_conv_s
}
}
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
/**
* @brief Set the converter mode: RGB to RGB
*
* @param dev LCD register base address
* @param in_color_format Input color format
* @param out_color_format Output color format
*/
static inline void lcd_ll_set_rgb2rgb_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format)
{
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 0;
dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3;
if (in_color_format == LCD_COLOR_FMT_RGB888 && out_color_format == LCD_COLOR_FMT_RGB565) {
dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 0;
} else if (in_color_format == LCD_COLOR_FMT_RGB565 && out_color_format == LCD_COLOR_FMT_RGB888) {
dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 1;
} else {
abort();
}
}
#endif // LCD_LL_SUPPORT(RGB2RGB_CONV)
/**
* @brief Set the converter mode: RGB to YUV
*
@@ -295,6 +320,9 @@ static inline void lcd_ll_set_yuv_convert_std(lcd_cam_dev_t *dev, lcd_yuv_conv_s
*/
static inline void lcd_ll_set_rgb2yuv_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format)
{
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 2;
#endif
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 1;
dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3;
switch (out_color_format) {
@@ -318,6 +346,9 @@ static inline void lcd_ll_set_rgb2yuv_convert_mode(lcd_cam_dev_t *dev, lcd_color
*/
static inline void lcd_ll_set_yuv2rgb_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format)
{
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 2;
#endif
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 0;
dev->lcd_rgb_yuv.lcd_conv_yuv2yuv_mode = 3;
switch (in_color_format) {
@@ -341,6 +372,9 @@ static inline void lcd_ll_set_yuv2rgb_convert_mode(lcd_cam_dev_t *dev, lcd_color
*/
static inline void lcd_ll_set_yuv2yuv_convert_mode(lcd_cam_dev_t *dev, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format)
{
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
dev->lcd_rgb_yuv.lcd_conv_rgb2rgb_mode = 2;
#endif
dev->lcd_rgb_yuv.lcd_conv_trans_mode = 1;
switch (in_color_format) {
case LCD_COLOR_FMT_YUV422_UYVY:
@@ -424,9 +458,6 @@ static inline void lcd_ll_set_dma_read_stride(lcd_cam_dev_t *dev, uint32_t strid
case 24:
dev->lcd_user.lcd_byte_mode = 2;
break;
case 32:
dev->lcd_user.lcd_byte_mode = 3;
break;
default:
abort();
}
@@ -16,6 +16,7 @@
#include "soc/system_struct.h"
#define LCD_LL_GET(_attr) LCD_LL_ ## _attr
#define LCD_LL_SUPPORT(_feat) LCD_LL_SUPPORT_ ## _feat
#define LCD_LL_RGB_BUS_WIDTH 16
#define LCD_LL_RGB_PANEL_NUM 1
#define LCD_LL_I80_BUS_WIDTH 16
@@ -201,12 +202,12 @@ static inline void lcd_ll_enable_color_convert(lcd_cam_dev_t *dev, bool en)
}
/**
* @brief Set convert data line width
* @brief Set convert input data line width for YUV<->RGB conversion
*
* @param dev LCD register base address
* @param width data line width (8 or 16)
*/
static inline void lcd_ll_set_convert_data_width(lcd_cam_dev_t *dev, uint32_t width)
static inline void lcd_ll_set_yuv_convert_input_data_width(lcd_cam_dev_t *dev, uint32_t width)
{
HAL_ASSERT(width == 8 || width == 16);
dev->lcd_rgb_yuv.lcd_conv_mode_8bits_on = (width == 8) ? 1 : 0;
+42 -23
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"
@@ -44,7 +44,7 @@ struct esp_lcd_dpi_panel_t {
esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function
void* hook_ctx; // Hook context
bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation
async_color_convert_handle_t fbcpy_handle; // Async color convert handle used for same-format DMA2D frame buffer copy
async_memcpy_dma2d_handle_t fbcpy_handle; // DMA2D async 2D memcpy handle used for same-format frame buffer copy
#if CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock; // Power management lock
@@ -64,11 +64,11 @@ static bool dpi_panel_draw_bitmap_hook_end(esp_lcd_panel_t *panel)
return false;
}
static bool async_fbcpy_done_cb(async_color_convert_handle_t conv_hdl, async_color_convert_event_data_t *event, void *cb_args)
static bool async_fbcpy_done_cb(async_memcpy_dma2d_handle_t mcp, async_memcpy_dma2d_event_data_t *event, void *cb_args)
{
bool need_yield = false;
esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)cb_args;
(void)conv_hdl;
(void)mcp;
(void)event;
if (dpi_panel->on_hook_end) {
@@ -476,7 +476,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,
@@ -489,16 +491,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;
}
@@ -523,24 +522,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;
@@ -555,14 +554,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;
@@ -592,14 +590,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
@@ -631,7 +650,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,
@@ -223,52 +223,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
*
@@ -288,7 +242,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
+54 -1
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
+28
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
+254 -32
View File
@@ -49,6 +49,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"
@@ -90,6 +91,8 @@ static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t rgb_panel_draw_bitmap_2d(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data,
size_t src_x_size, size_t src_y_size, int src_x_start, int src_y_start, int src_x_end, int src_y_end);
static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
@@ -167,6 +170,11 @@ struct esp_rgb_panel_t {
uint32_t bb_behind_cache: 1; // Whether the bounce buffer is behind the cache
uint32_t user_fb: 1; // Whether the frame buffer is provided by user
} flags;
// hook fields
esp_lcd_panel_draw_bitmap_hook_t draw_bitmap_hook; // Draw bitmap hook function
void* hook_ctx; // Hook context
bool (*on_hook_end)(esp_lcd_panel_handle_t panel); // Callback to be invoked when the draw bitmap hook completes its operation
async_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)
@@ -349,6 +357,12 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
ESP_RETURN_ON_FALSE(in_color_format != 0, ESP_ERR_INVALID_ARG, TAG, "cannot determine input color format");
// if out_color_format is not specified, set it the same as in_color_format
lcd_color_format_t out_color_format = rgb_panel_config->out_color_format ? rgb_panel_config->out_color_format : in_color_format;
if ((in_color_format == LCD_COLOR_FMT_RGB565 && out_color_format == LCD_COLOR_FMT_RGB888) ||
(in_color_format == LCD_COLOR_FMT_RGB888 && out_color_format == LCD_COLOR_FMT_RGB565)) {
#if !LCD_LL_SUPPORT(RGB2RGB_CONV)
ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "RGB to RGB conversion is not supported");
#endif
}
// calculate buffer size
size_t fb_bits_per_pixel = color_hal_pixel_format_fourcc_get_bit_depth(in_color_format);
@@ -446,6 +460,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;
@@ -566,8 +581,8 @@ esp_err_t esp_lcd_rgb_panel_set_yuv_conversion(esp_lcd_panel_handle_t panel, con
// set color range
lcd_ll_set_input_color_range(hal->dev, config->in_color_range);
lcd_ll_set_output_color_range(hal->dev, config->out_color_range);
// set conversion data width
lcd_ll_set_convert_data_width(hal->dev, rgb_panel->data_width);
// set conversion input data width
lcd_ll_set_yuv_convert_input_data_width(hal->dev, rgb_panel->data_width);
return ESP_OK;
}
@@ -589,6 +604,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;
@@ -617,7 +636,22 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg);
// enable RGB mode and set data width
lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true);
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
if ((rgb_panel->in_color_format == LCD_COLOR_FMT_RGB888 && rgb_panel->out_color_format == LCD_COLOR_FMT_RGB565) || \
(rgb_panel->in_color_format == LCD_COLOR_FMT_RGB565 && rgb_panel->out_color_format == LCD_COLOR_FMT_RGB888)) {
lcd_ll_set_rgb2rgb_convert_mode(rgb_panel->hal.dev, rgb_panel->in_color_format, rgb_panel->out_color_format);
// RGB2RGB converter always uses full color range
lcd_ll_set_input_color_range(rgb_panel->hal.dev, LCD_COLOR_RANGE_FULL);
lcd_ll_set_output_color_range(rgb_panel->hal.dev, LCD_COLOR_RANGE_FULL);
}
#endif
// ESP32S3 dma stride is equal to the data width
#if CONFIG_IDF_TARGET_ESP32S3
lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->data_width);
#else
lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->fb_bits_per_pixel);
#endif
// enable conversion if the input color format is different from the output color format
lcd_ll_enable_color_convert(rgb_panel->hal.dev, rgb_panel->in_color_format != rgb_panel->out_color_format);
// enable data phase only
@@ -658,17 +692,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;
@@ -676,6 +766,12 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
x_end += rgb_panel->x_gap;
y_end += rgb_panel->y_gap;
// save the original coordinates before clipping
int unclipped_x_start = x_start;
int unclipped_y_start = y_start;
int unclipped_x_end = x_end;
int unclipped_y_end = y_end;
// clip to boundaries
if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) {
x_start = MAX(x_start, 0);
@@ -696,6 +792,20 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
return ESP_OK;
}
// adjust the source coordinates to the clipped region
src_x_start += x_start - unclipped_x_start;
src_y_start += y_start - unclipped_y_start;
src_x_end -= unclipped_x_end - x_end;
src_y_end -= unclipped_y_end - y_end;
if (x_start >= x_end || y_start >= y_end || src_x_start >= src_x_end || src_y_start >= src_y_end) {
// no valid region to draw, skip
if (cb) {
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
}
return ESP_OK;
}
// check if we want to copy the draw buffer to the internal frame buffer
bool draw_buf_copy_to_fb = true;
uint8_t draw_buf_fb_index = 0;
@@ -707,17 +817,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) {
@@ -726,22 +863,21 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
COPY_PIXEL_CODE_BLOCK(24)
}
// do memory sync only when the frame buffer is mounted to the DMA link list and behind the cache
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache && flush_ptr) {
esp_cache_msync(flush_ptr, bytes_to_flush, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
// after the draw buffer finished copying, notify the user to recycle the draw buffer
if (cb) {
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
}
} else {
ESP_LOGV(TAG, "draw buffer is part of the frame buffer");
// the new frame buffer index is changed
} else { // no copy, just do cache memory write back
ESP_LOGV(TAG, "draw buffer is in frame buffer memory range, do cache write back only");
// only write back the LCD lines that updated by the draw buffer
rgb_panel->cur_fb_index = draw_buf_fb_index;
// when this function is called, the frame buffer already reflects the draw buffer changes
// if the frame buffer is also mounted to the DMA, we need to do the sync between them
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
size_t cache_sync_size = (y_end - y_start) * bytes_per_line;
uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
size_t cache_sync_size = (y_end - y_start) * h_res * bytes_per_pixel;
// the buffer to be flushed is still within the frame buffer, so even an unaligned address is OK
if (rgb_panel->flags.fb_behind_cache) {
esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
}
// after the draw buffer finished copying, notify the user to recycle the draw buffer
@@ -750,18 +886,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;
}
@@ -1327,6 +1452,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)
@@ -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
+49 -29
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; \
@@ -292,7 +292,6 @@ typedef struct {
typedef struct {
uint32_t count;
SemaphoreHandle_t draw_sem;
} test_dpi_panel_color_trans_done_callback_ctx_t;
IRAM_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_client, ppa_event_data_t *edata, void *user_ctx)
@@ -307,6 +306,12 @@ IRAM_ATTR static bool test_ppa_srm_trans_done_callback(ppa_client_handle_t ppa_c
}
}
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(hook_ctx->draw_sem, &task_woken);
if (task_woken == pdTRUE) {
need_yield = true;
}
return need_yield;
}
@@ -353,11 +358,9 @@ static esp_err_t test_draw_bitmap_hook_ppa(esp_lcd_panel_handle_t panel, const e
IRAM_ATTR static bool test_dpi_panel_color_trans_done_count_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t task_woken = pdFALSE;
test_dpi_panel_color_trans_done_callback_ctx_t *color_trans_done_ctx = (test_dpi_panel_color_trans_done_callback_ctx_t *)user_ctx;
color_trans_done_ctx->count++;
xSemaphoreGiveFromISR(color_trans_done_ctx->draw_sem, &task_woken);
return task_woken == pdTRUE;
return false;
}
TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
@@ -437,7 +440,6 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
};
test_dpi_panel_color_trans_done_callback_ctx_t color_trans_done_ctx = {
.draw_sem = draw_sem,
.count = 0,
};
TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &color_trans_done_ctx));
@@ -463,6 +465,7 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
img, 200, 200, 0, 0, 200, 200);
vTaskDelay(pdMS_TO_TICKS(10));
}
xSemaphoreTake(draw_sem, portMAX_DELAY);
TEST_ASSERT_EQUAL_INT(100, color_trans_done_ctx.count);
hooks.draw_bitmap_hook = NULL;
@@ -472,7 +475,7 @@ TEST_CASE("MIPI DSI use PPA (EK79007)", "[mipi_dsi]")
TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel));
TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io));
TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus));
vSemaphoreDelete(draw_sem);
vSemaphoreDeleteWithCaps(draw_sem);
free(img);
test_bsp_disable_dsi_phy_power();
@@ -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)
@@ -68,6 +68,32 @@ extern "C" {
#define TEST_LCD_DATA15_GPIO 19 // R4
#define TEST_LCD_DISP_EN_GPIO -1
// GPIO definitions for 24-bit RGB888 interface (used by RGB565 to RGB888 conversion test)
#define TEST_LCD_24BIT_DATA0_GPIO 34
#define TEST_LCD_24BIT_DATA1_GPIO 12
#define TEST_LCD_24BIT_DATA2_GPIO 10
#define TEST_LCD_24BIT_DATA3_GPIO 40
#define TEST_LCD_24BIT_DATA4_GPIO 42
#define TEST_LCD_24BIT_DATA5_GPIO 27
#define TEST_LCD_24BIT_DATA6_GPIO 29
#define TEST_LCD_24BIT_DATA7_GPIO 31
#define TEST_LCD_24BIT_DATA8_GPIO 16
#define TEST_LCD_24BIT_DATA9_GPIO 14
#define TEST_LCD_24BIT_DATA10_GPIO 21
#define TEST_LCD_24BIT_DATA11_GPIO 23
#define TEST_LCD_24BIT_DATA12_GPIO 26
#define TEST_LCD_24BIT_DATA13_GPIO 28
#define TEST_LCD_24BIT_DATA14_GPIO 30
#define TEST_LCD_24BIT_DATA15_GPIO 32
#define TEST_LCD_24BIT_DATA16_GPIO 22
#define TEST_LCD_24BIT_DATA17_GPIO 20
#define TEST_LCD_24BIT_DATA18_GPIO 18
#define TEST_LCD_24BIT_DATA19_GPIO 6
#define TEST_LCD_24BIT_DATA20_GPIO 0
#define TEST_LCD_24BIT_DATA21_GPIO 15
#define TEST_LCD_24BIT_DATA22_GPIO 17
#define TEST_LCD_24BIT_DATA23_GPIO 19
#else
#error "Unsupported target"
#endif
@@ -9,14 +9,20 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "soc/soc_caps.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "esp_random.h"
#include "esp_timer.h"
#include "esp_attr.h"
#include "hal/lcd_ll.h"
#include "test_rgb_board.h"
#include "esp_private/spi_flash_os.h"
#include "esp_clk_tree.h"
#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
@@ -26,13 +32,15 @@
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, lcd_color_format_t in_color_format, size_t bb_pixels, lcd_clock_source_t clk_src, bool refresh_on_demand, bool user_fb,
static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, lcd_color_format_t in_color_format, lcd_color_format_t out_color_format,
size_t bb_pixels, lcd_clock_source_t clk_src, bool refresh_on_demand, bool user_fb,
esp_lcd_rgb_panel_vsync_cb_t vsync_cb, void *user_data)
{
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = data_width,
.in_color_format = in_color_format,
.out_color_format = out_color_format,
.dma_burst_size = 64,
.bounce_buffer_size_px = bb_pixels,
.clk_src = clk_src,
@@ -80,6 +88,38 @@ static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, l
panel_config.user_fbs[0] = frame_buffer;
}
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
if (data_width == 24) {
const int data_gpios[24] = {
TEST_LCD_24BIT_DATA0_GPIO,
TEST_LCD_24BIT_DATA1_GPIO,
TEST_LCD_24BIT_DATA2_GPIO,
TEST_LCD_24BIT_DATA3_GPIO,
TEST_LCD_24BIT_DATA4_GPIO,
TEST_LCD_24BIT_DATA5_GPIO,
TEST_LCD_24BIT_DATA6_GPIO,
TEST_LCD_24BIT_DATA7_GPIO,
TEST_LCD_24BIT_DATA8_GPIO,
TEST_LCD_24BIT_DATA9_GPIO,
TEST_LCD_24BIT_DATA10_GPIO,
TEST_LCD_24BIT_DATA11_GPIO,
TEST_LCD_24BIT_DATA12_GPIO,
TEST_LCD_24BIT_DATA13_GPIO,
TEST_LCD_24BIT_DATA14_GPIO,
TEST_LCD_24BIT_DATA15_GPIO,
TEST_LCD_24BIT_DATA16_GPIO,
TEST_LCD_24BIT_DATA17_GPIO,
TEST_LCD_24BIT_DATA18_GPIO,
TEST_LCD_24BIT_DATA19_GPIO,
TEST_LCD_24BIT_DATA20_GPIO,
TEST_LCD_24BIT_DATA21_GPIO,
TEST_LCD_24BIT_DATA22_GPIO,
TEST_LCD_24BIT_DATA23_GPIO,
};
memcpy(panel_config.data_gpio_nums, data_gpios, sizeof(data_gpios));
}
#endif
TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
esp_lcd_rgb_panel_event_callbacks_t cbs = {
@@ -98,7 +138,7 @@ TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]")
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
@@ -120,7 +160,7 @@ TEST_CASE("lcd_rgb_panel_8bit_interface", "[lcd]")
printf("initialize RGB panel with stream mode\r\n");
// bpp for RGB888 is 24
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(8, LCD_COLOR_FMT_RGB888, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(8, LCD_COLOR_FMT_RGB888, LCD_COLOR_FMT_RGB888, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
uint8_t color_byte = esp_random() & 0xFF;
printf("flush random color block 0x%x\r\n", color_byte);
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
@@ -133,6 +173,53 @@ TEST_CASE("lcd_rgb_panel_8bit_interface", "[lcd]")
free(img);
}
#if LCD_LL_SUPPORT(RGB2RGB_CONV)
TEST_CASE("lcd_rgb_panel_rgb2rgb_conversion", "[lcd]")
{
uint8_t *img = malloc(100 * 100 * 3);
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with RGB888 frame buffer and RGB565 output\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB888, LCD_COLOR_FMT_RGB565,
0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t red = esp_random() & 0xFF;
uint8_t green = esp_random() & 0xFF;
uint8_t blue = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
for (int j = 0; j < 100 * 100; j++) {
img[j * 3 + 0] = blue;
img[j * 3 + 1] = green;
img[j * 3 + 2] = red;
}
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
vTaskDelay(pdMS_TO_TICKS(10));
}
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
vTaskDelay(pdMS_TO_TICKS(500));
printf("initialize RGB panel with RGB565 frame buffer and RGB888 output\r\n");
panel_handle = test_rgb_panel_initialization(24, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB888,
0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
vTaskDelay(pdMS_TO_TICKS(10));
}
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(img);
}
#endif
TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
TaskHandle_t task_to_notify = (TaskHandle_t)user_ctx;
@@ -148,7 +235,7 @@ TEST_CASE("lcd_rgb_panel_refresh_on_demand", "[lcd]")
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
printf("initialize RGB panel with non-stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, true, false, test_rgb_panel_trans_done, cur_task);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, true, false, test_rgb_panel_trans_done, cur_task);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
@@ -173,7 +260,7 @@ TEST_CASE("lcd_rgb_panel_bounce_buffer", "[lcd]")
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
printf("initialize RGB panel with non-stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 20 * TEST_LCD_H_RES, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_trans_done, cur_task);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 20 * TEST_LCD_H_RES, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_trans_done, cur_task);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
@@ -196,7 +283,7 @@ TEST_CASE("lcd_rgb_panel_update_pclk", "[lcd]")
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("flush one clock block to the LCD\r\n");
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
@@ -225,7 +312,7 @@ TEST_CASE("lcd_rgb_panel_restart", "[lcd]")
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("flush one clock block to the LCD\r\n");
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
@@ -244,7 +331,7 @@ TEST_CASE("lcd_rgb_panel_restart", "[lcd]")
free(img);
#else
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, esp_lcd_rgb_panel_restart(panel_handle));
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
#endif
@@ -261,7 +348,7 @@ TEST_CASE("lcd_rgb_panel_rotate", "[lcd]")
memset(img, color_byte, w * h * sizeof(uint16_t));
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, NULL, NULL);
printf("Update the rotation of panel\r\n");
for (size_t i = 0; i < 8; i++) {
@@ -286,7 +373,7 @@ TEST_CASE("lcd_rgb_panel_user_frame_buffer", "[lcd]")
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, true, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, true, NULL, NULL);
printf("flush one clock block to the LCD\r\n");
uint8_t color_byte = esp_random() & 0xFF;
@@ -320,7 +407,7 @@ TEST_CASE("lcd_rgb_panel_use_apll", "[lcd]")
printf("APLL frequency: %"PRIu32" Hz\r\n", real_freq);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_APLL, false, false, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_APLL, false, false, NULL, NULL);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
@@ -361,7 +448,7 @@ TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]")
uint32_t callback_calls = 0;
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_count_in_callback, &callback_calls);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, LCD_COLOR_FMT_RGB565, LCD_COLOR_FMT_RGB565, 0, LCD_CLK_SRC_DEFAULT, false, false, test_rgb_panel_count_in_callback, &callback_calls);
printf("flush one clock block to the LCD\r\n");
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
@@ -382,3 +469,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, 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, 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, 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)