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:

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

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

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.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.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: 打开文件并从特定位置读入数据

完整的代码实现如下:

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

相关推荐
Net_Walke3 小时前
【STM32】CLion STM32开发环境搭建
stm32·单片机
laocooon5238578866 小时前
运行当前位置,显示文件全名,检查是否扩展名多次重叠
stm32·单片机·嵌入式硬件
沉醉不知归路16 小时前
cursor导入keil工程详细步骤
stm32
D.....l7 小时前
STM32学习(MCU控制)(I2C 模拟)
stm32·单片机·学习
A9better8 小时前
嵌入式开发学习日志42——stm32之SPI工作方式
stm32·单片机·嵌入式硬件·学习
D.....l9 小时前
STM32学习(MCU控制)(SysTick and TIM)
stm32·单片机·学习
python百炼成钢13 小时前
10.串口
linux·stm32·单片机·嵌入式硬件
充哥单片机设计21 小时前
【STM32项目开源】基于STM32的智能水质检测系统
stm32·单片机·嵌入式硬件
wuk99821 小时前
基于STM32平台的ADS1292心电采集驱动程序
stm32·单片机·嵌入式硬件
gihigo19981 天前
基于STM32F4系列MCU和CS5530 24位SDADC的称重传感器系统实现
stm32·单片机·嵌入式硬件