feat(lcd): support buffer switch interrupt

This commit is contained in:
Chen Jichang
2026-04-20 18:16:49 +08:00
parent 33589325c7
commit e1d48975fb
26 changed files with 337 additions and 104 deletions

View File

@@ -465,6 +465,12 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
gdma_group_t *group = pair->group;
gdma_hal_context_t *hal = &group->hal;
gdma_tx_channel_t *tx_chan = __containerof(dma_chan, gdma_tx_channel_t, base);
bool link_switch_event_supported = gdma_hal_is_tx_link_switch_event_supported(hal);
if (cbs->on_link_switch) {
ESP_RETURN_ON_FALSE(link_switch_event_supported,
ESP_ERR_NOT_SUPPORTED, TAG, "on_link_switch not supported");
}
if (dma_chan->flags.isr_cache_safe) {
if (cbs->on_trans_eof) {
@@ -475,6 +481,10 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_descr_err), ESP_ERR_INVALID_ARG,
TAG, "on_descr_err not in IRAM");
}
if (cbs->on_link_switch) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_link_switch), ESP_ERR_INVALID_ARG,
TAG, "on_link_switch not in IRAM");
}
if (user_data) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG,
TAG, "user context not in internal RAM");
@@ -488,6 +498,12 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
portENTER_CRITICAL(&pair->spinlock);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_EOF, cbs->on_trans_eof != NULL);
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_DESC_ERROR, cbs->on_descr_err != NULL);
#if GDMA_LL_EVENT_TX_LINK_SWITCH
if (link_switch_event_supported) {
gdma_hal_enable_intr(hal, pair->pair_id, GDMA_CHANNEL_DIRECTION_TX, GDMA_LL_EVENT_TX_LINK_SWITCH,
cbs->on_link_switch != NULL);
}
#endif // GDMA_LL_EVENT_TX_LINK_SWITCH
portEXIT_CRITICAL(&pair->spinlock);
memcpy(&tx_chan->cbs, cbs, sizeof(gdma_tx_event_callbacks_t));
@@ -601,6 +617,30 @@ esp_err_t gdma_reset(gdma_channel_handle_t dma_chan)
return ESP_OK;
}
esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan)
{
if (!dma_tx_chan || dma_tx_chan->direction != GDMA_CHANNEL_DIRECTION_TX) {
return ESP_ERR_INVALID_ARG;
}
gdma_pair_t *pair = dma_tx_chan->pair;
gdma_group_t *group = pair->group;
gdma_hal_context_t *hal = &group->hal;
gdma_tx_channel_t *tx_chan = __containerof(dma_tx_chan, gdma_tx_channel_t, base);
if (!gdma_hal_is_tx_link_switch_event_supported(hal)) {
return ESP_ERR_NOT_SUPPORTED;
}
if (!tx_chan->cbs.on_link_switch) {
return ESP_ERR_INVALID_STATE;
}
portENTER_CRITICAL_SAFE(&dma_tx_chan->spinlock);
gdma_hal_request_link_switch_event(hal, pair->pair_id, dma_tx_chan->direction);
portEXIT_CRITICAL_SAFE(&dma_tx_chan->spinlock);
return ESP_OK;
}
static void gdma_release_group_handle(gdma_group_t *group)
{
int group_id = group->group_id;
@@ -868,6 +908,12 @@ void gdma_default_tx_isr(void *args)
if ((intr_status & GDMA_LL_EVENT_TX_DESC_ERROR) && tx_chan->cbs.on_descr_err) {
need_yield |= tx_chan->cbs.on_descr_err(&tx_chan->base, NULL, tx_chan->user_data);
}
#if GDMA_LL_EVENT_TX_LINK_SWITCH
if (gdma_hal_is_tx_link_switch_event_supported(hal) &&
(intr_status & GDMA_LL_EVENT_TX_LINK_SWITCH) && tx_chan->cbs.on_link_switch) {
need_yield |= tx_chan->cbs.on_link_switch(&tx_chan->base, NULL, tx_chan->user_data);
}
#endif // GDMA_LL_EVENT_TX_LINK_SWITCH
if (need_yield) {
portYIELD_FROM_ISR();
}
@@ -890,7 +936,7 @@ static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan)
#endif
intr_handle_t intr = NULL;
ret = esp_intr_alloc_intrstatus(gdma_periph_signals.groups[group->group_id].pairs[pair_id].rx_irq_id, isr_flags,
gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX), GDMA_LL_RX_EVENT_MASK,
gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_RX), hal->priv_data->rx_event_mask,
gdma_default_rx_isr, rx_chan, &intr);
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed");
rx_chan->base.intr = intr;
@@ -922,7 +968,7 @@ static esp_err_t gdma_install_tx_interrupt(gdma_tx_channel_t *tx_chan)
#endif
intr_handle_t intr = NULL;
ret = esp_intr_alloc_intrstatus(gdma_periph_signals.groups[group->group_id].pairs[pair_id].tx_irq_id, isr_flags,
gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX), GDMA_LL_TX_EVENT_MASK,
gdma_hal_get_intr_status_reg(hal, pair_id, GDMA_CHANNEL_DIRECTION_TX), hal->priv_data->tx_event_mask,
gdma_default_tx_isr, tx_chan, &intr);
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc interrupt failed");
tx_chan->base.intr = intr;

