LVGL has a "file system" abstraction module that enables you to attach any type of file system. The file system is identified by a drive letter. For example, if the SD card is associated with the letter "S", you can access a file similar to "S:path/to/file.txt".
The "file system" abstraction module is officially provided for the upper GUI to read files, pictures, fonts and other operations. You can use the file system API of LVGL like FatFS. When LVGL uses the resources of external storage devices, it will be called automatically. For example, the functions listed below are LVGL functions. After docking the file system, these functions will automatically read the files in the file system. As long as their corresponding decoding library is added, the picture can be displayed
lv_img_set_src(img, "P:lv_lib_lodepng/png_decoder_test.png");
lv_img_set_src(img2, "S.\\lv_lib_split_jpg\\example\\images\\small_image.sjpg");
lv_img_set_src(img, "S/path/to/image.bmp");
lv_obj_t * img = lv_gif_create_from_file(parent, "S/path/to/example.gif");
Development environment of this paper:
Visual Studio Code V1.58.2
LVGL version v7 ten
Chip platform: ESP32
IDF library version: 4.3.0
IDF TOOLS compilation tool chain version: 2.9
The software in this paper is based on the ESP32 project LV officially provided by LVGL_ port_ Modified from ESP32
LVGL ESP32 official address: https://github.com/lvgl/lv_port_esp32
The development board platform is based on HelloBug ESP32 development board
Development board purchase link: https://hellobug.taobao.com/
1, lvgl file system interface migration
There is no interface code in the official ESP32 project of LVGL, but it can be downloaded from the official git.
Address: https://github.com/lvgl/lv_fs_if
After downloading, directly copy it to the components folder in the project. The directory structure is as follows
It's not enough to copy the interface file. To take it as a module of LVGL, you have to write and add a cmakelists Txt on LV_ fs_ Under the if folder, the content and directory structure are as follows:
Include all under this folder c file. In addition, it refers to the file system in the idf library and the underlying SD card driver_ fs_ If is registered to the idf component, and finally two components lvgl and my are added_ sd_ FatFs component and lvgl are known to be the core components of lvgl, my_sd_fatfs is written by itself according to its name. It is used to initialize the underlying SD card and mount the underlying file system. How to write this will be discussed later.
2, Modify configuration
Open LV in the LVGL directory in the project directory_ conf.h. \components\lvgl\lv_conf.h, in H file, add the following configuration to enable the file system of LVGL
// File system interface #define LV_USE_FS_IF 1 #if LV_USE_FS_IF #define LV_FS_IF_FATFS 'S' #define LV_FS_IF_PC '\0' #define LV_FS_IF_POSIX '\0' #endif
III. initialize the underlying SD card driver
To connect to the lvgl file system, you must first initialize the underlying memory and mount the file system.
Create a folder in the project directory components_ sd_ FATFS, which you can name casually, means to initialize the source directory of the underlying FATFS, then create the include folder, and then create cmakelists txt,component.mk,my_sd_fatfs.c,my_sd_fatfs.h. The folder structure is as follows:
CMakeLists.txt file is mainly used to add files and other components for this component. Its contents are as follows:
set(mysrcs "my_sd_fatfs.c") idf_component_register(SRCS "${mysrcs}" INCLUDE_DIRS "include" REQUIRES fatfs)
component.mk file content
COMPONENT_ADD_INCLUDEDIRS := include COMPONENT_SRCDIRS := .
my_sd_fatfs.h) document content
#ifndef MY_SD_FATFS_H #define MY_SD_FATFS_H #ifdef __cplusplus extern "C" { #endif #include <string.h> #include <unistd.h> #include <sys/stat.h> #include "esp_vfs_fat.h" #include "driver/sdspi_host.h" #include "driver/spi_common.h" #include "sdmmc_cmd.h" #include "driver/sdmmc_host.h" void SD_Fatfs_Init(void); #endif /* LVGL_HELPERS_H */
my_sd_fatfs.c. document content:
#include "my_sd_fatfs.h" static const char *TAG = "TFCardFat"; #define MOUNT_POINT "/sdcard" // The first byte must be '/' #define SPI_DMA_CHAN 2 //When testing SD and SPI modes, remember that once the card is initialized in SPI mode, it cannot be reinitialized in SD mode without turning on the card power. #define PIN_NUM_MISO 19 #define PIN_NUM_MOSI 23 #define PIN_NUM_CLK 18 #define PIN_NUM_CS 5 static void get_fatfs_usage(size_t* out_total_bytes, size_t* out_free_bytes); void SD_Fatfs_Init(void) { esp_err_t ret; // ESP error definition sdmmc_card_t* card; // SD / MMC card information structure const char mount_point[] = MOUNT_POINT; // root directory char ReadFileBuff[64]; esp_vfs_fat_sdmmc_mount_config_t mount_config = { // File system mount configuration .format_if_mount_failed = false, // If the mount fails: true will repartition and format / false will not repartition and format .max_files = 5, // Maximum number of open files .allocation_unit_size = 16 * 1024 }; ESP_LOGI(TAG,"->Initializing SD card"); ESP_LOGI(TAG,"->Using SPI peripheralr"); sdmmc_host_t host = SDSPI_HOST_DEFAULT(); spi_bus_config_t bus_cfg = { .mosi_io_num = PIN_NUM_MOSI, .miso_io_num = PIN_NUM_MISO, .sclk_io_num = PIN_NUM_CLK, .quadwp_io_num = -1, .quadhd_io_num = -1, .max_transfer_sz = 4 * 1024 * sizeof(uint8_t), }; host.slot = 2; // SPI bus initialization ret = spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN); if (ret != ESP_OK) { printf("%s->Failed to initialize bus.\r\n",TAG); return; } // This initializes slots without card detection (CD) and write protect (WP) signals. // If your motherboard has these signals, please modify the slot_config.gpio_cd and slot_config.gpio_wp. sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();// SPI2_HOST 1 slot_config.gpio_cs = PIN_NUM_CS; slot_config.host_id = host.slot; // Mount file system ret = esp_vfs_fat_sdspi_mount(mount_point, &host, &slot_config, &mount_config, &card); if (ret != ESP_OK) { if (ret == ESP_FAIL) { printf("%s->Failed to mount filesystem.%s\r\n",TAG, "If you want the card to be formatted, set the EXAMPLE_FORMAT_IF_MOUNT_FAILED menuconfig option."); } else { printf("%s->Failed to initialize the card %s (%s). ",TAG, "Make sure SD card lines have pull-up resistors in place.", esp_err_to_name(ret)); } return; } // TF card has been initialized. Print TF Card properties sdmmc_card_print_info(stdout, card); // Print FAT FS size information size_t bytes_total, bytes_free; get_fatfs_usage(&bytes_total, &bytes_free); printf("%s->FAT FS Total: %d MB, Free: %d MB \r\n",TAG, bytes_total / 1024, bytes_free / 1024); }
At this time, POSIX and C standard library functions can be used to perform various operations on SD card files. The next step is to connect C standard functions with LVGL file system functions.
4, Docking file operation function
The file operation function of LVGL is in components\lvgl\lvgl\src\lv_misc\lv_fs.h file has definitions. Here, the functions to be docked are listed as follows: (do not modify this file!!)
lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode); lv_fs_res_t lv_fs_close(lv_fs_file_t * file_p); lv_fs_res_t lv_fs_remove(const char * path); lv_fs_res_t lv_fs_read(lv_fs_file_t * file_p, void * buf, uint32_t btr, uint32_t * br); lv_fs_res_t lv_fs_write(lv_fs_file_t * file_p, const void * buf, uint32_t btw, uint32_t * bw); lv_fs_res_t lv_fs_seek(lv_fs_file_t * file_p, uint32_t pos); lv_fs_res_t lv_fs_tell(lv_fs_file_t * file_p, uint32_t * pos); lv_fs_res_t lv_fs_trunc(lv_fs_file_t * file_p); lv_fs_res_t lv_fs_size(lv_fs_file_t * file_p, uint32_t * size); lv_fs_res_t lv_fs_rename(const char * oldname, const char * newname); lv_fs_res_t lv_fs_dir_open(lv_fs_dir_t * rddir_p, const char * path); lv_fs_res_t lv_fs_dir_read(lv_fs_dir_t * rddir_p, char * fn); lv_fs_res_t lv_fs_dir_close(lv_fs_dir_t * rddir_p); lv_fs_res_t lv_fs_free_space(char letter, uint32_t * total_p, uint32_t * free_p); char * lv_fs_get_letters(char * buf); const char * lv_fs_get_ext(const char * fn); char * lv_fs_up(char * path); const char * lv_fs_get_last(const char * path);
In fact, it is not necessary to realize the docking of all functions, and the most commonly used functions can be realized. I just look at how a LV file operation function is called to the bottom layer step by step. For example, components \ lvgl \ lvgl \ SRC \ LV_ misc\lv_ fs. LV in C_ fs_ The prototype of the open function is as follows: (do not modify this file)
lv_fs_res_t lv_fs_open(lv_fs_file_t * file_p, const char * path, lv_fs_mode_t mode) { file_p->drv = NULL; file_p->file_d = NULL; if(path == NULL) return LV_FS_RES_INV_PARAM; char letter = path[0]; file_p->drv = lv_fs_get_drv(letter); if(file_p->drv == NULL) { return LV_FS_RES_NOT_EX; } if(file_p->drv->ready_cb != NULL) { if(file_p->drv->ready_cb(file_p->drv) == false) { file_p->drv = NULL; return LV_FS_RES_HW_ERR; } } if(file_p->drv->open_cb == NULL) { file_p->drv = NULL; return LV_FS_RES_NOT_IMP; } const char * real_path = lv_fs_get_real_path(path); if(file_p->drv->file_size == 0) { /*Is file_d zero size?*/ /*Pass file_d's address to open_cb, so the implementor can allocate memory byself*/ return file_p->drv->open_cb(file_p->drv, &file_p->file_d, real_path, mode); } file_p->file_d = lv_mem_alloc(file_p->drv->file_size); LV_ASSERT_MEM(file_p->file_d); if(file_p->file_d == NULL) { file_p->drv = NULL; return LV_FS_RES_OUT_OF_MEM; /* Out of memory */ } lv_fs_res_t res = file_p->drv->open_cb(file_p->drv, file_p->file_d, real_path, mode); if(res != LV_FS_RES_OK) { lv_mem_free(file_p->file_d); file_p->file_d = NULL; file_p->drv = NULL; } return res; }
The function is preceded by various checks and the necessary memory to apply for opening files. It mainly depends on this sentence. The parameters are device operation pointer, file pointer, real directory and operation mode.
open_cb(file_p->drv, &file_p->file_d, real_path, mode);
The function pointer of this function points to components \ lv_fs_if\lv_ fs_ fatfs. FS in C_ Open function. lv_ fs_ The if component is the file system component that we downloaded from LVGL in the first step_ LV configured in #define. H_ USE_ FS_ If # 1 switch to point here.
To achieve docking, you need to change components\lv_fs_if\lv_fs_fatfs.c each file operation function in this file. LV in this file_ fs_ if_ fatfs_ The functions to be implemented are listed in the init function
fs_drv.open_cb = fs_open; fs_drv.close_cb = fs_close; fs_drv.read_cb = fs_read; fs_drv.write_cb = fs_write; fs_drv.seek_cb = fs_seek; fs_drv.tell_cb = fs_tell; fs_drv.free_space_cb = fs_free; fs_drv.size_cb = fs_size; fs_drv.remove_cb = fs_remove; fs_drv.rename_cb = fs_rename; fs_drv.trunc_cb = fs_trunc; fs_drv.rddir_size = sizeof(dir_t); fs_drv.dir_close_cb = fs_dir_close; fs_drv.dir_open_cb = fs_dir_open; fs_drv.dir_read_cb = fs_dir_read;
This c file has written out all the functions to be implemented, but they all return directly
lv_fs_res_t res = LV_FS_RES_NOT_IMP;
The first step is to modify the initialization function. Here, we call the function of initializing the underlying SD card driver in the third step to initialize the underlying file system.
/* Initialize your Storage device and File system. */ static void fs_init(void) { /* Initialize the SD card and FatFS itself. * Better to do it in your code to keep this library utouched for easy updating*/ // Initialize the SD card and FatFS itself. // It's best to do this in your code to keep the library unchanged for easy updating SD_Fatfs_Init(); }
Next, let's modify all the file operation functions.
fs_open
static lv_fs_res_t fs_open(lv_fs_drv_t * drv, void * file_p, const char * path, lv_fs_mode_t mode) { // Open file (void)file_p; const char * flags = ""; if(mode == LV_FS_MODE_WR) flags = "wb"; else if(mode == LV_FS_MODE_RD) flags = "rb"; else if(mode == (LV_FS_MODE_WR | LV_FS_MODE_RD)) flags = "rb+"; sprintf(f_path, "/sdcard/%s", path); file_t f = fopen(f_path, flags); if (NULL == f) { return LV_FS_RES_NOT_EX; } fseek(f, 0, SEEK_SET); /* 'file_p' Is a pointer to the file descriptor. We need to store our file descriptor here*/ file_t * fp = file_p; *fp = f; return LV_FS_RES_OK; }
fs_close
static lv_fs_res_t fs_close (lv_fs_drv_t * drv, void * file_p) { // Close file (void) drv; /*Unused*/ (void) drv; /*Unused*/ file_t * fp = file_p; // Fetch file operation handle if (NULL == fp) { ESP_LOGE(TAG, "NULL file pointer"); return LV_FS_RES_NOT_EX; } if (EOF == fclose(fp)) { ESP_LOGE(TAG, "Failed close file pointer"); return LV_FS_RES_NOT_EX; } return LV_FS_RES_OK; }
fs_read
static lv_fs_res_t fs_read (lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) { // read file (void) drv; /*Unused*/ file_t * fp = file_p;// Fetch file operation handle *br = fread(buf, 1, btr, (FILE *)file_p); return LV_FS_RES_OK; }
fs_write
static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) { // Write data to operation file (void) drv; /*Unused*/ file_t * fp = file_p;// Fetch file operation handle *bw = fwrite(buf, 1, btw, (FILE *)file_p); return LV_FS_RES_OK; }
fs_seek
static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, int whence) { // Set file operation pointer offset (void) drv; /*Unused*/ fseek((FILE *)file_p, pos, SEEK_SET); return LV_FS_RES_OK; }
fs_size
static lv_fs_res_t fs_size(lv_fs_drv_t * drv, void * file_p, uint32_t * size_p) { // Get current file size (Byte) (void) drv; /*Unused*/ file_t * fp = file_p;// Fetch file operation handle uint32_t cur = ftell((FILE *)file_p); fseek((FILE *)file_p, 0L, SEEK_END); *size_p = ftell((FILE *)file_p); fseek((FILE *)file_p, cur, SEEK_SET);// Recover file pointer return LV_FS_RES_OK; }
fs_tell
static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) { // Gets the current operation pointer position of the file (void) drv; /*Unused*/ *pos_p = ftell((FILE *)file_p); return LV_FS_RES_OK; }
fs_remove
static lv_fs_res_t fs_remove(lv_fs_drv_t * drv, const char *path) { char * pathbuf = lv_mem_alloc(128); sprintf(pathbuf,"/sdcard/%s",path); lv_fs_res_t res = LV_FS_RES_UNKNOWN; if(remove(pathbuf)==0){ lv_mem_free(pathbuf); return LV_FS_RES_OK; }else{ lv_mem_free(pathbuf); return res; } }
fs_rename
static lv_fs_res_t fs_rename (lv_fs_drv_t * drv, const char * oldname, const char * newname) { (void) drv; /*Unused*/ char * oldnamebuf = lv_mem_alloc(128); char * newnamebuf = lv_mem_alloc(128); sprintf(oldnamebuf,"/sdcard/%s",oldname); sprintf(newnamebuf,"/sdcard/%s",newname); // lv_fs_res_t res = LV_FS_RES_UNKNOWN; if(rename(oldnamebuf,newnamebuf)==0){ lv_mem_free(oldnamebuf); lv_mem_free(newnamebuf); return LV_FS_RES_OK; }else{ lv_mem_free(oldnamebuf); lv_mem_free(newnamebuf); return res; } }
fs_free
static lv_fs_res_t fs_free(lv_fs_drv_t * drv, uint32_t * total_p, uint32_t * free_p) { lv_fs_res_t res = LV_FS_RES_NOT_IMP; size_t total_sectors = 0,free_sectors = 0; size_t sd_total = 0,sd_total_KB = 0,sd_total_MB = 0; size_t sd_free = 0,sd_free_KB = 0,sd_free_MB = 0; FATFS *fs; size_t free_clusters; FRESULT fres = f_getfree("0:", &free_clusters, &fs); if(fres == FR_OK){ total_sectors = (fs->n_fatent - 2) * fs->csize; free_sectors = free_clusters * fs->csize; sd_total = total_sectors/1024; sd_total_KB = sd_total*fs->ssize; sd_total_MB = (sd_total*fs->ssize)/1024; sd_free = free_sectors/1024; sd_free_KB = sd_free*fs->ssize; sd_free_MB = (sd_free*fs->ssize)/1024; if (total_p != NULL) { *total_p = sd_total_KB; } if (free_p != NULL) { *free_p = sd_free_KB; } return LV_FS_RES_OK; }else{ *total_p = sd_total_KB; *free_p = sd_free_KB; return LV_FS_RES_UNKNOWN; } //printf("SD Cart sd_total_KB %d KByte\r\n ", sd_total_KB); // printf("SD Cart sd_total_MB %d MByte\r\n ", sd_total_MB); // printf("SD Cart sd_free_KB %d KByte\r\n ", sd_free_KB); // printf("SD Cart sd_free_MB %d MByte\r\n ", sd_free_MB); // Assuming that the total size is less than 4GiB, flash should be true for SPI }
fs_dir_open
static lv_fs_res_t fs_dir_open (lv_fs_drv_t * drv, void * dir_p, const char *path) { dir_t file_dir = NULL; //file_dir = opendir("/sdcard/"); (void)dir_p; (void) drv; /*Unused*/ char * pathbuf = lv_mem_alloc(128); sprintf(pathbuf,"/sdcard/%s",path); printf("fs_dir_open %s\r\n",pathbuf); file_dir = opendir(pathbuf); if (NULL == file_dir) { lv_mem_free(pathbuf); return LV_FS_RES_FS_ERR; } lv_mem_free(pathbuf); /* 'file_p' Is a pointer to the file descriptor. We need to store our file descriptor here*/ dir_t * fd = dir_p; *fd = file_dir; return LV_FS_RES_OK; }
fs_dir_read
static lv_fs_res_t fs_dir_read (lv_fs_drv_t * drv, void * dir_p, char *fn) { struct dirent *entry; dir_t *mdir_p = dir_p; do { entry = readdir(*mdir_p); if(entry) { if(entry->d_type == DT_DIR) sprintf(fn, "/%s", entry->d_name); else strcpy(fn, entry->d_name); } else { strcpy(fn, ""); } } while(strcmp(fn, "/.") == 0 || strcmp(fn, "/..") == 0); return LV_FS_RES_OK; }
fs_dir_close
static lv_fs_res_t fs_dir_close (lv_fs_drv_t * drv, void * dir_p) { (void) drv; /*Unused*/ closedir(dir_p); return LV_FS_RES_OK; }
5, Test code
The next step is to use the functions prompted by lvgl to actually operate the file and test it. Before that, you should first initialize the lvgl file system in the main function_ After the init function, the lv_ is called. fs_ if_ init(); Initialize the file system and define some things to be used later, such as file operation handle, folder operation handle, return value, quantity variable, read content array, etc
lv_init(); // Initialize LittlevGL lvgl_driver_init(); // Initializing the LCD touch chip SPI/IIC driver lv_fs_if_init(); lv_fs_file_t f; lv_fs_dir_t d; lv_fs_res_t res; uint32_t resCount; uint8_t buf[64];
Write operation function
The specific operation instructions are as follows:
Open s: / Hello. In read-only mode Txt file to test whether the file exists
Open s: / Hello. By writing Txt file, write hellobug esp32 string, and then close the file
Open s: / Hello. In read-only mode Txt file, read the contents and print, and then close the file
Delete s: / bug Txt (must not exist when running for the first time)
Put hello Txt to bug txt
Open bug in read-only mode Txt, get the file size
Read bug Txt all contents and print
Move the operation file pointer to the 7th byte
Gets the current pointer position of the file
Read the contents of the file from the current position of the pointer and print it
Close file
Open root directory
Cycle to read all directories and files under the directory
Close directory
Then add the following test code. Basically, all the interfaces just rewritten are used.
// Open a file in read-only mode to judge whether the file exists res = lv_fs_open(&f,"S:/hello.txt", LV_FS_MODE_RD); if (res == LV_FS_RES_OK) { // File exists lv_fs_close(&f); // Close file ESP_LOGI(TAG,"'hello.txt' file is existsres : %d",res); }else if(res == LV_FS_RES_NOT_EX){// file does not exist ESP_LOGI(TAG,"'hello.txt' file is not exists res : %d",res); } /Create a file and write its contents res = lv_fs_open(&f,"S:/hello.txt", LV_FS_MODE_WR);// Create a file if (res == LV_FS_RES_OK) { ESP_LOGI(TAG,"Create file 'hello.txt' Success res : %d",res); res = lv_fs_write(&f,"hellobug esp32", sizeof("hellobug esp32"), &resCount);// Create a file if (res == LV_FS_RES_OK) { ESP_LOGI(TAG,"Write file 'hello.txt' Success res : %d WriteByte: %d",res,resCount); } } lv_fs_close(&f); /Read the file just created and print the contents res = lv_fs_open(&f,"S:/hello.txt", LV_FS_MODE_RD);// Open a file read-only if(res == LV_FS_RES_OK) { ESP_LOGI(TAG,"lv_fs_open->Success %d",res); memset(buf,0,64); res = lv_fs_read(&f, buf, 64, &resCount); if(res != LV_FS_RES_OK) { ESP_LOGI(TAG,"lv_fs_read->Failed %d",res); }else{ ESP_LOGI(TAG,"lv_fs_read->Success %d",res); } ESP_LOGI(TAG,"lv_fs_read ReadByte : %d Content : %s",resCount,buf); lv_fs_close(&f); }else{ ESP_LOGI(TAG,"lv_fs_open->Failed %d",res); } // Delete file res = lv_fs_remove("S:/bug.txt"); ESP_LOGI(TAG,"lv_fs_remove-> bug.txt res:%d",res); // rename file res = lv_fs_rename("S:/hello.txt","S:/bug.txt"); ESP_LOGI(TAG,"lv_fs_rename-> oldname:hello.txt newname:bug.txt res:%d",res); /Read the file just renamed and print the content res = lv_fs_open(&f,"S:/bug.txt", LV_FS_MODE_RD);// Open a file read-only if(res == LV_FS_RES_OK) { // Get file size res = lv_fs_size(&f,&resCount); ESP_LOGI(TAG,"lv_fs_size-> bug.txt file size : %d Byte",resCount); ESP_LOGI(TAG,"lv_fs_open-> S:/bug.txt Success %d",res); memset(buf,0,64); res = lv_fs_read(&f, buf, 64, &resCount); if(res != LV_FS_RES_OK) { ESP_LOGI(TAG,"lv_fs_read-> S:/bug.txt Failed %d",res); }else{ ESP_LOGI(TAG,"lv_fs_read-> S:/bug.txt Success %d",res); } ESP_LOGI(TAG,"lv_fs_read S:/bug.txt ReadByte : %d Content : %s",resCount,buf); // Example of locating a file operation pointer res = lv_fs_seek(&f, 7); ESP_LOGI(TAG,"lv_fs_seek-> S:/bug.txt res %d",res); // Get file operation pointer example res = lv_fs_tell(&f, &resCount); ESP_LOGI(TAG,"lv_fs_tell-> S:/bug.txt res %d index: %d",res,resCount); memset(buf,0,64); res = lv_fs_read(&f, buf, 64, &resCount); ESP_LOGI(TAG,"lv_fs_read seek 7Byte S:/bug.txt ReadByte : %d Content : %s",resCount,buf); lv_fs_close(&f); }else{ ESP_LOGI(TAG,"lv_fs_open-> S:/bug.txt Failed %d",res); } res = lv_fs_dir_open(&d,"S:/"); ESP_LOGI(TAG,"lv_fs_dir_open-> res %d",res); while(1){ memset(buf,0,64); res = lv_fs_dir_read(&d,(char *)buf); if(buf[0] == '\0'){ break; } ESP_LOGI(TAG,"lv_fs_dir_read res : %d Content : %s",res,buf); } res = lv_fs_dir_close(&d); ESP_LOGI(TAG,"lv_fs_dir_close res : %d ",res);
Test result printing: