From 2f778456fd0ed4bb975e3c9bbd04047204103f9a Mon Sep 17 00:00:00 2001 From: lvhaiyu Date: Wed, 7 Aug 2024 10:07:06 +0800 Subject: [PATCH] feat(lcd): Modified the ILI9881 and ST7796 LCD component --- .build-test-rules.yml | 12 + .../lcd/esp_lcd_ili9881c/esp_lcd_ili9881c.c | 718 ++++++++++-------- .../lcd/esp_lcd_ili9881c/idf_component.yml | 4 +- .../include/esp_lcd_ili9881c.h | 111 ++- .../esp_lcd_ili9881c/test_apps/CMakeLists.txt | 6 + .../test_apps/main/CMakeLists.txt | 1 + .../test_apps/main/idf_component.yml | 8 + .../test_apps/main/test_esp_lcd_ili9881c.c | 263 +++++++ .../test_apps/sdkconfig.defaults | 3 + .../test_apps/sdkconfig.defaults.esp32p4 | 4 + components/lcd/esp_lcd_st7796/CMakeLists.txt | 3 +- components/lcd/esp_lcd_st7796/README.md | 56 +- .../lcd/esp_lcd_st7796/esp_lcd_st7796.c | 351 +-------- .../esp_lcd_st7796/esp_lcd_st7796_general.c | 354 +++++++++ .../lcd/esp_lcd_st7796/esp_lcd_st7796_mipi.c | 296 ++++++++ .../lcd/esp_lcd_st7796/idf_component.yml | 5 +- .../esp_lcd_st7796/include/esp_lcd_st7796.h | 78 +- .../priv_include/esp_lcd_st7796_interface.h | 45 ++ .../test_apps/main/CMakeLists.txt | 4 +- .../test_apps/main/test_app_main.c | 47 ++ ...st7796.c => test_esp_lcd_st7796_general.c} | 49 +- .../test_apps/main/test_esp_lcd_st7796_mipi.c | 225 ++++++ 22 files changed, 1936 insertions(+), 707 deletions(-) create mode 100644 components/lcd/esp_lcd_ili9881c/test_apps/CMakeLists.txt create mode 100644 components/lcd/esp_lcd_ili9881c/test_apps/main/CMakeLists.txt create mode 100644 components/lcd/esp_lcd_ili9881c/test_apps/main/idf_component.yml create mode 100644 components/lcd/esp_lcd_ili9881c/test_apps/main/test_esp_lcd_ili9881c.c create mode 100644 components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults create mode 100644 components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults.esp32p4 create mode 100644 components/lcd/esp_lcd_st7796/esp_lcd_st7796_general.c create mode 100644 components/lcd/esp_lcd_st7796/esp_lcd_st7796_mipi.c create mode 100644 components/lcd/esp_lcd_st7796/priv_include/esp_lcd_st7796_interface.h create mode 100644 components/lcd/esp_lcd_st7796/test_apps/main/test_app_main.c rename components/lcd/esp_lcd_st7796/test_apps/main/{test_esp_lcd_st7796.c => test_esp_lcd_st7796_general.c} (76%) create mode 100644 components/lcd/esp_lcd_st7796/test_apps/main/test_esp_lcd_st7796_mipi.c diff --git a/.build-test-rules.yml b/.build-test-rules.yml index 1a355ba5..0be46653 100644 --- a/.build-test-rules.yml +++ b/.build-test-rules.yml @@ -28,3 +28,15 @@ components/esp_lvgl_port/examples/rgb_lcd: disable: - if: IDF_VERSION_MAJOR < 5 reason: Example for RGB LCD is supported only for IDF >= 5.0 + +components/lcd/esp_lcd_ili9881c: + disable: + - if: (IDF_VERSION_MAJOR == 5 and IDF_VERSION_MINOR < 3) or IDF_VERSION_MAJOR < 5 + reason: Component is supported only for IDF >= 5.3 + - if: IDF_TARGET not in ["esp32p4"] + reason: Component is supported only for esp32p4 target + +components/lcd/esp_lcd_st7796/test_apps: + disable: + - if: IDF_VERSION_MAJOR < 5 + reason: Component is supported only for IDF >= 5.0 \ No newline at end of file diff --git a/components/lcd/esp_lcd_ili9881c/esp_lcd_ili9881c.c b/components/lcd/esp_lcd_ili9881c/esp_lcd_ili9881c.c index c3acb716..d961c78f 100644 --- a/components/lcd/esp_lcd_ili9881c/esp_lcd_ili9881c.c +++ b/components/lcd/esp_lcd_ili9881c/esp_lcd_ili9881c.c @@ -1,267 +1,75 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include -#include -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_check.h" +#include "esp_log.h" +#include "esp_lcd_panel_commands.h" #include "esp_lcd_panel_interface.h" #include "esp_lcd_panel_io.h" +#include "esp_lcd_mipi_dsi.h" #include "esp_lcd_panel_vendor.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lcd_panel_commands.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #include "driver/gpio.h" -#include "esp_log.h" -#include "esp_check.h" #include "esp_lcd_ili9881c.h" -#define ILI9881C_CMD_OPCODE (0x22) - -#define ILI9881C_GS_PANEL (1) -#define ILI9881C_SS_PANEL (1 << 1) -#define ILI9881C_REV_PANEL (1 << 2) -#define ILI9881C_BGR_PANEL (1 << 3) - -static const char *TAG = "ili9881c"; +#define ILI9881C_CMD_CNDBKxSEL (0xFF) +#define ILI9881C_CMD_BKxSEL_BYTE0 (0x98) +#define ILI9881C_CMD_BKxSEL_BYTE1 (0x81) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE0 (0x00) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE1 (0x01) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE2 (0x02) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE3 (0x03) +#define ILI9881C_CMD_BKxSEL_BYTE2_PAGE4 (0x04) -static esp_err_t panel_ili9881c_del(esp_lcd_panel_t *panel); -static esp_err_t panel_ili9881c_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_ili9881c_init(esp_lcd_panel_t *panel); -static esp_err_t panel_ili9881c_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_ili9881c_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_ili9881c_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_ili9881c_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_ili9881c_disp_on_off(esp_lcd_panel_t *panel, bool off); -static esp_err_t panel_ili9881c_sleep(esp_lcd_panel_t *panel, bool sleep); +#define ILI9881C_PAD_CONTROL (0xB7) +#define ILI9881C_DSI_2_LANE (0x03) +#define ILI9881C_DSI_3_4_LANE (0x02) -typedef struct { - int cmd; /*vendor_config; + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG, + "invalid vendor config"); + esp_err_t ret = ESP_OK; - ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)calloc(1, sizeof(ili9881c_panel_t)); ESP_RETURN_ON_FALSE(ili9881c, ESP_ERR_NO_MEM, TAG, "no mem for ili9881c panel"); @@ -273,7 +81,7 @@ esp_err_t esp_lcd_new_panel_ili9881c(const esp_lcd_panel_io_handle_t io, const e ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed"); } - switch (panel_dev_config->rgb_ele_order) { + switch (panel_dev_config->color_space) { case LCD_RGB_ELEMENT_ORDER_RGB: ili9881c->madctl_val = 0; break; @@ -281,7 +89,7 @@ esp_err_t esp_lcd_new_panel_ili9881c(const esp_lcd_panel_io_handle_t io, const e ili9881c->madctl_val |= LCD_CMD_BGR_BIT; break; default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb element order"); + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); break; } @@ -302,8 +110,8 @@ esp_err_t esp_lcd_new_panel_ili9881c(const esp_lcd_panel_io_handle_t io, const e // The ID register is on the CMD_Page 1 uint8_t ID1, ID2, ID3; - esp_lcd_panel_io_tx_param(io, 0xFF, (uint8_t[]) { - 0x98, 0x81, 0x01 + esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) { + ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1 }, 3); esp_lcd_panel_io_rx_param(io, 0x00, &ID1, 1); esp_lcd_panel_io_rx_param(io, 0x01, &ID2, 1); @@ -311,79 +119,301 @@ esp_err_t esp_lcd_new_panel_ili9881c(const esp_lcd_panel_io_handle_t io, const e ESP_LOGI(TAG, "ID1: 0x%x, ID2: 0x%x, ID3: 0x%x", ID1, ID2, ID3); ili9881c->io = io; + ili9881c->init_cmds = vendor_config->init_cmds; + ili9881c->init_cmds_size = vendor_config->init_cmds_size; + ili9881c->lane_num = vendor_config->mipi_config.lane_num; ili9881c->reset_gpio_num = panel_dev_config->reset_gpio_num; - ili9881c->reset_level = panel_dev_config->flags.reset_active_high; - ili9881c->base.del = panel_ili9881c_del; - ili9881c->base.reset = panel_ili9881c_reset; - ili9881c->base.init = panel_ili9881c_init; - ili9881c->base.invert_color = panel_ili9881c_invert_color; - ili9881c->base.set_gap = panel_ili9881c_set_gap; - ili9881c->base.mirror = panel_ili9881c_mirror; - ili9881c->base.swap_xy = panel_ili9881c_swap_xy; - ili9881c->base.disp_on_off = panel_ili9881c_disp_on_off; - ili9881c->base.disp_sleep = panel_ili9881c_sleep; - *ret_panel = &ili9881c->base; + ili9881c->flags.reset_level = panel_dev_config->flags.reset_active_high; + + // Create MIPI DPI panel + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), err, TAG, + "create MIPI DPI panel failed"); + ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel); + + // Save the original functions of MIPI DPI panel + ili9881c->del = (*ret_panel)->del; + ili9881c->init = (*ret_panel)->init; + // Overwrite the functions of MIPI DPI panel + (*ret_panel)->del = panel_ili9881c_del; + (*ret_panel)->init = panel_ili9881c_init; + (*ret_panel)->reset = panel_ili9881c_reset; + (*ret_panel)->mirror = panel_ili9881c_mirror; + (*ret_panel)->invert_color = panel_ili9881c_invert_color; + (*ret_panel)->disp_on_off = panel_ili9881c_disp_on_off; + (*ret_panel)->disp_sleep = panel_ili9881c_sleep; + (*ret_panel)->user_data = ili9881c; + ESP_LOGD(TAG, "new ili9881c panel @%p", ili9881c); return ESP_OK; err: if (ili9881c) { - panel_ili9881c_del(&ili9881c->base); + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(ili9881c); } return ret; } +static const ili9881c_lcd_init_cmd_t vendor_specific_init_default[] = { + // {cmd, { data }, data_size, delay_ms} + /**** CMD_Page 3 ****/ + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE3}, 3, 0}, + {0x01, (uint8_t []){0x00}, 1, 0}, + {0x02, (uint8_t []){0x00}, 1, 0}, + {0x03, (uint8_t []){0x53}, 1, 0}, + {0x04, (uint8_t []){0x53}, 1, 0}, + {0x05, (uint8_t []){0x13}, 1, 0}, + {0x06, (uint8_t []){0x04}, 1, 0}, + {0x07, (uint8_t []){0x02}, 1, 0}, + {0x08, (uint8_t []){0x02}, 1, 0}, + {0x09, (uint8_t []){0x00}, 1, 0}, + {0x0a, (uint8_t []){0x00}, 1, 0}, + {0x0b, (uint8_t []){0x00}, 1, 0}, + {0x0c, (uint8_t []){0x00}, 1, 0}, + {0x0d, (uint8_t []){0x00}, 1, 0}, + {0x0e, (uint8_t []){0x00}, 1, 0}, + {0x0f, (uint8_t []){0x00}, 1, 0}, + {0x10, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0x00}, 1, 0}, + {0x12, (uint8_t []){0x00}, 1, 0}, + {0x13, (uint8_t []){0x00}, 1, 0}, + {0x14, (uint8_t []){0x00}, 1, 0}, + {0x15, (uint8_t []){0x00}, 1, 0}, + {0x16, (uint8_t []){0x00}, 1, 0}, + {0x17, (uint8_t []){0x00}, 1, 0}, + {0x18, (uint8_t []){0x00}, 1, 0}, + {0x19, (uint8_t []){0x00}, 1, 0}, + {0x1a, (uint8_t []){0x00}, 1, 0}, + {0x1b, (uint8_t []){0x00}, 1, 0}, + {0x1c, (uint8_t []){0x00}, 1, 0}, + {0x1d, (uint8_t []){0x00}, 1, 0}, + {0x1e, (uint8_t []){0xc0}, 1, 0}, + {0x1f, (uint8_t []){0x80}, 1, 0}, + {0x20, (uint8_t []){0x02}, 1, 0}, + {0x21, (uint8_t []){0x09}, 1, 0}, + {0x22, (uint8_t []){0x00}, 1, 0}, + {0x23, (uint8_t []){0x00}, 1, 0}, + {0x24, (uint8_t []){0x00}, 1, 0}, + {0x25, (uint8_t []){0x00}, 1, 0}, + {0x26, (uint8_t []){0x00}, 1, 0}, + {0x27, (uint8_t []){0x00}, 1, 0}, + {0x28, (uint8_t []){0x55}, 1, 0}, + {0x29, (uint8_t []){0x03}, 1, 0}, + {0x2a, (uint8_t []){0x00}, 1, 0}, + {0x2b, (uint8_t []){0x00}, 1, 0}, + {0x2c, (uint8_t []){0x00}, 1, 0}, + {0x2d, (uint8_t []){0x00}, 1, 0}, + {0x2e, (uint8_t []){0x00}, 1, 0}, + {0x2f, (uint8_t []){0x00}, 1, 0}, + {0x30, (uint8_t []){0x00}, 1, 0}, + {0x31, (uint8_t []){0x00}, 1, 0}, + {0x32, (uint8_t []){0x00}, 1, 0}, + {0x33, (uint8_t []){0x00}, 1, 0}, + {0x34, (uint8_t []){0x00}, 1, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x36, (uint8_t []){0x00}, 1, 0}, + {0x37, (uint8_t []){0x00}, 1, 0}, + {0x38, (uint8_t []){0x3C}, 1, 0}, + {0x39, (uint8_t []){0x00}, 1, 0}, + {0x3a, (uint8_t []){0x00}, 1, 0}, + {0x3b, (uint8_t []){0x00}, 1, 0}, + {0x3c, (uint8_t []){0x00}, 1, 0}, + {0x3d, (uint8_t []){0x00}, 1, 0}, + {0x3e, (uint8_t []){0x00}, 1, 0}, + {0x3f, (uint8_t []){0x00}, 1, 0}, + {0x40, (uint8_t []){0x00}, 1, 0}, + {0x41, (uint8_t []){0x00}, 1, 0}, + {0x42, (uint8_t []){0x00}, 1, 0}, + {0x43, (uint8_t []){0x00}, 1, 0}, + {0x44, (uint8_t []){0x00}, 1, 0}, + {0x50, (uint8_t []){0x01}, 1, 0}, + {0x51, (uint8_t []){0x23}, 1, 0}, + {0x52, (uint8_t []){0x45}, 1, 0}, + {0x53, (uint8_t []){0x67}, 1, 0}, + {0x54, (uint8_t []){0x89}, 1, 0}, + {0x55, (uint8_t []){0xab}, 1, 0}, + {0x56, (uint8_t []){0x01}, 1, 0}, + {0x57, (uint8_t []){0x23}, 1, 0}, + {0x58, (uint8_t []){0x45}, 1, 0}, + {0x59, (uint8_t []){0x67}, 1, 0}, + {0x5a, (uint8_t []){0x89}, 1, 0}, + {0x5b, (uint8_t []){0xab}, 1, 0}, + {0x5c, (uint8_t []){0xcd}, 1, 0}, + {0x5d, (uint8_t []){0xef}, 1, 0}, + {0x5e, (uint8_t []){0x01}, 1, 0}, + {0x5f, (uint8_t []){0x08}, 1, 0}, + {0x60, (uint8_t []){0x02}, 1, 0}, + {0x61, (uint8_t []){0x02}, 1, 0}, + {0x62, (uint8_t []){0x0A}, 1, 0}, + {0x63, (uint8_t []){0x15}, 1, 0}, + {0x64, (uint8_t []){0x14}, 1, 0}, + {0x65, (uint8_t []){0x02}, 1, 0}, + {0x66, (uint8_t []){0x11}, 1, 0}, + {0x67, (uint8_t []){0x10}, 1, 0}, + {0x68, (uint8_t []){0x02}, 1, 0}, + {0x69, (uint8_t []){0x0F}, 1, 0}, + {0x6a, (uint8_t []){0x0E}, 1, 0}, + {0x6b, (uint8_t []){0x02}, 1, 0}, + {0x6c, (uint8_t []){0x0D}, 1, 0}, + {0x6d, (uint8_t []){0x0C}, 1, 0}, + {0x6e, (uint8_t []){0x06}, 1, 0}, + {0x6f, (uint8_t []){0x02}, 1, 0}, + {0x70, (uint8_t []){0x02}, 1, 0}, + {0x71, (uint8_t []){0x02}, 1, 0}, + {0x72, (uint8_t []){0x02}, 1, 0}, + {0x73, (uint8_t []){0x02}, 1, 0}, + {0x74, (uint8_t []){0x02}, 1, 0}, + {0x75, (uint8_t []){0x06}, 1, 0}, + {0x76, (uint8_t []){0x02}, 1, 0}, + {0x77, (uint8_t []){0x02}, 1, 0}, + {0x78, (uint8_t []){0x0A}, 1, 0}, + {0x79, (uint8_t []){0x15}, 1, 0}, + {0x7a, (uint8_t []){0x14}, 1, 0}, + {0x7b, (uint8_t []){0x02}, 1, 0}, + {0x7c, (uint8_t []){0x10}, 1, 0}, + {0x7d, (uint8_t []){0x11}, 1, 0}, + {0x7e, (uint8_t []){0x02}, 1, 0}, + {0x7f, (uint8_t []){0x0C}, 1, 0}, + {0x80, (uint8_t []){0x0D}, 1, 0}, + {0x81, (uint8_t []){0x02}, 1, 0}, + {0x82, (uint8_t []){0x0E}, 1, 0}, + {0x83, (uint8_t []){0x0F}, 1, 0}, + {0x84, (uint8_t []){0x08}, 1, 0}, + {0x85, (uint8_t []){0x02}, 1, 0}, + {0x86, (uint8_t []){0x02}, 1, 0}, + {0x87, (uint8_t []){0x02}, 1, 0}, + {0x88, (uint8_t []){0x02}, 1, 0}, + {0x89, (uint8_t []){0x02}, 1, 0}, + {0x8A, (uint8_t []){0x02}, 1, 0}, + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE4}, 3, 0}, + {0x6C, (uint8_t []){0x15}, 1, 0}, + {0x6E, (uint8_t []){0x30}, 1, 0}, + {0x6F, (uint8_t []){0x33}, 1, 0}, + {0x8D, (uint8_t []){0x1F}, 1, 0}, + {0x87, (uint8_t []){0xBA}, 1, 0}, + {0x26, (uint8_t []){0x76}, 1, 0}, + {0xB2, (uint8_t []){0xD1}, 1, 0}, + {0x35, (uint8_t []){0x1F}, 1, 0}, + {0x33, (uint8_t []){0x14}, 1, 0}, + {0x3A, (uint8_t []){0xA9}, 1, 0}, + {0x3B, (uint8_t []){0x3D}, 1, 0}, + {0x38, (uint8_t []){0x01}, 1, 0}, + {0x39, (uint8_t []){0x00}, 1, 0}, + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1}, 3, 0}, + {0x22, (uint8_t []){0x09}, 1, 0}, + {0x31, (uint8_t []){0x00}, 1, 0}, + {0x40, (uint8_t []){0x53}, 1, 0}, + {0x50, (uint8_t []){0xC0}, 1, 0}, + {0x51, (uint8_t []){0xC0}, 1, 0}, + {0x53, (uint8_t []){0x47}, 1, 0}, + {0x55, (uint8_t []){0x46}, 1, 0}, + {0x60, (uint8_t []){0x28}, 1, 0}, + {0x2E, (uint8_t []){0xC8}, 1, 0}, + {0xA0, (uint8_t []){0x01}, 1, 0}, + {0xA1, (uint8_t []){0x10}, 1, 0}, + {0xA2, (uint8_t []){0x1B}, 1, 0}, + {0xA3, (uint8_t []){0x0C}, 1, 0}, + {0xA4, (uint8_t []){0x14}, 1, 0}, + {0xA5, (uint8_t []){0x25}, 1, 0}, + {0xA6, (uint8_t []){0x1A}, 1, 0}, + {0xA7, (uint8_t []){0x1D}, 1, 0}, + {0xA8, (uint8_t []){0x68}, 1, 0}, + {0xA9, (uint8_t []){0x1B}, 1, 0}, + {0xAA, (uint8_t []){0x26}, 1, 0}, + {0xAB, (uint8_t []){0x5B}, 1, 0}, + {0xAC, (uint8_t []){0x1B}, 1, 0}, + {0xAD, (uint8_t []){0x17}, 1, 0}, + {0xAE, (uint8_t []){0x4F}, 1, 0}, + {0xAF, (uint8_t []){0x24}, 1, 0}, + {0xB0, (uint8_t []){0x2A}, 1, 0}, + {0xB1, (uint8_t []){0x4E}, 1, 0}, + {0xB2, (uint8_t []){0x5F}, 1, 0}, + {0xB3, (uint8_t []){0x39}, 1, 0}, + {0xC0, (uint8_t []){0x0F}, 1, 0}, + {0xC1, (uint8_t []){0x1B}, 1, 0}, + {0xC2, (uint8_t []){0x27}, 1, 0}, + {0xC3, (uint8_t []){0x16}, 1, 0}, + {0xC4, (uint8_t []){0x14}, 1, 0}, + {0xC5, (uint8_t []){0x28}, 1, 0}, + {0xC6, (uint8_t []){0x1D}, 1, 0}, + {0xC7, (uint8_t []){0x21}, 1, 0}, + {0xC8, (uint8_t []){0x6C}, 1, 0}, + {0xC9, (uint8_t []){0x1B}, 1, 0}, + {0xCA, (uint8_t []){0x26}, 1, 0}, + {0xCB, (uint8_t []){0x5B}, 1, 0}, + {0xCC, (uint8_t []){0x1B}, 1, 0}, + {0xCD, (uint8_t []){0x1B}, 1, 0}, + {0xCE, (uint8_t []){0x4F}, 1, 0}, + {0xCF, (uint8_t []){0x24}, 1, 0}, + {0xD0, (uint8_t []){0x2A}, 1, 0}, + {0xD1, (uint8_t []){0x4E}, 1, 0}, + {0xD2, (uint8_t []){0x5F}, 1, 0}, + {0xD3, (uint8_t []){0x39}, 1, 0}, + {ILI9881C_CMD_CNDBKxSEL, (uint8_t []){ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE0}, 3, 0}, + {0x35, (uint8_t []){0x00}, 1, 0}, + {0x29, (uint8_t []){0x00}, 0, 0}, + + //============ Gamma END=========== +}; + static esp_err_t panel_ili9881c_del(esp_lcd_panel_t *panel) { - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; if (ili9881c->reset_gpio_num >= 0) { gpio_reset_pin(ili9881c->reset_gpio_num); } + // Delete MIPI DPI panel + ili9881c->del(panel); free(ili9881c); - return ESP_OK; -} - -static esp_err_t panel_ili9881c_reset(esp_lcd_panel_t *panel) -{ - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); - esp_lcd_panel_io_handle_t io = ili9881c->io; - - // perform hardware reset - if (ili9881c->reset_gpio_num >= 0) { - gpio_set_level(ili9881c->reset_gpio_num, ili9881c->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(ili9881c->reset_gpio_num, !ili9881c->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - } else { // perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command - } + ESP_LOGD(TAG, "del ili9881c panel @%p", ili9881c); return ESP_OK; } static esp_err_t panel_ili9881c_init(esp_lcd_panel_t *panel) { - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; esp_lcd_panel_io_handle_t io = ili9881c->io; + const ili9881c_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + uint8_t lane_command = ILI9881C_DSI_2_LANE; + bool is_command0_enable = false; + bool is_cmd_overwritten = false; + + switch (ili9881c->lane_num) { + case 0: + case 2: + lane_command = ILI9881C_DSI_2_LANE; + break; + case 3: + case 4: + lane_command = ILI9881C_DSI_3_4_LANE; + break; + default: + ESP_LOGE(TAG, "Invalid lane number %d", ili9881c->lane_num); + return ESP_ERR_INVALID_ARG; + } - const ili9881c_lcd_init_cmd_t *init_cmds = vendor_specific_init_code_default; - uint16_t init_cmds_size = sizeof(vendor_specific_init_code_default) / sizeof(ili9881c_lcd_init_cmd_t); + // back to CMD_Page 1 + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) { + ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE1 + }, 3), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_PAD_CONTROL, (uint8_t[]) { + lane_command, + }, 1), TAG, "send command failed"); // back to CMD_Page 0 - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xFF, (uint8_t[]) { - 0x98, 0x81, 0x00 + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ILI9881C_CMD_CNDBKxSEL, (uint8_t[]) { + ILI9881C_CMD_BKxSEL_BYTE0, ILI9881C_CMD_BKxSEL_BYTE1, ILI9881C_CMD_BKxSEL_BYTE2_PAGE0 }, 3), TAG, "send command failed"); // exit sleep mode ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "io tx param failed"); vTaskDelay(pdMS_TO_TICKS(120)); - for (int i = 0; i < init_cmds_size; i++) { - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { ili9881c->madctl_val, }, 1), TAG, "send command failed"); @@ -391,62 +421,125 @@ static esp_err_t panel_ili9881c_init(esp_lcd_panel_t *panel) ili9881c->colmod_val, }, 1), TAG, "send command failed"); + // vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + if (ili9881c->init_cmds) { + init_cmds = ili9881c->init_cmds; + init_cmds_size = ili9881c->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(ili9881c_lcd_init_cmd_t); + } + + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + if (is_command0_enable && init_cmds[i].data_bytes > 0) { + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + ili9881c->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + ili9881c->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + is_cmd_overwritten = false; + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", + init_cmds[i].cmd); + } + } + + // Send command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + + if ((init_cmds[i].cmd == ILI9881C_CMD_CNDBKxSEL) && (((uint8_t *)init_cmds[i].data)[2] == ILI9881C_CMD_BKxSEL_BYTE2_PAGE0)) { + is_command0_enable = true; + } else if ((init_cmds[i].cmd == ILI9881C_CMD_CNDBKxSEL) && (((uint8_t *)init_cmds[i].data)[2] != ILI9881C_CMD_BKxSEL_BYTE2_PAGE0)) { + is_command0_enable = false; + } + } + ESP_LOGD(TAG, "send init commands success"); + + ESP_RETURN_ON_ERROR(ili9881c->init(panel), TAG, "init MIPI DPI panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_ili9881c_reset(esp_lcd_panel_t *panel) +{ + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = ili9881c->io; + + // Perform hardware reset + if (ili9881c->reset_gpio_num >= 0) { + gpio_set_level(ili9881c->reset_gpio_num, ili9881c->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(ili9881c->reset_gpio_num, !ili9881c->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + } else if (io) { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(20)); + } + return ESP_OK; } static esp_err_t panel_ili9881c_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) { - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; esp_lcd_panel_io_handle_t io = ili9881c->io; - int command = 0; + uint8_t command = 0; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + if (invert_color_data) { command = LCD_CMD_INVON; } else { command = LCD_CMD_INVOFF; } ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; } static esp_err_t panel_ili9881c_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) { - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; esp_lcd_panel_io_handle_t io = ili9881c->io; + uint8_t madctl_val = ili9881c->madctl_val; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + // Control mirror through LCD command if (mirror_x) { - ili9881c->madctl_val |= ILI9881C_SS_PANEL; + madctl_val |= ILI9881C_CMD_GS_BIT; } else { - ili9881c->madctl_val &= ~ILI9881C_SS_PANEL; + madctl_val &= ~ILI9881C_CMD_GS_BIT; } if (mirror_y) { - ili9881c->madctl_val |= ILI9881C_GS_PANEL; + madctl_val |= ILI9881C_CMD_SS_BIT; } else { - ili9881c->madctl_val &= ~ILI9881C_GS_PANEL; + madctl_val &= ~ILI9881C_CMD_SS_BIT; } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - ili9881c->madctl_val + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) { + madctl_val }, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_ili9881c_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - ESP_LOGW(TAG, "Swap XY is not supported in ILI9881C driver. Please use SW rotation."); - return ESP_ERR_NOT_SUPPORTED; -} + ili9881c->madctl_val = madctl_val; -static esp_err_t panel_ili9881c_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); - ili9881c->x_gap = x_gap; - ili9881c->y_gap = y_gap; return ESP_OK; } static esp_err_t panel_ili9881c_disp_on_off(esp_lcd_panel_t *panel, bool on_off) { - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; esp_lcd_panel_io_handle_t io = ili9881c->io; int command = 0; @@ -461,17 +554,18 @@ static esp_err_t panel_ili9881c_disp_on_off(esp_lcd_panel_t *panel, bool on_off) static esp_err_t panel_ili9881c_sleep(esp_lcd_panel_t *panel, bool sleep) { - ili9881c_panel_t *ili9881c = __containerof(panel, ili9881c_panel_t, base); + ili9881c_panel_t *ili9881c = (ili9881c_panel_t *)panel->user_data; esp_lcd_panel_io_handle_t io = ili9881c->io; int command = 0; + if (sleep) { command = LCD_CMD_SLPIN; } else { command = LCD_CMD_SLPOUT; } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, - "io tx param failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); vTaskDelay(pdMS_TO_TICKS(100)); return ESP_OK; } +#endif diff --git a/components/lcd/esp_lcd_ili9881c/idf_component.yml b/components/lcd/esp_lcd_ili9881c/idf_component.yml index e3531b02..e05e37cf 100644 --- a/components/lcd/esp_lcd_ili9881c/idf_component.yml +++ b/components/lcd/esp_lcd_ili9881c/idf_component.yml @@ -1,4 +1,6 @@ -version: "0.2.0" +version: "1.0.0" +targets: + - esp32p4 description: ESP LCD ILI9881C (MIPI DSI) url: https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_ili9881c dependencies: diff --git a/components/lcd/esp_lcd_ili9881c/include/esp_lcd_ili9881c.h b/components/lcd/esp_lcd_ili9881c/include/esp_lcd_ili9881c.h index bf172c53..6267b992 100644 --- a/components/lcd/esp_lcd_ili9881c/include/esp_lcd_ili9881c.h +++ b/components/lcd/esp_lcd_ili9881c/include/esp_lcd_ili9881c.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,25 +10,118 @@ #pragma once +#include +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED #include "esp_lcd_panel_vendor.h" +#include "esp_lcd_mipi_dsi.h" #ifdef __cplusplus extern "C" { #endif /** - * @brief Create LCD control panel for ILI9881C + * @brief LCD panel initialization commands. + * + */ +typedef struct { + int cmd; /*=5.3" \ No newline at end of file diff --git a/components/lcd/esp_lcd_ili9881c/test_apps/main/test_esp_lcd_ili9881c.c b/components/lcd/esp_lcd_ili9881c/test_apps/main/test_esp_lcd_ili9881c.c new file mode 100644 index 00000000..4ae60112 --- /dev/null +++ b/components/lcd/esp_lcd_ili9881c/test_apps/main/test_esp_lcd_ili9881c.c @@ -0,0 +1,263 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2c.h" +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_ldo_regulator.h" +#include "esp_dma_utils.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" + +#include "esp_lcd_ili9881c.h" + +#define TEST_LCD_H_RES (800) +#define TEST_LCD_V_RES (1280) +#define TEST_LCD_BIT_PER_PIXEL (24) +#define TEST_PIN_NUM_LCD_RST (-1) +#define TEST_PIN_NUM_BK_LIGHT (-1) // set to -1 if not used +#define TEST_LCD_BK_LIGHT_ON_LEVEL (1) +#define TEST_LCD_BK_LIGHT_OFF_LEVEL !TEST_LCD_BK_LIGHT_ON_LEVEL +#define TEST_MIPI_DSI_LANE_NUM (2) + +#if TEST_LCD_BIT_PER_PIXEL == 24 +#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB888) +#elif TEST_LCD_BIT_PER_PIXEL == 18 +#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB666) +#elif TEST_LCD_BIT_PER_PIXEL == 16 +#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB565) +#endif + +#define TEST_DELAY_TIME_MS (3000) + +#define TEST_MIPI_DSI_PHY_PWR_LDO_CHAN (3) +#define TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +static char *TAG = "ili9881c_test"; +static esp_ldo_channel_handle_t ldo_mipi_phy = NULL; +static esp_lcd_panel_handle_t panel_handle = NULL; +static esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; +static esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; +static SemaphoreHandle_t refresh_finish = NULL; + +IRAM_ATTR static bool test_notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + SemaphoreHandle_t refresh_finish = (SemaphoreHandle_t)user_ctx; + BaseType_t need_yield = pdFALSE; + + xSemaphoreGiveFromISR(refresh_finish, &need_yield); + + return (need_yield == pdTRUE); +} + +static void test_init_lcd(void) +{ +#if TEST_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Turn on LCD backlight"); + gpio_config_t bk_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << TEST_PIN_NUM_BK_LIGHT + }; + TEST_ESP_OK(gpio_config(&bk_gpio_config)); + TEST_ESP_OK(gpio_set_level(TEST_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL)); +#endif + + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state +#ifdef TEST_MIPI_DSI_PHY_PWR_LDO_CHAN + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = TEST_MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); +#endif + + ESP_LOGI(TAG, "Initialize MIPI DSI bus"); + esp_lcd_dsi_bus_config_t bus_config = ILI9881C_PANEL_BUS_DSI_2CH_CONFIG(); + TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_dbi_io_config_t dbi_config = ILI9881C_PANEL_IO_DBI_CONFIG(); + TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io)); + + ESP_LOGI(TAG, "Install LCD driver of ili9881c"); + esp_lcd_dpi_panel_config_t dpi_config = ILI9881C_800_1280_PANEL_60HZ_DPI_CONFIG(TEST_MIPI_DPI_PX_FORMAT); + ili9881c_vendor_config_t vendor_config = { + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + .lane_num = TEST_MIPI_DSI_LANE_NUM, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = TEST_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = TEST_LCD_BIT_PER_PIXEL, + .vendor_config = &vendor_config, + }; + TEST_ESP_OK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &panel_config, &panel_handle)); + TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + refresh_finish = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(refresh_finish); + esp_lcd_dpi_panel_event_callbacks_t cbs = { + .on_color_trans_done = test_notify_refresh_ready, + }; + TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(panel_handle, &cbs, refresh_finish)); +} + +static void test_deinit_lcd(void) +{ + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io)); + TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus)); + panel_handle = NULL; + mipi_dbi_io = NULL; + mipi_dsi_bus = NULL; + + if (ldo_mipi_phy) { + TEST_ESP_OK(esp_ldo_release_channel(ldo_mipi_phy)); + ldo_mipi_phy = NULL; + } + + vSemaphoreDelete(refresh_finish); + refresh_finish = NULL; + +#if TEST_PIN_NUM_BK_LIGHT >= 0 + TEST_ESP_OK(gpio_reset_pin(TEST_PIN_NUM_BK_LIGHT)); +#endif +} + +static void test_draw_color_bar(esp_lcd_panel_handle_t panel_handle, uint16_t h_res, uint16_t v_res) +{ + uint8_t byte_per_pixel = (TEST_LCD_BIT_PER_PIXEL + 7) / 8; + uint16_t row_line = v_res / byte_per_pixel / 8; + uint8_t *color = (uint8_t *)heap_caps_calloc(1, row_line * h_res * byte_per_pixel, MALLOC_CAP_DMA); + + for (int j = 0; j < byte_per_pixel * 8; j++) { + for (int i = 0; i < row_line * h_res; i++) { + for (int k = 0; k < byte_per_pixel; k++) { + color[i * byte_per_pixel + k] = (BIT(j) >> (k * 8)) & 0xff; + } + } + TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, j * row_line, h_res, (j + 1) * row_line, color)); + xSemaphoreTake(refresh_finish, portMAX_DELAY); + } + + uint16_t color_line = row_line * byte_per_pixel * 8; + uint16_t res_line = v_res - color_line; + if (res_line) { + for (int i = 0; i < res_line * h_res; i++) { + for (int k = 0; k < byte_per_pixel; k++) { + color[i * byte_per_pixel + k] = 0xff; + } + } + TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, color_line, h_res, v_res, color)); + xSemaphoreTake(refresh_finish, portMAX_DELAY); + } + + free(color); +} + +TEST_CASE("test ili9881c to draw pattern with MIPI interface", "[ili9881c][draw_pattern]") +{ + ESP_LOGI(TAG, "Initialize LCD device"); + test_init_lcd(); + + ESP_LOGI(TAG, "Show color bar pattern drawn by hardware"); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_BAR_VERTICAL)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_BAR_HORIZONTAL)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_NONE)); + + ESP_LOGI(TAG, "Deinitialize LCD device"); + test_deinit_lcd(); +} + +TEST_CASE("test ili9881c to draw color bar with MIPI interface", "[ili9881c][draw_color_bar]") +{ + ESP_LOGI(TAG, "Initialize LCD device"); + test_init_lcd(); + + ESP_LOGI(TAG, "Show color bar drawn by software"); + test_draw_color_bar(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS)); + + ESP_LOGI(TAG, "Deinitialize LCD device"); + test_deinit_lcd(); +} + +TEST_CASE("test ili9881c to mirror with MIPI interface", "[ili9881c][mirror]") +{ + ESP_LOGI(TAG, "Initialize LCD device"); + test_init_lcd(); + + ESP_LOGI(TAG, "Mirror the screen"); + for (size_t i = 0; i < 4; i++) { + TEST_ASSERT_NOT_EQUAL(esp_lcd_panel_mirror(panel_handle, i & 2, i & 1), ESP_FAIL); + + ESP_LOGI(TAG, "Mirror: %d", i); + test_draw_color_bar(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + ESP_LOGI(TAG, "Deinitialize LCD device"); + test_deinit_lcd(); +} +#endif + +// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (300) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + unity_utils_check_leak(before_free_8bit, after_free_8bit, "8BIT", TEST_MEMORY_LEAK_THRESHOLD); + unity_utils_check_leak(before_free_32bit, after_free_32bit, "32BIT", TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + /** + * ___ _ ___ ___ ___ ___ _ + * |_ _| | |_ _/ _ \ ( _ ) ( _ )/ | + * | || | | | (_) |/ _ \ / _ \| | + * | || |___ | |\__, | (_) | (_) | | + * |___|_____|___| /_/ \___/ \___/|_| + */ + printf(" ___ _ ___ ___ ___ ___ _ \r\n"); + printf("|_ _| | |_ _/ _ \\ ( _ ) ( _ )/ |\r\n"); + printf(" | || | | | (_) |/ _ \\ / _ \\| |\r\n"); + printf(" | || |___ | |\\__, | (_) | (_) | |\r\n"); + printf("|___|_____|___| /_/ \\___/ \\___/|_| \r\n"); + unity_run_menu(); +} diff --git a/components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults b/components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults new file mode 100644 index 00000000..521790c3 --- /dev/null +++ b/components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_FREERTOS_HZ=1000 +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=4096 diff --git a/components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults.esp32p4 b/components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults.esp32p4 new file mode 100644 index 00000000..4621955c --- /dev/null +++ b/components/lcd/esp_lcd_ili9881c/test_apps/sdkconfig.defaults.esp32p4 @@ -0,0 +1,4 @@ +CONFIG_COMPILER_OPTIMIZATION_PERF=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_SPEED_200M=y +CONFIG_IDF_EXPERIMENTAL_FEATURES=y diff --git a/components/lcd/esp_lcd_st7796/CMakeLists.txt b/components/lcd/esp_lcd_st7796/CMakeLists.txt index 7046f558..c6edcf1d 100644 --- a/components/lcd/esp_lcd_st7796/CMakeLists.txt +++ b/components/lcd/esp_lcd_st7796/CMakeLists.txt @@ -1,5 +1,6 @@ -idf_component_register(SRCS "esp_lcd_st7796.c" +idf_component_register(SRCS "esp_lcd_st7796.c" "esp_lcd_st7796_general.c" "esp_lcd_st7796_mipi.c" INCLUDE_DIRS "include" + PRIV_INCLUDE_DIRS "priv_include" REQUIRES "esp_lcd" PRIV_REQUIRES "driver") diff --git a/components/lcd/esp_lcd_st7796/README.md b/components/lcd/esp_lcd_st7796/README.md index 88ae070b..79a4c4f2 100644 --- a/components/lcd/esp_lcd_st7796/README.md +++ b/components/lcd/esp_lcd_st7796/README.md @@ -6,7 +6,7 @@ Implementation of the ST7796 LCD controller with esp_lcd component. | LCD controller | Communication interface | Component name | Link to datasheet | | :------------: | :---------------------: | :------------: | :---------------: | -| ST7796 | SPI/I80 | esp_lcd_st7796 | [Specification](https://www.displayfuture.com/Display/datasheet/controller/ST7796s.pdf) | +| ST7796 | SPI/I80/MIPI-DSI | esp_lcd_st7796 | [Specification](https://www.displayfuture.com/Display/datasheet/controller/ST7796s.pdf) | ## Add to project @@ -77,3 +77,57 @@ Alternatively, you can create `idf_component.yml`. More is in [Espressif's docum ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); #endif ``` + +### MIPI Interface + +```c +/** + * Uncomment these line if use custom initialization commands. + * The array should be declared as static const and positioned outside the function. + */ +// static const st7796_lcd_init_cmd_t lcd_init_cmds[] = { +// // cmd data data_size delay_ms +// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0}, +// {0xEF, (uint8_t []){0x08}, 1, 0}, +// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, +// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, +// ... +// }; + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = EXAMPLE_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); + + ESP_LOGI(TAG, "Initialize MIPI DSI bus"); + esp_lcd_dsi_bus_config_t bus_config = ST7796_PANEL_BUS_DSI_1CH_CONFIG(); + ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_dbi_io_config_t dbi_config = ST7796_PANEL_IO_DBI_CONFIG(); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io)); + + ESP_LOGI(TAG, "Install LCD driver of st7796"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_dpi_panel_config_t dpi_config = ST7796_320_480_PANEL_60HZ_DPI_CONFIG(EXAMPLE_MIPI_DPI_PX_FORMAT); + st7796_vendor_config_t vendor_config = { + // .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands + // .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t), + .flags.use_mipi_interface = 1, + .mipi_config = { + .dsi_bus = mipi_dsi_bus, + .dpi_config = &dpi_config, + }, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = EXAMPLE_LCD_BIT_PER_PIXEL, + .vendor_config = &vendor_config, + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(mipi_dbi_io, &panel_config, &panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); +``` diff --git a/components/lcd/esp_lcd_st7796/esp_lcd_st7796.c b/components/lcd/esp_lcd_st7796/esp_lcd_st7796.c index add69464..fdf840c8 100644 --- a/components/lcd/esp_lcd_st7796/esp_lcd_st7796.c +++ b/components/lcd/esp_lcd_st7796/esp_lcd_st7796.c @@ -1,353 +1,36 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include -#include -#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 "soc/soc_caps.h" #include "esp_check.h" +#include "esp_lcd_types.h" #include "esp_lcd_st7796.h" +#include "esp_lcd_st7796_interface.h" -static const char *TAG = "st7796"; +const char *TAG = "st7796"; -static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel); -static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel); -static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel); -static esp_err_t panel_st7796_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_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); -static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); -static esp_err_t panel_st7796_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); -static esp_err_t panel_st7796_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); -static esp_err_t panel_st7796_disp_on_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; - uint8_t fb_bits_per_pixel; - uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register - uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register - const st7796_lcd_init_cmd_t *init_cmds; - uint16_t init_cmds_size; -} st7796_panel_t; - -esp_err_t esp_lcd_new_panel_st7796(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 esp_lcd_new_panel_st7796(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; - st7796_panel_t *st7796 = NULL; - gpio_config_t io_conf = { 0 }; + ESP_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR, ESP_LCD_ST7796_VER_PATCH); + ESP_RETURN_ON_FALSE(panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "Invalid arguments"); + st7796_vendor_config_t *vendor_config = (st7796_vendor_config_t *)panel_dev_config->vendor_config; - ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - st7796 = (st7796_panel_t *)calloc(1, sizeof(st7796_panel_t)); - ESP_GOTO_ON_FALSE(st7796, ESP_ERR_NO_MEM, err, TAG, "no mem for st7796 panel"); + esp_err_t ret = ESP_ERR_NOT_SUPPORTED; - if (panel_dev_config->reset_gpio_num >= 0) { - io_conf.mode = GPIO_MODE_OUTPUT; - io_conf.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"); - } - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - switch (panel_dev_config->color_space) { - case ESP_LCD_COLOR_SPACE_RGB: - st7796->madctl_val = 0; - break; - case ESP_LCD_COLOR_SPACE_BGR: - st7796->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); - break; - } + if (vendor_config && vendor_config->flags.use_mipi_interface) { +#if SOC_MIPI_DSI_SUPPORTED + ret = esp_lcd_new_panel_st7796_mipi(io, panel_dev_config, ret_panel); #else - switch (panel_dev_config->rgb_endian) { - case LCD_RGB_ENDIAN_RGB: - st7796->madctl_val = 0; - break; - case LCD_RGB_ENDIAN_BGR: - st7796->madctl_val |= LCD_CMD_BGR_BIT; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); - break; - } + ESP_LOGE(TAG, "The chip does not support MIPI-DSI interface"); #endif - - switch (panel_dev_config->bits_per_pixel) { - case 16: // RGB565 - st7796->colmod_val = 0x05; - st7796->fb_bits_per_pixel = 16; - break; - case 18: // RGB666 - st7796->colmod_val = 0x06; - // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel - st7796->fb_bits_per_pixel = 24; - break; - case 24: // RGB888 - st7796->colmod_val = 0x07; - st7796->fb_bits_per_pixel = 24; - break; - default: - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); - break; - } - - st7796->io = io; - st7796->reset_gpio_num = panel_dev_config->reset_gpio_num; - st7796->reset_level = panel_dev_config->flags.reset_active_high; - if (panel_dev_config->vendor_config) { - st7796->init_cmds = ((st7796_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; - st7796->init_cmds_size = ((st7796_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; - } - st7796->base.del = panel_st7796_del; - st7796->base.reset = panel_st7796_reset; - st7796->base.init = panel_st7796_init; - st7796->base.draw_bitmap = panel_st7796_draw_bitmap; - st7796->base.invert_color = panel_st7796_invert_color; - st7796->base.set_gap = panel_st7796_set_gap; - st7796->base.mirror = panel_st7796_mirror; - st7796->base.swap_xy = panel_st7796_swap_xy; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - st7796->base.disp_off = panel_st7796_disp_on_off; -#else - st7796->base.disp_on_off = panel_st7796_disp_on_off; -#endif - *ret_panel = &(st7796->base); - ESP_LOGD(TAG, "new st7796 panel @%p", st7796); - - ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR, - ESP_LCD_ST7796_VER_PATCH); - - return ESP_OK; - -err: - if (st7796) { - if (panel_dev_config->reset_gpio_num >= 0) { - gpio_reset_pin(panel_dev_config->reset_gpio_num); - } - free(st7796); - } - return ret; -} - -static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - - if (st7796->reset_gpio_num >= 0) { - gpio_reset_pin(st7796->reset_gpio_num); - } - ESP_LOGD(TAG, "del st7796 panel @%p", st7796); - free(st7796); - return ESP_OK; -} - -static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - esp_lcd_panel_io_handle_t io = st7796->io; - - // perform hardware reset - if (st7796->reset_gpio_num >= 0) { - gpio_set_level(st7796->reset_gpio_num, st7796->reset_level); - vTaskDelay(pdMS_TO_TICKS(10)); - gpio_set_level(st7796->reset_gpio_num, !st7796->reset_level); - vTaskDelay(pdMS_TO_TICKS(120)); - } else { // perform software reset - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(120)); // spec, wait at least 5ms before sending new command - } - - return ESP_OK; -} - -static const st7796_lcd_init_cmd_t vendor_specific_init_default[] = { -// {cmd, { data }, data_size, delay_ms} - {0xf0, (uint8_t []){0xc3}, 1, 0}, - {0xf0, (uint8_t []){0x96}, 1, 0}, - {0xb4, (uint8_t []){0x01}, 1, 0}, - {0xb7, (uint8_t []){0xc6}, 1, 0}, - {0xe8, (uint8_t []){0x40, 0x8a, 0x00, 0x00, 0x29, 0x19, 0xa5, 0x33}, 8, 0}, - {0xc1, (uint8_t []){0x06}, 1, 0}, - {0xc2, (uint8_t []){0xa7}, 1, 0}, - {0xc5, (uint8_t []){0x18}, 1, 0}, - {0xe0, (uint8_t []){0xf0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0x54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b}, 14, 0}, - {0xe1, (uint8_t []){0xf0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2d, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b}, 14, 0}, - {0xf0, (uint8_t []){0x3c}, 1, 0}, - {0xf0, (uint8_t []){0x69}, 1, 0}, -}; - -static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - esp_lcd_panel_io_handle_t io = st7796->io; - - // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(100)); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - st7796->madctl_val, - }, 1), TAG, "send command failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { - st7796->colmod_val, - }, 1), TAG, "send command failed"); - - const st7796_lcd_init_cmd_t *init_cmds = NULL; - uint16_t init_cmds_size = 0; - if (st7796->init_cmds) { - init_cmds = st7796->init_cmds; - init_cmds_size = st7796->init_cmds_size; } else { - init_cmds = vendor_specific_init_default; - init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7796_lcd_init_cmd_t); + ret = esp_lcd_new_panel_st7796_general(io, panel_dev_config, ret_panel); } - bool is_cmd_overwritten = false; - for (int i = 0; i < init_cmds_size; i++) { - // Check if the command has been used or conflicts with the internal - switch (init_cmds[i].cmd) { - case LCD_CMD_MADCTL: - is_cmd_overwritten = true; - st7796->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - case LCD_CMD_COLMOD: - is_cmd_overwritten = true; - st7796->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; - break; - default: - is_cmd_overwritten = false; - break; - } - - if (is_cmd_overwritten) { - ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); - } - - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); - vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); - } - ESP_LOGD(TAG, "send init commands success"); - - return ESP_OK; -} - -static esp_err_t panel_st7796_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_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 = st7796->io; - - x_start += st7796->x_gap; - x_end += st7796->x_gap; - y_start += st7796->y_gap; - y_end += st7796->y_gap; - - // define an area of frame memory where MCU can access - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { - (x_start >> 8) & 0xFF, - x_start & 0xFF, - ((x_end - 1) >> 8) & 0xFF, - (x_end - 1) & 0xFF, - }, 4), TAG, "send command failed"); - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { - (y_start >> 8) & 0xFF, - y_start & 0xFF, - ((y_end - 1) >> 8) & 0xFF, - (y_end - 1) & 0xFF, - }, 4), TAG, "send command failed"); - // transfer frame buffer - size_t len = (x_end - x_start) * (y_end - y_start) * st7796->fb_bits_per_pixel / 8; - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send command failed"); - - return ESP_OK; -} - -static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - esp_lcd_panel_io_handle_t io = st7796->io; - int command = 0; - if (invert_color_data) { - command = LCD_CMD_INVON; - } else { - command = LCD_CMD_INVOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - esp_lcd_panel_io_handle_t io = st7796->io; - if (mirror_x) { - st7796->madctl_val |= LCD_CMD_MX_BIT; - } else { - st7796->madctl_val &= ~LCD_CMD_MX_BIT; - } - if (mirror_y) { - st7796->madctl_val |= LCD_CMD_MY_BIT; - } else { - st7796->madctl_val &= ~LCD_CMD_MY_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - st7796->madctl_val - }, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_st7796_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - esp_lcd_panel_io_handle_t io = st7796->io; - if (swap_axes) { - st7796->madctl_val |= LCD_CMD_MV_BIT; - } else { - st7796->madctl_val &= ~LCD_CMD_MV_BIT; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { - st7796->madctl_val - }, 1), TAG, "send command failed"); - return ESP_OK; -} - -static esp_err_t panel_st7796_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - st7796->x_gap = x_gap; - st7796->y_gap = y_gap; - return ESP_OK; -} - -static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off) -{ - st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); - esp_lcd_panel_io_handle_t io = st7796->io; - int command = 0; - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) - on_off = !on_off; -#endif - - if (on_off) { - command = LCD_CMD_DISPON; - } else { - command = LCD_CMD_DISPOFF; - } - ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); - return ESP_OK; + return ret; } diff --git a/components/lcd/esp_lcd_st7796/esp_lcd_st7796_general.c b/components/lcd/esp_lcd_st7796/esp_lcd_st7796_general.c new file mode 100644 index 00000000..b94c6285 --- /dev/null +++ b/components/lcd/esp_lcd_st7796/esp_lcd_st7796_general.c @@ -0,0 +1,354 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#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" + +#include "esp_lcd_st7796.h" +#include "esp_lcd_st7796_interface.h" + +static const char *TAG = "st7796_general"; + +static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel); +static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel); +static esp_err_t panel_st7796_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_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_st7796_swap_xy(esp_lcd_panel_t *panel, bool swap_axes); +static esp_err_t panel_st7796_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap); +static esp_err_t panel_st7796_disp_on_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; + uint8_t fb_bits_per_pixel; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register + const st7796_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; +} st7796_panel_t; + +esp_err_t esp_lcd_new_panel_st7796_general(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; + st7796_panel_t *st7796 = NULL; + gpio_config_t io_conf = { 0 }; + + ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + st7796 = (st7796_panel_t *)calloc(1, sizeof(st7796_panel_t)); + ESP_GOTO_ON_FALSE(st7796, ESP_ERR_NO_MEM, err, TAG, "no mem for st7796 panel"); + + if (panel_dev_config->reset_gpio_num >= 0) { + io_conf.mode = GPIO_MODE_OUTPUT; + io_conf.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"); + } + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + switch (panel_dev_config->color_space) { + case ESP_LCD_COLOR_SPACE_RGB: + st7796->madctl_val = 0; + break; + case ESP_LCD_COLOR_SPACE_BGR: + st7796->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space"); + break; + } +#else + switch (panel_dev_config->rgb_endian) { + case LCD_RGB_ENDIAN_RGB: + st7796->madctl_val = 0; + break; + case LCD_RGB_ENDIAN_BGR: + st7796->madctl_val |= LCD_CMD_BGR_BIT; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian"); + break; + } +#endif + + switch (panel_dev_config->bits_per_pixel) { + case 16: // RGB565 + st7796->colmod_val = 0x05; + st7796->fb_bits_per_pixel = 16; + break; + case 18: // RGB666 + st7796->colmod_val = 0x06; + // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel + st7796->fb_bits_per_pixel = 24; + break; + case 24: // RGB888 + st7796->colmod_val = 0x07; + st7796->fb_bits_per_pixel = 24; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + st7796->io = io; + st7796->reset_gpio_num = panel_dev_config->reset_gpio_num; + st7796->reset_level = panel_dev_config->flags.reset_active_high; + if (panel_dev_config->vendor_config) { + st7796->init_cmds = ((st7796_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds; + st7796->init_cmds_size = ((st7796_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size; + } + st7796->base.del = panel_st7796_del; + st7796->base.reset = panel_st7796_reset; + st7796->base.init = panel_st7796_init; + st7796->base.draw_bitmap = panel_st7796_draw_bitmap; + st7796->base.invert_color = panel_st7796_invert_color; + st7796->base.set_gap = panel_st7796_set_gap; + st7796->base.mirror = panel_st7796_mirror; + st7796->base.swap_xy = panel_st7796_swap_xy; +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + st7796->base.disp_off = panel_st7796_disp_on_off; +#else + st7796->base.disp_on_off = panel_st7796_disp_on_off; +#endif + *ret_panel = &(st7796->base); + ESP_LOGD(TAG, "new st7796 panel @%p", st7796); + + ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR, + ESP_LCD_ST7796_VER_PATCH); + + return ESP_OK; + +err: + if (st7796) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(st7796); + } + return ret; +} + +static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + + if (st7796->reset_gpio_num >= 0) { + gpio_reset_pin(st7796->reset_gpio_num); + } + ESP_LOGD(TAG, "del st7796 panel @%p", st7796); + free(st7796); + return ESP_OK; +} + +static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + esp_lcd_panel_io_handle_t io = st7796->io; + + // perform hardware reset + if (st7796->reset_gpio_num >= 0) { + gpio_set_level(st7796->reset_gpio_num, st7796->reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(st7796->reset_gpio_num, !st7796->reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else { // perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); // spec, wait at least 5ms before sending new command + } + + return ESP_OK; +} + +static const st7796_lcd_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + {0xf0, (uint8_t []){0xc3}, 1, 0}, + {0xf0, (uint8_t []){0x96}, 1, 0}, + {0xb4, (uint8_t []){0x01}, 1, 0}, + {0xb7, (uint8_t []){0xc6}, 1, 0}, + {0xe8, (uint8_t []){0x40, 0x8a, 0x00, 0x00, 0x29, 0x19, 0xa5, 0x33}, 8, 0}, + {0xc1, (uint8_t []){0x06}, 1, 0}, + {0xc2, (uint8_t []){0xa7}, 1, 0}, + {0xc5, (uint8_t []){0x18}, 1, 0}, + {0xe0, (uint8_t []){0xf0, 0x09, 0x0b, 0x06, 0x04, 0x15, 0x2f, 0x54, 0x42, 0x3c, 0x17, 0x14, 0x18, 0x1b}, 14, 0}, + {0xe1, (uint8_t []){0xf0, 0x09, 0x0b, 0x06, 0x04, 0x03, 0x2d, 0x43, 0x42, 0x3b, 0x16, 0x14, 0x17, 0x1b}, 14, 0}, + {0xf0, (uint8_t []){0x3c}, 1, 0}, + {0xf0, (uint8_t []){0x69}, 1, 0}, +}; + +static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + esp_lcd_panel_io_handle_t io = st7796->io; + + // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(100)); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + st7796->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { + st7796->colmod_val, + }, 1), TAG, "send command failed"); + + const st7796_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + if (st7796->init_cmds) { + init_cmds = st7796->init_cmds; + init_cmds_size = st7796->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7796_lcd_init_cmd_t); + } + + bool is_cmd_overwritten = false; + for (int i = 0; i < init_cmds_size; i++) { + // Check if the command has been used or conflicts with the internal + switch (init_cmds[i].cmd) { + case LCD_CMD_MADCTL: + is_cmd_overwritten = true; + st7796->madctl_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + case LCD_CMD_COLMOD: + is_cmd_overwritten = true; + st7796->colmod_val = ((uint8_t *)init_cmds[i].data)[0]; + break; + default: + is_cmd_overwritten = false; + break; + } + + if (is_cmd_overwritten) { + ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd); + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + return ESP_OK; +} + +static esp_err_t panel_st7796_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_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 = st7796->io; + + x_start += st7796->x_gap; + x_end += st7796->x_gap; + y_start += st7796->y_gap; + y_end += st7796->y_gap; + + // define an area of frame memory where MCU can access + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) { + (x_start >> 8) & 0xFF, + x_start & 0xFF, + ((x_end - 1) >> 8) & 0xFF, + (x_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) { + (y_start >> 8) & 0xFF, + y_start & 0xFF, + ((y_end - 1) >> 8) & 0xFF, + (y_end - 1) & 0xFF, + }, 4), TAG, "send command failed"); + // transfer frame buffer + size_t len = (x_end - x_start) * (y_end - y_start) * st7796->fb_bits_per_pixel / 8; + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send command failed"); + + return ESP_OK; +} + +static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + esp_lcd_panel_io_handle_t io = st7796->io; + int command = 0; + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + esp_lcd_panel_io_handle_t io = st7796->io; + if (mirror_x) { + st7796->madctl_val |= LCD_CMD_MX_BIT; + } else { + st7796->madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) { + st7796->madctl_val |= LCD_CMD_MY_BIT; + } else { + st7796->madctl_val &= ~LCD_CMD_MY_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + st7796->madctl_val + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7796_swap_xy(esp_lcd_panel_t *panel, bool swap_axes) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + esp_lcd_panel_io_handle_t io = st7796->io; + if (swap_axes) { + st7796->madctl_val |= LCD_CMD_MV_BIT; + } else { + st7796->madctl_val &= ~LCD_CMD_MV_BIT; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + st7796->madctl_val + }, 1), TAG, "send command failed"); + return ESP_OK; +} + +static esp_err_t panel_st7796_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + st7796->x_gap = x_gap; + st7796->y_gap = y_gap; + return ESP_OK; +} + +static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + st7796_panel_t *st7796 = __containerof(panel, st7796_panel_t, base); + esp_lcd_panel_io_handle_t io = st7796->io; + int command = 0; + +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0) + on_off = !on_off; +#endif + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} diff --git a/components/lcd/esp_lcd_st7796/esp_lcd_st7796_mipi.c b/components/lcd/esp_lcd_st7796/esp_lcd_st7796_mipi.c new file mode 100644 index 00000000..203d615f --- /dev/null +++ b/components/lcd/esp_lcd_st7796/esp_lcd_st7796_mipi.c @@ -0,0 +1,296 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_check.h" +#include "esp_log.h" +#include "esp_lcd_panel_commands.h" +#include "esp_lcd_panel_interface.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_vendor.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" + +#include "esp_lcd_st7796.h" +#include "esp_lcd_st7796_interface.h" + +typedef struct { + esp_lcd_panel_io_handle_t io; + int reset_gpio_num; + uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register + uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register + const st7796_lcd_init_cmd_t *init_cmds; + uint16_t init_cmds_size; + struct { + unsigned int reset_level: 1; + } flags; + // To save the original functions of MIPI DPI panel + esp_err_t (*del)(esp_lcd_panel_t *panel); + esp_err_t (*init)(esp_lcd_panel_t *panel); +} st7796_panel_t; + +static const char *TAG = "st7796_mipi"; + +static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel); +static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel); +static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel); +static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data); +static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y); +static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off); + +esp_err_t esp_lcd_new_panel_st7796_mipi(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_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments"); + st7796_vendor_config_t *vendor_config = (st7796_vendor_config_t *)panel_dev_config->vendor_config; + ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG, + "invalid vendor config"); + + esp_err_t ret = ESP_OK; + st7796_panel_t *st7796 = (st7796_panel_t *)calloc(1, sizeof(st7796_panel_t)); + ESP_RETURN_ON_FALSE(st7796, ESP_ERR_NO_MEM, TAG, "no mem for st7796 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 LCD_RGB_ELEMENT_ORDER_RGB: + st7796->madctl_val = 0; + break; + case LCD_RGB_ELEMENT_ORDER_BGR: + st7796->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: // RGB565 + st7796->colmod_val = 0x55; + break; + case 18: // RGB666 + st7796->colmod_val = 0x66; + break; + case 24: // RGB888 + st7796->colmod_val = 0x77; + break; + default: + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width"); + break; + } + + st7796->io = io; + st7796->init_cmds = vendor_config->init_cmds; + st7796->init_cmds_size = vendor_config->init_cmds_size; + st7796->reset_gpio_num = panel_dev_config->reset_gpio_num; + st7796->flags.reset_level = panel_dev_config->flags.reset_active_high; + + // Create MIPI DPI panel + esp_lcd_panel_handle_t panel_handle = NULL; + ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, &panel_handle), err, TAG, + "create MIPI DPI panel failed"); + ESP_LOGD(TAG, "new MIPI DPI panel @%p", panel_handle); + + // Save the original functions of MIPI DPI panel + st7796->del = panel_handle->del; + st7796->init = panel_handle->init; + // Overwrite the functions of MIPI DPI panel + panel_handle->del = panel_st7796_del; + panel_handle->init = panel_st7796_init; + panel_handle->reset = panel_st7796_reset; + panel_handle->mirror = panel_st7796_mirror; + panel_handle->invert_color = panel_st7796_invert_color; + panel_handle->disp_on_off = panel_st7796_disp_on_off; + panel_handle->user_data = st7796; + *ret_panel = panel_handle; + ESP_LOGD(TAG, "new st7796 panel @%p", st7796); + + ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_ST7796_VER_MAJOR, ESP_LCD_ST7796_VER_MINOR, + ESP_LCD_ST7796_VER_PATCH); + + return ESP_OK; + +err: + if (st7796) { + if (panel_dev_config->reset_gpio_num >= 0) { + gpio_reset_pin(panel_dev_config->reset_gpio_num); + } + free(st7796); + } + return ret; +} + +static const st7796_lcd_init_cmd_t vendor_specific_init_default[] = { +// {cmd, { data }, data_size, delay_ms} + {0x11, (uint8_t []){0x00}, 0, 120}, + {0x36, (uint8_t []){0x48}, 1, 0}, + {0x3A, (uint8_t []){0x77}, 1, 0}, + {0xF0, (uint8_t []){0xC3}, 1, 0}, + {0xF0, (uint8_t []){0x96}, 1, 0}, + {0xB4, (uint8_t []){0x02}, 1, 0}, + {0xB7, (uint8_t []){0xC6}, 1, 0}, + {0xB6, (uint8_t []){0x2F}, 1, 0}, + {0x11, (uint8_t []){0xC0, 0xF0, 0x35}, 3, 0}, + {0xC1, (uint8_t []){0x15}, 1, 0}, + {0xC2, (uint8_t []){0xAF}, 1, 0}, + {0xC3, (uint8_t []){0x09}, 1, 0}, + {0xC5, (uint8_t []){0x22}, 1, 0}, + {0xC6, (uint8_t []){0x00}, 1, 0}, + {0x11, (uint8_t []){0xE8, 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 9, 0}, + {0x11, (uint8_t []){0xE0, 0x70, 0x00, 0x05, 0x03, 0x02, 0x20, 0x29, 0x01, 0x45, 0x30, 0x09, 0x07, 0x22, 0x29}, 15, 0}, + {0x11, (uint8_t []){0xE1, 0x70, 0x0C, 0x10, 0x0F, 0x0E, 0x09, 0x35, 0x64, 0x48, 0x3A, 0x14, 0x13, 0x2E, 0x30}, 15, 0}, + {0x11, (uint8_t []){0xE0, 0x70, 0x04, 0x0A, 0x0B, 0x0A, 0x27, 0x31, 0x55, 0x47, 0x29, 0x13, 0x13, 0x29, 0x2D}, 15, 0}, + {0x11, (uint8_t []){0xE1, 0x70, 0x08, 0x0E, 0x09, 0x08, 0x04, 0x33, 0x32, 0x49, 0x36, 0x14, 0x14, 0x2A, 0x2F}, 15, 0}, + {0x21, (uint8_t []){0x00}, 0, 0}, + {0xF0, (uint8_t []){0xC3}, 1, 0}, + {0xF0, (uint8_t []){0x96}, 1, 120}, + {0xF0, (uint8_t []){0xC3}, 1, 0}, + {0x29, (uint8_t []){0x00}, 0, 0}, + {0x2C, (uint8_t []){0x00}, 0, 0}, + //============ Gamma END=========== +}; + +static esp_err_t panel_st7796_del(esp_lcd_panel_t *panel) +{ + st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data; + + if (st7796->reset_gpio_num >= 0) { + gpio_reset_pin(st7796->reset_gpio_num); + } + // Delete MIPI DPI panel + st7796->del(panel); + free(st7796); + ESP_LOGD(TAG, "del st7796 panel @%p", st7796); + + return ESP_OK; +} + +static esp_err_t panel_st7796_init(esp_lcd_panel_t *panel) +{ + st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = st7796->io; + const st7796_lcd_init_cmd_t *init_cmds = NULL; + uint16_t init_cmds_size = 0; + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) { + st7796->madctl_val, + }, 1), TAG, "send command failed"); + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) { + st7796->colmod_val, + }, 1), TAG, "send command failed"); + + // vendor specific initialization, it can be different between manufacturers + // should consult the LCD supplier for initialization sequence code + if (st7796->init_cmds) { + init_cmds = st7796->init_cmds; + init_cmds_size = st7796->init_cmds_size; + } else { + init_cmds = vendor_specific_init_default; + init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7796_lcd_init_cmd_t); + } + + for (int i = 0; i < init_cmds_size; i++) { + // Send command + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms)); + } + ESP_LOGD(TAG, "send init commands success"); + + ESP_RETURN_ON_ERROR(st7796->init(panel), TAG, "init MIPI DPI panel failed"); + + return ESP_OK; +} + +static esp_err_t panel_st7796_reset(esp_lcd_panel_t *panel) +{ + st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = st7796->io; + + // Perform hardware reset + if (st7796->reset_gpio_num >= 0) { + gpio_set_level(st7796->reset_gpio_num, st7796->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(10)); + gpio_set_level(st7796->reset_gpio_num, !st7796->flags.reset_level); + vTaskDelay(pdMS_TO_TICKS(120)); + } else if (io) { // Perform software reset + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed"); + vTaskDelay(pdMS_TO_TICKS(120)); + } + + return ESP_OK; +} + +static esp_err_t panel_st7796_invert_color(esp_lcd_panel_t *panel, bool invert_color_data) +{ + st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = st7796->io; + uint8_t command = 0; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + if (invert_color_data) { + command = LCD_CMD_INVON; + } else { + command = LCD_CMD_INVOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + + return ESP_OK; +} + +static esp_err_t panel_st7796_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y) +{ + st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = st7796->io; + uint8_t madctl_val = st7796->madctl_val; + + ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO"); + + // Control mirror through LCD command + if (mirror_x) { + madctl_val |= LCD_CMD_MX_BIT; + } else { + madctl_val &= ~LCD_CMD_MX_BIT; + } + if (mirror_y) { + madctl_val |= LCD_CMD_MY_BIT; + } else { + madctl_val &= ~LCD_CMD_MY_BIT; + } + + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) { + madctl_val + }, 1), TAG, "send command failed"); + st7796->madctl_val = madctl_val; + + return ESP_OK; +} + +static esp_err_t panel_st7796_disp_on_off(esp_lcd_panel_t *panel, bool on_off) +{ + st7796_panel_t *st7796 = (st7796_panel_t *)panel->user_data; + esp_lcd_panel_io_handle_t io = st7796->io; + int command = 0; + + if (on_off) { + command = LCD_CMD_DISPON; + } else { + command = LCD_CMD_DISPOFF; + } + ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed"); + return ESP_OK; +} +#endif diff --git a/components/lcd/esp_lcd_st7796/idf_component.yml b/components/lcd/esp_lcd_st7796/idf_component.yml index a3453e90..4829ebb9 100644 --- a/components/lcd/esp_lcd_st7796/idf_component.yml +++ b/components/lcd/esp_lcd_st7796/idf_component.yml @@ -1,8 +1,9 @@ -version: "1.2.1" +version: "1.3.0" targets: - esp32s2 - esp32s3 -description: ESP LCD ST7796 driver + - esp32p4 +description: ESP LCD ST7796 driver (SPI && I80 && MIPI DSI) url: https://github.com/espressif/esp-bsp/tree/master/components/lcd/esp_lcd_st7796 dependencies: idf: ">=4.4" diff --git a/components/lcd/esp_lcd_st7796/include/esp_lcd_st7796.h b/components/lcd/esp_lcd_st7796/include/esp_lcd_st7796.h index 9040454e..d3279ab4 100644 --- a/components/lcd/esp_lcd_st7796/include/esp_lcd_st7796.h +++ b/components/lcd/esp_lcd_st7796/include/esp_lcd_st7796.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -12,6 +12,10 @@ #include "hal/lcd_types.h" #include "esp_lcd_panel_vendor.h" +#include "esp_idf_version.h" +#if SOC_MIPI_DSI_SUPPORTED +#include "esp_lcd_mipi_dsi.h" +#endif #ifdef __cplusplus extern "C" { @@ -40,6 +44,15 @@ typedef struct { * Please refer to `vendor_specific_init_default` in source file. */ uint16_t init_cmds_size; /* #include "freertos/FreeRTOS.h" @@ -130,47 +132,4 @@ TEST_CASE("test st7796 to draw color bar with I80 interface", "[st7796][i80]") TEST_ESP_OK(esp_lcd_panel_io_del(io_handle)); TEST_ESP_OK(esp_lcd_del_i80_bus(i80_bus)); } - -// Some resources are lazy allocated in the LCD driver, the threadhold is left for that case -#define TEST_MEMORY_LEAK_THRESHOLD (-300) - -static size_t before_free_8bit; -static size_t before_free_32bit; - -static void check_leak(size_t before_free, size_t after_free, const char *type) -{ - ssize_t delta = after_free - before_free; - printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); - TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); -} - -void setUp(void) -{ - before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); -} - -void tearDown(void) -{ - size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); - size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); - check_leak(before_free_8bit, after_free_8bit, "8BIT"); - check_leak(before_free_32bit, after_free_32bit, "32BIT"); -} - -void app_main(void) -{ - /** - * __ _____ _____ _____ ___ __ - * / _\/__ \___ |___ / _ \ / /_ - * \ \ / /\/ / / / / (_) | '_ \ - * _\ \ / / / / / / \__, | (_) | - * \__/ \/ /_/ /_/ /_/ \___/ - */ - printf(" __ _____ _____ _____ ___ __\r\n"); - printf("/ _\\/__ \\___ |___ / _ \\ / /_\r\n"); - printf("\\ \\ / /\\/ / / / / (_) | '_ \\\r\n"); - printf("_\\ \\ / / / / / / \\__, | (_) |\r\n"); - printf("\\__/ \\/ /_/ /_/ /_/ \\___/\r\n"); - unity_run_menu(); -} +#endif diff --git a/components/lcd/esp_lcd_st7796/test_apps/main/test_esp_lcd_st7796_mipi.c b/components/lcd/esp_lcd_st7796/test_apps/main/test_esp_lcd_st7796_mipi.c new file mode 100644 index 00000000..8ca84bd3 --- /dev/null +++ b/components/lcd/esp_lcd_st7796/test_apps/main/test_esp_lcd_st7796_mipi.c @@ -0,0 +1,225 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "soc/soc_caps.h" + +#if SOC_MIPI_DSI_SUPPORTED + +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2c.h" +#include "driver/spi_master.h" +#include "driver/gpio.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_io.h" +#include "esp_ldo_regulator.h" +#include "esp_dma_utils.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "unity_test_utils_memory.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_st7796.h" + +#define TEST_LCD_H_RES (320) +#define TEST_LCD_V_RES (480) +#define TEST_LCD_BIT_PER_PIXEL (24) +#define TEST_PIN_NUM_LCD_RST (-1) +#define TEST_PIN_NUM_BK_LIGHT (-1) // set to -1 if not used +#define TEST_LCD_BK_LIGHT_ON_LEVEL (1) +#define TEST_LCD_BK_LIGHT_OFF_LEVEL !TEST_LCD_BK_LIGHT_ON_LEVEL + +#if TEST_LCD_BIT_PER_PIXEL == 24 +#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB888) +#elif TEST_LCD_BIT_PER_PIXEL == 18 +#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB666) +#elif TEST_LCD_BIT_PER_PIXEL == 16 +#define TEST_MIPI_DPI_PX_FORMAT (LCD_COLOR_PIXEL_FORMAT_RGB565) +#endif + +#define TEST_DELAY_TIME_MS (3000) + +#define TEST_MIPI_DSI_PHY_PWR_LDO_CHAN (3) +#define TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500) + +static char *TAG = "st7796_test"; +static esp_ldo_channel_handle_t ldo_mipi_phy = NULL; +static esp_lcd_panel_handle_t panel_handle = NULL; +static esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL; +static esp_lcd_panel_io_handle_t mipi_dbi_io = NULL; +static SemaphoreHandle_t refresh_finish = NULL; + +IRAM_ATTR static bool test_notify_refresh_ready(esp_lcd_panel_handle_t panel, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + SemaphoreHandle_t refresh_finish = (SemaphoreHandle_t)user_ctx; + BaseType_t need_yield = pdFALSE; + + xSemaphoreGiveFromISR(refresh_finish, &need_yield); + + return (need_yield == pdTRUE); +} + +static void test_init_lcd(void) +{ +#if TEST_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Turn on LCD backlight"); + gpio_config_t bk_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .pin_bit_mask = 1ULL << TEST_PIN_NUM_BK_LIGHT + }; + TEST_ESP_OK(gpio_config(&bk_gpio_config)); + TEST_ESP_OK(gpio_set_level(TEST_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL)); +#endif + + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state +#ifdef TEST_MIPI_DSI_PHY_PWR_LDO_CHAN + ESP_LOGI(TAG, "MIPI DSI PHY Powered on"); + esp_ldo_channel_config_t ldo_mipi_phy_config = { + .chan_id = TEST_MIPI_DSI_PHY_PWR_LDO_CHAN, + .voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }; + TEST_ESP_OK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy)); +#endif + + ESP_LOGI(TAG, "Initialize MIPI DSI bus"); + esp_lcd_dsi_bus_config_t bus_config = ST7796_PANEL_BUS_DSI_1CH_CONFIG(); + TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_dbi_io_config_t dbi_config = ST7796_PANEL_IO_DBI_CONFIG(); + TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io)); + + ESP_LOGI(TAG, "Install LCD driver of st7796"); + esp_lcd_dpi_panel_config_t dpi_config = ST7796_320_480_PANEL_60HZ_DPI_CONFIG(TEST_MIPI_DPI_PX_FORMAT); + st7796_vendor_config_t vendor_config = { + .flags.use_mipi_interface = 1, + .mipi_config.dsi_bus = mipi_dsi_bus, + .mipi_config.dpi_config = &dpi_config, + }; + const esp_lcd_panel_dev_config_t panel_config = { + .reset_gpio_num = TEST_PIN_NUM_LCD_RST, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .bits_per_pixel = TEST_LCD_BIT_PER_PIXEL, + .vendor_config = &vendor_config, + }; + TEST_ESP_OK(esp_lcd_new_panel_st7796(mipi_dbi_io, &panel_config, &panel_handle)); + TEST_ESP_OK(esp_lcd_panel_reset(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_init(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_disp_on_off(panel_handle, true)); + + refresh_finish = xSemaphoreCreateBinary(); + TEST_ASSERT_NOT_NULL(refresh_finish); + esp_lcd_dpi_panel_event_callbacks_t cbs = { + .on_color_trans_done = test_notify_refresh_ready, + }; + TEST_ESP_OK(esp_lcd_dpi_panel_register_event_callbacks(panel_handle, &cbs, refresh_finish)); +} + +static void test_deinit_lcd(void) +{ + TEST_ESP_OK(esp_lcd_panel_del(panel_handle)); + TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io)); + TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus)); + panel_handle = NULL; + mipi_dbi_io = NULL; + mipi_dsi_bus = NULL; + + if (ldo_mipi_phy) { + TEST_ESP_OK(esp_ldo_release_channel(ldo_mipi_phy)); + ldo_mipi_phy = NULL; + } + + vSemaphoreDelete(refresh_finish); + refresh_finish = NULL; + +#if TEST_PIN_NUM_BK_LIGHT >= 0 + TEST_ESP_OK(gpio_reset_pin(TEST_PIN_NUM_BK_LIGHT)); + TEST_ESP_OK(gpio_reset_pin(TEST_PIN_NUM_POWER)); +#endif +} + +static void test_draw_color_bar(esp_lcd_panel_handle_t panel_handle, uint16_t h_res, uint16_t v_res) +{ + uint8_t byte_per_pixel = (TEST_LCD_BIT_PER_PIXEL + 7) / 8; + uint16_t row_line = v_res / byte_per_pixel / 8; + uint8_t *color = (uint8_t *)heap_caps_calloc(1, row_line * h_res * byte_per_pixel, MALLOC_CAP_DMA); + + for (int j = 0; j < byte_per_pixel * 8; j++) { + for (int i = 0; i < row_line * h_res; i++) { + for (int k = 0; k < byte_per_pixel; k++) { + color[i * byte_per_pixel + k] = (BIT(j) >> (k * 8)) & 0xff; + } + } + TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, j * row_line, h_res, (j + 1) * row_line, color)); + xSemaphoreTake(refresh_finish, portMAX_DELAY); + } + + uint16_t color_line = row_line * byte_per_pixel * 8; + uint16_t res_line = v_res - color_line; + if (res_line) { + for (int i = 0; i < res_line * h_res; i++) { + for (int k = 0; k < byte_per_pixel; k++) { + color[i * byte_per_pixel + k] = 0xff; + } + } + TEST_ESP_OK(esp_lcd_panel_draw_bitmap(panel_handle, 0, color_line, h_res, v_res, color)); + xSemaphoreTake(refresh_finish, portMAX_DELAY); + } + + free(color); +} + +TEST_CASE("test st7796 to draw pattern with MIPI interface", "[st7796][draw_pattern]") +{ + ESP_LOGI(TAG, "Initialize LCD device"); + test_init_lcd(); + + ESP_LOGI(TAG, "Show color bar pattern drawn by hardware"); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_BAR_VERTICAL)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_BAR_HORIZONTAL)); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(panel_handle, MIPI_DSI_PATTERN_NONE)); + + ESP_LOGI(TAG, "Deinitialize LCD device"); + test_deinit_lcd(); +} + +TEST_CASE("test st7796 to draw color bar with MIPI interface", "[st7796][draw_color_bar]") +{ + ESP_LOGI(TAG, "Initialize LCD device"); + test_init_lcd(); + + ESP_LOGI(TAG, "Show color bar drawn by software"); + test_draw_color_bar(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES); + vTaskDelay(pdMS_TO_TICKS(TEST_DELAY_TIME_MS)); + + ESP_LOGI(TAG, "Deinitialize LCD device"); + test_deinit_lcd(); +} + +TEST_CASE("test st7796 to rotate with MIPI interface", "[st7796][rotate]") +{ + ESP_LOGI(TAG, "Initialize LCD device"); + test_init_lcd(); + + ESP_LOGI(TAG, "Mirror the screen"); + for (size_t i = 0; i < 4; i++) { + TEST_ASSERT_NOT_EQUAL(esp_lcd_panel_mirror(panel_handle, i & 2, i & 1), ESP_FAIL); + + ESP_LOGI(TAG, "Mirror: %d", i); + test_draw_color_bar(panel_handle, TEST_LCD_H_RES, TEST_LCD_V_RES); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + ESP_LOGI(TAG, "Deinitialize LCD device"); + test_deinit_lcd(); +} + +#endif