Merge branch 'bugfix/esp_lcd_improvements_v4.4' into 'release/v4.4'

bunch of esp lcd improvements (v4.4)

See merge request espressif/esp-idf!17555
This commit is contained in:
morris
2022-04-19 22:39:10 +08:00
27 changed files with 459 additions and 734 deletions

View File

@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -26,19 +26,17 @@
static const char *TAG = "gdma";
#if CONFIG_GDMA_ISR_IRAM_SAFE
#define GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
#if CONFIG_GDMA_ISR_IRAM_SAFE || CONFIG_GDMA_CTRL_FUNC_IN_IRAM
#define GDMA_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)
#else
#define GDMA_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
#define GDMA_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT
#endif // CONFIG_GDMA_ISR_IRAM_SAFE
#endif
#if CONFIG_GDMA_CTRL_FUNC_IN_IRAM
#define GDMA_CTRL_FUNC_ATTR IRAM_ATTR
#if CONFIG_GDMA_ISR_IRAM_SAFE
#define GDMA_INTR_ALLOC_FLAGS (ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_INTRDISABLED)
#else
#define GDMA_CTRL_FUNC_ATTR
#endif // CONFIG_GDMA_CTRL_FUNC_IN_IRAM
#define GDMA_INTR_ALLOC_FLAGS ESP_INTR_FLAG_INTRDISABLED
#endif
#define GDMA_INVALID_PERIPH_TRIG (0x3F)
#define SEARCH_REQUEST_RX_CHANNEL (1 << 0)
@@ -109,11 +107,9 @@ struct gdma_rx_channel_t {
};
static gdma_group_t *gdma_acquire_group_handle(int group_id);
static void gdma_release_group_handle(gdma_group_t *group);
static gdma_pair_t *gdma_acquire_pair_handle(gdma_group_t *group, int pair_id);
static void gdma_release_group_handle(gdma_group_t *group);
static void gdma_release_pair_handle(gdma_pair_t *pair);
static void gdma_uninstall_group(gdma_group_t *group);
static void gdma_uninstall_pair(gdma_pair_t *pair);
static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel);
static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel);
static esp_err_t gdma_install_rx_interrupt(gdma_rx_channel_t *rx_chan);
@@ -161,27 +157,28 @@ esp_err_t gdma_new_channel(const gdma_channel_alloc_config_t *config, gdma_chann
for (int i = 0; i < SOC_GDMA_GROUPS && search_code; i++) { // loop to search group
group = gdma_acquire_group_handle(i);
for (int j = 0; j < SOC_GDMA_PAIRS_PER_GROUP && search_code && group; j++) { // loop to search pair
ESP_GOTO_ON_FALSE(group, ESP_ERR_NO_MEM, err, TAG, "no mem for group(%d)", i);
for (int j = 0; j < SOC_GDMA_PAIRS_PER_GROUP && search_code; j++) { // loop to search pair
pair = gdma_acquire_pair_handle(group, j);
if (pair) {
portENTER_CRITICAL(&pair->spinlock);
if (!(search_code & pair->occupy_code)) { // pair has suitable position for acquired channel(s)
pair->occupy_code |= search_code;
search_code = 0; // exit search loop
}
portEXIT_CRITICAL(&pair->spinlock);
if (!search_code) {
portENTER_CRITICAL(&group->spinlock);
group->pair_ref_counts[j]++; // channel obtains a reference to pair
portEXIT_CRITICAL(&group->spinlock);
}
ESP_GOTO_ON_FALSE(pair, ESP_ERR_NO_MEM, err, TAG, "no mem for pair(%d,%d)", i, j);
portENTER_CRITICAL(&pair->spinlock);
if (!(search_code & pair->occupy_code)) { // pair has suitable position for acquired channel(s)
pair->occupy_code |= search_code;
search_code = 0; // exit search loop
}
portEXIT_CRITICAL(&pair->spinlock);
if (search_code) {
gdma_release_pair_handle(pair);
pair = NULL;
}
gdma_release_pair_handle(pair);
} // loop used to search pair
gdma_release_group_handle(group);
if (search_code) {
gdma_release_group_handle(group);
group = NULL;
}
} // loop used to search group
ESP_GOTO_ON_FALSE(search_code == 0, ESP_ERR_NOT_FOUND, err, TAG, "no free gdma channel, search code=%d", search_code);
assert(pair && group); // pair and group handle shouldn't be NULL
search_done:
// register TX channel
if (alloc_tx_channel) {
@@ -214,6 +211,12 @@ err:
if (alloc_rx_channel) {
free(alloc_rx_channel);
}
if (pair) {
gdma_release_pair_handle(pair);
}
if (group) {
gdma_release_group_handle(group);
}
return ret;
}
@@ -386,9 +389,7 @@ esp_err_t gdma_register_tx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
ESP_GOTO_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_eof), ESP_ERR_INVALID_ARG, err, TAG, "on_trans_eof not in IRAM");
}
if (user_data) {
ESP_GOTO_ON_FALSE(esp_ptr_in_dram(user_data) ||
esp_ptr_in_diram_dram(user_data) ||
esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in DRAM");
ESP_GOTO_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM");
}
#endif // CONFIG_GDMA_ISR_IRAM_SAFE
@@ -424,9 +425,7 @@ esp_err_t gdma_register_rx_event_callbacks(gdma_channel_handle_t dma_chan, gdma_
ESP_GOTO_ON_FALSE(esp_ptr_in_iram(cbs->on_recv_eof), ESP_ERR_INVALID_ARG, err, TAG, "on_recv_eof not in IRAM");
}
if (user_data) {
ESP_GOTO_ON_FALSE(esp_ptr_in_dram(user_data) ||
esp_ptr_in_diram_dram(user_data) ||
esp_ptr_in_rtc_dram_fast(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in DRAM");
ESP_GOTO_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, err, TAG, "user context not in internal RAM");
}
#endif // CONFIG_GDMA_ISR_IRAM_SAFE
@@ -447,7 +446,7 @@ err:
return ret;
}
GDMA_CTRL_FUNC_ATTR esp_err_t gdma_start(gdma_channel_handle_t dma_chan, intptr_t desc_base_addr)
esp_err_t gdma_start(gdma_channel_handle_t dma_chan, intptr_t desc_base_addr)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
@@ -468,7 +467,7 @@ err:
return ret;
}
GDMA_CTRL_FUNC_ATTR esp_err_t gdma_stop(gdma_channel_handle_t dma_chan)
esp_err_t gdma_stop(gdma_channel_handle_t dma_chan)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
@@ -487,7 +486,7 @@ err:
return ret;
}
GDMA_CTRL_FUNC_ATTR esp_err_t gdma_append(gdma_channel_handle_t dma_chan)
esp_err_t gdma_append(gdma_channel_handle_t dma_chan)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
@@ -506,7 +505,7 @@ err:
return ret;
}
GDMA_CTRL_FUNC_ATTR esp_err_t gdma_reset(gdma_channel_handle_t dma_chan)
esp_err_t gdma_reset(gdma_channel_handle_t dma_chan)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
@@ -525,7 +524,7 @@ err:
return ret;
}
static void gdma_uninstall_group(gdma_group_t *group)
static void gdma_release_group_handle(gdma_group_t *group)
{
int group_id = group->group_id;
bool do_deinitialize = false;
@@ -581,14 +580,7 @@ out:
return group;
}
static void gdma_release_group_handle(gdma_group_t *group)
{
if (group) {
gdma_uninstall_group(group);
}
}
static void gdma_uninstall_pair(gdma_pair_t *pair)
static void gdma_release_pair_handle(gdma_pair_t *pair)
{
gdma_group_t *group = pair->group;
int pair_id = pair->pair_id;
@@ -606,8 +598,7 @@ static void gdma_uninstall_pair(gdma_pair_t *pair)
if (do_deinitialize) {
free(pair);
ESP_LOGD(TAG, "del pair (%d,%d)", group->group_id, pair_id);
gdma_uninstall_group(group);
gdma_release_group_handle(group);
}
}
@@ -646,17 +637,12 @@ out:
return pair;
}
static void gdma_release_pair_handle(gdma_pair_t *pair)
{
if (pair) {
gdma_uninstall_pair(pair);
}
}
static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel)
{
gdma_pair_t *pair = dma_channel->pair;
gdma_group_t *group = pair->group;
int pair_id = pair->pair_id;
int group_id = group->group_id;
gdma_tx_channel_t *tx_chan = __containerof(dma_channel, gdma_tx_channel_t, base);
portENTER_CRITICAL(&pair->spinlock);
pair->tx_chan = NULL;
@@ -666,15 +652,16 @@ static esp_err_t gdma_del_tx_channel(gdma_channel_t *dma_channel)
if (dma_channel->intr) {
esp_intr_free(dma_channel->intr);
portENTER_CRITICAL(&pair->spinlock);
gdma_ll_tx_enable_interrupt(group->hal.dev, pair->pair_id, UINT32_MAX, false); // disable all interupt events
gdma_ll_tx_clear_interrupt_status(group->hal.dev, pair->pair_id, UINT32_MAX); // clear all pending events
gdma_ll_tx_enable_interrupt(group->hal.dev, pair_id, UINT32_MAX, false); // disable all interupt events
gdma_ll_tx_clear_interrupt_status(group->hal.dev, pair_id, UINT32_MAX); // clear all pending events
portEXIT_CRITICAL(&pair->spinlock);
ESP_LOGD(TAG, "uninstall interrupt service for tx channel (%d,%d)", group->group_id, pair->pair_id);
ESP_LOGD(TAG, "uninstall interrupt service for tx channel (%d,%d)", group_id, pair_id);
}
ESP_LOGD(TAG, "del tx channel (%d,%d)", group->group_id, pair->pair_id);
free(tx_chan);
gdma_uninstall_pair(pair);
ESP_LOGD(TAG, "del tx channel (%d,%d)", group_id, pair_id);
// channel has a reference on pair, release it now
gdma_release_pair_handle(pair);
return ESP_OK;
}
@@ -682,6 +669,8 @@ static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel)
{
gdma_pair_t *pair = dma_channel->pair;
gdma_group_t *group = pair->group;
int pair_id = pair->pair_id;
int group_id = group->group_id;
gdma_rx_channel_t *rx_chan = __containerof(dma_channel, gdma_rx_channel_t, base);
portENTER_CRITICAL(&pair->spinlock);
pair->rx_chan = NULL;
@@ -691,15 +680,15 @@ static esp_err_t gdma_del_rx_channel(gdma_channel_t *dma_channel)
if (dma_channel->intr) {
esp_intr_free(dma_channel->intr);
portENTER_CRITICAL(&pair->spinlock);
gdma_ll_rx_enable_interrupt(group->hal.dev, pair->pair_id, UINT32_MAX, false); // disable all interupt events
gdma_ll_rx_clear_interrupt_status(group->hal.dev, pair->pair_id, UINT32_MAX); // clear all pending events
gdma_ll_rx_enable_interrupt(group->hal.dev, pair_id, UINT32_MAX, false); // disable all interupt events
gdma_ll_rx_clear_interrupt_status(group->hal.dev, pair_id, UINT32_MAX); // clear all pending events
portEXIT_CRITICAL(&pair->spinlock);
ESP_LOGD(TAG, "uninstall interrupt service for rx channel (%d,%d)", group->group_id, pair->pair_id);
ESP_LOGD(TAG, "uninstall interrupt service for rx channel (%d,%d)", group_id, pair_id);
}
ESP_LOGD(TAG, "del rx channel (%d,%d)", group->group_id, pair->pair_id);
free(rx_chan);
gdma_uninstall_pair(pair);
ESP_LOGD(TAG, "del rx channel (%d,%d)", group_id, pair_id);
gdma_release_pair_handle(pair);
return ESP_OK;
}

