1.25 Cubemx_STM32H743II—QSPI

1、QSPI–W25Q256介绍

1.1 地址分配

  • W25Q256FV :256M-bit /32M byte
  • Standard SPI:CLK,/CS,DI,DO,/WP,/HOLD
  • Dual SPI:CLK,/CS,IO0,IO1,/WP,/Hold
  • Quad SPI:CLK,/CS,IO0,IO1,IO2,IO3
  • 3 or 4-Byte Addressing Mode
  • Sector/Block

512Block:

block000000000 --0000FFFFH64K
block100010000 --0001FFFFH64K
block200020000 --0002FFFFH64K
block127007F0000 --007FFFFFH64K
block12800800000 --0080FFFFH64K
block51101FF0000 --01FFFFFFFH64K

SIZE=512*64K(block)=32M

8192 Sector:

sector000000000 --00000FFFH4K
sector100001000 --00001FFFH4K
sector200002000 --00002FFFH4K
sector140000E000 --0000EFFFH4K
sector150000F000 --0000FFFFH4K
SIZE=8192*4K(Sector)==32M

PAGE:256Byte;
Sector(4K) = Page(256Byte) *16;
W25Q256 的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。

 有 512 个 Block,每个 Block 大小 64KB。
 每个 Block 有 16 个 Sector,每个 Sector 大小 4KB。
 每个 Sector 有 16 个 Page,每个 Page 大小是 256 字节。

1.2 特征

STM32H7 的 QSPI 接口具有如下特点:
⚫ 支持三种工作模式:间接模式、状态轮询模式和内存映射模式
⚫ 支持双闪存模式,可以并行访问两个 FLASH,可同时发送/接收 8 位数据
⚫ 支持 SDR(单倍率速率)和 DDR(双倍率速率)模式
⚫ 针对间接模式和内存映射模式,完全可编程操作码
⚫ 针对间接模式和内存映射模式,完全可编程帧格式
⚫ 集成 FIFO,用于发送和接收
⚫ 允许 8、16 和 32 位数据访问
⚫ 具有适用于间接模式操作的 DMA 通道
⚫ 在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断
⚫ 页大小是 256 字节,支持页编程,也就是一次编写 256 个字节,也可以一个一个编写。
⚫ 支持 4KB 为单位的扇区擦除,也可以 32KB 或者 64KB 为单位的擦除。

1.3 硬件框图

BANK1 外接 Flash
在这里插入图片描述

BANK1 和 BANK2 都外接 Flash:
在这里插入图片描述

1.4 时序图

在这里插入图片描述
 指令阶段
在此阶段,将命令(8 位指令)发送到 Flash,允许发送任何值。用户可以简单地将所需的命令写在指令字段中。根据软件和硬件配置,可以 1 线,2 线或者 4 线方式发送。在某些只发送地址的案例中,指令阶段可以跳过。

 地址阶段
在此阶段,将地址发送到 Flash,从指定的地址读取或写入数据。 地址阶段是完全可配置的,允许发送 1、2、3 或 4 个字节的地址。在间接模式和自动轮询模式下,用户可以简单地将所需的地址写入QUADSPI_AR 寄存器。据软件和硬件配置,可以 1 线,2 线或者 4 线方式方式发送。 在一些不需要地址的情况下,可以跳过地址阶段。

 交替字节阶段
QSPI 接口支持的一个额外阶段,具有更大的灵活性。它是通常用于控制操作模式。交替字节阶段是完全可配置的,并允许发送一,二,三或四字节。

 空周期阶段
在高速时钟下运行时,此阶段可以确保有足够的“周转”时间从输出模式切换到输入模式。

 数据阶段
这个阶段实现数据的收发。

1.3 QSPI 的 1 线,2 线,4 线,SDR 和 DDR 模式

1.3.1 1线

1 线(SingleSPI,虽然是一发一收,但属于 1 线方式)
发送用的数据线 BK1_IO0/SO(BK2_IO0/SO),接收用的数据线 BK1_IO1/SI(BK2_IO1/SI) 1 线模式下,所有线处于的状态:
 BK1_IO0 / SO(BK2_IO0 / SO)处于输出模式。
 BK1_IO1 / SI(BK2_IO1 / SI)处于输入模式(高阻抗)。
 BK1_IO2(BK2_IO2)处于输出模式并强制置 0
 BK1_IO3(BK2_IO3)处于输出模式并强制置 1。
在这里插入图片描述

1.3.2 2 线(Dual-SPI)

同时使用 BK1_IO0(BK2_IO0),BK1_IO1(BK2_IO1)做输入输出。
2 线模式下,所有线处于的状态:
 BK1_IO0 (BK2_IO0 )和 BK1_IO1(BK2_IO1)读取时处于输入(高阻)。其它情况下为输出。
 BK1_IO2(BK2_IO2)处于输出模式并强制置 0。
 BK1_IO3(BK2_IO3)处于输出模式并强制置 1
在这里插入图片描述

1.3.3 4 线(Quad-SPI)

同时使用 BK1_IO0(BK2_IO0),BK1_IO1(BK2_IO1),BK1_IO2(BK2_IO2),BK1_IO3(BK2_IO3)
做输入输出。
4 线模式下,当读取数据时,所有线处于输入(高阻),其它情况作为输出。
在这里插入图片描述

1.3.4 SDR

在 SDR 模式下,当 QSPI 驱动 BK1_IO0(BK2_IO0),BK1_IO1(BK2_IO1),BK1_IO2(BK2_IO2),BK1_IO3(BK2_IO3)信号时,这些信号仅在 CLK 的下降沿发生转变

1.3.5 SDR

在 SDR 模式下,当 QSPI 驱动 BK1_IO0(BK2_IO0),BK1_IO1(BK2_IO1),BK1_IO2(BK2_IO2),BK1_IO3(BK2_IO3)信号时,这些信号在 CLK 的上升沿和下降沿发生转变。

1.3.6 双 BANK(双 Flash)

双闪存就是将QSPI的两个 BANK 分别接一个 QSPI Flash,然后时钟公用,片选公用(也可以不公用),
从而实现 8 线模式。
在这里插入图片描述

2、间接模式

这里所谓的间接模式是指寄存器方式访问外设,就跟我们操作串口外设一样。间接模式主要用于以下场合:
 用于读取,写入,擦除和配置 QSPI Flash。
 如果不需要 AHB 总线访问 QSPI Flash(在内存映射模式用)。
 CPU 或者 DMA 通过 QSPI 数据寄存器执行所有操作。

在间接模式下,所有操作均通过 QSPI 寄存器执行,含读取和写入操作都由软件管理。 QSPI 接口类似于经典的 SPI 接口。传输的数据通过数据寄存器与 FIFO。在在此模式下,可以从大容量的外部 Flash 读取数据或向外部 Flash 写入数据,可以支持到 4GB 容量。

如果进行擦除或编程操作,则必须使用间接模式,并且所有操作必须由软件处理。在这种情况下,建议使用状态轮询模式,然后轮询闪存内部的状态寄存器以了解何时编程或擦除操作完成。

2.1正点原子初始化过程

正点原子初始化到读取工厂设备ID过程:
SPI:

  1. 0x35 0x00 //读状态寄存器2指令
  2. 0x06 //W25X_WriteEnable
  3. 0x31 0x02 //写状态寄存器2
  4. 0x38 //W25X_EnterQPIMode 使能QSPI
    QSPI :
  5. 0x90 0xEF18//读W25X_ManufactDeviceID
    在这里插入图片描述
    在这里插入图片描述

W25X_FastReadData 0x0B
//从倒数第100个地址处开始,读出SIZE个字节
W25QXX_Read(datatemp,flash_size-100,SIZE);
“Apollo STM32H7 QSPI TEST”
在这里插入图片描述

2.2 cubemx配置

在这里插入图片描述
在这里插入图片描述

2.2.1 QSPI 总线初始化结构体

2.2.1.1 QSPI_InitTypeDef
typedef struct
{
 uint32_t ClockPrescaler; 
 uint32_t FifoThreshold; 
 uint32_t SampleShifting; 
 uint32_t FlashSize; 
 uint32_t ChipSelectHighTime; 
 uint32_t ClockMode; 
 uint32_t FlashID; 
 uint32_t DualFlash; 
}QSPI_InitTypeDef;

 ClockPrescaler
设置时钟分频,参数范围 0 到 255。特别注意,这里是针对 HCLK3 作为 QSPI 时钟来说的。

 FifoThreshold
用于设置 FIFO 阀值,仅用于间接模式,参数范围 1 到 32,单位字节.

 FlashSize
Flash 大小是 2^(FlashSize + 1),单位字节。
间接模式下,最大支持的 Flash 大小是 4GB,内存映射模式,最大支持 256MB。

 ChipSelectHighTime
