引言
- 简述本篇文章目标:介绍如何将FatFs文件系统移植到STM32平台上的W25Q64串行FLASH。
- 回顾上篇内容:软件模拟SPI驱动的实现。
一、FatFs文件系统简介
文件系统相对庞大且复杂,需要根据具体应用的文件系统格式进行编写。通常,文件系统与底层驱动分离,便于移植。因此,在实际工程应用中,常常直接移植现成的文件系统源码,以提高开发效率。 FatFs 是用于小型嵌入式系统的通用 FAT/exFAT 文件系统模块。FatFs 模块按照 ANSI C (C89) 编写,并与磁盘 I/O 层完全分离。因此,它独立于平台。它可以集成到资源有限的小型微控制器中,如8051、PIC、AVR、ARM、Z80、RX等。 FatFs 支持 FAT12 、 FAT16 、 FAT32 等格式,所以我们利用上一篇文章 写好的 W25Q64的 芯片驱动,将 FatFs 文件系统的代码移植到工程之中,就可以利用文件系统的各种函数,对 Flash 芯片以“文件”格式进行读写操作了。 移植所用到的FatFs文件系统的源码可以从官网进行下载: FatFs官网1.1 源码下载
目前官网最新的版本为R0.15版本,我移植的时R0.14b版本,所以点击这里的Previous Releases,到之前的版本中选择需要的版本进行下载。
1.2 源码的目录结构
下载好的源码解压后,包括两个文件夹和一个txt文件。
1.3 帮助文档
其中documents文件夹中是一些帮助文档,00index_e.html是关于FatFs的简介,双击后就会进入到官网,在官网中可以查看FatFs文件系统的函数和用法,或者点击doc文件夹中各个以函数名作为名称的html文件了解函数的介绍及用法,利用这些函数我们就可以操作FLASH芯片,res文件夹中包括一些00index_e.html会用到的图片,还有6个名为app.c的文件,文件中都是一些FatFs具体的应用例程。
1.4 FatFs源码
source文件夹中是FatFs文件系统的源码,我们在移植的时候要将source文件夹中文件添加到工程中。
- 00readme.txt:对文件夹中各个文件的简单介绍。
- 00history.txt:介绍的是FatFs的版本更新情况。
- ff.c:FatFs的核心文件,文件管理的实现函数都在该文件中,该文件独立于底层操作文件的函数,利用这些函数可以实现对文件的读写。
- ffconf.h:该头文件中包含了对FatFs功能配置的宏定义,通过修改这些宏定义可以裁剪FatFs的功能。
- ff.h:常见的包括FatFs和应用程序模块的文件。
- diskio.h:diskio.c文件的函数声明以及diskio.c文件中用到的定义。
- diskio.c:包含底层存储截止的操作函数,这些函数要自己实现,主要添加底层驱动函数。
- ffunicode.c:可选的Unicode实用程序函数。
- ffsystem.c:可选O/S相关功能的示例。
这些文件中,diskio.c中的底层操作函数需要我们自己实现,ffconf.h文件中的宏定义需要做相应的修改,所以在移植过程中,需要我们自己修改的就只有diskio.c和ffconf.h两个文件。
二、FatFs的移植步骤
在工程目录中创建FatFs文件夹,将源码中source文件夹下的文件复制到该文件夹中。
复制完成后在工程中添加FatFs分组,并将diskio.c,ff.c,ffunicode.c添加到分组中,并且要将路径添加到工程中去。
完成后点击编译会出现13个错误和17个警告,其中包括头文件无法找到等各个错误,这里我们对diskio.c和ffconf.h文件进行修改,修改完成后就可以消除掉这些错误和警告。
2.1 修改ffconf.h中的宏定义值
- 将FF_USE_MKFS的值修改为1,因为后续要用到f_mkfs()函数来进行格式化操作。
- 这里将FF_CODE_PAGE的值修改为437,为了用于支持后续创建文件名称和读写文件时对英文的支持,修改之后必须将ffunicode.c添加到工程中,否则会报错。
- 将FF_USE_LFN设置为2,代表启用长文件名功能,并且长文件名所需的工作区是动态分配的,这样只有在需要处理长文件名时才会占用内存,从而节省了系统资源。
- 这里将FF_MAX_SS修改为4096,表示文件系统能够支持从512字节到4096字节的扇区大小范围。
FF_VOLUMES
用于配置文件系统同时支持的逻辑卷(卷)的数量。将FF_VOLUMES
设置为 1 表示该文件系统仅支持一个逻辑卷,若要再安装SD卡等需将该值修改为所有的逻辑卷的数量。
2.2 diskio.c文件修改
2.2.1 添加头文件并修改宏定义
只有SPI FLASH左右逻辑卷,暂时没有其他的,所以将其他进行屏蔽,只为SPI_FLASH这一个驱动设置编号即可。
2.2.2 disk_status() 设备状态获取函数
disk_status函数只有一个参数pdrv,表示物理盘号。一般使用switch来进行分支判断,因为我们目前只有SPI FLASH芯片,所以将其他分支都删除掉,只留下SPI FLASH芯片这一个分支,在这个分支下直接调用FLASH芯片驱动函数中的W25QXX_ReadID()函数来获取设备ID并判断是否正确,正确的话函数返回正常的标志,错误的话返回错误标志。
DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case SPI_FLASH :
if (W25Q64 == W25QXX_ReadID())
{
status &= ~STA_NOINIT;
}
else
{
status = STA_NOINIT;
}
break;
default:
status = STA_NOINIT;
}
return status;
}
2.2.3 disk_initialize() 设备初始化函数
disk_initialize函数也只有一个参数pdrv,用于指定物理盘号。在该函数中,我们调用FLASH驱动函数中的W25QXX_Init()函数来对FLASH芯片进行初始化,然后做一个短暂的延时,确保初始化完成,在调用disk_status函数来获取SPI FLASH芯片的状态,并返回状态值。
DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
uint16_t i;
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case SPI_FLASH :
W25QXX_Init();
i = 500;
while(--i); //进行短暂的延时
status = disk_status(SPI_FLASH);
break;
default:
status = STA_NOINIT;
}
return status;
}
2.2.4 disk_read() 读取扇区函数
disk_read 函数有四个形参。 pdrv 为设备物理编号; buff 是一个 BYTE 类型指针变量, 指向用来存放读取到数据的存储区的首地址;sector 是一个 LBA_t 类型变量,也就是一个DWORD类型变量,用于指定要读取数据的扇区首地址;count 是一个 UINT 类型变量,指定扇区数量。 对于 SPI Flash 芯片,主要是使用 W25QXX_Read() 函数来实现在指定地址读取指定长度的数据,它包括三个参数,第一个参数用于指定数据存放地址指针,第二个参数用于指定数据读取的起始地址,这里使用左移运算符,左移 12 位实际是乘以 4096 ,因为W25Q64芯片的每个扇区大小是4096个字节,第三个参数为要读取的扇区数,也需要使用左移运算符来换算成需要读取的字节数。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;
switch (pdrv) {
case SPI_FLASH :
/* 左移12位也就是乘以4096,因为一个扇区4096个字节 */
W25QXX_Read(buff, sector<<12, count<<12);
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
2.2.5 disk_write() 扇区写入函数
disk_read 函数也有四个形参。pdrv 为设备物理编号;buff 是一个 BYTE 类型指针变量, 指向存放的用于写入的数据存储区的首地址;sector 是一个 LBA_t 类型变量,也就是一个DWORD类型变量,用于指定要写入数据的扇区首地址;count 是一个 UINT 类型变量,指定要写入的扇区数量。
对于 SPI Flash 芯片,主要是使用 W25QXX_Write() 函数来实现在指定地址写入指定长度的数据,在写入数据之前,要使用W25QXX_Erase_Sector()函数将要写入的扇区的数据擦除,否则会写入失败。 W25QXX_Write()函数包括三个参数,第一个参数用于指定待写入数据存放地址的指针,第二个参数用于指定数据写入的起始地址,第三个参数为要写入的扇区数,使用左移运算符来换算成需要读取的字节数。#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 */
)
{
uint32_t write_addr;
DRESULT status = RES_PARERR;
if (!count){
return RES_PARERR; /* Check parameter */
}
switch (pdrv) {
case SPI_FLASH :
write_addr = sector << 12;
W25QXX_Erase_Sector(write_addr);
W25QXX_Write((u8 *)buff, write_addr, count<<12);
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
#endif
2.2.6 disk_ioctl() 其他控制函数
disk_ioctl 函数有三个形参, pdrv 为设备物理编号, cmd 为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等指令,buff 为指令所对应的数据指针。 对于 SPI Flash 芯片,为了支持 FatFs 格式化功能,需要用到获取扇区数量 (GET_SECTOR_COUNT) 指令和获取擦除块数量 (GET_BLOCK_SIZE) 指令。另外, SPI Flash芯片一般设置扇区大小为 4096 字节,所以需要用到获取扇区大小 (GET_SECTOR_SIZE) 指令。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 SPI_FLASH:
switch(cmd)
{
/* 扇区数量 2048*4096/1024/1024=8MB */
case GET_SECTOR_COUNT:
*(DWORD *)buff = 2048;
break;
/* 扇区大小 */
case GET_SECTOR_SIZE:
*(WORD *)buff = 4096;
break;
/* 同时擦除扇区个数 */
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
break;
}
status = RES_OK;
break;
default:
status = RES_PARERR;
}
return status;
}
三、FatFs功能测试
测试内容参照野火霸道例程中,适当做了一些修改,在main.c文件中实现,具体代码如下:
#include "stm32f10x.h"
#include "ff.h"
#include "ffconf.h"
#include "./usart/bsp_usart.h"
#include "./led/bsp_led.h"
#include "./flash/bsp_spi_flash.h"
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_flash; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]={0}; /* 读缓冲区 */
BYTE WriteBuffer[] = /* 写缓冲区*/
"welcome to use YeHuo board, this is a file system test.\r\n";
/*
* 函数名:main
* 描述 :主函数
* 输入 :无
* 输出 :无
*/
int main(void)
{
BYTE work[FF_MAX_SS]; // 工作缓冲区
MKFS_PARM opt = {
.fmt = FM_FAT|FM_SFD,
.n_fat = 1,
.align = 0,
.n_root = 0,
.au_size = 0
};
LED_GPIO_Config();
LED_BLUE;
USART_Config();
printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
res_flash = f_mount(&fs, "0:", 1);
// res_flash = FR_NO_FILESYSTEM;
if (res_flash == FR_NO_FILESYSTEM)
{
printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
res_flash = f_mkfs("0:", &opt, work, sizeof(work));
if (res_flash == FR_OK)
{
printf("》FLASH已成功格式化文件系统。\r\n");
res_flash = f_mount(NULL, "0:", 1);
res_flash = f_mount(&fs, "0:", 1);
if (res_flash == FR_OK)
{
printf("格式化后挂载成功\r\n");
}
else
{
printf("格式化后挂载失败, err = %d\r\n", res_flash);
}
}
else
{
LED_RED;
printf("《《格式化失败。》》\r\n");
printf("errcode = %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");
printf("请下载 SPI—读写串行FLASH 例程测试,如果正常,在该例程f_mount语句下if语句前临时多添加一句 res_flash = FR_NO_FILESYSTEM; 让重新直接执行格式化流程\r\n");
while(1);
}
else
{
LED_GREEN;
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
/*----------------------- 文件系统测试:写测试 -------------------*/
/* 打开文件,每次都以新建的形式打开,属性为可写 */
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
res_flash = f_open(&fnew, "0:/test1.txt",FA_CREATE_ALWAYS | FA_WRITE );
printf("/******************************************************/\r\n");
if ( res_flash == FR_OK )
{
printf("》打开/创建test1.txt文件成功,向文件写入数据。\r\n");
/* 将指定存储区内容写入到文件内 */
res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
if(res_flash == FR_OK)
{
printf("》文件写入成功,写入字节数据:%d\n",fnum);
printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
}
else
{
printf("!!文件写入失败:(%d)\n",res_flash);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
}
else
{
LED_RED;
printf("!!打开/创建文件失败。\r\n");
}
/*----------------------- 文件系统测试:读测试 -------------------*/
printf("\r\n****** 即将进行文件读取测试... ******\r\n");
res_flash = f_open(&fnew, "0:/test1.txt", FA_OPEN_EXISTING|FA_READ );
if ( res_flash == FR_OK )
{
LED_GREEN;
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
{
LED_RED;
printf("!!打开文件失败。\r\n");
}
/* 不再读写,关闭文件 */
f_close(&fnew);
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL, "0:", 1);
while(1);
}
测试串口打印的结果如下:
若想创建中文名称的文件名,写入中文数据的话,需要将ffconf.h文件中FF_CODE_PAGE的值修改为936。
如果在调用FatFs文件系统的函数创建文件时跳转到HardFault_Handler硬件错误中断中的情况,是因为栈空间不足导致,将启动文件中Stack_Size的值修改大一些即可。
结语
本文对FatFs文件系统进行了简要介绍,并实现了FatFs文件系统的移植以及移植完成后对文件系统基础功能的测试。
相关阅读
如果你有任何问题或建议,请在评论区留言。感谢阅读!
标签:文件,FLASH,res,flash,文件系统,W25Q64,STM32,FatFs,printf From: https://blog.csdn.net/m0_54592572/article/details/140735019