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:

bash 复制代码
#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:

bash 复制代码
#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添加包含信息:

bash 复制代码
#include "main.h"
#include "stm32h7xx_hal.h"

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

bash 复制代码
/* 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盘容量的函数

然后再配置读操作和写操作函数

完整的代码如下:

bash 复制代码
/* 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: 打开文件并从特定位置读入数据

完整的代码实现如下:

bash 复制代码
/* 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--

相关推荐
典则1 小时前
STM32FreeRtos入门(四)——任务状态和调度
stm32·单片机·嵌入式硬件
充哥单片机设计1 小时前
【STM32项目开源】基于STM32的智能天然气火灾监控
stm32·单片机·嵌入式硬件
充哥单片机设计1 小时前
【STM32项目开源】基于STM32的智能仓库火灾检测系统
stm32·单片机·嵌入式硬件
就叫飞六吧3 小时前
普中stm32大Dap烧录流程
stm32
A9better4 小时前
嵌入式开发学习日志38——stm32之看门狗
stm32·嵌入式硬件·学习
小莞尔4 小时前
【51单片机】【protues仿真】基于51单片机智能路灯控制系统
c语言·stm32·单片机·嵌入式硬件·51单片机
辰哥单片机设计14 小时前
TT直流减速电机(STM32)
stm32
A9better14 小时前
嵌入式开发学习日志36——stm32之USART串口通信前述
stm32·单片机·嵌入式硬件·学习
qq_4017004115 小时前
STM32低功耗Tickless模式
stm32·单片机
研猛男16 小时前
0、FreeRTOS编码和命名规则
笔记·stm32·freertos