feat(lcd): support parlio lcd interface

This commit is contained in:
Chen Jichang 2024-11-13 16:29:06 +08:00 committed by Chen Ji Chang
parent b6b0758e42
commit e890b4bd7e
35 changed files with 1119 additions and 59 deletions

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@ -43,6 +43,7 @@ typedef struct {
uint32_t allow_pd: 1; /*!< Set to allow power down. When this flag set, the driver will backup/restore the PARLIO registers before/after entering/exist sleep mode.
By this approach, the system can power off PARLIO's power domain.
This can save power, but at the expense of more RAM being consumed. */
uint32_t invert_valid_out: 1; /*!< Invert the output valid signal */
} flags; /*!< Extra configuration flags */
} parlio_tx_unit_config_t;

View File

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "driver/parlio_tx.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Get the alignment constraints for internal and external memory of the GDMA used in parlio_tx unit
*
* @param[in] tx_unit Parallel IO TX unit that created by `parlio_new_tx_unit`
* @param[out] int_mem_align Internal memory alignment
* @param[out] ext_mem_align External memory alignment
* @return
* - ESP_OK: Get alignment constraints successfully
* - ESP_ERR_INVALID_ARG: Get alignment constraints failed because of invalid argument
* - ESP_FAIL: Get alignment constraints failed because of other error
*/
esp_err_t parlio_tx_get_alignment_constraints(parlio_tx_unit_handle_t tx_unit, size_t *int_mem_align, size_t *ext_mem_align);
#ifdef __cplusplus
}
#endif

View File

@ -68,13 +68,6 @@ typedef dma_descriptor_align8_t parlio_dma_desc_t;
#define PARLIO_MAX_ALIGNED_DMA_BUF_SIZE DMA_DESCRIPTOR_BUFFER_MAX_SIZE_4B_ALIGNED
#endif
#ifdef CACHE_LL_L2MEM_NON_CACHE_ADDR
/* The descriptor address can be mapped by a fixed offset */
#define PARLIO_GET_NON_CACHED_DESC_ADDR(desc) (desc ? (parlio_dma_desc_t *)(CACHE_LL_L2MEM_NON_CACHE_ADDR(desc)) : NULL)
#else
#define PARLIO_GET_NON_CACHED_DESC_ADDR(desc) (desc)
#endif // CACHE_LL_L2MEM_NON_CACHE_ADDR
#if SOC_PERIPH_CLK_CTRL_SHARED
#define PARLIO_CLOCK_SRC_ATOMIC() PERIPH_RCC_ATOMIC()
#else

View File

@ -24,6 +24,7 @@
#include "esp_attr.h"
#include "esp_err.h"
#include "esp_rom_gpio.h"
#include "soc/gpio_sig_map.h"
#include "esp_intr_alloc.h"
#include "esp_pm.h"
#include "soc/parlio_periph.h"
@ -166,7 +167,7 @@ static esp_err_t parlio_tx_unit_configure_gpio(parlio_tx_unit_t *tx_unit, const
// connect the signal to the GPIO by matrix, it will also enable the output path properly
esp_rom_gpio_connect_out_signal(config->valid_gpio_num,
parlio_periph_signals.groups[group_id].tx_units[unit_id].data_sigs[PARLIO_LL_TX_DATA_LINE_AS_VALID_SIG],
false, false);
config->flags.invert_valid_out, false);
}
if (config->clk_out_gpio_num >= 0) {
gpio_func_sel(config->clk_out_gpio_num, PIN_FUNC_GPIO);
@ -232,6 +233,18 @@ static esp_err_t parlio_tx_unit_init_dma(parlio_tx_unit_t *tx_unit, const parlio
return ESP_OK;
}
esp_err_t parlio_tx_get_alignment_constraints(parlio_tx_unit_t *tx_unit, size_t *int_mem_align, size_t *ext_mem_align)
{
ESP_RETURN_ON_FALSE(tx_unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
if (int_mem_align) {
*int_mem_align = tx_unit->int_mem_align;
}
if (ext_mem_align) {
*ext_mem_align = tx_unit->ext_mem_align;
}
return ESP_OK;
}
static esp_err_t parlio_select_periph_clock(parlio_tx_unit_t *tx_unit, const parlio_tx_unit_config_t *config)
{
parlio_hal_context_t *hal = &tx_unit->base.group->hal;
@ -640,8 +653,15 @@ static void IRAM_ATTR parlio_tx_default_isr(void *args)
parlio_ll_clear_interrupt_status(hal->regs, PARLIO_LL_EVENT_TX_EOF);
parlio_ll_tx_start(hal->regs, false);
parlio_tx_trans_desc_t *trans_desc = NULL;
// invoke callback before sending the transaction to complete queue
parlio_tx_done_callback_t done_cb = tx_unit->on_trans_done;
if (done_cb) {
if (done_cb(tx_unit, NULL, tx_unit->user_data)) {
need_yield = true;
}
}
parlio_tx_trans_desc_t *trans_desc = NULL;
parlio_tx_fsm_t expected_fsm = PARLIO_TX_FSM_RUN;
if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_ENABLE_WAIT)) {
trans_desc = tx_unit->cur_trans;
@ -654,14 +674,6 @@ static void IRAM_ATTR parlio_tx_default_isr(void *args)
atomic_store(&tx_unit->fsm, PARLIO_TX_FSM_ENABLE);
}
// invoke callback
parlio_tx_done_callback_t done_cb = tx_unit->on_trans_done;
if (done_cb) {
if (done_cb(tx_unit, NULL, tx_unit->user_data)) {
need_yield = true;
}
}
// if the tx unit is till in enable state (i.e. not disabled by user), let's try start the next pending transaction
expected_fsm = PARLIO_TX_FSM_ENABLE;
if (atomic_compare_exchange_strong(&tx_unit->fsm, &expected_fsm, PARLIO_TX_FSM_RUN_WAIT)) {

View File

@ -43,27 +43,27 @@ extern "C" {
#define TEST_DATA6_GPIO 6
#define TEST_DATA7_GPIO 7
#elif CONFIG_IDF_TARGET_ESP32H2
#define TEST_CLK_GPIO 10
#define TEST_VALID_GPIO 11
#define TEST_DATA0_GPIO 0
#define TEST_DATA1_GPIO 1
#define TEST_DATA2_GPIO 2
#define TEST_DATA3_GPIO 3
#define TEST_DATA4_GPIO 4
#define TEST_DATA5_GPIO 5
#define TEST_DATA6_GPIO 8
#define TEST_DATA7_GPIO 9
#define TEST_VALID_GPIO 2
#define TEST_CLK_GPIO 3
#define TEST_DATA0_GPIO 8
#define TEST_DATA1_GPIO 5
#define TEST_DATA2_GPIO 9
#define TEST_DATA3_GPIO 10
#define TEST_DATA4_GPIO 27
#define TEST_DATA5_GPIO 11
#define TEST_DATA6_GPIO 26
#define TEST_DATA7_GPIO 12
#elif CONFIG_IDF_TARGET_ESP32P4
#define TEST_CLK_GPIO 33
#define TEST_VALID_GPIO 36
#define TEST_DATA0_GPIO 0
#define TEST_DATA1_GPIO 1
#define TEST_DATA2_GPIO 2
#define TEST_DATA3_GPIO 3
#define TEST_DATA4_GPIO 4
#define TEST_DATA5_GPIO 5
#define TEST_DATA6_GPIO 6
#define TEST_DATA7_GPIO 7
#define TEST_VALID_GPIO 32
#define TEST_DATA0_GPIO 24
#define TEST_DATA1_GPIO 25
#define TEST_DATA2_GPIO 26
#define TEST_DATA3_GPIO 27
#define TEST_DATA4_GPIO 28
#define TEST_DATA5_GPIO 29
#define TEST_DATA6_GPIO 30
#define TEST_DATA7_GPIO 31
#else
#error "Unsupported target"
#endif

View File

@ -303,7 +303,7 @@ TEST_CASE("parlio can transmit PSRAM buffer", "[parlio_tx]")
.data_gpio_nums = {
TEST_DATA0_GPIO,
},
.output_clk_freq_hz = 20 * 1000 * 1000,
.output_clk_freq_hz = 10 * 1000 * 1000,
.trans_queue_depth = 4,
.max_transfer_size = 65535,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_LSB,

View File

@ -30,6 +30,10 @@ if(CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA)
list(APPEND srcs "i80/esp_lcd_panel_io_i2s.c")
endif()
if(CONFIG_SOC_PARLIO_SUPPORT_SPI_LCD)
list(APPEND srcs "parl/esp_lcd_panel_io_parl.c")
endif()
if(CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED)
list(APPEND srcs "i80/esp_lcd_panel_io_i80.c")
endif()

View File

@ -376,7 +376,7 @@ static esp_err_t panel_io_i80_register_event_callbacks(esp_lcd_panel_io_handle_t
lcd_panel_io_i80_t *i80_device = __containerof(io, lcd_panel_io_i80_t, base);
if (i80_device->on_color_trans_done != NULL) {
ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was owerwritten!");
ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
}
i80_device->on_color_trans_done = cbs->on_color_trans_done;

View File

@ -0,0 +1,75 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#include "esp_lcd_types.h"
#include "soc/soc_caps.h"
#include "driver/parlio_types.h"
#define ESP_PARLIO_LCD_WIDTH_MAX 8 /*!< Maximum data width of parlio lcd interface */
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_PARLIO_SUPPORTED
/**
* @brief Parallel Panel IO configuration structure, for intel 8080 interface(8 data-lines) or SPI interface(1 data-lines)
*/
typedef struct {
int dc_gpio_num; /*!< GPIO used for D/C line */
int clk_gpio_num; /*!< GPIO used for CLK line */
int cs_gpio_num; /*!< GPIO used for CS line */
int data_gpio_nums[ESP_PARLIO_LCD_WIDTH_MAX]; /*!< GPIOs used for data lines */
size_t data_width; /*!< Number of data lines, 1(SPI) or 8(I80) */
uint32_t pclk_hz; /*!< Frequency of pixel clock */
parlio_clock_source_t clk_src; /*!< Clock source for the Parlio peripheral */
size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */
size_t dma_burst_size; /*!< DMA burst size, in bytes */
size_t trans_queue_depth; /*!< Transaction queue size, larger queue, higher throughput */
int lcd_cmd_bits; /*!< Bit-width of LCD command */
int lcd_param_bits; /*!< Bit-width of LCD parameter */
struct {
unsigned int dc_cmd_level: 1; /*!< Level of DC line in CMD phase */
unsigned int dc_data_level: 1; /*!< Level of DC line in DATA phase */
} dc_levels; /*!< Each LCD device might have its own D/C control logic */
struct {
unsigned int cs_active_high: 1; /*!< If set, a high level of CS line will select the device, otherwise, CS line is low level active */
} flags; /*!< Panel IO config flags */
} esp_lcd_panel_io_parl_config_t;
/**
* @brief Create LCD panel IO, for parlio interface
*
* @param[in] io_config IO configuration, for parlio interface
* @param[out] ret_io Returned panel IO handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NOT_SUPPORTED if some configuration can't be satisfied, e.g. pixel clock out of the range
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_panel_io_parl(const esp_lcd_panel_io_parl_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
/**
* @brief Allocate a draw buffer that can be used by parlio interface LCD panel
*
* @note This function differs from the normal 'heap_caps_*' functions in that it can also automatically handle the alignment required by DMA burst, cache line size, etc.
*
* @param[in] io Panel IO handle, created by `esp_lcd_new_panel_io_parl()`
* @param[in] size Size of memory to be allocated
* @param[in] caps Bitwise OR of MALLOC_CAP_* flags indicating the type of memory desired for the allocation
* @return Pointer to a new buffer of size 'size' with capabilities 'caps', or NULL if allocation failed
*/
void *esp_lcd_parlio_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps);
#endif // SOC_PARLIO_SUPPORTED
#ifdef __cplusplus
}
#endif

View File

@ -11,6 +11,7 @@
#include "esp_lcd_io_i80.h"
#include "esp_lcd_io_i2c.h"
#include "esp_lcd_io_spi.h"
#include "esp_lcd_io_parl.h"
#ifdef __cplusplus
extern "C" {

View File

@ -0,0 +1,324 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Although we're manipulating Parlio peripheral, it has nothing to do with the Parlio.
// In fact, we're simulating the Intel 8080 (8 data-width) or SPI(1 data-width) interface with Parlio peripheral.
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/queue.h>
#include "sdkconfig.h"
#if CONFIG_LCD_ENABLE_DEBUG_LOG
// The local log level must be defined before including esp_log.h
// Set the maximum log level for this source file
#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#endif
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "soc/soc_caps.h"
#include "soc/lcd_periph.h"
#include "hal/gpio_hal.h"
#include "driver/gpio.h"
#include "driver/parlio_tx.h"
#include "driver/parlio_types.h"
#include "esp_private/gpio.h"
#include "esp_private/parlio_private.h"
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_common.h"
static const char *TAG = "lcd_panel.io.parl";
typedef struct lcd_panel_io_parlio_t lcd_panel_io_parlio_t;
typedef struct parlio_trans_descriptor_t parlio_trans_descriptor_t;
static esp_err_t panel_io_parl_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size);
static esp_err_t panel_io_parl_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size);
static esp_err_t panel_io_parl_del(esp_lcd_panel_io_t *io);
static bool lcd_default_isr_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx);
static esp_err_t panel_io_parl_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx);
struct parlio_trans_descriptor_t {
lcd_panel_io_parlio_t *parlio_device; // parlio device issuing this transaction
const void *data; // Data buffer
uint32_t data_length; // Data buffer size
struct {
unsigned int en_trans_done_cb: 1;
} flags;
};
struct lcd_panel_io_parlio_t {
esp_lcd_panel_io_t base; // Base class of generic lcd panel io
parlio_tx_unit_handle_t tx_unit; // Parlio TX unit
size_t data_width; // Number of data lines
int dc_gpio_num; // GPIO used for DC line
int cs_gpio_num; // GPIO used for CS line
int lcd_cmd_bits; // Bit width of LCD command
int lcd_param_bits; // Bit width of LCD parameter
void *user_ctx; // private data used when transfer color data
esp_lcd_panel_io_color_trans_done_cb_t on_color_trans_done; // color data trans done callback
struct {
unsigned int dc_cmd_level: 1; // Level of DC line in CMD phase
unsigned int dc_data_level: 1; // Level of DC line in DATA phase
} dc_levels;
parlio_trans_descriptor_t trans_desc; // Transaction description
};
esp_err_t esp_lcd_new_panel_io_parl(const esp_lcd_panel_io_parl_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
{
esp_err_t ret = ESP_OK;
lcd_panel_io_parlio_t *parlio_device = NULL;
ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// Only support 1-bit(SPI) or 8-bit(I80) data width
#if SOC_PARLIO_SUPPORT_I80_LCD
ESP_GOTO_ON_FALSE(io_config->data_width == 1 || io_config->data_width == 8, ESP_ERR_INVALID_ARG, err,
TAG, "invalid bus width:%d", io_config->data_width);
#else
ESP_GOTO_ON_FALSE(io_config->data_width == 1, ESP_ERR_INVALID_ARG, err,
TAG, "invalid bus width:%d", io_config->data_width);
#endif
// allocate parlio device memory
parlio_device = heap_caps_calloc(1, sizeof(lcd_panel_io_parlio_t), MALLOC_CAP_DEFAULT);
ESP_GOTO_ON_FALSE(parlio_device, ESP_ERR_NO_MEM, err, TAG, "no mem for parlio device");
// initialize the parlio unit
parlio_tx_unit_handle_t tx_unit = NULL;
parlio_tx_unit_config_t pio_config = {
.clk_src = io_config->clk_src,
.data_width = io_config->data_width,
.clk_in_gpio_num = -1, // use internal clock source
.valid_gpio_num = io_config->cs_gpio_num,
.clk_out_gpio_num = io_config->clk_gpio_num,
.data_gpio_nums = {
io_config->data_gpio_nums[0],
io_config->data_gpio_nums[1],
io_config->data_gpio_nums[2],
io_config->data_gpio_nums[3],
io_config->data_gpio_nums[4],
io_config->data_gpio_nums[5],
io_config->data_gpio_nums[6],
io_config->data_gpio_nums[7],
},
.output_clk_freq_hz = io_config->pclk_hz,
.trans_queue_depth = io_config->trans_queue_depth ? io_config->trans_queue_depth : 4,
.max_transfer_size = io_config->max_transfer_bytes,
.sample_edge = PARLIO_SAMPLE_EDGE_POS,
.bit_pack_order = PARLIO_BIT_PACK_ORDER_MSB,
.dma_burst_size = io_config->dma_burst_size,
.flags.invert_valid_out = !io_config->flags.cs_active_high,
};
ESP_GOTO_ON_ERROR(parlio_new_tx_unit(&pio_config, &tx_unit), err, TAG, "config parlio tx unit failed");
parlio_tx_event_callbacks_t parlio_cbs = {
.on_trans_done = lcd_default_isr_callback,
};
ESP_GOTO_ON_ERROR(parlio_tx_unit_register_event_callbacks(tx_unit, &parlio_cbs, parlio_device), err, TAG, "register parlio tx callback failed");
// DC signal is controlled by software, set as general purpose IO
gpio_func_sel(io_config->dc_gpio_num, PIN_FUNC_GPIO);
gpio_output_enable(io_config->dc_gpio_num);
parlio_device->dc_gpio_num = io_config->dc_gpio_num;
parlio_device->tx_unit = tx_unit;
parlio_device->data_width = io_config->data_width;
parlio_device->lcd_cmd_bits = io_config->lcd_cmd_bits;
parlio_device->lcd_param_bits = io_config->lcd_param_bits;
parlio_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level;
parlio_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level;
parlio_device->cs_gpio_num = io_config->cs_gpio_num;
parlio_device->trans_desc.parlio_device = parlio_device;
// fill panel io function table
parlio_device->base.del = panel_io_parl_del;
parlio_device->base.tx_param = panel_io_parl_tx_param;
parlio_device->base.tx_color = panel_io_parl_tx_color;
parlio_device->base.register_event_callbacks = panel_io_parl_register_event_callbacks;
// enable parlio tx unit finally
ESP_GOTO_ON_ERROR(parlio_tx_unit_enable(tx_unit), err, TAG, "enable parlio tx unit failed");
*ret_io = &(parlio_device->base);
ESP_LOGD(TAG, "new parlio lcd panel io @%p", parlio_device);
return ESP_OK;
err:
if (parlio_device) {
if (parlio_device->tx_unit) {
parlio_del_tx_unit(parlio_device->tx_unit);
}
free(parlio_device);
}
return ret;
}
void *esp_lcd_parlio_alloc_draw_buffer(esp_lcd_panel_io_handle_t io, size_t size, uint32_t caps)
{
ESP_RETURN_ON_FALSE(io, NULL, TAG, "invalid argument");
lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
size_t int_mem_align = 0;
size_t ext_mem_align = 0;
void *buf = NULL;
parlio_tx_get_alignment_constraints(parlio_device->tx_unit, &int_mem_align, &ext_mem_align);
// alloc from external memory
if (caps & MALLOC_CAP_SPIRAM) {
buf = heap_caps_aligned_calloc(ext_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
} else {
buf = heap_caps_aligned_calloc(int_mem_align, 1, size, MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA);
}
return buf;
}
static esp_err_t panel_io_parl_del(esp_lcd_panel_io_t *io)
{
lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
// wait all pending transaction to finish
ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
ESP_LOGD(TAG, "del parlio lcd panel io @%p", parlio_device);
ESP_RETURN_ON_ERROR(parlio_tx_unit_disable(parlio_device->tx_unit), TAG, "disable parlio tx unit failed");
ESP_RETURN_ON_ERROR(parlio_del_tx_unit(parlio_device->tx_unit), TAG, "del parlio tx unit failed");
free(parlio_device);
return ESP_OK;
}
static esp_err_t panel_io_parl_register_event_callbacks(esp_lcd_panel_io_handle_t io, const esp_lcd_panel_io_callbacks_t *cbs, void *user_ctx)
{
lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
if (parlio_device->on_color_trans_done != NULL) {
ESP_LOGW(TAG, "Callback on_color_trans_done was already set and now it was overwritten!");
}
parlio_device->on_color_trans_done = cbs->on_color_trans_done;
parlio_device->user_ctx = user_ctx;
return ESP_OK;
}
static void parlio_lcd_prepare_cmd_buffer(parlio_trans_descriptor_t *trans_desc, const void *cmd)
{
lcd_panel_io_parlio_t *parlio_device = trans_desc->parlio_device;
uint8_t *from = (uint8_t *)cmd;
// LCD is big-endian, e.g. to send command 0x1234, byte 0x12 should appear on the data bus first
// However, the Parlio peripheral will send 0x34 first, so we reversed the order below
if (parlio_device->data_width < parlio_device->lcd_cmd_bits) {
int start = 0;
int end = parlio_device->lcd_cmd_bits / 8 - 1;
lcd_com_reverse_buffer_bytes(from, start, end);
}
trans_desc->data = cmd;
trans_desc->data_length = MAX(parlio_device->lcd_cmd_bits, parlio_device->data_width) / 8;
}
static void parlio_lcd_prepare_param_buffer(parlio_trans_descriptor_t *trans_desc, const void *param, size_t param_num)
{
lcd_panel_io_parlio_t *parlio_device = trans_desc->parlio_device;
uint8_t *from = (uint8_t *)param;
int param_size = parlio_device->lcd_param_bits / 8;
// LCD is big-endian, e.g. to send param 0x1234, byte 0x12 should appear on the data bus first
// However, the Parlio peripheral will send 0x34 first, so we reversed the order below
if (parlio_device->data_width < parlio_device->lcd_param_bits) {
for (size_t i = 0; i < param_num; i++) {
int start = i * param_size;
int end = start + param_size - 1;
lcd_com_reverse_buffer_bytes(from, start, end);
}
}
trans_desc->data = param;
trans_desc->data_length = param_num;
}
static void parlio_lcd_prepare_color_buffer(parlio_trans_descriptor_t *trans_desc, const void *color, size_t color_size)
{
trans_desc->data = color;
trans_desc->data_length = color_size;
}
static esp_err_t panel_io_parl_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, const void *param, size_t param_size)
{
lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
parlio_trans_descriptor_t *trans_desc = NULL;
// before issue a polling transaction, need to wait queued transactions finished
ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
trans_desc = &parlio_device->trans_desc;
trans_desc->flags.en_trans_done_cb = false;
parlio_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_cmd_level);
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
};
ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
if (param && param_size) {
parlio_lcd_prepare_param_buffer(trans_desc, param, param_size * 8 / parlio_device->lcd_param_bits);
// wait transmit done before changing DC level
ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_data_level);
ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
}
// In case the lcd_cmd/param data is on the stack, wait transmit done to prevent it from being recycled
ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
return ESP_OK;
}
static esp_err_t panel_io_parl_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, const void *color, size_t color_size)
{
lcd_panel_io_parlio_t *parlio_device = __containerof(io, lcd_panel_io_parlio_t, base);
parlio_trans_descriptor_t *trans_desc = NULL;
trans_desc = &parlio_device->trans_desc;
if (lcd_cmd != -1) {
// wait transmit done to prevent skipping previous color transfer callback
ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
trans_desc->flags.en_trans_done_cb = false; // no callback for command transfer
parlio_lcd_prepare_cmd_buffer(trans_desc, &lcd_cmd);
gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_cmd_level);
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
};
ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
// wait transmit done before changing DC level
ESP_RETURN_ON_ERROR(parlio_tx_unit_wait_all_done(parlio_device->tx_unit, -1), TAG, "error waiting transmit done");
}
parlio_lcd_prepare_color_buffer(trans_desc, color, color_size);
trans_desc->flags.en_trans_done_cb = true;
gpio_set_level(parlio_device->dc_gpio_num, parlio_device->dc_levels.dc_data_level);
parlio_transmit_config_t transmit_config = {
.idle_value = 0x00,
};
ESP_RETURN_ON_ERROR(parlio_tx_unit_transmit(parlio_device->tx_unit, trans_desc->data, trans_desc->data_length * 8, &transmit_config), TAG, "error transmit");
return ESP_OK;
}
IRAM_ATTR static bool lcd_default_isr_callback(parlio_tx_unit_handle_t tx_unit, const parlio_tx_done_event_data_t *edata, void *user_ctx)
{
lcd_panel_io_parlio_t *parlio_device = (lcd_panel_io_parlio_t *)user_ctx;
parlio_trans_descriptor_t *trans_desc = &parlio_device->trans_desc;
bool need_yield = false;
// device callback
if (trans_desc->flags.en_trans_done_cb) {
if (parlio_device->on_color_trans_done) {
if (parlio_device->on_color_trans_done(&parlio_device->base, NULL, parlio_device->user_ctx)) {
need_yield = true;
}
}
}
return need_yield;
}

