ITPub博客

首页 > 嵌入式/内核开发 > 嵌入式/内核开发 > 《STM32H7R7开发指南 V1.1 》第三十八章 XSPI实验

《STM32H7R7开发指南 V1.1 》第三十八章 XSPI实验

原创 嵌入式/内核开发 作者:正点原子 时间:2025-09-05 10:06:16 0 删除 编辑

第三十八章 XSPI实验


       本章,我们将介绍STM32H7R7的XSPI功能,并使用STM32H7R7自带的XSPI来实现对外部NOR Flash的读写,并将结果显示在LCD模块上。

       本章分为如下几个小节:

       38.1 XSPI及NOR Flash芯片简介

       38.2 硬件设计

       38.3 程序设计

       38.4 下载验证


        38.1 XSPI及NOR FLASH芯片介绍


       38.1.1 XSPI简介 

       XSPI是扩展的SPI总线接口,即Extended-SPI interface,支持大多数外部串行存储器,如串行PSRAM,串行NAND和串行NOR Flash。XSPI 是一种专用的通信接口,连接单、双、四、八或十六(条数据线)SPI Flash存储器。STM32H7R7具有XSPI接口,支持如下三种工作模式:

       1、间接模式:所有操作都使用XSPI寄存器来执行,比如预置命令、地址、数据和传输参数。

       2、状态轮询模式:周期性读取外部Flash状态寄存器,并根据需求将读取到的寄存器值与特定的值进行指定的逻辑位运算,直到得到特定的结果。

       3、内存映射模式:外部Flash映射到微控制器地址空间,系统将其视作内部存储器,支持读和写操作。

       STM32H7R7的XSPI接口具有如下特点:

       l 支持三种工作模式:间接模式、状态轮询模式和内存映射模式。

       l 支持内存映射方式的读写。

       l 支持单,双,四,八,十六进制通信。

       l 支持双闪存模式,可以并行访问两个Flash。

       l XSPI模式访问单个16位内存。

       l 支持SDR(单倍率速率)和DDR(双倍率速率)模式。

       l 针对间接模式和内存映射模式,完全可编程操作码。

       l 针对间接模式和内存映射模式,完全可编程帧格式。

       l 支持在读方向上对内存的封装式访问。

       l 支持HyperBus™。

       l 集成 FIFO,用于接收和传输。

       l 允许 8、16 和 32 位数据访问。

       l 支持DMA协议。

       l 具有适用于间接模式操作的DMA通道。

       l 在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断。

       l 双芯片选择支持(NCS1和NCS2)。


       38.1.1.1 XSPI框图

       STM32H7R7的XSPI单闪存模式的功能框图如图38.1.1.1.1所示:


图38.1.1.1.1.a XSPI框图(八线连接)


图38.1.1.1.1.b XSPI框图(双四线连接)


       上图分别展示了XSPI连接单八线外部Flash和XSPI连接双四线外部Flash的XSPI框图,上图中左边为STM32H7R7内部XSPI的框图,可以看到XSPI连接到64位AXI总线以及32位AHB总线上,此外还有5条XSPI的内部信号,如下表所示:


表38.1.1.1.2 XSPI接口引脚


       xspi_ker_ck,用于通讯过程的时钟。可以选择的时钟源有:hclk5(即AHB5)、pll2_s_ck和pll2_t_ck,实验中我们选择pll2_s_ck。经过sys_stm32_clock_init函数的配置,pll2_s_ck时钟频率为400MHz。xspi_ker_ck还需要经过一个分频器出来的时钟频率才作为XSPI的实际使用的时钟频率,该分频器的分频系数由XSPI_DCR2寄存器的PRESCALER[7:0]位设置,范围是:0~255。

       xspi_hclk,用于操作XSPI寄存器的时钟。时钟源来自hclk5,同样是经过sys_stm32_clock_init函数的配置,配置后HCLK5时钟频率为300MHz。

       xspi_aclk必须大于或等于XSPI内核时钟(xspi_ker_ck)。

       xspi_it,中断请求信号线。在达到FIFO阈值、超时、操作完成以及发送访问错误时产生中断。

       上图左边部分就是XSPI 内核。

       我们重点看看上图右边的XSPI接口引脚,以八线连接为例,通过12根线与外部Flash芯片连接,包括:8根数据线(IO0~7)、2根时钟线(CLK和NCLK(可选,使用差分时钟时使用))、1根数据选通线(DS)和1根片选线(NCS),具体如下表所示:


表38.1.1.1.3 XSPI接口引脚


       我们知道普通的SPI通信一般只有一根数据线(MOSI/MISO,发送/接收用),而XSPI则具有最多16根数据线,所以XSPI的单时钟数据传输率可以是普通SPI的至少8倍,且XSPI还支持DDR模式,单个时钟可以在但跟数据上传输2bits数据,可以大大提高通信速率。如果使用双闪存模式,同时访问两外部Flash。我们开发板有板载单个八线NOR Flash的版本和板载两个四线NOR Flash的版本。

       接下来,我们给大家简单介绍一下STM32H7R7 XSPI接口的的几个重要知识点。


       38.1.1.2 XSPI命令序列

       XSPI 通过命令与FLASH通信,每条命令包括:指令、地址、交替字节、空指令和数据这五个阶段,任一阶段均可通过配置XSPI_CCR寄存器相关字段跳过,但至少要包含指令、地址、交替字节或数据阶段之一。

       nCS 在每条指令开始前下降,在每条指令完成后再次上升。XSPI读命令时序,如图38.1.1.2.1所示:


图38.1.1.2.1 XSPI读命令时序


       XSPI传输有5个阶段,接下来我们分别介绍。


       ① 指令阶段

       此阶段通过XSPI_CCR寄存器的位ISIZE[1:0]指定发送到FLASH的指令大小(8/16/24/32位)。要发送到FLASH芯片的指令可由寄存器XSPI_IR[31:0]来设置。通过在XSPI_CCR中设置IDTR,可以在时钟的每个上升沿和下降沿上以DTR(双传输速率)模式发送指令。当寄存器XSPI_CCR的位IMODE[2:0]=000,则表示跳过指令阶段,命令序列从地址阶段开始(如果存在)。


       ② 地址阶段

       此阶段可以发送1~4字节地址给FLASH芯片,指示要操作的地址。地址字节长度由XSPI_CCR[13:12]寄存器的ADSIZE[1:0]字段指定,0~3表示1~4字节地址长度。在间接模式和轮询模式下,待发送的地址由XSPI_AR寄存器指定。地址阶段同样可以以单线/双线/四线模式发送,通过XSPI_CCR[11:10]寄存器的ADMODE[2:0]这三个位进行配置,如ADMODE [2:0]=000,则表示无需发送地址。


       ③ 交替字节(复用字节)阶段

       此阶段可以发送1~4字节数据给FLASH芯片,一般用于控制操作模式。待发送的交替字节数由XSPI_CCR[17:16]寄存器的ABSIZE[1:0]位配置。待发送的数据由XSPI_ABR寄存器中指定。交替字节同样可以以单线/双线/四线模式发送,通过XSPI_CCR[15:14]寄存器的ABMODE[2:0]这三个位配置,ABMODE[2:0]=000,则跳过交替字节阶段。


       ④ 空指令周期阶段

       在空指令周期阶段,在给定的1~31个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给FLASH芯片留出准备数据阶段的时间。这一阶段中给定的周期数由XSPI_CCR[22:18]寄存器的DCYC[4:0]位配置。 若DCYC为零,则跳过空指令周期阶段,命令序列直接进入下一个阶段。


       ⑤ 数据阶段

       此阶段可以从FLASH读取/写入任意字节数量的数据。在间接模式和自动轮询模式下,待发送/接收的字节数由XSPI_DLR寄存器指定。在间接写入模式下,发送到FLASH的数据必须写入XSPI_DR寄存器。在间接读取模式下,通过读取XSPI_DR寄存器获得从 FLASH接收的数据。数据阶段同样可以以单线/双线/四线模式发送,通过XSPI_CCR[25:24]寄存器的DMODE [2:0]这三个位进行配置,如DMODE [1:0]=00,则表示无数据。

       以上就是XSPI数据传输的5个阶段,其中交替字节阶段我们一般用不到,可以省略(通过设置ABMODE[2:0]=000)。

       另外说明一下,XSPI信号接口协议模式包括:单线SPI模式、双线SPI模式、四线SPI模式、SDR模式、DDR模式和双闪存模式。这些模式请大家自行参考官方手册。


       38.1.1.3 XSPI三种功能模式

       前面已经提到过XSPI的三种功能模式:间接模式、状态标志轮询模式和内存映射模式。下面对这三个功能模式分别简单介绍。


       ① 间接模式

       在间接模式下,通过写入XSPI寄存器来触发命令,通过读写数据寄存器来传输数据。

       当FMODE=00 (XSPI_CR[29:28])时,XSPI处于间接写入模式,在数据阶段,将数据写入数据寄存器(XSPI_DR),即可写入数据到FLASH。

       当FMODE=01时,XSPI处于间接读取模式,在数据阶段,读取XSPI_DR寄存器,即可读取FLASH里面的数据。

       读/写字节数由数据长度寄存器(XSPI_DLR)指定。当XSPI_DLR=0xFFFFFFFF时,则数据长度视为未定义,XSPI 将持续传输数据,直到到达FLASH结尾(FLASH容量由 XSPI_DCR1[20:16]寄存器的DEVSIZE[4:0]位定义)。如果不传输任何数据,则DMODE[2:0] (XSPI_CCR[26:24])应设置为000。

       当发送或接收的字节数(数据量)达到编程设定值时,如果TCIE=1,则TCF置1并产生中断。在数据量不确定的情况下,将根据DEVSIZE[4:0]定义的FLASH大小,在达到外部SPI FLASH的限制时,TCF置1。

       在间接模式下有三种触发命令启动的方式,分别是:

       (1)当不需要发送地址(ADMODE[2:0]==000)和数据(DMODE[1:0]==00)时,对INSTRUCTION[31:0](XSPI_IR[31:0])执行写入操作。

       (2)当需要发送地址(ADMODE[2:0]!=000),但不需要发送数据(DMODE[1:0]==00),对ADDRESS[31:0](XSPI_AR)执行写入操作。

       (3)当需要发送地址(ADMODE[2:0]!=000)和数据(DMODE[1:0]!=00)时,对DATA[31:0] (XSPI_DR)执行写入操作。

       如果命令启动,BUSY位(XSPI_SR的第5位)将自动置1。


       ② 状态标志轮询模式

       将FMODE字段(XSPI_CR[29:28]) 设置为10,使能状态标志轮询模式。在此模式下,将发送编程的帧并周期性检索数据。每帧中读取的最大数据量为4字节。如果XSPI_DLR请求更多的数据,则忽略多余部分并仅读取4个字节。在 XSPI_PIR 寄存器指定周期性。

       在检索到状态数据后,可在内部进行处理,以达到以下目的:

       (1)将状态匹配标志位置 1,如果使能,还将产生中断。

       (2)自动停止周期性检索状态字节。

       接收到的值可通过存储于XSPI_PSMKR寄存器中的值来进行屏蔽,并与存储在 XSPI_PSMAR寄存器中的值进行或运算或与运算。

       若是存在匹配,则状态匹配标志置1,并且在使能了中断的情况下还将产生中断;如果APMS 位置1,则XSPI自动停止。

       在任何情况下,最新的检索值都在XSPI_DR中可用。


       ③ 内存映射模式

       在配置为内存映射模式时,外部FLASH器件被视作内部存储器,只是存在访问延迟。在该模式下,仅允许对外部 FLASH 执行读取操作。将XSPI_CR寄存器中的FMODE设置为11可进入内存映射模式。当主器件访问存储器映射空间时,将发送已编程的指令和帧。另外数据长度寄存器(XSPI_DLR)在内存映射模式中无意义。

       XSPI外设若没有正确配置并使能,禁止访问XSPI Flash的存储区域。即使FLASH容量更大,寻址空间也无法超过256MB。如果访问的地址超出FSIZE定义的范围但仍在256MB 范围内,则生成总线错误。此错误的影响具体取决于尝试进行访问的总线主器件:

       (1)如果为Cortex® CPU,则会在使能总线故障时发生总线故障异常,在禁止总线故障时发生硬性故障(hard fault) 异常。

       (2)如果为 DMA,则生成 DMA传输错误,并自动禁用相应的 DMA 通道。

       内存映射模式支持字节、半字和字访问类型,并支持芯片内执(XIP)操作,XSPI接受下一个微控制器访问并提前加载后面地址中的字节。如果之后访问的是连续地址,由于值已经预取,访问将更快完成。

       默认情况下,即便在很长时间内不访问FLASH,XSPI也不会停止预取操作,之前的读取操作将保持激活状态并且 nCS 保持低电平。由于 nCS保持低电平时,FLASH 功耗增加,应用程序可能会激活超时计数器(TCEN = 1, XSPI_CR 的位 3)。从而在 FIFO中写满预取的数据后,若在 TIMEOUT[15:0] (QUADSPI_LPTR) 个周期的时长内没有访问,则释放 nCS。BUSY在第一个存储器映射访问发生时变为高电平。由于进行预取操作,BUSY在发生超时、中止或外设禁止前不会下降。


       38.1.1.4 XSPI FLASH配置

       SPI FLASH芯片的相关参数通过器件配置寄存器 (XSPI_DCR(1~4)) 来进行设置。寄存器XSPI_DCR1[20:16]的DEVSIZE[4:0]这5个位,用于指定外部存储器的大小,计算公式为:

Fcap=2^[FSIZE+1]

       FSIZE+1是对Flash寻址所需的地址位数。Fcap表示FLASH的容量,单位为字节,在间接模式下,最高支持4GB(使用32位进行寻址)容量的FLASH芯片。但是在内存映射模式下的可寻址空间限制为256MB。

       XSPI连续执行两条命令时,它在两条命令之间将片选信号 (nCS) 置为高电平默认仅一个 CLK周期。某些FLASH需要命令之间的时间更长,可以通过寄存器XSPI_DCR1[13:8]的CSHT[5:0](选高电平时间)这6个位设置高电平时长:0~63表示1~64个时钟周期(最大为64)。

       时钟模式,用于指定在nCS为高电平时,CLK的时钟极性。通过寄存器XSPI_DCR1[0]的CKMODE位指定:当CKMODE=0时,CLK在nCS为高电平期间保持低电平,称之为模式0;当CKMODE=1时,CLK在nCS为高电平期间保持高电平,称之为模式3。


       38.1.1.5 XSPI寄存器


       l XSPI控制寄存器(XSPI_CR)

       XSPI控制寄存器描述如图38.1.1.5.1所示:


图38.1.1.5.1 XSPI_CR寄存器


       该寄存器我们只关心需要用到的一些位(下同),首先是FTHRES[5:0],用于设置FIFO阈值,范围为0~63,表示FIFO的阈值为1~64字节。

       FMODE[1:0],这两个位用于设置功能模式:00,间接写入模式;01,间接读取模式;10,自动轮询模式;11,内存映射模式;

       MSEL[1:0]位,用于选择FLASH。

       DMM位,用于设置双闪存模式,我们用的是双闪存模式,所以设置此位为1即可。

       ABORT位,用于终止XSPI的当前传输,设置为1即可终止当前传输,在读写FLASH数据的时候,可能会用到。

       EN位,用于控制XSPI的使能,我们需要用到XSPI接口,所以必须设置此位为1。


       l XSPI设备配置寄存器1(XSPI_DCR1)

       XSPI设备配置寄存器1描述如图38.1.1.5.2所示:


