rgb_lcd: workaround auto-next frame bug

Closes https://github.com/espressif/esp-idf/issues/8620
This commit is contained in:
morris
2022-03-23 18:45:26 +08:00
parent d54df43d4c
commit ab3d75ab75
9 changed files with 204 additions and 112 deletions

View File

@@ -14,4 +14,5 @@ set(priv_requires "driver")
idf_component_register(SRCS ${srcs} idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${includes} INCLUDE_DIRS ${includes}
PRIV_REQUIRES ${priv_requires}) PRIV_REQUIRES ${priv_requires}
LDFRAGMENTS linker.lf)

View File

@@ -6,5 +6,16 @@ menu "LCD and Touch Panel"
help help
LCD driver allocates an internal buffer to transform the data into a proper format, because of LCD driver allocates an internal buffer to transform the data into a proper format, because of
the endian order mismatch. This option is to set the size of the buffer, in bytes. the endian order mismatch. This option is to set the size of the buffer, in bytes.
config LCD_RGB_ISR_IRAM_SAFE
depends on IDF_TARGET_ESP32S3
bool "RGB LCD ISR IRAM-Safe"
default n
select GDMA_CTRL_FUNC_IN_IRAM # need to restart GDMA in the LCD ISR
help
Ensure the LCD interrupt is IRAM-Safe by allowing the interrupt handler to be
executable when the cache is disabled (e.g. SPI Flash write).
If you want the LCD driver to keep flushing the screen even when cache ops disabled,
you can enable this option. Note, this will also increase the IRAM usage.
endmenu endmenu
endmenu endmenu

View File

