STM32+雷龙SD NAND(贴片SD卡)完成FATFS文件系统移植与测试

举报
DS小龙哥 发表于 2022/12/19 09:50:40 2022/12/19
【摘要】 在STM32项目开发中,经常会用到存储芯片存储数据。 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复;在存储芯片里也会存放很多资源文件。比如,开机音乐,界面上的菜单图标,字库文件,方便设备开机加载。

一、前言

在STM32项目开发中,经常会用到存储芯片存储数据。 比如:关机时保存机器运行过程中的状态数据,上电再从存储芯片里读取数据恢复;在存储芯片里也会存放很多资源文件。比如,开机音乐,界面上的菜单图标,字库文件,方便设备开机加载。

为了让单片机更加方便的读写这些资源文件,通常都会加文件系统,如果没有文件系统,直接读取写扇区的方式,对数据不好管理。 这篇文章就手把手教大家,在STM32上完成FATFS文件系统的移植;主控芯片采用STM32F103ZET6, 存储芯片我这里采用(雷龙) CS创世 SD NAND 。 SD NAND 简单来说就是贴片式SD卡,使用起来与普通的SD卡一样,简单的区别就是:比TF卡稳定,比eMMC便宜。 下面章节里会详细介绍下 CS创世 SD NAND。

下面是CS创世 SD NAND 与STM32开发的板的接线实物图:

image-20221206191450735

image-20221206191527228

这是读写扇区测试的结果:

image-20221206193549509

二、SD NAND 介绍

我当前使用的SD NAND型号是,CSNP32GCR01-AOW,容量是4GB。

image-20221201164524580

下面是通过编写STM32代码读取的存储信息:

Card Type:SDHC V2.0
Card ManufacturerID:102
Card RCA:5000
Card Capacity:3696 MB
Card BlockSize:512

芯片的详细参数如下:

1】不用写驱动程序自带坏块管理
【2】尺寸小巧,简单易用,兼容性强,稳定可靠,固件可定制,LGA-8封装
【3】标准SDIO接口,兼容SPI,兼容拔插式TF/SD卡,可替代普通TF/SD卡
【4】尺寸6.2x8mm,直接贴片,不占空间
【5】内置平均读写算法,通过1万次随机掉电测试
【6】耐高低温,机贴手贴都非常方便
【7】速度级别Class10(读取速度23.5MB/S写入速度12.3MB/S)
【8】支持标准的SD 2.0协议,用户可以直接移植标准驱动代码,省去了驱动代码编程环节。支持TF卡启动的SOC都可以用SD NAND9】比TF卡稳定,比eMMC便宜

**下面是芯片的实物图: ** 这是官网申请的样品,焊接了转接板,可以直接插在SD卡卡槽上测试。 最终选型之后,设计PCB板时,设计接口,直接贴片上去使用,非常稳定,抖动也不会导致,外置卡TF卡这种容易松动的问题。

这是雷龙的官网: http://www.longsto.com/product/35.html

image-20221201170132109

image-20221201163718601

image-20221201163735081

image-20221201163748859

三、编写SD NAND驱动代码

SD NAND 的驱动代码与正常的SD卡协议是一样的,支持标准的SD 2.0协议,下面我就直接贴出写好的驱动代码。

包括了模拟SPI,硬件SPI,SDIO等3种方式,完成对SD NAND 的读写。我当前使用的主控板子是STM32F103ZET6,如果你使用的板子不是这一款,可能还是其他的CPU也没关系;我这里直接贴出了SPI模拟时序的驱动代码,可以直接移植到任何单片机上使用,代码拷贝过去也只需要修改GPIO口即可,非常方便。

四、移植FATFS文件系统

前面第3章,完成了SD NAND的驱动代码编写,这一章节实现FATFS文件的移植。

4.1 FATFS文件系统介绍

(1)介绍

FatFs 是一种完全免费开源的 FAT 文件系统模块,专门为小型的嵌入式系统而设计。它完全用标准C 语言编写,所以具有良好的硬件平台独立性,可以移植到 8051、 PIC、 AVR、 SH、 Z80、 H8、 ARM 等系列单片机上而只需做简单的修改。它支持 FATl2、 FATl6 和 FAT32,支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对 8 位单片机和 16 位单片机做了优化。

