基于 SD 卡的 FatFs 文件系统

FatFs 接口函数

FatFs 文件系统与存储设备的连接函数在 diskio.c 文件中,主要有 5 个函数需要我们编写的。

宏定义和存储设备状态获取函数

/* 为每个设备定义一个物理编号 */
#define ATA			           0     // SD卡
#define SPI_FLASH		       1     // 预留外部SPI Flash使用

#define SD_BLOCKSIZE     512 

extern  SD_CardInfo SDCardInfo;

/*-----------------------------------------------------------------------*/
/* 获取设备状态                                                          */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
	BYTE pdrv		/* 物理编号 */
)
{
	DSTATUS status = STA_NOINIT;
	
	switch (pdrv) {
		case ATA:	/* SD CARD */
			status &= ~STA_NOINIT;
			break;
    
		case SPI_FLASH:        /* SPI Flash */   
			break;

		default:
			status = STA_NOINIT;
	}
	return status;
}

FatFs 支持同时挂载多个存储设备,通过定义为不同编号以区别。SD 卡一般定义为编号 0,编号 1 预留给串行 Flash 芯片使用。使用宏定义方式给出 SD 卡块大小,方便修改。实际上,SD 卡块 大小一般都是设置为 512 字节的,不管是标准 SD 卡还是高容量 SD 卡。

disk_status 函数要求返回存储设备的当前状态,对于 SD 卡一般返回 SD 卡插入状态,这里直接返 回正常状态。


存储设备初始化函数

/*-----------------------------------------------------------------------*/
/* 设备初始化                                                            */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
	BYTE pdrv				/* 物理编号 */
)
{
	DSTATUS status = STA_NOINIT;	
	switch (pdrv) {
		case ATA:	         /* SD CARD */
			if(SD_Init()==SD_OK)
			{
				status &= ~STA_NOINIT;
			}
			else 
			{
				status = STA_NOINIT;
			}
		
			break;
    
		case SPI_FLASH:    /* SPI Flash */ 
			break;
      
		default:
			status = STA_NOINIT;
	}
	return status;
}

该函数用于初始化存储设备,一般包括相关 GPIO 初始化、外设环境初始化、中断配置等等。对 于 SD 卡,直接调用 SD_Init 函数实现对 SD 卡初始化,如果函数返回 SD_OK 说明 SD 卡正确插 入,并且控制器可以与之正常通信。


存储设备数据读取函数

