基于SD卡的FatFs文件系统(FatFs移植到STM32)

平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线

工程介绍:主要文件在USER组中,bsp_sdio_sdcard.c,bsp_sdio_sdcard.h和main.c,另外FatFs是用来后面移植文件系统使用的,对于本节内容暂时不需要。bsp_sdio_sdcard.c和bsp_sdio_sdcard.h文件主要参考教材《STM32库开发实战指南——基于STM32F03》。另外就是本教材用到的FatFs系统源代码,这里温馨提示一下,关于FatFs目前网上笔者找到的最新版的参考资料不是太多,所以笔者在用最新版做的时候,一些函数原型发生了变化,虽然变化不大,也给我造成了一定的阻碍,所以建议大家下载稍微老一点的版本,这样资料较多,出问题好解决,我最终用的版本是R0.09,因为在使用函数f_mkfs()的过程中遇到问题,无法格式化SD卡,最终选择较老的版本。本文有些内容来自于其他网友总结,在此表示感谢。


整体的项目和上一节中的SDIO读写SD卡类似,细节上在添加了FatFs组,专门存放FatFs移植的内容。对于移植过程,就是对diskio.c文件的修改的过程。这个文件完成了与底层有多的操作,我们只需要实现一下函数即可,不同的平台函数实现略有不同,但是提供给用户最终的接口是相同的,也正是因为这一点,使得FatFs文件系统有了很好的可移植性。


再次申明一下,FatFs文件系统与存储设备的接口函数再diskio.c文件中,我们只需要完善该文件即可。这也就是所谓的软件移植裁剪过程吧。我们需要完善五个函数disk_status,disk_initialize,disk_read,disk_write,disk_ioctl等,还有一个获取时间戳的函数,可以直接return 0;

1.宏定义

#define SD_CARD	 0  //SD卡,卷标为0
#define EX_FLASH 1	//外部flash,卷标为1,为以后外部Flash扩展预留的

#define SD_BLOCKSIZE 512

//声明外部变量
extern SD_CardInfo SDCardInfo;
2.dis_status 获取磁盘的状态

//获得磁盘状态
DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;
	switch(pdrv)
	{
		case SD_CARD://SD卡
			status &= ~STA_NOINIT;
  		break;
		case EX_FLASH://外部flash
 			break;
		default:
			status = STA_NOINIT;
			break;
	}
	return status;
}  
3. disk_initialize 初始化磁盘,当遇到无法挂载SD卡,或者其他问题时,优先检查该函数,该函数会调用SD_Init函数,需要检查

SD_Init函数的返回结果是否为SD_OK。

//初始化磁盘
DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;
	switch(pdrv)
	{
		case SD_CARD://SD卡
			if(SD_OK == SD_Init())//SD卡初始化 
			{
				status &= ~STA_NOINIT;
			}
			else
			{
				status = STA_NOINIT;
			}
  		break;
		case EX_FLASH://外部flash
 			break;
		default:
			status = STA_NOINIT;
			break;
	}
	return status;
} 
4. disk_read 读取SD卡

//读扇区
//pdrv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	DWORD sector,	/* Sector address in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_Status = SD_OK;
	
	switch(pdrv)
	{
		case SD_CARD://SD卡
			if((DWORD)buff & 3)
			{
				DRESULT res = RES_OK;
				DWORD scratch[SD_BLOCKSIZE / 4];
				
				while(count--)
				{
					res = disk_read(SD_CARD, (void*)scratch, sector++, 1);
					
					if(res != RES_OK)
					{
						break;
					}
					memcpy(buff, scratch, SD_BLOCKSIZE);
					buff += SD_BLOCKSIZE;
				}
				return res;
			}
			
			SD_Status = SD_ReadMultiBlocks(buff, sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
			
			if(SD_Status == SD_OK)
			{
				//检查传输是否完成
				SD_Status = SD_WaitReadOperation();
				while(SD_GetStatus() != SD_TRANSFER_OK);
			}
			
			if(SD_Status != SD_OK)
			{
				status = RES_PARERR;
			}
			else
			{
				status = RES_OK;
			}
  		break;
		case EX_FLASH://外部flash
 			break;
		default:
			status = RES_PARERR;
			break;
	}
	return status;
}
5.disk_write 写数据到SD卡

