本章主要讲解FatFs文件系统在STM32上的移植。FatFs文件系统的移植非常简单!!!
如果需要了解FatFs文件系统一些基础知识的,请参考我的另一篇文章:
1、移植前的准备
本章适用STM32所有系列的芯片移植。以本章为例需要准备以下东西:
- 搭建一个点亮LED灯和串口正常打印的工程。
- 在工程中添加外部FLASH芯片W25QXX系列芯片的驱动,可正常读写。本章使用的是正点原子的W25QXX芯片的驱动。
- FatFs源文件。
- 一个STM32系列的开发板。
2、移植过程
2.1、搭建一个含有FATFS文件的工程
- 先搭建好一个W25QXX芯片正常驱动的KEIL工程
- 在KEIL工程中添加FATFS文件夹。
- 将FatFs源文件的source文件夹下面所有的.c和.h全部拷贝到KEIL工程中FATFS文件夹下。
- 在KEIL工程中添加FATFS文件。
5. 在main函数文件头部添加
#include "ff.h"
如果现在编译工程,可以发现有两个错误,一个是来自 diskio.c 文件,提示有一些头文件没找, diskio.c 文件内容是与底层设备输入输出接口函数文件,不同硬件设计驱动就不同,需要的文件也不同;
接下来要做的就是修改 diskio.c 文件和 ffconf.h 文件。
2.2、修改文件
仅需要修改两个文件:diskio.c 文件和 ffconf.h 文件。
2.2.1、修改diskio.c 文件
先给一个修改好的文件,后面再一个函数一个函数讲解。代码如下:
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "w25qxx.h"
/* Definitions of physical drive number for each drive */
#define ATA 0 // 预留SD卡使用
#define SPI_FLASH 1 // 外部SPI Flash
#define FLASH_SECTOR_SIZE 512
u16 FLASH_SECTOR_COUNT=2048*12; //W25Q1218,前12M字节给FATFS占用
#define FLASH_BLOCK_SIZE 8 //每个BLOCK有8个扇区
/*-----------------------------------------------------------------------*/
/* 获取设备状态 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case ATA: /* SD CARD */
break;
case SPI_FLASH:
/* SPI Flash状态检测:读取SPI Flash 设备ID */
if(0xEF17 == W25QXX_ReadID())
{
/* 设备ID读取结果正确 */
status &= ~STA_NOINIT;
}
else
{
/* 设备ID读取结果错误 */
status = STA_NOINIT;;
}
break;
default:
status = STA_NOINIT;
}
return status;
}
/*-----------------------------------------------------------------------*/
/* 设备初始化 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case ATA: /* SD CARD */
break;
case SPI_FLASH: /* SPI Flash */
/* 初始化SPI Flash */
W25QXX_Init();
/* 获取SPI Flash芯片状态 */
status=disk_status(SPI_FLASH);
break;
default:
status = STA_NOINIT;
}
return status;
}
/*-----------------------------------------------------------------------*/
//读扇区:读取扇区内容到指定存储区
//drv:磁盘编号0~9
//*buff:数据接收缓冲首地址
//sector:扇区地址
//count:需要读取的扇区数
/*-----------------------------------------------------------------------*/
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 status = RES_PARERR;
if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
switch (pdrv)
{
case ATA: /* SD CARD */
break;
case SPI_FLASH:
for(;count>0;count--)
{
W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#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 status = RES_PARERR;
if (!count) {
return RES_PARERR; /* Check parameter */
}
switch (pdrv)
{
case ATA: /* SD CARD */
break;
case SPI_FLASH:
for(;count>0;count--)
{
W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
#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 status = RES_PARERR;
switch (pdrv)
{
case ATA: /* SD CARD */
break;
case SPI_FLASH:
switch (cmd)
{
case CTRL_SYNC:
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
break;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
__weak DWORD get_fattime(void) {
/* 返回当前时间戳 */
return ((DWORD)(2015 - 1980) << 25) /* Year 2015 */
| ((DWORD)1 << 21) /* Month 1 */
| ((DWORD)1 << 16) /* Mday 1 */
| ((DWORD)0 << 11) /* Hour 0 */
| ((DWORD)0 << 5) /* Min 0 */
| ((DWORD)0 >> 1); /* Sec 0 */
}
底层设备驱动函数是存放在 diskio.c 文件,我们的目的就是把 diskio.c 中的函数接口与SPI Flash 芯片驱动连接起来。总共有五个函数,分别为:
- 设备状态获取(disk_status)
- 设备初始化(disk_initialize)
- 扇区读取(disk_read)
- 扇区写入(disk_write)
- 其他控制(disk_ioctl)。
接下来,我们对每个函数结合 SPI Flash 芯片驱动做详细讲解。
(1)、物理编号宏定义
#define ATA 0 // 预留SD卡使用
#define SPI_FLASH 1 // 外部SPI Flash
这两个宏定义在 FatFs 中非常重要, FatFs 是支持多物理设备的,必须为每个物理设备定义一个不同的编号。SD 卡是预留接口,在讲解 SDIO 接口相关章节后会用到,可以实现使用读写 SD 卡内文件。
(2)、设备状态获取
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case ATA: /* SD CARD */
break;
case SPI_FLASH:
/* SPI Flash状态检测:读取SPI Flash 设备ID */
if(0xEF17 == W25QXX_ReadID())
{
/* 设备ID读取结果正确 */
status &= ~STA_NOINIT;
}
else
{
/* 设备ID读取结果错误 */
status = STA_NOINIT;;
}
break;
default:
status = STA_NOINIT;
}
return status;
}
disk_status 函数只有一个参数 pdrv,表示物理编号。一般我们都是使用 switch 函数实现对 pdrv 的分支判断。对于 SD 卡只是预留接口,留空即可。对于 SPI Flash 芯片,我们直接调用在W25QXX_ReadID()获取设备 ID,然后判断是否正确,如果正确,函数返回正常标准;如果错误,函数返回异常标志。
(3)、设备初始化
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case ATA: /* SD CARD */
break;
case SPI_FLASH: /* SPI Flash */
/* 初始化SPI Flash */
W25QXX_Init();
/* 获取SPI Flash芯片状态 */
status=disk_status(SPI_FLASH);
break;
default:
status = STA_NOINIT;
}
return status;
}
disk_initialize 函数也是有一个参数 pdrv,用来指定设备物理编号。对于 SPI Flash 芯片我们调用 W25QXX_Init()函数实现对 SPI Flash 芯片引脚 GPIO 初始化配置以及 SPI 通信参数配置。 最后调用 disk_status 函数获取 SPI Flash 芯片状态,并返回状态值。
(4)、读取扇区
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 status = RES_PARERR;
if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误
switch (pdrv)
{
case ATA: /* SD CARD */
break;
case SPI_FLASH:
for(;count>0;count--)
{
W25QXX_Read(buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
disk_read 函数有四个形参。 pdrv 为设备物理编号。 buff 是一个 BYTE 类型指针变量,buff 指向用来存放读取到数据的存储区首地址。 sector 是一个 DWORD 类型变量,指定要读取数据的扇区首地址。 count 是一个 UINT 类型变量,指定扇区数量。
(5)、扇区写入
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 status = RES_PARERR;
if (!count) {
return RES_PARERR; /* Check parameter */
}
switch (pdrv)
{
case ATA: /* SD CARD */
break;
case SPI_FLASH:
for(;count>0;count--)
{
W25QXX_Write((u8*)buff,sector*FLASH_SECTOR_SIZE,FLASH_SECTOR_SIZE);
sector++;
buff+=FLASH_SECTOR_SIZE;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
disk_write 函数有四个形参, pdrv 为设备物理编号。 buff 指向待写入扇区数据的首地址。sector,指定要读取数据的扇区首地址。 count 指定扇区数量。对于 SPI Flash 芯片,调用数据写入函数(W25QXX_Write)把数据写入到指定位置内。
(6)、其它控制
DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
DRESULT status = RES_PARERR;
switch (pdrv)
{
case ATA: /* SD CARD */
break;
case SPI_FLASH:
switch (cmd)
{
case CTRL_SYNC:
break;
case GET_SECTOR_SIZE:
*(WORD*)buff = FLASH_SECTOR_SIZE;
break;
case GET_BLOCK_SIZE:
*(WORD*)buff = FLASH_BLOCK_SIZE;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = FLASH_SECTOR_COUNT;
break;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
disk_ioctl 函数有三个形参, pdrv 为设备物理编号, cmd 为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令, buff 为指令对应的数据指针。
(7)、时间戳获取
__weak DWORD get_fattime(void) {
/* 返回当前时间戳 */
return ((DWORD)(2015 - 1980) << 25) /* Year 2015 */
| ((DWORD)1 << 21) /* Month 1 */
| ((DWORD)1 << 16) /* Mday 1 */
| ((DWORD)0 << 11) /* Hour 0 */
| ((DWORD)0 << 5) /* Min 0 */
| ((DWORD)0 >> 1); /* Sec 0 */
}
get_fattime 函数用于获取当前时间戳,在 ff.c 文件中被调用。 FatFs 在文件创建、被修改时会记录时间,这里我们直接使用赋值方法设定时间戳。为更好的记录时间,可以使用控制器 RTC 功能。
2.2.2、修改ffconf.h 文件
ffconf.h 对每个配置选项都做了详细的使用情况说明。下面只列出修改的配置,其他配置采用默认即可。
#define FF_USE_MKFS 1
#define FF_CODE_PAGE 936
#define FF_USE_LFN 2
#define FF_LFN_UNICODE 2
#define FF_STRF_ENCODE 3
#define FF_VOLUMES 2
#define FF_MIN_SS 512
#define FF_MAX_SS 4096
- FF_USE_MKFS :格式化功能选择,为使用 FatFs 格式化功能,需要把它设置为 1。
- FF_CODE_PAGE :语言功能选择,并要求把相关语言文件添加到工程宏。为支持简 体中文文件名需要使用“936”。
- FF_USE_LFN:长文件名支持,默认不支持长文件名,这里配置为2,支持长文件名, 并指定使用栈空间为缓冲区。
- FF_LFN_UNICODE:文件名使用UTF-8格式。
- FF_STRF_ENCODE:文件使用UTF-8格式。
- FF_VOLUMES:指定物理设备数量,这里设置为 2,包括预留 SD 卡和 SPIFlash 芯片。
- FF_MIN_SS、 FF_MAX_SS :指定扇区大小的最小值和最大值。 SD 卡扇区大小一般都 为 512字节, SPI Flash 芯片扇区大小一般设置为 4096 字节,所以需要把_MAX_SS 改为 4096。
文件修改完毕。接下来就是进行测试。
3、FatFas移植测试
测试代码如下:
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "system_event.h"//里面包含的是usart.h
#include "w25qxx.h"
#include "ff.h"
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
char fpath[100]; /* 保存当前扫描路径 */
char readbuffer[512]; /* */
BYTE work[FF_MAX_SS];
char write[] = "欢迎关注redeemer奇,测试!";
int main(void)
{
DIR dir;
FATFS *pfs;
DWORD fre_clust, fre_sect, tot_sect;
uint16_t temp;
delay_init(168);
led_init();
system_event_init();//里面是串口初始化
W25QXX_Init();
printf("\r\n=======开始测试=====================\r\n");
//挂载文件系统
res_flash = f_mount(&fs,"1:",1);
if (res_flash == FR_NO_FILESYSTEM)
{
printf("》 FLASH 还没有文件系统,即将进行格式化...\r\n");
res_flash=f_mkfs("1:",0,work,sizeof(work));
if (res_flash == FR_OK)
{
printf("》 FLASH 已成功格式化文件系统。 \r\n");
res_flash = f_mount(NULL,"1:",1);
res_flash = f_mount(&fs,"1:",1);
}
else
{
printf("《《格式化失败。%d》》 \r\n",res_flash);
while (1);
}
}
else if(res_flash!=FR_OK)
{
printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
printf("!!可能原因:SPI Flash初始化不成功。\r\n");
while(1);
}
else
{
printf("外部Flash挂载文件系统成功。(%d)\r\n",res_flash);
}
/*---------------------- 文件系统测试:写测试 ----------------------*/
/* 打开文件,如果文件不存在则创建它 */
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
res_flash = f_open(&fnew, "1:data.txt",FA_CREATE_ALWAYS | FA_WRITE );
if ( res_flash == FR_OK )
{
printf("\r\n》打开/创建 文件成功,向文件写入数据。 \r\n");
res_flash=f_write(&fnew,write,sizeof(write),&fnum);
if (res_flash==FR_OK)
{
printf("》文件写入成功,写入字节数据: %d\n",fnum);
printf("》向文件写入的数据为: \r\n%s\r\n",write);
}
else
{
printf("!!文件写入失败: (%d)\n",res_flash);
}
f_close(&fnew);
}
else
{
printf("!!打开/创建文件失败。 %d\r\n",res_flash);
while(1);
}
/*---------------------- 文件系统测试:读测试 ----------------------*/
printf("****** 即将进行文件读取测试... ******\r\n");
res_flash = f_open(&fnew, "1:data.txt",FA_OPEN_EXISTING | FA_READ);
if (res_flash == FR_OK)
{
printf("》打开文件成功。 \r\n");
res_flash = f_read(&fnew, readbuffer, sizeof(readbuffer), &fnum);
if (res_flash==FR_OK)
{
printf("》文件读取成功,读到字节数据: %d\r\n",fnum);
printf("》读取得的文件数据为: \r\n%s \r\n", readbuffer);
}
else
{
printf("!!文件读取失败: (%d)\n",res_flash);
}
}
else
{
printf("!!打开文件失败。 \r\n");
}
f_close(&fnew);
while(1)
{
}
}
结束
欢迎大家关注我的微信号:redeemer奇
里面惊喜,有大量的学习资料持续更新。各种开源项目!
一起交流!一起努力!