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 小时前
【Unity3D】3D渲染流水线总结
学习·unity
宇寒风暖3 小时前
软件工程大复习(五) 需求工程与需求分析
笔记·学习·软件工程
yvestine3 小时前
数据挖掘——规则和最近邻分类器
人工智能·笔记·学习·机器学习·数据挖掘·规则·knn
leilifengxingmw4 小时前
Docker学习相关笔记,持续更新
笔记·学习·docker
thesky1234564 小时前
每天你好20250103(距离春节26天!!!)
学习·算法
1.1的24次方5 小时前
【公开笔记】-小白如何写论文
笔记·学习·论文笔记
nnnimok5 小时前
渗透学习笔记(十)PowerShell基础
笔记·学习
知难行难5 小时前
持续学习入门
学习
sealaugh326 小时前
aws(学习笔记第二十一课) 开发lambda应用程序
笔记·学习·aws
【上下求索】7 小时前
学习笔记079——数据结构之【树】
数据结构·笔记·学习