Cortex-m0 LPC1114移植FatFs文件系统

  • 学习4847次

一、入门引导

FatFs是一个通用的文件系统模块,用于在小型嵌入式系统中实现FAT文件系统。 FatFs 的编写遵循ANSI C,因此不依赖于硬件平台。它可以嵌入到便宜的微控制器中,如 8051, PIC, AVR, SH, Z80, H8, ARM 等等,不需要做任何修改。

这是一真正开源免费的文件系统。为什么是真正呢?因为你不仅可以免费下载到源代码,免费学习研究,最重要的是用在商业用途也不用向作者缴费。最后一点可是很多打着“开源”旗号的东西比不了的。

它的文件结构图如下所示。源代码网址:http://elm-chan.org/fsw/ff/00index_e.html

fatfs结构

支持FAT,FAT16,FAT32;

支持多扇区读写,使得效率更高;

支持长文件名读写;

支持中文;

移植步骤超级简单。

二、移植

这里FatFs是移植的R0.08版本。

下载下来以后,里面有这么几个文件:

fatfs文件

在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结构。在这里,如果你有时间,可以把所有的中日韩符号加上去。不过我觉得还是加上一些常用的就可以了。如果是短文件名的话,支持所有的中日韩符号。因为上面这个函数是在文件系统读写长文件名的时候才会用到的。

发表评论