View File

@@ -68,6 +68,7 @@ typedef bool (*gdma_event_callback_t)(gdma_channel_handle_t dma_chan, gdma_event
typedef struct {
gdma_event_callback_t on_trans_eof; /*!< Invoked when TX engine meets EOF descriptor */
gdma_event_callback_t on_descr_err; /*!< Invoked when DMA encounters a descriptor error */
gdma_event_callback_t on_link_switch; /*!< Invoked when TX link list switches to a new descriptor chain */
} gdma_tx_event_callbacks_t;
/**
@@ -281,6 +282,26 @@ esp_err_t gdma_get_group_channel_id(gdma_channel_handle_t dma_chan, int *group_i
*/
esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_tx_event_callbacks_t *cbs, void *user_data);
/**
* @brief Request an interrupt when the TX channel switches to a new descriptor chain
*
* @note This API is only available on targets that support TX link switch interrupt.
* @note Register the `on_link_switch` callback by `gdma_register_tx_event_callbacks()` before calling this API.
* @note The TX EOF event indicates that GDMA has reached an EOF descriptor. By contrast, the TX link switch
* event indicates that GDMA has started using the next descriptor chain after a link update.
* GDMA can prefetch descriptors from the updated link before the EOF event, so EOF doesn't always mean
* the previous descriptor chain is no longer referenced by GDMA. The link switch event is needed when
* the caller must know that the previous descriptor chain can be reused safely.
*
* @param[in] dma_tx_chan GDMA TX channel handle
* @return
* - ESP_OK: Request link switch event successfully
* - ESP_ERR_INVALID_ARG: Invalid argument
* - ESP_ERR_NOT_SUPPORTED: Link switch event is not supported
* - ESP_ERR_INVALID_STATE: Link switch callback is not registered
*/
esp_err_t gdma_request_link_switch_event(gdma_channel_handle_t dma_tx_chan);
/**
* @brief Set GDMA event callbacks for RX channel
* @note This API will install GDMA interrupt service for the channel internally

View File

@@ -20,24 +20,28 @@ entries:
gdma_hal_top: gdma_hal_clear_intr (noflash)
gdma_hal_top: gdma_hal_read_intr_status (noflash)
gdma_hal_top: gdma_hal_get_eof_desc_addr (noflash)
gdma_hal_top: gdma_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AHB-DMA version 1
if SOC_AHB_GDMA_VERSION = 1:
gdma_hal_ahb_v1: gdma_ahb_hal_clear_intr (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_read_intr_status (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_get_eof_desc_addr (noflash)
gdma_hal_ahb_v1: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AHB-DMA version 2
if SOC_AHB_GDMA_VERSION = 2:
gdma_hal_ahb_v2: gdma_ahb_hal_clear_intr (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_read_intr_status (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_get_eof_desc_addr (noflash)
gdma_hal_ahb_v2: gdma_ahb_hal_is_tx_link_switch_event_supported (noflash)
# GDMA implementation layer for AXI-DMA
if SOC_AXI_GDMA_SUPPORTED = y:
gdma_hal_axi: gdma_axi_hal_clear_intr (noflash)
gdma_hal_axi: gdma_axi_hal_read_intr_status (noflash)
gdma_hal_axi: gdma_axi_hal_get_eof_desc_addr (noflash)
gdma_hal_axi: gdma_axi_hal_is_tx_link_switch_event_supported (noflash)
# put GDMA control HAL functions in IRAM
if GDMA_CTRL_FUNC_IN_IRAM = y:

View File

@@ -41,7 +41,8 @@ struct esp_lcd_dpi_panel_t {
SemaphoreHandle_t draw_sem; // A semaphore used to synchronize the draw operations when DMA2D is used
esp_pm_lock_handle_t pm_lock; // Power management lock
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)
esp_lcd_dpi_panel_frame_buf_complete_cb_t on_frame_buf_complete; // Callback invoked when the frame buffer can be reused safely
esp_lcd_dpi_panel_vsync_cb_t on_vsync; // VSYNC event callback
void *user_ctx; // User context for the callback
};
@@ -83,10 +84,16 @@ bool mipi_dsi_dma_trans_done_cb(dw_gdma_channel_handle_t chan, const dw_gdma_tra
dw_gdma_channel_use_link_list(chan, link_list);
dw_gdma_channel_enable_ctrl(chan, true);
if (dpi_panel->on_frame_buf_complete) {
if (dpi_panel->on_frame_buf_complete(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
yield_needed = true;
}
}
#if !MIPI_DSI_BRG_LL_EVENT_VSYNC
// 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)) {
if (dpi_panel->on_vsync) {
if (dpi_panel->on_vsync(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
yield_needed = true;
}
}
@@ -109,8 +116,8 @@ void mipi_dsi_bridge_isr_handler(void *args)
ESP_DRAM_LOGE(TAG, "can't fetch data from external memory fast enough, underrun happens");
}
if (intr_status & MIPI_DSI_BRG_LL_EVENT_VSYNC) {
if (dpi_panel->on_refresh_done) {
if (dpi_panel->on_refresh_done(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
if (dpi_panel->on_vsync) {
if (dpi_panel->on_vsync(&dpi_panel->base, NULL, dpi_panel->user_ctx)) {
portYIELD_FROM_ISR();
}
}
@@ -626,19 +633,23 @@ esp_err_t esp_lcd_dpi_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
if (cbs->on_color_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
}
if (cbs->on_refresh_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_refresh_done), ESP_ERR_INVALID_ARG, TAG, "on_refresh_done callback not in IRAM");
if (cbs->on_vsync) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM");
}
if (cbs->on_frame_buf_complete) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete callback not in IRAM");
}
if (user_ctx) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
#endif // CONFIG_LCD_DSI_ISR_CACHE_SAFE
dpi_panel->on_color_trans_done = cbs->on_color_trans_done;
dpi_panel->on_refresh_done = cbs->on_refresh_done;
dpi_panel->on_vsync = cbs->on_vsync;
dpi_panel->on_frame_buf_complete = cbs->on_frame_buf_complete;
dpi_panel->user_ctx = user_ctx;
// enable the vsync interrupt if the callback is provided
mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_refresh_done != NULL);
mipi_dsi_brg_ll_enable_interrupt(dpi_panel->bus->hal.bridge, MIPI_DSI_BRG_LL_EVENT_VSYNC, cbs->on_vsync != NULL);
return ESP_OK;
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -178,18 +178,36 @@ 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
* when the frame buffer can be reused safely
*
* @deprecated Use esp_lcd_dpi_panel_frame_buf_complete_cb_t instead.
*/
typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_refresh_done_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked when the LCD controller sends the VSYNC signal.
*/
typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_vsync_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked
* when the frame buffer can be reused safely
*/
typedef esp_lcd_dpi_panel_general_cb_t esp_lcd_dpi_panel_frame_buf_complete_cb_t;
/**
* @brief Type of LCD DPI panel callbacks
*/
typedef struct {
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.
esp_lcd_dpi_panel_color_trans_done_cb_t on_color_trans_done; /*!< Invoked when user's draw buffer copied to the 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 */
union {
esp_lcd_dpi_panel_refresh_done_cb_t on_refresh_done __attribute__((deprecated("Deprecated, use on_frame_buf_complete instead"))); /*!< Deprecated, use on_frame_buf_complete instead */
esp_lcd_dpi_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< Invoked when the frame buffer can be reused safely
when the frame buffer is the draw buffer. */
};
esp_lcd_dpi_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */
} esp_lcd_dpi_panel_event_callbacks_t;
/**

View File

@@ -49,6 +49,14 @@
#include "rgb_lcd_rotation_sw.h"
#include "esp_private/sleep_retention.h"
#if SOC_AXI_GDMA_SUPPORTED
#include "hal/axi_dma_ll.h"
#endif
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
#define RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT 1
#endif
// hardware issue workaround
#if CONFIG_IDF_TARGET_ESP32S3
#define RGB_LCD_NEEDS_SEPARATE_RESTART_LINK 1
@@ -95,6 +103,9 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const
static void lcd_rgb_panel_release_gpio(esp_rgb_panel_t *rgb_panel);
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel);
static void rgb_lcd_default_isr_handler(void *args);
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
static bool lcd_rgb_panel_link_switch_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data);
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
struct esp_rgb_panel_t {
esp_lcd_panel_t base; // Base class of generic lcd panel
@@ -135,7 +146,7 @@ struct esp_rgb_panel_t {
size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR
size_t expect_eof_count; // record the number of DMA EOF event we expected to receive
esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; // draw buffer completes
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the bounce buffer finish copying the entire frame
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the buffer can be reused safely
esp_lcd_rgb_panel_vsync_cb_t on_vsync; // VSYNC event callback
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // callback used to fill a bounce buffer rather than copying from the frame buffer
void *user_ctx; // Reserved user's data of callback functions
@@ -703,7 +714,9 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
// it's hard to know the time when the new frame buffer starts
gdma_link_concat(rgb_panel->dma_fb_links[i], -1, rgb_panel->dma_fb_links[rgb_panel->cur_fb_index], 0);
}
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
ESP_RETURN_ON_ERROR(gdma_request_link_switch_event(rgb_panel->dma_chan), TAG, "request link switch event failed");
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
}
}
return ESP_OK;
@@ -947,7 +960,7 @@ static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan,
portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
need_yield = lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[bb]);
} else {
// if not bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller
// Once the preload has already done, the buffer complete callback is not reliable.
if (rgb_panel->on_frame_buf_complete) {
if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
@@ -957,6 +970,24 @@ static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan,
return need_yield;
}
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
static IRAM_ATTR bool lcd_rgb_panel_link_switch_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
(void)dma_chan;
(void)event_data;
bool need_yield = false;
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)user_data;
if (rgb_panel->on_frame_buf_complete) {
if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
}
}
return need_yield;
}
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
{
// alloc DMA channel and connect to LCD peripheral
@@ -984,11 +1015,19 @@ static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
// get the memory alignment required by the DMA
gdma_get_alignment_constraints(rgb_panel->dma_chan, &rgb_panel->int_mem_align, &rgb_panel->ext_mem_align);
// register DMA EOF callback
// register DMA event callbacks
gdma_tx_event_callbacks_t cbs = {
#if RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
// if no bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller.
// But the dma link may have preloaded the next frame with the current buffer.
// So we need to wait for the GDMA link switch event to invoke the on_frame_buf_complete callback.
.on_trans_eof = (rgb_panel->flags.stream_mode && !rgb_panel->bb_size) ? NULL : lcd_rgb_panel_eof_handler,
.on_link_switch = lcd_rgb_panel_link_switch_handler,
#else
.on_trans_eof = lcd_rgb_panel_eof_handler,
#endif // RGB_LCD_USE_GDMA_LINK_SWITCH_EVENT
};
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed");
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA event callbacks failed");
return ESP_OK;
}

View File

@@ -93,7 +93,7 @@ typedef bool (*esp_lcd_rgb_panel_general_cb_t)(esp_lcd_panel_handle_t panel, con
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_draw_buf_complete_cb_t;
/**
* @brief Declare the prototype of the function that will be invoked when a whole frame buffer is sent to the LCD DMA.
* @brief Declare the prototype of the function that will be invoked when a whole frame buffer can be reused safely.
* The LCD hardware may still need some blank time to finish the refresh.
*/
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_frame_buf_complete_cb_t;
@@ -135,7 +135,7 @@ typedef struct {
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */
union {
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_bounce_frame_finish __attribute__((deprecated)); /*!< Bounce buffer finish callback. */
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< A whole frame buffer was just sent to the LCD DMA */
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< Invoked when the frame buffer can be reused safely */
};
} esp_lcd_rgb_panel_event_callbacks_t;

