FAT(File Allocation Table)文件系统是一种用于磁盘驱动器、USB闪存驱动器、软盘等存储设备的文件系统。FAT文件系统主要有两种变体:FAT12和FAT16,它们后来被FAT32所取代。FAT32文件系统是对FAT16的扩展,支持更大的文件和磁盘分区。
FAT文件系统的核心是其文件分配表(FAT),这是一个表格,它记录了磁盘上每个簇的使用情况。簇是磁盘上的一组连续扇区,用于存储文件数据。在FAT文件系统中,每个文件和目录都占用一个或多个簇,并且FAT表记录了这些簇的使用情况。
以下是FAT文件系统的一些关键特点:
-
**簇分配**:FAT文件系统通过簇来管理文件数据。文件和目录信息存储在簇中,并且FAT表记录了哪些簇被文件和目录占用。
-
**文件分配表(FAT)**:FAT是一个表,它列出了每个簇的使用情况。FAT表中有两种类型的簇:空簇(未使用的簇)和已分配簇(被文件或目录占用的簇)。
-
**根目录**:FAT文件系统中的每个磁盘分区都有一个根目录,其中包含文件和目录的目录项。每个目录项都包含文件或目录的名称、大小、起始簇号等信息。
-
**文件大小限制**:FAT文件系统的文件大小受限于簇的大小。例如,FAT12和FAT16支持的最大文件大小为2GB,而FAT32支持的最大文件大小为2TB。
-
**兼容性**:FAT文件系统与不同的操作系统兼容,包括Windows、Linux和macOS。这使得FAT文件系统成为跨平台文件共享的理想选择。
-
**简单性**:FAT文件系统相对简单,易于实现和维护。这使得它成为许多嵌入式系统、旧计算机和便携式存储设备的首选文件系统。
-
**性能**:FAT文件系统具有较好的性能,因为它不需要复杂的索引结构来访问文件。文件数据直接存储在磁盘上的簇中,这使得文件访问速度较快。
FAT文件系统在历史上非常流行,但随着技术的发展,更高级的文件系统如NTFS(用于Windows)、EXT4(用于Linux)和APFS(用于macOS)等逐渐取代了FAT文件系统。然而,由于其简单性和广泛的兼容性,FAT文件系统仍然在一些旧设备和特殊应用中得到使用。
目前单片机中用的最多的文件系统就是fat32,得益于正点原子和野火的大量资料,让我们很快的就能开发出来相关的功能,本篇用来记录一次fat32的移植过程,芯片采用的是华大的PETB,flash才采用的是GD25Q40,虽然是GD系列的存储芯片,但是命令上基本兼容W25Q系列。
fat32源码的下载,可以从github下载:strawberryhacker/fat32: Tiny FAT32 file system implementation. (github.com)
也可以从官网下载,(不知道为什么我在家的电脑打不开fat官网,有遇到类似情况的小伙伴告诉我解决方案!)
可以配置ffconf.h的修改,可以参考其他博文。
这里只看diskio的移植:
cpp
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "ev_hc32f460_lqfp100_v2_w25qxx.h"
/* Definitions of physical drive number for each drive */
#define DEV_RAM 0 /* Example: Map Ramdisk to physical drive 0 */
#define DEV_MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
#define DEV_USB 2 /* Example: Map USB MSD to physical drive 2 */
// jinyuhang 本篇移植,只考虑了外部falsh,后续考虑SD卡
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat=RES_OK;
// BSP_W25QXX_Init();
return stat;
// int result;
// switch (pdrv) {
// case DEV_RAM :
// result = RAM_disk_status();
// // translate the reslut code here
// return stat;
// case DEV_MMC :
// result = MMC_disk_status();
// // translate the reslut code here
// return stat;
// case DEV_USB :
// result = USB_disk_status();
// // translate the reslut code here
// return stat;
// }
// return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
// DSTATUS stat;
// int result;
// switch (pdrv) {
// case DEV_RAM :
// result = RAM_disk_initialize();
// // translate the reslut code here
// return stat;
// case DEV_MMC :
// result = MMC_disk_initialize();
// // translate the reslut code here
// return stat;
// case DEV_USB :
// result = USB_disk_initialize();
// // translate the reslut code here
// return stat;
// }
// return STA_NOINIT;
DSTATUS stat=RES_OK;
BSP_W25QXX_Init();
return stat;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
// DRESULT res;
// int result;
// switch (pdrv) {
// case DEV_RAM :
// // translate the arguments here
// result = RAM_disk_read(buff, sector, count);
// // translate the reslut code here
// return res;
// case DEV_MMC :
// // translate the arguments here
// result = MMC_disk_read(buff, sector, count);
// // translate the reslut code here
// return res;
// case DEV_USB :
// // translate the arguments here
// result = USB_disk_read(buff, sector, count);
// // translate the reslut code here
// return res;
// }
// return RES_PARERR;
BSP_W25QXX_Read(sector * 512U, buff, (uint32_t)count * 512U);
return RES_OK;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
static void W25QXX_Write_NoCheck(uint8_t *pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint16_t pageremain;
pageremain = (uint16_t)(256U - WriteAddr % 256U);
if (NumByteToWrite <= pageremain) {
pageremain = NumByteToWrite;
}
for (;;) {
(void)BSP_W25QXX_Write(WriteAddr, pBuffer, pageremain);
if (NumByteToWrite == pageremain) {
break;
} else { //NumByteToWrite>pageremain
pBuffer += pageremain;
WriteAddr += pageremain;
NumByteToWrite -= pageremain;
if (NumByteToWrite > 256U) {
pageremain = 256U;
} else {
pageremain = NumByteToWrite;
}
}
}
}
static uint8_t u8CopybackBuf[W25Q64_SECTOR_SIZE];
static void SpiFlashWrite(uint8_t *pbuf, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t *pu8CbBuf;
pu8CbBuf = u8CopybackBuf;
secpos = WriteAddr / W25Q64_SECTOR_SIZE;
secoff = (uint16_t)(WriteAddr % W25Q64_SECTOR_SIZE);
secremain = (uint16_t)(W25Q64_SECTOR_SIZE - secoff);
if (NumByteToWrite <= secremain) {
/* less than 4K */
secremain = NumByteToWrite;
}
for (;;) {
(void)BSP_W25QXX_Read(secpos * W25Q64_SECTOR_SIZE, pu8CbBuf, W25Q64_SECTOR_SIZE);
/* check if blank sector */
for (i = 0U; i < secremain; i++) {
if (pu8CbBuf[secoff + i] != 0XFFU) {
break;
}
}
if (i < secremain) {
/* not blank, need erase */
(void)BSP_W25QXX_EraseSector(secpos * W25Q64_SECTOR_SIZE);
/* backup first */
for (i = 0U; i < secremain; i++) {
pu8CbBuf[i + secoff] = pbuf[i];
}
/* write back after erase */
W25QXX_Write_NoCheck(pu8CbBuf, secpos * W25Q64_SECTOR_SIZE, (uint16_t)W25Q64_SECTOR_SIZE);
} else {
W25QXX_Write_NoCheck(pbuf, WriteAddr, secremain);
}
if (NumByteToWrite == secremain) {
break;
} else {
/* next sector */
secpos++;
secoff = 0U;
pbuf += secremain;
WriteAddr += secremain;
NumByteToWrite -= secremain;
if (NumByteToWrite > W25Q64_SECTOR_SIZE) {
secremain = (uint16_t)W25Q64_SECTOR_SIZE;
} else {
secremain = NumByteToWrite;
}
}
}
}
#if FF_FS_READONLY == 0
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
LBA_t sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
// DRESULT res;
// int result;
// // switch (pdrv) {
// // case DEV_RAM :
// // // translate the arguments here
// // result = RAM_disk_write(buff, sector, count);
// // // translate the reslut code here
// // return res;
// // case DEV_MMC :
// // // translate the arguments here
// // result = MMC_disk_write(buff, sector, count);
// // // translate the reslut code here
// // return res;
// // case DEV_USB :
// // // translate the arguments here
// // result = USB_disk_write(buff, sector, count);
// // // translate the reslut code here
// // return res;
// // }
// return RES_PARERR;
SpiFlashWrite(( BYTE *)buff, sector * 512U, (uint32_t)count * 512U);
return RES_OK;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
// int result;
switch (pdrv) {
case DEV_RAM :
// Process of the command for the RAM drive
return res;
case DEV_MMC :
// Process of the command for the MMC/SD card
return res;
case DEV_USB :
// Process of the command the USB drive
return res;
}
return RES_PARERR;
}
//
DWORD get_fattime (void)
{
return 0;
}
主要实现初始化,读和写的函数就可以了,如果大家看了关于USB的例程的话,就会发现这里的读写函数就是USB-msc例程中的读写函数,完全可以使用。也得到了一个经验:USB的msc功能需要配置的读写和fat32需要的读写接口都是一样的,想想这个事情也合理,USB读取U盘也是fat32格式。
实测功能正常。需要注意的是,fat32是单片机读写flash的操作,USB的msc操作是通过USB主机如电脑读写flash的操作,这两个操作应该是独立的,否则会有冲突。在实际的项目中,如果接上USB,那么单片机内部的fat32就不应该再发生读写等冲突的操作了。