View File

@@ -1,4 +1,3 @@
[mapping:driver]
archive: libdriver.a
entries:
@@ -7,3 +6,8 @@ entries:
# placed in flash.
if TWAI_ISR_IN_IRAM = y && (TWAI_ERRATA_FIX_RX_FRAME_INVALID = y || TWAI_ERRATA_FIX_RX_FIFO_CORRUPT = y):
periph_ctrl: periph_module_reset (noflash)
if GDMA_CTRL_FUNC_IN_IRAM = y:
gdma: gdma_start (noflash)
gdma: gdma_stop (noflash)
gdma: gdma_append (noflash)
gdma: gdma_reset (noflash)

View File

@@ -14,4 +14,5 @@ set(priv_requires "driver")
idf_component_register(SRCS ${srcs}
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
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.
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

View File

@@ -151,6 +151,8 @@ typedef struct {
int data_gpio_nums[SOC_LCD_I80_BUS_WIDTH]; /*!< GPIOs used for data lines */
size_t bus_width; /*!< Number of data lines, 8 or 16 */
size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */
size_t psram_trans_align; /*!< DMA transfer alignment for data allocated from PSRAM */
size_t sram_trans_align; /*!< DMA transfer alignment for data allocated from SRAM */
} esp_lcd_i80_bus_config_t;
/**

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 vsync_pulse_width; /*!< Vertical sync width, unit: number of lines */
unsigned int vsync_back_porch; /*!< Vertical back porch, number of invalid lines between vsync and start of frame */
unsigned int vsync_front_porch; /*!< Vertical front porch, number of invalid lines between 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 {
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 */

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
*/
#pragma once
#include <stddef.h>
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "hal/dma_types.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#if SOC_LCDCAM_SUPPORTED
#include "hal/lcd_hal.h"
#endif
@@ -16,6 +19,9 @@
extern "C" {
#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
#if SOC_LCDCAM_SUPPORTED

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
*/
@@ -54,7 +54,7 @@ static esp_err_t i2s_lcd_init_dma_link(esp_lcd_i80_bus_handle_t bus);
static esp_err_t i2s_lcd_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
static void i2s_lcd_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus);
static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device);
static IRAM_ATTR void lcd_default_isr_handler(void *args);
static void lcd_default_isr_handler(void *args);
struct esp_lcd_i80_bus_t {
int bus_id; // Bus ID, index from 0
@@ -143,9 +143,16 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
#endif // SOC_I2S_TRANS_SIZE_ALIGN_WORD
ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer");
// I2S0 has the LCD mode, but the LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform
ESP_GOTO_ON_ERROR(i2s_priv_register_object(bus, 0), err, TAG, "register to I2S platform failed");
bus->bus_id = 0;
// LCD mode can't work with other modes at the same time, we need to register the driver object to the I2S platform
int bus_id = -1;
for (int i = 0; i < SOC_LCD_I80_BUSES; i++) {
if (i2s_priv_register_object(bus, i) == ESP_OK) {
bus_id = i;
break;
}
}
ESP_GOTO_ON_FALSE(bus_id != -1, ESP_ERR_NOT_FOUND, err, TAG, "no free i80 bus slot");
bus->bus_id = bus_id;
// initialize HAL layer
i2s_hal_init(&bus->hal, bus->bus_id);
// set peripheral clock resolution
@@ -157,7 +164,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
i2s_ll_tx_reset_fifo(bus->hal.dev);
// install interrupt service, (I2S LCD mode only uses the "TX Unit", which leaves "RX Unit" for other purpose)
// So the interrupt should also be able to share with other functionality
int isr_flags = ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED;
int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus->bus_id].irq_id, isr_flags,
(uint32_t)i2s_ll_get_intr_status_reg(bus->hal.dev),
I2S_LL_EVENT_TX_EOF, lcd_default_isr_handler, bus, &bus->intr);
@@ -251,7 +258,7 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
uint32_t pclk_prescale = bus->resolution_hz / 2 / io_config->pclk_hz;
ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= I2S_LL_BCK_MAX_PRESCALE, ESP_ERR_NOT_SUPPORTED, err, TAG,
"prescaler can't satisfy PCLK clock %u", io_config->pclk_hz);
i80_device = calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t));
i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io");
// create two queues for i80 device
i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
@@ -310,9 +317,12 @@ static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io)
lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
esp_lcd_i80_bus_t *bus = i80_device->bus;
lcd_i80_trans_descriptor_t *trans_desc = NULL;
size_t num_trans_inflight = i80_device->num_trans_inflight;
// wait all pending transaction to finish
for (size_t i = 0; i < i80_device->num_trans_inflight; i++) {
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
i80_device->num_trans_inflight--;
}
// remove from device list
portENTER_CRITICAL(&bus->spinlock);
@@ -453,12 +463,13 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
lcd_i80_trans_descriptor_t *trans_desc = NULL;
assert(param_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "parameter bytes too long, enlarge max_transfer_bytes");
assert(param_size <= CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE && "format buffer too small, increase CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE");
size_t num_trans_inflight = next_device->num_trans_inflight;
// before issue a polling transaction, need to wait queued transactions finished
for (size_t i = 0; i < next_device->num_trans_inflight; i++) {
xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY);
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
next_device->num_trans_inflight--;
}
next_device->num_trans_inflight = 0;
i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
// switch devices if necessary
@@ -517,12 +528,13 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
lcd_panel_io_i80_t *cur_device = bus->cur_device;
lcd_i80_trans_descriptor_t *trans_desc = NULL;
assert(color_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "color bytes too long, enlarge max_transfer_bytes");
size_t num_trans_inflight = next_device->num_trans_inflight;
// before issue a polling transaction, need to wait queued transactions finished
for (size_t i = 0; i < next_device->num_trans_inflight; i++) {
xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY);
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE(xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
next_device->num_trans_inflight--;
}
next_device->num_trans_inflight = 0;
i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
// switch devices if necessary
@@ -717,6 +729,8 @@ static IRAM_ATTR void lcd_default_isr_handler(void *args)
if (high_task_woken == pdTRUE) {
need_yield = true;
}
// sanity check
assert(trans_desc);
// only clear the interrupt status when we're sure there still remains transaction to handle
i2s_ll_clear_intr_status(bus->hal.dev, I2S_LL_EVENT_TX_EOF);
// switch devices if necessary

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
*/
@@ -16,13 +16,13 @@
#include "freertos/queue.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h"
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_rom_gpio.h"
#include "soc/soc_caps.h"
#include "soc/rtc.h" // for `rtc_clk_xtal_freq_get()`
#include "soc/soc_memory_types.h"
#include "hal/dma_types.h"
#include "hal/gpio_hal.h"
#include "esp_private/gdma.h"
@@ -40,6 +40,9 @@ typedef struct esp_lcd_i80_bus_t esp_lcd_i80_bus_t;
typedef struct lcd_panel_io_i80_t lcd_panel_io_i80_t;
typedef struct lcd_i80_trans_descriptor_t lcd_i80_trans_descriptor_t;
// This function is located in ROM (also see esp_rom/${target}/ld/${target}.rom.ld)
extern int Cache_WriteBack_Addr(uint32_t addr, uint32_t size);
static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io);
@@ -49,7 +52,7 @@ static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_c
static esp_err_t lcd_i80_bus_configure_gpio(esp_lcd_i80_bus_handle_t bus, const esp_lcd_i80_bus_config_t *bus_config);
static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device);
static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc);
static IRAM_ATTR void lcd_default_isr_handler(void *args);
static void lcd_default_isr_handler(void *args);
struct esp_lcd_i80_bus_t {
int bus_id; // Bus ID, index from 0
@@ -62,6 +65,8 @@ struct esp_lcd_i80_bus_t {
uint8_t *format_buffer; // The driver allocates an internal buffer for DMA to do data format transformer
size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source
gdma_channel_handle_t dma_chan; // DMA channel handle
size_t psram_trans_align; // DMA transfer alignment for data allocated from PSRAM
size_t sram_trans_align; // DMA transfer alignment for data allocated from SRAM
lcd_i80_trans_descriptor_t *cur_trans; // Current transaction
lcd_panel_io_i80_t *cur_device; // Current working device
LIST_HEAD(i80_device_list, lcd_panel_io_i80_t) device_list; // Head of i80 device list
@@ -119,11 +124,11 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
size_t num_dma_nodes = bus_config->max_transfer_bytes / DMA_DESCRIPTOR_BUFFER_MAX_SIZE + 1;
// DMA descriptors must be placed in internal SRAM
bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_DMA);
bus = heap_caps_calloc(1, sizeof(esp_lcd_i80_bus_t) + num_dma_nodes * sizeof(dma_descriptor_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 bus");
bus->num_dma_nodes = num_dma_nodes;
bus->bus_id = -1;
bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_DMA);
bus->format_buffer = heap_caps_calloc(1, CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE, MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
ESP_GOTO_ON_FALSE(bus->format_buffer, ESP_ERR_NO_MEM, err, TAG, "no mem for format buffer");
// register to platform
int bus_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_I80, bus);
@@ -142,7 +147,7 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
ESP_GOTO_ON_ERROR(ret, err, TAG, "select periph clock %d failed", bus_config->clk_src);
// install interrupt service, (LCD peripheral shares the same interrupt source with Camera peripheral with different mask)
// interrupt is disabled by default
int isr_flags = ESP_INTR_FLAG_INTRDISABLED | ESP_INTR_FLAG_SHARED;
int isr_flags = LCD_I80_INTR_ALLOC_FLAGS | ESP_INTR_FLAG_SHARED;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus_id].irq_id, isr_flags,
(uint32_t)lcd_ll_get_interrupt_status_reg(bus->hal.dev),
LCD_LL_EVENT_TRANS_DONE, lcd_default_isr_handler, bus, &bus->intr);
@@ -150,12 +155,14 @@ esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lc
lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, false); // disable all interrupts
lcd_ll_clear_interrupt_status(bus->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
bus->psram_trans_align = bus_config->psram_trans_align;
bus->sram_trans_align = bus_config->sram_trans_align;
ret = lcd_i80_init_dma_link(bus);
ESP_GOTO_ON_ERROR(ret, err, TAG, "install DMA failed");
// enable 8080 mode and set bus width
lcd_ll_enable_rgb_mode(bus->hal.dev, false);
lcd_ll_set_data_width(bus->hal.dev, bus_config->bus_width);
bus->bus_width = lcd_ll_get_data_width(bus->hal.dev);
bus->bus_width = bus_config->bus_width;
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(bus->hal.dev, true);
// enable trans done interrupt
@@ -237,7 +244,7 @@ esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_p
uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz;
ESP_GOTO_ON_FALSE(pclk_prescale > 0 && pclk_prescale <= LCD_LL_CLOCK_PRESCALE_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG,
"prescaler can't satisfy PCLK clock %u", io_config->pclk_hz);
i80_device = calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t));
i80_device = heap_caps_calloc(1, sizeof(lcd_panel_io_i80_t) + io_config->trans_queue_depth * sizeof(lcd_i80_trans_descriptor_t), LCD_I80_MEM_ALLOC_CAPS);
ESP_GOTO_ON_FALSE(i80_device, ESP_ERR_NO_MEM, err, TAG, "no mem for i80 panel io");
// create two queues for i80 device
i80_device->trans_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
@@ -302,8 +309,11 @@ static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io)
esp_lcd_i80_bus_t *bus = i80_device->bus;
lcd_i80_trans_descriptor_t *trans_desc = NULL;
// wait all pending transaction to finish
for (size_t i = 0; i < i80_device->num_trans_inflight; i++) {
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
size_t num_trans_inflight = i80_device->num_trans_inflight;
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
i80_device->num_trans_inflight--;
}
// remove from device list
portENTER_CRITICAL(&bus->spinlock);
@@ -370,19 +380,20 @@ static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, cons
i80_lcd_prepare_cmd_buffer(bus, next_device, &lcd_cmd);
uint32_t param_len = i80_lcd_prepare_param_buffer(bus, next_device, param, param_size);
// wait all pending transaction in the queue to finish
for (size_t i = 0; i < next_device->num_trans_inflight; i++) {
xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY);
size_t num_trans_inflight = next_device->num_trans_inflight;
for (size_t i = 0; i < num_trans_inflight; i++) {
ESP_RETURN_ON_FALSE( xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
next_device->num_trans_inflight--;
}
next_device->num_trans_inflight = 0;
uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev);
lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status);
// switch devices if necessary
lcd_i80_switch_devices(cur_device, next_device);
// set data format
lcd_ll_reverse_data_byte_order(bus->hal.dev, false);
lcd_ll_reverse_data_bit_order(bus->hal.dev, false);
lcd_ll_reverse_data_8bits_order(bus->hal.dev, next_device->lcd_param_bits > bus->bus_width);
lcd_ll_reverse_bit_order(bus->hal.dev, false);
lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->lcd_param_bits > bus->bus_width);
bus->cur_trans = NULL;
bus->cur_device = next_device;
// package a transaction
@@ -425,7 +436,8 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
trans_desc = &i80_device->trans_pool[i80_device->num_trans_inflight];
} else {
// transaction pool has used up, recycle one from done_queue
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
ESP_RETURN_ON_FALSE(xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY) == pdTRUE,
ESP_FAIL, TAG, "recycle inflight transactions failed");
i80_device->num_trans_inflight--;
}
trans_desc->i80_device = i80_device;
@@ -435,6 +447,12 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
trans_desc->data_length = color_size;
trans_desc->trans_done_cb = i80_device->on_color_trans_done;
trans_desc->user_ctx = i80_device->user_ctx;
if (esp_ptr_external_ram(color)) {
// flush framebuffer from cache to the physical PSRAM
Cache_WriteBack_Addr((uint32_t)color, color_size);
}
// send transaction to trans_queue
xQueueSend(i80_device->trans_queue, &trans_desc, portMAX_DELAY);
i80_device->num_trans_inflight++;
@@ -447,7 +465,8 @@ static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
static esp_err_t lcd_i80_select_periph_clock(esp_lcd_i80_bus_handle_t bus, lcd_clock_source_t clk_src)
{
esp_err_t ret = ESP_OK;
lcd_ll_set_group_clock_src(bus->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(bus->hal.dev, clk_src, LCD_PERIPH_CLOCK_PRE_SCALE, 0, 0);
switch (clk_src) {
case LCD_CLK_SRC_PLL160M:
bus->resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
@@ -488,6 +507,12 @@ static esp_err_t lcd_i80_init_dma_link(esp_lcd_i80_bus_handle_t bus)
.owner_check = true
};
gdma_apply_strategy(bus->dma_chan, &strategy_config);
// set DMA transfer ability
gdma_transfer_ability_t ability = {
.psram_trans_align = bus->psram_trans_align,
.sram_trans_align = bus->sram_trans_align,
};
gdma_set_transfer_ability(bus->dma_chan, &ability);
return ESP_OK;
err:
if (bus->dma_chan) {
@@ -540,6 +565,9 @@ static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descript
lcd_ll_set_command(bus->hal.dev, bus->bus_width, trans_desc->cmd_value);
if (trans_desc->data) { // some specific LCD commands can have no parameters
gdma_start(bus->dma_chan, (intptr_t)(bus->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);
}
lcd_ll_start(bus->hal.dev);
}
@@ -612,14 +640,15 @@ IRAM_ATTR static void lcd_default_isr_handler(void *args)
if (high_task_woken == pdTRUE) {
need_yield = true;
}
// sanity check
assert(trans_desc);
// only clear the interrupt status when we're sure there still remains transaction to handle
lcd_ll_clear_interrupt_status(bus->hal.dev, intr_status);
// switch devices if necessary
lcd_i80_switch_devices(cur_device, next_device);
// only reverse data bit/bytes for color data
lcd_ll_reverse_data_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
lcd_ll_reverse_data_byte_order(bus->hal.dev, next_device->flags.swap_color_bytes);
lcd_ll_reverse_data_8bits_order(bus->hal.dev, false);
lcd_ll_reverse_bit_order(bus->hal.dev, next_device->flags.reverse_color_bits);
lcd_ll_swap_byte_order(bus->hal.dev, bus->bus_width, next_device->flags.swap_color_bytes);
bus->cur_trans = trans_desc;
bus->cur_device = next_device;
// mount data to DMA links

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
*/
@@ -11,12 +11,15 @@
#include <sys/cdefs.h>
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "hal/spi_ll.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_lcd_common.h"
#define LCD_SPI_MAX_DATA_SIZE (SPI_LL_DATA_MAX_BIT_LEN / 8)
static const char *TAG = "lcd_panel.io.spi";
static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
@@ -29,7 +32,7 @@ typedef struct {
spi_transaction_t base;
struct {
unsigned int dc_gpio_level: 1;
unsigned int trans_is_color: 1;
unsigned int en_trans_done_cb: 1;
} flags;
} lcd_spi_trans_descriptor_t;
@@ -239,18 +242,53 @@ static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, cons
ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) command failed");
// sending LCD color data
lcd_trans->flags.trans_is_color = 1;
lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode
lcd_trans->base.length = color_size * 8; // transaction length is in bits
lcd_trans->base.tx_buffer = color;
if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary
lcd_trans->base.cmd = spi_panel_io->flags.dc_data_level;
}
// color data is usually large, using queue+blocking mode
ret = spi_device_queue_trans(spi_panel_io->spi_dev, &lcd_trans->base, portMAX_DELAY);
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (queue) color failed");
spi_panel_io->num_trans_inflight++;
// split to chunks if required:
// the SPI bus has a maximum transaction size determined by SPI_USR_MOSI_DBITLEN's bit width
do {
size_t chunk_size = color_size;
if (spi_panel_io->num_trans_inflight < spi_panel_io->queue_size) {
// get the next available transaction
lcd_trans = &spi_panel_io->trans_pool[spi_panel_io->num_trans_inflight];
} else {
// transaction pool has used up, recycle one transaction
ret = spi_device_get_trans_result(spi_panel_io->spi_dev, &spi_trans, portMAX_DELAY);
ESP_GOTO_ON_ERROR(ret, err, TAG, "recycle spi transactions failed");
lcd_trans = __containerof(spi_trans, lcd_spi_trans_descriptor_t, base);
spi_panel_io->num_trans_inflight--;
}
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
// SPI per-transfer size has its limitation, if the color buffer is too big, we need to split it into multiple trunks
if (chunk_size > LCD_SPI_MAX_DATA_SIZE) {
// cap the transfer size to the maximum supported by the bus
chunk_size = LCD_SPI_MAX_DATA_SIZE;
} else {
// mark en_trans_done_cb only at the last round to avoid premature completion callback
lcd_trans->flags.en_trans_done_cb = 1;
}
lcd_trans->base.user = spi_panel_io;
lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode
lcd_trans->base.length = chunk_size * 8; // transaction length is in bits
lcd_trans->base.tx_buffer = color;
if (spi_panel_io->flags.dc_as_cmd_phase) { // encoding DC value to SPI command phase when necessary
lcd_trans->base.cmd = spi_panel_io->flags.dc_data_level;
}
if (spi_panel_io->flags.octal_mode) {
// use 8 lines for transmitting command, address and data
lcd_trans->base.flags |= (SPI_TRANS_MULTILINE_CMD | SPI_TRANS_MULTILINE_ADDR | SPI_TRANS_MODE_OCT);
}
// color data is usually large, using queue+blocking mode
ret = spi_device_queue_trans(spi_panel_io->spi_dev, &lcd_trans->base, portMAX_DELAY);
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (queue) color failed");
spi_panel_io->num_trans_inflight++;
// move on to the next chunk
color = (const uint8_t *)color + chunk_size;
color_size -= chunk_size;
} while (color_size > 0); // continue while we have remaining data to transmit
err:
return ret;
@@ -269,7 +307,7 @@ static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans)
{
esp_lcd_panel_io_spi_t *spi_panel_io = trans->user;
lcd_spi_trans_descriptor_t *lcd_trans = __containerof(trans, lcd_spi_trans_descriptor_t, base);
if (lcd_trans->flags.trans_is_color) {
if (lcd_trans->flags.en_trans_done_cb) {
if (spi_panel_io->on_color_trans_done) {
spi_panel_io->on_color_trans_done(&spi_panel_io->base, NULL, spi_panel_io->user_ctx);
}

View File

@@ -16,8 +16,6 @@
#include "freertos/semphr.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_pm.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_rgb.h"
@@ -39,6 +37,12 @@
#include "hal/lcd_hal.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";
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_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 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 {
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
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
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
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
@@ -87,7 +89,6 @@ struct esp_rgb_panel_t {
struct {
unsigned int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
unsigned int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
unsigned int 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
} flags;
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->data_width == 16, ESP_ERR_NOT_SUPPORTED, err, TAG,
"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
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;
@@ -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++;
}
// 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");
rgb_panel->num_dma_nodes = num_dma_nodes;
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;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module);
periph_module_reset(lcd_periph_signals.panels[panel_id].module);
// alloc frame buffer
bool alloc_from_psram = false;
// fb_in_psram is only an option, if there's no PSRAM on board, we still alloc from SRAM
@@ -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->fb_size = fb_size;
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
lcd_hal_init(&rgb_panel->hal, panel_id);
// set peripheral clock resolution
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");
// 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,
(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);
@@ -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;
// return base class
*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;
err:
@@ -196,9 +204,6 @@ err:
if (rgb_panel->fb) {
free(rgb_panel->fb);
}
if (rgb_panel->done_sem) {
vSemaphoreDelete(rgb_panel->done_sem);
}
if (rgb_panel->dma_chan) {
gdma_disconnect(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)
{
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;
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
esp_intr_free(rgb_panel->intr);
periph_module_disable(lcd_periph_signals.panels[panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
vSemaphoreDelete(rgb_panel->done_sem);
free(rgb_panel->fb);
if (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);
// generate the hsync at the very begining of line
lcd_ll_set_hsync_position(rgb_panel->hal.dev, 0);
// starting sending next frame automatically
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, rgb_panel->flags.stream_mode);
// restart flush by hardware has some limitation, instead, the driver will restart the flush in the VSYNC end interrupt by software
lcd_ll_enable_auto_next_frame(rgb_panel->hal.dev, false);
// trigger interrupt on the end of frame
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);
err:
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);
y_start = MIN(y_start, 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
int bytes_per_pixel = rgb_panel->data_width / 8;
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
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) {
// in one-off mode, the "new frame" flag is controlled by this API
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);
lcd_rgb_panel_start_transmission(rgb_panel);
}
// start LCD engine
lcd_ll_start(rgb_panel->hal.dev);
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)
{
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) {
case LCD_CLK_SRC_PLL160M:
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].next = &panel->dma_nodes[i + 1];
}
// fix the last DMA descriptor according to whether the LCD works in stream mode
if (panel->flags.stream_mode) {
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
}
// one-off DMA chain
panel->dma_nodes[panel->num_dma_nodes - 1].next = NULL;
// mount the frame buffer to the DMA descriptors
lcd_com_mount_dma_data(panel->dma_nodes, panel->fb, panel->fb_size);
// alloc DMA channel and connect to LCD peripheral
@@ -496,32 +494,37 @@ err:
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)
{
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;
BaseType_t high_task_woken = pdFALSE;
uint32_t intr_status = lcd_ll_get_interrupt_status(panel->hal.dev);
lcd_ll_clear_interrupt_status(panel->hal.dev, intr_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);
if (intr_status & LCD_LL_EVENT_VSYNC_END) {
if (panel->flags.new_frame) { // the finished one is a new frame
if (panel->on_frame_trans_done) {
if (panel->on_frame_trans_done(&panel->base, NULL, panel->user_ctx)) {
need_yield = true;
}
}
xSemaphoreGiveFromISR(panel->done_sem, &high_task_woken);
if (high_task_woken == pdTRUE) {
// call user registered callback
if (rgb_panel->on_frame_trans_done) {
if (rgb_panel->on_frame_trans_done(&rgb_panel->base, NULL, rgb_panel->user_ctx)) {
need_yield = true;
}
}
// 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;
// to restart the transmission
if (rgb_panel->flags.stream_mode) {
lcd_rgb_panel_start_transmission(rgb_panel);
}
}
if (need_yield) {

View File

@@ -71,59 +71,3 @@ TEST_CASE("lcd panel with i2c interface (ssd1306)", "[lcd]")
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(i2c_driver_delete(TEST_I2C_HOST_ID));
}
// The following test shows a porting example of LVGL GUI library
// To run the LVGL tests, you need to clone the LVGL library into components directory firstly
#if CONFIG_LV_USE_USER_DATA
#include "test_lvgl_port.h"
#if CONFIG_LV_COLOR_DEPTH_1
static bool notify_lvgl_ready_to_flush(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_t *disp = *(lv_disp_t **)user_ctx;
lv_disp_flush_ready(&disp->driver);
return false;
}
TEST_CASE("lvgl gui with i2c interface (ssd1306)", "[lcd][lvgl][ignore]")
{
// initialize LVGL graphics library
lv_disp_t *disp = NULL;
lv_init();
i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = TEST_I2C_SDA_GPIO,
.scl_io_num = TEST_I2C_SCL_GPIO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = TEST_LCD_PIXEL_CLOCK_HZ,
};
TEST_ESP_OK(i2c_param_config(TEST_I2C_HOST_ID, &conf));
TEST_ESP_OK(i2c_driver_install(TEST_I2C_HOST_ID, I2C_MODE_MASTER, 0, 0, 0));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i2c_config_t io_config = {
.dev_addr = TEST_I2C_DEV_ADDR,
.control_phase_bytes = 1, // According to SSD1306 datasheet
.dc_bit_offset = 6, // According to SSD1306 datasheet
.lcd_cmd_bits = 8, // According to SSD1306 datasheet
.lcd_param_bits = 8, // According to SSD1306 datasheet
.on_color_trans_done = notify_lvgl_ready_to_flush,
.user_ctx = &disp,
};
TEST_ESP_OK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TEST_I2C_HOST_ID, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.bits_per_pixel = 1,
.color_space = ESP_LCD_COLOR_SPACE_MONOCHROME,
.reset_gpio_num = -1,
};
TEST_ESP_OK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp);
}
#endif // CONFIG_LV_COLOR_DEPTH_1
#endif // CONFIG_LV_USE_USER_DATA

View File

@@ -408,157 +408,6 @@ TEST_CASE("lcd panel with i80 interface (st7789, 8bits)", "[lcd]")
#undef TEST_IMG_SIZE
}
// The following test shows a porting example of LVGL GUI library
// To run the LVGL tests, you need to clone the LVGL library into components directory firstly
#if CONFIG_LV_USE_USER_DATA
#include "test_lvgl_port.h"
static bool notify_lvgl_ready_to_flush(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_t *disp = *(lv_disp_t **)user_ctx;
lv_disp_flush_ready(&disp->driver);
return false;
}
TEST_CASE("lvgl gui with i80 interface (st7789, 8bits)", "[lcd][lvgl][ignore]")
{
// initialize LVGL graphics library
lv_disp_t *disp = NULL;
lv_init();
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << TEST_LCD_BK_LIGHT_GPIO
};
TEST_ESP_OK(gpio_config(&bk_gpio_config));
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.wr_gpio_num = TEST_LCD_PCLK_GPIO,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
TEST_LCD_DATA1_GPIO,
TEST_LCD_DATA2_GPIO,
TEST_LCD_DATA3_GPIO,
TEST_LCD_DATA4_GPIO,
TEST_LCD_DATA5_GPIO,
TEST_LCD_DATA6_GPIO,
TEST_LCD_DATA7_GPIO,
},
.bus_width = 8,
.max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t)
};
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = 10000000, // 10MHz
.trans_queue_depth = 10,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.flags = {
.swap_color_bytes = 1,
},
.on_color_trans_done = notify_lvgl_ready_to_flush,
.user_ctx = &disp,
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
// turn off backlight
gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0);
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_invert_color(panel_handle, true);
// the gap is LCD panel specific, even panels with the same driver IC, can have different gap value
esp_lcd_panel_set_gap(panel_handle, 0, 20);
// turn on backlight
gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1);
test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp);
}
#define TEST_NT35510_DATA_WIDTH (8) // change this to 16 when NT35510 is configured to 16bit in length
TEST_CASE("lvgl gui with i80 interface (nt35510, 8/16bits)", "[lcd][lvgl][ignore]")
{
// initialize LVGL graphics library
lv_disp_t *disp = NULL;
lv_init();
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.wr_gpio_num = TEST_LCD_PCLK_GPIO,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
TEST_LCD_DATA1_GPIO,
TEST_LCD_DATA2_GPIO,
TEST_LCD_DATA3_GPIO,
TEST_LCD_DATA4_GPIO,
TEST_LCD_DATA5_GPIO,
TEST_LCD_DATA6_GPIO,
TEST_LCD_DATA7_GPIO,
TEST_LCD_DATA8_GPIO,
TEST_LCD_DATA9_GPIO,
TEST_LCD_DATA10_GPIO,
TEST_LCD_DATA11_GPIO,
TEST_LCD_DATA12_GPIO,
TEST_LCD_DATA13_GPIO,
TEST_LCD_DATA14_GPIO,
TEST_LCD_DATA15_GPIO,
},
.bus_width = TEST_NT35510_DATA_WIDTH,
.max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t)
};
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = 10000000, // 10MHz
.trans_queue_depth = 4,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
.on_color_trans_done = notify_lvgl_ready_to_flush,
.user_ctx = &disp,
.lcd_cmd_bits = 16,
.lcd_param_bits = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = -1,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle));
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_swap_xy(panel_handle, true);
esp_lcd_panel_mirror(panel_handle, true, false);
test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp);
}
#endif // CONFIG_LV_USE_USER_DATA
#endif // SOC_LCD_I80_SUPPORTED
#if SOC_I2S_LCD_I80_VARIANT

