Revert to LVGL 9.0.0 (#64)

* Revert "Update ESP LCD Touch dependencies (#63)"

This reverts commit cbd0355cec25e9d0f83f328ac2cae78e0e3b83e3.

* Revert "Updated board configs for LVGL changes (#62)"

This reverts commit 52d769854f6805407f7ee08adaa78b3a406fa13e.

* Revert "Various updates (#60)"

This reverts commit a8a664703b131963222d2035a3a85ecede29e633.
This commit is contained in:
Ken Van Hoeylandt 2024-10-28 23:08:24 +01:00 committed by GitHub
parent cbd0355cec
commit 62d30e8b7b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 706 additions and 2335 deletions

View File

@ -1,6 +1,6 @@
dependencies:
espressif/esp_lcd_ili9341: "2.0.0"
espressif/esp_lcd_touch_cst816s: "1.0.3~1"
espressif/esp_lcd_touch_gt911: "1.1.1~1"
espressif/esp_lcd_touch_cst816s: "1.0.3"
espressif/esp_lcd_touch_gt911: "1.1.1"
espressif/esp_lcd_touch: "1.1.2"
idf: '~5.3.1'

View File

@ -1,70 +1,11 @@
# Changelog
## 2.3.3
### Features
- Updated RGB screen flush handling in LVGL9.
## 2.3.2
### Fixes
- Fixed rotation type compatibility with LVGL8.
## 2.3.1
### Fixes
- Fixed LVGL version resolution if LVGL is not a managed component
- Fixed link error with LVGL v9.2
- Fixed event error with LVGL v9.2
## 2.3.0
### Fixes
- Fixed LVGL port for using with LVGL9 OS FreeRTOS enabled
- Fixed bad handled touch due to synchronization timer task
### Features
- Added support for SW rotation in LVGL9
## 2.2.2
### Fixes
- Fixed missing callback in IDF4.4.3 and lower for LVGL port
## 2.2.1
### Fixes
- Added missing includes
- Fixed watchdog error in some cases in LVGL9
## 2.2.0
### Features
- Added RGB display support
- Added support for direct mode and full refresh mode
### Breaking changes
- Removed MIPI-DSI from display configuration structure - use `lvgl_port_add_disp_dsi` instead
## 2.1.0
### Features
- Added LVGL sleep feature: The esp_lvgl_port handling can sleep if the display and touch are inactive (only with LVGL9)
- Added support for different display color modes (only with LVGL9)
- Added script for generating C array images during build (depends on LVGL version)
### Fixes
- Applied initial display rotation from configuration https://github.com/espressif/esp-bsp/pull/278
- Added blocking wait for LVGL task stop during esp_lvgl_port de-initialization https://github.com/espressif/esp-bsp/issues/277
- Added missing esp_idf_version.h include
## 2.0.0
## 1.5.0
### Features
- Divided into files per feature
- Added support for LVGL9
- Added support for MIPI-DSI display
## 1.4.0

View File

@ -1,34 +1,13 @@
include($ENV{IDF_PATH}/tools/cmake/version.cmake) # $ENV{IDF_VERSION} was added after v4.3...
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_LESS "4.4")
return()
endif()
# This component uses a CMake workaround, so we can compile esp_lvgl_port for both LVGL8.x and LVGL9.x
# At the time of idf_component_register() we don't know which LVGL version is used, so we only register an INTERFACE component (with no sources)
# Later, when we know the LVGL version, we create another CMake library called 'lvgl_port_lib' and link it to the 'esp_lvgl_port' INTERFACE component
idf_component_register(
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "priv_include"
REQUIRES "esp_lcd")
# Get LVGL version
idf_build_get_property(build_components BUILD_COMPONENTS)
if(lvgl IN_LIST build_components)
set(lvgl_name lvgl) # Local component
set(lvgl_ver $ENV{LVGL_VERSION}) # Get the version from env variable (set from LVGL v9.2)
else()
set(lvgl_name lvgl__lvgl) # Managed component
idf_component_get_property(lvgl_ver ${lvgl_name} COMPONENT_VERSION) # Get the version from esp-idf build system
endif()
if("${lvgl_ver}" STREQUAL "")
message("Could not determine LVGL version, assuming v9.x")
set(lvgl_ver "9.0.0")
endif()
# Select folder by LVGL version
#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")
@ -37,8 +16,10 @@ else()
set(PORT_FOLDER "lvgl9")
endif()
# Add LVGL port extensions
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 "")
@ -76,43 +57,9 @@ if("usb_host_hid" IN_LIST build_components)
list(APPEND ADD_LIBS idf::usb_host_hid)
endif()
# Include SIMD assembly source code for rendering, only for (9.1.0 <= LVG_version < 9.2.0) and only for esp32 and esp32s3
if((lvgl_ver VERSION_GREATER_EQUAL "9.1.0") AND (lvgl_ver VERSION_LESS "9.2.0"))
if(CONFIG_IDF_TARGET_ESP32 OR CONFIG_IDF_TARGET_ESP32S3)
message(VERBOSE "Compiling SIMD")
if(CONFIG_IDF_TARGET_ESP32S3)
file(GLOB_RECURSE ASM_SRCS ${PORT_PATH}/simd/*_esp32s3.S) # Select only esp32s3 related files
else()
file(GLOB_RECURSE ASM_SRCS ${PORT_PATH}/simd/*_esp32.S) # Select only esp32 related files
endif()
list(APPEND ADD_SRCS ${ASM_SRCS})
# Include component libraries, so lvgl component would see lvgl_port includes
idf_component_get_property(lvgl_lib ${lvgl_name} COMPONENT_LIB)
target_include_directories(${lvgl_lib} PRIVATE "include")
# Force link .S files
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_color_blend_to_argb8888_esp")
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u lv_color_blend_to_rgb565_esp")
endif()
if(ADD_SRCS)
target_sources(${COMPONENT_LIB} PRIVATE ${ADD_SRCS})
endif()
if(ADD_LIBS)
target_link_libraries(${COMPONENT_LIB} PRIVATE ${ADD_LIBS})
endif()
# Here we create the real lvgl_port_lib
add_library(lvgl_port_lib STATIC
${PORT_PATH}/esp_lvgl_port.c
${PORT_PATH}/esp_lvgl_port_disp.c
${ADD_SRCS}
)
target_include_directories(lvgl_port_lib PUBLIC "include")
target_include_directories(lvgl_port_lib PRIVATE "priv_include")
target_link_libraries(lvgl_port_lib PUBLIC
idf::esp_lcd
idf::${lvgl_name}
)
target_link_libraries(lvgl_port_lib PRIVATE
idf::esp_timer
${ADD_LIBS}
)
# Finally, link the lvgl_port_lib its esp-idf interface library
target_link_libraries(${COMPONENT_LIB} INTERFACE lvgl_port_lib)

View File

@ -1,24 +1,21 @@
# LVGL ESP Portation
[![Component Registry](https://components.espressif.com/components/espressif/esp_lvgl_port/badge.svg)](https://components.espressif.com/components/espressif/esp_lvgl_port)
![maintenance-status](https://img.shields.io/badge/maintenance-actively--developed-brightgreen.svg)
This component helps with using LVGL with Espressif's LCD and touch drivers. It can be used with any project with LCD display.
This component helps with using LVGL with Espressif's LCD and touch drivers. It can be used with any project with LCD display.
## Features
* Initialization of the LVGL
* Create task and timer
* Handle rotating
* Power saving
* Add/remove display (using [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html))
* Add/remove touch input (using [`esp_lcd_touch`](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch))
* Add/remove navigation buttons input (using [`button`](https://github.com/espressif/esp-iot-solution/tree/master/components/button))
* Add/remove encoder input (using [`knob`](https://github.com/espressif/esp-iot-solution/tree/master/components/knob))
* Add/remove USB HID mouse/keyboard input (using [`usb_host_hid`](https://components.espressif.com/components/espressif/usb_host_hid))
## LVGL Version
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 define this requirement in `idf_component.yml` in your project like this:
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:
@ -28,7 +25,7 @@ This component supports **LVGL8** and **LVGL9**. By default, it selects the l
### 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 retyped 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.**
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
@ -40,16 +37,16 @@ This component is fully compatible with LVGL version 9. All types and functions
### Add screen
Add an LCD screen to the LVGL. It can be called multiple times for adding multiple LCD screens.
Add an LCD screen to the LVGL. It can be called multiple times for adding multiple LCD screens.
``` c
static lv_disp_t * disp_handle;
/* LCD IO */
esp_lcd_panel_io_handle_t io_handle = NULL;
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t) 1, &io_config, &io_handle));
/* LCD driver initialization */
/* LCD driver initialization */
esp_lcd_panel_handle_t lcd_panel_handle;
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &lcd_panel_handle));
@ -62,8 +59,7 @@ Add an LCD screen to the LVGL. It can be called multiple times for adding multip
.hres = DISP_WIDTH,
.vres = DISP_HEIGHT,
.monochrome = false,
.mipi_dsi = false,
.color_format = LV_COLOR_FORMAT_RGB565,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation = {
.swap_xy = false,
.mirror_x = false,
@ -75,20 +71,16 @@ Add an LCD screen to the LVGL. It can be called multiple times for adding multip
}
};
disp_handle = lvgl_port_add_disp(&disp_cfg);
/* ... the rest of the initialization ... */
/* If deinitializing LVGL port, remember to delete all displays: */
lvgl_port_remove_disp(disp_handle);
```
> [!NOTE]
> 1. For adding RGB or MIPI-DSI screen, use functions `lvgl_port_add_disp_rgb` or `lvgl_port_add_disp_dsi`.
> 2. DMA buffer can be used only when you use color format `LV_COLOR_FORMAT_RGB565`.
### Add touch input
Add touch input to the LVGL. It can be called more times for adding more touch inputs.
Add touch input to the LVGL. It can be called more times for adding more touch inputs.
``` c
/* Touch driver initialization */
...
@ -101,7 +93,7 @@ Add touch input to the LVGL. It can be called more times for adding more touch i
.handle = tp,
};
lv_indev_t* touch_handle = lvgl_port_add_touch(&touch_cfg);
/* ... the rest of the initialization ... */
/* If deinitializing LVGL port, remember to delete all touches: */
@ -146,14 +138,14 @@ Add buttons input to the LVGL. It can be called more times for adding more butto
/* Add buttons input (for selected screen) */
lv_indev_t* buttons_handle = lvgl_port_add_navigation_buttons(&btns);
/* ... the rest of the initialization ... */
/* If deinitializing LVGL port, remember to delete all buttons: */
lvgl_port_remove_navigation_buttons(buttons_handle);
```
> [!NOTE]
> When you use navigation buttons for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.
**Note:** When you use navigation buttons for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.
### Add encoder input
@ -181,14 +173,14 @@ Add encoder input to the LVGL. It can be called more times for adding more encod
/* Add encoder input (for selected screen) */
lv_indev_t* encoder_handle = lvgl_port_add_encoder(&encoder);
/* ... the rest of the initialization ... */
/* If deinitializing LVGL port, remember to delete all encoders: */
lvgl_port_remove_encoder(encoder_handle);
```
> [!NOTE]
> When you use encoder for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.
**Note:** When you use encoder for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.
### Add USB HID keyboard and mouse input
@ -225,8 +217,7 @@ Keyboard special behavior (when objects are in group):
- **ARROWS** or **HOME** or **END**: Move in text area
- **DEL** or **Backspace**: Remove character in textarea
> [!NOTE]
> When you use keyboard for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.
**Note:** When you use keyboard for control LVGL objects, these objects must be added to LVGL groups. See [LVGL documentation](https://docs.lvgl.io/master/overview/indev.html?highlight=lv_indev_get_act#keypad-and-encoder) for more info.
### LVGL API usage
@ -263,11 +254,7 @@ Display rotation can be changed at runtime.
lv_disp_set_rotation(disp_handle, LV_DISP_ROT_90);
```
> [!NOTE]
> This feature consume more RAM.
> [!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.
**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
@ -285,79 +272,6 @@ If the SRAM is insufficient, you can use the PSRAM as a canvas and use a small t
}
```
### Generating images (C Array)
Images can be generated during build by adding these lines to end of the main CMakeLists.txt:
```
# Generate C array for each image
lvgl_port_create_c_image("images/logo.png" "images/" "ARGB8888" "NONE")
lvgl_port_create_c_image("images/image.png" "images/" "ARGB8888" "NONE")
# Add generated images to build
lvgl_port_add_images(${COMPONENT_LIB} "images/")
```
Usage of create C image function:
```
lvgl_port_create_c_image(input_image output_folder color_format compression)
```
Available color formats:
L8,I1,I2,I4,I8,A1,A2,A4,A8,ARGB8888,XRGB8888,RGB565,RGB565A8,RGB888,TRUECOLOR,TRUECOLOR_ALPHA,AUTO
Available compression:
NONE,RLE,LZ4
> [!NOTE]
> Parameters `color_format` and `compression` are used only in LVGL 9.
## Power Saving
The LVGL port can be optimized for power saving mode. There are two main features.
### LVGL task sleep
For optimization power saving, the LVGL task should sleep, when it does nothing. Set `task_max_sleep_ms` to big value, the LVGL task will wait for events only.
The LVGL task can sleep till these situations:
* LVGL display invalidate
* LVGL animation in process
* Touch interrupt
* Button interrupt
* Knob interrupt
* USB mouse/keyboard interrupt
* Timeout (`task_max_sleep_ms` in configuration structure)
* User wake (by function `lvgl_port_task_wake`)
> [!WARNING]
> This feature is available from LVGL 9.
> [!NOTE]
> Don't forget to set the interrupt pin in LCD touch when you set a big time for sleep in `task_max_sleep_ms`.
### Stopping the timer
Timers can still work during light-sleep mode. You can stop LVGL timer before use light-sleep by function:
```
lvgl_port_stop();
```
and resume LVGL timer after wake by function:
```
lvgl_port_resume();
```
## Performance
Key feature of every graphical application is performance. Recommended settings for improving LCD performance is described in a separate document [here](docs/performance.md).
### Performance monitor
For show performance monitor in LVGL9, please add these lines to sdkconfig.defaults and rebuild all.
```
CONFIG_LV_USE_OBSERVER=y
CONFIG_LV_USE_SYSMON=y
CONFIG_LV_USE_PERF_MONITOR=y
```

View File

@ -1,6 +1,6 @@
# LCD & LVGL Performance
This document provides steps, how to set up your LCD and LVGL port for the best performance and comparison of different settings. All settings and measurements are valid for Espressif's chips. Examples in [ESP-BSP](https://github.com/espressif/esp-bsp) are ready to use with the best performance.
This document provides steps, how to set up your LCD and LVGL port for the best performance and comparison of different settings. All settings and measurements are valid for Espressif's chips.
## Performance metrics
@ -26,7 +26,7 @@ On the other hand, the frame buffer(s) will consume significant portion of your
Main takeaways from the graph are:
* The size of **LVGL buffer** and **double buffering** feature has big impact on performance.
* The size of **LVGL buffer** and **double buffering** feature has big impact on performance.
* Frame buffer size >25% of the screen does not bring relevant performance boost
* Frame buffer size <10% will have severe negative effect on performance
@ -67,7 +67,7 @@ The main LVGL task can be processed on the second core of the CPU. It can increa
### Using esp-idf `memcpy` and `memset` instead LVGL's configuration
Native esp-idf implementation are a little (~1-3 FPS) faster (only for LVGL8).
Native esp-idf implementation are a little (~1-3 FPS) faster.
* `CONFIG_LV_MEMCPY_MEMSET_STD=y`
@ -75,45 +75,10 @@ Native esp-idf implementation are a little (~1-3 FPS) faster (only for LVGL8).
This setting can improve subjective performance during screen transitions (scrolling, etc.).
LVGL8
* `CONFIG_LV_DISP_DEF_REFR_PERIOD=10`
LVGL9
* `CONFIG_LV_DEF_REFR_PERIOD=10`
## Example FPS improvement vs graphical settings
The LVGL9 benchmark demo uses a different algorithm for measuring FPS. In this case, we used the same algorithm for measurement in LVGL8 for comparison.
### RGB LCD, PSRAM (octal) with GDMA - ESP32-S3-LCD-EV-BOARD
<img src="https://github.com/espressif/esp-bsp/blob/master/docu/pics/esp32-s3-lcd-ev-board_800x480.png?raw=true" align="right" width="300px" />
Default settings:
* BSP example `display_lvgl_demos`
* LCD: 4.3" 800x480
* Interface: RGB
* LVGL buffer size: 800 x 480
* LVGL buffer mode: Direct (avoid-tearing)
* LVGL double buffer: NO
* Optimization: Debug
* CPU frequency: 160 MHz
* Flash frequency: 80 MHz
* PSRAM frequency: 80 MHz
* Flash mode: DIO
* LVGL display refresh period: 30 ms
| Average FPS (LVGL8) | Average FPS (LVGL 9.2) | Changed settings |
| :---: | :---: | ---------------- |
| 12 | 9 | Default |
| 13 | 9 | + Optimization: Performance (`CONFIG_COMPILER_OPTIMIZATION_PERF=y`) |
| 14 | 11 | + CPU frequency: 240 MHz (`CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`) |
| 14 | 11 | + Flash mode: QIO (`CONFIG_ESPTOOLPY_FLASHMODE_QIO=y`) |
| 15 | 11 | + LVGL fast memory enabled (`CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y`) |
| 16 | 11 | + (`CONFIG_LV_DISP_DEF_REFR_PERIOD=10` / `CONFIG_LV_DEF_REFR_PERIOD=10`) |
### Parallel 8080 LCD (only for LVGL8)
<img src="https://github.com/espressif/esp-bsp/blob/master/docu/pics/7inch-Capacitive-Touch-LCD-C_l.jpg?raw=true" align="right" width="300px" />
Default settings:
@ -129,7 +94,7 @@ Default settings:
* Flash mode: DIO
* LVGL display refresh period: 30 ms
#### Internal RAM with DMA
### Internal RAM with DMA
| Average FPS | Weighted FPS | Changed settings |
| :---: | :---: | ---------------- |
@ -140,7 +105,7 @@ Default settings:
| 28 | **60** | + LVGL fast memory enabled (`CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y`) |
| 41 | 55 | + (`CONFIG_LV_DISP_DEF_REFR_PERIOD=10`) |
#### PSRAM (QUAD) without DMA
### PSRAM (QUAD) without DMA
Default changes:
* LCD IO clock: 2 MHz
@ -159,10 +124,36 @@ Default changes:
[^1]: This is not working in default and sometimes in fast changes on screen is not working properly.
### RGB LCD (without `esp_lvgl_port`), PSRAM (octal) with GDMA - ESP32-S3-LCD-EV-BOARD
<img src="https://github.com/espressif/esp-bsp/blob/master/docu/pics/esp32-s3-lcd-ev-board_800x480.png?raw=true" align="right" width="300px" />
Default settings:
* BSP example `display_lvgl_demos`
* LCD: 4.3" 800x480
* Interface: RGB
* LVGL buffer size: 800 x 100
* LVGL double buffer: NO
* Optimization: Debug
* CPU frequency: 160 MHz
* Flash frequency: 80 MHz
* PSRAM frequency: 80 MHz
* Flash mode: DIO
* LVGL display refresh period: 30 ms
| Average FPS | Weighted FPS | Changed settings |
| :---: | :---: | ---------------- |
| 18 | 24 | Default |
| 18 | 26 | + Optimization: Performance (`CONFIG_COMPILER_OPTIMIZATION_PERF=y`) |
| 21 | 32 | + CPU frequency: 240 MHz (`CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y`) |
| 21 | 32 | + Flash mode: QIO (`CONFIG_ESPTOOLPY_FLASHMODE_QIO=y`) |
| 21 | 32 | + LVGL fast memory enabled (`CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM=y`) |
| 35 | 34 | + (`CONFIG_LV_DISP_DEF_REFR_PERIOD=10`) |
## Conclusion
The graphical performance depends on a lot of things and settings, many of which affect the whole system (Compiler, Flash, CPU, PSRAM configuration...). The user should primarily focus on trade-off between frame-buffer(s) size and RAM consumption of the buffer, before optimizing the design further.
Other configuration options not covered in this document are:
* Hardware interfaces, color depth, screen definition (size), clocks, LCD controller and more.
* Hardware interfaces, color depth, screen definition (size), clocks, LCD controller and more.
* Complexity of the graphical application (number of LVGL objects and their styles).

View File

@ -0,0 +1,9 @@
# The following lines of boilerplate have to be in your project's CMakeLists
# in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
# "Trim" the build. Include the minimal set of components, main and anything it depends on.
set(COMPONENTS main)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(touchscreen)

View File

@ -0,0 +1,19 @@
# ESP LVGL Touch Screen Example
Very simple example for demonstration of initialization and usage of the `esp_lvgl_port` component. This example contains four main parts:
## 1. LCD HW initialization - `app_lcd_init()`
Standard HW initialization of the LCD using [`esp_lcd`](https://github.com/espressif/esp-idf/tree/master/components/esp_lcd) component. Settings of this example are fully compatible with [ESP-BOX](https://github.com/espressif/esp-bsp/tree/master/esp-box) board.
## 2. Touch HW initialization - `app_touch_init()`
Standard HW initialization of the LCD touch using [`esp_lcd_touch`](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch) component. Settings of this example are fully compatible with [ESP-BOX](https://github.com/espressif/esp-bsp/tree/master/esp-box) board.
## 3. LVGL port initialization - `app_lvgl_init()`
Initialization of the LVGL port.
## 4. LVGL objects example usage - `app_main_display()`
Very simple demonstration code of using LVGL objects after LVGL port initialization.

View File

@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")

View File

@ -0,0 +1,8 @@
dependencies:
idf: ">=4.4"
esp_lcd_touch_tt21100:
version: "^1"
override_path: "../../../../lcd_touch/esp_lcd_touch_tt21100/"
esp_lvgl_port:
version: ">=1.2.0"
override_path: "../../../"

View File

@ -0,0 +1,265 @@
/*
* SPDX-FileCopyrightText: 2022-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_err.h"
#include "esp_log.h"
#include "esp_check.h"
#include "driver/i2c.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "esp_lcd_touch_tt21100.h"
/* LCD size */
#define EXAMPLE_LCD_H_RES (320)
#define EXAMPLE_LCD_V_RES (240)
/* LCD settings */
#define EXAMPLE_LCD_SPI_NUM (SPI3_HOST)
#define EXAMPLE_LCD_PIXEL_CLK_HZ (40 * 1000 * 1000)
#define EXAMPLE_LCD_CMD_BITS (8)
#define EXAMPLE_LCD_PARAM_BITS (8)
#define EXAMPLE_LCD_COLOR_SPACE (ESP_LCD_COLOR_SPACE_BGR)
#define EXAMPLE_LCD_BITS_PER_PIXEL (16)
#define EXAMPLE_LCD_DRAW_BUFF_DOUBLE (1)
#define EXAMPLE_LCD_DRAW_BUFF_HEIGHT (50)
#define EXAMPLE_LCD_BL_ON_LEVEL (1)
/* LCD pins */
#define EXAMPLE_LCD_GPIO_SCLK (GPIO_NUM_7)
#define EXAMPLE_LCD_GPIO_MOSI (GPIO_NUM_6)
#define EXAMPLE_LCD_GPIO_RST (GPIO_NUM_48)
#define EXAMPLE_LCD_GPIO_DC (GPIO_NUM_4)
#define EXAMPLE_LCD_GPIO_CS (GPIO_NUM_5)
#define EXAMPLE_LCD_GPIO_BL (GPIO_NUM_45)
/* Touch settings */
#define EXAMPLE_TOUCH_I2C_NUM (0)
#define EXAMPLE_TOUCH_I2C_CLK_HZ (400000)
/* LCD touch pins */
#define EXAMPLE_TOUCH_I2C_SCL (GPIO_NUM_18)
#define EXAMPLE_TOUCH_I2C_SDA (GPIO_NUM_8)
#define EXAMPLE_TOUCH_GPIO_INT (GPIO_NUM_3)
static const char *TAG = "EXAMPLE";
/* LCD IO and panel */
static esp_lcd_panel_io_handle_t lcd_io = NULL;
static esp_lcd_panel_handle_t lcd_panel = NULL;
static esp_lcd_touch_handle_t touch_handle = NULL;
/* LVGL display and touch */
static lv_display_t *lvgl_disp = NULL;
static lv_indev_t *lvgl_touch_indev = NULL;
static esp_err_t app_lcd_init(void)
{
esp_err_t ret = ESP_OK;
/* LCD backlight */
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_LCD_GPIO_BL
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
/* LCD initialization */
ESP_LOGD(TAG, "Initialize SPI bus");
const spi_bus_config_t buscfg = {
.sclk_io_num = EXAMPLE_LCD_GPIO_SCLK,
.mosi_io_num = EXAMPLE_LCD_GPIO_MOSI,
.miso_io_num = GPIO_NUM_NC,
.quadwp_io_num = GPIO_NUM_NC,
.quadhd_io_num = GPIO_NUM_NC,
.max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t),
};
ESP_RETURN_ON_ERROR(spi_bus_initialize(EXAMPLE_LCD_SPI_NUM, &buscfg, SPI_DMA_CH_AUTO), TAG, "SPI init failed");
ESP_LOGD(TAG, "Install panel IO");
const esp_lcd_panel_io_spi_config_t io_config = {
.dc_gpio_num = EXAMPLE_LCD_GPIO_DC,
.cs_gpio_num = EXAMPLE_LCD_GPIO_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLK_HZ,
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
.spi_mode = 0,
.trans_queue_depth = 10,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)EXAMPLE_LCD_SPI_NUM, &io_config, &lcd_io), err, TAG, "New panel IO failed");
ESP_LOGD(TAG, "Install LCD driver");
const esp_lcd_panel_dev_config_t panel_config = {
.reset_gpio_num = EXAMPLE_LCD_GPIO_RST,
.color_space = EXAMPLE_LCD_COLOR_SPACE,
.bits_per_pixel = EXAMPLE_LCD_BITS_PER_PIXEL,
};
ESP_GOTO_ON_ERROR(esp_lcd_new_panel_st7789(lcd_io, &panel_config, &lcd_panel), err, TAG, "New panel failed");
esp_lcd_panel_reset(lcd_panel);
esp_lcd_panel_init(lcd_panel);
esp_lcd_panel_mirror(lcd_panel, true, true);
esp_lcd_panel_disp_on_off(lcd_panel, true);
/* LCD backlight on */
ESP_ERROR_CHECK(gpio_set_level(EXAMPLE_LCD_GPIO_BL, EXAMPLE_LCD_BL_ON_LEVEL));
return ret;
err:
if (lcd_panel) {
esp_lcd_panel_del(lcd_panel);
}
if (lcd_io) {
esp_lcd_panel_io_del(lcd_io);
}
spi_bus_free(EXAMPLE_LCD_SPI_NUM);
return ret;
}
static esp_err_t app_touch_init(void)
{
/* Initilize I2C */
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_TOUCH_I2C_SDA,
.sda_pullup_en = GPIO_PULLUP_DISABLE,
.scl_io_num = EXAMPLE_TOUCH_I2C_SCL,
.scl_pullup_en = GPIO_PULLUP_DISABLE,
.master.clk_speed = EXAMPLE_TOUCH_I2C_CLK_HZ
};
ESP_RETURN_ON_ERROR(i2c_param_config(EXAMPLE_TOUCH_I2C_NUM, &i2c_conf), TAG, "I2C configuration failed");
ESP_RETURN_ON_ERROR(i2c_driver_install(EXAMPLE_TOUCH_I2C_NUM, i2c_conf.mode, 0, 0, 0), TAG, "I2C initialization failed");
/* Initialize touch HW */
const esp_lcd_touch_config_t tp_cfg = {
.x_max = EXAMPLE_LCD_H_RES,
.y_max = EXAMPLE_LCD_V_RES,
.rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset
.int_gpio_num = EXAMPLE_TOUCH_GPIO_INT,
.levels = {
.reset = 0,
.interrupt = 0,
},
.flags = {
.swap_xy = 0,
.mirror_x = 1,
.mirror_y = 0,
},
};
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
const esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_TT21100_CONFIG();
ESP_RETURN_ON_ERROR(esp_lcd_new_panel_io_i2c((esp_lcd_i2c_bus_handle_t)EXAMPLE_TOUCH_I2C_NUM, &tp_io_config, &tp_io_handle), TAG, "");
return esp_lcd_touch_new_i2c_tt21100(tp_io_handle, &tp_cfg, &touch_handle);
}
static esp_err_t app_lvgl_init(void)
{
/* Initialize LVGL */
const lvgl_port_cfg_t lvgl_cfg = {
.task_priority = 4, /* LVGL task priority */
.task_stack = 4096, /* LVGL task stack size */
.task_affinity = -1, /* LVGL task pinned to core (-1 is no affinity) */
.task_max_sleep_ms = 500, /* Maximum sleep in LVGL task */
.timer_period_ms = 5 /* LVGL timer tick period in ms */
};
ESP_RETURN_ON_ERROR(lvgl_port_init(&lvgl_cfg), TAG, "LVGL port initialization failed");
/* Add LCD screen */
ESP_LOGD(TAG, "Add LCD screen");
const lvgl_port_display_cfg_t disp_cfg = {
.io_handle = lcd_io,
.panel_handle = lcd_panel,
.buffer_size = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_DRAW_BUFF_HEIGHT * sizeof(uint16_t),
.double_buffer = EXAMPLE_LCD_DRAW_BUFF_DOUBLE,
.hres = EXAMPLE_LCD_H_RES,
.vres = EXAMPLE_LCD_V_RES,
.monochrome = false,
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
.rotation = {
.swap_xy = false,
.mirror_x = true,
.mirror_y = true,
},
.flags = {
.buff_dma = true,
.swap_bytes = true,
}
};
lvgl_disp = lvgl_port_add_disp(&disp_cfg);
/* Add touch input (for selected screen) */
const lvgl_port_touch_cfg_t touch_cfg = {
.disp = lvgl_disp,
.handle = touch_handle,
};
lvgl_touch_indev = lvgl_port_add_touch(&touch_cfg);
return ESP_OK;
}
static void _app_button_cb(lv_event_t *e)
{
lv_disp_rotation_t rotation = lv_disp_get_rotation(lvgl_disp);
rotation++;
if (rotation > LV_DISPLAY_ROTATION_270) {
rotation = LV_DISPLAY_ROTATION_0;
}
/* LCD HW rotation */
lv_disp_set_rotation(lvgl_disp, rotation);
}
static void app_main_display(void)
{
lv_obj_t *scr = lv_scr_act();
/* Task lock */
lvgl_port_lock(0);
/* Your LVGL objects code here .... */
/* Label */
lv_obj_t *label = lv_label_create(scr);
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 */
lv_obj_t *btn = lv_btn_create(scr);
label = lv_label_create(btn);
lv_label_set_text_static(label, "Rotate screen");
lv_obj_align(btn, LV_ALIGN_BOTTOM_MID, 0, -30);
lv_obj_add_event_cb(btn, _app_button_cb, LV_EVENT_CLICKED, NULL);
/* Task unlock */
lvgl_port_unlock();
}
void app_main(void)
{
/* LCD HW initialization */
ESP_ERROR_CHECK(app_lcd_init());
/* Touch initialization */
ESP_ERROR_CHECK(app_touch_init());
/* LVGL initialization */
ESP_ERROR_CHECK(app_lvgl_init());
/* Show LVGL objects */
app_main_display();
}