(2)特点

【1】Windows兼容的FAT文件系统
【2】不依赖于平台,易于移植
【3】代码和工作区占用空间非常小
【4】多种配置选项
【5】多卷(物理驱动器和分区)
【6】多ANSI/OEM代码页,包括DBCS
【7】在ANSI/OEM或Unicode中长文件名的支持
【8】RTOS的支持
【9】多扇区大小的支持
【10】只读,最少API,I/O缓冲区等等

(3)移植性

fatfs模块是ANSI C(C89)编写的。 没有平台的依赖, 编译器只要符合ANSI C标准就可以编译。

fatf模块假设大小的字符/短/长8/16/32位和int是16或32位。 这些数据类型在integer.h文件中定义。这些数据类型在大多数的编译器中定义都符合要求。 如果现有的定义与编译器有任何冲突发生时,需要自己解决。

4.2 下载源码

下载地址:http://elm-chan.org/fsw/ff/00index_e.html

image-20221201191123204

FATFS有两个版本,一个大版本,一个小版本。小版本主要用于8位机(内存小)使用。

下载图:

image-20221201191144911

4.3 源码结构介绍

将下载的源码解压后可以得到两个文件夹: doc 和 src。 doc 里面主要是对 FATFS 的介绍(离线文档—英文和日文),而 src 里面才是我们需要的源码。

其中,与平台无关的是:

ffconf.h     FATFS配置文件
ff.h        应用层头文件
ff.c        应用层源文件
diskio.h    硬件层头文件
interger.h  数据类型定义头文件
option      可选的外部功能(比如支持中文等)

与平台相关的代码:

diskio.c     底层接口文件(需要用户提供)

FATFS 模块在移植的时候,我们一般只需要修改 2 个文件,即 ffconf.h 和 diskio.c。

FATFS模块的所有配置项都是存放在 ffconf.h 里面,我们可以通过配置里面的一些选项,来满足自己的需求。

image-20221201191424011

最顶层是应用层,使用者无需理会 FATFS 的内部结构和复杂的 FAT 协议,只需要调用FATFS 模块提供给用户的一系列应用接口函数,如 f_open, f_read, f_write 和 f_close 等,就可以像在 PC 上读写文件那样简单。

中间层 FATFS 模块, 实现了 FAT 文件读/写协议。 FATFS 模块提供的是 ff.c 和 ff.h。除非有必要,使用者一般不用修改,使用时将头文件直接包含进去即可。

需要我们编写移植代码的是 FATFS 模块提供的底层接口,它包括存储媒介读/写接口 ( disk、I/O) 和供给文件创建修改时间的实时时钟。

4.4 下载源码并加入到工程

先准备好一个有SD NAND驱动代码的STM32工程(代码前面第3章已经贴了),接着就完成下面的步骤。

image-20221201191530152

打开KEIL工程,添加FATFS文件源码:

image-20221201191606087

image-20221201191619365

加入.h文件主要是方便配。cc936.c 用于支持中文。

4.5 修改代码进行移植

(1)修改diskio.c文件

image-20221201191824668

注释掉现在不需要的用到的文件,因为我们现在用的是SD卡,与USB,ATA,MMC卡没关系。

并加入一个新的宏 :

#define SD 0

定义SD卡的物理驱动器号为0。

修改 disk_status函数,该函数主要是用来获取磁盘状态。现在未用到,可以直接函数体内代码删除。

修改截图:

image-20221201191925588

代码示例:

#include "diskio.h"		  /* fatf底层API */
#include "sd.h"		      /* SD卡驱动头文件  */
/* 定义每个驱动器的物理驱动器号*/
#define SD    0

/*-----------------------------------------------------------------------*/
/* 获取设备(磁盘)状态                                                     */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* 物理驱动识别 */
)
{
   return 0;  //该函数现在无需用到,直接返回0
}

修改disk_initialize函数,添加SD卡的初始化,其他不用到的代码直接删掉,该函数成功返回0,失败返回1。

修改截图:

image-20221201192033155

代码示例:

/*-----------------------------------------------------------------------*/
/* 初始化磁盘驱动                                                        */
/*-----------------------------------------------------------------------*/

DSTATUS disk_initialize (
	BYTE pdrv				/* 物理驱动识别 */
)
{
	DSTATUS stat;
	int result;

	switch (pdrv) {
	case SD :            //选择SD卡
		stat=SD_Init();   //初始化SD卡-用户自己提供
	}
	if(stat)return STA_NOINIT;  //磁盘未初始化
	return 0; //初始化成功
}

修改disk_read函数,加入SD卡读任意扇区的函数(需要用户自己提供),其他不用到的选项可以删掉。

image-20221201192136957

修改代码如下:

/*-----------------------------------------------------------------------*/
/* 读扇区                                                                */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
	BYTE pdrv,		/* 物理驱动编号 - 范围0-9*/
	BYTE *buff,		/* 数据缓冲区存储读取数据 */
	DWORD sector,  	/* 扇区地址*/
	UINT count		/* 需要读取的扇区数*/
)
{
	DRESULT res;
	int result;
	switch (pdrv) {
		case SD:
		  res=SD_Read_Data((u8*)buff,sector,count);  //读SD扇区函数--用户提供
		  return res; //在此处可以判错误
	}
	return RES_PARERR;  //无效参数
}

修改disk_write 函数,添加写扇区函数:

image-20221201192230352

代码:

/*-----------------------------------------------------------------------*/
/* 写扇区                                                                */
/*-----------------------------------------------------------------------*/

#if _USE_WRITE
DRESULT disk_write (
	BYTE pdrv,			  /* 物理驱动号*/
	const BYTE *buff,	       /* 要写入数据的首地址 */
	DWORD sector,		   /* 扇区地址 */
	UINT count			   /* 扇区数量*/
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
		case SD:
			res=SD_Write_Data((u8*)buff,sector,count); //写入扇区
		  return res;
	}
	return RES_PARERR;  //无效参数
}
#endif

修改disk_ioctl 函数,填充ioctl命令功能。这些功能是标准的命令,在diskio.h有定义。

image-20221201192311549

代码如下:

/*-----------------------------------------------------------------------*/
/* 其他函数                                              */
/*-----------------------------------------------------------------------*/

#if _USE_IOCTL
DRESULT disk_ioctl (
	BYTE pdrv,		/* 物理驱动号 */
	BYTE cmd,		  /* 控制码  */
	void *buff		/* 发送/接收数据缓冲区地址 */
)
{
	DRESULT res;
	int result;

	switch (pdrv) {
		case SD:
			 switch(cmd)
			 {
				 case CTRL_SYNC:      //等待写过程
					 SD_CS(0);          //选中SD卡
					 if(SD_Wait_Ready())result = RES_ERROR;/*等待卡准备好*/
				     else res = RES_OK;     //成功
					 SD_CS(1);            //释放SD卡
                        break;	 
				 
			 case GET_SECTOR_SIZE://获取扇区大小
			   *(DWORD*)buff = 512; 
		        res = RES_OK;     //成功
		        break;	
				 
			 case GET_BLOCK_SIZE:    //获取块大小
				*(WORD*)buff = 8;      //块大小(扇区为单位),一块等于8个扇区
		         res = RES_OK;
		         break;
				 
			 case GET_SECTOR_COUNT: //获取总扇区数量
		        *(DWORD*)buff = SD_Get_Sector_Count();
		        res = RES_OK;
		        break;
				 
			default:  //命令错误
		        res = RES_PARERR;
		        break;
			 }
		return res;
	}
	return RES_PARERR;  //返回状态
}

(2)修改ffconf.h文件

需要注意的一些宏配置:

#define _CODE_PAGE	936   //采用中文GBK编码       (64行)
#define	_USE_LFN	3     //动态的堆上工作             (93行)
#define	_MAX_LFN	255   /*_USE_LFN选项开关LFN(长文件名)特性。
#define _VOLUMES	1     /* 支持的磁盘数量(逻辑驱动器)。 */   (142)
#define	_MIN_SS		512                                  (165)
#define	_MAX_SS		512   /*这些选项配置支持扇区大小的范围。(512,1024, 4096*/ 
#define _FS_NORTC	    0    /*启用RTC时间功能*/   (202)
#define _NORTC_MON	    1
#define _NORTC_MDAY	1
#define _NORTC_YEAR	2015 //年  
/*需要实现:get_fattime()函数*/

ffconf.h 文件源码:

/*---------------------------------------------------------------------------/
/  FatFs - FAT文件系统模块配置文件  R0.11a (C)ChaN, 2015
/---------------------------------------------------------------------------*/

#define _FFCONF 64180	/* 版本识别*/

/*---------------------------------------------------------------------------/
/ 功能配置
/---------------------------------------------------------------------------*/

#define _FS_READONLY	0
/* 这个选项开关只读配置。(0:读/写或1:只读)   
/只读配置删除编写API函数,f_write(),f_sync(),   
/ f_unlink(),f_mkdir(),f_chmod(),f_rename(),f_truncate(),f_getfree()   
/写和可选的功能. */


#define _FS_MINIMIZE	0
/*此选项定义删除一些基本的API函数极小化水平。  
/   
/ 0:所有基本功能都是激活的。  
/ 1:f_stat(),f_getfree(),f_unlink(),f_mkdir(),f_chmod(),f_utime(),   
/ f_truncate()和f_rename()函数删除。  
/ 2:f_opendir(),f_readdir()和f_closedir()中除了1。  
/ 3:f_lseek()函数删除除了2。*/


#define	_USE_STRFUNC	1
/*这个选项开关字符串函数,f_gets(),f_putc(),f_puts()和 
/ f_printf()。  
/   
/ 0:禁用字符串函数。  
/ 1:启用没有LF-CRLF转换。  
/ 2:启用LF-CRLF(回车换行)转换。*/


#define _USE_FIND		0
/*这个选项开关过滤目录读取特性和相关功能,   
/ f_findfirst()和f_findnext()。(0:禁用或1:启用)*/


#define	_USE_MKFS		1
/* 这个选项开关f_mkfs()函数。(0:禁用或1:启用) */


#define	_USE_FASTSEEK	1
/* 这个选项开关快速寻求功能。(0:禁用或1:启用) */


#define _USE_LABEL		1
/*   磁盘卷标这个选项开关功能,f_getlabel()和f_setlabel()。  
/(0:禁用或1:启用) */


#define	_USE_FORWARD	0
/*  这个选项开关f_forward()函数。(0:禁用或1:启用)   
/启用它,也_FS_TINY需要设置为1. */


/*---------------------------------------------------------------------------/
/ 语言环境和名称空间配置
/---------------------------------------------------------------------------*/

#define _CODE_PAGE	936  //采用中文GBK编码
/* 这个选项指定OEM代码页在目标系统上使用。  
/不正确的代码页的设置会导致文件打开失败.
/
/   1   - ASCII (没有扩展字符。Non-LFN cfg。只有)
/   437 - U.S.
/   720 - 阿拉伯语
/   737 - 希腊语;
/   771 - 阿富汗
/   775 - 波罗的海
/   850 - 拉丁1
/   852 - 拉丁2
/   855 - 西里尔字母
/   857 - 土耳其语
/   860 - 葡萄牙语
/   861 - 冰岛语
/   862 - 希伯来人
/   863 - 加拿大法语
/   864 - 阿拉伯语
/   865 - 日耳曼民族的
/   866 - 俄语
/   869 - 希腊 2
/   932 - 日本人 (DBCS)
/   936 - 简体中文(DBCS)
/   949 - 韩国人 (DBCS)
/   950 - 繁体中文(DBCS)
*/