图38.1.1.5.2 XSPI_DCR1寄存器


       该寄存器可以设置FLASH芯片的容量(DEVSIZE)、片选高电平时间(CSHT)和时钟模式(CKMODE)等,这些位的设置说明见前面的38.1.1.4小节有详细讲解


       l XSPI设备配置寄存器2(XSPI_DCR2)

       XSPI设备配置寄存器2描述如图38.1.1.5.3所示:


图38.1.1.5.3 XSPI_DCR2寄存器


       该寄存器我们配置PRESCALER[7:0],用于设置AHB时钟预分频器:0~255,表示0~256分频。对于我们开发板上使用的单八线NOR Flash最大支持200MHz的时钟,因此这里设置PRESCALER=1,即2分频,得到XSPI时钟为200MHz(400/2);若是对于我们开发板上使用的双四线NOR Flash最大支持133MHz的时钟,因此这里设置PRESCALER=2,即3分频,得到XSPI时钟为133MHz(400/3)。

       WRAPSIZE[2:0]配置为0,即设置内存不支持包装读取。


       l XSPI状态寄存器(XSPI_SR)

       XSPI状态寄存器描述如图38.1.1.5.4所示:


图38.1.1.5.4 XSPI_SR寄存器


       BUSY位,指示操作是否忙。当该位为1时,表示XSPI正在执行操作。在操作完成或者FIFO为空的时候,该位自动清零。

       FTF位,表示FIFO是否到达阈值。在间接模式下,若达到FIFO阈值,或从FLASH读取完成后,FIFO中留有数据时,该位置1。只要阈值条件不再为“真”,该位就自动清零。

       TCF位,表示传输是否完成。在间接模式下,当传输的数据数量达到编程设定值,或在任何模式下传输中止时,该位置1。向XSPI_FCR寄存器的CTCF位写1,可以清零此位。


       l XSPI标志清除寄存器(XSPI_FCR)

       XSPI标志清除寄存器描述如图38.1.1.5.5所示:


图38.1.1.5.5 XSPI_FCR寄存器


       该寄存器,我们一般只用到CTCF位,用于清除XSPI的传输完成标志。


       l XSPI通信配置寄存器(XSPI_CCR)

       XSPI通信配置寄存器描述如图38.1.1.5.6所示:


图38.1.1.5.6 XSPI_CCR寄存器


       DDTR位,用于设置双倍率模式,我们没用到双倍率模式,所以设置此位为0。

       DMODE[2:0],这三个位用于设置数据模式:000,无数据;001,单线传输数据;010,双线传输数据;011,四线传输数据;100,八线传输数据;101,十六线传输数据。

       ABMODE[2:0],这三个位用于设置交替字节模式,我们一般设置为0,表示无交替字节。

       ADMODE[2:0],这三个位用于设置地址模式:000,无地址;001,单线传输地址;010,双线传输地址;011,四线传输地址;100,八线传输地址。

       IMODE[2:0],这三个位用于设置指令模式:000,无指令;001,单线传输指令;010,双线传输指令;011,四线传输指令;100,八线传输指令。


       l XSPI数据长度寄存器(XSPI_DLR)

       XSPI数据长度寄存器描述如图38.1.1.5.7所示:


图38.1.1.5.7 XSPI_DLR寄存器


       该寄存器为一个32位寄存器,可以设置的数据长度范围为:0~0XFFFFFFFF,当XSPI_DLR!=0XFFFFFFFF时,表示传输的字节长度(+1);当XSPI_DLR==0XFFFFFFFF时,表示不限传输长度,直到到达由FSIZE定义的FLASH结尾。


       l XSPI指令寄存器(XSPI_IR)

       XSPI指令寄存器描述如图38.1.1.5.8所示:


图38.1.1.5.8 XSPI_IR寄存器


       INSTRUCTION[31:0],这32个位用于设置将要发送给FLASH的指令。

       注意,以上这些位的配置,都必须在XSPI_SR寄存器的BUSY位为0时才可配置。


       l XSPI时序配置寄存器(XSPI_TCR)

       XSPI时序配置寄存器描述如图38.1.1.5.9所示:


图38.1.1.5.9 XSPI_TCR寄存器


       SSHIFT位,用于设置采样移位,默认情况下,XSPI接口在FLASH驱动数据后过半个CLK 周期开始采集数据。使用该位,可考虑外部信号延迟,推迟数据采集。我们一般设置此位为1,移位半个周期采集,确保数据稳定。

       DCYC[4:0],这5个位用于设置空指令周期数,可以控制空指令阶段的持续时间,设置范围为:0~31。设置为0,则表示没有空指令周期。


       38.1.2 NOR Flash简介  


       38.1.2.1 Flash简介 

       Flash是常见的用于存储数据的半导体器件,它具有容量大、可重复擦写、按“扇区/块”擦除、掉电后数据可继续保存的特性。常见的Flash主要有NOR Flash和NAND Flash两种类型,它们的特性如表38.1.2.1.1所示。NOR和NAND是两种数字门电路,可以简单地认为Flash内部存储单元使用哪种门作存储单元就是哪类型的Flash。U盘,SSD,eMMC等为NAND型,而NOR Flash则根据设计需要灵活应用于各类PCB上,如BIOS,手机等。


表38.1.2.1.1 NOR Flash和NAND Flash特性对比


       NOR与NAND在数据写入前都需要有擦除操作,但实际上NOR Flash的一个bit可以从1变成0,而要从0变1就要擦除后再写入,NAND Flash这两种情况都需要擦除。擦除操作的最小单位为“扇区/块”,这意味着有时候即使只写一字节的数据,则这个“扇区/块”上之前的数据都可能会被擦除。

       NOR的地址线和数据线分开,它可以按“字节”读写数据,符合CPU的指令译码执行要求,所以假如NOR上存储了代码指令,CPU给NOR一个地址,NOR就能向CPU返回一个数据让CPU执行,中间不需要额外的处理操作,这体现于表38.1.2.1.1中的支持XIP特性(eXecute In Place)。因此可以用NOR Flash直接作为嵌入式MCU的程序存储空间。

       NAND的数据和地址线共用,只能按“块”来读写数据,假如NAND上存储了代码指令,CPU给NAND地址后,它无法直接返回该地址的数据,所以不符合指令译码要求。

       若代码存储在NAND上,可以把它先加载到RAM存储器上,再由CPU执行。所以在功能上可以认为NOR是一种断电后数据不丢失的RAM,但它的擦除单位与RAM有区别,且读写速度比RAM要慢得多。

       Flash也有对应的缺点,我们在使用过程中需要尽量去规避这些问题:一是Flash的使用寿命,另一个是可能的位反转。

       使用寿命体现在:读写上是FLASH的擦除次数都是有限的(NOR Flash普遍是10万次左右),当它的使用接近寿命的时候,可能会出现写操作失败。由于NAND通常是整块擦写,块内有一位失效整个块就会失效,这被称为坏块。使用NAND Flash最好通过算法扫描介质找出坏块并标记为不可用,因为坏块上的数据是不准确的。

       位反转是数据位写入时为1,但经过一定时间的环境变化后可能实际变为0的情况,反之亦然。位反转的原因很多,可能是器件特性也可能与环境、干扰有关,由于位反转的的问题可能存在,所以FLASH存储器需要“探测/错误更正(EDC/ECC)”算法来确保数据的正确性。

       FLASH芯片有很多种芯片型号,在我们的norflash.h头文件中有定义芯片ID的宏定义,对应的就是不同型号的NOR FLASH芯片,比如有:W25Q16、W25Q32、W25Q64、W25Q128,它们是来自不同的厂商的同种规格的NOR FLASH芯片。它们的很多参数、操作都是一样的,所以我们的实验都是兼容它们的。

       由于这么多的芯片,我们就不一一进行介绍了,就拿其中一款型号进行介绍即可,其他的型号都是类似的。

       下面我们以MX25UM25645G为例,认识一下具体的NOR Flash的特性。

       MX25UM25645G是一款大容量SPI Flash产品,其容量为32MBytes。它将32M字节的容量分为512个块(Block),每个块大小为64K字节,每个块又分为16个扇区(Sector),每一个扇区16页,每页256个字节,即每个扇区4K个字节。MX25UM25645G的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给MX25UM25645G开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。

       MX25UM25645G的擦写周期多达10W次,具有20年的数据保存期限,支持电压为1.8V,MX25UM2565G支持标准的单线SPI,还支持八线的OPI(Octo-SPI),最高时钟频率可达200MHz,本章我们将利用STM32H7R7的XSPI接口来实现对MX25UM25645G的驱动。

       接下来,我们介绍一下本实验驱动MX25UM25645G需要用到的一些指令,MX25UM25645G的指令分为标准SPI模式下的SPI指令和八线OPI模式下的OPI指令,我们主要介绍MX25UM25645G的OPI指令,如表38.1.2.1所示:


