Merge branch 'feature/lcd_driver' into 'master'

esp_lcd component panel driver

Closes IDF-2933 and IDF-2934

See merge request espressif/esp-idf!12813
This commit is contained in:
Michael (XIAO Xufeng) 2021-05-13 04:29:07 +00:00
commit e941bc838c
36 changed files with 3910 additions and 13 deletions

View File

@ -84,6 +84,7 @@
/components/esp_https_server/ @esp-idf-codeowners/app-utilities
/components/esp_hw_support/ @esp-idf-codeowners/system
/components/esp_ipc/ @esp-idf-codeowners/system
/components/esp_lcd/ @esp-idf-codeowners/peripherals
/components/esp_local_ctrl/ @esp-idf-codeowners/app-utilities
/components/esp_netif/ @esp-idf-codeowners/network
/components/esp_pm/ @esp-idf-codeowners/power-management

View File

@ -412,6 +412,25 @@ err:
return ret;
}
esp_err_t gdma_reset(gdma_channel_handle_t dma_chan)
{
esp_err_t ret = ESP_OK;
gdma_pair_t *pair = NULL;
gdma_group_t *group = NULL;
ESP_GOTO_ON_FALSE(dma_chan, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
pair = dma_chan->pair;
group = pair->group;
if (dma_chan->direction == GDMA_CHANNEL_DIRECTION_RX) {
gdma_ll_rx_reset_channel(group->hal.dev, pair->pair_id);
} else {
gdma_ll_tx_reset_channel(group->hal.dev, pair->pair_id);
}
err:
return ret;
}
static void gdma_uninstall_group(gdma_group_t *group)
{
int group_id = group->group_id;

View File

@ -155,6 +155,7 @@ esp_err_t gdma_new_channel(const gdma_channel_alloc_config_t *config, gdma_chann
* @brief Connect GDMA channel to trigger peripheral
*
* @note Suggest to use helper macro `GDMA_MAKE_TRIGGER` to construct parameter `trig_periph`. e.g. GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_SHA,0)
* @note Connecting to a peripheral will also reset the DMA FIFO and FSM automatically
*
* @param[in] dma_chan GDMA channel handle, allocated by `gdma_new_channel`
* @param[in] trig_periph GDMA trigger peripheral
@ -279,6 +280,18 @@ esp_err_t gdma_stop(gdma_channel_handle_t dma_chan);
*/
esp_err_t gdma_append(gdma_channel_handle_t dma_chan);
/**
* @brief Reset DMA channel FIFO and internal finite state machine
* @note Resetting a DMA channel won't break the connection with the target peripheral
*
* @param[in] dma_chan GDMA channel handle, allocated by `gdma_new_channel`
* @return
* - ESP_OK: DMA channel reset successfully
* - ESP_ERR_INVALID_ARG: DMA channel reset failed due to invalid arguments
* - ESP_FAIL: DMA channel reset failed due to other errors
*/
esp_err_t gdma_reset(gdma_channel_handle_t dma_chan);
#ifdef __cplusplus
}
#endif

View File

@ -61,6 +61,28 @@ static void async_memcpy_verify_and_clear_testbench(uint32_t seed, uint32_t buff
free(dst_buf);
}
TEST_CASE("memory copy the same buffer with different content", "[async mcp]")
{
async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG();
config.backlog = 1;
async_memcpy_t driver = NULL;
TEST_ESP_OK(esp_async_memcpy_install(&config, &driver));
uint8_t sbuf[256] = {0};
uint8_t dbuf[256] = {0};
for (int j = 0; j < 20; j++) {
TEST_ESP_OK(esp_async_memcpy(driver, dbuf, sbuf, 256, NULL, NULL));
for (int i = 0; i < 256; i++) {
if (sbuf[i] != dbuf[i]) {
printf("location[%d]:s=%d,d=%d\r\n", i, sbuf[i], dbuf[i]);
TEST_FAIL_MESSAGE("destination data doesn't match source data");
} else {
sbuf[i] += 1;
}
}
}
TEST_ESP_OK(esp_async_memcpy_uninstall(driver));
}
TEST_CASE("memory copy by DMA one by one", "[async mcp]")
{
async_memcpy_config_t config = ASYNC_MEMCPY_DEFAULT_CONFIG();

View File

@ -0,0 +1,14 @@
set(srcs "src/esp_lcd_common.c"
"src/esp_lcd_panel_io.c"
"src/esp_lcd_panel_io_i2c.c"
"src/esp_lcd_panel_io_spi.c"
"src/esp_lcd_panel_io_i80.c"
"src/esp_lcd_panel_ssd1306.c"
"src/esp_lcd_panel_st7789.c"
"src/esp_lcd_panel_ops.c"
"src/esp_lcd_rgb_panel.c")
set(includes "include" "interface")
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS ${includes}
PRIV_INCLUDE_DIRS ${priv_includes})

View File

@ -0,0 +1,20 @@
menu "LCD and Touch Panel"
menu "LCD Peripheral Configuration"
depends on IDF_TARGET_ESP32S3
choice LCD_PERIPH_CLK_SRC
prompt "Select clock source for LCD peripheral"
default LCD_PERIPH_CLK_SRC_XTAL if PM_ENABLE
default LCD_PERIPH_CLK_SRC_PLL160M
help
The peripheral clock is where LCD bus clock derives from.
Each clock source has its unique feature, e.g.
1. XTAL clock can help LCD work stable when DFS is enabled
2. PLL160M can achieve higher pixel clock resolution
config LCD_PERIPH_CLK_SRC_PLL160M
bool "PLL_160M clock"
config LCD_PERIPH_CLK_SRC_XTAL
bool "XTAL clock"
endchoice # LCD_PERIPH_CLK_SRC
endmenu
endmenu

View File

@ -0,0 +1,6 @@
#
# Component Makefile
#
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_PRIV_INCLUDEDIRS := interface
COMPONENT_SRCDIRS := src

View File

@ -0,0 +1,201 @@
/*
* SPDX-FileCopyrightText: 2021 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"
#ifdef __cplusplus
extern "C" {
#endif
typedef void *esp_lcd_spi_bus_handle_t; /*!< Type of LCD SPI bus handle */
typedef void *esp_lcd_i2c_bus_handle_t; /*!< Type of LCD I2C bus handle */
typedef struct esp_lcd_i80_bus_t *esp_lcd_i80_bus_handle_t; /*!< Type of LCD intel 8080 bus handle */
/**
* @brief Transmit LCD command and corresponding parameters
*
* @note Commands sent by this function are short, so they are sent using polling transactions.
* The function does not return before the command tranfer is completed.
* If any queued transactions sent by `esp_lcd_panel_io_tx_color()` are still pending when this function is called,
* this function will wait until they are finished and the queue is empty before sending the command(s).
*
* @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
* @param[in] lcd_cmd The specific LCD command
* @param[in] lcd_cmd_bits Length of LCD command, in bits (e.g. 8 bits or 16 bits)
* @param[in] param Buffer that holds the command specific parameters, set to NULL if no parameter is needed for the command
* @param[in] param_size Size of `param` in memory, in bytes, set to zero if no parameter is needed for the command
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_io_tx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size);
/**
* @brief Transmit LCD RGB data
*
* @note This function will package the command and RGB data into a transaction, and push into a queue.
* The real transmission is performed in the background (DMA+interrupt).
* The caller should take care of the lifecycle of the `color` buffer.
* Recycling of color buffer should be done in the callback `on_color_trans_done()`.
*
* @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
* @param[in] lcd_cmd The specific LCD command
* @param[in] lcd_cmd_bits Length of LCD command, in bits (e.g. 8 bits or 16 bits)
* @param[in] color Buffer that holds the RGB color data
* @param[in] color_size Size of `color` in memory, in bytes
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_io_tx_color(esp_lcd_panel_io_handle_t io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size);
/**
* @brief Destory LCD panel IO handle (deinitialize panel and free all corresponding resource)
*
* @param[in] io LCD panel IO handle, which is created by factory API like `esp_lcd_new_panel_io_spi()`
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io);
/**
* @brief Panel IO configuration structure, for SPI interface
*/
typedef struct {
int cs_gpio_num; /*!< GPIO used for CS line */
int dc_gpio_num; /*!< GPIO used to select the D/C line, set this to -1 if the D/C line not controlled by manually pulling high/low GPIO */
int spi_mode; /*!< Traditional SPI mode (0~3) */
unsigned int pclk_hz; /*!< Frequency of pixel clock */
size_t trans_queue_depth; /*!< Size of internal transaction queue */
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); /*!< Callback, invoked when color data transfer has finished */
void *user_data; /*!< User private data, passed directly to on_trans_frame_done's user_data */
struct {
unsigned int dc_as_cmd_phase: 1; /*!< D/C line value is encoded into SPI transaction command phase */
unsigned int dc_low_on_data: 1; /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */
} flags;
} esp_lcd_panel_io_spi_config_t;
/**
* @brief Create LCD panel IO handle, for SPI interface
*
* @param[in] bus SPI bus handle
* @param[in] io_config IO configuration, for SPI interface
* @param[out] ret_io Returned IO handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
typedef struct {
uint32_t dev_addr; /*!< I2C device address */
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); /*!< Callback, invoked when color data transfer has finished */
void *user_data; /*!< User private data, passed directly to on_trans_frame_done's user_data */
size_t control_phase_bytes; /*!< I2C LCD panel will encode control information (e.g. D/C seclection) into control phase, in several bytes */
unsigned int dc_bit_offset; /*!< Offset of the D/C selection bit in control phase */
struct {
unsigned int dc_low_on_data: 1; /*!< If this flag is enabled, DC line = 0 means transfer data, DC line = 1 means transfer command; vice versa */
} flags;
} esp_lcd_panel_io_i2c_config_t;
/**
* @brief Create LCD panel IO handle, for I2C interface
*
* @param[in] bus I2C bus handle
* @param[in] io_config IO configuration, for I2C interface
* @param[out] ret_io Returned IO handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_panel_io_i2c(esp_lcd_i2c_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
#if SOC_LCD_I80_SUPPORTED
/**
* @brief LCD Intel 8080 bus configuration structure
*/
typedef struct {
int dc_gpio_num; /*!< GPIO used for D/C line */
int wr_gpio_num; /*!< GPIO used for WR line */
int data_gpio_nums[SOC_LCD_I80_BUS_WIDTH]; /*!< GPIOs used for data lines */
size_t data_width; /*!< Number of data lines, 8 or 16 */
size_t max_transfer_bytes; /*!< Maximum transfer size, this determines the length of internal DMA link */
} esp_lcd_i80_bus_config_t;
/**
* @brief Create Intel 8080 bus handle
*
* @param[in] bus_config Bus configuration
* @param[out] ret_bus Returned bus handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_ERR_NOT_FOUND if no free bus is available
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus);
/**
* @brief Destory Intel 8080 bus handle
*
* @param[in] bus Intel 8080 bus handle, created by `esp_lcd_new_i80_bus()`
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_INVALID_STATE if there still be some device attached to the bus
* - ESP_OK on success
*/
esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus);
/**
* @brief Panel IO configuration structure, for intel 8080 interface
*/
typedef struct {
int cs_gpio_num; /*!< GPIO used for CS line */
unsigned int pclk_hz; /*!< Frequency of pixel clock */
size_t trans_queue_depth; /*!< Transaction queue size, larger queue, higher throughput */
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); /*!< Callback, invoked when color data was tranferred done */
void *user_data; /*!< User private data, passed directly to on_trans_done's user_data */
struct {
unsigned int dc_idle_level: 1; /*!< Level of DC line in IDLE phase */
unsigned int dc_cmd_level: 1; /*!< Level of DC line in CMD phase */
unsigned int dc_dummy_level: 1; /*!< Level of DC line in DUMMY phase */
unsigned int dc_data_level: 1; /*!< Level of DC line in DATA phase */
} dc_levels; /*!< Each i80 device might have its own D/C control logic */
struct {
unsigned int invert_cs: 1; /*!< Whether to invert the CS line */
unsigned int reverse_color_bits: 1; /*!< Reverse the data bits, D[N:0] -> D[0:N] */
unsigned int swap_color_bytes: 1; /*!< Swap adjacent two color bytes */
unsigned int pclk_active_neg: 1; /*!< The display will write data lines when there's a falling edge on WR signal (a.k.a the PCLK) */
unsigned int pclk_idle_low: 1; /*!< The WR signal (a.k.a the PCLK) stays at low level in IDLE phase */
} flags;
} esp_lcd_panel_io_i80_config_t;
/**
* @brief Create LCD panel IO, for Intel 8080 interface
*
* @param[in] bus Intel 8080 bus handle, created by `esp_lcd_new_i80_bus()`
* @param[in] io_config IO configuration, for i80 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_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io);
#endif // SOC_LCD_I80_SUPPORTED
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2021 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"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Reset LCD panel
*
* @note Panel reset must be called before attempting to initialize the panel using `esp_lcd_panel_init()`.
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel);
/**
* @brief Initialize LCD panel
*
* @note Before calling this function, make sure the LCD panel has finished the `reset` stage by `esp_lcd_panel_reset()`.
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel);
/**
* @brief Deinitialize the LCD panel
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel);
/**
* @brief Draw bitmap on LCD panel
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] x_start Start index on x-axis (x_start included)
* @param[in] y_start Start index on y-axis (y_start included)
* @param[in] x_end End index on x-axis (x_end not included)
* @param[in] y_end End index on y-axis (y_end not included)
* @param[in] color_data RGB color data that will be dumped to the specific window range
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
/**
* @brief Mirror the LCD panel on specific axis
*
* @note Combined with `esp_lcd_panel_swap_xy()`, one can realize screen rotation
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] mirror_x Whether the panel will be mirrored about the x axis
* @param[in] mirror_y Whether the panel will be mirrored about the y axis
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
*/
esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y);
/**
* @brief Swap/Exchange x and y axis
*
* @note Combined with `esp_lcd_panel_mirror()`, one can realize screen rotation
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] swap_axes Whether to swap the x and y axis
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
*/
esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes);
/**
* @brief Set extra gap in x and y axis
*
* The gap is the space (in pixels) between the left/top sides of the LCD panel and the first row/column respectively of the actual contents displayed.
*
* @note Setting a gap is useful when positioning or centering a frame that is smaller than the LCD.
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] x_gap Extra gap on x axis, in pixels
* @param[in] y_gap Extra gap on y axis, in pixels
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap);
/**
* @brief Invert the color (bit-wise invert the color data line)
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] invert_color_data Whether to invert the color data
* @return
* - ESP_OK on success
*/
esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data);
/**
* @brief Turn off the display
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] off Whether to turn off the screen
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
*/
esp_err_t esp_lcd_panel_disp_off(esp_lcd_panel_handle_t panel, bool off);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,77 @@
/*
* SPDX-FileCopyrightText: 2021 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"
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_LCD_RGB_SUPPORTED
/**
* @brief LCD RGB timing structure
*/
typedef struct {
unsigned int pclk_hz; /*!< Frequency of pixel clock */
unsigned int h_res; /*!< Horizontal resolution, i.e. the number of pixels in a line */
unsigned int v_res; /*!< Vertical resolution, i.e. the number of lines in the frame */
unsigned int hsync_pulse_width; /*!< Horizontal sync width, unit: PCLK period */
unsigned int hsync_back_porch; /*!< Horizontal back porch, number of PCLK between hsync and start of line active data */
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 */
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 */
unsigned int de_idle_high: 1; /*!< The de signal is high in IDLE state */
unsigned int pclk_active_neg: 1; /*!< The display will write data lines when there's a falling edge on PCLK */
unsigned int pclk_idle_low: 1; /*!< The PCLK stays at low level in IDLE phase */
} flags;
} esp_lcd_rgb_timing_t;
/**
* @brief LCD RGB panel configuration structure
*/
typedef struct {
esp_lcd_rgb_timing_t timings; /*!< RGB timing parameters */
size_t data_width; /*!< Number of data lines */
int hsync_gpio_num; /*!< GPIO used for HSYNC signal */
int vsync_gpio_num; /*!< GPIO used for VSYNC signal */
int de_gpio_num; /*!< GPIO used for DE signal, set to -1 if it's not used */
int pclk_gpio_num; /*!< GPIO used for PCLK signal */
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; /*!< GPIOs used for data lines */
int disp_gpio_num; /*!< GPIO used for display control signal, set to -1 if it's not used */
bool (*on_frame_trans_done)(esp_lcd_panel_handle_t panel, void *user_data); /*!< Callback, invoked when one frame buffer has transferred done */
void *user_data; /*!< User data which would be passed to on_frame_trans_done's user_data */
struct {
unsigned int disp_active_low: 1; /*!< If this flag is enabled, a low level of display control signal can turn the screen on; vice versa */
unsigned int relax_on_idle: 1; /*!< If this flag is enabled, the host won't refresh the LCD if nothing changed in host's frame buffer (this is usefull for LCD with built-in GRAM) */
} flags;
} esp_lcd_rgb_panel_config_t;
/**
* @brief Create RGB LCD panel
*
* @param rgb_panel_config RGB panel configuration
* @param ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_ERR_NOT_FOUND if no free RGB panel is available
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel);
#endif // SOC_LCD_RGB_SUPPORTED
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2021 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"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Configuration structure for panel device
*/
typedef struct {
int reset_gpio_num; /*!< GPIO used to reset the LCD panel, set to -1 if it's not used */
esp_lcd_color_space_t color_space; /*!< Set the color space used by the LCD panel */
unsigned int bits_per_pixel; /*!< Color depth, in bpp */
struct {
unsigned int reset_active_high: 1; /*!< Setting this if the panel reset is high level active */
} flags;
void *vendor_config; /* vendor specific configuration, optional, left as NULL if not used */
} esp_lcd_panel_dev_config_t;
/**
* @brief Create LCD panel for model ST7789
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config general panel device configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
/**
* @brief Create LCD panel for model SSD1306
*
* @param[in] io LCD panel IO handle
* @param[in] panel_dev_config general panel device configuration
* @param[out] ret_panel Returned LCD panel handle
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_ERR_NO_MEM if out of memory
* - ESP_OK on success
*/
esp_err_t esp_lcd_new_panel_ssd1306(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
typedef struct esp_lcd_panel_io_t *esp_lcd_panel_io_handle_t; /*!< Type of LCD panel IO handle */
typedef struct esp_lcd_panel_t *esp_lcd_panel_handle_t; /*!< Type of LCD panel handle */
/**
* @brief LCD color space type definition
*/
typedef enum {
ESP_LCD_COLOR_SPACE_RGB, /*!< Color space: RGB */
ESP_LCD_COLOR_SPACE_BGR, /*!< Color space: BGR */
ESP_LCD_COLOR_SPACE_MONOCHROME, /*!< Color space: monochrome */
} esp_lcd_color_space_t;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct esp_lcd_panel_t esp_lcd_panel_t; /*!< Type of LCD panel */
/**
* @brief LCD panel interface
*/
struct esp_lcd_panel_t {
/**
* @brief Reset LCD panel
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @return
* - ESP_OK on success
*/
esp_err_t (*reset)(esp_lcd_panel_t *panel);
/**
* @brief Initialize LCD panel
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @return
* - ESP_OK on success
*/
esp_err_t (*init)(esp_lcd_panel_t *panel);
/**
* @brief Destory LCD panel
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @return
* - ESP_OK on success
*/
esp_err_t (*del)(esp_lcd_panel_t *panel);
/**
* @brief Draw bitmap on LCD panel
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] x_start Start index on x-axis (x_start included)
* @param[in] y_start Start index on y-axis (y_start included)
* @param[in] x_end End index on x-axis (x_end not included)
* @param[in] y_end End index on y-axis (y_end not included)
* @param[in] color_data RGB color data that will be dumped to the specific window range
* @return
* - ESP_OK on success
*/
esp_err_t (*draw_bitmap)(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
/**
* @brief Mirror the LCD panel on specific axis
*
* @note Combine this function with `swap_xy`, one can realize screen rotatation
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] x_axis Whether the panel will be mirrored about the x_axis
* @param[in] y_axis Whether the panel will be mirrored about the y_axis
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
*/
esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
/**
* @brief Swap/Exchange x and y axis
*
* @note Combine this function with `mirror`, one can realize screen rotatation
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] swap_axes Whether to swap the x and y axis
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
*/
esp_err_t (*swap_xy)(esp_lcd_panel_t *panel, bool swap_axes);
/**
* @brief Set extra gap in x and y axis
*
* @note The gap is only used for calculating the real coordinates.
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] x_gap Extra gap on x axis, in pixels
* @param[in] y_gap Extra gap on y axis, in pixels
* @return
* - ESP_OK on success
*/
esp_err_t (*set_gap)(esp_lcd_panel_t *panel, int x_gap, int y_gap);
/**
* @brief Invert the color (bit 1 -> 0 for color data line, and vice versa)
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] invert_color_data Whether to invert the color data
* @return
* - ESP_OK on success
*/
esp_err_t (*invert_color)(esp_lcd_panel_t *panel, bool invert_color_data);
/**
* @brief Turn off the display
*
* @param[in] panel LCD panel handle, which is created by other factory API like `esp_lcd_new_panel_st7789()`
* @param[in] off Whether to turn off the screen
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if this function is not supported by the panel
*/
esp_err_t (*disp_off)(esp_lcd_panel_t *panel, bool off);
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,66 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdbool.h>
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct esp_lcd_panel_io_t esp_lcd_panel_io_t; /*!< Type of LCD panel IO */
/**
* @brief LCD panel IO interface
*/
struct esp_lcd_panel_io_t {
/**
* @brief Transmit LCD command and corresponding parameters
*
* @note This is the panel-specific interface called by function `esp_lcd_panel_io_tx_param()`.
*
* @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
* @param[in] lcd_cmd The specific LCD command
* @param[in] lcd_cmd_bits Length of LCD command, in bits (e.g. 8 bits or 16 bits)
* @param[in] param Buffer that holds the command specific parameters, set to NULL if no parameter is needed for the command
* @param[in] param_size Size of `param` in memory, in bytes, set to zero if no parameter is needed for the command
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t (*tx_param)(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size);
/**
* @brief Transmit LCD RGB data
*
* @note This is the panel-specific interface called by function `esp_lcd_panel_io_tx_color()`.
*
* @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
* @param[in] lcd_cmd The specific LCD command
* @param[in] lcd_cmd_bits Length of LCD command, in bits (e.g. 8 bits or 16 bits)
* @param[in] color Buffer that holds the RGB color data
* @param[in] color_size Size of `color` in memory, in bytes
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t (*tx_color)(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size);
/**
* @brief Destory LCD panel IO handle (deinitialize all and free resource)
*
* @param[in] io LCD panel IO handle, which is created by other factory API like `esp_lcd_new_panel_io_spi()`
* @return
* - ESP_ERR_INVALID_ARG if parameter is invalid
* - ESP_OK on success
*/
esp_err_t (*del)(esp_lcd_panel_io_t *io);
};
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,126 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "freertos/FreeRTOS.h"
#include "soc/rtc.h" // for querying XTAL clock
#include "soc/soc_caps.h"
#if SOC_LCDCAM_SUPPORTED
#include "esp_lcd_common.h"
#include "hal/lcd_ll.h"
#include "hal/lcd_hal.h"
typedef struct esp_lcd_platform_t {
portMUX_TYPE spinlock; // spinlock used to protect platform level resources
union {
void *panels[SOC_LCD_RGB_PANELS]; // array of RGB LCD panel instances
void *buses[SOC_LCD_I80_BUSES]; // array of i80 bus instances
}; // LCD peripheral can only work under either RGB mode or intel 8080 mode
} esp_lcd_platform_t;
esp_lcd_platform_t s_lcd_platform = {
.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED,
.buses = {} // initially the bus slots and panel slots are empty
};
int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj)
{
int member_id = -1;
switch (device_type) {
case LCD_COM_DEVICE_TYPE_I80:
// search for a bus slot then register to platform
for (int i = 0; (i < SOC_LCD_I80_BUSES) && (member_id == -1); i++) {
portENTER_CRITICAL(&s_lcd_platform.spinlock);
if (!s_lcd_platform.buses[i]) {
s_lcd_platform.buses[i] = device_obj;
member_id = i;
}
portEXIT_CRITICAL(&s_lcd_platform.spinlock);
}
break;
case LCD_COM_DEVICE_TYPE_RGB:
// search for a panel slot then register to platform
for (int i = 0; (i < SOC_LCD_RGB_PANELS) && (member_id == -1); i++) {
portENTER_CRITICAL(&s_lcd_platform.spinlock);
if (!s_lcd_platform.panels[i]) {
s_lcd_platform.panels[i] = device_obj;
member_id = i;
}
portEXIT_CRITICAL(&s_lcd_platform.spinlock);
}
break;
default:
break;
}
return member_id;
}
void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id)
{
switch (device_type) {
case LCD_COM_DEVICE_TYPE_I80:
portENTER_CRITICAL(&s_lcd_platform.spinlock);
if (s_lcd_platform.buses[member_id]) {
s_lcd_platform.buses[member_id] = NULL;
}
portEXIT_CRITICAL(&s_lcd_platform.spinlock);
break;
case LCD_COM_DEVICE_TYPE_RGB:
portENTER_CRITICAL(&s_lcd_platform.spinlock);
if (s_lcd_platform.panels[member_id]) {
s_lcd_platform.panels[member_id] = NULL;
}
portEXIT_CRITICAL(&s_lcd_platform.spinlock);
break;
default:
break;
}
}
unsigned long lcd_com_select_periph_clock(lcd_hal_context_t *hal)
{
unsigned long resolution_hz = 0;
int clock_source = -1;
#if CONFIG_LCD_PERIPH_CLK_SRC_PLL160M
resolution_hz = 160000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
clock_source = LCD_LL_CLOCK_SRC_PLL160M;
#elif CONFIG_LCD_PERIPH_CLK_SRC_XTAL
resolution_hz = rtc_clk_xtal_freq_get() * 1000000 / LCD_PERIPH_CLOCK_PRE_SCALE;
clock_source = LCD_LL_CLOCK_SRC_XTAL;
#else
#error "invalid LCD peripheral clock source"
#endif
lcd_ll_set_group_clock_src(hal->dev, clock_source, LCD_PERIPH_CLOCK_PRE_SCALE, 1, 0);
return resolution_hz;
}
void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len)
{
size_t prepared_length = 0;
uint8_t *data = (uint8_t *)buffer;
dma_descriptor_t *desc = desc_head;
while (len > DMA_DESCRIPTOR_BUFFER_MAX_SIZE) {
desc->dw0.suc_eof = 0; // not the end of the transaction
desc->dw0.size = DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
desc->dw0.length = DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc->buffer = &data[prepared_length];
desc = desc->next; // move to next descriptor
prepared_length += DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
len -= DMA_DESCRIPTOR_BUFFER_MAX_SIZE;
}
if (len) {
desc->dw0.suc_eof = 1; // end of the transaction
desc->dw0.size = len;
desc->dw0.length = len;
desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA;
desc->buffer = &data[prepared_length];
desc = desc->next; // move to next descriptor
prepared_length += len;
}
}
#endif // SOC_LCDCAM_SUPPORTED