View File

@@ -1,4 +0,0 @@
#include "esp_lcd_panel_ops.h"
#include "lvgl.h"
void test_lvgl_task_loop(esp_lcd_panel_handle_t panel_handle, int h_res, int v_res, lv_disp_t **disp);

View File

@@ -1,113 +0,0 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "test_utils.h"
#include "esp_freertos_hooks.h"
#include "soc/soc_caps.h"
#if CONFIG_LV_USE_USER_DATA
#include "test_lvgl_port.h"
static void my_lvgl_flush(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
{
esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t) drv->user_data;
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map);
}
#if CONFIG_LV_COLOR_DEPTH_1
static void my_lvgl_set_px_cb(lv_disp_drv_t *disp_drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y,
lv_color_t color, lv_opa_t opa)
{
uint16_t byte_index = x + (( y >> 3 ) * buf_w);
uint8_t bit_index = y & 0x7;
if ((color.full == 0) && (LV_OPA_TRANSP != opa)) {
buf[byte_index] |= (1 << bit_index);
} else {
buf[byte_index] &= ~(1 << bit_index);
}
}
static void my_lvgl_rounder(lv_disp_drv_t *disp_drv, lv_area_t *area)
{
area->y1 = (area->y1 & (~0x7));
area->y2 = (area->y2 & (~0x7)) + 7;
}
#endif
static void increase_lvgl_tick(void)
{
lv_tick_inc(portTICK_PERIOD_MS);
}
static void create_demo_application(lv_disp_t *disp)
{
// Get the current screen
lv_obj_t *scr = lv_disp_get_scr_act(disp);
// Create a Label on the currently active screen
lv_obj_t *label = lv_label_create(scr, NULL);
// Modify the Label's text
lv_label_set_text(label, "Hello World");
// Align the Label to the center
lv_obj_align(label, NULL, LV_ALIGN_IN_TOP_MID, 0, 0);
#if !CONFIG_LV_COLOR_DEPTH_1
// new screen_spinner
lv_obj_t *screen_spinner = lv_spinner_create(scr, NULL);
lv_obj_align(screen_spinner, label, LV_ALIGN_OUT_BOTTOM_MID, 15, 20);
lv_obj_set_size(screen_spinner, 100, 100);
lv_spinner_set_arc_length(screen_spinner, 60);
lv_spinner_set_spin_time(screen_spinner, 1000);
lv_spinner_set_type(screen_spinner, LV_SPINNER_TYPE_SPINNING_ARC);
lv_spinner_set_dir(screen_spinner, LV_SPINNER_DIR_FORWARD);
lv_obj_t *bar = lv_bar_create(scr, NULL);
lv_obj_set_size(bar, 100, 20);
lv_obj_align(bar, screen_spinner, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
lv_bar_set_anim_time(bar, 2000);
lv_bar_set_value(bar, 100, LV_ANIM_ON);
#endif
}
void test_lvgl_task_loop(esp_lcd_panel_handle_t panel_handle, int h_res, int v_res, lv_disp_t **disp)
{
static lv_disp_buf_t disp_buf;
// alloc frame buffer used by LVGL
lv_color_t *buf1 = heap_caps_malloc(h_res * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(buf1);
lv_color_t *buf2 = heap_caps_malloc(h_res * 20 * sizeof(lv_color_t), MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(buf2);
lv_disp_buf_init(&disp_buf, buf1, buf2, h_res * 20);
// register display driver
lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = h_res;
disp_drv.ver_res = v_res;
disp_drv.flush_cb = my_lvgl_flush;
#if CONFIG_LV_COLOR_DEPTH_1
disp_drv.rounder_cb = my_lvgl_rounder;
disp_drv.set_px_cb = my_lvgl_set_px_cb;
#endif
disp_drv.buffer = &disp_buf;
disp_drv.user_data = panel_handle; // LV_USE_USER_DATA is disabled by default, need to enable it in menuconfig
*disp = lv_disp_drv_register(&disp_drv);
// Tick interface for LVGL
esp_register_freertos_tick_hook(increase_lvgl_tick);
// create a demo UI on that screen
create_demo_application(*disp);
while (1) {
vTaskDelay(pdMS_TO_TICKS(10));
lv_task_handler(); // The task running lv_task_handler should have lower priority than that running `lv_tick_inc`
}
}
#endif // CONFIG_LV_USE_USER_DATA

View File

@@ -5,6 +5,7 @@
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "soc/soc_caps.h"
#include "esp_attr.h"
#define TEST_LCD_H_RES (480)
#define TEST_LCD_V_RES (272)
@@ -29,20 +30,23 @@
#define TEST_LCD_DATA13_GPIO (16) // R2
#define TEST_LCD_DATA14_GPIO (17) // R3
#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
// RGB driver consumes a huge memory to save frame buffer, only test it with PSRAM enabled
#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_rgb_panel_config_t panel_config = {
.data_width = 16,
.psram_trans_align = 64,
.clk_src = LCD_CLK_SRC_PLL160M,
.disp_gpio_num = TEST_LCD_DISP_EN_GPIO,
.pclk_gpio_num = TEST_LCD_PCLK_GPIO,
.vsync_gpio_num = TEST_LCD_VSYNC_GPIO,
@@ -67,104 +71,80 @@ TEST_CASE("lcd rgb lcd panel", "[lcd]")
TEST_LCD_DATA15_GPIO,
},
.timings = {
.pclk_hz = 12000000,
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES,
.hsync_back_porch = 43,
.hsync_front_porch = 2,
.hsync_pulse_width = 1,
.vsync_back_porch = 12,
.vsync_front_porch = 1,
.hsync_back_porch = 68,
.hsync_front_porch = 20,
.hsync_pulse_width = 5,
.vsync_back_porch = 18,
.vsync_front_porch = 4,
.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_panel_reset(panel_handle));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
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);
}
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
}
free(img);
#undef TEST_IMG_SIZE
}
// The following test shows a porting example of LVGL GUI library
// To run the LVGL tests, you need to clone the LVGL library into components directory firstly
#if CONFIG_LV_USE_USER_DATA
#include "test_lvgl_port.h"
static bool notify_lvgl_ready_to_flush(esp_lcd_panel_handle_t panel, esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
lv_disp_t *disp = *(lv_disp_t **)user_ctx;
lv_disp_flush_ready(&disp->driver);
return false;
}
TEST_CASE("lvgl gui with rgb interface", "[lcd][lvgl][ignore]")
{
// initialize LVGL graphics library
lv_disp_t *disp = NULL;
lv_init();
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16,
.disp_gpio_num = -1,
.pclk_gpio_num = TEST_LCD_PCLK_GPIO,
.vsync_gpio_num = TEST_LCD_VSYNC_GPIO,
.hsync_gpio_num = TEST_LCD_HSYNC_GPIO,
.de_gpio_num = TEST_LCD_DE_GPIO,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
TEST_LCD_DATA1_GPIO,
TEST_LCD_DATA2_GPIO,
TEST_LCD_DATA3_GPIO,
TEST_LCD_DATA4_GPIO,
TEST_LCD_DATA5_GPIO,
TEST_LCD_DATA6_GPIO,
TEST_LCD_DATA7_GPIO,
TEST_LCD_DATA8_GPIO,
TEST_LCD_DATA9_GPIO,
TEST_LCD_DATA10_GPIO,
TEST_LCD_DATA11_GPIO,
TEST_LCD_DATA12_GPIO,
TEST_LCD_DATA13_GPIO,
TEST_LCD_DATA14_GPIO,
TEST_LCD_DATA15_GPIO,
},
.timings = {
.pclk_hz = 6000000,
.h_res = TEST_LCD_H_RES,
.v_res = TEST_LCD_V_RES,
.hsync_back_porch = 43,
.hsync_front_porch = 2,
.hsync_pulse_width = 1,
.vsync_back_porch = 12,
.vsync_front_porch = 1,
.vsync_pulse_width = 1,
},
.flags.fb_in_psram = 1,
.on_frame_trans_done = notify_lvgl_ready_to_flush,
.user_ctx = &disp,
};
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_init(panel_handle));
test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp);
return panel_handle;
}
#endif // CONFIG_LV_USE_USER_DATA
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++) {
uint8_t color_byte = esp_random() & 0xFF;
int x_start = esp_random() % (TEST_LCD_H_RES - 100);
int y_start = esp_random() % (TEST_LCD_V_RES - 100);
memset(img, color_byte, TEST_IMG_SIZE);
esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_start + 100, y_start + 100, img);
}
printf("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));
free(img);
}
#endif // CONFIG_SPIRAM_USE_MALLOC
#endif // SOC_LCD_RGB_SUPPORTED

