Merge branch 'feature/mipi_dpi_2ddma' into 'master'

Use DMA2D in MIPI DSI driver

See merge request espressif/esp-idf!28605
This commit is contained in:
morris 2024-01-31 11:25:32 +08:00
commit 97cda1a732
11 changed files with 491 additions and 29 deletions

View File

@ -14,6 +14,10 @@ set(includes "include" "interface")
set(priv_requires "esp_mm" "esp_psram" "esp_pm" "esp_driver_spi" "esp_driver_i2s")
set(public_requires "driver" "esp_driver_gpio" "esp_driver_i2c")
if(CONFIG_SOC_DMA2D_SUPPORTED)
list(APPEND srcs "src/esp_async_fbcpy.c")
endif()
if(CONFIG_SOC_I2C_SUPPORTED)
list(APPEND srcs "i2c/esp_lcd_panel_io_i2c_v1.c" "i2c/esp_lcd_panel_io_i2c_v2.c")
endif()

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

@ -0,0 +1,90 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "esp_err.h"
#include "hal/color_types.h"
/**
* @brief Async FrameBuffer copy context
*/
typedef struct esp_async_fbcpy_context_t *esp_async_fbcpy_handle_t;
/**
* @brief Async FrameBuffer copy configuration
*/
typedef struct {
} esp_async_fbcpy_config_t;
/**
* @brief Install Async FrameBuffer copy driver
*
* @param config Async FrameBuffer copy configuration
* @param mcp Returned Async FrameBuffer copy handle
* @return
* - ESP_OK: Install Async FrameBuffer copy driver successfully
* - ESP_ERR_INVALID_ARG: Install Async FrameBuffer copy driver failed because of invalid argument
* - ESP_ERR_NO_MEM: Install Async FrameBuffer copy driver failed because of out of memory
* - ESP_FAIL: Install Async FrameBuffer copy driver failed because of other error
*/
esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp);
/**
* @brief Uninstall Async FrameBuffer copy driver
*
* @param mcp Async FrameBuffer copy handle
* @return
* - ESP_OK: Uninstall Async FrameBuffer copy driver successfully
* - ESP_ERR_INVALID_ARG: Uninstall Async FrameBuffer copy driver failed because of invalid argument
* - ESP_FAIL: Uninstall Async FrameBuffer copy driver failed because of other error
*/
esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp);
/**
* @brief Async FrameBuffer copy transaction descriptor
*/
typedef struct {
const void *src_buffer; /*!< Source buffer */
void *dst_buffer; /*!< Destination buffer */
size_t src_buffer_size_x; /*!< Source buffer size in x direction, size count in the number of pixels */
size_t src_buffer_size_y; /*!< Source buffer size in y direction, size count in the number of pixels */
size_t dst_buffer_size_x; /*!< Destination buffer size in x direction, size count in the number of pixels */
size_t dst_buffer_size_y; /*!< Destination buffer size in y direction, size count in the number of pixels */
size_t src_offset_x; /*!< Copy action will start from this offset in source buffer in the x direction, offset count in the number of pixels */
size_t src_offset_y; /*!< Copy action will start from this offset in source buffer in the y direction, offset count in the number of pixels */
size_t dst_offset_x; /*!< Copy action will start from this offset in destination buffer in the x direction, offset count in the number of pixels */
size_t dst_offset_y; /*!< Copy action will start from this offset in destination buffer in the y direction, offset count in the number of pixels */
size_t copy_size_x; /*!< Copy size in the x direction, size count in the number of pixels */
size_t copy_size_y; /*!< Copy size in the y direction, size count in the number of pixels */
color_space_pixel_format_t pixel_format_unique_id; /*!< Pixel format unique ID */
} esp_async_fbcpy_trans_desc_t;
/**
* @brief Async FrameBuffer copy event data
*/
typedef struct {
} esp_async_fbcpy_event_data_t;
/**
* @brief Async FrameBuffer copy event callback prototype
*/
typedef bool (*esp_async_fbcpy_event_callback_t)(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_event_data_t *event_data, void *cb_args);
/**
* @brief Start Async FrameBuffer copy transaction
*
* @param mcp Async FrameBuffer copy handle
* @param transaction Async FrameBuffer copy transaction descriptor
* @param memcpy_done_cb Callback function that will be invoked when Async FrameBuffer copy transaction finishes
* @param cb_args User data
* @return
* - ESP_OK: Start Async FrameBuffer copy transaction successfully
* - ESP_ERR_INVALID_ARG: Start Async FrameBuffer copy transaction failed because of invalid argument
* - ESP_FAIL: Start Async FrameBuffer copy transaction failed because of other error
*/
esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction,
esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args);

View File

