STM32存储左右互搏 模拟U盘桥接QSPI总线FATS读写FLASH W25QXX

STM32存储左右互搏 模拟U盘桥接QSPI总线FATS读写FLASH W25QXX

STM32的USB接口可以模拟成为U盘,通过FATS文件系统对连接的存储单元进行U盘方式的读写。
这里介绍STM32CUBEIDE开发平台HAL库模拟U盘桥接Quad SPI总线FATS读写W25Q各型号FLASH的例程。
FLASH是常用的一种非易失存储单元,W25QXX系列Flash有不同容量的型号,如W25Q64的容量为64Mbit,也就是8MByte。

W25QXX介绍

W25QXX的SOIC封装如下所示,在采用QUAL SPI而不是SPI时,管脚定义为:
在这里插入图片描述
即由片选(/CS), 时钟(CLK), 双向4根输入输出线(IO0, IO1, IO2, IO3)组成6线QSPI信号接口。VCC和GND提供电源和接地连接。

例程采用STM32H750VBT6芯片, FLASH可以选择为8/16/32/64/128/256/512/1024 Mbit的W25Q型号。

STM32工程配置

首先建立基本工程并设置时钟:

在这里插入图片描述
对于STM32H7,有专用的内部48MHz时钟用于USB接口, 其它应用采用内部高速时钟接口即可:
在这里插入图片描述
在这里插入图片描述
设置UART1作为通讯串口:
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
单颗FLASH连接到了QSPI接口Bank1,对QSPI进行配置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
对FATS文件系统进行配置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
STM32H7资源多,可以将堆栈开大些:
在这里插入图片描述
保存并生成初始工程代码:
在这里插入图片描述

STM32工程代码

UART串口printf打印输出实现参考:STM32 UART串口printf函数应用及浮点打印代码空间节省 (HAL)

建立W25Q访问的库头文件W25QXX_QSPI.h:

#ifndef INC_W25QXX_H_
#define INC_W25QXX_H_

#include "main.h"

//W25QXX serial chip list:
#define W25Q80_ID 	0XEF13
#define W25Q16_ID 	0XEF14
#define W25Q32_ID 	0XEF15
#define W25Q64_ID 	0XEF16
#define W25Q128_ID	0XEF17
#define W25Q256_ID 0XEF18
#define W25Q512_ID 0XEF19
#define W25Q1024_ID 0XEF20

extern uint16_t W25QXX_TYPE; //To indicate W25QXX type used in this procedure

//command table for W25QXX access
#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

#define W25X_QUAD_QuadInputPageProgram 0x32
#define W25X_QUAD_FastReadQuadOutput 0x6B
#define W25X_QUAD_ManufactDeviceID 0x94
#define W25X_QUAD_FastRead 0xEB
#define W25X_QUAD_SetBurstwithWrap 0x77


uint8_t W25QXX_Init(void);
uint16_t  W25QXX_ReadID(void);  	    		  //Read W25QXX ID
uint8_t W25QXX_ReadSR(uint8_t reg_num);           //Read from status register
void W25QXX_4ByteAddr_Enable(void);               //Enable 4-byte address mode
void W25QXX_Write_SR(uint8_t reg_num,uint8_t d);  //Write to status register
void W25QXX_Write_Enable(void);  		          //Write enable
void W25QXX_Write_Disable(void);		          //Write disable
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite); //Write operation w/o check
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);            //Read operation
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);         //Write operation
void W25QXX_Erase_Chip(void);    	  	                                                //Erase whole chip
void W25QXX_Erase_Sector(uint32_t Sector_Num);	                                        //Erase sector in specific sector number
void W25QXX_Wait_Busy(void);           	       //Wait idle status before next operation
void W25QXX_PowerDown(void);        	       //Enter power-down mode
void W25QXX_WAKEUP(void);				       //Wake-up


#endif /* INC_W25QXX_H_ */

建立W25Q访问的库头文件W25QXX_QSPI.c:

#include "W25QXX_QSPI.h"

extern QSPI_HandleTypeDef hqspi;
extern void PY_Delay_us_t(uint32_t Delay);
extern HAL_StatusTypeDef QSPI_Send_CMD(uint8_t cmd,uint32_t addr,uint8_t mode,uint8_t dmcycle);
extern HAL_StatusTypeDef QSPI_Receive(uint8_t* buf,uint32_t datalen);
extern HAL_StatusTypeDef QSPI_Transmit(uint8_t* buf,uint32_t datalen);

#define Use_Quad_Line 1

uint16_t W25QXX_TYPE=W25Q64_ID;

//W25QXX initialization
uint8_t W25QXX_Init(void)
{
    uint8_t temp;

	W25QXX_TYPE=W25QXX_ReadID();

	if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID))
    {
        temp=W25QXX_ReadSR(3);              //read status register 3
        if((temp&0X01)==0)			        //judge address mode and configure to 4-byte address mode
		{
			QSPI_Send_CMD(W25X_Enable4ByteAddr, 0x00, (0<<6)|(0<<4)|(0<<2)|(1<<0), 0);
		}
    }

    if((W25QXX_TYPE==0x0000)||(W25QXX_TYPE==0xFFFF)) return 0;
    else return 1;
}

//Read status registers of W25QXX
//reg_num: register number from 1 to 3
//return: value of selected register

//SR1 (default 0x00):
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR: default 0, status register protection bit used with WP
//TB,BP2,BP1,BP0: FLASH region write protection configuration
//WEL: write enable lock
//BUSY: busy flag (1: busy; 0: idle)

//SR2:
//BIT7  6   5   4   3   2   1   0
//SUS   CMP LB3 LB2 LB1 (R) QE  SRP1

//SR3:
//BIT7      6    5    4   3   2   1   0
//HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
uint8_t W25QXX_ReadSR(uint8_t reg_num)
{
	uint8_t byte=0,command=0;
    switch(reg_num)
    {
        case 1:
            command=W25X_ReadStatusReg1;    //To read status register 1
            break;
        case 2:
            command=W25X_ReadStatusReg2;    //To read status register 2
            break;
        case 3:
            command=W25X_ReadStatusReg3;    //To read status register 3
            break;
        default:
            command=W25X_ReadStatusReg1;
            break;
    }

	QSPI_Send_CMD(command, 0x00, (1<<6)|(0<<4)|(0<<2)|(1<<0), 0);  //send command
	QSPI_Receive(&byte,1);                                         //read data
	return byte;
}

//Write status registers of W25QXX
//reg_num: register number from 1 to 3
//d: data for updating status register
void W25QXX_Write_SR(uint8_t reg_num,uint8_t d)
{
    uint8_t command=0;
    switch(reg_num)
    {
        case 1:
            command=W25X_WriteStatusReg1;    //To write status register 1
            break;
        case 2:
            command=W25X_WriteStatusReg2;    //To write status register 2
            break;
        case 3:
            command=W25X_WriteStatusReg3;    //To write status register 3
            break;
        default:
            command=W25X_WriteStatusReg1;
            break;
    }

	QSPI_Send_CMD(command, 0x00, (1<<6)|(0<<4)|(0<<2)|(1<<0), 0); //send command
	QSPI_Transmit(&d, 1);                                         //write data
}
//W25QXX write enable
void W25QXX_Write_Enable(void)
{
	QSPI_Send_CMD(W25X_WriteEnable, 0x00, (0<<6)|(0<<4)|(0<<2)|(1<<0), 0); //send command
}
//W25QXX write disable
void W25QXX_Write_Disable(void)
{
	QSPI_Send_CMD(W25X_WriteDisable, 0x00, (0<<6)|(0<<4)|(0<<2)|(1<<0), 0); //send command
}

