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:
- Corrupción: Si se va la luz mientras escribes, el sistema de archivos se rompe.
- 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ística | NVS (Non-Volatile Storage) | LittleFS |
|---|---|---|
| Paradigma | Clave-Valor (ID Numérico) | Archivos y Directorios (POSIX) |
| Uso ideal | Configuración, calibración, contadores | Logs, recursos web, datos complejos |
| Overhead | Muy bajo (rápido) | Medio (necesita buffers) |
| API | API NVS de Zephyr (nvs_read) | API VFS de Zephyr (fs_open, fs_read) |
| Estructura | Plana | Jerá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>;
};
};
};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=yUso 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;
}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);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