STM32完全学习——FLASH上FATFS文件管理系统

一、需要移植的接口

我们通过看官网的手册,可以看到我们只要完成下面函数的实现,就可以完成移植。我们这里只移植前5个函数,获取时间的函数我们不在这里移植。

二、移植接口函数

cpp 复制代码
DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat;
	switch (pdrv) {
		case DEV_FALSH :
			stat = EN25QXX_ReadSR();

			return stat;

		return stat;
	}
	return STA_NOINIT;
}
//获取存储器状态的函数
uint8_t EN25QXX_ReadSR(void)   
{  
	uint8_t byte = 0;   
	FLASH_CS = 0;                            //使能器件   
	SPI1_ReadWriteByte(EN25X_ReadStatusReg);    //发送读取状态寄存器命令    
	byte = SPI1_ReadWriteByte(0Xff);             //读取一个字节  
	FLASH_CS = 1;                            //取消片选     
	return byte;   
} 
cpp 复制代码
DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS stat = 0;;
	if (pdrv == DEV_FALSH)  //由于我只里关于FLASH的初始化,在main函数里面已经完成了,这里直接返回就行
	{
		return stat;
	}

	return STA_NOINIT;
}

需要注意的是读和写的函数里面的LBA_t sector这个程序内部代表的是扇区号,而不是扇区地址,但是我们的FLASH写和读函数里面传的参数是地址,因此一定要先将扇区号转化成对应的地址然后才可以进行传参。count代表的是扇区的个数而不是我们要写的字节数。而我们的读函数一次是读一个扇区,因此count是多少就代表我们要读多少个扇区。读完一个扇区后一定要将地址和数据进行更新,虽然在FALSH层面上读是不会限制大小的,但是由于我们是给FATFS文件系统使用,因此我们就要写成按一个扇区来读。

cpp 复制代码
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 */
)
{
	uint32_t i = 0;
	uint32_t addr = sector*SECTOR_SIZE;
	if (pdrv == DEV_FALSH)
	{
		for (i=0; i<count; i++)
		{
			EN25QXX_Read((BYTE *)buff, addr, SECTOR_SIZE);  //每运行一次就读一个扇区
			addr += SECTOR_SIZE;
			buff += SECTOR_SIZE;

		}
		return RES_OK;
	}


	return RES_PARERR;
}


//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void EN25QXX_Read(uint8_t *pBuffer,uint32_t ReadAddr, uint16_t NumByteToRead)   
{ 
 	uint16_t i;   										    
	FLASH_CS = 0;                            //使能器件   
    SPI1_ReadWriteByte(EN25X_ReadData);         //发送读取命令   
    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>16));  //发送24bit地址    
    SPI1_ReadWriteByte((uint8_t)((ReadAddr)>>8));   
    SPI1_ReadWriteByte((uint8_t)ReadAddr);   
    for(i=0; i<NumByteToRead; i++)
	{ 
        pBuffer[i] = SPI1_ReadWriteByte(0XFF);   //循环读数 
    }
	FLASH_CS = 1;  				    	      
} 


//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI1_ReadWriteByte(uint8_t TxData)
{
    uint8_t Rxdata;
	HAL_SPI_TransmitReceive_DMA(&hspi1, &TxData, &Rxdata, 1);
	delay_us(1);   //必须的需要这个延时,不然速度太快了

 	return Rxdata;          		    //返回收到的数据		
}

这里需要注意的是FLASH在每次写之前一定要先进行擦除,才可以写,因此我们这里还需要一个按扇区擦除的函数。

cpp 复制代码
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 i = 0;
	uint32_t addr = sector*SECTOR_SIZE;
	if (pdrv == DEV_FALSH)
	{
		for (i=0; i<count; i++)
		{
			EN25QXX_Erase_Sector(addr);
			EN25QXX_Write_Sector((BYTE *)buff, addr, SECTOR_SIZE);
			addr += SECTOR_SIZE;
			buff += SECTOR_SIZE;
		}
		return RES_OK;
	}
	


	return RES_PARERR;
}

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void EN25QXX_Erase_Sector(uint32_t Dst_Addr)   
{  
	//监视falsh擦除情况,测试用   
// 	printf("fe:%x\r\n",Dst_Addr);	  
    EN25QXX_Write_Enable();                  //SET WEL 	 
    EN25QXX_Wait_Busy();   
  	FLASH_CS = 0;                            //使能器件   
    SPI1_ReadWriteByte(EN25X_SectorErase);      //发送扇区擦除指令 
    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  //发送24bit地址    
    SPI1_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   
    SPI1_ReadWriteByte((uint8_t)Dst_Addr);  
	FLASH_CS = 1;                            //取消片选     	      
    EN25QXX_Wait_Busy();   				   //等待擦除完成
}
cpp 复制代码
DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
	if (pdrv == DEV_FALSH)
	{
		switch (cmd){
			case CTRL_SYNC:               //确保设备已完成挂起的写入过程。如果磁盘I/O层或存储设备具有回写式缓存,则脏缓存数据必须立即提交到介质。如果对介质的每个写操作都在以下时间内完成,则此命令不执行任何操作 disk_write 功能。
				return RES_OK;
			case GET_SECTOR_COUNT:{
				*(DWORD *)buff = 4096;     //表示扇区的个数
				return RES_OK;
			}		
			case GET_SECTOR_SIZE:{
				*(WORD *)buff = SECTOR_SIZE;  //表示每个扇区的大小
				return RES_OK;
			}	
		}
		return RES_PARERR;
	}
	return RES_PARERR;
}

三、注意事项

移植完上面的接口函数后,因为我们无法手动给FLASH里面格式化成FAT32文件系统,因此我们需要使用f_mkfs()函数,来完成格式化。当你使用这个函数的时候,你会发现会报错,那是因为在ffconf.h里面的相关配置没有打开。

如果你在编译的过程中发现下面的错误,那也是因为配置里面没有关闭使用时间这个选项

如果发现运行过程中,程序老是死在某个地方,他不是死循环,而是直接程序就不动了。那有可能是因为扇区的大小设置的不对,因为FATFS默认情况将512个字节作为一个簇,但是我们的FLASH里面的最小擦出的单元是一个扇区,而我们的一个扇区大小是4KB也就是4096个字节。因此我们要将扇区范围的上限提高一下。

cpp 复制代码
res = f_mkfs("0:/", 0, work, 4096);
这里传的参数也是一个扇区的大小,如果不对格式化会有问题。
相关推荐
西岸行者2 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意2 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习
im_AMBER3 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J3 天前
从“Hello World“ 开始 C++
c语言·c++·学习