STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX

STM32存储左右互搏 QSPI总线FATS文件读写FLASH W25QXX

FLASH是常用的一种非易失存储单元,W25QXX系列Flash有不同容量的型号,如W25Q64的容量为64Mbit,也就是8MByte。这里介绍STM32CUBEIDE开发平台HAL库Quad SPI总线实现FATS文件操作W25Q各型号FLASH的例程。非FATS直接Quad SPI操作W25QXX FLASH的方式见《STM32存储左右互搏 QSPI总线读写FLASH W25QXX》

W25QXX介绍

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

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

STM32工程配置

首先建立基本工程并设置时钟:
在这里插入图片描述

在这里插入图片描述
注意QSPI时钟在单独的时钟树支上:
在这里插入图片描述
QSPI接口可以配置为两个Bank(并行协同存储扩展),也可以配置为单个Bank,并且可以由4线(数据线)模式降级为单线/双线(数据线)模式。这里对于单个Flash,选择一个bank配置:
在这里插入图片描述
在这里插入图片描述
注意QSPI的片选信号是硬件自动控制,和SPI可以采用软件代码控制不同,所以不必单独指定一个GPIO作为片选:
在这里插入图片描述
不用DMA
在这里插入图片描述
配置FATS参数:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
配置串口UART1作为命令输入和打印输出接口:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
不用DMA:
在这里插入图片描述
保存并生成初始工程代码:
在这里插入图片描述

STM32工程代码

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

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

#ifndef INC_W25QXX_H_
#define INC_W25QXX_H_

#include "main.h"

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

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

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

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


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


#endif /* INC_W25QXX_H_ */

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

#include "W25QXX_QSPI.h"

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

#define Use_Quad_Line 1

uint16_t W25QXX_TYPE=W25Q64_ID;

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

	W25QXX_TYPE=W25QXX_ReadID();

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

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

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

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

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

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

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

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

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

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

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

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

		  return Temp;

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

}

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

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

	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

/* USER CODE END DECL */

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

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

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

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

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

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

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

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

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


main.c文件操作代码里包含几个QSPI操作函数定义如下:

#define page_byte_size 256
uint8_t sdbuffer[page_byte_size];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  while(uwTick!=firstms) ;

  while(uwTick!=secondms) counter++;

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

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

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

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

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

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


void PY_Delay_us(uint32_t Delay)
{
  __IO uint32_t delayReg;

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

  if(msNum>0) HAL_Delay(msNum);

  delayReg = 0;
  while(delayReg!=usNum) delayReg++;
}
/* USER CODE END PTD */

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

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

/* USER CODE END PM */

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

QSPI_HandleTypeDef hqspi;

UART_HandleTypeDef huart1;

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

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

FRESULT retFLASH;
FIL file;
FATFS *fs;

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

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

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

/* USER CODE END PFP */

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

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

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

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

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

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

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

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

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

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

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

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

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

	char * dpath = "0:"; //Disk Path
    for(uint8_t i=0; i<4; i++)
    {
	  USERPath[i] = *(dpath+i);
    }

	const TCHAR* filepath = "0:test.txt";
  /* USER CODE END 1 */

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

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

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

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

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  MX_QUADSPI_Init();
  MX_FATFS_Init();
  /* USER CODE BEGIN 2 */
  PY_usDelayTest();
  PY_usDelayOptimize();

  HAL_UART_Receive_IT(&huart1, uart1_rx, 1);

  W25QXX_Init();

  /* USER CODE END 2 */

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

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

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

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

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

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

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

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

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

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

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

	    }

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

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

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

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

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

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

		}

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

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

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

							if(retFLASH == FR_OK)
							{

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

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

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

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

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

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

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

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

    /* USER CODE END WHILE */

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

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

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

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

  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

  __HAL_RCC_SYSCFG_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);

  while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}

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

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

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

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

  /* USER CODE BEGIN QUADSPI_Init 0 */

  /* USER CODE END QUADSPI_Init 0 */

  /* USER CODE BEGIN QUADSPI_Init 1 */

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

  /* USER CODE END QUADSPI_Init 2 */

}

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

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

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

  /* USER CODE END USART1_Init 2 */

}

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

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

}

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

}
/* USER CODE END 4 */

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

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



STM32例程测试

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

STM32例程下载

STM32H750VBT6 QSPI总线FATS文件读写FLASH W25QXX例程下载

–End–

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

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

相关文章

Vue笔记(一)

常用指令 1.v-show与v-if底层原理的区别 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>创建一个V…

快速搭建宠物医院服务小程序的步骤,无需编程经验

如果你是一家宠物医院或者宠物服务机构&#xff0c;想要拥有一款方便用户预约、查询信息的小程序&#xff0c;那么乔拓云网提供的轻应用小程序是你的不二选择。下面将为你详细介绍如何轻松打造宠物医院服务小程序。 1. 进入乔拓云网后台&#xff0c;点击【轻应用小程序】中的【…

国产服务器操作系统

为何记录 最近的开发工作经常接触到国产服务器操作系统的业务&#xff0c;经常被x86、arm、龙芯、鲲鹏、欧拉...搞得一脸懵逼&#xff0c;遂记之&#xff01; 操作系统 这里按照应用场景分&#xff1a; 桌面操作系统&#xff1a;主要用于pc&#xff0c;如Windows、macOS、Li…

★【递归】【构造二叉树】Leetcode 106.从中序与后序遍历序列构造二叉树

★【递归】【构造二叉树】Leetcode 106.从中序与后序遍历序列构造二叉树 105. 从前序与中序遍历序列构造二叉树 106.从中序与后序遍历序列构造二叉树:star:思路分析递归解法 105. 从前序与中序遍历序列构造二叉树递归解法 ---------------&#x1f388;&#x1f388;题目链接&a…

大语言模型推理加速技术:计算加速篇

原文&#xff1a;大语言模型推理加速技术&#xff1a;计算加速篇 - 知乎 目录 简介 Transformer和Attention 瓶颈 优化目标 计算加速 计算侧优化 KVCache Kernel优化和算子融合 分布式推理 内存IO优化 Flash Attention Flash Decoding Continuous Batching Page…

嵌入式学习第二十一天!(线程)

线程&#xff1a; 1. 基本概念&#xff1a; 线程&#xff1a;线程是一个轻量级的进程&#xff0c;位于进程空间内部&#xff0c;一个进程中可以创建多个线程 2. 线程创建&#xff1a; 线程独占栈空间&#xff0c;文本段、数据段和堆区与进程共享 3. 线程调度&#xff1a; 与进程…

Tomcat 下部署若依单体应用可观测最佳实践

实现目标 采集指标信息采集链路信息采集日志信息采集 RUM 信息会话重放 即用户访问前端的一系列过程的会话录制信息&#xff0c;包括点击某个按钮、操作界面、停留时间等&#xff0c;有助于客户真是意图、操作复现 版本信息 Tomcat (9.0.81)Springboot(2.6.2)JDK (>8)DDT…

2024年软件测试路线,不同等级应具备的基本能力(总结)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、软件测试的正确…

大数据可视化的设计规范,全面剖析,很实用。

大数据可视化的设计规范需要考虑到数据量大、复杂度高、数据类型多样等特点。以下是一份常见的大数据可视化设计规范&#xff0c;供您参考&#xff1a; 设计原则 简单易用&#xff1a;保证用户操作简单、直观&#xff0c;降低用户认知负担。数据准确&#xff1a;保证数据准确…

数据可视化引领智慧工业新时代

在智慧工业的大潮中&#xff0c;数据可视化崭露头角&#xff0c;以其直观、清晰的方式赋能工业生产&#xff0c;为智慧工业的高效运转提供了强有力的支持。下面我就以可视化从业者的角度&#xff0c;简单聊聊这个话题。 数据可视化首先在智慧工业的生产监控中大显身手。通过将…

4-Bean的循环依赖

Bean的循环依赖 循环依赖指的是依赖闭环的问题 解决 首先我们来实例化A&#xff0c;实例化A时并没有处理依赖注入&#xff0c;因此会得到半成品A。有了半成品A&#xff0c;它会被封装成一个ObjectFactory&#xff0c;并且把它放入第三个缓存区singletonFactories中。 接下来要…

tmux的使用方法

1. tmux的定义 我&#xff1a;什么是tmux&#xff1f; GPT&#xff1a;tmux&#xff08;terminal multiplexer&#xff09;是一个强大的终端复用器&#xff0c;它允许用户在一个终端窗口中创建、访问和控制多个会话。使用tmux&#xff0c;你可以在一个窗口中打开多个终端会话&…

苍穹外卖 -- day11 - Apache ECharts- 营业额统计- 用户统计- 订单统计- 销量排名Top10

苍穹外卖-day11 课程内容 Apache ECharts 营业额统计 用户统计 订单统计 销量排名Top10 功能实现&#xff1a;数据统计 数据统计效果图&#xff1a; 1. Apache ECharts 1.1 介绍 Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观&#x…

docker 常用指令(启动,关闭,查看运行状态)

文章目录 docker 常用指令启动 docker关闭 docker查看 docker的运行状态 docker 常用指令 启动 docker systemctl start docker关闭 docker systemctl stop docker查看 docker的运行状态 systemctl status docker如下图所示&#xff1a; 表示docker正在运行中

【Vue】

什么是Vue Vue是一款用于构建用户界面的JavaScript框架&#xff0c;它基于标准HTML、CSS和JavaScript构建&#xff0c;并提供了一套声明式的、组件化的编程模型&#xff0c;帮助开发者高效地开发用户界面。Vue是一个JS库&#xff0c;无依赖其他JS库&#xff0c;直接引入一个JS文…

数据可视化--了解数据可视化和Excel数据可视化

目录 1.1科学可视化&#xff1a; 可视化是模式、关系、异常 1.2三基色原理&#xff1a; 三基色:红色、绿色和蓝色 1.3Excel数据可视化 1.3.1 excel数据分析-13个图表可视化技巧 1.3.2 excel数据分析-28个常用可视化图表&#xff08;video&#xff09; 1.3.3Excel可视化…

适配器模式(Adapter Pattern) C++

上一节&#xff1a;原型模式&#xff08;Prototype Pattern&#xff09; C 文章目录 0.理论1.组件2.类型3.什么时候使用 1.实践1.基础接口和类2.类适配器实现3.对象适配器实现 0.理论 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允…

基于JAVA的就医保险管理系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 科室档案模块2.2 医生档案模块2.3 预约挂号模块2.4 我的挂号模块 三、系统展示四、核心代码4.1 用户查询全部医生4.2 新增医生4.3 查询科室4.4 新增号源4.5 预约号源 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVue…

B站项目-基于Pytorch的ResNet垃圾图片分类

基于Pytorch的ResNet垃圾图片分类 数据集预处理 画图片的宽高分布散点图 import osimport matplotlib.pyplot as plt import PIL.Image as Imagedef plot_resolution(dataset_root_path):image_size_list []#存放图片尺寸for root, dirs, files in os.walk(dataset_root_pa…

提升Vue3应用效率的秘诀:深入比较ref与reactive!

ref 和 reactive 是 Vue3 中实现响应式数据的核心 API。ref 用于包装基本数据类型&#xff0c;而 reactive 用于处理对象和数组。尽管 reactive 似乎更适合处理对象&#xff0c;但 Vue3 官方文档更推荐使用 ref。 我的想法&#xff0c;ref就是比reactive好用&#xff0c;官方也…