//写扇区
//pdrv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
#if _USE_WRITE
DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	DWORD sector,		/* Sector address in LBA */
	UINT count			/* Number of sectors to write */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_Status = SD_OK;
	
	//检查参数
	if(!count)
	{
		return RES_PARERR;
	}
	
	switch(pdrv)
	{
		case SD_CARD://SD卡
			if((DWORD)buff & 3)
			{
				DRESULT res = RES_OK;
				DWORD scratch[SD_BLOCKSIZE / 4];
				
				while(count--)
				{
					memcpy(scratch, buff, SD_BLOCKSIZE);
					
					res = disk_write(SD_CARD, (void*)scratch, sector++, 1);
					if(res != RES_OK)
					{
						break;
					}
					buff += SD_BLOCKSIZE;
				}
				return res;
			}
			
			SD_Status = SD_WriteMultiBlocks((uint8_t *)buff, sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
			
			if(SD_Status == SD_OK)
			{
				//检查传输是否完成
				SD_Status = SD_WaitReadOperation();
				while(SD_GetStatus() != SD_TRANSFER_OK);
			}
			
			if(SD_Status != SD_OK)
			{
				status = RES_PARERR;
			}
			else
			{
				status = RES_OK;
			}
  		break;
		case EX_FLASH://外部flash
 			break;
		default:
			status = RES_PARERR;
			break;
	}
	return status;
}
6.disk_ioctl 函数,获取SD卡的某些参数,如块大小等。

//其他表参数的获得
//pdrv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	DRESULT res = RES_ERROR;						  			     
	switch(pdrv)//SD卡
	{
		case SD_CARD:
	    switch(cmd)
	    {
		    case GET_SECTOR_SIZE:
					*(WORD*)buff = SD_BLOCKSIZE; 
		      break;	 
		    case GET_BLOCK_SIZE:
					*(DWORD*)buff = SDCardInfo.CardBlockSize;
		      break;	 
		    case GET_SECTOR_COUNT:
		      *(DWORD*)buff = SDCardInfo.CardCapacity / SD_BLOCKSIZE;
		      break;
		    case CTRL_SYNC:
		      break;
	    }
		  res = RES_OK;
			break;
		case EX_FLASH://外部flash
 			break;
		default:
			res = RES_PARERR;
			break;
	}
  return res;
}
7.get_fattime函数,获取时间戳。没有实现。

//获得时间                                                                                                                                                                                                                                  
DWORD get_fattime (void)
{
	//返回当前时间戳
	return 0;
}
8.另外需要修改ffconf.h文件,另外注意,新版本的该文件都会在宏的前面加上FF前缀,例如对于_USE_LEN,新版本的可能是

FF_USE_LLEN,本节的内容都是按照旧版本来说的,因此宏没有以FF为前缀。

#define _USE_LEN 2  //长文件名支持,默认不支持长文件名
#define _USE_MKFS 1  //格式化功能选择,使能FatFs的格式化功能
#define _CODE_PAGE 936  //语言功能选择,需要同时把语言文件添加到工程中,为支持简体中文,我们需要添加cc963.c文件
#define _VOLUMES 2  //物理设备数量,这里设置为2,包括SD卡和预留的外部Flash芯片
#define _MIN_SS 512   //指定扇区大小的最小值
#define _MAX_SS 4096  //指定扇区大小的最大值
以上 就完成了FatFs的移植过程,接下来是对功能的测试,直接上代码了。

测试:

