Skip to main content

LittleFS Driver

The LittleFS driver mounts a flash partition as a POSIX-compatible filesystem so alarm audio files can be opened with standard fopen/fread calls. LittleFS is power-fail safe by design — it uses copy-on-write semantics and never leaves the filesystem in an inconsistent state after an unexpected reset.

See the Audio Storage page for the partition layout and how to flash audio files during development.


Why LittleFS Instead of FAT32?

LittleFSFAT32
Power-fail safeYes — copy-on-writeNo — can corrupt on power loss
Wear levellingYes — built-inNo
Filesystem recoveryAutomaticManual fsck or reformat
POSIX interfaceYesYes
Max file sizeLimited by partitionLimited by partition

The main advantage here is power-fail safety. The alarm clock may lose power at any time. LittleFS ensures the filesystem is always in a consistent state on the next boot, even if power is cut mid-write.


Mount

#include "esp_littlefs.h"

#define LFS_MOUNT_POINT "/lfs"

esp_err_t lfs_init(void) {
esp_vfs_littlefs_conf_t conf = {
.base_path = LFS_MOUNT_POINT,
.partition_label = "lfs",
.format_if_mount_failed = false, // never silently format in production
.dont_mount = false,
};

esp_err_t err = esp_vfs_littlefs_register(&conf);
if (err != ESP_OK) {
ESP_LOGE("lfs", "Failed to mount LittleFS partition 'lfs': %s",
esp_err_to_name(err));
return err;
}

ESP_LOGI("lfs", "LittleFS mounted at %s", LFS_MOUNT_POINT);
return ESP_OK;
}

After mounting, files are accessible via standard POSIX I/O:

FILE *f = fopen("/lfs/alarms/alarm.wav", "rb");

Unmount

LittleFS does not require explicit unmounting to prevent corruption (unlike FAT32), but unmounting releases the VFS registration if you need to reinitialise:

esp_err_t lfs_deinit(void) {
return esp_vfs_littlefs_unregister("lfs");
}

Listing Alarm Files

void lfs_list_alarms(void) {
DIR *dir = opendir("/lfs/alarms");
if (!dir) {
ESP_LOGE("lfs", "Cannot open /lfs/alarms");
return;
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_REG) { // regular files only
ESP_LOGI("lfs", " %s", entry->d_name);
}
}
closedir(dir);
}

OTA Audio Write (Temp-Then-Rename)

esp_err_t lfs_update_alarm_wav(const uint8_t *data, size_t len) {
// Write to a temp file — original is intact if power is lost mid-write
FILE *tmp = fopen("/lfs/alarms/alarm_tmp.wav", "wb");
if (!tmp) {
ESP_LOGE("lfs", "Cannot open temp file for writing");
return ESP_FAIL;
}

size_t written = fwrite(data, 1, len, tmp);
fclose(tmp);

if (written != len) {
ESP_LOGE("lfs", "Write incomplete (%zu of %zu bytes)", written, len);
unlink("/lfs/alarms/alarm_tmp.wav");
return ESP_FAIL;
}

// Atomic rename — LittleFS guarantees power-fail safety
if (rename("/lfs/alarms/alarm_tmp.wav", "/lfs/alarms/alarm.wav") != 0) {
ESP_LOGE("lfs", "Rename failed: %s", strerror(errno));
return ESP_FAIL;
}

ESP_LOGI("lfs", "alarm.wav updated (%zu bytes)", len);
return ESP_OK;
}

The rename() call is atomic on LittleFS — either the rename completes fully, or the original file is untouched. There is no window in which both the temp file and the original are absent.


Checking Available Space

size_t total = 0, used = 0;
esp_littlefs_info("lfs", &total, &used);
ESP_LOGI("lfs", "LittleFS: %zu KB used of %zu KB total", used / 1024, total / 1024);

Troubleshooting

SymptomLikely causeFix
Failed to mount LittleFS partition 'lfs'Partition label mismatch or no LittleFS image flashedConfirm partition_label = "lfs" matches partitions.csv; flash image with esptool.py write_flash
Mount succeeds but fopen returns NULLWrong path or directory doesn't existVerify the LittleFS image contains alarms/alarm.wav; check mklittlefs source directory
fopen succeeds but audio doesn't playCorrupt or wrong WAV formatConfirm 16-bit PCM WAV; check with afinfo (macOS) or soxi (Linux)
Filesystem corrupt after power lossShould not happen with LittleFS — but if it does, reflash the imageUse format_if_mount_failed = true temporarily during development to recover automatically
Wrong file size after OTA updatePower loss during write before renameTemp-then-rename pattern protects the original; delete the _tmp file and retry