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?
| LittleFS | FAT32 | |
|---|---|---|
| Power-fail safe | Yes — copy-on-write | No — can corrupt on power loss |
| Wear levelling | Yes — built-in | No |
| Filesystem recovery | Automatic | Manual fsck or reformat |
| POSIX interface | Yes | Yes |
| Max file size | Limited by partition | Limited 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
| Symptom | Likely cause | Fix |
|---|---|---|
Failed to mount LittleFS partition 'lfs' | Partition label mismatch or no LittleFS image flashed | Confirm partition_label = "lfs" matches partitions.csv; flash image with esptool.py write_flash |
Mount succeeds but fopen returns NULL | Wrong path or directory doesn't exist | Verify the LittleFS image contains alarms/alarm.wav; check mklittlefs source directory |
fopen succeeds but audio doesn't play | Corrupt or wrong WAV format | Confirm 16-bit PCM WAV; check with afinfo (macOS) or soxi (Linux) |
| Filesystem corrupt after power loss | Should not happen with LittleFS — but if it does, reflash the image | Use format_if_mount_failed = true temporarily during development to recover automatically |
| Wrong file size after OTA update | Power loss during write before rename | Temp-then-rename pattern protects the original; delete the _tmp file and retry |