View File

@@ -61,9 +61,10 @@ static void lcd_initialize_spi(esp_lcd_panel_io_handle_t *io_handle, esp_lcd_pan
TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TEST_SPI_HOST_ID, &io_config, io_handle));
}
#define TEST_IMG_SIZE (200 * 200 * sizeof(uint16_t))
static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle)
{
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(img);
@@ -79,10 +80,10 @@ static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_ha
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);
int x_start = esp_random() % (TEST_LCD_H_RES - 200);
int y_start = esp_random() % (TEST_LCD_V_RES - 200);
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 + 200, y_start + 200, img);
}
// turn off screen
esp_lcd_panel_disp_off(panel_handle, true);
@@ -91,7 +92,6 @@ static void lcd_panel_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_ha
TEST_ESP_OK(spi_bus_free(TEST_SPI_HOST_ID));
TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO));
free(img);
#undef TEST_IMG_SIZE
}
TEST_CASE("lcd panel spi io test", "[lcd]")
@@ -179,81 +179,3 @@ TEST_CASE("lcd panel with 1-line spi interface (st7789)", "[lcd]")
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lcd_panel_test(io_handle, panel_handle);
}
// The following test shows a porting example of LVGL GUI library
// To run the LVGL tests, you need to clone the LVGL library into components directory firstly
#if CONFIG_LV_USE_USER_DATA
#include "test_lvgl_port.h"
static bool notify_lvgl_ready_to_flush(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
lv_disp_t *disp = *(lv_disp_t **)user_ctx;
lv_disp_flush_ready(&disp->driver);
return false;
}
static void lvgl_gui_test(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, lv_disp_t **disp)
{
// initialize LVGL graphics library
lv_init();
// turn off backlight
gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 0);
esp_lcd_panel_reset(panel_handle);
esp_lcd_panel_init(panel_handle);
esp_lcd_panel_invert_color(panel_handle, true);
// the gap is LCD panel specific, even panels with the same driver IC, can have different gap value
esp_lcd_panel_set_gap(panel_handle, 0, 20);
// turn on backlight
gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1);
test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, disp);
}
#if SOC_SPI_SUPPORT_OCT
TEST_CASE("lvgl gui with 8-line spi interface (st7789)", "[lcd][lvgl][ignore]")
{
lv_disp_t *disp = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 8, 8, true);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lvgl_gui_test(io_handle, panel_handle, &disp);
}
TEST_CASE("lvgl gui with 8-line spi interface (nt35510)", "[lcd][lvgl][ignore]")
{
lv_disp_t *disp = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 16, 16, true);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_nt35510(io_handle, &panel_config, &panel_handle));
lvgl_gui_test(io_handle, panel_handle, &disp);
}
#endif // SOC_SPI_SUPPORT_OCT
TEST_CASE("lvgl gui with 1-line spi interface (st7789)", "[lcd][lvgl][ignore]")
{
lv_disp_t *disp = NULL;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_handle_t panel_handle = NULL;
lcd_initialize_spi(&io_handle, notify_lvgl_ready_to_flush, &disp, 8, 8, false);
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.color_space = ESP_LCD_COLOR_SPACE_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
lvgl_gui_test(io_handle, panel_handle, &disp);
}
#endif // CONFIG_LV_USE_USER_DATA