表38.1.2.1 MX25UM25645G指令


       上表列出了本章我们驱动MX25UM25645G所需要用到的大部分指令和对应的参数。首先,前面RSTEN和RST指令,是用来进行软件复位的。接下来的RDSR,是用来读取状态寄存器的,MX25UM25645G的状态寄存器,如表38.1.2.2所示:


表38.1.2.2 MX25UM25645G状态寄存器


       对于该状态寄存器,我们只关心我们需要用到的位即可:WIP。其他位的说明,请看MX25UM25645G的数据手册。

       WIP位,用于表示擦除/编程操作是否正在进行,当擦除/编程操作正在进行时,此位为1,此时MX25UM25645G不接受任何指令,当擦除/编程操作完成时,此位为0。此位为只读位,我们在执行某些操作的时候,必须等待此位为0。

       WREN指令,用于写使能MX25UM25645G,在需要对MX25UM25645G进行写入、擦除等操作之前,需要先发送改指令。

       CE、BE4B、SE4B指令,都是用于擦除MX25UM25645G的,分为为全片擦除、块擦除和扇区擦除。

       PP4B指令,用于对MX25UM25645G进行页编程操作。

       8DTRD指令,用于读取MX25UM25645G,且是使用OPI的八线SPI进行读取的。

       为了在程序上方便使用,我们把FLASH芯片的常用指令编码定义为宏定义的形式,存放在norflash.h文件中。


        38.2 硬件设计


       1. 例程功能

       通过KEY_UP按键来控制norflash的写入,通过按键KEY0来控制norflash的读取。并在LCD模块上显示相关信息。LED0闪烁用于提示程序正在运行。


       2. 硬件资源


       1)LED灯

              LED0  –  PD14


       2)独立按键

              WKUP  –  PC13

              KEY0  –  PE9


       3)MPU


       4)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(包括MCU屏和RGB屏,都支持)


       5)HyperRAM


       6)XSPI1

                XSPI1_CS – PO0

              XSPI1_CLK – PO4

              XSPI1_DQ0 – PP0

              XSPI1_DQ1 – PP1

              XSPI1_DQ2 – PP2

              XSPI1_DQ3 – PP3

              XSPI1_DQ4 – PP4

              XSPI1_DQ5 – PP5

              XSPI1_DQ6 – PP6

              XSPI1_DQ7 – PP7


       3. 原理图

       我们主要来看看NOR Flash和开发板的连接,如下图所示:


图38.3.1.a NOR Flash与开发板的连接原理(单八线版本)


图38.3.1.b NOR Flash与开发板的连接原理(双四线版本)


       本实验还支持多种型号的NOR Flash芯片,具体请看norflash.h文件的宏定义,在程序上只需要稍微修改一下,后面讲解程序的时候会提到。


        38.3 程序设计 


       38.3.1 XSPI的HAL库驱动

       XSPI在HAL库中的驱动代码在stm32h7rsxx_hal_xspi.c文件(及其头文件)中。


       1. HAL_XSPI_Init函数

       XSPI的初始化函数,其声明如下:

HAL_StatusTypeDef HAL_XSPI_Init(XSPI_HandleTypeDef *hxspi);


       l 函数描述:

       用于初始化XSPI。


       l 函数形参:

       形参1是XSPI_HandleTypeDef结构体类型指针变量,其定义如下:

typedef struct
{
    XSPI_TypeDef  *Instance; /* XSPI寄存器基地址 */
    XSPI_InitTypeDef Init;  /* XSPI初始化参数 */
    uint8_t    *pBuffPtr; /* 数据地址 */
    __IO uint32_t  XferSize; /* 数据大小 */
    __IO uint32_t  XferCount; /* 数据个数 */
    DMA_HandleTypeDef *hdmatx; /* 发送DMA句柄 */
    DMA_HandleTypeDef *hdmarx; /* 接收DMA句柄 */
    __IO uint32_t  State;  /* HAL库内部状态变量 */
    __IO uint32_t  ErrorCode; /* 错误代码 */
    uint32_t   Timeout; /* 超时时间 */
} XSPI_HandleTypeDef;

       1) Instance:用于设置XSPI寄存器基地址,设置为XSPI即可,这个官方已经为我们做好了宏定义。

       2) Init:用于设置XSPI的相关参数,XSPI_InitTypeDef结构体下面再进行详细讲解。

       3) pBuffPtr和XferCount:分别用于设置 XSPI 发送和接收的缓冲指针、数据量和剩余数据量。

       4) hdmatx和hdmarx:用于配置相关的 DMA 发送和接收参数。

       5) State:用于存放通讯过程中的工作状态。

       6) ErrorCode:通过该参数,用户可以了解到XSPI通讯过程中通信失败的原因。 

       7) Timeout:用于设置超时时间。XSPI访问时间一旦超出 Timeout这个变量值,那么ErrorCode成员变量就会被赋值为HAL_XSPI_ERROR_TIMEOUT,表示操作超时。

       下面重点来了解XSPI_InitTypeDef结构体的内容,其定义如下:

typedef struct
{
    uint32_t FifoThresholdByte;   /* FiFo阈值 */
    uint32_t MemoryMode;    /* 存储器模式 */
    uint32_t MemoryType;    /* 存储器类型 */
    uint32_t MemorySize;    /* 存储器大小 */
    uint32_t ChipSelectHighTimeCycle; /* 片选高电平时间 */
    uint32_t FreeRunningClock;   /* 空闲时发送时钟 */
    uint32_t ClockMode;     /* 空闲时时钟模式 */
    uint32_t WrapSize;     /* 交换大小 */
    uint32_t ClockPrescaler;   /* 时钟分频系数 */
    uint32_t SampleShifting;   /* 采样偏移 */
    uint32_t DelayHoldQuarterCycle; /* 数据保持周期 */
    uint32_t ChipSelectBoundary;  /* 片选边界 */
    uint32_t MaxTran;     /* 通讯时片选释放间隔 */
    uint32_t Refresh;     /* 片选释放间隔 */
    uint32_t MemorySelect;    /* 存储器片选信号 */
} XSPI_InitTypeDef;

       1)FifoThresholdByte:用于设置FIFO阈值级别,可设置范围为 0~63,对应XSPI_CR寄存器的FTHRES[5:0]位。

       2)MemoryMode:用于设置内存模式,对应XSPI_CR寄存器的FMODE[1:0]位。

       3)MemoryType:用于设置外部设备类型,

       4)MemorySize:它定义了连接到XSPI的外部设备的大小,对应于访问所需的地址位数

