基于STM32F103的Littlefs文件系统移植

移植平台: 正点原子STM32F1精英版V1.41
MCU:STM32F103ZET6
SPI Falsh:W25Q128

LittleFS是ARM mbedOS的官方推荐文件系统,具有轻量级,掉电安全的特性。

参考文档

文件系统移植

首先去Github上下载最新发型版本的LittleFs源码,我下载的是V2.2.1版本,并阅读README文档了解移植详情,因为README.md文档讲解很有限,所以下面会在移植过程中做出补充。在工程中添加文件系统如下:

基于STM32F103的Littlefs文件系统移植

以上两个文件在文件系统源码根目录下;

移植要点

  • 对 struct lfs_config的赋值;
  • LittleFS读写内存的分配函数指定;
  • 使用lfs_mount挂在文件系统;

lfs_config结构体

lfs_config为移植的重中之重,定义在lfs.h中:

// Configuration provided during initialization of the littlefs
struct lfs_config {
    // 用于传递信息给块设备,便于块设备驱动进行特定的处理,比如:告诉块设备驱动
	// 具体哪个范围用于文件系统(传参);
    // 这个内容的数据结构由块设备驱动来定义;
    void *context;

    // 从指定块内读数据
    // 
    int (*read)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size);

    // 写块接口,用于将一段数据写入到块中,这个块必须是已经被擦除的
	//(文件系统会确保擦除)
    int (*prog)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size);

   
    //擦除一个块
    int (*erase)(const struct lfs_config *c, lfs_block_t block);

    // Sync the state of the underlying block device. Negative error codes
    // are propogated to the user.(可以直接写空)
    int (*sync)(const struct lfs_config *c);

    // 最小读取字节数,所有的读取操作字节数必须是它的倍数(影响内存消耗)
    lfs_size_t read_size;

    // 最小写入字节数,所有的读取操作字节数必须是它的倍数(影响内存消耗)
    lfs_size_t prog_size;

	//擦除块字节数 不会影响内存消耗;这个数值可以比物理擦除地址大,但是这个数值应该尽可能小,
	//因为每个文件至少占用一个块;值必须是读取和编程粒度的整数倍;
    lfs_size_t block_size;

    // 可以被擦除的块数量,取决于设备容量
    lfs_size_t block_count;

	//littlefs逐出元数据日志并将元数据移动到另一个块之前的擦除周期数。 建议值在
	//100-1000范围内,较大的值具有较好的性能,但是会导致磨损分布不均匀。
	// -1 禁用块级磨损均衡
    int32_t block_cycles;

    // Size of block caches. 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.
	
	//块缓存的大小。 每个缓存都会在RAM中缓冲一部分块。littlefs需要一个读缓存,
	//一个程序缓存以及每个文件一个额外的缓存。 更大的缓存可以通过存储更多数据
	//来减少磁盘访问次数来提高性能。
	//该值必须是读取和编程大小的倍数,并且是块大小的因数。
    lfs_size_t cache_size;
	
	// 块分配时的预测深度(分配块时每次步进多少个块),这个数值必须为8的整数倍,
	//如1024表示每次预测1024个block。这个值对于内存消耗影响不大
    lfs_size_t lookahead_size;

    // 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
    // and aligned to a 32-bit boundary. 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 when zero. Stored in
    // superblock and must be respected by other littlefs drivers.
    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 when zero. Stored
    // in superblock and must be respected by other littlefs drivers.
    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 when zero.
    lfs_size_t attr_max;
};

read接口解析

/*
 * @brief 从指定块内读数据
 * @param [in] lfs_config格式参数;
 * @param [in] block 逻辑块编号,从0开始
 * @param [in] off 块内偏移,lfs在调用read接口时,传入的off值一定能被read_size整除
 * @param [out] 读出数据的输出缓冲区
 * @param [in] size 要读取的字节数,lfs在读取时会确保不会跨块;
 * @retval 0 成功 <0 错误码
 */
int (*read)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, void *buffer, lfs_size_t size);

write接口解析

/*
 * @brief 从指定块内读数据
 * @param [in] lfs_config格式参数;
 * @param [in] block 逻辑块编号,从0开始
 * @param [in] off 块内偏移,lfs在调用prog接口时,传入的off值一定能被rprog_size整除
 * @param [in] 写入数据的缓冲区
 * @param [in] size 要读取的字节数,lfs在读取时会确保不会跨块;
 * @retval 0 成功 <0 错误码
 */
int (*prog)(const struct lfs_config *c, lfs_block_t block,
            lfs_off_t off, const void *buffer, lfs_size_t size);

erase接口解析

/*
 * @brief 从指定块内读数据
 * @param [in] lfs_config格式参数;
 * @param [in] block 要擦除的逻辑块编号,从0开始
 * @retval 0 成功 <0 错误码
 */
int (*erase)(const struct lfs_config *c, lfs_block_t block);

上述接口需要根据想要移植的特定设备进行编写;

lfs的动态内存

