diff --git a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c index 5c6768d371..9c70792029 100644 --- a/components/esp_lcd/dsi/esp_lcd_panel_dpi.c +++ b/components/esp_lcd/dsi/esp_lcd_panel_dpi.c @@ -43,6 +43,7 @@ struct esp_lcd_dpi_panel_t { 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 + esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; // Callback invoked when one refresh operation finished (kinda like a vsync end) void *user_ctx; // User context for the callback }; @@ -69,16 +70,29 @@ static bool async_fbcpy_done_cb(esp_async_fbcpy_handle_t mcp, esp_async_fbcpy_ev } 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) +static bool dma_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_trans_done_event_data_t *event_data, void *user_data) { - dw_gdma_lli_handle_t lli = event_data->invalid_lli; + bool yield_needed = false; + esp_lcd_dpi_panel_t *dpi_panel = (esp_lcd_dpi_panel_t *)user_data; + uint8_t fb_index = dpi_panel->cur_fb_index; + dw_gdma_link_list_handle_t link_list = dpi_panel->link_lists[fb_index]; + + // restart the DMA transfer, keep refreshing the LCD dw_gdma_block_markers_t markers = { - .is_valid = true, // mark the block as valid so that the DMA can continue the transfer + .is_valid = true, + .is_last = true, }; - dw_gdma_lli_set_block_markers(lli, markers); - // after the item is marked as valid again, tell the DMA to continue the transfer - dw_gdma_channel_continue(chan); - return false; + dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers); + dw_gdma_channel_use_link_list(chan, link_list); + dw_gdma_channel_enable_ctrl(chan, true); + + // the DMA descriptor is large enough to carry a whole frame buffer, so this event can also be treated as a fake "vsync end" + if (dpi_panel->on_refresh_done) { + if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) { + yield_needed = true; + } + } + return yield_needed; } // Please note, errors happened in this function is just propagated to the caller @@ -109,8 +123,8 @@ static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel) // create DMA link lists dw_gdma_link_list_config_t link_list_config = { - .num_items = 1, // NOTE: we assume one DMA link item can carry the whole image - .link_type = DW_GDMA_LINKED_LIST_TYPE_CIRCULAR, + .num_items = DPI_PANEL_LLI_PER_FRAME, + .link_type = DW_GDMA_LINKED_LIST_TYPE_SINGLY, }; 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"); @@ -119,9 +133,9 @@ static esp_err_t dpi_panel_create_dma_link(esp_lcd_dpi_panel_t *dpi_panel) // register DMA ISR callbacks dw_gdma_event_callbacks_t dsi_dma_cbs = { - .on_invalid_block = dma_list_invalid_block_cb, + .on_full_trans_done = dma_trans_done_cb, }; - ESP_RETURN_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, NULL), TAG, "register DMA callbacks failed"); + ESP_RETURN_ON_ERROR(dw_gdma_channel_register_event_callbacks(dma_chan, &dsi_dma_cbs, dpi_panel), TAG, "register DMA callbacks failed"); return ESP_OK; } @@ -356,6 +370,7 @@ static esp_err_t dpi_panel_init(esp_lcd_panel_t *panel) 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, + .is_last = true, }; dw_gdma_lli_set_block_markers(dw_gdma_link_list_get_item(link_list, 0), markers); } @@ -410,11 +425,6 @@ static esp_err_t dpi_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int // the buffer to be flushed is still within the frame buffer, so even an unaligned address is OK esp_cache_msync(cache_sync_start, cache_sync_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) { @@ -506,6 +516,7 @@ esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t pane 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->on_refresh_done = cbs->on_refresh_done; dpi_panel->user_ctx = user_ctx; return ESP_OK; 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 04e570b8c4..bd7e50d284 100644 --- a/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h +++ b/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h @@ -140,20 +140,35 @@ 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 + * @brief A general function callback prototype for DPI panel driver * * @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); +typedef bool (*esp_lcd_dpi_panel_general_cb_t)(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); + +/** + * @brief Declare the prototype of the function that will be invoked + * when driver finishes coping user's color buffer to frame buffer + */ +typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_color_trans_done_cb_t; + +/** + * @brief Declare the prototype of the function that will be invoked + * when driver finishes refreshing the frame buffer to the screen + */ +typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_refresh_done_cb_t; /** * @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_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal frame buffer. + This is an indicator that the draw buffer can be recycled safely. + But doesn't mean the draw buffer finishes the refreshing to the screen. */ + esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done; /*!< Invoked when the internal frame buffer finishes refreshing to the screen */ } esp_lcd_dpi_panel_event_callbacks_t; /** diff --git a/components/esp_lcd/dsi/mipi_dsi_priv.h b/components/esp_lcd/dsi/mipi_dsi_priv.h index 1b650273d9..cdf89e30e3 100644 --- a/components/esp_lcd/dsi/mipi_dsi_priv.h +++ b/components/esp_lcd/dsi/mipi_dsi_priv.h @@ -26,6 +26,8 @@ #define DPI_PANEL_MAX_FB_NUM 3 // maximum number of supported frame buffers for DPI panel +#define DPI_PANEL_LLI_PER_FRAME 1 // NOTE: we assume ONE DMA link item can carry the WHOLE image (1920*1080) + #ifdef __cplusplus extern "C" { #endif diff --git a/examples/peripherals/lcd/mipi_dsi/README.md b/examples/peripherals/lcd/mipi_dsi/README.md index dfb2bc6945..8b7c760374 100644 --- a/examples/peripherals/lcd/mipi_dsi/README.md +++ b/examples/peripherals/lcd/mipi_dsi/README.md @@ -50,6 +50,8 @@ Before testing your LCD, you also need to read your LCD spec carefully, and then 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. +* Choose if you want to `Monitor FPS by GPIO`. If you choose `Yes`, then you can attach an oscilloscope or logic analyzer to the GPIO pin to monitor the FPS of the display. + Please note, the actual FPS should be **double** the square wave frequency. ### Build and Flash diff --git a/examples/peripherals/lcd/mipi_dsi/main/Kconfig.projbuild b/examples/peripherals/lcd/mipi_dsi/main/Kconfig.projbuild index 8fa9b219de..fd8d96bf12 100644 --- a/examples/peripherals/lcd/mipi_dsi/main/Kconfig.projbuild +++ b/examples/peripherals/lcd/mipi_dsi/main/Kconfig.projbuild @@ -5,4 +5,11 @@ menu "Example Configuration" 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. + + config EXAMPLE_MONITOR_FPS_BY_GPIO + bool "Monitor FPS by GPIO" + default y + help + Enable this option, you can visualize the FPS by attaching a logic analyzer to a specific GPIO. + The GPIO will output a square wave with the frequency of FPS/2. endmenu diff --git a/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c b/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c index 292266adb7..859895d501 100644 --- a/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c +++ b/examples/peripherals/lcd/mipi_dsi/main/mipi_dsi_lcd_example_main.c @@ -51,6 +51,10 @@ static const char *TAG = "example"; #define EXAMPLE_PIN_NUM_BK_LIGHT -1 #define EXAMPLE_PIN_NUM_LCD_RST -1 +#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO +#define EXAMPLE_PIN_NUM_FPS_MONITOR 20 // Monitor the FPS by toggling the GPIO +#endif + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////// Please update the following configuration according to your Application /////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -123,6 +127,17 @@ static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, esp_lc return false; } +#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO +static bool example_monitor_fps(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + static int io_level = 0; + // please note, the real FPS should be 2*frequency of this GPIO toggling + gpio_set_level(EXAMPLE_PIN_NUM_FPS_MONITOR, io_level); + io_level = !io_level; + return false; +} +#endif + 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 @@ -155,8 +170,23 @@ static void example_bsp_set_lcd_backlight(uint32_t level) #endif } +#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO +static void example_bsp_init_fps_monitor_io(void) +{ + gpio_config_t monitor_io_conf = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_FPS_MONITOR, + }; + ESP_ERROR_CHECK(gpio_config(&monitor_io_conf)); +} +#endif + void app_main(void) { +#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO + example_bsp_init_fps_monitor_io(); +#endif + example_bsp_enable_dsi_phy_power(); example_bsp_init_lcd_backlight(); example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL); @@ -246,6 +276,9 @@ void app_main(void) ESP_LOGI(TAG, "Register DPI panel event callback for LVGL flush ready notification"); esp_lcd_dpi_panel_event_callbacks_t cbs = { .on_color_trans_done = example_notify_lvgl_flush_ready, +#if CONFIG_EXAMPLE_MONITOR_FPS_BY_GPIO + .on_refresh_done = example_monitor_fps, +#endif }; ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, display));