elm FatFs文件系统移植总结

1 前言

本文将根据我的一些理解,针对elm FatFs文件系统做一个初步总结。

2 elm FatFs文件系统介绍

顾名思义FatFs文件系统就是针对FAT文件系统来的,主要是应用于MCU中,STM32官方提供的文件系统就是这个,STM32CubeMx工具也集成了这个文件系统,同时一些国产MCU操作系统中也集成了这个文件系统,比如RTT(rt-thread),它是第三方提供的开源代码,是一个日本人开发的,开源官网为: http://elm-chan.org/fsw/ff/00index_e.html .

FatFs文件系统之所以这么流行,主要是因为它简单易用,移植非常方便。

elm FatFs文件系统移植总结_第1张图片

图1 FatFs Module

如上图,中间绿色部分为FatFs文件系统模块。APP通过FatFs模块来实现对存储模块的读写访问。FatFs模块不但可以管理单个存储设备,同时也可以管理多个,如下图所示:

elm FatFs文件系统移植总结_第2张图片

图2 FatFs可以管理一到多个存储设备

如上图,蓝色部分为FatFs模块,绿色部分为读写外部存储设备的驱动接口。蓝色部分与绿色部分是分开的,也就是说,FatFs是与底层分开的,它是完全抽象出来的独立于HAL层之上的中间件模块。上层APP正是通过这个中间件模块来实现对底层存储模块的访问,而访问的手段或接口集合我们称之为驱动。一个驱动往往与一个具体的存储设备对应,FatFs与驱动是分开的,那么,我们需要将存储设备对应的操作驱动注册或链接到文件系统中,这样,文件系统才知道它底层有这个一个存储设备,并且可以通过这个驱动来操作它,当然,也可以注册多个设备的驱动,如上图右边的图b,这样,文件系统就知道它底层有多个存储设备,并在注册的过程中,为每个驱动分配一个唯一的卷号,以便后面使用卷号来操作存储设备中的文件。

3 FatFs文件系统移植

elm FatFs文件系统移植总结_第3张图片

图3 FatFs源码文件组织

如上图,蓝色部分为FatFs文件系统,ff.h负责对APP提供使用接口,ffconf.h为FatFs文件系统的配置参数,integer.h为FatFs文件系统内部通用数据类型定义,便于跨平台移植,diskio.h负责连接FatFs与存储设备驱动。因此,基于不同的存储设备,实现FatFs与底层驱动的对接是关键。

要实现FatFs与底层驱动的对接,我们首先得为底层存储设备实现一套驱动接口,FatFs为这个底层存储设备驱动专门定义了一个数据结构:

typedef struct
{
  DSTATUS (*disk_initialize) (BYTE);                     /*!< Initialize Disk Drive                     */
  DSTATUS (*disk_status)     (BYTE);                     /*!< Get Disk Status                           */
  DRESULT (*disk_read)       (BYTE, BYTE*, DWORD, UINT);       /*!< Read Sector(s)                            */
#if _USE_WRITE == 1 
  DRESULT (*disk_write)      (BYTE, const BYTE*, DWORD, UINT); /*!< Write Sector(s) when _USE_WRITE = 0       */
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1  
  DRESULT (*disk_ioctl)      (BYTE, BYTE, void*);              /*!< I/O control operation when _USE_IOCTL = 1 */
#endif /* _USE_IOCTL == 1 */

}Diskio_drvTypeDef;

即初始化,获取状态,读,写,控制着5个操作接口,一套这样的接口(也就是一个驱动)对应着一个存储设备。移植时,用户需要自己根据实际情况来实现这个驱动,当然,如果使用CubeMx来实现对U盘或SD卡读取的功能,CubeMx可以自动生成这个驱动。比如CubeMx为SD卡自动生成sd_diskio.c文件,为U盘则自动生成usbh_diskio.c这个U盘的驱动源码文件,如下:

elm FatFs文件系统移植总结_第4张图片

图4 驱动文件

并在各自驱动文件中定义驱动实例:

const Diskio_drvTypeDef  USBH_Driver =
{
  USBH_initialize,
  USBH_status,
  USBH_read, 
#if  _USE_WRITE == 1
  USBH_write,
#endif /* _USE_WRITE == 1 */  
#if  _USE_IOCTL == 1
  USBH_ioctl,
#endif /* _USE_IOCTL == 1 */
};
const Diskio_drvTypeDef  SD_Driver =
{
  SD_initialize,
  SD_status,
  SD_read, 
#if  _USE_WRITE == 1
  SD_write,
#endif /* _USE_WRITE == 1 */

#if  _USE_IOCTL == 1
  SD_ioctl,
#endif /* _USE_IOCTL == 1 */
};

有了存储设备的驱动实例,接下来我们需要将其注册进FatFs模块中,以便由其管理这两个存储设备。

/*## FatFS: Link the SD driver ###########################*/
  retSD = FATFS_LinkDriver(&SD_Driver, SD_Path);
  /*## FatFS: Link the USBH driver ###########################*/
  retUSBH = FATFS_LinkDriver(&USBH_Driver, USBH_Path);

如上代码就是将SD卡驱动与U盘驱动注册进FatFs模块,注册后,FatFs会为驱动分配一个唯一的盘符路径并返回保存在传入的路径中,如SD_Path,USBH_Path,其内容为”0:/”, “1:/”。这个就是盘符了。

这样就将驱动注册进FatFs模块,接下来检查驱动与HAL之间的BSP,比如SD卡往往是通过一个GPIO口的状态来判断是否当前SD卡是否存在:
bsp_driver_sd.c:

uint8_t BSP_SD_IsDetected(void)
{
  __IO uint8_t status = SD_PRESENT;

  /* USER CODE BEGIN 1 */
  /* user code can be inserted here */
    if(HAL_GPIO_ReadPin(SD_DETECT_GPIO_Port, SD_DETECT_Pin) != GPIO_PIN_RESET)
    {
        status =SD_NOT_PRESENT;
    }
  /* USER CODE END 1 */     

  return status;
}

这个是CubeMx无法帮忙自动实现的,需要手动实现。

另外,U盘的供电往往也是通过一个GPIO管脚的输出来控制,这个也需要手动来实现,比如:
usbh_conf.c:

USBH_StatusTypeDef  USBH_LL_DriverVBUS (USBH_HandleTypeDef *phost, uint8_t state)
{

  /* USER CODE BEGIN 0 */
  /* USER CODE END 0*/
  if (phost->id == HOST_FS)
  {
    if (state == 0)
    {
      /* Drive high Charge pump */
      /* ToDo: Add IOE driver control */
      /* USER CODE BEGIN DRIVE_HIGH_CHARGE_FOR_FS */
        HAL_GPIO_WritePin(GPIOH, GPIO_PIN_5, GPIO_PIN_SET);//disable usb VBUS
      /* USER CODE END DRIVE_HIGH_CHARGE_FOR_FS */
    }
    else
    {
      /* Drive low Charge pump */
      /* ToDo: Add IOE driver control */
      /* USER CODE BEGIN DRIVE_LOW_CHARGE_FOR_FS */
    HAL_GPIO_WritePin(GPIOH, GPIO_PIN_5, GPIO_PIN_RESET);
      /* USER CODE END DRIVE_HIGH_CHARGE_FOR_FS */
    }
  }
  HAL_Delay(200);
  return USBH_OK;
}

这样基本移植OK了,下面我们来介绍使用U盘或者SD卡内文件系统需要注意的事项。

4 使用文件系统

在正式访问文件系统之前,我们需要挂载文件系统,U盘内部存在一个FAT32文件系统,SD卡内部也存在一个文件系统,我们需要在U盘或者SD卡插入后来挂载它,并且在其拔出后卸载它。因此,我们需要一个比较可靠的SD卡和U盘拔插检测机制。

U盘和SD卡的拔插检测一般通过在主线程内的一个for循环内来检测,通过判断某一个状态标识,一旦发现插入,则挂载其文件系统到指定路径,挂载时,需要指定一个FATFS变量,这个变量最好使用静态全局变量。卸载是,使用NULL替代FATFS变量,表示卸载,如下U盘挂载与卸载示例:

if (pre_state != Appli_state)
{
switch(Appli_state)
    {
        case APPLICATION_DISCONNECT: //USB flash disk remove
        /* Register the file system object to the FatFs module */
            if(f_mount(NULL, USBH_Path, 0) != FR_OK)
            {
                USBH_UsrLog("ERROR : Cannot exit FatFs! \n");
            }
            break;
        case APPLICATION_READY:   //USB flash disk plugin
            /* Open or create a log file and ready to append */
            if(f_mount(&usbFs, USBH_Path, 0) != FR_OK)
            {
                break;
            }
            break;
        default:
            break;
    }
    pre_state = Appli_state;
}

如上,若第一个参数为NULL,f_mount函数会将第二个参数表示的路径上挂载的文件系统卸载掉。

下面是SD卡的挂载与卸载示例:

curSdCardStatus =BSP_SD_IsDetected();
if(curSdCardStatus !=preSdCardStatus)
{
    switch(curSdCardStatus)
    {
        case SD_PRESENT:
            BSP_SD_Init();
            MountFat32FileSystem();
            break;
        case SD_NOT_PRESENT:
            UnmountFilesystem();
            break;
    }
    preSdCardStatus =curSdCardStatus;
}
static int  MountFat32FileSystem(void)
{
FRESULT ret ;

    ret =f_mount(&SDFatFs, "0:/", 0);
    if(ret!= FR_OK)
        {
            if(ret ==FR_DISK_ERR)
        {
//          if(f_mkfs((TCHAR const*)SD_Path, 0, 0) != FR_OK)        //format the SDCard with FAT32 filesystem
//          {
//              /* FatFs Format Error */
//              Error_Handler();
//          }
        }
        else
        {
                Error_Handler();
        }
        }

    return 0;
}
static int UnmountFilesystem(void)
{
    FRESULT ret;
    ret =f_mount(NULL, "0:/", 0);

    return ret;
}

这里需要注意地是,f_mount函数的第三个参数为1时,表示立即挂载,为0时,表示暂时不挂载,等正真等到后边f_open时才会执行挂载。这里建议在U盘或SD卡插入时先不要立即挂载文件系统,因为在频繁插拔时,立即挂载文件系统容易导致系统出错,为了简化,一般不需要立即挂载,等后边对文件系统访问时,再来挂载也不迟。

最后就是正常使用了,f_open,f_read,f_write等等对文件进行操作,最好能使用绝对路径,如:

fr = f_open(&fil, "1:/mytest.txt", FA_READ | FA_WRITE | FA_CREATE_ALWAYS);

这个表示打开U盘内mytext.txt文件,若不存在,则创建这个文件。盘符”1:/”在这里对应着U盘,”0:/”对应着SD卡,这个在前面提到的注册或者叫链接驱动时,FatFs为驱动分配的唯一盘符,这个是按注册先后顺序自动分配的。

5 结束

  • FatFs简单实用,对于MCU来说非常适用。
  • SD卡若适用DMA,注意DMA中断与SD卡全局中断的优先级,SD卡全局中断优先级一定要高于DMA的中断优先级。

示例源码:http://download.csdn.net/detail/flydream0/9737235

你可能感兴趣的:(STM32,STM32的世界之旅)