在STM32407上移植FatFs文件系统

前段时间在开发板上STM32F407上成功移植了FatFs文件系统,折腾了大概一周的时间,于是想花点时间把过程记录下来,一方面让也许能让别人少走点弯路,另一方面就是时间长了忘了以后也可以翻出来看看。
废话不多说,直接开干。首先获得FatFs文件系统的源码,网址如下:
http://elm-chan.org/fsw/ff/00index_e.html
最新的FatFs文件系统版本为R0.13c。我用的也是这个。

下载下来以后:压缩包中有如下的三个文件,Source是我们需要的源码,其他的两个文件或者文件夹用不到。在STM32407上移植FatFs文件系统_第1张图片
然后source文件夹说明一下,里面的文件内容如下图所示。其中ff.h和ff.c是文件系统的实现,这个不需要我们去动,ffconf.h是需要我们配置的,文件系统裁剪都是通过修改其中的宏定义来实现。diskio.h和diskio.c是底层驱动文件,diskio.h也不需要改动。diskio.c则需要我们去编写。然后ffunicode.c相当于以前老版本的integer.h文件和option文件夹的合体,包含对各种语言的支持,这个不需要我们去关心,最后ffsystem.c其中有一些函数的实现,在某些情况下才用得到,老版本中这些函数包含在diskio.c文件下了,这个版本只不过相当于分离出来了,有些函数也需要我们去实现。
在STM32407上移植FatFs文件系统_第2张图片

具体步骤如下:
1.新建工程
在keil环境下,新建一个MDK工程,把Source文件夹加入到工程中。这里建工程就不细说了,大家都会。

2.下面开始修改第一个文件ffconf.h。这个文件中的每一个宏的作用和名字请参考以下网址
http://elm-chan.org/fsw/ff/doc/config.html
网页是英文的,看不太明白就打开google翻译,慢慢看,不急。这个module是日本人写的,说实话,我还得真感谢作者,没有出日文文档。
下面我贴出我自己修改的宏:
#define FF_FS_READONLY 0 只读,这个最好定义为0,不然很多函数操作用不了
#define FF_FS_MINIMIZE 0 相应的档位对应一些函数被移除,这里只要不是对RAM和Flash吃紧,就设置为0吧。
#define FF_USE_STRFUNC 1 这里设置对字符串的操作
#define FF_USE_FIND 0 这个设置是否使用查找功能
#define FF_USE_MKFS 0 这里设置是否使用格式化功能,如果是Flash或者没有被格式化的U盘或者SD卡,这里最好设置为1,如果你的SD卡或者U盘已经被格式化为Fat32了,这里可以设置为0。这里必须是Fat32格式啊,如果是NTFS格式,那就没法用。
#define FF_USE_FASTSEEK 1 这里设置是否使用快速查找功能
#define FF_USE_EXPAND 1 这里设置是否使用拓展功能
#define FF_USE_CHMOD 0
#define FF_USE_LABEL 1
#define FF_USE_FORWARD 0 这里的三个宏都是相应的功能是否启用

#define FF_CODE_PAGE 936 这里设置语言支持,936是中文支持,437是英文支持等
#define FF_USE_LFN 0
#define FF_MAX_LFN 255 这里配置是否使用长文件名,如果不配置,文件名长度最大为12个字符。配置以后,最大可以支持255个字符的文件名。不过配置不同的选项,其中使用的变量会储存在不同的储存区,比如1保存在BSS段,2保存在栈中,3保存在堆中。这里选择1和3都行,千万不要选择2,因为STM32的启动文件中,对栈的大小限制在512字节,很容易溢出。然后设置为3,就需要实现ffsystem.c文件中的内存分配malloc和内存释放free两个函数。

#define FF_LFN_UNICODE 0
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
#define FF_STRF_ENCODE 3
#define FF_FS_RPATH 0 这些就默认配置就好了

#define FF_VOLUMES 1 然后这个配置磁盘的数量,比如你只用到SD卡,就设置为1,如果用到U盘和SD卡,就设置为2,反正有几个就设置几个,但是有上限,最多10个。

#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS “RAM”,“NAND”,“CF”,“SD”,“SD2”,“USB”,“USB2”,“USB3”
这里也默认吧,上面就是挂载的时候,默认为0磁盘开始,第二个就叫1磁盘,然后下面的字符串就是对磁盘的描述,我也没用到,就保持默认。

#define FF_MULTI_PARTITION 0
#define FF_MIN_SS 512
#define FF_MAX_SS 512
#define FF_USE_TRIM 0
#define FF_FS_NOFSINFO 0 这里也保持默认吧,其中FF_MIN_SS和FF_MAX_SS用来设置扇区的大小,我本次移植只用到SD卡,它的扇区大小就是512字节。

#define FF_FS_TINY 0
#define FF_FS_EXFAT 0 这里设置是否支持tiny,tiny会裁剪掉很多功能。还有exFat支持,这个不是大容量U盘的话,就设置为0好了。

#define FF_FS_NORTC 0
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2018 这里是日期设置,如果diskio.c文件中没有RTC时钟的获取,文件应该就会采用这里设置的时间(我猜的…)

#define FF_FS_LOCK 0
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
这四个就保持默认吧,我也没有仔细研究。

3.然后就是diskio.c文件的编写。里面有几个函数需要实现。

/* Definitions of physical drive number for each drive */
#define DEV_SDCard		0	/* Example: Map SD card to physical drive 0 */
// #define DEV_USB		1	/* Example: Map USB MSD to physical drive 1 */

这里和ffconf.h文件中定义的FF_VOLUMES相对应,我只用到一个SD卡,后面的就注释掉了,然后编号只能从0开始。

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	switch (pdrv) 
	{
		case DEV_SDCard:
		{
			// result = SD_GetState();
			// if(result == SD_CARD_READY||result == SD_CARD_TRANSFER)return RES_OK;
			// if(result == SD_CARD_STANDBY||result == SD_CARD_TRANSFER||result ==SD_CARD_PROGRAMMING)return RES_NOTRDY;
			// else return RES_PARERR;
		}return RES_OK;
	}
	return STA_NOINIT;
}

这个函数访问设备的状态,我这里直接返回OK了,当然也可以根据设备的状态来返回不同的值,像我大括号中被注释掉的,因为这里老是出问题,我直接就把它注释了。

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	BYTE result;
	switch (pdrv) 
	{
		case DEV_SDCard:
		{
			result = SD_Init();
			if(result != SD_OK)return RES_NOTRDY;
			else return RES_OK;
		}return RES_OK;
		default:return RES_PARERR;
	}
	return STA_NOINIT;
}

然后是设备初始化函数,对磁盘进行初始化。

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	DWORD sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	BYTE result;
	if(!count) return RES_PARERR;		//写入数量不能为0
	switch (pdrv) 
	{
		case DEV_SDCard:
		{
			result = SD_ReadDisk(buff, sector, count);
			if(result != SD_OK)return RES_ERROR;
			else return RES_OK;
		}return RES_OK;
		default:return RES_PARERR;
	}

	return RES_ERROR;
}

然后是磁盘读函数。

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	DWORD sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	BYTE result;
	if(!count) return RES_PARERR;		//写入数量不能为0
	switch (pdrv)
	{
		case DEV_SDCard:
		{
			result = SD_WriteDisk(buff, sector, count);
			if(result != SD_OK)return RES_ERROR;
			else return RES_OK;
		}return RES_OK;
		default:return RES_PARERR;
	}

	return RES_ERROR;
}

然后是磁盘写函数。

其次如果用到长文件名,且变量定义在堆中,还需要实现这两个函数

void* ff_memalloc (	/* Returns pointer to the allocated memory block (null if not enough core) */
	UINT msize		/* Number of bytes to allocate */
)
{
	return malloc(msize);	/* Allocate a new memory block with POSIX API */
}


void ff_memfree (
	void* mblock	/* Pointer to the memory block to free (nothing to do if null) */
)
{
	free(mblock);	/* Free the memory block with POSIX API */
}

对了,如果要对中文文件名支持,就必须设置长文件名,因为中文一个字符就需要两个字节,然后前面提到的FF_CODE_PAGE也需要改为936.

DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	switch (pdrv) 
	{
		case DEV_SDCard:
		{
			 switch(cmd)
			{
				case CTRL_SYNC:
				{
					return RES_OK; 
				}	 
				case GET_SECTOR_SIZE:
				{
					*(DWORD*)buff = 512; 
					return RES_OK;
				}	 
				case GET_BLOCK_SIZE:
				{
					*(WORD*)buff = SDCardInfo.CardBlockSize;
					return RES_OK;
				}	 
				case GET_SECTOR_COUNT:
				{
					*(DWORD*)buff = SDCardInfo.CardCapacity/512;
					return RES_OK;
				}
				default:return RES_PARERR;
			}
		}return RES_OK;
		default:return RES_PARERR;
	}

	return RES_PARERR;
}

这个函数就是获取一些参数,比如SD卡的扇区大小,块的大小等,根据不同的硬件来写。

DWORD get_fattime (void)
{				 
	return 0;
}

这个就是获取RTC时钟,我直接返回0了。RTC没有开。

上面的函数中编写需要底层函数的支持,比如初始化,读写等的函数,基本上都是响应外设底层已经实现好了的,然后放到上面的几个函数中来调用罢了。

4.然后编译下载测试,挂载,打开,写,读等一些基本的函数都没有问题。移植到此告一段落。

最后说一下我在调试中遇到的几点头疼的问题:
1.在测试读写时,按照以下顺序open-write-read-close的顺序是读取不到写入的数据的,但采用open-write-close-open-read-close就可以读取到写入的数据。
原因:在写入数据时,文件指针随着数据到达文件的末尾,此时读取文件,当然是读取不到数据的,然后每次打开文件,文件指针都在文件首。所以按照第一个顺序操作,就需要如下的一个步骤:open-write-lseek-read-close。这里我都简写了。具体函数名按图索骥啊。

2.前面提到,在ffconf.h中,FF_VOLUMES设置为1时,驱动器号必须设置为0.官方文档中是这样表述的:
“Physical drive number to identify the target device. Always zero at single drive system.”

3.测试中文支持的时候,总是乱码。
原因:我用的代码编辑器是VScode,默认编码是UTF-8,而在keil中默认编码是GB2312。然而这两种文件在keil中打开都没有异常。但是当keil编辑的文件放到VScode中时,就会发现GB2312编码的文件在UTF-8编码打开时就会出现乱码。所以进行中文支持测试的时候,最好使用GB2312的编码格式。不过GBK格式和GB18030也应该没有问题,没有去尝试。

4.逻辑分区取消挂载f_mount(0,“1:”,0)或者f_mount(NULL,“1:”,0)。

5.SD卡在挂载的时候,返回FR_NOT_ENABLED。
原因:FATFS SDcard_Memory;仅仅定义是不够的,无论是全局变量(BSS)还是局部变量(栈)都是不行的。可以定义在堆区,即为其分配一块内存。于是有了如下两条语句。
my_mem_init(SRAMIN);
SDcard_Memory = (FATFS
)mymalloc(SRAMIN,sizeof(FATFS));
当然,也可以直接定义一个变量,不过要注意栈的大小。
FATFS SDcard_Memory;

原因如下:FATFS *SDcard_Memory;定义的是一个指针,这个指针指向的是FATFS类型的结构体,但是它并没有指向实体对象,所以它相当于一个野指针。导致FatFs文件系统在挂载时会出错。

好了,就写到这吧,有什么问题,百度吧,在后面问,反正我也不会回…

如果有什么错误,欢迎指正。我毕竟没有深入研究,这些东西,我个人觉得会用就行,如果真要揪出一二三来,很累。

你可能感兴趣的:(STM32)