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
+4
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()
@@ -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;
@@ -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
@@ -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" {
@@ -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;
}
@@ -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
@@ -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
@@ -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)
@@ -0,0 +1,4 @@
| Supported Targets | ESP32-C5 | ESP32-H2 | ESP32-P4 |
| ----------------- | -------- | -------- | -------- |
This test app is used to test LCDs with intel 8080 interface.
@@ -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)
@@ -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();
}
@@ -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
@@ -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
@@ -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()
@@ -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
@@ -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
@@ -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