Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

W25Q64-lfs

LittleFS port layer for the W25Q64 SPI NOR flash driver. Provides a complete lfs_config implementation using static buffers — no heap allocation required.

Features

  • Single-call setup: W25Q_LFS_BuildConfig fills a ready-to-use lfs_config
  • No dynamic memory — all three LittleFS working buffers live inside W25Q_LFS_Context
  • Inherits the transport-agnostic design of W25Q64-flash: works with any SPI peripheral via user-supplied callbacks
  • Full 8 MB capacity: 2048 × 4 KB blocks, 256-byte program granularity
  • Wear levelling enabled (block_cycles = 500, matching the W25Q64's 100 000-cycle rating)
  • Power-loss safe writes: LittleFS never leaves the filesystem in an inconsistent state after a sudden power failure
  • Tunable cache and lookahead sizes via compile-time macros

Project Layout

  • include/w25q64_lfs.h — public API and W25Q_LFS_Context definition
  • src/w25q64_lfs.c — block device callback implementations and W25Q_LFS_BuildConfig
  • examples/mount_format.c — setup, mount, and auto-format on first boot
  • examples/file_rw.c — write, read, verify, and list directory contents
  • examples/persistent_log.c — append-only sensor log with rotation across power cycles

Dependencies

Managed automatically by PlatformIO:

Quick Start

Add to your platformio.ini:

lib_deps = anurag3301/W25Q64-lfs@^0.1.4

Minimal flow

#include "w25q64_lfs.h"

/* -- Hardware ------------------------------------------------------------ */
#define FLASH_CS_PORT  GPIOA
#define FLASH_CS_PIN   GPIO_PIN_4

extern SPI_HandleTypeDef hspi1;
/* ------------------------------------------------------------------------ */

static int32_t flash_spi_transfer(void *user_context,
                                  const uint8_t *tx, uint8_t *rx, size_t length)
{
    SPI_HandleTypeDef *spi = (SPI_HandleTypeDef *)user_context;
    HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET);
    HAL_StatusTypeDef r = HAL_SPI_TransmitReceive(spi, (uint8_t *)tx, rx,
                                                   (uint16_t)length, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET);
    return (r == HAL_OK) ? W25Q_OK : W25Q_ERR_IO;
}

static void flash_delay_ms(void *user_context, uint32_t delay_ms)
{
    (void)user_context;
    HAL_Delay(delay_ms);
}

/* Must remain valid for the lifetime of the mounted filesystem */
static W25Q_LFS_Context lfs_ctx;

int main(void)
{
    /* HAL init, clock, GPIO, SPI go here */

    W25Q_Config flash = {
        .transfer_fn  = flash_spi_transfer,
        .delay_ms_fn  = flash_delay_ms,
        .user_context = &hspi1
    };

    struct lfs_config lfs_cfg;
    W25Q_LFS_BuildConfig(&flash, &lfs_ctx, &lfs_cfg);

    lfs_t lfs;
    if (lfs_mount(&lfs, &lfs_cfg) != LFS_ERR_OK) {
        lfs_format(&lfs, &lfs_cfg);
        lfs_mount(&lfs, &lfs_cfg);
    }

    /* Use the LittleFS API normally */
    lfs_file_t file;
    lfs_file_open(&lfs, &file, "/hello.txt", LFS_O_WRONLY | LFS_O_CREAT);
    lfs_file_write(&lfs, &file, "hello", 5);
    lfs_file_close(&lfs, &file);

    lfs_unmount(&lfs);
    while (1) {}
}

API

W25Q_LFS_Context

typedef struct {
    const W25Q_Config *flash;
    uint8_t read_buf    [W25Q_LFS_CACHE_SIZE];
    uint8_t prog_buf    [W25Q_LFS_CACHE_SIZE];
    uint8_t lookahead_buf[W25Q_LFS_LOOKAHEAD_SIZE];
} W25Q_LFS_Context;

Holds the W25Q_Config pointer and the three RAM buffers LittleFS requires. Allocate one instance per mounted filesystem. It must remain valid (not go out of scope) for the entire time the filesystem is mounted — declare it static or at global scope.

W25Q_LFS_BuildConfig

int32_t W25Q_LFS_BuildConfig(const W25Q_Config *flash,
                              W25Q_LFS_Context  *ctx,
                              struct lfs_config *out_cfg);

Fills out_cfg so it is ready for lfs_mount() or lfs_format(). Wires up the four block device callbacks, sets flash geometry constants, and points LittleFS at the buffers inside ctx.

Parameter Description
flash Initialised W25Q_Config with SPI callbacks set up
ctx Caller-allocated W25Q_LFS_Context; must remain valid while mounted
out_cfg Output: filled lfs_config ready for lfs_mount / lfs_format

Returns W25Q_OK on success, W25Q_ERR_INVALID_ARG if any pointer is NULL.

Tunable constants

Override before including w25q64_lfs.h or via compiler flags:

Macro Default Description
W25Q_LFS_CACHE_SIZE 256 Read and prog cache in bytes. Must be a multiple of 256 (one W25Q64 page). Larger values reduce flash reads but cost RAM.
W25Q_LFS_LOOKAHEAD_SIZE 64 Lookahead bitmap in bytes. Each byte tracks 8 blocks; 64 bytes covers 512 blocks (2 MB). Must be a multiple of 8.

Example — double the cache to two pages:

#define W25Q_LFS_CACHE_SIZE 512
#include "w25q64_lfs.h"

Filesystem geometry

These values are fixed to match the W25Q64's physical layout and are set inside W25Q_LFS_BuildConfig:

lfs_config field Value Source
read_size 1 NOR flash supports byte-granular reads
prog_size 256 W25Q64 page size
block_size 4096 W25Q64 sector size (minimum erase unit)
block_count 2048 8 MB ÷ 4 KB
block_cycles 500 Wear-levelling aggressiveness (100 000-cycle chip ÷ ~200 headroom)

Notes

W25Q_LFS_Context lifetime. LittleFS holds raw pointers to the buffers inside W25Q_LFS_Context. If the context is a local variable and goes out of scope while the filesystem is still mounted, the result is undefined behaviour. Declare it static or at file scope.

Mount / format once. On a freshly erased or never-formatted chip lfs_mount returns a negative error code. Call lfs_format once, then lfs_mount again. After a successful format the chip always mounts cleanly on subsequent boots without reformatting.

Always unmount before power-down. lfs_unmount flushes pending writes. Skipping it is safe in the sense that LittleFS will recover on the next mount, but the last in-progress write will be rolled back.

W25Q_Write does not erase. The block device callbacks erase at the sector level before every write. You do not need to call W25Q_EraseSector manually when using LittleFS — the erase callback handles it.

Wear levelling. block_cycles = 500 tells LittleFS to move data between blocks to spread erase cycles evenly. The W25Q64 is rated for 100 000 erase cycles per sector; with wear levelling enabled all 2048 sectors age together, giving an effective endurance of roughly 100 000 × 2048 write operations before any single sector wears out.

About

LittleFS block device adapter for the W25Q64 SPI NOR flash driver.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages