本文将根据我的一些理解,针对elm FatFs文件系统做一个初步总结。
顾名思义FatFs文件系统就是针对FAT文件系统来的,主要是应用于MCU中,STM32官方提供的文件系统就是这个,STM32CubeMx工具也集成了这个文件系统,同时一些国产MCU操作系统中也集成了这个文件系统,比如RTT(rt-thread),它是第三方提供的开源代码,是一个日本人开发的,开源官网为: http://elm-chan.org/fsw/ff/00index_e.html .
FatFs文件系统之所以这么流行,主要是因为它简单易用,移植非常方便。
如上图,中间绿色部分为FatFs文件系统模块。APP通过FatFs模块来实现对存储模块的读写访问。FatFs模块不但可以管理单个存储设备,同时也可以管理多个,如下图所示:
如上图,蓝色部分为FatFs模块,绿色部分为读写外部存储设备的驱动接口。蓝色部分与绿色部分是分开的,也就是说,FatFs是与底层分开的,它是完全抽象出来的独立于HAL层之上的中间件模块。上层APP正是通过这个中间件模块来实现对底层存储模块的访问,而访问的手段或接口集合我们称之为驱动。一个驱动往往与一个具体的存储设备对应,FatFs与驱动是分开的,那么,我们需要将存储设备对应的操作驱动注册或链接到文件系统中,这样,文件系统才知道它底层有这个一个存储设备,并且可以通过这个驱动来操作它,当然,也可以注册多个设备的驱动,如上图右边的图b,这样,文件系统就知道它底层有多个存储设备,并在注册的过程中,为每个驱动分配一个唯一的卷号,以便后面使用卷号来操作存储设备中的文件。
要实现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盘的驱动源码文件,如下:
并在各自驱动文件中定义驱动实例:
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卡内文件系统需要注意的事项。
在正式访问文件系统之前,我们需要挂载文件系统,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为驱动分配的唯一盘符,这个是按注册先后顺序自动分配的。
示例源码:http://download.csdn.net/detail/flydream0/9737235