命令之间的 CS 片选至少保持的高电平时钟周期 ChipSelectHighTime+1。支持的参数如下:

#define QSPI_CS_HIGH_TIME_1_CYCLE ((uint32_t)0x00000000U) 
#define QSPI_CS_HIGH_TIME_2_CYCLE ((uint32_t)QUADSPI_DCR_CSHT_0) 
#define QSPI_CS_HIGH_TIME_3_CYCLE ((uint32_t)QUADSPI_DCR_CSHT_1) 
#define QSPI_CS_HIGH_TIME_4_CYCLE ((uint32_t)QUADSPI_DCR_CSHT_0 | QUADSPI_DCR_CSHT_1) 
#define QSPI_CS_HIGH_TIME_5_CYCLE ((uint32_t)QUADSPI_DCR_CSHT_2) 
#define QSPI_CS_HIGH_TIME_6_CYCLE ((uint32_t)QUADSPI_DCR_CSHT_2 | QUADSPI_DCR_CSHT_0)
#define QSPI_CS_HIGH_TIME_7_CYCLE ((uint32_t)QUADSPI_DCR_CSHT_2 | QUADSPI_DCR_CSHT_1) 
#define QSPI_CS_HIGH_TIME_8_CYCLE ((uint32_t)QUADSPI_DCR_

 FlashID
用于选择要操作的 BANK,即用 BANK1 还是 BANK2 操作 Flash。

 DualFlash
用于选择是否使用双 BANK。

2.2.1.2 命令结构体 QSPI_CommandTypeDef
typedef struct
{
/*设置要发送的指令,参数范围 0x00 到 0xFF。*/
  uint32_t Instruction;        /* Specifies the Instruction to be sent
                                  This parameter can be a value (8-bit) between 0x00 and 0xFF */

/*设置要发送的地址,地址由是 1 个字节到 4 个字节来表示,参数范围 0x0 到 0xFFFFFFFF。*/                                  
  uint32_t Address;            /* Specifies the Address to be sent (Size from 1 to 4 bytes according AddressSize)
                                  This parameter can be a value (32-bits) between 0x0 and 0xFFFFFFFF */
     
/*交替字节大小,支持的参数如下:
#define QSPI_ALTERNATE_BYTES_8_BITS ((uint32_t)0x00000000) //!<8-bit alternate bytes
#define QSPI_ALTERNATE_BYTES_16_BITS ((uint32_t)QUADSPI_CCR_ABSIZE_0) //!<16-bit alternate bytes
#define QSPI_ALTERNATE_BYTES_24_BITS ((uint32_t)QUADSPI_CCR_ABSIZE_1) //!<24-bit alternate bytes
#define QSPI_ALTERNATE_BYTES_32_BITS ((uint32_t)QUADSPI_CCR_ABSIZE) //!<32-bit alternate bytes*/                                  
  uint32_t AlternateBytes;     /* Specifies the Alternate Bytes to be sent (Size from 1 to 4 bytes according AlternateBytesSize)
                                  This parameter can be a value (32-bits) between 0x0 and 0xFFFFFFFF */
 
 /*地址大小,即表示此地址需要的字节数:
 #define QSPI_ADDRESS_8_BITS ((uint32_t)0x00000000) //!<8-bit address
#define QSPI_ADDRESS_16_BITS ((uint32_t)QUADSPI_CCR_ADSIZE_0) //!<16-bit address*/                                 
  uint32_t AddressSize;        /* Specifies the Address Size
                                  This parameter can be a value of @ref QSPI_AddressSize */
   
 /*交替字节大小,支持的参数如下:
#define QSPI_ALTERNATE_BYTES_8_BITS ((uint32_t)0x00000000) //!<8-bit alternate bytes
#define QSPI_ALTERNATE_BYTES_16_BITS ((uint32_t)QUADSPI_CCR_ABSIZE_0) //!<16-bit alternate bytes
#define QSPI_ALTERNATE_BYTES_24_BITS ((uint32_t)QUADSPI_CCR_ABSIZE_1) //!<24-bit alternate bytes
#define QSPI_ALTERNATE_BYTES_32_BITS ((uint32_t)QUADSPI_CCR_ABSIZE) //!<32-bit alternate bytes*/                               
  uint32_t AlternateBytesSize; /* Specifies the Alternate Bytes Size
                                  This parameter can be a value of @ref QSPI_AlternateBytesSize */

/*执行空周期个数,参数范围 0 到 31:*/
  uint32_t DummyCycles;        /* Specifies the Number of Dummy Cycles.
                                  This parameter can be a number between 0 and 31 */

/*指令阶段需要几线模式:
#define QSPI_INSTRUCTION_NONE ((uint32_t)0x00000000) //!<No instruction
#define QSPI_INSTRUCTION_1_LINE ((uint32_t)QUADSPI_CCR_IMODE_0) //!<Instruction on a single line
#define QSPI_INSTRUCTION_2_LINES ((uint32_t)QUADSPI_CCR_IMODE_1) //!<Instruction on two lines
#define QSPI_INSTRUCTION_4_LINES ((uint32_t)QUADSPI_CCR_IMODE) //!<Instruction on four lines*/
  uint32_t InstructionMode;    /* Specifies the Instruction Mode
                                  This parameter can be a value of @ref QSPI_InstructionMode */

/*
地址阶段需要几线模式:
#define QSPI_ADDRESS_NONE ((uint32_t)0x00000000) //!<No address
#define QSPI_ADDRESS_1_LINE ((uint32_t)QUADSPI_CCR_ADMODE_0) //!<Address on a single line
#define QSPI_ADDRESS_2_LINES ((uint32_t)QUADSPI_CCR_ADMODE_1) //!<Address on two lines
#define QSPI_ADDRESS_4_LINES ((uint32_t)QUADSPI_CCR_ADMODE) //!<Address on four lines
*/
  uint32_t AddressMode;        /* Specifies the Address Mode
                                  This parameter can be a value of @ref QSPI_AddressMode */

/*
交替字节阶段需要几线模式:
#define QSPI_ALTERNATE_BYTES_NONE ((uint32_t)0x00000000) //!<No alternate bytes
#define QSPI_ALTERNATE_BYTES_1_LINE ((uint32_t)QUADSPI_CCR_ABMODE_0) //!<Alternate bytes on a single line
#define QSPI_ALTERNATE_BYTES_2_LINES ((uint32_t)QUADSPI_CCR_ABMODE_1) //!<Alternate bytes on two lines
#define QSPI_ALTERNATE_BYTES_4_LINES ((uint32_t)QUADSPI_CCR_ABMODE) //!<Alternate bytes on four lines*/
  uint32_t AlternateByteMode;  /* Specifies the Alternate Bytes Mode
                                  This parameter can be a value of @ref QSPI_AlternateBytesMode */

/*数据阶段需要几线模式:
#define QSPI_DATA_NONE ((uint32_t)0X00000000) //!<No data
#define QSPI_DATA_1_LINE ((uint32_t)QUADSPI_CCR_DMODE_0) //!<Data on a single line
#define QSPI_DATA_2_LINES ((uint32_t)QUADSPI_CCR_DMODE_1) //!<Data on two lines
#define QSPI_DATA_4_LINES ((uint32_t)QUADSPI_CCR_DMODE) //!<Data on four lines*/
  uint32_t DataMode;           /* Specifies the Data Mode (used for dummy cycles and data phases)
                                  This parameter can be a value of @ref QSPI_DataMode */

/*要传输的数据大小,参数范围 0 到 0xFFFFFFFF,如果设置为 0 表示不定长,直到存储器末尾*/
  uint32_t NbData;             /* Specifies the number of data to transfer. (This is the number of bytes)
                                  This parameter can be any value between 0 and 0xFFFFFFFF (0 means undefined length
                                  until end of memory)*/

/*用于设置是否使能 DDR 模式。数据阶段,交替字节阶段和数据传输阶段可以使用 DDR 模式。支持的
参数如下:
#define QSPI_DDR_MODE_DISABLE ((uint32_t)0x00000000) //!<Double data rate mode disabled
#define QSPI_DDR_MODE_ENABLE ((uint32_t)QUADSPI_CCR_DDRM) //!<Double data rate mode enabled*/
  uint32_t DdrMode;            /* Specifies the double data rate mode for address, alternate byte and data phase
                                  This parameter can be a value of @ref QSPI_DdrMode */

/*DDR 模式下,用于设置延迟半个时钟周期再做数据输出。
#define QSPI_DDR_HHC_ANALOG_DELAY ((uint32_t)0x00000000) 
#define QSPI_DDR_HHC_HALF_CLK_DELAY ((uint32_t)QUADSPI_CCR_DHHC)*/
  uint32_t DdrHoldHalfCycle;   /* Specifies if the DDR hold is enabled. When enabled it delays the data
                                  output by one half of system clock in DDR mode.
                                  This parameter can be a value of @ref QSPI_DdrHoldHalfCycle */

/*设置仅发送一次指令还是每次操作都发送指令,支持的参数如下:
#define QSPI_SIOO_INST_EVERY_CMD ((uint32_t)0x00000000) 
#define QSPI_SIOO_INST_ONLY_FIRST_CMD ((uint32_t)QUADSPI_CCR_SIOO)*/
  uint32_t SIOOMode;           /* Specifies the send instruction only once mode
                                  This parameter can be a value of @ref QSPI_SIOOMode */
}QSPI_CommandTypeDef;
2.2.1.3 自动查询结构体 QSPI_AutoPollingTypeDef
typedef struct
{
/*参数成员 Mask 屏蔽了状态寄存器的某些位后,状态寄存器的值与此参数成员值做匹配。参数范围 0x0 到 0xFFFFFFFF*/
  uint32_t Match;              /* Specifies the value to be compared with the masked status register to get a match.
                                  This parameter can be any value between 0 and 0xFFFFFFFF */

/*用于设置屏蔽位,比如 Mask = 0x01,表示仅保留 bit0 的数值,其它 bit 忽略。参数范围 0x0 到0xFFFFFFFF。*/
  uint32_t Mask;               /* Specifies the mask to be applied to the status bytes received.
                                  This parameter can be any value between 0 and 0xFFFFFFFF */

/*指定自动轮询阶段两次读取之间的时钟周期数。参数范围 0 到 0xFFFF*/
  uint32_t Interval;           /* Specifies the number of clock cycles between two read during automatic polling phases.
                                  This parameter can be any value between 0 and 0xFFFF */

/*用于设置状态寄存器大小,参数范围 1 到 4 个字节*/
  uint32_t StatusBytesSize;    /* Specifies the size of the status bytes received.
                                  This parameter can be any value between 1 and 4 */

/*参数成员 Mask 屏蔽了状态寄存器的某些位后,状态寄存器完全与参数成员 Match 一样(与操作的
含义)或者任意一个 bit 的值与参数成员 Match 中一个 bit 的值一样(或操作的含义),比如 Mask = 0x01,Match=0x00,MatchMode=与操作,表示不断查询状态寄存器 bit0,等待其为 0。 MatchMode 支持的参数成员如下:
#define QSPI_MATCH_MODE_AND ((uint32_t)0x00000000) //!<AND match mode between unmasked bits
#define QSPI_MATCH_MODE_OR ((uint32_t)QUADSPI_CR_PMM) //!<OR match mode between unmasked bits
  uint32_t MatchMode;          /* Specifies the method used for determining a match.
                                  This parameter can be a value of @ref QSPI_MatchMode */

/*当与参数成员 Match 匹配时,自动停止检测*/
  uint32_t AutomaticStop;      /* Specifies if automatic polling is stopped after a match.
                                  This parameter can be a value of @ref QSPI_AutomaticStop */
}QSPI_AutoPollingTypeDef;
2.2.1.4 QSPI 内存映射结构体 QSPI_MemoryMappedTypeDef
typedef struct
{
 uint32_t TimeOutPeriod; 
 uint32_t TimeOutActivation; 
}QSPI_MemoryMappedTypeDef;

下面将结构体成员逐一做个说明:
 TimeOutPeriod
FIFO 满时,释放芯片选择之前要等待的时钟周期数。参数范围 0 到 0xFFFF。

 TimeOutActivation
指定是否启用超时计数器以释放芯片选择,支持的参数成员如下:
#define QSPI_TIMEOUT_COUNTER_DISABLE ((uint32_t)0x00000000)
#define QSPI_TIMEOUT_COUNTER_ENABLE ((uint32_t)QUADSPI_CR_TCEN)

2.3 驱动代码

驱动使用正点原子代码,特别注意,添加下面重定义QSPI的句柄
#define QSPI_Handler hqspi

bsp_qspi.c

#include "bsp_w25qxx.h"

#ifdef QSPI_1
#include "bsp_qspi.h"
#include "pub_delay.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32H7开发板
//QSPI驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2017/8/14
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

QSPI_HandleTypeDef QSPI_Handler;    //QSPI句柄
    
//QSPI初始化
u8 QSPI_Init(void)
{
	/*
    QSPI_Handler.Instance=QUADSPI;                          //QSPI
    QSPI_Handler.Init.ClockPrescaler=1;                     //QPSI分频比,W25Q256最大频率为104M,
                                                            //所以此处应该为2,QSPI频率就为200/(1+1)=100MHZ
    QSPI_Handler.Init.FifoThreshold=4;                      //FIFO阈值为4个字节
    QSPI_Handler.Init.SampleShifting=QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)
    QSPI_Handler.Init.FlashSize=POSITION_VAL(0X2000000)-1;  //SPI FLASH大小,W25Q256大小为32M字节
    QSPI_Handler.Init.ChipSelectHighTime=QSPI_CS_HIGH_TIME_5_CYCLE;//片选高电平时间为5个时钟(10*5=55ns),即手册里面的tSHSL参数
    QSPI_Handler.Init.ClockMode=QSPI_CLOCK_MODE_0;          //模式0
    QSPI_Handler.Init.FlashID=QSPI_FLASH_ID_1;              //第一片flash
    QSPI_Handler.Init.DualFlash=QSPI_DUALFLASH_DISABLE;     //禁止双闪存模式
    if(HAL_QSPI_Init(&QSPI_Handler)==HAL_OK) return 0;      //QSPI初始化成功
    else return 1;
	*/
	return 1;
}

/*
//QSPI底层驱动,引脚配置,时钟使能
//此函数会被HAL_QSPI_Init()调用
//hqspi:QSPI句柄
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_QSPI_CLK_ENABLE();        //使能QSPI时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();       //使能GPIOB时钟
    __HAL_RCC_GPIOF_CLK_ENABLE();       //使能GPIOF时钟
    
    //初始化PB6 片选信号
    GPIO_Initure.Pin=GPIO_PIN_6;
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;          //复用
    GPIO_Initure.Pull=GPIO_PULLUP;              
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;  //高速
    GPIO_Initure.Alternate=GPIO_AF10_QUADSPI;   //复用为QSPI
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    
    //PF8,9
    GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9;
    GPIO_Initure.Pull=GPIO_NOPULL;              
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;   //高速
    HAL_GPIO_Init(GPIOF,&GPIO_Initure);
    
    //PB2
    GPIO_Initure.Pin=GPIO_PIN_2;
    GPIO_Initure.Alternate=GPIO_AF9_QUADSPI;   //复用为QSPI
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    
    //PF6,7
    GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7;
    HAL_GPIO_Init(GPIOF,&GPIO_Initure);
}
*/

//QSPI发送命令
//instruction:要发送的指令
//address:发送到的目的地址
//dummyCycles:空指令周期数
//	instructionMode:指令模式;QSPI_INSTRUCTION_NONE,QSPI_INSTRUCTION_1_LINE,QSPI_INSTRUCTION_2_LINE,QSPI_INSTRUCTION_4_LINE  
//	addressMode:地址模式; QSPI_ADDRESS_NONE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_2_LINE,QSPI_ADDRESS_4_LINE
//	addressSize:地址长度;QSPI_ADDRESS_8_BITS,QSPI_ADDRESS_16_BITS,QSPI_ADDRESS_24_BITS,QSPI_ADDRESS_32_BITS
//	dataMode:数据模式; QSPI_DATA_NONE,QSPI_DATA_1_LINE,QSPI_DATA_2_LINE,QSPI_DATA_4_LINE

void QSPI_Send_CMD(u32 instruction,u32 address,u32 dummyCycles,u32 instructionMode,u32 addressMode,u32 addressSize,u32 dataMode)
{
    QSPI_CommandTypeDef Cmdhandler;
    
    Cmdhandler.Instruction=instruction;                 	//指令
    Cmdhandler.Address=address;                            	//地址
    Cmdhandler.DummyCycles=dummyCycles;                     //设置空指令周期数
    Cmdhandler.InstructionMode=instructionMode;				//指令模式
    Cmdhandler.AddressMode=addressMode;   					//地址模式
    Cmdhandler.AddressSize=addressSize;   					//地址长度
    Cmdhandler.DataMode=dataMode;             				//数据模式
    Cmdhandler.SIOOMode=QSPI_SIOO_INST_EVERY_CMD;       	//每次都发送指令
    Cmdhandler.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE; //无交替字节
    Cmdhandler.DdrMode=QSPI_DDR_MODE_DISABLE;           	//关闭DDR模式
    Cmdhandler.DdrHoldHalfCycle=QSPI_DDR_HHC_ANALOG_DELAY;

    HAL_QSPI_Command(&QSPI_Handler,&Cmdhandler,5000);
}

#endif

#ifdef QSPI_2
#include "quadspi.h"
#include "pub_delay.h"
#include "bsp_qspi.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32H7开发板
//QSPI驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2017/8/14
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

QSPI_HandleTypeDef QSPI_Handler;    //QSPI句柄
#define  QSPI_Handler  hqspi

//QSPI初始化
u8 QSPI_Init(void)
{
	/*
    QSPI_Handler.Instance=QUADSPI;                          //QSPI
    QSPI_Handler.Init.ClockPrescaler=19;//1;                     //QPSI分频比,W25Q256最大频率为104M,
                                                            //所以此处应该为2,QSPI频率就为200/(1+1)=100MHZ
    QSPI_Handler.Init.FifoThreshold=4;                      //FIFO阈值为4个字节
    QSPI_Handler.Init.SampleShifting=QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)
    QSPI_Handler.Init.FlashSize=POSITION_VAL(0X2000000)-1;  //SPI FLASH大小,W25Q256大小为32M字节
    QSPI_Handler.Init.ChipSelectHighTime=QSPI_CS_HIGH_TIME_5_CYCLE;//片选高电平时间为5个时钟(10*5=55ns),即手册里面的tSHSL参数
    QSPI_Handler.Init.ClockMode=QSPI_CLOCK_MODE_0;          //模式0
    QSPI_Handler.Init.FlashID=QSPI_FLASH_ID_1;              //第一片flash
    QSPI_Handler.Init.DualFlash=QSPI_DUALFLASH_DISABLE;     //禁止双闪存模式
    if(HAL_QSPI_Init(&QSPI_Handler)==HAL_OK) return 0;      //QSPI初始化成功
    else return 1;
	*/
	return 1;
}


//QSPI底层驱动,引脚配置,时钟使能
//此函数会被HAL_QSPI_Init()调用
//hqspi:QSPI句柄
/*
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi)
{
    GPIO_InitTypeDef GPIO_Initure;
    
    __HAL_RCC_QSPI_CLK_ENABLE();        //使能QSPI时钟
    __HAL_RCC_GPIOB_CLK_ENABLE();       //使能GPIOB时钟
    __HAL_RCC_GPIOF_CLK_ENABLE();       //使能GPIOF时钟
    
    //初始化PB6 片选信号
    GPIO_Initure.Pin=GPIO_PIN_6;
    GPIO_Initure.Mode=GPIO_MODE_AF_PP;          //复用
    GPIO_Initure.Pull=GPIO_PULLUP;              
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;  //高速
    GPIO_Initure.Alternate=GPIO_AF10_QUADSPI;   //复用为QSPI
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    
    //PF8,9
    GPIO_Initure.Pin=GPIO_PIN_8|GPIO_PIN_9;
    GPIO_Initure.Pull=GPIO_NOPULL;              
    GPIO_Initure.Speed=GPIO_SPEED_FREQ_VERY_HIGH;   //高速
    HAL_GPIO_Init(GPIOF,&GPIO_Initure);
    
    //PB2
    GPIO_Initure.Pin=GPIO_PIN_2;
    GPIO_Initure.Alternate=GPIO_AF9_QUADSPI;   //复用为QSPI
    HAL_GPIO_Init(GPIOB,&GPIO_Initure);
    
    //PF6,7
    GPIO_Initure.Pin=GPIO_PIN_6|GPIO_PIN_7;
    HAL_GPIO_Init(GPIOF,&GPIO_Initure);
}
*/
//QSPI发送命令
//instruction:要发送的指令
//address:发送到的目的地址
//dummyCycles:空指令周期数
//	instructionMode:指令模式;QSPI_INSTRUCTION_NONE,QSPI_INSTRUCTION_1_LINE,QSPI_INSTRUCTION_2_LINE,QSPI_INSTRUCTION_4_LINE  
//	addressMode:地址模式; QSPI_ADDRESS_NONE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_2_LINE,QSPI_ADDRESS_4_LINE
//	addressSize:地址长度;QSPI_ADDRESS_8_BITS,QSPI_ADDRESS_16_BITS,QSPI_ADDRESS_24_BITS,QSPI_ADDRESS_32_BITS
//	dataMode:数据模式; QSPI_DATA_NONE,QSPI_DATA_1_LINE,QSPI_DATA_2_LINE,QSPI_DATA_4_LINE

void QSPI_Send_CMD(u32 instruction,u32 address,u32 dummyCycles,u32 instructionMode,u32 addressMode,u32 addressSize,u32 dataMode)
{
    QSPI_CommandTypeDef Cmdhandler;
    
    Cmdhandler.Instruction=instruction;                 	//指令
    Cmdhandler.Address=address;                            	//地址
    Cmdhandler.DummyCycles=dummyCycles;                     //设置空指令周期数
    Cmdhandler.InstructionMode=instructionMode;				//指令模式
    Cmdhandler.AddressMode=addressMode;   					//地址模式
    Cmdhandler.AddressSize=addressSize;   					//地址长度
    Cmdhandler.DataMode=dataMode;             				//数据模式
    Cmdhandler.SIOOMode=QSPI_SIOO_INST_EVERY_CMD;       	//每次都发送指令
    Cmdhandler.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE; //无交替字节
    Cmdhandler.DdrMode=QSPI_DDR_MODE_DISABLE;           	//关闭DDR模式
    Cmdhandler.DdrHoldHalfCycle=QSPI_DDR_HHC_ANALOG_DELAY;

    HAL_QSPI_Command(&QSPI_Handler,&Cmdhandler,5000);
}

//QSPI接收指定长度的数据
//buf:接收数据缓冲区首地址
//datalen:要传输的数据长度
//返回值:0,正常
//    其他,错误代码
u8 QSPI_Receive(u8* buf,u32 datalen)
{
    QSPI_Handler.Instance->DLR=datalen-1;                           //配置数据长度
    if(HAL_QSPI_Receive(&QSPI_Handler,buf,5000)==HAL_OK) return 0;  //接收数据
    else return 1;
}

//QSPI发送指定长度的数据
//buf:发送数据缓冲区首地址
//datalen:要传输的数据长度
//返回值:0,正常
//    其他,错误代码
u8 QSPI_Transmit(u8* buf,u32 datalen)
{
    QSPI_Handler.Instance->DLR=datalen-1;                            //配置数据长度
    if(HAL_QSPI_Transmit(&QSPI_Handler,buf,5000)==HAL_OK) return 0;  //发送数据
    else return 1;
}

#endif




bsp_qspi.h

#ifndef __QSPI_H
#define __QSPI_H

#include "bsp_w25qxx.h"

#ifdef QSPI_1
#include "sys.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32H7开发板
//QSPI驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2017/8/14
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	
extern QSPI_HandleTypeDef QSPI_Handler;    //QSPI句柄

u8 QSPI_Init(void);												//初始化QSPI
void QSPI_Send_CMD(u32 instruction,u32 address,u32 dummyCycles,u32 instructionMode,u32 addressMode,u32 addressSize,u32 dataMode);			//QSPI发送命令
//u8 QSPI_Receive(u8* buf,u32 datalen);							//QSPI接收数据
//u8 QSPI_Transmit(u8* buf,u32 datalen);							//QSPI发送数据

#endif

#ifdef QSPI_2
#include "sys.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32H7开发板
//QSPI驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2017/8/14
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	
extern QSPI_HandleTypeDef QSPI_Handler;    //QSPI句柄

u8 QSPI_Init(void);												//初始化QSPI
void QSPI_Send_CMD(u32 instruction,u32 address,u32 dummyCycles,u32 instructionMode,u32 addressMode,u32 addressSize,u32 dataMode);			//QSPI发送命令
u8 QSPI_Receive(u8* buf,u32 datalen);							//QSPI接收数据
u8 QSPI_Transmit(u8* buf,u32 datalen);							//QSPI发送数据
#endif

#endif

bsp_w25qxx.c


/*************************
  hqspi.Instance = QUADSPI;
  hqspi.Init.ClockPrescaler = 2-1; //QPSI 分频比,W25Q256 最大频率为 104M,
																	 //所以此处应该为 2,QSPI 频率就为 200/(1+1)=100MHZ
  hqspi.Init.FifoThreshold = 4;
  hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  hqspi.Init.FlashSize = 25-1;     //2^(fsize+1)=Fcap
  hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_5_CYCLE; //5*10nS=50nS
  hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
  hqspi.Init.FlashID = QSPI_FLASH_ID_1;
  hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
************************/
#include "bsp_w25qxx.h"

#ifdef QSPI_1
#include "main.h"
extern QSPI_HandleTypeDef hqspi;

#define W25Q_RESET()             QSPI_SendCmd(RESET_ENABLE_CMD,QSPI_INSTRUCTION_1_LINE,0,0,0,0);\
                                 QSPI_SendCmd(RESET_MEMORY_CMD,QSPI_INSTRUCTION_1_LINE,0,0,0,0)
