feat(mipi_dsi): support draw_bitmap by DMA2D engine

This feature can be enabled at runtime, by setting esp_lcd_dpi_panel_config_t:🎏:use_dma2d
This commit is contained in:
morris 2024-01-22 18:40:35 +08:00
parent b2de1b5665
commit 872058f086
7 changed files with 191 additions and 29 deletions

View File

@ -5,6 +5,7 @@
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "soc/soc_caps.h"
#include "esp_check.h"
#include "esp_lcd_panel_interface.h"
@ -12,6 +13,7 @@
#include "esp_clk_tree.h"
#include "esp_cache.h"
#include "mipi_dsi_priv.h"
#include "esp_async_fbcpy.h"
#include "esp_private/dw_gdma.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
@ -33,10 +35,38 @@ struct esp_lcd_dpi_panel_t {
uint32_t v_pixels; // Vertical pixels
size_t frame_buffer_size; // Frame buffer size
size_t bytes_per_pixel; // Bytes per pixel
lcd_color_rgb_pixel_format_t pixel_format; // RGB Pixel format
dw_gdma_channel_handle_t dma_chan; // DMA channel
dw_gdma_link_list_handle_t link_list; // DMA link list
esp_async_fbcpy_handle_t fbcpy_handle; // Use DMA2D to do frame buffer copy
SemaphoreHandle_t draw_sem; // A semaphore used to synchronize the draw operations when DMA2D is used
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; // Callback invoked when color data transfer has finished
void* user_ctx; // User context for the callback
};
IRAM_ATTR
static bool async_fbcpy_done_cb(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_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;
// release the draw semaphore first
BaseType_t task_woken = pdFALSE;
xSemaphoreGiveFromISR(dpi_panel->draw_sem, &task_woken);
if (task_woken == pdTRUE) {
need_yield = true;
}
if (dpi_panel->on_color_trans_done) {
if (dpi_panel->on_color_trans_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
need_yield = true;
}
}
return need_yield;
}
IRAM_ATTR
static bool dma_list_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gdma_break_event_data_t *event_data, void *user_data)
{
dw_gdma_lli_handle_t lli = event_data->invalid_lli;
@ -104,15 +134,20 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
esp_err_t ret = ESP_OK;
void *frame_buffer = NULL;
esp_lcd_dpi_panel_t *dpi_panel = NULL;
esp_async_fbcpy_handle_t fbcpy_ctx = NULL;
ESP_RETURN_ON_FALSE(bus && panel_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
ESP_RETURN_ON_FALSE(panel_config->virtual_channel < 4, ESP_ERR_INVALID_ARG, TAG, "invalid virtual channel %d", panel_config->virtual_channel);
ESP_RETURN_ON_FALSE(panel_config->dpi_clock_freq_mhz, ESP_ERR_INVALID_ARG, TAG, "invalid DPI clock frequency %"PRIu32, panel_config->dpi_clock_freq_mhz);
#if !SOC_DMA2D_SUPPORTED
ESP_RETURN_ON_FALSE(!panel_config->flags.use_dma2d, ESP_ERR_NOT_SUPPORTED, TAG, "DMA2D is not supported");
#endif // !SOC_DMA2D_SUPPORTED
int bus_id = bus->bus_id;
mipi_dsi_hal_context_t *hal = &bus->hal;
dpi_panel = heap_caps_calloc(1, sizeof(esp_lcd_dpi_panel_t), DSI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dpi_panel, ESP_ERR_NO_MEM, err, TAG, "no memory for DPI panel");
dpi_panel->virtual_channel = panel_config->virtual_channel;
dpi_panel->pixel_format = panel_config->pixel_format;
dpi_panel->bus = bus;
// allocate frame buffer from PSRAM
@ -142,6 +177,17 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
// preset the frame buffer with black color
ESP_GOTO_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), err, TAG, "cache write back failed");
#if SOC_DMA2D_SUPPORTED
if (panel_config->flags.use_dma2d) {
esp_async_fbcpy_config_t fbcpy_config = {};
ESP_GOTO_ON_ERROR(esp_async_fbcpy_install(&fbcpy_config, &fbcpy_ctx), err, TAG, "install async memcpy 2d failed");
dpi_panel->fbcpy_handle = fbcpy_ctx;
dpi_panel->draw_sem = xSemaphoreCreateBinaryWithCaps(DSI_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(dpi_panel->draw_sem, ESP_ERR_NO_MEM, err, TAG, "no memory for draw semaphore");
xSemaphoreGive(dpi_panel->draw_sem);
}
#endif // SOC_DMA2D_SUPPORTED
// if the clock source is not assigned, fallback to the default clock source
mipi_dsi_dpi_clock_source_t dpi_clk_src = panel_config->dpi_clk_src;
if (dpi_clk_src == 0) {
@ -211,9 +257,6 @@ err:
if (dpi_panel) {
dpi_panel_del(&dpi_panel->base);
}
if (frame_buffer) {
free(frame_buffer);
}
return ret;
}
@ -229,6 +272,7 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
}
// disable the DSI bridge
mipi_dsi_brg_ll_enable(hal->bridge, false);
// free memory
if (dpi_panel->dma_chan) {
dw_gdma_del_channel(dpi_panel->dma_chan);
}
@ -238,6 +282,12 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel)
if (dpi_panel->link_list) {
dw_gdma_del_link_list(dpi_panel->link_list);
}
if (dpi_panel->fbcpy_handle) {
esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle);
}
if (dpi_panel->draw_sem) {
vSemaphoreDelete(dpi_panel->draw_sem);
}
free(dpi_panel);
return ESP_OK;
}
@ -293,18 +343,50 @@ static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
uint8_t *frame_buffer = dpi_panel->frame_buffer;
size_t frame_buffer_size = dpi_panel->frame_buffer_size;
// TODO: memory copy by 2D-DMA
size_t bytes_per_pixel = dpi_panel->bytes_per_pixel;
const uint8_t *from = (const uint8_t *)color_data;
uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bytes_per_pixel;
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint32_t bytes_per_line = bytes_per_pixel * dpi_panel->h_pixels;
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;
if (!dpi_panel->fbcpy_handle) {
size_t bytes_per_pixel = dpi_panel->bytes_per_pixel;
const uint8_t *from = (const uint8_t *)color_data;
uint8_t *to = frame_buffer + (y_start * dpi_panel->h_pixels + x_start) * bytes_per_pixel;
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint32_t bytes_per_line = bytes_per_pixel * dpi_panel->h_pixels;
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;
}
ESP_RETURN_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed");
// invoke the trans done callback
if (dpi_panel->on_color_trans_done) {
dpi_panel->on_color_trans_done(&dpi_panel->base, NULL, dpi_panel->user_ctx);
}
} else {
// endure the previous draw operation has finish
ESP_RETURN_ON_FALSE(xSemaphoreTake(dpi_panel->draw_sem, 0) == pdTRUE, ESP_ERR_INVALID_STATE, TAG, "previous draw operation is not finished");
// write back the user's draw buffer, so that the DMA can see the correct data
size_t color_data_size = (x_end - x_start) * (y_end - y_start) * dpi_panel->bytes_per_pixel;
esp_cache_msync((void*)color_data, color_data_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
esp_async_fbcpy_trans_desc_t fbcpy_trans_config = {
.src_buffer = color_data,
.dst_buffer = dpi_panel->frame_buffer,
.src_buffer_size_x = x_end - x_start,
.src_buffer_size_y = y_end - y_start,
.dst_buffer_size_x = dpi_panel->h_pixels,
.dst_buffer_size_y = dpi_panel->v_pixels,
.src_offset_x = 0,
.src_offset_y = 0,
.dst_offset_x = x_start,
.dst_offset_y = y_start,
.copy_size_x = x_end - x_start,
.copy_size_y = y_end - y_start,
.pixel_format_unique_id = {
.color_space = COLOR_SPACE_RGB,
.pixel_format = dpi_panel->pixel_format,
},
};
ESP_RETURN_ON_ERROR(esp_async_fbcpy(dpi_panel->fbcpy_handle, &fbcpy_trans_config, async_fbcpy_done_cb, dpi_panel), TAG, "async memcpy failed");
}
ESP_RETURN_ON_ERROR(esp_cache_msync(frame_buffer, frame_buffer_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M), TAG, "cache write back failed");
return ESP_OK;
}
@ -333,3 +415,13 @@ esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t panel, mipi_dsi_p
return ESP_OK;
}
esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_dpi_panel_event_callbacks_t *cbs, void *user_ctx)
{
ESP_RETURN_ON_FALSE(panel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base);
dpi_panel->on_color_trans_done = cbs->on_color_trans_done;
dpi_panel->user_ctx = user_ctx;
return ESP_OK;
}

