首先,我们需要向FatFs来提供一些硬件接口,包括储存设备的初始化,读 / 写函数,硬件参数等,这些在diskio.c中实现:
diskio.c
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2014 */
/*-----------------------------------------------------------------------*/
/* 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 "diskio.h" /* FatFs lower layer API */
#include "./flash/bsp_spi_flash.h"
//#include "usbdisk.h" /* Example: Header file of existing USB MSD control module */
//#include "atadrive.h" /* Example: Header file of existing ATA harddisk control module */
//#include "sdcard.h" /* Example: Header file of existing MMC/SDC contorl module */
/* Definitions of physical drive number for each drive */
#define ATA 0 /* Example: Map ATA harddisk to physical drive 0 */
#define MMC 1 /* Example: Map MMC/SD card to physical drive 1 */
#define USB 2 /* Example: Map USB MSD to physical drive 2 */
#define SD_CARD 0
#define SPI_FLASH 1
/*-----------------------------------------------------------------------*/
/* Get Drive Status 读取驱动状态 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS stat;
// int result;
switch (pdrv) {
case SD_CARD :
// result = ATA_disk_status();
// translate the reslut code here
return stat;
case SPI_FLASH :
if (SPI_FLASH_ReadID() == sFLASH_ID)
{
// 状态正常
stat = 0;
}
else
{
// 状态异常
stat = STA_NOINIT;
}
// 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 SD_CARD :
// result = ATA_disk_initialize();
// translate the reslut code here
return stat;
case SPI_FLASH :
SPI_FLASH_Init(); // 这里只是初始化引脚和硬件SPI
SPI_Flash_WAKEUP(); // 确保FLASH不处在低功耗状态
return disk_status(SPI_FLASH);
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
/* 这里操作的单位都是扇区,要对应到实际的地址 */
DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Sector address in LBA */
UINT count /* Number of sectors to read */
)
{
DRESULT res;
// int result;
switch (pdrv) {
case SD_CARD :
// translate the arguments here
// result = ATA_disk_read(buff, sector, count);
// translate the reslut code here
return res;
case SPI_FLASH :
// 输入的是扇区号,这里要转换成地址
// W25Q64的每个扇区为4KB = 4096 字节
SPI_FLASH_BufferRead(buff, sector * 4096, count * 4096);
res = RES_OK; // 默认就认为读取正常
return res;
}
return RES_PARERR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if _USE_WRITE
DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Sector address in LBA */
UINT count /* Number of sectors to write */
)
{
DRESULT res;
// int result;
switch (pdrv) {
case SD_CARD :
// translate the arguments here
// result = ATA_disk_write(buff, sector, count);
// translate the reslut code here
return res;
case SPI_FLASH :
// 一定要先擦除再写入
SPI_FLASH_SectorErase(sector * 4096);
SPI_FLASH_BufferWrite((u8 *)buff, sector * 4096, count * 4096);
res = RES_OK;
return res;
}
return RES_PARERR;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
/* 杂项功能函数,用于传递存储器硬件相关的参数,以便于格式化文件系统 ------------*/
#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;
// int result;
switch (pdrv) {
case SD_CARD :
// Process of the command for the ATA drive
return res;
case SPI_FLASH :
switch (cmd)
{
/*
case CTRL_SYNC:
// 用于实现缓存,把要写入的文件,先保存在内存(缓存)中,然后再一次性写入到存储介质
break;
*/
// 返回扇区个数
case GET_SECTOR_COUNT:
// buff是一个void*空指针,我们根据命令格式,要转换成 DWORD* 类型的指针
*(DWORD *)buff = 2048;
break;
// 返回扇区大小
case GET_SECTOR_SIZE:
*(WORD *)buff = 4096;
break;
// 返回擦除扇区的最小个数
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
break;
}
res = RES_OK;
return res;
}
return RES_PARERR;
}
#endif
// 返回时间
DWORD get_fattime (void)
{
return 0;
}
其中包括了读取设备状态(通过读取SPI设备号来实现),存储设备初始化(stm32引脚配置和硬件SPI),读写操作(调用SPI读写函数),以及传递硬件参数的杂项函数。
然后,要在ffconf.h中进行一些配置(说明:这个主要用于裁剪,通过配置宏定义,可以实现 / 屏蔽相应功能):
// 格式化文件系统
#define _USE_MKFS 1
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
// 配置扇区的大小(有多少个字节),特别值得注意的是,两个值不要相等
#define _MIN_SS 512
#define _MAX_SS 4096
接下来就是在主函数中进行测试:
#include "stm32f10x.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"
#include "ff.h"
FATFS fsObject;
FIL fp;
UINT bw;
UINT br;
const char wData[] = "FatFs文件系统测试成功!";
char rData[4096] = "";
/*
* 函数名:main
* 描述 :主函数
* 输入 :无
* 输出 :无
*/
int main(void)
{
FRESULT res;
LED_GPIO_Config();
LED_BLUE;
/* 配置串口为:115200 8-N-1 */
USART_Config();
printf("\r\n 这是一个8Mbyte串行flash(W25Q64)实验 \r\n");
/* 挂载文件系统,就是初始化硬件GPIO接口和SPI,使能
第一个参数为文件系统句柄,第二个参数为路径,第三个参数1表示立即挂载 */
res = f_mount(&fsObject, "1:", 1);
printf("\tf_mount res = %d\n", res);
// 只有当存储介质不存在文件系统时,才进行格式化
if (res == FR_NO_FILESYSTEM)
{
res = f_mkfs("1:", 0, 0); // 格式化存储介质
printf("\tf_mkfs res = %d\n", res);
// 格式化后要取消挂载,然后再重新挂载文件系统
res = f_mount(NULL, "1:", 1);
res = f_mount(&fsObject, "1:", 1);
printf("\tsecond f_mount res = %d\n", res);
}
/* 开始测试 */
// 以可读可写权限打开一个文件
res = f_open(&fp, "1:abcd.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE);
printf("\tf_open res = %d\n", res);
// 如果打开文件成功,就可以写入数据
if (res == FR_OK)
{
res = f_write(&fp, wData, sizeof(wData), &bw);
printf("\tbw = %d\n", bw);
if (res == FR_OK)
{
f_lseek(&fp, 0); // 把文件的光标移到最开头处
res = f_read(&fp, rData, f_size(&fp), &br);
if (res == FR_OK)
{
printf("\t文件内容: %s, br = %d\n", rData, br);
}
}
f_close(&fp); // 关闭文件
}
while(1);
}
串口打印结果如下: