feat(rgb_lcd): support draw bitmap hook

This commit is contained in:
Chen Jichang
2026-06-17 15:49:51 +08:00
committed by Chen Ji Chang
parent 33fb0f6daf
commit f2cf926e0a
18 changed files with 979 additions and 122 deletions

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 参考
--------