View File

@ -0,0 +1,70 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "soc/soc_caps.h"
#if SOC_LCDCAM_SUPPORTED
#include "hal/lcd_hal.h"
#include "hal/dma_types.h"
#else
#error "lcd peripheral is not supported on this chip"
#endif
#ifdef __cplusplus
extern "C" {
#endif
#if SOC_LCDCAM_SUPPORTED
#define LCD_PERIPH_CLOCK_PRE_SCALE (2) // This is the minimum divider that can be applied to LCD peripheral
typedef enum {
LCD_COM_DEVICE_TYPE_I80,
LCD_COM_DEVICE_TYPE_RGB
} lcd_com_device_type_t;
/**
* @brief Register a LCD device to platform
*
* @param device_type Device type, refer to lcd_com_device_type_t
* @param device_obj Device object
* @return >=0: member_id, <0: no free lcd bus/panel slots
*/
int lcd_com_register_device(lcd_com_device_type_t device_type, void *device_obj);
/**
* @brief Remove a device from platform
*
* @param device_type Device type, refer to lcd_com_device_type_t
* @param member_id member ID
*/
void lcd_com_remove_device(lcd_com_device_type_t device_type, int member_id);
/**
* @brief Select clock source and return peripheral clock resolution (in Hz)
*
* @note The clock source selection is injected by the Kconfig system,
* dynamic switching peripheral clock source is not supported in driver.
*
* @param hal HAL object
* @return Peripheral clock resolution, in Hz
*/
unsigned long lcd_com_select_periph_clock(lcd_hal_context_t *hal);
/**
* @brief Mount data to DMA descriptors
*
* @param desc_head Point to the head of DMA descriptor chain
* @param buffer Data buffer
* @param len Size of the data buffer, in bytes
*/
void lcd_com_mount_dma_data(dma_descriptor_t *desc_head, const void *buffer, size_t len);
#endif // SOC_LCDCAM_SUPPORTED
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
/* Common LCD panel commands */
#define LCD_CMD_NOP 0x00 // This command is empty command
#define LCD_CMD_SWRESET 0x01 // Software reset registers (the built-in frame buffer is not affected)
#define LCD_CMD_RDDID 0x04 // Read 24-bit display ID
#define LCD_CMD_RDDST 0x09 // Read display status
#define LCD_CMD_RDDPM 0x0A // Read display power mode
#define LCD_CMD_RDD_MADCTL 0x0B // Read display MADCTL
#define LCD_CMD_RDD_COLMOD 0x0C // Read display pixel format
#define LCD_CMD_RDDIM 0x0D // Read display image mode
#define LCD_CMD_RDDSM 0x0E // Read display signal mode
#define LCD_CMD_RDDSR 0x0F // Read display self-diagnostic result
#define LCD_CMD_SLPIN 0x10 // Go into sleep mode (DC/DC, oscillator, scanning stopped, but memory keeps content)
#define LCD_CMD_SLPOUT 0x11 // Exit sleep mode
#define LCD_CMD_PTLON 0x12 // Turns on partial display mode
#define LCD_CMD_NORON 0x13 // Turns on normal display mode
#define LCD_CMD_INVOFF 0x20 // Recover from display inversion mode
#define LCD_CMD_INVON 0x21 // Go into display inversion mode
#define LCD_CMD_GAMSET 0x26 // Select Gamma curve for current display
#define LCD_CMD_DISPOFF 0x28 // Display off (disable frame buffer output)
#define LCD_CMD_DISPON 0x29 // Display on (enable frame buffer output)
#define LCD_CMD_CASET 0x2A // Set column address
#define LCD_CMD_RASET 0x2B // Set row address
#define LCD_CMD_RAMWR 0x2C // Write frame memory
#define LCD_CMD_RAMRD 0x2E // Read frame memory
#define LCD_CMD_PTLAR 0x30 // Define the partial area
#define LCD_CMD_VSCRDEF 0x33 // Vertical scrolling definition
#define LCD_CMD_TEOFF 0x34 // Turns of tearing effect
#define LCD_CMD_TEON 0x35 // Turns on tearing effect
#define LCD_CMD_MADCTL 0x36 // Memory data access control
#define LCD_CMD_MH_BIT (1 << 2) // Display data latch order, 0: refresh left to right, 1: refresh right to left
#define LCD_CMD_BGR_BIT (1 << 3) // RGB/BGR order, 0: RGB, 1: BGR
#define LCD_CMD_ML_BIT (1 << 4) // Line address order, 0: refresh top to bottom, 1: refresh bottom to top
#define LCD_CMD_MV_BIT (1 << 5) // Row/Column order, 0: normal mode, 1: reverse mode
#define LCD_CMD_MX_BIT (1 << 6) // Column address order, 0: left to right, 1: right to left
#define LCD_CMD_MY_BIT (1 << 7) // Row address order, 0: top to bottom, 1: bottom to top
#define LCD_CMD_VSCSAD 0x37 // Vertical scroll start address
#define LCD_CMD_IDMOFF 0x38 // Recover from IDLE mode
#define LCD_CMD_IDMON 0x39 // Fall into IDLE mode (8 color depth is displayed)
#define LCD_CMD_COLMOD 0x3A // Defines the format of RGB picture data, which is to be transferred via the MCU interface
#define LCD_CMD_RAMWRC 0x3C // Memory write continue
#define LCD_CMD_RAMRDC 0x3E // Memory read continue
#define LCD_CMD_STE 0x44 // Set tear scanline, tearing effect output signal when display module reaches line N
#define LCD_CMD_GDCAN 0x45 // Get scanline
#define LCD_CMD_WRDISBV 0x51 // Write display brightness
#define LCD_CMD_RDDISBV 0x52 // Read display brightness value

View File

@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_io_interface.h"
static const char *TAG = "lcd_panel.io";
esp_err_t esp_lcd_panel_io_tx_param(esp_lcd_panel_io_handle_t io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size)
{
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
return io->tx_param(io, lcd_cmd, lcd_cmd_bits, param, param_size);
}
esp_err_t esp_lcd_panel_io_tx_color(esp_lcd_panel_io_handle_t io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size)
{
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
return io->tx_color(io, lcd_cmd, lcd_cmd_bits, color, color_size);
}
esp_err_t esp_lcd_panel_io_del(esp_lcd_panel_io_handle_t io)
{
ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_ARG, TAG, "invalid panel io handle");
return io->del(io);
}