View File

@ -0,0 +1,4 @@
CONFIG_IDF_TARGET="esp32s3"
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_LV_COLOR_16_SWAP=y

View File

@ -1,4 +1,4 @@
version: "2.3.3"
version: "1.5.0"
description: ESP LVGL port
url: https://github.com/espressif/esp-bsp/tree/master/components/esp_lvgl_port
dependencies:

View File

@ -12,11 +12,11 @@
#pragma once
#include "esp_err.h"
#include "lvgl.h"
#include "esp_lvgl_port_disp.h"
#include "esp_lvgl_port_touch.h"
#include "esp_lvgl_port_knob.h"
#include "esp_lvgl_port_button.h"
#include "esp_lvgl_port_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 LVGL_VERSION_MAJOR == 8
@ -27,23 +27,6 @@
extern "C" {
#endif
/**
* @brief LVGL Port task event type
*/
typedef enum {
LVGL_PORT_EVENT_DISPLAY = 1,
LVGL_PORT_EVENT_TOUCH = 2,
LVGL_PORT_EVENT_USER = 99,
} lvgl_port_event_type_t;
/**
* @brief LVGL Port task events
*/
typedef struct {
lvgl_port_event_type_t type;
void *param;
} lvgl_port_event_t;
/**
* @brief Init configuration structure
*/
@ -89,7 +72,6 @@ esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg);
*
* @return
* - ESP_OK on success
* - ESP_ERR_TIMEOUT when stopping the LVGL task times out
*/
esp_err_t lvgl_port_deinit(void);
@ -119,7 +101,7 @@ void lvgl_port_unlock(void);
void lvgl_port_flush_ready(lv_display_t *disp);
/**
* @brief Stop lvgl timer
* @brief Stop lvgl task
*
*
* @return
@ -129,7 +111,7 @@ void lvgl_port_flush_ready(lv_display_t *disp);
esp_err_t lvgl_port_stop(void);
/**
* @brief Resume lvgl timer
* @brief Resume lvgl task
*
*
* @return
@ -138,20 +120,6 @@ esp_err_t lvgl_port_stop(void);
*/
esp_err_t lvgl_port_resume(void);
/**
* @brief Notify LVGL task, that display need reload
*
* @note It is called from LVGL events and touch interrupts
*
* @param event event type
* @param param user param
* @return
* - ESP_OK on success
* - ESP_ERR_NOT_SUPPORTED if it is not implemented
* - ESP_ERR_INVALID_STATE if queue is not initialized (can be returned after LVGL deinit)
*/
esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param);
#ifdef __cplusplus
}
#endif

