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