#define	_USE_LFN	3 //动态的堆上工作
#define	_MAX_LFN	255
/*_USE_LFN选项开关LFN(长文件名)特性。
/
/ 0:禁用LFN特性。_MAX_LFN没有影响。  
/ 1:启用LFN BSS静态工作缓冲区。总是不是线程安全的。  
/ 2:启用LFN与动态缓冲栈上的工作。  
/ 3:使LFN与动态缓冲区在堆上工作。
/
/  当启用LFN(长文件名)特性,Unicode(选项/ unicode.c)必须处理功能  
/被添加到项目中。LFN工作缓冲区占用(_MAX_LFN + 1)* 2字节。  
/当使用堆栈缓冲区,照顾堆栈溢出。当使用堆  
/工作缓冲区内存,内存管理功能,ff_memalloc()和  
/ ff_memfree(),必须添加到项目中。 */


#define	_LFN_UNICODE	0 
/* 这个选项开关字符编码的API。(0:ANSI / OEM或1:Unicode)   
路径名/使用Unicode字符串,并设置_LFN_UNICODE启用LFN特性  
/1。这个选项也会影响行为的字符串的I / O功能。
*/


#define _STRF_ENCODE	3
/* 当_LFN(长文件名)_UNICODE是1,这个选项选择文件的字符编码  
/通过字符串读取/写入I /O功能,f_gets(),f_putc(),f_puts和f_printf().
/
/  0: ANSI/OEM
/  1: UTF-16LE
/  2: UTF-16BE
/  3: UTF-8
/
/ 当_LFN_UNICODE = 0时,该选项没有影响。*/

#define _FS_RPATH	0
/* 这个选项配置相对路径的功能。  /   
/ 0:禁用相对路径特性和删除相关功能。  
/ 1:启用相对路径特性。f_chdir()和f_chdrive()是可用的。  
/ 2:f_getcwd()函数可用除了1。  /   
/注意,目录项读通过f_readdir()这个选项。 
*/

/*---------------------------------------------------------------------------/
/ 驱动/卷配置
/---------------------------------------------------------------------------*/


#define _VOLUMES	1
/* 支持的磁盘数量(逻辑驱动器)。 */


#define _STR_VOLUME_ID	0
#define _VOLUME_STRS	"RAM","NAND","CF","SD1","SD2","USB1","USB2","USB3"
/* STR_VOLUME_ID选项开关卷ID字符串功能。  
/当_STR_VOLUME_ID设置为1时,也可以使用预先定义的字符串在路径名称/数量。
为每个_VOLUME_STRS定义驱动ID字符串  
/逻辑驱动器。条目的数量必须等于_VOLUMES。有效字符  
/驱动ID字符串:a - z和0 - 9。*/


#define	_MULTI_PARTITION	0
/*  这个选项开关多分区的特性。在默认情况下(0),每个逻辑驱动器  
/号绑定到相同的物理驱动器号  
/物理驱动器将被安装。当启用分区特性(1),   
/每个逻辑驱动器号是绑定到任意物理驱动器和分区  
/中列出VolToPart[]。还f_fdisk()函数可用. */


#define	_MIN_SS		512
#define	_MAX_SS		512
/*  这些选项配置支持扇区大小的范围。(512,1024,   
/ 2048或4096)总是为大多数系统设置两个512,卡和所有类型的内存  
/硬盘。但是可能需要更大的值为车载闪存和一些  
/类型的光学媒体。当_MAX_SS大于_MIN_SS,fatf配置  
/变量扇区大小和GET_SECTOR_SIZE命令必须执行  disk_ioctl()函数. */


#define	_USE_TRIM	0
/* 这个选项开关ATA-TRIM特性。(0:禁用或1:启用)   
/启用削减特性,也应该实现CTRL_TRIM命令  
/ disk_ioctl()函数。*/


#define _FS_NOFSINFO	0
/*   
如果你需要知道正确的自由空间体积FAT32,设置一些0   
/选项,f_getfree()函数在第一次后体积将迫使山  
/全脂肪扫描。位1控制使用的集群数量分配。  /   
/ bit0 = 0:使用免费的集群计算FSINFO如果可用。  
/ bit0 = 1:不相信自由FSINFO集群计算。  
/ bit1 = 0:最后使用集群可用FSINFO如果数量分配。  
/ bit1 = 1:不相信最后分配FSINFO集群数量.
*/



