From cf20dfee1ddd95eaca70136db72f4edc90574b21 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 25 Jan 2024 19:00:44 +0800 Subject: [PATCH] feat(mipi_dsi): support multiple frame buffers --- components/esp_lcd/dsi/esp_lcd_panel_dpi.c | 169 +++++++++++++----- .../esp_lcd/dsi/include/esp_lcd_mipi_dsi.h | 15 ++ components/esp_lcd/dsi/mipi_dsi_priv.h | 2 + .../esp_lcd/include/esp_lcd_panel_ops.h | 8 +- .../interface/esp_lcd_panel_interface.h | 8 +- components/esp_lcd/src/esp_lcd_panel_ops.c | 8 +- .../mipi_dsi_lcd/main/test_mipi_dsi_panel.c | 79 ++++++++ 7 files changed, 232 insertions(+), 57 deletions(-) diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index b6396c1e6d..cd44f601cc 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -30,25 +30,27 @@ struct esp_lcd_dpi_panel_t { esp_lcd_panel_t base; // Base class of generic lcd panel esp_lcd_dsi_bus_handle_t bus; // DSI bus handle uint8_t virtual_channel; // Virtual channel ID, index from 0 - void *frame_buffer; // Frame buffer + uint8_t cur_fb_index; // Current frame buffer index + uint8_t num_fbs; // Number of frame buffers + uint8_t *fbs[DPI_PANEL_MAX_FB_NUM]; // Frame buffers uint32_t h_pixels; // Horizontal pixels 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 + dw_gdma_link_list_handle_t link_lists[DPI_PANEL_MAX_FB_NUM]; // 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 + 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; + esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)cb_args; // release the draw semaphore first BaseType_t task_woken = pdFALSE; @@ -79,9 +81,10 @@ static bool dma_list_invalid_block_cb(dw_gdma_channel_handle_t chan, const dw_gd return false; } +// Please note, errors happened in this function is just propagated to the caller +// dpi_panel_del() is actually doing the error handling static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel) { - esp_err_t ret = ESP_OK; dw_gdma_channel_handle_t dma_chan = NULL; dw_gdma_link_list_handle_t link_list = NULL; // sending image stream from memory to the DSI bridge @@ -101,38 +104,31 @@ static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel) .flow_controller = DW_GDMA_FLOW_CTRL_DST, // the DSI bridge as the DMA flow controller .chan_priority = 1, }; - ESP_GOTO_ON_ERROR(dw_gdma_new_channel(&dma_alloc_config, &dma_chan), err, TAG, "create DMA channel failed"); + ESP_RETURN_ON_ERROR(dw_gdma_new_channel(&dma_alloc_config, &dma_chan), TAG, "create DMA channel failed"); + dpi_panel->dma_chan = dma_chan; - // create a linked list for the DMA channel + // create DMA link lists dw_gdma_link_list_config_t link_list_config = { - .num_items = 1, // Assume one link item can carry the whole image + .num_items = 1, // NOTE: we assume one DMA link item can carry the whole image .link_type = DW_GDMA_LINKED_LIST_TYPE_CIRCULAR, }; - ESP_GOTO_ON_ERROR(dw_gdma_new_link_list(&link_list_config, &link_list), err, TAG, "create DMA link list failed"); + for (int i = 0; i < dpi_panel->num_fbs; i++) { + ESP_RETURN_ON_ERROR(dw_gdma_new_link_list(&link_list_config, &link_list), TAG, "create DMA link list failed"); + dpi_panel->link_lists[i] = link_list; + } // register DMA ISR callbacks dw_gdma_event_callbacks_t dsi_dma_cbs = { .on_invalid_block = dma_list_invalid_block_cb, }; - ESP_GOTO_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, NULL), err, TAG, "register DMA callbacks failed"); + ESP_RETURN_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, NULL), TAG, "register DMA callbacks failed"); - dpi_panel->dma_chan = dma_chan; - dpi_panel->link_list = link_list; return ESP_OK; -err: - if (dma_chan) { - dw_gdma_del_channel(dma_chan); - } - if (link_list) { - dw_gdma_del_link_list(link_list); - } - return ret; } 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) { 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"); @@ -141,6 +137,13 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_ #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 + size_t num_fbs = panel_config->num_fbs; + // if the user doesn't specify the number of frame buffers, then fallback to use one frame buffer + if (num_fbs == 0) { + num_fbs = 1; + } + ESP_RETURN_ON_FALSE(num_fbs <= DPI_PANEL_MAX_FB_NUM, ESP_ERR_INVALID_ARG, TAG, "num_fbs not within [1,%d]", DPI_PANEL_MAX_FB_NUM); + int bus_id = bus->bus_id; mipi_dsi_hal_context_t *hal = &bus->hal; @@ -149,6 +152,7 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_ dpi_panel->virtual_channel = panel_config->virtual_channel; dpi_panel->pixel_format = panel_config->pixel_format; dpi_panel->bus = bus; + dpi_panel->num_fbs = num_fbs; // allocate frame buffer from PSRAM size_t bytes_per_pixel = 0; @@ -167,15 +171,19 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_ // DMA doesn't have requirement on the buffer alignment, but the cache does uint32_t alignment = cache_line_size; size_t frame_buffer_size = panel_config->video_timing.h_size * panel_config->video_timing.v_size * bytes_per_pixel; - frame_buffer = heap_caps_aligned_calloc(alignment, 1, frame_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); - ESP_GOTO_ON_FALSE(frame_buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for frame buffer"); - dpi_panel->frame_buffer = frame_buffer; + uint8_t *frame_buffer = NULL; + for (int i = 0; i < num_fbs; i++) { + frame_buffer = heap_caps_aligned_calloc(alignment, 1, frame_buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); + ESP_GOTO_ON_FALSE(frame_buffer, ESP_ERR_NO_MEM, err, TAG, "no memory for frame buffer"); + dpi_panel->fbs[i] = frame_buffer; + ESP_LOGD(TAG, "fb[%d] @%p", i, frame_buffer); + // 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"); + } dpi_panel->frame_buffer_size = frame_buffer_size; dpi_panel->bytes_per_pixel = bytes_per_pixel; dpi_panel->h_pixels = panel_config->video_timing.h_size; dpi_panel->v_pixels = panel_config->video_timing.v_size; - // 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) { @@ -237,7 +245,7 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_ panel_config->video_timing.vsync_back_porch, panel_config->video_timing.v_size, panel_config->video_timing.vsync_front_porch); - mipi_dsi_brg_ll_set_num_pixel_bits(hal->bridge, panel_config->video_timing.h_size * panel_config->video_timing.v_size * 16); + mipi_dsi_brg_ll_set_num_pixel_bits(hal->bridge, panel_config->video_timing.h_size * panel_config->video_timing.v_size * bytes_per_pixel * 8); mipi_dsi_brg_ll_set_underrun_discard_count(hal->bridge, panel_config->video_timing.h_size); // let the DSI bridge as the DMA flow controller mipi_dsi_brg_ll_set_flow_controller(hal->bridge, MIPI_DSI_LL_FLOW_CONTROLLER_BRIDGE); @@ -251,7 +259,7 @@ esp_err_t esp_lcd_new_panel_dpi(esp_lcd_dsi_bus_handle_t bus, const esp_lcd_dpi_ dpi_panel->base.init = dpi_panel_init; dpi_panel->base.draw_bitmap = dpi_panel_draw_bitmap; *ret_panel = &dpi_panel->base; - ESP_LOGD(TAG, "new dpi panel @%p, fb@%p", dpi_panel, dpi_panel->frame_buffer); + ESP_LOGD(TAG, "dpi panel created @%p", dpi_panel); return ESP_OK; err: if (dpi_panel) { @@ -276,11 +284,15 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel) if (dpi_panel->dma_chan) { dw_gdma_del_channel(dpi_panel->dma_chan); } - if (dpi_panel->frame_buffer) { - free(dpi_panel->frame_buffer); + for (int i = 0; i < DPI_PANEL_MAX_FB_NUM; i++) { + if (dpi_panel->fbs[i]) { + free(dpi_panel->fbs[i]); + } } - if (dpi_panel->link_list) { - dw_gdma_del_link_list(dpi_panel->link_list); + for (int i = 0; i < DPI_PANEL_MAX_FB_NUM; i++) { + if (dpi_panel->link_lists[i]) { + dw_gdma_del_link_list(dpi_panel->link_lists[i]); + } } if (dpi_panel->fbcpy_handle) { esp_async_fbcpy_uninstall(dpi_panel->fbcpy_handle); @@ -292,18 +304,34 @@ static esp_err_t dpi_panel_del(esp_lcd_panel_t *panel) return ESP_OK; } +esp_err_t esp_lcd_dpi_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...) +{ + ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base); + ESP_RETURN_ON_FALSE(fb_num && fb_num <= dpi_panel->num_fbs, ESP_ERR_INVALID_ARG, TAG, "invalid frame buffer number"); + void **fb_itor = fb0; + va_list args; + va_start(args, fb0); + for (uint32_t i = 0; i < fb_num; i++) { + if (fb_itor) { + *fb_itor = dpi_panel->fbs[i]; + fb_itor = va_arg(args, void **); + } + } + va_end(args); + return ESP_OK; +} + static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel) { esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base); esp_lcd_dsi_bus_handle_t bus = dpi_panel->bus; mipi_dsi_hal_context_t *hal = &bus->hal; dw_gdma_channel_handle_t dma_chan = dpi_panel->dma_chan; - dw_gdma_link_list_handle_t link_list = dpi_panel->link_list; + dw_gdma_link_list_handle_t link_list = NULL; - ESP_RETURN_ON_ERROR(dw_gdma_channel_use_link_list(dma_chan, link_list), TAG, "use DMA link list failed"); dw_gdma_block_transfer_config_t dma_transfer_config = { .src = { - .addr = (uint32_t)(dpi_panel->frame_buffer), .burst_mode = DW_GDMA_BURST_MODE_INCREMENT, .burst_items = DW_GDMA_BURST_ITEMS_512, .burst_len = 16, @@ -318,11 +346,21 @@ static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel) }, .size = dpi_panel->frame_buffer_size * 8 / 64, }; - dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 0), &dma_transfer_config); - dw_gdma_block_markers_t markers = { - .is_valid = true, - }; - dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers); + for (int i = 0; i < dpi_panel->num_fbs; i++) { + link_list = dpi_panel->link_lists[i]; + dma_transfer_config.src.addr = (uint32_t)(dpi_panel->fbs[i]); + dw_gdma_lli_config_transfer(dw_gdma_link_list_get_item(link_list, 0), &dma_transfer_config); + dw_gdma_block_markers_t markers = { + .is_valid = true, + }; + dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers); + } + + // by default, we use the fb0 as the first working frame buffer + dpi_panel->cur_fb_index = 0; + link_list = dpi_panel->link_lists[0]; + dw_gdma_channel_use_link_list(dma_chan, link_list); + // enable the DMA channel dw_gdma_channel_enable_ctrl(dma_chan, true); // enable the video mode @@ -340,12 +378,45 @@ static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel) static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) { esp_lcd_dpi_panel_t *dpi_panel = __containerof(panel, esp_lcd_dpi_panel_t, base); - uint8_t *frame_buffer = dpi_panel->frame_buffer; + uint8_t cur_fb_index = dpi_panel->cur_fb_index; + uint8_t *frame_buffer = dpi_panel->fbs[cur_fb_index]; + uint8_t *draw_buffer = (uint8_t *)color_data; size_t frame_buffer_size = dpi_panel->frame_buffer_size; - if (!dpi_panel->fbcpy_handle) { + 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 + // if so, we don't need to copy the data, just do cache write back + if (draw_buffer >= dpi_panel->fbs[0] && draw_buffer < dpi_panel->fbs[0] + frame_buffer_size) { + draw_buf_fb_index = 0; + } else if (draw_buffer >= dpi_panel->fbs[1] && draw_buffer < dpi_panel->fbs[1] + frame_buffer_size) { + draw_buf_fb_index = 1; + } else if (draw_buffer >= dpi_panel->fbs[2] && draw_buffer < dpi_panel->fbs[2] + frame_buffer_size) { + draw_buf_fb_index = 2; + } else { + do_copy = true; + } + + if (!do_copy) { // no copy, just do cache memory write back + ESP_LOGD(TAG, "draw buffer is in frame buffer memory range, do cache write back only"); + size_t draw_buf_write_back_size = (y_end - y_start) * dpi_panel->h_pixels * dpi_panel->bytes_per_pixel; + uint8_t *cache_sync_start = dpi_panel->fbs[draw_buf_fb_index] + (y_start * dpi_panel->h_pixels) * dpi_panel->bytes_per_pixel; + esp_cache_msync(cache_sync_start, draw_buf_write_back_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED); + + // update the link connections for all DMA link lists, make draw_buf_fb_index take effect automatically in the next DMA loop + for (int i = 0; i < dpi_panel->num_fbs; i++) { + dw_gdma_lli_set_next(dw_gdma_link_list_get_item(dpi_panel->link_lists[i], 0), + dw_gdma_link_list_get_item(dpi_panel->link_lists[draw_buf_fb_index], 0)); + } + dpi_panel->cur_fb_index = draw_buf_fb_index; + // 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 if (!dpi_panel->fbcpy_handle) { // copy by CPU + ESP_LOGD(TAG, "copy draw buffer by CPU"); size_t bytes_per_pixel = dpi_panel->bytes_per_pixel; - const uint8_t *from = (const uint8_t *)color_data; + const uint8_t *from = draw_buffer; 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; @@ -359,17 +430,19 @@ static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int 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 + } else { // copy by DMA2D + ESP_LOGD(TAG, "copy draw buffer by DMA2D"); + // ensure 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 + // Note, the user draw buffer should be 1D array, and contiguous in memory, no stride 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_cache_msync(draw_buffer, 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 = draw_buffer, + .dst_buffer = (void *)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, diff --git a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h index 969bc64ff8..04e570b8c4 100644 --- a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h +++ b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h @@ -84,6 +84,8 @@ typedef struct { 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 that used by the MIPI LCD device */ + uint8_t num_fbs; /*!< Number of screen-sized frame buffers that allocated by the driver + By default (set to either 0 or 1) only one frame buffer will be created */ esp_lcd_video_timing_t video_timing; /*!< Video timing */ /// Extra configuration flags for MIPI DSI DPI panel struct extra_flags { @@ -106,6 +108,19 @@ typedef struct { */ 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); +/** + * @brief Get the address of the frame buffer(s) that allocated by the driver + * + * @param[in] dpi_panel MIPI DPI panel handle, returned from esp_lcd_new_panel_dpi() + * @param[in] fb_num Number of frame buffer(s) to get. This value must be the same as the number of the followed parameters. + * @param[out] fb0 Address of the frame buffer 0 (first frame buffer) + * @param[out] ... List of other frame buffers if any + * @return + * - ESP_ERR_INVALID_ARG: Get frame buffer address failed because of invalid argument + * - ESP_OK: Get frame buffer address successfully + */ +esp_err_t esp_lcd_dpi_panel_get_frame_buffer(esp_lcd_panel_handle_t dpi_panel, uint32_t fb_num, void **fb0, ...); + /** * @brief Set pre-defined pattern to the screen for testing or debugging purpose * diff --git a/components/esp_lcd/dsi/mipi_dsi_priv.h b/components/esp_lcd/dsi/mipi_dsi_priv.h index f16e0d3fb7..1b650273d9 100644 --- a/components/esp_lcd/dsi/mipi_dsi_priv.h +++ b/components/esp_lcd/dsi/mipi_dsi_priv.h @@ -24,6 +24,8 @@ #define DSI_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#define DPI_PANEL_MAX_FB_NUM 3 // maximum number of supported frame buffers for DPI panel + #ifdef __cplusplus extern "C" { #endif diff --git a/components/esp_lcd/include/esp_lcd_panel_ops.h b/components/esp_lcd/include/esp_lcd_panel_ops.h index 56b21a1ad4..47e22c78ca 100644 --- a/components/esp_lcd/include/esp_lcd_panel_ops.h +++ b/components/esp_lcd/include/esp_lcd_panel_ops.h @@ -48,10 +48,10 @@ esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel); * @brief Draw bitmap on LCD panel * * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_start Start index on x-axis (x_start included) - * @param[in] y_start Start index on y-axis (y_start included) - * @param[in] x_end End index on x-axis (x_end not included) - * @param[in] y_end End index on y-axis (y_end not included) + * @param[in] x_start Start pixel index in the target frame buffer, on x-axis (x_start is included) + * @param[in] y_start Start pixel index in the target frame buffer, on y-axis (y_start is included) + * @param[in] x_end End pixel index in the target frame buffer, on x-axis (x_end is not included) + * @param[in] y_end End pixel index in the target frame buffer, on y-axis (y_end is not included) * @param[in] color_data RGB color data that will be dumped to the specific window range * @return * - ESP_OK on success diff --git a/components/esp_lcd/interface/esp_lcd_panel_interface.h b/components/esp_lcd/interface/esp_lcd_panel_interface.h index ff5d289d79..d0041ec733 100644 --- a/components/esp_lcd/interface/esp_lcd_panel_interface.h +++ b/components/esp_lcd/interface/esp_lcd_panel_interface.h @@ -49,10 +49,10 @@ struct esp_lcd_panel_t { * @brief Draw bitmap on LCD panel * * @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()` - * @param[in] x_start Start index on x-axis (x_start included) - * @param[in] y_start Start index on y-axis (y_start included) - * @param[in] x_end End index on x-axis (x_end not included) - * @param[in] y_end End index on y-axis (y_end not included) + * @param[in] x_start Start pixel index in the target frame buffer, on x-axis (x_start is included) + * @param[in] y_start Start pixel index in the target frame buffer, on y-axis (y_start is included) + * @param[in] x_end End pixel index in the target frame buffer, on x-axis (x_end is not included) + * @param[in] y_end End pixel index in the target frame buffer, on y-axis (y_end is not included) * @param[in] color_data RGB color data that will be dumped to the specific window range * @return * - ESP_OK on success diff --git a/components/esp_lcd/src/esp_lcd_panel_ops.c b/components/esp_lcd/src/esp_lcd_panel_ops.c index acef06ce4e..ef19e3d328 100644 --- a/components/esp_lcd/src/esp_lcd_panel_ops.c +++ b/components/esp_lcd/src/esp_lcd_panel_ops.c @@ -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 */ @@ -13,6 +13,7 @@ static const char *TAG = "lcd_panel"; esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel) { ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); + ESP_RETURN_ON_FALSE(panel->reset, ESP_ERR_NOT_SUPPORTED, TAG, "reset is not supported by this panel"); return panel->reset(panel); } @@ -38,30 +39,35 @@ esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, i esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y) { ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); + ESP_RETURN_ON_FALSE(panel->mirror, ESP_ERR_NOT_SUPPORTED, TAG, "mirror is not supported by this panel"); return panel->mirror(panel, mirror_x, mirror_y); } esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes) { ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); + ESP_RETURN_ON_FALSE(panel->swap_xy, ESP_ERR_NOT_SUPPORTED, TAG, "swap_xy is not supported by this panel"); return panel->swap_xy(panel, swap_axes); } esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap) { ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); + ESP_RETURN_ON_FALSE(panel->set_gap, ESP_ERR_NOT_SUPPORTED, TAG, "set_gap is not supported by this panel"); return panel->set_gap(panel, x_gap, y_gap); } esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data) { ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); + ESP_RETURN_ON_FALSE(panel->invert_color, ESP_ERR_NOT_SUPPORTED, TAG, "invert_color is not supported by this panel"); return panel->invert_color(panel, invert_color_data); } esp_err_t esp_lcd_panel_disp_on_off(esp_lcd_panel_handle_t panel, bool on_off) { ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle"); + ESP_RETURN_ON_FALSE(panel->disp_on_off, ESP_ERR_NOT_SUPPORTED, TAG, "disp_on_off is not supported by this panel"); return panel->disp_on_off(panel, on_off); } diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c index c2c3724f07..eec61a14b7 100644 --- a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c @@ -163,3 +163,82 @@ TEST_CASE("MIPI DSI draw bitmap (ILI9881C)", "[mipi_dsi]") test_bsp_disable_dsi_phy_power(); } + +TEST_CASE("MIPI DSI with multiple frame buffers (ILI9881C)", "[mipi_dsi]") +{ + esp_lcd_dsi_bus_handle_t mipi_dsi_bus; + esp_lcd_panel_io_handle_t mipi_dbi_io; + esp_lcd_panel_handle_t mipi_dpi_panel; + esp_lcd_panel_handle_t ili9881c_ctrl_panel; + + test_bsp_enable_dsi_phy_power(); + + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = 2, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = 1000, // 1000 Mbps + }; + TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io)); + + esp_lcd_panel_dev_config_t lcd_dev_config = { + .bits_per_pixel = 16, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .reset_gpio_num = -1, + }; + TEST_ESP_OK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_reset(ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_init(ili9881c_ctrl_panel)); + // turn on display + TEST_ESP_OK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true)); + + esp_lcd_dpi_panel_config_t dpi_config = { + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = MIPI_DSI_DPI_CLK_MHZ, + .virtual_channel = 0, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .num_fbs = 3, // the driver will allocate and maintain 3 frame buffers + .video_timing = { + .h_size = MIPI_DSI_LCD_H_RES, + .v_size = MIPI_DSI_LCD_V_RES, + .hsync_back_porch = MIPI_DSI_LCD_HBP, + .hsync_pulse_width = MIPI_DSI_LCD_HSYNC, + .hsync_front_porch = MIPI_DSI_LCD_HFP, + .vsync_back_porch = MIPI_DSI_LCD_VBP, + .vsync_pulse_width = MIPI_DSI_LCD_VSYNC, + .vsync_front_porch = MIPI_DSI_LCD_VFP, + }, + }; + TEST_ESP_OK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel)); + TEST_ESP_OK(esp_lcd_panel_init(mipi_dpi_panel)); + + uint16_t *fbs[3]; + TEST_ESP_OK(esp_lcd_dpi_panel_get_frame_buffer(mipi_dpi_panel, 3, (void **)&fbs[0], (void **)&fbs[1], (void **)&fbs[2])); + + for (int i = 0; i < 3; i++) { + uint16_t color_byte = rand() & 0xFFFF; + int x_start = rand() % (MIPI_DSI_LCD_H_RES - 100); + int y_start = rand() % (MIPI_DSI_LCD_V_RES - 100); + for (int j = y_start; j < y_start + 100; j++) { + for (int k = x_start; k < x_start + 100; k++) { + fbs[i][j * MIPI_DSI_LCD_H_RES + k] = color_byte; + } + } + esp_lcd_panel_draw_bitmap(mipi_dpi_panel, x_start, y_start, x_start + 100, y_start + 100, fbs[i]); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel)); + TEST_ESP_OK(esp_lcd_panel_del(ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io)); + TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus)); + + test_bsp_disable_dsi_phy_power(); +}