STM32F767 QUADSPI的基本用法

本文介绍STM32F767 QUADSPI的基本用法。

开发环境

硬件环境

  • 电脑:Windows 10 Home x64
  • Apollo STM32F767开发板(ST-LINK V2仿真器)

软件环境

  • Keil Version 5.24.1 (Pack Installer:Keil.STM32F7xx_DFP.2.9.0.pack)
  • STM32CubeMX Version 4.25.0(Packages Manager:STM32CubeF7)

QUADSPI功能简介

主要特性

  • 三种功能模式:间接模式、状态轮询模式和内存映射模式
  • 双闪存模式,通过并行访问两个 Flash,可同时发送/接收 8 位数据
  • 支持 SDR 和 DDR 模式
  • 针对间接模式和内存映射模式,完全可编程操作码
  • 针对间接模式和内存映射模式,完全可编程帧格式
  • 集成 FIFO,用于发送和接收
  • 允许 8、16 和 32 位数据访问
  • 具有适用于间接模式操作的 DMA 通道
  • 在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断

QUADSPI 命令序列

 QUADSPI 通过命令与 Flash 通信每条命令包括指令、地址、交替字节、空指令和数据这五个阶段 任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。
nCS 在每条指令开始前下降,在每条指令完成后再次上升。

指令阶段

 这一阶段,将在 QUADSPI_CCR[7:0] 寄存器的 INSTRUCTION 字段中配置的一条 8 位指令发送到 Flash,指定待执行操作的类型。
 尽管大多数 Flash 从 IO0/SO 信号(单线 SPI 模式)只能以一次 1 位的方式接收指令,但指令阶段可选择一次发送 2 位(在双线 SPI 模式中通过 IO0/IO1)或一次发送 4 位(在四线SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[9:8] 寄存器中的 IMODE[1:0]字段进行配置。
 若 IMODE = 00,则跳过指令阶段,命令序列从地址阶段(如果存在)开始。

地址阶段

 在地址阶段,将 1-4 字节发送到 Flash,指示操作地址。待发送的地址字节数在QUADSPI_CCR[13:12] 寄存器的 ADSIZE[1:0] 字段中进行配置。在间接模式和自动轮询模式下,待发送的地址字节在 QUADSPI_AR 寄存器的 ADDRESS[31:0] 中指定。在内存映射模式下,则通过 AHB(来自于 Cortex® 或 DMA)直接给出地址。
 地址阶段可一次发送 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过QUADSPI_CCR[11:10] 寄存器中的 ADMODE[1:0] 字段进行配置。
 若 ADMODE = 00,则跳过地址阶段,命令序列直接进入下一阶段(如果存在)。

交替字节阶段

 在交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。待发送的交替字节数在 QUADSPI_CCR[17:16] 寄存器的 ABSIZE[1:0] 字段中进行配置。待发送的字节在QUADSPI_ABR 寄存器中指定。
交替字节阶段可一次发送 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过QUADSPI_CCR[15:14] 寄存器中的 ABMODE[1:0] 字段进行配置。
 若 ABMODE = 00,则跳过交替字节阶段,命令序列直接进入下一阶段(如果存在)。

空指令周期阶段

 在空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给 Flash 留出准备数据阶段的时间。这一阶段中给定的周期数在QUADSPI_CCR[22:18] 寄存器的 DCYC[4:0] 字段中指定。在 SDR 和 DDR 模式下,持续时间被指定为一定个数的全时钟周期。
 若 DCYC 为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
 空指令周期阶段的操作模式由 DMODE 确定。
 为确保数据信号从输出模式转变为输入模式有足够的“周转”时间,使用双线和四线模式从Flash 接收数据时,至少需要指定一个空指令周期。

数据阶段

 在数据阶段,可从 Flash 接收或向其发送任意数量的字节。
 在间接模式和自动轮询模式下,待发送/接收的字节数在 QUADSPI_DLR 寄存器中指定。
 在间接写入模式下,发送到 Flash 的数据必须写入 QUADSPI_DR 寄存器。在间接读取模式下,通过读取 QUADSPI_DR 寄存器获得从 Flash 接收的数据。
 在内存映射模式下,读取的数据通过 AHB 直接发送回 Cortex 或 DMA。
 数据阶段可一次发送/ 接收 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过QUADSPI_CCR[15:14] 寄存器中的 ABMODE[1:0] 字段进行配置。
 若 DMODE = 00,则跳过数据阶段,命令序列在拉高 nCS 时立即完成。这一配置仅可用于仅间接写入模式。

QUADSPI Flash 配置

 设备配置寄存器 (QUADSPI_DCR) 可用于指定外部 SPI Flash 的特性。
 FSIZE[4:0] 字段使用下面的公式定义外部存储器的大小:
$$Flash 中的字节数 = 2^{FSIZE+1}$$
 FSIZE+1 是对 Flash 寻址所需的地址位数。在间接模式下,Flash 容量最高可达 4GB(使用32 位进行寻址),但在内存映射模式下的可寻址空间限制为 256MB。
 如果 DFM = 1,FSIZE 表示两个 Flash 容量的总和。
 QUADSPI 连续执行两条命令时,它在两条命令之间将片选信号 (nCS) 置为高电平默认仅一个 CLK 周期时长。如果 Flash 需要命令之间的时间更长,可使用片选高电平时间 (CSHT) 字段指定 nCS 必须保持高电平的最少 CLK 周期数(最大为 8)。
 时钟模式 (CKMODE) 位指示命令之间的 CLK 信号逻辑电平(nCS = 1 时)。

QUADSPI 延迟数据采样

 默认情况下,QUADSPI 在 Flash 驱动信号后过半个 CLK 周期才对 Flash 驱动的数据采样。
 在外部信号延迟时,这有利于推迟数据采样。使用 SSHIFT 位 (QUADSPI_CR[4]),可将数据采样移位半个 CLK 周期。
 DDR 模式下不支持时钟移位:若 DDRM 位置 1,SSHIFT 位必须清零。

使用QSPI读写W25Q256FV

 W25Q256为32MB串行flash存储,支持dual/quad与QPI模式。本例中将使用QSPI接口对W25Q256进行读写。

QSPI的初始化

 QSPI的初始化函数如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
QSPI_HandleTypeDef hqspi;
/* QUADSPI init function */
void MX_QUADSPI_Init(void)
{
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 2;
hqspi.Init.FifoThreshold = 4;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
hqspi.Init.FlashSize = QSPI_FLASH_SIZE - 1;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_4_CYCLE;
hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;
hqspi.Init.FlashID = QSPI_FLASH_ID_1;
hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
if (HAL_QSPI_Init(&hqspi) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
}
void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(qspiHandle->Instance==QUADSPI)
{
/* USER CODE BEGIN QUADSPI_MspInit 0 */
/* USER CODE END QUADSPI_MspInit 0 */
/* QUADSPI clock enable */
__HAL_RCC_QSPI_CLK_ENABLE();
/**QUADSPI GPIO Configuration
PF6 ------> QUADSPI_BK1_IO3
PF7 ------> QUADSPI_BK1_IO2
PF8 ------> QUADSPI_BK1_IO0
PF9 ------> QUADSPI_BK1_IO1
PB2 ------> QUADSPI_CLK
PB6 ------> QUADSPI_BK1_NCS
*/
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
HAL_GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* USER CODE BEGIN QUADSPI_MspInit 1 */
/* USER CODE END QUADSPI_MspInit 1 */
}
}
void HAL_QSPI_MspDeInit(QSPI_HandleTypeDef* qspiHandle)
{
if(qspiHandle->Instance==QUADSPI)
{
/* USER CODE BEGIN QUADSPI_MspDeInit 0 */
/* USER CODE END QUADSPI_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_QSPI_CLK_DISABLE();
/**QUADSPI GPIO Configuration
PF6 ------> QUADSPI_BK1_IO3
PF7 ------> QUADSPI_BK1_IO2
PF8 ------> QUADSPI_BK1_IO0
PF9 ------> QUADSPI_BK1_IO1
PB2 ------> QUADSPI_CLK
PB6 ------> QUADSPI_BK1_NCS
*/
HAL_GPIO_DeInit(GPIOF, GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9);
HAL_GPIO_DeInit(GPIOB, GPIO_PIN_2|GPIO_PIN_6);
/* USER CODE BEGIN QUADSPI_MspDeInit 1 */
/* USER CODE END QUADSPI_MspDeInit 1 */
}
}

 其主要功能为:

  • 查询W25Q256FV手册可知,其最大可支持的读写时钟频率为104MHz;QSPI使用AHB1时钟,AHB主时钟的频率为216MHz,设置分频系数为2,则QSPI时钟为72MHz;
  • 设置QSPI的FIFO深度为4byte;
  • 设置移位半个周期进行数据采样,有助于提高数据读取稳定性;
  • 设置flash容量参数为32MB,对应QSPI_FLASH_SIZE为25;
  • 查询W25Q256FV手册可知,CS片选信号的禁止时间至少为50ns;因此设置片选高电平时间为4个时钟周期;
  • 时钟模式为模式0,即片选信号CS释放后,CLK保持低电平;W25Q256FV对此无要求;
  • FlashID选择Flash ID 1;
  • 非双flash模式;
  • 初始化QSPI。

 初始化函数如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
HAL_StatusTypeDef HAL_QSPI_Init(QSPI_HandleTypeDef *hqspi)
{
HAL_StatusTypeDef status = HAL_ERROR;
uint32_t tickstart = HAL_GetTick();
/* Check the QSPI handle allocation */
if(hqspi == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_QSPI_ALL_INSTANCE(hqspi->Instance));
assert_param(IS_QSPI_CLOCK_PRESCALER(hqspi->Init.ClockPrescaler));
assert_param(IS_QSPI_FIFO_THRESHOLD(hqspi->Init.FifoThreshold));
assert_param(IS_QSPI_SSHIFT(hqspi->Init.SampleShifting));
assert_param(IS_QSPI_FLASH_SIZE(hqspi->Init.FlashSize));
assert_param(IS_QSPI_CS_HIGH_TIME(hqspi->Init.ChipSelectHighTime));
assert_param(IS_QSPI_CLOCK_MODE(hqspi->Init.ClockMode));
assert_param(IS_QSPI_DUAL_FLASH_MODE(hqspi->Init.DualFlash));
if (hqspi->Init.DualFlash != QSPI_DUALFLASH_ENABLE )
{
assert_param(IS_QSPI_FLASH_ID(hqspi->Init.FlashID));
}
/* Process locked */
__HAL_LOCK(hqspi);
if(hqspi->State == HAL_QSPI_STATE_RESET)
{
/* Allocate lock resource and initialize it */
hqspi->Lock = HAL_UNLOCKED;
/* Init the low level hardware : GPIO, CLOCK */
HAL_QSPI_MspInit(hqspi);
/* Configure the default timeout for the QSPI memory access */
HAL_QSPI_SetTimeout(hqspi, HAL_QPSI_TIMEOUT_DEFAULT_VALUE);
}
/* Configure QSPI FIFO Threshold */
MODIFY_REG(hqspi->Instance->CR, QUADSPI_CR_FTHRES, ((hqspi->Init.FifoThreshold - 1) << 8));
/* Wait till BUSY flag reset */
status = QSPI_WaitFlagStateUntilTimeout(hqspi, QSPI_FLAG_BUSY, RESET, tickstart, hqspi->Timeout);
if(status == HAL_OK)
{
/* Configure QSPI Clock Prescaler and Sample Shift */
MODIFY_REG(hqspi->Instance->CR,(QUADSPI_CR_PRESCALER | QUADSPI_CR_SSHIFT | QUADSPI_CR_FSEL | QUADSPI_CR_DFM), ((hqspi->Init.ClockPrescaler << 24)| hqspi->Init.SampleShifting | hqspi->Init.FlashID| hqspi->Init.DualFlash ));
/* Configure QSPI Flash Size, CS High Time and Clock Mode */
MODIFY_REG(hqspi->Instance->DCR, (QUADSPI_DCR_FSIZE | QUADSPI_DCR_CSHT | QUADSPI_DCR_CKMODE),
((hqspi->Init.FlashSize << 16) | hqspi->Init.ChipSelectHighTime | hqspi->Init.ClockMode));
/* Enable the QSPI peripheral */
__HAL_QSPI_ENABLE(hqspi);
/* Set QSPI error code to none */
hqspi->ErrorCode = HAL_QSPI_ERROR_NONE;
/* Initialize the QSPI state */
hqspi->State = HAL_QSPI_STATE_READY;
}
/* Release Lock */
__HAL_UNLOCK(hqspi);
/* Return function status */
return status;
}

 其具体配置通过QUADSPI 控制寄存器 (QUADSPI_CR)与QUADSPI 器件配置寄存器 (QUADSPI_CCR)实现,这里不再详细说明。

QSPI的指令与读写实现

 QSPI的指令发送函数如下所示。该函数的输入参数可以完整实现对指令阶段的控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* 说明: 使用QSPI发送Cmd
* 参数: insMode: 指令模式
* ins: 指令值
* addrMode:地址模式
* addr: 地址值
* addrSize: 地址值大小
* alterMode: 复用字节模式
* alter: 复用字节值
* altersize: 复用字节大小
* dummyCycles: 空周期时钟数
* dataMode: 数据模式
* @返回值 无
*/
QSPI_StatusTypeDef QSPI_SendCmd(uint32_t insMode, uint32 ins, uint32_t addrMode, uint32_t addr, uint32_t addrSize, uint32 alterMode, uint32 alter, uint32 altersize, uint32_t dummyCycles, uint32_t dataMode)
{
QSPI_CommandTypeDef qspiCmdhandler;
qspiCmdhandler.Instruction = ins;
qspiCmdhandler.Address = addr;
qspiCmdhandler.AlternateBytes = alter;
qspiCmdhandler.AddressSize=addrSize;
qspiCmdhandler.AlternateBytesSize = altersize;
qspiCmdhandler.DummyCycles = dummyCycles;
qspiCmdhandler.InstructionMode = insMode;
qspiCmdhandler.AddressMode = addrMode;
qspiCmdhandler.AlternateByteMode = alterMode;
qspiCmdhandler.DataMode=dataMode;
qspiCmdhandler.SIOOMode=QSPI_SIOO_INST_EVERY_CMD;
qspiCmdhandler.DdrMode=QSPI_DDR_MODE_DISABLE;
qspiCmdhandler.DdrHoldHalfCycle=QSPI_DDR_HHC_ANALOG_DELAY;
if (HAL_OK == HAL_QSPI_Command(&hqspi,&qspiCmdhandler,HAL_QPSI_TIMEOUT_DEFAULT_VALUE))
return QSPI_OK;
else
return QSPI_ERROR;
}

 QSPI的的读写函数实现如下,均利用了HAL库完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
QSPI_StatusTypeDef QSPI_Receive(uint8 *buf,uint32 datalen)
{
hqspi.Instance->DLR = datalen - 1;
if(HAL_OK == HAL_QSPI_Receive(&hqspi,buf,HAL_QPSI_TIMEOUT_DEFAULT_VALUE))
return QSPI_OK;
else
return QSPI_ERROR;
}
QSPI_StatusTypeDef QSPI_Transmit(uint8 *buf,uint32 datalen)
{
hqspi.Instance->DLR = datalen - 1;
if(HAL_OK == HAL_QSPI_Transmit(&hqspi,buf,HAL_QPSI_TIMEOUT_DEFAULT_VALUE))
return QSPI_OK;
else
return QSPI_ERROR;
}

W25Q256FV的初始化

操作框图

 W25Q256FV的操作框图如下所示。其上电后根据ADP的值决定进入3byte或者4byte地址线模式,单均处于SPI模式下。之后可以控制其进去QPI模式。

 本例中,将其控制在QPI模式以达到最高性能。

运行模式判断

 运行模式的判断通过以SPI/QPI读取Device ID实现,哪种方法可以得到正确的ID则说明具体为哪种模式;确认模式后,在读取ADP值确认是3byte或者4byte地址线模式。

 函数实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
W25QxxModeTypeDef GetW25QxxMode(void)
{
uint16 id = 0;
uint8 buf;
if (QSPI_OK != W25QxxReadDeciveID(&id, SPI_3_Byte))
return UnknownMode;
if (Q25Q256FV_ID == id)
{
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_1_LINE, ReadStatusReg3, QSPI_ADDRESS_NONE, 0, 0, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_1_LINE))
return UnknownMode;
if (QSPI_OK != QSPI_Receive(&buf,1))
return UnknownMode;
if ((buf & ReadStatusReg3_ADS_Msk) != ReadStatusReg3_ADS_Msk)
return SPI_3_Byte;
else
return SPI_4_Byte;
}
if (QSPI_OK != W25QxxReadDeciveID(&id, QPI_3_Byte))
return UnknownMode;
if (Q25Q256FV_ID == id)
{
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, ReadStatusReg3, QSPI_ADDRESS_NONE, 0, 0, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_4_LINES))
return UnknownMode;
if (QSPI_OK != QSPI_Receive(&buf,1))
return UnknownMode;
if ((buf & ReadStatusReg3_ADS_Msk) != ReadStatusReg3_ADS_Msk)
return QPI_3_Byte;
else
return QPI_4_Byte;
}
return UnknownMode;
}

