四、移植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 下载源码
fatfs有两个版本,一个大版本,一个小版本。小版本主要用于8位机(内存小)使用。
下载图:
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 里面,我们可以通过配置里面的一些选项,来满足自己的需求。
最顶层是应用层,使用者无需理会 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章已经贴了),接着就完成下面的步骤。
打开keil工程,添加fatfs文件源码:
加入.h文件主要是方便配。cc936.c 用于支持中文。
4.5 修改代码进行移植(1)修改diskio.c文件
注释掉现在不需要的用到的文件,因为我们现在用的是sd卡,与usb,ata,mmc卡没关系。
并加入一个新的宏 :
#define sd 0
定义sd卡的物理驱动器号为0。
修改 disk_status函数,该函数主要是用来获取磁盘状态。现在未用到,可以直接函数体内代码删除。
修改截图:
代码示例:
#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。
修改截图:
代码示例:
/*-----------------------------------------------------------------------*/
/* 初始化磁盘驱动 */
/*-----------------------------------------------------------------------*/
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卡读任意扇区的函数(需要用户自己提供),其他不用到的选项可以删掉。
修改代码如下:
/*-----------------------------------------------------------------------*/
/* 读扇区 */
/*-----------------------------------------------------------------------*/
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 函数,添加写扇区函数:
代码:
/*-----------------------------------------------------------------------*/
/* 写扇区 */
/*-----------------------------------------------------------------------*/
#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有定义。
代码如下:
/*-----------------------------------------------------------------------*/
/* 其他函数 */
/*-----------------------------------------------------------------------*/
#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文件有动态内存的释放,动态内存申请,时间获取函数接口。
在diskio.c文件实现函数功能:
代码实现如下:
//动态内存分配
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启动文件如下:
(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);
}
这是读取字模,显示的效果:
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;
}
提升发电效能 嵌入式让太阳能追日系统更完善
三星S8能否救场 外媒体验过后这样说
Vishay新型电池分流器上市,精度更高,RTC性能更优
分享一个音频控制电源开关电路
统领4.5万机器人大军的贝索斯又押宝家庭机器人,试图扩展更多的场景
基于STM32+CS创世 SD NAND(贴片SD卡)完成FATFS文件系统移植与测试(下篇)
解决路由器DNS劫持的两种方法
VC5402与外部存储器的接口设计
Flash页、扇区、块的区别
如何提高仪表放大器的稳定性
落实陈吉宁书记调研要求 争创通用GPU行业领军企业
Linux常用指令及基础知识讲解
诺基亚拍照旗舰Nokia 9 PureView搭载后置五摄
PCB设计:如何避免关键设计错误?
混合云环境中的Kubernetes HPC使用经验
多元共进|2023 Google 谷歌开发者大会现场全回顾
苹果计划在今年要发布的三款新iPhone中删除3D Touch功能
美国空军实验室正在将TDI-J85发动机集成到Gray Wolf飞行器中去
全国建设项目超过1100个 5G+工业互联网仍在起步
搭建全自动微信小说漫画动漫源码系统开发cps分销系统模式开发