From 7f313496c347dc8f09b9bb90a35b65d64a4ae322 Mon Sep 17 00:00:00 2001 From: Vilem Zavodny Date: Wed, 6 Mar 2024 13:57:12 +0100 Subject: [PATCH] feat(LVGL port): Support for LVGL9 and support for MIPI-DSI screen. --- .build-test-rules.yml | 9 + .github/workflows/upload_component_noglib.yml | 1 + .idf_build_apps.toml | 7 +- bsp/esp-box-3/README.md | 2 +- bsp/esp-box-3/idf_component.yml | 3 +- bsp/esp-box-lite/README.md | 2 +- bsp/esp-box-lite/idf_component.yml | 3 +- bsp/esp-box/README.md | 2 +- bsp/esp-box/idf_component.yml | 3 +- bsp/esp32_azure_iot_kit/README.md | 2 +- bsp/esp32_azure_iot_kit/idf_component.yml | 3 +- bsp/esp32_c3_lcdkit/README.md | 2 +- bsp/esp32_c3_lcdkit/idf_component.yml | 3 +- bsp/esp32_p4_function_ev_board/README.md | 14 +- .../esp32_p4_function_ev_board.c | 6 +- .../idf_component.yml | 5 +- bsp/esp32_s2_kaluga_kit/README.md | 2 +- bsp/esp32_s2_kaluga_kit/idf_component.yml | 3 +- .../include/bsp/esp32_s2_kaluga_kit.h | 2 +- bsp/esp32_s3_eye/README.md | 2 +- bsp/esp32_s3_eye/idf_component.yml | 3 +- bsp/esp32_s3_korvo_2/README.md | 2 +- bsp/esp32_s3_korvo_2/idf_component.yml | 3 +- bsp/esp32_s3_usb_otg/README.md | 2 +- bsp/esp32_s3_usb_otg/idf_component.yml | 3 +- bsp/esp_bsp_generic/README.md | 2 +- bsp/esp_bsp_generic/idf_component.yml | 3 +- bsp/esp_wrover_kit/README.md | 2 +- bsp/esp_wrover_kit/idf_component.yml | 3 +- components/esp_lvgl_port/CHANGELOG.md | 33 + components/esp_lvgl_port/CMakeLists.txt | 60 +- components/esp_lvgl_port/README.md | 32 + components/esp_lvgl_port/esp_lvgl_port.c | 1355 ----------------- .../examples/i2c_oled/CMakeLists.txt | 4 + .../esp_lvgl_port/examples/i2c_oled/README.md | 67 + .../examples/i2c_oled/main/CMakeLists.txt | 2 + .../examples/i2c_oled/main/Kconfig.projbuild | 35 + .../i2c_oled/main/i2c_oled_example_main.c | 144 ++ .../examples/i2c_oled/main/idf_component.yml | 7 + .../examples/i2c_oled/main/lvgl_demo_ui.c | 18 + .../examples/i2c_oled/sdkconfig.defaults | 5 + .../touchscreen/main/idf_component.yml | 2 +- .../examples/touchscreen/main/main.c | 17 +- components/esp_lvgl_port/idf_component.yml | 4 +- .../images/{ => lvgl8}/img_cursor.c | 0 .../esp_lvgl_port/images/lvgl9/img_cursor.c | 66 + .../esp_lvgl_port/include/esp_lvgl_port.h | 232 +-- .../include/esp_lvgl_port_button.h | 64 + .../include/esp_lvgl_port_compatibility.h | 31 + .../include/esp_lvgl_port_disp.h | 86 ++ .../include/esp_lvgl_port_knob.h | 67 + .../include/esp_lvgl_port_touch.h | 62 + .../include/esp_lvgl_port_usbhid.h | 80 + .../esp_lvgl_port/src/lvgl8/esp_lvgl_port.c | 198 +++ .../src/lvgl8/esp_lvgl_port_button.c | 197 +++ .../src/lvgl8/esp_lvgl_port_disp.c | 379 +++++ .../src/lvgl8/esp_lvgl_port_knob.c | 161 ++ .../src/lvgl8/esp_lvgl_port_touch.c | 101 ++ .../src/lvgl8/esp_lvgl_port_usbhid.c | 465 ++++++ .../esp_lvgl_port/src/lvgl9/esp_lvgl_port.c | 198 +++ .../src/lvgl9/esp_lvgl_port_button.c | 203 +++ .../src/lvgl9/esp_lvgl_port_disp.c | 322 ++++ .../src/lvgl9/esp_lvgl_port_knob.c | 167 ++ .../src/lvgl9/esp_lvgl_port_touch.c | 107 ++ .../src/lvgl9/esp_lvgl_port_usbhid.c | 482 ++++++ test_app/CMakeLists.txt | 7 +- 66 files changed, 3924 insertions(+), 1635 deletions(-) create mode 100644 components/esp_lvgl_port/CHANGELOG.md delete mode 100644 components/esp_lvgl_port/esp_lvgl_port.c create mode 100644 components/esp_lvgl_port/examples/i2c_oled/CMakeLists.txt create mode 100644 components/esp_lvgl_port/examples/i2c_oled/README.md create mode 100644 components/esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt create mode 100644 components/esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild create mode 100644 components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c create mode 100644 components/esp_lvgl_port/examples/i2c_oled/main/idf_component.yml create mode 100644 components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c create mode 100644 components/esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults rename components/esp_lvgl_port/images/{ => lvgl8}/img_cursor.c (100%) create mode 100644 components/esp_lvgl_port/images/lvgl9/img_cursor.c create mode 100644 components/esp_lvgl_port/include/esp_lvgl_port_button.h create mode 100644 components/esp_lvgl_port/include/esp_lvgl_port_compatibility.h create mode 100644 components/esp_lvgl_port/include/esp_lvgl_port_disp.h create mode 100644 components/esp_lvgl_port/include/esp_lvgl_port_knob.h create mode 100644 components/esp_lvgl_port/include/esp_lvgl_port_touch.h create mode 100644 components/esp_lvgl_port/include/esp_lvgl_port_usbhid.h create mode 100644 components/esp_lvgl_port/src/lvgl8/esp_lvgl_port.c create mode 100644 components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c create mode 100644 components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c create mode 100644 components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c create mode 100644 components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c create mode 100644 components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c create mode 100644 components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c create mode 100644 components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c create mode 100644 components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c create mode 100644 components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c create mode 100644 components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c create mode 100644 components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c diff --git a/.build-test-rules.yml b/.build-test-rules.yml index 99591e44..ddd0e843 100644 --- a/.build-test-rules.yml +++ b/.build-test-rules.yml @@ -4,3 +4,12 @@ examples: reason: Example depends on BSP, which is supported only for IDF >= 5.0 - if: IDF_VERSION_MAJOR < 5 and IDF_TARGET in ["esp32c2", "esp32p4", "esp32c5", "esp32c6"] reason: Example depends on target, which is supported only for IDF >= 5.0 + +components/lcd/esp_lcd_gc9503/test_apps: + disable: + - if: IDF_VERSION_MAJOR < 5 + reason: Component is supported only for IDF >= 5.0 +components/lcd/esp_lcd_ssd1681: + disable: + - if: IDF_VERSION_MAJOR < 5 + reason: Component is supported only for IDF >= 5.0 diff --git a/.github/workflows/upload_component_noglib.yml b/.github/workflows/upload_component_noglib.yml index 85a1b82c..5a5fd7eb 100644 --- a/.github/workflows/upload_component_noglib.yml +++ b/.github/workflows/upload_component_noglib.yml @@ -29,3 +29,4 @@ jobs: namespace: "espressif" api_token: ${{ secrets.IDF_COMPONENT_API_TOKEN }} dry_run: ${{ github.ref_name != 'master' || github.repository_owner != 'espressif' }} + commit_sha: '' diff --git a/.idf_build_apps.toml b/.idf_build_apps.toml index 2cb836fd..d8902f66 100644 --- a/.idf_build_apps.toml +++ b/.idf_build_apps.toml @@ -1,6 +1,11 @@ target = "all" -paths = "examples" +paths = "." recursive = true +exclude = [ + "test_app", + "SquareLine", + "bsp" +] manifest_file = ".build-test-rules.yml" check_warnings = true ignore_warning_file = ".ignore_build_warnings.txt" diff --git a/bsp/esp-box-3/README.md b/bsp/esp-box-3/README.md index c902c1f2..5095e4e4 100644 --- a/bsp/esp-box-3/README.md +++ b/bsp/esp-box-3/README.md @@ -21,7 +21,7 @@ ESP32-S3-BOX-3 also uses a Type-C USB connector that provides 5 V of power input | [espressif/esp_lcd_ili9341](https://components.espressif.com/components/espressif/esp_lcd_ili9341) | ^1 | | [espressif/esp_lcd_touch_gt911](https://components.espressif.com/components/espressif/esp_lcd_touch_gt911) | ^1 | |[espressif/esp_lcd_touch_tt21100](https://components.espressif.com/components/espressif/esp_lcd_touch_tt21100)| ^1 | -| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^1 | +| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 | | [espressif/icm42670](https://components.espressif.com/components/espressif/icm42670) | ^1 | | idf |>=4.4.5| | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp-box-3/idf_component.yml b/bsp/esp-box-3/idf_component.yml index e3b4e5d5..82c6c8f5 100644 --- a/bsp/esp-box-3/idf_component.yml +++ b/bsp/esp-box-3/idf_component.yml @@ -17,8 +17,9 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" esp_codec_dev: version: "^1" diff --git a/bsp/esp-box-lite/README.md b/bsp/esp-box-lite/README.md index 94e922a0..f7a86960 100644 --- a/bsp/esp-box-lite/README.md +++ b/bsp/esp-box-lite/README.md @@ -18,7 +18,7 @@ ESP32-S3-BOX-Lite also uses a Type-C USB connector that provides 5 V of power in |----------------------------------------------------------------------------------------------|-------| | [espressif/button](https://components.espressif.com/components/espressif/button) | >=2.4 | |[espressif/esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev)| ^1.0.3| -|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^1 | +|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^2 | | idf |>=4.4.5| | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp-box-lite/idf_component.yml b/bsp/esp-box-lite/idf_component.yml index bb8b287e..dbdecf85 100644 --- a/bsp/esp-box-lite/idf_component.yml +++ b/bsp/esp-box-lite/idf_component.yml @@ -17,8 +17,9 @@ dependencies: public: true espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" esp_codec_dev: version: "^1.0.3" diff --git a/bsp/esp-box/README.md b/bsp/esp-box/README.md index f25d0b71..95dcb323 100644 --- a/bsp/esp-box/README.md +++ b/bsp/esp-box/README.md @@ -19,7 +19,7 @@ ESP32-S3-BOX also uses a Type-C USB connector that provides 5 V of power input, | [espressif/button](https://components.espressif.com/components/espressif/button) | >=2.5 | | [espressif/esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev) | ^1.0.3| |[espressif/esp_lcd_touch_tt21100](https://components.espressif.com/components/espressif/esp_lcd_touch_tt21100)| ^1 | -| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^1 | +| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 | | [espressif/icm42670](https://components.espressif.com/components/espressif/icm42670) | ^1 | | idf |>=4.4.5| | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp-box/idf_component.yml b/bsp/esp-box/idf_component.yml index 2a3e2b2e..5a2fade2 100644 --- a/bsp/esp-box/idf_component.yml +++ b/bsp/esp-box/idf_component.yml @@ -15,8 +15,9 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" esp_codec_dev: version: "^1.0.3" diff --git a/bsp/esp32_azure_iot_kit/README.md b/bsp/esp32_azure_iot_kit/README.md index fefe2f9f..276bc800 100644 --- a/bsp/esp32_azure_iot_kit/README.md +++ b/bsp/esp32_azure_iot_kit/README.md @@ -18,7 +18,7 @@ Please refer to specific README.md files in in examples folder. |----------------------------------------------------------------------------------------------|----------| | [espressif/bh1750](https://components.espressif.com/components/espressif/bh1750) | ^1.0.0 | | [espressif/button](https://components.espressif.com/components/espressif/button) |>=2.5,<4.0| -|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^1 | +|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^2 | | [espressif/fbm320](https://components.espressif.com/components/espressif/fbm320) | ^1.0.0 | | [espressif/hts221](https://components.espressif.com/components/espressif/hts221) | ^1.1.1 | | [espressif/mag3110](https://components.espressif.com/components/espressif/mag3110) | ^1.0.0 | diff --git a/bsp/esp32_azure_iot_kit/idf_component.yml b/bsp/esp32_azure_iot_kit/idf_component.yml index 2b7e362d..17b098f8 100644 --- a/bsp/esp32_azure_iot_kit/idf_component.yml +++ b/bsp/esp32_azure_iot_kit/idf_component.yml @@ -13,8 +13,9 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" button: version: ">=2.5,<4.0" diff --git a/bsp/esp32_c3_lcdkit/README.md b/bsp/esp32_c3_lcdkit/README.md index 2ba758c5..4eff6acc 100644 --- a/bsp/esp32_c3_lcdkit/README.md +++ b/bsp/esp32_c3_lcdkit/README.md @@ -19,7 +19,7 @@ ESP32-C3-LCDkit also uses a Type-C USB connector that provides 5 V of power inpu | [espressif/button](https://components.espressif.com/components/espressif/button) |>=2,<4.0| | [espressif/esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev) | ^1 | |[espressif/esp_lcd_gc9a01](https://components.espressif.com/components/espressif/esp_lcd_gc9a01)| ^1 | -| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^1 | +| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 | | [espressif/knob](https://components.espressif.com/components/espressif/knob) | ^0.1.3 | | [espressif/led_strip](https://components.espressif.com/components/espressif/led_strip) | ^2 | | idf | >=5.0.0| diff --git a/bsp/esp32_c3_lcdkit/idf_component.yml b/bsp/esp32_c3_lcdkit/idf_component.yml index 1ba7027e..efd9e434 100644 --- a/bsp/esp32_c3_lcdkit/idf_component.yml +++ b/bsp/esp32_c3_lcdkit/idf_component.yml @@ -14,7 +14,8 @@ dependencies: espressif/esp_lvgl_port: public: true - version: ^1 + version: "^2" + override_path: "../../components/esp_lvgl_port" led_strip: version: "^2" diff --git a/bsp/esp32_p4_function_ev_board/README.md b/bsp/esp32_p4_function_ev_board/README.md index f437bc7d..aeb908e0 100644 --- a/bsp/esp32_p4_function_ev_board/README.md +++ b/bsp/esp32_p4_function_ev_board/README.md @@ -6,11 +6,11 @@ ESP32-P4 Function EV Board is internal Espressif board for testing features on E ### Dependencies -| component | version | -|----------------------------------------------------------------------------------------------------------|---------------| -| [espressif/esp_lcd_ili9881c](https://components.espressif.com/components/espressif/esp_lcd_ili9881c) | >=0.2.0 | -|[espressif/esp_lcd_touch_gt911](https://components.espressif.com/components/espressif/esp_lcd_touch_gt911)| ^1 | -| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) |fix/lvgl_port_9| -| idf | >=5.2 | -| [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | +| component |version| +|----------------------------------------------------------------------------------------------------------|-------| +| [espressif/esp_lcd_ili9881c](https://components.espressif.com/components/espressif/esp_lcd_ili9881c) |>=0.2.0| +|[espressif/esp_lcd_touch_gt911](https://components.espressif.com/components/espressif/esp_lcd_touch_gt911)| ^1 | +| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 | +| idf | >=5.2 | +| [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp32_p4_function_ev_board/esp32_p4_function_ev_board.c b/bsp/esp32_p4_function_ev_board/esp32_p4_function_ev_board.c index ce67b5d1..355c30ad 100644 --- a/bsp/esp32_p4_function_ev_board/esp32_p4_function_ev_board.c +++ b/bsp/esp32_p4_function_ev_board/esp32_p4_function_ev_board.c @@ -29,6 +29,7 @@ static const char *TAG = "ESP32_P4_EV"; static lv_indev_t *disp_indev = NULL; #endif // (BSP_CONFIG_NO_GRAPHIC_LIB == 0) +sdmmc_card_t *bsp_sdcard = NULL; // Global uSD card handler static esp_lcd_touch_handle_t tp; // LCD touch handle static bool i2c_initialized = false; static TaskHandle_t usb_host_task; // USB Host Library task @@ -72,10 +73,11 @@ esp_err_t bsp_sdcard_mount(void) .format_if_mount_failed = false, #endif .max_files = 5, - .allocation_unit_size = 16 * 1024 + .allocation_unit_size = 64 * 1024 }; - const sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + sdmmc_host_t host = SDMMC_HOST_DEFAULT(); + host.max_freq_khz = SDMMC_FREQ_HIGHSPEED; const sdmmc_slot_config_t slot_config = { .clk = BSP_SD_CLK, .cmd = BSP_SD_CMD, diff --git a/bsp/esp32_p4_function_ev_board/idf_component.yml b/bsp/esp32_p4_function_ev_board/idf_component.yml index 1399b2f9..eed65801 100644 --- a/bsp/esp32_p4_function_ev_board/idf_component.yml +++ b/bsp/esp32_p4_function_ev_board/idf_component.yml @@ -15,7 +15,6 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: fix/lvgl_port_9 + version: "^2" public: true - path: "components/esp_lvgl_port/" - git: https://github.com/espressif/esp-bsp.git + override_path: "../../components/esp_lvgl_port" diff --git a/bsp/esp32_s2_kaluga_kit/README.md b/bsp/esp32_s2_kaluga_kit/README.md index a6da6e8e..52d0e821 100644 --- a/bsp/esp32_s2_kaluga_kit/README.md +++ b/bsp/esp32_s2_kaluga_kit/README.md @@ -28,7 +28,7 @@ Please refer to README.md in examples folder. | [espressif/button](https://components.espressif.com/components/espressif/button) |>=2.5,<4.0| | [espressif/esp32-camera](https://components.espressif.com/components/espressif/esp32-camera) | ^2.0.2 | |[espressif/esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev)| ^1.0.3 | -|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^1 | +|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^2 | | [espressif/led_strip](https://components.espressif.com/components/espressif/led_strip) | ^2.5 | | idf | >=4.4.5 | | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp32_s2_kaluga_kit/idf_component.yml b/bsp/esp32_s2_kaluga_kit/idf_component.yml index 16e20f9b..2cc4bea9 100644 --- a/bsp/esp32_s2_kaluga_kit/idf_component.yml +++ b/bsp/esp32_s2_kaluga_kit/idf_component.yml @@ -13,8 +13,9 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: ^1 + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" button: version: ">=2.5,<4.0" diff --git a/bsp/esp32_s2_kaluga_kit/include/bsp/esp32_s2_kaluga_kit.h b/bsp/esp32_s2_kaluga_kit/include/bsp/esp32_s2_kaluga_kit.h index ca60043f..cea9485e 100644 --- a/bsp/esp32_s2_kaluga_kit/include/bsp/esp32_s2_kaluga_kit.h +++ b/bsp/esp32_s2_kaluga_kit/include/bsp/esp32_s2_kaluga_kit.h @@ -298,7 +298,7 @@ esp_err_t bsp_spiffs_unmount(void); #define BSP_LCD_PIXEL_CLOCK_HZ (40 * 1000 * 1000) #define BSP_LCD_SPI_NUM (SPI3_HOST) -#define BSP_LCD_DRAW_BUFF_SIZE (BSP_LCD_H_RES * BSP_LCD_V_RES) +#define BSP_LCD_DRAW_BUFF_SIZE (BSP_LCD_H_RES * 20) #define BSP_LCD_DRAW_BUFF_DOUBLE (0) /** diff --git a/bsp/esp32_s3_eye/README.md b/bsp/esp32_s3_eye/README.md index 5124726b..ea0391e2 100644 --- a/bsp/esp32_s3_eye/README.md +++ b/bsp/esp32_s3_eye/README.md @@ -20,7 +20,7 @@ The ESP32-S3-EYE board consists of two parts: the main board (ESP32-S3-EYE-MB) t | [espressif/button](https://components.espressif.com/components/espressif/button) |>=2.5,<4.0| | [espressif/esp32-camera](https://components.espressif.com/components/espressif/esp32-camera) | ^2.0.2 | |[espressif/esp_codec_dev](https://components.espressif.com/components/espressif/esp_codec_dev)| ^1 | -|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^1 | +|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^2 | | idf | >=4.4.5 | | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp32_s3_eye/idf_component.yml b/bsp/esp32_s3_eye/idf_component.yml index a0d38aff..d622bfc6 100644 --- a/bsp/esp32_s3_eye/idf_component.yml +++ b/bsp/esp32_s3_eye/idf_component.yml @@ -13,8 +13,9 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" esp32-camera: version: "^2.0.2" diff --git a/bsp/esp32_s3_korvo_2/README.md b/bsp/esp32_s3_korvo_2/README.md index 61ed6e49..86648805 100644 --- a/bsp/esp32_s3_korvo_2/README.md +++ b/bsp/esp32_s3_korvo_2/README.md @@ -18,7 +18,7 @@ The ESP32-S3-Korvo-2 is a multimedia development board based on the ESP32-S3 chi |[espressif/esp_io_expander_tca9554](https://components.espressif.com/components/espressif/esp_io_expander_tca9554)| ^1 | | [espressif/esp_lcd_ili9341](https://components.espressif.com/components/espressif/esp_lcd_ili9341) | ^1 | | [espressif/esp_lcd_touch_tt21100](https://components.espressif.com/components/espressif/esp_lcd_touch_tt21100) | ^1 | -| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^1 | +| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 | | idf | >=4.4.5 | | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp32_s3_korvo_2/idf_component.yml b/bsp/esp32_s3_korvo_2/idf_component.yml index 38765614..bb687c09 100644 --- a/bsp/esp32_s3_korvo_2/idf_component.yml +++ b/bsp/esp32_s3_korvo_2/idf_component.yml @@ -31,5 +31,6 @@ dependencies: public: true espressif/esp_lvgl_port: - version: ^1 + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" diff --git a/bsp/esp32_s3_usb_otg/README.md b/bsp/esp32_s3_usb_otg/README.md index eb0842c2..a7ef4521 100644 --- a/bsp/esp32_s3_usb_otg/README.md +++ b/bsp/esp32_s3_usb_otg/README.md @@ -20,7 +20,7 @@ ESP32-S3-USB-OTG is a development board that focuses on USB-OTG function verific | component | version | |----------------------------------------------------------------------------------------------|----------| | [espressif/button](https://components.espressif.com/components/espressif/button) |>=2.5,<4.0| -|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^1 | +|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^2 | | idf | >=4.4 | | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp32_s3_usb_otg/idf_component.yml b/bsp/esp32_s3_usb_otg/idf_component.yml index 1baf3b43..8d332ab8 100644 --- a/bsp/esp32_s3_usb_otg/idf_component.yml +++ b/bsp/esp32_s3_usb_otg/idf_component.yml @@ -17,5 +17,6 @@ dependencies: public: true espressif/esp_lvgl_port: - version: ^1 + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" diff --git a/bsp/esp_bsp_generic/README.md b/bsp/esp_bsp_generic/README.md index bb37ac00..67f03236 100644 --- a/bsp/esp_bsp_generic/README.md +++ b/bsp/esp_bsp_generic/README.md @@ -228,7 +228,7 @@ Example code: | [espressif/esp_lcd_touch_gt1151](https://components.espressif.com/components/espressif/esp_lcd_touch_gt1151) | ^1 | | [espressif/esp_lcd_touch_gt911](https://components.espressif.com/components/espressif/esp_lcd_touch_gt911) | ^1 | |[espressif/esp_lcd_touch_tt21100](https://components.espressif.com/components/espressif/esp_lcd_touch_tt21100)| ^1 | -| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^1 | +| [espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port) | ^2 | | [espressif/led_indicator](https://components.espressif.com/components/espressif/led_indicator) | ^0.9 | | idf | >=4.4.2 | | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp_bsp_generic/idf_component.yml b/bsp/esp_bsp_generic/idf_component.yml index 0f3d189c..18866b34 100644 --- a/bsp/esp_bsp_generic/idf_component.yml +++ b/bsp/esp_bsp_generic/idf_component.yml @@ -26,8 +26,9 @@ dependencies: public: true espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" examples: - path: ../../examples/generic_button_led diff --git a/bsp/esp_wrover_kit/README.md b/bsp/esp_wrover_kit/README.md index 578a5333..dfe12e40 100644 --- a/bsp/esp_wrover_kit/README.md +++ b/bsp/esp_wrover_kit/README.md @@ -23,7 +23,7 @@ Most of the ESP32 I/O pins are broken out to the board’s pin headers for easy | component | version | |----------------------------------------------------------------------------------------------|----------| | [espressif/button](https://components.espressif.com/components/espressif/button) |>=2.5,<4.0| -|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^1 | +|[espressif/esp_lvgl_port](https://components.espressif.com/components/espressif/esp_lvgl_port)| ^2 | | idf | >=4.4.5 | | [lvgl/lvgl](https://components.espressif.com/components/lvgl/lvgl) | ^8 | diff --git a/bsp/esp_wrover_kit/idf_component.yml b/bsp/esp_wrover_kit/idf_component.yml index efba6db0..aa3f3ab4 100644 --- a/bsp/esp_wrover_kit/idf_component.yml +++ b/bsp/esp_wrover_kit/idf_component.yml @@ -13,8 +13,9 @@ dependencies: lvgl/lvgl: "^8" espressif/esp_lvgl_port: - version: "^1" + version: "^2" public: true + override_path: "../../components/esp_lvgl_port" button: version: ">=2.5,<4.0" diff --git a/components/esp_lvgl_port/CHANGELOG.md b/components/esp_lvgl_port/CHANGELOG.md new file mode 100644 index 00000000..8a5c5836 --- /dev/null +++ b/components/esp_lvgl_port/CHANGELOG.md @@ -0,0 +1,33 @@ +# Changelog + +## 2.0.0 + +### Features + +- Divided into files per feature +- Added support for LVGL9 +- Added support for MIPI-DSI display + +## 1.4.0 + +### Features + +- Added support for USB HID mouse/keyboard as an input device + +## 1.3.0 + +### Features + +- Added low power interface + +## 1.2.0 + +### Features + +- Added support for encoder (knob) as an input device + +## 1.1.0 + +### Features + +- Added support for navigation buttons as an input device diff --git a/components/esp_lvgl_port/CMakeLists.txt b/components/esp_lvgl_port/CMakeLists.txt index 9a9edb3f..998deaf0 100644 --- a/components/esp_lvgl_port/CMakeLists.txt +++ b/components/esp_lvgl_port/CMakeLists.txt @@ -1,29 +1,69 @@ -file(GLOB_RECURSE IMAGE_SOURCES images/*.c) +include($ENV{IDF_PATH}/tools/cmake/version.cmake) # $ENV{IDF_VERSION} was added after v4.3... -idf_component_register(SRCS "esp_lvgl_port.c" ${IMAGE_SOURCES} INCLUDE_DIRS "include" REQUIRES "esp_lcd" PRIV_REQUIRES "esp_timer") +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "4.4") + return() +endif() + +#Get LVGL version +idf_component_get_property(lvgl_ver lvgl__lvgl COMPONENT_VERSION) +if(lvgl_ver EQUAL "") + idf_component_get_property(lvgl_ver lvgl COMPONENT_VERSION) +endif() +message(STATUS "LVGL version: ${lvgl_ver}") + +#Select folder by LVGL version +if(lvgl_ver VERSION_LESS "9.0.0") + message(VERBOSE "Compiling esp_lvgl_port for LVGL8") + set(PORT_FOLDER "lvgl8") +else() + message(VERBOSE "Compiling esp_lvgl_port for LVGL9") + set(PORT_FOLDER "lvgl9") +endif() + +set(PORT_PATH "src/${PORT_FOLDER}") + +idf_component_register(SRCS "${PORT_PATH}/esp_lvgl_port.c" "${PORT_PATH}/esp_lvgl_port_disp.c" INCLUDE_DIRS "include" REQUIRES "esp_lcd" PRIV_REQUIRES "esp_timer") + +set(ADD_SRCS "") +set(ADD_LIBS "") idf_build_get_property(build_components BUILD_COMPONENTS) if("espressif__button" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::espressif__button) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_button.c") + list(APPEND ADD_LIBS idf::espressif__button) endif() if("button" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::button) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_button.c") + list(APPEND ADD_LIBS idf::button) endif() if("espressif__esp_lcd_touch" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::espressif__esp_lcd_touch) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_touch.c") + list(APPEND ADD_LIBS idf::espressif__esp_lcd_touch) endif() if("esp_lcd_touch" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::esp_lcd_touch) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_touch.c") + list(APPEND ADD_LIBS idf::esp_lcd_touch) endif() if("espressif__knob" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::espressif__knob) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_knob.c") + list(APPEND ADD_LIBS idf::espressif__knob) endif() if("knob" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::knob) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_knob.c") + list(APPEND ADD_LIBS idf::knob) endif() if("espressif__usb_host_hid" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::espressif__usb_host_hid) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_usbhid.c" "images/${PORT_FOLDER}/img_cursor.c") + list(APPEND ADD_LIBS idf::espressif__usb_host_hid) endif() if("usb_host_hid" IN_LIST build_components) - target_link_libraries(${COMPONENT_LIB} PRIVATE idf::usb_host_hid) + list(APPEND ADD_SRCS "${PORT_PATH}/esp_lvgl_port_usbhid.c" "images/${PORT_FOLDER}/img_cursor.c") + list(APPEND ADD_LIBS idf::usb_host_hid) +endif() + +if(ADD_SRCS) + target_sources(${COMPONENT_LIB} PRIVATE ${ADD_SRCS}) +endif() +if(ADD_LIBS) + target_link_libraries(${COMPONENT_LIB} PRIVATE ${ADD_LIBS}) endif() diff --git a/components/esp_lvgl_port/README.md b/components/esp_lvgl_port/README.md index facef344..fb6f5a11 100644 --- a/components/esp_lvgl_port/README.md +++ b/components/esp_lvgl_port/README.md @@ -12,6 +12,24 @@ This component helps with using LVGL with Espressif's LCD and touch drivers. It * Add/remove touch input (using [`esp_lcd_touch`](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch)) * Add/remove navigation buttons input (using [`button`](https://github.com/espressif/esp-iot-solution/tree/master/components/button)) * Add/remove encoder input (using [`knob`](https://github.com/espressif/esp-iot-solution/tree/master/components/knob)) +* Add/remove USB HID mouse/keyboard input (using [`usb_host_hid`](https://components.espressif.com/components/espressif/usb_host_hid)) + +## LVGL Version + +> [!WARNING] +> LVGL9 is not stable and it not recommended to use it. + +This component supports **LVGL8** and **LVGL9**. By default, it selects the latest LVGL version. If you want to use a specific version (e.g. latest LVGL8), you can easily put into `idf_component.yml` in your project like this: + +``` + lvgl/lvgl: + version: "^8" + public: true +``` + +### LVGL Version Compatibility + +This component is fully compatible with LVGL version 9. All types and functions are used from LVGL9. Some LVGL9 types are not supported in LVGL8 and there are retyping in [`esp_lvgl_port_compatibility.h`](include/esp_lvgl_port_compatibility.h) header file. **Please, be aware, that some draw and object functions are not compatible between LVGL8 and LVGL9.** ## Usage @@ -45,6 +63,7 @@ Add an LCD screen to the LVGL. It can be called multiple times for adding multip .hres = DISP_WIDTH, .vres = DISP_HEIGHT, .monochrome = false, + .mipi_dsi = false, /* Rotation values must be same as used in esp_lcd for initial settings of the screen */ .rotation = { .swap_xy = false, @@ -53,6 +72,7 @@ Add an LCD screen to the LVGL. It can be called multiple times for adding multip }, .flags = { .buff_dma = true, + .swap_bytes = false, } }; disp_handle = lvgl_port_add_disp(&disp_cfg); @@ -219,6 +239,7 @@ Every LVGL calls must be protected with these lock/unlock commands: ``` ### Rotating screen + LVGL port supports rotation of the display. You can select whether you'd like software rotation or hardware rotation. Software rotation requires no additional logic in your `flush_cb` callback. @@ -233,6 +254,7 @@ Rotation mode can be selected in the `lvgl_port_display_cfg_t` structure. } ``` Display rotation can be changed at runtime. + ``` c lv_disp_set_rotation(disp_handle, LV_DISP_ROT_90); ``` @@ -258,3 +280,13 @@ If the SRAM is insufficient, you can use the PSRAM as a canvas and use a small t ## Performance Key feature of every graphical application is performance. Recommended settings for improving LCD performance is described in a separate document [here](docs/performance.md). + +### Performance monitor + +For show performance monitor in LVGL9, please add these lines to sdkconfig.defaults and rebuild all. + +``` +CONFIG_LV_USE_OBSERVER=y +CONFIG_LV_USE_SYSMON=y +CONFIG_LV_USE_PERF_MONITOR=y +``` diff --git a/components/esp_lvgl_port/esp_lvgl_port.c b/components/esp_lvgl_port/esp_lvgl_port.c deleted file mode 100644 index e73e83f1..00000000 --- a/components/esp_lvgl_port/esp_lvgl_port.c +++ /dev/null @@ -1,1355 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#include "esp_system.h" -#include "esp_log.h" -#include "esp_err.h" -#include "esp_check.h" -#include "esp_timer.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/semphr.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_ops.h" -#include "esp_lvgl_port.h" - -#include "lvgl.h" - - -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -#include "esp_lcd_touch.h" -#endif - -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -#include "usb/hid_host.h" -#include "usb/hid_usage_keyboard.h" -#include "usb/hid_usage_mouse.h" -/* LVGL image of cursor */ -LV_IMG_DECLARE(img_cursor) -#endif - -#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0)) -#define LVGL_PORT_HANDLE_FLUSH_READY 0 -#else -#define LVGL_PORT_HANDLE_FLUSH_READY 1 -#endif - -static const char *TAG = "LVGL"; - -/******************************************************************************* -* Types definitions -*******************************************************************************/ - -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -typedef struct { - QueueHandle_t queue; /* USB HID queue */ - TaskHandle_t task; /* USB HID task */ - bool running; /* USB HID task running */ - struct { - lv_indev_drv_t drv; /* LVGL mouse input device driver */ - uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */ - int16_t x; /* Mouse X coordinate */ - int16_t y; /* Mouse Y coordinate */ - bool left_button; /* Mouse left button state */ - } mouse; - struct { - lv_indev_drv_t drv; /* LVGL keyboard input device driver */ - uint32_t last_key; - bool pressed; - } kb; -} lvgl_port_usb_hid_ctx_t; - -typedef struct { - hid_host_device_handle_t hid_device_handle; - hid_host_driver_event_t event; - void *arg; -} lvgl_port_usb_hid_event_t; - -#endif - -typedef struct lvgl_port_ctx_s { - SemaphoreHandle_t lvgl_mux; - esp_timer_handle_t tick_timer; - bool running; - int task_max_sleep_ms; -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT - lvgl_port_usb_hid_ctx_t hid_ctx; -#endif -} lvgl_port_ctx_t; - -typedef struct { - esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */ - esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */ - lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */ - lv_disp_drv_t disp_drv; /* LVGL display driver */ - lv_color_t *trans_buf; /* Buffer send to driver */ - uint32_t trans_size; /* Maximum size for one transport */ - SemaphoreHandle_t trans_sem; /* Idle transfer mutex */ -} lvgl_port_display_ctx_t; - -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -typedef struct { - esp_lcd_touch_handle_t handle; /* LCD touch IO handle */ - lv_indev_drv_t indev_drv; /* LVGL input device driver */ -} lvgl_port_touch_ctx_t; -#endif - -#ifdef ESP_LVGL_PORT_KNOB_COMPONENT -typedef struct { - knob_handle_t knob_handle; /* Encoder knob handlers */ - button_handle_t btn_handle; /* Encoder button handlers */ - lv_indev_drv_t indev_drv; /* LVGL input device driver */ - bool btn_enter; /* Encoder button enter state */ -} lvgl_port_encoder_ctx_t; -#endif - -#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT - -typedef enum { - LVGL_PORT_NAV_BTN_PREV, - LVGL_PORT_NAV_BTN_NEXT, - LVGL_PORT_NAV_BTN_ENTER, - LVGL_PORT_NAV_BTN_CNT, -} lvgl_port_nav_btns_t; - -typedef struct { - button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */ - lv_indev_drv_t indev_drv; /* LVGL input device driver */ - bool btn_prev; /* Button prev state */ - bool btn_next; /* Button next state */ - bool btn_enter; /* Button enter state */ -} lvgl_port_nav_btns_ctx_t; -#endif - -/******************************************************************************* -* Local variables -*******************************************************************************/ -static lvgl_port_ctx_t lvgl_port_ctx; -static int lvgl_port_timer_period_ms = 5; - -/******************************************************************************* -* Function definitions -*******************************************************************************/ -static void lvgl_port_task(void *arg); -static esp_err_t lvgl_port_tick_init(void); -static void lvgl_port_task_deinit(void); - -// LVGL callbacks -#if LVGL_PORT_HANDLE_FLUSH_READY -static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); -#endif -static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map); -static void lvgl_port_update_callback(lv_disp_drv_t *drv); -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); -#endif -#ifdef ESP_LVGL_PORT_KNOB_COMPONENT -static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); -static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2); -static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2); -#endif -#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT -static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); -static void lvgl_port_btn_down_handler(void *arg, void *arg2); -static void lvgl_port_btn_up_handler(void *arg, void *arg2); -#endif -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void); -static void lvgl_port_usb_hid_task(void *arg); -static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); -static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); -static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg); -#endif -static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa); -/******************************************************************************* -* Public API functions -*******************************************************************************/ - -esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg) -{ - esp_err_t ret = ESP_OK; - ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); - ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1)); - - memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); - - - /* LVGL init */ - lv_init(); - /* Tick init */ - lvgl_port_timer_period_ms = cfg->timer_period_ms; - ESP_RETURN_ON_ERROR(lvgl_port_tick_init(), TAG, ""); - /* Create task */ - lvgl_port_ctx.task_max_sleep_ms = cfg->task_max_sleep_ms; - if (lvgl_port_ctx.task_max_sleep_ms == 0) { - lvgl_port_ctx.task_max_sleep_ms = 500; - } - lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex(); - ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!"); - - BaseType_t res; - if (cfg->task_affinity < 0) { - res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL); - } else { - res = xTaskCreatePinnedToCore(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL, cfg->task_affinity); - } - ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!"); - -err: - if (ret != ESP_OK) { - lvgl_port_deinit(); - } - - return ret; -} - -esp_err_t lvgl_port_resume(void) -{ - esp_err_t ret = ESP_ERR_INVALID_STATE; - - if (lvgl_port_ctx.tick_timer != NULL) { - lv_timer_enable(true); - ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_timer_period_ms * 1000); - } - - return ret; -} - -esp_err_t lvgl_port_stop(void) -{ - esp_err_t ret = ESP_ERR_INVALID_STATE; - - if (lvgl_port_ctx.tick_timer != NULL) { - lv_timer_enable(false); - ret = esp_timer_stop(lvgl_port_ctx.tick_timer); - } - - return ret; -} - -esp_err_t lvgl_port_deinit(void) -{ - /* Stop and delete timer */ - if (lvgl_port_ctx.tick_timer != NULL) { - esp_timer_stop(lvgl_port_ctx.tick_timer); - esp_timer_delete(lvgl_port_ctx.tick_timer); - lvgl_port_ctx.tick_timer = NULL; - } - - /* Stop running task */ - if (lvgl_port_ctx.running) { - lvgl_port_ctx.running = false; - } else { - lvgl_port_task_deinit(); - } - - return ESP_OK; -} - -lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg) -{ - esp_err_t ret = ESP_OK; - lv_disp_t *disp = NULL; - lv_color_t *buf1 = NULL; - lv_color_t *buf2 = NULL; - lv_color_t *buf3 = NULL; - SemaphoreHandle_t trans_sem = NULL; - assert(disp_cfg != NULL); - assert(disp_cfg->io_handle != NULL); - assert(disp_cfg->panel_handle != NULL); - assert(disp_cfg->buffer_size > 0); - assert(disp_cfg->hres > 0); - assert(disp_cfg->vres > 0); - - /* Display context */ - lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); - ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); - disp_ctx->io_handle = disp_cfg->io_handle; - disp_ctx->panel_handle = disp_cfg->panel_handle; - disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy; - disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x; - disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y; - disp_ctx->trans_size = disp_cfg->trans_size; - - uint32_t buff_caps = MALLOC_CAP_DEFAULT; - if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram && (0 == disp_cfg->trans_size)) { - ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!"); - } else if (disp_cfg->flags.buff_dma) { - buff_caps = MALLOC_CAP_DMA; - } else if (disp_cfg->flags.buff_spiram) { - buff_caps = MALLOC_CAP_SPIRAM; - } - - /* alloc draw buffers used by LVGL */ - /* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */ - buf1 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color_t), buff_caps); - ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!"); - if (disp_cfg->double_buffer) { - buf2 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color_t), buff_caps); - ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!"); - } - - if (disp_cfg->trans_size) { - buf3 = heap_caps_malloc(disp_cfg->trans_size * sizeof(lv_color_t), MALLOC_CAP_DMA); - ESP_GOTO_ON_FALSE(buf3, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for buffer(transport) allocation!"); - disp_ctx->trans_buf = buf3; - - trans_sem = xSemaphoreCreateCounting(1, 0); - ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore"); - disp_ctx->trans_sem = trans_sem; - } - - lv_disp_draw_buf_t *disp_buf = malloc(sizeof(lv_disp_draw_buf_t)); - ESP_GOTO_ON_FALSE(disp_buf, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL display buffer allocation!"); - - /* initialize LVGL draw buffers */ - lv_disp_draw_buf_init(disp_buf, buf1, buf2, disp_cfg->buffer_size); - - ESP_LOGD(TAG, "Register display driver to LVGL"); - lv_disp_drv_init(&disp_ctx->disp_drv); - disp_ctx->disp_drv.hor_res = disp_cfg->hres; - disp_ctx->disp_drv.ver_res = disp_cfg->vres; - disp_ctx->disp_drv.flush_cb = lvgl_port_flush_callback; - disp_ctx->disp_drv.sw_rotate = disp_cfg->flags.sw_rotate; - if (disp_ctx->disp_drv.sw_rotate == false) { - disp_ctx->disp_drv.drv_update_cb = lvgl_port_update_callback; - } - - disp_ctx->disp_drv.draw_buf = disp_buf; - disp_ctx->disp_drv.user_data = disp_ctx; - -#if LVGL_PORT_HANDLE_FLUSH_READY - /* Register done callback */ - const esp_lcd_panel_io_callbacks_t cbs = { - .on_color_trans_done = lvgl_port_flush_ready_callback, - }; - esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv); -#endif - - /* Monochrome display settings */ - if (disp_cfg->monochrome) { - /* When using monochromatic display, there must be used full bufer! */ - ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == disp_cfg->buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!"); - - disp_ctx->disp_drv.full_refresh = 1; - disp_ctx->disp_drv.set_px_cb = lvgl_port_pix_monochrome_callback; - } - - disp = lv_disp_drv_register(&disp_ctx->disp_drv); - -err: - if (ret != ESP_OK) { - if (buf1) { - free(buf1); - } - if (buf2) { - free(buf2); - } - if (buf3) { - free(buf3); - } - if (trans_sem) { - vSemaphoreDelete(trans_sem); - } - if (disp_ctx) { - free(disp_ctx); - } - } - - return disp; -} - -esp_err_t lvgl_port_remove_disp(lv_disp_t *disp) -{ - assert(disp); - lv_disp_drv_t *disp_drv = disp->driver; - assert(disp_drv); - lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)disp_drv->user_data; - - lv_disp_remove(disp); - - if (disp_drv) { - if (disp_drv->draw_buf && disp_drv->draw_buf->buf1) { - free(disp_drv->draw_buf->buf1); - disp_drv->draw_buf->buf1 = NULL; - } - if (disp_drv->draw_buf && disp_drv->draw_buf->buf2) { - free(disp_drv->draw_buf->buf2); - disp_drv->draw_buf->buf2 = NULL; - } - if (disp_drv->draw_buf) { - free(disp_drv->draw_buf); - disp_drv->draw_buf = NULL; - } - } - - free(disp_ctx); - - return ESP_OK; -} - -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg) -{ - assert(touch_cfg != NULL); - assert(touch_cfg->disp != NULL); - assert(touch_cfg->handle != NULL); - - /* Touch context */ - lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t)); - if (touch_ctx == NULL) { - ESP_LOGE(TAG, "Not enough memory for touch context allocation!"); - return NULL; - } - touch_ctx->handle = touch_cfg->handle; - - /* Register a touchpad input device */ - lv_indev_drv_init(&touch_ctx->indev_drv); - touch_ctx->indev_drv.type = LV_INDEV_TYPE_POINTER; - touch_ctx->indev_drv.disp = touch_cfg->disp; - touch_ctx->indev_drv.read_cb = lvgl_port_touchpad_read; - touch_ctx->indev_drv.user_data = touch_ctx; - return lv_indev_drv_register(&touch_ctx->indev_drv); -} - -esp_err_t lvgl_port_remove_touch(lv_indev_t *touch) -{ - assert(touch); - lv_indev_drv_t *indev_drv = touch->driver; - assert(indev_drv); - lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data; - - /* Remove input device driver */ - lv_indev_delete(touch); - - if (touch_ctx) { - free(touch_ctx); - } - - return ESP_OK; -} -#endif - -#ifdef ESP_LVGL_PORT_KNOB_COMPONENT -lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg) -{ - esp_err_t ret = ESP_OK; - lv_indev_t *indev = NULL; - assert(encoder_cfg != NULL); - assert(encoder_cfg->disp != NULL); - - /* Encoder context */ - lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t)); - if (encoder_ctx == NULL) { - ESP_LOGE(TAG, "Not enough memory for encoder context allocation!"); - return NULL; - } - - /* Encoder_a/b */ - if (encoder_cfg->encoder_a_b != NULL) { - encoder_ctx->knob_handle = iot_knob_create(encoder_cfg->encoder_a_b); - ESP_GOTO_ON_FALSE(encoder_ctx->knob_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for knob create!"); - } - - /* Encoder Enter */ - if (encoder_cfg->encoder_enter != NULL) { - encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter); - ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); - } - - ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, encoder_ctx)); - ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, encoder_ctx)); - - encoder_ctx->btn_enter = false; - - /* Register a encoder input device */ - lv_indev_drv_init(&encoder_ctx->indev_drv); - encoder_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER; - encoder_ctx->indev_drv.disp = encoder_cfg->disp; - encoder_ctx->indev_drv.read_cb = lvgl_port_encoder_read; - encoder_ctx->indev_drv.user_data = encoder_ctx; - indev = lv_indev_drv_register(&encoder_ctx->indev_drv); - -err: - if (ret != ESP_OK) { - if (encoder_ctx->knob_handle != NULL) { - iot_knob_delete(encoder_ctx->knob_handle); - } - - if (encoder_ctx->btn_handle != NULL) { - iot_button_delete(encoder_ctx->btn_handle); - } - - if (encoder_ctx != NULL) { - free(encoder_ctx); - } - } - return indev; -} - -esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder) -{ - assert(encoder); - lv_indev_drv_t *indev_drv = encoder->driver; - assert(indev_drv); - lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data; - - if (encoder_ctx->knob_handle != NULL) { - iot_knob_delete(encoder_ctx->knob_handle); - } - - if (encoder_ctx->btn_handle != NULL) { - iot_button_delete(encoder_ctx->btn_handle); - } - - if (encoder_ctx != NULL) { - free(encoder_ctx); - } - - return ESP_OK; -} -#endif - -#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT -lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg) -{ - esp_err_t ret = ESP_OK; - lv_indev_t *indev = NULL; - assert(buttons_cfg != NULL); - assert(buttons_cfg->disp != NULL); - - /* Touch context */ - lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t)); - if (buttons_ctx == NULL) { - ESP_LOGE(TAG, "Not enough memory for buttons context allocation!"); - return NULL; - } - - /* Previous button */ - if (buttons_cfg->button_prev != NULL) { - buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev); - ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); - } - - /* Next button */ - if (buttons_cfg->button_next != NULL) { - buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next); - ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); - } - - /* Enter button */ - if (buttons_cfg->button_enter != NULL) { - buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter); - ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); - } - - /* Button handlers */ - for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { - ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, buttons_ctx)); - ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx)); - } - - buttons_ctx->btn_prev = false; - buttons_ctx->btn_next = false; - buttons_ctx->btn_enter = false; - - /* Register a touchpad input device */ - lv_indev_drv_init(&buttons_ctx->indev_drv); - buttons_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER; - buttons_ctx->indev_drv.disp = buttons_cfg->disp; - buttons_ctx->indev_drv.read_cb = lvgl_port_navigation_buttons_read; - buttons_ctx->indev_drv.user_data = buttons_ctx; - buttons_ctx->indev_drv.long_press_repeat_time = 300; - indev = lv_indev_drv_register(&buttons_ctx->indev_drv); - -err: - if (ret != ESP_OK) { - for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { - if (buttons_ctx->btn[i] != NULL) { - iot_button_delete(buttons_ctx->btn[i]); - } - } - - if (buttons_ctx != NULL) { - free(buttons_ctx); - } - } - - return indev; -} - -esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons) -{ - assert(buttons); - lv_indev_drv_t *indev_drv = buttons->driver; - assert(indev_drv); - lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data; - - /* Remove input device driver */ - lv_indev_delete(buttons); - - if (buttons_ctx) { - free(buttons_ctx); - } - - return ESP_OK; -} -#endif - -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg) -{ - lv_indev_t *indev; - assert(mouse_cfg); - assert(mouse_cfg->disp); - assert(mouse_cfg->disp->driver); - - /* Initialize USB HID */ - lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); - if (hid_ctx == NULL) { - return NULL; - } - - /* Mouse sensitivity cannot be zero */ - hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity); - /* Default coordinates to screen center */ - hid_ctx->mouse.x = (mouse_cfg->disp->driver->hor_res * hid_ctx->mouse.sensitivity) / 2; - hid_ctx->mouse.y = (mouse_cfg->disp->driver->ver_res * hid_ctx->mouse.sensitivity) / 2; - - /* Register a mouse input device */ - lv_indev_drv_init(&hid_ctx->mouse.drv); - hid_ctx->mouse.drv.type = LV_INDEV_TYPE_POINTER; - hid_ctx->mouse.drv.disp = mouse_cfg->disp; - hid_ctx->mouse.drv.read_cb = lvgl_port_usb_hid_read_mouse; - hid_ctx->mouse.drv.user_data = hid_ctx; - indev = lv_indev_drv_register(&hid_ctx->mouse.drv); - - /* Set image of cursor */ - lv_obj_t *cursor = mouse_cfg->cursor_img; - if (cursor == NULL) { - cursor = lv_img_create(lv_scr_act()); - lv_img_set_src(cursor, &img_cursor); - } - lv_indev_set_cursor(indev, cursor); - - return indev; -} - -lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg) -{ - lv_indev_t *indev; - assert(keyboard_cfg); - assert(keyboard_cfg->disp); - - /* Initialize USB HID */ - lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); - if (hid_ctx == NULL) { - return NULL; - } - - /* Register a keyboard input device */ - lv_indev_drv_init(&hid_ctx->kb.drv); - hid_ctx->kb.drv.type = LV_INDEV_TYPE_KEYPAD; - hid_ctx->kb.drv.disp = keyboard_cfg->disp; - hid_ctx->kb.drv.read_cb = lvgl_port_usb_hid_read_kb; - hid_ctx->kb.drv.user_data = hid_ctx; - indev = lv_indev_drv_register(&hid_ctx->kb.drv); - - return indev; -} - -esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid) -{ - assert(hid); - lv_indev_drv_t *indev_drv = hid->driver; - assert(indev_drv); - lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; - - /* Remove input device driver */ - lv_indev_delete(hid); - - /* If all hid input devices are removed, stop task and clean all */ - if (lvgl_port_ctx.hid_ctx.mouse.drv.disp == NULL && lvgl_port_ctx.hid_ctx.kb.drv.disp) { - hid_ctx->running = false; - } - - return ESP_OK; -} -#endif - -bool lvgl_port_lock(uint32_t timeout_ms) -{ - assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); - - const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); - return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE; -} - -void lvgl_port_unlock(void) -{ - assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); - xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux); -} - -void lvgl_port_flush_ready(lv_disp_t *disp) -{ - assert(disp); - assert(disp->driver); - lv_disp_flush_ready(disp->driver); -} - - - -/******************************************************************************* -* Private functions -*******************************************************************************/ - -static void lvgl_port_task(void *arg) -{ - uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; - - ESP_LOGI(TAG, "Starting LVGL task"); - lvgl_port_ctx.running = true; - while (lvgl_port_ctx.running) { - if (lvgl_port_lock(0)) { - task_delay_ms = lv_timer_handler(); - lvgl_port_unlock(); - } - if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) { - task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; - } else if (task_delay_ms < 1) { - task_delay_ms = 1; - } - vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); - } - - lvgl_port_task_deinit(); - - /* Close task */ - vTaskDelete( NULL ); -} - -static void lvgl_port_task_deinit(void) -{ - if (lvgl_port_ctx.lvgl_mux) { - vSemaphoreDelete(lvgl_port_ctx.lvgl_mux); - } - memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); -#if LV_ENABLE_GC || !LV_MEM_CUSTOM - /* Deinitialize LVGL */ - lv_deinit(); -#endif -} - -#if LVGL_PORT_HANDLE_FLUSH_READY -static bool lvgl_port_flush_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) -{ - BaseType_t taskAwake = pdFALSE; - - lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; - assert(disp_drv != NULL); - lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; - assert(disp_ctx != NULL); - lv_disp_flush_ready(disp_drv); - - if (disp_ctx->trans_size && disp_ctx->trans_sem) { - xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake); - } - - return false; -} -#endif - -static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) -{ - assert(drv != NULL); - lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; - assert(disp_ctx != NULL); - - int x_draw_start; - int x_draw_end; - int y_draw_start; - int y_draw_end; - - int y_start_tmp; - int y_end_tmp; - - int trans_count; - int trans_line; - int max_line; - - const int x_start = area->x1; - const int x_end = area->x2; - const int y_start = area->y1; - const int y_end = area->y2; - const int width = x_end - x_start + 1; - const int height = y_end - y_start + 1; - - lv_color_t *from = color_map; - lv_color_t *to = NULL; - - if (0 == disp_ctx->trans_size) { - esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map); - } else { - y_start_tmp = y_start; - max_line = ((disp_ctx->trans_size / width) > height) ? (height) : (disp_ctx->trans_size / width); - trans_count = height / max_line + (height % max_line ? (1) : (0)); - - for (int i = 0; i < trans_count; i++) { - trans_line = (y_end - y_start_tmp + 1) > max_line ? max_line : (y_end - y_start_tmp + 1); - y_end_tmp = (y_end - y_start_tmp + 1) > max_line ? (y_start_tmp + max_line - 1) : y_end; - - to = disp_ctx->trans_buf; - for (int y = 0; y < trans_line; y++) { - for (int x = 0; x < width; x++) { - *(to + y * (width) + x) = *(from + y * (width) + x); - } - } - x_draw_start = x_start; - x_draw_end = x_end; - y_draw_start = y_start_tmp; - y_draw_end = y_end_tmp; - esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to); - - from += max_line * width; - y_start_tmp += max_line; - xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY); - } - } -} - -static void lvgl_port_update_callback(lv_disp_drv_t *drv) -{ - assert(drv); - lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; - assert(disp_ctx != NULL); - esp_lcd_panel_handle_t panel_handle = disp_ctx->panel_handle; - - /* Solve rotation screen and touch */ - switch (drv->rotated) { - case LV_DISP_ROT_NONE: - /* Rotate LCD display */ - esp_lcd_panel_swap_xy(panel_handle, disp_ctx->rotation.swap_xy); - esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); - break; - case LV_DISP_ROT_90: - /* Rotate LCD display */ - esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy); - if (disp_ctx->rotation.swap_xy) { - esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); - } else { - esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); - } - break; - case LV_DISP_ROT_180: - /* Rotate LCD display */ - esp_lcd_panel_swap_xy(panel_handle, disp_ctx->rotation.swap_xy); - esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); - break; - case LV_DISP_ROT_270: - /* Rotate LCD display */ - esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy); - if (disp_ctx->rotation.swap_xy) { - esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); - } else { - esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); - } - break; - } -} - -static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) -{ - if (drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) { - lv_coord_t tmp_x = x; - x = y; - y = tmp_x; - } - - /* Write to the buffer as required for the display. - * It writes only 1-bit for monochrome displays mapped vertically.*/ - buf += drv->hor_res * (y >> 3) + x; - if (lv_color_to1(color)) { - (*buf) &= ~(1 << (y % 8)); - } else { - (*buf) |= (1 << (y % 8)); - } -} - -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) -{ - assert(indev_drv); - lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data; - assert(touch_ctx->handle); - - uint16_t touchpad_x[1] = {0}; - uint16_t touchpad_y[1] = {0}; - uint8_t touchpad_cnt = 0; - - /* Read data from touch controller into memory */ - esp_lcd_touch_read_data(touch_ctx->handle); - - /* Read data from touch controller */ - bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_ctx->handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1); - - if (touchpad_pressed && touchpad_cnt > 0) { - data->point.x = touchpad_x[0]; - data->point.y = touchpad_y[0]; - data->state = LV_INDEV_STATE_PRESSED; - } else { - data->state = LV_INDEV_STATE_RELEASED; - } -} -#endif - -#ifdef ESP_LVGL_PORT_KNOB_COMPONENT -static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) -{ - static int32_t last_v = 0; - - assert(indev_drv); - lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data; - assert(ctx); - - int32_t invd = iot_knob_get_count_value(ctx->knob_handle); - knob_event_t event = iot_knob_get_event(ctx->knob_handle); - - if (last_v ^ invd) { - last_v = invd; - data->enc_diff = (KNOB_LEFT == event) ? (-1) : ((KNOB_RIGHT == event) ? (1) : (0)); - } else { - data->enc_diff = 0; - } - data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; -} - -static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2) -{ - lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; - button_handle_t button = (button_handle_t)arg; - if (ctx && button) { - /* ENTER */ - if (button == ctx->btn_handle) { - ctx->btn_enter = true; - } - } -} - -static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2) -{ - lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; - button_handle_t button = (button_handle_t)arg; - if (ctx && button) { - /* ENTER */ - if (button == ctx->btn_handle) { - ctx->btn_enter = false; - } - } -} -#endif - -#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT -static uint32_t last_key = 0; -static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) -{ - assert(indev_drv); - lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data; - assert(ctx); - - /* Buttons */ - if (ctx->btn_prev) { - data->key = LV_KEY_LEFT; - last_key = LV_KEY_LEFT; - data->state = LV_INDEV_STATE_PRESSED; - } else if (ctx->btn_next) { - data->key = LV_KEY_RIGHT; - last_key = LV_KEY_RIGHT; - data->state = LV_INDEV_STATE_PRESSED; - } else if (ctx->btn_enter) { - data->key = LV_KEY_ENTER; - last_key = LV_KEY_ENTER; - data->state = LV_INDEV_STATE_PRESSED; - } else { - data->key = last_key; - data->state = LV_INDEV_STATE_RELEASED; - } -} - -static void lvgl_port_btn_down_handler(void *arg, void *arg2) -{ - lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; - button_handle_t button = (button_handle_t)arg; - if (ctx && button) { - /* PREV */ - if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { - ctx->btn_prev = true; - } - /* NEXT */ - if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { - ctx->btn_next = true; - } - /* ENTER */ - if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { - ctx->btn_enter = true; - } - } -} - -static void lvgl_port_btn_up_handler(void *arg, void *arg2) -{ - lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; - button_handle_t button = (button_handle_t)arg; - if (ctx && button) { - /* PREV */ - if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { - ctx->btn_prev = false; - } - /* NEXT */ - if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { - ctx->btn_next = false; - } - /* ENTER */ - if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { - ctx->btn_enter = false; - } - } -} -#endif - -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void) -{ - esp_err_t ret; - - /* USB HID is already initialized */ - if (lvgl_port_ctx.hid_ctx.task) { - return &lvgl_port_ctx.hid_ctx; - } - - /* USB HID host driver config */ - const hid_host_driver_config_t hid_host_driver_config = { - .create_background_task = true, - .task_priority = 5, - .stack_size = 4096, - .core_id = 0, - .callback = lvgl_port_usb_hid_callback, - .callback_arg = &lvgl_port_ctx.hid_ctx, - }; - - ret = hid_host_install(&hid_host_driver_config); - if (ret != ESP_OK) { - ESP_LOGE(TAG, "USB HID install failed!"); - return NULL; - } - - lvgl_port_ctx.hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t)); - xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_port_ctx.hid_ctx, 2, &lvgl_port_ctx.hid_ctx.task); - - return &lvgl_port_ctx.hid_ctx; -} - -static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift) -{ - char ret_key = 0; - - const uint8_t keycode2ascii [57][2] = { - {0, 0}, /* HID_KEY_NO_PRESS */ - {0, 0}, /* HID_KEY_ROLLOVER */ - {0, 0}, /* HID_KEY_POST_FAIL */ - {0, 0}, /* HID_KEY_ERROR_UNDEFINED */ - {'a', 'A'}, /* HID_KEY_A */ - {'b', 'B'}, /* HID_KEY_B */ - {'c', 'C'}, /* HID_KEY_C */ - {'d', 'D'}, /* HID_KEY_D */ - {'e', 'E'}, /* HID_KEY_E */ - {'f', 'F'}, /* HID_KEY_F */ - {'g', 'G'}, /* HID_KEY_G */ - {'h', 'H'}, /* HID_KEY_H */ - {'i', 'I'}, /* HID_KEY_I */ - {'j', 'J'}, /* HID_KEY_J */ - {'k', 'K'}, /* HID_KEY_K */ - {'l', 'L'}, /* HID_KEY_L */ - {'m', 'M'}, /* HID_KEY_M */ - {'n', 'N'}, /* HID_KEY_N */ - {'o', 'O'}, /* HID_KEY_O */ - {'p', 'P'}, /* HID_KEY_P */ - {'q', 'Q'}, /* HID_KEY_Q */ - {'r', 'R'}, /* HID_KEY_R */ - {'s', 'S'}, /* HID_KEY_S */ - {'t', 'T'}, /* HID_KEY_T */ - {'u', 'U'}, /* HID_KEY_U */ - {'v', 'V'}, /* HID_KEY_V */ - {'w', 'W'}, /* HID_KEY_W */ - {'x', 'X'}, /* HID_KEY_X */ - {'y', 'Y'}, /* HID_KEY_Y */ - {'z', 'Z'}, /* HID_KEY_Z */ - {'1', '!'}, /* HID_KEY_1 */ - {'2', '@'}, /* HID_KEY_2 */ - {'3', '#'}, /* HID_KEY_3 */ - {'4', '$'}, /* HID_KEY_4 */ - {'5', '%'}, /* HID_KEY_5 */ - {'6', '^'}, /* HID_KEY_6 */ - {'7', '&'}, /* HID_KEY_7 */ - {'8', '*'}, /* HID_KEY_8 */ - {'9', '('}, /* HID_KEY_9 */ - {'0', ')'}, /* HID_KEY_0 */ - {'\r', '\r'}, /* HID_KEY_ENTER */ - {0, 0}, /* HID_KEY_ESC */ - {'\b', 0}, /* HID_KEY_DEL */ - {0, 0}, /* HID_KEY_TAB */ - {' ', ' '}, /* HID_KEY_SPACE */ - {'-', '_'}, /* HID_KEY_MINUS */ - {'=', '+'}, /* HID_KEY_EQUAL */ - {'[', '{'}, /* HID_KEY_OPEN_BRACKET */ - {']', '}'}, /* HID_KEY_CLOSE_BRACKET */ - {'\\', '|'}, /* HID_KEY_BACK_SLASH */ - {'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH - {';', ':'}, /* HID_KEY_COLON */ - {'\'', '"'}, /* HID_KEY_QUOTE */ - {'`', '~'}, /* HID_KEY_TILDE */ - {',', '<'}, /* HID_KEY_LESS */ - {'.', '>'}, /* HID_KEY_GREATER */ - {'/', '?'} /* HID_KEY_SLASH */ - }; - - if (shift > 1) { - shift = 1; - } - - if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) { - ret_key = keycode2ascii[key][shift]; - } - - return ret_key; -} - -static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, const hid_host_interface_event_t event, void *arg) -{ - hid_host_dev_params_t dev; - hid_host_device_get_params(hid_device_handle, &dev); - lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; - uint8_t data[10]; - unsigned int data_length = 0; - - assert(hid_ctx != NULL); - - switch (event) { - case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: - hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length); - if (dev.proto == HID_PROTOCOL_KEYBOARD) { - hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data; - if (data_length < sizeof(hid_keyboard_input_report_boot_t)) { - return; - } - for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { - if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) { - char key = 0; - - /* LVGL special keys */ - if (keyboard->key[i] == HID_KEY_TAB) { - if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) { - key = LV_KEY_PREV; - } else { - key = LV_KEY_NEXT; - } - } else if (keyboard->key[i] == HID_KEY_RIGHT) { - key = LV_KEY_RIGHT; - } else if (keyboard->key[i] == HID_KEY_LEFT) { - key = LV_KEY_LEFT; - } else if (keyboard->key[i] == HID_KEY_DOWN) { - key = LV_KEY_DOWN; - } else if (keyboard->key[i] == HID_KEY_UP) { - key = LV_KEY_UP; - } else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) { - key = LV_KEY_ENTER; - } else if (keyboard->key[i] == HID_KEY_DELETE) { - key = LV_KEY_DEL; - } else if (keyboard->key[i] == HID_KEY_HOME) { - key = LV_KEY_HOME; - } else if (keyboard->key[i] == HID_KEY_END) { - key = LV_KEY_END; - } else { - /* Get ASCII char */ - key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift)); - } - - if (key == 0) { - ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]); - } - hid_ctx->kb.last_key = key; - hid_ctx->kb.pressed = true; - } - } - - } else if (dev.proto == HID_PROTOCOL_MOUSE) { - hid_mouse_input_report_boot_t *mouse = (hid_mouse_input_report_boot_t *)data; - if (data_length < sizeof(hid_mouse_input_report_boot_t)) { - break; - } - hid_ctx->mouse.left_button = mouse->buttons.button1; - hid_ctx->mouse.x += mouse->x_displacement; - hid_ctx->mouse.y += mouse->y_displacement; - } - break; - case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: - break; - case HID_HOST_INTERFACE_EVENT_DISCONNECTED: - hid_host_device_close(hid_device_handle); - break; - default: - break; - } -} - -static void lvgl_port_usb_hid_task(void *arg) -{ - hid_host_dev_params_t dev; - lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg; - hid_host_device_handle_t hid_device_handle = NULL; - lvgl_port_usb_hid_event_t msg; - - assert(ctx); - - ctx->running = true; - - while (ctx->running) { - if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) { - hid_device_handle = msg.hid_device_handle; - hid_host_device_get_params(hid_device_handle, &dev); - - switch (msg.event) { - case HID_HOST_DRIVER_EVENT_CONNECTED: - /* Handle mouse or keyboard */ - if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) { - const hid_host_device_config_t dev_config = { - .callback = lvgl_port_usb_hid_host_interface_callback, - .callback_arg = ctx - }; - - ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) ); - ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) ); - ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) ); - ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) ); - } - break; - default: - break; - } - } - } - - xQueueReset(ctx->queue); - vQueueDelete(ctx->queue); - - hid_host_uninstall(); - - memset(&lvgl_port_ctx.hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t)); - - vTaskDelete(NULL); -} - -static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) -{ - int16_t width = 0; - int16_t height = 0; - assert(indev_drv); - lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; - assert(ctx); - - if (indev_drv->disp->driver->rotated == LV_DISP_ROT_NONE || indev_drv->disp->driver->rotated == LV_DISP_ROT_180) { - width = indev_drv->disp->driver->hor_res; - height = indev_drv->disp->driver->ver_res; - } else { - width = indev_drv->disp->driver->ver_res; - height = indev_drv->disp->driver->hor_res; - } - - /* Screen borders */ - if (ctx->mouse.x < 0) { - ctx->mouse.x = 0; - } else if (ctx->mouse.x > width * ctx->mouse.sensitivity) { - ctx->mouse.x = width * ctx->mouse.sensitivity; - } - if (ctx->mouse.y < 0) { - ctx->mouse.y = 0; - } else if (ctx->mouse.y > height * ctx->mouse.sensitivity) { - ctx->mouse.y = height * ctx->mouse.sensitivity; - } - - /* Get coordinates by rotation with sensitivity */ - switch (indev_drv->disp->driver->rotated) { - case LV_DISP_ROT_NONE: - data->point.x = ctx->mouse.x / ctx->mouse.sensitivity; - data->point.y = ctx->mouse.y / ctx->mouse.sensitivity; - break; - case LV_DISP_ROT_90: - data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity; - data->point.x = ctx->mouse.y / ctx->mouse.sensitivity; - break; - case LV_DISP_ROT_180: - data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity; - data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity; - break; - case LV_DISP_ROT_270: - data->point.y = ctx->mouse.x / ctx->mouse.sensitivity; - data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity; - break; - } - - if (ctx->mouse.left_button) { - data->state = LV_INDEV_STATE_PRESSED; - } else { - data->state = LV_INDEV_STATE_RELEASED; - } -} - -static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) -{ - assert(indev_drv); - lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; - assert(ctx); - - data->key = ctx->kb.last_key; - if (ctx->kb.pressed) { - data->state = LV_INDEV_STATE_PRESSED; - ctx->kb.pressed = false; - } else { - data->state = LV_INDEV_STATE_RELEASED; - ctx->kb.last_key = 0; - } -} - -static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg) -{ - lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; - - const lvgl_port_usb_hid_event_t msg = { - .hid_device_handle = hid_device_handle, - .event = event, - .arg = arg - }; - - xQueueSend(hid_ctx->queue, &msg, 0); -} -#endif - -static void lvgl_port_tick_increment(void *arg) -{ - /* Tell LVGL how many milliseconds have elapsed */ - lv_tick_inc(lvgl_port_timer_period_ms); -} - -static esp_err_t lvgl_port_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 = &lvgl_port_tick_increment, - .name = "LVGL tick", - }; - ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, "Creating LVGL timer filed!"); - return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_timer_period_ms * 1000); -} diff --git a/components/esp_lvgl_port/examples/i2c_oled/CMakeLists.txt b/components/esp_lvgl_port/examples/i2c_oled/CMakeLists.txt new file mode 100644 index 00000000..50138627 --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(i2c_oled) diff --git a/components/esp_lvgl_port/examples/i2c_oled/README.md b/components/esp_lvgl_port/examples/i2c_oled/README.md new file mode 100644 index 00000000..ed1d7f31 --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/README.md @@ -0,0 +1,67 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C6 | ESP32-H2 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | -------- | -------- | -------- | + +# I2C OLED example + +[esp_lcd](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) supports I2C interfaced OLED LCD, whose color depth is usually 1bpp. + +This example shows how to make use of the SSD1306 panel driver from `esp_lcd` component to facilitate the porting of LVGL library. In the end, example will display a scrolling text on the OLED screen. + +## LVGL Version + +This example is using the **LVGL8** version. For use it with LVGL9 version, please remove this line from [idf_component.yml](main/idf_component.yml) file: +``` +lvgl/lvgl: "^8" +``` + +## How to use the example + +### Hardware Required + +* An ESP development board +* An SSD1306 OLED LCD, with I2C interface +* An USB cable for power supply and programming + +### Hardware Connection + +The connection between ESP Board and the LCD is as follows: + +``` + ESP Board OLED LCD (I2C) ++------------------+ +-------------------+ +| GND+--------------+GND | +| | | | +| 3V3+--------------+VCC | +| | | | +| SDA+--------------+SDA | +| | | | +| SCL+--------------+SCL | ++------------------+ +-------------------+ +``` + +The GPIO number used by this example can be changed in [lvgl_example_main.c](main/i2c_oled_example_main.c). Please pay attention to the I2C hardware device address as well, you should refer to your module's spec and schematic to determine that address. + +### Build and Flash + +Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A scrolling text will show up on the LCD as expected. + +The first time you run `idf.py` for the example will cost extra time as the build system needs to address the component dependencies and downloads the missing components from registry into `managed_components` folder. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +### Example Output + +```bash +... +I (0) cpu_start: Starting scheduler on APP CPU. +I (345) example: Initialize I2C bus +I (345) example: Install panel IO +I (345) example: Install SSD1306 panel driver +I (455) example: Initialize LVGL library +I (455) example: Register display driver to LVGL +I (455) example: Install LVGL tick timer +I (455) example: Display LVGL Scroll Text +... +``` diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt b/components/esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt new file mode 100644 index 00000000..b40b17ee --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "i2c_oled_example_main.c" "lvgl_demo_ui.c" + INCLUDE_DIRS ".") diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild b/components/esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild new file mode 100644 index 00000000..f868a521 --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/main/Kconfig.projbuild @@ -0,0 +1,35 @@ +menu "Example Configuration" + + choice EXAMPLE_LCD_CONTROLLER + prompt "LCD controller model" + default EXAMPLE_LCD_CONTROLLER_SSD1306 + help + Select LCD controller model + + config EXAMPLE_LCD_CONTROLLER_SSD1306 + bool "SSD1306" + + config EXAMPLE_LCD_CONTROLLER_SH1107 + bool "SH1107" + endchoice + + if EXAMPLE_LCD_CONTROLLER_SSD1306 + choice EXAMPLE_SSD1306_HEIGHT + prompt "SSD1306 Height in pixels" + default EXAMPLE_SSD1306_HEIGHT_64 + help + Height of the display in pixels. a.k.a vertical resolution + + config EXAMPLE_SSD1306_HEIGHT_64 + bool "64" + config EXAMPLE_SSD1306_HEIGHT_32 + bool "32" + endchoice + + config EXAMPLE_SSD1306_HEIGHT + int + default 64 if EXAMPLE_SSD1306_HEIGHT_64 + default 32 if EXAMPLE_SSD1306_HEIGHT_32 + endif + +endmenu diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c b/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c new file mode 100644 index 00000000..fe37ccb0 --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/main/i2c_oled_example_main.c @@ -0,0 +1,144 @@ +/* + * 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_timer.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "driver/i2c.h" +#include "esp_err.h" +#include "esp_log.h" +#include "lvgl.h" +#include "esp_lvgl_port.h" + +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 +#include "esp_lcd_sh1107.h" +#else +#include "esp_lcd_panel_vendor.h" +#endif + +static const char *TAG = "example"; + +#define I2C_HOST 0 + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////// Please update the following configuration according to your LCD spec ////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +#define EXAMPLE_LCD_PIXEL_CLOCK_HZ (400 * 1000) +#define EXAMPLE_PIN_NUM_SDA 18 +#define EXAMPLE_PIN_NUM_SCL 23 +#define EXAMPLE_PIN_NUM_RST -1 +#define EXAMPLE_I2C_HW_ADDR 0x3C + +// The pixel number in horizontal and vertical +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 +#define EXAMPLE_LCD_H_RES 128 +#define EXAMPLE_LCD_V_RES CONFIG_EXAMPLE_SSD1306_HEIGHT +#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 +#define EXAMPLE_LCD_H_RES 64 +#define EXAMPLE_LCD_V_RES 128 +#endif +// Bit number used to represent command and parameter +#define EXAMPLE_LCD_CMD_BITS 8 +#define EXAMPLE_LCD_PARAM_BITS 8 + +extern void example_lvgl_demo_ui(lv_disp_t *disp); + +void app_main(void) +{ + ESP_LOGI(TAG, "Initialize I2C bus"); + i2c_config_t i2c_conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = EXAMPLE_PIN_NUM_SDA, + .scl_io_num = EXAMPLE_PIN_NUM_SCL, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = EXAMPLE_LCD_PIXEL_CLOCK_HZ, + }; + ESP_ERROR_CHECK(i2c_param_config(I2C_HOST, &i2c_conf)); + ESP_ERROR_CHECK(i2c_driver_install(I2C_HOST, I2C_MODE_MASTER, 0, 0, 0)); + + ESP_LOGI(TAG, "Install panel IO"); + esp_lcd_panel_io_handle_t io_handle = NULL; + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = EXAMPLE_I2C_HW_ADDR, + .control_phase_bytes = 1, // According to SSD1306 datasheet + .lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet + .lcd_param_bits = EXAMPLE_LCD_CMD_BITS, // According to SSD1306 datasheet +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 + .dc_bit_offset = 6, // According to SSD1306 datasheet +#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + .dc_bit_offset = 0, // According to SH1107 datasheet + .flags = + { + .disable_control_phase = 1, + } +#endif + }; + ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(I2C_HOST, &io_config, &io_handle)); + + ESP_LOGI(TAG, "Install SSD1306 panel driver"); + esp_lcd_panel_handle_t panel_handle = NULL; + esp_lcd_panel_dev_config_t panel_config = { + .bits_per_pixel = 1, + .reset_gpio_num = EXAMPLE_PIN_NUM_RST, +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5,0,0)) + .color_space = ESP_LCD_COLOR_SPACE_MONOCHROME, +#endif + }; +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SSD1306 +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0)) + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = EXAMPLE_LCD_V_RES, + }; + panel_config.vendor_config = &ssd1306_config; +#endif + ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(io_handle, &panel_config, &panel_handle)); +#elif CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + ESP_ERROR_CHECK(esp_lcd_new_panel_sh1107(io_handle, &panel_config, &panel_handle)); +#endif + + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); + +#if CONFIG_EXAMPLE_LCD_CONTROLLER_SH1107 + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true)); +#endif + + ESP_LOGI(TAG, "Initialize LVGL"); + const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + lvgl_port_init(&lvgl_cfg); + + const lvgl_port_display_cfg_t disp_cfg = { + .io_handle = io_handle, + .panel_handle = panel_handle, + .buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES, + .double_buffer = true, + .hres = EXAMPLE_LCD_H_RES, + .vres = EXAMPLE_LCD_V_RES, + .monochrome = true, + .rotation = { + .swap_xy = false, + .mirror_x = false, + .mirror_y = false, + } + }; + lv_disp_t *disp = lvgl_port_add_disp(&disp_cfg); + + /* Rotation of the screen */ + lv_disp_set_rotation(disp, LV_DISP_ROT_NONE); + + ESP_LOGI(TAG, "Display LVGL Scroll Text"); + // Lock the mutex due to the LVGL APIs are not thread-safe + if (lvgl_port_lock(0)) { + example_lvgl_demo_ui(disp); + // Release the mutex + lvgl_port_unlock(); + } +} diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/idf_component.yml b/components/esp_lvgl_port/examples/i2c_oled/main/idf_component.yml new file mode 100644 index 00000000..83ea34b7 --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/main/idf_component.yml @@ -0,0 +1,7 @@ +dependencies: + idf: ">=4.4" + lvgl/lvgl: "^8" + esp_lcd_sh1107: "^1" + esp_lvgl_port: + version: "*" + override_path: "../../../" diff --git a/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c b/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c new file mode 100644 index 00000000..fa60a8b9 --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/main/lvgl_demo_ui.c @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: CC0-1.0 + */ + +#include "lvgl.h" + +void example_lvgl_demo_ui(lv_disp_t *disp) +{ + lv_obj_t *scr = lv_disp_get_scr_act(disp); + lv_obj_t *label = lv_label_create(scr); + lv_label_set_long_mode(label, LV_LABEL_LONG_SCROLL_CIRCULAR); /* Circular scroll */ + lv_label_set_text(label, "Hello Espressif, Hello LVGL."); + /* Size of the screen (if you use rotation 90 or 270, please set disp->driver->ver_res) */ + lv_obj_set_width(label, disp->driver->hor_res); + lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 0); +} diff --git a/components/esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults b/components/esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults new file mode 100644 index 00000000..d92efacc --- /dev/null +++ b/components/esp_lvgl_port/examples/i2c_oled/sdkconfig.defaults @@ -0,0 +1,5 @@ +# This file was generated using idf.py save-defconfig. It can be edited manually. +# Espressif IoT Development Framework (ESP-IDF) Project Minimal Configuration +# +CONFIG_LV_USE_USER_DATA=y +CONFIG_LV_COLOR_DEPTH_1=y diff --git a/components/esp_lvgl_port/examples/touchscreen/main/idf_component.yml b/components/esp_lvgl_port/examples/touchscreen/main/idf_component.yml index e37ecbed..5f7c9fe3 100644 --- a/components/esp_lvgl_port/examples/touchscreen/main/idf_component.yml +++ b/components/esp_lvgl_port/examples/touchscreen/main/idf_component.yml @@ -4,5 +4,5 @@ dependencies: version: "^1" override_path: "../../../../lcd_touch/esp_lcd_touch_tt21100/" esp_lvgl_port: - version: ">=1.2.0" + version: "*" override_path: "../../../" diff --git a/components/esp_lvgl_port/examples/touchscreen/main/main.c b/components/esp_lvgl_port/examples/touchscreen/main/main.c index 987da8c3..f2b3506f 100644 --- a/components/esp_lvgl_port/examples/touchscreen/main/main.c +++ b/components/esp_lvgl_port/examples/touchscreen/main/main.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -57,7 +57,7 @@ static esp_lcd_panel_handle_t lcd_panel = NULL; static esp_lcd_touch_handle_t touch_handle = NULL; /* LVGL display and touch */ -static lv_disp_t *lvgl_disp = NULL; +static lv_display_t *lvgl_disp = NULL; static lv_indev_t *lvgl_touch_indev = NULL; static esp_err_t app_lcd_init(void) @@ -190,6 +190,7 @@ static esp_err_t app_lvgl_init(void) }, .flags = { .buff_dma = true, + .swap_bytes = true, } }; lvgl_disp = lvgl_port_add_disp(&disp_cfg); @@ -206,10 +207,10 @@ static esp_err_t app_lvgl_init(void) static void _app_button_cb(lv_event_t *e) { - lv_disp_rot_t rotation = lv_disp_get_rotation(lvgl_disp); + lv_disp_rotation_t rotation = lv_disp_get_rotation(lvgl_disp); rotation++; - if (rotation > LV_DISP_ROT_270) { - rotation = LV_DISP_ROT_NONE; + if (rotation > LV_DISPLAY_ROTATION_270) { + rotation = LV_DISPLAY_ROTATION_0; } /* LCD HW rotation */ @@ -227,10 +228,14 @@ static void app_main_display(void) /* Label */ lv_obj_t *label = lv_label_create(scr); - lv_label_set_recolor(label, true); lv_obj_set_width(label, EXAMPLE_LCD_H_RES); lv_obj_set_style_text_align(label, LV_TEXT_ALIGN_CENTER, 0); +#if LVGL_VERSION_MAJOR == 8 + lv_label_set_recolor(label, true); lv_label_set_text(label, "#FF0000 "LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"#\n#FF9400 "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING" #"); +#else + lv_label_set_text(label, LV_SYMBOL_BELL" Hello world Espressif and LVGL "LV_SYMBOL_BELL"\n "LV_SYMBOL_WARNING" For simplier initialization, use BSP "LV_SYMBOL_WARNING); +#endif lv_obj_align(label, LV_ALIGN_CENTER, 0, -30); /* Button */ diff --git a/components/esp_lvgl_port/idf_component.yml b/components/esp_lvgl_port/idf_component.yml index c888f77a..c220297c 100644 --- a/components/esp_lvgl_port/idf_component.yml +++ b/components/esp_lvgl_port/idf_component.yml @@ -1,8 +1,8 @@ -version: "1.4.0" +version: "2.0.0" description: ESP LVGL port url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port dependencies: idf: ">=4.4" lvgl/lvgl: - version: "^8" + version: ">=8,<10" public: true diff --git a/components/esp_lvgl_port/images/img_cursor.c b/components/esp_lvgl_port/images/lvgl8/img_cursor.c similarity index 100% rename from components/esp_lvgl_port/images/img_cursor.c rename to components/esp_lvgl_port/images/lvgl8/img_cursor.c diff --git a/components/esp_lvgl_port/images/lvgl9/img_cursor.c b/components/esp_lvgl_port/images/lvgl9/img_cursor.c new file mode 100644 index 00000000..0e16e53c --- /dev/null +++ b/components/esp_lvgl_port/images/lvgl9/img_cursor.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifdef __has_include +#if __has_include("lvgl.h") +#ifndef LV_LVGL_H_INCLUDE_SIMPLE +#define LV_LVGL_H_INCLUDE_SIMPLE +#endif +#endif +#endif + +#if defined(LV_LVGL_H_INCLUDE_SIMPLE) +#include "lvgl.h" +#else +#include "lvgl/lvgl.h" +#endif + + +#ifndef LV_ATTRIBUTE_MEM_ALIGN +#define LV_ATTRIBUTE_MEM_ALIGN +#endif + +#ifndef LV_ATTRIBUTE_IMG_DUST +#define LV_ATTRIBUTE_IMG_DUST +#endif + +static const +LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_DUST +uint8_t img_cursor_20px_map[] = { + + 0x00, 0x00, 0x00, 0xb2, 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xe4, 0x00, 0x00, 0x00, 0xae, 0x00, 0x00, 0x00, 0x6b, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x71, 0x00, 0x00, 0x00, 0xfc, 0x5b, 0x5b, 0x5b, 0xf1, 0x93, 0x93, 0x93, 0xfc, 0x41, 0x41, 0x41, 0xf9, 0x1e, 0x1e, 0x1e, 0xfe, 0x06, 0x06, 0x06, 0xf4, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x2e, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00, 0xe4, 0x93, 0x93, 0x93, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xe3, 0xe3, 0xe3, 0xf9, 0xc8, 0xc8, 0xc8, 0xfe, 0x6c, 0x6c, 0x6c, 0xf3, 0x20, 0x20, 0x20, 0xfe, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x9c, 0x00, 0x00, 0x00, 0x51, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0xae, 0x41, 0x41, 0x41, 0xf9, 0xe3, 0xe3, 0xe3, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfb, 0xfb, 0xfa, 0xac, 0xac, 0xac, 0xf0, 0x6f, 0x6f, 0x6f, 0xfb, 0x26, 0x26, 0x26, 0xf9, 0x0f, 0x0f, 0x0f, 0xff, 0x00, 0x00, 0x00, 0xe8, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x1e, 0x1e, 0x1e, 0xfe, 0xc8, 0xc8, 0xc8, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfb, 0xd4, 0xd4, 0xd4, 0xf9, 0xae, 0xae, 0xae, 0xf6, 0x48, 0x48, 0x48, 0xf5, 0x1f, 0x1f, 0x1f, 0xff, 0x00, 0x00, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xcb, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x06, 0x06, 0x06, 0xf4, 0x6c, 0x6c, 0x6c, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf2, 0xf2, 0xf2, 0xf5, 0x7e, 0x7e, 0x7e, 0xf5, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb9, 0x20, 0x20, 0x20, 0xfe, 0xfb, 0xfb, 0xfb, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xef, 0xef, 0xf4, 0x73, 0x73, 0x73, 0xfc, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0xf8, 0xac, 0xac, 0xac, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0x2e, 0x2e, 0x2e, 0xfd, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x00, 0x00, 0xd7, 0x6f, 0x6f, 0x6f, 0xfb, 0xfc, 0xfc, 0xfc, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0x2e, 0x2e, 0x2e, 0xfd, 0x00, 0x00, 0x00, 0xdd, 0x00, 0x00, 0x00, 0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x9c, 0x26, 0x26, 0x26, 0xf9, 0xd4, 0xd4, 0xd4, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0xca, 0xca, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x03, 0x03, 0x03, 0xdd, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x52, 0x0f, 0x0f, 0x0f, 0xff, 0xae, 0xae, 0xae, 0xf6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0xb0, 0xb0, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0xe8, 0x48, 0x48, 0x48, 0xf5, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xc8, 0xc8, 0xf9, 0xc8, 0xc8, 0xc8, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xca, 0xca, 0xca, 0xfa, 0x2b, 0x2b, 0x2b, 0xfd, 0x03, 0x03, 0x03, 0xdd, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x1f, 0x1f, 0x1f, 0xff, 0xf2, 0xf2, 0xf2, 0xf5, 0xf0, 0xf0, 0xf0, 0xf4, 0x2e, 0x2e, 0x2e, 0xfd, 0x2e, 0x2e, 0x2e, 0xfd, 0xca, 0xca, 0xca, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb0, 0xb0, 0xb0, 0xfa, 0x2b, 0x2b, 0x2b, 0xfc, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0x2c, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4a, 0x00, 0x00, 0x00, 0xf0, 0x7e, 0x7e, 0x7e, 0xf5, 0x73, 0x73, 0x73, 0xfc, 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0xdd, 0x2b, 0x2b, 0x2b, 0xfc, 0xb0, 0xb0, 0xb0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xb1, 0xb1, 0xb1, 0xef, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xd7, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0xcb, 0x12, 0x12, 0x12, 0xfd, 0x12, 0x12, 0x12, 0xfd, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x37, 0x03, 0x03, 0x03, 0xdd, 0x2b, 0x2b, 0x2b, 0xfd, 0xca, 0xca, 0xca, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0x86, 0x86, 0x86, 0xf2, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xb6, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xe7, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xc2, 0x2b, 0x2b, 0x2b, 0xfc, 0xb0, 0xb0, 0xb0, 0xfa, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xfc, 0xfc, 0xfc, 0x74, 0x74, 0x74, 0xf8, 0x06, 0x06, 0x06, 0xfe, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x16, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x03, 0x03, 0x03, 0xdd, 0x2b, 0x2b, 0x2b, 0xfc, 0xb1, 0xb1, 0xb1, 0xef, 0x86, 0x86, 0x86, 0xf2, 0x06, 0x06, 0x06, 0xfe, 0x00, 0x00, 0x00, 0xb9, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x9d, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00, 0xd7, 0x00, 0x00, 0x00, 0xb6, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + +}; + +const lv_img_dsc_t img_cursor = { + .header.magic = LV_IMAGE_HEADER_MAGIC, + .header.cf = LV_COLOR_FORMAT_ARGB8888, + .header.flags = 0, + .header.w = 20, + .header.h = 20, + .header.stride = 80, + .data_size = 1600, + .data = img_cursor_20px_map, +}; diff --git a/components/esp_lvgl_port/include/esp_lvgl_port.h b/components/esp_lvgl_port/include/esp_lvgl_port.h index f19b2d72..6c15776b 100644 --- a/components/esp_lvgl_port/include/esp_lvgl_port.h +++ b/components/esp_lvgl_port/include/esp_lvgl_port.h @@ -12,28 +12,15 @@ #pragma once #include "esp_err.h" -#include "esp_lcd_panel_io.h" -#include "esp_lcd_panel_ops.h" #include "lvgl.h" - -#if __has_include ("esp_lcd_touch.h") -#include "esp_lcd_touch.h" -#define ESP_LVGL_PORT_TOUCH_COMPONENT 1 -#endif - -#if __has_include ("iot_knob.h") -#include "iot_knob.h" -#include "iot_button.h" -#define ESP_LVGL_PORT_KNOB_COMPONENT 1 -#endif - -#if __has_include ("iot_button.h") -#include "iot_button.h" -#define ESP_LVGL_PORT_BUTTON_COMPONENT 1 -#endif - -#if __has_include ("usb/hid_host.h") -#define ESP_LVGL_PORT_USB_HOST_HID_COMPONENT 1 +#include "esp_lvgl_port_disp.h" +#include "esp_lvgl_port_touch.h" +#include "esp_lvgl_port_knob.h" +#include "esp_lvgl_port_button.h" +#include "esp_lvgl_port_usbhid.h" + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" #endif #ifdef __cplusplus @@ -51,87 +38,6 @@ typedef struct { int timer_period_ms; /*!< LVGL timer tick period in ms */ } lvgl_port_cfg_t; -/** - * @brief Rotation configuration - */ -typedef struct { - bool swap_xy; /*!< LCD Screen swapped X and Y (in esp_lcd driver) */ - bool mirror_x; /*!< LCD Screen mirrored X (in esp_lcd driver) */ - bool mirror_y; /*!< LCD Screen mirrored Y (in esp_lcd driver) */ -} lvgl_port_rotation_cfg_t; - -/** - * @brief Configuration display structure - */ -typedef struct { - esp_lcd_panel_io_handle_t io_handle; /*!< LCD panel IO handle */ - esp_lcd_panel_handle_t panel_handle; /*!< LCD panel handle */ - uint32_t buffer_size; /*!< Size of the buffer for the screen in pixels */ - bool double_buffer; /*!< True, if should be allocated two buffers */ - uint32_t trans_size; /*!< Allocated buffer will be in SRAM to move framebuf */ - uint32_t hres; /*!< LCD display horizontal resolution */ - uint32_t vres; /*!< LCD display vertical resolution */ - bool monochrome; /*!< True, if display is monochrome and using 1bit for 1px */ - lvgl_port_rotation_cfg_t rotation; /*!< Default values of the screen rotation */ - - struct { - unsigned int buff_dma: 1; /*!< Allocated LVGL buffer will be DMA capable */ - unsigned int buff_spiram: 1; /*!< Allocated LVGL buffer will be in PSRAM */ - unsigned int sw_rotate: 1; /*!< Use software rotation (slower) */ - } flags; -} lvgl_port_display_cfg_t; - -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -/** - * @brief Configuration touch structure - */ -typedef struct { - lv_disp_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ - esp_lcd_touch_handle_t handle; /*!< LCD touch IO handle */ -} lvgl_port_touch_cfg_t; -#endif - -#ifdef ESP_LVGL_PORT_KNOB_COMPONENT -/** - * @brief Configuration of the encoder structure - */ -typedef struct { - lv_disp_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ - const knob_config_t *encoder_a_b; - const button_config_t *encoder_enter; /*!< Navigation button for enter */ -} lvgl_port_encoder_cfg_t; -#endif - -#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT -/** - * @brief Configuration of the navigation buttons structure - */ -typedef struct { - lv_disp_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ - const button_config_t *button_prev; /*!< Navigation button for previous */ - const button_config_t *button_next; /*!< Navigation button for next */ - const button_config_t *button_enter; /*!< Navigation button for enter */ -} lvgl_port_nav_btns_cfg_t; -#endif - -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -/** - * @brief Configuration of the mouse input - */ -typedef struct { - lv_disp_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ - uint8_t sensitivity; /*!< Mouse sensitivity (cannot be zero) */ - lv_obj_t *cursor_img; /*!< Mouse cursor image, if NULL then used default */ -} lvgl_port_hid_mouse_cfg_t; - -/** - * @brief Configuration of the keyboard input - */ -typedef struct { - lv_disp_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ -} lvgl_port_hid_keyboard_cfg_t; -#endif - /** * @brief LVGL port configuration structure * @@ -139,7 +45,7 @@ typedef struct { #define ESP_LVGL_PORT_INIT_CONFIG() \ { \ .task_priority = 4, \ - .task_stack = 4096, \ + .task_stack = 6144, \ .task_affinity = -1, \ .task_max_sleep_ms = 500, \ .timer_period_ms = 5, \ @@ -169,124 +75,6 @@ esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg); */ esp_err_t lvgl_port_deinit(void); -/** - * @brief Add display handling to LVGL - * - * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_disp for free all memory! - * - * @param disp_cfg Display configuration structure - * @return Pointer to LVGL display or NULL when error occurred - */ -lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg); - -/** - * @brief Remove display handling from LVGL - * - * @note Free all memory used for this display. - * - * @return - * - ESP_OK on success - */ -esp_err_t lvgl_port_remove_disp(lv_disp_t *disp); - -#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT -/** - * @brief Add LCD touch as an input device - * - * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_touch for free all memory! - * - * @param touch_cfg Touch configuration structure - * @return Pointer to LVGL touch input device or NULL when error occurred - */ -lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg); - -/** - * @brief Remove selected LCD touch from input devices - * - * @note Free all memory used for this display. - * - * @return - * - ESP_OK on success - */ -esp_err_t lvgl_port_remove_touch(lv_indev_t *touch); -#endif - -#ifdef ESP_LVGL_PORT_KNOB_COMPONENT -/** - * @brief Add encoder as an input device - * - * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_encoder for free all memory! - * - * @param encoder_cfg Encoder configuration structure - * @return Pointer to LVGL encoder input device or NULL when error occurred - */ -lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg); - -/** - * @brief Remove encoder from input devices - * - * @note Free all memory used for this input device. - * - * @return - * - ESP_OK on success - */ -esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder); -#endif - -#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT -/** - * @brief Add buttons as an input device - * - * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_navigation_buttons for free all memory! - * - * @param buttons_cfg Buttons configuration structure - * @return Pointer to LVGL buttons input device or NULL when error occurred - */ -lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg); - -/** - * @brief Remove selected buttons from input devices - * - * @note Free all memory used for this input device. - * - * @return - * - ESP_OK on success - */ -esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons); -#endif - -#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT -/** - * @brief Add USB HID mouse as an input device - * - * @note The USB host must be initialized before. Use `usb_host_install` for host initialization. - * - * @param mouse_cfg mouse configuration structure - * @return Pointer to LVGL buttons input device or NULL when error occurred - */ -lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg); - -/** - * @brief Add USB HID keyboard as an input device - * - * @note The USB host must be initialized before. Use `usb_host_install` for host initialization. - * - * @param keyboard_cfg keyboard configuration structure - * @return Pointer to LVGL buttons input device or NULL when error occurred - */ -lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg); - -/** - * @brief Remove selected USB HID from input devices - * - * @note Free all memory used for this input device. When removed all HID devices, the HID task will be freed. - * - * @return - * - ESP_OK on success - */ -esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid); -#endif - /** * @brief Take LVGL mutex * @@ -310,7 +98,7 @@ void lvgl_port_unlock(void); * * @param disp LVGL display handle (returned from lvgl_port_add_disp) */ -void lvgl_port_flush_ready(lv_disp_t *disp); +void lvgl_port_flush_ready(lv_display_t *disp); /** * @brief Stop lvgl task diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_button.h b/components/esp_lvgl_port/include/esp_lvgl_port_button.h new file mode 100644 index 00000000..e7b275cb --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_button.h @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port button + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("iot_button.h") +#include "iot_button.h" +#define ESP_LVGL_PORT_BUTTON_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_BUTTON_COMPONENT +/** + * @brief Configuration of the navigation buttons structure + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + const button_config_t *button_prev; /*!< Navigation button for previous */ + const button_config_t *button_next; /*!< Navigation button for next */ + const button_config_t *button_enter; /*!< Navigation button for enter */ +} lvgl_port_nav_btns_cfg_t; + +/** + * @brief Add buttons as an input device + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_navigation_buttons for free all memory! + * + * @param buttons_cfg Buttons configuration structure + * @return Pointer to LVGL buttons input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg); + +/** + * @brief Remove selected buttons from input devices + * + * @note Free all memory used for this input device. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_compatibility.h b/components/esp_lvgl_port/include/esp_lvgl_port_compatibility.h new file mode 100644 index 00000000..b3619b2c --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_compatibility.h @@ -0,0 +1,31 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port compatibility + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Backward compatibility with LVGL 8 + */ +typedef lv_disp_t lv_display_t; +typedef enum { + LV_DISPLAY_ROTATION_0 = LV_DISP_ROT_NONE, + LV_DISPLAY_ROTATION_90 = LV_DISP_ROT_90, + LV_DISPLAY_ROTATION_180 = LV_DISP_ROT_180, + LV_DISPLAY_ROTATION_270 = LV_DISP_ROT_270 +} lv_disp_rotation_t; + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_disp.h b/components/esp_lvgl_port/include/esp_lvgl_port_disp.h new file mode 100644 index 00000000..a6eaf8af --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_disp.h @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port display + */ + +#pragma once + +#include "esp_err.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "lvgl.h" + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Rotation configuration + */ +typedef struct { + bool swap_xy; /*!< LCD Screen swapped X and Y (in esp_lcd driver) */ + bool mirror_x; /*!< LCD Screen mirrored X (in esp_lcd driver) */ + bool mirror_y; /*!< LCD Screen mirrored Y (in esp_lcd driver) */ +} lvgl_port_rotation_cfg_t; + +/** + * @brief Configuration display structure + */ +typedef struct { + esp_lcd_panel_io_handle_t io_handle; /*!< LCD panel IO handle */ + esp_lcd_panel_handle_t panel_handle; /*!< LCD panel handle */ + esp_lcd_panel_handle_t control_handle; /*!< LCD panel control handle */ + + uint32_t buffer_size; /*!< Size of the buffer for the screen in pixels */ + bool double_buffer; /*!< True, if should be allocated two buffers */ + uint32_t trans_size; /*!< Allocated buffer will be in SRAM to move framebuf */ + + uint32_t hres; /*!< LCD display horizontal resolution */ + uint32_t vres; /*!< LCD display vertical resolution */ + + bool monochrome; /*!< True, if display is monochrome and using 1bit for 1px */ + bool mipi_dsi; /*!< True, if display is MIPI-DSI */ + + lvgl_port_rotation_cfg_t rotation; /*!< Default values of the screen rotation */ + + struct { + unsigned int buff_dma: 1; /*!< Allocated LVGL buffer will be DMA capable */ + unsigned int buff_spiram: 1; /*!< Allocated LVGL buffer will be in PSRAM */ + unsigned int sw_rotate: 1; /*!< Use software rotation (slower) */ + unsigned int swap_bytes: 1; /*!< Swap bytes in RGB656 (16-bit) before send to LCD driver */ + } flags; +} lvgl_port_display_cfg_t; + +/** + * @brief Add display handling to LVGL + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_disp for free all memory! + * + * @param disp_cfg Display configuration structure + * @return Pointer to LVGL display or NULL when error occurred + */ +lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg); + +/** + * @brief Remove display handling from LVGL + * + * @note Free all memory used for this display. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_disp(lv_display_t *disp); + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_knob.h b/components/esp_lvgl_port/include/esp_lvgl_port_knob.h new file mode 100644 index 00000000..47b667fb --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_knob.h @@ -0,0 +1,67 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port knob + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("iot_knob.h") +#if !__has_include("iot_button.h") +#error LVLG Knob requires button component. Please add it with idf.py add-dependency espressif/button +#endif +#include "iot_knob.h" +#include "iot_button.h" +#define ESP_LVGL_PORT_KNOB_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_KNOB_COMPONENT +/** + * @brief Configuration of the encoder structure + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + const knob_config_t *encoder_a_b; + const button_config_t *encoder_enter; /*!< Navigation button for enter */ +} lvgl_port_encoder_cfg_t; + +/** + * @brief Add encoder as an input device + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_encoder for free all memory! + * + * @param encoder_cfg Encoder configuration structure + * @return Pointer to LVGL encoder input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg); + +/** + * @brief Remove encoder from input devices + * + * @note Free all memory used for this input device. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_touch.h b/components/esp_lvgl_port/include/esp_lvgl_port_touch.h new file mode 100644 index 00000000..b14037d2 --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_touch.h @@ -0,0 +1,62 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port touch + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("esp_lcd_touch.h") +#include "esp_lcd_touch.h" +#define ESP_LVGL_PORT_TOUCH_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_TOUCH_COMPONENT +/** + * @brief Configuration touch structure + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + esp_lcd_touch_handle_t handle; /*!< LCD touch IO handle */ +} lvgl_port_touch_cfg_t; + +/** + * @brief Add LCD touch as an input device + * + * @note Allocated memory in this function is not free in deinit. You must call lvgl_port_remove_touch for free all memory! + * + * @param touch_cfg Touch configuration structure + * @return Pointer to LVGL touch input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg); + +/** + * @brief Remove selected LCD touch from input devices + * + * @note Free all memory used for this display. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_touch(lv_indev_t *touch); +#endif + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/include/esp_lvgl_port_usbhid.h b/components/esp_lvgl_port/include/esp_lvgl_port_usbhid.h new file mode 100644 index 00000000..f8d303cd --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_usbhid.h @@ -0,0 +1,80 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief ESP LVGL port USB HID + */ + +#pragma once + +#include "esp_err.h" +#include "lvgl.h" + +#if __has_include ("usb/hid_host.h") +#define ESP_LVGL_PORT_USB_HOST_HID_COMPONENT 1 +#endif + +#if LVGL_VERSION_MAJOR == 8 +#include "esp_lvgl_port_compatibility.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef ESP_LVGL_PORT_USB_HOST_HID_COMPONENT +/** + * @brief Configuration of the mouse input + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ + uint8_t sensitivity; /*!< Mouse sensitivity (cannot be zero) */ + lv_obj_t *cursor_img; /*!< Mouse cursor image, if NULL then used default */ +} lvgl_port_hid_mouse_cfg_t; + +/** + * @brief Configuration of the keyboard input + */ +typedef struct { + lv_display_t *disp; /*!< LVGL display handle (returned from lvgl_port_add_disp) */ +} lvgl_port_hid_keyboard_cfg_t; + +/** + * @brief Add USB HID mouse as an input device + * + * @note The USB host must be initialized before. Use `usb_host_install` for host initialization. + * + * @param mouse_cfg mouse configuration structure + * @return Pointer to LVGL buttons input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg); + +/** + * @brief Add USB HID keyboard as an input device + * + * @note The USB host must be initialized before. Use `usb_host_install` for host initialization. + * + * @param keyboard_cfg keyboard configuration structure + * @return Pointer to LVGL buttons input device or NULL when error occurred + */ +lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg); + +/** + * @brief Remove selected USB HID from input devices + * + * @note Free all memory used for this input device. When removed all HID devices, the HID task will be freed. + * + * @return + * - ESP_OK on success + */ +esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid); +#endif + + +#ifdef __cplusplus +} +#endif diff --git a/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port.c b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port.c new file mode 100644 index 00000000..423f0e20 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port.c @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_lvgl_port.h" +#include "lvgl.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct lvgl_port_ctx_s { + SemaphoreHandle_t lvgl_mux; + esp_timer_handle_t tick_timer; + bool running; + int task_max_sleep_ms; + int timer_period_ms; +} lvgl_port_ctx_t; + +/******************************************************************************* +* Local variables +*******************************************************************************/ +static lvgl_port_ctx_t lvgl_port_ctx; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static void lvgl_port_task(void *arg); +static esp_err_t lvgl_port_tick_init(void); +static void lvgl_port_task_deinit(void); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1)); + + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); + + /* LVGL init */ + lv_init(); + /* Tick init */ + lvgl_port_ctx.timer_period_ms = cfg->timer_period_ms; + ESP_RETURN_ON_ERROR(lvgl_port_tick_init(), TAG, ""); + /* Create task */ + lvgl_port_ctx.task_max_sleep_ms = cfg->task_max_sleep_ms; + if (lvgl_port_ctx.task_max_sleep_ms == 0) { + lvgl_port_ctx.task_max_sleep_ms = 500; + } + lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex(); + ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!"); + + BaseType_t res; + if (cfg->task_affinity < 0) { + res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL); + } else { + res = xTaskCreatePinnedToCore(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL, cfg->task_affinity); + } + ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!"); + +err: + if (ret != ESP_OK) { + lvgl_port_deinit(); + } + + return ret; +} + +esp_err_t lvgl_port_resume(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(true); + ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); + } + + return ret; +} + +esp_err_t lvgl_port_stop(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(false); + ret = esp_timer_stop(lvgl_port_ctx.tick_timer); + } + + return ret; +} + +esp_err_t lvgl_port_deinit(void) +{ + /* Stop and delete timer */ + if (lvgl_port_ctx.tick_timer != NULL) { + esp_timer_stop(lvgl_port_ctx.tick_timer); + esp_timer_delete(lvgl_port_ctx.tick_timer); + lvgl_port_ctx.tick_timer = NULL; + } + + /* Stop running task */ + if (lvgl_port_ctx.running) { + lvgl_port_ctx.running = false; + } else { + lvgl_port_task_deinit(); + } + + return ESP_OK; +} + +bool lvgl_port_lock(uint32_t timeout_ms) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + + const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE; +} + +void lvgl_port_unlock(void) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_task(void *arg) +{ + uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + + ESP_LOGI(TAG, "Starting LVGL task"); + lvgl_port_ctx.running = true; + while (lvgl_port_ctx.running) { + if (lvgl_port_lock(0)) { + task_delay_ms = lv_timer_handler(); + lvgl_port_unlock(); + } + if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) { + task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + } else if (task_delay_ms < 1) { + task_delay_ms = 1; + } + vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); + } + + lvgl_port_task_deinit(); + + /* Close task */ + vTaskDelete( NULL ); +} + +static void lvgl_port_task_deinit(void) +{ + if (lvgl_port_ctx.lvgl_mux) { + vSemaphoreDelete(lvgl_port_ctx.lvgl_mux); + } + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + /* Deinitialize LVGL */ + lv_deinit(); +#endif +} + +static void lvgl_port_tick_increment(void *arg) +{ + /* Tell LVGL how many milliseconds have elapsed */ + lv_tick_inc(lvgl_port_ctx.timer_period_ms); +} + +static esp_err_t lvgl_port_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 = &lvgl_port_tick_increment, + .name = "LVGL tick", + }; + ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, "Creating LVGL timer filed!"); + return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); +} diff --git a/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c new file mode 100644 index 00000000..75782464 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_button.c @@ -0,0 +1,197 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef enum { + LVGL_PORT_NAV_BTN_PREV, + LVGL_PORT_NAV_BTN_NEXT, + LVGL_PORT_NAV_BTN_ENTER, + LVGL_PORT_NAV_BTN_CNT, +} lvgl_port_nav_btns_t; + +typedef struct { + button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */ + lv_indev_drv_t indev_drv; /* LVGL input device driver */ + bool btn_prev; /* Button prev state */ + bool btn_next; /* Button next state */ + bool btn_enter; /* Button enter state */ +} lvgl_port_nav_btns_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_btn_up_handler(void *arg, void *arg2); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg) +{ + esp_err_t ret = ESP_OK; + lv_indev_t *indev = NULL; + assert(buttons_cfg != NULL); + assert(buttons_cfg->disp != NULL); + + /* Touch context */ + lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t)); + if (buttons_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for buttons context allocation!"); + return NULL; + } + + /* Previous button */ + if (buttons_cfg->button_prev != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + /* Next button */ + if (buttons_cfg->button_next != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + /* Enter button */ + if (buttons_cfg->button_enter != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + /* Button handlers */ + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, buttons_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx)); + } + + buttons_ctx->btn_prev = false; + buttons_ctx->btn_next = false; + buttons_ctx->btn_enter = false; + + /* Register a touchpad input device */ + lv_indev_drv_init(&buttons_ctx->indev_drv); + buttons_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER; + buttons_ctx->indev_drv.disp = buttons_cfg->disp; + buttons_ctx->indev_drv.read_cb = lvgl_port_navigation_buttons_read; + buttons_ctx->indev_drv.user_data = buttons_ctx; + buttons_ctx->indev_drv.long_press_repeat_time = 300; + indev = lv_indev_drv_register(&buttons_ctx->indev_drv); + +err: + if (ret != ESP_OK) { + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { + if (buttons_ctx->btn[i] != NULL) { + iot_button_delete(buttons_ctx->btn[i]); + } + } + + if (buttons_ctx != NULL) { + free(buttons_ctx); + } + } + + return indev; +} + +esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons) +{ + assert(buttons); + lv_indev_drv_t *indev_drv = buttons->driver; + assert(indev_drv); + lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data; + + /* Remove input device driver */ + lv_indev_delete(buttons); + + if (buttons_ctx) { + free(buttons_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_navigation_buttons_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + static uint32_t last_key = 0; + assert(indev_drv); + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)indev_drv->user_data; + assert(ctx); + + /* Buttons */ + if (ctx->btn_prev) { + data->key = LV_KEY_LEFT; + last_key = LV_KEY_LEFT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_next) { + data->key = LV_KEY_RIGHT; + last_key = LV_KEY_RIGHT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_enter) { + data->key = LV_KEY_ENTER; + last_key = LV_KEY_ENTER; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->key = last_key; + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = true; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = true; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = true; + } + } +} + +static void lvgl_port_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = false; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = false; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = false; + } + } +} diff --git a/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c new file mode 100644 index 00000000..0796073e --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c @@ -0,0 +1,379 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lvgl_port.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +#include "esp_lcd_mipi_dsi.h" +#endif + +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0)) +#define LVGL_PORT_HANDLE_FLUSH_READY 0 +#else +#define LVGL_PORT_HANDLE_FLUSH_READY 1 +#endif + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */ + esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */ + esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */ + lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */ + lv_disp_drv_t disp_drv; /* LVGL display driver */ + lv_color_t *trans_buf; /* Buffer send to driver */ + uint32_t trans_size; /* Maximum size for one transport */ + SemaphoreHandle_t trans_sem; /* Idle transfer mutex */ +} lvgl_port_display_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); +#endif +#endif +static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map); +static void lvgl_port_update_callback(lv_disp_drv_t *drv); +static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg) +{ + esp_err_t ret = ESP_OK; + lv_disp_t *disp = NULL; + lv_color_t *buf1 = NULL; + lv_color_t *buf2 = NULL; + lv_color_t *buf3 = NULL; + SemaphoreHandle_t trans_sem = NULL; + assert(disp_cfg != NULL); + assert(disp_cfg->io_handle != NULL); + assert(disp_cfg->panel_handle != NULL); + assert(disp_cfg->buffer_size > 0); + assert(disp_cfg->hres > 0); + assert(disp_cfg->vres > 0); + + /* Display context */ + lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); + ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); + disp_ctx->io_handle = disp_cfg->io_handle; + disp_ctx->panel_handle = disp_cfg->panel_handle; + disp_ctx->control_handle = disp_cfg->control_handle; + disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy; + disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x; + disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y; + disp_ctx->trans_size = disp_cfg->trans_size; + + uint32_t buff_caps = MALLOC_CAP_DEFAULT; + if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram && (0 == disp_cfg->trans_size)) { + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!"); + } else if (disp_cfg->flags.buff_dma) { + buff_caps = MALLOC_CAP_DMA; + } else if (disp_cfg->flags.buff_spiram) { + buff_caps = MALLOC_CAP_SPIRAM; + } + + if (disp_cfg->trans_size) { + buf3 = heap_caps_malloc(disp_cfg->trans_size * sizeof(lv_color_t), MALLOC_CAP_DMA); + ESP_GOTO_ON_FALSE(buf3, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for buffer(transport) allocation!"); + disp_ctx->trans_buf = buf3; + + trans_sem = xSemaphoreCreateCounting(1, 0); + ESP_GOTO_ON_FALSE(trans_sem, ESP_ERR_NO_MEM, err, TAG, "Failed to create transport counting Semaphore"); + disp_ctx->trans_sem = trans_sem; + } + + /* alloc draw buffers used by LVGL */ + /* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */ + buf1 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color_t), buff_caps); + ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!"); + if (disp_cfg->double_buffer) { + buf2 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color_t), buff_caps); + ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!"); + } + lv_disp_draw_buf_t *disp_buf = malloc(sizeof(lv_disp_draw_buf_t)); + ESP_GOTO_ON_FALSE(disp_buf, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL display buffer allocation!"); + + /* initialize LVGL draw buffers */ + lv_disp_draw_buf_init(disp_buf, buf1, buf2, disp_cfg->buffer_size); + + ESP_LOGD(TAG, "Register display driver to LVGL"); + lv_disp_drv_init(&disp_ctx->disp_drv); + disp_ctx->disp_drv.hor_res = disp_cfg->hres; + disp_ctx->disp_drv.ver_res = disp_cfg->vres; + disp_ctx->disp_drv.flush_cb = lvgl_port_flush_callback; + disp_ctx->disp_drv.draw_buf = disp_buf; + disp_ctx->disp_drv.user_data = disp_ctx; + + disp_ctx->disp_drv.sw_rotate = disp_cfg->flags.sw_rotate; + if (disp_ctx->disp_drv.sw_rotate == false) { + disp_ctx->disp_drv.drv_update_cb = lvgl_port_update_callback; + } + +#if LVGL_PORT_HANDLE_FLUSH_READY + if (disp_cfg->mipi_dsi) { +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) + /* Register done callback */ + const esp_lcd_dpi_panel_event_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_panel_ready_callback, + }; + esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv); +#else + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!"); +#endif + } else { + /* Register done callback */ + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_io_ready_callback, + }; + esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv); + } +#endif + + /* Monochrome display settings */ + if (disp_cfg->monochrome) { + /* When using monochromatic display, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == disp_cfg->buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!"); + + disp_ctx->disp_drv.full_refresh = 1; + disp_ctx->disp_drv.set_px_cb = lvgl_port_pix_monochrome_callback; + } + + disp = lv_disp_drv_register(&disp_ctx->disp_drv); + +err: + if (ret != ESP_OK) { + if (buf1) { + free(buf1); + } + if (buf2) { + free(buf2); + } + if (buf3) { + free(buf3); + } + if (trans_sem) { + vSemaphoreDelete(trans_sem); + } + if (disp_ctx) { + free(disp_ctx); + } + } + + return disp; +} + +esp_err_t lvgl_port_remove_disp(lv_disp_t *disp) +{ + assert(disp); + lv_disp_drv_t *disp_drv = disp->driver; + assert(disp_drv); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)disp_drv->user_data; + + lv_disp_remove(disp); + + if (disp_drv) { + if (disp_drv->draw_buf && disp_drv->draw_buf->buf1) { + free(disp_drv->draw_buf->buf1); + disp_drv->draw_buf->buf1 = NULL; + } + if (disp_drv->draw_buf && disp_drv->draw_buf->buf2) { + free(disp_drv->draw_buf->buf2); + disp_drv->draw_buf->buf2 = NULL; + } + if (disp_drv->draw_buf) { + free(disp_drv->draw_buf); + disp_drv->draw_buf = NULL; + } + } + + free(disp_ctx); + + return ESP_OK; +} + +void lvgl_port_flush_ready(lv_disp_t *disp) +{ + assert(disp); + assert(disp->driver); + lv_disp_flush_ready(disp->driver); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) +{ + BaseType_t taskAwake = pdFALSE; + + lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; + assert(disp_ctx != NULL); + lv_disp_flush_ready(disp_drv); + + if (disp_ctx->trans_size && disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake); + } + + return false; +} +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + BaseType_t taskAwake = pdFALSE; + + lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx; + assert(disp_drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data; + assert(disp_ctx != NULL); + lv_disp_flush_ready(disp_drv); + + if (disp_ctx->trans_size && disp_ctx->trans_sem) { + xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake); + } + + return false; +} +#endif +#endif + +static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map) +{ + assert(drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; + assert(disp_ctx != NULL); + + int x_draw_start; + int x_draw_end; + int y_draw_start; + int y_draw_end; + + int y_start_tmp; + int y_end_tmp; + + int trans_count; + int trans_line; + int max_line; + + const int x_start = area->x1; + const int x_end = area->x2; + const int y_start = area->y1; + const int y_end = area->y2; + const int width = x_end - x_start + 1; + const int height = y_end - y_start + 1; + + lv_color_t *from = color_map; + lv_color_t *to = NULL; + + if (0 == disp_ctx->trans_size) { + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map); + } else { + y_start_tmp = y_start; + max_line = ((disp_ctx->trans_size / width) > height) ? (height) : (disp_ctx->trans_size / width); + trans_count = height / max_line + (height % max_line ? (1) : (0)); + + for (int i = 0; i < trans_count; i++) { + trans_line = (y_end - y_start_tmp + 1) > max_line ? max_line : (y_end - y_start_tmp + 1); + y_end_tmp = (y_end - y_start_tmp + 1) > max_line ? (y_start_tmp + max_line - 1) : y_end; + + to = disp_ctx->trans_buf; + for (int y = 0; y < trans_line; y++) { + for (int x = 0; x < width; x++) { + *(to + y * (width) + x) = *(from + y * (width) + x); + } + } + x_draw_start = x_start; + x_draw_end = x_end; + y_draw_start = y_start_tmp; + y_draw_end = y_end_tmp; + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to); + + from += max_line * width; + y_start_tmp += max_line; + xSemaphoreTake(disp_ctx->trans_sem, portMAX_DELAY); + } + } +} + +static void lvgl_port_update_callback(lv_disp_drv_t *drv) +{ + assert(drv); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data; + assert(disp_ctx != NULL); + esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle); + + /* Solve rotation screen and touch */ + switch (drv->rotated) { + case LV_DISP_ROT_NONE: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + break; + case LV_DISP_ROT_90: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } + break; + case LV_DISP_ROT_180: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + break; + case LV_DISP_ROT_270: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } + break; + } +} + +static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf, lv_coord_t buf_w, lv_coord_t x, lv_coord_t y, lv_color_t color, lv_opa_t opa) +{ + if (drv->rotated == LV_DISP_ROT_90 || drv->rotated == LV_DISP_ROT_270) { + lv_coord_t tmp_x = x; + x = y; + y = tmp_x; + } + + /* Write to the buffer as required for the display. + * It writes only 1-bit for monochrome displays mapped vertically.*/ + buf += drv->hor_res * (y >> 3) + x; + if (lv_color_to1(color)) { + (*buf) &= ~(1 << (y % 8)); + } else { + (*buf) |= (1 << (y % 8)); + } +} diff --git a/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c new file mode 100644 index 00000000..328e405a --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_knob.c @@ -0,0 +1,161 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + knob_handle_t knob_handle; /* Encoder knob handlers */ + button_handle_t btn_handle; /* Encoder button handlers */ + lv_indev_drv_t indev_drv; /* LVGL input device driver */ + bool btn_enter; /* Encoder button enter state */ +} lvgl_port_encoder_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg) +{ + esp_err_t ret = ESP_OK; + lv_indev_t *indev = NULL; + assert(encoder_cfg != NULL); + assert(encoder_cfg->disp != NULL); + + /* Encoder context */ + lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t)); + if (encoder_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for encoder context allocation!"); + return NULL; + } + + /* Encoder_a/b */ + if (encoder_cfg->encoder_a_b != NULL) { + encoder_ctx->knob_handle = iot_knob_create(encoder_cfg->encoder_a_b); + ESP_GOTO_ON_FALSE(encoder_ctx->knob_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for knob create!"); + } + + /* Encoder Enter */ + if (encoder_cfg->encoder_enter != NULL) { + encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter); + ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, encoder_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, encoder_ctx)); + + encoder_ctx->btn_enter = false; + + /* Register a encoder input device */ + lv_indev_drv_init(&encoder_ctx->indev_drv); + encoder_ctx->indev_drv.type = LV_INDEV_TYPE_ENCODER; + encoder_ctx->indev_drv.disp = encoder_cfg->disp; + encoder_ctx->indev_drv.read_cb = lvgl_port_encoder_read; + encoder_ctx->indev_drv.user_data = encoder_ctx; + indev = lv_indev_drv_register(&encoder_ctx->indev_drv); + +err: + if (ret != ESP_OK) { + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + } + return indev; +} + +esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder) +{ + assert(encoder); + lv_indev_drv_t *indev_drv = encoder->driver; + assert(indev_drv); + lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data; + + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + static int32_t last_v = 0; + + assert(indev_drv); + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)indev_drv->user_data; + assert(ctx); + + int32_t invd = iot_knob_get_count_value(ctx->knob_handle); + knob_event_t event = iot_knob_get_event(ctx->knob_handle); + + if (last_v ^ invd) { + last_v = invd; + data->enc_diff = (KNOB_LEFT == event) ? (-1) : ((KNOB_RIGHT == event) ? (1) : (0)); + } else { + data->enc_diff = 0; + } + data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; +} + +static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = true; + } + } +} + +static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = false; + } + } +} diff --git a/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c new file mode 100644 index 00000000..aea12217 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_touch.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lcd_touch.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + esp_lcd_touch_handle_t handle; /* LCD touch IO handle */ + lv_indev_drv_t indev_drv; /* LVGL input device driver */ +} lvgl_port_touch_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg) +{ + assert(touch_cfg != NULL); + assert(touch_cfg->disp != NULL); + assert(touch_cfg->handle != NULL); + + /* Touch context */ + lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t)); + if (touch_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for touch context allocation!"); + return NULL; + } + touch_ctx->handle = touch_cfg->handle; + + /* Register a touchpad input device */ + lv_indev_drv_init(&touch_ctx->indev_drv); + touch_ctx->indev_drv.type = LV_INDEV_TYPE_POINTER; + touch_ctx->indev_drv.disp = touch_cfg->disp; + touch_ctx->indev_drv.read_cb = lvgl_port_touchpad_read; + touch_ctx->indev_drv.user_data = touch_ctx; + return lv_indev_drv_register(&touch_ctx->indev_drv); +} + +esp_err_t lvgl_port_remove_touch(lv_indev_t *touch) +{ + assert(touch); + lv_indev_drv_t *indev_drv = touch->driver; + assert(indev_drv); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data; + + /* Remove input device driver */ + lv_indev_delete(touch); + + if (touch_ctx) { + free(touch_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)indev_drv->user_data; + assert(touch_ctx->handle); + + uint16_t touchpad_x[1] = {0}; + uint16_t touchpad_y[1] = {0}; + uint8_t touchpad_cnt = 0; + + /* Read data from touch controller into memory */ + esp_lcd_touch_read_data(touch_ctx->handle); + + /* Read data from touch controller */ + bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_ctx->handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1); + + if (touchpad_pressed && touchpad_cnt > 0) { + data->point.x = touchpad_x[0]; + data->point.y = touchpad_y[0]; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} diff --git a/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c new file mode 100644 index 00000000..98ff3623 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_usbhid.c @@ -0,0 +1,465 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +#include "usb/hid_host.h" +#include "usb/hid_usage_keyboard.h" +#include "usb/hid_usage_mouse.h" + +/* LVGL image of cursor */ +LV_IMG_DECLARE(img_cursor) + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + QueueHandle_t queue; /* USB HID queue */ + TaskHandle_t task; /* USB HID task */ + bool running; /* USB HID task running */ + struct { + lv_indev_drv_t drv; /* LVGL mouse input device driver */ + uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */ + int16_t x; /* Mouse X coordinate */ + int16_t y; /* Mouse Y coordinate */ + bool left_button; /* Mouse left button state */ + } mouse; + struct { + lv_indev_drv_t drv; /* LVGL keyboard input device driver */ + uint32_t last_key; + bool pressed; + } kb; +} lvgl_port_usb_hid_ctx_t; + +typedef struct { + hid_host_device_handle_t hid_device_handle; + hid_host_driver_event_t event; + void *arg; +} lvgl_port_usb_hid_event_t; + +/******************************************************************************* +* Local variables +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t lvgl_hid_ctx; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void); +static void lvgl_port_usb_hid_task(void *arg); +static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg) +{ + lv_indev_t *indev; + assert(mouse_cfg); + assert(mouse_cfg->disp); + assert(mouse_cfg->disp->driver); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + /* Mouse sensitivity cannot be zero */ + hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity); + /* Default coordinates to screen center */ + hid_ctx->mouse.x = (mouse_cfg->disp->driver->hor_res * hid_ctx->mouse.sensitivity) / 2; + hid_ctx->mouse.y = (mouse_cfg->disp->driver->ver_res * hid_ctx->mouse.sensitivity) / 2; + + /* Register a mouse input device */ + lv_indev_drv_init(&hid_ctx->mouse.drv); + hid_ctx->mouse.drv.type = LV_INDEV_TYPE_POINTER; + hid_ctx->mouse.drv.disp = mouse_cfg->disp; + hid_ctx->mouse.drv.read_cb = lvgl_port_usb_hid_read_mouse; + hid_ctx->mouse.drv.user_data = hid_ctx; + indev = lv_indev_drv_register(&hid_ctx->mouse.drv); + + /* Set image of cursor */ + lv_obj_t *cursor = mouse_cfg->cursor_img; + if (cursor == NULL) { + cursor = lv_img_create(lv_scr_act()); + lv_img_set_src(cursor, &img_cursor); + } + lv_indev_set_cursor(indev, cursor); + + return indev; +} + +lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg) +{ + lv_indev_t *indev; + assert(keyboard_cfg); + assert(keyboard_cfg->disp); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + /* Register a keyboard input device */ + lv_indev_drv_init(&hid_ctx->kb.drv); + hid_ctx->kb.drv.type = LV_INDEV_TYPE_KEYPAD; + hid_ctx->kb.drv.disp = keyboard_cfg->disp; + hid_ctx->kb.drv.read_cb = lvgl_port_usb_hid_read_kb; + hid_ctx->kb.drv.user_data = hid_ctx; + indev = lv_indev_drv_register(&hid_ctx->kb.drv); + + return indev; +} + +esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid) +{ + assert(hid); + lv_indev_drv_t *indev_drv = hid->driver; + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; + + /* Remove input device driver */ + lv_indev_delete(hid); + + /* If all hid input devices are removed, stop task and clean all */ + if (lvgl_hid_ctx.mouse.drv.disp == NULL && lvgl_hid_ctx.kb.drv.disp) { + hid_ctx->running = false; + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void) +{ + esp_err_t ret; + + /* USB HID is already initialized */ + if (lvgl_hid_ctx.task) { + return &lvgl_hid_ctx; + } + + /* USB HID host driver config */ + const hid_host_driver_config_t hid_host_driver_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = lvgl_port_usb_hid_callback, + .callback_arg = &lvgl_hid_ctx, + }; + + ret = hid_host_install(&hid_host_driver_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB HID install failed!"); + return NULL; + } + + lvgl_hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t)); + xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_hid_ctx, 2, &lvgl_hid_ctx.task); + + return &lvgl_hid_ctx; +} + +static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift) +{ + char ret_key = 0; + + const uint8_t keycode2ascii [57][2] = { + {0, 0}, /* HID_KEY_NO_PRESS */ + {0, 0}, /* HID_KEY_ROLLOVER */ + {0, 0}, /* HID_KEY_POST_FAIL */ + {0, 0}, /* HID_KEY_ERROR_UNDEFINED */ + {'a', 'A'}, /* HID_KEY_A */ + {'b', 'B'}, /* HID_KEY_B */ + {'c', 'C'}, /* HID_KEY_C */ + {'d', 'D'}, /* HID_KEY_D */ + {'e', 'E'}, /* HID_KEY_E */ + {'f', 'F'}, /* HID_KEY_F */ + {'g', 'G'}, /* HID_KEY_G */ + {'h', 'H'}, /* HID_KEY_H */ + {'i', 'I'}, /* HID_KEY_I */ + {'j', 'J'}, /* HID_KEY_J */ + {'k', 'K'}, /* HID_KEY_K */ + {'l', 'L'}, /* HID_KEY_L */ + {'m', 'M'}, /* HID_KEY_M */ + {'n', 'N'}, /* HID_KEY_N */ + {'o', 'O'}, /* HID_KEY_O */ + {'p', 'P'}, /* HID_KEY_P */ + {'q', 'Q'}, /* HID_KEY_Q */ + {'r', 'R'}, /* HID_KEY_R */ + {'s', 'S'}, /* HID_KEY_S */ + {'t', 'T'}, /* HID_KEY_T */ + {'u', 'U'}, /* HID_KEY_U */ + {'v', 'V'}, /* HID_KEY_V */ + {'w', 'W'}, /* HID_KEY_W */ + {'x', 'X'}, /* HID_KEY_X */ + {'y', 'Y'}, /* HID_KEY_Y */ + {'z', 'Z'}, /* HID_KEY_Z */ + {'1', '!'}, /* HID_KEY_1 */ + {'2', '@'}, /* HID_KEY_2 */ + {'3', '#'}, /* HID_KEY_3 */ + {'4', '$'}, /* HID_KEY_4 */ + {'5', '%'}, /* HID_KEY_5 */ + {'6', '^'}, /* HID_KEY_6 */ + {'7', '&'}, /* HID_KEY_7 */ + {'8', '*'}, /* HID_KEY_8 */ + {'9', '('}, /* HID_KEY_9 */ + {'0', ')'}, /* HID_KEY_0 */ + {'\r', '\r'}, /* HID_KEY_ENTER */ + {0, 0}, /* HID_KEY_ESC */ + {'\b', 0}, /* HID_KEY_DEL */ + {0, 0}, /* HID_KEY_TAB */ + {' ', ' '}, /* HID_KEY_SPACE */ + {'-', '_'}, /* HID_KEY_MINUS */ + {'=', '+'}, /* HID_KEY_EQUAL */ + {'[', '{'}, /* HID_KEY_OPEN_BRACKET */ + {']', '}'}, /* HID_KEY_CLOSE_BRACKET */ + {'\\', '|'}, /* HID_KEY_BACK_SLASH */ + {'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH + {';', ':'}, /* HID_KEY_COLON */ + {'\'', '"'}, /* HID_KEY_QUOTE */ + {'`', '~'}, /* HID_KEY_TILDE */ + {',', '<'}, /* HID_KEY_LESS */ + {'.', '>'}, /* HID_KEY_GREATER */ + {'/', '?'} /* HID_KEY_SLASH */ + }; + + if (shift > 1) { + shift = 1; + } + + if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) { + ret_key = keycode2ascii[key][shift]; + } + + return ret_key; +} + +static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, const hid_host_interface_event_t event, void *arg) +{ + hid_host_dev_params_t dev; + hid_host_device_get_params(hid_device_handle, &dev); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + uint8_t data[10]; + unsigned int data_length = 0; + + assert(hid_ctx != NULL); + + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length); + if (dev.proto == HID_PROTOCOL_KEYBOARD) { + hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data; + if (data_length < sizeof(hid_keyboard_input_report_boot_t)) { + return; + } + for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { + if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) { + char key = 0; + + /* LVGL special keys */ + if (keyboard->key[i] == HID_KEY_TAB) { + if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) { + key = LV_KEY_PREV; + } else { + key = LV_KEY_NEXT; + } + } else if (keyboard->key[i] == HID_KEY_RIGHT) { + key = LV_KEY_RIGHT; + } else if (keyboard->key[i] == HID_KEY_LEFT) { + key = LV_KEY_LEFT; + } else if (keyboard->key[i] == HID_KEY_DOWN) { + key = LV_KEY_DOWN; + } else if (keyboard->key[i] == HID_KEY_UP) { + key = LV_KEY_UP; + } else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) { + key = LV_KEY_ENTER; + } else if (keyboard->key[i] == HID_KEY_DELETE) { + key = LV_KEY_DEL; + } else if (keyboard->key[i] == HID_KEY_HOME) { + key = LV_KEY_HOME; + } else if (keyboard->key[i] == HID_KEY_END) { + key = LV_KEY_END; + } else { + /* Get ASCII char */ + key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift)); + } + + if (key == 0) { + ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]); + } + hid_ctx->kb.last_key = key; + hid_ctx->kb.pressed = true; + } + } + + } else if (dev.proto == HID_PROTOCOL_MOUSE) { + hid_mouse_input_report_boot_t *mouse = (hid_mouse_input_report_boot_t *)data; + if (data_length < sizeof(hid_mouse_input_report_boot_t)) { + break; + } + hid_ctx->mouse.left_button = mouse->buttons.button1; + hid_ctx->mouse.x += mouse->x_displacement; + hid_ctx->mouse.y += mouse->y_displacement; + } + break; + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + hid_host_device_close(hid_device_handle); + break; + default: + break; + } +} + +static void lvgl_port_usb_hid_task(void *arg) +{ + hid_host_dev_params_t dev; + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg; + hid_host_device_handle_t hid_device_handle = NULL; + lvgl_port_usb_hid_event_t msg; + + assert(ctx); + + ctx->running = true; + + while (ctx->running) { + if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) { + hid_device_handle = msg.hid_device_handle; + hid_host_device_get_params(hid_device_handle, &dev); + + switch (msg.event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + /* Handle mouse or keyboard */ + if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) { + const hid_host_device_config_t dev_config = { + .callback = lvgl_port_usb_hid_host_interface_callback, + .callback_arg = ctx + }; + + ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) ); + ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) ); + ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) ); + ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) ); + } + break; + default: + break; + } + } + } + + xQueueReset(ctx->queue); + vQueueDelete(ctx->queue); + + hid_host_uninstall(); + + memset(&lvgl_hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t)); + + vTaskDelete(NULL); +} + +static void lvgl_port_usb_hid_read_mouse(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + int16_t width = 0; + int16_t height = 0; + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; + assert(ctx); + + if (indev_drv->disp->driver->rotated == LV_DISP_ROT_NONE || indev_drv->disp->driver->rotated == LV_DISP_ROT_180) { + width = indev_drv->disp->driver->hor_res; + height = indev_drv->disp->driver->ver_res; + } else { + width = indev_drv->disp->driver->ver_res; + height = indev_drv->disp->driver->hor_res; + } + + /* Screen borders */ + if (ctx->mouse.x < 0) { + ctx->mouse.x = 0; + } else if (ctx->mouse.x > width * ctx->mouse.sensitivity) { + ctx->mouse.x = width * ctx->mouse.sensitivity; + } + if (ctx->mouse.y < 0) { + ctx->mouse.y = 0; + } else if (ctx->mouse.y > height * ctx->mouse.sensitivity) { + ctx->mouse.y = height * ctx->mouse.sensitivity; + } + + /* Get coordinates by rotation with sensitivity */ + switch (indev_drv->disp->driver->rotated) { + case LV_DISP_ROT_NONE: + data->point.x = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISP_ROT_90: + data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISP_ROT_180: + data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISP_ROT_270: + data->point.y = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + } + + if (ctx->mouse.left_button) { + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_usb_hid_read_kb(lv_indev_drv_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)indev_drv->user_data; + assert(ctx); + + data->key = ctx->kb.last_key; + if (ctx->kb.pressed) { + data->state = LV_INDEV_STATE_PRESSED; + ctx->kb.pressed = false; + } else { + data->state = LV_INDEV_STATE_RELEASED; + ctx->kb.last_key = 0; + } +} + +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg) +{ + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + + const lvgl_port_usb_hid_event_t msg = { + .hid_device_handle = hid_device_handle, + .event = event, + .arg = arg + }; + + xQueueSend(hid_ctx->queue, &msg, 0); +} diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c new file mode 100644 index 00000000..4e65e5cb --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port.c @@ -0,0 +1,198 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_system.h" +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_lvgl_port.h" +#include "lvgl.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct lvgl_port_ctx_s { + SemaphoreHandle_t lvgl_mux; + esp_timer_handle_t tick_timer; + bool running; + int task_max_sleep_ms; + int timer_period_ms; +} lvgl_port_ctx_t; + +/******************************************************************************* +* Local variables +*******************************************************************************/ +static lvgl_port_ctx_t lvgl_port_ctx; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ +static void lvgl_port_task(void *arg); +static esp_err_t lvgl_port_tick_init(void); +static void lvgl_port_task_deinit(void); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(cfg, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(cfg->task_affinity < (configNUM_CORES), ESP_ERR_INVALID_ARG, err, TAG, "Bad core number for task! Maximum core number is %d", (configNUM_CORES - 1)); + + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); + + /* LVGL init */ + lv_init(); + /* Tick init */ + lvgl_port_ctx.timer_period_ms = cfg->timer_period_ms; + ESP_RETURN_ON_ERROR(lvgl_port_tick_init(), TAG, ""); + /* Create task */ + lvgl_port_ctx.task_max_sleep_ms = cfg->task_max_sleep_ms; + if (lvgl_port_ctx.task_max_sleep_ms == 0) { + lvgl_port_ctx.task_max_sleep_ms = 500; + } + lvgl_port_ctx.lvgl_mux = xSemaphoreCreateRecursiveMutex(); + ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL mutex fail!"); + + BaseType_t res; + if (cfg->task_affinity < 0) { + res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL); + } else { + res = xTaskCreatePinnedToCore(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL, cfg->task_affinity); + } + ESP_GOTO_ON_FALSE(res == pdPASS, ESP_FAIL, err, TAG, "Create LVGL task fail!"); + +err: + if (ret != ESP_OK) { + lvgl_port_deinit(); + } + + return ret; +} + +esp_err_t lvgl_port_resume(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(true); + ret = esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); + } + + return ret; +} + +esp_err_t lvgl_port_stop(void) +{ + esp_err_t ret = ESP_ERR_INVALID_STATE; + + if (lvgl_port_ctx.tick_timer != NULL) { + lv_timer_enable(false); + ret = esp_timer_stop(lvgl_port_ctx.tick_timer); + } + + return ret; +} + +esp_err_t lvgl_port_deinit(void) +{ + /* Stop and delete timer */ + if (lvgl_port_ctx.tick_timer != NULL) { + esp_timer_stop(lvgl_port_ctx.tick_timer); + esp_timer_delete(lvgl_port_ctx.tick_timer); + lvgl_port_ctx.tick_timer = NULL; + } + + /* Stop running task */ + if (lvgl_port_ctx.running) { + lvgl_port_ctx.running = false; + } else { + lvgl_port_task_deinit(); + } + + return ESP_OK; +} + +bool lvgl_port_lock(uint32_t timeout_ms) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + + const TickType_t timeout_ticks = (timeout_ms == 0) ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + return xSemaphoreTakeRecursive(lvgl_port_ctx.lvgl_mux, timeout_ticks) == pdTRUE; +} + +void lvgl_port_unlock(void) +{ + assert(lvgl_port_ctx.lvgl_mux && "lvgl_port_init must be called first"); + xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_task(void *arg) +{ + uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + + ESP_LOGI(TAG, "Starting LVGL task"); + lvgl_port_ctx.running = true; + while (lvgl_port_ctx.running) { + if (lv_display_get_default() && lvgl_port_lock(0)) { + task_delay_ms = lv_timer_handler(); + lvgl_port_unlock(); + } + if ((task_delay_ms > lvgl_port_ctx.task_max_sleep_ms) || (1 == task_delay_ms)) { + task_delay_ms = lvgl_port_ctx.task_max_sleep_ms; + } else if (task_delay_ms < 1) { + task_delay_ms = 1; + } + vTaskDelay(pdMS_TO_TICKS(task_delay_ms)); + } + + lvgl_port_task_deinit(); + + /* Close task */ + vTaskDelete( NULL ); +} + +static void lvgl_port_task_deinit(void) +{ + if (lvgl_port_ctx.lvgl_mux) { + vSemaphoreDelete(lvgl_port_ctx.lvgl_mux); + } + memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx)); +#if LV_ENABLE_GC || !LV_MEM_CUSTOM + /* Deinitialize LVGL */ + lv_deinit(); +#endif +} + +static void lvgl_port_tick_increment(void *arg) +{ + /* Tell LVGL how many milliseconds have elapsed */ + lv_tick_inc(lvgl_port_ctx.timer_period_ms); +} + +static esp_err_t lvgl_port_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 = &lvgl_port_tick_increment, + .name = "LVGL tick", + }; + ESP_RETURN_ON_ERROR(esp_timer_create(&lvgl_tick_timer_args, &lvgl_port_ctx.tick_timer), TAG, "Creating LVGL timer filed!"); + return esp_timer_start_periodic(lvgl_port_ctx.tick_timer, lvgl_port_ctx.timer_period_ms * 1000); +} diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c new file mode 100644 index 00000000..ad14ed5c --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_button.c @@ -0,0 +1,203 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef enum { + LVGL_PORT_NAV_BTN_PREV, + LVGL_PORT_NAV_BTN_NEXT, + LVGL_PORT_NAV_BTN_ENTER, + LVGL_PORT_NAV_BTN_CNT, +} lvgl_port_nav_btns_t; + +typedef struct { + button_handle_t btn[LVGL_PORT_NAV_BTN_CNT]; /* Button handlers */ + lv_indev_t *indev; /* LVGL input device driver */ + bool btn_prev; /* Button prev state */ + bool btn_next; /* Button next state */ + bool btn_enter; /* Button enter state */ +} lvgl_port_nav_btns_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_navigation_buttons_read(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_btn_up_handler(void *arg, void *arg2); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *buttons_cfg) +{ + lv_indev_t *indev; + esp_err_t ret = ESP_OK; + assert(buttons_cfg != NULL); + assert(buttons_cfg->disp != NULL); + + /* Touch context */ + lvgl_port_nav_btns_ctx_t *buttons_ctx = malloc(sizeof(lvgl_port_nav_btns_ctx_t)); + if (buttons_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for buttons context allocation!"); + return NULL; + } + + /* Previous button */ + if (buttons_cfg->button_prev != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV] = iot_button_create(buttons_cfg->button_prev); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_PREV], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + /* Next button */ + if (buttons_cfg->button_next != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT] = iot_button_create(buttons_cfg->button_next); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_NEXT], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + /* Enter button */ + if (buttons_cfg->button_enter != NULL) { + buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER] = iot_button_create(buttons_cfg->button_enter); + ESP_GOTO_ON_FALSE(buttons_ctx->btn[LVGL_PORT_NAV_BTN_ENTER], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + /* Button handlers */ + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_DOWN, lvgl_port_btn_down_handler, buttons_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(buttons_ctx->btn[i], BUTTON_PRESS_UP, lvgl_port_btn_up_handler, buttons_ctx)); + } + + buttons_ctx->btn_prev = false; + buttons_ctx->btn_next = false; + buttons_ctx->btn_enter = false; + + lvgl_port_lock(0); + /* Register a touchpad input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER); + lv_indev_set_read_cb(indev, lvgl_port_navigation_buttons_read); + lv_indev_set_disp(indev, buttons_cfg->disp); + lv_indev_set_user_data(indev, buttons_ctx); + //buttons_ctx->indev->long_press_repeat_time = 300; + buttons_ctx->indev = indev; + lvgl_port_unlock(); + + return indev; + +err: + if (ret != ESP_OK) { + for (int i = 0; i < LVGL_PORT_NAV_BTN_CNT; i++) { + if (buttons_ctx->btn[i] != NULL) { + iot_button_delete(buttons_ctx->btn[i]); + } + } + + if (buttons_ctx != NULL) { + free(buttons_ctx); + } + } + + return NULL; +} + +esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons) +{ + assert(buttons); + lvgl_port_nav_btns_ctx_t *buttons_ctx = (lvgl_port_nav_btns_ctx_t *)lv_indev_get_user_data(buttons); + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(buttons); + lvgl_port_unlock(); + + if (buttons_ctx) { + free(buttons_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + + +static void lvgl_port_navigation_buttons_read(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + static uint32_t last_key = 0; + + assert(indev_drv); + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *)lv_indev_get_user_data(indev_drv); + assert(ctx); + + /* Buttons */ + if (ctx->btn_prev) { + data->key = LV_KEY_LEFT; + last_key = LV_KEY_LEFT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_next) { + data->key = LV_KEY_RIGHT; + last_key = LV_KEY_RIGHT; + data->state = LV_INDEV_STATE_PRESSED; + } else if (ctx->btn_enter) { + data->key = LV_KEY_ENTER; + last_key = LV_KEY_ENTER; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->key = last_key; + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = true; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = true; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = true; + } + } +} + +static void lvgl_port_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_nav_btns_ctx_t *ctx = (lvgl_port_nav_btns_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* PREV */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_PREV]) { + ctx->btn_prev = false; + } + /* NEXT */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_NEXT]) { + ctx->btn_next = false; + } + /* ENTER */ + if (button == ctx->btn[LVGL_PORT_NAV_BTN_ENTER]) { + ctx->btn_enter = false; + } + } +} diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c new file mode 100644 index 00000000..84bdeffa --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -0,0 +1,322 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_heap_caps.h" +#include "esp_lcd_panel_io.h" +#include "esp_lcd_panel_ops.h" +#include "esp_lvgl_port.h" + +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +#include "esp_lcd_mipi_dsi.h" +#endif + +#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0)) +#define LVGL_PORT_HANDLE_FLUSH_READY 0 +#else +#define LVGL_PORT_HANDLE_FLUSH_READY 1 +#endif + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */ + esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */ + esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */ + lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */ + lv_color16_t *draw_buffs[2]; /* Display draw buffers */ + lv_display_t *disp_drv; /* LVGL display driver */ + struct { + unsigned int monochrome: 1; /* True, if display is monochrome and using 1bit for 1px */ + unsigned int swap_bytes: 1; /* Swap bytes in RGB656 (16-bit) before send to LCD driver */ + } flags; +} lvgl_port_display_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx); +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx); +#endif +#endif +static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map); +static void lvgl_port_disp_size_update_callback(lv_event_t *e); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg) +{ + esp_err_t ret = ESP_OK; + lv_display_t *disp = NULL; + lv_color16_t *buf1 = NULL; + lv_color16_t *buf2 = NULL; + assert(disp_cfg != NULL); + assert(disp_cfg->io_handle != NULL); + assert(disp_cfg->panel_handle != NULL); + assert(disp_cfg->buffer_size > 0); + assert(disp_cfg->hres > 0); + assert(disp_cfg->vres > 0); + + /* Display context */ + lvgl_port_display_ctx_t *disp_ctx = malloc(sizeof(lvgl_port_display_ctx_t)); + ESP_GOTO_ON_FALSE(disp_ctx, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for display context allocation!"); + disp_ctx->io_handle = disp_cfg->io_handle; + disp_ctx->panel_handle = disp_cfg->panel_handle; + disp_ctx->control_handle = disp_cfg->control_handle; + disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy; + disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x; + disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y; + disp_ctx->flags.swap_bytes = disp_cfg->flags.swap_bytes; + + uint32_t buff_caps = MALLOC_CAP_DEFAULT; + if (disp_cfg->flags.buff_dma && disp_cfg->flags.buff_spiram) { + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "Alloc DMA capable buffer in SPIRAM is not supported!"); + } else if (disp_cfg->flags.buff_dma) { + buff_caps = MALLOC_CAP_DMA; + } else if (disp_cfg->flags.buff_spiram) { + buff_caps = MALLOC_CAP_SPIRAM; + } + + /* alloc draw buffers used by LVGL */ + /* it's recommended to choose the size of the draw buffer(s) to be at least 1/10 screen sized */ + buf1 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color16_t), buff_caps); + ESP_GOTO_ON_FALSE(buf1, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf1) allocation!"); + if (disp_cfg->double_buffer) { + buf2 = heap_caps_malloc(disp_cfg->buffer_size * sizeof(lv_color16_t), buff_caps); + ESP_GOTO_ON_FALSE(buf2, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (buf2) allocation!"); + } + + disp_ctx->draw_buffs[0] = buf1; + disp_ctx->draw_buffs[1] = buf2; + + lvgl_port_lock(0); + disp = lv_display_create(disp_cfg->hres, disp_cfg->vres); + + /* Monochrome display settings */ + if (disp_cfg->monochrome) { + /* When using monochromatic display, there must be used full bufer! */ + ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == disp_cfg->buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!"); + + disp_ctx->flags.monochrome = 1; + + //lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565); + lv_display_set_buffers(disp, buf1, buf2, disp_cfg->buffer_size * sizeof(lv_color16_t), LV_DISPLAY_RENDER_MODE_FULL); + } else { + lv_display_set_buffers(disp, buf1, buf2, disp_cfg->buffer_size * sizeof(lv_color16_t), LV_DISPLAY_RENDER_MODE_PARTIAL); + } + + + lv_display_set_flush_cb(disp, lvgl_port_flush_callback); + lv_display_add_event_cb(disp, lvgl_port_disp_size_update_callback, LV_EVENT_RESOLUTION_CHANGED, disp_ctx); + + lv_display_set_user_data(disp, disp_ctx); + disp_ctx->disp_drv = disp; + +#if LVGL_PORT_HANDLE_FLUSH_READY + if (disp_cfg->mipi_dsi) { +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) + /* Register done callback (MIPI-DSI) */ + const esp_lcd_dpi_panel_event_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_panel_ready_callback, + }; + esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, disp_ctx->disp_drv); +#else + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!"); +#endif + } else { + /* Register done callback */ + const esp_lcd_panel_io_callbacks_t cbs = { + .on_color_trans_done = lvgl_port_flush_io_ready_callback, + }; + esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, disp_ctx->disp_drv); + } +#endif + + lvgl_port_unlock(); + +err: + if (ret != ESP_OK) { + if (buf1) { + free(buf1); + } + if (buf2) { + free(buf2); + } + if (disp_ctx) { + free(disp_ctx); + } + } + + return disp; +} + +esp_err_t lvgl_port_remove_disp(lv_display_t *disp) +{ + assert(disp); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(disp); + + lvgl_port_lock(0); + lv_disp_remove(disp); + lvgl_port_unlock(); + + if (disp_ctx->draw_buffs[0]) { + free(disp_ctx->draw_buffs[0]); + } + + if (disp_ctx->draw_buffs[1]) { + free(disp_ctx->draw_buffs[1]); + } + + free(disp_ctx); + + return ESP_OK; +} + +void lvgl_port_flush_ready(lv_display_t *disp) +{ + assert(disp); + lv_disp_flush_ready(disp); +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +#if LVGL_PORT_HANDLE_FLUSH_READY +static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) +{ + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + lv_disp_flush_ready(disp_drv); + return false; +} +#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0)) +static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx) +{ + lv_display_t *disp_drv = (lv_display_t *)user_ctx; + assert(disp_drv != NULL); + lv_disp_flush_ready(disp_drv); + return false; +} +#endif +#endif + +static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_t *color_map) +{ + uint8_t *buf = color_map; + lv_color16_t *color = (lv_color16_t *)color_map; + uint16_t hor_res = lv_display_get_physical_horizontal_resolution(display); + uint16_t ver_res = lv_display_get_physical_vertical_resolution(display); + uint16_t res = hor_res; + bool swap_xy = (lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_90 || lv_display_get_rotation(display) == LV_DISPLAY_ROTATION_270); + + int x1 = area->x1; + int x2 = area->x2; + int y1 = area->y1; + int y2 = area->y2; + + int out_x, out_y; + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + bool chroma_color = (color[hor_res * y + x].blue > 16); + + if (swap_xy) { + out_x = y; + out_y = x; + res = ver_res; + } else { + out_x = x; + out_y = y; + res = hor_res; + } + + /* Write to the buffer as required for the display. + * It writes only 1-bit for monochrome displays mapped vertically.*/ + buf = color_map + res * (out_y >> 3) + (out_x); + if (chroma_color) { + (*buf) &= ~(1 << (out_y % 8)); + } else { + (*buf) |= (1 << (out_y % 8)); + } + } + } +} + +static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) +{ + assert(drv != NULL); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(drv); + assert(disp_ctx != NULL); + + //TODO: try to use SPI_SWAP_DATA_RX from https://docs.espressif.com/projects/esp-idf/en/v5.1/esp32s3/api-reference/peripherals/spi_master.html#c.SPI_SWAP_DATA_TX + if (disp_ctx->flags.swap_bytes) { + size_t len = lv_area_get_size(area); + lv_draw_sw_rgb565_swap(color_map, len); + } + + /* Transfor data in buffer for monochromatic screen */ + if (disp_ctx->flags.monochrome) { + _lvgl_port_transform_monochrome(drv, area, color_map); + } + + const int offsetx1 = area->x1; + const int offsetx2 = area->x2; + const int offsety1 = area->y1; + const int offsety2 = area->y2; + // copy a buffer's content to a specific area of the display + esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, offsetx1, offsety1, offsetx2 + 1, offsety2 + 1, color_map); +} + +static void lvgl_port_disp_size_update_callback(lv_event_t *e) +{ + assert(e); + lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)e->user_data; + assert(disp_ctx != NULL); + esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle); + + /* Solve rotation screen and touch */ + switch (lv_display_get_rotation(disp_ctx->disp_drv)) { + case LV_DISPLAY_ROTATION_0: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + break; + case LV_DISPLAY_ROTATION_90: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } + break; + case LV_DISPLAY_ROTATION_180: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy); + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + break; + case LV_DISPLAY_ROTATION_270: + /* Rotate LCD display */ + esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy); + if (disp_ctx->rotation.swap_xy) { + esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y); + } else { + esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y); + } + break; + } +} diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c new file mode 100644 index 00000000..6d504d7b --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c @@ -0,0 +1,167 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + knob_handle_t knob_handle; /* Encoder knob handlers */ + button_handle_t btn_handle; /* Encoder button handlers */ + lv_indev_t *indev; /* LVGL input device driver */ + bool btn_enter; /* Encoder button enter state */ +} lvgl_port_encoder_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2); +static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg) +{ + lv_indev_t *indev; + esp_err_t ret = ESP_OK; + assert(encoder_cfg != NULL); + assert(encoder_cfg->disp != NULL); + + /* Encoder context */ + lvgl_port_encoder_ctx_t *encoder_ctx = malloc(sizeof(lvgl_port_encoder_ctx_t)); + if (encoder_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for encoder context allocation!"); + return NULL; + } + + /* Encoder_a/b */ + if (encoder_cfg->encoder_a_b != NULL) { + encoder_ctx->knob_handle = iot_knob_create(encoder_cfg->encoder_a_b); + ESP_GOTO_ON_FALSE(encoder_ctx->knob_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for knob create!"); + } + + /* Encoder Enter */ + if (encoder_cfg->encoder_enter != NULL) { + encoder_ctx->btn_handle = iot_button_create(encoder_cfg->encoder_enter); + ESP_GOTO_ON_FALSE(encoder_ctx->btn_handle, ESP_ERR_NO_MEM, err, TAG, "Not enough memory for button create!"); + } + + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_DOWN, lvgl_port_encoder_btn_down_handler, encoder_ctx)); + ESP_ERROR_CHECK(iot_button_register_cb(encoder_ctx->btn_handle, BUTTON_PRESS_UP, lvgl_port_encoder_btn_up_handler, encoder_ctx)); + + encoder_ctx->btn_enter = false; + + lvgl_port_lock(0); + /* Register a encoder input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER); + lv_indev_set_read_cb(indev, lvgl_port_encoder_read); + lv_indev_set_disp(indev, encoder_cfg->disp); + lv_indev_set_user_data(indev, encoder_ctx); + encoder_ctx->indev = indev; + lvgl_port_unlock(); + + return indev; + +err: + if (ret != ESP_OK) { + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + } + return NULL; +} + +esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder) +{ + assert(encoder); + lvgl_port_encoder_ctx_t *encoder_ctx = (lvgl_port_encoder_ctx_t *)lv_indev_get_user_data(encoder); + + if (encoder_ctx->knob_handle != NULL) { + iot_knob_delete(encoder_ctx->knob_handle); + } + + if (encoder_ctx->btn_handle != NULL) { + iot_button_delete(encoder_ctx->btn_handle); + } + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(encoder); + lvgl_port_unlock(); + + if (encoder_ctx != NULL) { + free(encoder_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_encoder_read(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + static int32_t last_v = 0; + assert(indev_drv); + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *)lv_indev_get_user_data(indev_drv); + assert(ctx); + + int32_t invd = iot_knob_get_count_value(ctx->knob_handle); + knob_event_t event = iot_knob_get_event(ctx->knob_handle); + + if (last_v ^ invd) { + last_v = invd; + data->enc_diff = (KNOB_LEFT == event) ? (-1) : ((KNOB_RIGHT == event) ? (1) : (0)); + } else { + data->enc_diff = 0; + } + data->state = (true == ctx->btn_enter) ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED; +} + +static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = true; + } + } +} + +static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2) +{ + lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2; + button_handle_t button = (button_handle_t)arg; + if (ctx && button) { + /* ENTER */ + if (button == ctx->btn_handle) { + ctx->btn_enter = false; + } + } +} diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c new file mode 100644 index 00000000..6aac2fb6 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_touch.c @@ -0,0 +1,107 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lcd_touch.h" +#include "esp_lvgl_port.h" + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + esp_lcd_touch_handle_t handle; /* LCD touch IO handle */ + lv_indev_t *indev; /* LVGL input device driver */ +} lvgl_port_touch_ctx_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data); + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg) +{ + lv_indev_t *indev; + assert(touch_cfg != NULL); + assert(touch_cfg->disp != NULL); + assert(touch_cfg->handle != NULL); + + /* Touch context */ + lvgl_port_touch_ctx_t *touch_ctx = malloc(sizeof(lvgl_port_touch_ctx_t)); + if (touch_ctx == NULL) { + ESP_LOGE(TAG, "Not enough memory for touch context allocation!"); + return NULL; + } + touch_ctx->handle = touch_cfg->handle; + + lvgl_port_lock(0); + /* Register a touchpad input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(indev, lvgl_port_touchpad_read); + lv_indev_set_disp(indev, touch_cfg->disp); + lv_indev_set_user_data(indev, touch_ctx); + touch_ctx->indev = indev; + lvgl_port_unlock(); + + return indev; +} + +esp_err_t lvgl_port_remove_touch(lv_indev_t *touch) +{ + assert(touch); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)lv_indev_get_user_data(touch); + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(touch); + lvgl_port_unlock(); + + if (touch_ctx) { + free(touch_ctx); + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *)lv_indev_get_user_data(indev_drv); + assert(touch_ctx); + assert(touch_ctx->handle); + + uint16_t touchpad_x[1] = {0}; + uint16_t touchpad_y[1] = {0}; + uint8_t touchpad_cnt = 0; + + /* Read data from touch controller into memory */ + esp_lcd_touch_read_data(touch_ctx->handle); + + /* Read data from touch controller */ + bool touchpad_pressed = esp_lcd_touch_get_coordinates(touch_ctx->handle, touchpad_x, touchpad_y, NULL, &touchpad_cnt, 1); + + if (touchpad_pressed && touchpad_cnt > 0) { + data->point.x = touchpad_x[0]; + data->point.y = touchpad_y[0]; + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} diff --git a/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c new file mode 100644 index 00000000..4cb654da --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c @@ -0,0 +1,482 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_log.h" +#include "esp_err.h" +#include "esp_check.h" +#include "esp_lvgl_port.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" + +#include "usb/hid_host.h" +#include "usb/hid_usage_keyboard.h" +#include "usb/hid_usage_mouse.h" + +/* LVGL image of cursor */ +LV_IMG_DECLARE(img_cursor) + +static const char *TAG = "LVGL"; + +/******************************************************************************* +* Types definitions +*******************************************************************************/ + +typedef struct { + QueueHandle_t queue; /* USB HID queue */ + TaskHandle_t task; /* USB HID task */ + bool running; /* USB HID task running */ + struct { + lv_indev_t *indev; /* LVGL mouse input device driver */ + uint8_t sensitivity; /* Mouse sensitivity (cannot be zero) */ + int16_t x; /* Mouse X coordinate */ + int16_t y; /* Mouse Y coordinate */ + bool left_button; /* Mouse left button state */ + } mouse; + struct { + lv_indev_t *indev; /* LVGL keyboard input device driver */ + uint32_t last_key; + bool pressed; + } kb; +} lvgl_port_usb_hid_ctx_t; + +typedef struct { + hid_host_device_handle_t hid_device_handle; + hid_host_driver_event_t event; + void *arg; +} lvgl_port_usb_hid_event_t; + +/******************************************************************************* +* Function definitions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void); +static void lvgl_port_usb_hid_task(void *arg); +static void lvgl_port_usb_hid_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_read_kb(lv_indev_t *indev_drv, lv_indev_data_t *data); +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg); + +/******************************************************************************* +* Local variables +*******************************************************************************/ +static lvgl_port_usb_hid_ctx_t lvgl_hid_ctx; + +/******************************************************************************* +* Public API functions +*******************************************************************************/ + +lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *mouse_cfg) +{ + lv_indev_t *indev; + assert(mouse_cfg); + assert(mouse_cfg->disp); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + /* Mouse sensitivity cannot be zero */ + hid_ctx->mouse.sensitivity = (mouse_cfg->sensitivity == 0 ? 1 : mouse_cfg->sensitivity); + + int32_t ver_res = lv_display_get_vertical_resolution(mouse_cfg->disp); + int32_t hor_res = lv_display_get_physical_horizontal_resolution(mouse_cfg->disp); + + /* Default coordinates to screen center */ + hid_ctx->mouse.x = (hor_res * hid_ctx->mouse.sensitivity) / 2; + hid_ctx->mouse.y = (ver_res * hid_ctx->mouse.sensitivity) / 2; + + lvgl_port_lock(0); + /* Register a mouse input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(indev, lvgl_port_usb_hid_read_mouse); + lv_indev_set_disp(indev, mouse_cfg->disp); + lv_indev_set_user_data(indev, hid_ctx); + hid_ctx->mouse.indev = indev; + lvgl_port_unlock(); + + /* Set image of cursor */ + lv_obj_t *cursor = mouse_cfg->cursor_img; + if (cursor == NULL) { + cursor = lv_img_create(lv_scr_act()); + lv_img_set_src(cursor, &img_cursor); + } + lv_indev_set_cursor(indev, cursor); + + return indev; +} + +lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cfg_t *keyboard_cfg) +{ + lv_indev_t *indev; + assert(keyboard_cfg); + assert(keyboard_cfg->disp); + + /* Initialize USB HID */ + lvgl_port_usb_hid_ctx_t *hid_ctx = lvgl_port_hid_init(); + if (hid_ctx == NULL) { + return NULL; + } + + lvgl_port_lock(0); + /* Register a mouse input device */ + indev = lv_indev_create(); + lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD); + lv_indev_set_read_cb(indev, lvgl_port_usb_hid_read_kb); + lv_indev_set_disp(indev, keyboard_cfg->disp); + lv_indev_set_user_data(indev, hid_ctx); + hid_ctx->kb.indev = indev; + lvgl_port_unlock(); + + return indev; +} + +esp_err_t lvgl_port_remove_usb_hid_input(lv_indev_t *hid) +{ + assert(hid); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_user_data(hid); + + lvgl_port_lock(0); + /* Remove input device driver */ + lv_indev_delete(hid); + lvgl_port_unlock(); + + if (lvgl_hid_ctx.mouse.indev == hid) { + lvgl_hid_ctx.mouse.indev = NULL; + } else if (lvgl_hid_ctx.kb.indev == hid) { + lvgl_hid_ctx.kb.indev = NULL; + } + + /* If all hid input devices are removed, stop task and clean all */ + if (lvgl_hid_ctx.mouse.indev == NULL && lvgl_hid_ctx.kb.indev) { + hid_ctx->running = false; + } + + return ESP_OK; +} + +/******************************************************************************* +* Private functions +*******************************************************************************/ + +static lvgl_port_usb_hid_ctx_t *lvgl_port_hid_init(void) +{ + esp_err_t ret; + + /* USB HID is already initialized */ + if (lvgl_hid_ctx.task) { + return &lvgl_hid_ctx; + } + + /* USB HID host driver config */ + const hid_host_driver_config_t hid_host_driver_config = { + .create_background_task = true, + .task_priority = 5, + .stack_size = 4096, + .core_id = 0, + .callback = lvgl_port_usb_hid_callback, + .callback_arg = &lvgl_hid_ctx, + }; + + ret = hid_host_install(&hid_host_driver_config); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "USB HID install failed!"); + return NULL; + } + + lvgl_hid_ctx.queue = xQueueCreate(10, sizeof(lvgl_port_usb_hid_event_t)); + xTaskCreate(&lvgl_port_usb_hid_task, "hid_task", 4 * 1024, &lvgl_hid_ctx, 2, &lvgl_hid_ctx.task); + + return &lvgl_hid_ctx; +} + +static char usb_hid_get_keyboard_char(uint8_t key, uint8_t shift) +{ + char ret_key = 0; + + const uint8_t keycode2ascii [57][2] = { + {0, 0}, /* HID_KEY_NO_PRESS */ + {0, 0}, /* HID_KEY_ROLLOVER */ + {0, 0}, /* HID_KEY_POST_FAIL */ + {0, 0}, /* HID_KEY_ERROR_UNDEFINED */ + {'a', 'A'}, /* HID_KEY_A */ + {'b', 'B'}, /* HID_KEY_B */ + {'c', 'C'}, /* HID_KEY_C */ + {'d', 'D'}, /* HID_KEY_D */ + {'e', 'E'}, /* HID_KEY_E */ + {'f', 'F'}, /* HID_KEY_F */ + {'g', 'G'}, /* HID_KEY_G */ + {'h', 'H'}, /* HID_KEY_H */ + {'i', 'I'}, /* HID_KEY_I */ + {'j', 'J'}, /* HID_KEY_J */ + {'k', 'K'}, /* HID_KEY_K */ + {'l', 'L'}, /* HID_KEY_L */ + {'m', 'M'}, /* HID_KEY_M */ + {'n', 'N'}, /* HID_KEY_N */ + {'o', 'O'}, /* HID_KEY_O */ + {'p', 'P'}, /* HID_KEY_P */ + {'q', 'Q'}, /* HID_KEY_Q */ + {'r', 'R'}, /* HID_KEY_R */ + {'s', 'S'}, /* HID_KEY_S */ + {'t', 'T'}, /* HID_KEY_T */ + {'u', 'U'}, /* HID_KEY_U */ + {'v', 'V'}, /* HID_KEY_V */ + {'w', 'W'}, /* HID_KEY_W */ + {'x', 'X'}, /* HID_KEY_X */ + {'y', 'Y'}, /* HID_KEY_Y */ + {'z', 'Z'}, /* HID_KEY_Z */ + {'1', '!'}, /* HID_KEY_1 */ + {'2', '@'}, /* HID_KEY_2 */ + {'3', '#'}, /* HID_KEY_3 */ + {'4', '$'}, /* HID_KEY_4 */ + {'5', '%'}, /* HID_KEY_5 */ + {'6', '^'}, /* HID_KEY_6 */ + {'7', '&'}, /* HID_KEY_7 */ + {'8', '*'}, /* HID_KEY_8 */ + {'9', '('}, /* HID_KEY_9 */ + {'0', ')'}, /* HID_KEY_0 */ + {'\r', '\r'}, /* HID_KEY_ENTER */ + {0, 0}, /* HID_KEY_ESC */ + {'\b', 0}, /* HID_KEY_DEL */ + {0, 0}, /* HID_KEY_TAB */ + {' ', ' '}, /* HID_KEY_SPACE */ + {'-', '_'}, /* HID_KEY_MINUS */ + {'=', '+'}, /* HID_KEY_EQUAL */ + {'[', '{'}, /* HID_KEY_OPEN_BRACKET */ + {']', '}'}, /* HID_KEY_CLOSE_BRACKET */ + {'\\', '|'}, /* HID_KEY_BACK_SLASH */ + {'\\', '|'}, /* HID_KEY_SHARP */ // HOTFIX: for NonUS Keyboards repeat HID_KEY_BACK_SLASH + {';', ':'}, /* HID_KEY_COLON */ + {'\'', '"'}, /* HID_KEY_QUOTE */ + {'`', '~'}, /* HID_KEY_TILDE */ + {',', '<'}, /* HID_KEY_LESS */ + {'.', '>'}, /* HID_KEY_GREATER */ + {'/', '?'} /* HID_KEY_SLASH */ + }; + + if (shift > 1) { + shift = 1; + } + + if ((key >= HID_KEY_A) && (key <= HID_KEY_SLASH)) { + ret_key = keycode2ascii[key][shift]; + } + + return ret_key; +} + +static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t hid_device_handle, const hid_host_interface_event_t event, void *arg) +{ + hid_host_dev_params_t dev; + hid_host_device_get_params(hid_device_handle, &dev); + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + uint8_t data[10]; + unsigned int data_length = 0; + + assert(hid_ctx != NULL); + + switch (event) { + case HID_HOST_INTERFACE_EVENT_INPUT_REPORT: + hid_host_device_get_raw_input_report_data(hid_device_handle, data, sizeof(data), &data_length); + if (dev.proto == HID_PROTOCOL_KEYBOARD) { + hid_keyboard_input_report_boot_t *keyboard = (hid_keyboard_input_report_boot_t *)data; + if (data_length < sizeof(hid_keyboard_input_report_boot_t)) { + return; + } + for (int i = 0; i < HID_KEYBOARD_KEY_MAX; i++) { + if (keyboard->key[i] > HID_KEY_ERROR_UNDEFINED) { + char key = 0; + + /* LVGL special keys */ + if (keyboard->key[i] == HID_KEY_TAB) { + if ((keyboard->modifier.left_shift || keyboard->modifier.right_shift)) { + key = LV_KEY_PREV; + } else { + key = LV_KEY_NEXT; + } + } else if (keyboard->key[i] == HID_KEY_RIGHT) { + key = LV_KEY_RIGHT; + } else if (keyboard->key[i] == HID_KEY_LEFT) { + key = LV_KEY_LEFT; + } else if (keyboard->key[i] == HID_KEY_DOWN) { + key = LV_KEY_DOWN; + } else if (keyboard->key[i] == HID_KEY_UP) { + key = LV_KEY_UP; + } else if (keyboard->key[i] == HID_KEY_ENTER || keyboard->key[i] == HID_KEY_KEYPAD_ENTER) { + key = LV_KEY_ENTER; + } else if (keyboard->key[i] == HID_KEY_DELETE) { + key = LV_KEY_DEL; + } else if (keyboard->key[i] == HID_KEY_HOME) { + key = LV_KEY_HOME; + } else if (keyboard->key[i] == HID_KEY_END) { + key = LV_KEY_END; + } else { + /* Get ASCII char */ + key = usb_hid_get_keyboard_char(keyboard->key[i], (keyboard->modifier.left_shift || keyboard->modifier.right_shift)); + } + + if (key == 0) { + ESP_LOGI(TAG, "Not recognized key: %c (%d)", keyboard->key[i], keyboard->key[i]); + } + hid_ctx->kb.last_key = key; + hid_ctx->kb.pressed = true; + } + } + + } else if (dev.proto == HID_PROTOCOL_MOUSE) { + hid_mouse_input_report_boot_t *mouse = (hid_mouse_input_report_boot_t *)data; + if (data_length < sizeof(hid_mouse_input_report_boot_t)) { + break; + } + hid_ctx->mouse.left_button = mouse->buttons.button1; + hid_ctx->mouse.x += mouse->x_displacement; + hid_ctx->mouse.y += mouse->y_displacement; + } + break; + case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR: + break; + case HID_HOST_INTERFACE_EVENT_DISCONNECTED: + hid_host_device_close(hid_device_handle); + break; + default: + break; + } +} + +static void lvgl_port_usb_hid_task(void *arg) +{ + hid_host_dev_params_t dev; + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)arg; + hid_host_device_handle_t hid_device_handle = NULL; + lvgl_port_usb_hid_event_t msg; + + assert(ctx); + + ctx->running = true; + + while (ctx->running) { + if (xQueueReceive(ctx->queue, &msg, pdMS_TO_TICKS(50))) { + hid_device_handle = msg.hid_device_handle; + hid_host_device_get_params(hid_device_handle, &dev); + + switch (msg.event) { + case HID_HOST_DRIVER_EVENT_CONNECTED: + /* Handle mouse or keyboard */ + if (dev.proto == HID_PROTOCOL_KEYBOARD || dev.proto == HID_PROTOCOL_MOUSE) { + const hid_host_device_config_t dev_config = { + .callback = lvgl_port_usb_hid_host_interface_callback, + .callback_arg = ctx + }; + + ESP_ERROR_CHECK( hid_host_device_open(hid_device_handle, &dev_config) ); + ESP_ERROR_CHECK( hid_class_request_set_idle(hid_device_handle, 0, 0) ); + ESP_ERROR_CHECK( hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT) ); + ESP_ERROR_CHECK( hid_host_device_start(hid_device_handle) ); + } + break; + default: + break; + } + } + } + + xQueueReset(ctx->queue); + vQueueDelete(ctx->queue); + + hid_host_uninstall(); + + memset(&lvgl_hid_ctx, 0, sizeof(lvgl_port_usb_hid_ctx_t)); + + vTaskDelete(NULL); +} + +static void lvgl_port_usb_hid_read_mouse(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + int16_t width = 0; + int16_t height = 0; + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_user_data(indev_drv); + assert(ctx); + + lv_display_t *disp = lv_indev_get_display(indev_drv); + assert(disp); + if (lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_0 || lv_display_get_rotation(disp) == LV_DISPLAY_ROTATION_180) { + width = lv_display_get_physical_horizontal_resolution(disp); + height = lv_display_get_vertical_resolution(disp); + } else { + width = lv_display_get_vertical_resolution(disp); + height = lv_display_get_physical_horizontal_resolution(disp); + } + + /* Screen borders */ + if (ctx->mouse.x < 0) { + ctx->mouse.x = 0; + } else if (ctx->mouse.x > width * ctx->mouse.sensitivity) { + ctx->mouse.x = width * ctx->mouse.sensitivity; + } + if (ctx->mouse.y < 0) { + ctx->mouse.y = 0; + } else if (ctx->mouse.y > height * ctx->mouse.sensitivity) { + ctx->mouse.y = height * ctx->mouse.sensitivity; + } + + /* Get coordinates by rotation with sensitivity */ + switch (lv_display_get_rotation(disp)) { + case LV_DISPLAY_ROTATION_0: + data->point.x = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISPLAY_ROTATION_90: + data->point.y = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISPLAY_ROTATION_180: + data->point.x = width - ctx->mouse.x / ctx->mouse.sensitivity; + data->point.y = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + case LV_DISPLAY_ROTATION_270: + data->point.y = ctx->mouse.x / ctx->mouse.sensitivity; + data->point.x = height - ctx->mouse.y / ctx->mouse.sensitivity; + break; + } + + if (ctx->mouse.left_button) { + data->state = LV_INDEV_STATE_PRESSED; + } else { + data->state = LV_INDEV_STATE_RELEASED; + } +} + +static void lvgl_port_usb_hid_read_kb(lv_indev_t *indev_drv, lv_indev_data_t *data) +{ + assert(indev_drv); + lvgl_port_usb_hid_ctx_t *ctx = (lvgl_port_usb_hid_ctx_t *)lv_indev_get_user_data(indev_drv); + assert(ctx); + + data->key = ctx->kb.last_key; + if (ctx->kb.pressed) { + data->state = LV_INDEV_STATE_PRESSED; + ctx->kb.pressed = false; + } else { + data->state = LV_INDEV_STATE_RELEASED; + ctx->kb.last_key = 0; + } +} + +static void lvgl_port_usb_hid_callback(hid_host_device_handle_t hid_device_handle, const hid_host_driver_event_t event, void *arg) +{ + lvgl_port_usb_hid_ctx_t *hid_ctx = (lvgl_port_usb_hid_ctx_t *)arg; + + const lvgl_port_usb_hid_event_t msg = { + .hid_device_handle = hid_device_handle, + .event = event, + .arg = arg + }; + + xQueueSend(hid_ctx->queue, &msg, 0); +} diff --git a/test_app/CMakeLists.txt b/test_app/CMakeLists.txt index 163244f4..9279f5df 100644 --- a/test_app/CMakeLists.txt +++ b/test_app/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.5) set(EXTRA_COMPONENT_DIRS "../components") +set(EXCLUDE_COMPONENTS "") include($ENV{IDF_PATH}/tools/cmake/version.cmake) # $ENV{IDF_VERSION} was added after v4.3... @@ -13,11 +14,11 @@ if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "4.4") list(APPEND EXTRA_COMPONENT_DIRS "../components/io_expander") endif() if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "4.4") - set(EXCLUDE_COMPONENTS "es8311" "es7210" "esp_lvgl_port" "ds18b20" "icm42670") + list(APPEND EXCLUDE_COMPONENTS "es8311" "es7210" "esp_lvgl_port" "ds18b20" "icm42670") elseif("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "5.0") - set(EXCLUDE_COMPONENTS "esp_lcd_touch_stmpe610" "ds18b20" "esp_lcd_ssd1681") + list(APPEND EXCLUDE_COMPONENTS "esp_lcd_touch_stmpe610" "ds18b20" "esp_lcd_ssd1681") elseif("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "5.3") - set(EXCLUDE_COMPONENTS "esp_lcd_ili9881c") + list(APPEND EXCLUDE_COMPONENTS "esp_lcd_ili9881c") endif() # Test rgb lcd components only in esp32s3