//Read chip ID
//return:
//0XEF13 for W25Q80
//0XEF14 for W25Q16
//0XEF15 for W25Q32
//0XEF16 for W25Q64
//0XEF17 for W25Q128
//0XEF18 for W25Q256
uint16_t W25QXX_ReadID(void)
{
	uint16_t Temp = 0;
	uint8_t st;
	uint8_t TD[8];

	if(Use_Quad_Line)
	{
		uint8_t extra_dummy = 4; //Adjust dummy here for I/O direction adjustment delay
		QSPI_Send_CMD(W25X_QUAD_ManufactDeviceID, 0x000000f0, (3<<6)|(3<<4)|(3<<2)|(1<<0), extra_dummy);   ///To read Manufacturer/Device ID in Quad mode
		st = QSPI_Receive(TD, 2);
		if(st==0)
		{
			  Temp = (TD[0]<<8)|TD[1];
		}
		else
		{
			  Temp = 0;
		}

		  return Temp;
	}
	else
	{
		  QSPI_Send_CMD(W25X_ManufactDeviceID, 0x00, (1<<6)|(0<<4)|(0<<2)|(1<<0), 0);  //To read Manufacturer/Device ID in single line mode
		  st = QSPI_Receive(TD, 5);
		  if(st==0)
		  {
			  Temp = (TD[3]<<8)|TD[4];
		  }
		  else
		  {
			  Temp = 0;
		  }

		  return Temp;

	}
}
//Read W25QXX from specific address for specific byte length
//pBuffer: data buffer
//ReadAddr: specific address
//NumByteToRead: specific byte length (max 65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
	if(Use_Quad_Line)
	{
		  uint8_t extra_dummy = 8;    //Adjust dummy here for I/O direction adjustment delay
		  if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID))
		  {
			  QSPI_Send_CMD(W25X_QUAD_FastReadQuadOutput, ReadAddr, (3<<6)|(3<<4)|(1<<2)|(1<<0), extra_dummy);
		  }
		  else
		  {
			  QSPI_Send_CMD(W25X_QUAD_FastReadQuadOutput, ReadAddr, (3<<6)|(2<<4)|(1<<2)|(1<<0), extra_dummy);
		  }
		  QSPI_Receive(pBuffer, NumByteToRead); //read data
	}
	else
	{
		  uint8_t extra_dummy = 8;    //Adjust dummy here for I/O direction adjustment delay
		  if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID))
		  {
			  QSPI_Send_CMD(W25X_ReadData, ReadAddr, (1<<6)|(3<<4)|(1<<2)|(1<<0), extra_dummy);
		  }
		  else
		  {
			  QSPI_Send_CMD(W25X_ReadData, ReadAddr, (1<<6)|(2<<4)|(1<<2)|(1<<0), extra_dummy);
		  }
		  QSPI_Receive(pBuffer, NumByteToRead); //read data
	}

}

//Write W25QXX not more than 1 page (256 bytes)
//pBuffer: data buffer
//WriteAddr: specific address
//NumByteToWrite: specific byte length (max 256)
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    W25QXX_Write_Enable();                                        //write enable

    //send write command
	if(Use_Quad_Line)
	{
	    if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID))
	    {
	        QSPI_Send_CMD(W25X_QUAD_QuadInputPageProgram, WriteAddr, (3<<6)|(3<<4)|(1<<2)|(1<<0), 0);
	    }
	    else
	    {
	    	QSPI_Send_CMD(W25X_QUAD_QuadInputPageProgram, WriteAddr, (3<<6)|(2<<4)|(1<<2)|(1<<0), 0);
	    }
	}
	else
	{
	    if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID))
	    {
	        QSPI_Send_CMD(W25X_PageProgram, WriteAddr, (1<<6)|(3<<4)|(1<<2)|(1<<0), 0);
	    }
	    else
	    {
	    	QSPI_Send_CMD(W25X_PageProgram, WriteAddr, (1<<6)|(2<<4)|(1<<2)|(1<<0), 0);
	    }

	}

    QSPI_Transmit(pBuffer, NumByteToWrite); //write data
	W25QXX_Wait_Busy();
}

//Write W25QXX w/o erase check and w/o byte number restriction
//pBuffer: data buffer
//WriteAddr: specific address
//NumByteToWrite: specific byte length (max 65535)
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
	uint16_t remained_byte_num_in_page;
	remained_byte_num_in_page=256-WriteAddr%256;                                                       //remained byte number in page
	if( NumByteToWrite <= remained_byte_num_in_page ) remained_byte_num_in_page = NumByteToWrite;      //data can be written in single page
	while(1)
	{
		W25QXX_Write_Page(pBuffer,WriteAddr,remained_byte_num_in_page);
		if(NumByteToWrite==remained_byte_num_in_page)break;                                            //end write operation
	 	else                                                                                           //NumByteToWrite>remained_byte_num_in_page
		{
			pBuffer+=remained_byte_num_in_page;
			WriteAddr+=remained_byte_num_in_page;

			NumByteToWrite-=remained_byte_num_in_page;
			if(NumByteToWrite>256) remained_byte_num_in_page=256;                                       //for whole page write
			else remained_byte_num_in_page=NumByteToWrite; 	                                           //for non-whole page write
		}
	};
}

//Write W25QXX w/ erase after check and w/o byte number restriction
//pBuffer: data buffer
//WriteAddr: specific address
//NumByteToWrite: specific byte length (max 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;                                        //sector number (16 pages for 1 sector) for destination address
	secoff=WriteAddr%4096;                                        //offset address in sector for destination address
	secremain=4096-secoff;                                        //remained space for sector
 	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;        //data can be written in single sector
	while(1)
	{
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);                 //read sector data for ease necessity judgment
		for(i=0;i<secremain;i++)                                  //check sector data status
		{
			if(W25QXX_BUF[secoff+i]!=0XFF) break;                 //ease necessary
		}

		if(i<secremain)                                           //for ease
		{
			W25QXX_Erase_Sector(secpos);                          //ease sector
			for(i=0;i<secremain;i++)	                          //data copy
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);     //write sector

		}
		else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);   //write data for sector unnecessary to erase

		if(NumByteToWrite==secremain)break;                        //for operation end
		else                                                       //for operation continuing
		{
			secpos++;                                              //sector number + 1
			secoff=0;                                              //offset address from 0

		   	pBuffer+=secremain;                                    //pointer adjustment
			WriteAddr+=secremain;                                  //write address adjustment
		   	NumByteToWrite-=secremain;				               //write number adjustment
			if(NumByteToWrite>4096) secremain=4096;	               //not last sector
			else secremain=NumByteToWrite;			               //last sector
		}
	};
}

