diff --git a/components/esp_lcd/test_apps/.build-test-rules.yml b/components/esp_lcd/test_apps/.build-test-rules.yml index 2ba3dd854a..a4c2fd4995 100644 --- a/components/esp_lcd/test_apps/.build-test-rules.yml +++ b/components/esp_lcd/test_apps/.build-test-rules.yml @@ -30,6 +30,16 @@ components/esp_lcd/test_apps/i80_lcd: disable: - if: SOC_LCD_I80_SUPPORTED != 1 +components/esp_lcd/test_apps/mipi_dsi_lcd: + depends_components: + - esp_lcd + disable: + - if: SOC_LCD_MIPI_DSI_SUPPORTED != 1 + disable_test: + - if: IDF_TARGET == "esp32p4" + temporary: true + reason: lack of runners + components/esp_lcd/test_apps/rgb_lcd: depends_components: - esp_lcd diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/CMakeLists.txt b/components/esp_lcd/test_apps/mipi_dsi_lcd/CMakeLists.txt new file mode 100644 index 0000000000..b28d8784fe --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/CMakeLists.txt @@ -0,0 +1,8 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.16) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(mipi_dsi_lcd_panel_test) diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/README.md b/components/esp_lcd/test_apps/mipi_dsi_lcd/README.md new file mode 100644 index 0000000000..aa2b4591a4 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/README.md @@ -0,0 +1,4 @@ +| Supported Targets | ESP32-P4 | +| ----------------- | -------- | + +This test app is used to test MIPI DSI interfaced LCDs. diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt new file mode 100644 index 0000000000..ab9796d92d --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/CMakeLists.txt @@ -0,0 +1,9 @@ +set(srcs "test_app_main.c" + "test_mipi_dsi_board.c" + "test_mipi_dsi_panel.c") + +# In order for the cases defined by `TEST_CASE` to be linked into the final elf, +# the component can be registered as WHOLE_ARCHIVE +idf_component_register(SRCS ${srcs} + PRIV_REQUIRES esp_lcd unity + WHOLE_ARCHIVE) diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/idf_component.yml b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/idf_component.yml new file mode 100644 index 0000000000..bcad3c93db --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/idf_component.yml @@ -0,0 +1,2 @@ +dependencies: + esp_lcd_ili9881c: ">=0.1.0" diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_app_main.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_app_main.c new file mode 100644 index 0000000000..c6de63b5ae --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_app_main.c @@ -0,0 +1,51 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in LCD driver, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-500) + +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(); +} diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_board.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_board.c new file mode 100644 index 0000000000..56867df2f8 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_board.c @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "unity.h" +#include "test_mipi_dsi_board.h" +#include "esp_private/esp_ldo.h" + +static esp_ldo_unit_handle_t phy_pwr_unit = NULL; + +void test_bsp_enable_dsi_phy_power(void) +{ + // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state + esp_ldo_unit_init_cfg_t ldo_cfg = { + .unit_id = TEST_MIPI_DSI_PHY_PWR_LDO_UNIT, + .cfg = { + .voltage_mv = TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV, + }, + }; + TEST_ESP_OK(esp_ldo_init_unit(&ldo_cfg, &phy_pwr_unit)); + TEST_ESP_OK(esp_ldo_enable_unit(phy_pwr_unit)); +} + +void test_bsp_disable_dsi_phy_power(void) +{ + TEST_ESP_OK(esp_ldo_disable_unit(phy_pwr_unit)); + TEST_ESP_OK(esp_ldo_deinit_unit(phy_pwr_unit)); +} diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_board.h b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_board.h new file mode 100644 index 0000000000..b7543de915 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_board.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +// FPS = 80000000/(40+140+40+800)/(4+16+16+1280) = 60Hz +#define MIPI_DSI_DPI_CLK_MHZ 80 +#define MIPI_DSI_LCD_H_RES 800 +#define MIPI_DSI_LCD_V_RES 1280 +#define MIPI_DSI_LCD_HSYNC 40 +#define MIPI_DSI_LCD_HBP 140 +#define MIPI_DSI_LCD_HFP 40 +#define MIPI_DSI_LCD_VSYNC 4 +#define MIPI_DSI_LCD_VBP 16 +#define MIPI_DSI_LCD_VFP 16 + +#define TEST_MIPI_DSI_PHY_PWR_LDO_UNIT 3 +#define TEST_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500 + +void test_bsp_enable_dsi_phy_power(void); +void test_bsp_disable_dsi_phy_power(void); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c new file mode 100644 index 0000000000..c2c3724f07 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/main/test_mipi_dsi_panel.c @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "esp_lcd_mipi_dsi.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lcd_panel_io.h" +#include "esp_random.h" +#include "esp_attr.h" +#include "test_mipi_dsi_board.h" +#include "esp_lcd_ili9881c.h" + +TEST_CASE("MIPI DSI Pattern Generator (ILI9881C)", "[mipi_dsi]") +{ + esp_lcd_dsi_bus_handle_t mipi_dsi_bus; + esp_lcd_panel_io_handle_t mipi_dbi_io; + esp_lcd_panel_handle_t mipi_dpi_panel; + esp_lcd_panel_handle_t ili9881c_ctrl_panel; + + test_bsp_enable_dsi_phy_power(); + + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = 2, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = 1000, // 1000 Mbps + }; + TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io)); + + esp_lcd_panel_dev_config_t lcd_dev_config = { + .bits_per_pixel = 16, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .reset_gpio_num = -1, + }; + TEST_ESP_OK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_reset(ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_init(ili9881c_ctrl_panel)); + // turn on display + TEST_ESP_OK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true)); + + esp_lcd_dpi_panel_config_t dpi_config = { + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = MIPI_DSI_DPI_CLK_MHZ, + .virtual_channel = 0, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .video_timing = { + .h_size = MIPI_DSI_LCD_H_RES, + .v_size = MIPI_DSI_LCD_V_RES, + .hsync_back_porch = MIPI_DSI_LCD_HBP, + .hsync_pulse_width = MIPI_DSI_LCD_HSYNC, + .hsync_front_porch = MIPI_DSI_LCD_HFP, + .vsync_back_porch = MIPI_DSI_LCD_VBP, + .vsync_pulse_width = MIPI_DSI_LCD_VSYNC, + .vsync_front_porch = MIPI_DSI_LCD_VFP, + }, + }; + TEST_ESP_OK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel)); + TEST_ESP_OK(esp_lcd_panel_init(mipi_dpi_panel)); + + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_BAR_HORIZONTAL)); + vTaskDelay(pdMS_TO_TICKS(1000)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_BAR_VERTICAL)); + vTaskDelay(pdMS_TO_TICKS(1000)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_BER_VERTICAL)); + vTaskDelay(pdMS_TO_TICKS(1000)); + TEST_ESP_OK(esp_lcd_dpi_panel_set_pattern(mipi_dpi_panel, MIPI_DSI_PATTERN_NONE)); + + TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel)); + TEST_ESP_OK(esp_lcd_panel_del(ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io)); + TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus)); + + test_bsp_disable_dsi_phy_power(); +} + +#define TEST_IMG_SIZE (100 * 100 * sizeof(uint16_t)) + +TEST_CASE("MIPI DSI draw bitmap (ILI9881C)", "[mipi_dsi]") +{ + esp_lcd_dsi_bus_handle_t mipi_dsi_bus; + esp_lcd_panel_io_handle_t mipi_dbi_io; + esp_lcd_panel_handle_t mipi_dpi_panel; + esp_lcd_panel_handle_t ili9881c_ctrl_panel; + + test_bsp_enable_dsi_phy_power(); + + uint8_t *img = malloc(TEST_IMG_SIZE); + TEST_ASSERT_NOT_NULL(img); + + esp_lcd_dsi_bus_config_t bus_config = { + .bus_id = 0, + .num_data_lanes = 2, + .phy_clk_src = MIPI_DSI_PHY_CLK_SRC_DEFAULT, + .lane_bit_rate_mbps = 1000, // 1000 Mbps + }; + TEST_ESP_OK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus)); + + esp_lcd_dbi_io_config_t dbi_config = { + .virtual_channel = 0, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + }; + TEST_ESP_OK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &mipi_dbi_io)); + + esp_lcd_panel_dev_config_t lcd_dev_config = { + .bits_per_pixel = 16, + .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, + .reset_gpio_num = -1, + }; + TEST_ESP_OK(esp_lcd_new_panel_ili9881c(mipi_dbi_io, &lcd_dev_config, &ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_reset(ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_init(ili9881c_ctrl_panel)); + // turn on display + TEST_ESP_OK(esp_lcd_panel_disp_on_off(ili9881c_ctrl_panel, true)); + + esp_lcd_dpi_panel_config_t dpi_config = { + .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, + .dpi_clock_freq_mhz = MIPI_DSI_DPI_CLK_MHZ, + .virtual_channel = 0, + .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565, + .video_timing = { + .h_size = MIPI_DSI_LCD_H_RES, + .v_size = MIPI_DSI_LCD_V_RES, + .hsync_back_porch = MIPI_DSI_LCD_HBP, + .hsync_pulse_width = MIPI_DSI_LCD_HSYNC, + .hsync_front_porch = MIPI_DSI_LCD_HFP, + .vsync_back_porch = MIPI_DSI_LCD_VBP, + .vsync_pulse_width = MIPI_DSI_LCD_VSYNC, + .vsync_front_porch = MIPI_DSI_LCD_VFP, + }, + }; + TEST_ESP_OK(esp_lcd_new_panel_dpi(mipi_dsi_bus, &dpi_config, &mipi_dpi_panel)); + TEST_ESP_OK(esp_lcd_panel_init(mipi_dpi_panel)); + + for (int i = 0; i < 100; i++) { + uint8_t color_byte = rand() & 0xFF; + int x_start = rand() % (MIPI_DSI_LCD_H_RES - 100); + int y_start = rand() % (MIPI_DSI_LCD_V_RES - 100); + memset(img, color_byte, TEST_IMG_SIZE); + esp_lcd_panel_draw_bitmap(mipi_dpi_panel, x_start, y_start, x_start + 100, y_start + 100, img); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + TEST_ESP_OK(esp_lcd_panel_del(mipi_dpi_panel)); + TEST_ESP_OK(esp_lcd_panel_del(ili9881c_ctrl_panel)); + TEST_ESP_OK(esp_lcd_panel_io_del(mipi_dbi_io)); + TEST_ESP_OK(esp_lcd_del_dsi_bus(mipi_dsi_bus)); + free(img); + + test_bsp_disable_dsi_phy_power(); +} diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py b/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py new file mode 100644 index 0000000000..eab77ed4c9 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/pytest_mipi_dsi_lcd.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32p4 +@pytest.mark.generic +def test_rgb_lcd(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.defaults b/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.defaults new file mode 100644 index 0000000000..0f3b68ef1e --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_FREERTOS_HZ=1000 +CONFIG_IDF_EXPERIMENTAL_FEATURES=y diff --git a/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.defaults.esp32p4 b/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.defaults.esp32p4 new file mode 100644 index 0000000000..702fac3342 --- /dev/null +++ b/components/esp_lcd/test_apps/mipi_dsi_lcd/sdkconfig.defaults.esp32p4 @@ -0,0 +1,3 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_HEX=y +CONFIG_SPIRAM_SPEED_200M=y