#define W25QXX_ENTER_QSPI()      QSPI_SendCmd(W25X_EnterQPIMode,QSPI_INSTRUCTION_1_LINE,0,0,0,0)
#define W25Q_WRITE_ENABLE()      QSPI_SendCmd(W25X_WriteEnable,QSPI_INSTRUCTION_4_LINES,0,0,0,0)
#define W25QXX_ENTER_4BYTEADDR() QSPI_SendCmd(W25X_Enable4ByteAddr,QSPI_INSTRUCTION_4_LINES,0,0,0,0)


/***************************************************************************************
  * @brief   发送指令
  * @input   
  * @return  
***************************************************************************************/
void QSPI_SendCmd(uint32_t cmd,uint32_t cmdMode,uint32_t addr,uint32_t addrMode,uint32_t addrSize,uint32_t dataMode)
{
    QSPI_CommandTypeDef s_command = {0};
    
    s_command.Instruction     = cmd;          //指令
    s_command.InstructionMode = cmdMode;	  //指令模式
    s_command.Address         = addr;         //地址
    s_command.AddressMode     = addrMode;     //地址模式
    s_command.AddressSize     = addrSize;     //地址长度
    s_command.DataMode        = dataMode;     //数据模式
    
    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler();
    }
}


/***************************************************************************************
  * @brief   QSPI接收指定长度的数据
  * @input   buf:接收数据缓冲区首地址
             len:要传输的数据长度
  * @return  
***************************************************************************************/
void QSPI_Receive(uint8_t* buf,uint32_t len)
{
    hqspi.Instance->DLR = len - 1;                 //配置数据长度
    if(HAL_QSPI_Receive(&hqspi, buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler();
    }
}


/***************************************************************************************
  * @brief   QSPI发送指定长度的数据
  * @input   buf:发送数据缓冲区首地址
             len:要传输的数据长度
  * @return  
***************************************************************************************/
void QSPI_Transmit(uint8_t* buf,uint32_t len)
{
    hqspi.Instance->DLR = len - 1;                            //配置数据长度
    if(HAL_QSPI_Transmit(&hqspi, buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler();
    }
}


/***************************************************************************************
  * @brief   W25QXX进入QSPI模式
  * @input
  * @return
***************************************************************************************/
void W25QXX_SetQE(void)
{
    uint8_t value = 0x02;
    /* 1.写使能 */
    QSPI_SendCmd(W25X_WriteEnable,QSPI_INSTRUCTION_1_LINE,0,0,0,0);
    /* 2.发送写状态寄存器2命令 */
    QSPI_SendCmd(W25X_WriteStatusReg2,QSPI_INSTRUCTION_1_LINE,0,0,0,QSPI_DATA_1_LINE);
    /* 3.发送数据 */
    QSPI_Transmit(&value, 1);
}


/***************************************************************************************
  * @brief   0XEF18,表示芯片型号为W25Q256
  * @input
  * @return
***************************************************************************************/
uint16_t W25QXX_ReadId(void)
{
    uint8_t pData[2];

    QSPI_SendCmd(W25X_ManufactDeviceID,QSPI_INSTRUCTION_4_LINES,
                 0,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_24_BITS,QSPI_DATA_4_LINES);
    QSPI_Receive(pData,2);

    return pData[1] | ( pData[0] << 8 );
}

/***************************************************************************************
  * @brief   设置读参数
  * @input   
  * @return  
***************************************************************************************/
void W25QXX_SetReadParam(void)
{
    uint8_t para = 3 << 4;设置P4&P5=11,8个dummy clocks,104M
    
    /* 1.发送命令 */
    QSPI_SendCmd(W25X_SetReadParam,QSPI_INSTRUCTION_4_LINES,0,0,0,QSPI_DATA_4_LINES);
    /* 2.发送数据 */
    QSPI_Transmit(&para, 1);
}


/***************************************************************************************
  * @brief  等待空闲
  * @input   
  * @return  
***************************************************************************************/
void W25QXX_WaitIdle(void)   
{
    uint8_t sta_reg1 = 0x00;
    do{
        QSPI_SendCmd(W25X_ReadStatusReg1,QSPI_INSTRUCTION_4_LINES,0,0,0,QSPI_DATA_4_LINES);
        QSPI_Receive(&sta_reg1,1);
    }while( (sta_reg1&0x01) == 0x01 );
}

/***************************************************************************************
  * @brief   擦除QSPI某个扇区
  * @input   
  * @return  
***************************************************************************************/
void W25QXX_EraseSector(uint32_t sector_id)
{
    uint32_t addr = sector_id * 4096;
	
    W25Q_WRITE_ENABLE();
    W25QXX_WaitIdle();
    QSPI_SendCmd(W25X_SectorErase,QSPI_INSTRUCTION_4_LINES,addr,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,0);
    W25QXX_WaitIdle();
}


/***************************************************************************************
  * @brief   读取SPI FLASH,仅支持QPI模式
  * @input   pBuffer:数据存储区
             ReadAddr:开始读取的地址(最大32bit)
             NumByteToRead:要读取的字节数(最大65535)
  * @return
***************************************************************************************/
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
    QSPI_CommandTypeDef s_command = {0};
    s_command.Instruction       = W25X_FastReadData;
    s_command.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
    s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
    s_command.AddressSize       = QSPI_ADDRESS_32_BITS;
    s_command.DataMode          = QSPI_DATA_4_LINES;
    s_command.DummyCycles       = 8;
    s_command.NbData            = NumByteToRead;
    s_command.Address           = ReadAddr;
    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler();
    }
    if (HAL_QSPI_Receive(&hqspi, pBuffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        Error_Handler();
    }
}




/***************************************************************************************
  * @brief   SPI在一页(0~65535)内写入少于256个字节的数据
  * @input   pBuffer:数据存储区
             WriteAddr:开始写入的地址(最大32bit)
             NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
  * @return  
***************************************************************************************/
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
	W25Q_WRITE_ENABLE();				//写使能
	W25QXX_WaitIdle();
	QSPI_SendCmd(W25X_PageProgram,QSPI_INSTRUCTION_4_LINES,
								WriteAddr,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,QSPI_DATA_4_LINES);
	QSPI_Transmit(pBuffer, NumByteToWrite);	         	      
	W25QXX_WaitIdle();					//等待写入结束
}

/***************************************************************************************
  * @brief   无检验写SPI FLASH ,具有自动换页功能 
  * @input   pBuffer:数据存储区
             WriteAddr:开始写入的地址(最大32bit)
             NumByteToWrite:要写入的字节数(最大65535)
  * @return  
***************************************************************************************/
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)   
{
	uint16_t page_remain = 256 - WriteAddr % 256; //单页剩余的字节数		
    
	if(NumByteToWrite <= page_remain)
	{
		page_remain = NumByteToWrite;//不大于256个字节
	}
    
	while(1)
	{
		W25QXX_Write_Page(pBuffer, WriteAddr, page_remain);
        
		if(NumByteToWrite == page_remain)
		{
			break;//写入结束了
	 	} 
		else 
		{
			pBuffer += page_remain;
			WriteAddr += page_remain;	

			NumByteToWrite -= page_remain;//减去已经写入了的字节数
			if(NumByteToWrite > 256)
				page_remain = 256;        //一次可以写入256个字节
			else 
				page_remain = NumByteToWrite; //不够256个字节了
		}
	}
}

/***************************************************************************************
  * @brief   写SPI FLASH  
  * @input   pBuffer:数据存储区
             WriteAddr:开始写入的地址(最大32bit)	
             NumByteToWrite:要写入的字节数(最大65535)   
  * @return  
***************************************************************************************/
uint8_t W25QXX_Buffer[4096];		 
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)   
{
	uint32_t sec_id;
	uint16_t sec_offset;
	uint16_t sec_remain;	   
 	uint16_t i;    
	uint8_t * W25Q_Buf = W25QXX_Buffer;	  
    
 	sec_id     = WriteAddr / 4096;//扇区地址  
	sec_offset = WriteAddr % 4096;//在扇区内的偏移
	sec_remain = 4096 - sec_offset;//扇区剩余空间大小   
    
 	if(NumByteToWrite <= sec_remain)//
    sec_remain = NumByteToWrite;//不大于4096个字节
    
	while(1) 
	{
		W25QXX_Read(W25Q_Buf, sec_id*4096, 4096);//读出整个扇区的内容
		for(i=0; i<sec_remain; i++)//校验数据
		{
			if(W25Q_Buf[sec_offset+i] != 0xFF)
        break;//需要擦除  	  
		}
        
		if(i < sec_remain)
	  {//需要擦除
			W25QXX_EraseSector(sec_id);//擦除这个扇区
			for(i=0;i<sec_remain;i++)
			{	   //复制
				W25Q_Buf[i+sec_offset] = pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25Q_Buf, sec_id*4096, 4096);//写入整个扇区  
		} 
		else 
		{
			W25QXX_Write_NoCheck(pBuffer,WriteAddr,sec_remain);//写已经擦除了的,直接写入扇区剩余区间. 	
    }
        
		if(NumByteToWrite == sec_remain)
		{
			break;       //写入结束了
		} 
		else 
		{         //写入未结束
		sec_id++;    //扇区地址增1
		sec_offset = 0;//偏移位置为0 	 

		pBuffer += sec_remain;       //指针偏移
		WriteAddr += sec_remain;     //写地址偏移	   
		NumByteToWrite -= sec_remain;//字节数递减
		if(NumByteToWrite > 4096)
			sec_remain = 4096;	     //下一个扇区还是写不完
		else 
			sec_remain = NumByteToWrite;//下一个扇区可以写完了
		}
	}
}

/**
* @brief Configure the QSPI in memory-mapped mode
* @param None 
* @retval QSPI memory status
*/
void W25QXX_MemoryMappedMode(void)
{
  QSPI_CommandTypeDef s_command;
  QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;

  /* Configure the command for the read instruction */
  s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;
  s_command.Instruction = W25X_FastReadData;
  s_command.AddressMode = QSPI_ADDRESS_4_LINES;
  s_command.AddressSize = QSPI_ADDRESS_32_BITS;
  s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  s_command.DataMode = QSPI_DATA_4_LINES;
  s_command.DummyCycles = 8;
  s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
  s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

  /* Configure the memory mapped mode */
  s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
  s_mem_mapped_cfg.TimeOutPeriod = 0; //1;

  if (HAL_QSPI_MemoryMapped(&hqspi, &s_command, &s_mem_mapped_cfg) != HAL_OK)
  {
    Error_Handler();
  }
}


uint16_t W25_ID;
uint8_t TEXT_Buffer[] = {"abcdefghijklmnopqrstuvwxyz"};
//uint16_t test_buf[10] __attribute__((at(0x90000000)));
uint8_t W25_RxBuf[50] = {0};
#define SIZE sizeof(TEXT_Buffer)
/***************************************************************************************
  * @brief   W25Q256初始化
  * @input
  * @return
***************************************************************************************/
void W25QXX_Init(void)
{
    W25QXX_SetQE();
		//delay_ms(20);
	  HAL_Delay(20);
    W25QXX_ENTER_QSPI();
    W25QXX_ENTER_4BYTEADDR();
    W25QXX_SetReadParam();//设置读参数
//    W25QXX_MemoryMappedMode();
//    test_buf[0] = 0xAA;
    
    W25QXX_Write(TEXT_Buffer,W25Q256_SIZE - 100, SIZE);
    W25QXX_Read(W25_RxBuf, W25Q256_SIZE - 100, SIZE);
	
		W25_ID = W25QXX_ReadId();
}
#endif

#ifdef QSPI_2

#include "quadspi.h"
#include "pub_delay.h"
#include "usart.h" 
#include "bsp_qspi.h"
#include "bsp_w25qxx.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板 
//W25QXX QPI模式驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/7/18
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

u16 W25QXX_TYPE=W25Q256;	//默认是W25Q256
u8 W25QXX_QPI_MODE=0;		//QSPI模式标志:0,SPI模式;1,QPI模式.

//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q256
//容量为32M字节,共有512个Block,8192个Sector 
													 
//初始化SPI FLASH的IO口
void W25QXX_Init(void)
{ 
    u8 temp;    
	QSPI_Init();					//初始化QSPI
 	W25QXX_Qspi_Enable();			//使能QSPI模式
	W25QXX_TYPE=W25QXX_ReadId();	//读取FLASH ID.
	//printf("ID:%x\r\n",W25QXX_TYPE);
	if(W25QXX_TYPE==W25Q256)        //SPI FLASH为W25Q256
    {
        temp=W25QXX_ReadSR(3);      //读取状态寄存器3,判断地址模式
        if((temp&0X01)==0)			//如果不是4字节地址模式,则进入4字节地址模式
		{ 
			W25QXX_Write_Enable();	//写使能
			QSPI_Send_CMD(W25X_Enable4ByteAddr,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);//QPI,使能4字节地址指令,地址为0,无数据_8位地址_无地址_4线传输指令,无空周期,0个字节数据 
		}
		W25QXX_Write_Enable();		//写使能
		QSPI_Send_CMD(W25X_SetReadParam,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_4_LINES); 		//QPI,设置读参数指令,地址为0,4线传数据_8位地址_无地址_4线传输指令,无空周期,1个字节数据
		temp=3<<4;					//设置P4&P5=11,8个dummy clocks,104M
		QSPI_Transmit(&temp,1);		//发送1个字节	   
    }
}  
//W25QXX进入QSPI模式 
void W25QXX_Qspi_Enable(void)
{
	u8 stareg2;
	stareg2=W25QXX_ReadSR(2);		//先读出状态寄存器2的原始值
	if((stareg2&0X02)==0)			//QE位未使能
	{
		W25QXX_Write_Enable();		//写使能 
		stareg2|=1<<1;				//使能QE位		
		W25QXX_Write_SR(2,stareg2);	//写状态寄存器2
	}
	QSPI_Send_CMD(W25X_EnterQPIMode,0,0,QSPI_INSTRUCTION_1_LINE,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);//写command指令,地址为0,无数据_8位地址_无地址_单线传输指令,无空周期,0个字节数据
	W25QXX_QPI_MODE=1;				//标记QSPI模式
}

//W25QXX退出QSPI模式 
void W25QXX_Qspi_Disable(void)
{ 
	QSPI_Send_CMD(W25X_ExitQPIMode,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);//写command指令,地址为0,无数据_8位地址_无地址_4线传输指令,无空周期,0个字节数据
	W25QXX_QPI_MODE=0;				//标记SPI模式
}

//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
//状态寄存器1:
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
//状态寄存器2:
//BIT7  6   5   4   3   2   1   0
//SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
//状态寄存器3:
//BIT7      6    5    4   3   2   1   0
//HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
u8 W25QXX_ReadSR(u8 regno)   
{  
	u8 byte=0,command=0; 
    switch(regno)
    {
        case 1:
            command=W25X_ReadStatusReg1;    //读状态寄存器1指令
            break;
        case 2:
            command=W25X_ReadStatusReg2;    //读状态寄存器2指令
            break;
        case 3:
            command=W25X_ReadStatusReg3;    //读状态寄存器3指令
            break;
        default:
            command=W25X_ReadStatusReg1;    
            break;
    }   
	if(W25QXX_QPI_MODE)QSPI_Send_CMD(command,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_4_LINES);	//QPI,写command指令,地址为0,4线传数据_8位地址_无地址_4线传输指令,无空周期,1个字节数据
	else QSPI_Send_CMD(command,0,0,QSPI_INSTRUCTION_1_LINE,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_1_LINE);				//SPI,写command指令,地址为0,单线传数据_8位地址_无地址_单线传输指令,无空周期,1个字节数据
	QSPI_Receive(&byte,1);	        
	return byte;   
}   

//写W25QXX状态寄存器
void W25QXX_Write_SR(u8 regno,u8 sr)   
{   
    u8 command=0;
    switch(regno)
    {
        case 1:
            command=W25X_WriteStatusReg1;    //写状态寄存器1指令
            break;
        case 2:
            command=W25X_WriteStatusReg2;    //写状态寄存器2指令
            break;
        case 3:
            command=W25X_WriteStatusReg3;    //写状态寄存器3指令
            break;
        default:
            command=W25X_WriteStatusReg1;    
            break;
    }   
	if(W25QXX_QPI_MODE)
		QSPI_Send_CMD(command,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_4_LINES);	//QPI,写command指令,地址为0,4线传数据_8位地址_无地址_4线传输指令,无空周期,1个字节数据
	else 
		QSPI_Send_CMD(command,0,0, QSPI_INSTRUCTION_1_LINE,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_1_LINE);		//SPI,写command指令,地址为0,单线传数据_8位地址_无地址_单线传输指令,无空周期,1个字节数据
	QSPI_Transmit(&sr,1);	         	      
}  

//W25QXX写使能	
//将S1寄存器的WEL置位   
void W25QXX_Write_Enable(void)   
{
	if(W25QXX_QPI_MODE)QSPI_Send_CMD(W25X_WriteEnable,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);	//QPI,写使能指令,地址为0,无数据_8位地址_无地址_4线传输指令,无空周期,0个字节数据
	else QSPI_Send_CMD(W25X_WriteEnable,0,0,QSPI_INSTRUCTION_1_LINE,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);				//SPI,写使能指令,地址为0,无数据_8位地址_无地址_单线传输指令,无空周期,0个字节数据
} 