//Erase whole chip, long waiting...
void W25QXX_Erase_Chip(void)
{
    W25QXX_Write_Enable();                  //write enable
    W25QXX_Wait_Busy();
    QSPI_Send_CMD(W25X_ChipErase, 0x00, (0<<6)|(0<<4)|(0<<2)|(1<<0), 0); //send erase command
	W25QXX_Wait_Busy();   				    //wait for erase complete
}

//Erase one sector
//Sector_Num: sector number
void W25QXX_Erase_Sector(uint32_t Sector_Num)
{
 	Sector_Num *= 4096;
    W25QXX_Write_Enable();                                     //write enable
    W25QXX_Wait_Busy();

    if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID)) //send highest 8-bit address
    {
    	QSPI_Send_CMD(W25X_SectorErase, Sector_Num, (0<<6)|(3<<4)|(1<<2)|(1<<0), 0); //send erase command
    }
    else
    {
    	QSPI_Send_CMD(W25X_SectorErase, Sector_Num, (0<<6)|(2<<4)|(1<<2)|(1<<0), 0); //send erase command
    }
    W25QXX_Wait_Busy();   				                       //wait for erase complete
}

//Wait idle status before next operation
void W25QXX_Wait_Busy(void)
{
	while((W25QXX_ReadSR(1)&0x01)==0x01);    //wait for busy flag cleared
}

//Enter power-down mode
#define tDP_us 3
void W25QXX_PowerDown(void)
{
    QSPI_Send_CMD(W25X_PowerDown, 0, (0<<6)|(0<<4)|(0<<2)|(1<<0), 0);        //send power-down command
	PY_Delay_us_t(tDP_us);                   //tDP
}
//Wake-up
#define tRES1_us 3
void W25QXX_WAKEUP(void)
{
    QSPI_Send_CMD(W25X_ReleasePowerDown, 0, (0<<6)|(0<<4)|(0<<2)|(1<<0), 0); //send release power-down command
	PY_Delay_us_t(tRES1_us);                 //tRES1
}

对ffconf.h添加包含信息:
在这里插入图片描述

#include "main.h"
#include "stm32h7xx_hal.h"

修改user_diskio.c,对文件操作函数与底层FLASH读写提供连接:

/* USER CODE BEGIN Header */
/**
 ******************************************************************************
  * @file    user_diskio.c
  * @brief   This file includes a diskio driver skeleton to be completed by the user.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
 /* USER CODE END Header */

#ifdef USE_OBSOLETE_USER_CODE_SECTION_0
/*
 * Warning: the user section 0 is no more in use (starting from CubeMx version 4.16.0)
 * To be suppressed in the future.
 * Kept to ensure backward compatibility with previous CubeMx versions when
 * migrating projects.
 * User code previously added there should be copied in the new user sections before
 * the section contents can be deleted.
 */
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
#endif

/* USER CODE BEGIN DECL */
/**************************SELF DEFINITION PART************/
#include "diskio.h"		/* Declarations of disk functions */
#include "W25QXX_QSPI.h"
/**********************************************************/
/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "ff_gen_drv.h"

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/

/* Private variables ---------------------------------------------------------*/
/* Disk status */
static volatile DSTATUS Stat = STA_NOINIT;

/* USER CODE END DECL */

/* Private function prototypes -----------------------------------------------*/
DSTATUS USER_initialize (BYTE pdrv);
DSTATUS USER_status (BYTE pdrv);
DRESULT USER_read (BYTE pdrv, BYTE *buff, DWORD sector, UINT count);
#if _USE_WRITE == 1
  DRESULT USER_write (BYTE pdrv, const BYTE *buff, DWORD sector, UINT count);
#endif /* _USE_WRITE == 1 */
#if _USE_IOCTL == 1
  DRESULT USER_ioctl (BYTE pdrv, BYTE cmd, void *buff);
#endif /* _USE_IOCTL == 1 */

Diskio_drvTypeDef  USER_Driver =
{
  USER_initialize,
  USER_status,
  USER_read,
#if  _USE_WRITE
  USER_write,
#endif  /* _USE_WRITE == 1 */
#if  _USE_IOCTL == 1
  USER_ioctl,
#endif /* _USE_IOCTL == 1 */
};

/* Private functions ---------------------------------------------------------*/

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_initialize (
	BYTE pdrv           /* Physical drive nmuber to identify the drive */
)
{
  /* USER CODE BEGIN INIT */
	/**************************SELF DEFINITION PART************/
		  uint8_t res;
		  res = W25QXX_Init();

		  if(res) return RES_OK;
		  else return  STA_NOINIT;
	/**********************************************************/
	/*
    Stat = STA_NOINIT;
    return Stat;
    */
  /* USER CODE END INIT */
}

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS USER_status (
	BYTE pdrv       /* Physical drive number to identify the drive */
)
{
  /* USER CODE BEGIN STATUS */
	/**************************SELF DEFINITION PART************/
		switch (pdrv)
			{
				case 0 :
					return RES_OK;
				case 1 :
					return RES_OK;
				case 2 :
					return RES_OK;
				default:
					return STA_NOINIT;
			}
	/**********************************************************/
	/*
    Stat = STA_NOINIT;
    return Stat;
    */
  /* USER CODE END STATUS */
}

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT USER_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 */
)
{
  /* USER CODE BEGIN READ */
	/**************************SELF DEFINITION PART************/
		    uint16_t len;
			if( !count )
			{
				return RES_PARERR;  /* count不能等于0,否则返回参数错�?? */
			}
			switch (pdrv)
			{
				case 0:
					sector <<= 9; //Convert sector number to byte address
				    len = count*512;
				    W25QXX_Read(buff, sector, len);
				    return RES_OK;
				default:
					return RES_ERROR;
			}
	/**********************************************************/
	/*
    return RES_OK;
    */
  /* USER CODE END READ */
}

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT USER_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 */
)
{
  /* USER CODE BEGIN WRITE */
  /* USER CODE HERE */
	/**************************SELF DEFINITION PART************/
		    uint16_t len;
			if( !count )
			{
				return RES_PARERR;  /* count不能等于0,否则返回参数错�?? */
			}
			switch (pdrv)
			{
				case 0:
					sector <<= 9; //Convert sector number to byte address
				    len = count*512;
				    W25QXX_Write((uint8_t *)buff, sector, len);
				    return RES_OK;
				default:
					return RES_ERROR;
			}
	/*********************************************************/
	/*
    return RES_OK;
    */
  /* USER CODE END WRITE */
}
#endif /* _USE_WRITE == 1 */

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT USER_ioctl (
	BYTE pdrv,      /* Physical drive nmuber (0..) */
	BYTE cmd,       /* Control code */
	void *buff      /* Buffer to send/receive control data */
)
{
  /* USER CODE BEGIN IOCTL */
	/**************************SELF DEFINITION PART************/
             #define user_sector_byte_size 512
		     DRESULT res;
			 switch(cmd)
			    {
				    case CTRL_SYNC:
								W25QXX_Wait_Busy();
								res=RES_OK;
				        break;
				    case GET_SECTOR_SIZE:
				        *(WORD*)buff = user_sector_byte_size;
				        res = RES_OK;
				        break;
				    case GET_BLOCK_SIZE:
				        *(WORD*)buff = 4096/user_sector_byte_size;
				        res = RES_OK;
				        break;
				    case GET_SECTOR_COUNT:
				    	W25QXX_TYPE=W25QXX_ReadID();
				    	if(W25QXX_TYPE==W25Q80_ID) *(DWORD*)buff = (8*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q16_ID) *(DWORD*)buff = (16*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q32_ID) *(DWORD*)buff = (32*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q64_ID) *(DWORD*)buff = (64*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q128_ID) *(DWORD*)buff = (128*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q256_ID) *(DWORD*)buff = (256*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q512_ID) *(DWORD*)buff = (512*1024*1024/512);
				    	else if(W25QXX_TYPE==W25Q1024_ID) *(DWORD*)buff = (1024*1024*1024/512);
				    	else *(DWORD*)buff = (8*1024*1024/512);
				        res = RES_OK;
				        break;
				    default:
				        res = RES_PARERR;
				        break;
			    }
				return res;
	/**********************************************************/
	/*
    DRESULT res = RES_ERROR;
    return res;
    */
  /* USER CODE END IOCTL */
}
#endif /* _USE_IOCTL == 1 */


