在STM32上使用FAT32文件系统访问SD卡外设

源代码

FAT32.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#ifndef __FAT32_H
#define __FAT32_H

#include "stm32f10x.h"
#include "sdio_sdcard.h"
#define WORD u16
#define DWORD u32
#define BYTE u8
#define END_FAT 0x0fffffff
#define BAD_FAT 0xf7ffffff
#define INVALID_VALUE 0xffffffff
// 我们规定,SD卡为MBR分区且只有一个主分区;扇区大小为512字节,即WORD[DBR+0Bh]==0x0200;根目录在2号簇
// 目前文件存储只支持短文件名,即文件名不能超过8字节,扩展名不能超过3字节

typedef __packed struct
{
BYTE other[8];
DWORD startSectorNo;
DWORD totalSectorsNum;
}MBRPartition;
typedef __packed struct
{
BYTE MBRCode[446];
MBRPartition P[4];
WORD Signature;
}MBR;
typedef __packed struct
{
BYTE Version[11];
WORD BytesOfSector; // 每扇区字节数
BYTE SectorsOfCluster; // 每簇的扇区数
WORD FATSectorStart; // FAT表的起始扇区号
BYTE NumsOfFAT; // FAT表个数
BYTE Reserve1[15];
DWORD NumsOfSectors; // 文件系统总扇区数
DWORD SizeOfFAT; // FAT表的大小(单位扇区)
DWORD Reserve2;
DWORD RootDirCluster; // 根目录所在簇
WORD SizeOfDBR;
WORD BackupDBR;
BYTE Reserve3[458];
WORD Signature;
}DBR;

typedef __packed struct
{
DWORD Signature1; // 52 52 61 41 RRaA
BYTE Reserve1[480];
DWORD Signature2; // 72 72 41 61 rrAa
DWORD NumsOfFreeCluster; // 空闲簇数
DWORD NextAvaliableCluster; // 下一个可用簇数
BYTE Reserve2[14];
WORD Signature; // 55 AA
}FSINFO;

typedef __packed struct
{
// Next[0]==0x0ffffff8
// 若簇号下一簇为0x0fffffff则没有下一簇,为0xf7ffffff则为坏簇
DWORD Next[128]; // 该簇的下一号簇
}FATTable;

typedef __packed struct
{
BYTE Name[8]; // 文件名,不足8字节用空格补齐。第一个字节若为0xE5则表示被删除
BYTE ExtName[3]; // 扩展名
BYTE Attribute; // 文件属性,0x10为目录,0x20为文件
BYTE Reserve; // 0x00
BYTE CreateTime[5];
WORD LastOpenTime;
WORD HighCluster; // 起始簇高16位
DWORD LastChangeTime;
WORD LowCluster; // 起始簇的低16位
DWORD FileSize; // 文件大小
}DirItem;

typedef __packed struct
{
DirItem dir[16];
}DirItemsOfSector;

typedef enum
{
FAT_NoError,
FAT_SDError,
FAT_SignBad, // SD卡可以读取数据,但FAT标识被损坏或者不是FAT32文件系统。需要重新格式化SD卡
FAT_CannotFindFile,
FAT_FileNameInvalid,
FAT_DiskFull,
FAT_FileExisted,
}FAT32_Error;

DWORD FAT32_ClusterToSector(DWORD clusterNum);
DWORD FAT32_GetNextCluster(DWORD clusterNum);
int FAT32_AllZero(BYTE* p, int n);
DWORD FAT32_FindUnusedDirItem(void);
FAT32_Error __FAT32_OpenFile(const char*Name, DWORD clusterOfDir, DWORD* FileCluster, DWORD* FileSize);
DWORD FAT32_FindDirItems(DWORD clusterNum, BYTE Name[8], BYTE Extend[3], int isDir, DWORD* FileSize);
FAT32_Error __FAT32_CreateFile(const char*Name, DWORD clusterOfDir, DWORD* sectorOfDirItem, DWORD* idx, int cover);


// 供其他模块调用的功能函数
FAT32_Error FAT32_Init(void);
#define FAT32_OpenFile(Name,Pointer_FileCluster,Pointer_FileSize) __FAT32_OpenFile(Name,2,Pointer_FileCluster,Pointer_FileSize)
#define FAT32_CreateFile(Name, Pointer_sectorOfDirItem, Pointer_idx, Cover) __FAT32_CreateFile(Name, 2, Pointer_sectorOfDirItem, Pointer_idx, Cover)

