FATFS文件系统使用心得(SD卡-SPI模式)

目录

一、FATFS文件系统介绍

1.文件系统简介

2.SD卡简介

3.文件系统资源包下载地址

4.fatfs文件资源包结构

(1)ff15a文件结构

(2)source文件结构

二、0.11a版本使用

1.单片机开发板

2.DS3231模块及原理图

3.代码部分

4.运行结果

5.遇到的问题


初学单片机,分享一些使用心得,如有错误,敬请包涵。

一、FATFS文件系统介绍

1.文件系统简介

FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由ANSI C语言编写并且完全独立于底层的I/O介质。 因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。 FatFs支持FAT12、FAT16、FAT32等格式,

支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8 位单片机和16 位单片机做了优化。 所以我们利用前面写好的SPI模式SD卡驱动,把FatFs文件系统代码移植到工程之中, 就可以利用文件系统的各种函数,对SD卡以“文件”格式进行读写操作了。

特征:

  • DOS/Windows 兼容的 FAT/exFAT 文件系统。
  • 平台独立。易于移植
  • 程序代码和工作区占用空间非常小。
  • 支持的各种配置选项
    • ANSI/OEM 或 Unicode 中的长文件名。
    • exFAT 文件系统、64 位 LBA 和用于大型存储的 GPT。
    • 适用于 RTOS 的线程安全。
    • 多个卷。(物理驱动器和分区)
    • 可变扇区大小。
    • 多个代码页,包括 DBCS。
    • 只读、可选 API、I/O 缓冲区等.

2.SD卡简介

SD存储卡(Secure Digital Memory Card)是一种基于半导体快闪存储器的新一代高速存储设备。SD存储卡的技术是从MMC卡(MultiMedia Card格式上发展而来,在兼容SD存储卡基础上发展了SDIO(SD Input/ Output)卡,此兼容性包括机械,电子,电力,信号和软件,通常将SD、SDIO卡俗称SD存储卡

SD卡有两种驱动模式:SPI模式与SDIO模式。它们所使用的接口信号是不同的。在SPI模式下,只会用到SD卡的4根信号线,即CS、DI、SCLK与DO(分别是SD卡的片选、数据输入、时钟与数据输出)。

SD卡共支持三种传输模式:SPI模式(独立序列输入和序列输出),1位SD模式(独立指令和数据通道,独有的传输格式),4位SD模式(使用额外的针脚以及某些重新设置的针脚。支持四位宽的并行传输)。

参考连接:STM32使用SPI方式读写SD 卡

3.文件系统资源包下载地址

链接:FATFS官网

官网已更新到0.15a版本

4.fatfs文件资源包结构

(1)ff15a文件结构

documents文件:是帮助文件,当作是一个官方的参考手册、说明书来看,里面有对文件系统的各个API的使用进行介绍说明

source文件:是整个文件系统的核心所在,包含了文件系统的全部源文件和头文件。

LICENSE文档:是开源许可文件说明。

(2)source文件结构

0.15a版本source文件结构
文件说明
00history介绍了FatFs的版本更新情况不需要修改
00readme说明了当前目录下 diskio.c 、diskio.h、ff.c、ff.h等的功能不需要修改
diskio.cIO模块接口层文件需要配置API接口
diskio.hIO模块接口层文件不需要修改
ff.cFATFS和应用模块文件不需要修改
ff.hFATFS和应用模块文件不需要修改
ffonf.cFATFS模块配置文件根据需求修改参数
ffsystem.c根据是否有操作系统来修改这个文件
ffunicode.c可选,根据 ffconf.h 的配置,进行 Unicode 编码转换不需要修改

注:0.13版本之前的文件如下0.13版本之后的文件将option文件夹整合到了ffunicode.c中,主要会用到cc936.c文件,它是用来配置中文格式的。

0.12版本source文件结构

option文件
option文件

二、0.11a版本使用

1.单片机开发板

我是用的是野火STM32F103RCT6min开发板,硬件资源如下图,

2.DS3231模块及原理图

3.代码部分

目前实现的功能是利用DS3231时钟模块和FATFS文件系统,实现时间的连续存储

目前只用到sd卡,所以设备只用到一个,SD卡设备用“0”宏定义

diskio.c


#include <string.h>
#include "diskio.h"
#include "stm32f10x.h"
#include "bsp_spi_sdcard.h"

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

//固定只支持blocksize大小为512的卡,兼容大于512的卡时,该卡容量会变小
#define SD_BLOCKSIZE     512//SDCardInfo.CardBlockSize 


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

		default:
			status = STA_NOINIT;
	}
	return status;
}

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


/*-----------------------------------------------------------------------*/
/* 读扇区:读取扇区内容到指定存储区                                              */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE pdrv,		/* 设备物理编号(0..) */
	BYTE *buff,		/* 数据缓存区 */
	DWORD sector,	/* 扇区首地址 */
	UINT count		/* 扇区个数(1..128) */
)
{
	DRESULT status = RES_PARERR;
	SD_Error SD_state = SD_RESPONSE_NO_ERROR;
	
	switch (pdrv) {
		case SD_CARD:	/* SD CARD */						
			
			SD_state=SD_ReadMultiBlocks(buff,(uint64_t)sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);

			if(SD_state!=SD_RESPONSE_NO_ERROR)
				status = RES_PARERR;
		  else
			  status = RES_OK;	
			break;   
			
		case SPI_FLASH:
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}



/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上                                      */
/*-----------------------------------------------------------------------*/
#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_RESPONSE_NO_ERROR;
	
	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}

	switch (pdrv) {
		case SD_CARD:	/* SD CARD */  
		
			SD_state=SD_WriteMultiBlocks((uint8_t *)buff,(uint64_t)sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);

			if(SD_state!=SD_RESPONSE_NO_ERROR)
				status = RES_PARERR;
		  else
			  status = RES_OK;	
		break;

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


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

#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* 物理编号 */
	BYTE cmd,		  /* 控制指令 */
	void *buff		/* 写入或者读取数据地址指针 */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) {
		case SD_CARD:	/* 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;
				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

							 
__weak DWORD get_fattime(void) {
	/* 返回当前时间戳 */
	return	  ((DWORD)(2025 - 1980) << 25)	/* Year 2015 */
			| ((DWORD)1 << 21)				/* Month 1 */
			| ((DWORD)1 << 16)				/* Mday 1 */
			| ((DWORD)0 << 11)				/* Hour 0 */
			| ((DWORD)0 << 5)				  /* Min 0 */
			| ((DWORD)0 >> 1);				/* Sec 0 */
}

fat.c

包含SD卡格式化

#include "stm32f10x.h"       // Device header
#include "ff.h"
#include "DS3231.h"
#include "delay.h"
#include "bsp_led.h"
#include "Serial.h"

#include "string.h"
#include "stdio.h"

FATFS fs;					   /* FatFs文件系统对象 */
FIL fnew;					   /* 文件对象 */
FRESULT res_sd;               /* 文件操作结果 */
UINT fnum;            		   /* 文件成功读写数量 */
char ReadBuffer[1024]={0};     /* 读缓冲区 */
char  WriteBuffer[1024]={0};         /* 写入的数据*/
char fileName[100];			//创建的文件名

char  TimeData[30]={0};  //缓存时间数据
char  stringBuffer[72]={0};  //缓存传感器数据



uint8_t state=0;//定位在这里:写入次数


void SET_FAT(void)	//文件系统格式化,创建文件系统
{
	printf("\r\n****** 这是一个SD卡 文件系统实验 ******\r\n");
	//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
	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("《《格式化失败。》》res_sd =%d\r\n",res_sd);
			while(1);
		}
	}
  else if(res_sd!=FR_OK)
  {
    printf("!!SD卡挂载文件系统失败。(%d)\r\n",res_sd);
    printf("!!可能原因:SD卡初始化不成功。\r\n");
		while(1);
  }
  else
  {
    printf("》文件系统挂载成功,可以进行读写测试\r\n");
	  
  }
}
/*********************************************/
  
void CreatFile(void)
{
	Get_DS3231_Time();
	printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
	sprintf(fileName,"0:测量数据_%04d%02d%02d.txt",calendar.w_year+2000,calendar.w_month,calendar.w_date);
	printf("》测量数据_%04d%02d%02d.txt\r\n",calendar.w_year+2000,calendar.w_month,calendar.w_date);
}

写入数据,追加数据代码

追加数据主要使用f_lseek函数,通过移动指针位置的方法来实现追加数据,原因是0.11a版本中没有常用的FA_OPEN_APPEND函数来实现追加数据,fatfs官网在0.13版本之后,加入了FA_OPEN_APPEND函数

链接:fatfs官网

void WRITE_FAT(void)		//写入数据
{	
/*----------------------- 文件系统测试:写入数据 -----------------------------*/
	Get_DS3231_Time();
	res_sd = f_open(&fnew, fileName,FA_WRITE |FA_OPEN_ALWAYS ); //打开成功
	if ( res_sd == FR_OK )
	{
        sprintf(WriteBuffer,"%04d-%02d-%02d %02d:%02d:%02d\n", calendar.w_year + 2000, calendar.w_month, calendar.w_date, calendar.hour, calendar.min, calendar.sec);
        res_sd = f_write(&fnew, WriteBuffer, strlen(WriteBuffer), &fnum);
               
    if(res_sd==FR_OK) 
    {
      printf("》文件写入成功,写入字节数据:%d\r\n",fnum);
      printf("》向文件写入的数据为:\r\n%s",WriteBuffer);
    }
    else
    {
      printf("!!文件写入失败:(%d)\n",res_sd);
    }    
	/* 不再读写,关闭文件 */
    f_close(&fnew);
	}
	else
	{	
		printf("!!打开/创建文件失败。\r\n");
	}
}


void REMOVE_FAT(void)		//移动指针位置,追加数据
{  	delay_ms(1000);
	Get_DS3231_Time();
	printf("\r\n****** 追加数据测试.. ******\r\n");			
	f_open(&fnew, fileName, FA_WRITE |FA_OPEN_ALWAYS  );//区分写入成功 //打开文件,指针为0
	res_sd = f_lseek(&fnew,20*(state+1));//移动31*(state+1)个字节,从state=0开始,移动一次,指针增加112    
	sprintf(stringBuffer, "%04d-%02d-%02d %02d:%02d:%02d\n",   calendar.w_year + 2000, calendar.w_month, calendar.w_date, 
                    calendar.hour, calendar.min, calendar.sec);  //时间数据缓存至WriteBuffer

	stringBuffer[20] = '\0';
		
	printf("Data:%s\n",stringBuffer); 
  //将字符串写入SD卡
    res_sd=f_write(&fnew,stringBuffer,strlen(stringBuffer),&fnum);		
	
	printf("state:%d",state);    
	state++; //写入次数			
			/* 不再读写,关闭文件 */
	f_close(&fnew);	
}
void READ_FAT(void)		//读数据
{
/*------------------- 文件系统测试:读数据 ------------------------------------*/
	printf("****** 即将进行文件读取测试... ******\r\n");
	res_sd = f_open(&fnew, fileName, FA_READ |FA_OPEN_EXISTING ); 	 
	if(res_sd == FR_OK){
		printf("》打开文件成功。\r\n");
	}
		res_sd = f_read(&fnew, ReadBuffer, strlen(ReadBuffer), &fnum);
        /* 实测SPI_SD驱动下读取大于512字节的数据在SD卡里打开会显示乱码,如需读取大量数据使用f_read_co替代上面f_read即可 */
   //     res_sd = f_read_co(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
    if(res_sd==FR_OK){
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
	  printf("》读取得的文件数据为:\r\n%s\r\n", ReadBuffer);	
    }
    else{
      printf("!!文件读取失败:(%d)\n",res_sd);
    }		
	
	/* 不再读写,关闭文件 */
	f_close(&fnew);	
	
	/* 不再使用文件系统,取消挂载文件系统 */
	f_mount(NULL,"0:",1);
  
  /* 操作完成,停机 */
}

读取文件内容的函数,没有在主函数中用到

main.c

有一些函数的初始化时暂时用不到的,我没删除,但不影响使用,

int main(void)
{
 
	uint8_t state1;
	delay_init();
	MyI2C1_Init();
	DS3231_Init();			//DS3231初始化
	LED_GPIO_Config();
	Serial_Init();
	OLED_Init();
		
	
	SET_FAT();
//	Set_DS3231_Time(25,3,15,12,00,00,6);//设置时间为2025年3月15日12时00分00秒星期6,下载一次之后,注释掉重新下载
	CreatFile();
//	Get_DS3231_Time();	//获取DS3231的时间
	
	

	while(1)
	{  
		
			if(state==0)
			{
				WRITE_FAT();
				REMOVE_FAT();
			}
			else 
			{
					REMOVE_FAT();	
			    
			}
			     Num=0;
			delay_ms(1000);

	}
//	READ_FAT();

}

4.运行结果

串口助手显示

SD卡中创建的文件显示,存储的时间无问题,但想要每秒存储还需调整main.c中的代码

5.遇到的问题

问题1:

追加数据时,我使用如下方法来实现移动指针的位置,20是时间的长度

res_sd = f_lseek(&fnew,20*(state+1));.

下面的方法应该也是可以的,不知道为什么实现不了连续存储,我也没有深入研究,

f_lseek(&fnew f_size(&fnew));

结尾:以上就是在FATAS文件系统中存储时间的代码,原本的代码是用来存储磁传感器数据及时间的,去掉了存储传感器数据 代码,目前实现时间存储目前什么问题,后续,我会继续更新存储传感器数据及时间的代码,还有加入蓝牙模块来控制单片机实现创建、存储等


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值