上面配置的FATS协议是用户可操作的显式协议,譬如本例程通过串口发送指令后控制对FLASH的FATS操作。STM32的U盘接口相当于包含对PC端的握手协议和对内部的隐式FATS协议,还需要配置底层对针对的存储单元读写操作函数。
配置U盘接口包含如下部分:
引入头文件申明
在这里插入图片描述
设置U盘识别的大小参数:
在这里插入图片描述
这里0x4000对应16K,0x200对应512(字节), 16K*512=8M, 所以U盘会识别为8MB的U盘。

然后是U盘初始化的设置
在这里插入图片描述
U盘识别时获取U盘容量的函数
在这里插入图片描述
然后再配置读操作和写操作函数
在这里插入图片描述
完整的代码如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : usbd_storage_if.c
  * @version        : v1.0_Cube
  * @brief          : Memory management layer.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "usbd_storage_if.h"

/* USER CODE BEGIN INCLUDE */
#include "W25QXX_QSPI.h"
/* USER CODE END INCLUDE */

/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PV */
/* Private variables ---------------------------------------------------------*/

/* USER CODE END PV */

/** @addtogroup STM32_USB_OTG_DEVICE_LIBRARY
  * @brief Usb device.
  * @{
  */

/** @defgroup USBD_STORAGE
  * @brief Usb mass storage device module
  * @{
  */

/** @defgroup USBD_STORAGE_Private_TypesDefinitions
  * @brief Private types.
  * @{
  */

/* USER CODE BEGIN PRIVATE_TYPES */
#if 0
/* USER CODE END PRIVATE_TYPES */

/**
  * @}
  */

/** @defgroup USBD_STORAGE_Private_Defines
  * @brief Private defines.
  * @{
  */

#define STORAGE_LUN_NBR                  1
#define STORAGE_BLK_NBR                  0x10000
#define STORAGE_BLK_SIZ                  0x200

/* USER CODE BEGIN PRIVATE_DEFINES */
#endif

#define STORAGE_LUN_NBR                  1         //STORAGE_LUN_NBR : disk number
#define STORAGE_BLK_NBR                  0x4000    //STORAGE_BLK_NBR : block number
#define STORAGE_BLK_SIZ                  0x200     //TORAGE_BLK_SIZ : block size
/* USER CODE END PRIVATE_DEFINES */

/**
  * @}
  */

/** @defgroup USBD_STORAGE_Private_Macros
  * @brief Private macros.
  * @{
  */

/* USER CODE BEGIN PRIVATE_MACRO */

/* USER CODE END PRIVATE_MACRO */

/**
  * @}
  */

/** @defgroup USBD_STORAGE_Private_Variables
  * @brief Private variables.
  * @{
  */

/* USER CODE BEGIN INQUIRY_DATA_FS */
/** USB Mass storage Standard Inquiry Data. */
const int8_t STORAGE_Inquirydata_FS[] = {/* 36 */

  /* LUN 0 */
  0x00,
  0x80,
  0x02,
  0x02,
  (STANDARD_INQUIRY_DATA_LEN - 5),
  0x00,
  0x00,
  0x00,
  'S', 'T', 'M', ' ', ' ', ' ', ' ', ' ', /* Manufacturer : 8 bytes */
  'P', 'r', 'o', 'd', 'u', 'c', 't', ' ', /* Product      : 16 Bytes */
  ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ',
  '0', '.', '0' ,'1'                      /* Version      : 4 Bytes */
};
/* USER CODE END INQUIRY_DATA_FS */

/* USER CODE BEGIN PRIVATE_VARIABLES */

/* USER CODE END PRIVATE_VARIABLES */

/**
  * @}
  */

/** @defgroup USBD_STORAGE_Exported_Variables
  * @brief Public variables.
  * @{
  */

extern USBD_HandleTypeDef hUsbDeviceFS;

/* USER CODE BEGIN EXPORTED_VARIABLES */

/* USER CODE END EXPORTED_VARIABLES */

/**
  * @}
  */

/** @defgroup USBD_STORAGE_Private_FunctionPrototypes
  * @brief Private functions declaration.
  * @{
  */

static int8_t STORAGE_Init_FS(uint8_t lun);
static int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size);
static int8_t STORAGE_IsReady_FS(uint8_t lun);
static int8_t STORAGE_IsWriteProtected_FS(uint8_t lun);
static int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len);
static int8_t STORAGE_GetMaxLun_FS(void);

/* USER CODE BEGIN PRIVATE_FUNCTIONS_DECLARATION */

/* USER CODE END PRIVATE_FUNCTIONS_DECLARATION */

/**
  * @}
  */

USBD_StorageTypeDef USBD_Storage_Interface_fops_FS =
{
  STORAGE_Init_FS,
  STORAGE_GetCapacity_FS,
  STORAGE_IsReady_FS,
  STORAGE_IsWriteProtected_FS,
  STORAGE_Read_FS,
  STORAGE_Write_FS,
  STORAGE_GetMaxLun_FS,
  (int8_t *)STORAGE_Inquirydata_FS
};

/* Private functions ---------------------------------------------------------*/
/**
  * @brief  Initializes the storage unit (medium) over USB FS IP
  * @param  lun: Logical unit number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Init_FS(uint8_t lun)
{
  /* USER CODE BEGIN 2 */
	   //UNUSED(lun);

       W25QXX_Init();

	   return (USBD_OK);
  /* USER CODE END 2 */
}

