Cortex-m0 LPC1114移植FatFs文件系统
一、入门引导
FatFs是一个通用的文件系统模块,用于在小型嵌入式系统中实现FAT文件系统。 FatFs 的编写遵循ANSI C,因此不依赖于硬件平台。它可以嵌入到便宜的微控制器中,如 8051, PIC, AVR, SH, Z80, H8, ARM 等等,不需要做任何修改。
这是一真正开源免费的文件系统。为什么是真正呢?因为你不仅可以免费下载到源代码,免费学习研究,最重要的是用在商业用途也不用向作者缴费。最后一点可是很多打着“开源”旗号的东西比不了的。
它的文件结构图如下所示。源代码网址:http://elm-chan.org/fsw/ff/00index_e.html
支持FAT,FAT16,FAT32;
支持多扇区读写,使得效率更高;
支持长文件名读写;
支持中文;
移植步骤超级简单。
二、移植
这里FatFs是移植的R0.08版本。
下载下来以后,里面有这么几个文件:
在KEIL下编译,只需修改上面的diskio.c文件和ffconf.h文件就可以了。其中,ffconf.h文件是配置文件,可以配置文件系统支持的范围。diskio.c文件是和硬件连接的几口,把里面的几个函数实现了即可。修改以后的diskio.c文件如下所示:
/*-----------------------------------------------------------------------*/ /* Inidialize a Drive?????????????????????????????????????????????? ?????*/ /*-----------------------------------------------------------------------*/ DSTATUS disk_initialize ( BYTE drv?????????????????????????? ?????? ?/* Physical drive nmuber (0..) */ ) { DSTATUS stat; stat = 0; return stat; } /*-----------------------------------------------------------------------*/ /* Return Disk Status??????????????????????????????????????????????????? */ /*-----------------------------------------------------------------------*/ DSTATUS disk_status ( BYTE drv???????????? /* Physical drive nmuber (0..) */ ) { DSTATUS stat; stat = 0; return stat; } /*-----------------------------------------------------------------------*/ /* Read Sector(s)??????????????????????????????????????????????????????? */ /*-----------------------------------------------------------------------*/ DRESULT disk_read ( BYTE drv,???????????? /* Physical drive nmuber (0..) */ BYTE *buff,???????? /* Data buffer to store read data */ DWORD sector,???? /* Sector address (LBA) */ BYTE count????????? /* Number of sectors to read (1..255) */ ) { if (count < 1) { return RES_PARERR; } if (count == 1)? /* Single block read?? */ { if (SD_ReadSingleBlock(sector, buff) != 0) { return RES_ERROR; } } else???????????? ?/* Multiple block read */ { if(SD_ReadMultiBlock(sector, buff, count) != 0) { return RES_ERROR; } } return RES_OK; } /*-----------------------------------------------------------------------*/ /* Write Sector(s)?????????????????????????????????????????????????????? */ /*-----------------------------------------------------------------------*/ /* The FatFs module will issue multiple sector transfer request /? (count > 1) to the disk I/O layer. The disk function should process /? the multiple sector transfer properly Do. not translate it into /? multiple single sector transfers to the media, or the data read/write /? performance may be drasticaly decreased. */ #if _READONLY == 0 DRESULT disk_write ( BYTE drv,??????????????????? /* Physical drive nmuber (0..) */ const BYTE *buff, /* Data to be written */ DWORD sector,??????????? /* Sector address (LBA) */ BYTE count???????????????? /* Number of sectors to write (1..255) */ ) { if (count < 1) { return RES_PARERR; } if (count == 1)? /* Single block write?? */ { if (SD_WriteSingleBlock(sector, buff) != 0) { return RES_ERROR; } } else????????????? /* Multiple block write */ { if(SD_WriteMultiBlock(sector, buff, count) != 0) { return RES_ERROR; } } return RES_OK; } #endif /* _READONLY */ /*-----------------------------------------------------------------------*/ /* Miscellaneous Functions?????????????????????????????????????????????? */ /*-----------------------------------------------------------------------*/ DRESULT disk_ioctl ( BYTE drv,???????????? /* Physical drive nmuber (0..) */ BYTE ctrl,??????????? /* Control code */ void *buff???????????? /* Buffer to send/receive control data */ ) { DRESULT res; BYTE n, csd[16]; DWORD csize; res = RES_ERROR; switch (ctrl) { case CTRL_SYNC?????? : res = RES_OK; break; case GET_SECTOR_COUNT: /* Get number of sectors on the disk (WORD) */ if(SD_GetCSD(csd) == 0) { if((csd[0] >> 6) == 1) /* SDC ver 2.00 */ { csize = ((DWORD)csd[9]) + ((DWORD)csd[8] << 8) + 1; *(DWORD*)buff = (DWORD)csize << 10; } else /* MMC or SDC ver 1.XX */ { n = (csd[5] & 0x0F) + ((csd[10] & 0x80) >> 7) + ((csd[9] & 0x03) << 1) + 2; csize = (csd[8] >> 6) + ((WORD)csd[7] << 2) + ((WORD)(csd[6] & 0x03) << 10) + 1; *(DWORD*)buff = (DWORD)csize << (n - 9); } res = RES_OK; } break; case GET_SECTOR_SIZE : /* Get sectors on the disk (WORD) */ *(DWORD*)buff = 512; res = RES_OK; break; case GET_BLOCK_SIZE? : if (SD_GetCSD(csd) == 0) /* Read CSD */ { *(DWORD*)buff = (((csd[10] & 0x3F) << 1) + ((WORD)(csd[11] & 0x80) >> 7) + 1) << ((csd[13] >> 6) - 1); res = RES_OK; } break; default????????????? : res = RES_OK; break; } return res; } /******************************************************************************* *???????????????????????????????????? End Of File *******************************************************************************/
上面一共有五个函数:
DSTATUS disk_initialize (BYTE);
DSTATUS disk_status (BYTE);
DRESULT disk_read (BYTE, BYTE*, DWORD, BYTE);
#if?? _READONLY == 0
DRESULT disk_write (BYTE, const BYTE*, DWORD, BYTE);
#endif
DRESULT disk_ioctl (BYTE, BYTE, void*);
第一个函数用来初始化SD卡;
第二个函数返回0就可以;
第三个和第四个函数是SD卡的写函数,把读写单扇区和多扇区函数放到里面就可以。
第五个函数用来获取SD卡的扇区大小等信息。把GET_SECTOR_COUNT、GET_SECTOR_SIZE、GET_BLOCK_SIZE这三个命令实现了即可。
然后,再加一个
DWORD get_fattime (void)
{
return 0;
}
函数,这个函数放到ffconf.h或ff.c文件里都可以。这个函数是返回时钟的,由于LPC1114没有实时时钟功能,这里直接返回0。
到这里。文件系统移植就成功了。不过,现在的文件系统还不支持长文件名。
计算机系的学生对短文件名和长文件名应该比较熟悉。
短文件名是指8.3格式的文件名。8是指在点之前最多有8个字节,3是指在点之后最多有3个字节。比如ration.txt就符合这个要求,richration.txt就超标了。鉴于短文件名的缺点,后来才有了长文件名。关于长短文件名的历史和故事比较渊源!
为了让文件系统支持长文件名。需要修改ffconf.h中的参数。在ffconf.h文件中,找到这个:
#define? _USE_LFN? 0??????????? /* 0 to 3 */
把上面的_USE_LFN改成1,即可。
这时候,如果想让文件系统支持中文,还需要把ffconf.h文件中的_CODE_PAGE参数修改成936,如下所示:
#define _CODE_PAGE?? 936
然后在工程中添加cc936.c文件。这个文件在option文件夹中。
这时候,中文的长文件名也可以支持了,你写一个“中华人民共和国万岁.txt”
文件都可以了。不过,这时,你编译后,会发现无法通过。原因是LPC1114的RAM,ROM实在是太小了。原来在cc936.c文件当中,存放着GBK和Unicode的相互转换表。这两张表可谓巨大呀!所以这个文件也要改动一下了。改动方法如下:
一.把两张装换表全部删除。(因为这两张表已经在W25X16中了)
二.修改一个函数。(转码函数)
三.完成。
简单吧!
在cc936.c文件当中,一共有两张表和两个函数,我们需要修改的函数如下:
WCHAR ff_convert (?? /* Converted code, 0 means conversion error */ WCHAR??? src,??? /* Character code to be converted */ UINT???????? dir????????????? /* 0: Unicode to OEMCP, 1: OEMCP to Unicode */ ) { WCHAR c; uint32 offset;?????? ? ?// W25X16地址偏移 uint8 GBKH,GBKL;?? // GBK码高位与低位 uint8 unigbk[2];?? // 暂存GBK高位与低位字节 uint8 gbkuni[2];? ?// 暂存UNICODE高位与低位字节 if (src < 0x80) {? /* ASCII */ c = src; } else { if(dir == 0) ?/* Unicode to OEMCP */ { switch(src) { case 0x3001: c = 0xA1A2;break;??? ? ?// 支持符号: 、 中文顿号 case 0x300A: c = 0xA1B6;break;??? ?// 支持符号:《 case 0x300B: c = 0xA1B7;break; ???//? 支持符号:》 case 0x201C: c = 0xA1B0;break;?? ? ?// 支持符号: “ 中文左双引号 case 0x201D: c = 0xA1B1;break;??? ?// 支持符号:”?? 中文右双引号 case 0x2606: c = 0xA1EE;break; ? ???//? 支持符号:☆ case 0x2605: c = 0xA1EF;break;??? ?? // 支持符号: ★ case 0x2018: c = 0xA1AE;break;??? ?// 支持符号:‘ 中文左单引号 case 0x2019: c = 0xA1AF;break; ???//? 支持符号:’中文右单引号 case 0x3010: c = 0xA1BE;break;??? ? ?// 支持符号: 【 case 0x3011: c = 0xA1BF;break;???? // 支持符号: 】 case 0x3016: c = 0xA1BC;break; ???//? 支持符号:〖 case 0x3017: c = 0xA1BD;break;?? ? ?// 支持符号: 〗 case 0x2299: c = 0xA1D1;break;??? ?// 支持符号:⊙ case 0x2116: c = 0xA1ED;break; ???//? 支持符号:№ case 0x2236: c = 0xA1C3;break;??? ??// 支持符号: ∶ case 0x203B: c = 0xA1F9;break;??? ?// 支持符号:※ case 0x221E: c = 0xA1DE;break; ???//? 支持符号:∞ default: if( (src > 0x4DFF) && (src < 0x9FA6) )// 汉字区 { offset = ((((uint32)src - 0x4E00) * 2) + 0x0C0000);? /* 得到W25X16的UTG地址 */ W25X16_Read(unigbk,offset,2); /* 获取GBK码 */ c = (((uint16)unigbk[0])<<8)+(uint16)unigbk[1];????? /* 把GBK码给了c */ } else c = 0xA1A1; ?// 如果是其它符号,都用符号:NULL 代替 break; } } else if(dir == 1)? /* OEMCP to Unicode */ { GBKH=(uint8)(src>>8);? // 获取GBK高位字节 GBKL=(uint8)(src);???? ?? // 获取GBK低位字节 GBKH-=0x81; GBKL-=0x40; offset=((uint32)192*GBKH+GBKL)*2;/* 得到W25X16的GTU地址 */ W25X16_Read(gbkuni,offset+0x0D0000,2);? ?/* 获取UNICODE码 */ c = (((uint16)gbkuni[1])<<8)+(uint16)gbkuni[0];????? /* 把UNICODE码给了c */ } } return c; }
这个函数的功能是,把GBK码换成Unicode码或把Unicode码换成GBK码,关于这两个表的用途,和这两个表在W25X16中的位置,你可以在第十八章《中文字库制作》里面找到。这里就不讲了。
在上面的GBK转Unicode比较简单,直接从W25X16取值即可。
Unicode转GBK码比较复杂一些,因为在我做的转换表当中,只有汉字的码,没有任何符号。所以为了在长文件名中支持一些常用符号,才有了上面的swtich结构。在这里,如果你有时间,可以把所有的中日韩符号加上去。不过我觉得还是加上一些常用的就可以了。如果是短文件名的话,支持所有的中日韩符号。因为上面这个函数是在文件系统读写长文件名的时候才会用到的。