LittleFS means nothing

ChrisCurl

Active member
There are all these boards that claim to support LittleFS, but each one is different from the others. So LittleFS really doesn't mean anything. I develop my projects to be able to run on PCs (both Windows and Linux), as well as development boards that support the Arduino IDE. Why can't boards that support saving files on the flash provide the same subet of the Posix file operations, fopen(), fclose(), fdelete(), fread(), fwrite(), fseek() and ftell()? That would give us all we need.
 
We don't support any Posix APIs on Teensy, for files or nearly anything else.

We do support the Arduino SD library API.
 
We don't support any Posix APIs on Teensy, for files or nearly anything else.

We do support the Arduino SD library API.

Thanks, and sorry for the rant. I love my Teensy4.0.

So can I use the Arduino SD Library instead, and not use the Teensy flavor of LittleFS (LittleFS_Program)? I don't need a SD card as my program is so minimal that it leaves over a megabyte for files, which is more than enough for my purposes.

It's just that including the support for Files is a pain because I also have to support PCs (both Windows and Linux) as well as other boards. Almost everything else is not board-specific, but I have not found any consistency in the way files are supported on the different development boards, so I end up having to become too familiar with each different flavor and re-developing the same thing over and over again.
 
Last edited:
Thanks, and sorry for the rant. I love my Teensy4.0.

So can I use the Arduino SD Library instead, and not use the Teensy flavor of LittleFS (LittleFS_Program)? I don't need a SD card as my program is so minimal that it leaves over a megabyte for files, which is more than enough for my purposes.

It's just that including the support for Files is a pain because I also have to support PCs (both Windows and Linux) as well as other boards. Almost everything else is not board-specific, but I have not found any consistency in the way files are supported on the different development boards, so I end up having to become too familiar with each different flavor and re-developing the same thing over and over again.

Or the alternative is to write your own library that does what you want. You use that library exclusively for file I/O. The library then has several variants, depending on the OS, microprocessor, phase of the moon, etc. Within the library, you have to do all of the stuff needed for a specific system, but the rest of your code just uses your library with the abstraction you want. That minimizes the amount of code you have to re-invent as you move stuff from one platform to another. Sure, if you only have one application, then doing the library may be more work, but if you have 10 or 20 different apps, then it is less work overall.
 
Or the alternative is to write your own library that does what you want. You use that library exclusively for file I/O. The library then has several variants, depending on the OS, microprocessor, phase of the moon, etc. Within the library, you have to do all of the stuff needed for a specific system, but the rest of your code just uses your library with the abstraction you want. That minimizes the amount of code you have to re-invent as you move stuff from one platform to another. Sure, if you only have one application, then doing the library may be more work, but if you have 10 or 20 different apps, then it is less work overall.

That is exactly my point. There are already APIs defined for file ops, the Posix one and LittleFS. Posix isn't supported, and LittleFS is not consistent. So now I have to create ANOTHER one, which is exactly what I was hoping to avoid.
 
There are lots of standard ones. Actually, LittleFs as well as SD.h as well as MSC stuff (opening files on USBHost drives) are all setup to use FS.h on the Teensy boards.

Note SDFat has other apis... Where the SD library is a thin wrapper over it, that gives it the same interface as the others.

Why I mention this. Is you don't have to reinvent the entire wheel here.

You could decide on what API/Interface you wish to use and when necessary, write a simple wrapper.
I did some of this earlier when I was playing with other boards like RPI, BBBK, Odroid, UP, Edison... Where I ported some of the Arduino library code over to Linux, such that I could
if I wanted to do so, do things like: myfile.print(....) or XBeeSerial.printf(....)
 
There are lots of standard ones. Actually, LittleFs as well as SD.h as well as MSC stuff (opening files on USBHost drives) are all setup to use FS.h on the Teensy boards.

Note SDFat has other apis... Where the SD library is a thin wrapper over it, that gives it the same interface as the others.

Why I mention this. Is you don't have to reinvent the entire wheel here.