/**
  * @brief  Returns the medium capacity.
  * @param  lun: Logical unit number.
  * @param  block_num: Number of total block number.
  * @param  block_size: Block size.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{
  /* USER CODE BEGIN 3 */
	  //UNUSED(lun);

	  *block_num  = STORAGE_BLK_NBR;
	  *block_size = STORAGE_BLK_SIZ;
	  return (USBD_OK);
  /* USER CODE END 3 */
}

/**
  * @brief   Checks whether the medium is ready.
  * @param  lun:  Logical unit number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_IsReady_FS(uint8_t lun)
{
  /* USER CODE BEGIN 4 */
  //UNUSED(lun);

  return (USBD_OK);
  /* USER CODE END 4 */
}

/**
  * @brief  Checks whether the medium is write protected.
  * @param  lun: Logical unit number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_IsWriteProtected_FS(uint8_t lun)
{
  /* USER CODE BEGIN 5 */
  //UNUSED(lun);

  return (USBD_OK);
  /* USER CODE END 5 */
}

/**
  * @brief  Reads data from the medium.
  * @param  lun: Logical unit number.
  * @param  buf: data buffer.
  * @param  blk_addr: Logical block address.
  * @param  blk_len: Blocks number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 6 */
	  //UNUSED(lun);
	  //UNUSED(buf);
	  //UNUSED(blk_addr);
	  //UNUSED(blk_len);
	  W25QXX_Read(buf, blk_addr*STORAGE_BLK_SIZ, blk_len*STORAGE_BLK_SIZ);

	  return (USBD_OK);
  /* USER CODE END 6 */
}

/**
  * @brief  Writes data into the medium.
  * @param  lun: Logical unit number.
  * @param  buf: data buffer.
  * @param  blk_addr: Logical block address.
  * @param  blk_len: Blocks number.
  * @retval USBD_OK if all operations are OK else USBD_FAIL
  */
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{
  /* USER CODE BEGIN 7 */
	 //UNUSED(lun);
	  //UNUSED(buf);
	  //UNUSED(blk_addr);
	  //UNUSED(blk_len);
	  W25QXX_Write(buf, blk_addr*STORAGE_BLK_SIZ, blk_len*STORAGE_BLK_SIZ);

	  return (USBD_OK);
  /* USER CODE END 7 */
}

/**
  * @brief  Returns the Max Supported LUNs.
  * @param  None
  * @retval Lun(s) number.
  */
int8_t STORAGE_GetMaxLun_FS(void)
{
  /* USER CODE BEGIN 8 */
  return (STORAGE_LUN_NBR - 1);
  /* USER CODE END 8 */
}

/* USER CODE BEGIN PRIVATE_FUNCTIONS_IMPLEMENTATION */

/* USER CODE END PRIVATE_FUNCTIONS_IMPLEMENTATION */

/**
  * @}
  */

/**
  * @}
  */


然后在main.c里根据串口输入命令(16进制单字节)实现如下功能:
0x01. 读取FLASH ID
0x02. 装载FATS文件系统
0x03: 创建/打开文件并从头位置写入数据
0x04: 打开文件并从头位置读入数据
0x05: 创建/打开文件并从特定位置写入数据
0x06: 打开文件并从特定位置读入数据
完整的代码实现如下:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
//Written by Pegasus Yu in 2023
//NOte: to access W25Qxx, send single line command at first to trigger consequent operation mode for address and data which could be 1-line or 4-line
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "usb_device.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <string.h>
#include "usart.h"
#include "W25QXX_QSPI.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
__IO float usDelayBase;
void PY_usDelayTest(void)
{
  __IO uint32_t firstms, secondms;
  __IO uint32_t counter = 0;

  firstms = HAL_GetTick()+1;
  secondms = firstms+1;

  while(uwTick!=firstms) ;

  while(uwTick!=secondms) counter++;

  usDelayBase = ((float)counter)/1000;
}

void PY_Delay_us_t(uint32_t Delay)
{
  __IO uint32_t delayReg;
  __IO uint32_t usNum = (uint32_t)(Delay*usDelayBase);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}

void PY_usDelayOptimize(void)
{
  __IO uint32_t firstms, secondms;
  __IO float coe = 1.0;

  firstms = HAL_GetTick();
  PY_Delay_us_t(1000000) ;
  secondms = HAL_GetTick();

  coe = ((float)1000)/(secondms-firstms);
  usDelayBase = coe*usDelayBase;
}


void PY_Delay_us(uint32_t Delay)
{
  __IO uint32_t delayReg;

  __IO uint32_t msNum = Delay/1000;
  __IO uint32_t usNum = (uint32_t)((Delay%1000)*usDelayBase);

  if(msNum>0) HAL_Delay(msNum);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}
/* 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 ---------------------------------------------------------*/

QSPI_HandleTypeDef hqspi;

UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */
uint8_t uart1_rx[16];
uint8_t cmd;

uint8_t Flash_mount_status = 0; //FLASH fats mount status indication (0: unmount; 1: mount)
uint8_t FATS_Buff[_MAX_SS]; //Buffer for f_mkfs() operation

FRESULT retFLASH;
FIL file;
FATFS *fs;

UINT bytesread;
UINT byteswritten;
uint8_t rBuffer[20];      //Buffer for read
uint8_t WBuffer[20] ={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}; //Buffer for write

#define user_sector_byte_size 512
uint8_t flashbuffer[user_sector_byte_size];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);
static void MX_QUADSPI_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

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