//FAT功能测试:格式化测试,文件写入测试,文件读取测试(基本功能)
FATFS fs; //FatFs文件系统对象
FIL fnew; //文件对象
FRESULT res_sd;//文件操作结果
UINT fnum; //文件成功读写数量
BYTE ReadBuffer[1024] = {0};
BYTE WriteBuffer[] = "成功移植了FatFs文件系统!\r\n"; //写缓存区
main 主函数,包含格式化测试和文件读写测试。
int main()
{
	//串口配置
	USART_Config();
	
	//初始化LED
	LED_GPIO_Config();
	
	//初始化SD卡
	if(SD_Init() == SD_OK)
	{
		printf("SD卡初始化成功,即将挂载SD卡。\r\n");
	}
	
	//挂载SD卡
	res_sd = f_mount(&fs, "0:", 1);
	
	//***********************格式化测试****************************
	if(res_sd == FR_NO_FILESYSTEM)
	{
		printf("SD卡没有文件系统,即将进行格式化...\r\n");
		//格式化
		res_sd = f_mkfs("0:", 0, 0);
		
		if(res_sd == FR_OK)
		{
			printf("SD卡成功格式化!\r\n");
			//格式化后先取消挂载
			res_sd = f_mount(NULL, "0:", 1);
			//再重新挂载
			res_sd = f_mount(&fs, "0:", 1);
		}
		else
		{
			printf("文件格式化失败!错误代码:%d\r\n",res_sd);
			while(1);
		}
	}
	else if(res_sd != FR_OK)
	{
		printf("挂载文件系统失败!可能是因为文件初始化失败!错误代码:%d\r\n", res_sd);
	}
	else
	{
		printf("文件系统挂载成功, 可进行读写测试!\r\n");
	}
	
	//***********************写测试****************************
	//打开文件,如果文件不存在则创建它
	printf("即将进行文件写入测试....\r\n");
	//打开文件,若不存在就创建
	res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE);
	//文件打开成功
	if(res_sd == FR_OK)
	{
		printf("打开文件成功!开始写入数据!\r\n");
		res_sd= f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum);
		
		if(res_sd == FR_OK)
		{
			printf("数据写入成功!\r\n");
			printf("数据:%s。共写入%d个字符\r\n", WriteBuffer, fnum);
		}
		else
		{
			printf("数据写入失败!\r\n");
		}
		
		//关闭文件
		f_close(&fnew);
	}
	
	//***********************读测试****************************
	//打开文件,如果文件不存在则创建它
	printf("即将进行文件读取测试....\r\n");
	//打开文件,若不存在就创建
	res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_OPEN_EXISTING | FA_READ);
	//文件打开成功
	if(res_sd == FR_OK)
	{
		printf("打开文件成功!开始读取数据!\r\n");
		res_sd= f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
		
		if(res_sd == FR_OK)
		{
			printf("数据读取成功!\r\n");
			printf("数据:%s\r\n", ReadBuffer);
		}
		else
		{
			printf("数据读取失败!\r\n");
		}
		
		//关闭文件
		f_close(&fnew);
	}
	
	scan_files("FatFs读写测试文件.txt");
	
	//其他功能测试
	file_check();
	
	//多项功能测试
	miscellaneous();
	
	//取消挂载文件系统
	f_mount(NULL, "0:", 1);
	
	while(1);
}
补充,如有需要可以借鉴,其他功能测试。
//多项功能测试
static FRESULT miscellaneous()
{
	DIR dir;
	FATFS *pfs;
	DWORD fre_clust, fre_sect, tot_sect;
	
	printf("\r\n*************************设备信息获取***************************\r\n");
	//获取设备信息和空簇大小
	res_sd = f_getfree("0:", &fre_clust, &pfs);
	
	//计算得到总的扇区个数和空扇区个数
	tot_sect = (pfs->n_fatent - 2) * pfs->csize;
	fre_sect = fre_clust * pfs->csize;
	
	printf("设备总空间:%10lu KB\r\n可用空间:%10lu KB。\r\n", tot_sect*4, fre_sect*4);
	
	printf("\r\n*************************文件定位和格式化写入功能测试***************************\r\n");
	//打开文件,若不存在就创建
	res_sd = f_open(&fnew, "0:FatFs多项功能测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
	//文件打开成功
	if(res_sd == FR_OK)
	{
		printf("打开文件成功!开始读取数据!\r\n");
		res_sd= f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum);
		
		if(res_sd == FR_OK)
		{
			printf("数据写入成功!\r\n");
			printf("数据:%s\r\n", WriteBuffer);
			//文件定位,定位到文件末尾,f_size获取文件大小
			res_sd = f_lseek(&fnew, f_size(&fnew) - 1);
			if(res_sd == FR_OK)
			{
				//在原文件中追加一行内容
				f_printf(&fnew, "在原文件中追加一行内容。\n");
				f_printf(&fnew, "设备总空间:%10lu KB\r\n可用空间:%10lu KB。\r\n", tot_sect*4, fre_sect*4);
				
				//文件定位到起始位置
				res_sd = f_lseek(&fnew, 0);
				
				if(res_sd == FR_OK)
				{
					//打开文件,若不存在就创建
					res_sd = f_open(&fnew, "0:FatFs多项功能测试文件.txt", FA_OPEN_EXISTING | FA_READ);
					//文件打开成功
					if(res_sd == FR_OK)
					{
						printf("打开文件成功!开始读取数据!\r\n");
						res_sd= f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
						
						if(res_sd == FR_OK)
						{
							printf("数据读取成功!\r\n");
							printf("数据:%s\r\n", ReadBuffer);
						}
						else
						{
							printf("数据读取失败!\r\n");
						}
						
						//关闭文件
						f_close(&fnew);
					}
				}
			}
		}
		else
		{
			printf("数据读取失败!\r\n");
		}
		
		//关闭文件
		f_close(&fnew);
	}
	printf("\r\n*************************目录创建和重命名功能测试***************************\r\n");
	//尝试打开目录
	res_sd = f_opendir(&dir, "0:TestDir");
	if(res_sd != FR_OK)
	{
		//打开目录失败,开始创建目录
		res_sd = f_mkdir("0:TestDir");
	}
	else
	{
		//如果目录已经存在,关闭它
		res_sd = f_closedir(&dir);
		//删除文件
		f_unlink("0:FatFs读写测试文件.txt");
	}
	
	if(res_sd == FR_OK)
	{
		//重命名并移动文件
		res_sd = f_rename("0:FatFs多项功能测试文件.txt", "0:/TestDir/FatFs多项功能测试文件.txt");
		
		if(res_sd == FR_OK)
		{
			printf("重命名并移动文件成功!\r\n");
		}
		else
		{
			printf("重命名并移动文件失败!\r\n");
		}
	}
}

//文件信息获取
static FRESULT file_check()
{
	//文件信息
	static FILINFO fno;
	
	//获取文件信息,必须确保文件存在
	res_sd = f_stat("0:FatFs读写测试文件.txt", &fno);
	
	if(res_sd == FR_OK)
	{
		printf("0:FatFs读写测试文件.txt的信息如下:\r\n");
		printf("文件大小:%ld\r\n", fno.fsize);
		printf("时间戳:%u/%02u/%02u, %02u:%02u\r\n", (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31, fno.ftime >> 11, fno.ftime >> 5 &63);
		printf("属性:%c%c%c%c%c\r\n", 
		(fno.fattrib & AM_DIR) ? 'D' : '-', //目录
		(fno.fattrib & AM_RDO) ? 'R' : '-', //只读
		(fno.fattrib & AM_HID) ? 'H' : '-', //隐藏
		(fno.fattrib & AM_SYS) ? 'S' : '-', //系统文件
		(fno.fattrib & AM_ARC) ? 'A' : '-');//档案文件
	}
}

//路径扫描
static FRESULT scan_files(char* path)
{
	FRESULT res;//在递归过程中被修改,不用全局变量
	FILINFO fno;
	DIR dir;
	int i;
	char* fn;
	
	#if _USE_LEN//长文件名支持
	//简体中文需要两个字节保存一个字
	static char lfn[_MAX_LFN*2+1];
	fno.lfname = lfn;
	fno.lfsize = sizeof(lfn);
	#endif
	
	//打开目录
	res = f_opendir(&dir, path);
	if(res == FR_OK)
	{
		i = strlen(path);
		while(1)
		{
			//读取目录下的内容
			res = f_readdir(&dir, &fno);
			if(res != FR_OK || fno.fname[0] == 0)
			{
				break;
			}
			#if _USE_LEN
			fn = *fno.lfname ? fno.lfname : fno.fname;
			#else
			fn = fno.fname;
			#endif
			
			//点表示当前目录,跳过
			if(*fn == '.')
				continue;
			//目录,递归读取
			if(fno.fattrib & AM_DIR)
			{
				//合成完整目录名
				sprintf(&path[i], "/%s", fn);
				
				//递归遍历
				res = scan_files(path);
				
				path[i] = 0;
				if(res != FR_OK)
				{
					break;
				}
				else
				{
					printf("%s/%s\r\n", path, fn);//输出文件名,可以在这里提取特定格式的文件路径
				}
			}
		}
	}
	return res;
}
感谢大家的耐心阅读,如果有问题,欢迎一起讨论。

猜你喜欢

转载自blog.csdn.net/zyxhangiian123456789/article/details/79098483