View File

@@ -18,7 +18,7 @@
#include "test_mipi_dsi_board.h"
#include "esp_lcd_ek79007.h"
IRAM_ATTR static bool test_rgb_panel_count_in_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
IRAM_ATTR static bool test_dpi_panel_count_in_callback(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
uint32_t *count = (uint32_t *)user_ctx;
*count = *count + 1;
@@ -91,7 +91,7 @@ TEST_CASE("MIPI DSI draw bitmap (EK79007) IRAM Safe", "[mipi_dsi]")
uint32_t callback_calls = 0;
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_refresh_done = test_rgb_panel_count_in_callback,
.on_frame_buf_complete = test_dpi_panel_count_in_callback,
};
TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, &callback_calls));

View File

@@ -23,8 +23,8 @@ extern "C" {
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
// for M2M mode, hardware will automatically assign peri_sel ID depends on the channel number (ch0: 10, ch1: 11, ch2: 12)
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0x1C00)

View File

@@ -23,8 +23,8 @@ extern "C" {
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFE75)

View File

@@ -23,8 +23,8 @@ extern "C" {
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x7F)
#define GDMA_LL_TX_EVENT_MASK (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x7F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x3F)
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFC00)

View File

@@ -25,6 +25,8 @@ extern "C" {
// any "dummy" peripheral ID can be used for M2M mode
#define AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFAC2)
#define AHB_DMA_LL_INVALID_PERIPH_ID (0x3F)
#define AHB_DMA_LL_RX_EVENT_MASK (0x1F)
#define AHB_DMA_LL_TX_EVENT_MASK (0x0F)
///////////////////////////////////// Common /////////////////////////////////////////
/**

View File

@@ -13,6 +13,7 @@
#include "hal/hal_utils.h"
#include "hal/gdma_types.h"
#include "hal/gdma_ll.h"
#include "hal/config.h"
#include "soc/axi_dma_struct.h"
#include "soc/axi_dma_reg.h"
@@ -25,6 +26,13 @@ extern "C" {
// any "dummy" peripheral ID can be used for M2M mode
#define AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK (0xFFC0)
#define AXI_DMA_LL_INVALID_PERIPH_ID (0x3F)
#define AXI_DMA_LL_RX_EVENT_MASK (0x1F)
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
#define AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT 1
#define AXI_DMA_LL_TX_EVENT_MASK (0x40F)
#else
#define AXI_DMA_LL_TX_EVENT_MASK (0x0F)
#endif
///////////////////////////////////// Common /////////////////////////////////////////
/**
@@ -468,6 +476,42 @@ static inline void axi_dma_ll_tx_restart(axi_dma_dev_t *dev, uint32_t channel)
dev->out[channel].conf.out_link1.outlink_restart_chn = 1;
}
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
/**
* @brief Request link switch done indication for TX channel
*/
static inline void axi_dma_ll_tx_request_link_switch_event(axi_dma_dev_t *dev, uint32_t channel)
{
switch (channel) {
case 0:
dev->link_switch_state.link_switch_state_ch0 = 1;
break;
case 1:
dev->link_switch_state.link_switch_state_ch1 = 1;
break;
case 2:
dev->link_switch_state.link_switch_state_ch2 = 1;
break;
default:
break;
}
}
#endif
/**
* @brief Check if TX link switch done indication is supported
*/
__attribute__((always_inline))
static inline bool axi_dma_ll_tx_is_link_switch_event_supported(axi_dma_dev_t *dev)
{
(void)dev;
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
return true;
#else
return false;
#endif
}
/**
* @brief Check if DMA TX descriptor FSM is in IDLE state
*/

View File

@@ -18,11 +18,13 @@
#define GDMA_LL_CHANNEL_MAX_PRIORITY 5 // supported priority levels: [0,5]
#define GDMA_LL_RX_EVENT_MASK (0x1F)
#define GDMA_LL_TX_EVENT_MASK (0x0F)
#define GDMA_LL_INVALID_PERIPH_ID (0x3F)
// the following event bits are only supported by axi-dma
#if HAL_CONFIG(CHIP_SUPPORT_MIN_REV) >= 300
#define GDMA_LL_EVENT_TX_LINK_SWITCH (1<<10)
#endif
// the following event bits are identical for ahb-dma and axi-dma
#define GDMA_LL_EVENT_TX_TOTAL_EOF (1<<3)
#define GDMA_LL_EVENT_TX_DESC_ERROR (1<<2)
#define GDMA_LL_EVENT_TX_EOF (1<<1)

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -11,6 +11,8 @@
static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = {
.m2m_free_periph_mask = GDMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = GDMA_LL_TX_EVENT_MASK,
.rx_event_mask = GDMA_LL_RX_EVENT_MASK,
};
void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr)
@@ -175,6 +177,12 @@ void gdma_ahb_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha
}
#endif // SOC_GDMA_SUPPORT_ETM
bool gdma_ahb_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
(void)hal;
return false;
}
void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
{
hal->dev = GDMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID);
@@ -198,5 +206,6 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
#if GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE
hal->set_burst_size = gdma_ahb_hal_set_burst_size;
#endif // GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE
hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported;
hal->priv_data = &gdma_ahb_hal_priv_data;
}

