STM32存储左右互搏 QSPI总线读写64 Mbit容量SRAM VTI7064
QSPI(Quad-SPI)设备有两种常见操作模式,一种QSPI设备上电后直接进入QSPI模式,操作时命令,地址和数据都是多线传输。另一种QSPI设备上电后进入常规SPI操作模式,可以通过发送SPI指令,切换设备进入QSPI模式,之后也可以发送QSPI指令切换回SPI模式,故这种设备可以工作在数据率较低的SPI模式,也可工作在数据率更高的QSPI模式。
VTI7064是高容量SPI/QSPI双模式SRAM,容量达到64 Mbit,即8M Byte,可用作嵌入式系统MCU的扩展SRAM,相比FLASH而言访问速度更快且写操作不需要预擦除,适合用作图片或计算数据的缓存。有常压和低压版本:
VTI7064的操作命令表,从中可见在QSPI模式的总线时钟速度可达84MHz:
部分STM32芯片具有Dual QSPI总线接口,可以接单片或双片QSPI设备,在接双片QSPI VTI7064时,达到最高数据传输率。STM32芯片的QSPI接口可达到133MHz时钟频率,高于VTI7064, 故Dual QSPI接2片VTI7064时,理论最高数据率为84M Bytes/s或104M Bytes/s。此时是最接近于并行SDRAM 133MHz的数据率实现,速率虽然仍低于SDRAM,但接口线数少于SDRAM。
STM32的QSPI介绍可以参考官方文档:
Quad-SPI interface on STM32 microcontrollers and microprocessors (AN4760)
当前如下STM32芯片具有QSPI功能:
STM32 QSPI接口配置参数
STM32 QSPI接口配置参数有如下一些:
ClockPrescaler:时钟分频因子,取值>=0, 设置为N时,真实的计算分频因子为N+1,所以设置为0时为1分频即不分频,设置为7时为8分频, 需要注意分频是从哪个时钟分支进行分频(不同系列芯片存在不同):
Fifo Threshold: 设置发送或接收时,超过多少字节触发一次Fifo门限中断,取值1~32。FIFO模式常用于在源数据传输到目标之前临时存放这些数据,也就是说数据先存放到FIFO中,待FIFO数据量达到一定阈值,再将数据传输到目标, Fifo Threshold描述为缓冲区大小。但实际上并非如此,ST已经修改了Fifo Threshold的用处。对应的Fifo-threshold中断,并非如文档描述所言,在Fifo每次达到设置的阀值时触发,经过代码分析,实际已改为如下功能实现:
可参考Fifo-threshold中断的响应函数官方代码理解分析:
/**
* @brief Handle QSPI interrupt request.
* @param hqspi QSPI handle
* @retval None
*/
void HAL_QSPI_IRQHandler(QSPI_HandleTypeDef *hqspi)
{
__IO uint32_t *data_reg;
uint32_t flag = READ_REG(hqspi->Instance->SR);
uint32_t itsource = READ_REG(hqspi->Instance->CR);
/* QSPI Fifo Threshold interrupt occurred ----------------------------------*/
if(((flag & QSPI_FLAG_FT) != 0U) && ((itsource & QSPI_IT_FT) != 0U))
{
data_reg = &hqspi->Instance->DR;
if(hqspi->State == HAL_QSPI_STATE_BUSY_INDIRECT_TX)
{
/* Transmission process */
while(__HAL_QSPI_GET_FLAG(hqspi, QSPI_FLAG_FT) != RESET)
{
if (hqspi->TxXferCount > 0U)
{
/* Fill the FIFO until the threshold is reached */
*((__IO uint8_t *)data_reg) = *hqspi->pTxBuffPtr;
hqspi->pTxBuffPtr++;
hqspi->TxXferCount--;
}
else
{
/* No more data available for the transfer */
/* Disable the QSPI FIFO Threshold Interrupt */
__HAL_QSPI_DISABLE_IT(hqspi, QSPI_IT_FT);
break;
}
}
}
else if(hqspi->State == HAL_QSPI_STATE_BUSY_INDIRECT_RX)
{
/* Receiving Process */
while(__HAL_QSPI_GET_FLAG(hqspi, QSPI_FLAG_FT) != RESET)
{
if (hqspi->RxXferCount > 0U)
{
/* Read the FIFO until the threshold is reached */
*hqspi->pRxBuffPtr = *((__IO uint8_t *)data_reg);
hqspi->pRxBuffPtr++;
hqspi->RxXferCount--;
}
else
{
/* All data have been received for the transfer */
/* Disable the QSPI FIFO Threshold Interrupt */
__HAL_QSPI_DISABLE_IT(hqspi, QSPI_IT_FT);
break;
}
}
}
else
{
/* Nothing to do */
}
/* FIFO Threshold callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->FifoThresholdCallback(hqspi);
#else
HAL_QSPI_FifoThresholdCallback(hqspi);
#endif
}
/* QSPI Transfer Complete interrupt occurred -------------------------------*/
else if(((flag & QSPI_FLAG_TC) != 0U) && ((itsource & QSPI_IT_TC) != 0U))
{
/* Clear interrupt */
WRITE_REG(hqspi->Instance->FCR, QSPI_FLAG_TC);
/* Disable the QSPI FIFO Threshold, Transfer Error and Transfer complete Interrupts */
__HAL_QSPI_DISABLE_IT(hqspi, QSPI_IT_TC | QSPI_IT_TE | QSPI_IT_FT);
/* Transfer complete callback */
if(hqspi->State == HAL_QSPI_STATE_BUSY_INDIRECT_TX)
{
if ((hqspi->Instance->CR & QUADSPI_CR_DMAEN) != 0U)
{
/* Disable using MDMA by clearing DMAEN, note that DMAEN bit is "reserved"
but no impact on H7 HW and it minimize the cost in the footprint */
CLEAR_BIT(hqspi->Instance->CR, QUADSPI_CR_DMAEN);
/* Disable the MDMA channel */
__HAL_MDMA_DISABLE(hqspi->hmdma);
}
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
/* TX Complete callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->TxCpltCallback(hqspi);
#else
HAL_QSPI_TxCpltCallback(hqspi);
#endif
}
else if(hqspi->State == HAL_QSPI_STATE_BUSY_INDIRECT_RX)
{
if ((hqspi->Instance->CR & QUADSPI_CR_DMAEN) != 0U)
{
/* Disable using MDMA by clearing DMAEN, note that DMAEN bit is "reserved"
but no impact on H7 HW and it minimize the cost in the footprint */
CLEAR_BIT(hqspi->Instance->CR, QUADSPI_CR_DMAEN);
/* Disable the MDMA channel */
__HAL_MDMA_DISABLE(hqspi->hmdma);
}
else
{
data_reg = &hqspi->Instance->DR;
while(READ_BIT(hqspi->Instance->SR, QUADSPI_SR_FLEVEL) != 0U)
{
if (hqspi->RxXferCount > 0U)
{
/* Read the last data received in the FIFO until it is empty */
*hqspi->pRxBuffPtr = *((__IO uint8_t *)data_reg);
hqspi->pRxBuffPtr++;
hqspi->RxXferCount--;
}
else
{
/* All data have been received for the transfer */
break;
}
}
}
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
/* RX Complete callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->RxCpltCallback(hqspi);
#else
HAL_QSPI_RxCpltCallback(hqspi);
#endif
}
else if(hqspi->State == HAL_QSPI_STATE_BUSY)
{
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
/* Command Complete callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->CmdCpltCallback(hqspi);
#else
HAL_QSPI_CmdCpltCallback(hqspi);
#endif
}
else if(hqspi->State == HAL_QSPI_STATE_ABORT)
{
/* Reset functional mode configuration to indirect write mode by default */
CLEAR_BIT(hqspi->Instance->CCR, QUADSPI_CCR_FMODE);
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
if (hqspi->ErrorCode == HAL_QSPI_ERROR_NONE)
{
/* Abort called by the user */
/* Abort Complete callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->AbortCpltCallback(hqspi);
#else
HAL_QSPI_AbortCpltCallback(hqspi);
#endif
}
else
{
/* Abort due to an error (eg : MDMA error) */
/* Error callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->ErrorCallback(hqspi);
#else
HAL_QSPI_ErrorCallback(hqspi);
#endif
}
}
else
{
/* Nothing to do */
}
}
/* QSPI Status Match interrupt occurred ------------------------------------*/
else if(((flag & QSPI_FLAG_SM) != 0U) && ((itsource & QSPI_IT_SM) != 0U))
{
/* Clear interrupt */
WRITE_REG(hqspi->Instance->FCR, QSPI_FLAG_SM);
/* Check if the automatic poll mode stop is activated */
if(READ_BIT(hqspi->Instance->CR, QUADSPI_CR_APMS) != 0U)
{
/* Disable the QSPI Transfer Error and Status Match Interrupts */
__HAL_QSPI_DISABLE_IT(hqspi, (QSPI_IT_SM | QSPI_IT_TE));
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
}
/* Status match callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->StatusMatchCallback(hqspi);
#else
HAL_QSPI_StatusMatchCallback(hqspi);
#endif
}
/* QSPI Transfer Error interrupt occurred ----------------------------------*/
else if(((flag & QSPI_FLAG_TE) != 0U) && ((itsource & QSPI_IT_TE) != 0U))
{
/* Clear interrupt */
WRITE_REG(hqspi->Instance->FCR, QSPI_FLAG_TE);
/* Disable all the QSPI Interrupts */
__HAL_QSPI_DISABLE_IT(hqspi, QSPI_IT_SM | QSPI_IT_TC | QSPI_IT_TE | QSPI_IT_FT);
/* Set error code */
hqspi->ErrorCode |= HAL_QSPI_ERROR_TRANSFER;
if ((hqspi->Instance->CR & QUADSPI_CR_DMAEN) != 0U)
{
/* Disable using MDMA by clearing DMAEN, note that DMAEN bit is "reserved"
but no impact on H7 HW and it minimize the cost in the footprint */
CLEAR_BIT(hqspi->Instance->CR, QUADSPI_CR_DMAEN);
/* Disable the MDMA channel */
hqspi->hmdma->XferAbortCallback = QSPI_DMAAbortCplt;
if (HAL_MDMA_Abort_IT(hqspi->hmdma) != HAL_OK)
{
/* Set error code to DMA */
hqspi->ErrorCode |= HAL_QSPI_ERROR_DMA;
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
/* Error callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->ErrorCallback(hqspi);
#else
HAL_QSPI_ErrorCallback(hqspi);
#endif
}
}
else
{
/* Change state of QSPI */
hqspi->State = HAL_QSPI_STATE_READY;
/* Error callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->ErrorCallback(hqspi);
#else
HAL_QSPI_ErrorCallback(hqspi);
#endif
}
}
/* QSPI Timeout interrupt occurred -----------------------------------------*/
else if(((flag & QSPI_FLAG_TO) != 0U) && ((itsource & QSPI_IT_TO) != 0U))
{
/* Clear interrupt */
WRITE_REG(hqspi->Instance->FCR, QSPI_FLAG_TO);
/* Timeout callback */
#if (USE_HAL_QSPI_REGISTER_CALLBACKS == 1)
hqspi->TimeOutCallback(hqspi);
#else
HAL_QSPI_TimeOutCallback(hqspi);
#endif
}
else
{
/* Nothing to do */
}
}
Sample Shifting: STM32接收数据延迟半周期采样,即由于时钟沿从STM32管脚到QSPI设备管脚的延时+QSPI设备接收到时钟沿后的数据输出延时+QSPI设备数据传输到STM32管脚延时较大时,可以选择STM32延迟半时钟周期采样接收数据。只是内部延迟半时钟周期采样,并不和半周期后的信号沿对应,所以最后一次采样不需要增加时钟边沿输出。
Flash Size: 设置要访问的QSPI设备可访问空间大小,针对字节单位,以2的N次方的表达方式设置,取值0~31, 设置为N时实际计算取值为N+1, 所以取值为0时,对应2字节; 取值为9时,对应1K字节,取值为19时,对应1M字节,取值为22时,对应8M字节。需要注意如果连接了2片QSPI设备,这里的Flash Size设置要对应总的字节地址数量,即单片QSPI设备取值设置时的2倍,如2片8M Bytes的QSPI设备,取值设置成23:
Chip Select High Time: 因为QSPI接口的片选是硬件控制,和SPI接口不同(可以设置片选为软件控制并指定任意GPIO为片选输出管脚),管脚为特定管脚且时序由芯片内部QSPI模块控制,在连续发送两个命令时,每个命令发送都会有片选拉低和拉高的过程,而在两个命令发送之间的片选高电平延时默认为1个时钟周期,但如果QSPI设备要求有更多的延时,则可以根据需要设置为更多的时钟周期延时。
Clock Mode:设置QSPI接口空闲时(片选为高电平)时的时钟输出电平,有Low和High两个选项,实际上是指定片选有效后第一个时钟上升沿之前的时序特性,QSPI设备固定为时钟上升沿采样数据电平。设置Clock Mode为Low,则在片选后的第一个时钟上升沿之前,不发送一个下降沿,效果对应SPI接口配置时的Mode 0; 设置Clock Mode为High,则在片选后的第一个时钟上升沿之前,发送一个下降沿,效果对应SPI接口配置时的Mode 3:
Dual Flash: 在配置为单路QSPI接口模式(Bank1 或 Bank2)时,Dual Flash选项固定为Disable。在配置为双路(Dual Bank)QSPI接口模式时,Dual Flash选项可控制两路QSPI接口工作在独立模式还是联合模式,Dual Flash选型配置为Enable时为联合模式,两路QSPI接口同时发送和同时接收进行操作。Dual Flash选型配置为Disable时为独立模式,可以控制单路QSPI接口工作,此时另外一路QSPI接口不工作,并可以切换,因为QSPI的访问要在IO管脚上发送命令和地址,所以不工作的一路QSPI的IO管脚不会发出命令和地址,而片选和时钟仍然可以共用。在代码控制阶段通过库函数里相应的变量(FlashID)进行选择通道:
FlashID的取值:
另外,如果QSPI设备支持DDR模式(时钟上升沿和下降沿都采样数据),则在库函数实施阶段可以进一步提高数据吞吐率。这里VTI7064并不支持DDR模式。
STM32 QSPI操作模式
STM32 QPSI控制器有三种操作模式:
-
第一种模式为间接模式,通过读写STM32内部控制寄存器,触发QSPI硬件模块对外部QSPI设备进行操作,为常规操作模式,这里的范例实现采用间接模式。
-
第二种模式为状态标志自动轮询模式,可以设定周期性的轮询某个状态标志,当状态变化则发出中断通知,此操作由硬件模块执行,不占用MCU时间。这里的范例不采用此模式。
-
第三种为内存映射模式,即将外部QSPI设备当做内部FLASH使用,外部QSPI设备可以存放代码,STM32可以进行读取。这里的范例不采用此模式。
STM32 工程配置
这里以STM32H743VIT6为范例芯片,连接两片VTI7064 SRAM, 以STM32CUBEIDE为开发环境,实现STM32 QSPI对SRAM的访问。
首先建立基本工程并配置时钟:
本范例采用USB虚拟串口作为通讯端口,所以配置USB端口:
USB的时钟部分配置,注意使能QSPI后这里也设置时钟为240MHz:
USB端口配置为虚拟串口,采用默认配置即可:
然后进行QSPI接口的配置,首先配置为Bank1单独模式(后面会描述双Bank的配置和使用):
接口总线要跑得快,需要良好的PCB设计,这里范例先将QSPI的访问速度降下来,Clock Prescaler设置为3,实际上就是4分频,时钟部分设置QSPI时钟为240MHz,因此最后产生的QSPI运行时钟为60MHz。Fifo Threshold设置大于0的值,随便设置,前面已对QSPI的Fifo Threshold及各部分做了介绍。这里因为是独立的QSPI接口模式,所以直接在Flash ID选项处选择采用Bank1或是Bank2。Dual Flash选项则为不可选的Disabled。
另外QSPI接口也可以弱化为单线或双线模式,既然采用了QSPI总线,必然是对访问速度有要求,所以直接用4线模式:
实际上,配置为4线访问模式后,在代码里还可以实时切换为单线和双线协议访问模式。上面的设置更体现GPIO物理管脚的占用量。
范例里会实现Block, Interrupt, DMA三种访问,所以打开DMA开关,但是不需要做更复杂的DMA转发,所以采用默认单字节传送配置即可(Buffer Transfer Length和前面设置的Fifo Threshold一致):
使能中断:
可以看到相应Bank所占用的管脚,将管脚信号速度级别调为最高:
检查中断相关代码的自动生成已点上:
保存并生成初始工程代码:
STM32 工程代码
这里范例设计的逻辑为通过USB虚拟串口进行控制:
发送0x01为初始化VTI7064;
发送0x02为读VTI7064 ID, 验证接口访问是否正常
发送0x03为发送SPI指令控制VTI7064进入Quad SPI模式
发送0x04为发送Quad SPI指令控制VTI7064退出Quad SPI模式
发送0x05 + 一串十六进制数据,STM32将这些数据以Block写模式写入VTI7064
发送0x06, STM32以Block读模式从VTI7064读出数据
发送0x07 + 一串十六进制数据,STM32将这些数据以Interrupt写模式写入VTI7064
发送0x08, STM32以Interrupt读模式从VTI7064读出数据
发送0x09 + 一串十六进制数据,STM32将这些数据以DMA写模式写入VTI7064
发送0x0A, STM32以DMA读模式从VTI7064读出数据
发送0x0B, 测试发送数小于Fifo Threshold接收到的中断数量
发送0x0C, 测试发送数大于Fifo Threshold接收到的中断数量
实际上0x0B和0x0C测试都只能收到1次中断,原理前面已介绍。
发送0x55, 针对双Bank切换模式,切换选中的Bank为Bank1
发送0xAA, 针对双Bank切换模式,切换选中的Bank为Bank2
范例逻辑做了简化,无论从串口收到多少数据,都是以1024个字节进行写读操作,可自行调整实时写读数量。
USB虚拟串口的介绍可参考: STM32 USB VCOM和HID的区别,配置及Echo功能实现(HAL)
USB虚拟串口接收部分的代码:
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
extern uint8_t cmd;
extern uint8_t DB[1024];
if(Buf[0]==0x01) //to initialize VTI7064
{
cmd = 0x01;
}
else if(Buf[0]==0x02) //to read id of VTI7064
{
cmd = 0x02;
}
else if(Buf[0]==0x03) //to enter Quad mode of VTI7064
{
cmd = 0x03;
}
else if(Buf[0]==0x04) //to leav Quad mode of VTI7064
{
cmd = 0x04;
}
else if(Buf[0]==0x05) //to write data to VTI7064 in block mode
{
if(((*Len-1)<=sizeof(DB))&&((*Len-1)>0))
{
memset(DB, 0, sizeof(DB));
for(uint32_t i=0; i<((*Len)-1); i++)
{
DB[i] = Buf[i+1];
}
cmd = 0x05;
}
}
else if(Buf[0]==0x06) //to read data from VTI7064 in block mode
{
cmd = 0x06;
}
else if(Buf[0]==0x07) //to write data to VTI7064 in INT mode
{
if(((*Len-1)<=sizeof(DB))&&((*Len-1)>0))
{
memset(DB, 0, sizeof(DB));
for(uint32_t i=0; i<((*Len)-1); i++)
{
DB[i] = Buf[i+1];
}
cmd = 0x07;
}
}
else if(Buf[0]==0x08) //to read data from VTI7064 in INT mode
{
cmd = 0x08;
}
else if(Buf[0]==0x09) //to write data to VTI7064 in DMA mode
{
if(((*Len-1)<=sizeof(DB))&&((*Len-1)>0))
{
memset(DB, 0, sizeof(DB));
for(uint32_t i=0; i<((*Len)-1); i++)
{
DB[i] = Buf[i+1];
}
cmd = 0x09;
}
}
else if(Buf[0]==0x0a) //to read data from VTI7064 in DMA mode
{
cmd = 0x0a;
}
else if(Buf[0]==0x0b) //to test threshold INT with data sending < threshold
{
cmd = 0x0b;
}
else if(Buf[0]==0x0c) //to test threshold INT with data sending > threshold
{
cmd = 0x0c;
}
else if(Buf[0]==0x55) //to switch to use bank1 interfaced QSPI device for dual-bank switch mode (dual flash mode disable)
{
cmd = 0x55;
}
else if(Buf[0]==0xaa) //to switch to use bank2 interfaced QSPI device for dual-bank switch mode (dual flash mode disable)
{
cmd = 0xaa;
}
else;
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
/* USER CODE END 6 */
}
main.c主程序代码:
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* <h2><center>© Copyright (c) 2023 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
*
******************************************************************************
*/
//Written by Pegasus Yu in 2023
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usb_device.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
/* 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 ---------------------------------------------------------*/
QSPI_HandleTypeDef hqspi;
MDMA_HandleTypeDef hmdma_quadspi_fifo_th;
/* USER CODE BEGIN PV */
/*
QSPI TX in block mode for command byte
cmd: command to be sent to device
addr: address to be sent to device
mode: operation mode set as
mode[1:0] for command transmission mode ( 00: no command; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
mode[3:2] for address transmission mode ( 00: no address; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
mode[5:4] for address length ( 00:8-bit address; 01: 16-bit address; 10: 24-bit address; 11: 32-bit address )
mode[7:6] for data transmission mode ( 00: no command; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
dmcycle: dummy clock cycle
*/
HAL_StatusTypeDef QSPI_Send_CMD(uint8_t cmd,uint32_t addr,uint8_t mode,uint8_t dmcycle)
{
QSPI_CommandTypeDef Cmdhandler;
Cmdhandler.Instruction=cmd; //set cmd
Cmdhandler.Address=addr; //set address
Cmdhandler.DummyCycles=dmcycle; //set dummy circle number
/*set cmd transmission mode*/
if(((mode>>0)&0x03) == 0)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_NONE;
else if(((mode>>0)&0x03) == 1)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_1_LINE;
else if(((mode>>0)&0x03) == 2)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_2_LINES;
else if(((mode>>0)&0x03) == 3)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_4_LINES;
/*set address transmission mode*/
if(((mode>>2)&0x03) == 0)
Cmdhandler.AddressMode=QSPI_ADDRESS_NONE;
else if(((mode>>2)&0x03) == 1)
Cmdhandler.AddressMode=QSPI_ADDRESS_1_LINE;
else if(((mode>>2)&0x03) == 2)
Cmdhandler.AddressMode=QSPI_ADDRESS_2_LINES;
else if(((mode>>2)&0x03) == 3)
Cmdhandler.AddressMode=QSPI_ADDRESS_4_LINES;
/*set address length*/
if(((mode>>4)&0x03) == 0)
Cmdhandler.AddressSize=QSPI_ADDRESS_8_BITS;
else if(((mode>>4)&0x03) == 1)
Cmdhandler.AddressSize=QSPI_ADDRESS_16_BITS;
else if(((mode>>4)&0x03) == 2)
Cmdhandler.AddressSize=QSPI_ADDRESS_24_BITS;
else if(((mode>>4)&0x03) == 3)
Cmdhandler.AddressSize=QSPI_ADDRESS_32_BITS;
/*set data transmission mode*/
if(((mode>>6)&0x03) == 0)
Cmdhandler.DataMode=QSPI_DATA_NONE;
else if(((mode>>6)&0x03) == 1)
Cmdhandler.DataMode=QSPI_DATA_1_LINE;
else if(((mode>>6)&0x03) == 2)
Cmdhandler.DataMode=QSPI_DATA_2_LINES;
else if(((mode>>6)&0x03) == 3)
Cmdhandler.DataMode=QSPI_DATA_4_LINES;
Cmdhandler.SIOOMode=QSPI_SIOO_INST_EVERY_CMD; /*Send instruction on every transaction*/
Cmdhandler.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE; /*No alternate bytes*/
Cmdhandler.DdrMode=QSPI_DDR_MODE_DISABLE; /*Double data rate mode disabled*/
Cmdhandler.DdrHoldHalfCycle=QSPI_DDR_HHC_ANALOG_DELAY; /*Delay the data output using analog delay in DDR mode*/
return HAL_QSPI_Command(&hqspi,&Cmdhandler,5000);
}
//QSPI RX in block mode for data
//buf : buffer address for RX data
//datalen : RX data length
//return : 0, OK
// others, error code
HAL_StatusTypeDef QSPI_Receive(uint8_t* buf,uint32_t datalen)
{
hqspi.Instance->DLR=datalen-1;
return HAL_QSPI_Receive(&hqspi,buf,5000);
}
//QSPI TX in block mode for data
//buf : buffer address for TX data
//datalen : TX data length
//return : 0, OK
// others, error code
HAL_StatusTypeDef QSPI_Transmit(uint8_t* buf,uint32_t datalen)
{
hqspi.Instance->DLR=datalen-1;
return HAL_QSPI_Transmit(&hqspi,buf,5000);
}
/*
QSPI TX in INT mode for command byte
cmd: command to be sent to device
addr: address to be sent to device
mode: operation mode set as
mode[1:0] for command transmission mode ( 00: no command; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
mode[3:2] for address transmission mode ( 00: no address; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
mode[5:4] for address length ( 00:8-bit address; 01: 16-bit address; 10: 24-bit address; 11: 32-bit address )
mode[7:6] for data transmission mode ( 00: no command; 01: single-line transmission; 10: dual-line transmission; 11: four-line transmission )
dmcycle: dummy clock cycle
*/
HAL_StatusTypeDef QSPI_Send_CMD_INT(uint8_t cmd,uint32_t addr,uint8_t mode,uint8_t dmcycle)
{
QSPI_CommandTypeDef Cmdhandler;
Cmdhandler.Instruction=cmd; //set cmd
Cmdhandler.Address=addr; //set address
Cmdhandler.DummyCycles=dmcycle; //set dummy circle number
/*set cmd transmission mode*/
if(((mode>>0)&0x03) == 0)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_NONE;
else if(((mode>>0)&0x03) == 1)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_1_LINE;
else if(((mode>>0)&0x03) == 2)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_2_LINES;
else if(((mode>>0)&0x03) == 3)
Cmdhandler.InstructionMode=QSPI_INSTRUCTION_4_LINES;
/*set address transmission mode*/
if(((mode>>2)&0x03) == 0)
Cmdhandler.AddressMode=QSPI_ADDRESS_NONE;
else if(((mode>>2)&0x03) == 1)
Cmdhandler.AddressMode=QSPI_ADDRESS_1_LINE;
else if(((mode>>2)&0x03) == 2)
Cmdhandler.AddressMode=QSPI_ADDRESS_2_LINES;
else if(((mode>>2)&0x03) == 3)
Cmdhandler.AddressMode=QSPI_ADDRESS_4_LINES;
/*set address length*/
if(((mode>>4)&0x03) == 0)
Cmdhandler.AddressSize=QSPI_ADDRESS_8_BITS;
else if(((mode>>4)&0x03) == 1)
Cmdhandler.AddressSize=QSPI_ADDRESS_16_BITS;
else if(((mode>>4)&0x03) == 2)
Cmdhandler.AddressSize=QSPI_ADDRESS_24_BITS;
else if(((mode>>4)&0x03) == 3)
Cmdhandler.AddressSize=QSPI_ADDRESS_32_BITS;
/*set data transmission mode*/
if(((mode>>6)&0x03) == 0)
Cmdhandler.DataMode=QSPI_DATA_NONE;
else if(((mode>>6)&0x03) == 1)
Cmdhandler.DataMode=QSPI_DATA_1_LINE;
else if(((mode>>6)&0x03) == 2)
Cmdhandler.DataMode=QSPI_DATA_2_LINES;
else if(((mode>>6)&0x03) == 3)
Cmdhandler.DataMode=QSPI_DATA_4_LINES;
Cmdhandler.SIOOMode=QSPI_SIOO_INST_EVERY_CMD; /*Send instruction on every transaction*/
Cmdhandler.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE; /*No alternate bytes*/
Cmdhandler.DdrMode=QSPI_DDR_MODE_DISABLE; /*Double data rate mode disabled*/
Cmdhandler.DdrHoldHalfCycle=QSPI_DDR_HHC_ANALOG_DELAY; /*Delay the data output using analog delay in DDR mode*/
return HAL_QSPI_Command_IT(&hqspi,&Cmdhandler);
}
//QSPI RX in INT mode for data
//buf : buffer address for RX data
//datalen : RX data length
//return : 0, OK
// others, error code
HAL_StatusTypeDef QSPI_Receive_INT(uint8_t* buf,uint32_t datalen)
{
hqspi.Instance->DLR=datalen-1;
return HAL_QSPI_Receive_IT(&hqspi,buf);
}
//QSPI TX in INT mode for data
//buf : buffer address for TX data
//datalen : TX data length
//return : 0, OK
// others, error code
HAL_StatusTypeDef QSPI_Transmit_INT(uint8_t* buf,uint32_t datalen)
{
hqspi.Instance->DLR=datalen-1;
return HAL_QSPI_Transmit_IT(&hqspi,buf);
}
//QSPI RX in DMA mode for data
//buf : buffer address for RX data
//datalen : RX data length
//return : 0, OK
// others, error code
HAL_StatusTypeDef QSPI_Receive_DMA(uint8_t* buf,uint32_t datalen)
{
hqspi.Instance->DLR=datalen-1;
return HAL_QSPI_Receive_DMA(&hqspi,buf);
}
//QSPI TX in DMA mode for data
//buf : buffer address for TX data
//datalen : TX data length
//return : 0, OK
// others, error code
HAL_StatusTypeDef QSPI_Transmit_DMA(uint8_t* buf,uint32_t datalen)
{
hqspi.Instance->DLR=datalen-1;
return HAL_QSPI_Transmit_DMA(&hqspi,buf);
}
uint8_t VTI706_Quad_Status = 0;
void VTI7064_Rst_Init(void)
{
if(VTI706_Quad_Status==0) QSPI_Send_CMD(0x66, 0x00, 0x01, 0);
}
uint64_t VTI7064_Read_ID(void)
{
uint64_t VID = 0;
if(VTI706_Quad_Status==0)
{
QSPI_Send_CMD(0x9F, 0x00, 0x65, 0);
if(QSPI_Receive((uint8_t *)&VID, 8)==0) return VID;
else return 0;
}
else return 0;
}
void VTI7064_Enter_Quad(void)
{
if(VTI706_Quad_Status==0) QSPI_Send_CMD(0x35, 0x00, 0x01, 0);
VTI706_Quad_Status = 1;
}
void VTI7064_Exit_Quad(void)
{
if(VTI706_Quad_Status==1) QSPI_Send_CMD(0xF5, 0x00, 0x03, 0);
VTI706_Quad_Status = 0;
}
void VTI7064_Quad_Write(uint32_t addr, uint8_t * data, uint32_t datalen)
{
if(VTI706_Quad_Status==1)
{
QSPI_Send_CMD(0x38, addr, 0xEF, 0);
QSPI_Transmit(data, datalen);
}
}
void VTI7064_Quad_Read(uint32_t addr, uint8_t * data, uint32_t datalen)
{
if(VTI706_Quad_Status==1)
{
QSPI_Send_CMD(0xEB, addr, 0xEF, 6);
QSPI_Receive(data, datalen);
}
}
void VTI7064_Quad_Write_INT(uint32_t addr, uint8_t * data, uint32_t datalen)
{
if(VTI706_Quad_Status==1)
{
QSPI_Send_CMD(0x38, addr, 0xEF, 0);
QSPI_Transmit_INT(data, datalen);
}
}
void VTI7064_Quad_Read_INT(uint32_t addr, uint8_t * data, uint32_t datalen)
{
if(VTI706_Quad_Status==1)
{
QSPI_Send_CMD(0xEB, addr, 0xEF, 6);
QSPI_Receive_INT(data, datalen);
}
}
void VTI7064_Quad_Write_DMA(uint32_t addr, uint8_t * data, uint32_t datalen)
{
if(VTI706_Quad_Status==1)
{
QSPI_Send_CMD(0x38, addr, 0xEF, 0);
QSPI_Transmit_DMA(data, datalen);
}
}
void VTI7064_Quad_Read_DMA(uint32_t addr, uint8_t * data, uint32_t datalen)
{
if(VTI706_Quad_Status==1)
{
QSPI_Send_CMD(0xEB, addr, 0xEF, 6);
QSPI_Receive_DMA(data, datalen);
}
}
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_MDMA_Init(void);
static void MX_QUADSPI_Init(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#define BufSize 1024
uint8_t cmd = 0x00;
uint8_t cmd_int = 0x00;
uint8_t DB[BufSize];
uint64_t VTI7064_ID = 0;
char * console;
QSPI_HandleTypeDef * hqspi1;
uint32_t FifoThreshold_NUM = 0;
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
hqspi1 = &hqspi;
/* USER CODE END 1 */
/* 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_USB_DEVICE_Init();
MX_MDMA_Init();
MX_QUADSPI_Init();
/* USER CODE BEGIN 2 */
memset(DB, 0, sizeof(DB));
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
if(cmd==0x01) //Reset and initialization in block mode
{
cmd = 0;
VTI7064_Rst_Init();
console = "\r\nInitialization Done\r\n";
CDC_Transmit_FS(console, strlen(console));
}
else if(cmd==0x02) //Get ID in block mode
{
cmd = 0;
VTI7064_ID = VTI7064_Read_ID();
CDC_Transmit_FS(&VTI7064_ID, 8);
}
else if(cmd==0x03) //Enter Quad mode in block mode
{
cmd = 0;
if(VTI706_Quad_Status==0)
{
VTI7064_Enter_Quad();
console = "\r\nEnter Quad Mode\r\n";
CDC_Transmit_FS(console, strlen(console));
}
}
else if(cmd==0x04) //Exit Quad mode in block mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
VTI7064_Exit_Quad();
console = "\r\nExit Quad Mode\r\n";
CDC_Transmit_FS(console, strlen(console));
}
}
else if(cmd==0x05) //to write data to VTI7064 in block mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
VTI7064_Quad_Write(0, DB, sizeof(DB));
console = "\r\nWrite Operation Done\r\n";
CDC_Transmit_FS(console, strlen(console));
}
}
else if(cmd==0x06) //to read data from VTI7064 in block mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
memset(DB, 0, sizeof(DB));
VTI7064_Quad_Read(0, DB, sizeof(DB));
}
CDC_Transmit_FS(DB, sizeof(DB));
}
else if(cmd==0x07) //to write data to VTI7064 in INT mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
cmd_int = 7;
VTI7064_Quad_Write_INT(0, DB, sizeof(DB));
}
}
else if(cmd==0x08) //to read data from VTI7064 in INT mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
cmd_int = 8;
memset(DB, 0, sizeof(DB));
VTI7064_Quad_Read_INT(0, DB, sizeof(DB));
}
}
else if(cmd==0x09) //to write data to VTI7064 in DMA mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
cmd_int = 9;
VTI7064_Quad_Write_DMA(0, DB, sizeof(DB));
}
}
else if(cmd==0x0a) //to read data from VTI7064 in DMA mode
{
cmd = 0;
if(VTI706_Quad_Status==1)
{
cmd_int = 10;
memset(DB, 0, sizeof(DB));
VTI7064_Quad_Read_DMA(0, DB, sizeof(DB));
}
}
else if(cmd==0x0b) //to test threshold INT with data sending < threshold
{
cmd = 0;
cmd_int = 11;
FifoThreshold_NUM = 0;
if(VTI706_Quad_Status==1)
{
VTI7064_Quad_Write_INT(0, DB, 4); //Sending 4-byte data
}
}
else if(cmd==0x0c) //to test threshold INT with data sending > threshold
{
cmd = 0;
cmd_int = 12;
FifoThreshold_NUM = 0;
if(VTI706_Quad_Status==1)
{
VTI7064_Quad_Write_INT(0, DB, sizeof(DB)); //Sending full data exceeding Fifo size
}
}
else if(cmd==0x55) //to switch to use bank1 interfaced QSPI device for dual-bank switch mode (dual flash mode disable)
{
cmd = 0;
HAL_QSPI_SetFlashID(&hqspi, QSPI_FLASH_ID_1);
console = "\r\nBank 1 was selected!\r\n";
CDC_Transmit_FS(console, strlen(console));
}
else if(cmd==0xaa) //to switch to use bank2 interfaced QSPI device for dual-bank switch mode (dual flash mode disable)
{
cmd = 0;
HAL_QSPI_SetFlashID(&hqspi, QSPI_FLASH_ID_2);
console = "\r\nBank 2 was selected!\r\n";
CDC_Transmit_FS(console, strlen(console));
}
else;
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {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)) {}
__HAL_RCC_SYSCFG_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);
while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI48|RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSI48State = RCC_HSI48_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 4;
RCC_OscInitStruct.PLL.PLLN = 480;
RCC_OscInitStruct.PLL.PLLP = 2;
RCC_OscInitStruct.PLL.PLLQ = 2;
RCC_OscInitStruct.PLL.PLLR = 2;
RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_1;
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 buses 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_4) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief QUADSPI Initialization Function
* @param None
* @retval None
*/
static void MX_QUADSPI_Init(void)
{
/* USER CODE BEGIN QUADSPI_Init 0 */
/* USER CODE END QUADSPI_Init 0 */
/* USER CODE BEGIN QUADSPI_Init 1 */
/* USER CODE END QUADSPI_Init 1 */
/* QUADSPI parameter configuration*/
hqspi.Instance = QUADSPI;
hqspi.Init.ClockPrescaler = 3;
hqspi.Init.FifoThreshold = 32;
hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
hqspi.Init.FlashSize = 22;
hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_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();
}
/* USER CODE BEGIN QUADSPI_Init 2 */
/* USER CODE END QUADSPI_Init 2 */
}
/**
* Enable MDMA controller clock
*/
static void MX_MDMA_Init(void)
{
/* MDMA controller clock enable */
__HAL_RCC_MDMA_CLK_ENABLE();
/* Local variables */
/* MDMA interrupt initialization */
/* MDMA_IRQn interrupt configuration */
HAL_NVIC_SetPriority(MDMA_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(MDMA_IRQn);
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOH_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
}
/* USER CODE BEGIN 4 */
void HAL_QSPI_FifoThresholdCallback(QSPI_HandleTypeDef *hqspi)
{
if(hqspi==hqspi1)
{
FifoThreshold_NUM += 1; //Count int times
}
}
void HAL_QSPI_CmdCpltCallback(QSPI_HandleTypeDef *hqspi)
{
//unused
}
void HAL_QSPI_RxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
if(hqspi==hqspi1)
{
if((cmd_int==8)||(cmd_int==10))
{
cmd_int = 0;
CDC_Transmit_FS(DB, sizeof(DB));
}
}
}
void HAL_QSPI_TxCpltCallback(QSPI_HandleTypeDef *hqspi)
{
if(hqspi==hqspi1)
{
if((cmd_int==7)||(cmd_int==9))
{
cmd_int =0;
console = "\r\nWrite Operation Done\r\n";
CDC_Transmit_FS(console, strlen(console));
}
if((cmd_int==11)||(cmd_int==12))
{
cmd_int =0;
CDC_Transmit_FS(&FifoThreshold_NUM, 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 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 CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
代码测试效果
STM32 QSPI双区切换访问模式
设置为双区切换访问模式,升级代码生成即可。
在代码里可以进行要访问的Bank的切换
STM32 QSPI双区合并访问模式
设置为双区合并访问模式,,升级代码生成即可。逻辑代码实现不变化,但每次操作写或读时,会把STM32内部偶地址对应Bank1, 基地址对应Bank2进行数据写读。也即STM32内存的一个字节数据会写入到一个Bank,而不是分成两个4位写到两个Bank,读也是一样。在四线QSPI模式,写一个字节到一个Bank,实际上会有两次写过程(每次写4位),读也是一样。
STM32 QSPI片选节约
STM32 QSPI可以选择片选节约方式,如果到外部存储设备的片选一直外拉有效,可以选择Disable。也可以2个Bank的片选共用,或者片选各用。
范例代码下载
STM32 QSPI总线读写64 Mbit容量SRAM VTI7064范例
–End