外部设备。

       5)ChipSelectHighTimeCycle:用于设置片选高电平时间,取值范围:XSPI_CS_HIGH_TIME_1_CYCLE ~ XSPI_CS_HIGH_TIME_8_CYCLE,表示 1~64个周期,对应XSPI_DCR1寄存器的CSHT[5:0]位。CSHT+1定义片选 (nCS) 在发送至 Flash 的命令之间必须保持高电平的最少CLK周期数。

       6)FreeRunningClock:用于设置启用或不启用自由运行时钟。

       7)ClockMode:用于设置时钟模式,对应XSPI_DCR1寄存器CKMODE位,指示 CLK在命令之间(nCS = 1 时)的电平,可以选择的参数是:XSPI_CLOCK_MODE_0(表示模式0)或者XSPI_CLOCK_MODE_3(表示模式3)。模式 0是:nCS为高电平(片选释放)时,CLK 必须保持低电平。模式3是:nCS 为高电平(片选释放)时,CLK 必须保持高电平。

       8)WrapSize:可设置配置内存的包装大小,对应XSPI_DCR2寄存器的WRAPSIZE[2:0]位。

       9)ClockPrescaler:用于设置预分频系数,对应XSPI_DCR2寄存器的PRESCALER[7:0]位,取值范围是 0~255。仅可在 BUSY = 0 时修改该字段。

       10)SampleShifting:用于设置采样移位,对应XSPI_TCR寄存器的SSHIFT位。使用该位是考虑到外部信号延迟时,推迟数据采样。可以取值 XSPI_SAMPLE_SHIFTING_NONE(即0):不发生移位;XSPI_SAMPLE_SHIFTING_HALFCYCLE(即1):移位半个周期。在DDR模式下 (DDRM = 1),固件必须确保SSHIFT = 0。

       11)DelayHoldQuarterCycle:它允许保持1/4周期的数据。

       12)ChipSelectBoundary:用于设置定义释放芯片选择的字节边界。

       13)MaxTran:用于设置启用通信调节功能。

       14)Refresh:用于设置刷新特性,可选择的范围为0~0xFFFFFFFF。

       15)MemorySelect:用于设置nCS的输出。


       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。


       l 注意事项:

       XSPI的MSP初始化函数HAL_XSPI_MspInit,该函数声明如下:

void HAL_XSPI_MspInit(XSPI_HandleTypeDef *hxspi);


       2. HAL_XSPI_Command函数

       XSPI设置命令配置函数,其声明如下:

HAL_StatusTypeDef HAL_XSPI_Command(XSPI_HandleTypeDef *hxspi,
XSPI_RegularCmdTypeDef *const pCmd, uint32_t Timeout);


       l 函数描述:

       该函数用来配置XSPI命令。


       l 函数形参:

       形参1是XSPI_HandleTypeDef结构体类型指针变量。

       形参2是XSPI_RegularCmdTypeDef结构体类型指针变量,其定义如下:

typedef struct
{
  uint32_t OperationType;             /* 操作寄存器类型 */
  uint32_t IOSelect;                  /* 选择IO */
  uint32_t Instruction;               /* 设备指令 */
  uint32_t InstructionMode;           /* 指令模式  */
  uint32_t InstructionWidth;          /* 指令宽度 */
  uint32_t InstructionDTRMode;        /* 指令DTR模式 */
  uint32_t Address;                   /* 地址 */
  uint32_t AddressMode;               /* 地址模式 */
  uint32_t AddressWidth;              /* 地址宽度 */
  uint32_t AddressDTRMode;            /* 地址DTR模式 */
  uint32_t AlternateBytes;            /* 可选字节 */
  uint32_t AlternateBytesMode;        /* 可选字节模式 */
  uint32_t AlternateBytesWidth;       /* 可选字节宽度 */
  uint32_t AlternateBytesDTRMode;     /* 可选字节DTR模式 */
  uint32_t DataMode;                  /* 数据模式 */
  uint32_t DataLength;                /* 数据长度 */
  uint32_t DataDTRMode;               /* 数据DTR模式 */
  uint32_t DummyCycles;               /* 空周期指令周期数 */
  uint32_t DQSMode;                   /* DQS模式 */
} XSPI_RegularCmdTypeDef;

       1)OperationType:用于设置操作寄存器的类型,用于对寄存器进行写操作。

       2)IOSelect:表示用于与外部内存交换数据的IO。

       3)Instruction:设置通信指令,指定要发送到外部XSPI设备的指令。

       4)InstructionMode:用于指定指令阶段模式,如下五种:

       HAL_XSPI_INSTRUCTION_NONE:无指令;

       HAL_XSPI_INSTRUCTION_1_LINE:单线传输指令;

       HAL_XSPI_INSTRUCTION_2_LINES:双线传输指令;

       HAL_XSPI_INSTRUCTION_4_LINES:四线传输指令。

       HAL_XSPI_INSTRUCTION_8_LINES:八线传输指令。

       5)InstructionWidth:用于设置指令的宽度,如下四种:

       HAL_XSPI_INSTRUCTION_8_BITS:8位指令;

       HAL_XSPI_INSTRUCTION_16_BITS:16位指令;

       HAL_XSPI_INSTRUCTION_24_BITS:24位指令;

       HAL_XSPI_INSTRUCTION_32_BITS:32位指令;

       6)InstructionDTRMode:用于设置启用或不启用指令阶段的DTR模式。

       7)Address:指定要发送到外部XSPI设备的地址,BUSY = 0 或 FMODE = 11(内存映射模式)时,将忽略写入该字段。在双闪存模式下,由于地址始终为偶地址,ADDRESS[0] 自动保持为“0”。

       8)AddressMode:指定地址模式,如下五种:

       HAL_XSPI_ADDRESS_NONE:无地址;HAL_XSPI_ADDRESS_1_LINE:单线传输地址;HAL_XSPI_ADDRESS_2_LINES:双线传输地址;HAL_XSPI_ADDRESS_4_LINES:四线传输地址;HAL_XSPI_ADDRESS_8_LINES:八线传输地址。

       9)AddressWidth:用来设置地址的宽度。

       10)AddressDTRMode:用于启用或不启用地址阶段的DTR模式。

       11)AlternateBytes:指定要在地址后立即发送到外部XSPI设备的可选数据。

       12)AlternateByteMode:指定交替字节模式,如下五种:

       HAL_XSPI_ALT_BYTES_NONE:无交替字节;

       HAL_XSPI_ALT_BYTES_1_LINE:单线传输交替字节;

       HAL_XSPI_ALT_BYTES_2_LINES:双线传输交替字节;

       HAL_XSPI_ALT_BYTES_4_LINES:四线传输交替字节。

       HAL_XSPI_ALT_BYTES_8_LINES:八线传输交替字节。

       13)AlternateBytesWidth:用于设置备用字节的宽度,可以是8位,16位,24位或者32位。

       14)AlternateBytesDTRMode:用于设置备用字节阶段启用或不启用DTR模式。

       15)DataMode:指定数据模式,如下六种:

       HAL_XSPI_DATA_NONE:无数据;HAL_XSPI_DATA_1_LINE:单线传输数据;HAL_XSPI_DATA_2_LINES:双线传输数据;HAL_XSPI_DATA_4_LINES:四线传输数据;HAL_XSPI_DATA_8_LINES:八线传输数据;HAL_XSPI_DATA_16_LINES:十六线传输数据。

       16)DataLength:用于命令传输的数据量。

       17)DataDTRMode:用于启用或不启用数据阶段的DTR模式。

       18)DummyCycles:定义空指令阶段持续周期,SDR和DDR模式下,指定CLK周期数(0~31)。

       19)DQSMode:用于设置是否启用数据频闪管理。

       形参3用于设置超时时间。


       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。


       3. HAL_XSPI_Receive函数

       XSPI接收数据函数,其声明如下:

HAL_StatusTypeDef HAL_XSPI_Receive(XSPI_HandleTypeDef *hxspi,
uint8_t *const pData, uint32_t Timeout);


       l 函数描述:

       该函数用来接收数据。


       l 函数形参:

       形参1是XSPI_HandleTypeDef结构体类型指针变量。

       形参2是uint8_t类型指针变量,存放接收数据缓冲区指针。 

       形参3设置操作超时时间。


       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。


       4. HAL_XSPI_Transmit函数

       XSPI发送数据函数,其声明如下:

HAL_StatusTypeDef HAL_XSPI_Transmit (XSPI_HandleTypeDef *hxspi,
   const uint8_t *pData);

       l 函数描述:

       该函数用来发送数据。


       l 函数形参:

       形参1是XSPI_HandleTypeDef结构体类型指针变量。

       形参2是uint8_t类型指针变量,存放发送数据缓冲区指针。 

       形参3设置操作超时时间。


       l 函数返回值:

       HAL_StatusTypeDef枚举类型的值。

       XSPI初始化配置步骤 


       1)开启XSPI接口和相关IO的时钟,设置IO口的复用功能。

       要使用XSPI,肯定要先开启其时钟(由RCC的AHB5ENR控制),然后根据我们使用的XSPI IO口,开启对应IO口的时钟,并初始化相关IO口的复用功能(选择XSPI复用功能)。

       XSPI时钟使能方法为:

__HAL_RCC_XSPI_CLK_ENABLE();        /* 使能XSPI时钟 */

       这里大家要注意,和其他外设处理方法一样,HAL库提供了XSPI的初始化回调函数HAL_XSPI_MspInit,一般用来编写与MCU相关的初始化操作。时钟使能和IO口初始化一般在回调函数中编写。


       2)设置XSPI相关参数。

       此部分需要设置两个寄存器:XSPI_CR和XSPI_DCR,控制XSPI的时钟、片选参数、FLASH容量和时钟模式等参数,设定NOR FLASH的工作条件。最后,使能XSPI,完成对XSPI的初始化。HAL库中设置XSPI相关参数函数为HAL_XSPI_Init,该函数声明为:

HAL_StatusTypeDef HAL_XSPI_Init(XSPI_HandleTypeDef *hxspi);

       XSPI_HandleTypeDef结构体这些成员变量是用来配置XSPI_CR寄存器和XSPI_DCR寄存器相应位,大家可以结合这两个寄存器的位定义和结构体定义来理解。

       对于HAL_XSPI_Init函数使用范例请参考后面部分程序源码。

       XSPI发送命令步骤 

       1)等待XSPI空闲。

       在XSPI发送命令前,必须先等待XSPI空闲,通过判断XSPI_SR寄存器的BUSY位为0,来确定。

       2)设置命令参数。

       此部分主要是通过通信配置寄存器(XSPI_CCR)设置,将XSPI配置为:每次都发送指令、间接写模式,根据具体需要设置:指令、地址、空周期和数据等的传输位宽等信息。如果需要发送地址,则配置地址寄存器(XSPI_AR)。

       在配置完成以后,即可启动发送。如果不需要传输数据,则需要等待命令发送完成(等待XSPI_SR寄存器的TCF位为1)。

       在HAL库中上述两个步骤是通过函数HAL_XSPI_Command来实现,该函数声明为:

HAL_StatusTypeDef  HAL_XSPI_Command(XSPI_HandleTypeDef *hxspi, 
XSPI_RegularCmdTypeDef *const pCmd,uint32_t Timeout);


       XSPI读数据步骤 

       1)设置数据传输长度。

       通过设置数据长度寄存器(XSPI_DLR),配置需要传输的字节数。  

       2)设置XSPI工作模式并设置地址。

       因为要读取数据,所以,设置XSPI_CR寄存器的FMODE[1:0]位为01,工作在间接读取模式。然后,通过地址寄存器(XSPI_AR),设置我们将要读取的数据的首地址。

       3)读取数据。

       在发送完地址以后,就可以读取数据了,不过要等待数据准备好,通过判断XSPI_SR寄存器的FTF和TCF位,当这两个位任意一个位为1的时候,我们就可以读取XSPI_DR寄存器来获取从FLASH读到的数据。

       最后,在所有数据接收完成以后,终止传输(ABORT),清除传输完成标志位(TCF)。

HAL库中,读取数据是通过函数HAL_XSPI_Receive来实现的,该函数声明为:

HAL_StatusTypeDef     HAL_XSPI_Receive(XSPI_HandleTypeDef *hxspi, 
uint8_t *const pData, uint32_t Timeout);

       在调用该函数读取数据之前,我们会先调用上个步骤讲解的函数HAL_XSPI_Command来指定读取数据的存放空间。


       XSPI写数据步骤 

       1)设置数据传输长度。

       通过设置数据长度寄存器(XSPI_DLR),配置需要传输的字节数。  

       2)设置XSPI工作模式并设置地址。

       因为要读取数据,所以,设置XSPI_CR寄存器的FMODE[1:0]位为00,工作在间接写入模式。然后,通过地址寄存器(XSPI_AR),设置我们将要写入的数据的首地址。

       3)写入数据。

       在发送完地址以后,就可以写入数据了,不过要等待FIFO不满,通过判断XSPI_SR寄存器的FTF位,当这个位为1的时候,表示FIFO可以写入数据,此时往XSPI_DR写入需要发送的数据,就可以实现写入数据到FLASH。

       最后,在所有数据写入完成以后,终止传输(ABORT),清除传输完成标志位(TCF)。

在HAL库中,XSPI发送数据是通过函数HAL_XSPI_Transmit来实现的,该函数声明为:

HAL_StatusTypeDef     HAL_XSPI_Transmit_DMA(XSPI_HandleTypeDef *hxspi, 
const uint8_t *pData);

       同理,在调用该函数发送数据之前,我们会先调用HAL_XSPI_Command函数来指定要写入数据的存储地址信息。


       FLASH芯片初始化步骤 

       1)使能XSPI模式。

       因为我们是通过XSPI访问外部Flash的,所以先设置Flash工作在对应模式下,例程中,我们配置单八线版本的外部Flash为八线SPI模式,配置双四线版本的两个外部Flash均为四线SPI模式。

       2)设置4字节地址模式。

       对于双四线版本的外部Flash,上电后,一般默认是3字节地址模式,我们还需要设置其为4字节地址模式,否则只能访问最大16MB的地址空间。


       38.3.2 程序解析

       本实验中,我们通过调用HAL库的函数去驱动XSPI进行通信,所以需要在工程中的Drivers/STM32H7RSxx_HAL_Driver分组下添加stm32h7rsxx_hal_xspi.c文件去支持。实验工程中,我们新增了norflash.c存放NOR Flash驱动代码,norflash_mx25um25645g.c存放MX25UM25645G的兼容驱动代码和norflash_w25q128_dual.c文件存放双片W25Q128的兼容驱动代码。


       1. XSPI驱动代码

       这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。XSPI驱动源码包括两个文件:norflash.c和norflash.h。

       norflash.h头文件对XSPI的设备类型和缓冲区大小进行了定义,代码如下:

/* NOR Flash设备支持定义 */
#define NORFLASH_SUPPORT_MX25UM25645G   /* MX25UM25645G */
#define NORFLASH_SUPPORT_W25Q128_DUAL   /* 双W25Q128 */
 
/* NOR Flash扇区缓冲区大小定义 */
#define NORFLASH_SECTOR_BUFFER_SIZE (0x00002000UL)
 
/* NOR Flash内存映射基地址定义 */
#define NORFLASH_MEMORY_MAPPED_BASE (XSPI1_BASE)
 
/* NOR Flash设备类型定义 */
typedef enum : uint8_t {
    NORFlash_Unknow = 0,    /* 未知 */
#ifdef NORFLASH_SUPPORT_MX25UM25645G
    NORFlash_MX25UM25645G,  /* MX25UM25645G */
#endif /* NORFLASH_SUPPORT_MX25UM25645G */
#ifdef NORFLASH_SUPPORT_W25Q128_DUAL
    NORFlash_W25Q128_Dual,  /* 双W25Q128 */
#endif /* NORFLASH_SUPPORT_W25Q128_DUAL */
    NORFlash_Dummy,
} norflash_type_t;
 
