logo
CODEMBIT
Reliable software engineering partner
Home Blog Tools

How to use littlefs with CMSIS flash driver

By Toni Akkala, 26th of August, 2022

Overview

Littlefs is a small fail-safe filesystem designed for microcontrollers and licensed with BDS-3-Clause. It is designed to handle random power failures and dynamic wear leveling. It has bounded memory which means it doesn't increase when the filesystem gets larger. This blog will show you how to integrate littlefs to a CMSIS flash driver when using a Renesas EK-RA6M4 board.

Our previous post implemented the CMSIS flash driver for the Renesas EK-RA6M4 board Quad-SPI flash memory. Adding filesystem layer on top of this driver should be quite trivial. FSP configurator actually supports littlefs directly but only when used with Renesas internal data flash memory with r_flash_hp driver. As we want to use our own CMSIS driver with external flash memory we have to do the littlefs integration by ourselves.

littlefs on FSP
Figure: littlefs in FSP

Implementation

At first, I downloaded the sources for the littlefs from the GitHub repository and added those to my e2 studio project. I added that new folder to the include search path and copied the example code to update boot count from README.md to hal_entry function.

The following code includes my CMSIS flash memory driver, header for the filesystem and a new wrapper header created for all things to be implemented by the filesystem user. Memory needed by the filesystem and one file are allocated with the lfs and file variables. lfs_config structure specifies callback function pointers and a memory configuration, here we have a forward declaration for the structure which is implemented in my wrapper. After driver initialization, this code follows the example from the README.md:

#include "cmsis_flash_mx25l256.h" #include "littlefs_cmsis_wrapper.h" #include "lfs.h" // variables used by the filesystem lfs_t lfs; lfs_file_t file; extern const struct lfs_config cfg; ... // Initialize external flash memory flash_driver.Initialize(NULL); // 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) { lfs_format(&lfs, &cfg); lfs_mount(&lfs, &cfg); } // read current count uint32_t boot_count = 0; lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); 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 lfs_unmount(&lfs);

The main point of the integration is the implementation of the lfs_config structure. Here is my implementation for the filesystem configuration and function callbacks. Block device configuration definitions are from CMSIS flash memory driver header. Each block device operation will call corresponding CMSIS flash memory driver function and map the error from the driver error code to the littlefs error code. Nothing fancy.

#include "littlefs_cmsis_wrapper.h" #include "cmsis_flash_mx25l256.h" // configuration of the filesystem is provided by this struct const struct lfs_config cfg = { // block device operations .read = block_read, .prog = block_program, .erase = block_erase, .sync = block_sync, // block device configuration .read_size = 1, .prog_size = FLASH_PAGE_SIZE, .block_size = FLASH_SECTOR_SIZE, .block_count = FLASH_SECTOR_COUNT, .cache_size = FLASH_PAGE_SIZE, .lookahead_size = 16, .block_cycles = 500, }; int block_read(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { if (flash_driver.ReadData((c->block_size * block) + off, buffer, size) == (int32_t)size) { return LFS_ERR_OK; } return LFS_ERR_IO; } int block_program(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { if (flash_driver.ProgramData((c->block_size * block) + off, buffer, size) == ARM_DRIVER_OK) { return LFS_ERR_OK; } return LFS_ERR_IO; } int block_erase(const struct lfs_config *c, lfs_block_t block) { if (flash_driver.EraseSector(c->block_size * block) == ARM_DRIVER_OK) { return LFS_ERR_OK; } return LFS_ERR_IO; } int block_sync(const struct lfs_config *c) { (void)c; return LFS_ERR_OK; }

Testing

Becuase I added my test code to the hal_entry function which is run only during boot, I needed to restart the debug session couple of times to make sure that the boot_count variable was loaded correctly from the filesystem. After this I can verify that the file was stored correctly to the filesystem.

littlefs in action
Figure: littlefs in action

Conclusion

After having the CMSIS flash memory driver implemented, it was trivial to add filesystem layer on top of that. The only thing you need to do is to create the filesystem configuration structure and implement those functions callbacks specified in lfs.h header which contains also most of the documentation for the filesystem. And if you really want to know what is going on, you should read littlefs DESIGN.md and SPEC.md files.

References