/*---------------------------------------------------------------------------/
/ 系统配置列表
/---------------------------------------------------------------------------*/

#define	_FS_TINY	0
/* 这个选项开关小缓冲区配置。(0:正常或1:小)   
/小配置,文件对象的大小(FIL)_MAX_SS减少字节。而不是私人部门从文件对象,缓冲了  
/公共部门缓冲文件系统中的对象(fatf)是用于该文件  
/数据传输. */


#define _FS_NORTC	0
#define _NORTC_MON	1
#define _NORTC_MDAY	1
#define _NORTC_YEAR	2015 //年
/* _FS_NORTC选项开关时间戳的特性。如果系统没有/
 RTC函数或不需要有效的时间戳,_FS_NORTC 1设置为禁用/
 时间戳的特性。所有对象修改fatf将有一个固定的时间戳。/
  固定的时间定义为_NORTC_MON _NORTC_MDAY _NORTC_YEAR。  

/当启用时间戳特性(_FS_NORTC = = 0),需要实现get_fattime()函数。  /
 添加到项目RTC读当前时间形式。_NORTC_MON,   /
_NORTC_MDAY和_NORTC_YEAR没有效果。  
/这些选项没有影响只读配置(_FS_READONLY = = 1)。 */


#define	_FS_LOCK	0
/*  _FS_LOCK选项开关控制复制的文件打开的文件锁定功能  
/和非法操作打开对象。这个选项_FS_READONLY时必须是0   
/是1。  /   
/ 0:禁用文件锁定功能。为了避免体积腐败、应用程序  
/应该避免非法打开,删除和重命名的开放对象。  
/ > 0:启用文件锁定功能。值定义了多少文件/子目录  
可以同时打开的/文件锁的控制之下。注意,这个文件独立于re-entrancy /锁功能。 */



#define _FS_REENTRANT	0
#define _FS_TIMEOUT		1000
#define	_SYNC_t			HANDLE
/*  _FS_REENTRANT选项开关re-entrancy fatf的(线程安全)   
/模块本身。注意,不管这个选项,文件访问不同  
/体积始终是凹角和音量控制功能,f_mount(),f_mkfs()   
/和f_fdisk()函数,总是不凹角。只有文件/目录的访问  
/相同的体积是这个功能的控制。  
/   
/ 0:禁用re-entrancy。_FS_TIMEOUT和_SYNC_t没有效果。  
/ 1:启用re-entrancy。还提供用户同步处理程序,   
/ ff_req_grant(),ff_rel_grant(),ff_del_syncobj()和ff_cre_syncobj()   
/函数,必须添加到项目中。样品中可用  
/选项
/ syscall.c。
/
/  _FS_TIMEOUT定义超时时间单位的滴答声。  
/ _SYNC_t定义了O 
/ S依赖同步对象类型。例如处理、ID、OS_EVENT *   
/ SemaphoreHandle_t等. .O / S的头文件定义需要  
/包括在ff.c的范围。 */


#define _WORD_ACCESS	0
/* _WORD_ACCESS选项是一个只有依赖于平台的选择。
它定义了这个词/访问方法是用来体积上的数据。
/
/ 0:逐字节的访问。总是兼容所有平台。  
/ 1:词的访问。不要选择这个,除非在下列条件。  
/   
/ *地址对齐内存访问总是允许所有指令。  
/ *字节顺序的记忆是低位优先。  
/   
/如果是这样的情况,_WORD_ACCESS也可以减少代码的大小设置为1。  
/下表显示允许设置某种类型的处理器。
/
/  ARM7TDMI   0   *2          ColdFire   0    *1         V850E      0    *2
/  Cortex-M3  0   *3          Z80        0/1             V850ES     0/1
/  Cortex-M0  0   *2          x86        0/1             TLCS-870   0/1
/  AVR        0/1             RX600(LE)  0/1             TLCS-900   0/1
/  AVR32      0   *1          RL78       0    *2         R32C       0    *2
/  PIC18      0/1             SH-2       0    *1         M16C       0/1
/  PIC24      0   *2          H8S        0    *1         MSP430     0    *2
/  PIC32      0   *1          H8/300H    0    *1         8051       0/1
/
/   
* 1:高位优先。  / 
* 2:不支持不连续的内存访问。  / 
* 3:一些编译器生成LDM(逻辑磁盘管理器 ) / STM mem_cpy(内存拷贝)函数。
*/

