花了几天的时间好不容易自己移植好了FATFS,以前一般都是用别个现成的东西,真的自己移植还是有一点点的操蛋。
移植FATFS其实不难,当然这是对于一个成功移植好的人来说。FATFS移植资料网上有一大堆,但是在移植成功之前还是搞得我一头雾水.
1、准备工作
硬件:STM32F4+W25Q64
软件:FATFS 0.1版本,好像现在最高的就是0.1版的吧,没去细查。
FATFS官方源码下载好之后只有几个文件。
添加两个C文件进去就好了,添加文件这种事比较基础,没什么好说的。
只要我这截图上有的文件都加上去了,基本上就可以了。
这里面需要我们修改的是diskio.c,其它文件不用动,当然有时候动动也没事,我用别人的东西总喜欢看看里面是什么。
在diskio.c里面,一共有6个接口函数,我按照重要性从最不重要的开始讲
参考正点原子的代码,我在开头宏定义了扇区大小等内容。
#define FLASH_SECTOR_SIZE 512
#define FLASH_BLOCK_SIZE 8
uint32_t FLASH_SECTOR_COUNT = 2048*6;
这三个内容在diskio.c都会用到。这里解释一下这三个东西,我之前查这个搞了半天没理解
FLASH_SECTOR_SIZE 是指一个扇区块的大小 512个字节
FLASH_BLOCK_SIZE 一个扇区分成了8块,也就是4K的大小,为什么是八块,百度后发现是因为与FAT类型有关,具体解释在正点原子
FLASH_SECTOR_COUNT 这一段的内容是指有多少个扇区块,有什么用呢,在格式化W25Q64的时候,这个就可以认为是我的W25Q64的容量
因为在W25Q64里面是没有MBR/DBR这些大小的,所以需要使用FATFS格式化成一个存储设备
要使用格式化就需要使用diskio.c里面的disk_ioctl函数
这是第一个函数。
#if _USE_IOCTL
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT res;
switch(cmd)
{
case CTRL_SYNC:
res = RES_OK;
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
res = RES_OK;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
res = RES_OK;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
res = RES_OK;
break;
default:
res = RES_PARERR;
break;
}
return res;
}
#endif
从函数可以看到,这里需要修改宏定义_USE_IOCTL,这个定义在FATFS附带的头文件ffconf.h里面
#define _USE_WRITE 1 /* 1: Enable disk_write function */
#define _USE_IOCTL 1 /* 1: Enable disk_ioctl fucntion */
同样在ffconf.h里面,需要配置
#define _USE_MKFS 1 /* 0:Disable or 1:Enable */
/* To enable f_mkfs() function, set _USE_MKFS to 1 and set _FS_READONLY to 0 */
如果不置1,可能编译报错
然后是第二个函数,不用理他,一个获取时间的
/*-----------------------------------------------------------------------*/
/* Get current time */
/*-----------------------------------------------------------------------*/
DWORD get_fattime(void)
{
return RES_OK;
}
第三个函数,获取状态,也不用理他
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber (0..) */
)
{
return RES_OK;
}
第四个函数是初始化函数,把自己W25Q64的初始化接口放进去就可以了
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber (0..) */
)
{
//DSTATUS stat;
uint8_t result;
if(pdrv)
{return STA_NOINIT;}
result = W25Q64Init();
if(result==0)
{return RES_OK;}
else
{return STA_NOINIT;}
}
做初始化函数之前,一定要自己测试可以正常读写W25Q64,SD卡也是一样的
一般来说,这四个函数都不会有什么问题,影响成功关键的是读写的接口函数,首先是写的函数,这里最好是一次写512个字节的数据。
我的做法是调用FATFS自带的格式化函数,这个函数会使用到写函数,有一段内容需要写进来,这段内容是DBR,这里提供一个我参考的链接:http://www.devlabs.cn/?p=226
我首先在main函数里面挂载文件系统,紧接着格式化设备,一定要先挂载,再格式化,否则可能提示找不着设备
result = f_mount(&fs, "0:", 1); //挂载文件系统
result = f_mkfs("0:",0,4096); //格式化W25Q64,重点刷入DBR数据
挂载文件系统如果返回报错,就用
result = f_mount(&fs, "0:",0);
她们的区别是最后一个参数,1为立即挂载,0是在执行例如OPEN等操作的时候才挂载
f_mkfs("0:",0,4096);这个建议用我这种,第一个参数是路径,第二个是参数0是需要引导区,1是不要引导区
这里也决定了格式化完了之后是什么设备 0:FDISK, 1:SFD 1是那种超级软盘。
最后一个参数4096可以随便了,大家可以用0 ,自动分配
#if _USE_WRITE
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber (0..) */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address (LBA) */
BYTE count /* Number of sectors to write (1..128) */
)
{
BYTE tmp;
// uint8_t Reardata[FLASH_SECTOR_SIZE];
if (!count) return RES_PARERR;
for(;count>0;count--)
{
tmp = W25Q64Write(sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE,(uint8_t*)buff);
// W25Q64Read(sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE, Reardata);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
if(tmp==0)
return RES_OK;
else
return RES_ERROR;
}
这个是我的写函数,网上很多帖子没说这里要个什么效果,我这里说一下
执行格式化函数,在FF.H里面会调用这个函数,去写设备的引导区。
第一次进来的时候,也就是sector=0的时候,读取出来要有512个数据,前面510个数据是0,然后紧跟55 AA
第二次进来的时候,sector=0x3f,稍后解释,读出来512个字节,数据里面要有FAT字样,最后两个数据是 55 AA
之后就不要管了,只要做到这个效果,写函数基本完成了。
不过如果大家使用的是
f_mkfs("0:",1,4096); 初始化成超级软盘,第一次进来直接就是第二次的数据,就是说第一次就可以看到FAT字样,结尾数据仍然要有
判断W25Q64是不是有一个可用的文件系统,在FF.H里面
static
BYTE check_fs ( /* 0:FAT boor sector, 1:Valid boor sector but not FAT, 2:Not a boot sector, 3:Disk error */
FATFS* fs, /* File system object */
DWORD sect /* Sector# (lba) to check if it is an FAT boot record or not */
)
{
fs->wflag = 0; fs->winsect = 0xFFFFFFFF; /* Invaidate window */
if (move_window(fs, sect) != FR_OK) /* Load boot record */
return 3;
if (LD_WORD(&fs->win[BS_55AA]) != 0xAA55) /* Check boot record signature (always placed at offset 510 even if the sector size is >512) */
return 2;
if ((LD_DWORD(&fs->win[BS_FilSysType]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */
return 0;
if ((LD_DWORD(&fs->win[BS_FilSysType32]) & 0xFFFFFF) == 0x544146) /* Check "FAT" string */
return 0;
return 1;
}
首先判断数据结尾是不是55 AA,然后判断有没有FAT字样,这就是我之前要大家注意的两步。
初始化成FDISK,这函数会进来两次,作用是,第一次给写函数的sector=0 ,第二次是0x3f
写函数还有个要求,就是这一次写数据,要保证不把同扇区的其它数据给改变了,发一下我的鞋函数
uint8_t W25QXX_BUFFER[4096];
uint8_t W25Q64Write(uint32_t Addr, uint32_t num, uint8_t * data)
{
uint32_t secpos;
uint16_t secoff;
uint16_t secremain;
uint16_t i;
uint8_t Test_Reardata[512];
secpos=Addr/4096;//扇区地址
secoff=Addr%4096;//在扇区内的偏移
secremain=4096-secoff;//扇区剩余空间大小
if(num<=secremain) secremain = num;//不大于4096个字节
while(1)
{
W25Q64Read(secpos*4096,4096,W25QXX_BUFFER);//读出整个扇区的内容
for(i=0;i
if(W25QXX_BUFFER[secoff+i]!=0XFF) break;//需要擦除
}
if(i
W25Q64SectorErase(secpos*4096);//擦除整个扇区
for(i=0;i
W25QXX_BUFFER[i+secoff]=data[i];
}
// W25Q64Read(secpos*4096, 512, Test_Reardata);
W25QXX_Write_NoCheck(W25QXX_BUFFER,secpos*4096,4096);//写入整个扇区
}else
{
W25QXX_Write_NoCheck(data,Addr,secremain);//写入整个扇区
}
if(num==secremain)break;//写入结束
else//写入未结束
{
secpos++;//扇区地址加1
secoff=0;//偏移位置为0
data+=secremain; //指针偏移
Addr+=secremain;//写地址偏移
num-=secremain; //字节数递减
if(num>4096)secremain=4096; //下一个扇区写不完
else secremain=num; //写一个扇区可以写完
}
};
return 0;
}
然后是读函数
DRESULT disk_read (
BYTE pdrv, /* 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..128) */
)
{
BYTE tmp;
if (!count) return RES_PARERR;
for(;count>0;count--)
{
tmp=W25Q64Read(sector*FLASH_SECTOR_SIZE, FLASH_SECTOR_SIZE, buff);
// HAL_Delay(5);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
buff-=FLASH_SECTOR_SIZE;
if(tmp==0)
return RES_OK;
else
return RES_ERROR;
}
比写要简单,能读512个字节就可以了