zephyr-sistema-archivos-littlefs

Sistema de archivos LittleFS en Zephyr

  • 5 min

LittleFS es un sistema de archivos diseñado para memorias flash y tolerante a cortes de alimentación que ofrece archivos y directorios sobre una partición.

En el artículo anterior vimos cómo NVS nos permite guardar variables de configuración (SSID, contraseñas) de forma eficiente. Pero NVS tiene una limitación: funciona con IDs numéricos, no con nombres de archivo.

¿Qué pasa si quieres guardar un registro de eventos (log.txt), un archivo CSV con datos de sensores (data.csv) o incluso recursos web (index.html)? NVS se queda corto.

Necesitamos un sistema de archivos real. En el mundo de los PCs usamos NTFS o FAT32. En el mundo de los microcontroladores, el rey indiscutible es LittleFS.

¿Por qué LittleFS y no FAT?

Podrías pensar: “¿Por qué no usar FAT32? Así podría leer la memoria desde Windows”.

FAT32 es terrible para memorias Flash de microcontroladores:

  1. Corrupción: Si se va la luz mientras escribes, el sistema de archivos se rompe.
  2. Desgaste: FAT escribe constantemente en la tabla de asignación (los mismos sectores), quemando la Flash en ese punto.

LittleFS está diseñado por ARM específicamente para esto:

  • Fail-Safe: Es resistente a cortes de energía. Utiliza una técnica llamada Copy-on-Write. Nunca sobrescribe datos; escribe en un bloque nuevo y, solo cuando termina, actualiza el puntero.
  • Wear Leveling: Distribuye los datos por toda la memoria para que dure más.
  • RAM: Consume muy poca memoria RAM.

NVS vs LittleFS: ¿Cuál elijo?

CaracterísticaNVS (Non-Volatile Storage)LittleFS
ParadigmaClave-Valor (ID Numérico)Archivos y Directorios (POSIX)
Uso idealConfiguración, calibración, contadoresLogs, recursos web, datos complejos
OverheadMuy bajo (rápido)Medio (necesita buffers)
APIAPI NVS de Zephyr (nvs_read)API VFS de Zephyr (fs_open, fs_read)
EstructuraPlanaJerárquica (Carpetas)

Definición en Devicetree

Al igual que con NVS, necesitamos una partición de Flash. Pero esta vez, vamos a decirle a Zephyr que esa partición pertenece a un sistema de archivos.

En tu archivo .overlay (o usando la partición storage_partition que venga por defecto), añadimos la configuración de montaje automático.

/ {
    fstab {
        compatible = "zephyr,fstab";
        lfs1: lfs1 {
            /* Montar automáticamente en /lfs1 */
            compatible = "zephyr,fstab,littlefs";
            mount-point = "/lfs1";
            partition = <&storage_partition>;
            automount;
            
            /* Configuraciones de bloque (opcionales, automáticas si no se ponen) */
            read-size = <16>;
            prog-size = <16>;
            cache-size = <64>;
            lookahead-size = <32>;
            block-cycles = <512>;
        };
    };
};
Copied!

El nodo fstab (File System Table) es una característica moderna de Zephyr. Permite que el sistema monte el disco automáticamente al arrancar, igual que hace Linux con /etc/fstab.

Configuración (prj.conf)

Activamos el subsistema de archivos y el driver de LittleFS.

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

# Opcional: Activar comandos de Shell para probar (¡Muy recomendado!)
CONFIG_FILE_SYSTEM_SHELL=y
Copied!

Uso de la API de archivos

Aquí viene la buena noticia: Zephyr utiliza la API estándar POSIX. Si alguna vez habéis programado en C para Linux o Windows (fopen, fwrite…), ya sabéis usar LittleFS en Zephyr.

La única diferencia es que usamos las versiones de Zephyr (fs_open, fs_write…) y estructuras struct fs_file_t.

Ejemplo: Escribiendo un log de arranque

#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);

/* Ruta del archivo (coincide con el mount-point del overlay) */
#define LOG_FILE_PATH "/lfs1/boot_log.txt"

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

    /* 1. Inicializar la estructura del archivo */
    fs_file_t_init(&file);

    /* 2. Abrir (o crear) el archivo */
    /* Flags: CREATE (si no existe), WRITE (escritura), APPEND (añadir al final) */
    rc = fs_open(&file, LOG_FILE_PATH, FS_O_CREATE | FS_O_WRITE | FS_O_APPEND);
    
    if (rc < 0) {
        LOG_ERR("Error abriendo archivo: %d", rc);
        return rc;
    }

    /* 3. Escribir datos */
    /* Podemos usar snprintk para formatear texto antes de escribir */
    char boot_msg[] = "Sistema arrancado\n";
    
    rc = fs_write(&file, boot_msg, strlen(boot_msg));
    if (rc < 0) {
        LOG_ERR("Error escribiendo: %d", rc);
    } else {
        LOG_INF("Log guardado correctamente.");
    }

    /* 4. Cerrar archivo (Esto asegura que los datos se guardan en flash) */
    fs_close(&file);
    
    /* --- Lectura para verificar --- */
    
    /* Reabrimos en modo lectura */
    fs_open(&file, LOG_FILE_PATH, FS_O_READ);
    
    char buffer[64];
    ssize_t bytes_read;

    LOG_INF("Contenido del log:");
    while ((bytes_read = fs_read(&file, buffer, sizeof(buffer) - 1)) > 0) {
        buffer[bytes_read] = '\0'; // Terminador nulo
        printk("%s", buffer);
    }
    
    fs_close(&file);

    return 0;
}
Copied!

Listando archivos (ls)

También podemos listar el contenido de un directorio usando fs_opendir y 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);
    /* Si el nombre está vacío, hemos terminado */
    if (entry.name[0] == 0) {
        break;
    }
    printk("Archivo: %s (Tamaño: %zu bytes)\n", entry.name, entry.size);
}

fs_closedir(&dir);
Copied!

Explorar desde la shell

Si activaste CONFIG_FILE_SYSTEM_SHELL=y, puedes gestionar los archivos desde la consola UART sin reprogramar. Esto es utilísimo para depurar.

Comandos disponibles:

  • fs mount: Muestra los puntos de montaje.
  • fs ls /lfs1: Lista archivos.
  • fs cd /lfs1: Cambia directorio.
  • fs cat log.txt: Muestra el contenido de un archivo.
  • fs rm log.txt: Borra un archivo.
uart:~$ fs ls /lfs1
boot_log.txt    18
Copied!