/* NOR Flash设备定义 */
typedef struct {
    /* NOR Flash设备类型 */
    norflash_type_t type;
    
    /* NOR Flash设备参数 */
    struct {
        uint8_t empty_value;    /* 擦后数据值 */
        uint32_t chip_size;     /* 全片大小 */
        uint32_t block_size;    /* 块大小 */
        uint32_t sector_size;   /* 扇区大小 */
        uint32_t page_size;     /* 页大小 */
    } parameter;
    
    /* NOR Flash操作函数集合 */
    struct {
        uint8_t (*init)(XSPI_HandleTypeDef *hxspi);        /* 初始化 */
        uint8_t (*deinit)(XSPI_HandleTypeDef *hxspi);      /* 反初始化 */
        uint8_t (*erase_chip)(XSPI_HandleTypeDef *hxspi);  /* 全片擦除 */
        uint8_t (*erase_block)(XSPI_HandleTypeDef *hxspi, uint32_t address);
        /* 块擦除 */
        uint8_t (*erase_sector)(XSPI_HandleTypeDef *hxspi, uint32_t address);
        /* 扇区擦除 */
        uint8_t (*program_page)(XSPI_HandleTypeDef *hxspi, uint32_t address, 
uint8_t *data, uint32_t length);   /* 页编程 */
        uint8_t (*read)(XSPI_HandleTypeDef *hxspi, uint32_t address, uint8_t
*data, uint32_t length);           /* 读 */
        uint8_t (*memory_mapped)(XSPI_HandleTypeDef *hxspi);  /* 内存映射 */
    } ops;
} norflash_t;

       下面我们开始介绍norflash.c的程序,首先是XSPI接口初始化函数,其定义如下:

/**
 * @brief   初始化XSPI1
 * @param   hxspi: XSPI句柄指针
 * @param   type: NOR Flash设备类型(参考norflash.h文件中的norflash_type_t定义)
 * @retval  初始化结果
 * @arg     0: 初始化成功
 * @arg     1: 初始化失败
 */
static uint8_t norflash_xspi1_init(XSPI_HandleTypeDef *hxspi, norflash_type_t type)
{
    __IO uint8_t *ptr;
    uint32_t i;
    XSPIM_CfgTypeDef xspim_cfg_struct = {0};
    
    if (hxspi == NULL)
    {
        return 1;
    }
    
    if ((type == NORFlash_Unknow) || (type >= NORFlash_Dummy))
    {
        return 1;
    }
    
    ptr = (__IO uint8_t *)hxspi;
    for (i = 0; i < sizeof(XSPI_HandleTypeDef); i++)
    {
        *ptr++ = 0x00;
    }
    
    /* 反初始化XSPI */
    hxspi->Instance = XSPI1;
    if (HAL_XSPI_DeInit(hxspi) != HAL_OK)
    {
        return 1;
    }
    
    /* 初始化XSPI */
    hxspi->Instance = XSPI1;
    hxspi->Init.FifoThresholdByte = 4;
    if (0)
    {
        
    }
#ifdef NORFLASH_SUPPORT_MX25UM25645G
    else if (type == NORFlash_MX25UM25645G)
    {
        hxspi->Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
        hxspi->Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX;
    }
#endif /* NORFLASH_SUPPORT_MX25UM25645G */
#ifdef NORFLASH_SUPPORT_W25Q128_DUAL
    else if (type == NORFlash_W25Q128_Dual)
    {
        hxspi->Init.MemoryMode = HAL_XSPI_DUAL_MEM;
        hxspi->Init.MemoryType = HAL_XSPI_MEMTYPE_APMEM;
    }
#endif /* NORFLASH_SUPPORT_W25Q128_DUAL */
    hxspi->Init.MemorySize = HAL_XSPI_SIZE_256MB;
    hxspi->Init.ChipSelectHighTimeCycle = 1;
    hxspi->Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
    hxspi->Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
    hxspi->Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED;
    hxspi->Init.ClockPrescaler = 4 - 1;
    hxspi->Init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_NONE;
    hxspi->Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
    hxspi->Init.ChipSelectBoundary = HAL_XSPI_BONDARYOF_NONE;
    hxspi->Init.MaxTran = 0;
    hxspi->Init.Refresh = 0;
    hxspi->Init.MemorySelect = HAL_XSPI_CSSEL_NCS1;
    if (HAL_XSPI_Init(hxspi) != HAL_OK)
    {
        return 1;
    }
    
    /* 配置XSPIM */
    xspim_cfg_struct.nCSOverride = HAL_XSPI_CSSEL_OVR_NCS1;
    xspim_cfg_struct.IOPort = HAL_XSPIM_IOPORT_1;
if (HAL_XSPIM_Config(hxspi, &xspim_cfg_struct, 
HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    {
        return 1;
    }
    
    return 0;
}

       这里我们需要注意的是,XSPI只需要选择预分频系数就可以确定XSPI的时钟频率。时钟模式选择模式0,在未进行任何操作时 CLK 保持低电平。


       2. NORFLASH驱动代码

       在上一小节已经对XSPI协议需要用到的东西都封装好了。那么现在就要在XSPI通信的基础上,通过前面分析的外部Flash的工作时序拟定通信代码。

       由于这部分的代码量比较多,这里就不一一贴出来介绍。介绍几个重点,其余的请自行查看源码。

       下面介绍NOR FLASH初始化函数,其定义如下:

/**
 * @brief   初始化NOR Flash
 * @param   无
 * @retval  NOR Flash设备类型(参考norflash.h文件中的norflash_type_t定义)
 */
norflash_type_t norflash_init(void)
{
    uint8_t norflash_index;
    
    if (norflash != NULL)
    {
        if (norflash_deinit() != 0)
        {
            return 1;
        }
    }
    
for (norflash_index = 0; norflash_index < (sizeof(norflashs) / 
sizeof(norflashs[0])); norflash_index++)
    {
        /* 初始化XSPI1 */
        if (norflash_xspi1_init(&xspi1_handle, norflashs[norflash_index]
->type) != 0)
        {
            break;
        }
        
        if (norflashs[norflash_index]->ops.init == NULL)
        {
            continue;
        }
        
        /* 初始化NOR Flash设备 */
        if (norflashs[norflash_index]->ops.init(&xspi1_handle) == 0)
        {
            if (norflashs[norflash_index]->type == NORFlash_MX25UM25645G)
            {
                /* MX25UM25645G最高支持200MHz时钟 */
                HAL_XSPI_SetClockPrescaler(&xspi1_handle, 2 - 1);
            }
            norflash = norflashs[norflash_index];
            return norflash->type;
        }
    }
    
    return NORFlash_Unknow;
}

       该函数用于初始化NOR FLASH,首先调用norflash_xspi1_init函数,初始化XSPI接口。然后再初始化外部NOR Flash。

       接下来介绍读取NOR FLASH函数,其定义如下:

/**
 * @brief   读NOR Flash
 * @param   address: 地址
 * @param   data: 数据缓冲区指针
 * @param   length: 数据长度
 * @retval  读结果
 * @arg     0: 读成功
 * @arg     1: 读失败
 */
uint8_t norflash_read(uint32_t address, uint8_t *data, uint32_t length)
{
    uint32_t index;
    uint32_t length_16b;
    
    if (norflash == NULL)
    {
        return 1;
    }
    
    /* 内存映射状态下直接从映射地址读取 */
    if (xspi1_handle.State == HAL_XSPI_STATE_BUSY_MEM_MAPPED)
    {
        length_16b = length >> 1;
        for (index = 0; index < length_16b; index++)
        {
            ((uint16_t *)data)[index] = ((volatile uint16_t *)
(NORFLASH_MEMORY_MAPPED_BASE + address))[index];
        }
        
        if (length & 1UL)
        {
            data[length - 1] = *(volatile uint8_t *)
(NORFLASH_MEMORY_MAPPED_BASE + address + length - 1);
        }
        
        return 0;
    }
    
    /* 读NOR Flash设备 */
    if (norflash->ops.read != NULL)
    {
        if (norflash->ops.read(&xspi1_handle, address, data, length) == 0)
        {
            return 0;
        }
    }
    
    return 1;
}

       该函数用于从NOR FLASH的指定位置读出指定长度的数据,由于NOR FLASH支持以任意地址(但是不能超过NOR FLASH的地址范围)开始读取数据,所以,这个代码相对来说比较简单。

       有读函数,那肯定就有写函数,接下来我们介绍一下NOR FLASH写函数,其定义如下:

/**
 * @brief   写NOR Flash
 * @param   address: 地址
 * @param   data: 数据缓冲区指针
 * @param   length: 数据长度
 * @retval  写结果
 * @arg     0: 写成功
 * @arg     1: 写失败
 */
uint8_t norflash_write(uint32_t address, uint8_t *data, uint32_t length)
{
    uint32_t sector_size;
    uint32_t sectors_index;
    uint32_t sector_offset;
    uint32_t sector_remain;
    uint32_t sector_write_length;
    uint32_t data_index;
    uint32_t page_size;
    uint32_t pages_index;
    uint32_t page_offset;
    uint32_t page_remain;
    uint32_t page_write_length;
    
    if (norflash == NULL)
    {
        return 1;
    }
    
    if (xspi1_handle.State == HAL_XSPI_STATE_BUSY_MEM_MAPPED)
    {
        return 1;
    }
    
    sector_size = norflash_get_sector_size();
    page_size = norflash_get_page_size();
    
    /* NOR Flash扇区缓冲区大小校验 */
if (sector_size > (sizeof(norflash_sector_buffer) / 
sizeof(norflash_sector_buffer[0])))
    {
        return 1;
    }
    
    sectors_index = address / sector_size;
    sector_offset = address % sector_size;
    while (length != 0)
    {
        sector_remain = sector_size - sector_offset;
        sector_write_length = (length < sector_remain) ? length:sector_remain;
        length -= sector_write_length;
        
        /* 读取NOR Flash扇区数据 */
        if(norflash_read(sectors_index * sector_size, norflash_sector_buffer, 
sector_size) != 0)
        {
            return 1;
        }
        
        /* NOR Flash扇区待写入位置数据空校验 */
        for (data_index = 0; data_index < sector_write_length; data_index++)
        {
            if (norflash_sector_buffer[sector_offset + data_index] != 
norflash_get_empty_value())
            {
                break;
            }
        }
        
        /* NOR Flash扇区待写入位置有非空数据 */
        if (data_index < sector_write_length)
        {
            /* 擦除NOR Flash扇区 */
            if (norflash_erase_sector(sectors_index * sector_size) != 0)
            {
                return 1;
            }
            
            /* 待写入数据写入NOR Flash扇区缓冲区 */
            for (data_index = 0; data_index < sector_write_length;data_index++)
            {
                norflash_sector_buffer[sector_offset + data_index] = *data;
                data++;
            }
            
            /* NOR Flash扇区缓冲区数据写入NOR Flash */
            for (pages_index = 0; pages_index < (sector_size / page_size); 
pages_index++)
            {
                if (norflash_program_page(sectors_index * sector_size + 
pages_index * page_size, &norflash_sector_buffer[pages_index
* page_size], page_size) != 0)
                {
                    return 1;
                }
            }
        }
        /* NOR Flash扇区待写入位置无非空数据 */
        else
        {
            pages_index = sector_offset / page_size;
            page_offset = sector_offset % page_size;
            while (sector_write_length != 0)
            {
                page_remain = page_size - page_offset;
                page_write_length = (sector_write_length < page_remain) ? 
sector_write_length : page_remain;
                sector_write_length -= page_write_length;
                
                /* 待写入数据写入NOR Flash */
                if (norflash_program_page(sectors_index * sector_size + 
pages_index * page_size + page_offset, data, page_write_length)
!= 0)
                {
                    return 1;
                }
                data += page_write_length;
                
                pages_index++;
                page_offset = 0;
            }
        }
        
        sectors_index++;
        sector_offset = 0;
    }
    
    return 0;
}

       该函数可以在NOR FLASH的任意地址开始写入任意长度(必须不超过NOR FLASH的容量)的数据。我们这里简单介绍一下思路:先获得首地址(addr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。


       3. main.c代码

       在main.c里面编写如下代码:

static uint8_t g_text_buf[] = {"STM32 XSPI TEST"};
#define TEXT_SIZE (sizeof(g_text_buf))
 
int main(void)
{
    uint8_t t = 0;
    uint8_t key;
    norflash_type_t norflash_type;
    uint32_t flashsize;
    uint8_t data[TEXT_SIZE];
    
    sys_mpu_config();                   /* 配置MPU */
    sys_cache_enable();                 /* 使能Cache */
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(300, 6, 2);    /* 配置时钟,600MHz */
    delay_init(600);                    /* 初始化延时 */
    usart_init(115200);                 /* 初始化串口 */
    led_init();                         /* 初始化LED */
    key_init();                         /* 初始化按键 */
    hyperram_init();                    /* 初始化HyperRAM */
    lcd_init();                         /* 初始化LCD */
    
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "XSPI TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "WK_UP:Write KEY0:Read", RED);
    
    /* 初始化NOR Flash */
    norflash_type = norflash_init();
    /* 开启NOR Flash内存映射 */
    norflash_memory_mapped();
    if (norflash_type == NORFlash_Unknow)
    {
        while (1)
        {
            lcd_show_string(30, 130, 200, 16, 16, "NOR Flash Check Failed!", 
RED);
            delay_ms(500);
            lcd_show_string(30, 130, 200, 16, 16, "Please Check!   ", RED);
            delay_ms(500);
            LED0_TOGGLE();
        }
    }
    lcd_show_string(30, 130, 200, 16, 16, "NOR Flash Ready!", RED);
    
    /* 获取NOR Flash片大小 */
    flashsize = norflash_get_chip_size();
    
    while (1)
    {
        key = key_scan(0);
        if (key == WKUP_PRES)
        {
            /* 往NOR Flash写入数据 */
            lcd_fill(0, 150, 239, 319, WHITE);
            lcd_show_string(30, 150, 200, 16, 16, "Start Write Flash....", 
BLUE);
            norflash_init();
            norflash_write(flashsize - TEXT_SIZE, g_text_buf, TEXT_SIZE);
            norflash_memory_mapped();
            lcd_show_string(30, 150, 200, 16, 16, "Flash Write Finished!", 
BLUE);
        }
        else if (key == KEY0_PRES)
        {
            /* 从NOR Flash读取数据 */
            lcd_show_string(30, 150, 200, 16, 16, "Start Read Flash.... ", 
BLUE);
            norflash_read(flashsize - TEXT_SIZE, data, TEXT_SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is:  ", 
BLUE);
            lcd_show_string(30, 170, 200, 16, 16, (char *)data, BLUE);
        }
        
        if (++t == 20)
        {
            t = 0;
            LED0_TOGGLE();
        }
        
        delay_ms(10);
    }
}

       在main函数前面,我们定义了g_text_buf数组,用于存放要写入到FLASH的字符串。main函数代码和IIC实验部分代码大同小异,具体流程大致是:在完成系统级和用户级初始化工作后,读取FLASH的ID,然后通过KEY0去读取(flashsize-TEXT_SIZE)地址处开始的数据并把数据显示在LCD上;另外还可以通过WKUP在(flashsize-TEXT_SIZE)地址处写入g_text_buf数据并在LCD界面中显示传输中,完成后并显示“FLASH Write Finished!”。


        38 .4 下载验证

       将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图38.4.1所示:


图38.4.1 XSPI实验程序运行效果图


       通过先按下WKUP写入数据,然后再按KEY0读取数据,得到如图38.4.2所示:


图38.4.2 操作后的显示效果图


       程序在开机的时候会检测Norflash是否存在,如果不存在则会在 TFTLCD 模块上显示错误信息,同时LED0慢闪。


来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70040860/viewspace-3095013/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2024-08-30

  • 博文量
    588
  • 访问量
    764725