1、前言
上一篇文章我讲述了在SDIO模式下读取SD卡,在文章最后说了需要注意的地方,同时也是裸机下利用SDIO模式的不足,今天给大家讲一讲在FatFs文件系统下读取SD卡的该如何做,以及相比于裸机下SDIO模式的优势。
测试环境:
STM32F103ZET6(含卡槽)
STM32CubeMX 5.6.0
Keil 5.33
16G 或 32G的SD-Micro卡
2、FatFs系统简介
FatFs文件系统是用于嵌入式的文件系统,和电脑上的资源管理器功能相似,说的通俗点就是创建文件,打开文件,修改文件的功能。FatFs文件系统作为一个中间层,可以为用户提供一个操作SD卡的环境,其主要作用就是利用其自身的架构,让用户直接操作文件系统中定义的函数就可以对SD卡实现擦除读写等功能。我们可以看一下FatFs官网上的一张图。
可以看到顶层的应用层就是我们所能看见并且所能操作的东西,我们操作FatFs文件系统中的API函数通过FatFs这个中间层,来直接驱动硬件层。这就是使用FatFs文件系统的好处,方便简单,不需要了解硬件的结构以及使用说明,对硬件发动的所有指令均由文件系统中封装的函数写好了。
3、利用STM32CubeMX创建工程
打开CubeMX后选择单片机型号,接下来对系统内核进行配置,如下图所示,RCC选用外部的高速时钟
SYS的Debug选项选择SW,基础时间源选用TIM1定时器,如下图配置
SDIO的配置选择1位宽线程,这一点很重要,我之前选择4位宽线程,挂载系统时总是报出GPIO硬件层错误,困扰了我好几天。SDIOCLK的数值可以参考我的上一篇文章HAL库 CubeMX STM32通过SDIO模式实现对SD卡的读写的配置。本此测试SDIO配置如下
使能串口1与电脑进行通讯,如下图
使能FatFs,选用SD Card模式,语言设置可以设置成汉语,如果生成工程编译后报出内存溢出错误,可以改为英语,使能栈上可以使用动态的Buffer,最终配置如下图所示
时钟树配置如下
生成工程前配置如下,根据自己的需求来定,注意增加堆栈大小
生成工程后便可添加代码。
4、函数介绍
我们首先看一下生成的Keil工程中的中间层文件,如下图
图中两个比较重要的文件我已经标出,一个是ff.c文件,这里面定义了FatFs文件系统中的API函数;另一个是diskio.c文件,这个文件中的函数主要和SD卡硬件打交道,如果不用CubeMX工具导入FatFs文件系统而是自己手动移植,那么这个文件是需要自己手动修改的,但谁让我们用的CubeMX呢,ST的工程师已经做好了改动,此文件如果由CubeMX生成则不需要修改,可以直接拿来用,确实方便了不少,方便萌新上手。
接下来我再带大家看几个我们经常用到的函数,在ff.c文件中我们可以看到
挂载系统的函数,我们要向用文件系统,首先要在SD卡中挂载一个文件系统,这就像我们在电脑创建一个D盘一样,函数如下
挂载好系统后,可以对系统进行格式化处理,格式化函数如下
格式化后便可以在我们挂载的区域进行创建文件以及读写文件的操作
创建文件函数如下
f_open函数咋一看不是打开文件的函数吗,怎么就是创建文件了,其实这个函数比较特别,既可以打开文件,也可以创建文件,这取决于此函数的一个形参为多少。
写函数如下
读取文件的函数如下
打开文件并对文件读写完之后,我们别忘了关闭文件,如果不关闭文件,再打开别的文件是会出现问题,关闭文件函数如下
以上便是我们比较常用到的函数了,当然还有一个函数比较重要,我要给大家强调一下,看过我上一篇文章的一定知道我说过在不用FatFs文件系统时,读取文件函数的地址只能从之前写入的地址开始读,不能从中间开始,因为中间没有地址供函数识别。而FatFs文件系统不一样,它提供了一个函数,可以在文件的头地址基础上偏移一定的地址来读取文件,偏移量单位为字节,函数如下所示
这个函数可以偏移任意字节来读取文件,如果偏移量超过了你数据的总字节数,那么还能扩展你的文件大小,扩展部分均为0。是不是超级方便,这也是文件系统的优势所在。
以上的函数我就不具体介绍了,大家自行去了解,各种形参是什么可以去官网学习,官网链接在这: FatFs官网。了解了基础函数之后,我们就可以编写程序了。
有需要注意的一点是,目前官网是最新的版本,而CubeMX生成的版本不是最新的,版本可以在ff.c文件最上面找到,如下图所示
这一版是2015年的。不同版本函数可能稍有不同,需要注意。
5、代码编写
在/* USER CODE BEGIN Includes /与/ USER CODE END Includes */之间添加如下头文件
// 声明一些头文件
#include "stdio.h"
#include "ctype.h"
#include "string.h"
在/* USER CODE BEGIN PV /与/ USER CODE END PV */之间添加如下变量
FIL file;
UINT br,bw;
uint16_t rBuffer[20]; //将SD卡的数据,读入到缓存区
uint16_t WBuffer[20] ={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
再在/* USER CODE BEGIN 2 /与/ USER CODE END 2 */之间添加如下代码
#if 1 //挂载系统
retSD = f_mount(&SDFatFS,"0:",1);
switch(retSD)
{
case FR_NO_FILESYSTEM : //如果没有文件系统,则进行格式化
{
retSD = f_mkfs("0:",0,0);
if(retSD != FR_OK ) //如果格式化不成功,报错
{
printf("mkfs error %d\r\n",retSD);
}
}
break;
case FR_OK :
{
printf("mount successful\r\n");
#if 1 //创建文件并写数据
retSD = f_open(&file,"0:State.txt",FA_CREATE_ALWAYS | FA_WRITE); //判断创建或打开文件是否成功
if(retSD == FR_OK)
{
printf("open successful\r\n");
retSD = f_write(&file,(const void *)State,sizeof(WBuffer),&bw);
if(retSD == FR_OK) //判断写入数据是否成功
{
printf("write successful\r\n");
f_close(&file); //关闭文件
}
else
{
printf("write error %d\r\n",retSD);
}
}
else
{
printf("open error %d\r\n",retSD);
}
#endif
}
break;
default :
printf("other error %d\r\n",retSD);
break;
}
#if 0 //移位读取SD卡数据
retSD = f_open(&file,"0:Temperature_Probe_1.txt",FA_OPEN_EXISTING | FA_READ);
if(retSD == FR_OK)
{
printf("open successful\r\n");
retSD = f_lseek(&file,f_tell(&file)+ 4); //这个函数用于移位,f_tell函数是获取文件首地址的函数
if(retSD == FR_OK)
{
retSD = f_read(&file,(void *)rBuffer,6,&br);
if(retSD == FR_OK) //判断读取文件操作是否成功
{
printf("read successful\r\n");
uint8_t j = 0;
for(uint16_t i = 0;i < 2000;i++) //次for循环用于将读到的数据打印到电脑
{
j++;
printf("%d ",rBuffer[i]);
if(j == 10)
{
printf("\r\n");
j = 0;
}
}
f_close(&file);
}
}
else
{
printf("open error %d\r\n",retSD);
}
}
#endif
#if 1 //读取SD卡中数据
retSD = f_open(&file,"0:State.txt",FA_OPEN_EXISTING | FA_READ);
if(retSD == FR_OK)
{
printf("open successful\r\n");
retSD = f_read(&file,(uint8_t *)rBuffer,sizeof(rBuffer),&br);
if(retSD == FR_OK)
{
printf("read successful\r\n");
uint8_t j = 0;
for(uint16_t i = 0;i < 2000;i++)
{
j++;
printf("%d ",rBuffer[i]);
if(j == 10)
{
printf("\r\n");
j = 0;
}
}
f_close(&file);
}
else
{
printf("read error %d\r\n",retSD);
}
}
else
{
printf("open error %d\r\n",retSD);
}
#endif
#endif
以上便是本次讲解的主要内容,如果有不合适准确的地方希望大家能够指出,谢谢大家赏脸。