【海东青电子原创文章,转载自:STM32硬件基础--QaudSPI总线读写片外FLASH(一) - 简书】
(文中代码下载地址:https://github.com/haidongqing/qspi-readid)
STM32F746G-DISCO开发板上,通过QaudSPI接口连接了一片MICRON公司的NOR FLASH,型号为 N25Q128A13EF840E 。这里涉及了2个方面的技术问题:
1)Qaud SPI 总线接口;
2)ST MCU如何通过Qaud SPI 接口读写NOR FLASH。
先来看看什么是Qaud SPI,它是SPI的“升级版”,也有简写成QSPI的,称为四线SPI,可以理解为在传统的SPI总线上,扩充了4条“双向”数据线。这样,Qaud SPI就有6根线:片选信号CS、时钟CLK、数据线D0-D3(SPI是4根线:CS、CLK、MISO、MOSI)。功能的增强一定带来新的复杂性,QSPI也不例外 ---- 相比较SPI而言,QSPI不再是只按照CLK的时钟时序读或写数据那么简单了,而是需要先发送“命令”才能读写数据。所以,QSPI总线的驱动、编程要复杂一些。
为了简化QSPI的编程、使用,STM32系列MCU在内部增加了一个QSPI控制器,通过一些相关的寄存器控制对QSPI的操作。总体而言,QSPI有3种工作模式:单线(Single,或Extended)、双线(Daul)和四线(Qaud),其中四线SPI最常用(显然,这种模式下数据吞吐率最大、效率最高),这也是我们下面要讨论的重点。
图一
上图是STM32F746用户手册中QaudSPI功能框图(单BANK的情形,746也支持双BANK模式,此处不作讨论),可见MCU与FLASH之间就是6根信号线。
既然我们关心的是如果读写FLASH,就得先看看FLASH这个外部器件对读写数据有哪些要求(时序,读写命令等)。
首先,FLASH在进入“常规工作”状态之前,需要先进行“配置”,即参数初始化,比如QSPI总线模式、读数据延迟周期等。N25Q内部有2种类型的配置寄存器:Nonvolatile Configuration Register 和 Volatile Configuration Register,前者是非易失的,后者是易失的。读配置寄存器有相应的命令:
图二
图三
这是很简单的读取命令,MCU通过QSPI总线发送一个命令字 B5 ,FLASH接收到之后,在数据线上送出配置寄存器中的内容。写配置寄存器如下:
图四
发送2个字节:命令字 B1 + 一字节配置内容,总共16 bit。读写配置寄存器的时序图比较简单,但这个是基础,读懂了图二、图四的时序图,就能读懂下面稍复杂一些的时序图。
第二,读出数据的具体操作。最简单的,是先读出FLASH的ID。任何一个厂家的FLASH器件,内部都有一个device id,以及一些扩展信息,说明这个FLASH的生产商、芯片容量等基础参数。
图五
测试时,使用了READ ID(9E) 命令,它只能用于Extended模式。注意上图的 Notes 2 :
图六
表明此命令不需要地址,读延迟周期(Dummy clock cycles)为0,即不需要。请注意 Dummy clock cycles 这个参数,这是个非常重要的参数,在读取FLAHS数据时,从发出读命令,到FLASH将数据送上数据线,是需要一定时间的,这个时间就是 Dummy clock cycles,后面讲到对FLASH读数据时还会重点提到。正确读出ID的结果会是这样的:
图七
读ID ,得到的数据(前3个)是:20h,BAh,18h。
(注:JEDEC,Joint Electron Device Engineering Council,JEDEC固态技术协会,EIA的分支,FLASH厂家的ID应该是由这个标准化组织分配的)
好了,我们就只先做最简单的一步:如何读出FLAHS的ID。MCU这一端的工作,主要是使用CubeMX正确配置QaudSPI,并编写代码访问FLASH。先看看STM32F746G-DISCO 开发板的原理图、确定QSPI用到的pin:
图八
图九
图十
运行STM32Cube,选择STM32F746NG芯片,配置RCC、SYS和时钟:
图十一
图十二
图十三
最后是主角:QaudSPI:
图十四
上图中,
1、选择QSPI的四线模式。
2、FLASH的时钟频率,选择对HCLK 2分频,就是 216/(1+1) = 108 MHz,正好是N25Q芯片的最高可用频率(见;N25Q数据手册)。---- 之前这个参数设置成了2,是错误的,感谢网友 “海鸦” 的提醒!!
3、QSPI针对读写数据设计了FIFO缓冲区,这是缓冲区长度。
4、选择延迟半个时钟采样周期、利于适应FLASH的可能延迟,见图十五。
5、N25Q容量是128Mbit,即16MByte,那么按字节寻址的话有16M个地址,16M = 2的24次方,所以填写24。
6、连续2个命令之间需要的延迟间隔时间(时钟周期)。QSPI每次向FLASH发送命令,片选信号CS都有一个拉低、再拉高的过程,2个命令之间CS保持高电平(无效状态),这个高电平保持多久,称为 Chip Select High Time(见图十六)。N25Q要求这个时间最小是50ns(见图十七),FLASH时钟是108MHz,50ns/(1/108MHz) = 5.4,即最小需要5.4个时钟周期,所以这里选择了6。
7、Clock Mode,空闲时CLK的电平状态,似无大碍,这里选了low(模式0;如果high,是模式3)。
图十五
图十六
图十七
QaudSPI的配置还没完事,还需要检查GPIO的复用功能是否配置正确(这是有血的教训的,见这里)。CubeMX默认的QSPI管脚配置是:
图十八
需要按下图重新分配pin:
图十九
生成KEIL代码。main.c中,函数 MX_QUADSPI_Init() 是CubeMX生成的QSPI的初始化函数。
先定义一个常数数组,包含了N25Q的ID信息:
const uint8_t bN25Q_ID[BUFFERSIZE] = {0x20, 0xBA, 0x18}; //Manufacturer ID, Memory Type, Memory Capacity
然后读FLASH:
图二十
sCommand 是 QSPI_CommandTypeDef 类型的结构体,负责配置命令模式、命令字、数据格式、数据长度等关键信息,然后调用 HAL_QSPI_Command() 发送命令。在 HAL_QSPI_Command() 内部又调用了 QSPI_Config(),通过写QSPI相关的一些寄存器最终实现发送命令的操作。在调用 QSPI_Config() 时,使用了一个重要的参数:QSPI_FUNCTIONAL_MODE_INDIRECT_WRITE,它约定了QSPI在此次与FLASH的会话中,使用的是QSPI的“间接操作模式”(QSPI有三种操作模式,详情将在下一讲中介绍,预留链接)。
读出ID结果如下:
图二十一
读出的数值确实是 20h,BAh,18h;开发板背面的LED慢闪,表明读取ID正确(如果出错,LED将快闪)。
本例的代码下载地址:https://github.com/haidongqing/qspi-readid。
下一节将介绍QSPI的三种操作模式、并实现读FLASH数据:《STM32硬件基础--QaudSPI总线读写片外FLASH(二)》。
补充:关于QaudSPI的时钟频率
图二十二
分频系数数值+1,是真正的分频倍数。为了2分频,应该将 PRESCALER 参数设置为1;如果设置为2,就是3分频了,QaudSPI时钟频率更低了,程序仍能正常执行,只不过读写速率降低了、效率低了。经测试,如果将 PRESCALER 参数设置为0,QaudSPI时钟频率==HCLK 了,超出了最大频率范围,读出的ID是错误的。
TM32F746的文档在介绍QaudSPI时,上来就说了三种工作模式:
图一
1、“间接”模式
2、状态轮询模式
3、内存映射模式
这是什么意思呢?本人当时读文档读到此处时,是一脸的懵逼。有了前一讲 《STM32硬件基础--QaudSPI总线读写片外FLASH(一)》的基础,再来理解就容易多了。QSPI的这三种工作模式,是相对于ST MCU而言的,是为了简化MCU对QSPI器件(多为FLASH)而设立的。MCU针对FLASH主要就是做了两件事:一个是读写数据等收发命令,还有一个是:要经常查询FLASH的状态。比如写入数据,在写FLASH之前,必须先进行擦除,擦除需要时间,后续的写入命令必须等待擦除完成。怎么能知道是否擦除完成了呢?就得不断查询FLASH的状态。所以,常用的读写等命令就对应了上面的模式一:间接模式(ST MCU又把它具体分为间接写、间接读两种模式),而查询FLASH状态就对应了模式二:状态轮询。
至于模式三:内存映射模式,是为了简化读FLASH的操作。实际应用中,片外FLASH大多用于图片等静态数据存储,其写入是在烧写MCU时通过ST-LINK之类软件工具完成的。MCU运行时,基本上都是只从FLASH中读出数据。为了简化这个操作,可以使用模式三,将片外FLASH的地址映射到片内的地址空间(BANK1的起始地址为0x9000 0000),从而可以像访问片内地址一样来访问外部FLASH。不难理解,模式三只能用于读、不能写。(而且,映射的最大地址空间不能超过256MB。)
下面先来看看读数据的实现(代码下载地址:https://github.com/haidongqing/qspi-readflash)。我们采用四线快读方式:
图二
注意注解5:
图三
N25Q要求在这种读数据模式下,需要10个 Dummy clock cycles。时序图:
图四
发出read指令后,需要等待10个 Dummy cycles。在读操作之前,需要先对FLASH设置这个参数。在《STM32硬件基础--QaudSPI总线读写片外FLASH(一)》中提到,N25Q有2个配置寄存器:Nonvolatile Configuration Register 和 Volatile Configuration Register,前者是非易失的,后者是易失的,这里派上了用场,我们的方式是修改易失的 Volatile Configuration Register(代码中,MCU每次上电都要做这个操作):先读出 Volatile Configuration Register 的值,设置 Dummy cycles,之后再写回 Volatile Configuration Register。
图五
因为涉及了FLASH的写操作,先要使能写操作、然后才能写。代码中专门有一个函数 QSPI_WriteEnable() 来使能“写”:
图六
FLASH的写使能状态保存在FLASH的状态寄存器 status register 中:
图七
写状态寄存器、对FLASH编程(写)、擦除时,需要使能写。发出使能写指令后,需要查询该指令是否顺利完成了,即前面提到的“模式二”。这里使用了一个函数:HAL_QSPI_AutoPolling()。查询时,我们只关心位于 bit1 的 Write enable latch 标志位是否被置1了,所以 sConfig 结构体(轮询模式参数配置)中,屏蔽字(.Mask)设为0x02(bit1有效),匹配目标(.Match)设为0x02。
完整的修改Status Resgister 的代码在函数 QSPI_DummyCyclesCfg() 中实现,然后读取FLASH数据:
图八
正确读出数据后,开发板上的LED慢闪;如出错,LED长亮。FLASH出厂时,数据全为0xFF。本实验中,先用预先提供的 set_01234.hex 文件烧写开发板、并复位运行,将在FLASH地址起始处写入 “0123456789” 十个字符,然后再运行上面例程,即可看到正确的读出结果。
再来看看模式三,内存映射模式下,如何读取FLASH数据,代码详见:https://github.com/haidongqing/qspi-readflash-mapped。
图九
核心是函数:HAL_QSPI_MemoryMapped(),在其内部使用参数 QSPI_FUNCTIONAL_MODE_MEMORY_MAPPED 调用了 QSPI_Config(),从而实现对模式三的设定:
/* Call the configuration function */
QSPI_Config(hqspi, cmd, QSPI_FUNCTIONAL_MODE_MEMORY_MAPPED);
然后按内部地址方式读出数据:
图十
MCU对片外FLASH的操作以读为主,到此,有关QSPI使用的关键点已经讲完了。有兴趣的同学,请继续下一讲:《QSPI如何写FLASH数据》。
作者:海东青电子
链接:https://www.jianshu.com/p/77f7bd0817e9
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
【海东青电子原创文章,转载请注明出处:STM32硬件基础--QaudSPI总线读写片外FLASH(三) - 简书】
N25Q是NOR FLASH,每次写入数据之前,需要先进行擦除。而且,在发送写数据指令之前,需要先“使能写”(见上一篇:STM32硬件基础--QaudSPI总线读写片外FLASH(二))。执行写之后,还需要查询FLASH的状态寄存器,看看是否写入完毕。
完整的示例代码的下载地址:https://github.com/haidongqing/qspi-writeflash。
第一步是擦除:
图一
擦除之前,也需要先使能写。发出擦除指令后,要查询此指令是否完成,函数 QSPI_AutoPollingMemReady() 查询FLASH是否进入就绪状态:
图二
.Match、.Mask 的值为什么是0x00和0x01呢?来看看N25Q的数据手册中关于状态寄存器的说明:
图三
需要查询寄存器的bit0是否为0,所以屏蔽字是0x01,目标值是0x00。
第二步:写入。
图四
待写入的数据(字节类型)保存在 aTxBuffer 中,长度为 BUFFERSIZE,赋值给了 .NbDate 。执行写入后,调用函数 QSPI_AutoPollingMemReady() 查询FLASH是否写入完毕。
以上只是最简单的写FLASH示例,实际使用时,最重要的是要处理跨扇区写的问题,即 当BUFFERSIZE大于FLASH的扇区size时,写第二个扇区时,要重新设置 sCommand.Address(递增;之后的其他扇区照此类推),可参考ST官方的例程 -- “QSPI_ReadWrite_IT” :
图五
(完)
作者:海东青电子
链接:https://www.jianshu.com/p/3d8b6f2c95a3
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。