Merge branch 'feature/rgb-lcd-auto-next_frame' into 'master'

rgb_lcd: Double Frame Buffer && Bounce Buffer

Closes IDF-4494 and IDFGH-7737

See merge request espressif/esp-idf!18545
This commit is contained in:
morris 2022-07-14 16:21:52 +08:00
commit d95afae0d2
16 changed files with 990 additions and 488 deletions

View File

@ -18,12 +18,20 @@ menu "LCD and Touch Panel"
config LCD_RGB_ISR_IRAM_SAFE
bool "RGB LCD ISR IRAM-Safe"
default n
select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR
help
Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be
executable when the cache is disabled (e.g. SPI Flash write).
If you want the LCD driver to keep flushing the screen even when cache ops disabled,
you can enable this option. Note, this will also increase the IRAM usage.
config LCD_RGB_RESTART_IN_VSYNC
bool "Restart transmission in VSYNC"
default n
select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR
help
Reset the GDMA channel every VBlank to stop permanent desyncs from happening.
Only need to enable it when in your application, the DMA can't deliver data
as fast as the LCD consumes it.
endif # SOC_LCD_RGB_SUPPORTED
endmenu
endmenu

View File

@ -5,6 +5,7 @@
*/
#pragma once
#include <stdint.h>
#include <stdbool.h>
#include "esp_err.h"
#include "esp_lcd_types.h"
@ -51,22 +52,22 @@ extern "C" {
* @endverbatim
*/
typedef struct {
unsigned int pclk_hz; /*!< Frequency of pixel clock */
unsigned int h_res; /*!< Horizontal resolution, i.e. the number of pixels in a line */
unsigned int v_res; /*!< Vertical resolution, i.e. the number of lines in the frame */
unsigned int hsync_pulse_width; /*!< Horizontal sync width, unit: PCLK period */
unsigned int hsync_back_porch; /*!< Horizontal back porch, number of PCLK between hsync and start of line active data */
unsigned int hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */
unsigned int vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */
unsigned int vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */
uint32_t pclk_hz; /*!< Frequency of pixel clock */
uint32_t h_res; /*!< Horizontal resolution, i.e. the number of pixels in a line */
uint32_t v_res; /*!< Vertical resolution, i.e. the number of lines in the frame */
uint32_t hsync_pulse_width; /*!< Horizontal sync width, unit: PCLK period */
uint32_t hsync_back_porch; /*!< Horizontal back porch, number of PCLK between hsync and start of line active data */
uint32_t hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */
uint32_t vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */
uint32_t vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
uint32_t vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */
struct {
unsigned int hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */
unsigned int vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */
unsigned int de_idle_high: 1; /*!< The de signal is high in IDLE state */
unsigned int pclk_active_neg: 1; /*!< Whether the display data is clocked out on the falling edge of PCLK */
unsigned int pclk_idle_high: 1; /*!< The PCLK stays at high level in IDLE phase */
} flags; /*!< LCD RGB timing flags */
uint32_t hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */
uint32_t vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */
uint32_t de_idle_high: 1; /*!< The de signal is high in IDLE state */
uint32_t pclk_active_neg: 1; /*!< Whether the display data is clocked out on the falling edge of PCLK */
uint32_t pclk_idle_high: 1; /*!< The PCLK stays at high level in IDLE phase */
} flags; /*!< LCD RGB timing flags */
} esp_lcd_rgb_timing_t;
/**
@ -76,142 +77,137 @@ typedef struct {
} esp_lcd_rgb_panel_event_data_t;
/**
* @brief Declare the prototype of the function that will be invoked when panel IO finishes transferring color data
* @brief RGB LCD VSYNC event callback prototype
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param[in] edata Panel event data, fed by driver
* @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_config_t`
* @param[in] user_ctx User data, passed from `esp_lcd_rgb_panel_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*esp_lcd_rgb_panel_frame_trans_done_cb_t)(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
typedef bool (*esp_lcd_rgb_panel_vsync_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
/**
* @brief Prototype for function to re-fill a bounce buffer. Note this is called in ISR context.
* @brief Prototype for function to re-fill a bounce buffer, rather than copying from the frame buffer
*
* @param bounce_buf Bounce buffer to write data into
* @param pos_px How many pixels already were sent to the display this frame, in other words, at what pixel
* the routine should start putting data into bounce_buf
* @param len_bytes Length, in bytes, of the bounce buffer. Routine should fill this length fully.
* @param user_ctx Opaque pointer that was passed as bounce_buffer_cb_user_ctx to esp_lcd_new_rgb_panel
* @return True if the callback woke up a higher-priority task, false otherwise.
* @param[in] bounce_buf Bounce buffer to write data into
* @param[in] pos_px How many pixels already were sent to the display in this frame, in other words,
* at what pixel the routine should start putting data into bounce_buf
* @param[in] len_bytes Length, in bytes, of the bounce buffer. Routine should fill this length fully.
* @param[in] user_ctx Opaque pointer that was passed from `esp_lcd_rgb_panel_register_event_callbacks()`
* @return Whether a high priority task has been waken up by this function
*/
typedef bool (*esp_lcd_rgb_panel_bounce_buf_fill_cb_t)(void *bounce_buf, int pos_px, int len_bytes, void *user_ctx);
typedef bool (*esp_lcd_rgb_panel_bounce_buf_fill_cb_t)(esp_lcd_panel_handle_t panel, void *bounce_buf, int pos_px, int len_bytes, void *user_ctx);
/**
* @brief LCD RGB framebuffer operation modes
*
* With regards to how the framebuffer is accessed and where it is located, the RGB LCD panel driver can
* operate in four modes:
*
* - Framebuffer in internal memory.
* - Framebuffer in PSRAM, accessed using EDMA
* - Framebuffer in PSRAM, smaller bounce buffers in internal memory.
* - No framebuffer in driver, bounce buffers in internal memory filled by callback.
*
* The first option (framebuffer in internal memory) is the default and simplest. There is a framebuffer in
* internal memory that is read out once a frame using DMA and the data is sent out to the LCD verbatim. It
* needs no CPU intervention to function, but it has the downside that it uses up a fair bit of the limited
* amount of internal memory. This is the default if you do not specify flags or bounce buffer options.
*
* The second option is useful if you have PSRAM and want to store the framebuffer there rather than in the
* limited internal memory. The LCD peripheral will use EDMA to fetch frame data directly from the PSRAM,
* bypassing the internal cache. If you use this, after writing to the framebuffer, make sure to use e.g.
* Cache_WriteBack_Addr to make sure the framebuffer is actually written back to the PSRAM. Not doing this
* will lead to image corruption.
* The downside of this is that when both the CPU as well as peripherals need access to the EDMA, the
* bandwidth will be shared between the two, that is, EDMA gets half and the CPUs the other half. If
* there's other peripherals using EDMA as well, with a high enough pixel clock this can lead to starvation
* of the LCD peripheral, leading to display corruption. However, if the pixel clock is low enough for this
* not to be an issue, this is a solution that uses almost no CPU intervention. This option can be enabled
* by setting the ``fb_in_psram`` flag.
*
* The third option makes use of two so-called 'bounce buffers' in internal memory, but a main framebuffer that
* is still in PSRAM. These bounce buffers are buffers large enough to hold e.g. a few lines of display data,
* but still significantly less than the main framebuffer. The LCD peripheral will use DMA to read data from
* one of the bounce buffers, and meanwhile an interrupt routine will use the CPU to copy data from the main
* PSRAM framebuffer into the other bounce buffer. Once the LCD peripheral has finished reading the bounce
* buffer, the two buffers change place and the CPU can fill the others. Note that as the CPU reads the
* framebuffer data through the cache, it's not needed to call Cache_WriteBack_Addr() anymore.
* The advantage here is that, as it's easier to control CPU memory bandwith use than EDMA memory bandwith
* use, doing this can lead to higher pixel clocks being supported. As the bounce buffers are larger than
* the FIFOs in the EDMA path, this method is also more robust against short bandwidth spikes. The
* downside is a major increase in CPU use. This mode is selected by setting the ``fb_in_psram`` flag and
* additionally specifying a (non-zero) bounce_buffer_size_px value. This value is dependent on your use
* case, but a suggested initial value would be e.g. 8 times the amount of pixels in one LCD line.
*
* Note that this third option also allows for a ``bb_do_cache_invalidate`` flag to be set. Enabling this
* frees up the cache lines after they're used to read out the framebuffer data from PSRAM, but it may lead
* to slight corruption if the other core writes data to the framebuffer at the exact time the cache lines
* are freed up. (Technically, a write to the framebuffer can be ignored if it falls between the cache
* writeback and the cache invalidate calls.)
*
* Finally, the fourth option is the same as the third option, but there is no PSRAM frame buffer initialized
* by the LCD driver. Instead, the user supplies a callback function that is responsible for filling the
* bounce buffers. As this driver does not care where the written pixels come from, this allows for
* the callback doing e.g. on-the-fly conversion from a smaller, 8-bit-per-pixel PSRAM framebuffer to
* an 16-bit LCD, or even procedurally-generated framebuffer-less graphics. This option is selected
* by not setting the ``fb_in_psram`` flag but supplying both a ``bounce_buffer_size_px`` value as well
* as a ``on_bounce_empty`` callback.
* @brief Group of supported RGB LCD panel callbacks
* @note The callbacks are all running under ISR environment
* @note When CONFIG_LCD_RGB_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM.
*/
typedef struct {
esp_lcd_rgb_panel_vsync_cb_t on_vsync; /*!< VSYNC event callback */
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< Bounce buffer empty callback. */
} esp_lcd_rgb_panel_event_callbacks_t;
/**
* @brief LCD RGB panel configuration structure
*/
typedef struct {
lcd_clock_source_t clk_src; /*!< Clock source for the RGB LCD peripheral */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters */
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters, including the screen resolution */
size_t data_width; /*!< Number of data lines */
size_t sram_trans_align; /*!< Alignment for framebuffer that allocated in SRAM */
size_t psram_trans_align; /*!< Alignment for framebuffer that allocated in PSRAM */
size_t bits_per_pixel; /*!< Color depth, in bpp, specially, if set to zero, it will default to `data_width`.
When using a Serial RGB interface, this value could be different from `data_width` */
size_t bounce_buffer_size_px; /*!< If it's non-zero, the driver allocates two DRAM bounce buffers for DMA use.
DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */
size_t sram_trans_align; /*!< Alignment of buffers (frame buffer or bounce buffer) that allocated in SRAM */
size_t psram_trans_align; /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
int hsync_gpio_num; /*!< GPIO used for HSYNC signal */
int vsync_gpio_num; /*!< GPIO used for VSYNC signal */
int de_gpio_num; /*!< GPIO used for DE signal, set to -1 if it's not used */
int pclk_gpio_num; /*!< GPIO used for PCLK signal */
int disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */
int disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */
esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; /*!< Callback invoked when one frame buffer has transferred done */
int bounce_buffer_size_px; /*!< If not-zero, the driver uses a bounce buffer in internal memory to DMA from. Value is in pixels. */
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; /*!< If we use a bounce buffer, this function gets called to fill that, rather than copying from the framebuffer */
void *bounce_buffer_cb_user_ctx; /*!< Opaque parameter to pass to the on_bounce_empty function */
void *user_ctx; /*!< User data which would be passed to on_frame_trans_done's user_ctx */
struct {
unsigned int disp_active_low: 1; /*!< If this flag is enabled, a low level of display control signal can turn the screen on; vice versa */
unsigned int relax_on_idle: 1; /*!< If this flag is enabled, the host won't refresh the LCD if nothing changed in host's frame buffer (this is usefull for LCD with built-in GRAM) */
unsigned int fb_in_psram: 1; /*!< If this flag is enabled, the frame buffer will be allocated from PSRAM preferentially */
unsigned int bb_do_cache_invalidate:1; /*!< If this flag is enabled, in bounceback mode we'll do a cache invalidate on the read data, freeing the cache. Can be dangerous if data is written from other core. */
uint32_t disp_active_low: 1; /*!< If this flag is enabled, a low level of display control signal can turn the screen on; vice versa */
uint32_t refresh_on_demand: 1; /*!< If this flag is enabled, the host only refresh the frame buffer when `esp_lcd_panel_draw_bitmap` is called.
This is useful when the LCD screen has a GRAM and can refresh the LCD by itself. */
uint32_t fb_in_psram: 1; /*!< If this flag is enabled, the frame buffer will be allocated from PSRAM, preferentially */
uint32_t double_fb: 1; /*!< If this flag is enabled, the driver will allocate two screen sized frame buffer */
uint32_t no_fb: 1; /*!< If this flag is enabled, the driver won't allocate frame buffer.
Instead, user should fill in the bounce buffer manually in the `on_bounce_empty` callback */
uint32_t bb_invalidate_cache: 1; /*!< If this flag is enabled, in bounce back mode we'll do a cache invalidate on the read data, freeing the cache.
Can be dangerous if data is written from other core(s). */
} flags; /*!< LCD RGB panel configuration flags */
} esp_lcd_rgb_panel_config_t;
/**
* @brief Create RGB LCD panel
*
* @param rgb_panel_config RGB panel configuration
* @param ret_panel Returned LCD panel handle
* @param[in] rgb_panel_config RGB panel configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_ERR_NOT_FOUND if no free RGB panel is available
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG: Create RGB LCD panel failed because of invalid argument
* - ESP_ERR_NO_MEM: Create RGB LCD panel failed because of out of memory
* - ESP_ERR_NOT_FOUND: Create RGB LCD panel failed because some mandatory hardware resources are not found
* - ESP_OK: Create RGB LCD panel successfully
*/
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel);
/**
* @brief Register LCD RGB panel event callbacks
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param[in] callbacks Group of callback functions
* @param[in] user_ctx User data, which will be passed to the callback functions directly
* @return
* - ESP_OK: Set event callbacks successfully
* - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument
* - ESP_FAIL: Set event callbacks failed because of other error
*/
esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_callbacks_t *callbacks, void *user_ctx);
/**
* @brief Set frequency of PCLK for RGB LCD panel
*
* @note The PCLK frequency is set in the `esp_lcd_rgb_timing_t` and gets configured during LCD panel initialization.
* Usually you don't need to call this function to set the PCLK again, but in some cases, you may need to change the PCLK frequency.
* e.g. to slow down the PCLK frequency to reduce power consumption or to reduce the memory throughput.
* Usually you don't need to call this function to set the PCLK again, but in some cases, you might want to change the PCLK frequency.
* e.g. slow down the PCLK frequency to reduce power consumption or to reduce the memory throughput during OTA.
* @note This function doesn't cause the hardware to update the PCLK immediately but to record the new frequency and set a flag internally.
* Next time when start a new transaction, the driver will update the PCLK automatically.
* Only in the next VSYNC event handler, will the driver attempt to update the PCLK frequency.
*
* @param panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param freq_hz Frequency of pixel clock, in Hz
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param[in] freq_hz Frequency of pixel clock, in Hz
* @return
* - ESP_ERR_NOT_SUPPORTED if frequency is unreachable
* - ESP_ERR_INVALID_ARG if parameter panel is invalid
* - ESP_OK on success
* - ESP_ERR_INVALID_ARG: Set PCLK frequency failed because of invalid argument
* - ESP_OK: Set PCLK frequency successfully
*/
esp_err_t esp_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz);
esp_err_t esp_lcd_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz);
/**
* @brief Get the address of the frame buffer(s) that allocated by the driver
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @param[in] fb_num Number of frame buffer(s) to get. This value must be the same as the number of the following parameters.
* @param[out] fb0 Returned address of the frame buffer 0
* @param[out] ... List of other frame buffer addresses
* @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_rgb_panel_get_frame_buffer(esp_lcd_panel_handle_t panel, uint32_t fb_num, void **fb0, ...);
/**
* @brief Manually trigger once transmission of the frame buffer to the LCD panel
*
* @note This function should only be called when the RGB panel is working under the `refresh_on_demand` mode.
*
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel()`
* @return
* - ESP_ERR_INVALID_ARG: Start a refresh failed because of invalid argument
* - ESP_ERR_INVALID_STATE: Start a refresh failed because the LCD panel is not created with the `refresh_on_demand` flag enabled.
* - ESP_OK: Start a refresh successfully
*/
esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel);
#endif // SOC_LCD_RGB_SUPPORTED