You could decide on what API/Interface you wish to use and when necessary, write a simple wrapper.
I did some of this earlier when I was playing with other boards like RPI, BBBK, Odroid, UP, Edison... Where I ported some of the Arduino library code over to Linux, such that I could
if I wanted to do so, do things like: myfile.print(....) or XBeeSerial.printf(....)
That is exactly what I am trying to do ... create a LittleFS-based version of my own little wrapper for a few typical file operations ... an API with a feel like a subset of the POSIX file API:

// FILEs - the c4 FILE API
extern void fileInit();
extern int fOpen(char *fn, int md);
extern void fClose(int fh);
extern int fRead(char *buf, int sz, int fh);
extern int fWrite(char *buf, int sz, int fh);
extern int fGetC(int fh);
extern void fPutC(int c, int fh);
extern int fGetS(char *buf, int sz, int fh);
extern void fDelete(char *fn);
extern void fList();

The documentation I found for the Teensy support of LittleFS starts directs me to start with LittleFS_<type>, e.g.:

#include <LittleFS.h>
LittleFS_Program myFS;

LittleFS_Program appears to be a Teensy-specific thing, which breaks my goal right away.

Maybe there is another way to use littlefs that is standard, but I am not aware of it.
 
Last edited:
LittleFS_Program appears to be a Teensy-specific thing, which breaks my goal right away.

Your goal is unreasonable.

With an operating system like Linux or Windows, thousands of driver files are pre-installed and more can be added at runtime (especially common with Windows). Usually some form of dynamic linking at runtime and address space virtualization is used to allow thousands of separately developed drivers to be able to be used as needed in any combination. Usually many megabytes of storage media are used for the set of drivers, and kernels tend to consume many megabytes of RAM.

Embedded "bare metal" systems like Teensy have all the code compiled into a single static executable binary. There isn't any sort of provision for drivers to be loaded and dynamically linked at runtime. The entire space for executable code is only a couple megabytes and on-chip RAM (not separate DRAM silicon) is a precious resource usually measured in kilobytes rather than gigabytes. The hardware is just fundamentally different from a conventional PC or single board computer which is meant to be similar to a PC, and the way software is built needs to be done differently to work with the fundamentally different hardware.

Class names like LittleFS_Program are the way you create instances of the device drivers which will be built into your program which runs on Teensy. The LittleFS package for Teensy has many of these names, each representing a device driver for a specific type of storage media. You compiled code will have the device drivers for the instance you create in the code, and only those drivers, no others.

Likewise, if you use LittleFS_Program or other LittleFS classes, but not SD or the USBHost storage classes, your program will be built with code for the LittleFS filesystem format, but no code for FAT-based filesystems would be built into the program. These class names and instances of them in the code are essential for the static binary executable to "know" which drivers and feature to build.

Expecting 100% or near 100% POSIX compatibility from a bare metal microcontroller is just not realistic.

Perhaps more could be done to emulate POSIX APIs, but the hardware and the way software is built are fundamentally different, so you're just not ever going to get really good POSIX compliance on a bare-metal microcontroller platform.
 
Why can't boards that support saving files on the flash provide the same subet of the Posix file operations, fopen(), fclose(), fdelete(), fread(), fwrite(), fseek() and ftell()? That would give us all we need.

You can use Elm Chan's fatfs that uses the fopen() etc API, that is used in non-Arduino environments
There is a port to Teensy https://github.com/WMXZ-EU/uSDFS, which, however, has not been touched for 17 months, but you are free to try it.
 
Your goal is unreasonable.
...
Perhaps more could be done to emulate POSIX APIs, but the hardware and the way software is built are fundamentally different, so you're just not ever going to get really good POSIX compliance on a bare-metal microcontroller platform.
Certainly you can see that the LittleFS_<x> functions map very closely to the POSIX versions. So honestly can't see why the functionality can't use the same names.

I have wrapped them for Teensy; now I will be doing it all again for the Pico, and again for the ESP_<x>. Note that this is for a Forth system, so the arguments are on the DATA stack, but you can see the mapping there pretty easily.

Code:
// file-teensy.h
// File ops on the Teensy are different
#include <LittleFS.h>

LittleFS_Program myFS;

#define NF 10
#define VALIDF(x) BTW(x,1,NF) && (files[x])

static File files[NF+1];
static int isInit = 0;

void fileInit() {
    myFS.begin(1 * 1024 * 1024);
    printString("\r\nLittleFS: initialized");
    printStringF("\r\nTotal Size: %llu bytes, Used: %llu", myFS.totalSize(), myFS.usedSize());
    for (int i = 0; i <= NF; i++) { files[i] = 0; }
    isInit = 1;
}

int openSlot() {
    if (!isInit) { fileInit(); }
    for (int i = 1; i <= NF; i++) {
        if (files[i] == 0) { return i; }
    }
    return 0;
}

// (name mode--fh)
void fOpen() {
    CELL mode = pop();
    char *fn = (char*)pop();
    push(0);
    int i = openSlot();
    if (i) {
        files[i] = myFS.open(fn, (mode) ? FILE_WRITE : FILE_READ);
        if (files[i]) {
          pop();
          push(i);
        }
    }
}

// (fh--)
void fClose() {
    CELL fh = pop();
    if (VALIDF(fh)) { 
        files[fh].close();
        files[fh] = 0;
    }
}

// (a sz fh--n)
void fRead() {
    CELL fh = pop();
    CELL sz = pop();
    char *a = (char*)pop();
    CELL n = 0;
    if (VALIDF(fh)) { n=files[fh].read(a, sz); }
    push(n);
}

// (a sz fh--n)
void fWrite() {
    CELL fh = pop();
    CELL sz = pop();
    char *a = (char*)pop();
    CELL n = 0;
    if (VALIDF(fh)) { n = files[fh].write(a, sz); }
    push(n);
}

// (fh--c n)
void fGetC() {
    CELL c = 0, n = 0, fh = pop(); 
    if (VALIDF(fh)) {
      n = files[fh].read((char*)&c, 1);
    }
    push(c);
    push(n);
}

// (c fh--)
void fPutC() {
    CELL fh = pop();
    byte c = (byte)pop();
    if (VALIDF(fh)) { files[fh].write(&c, 1); }
}

// (a sz fh--f)
void fGetS() {
    CELL fh = pop();
    CELL sz = pop();
    char *a = (char *)pop();
    *a = 0;
    if (VALIDF(fh) && (files[fh].available())) {
        files[fh].readBytesUntil('\n', a, sz);
        push(1);
        return;
    }
    push(0);
}

// (fn--)
void fDelete() {
    char* fn = (char*)pop();
    if (myFS.remove(fn)) { printString("-deleted-"); }
    else { printString("-noFile-"); }
}

// (--)
void fList() {
    File dir = myFS.open("/");
    while(true) {
         File entry = dir.openNextFile();
         if (!entry) { break; }
         printString(entry.name());
         // files have sizes, directories do not
         if (entry.isDirectory()) { printStringF(" (dir)\r\n"); }
         else { printStringF(" (%llu)\r\n", entry.size()); }
         //char *x = entry.size();
         // fprintStringF("%d", (int)x);
      entry.close();
    }
    dir.close();
}

// (--)
void fSaveSys() {
    myFS.remove("/system.c4");
    File fp = myFS.open("/system.c4", FILE_WRITE);
    if (fp) {
        fp.write(&mem[0], MEM_SZ);
        fp.close();
        printString("-saved-");
    } else { printString("-error-"); }
}

// (--)
int fLoadSys() {
    File fp = myFS.open("/system.c4", FILE_READ);
    if (fp) {
        vmReset();
        fp.read(&mem[0], MEM_SZ);
        fp.close();
        printString("-loaded-");
    } else { printString("-error-"); }
    return (fp) ? 1 : 0;
}

// (--)
void fLoad(const char *fn) {
      printString("-load-");
}


Perhaps I'll create a GitHub repo for it, get famous, and be able to quit my day job :).
 
Back
Top