前段时间淘了一个STM32H750XBH6_ArtPi开发板,板载两颗华邦的Flash芯片,一颗为W25Q64JV(8Mbytes),通过STM32H750XBH6的QUASDSPI控制用于XIP;一颗为W25Q128JV(16Mbytes)用于数据存储。
然后W25Q64JV使用Quad SPI方式连接,W25Q128JV使用标准SPI连接。
本博文基于此开发板从基础介绍STM32 Quad SPI硬件模块。
参考ST官方Quad SPI使用文档
STM32系列芯片上的Quad-SPI允许4根数据线用于主机和外部Quad-SPI的存储设备通信。支持传统的SPI也支持双线模式。Quad-SPI使用最多6根线,其中四根线用于数据输入输出。
我们开发板上的芯片为STM32H750XBH6,其Quad-SPI支持的SDR(单时钟速率下)最大时钟频率为133MHz, DDR(双时钟速率下)为100MHz;硬件FIFO最大32字节,支持双Flash,即BINK1和BINK2;内存映射模式下最大支持256Mbytes的大小,间接模式下支持4Gbytes空间。
在内存映射模式下,Quad-SPI接口被映射到AHB总线上,也就是说AHB总线可以直接读取数据(即通过指针直接读取)。
Quad-SPI接口提供了一个完全可配置的帧格式,最大包含5个阶段并且每个阶段都可以完全配置(也就是说每个阶段的数据长度和数据线数完全可以独立配置)。
从上图我们可以看到这五个阶段分别为指令阶段、地址阶段、交替(可选)字节阶段、空指令阶段、数据阶段。
指令阶段可以发送一个字节的数据,通过往寄存器QUADSPI_CCR[7:0]的INSTRUCTION字段写入即可。指令可以通过1/2/4线发送。一般1线的比较常见。
地址阶段可以发送1/2/3/4个字节,写入QUADSPI_AR寄存器即可;可以通过1/2/4线发送,通过ADSIZE指定地址字节长度,通过ADMODE指定是否有地址/1线/2线/4线模式。
通过ABSIZE指定发送的1/2/3/4字节,通过ABMODE指定是否有交替字节、1/2/4线发送。选项字节写入QUADSPI_ABR寄存器。
通过DCYC[4:0]最大可以设置31个周期。
数据阶段可以配置读写任意字节的数据。通过QUADSPI_DLR设置数据长度,通过QUADSPI_DR寄存器读写数据;在内存映射模式下只能读取数据,其通过QUADSPI FIFO进行读取。
数据阶段可以1/2/4线发送,也可以没有数据阶段。如果QUADSPI_DLR=0xFFFFFFFF,则数据传输直到字节数等于FSIZE。
间接模式用于读写、擦除操作;所有的操作都是通过QUADSPI数据寄存器来执行,可以使用CPU也可以使用中断和DMA;也用于配置Quad-SPI Flash内存。
传输的数据通过数寄存器和FIFO。
自动轮询模式也可用于检测Flash内存中的状态寄存器的改变并产生中断,在检查擦除或者编程操作时特别有用。
用于读取Quad-SPI Flash内存的状态以及用于检测擦除或者编程的完成。开发者可以配置轮询周期以及轮询的掩码和匹配模式(与/或);当匹配发生时,自动产生中断并且停止。
用于读操作;将外部Quad-SPI Flash内存看作内部空间,这样任何AHB主设备就可以自动读取;使用内存映射模式,QuadSPI自动管理预取机制,这可以优化外部Flash的读写执行性能 。
内存映射模式下最大访问大小为256Mbytes,访问地址为0x90000000 - 0x9FFFFFFF;
不支持直接从外部Quad-SPI Flash启动,但是可以先从内部Flash启动,然后配置QUADSPI为内存映射模式,然后就可以从外部Quad-SPI Flash执行代码。
总共有5个中断,分别为超时、状态匹配、FIFO达到阈值、传输完成(传输被终止)、传输错误;使用中断必须使能QUADSPI全局中断。、
间接模式下可以DMA读写外部Quad-SPI Flash,内存映射模式下只支持读取。
在DMA模式下,DMA负责过程控制。
经过上面的讲解,大家应该对QUADSPI已经有了一个大致了解,下面我们通过代码工程来进一步加深理解;本篇我们只讲述轮询方式控制,至于中断和DMA方式的实现,后面再给大家讲解。(要先给大家讲解一下移植FreeRTOS,不然中断或者DMA模式跟轮询的方式效果上将没有大的差别)。
下面我们来创建一个工程,为了简单我们还是使用STM32CubeMX生成一个Keil MDK工程
参考《手把手系列--使用STM32CubeMX生成代码工程》
为方便学习,这边提供的已经准备好的工程给大家下载
链接:https://pan.baidu.com/s/1G_k7gXvPgc9O-sy3aqGBpw
提取码:r2fl
工程中我们提供了W25Q64JV的驱动源码,下面我们根据源码一一讲解如何使用Quad-SPI。
关于W25Q64JV的描述请参考《手把手系列--华邦W25Q64JV Flash操作指南》。
我们就以W25Q64JV的写使能命令为例说明
首先我们先来看QSPI_CommandTypeDef这个结构体
Instruction字段设定我们需要发送的指令,当前我们设定为W25Q64JV_WRITE_ENABLE(0x06);
Address字段设定我们需要发送的地址(当前指令下不需要发送地址);
AlternateBytes字段设定交替字节阶段的数据(当前指令下不需要交替字节);
AddressSize字段设定地址阶段的字节数(当前指令下不需要地址);
AlternateBytesSize字段设定交替字节阶段的字节数(当前指令下不需要交替字节);
DummyCycles字段设定空指令阶段的周期数(当前指令下不需要空指令);
InstructionMode字段设定指令的发送方式,即选用1/2/4线进行指令的发送;根据W25Q64JV芯片手册说明,我们应该选用一线,即QSPI_INSTRUCTION_1_LINE;
AddressMode字段设定地址的发送方式,即选用1/2/4线进行地址的发送(当前指令下不需要发送地址字节);
AlternateByteMode字段设定交替字节的发送方式,即选用1/2/4线进行交替字节的发送(当前指令下不需要发送交替字节);
DataMode字段设定数据的发送方式,即选用1/2/4线进行交替字节的发送(当前指令下不需要发送数据);
NbData字段设定需要发送的数据字节的数量(当前指令下不需要发送数据);
DdrMode字段设定使用double date rate,即双数据速率,即一个时钟周期内发送两个bit(当前指令下不需要);
当我们设定好结构体相关字段后,我们就可以调用HAL_QSPI_Command接口发送请求,由于这条指令只有发送没有接收,所以我们不需要调用HAL_QSPI_Receive获取数据。
这其实是Quad-SPI间接模式的使用(即通过STM32 Quad-SPI寄存器来控制W25Q64JV)。
接下来我们再分析一下这条函数调用
HAL_QSPI_AutoPolling(&hqspi, &cmd, &conf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
首先我们需要看下cmd这个变量,我们设定了Instruction为W25Q64JV_STATUS_REG1(读W25Q64JV状态寄存器1的值);
我们又定义了一个QSPI_AutoPollingTypeDef类型的变量
字段Match设定匹配值;
字段Mask设定需要匹配掩码;
字段Interval设定状态轮询的周期;
字段StatusBytesSize设定读取的状态字节数;
字段MatchMode设定匹配的模式(与、或);
字段AutomaticStop设定匹配成功后是否自动停止;
由于我们需要检测状态寄存器1的bit1位是否为1(WEL位为1);故我们如下设定:
这其实是Quad-SPI状态轮询机制的使用。
QSPI_W25Q64JV_WriteEnable这个接口就是W25Q64JV进行擦除、页编程必须要调用的。
至于其他接口的实现细节,大家可以阅读源码分析。
由于要注意的是QSPI_W25Q64JV_Write这个接口实现细节(这个接口会根据当前Flash内部的数据来决定是否需要先进行擦除然后再页编程)。