[EN] LittleFS Filesystem

The article discusses the use of the LittleFS library and introduces a library developed for use with the esp32 microcontroller, which includes a plugin for the Arduino IDE for uploading files to the microcontroller’s ROM. Make it convenient to load data to store and run. For this reason, if programmers find it difficult to transcode HTML/CSS/JavaScript to be a string manually and switching to uploading files to esp32 and reading the web files directly to use will be something that will require training to use LittleFS as a reliable library.


LittleFS

LittleFS is a small filesystem designed for use with microcontrollers under a license BSD-3-Clause, which allows users to write, edit, close and delete files, which LittleFS features include:

  • If the power goes out, the file system will revert to its previous active state.
  • Designed to evade damaged flash blocks.

The procedure for using the LittleFS file system is as follows.

  1. Set up the file system.
  2. mount()
  3. If mount() doesn’t show, no filesystem is installed, call format() .
  4. Active directories and files
  5. unmount()

A detailed study of the lfs.h file (accessed on 2021-11-01) is as follows:

lfs_config‘s structure

The lfs_config structure is used to configure the desired file system. The details of the preliminary values that must be set before use are as follows.

const struct lfs_config cfg = {
    // block device operations
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // block device configuration
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

In the example above, you can see that the settings are as follows:

  • Configurable to read, prog, erase, sync, enabling data creation, reading, deleting and syncing.
  • Set the block size.
    • Reading is 16 blocks in size.
    • Writing is 16 blocks in size.
    • Block size is 4096 bytes.
    • Number of blocks is 128 blocks.
    • The size of the cache is 16 blocks.
    • The size of the lookhead (the buffer of the next block) is 16 blocks.
    • The block cycle value or the value of the number of times to delete a block is 500 (usually set to 100-1000).

lfs_info‘s structure

The lfs_info data structure is used to store file details which are detailed as follows

struct lfs_info {
    uint8_t type;
    lfs_size_t size;
    char name[LFS_NAME_MAX+1];
};

where

  • type is the type of file
    • LFS_TYPE_REG is a file
    • LFS_TYPE_DIR is a directory
  • size is the size of a REG file that cannot exceed a 32-bit integer.
  • name is the name of the file or directory

lfs_config’s structure

LittleFS’ lfs_config data structure is as follows:

struct lfs_file_config {
    void *buffer;
    struct lfs_attr *attrs;
    lfs_size_t attr_count;
};

where

  • buffer is a pointer variable to cache memory using the lfs_malloc() command.
  • lfs_attr is a variable that points to a list of all attributes.
  • attr_count is the number of attributes stored in lfs_config

lfs_attr’s structure

The lfs_attr data structure has the following format:

struct lfs_attr {
    uint8_t type;
    void *buffer;
    lfs_size_t size;
};

where

  • type for storing the type of attribute
  • buffer for storing attribute storage location memory.
  • size is the size of the attribute that does not exceed LFS_ATTR_MAX

Type of error

The errors reported in LittleFS operations are as follows:

  • LFS_ERR_OK means no error
  • LFS_ERR_IO means an error occurred during the operation of the device.
  • LFS_ERR_CORRUP means the memory block is corrupted.
  • LFS_ERR_NOENT means no directory listing
  • LFS_ERR_EXIST means a list of files or directories already exists.
  • LFS_ERR_NOTDIR means the list is not a directory
  • LFS_ERR_ISDIR means to the listing as a directory.
  • LFS_ERR_NOTEMPTY means directory is not empty
  • LFS_ERR_BADF means the file number is invalid.
  • LFS_ERR_FBIG means the file size is too large
  • LFS_ERR_INVAL means the parameter is invalid.
  • LFS_ERR_NOSPC means there is not enough space on the device.
  • LFS_ERR_NOMEM means insufficient memory
  • LFS_ERR_NOATTR means attribute has no data
  • LFS_ERR_NAMETOOLONG means the filename is too long.

lfs_format()

The command for formatting a block to use with LittleFS has the following syntax: This will return a negative value if an error occurs.

int lfs_format(lfs_t *lfs, const struct lfs_config *config);

lfs_mount()

The command for associating a file system as a drive to use has the following command format:

int lfs_mount(lfs_t *lfs, const struct lfs_config *config);

lfs_unmount()

The command to unlink a filesystem after it is no longer active is as follows:

int lfs_unmount(lfs_t *lfs);

lfs_remove()

For deleting files or directories. Deleting directories only applies to empty directories that do not contain nested files or directories. The format of the command is as follows.

int lfs_remove(lfs_t *lfs, const char *path);

lfs_rename()

The command for renaming a file or directory is as follows.

int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);

lfs_stat()

If you want the information of the file according to the lfs_info structure, you can use the following command:

int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);

Open file

The command and syntax for activating a file are:

int lfs_file_open(lfs_t *lfs, lfs_file_t *file, const char *path, int flags);

Close file

To close an open file, use the following command:

int lfs_file_close(lfs_t *lfs, lfs_file_t *file);

Read data from file

The command to read data from a file is lfs_file_read() .

lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, void *buffer, lfs_size_t size);

Writing data to files

To write data from a buffer to a file of the lfs file system, the format is as follows.

lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, const void *buffer, lfs_size_t size);

Moving the file pointer

The file system has variables for being the default pointer for writing or reading data. They tend to automatically reposition when opening, closing, reading and writing files. Programmers can change the position of this pointer with the command lfs_file_seek() according to the usage pattern as follows.

lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, lfs_soff_t off, int whence);

The whence value is as follows.

  • LFS_SEEK_SET means starting from the beginning of the file
  • LFS_SEEK_CUR means starting from the current position
  • LFS_SEEK_END means starting from the end of the file

Truncating the file

If you want to cut the file from where the file pointer is pointing without deleting the file to use the command lfs_truncate() in the following format.

int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);

The position of the pointer in the file.

To know the pointer location of a file can be read from the following command.

lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);

lfs_file_rewind()

If you want the pointer of the file return to the beginning of the file, you can use the command lfs_file_rewind() in the following format.

int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);

Want to know the size of the file

If the programmer wants to know the size of the enabled file, run the following command.

int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);

Directory creation

The command to create a directory is

int lfs_mkdir(lfs_t *lfs, const char *path);

Commands to open directories

If you want to move the location of a directory into a specified child directory or by the specified path, use the command in the following format

int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);

Close directory

If you want to disable a directory to disable the entries under it, you can do so with the following command.

int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);

Directory listing

To view entries within the system directory or system path, use the following command. The list is stored in a memory buffer called info, which is equivalent to using the ls command in Unix operating systems.

int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);

Relocate directory

To move the location to another directory use the following command. This is the same as using the cd command in Unix operating systems.

int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);

Returning to the root directory

To move a directory reference to the root directory of a file system, use the following command.

int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);

Current directory

To read the location of the current active directory run the command lfs_dir_tell() in the following format.

lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);

File system size

If you want to know the size of the file system, use the command in the following format.

lfs_ssize_t lfs_fs_size(lfs_t *lfs);

Accessing all directories

Accessing an entire directory or traverse to access a directory’s logs can be done in the form of the following command.

int lfs_fs_traverse(lfs_t lfs, int (cb)(void*, lfs_block_t), void *data);

It must create a function to be called from the command lfs_fs_traverse() with the following function format.

int function_name( lfs_block_t b ) {
    return data;
}

An example of the default program included with LittleFS is to create a filesystem to count the number of file system calls. This lets you know how many times the file system has been activated (In the past, we used to use this method for locking the number of system usage, to create a system that is a sample job).

#include "lfs.h"

// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;

// configuration of the filesystem is provided by this struct
const struct lfs_config cfg = {
    // block device operations
    .read  = user_provided_block_device_read,
    .prog  = user_provided_block_device_prog,
    .erase = user_provided_block_device_erase,
    .sync  = user_provided_block_device_sync,

    // block device configuration
    .read_size = 16,
    .prog_size = 16,
    .block_size = 4096,
    .block_count = 128,
    .cache_size = 16,
    .lookahead_size = 16,
    .block_cycles = 500,
};

// entry point
int main(void) {
    // mount the filesystem
    int err = lfs_mount(&lfs, &cfg);

    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) {
        lfs_format(&lfs, &cfg);
        lfs_mount(&lfs, &cfg);
    }

    // read current count
    uint32_t boot_count = 0;
    lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));

    // update boot count
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);

    // release any resources we were using
    lfs_unmount(&lfs);

    // print the boot count
    printf("boot_count: %d\n", boot_count);
}

LittleFS for ESP32

From the advantages of LittleFS and the instruction set of LittleFS above, it has been developed for use with ESP32 on the framework of Arduino developed by lorol. To install it, go to the menu Sketch -> Include Library -> Manage Libraries. … as in Figure 1, then select the search list as LittleFS, you will find a library named ESP32 as in Figure 2.

Figure 1 Manage Libraries…
Figure 2 LittleFS_esp32 library

There are two examples of programs that come with the library:

There is also an optional tool for uploading files from the programmer to store in the microcontroller via the serial communication port named arduino-esp32fs-plugin. For esp8266, you can learn from Random Nerd Tutorial.

Conclusion

From this article, we will find that using LittleFS we can use FlashROM of esp32/esp8266 to get more variety. This makes it possible to use the unused portion of the program independently of the developed program. However, you should be aware that the flash ROMs that come with esp32 or esp8266 have an expiration date. Usually set for around 1000 erase/write cycles (It must be erased before it can be overwritten.). So if you’re worried that using LittleFS will cause Flash ROMs to expire faster. Let’s look back. Usually, every time the program is uploaded to the microcontroller, it causes deletion and overwriting. Which the location beyond our program is not used and if you look at writing 1 program each day, it means that the ROM expires 1 time per day, making it possible to write 1000 programs (approximately) or take up to 3 years before that chip expires and even after the expiration of the work, that’s not about chip wastage. When the program is still on and the chip has not been damaged, the program on the chip is still available. If you are worried that if writing is often deleted and corrupted, what to do with corrupted data? This must depend on your system design. What is written information? Is it permanent or is it a long-term value? If so, and is it valid enough to function within the warranty period we provide to the customer? then keep it in the rom (But don’t forget to make a backup system too) and if anything changes often, it should be stored on other system media, such as throwing it into the cloud or using an SD-Card (which is longer life), etc.

Finally, have fun with programming.

References

  1. github : LittleFS
  2. lorol/LITTLEFS
  3. lorol/arduino-esp32fs-plugin
  4. Random Nerd Tutorials: Install ESP8266 NodeMCU LittleFS Filesystem Uploader in Arduino IDE

(C) 2020-2022, By Jarut Busarathid and Danai Jedsadathitikul
Updated 2022-01-10