View File

@ -19,13 +19,6 @@ extern "C" {
* @brief Backward compatibility with LVGL 8
*/
typedef lv_disp_t lv_display_t;
typedef enum {
LV_DISPLAY_ROTATION_0 = LV_DISP_ROT_NONE,
LV_DISPLAY_ROTATION_90 = LV_DISP_ROT_90,
LV_DISPLAY_ROTATION_180 = LV_DISP_ROT_180,
LV_DISPLAY_ROTATION_270 = LV_DISP_ROT_270
} lv_disp_rotation_t;
typedef lv_disp_rotation_t lv_display_rotation_t;
#ifdef __cplusplus
}

View File

@ -39,52 +39,24 @@ typedef struct {
typedef struct {
esp_lcd_panel_io_handle_t io_handle; /*!< LCD panel IO handle */
esp_lcd_panel_handle_t panel_handle; /*!< LCD panel handle */
esp_lcd_panel_handle_t control_handle; /*!< LCD panel control handle */
uint32_t buffer_size; /*!< Size of the buffer for the screen in pixels */
bool double_buffer; /*!< True, if should be allocated two buffers */
uint32_t trans_size; /*!< Allocated buffer will be in SRAM to move framebuf (optional) */
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 */
lvgl_port_rotation_cfg_t rotation; /*!< Default values of the screen rotation */
#if LVGL_VERSION_MAJOR >= 9
lv_color_format_t color_format; /*!< The color format of the display */
#endif
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) or PPA if available */
#if LVGL_VERSION_MAJOR >= 9
unsigned int swap_bytes: 1; /*!< Swap bytes in RGB656 (16-bit) color format before send to LCD driver */
#endif
unsigned int full_refresh: 1;/*!< 1: Always make the whole screen redrawn */
unsigned int direct_mode: 1; /*!< 1: Use screen-sized buffers and draw to absolute coordinates */
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 Configuration RGB display structure
*/
typedef struct {
struct {
unsigned int bb_mode: 1; /*!< 1: Use bounce buffer mode */
unsigned int avoid_tearing: 1; /*!< 1: Use internal RGB buffers as a LVGL draw buffers to avoid tearing effect */
} flags;
} lvgl_port_display_rgb_cfg_t;
/**
* @brief Configuration MIPI-DSI display structure
*/
typedef struct {
int dummy;
} lvgl_port_display_dsi_cfg_t;
/**
* @brief Add I2C/SPI/I8080 display handling to LVGL
* @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!
*
@ -93,28 +65,6 @@ typedef struct {
*/
lv_display_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg);
/**
* @brief Add MIPI-DSI 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
* @param dsi_cfg MIPI-DSI display specific configuration structure
* @return Pointer to LVGL display or NULL when error occurred
*/
lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_dsi_cfg_t *dsi_cfg);
/**
* @brief Add RGB 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
* @param rgb_cfg RGB display specific configuration structure
* @return Pointer to LVGL display or NULL when error occurred
*/
lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_rgb_cfg_t *rgb_cfg);
/**
* @brief Remove display handling from LVGL
*

View File

@ -15,9 +15,6 @@
#include "lvgl.h"
#if __has_include ("iot_knob.h")
#if !__has_include("iot_button.h")
#error LVLG Knob requires button component. Please add it with idf.py add-dependency espressif/button
#endif
#include "iot_knob.h"
#include "iot_button.h"
#define ESP_LVGL_PORT_KNOB_COMPONENT 1

View File

@ -1,90 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#if !CONFIG_LV_DRAW_SW_ASM_CUSTOM
#warning "esp_lvgl_port_lv_blend.h included, but CONFIG_LV_DRAW_SW_ASM_CUSTOM not set. Assembly rendering not used"
#else
/*********************
* DEFINES
*********************/
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888
#define LV_DRAW_SW_COLOR_BLEND_TO_ARGB8888(dsc) \
_lv_color_blend_to_argb8888_esp(dsc)
#endif
#ifndef LV_DRAW_SW_COLOR_BLEND_TO_RGB565
#define LV_DRAW_SW_COLOR_BLEND_TO_RGB565(dsc) \
_lv_color_blend_to_rgb565_esp(dsc)
#endif
/**********************
* TYPEDEFS
**********************/
typedef struct {
uint32_t opa;
void *dst_buf;
uint32_t dst_w;
uint32_t dst_h;
uint32_t dst_stride;
const void *src_buf;
uint32_t src_stride;
const lv_opa_t *mask_buf;
uint32_t mask_stride;
} asm_dsc_t;
/**********************
* GLOBAL PROTOTYPES
**********************/
extern int lv_color_blend_to_argb8888_esp(asm_dsc_t *asm_dsc);
static inline lv_result_t _lv_color_blend_to_argb8888_esp(_lv_draw_sw_blend_fill_dsc_t *dsc)
{
asm_dsc_t asm_dsc = {
.dst_buf = dsc->dest_buf,
.dst_w = dsc->dest_w,
.dst_h = dsc->dest_h,
.dst_stride = dsc->dest_stride,
.src_buf = &dsc->color,
};
return lv_color_blend_to_argb8888_esp(&asm_dsc);
}
extern int lv_color_blend_to_rgb565_esp(asm_dsc_t *asm_dsc);
static inline lv_result_t _lv_color_blend_to_rgb565_esp(_lv_draw_sw_blend_fill_dsc_t *dsc)
{
asm_dsc_t asm_dsc = {
.dst_buf = dsc->dest_buf,
.dst_w = dsc->dest_w,
.dst_h = dsc->dest_h,
.dst_stride = dsc->dest_stride,
.src_buf = &dsc->color,
};
return lv_color_blend_to_rgb565_esp(&asm_dsc);
}
#endif // CONFIG_LV_DRAW_SW_ASM_CUSTOM
#ifdef __cplusplus
} /*extern "C"*/
#endif

View File

@ -1,47 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief ESP LVGL port private
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Rotation configuration
*/
typedef enum {
LVGL_PORT_DISP_TYPE_OTHER,
LVGL_PORT_DISP_TYPE_DSI,
LVGL_PORT_DISP_TYPE_RGB,
} lvgl_port_disp_type_t;
/**
* @brief Rotation configuration
*/
typedef struct {
unsigned int avoid_tearing: 1; /*!< Use internal RGB buffers as a LVGL draw buffers to avoid tearing effect */
} lvgl_port_disp_priv_cfg_t;
/**
* @brief Notify LVGL task
*
* @note It is called from RGB vsync ready
*
* @param value notification value
* @return
* - true, whether a high priority task has been waken up by this function
*/
bool lvgl_port_task_notify(uint32_t value);
#ifdef __cplusplus
}
#endif

View File

