欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入

程序员文章站 2024-02-22 20:31:35
...
在写博客之前想解释一下FATFS的,但是觉得好像自己的理解也不是特别深入,为了防止讲错误导大家,还是请先了解一下fatfs,以及SD卡的一些知识。网上还是有很多的,请大佬们自行了解。

一、关于SD卡、FATFS的配置其实都是一些默认配置,在CUBEMX上配置即可。有几个需要注意的地方就是:

a.在使能 SDIO时,选择了数据宽度为4 bit,所以特别要注意时钟频率。由于我所使用的是SDHC类型的卡(有SD卡、MMC卡。SDHC指的是高容量的SD卡,其存储容量为4-32GB,其文件特征为FATFS32,emmmm详细的就不再说了,请大佬们自行了解。)
【SD卡根据其使用的系统规范】

系统规范版本 时钟
V3.31 1-20MHz
V4.2和V4.4 1-48MHz
因为所用的SD卡的系统版本是V3.31的,所以其时钟频率是有限制的。至于这个能不能用,我先卖个关子。

接下来是配置相关的功能。Oh,我用的开发板是阿波罗的,芯片型号是STM32F429Ix。
1.RCC,这个就不说了。基本都一样
2.SYS,同上。
3.USART,把读写结果都通过串口打印到PC。
4.SDIO,由于我的CUBEMX更新了,好像之前不是这个名称的。
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
在这里选择了SD 4 bit Wide bus(注意这里哦)
在写SD卡的是通过块传输数据的,对这个不理解的可以自行学习。
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入

由于需要使能中断,因此要调整相应的优先级,才能正常工作。

其优先级为:

功能 优先级
SDIO 5
DMA 6

然后就是使能SD卡了,关于SD卡的驱动,在CUBEMX的库里面是自动生成的,所以其实是不用理的。
在网上很多关于SD卡的博客中,都有写出需要另外单独使能SD卡,但是我实际应用中是不需要的,为什么我也不知道。我看了大佬们的博客,觉得可能是因为之前的CUBEMX版本的问题,新版本的CUBEMX是已经生成使能了。所以不用理。
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
下面是时钟树的配置。由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
根据SD的系统版本,时钟频率应该小于20MHZ的,所以这里是19.2MHZ。
还有需要注意的是栈的设置,默认的栈大小为0x200,需更改大一点,如0x1000。
然后生成相应的工程。

二、修改相应代码

1、首先是串口的重定义。这个其实很简单,就是在usart.c文件中加入相关的重定义代码,就用使用printf功能了。

struct __FILE 
{ 
	int handle; 
}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
void _sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{ 	
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
	USART1->DR = (uint8_t) ch;      
	return ch;
}

2、然后在main.c函数中的/* USER CODE BEGIN PV */后添加如下定义

/* USER CODE BEGIN PV */
FATFS fs;                 //工作空间
FIL fil;                  // 文件项目
uint32_t byteswritten;    // 写文件计数
uint32_t bytesread;       // 读文件计数
uint8_t wtext[] = "This's a SD card,it was SDHC and 8G!!"; // 写的内容
uint8_t rtext[1024];             // 读取的buff
char filename[] = "test_first.txt"; // 文件名

/* USER CODE END PV */

3、然后加入测试函数

