HAL库W25Q16+fatfs文件系统移植

配置时钟树

配置时钟树时钟频率为72

SPI1外挂SPIflash

其他不用改这里挂的是一个W25Q16

文件分类管理

生成原始代码

加入W25Q16的驱动代码

忘记配片选线了,这里加上

  /*Configure GPIO pin : PtPin */
  GPIO_InitStruct.Pin = GPIO_PIN_4;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

W25Q16.c

#include "main.h"

uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
    uint8_t Rxdata;
		HAL_SPI_TransmitReceive(&hspi1,&TxData,&Rxdata,1, 1000);       
 	return Rxdata;          		    //返回收到的数据		
}

uint16_t W25QXX_TYPE = 0;	//默认是W25Q256

/**
  * @brief 
  * @param
  * @param 
  * @retval None
  */													 
uint8_t W25QXX_Init(void)
{ 
  uint8_t temp;
	
	W25QXX_TYPE = W25QXX_ReadID();	        //读取FLASH ID.
	switch(W25QXX_TYPE)
	{
		case W25Q80 :
		{
			 printf("W25Q80 \r\n");
			 temp = W25Q_OK;
		   break;
		}
    case W25Q16 :
		{
			 printf("W25Q16 \r\n");
			 temp = W25Q_OK;
		   break;
		}
		case W25Q32 :
		{
			 printf("W25Q32 \r\n");
			 temp = W25Q_OK;
		   break;
		}
		case W25Q64 :
		{
			 printf("W25Q64 \r\n");
			 temp = W25Q_OK;
		   break;
		}
		case W25Q128 :
		{
			 printf("W25Q128 \r\n");
			 temp = W25Q_OK;
		   break;
		}
		case W25Q256 :
		{
			 printf("W25Q256 \r\n");
			 temp=W25QXX_Read_SR(3);            //读取状态寄存器3,判断地址模式
			 if((temp&0X01)==0)			          //如果不是4字节地址模式,则进入4字节地址模式
			 {
				 W25QXX_CS_OK; 			            //选中
				 SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令   
				 W25QXX_CS_NON;       		        //取消片选   
			 }
			 temp = W25Q_OK;
		   break;
		}
		default:
			temp = W25Q_ERROR;
		  break;
	}
  return temp;
}  

//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
//状态寄存器1:
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
//状态寄存器2:
//BIT7  6   5   4   3   2   1   0
//SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
//状态寄存器3:
//BIT7      6    5    4   3   2   1   0
//HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
uint8_t W25QXX_Read_SR(uint8_t regno)   
{  
	uint8_t byte=0,command=0; 
    switch(regno)
    {
        case 1:
            command=W25X_ReadStatusReg1;    //读状态寄存器1指令
            break;
        case 2:
            command=W25X_ReadStatusReg2;    //读状态寄存器2指令
            break;
        case 3:
            command=W25X_ReadStatusReg3;    //读状态寄存器3指令
            break;
        default:
            command=W25X_ReadStatusReg1;    
            break;
    }    
	W25QXX_CS_OK;                           //使能器件   
	SPI2_ReadWriteByte(command);            //发送读取状态寄存器命令    
	byte=SPI2_ReadWriteByte(0Xff);          //读取一个字节  
	W25QXX_CS_NON;                          //取消片选     
	return byte;   
} 

//写W25QXX状态寄存器
void W25QXX_Write_SR(uint8_t regno,uint8_t sr)   
{   
    uint8_t command=0;
    switch(regno)
    {
        case 1:
            command=W25X_WriteStatusReg1;    //写状态寄存器1指令
            break;
        case 2:
            command=W25X_WriteStatusReg2;    //写状态寄存器2指令
            break;
        case 3:
            command=W25X_WriteStatusReg3;    //写状态寄存器3指令
            break;
        default:
            command=W25X_WriteStatusReg1;    
            break;
    }   
	W25QXX_CS_OK;                            //使能器件   
	SPI2_ReadWriteByte(command);            //发送写取状态寄存器命令    
	SPI2_ReadWriteByte(sr);                 //写入一个字节  
	W25QXX_CS_NON;                             //取消片选     	      
}   
//W25QXX写使能	
//将WEL置位   
void W25QXX_Write_Enable(void)   
{
	W25QXX_CS_OK;                            //使能器件   
  SPI2_ReadWriteByte(W25X_WriteEnable);   //发送写使能  
	W25QXX_CS_NON;                             //取消片选     	      
} 
//W25QXX写禁止	
//将WEL清零  
void W25QXX_Write_Disable(void)   
{  
	W25QXX_CS_OK;                            //使能器件   
  SPI2_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令    
	W25QXX_CS_NON;                             //取消片选     	      
} 

