本帖最后由 正点原子运营 于 2026-5-9 09:56 编辑
第三十八章 XSPI实验
1)实验平台:正点原子STM32H7R7开发板
2)章节摘自【正点原子】STM32H7R7开发指南 V1.1
3)购买链接: https://detail.tmall.com/item.htm?id=820823382459
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/stm32/zdyz_stm32h7rx.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子STM32开发板技术交流群:756580169
本章,我们将介绍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接口具有如下特点:
支持三种工作模式:间接模式、状态轮询模式和内存映射模式。
支持内存映射方式的读写。
支持单,双,四,八,十六进制通信。
支持双闪存模式,可以并行访问两个Flash。
XSPI模式访问单个16位内存。
支持SDR(单倍率速率)和DDR(双倍率速率)模式。
针对间接模式和内存映射模式,完全可编程操作码。
针对间接模式和内存映射模式,完全可编程帧格式。
支持在读方向上对内存的封装式访问。
支持HyperBus。
集成 FIFO,用于接收和传输。
允许 8、16 和 32 位数据访问。
支持DMA协议。
具有适用于间接模式操作的DMA通道。
在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断。
双芯片选择支持(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寄存器
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。
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小节有详细讲解
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,即设置内存不支持包装读取。
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,可以清零此位。
XSPI标志清除寄存器(XSPI_FCR)
XSPI标志清除寄存器描述如图38.1.1.5.5所示:
图38.1.1.5.5 XSPI_FCR寄存器
该寄存器,我们一般只用到CTCF位,用于清除XSPI的传输完成标志。
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,八线传输指令。
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结尾。
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时才可配置。
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);
复制代码 函数描述:
用于初始化XSPI。
函数形参:
形参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的输出。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
注意事项:
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);
复制代码 函数描述:
该函数用来配置XSPI命令。
函数形参:
形参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用于设置超时时间。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
3. HAL_XSPI_Receive函数
XSPI接收数据函数,其声明如下:
- HAL_StatusTypeDef HAL_XSPI_Receive(XSPI_HandleTypeDef *hxspi,
- uint8_t *const pData, uint32_t Timeout);
复制代码 函数描述:
该函数用来接收数据。
函数形参:
形参1是XSPI_HandleTypeDef结构体类型指针变量。
形参2是uint8_t类型指针变量,存放接收数据缓冲区指针。
形参3设置操作超时时间。
函数返回值:
HAL_StatusTypeDef枚举类型的值。
4. HAL_XSPI_Transmit函数
XSPI发送数据函数,其声明如下:
- HAL_StatusTypeDef HAL_XSPI_Transmit (XSPI_HandleTypeDef *hxspi,
- const uint8_t *pData);
复制代码 函数描述:
该函数用来发送数据。
函数形参:
形参1是XSPI_HandleTypeDef结构体类型指针变量。
形参2是uint8_t类型指针变量,存放发送数据缓冲区指针。
形参3设置操作超时时间。
函数返回值:
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
- * [url=home.php?mod=space&uid=271674]@param[/url] 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写函数,其定义如下:
该函数可以在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慢闪。 |