It never returns from “Formatting LittleFS…”. We’ve tried different slice sizes and buffer sizes. It looks like LittleFS’s format operation hangs on ESP32 when using RIOT OS’s mtd_mapper + periph_flash backend. We need a working persistent storage method for ESP32 in RIOT OS without ESP-IDF.
main.c:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "vfs.h"
#include "fs/littlefs2_fs.h"
#include "mtd.h"
#include "mtd_mapper.h"
#include "shell.h"
/* Filesystem placement */
#define MOUNT_POINT "/fs"
#define LOG_FILE "/fs/log.txt"
#define LFS_SIZE_BYTES (1 * 256 * 1024)
/* Some RIOT boards define MTD_0; fallback to device 0 if not */
#ifndef MTD_0
#define MTD_0 mtd_dev_get(0)
#endif
/* --- GLOBAL state: must persist after mount_lfs() returns --- */
static littlefs2_desc_t g_lfs;
static mtd_mapper_parent_t g_parent;
static mtd_mapper_region_t g_region;
/* ------------------------------------------------------------ */
static int mount_lfs(void)
{
mtd_dev_t *base = MTD_0;
if (!base) { puts("ERROR: No base MTD (MTD_0)"); return -1; }
const uint32_t sector_size = base->page_size * base->pages_per_sector;
if (!sector_size) { puts("ERROR: bad flash geometry"); return -1; }
const uint32_t total_sectors = base->sector_count;
const uint32_t lfs_sectors = LFS_SIZE_BYTES / sector_size;
if (lfs_sectors == 0 || lfs_sectors >= total_sectors) {
puts("ERROR: LFS_SIZE_BYTES invalid for this flash");
return -1;
}
const uint32_t start_sector = total_sectors - lfs_sectors;
/* Initialize globals via temporaries to respect the macro semantics */
mtd_mapper_parent_t tmp_parent = MTD_PARENT_INIT(base);
mtd_mapper_region_t tmp_region = {
.mtd = {
.driver = &mtd_mapper_driver,
.sector_count = lfs_sectors,
.pages_per_sector = base->pages_per_sector,
.page_size = base->page_size,
.write_size = base->write_size,
},
.parent = &g_parent, /* will point to the global after copy */
.sector = start_sector
};
g_parent = tmp_parent;
g_region = tmp_region;
memset(&g_lfs, 0, sizeof(g_lfs));
g_lfs.dev = &g_region.mtd;
/* Try to mount; if corrupt/blank, format then mount again */
int res = lfs_mount(&g_lfs.fs, &g_lfs.config);
if (res < 0) {
puts("Formatting LittleFS...");
if (lfs_format(&g_lfs.fs, &g_lfs.config) < 0) {
puts("ERROR: lfs_format() failed");
return -1;
}
if (lfs_mount(&g_lfs.fs, &g_lfs.config) < 0) {
puts("ERROR: lfs_mount() failed after format");
return -1;
}
}
/* Register FS with VFS */
vfs_mount_t mnt = {
.mount_point = MOUNT_POINT,
.fs = &littlefs2_file_system,
.private_data = &g_lfs,
};
if (vfs_mount(&mnt) != 0) {
puts("ERROR: vfs_mount() failed");
lfs_unmount(&g_lfs.fs);
return -1;
}
puts("LittleFS mounted at /fs");
return 0;
}
/* -------- Shell commands -------- */
static int cmd_logshow(int argc, char **argv)
{
(void)argc; (void)argv;
FILE *fp = fopen(LOG_FILE, "r");
if (!fp) { puts("No log file yet."); return 0; }
puts("=== /fs/log.txt ===");
char buf[128];
while (fgets(buf, sizeof(buf), fp)) printf("%s", buf);
puts("\n====================");
fclose(fp);
return 0;
}
static int cmd_logadd(int argc, char **argv)
{
if (argc < 2) { puts("usage: logadd <text>"); return 1; }
FILE *fp = fopen(LOG_FILE, "a");
if (!fp) { puts("open for write failed"); return 1; }
for (int i = 1; i < argc; i++) {
fputs(argv[i], fp);
if (i + 1 < argc) fputc(' ', fp);
}
fputc('\n', fp);
fclose(fp);
puts("OK");
return 0;
}
static int cmd_logclear(int argc, char **argv)
{
(void)argc; (void)argv;
FILE *fp = fopen(LOG_FILE, "w");
if (!fp) { puts("open for write failed"); return 1; }
fclose(fp);
puts("cleared");
return 0;
}
static const shell_command_t cmds[] = {
{ "logshow", "show /fs/log.txt", cmd_logshow },
{ "logadd", "append a line", cmd_logadd },
{ "logclear", "truncate the log file", cmd_logclear},
{ NULL, NULL, NULL }
};
int main(void)
{
puts("=== LittleFS2 test with shell (ESP32/RIOT) ===");
if (mount_lfs() != 0) {
puts("Mount failed — filesystem not available.");
}
puts("Commands: logshow | logadd <text> | logclear | ps | help");
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(cmds, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}
Makefile:
include ../Makefile.net_common
RIOTBASE ?= $(CURDIR)/../../../..
# If no BOARD is found in the environment, use this default:
BOARD ?= esp32-wroom-32
QUIET ?= 1
USEMODULE += vfs
USEMODULE += littlefs2_fs
USEPKG += littlefs2
USEMODULE += mtd
USEMODULE += mtd_mapper
USEMODULE += periph_flash
USEMODULE += shell
USEMODULE += ps
USEMODULE += vfs_default
USEMODULE += stdio_uart
USEMODULE += stdin
# Tell RIOT where your partitions CSV is:
# (Path is relative to this Makefile; adjust if you put it somewhere else.)
CFLAGS += -DESP32_PARTITIONS_CSV=\"partitions.csv\"
include $(RIOTBASE)/Makefile.include