diff --git a/Kconfig b/Kconfig index 23c2c25fc..22dc4e4a1 100644 --- a/Kconfig +++ b/Kconfig @@ -964,6 +964,17 @@ menu "LVGL configuration" default 0 depends on LV_USE_FS_FATFS + config LV_USE_FS_LITTLEFS + bool "File system on top of LittleFS" + config LV_FS_LITTLEFS_LETTER + int "Set an upper cased letter on which the drive will accessible (e.g. 'A' i.e. 65)" + default 0 + depends on LV_USE_FS_LITTLEFS + config LV_FS_LITTLEFS_CACHE_SIZE + int ">0 to cache this number of bytes in lv_fs_read()" + default 0 + depends on LV_USE_FS_LITTLEFS + config LV_USE_PNG bool "PNG decoder library" diff --git a/docs/libs/fsdrv.md b/docs/libs/fsdrv.md index 7b9015aa2..8f828d90d 100644 --- a/docs/libs/fsdrv.md +++ b/docs/libs/fsdrv.md @@ -2,14 +2,38 @@ # File System Interfaces LVGL has a [File system](https://docs.lvgl.io/master/overview/file-system.html) module to provide an abstraction layer for various file system drivers. +You still need to provide the drivers and libraries, this extension provides only the bridge between FATFS, LittleFS, STDIO, POSIX, WIN32 and LVGL. -LVG has built in support for: -- [FATFS](http://elm-chan.org/fsw/ff/00index_e.html) -- STDIO (Linux and Windows using C standard function .e.g fopen, fread) -- POSIX (Linux and Windows using POSIX function .e.g open, read) -- WIN32 (Windows using Win32 API function .e.g CreateFileA, ReadFile) +## Built in wrappers -You still need to provide the drivers and libraries, this extension provides only the bridge between FATFS, STDIO, POSIX, WIN32 and LVGL. +### FATFS + +Bridge for [FatFS](http://elm-chan.org/fsw/ff/00index_e.html). FatFS itself is not part of LVGL, but can be added and initialized externally. + + +### LittleFS + +Though `lv_fs_littlefs` uses [LittleFS]((https://github.com/littlefs-project/littlefs)) API, the LittleFS library needs other external libraries that handle the mounting of partitions and low-level accesses, according to the given architecture. The functions for the latter are given to the lfs_t structure as pointers by an external low-level library. + +There's a convenience function called `lv_fs_littlefs_set_driver(LV_FS_LITTLEFS_LETTER, my_lfs)`, specific to `lv_fs_littlefs`, to attach a `lfs_t` object's pointer to a registered driver-letter. See its comments for more info. + + +[esp_littlefs](https://components.espressif.com/components/joltwallet/littlefs) is a wrapper for LittleFS to be used in Espressif ESP-devices. It handles the mounting and has the low-level `littlefs_api` functions to read/write/erase blocks that LittleFS library needs. On mounting by `esp_littlefs` the `lfs_t` structures are created. You need to get a handle to these to use ESP with `lv_fs_littlefs`, as all functions use that `lfs_t` in LittleFS to identify the mounted partition. + + +In case you don't find a special function in the `lv_fs_littlefs` wrapper, you can look for it in the `esp_littlefs` API and use it directly, as `lv_fs_littlefs` and the `esp_littlefs` APIs can be used side-by-side. + +### STDIO + +Bride to C standard functions on Linux and Windows. For example `fopen`, `fread`, etc. + +### POSIX + +Bride to POSIX functions on Linux and Windows. For example `open`, `read`, etc. + +### WIN32 + +Bride to Win32 API function. For example `CreateFileA`, `ReadFile`, etc. ## Usage diff --git a/lv_conf_template.h b/lv_conf_template.h index 66535138d..8923183a8 100644 --- a/lv_conf_template.h +++ b/lv_conf_template.h @@ -639,6 +639,13 @@ #define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ #endif +/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/ +#define LV_USE_FS_LITTLEFS 0 +#if LV_USE_FS_LITTLEFS + #define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ +#endif + /*PNG decoder library*/ #define LV_USE_PNG 0 diff --git a/src/extra/libs/fsdrv/lv_fs_littlefs.c b/src/extra/libs/fsdrv/lv_fs_littlefs.c new file mode 100644 index 000000000..44261c31d --- /dev/null +++ b/src/extra/libs/fsdrv/lv_fs_littlefs.c @@ -0,0 +1,332 @@ +/** + * @file lv_fs_littlefs.c + * + */ + +/********************* + * INCLUDES + *********************/ +#include "../../../lvgl.h" + +#if LV_USE_FS_LITTLEFS +#include "lfs.h" + +/********************* + * DEFINES + *********************/ + +#if LV_FS_LITTLEFS_LETTER == '\0' + #error "LV_FS_LITTLEFS_LETTER must be an upper case ASCII letter" +#endif + +/********************** + * TYPEDEFS + **********************/ + +/********************** + * STATIC PROTOTYPES + **********************/ + +static void fs_init(void); + +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode); +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p); + +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br); +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw); + +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence); +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p); + +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path); +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * dir_p, char * fn); +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * dir_p); + +/********************** + * STATIC VARIABLES + **********************/ + +/********************** + * GLOBAL PROTOTYPES + **********************/ + +/********************** + * MACROS + **********************/ + +/********************** + * GLOBAL FUNCTIONS + **********************/ + +void lv_fs_littlefs_init(void) +{ + /*---------------------------------------------------- + * Initialize your storage device and File System + * -------------------------------------------------*/ + fs_init(); + + /*--------------------------------------------------- + * Register the file system interface in LVGL + *--------------------------------------------------*/ + + /*Add a simple drive to open images*/ + static lv_fs_drv_t fs_drv; /*A driver descriptor*/ + lv_fs_drv_init(&fs_drv); + + /*Set up fields...*/ + fs_drv.letter = LV_FS_LITTLEFS_LETTER; + fs_drv.cache_size = LV_FS_LITTLEFS_CACHE_SIZE; + + 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.dir_open_cb = fs_dir_open; + fs_drv.dir_close_cb = fs_dir_close; + fs_drv.dir_read_cb = fs_dir_read; + + /*#if LV_USE_USER_DATA*/ + fs_drv.user_data = NULL; + /*#endif*/ + + lv_fs_drv_register(&fs_drv); +} + +/** + * Convenience function to attach registered driver to lfs_t structure by driver-label + * @param label the label assigned to the driver when it was registered + * @param lfs_p the pointer to the lfs_t structure initialized by external code/library + * @return pointer to a driver descriptor or NULL on error + */ +lv_fs_drv_t * lv_fs_littlefs_set_driver(char label, void * lfs_p) +{ + lv_fs_drv_t * drv_p = lv_fs_get_drv(label); + if(drv_p != NULL) drv_p->user_data = (lfs_t *) lfs_p; + return drv_p; +} + +/********************** + * STATIC FUNCTIONS + **********************/ + +/*Initialize your Storage device and File system.*/ +static void fs_init(void) +{ + /* Initialize the internal flash or SD-card and LittleFS itself. + * Better to do it in your code to keep this library untouched for easy updating */ +} + +/** + * Open a file + * @param drv pointer to a driver where this function belongs + * @param path path to the file beginning with the driver letter (e.g. S:/folder/file.txt) + * @param mode read: FS_MODE_RD, write: FS_MODE_WR, both: FS_MODE_RD | FS_MODE_WR + * @return pointer to a file descriptor or NULL on error + */ +static void * fs_open(lv_fs_drv_t * drv, const char * path, lv_fs_mode_t mode) +{ + lfs_t * lfs_p = drv->user_data; + uint32_t flags = 0; + + flags = mode == LV_FS_MODE_RD ? LFS_O_RDONLY + : mode == LV_FS_MODE_WR ? LFS_O_WRONLY + : mode == (LV_FS_MODE_WR | LV_FS_MODE_RD) ? LFS_O_RDWR : 0; + + lfs_file_t * file_p = lv_mem_alloc(sizeof(lfs_file_t)); + if(file_p == NULL) return NULL; + + int result = lfs_file_open(lfs_p, file_p, path, flags); + + if(result != LFS_ERR_OK) { + lv_mem_free(file_p); + return NULL; + } + + return file_p; +} + +/** + * Close an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. (opened with fs_open) + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_close(lv_fs_drv_t * drv, void * file_p) +{ + lfs_t * lfs_p = drv->user_data; + + int result = lfs_file_close(lfs_p, file_p); + lv_mem_free(file_p); + /*lv_mem_free( lfs_p );*/ /*allocated and freed by outside-code*/ + + if(result != LFS_ERR_OK) return LV_FS_RES_UNKNOWN; + return LV_FS_RES_OK; +} + +/** + * Read data from an opened file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. + * @param buf pointer to a memory block where to store the read data + * @param btr number of Bytes To Read + * @param br the real number of read bytes (Byte Read) + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_read(lv_fs_drv_t * drv, void * file_p, void * buf, uint32_t btr, uint32_t * br) +{ + lfs_t * lfs_p = drv->user_data; + + lfs_ssize_t result = lfs_file_read(lfs_p, file_p, buf, btr); + if(result < 0) return LV_FS_RES_UNKNOWN; + + *br = (uint32_t) result; + return LV_FS_RES_OK; +} + +/** + * Write into a file + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable + * @param buf pointer to a buffer with the bytes to write + * @param btw Bytes To Write + * @param bw the number of real written bytes (Bytes Written). NULL if unused. + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_write(lv_fs_drv_t * drv, void * file_p, const void * buf, uint32_t btw, uint32_t * bw) +{ +#ifndef LFS_READONLY + lfs_t * lfs_p = drv->user_data; + + lfs_ssize_t result = lfs_file_write(lfs_p, file_p, buf, btw); + if(result < 0 || lfs_file_sync(lfs_p, file_p) < 0) return LV_FS_RES_UNKNOWN; + + *bw = (uint32_t) result; + return LV_FS_RES_OK; +#else + return LV_FS_RES_NOT_IMP; +#endif +} + +/** + * Set the read write pointer. Also expand the file size if necessary. + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. (opened with fs_open ) + * @param pos the new position of read write pointer + * @param whence tells from where to interpret the `pos`. See @lv_fs_whence_t + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_seek(lv_fs_drv_t * drv, void * file_p, uint32_t pos, lv_fs_whence_t whence) +{ + lfs_t * lfs_p = drv->user_data; + + int lfs_whence = whence == LV_FS_SEEK_SET ? LFS_SEEK_SET + : whence == LV_FS_SEEK_CUR ? LFS_SEEK_CUR + : whence == LV_FS_SEEK_END ? LFS_SEEK_END : 0; + + lfs_soff_t result = lfs_file_seek(lfs_p, file_p, pos, lfs_whence); + if(result < 0) return LV_FS_RES_UNKNOWN; + + /*pos = result;*/ /*not supported by lv_fs*/ + return LV_FS_RES_OK; +} + +/** + * Give the position of the read write pointer + * @param drv pointer to a driver where this function belongs + * @param file_p pointer to a file_t variable. + * @param pos_p pointer to where to store the result + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_tell(lv_fs_drv_t * drv, void * file_p, uint32_t * pos_p) +{ + lfs_t * lfs_p = drv->user_data; + + lfs_soff_t result = lfs_file_tell(lfs_p, file_p); + if(result < 0) return LV_FS_RES_UNKNOWN; + + *pos_p = (uint32_t) result; + return LV_FS_RES_OK; +} + +/** + * Initialize a 'lv_fs_dir_t' variable for directory reading + * @param drv pointer to a driver where this function belongs + * @param path path to a directory + * @return pointer to the directory read descriptor or NULL on error + */ +static void * fs_dir_open(lv_fs_drv_t * drv, const char * path) +{ + lfs_t * lfs_p = drv->user_data; + + lfs_dir_t * dir_p = lv_mem_alloc(sizeof(lfs_dir_t)); + if(dir_p == NULL) return NULL; + + int result = lfs_dir_open(lfs_p, dir_p, path); + if(result != LFS_ERR_OK) { + lv_mem_free(dir_p); + return NULL; + } + + return dir_p; +} + +/** + * Read the next filename form a directory. + * The name of the directories will begin with '/' + * @param drv pointer to a driver where this function belongs + * @param rddir_p pointer to an initialized 'lv_fs_dir_t' variable + * @param fn pointer to a buffer to store the filename + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_read(lv_fs_drv_t * drv, void * rddir_p, char * fn) +{ + lfs_t * lfs_p = drv->user_data; + struct lfs_info info; + int result; + + info.name[0] = '\0'; + + do { + result = lfs_dir_read(lfs_p, rddir_p, &info); + if(result > 0) { + if(info.type == LFS_TYPE_DIR) { + fn[0] = '/'; + strcpy(&fn[1], info.name); + } + else strcpy(fn, info.name); + } + else if(result == 0) fn[0] = '\0'; /*dir-scan ended*/ + else return LV_FS_RES_UNKNOWN; + + } while(!strcmp(fn, "/.") || !strcmp(fn, "/..")); + + return LV_FS_RES_OK; +} + +/** + * Close the directory reading + * @param drv pointer to a driver where this function belongs + * @param rddir_p pointer to an initialized 'lv_fs_dir_t' variable + * @return LV_FS_RES_OK: no error or any error from @lv_fs_res_t enum + */ +static lv_fs_res_t fs_dir_close(lv_fs_drv_t * drv, void * rddir_p) +{ + lfs_t * lfs_p = drv->user_data; + + int result = lfs_dir_close(lfs_p, rddir_p); + lv_mem_free(rddir_p); + + if(result != LFS_ERR_OK) return LV_FS_RES_UNKNOWN; + return LV_FS_RES_OK; +} + +#else /*LV_USE_FS_LITTLEFS == 0*/ + +#if defined(LV_FS_LITTLEFS_LETTER) && LV_FS_LITTLEFS_LETTER != '\0' + #warning "LV_USE_FS_LITTLEFS is not enabled but LV_FS_LITTLEFS_LETTER is set" +#endif + +#endif /*LV_USE_FS_POSIX*/ diff --git a/src/extra/libs/fsdrv/lv_fsdrv.h b/src/extra/libs/fsdrv/lv_fsdrv.h index 285d598fd..b864ad6f3 100644 --- a/src/extra/libs/fsdrv/lv_fsdrv.h +++ b/src/extra/libs/fsdrv/lv_fsdrv.h @@ -31,6 +31,11 @@ extern "C" { void lv_fs_fatfs_init(void); #endif +#if LV_USE_FS_LITTLEFS != '\0' +void lv_fs_littlefs_init(void); +lv_fs_drv_t * lv_fs_littlefs_set_driver(char label, void * lfs_p); +#endif + #if LV_USE_FS_STDIO != '\0' void lv_fs_stdio_init(void); #endif diff --git a/src/extra/lv_extra.c b/src/extra/lv_extra.c index 0b5000240..380833725 100644 --- a/src/extra/lv_extra.c +++ b/src/extra/lv_extra.c @@ -50,6 +50,10 @@ void lv_extra_init(void) lv_fs_fatfs_init(); #endif +#if LV_USE_FS_LITTLEFS != '\0' + lv_fs_littlefs_init(); +#endif + #if LV_USE_FS_STDIO != '\0' lv_fs_stdio_init(); #endif diff --git a/src/lv_conf_internal.h b/src/lv_conf_internal.h index a625ba960..ef562e2d2 100644 --- a/src/lv_conf_internal.h +++ b/src/lv_conf_internal.h @@ -2099,6 +2099,31 @@ #endif #endif +/*API for LittleFS (library needs to be added separately). Uses lfs_file_open, lfs_file_read, etc*/ +#ifndef LV_USE_FS_LITTLEFS + #ifdef CONFIG_LV_USE_FS_LITTLEFS + #define LV_USE_FS_LITTLEFS CONFIG_LV_USE_FS_LITTLEFS + #else + #define LV_USE_FS_LITTLEFS 0 + #endif +#endif +#if LV_USE_FS_LITTLEFS + #ifndef LV_FS_LITTLEFS_LETTER + #ifdef CONFIG_LV_FS_LITTLEFS_LETTER + #define LV_FS_LITTLEFS_LETTER CONFIG_LV_FS_LITTLEFS_LETTER + #else + #define LV_FS_LITTLEFS_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/ + #endif + #endif + #ifndef LV_FS_LITTLEFS_CACHE_SIZE + #ifdef CONFIG_LV_FS_LITTLEFS_CACHE_SIZE + #define LV_FS_LITTLEFS_CACHE_SIZE CONFIG_LV_FS_LITTLEFS_CACHE_SIZE + #else + #define LV_FS_LITTLEFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/ + #endif + #endif +#endif + /*PNG decoder library*/ #ifndef LV_USE_PNG #ifdef CONFIG_LV_USE_PNG diff --git a/src/misc/lv_fs.h b/src/misc/lv_fs.h index 9f65e1b2a..c941402c5 100644 --- a/src/misc/lv_fs.h +++ b/src/misc/lv_fs.h @@ -57,7 +57,6 @@ enum { }; typedef uint8_t lv_fs_mode_t; - /** * Seek modes. */