/*
QSPI TX in block mode for command byte
cmd: command to be sent to device
addr: address to be sent to device
mode: operation mode set as
	mode[1:0] for command transmission mode ( 00: no command; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
	mode[3:2] for address transmission mode ( 00: no address; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
	mode[5:4] for address length ( 00:8-bit address; 01: 16-bit address; 10: 24-bit address; 11: 32-bit address )
	mode[7:6] for data transmission mode ( 00: no command; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
dmcycle: dummy clock cycle
*/
HAL_StatusTypeDef QSPI_Send_CMD(uint8_t cmd,uint32_t addr,uint8_t mode,uint8_t dmcycle)
{
	QSPI_CommandTypeDef Cmdhandler;

	Cmdhandler.Instruction=cmd;		//set cmd
	Cmdhandler.Address=addr;		//set address
	Cmdhandler.DummyCycles=dmcycle; //set dummy circle number

	/*set cmd transmission mode*/
	if(((mode>>0)&0x03) == 0)
	Cmdhandler.InstructionMode=QSPI_INSTRUCTION_NONE;
	else if(((mode>>0)&0x03) == 1)
	Cmdhandler.InstructionMode=QSPI_INSTRUCTION_1_LINE;
	else if(((mode>>0)&0x03) == 2)
	Cmdhandler.InstructionMode=QSPI_INSTRUCTION_2_LINES;
	else if(((mode>>0)&0x03) == 3)
	Cmdhandler.InstructionMode=QSPI_INSTRUCTION_4_LINES;

	/*set address transmission mode*/
	if(((mode>>2)&0x03) == 0)
	Cmdhandler.AddressMode=QSPI_ADDRESS_NONE;
	else if(((mode>>2)&0x03) == 1)
	Cmdhandler.AddressMode=QSPI_ADDRESS_1_LINE;
	else if(((mode>>2)&0x03) == 2)
	Cmdhandler.AddressMode=QSPI_ADDRESS_2_LINES;
	else if(((mode>>2)&0x03) == 3)
	Cmdhandler.AddressMode=QSPI_ADDRESS_4_LINES;

	/*set address length*/
	if(((mode>>4)&0x03) == 0)
	Cmdhandler.AddressSize=QSPI_ADDRESS_8_BITS;
	else if(((mode>>4)&0x03) == 1)
	Cmdhandler.AddressSize=QSPI_ADDRESS_16_BITS;
	else if(((mode>>4)&0x03) == 2)
	Cmdhandler.AddressSize=QSPI_ADDRESS_24_BITS;
	else if(((mode>>4)&0x03) == 3)
	Cmdhandler.AddressSize=QSPI_ADDRESS_32_BITS;

	/*set data transmission mode*/
	if(((mode>>6)&0x03) == 0)
	Cmdhandler.DataMode=QSPI_DATA_NONE;
	else if(((mode>>6)&0x03) == 1)
	Cmdhandler.DataMode=QSPI_DATA_1_LINE;
	else if(((mode>>6)&0x03) == 2)
	Cmdhandler.DataMode=QSPI_DATA_2_LINES;
	else if(((mode>>6)&0x03) == 3)
	Cmdhandler.DataMode=QSPI_DATA_4_LINES;

	Cmdhandler.SIOOMode=QSPI_SIOO_INST_EVERY_CMD;				/*Send instruction on every transaction*/
	Cmdhandler.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE;		/*No alternate bytes*/
	Cmdhandler.DdrMode=QSPI_DDR_MODE_DISABLE;					/*Double data rate mode disabled*/
	Cmdhandler.DdrHoldHalfCycle=QSPI_DDR_HHC_ANALOG_DELAY;      /*Delay the data output using analog delay in DDR mode*/

	return HAL_QSPI_Command(&hqspi,&Cmdhandler,5000);
}

//QSPI RX in block mode for data
//buf : buffer address for RX data
//datalen : RX data length
//return : 0, OK
//         others, error code
HAL_StatusTypeDef QSPI_Receive(uint8_t* buf,uint32_t datalen)
{
    hqspi.Instance->DLR=datalen-1;
    return HAL_QSPI_Receive(&hqspi,buf,5000);
}

//QSPI TX in block mode for data
//buf : buffer address for TX data
//datalen : TX data length
//return : 0, OK
//         others, error code
HAL_StatusTypeDef QSPI_Transmit(uint8_t* buf,uint32_t datalen)
{
    hqspi.Instance->DLR=datalen-1;
    return HAL_QSPI_Transmit(&hqspi,buf,5000);
}

extern char USERPath[4];
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	Flash_mount_status = 0;
	uint32_t FLASH_Read_Size;

	char * dpath = "0:"; //Disk Path
    for(uint8_t i=0; i<4; i++)
    {
	  USERPath[i] = *(dpath+i);
    }
	const TCHAR* filepath = "0:test.txt";
  /* 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_USART1_UART_Init();
  MX_QUADSPI_Init();
  MX_FATFS_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */
  PY_usDelayTest();
  PY_usDelayOptimize();

  HAL_UART_Receive_IT(&huart1, uart1_rx, 1);

  W25QXX_Init();

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		 if(cmd==0x01) //Read ID
		 {
			  cmd = 0;

			  printf("Flash ID = 0x%x\r\n\r\n",  W25QXX_ReadID());
		      printf("W25Q80_ID: 0XEF13\r\n");
		      printf("W25Q16_ID: 0XEF14\r\n");
		      printf("W25Q32_ID: 0XEF15\r\n");
		      printf("W25Q64_ID: 0XEF16\r\n");
		      printf("W25Q128_ID: 0XEF17\r\n");
		      printf("W25Q256_ID: 0XEF18\r\n");
		      printf("W25Q512_ID: 0XEF18\r\n");
		      printf("W25Q1024_ID: 0XEF20\r\n");
		  }
	     else if(cmd==2) //Flash File System Mount
	     {
	    	 cmd = 0;

	    	 retFLASH=f_mount(&USERFatFS, (TCHAR const*)USERPath, 1);
	    	    		 if (retFLASH != FR_OK)
	    	    		 {
	    	    		   printf("File system mount failure: %d\r\n", retFLASH);

	    	    		   if(retFLASH==FR_NO_FILESYSTEM)
	    	    		   {
	    	    			   printf("No file system. Now to format......\r\n");
	    	    			   retFLASH = f_mkfs((TCHAR const*)USERPath, FM_FAT, 1024, FATS_Buff, sizeof(FATS_Buff)); //FLASH formatting
	    	    			   if(retFLASH == FR_OK)
	    	    			   {
	    	                      printf("FLASH formatting success!\r\n");
	    	    			   }
	    	    				else
	    	    			   {
	    	    				  printf("FLASH formatting failure!\r\n");
	    	    			   }

	    	    		   }
	    	    		 }
	    	    		 else
	    	    		 {
	    	    			 Flash_mount_status = 1;
	    	    			 printf("File system mount success\r\n");
	    	    		 }
	     }

		 else if(cmd==3) //File creation and write
		 {
				  cmd = 0;

				  if(Flash_mount_status==0) printf("\r\nFLASH File system not mounted: %d\r\n",retFLASH);
				  else
				  {
						retFLASH = f_open( &file, filepath, FA_CREATE_ALWAYS | FA_WRITE );  //Open or create file
						if(retFLASH == FR_OK)
						{
							printf("\r\nFile open or creation successful\r\n");

							retFLASH = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten); //Write data

							if(retFLASH == FR_OK)
							{
								printf("\r\nFile write successful\r\n");

							}
							else
							{
								printf("\r\nFile write error: %d\r\n",retFLASH);
							}

							f_close(&file);   //Close file
						}
						else
						{
							printf("\r\nFile open or creation error %d\r\n",retFLASH);
						}
				   }

	    }

	    else if(cmd==4) //File read
	    {
				  cmd = 0;

				  if(Flash_mount_status==0) printf("\r\nFLASH File system not mounted: %d\r\n",retFLASH);
				  else
				  {
						retFLASH = f_open( &file, filepath, FA_OPEN_EXISTING | FA_READ); //Open file
						if(retFLASH == FR_OK)
						{
							printf("\r\nFile open successful\r\n");

							retFLASH = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread); //Read data

							if(retFLASH == FR_OK)
							{
								printf("\r\nFile read successful\r\n");
								PY_Delay_us_t(200000);

								FLASH_Read_Size = sizeof(rBuffer);
								for(uint16_t i = 0;i < FLASH_Read_Size;i++)
								{
									printf("%d ", rBuffer[i]);
								}
								printf("\r\n");

							}
							else
							{
								printf("\r\nFile read error: %d\r\n", retFLASH);
							}
							f_close(&file); //Close file
						}
						else
						{
							printf("\r\nFile open error: %d\r\n", retFLASH);
						}
				  }

		}

		else if(cmd==5) //File locating write
	    {
				  cmd = 0;

				  if(Flash_mount_status==0) printf("\r\nFLASH File system not mounted: %d\r\n",retFLASH);
				  else
				  {
						retFLASH = f_open( &file, filepath, FA_CREATE_ALWAYS | FA_WRITE);  //Open or create file
						if(retFLASH == FR_OK)
						{
							printf("\r\nFile open or creation successful\r\n");

							retFLASH=f_lseek( &file, f_tell(&file) + sizeof(WBuffer) ); //move file operation pointer, f_tell(&file) gets file head locating

							if(retFLASH == FR_OK)
							{

								retFLASH = f_write( &file, (const void *)WBuffer, sizeof(WBuffer), &byteswritten);
								if(retFLASH == FR_OK)
								{
									printf("\r\nFile locating write successful\r\n");
								}
								else
								{
									printf("\r\nFile locating write error: %d\r\n", retFLASH);
								}

							}
							else
							{
								printf("\r\nFile pointer error: %d\r\n",retFLASH);
							}

							f_close(&file);   //Close file
						}
						else
						{
							printf("\r\nFile open or creation error %d\r\n",retFLASH);
						}
				  }
		}

	    else if(cmd==6) //File locating read
		{
				  cmd = 0;

				  if(Flash_mount_status==0) printf("\r\nFLASH File system not mounted: %d\r\n",retFLASH);
				  else
				  {
						retFLASH = f_open(&file, filepath, FA_OPEN_EXISTING | FA_READ); //Open file
						if(retFLASH == FR_OK)
						{
							printf("\r\nFile open successful\r\n");

							retFLASH =  f_lseek(&file,f_tell(&file)+ sizeof(WBuffer)/2); //move file operation pointer, f_tell(&file) gets file head locating

							if(retFLASH == FR_OK)
							{
								retFLASH = f_read( &file, (void *)rBuffer, sizeof(rBuffer), &bytesread);
								if(retFLASH == FR_OK)
								{
									printf("\r\nFile locating read successful\r\n");
									PY_Delay_us_t(200000);

									FLASH_Read_Size = sizeof(rBuffer);
									for(uint16_t i = 0;i < FLASH_Read_Size;i++)
									{
										printf("%d ",rBuffer[i]);
									}
									printf("\r\n");
								}
								else
								{
									printf("\r\nFile locating read error: %d\r\n",retFLASH);
								}
							}
							else
							{
								printf("\r\nFile pointer error: %d\r\n",retFLASH);
							}
							f_close(&file);
						}
						else
						{
							printf("\r\nFile open error: %d\r\n",retFLASH);
						}
				  }
	     }

    /* 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};

  /** Supply configuration update enable
  */
  HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);

  /** Configure the main internal regulator output voltage
  */
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);

  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_DIV1;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 60;
  RCC_OscInitStruct.PLL.PLLP = 2;
  RCC_OscInitStruct.PLL.PLLQ = 2;
  RCC_OscInitStruct.PLL.PLLR = 2;
  RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_3;
  RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;
  RCC_OscInitStruct.PLL.PLLFRACN = 0;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2
                              |RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
  RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;

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