View File

@ -0,0 +1,149 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
static const char *TAG = "lcd_panel.io.i2c";
static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io);
static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size);
static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size);
typedef struct {
esp_lcd_panel_io_t base; // Base class of generic lcd panel io
uint32_t i2c_bus_id; // I2C bus id, indicating which I2C port
uint32_t dev_addr; // Device address
uint32_t control_phase_cmd; // control byte when transferring command
uint32_t control_phase_data; // control byte when transferring data
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); // User register's callback, invoked when color data trans done
void *user_data; // User's private data, passed directly to callback on_color_trans_done()
} lcd_panel_io_i2c_t;
esp_err_t esp_lcd_new_panel_io_i2c(esp_lcd_i2c_bus_handle_t bus, const esp_lcd_panel_io_i2c_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i2c_t *i2c_panel_io = NULL;
ESP_GOTO_ON_FALSE(io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(io_config->control_phase_bytes * 8 > io_config->dc_bit_offset, ESP_ERR_INVALID_ARG, err, TAG, "D/C bit exceeds control bytes");
i2c_panel_io = calloc(1, sizeof(lcd_panel_io_i2c_t));
ESP_GOTO_ON_FALSE(i2c_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c panel io");
i2c_panel_io->i2c_bus_id = (uint32_t)bus;
i2c_panel_io->on_color_trans_done = io_config->on_color_trans_done;
i2c_panel_io->control_phase_data = (!io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
i2c_panel_io->control_phase_cmd = (io_config->flags.dc_low_on_data) << (io_config->dc_bit_offset);
i2c_panel_io->user_data = io_config->user_data;
i2c_panel_io->dev_addr = io_config->dev_addr;
i2c_panel_io->base.del = panel_io_i2c_del;
i2c_panel_io->base.tx_param = panel_io_i2c_tx_param;
i2c_panel_io->base.tx_color = panel_io_i2c_tx_color;
*ret_io = &(i2c_panel_io->base);
ESP_LOGD(TAG, "new i2c lcd panel io @%p", i2c_panel_io);
return ESP_OK;
err:
return ret;
}
static esp_err_t panel_io_i2c_del(esp_lcd_panel_io_t *io)
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
ESP_LOGD(TAG, "del lcd panel io spi @%p", i2c_panel_io);
free(i2c_panel_io);
return ret;
}
static esp_err_t panel_io_i2c_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size)
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
i2c_cmd_handle_t cmd_link = i2c_cmd_link_create();
ESP_GOTO_ON_FALSE(cmd_link, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c cmd link");
ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_WRITE, true), err, TAG, "write address failed"); // address phase
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, i2c_panel_io->control_phase_cmd, true), err, TAG, "write control command failed"); // control phase
switch (lcd_cmd_bits / 8) { // LCD command
case 4:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 24) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
case 3:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 16) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
case 2:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 8) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
case 1:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 0) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
default:
break;
}
if (param) {
uint8_t *data = (uint8_t *) param; // parameters for that command
ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, data, param_size, true), err, TAG, "write param failed");
}
ESP_GOTO_ON_ERROR(i2c_master_stop(cmd_link), err, TAG, "issue stop failed"); // stop phase
ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(i2c_panel_io->i2c_bus_id, cmd_link, portMAX_DELAY), err, TAG, "i2c transaction failed");
i2c_cmd_link_delete(cmd_link);
return ESP_OK;
err:
if (cmd_link) {
i2c_cmd_link_delete(cmd_link);
}
return ret;
}
static esp_err_t panel_io_i2c_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size)
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i2c_t *i2c_panel_io = __containerof(io, lcd_panel_io_i2c_t, base);
i2c_cmd_handle_t cmd_link = i2c_cmd_link_create();
ESP_GOTO_ON_FALSE(cmd_link, ESP_ERR_NO_MEM, err, TAG, "no mem for i2c cmd link");
ESP_GOTO_ON_ERROR(i2c_master_start(cmd_link), err, TAG, "issue start failed"); // start phase
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (i2c_panel_io->dev_addr << 1) | I2C_MASTER_WRITE, true), err, TAG, "write address failed"); // address phase
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, i2c_panel_io->control_phase_data, true), err, TAG, "write control data failed"); // control phase
switch (lcd_cmd_bits / 8) { // LCD command
case 4:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 24) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
case 3:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 16) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
case 2:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 8) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
case 1:
ESP_GOTO_ON_ERROR(i2c_master_write_byte(cmd_link, (lcd_cmd >> 0) & 0xFF, true), err, TAG, "write LCD cmd failed"); // fall-through
default:
break;
}
ESP_GOTO_ON_ERROR(i2c_master_write(cmd_link, color, color_size, true), err, TAG, "write color failed"); // LCD gram data
ESP_GOTO_ON_ERROR(i2c_master_stop(cmd_link), err, TAG, "issue stop failed"); // stop phase
ESP_GOTO_ON_ERROR(i2c_master_cmd_begin(i2c_panel_io->i2c_bus_id, cmd_link, portMAX_DELAY), err, TAG, "i2c transaction failed");
i2c_cmd_link_delete(cmd_link);
// trans done callback
if (i2c_panel_io->on_color_trans_done) {
i2c_panel_io->on_color_trans_done(&(i2c_panel_io->base), i2c_panel_io->user_data, NULL);
}
return ESP_OK;
err:
if (cmd_link) {
i2c_cmd_link_delete(cmd_link);
}
return ret;
}

View File