初始化

 W25Q256FV初始化函数的功能包括:

  • 设置为QPI模式;
  • 设置为4byte地址线模式;
  • 设置SetReadParameters参数,以便获得最高读写速度;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
QSPI_StatusTypeDef W25QxxEnterQspiMode_FourByte(void)
{
uint8 buf = 0;
QSPI_StatusTypeDef res = QSPI_OK;
if (QSPI_OK != W25QxxReadReg(ReadStatusReg2, &buf)) return QSPI_ERROR;
if ((buf & ReadStatusReg2_QE_Msk) != ReadStatusReg2_QE_Msk)
{
if (QSPI_OK != W25QxxWriteVolatileEnable()) return QSPI_ERROR;
buf |= ReadStatusReg2_QE_Msk;
if (QSPI_OK != W25QxxWriteReg(WriteStatusReg2, &buf)) return QSPI_ERROR;
if (QSPI_OK != W25QxxWriteDisable()) return QSPI_ERROR;
}
if ((W25QxxMode == SPI_3_Byte) || (W25QxxMode == SPI_4_Byte))
res = QSPI_SendCmd(QSPI_INSTRUCTION_1_LINE, EnterQPIMode, QSPI_ADDRESS_NONE, 0, 0, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_NONE);
if (res != QSPI_OK)
return QSPI_ERROR;
if (QSPI_OK != W25QxxReadReg(ReadStatusReg3, &buf)) return QSPI_ERROR;
if ((buf & ReadStatusReg3_ADP_Msk) != ReadStatusReg3_ADP_Msk)
{
if (QSPI_OK != W25QxxWriteVolatileEnable()) return QSPI_ERROR;
buf |= ReadStatusReg3_ADP_Msk;
if (QSPI_OK != W25QxxWriteReg(WriteStatusReg3, &buf)) return QSPI_ERROR;
if (QSPI_OK != W25QxxWriteDisable()) return QSPI_ERROR;
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, Enter4ByteAddrMode, QSPI_ADDRESS_NONE, 0, 0, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_NONE))
return QSPI_ERROR;
}
W25QxxMode = QPI_4_Byte;
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, SetReadParameters, QSPI_ADDRESS_NONE, 0, 0, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_4_LINES))
return QSPI_ERROR;
buf = SetReadParametersVal;
if (QSPI_OK != QSPI_Transmit(&buf,1)) return QSPI_ERROR;
return QSPI_OK;
}

