首页 > 其他分享 >STM32 串行FLASH W25Q64 移植文件系统FatFs ——(2)

STM32 串行FLASH W25Q64 移植文件系统FatFs ——(2)

时间:2024-07-27 16:28:49浏览次数:19  
标签:文件 FLASH res flash 文件系统 W25Q64 STM32 FatFs printf

引言

  • 简述本篇文章目标:介绍如何将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

相关文章

  • 2024年最新STM32单片机简介
    一、STM32简介    STM32是ST公司基于ARMCortex-M内核开发的32位微控制器。    STM32常应用在嵌入式领域,如智能车、无人机、机器人、无线通信、物联网、工业控制、娱乐电子产品等。    STM32功能强大、性能优异、片上资源丰富、功耗低,是一款经典的......
  • STM32+ESP8266-连接阿里云-创建云产品流转实现STM32与Android app通讯(1)
    前言本文章的内容为STM32通过ESP8266利用AT指令连接阿里云平台,并创建设备和创建云产品流转主题,来为实现Androidapp与STM32的发送接收数据做准备。Androidapp的实现由于篇幅不宜过长,将放到下一篇文章中。演示视频实现一个简单的app来控制stm32开关灯、蜂鸣器、门(舵机),显示温......
  • STM32开发环境配置记录——关于PlatformIO + VSCode + CubeMX的集成环境配置
    前言​ 为什么配置这样的一个环境呢?鄙人受够了Keil5那个简陋的工作环境了,实在是用不下去,调试上很容易跟CubeMX的代码产生不协调导致调试——发布代码不一致造成的一系列问题。CubeIDE虽说不错,但是它的代码辅助功能和构建系统实在不敢恭维,经常出现Makefile未同步导致符号定义冲突,......
  • 零基础STM32单片机编程入门(二十二) ESP8266 WIFI模块实战含源码
    文章目录一.概要二.ESP8266WIFI模块主要性能参数三.ESP8266WIFI模块芯片内部框图四.ESP8266WIFI模块原理图五.ESP8266WIFI模块与单片机通讯方法1.硬件连接2.ESP8266模块AT指令介绍六.STM32单片机与ESP8266WIFI模块通讯实验1.硬件准备2.软件工程3.软件主要代码4.实验......
  • CH32V系列MCU:关于将“变量”放在FLASH某一位置方法
    以CH32V307为例,配置方法如下: 关于给定义的变量赋值,注意要通过FLASH编程操作写入,将值写入FLASH该位置。完成后可直接通过读取该变量获取该值。如下图: 配置代码如下:/**********************************(C)COPYRIGHT********************************FileName......
  • Clion开发STM32——移植FreeModbus
    STM32型号:STM32H743VIT6FreeModbus版本:1.6使用工具:stm32cubeMX,Clion使用STM32作从机,模式:RTU网上用keil的比较多,用Clion的比较少,如果你也用Clion,那么希望本文可以给你提供些许参考。1下载官网源码官网地址:https://www.embedded-experts.at/en/freemodbus/about/dem......
  • STM32 HAL 定时器代替HAL_Delay延时函数
    因为不知道的原因SysTick_Handler不进入中断,HAL_IncTick函数中的uwTick计数值不能自增,延时函数卡死。用定时器代替系统时钟计数,并重写HAL_Delay延时函数。不使用中断。需要找一个32位计数周期的定时器,才能和32位计数值匹配。我选择的TIM2,STM32H750的TIM2挂载在APB1上,时钟树设......
  • (12)RCC与时钟树编程—基于铁头山羊的STM32标准库教程
    时钟树倍频与分频: LSI:位于芯片内的低速时钟(低速内部时钟):36.768KHz HSI:位于芯片内的高速时钟(高速内部时钟):8MHzLSE:位于芯片外的低速时钟(低速外部时钟):36.768KHzHSE:位于芯片外的高速时钟(低速外部时钟):4~16MHz配置RCC时钟的标准库函数:RCC_HSEConfig(值1);//HSE开......
  • STM32F407最小系统板烧录基于ST-LINK /V2
    STM32F407最小系统板烧录ST-LINK/V2背景我们使用的单片机最小系统板为STM32F407ZGT6,下载器为正点原子.方法下载测试程序下载好程序`LoadTest`,地址为Casdos/STM32F407NUEDC:电赛,尤其针对stm32f407zet6最小开发版相关代码(github.com)按图连接SWD和其它线路,注意SW......
  • STM32的外部中断详解
    一、什么是中断?想象一下你正在家里做饭,突然门铃响了,你听到门铃声后,会暂时放下手中的事情(比如炒菜),去开门看看是谁。在这个例子中,门铃声就是一个“中断”,它打断了你原本在做的事情(炒菜)。在STM32(一个常用的微控制器)中,中断也是类似的概念。STM32有很多功能,比如控制LED灯、读取传......