@ -0,0 +1,518 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include <sys/queue.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.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 "hal/dma_types.h"
#include "hal/gpio_hal.h"
#include "esp_private/gdma.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#if SOC_LCDCAM_SUPPORTED
#include "esp_lcd_common.h"
#include "soc/lcd_periph.h"
#include "hal/lcd_ll.h"
#include "hal/lcd_hal.h"
static const char *TAG = "lcd_panel.io.i80";
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;
static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, 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, int lcd_cmd_bits, const void *color, size_t color_size);
static esp_err_t panel_io_i80_del(esp_lcd_panel_io_t *io);
static esp_err_t lcd_i80_bus_create_trans_link(esp_lcd_i80_bus_handle_t bus);
static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus);
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);
struct esp_lcd_i80_bus_t {
int bus_id; // Bus ID, index from 0
portMUX_TYPE spinlock; // spinlock used to protect i80 bus members(hal, device_list, cur_trans)
lcd_hal_context_t hal; // Hal object
size_t data_width; // Number of data lines
intr_handle_t intr; // LCD peripheral interrupt handle
size_t num_dma_nodes; // Number of DMA descriptors
size_t resolution_hz; // LCD_CLK resolution, determined by selected clock source
gdma_channel_handle_t dma_chan; // DMA channel handle
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
dma_descriptor_t dma_nodes[0]; // DMA descriptor pool, the descriptors are shared by all i80 devices
};
struct lcd_i80_trans_descriptor_t {
lcd_panel_io_i80_t *i80_device; // i80 device issuing this transaction
int cmd_value; // Command value
uint32_t cmd_cycles; // Command cycles
const void *data; // Data buffer
uint32_t data_length; // Data buffer size
void *cb_user_data; // private data used by trans_done_cb
bool (*trans_done_cb)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); // transaction done callback
};
struct lcd_panel_io_i80_t {
esp_lcd_panel_io_t base; // Base class of generic lcd panel io
esp_lcd_i80_bus_t *bus; // Which bus the device is attached to
int cs_gpio_num; // GPIO used for CS line
unsigned int pclk_hz; // PCLK clock frequency
size_t clock_prescale; // Prescaler coefficient, determined by user's configured PCLK frequency
QueueHandle_t trans_queue; // Transaction queue, transactions in this queue are pending for scheduler to dispatch
QueueHandle_t done_queue; // Transaction done queue, transactions in this queue are finished but not recycled by the caller
size_t queue_size; // Size of transaction queue
size_t num_trans_working; // Number of transactions that are undergoing (the descriptor not recycled yet)
void *cb_user_data; // private data used when transfer color data
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); // color data trans done callback
LIST_ENTRY(lcd_panel_io_i80_t) device_list_entry; // Entry of i80 device list
struct {
int dc_idle_level: 1; // Level of DC line in IDLE phase
int dc_cmd_level: 1; // Level of DC line in CMD phase
int dc_dummy_level: 1; // Level of DC line in DUMMY phase
int dc_data_level: 1; // Level of DC line in DATA phase
} dc_levels;
struct {
int invert_cs: 1; // Whether to invert the CS line
int reverse_color_bits: 1; // Reverse the data bits, D[N:0] -> D[0:N]
int swap_color_bytes: 1; // Swap adjacent two data bytes before sending out
int pclk_active_neg: 1; // The display will write data lines when there's a falling edge on WR line
int pclk_idle_low: 1; // The WR line keeps at low level in IDLE phase
} flags;
lcd_i80_trans_descriptor_t trans_pool[0]; // Transaction pool
};
esp_err_t esp_lcd_new_i80_bus(const esp_lcd_i80_bus_config_t *bus_config, esp_lcd_i80_bus_handle_t *ret_bus)
{
esp_err_t ret = ESP_OK;
esp_lcd_i80_bus_t *bus = NULL;
ESP_GOTO_ON_FALSE(bus_config && ret_bus, ESP_ERR_INVALID_ARG, err_arg, 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);
ESP_GOTO_ON_FALSE(bus, ESP_ERR_NO_MEM, no_mem_bus, TAG, "no mem for i80 bus");
bus->num_dma_nodes = num_dma_nodes;
// register to platform
int bus_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_I80, bus);
ESP_GOTO_ON_FALSE(bus_id >= 0, ESP_ERR_NOT_FOUND, no_slot, TAG, "no free i80 bus slot");
bus->bus_id = bus_id;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.buses[bus_id].module);
// initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&bus->hal, bus_id);
// reset peripheral and FIFO
lcd_ll_reset(bus->hal.dev);
lcd_ll_fifo_reset(bus->hal.dev);
lcd_ll_enable_clock(bus->hal.dev, true);
// 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;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.buses[bus_id].irq_id, isr_flags,
lcd_ll_get_interrupt_status_reg(bus->hal.dev),
LCD_LL_EVENT_TRANS_DONE, lcd_default_isr_handler, bus, &bus->intr);
ESP_GOTO_ON_ERROR(ret, no_int, TAG, "install interrupt failed");
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
ret = lcd_i80_bus_create_trans_link(bus);
ESP_GOTO_ON_ERROR(ret, no_dma, TAG, "install DMA failed");
// set peripheral clock resolution
bus->resolution_hz = lcd_com_select_periph_clock(&bus->hal);
// enable 8080 mode and set data width
lcd_ll_enable_rgb_mode(bus->hal.dev, false);
lcd_ll_set_data_width(bus->hal.dev, bus_config->data_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
lcd_ll_enable_interrupt(bus->hal.dev, LCD_LL_EVENT_TRANS_DONE, true);
// trigger a quick "trans done" event, and wait for the interrupt line goes active
// this could ensure we go into ISR handler next time we call `esp_intr_enable`
lcd_periph_trigger_quick_trans_done_event(bus);
// configure GPIO
ret = lcd_i80_bus_configure_gpio(bus, bus_config);
ESP_GOTO_ON_ERROR(ret, no_gpio, TAG, "configure GPIO failed");
// fill other i80 bus runtime parameters
LIST_INIT(&bus->device_list); // initialize device list head
bus->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED;
bus->data_width = lcd_ll_get_data_width(bus->hal.dev);
*ret_bus = bus;
ESP_LOGD(TAG, "new i80 bus(%d) @%p, %zu dma nodes", bus_id, bus, bus->num_dma_nodes);
return ESP_OK;
no_gpio:
gdma_disconnect(bus->dma_chan);
gdma_del_channel(bus->dma_chan);
no_dma:
esp_intr_free(bus->intr);
no_int:
periph_module_disable(lcd_periph_signals.buses[bus_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus->bus_id);
no_slot:
free(bus);
no_mem_bus:
err_arg:
return ret;
}
esp_err_t esp_lcd_del_i80_bus(esp_lcd_i80_bus_handle_t bus)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(bus, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(LIST_EMPTY(&bus->device_list), ESP_ERR_INVALID_STATE, err, TAG, "device list not empty");
int bus_id = bus->bus_id;
gdma_disconnect(bus->dma_chan);
gdma_del_channel(bus->dma_chan);
esp_intr_free(bus->intr);
periph_module_disable(lcd_periph_signals.buses[bus_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_I80, bus_id);
free(bus);
ESP_LOGD(TAG, "del i80 bus(%d)", bus_id);
err:
return ret;
}
esp_err_t esp_lcd_new_panel_io_i80(esp_lcd_i80_bus_handle_t bus, const esp_lcd_panel_io_i80_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
{
esp_err_t ret = ESP_OK;
lcd_panel_io_i80_t *i80_device = NULL;
ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
// check if pixel clock setting is valid
uint32_t pclk_prescale = bus->resolution_hz / io_config->pclk_hz;
ESP_GOTO_ON_FALSE(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));
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 *));
ESP_GOTO_ON_FALSE(i80_device->trans_queue, ESP_ERR_NO_MEM, err, TAG, "create trans queue failed");
i80_device->done_queue = xQueueCreate(io_config->trans_queue_depth, sizeof(lcd_i80_trans_descriptor_t *));
ESP_GOTO_ON_FALSE(i80_device->done_queue, ESP_ERR_NO_MEM, err, TAG, "create done queue failed");
// adding device to list
portENTER_CRITICAL(&bus->spinlock);
LIST_INSERT_HEAD(&bus->device_list, i80_device, device_list_entry);
portEXIT_CRITICAL(&bus->spinlock);
// we don't initialize the i80 bus at the memont, but initialize the bus when start a transaction for a new device
// so save these as i80 device runtime parameters
i80_device->bus = bus;
i80_device->queue_size = io_config->trans_queue_depth;
i80_device->clock_prescale = pclk_prescale;
i80_device->pclk_hz = bus->resolution_hz / pclk_prescale;
i80_device->dc_levels.dc_cmd_level = io_config->dc_levels.dc_cmd_level;
i80_device->dc_levels.dc_data_level = io_config->dc_levels.dc_data_level;
i80_device->dc_levels.dc_dummy_level = io_config->dc_levels.dc_dummy_level;
i80_device->dc_levels.dc_idle_level = io_config->dc_levels.dc_idle_level;
i80_device->cs_gpio_num = io_config->cs_gpio_num;
i80_device->flags.reverse_color_bits = io_config->flags.reverse_color_bits;
i80_device->flags.swap_color_bytes = io_config->flags.swap_color_bytes;
i80_device->flags.invert_cs = io_config->flags.invert_cs;
i80_device->flags.pclk_idle_low = io_config->flags.pclk_idle_low;
i80_device->flags.pclk_active_neg = io_config->flags.pclk_active_neg;
i80_device->on_color_trans_done = io_config->on_color_trans_done;
i80_device->cb_user_data = io_config->user_data;
// fill panel io function table
i80_device->base.del = panel_io_i80_del;
i80_device->base.tx_param = panel_io_i80_tx_param;
i80_device->base.tx_color = panel_io_i80_tx_color;
// we only configure the CS GPIO as output, don't connect to the peripheral signal at the moment
// we will connect the CS GPIO to peripheral signal when switching devices in lcd_i80_switch_devices()
gpio_set_level(io_config->cs_gpio_num, !io_config->flags.invert_cs);
gpio_set_direction(io_config->cs_gpio_num, GPIO_MODE_OUTPUT);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[io_config->cs_gpio_num], PIN_FUNC_GPIO);
*ret_io = &(i80_device->base);
ESP_LOGD(TAG, "new i80 lcd panel io @%p on bus(%d)", i80_device, bus->bus_id);
return ESP_OK;
err:
if (i80_device) {
if (i80_device->trans_queue) {
vQueueDelete(i80_device->trans_queue);
}
if (i80_device->done_queue) {
vQueueDelete(i80_device->done_queue);
}
free(i80_device);
}
return ret;
}
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;
// wait all pending transaction to finish
for (size_t i = 0; i < i80_device->num_trans_working; i++) {
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
}
// remove from device list
portENTER_CRITICAL(&bus->spinlock);
LIST_REMOVE(i80_device, device_list_entry);
portEXIT_CRITICAL(&bus->spinlock);
ESP_LOGD(TAG, "del i80 lcd panel io @%p", i80_device);
vQueueDelete(i80_device->trans_queue);
vQueueDelete(i80_device->done_queue);
free(i80_device);
return ESP_OK;
}
static esp_err_t panel_io_i80_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size)
{
lcd_panel_io_i80_t *next_device = __containerof(io, lcd_panel_io_i80_t, base);
esp_lcd_i80_bus_t *bus = next_device->bus;
lcd_panel_io_i80_t *cur_device = bus->cur_device;
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");
uint32_t cmd_cycles = lcd_cmd_bits / bus->data_width;
// in case data_width=16 and cmd_bits=8, we still need 1 cmd_cycle
if (cmd_cycles * bus->data_width < lcd_cmd_bits) {
cmd_cycles++;
}
// wait all pending transaction in the queue to finish
for (size_t i = 0; i < next_device->num_trans_working; i++) {
xQueueReceive(next_device->done_queue, &trans_desc, portMAX_DELAY);
}
next_device->num_trans_working = 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);
// don't reverse bit/bytes for parameters
lcd_ll_reverse_data_bit_order(bus->hal.dev, false);
lcd_ll_reverse_data_byte_order(bus->hal.dev, bus->data_width, false);
bus->cur_trans = NULL;
bus->cur_device = next_device;
// package a transaction
trans_desc = &next_device->trans_pool[0];
trans_desc->i80_device = next_device;
trans_desc->cmd_cycles = cmd_cycles;
trans_desc->cmd_value = lcd_cmd;
trans_desc->data = param;
trans_desc->data_length = param_size;
trans_desc->trans_done_cb = NULL; // no callback for parameter transaction
// mount data to DMA links
lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length);
lcd_start_transaction(bus, trans_desc);
// polling the trans done event, but don't clear the event status
while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE));
return ESP_OK;
}
static esp_err_t panel_io_i80_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size)
{
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;
assert(color_size <= (bus->num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) && "color bytes too long, enlarge max_transfer_bytes");
// in case data_width=16 and cmd_bits=8, we still need 1 cmd_cycle
uint32_t cmd_cycles = lcd_cmd_bits / bus->data_width;
if (cmd_cycles * bus->data_width < lcd_cmd_bits) {
cmd_cycles++;
}
if (i80_device->num_trans_working < i80_device->queue_size) {
trans_desc = &i80_device->trans_pool[i80_device->num_trans_working];
} else {
// transaction pool has used up, recycle one from done_queue
xQueueReceive(i80_device->done_queue, &trans_desc, portMAX_DELAY);
i80_device->num_trans_working--;
}
trans_desc->i80_device = i80_device;
trans_desc->cmd_cycles = cmd_cycles;
trans_desc->cmd_value = lcd_cmd;
trans_desc->data = color;
trans_desc->data_length = color_size;
trans_desc->trans_done_cb = i80_device->on_color_trans_done;
trans_desc->cb_user_data = i80_device->cb_user_data;
// send transaction to trans_queue
xQueueSend(i80_device->trans_queue, &trans_desc, portMAX_DELAY);
i80_device->num_trans_working++;
// enable interrupt and go into isr handler, where we fetch the transactions from trans_queue and start it
// we will go into `lcd_default_isr_handler` almost at once, because the "trans done" event is active at the moment
esp_intr_enable(bus->intr);
return ESP_OK;
}
static esp_err_t lcd_i80_bus_create_trans_link(esp_lcd_i80_bus_handle_t bus)
{
esp_err_t ret = ESP_OK;
// chain DMA descriptors
for (int i = 0; i < bus->num_dma_nodes; i++) {
bus->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU;
bus->dma_nodes[i].next = &bus->dma_nodes[i + 1];
}
bus->dma_nodes[bus->num_dma_nodes - 1].next = NULL; // one-off DMA chain
// alloc DMA channel and connect to LCD peripheral
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
};
ret = gdma_new_channel(&dma_chan_config, &bus->dma_chan);
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc DMA channel failed");
gdma_connect(bus->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
gdma_strategy_config_t strategy_config = {
.auto_update_desc = true,
.owner_check = true
};
gdma_apply_strategy(bus->dma_chan, &strategy_config);
return ESP_OK;
err:
if (bus->dma_chan) {
gdma_del_channel(bus->dma_chan);
}
return ret;
}
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)
{
int bus_id = bus->bus_id;
// check validation of GPIO number
bool valid_gpio = (bus_config->wr_gpio_num >= 0) && (bus_config->dc_gpio_num >= 0);
for (size_t i = 0; i < bus_config->data_width; i++) {
valid_gpio = valid_gpio && (bus_config->data_gpio_nums[i] >= 0);
}
if (!valid_gpio) {
return ESP_ERR_INVALID_ARG;
}
// connect peripheral signals via GPIO matrix
for (size_t i = 0; i < bus_config->data_width; i++) {
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->data_gpio_nums[i]], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->data_gpio_nums[i], GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(bus_config->data_gpio_nums[i], lcd_periph_signals.buses[bus_id].data_sigs[i], false, false);
}
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->dc_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->dc_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(bus_config->dc_gpio_num, lcd_periph_signals.buses[bus_id].dc_sig, false, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[bus_config->wr_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(bus_config->wr_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(bus_config->wr_gpio_num, lcd_periph_signals.buses[bus_id].wr_sig, false, false);
return ESP_OK;
}
static void lcd_periph_trigger_quick_trans_done_event(esp_lcd_i80_bus_handle_t bus)
{
// trigger a quick interrupt event by a dummy transaction, wait the LCD interrupt line goes active
// next time when esp_intr_enable is invoked, we can go into interrupt handler immediately
// where we dispatch transactions for i80 devices
lcd_ll_set_phase_cycles(bus->hal.dev, 0, 1, 0);
lcd_ll_start(bus->hal.dev);
while (!(lcd_ll_get_interrupt_status(bus->hal.dev) & LCD_LL_EVENT_TRANS_DONE));
}
static void lcd_start_transaction(esp_lcd_i80_bus_t *bus, lcd_i80_trans_descriptor_t *trans_desc)
{
// by default, the dummy phase is disabled because it's not common for most LCDs
// Number of data phase cycles are controlled by DMA buffer length, we only need to enable/disable the phase here
lcd_ll_set_phase_cycles(bus->hal.dev, trans_desc->cmd_cycles, 0, trans_desc->data ? 1 : 0);
lcd_ll_set_command(bus->hal.dev, bus->data_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));
}
lcd_ll_start(bus->hal.dev);
}
static void lcd_i80_switch_devices(lcd_panel_io_i80_t *cur_device, lcd_panel_io_i80_t *next_device)
{
// we assume the next_device and cur_device are attached to the same bus
esp_lcd_i80_bus_t *bus = next_device->bus;
if (next_device != cur_device) {
// reconfigure PCLK for the new device
lcd_ll_set_pixel_clock_prescale(bus->hal.dev, next_device->clock_prescale);
lcd_ll_set_clock_idle_level(bus->hal.dev, !next_device->flags.pclk_idle_low);
lcd_ll_set_pixel_clock_edge(bus->hal.dev, next_device->flags.pclk_active_neg);
// configure DC line level for the new device
lcd_ll_set_dc_level(bus->hal.dev, next_device->dc_levels.dc_idle_level, next_device->dc_levels.dc_cmd_level,
next_device->dc_levels.dc_dummy_level, next_device->dc_levels.dc_data_level);
if (cur_device) {
// disconnect current CS GPIO from peripheral signal
esp_rom_gpio_connect_out_signal(cur_device->cs_gpio_num, SIG_GPIO_OUT_IDX, false, false);
}
// connect CS signal to the new device
esp_rom_gpio_connect_out_signal(next_device->cs_gpio_num, lcd_periph_signals.buses[bus->bus_id].cs_sig,
next_device->flags.invert_cs, false);
}
}
IRAM_ATTR static void lcd_default_isr_handler(void *args)
{
esp_lcd_i80_bus_t *bus = (esp_lcd_i80_bus_t *)args;
lcd_i80_trans_descriptor_t *trans_desc = NULL;
lcd_panel_io_i80_t *cur_device = NULL;
lcd_panel_io_i80_t *next_device = NULL;
BaseType_t high_task_woken = pdFALSE;
bool need_yield = false;
uint32_t intr_status = lcd_ll_get_interrupt_status(bus->hal.dev);
if (intr_status & LCD_LL_EVENT_TRANS_DONE) {
// disable interrupt temporarily, only re-enable when there be remained transaction in the queue
esp_intr_disable(bus->intr);
trans_desc = bus->cur_trans; // the finished transaction
cur_device = bus->cur_device;// the working device
// process finished transaction
if (trans_desc) {
assert(trans_desc->i80_device == cur_device && "transaction device mismatch");
// device callback
if (trans_desc->trans_done_cb) {
if (trans_desc->trans_done_cb(&cur_device->base, trans_desc->cb_user_data, NULL)) {
need_yield = true;
}
}
// move transaction to done_queue
// there won't be case that will overflow the queue, so skip checking the return value
high_task_woken = pdFALSE;
xQueueSendFromISR(cur_device->done_queue, &trans_desc, &high_task_woken);
if (high_task_woken == pdTRUE) {
need_yield = true;
}
bus->cur_trans = NULL;
}
// fetch transactions from devices' trans_queue
// Note: the first registered device will have the highest priority to be scheduled
LIST_FOREACH(next_device, &bus->device_list, device_list_entry) {
high_task_woken = pdFALSE;
if (xQueueReceiveFromISR(next_device->trans_queue, &trans_desc, &high_task_woken) == pdTRUE) {
if (high_task_woken == pdTRUE) {
need_yield = true;
}
// 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, bus->data_width, next_device->flags.swap_color_bytes);
bus->cur_trans = trans_desc;
bus->cur_device = next_device;
// mount data to DMA links
lcd_com_mount_dma_data(bus->dma_nodes, trans_desc->data, trans_desc->data_length);
// enable interrupt again, because the new transaction can trigger new trans done event
esp_intr_enable(bus->intr);
lcd_start_transaction(bus, trans_desc);
break; // exit for-each loop
}
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}
#endif // SOC_LCDCAM_SUPPORTED

View File

@ -0,0 +1,230 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <string.h>
#include <sys/cdefs.h>
#include "esp_lcd_panel_io_interface.h"
#include "esp_lcd_panel_io.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
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, int lcd_cmd_bits, const void *param, size_t param_size);
static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size);
static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io);
static void lcd_spi_pre_trans_cb(spi_transaction_t *trans);
static void lcd_spi_post_trans_color_cb(spi_transaction_t *trans);
typedef struct {
spi_transaction_t base;
struct {
unsigned int dc_gpio_level: 1;
unsigned int trans_is_color: 1;
} flags;
} lcd_spi_trans_descriptor_t;
typedef struct {
esp_lcd_panel_io_t base; // Base class of generic lcd panel io
spi_device_handle_t spi_dev; // SPI device handle
int dc_gpio_num; // D/C line GPIO number
bool (*on_color_trans_done)(esp_lcd_panel_io_handle_t panel_io, void *user_data, void *event_data); // User register's callback, invoked when color data trans done
void *user_data; // User's private data, passed directly to callback on_color_trans_done
size_t queue_size; // Size of transaction queue
size_t num_trans_working; // Number of transactions that are undergoing (the descriptor not recycled yet)
struct {
int dc_as_cmd_phase: 1; // D/C line value is encoded into SPI transaction command phase
int dc_data_level: 1; // Indicates the level of DC line when tranfering data
} flags;
lcd_spi_trans_descriptor_t trans_pool[0]; // Transaction pool
} esp_lcd_panel_io_spi_t;
esp_err_t esp_lcd_new_panel_io_spi(esp_lcd_spi_bus_handle_t bus, const esp_lcd_panel_io_spi_config_t *io_config, esp_lcd_panel_io_handle_t *ret_io)
{
esp_err_t ret = ESP_OK;
esp_lcd_panel_io_spi_t *spi_panel_io = NULL;
ESP_GOTO_ON_FALSE(bus && io_config && ret_io, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(!(io_config->flags.dc_as_cmd_phase && io_config->dc_gpio_num >= 0),
ESP_ERR_INVALID_ARG, err, TAG, "invalid DC mode");
spi_panel_io = calloc(1, sizeof(esp_lcd_panel_io_spi_t) + sizeof(lcd_spi_trans_descriptor_t) * io_config->trans_queue_depth);
ESP_GOTO_ON_FALSE(spi_panel_io, ESP_ERR_NO_MEM, err, TAG, "no mem for spi panel io");
spi_device_interface_config_t devcfg = {
.clock_speed_hz = io_config->pclk_hz,
.mode = io_config->spi_mode,
.spics_io_num = io_config->cs_gpio_num,
.queue_size = io_config->trans_queue_depth,
.command_bits = io_config->flags.dc_as_cmd_phase ? 1 : 0, // whether to encode DC line into command transaction
.pre_cb = lcd_spi_pre_trans_cb, // pre-transaction callback, mainly control DC gpio level
.post_cb = io_config->on_color_trans_done ? lcd_spi_post_trans_color_cb : NULL, // post-transaction, where we invoke user registered "on_color_trans_done()"
};
ret = spi_bus_add_device((spi_host_device_t)bus, &devcfg, &spi_panel_io->spi_dev);
ESP_GOTO_ON_ERROR(ret, err, TAG, "adding spi device to bus failed");
// if the DC line is not encoded into any spi transaction phase or it's not controlled by SPI peripheral
if (io_config->dc_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << io_config->dc_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for D/C line failed");
}
spi_panel_io->flags.dc_as_cmd_phase = io_config->flags.dc_as_cmd_phase;
spi_panel_io->flags.dc_data_level = !io_config->flags.dc_low_on_data;
spi_panel_io->on_color_trans_done = io_config->on_color_trans_done;
spi_panel_io->user_data = io_config->user_data;
spi_panel_io->dc_gpio_num = io_config->dc_gpio_num;
spi_panel_io->queue_size = io_config->trans_queue_depth;
spi_panel_io->base.tx_param = panel_io_spi_tx_param;
spi_panel_io->base.tx_color = panel_io_spi_tx_color;
spi_panel_io->base.del = panel_io_spi_del;
*ret_io = &(spi_panel_io->base);
ESP_LOGD(TAG, "new spi lcd panel io @%p", spi_panel_io);
return ESP_OK;
err:
if (spi_panel_io) {
if (io_config->dc_gpio_num >= 0) {
gpio_reset_pin(io_config->dc_gpio_num);
}
free(spi_panel_io);
}
return ret;
}
static esp_err_t panel_io_spi_del(esp_lcd_panel_io_t *io)
{
esp_err_t ret = ESP_OK;
spi_transaction_t *spi_trans = NULL;
esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
// wait all pending transaction to finish
for (size_t i = 0; i < spi_panel_io->num_trans_working; i++) {
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");
}
spi_bus_remove_device(spi_panel_io->spi_dev);
if (spi_panel_io->dc_gpio_num >= 0) {
gpio_reset_pin(spi_panel_io->dc_gpio_num);
}
ESP_LOGD(TAG, "del lcd panel io spi @%p", spi_panel_io);
free(spi_panel_io);
err:
return ret;
}
static esp_err_t panel_io_spi_tx_param(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *param, size_t param_size)
{
esp_err_t ret = ESP_OK;
spi_transaction_t *spi_trans = NULL;
lcd_spi_trans_descriptor_t *lcd_trans = NULL;
esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
// before issue a polling transaction, need to wait queued transactions finished
for (size_t i = 0; i < spi_panel_io->num_trans_working; i++) {
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");
}
spi_panel_io->num_trans_working = 0;
lcd_trans = &spi_panel_io->trans_pool[0];
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
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 command mode
lcd_trans->base.length = lcd_cmd_bits;
lcd_trans->base.tx_buffer = &lcd_cmd;
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;
}
// command is short, using polling mode
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");
if (param && param_size) {
lcd_trans->flags.dc_gpio_level = spi_panel_io->flags.dc_data_level; // set D/C line to data mode
lcd_trans->base.length = param_size * 8; // transaction length is in bits
lcd_trans->base.tx_buffer = param;
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;
}
// parameter is usually short, using polling mode
ret = spi_device_polling_transmit(spi_panel_io->spi_dev, &lcd_trans->base);
ESP_GOTO_ON_ERROR(ret, err, TAG, "spi transmit (polling) param failed");
}
err:
return ret;
}
static esp_err_t panel_io_spi_tx_color(esp_lcd_panel_io_t *io, int lcd_cmd, int lcd_cmd_bits, const void *color, size_t color_size)
{
esp_err_t ret = ESP_OK;
spi_transaction_t *spi_trans = NULL;
lcd_spi_trans_descriptor_t *lcd_trans = NULL;
esp_lcd_panel_io_spi_t *spi_panel_io = __containerof(io, esp_lcd_panel_io_spi_t, base);
// before issue a polling transaction, need to wait queued transactions finished
for (size_t i = 0; i < spi_panel_io->num_trans_working; i++) {
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");
}
spi_panel_io->num_trans_working = 0;
lcd_trans = &spi_panel_io->trans_pool[0];
memset(lcd_trans, 0, sizeof(lcd_spi_trans_descriptor_t));
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 command mode
lcd_trans->base.length = lcd_cmd_bits;
lcd_trans->base.tx_buffer = &lcd_cmd;
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;
}
// command is short, using polling mode
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_working++;
err:
return ret;
}
static void lcd_spi_pre_trans_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 (spi_panel_io->dc_gpio_num >= 0) { // set D/C line level if necessary
gpio_set_level(spi_panel_io->dc_gpio_num, lcd_trans->flags.dc_gpio_level);
}
}
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 (spi_panel_io->on_color_trans_done) {
spi_panel_io->on_color_trans_done(&spi_panel_io->base, spi_panel_io->user_data, NULL);
}
}
}