View File

@@ -12,6 +12,8 @@
static gdma_hal_priv_data_t gdma_ahb_hal_priv_data = {
.m2m_free_periph_mask = AHB_DMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = AHB_DMA_LL_TX_EVENT_MASK,
.rx_event_mask = AHB_DMA_LL_RX_EVENT_MASK,
};
void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr)
@@ -232,6 +234,12 @@ void gdma_ahb_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha
}
#endif // SOC_GDMA_SUPPORT_ETM
bool gdma_ahb_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
(void)hal;
return false;
}
void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
{
hal->ahb_dma_dev = AHB_DMA_LL_GET_HW(config->group_id - GDMA_LL_AHB_GROUP_START_ID);
@@ -261,5 +269,6 @@ void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
#if GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE
hal->set_burst_size = gdma_ahb_hal_set_burst_size;
#endif // GDMA_LL_AHB_BURST_SIZE_ADJUSTABLE
hal->is_tx_link_switch_event_supported = gdma_ahb_hal_is_tx_link_switch_event_supported;
ahb_dma_ll_set_default_memory_range(hal->ahb_dma_dev);
}

View File

@@ -12,6 +12,8 @@
static gdma_hal_priv_data_t gdma_axi_hal_priv_data = {
.m2m_free_periph_mask = AXI_DMA_LL_M2M_FREE_PERIPH_ID_MASK,
.tx_event_mask = AXI_DMA_LL_TX_EVENT_MASK,
.rx_event_mask = AXI_DMA_LL_RX_EVENT_MASK,
};
void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr)
@@ -163,6 +165,15 @@ uint32_t gdma_axi_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gd
}
}
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
void gdma_axi_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir)
{
if (dir == GDMA_CHANNEL_DIRECTION_TX) {
axi_dma_ll_tx_request_link_switch_event(hal->axi_dma_dev, chan_id);
}
}
#endif // AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
#if SOC_GDMA_SUPPORT_CRC
void gdma_axi_hal_clear_crc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir)
{
@@ -230,6 +241,11 @@ void gdma_axi_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_cha
}
#endif // SOC_GDMA_SUPPORT_ETM
bool gdma_axi_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
return axi_dma_ll_tx_is_link_switch_event_supported(hal->axi_dma_dev);
}
void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
{
hal->axi_dma_dev = AXI_DMA_LL_GET_HW(config->group_id - GDMA_LL_AXI_GROUP_START_ID);
@@ -257,5 +273,10 @@ void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config)
#if SOC_GDMA_SUPPORT_ETM
hal->enable_etm_task = gdma_axi_hal_enable_etm_task;
#endif // SOC_GDMA_SUPPORT_ETM
hal->is_tx_link_switch_event_supported = gdma_axi_hal_is_tx_link_switch_event_supported;
#if AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
hal->request_link_switch_event = gdma_axi_hal_request_link_switch_event;
#endif // AXI_DMA_LL_SUPPORT_TX_LINK_SWITCH_EVENT
axi_dma_ll_set_default_memory_range(hal->axi_dma_dev);
}