View File

@ -40,6 +40,13 @@ components/esp_lcd/test_apps/mipi_dsi_lcd:
temporary: true
reason: lack of runners, DSI can't work without an LCD connected
components/esp_lcd/test_apps/parlio_lcd:
depends_components:
- esp_lcd
- esp_driver_parlio
disable:
- if: SOC_PARLIO_SUPPORT_SPI_LCD != 1
components/esp_lcd/test_apps/rgb_lcd:
depends_components:
- esp_lcd

View File

@ -12,13 +12,13 @@ extern "C" {
#define TEST_LCD_H_RES (240)
#define TEST_LCD_V_RES (280)
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
#define TEST_LCD_BK_LIGHT_GPIO (1)
#define TEST_LCD_RST_GPIO (2)
#define TEST_LCD_CS_GPIO (3)
#define TEST_LCD_DC_GPIO (4)
#define TEST_LCD_PCLK_GPIO (5)
#define TEST_LCD_DATA0_GPIO (6)
#if CONFIG_IDF_TARGET_ESP32S3
#define TEST_LCD_BK_LIGHT_GPIO (18)
#define TEST_LCD_RST_GPIO (5)
#define TEST_LCD_CS_GPIO (0)
#define TEST_LCD_DC_GPIO (19)
#define TEST_LCD_PCLK_GPIO (2)
#define TEST_LCD_DATA0_GPIO (4)
#define TEST_LCD_DATA1_GPIO (7)
#define TEST_LCD_DATA2_GPIO (8)
#define TEST_LCD_DATA3_GPIO (9)
@ -78,6 +78,28 @@ extern "C" {
#define TEST_LCD_DATA13_GPIO (25)
#define TEST_LCD_DATA14_GPIO (16)
#define TEST_LCD_DATA15_GPIO (17)
#elif CONFIG_IDF_TARGET_ESP32P4
#define TEST_LCD_BK_LIGHT_GPIO (48)
#define TEST_LCD_RST_GPIO (35)
#define TEST_LCD_PCLK_GPIO (33)
#define TEST_LCD_CS_GPIO (32)
#define TEST_LCD_DC_GPIO (34)
#define TEST_LCD_DATA0_GPIO (24)
#define TEST_LCD_DATA1_GPIO (25)
#define TEST_LCD_DATA2_GPIO (26)
#define TEST_LCD_DATA3_GPIO (27)
#define TEST_LCD_DATA4_GPIO (28)
#define TEST_LCD_DATA5_GPIO (29)
#define TEST_LCD_DATA6_GPIO (30)
#define TEST_LCD_DATA7_GPIO (31)
#define TEST_LCD_DATA8_GPIO (12)
#define TEST_LCD_DATA9_GPIO (13)
#define TEST_LCD_DATA10_GPIO (14)
#define TEST_LCD_DATA11_GPIO (15)
#define TEST_LCD_DATA12_GPIO (26)
#define TEST_LCD_DATA13_GPIO (25)
#define TEST_LCD_DATA14_GPIO (16)
#define TEST_LCD_DATA15_GPIO (17)
#endif
#ifdef __cplusplus

View File

@ -0,0 +1,8 @@
# This is the project CMakeLists.txt file for the test subproject
cmake_minimum_required(VERSION 3.16)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(parlio_lcd_panel_test)

View File

@ -0,0 +1,4 @@
| Supported Targets | ESP32-C5 | ESP32-H2 | ESP32-P4 |
| ----------------- | -------- | -------- | -------- |
This test app is used to test LCDs with intel 8080 interface.

View File

@ -0,0 +1,8 @@
set(srcs "test_app_main.c"
"test_parlio_lcd_panel.c")
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES esp_lcd unity driver esp_driver_parlio
WHOLE_ARCHIVE)

View File

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include "unity.h"
#include "unity_test_runner.h"
#include "esp_heap_caps.h"
// Some resources are lazy allocated in LCD driver, the threadhold is left for that case
#define TEST_MEMORY_LEAK_THRESHOLD (-300)
static size_t before_free_8bit;
static size_t before_free_32bit;
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}
void app_main(void)
{
// ____ ___ ____ __ ________ __ __________ _______________________
// / __ \/ | / __ \/ / / _/ __ \ / / / ____/ __ \ /_ __/ ____/ ___/_ __/
// / /_/ / /| | / /_/ / / / // / / / / / / / / / / / / / / __/ \__ \ / /
// / ____/ ___ |/ _, _/ /____/ // /_/ / / /___/ /___/ /_/ / / / / /___ ___/ // /
// /_/ /_/ |_/_/ |_/_____/___/\____/ /_____/\____/_____/ /_/ /_____//____//_/
printf(" ____ ___ ____ __ ________ __ __________ _______________________\r\n");
printf(" / __ \\/ | / __ \\/ / / _/ __ \\ / / / ____/ __ \\ /_ __/ ____/ ___/_ __/\r\n");
printf(" / /_/ / /| | / /_/ / / / // / / / / / / / / / / / / / / __/ \\__ \\ / / \r\n");
printf(" / ____/ ___ |/ _, _/ /____/ // /_/ / / /___/ /___/ /_/ / / / / /___ ___/ // / \r\n");
printf(" /_/ /_/ |_/_/ |_/_____/___/\\____/ /_____/\\____/_____/ /_/ /_____//____//_/ \r\n");
unity_run_menu();
}

