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

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

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

W25QXX介绍

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

例程采用STM32H750VBT6芯片, FLASH可以选择为8/16/32/64/128/256/512/1024 Mbit的W25Q型号。例程实现可以通过U盘形式和串口控制双方式对FLASH内的文件进行操作,实现可交换操作的特性。

STM32工程配置

首先建立基本工程并设置时钟:
在这里插入图片描述
对于STM32H7,有专用的内部48MHz时钟用于USB接口, 其它应用采用内部高速时钟接口即可:
在这里插入图片描述
在这里插入图片描述
设置UART1作为通讯串口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
FLASH连接到了SPI2接口,对SPI2进行配置:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
不用中断和DMA模式,SPI2的片选才用软件代码控制,配置一个GPIO作为片选输出:
在这里插入图片描述

配置USB接口
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
将USB接口例化为U盘模式:

在这里插入图片描述

在这里插入图片描述
对FATS文件系统进行配置
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
STM32H7资源多,可以将堆栈开大些:
在这里插入图片描述

保存并生成初始工程代码:
在这里插入图片描述

STM32工程代码

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

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

#ifndef INC_W25QXX_H_
#define INC_W25QXX_H_

#include "main.h"

uint8_t SPI2_ReadWriteByte(uint8_t TxData);

//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

//W25QXX chip select control function
#define W25QXX_CS(n)  ( n ? HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_RESET) )

//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

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.c:

#include "W25QXX.h"

extern SPI_HandleTypeDef hspi2;
extern void PY_Delay_us_t(uint32_t Delay);
//Write and read one byte in SPI2
uint8_t SPI2_ReadWriteByte(uint8_t TxData)
{
    uint8_t Rxdata;
    HAL_SPI_TransmitReceive(&hspi2,&TxData,&Rxdata,1, 1000);
 	return Rxdata;
}

uint16_t W25QXX_TYPE=W25Q64_ID;

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

	W25QXX_CS(1);

	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
		{
			W25QXX_CS(0);
			SPI2_ReadWriteByte(W25X_Enable4ByteAddr);
			W25QXX_CS(1);
		}
    }

    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;
    }
	W25QXX_CS(0);
	SPI2_ReadWriteByte(command);    //send command
	byte=SPI2_ReadWriteByte(0Xff);  //read data
	W25QXX_CS(1);
	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;
    }
	W25QXX_CS(0);
	SPI2_ReadWriteByte(command);            //send command
	SPI2_ReadWriteByte(d);                  //write data
	W25QXX_CS(1);
}
//W25QXX write enable
void W25QXX_Write_Enable(void)
{
	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_WriteEnable);
	W25QXX_CS(1);
}
//W25QXX write disable
void W25QXX_Write_Disable(void)
{
	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_WriteDisable);
	W25QXX_CS(1);
}

//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;
	W25QXX_CS(0);
	SPI2_ReadWriteByte(0x90);          //send command
	SPI2_ReadWriteByte(0x00);
	SPI2_ReadWriteByte(0x00);
	SPI2_ReadWriteByte(0x00);
	Temp|=SPI2_ReadWriteByte(0xFF)<<8; //read high byte data
	Temp|=SPI2_ReadWriteByte(0xFF);    //read low byte data
	W25QXX_CS(1);
	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)
{
 	uint16_t i;
	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_ReadData);                   //send read command
    if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID))  //send highest 8-bit address
    {
        SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>24));
    }
    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>16));       //send 24-bit address
    SPI2_ReadWriteByte((uint8_t)((ReadAddr)>>8));
    SPI2_ReadWriteByte((uint8_t)ReadAddr);
    for(i=0;i<NumByteToRead;i++)
	{
        pBuffer[i]=SPI2_ReadWriteByte(0XFF);             //read data
    }
	W25QXX_CS(1);
}

