当前位置: 首页 > news >正文

STM32H723Zx OCTO-SPI(OSPI) 读写 W25Q64

STM32H723系列OCTO SPI和 STM32H743系列的QUAD SPI接口有区别,驱动不通用。STM32H723的OSPI为升级版的QSPI,功能更强大,相对743的QSPI驱动程序有区别,但思路大同小异。网络上关于STM32H723的OSPI例程资料相对较少,开发参考资料不多,本文分享STM32H723的OSPI双线(DUAL)和四线(QUAD)读写W25Q系列 NOR Flash芯片的例程。

最近参考了某板的例程,调试通了STM32H723系列通过OSPI接口读写W25Q64 NOR FLASH,(比W25Q128容量小的都可以,W25Q256及以上的要自己适配),实测双线模式和四线模式读写速度几乎没区别,约13~15MB/s,双线模式(DUAL)还能省两个IO用作其他用途。

W25Q64与STM32H7接线部分原理图如下,如使用的PIN不一样,自己配置CUBEMX和修改程序适配。PB13_QSPI_IO2和PD13_QSPI_IO3在双线模式下未使用,可断开R33 R34电阻,PB13和PD13用作其他用途。W25Qxx芯片型号全称以Q结尾的,PIN3--W写保护引脚是无效的,只能作为IO2使用,PIN7--HOLD同理只能作为IO3,考虑到对非Q结尾型号的兼容性,最好给这两个脚上拉到3.3V。

要注意W25Qxx的CLK时钟不能高于133MHz,推荐不高于100MHz,要根据自己的时钟树计算分频系数,计算公式为F_OSPICLK = F_SCLK/(ClockPrescaler+1),F_SCLK为时钟源频率,ClockPrescaler为分频系数。比如本例用HCLK3,275MHz,作3分频,分频系数为2,OSPI时钟频率为91.67MHz左右。

hospi1.Init.ClockPrescaler 			  = 2;

双线和四线模式的两个例程是同一个"ospi_w25q64.c"和"ospi_w25q64.h"文件,通过头文件宏定义

#define  OSPI_MODE        QUAD

配置所需的模式。

话不多说,放代码:

ospi_w25q64.h

/********************************************************************************* @file    ospi_w25q64.h* @author  * @version V1.0.0* @date    Augest-6-2025* @brief   W25Q64 驱动.******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef	_OSPI_W25Q64_H
#define _OSPI_W25Q64_H/* Includes ------------------------------------------------------------------*/
#include "main.h"/* Exported types ------------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
#define  CUBEMX_INIT    1       //使用cubeMX生成的QCTOSPI初始化函数
#define  DBG_PRT        0       //是否开启调试打印
#define  TEST_EN        1       //是否开启测试模块#if DBG_PRT == 1#include "usart.h"
#endif//OCTO SPI接口模式
#define  SINGLE            0
#define  DUAL              1
#define  QUAD              2
#define  OSPI_MODE        QUAD/* Exported macro ------------------------------------------------------------*/
#define OSPI_W25Qxx_OK           	    	 0		// W25Qxx通信正常
#define W25Qxx_ERROR_INIT         		  -1		// 初始化错误
#define W25Qxx_ERROR_WriteEnable        -2		// 写使能错误
#define W25Qxx_ERROR_AUTOPOLLING        -3		// 轮询等待错误,无响应
#define W25Qxx_ERROR_Erase         		  -4		// 擦除错误
#define W25Qxx_ERROR_TRANSMIT         	-5		// 传输错误
#define W25Qxx_ERROR_MemoryMapped		    -6    // 内存映射模式错误#define W25Qxx_CMD_EnableReset  	  	0x66		// 使能复位
#define W25Qxx_CMD_ResetDevice      	0x99		// 复位器件
#define W25Qxx_CMD_JedecID 		    	  0x9F		// JEDEC ID  
#define W25Qxx_CMD_WriteEnable		    0X06		// 写使能#define W25Qxx_CMD_SectorErase 		    0x20		// 扇区擦除,4K字节, 参考擦除时间 45ms
#define W25Qxx_CMD_BlockErase_32K 	  0x52		// 块擦除,  32K字节,参考擦除时间 120ms
#define W25Qxx_CMD_BlockErase_64K 	  0xD8		// 块擦除,  64K字节,参考擦除时间 150ms
#define W25Qxx_CMD_ChipErase 			    0xC7		// 整片擦除,参考擦除时间 20S//dual 双线dualspi使用
#define W25Qxx_CMD_PageProgram       	0x02  	// 1-1-1模式下(1线指令1线地址1线数据),页编程指令
#define W25Qxx_CMD_FastReadDual_IO    0xBB  	// 1-2-2模式下(1线指令2线地址2线数据),快速读取指令//quad 四线quadspi使用
#define W25Qxx_CMD_QuadInputPageProgram  	0x32  	// 1-1-4模式下(1线指令1线地址4线数据),页编程指令,参考写入时间 0.4ms 
#define W25Qxx_CMD_FastReadQuad_IO       	0xEB  	// 1-4-4模式下(1线指令4线地址4线数据),快速读取指令#define W25Qxx_CMD_ReadStatus_REG1			0X05			// 读状态寄存器1
#define W25Qxx_Status_REG1_BUSY  			  0x01			// 读状态寄存器1的第0位(只读),Busy标志位,当正在擦除/写入数据/写命令时会被置1
#define W25Qxx_Status_REG1_WEL  				0x02			// 读状态寄存器1的第1位(只读),WEL写使能标志位,该标志位为1时,代表可以进行写操作#define W25Qxx_PageSize       				  256			    // 页大小,256字节
#define W25Qxx_FlashSize       				  0x800000		// W25Q64大小,8M字节
#define W25Qxx_FLASH_ID           			0Xef4017    // W25Q64 JEDEC ID
#define W25Qxx_ChipErase_TIMEOUT_MAX		100000U		  // 超时等待时间,W25Q64整片擦除所需最大时间是100S
#define W25Qxx_Mem_Addr							    0x90000000 	// 内存映射模式的地址/* Exported variable ---------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/
int8_t OSPI_W25Qxx_Init(void);         // W25Qxx初始化
uint32_t OSPI_W25Qxx_ReadID(void);     // 读取器件IDint8_t OSPI_W25Qxx_MemoryMappedMode(void);	// 将OSPI设置为内存映射模式int8_t OSPI_W25Qxx_SectorErase(uint32_t SectorAddress);        // 扇区擦除,4K字节, 参考擦除时间 45ms
int8_t OSPI_W25Qxx_BlockErase_32K (uint32_t SectorAddress);    // 块擦除,  32K字节,参考擦除时间 120ms
int8_t OSPI_W25Qxx_BlockErase_64K (uint32_t SectorAddress);    // 块擦除,  64K字节,参考擦除时间 150ms,实际建议使用64K擦除,擦除的时间最快
int8_t OSPI_W25Qxx_ChipErase (void);                           // 整片擦除,参考擦除时间 20Sint8_t OSPI_W25Qxx_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);    // 按页写入,最大256字节
int8_t OSPI_W25Qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t Size);            // 写入数据,最大不能超过flash芯片的大小
int8_t OSPI_W25Qxx_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);     // 读取数据,最大不能超过flash芯片的大小#if  TEST_EN  ==  1int8_t OSPI_W25Qxx_Test(void);
#endif#endif /* _OSPI_W25Q64_H *//********************************************* *****END OF FILE****/

ospi_w25q64.c

/********************************************************************************* @file    ospi_w25q64.c* @author  * @version V1.0.0* @date    Augest-6-2025* @brief   W25Q64 驱动.******************************************************************************
>>>>> 重要说明:**	1.W25QXX的擦除时间是限定的!!! 手册给出的典型参考值为: 4K-45ms, 32K-120ms ,64K-150ms,整片擦除20S**	2.W25QXX的写入时间是限定的!!! 手册给出的典型参考值为: 256字节-0.4ms,也就是 1M字节/s (实测大概在600K字节/s左右)**	3.如果使用库函数直接读取,那么是否使用DMA、是否开启Cache、编译器的优化等级以及数据存储区的位置(内部 TCM SRAM 或者 AXI SRAM)都会影响读取的速度**	4.如果使用内存映射模式,则读取性能只与OSPI的驱动时钟以及是否开启Cache有关**	5.实际使用中,当数据比较大时,建议使用64K或者32K擦除,擦除时间比4K擦除块	**	6.OSPI模块接系统时钟HCLK3,时钟频率275M,	hospi1.Init.ClockPrescaler  = 2; 做3分频处理,约91.67M*******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "ospi_w25q64.h"#if	CUBEMX_INIT == 1#include "octospi.h"
#endif/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
#if	CUBEMX_INIT == 0OSPI_HandleTypeDef hospi1;  //使用cubeMX生成的hospi1
#endif/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
#if	CUBEMX_INIT == 0
/*** @brief  OSPI引脚初始化函数 (QUAD SPI, 已有系统初始化可不调用)* @param  hospi -  OSPI_HandleTypeDef定义的变量,即表示定义的OSPI句柄* @retval None
*/
void W25Q_HAL_OSPI_MspInit(OSPI_HandleTypeDef* hospi)
{GPIO_InitTypeDef GPIO_InitStruct = {0};if(hospi->Instance==OCTOSPI1){	__HAL_RCC_OCTOSPIM_CLK_ENABLE();	// 使能OSPI时钟__HAL_RCC_OSPI1_CLK_ENABLE();__HAL_RCC_OSPI1_FORCE_RESET();		// 复位QSPI__HAL_RCC_OSPI1_RELEASE_RESET();__HAL_RCC_GPIOB_CLK_ENABLE();		// 使能 对应的 IO口时钟__HAL_RCC_GPIOD_CLK_ENABLE();__HAL_RCC_GPIOG_CLK_ENABLE();		/****引脚定义********************************************PD13    ------> OCTOSPIM_P1_IO3PB13    ------> OCTOSPIM_P1_IO2PD11    ------> OCTOSPIM_P1_IO0PD12    ------> OCTOSPIM_P1_IO1PB2     ------> OCTOSPIM_P1_CLKPG6     ------> OCTOSPIM_P1_NCS		*******************************************************/GPIO_InitStruct.Pin = GPIO_PIN_2;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPIM_P1;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);#if  OSPI_MODE  ==  QUADGPIO_InitStruct.Pin = GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF4_OCTOSPIM_P1;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
#endif#if  OSPI_MODE  ==  QUADGPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13;
#endif#if  OSPI_MODE  ==  DUALGPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
#endifGPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF9_OCTOSPIM_P1;HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);GPIO_InitStruct.Pin = GPIO_PIN_6;GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;GPIO_InitStruct.Pull = GPIO_NOPULL;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;GPIO_InitStruct.Alternate = GPIO_AF10_OCTOSPIM_P1;HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);#if  OSPI_MODE  ==  DUAL//如果使用OSPI 2线模式,如果IO2 IO3没有独立上拉电阻,并且和4线一样用stm32 IO控制,则初始化下列引脚//PD12-IO2,PD13-IO3高电平//否则,不用初始化,注释即可//注意:W25Qxx芯片,型号全称以“Q”结尾的,/WP和/HOLD固定作为IO2/IO3且不可更改,可不用控制IO电平GPIO_InitTypeDef GPIO_InitStruct = {0};/* GPIO Ports Clock Enable */__HAL_RCC_GPIOB_CLK_ENABLE();__HAL_RCC_GPIOD_CLK_ENABLE();/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOB, GPIO_PIN_13, GPIO_PIN_SET);/*Configure GPIO pin Output Level */HAL_GPIO_WritePin(GPIOD, GPIO_PIN_13, GPIO_PIN_SET);/*Configure GPIO pin : DUAL_IO2_Pin */GPIO_InitStruct.Pin = GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/*Configure GPIO pin : DUAL_IO3_Pin */GPIO_InitStruct.Pin = GPIO_PIN_13;GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;GPIO_InitStruct.Pull = GPIO_PULLUP;GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
#endif}
}/*** @brief  初始化 OSPI 配置 (QUAD SPI, 已有系统初始化可不调用)* @param  None* @retval None
*/
void W25Q_MX_OCTOSPI1_Init(void)
{OSPIM_CfgTypeDef sOspiManagerCfg = {0};/*在某些场合,例如用作下载算法时,需要手动清零句柄等参数,否则会工作不正常*/	uint32_t i;char *p;p = (char *)&hospi1;for (i = 0; i < sizeof(hospi1); i++){*p++ = 0;}	hospi1.Instance  = OCTOSPI1;  HAL_OSPI_DeInit(&hospi1);           // 复位OSPI	
/********************/	hospi1.Instance 						    	= OCTOSPI1;								// OSPI外设hospi1.Init.ClockPrescaler 			  = 2;                                      // 时钟分频值,将OSPI内核时钟进行 2 分频得到OSPI通信驱动时钟hospi1.Init.FifoThreshold 			  = 8;                                     	// FIFO阈值hospi1.Init.DualQuad 					    = HAL_OSPI_DUALQUAD_DISABLE;              // 禁止双OSPI模式hospi1.Init.MemoryType 					  = HAL_OSPI_MEMTYPE_MICRON;                // 存储器模式,只有8线模式才会用到hospi1.Init.DeviceSize 					  = 23;					                        // flash大小,核心板采用是8M字节的W25Q64,这里设置为23,即2^23					         hospi1.Init.ChipSelectHighTime 	  = 1;                                      // 片选保持高电平的时间hospi1.Init.FreeRunningClock 		  = HAL_OSPI_FREERUNCLK_DISABLE;            // 禁止自由时钟模式hospi1.Init.ClockMode 					  = HAL_OSPI_CLOCK_MODE_3;                  // 模式3hospi1.Init.WrapSize					  	= HAL_OSPI_WRAP_NOT_SUPPORTED;            // 不使用 wrap-sizehospi1.Init.SampleShifting 		  	= HAL_OSPI_SAMPLE_SHIFTING_HALFCYCLE;     // 半个CLK周期之后进行采样hospi1.Init.DelayHoldQuarterCycle	= HAL_OSPI_DHQC_DISABLE;                  // 不使用数据保持功能hospi1.Init.ChipSelectBoundary 		= 0;											      // 禁止片选边界功能hospi1.Init.DelayBlockBypass 			= HAL_OSPI_DELAY_BLOCK_BYPASSED;          // 延时模块旁路hospi1.Init.MaxTran              = 0;                                      // 禁止通信管理功能hospi1.Init.Refresh              = 0;                                      // 禁止刷新功能HAL_OSPI_Init(&hospi1);    // 初始化 OSPI1 设置sOspiManagerCfg.ClkPort = 1;     // OSPI引脚分配管理器设置,使用 Port1 的 CLKsOspiManagerCfg.NCSPort = 1;     // OSPI引脚分配管理器设置,使用 Port1 的 NCSsOspiManagerCfg.IOLowPort = HAL_OSPIM_IOPORT_1_LOW;   // OSPI引脚分配管理器设置,使用 Port1 的低4位引脚,IO[3:0]HAL_OSPIM_Config(&hospi1, &sOspiManagerCfg, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);  // 初始化OSPI引脚分配管理器设置
}
#endif/*** @brief  初始化 OSPI 配置,读取W25Q64ID* @param  无* @retval OSPI_W25Qxx_OK - 初始化成功,W25Qxx_ERROR_INIT - 初始化错误
*/
int8_t OSPI_W25Qxx_Init(void)
{uint32_t	Device_ID;	// 器件ID#if	CUBEMX_INIT == 0W25Q_MX_OCTOSPI1_Init();							// 初始化 OSPI 配置
#endifDevice_ID = OSPI_W25Qxx_ReadID(); 		// 读取器件IDif( Device_ID == W25Qxx_FLASH_ID )		// 进行匹配{
#if DBG_PRT == 1printf ("W25Q64 OK,flash ID:%X\r\n",Device_ID);		// 初始化成功
#endifreturn OSPI_W25Qxx_OK;			// 返回成功标志		}else{
#if DBG_PRT == 1printf ("W25Q64 ERROR!!!!!  ID:%X\r\n",Device_ID);	// 初始化失败	
#endifreturn W25Qxx_ERROR_INIT;		// 返回错误标志}	
}/*** @brief  使用自动轮询标志查询,等待通信结束* @param  无* @retval OSPI_W25Qxx_OK - 通信正常结束,W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应
*/
int8_t OSPI_W25Qxx_AutoPollingMemReady(void)
{OSPI_RegularCmdTypeDef  sCommand;		  // OSPI传输配置OSPI_AutoPollingTypeDef sConfig;			// 轮询比较相关配置参数sCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash IDsCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.Address            = 0x0;                                // 地址0sCommand.AddressMode        = HAL_OSPI_ADDRESS_NONE;              // 无地址模式sCommand.AddressSize        = HAL_OSPI_ADDRESS_24_BITS;           // 地址长度24位sCommand.AddressDtrMode     = HAL_OSPI_ADDRESS_DTR_DISABLE;       // 禁止地址DTR模式sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节sCommand.DataMode           = HAL_OSPI_DATA_1_LINE;               // 1线数据模式sCommand.DataDtrMode        = HAL_OSPI_DATA_DTR_DISABLE;          // 禁止数据DTR模式sCommand.NbData             = 1;                                  // 通信数据长度sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQSsCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令sCommand.Instruction        = W25Qxx_CMD_ReadStatus_REG1;         // 读状态信息寄存器if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}// 不停的查询 W25Qxx_CMD_ReadStatus_REG1 寄存器,将读取到的状态字节中的 W25Qxx_Status_REG1_BUSY 不停的与0作比较
// 读状态寄存器1的第0位(只读),Busy标志位,当正在擦除/写入数据/写命令时会被置1,空闲或通信结束为0
// FANKE	sConfig.Match         = 0;											//	匹配值	sConfig.MatchMode     = HAL_OSPI_MATCH_MODE_AND;			//	与运算sConfig.Interval      = 0x10;										//	轮询间隔sConfig.AutomaticStop = HAL_OSPI_AUTOMATIC_STOP_ENABLE;	// 自动停止模式sConfig.Mask          = W25Qxx_Status_REG1_BUSY; 			// 对在轮询模式下接收的状态字节进行屏蔽,只比较需要用到的位// 发送轮询等待命令if (HAL_OSPI_AutoPolling(&hospi1, &sConfig,HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}return OSPI_W25Qxx_OK; // 通信正常结束	
}/*** @brief  初始化 OSPI 配置,读取器件ID* @param  无* @retval W25Qxx_ID - 读取到的器件ID,W25Qxx_ERROR_INIT - 通信、初始化错误
*/
uint32_t OSPI_W25Qxx_ReadID(void)	
{OSPI_RegularCmdTypeDef  sCommand;		// OSPI传输配置uint8_t	OSPI_ReceiveBuff[3];		      // 存储OSPI读到的数据uint32_t	W25Qxx_ID;					      // 器件的IDsCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash IDsCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.AddressMode        = HAL_OSPI_ADDRESS_NONE;              // 无地址模式sCommand.AddressSize        = HAL_OSPI_ADDRESS_24_BITS;           // 地址长度24位   sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节sCommand.DataMode           = HAL_OSPI_DATA_1_LINE;               // 1线数据模式sCommand.DataDtrMode        = HAL_OSPI_DATA_DTR_DISABLE;          // 禁止数据DTR模式sCommand.NbData             = 3;                                  // 传输数据的长度sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQSsCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令   sCommand.Instruction        = W25Qxx_CMD_JedecID;                 // 执行读器件ID命令HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);	// 发送指令HAL_OSPI_Receive (&hospi1, OSPI_ReceiveBuff, HAL_OSPI_TIMEOUT_DEFAULT_VALUE);	// 接收数据W25Qxx_ID = (OSPI_ReceiveBuff[0] << 16) | (OSPI_ReceiveBuff[1] << 8 ) | OSPI_ReceiveBuff[2];	// 将得到的数据组合成IDreturn W25Qxx_ID; // 返回ID
}/*** @brief  将OSPI设置为内存映射模式* @param  无* @retval OSPI_W25Qxx_OK - 写使能成功,W25Qxx_ERROR_WriteEnable - 写使能失败
*/
int8_t OSPI_W25Qxx_MemoryMappedMode(void)
{OSPI_RegularCmdTypeDef     sCommand;         // QSPI传输配置OSPI_MemoryMappedTypeDef   sMemMappedCfg;    // 内存映射访问参数#if  OSPI_MODE  ==  QUADsCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash IDsCommand.Instruction             = W25Qxx_CMD_FastReadQuad_IO;             // 1-4-4模式下(1线指令4线地址4线数据),快速读取指令sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.AddressMode             = HAL_OSPI_ADDRESS_4_LINES;               // 4线地址模式sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          // 无交替字节    sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   // 禁止替字节DTR模式 sCommand.DataMode                = HAL_OSPI_DATA_4_LINES;                  // 4线数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式 sCommand.DummyCycles             = 6;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQS sCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令   
#endif#if  OSPI_MODE  ==  DUALsCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash IDsCommand.Instruction             = W25Qxx_CMD_FastReadDual_IO;             // 1-2-2模式下(1线指令2线地址2线数据),快速读取指令sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.AddressMode             = HAL_OSPI_ADDRESS_2_LINES;               // 2线地址模式sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          // 无交替字节    sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   // 禁止替字节DTR模式 sCommand.DataMode                = HAL_OSPI_DATA_2_LINES;                  // 2线数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式 sCommand.DummyCycles             = 4;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQS sCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令   
#endif// 写配置if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_TRANSMIT;		// 传输数据错误}   sMemMappedCfg.TimeOutActivation  = HAL_OSPI_TIMEOUT_COUNTER_DISABLE;          // 禁用超时计数器, nCS 保持激活状态sMemMappedCfg.TimeOutPeriod      = 0;                                         // 超时判断周期// 开启内存映射模式if (HAL_OSPI_MemoryMapped(&hospi1,  &sMemMappedCfg) != HAL_OK)	// 进行配置{return W25Qxx_ERROR_MemoryMapped; 	// 设置内存映射模式错误}return OSPI_W25Qxx_OK; // 配置成功
}/*** @brief  发送写使能命令* @param  无* @retval OSPI_W25Qxx_OK - 写使能成功,W25Qxx_ERROR_WriteEnable - 写使能失败
*/
int8_t OSPI_W25Qxx_WriteEnable(void)
{OSPI_RegularCmdTypeDef  sCommand;// OSPI传输配置OSPI_AutoPollingTypeDef sConfig;// 轮询比较相关配置参数sCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash ID                       sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.Address                 = 0;                                      // 地址0sCommand.AddressMode             = HAL_OSPI_ADDRESS_NONE;                  // 无地址模式   sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   //	禁止替字节DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          //	无交替字节sCommand.DataMode                = HAL_OSPI_DATA_NONE;                     // 无数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式sCommand.DummyCycles             = 0;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQSsCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令sCommand.Instruction             = W25Qxx_CMD_WriteEnable;                 // 写使能命令// 发送写使能命令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_WriteEnable;	}// 发送查询状态寄存器命令 sCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash ID sCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.AddressMode        = HAL_OSPI_ADDRESS_NONE;              // 无地址模式  sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节   sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQS   sCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令sCommand.DataMode           = HAL_OSPI_DATA_1_LINE;               // 1线数据模式sCommand.NbData             = 1;                                  // 通信数据长度sCommand.Instruction        = W25Qxx_CMD_ReadStatus_REG1;         // 查询状态寄存器命令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_WriteEnable;	}// 不停的查询 W25Qxx_CMD_ReadStatus_REG1 寄存器,将读取到的状态字节中的 W25Qxx_Status_REG1_WEL 不停的与 0x02 作比较
// 读状态寄存器1的第1位(只读),WEL写使能标志位,该标志位为1时,代表可以进行写操作
// FANKE	7B0	sConfig.Match         = 0x02;										//	匹配值	sConfig.MatchMode     = HAL_OSPI_MATCH_MODE_AND;			//	与运算sConfig.Interval      = 0x10;										//	轮询间隔sConfig.AutomaticStop = HAL_OSPI_AUTOMATIC_STOP_ENABLE;	// 自动停止模式sConfig.Mask          = W25Qxx_Status_REG1_WEL; 			// 对在轮询模式下接收的状态字节进行屏蔽,只比较需要用到的位if (HAL_OSPI_AutoPolling(&hospi1, &sConfig,HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}return OSPI_W25Qxx_OK;  // 通信正常结束
}/*** @brief  进行扇区擦除操作,每次擦除4K字节* @param  SectorAddress - 要擦除的地址* @retval OSPI_W25Qxx_OK - 擦除成功*			    W25Qxx_ERROR_Erase - 擦除失败*				  W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.按照 W25Q64JV 数据手册给出的擦除参考时间,典型值为 45ms,最大值为400ms*				 2.实际的擦除速度可能大于45ms,也可能小于45ms*				 3.flash使用的时间越长,擦除所需时间也会越长
*/
int8_t OSPI_W25Qxx_SectorErase(uint32_t SectorAddress)	
{OSPI_RegularCmdTypeDef  sCommand;		// OSPI传输配置sCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash IDsCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.Address            = SectorAddress;                      // 地址sCommand.AddressMode        = HAL_OSPI_ADDRESS_1_LINE;            // 1线地址模式sCommand.AddressSize        = HAL_OSPI_ADDRESS_24_BITS;           // 地址长度24位sCommand.AddressDtrMode     = HAL_OSPI_ADDRESS_DTR_DISABLE;       // 禁止地址DTR模式sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节sCommand.DataMode           = HAL_OSPI_DATA_NONE;                 // 无数据模式sCommand.DataDtrMode        = HAL_OSPI_DATA_DTR_DISABLE;          // 禁止数据DTR模式sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQSsCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令sCommand.Instruction        = W25Qxx_CMD_SectorErase;             // 扇区擦除指令,每次擦除4K字节// 发送写使能if (OSPI_W25Qxx_WriteEnable() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_WriteEnable;		// 写使能失败}// 发送擦除指令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}   // 使用自动轮询标志位,等待擦除的结束 if (OSPI_W25Qxx_AutoPollingMemReady() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_AUTOPOLLING;		// 轮询等待无响应}return OSPI_W25Qxx_OK; // 擦除成功
}/*** @brief  进行块擦除操作,每次擦除32K字节* @param  SectorAddress - 要擦除的地址* @retval OSPI_W25Qxx_OK - 擦除成功*			    W25Qxx_ERROR_Erase - 擦除失败*				  W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.按照 W25Q64JV 数据手册给出的擦除参考时间,典型值为 120ms,最大值为1600ms*				 2.实际的擦除速度可能大于120ms,也可能小于120ms*				 3.flash使用的时间越长,擦除所需时间也会越长
*/
int8_t OSPI_W25Qxx_BlockErase_32K (uint32_t SectorAddress)	
{OSPI_RegularCmdTypeDef  sCommand;		// OSPI传输配置sCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash IDsCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.Address            = SectorAddress;                      // 地址sCommand.AddressMode        = HAL_OSPI_ADDRESS_1_LINE;            // 1线地址模式sCommand.AddressSize        = HAL_OSPI_ADDRESS_24_BITS;           // 地址长度24位sCommand.AddressDtrMode     = HAL_OSPI_ADDRESS_DTR_DISABLE;       // 禁止地址DTR模式sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节sCommand.DataMode           = HAL_OSPI_DATA_NONE;                 // 无数据模式sCommand.DataDtrMode        = HAL_OSPI_DATA_DTR_DISABLE;          // 禁止数据DTR模式sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQSsCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令sCommand.Instruction        = W25Qxx_CMD_BlockErase_32K;          // 块擦除指令,每次擦除32K字节// 发送写使能if (OSPI_W25Qxx_WriteEnable() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_WriteEnable;		// 写使能失败}// 发送擦除指令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}   // 使用自动轮询标志位,等待擦除的结束 if (OSPI_W25Qxx_AutoPollingMemReady() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_AUTOPOLLING;		// 轮询等待无响应}return OSPI_W25Qxx_OK; // 擦除成功	
}/*** @brief  进行块擦除操作,每次擦除64K字节* @param  SectorAddress - 要擦除的地址* @retval OSPI_W25Qxx_OK - 擦除成功*			    W25Qxx_ERROR_Erase - 擦除失败*				  W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.按照 W25Q64JV 数据手册给出的擦除参考时间,典型值为 150ms,最大值为2000ms*				 2.实际的擦除速度可能大于150ms,也可能小于150ms*				 3.flash使用的时间越长,擦除所需时间也会越长*				 4.实际使用建议使用64K擦除,擦除的时间最快
*/
int8_t OSPI_W25Qxx_BlockErase_64K (uint32_t SectorAddress)	
{OSPI_RegularCmdTypeDef  sCommand;		// OSPI传输配置sCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash IDsCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.Address            = SectorAddress;                      // 地址sCommand.AddressMode        = HAL_OSPI_ADDRESS_1_LINE;            // 1线地址模式sCommand.AddressSize        = HAL_OSPI_ADDRESS_24_BITS;           // 地址长度24位sCommand.AddressDtrMode     = HAL_OSPI_ADDRESS_DTR_DISABLE;       // 禁止地址DTR模式sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节sCommand.DataMode           = HAL_OSPI_DATA_NONE;                 // 无数据模式sCommand.DataDtrMode        = HAL_OSPI_DATA_DTR_DISABLE;          // 禁止数据DTR模式sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQSsCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令sCommand.Instruction        = W25Qxx_CMD_BlockErase_64K;          // 扇区擦除指令,每次擦除64K字节// 发送写使能if (OSPI_W25Qxx_WriteEnable() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_WriteEnable;		// 写使能失败}// 发送擦除指令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}   // 使用自动轮询标志位,等待擦除的结束 if (OSPI_W25Qxx_AutoPollingMemReady() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_AUTOPOLLING;		// 轮询等待无响应}return OSPI_W25Qxx_OK; // 擦除成功			
}/*** @brief  进行整片擦除操作* @param  无* @retval OSPI_W25Qxx_OK - 擦除成功*			    W25Qxx_ERROR_Erase - 擦除失败*				  W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.按照 W25Q64JV 数据手册给出的擦除参考时间,典型值为 20s,最大值为100s*				 2.实际的擦除速度可能大于20s,也可能小于20s*				 3.flash使用的时间越长,擦除所需时间也会越长
*/
int8_t OSPI_W25Qxx_ChipErase (void)	
{OSPI_RegularCmdTypeDef  sCommand;		// OSPI传输配置OSPI_AutoPollingTypeDef sConfig;   // 轮询比较相关配置参数sCommand.OperationType      = HAL_OSPI_OPTYPE_COMMON_CFG;         // 通用配置sCommand.FlashId            = HAL_OSPI_FLASH_ID_1;                // flash IDsCommand.InstructionMode    = HAL_OSPI_INSTRUCTION_1_LINE;        // 1线指令模式sCommand.InstructionSize    = HAL_OSPI_INSTRUCTION_8_BITS;        // 指令长度8位sCommand.InstructionDtrMode = HAL_OSPI_INSTRUCTION_DTR_DISABLE;   // 禁止指令DTR模式sCommand.AddressMode        = HAL_OSPI_ADDRESS_NONE;              // 无地址模式sCommand.AddressDtrMode     = HAL_OSPI_ADDRESS_DTR_DISABLE;       // 禁止地址DTR模式   sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE;      //	无交替字节sCommand.DataMode           = HAL_OSPI_DATA_NONE;                 // 无数据模式sCommand.DataDtrMode        = HAL_OSPI_DATA_DTR_DISABLE;          // 禁止数据DTR模式sCommand.DummyCycles        = 0;                                  // 空周期个数sCommand.DQSMode            = HAL_OSPI_DQS_DISABLE;               // 不使用DQSsCommand.SIOOMode           = HAL_OSPI_SIOO_INST_EVERY_CMD;       // 每次传输数据都发送指令sCommand.Instruction        = W25Qxx_CMD_ChipErase;               // 全片擦除指令// 发送写使能if (OSPI_W25Qxx_WriteEnable() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_WriteEnable;		// 写使能失败}// 发送擦除指令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}   // 发送查询状态寄存器命令sCommand.DataMode       = HAL_OSPI_DATA_1_LINE;          // 一线数据模式sCommand.NbData         = 1;                             // 数据长度1sCommand.Instruction    = W25Qxx_CMD_ReadStatus_REG1;    // 状态寄存器命令if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING;	}// 不停的查询 W25Qxx_CMD_ReadStatus_REG1 寄存器,将读取到的状态字节中的 W25Qxx_Status_REG1_BUSY 不停的与0作比较
// 读状态寄存器1的第0位(只读),Busy标志位,当正在擦除/写入数据/写命令时会被置1,空闲或通信结束为0sConfig.Match         = 0;											//	匹配值	sConfig.MatchMode     = HAL_OSPI_MATCH_MODE_AND;			//	与运算sConfig.Interval      = 0x10;										//	轮询间隔sConfig.AutomaticStop = HAL_OSPI_AUTOMATIC_STOP_ENABLE;	// 自动停止模式sConfig.Mask          = W25Qxx_Status_REG1_BUSY; 			// 对在轮询模式下接收的状态字节进行屏蔽,只比较需要用到的位// W25Q64整片擦除的典型参考时间为20s,最大时间为100s,这里的超时等待值 W25Qxx_ChipErase_TIMEOUT_MAX 为 100Sif (HAL_OSPI_AutoPolling(&hospi1, &sConfig,W25Qxx_ChipErase_TIMEOUT_MAX) != HAL_OK){return W25Qxx_ERROR_AUTOPOLLING; // 轮询等待无响应}return OSPI_W25Qxx_OK; // 擦除成功				
}/*** @brief  按页写入,最大只能256字节,在数据写入之前,请务必完成擦除操作* @param  pBuffer 		 - 要写入的数据*				 WriteAddr 		 - 要写入 W25Qxx 的地址*				 NumByteToWrite - 数据长度,最大只能256字节* @retval OSPI_W25Qxx_OK 		     - 写数据成功*			    W25Qxx_ERROR_WriteEnable - 写使能失败*				  W25Qxx_ERROR_TRANSMIT	  - 传输失败*				  W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.Flash的写入时间和擦除时间一样,是限定的,并不是说OSPI驱动时钟133M就可以以这个速度进行写入*				 2.按照 W25Q64JV 数据手册给出的 页(256字节) 写入参考时间,典型值为 0.4ms,最大值为3ms*				 3.实际的写入速度可能大于0.4ms,也可能小于0.4ms*				 4.Flash使用的时间越长,写入所需时间也会越长*				 5.在数据写入之前,请务必完成擦除操作
*/
int8_t OSPI_W25Qxx_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{OSPI_RegularCmdTypeDef  sCommand;// OSPI传输配置#if  OSPI_MODE  ==  QUADsCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash IDsCommand.Instruction             = W25Qxx_CMD_QuadInputPageProgram;        // 1-1-4模式下(1线指令1线地址4线数据),页编程指令sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.Address                 = WriteAddr;                              // 地址sCommand.AddressMode             = HAL_OSPI_ADDRESS_1_LINE;                // 1线地址模式sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          // 无交替字节         sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   // 禁止替字节DTR模式sCommand.DataMode                = HAL_OSPI_DATA_4_LINES;                  // 4线数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式sCommand.NbData                  = NumByteToWrite;                         // 数据长度sCommand.DummyCycles             = 0;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQSsCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令   
#endif#if  OSPI_MODE  ==  DUALsCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash IDsCommand.Instruction             = W25Qxx_CMD_PageProgram;                 // 1-1-1模式下(1线指令1线地址1线数据),页编程指令sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.Address                 = WriteAddr;                              // 地址sCommand.AddressMode             = HAL_OSPI_ADDRESS_1_LINE;                // 1线地址模式sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          // 无交替字节         sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   // 禁止替字节DTR模式sCommand.DataMode                = HAL_OSPI_DATA_1_LINE;                   // 1线数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式sCommand.NbData                  = NumByteToWrite;                         // 数据长度sCommand.DummyCycles             = 0;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQSsCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令   
#endif// 写使能if (OSPI_W25Qxx_WriteEnable() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_WriteEnable;	// 写使能失败}// 写命令  if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_TRANSMIT;		// 传输数据错误}   // 开始传输数据if (HAL_OSPI_Transmit(&hospi1, pBuffer, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_TRANSMIT;		// 传输数据错误}// 使用自动轮询标志位,等待写入的结束 if (OSPI_W25Qxx_AutoPollingMemReady() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_AUTOPOLLING;		// 轮询等待无响应}return OSPI_W25Qxx_OK; // 写数据成功
}/*** @brief  写入数据,最大不能超过flash芯片的大小,请务必完成擦除操作* @param  pBuffer 		 - 要写入的数据*				 WriteAddr 		 - 要写入 W25Qxx 的地址*				 NumByteToWrite - 数据长度,最大不能超过flash芯片的大小* @retval OSPI_W25Qxx_OK 		     - 写数据成功*			    W25Qxx_ERROR_WriteEnable - 写使能失败*				  W25Qxx_ERROR_TRANSMIT	  - 传输失败*				  W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.Flash的写入时间和擦除时间一样,是有限定的,并不是说OSPI驱动时钟133M就可以以这个速度进行写入*				 2.按照 W25Q64JV 数据手册给出的 页 写入参考时间,典型值为 0.4ms,最大值为3ms*				 3.实际的写入速度可能大于0.4ms,也可能小于0.4ms*				 4.Flash使用的时间越长,写入所需时间也会越长*				 5.在数据写入之前,请务必完成擦除操作
*/
int8_t OSPI_W25Qxx_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t Size)
{	uint32_t end_addr, current_size, current_addr;uint8_t *write_data;  // 要写入的数据current_size = W25Qxx_PageSize - (WriteAddr % W25Qxx_PageSize); // 计算当前页还剩余的空间if (current_size > Size)	// 判断当前页剩余的空间是否足够写入所有数据{current_size = Size;		// 如果足够,则直接获取当前长度}current_addr = WriteAddr;		// 获取要写入的地址end_addr = WriteAddr + Size;	// 计算结束地址write_data = pBuffer;			// 获取要写入的数据do{// 按页写入数据if(OSPI_W25Qxx_WritePage(write_data, current_addr, current_size) != OSPI_W25Qxx_OK){return W25Qxx_ERROR_TRANSMIT;}else // 按页写入数据成功,进行下一次写数据的准备工作{current_addr += current_size;	// 计算下一次要写入的地址write_data += current_size;	// 获取下一次要写入的数据存储区地址// 计算下一次写数据的长度current_size = ((current_addr + W25Qxx_PageSize) > end_addr) ? (end_addr - current_addr) : W25Qxx_PageSize;}}while (current_addr < end_addr) ; // 判断数据是否全部写入完毕return OSPI_W25Qxx_OK;	// 写入数据成功
}/*** @brief  读取数据,最大不能超过flash芯片的大小* @param  pBuffer 		 - 要读取的数据*				 ReadAddr 		 - 要读取 W25Qxx 的地址*				 NumByteToRead  - 数据长度,最大不能超过flash芯片的大小* @retval OSPI_W25Qxx_OK 		     - 读数据成功*				 W25Qxx_ERROR_TRANSMIT	  - 传输失败*				 W25Qxx_ERROR_AUTOPOLLING - 轮询等待无响应*	说    明: 1.Flash的读取速度取决于OSPI的通信时钟,最大不能超过133M*				 2.这里使用的是1-4-4模式下(1线指令4线地址4线数据),快速读取指令 Fast Read Quad I/O*				 3.使用快速读取指令是有空周期的,具体参考W25Q64JV的手册  Fast Read Quad I/O  (0xEB)指令*				 4.实际使用中,是否使用DMA、编译器的优化等级以及数据存储区的位置(内部 TCM SRAM 或者 AXI SRAM)都会影响读取的速度
*/
int8_t OSPI_W25Qxx_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
{OSPI_RegularCmdTypeDef  sCommand;// OSPI传输配置#if  OSPI_MODE  ==  QUADsCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash IDsCommand.Instruction             = W25Qxx_CMD_FastReadQuad_IO;             // 1-4-4模式下(1线指令4线地址4线数据),快速读取指令sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.Address                 = ReadAddr;                               // 地址sCommand.AddressMode             = HAL_OSPI_ADDRESS_4_LINES;               // 4线地址模式sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          // 无交替字节    sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   // 禁止替字节DTR模式 sCommand.DataMode                = HAL_OSPI_DATA_4_LINES;                  // 4线数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式 sCommand.NbData                  = NumByteToRead;                          // 数据长度sCommand.DummyCycles             = 6;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQS sCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令   #endif#if  OSPI_MODE  ==  DUALsCommand.OperationType           = HAL_OSPI_OPTYPE_COMMON_CFG;             // 通用配置sCommand.FlashId                 = HAL_OSPI_FLASH_ID_1;                    // flash IDsCommand.Instruction             = W25Qxx_CMD_FastReadDual_IO;             // 1-2-2模式下(1线指令2线地址2线数据),快速读取指令sCommand.InstructionMode         = HAL_OSPI_INSTRUCTION_1_LINE;            // 1线指令模式sCommand.InstructionSize         = HAL_OSPI_INSTRUCTION_8_BITS;            // 指令长度8位sCommand.InstructionDtrMode      = HAL_OSPI_INSTRUCTION_DTR_DISABLE;       // 禁止指令DTR模式sCommand.Address                 = ReadAddr;                               // 地址sCommand.AddressMode             = HAL_OSPI_ADDRESS_2_LINES;               // 2线地址模式sCommand.AddressSize             = HAL_OSPI_ADDRESS_24_BITS;               // 地址长度24位sCommand.AddressDtrMode          = HAL_OSPI_ADDRESS_DTR_DISABLE;           // 禁止地址DTR模式sCommand.AlternateBytesMode      = HAL_OSPI_ALTERNATE_BYTES_NONE;          // 无交替字节    sCommand.AlternateBytesDtrMode   = HAL_OSPI_ALTERNATE_BYTES_DTR_DISABLE;   // 禁止替字节DTR模式 sCommand.DataMode                = HAL_OSPI_DATA_2_LINES;                  // 2线数据模式sCommand.DataDtrMode             = HAL_OSPI_DATA_DTR_DISABLE;              // 禁止数据DTR模式 sCommand.NbData                  = NumByteToRead;                          // 数据长度sCommand.DummyCycles             = 4;                                      // 空周期个数sCommand.DQSMode                 = HAL_OSPI_DQS_DISABLE;                   // 不使用DQS sCommand.SIOOMode                = HAL_OSPI_SIOO_INST_EVERY_CMD;           // 每次传输数据都发送指令   #endif// 写命令  if (HAL_OSPI_Command(&hospi1, &sCommand, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_TRANSMIT;		// 传输数据错误}   //	接收数据if (HAL_OSPI_Receive(&hospi1, pBuffer, HAL_OSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK){return W25Qxx_ERROR_TRANSMIT;		// 传输数据错误}// 使用自动轮询标志位,等待接收的结束  if (OSPI_W25Qxx_AutoPollingMemReady() != OSPI_W25Qxx_OK){return W25Qxx_ERROR_AUTOPOLLING;		// 轮询等待无响应}return OSPI_W25Qxx_OK;	// 读取数据成功
}#if  TEST_EN  ==  1      //测试模块#define W25Qxx_NumByteToTest   	32*1024					// 测试数据的长度,32Kint32_t OSPI_Status ; 		 //检测标志位
uint8_t  W25Qxx_WriteBuffer[W25Qxx_NumByteToTest];		//	写数据数组
uint8_t  W25Qxx_ReadBuffer[W25Qxx_NumByteToTest];		//	读数据数组
uint32_t ExecutionTime_Begin;		// 开始时间
uint32_t ExecutionTime_End;		// 结束时间
uint32_t ExecutionTime;				// 执行时间	
float    ExecutionSpeed;			// 执行速度/*** @brief  进行简单的读写测试,并计算速度* @param  无* @retval OSPI_W25Qxx_OK - 测试成功并通过
*/
int8_t OSPI_W25Qxx_Test(void)		//Flash读写测试
{uint32_t i = 0;	// 计数变量uint32_t W25Qxx_TestAddr = 0;// 测试地址	// 擦除 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 	ExecutionTime_Begin  = HAL_GetTick();	// 获取 systick 当前时间,单位msOSPI_Status 			   = OSPI_W25Qxx_BlockErase_32K(W25Qxx_TestAddr);	// 擦除32K字节ExecutionTime_End		 = HAL_GetTick();	// 获取 systick 当前时间,单位msExecutionTime = ExecutionTime_End - ExecutionTime_Begin; // 计算擦除时间,单位msif( OSPI_Status == OSPI_W25Qxx_OK ){
#if DBG_PRT == 1printf ("\r\nW25Q64 擦除成功, 擦除32K字节所需时间: %d ms\r\n",ExecutionTime);		
#endif}else{
#if DBG_PRT == 1printf ("\r\n 擦除失败!!!!!  错误代码:%d\r\n",OSPI_Status);
#endifwhile (1);}	// 写入 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>for(i = 0; i < W25Qxx_NumByteToTest; i++)  //先将数据写入数组{W25Qxx_WriteBuffer[i] = i;}ExecutionTime_Begin  = HAL_GetTick();	// 获取 systick 当前时间,单位msOSPI_Status				   = OSPI_W25Qxx_WriteBuffer(W25Qxx_WriteBuffer,W25Qxx_TestAddr,W25Qxx_NumByteToTest); // 写入数据ExecutionTime_End		 = HAL_GetTick();	// 获取 systick 当前时间,单位msExecutionTime  = ExecutionTime_End - ExecutionTime_Begin; 		// 计算擦除时间,单位msExecutionSpeed = (float)W25Qxx_NumByteToTest / ExecutionTime ; // 计算写入速度,单位 KB/Sif( OSPI_Status == OSPI_W25Qxx_OK ){
#if DBG_PRT == 1printf ("\r\n写入成功,数据大小:%d KB, 耗时: %d ms, 写入速度:%.2f KB/s\r\n",W25Qxx_NumByteToTest/1024,ExecutionTime,ExecutionSpeed);		
#endif}else{
#if DBG_PRT == 1printf ("\r\n写入错误!!!!!  错误代码:%d\r\n",OSPI_Status);
#endifwhile (1);}	// 读取	>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ExecutionTime_Begin = HAL_GetTick();	// 获取 systick 当前时间,单位ms	OSPI_Status				  = OSPI_W25Qxx_ReadBuffer(W25Qxx_ReadBuffer,W25Qxx_TestAddr,W25Qxx_NumByteToTest);	// 读取数据ExecutionTime_End		= HAL_GetTick();	// 获取 systick 当前时间,单位msExecutionTime  = ExecutionTime_End - ExecutionTime_Begin; 					// 计算擦除时间,单位msExecutionSpeed = (float)W25Qxx_NumByteToTest/1024/1024 / ExecutionTime*1000 ; 	// 计算读取速度,单位 MB/S if( OSPI_Status == OSPI_W25Qxx_OK ){
#if DBG_PRT == 1printf ("\r\n读取成功,数据大小:%d KB, 耗时: %d ms, 读取速度:%.2f MB/s \r\n",W25Qxx_NumByteToTest/1024,ExecutionTime,ExecutionSpeed);		
#endif}else{
#if DBG_PRT == 1printf ("\r\n读取错误!!!!!  错误代码:%d\r\n",OSPI_Status);
#endifwhile (1);}// 数据校验 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   for(i = 0; i < W25Qxx_NumByteToTest; i++)	//验证读出的数据是否等于写入的数据{if( W25Qxx_WriteBuffer[i] != W25Qxx_ReadBuffer[i] )	//如果数据不相等,则返回0	{
#if DBG_PRT == 1printf ("\r\n数据校验失败!!!!!出错位置:%d\r\n",i);	
#endifwhile(1);}}	
#if DBG_PRT == 1	printf ("\r\n校验通过!!!!! QSPI驱动W25Q64测试正常\r\n");		
#endif// 读取整片Flash的数据,用以测试速度 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#if DBG_PRT == 1	printf ("\r\n*****************************************************************************************************\r\n");		printf ("\r\n上面的测试中,读取的数据比较小,耗时很短,加之测量的最小单位为ms,计算出的读取速度误差较大\r\n");		printf ("\r\n接下来读取整片flash的数据用以测试速度,这样得出的速度误差比较小\r\n");		printf ("\r\n开始读取>>>>\r\n");		
#endifExecutionTime_Begin = HAL_GetTick();	// 获取 systick 当前时间,单位ms		for(i = 0; i < W25Qxx_FlashSize/(W25Qxx_NumByteToTest); i++)	// 每次读取 W25Qxx_NumByteToTest 字节的数据{OSPI_Status			= OSPI_W25Qxx_ReadBuffer(W25Qxx_ReadBuffer,W25Qxx_TestAddr,W25Qxx_NumByteToTest);	// 读取数据W25Qxx_TestAddr = W25Qxx_TestAddr + W25Qxx_NumByteToTest;		}ExecutionTime_End	= HAL_GetTick();	// 获取 systick 当前时间,单位msExecutionTime  = ExecutionTime_End - ExecutionTime_Begin; 								// 计算擦除时间,单位msExecutionSpeed = (float)W25Qxx_FlashSize/1024/1024 / ExecutionTime*1000  ; 	// 计算读取速度,单位 MB/S if( OSPI_Status == OSPI_W25Qxx_OK ){
#if DBG_PRT == 1printf ("\r\n读取成功,数据大小:%d MB, 耗时: %d ms, 读取速度:%.2f MB/s \r\n",W25Qxx_FlashSize/1024/1024,ExecutionTime,ExecutionSpeed);		
#endif}else{
#if DBG_PRT == 1printf ("\r\n读取错误!!!!!  错误代码:%d\r\n",OSPI_Status);
#endifwhile (1);}	return OSPI_W25Qxx_OK ;  // 测试通过
}
#endif/********************************************* *****END OF FILE****/

配置好cubemx生成项目后,

main.c初始化部分加入"ospi_w25q64.h"头文件以及如下代码

	OSPI_W25Qxx_Init();
#if  TEST_EN  ==  1OSPI_W25Qxx_Test();
#endif

然后就可以愉快的debug打断点,测试NOR FLASH读写速度了。

参考例程项目放网盘了,基于CUBEMX创建,请自取,内含参考的源码项目:

通过网盘分享的文件:STM32H723_OCTOSPI.rar
链接: https://pan.baidu.com/s/1iLLVl_CP3d14Ivqwpr-xVg?pwd=jfwu 提取码: jfwu

http://www.dtcms.com/a/347981.html

相关文章:

  • fastmcp 客服端远程MCP服务调用;多工具 MCP服务情景案例;集成fastapi服务
  • Vue.js 核心机制深度学习笔记
  • 阿里云 OSS 前端直传实战:表单上传 + Policy 模式详解
  • 基于魔搭社区与阿里云百炼的云端 RAG 私有知识问答系统实现
  • GHOST 小巧方便的 WinXP !
  • 华为网路设备学习-30(BGP协议 五)Community、
  • 【重学MySQL】八十八、8.0版本核心新特性全解析
  • 质量管理与项目管理学习-初识1
  • ThinkPHP8学习篇(四):请求和响应
  • 基于FPGA的情绪感知系统设计方案:心理健康监测应用(一)
  • centos搭建gitlab服务器
  • 【R语言】R语言中 rbind() 与 merge() 的区别详解
  • 【企业标准开发框架 01】全局异常处理+自定义异常类
  • JAVA限流方法
  • System.IO.Pipelines 与“零拷贝”:在 .NET 打造高吞吐二进制 RPC
  • 【SpringBoot集成篇】SpringBoot 深度集成 Elasticsearch 搜索引擎指南
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十五)网格布局
  • rust语言 (1.88) egui (0.32.1) 学习笔记(逐行注释)(十三)菜单、右键菜单
  • 【JavaEE】了解synchronized
  • 大数据毕业设计选题推荐-基于大数据的丙型肝炎患者数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • 【数据结构】从基础到实战:全面解析归并排序与计数排序
  • 基于stm32汽车雨刮器控制系统设计
  • Java基础第3天总结(面向对象)
  • Shell Case 条件语句详解
  • EP01:【DA】数据分析的概述
  • 01Shell脚本入门:基础命令与变量解析
  • JVM之【类加载系统】
  • 【Qt开发】常用控件(六)
  • Golang云端编程深度指南:架构本质与高阶实践
  • Flink Slot 不足导致任务Pending修复方案