File System in QSPI Flash?

Hello all, I’m wondering if anybody has attempted to use littlefs or even FatFs for storing and managing user created files on the QSPI Flash?

I’m working on a system where users will be able to create and save CV recordings, and then later browse through a list of them and load from memory and I’m trying to figure out the best way to do it on the external flash in a way that I’ll regret the least.

Does anybody have any suggestions / hard won knowledge that would be helpful to know ahead of time?

I ended up going with littlefs, and after a bit of struggling managed to get a 6mb filesystem going on the qspi flash. It boots up and formats way faster than I expected, but I guess my flash was mostly blank.





// ##:::::::'####:'########:'########:'##:::::::'########:'########::'######::
// ##:::::::. ##::... ##..::... ##..:: ##::::::: ##.....:: ##.....::'##... ##:
// ##:::::::: ##::::: ##::::::: ##:::: ##::::::: ##::::::: ##::::::: ##:::..::
// ##:::::::: ##::::: ##::::::: ##:::: ##::::::: ######::: ######:::. ######::
// ##:::::::: ##::::: ##::::::: ##:::: ##::::::: ##...:::: ##...:::::..... ##:
// ##:::::::: ##::::: ##::::::: ##:::: ##::::::: ##::::::: ##:::::::'##::: ##:
// ########:'####:::: ##::::::: ##:::: ########: ########: ##:::::::. ######::
// ........::....:::::..::::::::..:::::........::........::..:::::::::......::

#include "main.h"
#include "littlefs/lfs.h"

// variables used by the filesystem
lfs_t lfs;
lfs_file_t file;


//***  LITTLE FS PROTOTYPES  *******************************

int lfs_read(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size);

int lfs_prog(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size);

int lfs_erase(const struct lfs_config *c, lfs_block_t block);

int lfs_sync(const struct lfs_config *c);

//***  DAISY SEED LITTLEFS SETTINGS  *******************************

#define LFS_BLOCK_SIZE      4096       // One erase sector
#define LFS_BLOCK_COUNT     (6 * 1024 * 1024 / LFS_BLOCK_SIZE) // 6MB total, leaving 2mb untouched
#define LFS_READ_SIZE       256        // Smallest flash read unit  (could be smaller on Daisy)
#define LFS_PROG_SIZE       256        // Smallest flash write unit 
#define LFS_CACHE_SIZE      256        // Cache one full read/write
#define LFS_LOOKAHEAD_SIZE  256        // Block allocation table
#define LFS_BLOCK_CYCLES    1000       // Default wear-leveling


const struct lfs_config cfg = {
    .context        = NULL,            // Optionally store some sort of context handle here
    .read           = lfs_read,
    .prog           = lfs_prog,
    .erase          = lfs_erase,
    .sync           = lfs_sync,

    .read_size      = LFS_READ_SIZE,
    .prog_size      = LFS_PROG_SIZE,
    .block_size     = LFS_BLOCK_SIZE,
    .block_count    = LFS_BLOCK_COUNT,
    .block_cycles   = LFS_BLOCK_CYCLES,
    .cache_size     = LFS_CACHE_SIZE,
    .lookahead_size = LFS_LOOKAHEAD_SIZE,
    
};




#define QSPI_BASE_ADDR  0x90000000      // Flash is memory mapped here 
#define LFS_BASE_ADDR   0x200000        // leave first two megabytes untouched, no other offset
                                        // needed for erase and write 

#define MEM_MAP_LFS_BASE_ADDR  LFS_BASE_ADDR + QSPI_BASE_ADDR   // memory mapped location, needed for read


//******************************************
//********  READ  **************************
//******************************************

int lfs_read(const struct lfs_config *c,
             lfs_block_t block,
             lfs_off_t off,
             void *buffer,
             lfs_size_t size) 
{
    // Sanity checks 
    if ((off + size) > c->block_size) 
    {
        return -EINVAL; // Trying to read past block boundary
    }

    // Compute address for memory-mapped QSPI flash
    uint8_t *flash_ptr = (uint8_t *)(MEM_MAP_LFS_BASE_ADDR + (block * c->block_size) + off);

    dsy_dma_invalidate_cache_for_buffer(flash_ptr, size);

    memcpy(buffer, flash_ptr, size);
    return 0;   
}

//******************************************
//********  WRITE  *************************
//******************************************

int lfs_prog(const struct lfs_config *c,
             lfs_block_t block,
             lfs_off_t off,
             const void *buffer,
             lfs_size_t size) 
{
    uint32_t addr = LFS_BASE_ADDR + block * c->block_size + off;  //LFS_BASE_ADDR and goes up (no mem map)

    // Daisy Seed flash page write function (4k)
    if (hw.qspi.WritePage(addr, size, (uint8_t *)buffer) != QSPIHandle::Result::OK) 
    {
        return -EIO;
    }

    return 0;
}

//******************************************
//********  ERASE  *************************
//******************************************


int lfs_erase(const struct lfs_config *c, lfs_block_t block) 
{
    uint32_t addr = LFS_BASE_ADDR + block * c->block_size;      //LFS_BASE_ADDR and up (no mem map)

    if (hw.qspi.EraseSector(addr) != QSPIHandle::Result::OK) 
    {
        return -EIO;
    }

    return 0;
}
//******************************************
//********  SYNC  **************************
//******************************************

int lfs_sync(const struct lfs_config *c) 
{
    //  Wait for last write/erase to complete
    //  Not needed on Daisy Seed
    return 0;
}


//******************************************
//******************************************
//**********  TEST  ************************
//******************************************
//******************************************




void init_littlefs()
{
    hw.PrintLine("Mounting Filesystem");


    // mount the filesystem
    int err = lfs_mount(&lfs, &cfg);

    // reformat if we can't mount the filesystem
    // this should only happen on the first boot
    if (err) 
    {
        hw.PrintLine("Formatting Flash");
        err = lfs_format(&lfs, &cfg);
        err = lfs_mount(&lfs, &cfg);
        hw.PrintLine("Format Complete");
    }

    // read current count
    uint32_t boot_count = 0;
    err = lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
    err = lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count));

    // update boot count
    boot_count += 1;
    lfs_file_rewind(&lfs, &file);
    lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count));

    // remember the storage is not updated until the file is closed successfully
    lfs_file_close(&lfs, &file);

    // release any resources we were using
    hw.PrintLine("Unmounting");
    lfs_unmount(&lfs);

    // print the boot count
    hw.PrintLine("boot_count: %d\n", boot_count);
}

1 Like

If anybody who is more experienced with littlefs than me has any comments on the implementation or the selection of my settings choices, let me know.

But hopefully this will save someone else some time.