#endif

FAT32.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
#include "FAT32.h"

MBR mbr;
DBR dbr;
FSINFO fs;
FATTable fat;
DirItemsOfSector dir;
BYTE SDBUF_DATA[512];

FAT32_Error FAT32_Init(void)
{
// 初始化FAT32文件系统,前提:SD卡初始化成功
// SD_ERROR代表文件系统被破坏
BYTE status;
// 读MBR
status = SD_ReadDisk((u8*)&mbr,0,1);
if(status) return FAT_SDError;
if(mbr.Signature!=0xAA55)return FAT_SignBad;
// 读DBR
status = SD_ReadDisk((u8*)&dbr,mbr.P[0].startSectorNo,1);
if(status) return FAT_SDError;
if(dbr.Signature!=0xAA55)return FAT_SignBad;
// 读FSINFO
status = SD_ReadDisk((u8*)&fs,mbr.P[0].startSectorNo+1,1);
if(status) return FAT_SDError;
if(fs.Signature!=0xAA55)return FAT_SignBad;

return FAT_NoError;
}

DWORD FAT32_ClusterToSector(DWORD clusterNum)
{
// 将簇数转换为扇区数
return mbr.P[0].startSectorNo + (DWORD)dbr.FATSectorStart + (DWORD)dbr.NumsOfFAT * dbr.SizeOfFAT + (clusterNum - dbr.RootDirCluster) * (DWORD)dbr.SectorsOfCluster;
}

DWORD FAT32_GetNextCluster(DWORD clusterNum)
{
// 通过FAT表获取该簇的下一簇,如果SD读取失败则返回INVALID_VALUE
BYTE st = SD_ReadDisk((u8*)&fat,(mbr.P[0].startSectorNo + dbr.FATSectorStart + clusterNum / 128),1);
if(st)return INVALID_VALUE;
return fat.Next[clusterNum % 128];
}

int FAT32_AllZero(BYTE* p, int n)
{
// 判断某处地址n字节是否全为0
int st = 1;
for (int i = 0; i < n && st; i++)st = *(p + i) == 0;
return st;
}

DWORD FAT32_FindUnusedDirItem(void)
{
// 寻找某一扇区目录项可用位置的偏移。调用前提:读取目录扇区到dir里
for (int i = 0; i < 16; i++)
{
if (dir.dir[i].Name[0] == 0xE5)return i;
if (FAT32_AllZero((BYTE*)(&dir.dir[i]), sizeof(DirItem)))return i;
}
return INVALID_VALUE;
}

FAT32_Error __FAT32_OpenFile(const char*Name, DWORD clusterOfDir, DWORD* FileCluster, DWORD*FileSize)
{
// 打开指定目录的文件,要求目录和文件名长度均不超过8个字节,且用“/”标识目录和文件的分隔
// 返回值为0则代表成功,并通过FileCluster和FileSize指针返回文件的起始簇和文件大小
// 有效的文件名例如:"image/1.bmp"、"image/time/2.jpg"
*FileCluster = END_FAT;
*FileSize = 0;
if(clusterOfDir == END_FAT) return FAT_CannotFindFile;
if(clusterOfDir == INVALID_VALUE) return FAT_SDError;
// 寻找文件
BYTE fileName[8];
BYTE ExtendName[3];
u8 fk=0;
u8 st=0;
DWORD Size;
for(fk=0;fk<8;fk++)
{
if(!st)
{
if(Name[fk]=='/')st=1;
else if(Name[fk]=='.')st=2;
}
if(st==1||st==2)fileName[fk]=' ';
else fileName[fk]=(Name[fk]>='a'&&Name[fk]<='z'?Name[fk]-'a'+'A':Name[fk]);
}
if(!st) return FAT_FileNameInvalid;

if(st==1)
{
// 寻找目录的簇号
const char*p=Name;
while(*p!='/')p++;
p++;
return __FAT32_OpenFile(p, FAT32_FindDirItems(clusterOfDir, fileName, ExtendName, 1, &Size), FileCluster, FileSize);
}
else
{
// 寻找文件的簇号
const char*p=Name;
while(*p!='.')p++;
p++;
for(int i=0;i<3;i++)
{
if(*p)
{
ExtendName[i]=(*p>='a'&&*p<='z'?*p-'a'+'A':*p);
p++;
}
else ExtendName[i]=' ';
}
if(*p) return FAT_FileNameInvalid;
*FileCluster = FAT32_FindDirItems(clusterOfDir, fileName, ExtendName, 0, FileSize);
if(*FileCluster == INVALID_VALUE) return FAT_SDError;
if(*FileCluster == END_FAT) return FAT_CannotFindFile;
}
return FAT_NoError;
}

