forked from espressif/esp-idf
feat(lcd): support rgb lcd driver for esp32p4
This commit is contained in:
@ -115,6 +115,10 @@ uintptr_t gdma_link_get_head_addr(gdma_link_list_handle_t list);
|
||||
* v item_index
|
||||
* Link B: B1 --> B2 --> B3 --> B4
|
||||
*
|
||||
* After concatenation:
|
||||
* Link A: A1 --> B3 --> B4
|
||||
* Link B: B1 --> B2 --> B3 --> B4
|
||||
*
|
||||
* @param[in] first_link First link list handle, allocated by `gdma_new_link_list`
|
||||
* @param[in] first_link_item_index Index of the item in the first link list (-1 means the last item)
|
||||
* @param[in] second_link Second link list handle, allocated by `gdma_new_link_list`
|
||||
|
@ -1,47 +1,43 @@
|
||||
menu "LCD and Touch Panel"
|
||||
comment "LCD Touch Drivers are maintained in the ESP Component Registry"
|
||||
menu "ESP-Driver:LCD Controller Configurations"
|
||||
config LCD_ENABLE_DEBUG_LOG
|
||||
bool "Enable debug log"
|
||||
default n
|
||||
help
|
||||
whether to enable the debug log message for LCD driver.
|
||||
Note that, this option only controls the LCD driver log, won't affect other drivers.
|
||||
|
||||
menu "LCD Peripheral Configuration"
|
||||
config LCD_ENABLE_DEBUG_LOG
|
||||
bool "Enable debug log"
|
||||
if SOC_LCD_RGB_SUPPORTED
|
||||
config LCD_RGB_ISR_IRAM_SAFE
|
||||
bool "RGB LCD ISR IRAM-Safe"
|
||||
select GDMA_ISR_IRAM_SAFE # bounce buffer mode relies on GDMA EOF interrupt
|
||||
default n
|
||||
help
|
||||
whether to enable the debug log message for LCD driver.
|
||||
Note that, this option only controls the LCD driver log, won't affect other drivers.
|
||||
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.
|
||||
|
||||
if SOC_LCD_RGB_SUPPORTED
|
||||
config LCD_RGB_ISR_IRAM_SAFE
|
||||
bool "RGB LCD ISR IRAM-Safe"
|
||||
select GDMA_ISR_IRAM_SAFE # bounce buffer mode relies on GDMA EOF interrupt
|
||||
default n
|
||||
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 "Always restart RGB LCD transmission in VSYNC"
|
||||
default n
|
||||
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
|
||||
|
||||
config LCD_RGB_RESTART_IN_VSYNC
|
||||
bool "Restart transmission in VSYNC"
|
||||
default n
|
||||
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
|
||||
|
||||
if SOC_MIPI_DSI_SUPPORTED
|
||||
config LCD_DSI_ISR_IRAM_SAFE
|
||||
bool "DSI LCD ISR IRAM-Safe"
|
||||
default n
|
||||
select DW_GDMA_ISR_IRAM_SAFE
|
||||
select DW_GDMA_CTRL_FUNC_IN_IRAM
|
||||
select DW_GDMA_SETTER_FUNC_IN_IRAM
|
||||
select DW_GDMA_GETTER_FUNC_IN_IRAM
|
||||
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.
|
||||
endif # SOC_MIPI_DSI_SUPPORTED
|
||||
endmenu
|
||||
if SOC_MIPI_DSI_SUPPORTED
|
||||
config LCD_DSI_ISR_IRAM_SAFE
|
||||
bool "DSI LCD ISR IRAM-Safe"
|
||||
default n
|
||||
select DW_GDMA_ISR_IRAM_SAFE
|
||||
select DW_GDMA_CTRL_FUNC_IN_IRAM
|
||||
select DW_GDMA_SETTER_FUNC_IN_IRAM
|
||||
select DW_GDMA_GETTER_FUNC_IN_IRAM
|
||||
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.
|
||||
endif # SOC_MIPI_DSI_SUPPORTED
|
||||
endmenu
|
||||
|
@ -44,6 +44,12 @@
|
||||
#include "hal/lcd_ll.h"
|
||||
#include "hal/cache_hal.h"
|
||||
#include "hal/cache_ll.h"
|
||||
#include "rgb_lcd_rotation_sw.h"
|
||||
|
||||
// hardware issue workaround
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
#define RGB_LCD_NEEDS_SEPARATE_RESTART_LINK 1
|
||||
#endif
|
||||
|
||||
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
|
||||
#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
|
||||
@ -66,16 +72,6 @@
|
||||
#define RGB_LCD_PANEL_MAX_FB_NUM 3 // maximum supported frame buffer number
|
||||
#define RGB_LCD_PANEL_BOUNCE_BUF_NUM 2 // bounce buffer number
|
||||
|
||||
#define RGB_PANEL_SWAP_XY 0
|
||||
#define RGB_PANEL_MIRROR_Y 1
|
||||
#define RGB_PANEL_MIRROR_X 2
|
||||
|
||||
typedef enum {
|
||||
ROTATE_MASK_SWAP_XY = BIT(RGB_PANEL_SWAP_XY),
|
||||
ROTATE_MASK_MIRROR_Y = BIT(RGB_PANEL_MIRROR_Y),
|
||||
ROTATE_MASK_MIRROR_X = BIT(RGB_PANEL_MIRROR_X),
|
||||
} panel_rotate_mask_t;
|
||||
|
||||
static const char *TAG = "lcd_panel.rgb";
|
||||
|
||||
typedef struct esp_rgb_panel_t esp_rgb_panel_t;
|
||||
@ -89,10 +85,10 @@ static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mi
|
||||
static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
|
||||
static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
|
||||
static esp_err_t rgb_panel_disp_on_off(esp_lcd_panel_t *panel, bool off);
|
||||
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src);
|
||||
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *rgb_panel, lcd_clock_source_t clk_src);
|
||||
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel);
|
||||
static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel);
|
||||
static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config);
|
||||
static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const esp_lcd_rgb_panel_config_t *panel_config);
|
||||
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel);
|
||||
static void rgb_lcd_default_isr_handler(void *args);
|
||||
|
||||
@ -112,7 +108,9 @@ struct esp_rgb_panel_t {
|
||||
gdma_channel_handle_t dma_chan; // DMA channel handle
|
||||
gdma_link_list_handle_t dma_fb_links[RGB_LCD_PANEL_MAX_FB_NUM]; // DMA link lists for multiple frame buffers
|
||||
gdma_link_list_handle_t dma_bb_link; // DMA link list for bounce buffer
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
gdma_link_list_handle_t dma_restart_link; // DMA link list for restarting the DMA
|
||||
#endif
|
||||
uint8_t *fbs[RGB_LCD_PANEL_MAX_FB_NUM]; // Frame buffers
|
||||
uint8_t *bounce_buffer[RGB_LCD_PANEL_BOUNCE_BUF_NUM]; // Pointer to the bounce buffers
|
||||
size_t fb_size; // Size of frame buffer, in bytes
|
||||
@ -121,15 +119,16 @@ struct esp_rgb_panel_t {
|
||||
uint8_t bb_fb_index; // Current frame buffer index which used by bounce buffer
|
||||
size_t int_mem_align; // DMA buffer alignment for internal memory
|
||||
size_t ext_mem_align; // DMA buffer alignment for external memory
|
||||
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color"
|
||||
int data_gpio_nums[SOC_LCDCAM_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)
|
||||
int bounce_pos_px; // Position in whatever source material is used for the bounce buffer, in pixels
|
||||
size_t bb_eof_count; // record the number we received the DMA EOF event, compare with `expect_eof_count` in the VSYNC_END ISR
|
||||
size_t expect_eof_count; // record the number of DMA EOF event we expected to receive
|
||||
esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; // draw buffer completes
|
||||
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; // callback used to notify when the bounce buffer finish copying the entire frame
|
||||
esp_lcd_rgb_panel_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
|
||||
esp_lcd_rgb_panel_bounce_buf_finish_cb_t on_bounce_frame_finish; // callback used to notify when the bounce buffer finish copying the entire frame
|
||||
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
|
||||
@ -142,7 +141,6 @@ struct esp_rgb_panel_t {
|
||||
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 need_restart: 1; // Whether to restart the LCD controller and the DMA
|
||||
uint32_t bb_invalidate_cache: 1; // Whether to do cache invalidation in bounce buffer mode
|
||||
uint32_t fb_behind_cache: 1; // Whether the frame buffer is behind the cache
|
||||
uint32_t bb_behind_cache: 1; // Whether the bounce buffer is behind the cache
|
||||
} flags;
|
||||
@ -229,9 +227,11 @@ static esp_err_t lcd_rgb_panel_destroy(esp_rgb_panel_t *rgb_panel)
|
||||
if (rgb_panel->dma_bb_link) {
|
||||
gdma_del_link_list(rgb_panel->dma_bb_link);
|
||||
}
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
if (rgb_panel->dma_restart_link) {
|
||||
gdma_del_link_list(rgb_panel->dma_restart_link);
|
||||
}
|
||||
#endif
|
||||
if (rgb_panel->intr) {
|
||||
esp_intr_free(rgb_panel->intr);
|
||||
}
|
||||
@ -262,8 +262,6 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
|
||||
ESP_ERR_INVALID_ARG, TAG, "num_fbs conflicts with no_fb");
|
||||
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.no_fb && rgb_panel_config->bounce_buffer_size_px == 0),
|
||||
ESP_ERR_INVALID_ARG, TAG, "must set bounce buffer if there's no frame buffer");
|
||||
ESP_RETURN_ON_FALSE(!(rgb_panel_config->flags.refresh_on_demand && rgb_panel_config->bounce_buffer_size_px),
|
||||
ESP_ERR_INVALID_ARG, TAG, "refresh on demand is not supported under bounce buffer mode");
|
||||
|
||||
// determine number of framebuffers
|
||||
size_t num_fbs = 1;
|
||||
@ -287,9 +285,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
|
||||
size_t bb_size = rgb_panel_config->bounce_buffer_size_px * fb_bits_per_pixel / 8;
|
||||
size_t expect_bb_eof_count = 0;
|
||||
if (bb_size) {
|
||||
// we want the bounce can always end in the second buffer
|
||||
ESP_RETURN_ON_FALSE(fb_size % (2 * bb_size) == 0, ESP_ERR_INVALID_ARG, TAG,
|
||||
"fb size must be even multiple of bounce buffer size");
|
||||
ESP_RETURN_ON_FALSE(fb_size % bb_size == 0, ESP_ERR_INVALID_ARG, TAG, "frame buffer size must be multiple of bounce buffer size");
|
||||
expect_bb_eof_count = fb_size / bb_size;
|
||||
}
|
||||
|
||||
@ -364,7 +360,6 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
|
||||
rgb_panel->output_bits_per_pixel = fb_bits_per_pixel; // by default, the output bpp is the same as the frame buffer bpp
|
||||
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_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;
|
||||
@ -401,19 +396,23 @@ esp_err_t esp_lcd_rgb_panel_register_event_callbacks(esp_lcd_panel_handle_t pane
|
||||
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_color_trans_done) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_color_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_color_trans_done callback not in IRAM");
|
||||
}
|
||||
if (callbacks->on_frame_buf_complete) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_frame_buf_complete), ESP_ERR_INVALID_ARG, TAG, "on_frame_buf_complete 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 (callbacks->on_bounce_frame_finish) {
|
||||
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(callbacks->on_bounce_frame_finish), ESP_ERR_INVALID_ARG, TAG, "on_bounce_frame_finish 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_color_trans_done = callbacks->on_color_trans_done;
|
||||
rgb_panel->on_frame_buf_complete = callbacks->on_frame_buf_complete;
|
||||
rgb_panel->on_bounce_empty = callbacks->on_bounce_empty;
|
||||
rgb_panel->on_bounce_frame_finish = callbacks->on_bounce_frame_finish;
|
||||
rgb_panel->user_ctx = user_ctx;
|
||||
return ESP_OK;
|
||||
}
|
||||
@ -554,7 +553,8 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
|
||||
// enable RGB mode and set data width
|
||||
lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true);
|
||||
lcd_ll_set_dma_read_stride(rgb_panel->hal.dev, rgb_panel->data_width);
|
||||
lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1); // enable data phase only
|
||||
// enable data phase only
|
||||
lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1);
|
||||
// number of data cycles is controlled by DMA buffer size
|
||||
lcd_ll_enable_output_always_on(rgb_panel->hal.dev, true);
|
||||
// configure HSYNC, VSYNC, DE signal idle state level
|
||||
@ -572,7 +572,8 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
|
||||
lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true);
|
||||
// generate the hsync at the very beginning of line
|
||||
lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
|
||||
// send next frame automatically in stream mode
|
||||
// in stream mode, after finish one frame, the LCD controller will ask for data automatically from the DMA
|
||||
// DMA should prepare the next frame data within porch region
|
||||
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);
|
||||
@ -586,157 +587,18 @@ static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
|
||||
return ret;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
#define COPY_PIXEL_CODE_BLOCK(_bpp) \
|
||||
switch (rgb_panel->rotate_mask) \
|
||||
{ \
|
||||
case 0: \
|
||||
{ \
|
||||
uint8_t *to = fb + (y_start * h_res + 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; \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + y_start * bytes_per_line; \
|
||||
} \
|
||||
break; \
|
||||
case ROTATE_MASK_MIRROR_X: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
|
||||
for (size_t x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
copy_pixel_##_bpp##bpp(to + index, from); \
|
||||
index -= bytes_per_pixel; \
|
||||
from += bytes_per_pixel; \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + y_start * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_MIRROR_Y: \
|
||||
{ \
|
||||
uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + 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; \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
|
||||
} \
|
||||
break; \
|
||||
case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
|
||||
for (size_t x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
copy_pixel_##_bpp##bpp(to + index, from); \
|
||||
index -= bytes_per_pixel; \
|
||||
from += bytes_per_pixel; \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = (x * h_res + y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + x_start * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + x_start * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_Y: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
|
||||
break; \
|
||||
default: \
|
||||
break; \
|
||||
}
|
||||
|
||||
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);
|
||||
ESP_RETURN_ON_FALSE(rgb_panel->num_fbs > 0, ESP_ERR_NOT_SUPPORTED, TAG, "no frame buffer installed");
|
||||
esp_lcd_rgb_panel_draw_buf_complete_cb_t cb = rgb_panel->on_color_trans_done;
|
||||
|
||||
// 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 if (color_data == rgb_panel->fbs[2]) {
|
||||
rgb_panel->cur_fb_index = 2;
|
||||
} else {
|
||||
// we do the copy only if the color_data is different from either frame buffer
|
||||
do_copy = true;
|
||||
}
|
||||
uint8_t *draw_buffer = (uint8_t *)color_data;
|
||||
size_t fb_size = rgb_panel->fb_size;
|
||||
int h_res = rgb_panel->timings.h_res;
|
||||
int v_res = rgb_panel->timings.v_res;
|
||||
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
|
||||
uint32_t bytes_per_line = bytes_per_pixel * h_res;
|
||||
|
||||
// adjust the flush window by adding extra gap
|
||||
x_start += rgb_panel->x_gap;
|
||||
@ -745,8 +607,6 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
|
||||
y_end += rgb_panel->y_gap;
|
||||
|
||||
// clip to boundaries
|
||||
int h_res = rgb_panel->timings.h_res;
|
||||
int v_res = rgb_panel->timings.v_res;
|
||||
if (rgb_panel->rotate_mask & ROTATE_MASK_SWAP_XY) {
|
||||
x_start = MAX(x_start, 0);
|
||||
x_end = MIN(x_end, v_res);
|
||||
@ -759,15 +619,24 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
|
||||
y_end = MIN(y_end, v_res);
|
||||
}
|
||||
|
||||
int bytes_per_pixel = rgb_panel->fb_bits_per_pixel / 8;
|
||||
int pixels_per_line = rgb_panel->timings.h_res;
|
||||
uint32_t bytes_per_line = bytes_per_pixel * pixels_per_line;
|
||||
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
|
||||
size_t bytes_to_flush = v_res * h_res * bytes_per_pixel;
|
||||
uint8_t *flush_ptr = fb;
|
||||
// check if we want to copy the draw buffer to the internal frame buffer
|
||||
bool draw_buf_copy_to_fb = true;
|
||||
uint8_t draw_buf_fb_index = 0;
|
||||
for (int i = 0; i < rgb_panel->num_fbs; i++) {
|
||||
if (draw_buffer >= rgb_panel->fbs[i] && draw_buffer < rgb_panel->fbs[i] + fb_size) {
|
||||
draw_buf_fb_index = i;
|
||||
draw_buf_copy_to_fb = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (draw_buf_copy_to_fb) {
|
||||
// sync the draw buffer with the frame buffer by CPU copy
|
||||
ESP_LOGV(TAG, "copy draw buffer to frame buffer by CPU");
|
||||
uint8_t *fb = rgb_panel->fbs[rgb_panel->cur_fb_index];
|
||||
size_t bytes_to_flush = v_res * bytes_per_line;
|
||||
uint8_t *flush_ptr = fb;
|
||||
|
||||
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;
|
||||
size_t offset = y_start * copy_bytes_per_line + x_start * bytes_per_pixel;
|
||||
@ -779,12 +648,29 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
|
||||
} else if (3 == bytes_per_pixel) {
|
||||
COPY_PIXEL_CODE_BLOCK(24)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that if we use a bounce buffer, the data gets read by the CPU as well so no need to write back
|
||||
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
|
||||
ESP_RETURN_ON_ERROR(esp_cache_msync(flush_ptr, bytes_to_flush, 0), TAG, "flush cache buffer failed");
|
||||
// do memory sync only when the frame buffer is mounted to the DMA link list and behind the cache
|
||||
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
|
||||
esp_cache_msync(flush_ptr, bytes_to_flush, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
|
||||
}
|
||||
// after the draw buffer finished copying, notify the user to recycle the draw buffer
|
||||
if (cb) {
|
||||
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGV(TAG, "draw buffer is part of the frame buffer");
|
||||
// the new frame buffer index is changed
|
||||
rgb_panel->cur_fb_index = draw_buf_fb_index;
|
||||
// when this function is called, the frame buffer already reflects the draw buffer changes
|
||||
// if the frame buffer is also mounted to the DMA, we need to do the sync between them
|
||||
if (!rgb_panel->bb_size && rgb_panel->flags.fb_behind_cache) {
|
||||
uint8_t *cache_sync_start = rgb_panel->fbs[draw_buf_fb_index] + (y_start * h_res) * bytes_per_pixel;
|
||||
size_t cache_sync_size = (y_end - y_start) * bytes_per_line;
|
||||
esp_cache_msync(cache_sync_start, cache_sync_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
|
||||
}
|
||||
// after the draw buffer finished copying, notify the user to recycle the draw buffer
|
||||
if (cb) {
|
||||
cb(&rgb_panel->base, NULL, rgb_panel->user_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
if (!rgb_panel->bb_size) {
|
||||
@ -895,24 +781,24 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *rgb_panel, const
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src)
|
||||
static esp_err_t lcd_rgb_panel_select_clock_src(esp_rgb_panel_t *rgb_panel, lcd_clock_source_t clk_src)
|
||||
{
|
||||
// get clock source frequency
|
||||
uint32_t src_clk_hz = 0;
|
||||
ESP_RETURN_ON_ERROR(esp_clk_tree_src_get_freq_hz((soc_module_clk_t)clk_src, ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &src_clk_hz),
|
||||
TAG, "get clock source frequency failed");
|
||||
panel->src_clk_hz = src_clk_hz;
|
||||
rgb_panel->src_clk_hz = src_clk_hz;
|
||||
esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true);
|
||||
LCD_CLOCK_SRC_ATOMIC() {
|
||||
lcd_ll_select_clk_src(panel->hal.dev, clk_src);
|
||||
lcd_ll_select_clk_src(rgb_panel->hal.dev, clk_src);
|
||||
}
|
||||
|
||||
// create pm lock based on different clock source
|
||||
// clock sources like PLL and XTAL will be turned off in light sleep
|
||||
#if CONFIG_PM_ENABLE
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "rgb_panel", &panel->pm_lock), TAG, "create pm lock failed");
|
||||
ESP_RETURN_ON_ERROR(esp_pm_lock_create(ESP_PM_NO_LIGHT_SLEEP, 0, "rgb_panel", &rgb_panel->pm_lock), TAG, "create pm lock failed");
|
||||
// hold the lock during the whole lifecycle of RGB panel
|
||||
esp_pm_lock_acquire(panel->pm_lock);
|
||||
esp_pm_lock_acquire(rgb_panel->pm_lock);
|
||||
ESP_LOGD(TAG, "installed pm lock and hold the lock during the whole panel lifecycle");
|
||||
#endif
|
||||
|
||||
@ -923,52 +809,73 @@ static IRAM_ATTR bool lcd_rgb_panel_fill_bounce_buffer(esp_rgb_panel_t *panel, u
|
||||
{
|
||||
bool need_yield = false;
|
||||
int bytes_per_pixel = panel->fb_bits_per_pixel / 8;
|
||||
if (panel->num_fbs == 0) {
|
||||
if (unlikely(panel->num_fbs == 0)) {
|
||||
// driver doesn't maintain a frame buffer, so ask the user to fill the bounce buffer
|
||||
if (panel->on_bounce_empty) {
|
||||
// We don't have a frame buffer here; we need to call a callback to refill the bounce buffer
|
||||
if (panel->on_bounce_empty(&panel->base, buffer, panel->bounce_pos_px, panel->bb_size, panel->user_ctx)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We do have frame buffer; copy from there.
|
||||
// Note: if the cache is disabled, and accessing the PSRAM by DCACHE will crash.
|
||||
// copy partial frame buffer to the bounce buffer
|
||||
// Note: if the frame buffer is behind a cache, and the cache is disabled, crash would happen here when auto write back happens
|
||||
memcpy(buffer, &panel->fbs[panel->bb_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.
|
||||
esp_cache_msync(&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel], (size_t)panel->bb_size, ESP_CACHE_MSYNC_FLAG_INVALIDATE);
|
||||
}
|
||||
}
|
||||
// do memory sync if the bounce buffer is behind the cache
|
||||
if (panel->flags.bb_behind_cache) {
|
||||
esp_cache_msync(buffer, panel->bb_size, ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_UNALIGNED);
|
||||
}
|
||||
|
||||
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->bb_fb_index = panel->cur_fb_index;
|
||||
if (panel->on_bounce_frame_finish) {
|
||||
if (panel->on_bounce_frame_finish(&panel->base, NULL, panel->user_ctx)) {
|
||||
esp_lcd_rgb_panel_frame_buf_complete_cb_t cb = panel->on_frame_buf_complete;
|
||||
if (cb) {
|
||||
if (cb(&panel->base, NULL, panel->user_ctx)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (panel->num_fbs > 0) {
|
||||
// Preload the next bit of buffer from psram
|
||||
|
||||
// Preload the next bit of buffer to the cache memory, this can improve the performance
|
||||
if (panel->num_fbs > 0 && panel->flags.fb_behind_cache) {
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
Cache_Start_DCache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel],
|
||||
panel->bb_size, 0);
|
||||
#elif CONFIG_IDF_TARGET_ESP32P4
|
||||
Cache_Start_L2_Cache_Preload((uint32_t)&panel->fbs[panel->bb_fb_index][panel->bounce_pos_px * bytes_per_pixel],
|
||||
panel->bb_size, 0);
|
||||
#else
|
||||
#error "Unsupported target"
|
||||
#endif
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Figure out which bounce buffer to write to.
|
||||
portENTER_CRITICAL_ISR(&panel->spinlock);
|
||||
int bb = panel->bb_eof_count % RGB_LCD_PANEL_BOUNCE_BUF_NUM;
|
||||
panel->bb_eof_count++;
|
||||
portEXIT_CRITICAL_ISR(&panel->spinlock);
|
||||
return lcd_rgb_panel_fill_bounce_buffer(panel, panel->bounce_buffer[bb]);
|
||||
bool need_yield = false;
|
||||
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)user_data;
|
||||
|
||||
if (rgb_panel->bb_size) {
|
||||
// in bounce buffer mode, the DMA EOF means time to fill the finished bounce buffer
|
||||
// Figure out which bounce buffer to write to
|
||||
portENTER_CRITICAL_ISR(&rgb_panel->spinlock);
|
||||
int bb = rgb_panel->bb_eof_count % RGB_LCD_PANEL_BOUNCE_BUF_NUM;
|
||||
rgb_panel->bb_eof_count++;
|
||||
portEXIT_CRITICAL_ISR(&rgb_panel->spinlock);
|
||||
need_yield = lcd_rgb_panel_fill_bounce_buffer(rgb_panel, rgb_panel->bounce_buffer[bb]);
|
||||
} else {
|
||||
// if not bounce buffer, the DMA EOF event means the end of a frame has been sent out to the LCD controller
|
||||
if (rgb_panel->on_frame_buf_complete) {
|
||||
if (rgb_panel->on_frame_buf_complete(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
|
||||
need_yield = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return need_yield;
|
||||
}
|
||||
|
||||
static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
|
||||
@ -995,25 +902,27 @@ static esp_err_t lcd_rgb_create_dma_channel(esp_rgb_panel_t *rgb_panel)
|
||||
// get the memory alignment required by the DMA
|
||||
gdma_get_alignment_constraints(rgb_panel->dma_chan, &rgb_panel->int_mem_align, &rgb_panel->ext_mem_align);
|
||||
|
||||
// we need to refill the bounce buffer in the DMA EOF interrupt, so only register the callback for bounce buffer mode
|
||||
if (rgb_panel->bb_size) {
|
||||
gdma_tx_event_callbacks_t cbs = {
|
||||
.on_trans_eof = lcd_rgb_panel_eof_handler,
|
||||
};
|
||||
gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel);
|
||||
}
|
||||
// register DMA EOF callback
|
||||
gdma_tx_event_callbacks_t cbs = {
|
||||
.on_trans_eof = lcd_rgb_panel_eof_handler,
|
||||
};
|
||||
ESP_RETURN_ON_ERROR(gdma_register_tx_event_callbacks(rgb_panel->dma_chan, &cbs, rgb_panel), TAG, "register DMA EOF callback failed");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
// If we restart GDMA, 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 link (called restart link) to restart the transaction
|
||||
#define LCD_FIFO_PRESERVE_SIZE_PX (LCD_LL_FIFO_DEPTH + 1)
|
||||
#endif
|
||||
|
||||
static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
|
||||
{
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
// the restart link shares the same buffer with the frame/bounce buffer but start from a different offset
|
||||
int restart_skip_bytes = LCD_FIFO_PRESERVE_SIZE_PX * (rgb_panel->fb_bits_per_pixel / 8);
|
||||
#endif
|
||||
if (rgb_panel->bb_size) {
|
||||
// DMA is used to convey the bounce buffer
|
||||
size_t num_dma_nodes_per_bounce_buffer = (rgb_panel->bb_size + LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
|
||||
@ -1035,6 +944,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
|
||||
}
|
||||
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_bb_link, 0, mount_cfgs, RGB_LCD_PANEL_BOUNCE_BUF_NUM, NULL),
|
||||
TAG, "mount DMA bounce buffers failed");
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
// create restart link
|
||||
gdma_link_list_config_t restart_link_cfg = {
|
||||
.buffer_alignment = rgb_panel->int_mem_align,
|
||||
@ -1054,6 +964,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
|
||||
|
||||
// Magic here: we use the restart link to restart the bounce buffer link list, so concat them
|
||||
gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_bb_link, 1);
|
||||
#endif
|
||||
} else {
|
||||
// DMA is used to convey the frame buffer
|
||||
size_t num_dma_nodes = (rgb_panel->fb_size + LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE - 1) / LCD_DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
|
||||
@ -1079,6 +990,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
|
||||
ESP_RETURN_ON_ERROR(gdma_link_mount_buffers(rgb_panel->dma_fb_links[i], 0, &mount_cfg, 1, NULL),
|
||||
TAG, "mount DMA frame buffer failed");
|
||||
}
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
// create restart link
|
||||
gdma_link_list_config_t restart_link_cfg = {
|
||||
.buffer_alignment = rgb_panel->flags.fb_in_psram ? rgb_panel->ext_mem_align : rgb_panel->int_mem_align,
|
||||
@ -1098,6 +1010,7 @@ static esp_err_t lcd_rgb_panel_init_trans_link(esp_rgb_panel_t *rgb_panel)
|
||||
|
||||
// Magic here: we use the restart link to restart the frame buffer link list, so concat them
|
||||
gdma_link_concat(rgb_panel->dma_restart_link, 0, rgb_panel->dma_fb_links[0], 1);
|
||||
#endif
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
@ -1145,8 +1058,17 @@ static IRAM_ATTR void lcd_rgb_panel_try_restart_transmission(esp_rgb_panel_t *pa
|
||||
}
|
||||
|
||||
gdma_reset(panel->dma_chan);
|
||||
lcd_ll_fifo_reset(panel->hal.dev);
|
||||
#if RGB_LCD_NEEDS_SEPARATE_RESTART_LINK
|
||||
// restart the DMA by a special DMA node
|
||||
gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_restart_link));
|
||||
#else
|
||||
if (panel->bb_size) {
|
||||
gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_bb_link));
|
||||
} else {
|
||||
gdma_start(panel->dma_chan, gdma_link_get_head_addr(panel->dma_fb_links[panel->cur_fb_index]));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (panel->bb_size) {
|
||||
// Fill 2nd bounce buffer while 1st is being sent out, if needed.
|
||||
@ -1162,6 +1084,7 @@ static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
|
||||
// reset FIFO of DMA and LCD, in case there remains old frame data
|
||||
gdma_reset(rgb_panel->dma_chan);
|
||||
lcd_ll_stop(rgb_panel->hal.dev);
|
||||
lcd_ll_reset(rgb_panel->hal.dev);
|
||||
lcd_ll_fifo_reset(rgb_panel->hal.dev);
|
||||
|
||||
// pre-fill bounce buffers if needed
|
||||
@ -1203,8 +1126,11 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args)
|
||||
esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args;
|
||||
bool need_yield = false;
|
||||
|
||||
// clear the interrupt status
|
||||
uint32_t intr_status = lcd_ll_get_interrupt_status(rgb_panel->hal.dev);
|
||||
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status);
|
||||
|
||||
// VSYNC event happened
|
||||
if (intr_status & LCD_LL_EVENT_VSYNC_END) {
|
||||
// call user registered callback
|
||||
if (rgb_panel->on_vsync) {
|
||||
@ -1222,6 +1148,7 @@ IRAM_ATTR static void rgb_lcd_default_isr_handler(void *args)
|
||||
}
|
||||
|
||||
}
|
||||
// yield if needed
|
||||
if (need_yield) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -77,14 +77,32 @@ typedef struct {
|
||||
} esp_lcd_rgb_panel_event_data_t;
|
||||
|
||||
/**
|
||||
* @brief RGB LCD VSYNC event callback prototype
|
||||
* @brief A general function callback prototype for RGB panel driver
|
||||
*
|
||||
* @param[in] panel LCD panel handle, returned from `esp_lcd_new_rgb_panel`
|
||||
* @param[in] edata Panel event data, fed by driver
|
||||
* @param[in] panel LCD panel handle, which is created by factory API like `esp_lcd_new_rgb_panel`
|
||||
* @param[in] edata RGB panel event data, provided by driver
|
||||
* @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_vsync_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
|
||||
typedef bool (*esp_lcd_rgb_panel_general_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
|
||||
|
||||
/**
|
||||
* @brief Declare the prototype of the function that will be invoked when the user draw buffer is complete.
|
||||
* The draw buffer can be recycled after this event.
|
||||
*/
|
||||
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_draw_buf_complete_cb_t;
|
||||
|
||||
/**
|
||||
* @brief Declare the prototype of the function that will be invoked when a whole frame buffer is sent to the LCD DMA.
|
||||
* The LCD hardware may still need some blank time to finish the refresh.
|
||||
*/
|
||||
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_frame_buf_complete_cb_t;
|
||||
|
||||
/**
|
||||
* @brief Declare the prototype of the function that will be invoked when the LCD controller sends the VSYNC signal.
|
||||
* It means, the LCD hardware should be ready, and after some blank time, the next frame will be flushed to the LCD controller.
|
||||
*/
|
||||
typedef esp_lcd_rgb_panel_general_cb_t esp_lcd_rgb_panel_vsync_cb_t;
|
||||
|
||||
/**
|
||||
* @brief Prototype for function to re-fill a bounce buffer, rather than copying from the frame buffer
|
||||
@ -99,15 +117,10 @@ typedef bool (*esp_lcd_rgb_panel_vsync_cb_t)(esp_lcd_panel_handle_t panel, const
|
||||
*/
|
||||
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 Prototype for the function to be called when the bounce buffer finish copying the entire frame.
|
||||
*
|
||||
* @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_register_event_callbacks()`
|
||||
* @return Whether a high priority task has been waken up by this function
|
||||
*/
|
||||
typedef bool (*esp_lcd_rgb_panel_bounce_buf_finish_cb_t)(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
|
||||
/** @cond */
|
||||
/// for backward compatible
|
||||
typedef esp_lcd_rgb_panel_frame_buf_complete_cb_t esp_lcd_rgb_panel_bounce_buf_finish_cb_t __attribute__((deprecated("esp_lcd_rgb_panel_bounce_buf_finish_cb_t is deprecated, use esp_lcd_rgb_panel_frame_buf_complete_cb_t instead")));
|
||||
/** @endcond */
|
||||
|
||||
/**
|
||||
* @brief Group of supported RGB LCD panel callbacks
|
||||
@ -115,9 +128,15 @@ typedef bool (*esp_lcd_rgb_panel_bounce_buf_finish_cb_t)(esp_lcd_panel_handle_t
|
||||
* @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_bounce_buf_finish_cb_t on_bounce_frame_finish; /*!< Bounce buffer finish callback. */
|
||||
esp_lcd_rgb_panel_draw_buf_complete_cb_t on_color_trans_done; /*!< Invoked when user's color buffer copied to the internal frame buffer.
|
||||
This is an indicator that the draw buffer can be recycled safely.
|
||||
But doesn't mean the draw buffer finishes the refreshing to the screen. */
|
||||
esp_lcd_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. */
|
||||
union {
|
||||
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_bounce_frame_finish __attribute__((deprecated)); /*!< Bounce buffer finish callback. */
|
||||
esp_lcd_rgb_panel_frame_buf_complete_cb_t on_frame_buf_complete; /*!< A whole frame buffer was just sent to the LCD DMA */
|
||||
};
|
||||
} esp_lcd_rgb_panel_event_callbacks_t;
|
||||
|
||||
/**
|
||||
@ -134,7 +153,7 @@ typedef struct {
|
||||
DMA fetching from DRAM bounce buffer is much faster than PSRAM frame buffer. */
|
||||
size_t sram_trans_align __attribute__((deprecated)); /*!< Alignment of buffers (frame buffer or bounce buffer) that allocated in SRAM */
|
||||
union {
|
||||
size_t psram_trans_align; /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
|
||||
size_t psram_trans_align __attribute__((deprecated)); /*!< Alignment of buffers (frame buffer) that allocated in PSRAM */
|
||||
size_t dma_burst_size; /*!< DMA burst size, in bytes */
|
||||
};
|
||||
int hsync_gpio_num; /*!< GPIO used for HSYNC signal */
|
||||
@ -142,11 +161,10 @@ typedef struct {
|
||||
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, set to -1 if it's not used */
|
||||
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 data_gpio_nums[SOC_LCDCAM_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */
|
||||
struct {
|
||||
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 refresh_on_demand: 1; /*!< If this flag is enabled, the host only refresh the frame buffer in `esp_lcd_panel_draw_bitmap` and `esp_lcd_rgb_panel_refresh`. */
|
||||
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, same as num_fbs=2 */
|
||||
uint32_t no_fb: 1; /*!< If this flag is enabled, the driver won't allocate frame buffer.
|
||||
|
162
components/esp_lcd/rgb/rgb_lcd_rotation_sw.h
Normal file
162
components/esp_lcd/rgb/rgb_lcd_rotation_sw.h
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "esp_bit_defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define RGB_PANEL_SWAP_XY 0
|
||||
#define RGB_PANEL_MIRROR_Y 1
|
||||
#define RGB_PANEL_MIRROR_X 2
|
||||
|
||||
typedef enum {
|
||||
ROTATE_MASK_SWAP_XY = BIT(RGB_PANEL_SWAP_XY),
|
||||
ROTATE_MASK_MIRROR_Y = BIT(RGB_PANEL_MIRROR_Y),
|
||||
ROTATE_MASK_MIRROR_X = BIT(RGB_PANEL_MIRROR_X),
|
||||
} panel_rotate_mask_t;
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_8bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_16bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
__attribute__((always_inline))
|
||||
static inline void copy_pixel_24bpp(uint8_t *to, const uint8_t *from)
|
||||
{
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
*to++ = *from++;
|
||||
}
|
||||
|
||||
#define COPY_PIXEL_CODE_BLOCK(_bpp) \
|
||||
switch (rgb_panel->rotate_mask) \
|
||||
{ \
|
||||
case 0: \
|
||||
{ \
|
||||
uint8_t *to = fb + (y_start * h_res + 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; \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + y_start * bytes_per_line; \
|
||||
} \
|
||||
break; \
|
||||
case ROTATE_MASK_MIRROR_X: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
uint32_t index = (y * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
|
||||
for (size_t x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
copy_pixel_##_bpp##bpp(to + index, from); \
|
||||
index -= bytes_per_pixel; \
|
||||
from += bytes_per_pixel; \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + y_start * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_MIRROR_Y: \
|
||||
{ \
|
||||
uint8_t *to = fb + ((v_res - 1 - y_start) * h_res + 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; \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
|
||||
} \
|
||||
break; \
|
||||
case ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
uint32_t index = ((v_res - 1 - y) * h_res + (h_res - 1 - x_start)) * bytes_per_pixel; \
|
||||
for (size_t x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
copy_pixel_##_bpp##bpp(to + index, from); \
|
||||
index -= bytes_per_pixel; \
|
||||
from += bytes_per_pixel; \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (y_end - y_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - y_end) * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = (x * h_res + y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + x_start * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = (x * h_res + h_res - 1 - y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + x_start * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_Y: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = ((v_res - 1 - x) * h_res + y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
|
||||
break; \
|
||||
case ROTATE_MASK_SWAP_XY | ROTATE_MASK_MIRROR_X | ROTATE_MASK_MIRROR_Y: \
|
||||
for (int y = y_start; y < y_end; y++) \
|
||||
{ \
|
||||
for (int x = x_start; x < x_end; x++) \
|
||||
{ \
|
||||
uint32_t j = y * copy_bytes_per_line + x * bytes_per_pixel - offset; \
|
||||
uint32_t i = ((v_res - 1 - x) * h_res + h_res - 1 - y) * bytes_per_pixel; \
|
||||
copy_pixel_##_bpp##bpp(to + i, from + j); \
|
||||
} \
|
||||
} \
|
||||
bytes_to_flush = (x_end - x_start) * bytes_per_line; \
|
||||
flush_ptr = fb + (v_res - x_end) * bytes_per_line; \
|
||||
break; \
|
||||
default: \
|
||||
break; \
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
@ -9,7 +9,7 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// FPS = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz
|
||||
// Refresh Rate = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz
|
||||
#define MIPI_DSI_DPI_CLK_MHZ 80
|
||||
#define MIPI_DSI_LCD_H_RES 800
|
||||
#define MIPI_DSI_LCD_V_RES 1280
|
||||
|
@ -1,4 +1,4 @@
|
||||
| Supported Targets | ESP32-S3 |
|
||||
| ----------------- | -------- |
|
||||
| Supported Targets | ESP32-P4 | ESP32-S3 |
|
||||
| ----------------- | -------- | -------- |
|
||||
|
||||
This test app is used to test RGB565 interfaced LCDs.
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
@ -11,6 +11,16 @@ extern "C" {
|
||||
|
||||
#define TEST_LCD_H_RES 800
|
||||
#define TEST_LCD_V_RES 480
|
||||
#define TEST_LCD_HSYNC 1
|
||||
#define TEST_LCD_HBP 40
|
||||
#define TEST_LCD_HFP 20
|
||||
#define TEST_LCD_VSYNC 1
|
||||
#define TEST_LCD_VBP 10
|
||||
#define TEST_LCD_VFP 5
|
||||
|
||||
#define TEST_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000)
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
|
||||
#define TEST_LCD_VSYNC_GPIO 3
|
||||
#define TEST_LCD_HSYNC_GPIO 46
|
||||
@ -34,7 +44,33 @@ extern "C" {
|
||||
#define TEST_LCD_DATA15_GPIO 40 // R4
|
||||
#define TEST_LCD_DISP_EN_GPIO -1
|
||||
|
||||
#define TEST_LCD_PIXEL_CLOCK_HZ (18 * 1000 * 1000)
|
||||
#elif CONFIG_IDF_TARGET_ESP32P4
|
||||
|
||||
#define TEST_LCD_VSYNC_GPIO 41
|
||||
#define TEST_LCD_HSYNC_GPIO 39
|
||||
#define TEST_LCD_DE_GPIO 43
|
||||
#define TEST_LCD_PCLK_GPIO 33
|
||||
#define TEST_LCD_DATA0_GPIO 40 // B0
|
||||
#define TEST_LCD_DATA1_GPIO 42 // B1
|
||||
#define TEST_LCD_DATA2_GPIO 27 // B2
|
||||
#define TEST_LCD_DATA3_GPIO 29 // B3
|
||||
#define TEST_LCD_DATA4_GPIO 31 // B4
|
||||
#define TEST_LCD_DATA5_GPIO 21 // G0
|
||||
#define TEST_LCD_DATA6_GPIO 23 // G1
|
||||
#define TEST_LCD_DATA7_GPIO 26 // G2
|
||||
#define TEST_LCD_DATA8_GPIO 28 // G3
|
||||
#define TEST_LCD_DATA9_GPIO 30 // G4
|
||||
#define TEST_LCD_DATA10_GPIO 32 // G5
|
||||
#define TEST_LCD_DATA11_GPIO 6 // R0
|
||||
#define TEST_LCD_DATA12_GPIO 0 // R1
|
||||
#define TEST_LCD_DATA13_GPIO 15 // R2
|
||||
#define TEST_LCD_DATA14_GPIO 17 // R3
|
||||
#define TEST_LCD_DATA15_GPIO 19 // R4
|
||||
#define TEST_LCD_DISP_EN_GPIO -1
|
||||
|
||||
#else
|
||||
#error "Unsupported target"
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
|
||||
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
@ -62,12 +62,12 @@ static esp_lcd_panel_handle_t test_rgb_panel_initialization(size_t data_width, s
|
||||
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
|
||||
.h_res = TEST_LCD_H_RES,
|
||||
.v_res = TEST_LCD_V_RES,
|
||||
.hsync_back_porch = 68,
|
||||
.hsync_front_porch = 20,
|
||||
.hsync_pulse_width = 5,
|
||||
.vsync_back_porch = 18,
|
||||
.vsync_front_porch = 4,
|
||||
.vsync_pulse_width = 1,
|
||||
.hsync_back_porch = TEST_LCD_HBP,
|
||||
.hsync_front_porch = TEST_LCD_HFP,
|
||||
.hsync_pulse_width = TEST_LCD_HSYNC,
|
||||
.vsync_back_porch = TEST_LCD_VBP,
|
||||
.vsync_front_porch = TEST_LCD_VFP,
|
||||
.vsync_pulse_width = TEST_LCD_VSYNC,
|
||||
},
|
||||
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
|
||||
.flags.refresh_on_demand = refresh_on_demand,
|
||||
@ -99,6 +99,7 @@ TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]")
|
||||
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);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
}
|
||||
printf("delete RGB panel\r\n");
|
||||
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
|
||||
|
@ -1,6 +1,5 @@
|
||||
# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
@ -15,5 +14,19 @@ from pytest_embedded import Dut
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_rgb_lcd(dut: Dut) -> None:
|
||||
def test_rgb_lcd_esp32s3(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
||||
|
||||
@pytest.mark.esp32p4
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'iram_safe',
|
||||
'release',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_rgb_lcd_esp32p4(dut: Dut) -> None:
|
||||
dut.run_all_single_board_cases()
|
||||
|
@ -0,0 +1,5 @@
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_HEX=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
@ -59,6 +59,10 @@ config SOC_LCDCAM_I80_LCD_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_LCDCAM_RGB_LCD_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_MIPI_CSI_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
@ -1175,6 +1179,10 @@ config SOC_LCD_I80_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_LCD_RGB_SUPPORTED
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_LCDCAM_I80_NUM_BUSES
|
||||
int
|
||||
default 1
|
||||
@ -1187,6 +1195,14 @@ config SOC_LCDCAM_RGB_NUM_PANELS
|
||||
int
|
||||
default 1
|
||||
|
||||
config SOC_LCDCAM_RGB_DATA_WIDTH
|
||||
int
|
||||
default 24
|
||||
|
||||
config SOC_LCD_SUPPORT_RGB_YUV_CONV
|
||||
bool
|
||||
default y
|
||||
|
||||
config SOC_MCPWM_GROUPS
|
||||
int
|
||||
default 2
|
||||
|
@ -29,9 +29,9 @@
|
||||
#define SOC_GPTIMER_SUPPORTED 1
|
||||
#define SOC_PCNT_SUPPORTED 1
|
||||
#define SOC_LCDCAM_SUPPORTED 1
|
||||
#define SOC_LCDCAM_CAM_SUPPORTED 1
|
||||
#define SOC_LCDCAM_CAM_SUPPORTED 1 // support the camera driver based on the LCD_CAM peripheral
|
||||
#define SOC_LCDCAM_I80_LCD_SUPPORTED 1 // support the Intel 8080 bus driver based on the LCD_CAM peripheral
|
||||
// #define SOC_LCDCAM_RGB_LCD_SUPPORTED 1 // TODO: IDF-7465
|
||||
#define SOC_LCDCAM_RGB_LCD_SUPPORTED 1 // support the RGB LCD driver based on the LCD_CAM peripheral
|
||||
#define SOC_MIPI_CSI_SUPPORTED 1
|
||||
#define SOC_MIPI_DSI_SUPPORTED 1
|
||||
#define SOC_MCPWM_SUPPORTED 1
|
||||
@ -428,11 +428,12 @@
|
||||
/*-------------------------- LCD CAPS ----------------------------------------*/
|
||||
/* I80 bus and RGB timing generator can't work at the same time in the LCD_CAM peripheral */
|
||||
#define SOC_LCD_I80_SUPPORTED 1 /*!< support intel 8080 driver */
|
||||
#define SOC_LCD_RGB_SUPPORTED 1 /*!< RGB LCD is supported */
|
||||
#define SOC_LCDCAM_I80_NUM_BUSES 1U /*!< LCD_CAM peripheral provides one LCD Intel 8080 bus */
|
||||
#define SOC_LCDCAM_I80_BUS_WIDTH 24 /*!< Intel 8080 bus max data width */
|
||||
#define SOC_LCDCAM_RGB_NUM_PANELS 1U /*!< Support one RGB LCD panel */
|
||||
// #define SOC_LCD_RGB_DATA_WIDTH 24 /*!< Number of LCD data lines */
|
||||
// #define SOC_LCD_SUPPORT_RGB_YUV_CONV 1 /*!< Support color format conversion between RGB and YUV */
|
||||
#define SOC_LCDCAM_RGB_DATA_WIDTH 24 /*!< Number of LCD data lines */
|
||||
#define SOC_LCD_SUPPORT_RGB_YUV_CONV 1 /*!< Support color format conversion between RGB and YUV */
|
||||
|
||||
/*-------------------------- MCPWM CAPS --------------------------------------*/
|
||||
#define SOC_MCPWM_GROUPS (2U) ///< 2 MCPWM groups on the chip (i.e., the number of independent MCPWM peripherals)
|
||||
|
@ -44,5 +44,6 @@ INPUT += \
|
||||
$(PROJECT_PATH)/components/esp_driver_jpeg/include/driver/jpeg_encode.h \
|
||||
$(PROJECT_PATH)/components/esp_driver_ppa/include/driver/ppa.h \
|
||||
$(PROJECT_PATH)/components/esp_lcd/dsi/include/esp_lcd_mipi_dsi.h \
|
||||
$(PROJECT_PATH)/components/esp_lcd/rgb/include/esp_lcd_panel_rgb.h \
|
||||
$(PROJECT_PATH)/components/sdmmc/include/sd_pwr_ctrl.h \
|
||||
$(PROJECT_PATH)/components/sdmmc/include/sd_pwr_ctrl_by_on_chip_ldo.h \
|
||||
|
@ -3,17 +3,17 @@ RGB Interfaced LCD
|
||||
|
||||
:link_to_translation:`zh_CN:[中文]`
|
||||
|
||||
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`.
|
||||
RGB LCD panel is created by :cpp:func:`esp_lcd_new_rgb_panel`, with various configurations specified in :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` sets 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` sets 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 value set in :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. But in some cases, these two values must be different. For example, a serial RGB interfaced 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 GPIO pins used by the RGB LCD controller. If any of them are not used, please set them to `-1`.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` sets the DMA transfer burst size. The value must be a power of 2.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` sets 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::num_fbs` sets the number of frame buffers allocated by the driver. For backward compatibility, ``0`` means to allocate ``one`` frame buffer. Please use :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` if you do not want to allocate any frame buffer.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::clk_src` selects the clock source of 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` sets number of data lines consumed by the RGB interface. It can be 8/16/24.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::bits_per_pixel` specifies the number of bits per pixel. This differs from :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. By default, if this field is set to 0, the driver will automatically match the bpp to the value set in :cpp:member:`esp_lcd_rgb_panel_config_t::data_width`. However, in some scenarios, these values need to be different. For instance, a serial RGB interfaced LCD might only require ``8`` data lines, but the color depth could be ``RGB888``, meaning :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 GPIO pins consumed by the RGB LCD controller. If any of them are not used, please set them to ``-1``.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::dma_burst_size` specifies the size of the DMA transfer burst. Ensure this value is a power of 2.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px` specifies the size of the bounce buffer. This is required only for the "bounce buffer" mode. For more details, see :ref:`bounce_buffer_with_single_psram_frame_buffer`.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::timings` specifies the timing parameters unique to the LCD panel. These parameters, detailed in :cpp:type:`esp_lcd_rgb_timing_t`, include the LCD resolution and blanking porches. Ensure they are set according to your LCD's datasheet.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` determines if the frame buffer should be allocated from PSRAM. For further details, see :ref:`single_frame_buffer_in_psram`.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::num_fbs` specifies how many frame buffers the driver should allocate. For backward compatibility, setting this to ``0`` will allocate a single frame buffer. If you don't want to allocate any frame buffer, use :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` instead.
|
||||
- :cpp:member:`esp_lcd_rgb_panel_config_t::no_fb` determines whether frame buffer will be allocated. When it is set, no frame buffer will be allocated. This is also called the :ref:`bounce_buffer_only` mode.
|
||||
|
||||
RGB LCD Frame Buffer Operation Modes
|
||||
@ -65,7 +65,7 @@ This is the default and simplest and you do not have to specify flags or bounce
|
||||
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 CPU gets the other half. If there are other peripherals using EDMA as well, with a high enough pixel clock, they may cause starvation of the LCD peripheral, resulting in display corruption. However, if the pixel clock is low enough to avoid this issue, it provides a solution with minimal CPU intervention.
|
||||
If you have PSRAM and prefer to store the frame buffer there instead of using the limited internal memory, the LCD peripheral can utilize EDMA to fetch frame data directly from PSRAM, bypassing the internal cache. This can be enabled by setting :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` to ``true``. The trade-off is that when both the CPU and EDMA need access to PSRAM, the bandwidth is **shared** between them, meaning EDMA and the CPU each get half. If other peripherals are also using EDMA, a high pixel clock might cause LCD peripheral starvation, leading to display corruption. However, with a sufficiently low pixel clock, this approach minimizes CPU intervention.
|
||||
|
||||
.. only:: esp32s3
|
||||
|
||||
@ -112,7 +112,7 @@ If you have PSRAM and want to store the frame buffer there rather than in the li
|
||||
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.
|
||||
To prevent tearing effects, the simplest method is to use two screen-sized frame buffers. Given the limited internal memory, these buffers must be allocated from PSRAM. This ensures that the frame buffer being written to by the CPU and the one being read by the EDMA are always distinct and independent. The EDMA will only switch between the two buffers once the current write operation is complete and the frame has been fully transmitted to the LCD. The main drawback of this approach is the need to maintain synchronization between the two frame buffers.
|
||||
|
||||
.. code:: c
|
||||
|
||||
@ -155,15 +155,15 @@ To avoid tearing effect, using two screen sized frame buffers is the easiest app
|
||||
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 uses DMA to read data from one of the bounce buffers, and meanwhile an interrupt routine uses 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 that the LCD **CAN NOT** work if we disable the cache of the external memory, via e.g., OTA or NVS write to the main flash.
|
||||
This mode allocates two "bounce buffers" from internal memory and a main frame buffer in PSRAM. To enable this mode, set the :cpp:member:`esp_lcd_rgb_panel_config_t::fb_in_psram` flag and specify a non-zero value for :cpp:member:`esp_lcd_rgb_panel_config_t::bounce_buffer_size_px`. The bounce buffers only need to hold a few lines of display data, which is much smaller than the main frame buffer. The LCD peripheral uses DMA to read data from one bounce buffer while an interrupt routine uses the CPU DCache to copy data from the main PSRAM frame buffer into the other bounce buffer. Once the LCD peripheral finishes reading from the bounce buffer, the buffers swap roles, allowing the CPU to fill the other one. The advantage of this mode is achieving a higher pixel clock frequency. Since 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 significant increase in CPU usage, and the LCD **CANNOT** function if the external memory cache is disabled, such as during OTA or NVS writes to the main flash.
|
||||
|
||||
.. note::
|
||||
|
||||
It is highly recommended to turn on the "PSRAM XIP (Execute In Place)" feature in this mode by enabling the Kconfig options: :ref:`CONFIG_SPIRAM_FETCH_INSTRUCTIONS` and :ref:`CONFIG_SPIRAM_RODATA`, which allows the CPU to fetch instructions and readonly data from the PSRAM instead of the main flash. What is more, the external memory cache will not be disabled even if you attempt to write to the main flash through SPI 1. This makes it possible to display an OTA progress bar for your application.
|
||||
For optimal performance in this mode, it is highly recommended to enable the "PSRAM XIP (Execute In Place)" feature by turning on the Kconfig option: :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`. This allows the CPU to fetch instructions and read-only data directly from PSRAM instead of the main flash. Additionally, the external memory cache remains active even when writing to the main flash via SPI 1, making it feasible to display an OTA progress bar during your application updates.
|
||||
|
||||
.. note::
|
||||
|
||||
This mode still has another problem which is also caused by insufficient PSRAM bandwidth. For example, when your draw buffers are allocated from PSRAM, and their contents are copied into the internal frame buffer on CPU Core 1, on CPU Core 0, there is another memory copy happening in the DMA EOF ISR. In this situation, both CPUs are accessing the PSRAM by cache and sharing the bandwidth of the PSRAM. This increases the memory copy time that spent in the DMA EOF ISR significantly. The driver can not switch the bounce buffer in time, thus leading to a shift on the LCD screen. Although the driver can detect such a condition and perform a restart in the LCD's VSYNC interrupt handler, you still can see a flickering on the screen.
|
||||
This mode also faces issues due to limited PSRAM bandwidth. For instance, if your draw buffers are in PSRAM and their contents are copied to the internal frame buffer by CPU Core 1, while CPU Core 0 is performing another memory copy in the DMA EOF ISR, both CPUs will be accessing PSRAM via cache, sharing its bandwidth. This significantly increases the memory copy time in the DMA EOF ISR, causing the driver to fail in switching the bounce buffer promptly, resulting in a screen shift. Although the driver can detect this condition and restart in the LCD's VSYNC interrupt handler, you may still notice flickering on the screen.
|
||||
|
||||
.. code:: c
|
||||
|
||||
@ -201,8 +201,6 @@ This mode allocates two so-called ``bounce buffers`` from the internal memory, a
|
||||
};
|
||||
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 are 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
|
||||
@ -212,8 +210,8 @@ This mode is similar to :ref:`bounce_buffer_with_single_psram_frame_buffer`, but
|
||||
|
||||
.. note::
|
||||
|
||||
In a well-designed embedded application, situations where the DMA can not deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can happen in theory. 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, a desynchronization between the LCD address for which the DMA reads the data and the LCD address for which the LCD peripheral outputs data would occur, leading to a **permanently** shifted image.
|
||||
In order to stop this from happening, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, so the driver can restart the DMA in the VBlank interrupt automatically, or call :cpp:func:`esp_lcd_rgb_panel_restart` to restart the DMA manually. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted in the next VSYNC event.
|
||||
In a well-designed embedded application, situations where the DMA cannot deliver data as fast as the LCD consumes it should be avoided. However, such scenarios can theoretically occur. In the {IDF_TARGET_NAME} hardware, this results in the LCD outputting dummy bytes while the DMA waits for data. If the DMA were to run in a continuous stream, it could cause a desynchronization between the LCD address from which the DMA reads data and the address from which the LCD peripheral outputs data, leading to a **permanently** shifted image.
|
||||
To prevent this, you can either enable the :ref:`CONFIG_LCD_RGB_RESTART_IN_VSYNC` option, allowing the driver to automatically restart the DMA during the VBlank interrupt, or call :cpp:func:`esp_lcd_rgb_panel_restart` to manually restart the DMA. Note that :cpp:func:`esp_lcd_rgb_panel_restart` does not restart the DMA immediately; instead, the DMA will be restarted at the next VSYNC event.
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
@ -159,7 +159,7 @@ bounce buffer 与 PSRAM frame buffer
|
||||
|
||||
.. note::
|
||||
|
||||
强烈建议在此模式下启用 Kconfig 选项::ref:`CONFIG_SPIRAM_FETCH_INSTRUCTIONS` 和 :ref:`CONFIG_SPIRAM_RODATA`,开启“PSRAM XIP(就地执行)”功能,使 CPU 能从 PSRAM 里而不是主 flash 中提取指令和只读数据。此外,即使想通过 SPI 1 写入主 flash,外部存储器 cache 也不会被禁用,应用程序便能正常显示 OTA 进度条。
|
||||
强烈建议在此模式下启用 Kconfig 选项::ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM`,开启“PSRAM XIP(就地执行)”功能,使 CPU 能从 PSRAM 里而不是主 flash 中提取指令和只读数据。此外,即使想通过 SPI 1 写入主 flash,外部存储器 cache 也不会被禁用,应用程序便能正常显示 OTA 进度条。
|
||||
|
||||
.. note::
|
||||
|
||||
@ -201,8 +201,6 @@ bounce buffer 与 PSRAM frame buffer
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
|
||||
|
||||
请注意,此模式下还可以设置 :cpp:member:`esp_lcd_rgb_panel_config_t::bb_invalidate_cache` 标志。启用此功能,从 PSRAM 中读取 frame buffer 数据后可以释放 cache 行。但如果在 cache 行被释放时,另一个内核恰好将数据写入 frame buffer 中,则可能导致轻微的损坏(从技术上讲,在 cache 写回和调用失效之间的时间窗口内,对 frame buffer 的写入操作会被忽略)。
|
||||
|
||||
.. _bounce_buffer_only:
|
||||
|
||||
只应用 bounce buffer
|
||||
|
@ -1,5 +1,5 @@
|
||||
| Supported Targets | ESP32-S3 |
|
||||
| ----------------- | -------- |
|
||||
| Supported Targets | ESP32-P4 | ESP32-S3 |
|
||||
| ----------------- | -------- | -------- |
|
||||
|
||||
# RGB LCD Panel Example
|
||||
|
||||
@ -19,8 +19,8 @@ This example uses 3 kinds of **buffering mode**:
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* 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 ESP development board, which supports the RGB LCD peripheral
|
||||
* A general RGB panel, 16/24 bit-width, with HSYNC, VSYNC and DE signal
|
||||
* An USB cable for power supply and programming
|
||||
|
||||
### Hardware Connection
|
||||
@ -36,7 +36,7 @@ The connection between ESP Board and the LCD is as follows:
|
||||
| | | |
|
||||
| PCLK+--------------+PCLK |
|
||||
| | | |
|
||||
| DATA[15:0]+--------------+DATA[15:0] |
|
||||
| DATA[N:0]+--------------+DATA[N:0] |
|
||||
| | | |
|
||||
| HSYNC+--------------+HSYNC |
|
||||
| | | |
|
||||
@ -55,9 +55,9 @@ The connection between ESP Board and the LCD is as follows:
|
||||
|
||||
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)
|
||||
1. `Use single frame buffer`: The RGB LCD driver allocates one frame buffer and mount it to the DMA. The example also allocates one draw buffer for the LVGL library. The draw buffer contents are copied to the frame buffer by the CPU.
|
||||
2. `Use double frame buffer`: The RGB LCD driver allocates two frame buffers and mount them to the DMA. The LVGL library draws directly to the offline frame buffer while the online frame buffer is displayed by the RGB LCD controller.
|
||||
3. `Use bounce buffer`: The RGB LCD driver allocates one frame buffer and two bounce buffers. The bounce buffers are mounted to the DMA. The frame buffer contents are copied to the bounce buffers by the CPU. The example also allocates one draw buffer for the LVGL library. The draw buffer contents are copied to the frame buffer by the CPU.
|
||||
4. Choose the number of LCD data lines in `RGB LCD Data Lines`
|
||||
5. Set the GPIOs used by RGB LCD peripheral in `GPIO assignment`, e.g. the synchronization signals (HSYNC, VSYNC, DE) and the data lines
|
||||
|
||||
@ -97,16 +97,13 @@ I (1102) main_task: Returned from app_main()
|
||||
|
||||
* Why the LCD doesn't light up?
|
||||
* 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).
|
||||
* No memory for frame buffer
|
||||
* Where to allocate the 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 maximum PCLK due to the bandwidth of **SPI0**.
|
||||
* LCD screen drift
|
||||
* Why LCD screen drifts?
|
||||
* Slow down the PCLK frequency
|
||||
* 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_SPIRAM_XIP_FROM_PSRAM`, which can saves some bandwidth of SPI0 from being consumed by ICache.
|
||||
* How to further increase the 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_XIP_FROM_PSRAM` 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.
|
||||
* Why the RGB timing is correct but the LCD doesn't show anything?
|
||||
|
@ -1,23 +1,29 @@
|
||||
menu "Example Configuration"
|
||||
config EXAMPLE_DOUBLE_FB
|
||||
bool "Use double Frame Buffer"
|
||||
default "n"
|
||||
choice EXAMPLE_LCD_BUFFER_MODE
|
||||
prompt "RGB LCD Buffer Mode"
|
||||
default EXAMPLE_USE_SINGLE_FB
|
||||
help
|
||||
Enable this option, driver will allocate two frame buffers.
|
||||
Select the LCD buffer mode.
|
||||
|
||||
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_USE_SINGLE_FB
|
||||
bool "Use single frame buffer"
|
||||
help
|
||||
Allocate one frame buffer in the driver.
|
||||
Allocate one draw buffer in LVGL.
|
||||
|
||||
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.
|
||||
config EXAMPLE_USE_DOUBLE_FB
|
||||
bool "Use double frame buffer"
|
||||
help
|
||||
Allocate two frame buffers in the driver.
|
||||
The frame buffers also work as ping-pong draw buffers in LVGL.
|
||||
|
||||
config EXAMPLE_USE_BOUNCE_BUFFER
|
||||
bool "Use bounce buffer"
|
||||
help
|
||||
Allocate one frame buffer in the driver.
|
||||
Allocate two bounce buffers in the driver.
|
||||
Allocate one draw buffer in LVGL.
|
||||
endchoice
|
||||
|
||||
choice EXAMPLE_LCD_DATA_LINES
|
||||
prompt "RGB LCD Data Lines"
|
||||
@ -27,11 +33,15 @@ menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_LCD_DATA_LINES_16
|
||||
bool "16 data lines"
|
||||
|
||||
config EXAMPLE_LCD_DATA_LINES_24
|
||||
bool "24 data lines"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_LCD_DATA_LINES
|
||||
int
|
||||
default 16 if EXAMPLE_LCD_DATA_LINES_16
|
||||
default 24 if EXAMPLE_LCD_DATA_LINES_24
|
||||
|
||||
menu "GPIO assignment"
|
||||
config EXAMPLE_LCD_VSYNC_GPIO
|
||||
@ -114,5 +124,45 @@ menu "Example Configuration"
|
||||
int "DATA15 GPIO"
|
||||
help
|
||||
GPIO pin number for data bus[15].
|
||||
config EXAMPLE_LCD_DATA16_GPIO
|
||||
int "DATA16 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[16].
|
||||
config EXAMPLE_LCD_DATA17_GPIO
|
||||
int "DATA17 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[17].
|
||||
config EXAMPLE_LCD_DATA18_GPIO
|
||||
int "DATA18 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[18].
|
||||
config EXAMPLE_LCD_DATA19_GPIO
|
||||
int "DATA19 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[19].
|
||||
config EXAMPLE_LCD_DATA20_GPIO
|
||||
int "DATA20 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[20].
|
||||
config EXAMPLE_LCD_DATA21_GPIO
|
||||
int "DATA21 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[21].
|
||||
config EXAMPLE_LCD_DATA22_GPIO
|
||||
int "DATA22 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[22].
|
||||
config EXAMPLE_LCD_DATA23_GPIO
|
||||
int "DATA23 GPIO"
|
||||
depends on EXAMPLE_LCD_DATA_LINES > 16
|
||||
help
|
||||
GPIO pin number for data bus[23].
|
||||
endmenu
|
||||
endmenu
|
||||
|
@ -10,7 +10,6 @@
|
||||
#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"
|
||||
@ -61,17 +60,31 @@ static const char *TAG = "example";
|
||||
#define EXAMPLE_PIN_NUM_DATA13 CONFIG_EXAMPLE_LCD_DATA13_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA14 CONFIG_EXAMPLE_LCD_DATA14_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA15 CONFIG_EXAMPLE_LCD_DATA15_GPIO
|
||||
#if CONFIG_EXAMPLE_LCD_DATA_LINES > 16
|
||||
#define EXAMPLE_PIN_NUM_DATA16 CONFIG_EXAMPLE_LCD_DATA16_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA17 CONFIG_EXAMPLE_LCD_DATA17_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA18 CONFIG_EXAMPLE_LCD_DATA18_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA19 CONFIG_EXAMPLE_LCD_DATA19_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA20 CONFIG_EXAMPLE_LCD_DATA20_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA21 CONFIG_EXAMPLE_LCD_DATA21_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA22 CONFIG_EXAMPLE_LCD_DATA22_GPIO
|
||||
#define EXAMPLE_PIN_NUM_DATA23 CONFIG_EXAMPLE_LCD_DATA23_GPIO
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_DOUBLE_FB
|
||||
#if CONFIG_EXAMPLE_USE_DOUBLE_FB
|
||||
#define EXAMPLE_LCD_NUM_FB 2
|
||||
#else
|
||||
#define EXAMPLE_LCD_NUM_FB 1
|
||||
#endif // CONFIG_EXAMPLE_DOUBLE_FB
|
||||
#endif // CONFIG_EXAMPLE_USE_DOUBLE_FB
|
||||
|
||||
#if CONFIG_EXAMPLE_LCD_DATA_LINES_16
|
||||
#define EXAMPLE_DATA_BUS_WIDTH 16
|
||||
#define EXAMPLE_PIXEL_SIZE 2
|
||||
#define EXAMPLE_LV_COLOR_FORMAT LV_COLOR_FORMAT_RGB565
|
||||
#elif CONFIG_EXAMPLE_LCD_DATA_LINES_24
|
||||
#define EXAMPLE_DATA_BUS_WIDTH 24
|
||||
#define EXAMPLE_PIXEL_SIZE 3
|
||||
#define EXAMPLE_LV_COLOR_FORMAT LV_COLOR_FORMAT_RGB888
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@ -86,23 +99,13 @@ static const char *TAG = "example";
|
||||
// LVGL library is not thread-safe, this example will call LVGL APIs from different tasks, so use a mutex to protect it
|
||||
static _lock_t lvgl_api_lock;
|
||||
|
||||
// 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_display_t *disp);
|
||||
|
||||
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)
|
||||
static bool example_notify_lvgl_flush_ready(esp_lcd_panel_handle_t panel, const esp_lcd_rgb_panel_event_data_t *event_data, void *user_ctx)
|
||||
{
|
||||
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;
|
||||
lv_display_t *disp = (lv_display_t *)user_ctx;
|
||||
lv_display_flush_ready(disp);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map)
|
||||
@ -112,13 +115,8 @@ static void example_lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uin
|
||||
int offsetx2 = area->x2;
|
||||
int offsety1 = area->y1;
|
||||
int offsety2 = area->y2;
|
||||
#if CONFIG_EXAMPLE_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, px_map);
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
|
||||
static void example_increase_lvgl_tick(void *arg)
|
||||
@ -165,14 +163,6 @@ static void example_bsp_set_lcd_backlight(uint32_t level)
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#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
|
||||
|
||||
ESP_LOGI(TAG, "Turn off LCD backlight");
|
||||
example_bsp_init_lcd_backlight();
|
||||
example_bsp_set_lcd_backlight(EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
|
||||
@ -209,6 +199,16 @@ void app_main(void)
|
||||
EXAMPLE_PIN_NUM_DATA13,
|
||||
EXAMPLE_PIN_NUM_DATA14,
|
||||
EXAMPLE_PIN_NUM_DATA15,
|
||||
#if CONFIG_EXAMPLE_LCD_DATA_LINES > 16
|
||||
EXAMPLE_PIN_NUM_DATA16,
|
||||
EXAMPLE_PIN_NUM_DATA17,
|
||||
EXAMPLE_PIN_NUM_DATA18,
|
||||
EXAMPLE_PIN_NUM_DATA19,
|
||||
EXAMPLE_PIN_NUM_DATA20,
|
||||
EXAMPLE_PIN_NUM_DATA21,
|
||||
EXAMPLE_PIN_NUM_DATA22,
|
||||
EXAMPLE_PIN_NUM_DATA23
|
||||
#endif
|
||||
},
|
||||
.timings = {
|
||||
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
|
||||
@ -246,7 +246,7 @@ void app_main(void)
|
||||
// create draw buffers
|
||||
void *buf1 = NULL;
|
||||
void *buf2 = NULL;
|
||||
#if CONFIG_EXAMPLE_DOUBLE_FB
|
||||
#if CONFIG_EXAMPLE_USE_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));
|
||||
// set LVGL draw buffers and direct mode
|
||||
@ -259,14 +259,14 @@ void app_main(void)
|
||||
assert(buf1);
|
||||
// set LVGL draw buffers and partial mode
|
||||
lv_display_set_buffers(display, buf1, buf2, draw_buffer_sz, LV_DISPLAY_RENDER_MODE_PARTIAL);
|
||||
#endif // CONFIG_EXAMPLE_DOUBLE_FB
|
||||
#endif // CONFIG_EXAMPLE_USE_DOUBLE_FB
|
||||
|
||||
// set the callback which can copy the rendered image to an area of the display
|
||||
lv_display_set_flush_cb(display, example_lvgl_flush_cb);
|
||||
|
||||
ESP_LOGI(TAG, "Register event callbacks");
|
||||
esp_lcd_rgb_panel_event_callbacks_t cbs = {
|
||||
.on_vsync = example_on_vsync_event,
|
||||
.on_color_trans_done = example_notify_lvgl_flush_ready,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(panel_handle, &cbs, display));
|
||||
|
||||
|
@ -15,7 +15,29 @@ from pytest_embedded import Dut
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_rgb_lcd_lvgl(dut: Dut) -> None:
|
||||
def test_rgb_lcd_lvgl_esp32s3(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Turn off LCD backlight')
|
||||
dut.expect_exact('example: Install RGB LCD panel driver')
|
||||
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: Install LVGL tick timer')
|
||||
dut.expect_exact('example: Create LVGL task')
|
||||
dut.expect_exact('example: Display LVGL UI')
|
||||
|
||||
|
||||
@pytest.mark.esp32p4
|
||||
@pytest.mark.generic
|
||||
@pytest.mark.parametrize(
|
||||
'config',
|
||||
[
|
||||
'single_fb_with_bb',
|
||||
'single_fb_no_bb',
|
||||
'double_fb',
|
||||
],
|
||||
indirect=True,
|
||||
)
|
||||
def test_rgb_lcd_lvgl_esp32p4(dut: Dut) -> None:
|
||||
dut.expect_exact('example: Turn off LCD backlight')
|
||||
dut.expect_exact('example: Install RGB LCD panel driver')
|
||||
dut.expect_exact('example: Initialize RGB LCD panel')
|
||||
|
@ -1 +1 @@
|
||||
CONFIG_EXAMPLE_DOUBLE_FB=y
|
||||
CONFIG_EXAMPLE_USE_DOUBLE_FB=y
|
||||
|
@ -1,2 +1 @@
|
||||
CONFIG_EXAMPLE_DOUBLE_FB=n
|
||||
CONFIG_EXAMPLE_USE_BOUNCE_BUFFER=n
|
||||
CONFIG_EXAMPLE_USE_SINGLE_FB=y
|
||||
|
@ -1,2 +1 @@
|
||||
CONFIG_EXAMPLE_DOUBLE_FB=n
|
||||
CONFIG_EXAMPLE_USE_BOUNCE_BUFFER=y
|
||||
|
@ -0,0 +1,46 @@
|
||||
# enable the experimental features for higher PSRAM speed
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_HEX=y
|
||||
CONFIG_SPIRAM_SPEED_200M=y
|
||||
|
||||
# LCD_CAM support 24 data lines at most
|
||||
CONFIG_EXAMPLE_LCD_DATA_LINES_24=y
|
||||
CONFIG_LV_COLOR_DEPTH_24=y
|
||||
|
||||
# Default GPIO assignment
|
||||
CONFIG_EXAMPLE_LCD_VSYNC_GPIO=41
|
||||
CONFIG_EXAMPLE_LCD_HSYNC_GPIO=39
|
||||
CONFIG_EXAMPLE_LCD_DE_GPIO=43
|
||||
CONFIG_EXAMPLE_LCD_PCLK_GPIO=33
|
||||
|
||||
# B0:B7 <=> DATA0:DATA7
|
||||
CONFIG_EXAMPLE_LCD_DATA0_GPIO=34
|
||||
CONFIG_EXAMPLE_LCD_DATA1_GPIO=12
|
||||
CONFIG_EXAMPLE_LCD_DATA2_GPIO=10
|
||||
CONFIG_EXAMPLE_LCD_DATA3_GPIO=40
|
||||
CONFIG_EXAMPLE_LCD_DATA4_GPIO=42
|
||||
CONFIG_EXAMPLE_LCD_DATA5_GPIO=27
|
||||
CONFIG_EXAMPLE_LCD_DATA6_GPIO=29
|
||||
CONFIG_EXAMPLE_LCD_DATA7_GPIO=31
|
||||
|
||||
# G0:G7 <=> DATA8:DATA15
|
||||
CONFIG_EXAMPLE_LCD_DATA8_GPIO=16
|
||||
CONFIG_EXAMPLE_LCD_DATA9_GPIO=14
|
||||
CONFIG_EXAMPLE_LCD_DATA10_GPIO=21
|
||||
CONFIG_EXAMPLE_LCD_DATA11_GPIO=23
|
||||
CONFIG_EXAMPLE_LCD_DATA12_GPIO=26
|
||||
CONFIG_EXAMPLE_LCD_DATA13_GPIO=28
|
||||
CONFIG_EXAMPLE_LCD_DATA14_GPIO=30
|
||||
CONFIG_EXAMPLE_LCD_DATA15_GPIO=32
|
||||
|
||||
# R0:R7 <=> DATA16:DATA23
|
||||
CONFIG_EXAMPLE_LCD_DATA16_GPIO=22
|
||||
CONFIG_EXAMPLE_LCD_DATA17_GPIO=20
|
||||
CONFIG_EXAMPLE_LCD_DATA18_GPIO=18
|
||||
CONFIG_EXAMPLE_LCD_DATA19_GPIO=6
|
||||
CONFIG_EXAMPLE_LCD_DATA20_GPIO=0
|
||||
CONFIG_EXAMPLE_LCD_DATA21_GPIO=15
|
||||
CONFIG_EXAMPLE_LCD_DATA22_GPIO=17
|
||||
CONFIG_EXAMPLE_LCD_DATA23_GPIO=19
|
Reference in New Issue
Block a user