View File

@ -3,4 +3,3 @@ archive: libesp_lcd.a
entries:
if LCD_RGB_ISR_IRAM_SAFE = y:
esp_lcd_common: lcd_com_mount_dma_data (noflash)
esp_lcd_rgb_panel: lcd_rgb_panel_start_transmission (noflash)

View File

@ -5,6 +5,7 @@
*/
#include <stdlib.h>
#include <stdarg.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#include <string.h>
@ -38,7 +39,8 @@
#include "soc/lcd_periph.h"
#include "hal/lcd_hal.h"
#include "hal/lcd_ll.h"
#include <rom/cache.h>
#include "hal/gdma_ll.h"
#include "rom/cache.h"
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
@ -50,7 +52,6 @@ static const char *TAG = "lcd_panel.rgb";
typedef struct esp_rgb_panel_t esp_rgb_panel_t;
static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel);
@ -70,58 +71,119 @@ struct esp_rgb_panel_t {
esp_lcd_panel_t base; // Base class of generic lcd panel
int panel_id; // LCD panel ID
lcd_hal_context_t hal; // Hal layer object
size_t data_width; // Number of data lines (e.g. for RGB565, the data width is 16)
size_t data_width; // Number of data lines
size_t bits_per_pixel; // Color depth, in bpp
size_t sram_trans_align; // Alignment for framebuffer that allocated in SRAM
size_t psram_trans_align; // Alignment for framebuffer that allocated in PSRAM
int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off"
intr_handle_t intr; // LCD peripheral interrupt handle
esp_pm_lock_handle_t pm_lock; // Power management lock
size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer
uint8_t *fb; // Frame buffer
uint8_t *fbs[2]; // Frame buffers
uint8_t cur_fb_index; // Current frame buffer index (0 or 1)
size_t fb_size; // Size of frame buffer
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color"
uint32_t src_clk_hz; // Peripheral source clock resolution
esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width)
size_t bb_size; // If not-zero, the driver uses two bounce buffers allocated from internal memory
int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
uint8_t *bounce_buffer[2]; // Pointer to the bounce buffers
gdma_channel_handle_t dma_chan; // DMA channel handle
esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done
int bounce_buffer_size_bytes; //If not-zero, the driver uses a bounce buffer in internal memory to DMA from. It's in bytes here.
uint8_t *bounce_buffer[2]; //Pointer to the bounce buffers
int bounce_buf_frame_start; //If frame restarts, which bb has the initial frame data?
esp_lcd_rgb_panel_bounce_buf_fill_cb_t on_bounce_empty; // If we use a bounce buffer, this function gets called to fill it rather than copying from the framebuffer
void *bounce_buffer_cb_user_ctx; //Callback data pointer
void *user_ctx; // Reserved user's data of callback functions
int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
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
int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window
int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window
portMUX_TYPE spinlock; // to protect panel specific resource from concurrent access (e.g. between task and ISR)
struct {
unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
unsigned int fb_in_psram: 1; // Whether the frame buffer is in PSRAM
unsigned int need_update_pclk: 1; // Whether to update the PCLK before start a new transaction
unsigned int bb_do_cache_invalidate: 1; //If we do cache invalidate in bb psram fb mode
uint32_t disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
uint32_t stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
uint32_t no_fb: 1; // No frame buffer allocated in the driver
uint32_t fb_in_psram: 1; // Whether the frame buffer is in PSRAM
uint32_t need_update_pclk: 1; // Whether to update the PCLK before start a new transaction
uint32_t bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode
} flags;
dma_descriptor_t dma_restart_node; //DMA descriptor used to restart the transfer
dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes`
dma_descriptor_t *dma_links[2]; // fbs[0] <-> dma_links[0], fbs[1] <-> dma_links[1]
dma_descriptor_t dma_restart_node; // DMA descriptor used to restart the transfer
dma_descriptor_t dma_nodes[]; // DMA descriptors pool
};
/*
A note about DMA desync:
static esp_err_t lcd_rgb_panel_alloc_frame_buffers(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_rgb_panel_t *rgb_panel)
{
bool fb_in_psram = false;
size_t psram_trans_align = rgb_panel_config->psram_trans_align ? rgb_panel_config->psram_trans_align : 64;
size_t sram_trans_align = rgb_panel_config->sram_trans_align ? rgb_panel_config->sram_trans_align : 4;
rgb_panel->psram_trans_align = psram_trans_align;
rgb_panel->sram_trans_align = sram_trans_align;
It should never happen in a well-designed embedded application, but it can in theory be possible
that GDMA cannot deliver data as fast as the LCD consumes it. In the ESP32-S3 hardware, this
leads to the LCD simply outputting dummy bytes while GDMA waits for data. If we were to run
DMA in a simple circular fashion, this would mean a de-sync between the LCD address the GDMA
reads the data for and the LCD address the LCD peripheral thinks it outputs data for,
leading to a permanently shifted image.
// alloc frame buffer
if (!rgb_panel_config->flags.no_fb) {
// fb_in_psram is only an option, if there's no PSRAM on board, we fallback to alloc from SRAM
if (rgb_panel_config->flags.fb_in_psram) {
#if CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC
if (esp_psram_is_initialized()) {
fb_in_psram = true;
}
#endif
}
for (int i = 0; i < (rgb_panel_config->flags.double_fb ? 2 : 1); i++) {
if (fb_in_psram) {
// the low level malloc function will help check the validation of alignment
rgb_panel->fbs[i] = heap_caps_aligned_calloc(psram_trans_align, 1, rgb_panel->fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
rgb_panel->fbs[i] = heap_caps_aligned_calloc(sram_trans_align, 1, rgb_panel->fb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
ESP_RETURN_ON_FALSE(rgb_panel->fbs[i], ESP_ERR_NO_MEM, TAG, "no mem for frame buffer");
}
}
In order to stop this from happening, we restart GDMA in the VBlank interrupt; this way we always
know where it starts. However, the LCD peripheral also has a FIFO, and at the time of the VBlank,
it already has read some data in there. We cannot reset this FIFO entirely, there's always one
pixel that remains. So instead, when we restart DMA, we take into account it does not need to
output the data that already is in the FIFO and we restart it using a descriptor that starts
at the position after the last pixel in the LCD fifo.
*/
// alloc bounce buffer
if (rgb_panel->bb_size) {
for (int i = 0; i < 2; i++) {
// bounce buffer must come from SRAM
rgb_panel->bounce_buffer[i] = heap_caps_aligned_calloc(sram_trans_align, 1, rgb_panel->bb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
ESP_RETURN_ON_FALSE(rgb_panel->bounce_buffer[i], ESP_ERR_NO_MEM, TAG, "no mem for bounce buffer");
}
}
rgb_panel->cur_fb_index = 0;
rgb_panel->flags.fb_in_psram = fb_in_psram;
return ESP_OK;
}
static esp_err_t lcd_rgb_panel_destory(esp_rgb_panel_t *rgb_panel)
{
lcd_ll_enable_clock(rgb_panel->hal.dev, false);
if (rgb_panel->panel_id >= 0) {
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
}
if (rgb_panel->fbs[0]) {
free(rgb_panel->fbs[0]);
}
if (rgb_panel->fbs[1]) {
free(rgb_panel->fbs[1]);
}
if (rgb_panel->bounce_buffer[0]) {
free(rgb_panel->bounce_buffer[0]);
}
if (rgb_panel->bounce_buffer[1]) {
free(rgb_panel->bounce_buffer[1]);
}
if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
}
if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr);
}
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
return ESP_OK;
}
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel)
{
@ -131,105 +193,65 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
esp_err_t ret = ESP_OK;
esp_rgb_panel_t *rgb_panel = NULL;
ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err, TAG,
"unsupported data width %d", rgb_panel_config->data_width);
ESP_GOTO_ON_FALSE(!(rgb_panel_config->bounce_buffer_size_px == 0 && rgb_panel_config->on_bounce_empty != NULL),
ESP_ERR_INVALID_ARG, err, TAG, "cannot have bounce empty callback without having a bounce buffer");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->bounce_buffer_size_px != 0 && rgb_panel_config->on_bounce_empty == NULL &&
!rgb_panel_config->flags.fb_in_psram), ESP_ERR_INVALID_ARG, err, TAG,
"bounce buffer without callback and main fb not in psram does not make sense");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16 || rgb_panel_config->data_width == 8,
ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported data width %d", rgb_panel_config->data_width);
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.double_fb && rgb_panel_config->flags.no_fb),
ESP_ERR_INVALID_ARG, err, TAG, "invalid frame buffer number");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
ESP_ERR_INVALID_ARG, err, TAG, "must set bounce buffer if there's no frame buffer");
ESP_GOTO_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px),
ESP_ERR_INVALID_ARG, err, TAG, "refresh on demand is not supported under bounce buffer mode");
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
if (rgb_panel_config->on_frame_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(rgb_panel_config->on_frame_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_frame_trans_done callback not in IRAM");
}
if (rgb_panel_config->user_ctx) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(rgb_panel_config->user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
ESP_GOTO_ON_FALSE(rgb_panel_config->bounce_buffer_size_px == 0,
ESP_ERR_INVALID_ARG, err, TAG, "bounce buffer mode is not IRAM Safe");
#endif
// calculate the number of DMA descriptors
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * rgb_panel_config->data_width / 8;
size_t num_dma_nodes;
int bounce_bytes = 0;
if (rgb_panel_config->bounce_buffer_size_px == 0) {
// No bounce buffers. DMA descriptors need to fit entire framebuffer
num_dma_nodes = (fb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
} else {
//The FB needs to be an integer multiple of the size of a bounce buffer (so
//we end on the end of the second bounce buffer). Adjust the size of the
//bounce buffers if it is not.
int pixel_data_bytes = rgb_panel_config->data_width / 8; //size of one pixel, in bytes
int no_pixels = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res;
bounce_bytes = rgb_panel_config->bounce_buffer_size_px * pixel_data_bytes;
if (no_pixels % (rgb_panel_config->bounce_buffer_size_px * pixel_data_bytes)) {
//Search for some value that does work. Yes, this is a stupidly simple algo, but it only
//needs to run on startup.
for (int a=rgb_panel_config->bounce_buffer_size_px; a>0; a--) {
if ((no_pixels % (a * pixel_data_bytes))==0) {
bounce_bytes = a * pixel_data_bytes;
ESP_LOGW(TAG, "Frame buffer is not an integer multiple of bounce buffers.");
ESP_LOGW(TAG, "Adjusted bounce buffer size from %d to %d pixels to fix this.",
rgb_panel_config->bounce_buffer_size_px, bounce_bytes/pixel_data_bytes);
break;
}
}
}
// DMA descriptors need to fit both bounce buffers
num_dma_nodes = (bounce_bytes + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
num_dma_nodes = num_dma_nodes * 2; //as we have two bounce buffers
// bpp defaults to the number of data lines, but for serial RGB interface, they're not equal
size_t bits_per_pixel = rgb_panel_config->data_width;
if (rgb_panel_config->bits_per_pixel) { // override bpp if it's set
bits_per_pixel = rgb_panel_config->bits_per_pixel;
}
// calculate buffer size
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * bits_per_pixel / 8;
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * bits_per_pixel / 8;
if (bb_size) {
// we want the bounce can always end in the second buffer
ESP_GOTO_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, err, TAG,
"fb size must be even multiple of bounce buffer size");
}
// calculate the number of DMA descriptors
size_t num_dma_nodes = 0;
if (bb_size) {
// in bounce buffer mode, DMA is used to convey the bounce buffer, not the frame buffer.
// frame buffer is copied to bounce buffer by CPU
num_dma_nodes = (bb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
} else {
// Not bounce buffer mode, DMA descriptors need to fit the entire frame buffer
num_dma_nodes = (fb_size + DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
}
// DMA descriptors must be placed in internal SRAM (requested by DMA)
rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
// multiply 2 because of double frame buffer mode (two frame buffer) and bounce buffer mode (two bounce buffer)
rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t) * 2,
MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel");
rgb_panel->num_dma_nodes = num_dma_nodes;
rgb_panel->fb_size = fb_size;
rgb_panel->bb_size = bb_size;
rgb_panel->panel_id = -1;
// register to platform
int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel);
ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, err, TAG, "no free rgb panel slot");
rgb_panel->panel_id = panel_id;
rgb_panel->bounce_buffer_size_bytes = bounce_bytes;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module);
periph_module_reset(lcd_periph_signals.panels[panel_id].module);
// alloc frame buffer
bool alloc_from_psram = false;
// fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM
if (rgb_panel_config->flags.fb_in_psram) {
#if CONFIG_SPIRAM_USE_MALLOC || CONFIG_SPIRAM_USE_CAPS_ALLOC
if (esp_psram_is_initialized()) {
alloc_from_psram = true;
}
#endif
}
size_t psram_trans_align = rgb_panel_config->psram_trans_align ? rgb_panel_config->psram_trans_align : 64;
size_t sram_trans_align = rgb_panel_config->sram_trans_align ? rgb_panel_config->sram_trans_align : 4;
rgb_panel->fb_size = fb_size;
if (!rgb_panel_config->on_bounce_empty) {
//We need to allocate a framebuffer.
if (alloc_from_psram) {
// the low level malloc function will help check the validation of alignment
rgb_panel->fb = heap_caps_aligned_calloc(psram_trans_align, 1, fb_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
} else {
rgb_panel->fb = heap_caps_aligned_calloc(sram_trans_align, 1, fb_size, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
ESP_GOTO_ON_FALSE(rgb_panel->fb, ESP_ERR_NO_MEM, err, TAG, "no mem for frame buffer");
rgb_panel->psram_trans_align = psram_trans_align;
rgb_panel->sram_trans_align = sram_trans_align;
rgb_panel->flags.fb_in_psram = alloc_from_psram;
}
if (rgb_panel->bounce_buffer_size_bytes) {
//We need to allocate bounce buffers.
rgb_panel->bounce_buffer[0] = heap_caps_aligned_calloc(sram_trans_align, 1,
rgb_panel->bounce_buffer_size_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
rgb_panel->bounce_buffer[1] = heap_caps_aligned_calloc(sram_trans_align, 1,
rgb_panel->bounce_buffer_size_bytes, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
if (rgb_panel_config->on_bounce_empty) {
rgb_panel->on_bounce_empty = rgb_panel_config->on_bounce_empty;
rgb_panel->bounce_buffer_cb_user_ctx = rgb_panel_config->bounce_buffer_cb_user_ctx;
}
// allocate frame buffers + bounce buffers
ESP_GOTO_ON_ERROR(lcd_rgb_panel_alloc_frame_buffers(rgb_panel_config, rgb_panel), err, TAG, "alloc frame buffers failed");
// initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&rgb_panel->hal, panel_id);
// enable clock gating
@ -245,8 +267,9 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
ESP_GOTO_ON_ERROR(ret, err, TAG, "install interrupt failed");
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, false); // disable all interrupts
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
rgb_panel->flags.stream_mode = !rgb_panel_config->flags.relax_on_idle;
rgb_panel->flags.stream_mode = !rgb_panel_config->flags.refresh_on_demand;
ret = lcd_rgb_panel_create_trans_link(rgb_panel);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
// configure GPIO
@ -256,11 +279,11 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, SOC_LCD_RGB_DATA_WIDTH);
rgb_panel->timings = rgb_panel_config->timings;
rgb_panel->data_width = rgb_panel_config->data_width;
rgb_panel->bits_per_pixel = bits_per_pixel;
rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num;
rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low;
rgb_panel->flags.bb_do_cache_invalidate = rgb_panel_config->flags.bb_do_cache_invalidate;
rgb_panel->on_frame_trans_done = rgb_panel_config->on_frame_trans_done;
rgb_panel->user_ctx = rgb_panel_config->user_ctx;
rgb_panel->flags.no_fb = rgb_panel_config->flags.no_fb;
rgb_panel->flags.bb_invalidate_cache = rgb_panel_config->flags.bb_invalidate_cache;
rgb_panel->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
// fill function table
rgb_panel->base.del = rgb_panel_del;
@ -274,39 +297,44 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->base.set_gap = rgb_panel_set_gap;
// return base class
*ret_panel = &(rgb_panel->base);
ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb @%p, size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb, rgb_panel->fb_size);
ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb0 @%p, fb1 @%p, fb_size=%zu, bb0 @%p, bb1 @%p, bb_size=%zu",
rgb_panel->panel_id, rgb_panel, rgb_panel->fbs[0], rgb_panel->fbs[1], rgb_panel->fb_size,
rgb_panel->bounce_buffer[0], rgb_panel->bounce_buffer[1], rgb_panel->bb_size);
return ESP_OK;
err:
if (rgb_panel) {
if (rgb_panel->panel_id >= 0) {
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
}
if (rgb_panel->fb) {
free(rgb_panel->fb);
}
if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
}
if (rgb_panel->intr) {
esp_intr_free(rgb_panel->intr);
}
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
lcd_rgb_panel_destory(rgb_panel);
}
return ret;
}
esp_err_t esp_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz)
esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_callbacks_t *callbacks, void *user_ctx)
{
ESP_RETURN_ON_FALSE(panel && callbacks, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
if (callbacks->on_vsync) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_vsync), ESP_ERR_INVALID_ARG, TAG, "on_vsync callback not in IRAM");
}
if (callbacks->on_bounce_empty) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_empty), ESP_ERR_INVALID_ARG, TAG, "on_bounce_empty 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_RGB_ISR_IRAM_SAFE
rgb_panel->on_vsync = callbacks->on_vsync;
rgb_panel->on_bounce_empty = callbacks->on_bounce_empty;
rgb_panel->user_ctx = user_ctx;
return ESP_OK;
}
esp_err_t esp_lcd_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
// the pclk frequency will be updated in `lcd_rgb_panel_start_transmission()`
// the pclk frequency will be updated in the `LCD_LL_EVENT_VSYNC_END` event handler
portENTER_CRITICAL(&rgb_panel->spinlock);
rgb_panel->flags.need_update_pclk = true;
rgb_panel->timings.pclk_hz = freq_hz;
@ -314,24 +342,38 @@ esp_err_t esp_rgb_panel_set_pclk(esp_lcd_panel_handle_t panel, uint32_t freq_hz)
return ESP_OK;
}
esp_err_t esp_lcd_rgb_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_RETURN_ON_FALSE(fb_num && fb_num <= 2, ESP_ERR_INVALID_ARG, TAG, "invalid frame buffer number");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
void **fb_itor = fb0;
va_list args;
va_start(args, fb0);
for (int i = 0; i < fb_num; i++) {
if (fb_itor) {
*fb_itor = rgb_panel->fbs[i];
fb_itor = va_arg(args, void **);
}
}
va_end(args);
return ESP_OK;
}
esp_err_t esp_lcd_rgb_panel_refresh(esp_lcd_panel_handle_t panel)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
ESP_RETURN_ON_FALSE(!rgb_panel->flags.stream_mode, ESP_ERR_INVALID_STATE, TAG, "refresh on demand is not enabled");
lcd_rgb_panel_start_transmission(rgb_panel);
return ESP_OK;
}
static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
int panel_id = rgb_panel->panel_id;
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
esp_intr_free(rgb_panel->intr);
lcd_ll_enable_clock(rgb_panel->hal.dev, false);
periph_module_disable(lcd_periph_signals.panels[panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
free(rgb_panel->bounce_buffer[0]);
free(rgb_panel->bounce_buffer[1]);
free(rgb_panel->fb);
if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock);
esp_pm_lock_delete(rgb_panel->pm_lock);
}
free(rgb_panel);
ESP_RETURN_ON_ERROR(lcd_rgb_panel_destory(rgb_panel), TAG, "destroy rgb panel(%d) failed", panel_id);
ESP_LOGD(TAG, "del rgb panel(%d)", panel_id);
return ESP_OK;
}
@ -365,17 +407,17 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
// configure blank region timing
lcd_ll_set_blank_cycles(rgb_panel->hal.dev, 1, 1); // RGB panel always has a front and back blank (porch region)
lcd_ll_set_horizontal_timing(rgb_panel->hal.dev, rgb_panel->timings.hsync_pulse_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res * rgb_panel->bits_per_pixel / rgb_panel->data_width,
rgb_panel->timings.hsync_front_porch);
lcd_ll_set_vertical_timing(rgb_panel->hal.dev, rgb_panel->timings.vsync_pulse_width,
rgb_panel->timings.vsync_back_porch, rgb_panel->timings.v_res,
rgb_panel->timings.vsync_front_porch);
// output hsync even in porch region
lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true);
// generate the hsync at the very begining of line
// generate the hsync at the very beginning of line
lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
// restart flush by hardware has some limitation, instead, the driver will restart the flush in the VSYNC end interrupt by software
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, false);
// send next frame automatically in stream mode
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
// trigger interrupt on the end of frame
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true);
// enable intr
@ -391,12 +433,20 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
static esp_err_t rgb_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_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
if (rgb_panel->fb == NULL) {
//Can't draw a bitmap to a non-existing framebuffer.
//This happens when e.g. we use an external callback to refill the bounce buffers.
return ESP_ERR_NOT_SUPPORTED;
}
ESP_RETURN_ON_FALSE(!rgb_panel->flags.no_fb, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed");
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
// check if we need to copy the draw buffer (pointed by the color_data) to the driver's frame buffer
bool do_copy = false;
if (color_data == rgb_panel->fbs[0]) {
rgb_panel->cur_fb_index = 0;
} else if (color_data == rgb_panel->fbs[1]) {
rgb_panel->cur_fb_index = 1;
} else {
// we do the copy only if the color_data is different from either frame buffer
do_copy = true;
}
// adjust the flush window by adding extra gap
x_start += rgb_panel->x_gap;
y_start += rgb_panel->y_gap;
@ -408,29 +458,36 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
y_start = MIN(y_start, rgb_panel->timings.v_res);
y_end = MIN(y_end, rgb_panel->timings.v_res);
// convert the frame buffer to 3D array
int bytes_per_pixel = rgb_panel->data_width / 8;
int bytes_per_pixel = rgb_panel->bits_per_pixel / 8;
int pixels_per_line = rgb_panel->timings.h_res;
uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line;
const uint8_t *from = (const uint8_t *)color_data;
// manipulate the frame buffer
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint8_t *to = rgb_panel->fb + (y_start * pixels_per_line + x_start) * bytes_per_pixel;
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 (rgb_panel->flags.fb_in_psram && !rgb_panel->bounce_buffer_size_bytes) {
// CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
// Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back.
uint32_t bytes_to_flush = (y_end - y_start) * bytes_per_line;
Cache_WriteBack_Addr((uint32_t)(rgb_panel->fb + y_start * bytes_per_line), bytes_to_flush);
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
if (do_copy) {
// copy the UI draw buffer into internal frame buffer
const uint8_t *from = (const uint8_t *)color_data;
uint32_t copy_bytes_per_line = (x_end - x_start) * bytes_per_pixel;
uint8_t *to = fb + (y_start * pixels_per_line + x_start) * bytes_per_pixel;
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;
}
}
// restart the new transmission
if (!rgb_panel->flags.stream_mode) {
lcd_rgb_panel_start_transmission(rgb_panel);
if (rgb_panel->flags.fb_in_psram && !rgb_panel->bb_size) {
// CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
// Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back
uint32_t bytes_to_flush = (y_end - y_start) * bytes_per_line;
Cache_WriteBack_Addr((uint32_t)(fb + y_start * bytes_per_line), bytes_to_flush);
}
if (!rgb_panel->bb_size) {
if (rgb_panel->flags.stream_mode) {
// the DMA will convey the new frame buffer next time
rgb_panel->dma_nodes[rgb_panel->num_dma_nodes - 1].next = rgb_panel->dma_links[rgb_panel->cur_fb_index];
rgb_panel->dma_nodes[rgb_panel->num_dma_nodes * 2 - 1].next = rgb_panel->dma_links[rgb_panel->cur_fb_index];
}
}
return ESP_OK;
@ -462,7 +519,7 @@ static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
rgb_panel->x_gap = x_gap;
rgb_panel->x_gap = y_gap;
rgb_panel->y_gap = y_gap;
return ESP_OK;
}
@ -562,173 +619,181 @@ static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_cloc
static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, uint8_t *buffer)
{
bool need_yield = false;
int bytes_per_pixel = panel->data_width / 8;
if (panel->on_bounce_empty) {
//We don't have a framebuffer here; we need to call a callback to refill the bounce buffer
//for us.
need_yield=panel->on_bounce_empty((void*)buffer, panel->bounce_pos_px,
panel->bounce_buffer_size_bytes, panel->bounce_buffer_cb_user_ctx);
int bytes_per_pixel = panel->bits_per_pixel / 8;
if (panel->flags.no_fb) {
if (panel->on_bounce_empty) {
// We don't have a frame buffer here; we need to call a callback to refill the bounce buffer
need_yield = panel->on_bounce_empty(&panel->base, buffer, panel->bounce_pos_px, panel->bb_size, panel->user_ctx);
}
} else {
//We do have a framebuffer; copy from there.
memcpy(buffer, &panel->fb[panel->bounce_pos_px * bytes_per_pixel], panel->bounce_buffer_size_bytes);
if (panel->flags.bb_do_cache_invalidate) {
//We don't need the bytes we copied from psram anymore.
//Make sure that if anything happened to have changed (because the line already was in cache) we write
//the data back.
Cache_WriteBack_Addr((uint32_t)&panel->fb[panel->bounce_pos_px * bytes_per_pixel], panel->bounce_buffer_size_bytes);
//Invalidate the data. Note: possible race: perhaps something on the other core can squeeze a write
//between this and the writeback, in which case that data gets discarded.
Cache_Invalidate_Addr((uint32_t)&panel->fb[panel->bounce_pos_px * bytes_per_pixel], panel->bounce_buffer_size_bytes);
// We do have frame buffer; copy from there.
// Note: if the cache is diabled, and accessing the PSRAM by DCACHE will crash.
memcpy(buffer, &panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
if (panel->flags.bb_invalidate_cache) {
// We don't need the bytes we copied from the psram anymore
// Make sure that if anything happened to have changed (because the line already was in cache) we write the data back.
Cache_WriteBack_Addr((uint32_t)&panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
// Invalidate the data.
// Note: possible race: perhaps something on the other core can squeeze a write between this and the writeback,
// in which case that data gets discarded.
Cache_Invalidate_Addr((uint32_t)&panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel], panel->bb_size);
}
}
panel->bounce_pos_px+=panel->bounce_buffer_size_bytes / bytes_per_pixel;
//If the bounce pos is larger than the framebuffer size, wrap around so the next isr starts pre-loading
//the next frame.
panel->bounce_pos_px += panel->bb_size / bytes_per_pixel;
// If the bounce pos is larger than the frame buffer size, wrap around so the next isr starts pre-loading the next frame.
if (panel->bounce_pos_px >= panel->fb_size / bytes_per_pixel) {
panel->bounce_pos_px=0;
panel->bounce_pos_px = 0;
}
if (!panel->on_bounce_empty) {
//Preload the next bit of buffer into psram.
Cache_Start_DCache_Preload((uint32_t)&panel->fb[panel->bounce_pos_px * bytes_per_pixel],
panel->bounce_buffer_size_bytes, 0);
if (!panel->flags.no_fb) {
// Preload the next bit of buffer from psram
Cache_Start_DCache_Preload((uint32_t)&panel->fbs[panel->cur_fb_index][panel->bounce_pos_px * bytes_per_pixel],
panel->bb_size, 0);
}
return need_yield;
}
//This is called in bounce buffer mode, when one bounce buffer has been fully sent to the LCD peripheral.
// This is called in bounce buffer mode, when one bounce buffer has been fully sent to the LCD peripheral.
static IRAM_ATTR bool lcd_rgb_panel_eof_handler(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data)
{
esp_rgb_panel_t *panel = (esp_rgb_panel_t*)user_data;
dma_descriptor_t *desc = (dma_descriptor_t*)event_data->tx_eof_desc_addr;
//Figure out which bounce buffer to write to.
//Note: what we receive is the *last* descriptor of this bounce buffer.
int bb=(desc==&panel->dma_nodes[panel->num_dma_nodes - 1])?1:0;
esp_rgb_panel_t *panel = (esp_rgb_panel_t *)user_data;
dma_descriptor_t *desc = (dma_descriptor_t *)event_data->tx_eof_desc_addr;
// Figure out which bounce buffer to write to.
// Note: what we receive is the *last* descriptor of this bounce buffer.
int bb = (desc == &panel->dma_nodes[panel->num_dma_nodes - 1]) ? 0 : 1;
return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]);
}
//If we restart GDMA, this many pixels will already have been transfered to the
//LCD peripheral. Looks like that has 16 pixels of FIFO plus one holding register.
#define LCD_FIFO_SIZE_PX 17
// If we restart GDMA, many pixels already have been transferred to the LCD peripheral.
// Looks like that has 16 pixels of FIFO plus one holding register.
#define LCD_FIFO_PRESERVE_SIZE_PX (GDMA_LL_L2FIFO_BASE_SIZE + 1)
static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel)
{
esp_err_t ret = ESP_OK;
if (panel->bounce_buffer_size_bytes == 0) {
// Create DMA descriptors for main framebuffer:
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes; i++) {
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
}
// loop end back to start (note: this is needed as we restart the DMA transaction in the VBlank,
// but it should already have LCD_FIFO_PIX bytes from the start of the buffer at that time)
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0];
// mount the frame buffer to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_nodes, panel->fb, panel->fb_size);
} else {
//Create DMA descriptors for bounce buffers:
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes; i++) {
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
}
// loop end back to start
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0];
// set eof on end of 1st and 2nd bounce buffer so we get an interrupt when they're fully sent
panel->dma_nodes[(panel->num_dma_nodes/2) - 1].dw0.suc_eof=1;
panel->dma_nodes[panel->num_dma_nodes - 1].dw0.suc_eof=1;
// mount the bounce buffers to the DMA descriptors
lcd_com_mount_dma_data(&panel->dma_nodes[0], panel->bounce_buffer[0], panel->bounce_buffer_size_bytes);
lcd_com_mount_dma_data(&panel->dma_nodes[(panel->num_dma_nodes/2)], panel->bounce_buffer[1], panel->bounce_buffer_size_bytes);
panel->dma_links[0] = &panel->dma_nodes[0];
panel->dma_links[1] = &panel->dma_nodes[panel->num_dma_nodes];
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes * 2; i++) {
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
}
//On restart, the data sent to the LCD peripheral needs to start 17 pixels after the FB start (the length of the
//lcd fifo) so we use a special DMA node to restart the DMA transaction.
if (panel->bb_size) {
// loop end back to start
panel->dma_nodes[panel->num_dma_nodes * 2 - 1].next = &panel->dma_nodes[0];
// mount the bounce buffers to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_links[0], panel->bounce_buffer[0], panel->bb_size);
lcd_com_mount_dma_data(panel->dma_links[1], panel->bounce_buffer[1], panel->bb_size);
} else {
if (panel->flags.stream_mode) {
// circle DMA descriptors chain for each frame buffer
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0];
panel->dma_nodes[panel->num_dma_nodes * 2 - 1].next = &panel->dma_nodes[panel->num_dma_nodes];
} else {
// one-off DMA descriptors chain
panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL;
panel->dma_nodes[panel->num_dma_nodes * 2 - 1].next = NULL;
}
// mount the frame buffer to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_links[0], panel->fbs[0], panel->fb_size);
if (panel->fbs[1]) {
lcd_com_mount_dma_data(panel->dma_links[1], panel->fbs[1], panel->fb_size);
}
}
#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
// On restart, the data sent to the LCD peripheral needs to start LCD_FIFO_PRESERVE_SIZE_PX pixels after the FB start
// so we use a dedicated DMA node to restart the DMA transaction
memcpy(&panel->dma_restart_node, &panel->dma_nodes[0], sizeof(panel->dma_restart_node));
int restart_skip_bytes=LCD_FIFO_SIZE_PX*sizeof(uint16_t);
uint8_t *p=(uint8_t*)panel->dma_restart_node.buffer;
panel->dma_restart_node.buffer=&p[restart_skip_bytes];
panel->dma_restart_node.dw0.length-=restart_skip_bytes;
panel->dma_restart_node.dw0.size-=restart_skip_bytes;
int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * sizeof(uint16_t);
uint8_t *p = (uint8_t *)panel->dma_restart_node.buffer;
panel->dma_restart_node.buffer = &p[restart_skip_bytes];
panel->dma_restart_node.dw0.length -= restart_skip_bytes;
panel->dma_restart_node.dw0.size -= restart_skip_bytes;
#endif
// alloc DMA channel and connect to LCD peripheral
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
};
ret = gdma_new_channel(&dma_chan_config, &panel->dma_chan);
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc DMA channel failed");
ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &panel->dma_chan), TAG, "alloc DMA channel failed");
gdma_connect(panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_transfer_ability_t ability = {
.psram_trans_align = panel->psram_trans_align,
.sram_trans_align = panel->sram_trans_align,
};
gdma_set_transfer_ability(panel->dma_chan, &ability);
if (panel->bounce_buffer_size_bytes != 0) {
// register callback to re-fill bounce buffers once they're fully sent
gdma_tx_event_callbacks_t cbs={0};
cbs.on_trans_eof = lcd_rgb_panel_eof_handler;
// we need to refill the bounce buffer in the DMA EOF interrupt, so only register the callback for bounce buffer mode
if (panel->bb_size) {
gdma_tx_event_callbacks_t cbs = {
.on_trans_eof = lcd_rgb_panel_eof_handler,
};
gdma_register_tx_event_callbacks(panel->dma_chan, &cbs, panel);
}
// the start of DMA should be prior to the start of LCD engine
gdma_start(panel->dma_chan, (intptr_t)panel->dma_nodes);
err:
return ret;
return ESP_OK;
}
static IRAM_ATTR void lcd_rgb_panel_restart_transmission(esp_rgb_panel_t *panel)
#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
static IRAM_ATTR void lcd_rgb_panel_restart_transmission_in_isr(esp_rgb_panel_t *panel)
{
if (panel->bounce_buffer_size_bytes != 0) {
//Catch de-synced framebuffer and reset if needed.
if (panel->bounce_pos_px > panel->bounce_buffer_size_bytes) panel->bounce_pos_px=0;
//Pre-fill bounce buffer 0, if the EOF ISR didn't do that already
if (panel->bounce_pos_px < panel->bounce_buffer_size_bytes/2) {
if (panel->bb_size) {
// Catch de-synced frame buffer and reset if needed.
if (panel->bounce_pos_px > panel->bb_size) {
panel->bounce_pos_px = 0;
}
// Pre-fill bounce buffer 0, if the EOF ISR didn't do that already
if (panel->bounce_pos_px < panel->bb_size / 2) {
lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[0]);
}
}
gdma_reset(panel->dma_chan);
// restart the DMA by a special DMA node
gdma_start(panel->dma_chan, (intptr_t)&panel->dma_restart_node);
if (panel->bounce_buffer_size_bytes != 0) {
//Fill 2nd bounce buffer while 1st is being sent out, if needed.
if (panel->bounce_pos_px < panel->bounce_buffer_size_bytes) {
if (panel->bb_size) {
// Fill 2nd bounce buffer while 1st is being sent out, if needed.
if (panel->bounce_pos_px < panel->bb_size) {
lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[0]);
}
}
}
#endif
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
{
// reset FIFO of DMA and LCD, incase there remains old frame data
gdma_reset(rgb_panel->dma_chan);
lcd_ll_stop(rgb_panel->hal.dev);
// check whether to update the PCLK frequency
portENTER_CRITICAL_SAFE(&rgb_panel->spinlock);
if (unlikely(rgb_panel->flags.need_update_pclk)) {
rgb_panel->flags.need_update_pclk = false;
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz);
}
portEXIT_CRITICAL_SAFE(&rgb_panel->spinlock);
lcd_ll_fifo_reset(rgb_panel->hal.dev);
//pre-fill bounce buffers if needed
if (rgb_panel->bounce_buffer_size_bytes != 0) {
// pre-fill bounce buffers if needed
if (rgb_panel->bb_size) {
rgb_panel->bounce_pos_px = 0;
lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[0]);
lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[1]);
}
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes);
// the start of DMA should be prior to the start of LCD engine
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_links[rgb_panel->cur_fb_index]);
// delay 1us is sufficient for DMA to pass data to LCD FIFO
// in fact, this is only needed when LCD pixel clock is set too high
esp_rom_delay_us(1);
// start LCD engine
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
lcd_ll_start(rgb_panel->hal.dev);
}
IRAM_ATTR static void lcd_rgb_panel_try_update_pclk(esp_rgb_panel_t *rgb_panel)
{
portENTER_CRITICAL_ISR(&rgb_panel->spinlock);
if (unlikely(rgb_panel->flags.need_update_pclk)) {
rgb_panel->flags.need_update_pclk = false;
rgb_panel->timings.pclk_hz = lcd_hal_cal_pclk_freq(&rgb_panel->hal, rgb_panel->src_clk_hz, rgb_panel->timings.pclk_hz);
}
portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
}
IRAM_ATTR static void lcd_default_isr_handler(void *args)
{
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args;
@ -738,24 +803,29 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status);
if (intr_status & LCD_LL_EVENT_VSYNC_END) {
// call user registered callback
if (rgb_panel->on_frame_trans_done) {
if (rgb_panel->on_frame_trans_done(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
if (rgb_panel->on_vsync) {
if (rgb_panel->on_vsync(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
}
}
// check whether to update the PCLK frequency, it should be safe to update the PCLK frequency in the VSYNC interrupt
lcd_rgb_panel_try_update_pclk(rgb_panel);
if (rgb_panel->flags.stream_mode) {
// As described above, we reset the GDMA channel every VBlank to stop permanent
// desyncs from happening.
#if CONFIG_LCD_RGB_RESTART_IN_VSYNC
// reset the GDMA channel every VBlank to stop permanent desyncs from happening.
// Note that this fix can lead to single-frame desyncs itself, as in: if this interrupt
// is late enough, the display will shift as the LCD controller already read out the
// first data bytes, and resetting DMA will re-send those. However, the single-frame
// desync this leads to is preferable to the permanent desync that could otherwise
// happen. It's also not super-likely as this interrupt has the entirety of the VBlank
// time to reset DMA.
lcd_rgb_panel_restart_transmission(rgb_panel);
lcd_rgb_panel_restart_transmission_in_isr(rgb_panel);
#endif
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}

View File

@ -9,29 +9,29 @@
extern "C" {
#endif
#define TEST_LCD_H_RES 480
#define TEST_LCD_V_RES 272
#define TEST_LCD_H_RES 800
#define TEST_LCD_V_RES 480
#define TEST_LCD_VSYNC_GPIO 48
#define TEST_LCD_HSYNC_GPIO 47
#define TEST_LCD_DE_GPIO 45
#define TEST_LCD_PCLK_GPIO 21
#define TEST_LCD_DATA0_GPIO 3 // B0
#define TEST_LCD_DATA1_GPIO 4 // B1
#define TEST_LCD_DATA2_GPIO 5 // B2
#define TEST_LCD_DATA3_GPIO 6 // B3
#define TEST_LCD_DATA4_GPIO 7 // B4
#define TEST_LCD_DATA5_GPIO 8 // G0
#define TEST_LCD_DATA6_GPIO 9 // G1
#define TEST_LCD_DATA7_GPIO 10 // G2
#define TEST_LCD_DATA8_GPIO 11 // G3
#define TEST_LCD_DATA9_GPIO 12 // G4
#define TEST_LCD_DATA10_GPIO 13 // G5
#define TEST_LCD_DATA11_GPIO 14 // R0
#define TEST_LCD_DATA12_GPIO 15 // R1
#define TEST_LCD_DATA13_GPIO 16 // R2
#define TEST_LCD_DATA14_GPIO 17 // R3
#define TEST_LCD_DATA15_GPIO 18 // R4
#define TEST_LCD_VSYNC_GPIO 3
#define TEST_LCD_HSYNC_GPIO 46
#define TEST_LCD_DE_GPIO 0
#define TEST_LCD_PCLK_GPIO 9
#define TEST_LCD_DATA0_GPIO 14 // B0
#define TEST_LCD_DATA1_GPIO 13 // B1
#define TEST_LCD_DATA2_GPIO 12 // B2
#define TEST_LCD_DATA3_GPIO 11 // B3
#define TEST_LCD_DATA4_GPIO 10 // B4
#define TEST_LCD_DATA5_GPIO 39 // G0
#define TEST_LCD_DATA6_GPIO 38 // G1
#define TEST_LCD_DATA7_GPIO 45 // G2
#define TEST_LCD_DATA8_GPIO 48 // G3
#define TEST_LCD_DATA9_GPIO 47 // G4
#define TEST_LCD_DATA10_GPIO 21 // G5
#define TEST_LCD_DATA11_GPIO 1 // R0
#define TEST_LCD_DATA12_GPIO 2 // R1
#define TEST_LCD_DATA13_GPIO 42 // R2
#define TEST_LCD_DATA14_GPIO 41 // R3
#define TEST_LCD_DATA15_GPIO 40 // R4
#define TEST_LCD_DISP_EN_GPIO -1
#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000)

View File

@ -24,12 +24,15 @@
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
static esp_lcd_panel_handle_t test_rgb_panel_initialization(bool stream_mode, esp_lcd_rgb_panel_frame_trans_done_cb_t cb, void *user_data)
static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, size_t bpp, size_t bb_pixels, bool refresh_on_demand,
esp_lcd_rgb_panel_vsync_cb_t vsync_cb, void *user_data)
{
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16,
.data_width = data_width,
.psram_trans_align = 64,
.bounce_buffer_size_px = bb_pixels,
.bits_per_pixel = bpp,
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = TEST_LCD_DISP_EN_GPIO,
.pclk_gpio_num = TEST_LCD_PCLK_GPIO,
@ -65,13 +68,16 @@ static esp_lcd_panel_handle_t test_rgb_panel_initialization(bool stream_mode, es
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.on_frame_trans_done = cb,
.user_ctx = user_data,
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
.flags.relax_on_idle = !stream_mode,
.flags.refresh_on_demand = refresh_on_demand,
};
TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
esp_lcd_rgb_panel_event_callbacks_t cbs = {
.on_vsync = vsync_cb,
};
TEST_ESP_OK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, user_data));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
@ -84,7 +90,7 @@ TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]")
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(true, NULL, NULL);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, NULL, NULL);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
@ -98,7 +104,27 @@ TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]")
free(img);
}
TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
TEST_CASE("lcd_rgb_panel_8bit_interface", "[lcd]")
{
uint8_t *img = malloc(100 * 100 * 3);
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
// bpp for RGB888 is 24
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(8, 24, 0, false, NULL, NULL);
uint8_t color_byte = esp_random() & 0xFF;
printf("flush random color block 0x%x\r\n", color_byte);
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, 100 * 100 * 3);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
vTaskDelay(pdMS_TO_TICKS(2000));
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(img);
}
TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
TaskHandle_t task_to_notify = (TaskHandle_t)user_ctx;
BaseType_t high_task_wakeup;
@ -106,14 +132,39 @@ TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_trans_done(esp_lcd_panel_handl
return high_task_wakeup == pdTRUE;
}
TEST_CASE("lcd_rgb_panel_one_shot_mode", "[lcd]")
TEST_CASE("lcd_rgb_panel_refresh_on_demand", "[lcd]")
{
uint8_t *img = malloc(TEST_IMG_SIZE);
TEST_ASSERT_NOT_NULL(img);
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
printf("initialize RGB panel with ont-shot mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(false, test_rgb_panel_trans_done, cur_task);
printf("initialize RGB panel with non-stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, true, test_rgb_panel_trans_done, cur_task);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
esp_lcd_rgb_panel_refresh(panel_handle);
// wait for flush done
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
}
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(img);
}
TEST_CASE("lcd_rgb_panel_bounce_buffer", "[lcd]")
{
uint8_t *img = malloc(TEST_IMG_SIZE);
TEST_ASSERT_NOT_NULL(img);
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
printf("initialize RGB panel with non-stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 10 * TEST_LCD_H_RES, false, test_rgb_panel_trans_done, cur_task);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
@ -130,8 +181,36 @@ TEST_CASE("lcd_rgb_panel_one_shot_mode", "[lcd]")
free(img);
}
TEST_CASE("lcd_rgb_panel_update_pclk", "[lcd]")
{
uint8_t *img = malloc(TEST_IMG_SIZE);
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, NULL, NULL);
printf("flush one clock block to the LCD\r\n");
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
printf("The LCD driver should keep flushing the color block in the background (as it's in stream mode)\r\n");
vTaskDelay(pdMS_TO_TICKS(1000));
printf("Update the PCLK in the background\r\n");
const uint32_t test_pclk_freq[] = {10000000, 12000000, 8000000};
for (size_t i = 0; i < sizeof(test_pclk_freq) / sizeof(test_pclk_freq[0]); i++) {
esp_lcd_rgb_panel_set_pclk(panel_handle, test_pclk_freq[i]);
vTaskDelay(pdMS_TO_TICKS(1000));
}
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(img);
}
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_count_in_callback(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
TEST_LCD_CALLBACK_ATTR static bool test_rgb_panel_count_in_callback(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
uint32_t *count = (uint32_t *)user_ctx;
*count = *count + 1;
@ -142,7 +221,7 @@ static void IRAM_ATTR test_disable_flash_cache(void)
{
// disable flash cache
spi_flash_guard_get()->start();
esp_rom_delay_us(100000);
esp_rom_delay_us(200000);
// enable flash cache
spi_flash_guard_get()->end();
}
@ -155,7 +234,7 @@ TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]")
uint32_t callback_calls = 0;
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(true, test_rgb_panel_count_in_callback, &callback_calls);
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(16, 16, 0, false, test_rgb_panel_count_in_callback, &callback_calls);
printf("flush one clock block to the LCD\r\n");
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
@ -169,7 +248,7 @@ TEST_CASE("lcd_rgb_panel_iram_safe", "[lcd]")
test_disable_flash_cache();
printf("the RGB ISR handle should keep working while the flash cache is disabled\r\n");
printf("callback calls: %d\r\n", callback_calls);
TEST_ASSERT(callback_calls > 5);
TEST_ASSERT(callback_calls > 2);
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));

View File

@ -4,7 +4,7 @@ LCD
Introduction
------------
ESP chips can generate various kinds of timings that needed by common LCDs on the market, like SPI LCD, I80 LCD (a.k.a Intel 8080 parallel LCD), RGB LCD, I2C LCD, etc. The ``esp_lcd`` component is officially to support those LCDs with a group of universal APIs across chips.
ESP chips can generate various kinds of timings that needed by common LCDs on the market, like SPI LCD, I80 LCD (a.k.a Intel 8080 parallel LCD), RGB/SRGB LCD, I2C LCD, etc. The ``esp_lcd`` component is officially to support those LCDs with a group of universal APIs across chips.
Functional Overview
-------------------
@ -12,11 +12,214 @@ Functional Overview
In ``esp_lcd``, an LCD panel is represented by :cpp:type:`esp_lcd_panel_handle_t`, which plays the role of an **abstract frame buffer**, regardless of the frame memory is allocated inside ESP chip or in external LCD controller. Based on the location of the frame buffer, the LCD panel allocation functions are mainly grouped into the following categories:
- ``RGB LCD panel`` - is simply based on a group of specific synchronous signals indicating where to start and stop a frame.
- ``Controller based LCD panel`` involves multiple steps to get a panel handle, like bus allocation, IO device registration and controller driver install.
After we get the LCD handle, the remaining LCD operations are the same for different LCD interfaces and vendors.
.. only:: SOC_LCD_RGB_SUPPORTED
RGB Interfaced LCD
------------------
RGB LCD panel is allocated in one step: :cpp:func:`esp_lcd_new_rgb_panel`, with various configurations specified by :cpp:type:`esp_lcd_rgb_panel_config_t`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::clk_src` selects the clock source for the RGB LCD controller. The available clock sources are listed in :cpp:type:`lcd_clock_source_t`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::data_width` set number of data lines used by the RGB interface. Currently, the supported value can be 8 or 16.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` set the number of bits per pixel. This is different from :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. By default, if you set this field to 0, the driver will automatically adjust the bpp to the :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. But in some cases, these two value must be different. For example, a Serial RGB interface LCD only needs ``8`` data lines, but the color width can reach to ``RGB888``, i.e. the :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` should be set to ``24``.
- :cpp:member:`esp_lcd_rgb_panel_config_t::hsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::vsync_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::de_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::pclk_gpio_num`, :cpp:member:`esp_lcd_rgb_panel_config_t::disp_gpio_num` and :cpp:member:`esp_lcd_rgb_panel_config_t::data_gpio_nums` are the GPIO pins used by the RGB LCD controller. If some of them are not used, please set it to `-1`.
- :cpp:member:`esp_lcd_rgb_panel_config_t::sram_trans_align` and :cpp:member:`esp_lcd_rgb_panel_config_t::psram_trans_align` set the alignment of the allocated frame buffer. Internally, the DMA transfer ability will adjust against these alignment values. A higher alignment value can lead to a bigger DMA burst size. Please note, the alignment value must be a power of 2.
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` set the size of bounce buffer. This is only necessary for a so-called "bounce buffer" mode. Please refer to :ref:`bounce_buffer_with_single_psram_frame_buffer` for more information.
- :cpp:member:`esp_lcd_rgb_panel_config_t::timings` sets the LCD panel specific timing parameters. All required parameters are listed in the :cpp:type:`esp_lcd_rgb_timing_t`, including the LCD resolution and blanking porches. Please fill them according to the datasheet of your LCD.
- :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` sets whether to allocate the frame buffer from PSRAM or not. Please refer to :ref:`single_frame_buffer_in_psram` for more information.
- :cpp:member:`esp_lcd_rgb_panel_config_t::double_fb` sets whether to enable the double frame buffer mode. Please refer to :ref:`double_frame_buffer_in_psram` for more information.
- :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` sets whether to use the :ref:`bounce_buffer_only` mode.
RGB LCD Frame Buffer Operation Modes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Most of the time, the RGB LCD driver should maintain at least one screen sized frame buffer. According to the number and location of the frame buffer, the driver provides several different buffer modes.
Single Frame Buffer in Internal Memory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is the default and simplest and you don't have to specify flags or bounce buffer options. A frame buffer is allocated from the internal memory. The frame data is read out by DMA to the LCD verbatim. It needs no CPU intervention to function, but it has the downside that it uses up a fair bit of the limited amount of internal memory.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _single_frame_buffer_in_psram:
Single Frame Buffer in PSRAM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If you have PSRAM and want to store the frame buffer there rather than in the limited internal memory, the LCD peripheral will use EDMA to fetch frame data directly from the PSRAM, bypassing the internal cache. You can enable this feature by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` to ``true``. The downside of this is that when both the CPU as well as EDMA need access to the PSRAM, the bandwidth will be **shared** between them, that is, EDMA gets half and the CPUs the other half. If there're other peripherals using EDMA as well, with a high enough pixel clock this can lead to starvation of the LCD peripheral, leading to display corruption. However, if the pixel clock is low enough for this not to be an issue, this is a solution that uses almost no CPU intervention.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _double_frame_buffer_in_psram:
Double Frame Buffer in PSRAM
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To avoid tearing effect, using two screen sized frame buffers is the easiest approach. In this mode, the frame buffer can only be allocated from PSRAM, because of the limited internal memory. The frame buffer that the CPU write to and the frame buffer that the EDMA read from are guaranteed to be different and independent. The EDMA will only switch between the two frame buffers when the previous write operation is finished and the current frame has been sent to the LCD. The downside of this mode is that, you have to maintain the synchronization between the two frame buffers.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
.flags.double_fb = true, // allocate double frame buffer
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
.. _bounce_buffer_with_single_psram_frame_buffer:
Bounce Buffer with Single PSRAM Frame Buffer
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This mode allocates two so-called ``bounce buffers`` from the internal memory, and a main frame buffer that is still in PSRAM. This mode is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` flag and additionally specifying a non-zero :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. The bounce buffers only need to be large enough to hold a few lines of display data, which is significantly less than the main frame buffer. The LCD peripheral will use DMA to read data from one of the bounce buffers, and meanwhile an interrupt routine will use the CPU DCache to copy data from the main PSRAM frame buffer into the other bounce buffer. Once the LCD peripheral has finished reading the bounce buffer, the two buffers change place and the CPU can fill the others. The advantage of this mode is that, you can achieve higher pixel clock frequency. As the bounce buffers are larger than the FIFOs in the EDMA path, this method is also more robust against short bandwidth spikes. The downside is a major increase in CPU use and the LCD **CAN'T** work if the cache is disabled by flash operations, e.g. OTA or NVS write.
.. code:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.clk_src = LCD_CLK_SRC_DEFAULT,
.bounce_buffer_size_px = 10 * EXAMPLE_LCD_H_RES, // allocate 10 lines data as bounce buffer from internal memory
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.vsync_gpio_num = EXAMPLE_PIN_NUM_VSYNC,
.hsync_gpio_num = EXAMPLE_PIN_NUM_HSYNC,
.de_gpio_num = EXAMPLE_PIN_NUM_DE,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0,
EXAMPLE_PIN_NUM_DATA1,
EXAMPLE_PIN_NUM_DATA2,
// other GPIOs
// The number of GPIOs here should be the same to the value of `data_width` above
...
},
// The timing parameters should refer to your LCD spec
.timings = {
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.h_res = EXAMPLE_LCD_H_RES,
.v_res = EXAMPLE_LCD_V_RES,
.hsync_back_porch = 40,
.hsync_front_porch = 20,
.hsync_pulse_width = 1,
.vsync_back_porch = 8,
.vsync_front_porch = 4,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = true, // allocate frame buffer from PSRAM
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
Note that this mode also allows for a :cpp:member:`esp_lcd_rgb_panel_config_t::bb_invalidate_cache` flag to be set. Enabling this frees up the cache lines after they're used to read out the frame buffer data from PSRAM, but it may lead to slight corruption if the other core writes data to the frame buffer at the exact time the cache lines are freed up. (Technically, a write to the frame buffer can be ignored if it falls between the cache writeback and the cache invalidate calls.)
.. _bounce_buffer_only:
Bounce Buffer Only
~~~~~~~~~~~~~~~~~~
This mode is similar to the :ref:`bounce_buffer_with_single_psram_frame_buffer`, but there is no PSRAM frame buffer initialized by the LCD driver. Instead, the user supplies a callback function that is responsible for filling the bounce buffers. As this driver does not care where the written pixels come from, this allows for the callback doing e.g. on-the-fly conversion from a smaller, 8-bit-per-pixel PSRAM frame buffer to an 16-bit LCD, or even procedurally-generated frame-buffer-less graphics. This option is selected by setting the :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` flag and supplying a :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` value. And then register the :cpp:member:`esp_lcd_rgb_panel_event_callbacks_t::on_bounce_empty` callback by calling :cpp:func:`esp_lcd_rgb_panel_register_event_callbacks`.
.. note::
It should never happen in a well-designed embedded application, but it can in theory be possible that the DMA cannot deliver data as fast as the LCD consumes it. In the {IDF_TARGET_NAME} hardware, this leads to the LCD simply outputting dummy bytes while DMA waits for data. If we were to run DMA in a stream fashion, this would mean a de-sync between the LCD address the DMA reads the data for and the LCD address the LCD peripheral thinks it outputs data for, leading to a **permanently** shifted image.
In order to stop this from happening, you can enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, so the driver will restart the DMA in the VBlank interrupt; this way we always know where it starts.
Application Example
-------------------

View File

@ -267,6 +267,9 @@ LCD
- The LCD panel initialization flow is slightly changed. Now the :cpp:func:`esp_lcd_panel_init` won't turn on the display automatically. User needs to call :cpp:func:`esp_lcd_panel_disp_on_off` to manually turn on the display. Note, this is different from turning on backlight. With this breaking change, user can flush a predefined pattern to the screen before turning on the screen. This can help avoid random noise on the screen after a power on reset.
- :cpp:func:`esp_lcd_panel_disp_off` is deprecated, please use :cpp:func:`esp_lcd_panel_disp_on_off` instead.
- ``dc_as_cmd_phase`` is removed. The SPI LCD driver currently doesn't support a 9bit SPI LCD. Please always use a dedicated GPIO to control the LCD D/C line.
- The way to register RGB panel event callbacks has been moved from the :cpp:type:`esp_lcd_rgb_panel_config_t` into a separate API :cpp:func:`esp_lcd_rgb_panel_register_event_callbacks`. However, the event callback signature is not changed.
- Previous ``relax_on_idle`` flag in :cpp:type:`esp_lcd_rgb_panel_config_t` has been renamed into :cpp:member:`esp_lcd_rgb_panel_config_t::refresh_on_demand`, which expresses the same meaning but with a clear name.
- If the RGB LCD is created with the ``refresh_on_demand`` flag enabled, the driver won't start a refresh in the :cpp:func:`esp_lcd_panel_draw_bitmap`. Now you have to call :cpp:func:`esp_lcd_rgb_panel_refresh` to refresh the screen by yourself.
.. only:: SOC_MCPWM_SUPPORTED

View File

@ -1,16 +1,22 @@
| Supported Targets | ESP32-S3 |
| ----------------- | -------- |
# RGB panel example
[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) supports RGB interfaced LCD panel, with a frame buffer managed by the driver itself.
# RGB LCD Panel Example
This example shows the general process of installing an RGB panel driver, and displays a scatter chart on the screen based on the LVGL library. For more information about porting the LVGL library, please refer to [another lvgl porting example](../i80_controller/README.md).
[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) supports RGB interfaced LCD panel, with one or two frame buffer(s) managed by the driver itself.
This example shows the general process of installing an RGB panel driver, and displays a scatter chart on the screen based on the LVGL library. For more information about porting the LVGL library, please refer to [official porting guide](https://docs.lvgl.io/master/porting/index.html). This example uses two kinds of **buffering mode** based on the number of frame buffers:
| Number of Frame Buffers | LVGL buffering mode | Way to avoid tear effect |
|-------------------------|---------------------|-------------------------------------------------------------------------------------------------------------|
| 1 | Two buffers | Extra synchronization mechanism is needed, e.g. using semaphore. |
| 2 | Full refresh | There's no intersection between writing to an offline frame buffer and reading from an online frame buffer. |
## How to use the example
### Hardware Required
* An ESP development board, which has RGB LCD peripheral supported and PSRAM onboard
* An ESP development board, which has RGB LCD peripheral supported and **Octal PSRAM** onboard
* A general RGB panel, 16 bit-width, with HSYNC, VSYNC and DE signal
* An USB cable for power supply and programming
@ -46,7 +52,15 @@ The GPIO number used by this example can be changed in [lvgl_example_main.c](mai
Especially, please pay attention to the level used to turn on the LCD backlight, some LCD module needs a low level to turn it on, while others take a high level. You can change the backlight level macro `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL` in [lvgl_example_main.c](main/rgb_lcd_example_main.c).
If the RGB LCD panel only supports DE mode, you can bypass the `HSYNC` and `VSYNC` signals, by assigning `EXAMPLE_PIN_NUM_HSYNC` and `EXAMPLE_PIN_NUM_VSYNC` with `-1`.
If the RGB LCD panel only supports DE mode, you can even bypass the `HSYNC` and `VSYNC` signals, by assigning `EXAMPLE_PIN_NUM_HSYNC` and `EXAMPLE_PIN_NUM_VSYNC` with `-1`.
### Configure
Run `idf.py menuconfig` and go to `Example Configuration`:
1. Choose whether to `Use double Frame Buffer`
2. Choose whether to `Avoid tearing effect` (available only when step `1` was chosen to false)
3. Choose whether to `Use bounce buffer` (available only when step `1` was chosen to false)
### Build and Flash
@ -63,15 +77,19 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
```bash
...
I (0) cpu_start: Starting scheduler on APP CPU.
I (731) spiram: Reserving pool of 32K of internal memory for DMA/internal allocations
I (731) example: Turn off LCD backlight
I (731) gpio: GPIO[39]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (731) example: Install RGB panel driver
I (741) example: Turn on LCD backlight
I (741) example: Initialize LVGL library
I (741) example: Register display driver to LVGL
I (741) example: Install LVGL tick timer
I (741) example: Display LVGL Scatter Chart
I (856) esp_psram: Reserving pool of 32K of internal memory for DMA/internal allocations
I (856) example: Create semaphores
I (866) example: Turn off LCD backlight
I (866) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
I (876) example: Install RGB LCD panel driver
I (906) example: Register event callbacks
I (906) example: Initialize RGB LCD panel
I (906) example: Turn on LCD backlight
I (906) example: Initialize LVGL library
I (916) example: Allocate separate LVGL draw buffers from PSRAM
I (916) example: Register display driver to LVGL
I (926) example: Install LVGL tick timer
I (926) example: Display LVGL Scatter Chart
...
```
@ -80,9 +98,16 @@ I (741) example: Display LVGL Scatter Chart
* Why the LCD doesn't light up?
* Check the backlight's turn-on level, and update it in `EXAMPLE_LCD_BK_LIGHT_ON_LEVEL`
* No memory for frame buffer
* The frame buffer of RGB panel is located in ESP side (unlike other controller based LCDs, where the frame buffer is located in external chip). As the frame buffer usually consumes much RAM (depends on the LCD resolution and color depth), we recommend to put the frame buffer into PSRAM (like what we do in this example). However, putting frame buffer in PSRAM will limit the PCLK to around 12MHz (due to the bandwidth of PSRAM).
* The frame buffer of RGB panel is located in ESP side (unlike other controller based LCDs, where the frame buffer is located in external chip). As the frame buffer usually consumes much RAM (depends on the LCD resolution and color depth), we recommend to put the frame buffer into PSRAM (like what we do in this example). However, putting frame buffer in PSRAM will limit the maximum PCLK due to the bandwidth of **SPI0**.
* LCD screen drift
* Slow down the PCLK frequency
* Adjust other timing parameters like PCLK clock edge (by `pclk_active_neg`), sync porches like HBP (by `hsync_back_porch`) according to your LCD spec
* Adjust other timing parameters like PCLK clock edge (by `pclk_active_neg`), sync porches like VBP (by `vsync_back_porch`) according to your LCD spec
* Enable `CONFIG_SPIRAM_FETCH_INSTRUCTIONS` and `CONFIG_SPIRAM_RODATA`, which can saves some bandwidth of SPI0 from being consumed by ICache.
* LCD screen tear effect
* Using double frame buffers
* Or adding an extra synchronization mechanism between writing (by Cache) and reading (by EDMA) the frame buffer.
* Low PCLK frequency
* Enable `CONFIG_EXAMPLE_USE_BOUNCE_BUFFER`, which will make the LCD controller fetch data from internal SRAM (instead of the PSRAM), but at the cost of increasing CPU usage.
* Enable `CONFIG_SPIRAM_FETCH_INSTRUCTIONS` and `CONFIG_SPIRAM_RODATA` can also help if the you're not using the bounce buffer mode. These two configurations can save some **SPI0** bandwidth from being consumed by ICache.
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -0,0 +1,21 @@
menu "Example Configuration"
config EXAMPLE_DOUBLE_FB
bool "Use double Frame Buffer"
default "n"
help
Enable this option, driver will allocate two frame buffers.
config EXAMPLE_USE_BOUNCE_BUFFER
depends on !EXAMPLE_DOUBLE_FB
bool "Use bounce buffer"
help
Enable bounce buffer mode can achieve higher PCLK frequency at the cost of higher CPU consumption.
config EXAMPLE_AVOID_TEAR_EFFECT_WITH_SEM
depends on !EXAMPLE_DOUBLE_FB
bool "Avoid tearing effect"
default "y"
help
Enable this option, the example will use a pair of semaphores to avoid the tearing effect.
Note, if the Double Frame Buffer is used, then we can also avoid the tearing effect without the lock.
endmenu

View File

@ -5,8 +5,10 @@
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_timer.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_rgb.h"
@ -20,40 +22,57 @@ static const char *TAG = "example";
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////// Please update the following configuration according to your LCD spec //////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000)
#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000)
#define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL 1
#define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL
#define EXAMPLE_PIN_NUM_BK_LIGHT 39
#define EXAMPLE_PIN_NUM_HSYNC 47
#define EXAMPLE_PIN_NUM_VSYNC 48
#define EXAMPLE_PIN_NUM_DE 45
#define EXAMPLE_PIN_NUM_PCLK 21
#define EXAMPLE_PIN_NUM_DATA0 3 // B0
#define EXAMPLE_PIN_NUM_DATA1 4 // B1
#define EXAMPLE_PIN_NUM_DATA2 5 // B2
#define EXAMPLE_PIN_NUM_DATA3 6 // B3
#define EXAMPLE_PIN_NUM_DATA4 7 // B4
#define EXAMPLE_PIN_NUM_DATA5 8 // G0
#define EXAMPLE_PIN_NUM_DATA6 9 // G1
#define EXAMPLE_PIN_NUM_DATA7 10 // G2
#define EXAMPLE_PIN_NUM_DATA8 11 // G3
#define EXAMPLE_PIN_NUM_DATA9 12 // G4
#define EXAMPLE_PIN_NUM_DATA10 13 // G5
#define EXAMPLE_PIN_NUM_DATA11 14 // R0
#define EXAMPLE_PIN_NUM_DATA12 15 // R1
#define EXAMPLE_PIN_NUM_DATA13 16 // R2
#define EXAMPLE_PIN_NUM_DATA14 17 // R3
#define EXAMPLE_PIN_NUM_DATA15 18 // R4
#define EXAMPLE_PIN_NUM_BK_LIGHT 4
#define EXAMPLE_PIN_NUM_HSYNC 46
#define EXAMPLE_PIN_NUM_VSYNC 3
#define EXAMPLE_PIN_NUM_DE 0
#define EXAMPLE_PIN_NUM_PCLK 9
#define EXAMPLE_PIN_NUM_DATA0 14 // B0
#define EXAMPLE_PIN_NUM_DATA1 13 // B1
#define EXAMPLE_PIN_NUM_DATA2 12 // B2
#define EXAMPLE_PIN_NUM_DATA3 11 // B3
#define EXAMPLE_PIN_NUM_DATA4 10 // B4
#define EXAMPLE_PIN_NUM_DATA5 39 // G0
#define EXAMPLE_PIN_NUM_DATA6 38 // G1
#define EXAMPLE_PIN_NUM_DATA7 45 // G2
#define EXAMPLE_PIN_NUM_DATA8 48 // G3
#define EXAMPLE_PIN_NUM_DATA9 47 // G4
#define EXAMPLE_PIN_NUM_DATA10 21 // G5
#define EXAMPLE_PIN_NUM_DATA11 1 // R0
#define EXAMPLE_PIN_NUM_DATA12 2 // R1
#define EXAMPLE_PIN_NUM_DATA13 42 // R2
#define EXAMPLE_PIN_NUM_DATA14 41 // R3
#define EXAMPLE_PIN_NUM_DATA15 40 // R4
#define EXAMPLE_PIN_NUM_DISP_EN -1
// The pixel number in horizontal and vertical
#define EXAMPLE_LCD_H_RES 480
#define EXAMPLE_LCD_V_RES 272
#define EXAMPLE_LCD_H_RES 800
#define EXAMPLE_LCD_V_RES 480
#define EXAMPLE_LVGL_TICK_PERIOD_MS 2
// we use two semaphores to sync the VSYNC event and the LVGL task, to avoid potential tearing effect
#if CONFIG_EXAMPLE_AVOID_TEAR_EFFECT_WITH_SEM
SemaphoreHandle_t sem_vsync_end;
SemaphoreHandle_t sem_gui_ready;
#endif
extern void example_lvgl_demo_ui(lv_obj_t *scr);
static bool example_on_vsync_event(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_data)
{
BaseType_t high_task_awoken = pdFALSE;
#if CONFIG_EXAMPLE_AVOID_TEAR_EFFECT_WITH_SEM
if (xSemaphoreTakeFromISR(sem_gui_ready, &high_task_awoken) == pdTRUE) {
xSemaphoreGiveFromISR(sem_vsync_end, &high_task_awoken);
}
#endif
return high_task_awoken == pdTRUE;
}
static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
@ -61,7 +80,11 @@ static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
// copy a buffer's content to a specific area of the display
#if CONFIG_EXAMPLE_AVOID_TEAR_EFFECT_WITH_SEM
xSemaphoreGive(sem_gui_ready);
xSemaphoreTake(sem_vsync_end, portMAX_DELAY);
#endif
// 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);
}
@ -77,18 +100,31 @@ void app_main(void)
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv; // contains callback functions
#if CONFIG_EXAMPLE_AVOID_TEAR_EFFECT_WITH_SEM
ESP_LOGI(TAG, "Create semaphores");
sem_vsync_end = xSemaphoreCreateBinary();
assert(sem_vsync_end);
sem_gui_ready = xSemaphoreCreateBinary();
assert(sem_gui_ready);
#endif
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
#endif
ESP_LOGI(TAG, "Install RGB panel driver");
ESP_LOGI(TAG, "Install RGB LCD panel driver");
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, // RGB565 in parallel mode, thus 16bit in width
.psram_trans_align = 64,
#if CONFIG_EXAMPLE_USE_BOUNCE_BUFFER
.bounce_buffer_size_px = 10 * EXAMPLE_LCD_H_RES,
#endif
.clk_src = LCD_CLK_SRC_DEFAULT,
.disp_gpio_num = EXAMPLE_PIN_NUM_DISP_EN,
.pclk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
@ -126,25 +162,46 @@ void app_main(void)
.vsync_pulse_width = 1,
.flags.pclk_active_neg = true,
},
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
.flags.fb_in_psram = true, // allocate frame buffer in PSRAM
#if CONFIG_EXAMPLE_DOUBLE_FB
.flags.double_fb = true, // allocate double frame buffer
#endif // CONFIG_EXAMPLE_DOUBLE_FB
};
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
ESP_LOGI(TAG, "Register event callbacks");
esp_lcd_rgb_panel_event_callbacks_t cbs = {
.on_vsync = example_on_vsync_event,
};
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, &disp_drv));
ESP_LOGI(TAG, "Initialize RGB LCD panel");
ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
#endif
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
// alloc draw buffers used by LVGL from PSRAM
lv_color_t *buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
void *buf1 = NULL;
void *buf2 = NULL;
#if CONFIG_EXAMPLE_DOUBLE_FB
ESP_LOGI(TAG, "Use frame buffers as LVGL draw buffers");
ESP_ERROR_CHECK(esp_lcd_rgb_panel_get_frame_buffer(panel_handle, 2, &buf1, &buf2));
// initialize LVGL draw buffers
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES);
#else
ESP_LOGI(TAG, "Allocate separate LVGL draw buffers from PSRAM");
buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
assert(buf1);
lv_color_t *buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
assert(buf2);
// initialize LVGL draw buffers
lv_disp_draw_buf_init(&disp_buf, buf1, buf2, EXAMPLE_LCD_H_RES * 100);
#endif // CONFIG_EXAMPLE_DOUBLE_FB
ESP_LOGI(TAG, "Register display driver to LVGL");
lv_disp_drv_init(&disp_drv);
@ -153,6 +210,9 @@ void app_main(void)
disp_drv.flush_cb = example_lvgl_flush_cb;
disp_drv.draw_buf = &disp_buf;
disp_drv.user_data = panel_handle;
#if CONFIG_EXAMPLE_DOUBLE_FB
disp_drv.full_refresh = true; // the full_refresh mode can maintain the synchronization between the two frame buffers
#endif
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
ESP_LOGI(TAG, "Install LVGL tick timer");