//读取芯片ID
//返回值如下:				   
//0XEF13,表示芯片型号为W25Q80  
//0XEF14,表示芯片型号为W25Q16    
//0XEF15,表示芯片型号为W25Q32  
//0XEF16,表示芯片型号为W25Q64 
//0XEF17,表示芯片型号为W25Q128 	  
//0XEF18,表示芯片型号为W25Q256
uint16_t W25QXX_ReadID(void)
{
	uint16_t Temp = 0;	  
	W25QXX_CS_OK;				    
	SPI2_ReadWriteByte(0x90);//发送读取ID命令	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	    
	SPI2_ReadWriteByte(0x00); 	 			   
	Temp|=SPI2_ReadWriteByte(0xFF)<<8;  
	Temp|=SPI2_ReadWriteByte(0xFF);	 
	W25QXX_CS_NON; 				    
	return Temp;
}   		    
//读取SPI FLASH  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)   
{ 
 	  uint16_t i;  
	
	  W25QXX_CS_OK;                            //使能器件   
    SPI2_ReadWriteByte(W25X_ReadData);      //发送读取命令  
    if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
    {
        SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>24));    
    }
    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));   //发送24bit地址    
    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));   
    SPI2_ReadWriteByte((uint8_t)ReadAddr);   
    for(i=0;i<NumByteToRead;i++)
	  { 
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);    //循环读数  
    }
	  W25QXX_CS_NON;   				    	      
}  
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
 	  uint16_t i;  
	
    W25QXX_Write_Enable();                  //SET WEL 
  	W25QXX_CS_OK;                           //使能器件   
    SPI2_ReadWriteByte(W25X_PageProgram);   //发送写页命令   
    if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
    {
        SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>24)); 
    }
    SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16)); //发送24bit地址    
    SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));   
    SPI2_ReadWriteByte((uint8_t)WriteAddr);   
    for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数  
	  W25QXX_CS_NON;                             //取消片选 
	  W25QXX_Wait_Busy();					   //等待写入结束
} 
//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 			 		 
	uint16_t pageremain;	
	
	pageremain=256-WriteAddr%256; //单页剩余的字节数		 	    
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
	while(1)
	{	   
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite==pageremain)break;//写入结束了
	 	else //NumByteToWrite>pageremain
		{
			pBuffer+=pageremain;
			WriteAddr+=pageremain;	

			NumByteToWrite-=pageremain;			  //减去已经写入了的字节数
			if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
			else pageremain=NumByteToWrite; 	  //不够256个字节了
		}
	};	    
} 
//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)						
//NumByteToWrite:要写入的字节数(最大65535)   
uint8_t W25QXX_BUFFER[4096];		 
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{ 
	uint32_t secpos;
	uint16_t secoff;
	uint16_t secremain;	   
 	uint16_t i;    
	uint8_t * W25QXX_BUF;	  
	
  W25QXX_BUF=W25QXX_BUFFER;	     
 	secpos=WriteAddr/4096;//扇区地址  
	secoff=WriteAddr%4096;//在扇区内的偏移
	secremain=4096-secoff;//扇区剩余空间大小   
 	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
 	if(NumByteToWrite<=secremain) secremain=NumByteToWrite;//不大于4096个字节
	while(1) 
	{	
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break; //需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXX_Erase_Sector(secpos);//擦除这个扇区
			for(i=0;i<secremain;i++)	   //复制
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  

		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(NumByteToWrite==secremain)break;//写入结束了
		else//写入未结束
		{
			secpos++;//扇区地址增1
			secoff=0;//偏移位置为0 	 

		  pBuffer+=secremain;  //指针偏移
			WriteAddr+=secremain;//写地址偏移	   
		  NumByteToWrite-=secremain;				//字节数递减
			if(NumByteToWrite>4096)secremain=4096;	//下一个扇区还是写不完
			else secremain=NumByteToWrite;			//下一个扇区可以写完了
		}	 
	} 
}
//擦除整个芯片		  
//等待时间超长...
void W25QXX_Erase_Chip(void)   
{                                   
    W25QXX_Write_Enable();                     //SET WEL 
    W25QXX_Wait_Busy();   
  	W25QXX_CS_OK;                              //使能器件   
    SPI2_ReadWriteByte(W25X_ChipErase);        //发送片擦除命令  
	  W25QXX_CS_NON;                             //取消片选     	      
	  W25QXX_Wait_Busy();   				             //等待芯片擦除结束
}   
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)   
{  
	//监视falsh擦除情况,测试用   
 	//printf("fe:%x\r\n",Dst_Addr);	  
  	Dst_Addr*=4096;
    W25QXX_Write_Enable();                  //SET WEL 	 
    W25QXX_Wait_Busy();   
  	W25QXX_CS_OK;                           //使能器件   
    SPI2_ReadWriteByte(W25X_SectorErase);   //发送扇区擦除指令 
    if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
    {
        SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>24)); 
    }
    SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  //发送24bit地址    
    SPI2_ReadWriteByte((uint8_t)((Dst_Addr)>>8));   
    SPI2_ReadWriteByte((uint8_t)Dst_Addr);  
	  W25QXX_CS_NON;                           //取消片选     	      
    W25QXX_Wait_Busy();   				           //等待擦除完成
}  
//等待空闲
void W25QXX_Wait_Busy(void)   
{   
	while((W25QXX_Read_SR(1)&0x01)==0x01);   // 等待BUSY位清空
}  

//获取状态 (1,忙;0,空闲)
uint8_t W25QXX_Get_State(void)
{
	 uint8_t temp;
	
   temp = W25QXX_Read_SR(1);
	 temp = temp&0x01;
	 return temp;
}
//进入掉电模式
void W25QXX_PowerDown(void)   
{ 
  	W25QXX_CS_OK;                            //使能器件   
    SPI2_ReadWriteByte(W25X_PowerDown);      //发送掉电命令  
	  W25QXX_CS_NON;                           //取消片选     	      
    HAL_Delay(3);                            //等待TPD  
}   
//唤醒
void W25QXX_WAKEUP(void)   
{  
  	W25QXX_CS_OK;                                //使能器件   
    SPI2_ReadWriteByte(W25X_ReleasePowerDown);   //  send W25X_PowerDown command 0xAB    
	  W25QXX_CS_NON;                               //取消片选     	      
    HAL_Delay(3);                                //等待TRES1
}   

w25q16.h

#ifndef __W25QXX_H
#define __W25QXX_H
#include "typedef.h"
#include "sys.h"
#include "spi.h"

//W25X系列/Q系列芯片列表	   
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16	
//W25Q128 ID  0XEF17	
//W25Q256 ID  0XEF18
#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17
#define W25Q256 0XEF18



#define   W25Q_OK                        ((uint8_t)0x00)
#define   W25Q_ERROR                     ((uint8_t)0x01)

//#define W25QXX_CS_NON    HAL_GPIO_WritePin(W25QXX_CS_GPIO_Port,W25QXX_CS_Pin,GPIO_PIN_SET)
//#define W25QXX_CS_OK     HAL_GPIO_WritePin(W25QXX_CS_GPIO_Port,W25QXX_CS_Pin,GPIO_PIN_RESET)

#define W25QXX_CS_NON   PAout(4)=1
#define W25QXX_CS_OK    PAout(4)=0

// 
//指令表
#define W25X_WriteEnable		      0x06 
#define W25X_WriteDisable		      0x04 
#define W25X_ReadStatusReg1		    0x05 
#define W25X_ReadStatusReg2		    0x35 
#define W25X_ReadStatusReg3		    0x15 
#define W25X_WriteStatusReg1      0x01 
#define W25X_WriteStatusReg2      0x31 
#define W25X_WriteStatusReg3      0x11 
#define W25X_ReadData			        0x03 
#define W25X_FastReadData		      0x0B 
#define W25X_FastReadDual		      0x3B 
#define W25X_PageProgram		      0x02 
#define W25X_BlockErase			      0xD8 
#define W25X_SectorErase		      0x20 
#define W25X_ChipErase			      0xC7 
#define W25X_PowerDown			      0xB9 
#define W25X_ReleasePowerDown	    0xAB 
#define W25X_DeviceID			        0xAB 
#define W25X_ManufactDeviceID   	0x90 
#define W25X_JedecDeviceID		    0x9F 
#define W25X_Enable4ByteAddr      0xB7
#define W25X_Exit4ByteAddr        0xE9

extern uint16_t W25QXX_TYPE;					//定义W25QXX芯片型号	

uint8_t W25QXX_Init(void);
uint16_t W25QXX_ReadID(void);  	    		//读取FLASH ID
uint8_t W25QXX_Get_State(void);
uint8_t W25QXX_Read_SR(uint8_t regno);             //读取状态寄存器 
void W25QXX_4ByteAddr_Enable(void);     //使能4字节地址模式
void W25QXX_Write_SR(uint8_t regno,uint8_t sr);   //写状态寄存器
void W25QXX_Write_Enable(void);  		//写使能 
void W25QXX_Write_Disable(void);		//写保护
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);   //读取flash
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);    	  	//整片擦除
void W25QXX_Erase_Sector(uint32_t Dst_Addr);	//扇区擦除
void W25QXX_Wait_Busy(void);           	//等待空闲
void W25QXX_PowerDown(void);        	//进入掉电模式
void W25QXX_WAKEUP(void);				//唤醒

#endif

接下载修改fatfs底层驱动代码,很多文章说修改user_diskio.c里的底层read,write,ioctr,status,这里采用直接修改diskio.c对应的函数,因为fatfs就是通过这几个函数进行底层io的,hal库里,不外乎是用这些函数去调用user_diskio.c对应的函数接口。

先看看文件系统的文件架构。

这是hal库生成的,但如果去官网下载,好像有点不大对,应该是hal库对其进行了一定的封装,额但这不管我吊事,我秉承代码能用就行的原则。

接下来修改底层个IO,disk_read,disk_write,disk_initialize,disk_status,disk_ioctl.这个步骤是非常重要的步骤。

DSTATUS disk_status 

/**
  * @brief  Gets Disk Status 
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
  DSTATUS stat;
  	if(W25QXX_Get_State() == W25Q_OK)
	{
			stat &= ~STA_NOINIT;
	}
  
  return stat;  
  
  
  
//  stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);
//  return stat;
}

DSTATUS disk_initialize

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
  DSTATUS stat = RES_OK;
  
  

	  stat = W25QXX_Init();
	  
		delay_ms(1);

	  if(!stat) return RES_OK;
	  else return  STA_NOINIT;
  
  
 
//  if(disk.is_initialized[pdrv] == 0)
//  { 
//    disk.is_initialized[pdrv] = 1;
//    stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
//  }
  return stat;
}

DRESULT disk_read

//这里的定义一个扇区大小为512,用于内存管理的
#define SECTOR_SIZE      512
DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	DWORD sector,	        /* Sector address in LBA */
	UINT count		/* Number of sectors to read */
)
{
//  DRESULT res;
  	if( !count )
		{
			return RES_PARERR;  /* count不能等于0,否则返回参数错误*/
		}
		for(;count>0;count--)
		{
			W25QXX_Read((uint8_t *)buff, sector*SECTOR_SIZE, SECTOR_SIZE);
		
			sector++;
			buff+=SECTOR_SIZE;
		}
  	return RES_OK;
  
  
//  res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
//  return res;
}

DRESULT disk_write

#if _USE_WRITE == 1
DRESULT disk_write (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	DWORD sector,		/* Sector address in LBA */
	UINT count        	/* Number of sectors to write */
)
{
//  DRESULT res;
  
  	if( !count )
		{
			return RES_PARERR;  /* count不能等于0,否则返回参数错误*/
		}
		
		for(;count>0;count--)
		{
			
			W25QXX_Write((uint8_t *)buff, sector*SECTOR_SIZE, SECTOR_SIZE);
			sector++;
			buff+=SECTOR_SIZE;
		}

		return RES_OK;
  
  
  
  
  
  
//  res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);
//  return res;
}

DRESULT disk_ioctl

#if _USE_IOCTL == 1
DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
  DRESULT res;
 switch(cmd)
	{
		
		case CTRL_SYNC:
				W25QXX_Wait_Busy();
				res=RES_OK;
			break;
   
		case GET_SECTOR_SIZE:
       //扇区大小
			*(WORD*)buff = 512;
			res = RES_OK;
			break;
   
		case GET_BLOCK_SIZE:
       //块大小
			*(WORD*)buff = 8;
			res = RES_OK;
			break;
		case GET_SECTOR_COUNT:
		//W25Q16总的扇区个数,512
			*(DWORD*)buff = 512*8;
			res = RES_OK;
			break;
		default:
			res = RES_PARERR;
			break;
	}
	return res;
  
  
  
  
  
  