View File

@ -83,8 +83,12 @@ typedef struct {
uint8_t virtual_channel; /*!< Virtual channel ID, index from 0 */
mipi_dsi_dpi_clock_source_t dpi_clk_src; /*!< MIPI DSI DPI clock source */
uint32_t dpi_clock_freq_mhz; /*!< DPI clock frequency in MHz */
lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format */
lcd_color_rgb_pixel_format_t pixel_format; /*!< Pixel format that used by the MIPI LCD device */
esp_lcd_video_timing_t video_timing; /*!< Video timing */
/// Extra configuration flags for MIPI DSI DPI panel
struct extra_flags {
uint32_t use_dma2d: 1; /*!< Use DMA2D to copy user buffer to the frame buffer when necessary */
} flags; /*!< Extra configuration flags */
} esp_lcd_dpi_panel_config_t;
/**
@ -97,6 +101,7 @@ typedef struct {
* - ESP_OK: Create MIPI DSI data panel successfully
* - ESP_ERR_INVALID_ARG: Create MIPI DSI data panel failed because of invalid argument
* - ESP_ERR_NO_MEM: Create MIPI DSI data panel failed because of out of memory
* - ESP_ERR_NOT_SUPPORTED: Create MIPI DSI data panel failed because of unsupported feature
* - ESP_FAIL: Create MIPI DSI data panel failed because of other error
*/
esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_panel_config_t *panel_config, esp_lcd_panel_handle_t *ret_panel);
@ -104,14 +109,49 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_
/**
* @brief Set pre-defined pattern to the screen for testing or debugging purpose
*
* @param[in] dbi_panel MIPI DBI panel handle, returned from `esp_lcd_new_panel_dpi`
* @param[in] dpi_panel MIPI DPI panel handle, returned from esp_lcd_new_panel_dpi()
* @param[in] pattern Pattern type
* @return
* - ESP_OK: Set pattern successfully
* - ESP_ERR_INVALID_ARG: Set pattern failed because of invalid argument
* - ESP_FAIL: Set pattern failed because of other error
*/
esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t dbi_panel, mipi_dsi_pattern_type_t pattern);
esp_err_t esp_lcd_dpi_panel_set_pattern(esp_lcd_panel_handle_t dpi_panel, mipi_dsi_pattern_type_t pattern);
/**
* @brief Type of LCD DPI panel event data
*/
typedef struct {
} esp_lcd_dpi_panel_event_data_t;
/**
* @brief Declare the prototype of the function that will be invoked when DPI panel finishes transferring color data
*
* @param[in] panel LCD panel handle, which is created by factory API like esp_lcd_new_panel_dpi()
* @param[in] edata DPI panel event data, fed by driver
* @param[in] user_ctx User data
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*esp_lcd_dpi_panel_color_trans_done_cb_t)(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
/**
* @brief Type of LCD DPI panel callbacks
*/
typedef struct {
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Callback invoked when color data transfer has finished */
} esp_lcd_dpi_panel_event_callbacks_t;
/**
* @brief Register LCD DPI panel callbacks
*
* @param[in] dpi_panel LCD DPI panel handle, which is returned from esp_lcd_new_panel_dpi()
* @param[in] cbs structure with all LCD panel callbacks
* @param[in] user_ctx User private data, passed directly to callback's user_ctx
* @return
* - ESP_ERR_INVALID_ARG: Register callbacks failed because of invalid argument
* - ESP_OK: Register callbacks successfully
*/
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);
#ifdef __cplusplus
}

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -33,15 +33,7 @@
#define LCD_CMD_VSCRDEF 0x33 // Vertical scrolling definition
#define LCD_CMD_TEOFF 0x34 // Turns off tearing effect
#define LCD_CMD_TEON 0x35 // Turns on tearing effect
#define LCD_CMD_MADCTL 0x36 // Memory data access control
#define LCD_CMD_MH_BIT (1 << 2) // Display data latch order, 0: refresh left to right, 1: refresh right to left
#define LCD_CMD_BGR_BIT (1 << 3) // RGB/BGR order, 0: RGB, 1: BGR
#define LCD_CMD_ML_BIT (1 << 4) // Line address order, 0: refresh top to bottom, 1: refresh bottom to top
#define LCD_CMD_MV_BIT (1 << 5) // Row/Column order, 0: normal mode, 1: reverse mode
#define LCD_CMD_MX_BIT (1 << 6) // Column address order, 0: left to right, 1: right to left
#define LCD_CMD_MY_BIT (1 << 7) // Row address order, 0: top to bottom, 1: bottom to top
#define LCD_CMD_MADCTL 0x36 // Memory data access control
#define LCD_CMD_VSCSAD 0x37 // Vertical scroll start address
#define LCD_CMD_IDMOFF 0x38 // Recover from IDLE mode
#define LCD_CMD_IDMON 0x39 // Fall into IDLE mode (8 color depth is displayed)
@ -52,3 +44,13 @@
#define LCD_CMD_GDCAN 0x45 // Get scan line
#define LCD_CMD_WRDISBV 0x51 // Write display brightness
#define LCD_CMD_RDDISBV 0x52 // Read display brightness value
/////////// Warning, It turns out that, the following bitmask is not defined as a standard, some manufacturer may use different bit position
/////////// Please check the datasheet of your LCD panel before using the following bitmask
/////////// IDF will remove them in the next major release (esp-idf 6.0)
#define LCD_CMD_MH_BIT (1 << 2) // Display data latch order, 0: refresh left to right, 1: refresh right to left
#define LCD_CMD_BGR_BIT (1 << 3) // RGB/BGR order, 0: RGB, 1: BGR
#define LCD_CMD_ML_BIT (1 << 4) // Line address order, 0: refresh top to bottom, 1: refresh bottom to top
#define LCD_CMD_MV_BIT (1 << 5) // Row/Column order, 0: normal mode, 1: reverse mode
#define LCD_CMD_MX_BIT (1 << 6) // Column address order, 0: left to right, 1: right to left
#define LCD_CMD_MY_BIT (1 << 7) // Row address order, 0: top to bottom, 1: bottom to top

