**************************************************************************
东方青学STM32F7
———————转载请注明ICKey————————
**************************************************************************
STM32F7-Discovery驱动外部Quad-SPIFlash
-----东方青
关于Quad-SPI,说实话,以前就听过它的存在,也仅仅就是知道他的存在,一直都没有使用过,但是,知道一点的是,它的速度比标准的SPI通信协议的速度可不是一个等级的,它的带宽是标准SPI的4倍,那么速度呢?呵呵!你懂的。那么下面就得先了解一下Quad-SPI。
Quad-SPI主要是针对SPIFlash而设计的SPI总线协议,它的存在就是为了彻底摒弃普通Flash和EEPROM器件必须经历的写时序,并且最大程度的增加带宽和读写速度。在STM32F7系列MCU的参考手册中,有如下介绍:
模式挺多!但是我想我关注的应该是内存映射模式,其他模式也就是速度快了一点,和普通Flash一样,而内存映射模式就是MCU将Flash的内存空间进行映射,就像使用自己的内存空间一样。而普通模式就算是用来存放MCU的程序,那也没有到像使用MCU自己 的内存空间的级别。当然遗憾的一点是,STM32F7虽然可以映射4GB的内存空间,但是遗憾的是,只能寻址256Mbit的内存空间,啊啊啊!而STM32F7-Discovery板卡板载的QuadSPIFlash只有128Mbit。
基本的关于QUADSPI的框图如下:
嘿嘿!不用多说,有图有真相。具体参看STM32F7参考手册!
再看看命令序列图:
从图中可以看出,可以分为5个指令序列,但是这不重要,重要的是任一阶段都可以跳过,只要包含指令、地址、交替字节、数据这几个阶段之一就好。这就是摒弃那些好多的复杂的时序啦!有点nandFlash的味道了。
OK!Quad-SPI总线的介绍到此结束!是时候让它为我所用的时候了。自己写驱动?嘿嘿!这是不可能的!倒不是驱动复杂,而是没必要!而且到了F7的水平,主要要求的是快速开发和复用!理解原理就好!那么下面就开始移植ST的关于Quad-SPI的驱动到我们自己的程序。
(1)确定硬件
先看原理图:
再看Datasheet,如下图:
嘿嘿!硬件搞定!证据充足!知道自己在玩啥了!知道该操作那些GPIO口和该配置那些外设了!就这么愉快的确定了!哈哈!
(2)添加STM32Cube库与Quad-SPI相关的驱动程序
然后打开控制宏进行编译调用,如下图:
将宏HAL_QSPI_MODULE_ENABLED的注释干掉,才能使工程能够调用STM32Cube库的与Quad-SPI相关的底层驱动程序。
(3)添加Flash相关头文件,并调用
因为板卡使用的Quad-SPIFlash的型号为N25Q128A,通过资料了解,其为128Mbit即16MByte大小的Flash。因此要操作外部器件,就必不可少的需要操作外部设备的寄存器,往相应的寄存器写入适当的数据进行对外设的控制,所以就算是Quad-SPIFlash也是需要的,所以,需要添加其寄存器的地址和命令啦!如果没有,那就只能参考SPIFlash的Datasheet进行自己编写喽!大不了苦逼一点!但是很幸运,ST已经帮我们干了这件事!所以直接使用即可!
在STM32CubeF7库中的如下路径找到n25q128a文件夹,如下图:
在自己的工程Libraries目录下,创建文件夹Components,然后在此文件夹中存放从库中拷贝过来的n25q128a文件夹。如下图:
在工程中的与Quad-SPI相关的设备头文件(我的文件名是QSPI_Device.h)中添加如下包含:
各种方法可以使用!萝卜白菜各有所爱!我就是用这种方法装B。
(4)在STM32Cube库中,找到与Quad-SPI相关的设备驱动代码,然后Copy到工程当中,我分别存放在QSPI_Device.c和QSPI_Device.h文件中。
(5)移植中断处理函数。
在STM32CubeF7库的stm32f7xx_hal_qspi.c文件中,存在这样一个函数:void HAL_QSPI_IRQHandler(QSPI_HandleTypeDef *hqspi);它的作用是Quad-SPI接口和控制器的中断处理函数,它已经由ST进行编写,并提供给用户使用,所以直接移植它来使用即可。查看中断向量表,找到与QSPI相关的中断向量地址,以此作为函数名在stm32f7xx_it.c文件中建立QSPI的中断函数,如下图:
完事之后!现在可以Rebuild编译了!基本上可以达到无警告无错误。
(6)代码分析
其实关于Flash的代码都是一样的,无非这几方面:
<1>通信总线相关
A.初始化总线
B.向总线写数据
C.从总线读数据
<2>Flash存储相关
A.初始化
B.擦除Flash(包括扇区擦除、页擦除、全局擦除等)
C.写Flash(字节写、扇区写、页写等)
D.读Flash(读字节、扇区、页等)
通过以上,基本上就可以确定了,只要Quad-SPI总线通信配置好,能够正常的通信,那么操作Flash就是一件很简单的事了。因为一切都是移植了ST的例程,成功是必然,但是呢,还是很有必要去分析分析代码的实现。
这里主要分析初始化的过程,至于Flash的读写,无非就是操作寄存器写数据和读数据。
如下为初始化函数:
如上图可知,初始化分为5个部分,过程顺序分别如下:
A.将Quad-SPI的配置恢复默认,而对于QSPIHandle.Instance = QUADSPI;语句,QUADSPI是一个基地址,并且还是Quad-SPI的基地址。定义如下:
所以恢复默认配置的是QUADSPI的寄存器的配置。
B.系统板级的初始化BSP_QSPI_MspInit(&QSPIHandle, NULL);。
嘿嘿!有点意思!且看它的原型:
从上图中,分成了3部走,一堆时钟要打开(可以看到是Quad-SPI的时钟和各个GPIO的时钟)、初始化GPIO端口、启用中断,使能中断。这的确是板级初始化啊。
C.Quad-SPI初始化。
这涉及到两个结构体类型,QSPI_HandleTypeDef和QSPI_InitTypeDef。原型如下:
结构体类型QSPI_HandleTypeDef基本上是关乎所有QSPI的配置了,在这里只讨论与初始化相关的结构体成员Init,它管理着所有关于QSPI通信的初始化参数配置,类型为QSPI_InitTypeDef。原型如下:
嘿嘿!成员很齐哦!Quad-SPI基于AHB总线时钟的分频数、缓存FIFO的长度阀值、采样时钟参数、Flash的长度、指定芯片选择高时钟、时钟的模式、Flash的ID和双闪存模式状态。
所以通过配置全局变量QSPIHandle的Init成员,最后通过HAL_QSPI_Init(&QSPIHandle)函数进行初始化Quad-SPI设备。那么且看它是如何配置的:
首先是关于Quad-SPI时钟的配置,分频数为1,那么就得看看系统架构图了,如下图:
从上图可看到Quad-SPI的时钟来源于一个32位AHB总线,从前面系统的配置可知,AHB总线的频率被配置为最高频率216MHz,再看时钟树,如图:
可以看到了时钟的来源走线,再看看Quad-SPI自己对时钟的要求:
嘿嘿!一切都清楚了,Quad-SPI的时钟分频系数在寄存器QUADSPI_CR的[31...24]位设置,在程序中被设置值为1,即为2分频,即为261MHz/2=108MHz。
缓存FIFO的长度阀值的设置:还是QUADSPI_CR寄存器,并且是QUADSPI_CR[12...8]位,且看下图解释:
上图已详细解释,在程序中,阀值设为4。
采用时钟参数的设置,还是QUADSPI_CR寄存器,并且是QUADSPI_CR[4]位,如下图:
在程序中设置了半个周期转向移位。
最后就是Flash的长度、指定芯片选择高时钟、时钟的模式、Flash的ID和双闪存模式状态的配置了,其实无非也就是配置寄存器,当然,在调用库函数时,这些寄存器是看不到的,在这里只是提供一个分析的方法。具体参考程序、参考手册和Quad-SPIFlash的Datasheet即可。配置好相应参数之后,直接调用HAL_QSPI_Init(&QSPIHandle)函数初始化,就完事了。
D.重置QSPI内存。
先看看原型,如下图:
从上图可以看到,重置内存分为了四个部分,而且出现了一个新的结构体类型QSPI_CommandTypeDef,且看原型:
从原型和它的注释,基本可以确定此结构体类型是QSPI命令结构定义类型,也就是说所有的命令都将使用此结构体进行玩耍。而且从QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi)函数的原型可知,当配置好QSPI_CommandTypeDef的变量值之后,就是一堆发送命令是语句,并且每次发送都会改变QSPI_CommandTypeDef结构成员Instruction的值。呵呵!这有点意思!那么先看看QSPI_CommandTypeDef结构类型的成员各自代表的含义:
OK!基本上可以确定了。在函数uint8_t QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi)的最前面先配置结构体类型QSPI_CommandTypeDef的局部变量s_command的成员取值,然后就是发送命令了。
关于命令成员的配置,也是QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi)函数的第一部分,配置如下:
我已经详细注释,不在多说!但是可以很明确一点是,其他配置不变,当需要发送不同指令时,更改结构体成员Instruction的取值即可。所以QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi)第二步要干的事是复位Quad-SPIFlas器件,第三步要干的事是复位整个Quad-SPIFlash的存储空间,使其恢复默认值,其实我觉得可以理解成整片擦除Flash,默认值通常为0XFF吧。
关于QSPI_ResetMemory(QSPI_HandleTypeDef *hqspi)函数的第四步,是配置STM32F7的Quad-SPI控制器的工作方式,而且从调用可以看出是配置为自动轮询方式工作,并且要等待Flash准备好工作。先看看函数的原型:
嘿嘿!有点意思,又多出了一个结构体类型QSPI_AutoPollingTypeDef的局部变量s_config,而且根据注释可知,此结构体类型为自动轮询工作方式的结构类型。而关于函数uint8_t QSPI_AutoPollingMemReady(QSPI_HandleTypeDef *hqspi, uint32_t Timeout)的原型内容,也无非是准备并配置好命令,然后命令结构和自动轮询结构一起,配置Quad-SPI控制器和Quad-SPIFlash。所以现在主要值得研究的是新出现的QSPI_AutoPollingTypeDef结构。且看结构原型:
呵呵!每个成员所代表的意义就如上了,这就还需要另外的证据,如下:
寄存器就不分析了!!且看它的功能配置讲解即可。嘿嘿!证据就充足了。那么关于其在函数中的配置也就没必要再多解释了,再看看设置函数的原型,其实调用的HAL库,但是不管它,我们只要知道他的作用即可,如下:
嘿嘿!函数功能是在阻塞模式下配置QSPI自动轮询方式,并且可配置超时机制。关于参数就不在多说,看注释和配置就可知。现在基本就明白了,这一步的工作主要是配置STM32F7的Quad-SPI控制器接口也Quad-SPIFlash的通信机制。嘿嘿!这一步就基本完事了。
E.配置虚拟存储周期
说实话,这一步的配置,如果没有仔细的研究研究STM32F7系列MCU的参考手册(关于Quad-SPI部分),还真不太理解这是啥意思,“虚拟”!呵呵!有点意思!先看看函数原型吧!这也是初始化配置的最后一步配置(第5步),如下图:
貌似这个函数要干的事不少啊!整整分为7个步骤来走!而且一眼可以看出来它所要做的事。步骤顺序如:配置命令结构变量并将指令设置为READ_VOL_CFG_REG_CMD(意为读取Quad-SPIFlash的volatile寄存器的值,指令为RDVCR)、发生READ_VOL_CFG_REG_CMD指令给Quad-SPIFlash(n25q128a)、接收要读取的数据(RDVCR寄存器的值)、使能对Quad-SPIFlash的写操作、更新Quad-SPIFlash的volatile配置寄存器(修改指令值为WRITE_VOL_CFG_REG_CMD)、发送volatile寄存器写指令WRITE_VOL_CFG_REG_CMD到Quad-SPIflash(指令为WRVCR)、最后做数据传输。
从以上步骤解释基本上就可以确定,主要是对Quad-SPIFlash(n25q128a)的volatile配置寄存器进行读和写,那么下面看看这个寄存器的解释,通过查找n25q128aDatasheet有:
指令就如上了,那么再看看关于volatile配置寄存器的解释:
寄存器都分解如上了!没什么可解释的,那么还有一个问题是,这操作的到底是啥东东哇!弄了半天意思还是有点模模糊糊的!其实呢,这里操作的是5个阶段中的空周期阶段,如下图:
没错!就是它!那么搞的这么复杂,到底是要干嘛呢?其实在参考手册中已经有了解释,如下图:
所以,在uint8_t QSPI_DummyCyclesCfg(QSPI_HandleTypeDef *hqspi)函数的最后有如下调用:
其所调用的参数hqspi为形参、HAL_QPSI_TIMEOUT_DEFAULT_VALUE为设定超时的时间值、
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|