lfs同时支持静态和动态内存两种方式,使用LFS_NO_MALLOC来进行开启关闭,当未定义LFS_NO_MALLOC时,用户需要提供自己的内存申请以及释放函数,接口在lfs_util.h中定义:

// Allocate memory, only used if buffers are not provided to littlefs
// Note, memory must be 64-bit aligned
static inline void *lfs_malloc(size_t size) {
#ifndef LFS_NO_MALLOC
    return mymalloc(SRAMIN,size);  //用户申请内存函数
#else
    (void)size;
    return NULL;
#endif
}

// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs_free(void *p) {
#ifndef LFS_NO_MALLOC
    myfree(SRAMIN,p);   //用户释放内存函数
#else
    (void)p;
#endif
}

如果用户不支持动态内存,则需要定义LFS_NO_MALLOC,并且需要在lfs_config结构体中给read_buffer/prog_buffer/lookahead_buffer/file_buffer设置静态内存,并且同一时刻只能打开一个文件(实例);
若要支持同时打开多个文件,则动态内存(HEAP)是必须的。

测试实例

我基于正点原子STM32F1精英版V1.41开发板进行了littleFS的移植,并且同时支持了STM32内部Flash和外部的W25Q128,项目地址为 :

基于正点原子STM32F1精英版的LittleFS移植

项目测试例如下所示:


/* lfs for STM32Flash ********************************************************/
lfs_t lfs_Stm32Flash; 
lfs_file_t file_Stm32Flash;

// configuration of the filesystem is provided by this struct
const struct lfs_config cfg_Stm32Flash = {
    // block device operations
    .read  = stm32flash_readLittlefs,
    .prog  = stm32flash_writeLittlefs,
    .erase = stm32flash_eraseLittlefs,
    .sync  = stm32flash_syncLittlefs,

    // block device configuration
    .read_size = 128,
    .prog_size = 128,
    .block_size = STM32Flash_ERASE_GRAN,
    .block_count = STM32Flash_NUM_GRAN/2,
    .cache_size = 512,
    .lookahead_size = 512,
    .block_cycles = 500,
};



extern void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite);  


int main(void)
{
	int err = -1;
	
	
	
	delay_init();	    	 //ÑÓʱº¯Êý³õʼ»¯	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//ÉèÖÃÖжÏÓÅÏȼ¶·Ö×éΪ×é2£º2λÇÀÕ¼ÓÅÏȼ¶£¬2λÏìÓ¦ÓÅÏȼ¶
	uart_init(115200);	 	//´®¿Ú³õʼ»¯Îª115200
	LED_Init();		  		//³õʼ»¯ÓëLEDÁ¬½ÓµÄÓ²¼þ½Ó¿Ú
	KEY_Init();				//°´¼ü³õʼ»¯		 	 	
	W25QXX_Init();			//W25QXX³õʼ»¯
    
	
	
	

	while(W25QXX_ReadID()!=W25Q128)								//¼ì²â²»µ½W25Q128
	{
		printf("W25Q128 Check Failed!\r\n");
		delay_ms(500);
		printf("Please Check!        \r\n");
		delay_ms(500);
		LED0=!LED0;//DS0ÉÁ˸
	}
	printf("W25Q128 Ready!\r\n");  
	
	
	
	

//    err =  lfs_mount(&lfs, &cfg);
//	
//	if(err )
//	{
//		lfs_format(&lfs, &cfg);
//        lfs_mount(&lfs, &cfg);
//	}
//	
	
	err = lfs_mount(&lfs_Stm32Flash, &cfg_Stm32Flash);
	
	if( err )
	{
		lfs_format(&lfs_Stm32Flash, &cfg_Stm32Flash);
		lfs_mount(&lfs_Stm32Flash, &cfg_Stm32Flash);
	}
	
	
	
	
	
	while(1)
	{
		LED0=!LED0;

		
		uint32_t boot_count = 0;
		lfs_file_open(&lfs_Stm32Flash, &file_Stm32Flash, "boot_count", LFS_O_RDWR | LFS_O_CREAT);
		lfs_file_read(&lfs_Stm32Flash, &file_Stm32Flash, &boot_count, sizeof(boot_count));

		
		// update boot count
		boot_count += 1;
		lfs_file_rewind(&lfs_Stm32Flash, &file_Stm32Flash);  // seek the file to begin
		lfs_file_write(&lfs_Stm32Flash, &file_Stm32Flash, &boot_count, sizeof(boot_count));

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

//		// release any resources we were using
//		lfs_unmount(&lfs);

		// print the boot count
		printf("boot_count: %d\n", boot_count);

		delay_ms(20000);
	}
}

下载运行后串口打印效果如下:

基于STM32F103的Littlefs文件系统移植

工程移植到此完成,接口的优化后面有时间再写。

基于STM32F103的Littlefs文件系统移植 - 蜗牛韩 - 博客园 (cnblogs.com)

上一篇:git 大文件上传解决方案(LFS)


下一篇:git lfs的使用