View File

@ -45,6 +45,12 @@ The connection between ESP Board and the LCD is as follows:
Before testing your LCD, you also need to read your LCD spec carefully, and then adjust the values like "resolution" and "blank time" in the [main](./main/mipi_dsi_lcd_example_main.c) file.
### Configure
Run `idf.py menuconfig` and go to `Example Configuration`:
* Choose whether to `Use DMA2D to copy draw buffer to frame buffer` asynchronously. If you choose `No`, the draw buffer will be copied to the frame buffer synchronously by CPU.
### Build and Flash
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A LVGL widget should show up on the LCD as expected.

View File

@ -0,0 +1,8 @@
menu "Example Configuration"
config EXAMPLE_USE_DMA2D_COPY_FRAME
bool "Use DMA2D to copy draw buffer to frame buffer"
default y
help
Enable this option, DMA2D will be used to copy the LVGL draw buffer to the target frame buffer.
This can save some CPU time and improve the performance.
endmenu

View File

@ -1,3 +1,3 @@
dependencies:
lvgl/lvgl: "~8.3.0"
esp_lcd_ili9881c: ">=0.1.0"
esp_lcd_ili9881c: "*"

View File

@ -75,7 +75,6 @@ static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_
int offsety2 = area->y2;
// pass the draw buffer to the driver
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
lv_disp_flush_ready(drv);
}
static void example_increase_lvgl_tick(void *arg)
@ -117,6 +116,13 @@ static void example_lvgl_port_task(void *arg)
}
}
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_disp_drv_t *disp_driver = (lv_disp_drv_t *)user_ctx;
lv_disp_flush_ready(disp_driver);
return false;
}
static void example_bsp_enable_dsi_phy_power(void)
{
// Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
@ -210,8 +216,16 @@ void app_main(void)
.vsync_pulse_width = EXAMPLE_MIPI_DSI_LCD_VSYNC,
.vsync_front_porch = EXAMPLE_MIPI_DSI_LCD_VFP,
},
#if CONFIG_EXAMPLE_USE_DMA2D_COPY_FRAME
.flags.use_dma2d = true,
#endif
};
ESP_ERROR_CHECK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel));
// register event callbacks
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = example_notify_lvgl_flush_ready,
};
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &disp_drv));
ESP_ERROR_CHECK(esp_lcd_panel_init(mipi_dpi_panel));
// turn on backlight