mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
lcd: add esp_lcd component
* Support intel 8080 LCD panel IO on ESP32-S3 * Support RGB LCD panel on ESP32-S3 * Support SPI && I2C LCD panel IO on all esp chips
This commit is contained in:
parent
270ed70f3e
commit
e10202a608
@ -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
|
||||
|
14
components/esp_lcd/CMakeLists.txt
Normal file
14
components/esp_lcd/CMakeLists.txt
Normal 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})
|
20
components/esp_lcd/Kconfig
Normal file
20
components/esp_lcd/Kconfig
Normal 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
|
6
components/esp_lcd/component.mk
Normal file
6
components/esp_lcd/component.mk
Normal file
@ -0,0 +1,6 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_PRIV_INCLUDEDIRS := interface
|
||||
COMPONENT_SRCDIRS := src
|
201
components/esp_lcd/include/esp_lcd_panel_io.h
Normal file
201
components/esp_lcd/include/esp_lcd_panel_io.h
Normal 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
|
126
components/esp_lcd/include/esp_lcd_panel_ops.h
Normal file
126
components/esp_lcd/include/esp_lcd_panel_ops.h
Normal 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
|
77
components/esp_lcd/include/esp_lcd_panel_rgb.h
Normal file
77
components/esp_lcd/include/esp_lcd_panel_rgb.h
Normal 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
|
57
components/esp_lcd/include/esp_lcd_panel_vendor.h
Normal file
57
components/esp_lcd/include/esp_lcd_panel_vendor.h
Normal 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
|
26
components/esp_lcd/include/esp_lcd_types.h
Normal file
26
components/esp_lcd/include/esp_lcd_types.h
Normal 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
|
126
components/esp_lcd/interface/esp_lcd_panel_interface.h
Normal file
126
components/esp_lcd/interface/esp_lcd_panel_interface.h
Normal 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
|
66
components/esp_lcd/interface/esp_lcd_panel_io_interface.h
Normal file
66
components/esp_lcd/interface/esp_lcd_panel_io_interface.h
Normal 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
|
126
components/esp_lcd/src/esp_lcd_common.c
Normal file
126
components/esp_lcd/src/esp_lcd_common.c
Normal 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
|
70
components/esp_lcd/src/esp_lcd_common.h
Normal file
70
components/esp_lcd/src/esp_lcd_common.h
Normal 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
|
54
components/esp_lcd/src/esp_lcd_panel_commands.h
Normal file
54
components/esp_lcd/src/esp_lcd_panel_commands.h
Normal 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
|
29
components/esp_lcd/src/esp_lcd_panel_io.c
Normal file
29
components/esp_lcd/src/esp_lcd_panel_io.c
Normal 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);
|
||||
}
|
149
components/esp_lcd/src/esp_lcd_panel_io_i2c.c
Normal file
149
components/esp_lcd/src/esp_lcd_panel_io_i2c.c
Normal 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;
|
||||
}
|
518
components/esp_lcd/src/esp_lcd_panel_io_i80.c
Normal file
518
components/esp_lcd/src/esp_lcd_panel_io_i80.c
Normal 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
|
230
components/esp_lcd/src/esp_lcd_panel_io_spi.c
Normal file
230
components/esp_lcd/src/esp_lcd_panel_io_spi.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
65
components/esp_lcd/src/esp_lcd_panel_ops.c
Normal file
65
components/esp_lcd/src/esp_lcd_panel_ops.c
Normal 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);
|
||||
}
|
235
components/esp_lcd/src/esp_lcd_panel_ssd1306.c
Normal file
235
components/esp_lcd/src/esp_lcd_panel_ssd1306.c
Normal 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;
|
||||
}
|
263
components/esp_lcd/src/esp_lcd_panel_st7789.c
Normal file
263
components/esp_lcd/src/esp_lcd_panel_st7789.c
Normal 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;
|
||||
}
|
443
components/esp_lcd/src/esp_lcd_rgb_panel.c
Normal file
443
components/esp_lcd/src/esp_lcd_rgb_panel.c
Normal 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
|
3
components/esp_lcd/test/CMakeLists.txt
Normal file
3
components/esp_lcd/test/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
idf_component_register(SRC_DIRS .
|
||||
PRIV_INCLUDE_DIRS .
|
||||
PRIV_REQUIRES cmock test_utils esp_lcd)
|
7
components/esp_lcd/test/component.mk
Normal file
7
components/esp_lcd/test/component.mk
Normal file
@ -0,0 +1,7 @@
|
||||
#
|
||||
#Component Makefile
|
||||
#
|
||||
|
||||
COMPONENT_SRCDIRS := .
|
||||
|
||||
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
|
125
components/esp_lcd/test/test_i2c_lcd_panel.c
Normal file
125
components/esp_lcd/test/test_i2c_lcd_panel.c
Normal 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
|
355
components/esp_lcd/test/test_i80_lcd_panel.c
Normal file
355
components/esp_lcd/test/test_i80_lcd_panel.c
Normal 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
|
113
components/esp_lcd/test/test_lvgl_port.c
Normal file
113
components/esp_lcd/test/test_lvgl_port.c
Normal 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
|
4
components/esp_lcd/test/test_lvgl_port.h
Normal file
4
components/esp_lcd/test/test_lvgl_port.h
Normal 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);
|
164
components/esp_lcd/test/test_rgb_panel.c
Normal file
164
components/esp_lcd/test/test_rgb_panel.c
Normal 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
|
157
components/esp_lcd/test/test_spi_lcd_panel.c
Normal file
157
components/esp_lcd/test/test_spi_lcd_panel.c
Normal 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
|
@ -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)
|
||||
|
@ -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"
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user