/* USER CODE BEGIN 4 */
 void SD_Fatfs_Test(void)
 {
	 int i;
    printf("文件挂载\r\n" );
	  /*-1- 挂载文件系统*/
    retSD = f_mount(&fs, "", 0);
    if(retSD==FR_OK)
    {
         printf("MOUNT OK\r\n");
         printf("FAILED0: %d\r\n",retSD);
    }
    else
        printf("FAILED2: %d\r\n",retSD);
		
		i=FATFS_GetAttachedDriversNbr();//用于检测挂载的SD卡的数量
		printf("已挂载的驱动:%d\r\n",i);
		
	/*-2-创建新的文件并写入数据*/
    printf("文件打开\r\n" );
    retSD = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE ); 
    if(retSD==FR_OK)
    {
         printf("OPEN OK\r\n");
    }
    else
        printf("FAILED3: %d\r\n",retSD);
		
     /*-3- 在txt文件中写入数据*/
     retSD = f_write(&fil, wtext, sizeof(wtext), (void *)&byteswritten);	//在文件内写入wtext内的内容   这里用定义好的数组存储数据,可以用来转存接收到的数据
    if(retSD)															//返回值不为0(出现问题)
        printf(" write file error : %d\r\n",retSD);							//返回值不为0(出现问题)
    else
    {
        printf(" write file sucess!!! \r\n");
        printf(" write Data : %s\r\n",wtext);							//打印写入的数据
    }
    
		/*-4- 关闭txt文件*/
     printf("文件关闭\r\n" );
     retSD = f_close(&fil);	
        
    if(retSD==FR_OK)
    {
         printf("close OK\r\n");
    }
    else
        printf("FAILED5: %d\r\n",retSD);
     
     /*-5- 打开文件读取数据*/
    retSD = f_open(&fil, filename, FA_READ);							//打开文件,权限为只读
    if(retSD)															//返回值不为0(出现问题)
        printf(" open file error : %d\r\n",retSD);					//打印问题代码
    else
        printf(" open file sucess!!! \r\n");
     
   /*-6- 读取txt文件数据*/
    retSD = f_read(&fil, rtext, sizeof(rtext), (UINT*)&bytesread);		//读取文件内容放到rtext中
    if(retSD)															//返回值不为0(出现问题)
        printf(" read error!!! %d\r\n",retSD);						//打印问题代码
    else
    {
        printf(" read sucess!!! \r\n");
        printf(" read Data : %s\r\n",rtext);							//打印读取到的内容
    }
     
    /*-7- 关闭文件*/
    retSD = f_close(&fil);												//关闭该文件
    if(retSD)  															//返回值不为0(出现问题)
        printf(" close error!!! %d\r\n",retSD);						//打印问题代码
    else
        printf(" close sucess!!! \r\n");
     
    /*##-8- 读写一致性检测 ############*/
    if(bytesread == byteswritten)										//如果读写位数一致
    { 
        printf(" FatFs is working well!!!\r\n");						//打印文件系统工作正常
    }
		else
		printf("Working of FatFs was wrong!\r\n");

		
		
 }    
/* USER CODE END 4 */

在程序里面是不用调用相关的使能函数的,新版本的CUBEMX已经帮你完成这件事了。
然后这里使用了f_mount函数,其实这个是FATFS的相关函数,是进行相应操作的函数,下面是一些介绍:(在生成的ff.h文件中有)

/* FatFs module application interface                           */

FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);				/* Open or create a file */
FRESULT f_close (FIL* fp);											/* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br);			/* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw);	/* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs);								/* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp);										/* Truncate the file */
FRESULT f_sync (FIL* fp);											/* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path);						/* Open a directory */
FRESULT f_closedir (DIR* dp);										/* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno);							/* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern);	/* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno);							/* Find next file */
FRESULT f_mkdir (const TCHAR* path);								/* Create a sub directory */
FRESULT f_unlink (const TCHAR* path);								/* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new);	/* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno);					/* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask);			/* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno);			/* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path);								/* Change current directory */
FRESULT f_chdrive (const TCHAR* path);								/* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len);							/* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs);	/* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn);	/* Get volume label */
FRESULT f_setlabel (const TCHAR* label);							/* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf);	/* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt);					/* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt);			/* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len);	/* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, void* work);			/* Divide a physical drive into some partitions */

然后由于使用了这些函数,在程序中可以返回相应的返回值,通过返回值就可以更快的锁定错误了。

/* File function return code (FRESULT) */

typedef enum {
	FR_OK = 0,				/* (0) Succeeded */
	FR_DISK_ERR,			/* (1) A hard error occurred in the low level disk I/O layer */
	FR_INT_ERR,				/* (2) Assertion failed */
	FR_NOT_READY,			/* (3) The physical drive cannot work */
	FR_NO_FILE,				/* (4) Could not find the file */
	FR_NO_PATH,				/* (5) Could not find the path */
	FR_INVALID_NAME,		/* (6) The path name format is invalid */
	FR_DENIED,				/* (7) Access denied due to prohibited access or directory full */
	FR_EXIST,				/* (8) Access denied due to prohibited access */
	FR_INVALID_OBJECT,		/* (9) The file/directory object is invalid */
	FR_WRITE_PROTECTED,		/* (10) The physical drive is write protected */
	FR_INVALID_DRIVE,		/* (11) The logical drive number is invalid */
	FR_NOT_ENABLED,			/* (12) The volume has no work area */
	FR_NO_FILESYSTEM,		/* (13) There is no valid FAT volume */
	FR_MKFS_ABORTED,		/* (14) The f_mkfs() aborted due to any problem */
	FR_TIMEOUT,				/* (15) Could not get a grant to access the volume within defined period */
	FR_LOCKED,				/* (16) The operation is rejected according to the file sharing policy */
	FR_NOT_ENOUGH_CORE,		/* (17) LFN working buffer could not be allocated */
	FR_TOO_MANY_OPEN_FILES,	/* (18) Number of open files > _FS_LOCK */
	FR_INVALID_PARAMETER	/* (19) Given parameter is invalid */
} FRESULT;

这是返回值列表,一般出现什么问题都会在这里反馈。
到这里基本就把程序搞定了。接下来就是验证了。

三、下载验证

在插上SD卡之后,打开串口调试助手,设置与串口1相应的波特率,通电。
结果如下:
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
噔噔噔噔~发现了吗?第一次读写是成功的,但是我第二次读写就死活不成功。这个是为什么呢?这个是因为我读写之后把SD卡拔下来,插到电脑上看看是否真的成功,其结果如下:
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
然后就一脸全懵,不知道咋回事,从网上找原因,然后看到很多大佬说是因为没有使能到SD卡,但是我一开始是能用的,然后我就加了使能程序,但是并不能正常工作。
这是根据网上所提到的各种原因疯狂调试的结果:
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
在掉了很多头发之后,发现返回了返回值,然后我就在程序中差,发现返回值为3和9基本都是废话,但是发现打开文件的时候返回了一个1,返回1的意思是:

	FR_DISK_ERR,			/* (1) A hard error occurred in the low level disk I/O layer */

就是说硬件的低级IO出现了错误,一瞬间我以为这个SD卡被我读写一次就报废了,心都要凉了。然后下载原子的例程发现并没有坏,就又懵了。然后就继续到处查资料咯。然后在 开发指南上发现了,关于分频的设置。虽然原子的 教程上是说支持48MHZ,但是根据不同的数据宽度,其频率是不一样的,再加上这个系统版本还不能超过20MHZ,所以根据这两个条件,我大胆的推测了一下,1bit的可能最高可以达到48MHZ,而4bit 的估计只能达到12MHZ,即

1bit 4bit
48MHZ 12MHZ

所以在经过时钟树的调整之后
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
重新生成工程。在经过编译下载之后其结果:
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
就能够疯狂的读写了。所以关于SD卡的读写,最主要的就是分频 的设置了。
然后,关于FATFS,就读写而言,转换成简单的逻辑就是,生成文件,然后打开生成的文件,对其进行读或者写操作(只能一种操作),然后关闭文件,这样就完成了一次操作了。要进行下一次操作时,就重复上一次的操作,只是修改中间的操作函数 即可。

然后插SD卡,
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
在电脑能正常查看到,到这里就结束了,不过多说一句,这个可以通过接收数据转存到设定的接收buff中,但是由于工作性质问题,可能存在多次读写,而不是每次都新建txt文件,所以就可以利用下面的函数:

FRESULT f_lseek (FIL* fp, FSIZE_t ofs);								/* Move file pointer of the file object */

这个函数就是移动指针,因为
文件对象结构体(FIL类型):存放文件的相关信息,打开关闭读写文件等操作时需要使用其指针
目录对象结构体(DIR类型):存放目录的相关信息,对目录操作时需要其指针
文件状态结构体(FILINFO类型):存放文件的大小属性文件名等信息
文件系统对象结构体(FATFS类型)
所以在操作的时候需要操作指针。具体的实现函数如下

/***增加数据,打开文件***/
		retSD = f_open(&fil, filename, FA_CREATE_ALWAYS | FA_WRITE ); 
		FileSize = f_size(&fil);  
		
		if(retSD)
			 printf(" open file error : %d\r\n",retSD);					//打印问题代码
		else
       printf(" open file sucess!!! \r\n");
		
		/***** 增加 数据****/
		if(FileSize>sizeof(wtext))
		retSD=f_lseek(&fil,f_size(&fil));	
		if(retSD)															//返回值不为0(出现问题)
        printf(" Increase error!!! %d\r\n",retSD);						//打印问题代码
    else
    {
        printf(" Increase sucess!!! \r\n");
        //printf(" Increase Data : %s\r\n",rtext);							//打印读取到的内容
    }
		
		
		/*******写入数据******/
		retSD=f_write(&fil, wtext, sizeof(wtext), &byteswritten);
		if(retSD)															//返回值不为0(出现问题)
        printf(" write file error : %d\r\n",retSD);							//返回值不为0(出现问题)
    else
    {
        printf(" write file sucess!!! \r\n");
        printf(" write Data : %s\r\n",wtext);							//打印写入的数据
    }
		
    /*****写完之后关闭文件******/
		retSD=f_close(&fil);//一定要关闭	
		if(retSD)  															//返回值不为0(出现问题)
        printf(" close error!!! %d\r\n",retSD);						//打印问题代码
    else
        printf(" close sucess!!! \r\n");

这样就可以二次写入数据了。
验证结果如下:
由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入

以上就是今天经历被疯狂摧残个掉头发的一个下午之后的结果。有什么不对的地方请大佬们不吝指正,谢谢大家!

***欢迎转载,注明出处即可!***