@ -1,78 +0,0 @@
# lvgl_port_create_c_image
#
# Create a C array of image for using with LVGL
function(lvgl_port_create_c_image image_path output_path color_format compression)
#Get Python
idf_build_get_property(python PYTHON)
#Get LVGL version
idf_build_get_property(build_components BUILD_COMPONENTS)
if(lvgl IN_LIST build_components)
set(lvgl_name lvgl) # Local component
set(lvgl_ver $ENV{LVGL_VERSION}) # Get the version from env variable (set from LVGL v9.2)
else()
set(lvgl_name lvgl__lvgl) # Managed component
idf_component_get_property(lvgl_ver ${lvgl_name} COMPONENT_VERSION) # Get the version from esp-idf build system
endif()
if("${lvgl_ver}" STREQUAL "")
message("Could not determine LVGL version, assuming v9.x")
set(lvgl_ver "9.0.0")
endif()
get_filename_component(image_full_path ${image_path} ABSOLUTE)
get_filename_component(output_full_path ${output_path} ABSOLUTE)
if(NOT EXISTS ${image_full_path})
message(FATAL_ERROR "Input image (${image_full_path}) not exists!")
endif()
message(STATUS "Generating C array image: ${image_path}")
#Create C array image by LVGL version
if(lvgl_ver VERSION_LESS "9.0.0")
if(CONFIG_LV_COLOR_16_SWAP)
set(color_format "RGB565SWAP")
else()
set(color_format "RGB565")
endif()
execute_process(COMMAND git clone https://github.com/W-Mai/lvgl_image_converter.git "${CMAKE_BINARY_DIR}/lvgl_image_converter")
execute_process(COMMAND git checkout 9174634e9dcc1b21a63668969406897aad650f35 WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/lvgl_image_converter" OUTPUT_QUIET)
execute_process(COMMAND ${python} -m pip install --upgrade pip)
execute_process(COMMAND ${python} -m pip install pillow==10.3.0)
#execute_process(COMMAND python -m pip install -r "${CMAKE_BINARY_DIR}/lvgl_image_converter/requirements.txt")
execute_process(COMMAND ${python} "${CMAKE_BINARY_DIR}/lvgl_image_converter/lv_img_conv.py"
-ff C
-f true_color_alpha
-cf ${color_format}
-o ${output_full_path}
${image_full_path})
else()
idf_component_get_property(lvgl_dir ${lvgl_name} COMPONENT_DIR)
get_filename_component(script_path ${lvgl_dir}/scripts/LVGLImage.py ABSOLUTE)
set(lvglimage_py ${python} ${script_path})
#Install dependencies
execute_process(COMMAND ${python} -m pip install pypng lz4 OUTPUT_QUIET)
execute_process(COMMAND ${lvglimage_py}
--ofmt=C
--cf=${color_format}
--compress=${compression}
-o ${output_full_path}
${image_full_path})
endif()
endfunction()
# lvgl_port_add_images
#
# Add all images to build
function(lvgl_port_add_images component output_path)
#Add images to sources
file(GLOB_RECURSE IMAGE_SOURCES ${output_path}*.c)
target_sources(${component} PRIVATE ${IMAGE_SOURCES})
idf_build_set_property(COMPILE_OPTIONS "-DLV_LVGL_H_INCLUDE_SIMPLE=1" APPEND)
endfunction()

View File

@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_system.h"
#include "esp_log.h"
#include "esp_err.h"
@ -14,21 +13,16 @@
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#include "lvgl.h"
static const char *TAG = "LVGL";
#define ESP_LVGL_PORT_TASK_MUX_DELAY_MS 10000
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct lvgl_port_ctx_s {
TaskHandle_t lvgl_task;
SemaphoreHandle_t lvgl_mux;
SemaphoreHandle_t task_mux;
esp_timer_handle_t tick_timer;
bool running;
int task_max_sleep_ms;
@ -69,18 +63,14 @@ esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)
if (lvgl_port_ctx.task_max_sleep_ms == 0) {
lvgl_port_ctx.task_max_sleep_ms = 500;
}
/* LVGL semaphore */
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!");
/* Task semaphore */
lvgl_port_ctx.task_mux = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.task_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL task sem fail!");
BaseType_t res;
if (cfg->task_affinity < 0) {
res = xTaskCreate(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task);
res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL);
} else {
res = xTaskCreatePinnedToCore(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task, cfg->task_affinity);
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!");
@ -128,17 +118,10 @@ esp_err_t lvgl_port_deinit(void)
/* Stop running task */
if (lvgl_port_ctx.running) {
lvgl_port_ctx.running = false;
} else {
lvgl_port_task_deinit();
}
/* Wait for stop task */
if (xSemaphoreTake(lvgl_port_ctx.task_mux, pdMS_TO_TICKS(ESP_LVGL_PORT_TASK_MUX_DELAY_MS)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to stop LVGL task");
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Stopped LVGL task");
lvgl_port_task_deinit();
return ESP_OK;
}
@ -156,26 +139,6 @@ void lvgl_port_unlock(void)
xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux);
}
esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param)
{
ESP_LOGE(TAG, "Task wake is not supported, when used LVGL8!");
return ESP_ERR_NOT_SUPPORTED;
}
IRAM_ATTR bool lvgl_port_task_notify(uint32_t value)
{
BaseType_t need_yield = pdFALSE;
// Notify LVGL task
if (xPortInIsrContext() == pdTRUE) {
xTaskNotifyFromISR(lvgl_port_ctx.lvgl_task, value, eNoAction, &need_yield);
} else {
xTaskNotify(lvgl_port_ctx.lvgl_task, value, eNoAction);
}
return (need_yield == pdTRUE);
}
/*******************************************************************************
* Private functions
*******************************************************************************/
@ -184,13 +147,6 @@ static void lvgl_port_task(void *arg)
{
uint32_t task_delay_ms = lvgl_port_ctx.task_max_sleep_ms;
/* Take the task semaphore */
if (xSemaphoreTake(lvgl_port_ctx.task_mux, 0) != pdTRUE) {
ESP_LOGE(TAG, "Failed to take LVGL task sem");
lvgl_port_task_deinit();
vTaskDelete( NULL );
}
ESP_LOGI(TAG, "Starting LVGL task");
lvgl_port_ctx.running = true;
while (lvgl_port_ctx.running) {
@ -206,8 +162,7 @@ static void lvgl_port_task(void *arg)
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
/* Give semaphore back */
xSemaphoreGive(lvgl_port_ctx.task_mux);
lvgl_port_task_deinit();
/* Close task */
vTaskDelete( NULL );
@ -218,9 +173,6 @@ static void lvgl_port_task_deinit(void)
if (lvgl_port_ctx.lvgl_mux) {
vSemaphoreDelete(lvgl_port_ctx.lvgl_mux);
}
if (lvgl_port_ctx.task_mux) {
vSemaphoreDelete(lvgl_port_ctx.task_mux);
}
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
/* Deinitialize LVGL */

View File

@ -4,27 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "esp_idf_version.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_lcd_panel_rgb.h"
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
#include "esp_lcd_mipi_dsi.h"
#endif
#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0))
#define LVGL_PORT_HANDLE_FLUSH_READY 0
@ -39,10 +25,8 @@ static const char *TAG = "LVGL";
*******************************************************************************/
typedef struct {
lvgl_port_disp_type_t disp_type; /* Display type */
esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */
esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */
esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */
lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */
lv_disp_drv_t disp_drv; /* LVGL display driver */
lv_color_t *trans_buf; /* Buffer send to driver */
@ -53,16 +37,9 @@ typedef struct {
/*******************************************************************************
* Function definitions
*******************************************************************************/
static lv_disp_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg);
static lvgl_port_display_ctx_t *lvgl_port_get_display_ctx(lv_disp_t *disp);
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static bool lvgl_port_flush_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
#endif
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);
@ -74,83 +51,111 @@ static void lvgl_port_pix_monochrome_callback(lv_disp_drv_t *drv, uint8_t *buf,
lv_disp_t *lvgl_port_add_disp(const lvgl_port_display_cfg_t *disp_cfg)
{
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
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);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_OTHER;
/* 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;
assert(disp_ctx->io_handle != NULL);
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
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_io_ready_callback,
};
/* Register done callback */
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv);
#endif
}
return disp;
}
lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_dsi_cfg_t *dsi_cfg)
{
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_DSI;
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
const esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_panel_ready_callback,
};
/* Register done callback */
esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, &disp_ctx->disp_drv);
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!");
#endif
}
return disp;
}
lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_rgb_cfg_t *rgb_cfg)
{
assert(rgb_cfg != NULL);
const lvgl_port_disp_priv_cfg_t priv_cfg = {
.avoid_tearing = rgb_cfg->flags.avoid_tearing,
/* Register done callback */
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_ready_callback,
};
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = lvgl_port_get_display_ctx(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_RGB;
#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
/* Register done callback */
const esp_lcd_rgb_panel_event_callbacks_t vsync_cbs = {
.on_vsync = lvgl_port_flush_vsync_ready_callback,
};
const esp_lcd_rgb_panel_event_callbacks_t bb_cbs = {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
.on_bounce_frame_finish = lvgl_port_flush_vsync_ready_callback,
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, &disp_ctx->disp_drv);
#endif
};
if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, &disp_ctx->disp_drv));
} else {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, &disp_ctx->disp_drv));
/* 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);
}
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!");
#endif
}
return disp;
@ -196,147 +201,8 @@ void lvgl_port_flush_ready(lv_disp_t *disp)
* Private functions
*******************************************************************************/
static lvgl_port_display_ctx_t *lvgl_port_get_display_ctx(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;
return disp_ctx;
}
static lv_disp_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_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;
uint32_t buffer_size = 0;
SemaphoreHandle_t trans_sem = NULL;
assert(disp_cfg != 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!");
memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t));
disp_ctx->io_handle = disp_cfg->io_handle;
disp_ctx->panel_handle = disp_cfg->panel_handle;
disp_ctx->control_handle = disp_cfg->control_handle;
disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy;
disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x;
disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y;
disp_ctx->trans_size = disp_cfg->trans_size;
buffer_size = disp_cfg->buffer_size;
/* Use RGB internal buffers for avoid tearing effect */
if (priv_cfg && priv_cfg->avoid_tearing) {
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
buffer_size = disp_cfg->hres * disp_cfg->vres;
ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, "Get RGB buffers failed");
#endif
} else {
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(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(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, 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;
}
/* 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 == 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;
} else if (disp_cfg->flags.direct_mode) {
/* When using direct_mode, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Direct mode must using full buffer!");
disp_ctx->disp_drv.direct_mode = 1;
} else if (disp_cfg->flags.full_refresh) {
/* When using full_refresh, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Full refresh must using full buffer!");
disp_ctx->disp_drv.full_refresh = 1;
}
disp = lv_disp_drv_register(&disp_ctx->disp_drv);
/* Apply rotation from initial display configuration */
lvgl_port_update_callback(&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;
}
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
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;
@ -352,38 +218,6 @@ static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io
return false;
}
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t taskAwake = pdFALSE;
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
lvgl_port_display_ctx_t *disp_ctx = disp_drv->user_data;
assert(disp_ctx != NULL);
lv_disp_flush_ready(disp_drv);
if (disp_ctx->trans_size && disp_ctx->trans_sem) {
xSemaphoreGiveFromISR(disp_ctx->trans_sem, &taskAwake);
}
return false;
}
#endif
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static bool lvgl_port_flush_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t need_yield = pdFALSE;
lv_disp_drv_t *disp_drv = (lv_disp_drv_t *)user_ctx;
assert(disp_drv != NULL);
need_yield = lvgl_port_task_notify(ULONG_MAX);
return (need_yield == pdTRUE);
}
#endif
#endif
static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_map)
@ -414,22 +248,8 @@ static void lvgl_port_flush_callback(lv_disp_drv_t *drv, const lv_area_t *area,
lv_color_t *from = color_map;
lv_color_t *to = NULL;
if (disp_ctx->trans_size == 0) {
if (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB && (drv->direct_mode || drv->full_refresh)) {
if (lv_disp_flush_is_last(drv)) {
/* If the interface is I80 or SPI, this step cannot be used for drawing. */
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map);
/* Waiting for the last frame buffer to complete transmission */
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
} else {
esp_lcd_panel_draw_bitmap(disp_ctx->panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map);
}
if (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB) {
lv_disp_flush_ready(drv);
}
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);
@ -463,36 +283,36 @@ static void lvgl_port_update_callback(lv_disp_drv_t *drv)
assert(drv);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)drv->user_data;
assert(disp_ctx != NULL);
esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle);
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(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
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(control_handle, !disp_ctx->rotation.swap_xy);
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
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(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
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(control_handle, !disp_ctx->rotation.swap_xy);
esp_lcd_panel_swap_xy(panel_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(panel_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
esp_lcd_panel_mirror(panel_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
}
break;
}

View File

@ -4,14 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_keyboard.h"

View File

@ -4,34 +4,25 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#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/portmacro.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#include "lvgl.h"
static const char *TAG = "LVGL";
#define ESP_LVGL_PORT_TASK_MUX_DELAY_MS 10000
/*******************************************************************************
* Types definitions
*******************************************************************************/
typedef struct lvgl_port_ctx_s {
TaskHandle_t lvgl_task;
SemaphoreHandle_t lvgl_mux;
SemaphoreHandle_t timer_mux;
QueueHandle_t lvgl_queue;
SemaphoreHandle_t task_init_mux;
esp_timer_handle_t tick_timer;
bool running;
int task_max_sleep_ms;
@ -62,31 +53,24 @@ esp_err_t lvgl_port_init(const lvgl_port_cfg_t *cfg)
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;
}
/* Timer semaphore */
lvgl_port_ctx.timer_mux = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.timer_mux, ESP_ERR_NO_MEM, err, TAG, "Create timer mutex fail!");
/* LVGL semaphore */
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!");
/* Task init semaphore */
lvgl_port_ctx.task_init_mux = xSemaphoreCreateMutex();
ESP_GOTO_ON_FALSE(lvgl_port_ctx.task_init_mux, ESP_ERR_NO_MEM, err, TAG, "Create LVGL task sem fail!");
/* Task queue */
lvgl_port_ctx.lvgl_queue = xQueueCreate(100, sizeof(lvgl_port_event_t));
ESP_GOTO_ON_FALSE(lvgl_port_ctx.lvgl_queue, ESP_ERR_NO_MEM, err, TAG, "Create LVGL queue fail!");
BaseType_t res;
if (cfg->task_affinity < 0) {
res = xTaskCreate(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task);
res = xTaskCreate(lvgl_port_task, "LVGL task", cfg->task_stack, NULL, cfg->task_priority, NULL);
} else {
res = xTaskCreatePinnedToCore(lvgl_port_task, "taskLVGL", cfg->task_stack, NULL, cfg->task_priority, &lvgl_port_ctx.lvgl_task, cfg->task_affinity);
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!");
@ -134,17 +118,10 @@ esp_err_t lvgl_port_deinit(void)
/* Stop running task */
if (lvgl_port_ctx.running) {
lvgl_port_ctx.running = false;
} else {
lvgl_port_task_deinit();
}
/* Wait for stop task */
if (xSemaphoreTake(lvgl_port_ctx.task_init_mux, pdMS_TO_TICKS(ESP_LVGL_PORT_TASK_MUX_DELAY_MS)) != pdTRUE) {
ESP_LOGE(TAG, "Failed to stop LVGL task");
return ESP_ERR_TIMEOUT;
}
ESP_LOGI(TAG, "Stopped LVGL task");
lvgl_port_task_deinit();
return ESP_OK;
}
@ -162,107 +139,30 @@ void lvgl_port_unlock(void)
xSemaphoreGiveRecursive(lvgl_port_ctx.lvgl_mux);
}
esp_err_t lvgl_port_task_wake(lvgl_port_event_type_t event, void *param)
{
if (!lvgl_port_ctx.lvgl_queue) {
return ESP_ERR_INVALID_STATE;
}
lvgl_port_event_t ev = {
.type = event,
.param = param,
};
if (xPortInIsrContext() == pdTRUE) {
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(lvgl_port_ctx.lvgl_queue, &ev, &xHigherPriorityTaskWoken);
if (xHigherPriorityTaskWoken) {
portYIELD_FROM_ISR( );
}
} else {
xQueueSend(lvgl_port_ctx.lvgl_queue, &ev, 0);
}
return ESP_OK;
}
IRAM_ATTR bool lvgl_port_task_notify(uint32_t value)
{
BaseType_t need_yield = pdFALSE;
// Notify LVGL task
if (xPortInIsrContext() == pdTRUE) {
xTaskNotifyFromISR(lvgl_port_ctx.lvgl_task, value, eNoAction, &need_yield);
} else {
xTaskNotify(lvgl_port_ctx.lvgl_task, value, eNoAction);
}
return (need_yield == pdTRUE);
}
/*******************************************************************************
* Private functions
*******************************************************************************/
static void lvgl_port_task(void *arg)
{
lvgl_port_event_t event;
uint32_t task_delay_ms = 0;
lv_indev_t *indev = NULL;
/* Take the task semaphore */
if (xSemaphoreTake(lvgl_port_ctx.task_init_mux, 0) != pdTRUE) {
ESP_LOGE(TAG, "Failed to take LVGL task sem");
lvgl_port_task_deinit();
vTaskDelete( NULL );
}
/* LVGL init */
lv_init();
/* Tick init */
lvgl_port_tick_init();
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) {
/* Wait for queue or timeout (sleep task) */
TickType_t wait = (pdMS_TO_TICKS(task_delay_ms) >= 1 ? pdMS_TO_TICKS(task_delay_ms) : 1);
xQueueReceive(lvgl_port_ctx.lvgl_queue, &event, wait);
if (lv_display_get_default() && lvgl_port_lock(0)) {
/* Call read input devices */
if (event.type == LVGL_PORT_EVENT_TOUCH) {
xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY);
if (event.param != NULL) {
lv_indev_read(event.param);
} else {
indev = lv_indev_get_next(NULL);
while (indev != NULL) {
lv_indev_read(indev);
indev = lv_indev_get_next(indev);
}
}
xSemaphoreGive(lvgl_port_ctx.timer_mux);
}
/* Handle LVGL */
task_delay_ms = lv_timer_handler();
lvgl_port_unlock();
} else {
task_delay_ms = 1; /*Keep trying*/
}
if (task_delay_ms == LV_NO_TIMER_READY) {
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;
}
/* Minimal dealy for the task. When there is too much events, it takes time for other tasks and interrupts. */
vTaskDelay(1);
vTaskDelay(pdMS_TO_TICKS(task_delay_ms));
}
/* Give semaphore back */
xSemaphoreGive(lvgl_port_ctx.task_init_mux);
lvgl_port_task_deinit();
/* Close task */
vTaskDelete( NULL );
@ -270,18 +170,9 @@ static void lvgl_port_task(void *arg)
static void lvgl_port_task_deinit(void)
{
if (lvgl_port_ctx.timer_mux) {
vSemaphoreDelete(lvgl_port_ctx.timer_mux);
}
if (lvgl_port_ctx.lvgl_mux) {
vSemaphoreDelete(lvgl_port_ctx.lvgl_mux);
}
if (lvgl_port_ctx.task_init_mux) {
vSemaphoreDelete(lvgl_port_ctx.task_init_mux);
}
if (lvgl_port_ctx.lvgl_queue) {
vQueueDelete(lvgl_port_ctx.lvgl_queue);
}
memset(&lvgl_port_ctx, 0, sizeof(lvgl_port_ctx));
#if LV_ENABLE_GC || !LV_MEM_CUSTOM
/* Deinitialize LVGL */
@ -291,10 +182,8 @@ static void lvgl_port_task_deinit(void)
static void lvgl_port_tick_increment(void *arg)
{
xSemaphoreTake(lvgl_port_ctx.timer_mux, portMAX_DELAY);
/* Tell LVGL how many milliseconds have elapsed */
lv_tick_inc(lvgl_port_ctx.timer_period_ms);
xSemaphoreGive(lvgl_port_ctx.timer_mux);
}
static esp_err_t lvgl_port_tick_init(void)

View File

@ -88,7 +88,6 @@ lv_indev_t *lvgl_port_add_navigation_buttons(const lvgl_port_nav_btns_cfg_t *but
/* Register a touchpad input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
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);
@ -111,7 +110,7 @@ err:
}
}
return NULL;
return buttons_ctx->indev;
}
esp_err_t lvgl_port_remove_navigation_buttons(lv_indev_t *buttons)
@ -181,9 +180,6 @@ static void lvgl_port_btn_down_handler(void *arg, void *arg2)
ctx->btn_enter = true;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_btn_up_handler(void *arg, void *arg2)
@ -204,7 +200,4 @@ static void lvgl_port_btn_up_handler(void *arg, void *arg2)
ctx->btn_enter = false;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}

View File

@ -4,27 +4,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_heap_caps.h"
#include "esp_idf_version.h"
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lvgl_port.h"
#include "esp_lvgl_port_priv.h"
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
#include "esp_lcd_panel_rgb.h"
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
#include "esp_lcd_mipi_dsi.h"
#endif
#if (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 4, 4)) || (ESP_IDF_VERSION == ESP_IDF_VERSION_VAL(5, 0, 0))
#define LVGL_PORT_HANDLE_FLUSH_READY 0
@ -39,41 +25,26 @@ static const char *TAG = "LVGL";
*******************************************************************************/
typedef struct {
lvgl_port_disp_type_t disp_type; /* Display type */
esp_lcd_panel_io_handle_t io_handle; /* LCD panel IO handle */
esp_lcd_panel_handle_t panel_handle; /* LCD panel handle */
esp_lcd_panel_handle_t control_handle; /* LCD panel control handle */
lvgl_port_rotation_cfg_t rotation; /* Default values of the screen rotation */
lv_color_t *draw_buffs[3]; /* Display draw buffers */
lv_color16_t *draw_buffs[2]; /* Display draw buffers */
lv_display_t *disp_drv; /* LVGL display driver */
lv_display_rotation_t current_rotation;
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 */
unsigned int full_refresh: 1; /* Always make the whole screen redrawn */
unsigned int direct_mode: 1; /* Use screen-sized buffers and draw to absolute coordinates */
unsigned int sw_rotate: 1; /* Use software rotation (slower) or PPA if available */
} flags;
} lvgl_port_display_ctx_t;
/*******************************************************************************
* Function definitions
*******************************************************************************/
static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg);
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static bool lvgl_port_flush_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx);
#endif
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx);
#endif
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_flush_wait_callback(lv_display_t *drv);
static void lvgl_port_disp_size_update_callback(lv_event_t *e);
static void lvgl_port_disp_rotation_update(lvgl_port_display_ctx_t *disp_ctx);
static void lvgl_port_display_invalidate_callback(lv_event_t *e);
/*******************************************************************************
* Public API functions
@ -81,106 +52,94 @@ static void lvgl_port_display_invalidate_callback(lv_event_t *e);
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);
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
disp = lv_display_create(disp_cfg->hres, disp_cfg->vres);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_OTHER;
/* 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!");
assert(disp_cfg->io_handle != NULL);
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
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_io_ready_callback,
};
/* Register done callback */
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, disp);
#endif
/* Apply rotation from initial display configuration */
lvgl_port_disp_rotation_update(disp_ctx);
}
lvgl_port_unlock();
return disp;
}
lv_display_t *lvgl_port_add_disp_dsi(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_dsi_cfg_t *dsi_cfg)
{
lvgl_port_lock(0);
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, NULL);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_DSI;
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
const esp_lcd_dpi_panel_event_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_panel_ready_callback,
};
/* Register done callback */
esp_lcd_dpi_panel_register_event_callbacks(disp_ctx->panel_handle, &cbs, disp);
/* Apply rotation from initial display configuration */
lvgl_port_disp_rotation_update(disp_ctx);
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "MIPI-DSI is supported only on ESP32P4 and from IDF 5.3!");
#endif
}
lvgl_port_unlock();
return disp;
}
lv_display_t *lvgl_port_add_disp_rgb(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_display_rgb_cfg_t *rgb_cfg)
{
lvgl_port_lock(0);
assert(rgb_cfg != NULL);
const lvgl_port_disp_priv_cfg_t priv_cfg = {
.avoid_tearing = rgb_cfg->flags.avoid_tearing,
/* Register done callback */
const esp_lcd_panel_io_callbacks_t cbs = {
.on_color_trans_done = lvgl_port_flush_ready_callback,
};
lv_disp_t *disp = lvgl_port_add_disp_priv(disp_cfg, &priv_cfg);
if (disp != NULL) {
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(disp);
/* Set display type */
disp_ctx->disp_type = LVGL_PORT_DISP_TYPE_RGB;
#if (CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0))
/* Register done callback */
const esp_lcd_rgb_panel_event_callbacks_t vsync_cbs = {
.on_vsync = lvgl_port_flush_vsync_ready_callback,
};
const esp_lcd_rgb_panel_event_callbacks_t bb_cbs = {
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
.on_bounce_frame_finish = lvgl_port_flush_vsync_ready_callback,
#endif
};
if (rgb_cfg->flags.bb_mode && (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2))) {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &bb_cbs, &disp_ctx->disp_drv));
} else {
ESP_ERROR_CHECK(esp_lcd_rgb_panel_register_event_callbacks(disp_ctx->panel_handle, &vsync_cbs, &disp_ctx->disp_drv));
}
#else
ESP_RETURN_ON_FALSE(false, NULL, TAG, "RGB is supported only on ESP32S3 and from IDF 5.0!");
esp_lcd_panel_io_register_event_callbacks(disp_ctx->io_handle, &cbs, disp_ctx->disp_drv);
#endif
/* Set wait callback */
if (disp_ctx->flags.full_refresh || disp_ctx->flags.direct_mode) {
lv_display_set_flush_wait_cb(disp, lvgl_port_flush_wait_callback);
}
/* Apply rotation from initial display configuration */
lvgl_port_disp_rotation_update(disp_ctx);
}
lvgl_port_unlock();
err:
if (ret != ESP_OK) {
if (buf1) {
free(buf1);
}
if (buf2) {
free(buf2);
}
if (disp_ctx) {
free(disp_ctx);
}
}
return disp;
}
@ -201,10 +160,6 @@ esp_err_t lvgl_port_remove_disp(lv_display_t *disp)
free(disp_ctx->draw_buffs[1]);
}
if (disp_ctx->draw_buffs[2]) {
free(disp_ctx->draw_buffs[2]);
}
free(disp_ctx);
return ESP_OK;
@ -220,183 +175,20 @@ void lvgl_port_flush_ready(lv_display_t *disp)
* Private functions
*******************************************************************************/
static lv_display_t *lvgl_port_add_disp_priv(const lvgl_port_display_cfg_t *disp_cfg, const lvgl_port_disp_priv_cfg_t *priv_cfg)
{
esp_err_t ret = ESP_OK;
lv_display_t *disp = NULL;
lv_color_t *buf1 = NULL;
lv_color_t *buf2 = NULL;
uint32_t buffer_size = 0;
assert(disp_cfg != NULL);
assert(disp_cfg->panel_handle != NULL);
assert(disp_cfg->buffer_size > 0);
assert(disp_cfg->hres > 0);
assert(disp_cfg->vres > 0);
buffer_size = disp_cfg->buffer_size;
/* Check supported display color formats */
ESP_RETURN_ON_FALSE(disp_cfg->color_format == 0 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB565 || disp_cfg->color_format == LV_COLOR_FORMAT_RGB888 || disp_cfg->color_format == LV_COLOR_FORMAT_XRGB8888 || disp_cfg->color_format == LV_COLOR_FORMAT_ARGB8888, NULL, TAG, "Not supported display color format!");
lv_color_format_t display_color_format = (disp_cfg->color_format != 0 ? disp_cfg->color_format : LV_COLOR_FORMAT_RGB565);
if (disp_cfg->flags.swap_bytes) {
/* Swap bytes can be used only in RGB656 color format */
ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565, NULL, TAG, "Swap bytes can be used only in display color format RGB565!");
}
if (disp_cfg->flags.buff_dma) {
/* DMA buffer can be used only in RGB656 color format */
ESP_RETURN_ON_FALSE(display_color_format == LV_COLOR_FORMAT_RGB565, NULL, TAG, "DMA buffer can be used only in display color format RGB565 (not alligned copy)!");
}
/* 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!");
memset(disp_ctx, 0, sizeof(lvgl_port_display_ctx_t));
disp_ctx->io_handle = disp_cfg->io_handle;
disp_ctx->panel_handle = disp_cfg->panel_handle;
disp_ctx->control_handle = disp_cfg->control_handle;
disp_ctx->rotation.swap_xy = disp_cfg->rotation.swap_xy;
disp_ctx->rotation.mirror_x = disp_cfg->rotation.mirror_x;
disp_ctx->rotation.mirror_y = disp_cfg->rotation.mirror_y;
disp_ctx->flags.swap_bytes = disp_cfg->flags.swap_bytes;
disp_ctx->flags.sw_rotate = disp_cfg->flags.sw_rotate;
disp_ctx->current_rotation = LV_DISPLAY_ROTATION_0;
uint32_t buff_caps = 0;
#if SOC_PSRAM_DMA_CAPABLE == 0
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!");
}
#endif
if (disp_cfg->flags.buff_dma) {
buff_caps |= MALLOC_CAP_DMA;
}
if (disp_cfg->flags.buff_spiram) {
buff_caps |= MALLOC_CAP_SPIRAM;
}
if (buff_caps == 0) {
buff_caps |= MALLOC_CAP_DEFAULT;
}
/* Use RGB internal buffers for avoid tearing effect */
if (priv_cfg && priv_cfg->avoid_tearing) {
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
buffer_size = disp_cfg->hres * disp_cfg->vres;
ESP_GOTO_ON_ERROR(esp_lcd_rgb_panel_get_frame_buffer(disp_cfg->panel_handle, 2, (void *)&buf1, (void *)&buf2), err, TAG, "Get RGB buffers failed");
#endif
} else {
/* 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(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(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!");
}
disp_ctx->draw_buffs[0] = buf1;
disp_ctx->draw_buffs[1] = buf2;
}
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 == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Monochromatic display must using full buffer!");
disp_ctx->flags.monochrome = 1;
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_FULL);
} else if (disp_cfg->flags.direct_mode) {
/* When using direct_mode, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Direct mode must using full buffer!");
disp_ctx->flags.direct_mode = 1;
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_DIRECT);
} else if (disp_cfg->flags.full_refresh) {
/* When using full_refresh, there must be used full bufer! */
ESP_GOTO_ON_FALSE((disp_cfg->hres * disp_cfg->vres == buffer_size), ESP_ERR_INVALID_ARG, err, TAG, "Full refresh must using full buffer!");
disp_ctx->flags.full_refresh = 1;
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_FULL);
} else {
lv_display_set_buffers(disp, buf1, buf2, buffer_size * sizeof(lv_color_t), LV_DISPLAY_RENDER_MODE_PARTIAL);
}
lv_display_set_color_format(disp, display_color_format);
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_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_INVALIDATE_AREA, disp_ctx);
lv_display_add_event_cb(disp, lvgl_port_display_invalidate_callback, LV_EVENT_REFR_REQUEST, disp_ctx);
lv_display_set_user_data(disp, disp_ctx);
disp_ctx->disp_drv = disp;
/* Use SW rotation */
if (disp_cfg->flags.sw_rotate) {
disp_ctx->draw_buffs[2] = heap_caps_malloc(buffer_size * sizeof(lv_color_t), buff_caps);
ESP_GOTO_ON_FALSE(disp_ctx->draw_buffs[2], ESP_ERR_NO_MEM, err, TAG, "Not enough memory for LVGL buffer (rotation buffer) allocation!");
}
err:
if (ret != ESP_OK) {
if (buf1) {
free(buf1);
}
if (buf2) {
free(buf2);
}
if (disp_ctx->draw_buffs[2]) {
free(disp_ctx->draw_buffs[2]);
}
if (disp_ctx) {
free(disp_ctx);
}
}
return disp;
}
#if LVGL_PORT_HANDLE_FLUSH_READY
static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
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;
}
#if (CONFIG_IDF_TARGET_ESP32P4 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0))
static bool lvgl_port_flush_panel_ready_callback(esp_lcd_panel_handle_t panel_io, esp_lcd_dpi_panel_event_data_t *edata, void *user_ctx)
{
lv_display_t *disp_drv = (lv_display_t *)user_ctx;
assert(disp_drv != NULL);
lv_disp_flush_ready(disp_drv);
return false;
}
#endif
#if CONFIG_IDF_TARGET_ESP32S3 && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
static bool lvgl_port_flush_vsync_ready_callback(esp_lcd_panel_handle_t panel_io, const esp_lcd_rgb_panel_event_data_t *edata, void *user_ctx)
{
BaseType_t need_yield = pdFALSE;
lv_display_t *disp_drv = (lv_display_t *)user_ctx;
assert(disp_drv != NULL);
need_yield = lvgl_port_task_notify(ULONG_MAX);
lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, disp_drv);
return (need_yield == pdTRUE);
}
#endif
#endif
static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area_t *area, uint8_t *color_map)
{
uint8_t *buf = color_map;
lv_color_t *color = (lv_color_t *)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;
@ -434,165 +226,67 @@ static void _lvgl_port_transform_monochrome(lv_display_t *display, const lv_area
}
}
void lvgl_port_rotate_area(lv_display_t *disp, lv_area_t *area)
{
lv_display_rotation_t rotation = lv_display_get_rotation(disp);
int32_t w = lv_area_get_width(area);
int32_t h = lv_area_get_height(area);
int32_t hres = lv_display_get_horizontal_resolution(disp);
int32_t vres = lv_display_get_vertical_resolution(disp);
if (rotation == LV_DISPLAY_ROTATION_90 || rotation == LV_DISPLAY_ROTATION_270) {
vres = lv_display_get_horizontal_resolution(disp);
hres = lv_display_get_vertical_resolution(disp);
}
switch (rotation) {
case LV_DISPLAY_ROTATION_0:
return;
case LV_DISPLAY_ROTATION_90:
area->y2 = vres - area->x1 - 1;
area->x1 = area->y1;
area->x2 = area->x1 + h - 1;
area->y1 = area->y2 - w + 1;
break;
case LV_DISPLAY_ROTATION_180:
area->y2 = vres - area->y1 - 1;
area->y1 = area->y2 - h + 1;
area->x2 = hres - area->x1 - 1;
area->x1 = area->x2 - w + 1;
break;
case LV_DISPLAY_ROTATION_270:
area->x1 = hres - area->y2 - 1;
area->y2 = area->x2;
area->x2 = area->x1 + h - 1;
area->y1 = area->y2 - w + 1;
break;
}
}
static void lvgl_port_flush_wait_callback(lv_display_t *drv)
{
assert(drv != NULL);
if (lv_disp_flush_is_last(drv)) {
/* Waiting for the last frame buffer to complete transmission */
ulTaskNotifyValueClear(NULL, ULONG_MAX);
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
}
}
static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map)
{
assert(drv != NULL);
assert(area != NULL);
assert(color_map != NULL);
lvgl_port_display_ctx_t *disp_ctx = (lvgl_port_display_ctx_t *)lv_display_get_user_data(drv);
assert(disp_ctx != NULL);
int offsetx1 = area->x1;
int offsetx2 = area->x2;
int offsety1 = area->y1;
int offsety2 = area->y2;
/* SW rotation enabled */
if (disp_ctx->flags.sw_rotate && (disp_ctx->current_rotation > LV_DISPLAY_ROTATION_0 || disp_ctx->flags.swap_bytes)) {
/* SW rotation */
if (disp_ctx->draw_buffs[2]) {
int32_t ww = lv_area_get_width(area);
int32_t hh = lv_area_get_height(area);
lv_color_format_t cf = lv_display_get_color_format(drv);
uint32_t w_stride = lv_draw_buf_width_to_stride(ww, cf);
uint32_t h_stride = lv_draw_buf_width_to_stride(hh, cf);
if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_180) {
lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], hh, ww, h_stride, h_stride, LV_DISPLAY_ROTATION_180, cf);
} else if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_90) {
lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_270, cf);
} else if (disp_ctx->current_rotation == LV_DISPLAY_ROTATION_270) {
lv_draw_sw_rotate(color_map, disp_ctx->draw_buffs[2], ww, hh, w_stride, h_stride, LV_DISPLAY_ROTATION_90, cf);
}
color_map = (uint8_t *)disp_ctx->draw_buffs[2];
lvgl_port_rotate_area(drv, (lv_area_t *)area);
offsetx1 = area->x1;
offsetx2 = area->x2;
offsety1 = area->y1;
offsety2 = area->y2;
}
} else if (disp_ctx->flags.swap_bytes) {
//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);
}
/* Transfer data in buffer for monochromatic screen */
/* Transfor data in buffer for monochromatic screen */
if (disp_ctx->flags.monochrome) {
_lvgl_port_transform_monochrome(drv, area, color_map);
}
/* Draw */
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);
/* Call flush ready only in RGB screen when not full refresh or direct mode */
if (disp_ctx->disp_type == LVGL_PORT_DISP_TYPE_RGB && !disp_ctx->flags.full_refresh && !disp_ctx->flags.direct_mode) {
lv_disp_flush_ready(drv);
}
}
static void lvgl_port_disp_rotation_update(lvgl_port_display_ctx_t *disp_ctx)
{
assert(disp_ctx != NULL);
disp_ctx->current_rotation = lv_display_get_rotation(disp_ctx->disp_drv);
if (disp_ctx->flags.sw_rotate) {
return;
}
esp_lcd_panel_handle_t control_handle = (disp_ctx->control_handle ? disp_ctx->control_handle : disp_ctx->panel_handle);
/* Solve rotation screen and touch */
switch (lv_display_get_rotation(disp_ctx->disp_drv)) {
case LV_DISPLAY_ROTATION_0:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
break;
case LV_DISPLAY_ROTATION_90:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
}
break;
case LV_DISPLAY_ROTATION_180:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, disp_ctx->rotation.swap_xy);
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
break;
case LV_DISPLAY_ROTATION_270:
/* Rotate LCD display */
esp_lcd_panel_swap_xy(control_handle, !disp_ctx->rotation.swap_xy);
if (disp_ctx->rotation.swap_xy) {
esp_lcd_panel_mirror(control_handle, disp_ctx->rotation.mirror_x, !disp_ctx->rotation.mirror_y);
} else {
esp_lcd_panel_mirror(control_handle, !disp_ctx->rotation.mirror_x, disp_ctx->rotation.mirror_y);
}
break;
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, disp_ctx->disp_drv);
}
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 *)lv_event_get_user_data(e);
lvgl_port_disp_rotation_update(disp_ctx);
}
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;
static void lvgl_port_display_invalidate_callback(lv_event_t *e)
{
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_DISPLAY, NULL);
/* 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;
}
}

View File

@ -29,7 +29,6 @@ typedef struct {
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);
static void lvgl_port_encoder_knob_handler(void *arg, void *arg2);
/*******************************************************************************
* Public API functions
@ -53,9 +52,6 @@ lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg)
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!");
ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_LEFT, lvgl_port_encoder_knob_handler, encoder_ctx));
ESP_ERROR_CHECK(iot_knob_register_cb(encoder_ctx->knob_handle, KNOB_RIGHT, lvgl_port_encoder_knob_handler, encoder_ctx));
}
/* Encoder Enter */
@ -73,15 +69,12 @@ lv_indev_t *lvgl_port_add_encoder(const lvgl_port_encoder_cfg_t *encoder_cfg)
/* Register a encoder input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_ENCODER);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
lv_indev_set_read_cb(indev, lvgl_port_encoder_read);
lv_indev_set_disp(indev, encoder_cfg->disp);
lv_indev_set_user_data(indev, encoder_ctx);
encoder_ctx->indev = indev;
lvgl_port_unlock();
return indev;
err:
if (ret != ESP_OK) {
if (encoder_ctx->knob_handle != NULL) {
@ -96,7 +89,7 @@ err:
free(encoder_ctx);
}
}
return NULL;
return encoder_ctx->indev;
}
esp_err_t lvgl_port_remove_encoder(lv_indev_t *encoder)
@ -157,9 +150,6 @@ static void lvgl_port_encoder_btn_down_handler(void *arg, void *arg2)
ctx->btn_enter = true;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2)
@ -172,14 +162,4 @@ static void lvgl_port_encoder_btn_up_handler(void *arg, void *arg2)
ctx->btn_enter = false;
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}
static void lvgl_port_encoder_knob_handler(void *arg, void *arg2)
{
lvgl_port_encoder_ctx_t *ctx = (lvgl_port_encoder_ctx_t *) arg2;
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, ctx->indev);
}

View File

@ -26,7 +26,6 @@ typedef struct {
*******************************************************************************/
static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data);
static void lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp);
/*******************************************************************************
* Public API functions
@ -34,8 +33,7 @@ static void lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp);
lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)
{
esp_err_t ret = ESP_OK;
lv_indev_t *indev = NULL;
lv_indev_t *indev;
assert(touch_cfg != NULL);
assert(touch_cfg->disp != NULL);
assert(touch_cfg->handle != NULL);
@ -48,33 +46,16 @@ lv_indev_t *lvgl_port_add_touch(const lvgl_port_touch_cfg_t *touch_cfg)
}
touch_ctx->handle = touch_cfg->handle;
if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
/* Register touch interrupt callback */
ret = esp_lcd_touch_register_interrupt_callback_with_data(touch_ctx->handle, lvgl_port_touch_interrupt_callback, touch_ctx);
ESP_GOTO_ON_ERROR(ret, err, TAG, "Error in register touch interrupt.");
}
lvgl_port_lock(0);
/* Register a touchpad input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
/* Event mode can be set only, when touch interrupt enabled */
if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
}
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();
err:
if (ret != ESP_OK) {
if (touch_ctx) {
free(touch_ctx);
}
}
return indev;
}
@ -88,11 +69,6 @@ esp_err_t lvgl_port_remove_touch(lv_indev_t *touch)
lv_indev_delete(touch);
lvgl_port_unlock();
if (touch_ctx->handle->config.int_gpio_num != GPIO_NUM_NC) {
/* Unregister touch interrupt callback */
esp_lcd_touch_register_interrupt_callback(touch_ctx->handle, NULL);
}
if (touch_ctx) {
free(touch_ctx);
}
@ -129,11 +105,3 @@ static void lvgl_port_touchpad_read(lv_indev_t *indev_drv, lv_indev_data_t *data
data->state = LV_INDEV_STATE_RELEASED;
}
}
static void IRAM_ATTR lvgl_port_touch_interrupt_callback(esp_lcd_touch_handle_t tp)
{
lvgl_port_touch_ctx_t *touch_ctx = (lvgl_port_touch_ctx_t *) tp->config.user_data;
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, touch_ctx->indev);
}