View File

@ -0,0 +1,65 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_panel_interface.h"
static const char *TAG = "lcd_panel";
esp_err_t esp_lcd_panel_reset(esp_lcd_panel_handle_t panel)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->reset(panel);
}
esp_err_t esp_lcd_panel_init(esp_lcd_panel_handle_t panel)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->init(panel);
}
esp_err_t esp_lcd_panel_del(esp_lcd_panel_handle_t panel)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->del(panel);
}
esp_err_t esp_lcd_panel_draw_bitmap(esp_lcd_panel_handle_t panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
}
esp_err_t esp_lcd_panel_mirror(esp_lcd_panel_handle_t panel, bool mirror_x, bool mirror_y)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->mirror(panel, mirror_x, mirror_y);
}
esp_err_t esp_lcd_panel_swap_xy(esp_lcd_panel_handle_t panel, bool swap_axes)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->swap_xy(panel, swap_axes);
}
esp_err_t esp_lcd_panel_set_gap(esp_lcd_panel_handle_t panel, int x_gap, int y_gap)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->set_gap(panel, x_gap, y_gap);
}
esp_err_t esp_lcd_panel_invert_color(esp_lcd_panel_handle_t panel, bool invert_color_data)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->invert_color(panel, invert_color_data);
}
esp_err_t esp_lcd_panel_disp_off(esp_lcd_panel_handle_t panel, bool off)
{
ESP_RETURN_ON_FALSE(panel, ESP_ERR_INVALID_ARG, TAG, "invalid panel handle");
return panel->disp_off(panel, off);
}

View File

@ -0,0 +1,235 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
static const char *TAG = "lcd_panel.ssd1306";
// SSD1306 commands
#define SSD1306_CMD_SET_MEMORY_ADDR_MODE 0x20
#define SSD1306_CMD_SET_COLUMN_RANGE 0x21
#define SSD1306_CMD_SET_PAGE_RANGE 0x22
#define SSD1306_CMD_SET_CHARGE_PUMP 0x8D
#define SSD1306_CMD_MIRROR_X_OFF 0xA0
#define SSD1306_CMD_MIRROR_X_ON 0xA1
#define SSD1306_CMD_INVERT_OFF 0xA6
#define SSD1306_CMD_INVERT_ON 0xA7
#define SSD1306_CMD_DISP_OFF 0xAE
#define SSD1306_CMD_DISP_ON 0xAF
#define SSD1306_CMD_MIRROR_Y_OFF 0xC0
#define SSD1306_CMD_MIRROR_Y_ON 0xC8
static esp_err_t panel_ssd1306_del(esp_lcd_panel_t *panel);
static esp_err_t panel_ssd1306_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_ssd1306_init(esp_lcd_panel_t *panel);
static esp_err_t panel_ssd1306_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t panel_ssd1306_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_ssd1306_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_ssd1306_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t panel_ssd1306_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t panel_ssd1306_disp_off(esp_lcd_panel_t *panel, bool off);
typedef struct {
esp_lcd_panel_t base;
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
bool reset_level;
int x_gap;
int y_gap;
unsigned int bits_per_pixel;
} ssd1306_panel_t;
esp_err_t esp_lcd_new_panel_ssd1306(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
{
esp_err_t ret = ESP_OK;
ssd1306_panel_t *ssd1306 = NULL;
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
ESP_GOTO_ON_FALSE(panel_dev_config->color_space == ESP_LCD_COLOR_SPACE_MONOCHROME, ESP_ERR_INVALID_ARG, err, TAG, "support monochrome only");
ESP_GOTO_ON_FALSE(panel_dev_config->bits_per_pixel == 1, ESP_ERR_INVALID_ARG, err, TAG, "bpp must be 1");
ssd1306 = calloc(1, sizeof(ssd1306_panel_t));
ESP_GOTO_ON_FALSE(ssd1306, ESP_ERR_NO_MEM, err, TAG, "no mem for ssd1306 panel");
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
ssd1306->io = io;
ssd1306->bits_per_pixel = panel_dev_config->bits_per_pixel;
ssd1306->reset_gpio_num = panel_dev_config->reset_gpio_num;
ssd1306->reset_level = panel_dev_config->flags.reset_active_high;
ssd1306->base.del = panel_ssd1306_del;
ssd1306->base.reset = panel_ssd1306_reset;
ssd1306->base.init = panel_ssd1306_init;
ssd1306->base.draw_bitmap = panel_ssd1306_draw_bitmap;
ssd1306->base.invert_color = panel_ssd1306_invert_color;
ssd1306->base.set_gap = panel_ssd1306_set_gap;
ssd1306->base.mirror = panel_ssd1306_mirror;
ssd1306->base.swap_xy = panel_ssd1306_swap_xy;
ssd1306->base.disp_off = panel_ssd1306_disp_off;
*ret_panel = &(ssd1306->base);
ESP_LOGD(TAG, "new ssd1306 panel @%p", ssd1306);
return ESP_OK;
err:
if (ssd1306) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(ssd1306);
}
return ret;
}
static esp_err_t panel_ssd1306_del(esp_lcd_panel_t *panel)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
if (ssd1306->reset_gpio_num >= 0) {
gpio_reset_pin(ssd1306->reset_gpio_num);
}
ESP_LOGD(TAG, "del ssd1306 panel @%p", ssd1306);
free(ssd1306);
return ESP_OK;
}
static esp_err_t panel_ssd1306_reset(esp_lcd_panel_t *panel)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
// perform hardware reset
if (ssd1306->reset_gpio_num >= 0) {
gpio_set_level(ssd1306->reset_gpio_num, ssd1306->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(ssd1306->reset_gpio_num, !ssd1306->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
}
return ESP_OK;
}
static esp_err_t panel_ssd1306_init(esp_lcd_panel_t *panel)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
esp_lcd_panel_io_handle_t io = ssd1306->io;
esp_lcd_panel_io_tx_param(io, SSD1306_CMD_DISP_OFF, 8, NULL, 0);
esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_MEMORY_ADDR_MODE, 8, (uint8_t[]) {
0x00 // horizontal addressing mode
}, 1);
esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_CHARGE_PUMP, 8, (uint8_t[]) {
0x14 // enable charge pump
}, 1);
esp_lcd_panel_io_tx_param(io, SSD1306_CMD_DISP_ON, 8, NULL, 0);
// SEG/COM will be ON after 100ms after sending DISP_ON command
vTaskDelay(pdMS_TO_TICKS(100));
return ESP_OK;
}
static esp_err_t panel_ssd1306_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
esp_lcd_panel_io_handle_t io = ssd1306->io;
// adding extra gap
x_start += ssd1306->x_gap;
x_end += ssd1306->x_gap;
y_start += ssd1306->y_gap;
y_end += ssd1306->y_gap;
// one page contains 8 rows (COMs)
uint8_t page_start = y_start / 8;
uint8_t page_end = (y_end - 1) / 8;
// define an area of frame memory where MCU can access
esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_COLUMN_RANGE, 8, (uint8_t[]) {
(x_start & 0x7F),
((x_end - 1) & 0x7F),
}, 2);
esp_lcd_panel_io_tx_param(io, SSD1306_CMD_SET_PAGE_RANGE, 8, (uint8_t[]) {
(page_start & 0x07),
(page_end & 0x07),
}, 2);
// transfer frame buffer
size_t len = (y_end - y_start) * (x_end - x_start) * ssd1306->bits_per_pixel / 8;
esp_lcd_panel_io_tx_color(io, 0, 0, color_data, len);
return ESP_OK;
}
static esp_err_t panel_ssd1306_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
esp_lcd_panel_io_handle_t io = ssd1306->io;
int command = 0;
if (invert_color_data) {
command = SSD1306_CMD_INVERT_ON;
} else {
command = SSD1306_CMD_INVERT_OFF;
}
esp_lcd_panel_io_tx_param(io, command, 8, NULL, 0);
return ESP_OK;
}
static esp_err_t panel_ssd1306_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
esp_lcd_panel_io_handle_t io = ssd1306->io;
int command = 0;
if (mirror_x) {
command = SSD1306_CMD_MIRROR_X_ON;
} else {
command = SSD1306_CMD_MIRROR_X_OFF;
}
esp_lcd_panel_io_tx_param(io, command, 8, NULL, 0);
if (mirror_y) {
command = SSD1306_CMD_MIRROR_Y_ON;
} else {
command = SSD1306_CMD_MIRROR_X_OFF;
}
esp_lcd_panel_io_tx_param(io, command, 8, NULL, 0);
return ESP_OK;
}
static esp_err_t panel_ssd1306_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
{
return ESP_ERR_NOT_SUPPORTED;
}
static esp_err_t panel_ssd1306_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
ssd1306->x_gap = x_gap;
ssd1306->y_gap = y_gap;
return ESP_OK;
}
static esp_err_t panel_ssd1306_disp_off(esp_lcd_panel_t *panel, bool off)
{
ssd1306_panel_t *ssd1306 = __containerof(panel, ssd1306_panel_t, base);
esp_lcd_panel_io_handle_t io = ssd1306->io;
int command = 0;
if (off) {
command = SSD1306_CMD_DISP_OFF;
} else {
command = SSD1306_CMD_DISP_ON;
}
esp_lcd_panel_io_tx_param(io, command, 8, NULL, 0);
return ESP_OK;
}

