【简介】
我们在上一篇基于S32K146 完成了片内flash 驱动的编写(【S32K146】S32K146片内flash编程),我们在此基础上继续适配littleFS文件系统。
LittleFS是一个应用于单片机内部flash和外挂NOR flash的文件系统。由于它相比传统的FAT文件系统更适合于小型嵌入式系统,具有以下特点:
掉电恢复能力:
设计用于处理随机电源故障。所有文件操作都有很强的写时拷贝保证,如果断电,文件系统将恢复到上一次已知的良好状态。
动态磨损均衡:
设计考虑到闪存,并提供动态块磨损均衡。此外,littlefs可以检测坏块并在它们周围工作。
有限RAM/ROM:
被设计为使用少量内存。RAM的使用是严格限制的,这意味着RAM的使用不会随着文件系统的增长而改变。文件系统不包含无界递归,动态内存仅限于可静态提供的可配置缓冲区。
官方的详细介绍参照此链接(https://github.com/littlefs-project/littlefs/)
S32K146 内部的Pflash 资源大小为1M,这个大小对普通的嵌入式开发资源是有很大的空闲的,本次试验基于内部的pflash 将后512K资源划分为文件系统分区,使用littlefs 进行管理,我们修改链接脚本把后512K资源保留出来给文件系统使用,本次试验使用的IAR环境。
【LittleFS适配】
LittleFS 的适配主要是需要实现文件系统依赖物理层的实现,LittleFS 的适配物理层需要配置的结构体如下:
// Configuration provided during initialization of the littlefs struct lfs_config { // Opaque user provided context that can be used to pass // information to the block device operations void *context; // Read a region in a block. Negative error codes are propagated // to the user. int (*read)(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size); // Program a region in a block. The block must have previously // been erased. Negative error codes are propagated to the user. // May return LFS_ERR_CORRUPT if the block should be considered bad. int (*prog)(const struct lfs_config *c, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size); // Erase a block. A block must be erased before being programmed. // The state of an erased block is undefined. Negative error codes // are propagated to the user. // May return LFS_ERR_CORRUPT if the block should be considered bad. int (*erase)(const struct lfs_config *c, lfs_block_t block); // Sync the state of the underlying block device. Negative error codes // are propagated to the user. int (*sync)(const struct lfs_config *c); #ifdef LFS_THREADSAFE // Lock the underlying block device. Negative error codes // are propagated to the user. int (*lock)(const struct lfs_config *c); // Unlock the underlying block device. Negative error codes // are propagated to the user. int (*unlock)(const struct lfs_config *c); #endif // Minimum size of a block read in bytes. All read operations will be a // multiple of this value. lfs_size_t read_size; // Minimum size of a block program in bytes. All program operations will be // a multiple of this value. lfs_size_t prog_size; // Size of an erasable block in bytes. This does not impact ram consumption // and may be larger than the physical erase size. However, non-inlined // files take up at minimum one block. Must be a multiple of the read and // program sizes. lfs_size_t block_size; // Number of erasable blocks on the device. Defaults to block_count stored // on disk when zero. lfs_size_t block_count; // Number of erase cycles before littlefs evicts metadata logs and moves // the metadata to another block. Suggested values are in the // range 100-1000, with large values having better performance at the cost // of less consistent wear distribution. // // Set to -1 to disable block-level wear-leveling. int32_t block_cycles; // Size of block caches in bytes. Each cache buffers a portion of a block in // RAM. The littlefs needs a read cache, a program cache, and one additional // cache per file. Larger caches can improve performance by storing more // data and reducing the number of disk accesses. Must be a multiple of the // read and program sizes, and a factor of the block size. lfs_size_t cache_size; // Size of the lookahead buffer in bytes. A larger lookahead buffer // increases the number of blocks found during an allocation pass. The // lookahead buffer is stored as a compact bitmap, so each byte of RAM // can track 8 blocks. lfs_size_t lookahead_size; // Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata // pairs that exceed this threshold will be compacted during lfs_fs_gc. // Defaults to ~88% block_size when zero, though the default may change // in the future. // // Note this only affects lfs_fs_gc. Normal compactions still only occur // when full. // // Set to -1 to disable metadata compaction during lfs_fs_gc. lfs_size_t compact_thresh; // Optional statically allocated read buffer. Must be cache_size. // By default lfs_malloc is used to allocate this buffer. void *read_buffer; // Optional statically allocated program buffer. Must be cache_size. // By default lfs_malloc is used to allocate this buffer. void *prog_buffer; // Optional statically allocated lookahead buffer. Must be lookahead_size. // By default lfs_malloc is used to allocate this buffer. void *lookahead_buffer; // Optional upper limit on length of file names in bytes. No downside for // larger names except the size of the info struct which is controlled by // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX or name_max stored on // disk when zero. lfs_size_t name_max; // Optional upper limit on files in bytes. No downside for larger files // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX or file_max stored // on disk when zero. lfs_size_t file_max; // Optional upper limit on custom attributes in bytes. No downside for // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to // LFS_ATTR_MAX or attr_max stored on disk when zero. lfs_size_t attr_max; // Optional upper limit on total space given to metadata pairs in bytes. On // devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB) // can help bound the metadata compaction time. Must be <= block_size. // Defaults to block_size when zero. lfs_size_t metadata_max; // Optional upper limit on inlined files in bytes. Inlined files live in // metadata and decrease storage requirements, but may be limited to // improve metadata-related performance. Must be <= cache_size, <= // attr_max, and <= block_size/8. Defaults to the largest possible // inline_max when zero. // // Set to -1 to disable inlined files. lfs_size_t inline_max; #ifdef LFS_MULTIVERSION // On-disk version to use when writing in the form of 16-bit major version // + 16-bit minor version. This limiting metadata to what is supported by // older minor versions. Note that some features will be lost. Defaults to // to the most recent minor version when zero. uint32_t disk_version; #endif };
我们将S32K146 的对应参数及flash 操作函数对接到此配置结构体中,对应的接口配置如下:
/* Definitions for BOARD_InitPeripherals functional group */ /* Maximum block read size definition */ #define LITTLEFS_READ_SIZE 16 /* Block count definition */ #define LITTLEFS_BLOCK_COUNT 128 /* Block cycles definition */ #define LITTLEFS_BLOCK_CYCLES 100 /* Minimum block cache size definition */ #define LITTLEFS_CACHE_SIZE 512 /* Minimum lookahead buffer size definition */ #define LITTLEFS_LOOKAHEAD_SIZE 16 /* Block starting address definition */ #define LITTLEFS_START_ADDR 0x00080000 /******************************************************************************************************** * Private Variable Definitions * *******************************************************************************************************/ static flash_ssd_config_t Pss_InitConfig_Lfs; /******************************************************************************************************** * Private Type Declarations * *******************************************************************************************************/ struct lfs_mflash_ctx { uint32_t start_addr; flash_ssd_config_t * Pss_InitConfig; }; /******************************************************************************************************** * Private Function Declarations * *******************************************************************************************************/ int lfs_mflash_read(const struct lfs_config *lfsc, lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size) { struct lfs_mflash_ctx *ctx; uint32_t flash_addr; ctx = (struct lfs_mflash_ctx *)lfsc->context; flash_addr = ctx->start_addr + block * lfsc->block_size + off; memcpy(buffer, (void *)flash_addr, size); return LFS_ERR_OK; } int lfs_mflash_prog( const struct lfs_config *lfsc, lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size) { struct lfs_mflash_ctx *ctx; uint32_t flash_addr; status_t ret; ctx = (struct lfs_mflash_ctx *)lfsc->context; flash_addr = ctx->start_addr + block * lfsc->block_size + off; ret = FLASH_DRV_Program(ctx->Pss_InitConfig,flash_addr,size,buffer); if (ret != STATUS_SUCCESS) return LFS_ERR_IO; return LFS_ERR_OK; } int lfs_mflash_erase(const struct lfs_config *lfsc, lfs_block_t block) { struct lfs_mflash_ctx *ctx; uint32_t flash_addr; status_t ret; ctx = (struct lfs_mflash_ctx *)lfsc->context; flash_addr = ctx->start_addr + block * lfsc->block_size; ret = FLASH_DRV_EraseSector(ctx->Pss_InitConfig,flash_addr,FEATURE_FLS_PF_BLOCK_SECTOR_SIZE); if (ret != STATUS_SUCCESS) return LFS_ERR_IO; return LFS_ERR_OK; } int lfs_mflash_sync(const struct lfs_config *lfsc) { return LFS_ERR_OK; } /* LittleFS context */ static struct lfs_mflash_ctx LittleFS_ctx; const struct lfs_config LittleFS_config = { .context = (void*)&LittleFS_ctx, .read = lfs_mflash_read, .prog = lfs_mflash_prog, .erase = lfs_mflash_erase, .sync = lfs_mflash_sync, #ifdef LFS_THREADSAFE .lock = lfs_mflash_lock, .unlock = lfs_mflash_unlock, #endif .read_size = LITTLEFS_READ_SIZE, .prog_size = FEATURE_FLS_PF_BLOCK_WRITE_UNIT_SIZE, .block_size = FEATURE_FLS_PF_BLOCK_SECTOR_SIZE, .block_count = LITTLEFS_BLOCK_COUNT, .block_cycles = LITTLEFS_BLOCK_CYCLES, .cache_size = LITTLEFS_CACHE_SIZE, .lookahead_size = LITTLEFS_LOOKAHEAD_SIZE };
调用一下接口初始化S32K146 FLASH
/******************************************************************************************************** * Global Function Declarations * *******************************************************************************************************/ static int lfs_flash_init(void) { status_t ret; /* Init lpi2c master */ ret = FLASH_DRV_Init(&Flash_InitConfig0,&Pss_InitConfig_Lfs); if(ret != STATUS_SUCCESS) { LOG_E("lfs FTFC init failed %x.",ret); } else { LOG_I("lfs FTFC init OK."); } LittleFS_ctx.Pss_InitConfig = &Pss_InitConfig_Lfs; LittleFS_ctx.start_addr = LITTLEFS_START_ADDR; return 1; } INIT_BOARD_EXPORT_LEVEL1(lfs_flash_init);
【添加测试命令】
适配完LittleFS 我们添加如下的测试命令来验证LittleFS的功能。
添加format 命令来格式化内部flash
static unsigned int format(char argc, char **argv) { int res; if (lfs_mounted) { printf("LFS is mounted, please unmount it first.\r\n"); return 1; } if (argc != 2 || strcmp(argv[1], "yes")) { printf("Are you sure? Please issue command \"format yes\" to proceed.\r\n"); return 1; } res = lfs_format(&lfs, &LittleFS_config); if (res) { printf("\rError formatting LFS: %d\r\n", res); } return 0; }
添加mount /unmout 来配置flash 的挂载路径
static unsigned int mount(char argc, char **argv) { int res; if (lfs_mounted) { printf("LFS already mounted\r\n"); return 0; } res = lfs_mount(&lfs, &LittleFS_config); if (res) { printf("\rError mounting LFS\r\n"); } else { lfs_mounted = 1; } return 0; } static unsigned int unmount(char argc, char **argv) { int res; if (!lfs_mounted) { printf("LFS not mounted\r\n"); return 0; } res = lfs_unmount(&lfs); if (res) { printf("\rError unmounting LFS: %i\r\n", res); } lfs_mounted = 0; return 0; }
添加 ls 来查看目录下的文件信息
static unsigned int ls(char argc, char **argv) { int res; char *path; lfs_dir_t dir; struct lfs_info info; int files; int dirs; if (!lfs_mounted) { printf("LFS not mounted\r\n"); return 0; } if (argc > 2) { printf("Invalid number of parameters\r\n"); return 1; } if (argc < 2) { path = "/"; } else { path = argv[1]; } /* open the directory */ res = lfs_dir_open(&lfs, &dir, path); if (res) { printf("\rError opening directory: %i\r\n", res); return 0; } printf(" Directory of %s\r\n", path); files = 0; dirs = 0; /* iterate until end of directory */ while ((res = lfs_dir_read(&lfs, &dir, &info)) != 0) { if (res < 0) { /* break the loop in case of an error */ printf("\rError reading directory: %i\r\n", res); break; } if (info.type == LFS_TYPE_REG) { printf("%8d %s\r\n", info.size, info.name); files++; } else if (info.type == LFS_TYPE_DIR) { printf(" DIR %s\r\n", info.name); dirs++; } else { printf("%???\r\n"); } } res = lfs_dir_close(&lfs, &dir); if (res) { printf("\rError closing directory: %i\r\n", res); return 0; } printf(" %d File(s), %d Dir(s)\r\n", files, dirs); return 0; }
添加rm 来删除文件文件夹
static unsigned int rm(char argc, char **argv) { int res; if (!lfs_mounted) { printf("LFS not mounted\r\n"); return 0; } res = lfs_remove(&lfs, argv[1]); if (res) { printf("\rError while removing: %i\r\n", res); } return 0; }
mkdir 来创建文件夹
static unsigned int mkdir(char argc, char **argv) { int res; if (!lfs_mounted) { printf("LFS not mounted\r\n"); return 0; } res = lfs_mkdir(&lfs, argv[1]); if (res) { printf("\rError creating directory: %i\r\n", res); } return 0; }
write/cat 来读写文件
static unsigned int write(char argc, char **argv) { int res; lfs_file_t file; if (!lfs_mounted) { printf("LFS not mounted\r\n"); return 0; } res = lfs_file_open(&lfs, &file, argv[1], LFS_O_WRONLY | LFS_O_APPEND | LFS_O_CREAT); if (res) { printf("\rError opening file: %i\r\n", res); return 0; } res = lfs_file_write(&lfs, &file, argv[2], strlen(argv[2])); if (res > 0) res = lfs_file_write(&lfs, &file, "\r\n", 2); if (res < 0) { printf("\rError writing file: %i\r\n", res); } res = lfs_file_close(&lfs, &file); if (res) { printf("\rError closing file: %i\r\n", res); } return 0; } static unsigned int cat(char argc, char **argv) { int res; lfs_file_t file; uint8_t buf[16]; if (!lfs_mounted) { printf("LFS not mounted\r\n"); return 0; } res = lfs_file_open(&lfs, &file, argv[1], LFS_O_RDONLY); if (res) { printf("\rError opening file: %i\r\n", res); return 0; } do { res = lfs_file_read(&lfs, &file, buf, sizeof(buf)); if (res < 0) { printf("\rError reading file: %i\r\n", res); break; } if(res > 0) printf("%s",(char *)buf); } while (res); res = lfs_file_close(&lfs, &file); if (res) { printf("\rError closing file: %i\r\n", res); } return 0; }
【功能验证】
通过上述的测试命令格式化flash 后 挂载到文件系统后通过mkdir 创建文夹后ls 查看当前目录文件,结果如下,创建的文件夹可以通过ls 获取
通过 write 创建文件并写入数据通过cat 读取验证,写入数据和读取的一致,文件按照预期的写入和读取了