Merge branch 'feat/add_lvgl_filesystem_example' into 'master'

feat(LCD):  Update i80 example of loading image from file system

Closes IDF-8026

See merge request espressif/esp-idf!25661
This commit is contained in:
morris 2023-09-06 22:20:17 +08:00
commit 6f6ed69c62
11 changed files with 177 additions and 55 deletions

View File

@ -1,5 +1,6 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- |
# LVGL porting example (based on i80 interfaced LCD controller)
LVGL is an open-source graphics library for creating modern GUIs. It has plenty of built-in graphical elements with low memory footprint, which is friendly for embedded GUI applications.
@ -26,7 +27,7 @@ This example uses the [esp_timer](https://docs.espressif.com/projects/esp-idf/en
The connection between ESP Board and the LCD is as follows:
```
```text
ESP Board LCD Screen
┌─────────────┐ ┌────────────────┐
│ │ │ │
@ -71,6 +72,8 @@ Run `idf.py menuconfig` to open a terminal UI where you can tune specific config
* `Allocate color data from PSRAM`: Select this option if you want to allocate the LVGL draw buffers from PSRAM.
* `LCD image source from`: Select where to load the image resource. See [Image Resource](#image-resource) for more details.
Run `idf.py -p PORT build flash monitor` to build, flash and monitor the project. A fancy animation will show up on the LCD as expected.
The first time you run `idf.py` for the example will cost extra time as the build system needs to address the component dependencies and downloads the missing components from registry into `managed_components` folder.
@ -99,10 +102,17 @@ I (558) example: Display LVGL animation
This example supports touch screen connected via I2C. You can enable it by running `idf.py menuconfig` and navigating to `Example Configuration -> Enable LCD touch`. When touch is enabled, there will be a new button in the GUI that can restart the animation.
These touch controllers are supported:
* [GT911](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch_gt911)
* [TT21100](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch_tt21100)
* [FT5X06](https://github.com/espressif/esp-bsp/tree/master/components/lcd_touch/esp_lcd_touch_ft5x06)
## Image Resource
This example supports two ways of reading images
* from the [SPIFFS file system](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/spiffs.html). This is the suggested way we use in the example. It may take a little bit longer to load the image because of the bottleneck of the SPI flash read speed, but it will save the binary size.
* from the embedded binary (i.e., pre-decode the image into an array and pack it together with the application firmware). By this way, you can get faster image loading speed at the cost of bloating your application binary. What's worse, if you enabled the [XIP from PSRAM](https://github.com/espressif/esp-idf/tree/master/examples/system/xip_from_psram) feature, it will increase the PSRAM usage as well.
## Troubleshooting
@ -110,4 +120,4 @@ These touch controllers are supported:
This is because of the limited PSRAM bandwidth, compared to the internal SRAM. You can either decrease the PCLK clock `EXAMPLE_LCD_PIXEL_CLOCK_HZ` in [i80_controller_example_main.c](main/i80_controller_example_main.c) or increase the PSRAM working frequency `SPIRAM_SPEED` from the KConfig (e.g. **ESP32S3-Specific** -> **Set RAM clock speed**) or decrease the FPS in LVGL configuration. For illustration, this example has set the refresh period to 100ms in the default sdkconfig file.
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.

View File

@ -1,4 +1,12 @@
file(GLOB_RECURSE IMAGE_SOURCES images/*.c)
set(embedded_images)
if(CONFIG_EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY)
file(GLOB_RECURSE embedded_images images/*.c)
endif()
idf_component_register(SRCS "i80_controller_example_main.c" "lvgl_demo_ui.c" ${IMAGE_SOURCES}
idf_component_register(SRCS "i80_controller_example_main.c" "lvgl_demo_ui.c" ${embedded_images}
INCLUDE_DIRS ".")
if(CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM)
# Create a partition to store the image resources in the filesystem
spiffs_create_partition_image(storage ./images/filesystem FLASH_IN_PROJECT)
endif()

View File

@ -68,4 +68,16 @@ menu "Example Configuration"
bool "FT5X06"
endchoice
choice EXAMPLE_LCD_IMAGE_SOURCE
prompt "LCD image source from"
default EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
help
Select LCD image source
config EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
bool "File system"
config EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY
bool "Embedded binary"
endchoice
endmenu

View File

@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
@ -11,13 +11,13 @@
#include "esp_lcd_panel_io.h"
#include "esp_lcd_panel_vendor.h"
#include "esp_lcd_panel_ops.h"
#include "esp_lcd_touch.h"
#include "esp_spiffs.h"
#include "driver/gpio.h"
#include "driver/i2c.h"
#include "esp_err.h"
#include "esp_log.h"
#include "lvgl.h"
#if CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
#include "driver/i2c.h"
#if CONFIG_EXAMPLE_LCD_TOUCH_CONTROLLER_GT911
#include "esp_lcd_touch_gt911.h"
#elif CONFIG_EXAMPLE_LCD_TOUCH_CONTROLLER_TT21100
@ -25,7 +25,6 @@
#elif CONFIG_EXAMPLE_LCD_TOUCH_CONTROLLER_FT5X06
#include "esp_lcd_touch_ft5x06.h"
#endif
#endif
static const char *TAG = "example";
@ -112,7 +111,7 @@ static void example_lvgl_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_
}
#if CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
static void example_lvgl_touch_cb(lv_indev_drv_t * drv, lv_indev_data_t * data)
static void example_lvgl_touch_cb(lv_indev_drv_t *drv, lv_indev_data_t *data)
{
uint16_t touchpad_x[1] = {0};
uint16_t touchpad_y[1] = {0};
@ -140,19 +139,46 @@ static void example_increase_lvgl_tick(void *arg)
lv_tick_inc(EXAMPLE_LVGL_TICK_PERIOD_MS);
}
void app_main(void)
#if CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
void example_init_filesystem(void)
{
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv; // contains callback functions
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
ESP_LOGI(TAG, "Initializing filesystem");
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = "storage",
.max_files = 5,
.format_if_mount_failed = true
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
// Use settings defined above to initialize and mount SPIFFS filesystem.
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
esp_err_t ret = esp_vfs_spiffs_register(&conf);
if (ret != ESP_OK) {
if (ret == ESP_FAIL) {
ESP_LOGE(TAG, "Failed to mount or format filesystem");
} else if (ret == ESP_ERR_NOT_FOUND) {
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
} else {
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
}
return;
}
size_t total = 0, used = 0;
ret = esp_spiffs_info(conf.partition_label, &total, &used);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s). Formatting...", esp_err_to_name(ret));
esp_spiffs_format(conf.partition_label);
return;
} else {
ESP_LOGI(TAG, "Partition size: total: %zu, used: %zu", total, used);
}
}
#endif // CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
void example_init_i80_bus(esp_lcd_panel_io_handle_t *io_handle, void *user_ctx)
{
ESP_LOGI(TAG, "Initialize Intel 8080 bus");
esp_lcd_i80_bus_handle_t i80_bus = NULL;
esp_lcd_i80_bus_config_t bus_config = {
@ -185,7 +211,7 @@ void app_main(void)
.sram_trans_align = 4,
};
ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
esp_lcd_panel_io_handle_t io_handle = NULL;
esp_lcd_panel_io_i80_config_t io_config = {
.cs_gpio_num = EXAMPLE_PIN_NUM_CS,
.pclk_hz = EXAMPLE_LCD_PIXEL_CLOCK_HZ,
@ -200,12 +226,15 @@ void app_main(void)
.swap_color_bytes = !LV_COLOR_16_SWAP, // Swap can be done in LvGL (default) or DMA
},
.on_color_trans_done = example_notify_lvgl_flush_ready,
.user_ctx = &disp_drv,
.user_ctx = user_ctx,
.lcd_cmd_bits = EXAMPLE_LCD_CMD_BITS,
.lcd_param_bits = EXAMPLE_LCD_PARAM_BITS,
};
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &io_handle));
ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, io_handle));
}
void example_init_lcd_panel(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t *panel)
{
esp_lcd_panel_handle_t panel_handle = NULL;
#if CONFIG_EXAMPLE_LCD_I80_CONTROLLER_ST7789
ESP_LOGI(TAG, "Install LCD driver of st7789");
@ -258,26 +287,28 @@ void app_main(void)
// ILI9341 is very similar to ST7789 and shares the same driver.
// Anything unconventional (such as this custom gamma table) can
// be issued here in user code and need not modify the driver.
esp_lcd_panel_io_tx_param(io_handle, 0xF2, (uint8_t[]) { 0 }, 1); // 3Gamma function disable
esp_lcd_panel_io_tx_param(io_handle, 0x26, (uint8_t[]) { 1 }, 1); // Gamma curve 1 selected
esp_lcd_panel_io_tx_param(io_handle, 0xF2, (uint8_t[]) {
0
}, 1); // 3Gamma function disable
esp_lcd_panel_io_tx_param(io_handle, 0x26, (uint8_t[]) {
1
}, 1); // Gamma curve 1 selected
esp_lcd_panel_io_tx_param(io_handle, 0xE0, (uint8_t[]) { // Set positive gamma
0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00 }, 15);
0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00
}, 15);
esp_lcd_panel_io_tx_param(io_handle, 0xE1, (uint8_t[]) { // Set negative gamma
0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F }, 15);
0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F
}, 15);
#endif
// user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
*panel = panel_handle;
}
#if CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
void example_init_lcd_touch(esp_lcd_touch_handle_t *tp_handle)
{
esp_lcd_touch_handle_t tp = NULL;
esp_lcd_panel_io_handle_t tp_io_handle = NULL;
ESP_LOGI(TAG, "Initialize I2C");
const i2c_config_t i2c_conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = EXAMPLE_I2C_SDA,
@ -315,7 +346,6 @@ void app_main(void)
},
};
/* Initialize touch */
#if CONFIG_EXAMPLE_LCD_TOUCH_CONTROLLER_GT911
ESP_LOGI(TAG, "Initialize touch controller GT911");
@ -327,8 +357,48 @@ void app_main(void)
ESP_LOGI(TAG, "Initialize touch controller FT5X06");
ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
#endif
*tp_handle = tp;
}
#endif // CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
#endif
void app_main(void)
{
static lv_disp_draw_buf_t disp_buf; // contains internal graphic buffer(s) called draw buffer(s)
static lv_disp_drv_t disp_drv; // contains callback functions
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
ESP_LOGI(TAG, "Turn off LCD backlight");
gpio_config_t bk_gpio_config = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = 1ULL << EXAMPLE_PIN_NUM_BK_LIGHT
};
ESP_ERROR_CHECK(gpio_config(&bk_gpio_config));
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_OFF_LEVEL);
#endif // EXAMPLE_PIN_NUM_BK_LIGHT >= 0
#if CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
example_init_filesystem();
#endif // CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
esp_lcd_panel_io_handle_t io_handle = NULL;
example_init_i80_bus(&io_handle, &disp_drv);
esp_lcd_panel_handle_t panel_handle = NULL;
example_init_lcd_panel(io_handle, &panel_handle);
#if CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
esp_lcd_touch_handle_t tp_handle = NULL;
example_init_lcd_touch(&tp_handle);
#endif // CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
// Stub: user can flush pre-defined pattern to the screen before we turn on the screen or backlight
ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
#if EXAMPLE_PIN_NUM_BK_LIGHT >= 0
ESP_LOGI(TAG, "Turn on LCD backlight");
gpio_set_level(EXAMPLE_PIN_NUM_BK_LIGHT, EXAMPLE_LCD_BK_LIGHT_ON_LEVEL);
#endif // EXAMPLE_PIN_NUM_BK_LIGHT >= 0
ESP_LOGI(TAG, "Initialize LVGL library");
lv_init();
@ -338,15 +408,12 @@ void app_main(void)
lv_color_t *buf2 = NULL;
#if CONFIG_EXAMPLE_LCD_I80_COLOR_IN_PSRAM
buf1 = heap_caps_aligned_alloc(EXAMPLE_PSRAM_DATA_ALIGNMENT, EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
#endif
assert(buf1);
#if CONFIG_EXAMPLE_LCD_I80_COLOR_IN_PSRAM
buf2 = heap_caps_aligned_alloc(EXAMPLE_PSRAM_DATA_ALIGNMENT, EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
#else
buf1 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
buf2 = heap_caps_malloc(EXAMPLE_LCD_H_RES * 100 * sizeof(lv_color_t), MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
#endif
#endif // CONFIG_EXAMPLE_LCD_I80_COLOR_IN_PSRAM
assert(buf1);
assert(buf2);
ESP_LOGI(TAG, "buf1@%p, buf2@%p", buf1, buf2);
// initialize LVGL draw buffers
@ -377,10 +444,9 @@ void app_main(void)
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.disp = disp;
indev_drv.read_cb = example_lvgl_touch_cb;
indev_drv.user_data = tp;
indev_drv.user_data = tp_handle;
lv_indev_drv_register(&indev_drv);
#endif
#endif // CONFIG_EXAMPLE_LCD_TOUCH_ENABLED
ESP_LOGI(TAG, "Display LVGL animation");
example_lvgl_demo_ui(disp);

View File

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,19 +1,22 @@
/*
* SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0-1.0
*/
#include <math.h>
#include "sdkconfig.h"
#include "lvgl.h"
#ifndef PI
#define PI (3.14159f)
#endif
#if CONFIG_EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY
// LVGL image declare
LV_IMG_DECLARE(esp_logo)
LV_IMG_DECLARE(esp_text)
#endif // CONFIG_EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY
typedef struct {
lv_obj_t *scr;
@ -21,9 +24,9 @@ typedef struct {
} my_timer_context_t;
static my_timer_context_t my_tim_ctx;
static lv_obj_t * btn;
static lv_obj_t *btn;
static lv_obj_t *arc[3];
static lv_obj_t *img_logo;
static lv_obj_t *img_logo = NULL;
static lv_obj_t *img_text = NULL;
static lv_color_t arc_color[] = {
LV_COLOR_MAKE(232, 87, 116),
@ -31,7 +34,6 @@ static lv_color_t arc_color[] = {
LV_COLOR_MAKE(90, 202, 228),
};
static void anim_timer_cb(lv_timer_t *timer)
{
my_timer_context_t *timer_ctx = (my_timer_context_t *) timer->user_data;
@ -57,7 +59,11 @@ static void anim_timer_cb(lv_timer_t *timer)
// Create new image and make it transparent
img_text = lv_img_create(scr);
#if CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
lv_img_set_src(img_text, "S:/spiffs/esp_text.png");
#elif CONFIG_EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY
lv_img_set_src(img_text, &esp_text);
#endif
lv_obj_set_style_img_opa(img_text, 0, 0);
}
@ -117,9 +123,9 @@ static void start_animation(lv_obj_t *scr)
lv_obj_add_state(btn, LV_STATE_DISABLED);
}
static void btn_cb(lv_event_t * e)
static void btn_cb(lv_event_t *e)
{
lv_obj_t * scr = lv_event_get_user_data(e);
lv_obj_t *scr = lv_event_get_user_data(e);
start_animation(scr);
}
@ -129,10 +135,13 @@ void example_lvgl_demo_ui(lv_disp_t *disp)
// Create image
img_logo = lv_img_create(scr);
#if CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM
lv_img_set_src(img_logo, "S:/spiffs/esp_logo.png");
#elif CONFIG_EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY
lv_img_set_src(img_logo, &esp_logo);
#endif
btn = lv_btn_create(scr);
lv_obj_t * lbl = lv_label_create(btn);
lv_obj_t *lbl = lv_label_create(btn);
lv_label_set_text_static(lbl, LV_SYMBOL_REFRESH" SHOW AGAIN");
lv_obj_set_style_text_font(lbl, &lv_font_montserrat_20, 0);
lv_obj_align(btn, LV_ALIGN_BOTTOM_LEFT, 30, -30);

View File

@ -0,0 +1,6 @@
# Name, Type, SubType, Offset, Size, Flags
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
nvs, data, nvs, 0x9000, 0x6000,
phy_init, data, phy, 0xf000, 0x1000,
factory, app, factory, 0x10000, 1M,
storage, data, spiffs, , 0xF0000,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, spiffs, , 0xF0000,

View File

@ -0,0 +1 @@
CONFIG_EXAMPLE_LCD_IMAGE_FROM_EMBEDDED_BINARY=y

View File

@ -0,0 +1,11 @@
CONFIG_EXAMPLE_LCD_IMAGE_FROM_FILE_SYSTEM=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_lvgl_example.csv"
CONFIG_LV_USE_PNG=y
CONFIG_LV_USE_FS_POSIX=y
# use 'S' (83 in ASCII) as drive letter for POSIX FS
CONFIG_LV_FS_POSIX_LETTER=83
CONFIG_LV_FS_POSIX_CACHE_SIZE=65535

View File

@ -63,7 +63,6 @@ void app_main(void)
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
}
#
// Check consistency of reported partiton size info.
if (used > total) {
ESP_LOGW(TAG, "Number of used bytes cannot be larger than total. Performing SPIFFS_check().");