View File

@ -0,0 +1,263 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <sys/cdefs.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_lcd_panel_interface.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 "driver/gpio.h"
#include "esp_log.h"
#include "esp_check.h"
static const char *TAG = "lcd_panel.st7789";
static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel);
static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel);
static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel);
static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t panel_st7789_disp_off(esp_lcd_panel_t *panel, bool off);
typedef struct {
esp_lcd_panel_t base;
esp_lcd_panel_io_handle_t io;
int reset_gpio_num;
bool reset_level;
int x_gap;
int y_gap;
unsigned int bits_per_pixel;
uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
uint8_t colmod_cal; // save surrent value of LCD_CMD_COLMOD register
} st7789_panel_t;
esp_err_t esp_lcd_new_panel_st7789(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
{
esp_err_t ret = ESP_OK;
st7789_panel_t *st7789 = NULL;
ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
st7789 = calloc(1, sizeof(st7789_panel_t));
ESP_GOTO_ON_FALSE(st7789, ESP_ERR_NO_MEM, err, TAG, "no mem for st7789 panel");
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_config_t io_conf = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
};
ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
}
switch (panel_dev_config->color_space) {
case ESP_LCD_COLOR_SPACE_RGB:
st7789->madctl_val = 0;
break;
case ESP_LCD_COLOR_SPACE_BGR:
st7789->madctl_val |= LCD_CMD_BGR_BIT;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
break;
}
switch (panel_dev_config->bits_per_pixel) {
case 16:
st7789->colmod_cal = 0x55;
break;
case 18:
st7789->colmod_cal = 0x66;
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
break;
}
st7789->io = io;
st7789->bits_per_pixel = panel_dev_config->bits_per_pixel;
st7789->reset_gpio_num = panel_dev_config->reset_gpio_num;
st7789->reset_level = panel_dev_config->flags.reset_active_high;
st7789->base.del = panel_st7789_del;
st7789->base.reset = panel_st7789_reset;
st7789->base.init = panel_st7789_init;
st7789->base.draw_bitmap = panel_st7789_draw_bitmap;
st7789->base.invert_color = panel_st7789_invert_color;
st7789->base.set_gap = panel_st7789_set_gap;
st7789->base.mirror = panel_st7789_mirror;
st7789->base.swap_xy = panel_st7789_swap_xy;
st7789->base.disp_off = panel_st7789_disp_off;
*ret_panel = &(st7789->base);
ESP_LOGD(TAG, "new st7789 panel @%p", st7789);
return ESP_OK;
err:
if (st7789) {
if (panel_dev_config->reset_gpio_num >= 0) {
gpio_reset_pin(panel_dev_config->reset_gpio_num);
}
free(st7789);
}
return ret;
}
static esp_err_t panel_st7789_del(esp_lcd_panel_t *panel)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
if (st7789->reset_gpio_num >= 0) {
gpio_reset_pin(st7789->reset_gpio_num);
}
ESP_LOGD(TAG, "del st7789 panel @%p", st7789);
free(st7789);
return ESP_OK;
}
static esp_err_t panel_st7789_reset(esp_lcd_panel_t *panel)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
esp_lcd_panel_io_handle_t io = st7789->io;
// perform hardware reset
if (st7789->reset_gpio_num >= 0) {
gpio_set_level(st7789->reset_gpio_num, st7789->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
gpio_set_level(st7789->reset_gpio_num, !st7789->reset_level);
vTaskDelay(pdMS_TO_TICKS(10));
} else { // perform software reset
esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, 8, NULL, 0);
vTaskDelay(pdMS_TO_TICKS(10)); // spec, wait at least 5m before sending new command
}
return ESP_OK;
}
static esp_err_t panel_st7789_init(esp_lcd_panel_t *panel)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
esp_lcd_panel_io_handle_t io = st7789->io;
// LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, 8, NULL, 0);
vTaskDelay(pdMS_TO_TICKS(100));
esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, 8, (uint8_t[]) {
0
}, 1);
esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, 8, (uint8_t[]) {
st7789->colmod_cal,
}, 1);
// turn on display
esp_lcd_panel_io_tx_param(io, LCD_CMD_DISPON, 8, NULL, 0);
return ESP_OK;
}
static esp_err_t panel_st7789_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
esp_lcd_panel_io_handle_t io = st7789->io;
x_start += st7789->x_gap;
x_end += st7789->x_gap;
y_start += st7789->y_gap;
y_end += st7789->y_gap;
// define an area of frame memory where MCU can access
esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, 8, (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, LCD_CMD_RASET, 8, (uint8_t[]) {
(y_start >> 8) & 0xFF,
y_start & 0xFF,
((y_end - 1) >> 8) & 0xFF,
(y_end - 1) & 0xFF,
}, 4);
// transfer frame buffer
size_t len = (x_end - x_start) * (y_end - y_start) * st7789->bits_per_pixel / 8;
esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, 8, color_data, len);
return ESP_OK;
}
static esp_err_t panel_st7789_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
esp_lcd_panel_io_handle_t io = st7789->io;
int command = 0;
if (invert_color_data) {
command = LCD_CMD_INVON;
} else {
command = LCD_CMD_INVOFF;
}
esp_lcd_panel_io_tx_param(io, command, 8, NULL, 0);
return ESP_OK;
}
static esp_err_t panel_st7789_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
esp_lcd_panel_io_handle_t io = st7789->io;
if (mirror_x) {
st7789->madctl_val |= LCD_CMD_MX_BIT;
} else {
st7789->madctl_val &= ~LCD_CMD_MX_BIT;
}
if (mirror_y) {
st7789->madctl_val |= LCD_CMD_MY_BIT;
} else {
st7789->madctl_val &= ~LCD_CMD_MY_BIT;
}
esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, 8, (uint8_t[]) {
st7789->madctl_val
}, 1);
return ESP_OK;
}
static esp_err_t panel_st7789_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
esp_lcd_panel_io_handle_t io = st7789->io;
if (swap_axes) {
st7789->madctl_val |= LCD_CMD_MV_BIT;
} else {
st7789->madctl_val &= ~LCD_CMD_MV_BIT;
}
esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, 8, (uint8_t[]) {
st7789->madctl_val
}, 1);
return ESP_OK;
}
static esp_err_t panel_st7789_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
st7789->x_gap = x_gap;
st7789->y_gap = y_gap;
return ESP_OK;
}
static esp_err_t panel_st7789_disp_off(esp_lcd_panel_t *panel, bool off)
{
st7789_panel_t *st7789 = __containerof(panel, st7789_panel_t, base);
esp_lcd_panel_io_handle_t io = st7789->io;
int command = 0;
if (off) {
command = LCD_CMD_DISPOFF;
} else {
command = LCD_CMD_DISPON;
}
esp_lcd_panel_io_tx_param(io, command, 8, NULL, 0);
return ESP_OK;
}

View File

