为DE2读SD卡的工程添加文件系统

  上个工程中,我们引入SD卡驱动,已经做到可以简单的按扇区去读取SD卡数据。但显然,我们在实际应用中,更希望可以使用类似于fopen的方式打开文件,即使用文件名去打开某一文件,然后读取SD卡中的数据,如何实现呢?

  这里就要引入文件系统的概念。需要明确的是,文件系统是在SD卡初始化后就已经建立完成的,我们要做的只是将这个文件系统中的某些参数读取出来,为我们按文件读取数据做准备。这些参数,主要分布在SD卡的物理第一扇区,分区的逻辑第一扇区,以及每个文件的第一个扇区里的文件头。

先说SD卡的物理第一扇区

01BE表示激活标记,判断整个分区是否时活动分区,如果是活动分区,就是80,否则就是00。一般是00。

01BF表示分区开始的磁头号,这里是03,但对电子盘来说没有意义

01C0-01C1表示起始扇区和柱面号,(低8位的)低6位是扇区号,高2位是柱面号的9,10位;高8位是柱面号的低8位。

 01C2表示分区类型,这里是0E,也可以表示FAT16

01C3分区结束的磁头号

01C4-01C5表示分区结束的扇区和柱面号,和起始扇区和柱面号格式一样

01C6-01C9表示逻辑地址的第0扇区

从01CA开始往后四个字节,按照大端模式存储,用于表示分区所占扇区:

00 1E 73 09,也就是1995529扇区,再加上247个扇区,刚好就是1995776扇区,即整个SD卡的扇区数。

最后两位是55 AA ,表示格式化完成。

——————————————————————————————————————————————————

再说分区1的逻辑第一扇区:

 

 

0000-0002 一般是EB XX 90或者E9 XX XX 无实际含义

0003-000A 无实际含义,只是一个名字

000B-000C 大端模式 实际是0X2000=512,表示一个扇区是512字节

000D 表示每簇的扇区数是32,即一个簇是32*512=32K

000E-000F 表示保留扇区数,经过此保留扇区数目后,开始出现FAT表的数据

0010 表示FAT表的数目,一般是两个

0011-0012 大端模式 实际是0X2000=512,一般FAT16都是512,表示根目录项数

0013-0014 表示总扇区数,当扇区数>0x10000时,偏移放在0020-0023处

0015 表示介质种类

0016-0017 表示FATSz16 表示一个分区表占的扇区数

0018-0019 表示每磁道的扇区数,由于这不是硬盘,因此没有意义

001A-001B 磁头数,同上

001C-001F FAT表所在的分区前面隐藏的扇区数,这里是F7即247

0020-0023 表示当扇区数>0x10000时分区总扇区数

0024  一般硬盘为0X80,软盘为0

0025  供NT使用,这里必须为0

0026  扩展引导标记

0027-002A 卷ID ,无实际意义

002B-0035 卷标,这里是NO NAME    

0036-003D 文件系统类型,这里是FAT16

003E-01BD 可执行代码

01BE   1分区头

1BF 开始的磁头地址

1C0-1C1 开始的扇区地址和柱面地址,和之前格式相同

1C2 分区类型

1C3 结束的磁头

1C4-1C5 结束的扇区和柱面

1C6-1C9 分区内第一个扇区地址

1CA-1CD 分区内的总扇区数

后面连着48个长度,代表234分区的格式数据,结构同上

1FE-1FF 格式化前面,必须是55AA

结合上述数据格式,我们可以定义一个结构体,用于记录SD卡一个分区的数据,结构体定义如下:

//卷信息(分区信息)
typedef struct{
bool bMount; //是否连接
//
unsigned int PartitionStartSecter; //分区起始扇区
unsigned int BPB_BytsPerSec; //每个扇区的byte数
unsigned int BPB_SecPerCluster; //每簇的扇区数
unsigned int BPB_RsvdSecCnt; //Boot记录占用多少扇区
unsigned int BPB_NumFATs; //FAT表数目
unsigned int BPB_RootEntCnt; //根目录项数
unsigned int BPB_FATSz; //每个FAT表所占扇区个数
//
unsigned int nBytesPerCluster; //每簇的byte数
unsigned int FatEntrySecter; //FAT入口扇
unsigned int RootDirectoryEntrySecter;//ROOT目录入口扇
unsigned int DataEntrySecter; //数据入口扇
//
#ifdef FAT_READONLY
char *szFatTable; // read into memory for speedup
#endif

}VOLUME_INFO;

 

而系统连接函数Fat_Mount()的构造思路也很简单,依次读物理第一扇区和逻辑第一扇区,然后将需要的数据从指定位数读出来,赋值即可。

大致内容如下:

bool Fat_Mount(FAT_DEVICE FatDevice){
#ifdef SD_CHECK
const int nMaxTry=10;
bool bFind = FALSE;
int nTry=0;
#endif
bool bSuccess = TRUE;
unsigned char szBlock[512];
int FirstPartitionEntry,PartitionType,FirstSectionInVolume1;
int nFatTableSize,nFatTableSecterNum;
#ifdef FAT_READONLY
int i;
#endif
//int BPB_BytsPerSec,BPB_RsvdSecCnt,BPB_NumFATs,BPB_RootEntCnt,BPB_FATSz;
//断开已有连接
Fat_Unmount();
//如果入口参数不是FAT_SD_CARD,直接返回FALSE,因为后续做的连接是基于SD卡的
if (FatDevice != FAT_SD_CARD)
return FALSE;

#ifdef SD_CHECK
//1. chek whether SD Card existed. Init SD card if it is present.
//等待SD卡初始化
while(!bFind && nTry++ < nMaxTry){
bFind = SD_card_init();
if (!bFind)
usleep(100*1000);
}
//如果等待次数过多,提示错误信息
if (!bFind){
FAT_DEBUG(("Cannot find SD card.\n"));
return FALSE;
}
#endif

//2. parsing Boot Sector system
//2.1 Read the Master Boot Record(MBR) of FAT file system (Locate the section 0)
// Offset:
// 000h(446 bytes): Executable Code (Boots Computer)
// 1BEh( 16 bytes): 1st Partition Entry
// 1CEh( 16 bytes): 2nd Partition Entry
// 1DEh( 16 bytes): 3nd Partition Entry
// 1EEh( 16 bytes): 4nd Partition Entry
// 1FEh( 2 bytes): Executable Maker (55h AAh)

// read first block
//读物理第一扇区
if (!SD_read_block(0, szBlock)){
FAT_DEBUG(("Read section 0 error.\n"));
return FALSE;
}

// check file system
//判断文件系统是FAT16还是FAT32
//FirstPartitionEntry指向激活标记
FirstPartitionEntry = 0x1BE;
//激活标记移4位,取分区类型
PartitionType = szBlock[FirstPartitionEntry + 4];
if (PartitionType != PARTITION_FAT16){
FAT_DEBUG(("the partition type is not supported.\n"));
return FALSE; // only support FAT16 in this example
}
// 2.2 Find the first section of partition 1
//找到分区中第一块扇区(即逻辑第一扇区),大端模式存储数据,四字节长度
FirstSectionInVolume1 = szBlock[FirstPartitionEntry + 8 + 3]*256*256*256 +
szBlock[FirstPartitionEntry + 8 + 2]*256*256 +
szBlock[FirstPartitionEntry + 8 + 1]*256 +
szBlock[FirstPartitionEntry + 8];

//3 Parsing the Volume Boot Record(BR)
//3.1 Read the Volume Boot Record(BR)
//读解析卷引导记录(逻辑第一扇区,包含了分区格式数据)
if (!SD_read_block(FirstSectionInVolume1, szBlock)){
FAT_DEBUG(("Read first sector in volume one fail.\n"));
return FALSE;
}
//写卷信息
//分区第一块扇区的实际物理扇区号
gVolumeInfo.PartitionStartSecter = FirstSectionInVolume1;
//每个扇区的byte数
gVolumeInfo.BPB_BytsPerSec = szBlock[0x0B+1]*256 + szBlock[0x0B];
//每簇扇区数
gVolumeInfo.BPB_SecPerCluster = szBlock[0x0D];
//保留扇区数,经过此保留扇区数目后,开始出现FAT表的数据
gVolumeInfo.BPB_RsvdSecCnt = szBlock[0x0E + 1]*256 + szBlock[0x0E];
//FAT表的数目
gVolumeInfo.BPB_NumFATs = szBlock[0x10];
//根目录项数
gVolumeInfo.BPB_RootEntCnt = szBlock[0x11+1]*256 + szBlock[0x11];
//表示一个分区表所占的扇区数
gVolumeInfo.BPB_FATSz = szBlock[0x16+1]*256 + szBlock[0x16];
#ifdef DUMP_DEBUG
FAT_DEBUG(("First section in partition 1: %04Xh(%d)\n", gVolumeInfo.PartitionStartSecter, gVolumeInfo.PartitionStartSecter));
FAT_DEBUG(("Byte Per Sector: %04Xh(%d)\n", gVolumeInfo.BPB_BytsPerSec, gVolumeInfo.BPB_BytsPerSec));
FAT_DEBUG(("Sector Per Clusoter: %02Xh(%d)\n", gVolumeInfo.BPB_SecPerCluster, gVolumeInfo.BPB_SecPerCluster));
FAT_DEBUG(("Reserved Sectors: %04Xh(%d)\n", gVolumeInfo.BPB_RsvdSecCnt, gVolumeInfo.BPB_RsvdSecCnt));
FAT_DEBUG(("Number of Copyies of FAT: %02Xh(%d)\n", gVolumeInfo.BPB_NumFATs, gVolumeInfo.BPB_NumFATs));
FAT_DEBUG(("Maxmun Root Directory Entries: %04Xh(%d)\n", gVolumeInfo.BPB_RootEntCnt, gVolumeInfo.BPB_RootEntCnt));
FAT_DEBUG(("Sectors Per FAT: %04Xh(%d)\n", gVolumeInfo.BPB_FATSz, gVolumeInfo.BPB_FATSz));
#endif
//
//FAT入口扇的地址,从分区起始扇区开始,还要加上Boot记录占用的扇区
//FAT入口扇的地址,分区起始扇区+保留扇区,之后就是FAT表的数据
gVolumeInfo.FatEntrySecter = gVolumeInfo.PartitionStartSecter + gVolumeInfo.BPB_RsvdSecCnt;
//Root目录入口扇的地址,从FAT入口扇开始,还要经过所有的FAT表,即FAT表数目*FAT表站占的扇区个数
gVolumeInfo.RootDirectoryEntrySecter = gVolumeInfo.FatEntrySecter + gVolumeInfo.BPB_NumFATs * gVolumeInfo.BPB_FATSz;
//数据入口扇,从root目录入口扇开始,要经过一个System Volume Information文件夹,占32个扇区,经过这个文件夹后,才能到文件数据
gVolumeInfo.DataEntrySecter = gVolumeInfo.RootDirectoryEntrySecter + ((gVolumeInfo.BPB_RootEntCnt*32)+(gVolumeInfo.BPB_BytsPerSec-1))/gVolumeInfo.BPB_BytsPerSec;

// read FAT table into memory
//读FAT表中的参数,设置每一簇的byte数
gVolumeInfo.nBytesPerCluster = gVolumeInfo.BPB_BytsPerSec * gVolumeInfo.BPB_SecPerCluster;
//FAT表占扇区个数
nFatTableSecterNum = gVolumeInfo.BPB_NumFATs * gVolumeInfo.BPB_FATSz;
//FAT表大小
nFatTableSize = nFatTableSecterNum * gVolumeInfo.BPB_BytsPerSec;
#ifdef FAT_READONLY
//此处验证是否只读
//为pVol的szFatTable开辟空间,实际相当于设置参数
gVolumeInfo.szFatTable = malloc(nFatTableSize);
if (!gVolumeInfo.szFatTable){
FAT_DEBUG(("fat malloc(%d) fail!", nFatTableSize));
return FALSE;
}
//尝试读取FAT表
for(i=0;i<nFatTableSecterNum; i++ ){
if (!SD_read_block(gVolumeInfo.FatEntrySecter+i, gVolumeInfo.szFatTable + i*gVolumeInfo.BPB_BytsPerSec)){
FAT_DEBUG(("Read first sector in volume one fail.\n"));
bSuccess = FALSE;
}
}

//释放空间
if (!bSuccess && gVolumeInfo.szFatTable){
free(gVolumeInfo.szFatTable);
gVolumeInfo.szFatTable = 0;
}
#endif

/*
// debug
SD_read_block(gVolumeInfo.RootEntry, szBlock);
DumpFat(szBlock);
for(i=0;i<512;i++){
if (i%20 == 0)
printf("\n[%06d]%02X ", i, szBlock[i]);
else
printf("%02X ", szBlock[i]);
}

*/
//成功,打印提示信息,返回success,同时也获取到gVolumeInfo变量里的信息
if (bSuccess){
FAT_DEBUG(("Fat_Mount success\n"));
}else{
FAT_DEBUG(("Fat_Mount fail\n"));
}
gVolumeInfo.bMount = bSuccess;
return bSuccess;
}

————————————————————————————————————————————
系统连接完成后,需要再获取目录信息,大致由两个结构体来承载数据,一个是FAT_DIRECTORY(文件背景结构体,用于存放单个文件的信息),另一个是

FAT_BROWSE_HANDLE(FAT目录句柄,用于存放目录中的文件个数)。仍然要先看磁盘的结构格式

(格式流程请看使用winhex查看FAT16格式结构 - Milton - 博客园 (cnblogs.com)

 

 从目录入口扇开始,每一个文件都以上述格式来存放文件背景信息,每个背景信息的长度是32byte。

(0C开始的10个保留位,在FAT16里依次是2位保留,2位生成时间,2位生成日期,2位最近访问时间,2位无意义)

  那么依靠Fat_FileBrowseBegin和Fat_FileBrowseNext两个函数即可完成目录的构造,接下来描述如何实现这两个函数。

1.根据DIR区数据格式分布,完成文件背景结构体和目录句柄的定义:

//文件背景结构体,用于存放单个文件的信息
typedef struct{
char Name[8];//文件名
char Extension[3];//拓展名
char Attribute;//属性
char reserved[2];
unsigned short CreateTime;//生成时间
unsigned short CreateDate;//生成日期
unsigned short LastAccessDate;//最近访问日期
unsigned short FirstLogicalClusterHi; // not used in FAT12/FAT16
unsigned short LastWriteTime;//最近修改时间
unsigned short LastWriteDate;//最近修改日期
unsigned short FirstLogicalCluster;//第一逻辑簇
unsigned int FileSize;//文件大小
}FAT_DIRECTORY;

typedef struct{
unsigned int DirectoryIndex;//目录入口索引(用于表示目录中有几个文件)
}FAT_BROWSE_HANDLE;

___________________________________________________________

下述两个函数,既用于生成目录结构体,也用于目录的访问

//功能函数,用于对入口参数的pFatBrowseHandle做初始化操作

bool Fat_FileBrowseBegin(FAT_BROWSE_HANDLE *pFatBrowseHandle){
if (!gVolumeInfo.bMount)
return FALSE;
//目录入口索引置0
pFatBrowseHandle->DirectoryIndex = 0;
return TRUE;
}

____________________________________________________________

//功能函数,用于按顺序浏览DIR区的文件背景信息,每次读取到的信息存放在入口参数pFatBrowseHandle里,并把pDirectory里的目录入口索引+1,用于读取下一个文件背景信息

bool Fat_FileBrowseNext(FAT_BROWSE_HANDLE *pFatBrowseHandle, FAT_DIRECTORY *pDirectory){
bool bFind = FALSE, bError=FALSE;
FAT_DIRECTORY *pDir;
unsigned int nSecter, nOffset;
char szBlock[512];
if (!gVolumeInfo.bMount)
return FALSE;
do{
//确定当前要读入目录的文件在目录里的偏移扇区
// 一个文件背景信息长度*目录中文件数/一个扇区byte数,可以算出偏移扇区数
nOffset = (sizeof(FAT_DIRECTORY)*pFatBrowseHandle->DirectoryIndex)/gVolumeInfo.BPB_BytsPerSec;
//确定当前要读入目录的文件在整个存储空间的物理扇区地址
//目录入口扇+偏移扇区=文件物理扇区
nSecter = gVolumeInfo.RootDirectoryEntrySecter + nOffset;
if (!SD_read_block(nSecter, szBlock)){
bError = TRUE;
}else{
//确定当前要读入目录的文件在当前扇区的偏移地址
//一个文件背景信息长度*目录中文件数%一个扇区byte数,可以算出在当前扇区的偏移地址
nOffset = (sizeof(FAT_DIRECTORY)*pFatBrowseHandle->DirectoryIndex)%gVolumeInfo.BPB_BytsPerSec;
//以缓存数组szBlock头指针+偏移地址,得到当前文件的文件背景信息,强转成FAT_DIRECTORY类结构体,数据根据地址一一对应
pDir = (FAT_DIRECTORY *)(szBlock + nOffset);
//目录文件+1
pFatBrowseHandle->DirectoryIndex++;
//判断内容是否合法
bFind = fatIsValid(pDir);
if (bFind)
*pDirectory = *pDir;
}
}while (!bFind && !fatIsLast(pDir) && !bError);
return bFind;
}

——————————————————————————————————

上述函数调用了一个fatIsValid函数,主要用于判断文件名是否合法,内容如下:

bool fatIsValid(FAT_DIRECTORY *pDir){
char szTest[] = {0x00, 0xE5, 0x22, 0x2A, 0x2B, 0x2C, 0x2E, 0x2F, 0x3A, 0x3B, 0x3C, 0x3E, 0x3F, 0x5B, 0x5C, 0x5D, 0x7C};
int i;
//要求文件名首字母不能为 空、0xE5 、“、*、+、,、.、/、:、;、<、>、?、[、\、]、}
for(i=0;i<sizeof(szTest)/sizeof(szTest[0]);i++){
if (pDir->Name[0] == szTest[i]){
return FALSE;
}
}
return TRUE;
}

____________________________________________________________
再定义文件访问的系列函数

先定义一个FAT文件句柄

//FAT文件句柄
typedef struct{
bool IsOpened;//是否已经打开
unsigned int SeekPos;//搜寻位置
unsigned int Cluster;//簇
unsigned int ClusterSeq; // zero base 簇序列,用于记录当前读到第几簇
FAT_DIRECTORY Directory;//文件背景信息
}FAT_FILE_HANDLE;

//打开文件,用pFileName打开,用FAT_FILE_HANDLE结构体变量接收
bool Fat_FileOpen(FAT_FILE_HANDLE *pFileHandle, const char *pFilename)

{
bool bSuccess = FALSE;
char szFilename[32];
FAT_BROWSE_HANDLE hBrowse;
FAT_DIRECTORY Dir;
//初始化文件句柄的open标志位为FALSE
pFileHandle->IsOpened = FALSE;
//开始浏览文件目录
if (Fat_FileBrowseBegin(&hBrowse)){
//遍历目录,获取文件名
while (!bSuccess && Fat_FileBrowseNext(&hBrowse, &Dir)){
Fat_ComposeFilename(&Dir, szFilename);
//当文件名与输入文件名相同,进行赋值
if (strcmpi(szFilename, pFilename) == 0){
pFileHandle->SeekPos = 0;
pFileHandle->Directory = Dir;
pFileHandle->IsOpened = TRUE;
pFileHandle->Cluster = Dir.FirstLogicalCluster;
pFileHandle->ClusterSeq = 0;
bSuccess = TRUE;
}
}
}
return bSuccess;
}

————————————————————————————————————

此处使用了Fat_ComposeFilename函数,功能是从FAT_DIRECTORY中获取文件名,内容如下:

void Fat_ComposeFilename(FAT_DIRECTORY *pDir, char *szFilename){
int i,nPos=0;
i=0;
//当文件名当前位不为空和空格,将该数据放在szFilename数组末端,直到遍历完或遇到空和空格
while(i < 8 && pDir->Name[i] != 0 && pDir->Name[i] != ' ')
szFilename[nPos++] = pDir->Name[i++];
//加.后缀
szFilename[nPos++] = '.';
i=0;
//添加拓展名,方式同上
while(i < 3 && pDir->Extension[i] != 0 && pDir->Extension[i] != ' ')
szFilename[nPos++] = pDir->Extension[i++];
szFilename[nPos++] = 0;
}

————————————————————————————————————————————

定义文件数据读取函数

思路如下:入口参数传递FAT_FILE_HANDLE 结构体对象、pBuffer数组的指针,以及要读取的数据大小

从FAT_FILE_HANDLE 结构体对象中读取文件信息

判断文件是否已经打开,若未打开,则提示读取失败,并直接返回

若打开,则继续处理。

根据FAT_FILE_HANDLE 结构体对象的SeekPos数据,确定该文件当前数据读取的位置,从而确定当前读到的簇序列。

根据确定的位置,完善读数据的偏移地址,再使用SD_read_block函数,按扇区读取指定大小的数据,并在每个循环判断已读取数据的大小是否满足入口参数的要求,若满足,则退出读数据循环,更新FAT_FILE_HANDLE 结构体对象里的位置数据,然后返回。

具体实现如下:

bool Fat_FileRead(FAT_FILE_HANDLE *pFileHandle, void *pBuffer, const int nBufferSize){

unsigned int Pos, PhysicalSecter, NextCluster, Cluster;
unsigned int BytesPerCluster, nReadCount=0, nClusterSeq;
int s;
bool bSuccess= TRUE;
char szBlock[512];
//判断文件是否打开
if (!pFileHandle->IsOpened){
FAT_DEBUG(("[FAT] Fat_FileRead, file not opened\r\n"));
return bSuccess;
}
//从分区信息结构体里获得每簇的byte数
BytesPerCluster = gVolumeInfo.nBytesPerCluster; //gVolumeInfo.BPB_BytsPerSec * gVolumeInfo.BPB_SecPerCluster;
//这里的pos是当前文件读到的位置(精确到第几个字节)
Pos = pFileHandle->SeekPos;
//用当前pos/每簇byte数,得到的nClusterSeq即当前读到的簇数
//如果每簇的byte数为16384,即每簇有32扇区
if (BytesPerCluster == 16384)
//pos>>14,即除16384
nClusterSeq = Pos >> 14;
else
nClusterSeq = Pos/BytesPerCluster;
//用当前pos-当前簇数*byte数,得到的Pos即基于当前簇数的位置(精确到当前簇数的第几个字节)
if (BytesPerCluster == 16384)
Pos -= (pFileHandle->ClusterSeq << 14);
else
Pos -= pFileHandle->ClusterSeq*BytesPerCluster;

Cluster = pFileHandle->Cluster;
//发现簇序列对不上//由于通常按512byte(一个扇区)读取数据,当恰好读完某簇的最后一个扇区,会导致簇序列差1
//这时人工补1即可
if (nClusterSeq != pFileHandle->ClusterSeq){
Cluster = pFileHandle->Cluster; //11/20/2007, richard
// move to first clustor for reading
//如果发现pos仍大于等于BytesPerCluster,说明还要再移动到下一簇(一般是等于)
//判断下一簇是否存在,若存在,更改pos和Cluster、ClusterSeq等数据
while (Pos >= BytesPerCluster && bSuccess){
// go to next cluster
NextCluster = fatNextCluster(Cluster);
if (NextCluster == 0){
bSuccess = FALSE;
FAT_DEBUG(("[FAT] Fat_FileRead, not next Cluster, current Cluster=%d\r\n", Cluster));
}else{
Cluster = NextCluster;
}
Pos -= BytesPerCluster;
pFileHandle->Cluster = Cluster;
pFileHandle->ClusterSeq++;
}
}

// reading
while(nReadCount < nBufferSize && bSuccess){
//修正物理扇区,减去FAT0和FAT1两个簇,得到物理第一簇,左移5位(*32)后+数据入口扇,得到该文件的物理第一扇
if (gVolumeInfo.BPB_SecPerCluster == 32)
PhysicalSecter = ((Cluster-2) << 5) + gVolumeInfo.DataEntrySecter; // -2: FAT0 & FAT1 are reserved
else
PhysicalSecter = (Cluster-2)*gVolumeInfo.BPB_SecPerCluster + gVolumeInfo.DataEntrySecter; // -2: FAT0 & FAT1 are reserved
for(s=0;s<gVolumeInfo.BPB_SecPerCluster && nReadCount < nBufferSize;s++){
//修正nCopyCount与Pos,实际nCopyCount大部分时刻都是512
if (Pos >= gVolumeInfo.BPB_BytsPerSec){
Pos -= gVolumeInfo.BPB_BytsPerSec;
}else{
int nCopyCount;
nCopyCount = gVolumeInfo.BPB_BytsPerSec;
if (Pos)
nCopyCount -= Pos;
if (nCopyCount > (nBufferSize-nReadCount))
nCopyCount = nBufferSize-nReadCount;
if (nCopyCount == 512){
//if (PhysicalSecter == 262749)
// FAT_DEBUG(("[FAT] here, 262749"));
//从物理第一扇区读取数据,存放在pBuffer,偏移地址是nReadCount
if (!SD_read_block(PhysicalSecter, (char *)pBuffer+nReadCount)){
bSuccess = FALSE; // fail
FAT_DEBUG(("[FAT] Fat_FileRead, SD_read_block fail, PhysicalSecter=%d (512)\r\n", PhysicalSecter));
}else{
//FAT_DEBUG(("[FAT] Fat_FileRead Success, PhysicalSecter=%d (512)\r\n", PhysicalSecter));
//读取成功,修正偏移地址
nReadCount += nCopyCount;
//每次读取完成,是完成了一个扇区的读取,所以将pos置零
if (Pos > 0)
Pos = 0;
}
}else{
if (!SD_read_block(PhysicalSecter, szBlock)){
bSuccess = FALSE; // fail
FAT_DEBUG(("[FAT] Fat_FileRead, SD_read_block fail\r\n"));
}else{
memcpy((void *)((char *)pBuffer+nReadCount), szBlock+Pos,nCopyCount);
nReadCount += nCopyCount;
//每次读取完成,是完成了一个扇区的读取,所以将pos置零
if (Pos > 0)
Pos = 0;
}
}

}
//扇区数+1
PhysicalSecter++;
}

// next cluster
//在while循环里一直读数据,直到在偏移地址nReadCount处无法读到数据,说明该簇读完,判断是否还有下一簇
//如果没有,就直接返回,并提示没有下一簇了
//如果有,则把pFileHandle里的簇数+1,并把第一逻辑簇修改为NextCluster,而下一次读取数据就从NextCluster开始
if (nReadCount < nBufferSize){
NextCluster = fatNextCluster(Cluster);
if (NextCluster == 0){
bSuccess = FALSE;
FAT_DEBUG(("[FAT] Fat_FileRead, no next cluster\r\n"));
}else{
Cluster = NextCluster;
}
//
pFileHandle->ClusterSeq++;
pFileHandle->Cluster = Cluster;
}
}
//数据读取完成,将pFileHandle的数据成员SeekPos+nBufferSize位,为下一次从该位子读数据做准备
if (bSuccess){
pFileHandle->SeekPos += nBufferSize;
}
return bSuccess;
}

函数中调用了fatNextCluster与fatClusterType函数,内容如下:

// For FAT16 only
CLUSTER_TYPE fatClusterType(unsigned short Fat){
CLUSTER_TYPE Type;

if (Fat > 0 && Fat < 0xFFF0)
Type = CLUSTER_NEXT_INFILE;
else if (Fat >= 0xFFF8) // && Fat <= (unsigned short)0xFFFF)
Type = CLUSTER_LAST_INFILE;
else if (Fat == (unsigned short)0x00)
Type = CLUSTER_UNUSED;
else if (Fat >= 0xFFF0 && Fat <= 0xFFF6)
Type = CLUSTER_RESERVED;
else if (Fat == 0xFFF7)
Type = CLUSTER_BAD;

return Type;

}

unsigned int fatNextCluster(unsigned short ThisCluster){
CLUSTER_TYPE ClusterType;
unsigned int NextCluster;
#ifdef FAT_READONLY
// const int nFatEntrySize = 2; // 2 byte for FAT16

// NextCluster = *(unsigned short *)(gVolumeInfo.szFatTable + ThisCluster*nFatEntrySize);
//获取下一簇的簇号,其实直接+1就行,
NextCluster = *(unsigned short *)(gVolumeInfo.szFatTable + (ThisCluster << 1));
//判断簇类型是否合法
ClusterType = fatClusterType(NextCluster);
if (ClusterType != CLUSTER_NEXT_INFILE && ClusterType != CLUSTER_LAST_INFILE){
NextCluster = 0; // invalid cluster
}
#else
int nFatEntryPerSecter;
const int nFatEntrySize = 2; // 2 byte for FAT16
unsigned int Secter;
char szBlock[512];
nFatEntryPerSecter = gVolumeInfo.BPB_BytsPerSec/nFatEntrySize;
Secter = gVolumeInfo.FatEntrySecter + (ThisCluster*nFatEntrySize)/gVolumeInfo.BPB_BytsPerSec;
if (SD_read_block(Secter,szBlock)){
NextCluster = *(unsigned short *)(szBlock + (ThisCluster%nFatEntryPerSecter)*nFatEntrySize);
ClusterType = fatClusterType(NextCluster);
if (ClusterType != CLUSTER_NEXT_INFILE && ClusterType != CLUSTER_LAST_INFILE)
NextCluster = 0; // invalid cluster
}
return NextCluster;

#endif
return NextCluster;
}

以上两个函数,一个用于获取下一簇号,另一个确定fat簇的类型(主要用于判断是否合法)。

这些函数编写完成后,编写主函数验证函数功能,要求功能如下:

1、可以读取SD卡里的所有文件,完成列表并打印。

2、可以读取给定文件名的文件的数据。

#include "my_includes.h"
#include "FatFileSystem.h"
#include "SDCardDriver.h"


#define LCD_DISPLAY
#define PIO_GREEN_LED_BASE LEDG_BASE
#define PIO_RED_LED_BASE LDER_BASE
void lcd_open(void);
void lcd_display(char *pText);
void led_display(alt_u32 mask);
void led_display_count(alt_u8 count);
bool LCD_Clear(void);
void LCD_Close(void);
bool LCD_TextOut(char *pText);

static FILE *fp=0;

bool LCD_Open(void){
fp = fopen(LCD_NAME, "w");
if (fp)
return TRUE;
return FALSE;
}

void lcd_display(char *pText){
#ifdef LCD_DISPLAY
LCD_Clear();
LCD_TextOut(pText);
#endif
}
bool LCD_TextOut(char *pText){
if (!fp)
return FALSE;
fwrite(pText, strlen(pText), 1, fp);
return TRUE;
}

bool LCD_Clear(void){
char szText[32]="\n\n";
if (!fp)
return FALSE;
fwrite(szText, strlen(szText), 1, fp);
return TRUE;
}
void LCD_Close(void){
if (fp)
fclose(fp);
fp = 0;
}

void led_display(alt_u32 Mask){ // 1: light, 0:unlight
alt_u32 ThisMask;
ThisMask = Mask;//~(Mask & 0x7FFFFFF);
IOWR_ALTERA_AVALON_PIO_DATA(PIO_GREEN_LED_BASE, ThisMask); //0:ligh, 1:unlight
IOWR_ALTERA_AVALON_PIO_DATA(PIO_RED_LED_BASE, ThisMask >> 9); //0:ligh, 1:unlight

}
void led_display_count(alt_u8 LightCount){ // 1: light, 0:unlight
alt_u32 Mask = 0;
int i;
for(i=0;i<LightCount;i++){
Mask <<= 1;
Mask |= 0x01;
}
IOWR_ALTERA_AVALON_PIO_DATA(PIO_GREEN_LED_BASE, Mask); //0:ligh, 1:unlight
IOWR_ALTERA_AVALON_PIO_DATA(PIO_RED_LED_BASE, Mask >> 8); //0:ligh, 1:unlight
}

#define BUF_SIZE 512
#define MAX_FILE_NUM 128
#define FILENAME_LEN 32
#define DEMO_PRINTF printf
typedef struct{
int nFileNum;
char szFilename[MAX_FILE_NUM][FILENAME_LEN];
}FILE_LIST;//文件列表

static FILE_LIST gFileList;

typedef struct {
FAT_FILE_HANDLE hFile;//文件句柄
alt_u8 szBuf[BUF_SIZE]; // one sector size of sd-card//数据缓存
alt_u32 uPlayIndex; //数据索引
alt_u32 uReadPos; //读地址
alt_u32 uPlayPos; //播放地址
alt_u32 uMaxPlayPos; //结束地址
char szFilename[FILENAME_LEN]; //文件名
alt_u8 nVolume;
bool bRepeatMode; //是否支持重写
} FILE_CONTEXT;
static FILE_CONTEXT gFilePlay;

//等待SD卡插入
void wait_sdcard_insert(void)
{
bool bFirstTime2Detect = TRUE;
alt_u8 led_mask = 0x02;
while(!SD_card_init()){
if (bFirstTime2Detect){
DEMO_PRINTF("Please insert SD card.\r\n");
lcd_display(("\rPlease insert\nSD card.\n"));
bFirstTime2Detect = FALSE;
led_display(led_mask);
usleep(100*1000);
led_mask ^= 0x02;
}//while
led_display(0x02);
DEMO_PRINTF("Find SD card\r\n");
}
};


bool File_Open(char *pFilename)
{
bool bSuccess;
//给gWavePlay的文件名数据成员赋值
strcpy(gFilePlay.szFilename, pFilename);
//打开文件,给hFile赋值
bSuccess = Fat_FileOpen(&gFilePlay.hFile, pFilename);
if (!bSuccess)
DEMO_PRINTF("file open fail.\n");
return bSuccess;
};
bool File_Read(bool *bEOF)
{
//由于文件格式未知,这里只读初始512byte数据
if(!Fat_FileRead(&gFilePlay.hFile,gFilePlay.szBuf,512))
{
DEMO_PRINTF("file read fail.\n");
return FALSE;
}
return TRUE;
};

// build wave list in gWavePlayList
//构建文件列表
int build_File_play_list(void) {
int count = 0;
FAT_BROWSE_HANDLE hFileBrowse;
FAT_DIRECTORY Directory;
FAT_FILE_HANDLE hFile;
alt_u8 szHeader[128];
char szFilename[FILENAME_LEN];

gFileList.nFileNum = 0;
// FatFileSystem.h
if (!Fat_FileBrowseBegin(&hFileBrowse)){
DEMO_PRINTF("browse file fail.\n");
return 0;
}

// FatFileSystem.h
while (Fat_FileBrowseNext(&hFileBrowse,&Directory)) {
//选择指定后缀的文件,这里选的是wav和bmp
if (strncmpi(Directory.Extension, "WAV", 3)==0||strncmpi(Directory.Extension, "BMP", 3) == 0)
{
Fat_ComposeFilename(&Directory, szFilename);

// parsing wave format (FatFileSystem.h)
if (!Fat_FileOpen(&hFile, szFilename)) {
DEMO_PRINTF("wave file open fail.\n");
continue;
}

// read file header (FatFileSystem.h)
if (!Fat_FileRead(&hFile, szHeader, sizeof(szHeader))){
DEMO_PRINTF("wave file read fail.\n");
continue;
}

// close file (FatFileSystem.h)
Fat_FileClose(&hFile);
//文件加入列表
strcpy(gFileList.szFilename[count],szFilename);
count++;
}
} // while

gFileList.nFileNum = count;
printf("gWavePlayList success\n");
//Fat_FileBrowseEnd(&hFileBrowse);
return count;
};


int main()
{
printf("hello world\n");
// open lcd
LCD_Open();
while(1)
{
wait_sdcard_insert();
//建立FAT连接
if (!Fat_Mount(FAT_SD_CARD)) {
DEMO_PRINTF("SD card mount fail.\n");
lcd_display(("SD card mount fail.\n\n"));
return 0;
}
//建立文件列表
// build wave list in gWavePlayList
if (build_File_play_list() == 0) {
DEMO_PRINTF("There is no file in the root directory of SD card.\n");
lcd_display(("No Files.\n\n"));
return 0;
}
//测试文件列表是否构建成功
int a=0;
printf("gPlayList:%d\n",gFileList.nFileNum);
for(a=0;a<gFileList.nFileNum;a++)
{
printf(gFileList.szFilename[a]);
printf("\n");
}
//打开文件,读取第一扇区数据
File_Open("1.bmp");
File_Read(FALSE);
int i;
for(i=0;i<512;i++)
{
printf("%d,",gFilePlay.szBuf[i]);
}
}
return 0;
}

以上是主函数文件内容。由于我们还不了解文件第一磁盘中文件头的格式内容,因此只能粗略的读取某一文件的第一扇区,等待后续了解文件头组成时,才能编写完整的读取全部数据的函数。

程序运行效果如下:

 

可以看到获取到了SD卡中的文件列表,读取到的1.bmp文件的第一扇区,与winHex读取数据做对比

 

 

 可以观察到完全相同,说明文件系统构建成功。

posted @ 2021-12-26 10:24  狂生墨客  阅读(22)  评论(0)    收藏  举报