278 lines
10 KiB
Markdown
278 lines
10 KiB
Markdown
# LVGL ESP Portation
|
||
|
||
[](https://components.espressif.com/components/espressif/esp_lvgl_port)
|
||
|
||
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
|
||
* 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))
|
||
|
||
## LVGL Version
|
||
|
||
This component supports **LVGL8** and **LVGL9**. By default, it selects the latest LVGL version. If you want to use a specific version (e.g. latest LVGL8), you can easily put into `idf_component.yml` in your project like this:
|
||
|
||
```
|
||
lvgl/lvgl:
|
||
version: "^8"
|
||
public: true
|
||
```
|
||
|
||
### LVGL Version Compatibility
|
||
|
||
This component is fully compatible with LVGL version 9. All types and functions are used from LVGL9. Some LVGL9 types are not supported in LVGL8 and there are retyping in [`esp_lvgl_port_compatibility.h`](include/esp_lvgl_port_compatibility.h) header file. **Please, be aware, that some draw and object functions are not compatible between LVGL8 and LVGL9.**
|
||
|
||
## Usage
|
||
|
||
### Initialization
|
||
``` c
|
||
const lvgl_port_cfg_t lvgl_cfg = ESP_LVGL_PORT_INIT_CONFIG();
|
||
esp_err_t err = lvgl_port_init(&lvgl_cfg);
|
||
```
|
||
|
||
### Add screen
|
||
|
||
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 */
|
||
esp_lcd_panel_handle_t lcd_panel_handle;
|
||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &lcd_panel_handle));
|
||
|
||
/* Add LCD screen */
|
||
const lvgl_port_display_cfg_t disp_cfg = {
|
||
.io_handle = io_handle,
|
||
.panel_handle = lcd_panel_handle,
|
||
.buffer_size = DISP_WIDTH*DISP_HEIGHT,
|
||
.double_buffer = true,
|
||
.hres = DISP_WIDTH,
|
||
.vres = DISP_HEIGHT,
|
||
.monochrome = false,
|
||
/* Rotation values must be same as used in esp_lcd for initial settings of the screen */
|
||
.rotation = {
|
||
.swap_xy = false,
|
||
.mirror_x = false,
|
||
.mirror_y = false,
|
||
},
|
||
.flags = {
|
||
.buff_dma = true,
|
||
.swap_bytes = false,
|
||
}
|
||
};
|
||
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);
|
||
```
|
||
|
||
### Add touch input
|
||
|
||
Add touch input to the LVGL. It can be called more times for adding more touch inputs.
|
||
``` c
|
||
/* Touch driver initialization */
|
||
...
|
||
esp_lcd_touch_handle_t tp;
|
||
esp_err_t err = esp_lcd_touch_new_i2c_gt911(io_handle, &tp_cfg, &tp);
|
||
|
||
/* Add touch input (for selected screen) */
|
||
const lvgl_port_touch_cfg_t touch_cfg = {
|
||
.disp = disp_handle,
|
||
.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: */
|
||
lvgl_port_remove_touch(touch_handle);
|
||
```
|
||
|
||
### Add buttons input
|
||
|
||
Add buttons input to the LVGL. It can be called more times for adding more buttons inputs for different displays. This feature is available only when the component `espressif/button` was added into the project.
|
||
``` c
|
||
/* Buttons configuration structure */
|
||
const button_config_t bsp_button_config[] = {
|
||
{
|
||
.type = BUTTON_TYPE_ADC,
|
||
.adc_button_config.adc_channel = ADC_CHANNEL_0, // ADC1 channel 0 is GPIO1
|
||
.adc_button_config.button_index = 0,
|
||
.adc_button_config.min = 2310, // middle is 2410mV
|
||
.adc_button_config.max = 2510
|
||
},
|
||
{
|
||
.type = BUTTON_TYPE_ADC,
|
||
.adc_button_config.adc_channel = ADC_CHANNEL_0, // ADC1 channel 0 is GPIO1
|
||
.adc_button_config.button_index = 1,
|
||
.adc_button_config.min = 1880, // middle is 1980mV
|
||
.adc_button_config.max = 2080
|
||
},
|
||
{
|
||
.type = BUTTON_TYPE_ADC,
|
||
.adc_button_config.adc_channel = ADC_CHANNEL_0, // ADC1 channel 0 is GPIO1
|
||
.adc_button_config.button_index = 2,
|
||
.adc_button_config.min = 720, // middle is 820mV
|
||
.adc_button_config.max = 920
|
||
},
|
||
};
|
||
|
||
const lvgl_port_nav_btns_cfg_t btns = {
|
||
.disp = disp_handle,
|
||
.button_prev = &bsp_button_config[0],
|
||
.button_next = &bsp_button_config[1],
|
||
.button_enter = &bsp_button_config[2]
|
||
};
|
||
|
||
/* 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.
|
||
|
||
### Add encoder input
|
||
|
||
Add encoder input to the LVGL. It can be called more times for adding more encoder inputs for different displays. This feature is available only when the component `espressif/knob` was added into the project.
|
||
``` c
|
||
|
||
const button_config_t encoder_btn_config = {
|
||
.type = BUTTON_TYPE_GPIO,
|
||
.gpio_button_config.active_level = false,
|
||
.gpio_button_config.gpio_num = GPIO_BTN_PRESS,
|
||
};
|
||
|
||
const knob_config_t encoder_a_b_config = {
|
||
.default_direction = 0,
|
||
.gpio_encoder_a = GPIO_ENCODER_A,
|
||
.gpio_encoder_b = GPIO_ENCODER_B,
|
||
};
|
||
|
||
/* Encoder configuration structure */
|
||
const lvgl_port_encoder_cfg_t encoder = {
|
||
.disp = disp_handle,
|
||
.encoder_a_b = &encoder_a_b_config,
|
||
.encoder_enter = &encoder_btn_config
|
||
};
|
||
|
||
/* 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.
|
||
|
||
### Add USB HID keyboard and mouse input
|
||
|
||
Add mouse and keyboard input to the LVGL. This feature is available only when the component [usb_host_hid](https://components.espressif.com/components/espressif/usb_host_hid) was added into the project.
|
||
|
||
``` c
|
||
/* USB initialization */
|
||
usb_host_config_t host_config = {
|
||
.skip_phy_setup = false,
|
||
.intr_flags = ESP_INTR_FLAG_LEVEL1,
|
||
};
|
||
ESP_ERROR_CHECK(usb_host_install(&host_config));
|
||
|
||
...
|
||
|
||
/* Add mouse input device */
|
||
const lvgl_port_hid_mouse_cfg_t mouse_cfg = {
|
||
.disp = display,
|
||
.sensitivity = 1, /* Sensitivity of the mouse moving */
|
||
};
|
||
lvgl_port_add_usb_hid_mouse_input(&mouse_cfg);
|
||
|
||
/* Add keyboard input device */
|
||
const lvgl_port_hid_keyboard_cfg_t kb_cfg = {
|
||
.disp = display,
|
||
};
|
||
kb_indev = lvgl_port_add_usb_hid_keyboard_input(&kb_cfg);
|
||
```
|
||
|
||
Keyboard special behavior (when objects are in group):
|
||
- **TAB**: Select next object
|
||
- **SHIFT** + **TAB**: Select previous object
|
||
- **ENTER**: Control object (e.g. click to button)
|
||
- **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.
|
||
|
||
### LVGL API usage
|
||
|
||
Every LVGL calls must be protected with these lock/unlock commands:
|
||
``` c
|
||
/* Wait for the other task done the screen operation */
|
||
lvgl_port_lock(0);
|
||
...
|
||
lv_obj_t * screen = lv_disp_get_scr_act(disp_handle);
|
||
lv_obj_t * obj = lv_label_create(screen);
|
||
...
|
||
/* Screen operation done -> release for the other task */
|
||
lvgl_port_unlock();
|
||
```
|
||
|
||
### Rotating screen
|
||
|
||
LVGL port supports rotation of the display. You can select whether you'd like software rotation or hardware rotation.
|
||
Software rotation requires no additional logic in your `flush_cb` callback.
|
||
|
||
Rotation mode can be selected in the `lvgl_port_display_cfg_t` structure.
|
||
``` c
|
||
const lvgl_port_display_cfg_t disp_cfg = {
|
||
...
|
||
.flags = {
|
||
...
|
||
.sw_rotate = true / false, // true: software; false: hardware
|
||
}
|
||
}
|
||
```
|
||
Display rotation can be changed at runtime.
|
||
|
||
``` c
|
||
lv_disp_set_rotation(disp_handle, LV_DISP_ROT_90);
|
||
```
|
||
|
||
**Note:** During the hardware rotating, the component call [`esp_lcd`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/lcd.html) API. When using software rotation, you cannot use neither `direct_mode` nor `full_refresh` in the driver. See [LVGL documentation](https://docs.lvgl.io/8.3/porting/display.html?highlight=sw_rotate) for more info.
|
||
|
||
### Using PSRAM canvas
|
||
|
||
If the SRAM is insufficient, you can use the PSRAM as a canvas and use a small trans_buffer to carry it, this makes drawing more efficient.
|
||
``` c
|
||
const lvgl_port_display_cfg_t disp_cfg = {
|
||
...
|
||
.buffer_size = DISP_WIDTH * DISP_HEIGHT, // in PSRAM, not DMA-capable
|
||
.trans_size = size, // in SRAM, DMA-capable
|
||
.flags = {
|
||
.buff_spiram = true,
|
||
.buff_dma = false,
|
||
...
|
||
}
|
||
}
|
||
```
|
||
|
||
## Performance
|
||
|
||
Key feature of every graphical application is performance. Recommended settings for improving LCD performance is described in a separate document [here](docs/performance.md).
|