@ -0,0 +1,443 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
#include <stdlib.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_attr.h"
#include "esp_check.h"
#include "esp_intr_alloc.h"
#include "esp_heap_caps.h"
#include "esp_lcd_panel_interface.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "esp_rom_gpio.h"
#include "soc/soc_caps.h"
#include "hal/dma_types.h"
#include "hal/gpio_hal.h"
#include "esp_private/gdma.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#if SOC_LCDCAM_SUPPORTED
#include "esp_lcd_common.h"
#include "soc/lcd_periph.h"
#include "hal/lcd_hal.h"
#include "hal/lcd_ll.h"
static const char *TAG = "lcd_panel.rgb";
typedef struct esp_rgb_panel_t esp_rgb_panel_t;
static esp_err_t rgb_panel_del(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel);
static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
static esp_err_t rgb_panel_disp_off(esp_lcd_panel_t *panel, bool off);
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);
struct esp_rgb_panel_t {
esp_lcd_panel_t base; // Base class of generic lcd panel
int panel_id; // LCD panel ID
lcd_hal_context_t hal; // Hal layer object
size_t data_width; // Number of data lines (e.g. for RGB565, the data width is 16)
int disp_gpio_num; // Display control GPIO, which is used to perform action like "disp_off"
intr_handle_t intr; // LCD peripheral interrupt handle
size_t num_dma_nodes; // Number of DMA descriptors that used to carry the frame buffer
uint8_t *fb; // Frame buffer
size_t fb_size; // Size of frame buffer
int data_gpio_nums[SOC_LCD_RGB_DATA_WIDTH]; // GPIOs used for data lines, we keep these GPIOs for action like "invert_color"
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
bool (*on_frame_trans_done)(esp_lcd_panel_t *panel, void *user_data); // Callback, invoked after frame trans done
void *user_data; // Reserved user's data of callback functions
int x_gap; // Extra gap in x coordinate, it's used when calculate the flush window
int y_gap; // Extra gap in y coordinate, it's used when calculate the flush window
struct {
int disp_en_level: 1; // The level which can turn on the screen by `disp_gpio_num`
int stream_mode: 1; // If set, the LCD transfers data continuously, otherwise, it stops refreshing the LCD when transaction done
int new_frame: 1; // Whether the frame we're going to flush is a new one
} flags;
dma_descriptor_t dma_nodes[0]; // DMA descriptor pool of size `num_dma_nodes`
};
esp_err_t esp_lcd_new_rgb_panel(const esp_lcd_rgb_panel_config_t *rgb_panel_config, esp_lcd_panel_handle_t *ret_panel)
{
esp_err_t ret = ESP_OK;
esp_rgb_panel_t *rgb_panel = NULL;
ESP_GOTO_ON_FALSE(rgb_panel_config && ret_panel, ESP_ERR_INVALID_ARG, err_arg, TAG, "invalid parameter");
ESP_GOTO_ON_FALSE(rgb_panel_config->data_width == 16, ESP_ERR_NOT_SUPPORTED, err_arg, TAG,
"unsupported data width %d", rgb_panel_config->data_width);
// 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;
if (fb_size > num_dma_nodes * DMA_DESCRIPTOR_BUFFER_MAX_SIZE) {
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);
ESP_GOTO_ON_FALSE(rgb_panel, ESP_ERR_NO_MEM, no_mem_panel, TAG, "no mem for rgb panel");
rgb_panel->num_dma_nodes = num_dma_nodes;
// alloc frame buffer, currently we have to put the frame buffer in SRAM
rgb_panel->fb = heap_caps_calloc(1, fb_size, MALLOC_CAP_INTERNAL);
ESP_GOTO_ON_FALSE(rgb_panel->fb, ESP_ERR_NO_MEM, no_mem_fb, TAG, "no mem for frame buffer");
rgb_panel->fb_size = fb_size;
// semaphore indicates new frame trans done
rgb_panel->done_sem = xSemaphoreCreateBinary();
ESP_GOTO_ON_FALSE(rgb_panel->done_sem, ESP_ERR_NO_MEM, no_mem_sem, TAG, "create done sem failed");
xSemaphoreGive(rgb_panel->done_sem); // initialize the semaphore count to 1
// register to platform
int panel_id = lcd_com_register_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel);
ESP_GOTO_ON_FALSE(panel_id >= 0, ESP_ERR_NOT_FOUND, no_slot, TAG, "no free rgb panel slot");
rgb_panel->panel_id = panel_id;
// enable APB to access LCD registers
periph_module_enable(lcd_periph_signals.panels[panel_id].module);
// initialize HAL layer, so we can call LL APIs later
lcd_hal_init(&rgb_panel->hal, panel_id);
// install interrupt service, (LCD peripheral shares the interrupt source with Camera by different mask)
int isr_flags = 0;
ret = esp_intr_alloc_intrstatus(lcd_periph_signals.panels[panel_id].irq_id, isr_flags,
lcd_ll_get_interrupt_status_reg(rgb_panel->hal.dev),
LCD_LL_EVENT_VSYNC_END, lcd_default_isr_handler, rgb_panel, &rgb_panel->intr);
ESP_GOTO_ON_ERROR(ret, no_int, TAG, "install interrupt failed");
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, false); // disable all interrupts
lcd_ll_clear_interrupt_status(rgb_panel->hal.dev, UINT32_MAX); // clear pending interrupt
// install DMA service
rgb_panel->flags.stream_mode = !rgb_panel_config->flags.relax_on_idle;
ret = lcd_rgb_panel_create_trans_link(rgb_panel);
ESP_GOTO_ON_ERROR(ret, no_dma, TAG, "install DMA failed");
// configure GPIO
ret = lcd_rgb_panel_configure_gpio(rgb_panel, rgb_panel_config);
ESP_GOTO_ON_ERROR(ret, no_gpio, TAG, "configure GPIO failed");
// fill other rgb panel runtime parameters
memcpy(rgb_panel->data_gpio_nums, rgb_panel_config->data_gpio_nums, SOC_LCD_RGB_DATA_WIDTH);
rgb_panel->timings = rgb_panel_config->timings;
rgb_panel->data_width = rgb_panel_config->data_width;
rgb_panel->disp_gpio_num = rgb_panel_config->disp_gpio_num;
rgb_panel->flags.disp_en_level = !rgb_panel_config->flags.disp_active_low;
rgb_panel->on_frame_trans_done = rgb_panel_config->on_frame_trans_done;
rgb_panel->user_data = rgb_panel_config->user_data;
// fill function table
rgb_panel->base.del = rgb_panel_del;
rgb_panel->base.reset = rgb_panel_reset;
rgb_panel->base.init = rgb_panel_init;
rgb_panel->base.draw_bitmap = rgb_panel_draw_bitmap;
rgb_panel->base.disp_off = rgb_panel_disp_off;
rgb_panel->base.invert_color = rgb_panel_invert_color;
rgb_panel->base.mirror = rgb_panel_mirror;
rgb_panel->base.swap_xy = rgb_panel_swap_xy;
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);
return ESP_OK;
no_gpio:
gdma_disconnect(rgb_panel->dma_chan);
gdma_del_channel(rgb_panel->dma_chan);
no_dma:
esp_intr_free(rgb_panel->intr);
no_int:
periph_module_disable(lcd_periph_signals.panels[rgb_panel->panel_id].module);
lcd_com_remove_device(LCD_COM_DEVICE_TYPE_RGB, rgb_panel->panel_id);
no_slot:
vSemaphoreDelete(rgb_panel->done_sem);
no_mem_sem:
free(rgb_panel->fb);
no_mem_fb:
free(rgb_panel);
no_mem_panel:
err_arg:
return ret;
}
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);
free(rgb_panel);
ESP_LOGD(TAG, "del rgb panel(%d)", panel_id);
return ESP_OK;
}
static esp_err_t rgb_panel_reset(esp_lcd_panel_t *panel)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
lcd_ll_fifo_reset(rgb_panel->hal.dev);
lcd_ll_reset(rgb_panel->hal.dev);
return ESP_OK;
}
static esp_err_t rgb_panel_init(esp_lcd_panel_t *panel)
{
esp_err_t ret = ESP_OK;
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
// configure clock
lcd_ll_enable_clock(rgb_panel->hal.dev, true);
// set peripheral clock resolution
rgb_panel->resolution_hz = lcd_com_select_periph_clock(&rgb_panel->hal);
// set PCLK frequency
uint32_t pclk_prescale = rgb_panel->resolution_hz / rgb_panel->timings.pclk_hz;
ESP_GOTO_ON_FALSE(pclk_prescale <= LCD_LL_CLOCK_PRESCALE_MAX, ESP_ERR_NOT_SUPPORTED, err, TAG,
"prescaler can't satisfy PCLK clock %uHz", rgb_panel->timings.pclk_hz);
lcd_ll_set_pixel_clock_prescale(rgb_panel->hal.dev, pclk_prescale);
rgb_panel->timings.pclk_hz = rgb_panel->resolution_hz / pclk_prescale;
// pixel clock phase and polarity
lcd_ll_set_clock_idle_level(rgb_panel->hal.dev, !rgb_panel->timings.flags.pclk_idle_low);
lcd_ll_set_pixel_clock_edge(rgb_panel->hal.dev, rgb_panel->timings.flags.pclk_active_neg);
// enable RGB mode and set data width
lcd_ll_enable_rgb_mode(rgb_panel->hal.dev, true);
lcd_ll_set_data_width(rgb_panel->hal.dev, rgb_panel->data_width);
lcd_ll_set_phase_cycles(rgb_panel->hal.dev, 0, 0, 1); // enable data phase only
// number of data cycles is controlled by DMA buffer size
lcd_ll_enable_output_always_on(rgb_panel->hal.dev, true);
// configure HSYNC, VSYNC, DE signal idle state level
lcd_ll_set_idle_level(rgb_panel->hal.dev, !rgb_panel->timings.flags.hsync_idle_low,
!rgb_panel->timings.flags.vsync_idle_low, rgb_panel->timings.flags.de_idle_high);
// configure blank region timing
lcd_ll_set_blank_cycles(rgb_panel->hal.dev, 1, 1); // RGB panel always has a front and back blank (porch region)
lcd_ll_set_horizontal_timing(rgb_panel->hal.dev, rgb_panel->timings.hsync_pulse_width,
rgb_panel->timings.hsync_back_porch, rgb_panel->timings.h_res,
rgb_panel->timings.hsync_front_porch);
lcd_ll_set_vertical_timing(rgb_panel->hal.dev, rgb_panel->timings.vsync_pulse_width,
rgb_panel->timings.vsync_back_porch, rgb_panel->timings.v_res,
rgb_panel->timings.vsync_front_porch);
// output hsync even in porch region
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);
// trigger interrupt on the end of frame
lcd_ll_enable_interrupt(rgb_panel->hal.dev, LCD_LL_EVENT_VSYNC_END, true);
ESP_LOGD(TAG, "rgb panel(%d) start, pclk=%uHz", rgb_panel->panel_id, rgb_panel->timings.pclk_hz);
err:
return ret;
}
static esp_err_t rgb_panel_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
// adjust the flush window by adding extra gap
x_start += rgb_panel->x_gap;
y_start += rgb_panel->y_gap;
x_end += rgb_panel->x_gap;
y_end += rgb_panel->y_gap;
// round the boundary
x_start = MIN(x_start, rgb_panel->timings.h_res);
x_end = MIN(x_end, rgb_panel->timings.h_res);
y_start = MIN(y_start, rgb_panel->timings.v_res);
y_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_pre_pixel = rgb_panel->data_width / 8;
int pixels_pre_line = rgb_panel->timings.h_res;
const uint8_t *from = (const uint8_t *)color_data;
uint8_t (*to)[pixels_pre_line][bytes_pre_pixel] = (uint8_t (*)[pixels_pre_line][bytes_pre_pixel])rgb_panel->fb;
// manipulate the frame buffer
for (int j = y_start; j < y_end; j++) {
for (int i = x_start; i < x_end; i++) {
for (int k = 0; k < bytes_pre_pixel; k++) {
to[j][i][k] = *from++;
}
}
}
// we don't care the exact frame ID, as long as it's different from the previous one
rgb_panel->new_frame_id++;
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);
}
// start LCD engine
lcd_ll_start(rgb_panel->hal.dev);
return ESP_OK;
}
static esp_err_t rgb_panel_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
int panel_id = rgb_panel->panel_id;
// inverting the data line by GPIO matrix
for (int i = 0; i < rgb_panel->data_width; i++) {
esp_rom_gpio_connect_out_signal(rgb_panel->data_gpio_nums[i], lcd_periph_signals.panels[panel_id].data_sigs[i],
invert_color_data, false);
}
return ESP_OK;
}
static esp_err_t rgb_panel_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
{
return ESP_ERR_NOT_SUPPORTED;
}
static esp_err_t rgb_panel_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
{
return ESP_ERR_NOT_SUPPORTED;
}
static esp_err_t rgb_panel_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
rgb_panel->x_gap = x_gap;
rgb_panel->x_gap = y_gap;
return ESP_OK;
}
static esp_err_t rgb_panel_disp_off(esp_lcd_panel_t *panel, bool off)
{
esp_rgb_panel_t *rgb_panel = __containerof(panel, esp_rgb_panel_t, base);
if (rgb_panel->disp_gpio_num < 0) {
return ESP_ERR_NOT_SUPPORTED;
}
if (off) { // turn off screen
gpio_set_level(rgb_panel->disp_gpio_num, !rgb_panel->flags.disp_en_level);
} else { // turn on screen
gpio_set_level(rgb_panel->disp_gpio_num, rgb_panel->flags.disp_en_level);
}
return ESP_OK;
}
static esp_err_t lcd_rgb_panel_configure_gpio(esp_rgb_panel_t *panel, const esp_lcd_rgb_panel_config_t *panel_config)
{
int panel_id = panel->panel_id;
// check validation of GPIO number
bool valid_gpio = (panel_config->hsync_gpio_num >= 0) && (panel_config->vsync_gpio_num >= 0) &&
(panel_config->pclk_gpio_num >= 0);
for (size_t i = 0; i < panel_config->data_width; i++) {
valid_gpio = valid_gpio && (panel_config->data_gpio_nums[i] >= 0);
}
if (!valid_gpio) {
return ESP_ERR_INVALID_ARG;
}
// connect peripheral signals via GPIO matrix
for (size_t i = 0; i < panel_config->data_width; i++) {
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->data_gpio_nums[i]], PIN_FUNC_GPIO);
gpio_set_direction(panel_config->data_gpio_nums[i], GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(panel_config->data_gpio_nums[i],
lcd_periph_signals.panels[panel_id].data_sigs[i], false, false);
}
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->hsync_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(panel_config->hsync_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(panel_config->hsync_gpio_num,
lcd_periph_signals.panels[panel_id].hsync_sig, false, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->vsync_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(panel_config->vsync_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(panel_config->vsync_gpio_num,
lcd_periph_signals.panels[panel_id].vsync_sig, false, false);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->pclk_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(panel_config->pclk_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(panel_config->pclk_gpio_num,
lcd_periph_signals.panels[panel_id].pclk_sig, false, false);
// DE signal might not be necessary for some RGB LCD
if (panel_config->de_gpio_num >= 0) {
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->de_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(panel_config->de_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(panel_config->de_gpio_num,
lcd_periph_signals.panels[panel_id].de_sig, false, false);
}
// disp enable GPIO is optional
if (panel_config->disp_gpio_num >= 0) {
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[panel_config->disp_gpio_num], PIN_FUNC_GPIO);
gpio_set_direction(panel_config->disp_gpio_num, GPIO_MODE_OUTPUT);
esp_rom_gpio_connect_out_signal(panel_config->disp_gpio_num, SIG_GPIO_OUT_IDX, false, false);
}
return ESP_OK;
}
static esp_err_t lcd_rgb_panel_create_trans_link(esp_rgb_panel_t *panel)
{
esp_err_t ret = ESP_OK;
// chain DMA descriptors
for (int i = 0; i < panel->num_dma_nodes; i++) {
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
}
// 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
gdma_channel_alloc_config_t dma_chan_config = {
.direction = GDMA_CHANNEL_DIRECTION_TX,
};
ret = gdma_new_channel(&dma_chan_config, &panel->dma_chan);
ESP_GOTO_ON_ERROR(ret, err, TAG, "alloc DMA channel failed");
gdma_connect(panel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_LCD, 0));
// the start of DMA should be prior to the start of LCD engine
gdma_start(panel->dma_chan, (intptr_t)panel->dma_nodes);
err:
return ret;
}
IRAM_ATTR static void lcd_default_isr_handler(void *args)
{
esp_rgb_panel_t *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);
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, panel->user_data)) {
need_yield = true;
}
}
xSemaphoreGiveFromISR(panel->done_sem, &high_task_woken);
if (high_task_woken == pdTRUE) {
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;
}
}
if (need_yield) {
portYIELD_FROM_ISR();
}
}
#endif // SOC_LCDCAM_SUPPORTED

View File

@ -0,0 +1,3 @@
idf_component_register(SRC_DIRS .
PRIV_INCLUDE_DIRS .
PRIV_REQUIRES cmock test_utils esp_lcd)

View File

@ -0,0 +1,7 @@
#
#Component Makefile
#
COMPONENT_SRCDIRS := .
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive

View File

@ -0,0 +1,125 @@
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "unity.h"
#include "test_utils.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_system.h"
#define TEST_LCD_H_RES (128)
#define TEST_LCD_V_RES (64)
#define TEST_I2C_SDA_GPIO (3)
#define TEST_I2C_SCL_GPIO (4)
#define TEST_I2C_HOST_ID (0)
#define TEST_I2C_DEV_ADDR (0x3C)
#define TEST_LCD_PIXEL_CLOCK_HZ (400 * 1000)
TEST_CASE("lcd panel with i2c interface (ssd1306)", "[lcd]")
{
const uint8_t pattern[][16] = {{
0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00,
0x00, 0x7E, 0x42, 0x42, 0x42, 0x42, 0x7E, 0x00
},
{
0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81,
0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81
}
};
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
};
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));
for (int i = 0; i < TEST_LCD_H_RES / 16; i++) {
for (int j = 0; j < TEST_LCD_V_RES / 8; j++) {
TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, i * 16, j * 8, i * 16 + 16, j * 8 + 8, pattern[i & 0x01]));
}
}
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
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, void *user_data, void *event_data)
{
lv_disp_t *disp = *(lv_disp_t **)user_data;
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
.on_color_trans_done = notify_lvgl_ready_to_flush,
.user_data = &disp,
};
TEST_ESP_OK(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)TEST_I2C_HOST_ID, &io_config, &io_handle));
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_panel_dev_config_t panel_config = {
.bits_per_pixel = 1,
.color_space = ESP_LCD_COLOR_SPACE_MONOCHROME,
.reset_gpio_num = -1,
};
TEST_ESP_OK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle));
TEST_ESP_OK(esp_lcd_panel_reset(panel_handle));
TEST_ESP_OK(esp_lcd_panel_init(panel_handle));
test_lvgl_task_loop(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES, &disp);
}
#endif // CONFIG_LV_COLOR_DEPTH_1
#endif // CONFIG_LV_USE_USER_DATA

View File