View File

@@ -1,16 +1,8 @@
// Copyright 2015-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
@@ -49,6 +41,8 @@ extern "C" {
#define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000)
#define SPI_LL_GET_HW(ID) ((ID)==0? &SPI1:((ID)==1? &SPI2 : &SPI3))
#define SPI_LL_DATA_MAX_BIT_LEN (1 << 24)
/**
* The data structure holding calculated clock configuration. Since the
* calculation needs long time, it should be calculated during initialization and

View File

@@ -40,6 +40,8 @@ extern "C" {
#define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000)
#define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):&GPSPI2)
#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18)
/**
* The data structure holding calculated clock configuration. Since the
* calculation needs long time, it should be calculated during initialization and

View File

@@ -1,16 +1,8 @@
// Copyright 2020 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
* SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/*******************************************************************************
* NOTICE
@@ -48,6 +40,8 @@ extern "C" {
#define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000)
#define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):&GPSPI2)
#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18)
/**
* The data structure holding calculated clock configuration. Since the
* calculation needs long time, it should be calculated during initialization and

View File

@@ -43,6 +43,8 @@ extern "C" {
#define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000)
#define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):((ID)==1? &GPSPI2 : &GPSPI3))
#define SPI_LL_DATA_MAX_BIT_LEN (1 << 23)
/**
* The data structure holding calculated clock configuration. Since the
* calculation needs long time, it should be calculated during initialization and

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
*/
@@ -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)
{
// 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);
dev->lcd_clock.lcd_clkm_div_a = div_a;
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:
dev->lcd_clock.lcd_clk_sel = 3;
break;
case LCD_CLK_SRC_PLL240M:
dev->lcd_clock.lcd_clk_sel = 2;
break;
case LCD_CLK_SRC_XTAL:
dev->lcd_clock.lcd_clk_sel = 1;
break;
default:
// disble LCD clock source
dev->lcd_clock.lcd_clk_sel = 0;
HAL_ASSERT(false && "unsupported clock source");
break;
}
@@ -60,7 +69,6 @@ static inline void lcd_ll_set_clock_idle_level(lcd_cam_dev_t *dev, bool level)
__attribute__((always_inline))
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;
}
@@ -68,7 +76,15 @@ __attribute__((always_inline))
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)
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)
@@ -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)
{
HAL_ASSERT(width == 8 || 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)
{
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;
}
__attribute__((always_inline))
static inline void lcd_ll_stop(lcd_cam_dev_t *dev)
{
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)
{
dev->lcd_user.lcd_reset = 1;
dev->lcd_user.lcd_reset = 0;
dev->lcd_user.lcd_reset = 1; // self clear
}
__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]
dev->lcd_user.lcd_bit_order = en;
}
__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)
{
dev->lcd_user.lcd_byte_order = 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_8bits_order = 0;
}
}
__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)
{
dev->lcd_misc.lcd_afifo_reset = 1;
dev->lcd_misc.lcd_afifo_reset = 0;
dev->lcd_misc.lcd_afifo_reset = 1; // self clear
}
__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))
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]
// in the second cycle, command[31:16] is sent out via lcd_data_out[15:0]
if (data_width == 8) {

View File

@@ -42,6 +42,8 @@ extern "C" {
#define SPI_LL_PERIPH_CLK_FREQ (80 * 1000000)
#define SPI_LL_GET_HW(ID) ((ID)==0? ({abort();NULL;}):((ID)==1? &GPSPI2 : &GPSPI3))
#define SPI_LL_DATA_MAX_BIT_LEN (1 << 18)
/**
* The data structure holding calculated clock configuration. Since the
* calculation needs long time, it should be calculated during initialization and

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
*/
@@ -19,6 +19,8 @@ extern "C" {
* +=====================+=========================+============================+
* | 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_XTAL | Medium resolution | No PM lock |
@@ -27,6 +29,7 @@ extern "C" {
*/
typedef enum {
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_XTAL, /*!< Select XTAL as the source clock */
} lcd_clock_source_t;

View File

@@ -163,7 +163,7 @@
/*-------------------------- LCD CAPS ----------------------------------------*/
/* Notes: On esp32, LCD intel 8080 timing is generated by I2S peripheral */
#define SOC_LCD_I80_SUPPORTED (1) /*!< Intel 8080 LCD is supported */
#define SOC_LCD_I80_BUSES (1) /*!< Only I2S0 has LCD mode */
#define SOC_LCD_I80_BUSES (2) /*!< Both I2S0/1 have LCD mode */
#define SOC_LCD_I80_BUS_WIDTH (24) /*!< Intel 8080 bus width */
/*-------------------------- LEDC CAPS ---------------------------------------*/

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
*/
@@ -40,6 +40,37 @@ const lcd_signal_conn_t lcd_periph_signals = {
I2S0O_DATA_OUT23_IDX,
},
.wr_sig = I2S0O_WS_OUT_IDX,
},
[1] = {
.module = PERIPH_I2S1_MODULE,
.irq_id = ETS_I2S1_INTR_SOURCE,
.data_sigs = {
I2S1O_DATA_OUT0_IDX,
I2S1O_DATA_OUT1_IDX,
I2S1O_DATA_OUT2_IDX,
I2S1O_DATA_OUT3_IDX,
I2S1O_DATA_OUT4_IDX,
I2S1O_DATA_OUT5_IDX,
I2S1O_DATA_OUT6_IDX,
I2S1O_DATA_OUT7_IDX,
I2S1O_DATA_OUT8_IDX,
I2S1O_DATA_OUT9_IDX,
I2S1O_DATA_OUT10_IDX,
I2S1O_DATA_OUT11_IDX,
I2S1O_DATA_OUT12_IDX,
I2S1O_DATA_OUT13_IDX,
I2S1O_DATA_OUT14_IDX,
I2S1O_DATA_OUT15_IDX,
I2S1O_DATA_OUT16_IDX,
I2S1O_DATA_OUT17_IDX,
I2S1O_DATA_OUT18_IDX,
I2S1O_DATA_OUT19_IDX,
I2S1O_DATA_OUT20_IDX,
I2S1O_DATA_OUT21_IDX,
I2S1O_DATA_OUT22_IDX,
I2S1O_DATA_OUT23_IDX,
},
.wr_sig = I2S1O_WS_OUT_IDX,
}
}
};