zephyr-sistema-archivos-littlefs

LittleFS File System in Zephyr

  • 5 min

LittleFS is a file system designed for flash memory and power-loss tolerant that provides files and directories over a partition.

In the previous article, we saw how NVS allows us to efficiently save configuration variables (SSID, passwords). But NVS has a limitation: it works with numeric IDs, not file names.

What if you want to save an event log (log.txt), a CSV file with sensor data (data.csv), or even web resources (index.html)? NVS falls short.

We need a real file system. In the PC world, we use NTFS or FAT32. In the microcontroller world, the undisputed king is LittleFS.

Why LittleFS and not FAT?

You might think: “Why not use FAT32? That way I could read the memory from Windows”.

FAT32 is terrible for microcontroller Flash memory:

  1. Corruption: If power is lost while writing, the file system breaks.
  2. Wear: FAT constantly writes to the allocation table (the same sectors), wearing out the Flash at that point.

LittleFS is designed by ARM specifically for this:

  • Fail-Safe: It is resistant to power outages. It uses a technique called Copy-on-Write. It never overwrites data; it writes to a new block and only updates the pointer when finished.
  • Wear Leveling: Distributes data across the entire memory to make it last longer.
  • RAM: Consumes very little RAM.

NVS vs LittleFS: Which one to choose?

FeatureNVS (Non-Volatile Storage)LittleFS
ParadigmKey-Value (Numeric ID)Files and Directories (POSIX)
Ideal UseConfiguration, calibration, countersLogs, web resources, complex data
OverheadVery low (fast)Medium (needs buffers)
APIZephyr NVS API (nvs_read)Zephyr VFS API (fs_open, fs_read)
StructureFlatHierarchical (Folders)

Devicetree Definition

As with NVS, we need a Flash partition. But this time, we will tell Zephyr that this partition belongs to a file system.

In your .overlay file (or using the default storage_partition), we add the auto-mount configuration.

/ {
    fstab {
        compatible = "zephyr,fstab";
        lfs1: lfs1 {
            /* Automatically mount at /lfs1 */
            compatible = "zephyr,fstab,littlefs";
            mount-point = "/lfs1";
            partition = <&storage_partition>;
            automount;
            
            /* Block configurations (optional, automatic if not set) */
            read-size = <16>;
            prog-size = <16>;
            cache-size = <64>;
            lookahead-size = <32>;
            block-cycles = <512>;
        };
    };
};
Copied!

The fstab (File System Table) node is a modern Zephyr feature. It allows the system to automatically mount the disk at boot, just like Linux does with /etc/fstab.

Configuration (prj.conf)

We enable the file system subsystem and the LittleFS driver.

CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_FILE_SYSTEM=y
CONFIG_FILE_SYSTEM_LITTLEFS=y

# Optional: Enable Shell commands for testing (Highly recommended!)
CONFIG_FILE_SYSTEM_SHELL=y
Copied!

Using the File API

Here comes the good news: Zephyr uses the standard POSIX API. If you’ve ever programmed in C for Linux or Windows (fopen, fwrite…), you already know how to use LittleFS in Zephyr.

The only difference is that we use Zephyr’s versions (fs_open, fs_write…) and struct fs_file_t structures.

Example: Writing a Boot Log

#include <zephyr/kernel.h>
#include <zephyr/fs/fs.h>
#include <zephyr/logging/log.h>
#include <string.h>

LOG_MODULE_REGISTER(demo_fs, LOG_LEVEL_INF);

/* File path (matches the mount-point from the overlay) */
#define LOG_FILE_PATH "/lfs1/boot_log.txt"

int main(void)
{
    struct fs_file_t file;
    int rc;

    /* 1. Initialize the file structure */
    fs_file_t_init(&file);

    /* 2. Open (or create) the file */
    /* Flags: CREATE (if it doesn't exist), WRITE (writing), APPEND (add to end) */
    rc = fs_open(&file, LOG_FILE_PATH, FS_O_CREATE | FS_O_WRITE | FS_O_APPEND);
    
    if (rc < 0) {
        LOG_ERR("Error opening file: %d", rc);
        return rc;
    }

    /* 3. Write data */
    /* We can use snprintk to format text before writing */
    char boot_msg[] = "System booted\n";
    
    rc = fs_write(&file, boot_msg, strlen(boot_msg));
    if (rc < 0) {
        LOG_ERR("Error writing: %d", rc);
    } else {
        LOG_INF("Log saved successfully.");
    }

    /* 4. Close the file (This ensures data is saved to flash) */
    fs_close(&file);
    
    /* --- Reading to verify --- */
    
    /* Reopen in read mode */
    fs_open(&file, LOG_FILE_PATH, FS_O_READ);
    
    char buffer[64];
    ssize_t bytes_read;

    LOG_INF("Log contents:");
    while ((bytes_read = fs_read(&file, buffer, sizeof(buffer) - 1)) > 0) {
        buffer[bytes_read] = '\0'; // Null terminator
        printk("%s", buffer);
    }
    
    fs_close(&file);

    return 0;
}
Copied!

Listing Files (ls)

We can also list the contents of a directory using fs_opendir and fs_readdir.

struct fs_dir_t dir;
struct fs_dirent entry;

fs_dir_t_init(&dir);
fs_opendir(&dir, "/lfs1");

while (1) {
    fs_readdir(&dir, &entry);
    /* If the name is empty, we are done */
    if (entry.name[0] == 0) {
        break;
    }
    printk("File: %s (Size: %zu bytes)\n", entry.name, entry.size);
}

fs_closedir(&dir);
Copied!

Exploring from the Shell

If you enabled CONFIG_FILE_SYSTEM_SHELL=y, you can manage files directly from the UART console without reprogramming. This is extremely useful for debugging.

Available commands:

  • fs mount: Shows mount points.
  • fs ls /lfs1: Lists files.
  • fs cd /lfs1: Changes directory.
  • fs cat log.txt: Displays the contents of a file.
  • fs rm log.txt: Deletes a file.
uart:~$ fs ls /lfs1
boot_log.txt    18
Copied!