(3)实现动态内存分配函数与时间函数

ff.h文件有动态内存的释放,动态内存申请,时间获取函数接口。

image-20221201192603132

在diskio.c文件实现函数功能:

image-20221201192626227

代码实现如下:

//动态内存分配
void* ff_memalloc (UINT msize)			    /* 分配内存块 */
{
	return (void*)malloc(msize); //分配空间
}


//动态内存释放
void ff_memfree (void* mblock)			    /* 空闲内存块 */
{
	free(mblock);              //释放空间
}


//返回FATFS时间
//获得时间  
DWORD get_fattime (void)
{	
	//Get_RTC_Timer(); //获取一次RTC时间
		return (RTC_Timer.year-1980)<<25|   //年
			  RTC_Timer.month<<21|  //月
		       RTC_Timer.day<<16|    //日
		       RTC_Timer.hour<<11|   //时
		       RTC_Timer.minute<<5|  //分
		       RTC_Timer.sec;        //秒
}



/*
Return Value
Currnet local time is returned with packed into a DWORD value. The bit field is as follows:
bit31:25
Year origin from the 1980 (0..127)
bit24:21
Month (1..12)
bit20:16
Day of the month(1..31)
bit15:11
Hour (0..23)
bit10:5
Minute (0..59)
bit4:0
Second / 2 (0..29)
*/

(4)修改堆栈空间

完成了上述的修改,还需要修改堆栈空间,因为长文件支持需要占用堆空间。

修改STM32启动文件如下:

image-20221201192757035

(5)编译工程测试

修改完毕之后,给开发板插上SD卡,调用API函数在SD卡创建一个文件,并写入数据,测试是否成功:

