由STM32CUBEMX生成的工程通过FATFS单次读写SD卡+连续写入
在写博客之前想解释一下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更新了,好像之前不是这个名称的。
在这里选择了SD 4 bit Wide bus(注意这里哦)
在写SD卡的是通过块传输数据的,对这个不理解的可以自行学习。
由于需要使能中断,因此要调整相应的优先级,才能正常工作。
其优先级为:
功能 | 优先级 |
---|---|
SDIO | 5 |
DMA | 6 |
然后就是使能SD卡了,关于SD卡的驱动,在CUBEMX的库里面是自动生成的,所以其实是不用理的。
在网上很多关于SD卡的博客中,都有写出需要另外单独使能SD卡,但是我实际应用中是不需要的,为什么我也不知道。我看了大佬们的博客,觉得可能是因为之前的CUBEMX版本的问题,新版本的CUBEMX是已经生成使能了。所以不用理。
下面是时钟树的配置。
根据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相应的波特率,通电。
结果如下:
噔噔噔噔~发现了吗?第一次读写是成功的,但是我第二次读写就死活不成功。这个是为什么呢?这个是因为我读写之后把SD卡拔下来,插到电脑上看看是否真的成功,其结果如下:
然后就一脸全懵,不知道咋回事,从网上找原因,然后看到很多大佬说是因为没有使能到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 |
所以在经过时钟树的调整之后
重新生成工程。在经过编译下载之后其结果:
就能够疯狂的读写了。所以关于SD卡的读写,最主要的就是分频 的设置了。
然后,关于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");
这样就可以二次写入数据了。
验证结果如下:
以上就是今天经历被疯狂摧残个掉头发的一个下午之后的结果。有什么不对的地方请大佬们不吝指正,谢谢大家!
***欢迎转载,注明出处即可!***