DWORD FAT32_FindDirItems(DWORD clusterNum, BYTE Name[8], BYTE Extend[3], int isDir, DWORD* FileSize)
{
// 从指定簇号的目录项寻找要打开的目录或者文件
// 返回值为指定目录或文件的起始簇号,若没有指定文件则返回END_FAT
// 若读取中途发生错误则返回INVALID_VALUE
// 若寻找到文件则同时通过FileSize返回文件大小
*FileSize = 0;
while (clusterNum != END_FAT)
{
DWORD sect = FAT32_ClusterToSector(clusterNum);
for (int j = 0; j < dbr.SectorsOfCluster; j++)
{
if(SD_ReadDisk((u8*)(&dir),sect,1)) return INVALID_VALUE;
for (int i = 0; i < 16; i++)
{
if(dir.dir[i].Name[0]==0xE5)continue;
if(isDir&&(dir.dir[i].Attribute&0x10))
{
int st=1;
for(int j=0;j<8&&st;j++)
st=Name[j]==dir.dir[i].Name[j];
if(st) return (DWORD)dir.dir[i].HighCluster*65536+(DWORD)dir.dir[i].LowCluster;
}
else if(!isDir&&(dir.dir[i].Attribute&0x20))
{
int st1 = 1, st2 = 1;
for (int j = 0; j < 8 && st1; j++)
st1 = Name[j] == dir.dir[i].Name[j];
for (int j = 0; j < 3 && st2; j++)
st2 = Extend[j] == dir.dir[i].ExtName[j];
if(st1&&st2)
{
*FileSize = dir.dir[i].FileSize;
return (DWORD)dir.dir[i].HighCluster*65536+(DWORD)dir.dir[i].LowCluster;
}
}
}
sect++;
}
clusterNum = FAT32_GetNextCluster(clusterNum);
if(clusterNum == INVALID_VALUE) return INVALID_VALUE;
}
return clusterNum;
}

DWORD StartCluster;
DWORD CurCluster;
DWORD CurSector;
DWORD fSize;
DWORD Cluster_off;
FAT32_Error __FAT32_CreateFile(const char*Name, DWORD clusterOfDir, DWORD* sectorOfDirItem, DWORD* idx, int cover)
{
// 创建指定目录的文件,要求目录和文件名长度均不超过8个字节,且用“/”标识目录和文件的分隔
// 返回值为0则代表成功,要求文件所在的目录必须存在。若成功则返回目录项所在的扇区数和索引,供写入文件使用
// 有效的文件名例如:"image/1.bmp"、"image/time/2.jpg"
// 被动函数,由OV7725的函数调用
StartCluster=0;
CurCluster=0;
CurSector=0;
fSize=0;
Cluster_off=0;
*sectorOfDirItem = INVALID_VALUE;
*idx = INVALID_VALUE;
if(clusterOfDir == END_FAT) return FAT_CannotFindFile;
if(clusterOfDir == INVALID_VALUE) return FAT_SDError;
// 寻找文件的目录
BYTE fileName[8];
BYTE ExtendName[3];
u8 fk=0;
u8 st=0;
DWORD Size;
for(fk=0;fk<8;fk++)
{
if(!st)
{
if(Name[fk]=='/')st=1;
else if(Name[fk]=='.')st=2;
}
if(st==1||st==2)fileName[fk]=' ';
else fileName[fk]=(Name[fk]>='a'&&Name[fk]<='z'?Name[fk]-'a'+'A':Name[fk]);
}
if(!st) return FAT_FileNameInvalid;

if(st==1)
{
// 寻找目录的簇号
const char*p=Name;
while(*p!='/')p++;
p++;
return __FAT32_CreateFile(p, FAT32_FindDirItems(clusterOfDir, fileName, ExtendName, 1, &Size), sectorOfDirItem, idx, cover);
}
else
{
const char*p=Name;
while(*p!='.')p++;
p++;
for(int i=0;i<3;i++)
{
if(*p)
{
ExtendName[i]=(*p>='a'&&*p<='z'?*p-'a'+'A':*p);
p++;
}
else ExtendName[i]=' ';
}
if(*p) return FAT_FileNameInvalid;

DWORD c,s;
if(!cover)if(__FAT32_OpenFile(Name,clusterOfDir,&c,&s)!=FAT_CannotFindFile)return FAT_FileExisted;

DWORD LastClusterOfDir = clusterOfDir;
while (clusterOfDir != END_FAT)
{
*sectorOfDirItem = FAT32_ClusterToSector(clusterOfDir);
for (int i = 0; i < dbr.SectorsOfCluster; i++)
{
if(SD_ReadDisk((u8*)&dir,*sectorOfDirItem,1))return FAT_SDError;
*idx = FAT32_FindUnusedDirItem();
if (~(*idx))break;
(*sectorOfDirItem)++;
}
if (~(*idx))break;
LastClusterOfDir = clusterOfDir;
clusterOfDir = FAT32_GetNextCluster(clusterOfDir);
}
if (!(~(*idx)))
{
// 为目录创建新的簇
if(SD_ReadDisk((u8*)&fat,(mbr.P[0].startSectorNo + dbr.FATSectorStart + LastClusterOfDir / 128),1))return FAT_SDError;
fat.Next[LastClusterOfDir % 128] = fs.NextAvaliableCluster;
*idx = 0;
*sectorOfDirItem = FAT32_ClusterToSector(fs.NextAvaliableCluster);

// 清空新簇的内容
DWORD Sector=*sectorOfDirItem;
for(int i=0;i<512;i++)SDBUF_DATA[i]=0;
for(int i=0;i<dbr.SectorsOfCluster;i++)
{
if(SD_WriteDisk(SDBUF_DATA,Sector,1))return FAT_SDError;
Sector++;
}

// 更新FAT1、FAT2
if(SD_WriteDisk((u8*)&fat,mbr.P[0].startSectorNo + dbr.FATSectorStart + LastClusterOfDir / 128,1)) return FAT_SDError;
if(SD_WriteDisk((u8*)&fat,mbr.P[0].startSectorNo + dbr.FATSectorStart + LastClusterOfDir / 128 + dbr.SizeOfFAT,1)) return FAT_SDError;
clusterOfDir = fs.NextAvaliableCluster;
if(SD_ReadDisk((u8*)&fat,(mbr.P[0].startSectorNo + dbr.FATSectorStart + clusterOfDir / 128),1))return FAT_SDError;
fat.Next[clusterOfDir % 128] = END_FAT;
if(SD_WriteDisk((u8*)&fat,mbr.P[0].startSectorNo + dbr.FATSectorStart + clusterOfDir / 128,1)) return FAT_SDError;
if(SD_WriteDisk((u8*)&fat,mbr.P[0].startSectorNo + dbr.FATSectorStart + clusterOfDir / 128 + dbr.SizeOfFAT,1)) return FAT_SDError;
// 更新FSINFO扇区
fs.NextAvaliableCluster++;
fs.NumsOfFreeCluster--;
if(SD_WriteDisk((u8*)&fs,mbr.P[0].startSectorNo+1,1))return FAT_SDError;
}
// 创建目录项
dir.dir[*idx].Attribute = 0x20;
for (int i = 0; i < 5; i++)dir.dir[*idx].CreateTime[i] = 0;
dir.dir[*idx].ExtName[0] = ExtendName[0];
dir.dir[*idx].ExtName[1] = ExtendName[1];
dir.dir[*idx].ExtName[2] = ExtendName[2];
dir.dir[*idx].FileSize = 0;
dir.dir[*idx].HighCluster = 0;
dir.dir[*idx].LastChangeTime = 0;
dir.dir[*idx].LastOpenTime = 0;
dir.dir[*idx].LowCluster = 0;
for (int i = 0; i < 8; i++)dir.dir[*idx].Name[i] = fileName[i];
dir.dir[*idx].Reserve = 0;
if(SD_WriteDisk((u8*)&dir,*sectorOfDirItem,1))return FAT_SDError;
}
return FAT_NoError;
}

代码解释

SD卡应用层————FAT32文件系统的操作

OV7725的写函数和ESP8266的读函数在正式使用时可以直接调用。但还是有必要记录下FAT32文件系统代码的编写思路,便于日后新功能的添加和代码调试。

目前的FAT32模块仅支持短文件名,即文件名不超过8字节,扩展名不超过3字节。若后续需要长文件名功能,再补充该功能。要求SD卡的FAT32文件系统满足:1扇区大小为512字节,0号扇区为MBR表,根目录在2号簇。(这些都是Windows格式化的默认参数,不用特殊工具也改不了这些参数)

驱动SD卡的库函数使用的是正点原子提供的SDIO驱动代码,FAT32库函数由本人编写。FAT32模块中驱动SD卡的函数只用了以下两个:

1
2
u8 SD_ReadDisk(u8*buf,u32 sector,u8 cnt); 	//读SD卡,fatfs/usb调用
u8 SD_WriteDisk(u8*buf,u32 sector,u8 cnt); //写SD卡,fatfs/usb调用

因此,在使用FAT32模块前,请先调用SD卡的初始化函数,确定单片机与SD卡可以正常通信后再使用FAT32模块的代码。

FAT32模块的函数如下:

1
2
3
4
5
FAT32_Error FAT32_Init(void);
#define FAT32_OpenFile(Name,Pointer_FileCluster,Pointer_FileSize) __FAT32_OpenFile(Name,2,Pointer_FileCluster,Pointer_FileSize)
FAT32_Error FAT32_ReadFileESP8266(DWORD clusterNum, DWORD fileSize);
#define FAT32_CreateFile(Name, Pointer_sectorOfDirItem, Pointer_idx, Cover) __FAT32_CreateFile(Name, 2, Pointer_sectorOfDirItem, Pointer_idx, Cover)
FAT32_Error FAT32_WriteFileOV7725(DWORD sectorOfDirItem, DWORD idx, DWORD fileSize, BYTE* DATA);

这五个函数可供外部代码调用。

其中第一个初始化函数用于从SD卡中读取MBR、DBR、FSINFO扇区的数据,以供打开文件和写入文件的使用。

FAT32_OpenFile(Name, *FileCluster, *FileSize)用于从SD卡中打开Name文件,并通过指针返回文件所在的起始簇和文件大小。

FAT32_ReadFileESP8266(clusterNum, fileSize)用于ESP8266上传图片时调用,读取从clusterNum号簇开始,fileSize字节的数据,发送到ESP8266的串口

FAT32_CreateFile(Name, sectorOfDirItem, idx, Cover)用于创建一个空文件Name,返回它所在目录项的扇区数以及这个目录项相对于这个扇区的索引值(一个目录项为32字节,因此一个扇区可以存储16个目录项,故idx为0——15的数值)。Cover为1时则为覆盖模式,为0若文件系统中存在Name文件则函数失败返回。

FAT32_WriteFileOV7725(sectorOfDirItem, idx, fileSize, *DATA)用于OV7725保存图片时调用,调用时需要给出CreateFile返回的sectorOfDirItem和idx,以及要写入的文件大小,并提供文件前512字节的数据。第一次调用时会为文件分配新的簇号,以及记录仍需写入多少字节数据,最后一次的调用(即写入字节数<=512字节)会更新FSINFO扇区、FAT1和FAT2的数据。调用者(OV7725_SaveBMPFile)不需要考虑第几次调用写函数,写函数内部的代码会自行判断。

以下是用于FAT32内部模块的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 根据DBR和MBR的参数将簇号转换成扇区号
DWORD FAT32_ClusterToSector(DWORD clusterNum);

// 通过FAT1表获取当前簇号的下一簇号,可能返回END_FAT即没有下一簇号
DWORD FAT32_GetNextCluster(DWORD clusterNum);

// 判断某内存区域是否全为0
int FAT32_AllZero(BYTE* p, int n);

// 根据读入进来的目录扇区,寻找未使用的目录项
DWORD FAT32_FindUnusedDirItem(void);

// 打开指定目录的文件,需要提供目录的簇号。供外部模块使用的宏将目录的簇号定义为2即根目录
FAT32_Error __FAT32_OpenFile(const char*Name, DWORD clusterOfDir, DWORD* FileCluster, DWORD* FileSize);

// 寻找某一目录中指定名字的目录或文件
DWORD FAT32_FindDirItems(DWORD clusterNum, BYTE Name[8], BYTE Extend[3], int isDir, DWORD* FileSize);

// 创建指定目录的文件,需要提供目录的簇号。供外部模块使用的宏将目录的簇号定义为2即根目录
FAT32_Error __FAT32_CreateFile(const char*Name, DWORD clusterOfDir, DWORD* sectorOfDirItem, DWORD* idx, int cover);

另外还根据FAT32文件系统结构定义了MBR、DBR、FAT表、FSINFO、目录项结构体,详细信息请见FAT32文件系统结构。