View File

@ -0,0 +1,28 @@
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32s3
@pytest.mark.octal_psram
@pytest.mark.parametrize(
'config',
[
'single_fb_with_bb',
'single_fb_no_bb',
'double_fb',
],
indirect=True,
)
def test_rgb_lcd_lvgl(dut: Dut) -> None:
dut.expect_exact('example: Turn off LCD backlight')
dut.expect_exact('example: Install RGB LCD panel driver')
dut.expect_exact('example: Register event callbacks')
dut.expect_exact('example: Initialize RGB LCD panel')
dut.expect_exact('example: Turn on LCD backlight')
dut.expect_exact('example: Initialize LVGL library')
dut.expect_exact('example: Register display driver to LVGL')
dut.expect_exact('example: Install LVGL tick timer')
dut.expect_exact('example: Display LVGL Scatter Chart')

View File

@ -0,0 +1 @@
CONFIG_EXAMPLE_DOUBLE_FB=y

View File

@ -0,0 +1,2 @@
CONFIG_EXAMPLE_DOUBLE_FB=n
CONFIG_EXAMPLE_USE_BOUNCE_BUFFER=n

View File

@ -0,0 +1,2 @@
CONFIG_EXAMPLE_DOUBLE_FB=n
CONFIG_EXAMPLE_USE_BOUNCE_BUFFER=y

View File

@ -1,3 +1,8 @@
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
# Enabling the following configurations can help increase the PCLK frequency in the case when
# the Frame Buffer is allocated from the PSRAM and fetched by EDMA
CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
CONFIG_SPIRAM_RODATA=y