@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_check.h"
#include "esp_cache.h"
#include "esp_heap_caps.h"
#include "soc/dma2d_channel.h"
#include "hal/cache_hal.h"
#include "hal/cache_ll.h"
#include "hal/dma2d_ll.h"
#include "esp_private/dma2d.h"
#include "esp_async_fbcpy.h"
#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
static const char *TAG = "async_fbcpy";
typedef struct esp_async_fbcpy_context_t {
dma2d_pool_handle_t client; // DMA2D client
dma2d_descriptor_t* tx_desc; // DMA2D TX descriptor
dma2d_descriptor_t* rx_desc; // DMA2D RX descriptor
dma2d_trans_t* trans_desc; // DMA2D transaction descriptor
size_t dma_desc_size; // DMA2D descriptor size
esp_async_fbcpy_event_callback_t memcpy_done_cb; // memory copy done callback
void *cb_args; // callback arguments
} esp_async_fbcpy_context_t;
static esp_err_t async_fbcpy_del_context(esp_async_fbcpy_context_t* ctx)
{
if (ctx->tx_desc) {
free(ctx->tx_desc);
}
if (ctx->rx_desc) {
free(ctx->rx_desc);
}
if (ctx->trans_desc) {
free(ctx->trans_desc);
}
if (ctx->client) {
dma2d_release_pool(ctx->client);
}
free(ctx);
return ESP_OK;
}
esp_err_t esp_async_fbcpy_install(const esp_async_fbcpy_config_t *config, esp_async_fbcpy_handle_t *mcp)
{
esp_err_t ret = ESP_OK;
esp_async_fbcpy_context_t *ctx = NULL;
dma2d_trans_t* trans_desc = NULL;
dma2d_descriptor_t* dma_tx_desc = NULL;
dma2d_descriptor_t* dma_rx_desc = NULL;
dma2d_pool_handle_t dma2d_client = NULL;
ESP_RETURN_ON_FALSE(config && mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
// allocate context memory
ctx = heap_caps_calloc(1, sizeof(esp_async_fbcpy_context_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(ctx, ESP_ERR_NO_MEM, err, TAG, "no mem for esp_async_fbcpy_context_t");
// according to the dma2d design, the transaction descriptor is also saved by the user
trans_desc = heap_caps_calloc(1, dma2d_get_trans_elm_size(), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(trans_desc, ESP_ERR_NO_MEM, err, TAG, "no mem for trans_desc");
ctx->trans_desc = trans_desc;
// allocate memory for DMA descriptor, the descriptor must be allocated from the internal memory, and alignment to the cache line size
uint32_t data_cache_line_size = cache_hal_get_cache_line_size(CACHE_LL_LEVEL_INT_MEM, CACHE_TYPE_DATA);
size_t alignment = MAX(DMA2D_LL_DESC_ALIGNMENT, data_cache_line_size);
size_t dma_desc_mem_size = ALIGN_UP(sizeof(dma2d_descriptor_align8_t), alignment);
dma_tx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
dma_rx_desc = heap_caps_aligned_calloc(alignment, 1, dma_desc_mem_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
ESP_GOTO_ON_FALSE(dma_tx_desc && dma_rx_desc, ESP_ERR_NO_MEM, err, TAG, "no memory for DMA2D descriptors");
ctx->tx_desc = dma_tx_desc;
ctx->rx_desc = dma_rx_desc;
ctx->dma_desc_size = dma_desc_mem_size;
// initialize DMA2D client
dma2d_pool_config_t dma2d_client_config = {}; // all follow default configurations
ESP_GOTO_ON_ERROR(dma2d_acquire_pool(&dma2d_client_config, &dma2d_client), err, TAG, "create DMA2D client failed");
ctx->client = dma2d_client;
*mcp = ctx;
return ESP_OK;
err:
if (ctx) {
async_fbcpy_del_context(ctx);
}
return ret;
}
esp_err_t esp_async_fbcpy_uninstall(esp_async_fbcpy_handle_t mcp)
{
ESP_RETURN_ON_FALSE(mcp, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
return async_fbcpy_del_context(mcp);
}
static void async_memcpy_setup_dma2d_descriptor(esp_async_fbcpy_context_t* mcp_ctx, esp_async_fbcpy_trans_desc_t* transaction)
{
dma2d_descriptor_t* tx_desc = mcp_ctx->tx_desc;
dma2d_descriptor_t* rx_desc = mcp_ctx->rx_desc;
size_t dma_desc_size = mcp_ctx->dma_desc_size;
uint8_t dma2d_pbyte = dma2d_desc_pixel_format_to_pbyte_value(transaction->pixel_format_unique_id);
tx_desc->buffer = (void*)transaction->src_buffer;
tx_desc->next = NULL;
tx_desc->dma2d_en = 1;
tx_desc->suc_eof = 1;
tx_desc->ha_length = transaction->src_buffer_size_x;
tx_desc->va_size = transaction->src_buffer_size_y;
tx_desc->hb_length = transaction->copy_size_x;
tx_desc->vb_size = transaction->copy_size_y;
tx_desc->x = transaction->src_offset_x;
tx_desc->y = transaction->src_offset_y;
tx_desc->pbyte = dma2d_pbyte;
tx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE;
tx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA;
rx_desc->buffer = transaction->dst_buffer;
rx_desc->next = NULL;
rx_desc->dma2d_en = 1;
rx_desc->suc_eof = 1;
rx_desc->ha_length = transaction->dst_buffer_size_x;
rx_desc->va_size = transaction->dst_buffer_size_y;
rx_desc->hb_length = transaction->copy_size_x;
rx_desc->vb_size = transaction->copy_size_y;
rx_desc->x = transaction->dst_offset_x;
rx_desc->y = transaction->dst_offset_y;
rx_desc->pbyte = dma2d_pbyte;
rx_desc->mode = DMA2D_DESCRIPTOR_BLOCK_RW_MODE_SINGLE;
rx_desc->owner = DMA2D_DESCRIPTOR_BUFFER_OWNER_DMA;
esp_cache_msync(tx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
esp_cache_msync(rx_desc, dma_desc_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE);
}
static bool dma2d_memcpy_done_cb(dma2d_channel_handle_t dma2d_chan, dma2d_event_data_t *event_data, void *user_data)
{
bool need_yield = false;
esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data;
if (mcp->memcpy_done_cb) {
need_yield = mcp->memcpy_done_cb(mcp, NULL, mcp->cb_args);
}
return need_yield;
}
static bool dma2d_job_picked_cb(uint32_t num_chans, const dma2d_trans_channel_info_t *dma2d_chans, void *user_data)
{
esp_async_fbcpy_context_t* mcp = (esp_async_fbcpy_context_t*)user_data;
dma2d_channel_handle_t tx_chan = NULL;
dma2d_channel_handle_t rx_chan = NULL;
for (uint32_t i = 0; i < num_chans; i++) {
if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_TX) {
tx_chan = dma2d_chans[i].chan;
}
if (dma2d_chans[i].dir == DMA2D_CHANNEL_DIRECTION_RX) {
rx_chan = dma2d_chans[i].chan;
}
}
dma2d_trigger_t trig_periph = {
.periph = DMA2D_TRIG_PERIPH_M2M,
.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_TX,
};
dma2d_connect(tx_chan, &trig_periph);
trig_periph.periph_sel_id = SOC_DMA2D_TRIG_PERIPH_M2M_RX;
dma2d_connect(rx_chan, &trig_periph);
dma2d_rx_event_callbacks_t dma_cbs = {
.on_recv_eof = dma2d_memcpy_done_cb,
};
dma2d_register_rx_event_callbacks(rx_chan, &dma_cbs, mcp);
dma2d_set_desc_addr(tx_chan, (intptr_t)(mcp->tx_desc));
dma2d_set_desc_addr(rx_chan, (intptr_t)(mcp->rx_desc));
dma2d_start(tx_chan);
dma2d_start(rx_chan);
return false;
}
esp_err_t esp_async_fbcpy(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_trans_desc_t* transaction, esp_async_fbcpy_event_callback_t memcpy_done_cb, void *cb_args)
{
ESP_RETURN_ON_FALSE(mcp && transaction, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
mcp->memcpy_done_cb = memcpy_done_cb;
mcp->cb_args = cb_args;
// mount the data to the DMA descriptor
async_memcpy_setup_dma2d_descriptor(mcp, transaction);
// submit the DMA2D request
dma2d_trans_config_t dma2d_trans_conf = {
.tx_channel_num = 1,
.rx_channel_num = 1,
.channel_flags = DMA2D_CHANNEL_FUNCTION_FLAG_SIBLING,
.on_job_picked = dma2d_job_picked_cb,
.user_config = mcp,
};
ESP_RETURN_ON_ERROR(dma2d_enqueue(mcp->client, &dma2d_trans_conf, mcp->trans_desc), TAG, "DMA2D enqueue failed");
return ESP_OK;
}

View File

@ -69,6 +69,8 @@ extern "C" {
// Peripheral selection ID register field width
#define DMA2D_LL_CHANNEL_PERIPH_SEL_BIT_WIDTH (3)
#define DMA2D_LL_DESC_ALIGNMENT 8 // Descriptor must be aligned to 8 bytes
///////////////////////////////////// Common /////////////////////////////////////////
/**
* @brief Enable the bus clock for 2D-DMA module

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