View File

@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#ifdef __cplusplus
extern "C" {
#endif
#define TEST_LCD_H_RES (240)
#define TEST_LCD_V_RES (280)
#if CONFIG_IDF_TARGET_ESP32P4
#define TEST_LCD_BK_LIGHT_GPIO (48)
#define TEST_LCD_RST_GPIO (35)
#define TEST_LCD_PCLK_GPIO (33)
#define TEST_LCD_CS_GPIO (32)
#define TEST_LCD_DC_GPIO (34)
#define TEST_LCD_DATA0_GPIO (24)
#define TEST_LCD_DATA1_GPIO (25)
#define TEST_LCD_DATA2_GPIO (26)
#define TEST_LCD_DATA3_GPIO (27)
#define TEST_LCD_DATA4_GPIO (28)
#define TEST_LCD_DATA5_GPIO (29)
#define TEST_LCD_DATA6_GPIO (30)
#define TEST_LCD_DATA7_GPIO (31)
#elif CONFIG_IDF_TARGET_ESP32H2
#define TEST_LCD_BK_LIGHT_GPIO (2)
#define TEST_LCD_RST_GPIO (14)
#define TEST_LCD_CS_GPIO (3)
#define TEST_LCD_DC_GPIO (13)
#define TEST_LCD_PCLK_GPIO (5)
#define TEST_LCD_DATA0_GPIO (4)
#elif CONFIG_IDF_TARGET_ESP32C5
#define TEST_LCD_BK_LIGHT_GPIO (1)
#define TEST_LCD_RST_GPIO (7)
#define TEST_LCD_CS_GPIO (27)
#define TEST_LCD_DC_GPIO (6)
#define TEST_LCD_PCLK_GPIO (25)
#define TEST_LCD_DATA0_GPIO (26)
#endif
#define TEST_LCD_PIXEL_CLOCK_HZ (10 * 1000 * 1000)
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,263 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_random.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_commands.h"
#include "soc/soc_caps.h"
#include "driver/gpio.h"
#include "driver/parlio_tx.h"
#include "test_parlio_board.h"
#define TEST_SPI_DATA_WIDTH 1
#define TEST_I80_DATA_WIDTH 8
#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t))
static void lcd_parlio_panel_with_st7789_interface(esp_lcd_panel_io_handle_t io_handle)
{
uint8_t *img = heap_caps_malloc(TEST_IMG_SIZE, MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL(img);
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_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_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 display
esp_lcd_panel_disp_on_off(panel_handle, true);
// turn on backlight
gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1);
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);
}
// turn off screen
esp_lcd_panel_disp_on_off(panel_handle, false);
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO));
free(img);
}
TEST_CASE("lcd_panel_simulate_SPI_interface(st7789)", "[lcd]")
{
esp_lcd_panel_io_parl_config_t io_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.clk_gpio_num = TEST_LCD_PCLK_GPIO,
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
},
.data_width = TEST_SPI_DATA_WIDTH,
.max_transfer_bytes = TEST_IMG_SIZE,
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_cmd_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
esp_lcd_panel_io_handle_t spi_io_handle = NULL;
TEST_ESP_OK(esp_lcd_new_panel_io_parl(&io_config, &spi_io_handle));
esp_lcd_panel_io_handle_t another_io_handle = NULL;
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, esp_lcd_new_panel_io_parl(&io_config, &another_io_handle));
lcd_parlio_panel_with_st7789_interface(spi_io_handle);
}
#if SOC_PARLIO_SUPPORT_I80_LCD
TEST_CASE("lcd_panel_simulate_I80_interface(st7789)", "[lcd]")
{
esp_lcd_panel_io_parl_config_t io_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.clk_gpio_num = TEST_LCD_PCLK_GPIO,
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.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,
},
.data_width = TEST_I80_DATA_WIDTH,
.max_transfer_bytes = TEST_IMG_SIZE,
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_cmd_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
};
esp_lcd_panel_io_handle_t i80_io_handle = NULL;
TEST_ESP_OK(esp_lcd_new_panel_io_parl(&io_config, &i80_io_handle));
lcd_parlio_panel_with_st7789_interface(i80_io_handle);
}
#endif
#undef TEST_IMG_SIZE
static bool on_color_trans_done(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
{
uint32_t *isr_counter = (uint32_t *)user_ctx;
(*isr_counter)++;
return false;
}
static void lcd_parlio_send_colors_to_fixed_region(size_t data_width)
{
int x_start = 100;
int y_start = 100;
int x_end = 200;
int y_end = 200;
size_t color_size = (x_end - x_start) * (y_end - y_start) * 2;
void *color_data = malloc(color_size);
TEST_ASSERT_NOT_NULL(color_data);
uint8_t color_byte = esp_random() & 0xFF;
memset(color_data, color_byte, color_size);
uint32_t isr_counter = 0;
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_parl_config_t io_config = {
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_cmd_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = 8,
.lcd_param_bits = 8,
.dc_gpio_num = TEST_LCD_DC_GPIO,
.clk_gpio_num = TEST_LCD_PCLK_GPIO,
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.data_gpio_nums = {
TEST_LCD_DATA0_GPIO,
#if SOC_PARLIO_SUPPORT_I80_LCD
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,
#endif
},
.data_width = data_width,
.max_transfer_bytes = color_size * 2,
};
TEST_ESP_OK(esp_lcd_new_panel_io_parl(&io_config, &io_handle));
printf("Register io panel event callback");
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = on_color_trans_done,
};
/* Register done callback */
ESP_ERROR_CHECK(esp_lcd_panel_io_register_event_callbacks(io_handle, &cbs, &isr_counter));
printf("creating LCD panel\r\n");
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = TEST_LCD_RST_GPIO,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
.bits_per_pixel = 16,
};
TEST_ESP_OK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
// we don't use the panel handle in this test, creating the panel just for a quick initialization
printf("initialize LCD panel\r\n");
// 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 display
esp_lcd_panel_disp_on_off(panel_handle, true);
// turn on backlight
gpio_set_level(TEST_LCD_BK_LIGHT_GPIO, 1);
printf("set the flush window for only once\r\n");
esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_CASET, (uint8_t[]) {
(x_start >> 8) & 0xFF,
x_start & 0xFF,
((x_end - 1) >> 8) & 0xFF,
(x_end - 1) & 0xFF,
}, 4);
esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_RASET, (uint8_t[]) {
(y_start >> 8) & 0xFF,
y_start & 0xFF,
((y_end - 1) >> 8) & 0xFF,
(y_end - 1) & 0xFF,
}, 4);
esp_lcd_panel_io_tx_param(io_handle, LCD_CMD_RAMWR, NULL, 0);
printf("send colors to the fixed region in multiple steps\r\n");
const int steps = 10;
int color_size_per_step = color_size / steps;
for (int i = 0; i < steps; i++) {
TEST_ESP_OK(esp_lcd_panel_io_tx_color(io_handle, -1, color_data + i * color_size_per_step, color_size_per_step));
}
vTaskDelay(pdMS_TO_TICKS(1000));
TEST_ASSERT_EQUAL(steps, isr_counter);
// change to another color
color_byte = esp_random() & 0xFF;
memset(color_data, color_byte, color_size);
for (int i = 0; i < steps; i++) {
TEST_ESP_OK(esp_lcd_panel_io_tx_color(io_handle, -1, color_data + i * color_size_per_step, color_size_per_step));
}
vTaskDelay(pdMS_TO_TICKS(100));
TEST_ASSERT_EQUAL(steps * 2, isr_counter);
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
free(color_data);
}
TEST_CASE("lcd_parlio_send_colors_to_fixed_region(SPI)", "[lcd]")
{
lcd_parlio_send_colors_to_fixed_region(TEST_SPI_DATA_WIDTH);
}
#if SOC_PARLIO_SUPPORT_I80_LCD
TEST_CASE("lcd_parlio_send_colors_to_fixed_region(I80)", "[lcd]")
{
lcd_parlio_send_colors_to_fixed_region(TEST_I80_DATA_WIDTH);
}
#endif

