mirror of
https://github.com/espressif/esp-idf.git
synced 2025-08-01 03:34:32 +02:00
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:
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
||||
/**
|
||||
|
@@ -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 */
|
||||
|
6
components/esp_lcd/linker.lf
Normal file
6
components/esp_lcd/linker.lf
Normal 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)
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
@@ -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
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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 ---------------------------------------*/
|
||||
|
@@ -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,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
Reference in New Issue
Block a user