[野火]i.MXRT库开发实战指南——基于i.MXRT1052
贡献与投稿
常见问题
·CPU:MIMXRT1052CVL5B·主频:600M
上一章我们已经全面介绍了SD卡的识别和简单的数据读写,也进行了简单的读写测试,不过像这样直接操作SD卡存储单元,在实际应用中是不现实的。SD卡一般用来存放文件,所以都需要加载文件系统到里面。
即使读者可能不了解文件系统,读者也一定对“文件”这个概念十分熟悉。数据在PC上是以文件的形式储存在磁盘中的,这些数据的形式一般为ASCII码或二进制形式。在上一章我们已经写好了SD卡的驱动函数,我们可以非常方便的在SD上读写数据。如需要一串字符写入SD卡,调用SD_WriteBlocks函数即可。
但是,这样直接存储数据会带来极大的不便,如难以记录有效数据的位置,难以确定存储介质的剩余空间,以及应以何种格式来解读数据。就如同一个巨大的图书馆无人管理,杂乱无章地存放着各种书籍,难以查找所需的文档。想象一下图书馆的采购人员购书后,把书籍往馆内一扔,拍拍屁股走人,当有人来借阅某本书的时候,就不得不一本本地查找。这样直接存储数据的方式对于小容量的存储介质如EEPROM还可以接受,但对于SPIFlash芯片或者SD卡之类的大容量设备,我们需要一种高效的方式来管理它的存储内容。
这些管理方式即为文件系统,它是为了存储和管理数据,而在存储介质建立的一种组织结构,这些结构包括操作系统引导区、目录和文件。常见的windows下的文件系统格式包括FAT32、NTFS、exFAT。在使用文件系统前,要先对存储介质进行格式化。格式化先擦除原来内容,在存储介质上新建一个文件分配表和目录。这样,文件系统就可以记录数据存放的物理地址,剩余空间。
使用文件系统时,数据都以文件的形式存储。写入新文件时,先在目录中创建一个文件索引,它指示了文件存放的物理地址,再把数据存储到该地址中。当需要读取数据时,可以从目录中找到该文件的索引,进而在相应的地址中读取出数据。具体还涉及到逻辑地址、簇大小、不连续存储等一系列辅助结构或处理过程。
文件系统的存在使我们在存取数据时,不再是简单地向某物理地址直接读写,而是要遵循它的读写格式。如经过逻辑转换,一个完整的文件可能被分开成多段存储到不连续的物理地址,使用目录或链表的方式来获知下一段的位置。
上一章的SD卡驱动只完成了向物理地址存储块中写入数据的工作,而根据文件系统格式的逻辑转换部分则需要额外的代码来完成。实质上,这个逻辑转换部分可以理解为当我们需要写入一段数据时,由它来求解向什么物理地址写入数据、以什么格式写入及写入一些原始数据以外的信息(如目录)。这个逻辑转换部分代码我们也习惯称之为文件系统。
上面提到的逻辑转换部分代码(文件系统)即为本章的要点,文件系统庞大而复杂,它需要根据应用的文件系统格式而编写,而且一般与驱动层分离开来,很方便移植,所以工程应用中一般是移植现成的文件系统源码。
FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由AISIC语言编写并且完全独立于底层的I/O介质。因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。FatFs支持FAT12、FAT16、FAT32等格式,所以我们利用前面写好的SD卡驱动,把FatFs文件系统代码移植到工程之中,就可以利用文件系统的各种函数,对SD卡以“文件”格式进行读写操作了。
FatFs文件系统的源码可以从fatfs官网下载:
第一步:打开链接如图281所示。
图281FatFs源码获取(1)
第二步:选择下载的版本如图282所示。在版本列表中选择需要的版本即可,本章以使用R0.13b。
图282FatFs源码获取(2)
在移植FatFs文件系统到开发板之前,我们先要到FatFs的官网获取源码,最新版本为R0.13c,官网有对FatFs做详细的介绍,有兴趣可以了解。解压之后可看到里面有doc和src这两个文件夹,见图283。doc文件夹里面是一些使用帮助文档;src才是FatFs文件系统的源码。
图283FatFs文件目录
打开doc文件夹,可看到如图284的文件目录:
图284doc文件夹的文件目录
doc文件夹下各个文件简要介绍如下:
打开source文件夹,可看到如图285所示的文件目录:
图285source文件夹的文件目录
option文件夹下是一些可选的外部c文件,包含了多语言支持需要用到的文件和转换函数。
00history.txt介绍了FatFs的版本更新情况。
00readme.txt说明了当前目录下diskio.c、diskio.h、ff.c、ff.h、integer.h的功能。
src文件夹下的源码文件功能简介如下:
建议阅读这些源码的顺序为:integer.h–>diskio.c–>ff.c。
阅读文件系统源码ff.c文件需要一定的功底,建议读者先阅读FAT32的文件格式,再去分析ff.c文件。若仅为使用文件系统,则只需要理解integer.h及diskio.c文件并会调用ff.c文件中的函数就可以了。本章主要讲解如何把FATFS文件系统移植到开发板上,并编写一个简单读写操作范例。
移植FatFs之前我们先通过FatFs的程序结构图了解FatFs在程序中的关系网络,见图286。
图286FatFs程序结构图
用户应用程序需要由用户编写,想实现什么功能就编写什么的程序,一般我们只用到f_mount()、f_open()、f_write()、f_read()就可以实现文件的读写操作。
FatFs组件是FatFs的主体,文件都在源码src文件夹中,其中ff.c、ff.h、integer.h以及diskio.h四个文件我们不需要改动,只需要修改ffconf.h和diskio.c两个文件。
底层设备输入输出要求实现存储设备的读写操作函数、存储设备信息获取函数等等。我们使用SD卡作为物理设备,在上一章节已经编写好了SD卡的驱动程序,这里我们就直接使用。
FatFs属于软件组件,不需要附带其他硬件电路。我们使用SD卡作为物理存储设备,其硬件电路在上一章已经做了分析,这里就直接使用。
上一章我们已经实现了SD卡芯片驱动程序,并实现了读写测试,为移植FatFs方便,我们直接拷贝一份工程,我们在工程基础上添加FatFs组件,并修改main函数的用户程序即可。
NXP官方已经将R0.13b版本的FatFs文件系统移植到了RT1052,保存在“SDK_2.5.0_MIMXRT1052xxxxBmiddlewarefatfs”目录下。本小节讲解如何将NXP官方移植的FatFs文件系统添加到我们的工程中。
图287添加fatfs文件夹
图288复制NXP官方文件
图289复制FatFs到工程
至此,我们需要的文件已经全部添加到了fatfs文件夹下。下一步就是将这些文件添加到工程。
图2810添加FatFS文件到工程
图2811添加FATFS路径到工程选项
图2812首次编译错误输出
打开“SD卡—FatFs文件系统读写SD卡—FatFs文件系统librariesfatfs”在fatfs文件夹内并没有ffconf.h文件,但是有一个ffconf_template.h文件。从文件名不难得出这是FatFs配置文件样板,本实验不用这个配置文件,我们直接复制NXP官方配置好文件。在“SDK_2.5.0_MIMXRT1052xxxxBboardsevkbimxrt1050fatfs_examplesfatfs_sdcard”目录下是基于NXP官方评估板的例程,由于我们开发板SD卡接口不同,我们不使用NXP官方评估板例程,但是FatFs文件系统配置文件ffconf.h文件是通用的,我们直接复制ffconf.h文件到我们的工程即可,最终结果如图2813所示。
图2813添加ffconf.h文件
图2814第二次编译错误提示
错误的原因是在main.c文件和fsl_sd_disk.c文件定义了同名变量g_sd。我们修改任意一个文件中的变量名或者在在其中一个文件变量定义改为extern引用。
至此,再次编译正常情况下没有错误。下一步需要为FatFs文件系统添加底层驱动函数以及修改ffconfig.h配置文件。
FatFs文件系统与底层介质的驱动分离开来,对底层介质的操作都要交给用户去实现,它仅仅是提供了一个函数接口而已。表281为FatFs移植时用户必须支持的函数。通过表281我们可以清晰知道很多函数是在一定条件下才需要添加的,只有前三个函数是必须添加的。我们完全可以根据实际需求选择实现用到的函数。
前三个函数是实现读文件最基本需求。接下来三个函数是实现创建文件、修改文件需要的。为实现格式化功能,需要在disk_ioctl添加两个获取物理设备信息选项。我们一般只有实现前面六个函数就可以了,已经足够满足大部分功能。
为支持简体中文长文件名称需要添加ff_convert和ff_wtoupper函数,实际这两个已经在cc936.c文件中实现了,我们只要直接把cc936.c文件添加到工程中就可以了。
后面六个函数一般都不用。如真有需要可以参考syscall.c文件(srcoption文件夹内)。
表281FatFs移植需要用户支持函数
函数
条件(ffconf.h)
备注
disk_statusdisk_initializedisk_read
总是需要
底层设备驱动函数
disk_writeget_fattimedisk_ioctl(CTRL_SYNC)
FF_FS_READONLY==0
disk_ioctl(GET_SECTOR_COUNT)disk_ioctl(GET_BLOCK_SIZE)
FF_USE_MKFS==1
disk_ioctl(GET_SECTOR_SIZE)
FF_MAX_SS!=FF_MIN_SS
disk_ioctl(CTRL_TRIM)
FF_USE_TRIM==1
ff_convertff_wtoupper
FF_USE_LFN!=0
Unicode支持,为支持简体中文,添加cc936.c到工程即可
ff_cre_syncobjff_del_syncobjff_req_grantff_rel_grant
FF_FS_REENTRANT==1
FatFs可重入配置,需要多任务系统支持(一般不需要)
ff_mem_allocff_mem_free
FF_USE_LFN==3
长文件名支持,缓冲区设置在堆空间(一般设置_USE_LFN=2)
底层设备驱动函数是存放在diskio.c文件,我们的目的就是把diskio.c中的函数接口与SD卡驱动连接起来。总共有五个函数,分别为设备状态获取(disk_status)、设备初始化(disk_initialize)、扇区读取(disk_read)、扇区写入(disk_write)、其他控制(disk_ioctl)。
接下来,我们对每个函数结合SD卡驱动做详细讲解。
宏定义
1234567/*为每个设备定义一个物理编号*/#defineRAMDISK0/*预留给外扩RAM使用*/#defineUSBDISK1/*预留给USB使用*/#defineSDDISK2/*SD卡设备物理编号*/#defineMMCDISK3/*预留给MMC卡使用mmc*/#defineSDSPIDISK4/*预留给SPI接口SD卡使用*/#defineNANDDISK5/*预留给nandflash使用*/这两个宏定义在FatFs中非常重要,FatFs是支持多物理设备的,必须为每个物理设备定义一个不同的编号。
本实验只用到了SD卡物理接口编号SDDISK,其他设备编号可以屏蔽掉,使用时再加上。
设备状态获取
123456789DSTATUSsd_disk_status(uint8_tphysicalDrive){if(physicalDrive!=SDDISK){returnSTA_NOINIT;}return0;}很明显,该函数是一个空函数。进入函数后使用if语句判断操作的是否为SD卡,如果不是,返回错误标志,如果是,返回0。我们暂时用不到这个函数,所以不必修改。
设备初始化
12345678910111213141516171819202122232425262728293031DSTATUSdisk_initialize(BYTEpdrv/*物理设备编号*/){/**********************第一部分*************************/DSTATUSstat;switch(pdrv){#ifdefRAM_DISK_ENABLEcaseRAMDISK:stat=ram_disk_initialize(pdrv);returnstat;#endif#ifdefUSB_DISK_ENABLEcaseUSBDISK:stat=USB_HostMsdInitializeDisk(pdrv);returnstat;#endif/***********************第二部分*************************/#ifdefSD_DISK_ENABLEcaseSDDISK:stat=sd_disk_initialize(pdrv);returnstat;#endif/******************此处省略其他条件编译语句********************/default:break;}returnSTA_NOINIT;}disk_initialize函数也是有一个参数pdrv,用来指定设备物理编号。对于SD卡我们调用sd_disk_initialize函数实现对SD卡使用的GPIO初始化、USDHC初始化、SD卡初始化。sd_disk_initialize函数如代码清单285所示。
读取扇区
12345678910111213141516171819202122232425DRESULTdisk_read(BYTEpdrv,/*设备物理编号*/BYTE*buff,/*数据缓存区*/DWORDsector,/*起始数据块编号*/UINTcount/*读取数据块数量*/){DRESULTres;switch(pdrv){/******************此处省略其他条件编译语句********************/#ifdefSD_DISK_ENABLEcaseSDDISK:res=sd_disk_read(pdrv,buff,sector,count);returnres;#endif/******************此处省略其他条件编译语句********************/default:break;}returnRES_PARERR;}disk_read函数有四个形参。pdrv为设备物理编号。buff是一个BYTE类型指针变量,buff指向用来存放读取到数据的存储区首地址。sector是一个DWORD类型变量,指定要读取数据的起始数据块编号。count是一个UINT类型变量,指定读取数据块的数量。
sd_disk_read函数原型如代码清单287所示。
123456789101112131415161718DRESULTsd_disk_read(uint8_tphysicalDrive,uint8_t*buffer,uint32_tsector,uint8_tcount){/**************************第一部分**************************/if(physicalDrive!=SDDISK){returnRES_PARERR;}/***************************第二部分*************************/if(kStatus_Success!=SD_ReadBlocks(&g_sd,buffer,sector,count)){returnRES_ERROR;}returnRES_OK;}同其它sd_disk_XXX函数类似,首先判断操作的是SD卡。第二部分,直接调用fsl_sd.c文件中的SD_ReadBlocks函数,该函数执行最终的SD卡读操作。
扇区写入
12345678910111213141516171819202122DRESULTdisk_write(BYTEpdrv,/*设备物理编号*/constBYTE*buff,/*数据缓存区*/DWORDsector,/*起始数据块编号*/UINTcount/*读取数据块数量*/){DRESULTres;switch(pdrv){/******************此处省略其他条件编译语句********************/#ifdefSD_DISK_ENABLEcaseSDDISK:res=sd_disk_write(pdrv,buff,sector,count);returnres;#endif/******************此处省略其他条件编译语句********************/default:break;}returnRES_PARERR;}disk_write函数有四个形参。pdrv为设备物理编号。buff是一个BYTE类型指针变量,buff指向用来存放读要写入数据的首地址。sector是一个DWORD类型变量,指定要写入数据的起始块编号。count是一个UINT类型变量,指定写入数据块的数量。如果操作的是SD卡,调用sd_disk_write函数完成SD卡写入操作,函数原型如代码清单289所示。
123456789101112131415161718DRESULTsd_disk_write(uint8_tphysicalDrive,constuint8_t*buffer,uint32_tsector,uint8_tcount){/**************************第一部分**************************/if(physicalDrive!=SDDISK){returnRES_PARERR;}/***************************第二部分*************************/if(kStatus_Success!=SD_WriteBlocks(&g_sd,buffer,sector,count)){returnRES_ERROR;}returnRES_OK;}同其它sd_disk_XXX函数类似,首先判断操作的是SD卡。第二部分,直接调用fsl_sd.c文件中的SD_WriteBlocks函数,该函数执行最终的SD卡写操作。
其他控制
12345678910111213141516171819202122DRESULTdisk_ioctl(BYTEpdrv,/*物理编号*/BYTEcmd,/*控制指令*/void*buff/*写入或者读取数据地址指针*/){DRESULTres;switch(pdrv){/******************此处省略其他条件编译语句********************/#ifdefSD_DISK_ENABLEcaseSDDISK:res=sd_disk_ioctl(pdrv,cmd,buff);returnres;#endif/******************此处省略其他条件编译语句********************/default:break;}returnRES_PARERR;}disk_ioctl函数有三个形参,pdrv为设备物理编号,cmd为控制指令,包括获取SD卡块数量命令(GET_SECTOR_COUNT)、获取SD卡块大小命令(GET_SECTOR_SIZE)、获取可擦除扇区块大小命(GET_BLOCK_SIZE)以及结束写入命令(CTRL_SYNC)。buff为指令对应的数据指针。
如果设置的是SD卡,则会调用sd_disk_ioctl函数,函数原型如代码清单2811所示。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657/****************************第一部分*************************/sd_card_tg_sd;DRESULTsd_disk_ioctl(uint8_tphysicalDrive,uint8_tcommand,void*buffer){DRESULTresult=RES_OK;if(physicalDrive!=SDDISK){returnRES_PARERR;}/****************************第二部分***********************/switch(command){caseGET_SECTOR_COUNT:if(buffer){*(uint32_t*)buffer=g_sd.blockCount;}else{result=RES_PARERR;}break;/***********************第三部分************************/caseGET_SECTOR_SIZE:if(buffer){*(uint32_t*)buffer=g_sd.blockSize;}else{result=RES_PARERR;}break;/***********************第四部分************************/caseGET_BLOCK_SIZE:if(buffer){*(uint32_t*)buffer=g_sd.csd.eraseSectorSize;}else{result=RES_PARERR;}break;/***********************第五部分************************/caseCTRL_SYNC:result=RES_OK;break;default:result=RES_PARERR;break;}returnresult;}下面简要讲解各部分代码。
ffconf.h文件是FatFs功能配置文件,我们可以对文件内容进行修改,使得FatFs更符合我们的要求。ffconf.h对每个配置选项都做了详细的使用情况说明。下面只列出相对于源码修改的配置,其他配置采用默认即可。
本实验主要使用FatFs软件功能,不需要其他硬件模块,使用与SD卡读写测试实验相同硬件配置即可。
在28.3FatFs文件系统移植章节我们介绍了FatFs文件系统移植步骤,完成了diskio.c文件内SD卡底层驱动程序的编写以及ffconf.h文件的修改。本小节将会调用FatFs文件系统提供的API操作SD卡。
挂载FATFS文件系统测试
使用文件系统之前,要挂载文件系统。在挂载过程中会调用diskio.c文件中的底层驱动文件完成SD卡的初始化等操作,挂载成功之后就可以调用FatFs文件系统中的API函数了。挂载测试函数如代码清单2813所示。
12345FRESULTf_mount(FATFS*fs,/*指向文件系统对象结构体指针(NULL:unmount)*/constTCHAR*path,/*要挂载/卸载的逻辑驱动器号*/BYTEopt/*选择操作0:取消挂载(delayedmount),1:立即挂载*/)函数参数介绍如下:
如果挂载成功,通过串口输出“挂载文件系统失败”提示信息,然后执行到第六部分代码。
为设备创建文件系统会格式化物理设备,所以创建之前要保证确认SD卡可以被格式化。程序在while(true)循环中使用GETCHAR函数获取串口上位机输入的选择,如果选择确定格式化则执行第五部分代码。
123456789FRESULTf_mkfs(constTCHAR*path,/*逻辑驱动编号*/BYTEopt,/*格式化选项*/DWORDau,/*分配单元的带下,单位(字节)*//*指向用于格式化进程的工作缓冲区的指针。当给定一个空指针时,函数为工作缓冲区分配一块堆空间,此时设置len没有作用*/void*work,UINTlen/*工作缓冲区的大小(以字节为单位)*/)函数各个参数介绍如下:
f_mkfs函数执行完成之后根据返回值判断文件系统创建是否成功,如果创建成功则再次调用f_mount函数挂载文件系统。否则输出错误提示,进入死循环程序。
创建和打开文件测试
文件的创建和打开使用相同的函数,只是函数配置参数不同。我们以文件创建测试为例讲解,文件打开测试函数类似。
123456789101112131415161718192021222324252627282930FRESULTf_touch_test(char*dir){/*********************第一部分***************************/FRESULTerror=FR_OK;FILg_fileObject;/*Fileobject*/PRINTF("\r\n创建“%s”文件......\r\n",dir);/*********************第二部分**************************/error=f_open(&g_fileObject,_T(dir),FA_CREATE_NEW);/**********************第三部分**************************/if(error){if(error==FR_EXIST){PRINTF("文件已经存在.\r\n");}else{PRINTF("创建文件失败\r\n");returnerror;}}else{PRINTF("创建文件成功\r\n");}returnerror;}代码主要内容是调用f_open函数创建文件。函数执行结束后需要根据返回值判断文件创建结果。下面简要讲解个各部分代码。
12345FRESULTf_open(FIL*fp,/*[OUT]指向文件描述结构体*/constTCHAR*path,/*[IN]文件名(路径)*/BYTEmode/*[IN]打开模式*/);函数各个参数讲解如下:
表格281文件打开方式
模式
含义
FA_READ
以只读方式打开。数据可以从文件中读取。
FA_WRITE
指定对对象的写访问。数据可以写入文件。
FA_OPEN_EXISTING
打开该文件。如果文件不存在,该函数将失败。(默认)
FA_CREATE_NEW
创建一个新文件。如果文件存在,则该函数将返回FR_EXIST,表示文件已存在
FA_CREATE_ALWAYS
创建一个新文件。如果文件存在,它将被截断并覆盖。
FA_OPEN_ALWAYS
打开文件,如果文件存在,则打开该文件。如果没有,将创建一个新文件。
FA_OPEN_APPEND
与FA_OPEN_ALWAYS相同,文件打开后只是读/写指针默认在文件的末尾
不同打开模式可以通过“或”的方式组合,常用的几种组合如表格282所示
表格282文件打开方式常用组合
以只读方式打开
以读写方式打开
新建文件并以只写方式打开,如果文件存在则将原文件截断并覆盖
FA_CREATE_ALWAYSFA_READ
新建文件并以读、写方式打开,如果文件存在则将原文件截断并覆盖
FA_WRITE末尾添加内容。
以只写方式打开文件,如果文件不存在,创建文件。如果文件存在,在文件
FA_OPEN_APPENDFA_READ
FA_WRITE件末尾添加内容。
以读、写方式打开文件,如果文件不存在,创建文件。如果文件存在,在文
FA_WRITET,表示文件已存在
以只读方式创建一个文件,如果文件存在,则该函数将返回FR_EXIS
FA_CREATE_NEWFA_READ
FA_WRITEST,表示文件已存在
以读、写方式创建一个文件,如果文件存在,则该函数将返回FR_EXI
一般情况下,我们选择以上组合方式即可满足对文件的读写需求。
创建目录测试
12345678910111213141516171819202122232425262728FRESULTf_mkdir_test(char*dir){/***********************第一部分****************/FRESULTerror;PRINTF("\r\n创建目录“%s”......\r\n",dir);/**********************第二部分*****************/error=f_mkdir(_T(dir));/**********************第三部分******************/if(error){if(error==FR_EXIST){PRINTF("目录已经存在\r\n");}else{PRINTF("创建目录失败.\r\n");returnerror;}}else{PRINTF("创建目录成功\r\n");}returnerror;}与创建文件类似。首先调用FatFs提供的函数f_mkdir创建目录,然后根据返回值判断创建结果。这部分代码比较简单,这里不再赘述。
读取目录测试
获取一个文件夹下的文件和目录需要两步。第一,使用f_opendir函数打开路径。第二步,调用f_readdir函数读取目录下的文件或文件夹。如代码清单2820所示。
1234FRESULTf_opendir(DIR*dp,/*指向要创建的目录对象的指针*/constTCHAR*path/*指向目录路径的指针*/)f_opendir函数共有两个参数,dp用于保存所创建目录对象的信息,打开成功后就可以使用该指针作为参数读取目录内容了。path,指定要打开的目录。
1234FRESULTf_readdir(DIR*dp,/*指向打开的路径*/FILINFO*fno/*指向保存文件信息的结构体指针*/)第二部分代码我们使用f_opendir函数打开了一个路径,f_readdir函数用于依次读取路径里的内容,每次读取都会将读到的信息保存一个FILINFO类型的文件信息结构体中,FILINFO结构体如代码清单2823所示。
12345#defineAM_RDO0x01/*只读*/#defineAM_HID0x02/*隐藏*/#defineAM_SYS0x04/*系统文件*/#defineAM_DIR0x10/*目录*/#defineAM_ARC0x20/*文件*/一个文件或目录的属性由这些选项组合得到。
调用f_readdir函数读取之后,我们可以根据返回值得到读取是否成功。根据传回的文件信息结构体指针FILINFO得到文件属性信息。我们在程序中判断读到的是文件还是文件夹,然后分别通过串口输出。
文件读写测试
123456FRESULTf_write(FIL*fp,/*指向文件描述符*/constvoid*buff,/*指向要发送的数据*/UINTbtw,/*发送数据量*/UINT*bw/*实际发送数据量*/)结合本程序,各个参数的作用简单讲解如下:
写入结束判断函数返回值类型,如果发送错误或者预期写入数据量与实际写入数据量不一致则输出错误提示。
1234FRESULTf_lseek(FIL*fp,/*文件描述符*/FSIZE_tofs/*相对于文件开始处的偏移*/)f_lseek函数共有两个参数,一个用于指定文件描述符,另外指定文件读写指针相对于文件起始位置的偏移,本次要从文件开始读取,所以偏移设为0即可。
同其他函数,根据函数的返回值判断函数执行是否成功,并输出提示信息。
123456FRESULTf_read(FIL*fp,/*指向文件描述符*/void*buff,/*接收数据缓冲区*/UINTbtr,/*接收数据量*/UINT*br/*实际接收数据量*/)f_read函数与f_write函数的参数非常类似,只是将读改为写,详细请参考f_write函数或者官方f_read函数说明文档。