From 5431be84898f5a473442046fbdfa9cd559f3ecb7 Mon Sep 17 00:00:00 2001 From: Vilem Zavodny Date: Thu, 4 Jan 2024 15:20:12 +0100 Subject: [PATCH] LVGL: Add support of LVGL9 into LVGL port --- .github/workflows/build_test.yml | 3 +- .../include/bsp/esp32_c3_lcdkit.h | 1 + components/esp_lvgl_port/CHANGELOG.md | 32 + components/esp_lvgl_port/CMakeLists.txt | 55 +- components/esp_lvgl_port/README.md | 17 + components/esp_lvgl_port/esp_lvgl_port.c | 1355 ----------------- .../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 | 80 + .../include/esp_lvgl_port_knob.h | 64 + .../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 | 337 ++++ .../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 | 292 ++++ .../src/lvgl9/esp_lvgl_port_knob.c | 165 ++ .../src/lvgl9/esp_lvgl_port_touch.c | 107 ++ .../src/lvgl9/esp_lvgl_port_usbhid.c | 479 ++++++ examples/display_rotation/main/main.c | 2 +- 30 files changed, 3471 insertions(+), 1597 deletions(-) create mode 100644 components/esp_lvgl_port/CHANGELOG.md delete mode 100644 components/esp_lvgl_port/esp_lvgl_port.c 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/.github/workflows/build_test.yml b/.github/workflows/build_test.yml index 3e86ad79..f0f8c1f2 100644 --- a/.github/workflows/build_test.yml +++ b/.github/workflows/build_test.yml @@ -12,7 +12,7 @@ jobs: build: strategy: matrix: - idf_ver: ["release-v4.3", "release-v4.4", "latest"] + idf_ver: ["release-v4.4", "release-v5.0", "release-v5.1", "release-v5.2", "latest"] idf_target: ["esp32", "esp32s2", "esp32c3", "esp32s3"] exclude: - idf_ver: "release-v4.3" @@ -31,4 +31,5 @@ jobs: export PEDANTIC_FLAGS="-DIDF_CI_BUILD -Werror -Werror=deprecated-declarations -Werror=unused-variable -Werror=unused-but-set-variable -Werror=unused-function" export EXTRA_CFLAGS="${PEDANTIC_FLAGS} -Wstrict-prototypes" export EXTRA_CXXFLAGS="${PEDANTIC_FLAGS}" + pip install idf-component-manager --upgrade idf.py build diff --git a/bsp/esp32_c3_lcdkit/include/bsp/esp32_c3_lcdkit.h b/bsp/esp32_c3_lcdkit/include/bsp/esp32_c3_lcdkit.h index 391968d0..3d2b795a 100644 --- a/bsp/esp32_c3_lcdkit/include/bsp/esp32_c3_lcdkit.h +++ b/bsp/esp32_c3_lcdkit/include/bsp/esp32_c3_lcdkit.h @@ -16,6 +16,7 @@ #include "driver/i2s_pdm.h" #include "iot_button.h" #include "lvgl.h" +#include "esp_lvgl_port.h" #include "esp_codec_dev.h" /************************************************************************************************** diff --git a/components/esp_lvgl_port/CHANGELOG.md b/components/esp_lvgl_port/CHANGELOG.md new file mode 100644 index 00000000..fe87ea93 --- /dev/null +++ b/components/esp_lvgl_port/CHANGELOG.md @@ -0,0 +1,32 @@ +# Changelog + +## 1.5.0 + +### Features + +- Divided into files per feature +- Added support for LVGL9 + +## 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..b4b525d3 100644 --- a/components/esp_lvgl_port/CMakeLists.txt +++ b/components/esp_lvgl_port/CMakeLists.txt @@ -1,29 +1,64 @@ -file(GLOB_RECURSE IMAGE_SOURCES images/*.c) -idf_component_register(SRCS "esp_lvgl_port.c" ${IMAGE_SOURCES} INCLUDE_DIRS "include" REQUIRES "esp_lcd" PRIV_REQUIRES "esp_timer") +#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..d634f990 100644 --- a/components/esp_lvgl_port/README.md +++ b/components/esp_lvgl_port/README.md @@ -13,6 +13,20 @@ This component helps with using LVGL with Espressif's LCD and touch drivers. It * 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)) +## LVGL Version + +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 ### Initialization @@ -53,6 +67,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 +234,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 +249,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); ``` 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/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..7aa31895 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: "1.5.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..c077f83f --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_disp.h @@ -0,0 +1,80 @@ +/* + * 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 */ + 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) */ + 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..7f37d6e1 --- /dev/null +++ b/components/esp_lvgl_port/include/esp_lvgl_port_knob.h @@ -0,0 +1,64 @@ +/* + * 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") +#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..45bae0a9 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl8/esp_lvgl_port_disp.c @@ -0,0 +1,337 @@ +/* + * 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 (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 */ + 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_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); +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->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 + /* 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; +} + +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_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)); + } +} 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..4527eff7 --- /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 buttons_ctx->indev; +} + +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..4a51d864 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_disp.c @@ -0,0 +1,292 @@ +/* + * 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 (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 */ + 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_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_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->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 + /* 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 + + 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_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; +} +#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 panel_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(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_DISPLAY_ROTATION_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_DISPLAY_ROTATION_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_DISPLAY_ROTATION_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; + } +} 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..03efe4c1 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_knob.c @@ -0,0 +1,165 @@ +/* + * 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(); + +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 encoder_ctx->indev; +} + +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..947142f1 --- /dev/null +++ b/components/esp_lvgl_port/src/lvgl9/esp_lvgl_port_usbhid.c @@ -0,0 +1,479 @@ +/* + * 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_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/examples/display_rotation/main/main.c b/examples/display_rotation/main/main.c index b5da5437..14855653 100644 --- a/examples/display_rotation/main/main.c +++ b/examples/display_rotation/main/main.c @@ -125,7 +125,7 @@ static void app_lvgl_display(void) /* Input device group */ lv_indev_t *indev = bsp_display_get_input_dev(); - if (indev && indev->driver && indev->driver->type == LV_INDEV_TYPE_ENCODER) { + if (indev && lv_indev_get_type(indev) == LV_INDEV_TYPE_ENCODER) { lv_group_t *main_group = lv_group_create(); lv_group_add_obj(main_group, btn_left); lv_group_add_obj(main_group, btn_right);