Update to LVGL 9.0.0 (#47)

- Updated LVGL from 8.3 to 9.0 (removed example/docs/demo folders)
- Updated esp_lvgl_port  to current status of the `lvgl9` branch on `esp-bsp`: https://github.com/espressif/esp-bsp/tree/lvgl9
- Updated all boards and drivers
- Removed `libs/lv_drivers` subproject as SDL is now supported by LVGL directly (although keyboard input seems broken)
- Updated `libs/lv_screenshot`
- Fixed the way `tt_statusbar` widget works due to behaviour change in LVGL
- Updated other lvgl code
This commit is contained in:
Ken Van Hoeylandt
2024-02-18 17:40:02 +01:00
committed by GitHub
parent 5fef25fb13
commit 473fb673bd
1630 changed files with 142517 additions and 182264 deletions
+32
View File
@@ -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
+46 -10
View File
@@ -1,29 +1,65 @@
file(GLOB_RECURSE IMAGE_SOURCES images/*.c)
idf_component_register(SRCS "esp_lvgl_port.c" ${IMAGE_SOURCES} INCLUDE_DIRS "include" REQUIRES "esp_lcd" "lvgl" 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()
set(lvgl_ver "9.0.0")
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" "lvgl" 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()
+48 -1
View File
@@ -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,11 +234,43 @@ 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.
Rotation mode can be selected in the `lvgl_port_display_cfg_t` structure.
``` c
const lvgl_port_display_cfg_t disp_cfg = {
...
.flags = {
...
.sw_rotate = true / false, // true: software; false: hardware
}
}
```
Display rotation can be changed at runtime.
``` c
lv_disp_set_rotation(disp_handle, LV_DISP_ROT_90);
```
**Note:** During the rotating, the component call [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) API.
**Note:** During the hardware rotating, the component call [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) API. When using software rotation, you cannot use neither `direct_mode` nor `full_refresh` in the driver. See [LVGL documentation](https://docs.lvgl.io/8.3/porting/display.html?highlight=sw_rotate) for more info.
### Using PSRAM canvas
If the SRAM is insufficient, you can use the PSRAM as a canvas and use a small trans_buffer to carry it, this makes drawing more efficient.
``` c
const lvgl_port_display_cfg_t disp_cfg = {
...
.buffer_size = DISP_WIDTH * DISP_HEIGHT, // in PSRAM, not DMA-capable
.trans_size = size, // in SRAM, DMA-capable
.flags = {
.buff_spiram = true,
.buff_dma = false,
...
}
}
```
## Performance
File diff suppressed because it is too large Load Diff
@@ -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 */
+8
View File
@@ -0,0 +1,8 @@
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,<10"
public: true
@@ -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,
};
+11 -221
View File
@@ -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
*/
@@ -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"
#include "esp_lvgl_port_button.h"
#include "esp_lvgl_port_compatibility.h"
#include "esp_lvgl_port_disp.h"
#include "esp_lvgl_port_knob.h"
#include "esp_lvgl_port_touch.h"
#include "esp_lvgl_port_usbhid.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
#if LVGL_VERSION_MAJOR == 8
#include "esp_lvgl_port_compatibility.h"
#endif
#ifdef __cplusplus
@@ -51,85 +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 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 */
} 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
*
@@ -137,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, \
@@ -167,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 occured
*/
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 occured
*/
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 occured
*/
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 occured
*/
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 occured
*/
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 occured
*/
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
*
@@ -308,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
@@ -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
@@ -0,0 +1,25 @@
/*
* 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;
#ifdef __cplusplus
}
#endif
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
}
@@ -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;
}
}
}
@@ -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));
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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);
}
@@ -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);
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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);
}