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:
block0 | 00000000 --0000FFFFH | 64K |
---|---|---|
block1 | 00010000 --0001FFFFH | 64K |
block2 | 00020000 --0002FFFFH | 64K |
… | … | … |
block127 | 007F0000 --007FFFFFH | 64K |
block128 | 00800000 --0080FFFFH | 64K |
… | … | … |
block511 | 01FF0000 --01FFFFFFFH | 64K |
SIZE=512*64K(block)=32M
8192 Sector:
sector0 | 00000000 --00000FFFH | 4K |
---|---|---|
sector1 | 00001000 --00001FFFH | 4K |
sector2 | 00002000 --00002FFFH | 4K |
… | … | … |
sector14 | 0000E000 --0000EFFFH | 4K |
sector15 | 0000F000 --0000FFFFH | 4K |
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:
- 0x35 0x00 //读状态寄存器2指令
- 0x06 //W25X_WriteEnable
- 0x31 0x02 //写状态寄存器2
- 0x38 //W25X_EnterQPIMode 使能QSPI
QSPI : - 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(¶, 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>© 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(); //等待擦除完成
}