//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)
{
 	uint16_t i;
    W25QXX_Write_Enable();                                       //write enable
	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_PageProgram);                        //send write command
    if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID)) //send highest 8-bit address
    {
        SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>24));
    }
    SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>16));               //send 24-bit address
    SPI2_ReadWriteByte((uint8_t)((WriteAddr)>>8));
    SPI2_ReadWriteByte((uint8_t)WriteAddr);
    for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);  //write data
	W25QXX_CS(1);
	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();
  	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_ChipErase);     //send erase command
	W25QXX_CS(1);
	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();
  	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_SectorErase);                      //send erase command
    if((W25QXX_TYPE==W25Q256_ID)||(W25QXX_TYPE==W25Q512_ID)||(W25QXX_TYPE==W25Q1024_ID)) //send highest 8-bit address
    {
        SPI2_ReadWriteByte((uint8_t)((Sector_Num)>>24));
    }
    SPI2_ReadWriteByte((uint8_t)((Sector_Num)>>16));           //send 24-bit address
    SPI2_ReadWriteByte((uint8_t)((Sector_Num)>>8));
    SPI2_ReadWriteByte((uint8_t)Sector_Num);
	W25QXX_CS(1);
    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)
{
  	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_PowerDown);      //send power-down command
	W25QXX_CS(1);
	PY_Delay_us_t(tDP_us);                   //tDP
}
//Wake-up
#define tRES1_us 3
void W25QXX_WAKEUP(void)
{
  	W25QXX_CS(0);
    SPI2_ReadWriteByte(W25X_ReleasePowerDown);//send release power-down command
	W25QXX_CS(1);
	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.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.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     //STORAGE_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 */

/**
  * @}
  */

/**
  * @}
  */


(番外)由上面的设置可以看出设计扩容盘的方式,如把8M的盘扩容成64M,则进行两步操作即可:

  1. 将STORAGE_BLK_NBR放大8倍从而盘容量识别扩大为64M
  2. 在读写函数里,对操作地址进行处理,即把操作地址对8M空间取余,这样刚过8M尾部空间的操作就会跳到头部空间进行,当然如果是写操作就会产生覆盖
    扩容盘是一种循环访问非常规方式,一般在行车记录仪等监控领域出现较多,用户想存储更长时间的数据,但实际上要查数据时都是近期数据,扩容盘能够保持对近期数据的记录,因此不良商家卖出扩容盘非法牟利。扩容盘的缺点是实际容量小于标称容量,要查远期数据是被覆盖丢失的,另一个缺点是如果一个文件正好存储时跨过了实际容量尾部,那么这个文件对PC端时损坏文件不能读取,如果正好这个近期文件是重要取证文件,则是不良事件。

然后在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
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "fatfs.h"
#include "usb_device.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
#include "W25QXX.h"
#include <string.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 ---------------------------------------------------------*/

SPI_HandleTypeDef hspi2;

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];

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

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

/* USER CODE END PFP */

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

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	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();

/* Configure the peripherals common clocks */
  PeriphCommonClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_SPI2_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==1) //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 Peripherals Common Clock Configuration
  * @retval None
  */
void PeriphCommonClock_Config(void)
{
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

  /** Initializes the peripherals clock
  */
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_CKPER;
  PeriphClkInitStruct.CkperClockSelection = RCC_CLKPSOURCE_HSI;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief SPI2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_SPI2_Init(void)
{

  /* USER CODE BEGIN SPI2_Init 0 */

  /* USER CODE END SPI2_Init 0 */

  /* USER CODE BEGIN SPI2_Init 1 */

  /* USER CODE END SPI2_Init 1 */
  /* SPI2 parameter configuration*/
  hspi2.Instance = SPI2;
  hspi2.Init.Mode = SPI_MODE_MASTER;
  hspi2.Init.Direction = SPI_DIRECTION_2LINES;
  hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
  hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
  hspi2.Init.NSS = SPI_NSS_SOFT;
  hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
  hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
  hspi2.Init.TIMode = SPI_TIMODE_DISABLE;
  hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
  hspi2.Init.CRCPolynomial = 0x0;
  hspi2.Init.NSSPMode = SPI_NSS_PULSE_ENABLE;
  hspi2.Init.NSSPolarity = SPI_NSS_POLARITY_LOW;
  hspi2.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA;
  hspi2.Init.TxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  hspi2.Init.RxCRCInitializationPattern = SPI_CRC_INITIALIZATION_ALL_ZERO_PATTERN;
  hspi2.Init.MasterSSIdleness = SPI_MASTER_SS_IDLENESS_00CYCLE;
  hspi2.Init.MasterInterDataIdleness = SPI_MASTER_INTERDATA_IDLENESS_00CYCLE;
  hspi2.Init.MasterReceiverAutoSusp = SPI_MASTER_RX_AUTOSUSP_DISABLE;
  hspi2.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_DISABLE;
  hspi2.Init.IOSwap = SPI_IO_SWAP_DISABLE;
  if (HAL_SPI_Init(&hspi2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI2_Init 2 */

  /* USER CODE END SPI2_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_InitTypeDef GPIO_InitStruct = {0};

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(GPIOB, GPIO_PIN_12, GPIO_PIN_SET);

  /*Configure GPIO pin : PB12 */
  GPIO_InitStruct.Pin = GPIO_PIN_12;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

}

/* 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盘桥接SPI总线FATS读写FLASH W25QXX例程下载

U盘模式升级方式

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

–End–

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

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

相关文章

【八股系列】react里组件通信有几种方式,分别怎样进行通信?

文章目录 1. props传递&#xff08;父向子通信&#xff09;:2. 回调函数作为props&#xff08;子向父通信&#xff09;:3. Context API&#xff1a;4. Redux或MobX等状态管理库&#xff1a;4.1 Redux使用示例 5. refs&#xff1a; 1. props传递&#xff08;父向子通信&#xff…

netty内存马探究

0x01 前言 基于netty动态创建pipeline的特性&#xff0c;其内存马的构造思路与tomcat有一定的区别&#xff0c;目前网上有关netty内存马的文章都围绕CVE-2022-22947和XXL-JOB两种场景展开&#xff0c;并未对其做更为详细的分析。本文就以上述两种场景为始&#xff0c;尝试从源码…

打造智能饲料工厂:全自动生产线设备

全自动饲料生产线设备实现了从原料投放到成品包装的全程自动化操作。通过传感器和控制系统&#xff0c;设备能够精确控制原料的配比和混合均匀度&#xff0c;确保饲料的质量稳定可靠。同时&#xff0c;自动化生产线还具备智能调节功能&#xff0c;能够根据生产需求自动调节生产…

四季变换,制氮机使用注意事项

随着四季的轮回变换&#xff0c;大自然展现着不同的风貌。对于制氮机而言&#xff0c;季节的变换同样会带来不同的使用挑战和注意事项。本文将为您揭示四季变换对制氮机使用的影响&#xff0c;帮助您更好地掌握制氮机的季节使用须知。 春季 温湿度变化&#xff1a;春季温湿度逐…

Shell脚本从入门到实战

一、概述 shell 是一个命令行解释器&#xff0c;它接受应用程序、用户命令&#xff0c;然后调用操作系统内核。 shell 还是一个功能强大编程语言&#xff0c;易调试&#xff0c;易编写&#xff0c;灵活性强。 二、mac 怎么重启docker 1.如何重启 Docker on Mac 在 macOS 上…

Flutter笔记:关于WebView插件的用法(上)

Flutter笔记 关于WebView插件的用法&#xff08;上&#xff09; - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:htt…

SpringMVC:拦截器(Interceptor)

1. 简介 拦截器&#xff08;Interceptor&#xff09;类似于过滤器&#xff08;Filter&#xff09; Spring MVC的拦截器作用是在请求到达控制器之前或之后进行拦截&#xff0c;可以对请求和响应进行一些特定的处理。拦截器可以用于很多场景下&#xff1a; 1. 登录验证&#xf…

Qt Creator 输入中文

安装fcitx-libs-qt或fcitx-libs-qt5 查看系统自带libfcitxplatforminputcontextplugin.so位置 dpkg -L fcitx-frontend-qt5 | grep .so文件位于 /usr/lib/x86_64-linux-gnu/qt5/plugins/platforminputcontexts/libfcitxplatforminputcontextplugin.so将上述文件拷贝至Qt安装…

燕之屋被授予“国家食品企业质量安全检测技术示范中心(无锡)分中心”牌匾

5月27日&#xff0c;江苏教育界与产业界对话对接系列活动——新型食品与大健康产业协同创新发展大会在江南大学召开。会上&#xff0c;燕之屋荣获“国家食品企业质量安全检测技术示范中心&#xff08;无锡&#xff09;分中心”授牌&#xff0c;燕之屋副总经理、研发技术总监范群…

Linux内网数据代理与数据映射实验

背景介绍 有两台主机&#xff0c;其中一台可以联网&#xff0c;而另一台只能与联网主机&#xff0c;尝试通过配置&#xff0c;使该主机也能正常上网&#xff0c;这常应用于企业内部不联网的服务器&#xff0c;需要安全可靠或临时上网的场景&#xff0c;借助另一台可以上网的内…

Docker:认识Docker Bridge网络

文章目录 为什么需要网络管理Docker网络架构简介CNMLibNetwork驱动Docker网络管理命令网络命令基本操作网络详解docker Bridge网络 容器之间的网络通信DNS解析 为什么需要网络管理 容器的网络默认会与宿主机器以及其他的容器相互隔离&#xff0c;但是还需要考虑到下面的这些问…

微软将 AI 工具和软件的研发工作外包给 OpenAI

微软将 AI 工具和软件的研发工作外包给 OpenAI 科技媒体 CNBC 报道称&#xff0c;微软正计划将其人工智能&#xff08;AI&#xff09;工具和软件的开发工作外包给OpenAI&#xff0c;这对竞争对手谷歌来说无疑是一个利好消息。 Okta首席执行官Todd McKinnon表示&#xff0c;微…

GPT-4o多模态大模型的架构设计

GPT-4o&#xff1a;大模型风向&#xff0c;OpenAI大更新 OpenAI震撼发布两大更新&#xff01;桌面版APP与全新UI的ChatGPT上线&#xff0c;简化用户操作&#xff0c;体验更自然。同时&#xff0c;全能模型GPT-4o惊艳亮相&#xff0c;跨模态即时响应&#xff0c;性能卓越且性价比…

vue防止多次点击

1.新建js 在util文件夹下新建一个preventReClick.js文件&#xff0c;名字可自定义&#xff0c;文件夹也可以根据你自己的目录来 2.js文件中通过自定义指令的形式封装一个名为preventReClick的指令 import Vue from vue // 通过自定义指令的形式封装一个名为preventReClick的…

动手学深度学习31 深度学习硬件 CPU和GPU

动手学深度学习31 深度学习硬件 CPU和GPU CPU和GPU主频 QA PPT&#xff1a; https://courses.d2l.ai/zh-v2/assets/pdfs/part-2_1.pdf 视频&#xff1a; https://www.bilibili.com/video/BV1TU4y1j7Wd/?p2&spm_id_frompageDriver&vd_sourceeb04c9a33e87ceba9c9a2e5f09…

..\USER\stm32f10x.h(298): error: #67: expected a “}“

原keil4的示例工程在用keil5打开之后出现报错&#xff1a; ..\USER\stm32f10x.h(298): error: #67: expected a "}" 在去掉手动添加的一个宏定义STM32F10X_HD后即可正常编译&#xff0c;因为KEIL5已经自动添加了

免费生成短链接,常用短网址生成站点推荐!

什么是短链接 "短链接"俗称"短网址"&#xff0c;通俗的讲就是将长的URL网址通过程序计算等方式&#xff0c;转换为简短的网址字符串&#xff0c;更便于使用者在第三方平台引用网址&#xff0c;节省字符数空间。 短链接最大的特点就是短&#xff0c;今天给…

大模型应用产品指北

一、试用类型 计算机视觉Computer Vision&#xff1a;Text-to-image(文生图) 二、国内产品 Aliyun通义千问 【体验】 可以替代Stable Diffusion、Midjourney;支持风格、滤镜; [传送门&#xff1a;通义万相] 【体验】Transformer架构&#xff1b;中英双语&#xff1b;开源可以…

WWDC24 快速回顾

今天凌晨&#xff0c;苹果公司在2024年全球开发者大会&#xff08;WWDC24&#xff09;上&#xff0c;发布了一系列的系统更新。然而&#xff0c;对于期待苹果带来突破性创新的消费者来说&#xff0c;今年的大会似乎并没有达到预期的震撼效果。 下面让我看看&#xff0c;苹果都带…

【小白专用 已验证24.6.12】MySQL连接使用-创建数据库和创建数据表

【小白专用 已验证24.6.12】Mysql 8.0的安装配置教程&#xff08;详细&#xff09;_mysql安装教程8.2-CSDN博客 1.在Windows开始搜索输入Mysql,并选择第一个打开。 2.输入安装时的密码&#xff0c;再回车&#xff0c;就连接上 MySQL 了 1. MySQLSQL语句通用语法 (1)SQL语句可以…