#include "ff.h"
FATFS fs;  // 用户定义的文件系统结构体
FIL  file;  // 用户定义的文件系统结构体
u8 buff[]="123 知识!!";
int main(void)
{
	u32 data;                //检测SD卡容量
	u8 i,res;
    LED_Init();              //LED灯初始化
    Delay_Init();
    KEY_Init();
    USART1_Init(72,115200);
    USART2_Init(36,115200);
     FLASH_Init();
	  Set_Font_addr(); //字库地址初始化
	  FSMC_SRAM_Init();
	  LCD_Init();
	  RTC_Init();     //RTC时钟初始化
	  while(SD_Init())    //检测不到SD卡,SD相关硬件初始化
		{
			i=!i;
			LCD_ShowString(60,150,200,16,16,"SD Card Error!  Please Check SD Card!!",0xf800);					
			Delay_ms(500);
			LED1(i)//DS0闪烁
		}
		
       f_mount(&fs,"0",1);  // 注册工作区,驱动器号 0,初始化后其他函数可使用里面的参数
		printf("注册工作区!\n");
		
		if(f_mkfs("0",0,4096))  //格式化SD卡
		{
			printf("格式化失败!!\n");
		}
		else
		{
			printf("格式化成功!!\n");
		}
		res = f_open(&file, "/file.c", FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
		if(res==0)
		{
			printf("文件创建成功!!\n");
		}
		else
		{
			printf("文件创建失败!!\n");
		}
		res =f_write(&file,buff,strlen((const char*)buff),&data);
		if(res==0)
		{
			printf("数据写入成功!!\n");
		}
		else
		{
			printf("数据写入失败!!\n");
		}
		printf("成功写入%d字节数据\n",data);
		f_close(&file);  //关闭文件
		//_FS_RPATH
		
		while(1)
		{
			Delay_ms(1000);
			LED1(1);
			Delay_ms(500);
			LED1(0);
		}
}

五、案例使用

5.1 读取GBK字库文件(LCD汉字显示)

产品开发中,如果设备带有LCD显示屏,一般会显示各种文字提示,或者机器操作说明,显示中文需要字库,为了方便字模的提取,可以将字库文件制作好之后放到SD NAND上,通过文件系统打开字库文件,读取字模进行显示。

下面贴出文件系统读取字模的核心代码:

/*
函数功能: 显示GBK字库数据
          u32 x  范围0~319
          u32 y  范围0~479
          u32 size  数据的宽度(必须是8的倍数)  是正方形
          u8 *p  中文
说明: 取模横向坐标必须保证是8的倍数
*/

void ILI9341_DisplayGBKData(u32 x,u32 y,u32 size,u8 *p)
{
		FIL fp;
		UINT br;
		u8 L,H;
	  u32 Addr;
	  u16 font_size=size/8*size; //字体占用的点阵码字节大小
	  u8 *buff=NULL;
		H=*p;
		L=*(p+1);
		if(L<0x7f)L=L-0x40;
		else L=L-0x41;
		H=H-0x81;
		Addr=(190*H+L)*font_size; //中文在字库里的偏移量
		buff=malloc(font_size);   //使用的堆空间
		if(buff==NULL)return;

		switch(size)
		{
			case 16:
				if(f_open(&fp,"0:/font/gbk16.DZK",FA_READ)!=FR_OK)
                {
                      printf("f_open error.\r\n");
                }
				f_lseek(&fp,Addr);
				f_read(&fp,buff,font_size,&br);
				f_close(&fp);
               
				break;
			case 24:
                f_open(&fp,"0:/font/gbk24.DZK",FA_READ);
				f_lseek(&fp,Addr);
				f_read(&fp,buff,font_size,&br);
				f_close(&fp);
				break;
			case 32:
				
				break;
		}
		//显示中文
		ILI9341_DisplayData(x,y,size,size,buff);
		
		//释放空间
		free(buff);
}

这是读取字模,显示的效果:

image-20221201194230582

5.2 读取MP3文件播放(开机音乐)

这个例子是演示文件系统的目录扫描函数使用方式,读取指定目录下的MP3文件进行播放。

u8 PlayerMP3(const char *path);
FATFS FatFs;
int main()
{
	LED_Init();
	BEEP_Init();
	KeyInit();
  USARTx_Init(USART1,72,115200);

  
  SDCardDeviceInit(); //初始化SD卡
  
//  res=f_mkfs("0:",FM_ANY,0,work,sizeof work);
//  if(res)printf("格式化失败!\n");
//  else printf("格式化成功!\n");
  f_mount(&FatFs, "0:", 0);   //注册工作区
  
  PlayerMP3("0:/MP3");
  
	while(1)
	{
    DelayMs(100);
    LED0=!LED0;
	}
}


/*
函数功能: 扫描目录mp3播放
0表示成功 1表示失败
*/
u8 PlayerMP3(const char *path)
{
    DIR dir;
    FRESULT res; 
    FILINFO fno; //存放读取的文件信息
    char *abs_path=NULL;  
    
    /*1. 打开目录*/    
    res=f_opendir(&dir,path);
    if(res!=FR_OK)return res;
    
    /*2. 循环读取目录*/
     while(1)
     {
        res=f_readdir(&dir,&fno);
        if(fno.fname[0] == 0 || res!=0)break;
        printf("文件名称: %s,文件大小: %ld 字节\r\n",fno.fname,fno.fsize);

        /*过滤目录*/
        if(strstr(fno.fname,".mp3"))
        {
            //申请存放文件名称的长度
            abs_path=malloc(strlen(path)+strlen(fno.fname)+1);
            if(abs_path==NULL)break;
             
            strcpy(abs_path,path);
            strcat(abs_path,"/");
            strcat(abs_path,fno.fname);
          
            printf("abs_path=%s\n",abs_path);
            VS1053_MP3(0,0,abs_path);     
            free(abs_path);
        }
    }
    
    /*3. 关闭目录*/
    f_closedir(&dir);
    return 0;
}
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。