//W25QXX写禁止	
//将WEL清零  
void W25QXX_Write_Disable(void)   
{  
	if(W25QXX_QPI_MODE)QSPI_Send_CMD(W25X_WriteDisable,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);//QPI,写禁止指令,地址为0,无数据_8位地址_无地址_4线传输指令,无空周期,0个字节数据
	else QSPI_Send_CMD(W25X_WriteDisable,0,0,QSPI_INSTRUCTION_1_LINE,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);				//SPI,写禁止指令,地址为0,无数据_8位地址_无地址_单线传输指令,无空周期,0个字节数据 
} 

//返回值如下:				   
//0XEF13,表示芯片型号为W25Q80  
//0XEF14,表示芯片型号为W25Q16    
//0XEF15,表示芯片型号为W25Q32  
//0XEF16,表示芯片型号为W25Q64 
//0XEF17,表示芯片型号为W25Q128 	  
//0XEF18,表示芯片型号为W25Q256
u16 W25QXX_ReadId(void)
{
	u8 temp[2];
	u16 deviceid;
	if(W25QXX_QPI_MODE)
		QSPI_Send_CMD(W25X_ManufactDeviceID,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_24_BITS,QSPI_DATA_4_LINES);//QPI,读id,地址为0,4线传输数据_24位地址_4线传输地址_4线传输指令,无空周期,2个字节数据
	else 
		QSPI_Send_CMD(W25X_ManufactDeviceID,0,0,QSPI_INSTRUCTION_1_LINE,QSPI_ADDRESS_1_LINE,QSPI_ADDRESS_24_BITS,QSPI_DATA_1_LINE);			//SPI,读id,地址为0,单线传输数据_24位地址_单线传输地址_单线传输指令,无空周期,2个字节数据
	QSPI_Receive(temp,2);
	deviceid=(temp[0]<<8)|temp[1];
	return deviceid;
}    

//读取SPI FLASH,仅支持QPI模式  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(最大32bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
	QSPI_Send_CMD(W25X_FastReadData,ReadAddr,8,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,QSPI_DATA_4_LINES);	//QPI,快速读数据,地址为ReadAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,8空周期,NumByteToRead个数据
	QSPI_Receive(pBuffer,NumByteToRead); 
}  

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(最大32bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
	W25QXX_Write_Enable();					//写使能
	QSPI_Send_CMD(W25X_PageProgram,WriteAddr,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,QSPI_DATA_4_LINES);	//QPI,页写指令,地址为WriteAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,无空周期,NumByteToWrite个数据
	QSPI_Transmit(pBuffer,NumByteToWrite);	         	      
	W25QXX_Wait_Busy();					   //等待写入结束
} 

//无检验写SPI FLASH 
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能 
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(最大32bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 			 		 
	u16 pageremain;	   
	pageremain=256-WriteAddr%256; //单页剩余的字节数		 	    
	if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
	while(1)
	{	   
		W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
		if(NumByteToWrite==pageremain)break;//写入结束了
	 	else //NumByteToWrite>pageremain
		{
			pBuffer+=pageremain;
			WriteAddr+=pageremain;	

			NumByteToWrite-=pageremain;			  //减去已经写入了的字节数
			if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
			else pageremain=NumByteToWrite; 	  //不够256个字节了
		}
	}   
} 

//写SPI FLASH  
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(最大32bit)						
//NumByteToWrite:要写入的字节数(最大65535)   
u8 W25QXX_BUFFER[4096];		 
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)   
{ 
	u32 secpos;
	u16 secoff;
	u16 secremain;	   
 	u16 i;    
	u8 * W25QXX_BUF;	  
   	W25QXX_BUF=W25QXX_BUFFER;	     
 	secpos=WriteAddr/4096;//扇区地址  
	secoff=WriteAddr%4096;//在扇区内的偏移
	secremain=4096-secoff;//扇区剩余空间大小   
 	//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用
 	if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
	while(1) 
	{	
		W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
		for(i=0;i<secremain;i++)//校验数据
		{
			if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除  	  
		}
		if(i<secremain)//需要擦除
		{
			W25QXX_Erase_Sector(secpos);//擦除这个扇区
			for(i=0;i<secremain;i++)	   //复制
			{
				W25QXX_BUF[i+secoff]=pBuffer[i];	  
			}
			W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区  

		}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
		if(NumByteToWrite==secremain)break;//写入结束了
		else//写入未结束
		{
			secpos++;//扇区地址增1
			secoff=0;//偏移位置为0 	 

		   	pBuffer+=secremain;  //指针偏移
			WriteAddr+=secremain;//写地址偏移	   
		   	NumByteToWrite-=secremain;				//字节数递减
			if(NumByteToWrite>4096)secremain=4096;	//下一个扇区还是写不完
			else secremain=NumByteToWrite;			//下一个扇区可以写完了
		}	 
	};	 
}

//擦除整个芯片		  
//等待时间超长...
void W25QXX_Erase_Chip(void)   
{                                   
    W25QXX_Write_Enable();					//SET WEL 
    W25QXX_Wait_Busy();   
	QSPI_Send_CMD(W25X_ChipErase,0,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_NONE,QSPI_ADDRESS_8_BITS,QSPI_DATA_NONE);//QPI,写全片擦除指令,地址为0,无数据_8位地址_无地址_4线传输指令,无空周期,0个字节数据
	W25QXX_Wait_Busy();						//等待芯片擦除结束
} 

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)   
{  
	 
 	//printf("fe:%x\r\n",Dst_Addr);			//监视falsh擦除情况,测试用  	  
 	Dst_Addr*=4096;
    W25QXX_Write_Enable();                  //SET WEL 	 
    W25QXX_Wait_Busy();  
	QSPI_Send_CMD(W25X_SectorErase,Dst_Addr,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_32_BITS,QSPI_DATA_NONE);//QPI,写扇区擦除指令,地址为0,无数据_32位地址_4线传输地址_4线传输指令,无空周期,0个字节数据
    W25QXX_Wait_Busy();   				    //等待擦除完成
}

//等待空闲
void W25QXX_Wait_Busy(void)   
{   
	while((W25QXX_ReadSR(1)&0x01)==0x01);   // 等待BUSY位清空
}   

#endif





bsp_w25qxx.h

#ifndef __BSP_W25QXX_H
#define __BSP_W25QXX_H

//#define QSPI_1
#define QSPI_2

#ifdef QSPI_1
#include "stdint.h"

#define W25Q256_SIZE (32*1024*1024)

/* 复位操作 */
#define RESET_ENABLE_CMD                     0x66
#define RESET_MEMORY_CMD                     0x99
//指令表
#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_SetReadParam		0xC0 
#define W25X_EnterQPIMode       0x38
#define W25X_ExitQPIMode        0xFF

void W25QXX_Init(void);
void W25QXX_EraseSector(uint32_t sector_id);
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void W25QXX_Read(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint16_t W25QXX_ReadId(void);
#endif

#ifdef QSPI_2
#ifndef __W25QXX_H
#define __W25QXX_H
#include "sys.h"
//	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板 
//W25QXX QPI模式驱动代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//创建日期:2016/7/18
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2014-2024
//All rights reserved									  
// 	

//W25X系列/Q系列芯片列表	   
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16	
//W25Q128 ID  0XEF17	
//W25Q256 ID  0XEF18
#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17
#define W25Q256 0XEF18

extern u16 W25QXX_TYPE;					//定义W25QXX芯片型号		   
 
// 
//指令表
#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_SetReadParam		0xC0 
#define W25X_EnterQPIMode       0x38
#define W25X_ExitQPIMode        0xFF

void W25QXX_Init(void);					//初始化W25QXX
void W25QXX_Qspi_Enable(void);			//使能QSPI模式
void W25QXX_Qspi_Disable(void);			//关闭QSPI模式
u16  W25QXX_ReadId(void);  	    		//读取FLASH ID
u8 W25QXX_ReadSR(u8 regno);             //读取状态寄存器 
void W25QXX_4ByteAddr_Enable(void);     //使能4字节地址模式
void W25QXX_Write_SR(u8 regno,u8 sr);   //写状态寄存器
void W25QXX_Write_Enable(void);  		//写使能 
void W25QXX_Write_Disable(void);		//写保护
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写flash,不校验
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //读取flash
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);    	  	//整片擦除
void W25QXX_Erase_Sector(u32 Dst_Addr);	//扇区擦除
void W25QXX_Wait_Busy(void);           	//等待空闲
#endif

#endif

#endif

main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "quadspi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "bsp.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

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

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
 //要写入到W25Q16的字符串数组