擦除与读写操作

 W25Q256FV初始化完毕后,按照QPI 4byte模式对其进行擦除与读写操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
QSPI_StatusTypeDef W25QxxEraseSector4KB(uint32 addr)
{
if (QSPI_OK != W25QxxWriteEnable()) return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("4KB erase done: %x\n", addr);};
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, EraseSector4KB, QSPI_ADDRESS_4_LINES, addr, QSPI_ADDRESS_32_BITS, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_NONE))
return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("W25QxxEraseSector4KB: %x\n", addr);};
return QSPI_OK;
}
QSPI_StatusTypeDef W25QxxEraseSector64KB(uint32 addr)
{
if (QSPI_OK != W25QxxWriteEnable()) return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("4KB erase done: %x\n", addr);};
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, EraseSector64KB, QSPI_ADDRESS_4_LINES, addr, QSPI_ADDRESS_32_BITS, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_NONE))
return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("W25QxxEraseSector64KB: %x\n", addr);};
return QSPI_OK;
}
QSPI_StatusTypeDef W25QxxEraseChip(void)
{
if (QSPI_OK != W25QxxWriteEnable()) return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("W25QxxEraseChip\n");};
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, ChipErase, QSPI_ADDRESS_NONE, 0, 0, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_NONE))
return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("W25QxxEraseChip\n");};
return QSPI_OK;
}
QSPI_StatusTypeDef W25QxxWritePage32B(uint32 addr, uint8 *writeBuff, uint32 writeBuffLen)
{
if (QSPI_OK != W25QxxWriteEnable()) return QSPI_ERROR;
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, PageProgram32B, QSPI_ADDRESS_4_LINES, addr, QSPI_ADDRESS_32_BITS, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 0, QSPI_DATA_4_LINES))
return QSPI_ERROR;
if (QSPI_OK != QSPI_Transmit(writeBuff,writeBuffLen))
return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("W25QxxWritePage32B: %x\n", addr);};
return QSPI_OK;
}
QSPI_StatusTypeDef W25QxxRead(uint32 addr, uint8 *readBuff, uint32 readBuffLen)
{
if (QSPI_OK != QSPI_SendCmd(QSPI_INSTRUCTION_4_LINES, FastRead, QSPI_ADDRESS_4_LINES, addr, QSPI_ADDRESS_32_BITS, QSPI_ALTERNATE_BYTES_NONE, 0, 0, 8, QSPI_DATA_4_LINES))
return QSPI_ERROR;
if (QSPI_OK != QSPI_Receive(readBuff, readBuffLen))
return QSPI_ERROR;
while (isW25QxxBusy() == TRUE) {printf("busy");};
return QSPI_OK;
}

实验验证

 测试函数设置如下:

  • 程序首先擦除了地址test_addr所在的4KB区域;
  • 之后读取擦除后的test_addr所在区域数值并打印;
  • 对test_addr所在区域进行32byte的数值写入;
  • 读取写入区域的数值并打印确定是否正确写入。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
uint32 test_addr = 0x00000000;
uint32 test_index = 0;
uint8 test_writeBuff[256];
uint8 test_readBuff[256];
printf("4KB erase addr is: %x\n", test_addr);
res = W25QxxEraseSector4KB(test_addr);
printf("W25QxxEraseSector4KB(test_addr) res is: %x\n", res);
printf("4KB erase done: %x\n\n", test_addr);
res = W25QxxRead(test_addr, test_readBuff, 256);
printf("W25QxxRead res is: %x\n", res);
for (test_index = 0; test_index < 256; test_index++)
printf("test_readBuff[test_index] is: %x\n", test_readBuff[test_index]);
for (test_index = 0; test_index < 256; test_index++)
test_writeBuff[test_index] = test_index;
res = W25QxxWritePage32B(test_addr, test_writeBuff, 256);
printf("W25QxxWritePage32B res is: %x\n", res);
res = W25QxxRead(test_addr, test_readBuff, 256);
printf("W25QxxRead res is: %x\n", res);
for (test_index = 0; test_index < 256; test_index++)
printf("test_readBuff[test_index] is: %x\n", test_readBuff[test_index]);

 程序运行结果符合预期。务必注意读写操作所针对的区域必须首先完成擦除操作。

文章目录
  1. 1. 开发环境
    1. 1.1. 硬件环境
    2. 1.2. 软件环境
  2. 2. QUADSPI功能简介
    1. 2.1. 主要特性
    2. 2.2. QUADSPI 命令序列
      1. 2.2.1. 指令阶段
      2. 2.2.2. 地址阶段
      3. 2.2.3. 交替字节阶段
      4. 2.2.4. 空指令周期阶段
      5. 2.2.5. 数据阶段
      6. 2.2.6. QUADSPI Flash 配置
      7. 2.2.7. QUADSPI 延迟数据采样
  3. 3. 使用QSPI读写W25Q256FV
    1. 3.1. QSPI的初始化
    2. 3.2. QSPI的指令与读写实现
    3. 3.3. W25Q256FV的初始化
      1. 3.3.1. 操作框图
      2. 3.3.2. 运行模式判断
      3. 3.3.3. 初始化
      4. 3.3.4. 擦除与读写操作
    4. 3.4. 实验验证
|