From f74f6a728aaa0a74a7c46f4570db3a384b7416b1 Mon Sep 17 00:00:00 2001 From: Liu Zhongwei Date: Tue, 18 Jun 2024 22:36:39 +0800 Subject: [PATCH] feat(repo): support build on the esp-idf --- .build-rules.yml | 48 + .codespellrc | 2 + .github/scripts/check_lib_versions.sh | 46 +- .github/workflows/check_lib_versions.yml | 5 +- .github/workflows/issue_comment.yml | 24 + .github/workflows/new_issues.yml | 24 + .github/workflows/new_prs.yml | 29 + .github/workflows/upload_component.yml | 20 + .gitlab-ci.yml | 28 + .gitlab/ci/build.yml | 125 ++ .gitlab/ci/deploy.yml | 25 + .gitlab/ci/pre_check.yml | 21 + .gitlab/ci/rules.yml | 209 ++++ .gitlab/tools/build_apps.py | 170 +++ .gitlab/tools/check_executables.py | 74 ++ .gitlab/tools/check_readme_links.py | 166 +++ .gitlab/tools/executable-list.txt | 4 + .gitlab/tools/idf_ci_utils.py | 112 ++ .gitlab/tools/push_to_github.sh | 13 + .pre-commit-config.yaml | 75 +- CHANGELOG.md | 2 +- CMakeLists.txt | 7 +- ESP_Panel_Board_Custom.h | 30 +- ESP_Panel_Board_Supported.h | 12 +- Kconfig | 26 + README.md | 447 +------ README_CN.md | 429 +------ conftest.py | 217 ++++ docs/Board_Contribution_Guide.md | 33 +- docs/Board_Contribution_Guide_CN.md | 31 +- docs/FAQ.md | 72 ++ docs/FAQ_CN.md | 72 ++ docs/How_To_Use.md | 356 ++++++ docs/How_To_Use_CN.md | 356 ++++++ docs/_static/block_diagram.drawio | 210 ++-- docs/_static/block_diagram.png | Bin 117803 -> 152308 bytes examples/LCD/3wireSPI_RGB/3wireSPI_RGB.ino | 20 +- examples/LCD/3wireSPI_RGB/README.md | 6 +- examples/LCD/QSPI/QSPI.ino | 8 +- examples/LCD/QSPI/README.md | 6 +- examples/LCD/RGB/README.md | 6 +- examples/LCD/RGB/RGB.ino | 6 +- examples/LCD/SPI/README.md | 6 +- examples/LCD/SPI/SPI.ino | 9 +- .../LVGL/v8/Porting/ESP_Panel_Board_Custom.h | 30 +- .../v8/Porting/ESP_Panel_Board_Supported.h | 12 +- examples/LVGL/v8/Porting/Porting.ino | 12 +- examples/LVGL/v8/Porting/README.md | 12 +- examples/LVGL/v8/Porting/lvgl_port_v8.cpp | 2 +- examples/LVGL/v8/Porting/lvgl_port_v8.h | 6 +- .../LVGL/v8/Rotation/ESP_Panel_Board_Custom.h | 30 +- .../v8/Rotation/ESP_Panel_Board_Supported.h | 12 +- examples/LVGL/v8/Rotation/README.md | 12 +- examples/LVGL/v8/Rotation/Rotation.ino | 12 +- examples/LVGL/v8/Rotation/lvgl_port_v8.cpp | 2 +- examples/LVGL/v8/Rotation/lvgl_port_v8.h | 6 +- .../Panel/PanelTest/ESP_Panel_Board_Custom.h | 30 +- .../PanelTest/ESP_Panel_Board_Supported.h | 12 +- examples/Panel/PanelTest/PanelTest.ino | 20 +- examples/Panel/PanelTest/README.md | 10 +- examples/PlatformIO/README.md | 12 +- examples/PlatformIO/platformio.ini | 1 - .../PlatformIO/src/ESP_Panel_Board_Custom.h | 30 +- .../src/ESP_Panel_Board_Supported.h | 12 +- examples/PlatformIO/src/lvgl_port_v8.cpp | 2 +- examples/PlatformIO/src/lvgl_port_v8.h | 6 +- .../v8/Porting/ESP_Panel_Board_Custom.h | 30 +- .../v8/Porting/ESP_Panel_Board_Supported.h | 12 +- examples/SquareLine/v8/Porting/Porting.ino | 12 +- examples/SquareLine/v8/Porting/README.md | 12 +- .../SquareLine/v8/Porting/lvgl_port_v8.cpp | 2 +- examples/SquareLine/v8/Porting/lvgl_port_v8.h | 6 +- .../v8/WiFiClock/ESP_Panel_Board_Custom.h | 30 +- .../v8/WiFiClock/ESP_Panel_Board_Supported.h | 12 +- examples/SquareLine/v8/WiFiClock/README.md | 12 +- .../SquareLine/v8/WiFiClock/WiFiClock.ino | 12 +- .../SquareLine/v8/WiFiClock/lvgl_port_v8.cpp | 2 +- .../SquareLine/v8/WiFiClock/lvgl_port_v8.h | 6 +- examples/Touch/I2C/I2C.ino | 6 +- examples/Touch/I2C/README.md | 6 +- examples/Touch/SPI/README.md | 6 +- examples/Touch/SPI/SPI.ino | 6 +- idf_component.yml | 10 + library.properties | 6 +- pytest.ini | 42 + src/ESP_Panel.cpp | 23 +- src/ESP_Panel.h | 37 +- src/ESP_PanelLog.h | 3 + src/ESP_PanelTypes.h | 5 +- src/ESP_PanelVersions.h | 128 +- src/ESP_Panel_Board_Internal.h | 157 +-- src/ESP_Panel_Board_Kconfig.h | 1102 +++++++++++++++++ src/ESP_Panel_Conf_Internal.h | 82 +- src/ESP_Panel_Conf_Kconfig.h | 71 ++ src/ESP_Panel_Library.h | 1 + src/backlight/Kconfig.in | 20 + src/board/Kconfig.board | 25 + src/board/Kconfig.board_custom | 803 ++++++++++++ src/board/Kconfig.board_supported | 60 + src/board/elecrow/CROWPANEL_7_0.h | 28 +- src/board/elecrow/Kconfig.elecrow | 4 + src/board/espressif/ESP32_C3_LCDKIT.h | 28 +- src/board/espressif/ESP32_S3_BOX.h | 28 +- src/board/espressif/ESP32_S3_BOX_3.h | 2 +- src/board/espressif/ESP32_S3_BOX_3_BETA.h | 28 +- src/board/espressif/ESP32_S3_BOX_LITE.h | 28 +- src/board/espressif/ESP32_S3_EYE.h | 28 +- src/board/espressif/ESP32_S3_KORVO_2.h | 28 +- src/board/espressif/ESP32_S3_LCD_EV_BOARD.h | 28 +- src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h | 28 +- .../espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h | 28 +- .../espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h | 28 +- src/board/espressif/ESP32_S3_USB_OTG.h | 28 +- src/board/espressif/Kconfig.espressif | 59 + src/board/jingcai/ESP32_4848S040C_I_Y_3.h | 2 +- src/board/jingcai/Kconfig.jingcai | 4 + src/board/m5stack/Kconfig.m5stack | 14 + src/board/m5stack/M5CORE2.h | 30 +- src/board/m5stack/M5CORES3.h | 36 +- src/board/m5stack/M5DIAL.h | 28 +- src/board/waveshare/ESP32_S3_Touch_LCD_1_85.h | 28 +- src/board/waveshare/ESP32_S3_Touch_LCD_2_1.h | 2 +- src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h | 28 +- src/board/waveshare/Kconfig.waveshare | 14 + src/bus/ESP_PanelBus.cpp | 6 +- src/bus/ESP_PanelBus.h | 4 +- src/bus/I2C.cpp | 5 + src/bus/SPI.cpp | 12 +- src/bus/SPI.h | 7 + src/bus/base/esp_lcd_panel_io_additions.h | 2 +- src/host/ESP_PanelHost.cpp | 40 +- src/host/ESP_PanelHost.h | 6 +- src/lcd/ESP_PanelLcd.cpp | 57 +- src/lcd/ESP_PanelLcd.h | 13 +- src/lcd/base/esp_lcd_custom_types.h | 3 + src/lcd/base/esp_lcd_gc9503.c | 2 +- src/lcd/base/esp_lcd_ili9341.c | 2 +- src/lcd/base/esp_lcd_st7701.c | 2 +- src/touch/ESP_PanelTouch.h | 2 +- src/touch/Kconfig.touch | 45 + src/touch/base/esp_lcd_touch.h | 6 +- src/touch/base/esp_lcd_touch_gt1151.c | 2 +- src/touch/base/esp_lcd_touch_xpt2046.c | 2 +- src/touch/base/esp_lcd_touch_xpt2046.h | 4 +- test_apps/lcd/3wire_spi_rgb/CMakeLists.txt | 5 + .../lcd/3wire_spi_rgb/main/CMakeLists.txt | 5 + .../lcd/3wire_spi_rgb/main/idf_component.yml | 9 + .../main/test_3wire_spi_rgb_lcd.cpp | 237 ++++ .../lcd/3wire_spi_rgb/main/test_app_main.c | 63 + .../lcd/3wire_spi_rgb/sdkconfig.defaults | 7 + test_apps/lcd/qspi/CMakeLists.txt | 5 + test_apps/lcd/qspi/main/CMakeLists.txt | 5 + test_apps/lcd/qspi/main/idf_component.yml | 9 + test_apps/lcd/qspi/main/test_app_main.c | 65 + test_apps/lcd/qspi/main/test_qspi_lcd.cpp | 160 +++ test_apps/lcd/qspi/sdkconfig.defaults | 2 + test_apps/lcd/rgb/CMakeLists.txt | 5 + test_apps/lcd/rgb/main/CMakeLists.txt | 5 + test_apps/lcd/rgb/main/idf_component.yml | 9 + test_apps/lcd/rgb/main/test_app_main.c | 63 + test_apps/lcd/rgb/main/test_rgb_lcd.cpp | 204 +++ test_apps/lcd/rgb/sdkconfig.defaults | 7 + test_apps/lcd/spi/CMakeLists.txt | 5 + test_apps/lcd/spi/main/CMakeLists.txt | 5 + test_apps/lcd/spi/main/idf_component.yml | 9 + test_apps/lcd/spi/main/test_app_main.c | 63 + test_apps/lcd/spi/main/test_spi_lcd.cpp | 161 +++ test_apps/lcd/spi/sdkconfig.defaults | 2 + test_apps/lvgl_port/CMakeLists.txt | 5 + test_apps/lvgl_port/main/CMakeLists.txt | 6 + test_apps/lvgl_port/main/Kconfig.projbuild | 26 + test_apps/lvgl_port/main/idf_component.yml | 11 + test_apps/lvgl_port/main/lvgl_port_v8.cpp | 687 ++++++++++ test_apps/lvgl_port/main/lvgl_port_v8.h | 170 +++ test_apps/lvgl_port/main/test_app_main.c | 63 + test_apps/lvgl_port/main/test_lvgl_port.cpp | 59 + test_apps/lvgl_port/partitions.csv | 5 + .../sdkconfig.ci.elecrow_crowpanel_7_0 | 11 + .../sdkconfig.ci.espressif_esp32_c3_lcdkit | 3 + .../sdkconfig.ci.espressif_esp32_s3_box | 9 + .../sdkconfig.ci.espressif_esp32_s3_box_3 | 9 + ...sdkconfig.ci.espressif_esp32_s3_box_3_beta | 9 + .../sdkconfig.ci.espressif_esp32_s3_box_lite | 9 + .../sdkconfig.ci.espressif_esp32_s3_eye | 9 + .../sdkconfig.ci.espressif_esp32_s3_korvo_2 | 9 + ...kconfig.ci.espressif_esp32_s3_lcd_ev_board | 11 + ...onfig.ci.espressif_esp32_s3_lcd_ev_board_2 | 11 + ....ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 | 11 + ...ig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 | 11 + .../sdkconfig.ci.espressif_esp32_s3_usb_otg | 3 + ...sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 | 11 + .../lvgl_port/sdkconfig.ci.m5stack_m5core2 | 3 + .../lvgl_port/sdkconfig.ci.m5stack_m5core3 | 9 + .../lvgl_port/sdkconfig.ci.m5stack_m5dial | 9 + ...onfig.ci.waveshare_esp32_s3_touch_lcd_1_85 | 11 + ...config.ci.waveshare_esp32_s3_touch_lcd_2_1 | 9 + ...config.ci.waveshare_esp32_s3_touch_lcd_4_3 | 11 + test_apps/lvgl_port/sdkconfig.defaults | 23 + test_apps/panel/CMakeLists.txt | 5 + test_apps/panel/main/CMakeLists.txt | 4 + test_apps/panel/main/idf_component.yml | 9 + test_apps/panel/main/test_app_main.c | 63 + test_apps/panel/main/test_panel.cpp | 114 ++ .../panel/sdkconfig.ci.elecrow_crowpanel_7_0 | 9 + .../sdkconfig.ci.espressif_esp32_c3_lcdkit | 3 + .../panel/sdkconfig.ci.espressif_esp32_s3_box | 9 + .../sdkconfig.ci.espressif_esp32_s3_box_3 | 9 + ...sdkconfig.ci.espressif_esp32_s3_box_3_beta | 9 + .../sdkconfig.ci.espressif_esp32_s3_box_lite | 9 + .../panel/sdkconfig.ci.espressif_esp32_s3_eye | 9 + .../sdkconfig.ci.espressif_esp32_s3_korvo_2 | 9 + ...kconfig.ci.espressif_esp32_s3_lcd_ev_board | 9 + ...onfig.ci.espressif_esp32_s3_lcd_ev_board_2 | 9 + ....ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 | 9 + ...ig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 | 9 + .../sdkconfig.ci.espressif_esp32_s3_usb_otg | 3 + ...sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 | 9 + test_apps/panel/sdkconfig.ci.m5stack_m5core2 | 3 + test_apps/panel/sdkconfig.ci.m5stack_m5core3 | 9 + test_apps/panel/sdkconfig.ci.m5stack_m5dial | 9 + ...onfig.ci.waveshare_esp32_s3_touch_lcd_1_85 | 9 + ...config.ci.waveshare_esp32_s3_touch_lcd_2_1 | 9 + ...config.ci.waveshare_esp32_s3_touch_lcd_4_3 | 9 + test_apps/panel/sdkconfig.defaults | 2 + test_apps/touch/i2c/CMakeLists.txt | 5 + test_apps/touch/i2c/main/CMakeLists.txt | 7 + test_apps/touch/i2c/main/idf_component.yml | 9 + test_apps/touch/i2c/main/test_app_main.c | 63 + test_apps/touch/i2c/main/test_i2c_touch.cpp | 119 ++ test_apps/touch/i2c/sdkconfig.defaults | 2 + test_apps/touch/spi/CMakeLists.txt | 5 + test_apps/touch/spi/main/CMakeLists.txt | 5 + test_apps/touch/spi/main/idf_component.yml | 9 + test_apps/touch/spi/main/test_app_main.c | 63 + test_apps/touch/spi/main/test_spi_touch.cpp | 110 ++ test_apps/touch/spi/sdkconfig.defaults | 2 + tools/check_file_version.py | 98 +- tools/sync_conf_files.py | 8 +- 238 files changed, 8788 insertions(+), 1845 deletions(-) create mode 100644 .build-rules.yml create mode 100644 .codespellrc mode change 100644 => 100755 .github/scripts/check_lib_versions.sh create mode 100644 .github/workflows/issue_comment.yml create mode 100644 .github/workflows/new_issues.yml create mode 100644 .github/workflows/new_prs.yml create mode 100644 .github/workflows/upload_component.yml create mode 100644 .gitlab-ci.yml create mode 100644 .gitlab/ci/build.yml create mode 100644 .gitlab/ci/deploy.yml create mode 100644 .gitlab/ci/pre_check.yml create mode 100644 .gitlab/ci/rules.yml create mode 100644 .gitlab/tools/build_apps.py create mode 100755 .gitlab/tools/check_executables.py create mode 100755 .gitlab/tools/check_readme_links.py create mode 100644 .gitlab/tools/executable-list.txt create mode 100644 .gitlab/tools/idf_ci_utils.py create mode 100755 .gitlab/tools/push_to_github.sh create mode 100644 Kconfig create mode 100644 conftest.py create mode 100644 docs/FAQ.md create mode 100644 docs/FAQ_CN.md create mode 100644 docs/How_To_Use.md create mode 100644 docs/How_To_Use_CN.md create mode 100644 idf_component.yml create mode 100644 pytest.ini create mode 100644 src/ESP_Panel_Board_Kconfig.h create mode 100644 src/ESP_Panel_Conf_Kconfig.h create mode 100644 src/backlight/Kconfig.in create mode 100644 src/board/Kconfig.board create mode 100644 src/board/Kconfig.board_custom create mode 100644 src/board/Kconfig.board_supported create mode 100644 src/board/elecrow/Kconfig.elecrow create mode 100644 src/board/espressif/Kconfig.espressif create mode 100644 src/board/jingcai/Kconfig.jingcai create mode 100644 src/board/m5stack/Kconfig.m5stack create mode 100644 src/board/waveshare/Kconfig.waveshare create mode 100644 src/touch/Kconfig.touch create mode 100644 test_apps/lcd/3wire_spi_rgb/CMakeLists.txt create mode 100644 test_apps/lcd/3wire_spi_rgb/main/CMakeLists.txt create mode 100644 test_apps/lcd/3wire_spi_rgb/main/idf_component.yml create mode 100644 test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp create mode 100644 test_apps/lcd/3wire_spi_rgb/main/test_app_main.c create mode 100644 test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults create mode 100644 test_apps/lcd/qspi/CMakeLists.txt create mode 100644 test_apps/lcd/qspi/main/CMakeLists.txt create mode 100644 test_apps/lcd/qspi/main/idf_component.yml create mode 100644 test_apps/lcd/qspi/main/test_app_main.c create mode 100644 test_apps/lcd/qspi/main/test_qspi_lcd.cpp create mode 100644 test_apps/lcd/qspi/sdkconfig.defaults create mode 100644 test_apps/lcd/rgb/CMakeLists.txt create mode 100644 test_apps/lcd/rgb/main/CMakeLists.txt create mode 100644 test_apps/lcd/rgb/main/idf_component.yml create mode 100644 test_apps/lcd/rgb/main/test_app_main.c create mode 100644 test_apps/lcd/rgb/main/test_rgb_lcd.cpp create mode 100644 test_apps/lcd/rgb/sdkconfig.defaults create mode 100644 test_apps/lcd/spi/CMakeLists.txt create mode 100644 test_apps/lcd/spi/main/CMakeLists.txt create mode 100644 test_apps/lcd/spi/main/idf_component.yml create mode 100644 test_apps/lcd/spi/main/test_app_main.c create mode 100644 test_apps/lcd/spi/main/test_spi_lcd.cpp create mode 100644 test_apps/lcd/spi/sdkconfig.defaults create mode 100644 test_apps/lvgl_port/CMakeLists.txt create mode 100644 test_apps/lvgl_port/main/CMakeLists.txt create mode 100644 test_apps/lvgl_port/main/Kconfig.projbuild create mode 100644 test_apps/lvgl_port/main/idf_component.yml create mode 100644 test_apps/lvgl_port/main/lvgl_port_v8.cpp create mode 100644 test_apps/lvgl_port/main/lvgl_port_v8.h create mode 100644 test_apps/lvgl_port/main/test_app_main.c create mode 100644 test_apps/lvgl_port/main/test_lvgl_port.cpp create mode 100644 test_apps/lvgl_port/partitions.csv create mode 100644 test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg create mode 100644 test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial create mode 100644 test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 create mode 100644 test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 create mode 100644 test_apps/lvgl_port/sdkconfig.defaults create mode 100644 test_apps/panel/CMakeLists.txt create mode 100644 test_apps/panel/main/CMakeLists.txt create mode 100644 test_apps/panel/main/idf_component.yml create mode 100644 test_apps/panel/main/test_app_main.c create mode 100644 test_apps/panel/main/test_panel.cpp create mode 100644 test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 create mode 100644 test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg create mode 100644 test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 create mode 100644 test_apps/panel/sdkconfig.ci.m5stack_m5core2 create mode 100644 test_apps/panel/sdkconfig.ci.m5stack_m5core3 create mode 100644 test_apps/panel/sdkconfig.ci.m5stack_m5dial create mode 100644 test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 create mode 100644 test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 create mode 100644 test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 create mode 100644 test_apps/panel/sdkconfig.defaults create mode 100644 test_apps/touch/i2c/CMakeLists.txt create mode 100644 test_apps/touch/i2c/main/CMakeLists.txt create mode 100644 test_apps/touch/i2c/main/idf_component.yml create mode 100644 test_apps/touch/i2c/main/test_app_main.c create mode 100644 test_apps/touch/i2c/main/test_i2c_touch.cpp create mode 100644 test_apps/touch/i2c/sdkconfig.defaults create mode 100644 test_apps/touch/spi/CMakeLists.txt create mode 100644 test_apps/touch/spi/main/CMakeLists.txt create mode 100644 test_apps/touch/spi/main/idf_component.yml create mode 100644 test_apps/touch/spi/main/test_app_main.c create mode 100644 test_apps/touch/spi/main/test_spi_touch.cpp create mode 100644 test_apps/touch/spi/sdkconfig.defaults diff --git a/.build-rules.yml b/.build-rules.yml new file mode 100644 index 00000000..bee2b420 --- /dev/null +++ b/.build-rules.yml @@ -0,0 +1,48 @@ +# Note: All operators are binary operators. For more than two operands, you may use the nested parentheses trick. +# For example: +# * A == 1 or (B == 2 and C in [1,2,3]) +# * (A == 1 and B == 2) or (C not in ["3", "4", 5]) + +# Test apps +test_apps/lcd/3wire_spi_rgb: + disable: + - if: SOC_LCD_RGB_SUPPORTED != 1 + - if: IDF_TARGET == "esp32p4" + temporary: true + reason: not ready + +test_apps/lcd/qspi: + disable: + - if: SOC_GPSPI_SUPPORTED != 1 + +test_apps/lcd/rgb: + disable: + - if: SOC_LCD_RGB_SUPPORTED != 1 + - if: IDF_TARGET == "esp32p4" + temporary: true + reason: not ready + +test_apps/lcd/spi: + disable: + - if: SOC_GPSPI_SUPPORTED != 1 + +test_apps/lvgl_port: + enable: + - if: INCLUDE_DEFAULT == 1 + +test_apps/panel: + enable: + - if: INCLUDE_DEFAULT == 1 + +test_apps/touch/i2c: + disable: + - if: SOC_I2C_SUPPORTED != 1 + +test_apps/touch/spi: + disable: + - if: SOC_GPSPI_SUPPORTED != 1 + +# Examples +# examples/esp_idf/esp_brookesia_phone_m5stace_core_s3: +# enable: +# - if: IDF_TARGET in ["esp32s3"] diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 00000000..0a3c74da --- /dev/null +++ b/.codespellrc @@ -0,0 +1,2 @@ +[codespell] +skip = ./src/touch/base/esp_lcd_touch_xpt2046.c diff --git a/.github/scripts/check_lib_versions.sh b/.github/scripts/check_lib_versions.sh old mode 100644 new mode 100755 index d458178c..cdfe6d8f --- a/.github/scripts/check_lib_versions.sh +++ b/.github/scripts/check_lib_versions.sh @@ -13,26 +13,6 @@ check_version_format() { return 0 } -if [ $# -lt 1 ]; then - latest_version="0.0.0" - echo "Don't get the lastest version, use \"0.0.0\" as default" -else - # Get the first input parameter as the version to be compared - latest_version="$1" - # Check the version format - check_version_format "${latest_version}" - result=$? - if [ ${result} -ne 0 ]; then - echo "The latest release version (${latest_version}) format is incorrect." - exit 1 - fi -fi - -# Specify the directory path -target_directory="./" - -echo "Checking directory: ${target_directory}" - # Function: Check if a file exists # Input parameters: $1 The file to check # Return value: 0 if the file exists, 1 if the file does not exist @@ -68,6 +48,32 @@ compare_versions() { return 0 } +# Get the latest release version +latest_version="v0.0.0" +for i in "$@"; do + case $i in + --latest_version=*) + latest_version="${i#*=}" + shift + ;; + *) + ;; + esac +done +# Check the version format +check_version_format "${latest_version}" +result=$? +if [ ${result} -ne 0 ]; then + echo "The latest release version (${latest_version}) format is incorrect." + exit 1 +fi +echo "Get the latest release version: ${latest_version}" + +# Specify the directory path +target_directory="./" + +echo "Checking directory: ${target_directory}" + echo "Checking file: library.properties" # Check if "library.properties" file exists check_file_exists "${target_directory}/library.properties" diff --git a/.github/workflows/check_lib_versions.yml b/.github/workflows/check_lib_versions.yml index a70f5403..48a48994 100644 --- a/.github/workflows/check_lib_versions.yml +++ b/.github/workflows/check_lib_versions.yml @@ -5,7 +5,7 @@ on: types: [opened, reopened, synchronize] jobs: - check_versions: + check_lib_versions: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 @@ -26,5 +26,4 @@ jobs: echo "prerelease: ${{ steps.last_release.outputs.prerelease }}" echo "url: ${{ steps.last_release.outputs.url }}" - name: Check & Compare versions - run: bash ./.github/scripts/check_lib_versions.sh ${{ steps.last_release.outputs.tag_name }} - + run: bash ./.github/scripts/check_lib_versions.sh --latest_version=${{ steps.last_release.outputs.tag_name }} diff --git a/.github/workflows/issue_comment.yml b/.github/workflows/issue_comment.yml new file mode 100644 index 00000000..3b6fcc19 --- /dev/null +++ b/.github/workflows/issue_comment.yml @@ -0,0 +1,24 @@ +name: Sync issue comments to JIRA + +# This workflow will be triggered when new issue comment is created (including PR comments) +on: issue_comment + +# Limit to single concurrent run for workflows which can create Jira issues. +# Same concurrency group is used in new_issues.yml +concurrency: jira_issues + +jobs: + sync_issue_comments_to_jira: + name: Sync Issue Comments to Jira + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Sync issue comments to JIRA + uses: espressif/github-actions/sync_issues_to_jira@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JIRA_PASS: ${{ secrets.JIRA_PASS }} + JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} + JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} + JIRA_URL: ${{ secrets.JIRA_URL }} + JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/.github/workflows/new_issues.yml b/.github/workflows/new_issues.yml new file mode 100644 index 00000000..f0fa4025 --- /dev/null +++ b/.github/workflows/new_issues.yml @@ -0,0 +1,24 @@ +name: Sync issues to Jira + +# This workflow will be triggered when a new issue is opened +on: issues + +# Limit to single concurrent run for workflows which can create Jira issues. +# Same concurrency group is used in issue_comment.yml +concurrency: jira_issues + +jobs: + sync_issues_to_jira: + name: Sync issues to Jira + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Sync GitHub issues to Jira project + uses: espressif/github-actions/sync_issues_to_jira@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JIRA_PASS: ${{ secrets.JIRA_PASS }} + JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} + JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} + JIRA_URL: ${{ secrets.JIRA_URL }} + JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/.github/workflows/new_prs.yml b/.github/workflows/new_prs.yml new file mode 100644 index 00000000..01d7fe26 --- /dev/null +++ b/.github/workflows/new_prs.yml @@ -0,0 +1,29 @@ +name: Sync remain PRs to Jira + +# This workflow will be triggered every hour, to sync remaining PRs (i.e. PRs with zero comment) to Jira project +# Note that, PRs can also get synced when new PR comment is created +on: + schedule: + - cron: "0 * * * *" + +# Limit to single concurrent run for workflows which can create Jira issues. +# Same concurrency group is used in issue_comment.yml +concurrency: jira_issues + +jobs: + sync_prs_to_jira: + name: Sync PRs to Jira + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Sync PRs to Jira project + uses: espressif/github-actions/sync_issues_to_jira@master + with: + cron_job: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + JIRA_PASS: ${{ secrets.JIRA_PASS }} + JIRA_PROJECT: ${{ secrets.JIRA_PROJECT }} + JIRA_COMPONENT: ${{ secrets.JIRA_COMPONENT }} + JIRA_URL: ${{ secrets.JIRA_URL }} + JIRA_USER: ${{ secrets.JIRA_USER }} diff --git a/.github/workflows/upload_component.yml b/.github/workflows/upload_component.yml new file mode 100644 index 00000000..f90855af --- /dev/null +++ b/.github/workflows/upload_component.yml @@ -0,0 +1,20 @@ +name: Push components to Espressif Component Service + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + upload_components: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@master + with: + submodules: 'recursive' + - name: Upload components to component service + uses: espressif/upload-components-ci-action@v1 + with: + name: "ESP32_Display_Panel" + namespace: "espressif" + api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..36c31d52 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,28 @@ +stages: + - pre_check + - build + - deploy + +workflow: + rules: + # Disable those non-protected push triggered pipelines + - if: '$CI_COMMIT_REF_NAME != "master" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^v\d+\.\d+(\.\d+)?($|-)/ && $CI_PIPELINE_SOURCE == "push"' + when: never + # when running merged result pipelines, it would create a temp commit id. use $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA instead of $CI_COMMIT_SHA. + # Please use PIPELINE_COMMIT_SHA at all places that require a commit sha + - if: $CI_OPEN_MERGE_REQUESTS != null + variables: + PIPELINE_COMMIT_SHA: $CI_MERGE_REQUEST_SOURCE_BRANCH_SHA + - if: $CI_OPEN_MERGE_REQUESTS == null + variables: + PIPELINE_COMMIT_SHA: $CI_COMMIT_SHA + - when: always + +variables: + COMPONENT_PATH: "$CI_PROJECT_DIR" + +include: + - '.gitlab/ci/rules.yml' + - '.gitlab/ci/pre_check.yml' + - '.gitlab/ci/build.yml' + - '.gitlab/ci/deploy.yml' diff --git a/.gitlab/ci/build.yml b/.gitlab/ci/build.yml new file mode 100644 index 00000000..a429ab4f --- /dev/null +++ b/.gitlab/ci/build.yml @@ -0,0 +1,125 @@ +.build_template: &build_template + stage: build + tags: + - build + image: ${IMAGE} + variables: + # Enable ccache for all build jobs. See configure_ci_environment.sh for more ccache related settings. + IDF_CCACHE_ENABLE: "1" + BATCH_BUILD: "1" + V: "0" + WARNING_STR: "" + +.build_examples_template: &build_examples_template + <<: *build_template + artifacts: + when: always + paths: + - "**/build*/size.json" + - "**/build*/build_log.txt" + - "**/build*/*.bin" + # upload to s3 server to save the artifacts size + - "**/build*/*.map" + - "**/build*/*.elf" + - "**/build*/flasher_args.json" + - "**/build*/flash_args" + - "**/build*/flash_project_args" + - "**/build*/config/sdkconfig.json" + - "**/build*/bootloader/*.bin" + - "**/build*/bootloader/*.elf" + - "**/build*/partition_table/*.bin" + - "**/build*/mmap_build/*.bin" + - "**/build*/**/*.bin" + - size_info.txt + expire_in: 1 week + variables: + IDF_CI_BUILD: "1" + # By configuring this macro, you can append the compiled configuration file. + # For example, using "sdkconf.etc=default" specifies the default sdkconfig file. + EXAMPLE_CONFIG: "=" + script: + - pip install "idf-component-manager" + - pip install idf_build_apps + - python .gitlab/tools/build_apps.py ${EXAMPLE_DIR} --config ${EXAMPLE_CONFIG} -t all -vv + +# Target ESP-IDF versions +.build_idf_active_release_version: + parallel: + matrix: + - IMAGE: espressif/idf:release-v5.1 + - IMAGE: espressif/idf:release-v5.2 + - IMAGE: espressif/idf:release-v5.3 + +# Test apps +build_test_apps_lcd_3wire_spi_rgb: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_lcd_3wire_spi_rgb + variables: + EXAMPLE_DIR: test_apps/lcd/3wire_spi_rgb + +build_test_apps_lcd_qspi: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_lcd_qspi + variables: + EXAMPLE_DIR: test_apps/lcd/qspi + +build_test_apps_lcd_rgb: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_lcd_rgb + variables: + EXAMPLE_DIR: test_apps/lcd/rgb + +build_test_apps_lcd_spi: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_lcd_spi + variables: + EXAMPLE_DIR: test_apps/lcd/spi + +build_test_apps_lvgl_port: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_lvgl_port + variables: + EXAMPLE_DIR: test_apps/lvgl_port + +build_test_apps_panel: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_panel + variables: + EXAMPLE_DIR: test_apps/panel + +build_test_apps_touch_i2c: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_touch_i2c + variables: + EXAMPLE_DIR: test_apps/touch/i2c + +build_test_apps_touch_spi: + extends: + - .build_examples_template + - .build_idf_active_release_version + - .rules:build:test_apps_touch_spi + variables: + EXAMPLE_DIR: test_apps/touch/spi + +# Examples +# build_example_esp_brookesia_phone_m5stace_core_s3: +# extends: +# - .build_examples_template +# - .build_esp32_s3_idf_release_version +# - .rules:build:example_esp_brookesia_phone_m5stace_core_s3 +# variables: +# EXAMPLE_DIR: examples/esp_idf/esp_brookesia_phone_m5stace_core_s3 diff --git a/.gitlab/ci/deploy.yml b/.gitlab/ci/deploy.yml new file mode 100644 index 00000000..7447498c --- /dev/null +++ b/.gitlab/ci/deploy.yml @@ -0,0 +1,25 @@ +push_to_github: + stage: deploy + only: + - master + - /^release\/v/ +# when: on_success + image: $CI_DOCKER_REGISTRY/esp32-ci-env + tags: + - github_sync + variables: + GIT_STRATEGY: clone + SUBMODULES_TO_FETCH: "none" + dependencies: [] + before_script: + - echo "skip default before_script" + script: + - mkdir -p ~/.ssh + - chmod 700 ~/.ssh + - echo -n $GH_PUSH_KEY > ~/.ssh/id_rsa_base64 + - base64 --decode --ignore-garbage ~/.ssh/id_rsa_base64 > ~/.ssh/id_rsa + - chmod 600 ~/.ssh/id_rsa + - echo -e "Host github.com\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config + - git remote remove github &>/dev/null || true + - git remote add github git@github.com:esp-arduino-libs/ESP32_Display_Panel.git + - ${COMPONENT_PATH}/.gitlab/tools/push_to_github.sh diff --git a/.gitlab/ci/pre_check.yml b/.gitlab/ci/pre_check.yml new file mode 100644 index 00000000..392e3566 --- /dev/null +++ b/.gitlab/ci/pre_check.yml @@ -0,0 +1,21 @@ +.pre_check_template: + stage: pre_check + image: python:3.9 + tags: ["build", "amd64", "internet"] + dependencies: [] + +check_pre_commit: + extends: + - .pre_check_template + before_script: + - pip install pre-commit + script: + - pre-commit run --show-diff-on-failure --color=always --all-files + +check_readme_links: + extends: + - .pre_check_template + - .rules:pre_check:readme + allow_failure: true + script: + - python ${CI_PROJECT_DIR}/.gitlab/tools/check_readme_links.py diff --git a/.gitlab/ci/rules.yml b/.gitlab/ci/rules.yml new file mode 100644 index 00000000..50720242 --- /dev/null +++ b/.gitlab/ci/rules.yml @@ -0,0 +1,209 @@ +############ +# Patterns # +############ + +# build system, if changed, build all apps +.patterns-build_system: &patterns-build_system + # For test + - ".gitlab/**/*" + - ".build-rules.yml" + - "conftest.py" + - "pytest.ini" + +# component folder +.patterns-component: &patterns-component + - "src/**/*" + - "CMakeLists.txt" + - "esp_brookesia_conf.h" + - "idf_component.yml" + - "Kconfig" + +# docs folder +.patterns-docs_md: &patterns-docs_md + - "**/*.md" + +# test_apps folder +.patterns-test_apps_lcd_3wire_spi_rgb: &patterns-test_apps_lcd_3wire_spi_rgb + - "test_apps/lcd/3wire_spi_rgb/**/*" + +.patterns-test_apps_lcd_qspi: &patterns-test_apps_lcd_qspi + - "test_apps/lcd/qspi/**/*" + +.patterns-test_apps_lcd_rgb: &patterns-test_apps_lcd_rgb + - "test_apps/lcd/rgb/**/*" + +.patterns-test_apps_lcd_spi: &patterns-test_apps_lcd_spi + - "test_apps/lcd/spi/**/*" + +.patterns-test_apps_lvgl_port: &patterns-test_apps_lvgl_port + - "test_apps/lvgl_port/**/*" + +.patterns-test_apps_panel: &patterns-test_apps_panel + - "test_apps/panel/**/*" + +.patterns-test_apps_touch_i2c: &patterns-test_apps_touch_i2c + - "test_apps/touch/i2c/**/*" + +.patterns-test_apps_touch_spi: &patterns-test_apps_touch_spi + - "test_apps/touch/spi/**/*" + +# examples folder +# .patterns-example_esp_brookesia_phone_m5stace_core_s3: &patterns-example_esp_brookesia_phone_m5stace_core_s3 +# - "examples/esp_idf/esp_brookesia_phone_m5stace_core_s3/**/*" + +############## +# if anchors # +############## +.if-protected: &if-protected + if: '($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_BRANCH =~ /^release\/v/ || $CI_COMMIT_TAG =~ /^v\d+\.\d+(\.\d+)?($|-)/)' + +.if-dev-push: &if-dev-push + if: '$CI_COMMIT_REF_NAME != "master" && $CI_COMMIT_BRANCH !~ /^release\/v/ && $CI_COMMIT_TAG !~ /^v\d+\.\d+(\.\d+)?($|-)/ && ($CI_PIPELINE_SOURCE == "push" || $CI_PIPELINE_SOURCE == "merge_request_event")' + +################## +# Auto Generated # +################## +.if-trigger-job: &if-trigger-job + if: '$BOT_DYNAMIC_TEST_JOBS && $BOT_DYNAMIC_TEST_JOBS =~ $CI_JOB_NAME' + +.if-label-build: &if-label-build + if: '$BOT_LABEL_BUILD || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*build(?:,[^,\n\r]+)*$/i' + +.if-label-target_test: &if-label-target_test + if: '$BOT_LABEL_TARGET_TEST || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*target_test(?:,[^,\n\r]+)*$/i' + +.if-label-pre_check: &if-label-pre_check + if: '$BOT_LABEL_PRE_CHECK || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*pre_check(?:,[^,\n\r]+)*$/i' + +.if_label-deploy: &if-label-deploy + if: '$BOT_LABEL_DEPLOY || $CI_MERGE_REQUEST_LABELS =~ /^(?:[^,\n\r]+,)*deploy(?:,[^,\n\r]+)*$/i' + +# rules for readme +.rules:pre_check:readme: + rules: + - <<: *if-protected + - <<: *if-label-pre_check + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-docs_md + - <<: *if-dev-push + changes: *patterns-build_system + +# rules for test_apps +.rules:build:test_apps_lcd_3wire_spi_rgb: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_lcd_3wire_spi_rgb + +.rules:build:test_apps_lcd_qspi: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_lcd_qspi + +.rules:build:test_apps_lcd_rgb: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_lcd_rgb + +.rules:build:test_apps_lcd_spi: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_lcd_spi + +.rules:build:test_apps_lvgl_port: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_lvgl_port + +.rules:build:test_apps_panel: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_panel + +.rules:build:test_apps_touch_i2c: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_touch_i2c + +.rules:build:test_apps_touch_spi: + rules: + - <<: *if-protected + - <<: *if-label-build + - <<: *if-label-target_test + - <<: *if-trigger-job + - <<: *if-dev-push + changes: *patterns-build_system + - <<: *if-dev-push + changes: *patterns-component + - <<: *if-dev-push + changes: *patterns-test_apps_touch_spi + +# rules for examples +# .rules:build:example_esp_brookesia_phone_m5stace_core_s3: +# rules: +# - <<: *if-protected +# - <<: *if-label-build +# - <<: *if-label-target_test +# - <<: *if-trigger-job +# - <<: *if-dev-push +# changes: *patterns-component +# - <<: *if-dev-push +# changes: *patterns-example_esp_brookesia_phone_m5stace_core_s3 +# - <<: *if-dev-push +# changes: *patterns-build_system diff --git a/.gitlab/tools/build_apps.py b/.gitlab/tools/build_apps.py new file mode 100644 index 00000000..e6265711 --- /dev/null +++ b/.gitlab/tools/build_apps.py @@ -0,0 +1,170 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +""" +This file is used in CI generate binary files for different kinds of apps +""" + +import argparse +import sys +import os +import re +import logging +from pathlib import Path +from typing import List + +from idf_build_apps import App, build_apps, find_apps, setup_logging + +logger = logging.getLogger('idf_build_apps') + +PROJECT_ROOT = Path(__file__).parent.parent.parent.absolute() +APPS_BUILD_PER_JOB = 30 +IGNORE_WARNINGS = [ +] + + +def _get_idf_version(): + if os.environ.get('IDF_VERSION'): + return os.environ.get('IDF_VERSION') + version_path = os.path.join(os.environ['IDF_PATH'], 'tools/cmake/version.cmake') + regex = re.compile(r'^\s*set\s*\(\s*IDF_VERSION_([A-Z]{5})\s+(\d+)') + ver = {} + with open(version_path) as f: + for line in f: + m = regex.match(line) + if m: + ver[m.group(1)] = m.group(2) + return '{}.{}'.format(int(ver['MAJOR']), int(ver['MINOR'])) + + +def get_cmake_apps( + paths, + target, + config_rules_str, + default_build_targets, +): # type: (List[str], str, List[str]) -> List[App] + idf_ver = _get_idf_version() + apps = find_apps( + paths, + recursive=True, + target=target, + build_dir=f'{idf_ver}/build_@t_@w', + config_rules_str=config_rules_str, + build_log_filename='build_log.txt', + size_json_filename='size.json', + check_warnings=True, + default_build_targets=default_build_targets, + manifest_files=[ + str(Path(PROJECT_ROOT) / '.build-rules.yml'), + ], + ) + return apps + + +def main(args): # type: (argparse.Namespace) -> None + default_build_targets = args.default_build_targets.split(',') if args.default_build_targets else None + apps = get_cmake_apps(args.paths, args.target, args.config, default_build_targets) + if args.find: + if args.output: + os.makedirs(os.path.dirname(os.path.realpath(args.output)), exist_ok=True) + with open(args.output, 'w') as fw: + for app in apps: + fw.write(app.to_json() + '\n') + else: + for app in apps: + print(app) + + sys.exit(0) + + if args.exclude_apps: + apps_to_build = [app for app in apps if app.name not in args.exclude_apps] + else: + apps_to_build = apps[:] + + logger.info('Found %d apps after filtering', len(apps_to_build)) + logger.info( + 'Suggest setting the parallel count to %d for this build job', + len(apps_to_build) // APPS_BUILD_PER_JOB + 1, + ) + + ret_code = build_apps( + apps_to_build, + parallel_count=args.parallel_count, + parallel_index=args.parallel_index, + dry_run=False, + collect_size_info=args.collect_size_info, + keep_going=True, + ignore_warning_strs=IGNORE_WARNINGS, + copy_sdkconfig=True + ) + + sys.exit(ret_code) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Build all the apps for different test types. Will auto remove those non-test apps binaries', + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + ) + parser.add_argument('paths', nargs='*', help='Paths to the apps to build.') + parser.add_argument( + '-t', '--target', + default='all', + help='Build apps for given target. could pass "all" to get apps for all targets', + ) + parser.add_argument( + '--config', + default=['sdkconfig.ci=default', 'sdkconfig.ci.*=', '=default'], + action='append', + help='Adds configurations (sdkconfig file names) to build. This can either be ' + 'FILENAME[=NAME] or FILEPATTERN. FILENAME is the name of the sdkconfig file, ' + 'relative to the project directory, to be used. Optional NAME can be specified, ' + 'which can be used as a name of this configuration. FILEPATTERN is the name of ' + 'the sdkconfig file, relative to the project directory, with at most one wildcard. ' + 'The part captured by the wildcard is used as the name of the configuration.', + ) + parser.add_argument( + '--parallel-count', default=1, type=int, help='Number of parallel build jobs.' + ) + parser.add_argument( + '--parallel-index', + default=1, + type=int, + help='Index (1-based) of the job, out of the number specified by --parallel-count.', + ) + parser.add_argument( + '--collect-size-info', + type=argparse.FileType('w'), + help='If specified, the test case name and size info json will be written to this file', + ) + parser.add_argument( + '--exclude-apps', + nargs='*', + help='Exclude build apps', + ) + parser.add_argument( + '--default-build-targets', + default=None, + help='default build targets used in manifest files', + ) + parser.add_argument( + '-v', '--verbose', + action='count', default=0, + help='Show verbose log message', + ) + parser.add_argument( + '--find', + action='store_true', + help='Find the buildable applications. If enable this option, build options will be ignored.', + ) + parser.add_argument( + '-o', '--output', + help='Print the found apps to the specified file instead of stdout' + ) + + arguments = parser.parse_args() + if not arguments.paths: + arguments.paths = [PROJECT_ROOT] + setup_logging(verbose=arguments.verbose) # Info + main(arguments) diff --git a/.gitlab/tools/check_executables.py b/.gitlab/tools/check_executables.py new file mode 100755 index 00000000..64260f86 --- /dev/null +++ b/.gitlab/tools/check_executables.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python +# +# SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +import argparse +import os +import sys +from typing import Iterable, List + +try: + from idf_ci_utils import is_executable +except ImportError: + sys.path.append(os.path.join(os.path.dirname(__file__))) + + from idf_ci_utils import is_executable + + +def _strip_each_item(iterable: Iterable) -> List: + res = [] + for item in iterable: + if item: + res.append(item.strip()) + return res + + +COMPONENT_PATH = os.getenv('COMPONENT_PATH', os.getcwd()) +EXECUTABLE_LIST_FN = os.path.join(COMPONENT_PATH, '.gitlab/tools/executable-list.txt') +known_executables = _strip_each_item(open(EXECUTABLE_LIST_FN).readlines()) + + +def check_executable_list() -> int: + ret = 0 + for index, fn in enumerate(known_executables): + if not os.path.exists(os.path.join(COMPONENT_PATH, fn)): + print('{}:{} {} not exists. Please remove it manually'.format(EXECUTABLE_LIST_FN, index + 1, fn)) + ret = 1 + return ret + + +def check_executables(files: List) -> int: + ret = 0 + for fn in files: + fn_executable = is_executable(fn) + fn_in_list = fn in known_executables + if fn_executable and not fn_in_list: + print('"{}" is not in {}'.format(fn, EXECUTABLE_LIST_FN)) + ret = 1 + if not fn_executable and fn_in_list: + print('"{}" is not executable but is in {}'.format(fn, EXECUTABLE_LIST_FN)) + ret = 1 + return ret + + +def check() -> int: + parser = argparse.ArgumentParser() + parser.add_argument('--action', choices=['executables', 'list'], required=True, + help='if "executables", pass all your executables to see if it\'s in the list.' + 'if "list", check if all items on your list exist') + parser.add_argument('filenames', nargs='*', help='Filenames to check.') + args = parser.parse_args() + + if args.action == 'executables': + ret = check_executables(args.filenames) + elif args.action == 'list': + ret = check_executable_list() + else: + raise ValueError + + return ret + + +if __name__ == '__main__': + sys.exit(check()) diff --git a/.gitlab/tools/check_readme_links.py b/.gitlab/tools/check_readme_links.py new file mode 100755 index 00000000..4850433c --- /dev/null +++ b/.gitlab/tools/check_readme_links.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python +# +# Checks that all links in the readme markdown files are valid +# +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +# + +import argparse +import concurrent.futures +import os +import os.path +import re +import sys +import urllib.error +import urllib.request +from collections import defaultdict, namedtuple +from pathlib import Path +from typing import List + +EXCLUDE_DOCS_LIST = [] + +# The apple apps links are not accessible from the company network for some reason +EXCLUDE_URL_LIST = [ + 'https://squareline.io/', + 'https://www.espressif.com/en/products/devkits', + 'https://m5stack.com/', + 'https://docs.m5stack.com/en/core/CoreS3', + 'https://docs.m5stack.com/en/core/core2', + 'https://docs.m5stack.com/en/core/M5Dial', + 'https://img.shields.io/github/v/release/esp-arduino-libs/ESP32_Display_Panel', + 'https://www.displaysmodule.com/sale-41828962-experience-the-power-of-the-esp32-display-module-sku-esp32-4848s040c-i-y-3.html', + 'https://www.displaysmodule.com/', + 'https://www.waveshare.com/', + 'https://www.waveshare.com/esp32-s3-touch-lcd-4.3.htm', + 'https://www.waveshare.com/esp32-s3-touch-lcd-2.1.htm', + 'https://www.waveshare.com/esp32-s3-touch-lcd-1.85.htm', +] + +Link = namedtuple('Link', ['file', 'url']) + + +class ReadmeLinkError(Exception): + def __init__(self, file: str, url: str) -> None: + self.file = file + self.url = url + + +class RelativeLinkError(ReadmeLinkError): + def __str__(self) -> str: + return 'Relative link error, file - {} not found, linked from {}'.format(self.url, self.file) + + +class UrlLinkError(ReadmeLinkError): + def __init__(self, file: str, url: str, error_code: str): + self.error_code = error_code + super().__init__(file, url) + + def __str__(self) -> str: + files = [str(f) for f in self.file] + return 'URL error, url - {} in files - {} is not accessible, request returned {}'.format(self.url, ', '.join(files), self.error_code) + + +# we do not want a failed test just due to bad network conditions, for non 404 errors we simply print a warning +def check_url(url: str, files: str, timeout: float) -> None: + try: + with urllib.request.urlopen(url, timeout=timeout): + return + except urllib.error.HTTPError as e: + if e.code == 404: + raise UrlLinkError(files, url, str(e)) + else: + print('Unable to access {}, err = {}'.format(url, str(e))) + except Exception as e: + print('Unable to access {}, err = {}'.format(url, str(e))) + + +def check_web_links(web_links: defaultdict) -> List: + + with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor: + errors = [] + future_to_url = {executor.submit(check_url, url, files, timeout=30): (url, files) for url, files in web_links.items()} + for future in concurrent.futures.as_completed(future_to_url): + try: + future.result() + except UrlLinkError as e: + errors.append(e) + + return errors + + +def check_file_links(file_links: List) -> List: + errors = [] + + for link in file_links: + link_path = link.file.parent / link.url + + if not Path.exists(link_path): + errors.append(RelativeLinkError(link.file, link.url)) + + print('Found {} errors with relative links'.format(len(errors))) + return errors + + +def get_md_links(folder: str) -> List: + MD_LINK_RE = r'\[.+?\]\((.+?)(#.+)?\)' + + idf_path_str = os.getenv('COMPONENT_PATH') + if idf_path_str is None: + raise RuntimeError("Environment variable 'COMPONENT_PATH' wasn't set.") + idf_path = Path(idf_path_str) + links = [] + + for path in (idf_path / folder).rglob('*.md'): + with path.open(encoding='utf8') as f: + content = f.read() + + for url in re.findall(MD_LINK_RE, content): + link = Link(path, url[0].lstrip()) + # Ignore "local" links + if not link.url.startswith('#'): + links.append(link) + + return links + + +def check_readme_links(args: argparse.Namespace) -> int: + + links = get_md_links('') + print('Found {} links'.format(len(links))) + + errors = [] + + web_links = defaultdict(list) + file_links = [] + + # Sort links into file and web links + for link in links: + if link.url.startswith('http'): + web_links[link.url].append(link.file) + else: + file_links.append(link) + + for url in EXCLUDE_URL_LIST: + if url in web_links: + del web_links[url] + + errors.extend(check_file_links(file_links)) + + if not args.skip_weburl: + errors.extend(check_web_links(web_links)) + + print('Found {} errors:'.format(len(errors))) + for e in errors: + print(e) + + return 1 if len(errors) > 0 else 0 + + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='check_readme_links.py: Checks for dead links in example READMEs', prog='check_readme_links.py') + parser.add_argument('--skip-weburl', '-w', action='store_true', help='Skip checking of web URLs, only check links to local files') + args = parser.parse_args() + + sys.exit(check_readme_links(args)) diff --git a/.gitlab/tools/executable-list.txt b/.gitlab/tools/executable-list.txt new file mode 100644 index 00000000..22439eee --- /dev/null +++ b/.gitlab/tools/executable-list.txt @@ -0,0 +1,4 @@ +.github/scripts/check_lib_versions.sh +.gitlab/tools/check_executables.py +.gitlab/tools/check_readme_links.py +.gitlab/tools/push_to_github.sh diff --git a/.gitlab/tools/idf_ci_utils.py b/.gitlab/tools/idf_ci_utils.py new file mode 100644 index 00000000..3e541fa5 --- /dev/null +++ b/.gitlab/tools/idf_ci_utils.py @@ -0,0 +1,112 @@ +# SPDX-FileCopyrightText: 2020-2024 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 + +# internal use only for CI +# some CI related util functions + +import logging +import os +import subprocess +import sys +from typing import Any, List + +IDF_PATH = os.path.abspath(os.getenv('IDF_PATH', os.path.join(os.path.dirname(__file__), '..', '..'))) + + +def get_submodule_dirs(full_path: bool = False) -> List[str]: + """ + To avoid issue could be introduced by multi-os or additional dependency, + we use python and git to get this output + :return: List of submodule dirs + """ + dirs = [] + try: + lines = ( + subprocess.check_output( + [ + 'git', + 'config', + '--file', + os.path.realpath(os.path.join(IDF_PATH, '.gitmodules')), + '--get-regexp', + 'path', + ] + ) + .decode('utf8') + .strip() + .split('\n') + ) + for line in lines: + _, path = line.split(' ') + if full_path: + dirs.append(os.path.join(IDF_PATH, path)) + else: + dirs.append(path) + except Exception as e: # pylint: disable=W0703 + logging.warning(str(e)) + + return dirs + + +def _check_git_filemode(full_path: str) -> bool: + try: + stdout = subprocess.check_output(['git', 'ls-files', '--stage', full_path]).strip().decode('utf-8') + except subprocess.CalledProcessError: + return True + + mode = stdout.split(' ', 1)[0] # e.g. 100644 for a rw-r--r-- + if any([int(i, 8) & 1 for i in mode[-3:]]): + return True + return False + + +def is_executable(full_path: str) -> bool: + """ + os.X_OK will always return true on windows. Use git to check file mode. + :param full_path: file full path + :return: True is it's an executable file + """ + if sys.platform == 'win32': + return _check_git_filemode(full_path) + return os.access(full_path, os.X_OK) + + +def get_git_files(path: str = IDF_PATH, full_path: bool = False) -> List[str]: + """ + Get the result of git ls-files + :param path: path to run git ls-files + :param full_path: return full path if set to True + :return: list of file paths + """ + try: + # this is a workaround when using under worktree + # if you're using worktree, when running git commit a new environment variable GIT_DIR would be declared, + # the value should be /.git/worktrees/ + # This would affect the return value of `git ls-files`, unset this would use the `cwd`value or its parent + # folder if no `.git` folder found in `cwd`. + workaround_env = os.environ.copy() + workaround_env.pop('GIT_DIR', None) + files = ( + subprocess.check_output(['git', 'ls-files'], cwd=path, env=workaround_env) + .decode('utf8') + .strip() + .split('\n') + ) + except Exception as e: # pylint: disable=W0703 + logging.warning(str(e)) + files = [] + return [os.path.join(path, f) for f in files] if full_path else files + + +def is_in_directory(file_path: str, folder: str) -> bool: + return os.path.realpath(file_path).startswith(os.path.realpath(folder) + os.sep) + + +def to_list(s: Any) -> List[Any]: + if isinstance(s, (set, tuple)): + return list(s) + + if isinstance(s, list): + return s + + return [s] diff --git a/.gitlab/tools/push_to_github.sh b/.gitlab/tools/push_to_github.sh new file mode 100755 index 00000000..f161f5c6 --- /dev/null +++ b/.gitlab/tools/push_to_github.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# gitlab-ci script to push current tested revision (tag or branch) to github + +set -ex + +if [ -n "${CI_COMMIT_TAG}" ]; then + # for tags + git push github "${CI_COMMIT_TAG}" +else + # for branches + git push github "${CI_COMMIT_SHA}:refs/heads/${CI_COMMIT_REF_NAME}" +fi diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ab8eed5..3b3129b7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -23,17 +23,70 @@ repos: args: ['--config=.flake8', '--tee', '--benchmark'] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - types_or: [c, c++] + # note: whitespace exclusions use multiline regex, see https://pre-commit.com/#regular-expressions + # items are: + # 1 - some file extensions + # 2 - any file matching *test*/*expected* (for host tests, if possible use this naming pattern always) + # 3 - any file with known-warnings in the name + # 4 - any directory named 'testdata' + # 5 - protobuf auto-generated files + exclude: &whitespace_excludes | + (?x)^( + .+\.(md|rst|map|bin)| + .+test.*\/.*expected.*| + .+known-warnings.*| + .+\/testdata\/.+| + .*_pb2.py| + .*.pb-c.h| + .*.pb-c.c| + .*.yuv + )$ - id: end-of-file-fixer - types_or: [c, c++] - - id: check-merge-conflict + exclude: *whitespace_excludes + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable - id: mixed-line-ending - types_or: [c, c++, text] - args: ['--fix=lf'] - description: Forces to replace line ending by the UNIX 'lf' character + args: ['-f=lf'] + - id: double-quote-string-fixer + - id: no-commit-to-branch + name: Do not use more than one slash in the branch name + args: ['--pattern', '^[^/]*/[^/]*/'] + - id: no-commit-to-branch + name: Do not use uppercase letters in the branch name + args: ['--pattern', '^[^A-Z]*[A-Z]'] + + - repo: https://github.com/espressif/conventional-precommit-linter + rev: v1.8.0 + hooks: + - id: conventional-precommit-linter + stages: [commit-msg] + args: + - --subject-min-length=15 + - --body-max-line-length=200 + + - repo: https://github.com/codespell-project/codespell + rev: v2.3.0 + hooks: + - id: codespell + args: ['-w' , '--config', '.codespellrc'] + + - repo: local + hooks: + - id: check-executables + name: Check File Permissions + entry: .gitlab/tools/check_executables.py --action executables + language: python + types: [executable] + exclude: '\.pre-commit/.+' + - id: check-executable-list + name: Validate executable-list.txt + entry: .gitlab/tools/check_executables.py --action list + language: python + pass_filenames: false + always_run: true - repo: local hooks: @@ -50,3 +103,11 @@ repos: entry: python3 tools/check_file_version.py ./ language: system files: '(.*ESP_Panel_(Board_Custom|Board_Supported|Conf)\.h|library.properties|.*ESP_PanelVersions.h)' + + - repo: local + hooks: + - id: check-library-versions + name: Check library versions + entry: ./.github/scripts/check_lib_versions.sh + language: system + files: '(idf_component.yml|library.properties)' diff --git a/CHANGELOG.md b/CHANGELOG.md index 383ddd80..8f93459f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ * fix(examples): fix WiFiClock wrong name `ScreenPassord` by @lboue (#82) * fix(examples): fix LCD using `configVendorCommands()` before `init()` * fix(examples): fix `LV_USE_DEMO_WIDGETS` typo by @lboue (#98) -* fix(examples): fix `Tearing fucntion` typo by @lboue (#96) +* fix(examples): fix `Tearing function` typo by @lboue (#96) * fix(examples): fix WiFiClock log HTTP error code to serial console by @lboue (#97) * fix(examples): fix WiFiClock description * fix(gt911): allow to set the GT911 touch device address by @lboue (#86) diff --git a/CMakeLists.txt b/CMakeLists.txt index 13cc6736..4061d68a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,14 +5,11 @@ file(GLOB_RECURSE C_SRCS "${SRCS_DIR}/*.c") idf_component_register( SRCS - ${C_SRCS} - ${CPP_SRCS} + ${C_SRCS} ${CPP_SRCS} INCLUDE_DIRS ${SRCS_DIR} REQUIRES - driver - esp_lcd - esp-io-expander + driver esp_lcd ) target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers -Wno-narrowing) diff --git a/ESP_Panel_Board_Custom.h b/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/ESP_Panel_Board_Custom.h +++ b/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/ESP_Panel_Board_Supported.h b/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/ESP_Panel_Board_Supported.h +++ b/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/Kconfig b/Kconfig new file mode 100644 index 00000000..ea268d4e --- /dev/null +++ b/Kconfig @@ -0,0 +1,26 @@ +menu "ESP Display Panel Configurations" + config ESP_PANEL_CONF_FILE_SKIP + bool "Unckeck this to ignore `ESP_Panel_Conf.h`" + default y + + config ESP_PANEL_BOARD_FILE_SKIP + bool "Unckeck this to ignore `ESP_Panel_Board_*.h`" + default y + + config ESP_PANEL_CHECK_RESULT_ASSERT + bool "Assert on error" + default n + help + If enabled, the driver will assert on error. Otherwise, the driver will return error code on error. + + config ESP_PANEL_ENABLE_LOG + bool "Enable output debug log" + default n + help + If enabled, the driver will output log for debugging. + + orsource "./src/touch/Kconfig.touch" + + orsource "./src/board/Kconfig.board" + +endmenu diff --git a/README.md b/README.md index 68585eab..6cce25a8 100644 --- a/README.md +++ b/README.md @@ -1,81 +1,55 @@ -[![GitHub Release](https://img.shields.io/github/v/release/esp-arduino-libs/ESP32_Display_Panel)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/releases) [![Arduino Lint](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml) +[![Arduino Lint](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml) [![Version Consistency](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/check_lib_versions.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/check_lib_versions.yml) + +**Latest Arduino Library Version**: [![GitHub Release](https://img.shields.io/github/v/release/esp-arduino-libs/ESP32_Display_Panel)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/releases) + +**Latest Espressif Component Version**: [![Espressif Release](https://components.espressif.com/components/espressif/esp32_display_panel/badge.svg)](https://components.espressif.com/components/espressif/esp32_display_panel) # ESP Display Panel * [中文版本](./README_CN.md) -ESP32_Display_Panel is an Arduino library designed for ESP SoCs to drive display panels and facilitate rapid GUI development. Users can develop directly for a variety of [supported development boards](docs/Board_Instructions.md) or create custom ones through simple adaptation. Additionally, ESP32_Display_Panel is compatible with various LCD and touch drivers, allowing users to develop using standalone drivers as needed. +ESP32_Display_Panel is a library designed specifically for ESP SoCs to drive display screens and enable rapid GUI development. Users can develop on multiple [internally supported development boards](#Development-Boards) directly or use simple adaptations for custom boards. Additionally, ESP32_Display_Panel supports various LCD and touch drivers, allowing users to develop with standalone drivers as needed. -ESP32_Display_Panel encapsulates various components from the [Espressif Components Registry](https://components.espressif.com/), requiring development based on [arduino-esp32](https://github.com/espressif/arduino-esp32), and can be directly downloaded from the Arduino IDE. +ESP32_Display_Panel integrates multiple display-related driver components from the [ESP Component Registry](https://components.espressif.com/). It can be obtained directly from the Espressif's server or downloaded from the Arduino IDE, enabling development with either the [Arduino](https://github.com/espressif/arduino-esp32) IDE or the [ESP-IDF](https://github.com/espressif/esp-idf) framework. -## Table of Contents +## Overview -- [ESP Display Panel](#esp-display-panel) - - [Table of Contents](#table-of-contents) - - [Overview](#overview) - - [Supported Development Boards and Drivers](#supported-development-boards-and-drivers) - - [Development Boards](#development-boards) - - [LCD Controllers](#lcd-controllers) - - [Touch Controllers](#touch-controllers) - - [Dependencies and Versions](#dependencies-and-versions) - - [How to Use](#how-to-use) - - [Configuration Instructions](#configuration-instructions) - - [Configuring Drivers](#configuring-drivers) - - [Using Supported Development Boards](#using-supported-development-boards) - - [Using Custom Development Boards](#using-custom-development-boards) - - [Usage Examples](#usage-examples) - - [Arduino IDE](#arduino-ide) - - [LCD](#lcd) - - [Touch](#touch) - - [Panel](#panel) - - [LVGL v8](#lvgl-v8) - - [SquareLine](#squareline) - - [PlatformIO](#platformio) - - [Other Relevant Instructions](#other-relevant-instructions) - - [Configuring Supported Development Boards](#configuring-supported-development-boards) - - [Configuring LVGL](#configuring-lvgl) - - [Porting SquareLine Project](#porting-squareline-project) - - [FAQ](#faq) - - [Where is the directory for Arduino libraries?](#where-is-the-directory-for-arduino-libraries) - - [How to Install ESP32\_Display\_Panel in Arduino IDE?](#how-to-install-esp32_display_panel-in-arduino-ide) - - [Where are the installation directory for arduino-esp32 and the SDK located?](#where-are-the-installation-directory-for-arduino-esp32-and-the-sdk-located) - - [How to fix screen drift issue when driving RGB LCD with ESP32-S3?](#how-to-fix-screen-drift-issue-when-driving-rgb-lcd-with-esp32-s3) - - [How to Use ESP32\_Display\_Panel on PlatformIO?](#how-to-use-esp32_display_panel-on-platformio) +The functional block diagram of ESP32_Display_Panel is shown below and includes the following features: -## Overview +- Supports a variety of **Espressif** official and third-party development boards, including **M5Stack**, **Elecrow**, **Waveshare**, and others. +- Supports **custom development board** adaptation. +- Supports a variety of device drivers, including interface **Bus**, **LCD**, **Touch**, **Backlight** and **IO Expander**. +- Supports dynamic driver configuration, such as enabling debug logs. +- Compatible with the **Arduino** IDE and **ESP-IDF** framework for compilation. -The functional block diagram of ESP32_Display_Panel is as follows, mainly comprising the following features: +
Block Diagram
-- Supports a variety of official Espressif development boards and third-party boards. -- Supports adaptation for custom development boards. -- Supports the use of standalone device drivers. -- Supports various types of device drivers, including interface buses, LCDs, touch screens, and backlight control. -- Supports dynamic configuration of drivers, such as enabling debug logs. +## How to Use -
Block Diagram
+Please refer to the documentation - [How to Use](./docs/How_To_Use.md). ## Supported Development Boards and Drivers ### Development Boards -Below is a list of [supported development boards](docs/Board_Instructions.md): +Below is the list of [supported development boards](docs/Board_Instructions.md): | **Manufacturer** | **Board Model** | -| --------------- | --------------- | +| ---------------- | --------------- | | [Espressif](docs/Board_Instructions.md#espressif) | ESP32-C3-LCDkit, ESP32-S3-BOX, ESP32-S3-BOX-3, ESP32-S3-BOX-3B, ESP32-S3-BOX-3(beta), ESP32-S3-BOX-Lite, ESP32-S3-EYE, ESP32-S3-Korvo-2, ESP32-S3-LCD-EV-Board, ESP32-S3-LCD-EV-Board-2, ESP32-S3-USB-OTG | | [Elecrow](docs/Board_Instructions.md#elecrow) | CrowPanel 7.0" | | [M5Stack](docs/Board_Instructions.md#m5stack) | M5STACK-M5CORE2, M5STACK-M5DIAL, M5STACK-M5CORES3 | | [Jingcai](docs/Board_Instructions.md#shenzhen-jingcai-intelligent) | ESP32-4848S040C_I_Y_3 | | [Waveshare](docs/Board_Instructions.md#waveshare) | ESP32-S3-Touch-LCD-4.3, ESP32-S3-Touch-LCD-1.85, ESP32-S3-Touch-LCD-2.1 | -Developers and manufacturers are welcomed to contribute PRs to add more development boards. For detailed instructions, please refer to the [`Board Development Guide`](./docs/Board_Contribution_Guide.md). +Developers and manufacturers are welcome to contribute PRs to add more boards. For details, please refer to the [Board Contribution Guide](./docs/Board_Contribution_Guide.md). ### LCD Controllers -Below is a list of [supported LCD controllers](docs/LCD_Controllers.md): +Below is the list of [supported LCD controllers](docs/LCD_Controllers.md): | **Manufacturer** | **Model** | -| --------------- | --------- | +| ---------------- | --------- | | Fitipower | EK9716B | | GalaxyCore | GC9A01, GC9B71, GC9503 | | Ilitek | ILI9341 | @@ -84,10 +58,10 @@ Below is a list of [supported LCD controllers](docs/LCD_Controllers.md): ### Touch Controllers -Below is a list of [supported touch controllers](docs/Touch_Controllers.md): +Below is the list of [supported touch controllers](docs/Touch_Controllers.md): | **Manufacturer** | **Model** | -| --------------- | --------- | +| ---------------- | --------- | | Hynitron | CST816S | | FocalTech | FT5x06 | | GOODiX | GT911, GT1151 | @@ -95,377 +69,6 @@ Below is a list of [supported touch controllers](docs/Touch_Controllers.md): | Parade | TT21100 | | Xptek | XPT2046 | -## Dependencies and Versions - -| **Dependency** | **Version** | -| -------------- | ----------- | -| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v3.0.0-alpha3 | -| [ESP32_IO_Expander](https://github.com/esp-arduino-libs/ESP32_IO_Expander) | >= 0.0.1 && < 0.1.0 | - -## How to Use - -For installation of the ESP32_Display_Panel library, refer to [How to Install ESP32_Display_Panel in Arduino IDE](#how-to-install-esp32_display_panel-in-arduino-ide). - -### Configuration Instructions - -Below are detailed instructions on how to configure ESP32_Display_Panel, mainly including [Configuring Drivers](#configuring-drivers), [Using Supported Development Boards](#using-supported-development-boards), and [Using Custom Development Boards](#using-custom-development-boards). These are all optional operations and are configured through specified header files. Users can choose to use them according to their needs, with the following characteristics: - -1. The path sequence for ESP32_Display_Panel to search for configuration files is: `Current Project Directory` > `Arduino Library Directory` > `ESP32_Display_Panel Directory`. -2. All examples in ESP32_Display_Panel include their required configuration files by default, which users can directly modify macro definitions. -3. For projects without configuration files, users can copy them from the root directory or examples of ESP32_Display_Panel to their own projects. -4. If multiple projects need to use the same configuration, users can place the configuration files in the [Arduino Library Directory](#where-is-the-directory-for-arduino-libraries), so that all projects can share the same configuration. - -> [!WARNING] -> * The same directory can simultaneously contain both `ESP_Panel_Board_Supported.h` and `ESP_Panel_Board_Custom.h` configuration files, but they cannot be enabled at the same time, meaning `ESP_PANEL_USE_SUPPORTED_BOARD` and `ESP_PANEL_USE_CUSTOM_BOARD` can only have one set to `1`. -> * If neither of the above two configuration files is enabled, users cannot use the `ESP_Panel` driver and can only use other standalone device drivers, such as `ESP_PanelBus`, `ESP_PanelLcd`, etc. -> * Since the configurations within these files might change, such as adding, deleting, or renaming, to ensure the compatibility of the program, the library manages the versions of these files independently and checks whether the configuration files currently used by the user are compatible with the library during compilation. Detailed version information and checking rules can be found at the end of the file. - -#### Configuring Drivers - -ESP32_Display_Panel configures driver functionality and parameters based on the [ESP_Panel_Conf.h](./ESP_Panel_Conf.h) file. Users can update the behavior or default parameters of the driver by modifying macro definitions in this file. For example, to enable debug log output, here is a snippet of the modified `ESP_Panel_Conf.h` file: - -```c -... -/* Set to 1 if print log message for debug */ -#define ESP_PANEL_ENABLE_LOG (1) // 0/1 -... -``` - -#### Using Supported Development Boards - -ESP32_Display_Panel configures `ESP_Panel` as the driver for the target development board based on the [ESP_Panel_Board_Supported.h](./ESP_Panel_Board_Supported.h) file. Users can select supported development boards by modifying macro definitions in this file. For example, to use the *ESP32-S3-BOX-3* development board, follow these steps: - -1. Set the `ESP_PANEL_USE_SUPPORTED_BOARD` macro definition in the `ESP_Panel_Board_Supported.h` file to `1`. -2. Uncomment the corresponding macro definition for the target development board model. - -Here is a snippet of the modified `ESP_Panel_Board_Supported.h` file: - -```c -... -/* Set to 1 if using a supported board */ -#define ESP_PANEL_USE_SUPPORTED_BOARD (1) // 0/1 - -#if ESP_PANEL_USE_SUPPORTED_BOARD -... -// #define BOARD_ESP32_C3_LCDKIT -// #define BOARD_ESP32_S3_BOX -#define BOARD_ESP32_S3_BOX_3 -// #define BOARD_ESP32_S3_BOX_3_BETA -... -#endif /* ESP_PANEL_USE_SUPPORTED_BOARD */ -``` - -#### Using Custom Development Boards - -ESP32_Display_Panel configures `ESP_Panel` as the driver for custom development boards based on the [ESP_Panel_Board_Custom.h](./ESP_Panel_Board_Custom.h) file. Users need to modify this file according to the actual parameters of the custom development board. For example, to use a custom development board with a *480x480 RGB ST7701 LCD + I2C GT911 Touch*, follow these steps: - -1. Set the `ESP_PANEL_USE_CUSTOM_BOARD` macro definition in the `ESP_Panel_Board_Custom.h` file to `1`. -2. Set the LCD-related macro definitions: - a. Set `ESP_PANEL_USE_LCD` to `1`. - b. Set `ESP_PANEL_LCD_WIDTH` and `ESP_PANEL_LCD_HEIGHT` to `480`. - c. Set `ESP_PANEL_LCD_BUS_TYPE` to `ESP_PANEL_BUS_TYPE_RGB`. - d. Set LCD signal pins and other parameters below `ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB`. - e. Uncomment and modify the `ESP_PANEL_LCD_VENDOR_INIT_CMD` macro definition according to the initialization command parameters provided by the screen vendor. - f. Modify other LCD configurations as needed. -3. Set the Touch-related macro definitions: - a. Set `ESP_PANEL_USE_TOUCH` to `1`. - b. Set Touch signal pins and other parameters below `ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C`. - c. Modify other Touch configurations as needed. -4. Enable other driver macro definitions as needed, such as `ESP_PANEL_USE_BACKLIGHT`, `ESP_PANEL_USE_EXPANDER`, etc. - -Here is a snippet of the modified `ESP_Panel_Board_Custom.h` file: - -```c -... -/* Set to 1 if using a custom board */ -#define ESP_PANEL_USE_CUSTOM_BOARD (1) // 0/1 - -/* Set to 1 when using an LCD panel */ -#define ESP_PANEL_USE_LCD (1) // 0/1 - -#if ESP_PANEL_USE_LCD -/** - * LCD Controller Name - */ -#define ESP_PANEL_LCD_NAME ST7701 - -/* LCD resolution in pixels */ -#define ESP_PANEL_LCD_WIDTH (480) -#define ESP_PANEL_LCD_HEIGHT (480) -... -/** - * LCD Bus Type. - */ -#define ESP_PANEL_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_RGB) -/** - * LCD Bus Parameters. - * - * Please refer to https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd.html and - * https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html for more details. - * - */ -#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB -... -#endif /* ESP_PANEL_LCD_BUS_TYPE */ -... -/** - * LCD Vendor Initialization Commands. - * - * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for - * initialization sequence code. Please uncomment and change the following macro definitions. Otherwise, the LCD driver - * will use the default initialization sequence code. - * - * There are two formats for the sequence code: - * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and - * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) - */ -#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ - { \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x31, 0x05}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xCD, {0x00}), \ - ... - ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x11), \ - } -... -#endif /* ESP_PANEL_USE_LCD */ - -/* Set to 1 when using a touch panel */ -#define ESP_PANEL_USE_TOUCH (1) // 0/1 -#if ESP_PANEL_USE_TOUCH -/** - * Touch controller name - */ -#define ESP_PANEL_TOUCH_NAME GT911 -... -/** - * Touch panel bus type - */ -#define ESP_PANEL_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C) -/* Touch panel bus parameters */ -#if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C -... -#endif /* ESP_PANEL_TOUCH_BUS_TYPE */ -... -#endif /* ESP_PANEL_USE_TOUCH */ -... -#define ESP_PANEL_USE_BACKLIGHT (1) // 0/1 -#if ESP_PANEL_USE_BACKLIGHT -... -#endif /* ESP_PANEL_USE_BACKLIGHT */ -... -#endif /* ESP_PANEL_USE_CUSTOM_BOARD */ -``` - -### Usage Examples - -The following are some examples of using ESP32_Display_Panel on different development platforms. - -#### Arduino IDE - -You can access them in the Arduino IDE by navigating to `File` > `Examples` > `ESP32_Display_Panel`. If you cannot find the `ESP32_Display_Panel` option, please check if the library has been installed correctly and ensure that an ESP development board is selected. - -##### LCD - -The following examples demonstrate how to develop different interface and model LCDs using standalone drivers and test them by displaying color bars: - -* [SPI](examples/LCD/SPI/) -* [QSPI](examples/LCD/QSPI/) -* [Single RGB](examples/LCD/RGB/) -* [3-wire SPI + RGB](examples/LCD/3wireSPI_RGB/) - -##### Touch - -The following example demonstrates how to develop touch screens of different interfaces and models using standalone drivers and test them by printing touch point coordinates: - -* [I2C](examples/Touch/I2C/) -* [SPI](examples/Touch/SPI/) - -##### Panel - -The following example demonstrates how to develop built-in or custom development boards using the `ESP_Panel` driver: - -* [Panel Test](examples/Panel/PanelTest/): This example tests by displaying color bars and printing touch point coordinates. - -##### LVGL v8 - -For configuring LVGL (v8.3.x), please refer to [here](#configuring-lvgl) for more detailed information. - -* [Porting](examples/LVGL/v8/Porting/): This example demonstrates how to port LVGL (v8.3.x). And for RGB LCD, it can enable the avoid tearing function. -* [Rotation](examples/LVGL/v8/Rotation/): This example demonstrates how to use LVGL to rotate the display. - -> [!WARNING] -> Currently, the anti-tearing feature is only supported for RGB LCD and requires LVGL version >= v8.3.9. If you are using a different type of LCD or an LVGL version that does not meet the requirements, please do not enable this feature. - -##### SquareLine - -To port the SquareLine project (v1.3.x), please refer to [here](#porting-squareline-project) for more detailed information. - -- [Porting](examples/SquareLine/v8/Porting/): This example demonstrates how to port the SquareLine project. -- [WiFiClock](examples/SquareLine/v8/WiFiClock/): This example implements a simple Wi-Fi clock and can display weather information. - -#### PlatformIO - -- [PlatformIO](examples/PlatformIO/): This example demonstrates how to use ESP32_Display_Panel in PlatformIO. By default, it is suitable for the **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** development boards. Users need to modify the [boards/ESP-LCD.json](examples/PlatformIO/boards/ESP-LCD.json) file according to the actual situation. - -## Other Relevant Instructions - -### Configuring Supported Development Boards - -For details on how to configure the supported development boards in the Arduino IDE, see [Board_Instructions - Recommended Configurations in the Arduino IDE](./docs/Board_Instructions.md#recommended-configurations-in-the-arduino-ide). - -### Configuring LVGL - -The functionality and parameters of LVGL can be configured by editing the `lv_conf.h` file, where users can modify macro definitions to update the behavior or default parameters of the driver. Here are some features for configuring LVGL: - -1. When using arduino-esp32 v3.x.x version, LVGL will search for the configuration file in the following order: `current project directory` > `Arduino library directory`. If the configuration file is not found, a compilation error indicating the absence of the configuration file will be prompted. Therefore, users need to ensure that at least one directory contains the `lv_conf.h` file. - -2. If multiple projects need to use the same configuration, users can place the configuration file in the [Arduino library directory](#where-is-the-directory-for-arduino-libraries), so that all projects can share the same configuration. - -Below are detailed steps for sharing the same LVGL configuration: - -1. Navigate to the [Arduino library directory](#where-is-the-directory-for-arduino-libraries). - -2. Enter the `lvgl` folder, copy the `lv_conf_template.h` file, and place the copy at the same level as the `lvgl` folder. Then, rename the copied file to `lv_conf.h`. - -3. Finally, the layout of the Arduino library folder should look like this: - - ``` - Arduino - |-libraries - |-lv_conf.h - |-lvgl - |-other_lib_1 - |-other_lib_2 - ``` - -4. Open the `lv_conf.h` file, and change the first `#if 0` to `#if 1` to enable the contents of the file. - -5. Set other configurations according to requirements. Here are some examples of common configuration options for LVGL v8: - - ```c - #define LV_COLOR_DEPTH 16 // Typically use 16-bit color depth (RGB565), - // but can also set it to `32` to support 24-bit color depth (RGB888) - #define LV_COLOR_16_SWAP 0 // If using SPI/QSPI LCD (e.g., ESP32-C3-LCDkit), set this to `1` - #define LV_COLOR_SCREEN_TRANSP 1 - #define LV_MEM_CUSTOM 1 - #define LV_MEMCPY_MEMSET_STD 1 - #define LV_TICK_CUSTOM 1 - #define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR - // Get higher performance but use more SRAM - #define LV_FONT_MONTSERRAT_N 1 // Enable all internal fonts needed (`N` should be replaced with font size) - ``` - -6. For more information, please refer to the [LVGL official documentation](https://docs.lvgl.io/8.3/get-started/platforms/arduino.html). - -### Porting SquareLine Project - -SquareLine Studio (v1.3.x) allows for the rapid design of beautiful UIs through visual editing. If you want to use UI source files exported from SquareLine in the Arduino IDE, you can follow these steps for porting: - -1. First, create a new project in SquareLine Studio. Go to `Create` -> `Arduino`, select `Arduino with TFT-eSPI` as the project template, then configure the LCD properties for the target development board in the `PROJECT SETTINGS` section, such as `Resolution` and `Color depth`. Finally, click the `Create` button to create the project. - -2. For existing projects, you can also click on `File` -> `Project Settings` in the navigation bar to enter the project settings. Then, in the `BOARD PROPERTIES` section, configure `Board Group` as `Arduino` and `Board` as `Arduino with TFT-eSPI`. Additionally, configure the LCD properties for the target development board in the `DISPLAY PROPERTIES` section. Finally, click the `Save` button to save the project settings. - -3. Once the UI design is complete and the export path is configured, click on `Export` -> `Create Template Project` and `Export UI Files` buttons in the menu bar to export the project and UI source files. The layout of the project directory will be as follows: - - ``` - Project - |-libraries - |-lv_conf.h - |-lvgl - |-readme.txt - |-TFT_eSPI - |-ui - |-README.md - |-ui - ``` - -4. Copy the `lv_conf.h`, `lvgl`, and `ui` folders from the `libraries` folder in the project directory to the Arduino library directory. If you need to use a locally installed `lvgl`, skip copying `lvgl` and `lv_conf.h`, then refer to the steps in the [LVGL Configuration](#configuring-lvgl) section to configure LVGL. The layout of the Arduino library folder will be as follows: - - ``` - Arduino - |-libraries - |-ESP32_Display_Panel - |-ESP_Panel_Conf.h (optional) - |-lv_conf.h (optional) - |-lvgl - |-ui - |-other_lib_1 - |-other_lib_2 - ``` - ## FAQ -### Where is the directory for Arduino libraries? - -You can find and modify the directory path for Arduino libraries by selecting `File` > `Preferences` > `Settings` > `Sketchbook location` from the menu bar in the Arduino IDE. - -### How to Install ESP32_Display_Panel in Arduino IDE? - -- If you want to install online, navigate to `Sketch` > `Include Library` > `Manage Libraries...` in the Arduino IDE, then search for `ESP32_Display_Panel` and click the `Install` button to install it. -- If you want to install manually, download the required version of the `.zip` file from [ESP32_Display_Panel](https://github.com/esp-arduino-libs/ESP32_Display_Panel), then navigate to `Sketch` > `Include Library` > `Add .ZIP Library...` in the Arduino IDE, select the downloaded `.zip` file, and click `Open` to install it. -- You can also refer to the guides on library installation in the [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) or [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library) documentation. - -### Where are the installation directory for arduino-esp32 and the SDK located? - -The default installation path for arduino-esp32 depends on your operating system: - -- Windows: `C:\Users\\AppData\Local\Arduino15\packages\esp32` -- Linux: `~/.arduino15/packages/esp32` - -The SDK for arduino-esp32 v3.x.x version is located in the `tools/esp32-arduino-libs/idf-release_x` directory within the default installation path. - -### How to fix screen drift issue when driving RGB LCD with ESP32-S3? - -When encountering screen drift issue when driving RGB LCD with ESP32-S3, you can follow these steps to resolve them: - -1. **Refer to Documentation**: Understand the issue description in detail, you can refer to [this document](https://docs.espressif.com/projects/esp-faq/en/latest/software-framework/peripherals/lcd.html#why-do-i-get-drift-overall-drift-of-the-display-when-esp32-s3-is-driving-an-rgb-lcd-screen). - -2. **Enable `Bounce Buffer + XIP on PSRAM` Feature**: To resolve the issue, it's recommended to enable the `Bounce Buffer + XIP on PSRAM` feature. Follow these steps: - - - **Step 1**: Download the "high_perf" version of the SDK from [arduino-esp32-sdk](https://github.com/esp-arduino-libs/arduino-esp32-sdk) and replace it in the [installation directory of arduino-esp32](#where-are-the-installation-directory-for-arduino-esp32-and-the-sdk-located). - - - **Step 2**: If you are using supported development boards, usually there's no need to modify the code as they set `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` to `(ESP_PANEL_LCD_WIDTH * 10)` by default. If the issue persists, refer to the example code below to increase the size of the `Bounce Buffer`. - - - **Step 3**: If you are using a custom board, confirm in the `ESP_Panel_Board_Custom.h` file whether `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` is set to non-zero. If the issue persists, increase the size of the `Bounce Buffer`. - - - **Step 4**: If you are using an independent driver, refer to the example code below to set the size of the `Bounce Buffer`. - - - **Step 5**: If you are developing an LVGL application, assign the task that initializes the RGB peripheral and the task that runs the LVGL `lv_timer_handler()` on the same core. Please refer to [the code](./examples/LVGL/v8/Porting/lvgl_port_v8.h#L53). - -3. **Example Code**: The following example code demonstrates how to modify the size of the `Bounce Buffer` using `ESP_Panel` driver or independent driver: - - **Example 1**: Modify the `Bounce Buffer` size using the `ESP_Panel` driver: - - ```c - ... - ESP_Panel *panel = new ESP_Panel(); - panel->init(); - // Start - ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); - // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. - rgb_bus->configRgbBounceBufferSize((ESP_PANEL_LCD_WIDTH * 20)); - // End - panel->begin(); - ... - ``` - - **Example 2**: Modify the `Bounce Buffer` size using an independent driver: - - ```c - ... - ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB(...); - // Start - // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. - lcd_bus->configRgbBounceBufferSize(EXAMPLE_LCD_WIDTH * 10); - // End - lcd_bus->begin(); - ... - ``` - -### How to Use ESP32_Display_Panel on PlatformIO? - -You can refer to the example [PlatformIO](examples/PlatformIO/) to use the ESP32_Display_Panel library in PlatformIO. By default, it is suitable for the **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** development boards. You need to modify the [boards/ESP-LCD.json](examples/PlatformIO/boards/ESP-LCD.json) file according to the actual situation. +Please refer to the documentation - [FAQ](./docs/FAQ.md). diff --git a/README_CN.md b/README_CN.md index d88e6f9a..7e2a6e02 100644 --- a/README_CN.md +++ b/README_CN.md @@ -1,59 +1,33 @@ -[![GitHub Release](https://img.shields.io/github/v/release/esp-arduino-libs/ESP32_Display_Panel)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/releases) [![Arduino Lint](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml) +[![Arduino Lint](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/arduino_lint.yml) [![pre-commit](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/pre-commit.yml) [![Version Consistency](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/check_lib_versions.yml/badge.svg)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/actions/workflows/check_lib_versions.yml) -# ESP Display Panel +**最新 Arduino 库版本**: [![GitHub Release](https://img.shields.io/github/v/release/esp-arduino-libs/ESP32_Display_Panel)](https://github.com/esp-arduino-libs/ESP32_Display_Panel/releases) -* [English Version](./README.md) +**最新 Espressif 组件版本**: [![Espressif Release](https://components.espressif.com/components/espressif/esp32_display_panel/badge.svg)](https://components.espressif.com/components/espressif/esp32_display_panel) -ESP32_Display_Panel 是专为 ESP SoCs 设计的 Arduino 库,用于驱动显示屏并实现快速 GUI 开发。用户不仅可以直接开发多款[内部支持的开发板](docs/Board_Instructions.md),还可以通过简单的适配来开发自定义的开发板。此外,ESP32_Display_Panel 还适配了多款 LCD 和触摸的驱动,用户也可以根据需要使用独立的驱动进行开发。 +# ESP Display Panel -ESP32_Display_Panel 封装了多种[乐鑫组件库](https://components.espressif.com/)中相关的组件,需要基于 [arduino-esp32](https://github.com/espressif/arduino-esp32) 进行开发,并且可以直接从 Arduino IDE 中下载获取。 +* [English Version](./README.md) -## 目录 +ESP32_Display_Panel 是专为 ESP SoCs 设计的用于驱动显示屏并实现快速 GUI 开发的库。用户不仅可以直接开发多款[内部支持的开发板](docs/Board_Instructions.md),还可以通过简单的适配来开发自定义的开发板。此外,ESP32_Display_Panel 还适配了多款 LCD 和触摸的驱动,用户也可以根据需要使用独立的驱动进行开发。 -- [ESP Display Panel](#esp-display-panel) - - [目录](#目录) - - [概述](#概述) - - [支持的开发板和驱动](#支持的开发板和驱动) - - [开发板](#开发板) - - [LCD 控制器](#lcd-控制器) - - [触摸控制器](#触摸控制器) - - [依赖项及版本](#依赖项及版本) - - [如何使用](#如何使用) - - [配置说明](#配置说明) - - [配置驱动](#配置驱动) - - [使用支持的开发板](#使用支持的开发板) - - [使用自定义开发板](#使用自定义开发板) - - [示例说明](#示例说明) - - [Arduino IDE](#arduino-ide) - - [LCD](#lcd) - - [Touch](#touch) - - [Panel](#panel) - - [LVGL v8](#lvgl-v8) - - [SquareLine](#squareline) - - [PlatformIO](#platformio) - - [其他相关说明](#其他相关说明) - - [配置支持的开发板](#配置支持的开发板) - - [配置 LVGL](#配置-lvgl) - - [移植 SquareLine 工程](#移植-squareline-工程) - - [常见问题解答](#常见问题解答) - - [Arduino 库的目录在哪儿?](#arduino-库的目录在哪儿) - - [如何在 Arduino IDE 中安装 ESP32\_Display\_Panel?](#如何在-arduino-ide-中安装-esp32_display_panel) - - [arduino-eps32 的安装目录以及 SDK 的目录在哪儿?](#arduino-eps32-的安装目录以及-sdk-的目录在哪儿) - - [使用 ESP32-S3 驱动 RGB LCD 时出现画面漂移问题的解决方案](#使用-esp32-s3-驱动-rgb-lcd-时出现画面漂移问题的解决方案) - - [如何在 PlatformIO 上使用 ESP32\_Display\_Panel?](#如何在-platformio-上使用-esp32_display_panel) +ESP32_Display_Panel 内部集成了多个[乐鑫组件库](https://components.espressif.com/)中显示屏相关的驱动组件,它可以直接从该组件库或从 Arduino IDE 中下载获取,因此用户可以基于 [Arduino](https://github.com/espressif/arduino-esp32) IDE 或 [ESP-IDF](https://github.com/espressif/esp-idf) 框架进行开发。 ## 概述 ESP32_Display_Panel 的功能框图如下所示,主要包含以下特性: -- 支持多款乐鑫官方以及第三方开发板。 -- 支持适配自定义的开发板。 -- 支持使用独立的设备驱动。 -- 支持多种类型的设备驱动,包括接口总线、LCD、触摸、背光。 +- 支持多种 **Espressif** 官方及第三方开发板,包括 **M5Stack**、**Elecrow**、**Waveshare** 等。 +- 支持适配 **自定义的开发板**。 +- 支持多种类型的设备驱动,包括 **接口总线**、**LCD**、**触摸**、**背光** 和 **IO 扩展**。 - 支持动态配置驱动,如开启调试 LOG 等。 +- 支持使用 **Arduino** IDE 或 **ESP-IDF** 框架进行编译。
块图
+## 如何使用 + +请参阅文档 - [如何使用](./docs/How_To_Use_CN.md) 。 + ## 支持的开发板和驱动 ### 开发板 @@ -95,377 +69,6 @@ ESP32_Display_Panel 的功能框图如下所示,主要包含以下特性: | Parade | TT21100 | | Xptek | XPT2046 | -## 依赖项及版本 - -| **依赖项** | **版本** | -| ---------- | -------- | -| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v3.0.0-alpha3 | -| [ESP32_IO_Expander](https://github.com/esp-arduino-libs/ESP32_IO_Expander) | >= 0.0.1 && < 0.1.0 | - -## 如何使用 - -关于 ESP32_Display_Panel 库的安装,请参阅 [如何在 Arduino IDE 中安装 ESP32_Display_Panel](#如何在-arduino-ide-中安装-ESP32_Display_Panel)。 - -### 配置说明 - -下面是关于如何配置 ESP32_Display_Panel 的详细说明,主要包含了 [配置驱动](#配置驱动), [使用支持的开发板](#使用支持的开发板), [使用自定义开发板](#使用自定义开发板) 三个部分,这些均为可选操作并且都是通过指定的头文件进行配置,用户可以根据需要自行选择使用,它们具有如下的特点: - -1. ESP32_Display_Panel 查找配置文件的路径顺序为:`当前工程目录` > `Arduino 库目录` > `ESP32_Display_Panel 目录`。 -2. ESP32_Display_Panel 中所有的示例工程都默认包含了各自所需的配置文件,用户可以直接修改其中的宏定义。 -3. 对于没有配置文件的工程,用户可以将其从 ESP32_Display_Panel 的根目录或者示例工程中复制到自己的工程中。 -4. 如果有多个工程需要使用相同的配置,用户可以将配置文件放在 [Arduino 库目录](#arduino-库的目录在哪儿)中,这样所有的工程都可以共享相同的配置。 - -> [!WARNING] -> * 同一个目录下可以同时包含 `ESP_Panel_Board_Supported.h` 和 `ESP_Panel_Board_Custom.h` 两种配置文件,但是它们不能同时被使能,即 `ESP_PANEL_USE_SUPPORTED_BOARD` 和 `ESP_PANEL_USE_CUSTOM_BOARD` 最多只能有一个为 `1`。 -> * 如果以上两个配置文件都被没有被使能,那么用户就无法使用 `ESP_Panel` 驱动,只能使用其他独立的设备驱动,如 `ESP_PanelBus`, `ESP_PanelLcd` 等。 -> * 由于这些文件内的配置可能会发生变化,比如新增、删除或重命名,为了保证程序的兼容性,库对它们分别进行了独立的版本管理,并在编译时检查用户当前使用的配置文件与库是否兼容。详细的版本信息以及检查规则可以在文件的末尾处找到。 - -#### 配置驱动 - -ESP32_Display_Panel 会根据 [ESP_Panel_Conf.h](./ESP_Panel_Conf.h) 文件来配置驱动的功能和参数,用户可以通过修改此文件中的宏定义来更新驱动的行为或默认参数。以使能用于调试的 LOG 输出为例,下面是修改后的 `ESP_Panel_Conf.h` 文件的部分内容: - -```c -... -/* Set to 1 if print log message for debug */ -#define ESP_PANEL_ENABLE_LOG (1) // 0/1 -... -``` - -#### 使用支持的开发板 - -ESP32_Display_Panel 会根据 [ESP_Panel_Board_Supported.h](./ESP_Panel_Board_Supported.h) 文件来配置 `ESP_Panel` 成为目标开发板的驱动,用户可以通过修改此文件中的宏定义来选择支持的开发板。以使用 *ESP32-S3-BOX-3* 开发板为例,修改步骤如下: - -1. 设置 `ESP_Panel_Board_Supported.h` 文件中的 `ESP_PANEL_USE_SUPPORTED_BOARD` 宏定义为 `1`。 -2. 根据目标开发板的型号,取消对应的宏定义的注释。 - -下面是修改后的 `ESP_Panel_Board_Supported.h` 文件的部分内容: - -```c -... -/* Set to 1 if using a supported board */ -#define ESP_PANEL_USE_SUPPORTED_BOARD (1) // 0/1 - -#if ESP_PANEL_USE_SUPPORTED_BOARD -... -// #define BOARD_ESP32_C3_LCDKIT -// #define BOARD_ESP32_S3_BOX -#define BOARD_ESP32_S3_BOX_3 -// #define BOARD_ESP32_S3_BOX_3_BETA -... -#endif -``` - -#### 使用自定义开发板 - -ESP32_Display_Panel 会根据 [ESP_Panel_Board_Custom.h](./ESP_Panel_Board_Custom.h) 文件来配置 `ESP_Panel` 成为自定义开发板的驱动,用户需要根据自定义开发板的实际参数对此文件进行修改。以使用 *480x480 RGB ST7701 LCD + I2C GT911 Touch* 的自定义开发板为例,修改步骤如下: - -1. 设置 `ESP_Panel_Board_Custom.h` 文件中的 `ESP_PANEL_USE_CUSTOM_BOARD` 宏定义为 `1`。 -2. 设置 LCD 相关宏定义: - a. 设置 `ESP_PANEL_USE_LCD` 为 `1` - b. 设置 `ESP_PANEL_LCD_WIDTH` 和 `ESP_PANEL_LCD_HEIGHT` 为 `480` - c. 设置 `ESP_PANEL_LCD_BUS_TYPE` 为 `ESP_PANEL_BUS_TYPE_RGB`。 - d. 在 `ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB` 下方的宏定义中设置 LCD 的信号引脚和其他参数。 - e. 根据屏厂提供的初始化命令参数,取消 `ESP_PANEL_LCD_VENDOR_INIT_CMD` 宏定义的注释并修改内容。 - f. 根据需要修改其他 LCD 配置 -2. 设置 Touch 相关宏定义: - a. 设置 `ESP_PANEL_USE_TOUCH` 为 `1` - b. 在 `ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C` 下方的宏定义中设置 Touch 的信号引脚和其他参数。 - c. 根据需要修改其他 Touch 配置 -3. 根据需要使能其他驱动的宏定义,如 `ESP_PANEL_USE_BACKLIGHT`, `ESP_PANEL_USE_EXPANDER` 等。 - -下面是修改后的 `ESP_Panel_Board_Custom.h` 文件的部分内容: - -```c -... -/* Set to 1 if using a custom board */ -#define ESP_PANEL_USE_CUSTOM_BOARD (1) // 0/1 - -/* Set to 1 when using an LCD panel */ -#define ESP_PANEL_USE_LCD (1) // 0/1 - -#if ESP_PANEL_USE_LCD -/** - * LCD Controller Name - */ -#define ESP_PANEL_LCD_NAME ST7701 - -/* LCD resolution in pixels */ -#define ESP_PANEL_LCD_WIDTH (480) -#define ESP_PANEL_LCD_HEIGHT (480) -... -/** - * LCD Bus Type. - */ -#define ESP_PANEL_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_RGB) -/** - * LCD Bus Parameters. - * - * Please refer to https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd.html and - * https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html for more details. - * - */ -#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB -... -#endif /* ESP_PANEL_LCD_BUS_TYPE */ -... -/** - * LCD Vendor Initialization Commands. - * - * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for - * initialization sequence code. Please uncomment and change the following macro definitions. Otherwise, the LCD driver - * will use the default initialization sequence code. - * - * There are two formats for the sequence code: - * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and - * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) - */ -#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ - { \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x31, 0x05}), \ - ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xCD, {0x00}), \ - ... - ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x11), \ - } -... -#endif /* ESP_PANEL_USE_LCD */ - -/* Set to 1 when using an touch panel */ -#define ESP_PANEL_USE_TOUCH (1) // 0/1 -#if ESP_PANEL_USE_TOUCH -/** - * Touch controller name - */ -#define ESP_PANEL_TOUCH_NAME GT911 -... -/** - * Touch panel bus type - */ -#define ESP_PANEL_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C) -/* Touch panel bus parameters */ -#if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C -... -#endif /* ESP_PANEL_TOUCH_BUS_TYPE */ -... -#endif /* ESP_PANEL_USE_TOUCH */ -... -#define ESP_PANEL_USE_BACKLIGHT (1) // 0/1 -#if ESP_PANEL_USE_BACKLIGHT -... -#endif /* ESP_PANEL_USE_BACKLIGHT */ -... -#endif /* ESP_PANEL_USE_CUSTOM_BOARD */ -``` - -### 示例说明 - -以下是在不同开发平台下使用 ESP32_Display_Panel 的一些示例。 - -#### Arduino IDE - -你可以在 Arduino IDE 中导航到 `File` > `Examples` > `ESP32_Display_Panel` 来访问它们。如果你找不到 `ESP32_Display_Panel` 选项,请检查库是否已正确安装,并确认选择了一个 ESP 开发板。 - -##### LCD - -以下示例演示了如何使用独立的驱动开发不同接口和不同型号的 LCD,并通过显示彩条进行测试: - -* [SPI](examples/LCD/SPI/) -* [QSPI](examples/LCD/QSPI/) -* [Single RGB](examples/LCD/RGB/) -* [3-wire SPI + RGB](examples/LCD/3wireSPI_RGB/) - -##### Touch - -以下示例演示了如何使用独立的驱动开发不同接口和不同型号的触摸屏,并通过打印触摸点坐标进行测试: - -* [I2C](examples/Touch/I2C/) -* [SPI](examples/Touch/SPI/) - -##### Panel - -以下示例演示了如何使用 `ESP_Panel` 驱动开发内置或自定义的开发板: - -* [Panel Test](examples/Panel/PanelTest/):此示例通过显示彩条和打印触摸点坐标进行测试。 - -##### LVGL v8 - -关于如何配置 LVGL(v8.3.x),请参阅[此处](#配置-lvgl)以获取更多详细信息。 - -* [Porting](examples/LVGL/v8/Porting/): 此示例演示了如何移植 LVGL(v8.3.x)。对于 RGB LCD,它还可以启用防撕裂功能。 -* [Rotation](examples/LVGL/v8/Rotation/): 此示例演示了如何使用 LVGL 来旋转显示屏。 - -> [!WARNING] -> 目前,防撕裂功能仅支持 RGB LCD,并且需要 LVGL 的版本满足 >= v8.3.9,如果使用的是其他类型的 LCD 或不符合要求的 LVGL 版本,请不要启用此功能。 - -##### SquareLine - -​ 要移植 Squarelina 项目(v1.3.x),请参阅[此处](#移植-SquareLine-工程)获取更多详细信息。 - -- [Porting](examples/SquareLine/v8/Porting): 此示例演示了如何移植 SquareLine 项目。 -- [WiFiClock](examples/SquareLine/v8/WiFiClock): 此示例实现了一个简单的 Wi-Fi 时钟,并且可以显示天气信息。 - -#### PlatformIO - -- [PlatformIO](examples/PlatformIO/): 此示例演示了如何在 PlatformIO 中使用 ESP32_Display_Panel。它默认情况下适用于 **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** 开发板,用户需要根据实际情况修改 [boards/ESP-LCD.json](examples/PlatformIO/boards/ESP-LCD.json) 文件。 - -## 其他相关说明 - -### 配置支持的开发板 - -关于如何在 Arduino IDE 中配置支持的开发板,请参考 [Board_Instructions - Recommended Configurations in the Arduino IDE](./docs/Board_Instructions.md#recommended-configurations-in-the-arduino-ide). - -### 配置 LVGL - -LVGL 的功能和参数可以通过编辑 `lv_conf.h` 文件来进行配置,用户可以修改此文件中的宏定义以更新驱动的行为或默认参数。以下是配置 LVGL 的一些特点和步骤: - -1. 在使用 arduino-esp32 v3.x.x 版本时,LVGL 会按照以下路径顺序查找配置文件:`当前工程目录` > `Arduino 库目录`。如果未找到配置文件,编译时会提示未找到配置文件的错误,因此用户需要确保至少有一个目录中包含 `lv_conf.h` 文件。 - -2. 如果多个工程需要使用相同的配置,用户可以将配置文件放在 [Arduino 库目录](#arduino-库的目录在哪儿)中,这样所有工程都可以共享相同的配置。 - -下面是共享相同 LVGL 配置的详细设置步骤: - -1. 导航到 [Arduino 库目录](#arduino-库的目录在哪儿)。 - -2. 进入 `lvgl` 文件夹,复制 `lv_conf_template.h` 文件,并将副本放在与 `lvgl` 文件夹同一级的位置,然后将复制的文件重命名为 `lv_conf.h`。 - -3. 最终,Arduino 库文件夹的布局如下所示: - - ``` - Arduino - |-libraries - |-lv_conf.h - |-lvgl - |-other_lib_1 - |-other_lib_2 - ``` - -4. 打开 `lv_conf.h` 文件,并将第一个 `#if 0` 修改为 `#if 1` 以启用文件的内容。 - -5. 根据需求设置其他配置。以下是一些常见的 LVGL v8 版本的配置项示例: - - ```c - #define LV_COLOR_DEPTH 16 // 通常使用 16 位色深(RGB565), - // 但也可以将其设置为 `32` 来支持 24 位色深(RGB888) - #define LV_COLOR_16_SWAP 0 // 如果使用 SPI/QSPI LCD(例如 ESP32-C3-LCDkit),需要将其设置为 `1` - #define LV_COLOR_SCREEN_TRANSP 1 - #define LV_MEM_CUSTOM 1 - #define LV_MEMCPY_MEMSET_STD 1 - #define LV_TICK_CUSTOM 1 - #define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR - // 获取更高的性能但占用更多的 SRAM - #define LV_FONT_MONTSERRAT_N 1 // 启用所有需要使用的内部字体(`N`应该替换为字体大小) - ``` - -6. 获取更多信息,请参考[ LVGL 官方文档](https://docs.lvgl.io/8.3/get-started/platforms/arduino.html)。 - -### 移植 SquareLine 工程 - -SquareLine Studio (v1.3.x) 可以通过图像化编辑的方式快速设计精美的 UI。如果想要在 Arduino IDE 中使用 SquareLine 导出的 UI 源文件,可以按照以下步骤进行移植: - -1. 首先,在 SquareLine Studio 中创建一个新的工程,进入 `Create` -> `Arduino` 一栏,选择 `Arduino with TFT-eSPI` 作为工程模板,然后在右侧的 `PROJECT SETTINGS` 一栏需要根据目标开发板的 LCD 属性进行配置,如 `Resolution` and `Color depth`,最后点击 `Create` 按钮创建工程。 - -2. 对于已有的工程,也可以在导航栏中点击 `File` -> `Project Settings` 按钮进入工程设置,然后在 `BOARD PROPERTIES` 一栏配置 `Board Group` 为 `Arduino`,`Board` 为 `Arduino with TFT-eSPI`,并且根据目标开发板的 LCD 属性在 `DISPLAY PROPERTIES` 一栏进行配置,最后点击 `Save` 按钮保存工程设置。 - -3. 完成 UI 设计并且配置好导出路径后,即可依次点击菜单栏中的 `Export` -> `Create Template Project` 和 `Export UI Files` 按钮导出工程及 UI 源文件,该工程目录的布局如下所示: - - ``` - Project - |-libraries - |-lv_conf.h - |-lvgl - |-readme.txt - |-TFT_eSPI - |-ui - |-README.md - |-ui - ``` - -4. 将工程目录下的 `libraries` 文件夹中的 `lv_conf.h`、`lvgl` 和 `ui` 复制到 Arduino 库目录中。如果需要使用本地安装的 `lvgl`,请跳过复制 `lvgl` 和 `lv_conf.h`,然后参考[步骤](#配置-lvgl)来配置 LVGL。Arduino 库文件夹的布局如下: - - ``` - Arduino - |-libraries - |-ESP32_Display_Panel - |-ESP_Panel_Conf.h (可选) - |-lv_conf.h (可选) - |-lvgl - |-ui - |-other_lib_1 - |-other_lib_2 - ``` - ## 常见问题解答 -### Arduino 库的目录在哪儿? - -您可以在 Arduino IDE 的菜单栏中选择 `File` > `Preferences` > `Settings` > `Sketchbook location` 来查找和修改 Arduino 库的目录路径。 - -### 如何在 Arduino IDE 中安装 ESP32_Display_Panel? - -- 如果您想要在线安装,可以在 Arduino IDE 中导航到 `Sketch` > `Include Library` > `Manage Libraries...`,然后搜索 `ESP32_Display_Panel`,点击 `Install` 按钮进行安装。 -- 如果您想要手动安装,可以从 [ESP32_Display_Panel](https://github.com/esp-arduino-libs/ESP32_Display_Panel) 下载所需版本的 `.zip` 文件,然后在 Arduino IDE 中导航到 `Sketch` > `Include Library` > `Add .ZIP Library...`,选择下载的 `.zip` 文件并点击 `Open` 按钮进行安装。 -- 您还可以查阅 [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) 或者 [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library) 文档中关于安装库的指南。 - -### arduino-eps32 的安装目录以及 SDK 的目录在哪儿? - -arduino-esp32 的默认安装路径取决于您的操作系统: - -- Windows: `C:\Users\\AppData\Local\Arduino15\packages\esp32` -- Linux: `~/.arduino15/packages/esp32` - -arduino-esp32 v3.x.x 版本的 SDK 位于默认安装路径下的 `tools > esp32-arduino-libs > idf-release_x` 目录中。 - -### 使用 ESP32-S3 驱动 RGB LCD 时出现画面漂移问题的解决方案 - -当您在使用 ESP32-S3 驱动 RGB LCD 时遇到画面漂移的问题时,您可以采用以下步骤来解决: - -1. **查看文档**:详细了解问题的说明,您可以参考[这篇文档](https://docs.espressif.com/projects/esp-faq/zh_CN/latest/software-framework/peripherals/lcd.html#esp32-s3-rgb-lcd)。 - -2. **启用 `Bounce Buffer + XIP on PSRAM` 特性**:为了解决问题,推荐启用 `Bounce Buffer + XIP on PSRAM` 特性。具体步骤如下: - - - **Step1**:从 [arduino-esp32-sdk](https://github.com/esp-arduino-libs/arduino-esp32-sdk) 下载 "high_perf" 版本的 SDK,并将其替换到 [arduino-esp32 的安装目录](#arduino-eps32-的安装目录以及-sdk-的目录在哪儿)中。 - - - **Step2**:如果您使用的是支持的开发板,则通常无需修改代码,因为它们默认设置了 `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` 为 `(ESP_PANEL_LCD_WIDTH * 10)`。如果问题仍然存在,请参考下面的示例代码来增大 `Bounce Bufer` 的大小。 - - - **Step3**:如果您使用的是自定义的开发板,请在 `ESP_Panel_Board_Custom.h` 文件中确认 `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` 是否设置为非 0。如果问题仍然存在,请增大 `Bounce Bufer` 的大小。 - - - **Step4**:如果您使用的是独立的驱动,请参考下面的示例代码来设置 `Bounce Bufer` 的大小。 - - - **Step5**:如果您正在开发 LVGL 应用,将执行 RGB 外设初始化的任务与执行 LVGL lv_timer_handler() 的任务分配在同一个核上,请参考 [代码](./examples/LVGL/v8/Porting/lvgl_port_v8.h#L53)。 - -3. **示例代码**:以下示例代码展示了如何通过 `ESP_Panel` 驱动或独立的驱动来修改 `Bounce Bufer` 的大小: - - **Example1**:使用 `ESP_Panel` 驱动修改 `Bounce Bufer` 大小: - - ```c - ... - ESP_Panel *panel = new ESP_Panel(); - panel->init(); - // Start - ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); - // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. - rgb_bus->configRgbBounceBufferSize((ESP_PANEL_LCD_WIDTH * 20)); - // End - panel->begin(); - ... - ``` - - **Example2**:使用独立的驱动修改 `Bounce Bufer` 大小: - - ```c - ... - ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB(...); - // Start - // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. - lcd_bus->configRgbBounceBufferSize(EXAMPLE_LCD_WIDTH * 10); - // End - lcd_bus->begin(); - ... - ``` - -### 如何在 PlatformIO 上使用 ESP32_Display_Panel? - -您可以参考示例 [PlatformIO](examples/PlatformIO/) 在 PlatformIO 中使用 ESP32_Display_Panel 库,它默认情况下适用于 **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** 开发板,您需要根据实际情况修改 [boards/ESP-LCD.json](examples/PlatformIO/boards/ESP-LCD.json) 文件。 +请参阅文档 - [常见问题解答](./docs/FAQ_CN.md) 。 diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..8e415863 --- /dev/null +++ b/conftest.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +import logging +import os +import pathlib +import re +import sys +from datetime import datetime +from typing import Callable, List, Optional, Tuple + +import pytest +from pytest import Config, FixtureRequest, Function, Session +from pytest_embedded.plugin import multi_dut_argument, multi_dut_fixture + +IDF_VERSION = os.environ.get('IDF_VERSION') +PYTEST_ROOT_DIR = str(pathlib.Path(__file__).parent) +logging.info(f'Pytest root dir: {PYTEST_ROOT_DIR}') + + +@pytest.fixture(scope='session', autouse=True) +def idf_version() -> str: + if os.environ.get('IDF_VERSION'): + return os.environ.get('IDF_VERSION') + idf_path = os.environ.get('IDF_PATH') + if not idf_path: + logging.warning('Failed to get IDF_VERSION!') + return '' + version_path = os.path.join(os.environ['IDF_PATH'], 'tools/cmake/version.cmake') + regex = re.compile(r'^\s*set\s*\(\s*IDF_VERSION_([A-Z]{5})\s+(\d+)') + ver = {} + with open(version_path) as f: + for line in f: + m = regex.match(line) + if m: + ver[m.group(1)] = m.group(2) + return '{}.{}'.format(int(ver['MAJOR']), int(ver['MINOR'])) + + +@pytest.fixture(scope='session', autouse=True) +def session_tempdir() -> str: + _tmpdir = os.path.join( + os.path.dirname(__file__), + 'pytest_log', + datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), + ) + os.makedirs(_tmpdir, exist_ok=True) + return _tmpdir + + +@pytest.fixture +@multi_dut_argument +def config(request: FixtureRequest) -> str: + config_marker = list(request.node.iter_markers(name='config')) + return config_marker[0].args[0] if config_marker else 'defaults' + + +@pytest.fixture +@multi_dut_argument +def app_path(request: FixtureRequest, test_file_path: str) -> str: + config_marker = list(request.node.iter_markers(name='app_path')) + if config_marker: + return config_marker[0].args[0] + else: + # compatible with old pytest-embedded parametrize --app_path + return request.config.getoption('app_path', None) or os.path.dirname(test_file_path) + + +@pytest.fixture +def test_case_name(request: FixtureRequest, target: str, config: str) -> str: + if not isinstance(target, str): + target = '|'.join(sorted(list(set(target)))) + if not isinstance(config, str): + config = '|'.join(sorted(list(config))) + return f'{target}.{config}.{request.node.originalname}' + + +@pytest.fixture +@multi_dut_fixture +def build_dir( + app_path: str, + target: Optional[str], + config: Optional[str], + idf_version: str +) -> Optional[str]: + """ + Check local build dir with the following priority: + + 1. /${IDF_VERSION}/build__ + 2. /${IDF_VERSION}/build_ + 3. /build__ + 4. /build + 5. + + Args: + app_path: app path + target: target + config: config + + Returns: + valid build directory + """ + + assert target + assert config + check_dirs = [] + if idf_version: + check_dirs.append(os.path.join(idf_version, f'build_{target}_{config}')) + check_dirs.append(os.path.join(idf_version, f'build_{target}')) + check_dirs.append(f'build_{target}_{config}') + check_dirs.append('build') + check_dirs.append('.') + for check_dir in check_dirs: + binary_path = os.path.join(app_path, check_dir) + if os.path.isdir(binary_path): + logging.info(f'find valid binary path: {binary_path}') + return check_dir + + logging.warning( + f'checking binary path: {binary_path} ... missing ... try another place') + + logging.error( + f'no build dir. Please build the binary "python .gitlab/tools/build_apps.py {app_path}" and run pytest again') + sys.exit(1) + + +@pytest.fixture(autouse=True) +@multi_dut_fixture +def junit_properties( + test_case_name: str, record_xml_attribute: Callable[[str, object], None] +) -> None: + """ + This fixture is autoused and will modify the junit report test case name to .. + """ + record_xml_attribute('name', test_case_name) + + +################## +# Hook functions # +################## +_idf_pytest_embedded_key = pytest.StashKey['IdfPytestEmbedded'] + + +def pytest_addoption(parser: pytest.Parser) -> None: + base_group = parser.getgroup('idf') + base_group.addoption( + '--env', + help='only run tests matching the environment NAME.', + ) + + +def pytest_configure(config: Config) -> None: + # Require cli option "--target" + help_commands = ['--help', '--fixtures', '--markers', '--version'] + for cmd in help_commands: + if cmd in config.invocation_params.args: + target = 'unneeded' + break + else: + target = config.getoption('target') + if not target: + raise ValueError('Please specify one target marker via "--target [TARGET]"') + + config.stash[_idf_pytest_embedded_key] = IdfPytestEmbedded( + target=target, + env_name=config.getoption('env'), + ) + config.pluginmanager.register(config.stash[_idf_pytest_embedded_key]) + + +def pytest_unconfigure(config: Config) -> None: + _pytest_embedded = config.stash.get(_idf_pytest_embedded_key, None) + if _pytest_embedded: + del config.stash[_idf_pytest_embedded_key] + config.pluginmanager.unregister(_pytest_embedded) + + +class IdfPytestEmbedded: + def __init__( + self, + target: Optional[str] = None, + env_name: Optional[str] = None, + ): + # CLI options to filter the test cases + self.target = target + self.env_name = env_name + + self._failed_cases: List[ + Tuple[str, bool, bool] + ] = [] # (test_case_name, is_known_failure_cases, is_xfail) + + @pytest.hookimpl(tryfirst=True) + def pytest_sessionstart(self, session: Session) -> None: + if self.target: + self.target = self.target.lower() + session.config.option.target = self.target + + # @pytest.hookimpl(tryfirst=True) + def pytest_collection_modifyitems(self, items: List[Function]) -> None: + # set default timeout 10 minutes for each case + for item in items: + # default timeout 5 mins + if 'timeout' not in item.keywords: + item.add_marker(pytest.mark.timeout(5 * 60)) + + # filter all the test cases with "--target" + if self.target: + def item_targets(item): + return [m.args[0] for m in item.iter_markers(name='target')] + items[:] = [item for item in items if self.target in item_targets(item)] + + # filter all the test cases with "--env" + if self.env_name: + def item_envs(item): + return [m.args[0] for m in item.iter_markers(name='env')] + items[:] = [item for item in items if self.env_name in item_envs(item)] diff --git a/docs/Board_Contribution_Guide.md b/docs/Board_Contribution_Guide.md index 08e98e90..fdbe11bc 100644 --- a/docs/Board_Contribution_Guide.md +++ b/docs/Board_Contribution_Guide.md @@ -1,18 +1,26 @@ # Board Contribution Guide -1. Newly added development boards must ensure the hardware schematics are open-source. Please provide a link or file. -2. This library currently only supports the APIs provided in ESP-IDF. It does not support other Arduino library APIs, such as Wire. +## Contribution Guidelines -**Note**: -1. It is recommended to use the vscode + Arduino CLI development environment. -2. Pull the ESP32_Display_Panel repository into the Arduino library directory before making modifications. -3. The project uses pre-commit to enforce commit standards. It is recommended to install the pre-commit library before committing using the following command: +1. The development board must at least ensure its hardware schematic is open-source, providing a link or file for reference. +2. To maintain compatibility across platforms, this library only supports APIs provided by ESP-IDF. Please do not include or use headers or APIs specific to other platforms, such as Arduino's `Wire`. -``` -pip3 install pre-commit && pre-commit install -``` +**Notes**: + +- Before making changes, it is recommended to add the ESP32_Display_Panel repository to your Arduino library directory to facilitate validation in an Arduino project. +- This project uses `pre-commit` to enforce commit standards. When making git commits, `pre-commit` will run automatically, so it is advised to install `pre-commit` beforehand by using the following commands: + + ```bash + # Install pre-commit + pip3 install pre-commit && pre-commit install + + # Run pre-commit on all files + pre-commit run --all-files + ``` + +- If you encounter a commit failure, it may be due to `pre-commit` standards. These checks automatically verify and enforce code formatting, style, and other standards. Please confirm and apply any necessary modifications, then re-commit. -## Modification Content +## File Modifications Using the adaption of the [`M5Stack M5DIAL`](https://github.com/esp-arduino-libs/ESP32_Display_Panel/commit/1886c668468626b9dd2ae975f7db12df5413378e) development board as an example. Following this guide, changes below will be made under the project: @@ -22,11 +30,10 @@ Using the adaption of the [`M5Stack M5DIAL`](https://github.com/esp-arduino-libs | -board | -m5stack [A] | -M5DIAL.h [A] - | -ESP_PanelBoard [M] + | -ESP_PanelBoard.h [M] | -README.md [M] | -ESP_PanelVersions.h [M] | -CHANGELOG.md [M] - | -ESP_Panel_Board_Custom.h | -ESP_Panel_Board_Supported.h [M] | -library.properties [M] | -README_CN.md [M] @@ -34,7 +41,7 @@ Using the adaption of the [`M5Stack M5DIAL`](https://github.com/esp-arduino-libs ``` Note: [A] stands for 'append' and [M] stands for 'modify' -## Modification Process +## Adaptation Process Using the adaption of `M5Stack M5DIAL` as an example, follow these steps to modify the relevant files: diff --git a/docs/Board_Contribution_Guide_CN.md b/docs/Board_Contribution_Guide_CN.md index 83d7e60a..06941c74 100644 --- a/docs/Board_Contribution_Guide_CN.md +++ b/docs/Board_Contribution_Guide_CN.md @@ -1,18 +1,26 @@ # 开发板贡献指南 -1. 新添加的开发板需要确保硬件原理图开源,需要提供链接或文件。 -2. 该库目前仅支持 ESP-IDF 提供的 API,不支持其他 Arduino 库 API,如 Wire。 +## 贡献说明 + +1. 开发板至少需要确保其硬件原理图开源,并提供链接或文件。 +2. 为了兼容其他平台,该库仅支持使用 ESP-IDF 提供的 API,请勿包含和使用其他特定平台的头文件以及 API,如 Arduino 的 `Wire`。 **注意**: -1. 推荐使用 vscode + Arduino CLI 开发环境。 -2. 在进行修改之前,将 ESP32_Display_Panel 仓库拉入 Arduino 库目录中。 -3. 项目使用 pre-commit 来规范提交内容,因此建议在提交之前安装 pre-commit 库,使用以下命令: -``` -pip3 install pre-commit && pre-commit install -``` +- 在进行修改之前,推荐将 ESP32_Display_Panel 仓库拉入 Arduino 库目录中,方便验证 Arduino 工程。 +- 项目使用 pre-commit 来规范提交内容,用户在进行 git 提交修改时会自动触发,因此建议在此之前安装 pre-commit 库,参考以下命令: + + ``` + # 安装 pre-commit + pip3 install pre-commit && pre-commit install + + # 强制执行 pre-commit + pre-commit run --all-files + ``` + +- 如果提交 git 修改时发现提交失败,可以检查是否是 pre-commit 规范导致的,规范会自动检查代码格式、代码风格等问题并进行修复,请确认和添加修改后再次提交即可。 -## 需修改内容 +## 文件修改 以适配 [`M5Stack M5DIAL`](https://github.com/esp-arduino-libs/ESP32_Display_Panel/commit/1886c668468626b9dd2ae975f7db12df5413378e) 开发板为例。按照本指南,以下更改将在项目中进行: @@ -22,11 +30,10 @@ pip3 install pre-commit && pre-commit install | -board | -m5stack [A] | -M5DIAL.h [A] - | -ESP_PanelBoard [M] + | -ESP_PanelBoard.h [M] | -README.md [M] | -ESP_PanelVersions.h [M] | -CHANGELOG.md [M] - | -ESP_Panel_Board_Custom.h | -ESP_Panel_Board_Supported.h [M] | -library.properties [M] | -README_CN.md [M] @@ -34,7 +41,7 @@ pip3 install pre-commit && pre-commit install ``` 注:[A] 代表 '添加',[M] 代表 '修改' -## 各文件修改流程 +## 适配流程 以适配 `M5Stack M5DIAL` 为例,按照以下步骤修改相关文件: diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 00000000..5c6d2f53 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,72 @@ +# FAQ + +## Where is the directory for Arduino libraries? + +You can find and modify the directory path for Arduino libraries by selecting `File` > `Preferences` > `Settings` > `Sketchbook location` from the menu bar in the Arduino IDE. + +## How to Install ESP32_Display_Panel in Arduino IDE? + +- If you want to install online, navigate to `Sketch` > `Include Library` > `Manage Libraries...` in the Arduino IDE, then search for `ESP32_Display_Panel` and click the `Install` button to install it. +- If you want to install manually, download the required version of the `.zip` file from [ESP32_Display_Panel](https://github.com/esp-arduino-libs/ESP32_Display_Panel), then navigate to `Sketch` > `Include Library` > `Add .ZIP Library...` in the Arduino IDE, select the downloaded `.zip` file, and click `Open` to install it. +- You can also refer to the guides on library installation in the [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) or [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library) documentation. + +## Where are the installation directory for arduino-esp32 and the SDK located? + +The default installation path for arduino-esp32 depends on your operating system: + +- Windows: `C:\Users\\AppData\Local\Arduino15\packages\esp32` +- Linux: `~/.arduino15/packages/esp32` + +The SDK for arduino-esp32 v3.x.x version is located in the `tools/esp32-arduino-libs/idf-release_x` directory within the default installation path. + +## How to fix screen drift issue when driving RGB LCD with ESP32-S3? + +When encountering screen drift issue when driving RGB LCD with ESP32-S3, you can follow these steps to resolve them: + +1. **Refer to Documentation**: Understand the issue description in detail, you can refer to [this document](https://docs.espressif.com/projects/esp-faq/en/latest/software-framework/peripherals/lcd.html#why-do-i-get-drift-overall-drift-of-the-display-when-esp32-s3-is-driving-an-rgb-lcd-screen). + +2. **Enable `Bounce Buffer + XIP on PSRAM` Feature**: To resolve the issue, it's recommended to enable the `Bounce Buffer + XIP on PSRAM` feature. Follow these steps: + + - **Step 1**: Download the "high_perf" version of the SDK from [arduino-esp32-sdk](https://github.com/esp-arduino-libs/arduino-esp32-sdk) and replace it in the [installation directory of arduino-esp32](#where-are-the-installation-directory-for-arduino-esp32-and-the-sdk-located). + + - **Step 2**: If you are using supported development boards, usually there's no need to modify the code as they set `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` to `(ESP_PANEL_LCD_WIDTH * 10)` by default. If the issue persists, refer to the example code below to increase the size of the `Bounce Buffer`. + + - **Step 3**: If you are using a custom board, confirm in the `ESP_Panel_Board_Custom.h` file whether `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` is set to non-zero. If the issue persists, increase the size of the `Bounce Buffer`. + + - **Step 4**: If you are using an independent driver, refer to the example code below to set the size of the `Bounce Buffer`. + + - **Step 5**: If you are developing an LVGL application, assign the task that initializes the RGB peripheral and the task that runs the LVGL `lv_timer_handler()` on the same core. Please refer to [the code](../examples/LVGL/v8/Porting/lvgl_port_v8.h#L53). + +3. **Example Code**: The following example code demonstrates how to modify the size of the `Bounce Buffer` using `ESP_Panel` driver or independent driver: + + **Example 1**: Modify the `Bounce Buffer` size using the `ESP_Panel` driver: + + ```c + ... + ESP_Panel *panel = new ESP_Panel(); + panel->init(); + // Start + ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); + // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. + rgb_bus->configRgbBounceBufferSize((ESP_PANEL_LCD_WIDTH * 20)); + // End + panel->begin(); + ... + ``` + + **Example 2**: Modify the `Bounce Buffer` size using an independent driver: + + ```c + ... + ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB(...); + // Start + // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. + lcd_bus->configRgbBounceBufferSize(EXAMPLE_LCD_WIDTH * 10); + // End + lcd_bus->begin(); + ... + ``` + +## How to Use ESP32_Display_Panel on PlatformIO? + +You can refer to the example [PlatformIO](../examples/PlatformIO/) to use the ESP32_Display_Panel library in PlatformIO. By default, it is suitable for the **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** development boards. You need to modify the [boards/ESP-LCD.json](../examples/PlatformIO/boards/ESP-LCD.json) file according to the actual situation. diff --git a/docs/FAQ_CN.md b/docs/FAQ_CN.md new file mode 100644 index 00000000..cd2d5174 --- /dev/null +++ b/docs/FAQ_CN.md @@ -0,0 +1,72 @@ +# 常见问题解答 + +## Arduino 库的目录在哪儿? + +您可以在 Arduino IDE 的菜单栏中选择 `File` > `Preferences` > `Settings` > `Sketchbook location` 来查找和修改 Arduino 库的目录路径。 + +## 如何在 Arduino IDE 中安装 ESP32_Display_Panel? + +- 如果您想要在线安装,可以在 Arduino IDE 中导航到 `Sketch` > `Include Library` > `Manage Libraries...`,然后搜索 `ESP32_Display_Panel`,点击 `Install` 按钮进行安装。 +- 如果您想要手动安装,可以从 [ESP32_Display_Panel](https://github.com/esp-arduino-libs/ESP32_Display_Panel) 下载所需版本的 `.zip` 文件,然后在 Arduino IDE 中导航到 `Sketch` > `Include Library` > `Add .ZIP Library...`,选择下载的 `.zip` 文件并点击 `Open` 按钮进行安装。 +- 您还可以查阅 [Arduino IDE v1.x.x](https://docs.arduino.cc/software/ide-v1/tutorials/installing-libraries) 或者 [Arduino IDE v2.x.x](https://docs.arduino.cc/software/ide-v2/tutorials/ide-v2-installing-a-library) 文档中关于安装库的指南。 + +## arduino-eps32 的安装目录以及 SDK 的目录在哪儿? + +arduino-esp32 的默认安装路径取决于您的操作系统: + +- Windows: `C:\Users\\AppData\Local\Arduino15\packages\esp32` +- Linux: `~/.arduino15/packages/esp32` + +arduino-esp32 v3.x.x 版本的 SDK 位于默认安装路径下的 `tools > esp32-arduino-libs > idf-release_x` 目录中。 + +## 使用 ESP32-S3 驱动 RGB LCD 时出现画面漂移问题的解决方案 + +当您在使用 ESP32-S3 驱动 RGB LCD 时遇到画面漂移的问题时,您可以采用以下步骤来解决: + +1. **查看文档**:详细了解问题的说明,您可以参考[这篇文档](https://docs.espressif.com/projects/esp-faq/zh_CN/latest/software-framework/peripherals/lcd.html#esp32-s3-rgb-lcd)。 + +2. **启用 `Bounce Buffer + XIP on PSRAM` 特性**:为了解决问题,推荐启用 `Bounce Buffer + XIP on PSRAM` 特性。具体步骤如下: + + - **Step1**:从 [arduino-esp32-sdk](https://github.com/esp-arduino-libs/arduino-esp32-sdk) 下载 "high_perf" 版本的 SDK,并将其替换到 [arduino-esp32 的安装目录](#arduino-eps32-的安装目录以及-sdk-的目录在哪儿)中。 + + - **Step2**:如果您使用的是支持的开发板,则通常无需修改代码,因为它们默认设置了 `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` 为 `(ESP_PANEL_LCD_WIDTH * 10)`。如果问题仍然存在,请参考下面的示例代码来增大 `Bounce Buffer` 的大小。 + + - **Step3**:如果您使用的是自定义的开发板,请在 `ESP_Panel_Board_Custom.h` 文件中确认 `ESP_PANEL_LCD_RGB_BOUNCE_BUF_SIZE` 是否设置为非 0。如果问题仍然存在,请增大 `Bounce Buffer` 的大小。 + + - **Step4**:如果您使用的是独立的驱动,请参考下面的示例代码来设置 `Bounce Buffer` 的大小。 + + - **Step5**:如果您正在开发 LVGL 应用,将执行 RGB 外设初始化的任务与执行 LVGL lv_timer_handler() 的任务分配在同一个核上,请参考 [代码](../examples/LVGL/v8/Porting/lvgl_port_v8.h#L53)。 + +3. **示例代码**:以下示例代码展示了如何通过 `ESP_Panel` 驱动或独立的驱动来修改 `Bounce Buffer` 的大小: + + **Example1**:使用 `ESP_Panel` 驱动修改 `Bounce Buffer` 大小: + + ```c + ... + ESP_Panel *panel = new ESP_Panel(); + panel->init(); + // Start + ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); + // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. + rgb_bus->configRgbBounceBufferSize((ESP_PANEL_LCD_WIDTH * 20)); + // End + panel->begin(); + ... + ``` + + **Example2**:使用独立的驱动修改 `Bounce Buffer` 大小: + + ```c + ... + ESP_PanelBus_RGB *lcd_bus = new ESP_PanelBus_RGB(...); + // Start + // The size of the bounce buffer must satisfy `width_of_lcd * height_of_lcd = size_of_buffer * N`, where N is an even number. + lcd_bus->configRgbBounceBufferSize(EXAMPLE_LCD_WIDTH * 10); + // End + lcd_bus->begin(); + ... + ``` + +## 如何在 PlatformIO 上使用 ESP32_Display_Panel? + +您可以参考示例 [PlatformIO](../examples/PlatformIO/) 在 PlatformIO 中使用 ESP32_Display_Panel 库,它默认情况下适用于 **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** 开发板,您需要根据实际情况修改 [boards/ESP-LCD.json](../examples/PlatformIO/boards/ESP-LCD.json) 文件。 diff --git a/docs/How_To_Use.md b/docs/How_To_Use.md new file mode 100644 index 00000000..ab37cbb7 --- /dev/null +++ b/docs/How_To_Use.md @@ -0,0 +1,356 @@ +# How to Use + +* [中文版本](./How_To_Use_CN.md) + +## Table of Contents + +- [How to Use](#how-to-use) + - [Table of Contents](#table-of-contents) + - [ESP-IDF Framework](#esp-idf-framework) + - [Dependencies and Versions](#dependencies-and-versions) + - [Adding to Project](#adding-to-project) + - [Configuration Instructions](#configuration-instructions) + - [Arduino IDE](#arduino-ide) + - [Dependencies and Versions](#dependencies-and-versions-1) + - [Installing the Library](#installing-the-library) + - [Configuration Instructions](#configuration-instructions-1) + - [Configuring Drivers](#configuring-drivers) + - [Using Supported Development Boards](#using-supported-development-boards) + - [Using Custom Development Boards](#using-custom-development-boards) + - [Usage Examples](#usage-examples) + - [LCD](#lcd) + - [Touch](#touch) + - [Panel](#panel) + - [LVGL v8](#lvgl-v8) + - [SquareLine](#squareline) + - [PlatformIO](#platformio) + - [Other Relevant Instructions](#other-relevant-instructions) + - [Configuring Supported Development Boards](#configuring-supported-development-boards) + - [Configuring LVGL](#configuring-lvgl) + - [Porting SquareLine Project](#porting-squareline-project) + +## ESP-IDF Framework + +### Dependencies and Versions + +| **Dependency** | **Version** | +| -------------- | ----------- | +| [esp-idf](https://github.com/espressif/esp-idf) | >= 5.1 | +| [esp32_io_expander](https://components.espressif.com/components/espressif/esp32_io_expander) | ^0.1.0 | + +### Adding to Project + +ESP32_Display_Panel has been uploaded to the [Espressif Component Registry](https://components.espressif.com/), and users can add it to their project using the `idf.py add-dependency` command, for example: + +```bash +idf.py add-dependency "espressif/esp32_display_panel" +``` + +Alternatively, users can create or modify the `idf_component.yml` file in the project directory. For more details, please refer to the [Espressif Documentation - IDF Component Manager](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/tools/idf-component-manager.html). + +### Configuration Instructions + +When developing with esp-idf, users can configure ESP32_Display_Panel through the menuconfig: + +1. Run the command `idf.py menuconfig`. +2. Navigate to `Component config` > `ESP Display Panel Configurations`. + +## Arduino IDE + +### Dependencies and Versions + +| **Dependency** | **Version** | +| -------------- | ----------- | +| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v3.0.0-alpha3 | +| [ESP32_IO_Expander](https://github.com/esp-arduino-libs/ESP32_IO_Expander) | >= 0.1.0 && < 0.2.0 | + +### Installing the Library + +For installation of the ESP32_Display_Panel library, refer to [How to Install ESP32_Display_Panel in Arduino IDE](./FAQ.md#how-to-install-esp32_display_panel-in-arduino-ide). + +### Configuration Instructions + +Below are detailed instructions on how to configure ESP32_Display_Panel, mainly including [Configuring Drivers](#configuring-drivers), [Using Supported Development Boards](#using-supported-development-boards), and [Using Custom Development Boards](#using-custom-development-boards). These are all optional operations and are configured through specified header files. Users can choose to use them according to their needs, with the following characteristics: + +1. The path sequence for ESP32_Display_Panel to search for configuration files is: `Current Project Directory` > `Arduino Library Directory` > `ESP32_Display_Panel Directory`. +2. All examples in ESP32_Display_Panel include their required configuration files by default, which users can directly modify macro definitions. +3. For projects without configuration files, users can copy them from the root directory or examples of ESP32_Display_Panel to their own projects. +4. If multiple projects need to use the same configuration, users can place the configuration files in the [Arduino Library Directory](./FAQ.md#where-is-the-directory-for-arduino-libraries), so that all projects can share the same configuration. + +> [!WARNING] +> * The same directory can simultaneously contain both `ESP_Panel_Board_Supported.h` and `ESP_Panel_Board_Custom.h` configuration files, but they cannot be enabled at the same time, meaning `ESP_PANEL_USE_SUPPORTED_BOARD` and `ESP_PANEL_USE_CUSTOM_BOARD` can only have one set to `1`. +> * If neither of the above two configuration files is enabled, users cannot use the `ESP_Panel` driver and can only use other standalone device drivers, such as `ESP_PanelBus`, `ESP_PanelLcd`, etc. +> * Since the configurations within these files might change, such as adding, deleting, or renaming, to ensure the compatibility of the program, the library manages the versions of these files independently and checks whether the configuration files currently used by the user are compatible with the library during compilation. Detailed version information and checking rules can be found at the end of the file. + +#### Configuring Drivers + +ESP32_Display_Panel configures driver functionality and parameters based on the [ESP_Panel_Conf.h](../ESP_Panel_Conf.h) file. Users can update the behavior or default parameters of the driver by modifying macro definitions in this file. For example, to enable debug log output, here is a snippet of the modified `ESP_Panel_Conf.h` file: + +```c +... +/* Set to 1 if print log message for debug */ +#define ESP_PANEL_ENABLE_LOG (1) // 0/1 +... +``` + +#### Using Supported Development Boards + +ESP32_Display_Panel configures `ESP_Panel` as the driver for the target development board based on the [ESP_Panel_Board_Supported.h](../ESP_Panel_Board_Supported.h) file. Users can select supported development boards by modifying macro definitions in this file. For example, to use the *ESP32-S3-BOX-3* development board, follow these steps: + +1. Set the `ESP_PANEL_USE_SUPPORTED_BOARD` macro definition in the `ESP_Panel_Board_Supported.h` file to `1`. +2. Uncomment the corresponding macro definition for the target development board model. + +Here is a snippet of the modified `ESP_Panel_Board_Supported.h` file: + +```c +... +/* Set to 1 if using a supported board */ +#define ESP_PANEL_USE_SUPPORTED_BOARD (1) // 0/1 + +#if ESP_PANEL_USE_SUPPORTED_BOARD +... +// #define BOARD_ESP32_C3_LCDKIT +// #define BOARD_ESP32_S3_BOX +#define BOARD_ESP32_S3_BOX_3 +// #define BOARD_ESP32_S3_BOX_3_BETA +... +#endif /* ESP_PANEL_USE_SUPPORTED_BOARD */ +``` + +#### Using Custom Development Boards + +ESP32_Display_Panel configures `ESP_Panel` as the driver for custom development boards based on the [ESP_Panel_Board_Custom.h](../ESP_Panel_Board_Custom.h) file. Users need to modify this file according to the actual parameters of the custom development board. For example, to use a custom development board with a *480x480 RGB ST7701 LCD + I2C GT911 Touch*, follow these steps: + +1. Set the `ESP_PANEL_USE_CUSTOM_BOARD` macro definition in the `ESP_Panel_Board_Custom.h` file to `1`. +2. Set the LCD-related macro definitions: + a. Set `ESP_PANEL_USE_LCD` to `1`. + b. Set `ESP_PANEL_LCD_WIDTH` and `ESP_PANEL_LCD_HEIGHT` to `480`. + c. Set `ESP_PANEL_LCD_BUS_TYPE` to `ESP_PANEL_BUS_TYPE_RGB`. + d. Set LCD signal pins and other parameters below `ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB`. + e. Uncomment and modify the `ESP_PANEL_LCD_VENDOR_INIT_CMD` macro definition according to the initialization command parameters provided by the screen vendor. + f. Modify other LCD configurations as needed. +3. Set the Touch-related macro definitions: + a. Set `ESP_PANEL_USE_TOUCH` to `1`. + b. Set Touch signal pins and other parameters below `ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C`. + c. Modify other Touch configurations as needed. +4. Enable other driver macro definitions as needed, such as `ESP_PANEL_USE_BACKLIGHT`, `ESP_PANEL_USE_EXPANDER`, etc. + +Here is a snippet of the modified `ESP_Panel_Board_Custom.h` file: + +```c +... +/* Set to 1 if using a custom board */ +#define ESP_PANEL_USE_CUSTOM_BOARD (1) // 0/1 + +/* Set to 1 when using an LCD panel */ +#define ESP_PANEL_USE_LCD (1) // 0/1 + +#if ESP_PANEL_USE_LCD +/** + * LCD Controller Name + */ +#define ESP_PANEL_LCD_NAME ST7701 + +/* LCD resolution in pixels */ +#define ESP_PANEL_LCD_WIDTH (480) +#define ESP_PANEL_LCD_HEIGHT (480) +... +/** + * LCD Bus Type. + */ +#define ESP_PANEL_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_RGB) +/** + * LCD Bus Parameters. + * + * Please refer to https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd.html and + * https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html for more details. + * + */ +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB +... +#endif /* ESP_PANEL_LCD_BUS_TYPE */ +... +/** + * LCD Vendor Initialization Commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. Please uncomment and change the following macro definitions. Otherwise, the LCD driver + * will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x31, 0x05}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xCD, {0x00}), \ + ... + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x11), \ + } +... +#endif /* ESP_PANEL_USE_LCD */ + +/* Set to 1 when using a touch panel */ +#define ESP_PANEL_USE_TOUCH (1) // 0/1 +#if ESP_PANEL_USE_TOUCH +/** + * Touch controller name + */ +#define ESP_PANEL_TOUCH_NAME GT911 +... +/** + * Touch panel bus type + */ +#define ESP_PANEL_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C) +/* Touch panel bus parameters */ +#if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C +... +#endif /* ESP_PANEL_TOUCH_BUS_TYPE */ +... +#endif /* ESP_PANEL_USE_TOUCH */ +... +#define ESP_PANEL_USE_BACKLIGHT (1) // 0/1 +#if ESP_PANEL_USE_BACKLIGHT +... +#endif /* ESP_PANEL_USE_BACKLIGHT */ +... +#endif /* ESP_PANEL_USE_CUSTOM_BOARD */ +``` + +### Usage Examples + +You can access them in the Arduino IDE by navigating to `File` > `Examples` > `ESP32_Display_Panel`. If you cannot find the `ESP32_Display_Panel` option, please check if the library has been installed correctly and ensure that an ESP development board is selected. + +#### LCD + +The following examples demonstrate how to develop different interface and model LCDs using standalone drivers and test them by displaying color bars: + +* [SPI](../examples/LCD/SPI/) +* [QSPI](../examples/LCD/QSPI/) +* [Single RGB](../examples/LCD/RGB/) +* [3-wire SPI + RGB](../examples/LCD/3wireSPI_RGB/) + +#### Touch + +The following example demonstrates how to develop touch screens of different interfaces and models using standalone drivers and test them by printing touch point coordinates: + +* [I2C](../examples/Touch/I2C/) +* [SPI](../examples/Touch/SPI/) + +#### Panel + +The following example demonstrates how to develop built-in or custom development boards using the `ESP_Panel` driver: + +* [Panel Test](../examples/Panel/PanelTest/): This example tests by displaying color bars and printing touch point coordinates. + +#### LVGL v8 + +For configuring LVGL (v8.3.x), please refer to [here](#configuring-lvgl) for more detailed information. + +* [Porting](../examples/LVGL/v8/Porting/): This example demonstrates how to port LVGL (v8.3.x). And for RGB LCD, it can enable the avoid tearing function. +* [Rotation](../examples/LVGL/v8/Rotation/): This example demonstrates how to use LVGL to rotate the display. + +> [!WARNING] +> Currently, the anti-tearing feature is only supported for RGB LCD and requires LVGL version >= v8.3.9. If you are using a different type of LCD or an LVGL version that does not meet the requirements, please do not enable this feature. + +#### SquareLine + +To port the SquareLine project (v1.3.x), please refer to [here](#porting-squareline-project) for more detailed information. + +- [Porting](../examples/SquareLine/v8/Porting/): This example demonstrates how to port the SquareLine project. +- [WiFiClock](../examples/SquareLine/v8/WiFiClock/): This example implements a simple Wi-Fi clock and can display weather information. + +### PlatformIO + +- [PlatformIO](../examples/PlatformIO/): This example demonstrates how to use ESP32_Display_Panel in PlatformIO. By default, it is suitable for the **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** development boards. Users need to modify the [boards/ESP-LCD.json](../examples/PlatformIO/boards/ESP-LCD.json) file according to the actual situation. + +## Other Relevant Instructions + +### Configuring Supported Development Boards + +For details on how to configure the supported development boards in the Arduino IDE, see [Board_Instructions - Recommended Configurations in the Arduino IDE](../docs/Board_Instructions.md#recommended-configurations-in-the-arduino-ide). + +### Configuring LVGL + +The functionality and parameters of LVGL can be configured by editing the `lv_conf.h` file, where users can modify macro definitions to update the behavior or default parameters of the driver. Here are some features for configuring LVGL: + +1. When using arduino-esp32 v3.x.x version, LVGL will search for the configuration file in the following order: `current project directory` > `Arduino library directory`. If the configuration file is not found, a compilation error indicating the absence of the configuration file will be prompted. Therefore, users need to ensure that at least one directory contains the `lv_conf.h` file. + +2. If multiple projects need to use the same configuration, users can place the configuration file in the [Arduino library directory](./FAQ.md#where-is-the-directory-for-arduino-libraries), so that all projects can share the same configuration. + +Below are detailed steps for sharing the same LVGL configuration: + +1. Navigate to the [Arduino library directory](./FAQ.md#where-is-the-directory-for-arduino-libraries). + +2. Enter the `lvgl` folder, copy the `lv_conf_template.h` file, and place the copy at the same level as the `lvgl` folder. Then, rename the copied file to `lv_conf.h`. + +3. Finally, the layout of the Arduino library folder should look like this: + + ``` + Arduino + |-libraries + |-lv_conf.h + |-lvgl + |-other_lib_1 + |-other_lib_2 + ``` + +4. Open the `lv_conf.h` file, and change the first `#if 0` to `#if 1` to enable the contents of the file. + +5. Set other configurations according to requirements. Here are some examples of common configuration options for LVGL v8: + + ```c + #define LV_COLOR_DEPTH 16 // Typically use 16-bit color depth (RGB565), + // but can also set it to `32` to support 24-bit color depth (RGB888) + #define LV_COLOR_16_SWAP 0 // If using SPI/QSPI LCD (e.g., ESP32-C3-LCDkit), set this to `1` + #define LV_COLOR_SCREEN_TRANSP 1 + #define LV_MEM_CUSTOM 1 + #define LV_MEMCPY_MEMSET_STD 1 + #define LV_TICK_CUSTOM 1 + #define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR + // Get higher performance but use more SRAM + #define LV_FONT_MONTSERRAT_N 1 // Enable all internal fonts needed (`N` should be replaced with font size) + ``` + +6. For more information, please refer to the [LVGL official documentation](https://docs.lvgl.io/8.3/get-started/platforms/arduino.html). + +### Porting SquareLine Project + +SquareLine Studio (v1.3.x) allows for the rapid design of beautiful UIs through visual editing. If you want to use UI source files exported from SquareLine in the Arduino IDE, you can follow these steps for porting: + +1. First, create a new project in SquareLine Studio. Go to `Create` -> `Arduino`, select `Arduino with TFT-eSPI` as the project template, then configure the LCD properties for the target development board in the `PROJECT SETTINGS` section, such as `Resolution` and `Color depth`. Finally, click the `Create` button to create the project. + +2. For existing projects, you can also click on `File` -> `Project Settings` in the navigation bar to enter the project settings. Then, in the `BOARD PROPERTIES` section, configure `Board Group` as `Arduino` and `Board` as `Arduino with TFT-eSPI`. Additionally, configure the LCD properties for the target development board in the `DISPLAY PROPERTIES` section. Finally, click the `Save` button to save the project settings. + +3. Once the UI design is complete and the export path is configured, click on `Export` -> `Create Template Project` and `Export UI Files` buttons in the menu bar to export the project and UI source files. The layout of the project directory will be as follows: + + ``` + Project + |-libraries + |-lv_conf.h + |-lvgl + |-readme.txt + |-TFT_eSPI + |-ui + |-README.md + |-ui + ``` + +4. Copy the `lv_conf.h`, `lvgl`, and `ui` folders from the `libraries` folder in the project directory to the Arduino library directory. If you need to use a locally installed `lvgl`, skip copying `lvgl` and `lv_conf.h`, then refer to the steps in the [LVGL Configuration](#configuring-lvgl) section to configure LVGL. The layout of the Arduino library folder will be as follows: + + ``` + Arduino + |-libraries + |-ESP32_Display_Panel + |-ESP_Panel_Conf.h (optional) + |-lv_conf.h (optional) + |-lvgl + |-ui + |-other_lib_1 + |-other_lib_2 + ``` diff --git a/docs/How_To_Use_CN.md b/docs/How_To_Use_CN.md new file mode 100644 index 00000000..e96a3413 --- /dev/null +++ b/docs/How_To_Use_CN.md @@ -0,0 +1,356 @@ +# 如何使用 + +* [English Version](./How_To_Use.md) + +## 目录 + +- [如何使用](#如何使用) + - [目录](#目录) + - [基于 ESP-IDF 框架](#基于-esp-idf-框架) + - [依赖项及版本](#依赖项及版本) + - [添加到工程](#添加到工程) + - [配置说明](#配置说明) + - [基于 Arduino IDE](#基于-arduino-ide) + - [依赖项及版本](#依赖项及版本-1) + - [安装库](#安装库) + - [配置说明](#配置说明-1) + - [配置驱动](#配置驱动) + - [使用支持的开发板](#使用支持的开发板) + - [使用自定义开发板](#使用自定义开发板) + - [示例说明](#示例说明) + - [LCD](#lcd) + - [Touch](#touch) + - [Panel](#panel) + - [LVGL v8](#lvgl-v8) + - [SquareLine](#squareline) + - [PlatformIO](#platformio) + - [其他相关说明](#其他相关说明) + - [配置支持的开发板](#配置支持的开发板) + - [配置 LVGL](#配置-lvgl) + - [移植 SquareLine 工程](#移植-squareline-工程) + +## 基于 ESP-IDF 框架 + +### 依赖项及版本 + +| **依赖项** | **版本** | +| ---------- | -------- | +| [esp-idf](https://github.com/espressif/esp-idf) | >= 5.1 | +| [esp32_io_expander](https://components.espressif.com/components/espressif/esp32_io_expander) | ^0.1.0 | + +### 添加到工程 + +ESP32_Display_Panel 已上传到 [Espressif 组件库](https://components.espressif.com/),用户可以通过 `idf.py add-dependency` 命令将它们添加到用户的项目中,例如: + +```bash +idf.py add-dependency "espressif/esp32_display_panel" +``` + +或者,用户也可以创建或修改工程目录下的 `idf_component.yml` 文件,详细内容请参阅 [Espressif 文档 - IDF 组件管理器](https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/tools/idf-component-manager.html)。 + +### 配置说明 + +在使用 esp-idf 开发时,用户可以通过修改 menuconfig 来配置 ESP32_Display_Panel: + +1. 运行命令 `idf.py menuconfig`。 +2. 导航到 `Component config` > `ESP Display Panel Configurations`。 + +## 基于 Arduino IDE + +### 依赖项及版本 + +| **依赖项** | **版本** | +| ---------- | -------- | +| [arduino-esp32](https://github.com/espressif/arduino-esp32) | >= v3.0.0-alpha3 | +| [ESP32_IO_Expander](https://github.com/esp-arduino-libs/ESP32_IO_Expander) | >= 0.1.0 && < 0.2.0 | + +### 安装库 + +关于 ESP32_Display_Panel 库的安装,请参阅 [如何在 Arduino IDE 中安装 ESP32_Display_Panel](./FAQ_CN.md#如何在-arduino-ide-中安装-ESP32_Display_Panel)。 + +### 配置说明 + +下面是关于如何配置 ESP32_Display_Panel 的详细说明,主要包含了 [配置驱动](#配置驱动), [使用支持的开发板](#使用支持的开发板), [使用自定义开发板](#使用自定义开发板) 三个部分,这些均为可选操作并且都是通过指定的头文件进行配置,用户可以根据需要自行选择使用,它们具有如下的特点: + +1. ESP32_Display_Panel 查找配置文件的路径顺序为:`当前工程目录` > `Arduino 库目录` > `ESP32_Display_Panel 目录`。 +2. ESP32_Display_Panel 中所有的示例工程都默认包含了各自所需的配置文件,用户可以直接修改其中的宏定义。 +3. 对于没有配置文件的工程,用户可以将其从 ESP32_Display_Panel 的根目录或者示例工程中复制到自己的工程中。 +4. 如果有多个工程需要使用相同的配置,用户可以将配置文件放在 [Arduino 库目录](./FAQ_CN.md#arduino-库的目录在哪儿)中,这样所有的工程都可以共享相同的配置。 + +> [!WARNING] +> * 同一个目录下可以同时包含 `ESP_Panel_Board_Supported.h` 和 `ESP_Panel_Board_Custom.h` 两种配置文件,但是它们不能同时被使能,即 `ESP_PANEL_USE_SUPPORTED_BOARD` 和 `ESP_PANEL_USE_CUSTOM_BOARD` 最多只能有一个为 `1`。 +> * 如果以上两个配置文件都被没有被使能,那么用户就无法使用 `ESP_Panel` 驱动,只能使用其他独立的设备驱动,如 `ESP_PanelBus`, `ESP_PanelLcd` 等。 +> * 由于这些文件内的配置可能会发生变化,比如新增、删除或重命名,为了保证程序的兼容性,库对它们分别进行了独立的版本管理,并在编译时检查用户当前使用的配置文件与库是否兼容。详细的版本信息以及检查规则可以在文件的末尾处找到。 + +#### 配置驱动 + +ESP32_Display_Panel 会根据 [ESP_Panel_Conf.h](../ESP_Panel_Conf.h) 文件来配置驱动的功能和参数,用户可以通过修改此文件中的宏定义来更新驱动的行为或默认参数。以使能用于调试的 LOG 输出为例,下面是修改后的 `ESP_Panel_Conf.h` 文件的部分内容: + +```c +... +/* Set to 1 if print log message for debug */ +#define ESP_PANEL_ENABLE_LOG (1) // 0/1 +... +``` + +#### 使用支持的开发板 + +ESP32_Display_Panel 会根据 [ESP_Panel_Board_Supported.h](../ESP_Panel_Board_Supported.h) 文件来配置 `ESP_Panel` 成为目标开发板的驱动,用户可以通过修改此文件中的宏定义来选择支持的开发板。以使用 *ESP32-S3-BOX-3* 开发板为例,修改步骤如下: + +1. 设置 `ESP_Panel_Board_Supported.h` 文件中的 `ESP_PANEL_USE_SUPPORTED_BOARD` 宏定义为 `1`。 +2. 根据目标开发板的型号,取消对应的宏定义的注释。 + +下面是修改后的 `ESP_Panel_Board_Supported.h` 文件的部分内容: + +```c +... +/* Set to 1 if using a supported board */ +#define ESP_PANEL_USE_SUPPORTED_BOARD (1) // 0/1 + +#if ESP_PANEL_USE_SUPPORTED_BOARD +... +// #define BOARD_ESP32_C3_LCDKIT +// #define BOARD_ESP32_S3_BOX +#define BOARD_ESP32_S3_BOX_3 +// #define BOARD_ESP32_S3_BOX_3_BETA +... +#endif +``` + +#### 使用自定义开发板 + +ESP32_Display_Panel 会根据 [ESP_Panel_Board_Custom.h](../ESP_Panel_Board_Custom.h) 文件来配置 `ESP_Panel` 成为自定义开发板的驱动,用户需要根据自定义开发板的实际参数对此文件进行修改。以使用 *480x480 RGB ST7701 LCD + I2C GT911 Touch* 的自定义开发板为例,修改步骤如下: + +1. 设置 `ESP_Panel_Board_Custom.h` 文件中的 `ESP_PANEL_USE_CUSTOM_BOARD` 宏定义为 `1`。 +2. 设置 LCD 相关宏定义: + a. 设置 `ESP_PANEL_USE_LCD` 为 `1` + b. 设置 `ESP_PANEL_LCD_WIDTH` 和 `ESP_PANEL_LCD_HEIGHT` 为 `480` + c. 设置 `ESP_PANEL_LCD_BUS_TYPE` 为 `ESP_PANEL_BUS_TYPE_RGB`。 + d. 在 `ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB` 下方的宏定义中设置 LCD 的信号引脚和其他参数。 + e. 根据屏厂提供的初始化命令参数,取消 `ESP_PANEL_LCD_VENDOR_INIT_CMD` 宏定义的注释并修改内容。 + f. 根据需要修改其他 LCD 配置 +2. 设置 Touch 相关宏定义: + a. 设置 `ESP_PANEL_USE_TOUCH` 为 `1` + b. 在 `ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C` 下方的宏定义中设置 Touch 的信号引脚和其他参数。 + c. 根据需要修改其他 Touch 配置 +3. 根据需要使能其他驱动的宏定义,如 `ESP_PANEL_USE_BACKLIGHT`, `ESP_PANEL_USE_EXPANDER` 等。 + +下面是修改后的 `ESP_Panel_Board_Custom.h` 文件的部分内容: + +```c +... +/* Set to 1 if using a custom board */ +#define ESP_PANEL_USE_CUSTOM_BOARD (1) // 0/1 + +/* Set to 1 when using an LCD panel */ +#define ESP_PANEL_USE_LCD (1) // 0/1 + +#if ESP_PANEL_USE_LCD +/** + * LCD Controller Name + */ +#define ESP_PANEL_LCD_NAME ST7701 + +/* LCD resolution in pixels */ +#define ESP_PANEL_LCD_WIDTH (480) +#define ESP_PANEL_LCD_HEIGHT (480) +... +/** + * LCD Bus Type. + */ +#define ESP_PANEL_LCD_BUS_TYPE (ESP_PANEL_BUS_TYPE_RGB) +/** + * LCD Bus Parameters. + * + * Please refer to https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-reference/peripherals/lcd.html and + * https://docs.espressif.com/projects/esp-iot-solution/en/latest/display/lcd/index.html for more details. + * + */ +#if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB +... +#endif /* ESP_PANEL_LCD_BUS_TYPE */ +... +/** + * LCD Vendor Initialization Commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. Please uncomment and change the following macro definitions. Otherwise, the LCD driver + * will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC2, {0x31, 0x05}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xCD, {0x00}), \ + ... + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x11), \ + } +... +#endif /* ESP_PANEL_USE_LCD */ + +/* Set to 1 when using an touch panel */ +#define ESP_PANEL_USE_TOUCH (1) // 0/1 +#if ESP_PANEL_USE_TOUCH +/** + * Touch controller name + */ +#define ESP_PANEL_TOUCH_NAME GT911 +... +/** + * Touch panel bus type + */ +#define ESP_PANEL_TOUCH_BUS_TYPE (ESP_PANEL_BUS_TYPE_I2C) +/* Touch panel bus parameters */ +#if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C +... +#endif /* ESP_PANEL_TOUCH_BUS_TYPE */ +... +#endif /* ESP_PANEL_USE_TOUCH */ +... +#define ESP_PANEL_USE_BACKLIGHT (1) // 0/1 +#if ESP_PANEL_USE_BACKLIGHT +... +#endif /* ESP_PANEL_USE_BACKLIGHT */ +... +#endif /* ESP_PANEL_USE_CUSTOM_BOARD */ +``` + +### 示例说明 + +用户可以在 Arduino IDE 中导航到 `File` > `Examples` > `ESP32_Display_Panel` 来访问它们。如果你找不到 `ESP32_Display_Panel` 选项,请检查库是否已正确安装,并确认选择了一个 ESP 开发板。 + +##### LCD + +以下示例演示了如何使用独立的驱动开发不同接口和不同型号的 LCD,并通过显示彩条进行测试: + +* [SPI](../examples/LCD/SPI/) +* [QSPI](../examples/LCD/QSPI/) +* [Single RGB](../examples/LCD/RGB/) +* [3-wire SPI + RGB](../examples/LCD/3wireSPI_RGB/) + +##### Touch + +以下示例演示了如何使用独立的驱动开发不同接口和不同型号的触摸屏,并通过打印触摸点坐标进行测试: + +* [I2C](../examples/Touch/I2C/) +* [SPI](../examples/Touch/SPI/) + +##### Panel + +以下示例演示了如何使用 `ESP_Panel` 驱动开发内置或自定义的开发板: + +* [Panel Test](../examples/Panel/PanelTest/):此示例通过显示彩条和打印触摸点坐标进行测试。 + +##### LVGL v8 + +关于如何配置 LVGL(v8.3.x),请参阅[此处](#配置-lvgl)以获取更多详细信息。 + +* [Porting](../examples/LVGL/v8/Porting/): 此示例演示了如何移植 LVGL(v8.3.x)。对于 RGB LCD,它还可以启用防撕裂功能。 +* [Rotation](../examples/LVGL/v8/Rotation/): 此示例演示了如何使用 LVGL 来旋转显示屏。 + +> [!WARNING] +> 目前,防撕裂功能仅支持 RGB LCD,并且需要 LVGL 的版本满足 >= v8.3.9,如果使用的是其他类型的 LCD 或不符合要求的 LVGL 版本,请不要启用此功能。 + +##### SquareLine + +​ 要移植 Squarelina 项目(v1.3.x),请参阅[此处](#移植-SquareLine-工程)获取更多详细信息。 + +- [Porting](../examples/SquareLine/v8/Porting): 此示例演示了如何移植 SquareLine 项目。 +- [WiFiClock](../examples/SquareLine/v8/WiFiClock): 此示例实现了一个简单的 Wi-Fi 时钟,并且可以显示天气信息。 + +#### PlatformIO + +- [PlatformIO](../examples/PlatformIO/): 此示例演示了如何在 PlatformIO 中使用 ESP32_Display_Panel。它默认情况下适用于 **ESP32-S3-LCD-EV-Board** and **ESP32-S3-LCD-EV-Board-2** 开发板,用户需要根据实际情况修改 [boards/ESP-LCD.json](../examples/PlatformIO/boards/ESP-LCD.json) 文件。 + +## 其他相关说明 + +### 配置支持的开发板 + +关于如何在 Arduino IDE 中配置支持的开发板,请参考 [Board_Instructions - Recommended Configurations in the Arduino IDE](../docs/Board_Instructions.md#recommended-configurations-in-the-arduino-ide). + +### 配置 LVGL + +LVGL 的功能和参数可以通过编辑 `lv_conf.h` 文件来进行配置,用户可以修改此文件中的宏定义以更新驱动的行为或默认参数。以下是配置 LVGL 的一些特点和步骤: + +1. 在使用 arduino-esp32 v3.x.x 版本时,LVGL 会按照以下路径顺序查找配置文件:`当前工程目录` > `Arduino 库目录`。如果未找到配置文件,编译时会提示未找到配置文件的错误,因此用户需要确保至少有一个目录中包含 `lv_conf.h` 文件。 + +2. 如果多个工程需要使用相同的配置,用户可以将配置文件放在 [Arduino 库目录](./FAQ_CN.md#arduino-库的目录在哪儿)中,这样所有工程都可以共享相同的配置。 + +下面是共享相同 LVGL 配置的详细设置步骤: + +1. 导航到 [Arduino 库目录](./FAQ_CN.md#arduino-库的目录在哪儿)。 + +2. 进入 `lvgl` 文件夹,复制 `lv_conf_template.h` 文件,并将副本放在与 `lvgl` 文件夹同一级的位置,然后将复制的文件重命名为 `lv_conf.h`。 + +3. 最终,Arduino 库文件夹的布局如下所示: + + ``` + Arduino + |-libraries + |-lv_conf.h + |-lvgl + |-other_lib_1 + |-other_lib_2 + ``` + +4. 打开 `lv_conf.h` 文件,并将第一个 `#if 0` 修改为 `#if 1` 以启用文件的内容。 + +5. 根据需求设置其他配置。以下是一些常见的 LVGL v8 版本的配置项示例: + + ```c + #define LV_COLOR_DEPTH 16 // 通常使用 16 位色深(RGB565), + // 但也可以将其设置为 `32` 来支持 24 位色深(RGB888) + #define LV_COLOR_16_SWAP 0 // 如果使用 SPI/QSPI LCD(例如 ESP32-C3-LCDkit),需要将其设置为 `1` + #define LV_COLOR_SCREEN_TRANSP 1 + #define LV_MEM_CUSTOM 1 + #define LV_MEMCPY_MEMSET_STD 1 + #define LV_TICK_CUSTOM 1 + #define LV_ATTRIBUTE_FAST_MEM IRAM_ATTR + // 获取更高的性能但占用更多的 SRAM + #define LV_FONT_MONTSERRAT_N 1 // 启用所有需要使用的内部字体(`N`应该替换为字体大小) + ``` + +6. 获取更多信息,请参考[ LVGL 官方文档](https://docs.lvgl.io/8.3/get-started/platforms/arduino.html)。 + +### 移植 SquareLine 工程 + +SquareLine Studio (v1.4.x) 可以通过图像化编辑的方式快速设计精美的 UI。如果想要在 Arduino IDE 中使用 SquareLine 导出的 UI 源文件,可以按照以下步骤进行移植: + +1. 首先,在 SquareLine Studio 中创建一个新的工程,进入 `Create` -> `Arduino` 一栏,选择 `Arduino with TFT-eSPI` 作为工程模板,然后在右侧的 `PROJECT SETTINGS` 一栏需要根据目标开发板的 LCD 属性进行配置,如 `Resolution` and `Color depth`,最后点击 `Create` 按钮创建工程。 + +2. 对于已有的工程,也可以在导航栏中点击 `File` -> `Project Settings` 按钮进入工程设置,然后在 `BOARD PROPERTIES` 一栏配置 `Board Group` 为 `Arduino`,`Board` 为 `Arduino with TFT-eSPI`,并且根据目标开发板的 LCD 属性在 `DISPLAY PROPERTIES` 一栏进行配置,最后点击 `Save` 按钮保存工程设置。 + +3. 完成 UI 设计并且配置好导出路径后,即可依次点击菜单栏中的 `Export` -> `Create Template Project` 和 `Export UI Files` 按钮导出工程及 UI 源文件,该工程目录的布局如下所示: + + ``` + Project + |-libraries + |-lv_conf.h + |-lvgl + |-readme.txt + |-TFT_eSPI + |-ui + |-README.md + |-ui + ``` + +4. 将工程目录下的 `libraries` 文件夹中的 `lv_conf.h`、`lvgl` 和 `ui` 复制到 Arduino 库目录中。如果需要使用本地安装的 `lvgl`,请跳过复制 `lvgl` 和 `lv_conf.h`,然后参考[步骤](#配置-lvgl)来配置 LVGL。Arduino 库文件夹的布局如下: + + ``` + Arduino + |-libraries + |-ESP32_Display_Panel + |-ESP_Panel_Conf.h (可选) + |-lv_conf.h (可选) + |-lvgl + |-ui + |-other_lib_1 + |-other_lib_2 + ``` diff --git a/docs/_static/block_diagram.drawio b/docs/_static/block_diagram.drawio index b7a581d0..5bec07dd 100644 --- a/docs/_static/block_diagram.drawio +++ b/docs/_static/block_diagram.drawio @@ -1,100 +1,112 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_static/block_diagram.png b/docs/_static/block_diagram.png index 978f230b135c37e4b9a5c5a57f4f5110700d13f4..845df05e7c072276a6f1fe376a21de580a0d8281 100644 GIT binary patch literal 152308 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

)X}Lte;MMPjXv|C`G<1~a!Dz}hbZ*ciujy; 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 ME8j}LssHSM0R&F$Q2+n{ literal 117803 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 |YMmCk7==RGXJ7=;XM7G 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�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, sizeof(lcd_init_cmd)/sizeof(lcd_init_cmd[0])); #endif + // lcd->configAutoReleaseBus(true); // If the "3-wire SPI" interface are sharing pins of the "RGB" interface to + // save GPIOs, please enable this function to release the bus object and pins + // (except CS signal). And then, the "3-wire SPI" interface cannot be used to + // transmit commands any more. + // lcd->configMirrorByCommand(true); // This function is conflict with `configAutoReleaseBus(true)`, please don't + // enable them at the same time lcd->init(); - lcd->reset(); + lcd->reset(); // If the `configAutoReleaseBus(true)` is called, here should not call `reset()` + // to deinit the LCD device lcd->begin(); - lcd->displayOn(); + lcd->displayOn(); // This function is conflict with `configAutoReleaseBus(true)`, please don't + // enable them at the same time #if EXAMPLE_ENABLE_PRINT_LCD_FPS lcd->attachRefreshFinishCallback(onVsyncEndCallback, nullptr); #endif diff --git a/examples/LCD/3wireSPI_RGB/README.md b/examples/LCD/3wireSPI_RGB/README.md index 1fb9cc44..accc64c2 100644 --- a/examples/LCD/3wireSPI_RGB/README.md +++ b/examples/LCD/3wireSPI_RGB/README.md @@ -10,9 +10,9 @@ The example demonstrates how to develop different model LCDs with "3-wire SPI + ## How to use -1. [Configure drivers](../../../README.md#configuring-drivers) if needed. +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. 2. Modify the macros in the example to match the parameters according to your hardware. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -35,4 +35,4 @@ RGB refresh rate: 60 ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/LCD/QSPI/QSPI.ino b/examples/LCD/QSPI/QSPI.ino index 40a8b9ec..64ddc310 100644 --- a/examples/LCD/QSPI/QSPI.ino +++ b/examples/LCD/QSPI/QSPI.ino @@ -11,9 +11,9 @@ * * ## How to use * - * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) if needed. + * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) if needed. * 2. Modify the macros in the example to match the parameters according to your hardware. - * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -49,7 +49,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ @@ -84,7 +84,7 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { diff --git a/examples/LCD/QSPI/README.md b/examples/LCD/QSPI/README.md index cc64e4b5..e92f624b 100644 --- a/examples/LCD/QSPI/README.md +++ b/examples/LCD/QSPI/README.md @@ -10,9 +10,9 @@ The example demonstrates how to develop different model LCDs with QSPI interface ## How to use -1. [Configure drivers](../../../README.md#configuring-drivers) if needed. +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. 2. Modify the macros in the example to match the parameters according to your hardware. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -48,4 +48,4 @@ IDLE loop ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/LCD/RGB/README.md b/examples/LCD/RGB/README.md index e2277170..4a36c469 100644 --- a/examples/LCD/RGB/README.md +++ b/examples/LCD/RGB/README.md @@ -10,9 +10,9 @@ The example demonstrates how to develop different model LCDs with RGB (without 3 ## How to use -1. [Configure drivers](../../../README.md#configuring-drivers) if needed. +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. 2. Modify the macros in the example to match the parameters according to your hardware. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -33,4 +33,4 @@ RGB refresh rate: 31 ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/LCD/RGB/RGB.ino b/examples/LCD/RGB/RGB.ino index 4de1af64..c66fd7ed 100644 --- a/examples/LCD/RGB/RGB.ino +++ b/examples/LCD/RGB/RGB.ino @@ -11,9 +11,9 @@ * * ## How to use * - * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) if needed. + * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) if needed. * 2. Modify the macros in the example to match the parameters according to your hardware. - * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -34,7 +34,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/examples/LCD/SPI/README.md b/examples/LCD/SPI/README.md index f21d0a38..0ab11b8e 100644 --- a/examples/LCD/SPI/README.md +++ b/examples/LCD/SPI/README.md @@ -10,9 +10,9 @@ The example demonstrates how to develop different model LCDs with SPI interface ## How to use -1. [Configure drivers](../../../README.md#configuring-drivers) if needed. +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. 2. Modify the macros in the example to match the parameters according to your hardware. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -48,4 +48,4 @@ IDLE loop ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/LCD/SPI/SPI.ino b/examples/LCD/SPI/SPI.ino index 91582307..64d7c9d9 100644 --- a/examples/LCD/SPI/SPI.ino +++ b/examples/LCD/SPI/SPI.ino @@ -11,9 +11,9 @@ * * ## How to use * - * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) if needed. + * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) if needed. * 2. Modify the macros in the example to match the parameters according to your hardware. - * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -49,7 +49,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ @@ -86,7 +86,7 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { @@ -113,7 +113,6 @@ const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { #define EXAMPLE_LCD_PIN_NUM_RST (-1) // Set to -1 if not used #define EXAMPLE_LCD_PIN_NUM_BK_LIGHT (45) // Set to -1 if not used #define EXAMPLE_LCD_BK_LIGHT_ON_LEVEL (1) - #define EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL !EXAMPLE_LCD_BK_LIGHT_ON_LEVEL /* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ diff --git a/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h b/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h +++ b/examples/LVGL/v8/Porting/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h b/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h +++ b/examples/LVGL/v8/Porting/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/examples/LVGL/v8/Porting/Porting.ino b/examples/LVGL/v8/Porting/Porting.ino index b2433d13..e7728911 100644 --- a/examples/LVGL/v8/Porting/Porting.ino +++ b/examples/LVGL/v8/Porting/Porting.ino @@ -15,17 +15,17 @@ * * 1. For **ESP32_Display_Panel**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) to configure drivers if needed. - * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-supported-development-boards) to configure it. - * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-custom-development-boards) to configure it. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-supported-development-boards) to configure it. + * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-custom-development-boards) to configure it. * * 2. For **lvgl**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-lvgl) to add *lv_conf.h* file and change the configurations. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. * - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. * * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported - * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -44,7 +44,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/examples/LVGL/v8/Porting/README.md b/examples/LVGL/v8/Porting/README.md index 31c868a8..e482d7d8 100644 --- a/examples/LVGL/v8/Porting/README.md +++ b/examples/LVGL/v8/Porting/README.md @@ -12,16 +12,16 @@ Follow the steps below to configure: 1. For **ESP32_Display_Panel**: - - Follow the [steps](../../../../README.md#configuring-drivers) to configure drivers if needed. - - If using a supported development board, follow the [steps](../../../../README.md#using-supported-development-boards) to configure it. - - If using a custom board, follow the [steps](../../../../README.md#using-custom-development-boards) to configure it. + - Follow the [steps](../../../../docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + - If using a supported development board, follow the [steps](../../../../docs/How_To_Use.md#using-supported-development-boards) to configure it. + - If using a custom board, follow the [steps](../../../../docs/How_To_Use.md#using-custom-development-boards) to configure it. 2. For **lvgl**: - - Follow the [steps](../../../../README.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. + - Follow the [steps](../../../../docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -40,4 +40,4 @@ IDLE loop ## Troubleshooting -Please check the [FAQ](../../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/LVGL/v8/Porting/lvgl_port_v8.cpp b/examples/LVGL/v8/Porting/lvgl_port_v8.cpp index 08cc35ab..cfd9f29d 100644 --- a/examples/LVGL/v8/Porting/lvgl_port_v8.cpp +++ b/examples/LVGL/v8/Porting/lvgl_port_v8.cpp @@ -198,7 +198,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Reset flag */ drv->full_refresh = 0; - // Roate and copy data from the whole screen LVGL's buffer to the next frame buffer + // Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer next_fb = flush_get_next_buf(lcd); rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); diff --git a/examples/LVGL/v8/Porting/lvgl_port_v8.h b/examples/LVGL/v8/Porting/lvgl_port_v8.h index 41014e0a..c7b7cdf2 100644 --- a/examples/LVGL/v8/Porting/lvgl_port_v8.h +++ b/examples/LVGL/v8/Porting/lvgl_port_v8.h @@ -83,7 +83,7 @@ #define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) /** * When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported. - * But users can set the rotation degree(0/90/180/270) here, but this funciton will extremely reduce FPS. + * But users can set the rotation degree(0/90/180/270) here, but this function will extremely reduce FPS. * So it is recommended to be used when using a low resolution display. * * Set the rotation degree: @@ -151,7 +151,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp); * * @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_lock(int timeout_ms); @@ -159,7 +159,7 @@ bool lvgl_port_lock(int timeout_ms); * @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the * `lvgl_port_lock()` function should be called before. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_unlock(void); diff --git a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h +++ b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h +++ b/examples/LVGL/v8/Rotation/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/examples/LVGL/v8/Rotation/README.md b/examples/LVGL/v8/Rotation/README.md index 200cd737..e2212da8 100644 --- a/examples/LVGL/v8/Rotation/README.md +++ b/examples/LVGL/v8/Rotation/README.md @@ -12,16 +12,16 @@ Then follow the steps below to configure: 1. For **ESP32_Display_Panel**: - - Follow the [steps](../../../../README.md#configuring-drivers) to configure drivers if needed. - - If using a supported development board, follow the [steps](../../../../README.md#using-supported-development-boards) to configure it. - - If using a custom board, follow the [steps](../../../../README.md#using-custom-development-boards) to configure it. + - Follow the [steps](../../../../docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + - If using a supported development board, follow the [steps](../../../../docs/How_To_Use.md#using-supported-development-boards) to configure it. + - If using a custom board, follow the [steps](../../../../docs/How_To_Use.md#using-custom-development-boards) to configure it. 2. For **lvgl**: - - Follow the [steps](../../../../README.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. + - Follow the [steps](../../../../docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -40,4 +40,4 @@ IDLE loop ## Troubleshooting -Please check the [FAQ](../../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/LVGL/v8/Rotation/Rotation.ino b/examples/LVGL/v8/Rotation/Rotation.ino index 31095bb6..7dee3f08 100644 --- a/examples/LVGL/v8/Rotation/Rotation.ino +++ b/examples/LVGL/v8/Rotation/Rotation.ino @@ -13,17 +13,17 @@ * * 1. For **ESP32_Display_Panel**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) to configure drivers if needed. - * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-supported-development-boards) to configure it. - * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-custom-development-boards) to configure it. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-supported-development-boards) to configure it. + * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-custom-development-boards) to configure it. * * 2. For **lvgl**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-lvgl) to add *lv_conf.h* file and change the configurations. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. * - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. * * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported - * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -42,7 +42,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp b/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp index 08cc35ab..cfd9f29d 100644 --- a/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp +++ b/examples/LVGL/v8/Rotation/lvgl_port_v8.cpp @@ -198,7 +198,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Reset flag */ drv->full_refresh = 0; - // Roate and copy data from the whole screen LVGL's buffer to the next frame buffer + // Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer next_fb = flush_get_next_buf(lcd); rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); diff --git a/examples/LVGL/v8/Rotation/lvgl_port_v8.h b/examples/LVGL/v8/Rotation/lvgl_port_v8.h index 41014e0a..c7b7cdf2 100644 --- a/examples/LVGL/v8/Rotation/lvgl_port_v8.h +++ b/examples/LVGL/v8/Rotation/lvgl_port_v8.h @@ -83,7 +83,7 @@ #define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) /** * When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported. - * But users can set the rotation degree(0/90/180/270) here, but this funciton will extremely reduce FPS. + * But users can set the rotation degree(0/90/180/270) here, but this function will extremely reduce FPS. * So it is recommended to be used when using a low resolution display. * * Set the rotation degree: @@ -151,7 +151,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp); * * @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_lock(int timeout_ms); @@ -159,7 +159,7 @@ bool lvgl_port_lock(int timeout_ms); * @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the * `lvgl_port_lock()` function should be called before. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_unlock(void); diff --git a/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h b/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h +++ b/examples/Panel/PanelTest/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h b/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h +++ b/examples/Panel/PanelTest/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/examples/Panel/PanelTest/PanelTest.ino b/examples/Panel/PanelTest/PanelTest.ino index 7c086272..30110db3 100644 --- a/examples/Panel/PanelTest/PanelTest.ino +++ b/examples/Panel/PanelTest/PanelTest.ino @@ -9,12 +9,12 @@ * * 1. For **ESP32_Display_Panel**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) to configure drivers if needed. - * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-supported-development-boards) to configure it. - * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-custom-development-boards) to configure it. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-supported-development-boards) to configure it. + * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-custom-development-boards) to configure it. * * 2. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported - * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 3. Verify and upload the example to your ESP board. * * ## Serial Output @@ -36,7 +36,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ @@ -61,7 +61,7 @@ IRAM_ATTR bool onRefreshFinishCallback(void *user_data) } #endif -#if EXAMPLE_TOUCH_ENABLE_ATTACH_CALLBACK +#if TEST_TOUCH_ENABLE_ATTACH_CALLBACK && (ESP_PANEL_TOUCH_IO_INT >= 0) IRAM_ATTR bool onTouchInterruptCallback(void *user_data) { esp_rom_printf("Touch interrupt callback\n"); @@ -107,15 +107,15 @@ void setup() backlight->on(); } - if (touch != nullptr) { + if ((touch != nullptr) && touch->isInterruptEnabled()) { #if EXAMPLE_TOUCH_ENABLE_ATTACH_CALLBACK - touch->attachInterruptCallback(onTouchInterruptCallback, NULL); + touch->attachInterruptCallback(onTouchInterruptCallback, NULL); #endif + Serial.println("Reading touch_device point..."); } else { Serial.println("Touch is not available"); + Serial.println("Panel test example end"); } - - Serial.println("Panel test example end"); } void loop() diff --git a/examples/Panel/PanelTest/README.md b/examples/Panel/PanelTest/README.md index 6011e46e..6ba7d007 100644 --- a/examples/Panel/PanelTest/README.md +++ b/examples/Panel/PanelTest/README.md @@ -8,11 +8,11 @@ Follow the steps below to configure: 1. For **ESP32_Display_Panel**: - - Follow the [steps](../../../README.md#configuring-drivers) to configure drivers if needed. - - If using a supported development board, follow the [steps](../../../README.md#using-supported-development-boards) to configure it. - - If using a custom board, follow the [steps](../../../README.md#using-custom-development-boards) to configure it. + - Follow the [steps](../../../docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + - If using a supported development board, follow the [steps](../../../docs/How_To_Use.md#using-supported-development-boards) to configure it. + - If using a custom board, follow the [steps](../../../docs/How_To_Use.md#using-custom-development-boards) to configure it. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -34,4 +34,4 @@ Touch point(3): x 371, y 317, strength 24 ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/PlatformIO/README.md b/examples/PlatformIO/README.md index fac6aa2a..c91f6302 100644 --- a/examples/PlatformIO/README.md +++ b/examples/PlatformIO/README.md @@ -10,16 +10,16 @@ Follow the steps below to configure: 1. For **ESP32_Display_Panel**: - - Follow the [steps](../../README.md#configuring-drivers) to configure drivers if needed. - - If using a supported development board, follow the [steps](../../README.md#using-supported-development-boards) to configure it. - - If using a custom board, follow the [steps](../../README.md#using-custom-development-boards) to configure it. + - Follow the [steps](../../docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + - If using a supported development board, follow the [steps](../../docs/How_To_Use.md#using-supported-development-boards) to configure it. + - If using a custom board, follow the [steps](../../docs/How_To_Use.md#using-custom-development-boards) to configure it. 2. For **lvgl**: - Follow the [steps](../../README.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. - - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. + - Modify the macros in the [lvgl_port_v8.h](./src/lvgl_port_v8.h) file to configure the LVGL porting parameters. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -38,4 +38,4 @@ IDLE loop ## Troubleshooting -Please check the [FAQ](../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/PlatformIO/platformio.ini b/examples/PlatformIO/platformio.ini index 810db2cc..fd3b4b11 100644 --- a/examples/PlatformIO/platformio.ini +++ b/examples/PlatformIO/platformio.ini @@ -19,4 +19,3 @@ lib_deps = https://github.com/esp-arduino-libs/ESP32_Display_Panel.git https://github.com/esp-arduino-libs/ESP32_IO_Expander.git https://github.com/lvgl/lvgl.git#release/v8.3 - diff --git a/examples/PlatformIO/src/ESP_Panel_Board_Custom.h b/examples/PlatformIO/src/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/examples/PlatformIO/src/ESP_Panel_Board_Custom.h +++ b/examples/PlatformIO/src/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/PlatformIO/src/ESP_Panel_Board_Supported.h b/examples/PlatformIO/src/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/examples/PlatformIO/src/ESP_Panel_Board_Supported.h +++ b/examples/PlatformIO/src/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/examples/PlatformIO/src/lvgl_port_v8.cpp b/examples/PlatformIO/src/lvgl_port_v8.cpp index 08cc35ab..cfd9f29d 100644 --- a/examples/PlatformIO/src/lvgl_port_v8.cpp +++ b/examples/PlatformIO/src/lvgl_port_v8.cpp @@ -198,7 +198,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Reset flag */ drv->full_refresh = 0; - // Roate and copy data from the whole screen LVGL's buffer to the next frame buffer + // Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer next_fb = flush_get_next_buf(lcd); rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); diff --git a/examples/PlatformIO/src/lvgl_port_v8.h b/examples/PlatformIO/src/lvgl_port_v8.h index 0a01155d..b0e25ee1 100644 --- a/examples/PlatformIO/src/lvgl_port_v8.h +++ b/examples/PlatformIO/src/lvgl_port_v8.h @@ -80,7 +80,7 @@ #define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) /** * When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported. - * But users can set the rotation degree(0/90/180/270) here, but this funciton will extremely reduce FPS. + * But users can set the rotation degree(0/90/180/270) here, but this function will extremely reduce FPS. * So it is recommended to be used when using a low resolution display. * * Set the rotation degree: @@ -148,7 +148,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp); * * @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_lock(int timeout_ms); @@ -156,7 +156,7 @@ bool lvgl_port_lock(int timeout_ms); * @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the * `lvgl_port_lock()` function should be called before. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_unlock(void); diff --git a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h +++ b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h +++ b/examples/SquareLine/v8/Porting/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/examples/SquareLine/v8/Porting/Porting.ino b/examples/SquareLine/v8/Porting/Porting.ino index b71d1d53..426a38c8 100644 --- a/examples/SquareLine/v8/Porting/Porting.ino +++ b/examples/SquareLine/v8/Porting/Porting.ino @@ -13,17 +13,17 @@ * * 1. For **ESP32_Display_Panel**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) to configure drivers if needed. - * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-supported-development-boards) to configure it. - * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-custom-development-boards) to configure it. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) to configure drivers if needed. + * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-supported-development-boards) to configure it. + * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-custom-development-boards) to configure it. * * 2. For **lvgl**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-lvgl) to add *lv_conf.h* file and change the configurations. + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. * - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. * * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported - * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * boards, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -43,7 +43,7 @@ * * ## Troubleshooting * - * Please first check [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) for troubleshooting. If you still cannot solve the problem, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please first check [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) for troubleshooting. If you still cannot solve the problem, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/examples/SquareLine/v8/Porting/README.md b/examples/SquareLine/v8/Porting/README.md index afe487b5..84eb3f41 100644 --- a/examples/SquareLine/v8/Porting/README.md +++ b/examples/SquareLine/v8/Porting/README.md @@ -12,17 +12,17 @@ Then follow the steps below to configure: 1. For **ESP32_Display_Panel**: - - [Configure drivers](../../../../README.md#configuring-drivers) if needed. - - If using a supported development board, follow the [steps](../../../../README.md#using-supported-development-boards) to configure it. - - If using a custom board, follow the [steps](../../../../README.md#using-custom-development-boards) to configure it. + - [Configure drivers](../../../../docs/How_To_Use.md#configuring-drivers) if needed. + - If using a supported development board, follow the [steps](../../../../docs/How_To_Use.md#using-supported-development-boards) to configure it. + - If using a custom board, follow the [steps](../../../../docs/How_To_Use.md#using-custom-development-boards) to configure it. 2. For **lvgl**: - - Follow the [steps](../../../../README.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. + - Follow the [steps](../../../../docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. - Modify the macros in the [lvgl_port_v8.h](./lvgl_port_v8.h) file to configure the LVGL porting parameters. 3. To directly use the example, please copy the [ui](./libraries/ui/) folder from `libraries` to [Arduino Library directory](../../../../README.md#where-is-the-directory-for-arduino-libraries). What's more, you can follow the [steps](../../../../README.md#porting-squareline-project) to port your own **SquareLine** project. -4. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../../README.md#configuring-supported-development-boards) +4. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters. For supported boards, please refter to [Configuring Supported Development Boards](../../../../docs/How_To_Use.md#configuring-supported-development-boards) 5. Verify and upload the example to your ESP board. ## Serial Output @@ -42,4 +42,4 @@ IDLE loop ## Troubleshooting -Please check the [FAQ](../../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp b/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp index 08cc35ab..cfd9f29d 100644 --- a/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp +++ b/examples/SquareLine/v8/Porting/lvgl_port_v8.cpp @@ -198,7 +198,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Reset flag */ drv->full_refresh = 0; - // Roate and copy data from the whole screen LVGL's buffer to the next frame buffer + // Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer next_fb = flush_get_next_buf(lcd); rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); diff --git a/examples/SquareLine/v8/Porting/lvgl_port_v8.h b/examples/SquareLine/v8/Porting/lvgl_port_v8.h index 41014e0a..c7b7cdf2 100644 --- a/examples/SquareLine/v8/Porting/lvgl_port_v8.h +++ b/examples/SquareLine/v8/Porting/lvgl_port_v8.h @@ -83,7 +83,7 @@ #define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) /** * When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported. - * But users can set the rotation degree(0/90/180/270) here, but this funciton will extremely reduce FPS. + * But users can set the rotation degree(0/90/180/270) here, but this function will extremely reduce FPS. * So it is recommended to be used when using a low resolution display. * * Set the rotation degree: @@ -151,7 +151,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp); * * @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_lock(int timeout_ms); @@ -159,7 +159,7 @@ bool lvgl_port_lock(int timeout_ms); * @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the * `lvgl_port_lock()` function should be called before. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_unlock(void); diff --git a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h index 5ed0f923..3ae31b50 100644 --- a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h +++ b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Custom.h @@ -173,21 +173,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -379,7 +381,7 @@ */ #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 3 #endif /* ESP_PANEL_USE_CUSTOM_BOARD */ diff --git a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h index 4c58e5a4..603e59bd 100644 --- a/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h +++ b/examples/SquareLine/v8/WiFiClock/ESP_Panel_Board_Supported.h @@ -26,9 +26,9 @@ * - BOARD_ESP32_S3_BOX_LITE (ESP32-S3-Box-Lite): https://github.com/espressif/esp-box/tree/master * - BOARD_ESP32_S3_EYE (ESP32-S3-EYE): https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md * - BOARD_ESP32_S3_KORVO_2 (ESP32-S3-Korvo-2): https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html - * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD (ESP32-S3-LCD-EV-Board(v1.1-v1.4)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 (ESP32-S3-LCD-EV-Board(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html - * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + * - BOARD_ESP32_S3_LCD_EV_BOARD_2 (ESP32-S3-LCD-EV-Board-2(v1.1-v1.4))): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html * - BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 (ESP32-S3-LCD-EV-Board-2(v1.5)): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html * - BOARD_ESP32_S3_USB_OTG (ESP32-S3-USB-OTG): https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html * @@ -56,9 +56,9 @@ /* * M5Stack (https://m5stack.com/): * - * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/zh_CN/core/core2 - * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/zh_CN/core/M5Dial - * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/zh_CN/core/CoreS3 + * - BOARD_M5STACK_M5CORE2 (M5STACK_M5CORE2): https://docs.m5stack.com/en/core/core2 + * - BOARD_M5STACK_M5DIAL (M5STACK_M5DIAL): https://docs.m5stack.com/en/core/M5Dial + * - BOARD_M5STACK_M5CORES3 (M5STACK_M5CORES3): https://docs.m5stack.com/en/core/CoreS3 */ // #define BOARD_M5STACK_M5CORE2 // #define BOARD_M5STACK_M5DIAL @@ -102,6 +102,6 @@ */ #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 1 #endif diff --git a/examples/SquareLine/v8/WiFiClock/README.md b/examples/SquareLine/v8/WiFiClock/README.md index 88ca9f57..ffd8a844 100644 --- a/examples/SquareLine/v8/WiFiClock/README.md +++ b/examples/SquareLine/v8/WiFiClock/README.md @@ -16,15 +16,15 @@ Then follow the steps below to configure the example. 1. For **ESP32_Display_Panel**: - - [Configure drivers](../../../../README.md#configuring-drivers) if needed. - - If using a supported development board, follow the [steps](../../../../README.md#using-supported-development-boards) to configure it. - - If using a custom board, follow the [steps](../../../../README.md#using-custom-development-boards) to configure it. + - [Configure drivers](../../../../docs/How_To_Use.md#configuring-drivers) if needed. + - If using a supported development board, follow the [steps](../../../../docs/How_To_Use.md#using-supported-development-boards) to configure it. + - If using a custom board, follow the [steps](../../../../docs/How_To_Use.md#using-custom-development-boards) to configure it. 2. Copy the [ui](./libraries/ui/) folder from `libraries` to [Arduino Library directory](../../../../README.md#where-is-the-directory-for-arduino-libraries). 3. For **lvgl**: - - Follow the [steps](../../../../README.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. Additionally, set the following configurations to `1`: + - Follow the [steps](../../../../docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* file and change the configurations. Additionally, set the following configurations to `1`: - `LV_FONT_MONTSERRAT_12` - `LV_FONT_MONTSERRAT_14` @@ -43,7 +43,7 @@ Then follow the steps below to configure the example. - Fill the name of the city for which need to obtain weather information (such as `Shanghai`) in the macro definition `WEATHER_CITY`. 6. To obtain and calibrate time information after connecting to Wi-Fi, Please correctly fill in your time zone within the macro `TIMEZONE_OFFSET` (such as `CST-8`). -7. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../../README.md#configuring-supported-development-boards) +7. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../../docs/How_To_Use.md#configuring-supported-development-boards) 8. Verify and upload the example to your ESP board. ## Serial Output @@ -65,4 +65,4 @@ wifi_list_switch: false ## Troubleshooting -Please check the [FAQ](../../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](../../../../README.md/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/SquareLine/v8/WiFiClock/WiFiClock.ino b/examples/SquareLine/v8/WiFiClock/WiFiClock.ino index 4cc6fb9f..6bf7af83 100644 --- a/examples/SquareLine/v8/WiFiClock/WiFiClock.ino +++ b/examples/SquareLine/v8/WiFiClock/WiFiClock.ino @@ -17,15 +17,15 @@ * * 1. For **ESP32_Display_Panel**: * - * - [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) if needed. - * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-supported-development-boards) to configure it. - * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#using-custom-development-boards) to configure it. + * - [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) if needed. + * - If using a supported development board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-supported-development-boards) to configure it. + * - If using a custom board, follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#using-custom-development-boards) to configure it. * * 2. Copy the [ui](./libraries/ui/) folder from `libraries` to [Arduino Library directory](https://github.com/esp-arduino-libs/ESP32_Display_Panel#where-is-the-directory-for-arduino-libraries). * * 3. For **lvgl**: * - * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-lvgl) to add *lv_conf.h* + * - Follow the [steps](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-lvgl) to add *lv_conf.h* * file and change the configurations. Additionally, set the following configurations to `1`: * * - `LV_FONT_MONTSERRAT_12` @@ -45,7 +45,7 @@ * - Fill the name of the city for which need to obtain weather information (such as `Shanghai`) in the macro definition `WEATHER_CITY`. * * 6. To obtain and calibrate time information after connecting to Wi-Fi, Please correctly fill in your time zone within the macro `TIMEZONE_OFFSET` (such as `CST-8`). - * 7. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * 7. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 8. Verify and upload the example to your ESP board. * * ## Serial Output @@ -67,7 +67,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp index 08cc35ab..cfd9f29d 100644 --- a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp +++ b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.cpp @@ -198,7 +198,7 @@ static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t /* Reset flag */ drv->full_refresh = 0; - // Roate and copy data from the whole screen LVGL's buffer to the next frame buffer + // Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer next_fb = flush_get_next_buf(lcd); rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); diff --git a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.h b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.h index 41014e0a..c7b7cdf2 100644 --- a/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.h +++ b/examples/SquareLine/v8/WiFiClock/lvgl_port_v8.h @@ -83,7 +83,7 @@ #define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) /** * When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported. - * But users can set the rotation degree(0/90/180/270) here, but this funciton will extremely reduce FPS. + * But users can set the rotation degree(0/90/180/270) here, but this function will extremely reduce FPS. * So it is recommended to be used when using a low resolution display. * * Set the rotation degree: @@ -151,7 +151,7 @@ bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp); * * @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_lock(int timeout_ms); @@ -159,7 +159,7 @@ bool lvgl_port_lock(int timeout_ms); * @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the * `lvgl_port_lock()` function should be called before. * - * @return ture if success, otherwise false + * @return true if success, otherwise false */ bool lvgl_port_unlock(void); diff --git a/examples/Touch/I2C/I2C.ino b/examples/Touch/I2C/I2C.ino index 903f64b5..fdf95e1e 100644 --- a/examples/Touch/I2C/I2C.ino +++ b/examples/Touch/I2C/I2C.ino @@ -11,9 +11,9 @@ * * ## How to use * - * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) if needed. + * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) if needed. * 2. Modify the macros in the example to match the parameters according to your hardware. - * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -34,7 +34,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/examples/Touch/I2C/README.md b/examples/Touch/I2C/README.md index 8e50f6cc..f7962021 100644 --- a/examples/Touch/I2C/README.md +++ b/examples/Touch/I2C/README.md @@ -10,9 +10,9 @@ The example demonstrates how to develop different model touches with I2C interfa ## How to use -1. [Configure drivers](../../../README.md#configuring-drivers) if needed. +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. 2. Modify the macros in the example to match the parameters according to your hardware. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -33,4 +33,4 @@ Touch point(4): x 353, y 391, strength 35 ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/Touch/SPI/README.md b/examples/Touch/SPI/README.md index 2b077674..7a15edaa 100644 --- a/examples/Touch/SPI/README.md +++ b/examples/Touch/SPI/README.md @@ -10,9 +10,9 @@ The example demonstrates how to develop different model touches with SPI interfa ## How to use -1. [Configure drivers](../../../README.md#configuring-drivers) if needed. +1. [Configure drivers](../../../docs/How_To_Use.md#configuring-drivers) if needed. 2. Modify the macros in the example to match the parameters according to your hardware. -3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../README.md#configuring-supported-development-boards) +3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](../../../docs/How_To_Use.md#configuring-supported-development-boards) 4. Verify and upload the example to your ESP board. ## Serial Output @@ -33,4 +33,4 @@ Touch point(4): x 353, y 391, strength 35 ## Troubleshooting -Please check the [FAQ](../../../README.md#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. +Please check the [FAQ](../../../docs/FAQ.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. diff --git a/examples/Touch/SPI/SPI.ino b/examples/Touch/SPI/SPI.ino index 63f3b69a..7e204c42 100644 --- a/examples/Touch/SPI/SPI.ino +++ b/examples/Touch/SPI/SPI.ino @@ -11,9 +11,9 @@ * * ## How to use * - * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-drivers) if needed. + * 1. [Configure drivers](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-drivers) if needed. * 2. Modify the macros in the example to match the parameters according to your hardware. - * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel#configuring-supported-development-boards) + * 3. Navigate to the `Tools` menu in the Arduino IDE to choose a ESP board and configure its parameters, please refter to [Configuring Supported Development Boards](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/How_To_Use.md#configuring-supported-development-boards) * 4. Verify and upload the example to your ESP board. * * ## Serial Output @@ -34,7 +34,7 @@ * * ## Troubleshooting * - * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel#faq) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. + * Please check the [FAQ](https://github.com/esp-arduino-libs/ESP32_Display_Panel/docs/faq.md) first to see if the same question exists. If not, please create a [Github issue](https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues). We will get back to you as soon as possible. * */ diff --git a/idf_component.yml b/idf_component.yml new file mode 100644 index 00000000..e39a12ec --- /dev/null +++ b/idf_component.yml @@ -0,0 +1,10 @@ +version: "0.2.0" +description: ESP32_Display_Panel is an library designed for ESP SoCs to drive display panels and facilitate rapid GUI development. +url: https://github.com/esp-arduino-libs/ESP32_Display_Panel +repository: https://github.com/esp-arduino-libs/ESP32_Display_Panel.git +issues: https://github.com/esp-arduino-libs/ESP32_Display_Panel/issues +dependencies: + idf: ">=5.1" + espressif/esp32_io_expander: + version: "^0.1.0" + public: true diff --git a/library.properties b/library.properties index 932480c0..97ee5217 100644 --- a/library.properties +++ b/library.properties @@ -1,11 +1,11 @@ name=ESP32_Display_Panel -version=0.1.9 +version=0.2.0 author=espressif maintainer=espressif -sentence=ESP32_Display_Panel is an Arduino library designed for ESP SoCs to drive display panels and facilitate rapid GUI development. +sentence=ESP32_Display_Panel is an library designed for ESP SoCs to drive display panels and facilitate rapid GUI development. paragraph=Currently supported boards:ESP32-C3-LCDkit,ESP32-S3-BOX,ESP32-S3-BOX-3,ESP32-S3-BOX-3B,ESP32-S3-BOX-3(beta),ESP32-S3-BOX-Lite,ESP32-S3-EYE,ESP32-S3-Korvo-2,ESP32-S3-LCD-EV-Board,ESP32-S3-LCD-EV-Board-2,ESP32-S3-USB-OTG,M5STACK-M5CORE2,M5STACK-M5DIAL,M5STACK-M5CORES3,ESP32-4848S040C_I_Y_3,ESP32-S3-Touch-LCD-4.3,ESP32-S3-Touch-LCD-1.85,ESP32-S3-Touch-LCD-2.1. Currently supported devices: Bus,LCD,Touch,Backlight,IO expander. Currently supported Bus: I2C,SPI,QSPI,3-wire SPI + RGB. Currently supported LCD controllers: EK9716B,GC9A01,GC9B71,GC9503,ILI9341,NV3022B,ST7262,ST7701,ST7789,ST7796,ST77916,ST77922. Currently supported Touch controllers: CST816S,FT5x06,GT1151,GT911,ST7123,TT21100,XPT2046. category=Other architectures=esp32 url=https://github.com/esp-arduino-libs/ESP32_Display_Panel includes=ESP_Panel_Library.h -depends=ESP32_IO_Expander (>=0.0.1 && <0.1.0) +depends=ESP32_IO_Expander (>=0.1.0 && <0.2.0) diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..8026cc74 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,42 @@ +[pytest] +# exclude examples/ota/simple_ota_example/pytest_simple_ota.py +norecursedirs = examples/ota/* +# only the files with prefix `pytest_` would be recognized as pytest test scripts. +python_files = pytest_*.py + +# set traceback to "short" to prevent the overwhelming tracebacks +addopts = + -s + --embedded-services esp,idf + --tb short + --skip-check-coredump y + +# ignore PytestExperimentalApiWarning for record_xml_attribute +filterwarnings = + ignore::_pytest.warning_types.PytestExperimentalApiWarning + + +markers = + # target markers + target: target chip name (--target) + # env markers + env: target test env name (--env) + # config markers + config: choose specific bins built by `sdkconfig.ci.` + # app_path markers + app_path: choose specific app_path, [/build_xxx] + + +# log related +log_cli = True +log_cli_level = INFO +log_cli_format = %(asctime)s %(levelname)s %(message)s +log_cli_date_format = %Y-%m-%d %H:%M:%S + +# junit related +junit_family = xunit1 + + +## log all to `system-out` when case fail +junit_logging = stdout +junit_log_passing_tests = False diff --git a/src/ESP_Panel.cpp b/src/ESP_Panel.cpp index a8ee00be..b4eaaf35 100644 --- a/src/ESP_Panel.cpp +++ b/src/ESP_Panel.cpp @@ -17,7 +17,7 @@ using namespace std; * Macros for adding host of bus * */ -#define _ADD_HOST(name, host, config, id) host.addHost##name(config, id) +#define _ADD_HOST(name, host, config, id) host->addHost##name(config, id) #define ADD_HOST(name, host, config, id) _ADD_HOST(name, host, config, id) /** * Macros for creating panel bus @@ -58,10 +58,11 @@ ESP_Panel::ESP_Panel(): _is_initialed(false), _use_external_expander(false), _lcd_bus_ptr(nullptr), - _lcd_ptr(nullptr), _touch_bus_ptr(nullptr), + _lcd_ptr(nullptr), _touch_ptr(nullptr), _backlight_ptr(nullptr), + _host_ptr(nullptr), _expander_ptr(nullptr) { } @@ -98,13 +99,13 @@ bool ESP_Panel::init(void) { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); - ESP_PanelHost host; + shared_ptr host_ptr = make_shared(); shared_ptr lcd_bus_ptr = nullptr; shared_ptr lcd_ptr = nullptr; shared_ptr touch_bus_ptr = nullptr; shared_ptr touch_ptr = nullptr; - shared_ptr expander_ptr = _expander_ptr; shared_ptr backlight_ptr = nullptr; + shared_ptr expander_ptr = _expander_ptr; ESP_LOGD(TAG, "Panel init start"); @@ -310,7 +311,7 @@ bool ESP_Panel::init(void) #else /* For non-RGB LCD, should use `ADD_HOST()` to init host when `ESP_PANEL_LCD_BUS_SKIP_INIT_HOST` enabled */ #if !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST - ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(ESP_PANEL_LCD_BUS_NAME, host, lcd_bus_host_config, ESP_PANEL_LCD_BUS_HOST), + ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(ESP_PANEL_LCD_BUS_NAME, host_ptr, lcd_bus_host_config, ESP_PANEL_LCD_BUS_HOST), false, "Add host failed"); #endif lcd_bus_ptr = CREATE_BUS_SKIP_HOST(ESP_PANEL_LCD_BUS_NAME, lcd_panel_io_config, ESP_PANEL_LCD_BUS_HOST); @@ -403,7 +404,7 @@ bool ESP_Panel::init(void) }; #if !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST - ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(ESP_PANEL_TOUCH_BUS_NAME, host, touch_host_config, ESP_PANEL_TOUCH_BUS_HOST), + ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(ESP_PANEL_TOUCH_BUS_NAME, host_ptr, touch_host_config, ESP_PANEL_TOUCH_BUS_HOST), false, "Add host failed"); #endif @@ -440,7 +441,7 @@ bool ESP_Panel::init(void) }, .clk_flags = I2C_SCLK_SRC_FLAG_FOR_NOMAL, }; - ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(I2C, host, expander_host_config, ESP_PANEL_EXPANDER_HOST), false, + ESP_PANEL_CHECK_FALSE_RET(ADD_HOST(I2C, host_ptr, expander_host_config, ESP_PANEL_EXPANDER_HOST), false, "Add host failed"); #endif expander_ptr = CREATE_EXPANDER(ESP_PANEL_EXPANDER_NAME, ESP_PANEL_EXPANDER_HOST, ESP_PANEL_EXPANDER_I2C_ADDRESS); @@ -448,7 +449,7 @@ bool ESP_Panel::init(void) #endif /* ESP_PANEL_USE_EXPANDER */ ESP_LOGD(TAG, "Initialize host"); - ESP_PANEL_CHECK_FALSE_RET(host.begin(), false, "Initialize host failed"); + ESP_PANEL_CHECK_FALSE_RET(host_ptr->begin(), false, "Initialize host failed"); // Save the created devices _lcd_bus_ptr = lcd_bus_ptr; @@ -456,6 +457,7 @@ bool ESP_Panel::init(void) _touch_bus_ptr = touch_bus_ptr; _touch_ptr = touch_ptr; _backlight_ptr = backlight_ptr; + _host_ptr = host_ptr; _expander_ptr = expander_ptr; _is_initialed = true; @@ -625,6 +627,11 @@ bool ESP_Panel::del(void) _expander_ptr = nullptr; } + if (_host_ptr != nullptr) { + ESP_LOGD(TAG, "Delete host"); + _host_ptr = nullptr; + } + _is_initialed = false; ESP_LOGD(TAG, "Delete panel"); diff --git a/src/ESP_Panel.h b/src/ESP_Panel.h index 479866c9..d6797d82 100644 --- a/src/ESP_Panel.h +++ b/src/ESP_Panel.h @@ -77,8 +77,22 @@ class ESP_Panel { * @brief Here are the functions to get the some parameters of the devices * */ - uint16_t getLcdWidth(void); - uint16_t getLcdHeight(void); + uint16_t getLcdWidth(void) + { +#ifdef ESP_PANEL_LCD_WIDTH + return ESP_PANEL_LCD_WIDTH; +#else + return 0; +#endif + } + uint16_t getLcdHeight(void) + { +#ifdef ESP_PANEL_LCD_HEIGHT + return ESP_PANEL_LCD_HEIGHT; +#else + return 0; +#endif + } private: bool _is_initialed; @@ -88,25 +102,8 @@ class ESP_Panel { std::shared_ptr _lcd_ptr; std::shared_ptr _touch_ptr; std::shared_ptr _backlight_ptr; + std::shared_ptr _host_ptr; std::shared_ptr _expander_ptr; }; -inline uint16_t ESP_Panel::getLcdWidth(void) -{ -#ifdef ESP_PANEL_LCD_WIDTH - return ESP_PANEL_LCD_WIDTH; -#else - return 0; -#endif -} - -inline uint16_t ESP_Panel::getLcdHeight(void) -{ -#ifdef ESP_PANEL_LCD_HEIGHT - return ESP_PANEL_LCD_HEIGHT; -#else - return 0; -#endif -} - #endif /* ESP_PANEL_USE_BOARD */ diff --git a/src/ESP_PanelLog.h b/src/ESP_PanelLog.h index 1cc6706b..9d1387d1 100644 --- a/src/ESP_PanelLog.h +++ b/src/ESP_PanelLog.h @@ -8,6 +8,9 @@ #include #include "ESP_Panel_Conf_Internal.h" #if ESP_PANEL_ENABLE_LOG +#ifdef LOG_LOCAL_LEVEL +#undef LOG_LOCAL_LEVEL +#endif #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG #endif #include "esp_log.h" diff --git a/src/ESP_PanelTypes.h b/src/ESP_PanelTypes.h index 66cab144..c6532daf 100644 --- a/src/ESP_PanelTypes.h +++ b/src/ESP_PanelTypes.h @@ -6,7 +6,6 @@ #pragma once -#include "esp_lcd_panel_rgb.h" #include "soc/soc_caps.h" #include "sdkconfig.h" @@ -61,7 +60,7 @@ #define ESP_PANEL_TOUCH_SPI_PANEL_IO_CONFIG(name, cs_io) _ESP_PANEL_TOUCH_SPI_PANEL_IO_CONFIG(name, cs_io) /** - * @brief Formater for single LCD vendor command with 8-bit parameter + * @brief Formatter for single LCD vendor command with 8-bit parameter * * @param[in] delay_ms Delay in milliseconds after this command * @param[in] command LCD command @@ -71,7 +70,7 @@ #define ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, ...) {command, (uint8_t []) __VA_ARGS__, sizeof((uint8_t []) \ __VA_ARGS__), delay_ms} /** - * @brief Formater for single LCD vendor command with no parameter + * @brief Formatter for single LCD vendor command with no parameter * * @param[in] delay_ms Delay in milliseconds after this command * @param[in] command LCD command diff --git a/src/ESP_PanelVersions.h b/src/ESP_PanelVersions.h index 5c154e33..f8338218 100644 --- a/src/ESP_PanelVersions.h +++ b/src/ESP_PanelVersions.h @@ -10,8 +10,8 @@ /* Library Version */ #define ESP_PANEL_VERSION_MAJOR 0 -#define ESP_PANEL_VERSION_MINOR 1 -#define ESP_PANEL_VERSION_PATCH 9 +#define ESP_PANEL_VERSION_MINOR 2 +#define ESP_PANEL_VERSION_PATCH 0 /* File `ESP_Panel_Conf.h` */ #define ESP_PANEL_CONF_VERSION_MAJOR 0 @@ -21,70 +21,76 @@ /* File `ESP_Panel_Board_Custom.h` */ #define ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR 2 -#define ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH 2 +#define ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH 3 /* File `ESP_Panel_Board_Supported.h` */ #define ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR 0 #define ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR 5 -#define ESP_PANEL_BOARD_SUPPORTED_VERSION_PATCH 0 +#define ESP_PANEL_BOARD_SUPPORTED_VERSION_PATCH 1 -/* Check if the current configuration file version is compatible with the library version */ -// File `ESP_Panel_Conf.h` -// If the version is not defined, set it to `0.1.0` -#if !defined(ESP_PANEL_CONF_FILE_VERSION_MAJOR) && \ - !defined(ESP_PANEL_CONF_FILE_VERSION_MINOR) && \ - !defined(ESP_PANEL_CONF_FILE_VERSION_PATCH) -#define ESP_PANEL_CONF_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_CONF_FILE_VERSION_MINOR 1 -#define ESP_PANEL_CONF_FILE_VERSION_PATCH 0 -#endif -// Check if the current configuration file version is compatible with the library version -#if ESP_PANEL_CONF_FILE_VERSION_MAJOR != ESP_PANEL_CONF_VERSION_MAJOR -#error "The file `ESP_Panel_Conf.h` version is not compatible. Please update it with the file from the library" -#elif ESP_PANEL_CONF_FILE_VERSION_MINOR < ESP_PANEL_CONF_VERSION_MINOR -#warning "The file `ESP_Panel_Conf.h` version is outdated. Some new configurations are missing" -#elif ESP_PANEL_CONF_FILE_VERSION_PATCH > ESP_PANEL_VERSION_PATCH -#warning "The file `ESP_Panel_Conf.h` version is newer than the library. Some new configurations are not supported" -#endif /* ESP_PANEL_CONF_INCLUDE_INSIDE */ +// *INDENT-OFF* -// File `ESP_Panel_Board_Custom.h` & `ESP_Panel_Board_Supported.h` -#ifdef ESP_PANEL_USE_BOARD -/* For using a supported board */ -#if CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD -// If the version is not defined, set it to `0.1.0` -#if !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR) && \ - !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR) && \ - !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH) -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 1 -#define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 -#endif -// Check if the current configuration file version is compatible with the library version -#if ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR -#error "The file `ESP_Panel_Board_Supported.h` version is not compatible. Please update it with the file from the library" -#elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR < ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR -#warning "The file `ESP_Panel_Board_Supported.h` version is outdated. Some new configurations are missing" -#elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH > ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR -#warning "The file `ESP_Panel_Board_Supported.h` version is newer than the library. Some new configurations are not supported" -#endif +#ifndef ESP_PANEL_CONF_FILE_SKIP + /* Check if the current configuration file version is compatible with the library version */ + // File `ESP_Panel_Conf.h` + // If the version is not defined, set it to `0.1.0` + #if !defined(ESP_PANEL_CONF_FILE_VERSION_MAJOR) && \ + !defined(ESP_PANEL_CONF_FILE_VERSION_MINOR) && \ + !defined(ESP_PANEL_CONF_FILE_VERSION_PATCH) + #define ESP_PANEL_CONF_FILE_VERSION_MAJOR 0 + #define ESP_PANEL_CONF_FILE_VERSION_MINOR 1 + #define ESP_PANEL_CONF_FILE_VERSION_PATCH 0 + #endif + // Check if the current configuration file version is compatible with the library version + #if ESP_PANEL_CONF_FILE_VERSION_MAJOR != ESP_PANEL_CONF_VERSION_MAJOR + #error "The file `ESP_Panel_Conf.h` version is not compatible. Please update it with the file from the library" + #elif ESP_PANEL_CONF_FILE_VERSION_MINOR < ESP_PANEL_CONF_VERSION_MINOR + #warning "The file `ESP_Panel_Conf.h` version is outdated. Some new configurations are missing" + #elif ESP_PANEL_CONF_FILE_VERSION_PATCH > ESP_PANEL_VERSION_PATCH + #warning "The file `ESP_Panel_Conf.h` version is newer than the library. Some new configurations are not supported" + #endif /* ESP_PANEL_CONF_INCLUDE_INSIDE */ +#endif /* ESP_PANEL_CONF_FILE_SKIP */ -#else /* For using a custom board */ +#ifndef ESP_PANEL_BOARD_FILE_SKIP + // File `ESP_Panel_Board_Custom.h` & `ESP_Panel_Board_Supported.h` + #ifdef ESP_PANEL_USE_BOARD + /* For using a supported board */ + #if ESP_PANEL_USE_SUPPORTED_BOARD + // If the version is not defined, set it to `0.1.0` + #if !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR) && \ + !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR) && \ + !defined(ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH) + #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR 0 + #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR 1 + #define ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH 0 + #endif + // Check if the current configuration file version is compatible with the library version + #if ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR + #error "The file `ESP_Panel_Board_Supported.h` version is not compatible. Please update it with the file from the library" + #elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR < ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR + #warning "The file `ESP_Panel_Board_Supported.h` version is outdated. Some new configurations are missing" + #elif ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR > ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR + #warning "The file `ESP_Panel_Board_Supported.h` version is newer than the library. Some new configurations are not supported" + #endif + #else /* For using a custom board */ + // If the version is not defined, set it to `0.1.0` + #if !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR) && \ + !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR) && \ + !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH) + #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 + #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 1 + #define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 + #endif + // Check if the current configuration file version is compatible with the library version + #if ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR + #error "The file `ESP_Panel_Board_Custom.h` version is not compatible. Please update it with the file from the library" + #elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR < ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR + #warning "The file `ESP_Panel_Board_Custom.h` version is outdated. Some new configurations are missing" + #elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR > ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH + #warning "The file `ESP_Panel_Board_Custom.h` version is newer than the library. Some new configurations are not supported" + #endif + #endif /* CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD */ + #endif /* ESP_PANEL_USE_BOARD */ +#endif /* ESP_PANEL_BOARD_FILE_SKIP */ -// If the version is not defined, set it to `0.1.0` -#if !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR) && \ - !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR) && \ - !defined(ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH) -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR 0 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR 1 -#define ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH 0 -#endif -// Check if the current configuration file version is compatible with the library version -#if ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR != ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR -#error "The file `ESP_Panel_Board_Custom.h` version is not compatible. Please update it with the file from the library" -#elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR < ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR -#warning "The file `ESP_Panel_Board_Custom.h` version is outdated. Some new configurations are missing" -#elif ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH > ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH -#warning "The file `ESP_Panel_Board_Custom.h` version is newer than the library. Some new configurations are not supported" -#endif -#endif /* CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD */ -#endif /* ESP_PANEL_USE_BOARD */ +// *INDENT-OFF* diff --git a/src/ESP_Panel_Board_Internal.h b/src/ESP_Panel_Board_Internal.h index e1d53bb6..91527867 100644 --- a/src/ESP_Panel_Board_Internal.h +++ b/src/ESP_Panel_Board_Internal.h @@ -11,37 +11,47 @@ /* Handle special Kconfig options */ #ifndef ESP_PANEL_KCONFIG_IGNORE #include "sdkconfig.h" - #ifdef CONFIG_ESP_PANEL_CONF_SKIP - #define ESP_PANEL_CONF_SKIP + #ifdef CONFIG_ESP_PANEL_BOARD_FILE_SKIP + #define ESP_PANEL_BOARD_FILE_SKIP #endif #endif #include "ESP_PanelTypes.h" -/* If "ESP_Panel_*_Board.h" are available from here, try to use them later */ -#ifdef __has_include - #if __has_include("ESP_Panel_Board_Supported.h") - #ifndef ESP_PANEL_SUPPORTED_BOARD_INCLUDE_SIMPLE - #define ESP_PANEL_SUPPORTED_BOARD_INCLUDE_SIMPLE +#ifndef ESP_PANEL_BOARD_FILE_SKIP + /* If "ESP_Panel_*_Board.h" are available from here, try to use them later */ + #ifdef __has_include + #if __has_include("ESP_Panel_Board_Supported.h") + #ifndef ESP_PANEL_SUPPORTED_BOARD_INCLUDE_SIMPLE + #define ESP_PANEL_SUPPORTED_BOARD_INCLUDE_SIMPLE + #endif + #elif __has_include("../../ESP_Panel_Board_Supported.h") + #ifndef ESP_PANEL_SUPPORTED_BOARD_INCLUDE_OUTSIDE + #define ESP_PANEL_SUPPORTED_BOARD_INCLUDE_OUTSIDE + #endif + #else + #ifndef ESP_PANEL_SUPPORTED_BOARD_INCLUDE_INSIDE + #define ESP_PANEL_SUPPORTED_BOARD_INCLUDE_INSIDE + #endif #endif - #elif __has_include("../../ESP_Panel_Board_Supported.h") - #ifndef ESP_PANEL_SUPPORTED_BOARD_INCLUDE_OUTSIDE - #define ESP_PANEL_SUPPORTED_BOARD_INCLUDE_OUTSIDE - #endif - #endif - #if __has_include("ESP_Panel_Board_Custom.h") - #ifndef ESP_PANEL_CUSTOM_BOARD_INCLUDE_SIMPLE - #define ESP_PANEL_CUSTOM_BOARD_INCLUDE_SIMPLE - #endif - #elif __has_include("../../ESP_Panel_Board_Custom.h") - #ifndef ESP_PANEL_CUSTOM_BOARD_INCLUDE_OUTSIDE - #define ESP_PANEL_CUSTOM_BOARD_INCLUDE_OUTSIDE + #if __has_include("ESP_Panel_Board_Custom.h") + #ifndef ESP_PANEL_CUSTOM_BOARD_INCLUDE_SIMPLE + #define ESP_PANEL_CUSTOM_BOARD_INCLUDE_SIMPLE + #endif + #elif __has_include("../../ESP_Panel_Board_Custom.h") + #ifndef ESP_PANEL_CUSTOM_BOARD_INCLUDE_OUTSIDE + #define ESP_PANEL_CUSTOM_BOARD_INCLUDE_OUTSIDE + #endif + #else + #ifndef ESP_PANEL_CUSTOM_BOARD_INCLUDE_INSIDE + #define ESP_PANEL_CUSTOM_BOARD_INCLUDE_INSIDE + #endif #endif #endif #endif -/* If "ESP_Panel_Board_*.h" are not skipped, include them */ -#ifndef ESP_PANEL_CONF_SKIP +#ifndef ESP_PANEL_CONF_FILE_SKIP + /* If "ESP_Panel_Board_*.h" are not skipped, include them */ #ifdef ESP_PANEL_SUPPORTED_BOARD_PATH /* If there is a path defined for "ESP_Panel_Board_Supported.h", use it */ #define __TO_STR_AUX(x) #x #define __TO_STR(x) __TO_STR_AUX(x) @@ -66,81 +76,88 @@ #endif #endif +#if !defined(ESP_PANEL_SUPPORTED_BOARD_INCLUDE_INSIDE) && !defined(ESP_PANEL_CUSTOM_BOARD_INCLUDE_INSIDE) + /** + * There are two purposes to include the this file: + * 1. Convert configuration items starting with `CONFIG_` to the required configuration items. + * 2. Define default values for configuration items that are not defined to keep compatibility. + * + */ + #include "ESP_Panel_Board_Kconfig.h" +#endif + /* Check if select both custom and supported board */ -#if (CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD) && \ - (CONFIG_ESP_PANEL_USE_CUSTOM_BOARD || ESP_PANEL_USE_CUSTOM_BOARD) +#if ESP_PANEL_USE_SUPPORTED_BOARD && ESP_PANEL_USE_CUSTOM_BOARD #error "Please select either a custom or a supported development board, cannot enable both simultaneously" #endif /* Check if use board */ -#if CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD || \ - CONFIG_ESP_PANEL_USE_CUSTOM_BOARD || ESP_PANEL_USE_CUSTOM_BOARD +#if ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_CUSTOM_BOARD #define ESP_PANEL_USE_BOARD #endif #ifdef ESP_PANEL_USE_BOARD -/* For using a supported board */ -#if CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD || ESP_PANEL_USE_SUPPORTED_BOARD -// Include the supported board header file -#include "board/ESP_PanelBoard.h" -#endif + /* For using a supported board, include the supported board header file */ + #if ESP_PANEL_USE_SUPPORTED_BOARD + #include "board/ESP_PanelBoard.h" + #endif -/* Define some special macros for devices */ -/*-------------------------------- LCD Related --------------------------------*/ -#if ESP_PANEL_USE_LCD - #if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI + /* Define some special macros for devices */ + /*-------------------------------- LCD Related --------------------------------*/ + #if ESP_PANEL_USE_LCD + #if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI - #include "hal/spi_types.h" - #define ESP_PANEL_LCD_BUS_NAME SPI - #define ESP_PANEL_LCD_BUS_HOST ((spi_host_device_t)ESP_PANEL_LCD_BUS_HOST_ID) + #include "hal/spi_types.h" + #define ESP_PANEL_LCD_BUS_NAME SPI + #define ESP_PANEL_LCD_BUS_HOST ((spi_host_device_t)ESP_PANEL_LCD_BUS_HOST_ID) - #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_QSPI + #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_QSPI - #include "hal/spi_types.h" - #define ESP_PANEL_LCD_BUS_NAME QSPI - #define ESP_PANEL_LCD_BUS_HOST ((spi_host_device_t)ESP_PANEL_LCD_BUS_HOST_ID) + #include "hal/spi_types.h" + #define ESP_PANEL_LCD_BUS_NAME QSPI + #define ESP_PANEL_LCD_BUS_HOST ((spi_host_device_t)ESP_PANEL_LCD_BUS_HOST_ID) - #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB + #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB - #ifndef SOC_LCD_RGB_SUPPORTED - #error "LCD RGB is only supported for ESP32-S3, please select the correct board." - #endif - #define ESP_PANEL_LCD_BUS_NAME RGB - #define ESP_PANEL_LCD_BUS_HOST (-1) + #ifndef SOC_LCD_RGB_SUPPORTED + #error "RGB is not supported for current SoC, please select the correct board." + #endif + #define ESP_PANEL_LCD_BUS_NAME RGB + #define ESP_PANEL_LCD_BUS_HOST (-1) - #else + #else - #error "Unkonw LCD panel bus type selected, please refer to the README for supported bus types" + #error "Unknown LCD panel bus type selected, please refer to the README for supported bus types" - #endif /* ESP_PANEL_LCD_BUS_TYPE */ -#endif /* ESP_PANEL_USE_LCD */ -/*-------------------------------- Touch Related --------------------------------*/ -#if ESP_PANEL_USE_TOUCH - #if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C + #endif /* ESP_PANEL_LCD_BUS_TYPE */ + #endif /* ESP_PANEL_USE_LCD */ + /*-------------------------------- Touch Related --------------------------------*/ + #if ESP_PANEL_USE_TOUCH + #if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C - #include "hal/i2c_types.h" - #define ESP_PANEL_TOUCH_BUS_NAME I2C - #define ESP_PANEL_TOUCH_BUS_HOST ((i2c_port_t)ESP_PANEL_TOUCH_BUS_HOST_ID) + #include "hal/i2c_types.h" + #define ESP_PANEL_TOUCH_BUS_NAME I2C + #define ESP_PANEL_TOUCH_BUS_HOST ((i2c_port_t)ESP_PANEL_TOUCH_BUS_HOST_ID) - #elif ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI + #elif ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI - #include "hal/spi_types.h" - #define ESP_PANEL_TOUCH_BUS_NAME SPI - #define ESP_PANEL_TOUCH_BUS_HOST ((spi_host_device_t)ESP_PANEL_TOUCH_BUS_HOST_ID) + #include "hal/spi_types.h" + #define ESP_PANEL_TOUCH_BUS_NAME SPI + #define ESP_PANEL_TOUCH_BUS_HOST ((spi_host_device_t)ESP_PANEL_TOUCH_BUS_HOST_ID) - #else + #else - #error "Unkonw Touch bus type selected, please refer to the README for supported bus types." + #error "Unknown Touch bus type selected, please refer to the README for supported bus types." - #endif /* ESP_PANEL_TOUCH_BUS_TYPE */ -#endif /* ESP_PANEL_USE_TOUCH */ -/*-------------------------------- IO Expander Related --------------------------------*/ -#if ESP_PANEL_USE_EXPANDER + #endif /* ESP_PANEL_TOUCH_BUS_TYPE */ + #endif /* ESP_PANEL_USE_TOUCH */ + /*-------------------------------- IO Expander Related --------------------------------*/ + #if ESP_PANEL_USE_EXPANDER - #include "hal/i2c_types.h" - #define ESP_PANEL_EXPANDER_HOST ((i2c_port_t)ESP_PANEL_EXPANDER_HOST_ID) + #include "hal/i2c_types.h" + #define ESP_PANEL_EXPANDER_HOST ((i2c_port_t)ESP_PANEL_EXPANDER_HOST_ID) -#endif /* ESP_PANEL_USE_EXPANDER */ + #endif /* ESP_PANEL_USE_EXPANDER */ #endif /* ESP_PANEL_USE_BOARD */ // *INDENT-OFF* diff --git a/src/ESP_Panel_Board_Kconfig.h b/src/ESP_Panel_Board_Kconfig.h new file mode 100644 index 00000000..34054e9d --- /dev/null +++ b/src/ESP_Panel_Board_Kconfig.h @@ -0,0 +1,1102 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "ESP_PanelTypes.h" + +// *INDENT-OFF* + +#ifndef ESP_PANEL_USE_SUPPORTED_BOARD + #ifdef CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD + #define ESP_PANEL_USE_SUPPORTED_BOARD CONFIG_ESP_PANEL_USE_SUPPORTED_BOARD + #else + #define ESP_PANEL_USE_SUPPORTED_BOARD 0 + #endif +#endif + +#ifndef ESP_PANEL_USE_CUSTOM_BOARD + #ifdef CONFIG_ESP_PANEL_USE_CUSTOM_BOARD + #define ESP_PANEL_USE_CUSTOM_BOARD CONFIG_ESP_PANEL_USE_CUSTOM_BOARD + #else + #define ESP_PANEL_USE_CUSTOM_BOARD 0 + #endif +#endif + +/** + * Supported Board + * + */ +#if ESP_PANEL_USE_SUPPORTED_BOARD + // Espressif + #ifndef BOARD_ESP32_C3_LCDKIT + #ifdef CONFIG_BOARD_ESP32_C3_LCDKIT + #define BOARD_ESP32_C3_LCDKIT CONFIG_BOARD_ESP32_C3_LCDKIT + #endif + #endif + #ifndef BOARD_ESP32_S3_BOX + #ifdef CONFIG_BOARD_ESP32_S3_BOX + #define BOARD_ESP32_S3_BOX CONFIG_BOARD_ESP32_S3_BOX + #endif + #endif + #ifndef BOARD_ESP32_S3_BOX_3 + #ifdef CONFIG_BOARD_ESP32_S3_BOX_3 + #define BOARD_ESP32_S3_BOX_3 CONFIG_BOARD_ESP32_S3_BOX_3 + #endif + #endif + #ifndef BOARD_ESP32_S3_BOX_3_BETA + #ifdef CONFIG_BOARD_ESP32_S3_BOX_3_BETA + #define BOARD_ESP32_S3_BOX_3_BETA CONFIG_BOARD_ESP32_S3_BOX_3_BETA + #endif + #endif + #ifndef BOARD_ESP32_S3_BOX_LITE + #ifdef CONFIG_BOARD_ESP32_S3_BOX_LITE + #define BOARD_ESP32_S3_BOX_LITE CONFIG_BOARD_ESP32_S3_BOX_LITE + #endif + #endif + #ifndef BOARD_ESP32_S3_EYE + #ifdef CONFIG_BOARD_ESP32_S3_EYE + #define BOARD_ESP32_S3_EYE CONFIG_BOARD_ESP32_S3_EYE + #endif + #endif + #ifndef BOARD_ESP32_S3_KORVO_2 + #ifdef CONFIG_BOARD_ESP32_S3_KORVO_2 + #define BOARD_ESP32_S3_KORVO_2 CONFIG_BOARD_ESP32_S3_KORVO_2 + #endif + #endif + #ifndef BOARD_ESP32_S3_LCD_EV_BOARD + #ifdef CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD + #define BOARD_ESP32_S3_LCD_EV_BOARD CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD + #endif + #endif + #ifndef BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 + #ifdef CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 + #define BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 + #endif + #endif + #ifndef BOARD_ESP32_S3_LCD_EV_BOARD_2 + #ifdef CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2 + #define BOARD_ESP32_S3_LCD_EV_BOARD_2 CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2 + #endif + #endif + #ifndef BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 + #ifdef CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 + #define BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 + #endif + #endif + #ifndef BOARD_ESP32_S3_USB_OTG + #ifdef CONFIG_BOARD_ESP32_S3_USB_OTG + #define BOARD_ESP32_S3_USB_OTG CONFIG_BOARD_ESP32_S3_USB_OTG + #endif + #endif + // Elecrow + #ifndef BOARD_ELECROW_CROWPANEL_7_0 + #ifdef CONFIG_BOARD_ELECROW_CROWPANEL_7_0 + #define BOARD_ELECROW_CROWPANEL_7_0 CONFIG_BOARD_ELECROW_CROWPANEL_7_0 + #endif + #endif + // M5Stack + #ifndef BOARD_M5STACK_M5CORE2 + #ifdef CONFIG_BOARD_M5STACK_M5CORE2 + #define BOARD_M5STACK_M5CORE2 CONFIG_BOARD_M5STACK_M5CORE2 + #endif + #endif + #ifndef BOARD_M5STACK_M5DIAL + #ifdef CONFIG_BOARD_M5STACK_M5DIAL + #define BOARD_M5STACK_M5DIAL CONFIG_BOARD_M5STACK_M5DIAL + #endif + #endif + #ifndef BOARD_M5STACK_M5CORES3 + #ifdef CONFIG_BOARD_M5STACK_M5CORES3 + #define BOARD_M5STACK_M5CORES3 CONFIG_BOARD_M5STACK_M5CORES3 + #endif + #endif + // Jingcai + #ifndef BOARD_ESP32_4848S040C_I_Y_3 + #ifdef CONFIG_BOARD_ESP32_4848S040C_I_Y_3 + #define BOARD_ESP32_4848S040C_I_Y_3 CONFIG_BOARD_ESP32_4848S040C_I_Y_3 + #endif + #endif + // Waveshare + #ifndef BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3 + #ifdef CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3 + #define BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3 CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3 + #endif + #endif + #ifndef BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85 + #ifdef CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85 + #define BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85 CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85 + #endif + #endif + #ifndef BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1 + #ifdef CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1 + #define BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1 CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1 + #endif + #endif +#endif /* ESP_PANEL_USE_SUPPORTED_BOARD */ + +/** + * Custom Board + * + */ +#if ESP_PANEL_USE_CUSTOM_BOARD + // LCD + #ifndef ESP_PANEL_USE_LCD + #ifdef CONFIG_ESP_PANEL_USE_LCD + #define ESP_PANEL_USE_LCD CONFIG_ESP_PANEL_USE_LCD + #else + #define ESP_PANEL_USE_LCD 0 + #endif + #endif + #if ESP_PANEL_USE_LCD + // Controller + #ifndef ESP_PANEL_LCD_CONTROLLER_EK9716B + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_EK9716B + #define ESP_PANEL_LCD_NAME EK9716B + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_GC9A01 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_GC9A01 + #define ESP_PANEL_LCD_NAME GC9A01 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_GC9B71 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_GC9B71 + #define ESP_PANEL_LCD_NAME GC9B71 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_GC9503 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_GC9503 + #define ESP_PANEL_LCD_NAME GC9503 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ILI9341 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ILI9341 + #define ESP_PANEL_LCD_NAME ILI9341 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_NV3022B + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_NV3022B + #define ESP_PANEL_LCD_NAME NV3022B + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_SH8601 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_SH8601 + #define ESP_PANEL_LCD_NAME SH8601 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_SPD2010 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_SPD2010 + #define ESP_PANEL_LCD_NAME SPD2010 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ST7262 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ST7262 + #define ESP_PANEL_LCD_NAME ST7262 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ST7701 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ST7701 + #define ESP_PANEL_LCD_NAME ST7701 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ST7789 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ST7789 + #define ESP_PANEL_LCD_NAME ST7789 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ST7796 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ST7796 + #define ESP_PANEL_LCD_NAME ST7796 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ST77916 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ST77916 + #define ESP_PANEL_LCD_NAME ST77916 + #endif + #endif + #ifndef ESP_PANEL_LCD_CONTROLLER_ST77922 + #ifdef CONFIG_ESP_PANEL_LCD_CONTROLLER_ST77922 + #define ESP_PANEL_LCD_NAME ST77922 + #endif + #endif + #ifndef ESP_PANEL_LCD_NAME + #error "Missing configuration: ESP_PANEL_LCD_NAME" + #endif + // Resolution + #ifndef ESP_PANEL_LCD_WIDTH + #ifdef CONFIG_ESP_PANEL_LCD_WIDTH + #define ESP_PANEL_LCD_WIDTH CONFIG_ESP_PANEL_LCD_WIDTH + #else + #error "Missing configuration: ESP_PANEL_LCD_WIDTH" + #endif + #endif + #ifndef ESP_PANEL_LCD_HEIGHT + #ifdef CONFIG_ESP_PANEL_LCD_HEIGHT + #define ESP_PANEL_LCD_HEIGHT CONFIG_ESP_PANEL_LCD_HEIGHT + #else + #error "Missing configuration: ESP_PANEL_LCD_HEIGHT" + #endif + #endif + // Bus + #ifndef ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + #ifdef CONFIG_ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + #define ESP_PANEL_LCD_BUS_SKIP_INIT_HOST CONFIG_ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + #else + #define ESP_PANEL_LCD_BUS_SKIP_INIT_HOST 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_BUS_TYPE + #ifdef CONFIG_ESP_PANEL_LCD_BUS_TYPE + #define ESP_PANEL_LCD_BUS_TYPE CONFIG_ESP_PANEL_LCD_BUS_TYPE + #else + #error "Missing configuration: ESP_PANEL_LCD_BUS_TYPE" + #endif + #endif + // SPI Bus + #if ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI + #ifndef ESP_PANEL_LCD_BUS_HOST_ID + #ifdef CONFIG_ESP_PANEL_LCD_BUS_HOST_ID + #define ESP_PANEL_LCD_BUS_HOST_ID CONFIG_ESP_PANEL_LCD_BUS_HOST_ID + #else + #error "Missing configuration: ESP_PANEL_LCD_BUS_HOST_ID" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_MODE + #ifdef CONFIG_ESP_PANEL_LCD_SPI_MODE + #define ESP_PANEL_LCD_SPI_MODE CONFIG_ESP_PANEL_LCD_SPI_MODE + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_MODE" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_CLK_HZ + #ifdef CONFIG_ESP_PANEL_LCD_SPI_CLK_HZ + #define ESP_PANEL_LCD_SPI_CLK_HZ CONFIG_ESP_PANEL_LCD_SPI_CLK_HZ + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_CLK_HZ" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + #ifdef CONFIG_ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + #define ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ CONFIG_ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + #else + #error "Missing configuration: CONFIG_ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_CMD_BITS + #ifdef CONFIG_ESP_PANEL_LCD_SPI_CMD_BITS + #define ESP_PANEL_LCD_SPI_CMD_BITS CONFIG_ESP_PANEL_LCD_SPI_CMD_BITS + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_CMD_BITS" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_PARAM_BITS + #ifdef CONFIG_ESP_PANEL_LCD_SPI_PARAM_BITS + #define ESP_PANEL_LCD_SPI_PARAM_BITS CONFIG_ESP_PANEL_LCD_SPI_PARAM_BITS + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_PARAM_BITS" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_CS + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_CS + #define ESP_PANEL_LCD_SPI_IO_CS CONFIG_ESP_PANEL_LCD_SPI_IO_CS + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_CS" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_DC + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_DC + #define ESP_PANEL_LCD_SPI_IO_DC CONFIG_ESP_PANEL_LCD_SPI_IO_DC + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_DC" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_SCK + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_SCK + #define ESP_PANEL_LCD_SPI_IO_SCK CONFIG_ESP_PANEL_LCD_SPI_IO_SCK + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_SCK" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_MOSI + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_MOSI + #define ESP_PANEL_LCD_SPI_IO_MOSI CONFIG_ESP_PANEL_LCD_SPI_IO_MOSI + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_MOSI" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_MISO + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_MISO + #define ESP_PANEL_LCD_SPI_IO_MISO CONFIG_ESP_PANEL_LCD_SPI_IO_MISO + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_MISO" + #endif + #endif + // QSPI Bus + #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_QSPI + #ifndef ESP_PANEL_LCD_BUS_HOST_ID + #ifdef CONFIG_ESP_PANEL_LCD_BUS_HOST_ID + #define ESP_PANEL_LCD_BUS_HOST_ID CONFIG_ESP_PANEL_LCD_BUS_HOST_ID + #else + #error "Missing configuration: ESP_PANEL_LCD_BUS_HOST_ID" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_MODE + #ifdef CONFIG_ESP_PANEL_LCD_SPI_MODE + #define ESP_PANEL_LCD_SPI_MODE CONFIG_ESP_PANEL_LCD_SPI_MODE + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_MODE" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_CLK_HZ + #ifdef CONFIG_ESP_PANEL_LCD_SPI_CLK_HZ + #define ESP_PANEL_LCD_SPI_CLK_HZ CONFIG_ESP_PANEL_LCD_SPI_CLK_HZ + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_CLK_HZ" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + #ifdef CONFIG_ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + #define ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ CONFIG_ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + #else + #error "Missing configuration: CONFIG_ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_CMD_BITS + #ifdef CONFIG_ESP_PANEL_LCD_SPI_CMD_BITS + #define ESP_PANEL_LCD_SPI_CMD_BITS CONFIG_ESP_PANEL_LCD_SPI_CMD_BITS + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_CMD_BITS" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_PARAM_BITS + #ifdef CONFIG_ESP_PANEL_LCD_SPI_PARAM_BITS + #define ESP_PANEL_LCD_SPI_PARAM_BITS CONFIG_ESP_PANEL_LCD_SPI_PARAM_BITS + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_PARAM_BITS" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_CS + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_CS + #define ESP_PANEL_LCD_SPI_IO_CS CONFIG_ESP_PANEL_LCD_SPI_IO_CS + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_CS" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_SCK + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_SCK + #define ESP_PANEL_LCD_SPI_IO_SCK CONFIG_ESP_PANEL_LCD_SPI_IO_SCK + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_SCK" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_DATA0 + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_DATA0 + #define ESP_PANEL_LCD_SPI_IO_DATA0 CONFIG_ESP_PANEL_LCD_SPI_IO_DATA0 + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_DATA0" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_DATA1 + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_DATA1 + #define ESP_PANEL_LCD_SPI_IO_DATA1 CONFIG_ESP_PANEL_LCD_SPI_IO_DATA1 + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_DATA1" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_DATA2 + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_DATA2 + #define ESP_PANEL_LCD_SPI_IO_DATA2 CONFIG_ESP_PANEL_LCD_SPI_IO_DATA2 + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_DATA2" + #endif + #endif + #ifndef ESP_PANEL_LCD_SPI_IO_DATA3 + #ifdef CONFIG_ESP_PANEL_LCD_SPI_IO_DATA3 + #define ESP_PANEL_LCD_SPI_IO_DATA3 CONFIG_ESP_PANEL_LCD_SPI_IO_DATA3 + #else + #error "Missing configuration: ESP_PANEL_LCD_SPI_IO_DATA3" + #endif + #endif + // RGB Bus + #elif ESP_PANEL_LCD_BUS_TYPE == ESP_PANEL_BUS_TYPE_RGB + #if !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + // 3-wire SPI Interface + #ifndef ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER + #ifdef CONFIG_ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER + #define ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER CONFIG_ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER + #else + #define ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER + #ifdef CONFIG_ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER + #define ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER CONFIG_ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER + #else + #define ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER + #ifdef CONFIG_ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER + #define ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER CONFIG_ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER + #else + #define ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO + #ifdef CONFIG_ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO + #define ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO CONFIG_ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO + #else + #define ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD + #ifdef CONFIG_ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD + #define ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD CONFIG_ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD + #else + #define ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD (!ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO) + #endif + #endif + #ifndef ESP_PANEL_LCD_3WIRE_SPI_IO_CS + #ifdef CONFIG_ESP_PANEL_LCD_3WIRE_SPI_IO_CS + #define ESP_PANEL_LCD_3WIRE_SPI_IO_CS CONFIG_ESP_PANEL_LCD_3WIRE_SPI_IO_CS + #else + #error "Missing configuration: ESP_PANEL_LCD_3WIRE_SPI_IO_CS" + #endif + #endif + #ifndef ESP_PANEL_LCD_3WIRE_SPI_IO_SCK + #ifdef CONFIG_ESP_PANEL_LCD_3WIRE_SPI_IO_SCK + #define ESP_PANEL_LCD_3WIRE_SPI_IO_SCK CONFIG_ESP_PANEL_LCD_3WIRE_SPI_IO_SCK + #else + #error "Missing configuration: ESP_PANEL_LCD_3WIRE_SPI_IO_SCK" + #endif + #endif + #ifndef ESP_PANEL_LCD_3WIRE_SPI_IO_SDA + #ifdef CONFIG_ESP_PANEL_LCD_3WIRE_SPI_IO_SDA + #define ESP_PANEL_LCD_3WIRE_SPI_IO_SDA CONFIG_ESP_PANEL_LCD_3WIRE_SPI_IO_SDA + #else + #error "Missing configuration: ESP_PANEL_LCD_3WIRE_SPI_IO_SDA" + #endif + #endif + #endif /* ESP_PANEL_LCD_BUS_SKIP_INIT_HOST */ + // RGB Interface + #ifndef ESP_PANEL_LCD_RGB_CLK_HZ + #ifdef CONFIG_ESP_PANEL_LCD_RGB_CLK_HZ + #define ESP_PANEL_LCD_RGB_CLK_HZ CONFIG_ESP_PANEL_LCD_RGB_CLK_HZ + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_CLK_HZ" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_HPW + #ifdef CONFIG_ESP_PANEL_LCD_RGB_HPW + #define ESP_PANEL_LCD_RGB_HPW CONFIG_ESP_PANEL_LCD_RGB_HPW + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_HPW" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_HBP + #ifdef CONFIG_ESP_PANEL_LCD_RGB_HBP + #define ESP_PANEL_LCD_RGB_HBP CONFIG_ESP_PANEL_LCD_RGB_HBP + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_HBP" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_HFP + #ifdef CONFIG_ESP_PANEL_LCD_RGB_HFP + #define ESP_PANEL_LCD_RGB_HFP CONFIG_ESP_PANEL_LCD_RGB_HFP + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_HFP" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_VPW + #ifdef CONFIG_ESP_PANEL_LCD_RGB_VPW + #define ESP_PANEL_LCD_RGB_VPW CONFIG_ESP_PANEL_LCD_RGB_VPW + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_VPW" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_VBP + #ifdef CONFIG_ESP_PANEL_LCD_RGB_VBP + #define ESP_PANEL_LCD_RGB_VBP CONFIG_ESP_PANEL_LCD_RGB_VBP + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_VBP" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_VFP + #ifdef CONFIG_ESP_PANEL_LCD_RGB_VFP + #define ESP_PANEL_LCD_RGB_VFP CONFIG_ESP_PANEL_LCD_RGB_VFP + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_VFP" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG + #ifdef CONFIG_ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG + #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG CONFIG_ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG + #else + #define ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_DATA_WIDTH + #ifdef CONFIG_ESP_PANEL_LCD_RGB_DATA_WIDTH + #define ESP_PANEL_LCD_RGB_DATA_WIDTH CONFIG_ESP_PANEL_LCD_RGB_DATA_WIDTH + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_DATA_WIDTH" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_PIXEL_BITS + #ifdef CONFIG_ESP_PANEL_LCD_RGB_PIXEL_BITS + #define ESP_PANEL_LCD_RGB_PIXEL_BITS CONFIG_ESP_PANEL_LCD_RGB_PIXEL_BITS + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_PIXEL_BITS" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_HSYNC + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_HSYNC + #define ESP_PANEL_LCD_RGB_IO_HSYNC CONFIG_ESP_PANEL_LCD_RGB_IO_HSYNC + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_HSYNC" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_VSYNC + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_VSYNC + #define ESP_PANEL_LCD_RGB_IO_VSYNC CONFIG_ESP_PANEL_LCD_RGB_IO_VSYNC + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_VSYNC" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DE + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DE + #define ESP_PANEL_LCD_RGB_IO_DE CONFIG_ESP_PANEL_LCD_RGB_IO_DE + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DE" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_PCLK + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_PCLK + #define ESP_PANEL_LCD_RGB_IO_PCLK CONFIG_ESP_PANEL_LCD_RGB_IO_PCLK + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_PCLK" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DISP + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DISP + #define ESP_PANEL_LCD_RGB_IO_DISP CONFIG_ESP_PANEL_LCD_RGB_IO_DISP + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DISP" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA0 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA0 + #define ESP_PANEL_LCD_RGB_IO_DATA0 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA0 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA0" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA1 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA1 + #define ESP_PANEL_LCD_RGB_IO_DATA1 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA1 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA1" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA2 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA2 + #define ESP_PANEL_LCD_RGB_IO_DATA2 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA2 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA2" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA3 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA3 + #define ESP_PANEL_LCD_RGB_IO_DATA3 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA3 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA3" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA4 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA4 + #define ESP_PANEL_LCD_RGB_IO_DATA4 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA4 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA4" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA5 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA5 + #define ESP_PANEL_LCD_RGB_IO_DATA5 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA5 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA5" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA6 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA6 + #define ESP_PANEL_LCD_RGB_IO_DATA6 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA6 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA6" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA7 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA7 + #define ESP_PANEL_LCD_RGB_IO_DATA7 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA7 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA7" + #endif + #endif + #if ESP_PANEL_LCD_RGB_DATA_WIDTH > 8 + #ifndef ESP_PANEL_LCD_RGB_IO_DATA8 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA8 + #define ESP_PANEL_LCD_RGB_IO_DATA8 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA8 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA8" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA9 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA9 + #define ESP_PANEL_LCD_RGB_IO_DATA9 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA9 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA9" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA10 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA10 + #define ESP_PANEL_LCD_RGB_IO_DATA10 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA10 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA10" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA11 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA11 + #define ESP_PANEL_LCD_RGB_IO_DATA11 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA11 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA11" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA12 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA12 + #define ESP_PANEL_LCD_RGB_IO_DATA12 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA12 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA12" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA13 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA13 + #define ESP_PANEL_LCD_RGB_IO_DATA13 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA13 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA13" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA14 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA14 + #define ESP_PANEL_LCD_RGB_IO_DATA14 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA14 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA14" + #endif + #endif + #ifndef ESP_PANEL_LCD_RGB_IO_DATA15 + #ifdef CONFIG_ESP_PANEL_LCD_RGB_IO_DATA15 + #define ESP_PANEL_LCD_RGB_IO_DATA15 CONFIG_ESP_PANEL_LCD_RGB_IO_DATA15 + #else + #error "Missing configuration: ESP_PANEL_LCD_RGB_IO_DATA15" + #endif + #endif + #endif /* ESP_PANEL_LCD_RGB_DATA_WIDTH */ + #else + #error "Invalid configuration: ESP_PANEL_LCD_BUS_TYPE" + #endif /* ESP_PANEL_LCD_BUS_TYPE */ + // Color Settings + #ifndef ESP_PANEL_LCD_COLOR_BITS + #ifdef CONFIG_ESP_PANEL_LCD_COLOR_BITS + #define ESP_PANEL_LCD_COLOR_BITS CONFIG_ESP_PANEL_LCD_COLOR_BITS + #else + #error "Missing configuration: ESP_PANEL_LCD_COLOR_BITS" + #endif + #endif + #ifndef ESP_PANEL_LCD_BGR_ORDER + #ifdef CONFIG_ESP_PANEL_LCD_BGR_ORDER + #define ESP_PANEL_LCD_BGR_ORDER CONFIG_ESP_PANEL_LCD_BGR_ORDER + #else + #define ESP_PANEL_LCD_BGR_ORDER 0 + #endif + #endif + #ifndef ESP_PANEL_LCD_INEVRT_COLOR + #ifdef CONFIG_ESP_PANEL_LCD_INEVRT_COLOR + #define ESP_PANEL_LCD_INEVRT_COLOR CONFIG_ESP_PANEL_LCD_INEVRT_COLOR + #else + #define ESP_PANEL_LCD_INEVRT_COLOR 0 + #endif + #endif + // Transformation settings + #ifndef ESP_PANEL_LCD_SWAP_XY + #ifdef CONFIG_ESP_PANEL_LCD_SWAP_XY + #define ESP_PANEL_LCD_SWAP_XY CONFIG_ESP_PANEL_LCD_SWAP_XY + #endif + #endif + #ifndef ESP_PANEL_LCD_MIRROR_X + #ifdef CONFIG_ESP_PANEL_LCD_MIRROR_X + #define ESP_PANEL_LCD_MIRROR_X CONFIG_ESP_PANEL_LCD_MIRROR_X + #endif + #endif + #ifndef ESP_PANEL_LCD_MIRROR_Y + #ifdef CONFIG_ESP_PANEL_LCD_MIRROR_Y + #define ESP_PANEL_LCD_MIRROR_Y CONFIG_ESP_PANEL_LCD_MIRROR_Y + #endif + #endif + #ifndef ESP_PANEL_LCD_IO_RST + #ifdef CONFIG_ESP_PANEL_LCD_IO_RST + #define ESP_PANEL_LCD_IO_RST CONFIG_ESP_PANEL_LCD_IO_RST + #else + #error "Missing configuration: ESP_PANEL_LCD_IO_RST" + #endif + #endif + #ifndef ESP_PANEL_LCD_RST_LEVEL + #ifdef CONFIG_ESP_PANEL_LCD_RST_LEVEL + #define ESP_PANEL_LCD_RST_LEVEL CONFIG_ESP_PANEL_LCD_RST_LEVEL + #else + #define ESP_PANEL_LCD_RST_LEVEL 0 + #endif + #endif + #endif /* ESP_PANEL_USE_LCD */ + + #ifndef ESP_PANEL_USE_TOUCH + #ifdef CONFIG_ESP_PANEL_USE_TOUCH + #define ESP_PANEL_USE_TOUCH CONFIG_ESP_PANEL_USE_TOUCH + #else + #define ESP_PANEL_USE_TOUCH 0 + #endif + #endif + // LCD Touch + #if ESP_PANEL_USE_TOUCH + // Controller + #ifndef ESP_PANEL_TOUCH_CONTROLLER_CST816S + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_CST816S + #define ESP_PANEL_TOUCH_NAME CST816S + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_FT5x06 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_FT5X06 + #define ESP_PANEL_TOUCH_NAME FT5X06 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_GT911 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_GT911 + #define ESP_PANEL_TOUCH_NAME GT911 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_GT1151 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_GT1151 + #define ESP_PANEL_TOUCH_NAME GT1151 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_ST1633 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_ST1633 + #define ESP_PANEL_TOUCH_NAME ST1633 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_ST7123 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_ST7123 + #define ESP_PANEL_TOUCH_NAME ST7123 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_TT21100 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_TT21100 + #define ESP_PANEL_TOUCH_NAME TT21100 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_CONTROLLER_STMPE610 + #ifdef CONFIG_ESP_PANEL_TOUCH_CONTROLLER_STMPE610 + #define ESP_PANEL_TOUCH_NAME STMPE610 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_NAME + #error "Missing configuration: ESP_PANEL_TOUCH_NAME" + #endif + // Resolution + #ifndef ESP_PANEL_TOUCH_H_RES + #ifdef CONFIG_ESP_PANEL_TOUCH_H_RES + #define ESP_PANEL_TOUCH_H_RES CONFIG_ESP_PANEL_TOUCH_H_RES + #else + #error "Missing configuration: ESP_PANEL_TOUCH_H_RES" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_V_RES + #ifdef CONFIG_ESP_PANEL_TOUCH_V_RES + #define ESP_PANEL_TOUCH_V_RES CONFIG_ESP_PANEL_TOUCH_V_RES + #else + #error "Missing configuration: ESP_PANEL_TOUCH_V_RES" + #endif + #endif + // Bus Settings + #ifndef ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + #ifdef CONFIG_ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + #define ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST CONFIG_ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + #else + #define ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST 0 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_BUS_TYPE + #ifdef CONFIG_ESP_PANEL_TOUCH_BUS_TYPE + #define ESP_PANEL_TOUCH_BUS_TYPE CONFIG_ESP_PANEL_TOUCH_BUS_TYPE + #else + #error "Missing configuration: ESP_PANEL_TOUCH_BUS_TYPE" + #endif + #endif + #if ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_I2C + // I2C Bus + #ifndef ESP_PANEL_TOUCH_BUS_HOST_ID + #ifdef CONFIG_ESP_PANEL_TOUCH_BUS_HOST_ID + #define ESP_PANEL_TOUCH_BUS_HOST_ID CONFIG_ESP_PANEL_TOUCH_BUS_HOST_ID + #else + #error "Missing configuration: ESP_PANEL_TOUCH_BUS_HOST_ID" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_I2C_ADDRESS + #ifdef CONFIG_ESP_PANEL_TOUCH_I2C_ADDRESS + #define ESP_PANEL_TOUCH_I2C_ADDRESS CONFIG_ESP_PANEL_TOUCH_I2C_ADDRESS + #else + #error "Missing configuration: ESP_PANEL_TOUCH_I2C_ADDRESS" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_I2C_CLK_HZ + #ifdef CONFIG_ESP_PANEL_TOUCH_I2C_CLK_HZ + #define ESP_PANEL_TOUCH_I2C_CLK_HZ CONFIG_ESP_PANEL_TOUCH_I2C_CLK_HZ + #else + #error "Missing configuration: ESP_PANEL_TOUCH_I2C_CLK_HZ" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_I2C_SCL_PULLUP + #ifdef CONFIG_ESP_PANEL_TOUCH_I2C_SCL_PULLUP + #define ESP_PANEL_TOUCH_I2C_SCL_PULLUP CONFIG_ESP_PANEL_TOUCH_I2C_SCL_PULLUP + #else + #define ESP_PANEL_TOUCH_I2C_SCL_PULLUP 0 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_I2C_SDA_PULLUP + #ifdef CONFIG_ESP_PANEL_TOUCH_I2C_SDA_PULLUP + #define ESP_PANEL_TOUCH_I2C_SDA_PULLUP CONFIG_ESP_PANEL_TOUCH_I2C_SDA_PULLUP + #else + #define ESP_PANEL_TOUCH_I2C_SDA_PULLUP 0 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_I2C_IO_SCL + #ifdef CONFIG_ESP_PANEL_TOUCH_I2C_IO_SCL + #define ESP_PANEL_TOUCH_I2C_IO_SCL CONFIG_ESP_PANEL_TOUCH_I2C_IO_SCL + #else + #error "Missing configuration: ESP_PANEL_TOUCH_I2C_IO_SCL" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_I2C_IO_SDA + #ifdef CONFIG_ESP_PANEL_TOUCH_I2C_IO_SDA + #define ESP_PANEL_TOUCH_I2C_IO_SDA CONFIG_ESP_PANEL_TOUCH_I2C_IO_SDA + #else + #error "Missing configuration: ESP_PANEL_TOUCH_I2C_IO_SDA" + #endif + #endif + #elif ESP_PANEL_TOUCH_BUS_TYPE == ESP_PANEL_BUS_TYPE_SPI + #ifndef ESP_PANEL_TOUCH_BUS_HOST_ID + #ifdef CONFIG_ESP_PANEL_TOUCH_BUS_HOST_ID + #define ESP_PANEL_TOUCH_BUS_HOST_ID CONFIG_ESP_PANEL_TOUCH_BUS_HOST_ID + #else + #error "Missing configuration: ESP_PANEL_TOUCH_BUS_HOST_ID" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_SPI_IO_CS + #ifdef CONFIG_ESP_PANEL_TOUCH_SPI_IO_CS + #define ESP_PANEL_TOUCH_SPI_IO_CS CONFIG_ESP_PANEL_TOUCH_SPI_IO_CS + #else + #error "Missing configuration: ESP_PANEL_TOUCH_SPI_IO_CS" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_SPI_IO_SCK + #ifdef CONFIG_ESP_PANEL_TOUCH_SPI_IO_SCK + #define ESP_PANEL_TOUCH_SPI_IO_SCK CONFIG_ESP_PANEL_TOUCH_SPI_IO_SCK + #else + #error "Missing configuration: ESP_PANEL_TOUCH_SPI_IO_SCK" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_SPI_IO_MOSI + #ifdef CONFIG_ESP_PANEL_TOUCH_SPI_IO_MOSI + #define ESP_PANEL_TOUCH_SPI_IO_MOSI CONFIG_ESP_PANEL_TOUCH_SPI_IO_MOSI + #else + #error "Missing configuration: ESP_PANEL_TOUCH_SPI_IO_MOSI" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_SPI_IO_MISO + #ifdef CONFIG_ESP_PANEL_TOUCH_SPI_IO_MISO + #define ESP_PANEL_TOUCH_SPI_IO_MISO CONFIG_ESP_PANEL_TOUCH_SPI_IO_MISO + #else + #error "Missing configuration: ESP_PANEL_TOUCH_SPI_IO_MISO" + #endif + #endif + #else + #error "Invalid configuration: ESP_PANEL_TOUCH_BUS_TYPE" + #endif /* ESP_PANEL_TOUCH_BUS_TYPE */ + // Transformation Settings + #ifndef ESP_PANEL_TOUCH_SWAP_XY + #ifdef CONFIG_ESP_PANEL_TOUCH_SWAP_XY + #define ESP_PANEL_TOUCH_SWAP_XY CONFIG_ESP_PANEL_TOUCH_SWAP_XY + #endif + #endif + #ifndef ESP_PANEL_TOUCH_MIRROR_X + #ifdef CONFIG_ESP_PANEL_TOUCH_MIRROR_X + #define ESP_PANEL_TOUCH_MIRROR_X CONFIG_ESP_PANEL_TOUCH_MIRROR_X + #endif + #endif + #ifndef ESP_PANEL_TOUCH_MIRROR_Y + #ifdef CONFIG_ESP_PANEL_TOUCH_MIRROR_Y + #define ESP_PANEL_TOUCH_MIRROR_Y CONFIG_ESP_PANEL_TOUCH_MIRROR_Y + #endif + #endif + #ifndef ESP_PANEL_TOUCH_IO_RST + #ifdef CONFIG_ESP_PANEL_TOUCH_IO_RST + #define ESP_PANEL_TOUCH_IO_RST CONFIG_ESP_PANEL_TOUCH_IO_RST + #else + #error "Missing configuration: ESP_PANEL_TOUCH_IO_RST" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_RST_LEVEL + #ifdef CONFIG_ESP_PANEL_TOUCH_RST_LEVEL + #define ESP_PANEL_TOUCH_RST_LEVEL CONFIG_ESP_PANEL_TOUCH_RST_LEVEL + #else + #define ESP_PANEL_TOUCH_RST_LEVEL 0 + #endif + #endif + #ifndef ESP_PANEL_TOUCH_IO_INT + #ifdef CONFIG_ESP_PANEL_TOUCH_IO_INT + #define ESP_PANEL_TOUCH_IO_INT CONFIG_ESP_PANEL_TOUCH_IO_INT + #else + #error "Missing configuration: ESP_PANEL_TOUCH_IO_INT" + #endif + #endif + #ifndef ESP_PANEL_TOUCH_INT_LEVEL + #ifdef CONFIG_ESP_PANEL_TOUCH_INT_LEVEL + #define ESP_PANEL_TOUCH_INT_LEVEL CONFIG_ESP_PANEL_TOUCH_INT_LEVEL + #else + #define ESP_PANEL_TOUCH_INT_LEVEL 0 + #endif + #endif + #endif /* ESP_PANEL_USE_TOUCH */ + + // Backlight + #ifndef ESP_PANEL_USE_BACKLIGHT + #ifdef CONFIG_ESP_PANEL_USE_BACKLIGHT + #define ESP_PANEL_USE_BACKLIGHT CONFIG_ESP_PANEL_USE_BACKLIGHT + #else + #define ESP_PANEL_USE_BACKLIGHT 0 + #endif + #endif + #if ESP_PANEL_USE_BACKLIGHT + #ifndef ESP_PANEL_BACKLIGHT_IO + #ifdef CONFIG_ESP_PANEL_BACKLIGHT_IO + #define ESP_PANEL_BACKLIGHT_IO CONFIG_ESP_PANEL_BACKLIGHT_IO + #else + #error "Missing configuration: ESP_PANEL_BACKLIGHT_IO" + #endif + #endif + #ifndef ESP_PANEL_BACKLIGHT_ON_LEVEL + #ifdef CONFIG_ESP_PANEL_BACKLIGHT_ON_LEVEL + #define ESP_PANEL_BACKLIGHT_ON_LEVEL CONFIG_ESP_PANEL_BACKLIGHT_ON_LEVEL + #else + #define ESP_PANEL_BACKLIGHT_ON_LEVEL 1 + #endif + #endif + #ifndef ESP_PANEL_BACKLIGHT_IDLE_OFF + #ifdef CONFIG_ESP_PANEL_BACKLIGHT_IDLE_OFF + #define ESP_PANEL_BACKLIGHT_IDLE_OFF CONFIG_ESP_PANEL_BACKLIGHT_IDLE_OFF + #else + #define ESP_PANEL_BACKLIGHT_IDLE_OFF 0 + #endif + #endif + #endif /* ESP_PANEL_USE_BACKLIGHT */ + + // IO Expander + #ifndef ESP_PANEL_USE_EXPANDER + #ifdef CONFIG_ESP_PANEL_USE_EXPANDER + #define ESP_PANEL_USE_EXPANDER CONFIG_ESP_PANEL_USE_EXPANDER + #else + #define ESP_PANEL_USE_EXPANDER 0 + #endif + #endif + #if ESP_PANEL_USE_EXPANDER + // CHIP + #ifndef ESP_PANEL_EXPANDER_CHIP_CH422G + #ifdef CONFIG_ESP_PANEL_EXPANDER_CHIP_CH422G + #define ESP_PANEL_EXPANDER_CHIP_NAME CH422G + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_CHIP_HT8574 + #ifdef CONFIG_ESP_PANEL_EXPANDER_CHIP_HT8574 + #define ESP_PANEL_EXPANDER_CHIP_NAME HT8574 + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_CHIP_TCA95xx_8bit + #ifdef CONFIG_ESP_PANEL_EXPANDER_CHIP_TCA95xx_8bit + #define ESP_PANEL_EXPANDER_CHIP_NAME TCA95xx_8bit + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_TYPE_TCA95xx_16bit + #ifdef CONFIG_ESP_PANEL_EXPANDER_CHIP_TCA95xx_16bit + #define ESP_PANEL_EXPANDER_CHIP_NAME TCA95xx_16bit + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_SKIP_INIT_HOST + #ifdef CONFIG_ESP_PANEL_EXPANDER_SKIP_INIT_HOST + #define ESP_PANEL_EXPANDER_SKIP_INIT_HOST CONFIG_ESP_PANEL_EXPANDER_SKIP_INIT_HOST + #else + #define ESP_PANEL_EXPANDER_SKIP_INIT_HOST 0 + #endif + #endif + // Bus Settings + #ifndef ESP_PANEL_EXPANDER_HOST_ID + #ifdef CONFIG_ESP_PANEL_EXPANDER_HOST_ID + #define ESP_PANEL_EXPANDER_HOST_ID CONFIG_ESP_PANEL_EXPANDER_HOST_ID + #else + #error "Missing configuration: ESP_PANEL_EXPANDER_HOST_ID" + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_I2C_ADDRESS + #ifdef CONFIG_ESP_PANEL_EXPANDER_I2C_ADDRESS + #define ESP_PANEL_EXPANDER_I2C_ADDRESS CONFIG_ESP_PANEL_EXPANDER_I2C_ADDRESS + #else + #error "Missing configuration: ESP_PANEL_EXPANDER_I2C_ADDRESS" + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_I2C_CLK_HZ + #ifdef CONFIG_ESP_PANEL_EXPANDER_I2C_CLK_HZ + #define ESP_PANEL_EXPANDER_I2C_CLK_HZ CONFIG_ESP_PANEL_EXPANDER_I2C_CLK_HZ + #else + #error "Missing configuration: ESP_PANEL_EXPANDER_I2C_CLK_HZ" + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_I2C_SCL_PULLUP + #ifdef CONFIG_ESP_PANEL_EXPANDER_I2C_SCL_PULLUP + #define ESP_PANEL_EXPANDER_I2C_SCL_PULLUP CONFIG_ESP_PANEL_EXPANDER_I2C_SCL_PULLUP + #else + #define ESP_PANEL_EXPANDER_I2C_SCL_PULLUP 0 + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_I2C_SDA_PULLUP + #ifdef CONFIG_ESP_PANEL_EXPANDER_I2C_SDA_PULLUP + #define ESP_PANEL_EXPANDER_I2C_SDA_PULLUP CONFIG_ESP_PANEL_EXPANDER_I2C_SDA_PULLUP + #else + #define ESP_PANEL_EXPANDER_I2C_SDA_PULLUP 0 + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_I2C_IO_SCL + #ifdef CONFIG_ESP_PANEL_EXPANDER_I2C_IO_SCL + #define ESP_PANEL_EXPANDER_I2C_IO_SCL CONFIG_ESP_PANEL_EXPANDER_I2C_IO_SCL + #else + #error "Missing configuration: ESP_PANEL_EXPANDER_I2C_IO_SCL" + #endif + #endif + #ifndef ESP_PANEL_EXPANDER_I2C_IO_SDA + #ifdef CONFIG_ESP_PANEL_EXPANDER_I2C_IO_SDA + #define ESP_PANEL_EXPANDER_I2C_IO_SDA CONFIG_ESP_PANEL_EXPANDER_I2C_IO_SDA + #else + #error "Missing configuration: ESP_PANEL_EXPANDER_I2C_IO_SDA" + #endif + #endif + #endif /* ESP_PANEL_USE_EXPANDER */ +#endif /* ESP_PANEL_USE_CUSTOM_BOARD */ + +// *INDENT-OFF* diff --git a/src/ESP_Panel_Conf_Internal.h b/src/ESP_Panel_Conf_Internal.h index d45f004c..e446e10b 100644 --- a/src/ESP_Panel_Conf_Internal.h +++ b/src/ESP_Panel_Conf_Internal.h @@ -11,11 +11,12 @@ /* Handle special Kconfig options */ #ifndef ESP_PANEL_KCONFIG_IGNORE #include "sdkconfig.h" - #ifdef CONFIG_ESP_PANEL_CONF_SKIP - #define ESP_PANEL_CONF_SKIP + #ifdef CONFIG_ESP_PANEL_CONF_FILE_SKIP + #define ESP_PANEL_CONF_FILE_SKIP #endif #endif +#ifndef ESP_PANEL_CONF_FILE_SKIP /* If "ESP_Panel_Conf.h" is available from here, try to use it later */ #ifdef __has_include #if __has_include("ESP_Panel_Conf.h") @@ -30,9 +31,11 @@ #define ESP_PANEL_CONF_INCLUDE_INSIDE #endif #endif +#else +#endif /* If "ESP_Panel_Conf.h" is not skipped, include it */ -#ifndef ESP_PANEL_CONF_SKIP +#ifndef ESP_PANEL_CONF_FILE_SKIP #ifdef ESP_PANEL_CONF_PATH /* If there is a path defined for "ESP_Panel_Conf.h" use it */ #define __TO_STR_AUX(x) #x #define __TO_STR(x) __TO_STR_AUX(x) @@ -49,72 +52,13 @@ #endif #ifndef ESP_PANEL_CONF_INCLUDE_INSIDE -/* Supplement macro definitions based on sdkconfig, use default values if not defined */ -/*-------------------------------- Debug configurations --------------------------------*/ -#ifndef ESP_PANEL_CHECK_RESULT_ASSERT - #ifdef CONFIG_ESP_PANEL_CHECK_RESULT_ASSERT - #define ESP_PANEL_CHECK_RESULT_ASSERT CONFIG_ESP_PANEL_CHECK_RESULT_ASSERT - #else - #define ESP_PANEL_CHECK_RESULT_ASSERT (0) - #endif -#endif -#ifndef ESP_PANEL_ENABLE_LOG - #ifdef CONFIG_ESP_PANEL_ENABLE_LOG - #define ESP_PANEL_ENABLE_LOG CONFIG_ESP_PANEL_ENABLE_LOG - #else - #define ESP_PANEL_ENABLE_LOG (0) - #endif -#endif -/*----------------------------- Touch driver configurations -----------------------------*/ -#ifndef ESP_PANEL_TOUCH_MAX_POINTS - #ifdef CONFIG_ESP_PANEL_TOUCH_MAX_POINTS - #define ESP_PANEL_TOUCH_MAX_POINTS CONFIG_ESP_PANEL_TOUCH_MAX_POINTS - #else - #define ESP_PANEL_TOUCH_MAX_POINTS (5) - #endif -#endif -#ifndef ESP_PANEL_TOUCH_MAX_BUTTONS - #ifdef CONFIG_ESP_PANEL_TOUCH_MAX_BUTTONS - #define ESP_PANEL_TOUCH_MAX_BUTTONS CONFIG_ESP_PANEL_TOUCH_MAX_BUTTONS - #else - #define ESP_PANEL_TOUCH_MAX_BUTTONS (1) - #endif -#endif -#ifndef ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD - #ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD - #define ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD CONFIG_ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD - #else - #define ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD (400) - #endif -#endif -#ifndef ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE - #ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE - #define ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE CONFIG_ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE - #else - #define ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE (0) - #endif -#endif -#ifndef ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE - #ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE - #define ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE CONFIG_ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE - #else - #define ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE (0) - #endif -#endif -#ifndef ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS - #ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS - #define ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS CONFIG_ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS - #else - #define ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS (1) - #endif -#endif -#ifndef ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING - #ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING - #define ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING CONFIG_ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING - #else - #define ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING (1) - #endif -#endif + /** + * There are two purposes to include the this file: + * 1. Convert configuration items starting with `CONFIG_` to the required configuration items. + * 2. Define default values for configuration items that are not defined to keep compatibility. + * + */ + #include "ESP_Panel_Conf_Kconfig.h" #endif // *INDENT-OFF* diff --git a/src/ESP_Panel_Conf_Kconfig.h b/src/ESP_Panel_Conf_Kconfig.h new file mode 100644 index 00000000..2d233856 --- /dev/null +++ b/src/ESP_Panel_Conf_Kconfig.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +/*----------------------------- General configurations -----------------------------*/ +#ifndef ESP_PANEL_CHECK_RESULT_ASSERT +#ifdef CONFIG_ESP_PANEL_CHECK_RESULT_ASSERT +#define ESP_PANEL_CHECK_RESULT_ASSERT CONFIG_ESP_PANEL_CHECK_RESULT_ASSERT +#else +#define ESP_PANEL_CHECK_RESULT_ASSERT (0) +#endif +#endif +#ifndef ESP_PANEL_ENABLE_LOG +#ifdef CONFIG_ESP_PANEL_ENABLE_LOG +#define ESP_PANEL_ENABLE_LOG CONFIG_ESP_PANEL_ENABLE_LOG +#else +#define ESP_PANEL_ENABLE_LOG (0) +#endif +#endif + +/*----------------------------- Touch driver configurations -----------------------------*/ +#ifndef ESP_PANEL_TOUCH_MAX_POINTS +#ifdef CONFIG_ESP_PANEL_TOUCH_MAX_POINTS +#define ESP_PANEL_TOUCH_MAX_POINTS CONFIG_ESP_PANEL_TOUCH_MAX_POINTS +#else +#define ESP_PANEL_TOUCH_MAX_POINTS (5) +#endif +#endif +#ifndef ESP_PANEL_TOUCH_MAX_BUTTONS +#ifdef CONFIG_ESP_PANEL_TOUCH_MAX_BUTTONS +#define ESP_PANEL_TOUCH_MAX_BUTTONS CONFIG_ESP_PANEL_TOUCH_MAX_BUTTONS +#else +#define ESP_PANEL_TOUCH_MAX_BUTTONS (1) +#endif +#endif +#ifndef ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD +#ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD +#define ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD CONFIG_ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD +#else +#define ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD (400) +#endif +#endif +#ifndef ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE +#ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE +#define ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE CONFIG_ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE +#else +#define ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE (0) +#endif +#endif +#ifndef ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE +#ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE +#define ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE CONFIG_ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE +#else +#define ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE (0) +#endif +#endif +#ifndef ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS +#ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS +#define ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS CONFIG_ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS +#else +#define ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS (1) +#endif +#endif +#ifndef ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING +#ifdef CONFIG_ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING +#define ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING CONFIG_ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING +#else +#define ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING (1) +#endif +#endif diff --git a/src/ESP_Panel_Library.h b/src/ESP_Panel_Library.h index a602f15b..23d72bd0 100644 --- a/src/ESP_Panel_Library.h +++ b/src/ESP_Panel_Library.h @@ -29,6 +29,7 @@ #include "lcd/GC9A01.h" #include "lcd/GC9B71.h" #include "lcd/ILI9341.h" +#include "lcd/NV3022B.h" #include "lcd/SH8601.h" #include "lcd/SPD2010.h" #include "lcd/ST7262.h" diff --git a/src/backlight/Kconfig.in b/src/backlight/Kconfig.in new file mode 100644 index 00000000..a9e53baa --- /dev/null +++ b/src/backlight/Kconfig.in @@ -0,0 +1,20 @@ +menu "Backlight" + config ESP_PANEL_BACKLIGHT_IO + int "Pin num" + default 45 + range 0 100 + + config ESP_PANEL_BACKLIGHT_ON_LEVEL + bool "The level to turn on" + default y + + config ESP_PANEL_BACKLIGHT_IDLE_OFF + bool "Turn off after initializing the panel" + default n + help + Set to 1 if you want to turn off the backlight after initializing the panel; otherwise, set it to turn on + + config ESP_PANEL_LCD_BL_USE_PWM + bool "Use PWM to control the brightness" + default y +endmenu diff --git a/src/board/Kconfig.board b/src/board/Kconfig.board new file mode 100644 index 00000000..18a2a9d1 --- /dev/null +++ b/src/board/Kconfig.board @@ -0,0 +1,25 @@ +menu "Board" + choice + prompt "Select the board type" + default ESP_PANEL_USE_SUPPORTED_BOARD + + config ESP_PANEL_USE_SUPPORTED_BOARD + bool "Supported board" + help + Enable this option if you are using a supported board. + + config ESP_PANEL_USE_CUSTOM_BOARD + bool "Custom board" + help + Select this option if you are using a custom board. + endchoice + + if ESP_PANEL_USE_SUPPORTED_BOARD + orsource "./Kconfig.board_supported" + endif + + if ESP_PANEL_USE_CUSTOM_BOARD + orsource "./Kconfig.board_custom" + endif + +endmenu diff --git a/src/board/Kconfig.board_custom b/src/board/Kconfig.board_custom new file mode 100644 index 00000000..cb0298aa --- /dev/null +++ b/src/board/Kconfig.board_custom @@ -0,0 +1,803 @@ +menu "Custom board configurations" + config ESP_PANEL_USE_LCD + bool "Use LCD" + default n + help + Enable this option if you are using a LCD. + + menu "LCD settings" + depends on ESP_PANEL_USE_LCD + choice + prompt "Controller" + default ESP_PANEL_LCD_CONTROLLER_ILI9341 + + config ESP_PANEL_LCD_CONTROLLER_EK9716B + bool "EK9716B" + + config ESP_PANEL_LCD_CONTROLLER_GC9A01 + bool "GC9A01" + + config ESP_PANEL_LCD_CONTROLLER_GC9B71 + bool "GC9B71" + + config ESP_PANEL_LCD_CONTROLLER_GC9503 + bool "GC9503" + + config ESP_PANEL_LCD_CONTROLLER_ILI9341 + bool "ILI9341" + + config ESP_PANEL_LCD_CONTROLLER_NV3022B + bool "NV3022B" + + config ESP_PANEL_LCD_CONTROLLER_SH8601 + bool "SH8601" + + config ESP_PANEL_LCD_CONTROLLER_SPD2010 + bool "SPD2010" + + config ESP_PANEL_LCD_CONTROLLER_ST7262 + bool "ST7262" + + config ESP_PANEL_LCD_CONTROLLER_ST7701 + bool "ST7701" + + config ESP_PANEL_LCD_CONTROLLER_ST7789 + bool "ST7789" + + config ESP_PANEL_LCD_CONTROLLER_ST7796 + bool "ST7796" + + config ESP_PANEL_LCD_CONTROLLER_ST77916 + bool "ST77916" + + config ESP_PANEL_LCD_CONTROLLER_ST77922 + bool "ST77922" + endchoice + + config ESP_PANEL_LCD_WIDTH + int "Pixels in width (horizontal resolution)" + default 320 + range 1 10000 + + config ESP_PANEL_LCD_HEIGHT + int "Pixels in height (vertical resolution)" + default 240 + range 1 10000 + + menu "Bus settings" + config ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + bool "Skip to initialize bus host" + default n + help + If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance. It is useful if other devices use the same host. Please ensure that the host is initialized only once. Set to 1 if only the RGB interface is used without the 3-wire SPI interface, + + choice + prompt "Bus type" + default ESP_PANEL_LCD_BUS_TYPE_SPI + + config ESP_PANEL_LCD_BUS_TYPE_SPI + bool "SPI" + + config ESP_PANEL_LCD_BUS_TYPE_QSPI + bool "QSPI" + + config ESP_PANEL_LCD_BUS_TYPE_RGB + bool "RGB" + endchoice + + config ESP_PANEL_LCD_BUS_TYPE + int + default 1 if ESP_PANEL_LCD_BUS_TYPE_SPI + default 2 if ESP_PANEL_LCD_BUS_TYPE_QSPI + default 3 if ESP_PANEL_LCD_BUS_TYPE_RGB + + menu "SPI bus settings" + depends on ESP_PANEL_LCD_BUS_TYPE_SPI + + config ESP_PANEL_LCD_BUS_HOST_ID + int "SPI host ID" + default 1 + range 1 3 + + config ESP_PANEL_LCD_SPI_MODE + int "SPI mode" + default 0 + range 0 3 + + config ESP_PANEL_LCD_SPI_CLK_HZ + int "SPI clock frequency (Hz)" + default 40000000 + range 1 80000000 + + config ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + int "SPI transaction queue size" + default 10 + range 1 100 + + config ESP_PANEL_LCD_SPI_CMD_BITS + int "SPI command bit length" + default 8 + range 0 32 + + config ESP_PANEL_LCD_SPI_PARAM_BITS + int "SPI parameter bit length" + default 8 + range 0 32 + + menu "Pins" + config ESP_PANEL_LCD_SPI_IO_CS + int "CS" + default 5 + range -1 100 + + config ESP_PANEL_LCD_SPI_IO_DC + int "DC (RS)" + default 4 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_SCK + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "SCLK (SCL)" + default 7 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_MOSI + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "MOSI (SDA)" + default 6 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_MISO + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "MISO (SDO)" + default -1 + range -1 100 + endmenu + endmenu + + menu "QSPI bus settings" + depends on ESP_PANEL_LCD_BUS_TYPE_QSPI + + config ESP_PANEL_LCD_BUS_HOST_ID + int "SPI host ID" + default 1 + range 1 3 + + config ESP_PANEL_LCD_SPI_MODE + int "SPI mode" + default 0 + range 0 3 + + config ESP_PANEL_LCD_SPI_CLK_HZ + int "SPI clock frequency (Hz)" + default 40000000 + range 1 80000000 + + config ESP_PANEL_LCD_SPI_TRANS_QUEUE_SZ + int "SPI transaction queue size" + default 10 + range 1 100 + + config ESP_PANEL_LCD_SPI_CMD_BITS + int "SPI command bit length" + default 32 + range 0 32 + + config ESP_PANEL_LCD_SPI_PARAM_BITS + int "SPI parameter bit length" + default 8 + range 0 32 + + menu "Pins" + config ESP_PANEL_LCD_SPI_IO_CS + int "CS" + default 5 + range -1 100 + + config ESP_PANEL_LCD_SPI_IO_SCK + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "SCLK (SCL)" + default 9 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_DATA0 + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "DATA0 (SDA)" + default 10 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_DATA1 + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "DATA1" + default 11 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_DATA2 + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "DATA2" + default 12 + range 0 100 + + config ESP_PANEL_LCD_SPI_IO_DATA3 + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + int "DATA3" + default 13 + range 0 100 + endmenu + endmenu + + menu "RGB bus settings" + depends on ESP_PANEL_LCD_BUS_TYPE_RGB + + menu "3-wire SPI interface" + depends on !ESP_PANEL_LCD_BUS_SKIP_INIT_HOST + config ESP_PANEL_LCD_3WIRE_SPI_CS_USE_EXPNADER + bool "Use IO expander to control CS" + default n + + config ESP_PANEL_LCD_3WIRE_SPI_SCL_USE_EXPNADER + bool "Use IO expander to control SCL" + default n + + config ESP_PANEL_LCD_3WIRE_SPI_SDA_USE_EXPNADER + bool "Use IO expander to control SDA" + default n + + config ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO + bool "Auto delete panel IO instance" + default n + help + If set to 1, the panel IO instance will be deleted automatically when the panel is initialized. If the 3-wire SPI pins are sharing other pins of the RGB interface to save GPIOs, please set it to 1 to release the panel IO and its pins (except CS signal). + + config ESP_PANEL_LCD_FLAGS_MIRROR_BY_CMD + bool "Mirror by LCD command instead of software" + default n if ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO + default y if !ESP_PANEL_LCD_FLAGS_AUTO_DEL_PANEL_IO + help + if set to 1, the `mirror()` function will be implemented by LCD command (e.g. 36h) + + menu "Pins" + config ESP_PANEL_LCD_3WIRE_SPI_IO_CS + int "CS" + default 0 + range 0 100 + + config ESP_PANEL_LCD_3WIRE_SPI_IO_SCK + int "SCL" + default 1 + range 0 100 + + config ESP_PANEL_LCD_3WIRE_SPI_IO_SDA + int "SDA" + default 2 + range 0 100 + endmenu + endmenu + + menu "RGB interface" + config ESP_PANEL_LCD_RGB_CLK_HZ + int "RGB clock frequency (Hz)" + default 16000000 + range 1 40000000 + + config ESP_PANEL_LCD_RGB_HPW + int "HPW (Horizontal Pulse Width)" + default 10 + range 0 1000 + + config ESP_PANEL_LCD_RGB_HBP + int "HBP (Horizontal Back Porch)" + default 10 + range 1 1000 + + config ESP_PANEL_LCD_RGB_HFP + int "HFP (Horizontal Front Porch)" + default 20 + range 0 1000 + + config ESP_PANEL_LCD_RGB_VPW + int "VPW (Vertical Pulse Width)" + default 10 + range 0 1000 + + config ESP_PANEL_LCD_RGB_VBP + int "VBP (Vertical Back Porch)" + default 10 + range 0 1000 + + config ESP_PANEL_LCD_RGB_VFP + int "VFP (Vertical Front Porch)" + default 10 + range 0 1000 + + config ESP_PANEL_LCD_RGB_PCLK_ACTIVE_NEG + bool "PCLK active on the falling edge" + default n + + choice + prompt "Data width & pixel format" + default ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + + config ESP_PANEL_LCD_RGB_DATA_WIDTH_8 + bool "8-bit & RGB888" + + config ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + bool "16-bit & RGB565" + endchoice + + config ESP_PANEL_LCD_RGB_DATA_WIDTH + int + default 8 if ESP_PANEL_LCD_RGB_DATA_WIDTH_8 + default 16 if ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + + config ESP_PANEL_LCD_RGB_PIXEL_BITS + int + default 24 if ESP_PANEL_LCD_RGB_DATA_WIDTH_8 + default 16 if ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + + menu "Pins" + config ESP_PANEL_LCD_RGB_IO_HSYNC + int "HSYNC" + default 46 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_VSYNC + int "VSYNC" + default 3 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DE + int "DE" + default 17 + range -1 100 + + config ESP_PANEL_LCD_RGB_IO_PCLK + int "PCLK" + default 9 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DISP + int "DISP" + default -1 + range -1 100 + + config ESP_PANEL_LCD_RGB_IO_DATA0 + int "DATA0" + default 10 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA1 + int "DATA1" + default 11 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA2 + int "DATA2" + default 12 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA3 + int "DATA3" + default 13 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA4 + int "DATA4" + default 14 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA5 + int "DATA5" + default 21 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA6 + int "DATA6" + default 47 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA7 + int "DATA7" + default 48 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA8 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA8" + default 45 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA9 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA9" + default 38 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA10 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA10" + default 39 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA11 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA11" + default 40 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA12 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA12" + default 41 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA13 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA13" + default 42 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA14 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA14" + default 2 + range 0 100 + + config ESP_PANEL_LCD_RGB_IO_DATA15 + depends on ESP_PANEL_LCD_RGB_DATA_WIDTH_16 + int "DATA15" + default 1 + range 0 100 + endmenu + endmenu + endmenu + endmenu + + menu "Color settings" + choice + prompt "Color bits" + default ESP_PANEL_LCD_COLOR_BITS_16 + + config ESP_PANEL_LCD_COLOR_BITS_8 + bool "8 bits" + + config ESP_PANEL_LCD_COLOR_BITS_16 + bool "16 bits" + endchoice + + config ESP_PANEL_LCD_COLOR_BITS + int + default 8 if ESP_PANEL_LCD_COLOR_BITS_8 + default 16 if ESP_PANEL_LCD_COLOR_BITS_16 + + choice + prompt "Color RGB element order" + default ESP_PANEL_LCD_COLOR_ORDER_RGB + + config ESP_PANEL_LCD_COLOR_ORDER_RGB + bool "RGB" + + config ESP_PANEL_LCD_COLOR_ORDER_BGR + bool "BGR" + endchoice + + config ESP_PANEL_LCD_BGR_ORDER + bool + default n if ESP_PANEL_LCD_COLOR_ORDER_RGB + default y if ESP_PANEL_LCD_COLOR_ORDER_BGR + + config ESP_PANEL_LCD_INEVRT_COLOR + bool "Invert color bit (0->1, 1->0)" + default n + endmenu + + menu "Transformation settings" + config ESP_PANEL_LCD_SWAP_XY + bool "Swap X and Y Axes" + default n + + config ESP_PANEL_LCD_MIRROR_X + bool "Mirror X Axes" + default n + + config ESP_PANEL_LCD_MIRROR_Y + bool "Mirror Y Axes" + default n + endmenu + + config ESP_PANEL_LCD_IO_RST + int "Reset pin" + default -1 + range -1 100 + + config ESP_PANEL_LCD_RST_LEVEL + depends on ESP_PANEL_LCD_IO_RST >= 0 + int "Reset level" + default 0 + range 0 1 + endmenu + + config ESP_PANEL_USE_TOUCH + bool "Use LCD touch" + default n + help + Enable this option if you are using a LCD touch. + + menu "LCD touch settings" + depends on ESP_PANEL_USE_TOUCH + choice + prompt "Controller" + default ESP_PANEL_TOUCH_CONTROLLER_TT21100 + + config ESP_PANEL_TOUCH_CONTROLLER_CST816S + bool "CST816S" + + config ESP_PANEL_TOUCH_CONTROLLER_FT5X06 + bool "FT5x06" + + config ESP_PANEL_TOUCH_CONTROLLER_GT911 + bool "GT911" + + config ESP_PANEL_TOUCH_CONTROLLER_GT1151 + bool "GT1151" + + config ESP_PANEL_TOUCH_CONTROLLER_ST1633 + bool "ST1633" + + config ESP_PANEL_TOUCH_CONTROLLER_ST7123 + bool "ST7123" + + config ESP_PANEL_TOUCH_CONTROLLER_TT21100 + bool "TT21100" + + config ESP_PANEL_TOUCH_CONTROLLER_STMPE610 + bool "STMPE610" + endchoice + + config ESP_PANEL_TOUCH_H_RES + int "Pixels in width (horizontal resolution)" + default ESP_PANEL_LCD_WIDTH if ESP_PANEL_USE_LCD + default 240 if !ESP_PANEL_USE_LCD + range 1 10000 + + config ESP_PANEL_TOUCH_V_RES + int "Pixels in height (vertical resolution)" + default ESP_PANEL_LCD_HEIGHT if ESP_PANEL_USE_LCD + default 240 if !ESP_PANEL_USE_LCD + range 1 10000 + + menu "Bus settings" + config ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + bool "Skip to initialize bus host" + default n + help + If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance. It is useful if other devices use the same host. Please ensure that the host is initialized only once. Set to 1 if only the RGB interface is used without the 3-wire SPI interface, + + choice + prompt "Bus type" + default ESP_PANEL_TOUCH_BUS_TYPE_I2C + + config ESP_PANEL_TOUCH_BUS_TYPE_I2C + bool "I2C" + + config ESP_PANEL_TOUCH_BUS_TYPE_SPI + bool "SPI" + endchoice + + config ESP_PANEL_TOUCH_BUS_TYPE + int + default 1 if ESP_PANEL_TOUCH_BUS_TYPE_SPI + default 4 if ESP_PANEL_TOUCH_BUS_TYPE_I2C + + menu "I2C bus settings" + depends on ESP_PANEL_TOUCH_BUS_TYPE_I2C + + config ESP_PANEL_TOUCH_BUS_HOST_ID + int "I2C host ID" + default 0 + range 0 1 + + config ESP_PANEL_TOUCH_I2C_ADDRESS + int "I2C address (7-bit)" + default 0 + range 0 255 + help + Typically set to 0 to use the default address. For touchs with only one address, set to 0. For touchs with multiple addresses, set to 0 or the address. Like GT911, there are two addresses: 0x5D(default) and 0x14 + + config ESP_PANEL_TOUCH_I2C_CLK_HZ + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + int "I2C clock frequency (Hz)" + default 400000 + range 1 400000 + + config ESP_PANEL_TOUCH_I2C_SCL_PULLUP + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + bool "Enable SCL Pull-up" + default y + + config ESP_PANEL_TOUCH_I2C_SDA_PULLUP + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + bool "Enable SDA Pull-up" + default y + + config ESP_PANEL_TOUCH_I2C_IO_SCL + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + int "SCL pin" + default 18 + range 0 100 + + config ESP_PANEL_TOUCH_I2C_IO_SDA + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + int "SDA pin" + default 8 + range 0 100 + endmenu + + menu "SPI bus settings" + depends on ESP_PANEL_TOUCH_BUS_TYPE_SPI + config ESP_PANEL_TOUCH_BUS_HOST_ID + int "SPI host ID" + default 1 + range 1 3 + + config ESP_PANEL_TOUCH_SPI_IO_CS + int "CS pin" + default 5 + range -1 100 + + config ESP_PANEL_TOUCH_SPI_IO_SCK + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + int "SCK (SCL) pin" + default 7 + range 0 100 + + config ESP_PANEL_TOUCH_SPI_IO_MOSI + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + int "MOSI (SDA) pin" + default 6 + range 0 100 + + config ESP_PANEL_TOUCH_SPI_IO_MISO + depends on !ESP_PANEL_TOUCH_BUS_SKIP_INIT_HOST + int "MISO (SDO) pin" + default 9 + range 0 100 + endmenu + endmenu + + menu "Transformation settings" + config ESP_PANEL_TOUCH_SWAP_XY + bool "Swap X and Y Axes" + default n + + config ESP_PANEL_TOUCH_MIRROR_X + bool "Mirror X Axes" + default n + + config ESP_PANEL_TOUCH_MIRROR_Y + bool "Mirror Y Axes" + default n + endmenu + + config ESP_PANEL_TOUCH_IO_RST + int "Reset pin" + default -1 + range -1 100 + + config ESP_PANEL_TOUCH_RST_LEVEL + depends on ESP_PANEL_TOUCH_IO_RST >= 0 + int "Reset level" + default 0 + range 0 1 + + config ESP_PANEL_TOUCH_IO_INT + int "Interrupt pin" + default -1 + range -1 100 + + config ESP_PANEL_TOUCH_INT_LEVEL + depends on ESP_PANEL_TOUCH_IO_INT >= 0 + int "Interrupt level" + default 0 + range 0 1 + endmenu + + config ESP_PANEL_USE_BACKLIGHT + bool "Use Backlight" + default n + help + Enable this option if you are using the backlight. + + menu "Backlight settings" + depends on ESP_PANEL_USE_BACKLIGHT + config ESP_PANEL_BACKLIGHT_IO + int "Pin" + default 45 + range 0 100 + + config ESP_PANEL_BACKLIGHT_ON_LEVEL + int "On level" + default 1 + range 0 1 + + config ESP_PANEL_BACKLIGHT_IDLE_OFF + bool "Idle off" + default n + help + Set to 1 if you want to turn off the backlight after initializing the panel; otherwise, set it to turn on. + endmenu + + config ESP_PANEL_USE_EXPANDER + bool "Use IO expander" + default n + help + Enable this option if you are using an IO expander. + + menu "IO expander settings" + depends on ESP_PANEL_USE_EXPANDER + choice + prompt "Chip" + default ESP_PANEL_EXPANDER_CHIP_TCA95xx_8bit + + config ESP_PANEL_EXPANDER_CHIP_CH422G + bool "CH422G" + + config ESP_PANEL_EXPANDER_CHIP_HT8574 + bool "HT8574" + + config ESP_PANEL_EXPANDER_CHIP_TCA95xx_8bit + bool "TCA95xx_8bit" + + config ESP_PANEL_EXPANDER_TYPE_TCA95xx_16bit + bool "TCA95xx_16bit" + endchoice + + config ESP_PANEL_EXPANDER_SKIP_INIT_HOST + bool "Skip to initialize bus host" + default n + help + If set to 1, the bus will skip to initialize the corresponding host. Users need to initialize the host in advance. It is useful if other devices use the same host. Please ensure that the host is initialized only once. Set to 1 if only the RGB interface is used without the 3-wire SPI interface, + + menu "I2C bus settings" + config ESP_PANEL_EXPANDER_HOST_ID + int "I2C host ID" + default 0 + range 0 1 + + config ESP_PANEL_EXPANDER_I2C_ADDRESS + int "I2C address (7-bit)" + default 0 + range 0 255 + help + The actual I2C address. Even for the same model of IC, the I2C address may be different, and confirmation based on the actual hardware connection is required. + + config ESP_PANEL_EXPANDER_I2C_CLK_HZ + depends on !ESP_PANEL_EXPANDER_BUS_SKIP_INIT_HOST + int "I2C clock frequency (Hz)" + default 400000 + range 1 400000 + + config ESP_PANEL_EXPANDER_I2C_SCL_PULLUP + depends on !ESP_PANEL_EXPANDER_BUS_SKIP_INIT_HOST + bool "Enable SCL Pull-up" + default y + + config ESP_PANEL_EXPANDER_I2C_SDA_PULLUP + depends on !ESP_PANEL_EXPANDER_BUS_SKIP_INIT_HOST + bool "Enable SDA Pull-up" + default y + + config ESP_PANEL_EXPANDER_I2C_IO_SCL + depends on !ESP_PANEL_EXPANDER_BUS_SKIP_INIT_HOST + int "SCL pin" + default 18 + range 0 100 + + config ESP_PANEL_EXPANDER_I2C_IO_SDA + depends on !ESP_PANEL_EXPANDER_BUS_SKIP_INIT_HOST + int "SDA pin" + default 8 + range 0 100 + endmenu + endmenu +endmenu diff --git a/src/board/Kconfig.board_supported b/src/board/Kconfig.board_supported new file mode 100644 index 00000000..9ec20df7 --- /dev/null +++ b/src/board/Kconfig.board_supported @@ -0,0 +1,60 @@ +menu "Supported board configurations" + choice + prompt "Select the manufacturer" + default BOARD_MANUFACTURER_ALL + + config BOARD_MANUFACTURER_ALL + bool "All" + help + Espressif, Elecrow, M5Stack, Shenzhen Jingcai Intelligent, Waveshare + + config BOARD_MANUFACTURER_ESPRESSIF + bool "Espressif" + help + https://www.espressif.com/en/products/devkits + + config BOARD_MANUFACTURER_ELECROW + bool "Elecrow" + help + https://www.elecrow.com + + config BOARD_MANUFACTURER_M5STACK + bool "M5Stack" + help + https://m5stack.com/ + + config BOARD_MANUFACTURER_JINGCAI + bool "Shenzhen Jingcai Intelligent" + help + https://www.displaysmodule.com/ + + config BOARD_MANUFACTURER_WAVESHARE + bool "Waveshare" + help + https://www.waveshare.com/ + endchoice + + choice + prompt "Select a target board" + + if BOARD_MANUFACTURER_ESPRESSIF || BOARD_MANUFACTURER_ALL + orsource "./espressif/Kconfig.espressif" + endif + + if BOARD_MANUFACTURER_ELECROW || BOARD_MANUFACTURER_ALL + orsource "./elecrow/Kconfig.elecrow" + endif + + if BOARD_MANUFACTURER_M5STACK || BOARD_MANUFACTURER_ALL + orsource "./m5stack/Kconfig.m5stack" + endif + + if BOARD_MANUFACTURER_JINGCAI || BOARD_MANUFACTURER_ALL + orsource "./jingcai/Kconfig.jingcai" + endif + + if BOARD_MANUFACTURER_WAVESHARE || BOARD_MANUFACTURER_ALL + orsource "./waveshare/Kconfig.waveshare" + endif + endchoice +endmenu diff --git a/src/board/elecrow/CROWPANEL_7_0.h b/src/board/elecrow/CROWPANEL_7_0.h index 98ff3f5e..46ba70a0 100644 --- a/src/board/elecrow/CROWPANEL_7_0.h +++ b/src/board/elecrow/CROWPANEL_7_0.h @@ -93,21 +93,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/elecrow/Kconfig.elecrow b/src/board/elecrow/Kconfig.elecrow new file mode 100644 index 00000000..7f06eba4 --- /dev/null +++ b/src/board/elecrow/Kconfig.elecrow @@ -0,0 +1,4 @@ +config BOARD_ELECROW_CROWPANEL_7_0 + bool "ELECROW_CROWPANEL_7_0" + help + https://www.elecrow.com/esp32-display-7-inch-hmi-display-rgb-tft-lcd-touch-screen-support-lvgl.html diff --git a/src/board/espressif/ESP32_C3_LCDKIT.h b/src/board/espressif/ESP32_C3_LCDKIT.h index 0d51a199..99597d3f 100644 --- a/src/board/espressif/ESP32_C3_LCDKIT.h +++ b/src/board/espressif/ESP32_C3_LCDKIT.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_BOX.h b/src/board/espressif/ESP32_S3_BOX.h index 5142e3c0..801ddf16 100644 --- a/src/board/espressif/ESP32_S3_BOX.h +++ b/src/board/espressif/ESP32_S3_BOX.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_BOX_3.h b/src/board/espressif/ESP32_S3_BOX_3.h index db765748..65ab88fc 100644 --- a/src/board/espressif/ESP32_S3_BOX_3.h +++ b/src/board/espressif/ESP32_S3_BOX_3.h @@ -69,7 +69,7 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ diff --git a/src/board/espressif/ESP32_S3_BOX_3_BETA.h b/src/board/espressif/ESP32_S3_BOX_3_BETA.h index 53f85005..8afc9757 100644 --- a/src/board/espressif/ESP32_S3_BOX_3_BETA.h +++ b/src/board/espressif/ESP32_S3_BOX_3_BETA.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_BOX_LITE.h b/src/board/espressif/ESP32_S3_BOX_LITE.h index 196a9ce0..dc0efa75 100644 --- a/src/board/espressif/ESP32_S3_BOX_LITE.h +++ b/src/board/espressif/ESP32_S3_BOX_LITE.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_EYE.h b/src/board/espressif/ESP32_S3_EYE.h index e4065e61..7b8ac7d4 100644 --- a/src/board/espressif/ESP32_S3_EYE.h +++ b/src/board/espressif/ESP32_S3_EYE.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_KORVO_2.h b/src/board/espressif/ESP32_S3_KORVO_2.h index 000af5c2..a6262a99 100644 --- a/src/board/espressif/ESP32_S3_KORVO_2.h +++ b/src/board/espressif/ESP32_S3_KORVO_2.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h index 16e933c7..7fd47cc4 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD.h @@ -109,21 +109,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h index ebe7cd3d..e909972c 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2.h @@ -93,21 +93,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h index 83a43a2f..cefbcb55 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_2_V1_5.h @@ -93,21 +93,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h index fa0a2999..a4626e5d 100644 --- a/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h +++ b/src/board/espressif/ESP32_S3_LCD_EV_BOARD_V1_5.h @@ -109,21 +109,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/ESP32_S3_USB_OTG.h b/src/board/espressif/ESP32_S3_USB_OTG.h index 17398827..0fd9eded 100644 --- a/src/board/espressif/ESP32_S3_USB_OTG.h +++ b/src/board/espressif/ESP32_S3_USB_OTG.h @@ -69,21 +69,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/espressif/Kconfig.espressif b/src/board/espressif/Kconfig.espressif new file mode 100644 index 00000000..57d934ef --- /dev/null +++ b/src/board/espressif/Kconfig.espressif @@ -0,0 +1,59 @@ +config BOARD_ESP32_C3_LCDKIT + bool "ESP32-C3-LCDkit" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32c3/esp32-c3-lcdkit/index.html + +config BOARD_ESP32_S3_BOX + bool "ESP32-S3-Box" + help + https://github.com/espressif/esp-box/tree/master + +config BOARD_ESP32_S3_BOX_3 + bool "ESP32-S3-Box-3 & ESP32-S3-Box-3B" + help + https://github.com/espressif/esp-box/tree/master + +config BOARD_ESP32_S3_BOX_3_BETA + bool "ESP32-S3-Box-3(beta)" + help + https://github.com/espressif/esp-box/tree/c4c954888e11250423f083df0067d99e22d59fbe + +config BOARD_ESP32_S3_BOX_LITE + bool "ESP32-S3-Box-Lite" + help + https://github.com/espressif/esp-box/tree/master + +config BOARD_ESP32_S3_EYE + bool "ESP32-S3-EYE" + help + https://github.com/espressif/esp-who/blob/master/docs/en/get-started/ESP32-S3-EYE_Getting_Started_Guide.md + +config BOARD_ESP32_S3_KORVO_2 + bool "ESP32-S3-Korvo-2" + help + https://docs.espressif.com/projects/esp-adf/en/latest/design-guide/dev-boards/user-guide-esp32-s3-korvo-2.html + +config BOARD_ESP32_S3_LCD_EV_BOARD + bool "ESP32-S3-LCD-EV-Board(v1.1-v1.4)" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + +config BOARD_ESP32_S3_LCD_EV_BOARD_V1_5 + bool "ESP32-S3-LCD-EV-Board(v1.5)" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html + +config BOARD_ESP32_S3_LCD_EV_BOARD_2 + bool "ESP32-S3-LCD-EV-Board-2(v1.1-v1.4)" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide_v1.4.html + +config BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5 + bool "ESP32-S3-LCD-EV-Board-2(v1.5)" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html + +config BOARD_ESP32_S3_USB_OTG + bool "ESP32-S3-USB-OTG" + help + https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-usb-otg/index.html diff --git a/src/board/jingcai/ESP32_4848S040C_I_Y_3.h b/src/board/jingcai/ESP32_4848S040C_I_Y_3.h index f5e94acb..3c23176c 100644 --- a/src/board/jingcai/ESP32_4848S040C_I_Y_3.h +++ b/src/board/jingcai/ESP32_4848S040C_I_Y_3.h @@ -107,7 +107,7 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ diff --git a/src/board/jingcai/Kconfig.jingcai b/src/board/jingcai/Kconfig.jingcai new file mode 100644 index 00000000..2779f1a7 --- /dev/null +++ b/src/board/jingcai/Kconfig.jingcai @@ -0,0 +1,4 @@ +config BOARD_ESP32_4848S040C_I_Y_3 + bool "ESP32-4848S040C_I_Y_3" + help + https://www.displaysmodule.com/sale-41828962-experience-the-power-of-the-esp32-display-module-sku-esp32-4848s040c-i-y-3.html diff --git a/src/board/m5stack/Kconfig.m5stack b/src/board/m5stack/Kconfig.m5stack new file mode 100644 index 00000000..e56b468d --- /dev/null +++ b/src/board/m5stack/Kconfig.m5stack @@ -0,0 +1,14 @@ +config BOARD_M5STACK_M5CORE2 + bool "M5STACK_M5CORE2" + help + https://docs.m5stack.com/en/core/core2 + +config BOARD_M5STACK_M5CORES3 + bool "M5STACK_M5CORES3" + help + https://docs.m5stack.com/en/core/CoreS3 + +config BOARD_M5STACK_M5DIAL + bool "M5STACK_M5DIAL" + help + https://docs.m5stack.com/en/core/M5Dial diff --git a/src/board/m5stack/M5CORE2.h b/src/board/m5stack/M5CORE2.h index b43b9b58..10c696d3 100644 --- a/src/board/m5stack/M5CORE2.h +++ b/src/board/m5stack/M5CORE2.h @@ -70,21 +70,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -192,7 +194,7 @@ #define ESP_PANEL_BEGIN_END_FUNCTION( panel ) \ { \ static const uint8_t AXP_ADDR = 0x34; \ - static const uint8_t I2C_MASTER_TIMEOUT_MS = 1000; \ + static const uint32_t I2C_MASTER_TIMEOUT_MS = 1000; \ static i2c_port_t i2c_master_port = I2C_NUM_0; \ \ uint8_t device_id = 0; \ diff --git a/src/board/m5stack/M5CORES3.h b/src/board/m5stack/M5CORES3.h index a7981eac..f05acc69 100644 --- a/src/board/m5stack/M5CORES3.h +++ b/src/board/m5stack/M5CORES3.h @@ -5,7 +5,6 @@ */ #pragma once -#include #include // *INDENT-OFF* @@ -73,21 +72,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ @@ -186,14 +187,9 @@ #define ESP_PANEL_BEGIN_START_FUNCTION( panel ) \ { \ const uint8_t AXP_ADDR = 0x34; \ - const uint8_t I2C_MASTER_TIMEOUT_MS = 1000; \ + const uint32_t I2C_MASTER_TIMEOUT_MS = 1000; \ const i2c_port_t i2c_master_port = I2C_NUM_0; \ - \ - uint8_t read_value = 0; \ - uint8_t write_value = 0; \ - uint8_t reg_addr = 0; \ uint8_t write_buf[2] = {0}; \ - \ ESP_LOGD(TAG, "Start AXP2101 Power"); \ write_buf[0] = 0x90; \ write_buf[1] = 0xBF; \ diff --git a/src/board/m5stack/M5DIAL.h b/src/board/m5stack/M5DIAL.h index 0c672c17..58afaa2e 100644 --- a/src/board/m5stack/M5DIAL.h +++ b/src/board/m5stack/M5DIAL.h @@ -159,21 +159,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/waveshare/ESP32_S3_Touch_LCD_1_85.h b/src/board/waveshare/ESP32_S3_Touch_LCD_1_85.h index 19f00310..f83cbe54 100644 --- a/src/board/waveshare/ESP32_S3_Touch_LCD_1_85.h +++ b/src/board/waveshare/ESP32_S3_Touch_LCD_1_85.h @@ -70,21 +70,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/waveshare/ESP32_S3_Touch_LCD_2_1.h b/src/board/waveshare/ESP32_S3_Touch_LCD_2_1.h index c4cfd571..432aa9e8 100644 --- a/src/board/waveshare/ESP32_S3_Touch_LCD_2_1.h +++ b/src/board/waveshare/ESP32_S3_Touch_LCD_2_1.h @@ -119,7 +119,7 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ #define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ diff --git a/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h b/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h index 3b2f72c2..1155896d 100644 --- a/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h +++ b/src/board/waveshare/ESP32_S3_Touch_LCD_4_3.h @@ -113,21 +113,23 @@ * * There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) */ -// #define ESP_PANEL_LCD_VENDOR_INIT_CMD \ -// { \ -// {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ -// {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ -// {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ -// {0x29, (uint8_t []){0x00}, 0, 120}, \ -// or \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ -// ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ -// ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ -// } +/* +#define ESP_PANEL_LCD_VENDOR_INIT_CMD() \ + { \ + {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, \ + {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, \ + {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, \ + {0x29, (uint8_t []){0x00}, 0, 120}, \ + or \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), \ + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), \ + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), \ + } +*/ /* LCD Color Settings */ /* LCD color depth in bits */ diff --git a/src/board/waveshare/Kconfig.waveshare b/src/board/waveshare/Kconfig.waveshare new file mode 100644 index 00000000..bc6f3a84 --- /dev/null +++ b/src/board/waveshare/Kconfig.waveshare @@ -0,0 +1,14 @@ +config BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3 + bool "ESP32_S3_Touch_LCD_4_3" + help + https://www.waveshare.com/esp32-s3-touch-lcd-4.3.htm + +config BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85 + bool "ESP32_S3_Touch_LCD_1_85" + help + https://www.waveshare.com/esp32-s3-touch-lcd-1.85.htm + +config BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1 + bool "ESP32_S3_Touch_LCD_2_1" + help + https://www.waveshare.com/esp32-s3-touch-lcd-2.1.htm diff --git a/src/bus/ESP_PanelBus.cpp b/src/bus/ESP_PanelBus.cpp index 51962971..3f818f7d 100644 --- a/src/bus/ESP_PanelBus.cpp +++ b/src/bus/ESP_PanelBus.cpp @@ -22,7 +22,7 @@ ESP_PanelBus::ESP_PanelBus(int host_id, uint8_t bus_type, bool host_need_init): bool ESP_PanelBus::readRegisterData(uint32_t address, void *data, uint32_t data_size) { ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_io_rx_param(handle, address, data, data_size), false, - "Read register(0x%x) failed", address); + "Read register(0x%" PRIx32 ") failed", address); return true; } @@ -30,7 +30,7 @@ bool ESP_PanelBus::readRegisterData(uint32_t address, void *data, uint32_t data_ bool ESP_PanelBus::writeRegisterData(uint32_t address, const void *data, uint32_t data_size) { ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_io_tx_param(handle, address, data, data_size), false, - "Read register(0x%x) failed", address); + "Read register(0x%" PRIx32 ") failed", address); return true; } @@ -38,7 +38,7 @@ bool ESP_PanelBus::writeRegisterData(uint32_t address, const void *data, uint32_ bool ESP_PanelBus::writeColorData(uint32_t address, const void *color, uint32_t color_size) { ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_io_tx_param(handle, address, color, color_size), false, - "Read register(0x%x) failed", address); + "Read register(0x%" PRIx32 ") failed", address); return true; } diff --git a/src/bus/ESP_PanelBus.h b/src/bus/ESP_PanelBus.h index 37ccd273..169ec47d 100644 --- a/src/bus/ESP_PanelBus.h +++ b/src/bus/ESP_PanelBus.h @@ -55,7 +55,7 @@ class ESP_PanelBus { * * @return true if success, otherwise false */ - bool del(void); + virtual bool del(void); /** * @brief Read the register data @@ -94,7 +94,7 @@ class ESP_PanelBus { * @brief Get the type of bus * * @return - * - ESP_PANEL_BUS_TYPE_UNKNOWN: Unkown bus + * - ESP_PANEL_BUS_TYPE_UNKNOWN: Unknown bus * - ESP_PANEL_BUS_TYPE_SPI: SPI bus * - ESP_PANEL_BUS_TYPE_RGB: RGB (DPI)bus * - ESP_PANEL_BUS_TYPE_QSPI: QSPI bus diff --git a/src/bus/I2C.cpp b/src/bus/I2C.cpp index c13f2c84..61a44bb3 100644 --- a/src/bus/I2C.cpp +++ b/src/bus/I2C.cpp @@ -113,8 +113,13 @@ bool ESP_PanelBus_I2C::begin(void) ESP_LOGD(TAG, "Init host[%d]", (int)host_id); } +#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 2, 0) ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)host_id, &io_config, &handle), false, "Create panel io failed"); +#else + ESP_PANEL_CHECK_ERR_RET(esp_lcd_new_panel_io_i2c_v1((esp_lcd_i2c_bus_handle_t)host_id, &io_config, &handle), false, + "Create panel io failed"); +#endif ESP_LOGD(TAG, "Panel IO @%p created", handle); return true; diff --git a/src/bus/SPI.cpp b/src/bus/SPI.cpp index a81a6646..22ee6963 100644 --- a/src/bus/SPI.cpp +++ b/src/bus/SPI.cpp @@ -57,6 +57,15 @@ ESP_PanelBus_SPI::~ESP_PanelBus_SPI() ESP_LOGE(TAG, "Delete panel io failed"); } +end: + ESP_LOGD(TAG, "Destroyed"); +} + +bool ESP_PanelBus_SPI::del(void) +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + ESP_PANEL_CHECK_FALSE_RET(ESP_PanelBus::del(), false, "Delete base panel failed"); if (host_need_init) { if (spi_bus_free((spi_host_device_t)host_id) != ESP_OK) { ESP_LOGE(TAG, "Delete host[%d] driver failed", host_id); @@ -65,8 +74,7 @@ ESP_PanelBus_SPI::~ESP_PanelBus_SPI() } } -end: - ESP_LOGD(TAG, "Destroyed"); + return true; } void ESP_PanelBus_SPI::configSpiMode(uint8_t mode) diff --git a/src/bus/SPI.h b/src/bus/SPI.h index 07eee1e6..ab388a84 100644 --- a/src/bus/SPI.h +++ b/src/bus/SPI.h @@ -111,6 +111,13 @@ class ESP_PanelBus_SPI: public ESP_PanelBus { */ ~ESP_PanelBus_SPI() override; + /** + * @brief Delete the bus object, release the resources + * + * @return true if success, otherwise false + */ + bool del(void) override; + /** * @brief Here are some functions to configure the SPI bus object. These functions should be called before `begin()` * diff --git a/src/bus/base/esp_lcd_panel_io_additions.h b/src/bus/base/esp_lcd_panel_io_additions.h index dd820c7f..8d6d0980 100644 --- a/src/bus/base/esp_lcd_panel_io_additions.h +++ b/src/bus/base/esp_lcd_panel_io_additions.h @@ -54,7 +54,7 @@ typedef struct { typedef struct { spi_line_config_t line_config; /*!< SPI line configuration */ uint32_t expect_clk_speed; /*!< Expected SPI clock speed, in Hz (1 ~ 500000 - * If this value is 0, it will be set to `PANEL_IO_3WIRE_SPI_CLK_MAX` by defaul + * If this value is 0, it will be set to `PANEL_IO_3WIRE_SPI_CLK_MAX` by default * The actual frequency may be very different due to the limitation of the software delay */ uint32_t spi_mode: 2; /*!< Traditional SPI mode (0 ~ 3) */ uint32_t lcd_cmd_bytes: 3; /*!< Bytes of LCD command (1 ~ 4) */ diff --git a/src/host/ESP_PanelHost.cpp b/src/host/ESP_PanelHost.cpp index 24b59ed6..d34799af 100644 --- a/src/host/ESP_PanelHost.cpp +++ b/src/host/ESP_PanelHost.cpp @@ -20,6 +20,9 @@ ESP_PanelHost::~ESP_PanelHost() { ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + if (!del()) { + ESP_LOGE(TAG, "Delete panel host failed"); + } ESP_LOGD(TAG, "Destroyed"); } @@ -36,7 +39,7 @@ bool ESP_PanelHost::addHostI2C(const i2c_config_t &host_config, i2c_port_t host_ } ESP_LOGD(TAG, "Host I2C[%d] is already exist", (int)host_id); ESP_PANEL_CHECK_FALSE_RET(!memcmp(&ret->second, &host_config, sizeof(i2c_config_t)), false, - "Attempt to add with a different configuartion"); + "Attempt to add with a different configuration"); return true; } @@ -56,7 +59,7 @@ bool ESP_PanelHost::addHostI2C(int scl_io, int sda_io, i2c_port_t host_id) } ESP_LOGD(TAG, "Host I2C[%d] is already exist", (int)host_id); ESP_PANEL_CHECK_FALSE_RET(!memcmp(&ret->second, &host_config, sizeof(i2c_config_t)), false, - "Attempt to add with a different configuartion"); + "Attempt to add with a different configuration"); return true; } @@ -75,7 +78,7 @@ bool ESP_PanelHost::addHostSPI(const spi_bus_config_t &host_config, spi_host_dev ESP_LOGD(TAG, "Host SPI[%d] is already exist", (int)host_id); ESP_PANEL_CHECK_FALSE_RET(compare_spi_host_config(ret->second, host_config), false, - "Attempt to add with a different configuartion"); + "Attempt to add with a different configuration"); return true; } @@ -96,7 +99,7 @@ bool ESP_PanelHost::addHostSPI(int sck_io, int sda_io, int sdo_io, spi_host_devi ESP_LOGD(TAG, "Host SPI[%d] is already exist", (int)host_id); ESP_PANEL_CHECK_FALSE_RET(compare_spi_host_config(ret->second, host_config), false, - "Attempt to add with a different configuartion"); + "Attempt to add with a different configuration"); return true; } @@ -114,7 +117,7 @@ bool ESP_PanelHost::addHostQSPI(const spi_bus_config_t &host_config, spi_host_de } ESP_LOGD(TAG, "Host SPI[%d] is already exist", (int)host_id); ESP_PANEL_CHECK_FALSE_RET(!memcmp(&ret->second, &host_config, sizeof(spi_bus_config_t)), false, - "Attempt to add with a different configuartion"); + "Attempt to add with a different configuration"); return true; } @@ -134,7 +137,7 @@ bool ESP_PanelHost::addHostQSPI(int sck_io, int d0_io, int d1_io, int d2_io, int } ESP_LOGD(TAG, "Host SPI[%d] is already exist", (int)host_id); ESP_PANEL_CHECK_FALSE_RET(!memcmp(&ret->second, &host_config, sizeof(spi_bus_config_t)), false, - "Attempt to add with a different configuartion"); + "Attempt to add with a different configuration"); return true; } @@ -165,6 +168,31 @@ bool ESP_PanelHost::begin(void) return true; } +bool ESP_PanelHost::del(void) +{ + ESP_PANEL_ENABLE_TAG_DEBUG_LOG(); + + // Uninstall all I2C hosts + if (_i2c_host_config_map.size() > 0) { + for (auto &it : _i2c_host_config_map) { + ESP_PANEL_CHECK_ERR_RET(i2c_driver_delete(it.first), false, "I2C[%d] delete driver failed", it.first); + ESP_LOGD(TAG, "Delete host I2C[%d]", (int)it.first); + } + _i2c_host_config_map.clear(); + } + + // Uninstall all SPI hosts + if (_spi_host_config_map.size() > 0) { + for (auto &it : _spi_host_config_map) { + ESP_PANEL_CHECK_ERR_RET(spi_bus_free(it.first), false, "SPI[%d] free failed", it.first); + ESP_LOGD(TAG, "Delete host SPI[%d]", (int)it.first); + } + _spi_host_config_map.clear(); + } + + return true; +} + bool ESP_PanelHost::compare_spi_host_config(spi_bus_config_t &old_config, const spi_bus_config_t &new_config) { spi_bus_config_t temp_config = { }; diff --git a/src/host/ESP_PanelHost.h b/src/host/ESP_PanelHost.h index fbf9b769..d0ef8d31 100644 --- a/src/host/ESP_PanelHost.h +++ b/src/host/ESP_PanelHost.h @@ -39,7 +39,10 @@ #define ESP_PANEL_HOST_SPI_MAX_TRANSFER_SIZE ((1 << 24) >> 3) #elif CONFIG_IDF_TARGET_ESP32S2 #define ESP_PANEL_HOST_SPI_MAX_TRANSFER_SIZE ((1 << 23) >> 3) -#elif CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2 +#else +// ESP32-C3, ESP32-C3, ESP32-C5, ESP32-C6, ESP32-C61 +// ESP32-S3 +// ESP32-P4 #define ESP_PANEL_HOST_SPI_MAX_TRANSFER_SIZE ((1 << 18) >> 3) #endif #define ESP_PANEL_HOST_SPI_CONFIG_DEFAULT(sck_io, sda_io, sdo_io) \ @@ -88,6 +91,7 @@ class ESP_PanelHost { bool addHostQSPI(int sck_io, int d0_io, int d1_io, int d2_io, int d3_io, spi_host_device_t host_id); bool begin(void); + bool del(void); private: bool compare_spi_host_config(spi_bus_config_t &old_config, const spi_bus_config_t &new_config); diff --git a/src/lcd/ESP_PanelLcd.cpp b/src/lcd/ESP_PanelLcd.cpp index 04e6aaf1..28e96038 100644 --- a/src/lcd/ESP_PanelLcd.cpp +++ b/src/lcd/ESP_PanelLcd.cpp @@ -4,12 +4,16 @@ * SPDX-License-Identifier: Apache-2.0 */ +#include #include "cstring" #include "ESP_PanelLog.h" +#include "soc/soc_caps.h" #include "esp_heap_caps.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_io.h" +#if SOC_LCD_RGB_SUPPORTED #include "esp_lcd_panel_rgb.h" +#endif #include "esp_memory_utils.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" @@ -38,6 +42,8 @@ static const char *TAG = "ESP_PanelLcd"; +using namespace std; + ESP_PanelLcd::ESP_PanelLcd(ESP_PanelBus *bus, uint8_t color_bits, int rst_io): x_coord_align(0), y_coord_align(0), @@ -186,6 +192,10 @@ bool ESP_PanelLcd::reset(void) bool ESP_PanelLcd::del(void) { ESP_PANEL_CHECK_ERR_RET(esp_lcd_panel_del(handle), false, "Delete panel failed"); + if (_refresh_finish_sem) { + vSemaphoreDelete(_refresh_finish_sem); + _refresh_finish_sem = NULL; + } ESP_LOGD(TAG, "LCD panel @%p deleted", handle); handle = NULL; @@ -295,9 +305,11 @@ bool ESP_PanelLcd::attachRefreshFinishCallback(std::function call #if SOC_LCD_RGB_SUPPORTED && CONFIG_LCD_RGB_ISR_IRAM_SAFE && \ !(CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_FETCH_INSTRUCTIONS) if (bus->getType() == ESP_PANEL_BUS_TYPE_RGB) { - ESP_PANEL_CHECK_FALSE_RET((esp_ptr_in_iram(callback), false, "Callback function should be placed in IRAM, add - `IRAM_ATTR` before the function")); - ESP_PANEL_CHECK_FALSE_RET((esp_ptr_internal(user_data), false, "User data should be placed in SRAM")); + ESP_PANEL_CHECK_FALSE_RET( + esp_ptr_in_iram(callback), false, + "Callback function should be placed in IRAM, add `IRAM_ATTR` before the function" + ); + ESP_PANEL_CHECK_FALSE_RET((esp_ptr_internal(user_data), false, "User data should be placed in SRAM")); } #endif @@ -319,17 +331,11 @@ bool ESP_PanelLcd::colorBarTest(uint16_t width, uint16_t height) int bytes_per_piexl = bits_per_piexl / 8; int line_per_bar = height / bits_per_piexl; int line_count = 0; - uint8_t *single_bar_buf = NULL; int res_line_count = 0; /* Malloc memory for a single color bar */ - try { - single_bar_buf = new uint8_t[line_per_bar * width * bytes_per_piexl]; - } catch (std::bad_alloc &e) { - ESP_PANEL_CHECK_FALSE_RET(false, false, "Malloc color buffer failed"); - } - - bool ret = true; + shared_ptr single_bar_buf(new uint8_t[line_per_bar * width * bytes_per_piexl]); + ESP_PANEL_CHECK_FALSE_RET(single_bar_buf != nullptr, false, "Malloc color buffer failed"); /* Draw color bar from top left to bottom right, the order is B - G - R */ for (int j = 0; j < bits_per_piexl; j++) { @@ -337,18 +343,17 @@ bool ESP_PanelLcd::colorBarTest(uint16_t width, uint16_t height) for (int k = 0; k < bytes_per_piexl; k++) { if ((bus->getType() == ESP_PANEL_BUS_TYPE_SPI) || (bus->getType() == ESP_PANEL_BUS_TYPE_QSPI)) { // For SPI interface, the data bytes should be swapped since the data is sent by LSB first - single_bar_buf[i * bytes_per_piexl + k] = SPI_SWAP_DATA_TX(BIT(j), bits_per_piexl) >> (k * 8); + single_bar_buf.get()[i * bytes_per_piexl + k] = SPI_SWAP_DATA_TX(BIT(j), bits_per_piexl) >> (k * 8); } else { - single_bar_buf[i * bytes_per_piexl + k] = BIT(j) >> (k * 8); + single_bar_buf.get()[i * bytes_per_piexl + k] = BIT(j) >> (k * 8); } } } line_count += line_per_bar; - ret = drawBitmapWaitUntilFinish(0, j * line_per_bar, width, line_per_bar, single_bar_buf); - if (ret != true) { - ESP_LOGE(TAG, "Draw bitmap failed"); - goto end; - } + ESP_PANEL_CHECK_FALSE_RET( + drawBitmapWaitUntilFinish(0, j * line_per_bar, width, line_per_bar, single_bar_buf.get()), false, + "Draw bitmap failed" + ); } /* Fill the rest of the screen with white color */ @@ -356,18 +361,14 @@ bool ESP_PanelLcd::colorBarTest(uint16_t width, uint16_t height) if (res_line_count > 0) { ESP_LOGD(TAG, "Fill the rest lines (%d) with white color", res_line_count); - memset(single_bar_buf, 0xff, res_line_count * width * bytes_per_piexl); - ret = drawBitmapWaitUntilFinish(0, line_count, width, res_line_count, single_bar_buf); - if (ret != true) { - ESP_LOGE(TAG, "Draw bitmap failed"); - goto end; - } + memset(single_bar_buf.get(), 0xff, res_line_count * width * bytes_per_piexl); + ESP_PANEL_CHECK_FALSE_RET( + drawBitmapWaitUntilFinish(0, line_count, width, res_line_count, single_bar_buf.get()), false, + "Draw bitmap failed" + ); } -end: - delete[] single_bar_buf; - - return ret; + return true; } int ESP_PanelLcd::getColorBits(void) diff --git a/src/lcd/ESP_PanelLcd.h b/src/lcd/ESP_PanelLcd.h index 86d80254..c8fb4293 100644 --- a/src/lcd/ESP_PanelLcd.h +++ b/src/lcd/ESP_PanelLcd.h @@ -8,9 +8,12 @@ #include #include +#include "soc/soc_caps.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_panel_vendor.h" +#if SOC_LCD_RGB_SUPPORTED #include "esp_lcd_panel_rgb.h" +#endif #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "soc/soc_caps.h" @@ -75,7 +78,7 @@ class ESP_PanelLcd { * for initialization sequence code. * @note There are two formats for the sequence code: * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} - * 2. Formater: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) * * @return true if success, otherwise false @@ -99,7 +102,7 @@ class ESP_PanelLcd { * `mirror()`function will be implemented by software * @note This function is conflict with `configAutoReleaseBus()`, please don't use them at the same time * - * @param en ture: enable, false: disable + * @param en true: enable, false: disable * */ void configMirrorByCommand(bool en); @@ -114,7 +117,7 @@ class ESP_PanelLcd { * cannot be used to transmit commands any more. * @note This function is conflict with `configMirrorByCommand()`, please don't use them at the same time * - * @param en ture: enable, false: disable + * @param en true: enable, false: disable * */ void configAutoReleaseBus(bool en); @@ -227,7 +230,7 @@ class ESP_PanelLcd { * @brief Swap the X and Y axis * * @note This function should be called after `begin()` - * @note This function typically calls `esp_lcd_panel_swap_xy()` to mirror the axises + * @note This function typically calls `esp_lcd_panel_swap_xy()` to mirror the axes * * @param en true: enable, false: disable * @@ -260,7 +263,7 @@ class ESP_PanelLcd { bool setGapY(uint16_t gap); /** - * @brief Invert every bit of pixel color data, like frome `0x55` to `0xAA` + * @brief Invert every bit of pixel color data, like from `0x55` to `0xAA` * * @note This function should be called after `begin()` * @note This function typically calls `esp_lcd_panel_invert_color()` to invert the color diff --git a/src/lcd/base/esp_lcd_custom_types.h b/src/lcd/base/esp_lcd_custom_types.h index b8ede8be..19342b70 100644 --- a/src/lcd/base/esp_lcd_custom_types.h +++ b/src/lcd/base/esp_lcd_custom_types.h @@ -6,7 +6,10 @@ #pragma once +#include "soc/soc_caps.h" +#if SOC_LCD_RGB_SUPPORTED #include "esp_lcd_panel_rgb.h" +#endif #include "soc/soc_caps.h" #include "sdkconfig.h" diff --git a/src/lcd/base/esp_lcd_gc9503.c b/src/lcd/base/esp_lcd_gc9503.c index 851222bf..ad2c23ae 100644 --- a/src/lcd/base/esp_lcd_gc9503.c +++ b/src/lcd/base/esp_lcd_gc9503.c @@ -319,8 +319,8 @@ static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel) } // Delete RGB panel gc9503->del(panel); - free(gc9503); ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503); + free(gc9503); return ESP_OK; } diff --git a/src/lcd/base/esp_lcd_ili9341.c b/src/lcd/base/esp_lcd_ili9341.c index bf2f4141..05e63c39 100644 --- a/src/lcd/base/esp_lcd_ili9341.c +++ b/src/lcd/base/esp_lcd_ili9341.c @@ -177,7 +177,7 @@ static esp_err_t panel_ili9341_reset(esp_lcd_panel_t *panel) static const esp_lcd_panel_vendor_init_cmd_t vendor_specific_init_default[] = { // {cmd, { data }, data_size, delay_ms} - /* Power contorl B, power control = 0, DC_ENA = 1 */ + /* Power control B, power control = 0, DC_ENA = 1 */ {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0}, /* Power on sequence control, * cp1 keeps 1 frame, 1st frame enable diff --git a/src/lcd/base/esp_lcd_st7701.c b/src/lcd/base/esp_lcd_st7701.c index a97f62a8..8f2c678a 100644 --- a/src/lcd/base/esp_lcd_st7701.c +++ b/src/lcd/base/esp_lcd_st7701.c @@ -311,8 +311,8 @@ static esp_err_t panel_st7701_del(esp_lcd_panel_t *panel) } // Delete RGB panel st7701->del(panel); - free(st7701); ESP_LOGD(TAG, "del st7701 panel @%p", st7701); + free(st7701); return ESP_OK; } diff --git a/src/touch/ESP_PanelTouch.h b/src/touch/ESP_PanelTouch.h index 3ea8662b..1a8e4d7f 100644 --- a/src/touch/ESP_PanelTouch.h +++ b/src/touch/ESP_PanelTouch.h @@ -126,7 +126,7 @@ class ESP_PanelTouch { * @brief Swap the X and Y axis * * @note This function should be called after `begin()` - * @note This function typically calls `esp_lcd_touch_set_swap_xy()` to mirror the axises + * @note This function typically calls `esp_lcd_touch_set_swap_xy()` to mirror the axes * * @param en true: enable, false: disable * diff --git a/src/touch/Kconfig.touch b/src/touch/Kconfig.touch new file mode 100644 index 00000000..558579ec --- /dev/null +++ b/src/touch/Kconfig.touch @@ -0,0 +1,45 @@ +menu "LCD touch driver" + config ESP_PANEL_TOUCH_MAX_POINTS + int "Max point number" + default 5 + help + Maximum number of touch points that can be handled by the touch driver. + This value should be set to the maximum number of touch points supported by the touch controller. + + config ESP_PANEL_TOUCH_MAX_BUTTONS + int "Max button number" + default 1 + help + Maximum number of buttons that can be handled by the touch driver. + This value should be set to the maximum number of buttons supported by the touch controller. + + menu "XPT2046" + config ESP_PANEL_TOUCH_XPT2046_Z_THRESHOLD + int "Minimum Z pressure threshold" + default 400 + + config ESP_PANEL_TOUCH_XPT2046_INTERRUPT_MODE + bool "Enable Interrupt (PENIRQ) output" + default n + help + Also called Full Power Mode. Enable this to configure the XPT2046 to output low on the PENIRQ output if a touch is detected. This mode uses more power when enabled. Note that this signal goes low normally when a read is active. + + config ESP_PANEL_TOUCH_XPT2046_VREF_ON_MODE + bool "Keep internal Vref enabled" + default n + help + Enable this to keep the internal Vref enabled between conversions. This uses slightly more power, but requires fewer transactions when reading the battery voltage, aux voltage and temperature. + + config ESP_PANEL_TOUCH_XPT2046_CONVERT_ADC_TO_COORDS + bool "Convert touch coordinates to screen coordinates" + default y + help + When this option is enabled the raw ADC values will be converted from 0-4096 to 0-{screen width} or 0-{screen height}. When this option is disabled the process_coordinates method will need to be used to convert the raw ADC values into a screen coordinate. + + config ESP_PANEL_TOUCH_XPT2046_ENABLE_LOCKING + bool "Enable data structure locking" + default n + help + By enabling this option the XPT2046 driver will lock the touch position data structures when reading values from the XPT2046 and when reading position data via API. WARNING: enabling this option may result in unintended crashes. + endmenu +endmenu diff --git a/src/touch/base/esp_lcd_touch.h b/src/touch/base/esp_lcd_touch.h index 96b354a7..d3801832 100644 --- a/src/touch/base/esp_lcd_touch.h +++ b/src/touch/base/esp_lcd_touch.h @@ -150,7 +150,7 @@ struct esp_lcd_touch_s { * @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array) * * @return - * - Returns true, when touched and coordinates readed. Otherwise returns false. + * - Returns true, when touched and coordinates read. Otherwise returns false. */ bool (*get_xy)(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); @@ -164,7 +164,7 @@ struct esp_lcd_touch_s { * @param state: Button state * * @return - * - Returns true, when touched and coordinates readed. Otherwise returns false. + * - Returns true, when touched and coordinates read. Otherwise returns false. */ esp_err_t (*get_button_state)(esp_lcd_touch_handle_t tp, uint8_t n, uint8_t *state); #endif @@ -291,7 +291,7 @@ esp_err_t esp_lcd_touch_read_data(esp_lcd_touch_handle_t tp); * @param max_point_num: Maximum count of touched points to return (equals with max size of x and y array) * * @return - * - Returns true, when touched and coordinates readed. Otherwise returns false. + * - Returns true, when touched and coordinates read. Otherwise returns false. */ bool esp_lcd_touch_get_coordinates(esp_lcd_touch_handle_t tp, uint16_t *x, uint16_t *y, uint16_t *strength, uint8_t *point_num, uint8_t max_point_num); diff --git a/src/touch/base/esp_lcd_touch_gt1151.c b/src/touch/base/esp_lcd_touch_gt1151.c index 7a397952..d2b8a75c 100644 --- a/src/touch/base/esp_lcd_touch_gt1151.c +++ b/src/touch/base/esp_lcd_touch_gt1151.c @@ -138,7 +138,7 @@ static esp_err_t read_data(esp_lcd_touch_handle_t tp) ESP_RETURN_ON_ERROR( i2c_read_bytes(tp, READ_XY_REG, buf, DATA_BUFF_LEN(touch_cnt)), TAG, "I2C read failed"); /* Clear all */ i2c_write_byte(tp, READ_XY_REG, 0); - /* Caculate checksum */ + /* Calculate checksum */ uint8_t checksum = 0; for (int i = 0; i < DATA_BUFF_LEN(touch_cnt); i++) { checksum += buf[i]; diff --git a/src/touch/base/esp_lcd_touch_xpt2046.c b/src/touch/base/esp_lcd_touch_xpt2046.c index dcf6a592..50afbcd0 100644 --- a/src/touch/base/esp_lcd_touch_xpt2046.c +++ b/src/touch/base/esp_lcd_touch_xpt2046.c @@ -41,7 +41,7 @@ static const char *TAG = "xpt2046"; #define XPT2046_PD_BITS (XPT2046_PD1_BIT | XPT2046_PD0_BIT) enum xpt2046_registers { - // START ADDR MODE SER/ VREF ADC (PENIRQ) + // START ADDR MODE SET/ VREF ADC (PENIRQ) // 12/8b DFR INT/EXT ENA Z_VALUE_1 = 0xB0 | XPT2046_PD_BITS, // 1 011 0 0 X X Z_VALUE_2 = 0xC0 | XPT2046_PD_BITS, // 1 100 0 0 X X diff --git a/src/touch/base/esp_lcd_touch_xpt2046.h b/src/touch/base/esp_lcd_touch_xpt2046.h index c67f4d67..9539ce0a 100644 --- a/src/touch/base/esp_lcd_touch_xpt2046.h +++ b/src/touch/base/esp_lcd_touch_xpt2046.h @@ -213,7 +213,7 @@ esp_err_t esp_lcd_touch_xpt2046_read_aux_level(const esp_lcd_touch_handle_t hand * calibration offset for accurate results. * * @param handle: XPT2046 instance handle. - * @param out_level: Approximate tempreature of the TSC2046 in degrees C + * @param out_level: Approximate temperature of the TSC2046 in degrees C * @return * - ESP_OK on success, otherwise returns ESP_ERR_xxx */ @@ -224,7 +224,7 @@ esp_err_t esp_lcd_touch_xpt2046_read_temp0_level(const esp_lcd_touch_handle_t ha * Low precision (1.6 degrees C) but high accuracy requires no calibration. * * @param handle: XPT2046 instance handle. - * @param out_level: Approximate tempreature of the TSC2046 in degrees C + * @param out_level: Approximate temperature of the TSC2046 in degrees C * @return * - ESP_OK on success, otherwise returns ESP_ERR_xxx */ diff --git a/test_apps/lcd/3wire_spi_rgb/CMakeLists.txt b/test_apps/lcd/3wire_spi_rgb/CMakeLists.txt new file mode 100644 index 00000000..3b65f42d --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(3wire_spi_rgb_lcd_test) diff --git a/test_apps/lcd/3wire_spi_rgb/main/CMakeLists.txt b/test_apps/lcd/3wire_spi_rgb/main/CMakeLists.txt new file mode 100644 index 00000000..1eb34163 --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "test_app_main.c" "test_3wire_spi_rgb_lcd.cpp" + PRIV_REQUIRES esp_lcd driver esp_timer + WHOLE_ARCHIVE +) diff --git a/test_apps/lcd/3wire_spi_rgb/main/idf_component.yml b/test_apps/lcd/3wire_spi_rgb/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp b/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp new file mode 100644 index 00000000..550d5ffa --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/main/test_3wire_spi_rgb_lcd.cpp @@ -0,0 +1,237 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +// *INDENT-OFF* + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_WIDTH (800) +#define TEST_LCD_HEIGHT (480) + // | 8-bit RGB888 | 16-bit RGB565 | +#define TEST_LCD_COLOR_BITS (18) // | 24 | 16/18/24 | +#define TEST_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | +#define TEST_LCD_RGB_TIMING_FREQ_HZ (16 * 1000 * 1000) +#define TEST_LCD_RGB_TIMING_HPW (10) +#define TEST_LCD_RGB_TIMING_HBP (10) +#define TEST_LCD_RGB_TIMING_HFP (20) +#define TEST_LCD_RGB_TIMING_VPW (10) +#define TEST_LCD_RGB_TIMING_VBP (10) +#define TEST_LCD_RGB_TIMING_VFP (10) +#define TEST_LCD_RGB_BOUNCE_BUFFER_SIZE (TEST_LCD_WIDTH * 10) +#define TEST_LCD_USE_EXTERNAL_CMD (1) +#if TEST_LCD_USE_EXTERNAL_CMD +/** + * LCD initialization commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. + * + * Please uncomment and change the following macro definitions, then use `configVendorCommands()` to pass them in the + * same format if needed. Otherwise, the LCD driver will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { + // {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, + // {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, + // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, + // {0x29, (uint8_t []){0x00}, 0, 120}, + // // or + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), +}; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_PIN_NUM_RGB_DISP (-1) +#define TEST_LCD_PIN_NUM_RGB_VSYNC (3) +#define TEST_LCD_PIN_NUM_RGB_HSYNC (46) +#define TEST_LCD_PIN_NUM_RGB_DE (17) +#define TEST_LCD_PIN_NUM_RGB_PCLK (9) + // | RGB565 | RGB666 | RGB888 | + // |--------|--------|--------| +#define TEST_LCD_PIN_NUM_RGB_DATA0 (10) // | B0 | B0-1 | B0-3 | +#define TEST_LCD_PIN_NUM_RGB_DATA1 (11) // | B1 | B2 | B4 | +#define TEST_LCD_PIN_NUM_RGB_DATA2 (12) // | B2 | B3 | B5 | +#define TEST_LCD_PIN_NUM_RGB_DATA3 (13) // | B3 | B4 | B6 | +#define TEST_LCD_PIN_NUM_RGB_DATA4 (14) // | B4 | B5 | B7 | +#define TEST_LCD_PIN_NUM_RGB_DATA5 (21) // | G0 | G0 | G0-2 | +#define TEST_LCD_PIN_NUM_RGB_DATA6 (47) // | G1 | G1 | G3 | +#define TEST_LCD_PIN_NUM_RGB_DATA7 (48) // | G2 | G2 | G4 | +#if TEST_LCD_RGB_DATA_WIDTH > 8 +#define TEST_LCD_PIN_NUM_RGB_DATA8 (45) // | G3 | G3 | G5 | +#define TEST_LCD_PIN_NUM_RGB_DATA9 (38) // | G4 | G4 | G6 | +#define TEST_LCD_PIN_NUM_RGB_DATA10 (39) // | G5 | G5 | G7 | +#define TEST_LCD_PIN_NUM_RGB_DATA11 (40) // | R0 | R0-1 | R0-3 | +#define TEST_LCD_PIN_NUM_RGB_DATA12 (41) // | R1 | R2 | R4 | +#define TEST_LCD_PIN_NUM_RGB_DATA13 (42) // | R2 | R3 | R5 | +#define TEST_LCD_PIN_NUM_RGB_DATA14 (2) // | R3 | R4 | R6 | +#define TEST_LCD_PIN_NUM_RGB_DATA15 (1) // | R4 | R5 | R7 | +#endif +#define TEST_LCD_PIN_NUM_SPI_CS (39) +#define TEST_LCD_PIN_NUM_SPI_SCK (48) +#define TEST_LCD_PIN_NUM_SPI_SDA (47) +#define TEST_LCD_PIN_NUM_RST (-1) // Set to -1 if not used +#define TEST_LCD_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 + +// *INDENT-OFF* + +/* Enable or disable printing RGB refresh rate */ +#define TEST_ENABLE_PRINT_LCD_FPS (1) +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define TEST_ENABLE_ATTACH_CALLBACK (1) +#define TEST_COLOR_BAR_SHOW_TIME_MS (3000) + +static const char *TAG = "test_3wire_spi_rgb_lcd"; + +static shared_ptr init_backlight(void) +{ +#if TEST_LCD_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Initialize backlight control pin and turn it on"); + shared_ptr backlight = make_shared( + TEST_LCD_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL, true + ); + TEST_ASSERT_NOT_NULL_MESSAGE(backlight, "Create backlight object failed"); + + TEST_ASSERT_TRUE_MESSAGE(backlight->begin(), "Backlight begin failed"); + TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); + + return backlight; +#else + return nullptr; +#endif +} + +static shared_ptr init_panel_bus(void) +{ + ESP_LOGI(TAG, "Create LCD bus"); + shared_ptr panel_bus = make_shared( +#if TEST_LCD_RGB_DATA_WIDTH == 8 + TEST_LCD_WIDTH, TEST_LCD_HEIGHT, + TEST_LCD_PIN_NUM_SPI_CS, TEST_LCD_PIN_NUM_SPI_SCK, TEST_LCD_PIN_NUM_SPI_SDA, + TEST_LCD_PIN_NUM_RGB_DATA0, TEST_LCD_PIN_NUM_RGB_DATA1, TEST_LCD_PIN_NUM_RGB_DATA2, TEST_LCD_PIN_NUM_RGB_DATA3, + TEST_LCD_PIN_NUM_RGB_DATA4, TEST_LCD_PIN_NUM_RGB_DATA5, TEST_LCD_PIN_NUM_RGB_DATA6, TEST_LCD_PIN_NUM_RGB_DATA7, + TEST_LCD_PIN_NUM_RGB_HSYNC, TEST_LCD_PIN_NUM_RGB_VSYNC, TEST_LCD_PIN_NUM_RGB_PCLK, TEST_LCD_PIN_NUM_RGB_DE, + TEST_LCD_PIN_NUM_RGB_DISP +#elif TEST_LCD_RGB_DATA_WIDTH == 16 + TEST_LCD_WIDTH, TEST_LCD_HEIGHT, + TEST_LCD_PIN_NUM_SPI_CS, TEST_LCD_PIN_NUM_SPI_SCK, TEST_LCD_PIN_NUM_SPI_SDA, + TEST_LCD_PIN_NUM_RGB_DATA0, TEST_LCD_PIN_NUM_RGB_DATA1, TEST_LCD_PIN_NUM_RGB_DATA2, TEST_LCD_PIN_NUM_RGB_DATA3, + TEST_LCD_PIN_NUM_RGB_DATA4, TEST_LCD_PIN_NUM_RGB_DATA5, TEST_LCD_PIN_NUM_RGB_DATA6, TEST_LCD_PIN_NUM_RGB_DATA7, + TEST_LCD_PIN_NUM_RGB_DATA8, TEST_LCD_PIN_NUM_RGB_DATA9, TEST_LCD_PIN_NUM_RGB_DATA10, TEST_LCD_PIN_NUM_RGB_DATA11, + TEST_LCD_PIN_NUM_RGB_DATA12, TEST_LCD_PIN_NUM_RGB_DATA13, TEST_LCD_PIN_NUM_RGB_DATA14, TEST_LCD_PIN_NUM_RGB_DATA15, + TEST_LCD_PIN_NUM_RGB_HSYNC, TEST_LCD_PIN_NUM_RGB_VSYNC, TEST_LCD_PIN_NUM_RGB_PCLK, TEST_LCD_PIN_NUM_RGB_DE, + TEST_LCD_PIN_NUM_RGB_DISP +#endif + ); + TEST_ASSERT_NOT_NULL_MESSAGE(panel_bus, "Create panel bus object failed"); + + panel_bus->configRgbTimingFreqHz(TEST_LCD_RGB_TIMING_FREQ_HZ); + panel_bus->configRgbTimingPorch( + TEST_LCD_RGB_TIMING_HPW, TEST_LCD_RGB_TIMING_HBP, TEST_LCD_RGB_TIMING_HFP, + TEST_LCD_RGB_TIMING_VPW, TEST_LCD_RGB_TIMING_VBP, TEST_LCD_RGB_TIMING_VFP + ); + panel_bus->configRgbBounceBufferSize(TEST_LCD_RGB_BOUNCE_BUFFER_SIZE); // Set bounce buffer to avoid screen drift + TEST_ASSERT_TRUE_MESSAGE(panel_bus->begin(), "Panel bus begin failed"); + + return panel_bus; +} + +#if TEST_ENABLE_PRINT_LCD_FPS +#define TEST_LCD_FPS_COUNT_MAX (100) +#ifndef millis +#define millis() (esp_timer_get_time() / 1000) +#endif + +DRAM_ATTR int frame_count = 0; +DRAM_ATTR int fps = 0; +DRAM_ATTR long start_time = 0; + +IRAM_ATTR bool onVsyncEndCallback(void *user_data) +{ + long frame_start_time = *(long *)user_data; + if (frame_start_time == 0) { + (*(long *)user_data) = millis(); + + return false; + } + + frame_count++; + if (frame_count >= TEST_LCD_FPS_COUNT_MAX) { + fps = TEST_LCD_FPS_COUNT_MAX * 1000 / (millis() - frame_start_time); + frame_count = 0; + (*(long *)user_data) = millis(); + } + + return false; +} +#endif + +static void run_test(shared_ptr lcd) +{ +#if TEST_LCD_USE_EXTERNAL_CMD + // Configure external initialization commands, should called before `init()` + lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); +#endif + TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); +#if TEST_LCD_PIN_NUM_RGB_DISP >= 0 + TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); +#endif +#if TEST_ENABLE_PRINT_LCD_FPS + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time), "Attach refresh callback failed" + ); +#endif + + ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); + TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); +} + +#define CREATE_LCD(name, panel_bus) \ + ({ \ + ESP_LOGI(TAG, "Create LCD device: " #name); \ + shared_ptr lcd = make_shared(panel_bus, TEST_LCD_COLOR_BITS, TEST_LCD_PIN_NUM_RST); \ + TEST_ASSERT_NOT_NULL_MESSAGE(lcd, "Create LCD object failed"); \ + lcd; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test LCD (" #name ") to draw color bar", "[3wire_spi_rgb_lcd][" #name "]") \ + { \ + shared_ptr backlight = init_backlight(); \ + shared_ptr panel_bus = init_panel_bus(); \ + shared_ptr lcd = CREATE_LCD(name, panel_bus.get()); \ + run_test(lcd); \ + } + +/** + * Here to create test cases for different LCDs + * + */ +CREATE_TEST_CASE(GC9503) +CREATE_TEST_CASE(ST7701) diff --git a/test_apps/lcd/3wire_spi_rgb/main/test_app_main.c b/test_apps/lcd/3wire_spi_rgb/main/test_app_main.c new file mode 100644 index 00000000..ad471114 --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf(" __\\$$$$$\\| $$ $$\\$$\\$$ | $$ | $$$$$$$\\| $$$$$ \\$$$$$$_\\$$$$$$\\| $$$$$$$ | $$ | $$$$$$$\\| $$ \\$$$$| $$$$$$$\\ | $$ | $$ __ | $$ | $$\r\n"); + printf("| \\__| $$| $$$$ \\$$$$ _| $$_ | $$ | $$| $$_____ | \\__| $$| $$ _| $$_ | $$ | $$| $$__| $$| $$__/ $$ | $$_____| $$__/ \\| $$__/ $$\r\n"); + printf(" \\$$ $$| $$$ \\$$$| $$ \\| $$ | $$| $$ \\ \\$$ $$| $$ | $$ \\ | $$ | $$ \\$$ $$| $$ $$ | $$ \\\\$$ $$| $$ $$\r\n"); + printf(" \\$$$$$$ \\$$ \\$$ \\$$$$$$ \\$$ \\$$ \\$$$$$$$$ \\$$$$$$ \\$$ \\$$$$$$ \\$$ \\$$ \\$$$$$$ \\$$$$$$$ \\$$$$$$$$ \\$$$$$$ \\$$$$$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults b/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults new file mode 100644 index 00000000..f627e6eb --- /dev/null +++ b/test_apps/lcd/3wire_spi_rgb/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lcd/qspi/CMakeLists.txt b/test_apps/lcd/qspi/CMakeLists.txt new file mode 100644 index 00000000..0a401187 --- /dev/null +++ b/test_apps/lcd/qspi/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(qspi_lcd_test) diff --git a/test_apps/lcd/qspi/main/CMakeLists.txt b/test_apps/lcd/qspi/main/CMakeLists.txt new file mode 100644 index 00000000..9fc1653a --- /dev/null +++ b/test_apps/lcd/qspi/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "test_app_main.c" "test_qspi_lcd.cpp" + PRIV_REQUIRES esp_lcd driver + WHOLE_ARCHIVE +) diff --git a/test_apps/lcd/qspi/main/idf_component.yml b/test_apps/lcd/qspi/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/lcd/qspi/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/lcd/qspi/main/test_app_main.c b/test_apps/lcd/qspi/main/test_app_main.c new file mode 100644 index 00000000..6dadfea8 --- /dev/null +++ b/test_apps/lcd/qspi/main/test_app_main.c @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf("| $$ _| $$ _\\$$$$$$\\| $$$$$$$ | $$ | $$ | $$ __ | $$ | $$\r\n"); + printf("| $$/ \\ $$| \\__| $$| $$ _| $$_ | $$_____| $$__/ \\| $$__/ $$\r\n"); + printf(" \\$$ $$ $$ \\$$ $$| $$ | $$ \\ | $$ \\\\$$ $$| $$ $$\r\n"); + printf(" \\$$$$$$\\ \\$$$$$$ \\$$ \\$$$$$$ \\$$$$$$$$ \\$$$$$$ \\$$$$$$$\r\n"); + printf(" \\$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/lcd/qspi/main/test_qspi_lcd.cpp b/test_apps/lcd/qspi/main/test_qspi_lcd.cpp new file mode 100644 index 00000000..e69d530a --- /dev/null +++ b/test_apps/lcd/qspi/main/test_qspi_lcd.cpp @@ -0,0 +1,160 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_WIDTH (532) +#define TEST_LCD_HEIGHT (300) +#define TEST_LCD_COLOR_BITS (16) +#define TEST_LCD_SPI_FREQ_HZ (40 * 1000 * 1000) +#define TEST_LCD_USE_EXTERNAL_CMD (1) +#if TEST_LCD_USE_EXTERNAL_CMD +/** + * LCD initialization commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. + * + * Please uncomment and change the following macro definitions, then use `configVendorCommands()` to pass them in the + * same format if needed. Otherwise, the LCD driver will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { + // {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, + // {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, + // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, + // {0x29, (uint8_t []){0x00}, 0, 120}, + // // or + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), +}; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_PIN_NUM_SPI_CS (9) +#define TEST_LCD_PIN_NUM_SPI_SCK (10) +#define TEST_LCD_PIN_NUM_SPI_DATA0 (11) +#define TEST_LCD_PIN_NUM_SPI_DATA1 (12) +#define TEST_LCD_PIN_NUM_SPI_DATA2 (13) +#define TEST_LCD_PIN_NUM_SPI_DATA3 (14) +#define TEST_LCD_PIN_NUM_RST (3) // Set to -1 if not used +#define TEST_LCD_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 + +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define TEST_ENABLE_ATTACH_CALLBACK (1) +#define TEST_COLOR_BAR_SHOW_TIME_MS (3000) + +static const char *TAG = "test_qspi_lcd"; + +static shared_ptr init_backlight(void) +{ +#if TEST_LCD_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Initialize backlight control pin and turn it on"); + shared_ptr backlight = make_shared( + TEST_LCD_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL, true + ); + TEST_ASSERT_NOT_NULL_MESSAGE(backlight, "Create backlight object failed"); + + TEST_ASSERT_TRUE_MESSAGE(backlight->begin(), "Backlight begin failed"); + TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); + + return backlight; +#else + return nullptr; +#endif +} + +static shared_ptr init_panel_bus(void) +{ + ESP_LOGI(TAG, "Create LCD bus"); + shared_ptr panel_bus = make_shared( + TEST_LCD_PIN_NUM_SPI_CS, TEST_LCD_PIN_NUM_SPI_SCK, + TEST_LCD_PIN_NUM_SPI_DATA0, TEST_LCD_PIN_NUM_SPI_DATA1, + TEST_LCD_PIN_NUM_SPI_DATA2, TEST_LCD_PIN_NUM_SPI_DATA3 + ); + TEST_ASSERT_NOT_NULL_MESSAGE(panel_bus, "Create panel bus object failed"); + + panel_bus->configQspiFreqHz(TEST_LCD_SPI_FREQ_HZ); + TEST_ASSERT_TRUE_MESSAGE(panel_bus->begin(), "Panel bus begin failed"); + + return panel_bus; +} + +#if TEST_ENABLE_ATTACH_CALLBACK +IRAM_ATTR static bool onDrawBitmapFinishCallback(void *user_data) +{ + esp_rom_printf("Draw bitmap finish callback\n"); + + return false; +} +#endif + +static void run_test(shared_ptr lcd) +{ +#if TEST_LCD_USE_EXTERNAL_CMD + // Configure external initialization commands, should called before `init()` + lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); +#endif + TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); +#if TEST_ENABLE_ATTACH_CALLBACK + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachRefreshFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" + ); +#endif + + ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); + TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); +} + +#define CREATE_LCD(name, panel_bus) \ + ({ \ + ESP_LOGI(TAG, "Create LCD device: " #name); \ + shared_ptr lcd = make_shared(panel_bus, TEST_LCD_COLOR_BITS, TEST_LCD_PIN_NUM_RST); \ + TEST_ASSERT_NOT_NULL_MESSAGE(lcd, "Create LCD object failed"); \ + lcd; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test LCD (" #name ") to draw color bar", "[qspi_lcd][" #name "]") \ + { \ + shared_ptr backlight = init_backlight(); \ + shared_ptr panel_bus = init_panel_bus(); \ + shared_ptr lcd = CREATE_LCD(name, panel_bus.get()); \ + run_test(lcd); \ + } + +/** + * Here to create test cases for different LCDs + * + */ +CREATE_TEST_CASE(GC9B71) +CREATE_TEST_CASE(SH8601) +CREATE_TEST_CASE(SPD2010) +CREATE_TEST_CASE(ST77916) +CREATE_TEST_CASE(ST77922) diff --git a/test_apps/lcd/qspi/sdkconfig.defaults b/test_apps/lcd/qspi/sdkconfig.defaults new file mode 100644 index 00000000..e0e5bb6d --- /dev/null +++ b/test_apps/lcd/qspi/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT= +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lcd/rgb/CMakeLists.txt b/test_apps/lcd/rgb/CMakeLists.txt new file mode 100644 index 00000000..32d71f90 --- /dev/null +++ b/test_apps/lcd/rgb/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(rgb_lcd_test) diff --git a/test_apps/lcd/rgb/main/CMakeLists.txt b/test_apps/lcd/rgb/main/CMakeLists.txt new file mode 100644 index 00000000..a5561bd4 --- /dev/null +++ b/test_apps/lcd/rgb/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "test_app_main.c" "test_rgb_lcd.cpp" + PRIV_REQUIRES esp_lcd driver esp_timer + WHOLE_ARCHIVE +) diff --git a/test_apps/lcd/rgb/main/idf_component.yml b/test_apps/lcd/rgb/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/lcd/rgb/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/lcd/rgb/main/test_app_main.c b/test_apps/lcd/rgb/main/test_app_main.c new file mode 100644 index 00000000..e802e57e --- /dev/null +++ b/test_apps/lcd/rgb/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf("| $$$$$$$\\| $$ \\$$$$| $$$$$$$\\ | $$ | $$ __ | $$ | $$\r\n"); + printf("| $$ | $$| $$__| $$| $$__/ $$ | $$_____| $$__/ \\| $$__/ $$\r\n"); + printf("| $$ | $$ \\$$ $$| $$ $$ | $$ \\\\$$ $$| $$ $$\r\n"); + printf(" \\$$ \\$$ \\$$$$$$ \\$$$$$$$ \\$$$$$$$$ \\$$$$$$ \\$$$$$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/lcd/rgb/main/test_rgb_lcd.cpp b/test_apps/lcd/rgb/main/test_rgb_lcd.cpp new file mode 100644 index 00000000..5c837770 --- /dev/null +++ b/test_apps/lcd/rgb/main/test_rgb_lcd.cpp @@ -0,0 +1,204 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +// *INDENT-OFF* + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_WIDTH (800) +#define TEST_LCD_HEIGHT (480) + // | 8-bit RGB888 | 16-bit RGB565 | +#define TEST_LCD_COLOR_BITS (18) // | 24 | 16/18/24 | +#define TEST_LCD_RGB_DATA_WIDTH (16) // | 8 | 16 | +#define TEST_LCD_RGB_TIMING_FREQ_HZ (16 * 1000 * 1000) +#define TEST_LCD_RGB_TIMING_HPW (10) +#define TEST_LCD_RGB_TIMING_HBP (10) +#define TEST_LCD_RGB_TIMING_HFP (20) +#define TEST_LCD_RGB_TIMING_VPW (10) +#define TEST_LCD_RGB_TIMING_VBP (10) +#define TEST_LCD_RGB_TIMING_VFP (10) +#define TEST_LCD_RGB_BOUNCE_BUFFER_SIZE (TEST_LCD_WIDTH * 10) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_PIN_NUM_RGB_DISP (-1) +#define TEST_LCD_PIN_NUM_RGB_VSYNC (3) +#define TEST_LCD_PIN_NUM_RGB_HSYNC (46) +#define TEST_LCD_PIN_NUM_RGB_DE (17) +#define TEST_LCD_PIN_NUM_RGB_PCLK (9) + // | RGB565 | RGB666 | RGB888 | + // |--------|--------|--------| +#define TEST_LCD_PIN_NUM_RGB_DATA0 (10) // | B0 | B0-1 | B0-3 | +#define TEST_LCD_PIN_NUM_RGB_DATA1 (11) // | B1 | B2 | B4 | +#define TEST_LCD_PIN_NUM_RGB_DATA2 (12) // | B2 | B3 | B5 | +#define TEST_LCD_PIN_NUM_RGB_DATA3 (13) // | B3 | B4 | B6 | +#define TEST_LCD_PIN_NUM_RGB_DATA4 (14) // | B4 | B5 | B7 | +#define TEST_LCD_PIN_NUM_RGB_DATA5 (21) // | G0 | G0 | G0-2 | +#define TEST_LCD_PIN_NUM_RGB_DATA6 (47) // | G1 | G1 | G3 | +#define TEST_LCD_PIN_NUM_RGB_DATA7 (48) // | G2 | G2 | G4 | +#if TEST_LCD_RGB_DATA_WIDTH > 8 +#define TEST_LCD_PIN_NUM_RGB_DATA8 (45) // | G3 | G3 | G5 | +#define TEST_LCD_PIN_NUM_RGB_DATA9 (38) // | G4 | G4 | G6 | +#define TEST_LCD_PIN_NUM_RGB_DATA10 (39) // | G5 | G5 | G7 | +#define TEST_LCD_PIN_NUM_RGB_DATA11 (40) // | R0 | R0-1 | R0-3 | +#define TEST_LCD_PIN_NUM_RGB_DATA12 (41) // | R1 | R2 | R4 | +#define TEST_LCD_PIN_NUM_RGB_DATA13 (42) // | R2 | R3 | R5 | +#define TEST_LCD_PIN_NUM_RGB_DATA14 (2) // | R3 | R4 | R6 | +#define TEST_LCD_PIN_NUM_RGB_DATA15 (1) // | R4 | R5 | R7 | +#endif +#define TEST_LCD_PIN_NUM_RST (-1) // Set to -1 if not used +#define TEST_LCD_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 + +// *INDENT-OFF* + +/* Enable or disable printing RGB refresh rate */ +#define TEST_ENABLE_PRINT_LCD_FPS (1) +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define TEST_ENABLE_ATTACH_CALLBACK (1) +#define TEST_COLOR_BAR_SHOW_TIME_MS (3000) + +static const char *TAG = "test_rgb_lcd"; + +static shared_ptr init_backlight(void) +{ +#if TEST_LCD_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Initialize backlight control pin and turn it on"); + shared_ptr backlight = make_shared( + TEST_LCD_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL, true + ); + TEST_ASSERT_NOT_NULL_MESSAGE(backlight, "Create backlight object failed"); + + TEST_ASSERT_TRUE_MESSAGE(backlight->begin(), "Backlight begin failed"); + TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); + + return backlight; +#else + return nullptr; +#endif +} + +static shared_ptr init_panel_bus(void) +{ + ESP_LOGI(TAG, "Create LCD bus"); + shared_ptr panel_bus = make_shared( +#if TEST_LCD_RGB_DATA_WIDTH == 8 + TEST_LCD_WIDTH, TEST_LCD_HEIGHT, + TEST_LCD_PIN_NUM_RGB_DATA0, TEST_LCD_PIN_NUM_RGB_DATA1, TEST_LCD_PIN_NUM_RGB_DATA2, TEST_LCD_PIN_NUM_RGB_DATA3, + TEST_LCD_PIN_NUM_RGB_DATA4, TEST_LCD_PIN_NUM_RGB_DATA5, TEST_LCD_PIN_NUM_RGB_DATA6, TEST_LCD_PIN_NUM_RGB_DATA7, + TEST_LCD_PIN_NUM_RGB_HSYNC, TEST_LCD_PIN_NUM_RGB_VSYNC, TEST_LCD_PIN_NUM_RGB_PCLK, TEST_LCD_PIN_NUM_RGB_DE, + TEST_LCD_PIN_NUM_RGB_DISP +#elif TEST_LCD_RGB_DATA_WIDTH == 16 + TEST_LCD_WIDTH, TEST_LCD_HEIGHT, + TEST_LCD_PIN_NUM_RGB_DATA0, TEST_LCD_PIN_NUM_RGB_DATA1, TEST_LCD_PIN_NUM_RGB_DATA2, TEST_LCD_PIN_NUM_RGB_DATA3, + TEST_LCD_PIN_NUM_RGB_DATA4, TEST_LCD_PIN_NUM_RGB_DATA5, TEST_LCD_PIN_NUM_RGB_DATA6, TEST_LCD_PIN_NUM_RGB_DATA7, + TEST_LCD_PIN_NUM_RGB_DATA8, TEST_LCD_PIN_NUM_RGB_DATA9, TEST_LCD_PIN_NUM_RGB_DATA10, TEST_LCD_PIN_NUM_RGB_DATA11, + TEST_LCD_PIN_NUM_RGB_DATA12, TEST_LCD_PIN_NUM_RGB_DATA13, TEST_LCD_PIN_NUM_RGB_DATA14, TEST_LCD_PIN_NUM_RGB_DATA15, + TEST_LCD_PIN_NUM_RGB_HSYNC, TEST_LCD_PIN_NUM_RGB_VSYNC, TEST_LCD_PIN_NUM_RGB_PCLK, TEST_LCD_PIN_NUM_RGB_DE, + TEST_LCD_PIN_NUM_RGB_DISP +#endif + ); + TEST_ASSERT_NOT_NULL_MESSAGE(panel_bus, "Create panel bus object failed"); + + panel_bus->configRgbTimingFreqHz(TEST_LCD_RGB_TIMING_FREQ_HZ); + panel_bus->configRgbTimingPorch( + TEST_LCD_RGB_TIMING_HPW, TEST_LCD_RGB_TIMING_HBP, TEST_LCD_RGB_TIMING_HFP, + TEST_LCD_RGB_TIMING_VPW, TEST_LCD_RGB_TIMING_VBP, TEST_LCD_RGB_TIMING_VFP + ); + panel_bus->configRgbBounceBufferSize(TEST_LCD_RGB_BOUNCE_BUFFER_SIZE); // Set bounce buffer to avoid screen drift + TEST_ASSERT_TRUE_MESSAGE(panel_bus->begin(), "Panel bus begin failed"); + + return panel_bus; +} + +#if TEST_ENABLE_PRINT_LCD_FPS +#define TEST_LCD_FPS_COUNT_MAX (100) +#ifndef millis +#define millis() (esp_timer_get_time() / 1000) +#endif + +DRAM_ATTR int frame_count = 0; +DRAM_ATTR int fps = 0; +DRAM_ATTR long start_time = 0; + +IRAM_ATTR bool onVsyncEndCallback(void *user_data) +{ + long frame_start_time = *(long *)user_data; + if (frame_start_time == 0) { + (*(long *)user_data) = millis(); + + return false; + } + + frame_count++; + if (frame_count >= TEST_LCD_FPS_COUNT_MAX) { + fps = TEST_LCD_FPS_COUNT_MAX * 1000 / (millis() - frame_start_time); + frame_count = 0; + (*(long *)user_data) = millis(); + } + + return false; +} +#endif + +static void run_test(shared_ptr lcd) +{ +#if TEST_LCD_USE_EXTERNAL_CMD + // Configure external initialization commands, should called before `init()` + lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); +#endif + TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); +#if TEST_LCD_PIN_NUM_RGB_DISP >= 0 + TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); +#endif +#if TEST_ENABLE_PRINT_LCD_FPS + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachRefreshFinishCallback(onVsyncEndCallback, (void *)&start_time), "Attach refresh callback failed" + ); +#endif + + ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); + TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); +} + +#define CREATE_LCD(name, panel_bus) \ + ({ \ + ESP_LOGI(TAG, "Create LCD device: " #name); \ + shared_ptr lcd = make_shared(panel_bus, TEST_LCD_COLOR_BITS, TEST_LCD_PIN_NUM_RST); \ + TEST_ASSERT_NOT_NULL_MESSAGE(lcd, "Create LCD object failed"); \ + lcd; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test LCD (" #name ") to draw color bar", "[rgb_lcd][" #name "]") \ + { \ + shared_ptr backlight = init_backlight(); \ + shared_ptr panel_bus = init_panel_bus(); \ + shared_ptr lcd = CREATE_LCD(name, panel_bus.get()); \ + run_test(lcd); \ + } + +/** + * Here to create test cases for different LCDs + * + */ +CREATE_TEST_CASE(ST7262) +CREATE_TEST_CASE(EK9716B) diff --git a/test_apps/lcd/rgb/sdkconfig.defaults b/test_apps/lcd/rgb/sdkconfig.defaults new file mode 100644 index 00000000..f627e6eb --- /dev/null +++ b/test_apps/lcd/rgb/sdkconfig.defaults @@ -0,0 +1,7 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_ESP_TASK_WDT_INIT=n +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lcd/spi/CMakeLists.txt b/test_apps/lcd/spi/CMakeLists.txt new file mode 100644 index 00000000..c22cd3f8 --- /dev/null +++ b/test_apps/lcd/spi/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(spi_lcd_test) diff --git a/test_apps/lcd/spi/main/CMakeLists.txt b/test_apps/lcd/spi/main/CMakeLists.txt new file mode 100644 index 00000000..26848d00 --- /dev/null +++ b/test_apps/lcd/spi/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "test_app_main.c" "test_spi_lcd.cpp" + PRIV_REQUIRES esp_lcd driver + WHOLE_ARCHIVE +) diff --git a/test_apps/lcd/spi/main/idf_component.yml b/test_apps/lcd/spi/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/lcd/spi/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/lcd/spi/main/test_app_main.c b/test_apps/lcd/spi/main/test_app_main.c new file mode 100644 index 00000000..103f7bcb --- /dev/null +++ b/test_apps/lcd/spi/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf(" _\\$$$$$$\\| $$$$$$$ | $$ | $$ | $$ __ | $$ | $$\r\n"); + printf("| \\__| $$| $$ _| $$_ | $$_____| $$__/ \\| $$__/ $$\r\n"); + printf(" \\$$ $$| $$ | $$ \\ | $$ \\\\$$ $$| $$ $$\r\n"); + printf(" \\$$$$$$ \\$$ \\$$$$$$ \\$$$$$$$$ \\$$$$$$ \\$$$$$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/lcd/spi/main/test_spi_lcd.cpp b/test_apps/lcd/spi/main/test_spi_lcd.cpp new file mode 100644 index 00000000..61cb6454 --- /dev/null +++ b/test_apps/lcd/spi/main/test_spi_lcd.cpp @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_WIDTH (320) +#define TEST_LCD_HEIGHT (240) +#define TEST_LCD_COLOR_BITS (16) +#define TEST_LCD_SPI_FREQ_HZ (40 * 1000 * 1000) +#define TEST_LCD_USE_EXTERNAL_CMD (1) +#if TEST_LCD_USE_EXTERNAL_CMD +/** + * LCD initialization commands. + * + * Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for + * initialization sequence code. + * + * Please uncomment and change the following macro definitions, then use `configVendorCommands()` to pass them in the + * same format if needed. Otherwise, the LCD driver will use the default initialization sequence code. + * + * There are two formats for the sequence code: + * 1. Raw data: {command, (uint8_t []){ data0, data1, ... }, data_size, delay_ms} + * 2. Formatter: ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(delay_ms, command, { data0, data1, ... }) and + * ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(delay_ms, command) + */ +const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = { + // {0xFF, (uint8_t []){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0}, + // {0xC0, (uint8_t []){0x3B, 0x00}, 2, 0}, + // {0xC1, (uint8_t []){0x0D, 0x02}, 2, 0}, + // {0x29, (uint8_t []){0x00}, 0, 120}, + // // or + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xFF, {0x77, 0x01, 0x00, 0x00, 0x10}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC0, {0x3B, 0x00}), + ESP_PANEL_LCD_CMD_WITH_8BIT_PARAM(0, 0xC1, {0x0D, 0x02}), + ESP_PANEL_LCD_CMD_WITH_NONE_PARAM(120, 0x29), +}; +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_LCD_PIN_NUM_SPI_CS (5) +#define TEST_LCD_PIN_NUM_SPI_DC (4) +#define TEST_LCD_PIN_NUM_SPI_SCK (7) +#define TEST_LCD_PIN_NUM_SPI_SDA (6) +#define TEST_LCD_PIN_NUM_SPI_SDO (-1) +#define TEST_LCD_PIN_NUM_RST (-1) // Set to -1 if not used +#define TEST_LCD_PIN_NUM_BK_LIGHT (45) // 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 + +/* Enable or disable the attachment of a callback function that is called after each bitmap drawing is completed */ +#define TEST_ENABLE_ATTACH_CALLBACK (1) +#define TEST_COLOR_BAR_SHOW_TIME_MS (3000) + +static const char *TAG = "test_spi_lcd"; + +static shared_ptr init_backlight(void) +{ +#if TEST_LCD_PIN_NUM_BK_LIGHT >= 0 + ESP_LOGI(TAG, "Initialize backlight control pin and turn it on"); + shared_ptr backlight = make_shared( + TEST_LCD_PIN_NUM_BK_LIGHT, TEST_LCD_BK_LIGHT_ON_LEVEL, true + ); + TEST_ASSERT_NOT_NULL_MESSAGE(backlight, "Create backlight object failed"); + + TEST_ASSERT_TRUE_MESSAGE(backlight->begin(), "Backlight begin failed"); + TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); + + return backlight; +#else + return nullptr; +#endif +} + +static shared_ptr init_panel_bus(void) +{ + ESP_LOGI(TAG, "Create LCD bus"); + shared_ptr panel_bus = make_shared( + TEST_LCD_PIN_NUM_SPI_CS, TEST_LCD_PIN_NUM_SPI_DC, TEST_LCD_PIN_NUM_SPI_SCK, + TEST_LCD_PIN_NUM_SPI_SDA, TEST_LCD_PIN_NUM_SPI_SDO + ); + TEST_ASSERT_NOT_NULL_MESSAGE(panel_bus, "Create panel bus object failed"); + + panel_bus->configSpiFreqHz(TEST_LCD_SPI_FREQ_HZ); + TEST_ASSERT_TRUE_MESSAGE(panel_bus->begin(), "Panel bus begin failed"); + + return panel_bus; +} + +#if TEST_ENABLE_ATTACH_CALLBACK +IRAM_ATTR static bool onDrawBitmapFinishCallback(void *user_data) +{ + esp_rom_printf("Draw bitmap finish callback\n"); + + return false; +} +#endif + +static void run_test(shared_ptr lcd) +{ +#if TEST_LCD_USE_EXTERNAL_CMD + // Configure external initialization commands, should called before `init()` + lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0])); +#endif + TEST_ASSERT_TRUE_MESSAGE(lcd->init(), "LCD init failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->reset(), "LCD reset failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->begin(), "LCD begin failed"); + TEST_ASSERT_TRUE_MESSAGE(lcd->displayOn(), "LCD display on failed"); +#if TEST_ENABLE_ATTACH_CALLBACK + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachRefreshFinishCallback(onDrawBitmapFinishCallback, nullptr), "Attach callback failed" + ); +#endif + + ESP_LOGI(TAG, "Draw color bar from top left to bottom right, the order is B - G - R"); + TEST_ASSERT_TRUE_MESSAGE(lcd->colorBarTest(TEST_LCD_WIDTH, TEST_LCD_HEIGHT), "LCD color bar test failed"); +} + +#define CREATE_LCD(name, panel_bus) \ + ({ \ + ESP_LOGI(TAG, "Create LCD device: " #name); \ + shared_ptr lcd = make_shared(panel_bus, TEST_LCD_COLOR_BITS, TEST_LCD_PIN_NUM_RST); \ + TEST_ASSERT_NOT_NULL_MESSAGE(lcd, "Create LCD object failed"); \ + lcd; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test LCD (" #name ") to draw color bar", "[spi_lcd][" #name "]") \ + { \ + shared_ptr backlight = init_backlight(); \ + shared_ptr panel_bus = init_panel_bus(); \ + shared_ptr lcd = CREATE_LCD(name, panel_bus.get()); \ + run_test(lcd); \ + } + +/** + * Here to create test cases for different LCDs + * + */ +CREATE_TEST_CASE(GC9A01) +CREATE_TEST_CASE(GC9B71) +CREATE_TEST_CASE(NV3022B) +CREATE_TEST_CASE(SH8601) +CREATE_TEST_CASE(SPD2010) +CREATE_TEST_CASE(ST7789) +CREATE_TEST_CASE(ST77916) +CREATE_TEST_CASE(ST77922) diff --git a/test_apps/lcd/spi/sdkconfig.defaults b/test_apps/lcd/spi/sdkconfig.defaults new file mode 100644 index 00000000..e0e5bb6d --- /dev/null +++ b/test_apps/lcd/spi/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT= +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/lvgl_port/CMakeLists.txt b/test_apps/lvgl_port/CMakeLists.txt new file mode 100644 index 00000000..a744d5d2 --- /dev/null +++ b/test_apps/lvgl_port/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lvgl_port_test) diff --git a/test_apps/lvgl_port/main/CMakeLists.txt b/test_apps/lvgl_port/main/CMakeLists.txt new file mode 100644 index 00000000..43ee3b82 --- /dev/null +++ b/test_apps/lvgl_port/main/CMakeLists.txt @@ -0,0 +1,6 @@ +idf_component_register( + SRCS "test_app_main.c" "test_lvgl_port.cpp" "lvgl_port_v8.cpp" + WHOLE_ARCHIVE +) + +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers) diff --git a/test_apps/lvgl_port/main/Kconfig.projbuild b/test_apps/lvgl_port/main/Kconfig.projbuild new file mode 100644 index 00000000..7446973a --- /dev/null +++ b/test_apps/lvgl_port/main/Kconfig.projbuild @@ -0,0 +1,26 @@ +menu "Test Configurations" + choice LVGL_PORT_AVOID_TEARING_MODE_CHOICE + prompt "Avoid Tearing Mode" + depends on SOC_LCD_RGB_SUPPORTED + default LVGL_PORT_AVOID_TEARING_MODE_3 + + config LVGL_PORT_AVOID_TEARING_MODE_NONE + bool "None" + + config LVGL_PORT_AVOID_TEARING_MODE_1 + bool "Mode1: LCD double-buffer & LVGL full-refresh" + + config LVGL_PORT_AVOID_TEARING_MODE_2 + bool "Mode2: LCD triple-buffer & LVGL full-refresh" + + config LVGL_PORT_AVOID_TEARING_MODE_3 + bool "Mode3: LCD double-buffer & LVGL direct-mode (recommended)" + endchoice + + config LVGL_PORT_AVOID_TEARING_MODE + int + default 3 if LVGL_PORT_AVOID_TEARING_MODE_3 + default 2 if LVGL_PORT_AVOID_TEARING_MODE_2 + default 1 if LVGL_PORT_AVOID_TEARING_MODE_1 + default 0 if LVGL_PORT_AVOID_TEARING_MODE_NONE || !SOC_LCD_RGB_SUPPORTED +endmenu diff --git a/test_apps/lvgl_port/main/idf_component.yml b/test_apps/lvgl_port/main/idf_component.yml new file mode 100644 index 00000000..1375de3b --- /dev/null +++ b/test_apps/lvgl_port/main/idf_component.yml @@ -0,0 +1,11 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../ESP32_Display_Panel" + lvgl/lvgl: + version: "^8" diff --git a/test_apps/lvgl_port/main/lvgl_port_v8.cpp b/test_apps/lvgl_port/main/lvgl_port_v8.cpp new file mode 100644 index 00000000..9478c387 --- /dev/null +++ b/test_apps/lvgl_port/main/lvgl_port_v8.cpp @@ -0,0 +1,687 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include "esp_timer.h" +#include "lvgl_port_v8.h" + +#define LVGL_PORT_BUFFER_NUM_MAX (2) + +static const char *TAG = "lvgl_port"; +static SemaphoreHandle_t lvgl_mux = nullptr; // LVGL mutex +static TaskHandle_t lvgl_task_handle = nullptr; + +#if LVGL_PORT_ROTATION_DEGREE != 0 +static void *get_next_frame_buffer(ESP_PanelLcd *lcd) +{ + static void *next_fb = NULL; + static void *fbs[2] = { NULL }; + + if (next_fb == NULL) { + fbs[0] = lcd->getRgbBufferByIndex(0); + fbs[1] = lcd->getRgbBufferByIndex(1); + next_fb = fbs[1]; + } else { + next_fb = (next_fb == fbs[0]) ? fbs[1] : fbs[0]; + } + + return next_fb; +} + +IRAM_ATTR static void rotate_copy_pixel(const lv_color_t *from, lv_color_t *to, uint16_t x_start, uint16_t y_start, + uint16_t x_end, uint16_t y_end, uint16_t w, uint16_t h, uint16_t rotate) +{ + int from_index = 0; + int to_index = 0; + int to_index_const = 0; + + switch (rotate) { + case 90: + to_index_const = (w - x_start - 1) * h; + for (int from_y = y_start; from_y < y_end + 1; from_y++) { + from_index = from_y * w + x_start; + to_index = to_index_const + from_y; + for (int from_x = x_start; from_x < x_end + 1; from_x++) { + *(to + to_index) = *(from + from_index); + from_index += 1; + to_index -= h; + } + } + break; + case 180: + to_index_const = h * w - x_start - 1; + for (int from_y = y_start; from_y < y_end + 1; from_y++) { + from_index = from_y * w + x_start; + to_index = to_index_const - from_y * w; + for (int from_x = x_start; from_x < x_end + 1; from_x++) { + *(to + to_index) = *(from + from_index); + from_index += 1; + to_index -= 1; + } + } + break; + case 270: + to_index_const = (x_start + 1) * h - 1; + for (int from_y = y_start; from_y < y_end + 1; from_y++) { + from_index = from_y * w + x_start; + to_index = to_index_const - from_y; + for (int from_x = x_start; from_x < x_end + 1; from_x++) { + *(to + to_index) = *(from + from_index); + from_index += 1; + to_index += h; + } + } + break; + default: + break; + } +} +#endif /* LVGL_PORT_ROTATION_DEGREE */ + +#if LVGL_PORT_AVOID_TEAR +#if LVGL_PORT_DIRECT_MODE +#if LVGL_PORT_ROTATION_DEGREE != 0 +typedef struct { + uint16_t inv_p; + uint8_t inv_area_joined[LV_INV_BUF_SIZE]; + lv_area_t inv_areas[LV_INV_BUF_SIZE]; +} lv_port_dirty_area_t; + +static lv_port_dirty_area_t dirty_area; + +static void flush_dirty_save(lv_port_dirty_area_t *dirty_area) +{ + lv_disp_t *disp = _lv_refr_get_disp_refreshing(); + dirty_area->inv_p = disp->inv_p; + for (int i = 0; i < disp->inv_p; i++) { + dirty_area->inv_area_joined[i] = disp->inv_area_joined[i]; + dirty_area->inv_areas[i] = disp->inv_areas[i]; + } +} + +typedef enum { + FLUSH_STATUS_PART, + FLUSH_STATUS_FULL +} lv_port_flush_status_t; + +typedef enum { + FLUSH_PROBE_PART_COPY, + FLUSH_PROBE_SKIP_COPY, + FLUSH_PROBE_FULL_COPY, +} lv_port_flush_probe_t; + +/** + * @brief Probe dirty area to copy + * + * @note This function is used to avoid tearing effect, and only work with LVGL direct-mode. + * + */ +static lv_port_flush_probe_t flush_copy_probe(lv_disp_drv_t *drv) +{ + static lv_port_flush_status_t prev_status = FLUSH_STATUS_PART; + lv_port_flush_status_t cur_status; + lv_port_flush_probe_t probe_result; + lv_disp_t *disp_refr = _lv_refr_get_disp_refreshing(); + + uint32_t flush_ver = 0; + uint32_t flush_hor = 0; + for (int i = 0; i < disp_refr->inv_p; i++) { + if (disp_refr->inv_area_joined[i] == 0) { + flush_ver = (disp_refr->inv_areas[i].y2 + 1 - disp_refr->inv_areas[i].y1); + flush_hor = (disp_refr->inv_areas[i].x2 + 1 - disp_refr->inv_areas[i].x1); + break; + } + } + /* Check if the current full screen refreshes */ + cur_status = ((flush_ver == drv->ver_res) && (flush_hor == drv->hor_res)) ? (FLUSH_STATUS_FULL) : (FLUSH_STATUS_PART); + + if (prev_status == FLUSH_STATUS_FULL) { + if ((cur_status == FLUSH_STATUS_PART)) { + probe_result = FLUSH_PROBE_FULL_COPY; + } else { + probe_result = FLUSH_PROBE_SKIP_COPY; + } + } else { + probe_result = FLUSH_PROBE_PART_COPY; + } + prev_status = cur_status; + + return probe_result; +} + +static inline void *flush_get_next_buf(ESP_PanelLcd *lcd) +{ + return get_next_frame_buffer(lcd); +} + +/** + * @brief Copy dirty area + * + * @note This function is used to avoid tearing effect, and only work with LVGL direct-mode. + * + */ +static void flush_dirty_copy(void *dst, void *src, lv_port_dirty_area_t *dirty_area) +{ + lv_coord_t x_start, x_end, y_start, y_end; + for (int i = 0; i < dirty_area->inv_p; i++) { + /* Refresh the unjoined areas*/ + if (dirty_area->inv_area_joined[i] == 0) { + x_start = dirty_area->inv_areas[i].x1; + x_end = dirty_area->inv_areas[i].x2; + y_start = dirty_area->inv_areas[i].y1; + y_end = dirty_area->inv_areas[i].y2; + + rotate_copy_pixel((lv_color_t *)src, (lv_color_t *)dst, x_start, y_start, x_end, y_end, LV_HOR_RES, LV_VER_RES, + LVGL_PORT_ROTATION_DEGREE); + } + } +} + +static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + const int offsetx1 = area->x1; + const int offsetx2 = area->x2; + const int offsety1 = area->y1; + const int offsety2 = area->y2; + void *next_fb = NULL; + lv_port_flush_probe_t probe_result = FLUSH_PROBE_PART_COPY; + lv_disp_t *disp = lv_disp_get_default(); + + /* Action after last area refresh */ + if (lv_disp_flush_is_last(drv)) { + /* Check if the `full_refresh` flag has been triggered */ + if (drv->full_refresh) { + /* Reset flag */ + drv->full_refresh = 0; + + // Rotate and copy data from the whole screen LVGL's buffer to the next frame buffer + next_fb = flush_get_next_buf(lcd); + rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, + LV_HOR_RES, LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + + /* Switch the current RGB frame buffer to `next_fb` */ + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); + + /* Waiting for the current frame buffer to complete transmission */ + ulTaskNotifyValueClear(NULL, ULONG_MAX); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + /* Synchronously update the dirty area for another frame buffer */ + flush_dirty_copy(flush_get_next_buf(lcd), color_map, &dirty_area); + flush_get_next_buf(lcd); + } else { + /* Probe the copy method for the current dirty area */ + probe_result = flush_copy_probe(drv); + + if (probe_result == FLUSH_PROBE_FULL_COPY) { + /* Save current dirty area for next frame buffer */ + flush_dirty_save(&dirty_area); + + /* Set LVGL full-refresh flag and set flush ready in advance */ + drv->full_refresh = 1; + disp->rendering_in_progress = false; + lv_disp_flush_ready(drv); + + /* Force to refresh whole screen, and will invoke `flush_callback` recursively */ + lv_refr_now(_lv_refr_get_disp_refreshing()); + } else { + /* Update current dirty area for next frame buffer */ + next_fb = flush_get_next_buf(lcd); + flush_dirty_save(&dirty_area); + flush_dirty_copy(next_fb, color_map, &dirty_area); + + /* Switch the current RGB frame buffer to `next_fb` */ + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); + + /* Waiting for the current frame buffer to complete transmission */ + ulTaskNotifyValueClear(NULL, ULONG_MAX); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + if (probe_result == FLUSH_PROBE_PART_COPY) { + /* Synchronously update the dirty area for another frame buffer */ + flush_dirty_save(&dirty_area); + flush_dirty_copy(flush_get_next_buf(lcd), color_map, &dirty_area); + flush_get_next_buf(lcd); + } + } + } + } + + lv_disp_flush_ready(drv); +} + +#else + +static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + const int offsetx1 = area->x1; + const int offsetx2 = area->x2; + const int offsety1 = area->y1; + const int offsety2 = area->y2; + + /* Action after last area refresh */ + if (lv_disp_flush_is_last(drv)) { + /* Switch the current RGB frame buffer to `color_map` */ + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); + + /* Waiting for the last frame buffer to complete transmission */ + ulTaskNotifyValueClear(NULL, ULONG_MAX); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + } + + lv_disp_flush_ready(drv); +} +#endif /* LVGL_PORT_ROTATION_DEGREE */ + +#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 2 + +static void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + const int offsetx1 = area->x1; + const int offsetx2 = area->x2; + const int offsety1 = area->y1; + const int offsety2 = area->y2; + + /* Switch the current RGB frame buffer to `color_map` */ + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); + + /* Waiting for the last frame buffer to complete transmission */ + ulTaskNotifyValueClear(NULL, ULONG_MAX); + ulTaskNotifyTake(pdTRUE, portMAX_DELAY); + + lv_disp_flush_ready(drv); +} + +#elif LVGL_PORT_FULL_REFRESH && LVGL_PORT_DISP_BUFFER_NUM == 3 + +#if LVGL_PORT_ROTATION_DEGREE == 0 +static void *lvgl_port_rgb_last_buf = NULL; +static void *lvgl_port_rgb_next_buf = NULL; +static void *lvgl_port_flush_next_buf = NULL; +#endif + +void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + const int offsetx1 = area->x1; + const int offsetx2 = area->x2; + const int offsety1 = area->y1; + const int offsety2 = area->y2; + +#if LVGL_PORT_ROTATION_DEGREE != 0 + void *next_fb = get_next_frame_buffer(lcd); + + /* Rotate and copy dirty area from the current LVGL's buffer to the next RGB frame buffer */ + rotate_copy_pixel((lv_color_t *)color_map, (lv_color_t *)next_fb, offsetx1, offsety1, offsetx2, offsety2, LV_HOR_RES, + LV_VER_RES, LVGL_PORT_ROTATION_DEGREE); + + /* Switch the current RGB frame buffer to `next_fb` */ + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)next_fb); +#else + drv->draw_buf->buf1 = color_map; + drv->draw_buf->buf2 = lvgl_port_flush_next_buf; + lvgl_port_flush_next_buf = color_map; + + /* Switch the current RGB frame buffer to `color_map` */ + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); + + lvgl_port_rgb_next_buf = color_map; +#endif + + lv_disp_flush_ready(drv); +} +#endif + +IRAM_ATTR bool onRgbVsyncCallback(void *user_data) +{ + BaseType_t need_yield = pdFALSE; +#if LVGL_PORT_FULL_REFRESH && (LVGL_PORT_DISP_BUFFER_NUM == 3) && (LVGL_PORT_ROTATION_DEGREE == 0) + if (lvgl_port_rgb_next_buf != lvgl_port_rgb_last_buf) { + lvgl_port_flush_next_buf = lvgl_port_rgb_last_buf; + lvgl_port_rgb_last_buf = lvgl_port_rgb_next_buf; + } +#else + TaskHandle_t task_handle = (TaskHandle_t)user_data; + // Notify that the current RGB frame buffer has been transmitted + xTaskNotifyFromISR(task_handle, ULONG_MAX, eNoAction, &need_yield); +#endif + return (need_yield == pdTRUE); +} + +#else + +void flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + const int offsetx1 = area->x1; + const int offsetx2 = area->x2; + const int offsety1 = area->y1; + const int offsety2 = area->y2; + + lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_map); + // For RGB LCD, directly notify LVGL that the buffer is ready + if (lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB) { + lv_disp_flush_ready(drv); + } +} + +static void update_callback(lv_disp_drv_t *drv) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + static bool disp_init_mirror_x = lcd->getMirrorXFlag(); + static bool disp_init_mirror_y = lcd->getMirrorYFlag(); + static bool disp_init_swap_xy = lcd->getSwapXYFlag(); + + switch (drv->rotated) { + case LV_DISP_ROT_NONE: + lcd->swapXY(disp_init_swap_xy); + lcd->mirrorX(disp_init_mirror_x); + lcd->mirrorY(disp_init_mirror_y); + break; + case LV_DISP_ROT_90: + lcd->swapXY(!disp_init_swap_xy); + lcd->mirrorX(disp_init_mirror_x); + lcd->mirrorY(!disp_init_mirror_y); + break; + case LV_DISP_ROT_180: + lcd->swapXY(disp_init_swap_xy); + lcd->mirrorX(!disp_init_mirror_x); + lcd->mirrorY(!disp_init_mirror_y); + break; + case LV_DISP_ROT_270: + lcd->swapXY(!disp_init_swap_xy); + lcd->mirrorX(!disp_init_mirror_x); + lcd->mirrorY(disp_init_mirror_y); + break; + } + + ESP_LOGD(TAG, "Update display rotation to %d", drv->rotated); + ESP_LOGD(TAG, "Current mirror x: %d, mirror y: %d, swap xy: %d", lcd->getMirrorXFlag(), lcd->getMirrorYFlag(), lcd->getSwapXYFlag()); +} + +#endif /* LVGL_PORT_AVOID_TEAR */ + +void rounder_callback(lv_disp_drv_t *drv, lv_area_t *area) +{ + ESP_PanelLcd *lcd = (ESP_PanelLcd *)drv->user_data; + uint16_t x1 = area->x1; + uint16_t x2 = area->x2; + uint16_t y1 = area->y1; + uint16_t y2 = area->y2; + + uint8_t x_align = lcd->getXCoordAlign(); + if (x_align > 1) { + // round the start of coordinate down to the nearest (x_align * M) number + area->x1 = (x1 / x_align) * x_align; + // round the end of coordinate down to the nearest (x_align * (N + 1) - 1) number + area->x2 = ((x2 + x_align - 1) / x_align + 1) * x_align - 1; + } + + uint8_t y_align = lcd->getYCoordAlign(); + if (y_align > 1) { + // round the start of coordinate down to the nearest (y_align * M) number + area->y1 = (y1 / y_align) * y_align; + // round the end of coordinate down to the nearest (y_align * (N + 1) - 1) number + area->y2 = ((y2 + y_align - 1) / y_align + 1) * y_align - 1; + } +} + +static lv_disp_t *display_init(ESP_PanelLcd *lcd) +{ + ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, nullptr, "Invalid LCD device"); + ESP_PANEL_CHECK_FALSE_RET(lcd->getHandle() != nullptr, nullptr, "LCD device is not initialized"); + + static lv_disp_draw_buf_t disp_buf; + static lv_disp_drv_t disp_drv; + + // Alloc draw buffers used by LVGL + void *buf[LVGL_PORT_BUFFER_NUM_MAX] = { nullptr }; + int buffer_size = 0; + + ESP_LOGD(TAG, "Malloc memory for LVGL buffer"); +#if !LVGL_PORT_AVOID_TEAR + // Avoid tearing function is disabled + buffer_size = LVGL_PORT_BUFFER_SIZE; + for (int i = 0; (i < LVGL_PORT_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { + buf[i] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), LVGL_PORT_BUFFER_MALLOC_CAPS); + assert(buf[i]); + ESP_LOGD(TAG, "Buffer[%d] address: %p, size: %d", i, buf[i], buffer_size * sizeof(lv_color_t)); + } +#else + // To avoid the tearing effect, we should use at least two frame buffers: one for LVGL rendering and another for RGB output + buffer_size = LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_HEIGHT; +#if (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE == 0) && LVGL_PORT_FULL_REFRESH + + // With the usage of three buffers and full-refresh, we always have one buffer available for rendering, + // eliminating the need to wait for the RGB's sync signal + lvgl_port_rgb_last_buf = lcd->getRgbBufferByIndex(0); + buf[0] = lcd->getRgbBufferByIndex(1); + buf[1] = lcd->getRgbBufferByIndex(2); + lvgl_port_rgb_next_buf = lvgl_port_rgb_last_buf; + lvgl_port_flush_next_buf = buf[1]; + +#elif (LVGL_PORT_DISP_BUFFER_NUM >= 3) && (LVGL_PORT_ROTATION_DEGREE != 0) + + buf[0] = lcd->getRgbBufferByIndex(2); + +#elif LVGL_PORT_DISP_BUFFER_NUM >= 2 + + for (int i = 0; (i < LVGL_PORT_DISP_BUFFER_NUM) && (i < LVGL_PORT_BUFFER_NUM_MAX); i++) { + buf[i] = lcd->getRgbBufferByIndex(i); + } + +#endif +#endif /* LVGL_PORT_AVOID_TEAR */ + + // initialize LVGL draw buffers + lv_disp_draw_buf_init(&disp_buf, buf[0], buf[1], buffer_size); + + ESP_LOGD(TAG, "Register display driver to LVGL"); + lv_disp_drv_init(&disp_drv); + disp_drv.flush_cb = flush_callback; +#if (LVGL_PORT_ROTATION_DEGREE == 90) || (LVGL_PORT_ROTATION_DEGREE == 270) + disp_drv.hor_res = LVGL_PORT_DISP_HEIGHT; + disp_drv.ver_res = LVGL_PORT_DISP_WIDTH; +#else + disp_drv.hor_res = LVGL_PORT_DISP_WIDTH; + disp_drv.ver_res = LVGL_PORT_DISP_HEIGHT; +#endif +#if LVGL_PORT_AVOID_TEAR // Only available when the tearing effect is enabled +#if LVGL_PORT_FULL_REFRESH + disp_drv.full_refresh = 1; +#elif LVGL_PORT_DIRECT_MODE + disp_drv.direct_mode = 1; +#endif +#else // Only available when the tearing effect is disabled + disp_drv.drv_update_cb = update_callback; +#endif /* LVGL_PORT_AVOID_TEAR */ + disp_drv.draw_buf = &disp_buf; + disp_drv.user_data = (void *)lcd; + // Only available when the coordinate alignment is enabled + if (lcd->getXCoordAlign() > 1 || lcd->getYCoordAlign() > 1) { + disp_drv.rounder_cb = rounder_callback; + } + + return lv_disp_drv_register(&disp_drv); +} + +static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + ESP_PanelTouch *tp = (ESP_PanelTouch *)indev_drv->user_data; + ESP_PanelTouchPoint point; + + /* Read data from touch controller */ + int read_touch_result = tp->readPoints(&point, 1); + if (read_touch_result > 0) { + data->point.x = point.x; + data->point.y = point.y; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static lv_indev_t *indev_init(ESP_PanelTouch *tp) +{ + ESP_PANEL_CHECK_FALSE_RET(tp != nullptr, nullptr, "Invalid touch device"); + ESP_PANEL_CHECK_FALSE_RET(tp->getHandle() != nullptr, nullptr, "Touch device is not initialized"); + + static lv_indev_drv_t indev_drv_tp; + + ESP_LOGD(TAG, "Register input driver to LVGL"); + lv_indev_drv_init(&indev_drv_tp); + indev_drv_tp.type = LV_INDEV_TYPE_POINTER; + indev_drv_tp.read_cb = touchpad_read; + indev_drv_tp.user_data = (void *)tp; + + return lv_indev_drv_register(&indev_drv_tp); +} + +#if !LV_TICK_CUSTOM +static void tick_increment(void *arg) +{ + /* Tell LVGL how many milliseconds have elapsed */ + lv_tick_inc(LVGL_PORT_TICK_PERIOD_MS); +} + +static esp_err_t tick_init(void) +{ + // Tick interface for LVGL (using esp_timer to generate 2ms periodic event) + const esp_timer_create_args_t lvgl_tick_timer_args = { + .callback = &tick_increment, + .name = "LVGL tick" + }; + esp_timer_handle_t lvgl_tick_timer = NULL; + ESP_ERROR_CHECK(esp_timer_create(&lvgl_tick_timer_args, &lvgl_tick_timer)); + return esp_timer_start_periodic(lvgl_tick_timer, LVGL_PORT_TICK_PERIOD_MS * 1000); +} +#endif + +static void lvgl_port_task(void *arg) +{ + ESP_LOGD(TAG, "Starting LVGL task"); + + uint32_t task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS; + while (1) { + if (lvgl_port_lock(-1)) { + task_delay_ms = lv_timer_handler(); + lvgl_port_unlock(); + } + if (task_delay_ms > LVGL_PORT_TASK_MAX_DELAY_MS) { + task_delay_ms = LVGL_PORT_TASK_MAX_DELAY_MS; + } else if (task_delay_ms < LVGL_PORT_TASK_MIN_DELAY_MS) { + task_delay_ms = LVGL_PORT_TASK_MIN_DELAY_MS; + } + vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); + } +} + +IRAM_ATTR bool onRefreshFinishCallback(void *user_data) +{ + lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data; + + lv_disp_flush_ready(drv); + + return false; +} + +bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp) +{ + ESP_PANEL_CHECK_FALSE_RET(lcd != nullptr, false, "Invalid LCD device"); +#if LVGL_PORT_AVOID_TEAR + ESP_PANEL_CHECK_FALSE_RET(lcd->getBus()->getType() == ESP_PANEL_BUS_TYPE_RGB, false, "Avoid tearing function only works with RGB LCD now"); + ESP_LOGD(TAG, "Avoid tearing is enabled, mode: %d", LVGL_PORT_AVOID_TEARING_MODE); +#endif + + lv_disp_t *disp = nullptr; + lv_indev_t *indev = nullptr; + + lv_init(); +#if !LV_TICK_CUSTOM + ESP_PANEL_CHECK_ERR_RET(tick_init(), false, "Initialize LVGL tick failed"); +#endif + + ESP_LOGD(TAG, "Initialize LVGL display driver"); + disp = display_init(lcd); + ESP_PANEL_CHECK_NULL_RET(disp, false, "Initialize LVGL display driver failed"); + // Record the initial rotation of the display + lv_disp_set_rotation(disp, LV_DISP_ROT_NONE); + + // For non-RGB LCD, need to notify LVGL that the buffer is ready when the refresh is finished + if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB) { + ESP_LOGD(TAG, "Attach refresh finish callback to LCD"); + lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver); + } + + if (tp != nullptr) { + ESP_LOGD(TAG, "Initialize LVGL input driver"); + indev = indev_init(tp); + ESP_PANEL_CHECK_NULL_RET(indev, false, "Initialize LVGL input driver failed"); + +#if LVGL_PORT_ROTATION_DEGREE == 90 + tp->swapXY(!tp->getSwapXYFlag()); + tp->mirrorY(!tp->getMirrorYFlag()); +#elif LVGL_PORT_ROTATION_DEGREE == 180 + tp->mirrorX(!tp->getMirrorXFlag()); + tp->mirrorY(!tp->getMirrorYFlag()); +#elif LVGL_PORT_ROTATION_DEGREE == 270 + tp->swapXY(!tp->getSwapXYFlag()); + tp->mirrorX(!tp->getMirrorYFlag()); +#endif + } + + ESP_LOGD(TAG, "Create mutex for LVGL"); + lvgl_mux = xSemaphoreCreateRecursiveMutex(); + ESP_PANEL_CHECK_NULL_RET(lvgl_mux, false, "Create LVGL mutex failed"); + + ESP_LOGD(TAG, "Create LVGL task"); + BaseType_t core_id = (LVGL_PORT_TASK_CORE < 0) ? tskNO_AFFINITY : LVGL_PORT_TASK_CORE; + BaseType_t ret = xTaskCreatePinnedToCore(lvgl_port_task, "lvgl", LVGL_PORT_TASK_STACK_SIZE, NULL, + LVGL_PORT_TASK_PRIORITY, &lvgl_task_handle, core_id); + ESP_PANEL_CHECK_FALSE_RET(ret == pdPASS, false, "Create LVGL task failed"); + +#if LVGL_PORT_AVOID_TEAR + lcd->attachRefreshFinishCallback(onRgbVsyncCallback, (void *)lvgl_task_handle); +#endif + + return true; +} + +bool lvgl_port_lock(int timeout_ms) +{ + ESP_PANEL_CHECK_NULL_RET(lvgl_mux, false, "LVGL mutex is not initialized"); + + const TickType_t timeout_ticks = (timeout_ms < 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return (xSemaphoreTakeRecursive(lvgl_mux, timeout_ticks) == pdTRUE); +} + +bool lvgl_port_unlock(void) +{ + ESP_PANEL_CHECK_NULL_RET(lvgl_mux, false, "LVGL mutex is not initialized"); + + xSemaphoreGiveRecursive(lvgl_mux); + + return true; +} + +bool lvgl_port_deinit(void) +{ + lvgl_port_lock(-1); + if (lvgl_task_handle != nullptr) { + vTaskDelete(lvgl_task_handle); + lvgl_task_handle = nullptr; + } + lvgl_port_unlock(); + + lv_deinit(); + if (lvgl_mux != nullptr) { + vSemaphoreDelete(lvgl_mux); + lvgl_mux = nullptr; + } + + return true; +} diff --git a/test_apps/lvgl_port/main/lvgl_port_v8.h b/test_apps/lvgl_port/main/lvgl_port_v8.h new file mode 100644 index 00000000..67948749 --- /dev/null +++ b/test_apps/lvgl_port/main/lvgl_port_v8.h @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#pragma once + +#include "sdkconfig.h" +#include "ESP_Panel_Library.h" +#include "lvgl.h" + +// *INDENT-OFF* + +/** + * LVGL related parameters, can be adjusted by users + * + */ +#define LVGL_PORT_DISP_WIDTH (ESP_PANEL_LCD_WIDTH) // The width of the display +#define LVGL_PORT_DISP_HEIGHT (ESP_PANEL_LCD_HEIGHT) // The height of the display +#define LVGL_PORT_TICK_PERIOD_MS (2) // The period of the LVGL tick task, in milliseconds + +/** + * + * LVGL buffer related parameters, can be adjusted by users: + * + * (These parameters will be useless if the avoid tearing function is enabled) + * + * - Memory type for buffer allocation: + * - MALLOC_CAP_SPIRAM: Allocate LVGL buffer in PSRAM + * - MALLOC_CAP_INTERNAL: Allocate LVGL buffer in SRAM + * + * (The SRAM is faster than PSRAM, but the PSRAM has a larger capacity) + * (For SPI/QSPI LCD, it is recommended to allocate the buffer in SRAM, because the SPI DMA does not directly support PSRAM now) + * + * - The size (in bytes) and number of buffers: + * - Lager buffer size can improve FPS, but it will occupy more memory. Maximum buffer size is `LVGL_PORT_DISP_WIDTH * LVGL_PORT_DISP_HEIGHT`. + * - The number of buffers should be 1 or 2. + * + */ +#define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) // Allocate LVGL buffer in SRAM +// #define LVGL_PORT_BUFFER_MALLOC_CAPS (MALLOC_CAP_SPIRAM) // Allocate LVGL buffer in PSRAM +#define LVGL_PORT_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 20) +#define LVGL_PORT_BUFFER_NUM (2) + +/** + * LVGL timer handle task related parameters, can be adjusted by users + * + */ +#define LVGL_PORT_TASK_MAX_DELAY_MS (500) // The maximum delay of the LVGL timer task, in milliseconds +#define LVGL_PORT_TASK_MIN_DELAY_MS (2) // The minimum delay of the LVGL timer task, in milliseconds +#define LVGL_PORT_TASK_STACK_SIZE (6 * 1024) // The stack size of the LVGL timer task, in bytes +#define LVGL_PORT_TASK_PRIORITY (2) // The priority of the LVGL timer task +#define LVGL_PORT_TASK_CORE (0) + // The core of the LVGL timer task, `-1` means the don't specify the core + // Default is the same as the Arduino task + // This can be set to `1` only if the SoCs support dual-core, + // otherwise it should be set to `-1` or `0` + +/** + * Avoid tering related configurations, can be adjusted by users. + * + * (Currently, This function only supports RGB LCD and the version of LVGL must be >= 8.3.9) + * + */ +/** + * Set the avoid tearing mode: + * - 0: Disable avoid tearing function + * - 1: LCD double-buffer & LVGL full-refresh + * - 2: LCD triple-buffer & LVGL full-refresh + * - 3: LCD double-buffer & LVGL direct-mode (recommended) + * + */ +#define LVGL_PORT_AVOID_TEARING_MODE (CONFIG_LVGL_PORT_AVOID_TEARING_MODE) + +#if LVGL_PORT_AVOID_TEARING_MODE != 0 +/** + * As the anti-tearing feature typically consumes more PSRAM bandwidth, for the ESP32-S3, we need to utilize the Bounce + * buffer functionality to enhance the RGB data bandwidth. + * + * This feature will occupy `LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE * 2 * bytes_per_pixel` of SRAM memory. + * + */ +#define LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE (LVGL_PORT_DISP_WIDTH * 10) +/** + * When avoid tearing is enabled, the LVGL software rotation `lv_disp_set_rotation()` is not supported. + * But users can set the rotation degree(0/90/180/270) here, but this function will extremely reduce FPS. + * So it is recommended to be used when using a low resolution display. + * + * Set the rotation degree: + * - 0: 0 degree + * - 90: 90 degree + * - 180: 180 degree + * - 270: 270 degree + * + */ +#define LVGL_PORT_ROTATION_DEGREE (0) + +/** + * Here, some important configurations will be set based on different anti-tearing modes and rotation angles. + * No modification is required here. + * + * Users should use `lcd_bus->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM);` to set the buffer number before. If screen drifting occurs, please refer to the Troubleshooting section in the README. + * initializing the LCD bus + * + */ +#define LVGL_PORT_AVOID_TEAR (1) +// Set the buffer number and refresh mode according to the different modes +#if LVGL_PORT_AVOID_TEARING_MODE == 1 + #define LVGL_PORT_DISP_BUFFER_NUM (2) + #define LVGL_PORT_FULL_REFRESH (1) +#elif LVGL_PORT_AVOID_TEARING_MODE == 2 + #define LVGL_PORT_DISP_BUFFER_NUM (3) + #define LVGL_PORT_FULL_REFRESH (1) +#elif LVGL_PORT_AVOID_TEARING_MODE == 3 + #define LVGL_PORT_DISP_BUFFER_NUM (2) + #define LVGL_PORT_DIRECT_MODE (1) +#else + #error "Invalid avoid tearing mode, please set macro `LVGL_PORT_AVOID_TEARING_MODE` to one of `LVGL_PORT_AVOID_TEARING_MODE_*`" +#endif +// Check rotation +#if (LVGL_PORT_ROTATION_DEGREE != 0) && (LVGL_PORT_ROTATION_DEGREE != 90) && (LVGL_PORT_ROTATION_DEGREE != 180) && \ + (LVGL_PORT_ROTATION_DEGREE != 270) + #error "Invalid rotation degree, please set to 0, 90, 180 or 270" +#elif LVGL_PORT_ROTATION_DEGREE != 0 + #ifdef LVGL_PORT_DISP_BUFFER_NUM + #undef LVGL_PORT_DISP_BUFFER_NUM + #define LVGL_PORT_DISP_BUFFER_NUM (3) + #endif +#endif +#endif /* LVGL_PORT_AVOID_TEARING_MODE */ + +// *INDENT-OFF* + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Porting LVGL with LCD and touch panel. This function should be called after the initialization of the LCD and touch panel. + * + * @param lcd The pointer to the LCD panel device, mustn't be nullptr + * @param tp The pointer to the touch panel device, set to nullptr if is not used + * + * @return true if success, otherwise false + */ +bool lvgl_port_init(ESP_PanelLcd *lcd, ESP_PanelTouch *tp); + +/** + * @brief Lock the LVGL mutex. This function should be called before calling any LVGL APIs when not in LVGL task, + * and the `lvgl_port_unlock()` function should be called later. + * + * @param timeout_ms The timeout of the mutex lock, in milliseconds. If the timeout is set to `-1`, it will wait indefinitely. + * + * @return true if success, otherwise false + */ +bool lvgl_port_lock(int timeout_ms); + +/** + * @brief Unlock the LVGL mutex. This function should be called after using LVGL APIs when not in LVGL task, and the + * `lvgl_port_lock()` function should be called before. + * + * @return true if success, otherwise false + */ +bool lvgl_port_unlock(void); + +bool lvgl_port_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/test_apps/lvgl_port/main/test_app_main.c b/test_apps/lvgl_port/main/test_app_main.c new file mode 100644 index 00000000..bf8ef112 --- /dev/null +++ b/test_apps/lvgl_port/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf("| $$$$$$$ | $$$$$$$$| $$\\$$ $$| $$$$$ | $$\r\n"); + printf("| $$ | $$ | $$| $$ \\$$$$| $$_____ | $$_____\r\n"); + printf("| $$ | $$ | $$| $$ \\$$$| $$ \\| $$ \\\r\n"); + printf(" \\$$ \\$$ \\$$ \\$$ \\$$ \\$$$$$$$$ \\$$$$$$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/lvgl_port/main/test_lvgl_port.cpp b/test_apps/lvgl_port/main/test_lvgl_port.cpp new file mode 100644 index 00000000..64931df2 --- /dev/null +++ b/test_apps/lvgl_port/main/test_lvgl_port.cpp @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" +#include "lvgl.h" +#include "lvgl_port_v8.h" +#include "lv_demos.h" + +#define TEST_DISPLAY_SHOW_TIME_MS (5000) + +#define delay(x) vTaskDelay(pdMS_TO_TICKS(x)) + +using namespace std; + +static const char *TAG = "test_lvgl_port"; + +TEST_CASE("Test panel lvgl port to show demo", "[panel][lvgl]") +{ + shared_ptr panel = make_shared(); + TEST_ASSERT_NOT_NULL_MESSAGE(panel, "Create panel object failed"); + + ESP_LOGI(TAG, "Initialize display panel"); + TEST_ASSERT_TRUE_MESSAGE(panel->init(), "Panel init failed"); +#if LVGL_PORT_AVOID_TEAR + // When avoid tearing function is enabled, configure the RGB bus according to the LVGL configuration + ESP_PanelBus_RGB *rgb_bus = static_cast(panel->getLcd()->getBus()); + rgb_bus->configRgbFrameBufferNumber(LVGL_PORT_DISP_BUFFER_NUM); + rgb_bus->configRgbBounceBufferSize(LVGL_PORT_RGB_BOUNCE_BUFFER_SIZE); +#endif + TEST_ASSERT_TRUE_MESSAGE(panel->begin(), "Panel begin failed"); + + ESP_LOGI(TAG, "Initialize LVGL"); + lvgl_port_init(panel->getLcd(), panel->getTouch()); + + ESP_LOGI(TAG, "Create UI"); + /* Lock the mutex due to the LVGL APIs are not thread-safe */ + lvgl_port_lock(-1); + + // lv_demo_widgets(); + // lv_demo_benchmark(); + lv_demo_music(); + // lv_demo_stress(); + + /* Release the mutex */ + lvgl_port_unlock(); + + delay(TEST_DISPLAY_SHOW_TIME_MS); + + lvgl_port_deinit(); +} diff --git a/test_apps/lvgl_port/partitions.csv b/test_apps/lvgl_port/partitions.csv new file mode 100644 index 00000000..417a304c --- /dev/null +++ b/test_apps/lvgl_port/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, , 0x6000, +phy_init, data, phy, , 0x1000, +factory, app, factory, , 3M, diff --git a/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 b/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 new file mode 100644 index 00000000..adeda816 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.elecrow_crowpanel_7_0 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ELECROW_CROWPANEL_7_0=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit new file mode 100644 index 00000000..e81429f1 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_c3_lcdkit @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32c3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_C3_LCDKIT=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box new file mode 100644 index 00000000..6e15d03d --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 new file mode 100644 index 00000000..efa09e4b --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX_3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta new file mode 100644 index 00000000..e859d720 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_3_beta @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX_3_BETA=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite new file mode 100644 index 00000000..5c3fe36e --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_box_lite @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX_LITE=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye new file mode 100644 index 00000000..236bdd7c --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_eye @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_EYE=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 new file mode 100644 index 00000000..ce44a69c --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_korvo_2 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_KORVO_2=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board new file mode 100644 index 00000000..2f555ac0 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 new file mode 100644 index 00000000..c2794b33 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 new file mode 100644 index 00000000..4eff920e --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 new file mode 100644 index 00000000..057721b7 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg new file mode 100644 index 00000000..266fd377 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.espressif_esp32_s3_usb_otg @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_USB_OTG=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 b/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 new file mode 100644 index 00000000..200614be --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_4848S040C_I_Y_3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 new file mode 100644 index 00000000..ef2f58da --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core2 @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_M5STACK_M5CORE2=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 new file mode 100644 index 00000000..e8126d4b --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5core3 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_M5STACK_M5CORES3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial new file mode 100644 index 00000000..89f97068 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.m5stack_m5dial @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_M5STACK_M5DIAL=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 new file mode 100644 index 00000000..2dc3c165 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 new file mode 100644 index 00000000..55839aba --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 new file mode 100644 index 00000000..0b31c5f1 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 @@ -0,0 +1,11 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y + +CONFIG_LVGL_PORT_AVOID_TEARING_MODE_3=y diff --git a/test_apps/lvgl_port/sdkconfig.defaults b/test_apps/lvgl_port/sdkconfig.defaults new file mode 100644 index 00000000..13c81f79 --- /dev/null +++ b/test_apps/lvgl_port/sdkconfig.defaults @@ -0,0 +1,23 @@ +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_FREERTOS_HZ=1000 +CONFIG_LV_MEM_SIZE_KILOBYTES=60 +CONFIG_LV_MEMCPY_MEMSET_STD=y +CONFIG_LV_FONT_MONTSERRAT_12=y +CONFIG_LV_FONT_MONTSERRAT_16=y +CONFIG_LV_FONT_MONTSERRAT_18=y +CONFIG_LV_FONT_MONTSERRAT_20=y +CONFIG_LV_FONT_MONTSERRAT_22=y +CONFIG_LV_FONT_MONTSERRAT_24=y +CONFIG_LV_FONT_MONTSERRAT_26=y +CONFIG_LV_FONT_MONTSERRAT_28=y +CONFIG_LV_FONT_MONTSERRAT_30=y +CONFIG_LV_FONT_MONTSERRAT_32=y +CONFIG_LV_FONT_MONTSERRAT_34=y +CONFIG_LV_USE_DEMO_WIDGETS=y +CONFIG_LV_USE_DEMO_KEYPAD_AND_ENCODER=y +CONFIG_LV_USE_DEMO_BENCHMARK=y +CONFIG_LV_USE_DEMO_STRESS=y +CONFIG_LV_USE_DEMO_MUSIC=y +CONFIG_LV_DEMO_MUSIC_AUTO_PLAY=y diff --git a/test_apps/panel/CMakeLists.txt b/test_apps/panel/CMakeLists.txt new file mode 100644 index 00000000..028915bb --- /dev/null +++ b/test_apps/panel/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(panal_test) diff --git a/test_apps/panel/main/CMakeLists.txt b/test_apps/panel/main/CMakeLists.txt new file mode 100644 index 00000000..6489599d --- /dev/null +++ b/test_apps/panel/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register( + SRCS "test_app_main.c" "test_panel.cpp" + WHOLE_ARCHIVE +) diff --git a/test_apps/panel/main/idf_component.yml b/test_apps/panel/main/idf_component.yml new file mode 100644 index 00000000..e69eb944 --- /dev/null +++ b/test_apps/panel/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../ESP32_Display_Panel" diff --git a/test_apps/panel/main/test_app_main.c b/test_apps/panel/main/test_app_main.c new file mode 100644 index 00000000..bf8ef112 --- /dev/null +++ b/test_apps/panel/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf("| $$$$$$$ | $$$$$$$$| $$\\$$ $$| $$$$$ | $$\r\n"); + printf("| $$ | $$ | $$| $$ \\$$$$| $$_____ | $$_____\r\n"); + printf("| $$ | $$ | $$| $$ \\$$$| $$ \\| $$ \\\r\n"); + printf(" \\$$ \\$$ \\$$ \\$$ \\$$ \\$$$$$$$$ \\$$$$$$$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/panel/main/test_panel.cpp b/test_apps/panel/main/test_panel.cpp new file mode 100644 index 00000000..f545cf13 --- /dev/null +++ b/test_apps/panel/main/test_panel.cpp @@ -0,0 +1,114 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +#define TEST_LCD_ENABLE_ATTACH_CALLBACK (0) +#define TEST_LCD_SHOW_TIME_MS (3000) + +#define TEST_TOUCH_ENABLE_ATTACH_CALLBACK (0) +#define TEST_TOUCH_READ_POINTS_NUM (5) +#define TEST_TOUCH_READ_TIME_MS (3000) +#define TEST_TOUCH_READ_DELAY_MS (30) + +#define delay(x) vTaskDelay(pdMS_TO_TICKS(x)) + +using namespace std; + +static const char *TAG = "test_panel"; + +#if TEST_LCD_ENABLE_ATTACH_CALLBACK +IRAM_ATTR static bool onLcdRefreshFinishCallback(void *user_data) +{ + esp_rom_printf("Refresh finish callback\n"); + + return false; +} +#endif + +#if TEST_TOUCH_ENABLE_ATTACH_CALLBACK && (ESP_PANEL_TOUCH_IO_INT >= 0) +IRAM_ATTR static bool onTouchInterruptCallback(void *user_data) +{ + esp_rom_printf("Touch interrupt callback\n"); + + return false; +} +#endif + +TEST_CASE("Test panel to draw color bar and read touch", "[panel]") +{ + shared_ptr panel = make_shared(); + TEST_ASSERT_NOT_NULL_MESSAGE(panel, "Create panel object failed"); + + ESP_LOGI(TAG, "Initialize display panel"); + TEST_ASSERT_TRUE_MESSAGE(panel->init(), "Panel init failed"); + TEST_ASSERT_TRUE_MESSAGE(panel->begin(), "Panel begin failed"); + + ESP_PanelLcd *lcd = panel->getLcd(); + ESP_PanelTouch *touch = panel->getTouch(); + ESP_PanelBacklight *backlight = panel->getBacklight(); + + if (backlight != nullptr) { + ESP_LOGI(TAG, "Turn off the backlight"); + backlight->off(); + } else { + ESP_LOGI(TAG, "Backlight is not available"); + } + + if (lcd != nullptr) { +#if TEST_LCD_ENABLE_ATTACH_CALLBACK + TEST_ASSERT_TRUE_MESSAGE( + lcd->attachRefreshFinishCallback(onLcdRefreshFinishCallback, NULL), "Attach refresh callback failed" + ); +#endif + ESP_LOGI(TAG, "Draw color bar from top to bottom, the order is B - G - R"); + TEST_ASSERT_TRUE_MESSAGE( + lcd->colorBarTest(panel->getLcdWidth(), panel->getLcdHeight()), "LCD color bar test failed" + ); + delay(TEST_LCD_SHOW_TIME_MS); + } else { + ESP_LOGI(TAG, "LCD is not available"); + } + + if (backlight != nullptr) { + ESP_LOGI(TAG, "Turn on the backlight"); + TEST_ASSERT_TRUE_MESSAGE(backlight->on(), "Backlight on failed"); + } + + if (touch != nullptr) { +#if TEST_LCD_ENABLE_ATTACH_CALLBACK && (ESP_PANEL_TOUCH_IO_INT >= 0) + TEST_ASSERT_TRUE_MESSAGE( + touch->attachInterruptCallback(onTouchInterruptCallback, NULL), "Attach touch interrupt callback failed" + ); +#endif + uint32_t t = 0; + ESP_PanelTouchPoint point[TEST_TOUCH_READ_POINTS_NUM]; + int read_touch_result = 0; + + ESP_LOGI(TAG, "Reading touch_device point..."); + while (t++ < TEST_TOUCH_READ_TIME_MS / TEST_TOUCH_READ_DELAY_MS) { + read_touch_result = touch->readPoints(point, TEST_TOUCH_READ_POINTS_NUM, TEST_TOUCH_READ_DELAY_MS); + if (read_touch_result > 0) { + for (int i = 0; i < read_touch_result; i++) { + ESP_LOGI(TAG, "Touch point(%d): x %d, y %d, strength %d\n", i, point[i].x, point[i].y, point[i].strength); + } + } else if (read_touch_result < 0) { + ESP_LOGE(TAG, "Read touch_device point failed"); + } + if (!touch->isInterruptEnabled()) { + delay(TEST_TOUCH_READ_DELAY_MS); + } + } + } else { + ESP_LOGI(TAG, "Touch is not available"); + } +} diff --git a/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 b/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 new file mode 100644 index 00000000..7d488c6c --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.elecrow_crowpanel_7_0 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ELECROW_CROWPANEL_7_0=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit b/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit new file mode 100644 index 00000000..e81429f1 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_c3_lcdkit @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32c3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_C3_LCDKIT=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box new file mode 100644 index 00000000..6e15d03d --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 new file mode 100644 index 00000000..efa09e4b --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX_3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta new file mode 100644 index 00000000..e859d720 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_3_beta @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX_3_BETA=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite new file mode 100644 index 00000000..5c3fe36e --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_box_lite @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_BOX_LITE=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye new file mode 100644 index 00000000..236bdd7c --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_eye @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_EYE=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 new file mode 100644 index 00000000..ce44a69c --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_korvo_2 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_KORVO_2=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board new file mode 100644 index 00000000..0246bddb --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 new file mode 100644 index 00000000..71375001 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 new file mode 100644 index 00000000..605f2d29 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_2_v1_5 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_2_V1_5=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 new file mode 100644 index 00000000..3186a9a8 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_lcd_ev_board_v1_5 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_LCD_EV_BOARD_V1_5=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg new file mode 100644 index 00000000..266fd377 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.espressif_esp32_s3_usb_otg @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_S3_USB_OTG=y diff --git a/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 b/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 new file mode 100644 index 00000000..7904e91b --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.jingcai_esp32_4848S040C_I_Y_3 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_ESP32_4848S040C_I_Y_3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5core2 b/test_apps/panel/sdkconfig.ci.m5stack_m5core2 new file mode 100644 index 00000000..ef2f58da --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5core2 @@ -0,0 +1,3 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_M5STACK_M5CORE2=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5core3 b/test_apps/panel/sdkconfig.ci.m5stack_m5core3 new file mode 100644 index 00000000..e8126d4b --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5core3 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_M5STACK_M5CORES3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.m5stack_m5dial b/test_apps/panel/sdkconfig.ci.m5stack_m5dial new file mode 100644 index 00000000..89f97068 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.m5stack_m5dial @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_M5STACK_M5DIAL=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 new file mode 100644 index 00000000..7848f3e2 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_1_85 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_1_85=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 new file mode 100644 index 00000000..55839aba --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_2_1 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_2_1=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 new file mode 100644 index 00000000..9cdf5a34 --- /dev/null +++ b/test_apps/panel/sdkconfig.ci.waveshare_esp32_s3_touch_lcd_4_3 @@ -0,0 +1,9 @@ +CONFIG_IDF_TARGET="esp32s3" +CONFIG_BOARD_MANUFACTURER_ALL=y +CONFIG_BOARD_WAVESHARE_ESP32_S3_Touch_LCD_4_3=y + +CONFIG_SPIRAM=y +CONFIG_SPIRAM_MODE_OCT=y +CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y +CONFIG_SPIRAM_RODATA=y +CONFIG_SPIRAM_SPEED_80M=y diff --git a/test_apps/panel/sdkconfig.defaults b/test_apps/panel/sdkconfig.defaults new file mode 100644 index 00000000..4f5a4441 --- /dev/null +++ b/test_apps/panel/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/touch/i2c/CMakeLists.txt b/test_apps/touch/i2c/CMakeLists.txt new file mode 100644 index 00000000..e97e2c30 --- /dev/null +++ b/test_apps/touch/i2c/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2c_touch_test) diff --git a/test_apps/touch/i2c/main/CMakeLists.txt b/test_apps/touch/i2c/main/CMakeLists.txt new file mode 100644 index 00000000..92afe161 --- /dev/null +++ b/test_apps/touch/i2c/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register( + SRCS "test_app_main.c" "test_i2c_touch.cpp" + PRIV_REQUIRES esp_lcd driver + WHOLE_ARCHIVE +) + +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-missing-field-initializers) diff --git a/test_apps/touch/i2c/main/idf_component.yml b/test_apps/touch/i2c/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/touch/i2c/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/touch/i2c/main/test_app_main.c b/test_apps/touch/i2c/main/test_app_main.c new file mode 100644 index 00000000..28b5cd63 --- /dev/null +++ b/test_apps/touch/i2c/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf(" | $$ | $$$$$$ | $$ __ | $$ | $$ | $$| $$ | $$| $$ | $$ | $$\r\n"); + printf(" _| $$_ | $$_____ | $$__/ \\ | $$ | $$__/ $$| $$__/ $$| $$_____ | $$ | $$\r\n"); + printf("| $$ \\| $$ \\ \\$$ $$ | $$ \\$$ $$ \\$$ $$ \\$$ \\| $$ | $$\r\n"); + printf(" \\$$$$$$ \\$$$$$$$$ \\$$$$$$ \\$$ \\$$$$$$ \\$$$$$$ \\$$$$$$$ \\$$ \\$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/touch/i2c/main/test_i2c_touch.cpp b/test_apps/touch/i2c/main/test_i2c_touch.cpp new file mode 100644 index 00000000..741d7272 --- /dev/null +++ b/test_apps/touch/i2c/main/test_i2c_touch.cpp @@ -0,0 +1,119 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your touch_device spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_TOUCH_ADDRESS (0) // Typically set to 0 to use the default address. +// - For touchs with only one address, set to 0 +// - For touchs with multiple addresses, set to 0 or the address +// Like GT911, there are two addresses: 0x5D(default) and 0x14 +#define TEST_TOUCH_WIDTH (480) +#define TEST_TOUCH_HEIGHT (480) +#define TEST_TOUCH_I2C_FREQ_HZ (400 * 1000) +#define TEST_TOUCH_READ_POINTS_NUM (5) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_TOUCH_PIN_NUM_I2C_SCL (10) +#define TEST_TOUCH_PIN_NUM_I2C_SDA (9) +#define TEST_TOUCH_PIN_NUM_RST (13) // Set to `-1` if not used +// For GT911, the RST pin is also used to configure the I2C address +#define TEST_TOUCH_PIN_NUM_INT (14) // Set to `-1` if not used +// For GT911, the INT pin is also used to configure the I2C address + +#define TEST_READ_TOUCH_DELAY_MS (30) +#define TEST_READ_TOUCH_TIME_MS (3000) + +static const char *TAG = "test_i2c_touch"; + +#define delay(x) vTaskDelay(pdMS_TO_TICKS(x)) + +#if TEST_TOUCH_PIN_NUM_INT >= 0 +IRAM_ATTR static bool onTouchInterruptCallback(void *user_data) +{ + esp_rom_printf("Touch interrupt callback\n"); + + return false; +} +#endif + +static void run_test(shared_ptr touch_device) +{ + touch_device->init(); + touch_device->begin(); +#if TEST_TOUCH_PIN_NUM_INT >= 0 + touch_device->attachInterruptCallback(onTouchInterruptCallback, NULL); +#endif + + uint32_t t = 0; + while (t++ < TEST_READ_TOUCH_TIME_MS / TEST_READ_TOUCH_DELAY_MS) { + ESP_PanelTouchPoint point[TEST_TOUCH_READ_POINTS_NUM]; + int read_touch_result = touch_device->readPoints(point, TEST_TOUCH_READ_POINTS_NUM, TEST_READ_TOUCH_DELAY_MS); + + if (read_touch_result > 0) { + for (int i = 0; i < read_touch_result; i++) { + ESP_LOGI(TAG, "Touch point(%d): x %d, y %d, strength %d\n", i, point[i].x, point[i].y, point[i].strength); + } + } else if (read_touch_result < 0) { + ESP_LOGE(TAG, "Read touch_device point failed"); + } +#if TEST_TOUCH_PIN_NUM_INT < 0 + delay(TEST_READ_TOUCH_DELAY_MS); +#endif + } +} + +#define CREATE_TOUCH_BUS(name) \ + ({ \ + ESP_LOGI(TAG, "Create touch bus"); \ + shared_ptr touch_bus = make_shared( \ + TEST_TOUCH_PIN_NUM_I2C_SCL, TEST_TOUCH_PIN_NUM_I2C_SDA, \ + (esp_lcd_panel_io_i2c_config_t)ESP_PANEL_TOUCH_I2C_PANEL_IO_CONFIG(name) \ + ); \ + TEST_ASSERT_NOT_NULL_MESSAGE(touch_bus, "Create panel bus object failed"); \ + touch_bus->configI2cFreqHz(TEST_TOUCH_I2C_FREQ_HZ); \ + TEST_ASSERT_TRUE_MESSAGE(touch_bus->begin(), "Panel bus begin failed"); \ + touch_bus; \ + }) +#define CREATE_TOUCH(name, touch_bus) \ + ({ \ + ESP_LOGI(TAG, "Create touch device: " #name); \ + shared_ptr touch_device = make_shared( \ + touch_bus, TEST_TOUCH_WIDTH, TEST_TOUCH_HEIGHT, TEST_TOUCH_PIN_NUM_RST, TEST_TOUCH_PIN_NUM_INT \ + ); \ + TEST_ASSERT_NOT_NULL_MESSAGE(touch_device, "Create TOUCH object failed"); \ + touch_device; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test touch (" #name ") to draw color bar", "[i2c_touch][" #name "]") \ + { \ + shared_ptr touch_bus = CREATE_TOUCH_BUS(name); \ + shared_ptr touch_device = CREATE_TOUCH(name, touch_bus.get()); \ + run_test(touch_device); \ + } + +/** + * Here to create test cases for different touchs + * + */ +CREATE_TEST_CASE(CST816S) +CREATE_TEST_CASE(FT5x06) +CREATE_TEST_CASE(GT1151) +CREATE_TEST_CASE(TT21100) +CREATE_TEST_CASE(ST1633) +CREATE_TEST_CASE(ST7123) diff --git a/test_apps/touch/i2c/sdkconfig.defaults b/test_apps/touch/i2c/sdkconfig.defaults new file mode 100644 index 00000000..e0e5bb6d --- /dev/null +++ b/test_apps/touch/i2c/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT= +CONFIG_FREERTOS_HZ=1000 diff --git a/test_apps/touch/spi/CMakeLists.txt b/test_apps/touch/spi/CMakeLists.txt new file mode 100644 index 00000000..03ad00c5 --- /dev/null +++ b/test_apps/touch/spi/CMakeLists.txt @@ -0,0 +1,5 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(spi_touch_test) diff --git a/test_apps/touch/spi/main/CMakeLists.txt b/test_apps/touch/spi/main/CMakeLists.txt new file mode 100644 index 00000000..aecd05fa --- /dev/null +++ b/test_apps/touch/spi/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRCS "test_app_main.c" "test_spi_touch.cpp" + PRIV_REQUIRES esp_lcd driver + WHOLE_ARCHIVE +) diff --git a/test_apps/touch/spi/main/idf_component.yml b/test_apps/touch/spi/main/idf_component.yml new file mode 100644 index 00000000..bbace3aa --- /dev/null +++ b/test_apps/touch/spi/main/idf_component.yml @@ -0,0 +1,9 @@ +## IDF Component Manager Manifest File +dependencies: + test_utils: + path: ${IDF_PATH}/tools/unit-test-app/components/test_utils + test_driver_utils: + path: ${IDF_PATH}/components/driver/test_apps/components/test_driver_utils + ESP32_Display_Panel: + version: "*" + override_path: "../../../../../ESP32_Display_Panel" diff --git a/test_apps/touch/spi/main/test_app_main.c b/test_apps/touch/spi/main/test_app_main.c new file mode 100644 index 00000000..bb5d99cc --- /dev/null +++ b/test_apps/touch/spi/main/test_app_main.c @@ -0,0 +1,63 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "unity.h" +#include "unity_test_runner.h" + +// 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"); + printf(" _\\$$$$$$\\| $$$$$$$ | $$ | $$ | $$ | $$| $$ | $$| $$ | $$ | $$\r\n"); + printf("| \\__| $$| $$ _| $$_ | $$ | $$__/ $$| $$__/ $$| $$_____ | $$ | $$\r\n"); + printf(" \\$$ $$| $$ | $$ \\ | $$ \\$$ $$ \\$$ $$ \\$$ \\| $$ | $$\r\n"); + printf(" \\$$$$$$ \\$$ \\$$$$$$ \\$$ \\$$$$$$ \\$$$$$$ \\$$$$$$$ \\$$ \\$$\r\n"); + unity_run_menu(); +} diff --git a/test_apps/touch/spi/main/test_spi_touch.cpp b/test_apps/touch/spi/main/test_spi_touch.cpp new file mode 100644 index 00000000..319b81da --- /dev/null +++ b/test_apps/touch/spi/main/test_spi_touch.cpp @@ -0,0 +1,110 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "unity.h" +#include "unity_test_runner.h" +#include "ESP_Panel_Library.h" + +using namespace std; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your touch_device spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_TOUCH_WIDTH (240) +#define TEST_TOUCH_HEIGHT (320) +#define TEST_TOUCH_SPI_FREQ_HZ (1 * 1000 * 1000) +#define TEST_TOUCH_READ_POINTS_NUM (1) + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your board spec //////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define TEST_TOUCH_PIN_NUM_SPI_CS (46) +#define TEST_TOUCH_PIN_NUM_SPI_SCK (10) +#define TEST_TOUCH_PIN_NUM_SPI_MOSI (14) +#define TEST_TOUCH_PIN_NUM_SPI_MISO (8) +#define TEST_TOUCH_PIN_NUM_RST (-1) +#define TEST_TOUCH_PIN_NUM_INT (-1) + +#define TEST_READ_TOUCH_DELAY_MS (30) +#define TEST_READ_TOUCH_TIME_MS (3000) + +static const char *TAG = "test_spi_touch"; + +#define delay(x) vTaskDelay(pdMS_TO_TICKS(x)) + +#if TEST_TOUCH_PIN_NUM_INT >= 0 +IRAM_ATTR static bool onTouchInterruptCallback(void *user_data) +{ + esp_rom_printf("Touch interrupt callback\n"); + + return false; +} +#endif + +static void run_test(shared_ptr touch_device) +{ + touch_device->init(); + touch_device->begin(); +#if TEST_TOUCH_PIN_NUM_INT >= 0 + touch_device->attachInterruptCallback(onTouchInterruptCallback, NULL); +#endif + + uint32_t t = 0; + while (t++ < TEST_READ_TOUCH_TIME_MS / TEST_READ_TOUCH_DELAY_MS) { + ESP_PanelTouchPoint point[TEST_TOUCH_READ_POINTS_NUM]; + int read_touch_result = touch_device->readPoints(point, TEST_TOUCH_READ_POINTS_NUM, TEST_READ_TOUCH_DELAY_MS); + + if (read_touch_result > 0) { + for (int i = 0; i < read_touch_result; i++) { + ESP_LOGI(TAG, "Touch point(%d): x %d, y %d, strength %d\n", i, point[i].x, point[i].y, point[i].strength); + } + } else if (read_touch_result < 0) { + ESP_LOGE(TAG, "Read touch_device point failed"); + } +#if TEST_TOUCH_PIN_NUM_INT < 0 + delay(TEST_READ_TOUCH_DELAY_MS); +#endif + } +} + +#define CREATE_TOUCH_BUS(name) \ + ({ \ + ESP_LOGI(TAG, "Create touch bus"); \ + shared_ptr touch_bus = make_shared( \ + TEST_TOUCH_PIN_NUM_SPI_SCK, TEST_TOUCH_PIN_NUM_SPI_MOSI, TEST_TOUCH_PIN_NUM_SPI_MISO, \ + (esp_lcd_panel_io_spi_config_t)ESP_PANEL_TOUCH_SPI_PANEL_IO_CONFIG(name, TEST_TOUCH_PIN_NUM_SPI_CS) \ + ); \ + TEST_ASSERT_NOT_NULL_MESSAGE(touch_bus, "Create panel bus object failed"); \ + touch_bus->configSpiFreqHz(TEST_TOUCH_SPI_FREQ_HZ); \ + TEST_ASSERT_TRUE_MESSAGE(touch_bus->begin(), "Panel bus begin failed"); \ + touch_bus; \ + }) +#define CREATE_TOUCH(name, touch_bus) \ + ({ \ + ESP_LOGI(TAG, "Create touch device: " #name); \ + shared_ptr touch_device = make_shared( \ + touch_bus, TEST_TOUCH_WIDTH, TEST_TOUCH_HEIGHT, TEST_TOUCH_PIN_NUM_RST, TEST_TOUCH_PIN_NUM_INT \ + ); \ + TEST_ASSERT_NOT_NULL_MESSAGE(touch_device, "Create TOUCH object failed"); \ + touch_device; \ + }) +#define CREATE_TEST_CASE(name) \ + TEST_CASE("Test touch (" #name ") to draw color bar", "[spi_touch][" #name "]") \ + { \ + shared_ptr touch_bus = CREATE_TOUCH_BUS(name); \ + shared_ptr touch_device = CREATE_TOUCH(name, touch_bus.get()); \ + run_test(touch_device); \ + } + +/** + * Here to create test cases for different touchs + * + */ +CREATE_TEST_CASE(XPT2046) diff --git a/test_apps/touch/spi/sdkconfig.defaults b/test_apps/touch/spi/sdkconfig.defaults new file mode 100644 index 00000000..e0e5bb6d --- /dev/null +++ b/test_apps/touch/spi/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_ESP_TASK_WDT= +CONFIG_FREERTOS_HZ=1000 diff --git a/tools/check_file_version.py b/tools/check_file_version.py index 9f0cb223..ddcc031f 100644 --- a/tools/check_file_version.py +++ b/tools/check_file_version.py @@ -5,92 +5,92 @@ import sys import re -internal_version_file = "src/ESP_PanelVersions.h" +internal_version_file = 'src/ESP_PanelVersions.h' internal_version_macross = [ { - "file": "library.properties", - "macro": { - "major": "ESP_PANEL_VERSION_MAJOR", - "minor": "ESP_PANEL_VERSION_MINOR", - "patch": "ESP_PANEL_VERSION_PATCH" + 'file': 'library.properties', + 'macro': { + 'major': 'ESP_PANEL_VERSION_MAJOR', + 'minor': 'ESP_PANEL_VERSION_MINOR', + 'patch': 'ESP_PANEL_VERSION_PATCH' }, }, { - "file": "ESP_Panel_Conf.h", - "macro": { - "major": "ESP_PANEL_CONF_VERSION_MAJOR", - "minor": "ESP_PANEL_CONF_VERSION_MINOR", - "patch": "ESP_PANEL_CONF_VERSION_PATCH" + 'file': 'ESP_Panel_Conf.h', + 'macro': { + 'major': 'ESP_PANEL_CONF_VERSION_MAJOR', + 'minor': 'ESP_PANEL_CONF_VERSION_MINOR', + 'patch': 'ESP_PANEL_CONF_VERSION_PATCH' }, }, { - "file": "ESP_Panel_Board_Custom.h", - "macro": { - "major": "ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR", - "minor": "ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR", - "patch": "ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH" + 'file': 'ESP_Panel_Board_Custom.h', + 'macro': { + 'major': 'ESP_PANEL_BOARD_CUSTOM_VERSION_MAJOR', + 'minor': 'ESP_PANEL_BOARD_CUSTOM_VERSION_MINOR', + 'patch': 'ESP_PANEL_BOARD_CUSTOM_VERSION_PATCH' }, }, { - "file": "ESP_Panel_Board_Supported.h", - "macro": { - "major": "ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR", - "minor": "ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR", - "patch": "ESP_PANEL_BOARD_SUPPORTED_VERSION_PATCH" + 'file': 'ESP_Panel_Board_Supported.h', + 'macro': { + 'major': 'ESP_PANEL_BOARD_SUPPORTED_VERSION_MAJOR', + 'minor': 'ESP_PANEL_BOARD_SUPPORTED_VERSION_MINOR', + 'patch': 'ESP_PANEL_BOARD_SUPPORTED_VERSION_PATCH' }, }, ] file_version_macros = [ { - "file": "ESP_Panel_Conf.h", - "macro": { - "major": "ESP_PANEL_CONF_FILE_VERSION_MAJOR", - "minor": "ESP_PANEL_CONF_FILE_VERSION_MINOR", - "patch": "ESP_PANEL_CONF_FILE_VERSION_PATCH" + 'file': 'ESP_Panel_Conf.h', + 'macro': { + 'major': 'ESP_PANEL_CONF_FILE_VERSION_MAJOR', + 'minor': 'ESP_PANEL_CONF_FILE_VERSION_MINOR', + 'patch': 'ESP_PANEL_CONF_FILE_VERSION_PATCH' }, }, { - "file": "ESP_Panel_Board_Custom.h", - "macro": { - "major": "ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR", - "minor": "ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR", - "patch": "ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH" + 'file': 'ESP_Panel_Board_Custom.h', + 'macro': { + 'major': 'ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MAJOR', + 'minor': 'ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_MINOR', + 'patch': 'ESP_PANEL_BOARD_CUSTOM_FILE_VERSION_PATCH' }, }, { - "file": "ESP_Panel_Board_Supported.h", - "macro": { - "major": "ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR", - "minor": "ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR", - "patch": "ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH" + 'file': 'ESP_Panel_Board_Supported.h', + 'macro': { + 'major': 'ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MAJOR', + 'minor': 'ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_MINOR', + 'patch': 'ESP_PANEL_BOARD_SUPPORTED_FILE_VERSION_PATCH' }, }, ] -arduino_version_file = "library.properties" +arduino_version_file = 'library.properties' def extract_file_version(file_path, version_dict): file_contents = [] - content_str = "" + content_str = '' with open(file_path, 'r') as file: file_contents.append(file.readlines()) for content in file_contents: content_str = ''.join(content) version_macro = version_dict['macro'] - major_version = re.search(r'#define ' + version_macro["major"] + r' (\d+)', content_str) - minor_version = re.search(r'#define ' + version_macro["minor"] + r' (\d+)', content_str) - patch_version = re.search(r'#define ' + version_macro["patch"] + r' (\d+)', content_str) + major_version = re.search(r'#define ' + version_macro['major'] + r' (\d+)', content_str) + minor_version = re.search(r'#define ' + version_macro['minor'] + r' (\d+)', content_str) + patch_version = re.search(r'#define ' + version_macro['patch'] + r' (\d+)', content_str) if major_version and minor_version and patch_version: - return {"file": version_dict['file'], "version": major_version.group(1) + '.' + minor_version.group(1) + '.' + patch_version.group(1)} + return {'file': version_dict['file'], 'version': major_version.group(1) + '.' + minor_version.group(1) + '.' + patch_version.group(1)} return None def extract_arduino_version(file_path): file_contents = [] - content_str = "" + content_str = '' with open(file_path, 'r') as file: file_contents.append(file.readlines()) for content in file_contents: @@ -99,12 +99,12 @@ def extract_arduino_version(file_path): version = re.search(r'version=(\d+\.\d+\.\d+)', content_str) if version: - return {"file": os.path.basename(file_path), "version": version.group(1)} + return {'file': os.path.basename(file_path), 'version': version.group(1)} return None -if __name__ == "__main__": +if __name__ == '__main__': if len(sys.argv) >= 3: search_directory = sys.argv[1] @@ -138,20 +138,20 @@ def extract_arduino_version(file_path): if (internal_version['file'] == versions['file']) and (internal_version['version'] != versions['version']): print(f"Version mismatch: '{internal_version['file']}'") sys.exit(1) - print(f"Version matched") + print(f'Version matched') # Extract arduino version arduino_version_path = os.path.join(search_directory, arduino_version_file) arduino_version = extract_arduino_version(arduino_version_path) print(f"Arduino version extracted from '{arduino_version_path}") if arduino_version: - print(f"Arduino version: {arduino_version}") + print(f'Arduino version: {arduino_version}') else: - print(f"Arduino version not found") + print(f'Arduino version not found') # Check arduino version for internal_version in internal_versions: if (internal_version['file'] == arduino_version_file) and (internal_version['version'] != arduino_version['version']): print(f"Arduino version mismatch: '{internal_version['file']}'") sys.exit(1) - print(f"Arduino Version matched") + print(f'Arduino Version matched') diff --git a/tools/sync_conf_files.py b/tools/sync_conf_files.py index 503c1da9..70df4005 100644 --- a/tools/sync_conf_files.py +++ b/tools/sync_conf_files.py @@ -5,7 +5,7 @@ import sys import shutil -exclude_dirs = ["./build"] +exclude_dirs = ['./build'] def is_in_directory(file_path, directory): @@ -24,7 +24,7 @@ def is_same_file(file1, file2): def replace_files(search_directory, file_path): - if os.path.dirname(file_path) == "": + if os.path.dirname(file_path) == '': file_path = os.path.join(search_directory, file_path) filename = os.path.basename(file_path) @@ -53,7 +53,7 @@ def replace_files(search_directory, file_path): shutil.copy(src_file, file_path) -if __name__ == "__main__": +if __name__ == '__main__': if len(sys.argv) >= 3: search_directory = sys.argv[1] @@ -61,4 +61,4 @@ def replace_files(search_directory, file_path): file_path = sys.argv[i] replace_files(search_directory, file_path) - print("Replacement completed.") + print('Replacement completed.')