u32 flash_size;
u32 test_Flash_ID;
const u8 Test_Buffer[]={"Apollo STM32H7 QSPI TEST"};
#define SIZE sizeof(Test_Buffer)
u8 datatemp[SIZE];	
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */

	flash_size=32*1024*1024;	//FLASH 大小为32M字节
  /* USER CODE END 1 */

  /* Enable I-Cache---------------------------------------------------------*/
  SCB_EnableICache();

  /* Enable D-Cache---------------------------------------------------------*/
  SCB_EnableDCache();

  /* 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();
  /* USER CODE BEGIN 2 */
	bsp_init();
	test_Flash_ID= W25QXX_ReadId();
	HAL_Delay(10);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
		LED0_TOGGLE;
		LED1_TOGGLE;
		HAL_Delay(200);
		W25QXX_Write((u8*)Test_Buffer,flash_size-100,SIZE);		//从倒数第100个地址处开始,写入SIZE长度的数据
		W25QXX_Read(datatemp,flash_size-100,SIZE);					//从倒数第100个地址处开始,读出SIZE个字节
	  __nop();
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {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)) {}
  /** Initializes the CPU, AHB and APB busses clocks 
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 2;
  RCC_OscInitStruct.PLL.PLLN = 64;
  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 busses 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_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_QSPI;
  PeriphClkInitStruct.QspiClockSelection = RCC_QSPICLKSOURCE_D1HCLK;
  PeriphClkInitStruct.Usart16ClockSelection = RCC_USART16CLKSOURCE_D2PCLK2;
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

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

  /* 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,
     tex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

bsp_led.c


/* 包含头文件 ----------------------------------------------------------------*/
#include "bsp_led.h"

/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
/* 私有变量 ------------------------------------------------------------------*/
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*/

/**
  * 函数功能: 板载LED灯IO引脚初始化.
  * 输入参数: 无
  * 返 回 值: 无
  * 说    明:使用宏定义方法代替具体引脚号,方便程序移植,只要简单修改bsp_led.h
  *           文件相关宏定义就可以方便修改引脚。
  */
void LED_GPIO_Init(void)
{
   /* 定义IO硬件初始化结构体变量 */ 
	;
}

/**
  * 函数功能: 设置板载LED灯的状态
  * 输入参数: LEDx:其中x可甚至为(1,2,3)用来选择对应的LED灯
  * 输入参数:state:设置LED灯的输出状态。
  *             可选值:LED_OFF:LED灯灭;
  *                     LED_ON: LED灯亮。
  *                     LED_TOGGLE:反转LED
  * 返 回 值: 无
  * 说    明:该函数使用类似标准库函数的编程方法,方便理解标准库函数编程思想。
  */
void LEDx_StateSet(uint8_t LEDx,LEDState_TypeDef state)
{
  /* 检查输入参数是否合法 */
  assert_param(IS_LED_TYPEDEF(LEDx));
  assert_param(IS_LED_STATE(state));
  
  /* 判断设置的LED灯状态,如果设置为LED灯灭 */
  if(state==LED_OFF)
  {
    if(LEDx & LED0)            
      LED0_OFF;/* LED1灭 */
    
    if(LEDx & LED1)
      LED1_OFF;/* LED2灭 */
    
    if(LEDx & LED2)
      LED2_OFF;/* LED3灭 */    
  }
  else if(state==LED_ON) /* 设置LED灯为亮 */
  {
    if(LEDx & LED0)
      LED0_ON;/* LED1亮 */
    
    if(LEDx & LED1)
      LED1_ON;/* LED2亮 */
    
    if(LEDx & LED2)
      LED2_ON;/* LED3亮 */ 
  }
  else
  {
    if(LEDx & LED0)
      LED0_TOGGLE;/* 设置引脚输出反转 */
    
    if(LEDx & LED1)
      LED1_TOGGLE;/* 设置引脚输出反转 */
    
    if(LEDx & LED2)
      LED2_TOGGLE;/* 设置引脚输出反转 */ 
  }
}

/******************* (C) COPYRIGHT 2015-2020 硬石嵌入式开发团队 *****END OF FILE****/

bsp_led.h

#ifndef __BSP_LED_H__
#define __BSP_LED_H__

/* 包含头文件 ----------------------------------------------------------------*/
#include "stm32h7xx_hal.h"
#include "main.h"
/* 类型定义 ------------------------------------------------------------------*/
typedef enum
{
  LED_OFF = 0,
  LED_ON  = 1,
  LED_TOGGLE = 2,
}LEDState_TypeDef;
#define IS_LED_STATE(STATE)           (((STATE) == LED_OFF) || ((STATE) == LED_ON) || ((STATE) == LED_TOGGLE))

/* 宏定义 --------------------------------------------------------------------*/
#define LED0                          (uint8_t)0x01
#define LED1                          (uint8_t)0x02
#define LED2                          (uint8_t)0x04
#define IS_LED_TYPEDEF(LED)           (((LED) == LED0) || ((LED) == LED1) || ((LED) == LED2))

/*
 * 以下宏定义内容跟开发板硬件息息相关,需要查看开发板电路原理图才能正确编写。
 * 例如,查原理图可在LED1灯接在stm32f407芯片的PH9引脚上,所以有关LED1的宏定义
 * 都是与GPIOH,GPIO_Pin_9相关的,我们专门把这些与开发板硬件相关的内容定义为宏,
 * 这对于修改或者移植程序非常方便。
 */

#define LED0_ON                       HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET)    // 输出高电平
#define LED0_OFF                      HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET)  // 输出低电平
#define LED0_TOGGLE                   HAL_GPIO_TogglePin(LED0_GPIO_Port,LED0_Pin)                // 输出反转

#define LED1_ON                       HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)    // 输出高电平
#define LED1_OFF                      HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET)  // 输出低电平
#define LED1_TOGGLE                   HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin)                // 输出反转

#define LED2_ON                       HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)    // 输出高电平
#define LED2_OFF                      HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET)  // 输出低电平
#define LED2_TOGGLE                   HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin)                // 输出反转

/* 扩展变量 ------------------------------------------------------------------*/
/* 函数声明 ------------------------------------------------------------------*/
void LED_GPIO_Init(void);
void LEDx_StateSet(uint8_t LEDx,LEDState_TypeDef state);

#endif  // __BSP_LED_H__



3、内存地址映射

在以下情况下使用内存映射模式:
 用于阅读操作。
 像使用内部 Flash 一样使用外部 QSPI Flash, 任何 AHB 总线主控都可以自主读取数据。
 用于从外部 QSPI Flash 执行代码。

QSPI 接口能够管理多达 256 MB 的内存,在内存映射模式下地址范围是 0x9000 0000 到 0x9FFF FFFF。

4、特别注意项( W25Q128与W25Q256有区别)

W25Q256FV与W25Q128JV读写地址长度不一样

W25Q256,32位地址
在这里插入图片描述
W25Q128,24位地址
在这里插入图片描述

如果是W25Q256 读写地址长度32位。
如果是W25Q128读写地址长度24位,修改地方如下(QSPI_ADDRESS_24_BITS):

//读取SPI FLASH,仅支持QPI模式  
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(最大32bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)   
{ 
	QSPI_Send_CMD(W25X_FastReadData,ReadAddr,8,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_24_BITS,QSPI_DATA_4_LINES);	// QSPI_ADDRESS_32_BITS QPI,快速读数据,地址为ReadAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,8空周期,NumByteToRead个数据
	QSPI_Receive(pBuffer,NumByteToRead); 
}  

//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(最大32bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!	 
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{
	W25QXX_Write_Enable();					//写使能
	QSPI_Send_CMD(W25X_PageProgram,WriteAddr,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_24_BITS,QSPI_DATA_4_LINES);	//QSPI_ADDRESS_32_BITS QPI,页写指令,地址为WriteAddr,4线传输数据_32位地址_4线传输地址_4线传输指令,无空周期,NumByteToWrite个数据
	QSPI_Transmit(pBuffer,NumByteToWrite);	         	      
	W25QXX_Wait_Busy();					   //等待写入结束
} 

//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)   
{  
	 
 	//printf("fe:%x\r\n",Dst_Addr);			//监视falsh擦除情况,测试用  	  
 	Dst_Addr*=4096;
    W25QXX_Write_Enable();                  //SET WEL 	 
    W25QXX_Wait_Busy();  
	QSPI_Send_CMD(W25X_SectorErase,Dst_Addr,0,QSPI_INSTRUCTION_4_LINES,QSPI_ADDRESS_4_LINES,QSPI_ADDRESS_24_BITS,QSPI_DATA_NONE);// QSPI_ADDRESS_32_BITS QPI,写扇区擦除指令,地址为0,无数据_32位地址_4线传输地址_4线传输指令,无空周期,0个字节数据
    W25QXX_Wait_Busy();   				    //等待擦除完成
}
  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值