@ -0,0 +1,355 @@
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "test_utils.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "soc/soc_caps.h"
#include "driver/gpio.h"
#define TEST_LCD_H_RES (240)
#define TEST_LCD_V_RES (280)
#define TEST_LCD_BK_LIGHT_GPIO (1)
#define TEST_LCD_RST_GPIO (2)
#define TEST_LCD_CS_GPIO (4)
#define TEST_LCD_DC_GPIO (5)
#define TEST_LCD_PCLK_GPIO (6)
#define TEST_LCD_DATA0_GPIO (33)
#define TEST_LCD_DATA1_GPIO (34)
#define TEST_LCD_DATA2_GPIO (35)
#define TEST_LCD_DATA3_GPIO (36)
#define TEST_LCD_DATA4_GPIO (37)
#define TEST_LCD_DATA5_GPIO (38)
#define TEST_LCD_DATA6_GPIO (39)
#define TEST_LCD_DATA7_GPIO (40)
#define TEST_LCD_DATA8_GPIO (41)
#define TEST_LCD_DATA9_GPIO (42)
#define TEST_LCD_DATA10_GPIO (15)
#define TEST_LCD_DATA11_GPIO (16)
#define TEST_LCD_DATA12_GPIO (17)
#define TEST_LCD_DATA13_GPIO (18)
#define TEST_LCD_DATA14_GPIO (19)
#define TEST_LCD_DATA15_GPIO (20)
#if SOC_LCD_I80_SUPPORTED
TEST_CASE("lcd i80 bus and device allocation", "[lcd]")
{
esp_lcd_i80_bus_handle_t i80_buses[SOC_LCD_I80_BUSES] = {};
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,
},
.data_width = 8,
.max_transfer_bytes = TEST_LCD_H_RES * 40 * sizeof(uint16_t)
};
for (int i = 0; i < SOC_LCD_I80_BUSES; i++) {
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_buses[i]));
}
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, esp_lcd_new_i80_bus(&bus_config, &i80_buses[0]));
esp_lcd_panel_io_handle_t io_handles[10] = {};
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = 5000000,
.trans_queue_depth = 4,
};
for (int i = 0; i < 10; i++) {
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_buses[0], &io_config, &io_handles[i]));
}
// can't delete bus handle before we delete all devices
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, esp_lcd_del_i80_bus(i80_buses[0]));
for (int i = 0; i < 10; i++) {
TEST_ESP_OK(esp_lcd_panel_io_del(io_handles[i]));
}
for (int i = 0; i < SOC_LCD_I80_BUSES; i++) {
TEST_ESP_OK(esp_lcd_del_i80_bus(i80_buses[i]));
}
}
TEST_CASE("lcd i80 device swap color bytes", "[lcd]")
{
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,
},
.data_width = 8,
.max_transfer_bytes = 20,
};
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_handle_t io_handles[4] = {};
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = 5000000,
.trans_queue_depth = 10,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
};
io_config.flags.reverse_color_bits = 0;
io_config.flags.swap_color_bytes = 0;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[0]));
io_config.flags.reverse_color_bits = 0;
io_config.flags.swap_color_bytes = 1;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[1]));
io_config.flags.reverse_color_bits = 1;
io_config.flags.swap_color_bytes = 0;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[2]));
io_config.flags.reverse_color_bits = 1;
io_config.flags.swap_color_bytes = 1;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[3]));
for (int i = 0; i < 4; i++) {
esp_lcd_panel_io_tx_param(io_handles[i], 0xA5, 8, (uint8_t[]) {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06
}, 6);
esp_lcd_panel_io_tx_color(io_handles[i], 0x5A, 8, (uint8_t[]) {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06
}, 6);
TEST_ESP_OK(esp_lcd_panel_io_del(io_handles[i]));
}
TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus));
}
TEST_CASE("lcd i80 device clock mode", "[lcd]")
{
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,
},
.data_width = 8,
.max_transfer_bytes = 20,
};
TEST_ESP_OK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_handle_t io_handles[4] = {};
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = TEST_LCD_CS_GPIO,
.pclk_hz = 5000000,
.trans_queue_depth = 10,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
};
io_config.flags.pclk_idle_low = 0;
io_config.flags.pclk_active_neg = 0;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[0]));
io_config.flags.pclk_idle_low = 0;
io_config.flags.pclk_active_neg = 1;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[1]));
io_config.flags.pclk_idle_low = 1;
io_config.flags.pclk_active_neg = 0;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[2]));
io_config.flags.pclk_idle_low = 1;
io_config.flags.pclk_active_neg = 1;
TEST_ESP_OK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handles[3]));
for (int i = 0; i < 4; i++) {
esp_lcd_panel_io_tx_param(io_handles[i], 0xA5, 8, (uint8_t[]) {
0x01, 0x02, 0x03, 0x04, 0x05, 0x06
}, 6);
TEST_ESP_OK(esp_lcd_panel_io_del(io_handles[i]));
}
TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus));
}
TEST_CASE("lcd panel with i80 interface (st7789)", "[lcd]")
{
#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);
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,
},
.data_width = 8,
.max_transfer_bytes = TEST_IMG_SIZE + 10,
};
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 = 5000000,
.trans_queue_depth = 10,
.dc_levels = {
.dc_idle_level = 0,
.dc_cmd_level = 0,
.dc_dummy_level = 0,
.dc_data_level = 1,
},
};
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);
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);
}
esp_lcd_panel_disp_off(panel_handle, true); // turn off screen
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus));
TEST_ESP_OK(gpio_reset_pin(TEST_LCD_BK_LIGHT_GPIO));
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_io_handle_t panel_io, void *user_data, void *event_data)
{
lv_disp_t *disp = *(lv_disp_t **)user_data;
lv_disp_flush_ready(&disp->driver);
return false;
}
TEST_CASE("lvgl gui with i80 interface (st7789)", "[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,
},
.data_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 = 8000000,
.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_data = &disp
};
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);
}
#endif // CONFIG_LV_USE_USER_DATA
#endif // SOC_LCD_I80_SUPPORTED

View File

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

View File

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

View File

@ -0,0 +1,164 @@
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "test_utils.h"
#include "esp_lcd_panel_rgb.h"
#include "esp_lcd_panel_ops.h"
#include "soc/soc_caps.h"
#define TEST_LCD_H_RES (480)
#define TEST_LCD_V_RES (272)
#define TEST_LCD_VSYNC_GPIO (19)
#define TEST_LCD_HSYNC_GPIO (18)
#define TEST_LCD_DE_GPIO (-1)
#define TEST_LCD_PCLK_GPIO (17)
#define TEST_LCD_DATA0_GPIO (42) // B0
#define TEST_LCD_DATA1_GPIO (41) // B1
#define TEST_LCD_DATA2_GPIO (40) // B2
#define TEST_LCD_DATA3_GPIO (39) // B3
#define TEST_LCD_DATA4_GPIO (38) // B4
#define TEST_LCD_DATA5_GPIO (4) // G0
#define TEST_LCD_DATA6_GPIO (5) // G1
#define TEST_LCD_DATA7_GPIO (6) // G2
#define TEST_LCD_DATA8_GPIO (7) // G3
#define TEST_LCD_DATA9_GPIO (15) // G4
#define TEST_LCD_DATA10_GPIO (16) // G5
#define TEST_LCD_DATA11_GPIO (37) // R0
#define TEST_LCD_DATA12_GPIO (36) // R1
#define TEST_LCD_DATA13_GPIO (35) // R2
#define TEST_LCD_DATA14_GPIO (34) // R3
#define TEST_LCD_DATA15_GPIO (33) // R4
#define TEST_LCD_DISP_EN_GPIO (-1)
#if SOC_LCD_RGB_SUPPORTED
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);
esp_lcd_panel_handle_t panel_handle = NULL;
esp_lcd_rgb_panel_config_t panel_config = {
.data_width = 16,
.disp_gpio_num = TEST_LCD_DISP_EN_GPIO,
.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 = 20000000,
.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,
},
};
// 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, void *user_data)
{
lv_disp_t *disp = *(lv_disp_t **)user_data;
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 = 20000000,
.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,
},
.on_frame_trans_done = notify_lvgl_ready_to_flush,
.user_data = &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);
}
#endif // CONFIG_LV_USE_USER_DATA
#endif // SOC_LCD_RGB_SUPPORTED

View File

@ -0,0 +1,157 @@
#include <stdio.h>
#include <string.h>
#include "sdkconfig.h"
#include "unity.h"
#include "test_utils.h"
#include "driver/spi_master.h"
#include "driver/gpio.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_system.h"
#define TEST_LCD_H_RES (240)
#define TEST_LCD_V_RES (280)
#define TEST_SPI_CLK_GPIO (2)
#define TEST_SPI_MOSI_GPIO (4)
#define TEST_LCD_RST_GPIO (5)
#define TEST_LCD_DC_GPIO (18)
#define TEST_LCD_BK_LIGHT_GPIO (19)
#define TEST_SPI_CS_GPIO (0)
#define TEST_SPI_HOST_ID (1)
#define TEST_LCD_PIXEL_CLOCK_HZ (20 * 1000 * 1000)
TEST_CASE("lcd panel with spi interface (st7789)", "[lcd]")
{
#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);
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));
spi_bus_config_t buscfg = {
.miso_io_num = -1,
.mosi_io_num = TEST_SPI_MOSI_GPIO,
.sclk_io_num = TEST_SPI_CLK_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = TEST_LCD_H_RES * TEST_LCD_V_RES * sizeof(uint16_t)
};
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST_ID, &buscfg, SPI_DMA_CH_AUTO));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.cs_gpio_num = TEST_SPI_CS_GPIO,
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.spi_mode = 0,
.trans_queue_depth = 10,
};
TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TEST_SPI_HOST_ID, &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);
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);
}
esp_lcd_panel_disp_off(panel_handle, true); // turn off screen
TEST_ESP_OK(esp_lcd_panel_del(panel_handle));
TEST_ESP_OK(esp_lcd_panel_io_del(io_handle));
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
}
// 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, void *user_data, void *event_data)
{
lv_disp_t *disp = *(lv_disp_t **)user_data;
lv_disp_flush_ready(&disp->driver);
return false;
}
TEST_CASE("lvgl gui with spi interface (st7789)", "[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));
spi_bus_config_t buscfg = {
.miso_io_num = -1,
.mosi_io_num = TEST_SPI_MOSI_GPIO,
.sclk_io_num = TEST_SPI_CLK_GPIO,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
.max_transfer_sz = TEST_LCD_H_RES * TEST_LCD_V_RES * 2
};
TEST_ESP_OK(spi_bus_initialize(TEST_SPI_HOST_ID, &buscfg, 1));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = TEST_LCD_DC_GPIO,
.cs_gpio_num = TEST_SPI_CS_GPIO,
.pclk_hz = TEST_LCD_PIXEL_CLOCK_HZ,
.spi_mode = 0,
.trans_queue_depth = 10,
.on_color_trans_done = notify_lvgl_ready_to_flush,
.user_data = &disp // we must use "address of disp" here, since the disp object has not been allocated
};
TEST_ESP_OK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)TEST_SPI_HOST_ID, &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);
}
#endif // CONFIG_LV_USE_USER_DATA

View File

@ -28,7 +28,6 @@ extern "C" {
// Interrupt event, bit mask
#define LCD_LL_EVENT_VSYNC_END (1 << 0)
#define LCD_LL_EVENT_TRANS_DONE (1 << 1)
#define LCD_LL_EVENT_MASK (LCD_LL_EVENT_VSYNC_END | LCD_LL_EVENT_TRANS_DONE)
// Clock source ID represented in register
#define LCD_LL_CLOCK_SRC_XTAL (1)
@ -185,20 +184,30 @@ static inline void lcd_ll_enable_auto_next_frame(lcd_cam_dev_t *dev, bool en)
dev->lcd_misc.lcd_next_frame_en = en;
}
static inline void lcd_ll_enable_output_hsync_in_porch_region(lcd_cam_dev_t *dev, bool en)
{
dev->lcd_ctrl2.lcd_hs_blank_en = en;
}
static inline void lcd_ll_set_hsync_position(lcd_cam_dev_t *dev, uint32_t offset_in_line)
{
dev->lcd_ctrl2.lcd_hsync_position = offset_in_line;
}
static inline void lcd_ll_set_horizontal_timing(lcd_cam_dev_t *dev, uint32_t hsw, uint32_t hbp, uint32_t active_width, uint32_t hfp)
{
dev->lcd_ctrl2.lcd_hsync_width = hsw;
dev->lcd_ctrl.lcd_hb_front = hbp;
dev->lcd_ctrl1.lcd_ha_width = active_width;
dev->lcd_ctrl1.lcd_ht_width = hsw + hbp + active_width + hfp;
dev->lcd_ctrl2.lcd_hsync_width = hsw - 1;
dev->lcd_ctrl.lcd_hb_front = hbp + hsw - 1;
dev->lcd_ctrl1.lcd_ha_width = active_width - 1;
dev->lcd_ctrl1.lcd_ht_width = hsw + hbp + active_width + hfp - 1;
}
static inline void lcd_ll_set_vertical_timing(lcd_cam_dev_t *dev, uint32_t vsw, uint32_t vbp, uint32_t active_height, uint32_t vfp)
{
dev->lcd_ctrl2.lcd_vsync_width = vsw;
dev->lcd_ctrl1.lcd_vb_front = vbp;
dev->lcd_ctrl.lcd_va_height = active_height;
dev->lcd_ctrl.lcd_vt_height = vsw + vbp + active_height + vfp;
dev->lcd_ctrl2.lcd_vsync_width = vsw - 1;
dev->lcd_ctrl1.lcd_vb_front = vbp + vsw - 1;
dev->lcd_ctrl.lcd_va_height = active_height - 1;
dev->lcd_ctrl.lcd_vt_height = vsw + vbp + active_height + vfp - 1;
}
static inline void lcd_ll_set_idle_level(lcd_cam_dev_t *dev, bool hsync_idle_level, bool vsync_idle_level, bool de_idle_level)

View File

@ -9,7 +9,7 @@
#define SOC_PCNT_SUPPORTED 1
#define SOC_TWAI_SUPPORTED 1
#define SOC_GDMA_SUPPORTED 1
#define SOC_I80_LCD_SUPPORTED 1
#define SOC_LCDCAM_SUPPORTED 1
#define SOC_MCPWM_SUPPORTED 1
#define SOC_DEDICATED_GPIO_SUPPORTED 1
#define SOC_CPU_CORES_NUM 2
@ -88,9 +88,13 @@
/*-------------------------- LCD CAPS ----------------------------------------*/
/* Notes: On esp32-s3, I80 bus and RGB timing generator can't work at the same time */
#define SOC_LCD_I80_SUPPORTED (1) /*!< Intel 8080 LCD is supported */
#define SOC_LCD_RGB_SUPPORTED (1) /*!< RGB LCD is supported */
#define SOC_LCD_I80_BUSES (1) /*!< Has one LCD Intel 8080 bus */
#define SOC_LCD_RGB_PANELS (1) /*!< Support one RGB LCD panel */
#define SOC_LCD_MAX_DATA_WIDTH (16) /*!< Maximum number of LCD data lines */
#define SOC_LCD_I80_BUS_WIDTH (16) /*!< Intel 8080 bus width */
#define SOC_LCD_RGB_DATA_WIDTH (16) /*!< Number of LCD data lines */
/*-------------------------- RTCIO CAPS --------------------------------------*/
#include "rtc_io_caps.h"

View File

@ -25,7 +25,7 @@ typedef struct {
struct {
const periph_module_t module;
const int irq_id;
const int data_sigs[SOC_LCD_MAX_DATA_WIDTH];
const int data_sigs[SOC_LCD_I80_BUS_WIDTH];
const int cs_sig;
const int dc_sig;
const int wr_sig;
@ -33,7 +33,7 @@ typedef struct {
struct {
const periph_module_t module;
const int irq_id;
const int data_sigs[SOC_LCD_MAX_DATA_WIDTH];
const int data_sigs[SOC_LCD_RGB_DATA_WIDTH];
const int hsync_sig;
const int vsync_sig;
const int pclk_sig;