View File

@ -0,0 +1,19 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: CC0-1.0
import pytest
from pytest_embedded import Dut
@pytest.mark.esp32p4
@pytest.mark.esp32h2
@pytest.mark.esp32c5
@pytest.mark.generic
@pytest.mark.parametrize(
'config',
[
'release',
],
indirect=True,
)
def test_parlio_lcd(dut: Dut) -> None:
dut.run_all_single_board_cases()

View File

@ -0,0 +1,5 @@
CONFIG_PM_ENABLE=y
CONFIG_FREERTOS_USE_TICKLESS_IDLE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y

View File

@ -0,0 +1,5 @@
# This file was generated using idf.py save-defconfig. It can be edited manually.
# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration
#
# CONFIG_ESP_TASK_WDT_INIT is not set
CONFIG_FREERTOS_HZ=1000

View File

@ -36,19 +36,19 @@ extern "C" {
#define TEST_LCD_PCLK_GPIO 2
#define TEST_LCD_DATA0_GPIO 4
#elif CONFIG_IDF_TARGET_ESP32P4
#define TEST_LCD_BK_LIGHT_GPIO 23
#define TEST_LCD_RST_GPIO 6
#define TEST_LCD_CS_GPIO 4
#define TEST_LCD_DC_GPIO 3
#define TEST_LCD_PCLK_GPIO 2
#define TEST_LCD_DATA0_GPIO 32
#define TEST_LCD_DATA1_GPIO 33
#define TEST_LCD_DATA2_GPIO 22
#define TEST_LCD_DATA3_GPIO 8
#define TEST_LCD_DATA4_GPIO 21
#define TEST_LCD_DATA5_GPIO 53
#define TEST_LCD_DATA6_GPIO 20
#define TEST_LCD_DATA7_GPIO 5
#define TEST_LCD_BK_LIGHT_GPIO 48
#define TEST_LCD_RST_GPIO 35
#define TEST_LCD_PCLK_GPIO 33
#define TEST_LCD_CS_GPIO 32
#define TEST_LCD_DC_GPIO 34
#define TEST_LCD_DATA0_GPIO 24
#define TEST_LCD_DATA1_GPIO 25
#define TEST_LCD_DATA2_GPIO 26
#define TEST_LCD_DATA3_GPIO 27
#define TEST_LCD_DATA4_GPIO 28
#define TEST_LCD_DATA5_GPIO 29
#define TEST_LCD_DATA6_GPIO 30
#define TEST_LCD_DATA7_GPIO 31
#else
#define TEST_LCD_BK_LIGHT_GPIO 18
#define TEST_LCD_RST_GPIO 5

View File

@ -959,6 +959,10 @@ config SOC_PARLIO_SUPPORT_SLEEP_RETENTION
bool
default y
config SOC_PARLIO_SUPPORT_SPI_LCD
bool
default y
config SOC_MPI_MEM_BLOCKS_NUM
int
default 4

View File

@ -381,6 +381,7 @@
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
#define SOC_PARLIO_TX_SIZE_BY_DMA 1 /*!< Transaction length is controlled by DMA instead of indicated by register */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */
/*--------------------------- MPI CAPS ---------------------------------------*/
#define SOC_MPI_MEM_BLOCKS_NUM (4)

View File

@ -935,6 +935,10 @@ config SOC_PARLIO_SUPPORT_SLEEP_RETENTION
bool
default y
config SOC_PARLIO_SUPPORT_SPI_LCD
bool
default y
config SOC_MPI_MEM_BLOCKS_NUM
int
default 4

View File

@ -354,6 +354,7 @@
#define SOC_PARLIO_RX_CLK_SUPPORT_OUTPUT 1 /*!< Support output RX clock to a GPIO */
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */
/*--------------------------- MPI CAPS ---------------------------------------*/
#define SOC_MPI_MEM_BLOCKS_NUM (4)

View File

@ -1343,6 +1343,14 @@ config SOC_PARLIO_SUPPORT_SLEEP_RETENTION
bool
default y
config SOC_PARLIO_SUPPORT_SPI_LCD
bool
default y
config SOC_PARLIO_SUPPORT_I80_LCD
bool
default y
config SOC_MPI_MEM_BLOCKS_NUM
int
default 4

View File

@ -480,6 +480,8 @@
#define SOC_PARLIO_TRANS_BIT_ALIGN 1 /*!< Support bit alignment in transaction */
#define SOC_PARLIO_TX_SIZE_BY_DMA 1 /*!< Transaction length is controlled by DMA instead of indicated by register */
#define SOC_PARLIO_SUPPORT_SLEEP_RETENTION 1 /*!< Support back up registers before sleep */
#define SOC_PARLIO_SUPPORT_SPI_LCD 1 /*!< Support to drive SPI interfaced LCD */
#define SOC_PARLIO_SUPPORT_I80_LCD 1 /*!< Support to drive I80 interfaced LCD */
/*--------------------------- MPI CAPS ---------------------------------------*/
#define SOC_MPI_MEM_BLOCKS_NUM (4)

View File

@ -135,6 +135,7 @@ USB_DOCS = ['api-reference/peripherals/usb_device.rst',
I80_LCD_DOCS = ['api-reference/peripherals/lcd/i80_lcd.rst']
RGB_LCD_DOCS = ['api-reference/peripherals/lcd/rgb_lcd.rst']
DSI_LCD_DOCS = ['api-reference/peripherals/lcd/dsi_lcd.rst']
PARLIO_LCD_DOCS = ['api-reference/peripherals/lcd/parl_lcd.rst']
# TODO: Merge this back with `USB_DOCS` IDF-9919 IDF-9920 IDF-9133
USB_OTG_DFU_DOCS = ['api-guides/dfu.rst']
@ -269,6 +270,7 @@ conditional_include_dict = {'SOC_BT_SUPPORTED':BT_DOCS,
'SOC_DEDICATED_GPIO_SUPPORTED':DEDIC_GPIO_DOCS,
'SOC_LCD_I80_SUPPORTED':I80_LCD_DOCS,
'SOC_LCD_RGB_SUPPORTED':RGB_LCD_DOCS,
'SOC_PARLIO_SUPPORT_SPI_LCD':PARLIO_LCD_DOCS,
'SOC_MIPI_DSI_SUPPORTED':DSI_LCD_DOCS,
'SOC_SPIRAM_SUPPORTED':SPIRAM_DOCS,
'SOC_PARLIO_SUPPORTED':PARLIO_DOCS,

View File

@ -179,6 +179,7 @@ INPUT = \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_io_i2c.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_io_i80.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_io_spi.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_io_parl.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_panel_io.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_panel_ops.h \
$(PROJECT_PATH)/components/esp_lcd/include/esp_lcd_panel_vendor.h \

View File

@ -32,6 +32,7 @@ This document will discuss how to create the control plane and data plane, as me
:SOC_LCD_I80_SUPPORTED: i80_lcd
:SOC_LCD_RGB_SUPPORTED: rgb_lcd
:SOC_MIPI_DSI_SUPPORTED: dsi_lcd
:SOC_PARLIO_SUPPORT_SPI_LCD: parl_lcd
.. note::
@ -84,6 +85,7 @@ Application Example
:SOC_LCD_RGB_SUPPORTED: * :example:`peripherals/lcd/rgb_panel` demonstrates how to install an RGB panel driver, display a scatter chart on the screen based on the LVGL library.
:SOC_I2C_SUPPORTED: * :example:`peripherals/lcd/i2c_oled` demonstrates how to use the SSD1306 panel driver from the `esp_lcd` component to facilitate the porting of LVGL library and display a scrolling text on the OLED screen.
:SOC_MIPI_DSI_SUPPORTED: * :example:`peripherals/lcd/mipi_dsi` demonstrates the general process of installing a MIPI DSI LCD driver, and displays a LVGL widget on the screen.
:SOC_PARLIO_SUPPORT_SPI_LCD: * :example:`peripherals/lcd/parlio_simulate` demonstrates how to use Parallel IO peripheral to drive an SPI or I80 Interfaced LCD.
API Reference

View File

@ -0,0 +1,76 @@
Parallel IO simulation of SPI or I80 Interfaced LCD
---------------------------------------------------
:link_to_translation:`zh_CN:[中文]`
Parallel IO is not a bus-type peripheral. The driver directly creates a Parallel IO device for the LCD. Currently the driver supports SPI (1 bit data width) and I80 (8 bit data width) modes.
#. Create Parallel IO device by :cpp:func:`esp_lcd_new_panel_io_parl`. You need to set up the following parameters for a Parallel IO device:
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_src` sets the clock source of the Parallel IO device. Note, the default clock source may be different between ESP targets.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_gpio_num` sets the GPIO number of the pixel clock (also referred as ``WR`` or ``SCLK`` in some LCD spec)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_gpio_num` sets the GPIO number of the data or command select pin (also referred as ``RS`` in some LCD spec)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::cs_gpio_num` sets the GPIO number of the chip select pin. (Note that the Parallel IO LCD driver only supports a single LCD device).
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` sets the bit width of the data bus (only support ``1`` or ``8``)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_gpio_nums` is the array of the GPIO number of the data bus. The number of GPIOs should be equal to the :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` value.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::max_transfer_bytes` sets the maximum number of bytes that can be transferred in one transaction.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dma_burst_size` sets the number of bytes transferred by dma burst.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::pclk_hz` sets the pixel clock frequency in Hz. Higher pixel clock frequency results in higher refresh rate, but may cause display abnormalities if the DMA bandwidth is not sufficient or the LCD controller chip does not support high pixel clock frequency.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_levels` sets the effective level for DC data selection and command selection.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_cmd_bits` and :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_param_bits` set the bit width of the command and parameter that recognized by the LCD controller chip. This is chip specific, you should refer to your LCD spec in advance.
- :cpp:member:`esp_lcd_panel_io_parl_config_t::trans_queue_depth` sets the maximum number of transactions that can be queued in the Parallel IO device. A bigger value means more transactions can be queued up, but it also consumes more memory.
.. code-block:: c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_parl_config_t io_config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.dc_gpio_num = EXAMPLE_PIN_NUM_DC,
.clk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0, // set DATA0 to drive SPI interfaced LCD or set DATA0~7 to drive I80 interfaced LCD
},
.data_width = 1, // set 1 to drive SPI interfaced LCD or set 8 to drive I80 interfaced LCD
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t), // transfer 100 lines of pixels (assume pixel is RGB565) at most in one transaction
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_cmd_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_parl(&io_config, io_handle));
.. only:: not SOC_PARLIO_SUPPORT_I80_LCD
.. note::
Due to hardware limitations, {IDF_TARGET_NAME} can not drive I80 interfaced LCD by Parallel IO.
#. Install the LCD controller driver. The LCD controller driver is responsible for sending the commands and parameters to the LCD controller chip. In this step, you need to specify the Parallel IO device handle that allocated in the last step, and some panel specific configurations:
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` sets the LCD's hardware reset GPIO number. If the LCD does not have a hardware reset pin, set this to ``-1``.
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` sets the RGB element order of each color data.
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` sets the bit width of the pixel color data. The LCD driver uses this value to calculate the number of bytes to send to the LCD controller chip.
- :cpp:member:`esp_lcd_panel_dev_config_t::data_endian` specifies the data endian to be transmitted to the screen. No need to specify for color data within one byte, like RGB232. For drivers that do not support specifying data endian, this field would be ignored.
.. code-block:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.bits_per_pixel = 16,
};
// Create LCD panel handle for ST7789, with the Parallel IO device handle
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
API Reference
-------------
.. include-build-file:: inc/esp_lcd_io_parl.inc

View File

@ -32,6 +32,7 @@ LCD 通常由两个主要平面组成:
:SOC_LCD_I80_SUPPORTED: i80_lcd
:SOC_LCD_RGB_SUPPORTED: rgb_lcd
:SOC_MIPI_DSI_SUPPORTED: dsi_lcd
:SOC_PARLIO_SUPPORT_SPI_LCD: parl_lcd
.. note::
@ -84,6 +85,7 @@ LCD 数据面板操作
:SOC_LCD_RGB_SUPPORTED: * :example:`peripherals/lcd/rgb_panel` 展示了如何安装 RGB 面板驱动程序,并基于 LVGL 库在屏幕上显示散点图。
:SOC_I2C_SUPPORTED: * :example:`peripherals/lcd/i2c_oled` 演示了如何使用 `esp_lcd` 组件中的 SSD1306 面板驱动来简化移植 LVGL 库,并在 OLED 屏幕上显示滚动文本。
:SOC_MIPI_DSI_SUPPORTED: * :example:`peripherals/lcd/mipi_dsi` 演示了如何安装 MIPI DSI LCD 驱动程序,并在屏幕上显示一个 LVGL 小部件。
:SOC_PARLIO_SUPPORT_SPI_LCD: * :example:`peripherals/lcd/parlio_simulate` 演示了如何使用 Parallel IO 外设驱动 SPI 或 I80 接口的屏幕。
API 参考

View File

@ -0,0 +1,76 @@
Parallel IO 模拟 SPI 或 I80 接口的 LCD
--------------------------------------------------------------
:link_to_translation:`en:[English]`
Parallel IO 并不是总线型的外设,驱动直接为 LCD 创建 Parallel IO 设备。目前驱动支持 SPI (1 bit 数据位宽) 和 I80 (8 bit 数据位宽 )模式。
#. 调用 :cpp:func:`esp_lcd_new_panel_io_parl` 创建 Parallel IO 设备。请设置以下参数:
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_src` 设置 Parallel IO 设备的时钟源。请注意,不同的 ESP 芯片可能有不同的默认时钟源。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::clk_gpio_num` 设置像素时钟的 GPIO 编号(在某些 LCD 规格书中也被称为 ``WR`` 或者 ``SCLK``
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_gpio_num` 设置数据或命令选择管脚的 GPIO 编号(在某些 LCD 规格书中也被称为 ``RS``
- :cpp:member:`esp_lcd_panel_io_parl_config_t::cs_gpio_num` 设置 CS 信号线的 GPIO 编号。注意Parallel IO LCD 驱动仅支持单个 LCD 设备)。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` 设置数据的位宽(仅支持 ``1`` 位或 ``8`` 位)
- :cpp:member:`esp_lcd_panel_io_parl_config_t::data_gpio_nums` 是数据总线的 GPIO 编号数组。GPIO 的数量应与 :cpp:member:`esp_lcd_panel_io_parl_config_t::data_width` 的值等同。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::max_transfer_bytes` 设置单次传输的最大字节数。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dma_burst_size` 设置 dma burst 传输的字节数。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::pclk_hz` 设置像素时钟的频率 (Hz)。较高的像素时钟频率会带来较高的刷新率,但如果 DMA 带宽不足或 LCD 控制器芯片不支持高像素时钟频率,则可能会导致显示异常。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::dc_levels` 设置 DC 数据选择和命令选择的有效电平。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::lcd_cmd_bits`:cpp:member:`esp_lcd_panel_io_spi_config_t::lcd_param_bits` 分别设置 LCD 控制器芯片可识别的命令及参数的位宽。不同芯片对位宽要求不同,请提前参阅 LCD 规格书。
- :cpp:member:`esp_lcd_panel_io_parl_config_t::trans_queue_depth` 设置 Parallel IO 传输队列的深度。该值越大,可以排队的传输越多,但消耗的内存也越多。
.. code-block:: c
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_parl_config_t io_config = {
.clk_src = PARLIO_CLK_SRC_DEFAULT,
.dc_gpio_num = EXAMPLE_PIN_NUM_DC,
.clk_gpio_num = EXAMPLE_PIN_NUM_PCLK,
.data_gpio_nums = {
EXAMPLE_PIN_NUM_DATA0, // 驱动 SPI 接口的 LCD 时需要设置 DATA0驱动 I80 接口的 LCD 时需要设置 DATA0~7
},
.data_width = 1, // 驱动 SPI 接口的 LCD 时数据宽度为 1驱动 I80 接口的 LCD 时数据宽度为 8
.max_transfer_bytes = EXAMPLE_LCD_H_RES * 100 * sizeof(uint16_t), // 单次最多可传输 100 行像素(假设像素格式为 RGB565
.dma_burst_size = EXAMPLE_DMA_BURST_SIZE,
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
.trans_queue_depth = 10,
.dc_levels = {
.dc_cmd_level = 0,
.dc_data_level = 1,
},
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_parl(&io_config, io_handle));
.. only:: not SOC_PARLIO_SUPPORT_I80_LCD
.. note::
注意,由于硬件限制,{IDF_TARGET_NAME} 不能通过 Parallel IO 模拟驱动 I80 接口 LCD。
#. 安装 LCD 控制器驱动程序。LCD 控制器驱动程序负责向 LCD 控制器芯片发送命令和参数。在此步骤中,需要指定上一步骤中分配到的 Parallel IO 设备句柄以及一些面板特定配置:
- :cpp:member:`esp_lcd_panel_dev_config_t::reset_gpio_num` 设置 LCD 的硬件复位 GPIO 编号。如果 LCD 没有硬件复位管脚,则将此设置为 ``-1``
- :cpp:member:`esp_lcd_panel_dev_config_t::rgb_ele_order` 设置每个颜色数据的 RGB 元素顺序。
- :cpp:member:`esp_lcd_panel_dev_config_t::bits_per_pixel` 设置像素颜色数据的位宽。LCD 驱动程序使用此值计算要发送到 LCD 控制器芯片的字节数。
- :cpp:member:`esp_lcd_panel_dev_config_t::data_endian` 指定传输到屏幕的数据的字节序。不超过一字节的颜色格式(如 RGB232不需要指定数据字节序。若驱动程序不支持指定数据字节序则将忽略此字段。
.. code-block:: c
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_PIN_NUM_RST,
.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR,
.bits_per_pixel = 16,
};
// 为 ST7789 创建 LCD 面板句柄,并指定 Parallel IO 设备句柄
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &panel_handle));
API 参考
--------
.. include-build-file:: inc/esp_lcd_io_parl.inc