View File

@@ -113,3 +113,13 @@ void gdma_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_channel
hal->enable_etm_task(hal, chan_id, dir, en_or_dis);
}
#endif // SOC_GDMA_SUPPORT_ETM
void gdma_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir)
{
hal->request_link_switch_event(hal, chan_id, dir);
}
bool gdma_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal)
{
return hal->is_tx_link_switch_event_supported(hal);
}

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -49,9 +49,9 @@ typedef struct {
typedef struct {
// The bitmap of the IDs that can be used by M2M are different between AXI DMA and AHB DMA, so we need to save a copy for each of them
uint32_t m2m_free_periph_mask;
// TODO: we can add more private data here, e.g. the interrupt event mask of interest
// for now, the AXI DMA and AHB DMA are sharing the same interrupt mask, so we don't need to store it here
// If one day they become incompatible, we shall save a copy for each of them as a private data
// Supported interrupt events can vary across DMA instances (e.g. AHB vs AXI)
uint32_t tx_event_mask;
uint32_t rx_event_mask;
} gdma_hal_priv_data_t;
/**
@@ -95,6 +95,8 @@ struct gdma_hal_context_t {
#if SOC_GDMA_SUPPORT_ETM
void (*enable_etm_task)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_or_dis); /// Enable the ETM task
#endif // SOC_GDMA_SUPPORT_ETM
bool (*is_tx_link_switch_event_supported)(gdma_hal_context_t *hal); /// Check if TX link-switch interrupt is supported
void (*request_link_switch_event)(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir); /// Raise the interrupt when tx link switch complete
};
void gdma_hal_deinit(gdma_hal_context_t *hal);
@@ -148,6 +150,10 @@ uint32_t gdma_hal_get_crc_result(gdma_hal_context_t *hal, int chan_id, gdma_chan
void gdma_hal_enable_etm_task(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_or_dis);
#endif
void gdma_hal_request_link_switch_event(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
bool gdma_hal_is_tx_link_switch_event_supported(gdma_hal_context_t *hal);
#ifdef __cplusplus
}
#endif

View File

@@ -12,36 +12,6 @@
extern "C" {
#endif
void gdma_ahb_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr);
void gdma_ahb_hal_stop(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_append(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_reset(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_set_priority(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t priority);
void gdma_ahb_hal_connect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, gdma_trigger_peripheral_t periph, int periph_sub_id);
void gdma_ahb_hal_disconnect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_ahb_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_data_burst, bool en_desc_burst);
void gdma_ahb_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz);
void gdma_ahb_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped);
void gdma_ahb_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis);
void gdma_ahb_hal_clear_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask);
uint32_t gdma_ahb_hal_read_intr_status(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool raw);
uint32_t gdma_ahb_hal_get_intr_status_reg(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
uint32_t gdma_ahb_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool is_success);
void gdma_ahb_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config);
#ifdef __cplusplus

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -12,36 +12,6 @@
extern "C" {
#endif
void gdma_axi_hal_start_with_desc(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, intptr_t desc_base_addr);
void gdma_axi_hal_stop(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_append(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_reset(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_set_priority(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t priority);
void gdma_axi_hal_connect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, gdma_trigger_peripheral_t periph, int periph_sub_id);
void gdma_axi_hal_disconnect_peri(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
void gdma_axi_hal_enable_burst(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_data_burst, bool en_desc_burst);
void gdma_axi_hal_set_burst_size(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t burst_sz);
void gdma_axi_hal_set_strategy(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool en_owner_check, bool en_desc_write_back, bool eof_till_popped);
void gdma_axi_hal_enable_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask, bool en_or_dis);
void gdma_axi_hal_clear_intr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, uint32_t intr_event_mask);
uint32_t gdma_axi_hal_read_intr_status(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool raw);
uint32_t gdma_axi_hal_get_intr_status_reg(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir);
uint32_t gdma_axi_hal_get_eof_desc_addr(gdma_hal_context_t *hal, int chan_id, gdma_channel_direction_t dir, bool is_success);
void gdma_axi_hal_init(gdma_hal_context_t *hal, const gdma_hal_config_t *config);
#ifdef __cplusplus

View File

@@ -1,4 +1,4 @@
dependencies:
lvgl/lvgl: "9.4.0"
lvgl/lvgl: "9.5.0"
esp_lcd_ili9881c: "^1.0.0"
esp_lcd_ek79007: "^1.0.0"

View File

@@ -331,7 +331,7 @@ void app_main(void)
esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = example_notify_lvgl_flush_ready,
#if CONFIG_EXAMPLE_MONITOR_REFRESH_BY_GPIO
.on_refresh_done = example_monitor_refresh_rate,
.on_vsync = example_monitor_refresh_rate,
#endif
};
ESP_ERROR_CHECK(esp_lcd_dpi_panel_register_event_callbacks(mipi_dpi_panel, &cbs, display));

View File

@@ -1,3 +1,3 @@
idf_component_register(SRCS "rgb_lcd_example_main.c" "lvgl_demo_ui.c"
PRIV_REQUIRES esp_lcd
PRIV_REQUIRES esp_lcd esp_timer
INCLUDE_DIRS ".")

View File

@@ -1,2 +1,2 @@
dependencies:
lvgl/lvgl: "9.2.0"
lvgl/lvgl: "9.5.0"

View File

@@ -101,15 +101,43 @@ static const char *TAG = "example";
// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it
static _lock_t lvgl_api_lock;
static TaskHandle_t lvgl_task_handle;
extern void example_lvgl_demo_ui(lv_display_t *disp);
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
static bool example_on_frame_buf_complete(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx)
{
(void)panel;
(void)event_data;
(void)user_ctx;
BaseType_t need_yield = pdFALSE;
if (lvgl_task_handle) {
vTaskNotifyGiveFromISR(lvgl_task_handle, &need_yield);
}
return need_yield == pdTRUE;
}
static void example_lvgl_flush_wait_cb(lv_display_t *disp)
{
// The flush callback only submits the rendered buffer to the LCD driver.
// With direct-mode double buffering, LVGL must wait until the RGB panel has
// switched away from the previous frame buffer before rendering into it again.
if (lv_display_flush_is_last(disp)) {
// Wait until the previous frame buffer is no longer referenced by DMA.
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
lv_display_flush_ready(disp);
}
#else
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx)
{
lv_display_t *disp = (lv_display_t *)user_ctx;
lv_display_flush_ready(disp);
return false;
}
#endif
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
{
@@ -118,6 +146,20 @@ static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uin
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
if (!lv_display_flush_is_last(disp)) {
lv_display_flush_ready(disp);
return;
}
// In direct mode, LVGL may flush multiple dirty areas. Switch the RGB panel to the new
// frame buffer only after the last dirty area has been rendered.
offsetx1 = 0;
offsety1 = 0;
offsetx2 = EXAMPLE_LCD_H_RES - 1;
offsety2 = EXAMPLE_LCD_V_RES - 1;
// Clear any stale completion from the previous frame before waiting for this frame.
ulTaskNotifyTake(pdTRUE, 0);
#endif
// pass the draw buffer to the driver
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, px_map);
}
@@ -264,10 +306,19 @@ void app_main(void)
// set the callback which can copy the rendered image to an area of the display
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
// The wait callback keeps LVGL from reusing a frame buffer until the panel driver
// reports that the buffer has finished refreshing.
lv_display_set_flush_wait_cb(display, example_lvgl_flush_wait_cb);
#endif
ESP_LOGI(TAG, "Register event callbacks");
esp_lcd_rgb_panel_event_callbacks_t cbs = {
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
.on_frame_buf_complete = example_on_frame_buf_complete,
#else
.on_color_trans_done = example_notify_lvgl_flush_ready,
#endif
};
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, display));
@@ -282,7 +333,7 @@ void app_main(void)
ESP_ERROR_CHECK(esp_timer_start_periodic(lvgl_tick_timer, EXAMPLE_LVGL_TICK_PERIOD_MS * 1000));
ESP_LOGI(TAG, "Create LVGL task");
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, NULL);
xTaskCreate(example_lvgl_port_task, "LVGL", EXAMPLE_LVGL_TASK_STACK_SIZE, NULL, EXAMPLE_LVGL_TASK_PRIORITY, &lvgl_task_handle);
ESP_LOGI(TAG, "Display LVGL UI");
// Lock the mutex due to the LVGL APIs are not thread-safe