View File

@ -4,14 +4,10 @@
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include "esp_log.h"
#include "esp_err.h"
#include "esp_check.h"
#include "esp_lvgl_port.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_keyboard.h"
@ -95,7 +91,6 @@ lv_indev_t *lvgl_port_add_usb_hid_mouse_input(const lvgl_port_hid_mouse_cfg_t *m
/* Register a mouse input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
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);
@ -129,7 +124,6 @@ lv_indev_t *lvgl_port_add_usb_hid_keyboard_input(const lvgl_port_hid_keyboard_cf
/* Register a mouse input device */
indev = lv_indev_create();
lv_indev_set_type(indev, LV_INDEV_TYPE_KEYPAD);
lv_indev_set_mode(indev, LV_INDEV_MODE_EVENT);
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);
@ -331,8 +325,6 @@ static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t h
}
}
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, hid_ctx->kb.indev);
} 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)) {
@ -341,9 +333,6 @@ static void lvgl_port_usb_hid_host_interface_callback(hid_host_device_handle_t h
hid_ctx->mouse.left_button = mouse->buttons.button1;
hid_ctx->mouse.x += mouse->x_displacement;
hid_ctx->mouse.y += mouse->y_displacement;
/* Wake LVGL task, if needed */
lvgl_port_task_wake(LVGL_PORT_EVENT_TOUCH, hid_ctx->mouse.indev);
}
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:

View File

@ -1,81 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL ARGB8888 simple fill for ESP32 processor
.section .text
.align 4
.global lv_color_blend_to_argb8888_esp
.type lv_color_blend_to_argb8888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_argb8888(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_argb8888_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint32_t
l32i.n a5, a2, 12 // a5 - dest_h in uint32_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 2 // a11 - dest_w_bytes = sizeof(uint32_t) * dest_w
movi a7, 0xff000000 // oppactiy mask
or a10, a7, a8 // apply oppacity
srli a9, a4, 2 // a9 - loop_len = dest_w / 4
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 8 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 12 // save 32 bits from a10 to dest_buff a3
addi.n a3, a3, 16 // increment dest_buff pointer by 16 bytes
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_mod_4_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return

View File

@ -1,328 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL ARGB8888 simple fill for ESP32S3 processor
.section .text
.align 4
.global lv_color_blend_to_argb8888_esp
.type lv_color_blend_to_argb8888_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_argb8888(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_argb8888_esp:
entry a1, 32
ee.zero.q q0 // dummy TIE instruction, to enable the TIE
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint32_t
l32i.n a5, a2, 12 // a5 - dest_h in uint32_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 2 // a11 - dest_w_bytes = sizeof(uint32_t) * dest_w
movi a7, 0xff000000 // oppactiy mask
or a10, a7, a8 // apply oppacity
// Check for short lengths
// dest_w should be at least 8, othewise it's not worth using esp32s3 TIE
bgei a4, 8, _esp32s3_implementation // Branch if dest_w is greater than or equal to 8
j .lv_color_blend_to_argb8888_esp32_body // Jump to esp32 implementation
_esp32s3_implementation:
ee.movi.32.q q0, a10, 0 // fill q0 register from a10 by 32 bits
ee.movi.32.q q0, a10, 1
ee.movi.32.q q0, a10, 2
ee.movi.32.q q0, a10, 3
// Check dest_buff alignment
movi.n a7, 0xf // 0xf alignment mask (16-byte alignment)
and a15, a7, a3 // 16-byte alignment mask AND dest_buff pointer
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
// Check dest_stride alignment
and a15, a7, a6 // 16-byte alignment mask AND dest_stride
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
// Check dest_w_bytes alignment
and a15, a7, a11 // 16-byte alignment mask AND dest_w_bytes
bnez a15, _unaligned_by_4byte // branch if a15 not equals to zero
//**********************************************************************************************************************
// all aligned, the most ideal case
// dest_buff (a3) - 16-byte aligned
// dest_stride (a6) - 16-byte multiple
// dest_w (a4) - 16-byte multiple
srli a9, a4, 2 // a9 - loop_len = dest_w / 4
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop_aligned:
loopnez a9, ._main_loop_aligned // 16 bytes (4 argb8888) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_aligned:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
_unaligned_by_4byte:
// Check dest_buff alignment
movi.n a7, 0x3 // 0x3 alignment mask (4-byte alignment)
and a15, a7, a3 // 4-byte alignment mask AND dest_buff pointer
bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero
// Check dest_stride alignment
and a15, a7, a6 // 4-byte alignment mask AND dest_stride pointer
bnez a15, _unaligned_by_1byte // branch if a15 not equals to zero
//**********************************************************************************************************************
// either dest_buff or dest_stride is not 16-byte aligned
// dest_w is always 4-byte multiple
// all of the following are 4-byte aligned
// dest_buff (a3) - 16-byte, or 4-byte aligned
// dest_stride (a6) - 16-byte, or 4-byte multiple
// dest_w (a4) - 4-byte multiple
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
movi.n a7, 0xf // 0xf alignment mask
.outer_loop_aligned_by_4byte:
// alignment check
and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer
mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes
beqz a15, _dest_buff_aligned_by_4byte // branch if a15 equals to zero
movi.n a14, 16 // a14 - 16
sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment)
// keep setting until dest_buff is aligned
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a15, 3, _aligning_mod_8_check_4byte // branch if 3-rd bit of unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_aligning_mod_8_check_4byte:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a15, 2, _aligning_mod_4_check_4byte // branch if 2-nd bit unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligning_mod_4_check_4byte:
_dest_buff_aligned_by_4byte:
// Calculate main loop_len
srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16
// Main loop
loopnez a9, ._main_loop_unaligned_by_4byte // 16 bytes (4 argb8888) in one loop
ee.vst.128.ip q0, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_unaligned_by_4byte:
// Check modulo 8 of the dest_w, if - then set 8 bytes
bbci a12, 3, _aligned_mod_8_check_4byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear
ee.vst.l.64.ip q0, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
_aligned_mod_8_check_4byte:
// Check modulo 4 of the dest_w, if - then set 4 bytes
bbci a12, 2, _aligned_mod_4_check_4byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligned_mod_4_check_4byte:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned_by_4byte
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
_unaligned_by_1byte:
//**********************************************************************************************************************
// either dest_buff or dest_stride is not 4-byte aligned
// dest_w is always 4-byte multiple
// dest_buff (a3) - 4-byte, or 1-byte aligned
// dest_stride (a6) - 4-byte, or 1-byte multiple
// dest_w (a4) - 4-byte multiple
mov a13, a3
ee.zero.q q1 // clear q1
ee.orq q1, q1, q0 // copy q0 to q1
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
movi.n a7, 0xf // 0xf alignment mask
.outer_loop_aligned_by_1byte:
// alignment check
and a15, a7, a3 // 0xf (alignment mask) AND dest_buff pointer
mov a12, a11 // a12 - local_dest_w_bytes = dest_w_bytes
beqz a15, _dest_buff_aligned_by_1byte // branch if a15 equals to zero
movi.n a14, 16 // a14 - 16
sub a15, a14, a15 // a15 = 16 - unalignment (lower 4 bits of dest_buff address)
sub a12, a12, a15 // local_dest_w_bytes = len - (16 - unalignment)
// keep setting until dest_buff is aligned
// Check modulo 8 of the unalignment, if - then set 8 bytes
bbci a15, 3, _aligning_mod_8_check_1byte// branch if 3-rd bit of unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 4 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_aligning_mod_8_check_1byte:
// Check modulo 4 of the unalignment, if - then set 4 bytes
bbci a15, 2, _aligning_mod_4_check_1byte // branch if 2-nd bit unalignment a15 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligning_mod_4_check_1byte:
// Check modulo 2 and 1 (the following 2 ifs do the same correction)
// modulo 2 and modulo 1 requires the same action, just once
bbci a15, 1, _aligning_mod_2_check_1byte
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
j _dest_buff_aligned_by_1byte
_aligning_mod_2_check_1byte:
bbci a15, 0, _dest_buff_aligned_by_1byte
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_dest_buff_aligned_by_1byte:
// Shift q reg, allowing to set 16-byte unaligned adata
wur.sar_byte a15 // apply unalignment to the SAR_BYTE
ee.src.q q2, q0, q1 // shift concat. of q0 and q1 to q2 by SAR_BYTE amount
// Calculate main loop_len
srli a9, a12, 4 // a9 - loop_len = local_dest_w_bytes / 16
// Main loop
loopnez a9, ._main_loop_unaligned_by_1byte // 16 bytes (4 argb8888) in one loop
ee.vst.128.ip q2, a3, 16 // store 16 bytes from q0 to dest_buff a3
._main_loop_unaligned_by_1byte:
// Firstly check mod 0 and mod 1 - correcting the aligned memory access
// Go back in one Byte, allow to correct after ee.vst.128.ip aligned access
addi a3, a3, -4
// Check modulo 2 of the dest_w, if - then set 2 bytes
// set SSSS in 0xSSSS0000
bbci a12, 1, _aligned_mod_2_check_1byte // branch if 1-st bit of dest_w a12 is clear
srli a14, a10, 16 // shift a10 in 16, allowing s16i (saving of lower 16 bits)
s16i a14, a3, 2 // save 16 bits from a10 to dest_buff a3, offset 2 bytes
// Check modulo 1 of the dest_w, if - then set 1 byte
// additionally set SS in 0x0000SS00
bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear
srli a14, a10, 8 // shift a10 in 8, allowing s8i
s8i a14, a3, 1 // save 8 bits from a10 to dest_buff a3, offset 1 byte
j _aligned_end
_aligned_mod_2_check_1byte:
// Check modulo 1 of the dest_w, if - then set 1 byte
// set SS in 0xSS000000
bbci a12, 0, _aligned_end // branch if 0-th bit of dest_w a12 is clear
srli a14, a10, 24 // shift a10 in 24, allowing s8i (saving of lower 8 bits)
s8i a14, a3, 3 // save 8 bits from a10 to dest_buff a3, offset 3 bytes
_aligned_end:
addi a3, a3, 4 // Increase the pointer back, correction for addi a3, a3, -4
// Check modulo 8 of the dest_w, if - then set 8 bytes
bbci a12, 3, _aligned_mod_8_check_1byte // branch if 3-rd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 4 bytes
//ee.vst.l.64.ip q2, a3, 8 // save lower 64 bits from q0 to dest_buff a3, increase dest_buff pointer by 8 bytes
_aligned_mod_8_check_1byte:
// Check modulo 4 of the dest_w, if - then set 4 bytes
bbci a12, 2, _aligned_mod_4_check_1byte // branch if 2-nd bit of local_dest_w_bytes a12 is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_aligned_mod_4_check_1byte:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop_aligned_by_1byte
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return
.lv_color_blend_to_argb8888_esp32_body:
srli a9, a4, 2 // a9 - loop_len = dest_w / 4
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
.outer_loop:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 8 // save 32 bits from a10 to dest_buff a3
s32i.n a10, a3, 12 // save 32 bits from a10 to dest_buff a3
addi.n a3, a3, 16 // increment dest_buff pointer by 16 bytes
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
s32i.n a10, a3, 4 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from a10 to dest_buff a3, offset 0 bytes
addi.n a3, a3, 4 // increment dest_buff pointer by 4 bytes
_mod_4_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return

View File

@ -1,149 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL RGB565 simple fill for ESP32 processor
.section .text
.align 4
.global lv_color_blend_to_rgb565_esp
.type lv_color_blend_to_rgb565_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_rgb565_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint16_t
l32i.n a5, a2, 12 // a5 - dest_h in uint16_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
// Convert color to rgb656
l8ui a15, a7, 2 // red
movi.n a14, 0xf8
and a13, a15, a14
slli a10, a13, 8
l8ui a15, a7, 0 // blue
and a13, a15, a14
srli a12, a13, 3
add a10, a10, a12
l8ui a15, a7, 1 // green
movi.n a14, 0xfc
and a13, a15, a14
slli a12, a13, 3
add a12, a10, a12 // a12 = 16-bit color
slli a10, a12, 16
movi.n a13, 0xFFFF0000
and a10, a10, a13
or a10, a10, a12 // a10 = 32-bit color (16bit + (16bit << 16))
movi.n a8, 0x3 // a8 = 0x3, dest_buff align mask
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
// cache init
// Prepare main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for original dest_w
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * dest_w
addi a4, a4, -1 // a4-- (decrement a4)
s32i.n a9, a1, 0 // cache.orig.loop_len
s32i.n a11, a1, 4 // cache.orig.dest_w_bytes
// Prepare decreased main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for dest_w - 1
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * (dest_w - 1)
s32i.n a9, a1, 8 // cache.decr.loop_len
s32i.n a11, a1, 12 // cache.decr.dest_w_bytes
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
.outer_loop:
// Check if the des_buff is 2-byte aligned
beqz a7, _dest_buff_2_byte_aligned // branch if a7 is equal to zero
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
l32i.n a9, a1, 8 // a9 = load cache.decr.loop_len
l32i.n a11, a1, 12 // a11 = load cache.decr.dest_w_bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2
j _dest_buff_unaligned
_dest_buff_2_byte_aligned:
l32i.n a9, a1, 0 // a11 = load cache.orig.loop_len
l32i.n a11, a1, 4 // a11 = load cache.orig.dest_w_bytes
_dest_buff_unaligned:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
s32i.n a10, a3, 16 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 16
s32i.n a10, a3, 20 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 20
s32i.n a10, a3, 24 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 24
s32i.n a10, a3, 28 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 28
addi.n a3, a3, 32 // increment dest_buff pointer by 32
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 16 bytes
bbci a11, 4, _mod_16_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
addi.n a3, a3, 16 // increment dest_buff pointer by 16
_mod_16_check:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
addi.n a3, a3, 4 // increment dest_buff pointer by 4
_mod_4_check:
// Check modulo 2 of the dest_w_bytes, if - then set 2 bytes
bbci a11, 1, _mod_2_check // branch if 1-st bit of dest_w_bytes is clear
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_mod_2_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return

View File

@ -1,149 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
// This is LVGL RGB565 simple fill for ESP32 processor
.section .text
.align 4
.global lv_color_blend_to_rgb565_esp
.type lv_color_blend_to_rgb565_esp,@function
// The function implements the following C code:
// void lv_color_blend_to_rgb565(_lv_draw_sw_blend_fill_dsc_t * dsc);
// Input params
//
// dsc - a2
// typedef struct {
// uint32_t opa; l32i 0
// void * dst_buf; l32i 4
// uint32_t dst_w; l32i 8
// uint32_t dst_h; l32i 12
// uint32_t dst_stride; l32i 16
// const void * src_buf; l32i 20
// uint32_t src_stride; l32i 24
// const lv_opa_t * mask_buf; l32i 28
// uint32_t mask_stride; l32i 32
// } asm_dsc_t;
lv_color_blend_to_rgb565_esp:
entry a1, 32
l32i.n a3, a2, 4 // a3 - dest_buff
l32i.n a4, a2, 8 // a4 - dest_w in uint16_t
l32i.n a5, a2, 12 // a5 - dest_h in uint16_t
l32i.n a6, a2, 16 // a6 - dest_stride in bytes
l32i.n a7, a2, 20 // a7 - src_buff (color)
l32i.n a8, a7, 0 // a8 - color as value
slli a11, a4, 1 // a11 - dest_w_bytes = sizeof(uint16_t) * dest_w
// Convert color to rgb656
l8ui a15, a7, 2 // red
movi.n a14, 0xf8
and a13, a15, a14
slli a10, a13, 8
l8ui a15, a7, 0 // blue
and a13, a15, a14
srli a12, a13, 3
add a10, a10, a12
l8ui a15, a7, 1 // green
movi.n a14, 0xfc
and a13, a15, a14
slli a12, a13, 3
add a12, a10, a12 // a12 = 16-bit color
slli a10, a12, 16
movi.n a13, 0xFFFF0000
and a10, a10, a13
or a10, a10, a12 // a10 = 32-bit color (16bit + (16bit << 16))
movi.n a8, 0x3 // a8 = 0x3, dest_buff align mask
sub a6, a6, a11 // dest_stride = dest_stride - dest_w_bytes
// cache init
// Prepare main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for original dest_w
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * dest_w
addi a4, a4, -1 // a4-- (decrement a4)
s32i.n a9, a1, 0 // cache.orig.loop_len
s32i.n a11, a1, 4 // cache.orig.dest_w_bytes
// Prepare decreased main loop length and dest_w_bytes
srli a9, a4, 4 // a9 = loop_len = dest_w / 8, calculate main loop_len for dest_w - 1
slli a11, a4, 1 // a11 = dest_w_bytes = sizeof(uint16_t) * (dest_w - 1)
s32i.n a9, a1, 8 // cache.decr.loop_len
s32i.n a11, a1, 12 // cache.decr.dest_w_bytes
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
.outer_loop:
// Check if the des_buff is 2-byte aligned
beqz a7, _dest_buff_2_byte_aligned // branch if a7 is equal to zero
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
l32i.n a9, a1, 8 // a9 = load cache.decr.loop_len
l32i.n a11, a1, 12 // a11 = load cache.decr.dest_w_bytes
addi.n a3, a3, 2 // increment dest_buff pointer by 2
j _dest_buff_unaligned
_dest_buff_2_byte_aligned:
l32i.n a9, a1, 0 // a11 = load cache.orig.loop_len
l32i.n a11, a1, 4 // a11 = load cache.orig.dest_w_bytes
_dest_buff_unaligned:
// Run main loop which sets 16 bytes in one loop run
loopnez a9, ._main_loop
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
s32i.n a10, a3, 16 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 16
s32i.n a10, a3, 20 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 20
s32i.n a10, a3, 24 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 24
s32i.n a10, a3, 28 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 28
addi.n a3, a3, 32 // increment dest_buff pointer by 32
._main_loop:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 16 bytes
bbci a11, 4, _mod_16_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
s32i.n a10, a3, 8 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 8
s32i.n a10, a3, 12 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 12
addi.n a3, a3, 16 // increment dest_buff pointer by 16
_mod_16_check:
// Finish the remaining bytes out of the loop
// Check modulo 8 of the dest_w_bytes, if - then set 8 bytes
bbci a11, 3, _mod_8_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
s32i.n a10, a3, 4 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 4
addi.n a3, a3, 8 // increment dest_buff pointer by 8 bytes
_mod_8_check:
// Check modulo 4 of the dest_w_bytes, if - then set 4 bytes
bbci a11, 2, _mod_4_check // branch if 2-nd bit of dest_w_bytes is clear
s32i.n a10, a3, 0 // save 32 bits from 32-bit color a10 to dest_buff a3, offset 0
addi.n a3, a3, 4 // increment dest_buff pointer by 4
_mod_4_check:
// Check modulo 2 of the dest_w_bytes, if - then set 2 bytes
bbci a11, 1, _mod_2_check // branch if 1-st bit of dest_w_bytes is clear
s16i a12, a3, 0 // save 16 bits from 16-bit color a12 to dest_buff a3, offset 0
addi.n a3, a3, 2 // increment dest_buff pointer by 2 bytes
_mod_2_check:
add a3, a3, a6 // dest_buff + dest_stride
addi.n a5, a5, -1 // decrease the outer loop
and a7, a8, a3 // a7 = dest_buff AND 0x3 (chck if the address is 4-byte aligned)
bnez a5, .outer_loop
movi.n a2, 1 // return LV_RESULT_OK = 1
retw.n // return

View File

@ -3,39 +3,36 @@
#include "save_bmp.h"
#include "save_png.h"
static void data_pre_processing(lv_draw_buf_t* snapshot, uint16_t bpp, lv_100ask_screenshot_sv_t screenshot_sv);
static void data_pre_processing(lv_image_dsc_t* snapshot, uint16_t bpp, lv_100ask_screenshot_sv_t screenshot_sv);
bool lv_screenshot_create(lv_obj_t* obj, lv_color_format_t cf, lv_100ask_screenshot_sv_t screenshot_sv, const char* filename) {
lv_draw_buf_t* snapshot = lv_snapshot_take(obj, cf);
int32_t width = lv_obj_get_width(obj);
int32_t height = lv_obj_get_height(obj);
lv_image_dsc_t* snapshot = lv_snapshot_take(obj, cf);
if (snapshot) {
data_pre_processing(snapshot, LV_COLOR_DEPTH, screenshot_sv);
if (screenshot_sv == LV_100ASK_SCREENSHOT_SV_PNG) {
if (LV_COLOR_DEPTH == 16) {
lv_screenshot_save_png_file(snapshot->data, width, height, 24, filename);
lv_screenshot_save_png_file(snapshot->data, snapshot->header.w, snapshot->header.h, 24, filename);
} else if (LV_COLOR_DEPTH == 32) {
lv_screenshot_save_png_file(snapshot->data, width, height, 32, filename);
lv_screenshot_save_png_file(snapshot->data, snapshot->header.w, snapshot->header.h, 32, filename);
}
} else if (screenshot_sv == LV_100ASK_SCREENSHOT_SV_BMP) {
if (LV_COLOR_DEPTH == 16) {
lve_screenshot_save_bmp_file(snapshot->data, width, height, 24, filename);
lve_screenshot_save_bmp_file(snapshot->data, snapshot->header.w, snapshot->header.h, 24, filename);
} else if (LV_COLOR_DEPTH == 32) {
lve_screenshot_save_bmp_file(snapshot->data, width, height, 32, filename);
lve_screenshot_save_bmp_file(snapshot->data, snapshot->header.w, snapshot->header.h, 32, filename);
}
}
lv_snapshot_free(snapshot);
return true;
}
lv_draw_buf_destroy(snapshot);
return false;
}
static void data_pre_processing(lv_draw_buf_t* snapshot, uint16_t bpp, lv_100ask_screenshot_sv_t screenshot_sv) {
static void data_pre_processing(lv_image_dsc_t* snapshot, uint16_t bpp, lv_100ask_screenshot_sv_t screenshot_sv) {
if (bpp == 16) {
uint16_t rgb565_data = 0;
uint32_t count = 0;

View File

@ -3,6 +3,7 @@
#include "lvgl.h"
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif

View File

@ -1,5 +1,4 @@
#include "save_bmp.h"
#include <string.h> // for memset()
typedef struct tagBITMAPFILEHEADER {
uint16_t bfType;

@ -1 +1 @@
Subproject commit aa7446344c6ec7631112ef031983ef24077e24d5
Subproject commit 09cb87cdc6a0168a98bc0a3182a8439b13249ead

View File

@ -30,5 +30,6 @@ CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
# LVGL
CONFIG_LV_DEF_REFR_PERIOD=17
CONFIG_LV_DISP_DEF_REFR_PERIOD=17
CONFIG_LV_INDEV_DEF_READ_PERIOD=17
CONFIG_LV_DPI_DEF=139

View File

@ -30,5 +30,6 @@ CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
# LVGL
CONFIG_LV_DEF_REFR_PERIOD=17
CONFIG_LV_DISP_DEF_REFR_PERIOD=17
CONFIG_LV_INDEV_DEF_READ_PERIOD=17
CONFIG_LV_DPI_DEF=139

View File

@ -29,5 +29,6 @@ CONFIG_ESP32S3_SPIRAM_SUPPORT=y
CONFIG_SPIRAM_MODE_OCT=y
CONFIG_SPIRAM_SPEED_80M=y
# LVGL
CONFIG_LV_DEF_REFR_PERIOD=17
CONFIG_LV_DISP_DEF_REFR_PERIOD=17
CONFIG_LV_INDEV_DEF_READ_PERIOD=17
CONFIG_LV_DPI_DEF=216

View File

@ -25,5 +25,6 @@ CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_FLASHMODE_QIO=y
# LVGL
CONFIG_LV_DEF_REFR_PERIOD=17
CONFIG_LV_DISP_DEF_REFR_PERIOD=17
CONFIG_LV_INDEV_DEF_READ_PERIOD=17
CONFIG_LV_DPI_DEF=160

View File

@ -20,4 +20,5 @@ CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
# Hardware defaults
CONFIG_TT_BOARD_CUSTOM=y
# LVGL
CONFIG_LV_DEF_REFR_PERIOD=17
CONFIG_LV_DISP_DEF_REFR_PERIOD=17
CONFIG_LV_INDEV_DEF_READ_PERIOD=17

View File

@ -46,7 +46,11 @@ bool tt_sdcard_mount(const SdCard* sdcard) {
.sdcard = sdcard
};
sdcard_unlock();
return (data.context != NULL);
if (data.context != NULL) {
return true;
} else {
return false;
}
} else {
TT_LOG_E(TAG, "Failed to lock");
return false;

View File

@ -7,7 +7,7 @@ extern "C" {
#endif
// Forward declarations
typedef struct lv_obj_t lv_obj_t;
typedef struct _lv_obj_t lv_obj_t;
typedef void* App;
typedef enum {

View File

@ -39,10 +39,9 @@ static void update_pin_widgets(Gpio* gpio) {
for (int j = 0; j < GPIO_NUM_MAX; ++j) {
int level = gpio->pin_states[j];
lv_obj_t* label = gpio->lv_pins[j];
void* label_user_data = lv_obj_get_user_data(label);
// The user data stores the state, so we can avoid unnecessary updates
if ((void*)level != label_user_data) {
lv_obj_set_user_data(label, (void*)level);
// user_data stores the state, so we can avoid unnecessary updates
if ((void*)level != label->user_data) {
label->user_data = (void*)level;
if (level == 0) {
lv_obj_set_style_text_color(label, lv_color_black(), 0);
} else {

View File

@ -25,12 +25,12 @@ static void update_mode(ScreenshotUi* ui) {
}
static void on_mode_set(lv_event_t* event) {
ScreenshotUi* ui = (ScreenshotUi*)lv_event_get_user_data(event);
ScreenshotUi* ui = (ScreenshotUi*)event->user_data;
update_mode(ui);
}
static void on_start_pressed(lv_event_t* event) {
ScreenshotUi* ui = lv_event_get_user_data(event);
ScreenshotUi* ui = event->user_data;
if (tt_screenshot_is_started()) {
TT_LOG_I(TAG, "Stop screenshot");

View File

@ -47,7 +47,7 @@ static bool is_image_file(const char* filename) {
static void update_views(FilesData* data);
static void on_navigate_up_pressed(lv_event_t* event) {
FilesData* files_data = (FilesData*)lv_event_get_user_data(event);
FilesData* files_data = (FilesData*)event->user_data;
if (strcmp(files_data->current_path, "/") != 0) {
TT_LOG_I(TAG, "Navigating upwards");
char new_absolute_path[MAX_PATH_LENGTH];
@ -62,13 +62,13 @@ static void on_exit_app_pressed(TT_UNUSED lv_event_t* event) {
loader_stop_app();
}
static void on_file_pressed(lv_event_t* event) {
lv_event_code_t code = lv_event_get_code(event);
static void on_file_pressed(lv_event_t* e) {
lv_event_code_t code = lv_event_get_code(e);
if (code == LV_EVENT_CLICKED) {
lv_obj_t* button = lv_event_get_current_target_obj(event);
lv_obj_t* button = e->current_target;
FilesData* files_data = lv_obj_get_user_data(button);
struct dirent* dir_entry = lv_event_get_user_data(event);
struct dirent* dir_entry = e->user_data;
TT_LOG_I(TAG, "Pressed %s %d", dir_entry->d_name, dir_entry->d_type);
switch (dir_entry->d_type) {

View File

@ -14,7 +14,7 @@
#define TAG "wifi_connect"
static void on_connect(lv_event_t* event) {
WifiConnect* wifi = (WifiConnect*)lv_event_get_user_data(event);
WifiConnect* wifi = (WifiConnect*)event->user_data;
WifiConnectView* view = &wifi->view;
const char* ssid = lv_textarea_get_text(view->ssid_textarea);
const char* password = lv_textarea_get_text(view->password_textarea);

View File

@ -15,27 +15,27 @@ static void on_enable_switch_changed(lv_event_t* event) {
lv_obj_t* enable_switch = lv_event_get_target(event);
if (code == LV_EVENT_VALUE_CHANGED) {
bool is_on = lv_obj_has_state(enable_switch, LV_STATE_CHECKED);
WifiManageBindings* bindings = (WifiManageBindings*)lv_event_get_user_data(event);
WifiManageBindings* bindings = (WifiManageBindings*)event->user_data;
bindings->on_wifi_toggled(is_on);
}
}
static void on_disconnect_pressed(lv_event_t* event) {
WifiManageBindings* bindings = (WifiManageBindings*)lv_event_get_user_data(event);
WifiManageBindings* bindings = (WifiManageBindings*)event->user_data;
bindings->on_disconnect();
}
// region Secondary updates
static void connect(lv_event_t* event) {
lv_obj_t* button = lv_event_get_current_target_obj(event);
lv_obj_t* button = event->current_target;
// Assumes that the second child of the button is a label ... risky
lv_obj_t* label = lv_obj_get_child(button, 1);
// We get the SSID from the button label because it's safer than alloc'ing
// our own and passing it as the event data
const char* ssid = lv_label_get_text(label);
TT_LOG_I(TAG, "Clicked AP: %s", ssid);
WifiManageBindings* bindings = (WifiManageBindings*)lv_event_get_user_data(event);
WifiManageBindings* bindings = (WifiManageBindings*)event->user_data;
bindings->on_connect_ssid(ssid);
}

View File

@ -25,7 +25,7 @@ void gui_loader_callback(const void* message, void* context) {
LoaderEvent* event = (LoaderEvent*)message;
if (event->type == LoaderEventTypeApplicationShowing) {
App* app = event->app_showing.app;
const AppManifest* app_manifest = tt_app_get_manifest(app);
AppManifest* app_manifest = tt_app_get_manifest(app);
gui_show_app(app, app_manifest->on_show, app_manifest->on_hide);
} else if (event->type == LoaderEventTypeApplicationHiding) {
gui_hide_app();

View File

@ -7,9 +7,8 @@
extern Gui* gui;
static void show_keyboard(lv_event_t* event) {
lv_obj_t* target = lv_event_get_current_target_obj(event);
gui_keyboard_show(target);
lv_obj_scroll_to_view(target, LV_ANIM_ON);
gui_keyboard_show(event->current_target);
lv_obj_scroll_to_view(event->current_target, LV_ANIM_ON);
}
static void hide_keyboard(TT_UNUSED lv_event_t* event) {

View File

@ -262,7 +262,7 @@ static void loader_do_stop_app() {
// Stop current app
App app_to_stop = loader_singleton->app_stack[current_app_index];
const AppManifest* manifest = tt_app_get_manifest(app_to_stop);
AppManifest* manifest = tt_app_get_manifest(app_to_stop);
app_transition_to_state(app_to_stop, AppStateHiding);
app_transition_to_state(app_to_stop, AppStateStopped);

View File

@ -39,7 +39,7 @@ typedef struct {
} LoaderEventAppHiding;
typedef struct {
const AppManifest* manifest;
AppManifest* manifest;
} LoaderEventAppStopped;
typedef struct {

View File

@ -1,4 +1,3 @@
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
#include "statusbar.h"
#include "mutex.h"

View File

@ -1,19 +1,10 @@
#define LV_USE_PRIVATE_API 1 // For actual lv_obj_t declaration
#include "toolbar.h"
#include "services/loader/loader.h"
#include "ui/spacer.h"
#include "ui/style.h"
typedef struct {
lv_obj_t obj;
lv_obj_t* title_label;
lv_obj_t* close_button;
lv_obj_t* close_button_image;
lv_obj_t* action_container;
ToolbarAction* action_array[TOOLBAR_ACTION_LIMIT];
uint8_t action_count;
} Toolbar;
#include "lvgl.h"
static void toolbar_constructor(const lv_obj_class_t* class_p, lv_obj_t* obj);

View File

@ -20,6 +20,16 @@ typedef struct {
void* _Nullable callback_context;
} ToolbarAction;
typedef struct {
lv_obj_t obj;
lv_obj_t* title_label;
lv_obj_t* close_button;
lv_obj_t* close_button_image;
lv_obj_t* action_container;
ToolbarAction* action_array[TOOLBAR_ACTION_LIMIT];
uint8_t action_count;
} Toolbar;
lv_obj_t* tt_toolbar_create(lv_obj_t* parent, const char* title);
lv_obj_t* tt_toolbar_create_for_app(lv_obj_t* parent, App app);
void tt_toolbar_set_title(lv_obj_t* obj, const char* title);