/**
  * @brief QUADSPI Initialization Function
  * @param None
  * @retval None
  */
static void MX_QUADSPI_Init(void)
{

  /* USER CODE BEGIN QUADSPI_Init 0 */

  /* USER CODE END QUADSPI_Init 0 */

  /* USER CODE BEGIN QUADSPI_Init 1 */

  /* USER CODE END QUADSPI_Init 1 */
  /* QUADSPI parameter configuration*/
  hqspi.Instance = QUADSPI;
  hqspi.Init.ClockPrescaler = 9;
  hqspi.Init.FifoThreshold = 4;
  hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  hqspi.Init.FlashSize = 25;
  hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_5_CYCLE;
  hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
  hqspi.Init.FlashID = QSPI_FLASH_ID_1;
  hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
  if (HAL_QSPI_Init(&hqspi) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN QUADSPI_Init 2 */

  /* USER CODE END QUADSPI_Init 2 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOE_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

}

/* USER CODE BEGIN 4 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart==&huart1)
	{
		cmd = uart1_rx[0];
		HAL_UART_Receive_IT(&huart1, uart1_rx, 1);
	}

}
/* USER CODE END 4 */

/**
  * @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 */
  __disable_irq();
  while (1)
  {
  }
  /* 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,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


STM32例程测试

串口指令0x01测试效果如下:
在这里插入图片描述

串口指令0x02测试效果如下:
在这里插入图片描述

串口指令0x03测试效果如下:
在这里插入图片描述

串口指令0x04测试效果如下:
在这里插入图片描述

串口指令0x05测试效果如下:
在这里插入图片描述

串口指令0x06测试效果如下:
在这里插入图片描述

通过串口对FLASH对操作后,可以将STM32 USB接口连接到PC, 从PC端可以看到U盘接入,打开后可以看到通过串口建立和操作的文件:
在这里插入图片描述
在这里插入图片描述
串口写入文件里的是1~20范围的16进制数据,这里显示为非常规字符,如果串口写入文件里的是英文字母对应的ASCII码16进制数据,文件里也就会显示为英文字母。
当通过U盘形式对文件进行操作后,同样也可以再通过串口对U盘形式操作过的文件访问操作。

STM32例程下载

STM32H750VBT6 模拟U盘桥接QSPI总线FATS读写FLASH W25QXX 例程下载

U盘模式升级方式

一种STM32升级方式为通过模拟U盘,从PC端将升级文件(.bin文件)存储进到外部FLASH里,STM32重新上电时识别标识从外部FLASH里将版本拷贝进到内部FLASH跳转空间,然后跳转执行。这是IAP升级的U盘接口常见方式,可以参考串口IAP升级过程,主要区别是版本数据获得方式不同。
一个不复杂的问题是为什么不从PC端直接写入.bin文件进到内部FLASH里,因为文件管理系统会占用额外的空间,也会导致文件放置首地址(跳转地址)易变,因此对MCU有限的内部FLASH空间而言不利资源使用。

–End–

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

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

相关文章

Pikachu靶场--SSRF

参考借鉴&#xff1a;pikachu靶场练习——SSRF详解_pikachu ssrf-CSDN博客 SSRF(curl) 先了解一下curl curl是一个非常实用的、用来与服务器之间传输数据的工具&#xff1b;支持的协议包括 (DICT, FILE, FTP, FTPS, GOPHER, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, POP3, PO…

C语言——链表专题

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 点击主页&#xff1a;optimistic_chen和专栏&#xff1a;c语言&#xff0c; 创作不易&#xff0c;大佬们点赞鼓…

【算法专题--链表】旋转链表 -- 高频面试题(图文详解,小白一看就懂!!)

目录 一、前言 二、题目描述 三、解题方法 ⭐解题思路---闭合为环 &#x1f34d; 案例图解 四、总结与提炼 五、共勉 一、前言 旋转链表 这道题&#xff0c;可以说是--链表专题--&#xff0c;最经典的一道题&#xff0c;也是在面试中频率最高的一道题目&#x…

Kotlin 中的内联函数

1 inline 内联函数&#xff1a;消除 Lambda 带来的运行时开销。 举例来说&#xff1a; fun main() {val num1 100val num2 80val result num1AndNum2(num1, num2) { n1, n2 ->n1 n2} }fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int …

APT 组织也在利用云存储进行攻击

研究人员发现&#xff0c;各类攻击者都在攻击行动中将恶意脚本、远控木马和诱饵文档等恶意文件上传到云服务器上&#xff0c;各种恶意文件组合起来完成恶意攻击。 某个攻击组织从发送钓鱼邮件到植入远控木马的过程如下所示&#xff1a; 攻击链 多个恶意文件串联起了整个攻击行…

跑路代码已上线,坐等删库中~

前言 或许大家会认为删库跑路都是运维或者DBA的事情&#xff0c;或许认为我没有线上数据库权限就不可能删库跑路。但是事实并非如此&#xff0c;建议大家仔细阅读此文章&#xff0c;赶紧排查下您的代码&#xff0c;很可能隐藏着这种删库程序。还是要呼吁大家&#xff0c;这个案…

三级医院智慧医院信息化规划方案(65页PPT)

方案介绍&#xff1a; 该方案通过信息化手段实现医院信息化全覆盖&#xff0c;优化诊疗流程、提高诊疗效率和准确性&#xff1b;同时实现医疗资源的合理配置和共享&#xff0c;提升医疗服务质量。通过优化患者就医流程、提供便捷的服务和宣传健康知识等方式提高患者满意度。通…

苏州大学气膜综合馆成为师生活动新中心—轻空间

苏州大学应用技术学院的气膜综合馆自建成以来&#xff0c;已成为校园内的热门活动场所。由轻空间&#xff08;江苏&#xff09;膜科技有限公司&#xff08;以下简称“轻空间”&#xff09;全力打造&#xff0c;这座现代化、环保的多功能运动场馆&#xff0c;不仅为师生提供了一…

代码随想录第35天|动态规划

理论基础 动态规划是由前一个状态推导出来的, 而贪心是局部直接选取最优 五部曲: 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 debug过程 : dp数组打印查看 509. 斐波那契数 参考 //动态规划的方法 …

Python基础教程——常用的36个经典案例!

Python 的简洁和强大使其成为许多开发者的首选语言。本文将介绍36个常用的Python经典代码案例。这些示例覆盖了基础语法、常见任务、以及一些高级功能。(文末附带精品学习资料) 1. 列表推导式 fizz_buzz_list [ "FizzBuzz" if i % 15 0 else "Fizz&qu…

陪玩系统源码,陪玩平台源码,陪玩app源码搭建

游戏陪玩app开发&#xff0c;软件搭建&#xff0c;程序制作、系统设计 目前&#xff0c;中国约有五到六亿游戏玩家&#xff0c;其中大约有两亿人选择付费游戏。这显示了绝大多数玩家都愿意为他们喜欢的游戏付费。随着游戏体验的不断改善&#xff0c;越来越多的玩家更倾向于找到…

基于Java的汽车租赁系统【附源码】

论文题目 设计&#xff08;论文&#xff09;综述&#xff08;1000字&#xff09; 当今社会&#xff0c;汽车租赁已成为一种受欢迎的出行方式。本文旨在探讨汽车租赁行业的发展趋势、市场规模及其对环境的影响。目前&#xff0c;汽车租赁行业正在经历着快速的发展。随着经济的发…

3D资产爆发,轻量化需求再度冲高,见证下一代3D崛起!

数字经济不断发展&#xff0c;3D资产和实体经济迎来深度融合的窗口期&#xff0c;3D资产应用外延催生大量新场景、新业态&#xff0c;一个3D资产构建的数字世界正出现在我们眼前。 数字经济不断发展&#xff0c;3D资产和实体经济迎来深度融合的窗口期&#xff0c;3D资产应用外…

【TB作品】MSP430,G2533单片机,红外发射,红外接收,红外通信,IR发射

文章目录 题目红外NEC协议介绍基本概述数据帧结构位表示数据传输示例重复码&#xff08;Repeat Code&#xff09;实现细节发送端接收端 典型应用结论 最终效果代码 题目 遥控器 硬件&#xff1a;msp430g2553、oled显示器、ds18b20温度传感器、红外发射器、按键 软件功能&#…

OpenAI用GPT-4o打造癌症筛查AI助手;手机就能检测中风,准确率达 82%!中国气象局发布AI气象大模型...

AI for Science 企业动态速览—— * 皇家墨尔本大学用 AI 检测患者中风&#xff0c;准确率达 82% * OpenAI 用 GPT-4o 模型打造癌症筛查 AI 助手 * 中国气象局发布 AI 气象大模型风清、风雷、风顺 * AI 药企英矽智能&#xff1a;小分子抑制剂已完成中国 IIa 期临床试验全部患者…

Socket——向FTP服务器发送消息并获得响应

1、简介 Socket&#xff08;套接字&#xff09;是网络编程中用于描述IP地址和端口的一个抽象概念&#xff0c;通过它可以实现不同主机间的通信。套接字可以分为几种不同的类型&#xff0c;每种类型对应不同的协议和传输模式。 1.1、基本概念 IP地址&#xff1a;用于标识网络…

厂区滴漏智能识别摄像机

当今&#xff0c;随着智能技术的迅猛发展&#xff0c;智能识别摄像机正逐步应用于各个行业&#xff0c;特别是在工业生产环境中&#xff0c;其作用愈发凸显。其中&#xff0c;厂区滴漏智能识别摄像机的应用成为了保障生产安全和环境保护的重要手段之一。厂区滴漏智能识别摄像机…

简述Java项目中VO,BO,PO,DO,DTO之类的文件概念、易混点

VO&#xff0c;BO&#xff0c;PO&#xff0c;DO&#xff0c;DTO 概念易混点一&#xff1a;VO和DTO- 让我们通过一个实例来阐释DTO和VO的概念及其应用差异&#xff1a;小结&#xff1a;VO专注于展示&#xff0c;而DTO则用于数据的传输和业务逻辑的处理。 二&#xff1a;BO和PO小…

记录 Bonobo Git 服务器 SMTP 设置

Bonobo 使用标准的 .NET SMTP 设置&#xff0c;可以在 web.config 中指定这些设置。 <system.net><mailSettings><smtp deliveryMethod"network" from"bonobobonoserver.your.domain"><network host"accessible.smtp.host"…

用一个暑假|用AlGC-stable diffusion 辅助服装设计及展示,让你在同龄人中脱颖而出!

大家好&#xff0c;我是设计师阿威 Stable Diffusion是一款开源AI绘画工具&#xff0c; 用户输入语言指令&#xff0c;即可自动生成各种风格的绘画图片 Stable Diffusion功能强大&#xff0c;生态完整、使用方便。支持大部分视觉模型上传&#xff0c;且可自己定制模型&#x…