Skip to content

1.4.0

Choose a tag to compare

@maxgerhardt maxgerhardt released this 06 Sep 16:29
· 61 commits to main since this release

A FAT filesystem on the chip's own flash — and on a USB stick

FatFS.begin();          // FAT on the internal flash partition
FatFSUSB.begin();       // ...and a PC sees it as a removable drive
SDFS.begin();           // the SD card, at the same time

Drag a file onto the drive from a PC and read it back from a sketch. There is
one copy of ChaN's FatFs now, built for two volumes, so the flash filesystem
and the SD card can be mounted together — which is what this release is for.

That differs from arduino-pico, whose FatFS is flash-only and whose SDFS
is a separate FAT implementation (SdFat) entirely. The library names match
upstream; the internals deliberately do not.

Verified on hardware: Windows enumerates the board as a 216 KB drive,
mounts it as a 198 KB FAT volume, and files copied on land in flash. Worst-case
stall inside a mass-storage write is 1.3 ms, so USB transfers absorb it.

New libraries

FatFS FAT on the internal flash, behind the same FS/File/Dir API as SDFS and LittleFS
FatFSUSB that volume presented to a host as a USB stick

SDFS and SD are unchanged for sketches — they simply became FatFs volume 1.

Before you use FatFS

A 256 KB minimum filesystem partition, from the Filesystem Size menu, or
board_build.filesystem_size = 256k. Below that the wear-levelling layer's
fixed reserve leaves under 90 KB, which is a legal but odd FAT12 volume and a
coin-toss on a Windows host. begin() refuses and names the setting.

It shares the partition with LittleFS, and only one filesystem can live
there. Flashing a FatFS sketch over a LittleFS one reformats it, and the
reverse is equally true — the sketch on the board decides which filesystem the
board has. If you want the flash readable from a PC, FAT is the one to pick; if
only sketches ever read it, LittleFS wears better and has no minimum size.

The sketch and a host must never both hold the volume. FatFs caches
directory and allocation sectors, so a host writing underneath that cache
corrupts one or both views silently. Watch FatFSUSB.hostChanged() and call
FatFS.end(); the USBDrive example shows the whole handover.

onPlug/onUnplug exist but are best-effort: they come from SCSI commands a
host is not obliged to send, and Windows sends neither — measured, mounting
and writing and ejecting with the callbacks silent throughout. hostChanged()
also goes true on the first host write, which no host can perform silently.
Only Windows has been tested; macOS and Linux are expected to be better
behaved, but nobody has checked.

Five bugs found porting the wear-levelling layer

All from upstream's flash translation layer writing 4096/512 as a literal
8 — right for the RP2040's 4096-byte erase blocks, wrong for this part's
8192, which hold sixteen 512-byte sectors. Four of them are silent:

  • The logical-to-physical map packed a 3-bit slot index, so LBA n aliased
    onto n+8: a boot sector appeared twice and a file's directory entry landed
    on the allocation table.
  • The per-block count of live sectors was a nibble, so a full 16-slot block
    counted 16, truncated to zero, and the allocator erased a block that still
    held data.
  • Garbage collection stopped filling a reclaimed block at 8 of 16 slots,
    freeing half the space it should and wearing the flash about twice as fast.
    Invisible to a correctness test; now 15 writes per erase against a
    theoretical 16.
  • gcScore() went negative for a block holding more than eight sectors,
    mis-ranking collection candidates.
  • Two loops had no bound — a corrupt block state or inconsistent free-space
    accounting spun forever, which on this part wedges the debug probe rather
    than merely hanging the sketch. Both now fail the write instead.

docs/hazards.md has the measurements. The regression tests are the point:
the aliasing was missed by the original tests because they used LBAs 0, 1, 17
and 100, and no two of those are eight apart.