//  res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);
//  return res;
}

OK现在文件系统以及搭建好了,先来测试一下结果,前提是放开一些接口。

打开ff.h文件找到文件系统的接口函数,可以看看具体有啥。

但很多函数都被宏给关闭了,接下来我们去打开部分需要的函数。

这些宏都被定义在ffconf.h中。自己按需打开。还一个到这里程序内存已经100多K了内存自己注意一下,我的用的AIR32F103CCT6有256K闪存大小。所以目前还是可以驾驭的。

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under Ultimate Liberty license
  * SLA0044, the "License"; You may not use this file except in compliance with
  * the License. You may obtain a copy of the License at:
  *                             www.st.com/SLA0044
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "cmsis_os.h"
#include "fatfs.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

UINT fnum ;                          /* 文件成功读写数量 */
BYTE ReadBuffer[1024] = {0};        /* 读缓冲区 */
BYTE WriteBuffer[]= "Hello World!\n";



/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
void MX_FREERTOS_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_FATFS_Init();
  MX_USART2_UART_Init();
  /* USER CODE BEGIN 2 */

 printf("****** 先测试 SPI FLASH 文件系统挂载 ******\r\n");
  
  // 在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
  retUSER = f_mount(&USERFatFS, "0:", 1);
	
  /*----------------------- 格式化测试 -----------------*/  
  /* 如果没有文件系统就格式化创建创建文件系统 */
  if(retUSER == FR_NO_FILESYSTEM)
  {
    printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
    /* 格式化 */
    retUSER = f_mkfs(USERPath, 0, 0);	    
		
    if(retUSER == FR_OK)
    {
      printf("》FLASH已成功格式化文件系统。\r\n");
      /* 格式化后,先取消挂载 */
      retUSER = f_mount(NULL, USERPath, 1);			
      /* 重新挂载	*/			
      retUSER = f_mount(&USERFatFS, USERPath, 1);
    }
    else
    {
      printf("《《格式化失败。》》\r\n");
      while(1);
    }
  }
  else if(retUSER != FR_OK)
  {
    printf("!!外部Flash挂载文件系统失败。(%d)\r\n", retUSER);
    printf("!!可能原因:SPI Flash初始化不成功。\r\n");
    while(1);
  }
  else
  {
    printf("》文件系统挂载成功,可以进行读写测试\r\n");
  }
  

/*------------------- 文件系统测试:读测试 --------------------------*/
	printf("****** 即将进行文件读取测试... ******\r\n");
	retUSER = f_open(&USERFile, "test.txt",FA_OPEN_EXISTING | FA_READ); 	 
	if(retUSER == FR_OK)
	{
		printf("》打开文件成功。\r\n");
		retUSER = f_read(&USERFile, ReadBuffer, sizeof(ReadBuffer), &fnum); 
    if(retUSER==FR_OK)
    {
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
      printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);	
    }
    else
    {
      printf("!!文件读取失败:(%d)\n",retUSER);
    }		
	}
	else
	{
		printf("!!打开文件失败。\r\n");
	}
	/* 不再读写,关闭文件 */
	f_close(&USERFile);	



  
  printf("****** 遍历当前目录 ******\r\n");
	  FATFS fs;                 /* Work area (file system object) for logical drive */
    FILINFO fno;             /* File information structure */
    DIR dir;                  /* Directory object */
    FRESULT res;
	 res = f_opendir(&dir, "/");
    if (res != FR_OK) printf("打开目录失败\n");  // 打开失败则退出
	 for(;;) {
        res = f_readdir(&dir, &fno); // 读取目录项
        if (res != FR_OK || fno.fname[0] == 0) break; // 读取错误或结束则退出
        if (fno.fname[0] == '.') continue; // 跳过隐藏文件
 
        // 输出文件名
        printf("%s\n", fno.fname);
 
        // 如果需要,可以检查fno.fsize来获取文件大小,或者使用fno.fattrib来判断是文件还是目录
    }
 
    // 关闭目录
    f_closedir(&dir);



  /* USER CODE END 2 */

  /* Call init function for freertos objects (in freertos.c) */
//  MX_FREERTOS_Init();

  /* Start scheduler */
//  osKernelStart();
  
  /* We should never get here as control is now taken by the scheduler */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  Period elapsed callback in non blocking mode
  * @note   This function is called  when TIM1 interrupt took place, inside
  * HAL_TIM_IRQHandler(). It makes a direct call to HAL_IncTick() to increment
  * a global variable "uwTick" used as application time base.
  * @param  htim : TIM handle
  * @retval None
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  /* USER CODE BEGIN Callback 0 */

  /* USER CODE END Callback 0 */
  if (htim->Instance == TIM1) {
    HAL_IncTick();
  }
  /* USER CODE BEGIN Callback 1 */

  /* USER CODE END Callback 1 */
}

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */

  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{ 
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

因为踩坑过,所以我先创建了一个txt文件,就没有加入创建文件的操作了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/478558.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

《由浅入深学习SAP财务》:第2章 总账模块 - 2.5 科目余额查询理

SAP提供了强大的科目余额查询功能&#xff0c;可以查询科目的借贷方及余额&#xff0c;同时&#xff0c;也可以追溯到明细凭证。在凭证记账后&#xff0c;科目的余额就会同步得到更新。预制凭证是不更新科目余额的。 科目余额查询及追溯的操作步骤 路径&#xff1a;SAP菜单&g…

我的春招求职面经

智能指针在面试时经常被问到&#xff0c;最近自己也在写&#xff0c;有一点思考&#xff0c;于是找到了这样一个题目&#xff0c;可以看看&#xff0c;上面这个代码有什么问题&#xff1f;留言区说出你的答案吧&#xff01; 最后分享一下之前的实习->春招->秋招等文章汇总…

atomgit访问令牌就创建的时候显示一下

AtomGit 是开放原子开源基金会提供的代码托管平台&#xff0c;帮助团队更快、更安全地交付更好的软件。 在创建访问令牌的时候&#xff0c;发现创建之后点不出来令牌字段&#xff0c;不像其它网站会在令牌列表那里显示出来。在“我的令牌”页面只能看到令牌名字和权限&#xff…

设计模式—组合模式

定义: 组合模式&#xff08;Composite Pattern&#xff09;又称为合成模式、部分-整体模式&#xff08;Part-Whole&#xff09;&#xff0c;主要用来描述部分与整体的关系。 定义&#xff1a;将对象组合成树形结构以表示“部分-整体”的层次结构&#xff0c;使用户对单个对象和…

适用于vue3的vant4组件 没有日期时间选择器

项目中需要用到日期和时间一同选择的场景 本来想用 如下代码 van-datetime-picker 发现咋整也不好使 刚开始还以为是引入的问题 后来发现是vant4根本就没这玩应了… <van-datetime-pickerv-model"currentDate"type"datetime"title"选择完整时间&q…

数据结构之排序一

目录 1.排序 一.概念及其分类 二.排序的稳定性 2.插入排序 一.基本思想 二.插入排序的实现 复杂度 稳定性的分析 3.希尔排序 一.预排序代码的实现 二.希尔排序代码实现 复杂度分析 4.clock函数 1.排序 一.概念及其分类 说到排序&#xff0c;我们都不陌生&#x…

向上生长

&#xff08;1&#xff09; 我记得2010年&#xff0c;在中国的苹果应用商店里&#xff0c;充斥的App还有很多&#xff1a;日历App、天气App、电池省电App、记事本App…。但这已经过去了2007-2008-2009三年&#xff0c;这些应用仍然很欢。 我有一个朋友算是中国最早一批开发iOS …

vue.js+element-ui的基础表单

遇到原生的html小型单页应用时&#xff0c;是脱离了vue框架&#xff0c;而我们又想使用vue的语法和element的组件加快我们的开发速度&#xff0c;这个时候就需要引用他们的js了。技术栈即htmlvue.jselement-ui。而使用它们的方法也很简单&#xff0c;引入对应的js和css文件即可…

博世力士乐发布在线配置液压系统3D CAD目录

博世力士乐自1795从美因河畔洛尔的一个铸铁铸造厂到20世纪50年代进入液压市场&#xff0c;2001年&#xff0c;曼内斯曼力士乐与博世自动化的合并&#xff0c;推动了驱动与控制解决方案的所有相关技术无缝集成的新水平。如今博世力士乐独特的行业专业知识已成为量身定制解决方案…

手上积累了一些企业目录,但是没有电话,在企XX天X查也没找到咋办?如何快速精准批量查询其他平台上查不到的企业电话?

在B端业务场景中&#xff0c;长期需要进行拓客。有时候是企业提供客户的联系方式&#xff0c;有时候是销售利用自己的人脉资源&#xff0c;对于资源不多的销售就需要查找到目标客户的联系方式。长期来说&#xff0c;销售都需要进行拓客&#xff0c;自己通过社交&#xff0c;网络…

下载 macOS 系统安装程序的方法

阅读信息&#xff1a; 版本&#xff1a;0.4.20231021 难度&#xff1a;1/10 到 4/10 阅读时间&#xff1a;5 分钟 适合操作系统&#xff1a;10.13, 10.14, 10.15, 11.x, 12.x&#xff0c;13.x, 14 更新2023-10-21 添加Mist的介绍支持版本的更新&#xff0c;13.x&#xff0…

Xilinx FPGA 远程升级时bin和bit文件使用注意

以Spartan-6 ISE开发环境为例。 ISE开发环境支持生成bit和bin格式的程序文件&#xff0c;可以在生成选项进行配置&#xff1a; 把生成的bit文件和bin文件进行二进制比较&#xff0c;发现bit比bin文件头部多了一些内容&#xff08;头部信息&#xff09;&#xff0c;剩余部分完…

web前端笔记+表单练习题+五彩导航栏练习题

web前端笔记 1-骨架快捷方式!enter<!DOCTYPE html><html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>骨架部分</titl…

mysql 事务基本介绍

目录 命令小结 一 MySQL事务的概念 &#xff08;一&#xff09;事务介绍 &#xff08;二&#xff09;事务特点 &#xff08;三&#xff09;事务的ACID特点 1&#xff0c;原子性 1.1 原子性具体形容 1.2 原子性案例 2 &#xff0c;一致性 2.1一致性具体介绍 2.2…

Cubemx串口配置

1.时钟 2.引脚配置 3.重写printf代码 /* USER CODE BEGIN 1 */ int __io_putchar(int ch){HAL_UART_Transmit(&huart1,(uint8_t *) ch, 1,1000);return ch; } /* USER CODE END 1 */

JavaEE 初阶篇-深入了解进程与线程(常见的面试题:进程与线程的区别)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 进程概述 2.0 线程概述 2.1 多线程概述 3.0 常见的面试题&#xff1a;谈谈进程与线程的区别 4.0 Java 实现多线程的常见方法 4.1 实现多线程方法 - 继承 Thread 类…

含“AI”量上涨,智能模组SC208系列助力智慧零售全场景高质发展

AI正重塑智慧零售产业&#xff0c;加速零售在采购、生产、供应链、销售、服务等方面改善运营效率和用户体验。零售行业经历了从线下到线上再到全渠道融合发展过程&#xff0c;“提质、降本、增效、高体验”是亘古不变的商业化与智能化方向。含“AI”量逐渐上涨的智慧零售正经历…

【Pt】新建项目时的设置

新建项目时需要在如下界面做一些设置。 一、模板与文件 模板通常选择“PBR - Metallic Roughness Alpha-blend” 文件可以选择fbx&#xff0c;abc&#xff0c;obj等格式的三维模型文件 二、项目设置 2.1 文件分辨率 指的是在软件中的预览效果&#xff0c;分辨率越高预览效果…

RUST egui体验

egui官方提供了web版的demo&#xff0c;效果还是很不错的&#xff0c;就是用的时候有点一头雾水&#xff0c;没有找到明确的指导怎么把这些组件插入到自己的application或者web。花了一天时间撸了一遍流程&#xff0c;记录一下&#xff0c;说不定以后能用到呢 >_< efram…

Redis学习二--常见问题及处理

基本概念 Redis基本概念数据结构 机制 持久化机制&#xff1a; RDB(内存快照)&#xff1a;某一时刻的内存快照以二进制的方式写入磁盘&#xff0c;可以手动触发和自动触发。 优点&#xff1a;生成文件小&#xff0c;恢复速度快&#xff0c;适用于灾难恢复。 缺点&#xff1a…