DRESULT disk_read (
	BYTE pdrv,		/* 设备物理编号(0..) */
	BYTE *buff,		/* 数据缓存区 */
	DWORD sector,	/* 扇区首地址 */
	UINT count		/* 扇区个数(1..128) */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_state = SD_OK;
	
	switch (pdrv) {
		case ATA:	/* SD CARD */						
		  if((DWORD)buff&3)
			{
				DRESULT res = RES_OK;
				DWORD scratch[SD_BLOCKSIZE / 4];

				while (count--) 
				{
					res = disk_read(ATA,(void *)scratch, sector++, 1);

					if (res != RES_OK) 
					{
						break;
					}
					memcpy(buff, scratch, SD_BLOCKSIZE);
					buff += SD_BLOCKSIZE;
		    }
		    return res;
			}
			
			SD_state=SD_ReadMultiBlocks(buff,sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
		  if(SD_state==SD_OK)
			{
				/* Check if the Transfer is finished */
				SD_state=SD_WaitReadOperation();
				while(SD_GetStatus() != SD_TRANSFER_OK);
			}
			if(SD_state!=SD_OK)
				status = RES_PARERR;
		  else
			  status = RES_OK;	
			break;   
			
		case SPI_FLASH:
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}

disk_read 函数用于从存储设备指定地址开始读取一定的数量的数据到指定存储区内。

对于 SD 卡, 最重要是使用 SD_ReadMultiBlocks 函数读取多块数据到存储区。这里需要注意的地方是 SD 卡数 据操作是使用 DMA 传输的,并设置数据尺寸为 32 位大小,为实现数据正确传输,要求存储区是 4 字节对齐。

在某些情况下,FatFs 提供的 buff 地址不是 4 字节对齐,这会导致 DMA 数据传输失 败,所以为保证数据传输正确,可以先判断存储区地址是否是 4 字节对齐,如果存储区地址已经 是 4 字节对齐,无需其他处理,直接使用 SD_ReadMultiBlocks 函数执行多块读取即可。如果判断 得到地址不是 4 字节对齐,则先申请一个 4 字节对齐的临时缓冲区,即局部数组变量 scratch,通 过定义为 DWORD 类型可以使得其自动 4 字节对齐,scratch 所占的总存储空间也是一个块大小,这样把一个块数据读取到 scratch 内,然后把 scratch 存储器内容拷贝到 buff 地址空间上就可以了。

SD_ReadMultiBlocks 函数用于从 SD 卡内读取多个块数据,它有四个形参,分别为存储区地址指 针、起始块地址、块大小以及块数量。为保证数据传输完整,还需要调用 SD_WaitReadOperation 函数和 SD_GetStatus 函数检测和保证传输完成。


存储设备数据写入函数

/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上                                      */
/*-----------------------------------------------------------------------*/
#if _USE_WRITE
DRESULT disk_write (
	BYTE pdrv,			  /* 设备物理编号(0..) */
	const BYTE *buff,	/* 欲写入数据的缓存区 */
	DWORD sector,		  /* 扇区首地址 */
	UINT count			  /* 扇区个数(1..128) */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_state = SD_OK;
	
	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}

	switch (pdrv) {
		case ATA:	/* SD CARD */  
			if((DWORD)buff&3)
			{
				DRESULT res = RES_OK;
				DWORD scratch[SD_BLOCKSIZE / 4];

				while (count--) 
				{
					memcpy( scratch,buff,SD_BLOCKSIZE);
					res = disk_write(ATA,(void *)scratch, sector++, 1);
					if (res != RES_OK) 
					{
						break;
					}					
					buff += SD_BLOCKSIZE;
		    }
		    return res;
			}		
		
			SD_state=SD_WriteMultiBlocks((uint8_t *)buff,sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
			if(SD_state==SD_OK)
			{
				/* Check if the Transfer is finished */
				SD_state=SD_WaitWriteOperation();

				/* Wait until end of DMA transfer */
				while(SD_GetStatus() != SD_TRANSFER_OK);			
			}
			if(SD_state!=SD_OK)
				status = RES_PARERR;
		  else
			  status = RES_OK;	
		break;

		case SPI_FLASH:
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}
#endif

disk_write 函数用于向存储设备指定地址写入指定数量的数据。

对于 SD 卡,执行过程与 disk_read 函数是非常相似,也必须先检测存储区地址是否是 4 字节对齐,如果是 4 字节对齐则直接调用 SD_WriteMultiBlocks 函数完成多块数据写入操作。如果不是 4 字节对齐,申请一个 4 字节对齐的 临时缓冲区,先把待写入的数据拷贝到该临时缓冲区内,然后才写入到 SD 卡。

SD_WriteMultiBlocks 函数是向 SD 卡写入多个块数据,它有四个形参,分别为存储区地址指针、 起始块地址、块大小以及块数量,它与 SD_ReadMultiBlocks 函数执行相互过程。最后也是需要使 用相关函数保存数据写入完整才退出 disk_write 函数。


其他控制函数

/*-----------------------------------------------------------------------*/
/* 其他控制                                                              */
/*-----------------------------------------------------------------------*/

#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* 物理编号 */
	BYTE cmd,		  /* 控制指令 */
	void *buff		/* 写入或者读取数据地址指针 */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) {
		case ATA:	/* SD CARD */
			switch (cmd) 
			{
				// Get R/W sector size (WORD) 
				case GET_SECTOR_SIZE :    
					*(WORD * )buff = SD_BLOCKSIZE;
				break;
				// Get erase block size in unit of sector (DWORD)
				case GET_BLOCK_SIZE :      
					*(DWORD * )buff = 1;//SDCardInfo.CardBlockSize;
				break;

				case GET_SECTOR_COUNT:
					*(DWORD * )buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize;
					break;
				case CTRL_SYNC :
				break;
			}
			status = RES_OK;
			break;
    
		case SPI_FLASH:		      
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}
#endif

disk_ioctl 函数有三个形参,pdrv 为设备物理编号,cmd 为控制指令,包括发出同步信号、获取扇 区数目、获取扇区大小、获取擦除块数量等等指令,buff 为指令对应的数据指针。

对于 SD 卡,为支持格式化功能,需要用到获取扇区数量 (GET_SECTOR_COUNT) 指令和获取块 尺寸 (GET_BLOCK_SIZE)。

另外,SD 卡扇区大小为 512 字节,串行 Flash 芯片一般设置扇区大小 为 4096 字节,所以需要用到获取扇区大小 (GET_SECTOR_SIZE) 指令。 至此,基于 SD 卡的 FatFs 文件系统移植就已经完成了,最重要就是 diskio.c 文件中 5 个函数的编 写。

接下来就编写 FatFs 基本的文件操作检测移植代码是否可以正确执行。


FatFs 功能测试

主要的测试包括格式化测试、文件写入测试和文件读取测试三个部分,主要程序都在 main.c 文 件中实现。

FATFS fs;													/* FatFs文件系统对象 */
FIL fnew;													/* 文件对象 */
FRESULT res_sd;                /* 文件操作结果 */
uint32_t fnum;            					  /* 文件成功读写数量 */
BYTE ReadBuffer[1024]={0};        /* 读缓冲区 */
uint8_t WriteBuffer1[] ={"2"};  
uint8_t WriteBuffer2[] ={"llllll" };  

FATFS 是在 ff.h 文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编 号、扇区大小等等信息,一般我们都需要为每个物理设备定义一个 FATFS 变量。

FIL 也是在 ff.h 文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件 很多基本属性,比如文件大小、路径、当前读写地址等等。如果需要在同一时间打开多个文件进 行读写,才需要定义多个 FIL 变量,不然一般定义一个 FIL 变量即可。 FRESULT 是也在 ff.h 文件定义的一个枚举类型,作为 FatFs 函数的返回值类型,主要管理 FatFs 运行中出现的错误。

总共有 19 种错误类型,包括物理设备读写错误、找不到文件、没有挂载工 作空间等等错误。这在实际编程中非常重要,当有错误出现是我们要停止文件读写,通过返回值 我们可以快速定位到错误发生的可能地点。

如果运行没有错误才返回 FR_OK。 fnum 是个 32 位无符号整形变量,用来记录实际读取或者写入数据的数组。 buffer 和 textFileBuffer 分别对应读取和写入数据缓存区,都是 8 位无符号整形数组。

主函数

int main(void)
{
	LED_GPIO_Config();	
	LED_BLUE;
	Debug_USART_Config();	
	res_sd = f_mount(&fs,"0:",1);
	
	res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE );
	if ( res_sd == FR_OK )
	{
		printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");
		f_lseek(&fnew,f_size(&fnew));
//		f_write(&fnew,WriteBuffer1,sizeof(WriteBuffer1),&fnum);
//	  f_write(&fnew, " \r\n", sizeof(" \r\n")-1, &fnum); 
		f_write(&fnew,WriteBuffer2,sizeof(WriteBuffer2),&fnum);
    f_write(&fnew, " \r\n", sizeof(" \r\n")-1, &fnum); 
    f_close(&fnew);
	}

	
	res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt",FA_READ); 	 
	if(res_sd == FR_OK)
	{
		LED_GREEN;
		res_sd = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), (void*)&fnum); 
    if(res_sd==FR_OK)
    {
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
      printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);	
    }
	}
	f_close(&fnew);	
  
	f_mount(NULL,"0:",1);
  

	while(1)
	{

	}
}

程序的开头首先初始化 RGB 彩灯和调试串口,用来指示程序进程。 FatFs 的第一步工作就是使用 f_mount 函数挂载工作区。

f_mount 函数有三个形参,第一个参数是 指向 FATFS 变量指针,如果赋值为 NULL 可以取消物理设备挂载。第二个参数为逻辑设备编号, 使用设备根路径表示,与物理设备编号挂钩,在代码清单:FatFs-1 中我们定义 SD 卡物理编号为 0,所以这里使用“0:”。第三个参数可选 0 或 1,1 表示立即挂载,0 表示不立即挂载,延迟挂载。

f_mount 函数会返回一个 FRESULT 类型值,指示运行情况。 如果 f_mount 函数返回值为 FR_NO_FILESYSTEM,说明 SD 卡没有 FAT 文件系统。我们就必须 对 SD 卡进行格式化处理。使用 f_mkfs 函数可以实现格式化操作。f_mkfs 函数有三个形参,第一 个参数为逻辑设备编号;第二参数可选 0 或者 1,0 表示设备为一般硬盘,1 表示设备为软盘。第 三个参数指定扇区大小,如果为 0,表示通过代码清单:FatFs-5 中 disk_ioctl 函数获取。

格式化成 功后需要先取消挂载原来设备,再重新挂载设备。 在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用 f_open 函数打开文 件,不再使用文件必须使用 f_close 函数关闭文件,这个跟电脑端操作文件步骤类似。

f_open 函 数有三个形参,第一个参数为文件对象指针。第二参数为目标文件,包含绝对路径的文件名称 和后缀名。第三个参数为访问文件模式选择,可以是打开已经存在的文件模式、读模式、写模 式、新建模式、总是新建模式等的或运行结果。

比如对于写测试,使用 FA_CREATE_ALWAYS 和 FA_WRITE 组合模式,就是总是新建文件并进行写模式。 f_close 函数用于不再对文件进行读写操作关闭文件,f_close 函数只要一个形参,为文件对象指 针。f_close 函数运行可以确保缓冲区完全写入到文件内。

成功打开文件之后就可以使用 f_write 函数和 f_read 函数对文件进行写操作和读操作。这两个函 数用到的参数是一致的,只不过一个是数据写入,一个是数据读取。f_write 函数第一个形参为文 件对象指针,使用与 f_open 函数一致即可。第二个参数为待写入数据的首地址,对于 f_read 函数 就是用来存放读出数据的首地址。第三个参数为写入数据的字节数,对于 f_read 函数就是欲读取 数据的字节数。第四个参数为 32 位无符号整形指针,这里使用 fnum 变量地址赋值给它,在运行 读写操作函数后,fnum 变量指示成功读取或者写入的字节个数。 最后,不再使用文件系统时,使用 f_mount 函数取消挂载。

下载验证

相关推荐

  1. 基于 SD FatFs 文件系统

    2024-07-12 17:22:05       20 阅读
  2. FATFS学习笔记——FATFS文件两种方式

    2024-07-12 17:22:05       48 阅读
  3. FAT32 文件系统详解

    2024-07-12 17:22:05       30 阅读
  4. android读取sd文件数据

    2024-07-12 17:22:05       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-12 17:22:05       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-12 17:22:05       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-12 17:22:05       58 阅读
  4. Python语言-面向对象

    2024-07-12 17:22:05       69 阅读

热门阅读

  1. 拥抱变革,AI工作新纪元

    2024-07-12 17:22:05       19 阅读
  2. 【若依】打开一个新页面

    2024-07-12 17:22:05       22 阅读
  3. Linux跨服务器文件传输

    2024-07-12 17:22:05       20 阅读
  4. 软设之享元模式

    2024-07-12 17:22:05       20 阅读
  5. 3179. K 秒后第 N 个元素的值

    2024-07-12 17:22:05       22 阅读
  6. mysql中的二进制数据类型

    2024-07-12 17:22:05       20 阅读
  7. mysql8遇到的报错Public Key Retrieval is not allowed

    2024-07-12 17:22:05       22 阅读
  8. MySQL事务

    2024-07-12 17:22:05       20 阅读
  9. C语言阶乘(只用逻辑运算中的短路效应判断)

    2024-07-12 17:22:05       21 阅读
  10. numpy 解释函数nanmax

    2024-07-12 17:22:05       22 阅读