@@ -59,7 +59,7 @@ typedef struct {
unsigned int hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */ unsigned int hsync_front_porch; /*!< Horizontal front porch, number of PCLK between the end of active data and the next hsync */
unsigned int vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */ unsigned int vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */
unsigned int vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */ unsigned int vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between then end of frame and the next vsync */ unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between the end of frame and the next vsync */
struct { struct {
unsigned int hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */ unsigned int hsync_idle_low: 1; /*!< The hsync signal is low in IDLE state */
unsigned int vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */ unsigned int vsync_idle_low: 1; /*!< The vsync signal is low in IDLE state */

View File

@@ -0,0 +1,6 @@
[mapping:esp_lcd]
archive: libesp_lcd.a
entries:
if LCD_RGB_ISR_IRAM_SAFE = y:
esp_lcd_common: lcd_com_mount_dma_data (noflash)
esp_lcd_rgb_panel: lcd_rgb_panel_start_transmission (noflash)

View File

@@ -1,13 +1,16 @@
/* /*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
#pragma once #pragma once
#include <stddef.h> #include <stddef.h>
#include "sdkconfig.h"
#include "soc/soc_caps.h" #include "soc/soc_caps.h"
#include "hal/dma_types.h" #include "hal/dma_types.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#if SOC_LCDCAM_SUPPORTED #if SOC_LCDCAM_SUPPORTED
#include "hal/lcd_hal.h" #include "hal/lcd_hal.h"
#endif #endif
@@ -16,6 +19,9 @@
extern "C" { extern "C" {
#endif #endif
#define LCD_I80_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
#define LCD_I80_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#define LCD_PERIPH_CLOCK_PRE_SCALE (2) // This is the minimum divider that can be applied to LCD peripheral #define LCD_PERIPH_CLOCK_PRE_SCALE (2) // This is the minimum divider that can be applied to LCD peripheral
#if SOC_LCDCAM_SUPPORTED #if SOC_LCDCAM_SUPPORTED

View File

@@ -16,8 +16,6 @@
#include "freertos/semphr.h" #include "freertos/semphr.h"
#include "esp_attr.h" #include "esp_attr.h"
#include "esp_check.h" #include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h" #include "esp_pm.h"
#include "esp_lcd_panel_interface.h" #include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_rgb.h"
@@ -39,6 +37,12 @@
#include "hal/lcd_hal.h" #include "hal/lcd_hal.h"
#include "hal/lcd_ll.h" #include "hal/lcd_ll.h"
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
#define LCD_RGB_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
#else
#define LCD_RGB_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
#endif
static const char *TAG = "lcd_panel.rgb"; static const char *TAG = "lcd_panel.rgb";
typedef struct esp_rgb_panel_t esp_rgb_panel_t; typedef struct esp_rgb_panel_t esp_rgb_panel_t;
@@ -58,7 +62,8 @@ static esp_err_t rgb_panel_disp_off(esp_lcd_panel_t *panel, bool off);
static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src); static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src);
static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel); static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *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 *panel, const esp_lcd_rgb_panel_config_t *panel_config);
static IRAM_ATTR void lcd_default_isr_handler(void *args); static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel);
static void lcd_default_isr_handler(void *args);
struct esp_rgb_panel_t { struct esp_rgb_panel_t {
esp_lcd_panel_t base; // Base class of generic lcd panel esp_lcd_panel_t base; // Base class of generic lcd panel
@@ -77,9 +82,6 @@ struct esp_rgb_panel_t {
size_t resolution_hz; // Peripheral clock resolution size_t resolution_hz; // Peripheral clock resolution
esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width) esp_lcd_rgb_timing_t timings; // RGB timing parameters (e.g. pclk, sync pulse, porch width)
gdma_channel_handle_t dma_chan; // DMA channel handle gdma_channel_handle_t dma_chan; // DMA channel handle
int new_frame_id; // ID for new frame, we use ID to identify whether the frame content has been updated
int cur_frame_id; // ID for current transferring frame
SemaphoreHandle_t done_sem; // Binary semaphore, indicating if the new frame has been flushed to LCD
esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done esp_lcd_rgb_panel_frame_trans_done_cb_t on_frame_trans_done; // Callback, invoked after frame trans done
void *user_ctx; // Reserved user's data of callback functions 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 x_gap; // Extra gap in x coordinate, it's used when calculate the flush window
@@ -87,7 +89,6 @@ struct esp_rgb_panel_t {
struct { struct {
unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num` unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
unsigned int new_frame: 1; // Whether the frame we're going to flush is a new one
unsigned int fb_in_psram: 1; // Whether the frame buffer is in PSRAM unsigned int fb_in_psram: 1; // Whether the frame buffer is in PSRAM
} flags; } flags;
dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes` dma_descriptor_t dma_nodes[]; // DMA descriptor pool of size `num_dma_nodes`
@@ -100,6 +101,16 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter"); ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid parameter");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err, TAG, ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err, TAG,
"unsupported data width %d", rgb_panel_config->data_width); "unsupported data width %d", rgb_panel_config->data_width);
#if CONFIG_LCD_RGB_ISR_IRAM_SAFE
if (rgb_panel_config->on_frame_trans_done) {
ESP_RETURN_ON_FALSE(esp_ptr_in_iram(rgb_panel_config->on_frame_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_frame_trans_done callback not in IRAM");
}
if (rgb_panel_config->user_ctx) {
ESP_RETURN_ON_FALSE(esp_ptr_internal(rgb_panel_config->user_ctx), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM");
}
#endif
// calculate the number of DMA descriptors // calculate the number of DMA descriptors
size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * rgb_panel_config->data_width / 8; size_t fb_size = rgb_panel_config->timings.h_res * rgb_panel_config->timings.v_res * rgb_panel_config->data_width / 8;
size_t num_dma_nodes = fb_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE; size_t num_dma_nodes = fb_size / DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
@@ -107,7 +118,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
num_dma_nodes++; num_dma_nodes++;
} }
// DMA descriptors must be placed in internal SRAM (requested by DMA) // DMA descriptors must be placed in internal SRAM (requested by DMA)
rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA); rgb_panel = heap_caps_calloc(1, sizeof(esp_rgb_panel_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel"); ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, err, TAG, "no mem for rgb panel");
rgb_panel->num_dma_nodes = num_dma_nodes; rgb_panel->num_dma_nodes = num_dma_nodes;
rgb_panel->panel_id = -1; rgb_panel->panel_id = -1;
@@ -117,6 +128,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->panel_id = panel_id; rgb_panel->panel_id = panel_id;
// enable APB to access LCD registers // enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module); periph_module_enable(lcd_periph_signals.panels[panel_id].module);
periph_module_reset(lcd_periph_signals.panels[panel_id].module);
// alloc frame buffer // alloc frame buffer
bool alloc_from_psram = false; bool alloc_from_psram = false;
// fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM // fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM
@@ -140,17 +152,13 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->sram_trans_align = sram_trans_align; rgb_panel->sram_trans_align = sram_trans_align;
rgb_panel->fb_size = fb_size; rgb_panel->fb_size = fb_size;
rgb_panel->flags.fb_in_psram = alloc_from_psram; rgb_panel->flags.fb_in_psram = alloc_from_psram;
// semaphore indicates new frame trans done
rgb_panel->done_sem = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(rgb_panel->done_sem, ESP_ERR_NO_MEM, err, TAG, "create done sem failed");
xSemaphoreGive(rgb_panel->done_sem); // initialize the semaphore count to 1
// initialize HAL layer, so we can call LL APIs later // initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&rgb_panel->hal, panel_id); lcd_hal_init(&rgb_panel->hal, panel_id);
// set peripheral clock resolution // set peripheral clock resolution
ret = lcd_rgb_panel_select_periph_clock(rgb_panel, rgb_panel_config->clk_src); ret = lcd_rgb_panel_select_periph_clock(rgb_panel, rgb_panel_config->clk_src);
ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed"); ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock failed");
// install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask) // install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask)
int isr_flags = ESP_INTR_FLAG_SHARED; int isr_flags = LCD_RGB_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags, ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags,
(uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev), (uint32_t)lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev),
LCD_LL_EVENT_VSYNC_END, lcd_default_isr_handler, rgb_panel, &rgb_panel->intr); LCD_LL_EVENT_VSYNC_END, lcd_default_isr_handler, rgb_panel, &rgb_panel->intr);
@@ -184,7 +192,7 @@ esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_conf
rgb_panel->base.set_gap = rgb_panel_set_gap; rgb_panel->base.set_gap = rgb_panel_set_gap;
// return base class // return base class
*ret_panel = &(rgb_panel->base); *ret_panel = &(rgb_panel->base);
ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb_size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb_size); ESP_LOGD(TAG, "new rgb panel(%d) @%p, fb @%p, size=%zu", rgb_panel->panel_id, rgb_panel, rgb_panel->fb, rgb_panel->fb_size);
return ESP_OK; return ESP_OK;
err: err:
@@ -196,9 +204,6 @@ err:
if (rgb_panel->fb) { if (rgb_panel->fb) {
free(rgb_panel->fb); free(rgb_panel->fb);
} }
if (rgb_panel->done_sem) {
vSemaphoreDelete(rgb_panel->done_sem);
}
if (rgb_panel->dma_chan) { if (rgb_panel->dma_chan) {
gdma_disconnect(rgb_panel->dma_chan); gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan); gdma_del_channel(rgb_panel->dma_chan);
@@ -218,14 +223,12 @@ err:
static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel) static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel)
{ {
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base); esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
xSemaphoreTake(rgb_panel->done_sem, portMAX_DELAY); // wait for last flush done
int panel_id = rgb_panel->panel_id; int panel_id = rgb_panel->panel_id;
gdma_disconnect(rgb_panel->dma_chan); gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan); gdma_del_channel(rgb_panel->dma_chan);
esp_intr_free(rgb_panel->intr); esp_intr_free(rgb_panel->intr);
periph_module_disable(lcd_periph_signals.panels[panel_id].module); periph_module_disable(lcd_periph_signals.panels[panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id); lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
vSemaphoreDelete(rgb_panel->done_sem);
free(rgb_panel->fb); free(rgb_panel->fb);
if (rgb_panel->pm_lock) { if (rgb_panel->pm_lock) {
esp_pm_lock_release(rgb_panel->pm_lock); esp_pm_lock_release(rgb_panel->pm_lock);
@@ -280,10 +283,16 @@ 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); lcd_ll_enable_output_hsync_in_porch_region(rgb_panel->hal.dev, true);
// generate the hsync at the very begining of line // generate the hsync at the very begining of line
lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0); lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
// starting sending next frame automatically // restart flush by hardware has some limitation, instead, the driver will restart the flush in the VSYNC end interrupt by software
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode); lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, false);
// trigger interrupt on the end of frame // trigger interrupt on the end of frame
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true); lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true);
// enable intr
esp_intr_enable(rgb_panel->intr);
// start transmission
if (rgb_panel->flags.stream_mode) {
lcd_rgb_panel_start_transmission(rgb_panel);
}
ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%uHz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz); ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%uHz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz);
err: err:
return ret; return ret;
@@ -303,7 +312,7 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
x_end = MIN(x_end, rgb_panel->timings.h_res); x_end = MIN(x_end, rgb_panel->timings.h_res);
y_start = MIN(y_start, rgb_panel->timings.v_res); y_start = MIN(y_start, rgb_panel->timings.v_res);
y_end = MIN(y_end, rgb_panel->timings.v_res); y_end = MIN(y_end, rgb_panel->timings.v_res);
xSemaphoreTake(rgb_panel->done_sem, portMAX_DELAY); // wait for last transaction done
// convert the frame buffer to 3D array // convert the frame buffer to 3D array
int bytes_per_pixel = rgb_panel->data_width / 8; int bytes_per_pixel = rgb_panel->data_width / 8;
int pixels_per_line = rgb_panel->timings.h_res; int pixels_per_line = rgb_panel->timings.h_res;
@@ -321,20 +330,12 @@ static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int
// CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back // CPU writes data to PSRAM through DCache, data in PSRAM might not get updated, so write back
Cache_WriteBack_Addr((uint32_t)&to[y_start][0][0], (y_end - y_start) * rgb_panel->timings.h_res * bytes_per_pixel); Cache_WriteBack_Addr((uint32_t)&to[y_start][0][0], (y_end - y_start) * rgb_panel->timings.h_res * bytes_per_pixel);
} }
// we don't care the exact frame ID, as long as it's different from the previous one
rgb_panel->new_frame_id++; // restart the new transmission
if (!rgb_panel->flags.stream_mode) { if (!rgb_panel->flags.stream_mode) {
// in one-off mode, the "new frame" flag is controlled by this API lcd_rgb_panel_start_transmission(rgb_panel);
rgb_panel->cur_frame_id = rgb_panel->new_frame_id;
rgb_panel->flags.new_frame = 1;
// reset FIFO of DMA and LCD, incase there remains old frame data
gdma_reset(rgb_panel->dma_chan);
lcd_ll_stop(rgb_panel->hal.dev);
lcd_ll_fifo_reset(rgb_panel->hal.dev);
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes);
} }
// start LCD engine
lcd_ll_start(rgb_panel->hal.dev);
return ESP_OK; return ESP_OK;
} }
@@ -439,7 +440,8 @@ static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_
static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src) static esp_err_t lcd_rgb_panel_select_periph_clock(esp_rgb_panel_t *panel, lcd_clock_source_t clk_src)
{ {
esp_err_t ret = ESP_OK; esp_err_t ret = ESP_OK;
lcd_ll_set_group_clock_src(panel->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0); // force to use integer division, as fractional division might lead to clock jitter
lcd_ll_set_group_clock_src(panel->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0);
switch (clk_src) { switch (clk_src) {
case LCD_CLK_SRC_PLL160M: case LCD_CLK_SRC_PLL160M:
panel->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE; panel->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
@@ -469,12 +471,8 @@ static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel)
panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; panel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
panel->dma_nodes[i].next = &panel->dma_nodes[i + 1]; panel->dma_nodes[i].next = &panel->dma_nodes[i + 1];
} }
// fix the last DMA descriptor according to whether the LCD works in stream mode // one-off DMA chain
if (panel->flags.stream_mode) { panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL;
panel->dma_nodes[panel->num_dma_nodes - 1].next = &panel->dma_nodes[0]; // chain into a circle
} else {
panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL; // one-off DMA chain
}
// mount the frame buffer to the DMA descriptors // mount the frame buffer to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_nodes, panel->fb, panel->fb_size); lcd_com_mount_dma_data(panel->dma_nodes, panel->fb, panel->fb_size);
// alloc DMA channel and connect to LCD peripheral // alloc DMA channel and connect to LCD peripheral
@@ -496,32 +494,37 @@ err:
return ret; return ret;
} }
static void lcd_rgb_panel_start_transmission(esp_rgb_panel_t *rgb_panel)
{
// reset FIFO of DMA and LCD, incase there remains old frame data
gdma_reset(rgb_panel->dma_chan);
lcd_ll_stop(rgb_panel->hal.dev);
lcd_ll_fifo_reset(rgb_panel->hal.dev);
gdma_start(rgb_panel->dma_chan, (intptr_t)rgb_panel->dma_nodes);
// delay 1us is sufficient for DMA to pass data to LCD FIFO
// in fact, this is only needed when LCD pixel clock is set too high
esp_rom_delay_us(1);
// start LCD engine
lcd_ll_start(rgb_panel->hal.dev);
}
IRAM_ATTR static void lcd_default_isr_handler(void *args) IRAM_ATTR static void lcd_default_isr_handler(void *args)
{ {
esp_rgb_panel_t *panel = (esp_rgb_panel_t *)args; esp_rgb_panel_t *rgb_panel = (esp_rgb_panel_t *)args;
bool need_yield = false; bool need_yield = false;
BaseType_t high_task_woken = pdFALSE;
uint32_t intr_status = lcd_ll_get_interrupt_status(panel->hal.dev); uint32_t intr_status = lcd_ll_get_interrupt_status(rgb_panel->hal.dev);
lcd_ll_clear_interrupt_status(panel->hal.dev, intr_status); lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, intr_status);
if (intr_status & LCD_LL_EVENT_VSYNC_END) { if (intr_status & LCD_LL_EVENT_VSYNC_END) {
if (panel->flags.new_frame) { // the finished one is a new frame // call user registered callback
if (panel->on_frame_trans_done) { if (rgb_panel->on_frame_trans_done) {
if (panel->on_frame_trans_done(&panel->base, NULL, panel->user_ctx)) { if (rgb_panel->on_frame_trans_done(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true; need_yield = true;
} }
} }
xSemaphoreGiveFromISR(panel->done_sem, &high_task_woken); // to restart the transmission
if (high_task_woken == pdTRUE) { if (rgb_panel->flags.stream_mode) {
need_yield = true; lcd_rgb_panel_start_transmission(rgb_panel);
}
}
// in stream mode, the "new frame" flag is controlled by comparing "new frame id" and "cur frame id"
if (panel->flags.stream_mode) {
// new_frame_id is only modified in `rgb_panel_draw_bitmap()`, fetch first and use below to avoid inconsistent
int new_frame_id = panel->new_frame_id;
panel->flags.new_frame = (panel->cur_frame_id != new_frame_id);
panel->cur_frame_id = new_frame_id;
} }
} }
if (need_yield) { if (need_yield) {

View File

@@ -5,6 +5,7 @@
#include "esp_lcd_panel_rgb.h" #include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_ops.h"
#include "soc/soc_caps.h" #include "soc/soc_caps.h"
#include "esp_attr.h"
#define TEST_LCD_H_RES (480) #define TEST_LCD_H_RES (480)
#define TEST_LCD_V_RES (272) #define TEST_LCD_V_RES (272)
@@ -29,20 +30,23 @@
#define TEST_LCD_DATA13_GPIO (16) // R2 #define TEST_LCD_DATA13_GPIO (16) // R2
#define TEST_LCD_DATA14_GPIO (17) // R3 #define TEST_LCD_DATA14_GPIO (17) // R3
#define TEST_LCD_DATA15_GPIO (18) // R4 #define TEST_LCD_DATA15_GPIO (18) // R4
#define TEST_LCD_DISP_EN_GPIO (39) #define TEST_LCD_DISP_EN_GPIO (-1)
#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000)
#if SOC_LCD_RGB_SUPPORTED #if SOC_LCD_RGB_SUPPORTED
// RGB driver consumes a huge memory to save frame buffer, only test it with PSRAM enabled // RGB driver consumes a huge memory to save frame buffer, only test it with PSRAM enabled
#if CONFIG_SPIRAM_USE_MALLOC #if CONFIG_SPIRAM_USE_MALLOC
TEST_CASE("lcd rgb lcd panel", "[lcd]")
{
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
uint8_t *img = malloc(TEST_IMG_SIZE);
TEST_ASSERT_NOT_NULL(img);
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
static esp_lcd_panel_handle_t test_rgb_panel_initialization(bool stream_mode, esp_lcd_rgb_panel_frame_trans_done_cb_t cb, void *user_data)
{
esp_lcd_panel_handle_t panel_handle = NULL; esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = { esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16, .data_width = 16,
.psram_trans_align = 64,
.clk_src = LCD_CLK_SRC_PLL160M,
.disp_gpio_num = TEST_LCD_DISP_EN_GPIO, .disp_gpio_num = TEST_LCD_DISP_EN_GPIO,
.pclk_gpio_num = TEST_LCD_PCLK_GPIO, .pclk_gpio_num = TEST_LCD_PCLK_GPIO,
.vsync_gpio_num = TEST_LCD_VSYNC_GPIO, .vsync_gpio_num = TEST_LCD_VSYNC_GPIO,
@@ -67,25 +71,37 @@ TEST_CASE("lcd rgb lcd panel", "[lcd]")
TEST_LCD_DATA15_GPIO, TEST_LCD_DATA15_GPIO,
}, },
.timings = { .timings = {
.pclk_hz = 12000000, .pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.h_res = TEST_LCD_H_RES, .h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES, .v_res = TEST_LCD_V_RES,
.hsync_back_porch = 43, .hsync_back_porch = 68,
.hsync_front_porch = 2, .hsync_front_porch = 20,
.hsync_pulse_width = 1, .hsync_pulse_width = 5,
.vsync_back_porch = 12, .vsync_back_porch = 18,
.vsync_front_porch = 1, .vsync_front_porch = 4,
.vsync_pulse_width = 1, .vsync_pulse_width = 1,
}, },
.flags.fb_in_psram = 1, .on_frame_trans_done = cb,
.user_ctx = user_data,
.flags.fb_in_psram = 1, // allocate frame buffer in PSRAM
.flags.relax_on_idle = !stream_mode,
}; };
// Test stream mode and one-off mode
for (int i = 0; i < 2; i++) {
panel_config.flags.relax_on_idle = i;
TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle)); TEST_ESP_OK(esp_lcd_new_rgb_panel(&panel_config, &panel_handle));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
return panel_handle;
}
TEST_CASE("lcd_rgb_panel_stream_mode", "[lcd]")
{
uint8_t *img = malloc(TEST_IMG_SIZE);
TEST_ASSERT_NOT_NULL(img);
printf("initialize RGB panel with stream mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(true, NULL, NULL);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) { for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF; uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100); int x_start = esp_random() % (TEST_LCD_H_RES - 100);
@@ -93,11 +109,41 @@ TEST_CASE("lcd rgb lcd panel", "[lcd]")
memset(img, color_byte, TEST_IMG_SIZE); memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img); esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
} }
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
free(img);
}
static IRAM_ATTR bool test_rgb_panel_trans_done(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
TaskHandle_t task_to_notify = (TaskHandle_t)user_ctx;
BaseType_t high_task_wakeup;
vTaskNotifyGiveFromISR(task_to_notify, &high_task_wakeup);
return high_task_wakeup == pdTRUE;
}
TEST_CASE("lcd_rgb_panel_one_shot_mode", "[lcd]")
{
uint8_t *img = malloc(TEST_IMG_SIZE);
TEST_ASSERT_NOT_NULL(img);
TaskHandle_t cur_task = xTaskGetCurrentTaskHandle();
printf("initialize RGB panel with ont-shot mode\r\n");
esp_lcd_panel_handle_t panel_handle = test_rgb_panel_initialization(false, test_rgb_panel_trans_done, cur_task);
printf("flush random color block\r\n");
for (int i = 0; i < 200; i++) {
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
// wait for flush done
TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)));
}
printf("delete RGB panel\r\n");
TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
}
free(img); free(img);
#undef TEST_IMG_SIZE
} }
// The following test shows a porting example of LVGL GUI library // The following test shows a porting example of LVGL GUI library

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -34,7 +34,11 @@ static inline void lcd_ll_enable_clock(lcd_cam_dev_t *dev, bool en)
static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_source_t src, int div_num, int div_a, int div_b) static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_source_t src, int div_num, int div_a, int div_b)
{ {
// lcd_clk = module_clock_src / (div_num + div_b / div_a) // lcd_clk = module_clock_src / (div_num + div_b / div_a)
HAL_ASSERT(div_num >= 2); HAL_ASSERT(div_num >= 2 && div_num <= 256);
// dic_num == 0 means 256 divider in hardware
if (div_num >= 256) {
div_num = 0;
}
HAL_FORCE_MODIFY_U32_REG_FIELD(dev->lcd_clock, lcd_clkm_div_num, div_num); HAL_FORCE_MODIFY_U32_REG_FIELD(dev->lcd_clock, lcd_clkm_div_num, div_num);
dev->lcd_clock.lcd_clkm_div_a = div_a; dev->lcd_clock.lcd_clkm_div_a = div_a;
dev->lcd_clock.lcd_clkm_div_b = div_b; dev->lcd_clock.lcd_clkm_div_b = div_b;
@@ -42,10 +46,15 @@ static inline void lcd_ll_set_group_clock_src(lcd_cam_dev_t *dev, lcd_clock_sour
case LCD_CLK_SRC_PLL160M: case LCD_CLK_SRC_PLL160M:
dev->lcd_clock.lcd_clk_sel = 3; dev->lcd_clock.lcd_clk_sel = 3;
break; break;
case LCD_CLK_SRC_PLL240M:
dev->lcd_clock.lcd_clk_sel = 2;
break;
case LCD_CLK_SRC_XTAL: case LCD_CLK_SRC_XTAL:
dev->lcd_clock.lcd_clk_sel = 1; dev->lcd_clock.lcd_clk_sel = 1;
break; break;
default: default:
// disble LCD clock source
dev->lcd_clock.lcd_clk_sel = 0;
HAL_ASSERT(false && "unsupported clock source"); HAL_ASSERT(false && "unsupported clock source");
break; break;
} }
@@ -60,7 +69,6 @@ static inline void lcd_ll_set_clock_idle_level(lcd_cam_dev_t *dev, bool level)
__attribute__((always_inline)) __attribute__((always_inline))
static inline void lcd_ll_set_pixel_clock_edge(lcd_cam_dev_t *dev, bool active_on_neg) static inline void lcd_ll_set_pixel_clock_edge(lcd_cam_dev_t *dev, bool active_on_neg)
{ {
dev->lcd_clock.lcd_clk_equ_sysclk = 0; // if we want to pixel_clk == lcd_clk, just make clkcnt = 0
dev->lcd_clock.lcd_ck_out_edge = active_on_neg; dev->lcd_clock.lcd_ck_out_edge = active_on_neg;
} }
@@ -68,7 +76,15 @@ __attribute__((always_inline))
static inline void lcd_ll_set_pixel_clock_prescale(lcd_cam_dev_t *dev, uint32_t prescale) static inline void lcd_ll_set_pixel_clock_prescale(lcd_cam_dev_t *dev, uint32_t prescale)
{ {
// Formula: pixel_clk = lcd_clk / (1 + clkcnt_n) // Formula: pixel_clk = lcd_clk / (1 + clkcnt_n)
dev->lcd_clock.lcd_clkcnt_n = prescale - 1; // clkcnt_n can't be zero
uint32_t scale = 1;
if (prescale == 1) {
dev->lcd_clock.lcd_clk_equ_sysclk = 1;
} else {
dev->lcd_clock.lcd_clk_equ_sysclk = 0;
scale = prescale - 1;
}
dev->lcd_clock.lcd_clkcnt_n = scale;
} }
static inline void lcd_ll_enable_rgb_yuv_convert(lcd_cam_dev_t *dev, bool en) static inline void lcd_ll_enable_rgb_yuv_convert(lcd_cam_dev_t *dev, bool en)
@@ -97,14 +113,10 @@ static inline void lcd_ll_set_blank_cycles(lcd_cam_dev_t *dev, uint32_t fk_cycle
static inline void lcd_ll_set_data_width(lcd_cam_dev_t *dev, uint32_t width) static inline void lcd_ll_set_data_width(lcd_cam_dev_t *dev, uint32_t width)
{ {
HAL_ASSERT(width == 8 || width == 16);
dev->lcd_user.lcd_2byte_en = (width == 16); dev->lcd_user.lcd_2byte_en = (width == 16);
} }
static inline uint32_t lcd_ll_get_data_width(lcd_cam_dev_t *dev)
{
return dev->lcd_user.lcd_2byte_en ? 16 : 8;
}
static inline void lcd_ll_enable_output_always_on(lcd_cam_dev_t *dev, bool en) static inline void lcd_ll_enable_output_always_on(lcd_cam_dev_t *dev, bool en)
{ {
dev->lcd_user.lcd_always_out_en = en; dev->lcd_user.lcd_always_out_en = en;
@@ -117,6 +129,7 @@ static inline void lcd_ll_start(lcd_cam_dev_t *dev)
dev->lcd_user.lcd_start = 1; dev->lcd_user.lcd_start = 1;
} }
__attribute__((always_inline))
static inline void lcd_ll_stop(lcd_cam_dev_t *dev) static inline void lcd_ll_stop(lcd_cam_dev_t *dev)
{ {
dev->lcd_user.lcd_start = 0; dev->lcd_user.lcd_start = 0;
@@ -125,33 +138,35 @@ static inline void lcd_ll_stop(lcd_cam_dev_t *dev)
static inline void lcd_ll_reset(lcd_cam_dev_t *dev) static inline void lcd_ll_reset(lcd_cam_dev_t *dev)
{ {
dev->lcd_user.lcd_reset = 1; dev->lcd_user.lcd_reset = 1; // self clear
dev->lcd_user.lcd_reset = 0;
} }
__attribute__((always_inline)) __attribute__((always_inline))
static inline void lcd_ll_reverse_data_bit_order(lcd_cam_dev_t *dev, bool en) static inline void lcd_ll_reverse_bit_order(lcd_cam_dev_t *dev, bool en)
{ {
// whether to change LCD_DATA_out[N:0] to LCD_DATA_out[0:N] // whether to change LCD_DATA_out[N:0] to LCD_DATA_out[0:N]
dev->lcd_user.lcd_bit_order = en; dev->lcd_user.lcd_bit_order = en;
} }
__attribute__((always_inline)) __attribute__((always_inline))
static inline void lcd_ll_reverse_data_byte_order(lcd_cam_dev_t *dev, bool en) static inline void lcd_ll_swap_byte_order(lcd_cam_dev_t *dev, uint32_t width, bool en)
{ {
HAL_ASSERT(width == 8 || width == 16);
if (width == 8) {
// {B0}{B1}{B2}{B3} => {B1}{B0}{B3}{B2}
dev->lcd_user.lcd_8bits_order = en;
dev->lcd_user.lcd_byte_order = 0;
} else if (width == 16) {
// {B1,B0},{B3,B2} => {B0,B1}{B2,B3}
dev->lcd_user.lcd_byte_order = en; dev->lcd_user.lcd_byte_order = en;
dev->lcd_user.lcd_8bits_order = 0;
}
} }
__attribute__((always_inline)) __attribute__((always_inline))
static inline void lcd_ll_reverse_data_8bits_order(lcd_cam_dev_t *dev, bool en)
{
dev->lcd_user.lcd_8bits_order = en;
}
static inline void lcd_ll_fifo_reset(lcd_cam_dev_t *dev) static inline void lcd_ll_fifo_reset(lcd_cam_dev_t *dev)
{ {
dev->lcd_misc.lcd_afifo_reset = 1; dev->lcd_misc.lcd_afifo_reset = 1; // self clear
dev->lcd_misc.lcd_afifo_reset = 0;
} }
__attribute__((always_inline)) __attribute__((always_inline))
@@ -171,6 +186,7 @@ static inline void lcd_ll_set_dc_delay_ticks(lcd_cam_dev_t *dev, uint32_t delay)
__attribute__((always_inline)) __attribute__((always_inline))
static inline void lcd_ll_set_command(lcd_cam_dev_t *dev, uint32_t data_width, uint32_t command) static inline void lcd_ll_set_command(lcd_cam_dev_t *dev, uint32_t data_width, uint32_t command)
{ {
HAL_ASSERT(data_width == 8 || data_width == 16);
// if command phase has two cycles, in the first cycle, command[15:0] is sent out via lcd_data_out[15:0] // if command phase has two cycles, in the first cycle, command[15:0] is sent out via lcd_data_out[15:0]
// in the second cycle, command[31:16] is sent out via lcd_data_out[15:0] // in the second cycle, command[31:16] is sent out via lcd_data_out[15:0]
if (data_width == 8) { if (data_width == 8) {

View File

@@ -1,5 +1,5 @@
/* /*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* *
* SPDX-License-Identifier: Apache-2.0 * SPDX-License-Identifier: Apache-2.0
*/ */
@@ -19,6 +19,8 @@ extern "C" {
* +=====================+=========================+============================+ * +=====================+=========================+============================+
* | LCD_CLK_SRC_PLL160M | High resolution | ESP_PM_APB_FREQ_MAX lock | * | LCD_CLK_SRC_PLL160M | High resolution | ESP_PM_APB_FREQ_MAX lock |
* +---------------------+-------------------------+----------------------------+ * +---------------------+-------------------------+----------------------------+
* | LCD_CLK_SRC_PLL240M | High resolution | ESP_PM_APB_FREQ_MAX lock |
* +---------------------+-------------------------+----------------------------+
* | LCD_CLK_SRC_APLL | Configurable resolution | ESP_PM_NO_LIGHT_SLEEP lock | * | LCD_CLK_SRC_APLL | Configurable resolution | ESP_PM_NO_LIGHT_SLEEP lock |
* +---------------------+-------------------------+----------------------------+ * +---------------------+-------------------------+----------------------------+
* | LCD_CLK_SRC_XTAL | Medium resolution | No PM lock | * | LCD_CLK_SRC_XTAL | Medium resolution | No PM lock |
@@ -27,6 +29,7 @@ extern "C" {
*/ */
typedef enum { typedef enum {
LCD_CLK_SRC_PLL160M, /*!< Select PLL160M as the source clock */ LCD_CLK_SRC_PLL160M, /*!< Select PLL160M as the source clock */
LCD_CLK_SRC_PLL240M, /*!< Select PLL240M as the source clock */
LCD_CLK_SRC_APLL, /*!< Select APLL as the source clock */ LCD_CLK_SRC_APLL, /*!< Select APLL as the source clock */
LCD_CLK_SRC_XTAL, /*!< Select XTAL as the source clock */ LCD_CLK_SRC_XTAL, /*!< Select XTAL as the source clock */
} lcd_clock_source_t; } lcd_clock_source_t;