Skip to content

feat: add a LittleFS FsImpl backend — on-chip flash storage is unreachable today #3984

Closed

Description

@zackees

Summary

FastLED has no LittleFS (or SPIFFS) filesystem backend. fl::FileSystem can only reach an SD card over SPI, the WASM browser VFS, or a host stub — so on-chip flash storage is unreachable from FastLED code.

Verified current state

FsImpl has exactly three implementations:

Backend File
Arduino SD over SPI src/platforms/fs_sdcard_arduino.hpp:200
WASM browser VFS src/platforms/wasm/fs_wasm.cpp.hpp:358
Host stub src/platforms/stub/fs_stub.cpp.hpp:28

The only mention of LittleFS in the tree is aspirational — src/fl/stl/detail/file_io.h:19: "Embedded (future): Can map to LittleFS, SPIFFS, SD card APIs, etc."

Consequence: any sketch needing on-device file storage must use a physical SD card. examples/Fx/FxLedmapper32x32/FxLedmapper32x32.ino:64 calls filesystem.beginSd(CHIP_SELECT_PIN) for exactly this reason — it is the only hardware option available.

Why this is a clean drop-in

FileSystem::begin(FsImplPtr) (src/fl/system/file_system.h:55) already accepts an arbitrary backend, alongside the SD-specific beginSd(int cs_pin). A LittleFS FsImpl needs no core API change — it implements the existing virtual interface and is passed to begin().

Scope is contained: implement FsImpl's virtuals over the platform LittleFS API, plus a make_littlefs_filesystem() factory mirroring make_sdcard_filesystem(int cs_pin).

Platform coverage to decide

Platform LittleFS availability
ESP32 (Arduino-ESP32) LittleFS.h in core
ESP8266 LittleFS.h in core (SPIFFS deprecated)
RP2040/RP2350 (arduino-pico) LittleFS.h in core
Teensy 4.x LittleFS_Program library — program flash, wiped on re-upload
AVR none

The Teensy wipe-on-upload behavior is a genuine semantic difference worth surfacing to users rather than papering over.

Why now

This is the blocker for FastLED/fbuild's filesystem-image work: a buildfs-generated LittleFS image would be unreadable by FastLED without this backend. The two should land together, or buildfs ships pointing at an API gap.

Related

Activity

  1. zackees commented on Aug 22, 2026

    @zackees
    MemberAuthor

    Tracked in the meta issue: #3986 — remote assets: hosting, build-time resolution, and on-device filesystem deployment.

  2. zackees commented on Aug 23, 2026

    @zackees
    MemberAuthor

    Blocked pending #4003.

    I started this — FsLittleFs : FsImpl over the Arduino <LittleFS.h> API, mirroring fl/system/sd/, with a make_littlefs_filesystem(bool format_on_fail) factory and its own translation unit so the linker can dead-strip it the same way the SD chain is.

    The natural next step was FileSystem::beginLittleFs() / FileSystem::littleFs() alongside beginSd() / sd() — and that is what surfaced the problem. Adding a method per backend widens a class that is already carrying a method per format (openVideo, loadJpeg, openMp3, readJson, readScreenMap, loadFled), in a header that re-exports video.h and jpeg.h to every consumer.

    I backed the work out rather than leave a half-built backend in the tree or commit to a shape that #4003 may change. The generic seam (FileSystem::begin(FsImplPtr) plus a free factory) is probably the right minimal answer and needs no new class methods at all — but that should follow the audit rather than pre-empt it.

    Implementation notes worth keeping for whoever picks this up:

    • FsImpl is a small interface — begin(), end(), openRead(), optional ls() — so the backend itself is straightforward.
    • The filebuf handle can be nearly identical to SDFileHandle; the LittleFS File API matches Arduino SD's (available(), size(), read(), seek(), position(), close()).
    • LittleFS.begin() differs by core: ESP32 takes (formatOnFail, basePath, maxOpenFiles, partitionLabel), while ESP8266 and RP2040 expose a no-argument begin(). Defaulting format_on_fail to false matters — silently reformatting flash loses whatever was there.
    • Guard on FL_HAS_INCLUDE(<LittleFS.h>) && FL_HAS_INCLUDE(<FS.h>), matching how the SD backend probes for <SD.h> / <fs.h> / <SPI.h>.
    • Preserve the dead-stripping property documented in fl/system/sd/file_system_sd.cpp.hpp (FastLED Meta: ESP32-S3 binary-size reduction — Batch 1 (~26 KB) + Batch 2 (~26–50 KB) (supersedes #2771) #2773 item 1.2) — that is a hard constraint, not a nicety.

    Settling #4003 first means this lands in the intended structure. The implementation is small; the interface question is the real decision.

  3. zackees commented on Aug 23, 2026

    @zackees
    MemberAuthor

    Resolved — merged in #4006

    FastLED can now reach on-chip flash storage. FsImpl previously had three implementations — Arduino SD over SPI, the WASM browser VFS, and a host stub — so a sketch wanting files on an ESP32 needed a physical SD card.

    fl::FileSystem fs;
    if (fs.begin(fl::getEmbeddedFs())) {
        fl::ifstream f = fs.openRead("data/frame.rgb");
    }

    What landed

    fl/fs/embedded/embedded_fs.h        fl::getEmbeddedFs()
    fl/fs/embedded/embedded_fs.cpp.hpp  body
    platforms/embedded_fs.h             dispatch
    platforms/esp/fs/embedded_fs_esp.hpp   LittleFS (ESP32 / ESP8266)
    platforms/shared/embedded_fs_noop.hpp  null elsewhere
    fl/build/fl.fs.embedded+.cpp        own translation unit
    

    Two design corrections along the way

    The API names the medium, not the driver. The first version exposed make_littlefs_filesystem(). That forces every platform — including the host stub, which just maps to a directory on disk — to hold an opinion about LittleFS, and ESP32 can back flash with LittleFS, SPIFFS or FatFS anyway. getEmbeddedFs() matches make_sdcard_filesystem(), which likewise is not called make_sdfat_filesystem(). Recorded in #4007.

    Entry point and backend share a translation unit. An intermediate version had the backend reached through platforms/**/_build.cpp.hpp, which lands it in platforms+.cpp — an object every sketch links, so the LittleFS chain could no longer be dropped. The final shape follows fl/system/delay.h: API header, matching .cpp.hpp, and a platforms/*.h dispatch header selecting a plain-header fragment, all composing into one TU. That preserves the tree-shaking fl.fs.sd+.cpp relies on (#2773 item 1.2).

    Verification

    A green ESP32 build proves nothing here, because the no-op fragment compiles identically — three green builds were consistent with the backend never being compiled at all. Putting #error in the LittleFS branch made esp32dev fail, which is the actual proof the real backend compiles against arduino-esp32. Every occurrence traced to a single include chain through fl.fs.embedded+.cpp, confirming the backend stays inside the tree-shakable TU.

    With the probe removed, bash compile esp32dev --examples Blink succeeds. Four host tests cover the null contract — the factory links with no backend present, returns null, begin() reports failure rather than crashing, and a failed mount leaves the FileSystem safe to call.

    Not included

    format_on_fail is honored on ESP32 and ignored on ESP8266, which has no such hook. RP2040 ships LittleFS and should gain a fragment; Teensy's LittleFS_Program is wiped on re-upload and deserves its own decision — both noted in #4007.

    This unblocks FastLED/fbuild#1358 (buildfs/uploadfs), which was explicitly waiting on a backend so a generated image would be readable.

  4. moved this from Triage to Done in FastLED Trackeron Aug 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions