你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

MiniPro STM32H750 开发指南_V1.1-QSPI实验

[复制链接]
STMCU小助手 发布时间:2022-10-7 22:24
QSPI实验4 n9 F; P2 O2 r1 x" C& r- }+ B
本章,我们将介绍STM32H750的QSPI功能,并使用STM32H750自带的QSPI来实现对外部NOR FLASH的读写,并将结果显示在LCD模块上。, ^) }- s; D, E
$ U  g4 B: F7 G) Z  \# s$ V% ^) m9 c
36.1 QSPI及NOR FLASH芯片简介
; ^7 m: x( X/ j9 F36.1.1 QSPI简介
) N+ N: {/ _5 p$ W- s% Z: w1 d# N' _4 xQSPI是Quad SPI的缩写,是Motorola公司推出SPI接口后的一种扩展接口,较SPI应用更为广泛。在 SPI 协议的基础上,Motorola 公司对其功能进行了增加,增加了队列传输机制,推出了队列串行外围接口协议(即 QSPI 协议)。QSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI FLASH存储器。STM32H7具有QSPI接口,支持如下三种工作模式:- V& V3 m4 z2 {
1、间接模式:使用QSPI寄存器执行全部操作。/ N  f! R- ]8 r: s; I% d- ~; ^, a
2、状态轮询模式:周期性读取外部FLASH状态寄存器,当标志位置1时会产生中断(如
6 w4 h1 u) q0 U8 d, ?擦除或烧写完成,产生中断)。0 @) G6 A' [" S1 h; Z2 }
3、内存映射模式:外部FLASH映射到微控制器地址空间,从而系统将其视作内部存储器。
6 z9 }+ b7 R! F% I$ zSTM32H7的QSPI接口具有如下特点:( Y1 o5 x6 X5 D; g  ^
支持三种工作模式:间接模式、状态轮询模式和内存映射模式。
% t, [% z7 g* \- b支持双闪存模式,可以并行访问两个FLASH,可同时发送/接收8位数据。
8 O/ A* ?  Q% E) f3 C支持SDR(单倍率速率)和DDR(双倍率速率)模式。+ Y8 P* T8 M+ N$ A
针对间接模式和内存映射模式,完全可编程操作码。
# t. l" V% v& g针对间接模式和内存映射模式,完全可编程帧格式。1 f' u  Y: ^' P& Z% f
集成 FIFO,用于发送和接收。- ~& P% @* S- A4 Y  F
允许 8、16 和 32 位数据访问。
2 N6 M) P& s& O. r* }3 j具有适用于间接模式操作的DMA通道。
: l) [0 ^/ q4 V( ?: i  N在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断。+ o4 ~/ ]2 E0 @- z  s3 W  P4 ^. u
36.1.1.1 QSPI框图
  `3 c3 m% I: g. v+ S' ^  x5 PSTM32H7的QSPI单闪存模式的功能框图如图36.1.1.1.1所示:
1 w2 |+ {! P: R: d, Z+ M
; L4 S/ ^* l1 h4 ?4 D# p' ^ d8b302ad53e345db9def7c897a3cedee.png
" N3 {  g! n3 v9 K4 K! p5 {. ~) l, `- j1 u# @2 O1 p7 G
图36.1.1.1.1 STM32H7 QSPI框图; A) X1 L2 E4 J! s
上图左边可以看到QSPI连接到64位 AXI 总线以及32位AHB总线上,此外还有5条QSPI的内部信号,如下表所示:6 ^1 q7 V7 t  u" f( A/ e: k4 `

  _' L6 U1 U% `6 ?" b 9de93a86ebce47009a1660425d2aec11.png 8 k1 S' L3 n& s; ?- F$ l# d

0 G, m& {( A* n8 T9 g; A; V7 b0 P表36.1.1.1.1 QSPI内部信号
6 i9 j- W, l, D. X7 _quadspi_ker_ck,用于通讯过程的时钟。可以选择的时钟源有:HCLK3(即AHP3)、PLL1Q、PLL2R和 PER_CK,实验中我们选择PLL2R。经过sys_stm32_clock_init函数的配置,PLL2R时钟频率为220MHZ。quadspi_ker_ck还需要经过一个分频器出来的时钟频率才作为QSPI的实际使用的时钟频率,该分频器的分频系数由QUADSPI_CR寄存器的PRESCALER[7:0]位设置,范围是:0~255。
+ Z1 V( R" a8 G2 u: X4 Gquadspi_hclk,用于操作QUADSPI寄存器的时钟。时钟源来自HCLK3,同样是经过sys_stm32_clock_init函数的配置,配置后HCLK3时钟频率为240MHZ。
# [( c2 ~* @% l% wquadspi_it,中断请求信号线。在达到FIFO阈值、超时、操作完成以及发送访问错误时产生中断。% B. c3 _* q2 k) j
quadsqp_ft_trg,达到FIFO阈值时触发MDMA请求。/ W% Y$ k$ E5 T2 }  V
quadspi_tc_trg,操作完成时触发MDMA请求' N% {& O4 l  o$ r+ n6 }
上图中间部分就是QUADSPI 内核。
; C4 m0 T9 f" p4 h我们重点看看上图右边的QSPI接口引脚,通过6根线与SPI FLASH芯片连接,包括:4根数据线(IO0~3)、1根时钟线(CLK)和1根片选线(nCS),具体如下表所示:
: v8 V  S9 c0 U1 U4 h 104bc8b71bd1464fa16328b89bd11d92.png
1 s3 o7 b: B5 k' v1 V" s! d. X1 C! g0 f- }+ [6 B
表36.1.1.1.2 QSPI接口引脚
1 x# L/ s  X0 D6 R& y我们知道普通的SPI通信一般只有一根数据线(MOSI/MISO,发送/接收用),而QSPI则具有4根数据线,所以QSPI的速率至少是普通SPI的4倍,可以大大提高通信速率。如果使用双闪存模式,同时访问两个Quad-SPI Flash,速度可以再翻一番。我们开发板板载了一个Quad-SPI Flash,所以我们使用单闪存模式,双闪存模式就不具体介绍了,感兴趣请自行查看手册。
6 W6 R. ]; h- B/ B; N4 O接下来,我们给大家简单介绍一下STM32H7 QSPI接口的的几个重要知识点。1 j" `) p" _! M  I& @' A/ Q$ v" g; w
36.1.1.2 QSPI命令序列, f/ X: h7 \0 h: b1 T2 J
QSPI 通过命令与FLASH通信,每条命令包括:指令、地址、交替字节、空指令和数据这五个阶段,任一阶段均可通过配置QUADSPI_CCR寄存器相关字段跳过,但至少要包含指令、地址、交替字节或数据阶段之一。! _% \6 ]# u1 l' B9 R5 J3 y4 \3 N
nCS 在每条指令开始前下降,在每条指令完成后再次上升。QSPI四线模式下的读命令时序,如图36.1.1.2.1所示:
$ h8 E& |- g4 T# |3 I# o" ^2 ^
4 t9 n- v9 y! B, Q* Q c755a29932c74353ab498630e80158cb.png
; e0 Z' {2 [; c
$ t! d# }  D* i图36.1.1.2.1 四线模式下QSPI读命令时序+ t- k1 m$ V  M7 h3 v
从上图可以看出一次QSPI传输的5个阶段,接下来我们分别介绍。
) d. S1 R6 X  Y+ K* J5 L# G① 指令阶段% m5 ?# D* V7 D/ Z, P
此阶段通过QUADSPI_CCR[7:0]寄存器的INSTRUCTION字段指定一个8位指令发送到FLASH。注意,指令阶段,一般是通过IO0单线发送,但是也可以配置为双线/四线发送指令,可以通过QUADSPI_CCR[9:8]寄存器的IMODE[1:0]这两个位进行配置,如IMODE[1:0]=00,则表示无需发送指令。% _6 {# p7 l, \/ \4 J7 t. p
②地址阶段
; p0 {$ W+ j  U% e) ?$ F7 q; j此阶段可以发送14字节地址给FLASH芯片,指示要操作的地址。地址字节长度由QUADSPI_CCR[13:12]寄存器的ADSIZE[1:0]字段指定,03表示1~4字节地址长度。在间接模式和轮询模式下,待发送的地址由QUADSPI_AR寄存器指定。地址阶段同样可以以单线/双线/四线模式发送,通过QUADSPI_CCR[11:10]寄存器的ADMODE[1:0]这两个位进行配置,如ADMODE [1:0]=00,则表示无需发送地址。
& u4 r- [( V8 I4 F& m" M" E6 Z4 ~③交替字节(复用字节)阶段' M5 r! z; F- e1 s+ A' h/ m" ?6 J% V) F
此阶段可以发送1~4字节数据给FLASH芯片,一般用于控制操作模式。待发送的交替字节数由QUADSPI_CCR[17:16]寄存器的ABSIZE[1:0]位配置。待发送的数据由QUADSPI_ABR寄存器中指定。交替字节同样可以以单线/双线/四线模式发送,通过QUADSPI_CCR[15:14]寄存器的ABMODE[1:0]这两个位配置,ABMODE[1:0]=00,则跳过交替字节阶段。
3 t$ R  q. R* v/ E④空指令周期阶段
# J$ g1 j; ~9 F) r8 n1 D在空指令周期阶段,在给定的1~31个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给FLASH芯片留出准备数据阶段的时间。这一阶段中给定的周期数由QUADSPI_CCR[22:18]寄存器的DCYC[4:0]位配置。 若DCYC为零,则跳过空指令周期阶段,命令序列直接进入下一个阶段。
- t4 R: S: G  `8 X5 U⑤数据阶段
! `' }0 }( c  t& m) Q) V此阶段可以从FLASH读取/写入任意字节数量的数据。在间接模式和自动轮询模式下,待发送/接收的字节数由QUADSPI_DLR寄存器指定。在间接写入模式下,发送到FLASH的数据必须写入QUADSPI_DR寄存器。在间接读取模式下,通过读取QUADSPI_DR寄存器获得从 FLASH接收的数据。数据阶段同样可以以单线/双线/四线模式发送,通过QUADSPI_CCR[25:24]寄存器的DMODE [1:0]这两个位进行配置,如DMODE [1:0]=00,则表示无数据。
2 ]! c1 @% q( w6 ?7 t- y# h9 {8 V以上就是QSPI数据传输的5个阶段,其中交替字节阶段我们一般用不到,可以省略(通过设置ABMODE[1:0]=00)。' g# B6 O- W2 F0 E
另外说明一下,QUADSPI信号接口协议模式包括:单线SPI模式、双线SPI模式、四线SPI模式、SDR模式、DDR模式和双闪存模式。这些模式请大家自行参考官方手册。1 q* T4 Q3 O. h) A* h; n
36.1.1.3 QSPI三种功能模式
# |& o; y/ |/ N1 w" P3 C# \( [前面已经提到过QSPI的三种功能模式:间接模式、状态标志轮询模式和内存映射模式。下面对这三个功能模式分别简单介绍。
4 e$ s% J( o0 [& @! p- A① 间接模式  i: \4 m. a4 `# m5 }0 o
在间接模式下,通过写入QUADSPI寄存器来触发命令,通过读写数据寄存器来传输数据。
! u( X: N# H$ C3 u) s当FMODE=00 (QUADSPI_CCR[27:26])时,QUADSPI处于间接写入模式,在数据阶段,将数据写入数据寄存器(QUADSPI_DR),即可写入数据到FLASH。3 t* K% G1 x) s$ O, e
当FMODE=01时,QUADSPI处于间接读取模式,在数据阶段,读取QUADSPI_DR寄存器,即可读取FLASH里面的数据。
+ Z/ i2 q& T, }9 i6 H, Q读/写字节数由数据长度寄存器(QUADSPI_DLR)指定。当QUADSPI_DLR=0xFFFFFFFF时,则数据长度视为未定义,QUADSPI 将持续传输数据,直到到达FLASH结尾(FLASH容量由 QUADSPI_DCR[20:16]寄存器的FSIZE[4:0]位定义)。如果不传输任何数据,则DMODE[1:0] (QUADSPI_CCR[25:24])应设置为00。2 v$ I2 q2 }+ x
当发送或接收的字节数(数据量)达到编程设定值时,如果TCIE=1,则TCF置1并产生中断。在数据量不确定的情况下,将根据FSIZE[4:0]定义的FLASH大小,在达到外部SPI FLASH的限制时,TCF置1。
: f8 P/ m& a; t在间接模式下有三种触发命令启动的方式,分别是:2 [, j3 _& T( Q
(1)当不需要发送地址(ADMODE[1:0]==00)和数据(DMODE[1:0]==00)时,对INSTRUCTION[7:0](QUADSPI_CCR[7:0])执行写入操作。1 _: `3 P! x0 u8 N2 u. A
(2)当需要发送地址(ADMODE[1:0]!=00),但不需要发送数据(DMODE[1:0]==00),对ADDRESS[31:0](QUADSPI_AR)执行写入操作。
0 _$ M+ p+ }! U- ~. V5 T7 r5 u(3)当需要发送地址(ADMODE[1:0]!=00)和数据(DMODE[1:0]!=00)时,对DATA[31:0] (QUADSPI_DR)执行写入操作。* k0 u( x- ]# u/ D  W
如果命令启动,BUSY位(QUADSPI_SR的第5位)将自动置1。9 Y# V6 l5 T7 |; t
②状态标志轮询模式
1 z5 f# u% F  [7 S将FMODE字段(QUADSPI_CCR[27:26]) 设置为10,使能状态标志轮询模式。在此模式下,将发送编程的帧并周期性检索数据。每帧中读取的最大数据量为4字节。如果QUADSPI_DLR请求更多的数据,则忽略多余部分并仅读取4个字节。在 QUADSPI_PISR 寄存器指定周期性。/ a, o  Q1 n2 [7 x5 W
在检索到状态数据后,可在内部进行处理,以达到以下目的:
' e! C5 u) [2 s(1)将状态匹配标志位置 1,如果使能,还将产生中断.
# q9 A/ h+ p8 _# }' I- i$ K(2)自动停止周期性检索状态字节。2 n5 R$ q7 G$ _: E  E
接收到的值可通过存储于QUADSPI_PSMKR寄存器中的值来进行屏蔽,并与存储在 QUADSPI_PSMAR寄存器中的值进行或运算或与运算。/ H0 b! {) @! W9 _7 g- T
若是存在匹配,则状态匹配标志置1,并且在使能了中断的情况下还将产生中断;如果AMPS 位置1,则QUADSPI自动停止。
/ e# i' F% A1 v1 V) T6 V, Y5 |) J在任何情况下,最新的检索值都在QUADSPI_DR中可用。
2 F$ [; F+ K  I; m( J9 F①内存映射模式0 c9 u6 H/ ~4 _/ i
在配置为内存映射模式时,外部FLASH器件被视作内部存储器,只是存在访问延迟。在该模式下,仅允许对外部 FLASH 执行读取操作。将QUADSPI_CCR寄存器中的FMODE设置为11可进入内存映射模式。当主器件访问存储器映射空间时,将发送已编程的指令和帧。另外数据长度寄存器(QUADSPI_DLR)在内存映射模式中无意义。
/ K: H4 |$ I/ H" E9 V* e& v! SQUADSPI外设若没有正确配置并使能,禁止访问QUADSPI Flash的存储区域。即使FLASH容量更大,寻址空间也无法超过256MB。如果访问的地址超出FSIZE定义的范围但仍在256MB 范围内,则生成总线错误。此错误的影响具体取决于尝试进行访问的总线主器件:
& I% P  a; F& b(1)如果为Cortex® CPU,则会在使能总线故障时发生总线故障异常,在禁止总线故障时发生硬性故障(hard fault) 异常。* b% V- O+ H* q- G6 A; I+ m  B8 P
(2)如果为 DMA,则生成 DMA传输错误,并自动禁用相应的 DMA 通道。
! }# W" X# E/ M% X4 ?# q6 }1 C$ G0 ?! e7 I内存映射模式支持字节、半字和字访问类型,并支持芯片内执(XIP)操作,QUADSPI接受下一个微控制器访问并提前加载后面地址中的字节。如果之后访问的是连续地址,由于值已经预取,访问将更快完成。
( A. f2 P# N! w$ w; w$ E# ?默认情况下,即便在很长时间内不访问FLASH,QUADSPI也不会停止预取操作,之前的读取操作将保持激活状态并且 nCS 保持低电平。由于 nCS保持低电平时,FLASH 功耗增加,应用程序可能会激活超时计数器(TCEN = 1, QUADSPI_CR 的位 3)。从而在 FIFO中写满预取的数据后,若在 TIMEOUT[15:0] (QUADSPI_LPTR) 个周期的时长内没有访问,则释放 nCS。BUSY在第一个存储器映射访问发生时变为高电平。由于进行预取操作,BUSY在发生超时、中止或外设禁止前不会下降。! ?. E3 X9 s2 E' ~/ J' Y* r' A/ `
36.1.1.4 QSPI FLASH配置
5 X! P' p% V& ^SPI FLASH芯片的相关参数通过器件配置寄存器 (QUADSPI_DCR) 来进行设置。寄存器QUADSPI_DCR[20:16]的FSIZE[4:0]这5个位,用于指定外部存储器的大小,计算公式为:, O$ y# `* w' J
Fcap=21
: p$ A: z* \; p# DFSIZE+1是对Flash寻址所需的地址位数。Fcap表示FLASH的容量,单位为字节,在间接模式下,最高支持4GB(使用32位进行寻址)容量的FLASH芯片。但是在内存映射模式下的可寻址空间限制为256MB。8 a. ]) r. S3 ]% X/ V
QSPI连续执行两条命令时,它在两条命令之间将片选信号 (nCS) 置为高电平默认仅一个 CLK周期。某些FLASH需要命令之间的时间更长,可以通过寄存器QUADSPI_DCR[10:8]的CSHT[2:0](选高电平时间)这3个位设置高电平时长:07表示18个时钟周期(最大为8)。0 q  o2 F) w  e5 z9 w! J
时钟模式,用于指定在nCS为高电平时,CLK的时钟极性。通过寄存器QUADSPI_DCR[0]的CKMODE位指定:当CKMODE=0时,CLK在nCS为高电平期间保持低电平,称之为模式0;当CKMODE=1时,CLK在nCS为高电平期间保持高电平,称之为模式3。4 ?' a7 U- S5 z
36.1.1.5 QUADSPI寄存器2 ?9 R3 ]$ }/ R( O: ?3 t) e
QUADSPI控制寄存器(QUADSPI_CR)
& ~3 E1 M6 N5 i2 o' Z! }QUADSPI控制寄存器描述如图36.1.1.5.1所示:
/ C  ~) ?# u- [9 o3 F, l" ?) c( @7 b4 H( s- e
35d153dd5b954cd19b65fb812e50be6d.png % o" Z5 J1 V# x+ O# u# |* b

9 c' F6 D% P1 |4 z4 M图36.1.1.5.1 QUADSPI_CR寄存器
6 Q3 K3 L8 s  ]  s* ~  s/ H该寄存器我们只关心需要用到的一些位(下同),首先是PRESCALER[7:0],用于设置AHB时钟预分频器:0255,表示0256分频。我们使用的W25Q128最大支持104Mhz的时钟,这里我们设置PRESCALER=2,即3分频,得到QSPI时钟为72Mhz(216/3)。! _' K: ~$ v" @! D8 N6 ^6 [
FTHRES[4:0],用于设置FIFO阈值,范围为031,表示FIFO的阈值为132字节。4 }) l/ L, Q( l& X: T. G2 P9 Q
FSEL位,用于选择FLASH,我们的W25Q128连接在STM32H7的QSPI BK1上面,所以设置此位为0即可。
) e/ T1 S; x  v; I2 m  e# |& DDFM位,用于设置双闪存模式,我们用的是单闪存模式,所以设置此位为0即可。
+ q& j' A# i" f8 Y; USSHIFT位,用于设置采样移位,默认情况下,QSPI接口在FLASH驱动数据后过半个CLK 周期开始采集数据。使用该位,可考虑外部信号延迟,推迟数据采集。我们一般设置此位为1,移位半个周期采集,确保数据稳定。
1 J; W) D) q* v( W7 Y+ T" ]% BABORT位,用于终止QSPI的当前传输,设置为1即可终止当前传输,在读写FLASH数据的时候,可能会用到。
9 `; }* [: z8 a; h* W; l7 H7 jEN位,用于控制QSPI的使能,我们需要用到QSPI接口,所以必须设置此位为1。1 t# f7 v: w$ @
QUADSPI器件配置寄存器(QUADSPI_ DCR). a$ ?7 L' ]8 ]
QUADSPI器件配置寄存器描述如图36.1.1.5.2所示:/ w4 w' e( l9 n  w# ]. M5 X0 m. ^
9 P' D7 |3 C: j6 N) [
f2db90ba4b4f4083a2fc5e04699a4de2.png
) K2 b/ r4 H2 x/ a
' {. F' e6 _9 F' z4 W, l图36.1.1.5.2 QUADSPI_ DCR寄存器' {# y8 Y. {" A
该寄存器可以设置FLASH芯片的容量(FSIZE)、片选高电平时间(CSHT)和时钟模式(CKMODE)等,这些位的设置说明见前面的36.1.1.4小节有详细讲解。! N" D. m) q- E+ O7 p1 \8 r5 K2 ^- t
QUADSPI状态寄存器(QUADSPI_ SR)
& ?0 ]- ?2 N8 @& \QUADSPI状态寄存器描述如图36.1.1.5.3所示:
  h2 |! @' M9 _# [, T
( K! C- a5 R( C7 @" P% z# k5 h 0c67727622474763b4081b99327b462d.png
& |; L. `/ o9 t9 C% Y$ ?$ ~0 {; y5 z7 Q$ H
图36.1.1.5.3 QUADSPI_ SR寄存器2 l2 z# q/ ?+ X1 m3 j: N
BUSY位,指示操作是否忙。当该位为1时,表示QSPI正在执行操作。在操作完成或者FIFO为空的时候,该位自动清零。
" N, M' k+ `8 F7 U4 eFTF位,表示FIFO是否到达阈值。在间接模式下,若达到FIFO阈值,或从FLASH读取完成后,FIFO中留有数据时,该位置1。只要阈值条件不再为“真”,该位就自动清零。
- U- S! h* S9 X8 O! |% M4 wTCF位,表示传输是否完成。在间接模式下,当传输的数据数量达到编程设定值,或在任何模式下传输中止时,该位置1。向QUADSPI_FCR寄存器的CTCF位写1,可以清零此位。
0 w: D+ A1 n3 M  K" ^& U+ CQUADSPI标志清零寄存器(QUADSPI_ FCR)
  [% |2 J/ V: |. P; i; o, qQUADSPI标志清零寄存器描述如图36.1.1.5.4所示:
' p2 [1 D( n+ |- R) T/ v3 U4 B$ t" p" h, E, ^; r1 ?9 x' i7 l5 |
22eb85ed41c74afda8a17300b8ad494e.png % _4 ^- F6 g+ {& Q: J) l

( o2 ^. [) p8 S! G9 ]* W" [图36.1.1.5.4 QUADSPI_ FCR寄存器
, r) T  x. Z. @% M  Z该寄存器,我们一般只用到CTCF位,用于清除QSPI的传输完成标志。
) d+ R4 @* D$ q! Y! R( f0 `QUADSPI通信配置寄存器(QUADSPI_ CCR)
. B% p; @: T+ o  [/ w- \( z8 XQUADSPI通信配置寄存器描述如图36.1.1.5.5所示:
# z  j* u# C9 g- B& u# [+ V2 t
+ b2 c2 ~1 ]/ q) N, o) \8 D& c 617cebe81c76488c9e4e9a3045f24cef.png . @. `8 K3 k$ o. ]

/ L8 P& o. e' O+ v7 j图36.1.1.5.5 QUADSPI_ CCR寄存器- ?/ m) J, B- `) s6 Z/ v
DDRM位,用于设置双倍率模式(DDR),我们没用到双倍率模式,所以设置此位为0。2 V. r$ p4 J, ~1 M/ q8 }- h
SIOO位,用于设置指令是否只发送一次,我们需要每次都发送指令,所以设置此位为0。
3 r3 R. O* a! F& s3 O6 u8 IFMODE[1:0],这两个位用于设置功能模式:00,间接写入模式;01,间接读取模式;10,自动轮询模式;11,内存映射模式;我们使用间接模式,所以此位根据需要设置为00/01。
2 Z' [* l3 s# G6 VDMODE[1:0],这两个位用于设置数据模式:00,无数据;01,单线传输数据;10,双线传输数据;11,四线传输数据;我们一般设置为00/11。7 T4 A$ _0 ?# `/ a$ y$ |: Z
DCYC[4:0],这5个位用于设置空指令周期数,可以控制空指令阶段的持续时间,设置范围为:0~31。设置为0,则表示没有空指令周期。/ w" ?. y& z( w7 \
ABMODE[1:0],这两个位用于设置交替字节模式,我们一般设置为0,表示无交替字节。1 m* \0 _4 s6 q
ADMODE[1:0],这两个位用于设置地址模式:00,无地址;01,单线传输地址;10,双线传输地址;11,四线传输地址;我们一般设置为00/11。! q$ @+ A& e- }9 L
IMODE[1:0],这两个位用于设置指令模式:00,无指令;01,单线传输指令;10,双线传输指令;11,四线传输指令;我们一般设置为00/11。
$ A  p1 B. u; B/ N, @INSTRUCTION[7:0],这8个位用于设置将要发送给FLASH的指令。
! z: R: O  F* Y* s- B) H1 u7 Q注意,以上这些位的配置,都必须在QUADSPI_SR寄存器的BUSY位为0时才可配置。
9 X* {% L( m4 f1 C3 P$ ?0 D7 N) |接下来,我们看QSPI数据长度寄存器:QUADSPI_DLR,该寄存器为一个32位寄存器,可以设置的数据长度范围为:0~0XFFFFFFFF,当QUADSPI_DLR!=0XFFFFFFFF时,表示传输的字节长度(+1);当QUADSPI_DLR==0XFFFFFFFF时,表示不限传输长度,直到到达由FSIZE定义的FLASH结尾。! X9 l+ F5 v/ P& k
接下来,我们看QSPI地址寄存器:QUADSPI_AR,该寄存器为一个32位寄存器,用于指定发送到FLASH的地址。) a; s9 n4 g* F- N, R6 L% L
接下来,我们看QSPI数据寄存器:QUADSPI_DR,该寄存器为一个32位寄存器,用于指定与外部SPI FLASH设备交换的数据。该寄存器支持字、半字和字节访问。& W- J; _) C4 r7 t1 J
在间接写入模式下,写入该寄存器的数据在数据阶段发送到FLASH,在此之前则存储于FIFO,如果 FIFO 满了,则暂停写入,直到 FIFO 具有足够的空间接受要写入的数据才继续。
; {" E( T$ l1 Z  t/ M% ]在间接模式下,读取该寄存器可获得(通过FIFO)已从FLASH接收的数据。如果FIFO所含字节数比读取操作要求的字节数少,且BUSY=1,则暂停读取,直到足够的数据出现或传输完成才继续。
  t$ c" F9 y1 W+ m36.1.2 NOR FLASH芯片简介' h4 Z( {# @. d# S3 b
NOR FLASH芯片有很多种芯片型号,在我们的norflash.h头文件中有定义芯片ID的宏定义,对应的就是不同型号的NOR FLASH芯片,比如有:W25Q128、BY25Q128、NM25Q128,它们是来自不同的厂商的同种规格的NOR FLASH芯片,内存空间都是128M字,即16M字节。它们的很多参数、操作都是一样的,所以我们的实验都是兼容它们的。
- c9 E: V9 s- e由于这么多的芯片我们就不一一进行介绍了,就拿其中一款型号进行介绍即可,其他的型号都是类似的。' i/ e& q; u" ~  z
W25Q128是一款大容量SPI FLASH产品,其容量为16M。它将16M字节的容量分为256个块(Block),每一个块大小为64K字节,每个块又分为16个扇区(Sector),每一个扇区16页,每页256个字节,即每个扇区4K字节。W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q128开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。: r7 T9 h  r  ^7 w. O1 \
W25Q128的擦写周期多达10W次,具有20年的数据保存期限,支持电压为2.7~3.6V,W25Q128支持标准的SPI,还支持双输出/四输出SPI和QPI(QPI即QSPI),最高时钟频率可达104Mhz(双输出时相当于208Mhz,四输出时相当于416M),本实验我们将使用STM32H7的QSPI接口来实现对W25Q128的驱动。7 b" T" [/ H, ~* L( l& @( C
接下来,我们介绍一下本实验驱动W25Q128需要用到的一些指令,如表36.1.2.1所示:
; I. m" m4 d" N* r. i3 N% y3 R* O& h( w, T5 k: @
339ea32194e54beaa633069446500d7b.png   H8 o; P4 U3 V# u1 p6 }% Y; P
" U+ {0 D. y' ~5 |3 c  z
1,在QPI模式下dummy时钟的个数,由读参数控制位P[5:4]位控制。
# }% u; P# W" R7 d) o2,传输的数据量,只要不停的给时钟就可以持续传输,对W25X_PageProgram指令,则单次传输最多不超过256字节,否则将覆盖之前写入的数据。8 e& ]% {5 N3 H8 J3 [
表36.1.2.1 W25Q128指令* [. r; J* o3 j% c% I3 }: j
上表列出了本章我们驱动W25Q128所需要用到的所有指令和对应的参数,注意SPI模式和QPI模式下时钟数的区别,可知QPI模式比SPI模式所需要的时钟数少的多,所以速度也快得多。接下来我们简单介绍一下这些指令。- F% M: Y) C5 B" j6 B' E
首先,前面6个指令,是用来读取/写入状态寄存器13的。在读取的时候,读取S23S0的数据,在写入的时候,写入S23S0。而S23S0则由三部分组成:S23S16,S15S8,S7~S0即状态寄存器3、2、1,如表36.1.2.2所示:
: b# m- t" H' t4 R% A! |: y; c8 o+ O
b1c5e051153a480398745967a634e1ea.png
9 _: E; d7 [7 ], B6 a+ T. `. y3 l( w! D! [2 M5 Z/ q
表36.1.2.2 W25Q128状态寄存器5 M/ l( k) ~5 t& G" m( U
这三个状态寄存器,我们只关心我们需要用到的一些位:ADS、QE和BUSY位。其他位的说明,请看W25Q128的数据手册。7 E( W; L+ U2 I
ADS位,表示W25Q128当前的地址模式,是一个只读位,当ADS=0的时候,表示当前是3字节地址模式,当ADS=1的时候,表示当前是4字节地址模式,我们需要使用4字节地址模式,所以在读取到该位为0的时候,必须通过W25X_Enable4ByteAddr指令,设置为4字节地址模式。
# b% S( d6 u" S# }/ N1 j% s$ FQE位,用于使能4线模式(Quad),此位可读可写,并且是可以保存的(掉电后可以继续保持上一次的值)。在本章,我们需要用到4线模式,所以在读到该位为0的时候,必须通过W25X_WriteStatusReg2指令设置此位为1,表示使能4线模式。
5 Q( F  t  Y$ G0 s. e; wBUSY位,用于表示擦除/编程操作是否正在进行,当擦除/编程操作正在进行时,此位为1,此时W25Q128不接受任何指令,当擦除/编程操作完成时,此位为0。此位为只读位,我们在执行某些操作的时候,必须等待此位为0。
8 U+ `" l9 h1 M; a, q) GW25X_ManufactDeviceID指令,用于读取W25Q128的ID,可以用于判断W25Q128是否正常。对于W25Q128来说:MF[7:0]=0XEF,ID[7:0]=0X18。
1 B9 V, E+ y; E; ^" T: W! AW25X_EnterQPIMode指令,用于设置W25Q128进入QPI模式。上电时,W25Q128默认是SPI模式,我们需要通过该指令设置其进入QPI模式。注意:在发送该指令之前,必须先设置状态寄存器2的QE位为1!!; q" \' F$ A4 a8 L2 U
W25X_Enable4ByteAddr指令,用于设置W25Q128进入4字节地址模式。当读取到ADS位为0的时候,我们必须通过此指令将W25Q128设置为4字节地址模式,否则将只能访问16MB的地址空间。4 z7 Q  R. @2 {+ I: J
W25X_SetReadParam指令,可以用于设置读参数控制位P[5:4],这两个位的描述如表36.1.2.3所示:$ \: c* q# r- p, Z4 }

5 q2 ?' d- v, o; g 61ad36f26a334717ae7eb6eaeb452919.png
) s9 M) c; f1 e# D& ^7 I' e0 R7 }; J5 r
表36.1.2.3 W25Q128读参数控制位
2 M0 N* F$ p+ l7 f为了让W25Q128可以工作在最大频率下,我们这里设置P[5:4]=11,即可工作在104Mhz的时钟频率下。此时,读取数据时的dummy时钟个数为8个(参见W25X_FastReadData指令)。
- W2 C& ?$ {! l" QW25X_WriteEnable指令,用于设置W25Q128写使能。在执行擦除、编程、写状态寄存器等操作之前,都必须通过该指令,设置W25Q128写使能,否则无法写入。# R) X* {  H0 i0 M4 b
W25X_FastReadData指令,用于读取FLASH数据,在发送完该指令以后,就可以读取W25Q128的数据了。该指令发送完成后,我们可以持续读取FLASH里面的数据,只要不停的给时钟,就可以不停的读取数据。7 B7 z0 b% V  o. ~% \/ |5 V0 f& ^6 w
W25X_PageProgram指令,用于编程FLASH(写入数据到FLASH),该指令发送完成后,最多可以一次写入256字节到W25Q128,超过256字节则需要多次发送该指令。
; i/ I2 s( R4 S6 a! C( X0 [' W3 @! LW25X_SectorErase指令,用于擦除一个扇区(4KB)的数据。因为FLASH具有只可以写0,不可以写1的特性,所以在写入数据的时候,一般需要先擦除(归1),再写。W25Q128的最小擦除单位为一个扇区(4KB)。该指令在写入数据的时候,经常要有用。
+ @3 P1 x6 G4 w3 F, N7 YW25X_ChipErase指令,用于全片擦除W25Q128。  N" O, h( v( @8 T3 d
为了在程序上方便使用,我们把FLASH芯片的常用指令编码定义为宏定义的形式,存放在norflash.h文件中。
3 N1 J* s+ W& o& M( x
! i$ ^. }, Q; x0 y- m. ?0 T9 p& e36.2 硬件设计# ?: f7 r1 r, G6 e
1.例程功能) _: H" P, x* n, x  E
通过KEY1按键来控制norflash的写入,通过按键KEY0来控制norflash的读取。并在LCD模块上面显示相关信息。我们还可以通过USMART控制读取norflash的ID、擦除某个扇区或整片擦除。LED0闪烁用于提示程序正在运行。
7 S5 y7 X# V3 A( e! w" W2.硬件资源
3 B. V4 V& d  h: l+ M; `! k1)RGB灯
. G3 D5 D0 e3 I3 J4 J. D0 X' t1 KRED :LED0 - PB42 ]% R1 v1 t& u* K. {0 a% O, u3 e& F
2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)8 i3 B$ M( z5 e; C8 y
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)3 a' H  Y' I7 N% s
4)独立按键 :KEY0 - PA1、KEY1 - PA15
  B3 l8 t5 I( U& |# ^- R5)QSPI(PB2/PB6/PD11/PD12/PD13/PE2)
5 Y' k5 A2 \4 ]2 ]8 R6)norflash(QSPI FLASH芯片,连接在QSPI接口上)6 C/ l& h2 {, z4 c
3.原理图9 ~4 A8 ^9 `, D
板载的QSPI FLASH芯片与STM32H750的连接关系,如下图所示:' o+ P7 Z5 L- P( `+ ~: d
3 Q& Z" ~) D: k! F( m* q
d2f611c0e6f24f9eb68b3b5b326e5015.png
* n$ ~: c( C, T8 @) l8 `8 u( o; k, O, t3 E  w
图36.2.1 QSPI FLASH芯片与STM32H750连接示意图
: j" `9 C1 Q2 b7 x* j- h7 l本实验支持多种型号的QSPI FLASH芯片,比如:BY25Q128/NM25Q128/W25Q128等等,具体请看norflash.h文件的宏定义,程序上只需要稍微修改一下,后面讲解程序的时候会说到。
7 A! p5 O- Z; o4 A8 o7 o* B+ u6 m* e1 J7 T
36.3 程序设计

  U, D5 b( }  ]- E# E# Z36.3.1 QSPI的HAL库驱动
8 @0 }* Q0 ?" h# ]8 t  Z9 d3 LQSPI在HAL库中的驱动代码在stm32h7xx_hal_qspi.c文件(及其头文件)中。
9 b4 G) r! B& g1.HAL_QSPI_Init函数
. `( }& T' u! dQSPI的初始化函数,其声明如下:8 K& M0 U8 K- T) r) i2 a. f
HAL_StatusTypeDef HAL_QSPI_Init(QSPI_HandleTypeDef *hqspi);
* ^2 L5 B1 w; z; Q* ^函数描述:) B5 o4 V5 ]) C
用于初始化QSPI。
- ?0 Z! E6 y0 c* r函数形参:
0 B) Z' g" n& V8 ]9 g1 ^# ^形参1是QSPI_HandleTypeDef结构体类型指针变量,其定义如下:' L  B) }1 e* `, T; F
  1. typedef struct
    + p, u! `$ w7 c' ^
  2. {3 ^+ s6 ]- o4 o( ^
  3.   QUADSPI_TypeDef                            *Instance;            /* QSPI寄存器基址 */  # [# t0 l2 q% l! g9 {
  4.   QSPI_InitTypeDef                      Init;                  /* QSPI参数配置结构体 */   
    , R2 I& j6 D8 v5 w, W- X
  5.   uint8_t                                *pTxBuffPtr;          /* 要发送数据的地址 */      
    % o9 h. p# t9 b
  6.   __IO uint16_t                         TxXferSize;           /* 要发送数据的大小 */      
    $ a/ t/ |1 U* I. }$ m
  7.   __IO uint16_t                         TxXferCount;          /* 剩余要发送数据的个数 */      ( H! e2 v" o* d7 c  P
  8.   uint8_t                                *pRxBuffPtr;          /* 要接收数据的地址 */      4 i/ d; g5 G; n
  9.   __IO uint16_t                         RxXferSize;           /* 要接收数据的大小 */      
    6 G7 C2 \* Q- O
  10.   __IO uint16_t                         RxXferCount;          /* 剩余要接收数据的个数 */      
    3 c& K9 g4 z% J
  11.   DMA_HandleTypeDef                    * hmdma;               /* DMA配置结构体 */        ' j: ]& q: i: y0 X9 B" q7 h
  12.   __IO HAL_LockTypeDef                 Lock;                  /* 锁对象 */      : Q8 v9 x, {+ N1 A6 v. u: C* W
  13.   __IO HAL_QSPI_StateTypeDef          State;                /* QSPI通信状态 */      
    3 O( [/ x; A- e% P9 H
  14.   __IO uint32_t                         ErrorCode;            /* 错误代码 */      
    % A& ~$ H' Y+ b. q; F
  15.   uint32_t                               Timeout;              /* 配置QSPI内存访问超时时间 */      
    - z' g! A( J% A' x+ A
  16. }QSPI_HandleTypeDef;; ]4 e2 p0 d- w0 O- v
复制代码
3 P" U" k0 F! i& y. N
Instance:用于设置QSPI寄存器基地址,设置为QUADSPI即可,这个官方已经为我们做好了宏定义。
& q% u3 [% [. G0 hInit:用于设置QSPI的相关参数,QSPI_InitTypeDef结构体下面再进行详细讲解。7 d; X' z8 ~8 @4 {
pTxBuffPtr、TxXferSize和TxXferCount:分别用于设置 QSPI 发送缓冲指针、发送数据量和发送剩余数据量。$ M- Z  K- C9 `5 R* B
pRxBuffPtr、RxXferSize和RxXferCount:分别用于设置接收缓冲指针、接收数据量和接收剩余数据量。/ m- _/ W9 f( o1 _
hmdma:用于配置相关的 DMA 参数。
) v1 t. j1 L7 {( t/ y4 d& ELock:用于分配锁资源,可选择 HAL_UNLOCKED 或者是 HAL_LOCKED两个参数。, X6 X6 ^# w1 q6 ~' x0 I* M) W
State:用于存放通讯过程中的工作状态。  n4 `9 y/ Z. S1 Q+ C! y( i! m
ErrorCode:通过该参数,用户可以了解到QSPI通讯过程中通信失败的原因。+ H! A& ], w4 r2 z9 a: I! T/ _- p
Timeout:用于设置超时时间。QSPI访问时间一旦超出 Timeout这个变量值,那么ErrorCode成员变量就会被赋值为HAL_QSPI_ERROR_TIMEOUT,表示操作超时。+ p9 G; C) g6 T8 G
下面重点来了解QSPI_InitTypeDef结构体的内容,其定义如下:
1 t, }9 w5 U# Q/ g
  1. typedef struct! K, k/ p5 D1 Q" \) Y' \! T8 [
  2. {
    % X* i8 P( C0 i2 N  W! e+ F) p
  3.   uint32_t ClockPrescaler;                      /* 时钟预分频系数 */                          , h) M* B0 Y: I% G- z- V( Z
  4.   uint32_t FifoThreshold;                       /* 设置FIFO阈值级别 */      $ y' s- Q+ i7 W; u6 h
  5.   uint32_t SampleShifting;                      /* 设置采样移位 */7 N6 j4 ?$ W' a; y- ?) I
  6.   uint32_t FlashSize;                            /* 设置FLASH大小 */         9 e5 Y; O  z0 U% L3 v* b9 ]
  7.   uint32_t ChipSelectHighTime;                 /* 设置片选高电平时间 */# v( G- F" c- Z: q7 M9 [/ w- ^
  8.   uint32_t ClockMode;                            /* 设置时钟模式 */         - M3 u0 ^+ ~4 P  ~( ^8 K
  9.   uint32_t FlashID;                              /* 闪存ID,第一片还是第二片 */               9 p8 T. \; ^5 ~$ t/ C$ Z6 l/ A
  10.   uint32_t DualFlash;                           /* 双闪存模式设置 */                       
    + ^9 _  B8 K# G( \& m4 o) y! d
  11. }QSPI_InitTypeDef;
复制代码

4 b& U1 p$ H0 a3 c) M0 Z# F4 RClockPrescaler:用于设置预分频系数,对应QUADSPI_CR寄存器的PRESCALER[7:0]位,取值范围是 0~255。仅可在 BUSY = 0 时修改该字段。8 V. a" ^$ g: Q1 n
FifoThreshold:用于设置FIFO阈值级别,可设置范围为 0~31,对应QUADSPI_CR寄存器的FTHRES[4:0]位。
; c! T5 R7 w1 h, xSampleShifting:用于设置采样移位,对应QUADSPI_CR寄存器的SSHIFT位。使用该位是考虑到外部信号延迟时,推迟数据采样。可以取值 QSPI_SAMPLE_SHIFTING_NONE(即0):不发生移位;QSPI_SAMPLE_SHIFTING_HALFCYCLE(即1):移位半个周期。在DDR模式下 (DDRM = 1),固件必须确保SSHIFT = 0。- n& G. D& x, M8 q/ ?2 b
FlashSize:用于设置FLASH大小,对应QUADSPI_ DCR寄存器的FSIZE[4:0]位,可设置的范围是:0到31之间的整数。FLASH 中的字节数= 2 [FSIZE+1]。在间接模式下,FLASH容量最高可达4GB(使用 32 位进行寻址),但在内存映射模式下的可寻址空间限制为256MB。9 m: g2 h; Y5 v5 A3 S- H  Y6 R1 x
ChipSelectHighTime:用于设置片选高电平时间,取值范围:QSPI_CS_HIGH_TIME_1_CYCLE ~ QSPI_CS_HIGH_TIME_8_CYCLE,表示 1~8个周期,对应QUADSPI_DCR寄存器的CSHT[2:0]位。CSHT+1定义片选 (nCS) 在发送至 Flash 的命令之间必须保持高电平的最少CLK周期数。
+ y& ]! a& e1 N. G8 ?* PClockMode:用于设置时钟模式,对应QUADSPI_DCR寄存器CKMODE位,指示 CLK在命令之间(nCS = 1 时)的电平,可以选择的参数是:QSPI_CLOCK_MODE_0(表示模式0)或者QSPI_CLOCK_MODE_3(表示模式3)。模式 0是:nCS为高电平(片选释放)时,CLK 必须保持低电平。模式3是:nCS 为高电平(片选释放)时,CLK 必须保持高电平。
) v; s0 k& Z" g" qFlashID:用于选择Flash1或者Flash2,单闪存模式下选择QSPI_FLASH_ID_1(表示Flash1)。
1 L$ t3 r  i8 PDualFlash:用于使能双闪存模式,QSPI_DUALFLASH_DISABLE:禁止双闪存模式;QSPI_DUALFLASH_ENABLE:使能双闪存模式。对应QUADSPI_CR寄存器DFM位。) X4 B9 ~8 i  `) a6 M7 ~
函数返回值:* _# x- g8 e; E0 \& w: X
HAL_StatusTypeDef枚举类型的值。. j- `" D4 P4 m! h4 C& u
注意事项:/ r) R: V9 N3 ^5 a9 U+ m
QSPI的MSP初始化函数HAL_QSPI_MspInit,该函数声明如下:3 _7 n3 \; K; l5 x2 ?
void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi);" g- [7 p) F; _5 q
HAL_QSPI_Command函数* @1 c; @- ^# r  Y$ u$ t, F
QSPI设置命令配置函数,其声明如下:
& F. l5 H9 D9 v+ N3 T3 Q" ^HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi,
5 d- @' R2 ]4 z1 s$ V# o3 cQSPI_CommandTypeDef *cmd, uint32_t Timeout);# G2 Z0 L4 P( q! W. Y
函数描述:8 P2 @# o3 U7 d* X
该函数用来配置QSPI命令。
5 D( R3 A9 M+ R  W* M* i函数形参:! ?2 u8 A, u3 d- p( R7 h5 T) b
形参1是QSPI_HandleTypeDef结构体类型指针变量。
: s" B' f9 B! N* o形参2是QSPI_CommandTypeDef结构体类型指针变量,其定义如下:+ ]4 w$ ^/ d7 j8 k
  1. typedef struct0 j- K/ Z7 r/ y/ |
  2. {
    8 t4 ~! m1 p5 a1 ~: I: H
  3.   uint32_t Instruction;                   /* 指令 */                                    
    , x" @# f/ ~, j. y  L* m
  4.   uint32_t Address;                        /* 地址 */                                 l5 b: c# H8 E' _% T& C- v8 J& j
  5.   uint32_t AlternateBytes;               /* 交替字节 */                             
    ' H4 c( P, @9 P+ E/ ^
  6.   uint32_t AddressSize;                   /* 地址长度 */                                    . K4 z1 b; q( T( O& _) J: L$ W! f
  7.   uint32_t AlternateBytesSize;           /* 交替字节大小 */                     
    , |& Q- x; t4 X" ?$ h
  8.   uint32_t DummyCycles;                   /* 控指令周期数 */                     
    # h( N! e; `# j5 ?2 ~5 E0 i0 e& b) E, p
  9.   uint32_t InstructionMode;              /* 指令模式 */                             0 s! M- {8 L2 q6 r6 w! z& r
  10.   uint32_t AddressMode;                   /* 地址模式 */                         " N4 m: Q- l" Q" L& f: D2 B
  11.   uint32_t AlternateByteMode;            /* 交替字节模式 */             8 w) R0 |0 p0 f! R: P, V* r
  12.   uint32_t DataMode;                       /* 数据模式 */  
    ) b0 s2 N5 _$ N) X* B" o3 K/ k
  13.   uint32_t NbData;                         /* 数据长度 */                                                                     6 E/ ?8 M* ?# \, a9 a" t
  14.   uint32_t DdrMode;                        /* 指定地址、备用字节和数据阶段的双数据速率模式 */                          
    , {9 O# v. {* g3 y
  15.   uint32_t DdrHoldHalfCycle;             /* 指定DDR模式下数据保持的周期 */                                                        
    0 {3 I; z; q5 w! D% ^# }; Z
  16.   uint32_t SIOOMode;                      /* 指定发送指令仅一次模式 */                         1 E" y! X2 @8 W
  17. }QSPI_CommandTypeDef;
复制代码

: b6 @; u! r( M+ ?! ?& PInstruction:设置通信指令,指定要发送到外部QSPI设备的指令,指令表定义在norflash.h里。
1 D2 W' G/ b/ r0 C( {) f- YAddress:指定要发送到外部QSPI设备的地址,BUSY = 0 或 FMODE = 11(内存映射模式)时,将忽略写入该字段。在双闪存模式下,由于地址始终为偶地址,ADDRESS[0] 自动保持为“0”。2 f( ?; g. o  ^" i" }
AlternateBytes:指定要在地址后立即发送到外部QSPI设备的可选数据。
+ @. d% W: W# M% s' a0 g9 HAddressSize:定义地址长度,可以是8位,16位,24位或者32位。
" a( ~! r4 {+ d: y" {AlternateBytesSize:定义交替字节长度,可以是8位,16位,24位或者32位。9 B, A7 x+ o' M
DummyCycles:定义空指令阶段持续周期,SDR和DDR模式下,指定CLK周期数(0~31)。, V" {6 ?; o0 \6 Z: X
InstructionMode:用于指定指令阶段模式,如下四种:# Y7 d2 T& p; r) K. a: g
QSPI_INSTRUCTION_NONE:无指令;4 Q; O/ F- h. c9 E' ]2 G$ ?
QSPI_INSTRUCTION_1_LINE:单线传输指令;
1 g* `4 n: b$ y6 gQSPI_INSTRUCTION_2_LINES:双线传输指令;' r* e2 k  _/ s7 g  y/ b1 w
QSPI_INSTRUCTION_4_LINES:四线传输指令。% c0 v& u7 B) H  A4 ~
AddressMode:指定地址模式,如下四种:( s3 x+ \; {/ P
QSPI_ADDRESS_NONE:无地址;QSPI_ADDRESS_1_LINE:单线传输地址;QSPI_ADDRESS_2_LINES:双线传输地址;QSPI_ADDRESS_4_LINES:四线传输地址。: B) p% }1 I$ @( r" d: ~0 D' ^
AlternateByteMode:指定交替字节模式,如下四种:
6 x& s- j; g' F: v* s' h/ L" dQSPI_ALTERNATE_BYTES_NONE:无交替字节;
5 N! X$ R! M* i$ @% j3 a0 H4 M, K( {+ mQSPI_ALTERNATE_BYTES_1_LINE:单线传输交替字节;0 G0 B9 H' p2 H4 j8 s6 {) U0 |* _
QSPI_ALTERNATE_BYTES_2_LINES:双线传输交替字节;8 `* c0 m2 c* b: B+ |9 x
QSPI_ALTERNATE_BYTES_4_LINES:四线传输交替字节。( ^( M4 K( A, y7 h; ~2 O! Z
DataMode:指定数据模式,如下四种:0 b( P* K0 O2 _3 L
QSPI_DATA_NONE:无数据;QSPI_DATA_1_LINE:单线传输数据;QSPI_DATA_2_LINES:双线传输数据;QSPI_DATA_4_LINES:四线传输数据。
# D9 s' }/ O5 {+ V/ [3 M6 v0 gNbData:用于设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。; F, r, K9 a9 K( ?1 y* J
DdrMode:为地址、交替字节和数据阶段设置 DDR 模式,可以选择的值是:
) n# K! f/ Y1 q( m. n5 r3 mQSPI_DDR_MODE_DISABLE:禁止 DDR 模式;
; K& n7 N3 Z: r; n! q2 S6 X% VQSPI_DDR_MODE_ENABLE:使能DDR 模式。
, q# _8 O7 M) x5 w; sDdrHoldHalfCycle:用于设置 DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,可选值如下:* d5 A3 l) V! e8 ^! W' e" J
QSPI_DDR_HHC_ANALOG_DELAY:使用模拟延迟来延迟数据输出;1 n% `, g, J2 w# U% K
QSPI_DDR_HHC_HALF_CLK_DELAY:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。$ \7 A- w1 s8 ^" ^! ~, \3 e! ?
SIOOMode:设置是否开启仅发送指令一次模式,可选值如下:$ s) @  G1 W8 k! I, g& M) ]% N/ j
QSPI_SIOO_INST_EVERY_CMD:在每个事务中发送指令;$ M# w9 }# r4 z9 b
QSPI_SIOO_INST_ONLY_FIRST_CMD:仅为第一条命令发送指令。
; O+ {% ^; b$ d; y: v- [形参3用于设置超时时间。
9 w0 u4 ?  S0 B2 U0 F! V+ m函数返回值:  E$ I$ }" P! `! Y7 d5 V
HAL_StatusTypeDef枚举类型的值。
$ W7 e7 @9 ?. `( k$ l7 Y; C1 AHAL_QSPI_Receive函数- `) h4 Y5 F0 X$ p, }; f( l0 }
QSPI接收数据函数,其声明如下:0 v  m; j( |8 G) l/ E
HAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi,
6 I: L, f$ W+ h& |' n' tuint8_t *pData, uint32_t Timeout);
, p& Z3 ]0 g9 h( r, I函数描述:
) u8 {, R. X" f5 N+ d该函数用来接收数据。
) Y% p2 Q+ ?/ w$ b  l函数形参:- n$ G6 w/ T8 B# d8 w/ p+ W* |3 j3 b
形参1是QSPI_HandleTypeDef结构体类型指针变量。. S+ r5 K9 l: G! Y0 l$ u" H
形参2是uint8_t类型指针变量,存放接收数据缓冲区指针。
* X7 D. h9 E4 d! A形参3设置操作超时时间。
  c$ R, H. P3 j, Z函数返回值:5 z: ]& r; c. i' n( P# e
HAL_StatusTypeDef枚举类型的值。
: y: F& d/ m  F3 D) e! DHAL_QSPI_Transmit函数: i/ ^# V  S* p- J  C1 x) ^
QSPI发送数据函数,其声明如下:
, i& l4 H, R# x3 fHAL_StatusTypeDef HAL_QSPI_Transmit (QSPI_HandleTypeDef *hqspi,
" z8 X7 @1 O  T. \, uuint8_t pData, uint32_t Timeout);
0 C) z* T' |1 e函数描述:
: b: V' j1 t; p: J5 r8 q) P该函数用来发送数据。
2 W. `6 [. M- F0 ?0 ?函数形参:
: ^% K! ~  q; m$ ]: O形参1是QSPI_HandleTypeDef结构体类型指针变量。
+ l+ n: m) [; f) K, E6 o形参2是uint8_t类型指针变量,存放发送数据缓冲区指针。7 r/ `( Y% u6 n, h
形参3设置操作超时时间。
0 r" T  O7 e4 a函数返回值:
, w. M" x1 |% NHAL_StatusTypeDef枚举类型的值。- @# Y# w4 x: p, A& c& {( t7 j
QSPI初始化配置步骤- L- A; A1 H. C: e$ ]2 g
1)开启QSPI接口和相关IO的时钟,设置IO口的复用功能。, W# r4 r# Z/ S2 R7 G: L; j) t
要使用QSPI,肯定要先开启其时钟(由AHB3ENR控制),然后根据我们使用的QSPI IO口,开启对应IO口的时钟,并初始化相关IO口的复用功能(选择QSPI复用功能)。5 p4 G0 J6 b" b# U/ z! a- _
QSPI时钟使能方法为:$ w+ Z% e3 ?/ ~" O6 Y+ Z
__HAL_RCC_QSPI_CLK_ENABLE(); / 使能QSPI时钟 */
, K5 `: n8 Z' {' M% v这里大家要注意,和其他外设处理方法一样,HAL库提供了QSPI的初始化回调函数HAL_QSPI_MspInit,一般用来编写与MCU相关的初始化操作。时钟使能和IO口初始化一般在回调函数中编写。1 [6 M6 l6 t! j% L  ~, |
2)设置QSPI相关参数。
9 I1 ?1 ~/ m' K6 j: q$ a7 f9 E, X& O此部分需要设置两个寄存器:QUADSPI_CR和QUADSPI_DCR,控制QSPI的时钟、片选参数、FLASH容量和时钟模式等参数,设定SPI FLASH的工作条件。最后,使能QSPI,完成对QSPI的初始化。HAL库中设置QSPI相关参数函数为HAL_QSPI_Init,该函数声明为:1 ~$ N' h* x5 g- V
HAL_StatusTypeDef HAL_QSPI_Init(QSPI_HandleTypeDef *hqspi);
. y( A! S9 L5 _) _9 @( }3 zQSPI_HandleTypeDef结构体这些成员变量是用来配置QUADSPI_CR寄存器和QUADSPI_DCR寄存器相应位,大家可以结合这两个寄存器的位定义和结构体定义来理解。: S' o- L  G3 ~. m: H  }0 E
对于HAL_QSPI_Init函数使用范例请参考后面33.3软件设置部分程序源码。
9 A8 f1 a' F, F( \' iQSPI发送命令步骤8 p4 K3 W) g+ t( Y- u( R
1)等待QSPI空闲。
' y' ^+ s- N, N3 C$ X# G' o. c在QSPI发送命令前,必须先等待QSPI空闲,通过判断QUADSPI_SR寄存器的BUSY位为0,来确定。
% Y+ Z0 H4 E2 W) O1 |2)设置命令参数。
8 a( h, n% N- k' c此部分主要是通过通信配置寄存器(QUADSPI_CCR)设置,将QSPI配置为:每次都发送指令、间接写模式,根据具体需要设置:指令、地址、空周期和数据等的传输位宽等信息。如果需要发送地址,则配置地址寄存器(QUADSPI_AR)。
* ]- u& z/ T( }$ F在配置完成以后,即可启动发送。如果不需要传输数据,则需要等待命令发送完成(等待QUADSPI_SR寄存器的TCF位为1)。! W9 V2 u& K- Z) e4 W& }3 V
在HAL库中上述两个步骤是通过函数HAL_QSPI_Command来实现,该函数声明为:) ^7 K; Q' {0 U& A/ T
HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi,+ K  G! ~* \- J' w- R
QSPI_CommandTypeDef *cmd, uint32_t Timeout);
" j' ^; l; q# k3 Z9 FQSPI读数据步骤
, g0 D, A  P5 r* P" c1 P1)设置数据传输长度。
# y6 N& k! H( _- _7 _3 e9 o1 i通过设置数据长度寄存器(QUADSPI_DLR),配置需要传输的字节数。
2 |$ R" J/ }& j3 C/ l8 I2 e% b2)设置QSPI工作模式并设置地址。( C' P  r9 \; N$ l
因为要读取数据,所以,设置QUADSPI_CCR寄存器的FMODE[1:0]位为01,工作在间接读取模式。然后,通过地址寄存器(QUADSPI_AR),设置我们将要读取的数据的首地址。  y5 {* z! h& S$ h/ _1 j
3)读取数据。
  S5 d7 p% T9 E5 e在发送完地址以后,就可以读取数据了,不过要等待数据准备好,通过判断QUADSPI_SR寄存器的FTF和TCF位,当这两个位任意一个位为1的时候,我们就可以读取QUADSPI_DR寄存器来获取从FLASH读到的数据。
2 i  w) `. V7 m: {最后,在所有数据接收完成以后,终止传输(ABORT),清除传输完成标志位(TCF)。3 @! B# v) y' B6 M: ?
HAL库中,读取数据是通过函数HAL_QSPI_Receive来实现的,该函数声明为:
+ z. W- w  s: ZHAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi,3 j! C; _7 V5 S% F- @5 _" S/ p- @
uint8_t *pData, uint32_t Timeout);
+ B* ^/ I- d: S在调用该函数读取数据之前,我们会先调用上个步骤讲解的函数HAL_QSPI_Command来指定读取数据的存放空间。
2 z8 G' F& v5 S5 `1 E% \QSPI写数据步骤+ V5 D3 C' M/ n0 m/ ~: z; O. L0 i
1)设置数据传输长度。
, g) \. r( S! @; G  b6 l, c7 E9 }+ h通过设置数据长度寄存器(QUADSPI_DLR),配置需要传输的字节数。
( _$ s; V; \' c7 P4 L3 G2)设置QSPI工作模式并设置地址。. K- q* ^( ~. w% f
因为要读取数据,所以,设置QUADSPI_CCR寄存器的FMODE[1:0]位为00,工作在间接写入模式。然后,通过地址寄存器(QUADSPI_AR),设置我们将要写入的数据的首地址。
8 P0 y2 S6 t* @2 n! w1 d: Q  B3)写入数据。
3 a+ U# t* A3 A在发送完地址以后,就可以写入数据了,不过要等待FIFO不满,通过判断QUADSPI_SR寄存器的FTF位,当这个位为1的时候,表示FIFO可以写入数据,此时往QUADSPI_DR写入需要发送的数据,就可以实现写入数据到FLASH。
  k, E; z$ i8 s0 z$ d: G- o" h最后,在所有数据写入完成以后,终止传输(ABORT),清除传输完成标志位(TCF)。
( t. p% U0 }& o+ }在HAL库中,QSPI发送数据是通过函数HAL_QSPI_Transmit来实现的,该函数声明为:
' y5 t# I* i2 \/ `HAL_StatusTypeDef HAL_QSPI_Transmit (QSPI_HandleTypeDef *hqspi,
  z6 Z9 U: E& s( o# O% ]9 E+ ouint8_t *pData, uint32_t Timeout);
8 `8 ~7 ~/ ~, \1 n* V8 \% S1 [5 X同理,在调用该函数发送数据之前,我们会先调用HAL_QSPI_Command函数来指定要写入数据的存储地址信息。
% C+ n/ c$ V9 f+ \: T* J+ L# P5 LFLASH芯片初始化步骤# [2 l6 p5 z& h+ ^0 c7 T) n
1)使能QPI模式。
+ x6 w1 s% Z& O4 q. b( x$ N& o6 q因为我们是通过QSPI访问W25Q128的,所以先设置W25Q128工作在QPI模式下。通过FLASH_EnterQPIMode指令控制。注意:在该指令发送之前,必须先使能W25Q128的QE位。1 j- N/ @0 }2 r7 F* C; P4 }- D: h7 B
2)设置4字节地址模式。
" F. t8 a5 E8 {. f5 v* n0 A$ f# GW25Q128上电后,一般默认是3字节地址模式,我们需要通FLASH_Enable4ByteAddr指令,设置其为四字节地址模式,否则只能访问16MB的地址空间。
. U4 E; f; M, c8 J. Y3)设置读参数。
' o) B% d" t/ A0 \' q7 @1 ~% Q$ P  T这一步,我们通过FLASH_SetReadParam指令,将P[5:4]设置为11,以支持最高速度访问W25Q128(8个dummy,104M时钟频率)。2 z2 b9 [( ^. y0 B+ N5 X! \
36.3.2 程序流程图
4 X- V, |$ X8 m$ J; C
) w4 Y& q2 A+ @7 G' X, w2 P. u; Z. R: V 8d2468196e6f4b43babaa8a0564eb317.png
; I- [6 t- G9 P/ \1 j! n: ]8 w+ T$ Q1 q8 U7 Y& z
图36.3.2.1 QSPI实验程序流程图; @, I. I6 ]0 |; E: Q% |1 f- d

8 X& ^* o# J: N) W5 W2 g2 l36.3.3 程序解析
; T  e% b6 R/ f1.QSPI驱动代码
* H, R6 T$ [! g+ |/ A这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。QSPI驱动源码包括两个文件:qspi.c和qspi.h。8 h( Z( T( o/ m6 N$ a: d. B
qspi.h头文件对QSPI相关引脚做了宏定义,该宏定义如下:2 r: r: t. f4 G( A8 x( Y) i
/* QSPI 相关 引脚 定义 */( c0 m$ ^- j5 @  F
  1. #define QSPI_BK1_CLK_GPIO_PORT          GPIOB
    # P; q% N+ ^% S* V
  2. #define QSPI_BK1_CLK_GPIO_PIN           GPIO_PIN_2# Z/ a( Q. j. g% Z. D" T
  3. #define QSPI_BK1_CLK_GPIO_AF            GPIO_AF9_QUADSPI
    8 g! g% O- L0 P
  4. #define QSPI_BK1_CLK_GPIO_CLK_ENABLE()  
    * A! A! v0 M; X5 _. ~. q! E
  5. do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0)   /* PB口时钟使能 */& }# z; b7 K& \# M

  6. & v# z$ F9 ]; u/ g  ~; i* O6 N
  7. #define QSPI_BK1_NCS_GPIO_PORT          GPIOB
    1 L6 h* |+ n( R2 O, G
  8. #define QSPI_BK1_NCS_GPIO_PIN           GPIO_PIN_6& e2 a/ g+ n2 m: N* b8 C& \  `
  9. #define QSPI_BK1_NCS_GPIO_AF            GPIO_AF10_QUADSPI  b2 h& d, x% l) I3 V6 e3 T
  10. #define QSPI_BK1_NCS_GPIO_CLK_ENABLE()  7 W# K! h2 l* a2 G/ E
  11. do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0)   /* PB口时钟使能 */
    / U  E8 o7 k- D- W* M

  12. : h3 r1 a) A4 K* U  z  _
  13. #define QSPI_BK1_IO0_GPIO_PORT          GPIOD
    4 d4 W2 `3 w. X1 P
  14. #define QSPI_BK1_IO0_GPIO_PIN           GPIO_PIN_11
    6 |5 ?9 {; S- Q" {# o: K% l4 h6 g6 l
  15. #define QSPI_BK1_IO0_GPIO_AF            GPIO_AF9_QUADSPI
    ; f  Q* a! \7 j  x8 z/ C
  16. #define QSPI_BK1_IO0_GPIO_CLK_ENABLE() # e  w/ p/ X5 f0 Y% u
  17. do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0)  /* PD口时钟使能 */
    6 J6 O9 A% i, r+ K* R$ y( H  ?1 y* r
  18. - `: ]* o* L9 `$ C
  19. #define QSPI_BK1_IO1_GPIO_PORT          GPIOD
    + a6 w% b, W  T3 |+ [* A' |
  20. #define QSPI_BK1_IO1_GPIO_PIN           GPIO_PIN_12
    ! I  I9 Z5 ~) [0 g$ t' ^0 K# C) Q
  21. #define QSPI_BK1_IO1_GPIO_AF            GPIO_AF9_QUADSPI
    & l( o; O' Y5 D, c7 s, q" H9 Z
  22. #define QSPI_BK1_IO1_GPIO_CLK_ENABLE() $ t, E  F2 l# E$ \0 o0 S
  23. do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0)   /* PD口时钟使能 */
    % y/ F6 p, s* o& T; ^9 ?
  24. 5 I7 b/ K& D5 x1 u$ z( @
  25. #define QSPI_BK1_IO2_GPIO_PORT          GPIOD/ R. `; E% H, u7 _- @* l2 y% ~
  26. #define QSPI_BK1_IO2_GPIO_PIN           GPIO_PIN_13& X; W! E. Q) C" F  k9 u: I
  27. #define QSPI_BK1_IO2_GPIO_AF            GPIO_AF9_QUADSPI+ j" W9 `! a+ @2 D& @, f% s9 M
  28. #define QSPI_BK1_IO2_GPIO_CLK_ENABLE()  
    + y! N1 d. z3 M$ B& J1 k
  29. do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0)   /* PD口时钟使能 */
    5 D0 I" d5 d& ~0 K5 U; ~7 X

  30. 1 k7 M; ?2 n2 x3 p5 T
  31. #define QSPI_BK1_IO3_GPIO_PORT          GPIOE: \( A9 _- ^) P; [7 Q# z/ ^
  32. #define QSPI_BK1_IO3_GPIO_PIN           GPIO_PIN_20 m- N8 X/ ^6 F; o# e$ B
  33. #define QSPI_BK1_IO3_GPIO_AF            GPIO_AF9_QUADSPI6 Z3 S$ ~* e: n) G
  34. #define QSPI_BK1_IO3_GPIO_CLK_ENABLE()  # z5 l; \, J* E
复制代码
+ I3 ^2 v9 E, a# @7 }4 N' F
do{ __HAL_RCC_GPIOE_CLK_ENABLE; }while(0) /* PE口时钟使能 */$ W% g8 y6 I4 D/ {2 w+ d5 ~
注意这6个GPIO都是用到复用功能,对应引脚的复用功能情况请看《STM32H750VBT6.pdf》数据手册79页之后的端口复用功能表格。
7 h$ ]  }$ I! O0 d+ Y/ }下面我们开始介绍qspi.c的程序,首先是QSPI接口初始化函数,其定义如下:1 l) L0 _7 S  U7 t/ I: l
5 K% v! A% {4 h0 f
  1. /**
    8 Z9 l+ a$ w' v
  2. * @brief              初始化QSPI接口# ?% M, K  W" O+ J) L! r
  3. * @param               无6 _6 ?! p3 y5 W  B
  4. * @retval              0, 成功; 1, 失败.
      v  K4 I. r$ K! ?+ M
  5. */3 d. X/ ?2 j5 U) g6 y
  6. uint8_t qspi_init(void)
    ! _% X9 J0 j( ~; @4 m0 L' c; [0 c
  7. {
    1 }0 C2 @1 S9 \: q
  8.     g_qspi_handle.Instance = QUADSPI;       /* QSPI */
    - f7 b3 J, s" k
  9. /* QPSI分频比,BY25Q128最大频率为108M,所以此处应该为2,QSPI频率就为
    # V  V! P9 J% }; c' T9 ]  w: R" _
  10. 220/(1+1)=110MHZ稍微有点超频,可以正常就好,不行就只能降低频率 */. }2 ?7 H% C! W( f
  11.     g_qspi_handle.Init.ClockPrescaler = 1;
    0 G3 ]6 M, o: I
  12. g_qspi_handle.Init.FifoThreshold = 4;  /* FIFO阈值为4个字节 */
    % p  Z1 ?# g, u% Y) @
  13. /* 采样移位半个周期(DDR模式下,必须设置为0) */
    + ^0 k, Z9 }. y5 v
  14. g_qspi_handle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
      m& W* \& N& d
  15. /* SPI FLASH大小,BY25Q128大小为32M字节,2^25,所以取权值25-1=24 */, x' h3 n8 a' `& [8 I( Y  m
  16. g_qspi_handle.Init.FlashSize = 25-1;         ' R7 m5 R& ~9 h0 W9 R' q8 k5 H
  17. /* 片选高电平时间为3个时钟(9.1*3=27.3ns),即手册里面的tSHSL参数 */ & f8 Y5 N1 ^6 J5 _. e# l) _
  18.     g_qspi_handle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE; & ^/ @% u* B' ^( [* K1 H
  19.     g_qspi_handle.Init.ClockMode = QSPI_CLOCK_MODE_3;        /* 模式3 */
      c# [4 R7 t: ]# i  e- x6 |; F
  20.     g_qspi_handle.Init.FlashID = QSPI_FLASH_ID_1;             /* 第一片flash */( R& v! U7 g1 n8 z$ N7 g9 F. c0 n
  21.     g_qspi_handle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;  /* 禁止双闪存模式 */
    0 b5 w% ?  s3 X! }
  22.     if(HAL_QSPI_Init(&g_qspi_handle) == HAL_OK) 7 k! c5 ]7 p1 o. d
  23.     {2 L) k2 N  P# p8 h' N) `
  24.         return 0;      /* QSPI初始化成功 */5 p1 k9 I, e" T' c4 o  e1 C
  25.     }
    ) a$ I8 E* G5 p# B. n+ }& N
  26.     else
    0 w8 a) H: p& B" Y5 Q. S# U
  27.     {$ W5 `$ ?; L. d: g7 I- `% o4 w
  28.         return 1;
    ! l2 _7 k! a/ N5 P8 d' j: O2 _
  29.     }
    ' m' _) y! X! Q: R  z4 J% E
  30. }
复制代码
这里我们需要注意的是,QSPI的时钟源在sys_stm32_clock_init函数中已经选择了PLL2R(我们设置为220MHZ),这里就不需要再选择时钟源了,只需要选择预分频系数就可以确定QSPI的时钟频率。时钟模式选择模式3,在未进行任何操作时 CLK 升至高电平。我们用单闪存模式,FlashID要选择QSPI_FLASH_ID_1。* Q/ n# B  _" X" |
我们用HAL_QSPI_MspInit函数来编写QSPI时钟和IO配置等代码,其定义如下:2 |0 [# U! U5 r; f( b8 C8 k! V
$ I9 v: ~$ `+ W7 w( N1 k
  1. /**# _8 ?, A5 r# ?4 l( x  C$ @
  2. * @brief              QSPI底层驱动,引脚配置,时钟使能+ z  E' }3 @4 g, t8 ?- a/ g/ l
  3. * @param               hqspi:QSPI句柄& s. ^1 I6 @* Z$ R! g5 @& y9 F
  4. * @note                此函数会被HAL_QSPI_Init()调用; O5 m* v, e; a, F( W
  5. * @retval              0, 成功; 1, 失败.( u; I; q6 g7 s, r  }! d
  6. */
    , s+ ?/ k, E. ~" F" o1 L
  7. void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi): z6 t1 f* ^8 ?2 T! y+ ~* g
  8. {+ P2 H, i/ G" m& H4 V( ?
  9.     GPIO_InitTypeDef gpio_init_struct;! a: V2 a8 |8 o6 d, k$ D
  10. 6 L2 C/ U/ S/ I8 I) i
  11.     __HAL_RCC_QSPI_CLK_ENABLE();      /* 使能QSPI时钟 */3 ?" ^8 H7 T/ j9 b3 F
  12.     __HAL_RCC_GPIOB_CLK_ENABLE();     /* GPIOB时钟使能 */
    9 S& `2 h3 L( t. |- m
  13.     __HAL_RCC_GPIOD_CLK_ENABLE();     /* GPIOD时钟使能 */8 N; w' x* l" Y3 `  D7 I/ Y8 F- _
  14.     __HAL_RCC_GPIOE_CLK_ENABLE();     /* GPIOE时钟使能 */' S% c- x+ U9 V/ J* t
  15. / b9 g* i8 `* Z( b1 q, j( ~
  16.     gpio_init_struct.Pin = QSPI_BK1_NCS_GPIO_PIN;, j# u! ^6 j0 n! T+ l2 ~% a
  17.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;                  /* 复用 */$ l% K$ `' ]- V& p" X9 H
  18.     gpio_init_struct.Pull = GPIO_PULLUP;                       /* 上拉 */
    # s2 |( d0 U9 b2 L* Q
  19.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;     /* 高速 */
    ) z9 `! r3 M: x
  20.     gpio_init_struct.Alternate = GPIO_AF10_QUADSPI;               /* 复用为QSPI */* K& y0 x& Y/ l0 _& h
  21.     /* 初始化QSPI_BK1_NCS引脚 */
    ' M, `; u- c5 s
  22. HAL_GPIO_Init(QSPI_BK1_NCS_GPIO_PORT, &gpio_init_struct);
    & R; C1 ~: q$ D- j9 F8 B
  23. . U/ |4 i7 ^% a! G5 s( k9 t; p
  24.     gpio_init_struct.Pin = QSPI_BK1_CLK_GPIO_PIN;
    + ^! `, Z. ~+ x
  25.     gpio_init_struct.Mode = GPIO_MODE_AF_PP;                  /* 复用 */
    ( i5 |: i% a! Q* n3 k
  26.     gpio_init_struct.Pull = GPIO_PULLUP;                       /* 上拉 */6 ?  W9 }& o# L+ {& R5 _$ Y
  27.     gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;     /* 高速 */
    / u) ]. ^, M- |2 h9 E2 A4 x& X
  28.     gpio_init_struct.Alternate = GPIO_AF9_QUADSPI;           /* 复用为QSPI */
    - r! R4 C+ C3 j" A
  29. /* 初始化QSPI_BK1_CLK引脚 */' [: v9 @& a3 I5 j8 c
  30. HAL_GPIO_Init(QSPI_BK1_CLK_GPIO_PORT, &gpio_init_struct);    : P" f* L( N) t+ I& O' v9 C2 m! l
  31. 3 Y, f3 C0 `8 [4 R( n+ k6 x
  32. gpio_init_struct.Pin = QSPI_BK1_IO0_GPIO_PIN;  k# x) j& z! X* N
  33. /* 初始化QSPI_BK1_IO0引脚 */* ]; h, b, K( j1 [: x
  34.     HAL_GPIO_Init(QSPI_BK1_IO0_GPIO_PORT, &gpio_init_struct);   
    ) G6 {8 X( V8 Q0 r; Q* k

  35. : M- d& S% U: \5 y( M1 x
  36. gpio_init_struct.Pin = QSPI_BK1_IO1_GPIO_PIN;
    7 h+ z7 d) |4 J, _( F) k6 x
  37. /* 初始化QSPI_BK1_IO1引脚 */1 c, P; R4 F2 k  _7 c+ L
  38.     HAL_GPIO_Init(QSPI_BK1_IO1_GPIO_PORT, &gpio_init_struct);    2 \* @. _' l* W" h$ ^
  39.   g2 [# t9 T3 X) `
  40. gpio_init_struct.Pin = QSPI_BK1_IO2_GPIO_PIN;
    ! i) T8 w" ]# k; p
  41. /* 初始化QSPI_BK1_IO2引脚 */
    % c) R  T  o6 z- R
  42.     HAL_GPIO_Init(QSPI_BK1_IO2_GPIO_PORT, &gpio_init_struct);    & T6 y! @7 j3 e8 l0 ^# u) [# {
  43. 4 I; `) U  G. o6 X  j6 M
  44. gpio_init_struct.Pin = QSPI_BK1_IO3_GPIO_PIN;
    " l0 _* ^' H( Z
  45. /* 初始化QSPI_BK1_IO3引脚 */. \: l# B' g# S
  46.     HAL_GPIO_Init(QSPI_BK1_IO3_GPIO_PORT, &gpio_init_struct);      w! a& c: y, Y# Y' q
  47. }" J7 a8 |% @1 K7 r( Y2 X6 U# z
复制代码
+ _% R+ Y& B! L& P! z7 _
这里初始化的6个引脚全部都要配置为复用功能模式,以及使能QSPI和相应IO时钟。
, v$ w! [1 _; }' Q0 s接下来介绍QSPI发送命令函数,其定义如下:7 R0 y" s3 z) o6 o! A
  T' F& [- h5 j: {! a
  1. /**) V8 n$ t7 F( o2 @) d2 Z5 q
  2. * @brief       QSPI发送命令5 r. {: G+ D9 O8 y4 r
  3. * @param       cmd : 要发送的指令8 @7 j5 {! @. a# g. B0 ^2 v7 ]
  4. * @param       addr: 发送到的目的地址+ ~" W/ c2 a" |9 E1 }
  5. * @param       mode: 模式,详细位定义如下:. w7 ~! b5 S! B' A) s2 _3 V" e
  6. *  @arg mode[1:0]: 指令模式;00,无指令;01,单线传输指令;10,双线传输指令;11,四线传输指令.
    6 o. I/ ^/ c& e/ T
  7. *  @arg mode[3:2]: 地址模式;00,无地址;01,单线传输地址;10,双线传输地址;11,四线传输地址.: [# O. ?1 D$ C
  8. *  @arg mode[5:4]: 地址长度;00,8位地址;01,16位地址; 10,24位地址;   11,32位地址.
    $ z  P  ~; f( s7 O( y8 k0 c
  9. *  @arg mode[7:6]: 数据模式;00,无数据; 01,单线传输数据;10,双线传输数据;11,四线传输数据.
    ; x% P; ?4 f  ^" T6 U0 B
  10. * @param       dmcycle: 空指令周期数0 `' T3 {2 Z2 B8 r
  11. * @retval      无
    1 C% W! K7 e) O4 l( F' l6 n
  12. */* J# W- B$ C! D4 o/ c
  13. void qspi_send_cmd(uint8_t cmd, uint32_t addr, uint8_t mode, uint8_t dmcycle)
    , i. |( x: ~: a
  14. {1 g2 D0 L. ?5 k& l0 d
  15.     QSPI_CommandTypeDef qspi_command_handle;1 d/ V$ S' }* X! u8 A& k/ e) p3 W3 H
  16. 2 ~6 U2 {' Q( `+ V; J" y' H
  17.     qspi_command_handle.Instruction = cmd;                         /* 指令 */
    5 v* H" d$ N" I4 o4 u1 r* M
  18.     qspi_command_handle.Address = addr;                             /* 地址 */; y, u5 [" ?  O
  19.     qspi_command_handle.DummyCycles = dmcycle;                     /* 设置空指令周期数 */
    3 e2 m2 O2 h+ _8 Y, I9 `" [
  20. - Y: v* z+ g, w- q
  21.     if(((mode >> 0) & 0x03) == 0)9 _( o' P9 x2 a
  22.     qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_NONE;   /* 指令模式 */
    9 l; N/ [% W: `+ N  v
  23.     else if(((mode >> 0) & 0x03) == 1)
    . ~" R- u) M7 U1 j
  24.     qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 指令模式 */
    / _% H  P+ Z( J6 N7 u4 ~- e3 z( m- l
  25.     else if(((mode >> 0) & 0x03) == 2). `. I- c1 n. P  j) S
  26.     qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_2_LINES;/* 指令模式 */
    - L( R! Z* x5 f0 l4 t1 `1 b# k
  27.     else if(((mode >> 0) & 0x03) == 3)
    7 @7 H8 D0 a: k( X5 N2 V, F( C5 P* u
  28.     qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_4_LINES;/* 指令模式 */
    ( f' S8 Y9 \% u6 T" E

  29. ! K6 e8 n! u$ D, M$ X; z# ]0 n
  30.     if(((mode >> 2) & 0x03) == 0)
    ! ]; d6 J- p4 _0 u6 j, h) o
  31.     qspi_command_handle.AddressMode = QSPI_ADDRESS_NONE;                   /* 地址模式 */$ M7 ~: o$ ]% W( y- k1 V% ^
  32.     else if(((mode >> 2) & 0x03) == 1)* N6 b: y& o: c( V# V8 v
  33.     qspi_command_handle.AddressMode = QSPI_ADDRESS_1_LINE;                /* 地址模式 */
    $ q+ H4 {8 a9 t, ]& P2 D: u
  34.     else if(((mode >> 2) & 0x03) == 2)
    6 ?5 c4 B0 Y. ~( N/ K% r' r" o
  35.     qspi_command_handle.AddressMode = QSPI_ADDRESS_2_LINES;               /* 地址模式 */0 r/ v2 I: N7 _. R; ~& y/ x
  36.     else if(((mode >> 2) & 0x03) == 3)) M, U. \" ?8 f) T$ \
  37.     qspi_command_handle.AddressMode = QSPI_ADDRESS_4_LINES;               /* 地址模式 */
    / k( T) |% _4 ^0 K. ?& |% b, M2 ^! p

  38. * t! o* O& x; T! z9 I
  39.     if(((mode >> 4)&0x03) == 0)( u3 v8 P, ^, `1 ]9 l# e
  40.     qspi_command_handle.AddressSize = QSPI_ADDRESS_8_BITS;                /* 地址长度 */# q, p. A( I4 \# ~/ ?
  41.     else if(((mode >> 4) & 0x03) == 1)& d% W0 g9 l( C4 v+ k
  42.     qspi_command_handle.AddressSize = QSPI_ADDRESS_16_BITS;               /* 地址长度 */
    7 f6 [2 z' x* F4 Y, m
  43.     else if(((mode >> 4) & 0x03) == 2)5 N  O  g" A5 u
  44.     qspi_command_handle.AddressSize = QSPI_ADDRESS_24_BITS;               /* 地址长度 */: t, Y, u8 ]# r9 X: p
  45.     else if(((mode >> 4) & 0x03) == 3)
    * d: x) L* z7 k
  46.     qspi_command_handle.AddressSize = QSPI_ADDRESS_32_BITS;               /* 地址长度 */2 I8 Q2 o, U4 ?6 ~0 M

  47. ; p% z1 q( Z; x0 B+ g. G6 V
  48.     if(((mode >> 6) & 0x03) == 0). u- \" I" h- L# x- q
  49.     qspi_command_handle.DataMode=QSPI_DATA_NONE;                           /* 数据模式 */
      z9 Y8 H- L8 ]/ {' V
  50.     else if(((mode >> 6) & 0x03) == 1)
    6 U: D' C  ]6 a3 K- V
  51.     qspi_command_handle.DataMode = QSPI_DATA_1_LINE;                      /* 数据模式 */
    3 D) [# M) ?; `
  52.     else if(((mode >> 6) & 0x03) == 2)/ K5 L* W1 V% O  ~; i
  53.     qspi_command_handle.DataMode = QSPI_DATA_2_LINES;                     /* 数据模式 */6 ^& K' m" f7 K  [
  54.     else if(((mode >> 6) & 0x03) == 3)$ {& O0 N5 M( `  I3 k
  55.     qspi_command_handle.DataMode = QSPI_DATA_4_LINES;                     /* 数据模式 */4 ~0 N" B+ U& h& |* D& |7 i6 l

  56. % l* r3 Y! D% ~* n  y2 N- ?" N# V
  57.     qspi_command_handle.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;   /* 每次都发送指令 */# G! R) w. W: ^$ @) W& j
  58.     qspi_command_handle.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE;/*无交替字节*/2 ~6 x3 s& _5 d" M# g
  59.     qspi_command_handle.DdrMode = QSPI_DDR_MODE_DISABLE;        /* 关闭DDR模式 */4 r0 _! s9 _2 n
  60.     qspi_command_handle.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;# b5 W; D, v( L# Y1 {

  61. 3 E6 }' n% }0 K  k/ ~: {5 e* O
  62.     HAL_QSPI_Command(&g_qspi_handle, &qspi_command_handle, 5000);
    4 g, Z+ g5 Q) J
  63. }
    6 k. l: X' x% i4 J; @, ]* V
复制代码
! L' J; Q3 P! [7 G) N% T* i" o
该函数主要就是配置QSPI_CommandTypeDef结构体的参数,并调用HAL_QSPI_Command函数配置发送命令,是一个重要的基础函数。% M2 Z1 z: v: Z& t/ z0 N: \% ~; k
接下来介绍的是QSPI接收函数,其定义如下:2 z' U. X: t, b) e+ f8 u

0 z0 G; \* ]% b" R5 n9 G
  1. /**/ F; f$ T2 N2 J- o* i* [/ P$ p4 T
  2. * @brief               QSPI接收指定长度的数据
    " U  [, Y5 @  ?* ?
  3. * @param               buf     : 接收数据缓冲区首地址
    / n; \' }5 k- |5 k; D1 q+ v3 z
  4. * @param               datalen : 要传输的数据长度) m7 b. c/ s, h) k# Q
  5. * @retval              0, 成功; 其他, 错误代码.) B+ \3 h( D& c
  6. *// H6 {! x0 b: D3 M/ l, L9 E9 w
  7. uint8_t qspi_receive(uint8_t *buf, uint32_t datalen)* e( [" }5 G8 l+ ^  O0 z$ T
  8. {
    . \' |; E/ G/ g2 x% H
  9.     g_qspi_handle.Instance->DLR = datalen - 1;   /* 配置数据长度 */
    ! C8 H7 c) u4 x4 X# n
  10.     if (HAL_QSPI_Receive(&g_qspi_handle, buf, 5000) == HAL_OK)
    ) K$ p% w& z( A
  11.     {8 w/ w3 F5 N  E$ X. T% P! @5 B
  12.         return 0;
    # W2 f1 Y9 w8 }. X* s# X$ ~5 z
  13.     }
    8 j2 _4 W  v9 s+ A" h! w4 L
  14.     else4 h$ n) N2 W0 W2 t2 f6 q/ D
  15.     {
      l: H* ?, ^& u  v+ V6 Q( g2 ], q
  16.         return 1;
    4 l1 ]7 x' T0 Q# e0 ~
  17.     }6 b# H; R: K1 M' v7 I6 O3 E
  18. }
复制代码

4 I9 B" {& i8 u$ E* A该函数首先把要接收数据的长度赋值到QUADSPI数据长度寄存器(QUADSPI_DLR)中,然后通过调用HAL_QSPI_Receive函数接收数据。/ G" J  M  Y: h& z/ p5 ^: }
接下来介绍的是QSPI发送函数,其定义如下:# ], T+ ~2 ~; M4 V! S
: i  C3 n% U! S: @, u
  1. /**
    * o+ F1 t" `  d7 |2 j- s
  2. * @brief              QSPI发送指定长度的数据! T, O' a7 z3 a, Z9 O" o+ G
  3. * @param               buf     : 发送数据缓冲区首地址
    ' I+ n- J, E! s% n; r9 ?$ p0 t% X
  4. * @param               datalen : 要传输的数据长度
    & n! w  E& I- `
  5. * @retval              0, 成功; 其他, 错误代码.
    " @9 m+ W: ~9 |  B
  6. */
    4 l! @: X5 H4 ?1 `4 A6 a- r5 h
  7. uint8_t qspi_transmit(uint8_t *buf, uint32_t datalen)6 k  V7 l+ W/ l+ [# U
  8. {9 i) x; m  b( C: y, z
  9.     g_qspi_handle.Instance->DLR = datalen - 1; /* 配置数据长度 */
    / h5 u# g* G8 I( P( _( ~  o  t
  10.     if (HAL_QSPI_Transmit(&g_qspi_handle, buf, 5000) == HAL_OK)
    0 L5 {; w9 B1 D( I. U
  11.     {
    + u& O$ c$ F- z( B6 d, _) O
  12.         return 0;- ]/ s; z2 ]) Q& o
  13.     }% I2 g6 F, T1 w# P
  14.     else! T- V3 D" f2 m- n7 \  F
  15.     {6 F9 ^& Q6 d+ b& e4 u0 B+ x
  16.         return 1;
    3 q! w9 Y  ?6 M
  17.     }8 I2 T2 H5 H6 W/ y: ~' s5 M
  18. }
复制代码

7 V8 g. A0 c: j1 x3 {0 S该函数首先把要发送数据的长度赋值到QUADSPI数据长度寄存器(QUADSPI_DLR)中,然后通过调用HAL_QSPI_Transmit函数发送数据。
0 ]* f* _- C! h  A$ \6 e2 B最后要介绍的一个函数是等待状态标志函数,其定义如下:& }6 j" |+ v! c6 I' N; K8 A' N: [2 @

& e0 D6 c  J- n2 _; Q8 E  M
  1. /**. ^& _% t6 a  U; R, X! `
  2. * @brief               等待状态标志4 K7 o& b# `9 ?2 e
  3. * @param               flag : 需要等待的标志位
    , I3 P2 X2 w0 s) X) L, ]& J+ z9 d
  4. * @param               sta  : 需要等待的状态! }8 X1 D7 q. p4 @9 i5 X  O
  5. * @param               wtime: 等待时间2 B1 S" |' g- T# h! ~9 @& ]# a1 _
  6. * @retval              0, 等待成功; 1, 等待失败.3 W8 E+ V2 f$ ^* i
  7. */# U' d; g4 N$ R% l/ I
  8. uint8_t qspi_wait_flag(uint32_t flag, uint8_t sta, uint32_t wtime)& |& r) D1 b$ I/ G* H- {; `& \" K
  9. {
    & A2 N* _( k) C9 h2 f' y0 f. B
  10.     uint8_t flagsta = 0;( p$ X3 ^, h" X, q0 E
  11. . |% m4 T5 C" z6 ~0 o
  12.     while (wtime)$ t1 z8 u: M. d" `+ F& L
  13.     {
    # ]9 s+ h7 p& s
  14.         flagsta = (QUADSPI->SR & flag) ? 1 : 0; /* 获取状态标志 */4 J- k# U: w( U  ^! @8 p* Q
  15. 7 S9 c! d* w$ `
  16.         if (flagsta == sta)break;6 g, f- l$ p4 y. U' i
  17. ) \1 L/ W4 g2 u; m. _
  18.         wtime--;/ _) J* ?0 q- R7 w; ^* L# Y
  19.     }# |, b9 F! X% ]) G+ I
  20. 1 f0 N5 t' {2 r( _1 z7 `% d
  21.     if (wtime)return 0;
    : X1 D9 d7 H6 j; W
  22.     else return 1;& V# {5 Y2 i* B" z
  23. }
复制代码

! y9 Q/ s( L9 P# Z$ }$ S: x1 C该函数可以设置一段时钟等待QUADSPI状态寄存器(QUADSPI_SR)的任意位为0,或者为1。然后通过返回值,判断等待是否成功,0表示等待成功;1表示等待失败。( l- F! [7 X0 V4 I) k! o# r
2. NORFLASH驱动代码# p* V0 p2 |* M: m7 @
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。NORFLASH驱动源码包括两个文件:norflash.c、norflash.h、norflash_ex.c和norflash_ex.h。, t! C* X( M7 }" s* R& b1 J
因为STM32H7不支持QSPI接口读时写,因此我们新建了norflash_ex.c和norflash_ex.h文件存放NOR FLASH驱动的拓展代码。该代码用于实现QSPI FLASH的数据写入,原理是:qspi.c、norflash.c和norflash_ex.c等3部分代码全部存储在H7的内部FLASH,我们需要保证操作QSPI FLASH的时候,CPU不会访问存放在QSPI FLASH的代码就可以实现QSPI FLASH数据写入。; H! E. I: ?* F, l
由于这部分代码量会比较多,这里就不一一贴出来介绍。介绍几个重点,其余的请自行查看源码。首先是norflash.h头文件中,我们做了一个FLASH芯片列表(宏定义),这些宏定义是一些支持的FLASH芯片的ID。接下来是FLASH芯片指令表的宏定义,这个请参考FLASH芯片手册比对得到。norflash_ex.h头文件只是一些函数声明,就不介绍了。: J5 X: m3 c( ?5 F6 [
下面介绍norflash.c文件几个重要的函数,首先是NOR FLASH初始化函数,其定义如下:! |0 Z, w- {: p. G7 h3 O

+ U  ]; d+ g% m: |" I- [* U& z
  1. /**8 |3 q, D" `; e% Q( I
  2. * @brief               初始化SPI NOR FLASH3 _" }$ C$ Q. z; M- c5 t6 k2 D3 u
  3. * @param               无% U; R% x, ?4 w2 B) O: f" W* f! ^
  4. * @retval              无
    ' Y8 X( s/ {5 o' W- S
  5. */4 a3 |: W8 I' z. o
  6. void norflash_init(void): a0 n8 M* M) q  D+ t5 G  w
  7. {
    ) g, M2 ?* Y: ~* Z# p' f: R: `& j- Y
  8.     uint8_t temp;
    + A, m0 S* X3 B& w
  9.     qspi_init();                /* 初始化QSPI */1 V, o( ^9 B; R; c
  10.     norflash_qspi_disable(); /* 退出QPI模式(避免芯片之前进入这个模式,导致下载失败) */7 c' w5 p6 h3 t/ B9 u  o6 \7 q
  11.     norflash_qe_enable();     /* 使能QE位 */* X. P! h% ~! l9 X8 O
  12.     g_norflash_type = norflash_read_id();/* 读取FLASH ID. */, C' ?! V. _" I6 p. B

  13. ) _* P$ l8 h& _  Z7 o
  14.     if (g_norflash_type == W25Q256)        /* SPI FLASH为W25Q256, 必须使能4字节地址模式 */
    . e$ ~) d, E/ y2 s6 H9 a% {$ Q& ?
  15.     {
    3 r5 x# }/ x% m) Q+ h
  16.         temp = norflash_read_sr(3);         /* 读取状态寄存器3,判断地址模式 */6 i' [2 F+ V; ^2 f
  17. / h! j* }0 A5 q" ^) o4 f0 K
  18.         if ((temp & 0X01) == 0)             /* 如果不是4字节地址模式,则进入4字节地址模式 */
    + e9 \2 A! Z4 W' v4 F0 m9 z( ~
  19.         {2 _: @7 e2 K: f- D1 M/ q
  20.             norflash_write_enable();        /* 写使能 */1 c' T! W1 D2 ^; A4 E1 K- t  @
  21.             temp |= 1 << 1;                   /* ADP=1, 上电4位地址模式 */5 n* \# m, P+ a+ K. v6 Y( \# }
  22.             norflash_write_sr(3, temp);        /* 写SR3 */
    ) h$ j: o0 v. O0 f& C. r; v6 ^
  23. 7 e' ~+ E1 c6 T$ C7 j
  24.              norflash_write_enable();            /* 写使能 */
    & M  ?: f$ s* |( A9 A
  25.              /* QPI,使能4字节地址指令,地址为0,无数据_8位地址_无地址_单线传输指令,- `6 d3 p6 P' P7 J6 _$ b2 G* h
  26. 无空周期,0个字节数据 */
    3 {- k% A1 Y1 d6 U- s
  27.             qspi_send_cmd(FLASH_Enable4ByteAddr, 0, (0 << 6) | (0 << 4)
    6 s0 P7 C1 Z9 j
  28. | (0 << 2) | (1 << 0), 0);
    6 }+ T0 u0 t  b& F) j# M) d# z# p' |
  29.         }
    " F' W( u% K& o1 i+ |
  30.     }
    / X: k$ g( h. u( G2 Z3 v
  31.     //printf("ID:%x\r\n", g_norflash_type);
    - a. F) d. G* ~* v# l' }. |4 l
  32. }
复制代码
2 s, W+ V: ?9 p9 u+ R4 K
该函数用于初始化NOR FLASH,首先调用qspi_init函数,初始化STM32H750的QSPI接口。然后退出QPI模式(避免芯片之前进入这个模式,导致下载失败),使能FLASH的QE位,使能IO2/IO3。最后读取FLASH ID,如果SPI FLASH为W25Q256,还必须使能4字节地址模式。调用本函数在初始化完成以后,我们便可以通过QSPI接口读写NOR FLASH的数据了。- `3 u$ P9 B* V2 v; k4 V, L$ R. Y; f
接下来介绍读取SPI FLASH函数,其定义如下:3 v& O6 a) ?  |# a# \) K

: a& q) {# b/ a; s& @4 R
  1. /**& A$ i/ e  ?! R% t2 ]$ j# l3 s
  2. * @brief             读取SPI FLASH,仅支持QSPI模式& c% A* ?# h8 |6 E. [7 G& x- W! y
  3. *   @note              在指定地址开始读取指定长度的数据
    * @6 d, f* x$ p( E. r
  4. * @param               pbuf    : 数据存储区+ o" ]6 S1 z6 J; l) A) C
  5. * @param               addr    : 开始读取的地址(最大32bit)% {& {; I% M1 ^" i7 E
  6. * @param               datalen : 要读取的字节数(最大65535)8 o7 y0 C6 K6 h. Y' H; T
  7. * @retval              无* v  f3 r1 S0 y+ y8 f) {, Y* k- V
  8. */
    2 o  @+ T; r) x6 L( c
  9. void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)) T& P; D* V; o% R" _9 K
  10. {9 d" i. q  K- I! c$ r
  11. /* QSPI,快速读数据,地址为addr,4线传输数据_24/32位地址_4线传输地址_1线传输指令,
    % s4 e" b; s, t5 k! }
  12. 6空周期,datalen个数据 */* v8 U: B/ y% e; k0 K
  13. qspi_send_cmd(FLASH_FastReadQuad, addr, (3 << 6) | (g_norflash_addrw << 4)
    & S3 i* a/ g7 s" ^$ R) {& h. U
  14. | (3 << 2) | (1 << 0), 6);
    & O  s5 c$ N$ j0 s& n
  15.     qspi_receive(pbuf, datalen);
    : x: N8 c( `* V( w8 i% z( l4 ]5 d/ i
  16. }
    / ?* }$ U9 e, N8 F1 V2 O% x, e+ @
复制代码

' G+ d9 [  W  Y% s) V  ~该函数用于从NOR FLASH的指定地址读出指定长度的数据,由于NOR FLASH支持以任意地址(但是不能超过NOR FLASH的地址范围)开始读取数据,所以,这个代码相对来说就比较简单了,通过qspi_send_cmd函数,发送FLASH_FastReadQuad指令,并发送读数据首地址(addr),然后通过qspi_receive函数循环读取数据,存放在pbuf里面。; g) r$ Z' `0 P
接下来,我们介绍写入NOR FLASH函数,其定义如下:
- S1 h/ C, @+ }0 Q) @3 M/ L
( e; a' y7 c: u7 y4 J1 ?0 W& i* i
  1. /**
    8 \% t6 r1 H  y! i
  2. * @brief             写SPI FLASH
    0 D3 X0 [. R- ?2 W+ H9 X* f
  3. *   @note             在指定地址开始写入指定长度的数据 , 该函数带擦除操作!
    - A; I- C4 q: H9 u
  4. *                     SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, 6 `( t- `! e& F% ]+ D4 ?- H5 Y+ V0 P
  5. 16个扇区为1个Block
    ; [$ K. b4 G8 e7 X2 |4 W) T
  6. *                     擦除的最小单位为Sector.
      T% i, I) i# o2 I8 X
  7. * @param              pbuf           : 数据存储区
    + u$ I/ r0 Q3 Z$ X/ B$ k! u+ F8 q
  8. * @param               addr            : 开始写入的地址(最大32bit)/ G6 u$ L6 S6 e* \
  9. * @param               datalen        : 要写入的字节数(最大65535)
    / H# E8 O% q& R& U
  10. * @retval              无
    2 h/ _( v/ D8 ~
  11. */
    * q! F5 ?. q. p# j
  12. uint8_t g_norflash_buf[4096];   /* 扇区缓存 */
    " b! X, D: k4 _# r3 N9 S5 s

  13. 3 J5 M; ^. z/ g3 I# U3 o
  14. void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)& i- H* m5 V/ a$ f, d4 k4 A
  15. {
    : F5 ]  h. D* N+ n  g. k! r
  16.     uint32_t secpos;, k2 U8 a. F( `. ?; B/ w: u
  17.     uint16_t secoff;; N( z! L. n* ^  O5 T2 p
  18.     uint16_t secremain;
    9 H9 y9 f0 Q2 M- v( U
  19.     uint16_t i;, f, ?( ~' u' b' Y! }% C# c
  20.     uint8_t *norflash_buf;
    3 }' H* Q) ]0 E2 ^
  21. $ Z& Y. W( U5 U9 l5 g' H& \
  22.     norflash_buf = g_norflash_buf;9 w+ T# }2 w1 S7 o) I
  23.     secpos = addr / 4096;       /* 扇区地址 */
    4 Z6 A2 N8 `& X/ D) q$ p+ D. i
  24.     secoff = addr % 4096;       /* 在扇区内的偏移 */4 N# _. n' \0 K/ g4 P; D' F4 ~+ D7 \
  25.     secremain = 4096 - secoff; /* 扇区剩余空间大小 */; D6 I* T5 }) I9 W$ `

  26. 5 a5 p% T2 L5 ~: b! h1 g$ h
  27.     //printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */2 o# _6 n/ _5 ]* a
  28.     if (datalen <= secremain)5 |  c: p/ P9 X
  29.     {3 h  D* t- o: I& y5 E  C( W. J( j1 e
  30.         secremain = datalen;    /* 不大于4096个字节 */
    0 z( l1 \, m. P% ^6 k0 a  E# G
  31.     }( U$ V2 t, _) G5 Y. `! U6 A. ?

  32.   j; r! @: [* j" B+ g
  33.     while (1)
    4 C, g2 w" ~* n  a% q; T1 f
  34.     {
    : t6 M% Q- M- i) P
  35.         norflash_read(norflash_buf, secpos * 4096, 4096);   /* 读出整个扇区的内容 */
    " }7 o9 x7 u2 Q2 `7 V
  36. 7 a; t' H! z9 A$ D" ~2 _8 f3 b% X7 y
  37.         for (i = 0; i < secremain; i++)   /* 校验数据 */
    2 D% B, a  N* |+ `' U0 W1 o4 J
  38.         {4 e3 p; X6 Z. u- G8 U5 g8 m
  39.             if (norflash_buf[secoff + i] != 0XFF)
    . D9 U+ |5 e+ w! G3 k
  40.             {
    + S( y3 V/ s& P" m9 i; z
  41.                 break;              /* 需要擦除, 直接退出for循环 */) G+ c! v) m: u9 w
  42.             }: h: Q, ?8 V0 Q' P
  43.         }
    8 S) n: K/ d: j8 F; q  k
  44. ( C; ~- ^8 {2 |) _3 @$ D' A
  45.         if (i < secremain)   /* 需要擦除 */. z2 F; o. z2 E6 R& Z
  46.         {6 k" [3 z" g" m% i& t
  47.             norflash_erase_sector(secpos);          /* 擦除这个扇区 */( T4 f. n0 Q8 @4 X
  48.             for (i = 0; i < secremain; i++)         /* 复制 */+ o, W  s; u, h+ Z
  49.             {
    9 t9 v5 a  Q8 I& e9 ]4 N4 h
  50.                 norflash_buf[i + secoff] = pbuf<i>;
    * Q1 A. ~) ]6 f# m7 G+ H  |' G
  51.             }6 `: ]5 a; I" t5 b
  52. /* 写入整个扇区 */
    % X/ l# g* H# p" A7 f" r
  53.             norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);  * Q2 [' ?7 b% s) q
  54.         }& L; N) R4 B! g3 D. ~  W' d
  55.         else                /* 写已经擦除了的,直接写入扇区剩余区间. */
    4 T4 D- s9 C# U. G+ `* ]
  56.         {- j6 t, D; O7 S  P8 ~* D" T9 n
  57.             norflash_write_nocheck(pbuf, addr, secremain);  /* 直接写扇区 */. O- U$ @# x& w: N% }+ a
  58.         }, _; B( h+ O. M
  59. ; ^( E. r0 N+ i; R- m7 w) l+ x5 b, Z( o
  60.         if (datalen == secremain)' z# d0 N7 t4 u- i' E3 u$ P9 u0 N1 k
  61.         {
    & h* Y4 E- z! H! o+ i
  62.             break;          /* 写入结束了 */
    & I1 N1 Q& o/ c' C
  63.         }
      O! C3 ], V3 n# T" \, v- o
  64.         else                /* 写入未结束 */
    3 z4 j0 P( b: n  F
  65.         {" ]+ v) v! T( M$ {! C' e
  66.             secpos++;                        /* 扇区地址增1 */
    , u5 s  d$ s5 `: s, O* ^6 ^
  67.             secoff = 0;                      /* 偏移位置为0 */
    , L+ F9 @3 k; L, s& C$ p
  68. ; U' U$ Y# {% D$ S; q. I/ A; u
  69.             pbuf += secremain;              /* 指针偏移 */5 g5 k) f3 F( m  i7 \
  70.             addr += secremain;              /* 写地址偏移 */1 o; r" z4 V+ |, g7 o
  71.             datalen -= secremain;          /* 字节数递减 */
    ( O* c- A3 L7 [1 ^0 r3 q

  72. " `+ L4 J1 D* H6 }7 ]8 g
  73.             if (datalen > 4096)/ ]. }, D5 c1 a/ {/ t
  74.             {* k# w# N  B; p
  75.                 secremain = 4096;           /* 下一个扇区还是写不完 */
    3 w8 M. u" F2 u# ^  a) D1 r" J
  76.             }0 d  }- n/ q9 t& Q! P' [
  77.             else+ ~) M3 i) X4 {8 n
  78.             {
    9 @# h; c5 ^7 N* s2 E- ~% ~4 m
  79.                 secremain = datalen;        /* 下一个扇区可以写完了 */0 o, ~- @; p: W! d9 Y
  80.             }
    6 V. A7 ~0 x" e
  81.         }, c  M& u. g" K/ Y0 e+ t: [
  82.     }
    5 H4 v- p9 O) i% m( b1 |! ]- H, G
  83. }</i>
复制代码

6 {/ a. `% @) g. ]该函数可以在NOR FLASH的任意地址开始写入任意长度(必须不超过NOR FLASH的容量)的数据。我们这里简单介绍一下思路:先获得首地址(addr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个g_norflash_buf的全局数组,用于擦除时缓存扇区内的数据。
! P2 [4 |/ }1 e1 w0 Knorflash.c文件我们就介绍这三个函数,其他请大家自行查阅。下面再介绍norflash_ex.c文件的几个重要函数。首先是QSPI接口进入内存映射模式函数,其定义如下:$ e& ?6 @8 a$ ]) U# Z* F

1 |1 y. h9 f2 Y
  1. /**
    1 M( _2 y  l( h+ Y7 R
  2. * @brief             QSPI接口进入内存映射模式' s( ]5 f9 B  A7 w! ~
  3. *   @note           调用该函数之前务必已经初始化了QSPI接口
    6 u2 f+ n3 |, ^
  4. *                     sys_qspi_enable_memmapmode or norflash_init
    8 j! n5 D9 B% D9 B0 K4 Q
  5. * @param              无
    . O: _+ R0 T6 e# E+ R) H+ x% t
  6. * @retval             无
    5 h3 _6 i; ]1 m4 M$ r$ Z3 |
  7. */
    1 F5 h/ P7 e+ k
  8. static void norflash_ex_enter_mmap(void)
    + L9 B9 B2 r: v9 V
  9. {8 D, B* N+ o; f
  10.     uint32_t tempreg = 0;$ n+ [5 w% K* C% a# w) i! W! B# W7 }! M

  11. ; v: M+ [8 `8 Z2 l! e# R0 m
  12.     /* BY/W25QXX 写使能(0X06指令) */
    $ H# J, o: r9 W1 u
  13.     while (QUADSPI->SR & (1 << 5)); /* 等待BUSY位清零 */8 N/ f2 M( z, F% z

  14. & J& j/ }' y" l. G) ^
  15.     QUADSPI->CCR = 0X00000106;       /* 发送0X06指令,BY/W25QXX写使能 */
    , K6 D8 l  d, i7 z, y
  16. 3 B: K, K, C1 y% k( B
  17.     while ((QUADSPI->SR & (1 << 1)) == 0);  /* 等待指令发送完成 */' t0 [+ ~- c/ F) M6 `5 W3 c; _+ N

  18. 2 O  o2 B4 `5 u( F5 P
  19.     QUADSPI->FCR |= 1 << 1;
    : w* i+ A4 o( |+ ]5 j9 u
  20. ! N7 l; n+ `  W# k, W
  21.     if (qspi_wait_flag(1 << 5, 0, 0XFFFF) == 0) /* 等待BUSY空闲 */5 \$ d3 U3 u+ y. L3 x  o
  22.     {
    % |6 [# s+ Z% i! R8 p& [+ U9 ?
  23.         tempreg =0XEB;/*INSTRUCTION[7:0]=0XEB,发送0XEB指令(Fast Read QUAD I/O)*/+ Y  H# m+ v9 F7 m  |9 t
  24.         tempreg |= 1 << 8;      /* IMODE[1:0]=1,单线传输指令 */2 `. q$ H1 U. C& N- Y
  25.         tempreg |= 3 << 10;     /* ADDRESS[1:0]=3,四线传输地址 */ 8 x% P/ [! q: \6 o9 A
  26.         tempreg |=(uint32_t)g_norflash_addrw<<12;/*ADSIZE[1:0]=2,24/32位地址长度*/
    : z& L+ R! N9 [; T* D7 L2 r
  27.         tempreg |= 3 << 14;     /* ABMODE[1:0]=3,四线传输交替字节 */
    # f5 Y' r5 Y0 m. r2 R
  28.         tempreg |= 0 << 16;     /* ABSIZE[1:0]=0,8位交替字节(M0~M7) */& X6 b* O& V0 l1 o, k- X
  29.         tempreg |= 4 << 18;     /* DCYC[4:0]=4,4个dummy周期 */
    7 F2 p$ b" Z# x/ P6 j5 P% W
  30.         tempreg |= 3 << 24;     /* DMODE[1:0]=3,四线传输数据 */
    ; z1 U! F0 o( f- |  H
  31.         tempreg |= 3 << 26;     /* FMODE[1:0]=3,内存映射模式 */
    1 Z! ^% Z, f. f" [  }
  32.         QUADSPI->CCR = tempreg;/* 设置CCR寄存器 */
    . g6 Q# n/ O6 Q. {$ Y+ c
  33.     }- N$ \- Y1 w1 b$ r! {
  34.     INTX_ENABLE();                  /* 开启中断 */
    . j# G3 E' H/ A$ f) s% S
  35. }
复制代码
1 g9 V4 y& V& Z/ l1 S; f
该函数使QSPI接口进入内存映射模式。内存映射模式:外部 FLASH 映射到微控制器地址空间,从而系统将其视作内部存储器。
) \. m! p0 m; [1 ?接下来要介绍的是QSPI接口退出内存映射模式函数,其定义如下:
# p6 Y7 z, d) b3 k4 f* m7 h0 A: Q0 x$ `, f
  1. /**  x, \4 ^$ u( y" Y* G/ H! Y
  2. * @brief             QSPI接口退出内存映射模式; v) H; V' q6 d9 I
  3. *   @note              调用该函数之前务必已经初始化了QSPI接口
      f" b! F1 _/ o' Q+ I) b2 ~
  4. *                       sys_qspi_enable_memmapmode or norflash_init6 r! L' I' u& Y& X  k  R
  5. * @param               无4 Z& l5 z- X& _* W% R* {0 Q) A
  6. * @retval              0, OK;  其他, 错误代码
    . E# G9 r) h8 ?. b' l
  7. */9 a  I1 g, x/ r! c
  8. static uint8_t norflash_ex_exit_mmap(void)
    # m: P2 N! F8 p7 I& |0 J
  9. {
    # i1 w6 n$ F9 i3 e  ^( w
  10.     uint8_t res = 0;8 ]# Z( w7 ]% ?0 i5 ~( e9 z
  11. / ?! K: o- L8 p- I% Z' E- D
  12.     INTX_DISABLE();                      /* 关闭中断 */
    ! }4 f! `& R+ N$ V+ ~
  13.     SCB_InvalidateICache();             /* 清空I CACHE */
    7 S1 G& z# ^! ?2 o+ B+ ~
  14.     SCB_InvalidateDCache();             /* 清空D CACHE */* }  \" W7 T: r
  15.     QUADSPI->CR &= ~(1 << 0);           /* 关闭 QSPI 接口 */$ `0 ]( H. ], d9 ^" ^- t) W0 ~! V/ g
  16.     QUADSPI->CR |= 1 << 1;              /* 退出MEMMAPED模式 */
    ; K# I: G0 e) \
  17.     res = qspi_wait_flag(1 << 5, 0, 0XFFFF);    /* 等待BUSY空闲 */
    9 z0 K% q6 P, q% D( U/ o. J/ I- P; k
  18. 0 ^9 d+ u( T' U$ K8 K' i" e
  19.     if (res == 0)
    1 E2 V+ Y5 ]" U" [& [! Y/ z% m: v0 a8 z
  20.     {
    4 c6 ^! B+ C- G7 E0 k$ O; x1 l  q
  21.         QUADSPI->CCR = 0;               /* CCR寄存器清零 */
    ! o1 K; Q8 w0 A
  22.         QUADSPI->CR |= 1 << 0;          /* 使能 QSPI 接口 */
    , A+ y, Q  X6 P
  23.     }" F! l- m( e' q- h
  24. 8 m2 H. c1 H4 e! T7 G" g
  25.     return res;( P, r( m# n* f/ U
  26. }
复制代码

5 ^  D7 U  P( b- s$ |% V) ^* W该函数使QSPI接口退出内存映射模式。norflash_ex_enter_mmap和norflash_ex_exit_mmap是成对存在的函数,也是norflash_ex.c文件中最重要的函数。
8 z: m  C8 ~& d" A' v- \接下来介绍QSPI FLASH写入数据函数,其定义如下:
' G) E. W; }' |/ ^) v/ n! Z) {! d; q% [
  1. /*** q' s0 v, n2 k- q, o" ]9 s4 Q- l
  2. * @brief             往 QSPI FLASH写入数据6 s& h; d  V9 g3 U) H
  3. *   @note              在指定地址开始写入指定长度的数据
    8 M3 |1 f& j& F+ ?8 ~, Q
  4. *                       该函数带擦除操作!% M8 V! P. [  W
  5. * @param               pbuf    : 数据存储区
    # `% O7 d9 K: i. t; C. a6 @
  6. * @param               addr    : 开始写入的地址(最大32bit)
    1 }; l. L0 j* l0 C1 o$ r8 n
  7. * @param               datalen : 要写入的字节数(最大65535)
    " _5 M# p- u9 Z( h
  8. * @retval              0, OK;  其他, 错误代码, m) n- Z) U: P: {/ A* P; ]
  9. */* e% g% {' \1 Z( {1 Z/ i9 g: q0 ]
  10. uint8_t norflash_ex_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
    3 S& M3 X3 n' ^: o
  11. {+ V+ S- C. i5 p! B, e# r# j0 S( j
  12.     uint8_t res = 0;/ |/ Q4 S4 d+ n& ]$ x5 Q- d7 T' b
  13.     res = norflash_ex_exit_mmap();  /* 退出内存映射模式 */% V' Z  @5 F) `, R  T; K
  14. $ ~  \& B  Y# I. y& p5 L; P
  15.     if (res == 0)2 I% H* f$ W% C, ]2 `) m
  16.     {
    8 C! J+ C) `8 ]7 Z2 K3 {
  17.         norflash_write(pbuf, addr, datalen);2 p( o- h! O' m( h
  18.     }
    5 v, e4 @" j9 m3 P- ^* Q; F, y

  19.   `4 p0 a7 D: c
  20.     norflash_ex_enter_mmap();       /* 进入内存映射模式 */
    : L- o/ Y% s& e( a* m5 L% b" p
  21.     return res;5 ^. f7 x( V! r# }8 P/ T
  22. }  i4 e, z1 c3 c
复制代码
0 i: [( w9 H" e# _9 h
因为STM32H7不支持QSPI接口读时写,所以往 QSPI FLASH写入数据前,需要先调用norflash_ex_exit_mmap函数退出内存映射模式。退出内存映射模式,CPU就不会在QSPI FLASH里读取程序指令,即避免了QSPI接口读(指令)时写。写好后,再进入内存映射模式。该思路也就是norflash_ex_write函数的操作过程。
( f8 g  d; L8 v  r+ j! T接下来介绍从QSPI FLASH读取数据函数,其定义如下:
2 i7 z1 B# b( v4 @& i! h
$ ?2 a% @: O9 B0 y
  1. /**; Z6 G4 i% ^3 D; z) S& m* t
  2. * @brief            从 QSPI FLASH 读取数据
    ) h3 Q+ g* O3 w- Y$ W' J! a$ @
  3. *   @note              在指定地址开始读取指定长度的数据(必须处于内存映射模式下,才可以执行)/ V. e3 ]2 M3 t9 S; G! r
  4. *; W$ Z" j9 P  ?) j- y5 {* |. \; N
  5. * @param               pbuf    : 数据存储区; e' C% J$ C; }( T$ s  W- t! L
  6. * @param               addr    : 开始读取的地址(最大32bit)
    ( P" B8 [# H) [& Q
  7. * @param               datalen : 要读取的字节数(最大65535)$ F% e7 P& g+ {* `& F  H
  8. * @retval              0, OK;  其他, 错误代码
    * F9 o: {$ E; N. {, p  i
  9. */' M! I+ P6 s/ o& |& M5 B6 M9 t6 f
  10. void norflash_ex_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)1 z1 S5 w6 @6 ^5 T/ w. j; O
  11. {9 y) S5 t4 @2 y- k
  12. uint16_t i = 0;
    2 F, Z. ~4 j- t2 i5 p
  13. /* 使用内存映射模式读取,QSPI的基址是0X90000000,所以这里要加上基址 */
    3 _- y$ d  ?5 J+ m: M) c
  14.     addr += 0X90000000;
    5 S$ T$ i! v0 P9 w! v& Q9 H2 x
  15.     INTX_DISABLE();         /* 关闭中断 */$ r1 z( ?- g# u

  16. 3 }6 u/ p: d8 H3 J+ b" c
  17.     for (i = 0; i < datalen; i++)
    0 m$ N3 f) s1 B+ h
  18.     {
    $ e; b  ~; |' i8 E: [
  19.         pbuf = *(volatile uint8_t *)(addr + i);
    ( V. E2 ^% _9 n- O- R' b5 ^$ |) M  y
  20.     }4 D' O6 {: d) b( m4 P
  21.     INTX_ENABLE();      /* 开启中断 */* z* D8 g4 ~# D& _: K0 X
  22. }
复制代码

4 G" K8 M: ?" R从QSPI FLASH 读取数据就没有写入这么麻烦了,因为不需要考虑STM32H7不支持QSPI接口读时写的问题,但是仍然有要注意的问题。首先是我们使用内存映射模式读取数据的话,还需要加上QSPI的基址。QSPI的基址在qspi_code.scf文件中定义,是0X90000000,所以这里要在QSPI FLASH开始读取的地址上,再加上基址0X90000000。读取的过程是不允许被打断的,所以还要关闭所有中断,读取完成才打开所有中断。9 `( ]( r0 O0 I* H$ {) K( h
norflash _ex.c文件我们就介绍这四个函数,其他请大家自行查阅。* B+ K" e$ t! A& l5 D
3. main.c代码
% V0 }3 v) \# H" K( O" ^$ ?在main.c里面编写如下代码:/ T9 C" s  y8 y( D
/* 要写入到FLASH的字符串数组 */
1 x! n$ f5 _5 e# L  Y. J. E' ~3 W( G: _
  1. const uint8_t g_text_buf[] = {"MiniPRO STM32H7 QSPI TEST"};/ v& \8 v/ x* T! J* q
  2. #define TEXT_SIZE       sizeof(g_text_buf)  /* TEXT字符串长度 */
    , k3 R( Z7 N- v6 F& Z0 Z

  3.   O/ r( M; u+ @4 q
  4. int main(void)5 w; C7 P: K! _( l& i$ h2 K: F
  5. {
    9 t( ]5 c- J/ t# R( a
  6.     uint8_t key;8 L+ B8 R) n7 t. m) }0 a# s( B2 }# Y% `! e
  7.     uint16_t i = 0;, Q) I( E8 ^* u4 `1 T" P& a) D5 t
  8.     uint8_t datatemp[TEXT_SIZE];. T. l# J  _+ X$ G! v4 x
  9.     uint32_t flashsize;3 ]. z# q- N3 |0 W( c1 `% U5 ^
  10.     uint16_t id = 0;
    " i! a4 n2 H) _+ o: {- G

  11. ' e  X% K) [8 {+ i# E4 Y
  12.     sys_cache_enable();                                   /* 打开L1-Cache */
    7 o/ L/ i. }! q
  13.     HAL_Init();                                             /* 初始化HAL库 */2 p" {* |( J6 O" \5 e4 H  _$ _
  14.     sys_stm32_clock_init(240, 2, 2, 4);                /* 设置时钟, 480Mhz */" l8 ^- L2 e# u* Y( n4 o, w
  15.     delay_init(480);                                       /* 延时初始化 */2 e/ b4 ^" ^0 z$ X2 f
  16.     usart_init(115200);                            /* 串口初始化为115200 */+ p! D$ p. |7 Q3 G1 O
  17.     usmart_dev.init(240);                                 /* 初始化USMART */
    / K9 j  ]0 ?0 B9 ^$ q* a8 ?
  18.     mpu_memory_protection();                      /* 保护相关存储区域 */" Q' `, w9 S$ v
  19.     led_init();                                             /* 初始化LED */: m3 r) g9 [/ s5 p* g
  20.     lcd_init();                                             /* 初始化LCD */
    ; }6 h! t9 M2 |, h' W2 @
  21.     key_init();                                             /* 初始化按键 */2 Z  h! S3 j' d3 \, B
  22.     /* 6 W6 C  i/ }: C+ q% J8 O
  23.      * 不需要调用norflash_init函数了,因为sys.c的sys_qspi_enable_memmapmode函数已+ X6 B9 t+ r& V* D+ T! h( t
  24.      * 经初始化了QSPI接口,如果再调用,则内存映射模式的设置被破坏,导致QSPI代码执行异常!
    ! R' h  ]& t, V# h- [" k/ s
  25.      * 除非不用分散加载,所有代码放内部FLASH,才可以调用该函数!否则将导致异常!
    ) N  Y  u, h* y6 q
  26.      */0 R8 @! a0 ^2 f& i" S: y" ?
  27.     //norflash_init();$ C$ P: Y' `) `6 z. P4 R
  28. % l9 b& l7 K) z& y6 j8 {( D
  29.     lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    8 E$ t* R: ?7 Q6 h: I* Z
  30.     lcd_show_string(30, 70, 200, 16, 16, "QSPI TEST", RED);; a2 {7 H2 R3 N0 ^8 a
  31.     lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);$ |+ g$ W- Z, W  S
  32. /* 显示提示信息 */
    . l* Y; d$ U' {6 e6 ~, G
  33. lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write  KEY0:Read", RED);
    0 c  W3 u& I2 t" B* Z6 x7 y1 C
  34. * m2 W; C* C! e) V+ U' O
  35. id = norflash_ex_read_id(); /* 读取FLASH ID */+ {, e. L. o1 q. F3 q

  36.   O& M: U3 u: U0 J6 s2 p+ Z
  37.     while ((id == 0) || (id == 0XFFFF)) /* 检测不到FLASH芯片 */' H* \. V* y9 m3 ^) h8 p
  38.     {$ i# i! {/ G0 G9 F5 N+ Z
  39.         lcd_show_string(30, 130, 200, 16, 16, "FLASH Check Failed!", RED);+ K& a: b5 d/ V6 w4 Q
  40.         delay_ms(500);9 m$ k9 g( X2 R  S) i
  41.         lcd_show_string(30, 130, 200, 16, 16, "Please Check!      ", RED);! k+ `- |" x8 z  S0 s
  42.         delay_ms(500);; o# E# Y. a( l1 D" G! j, k
  43.         LED0_TOGGLE();      /* LED0闪烁 */
    4 m, p7 ?! E( \; [2 h8 ?, a. L# Z6 {
  44. }
    % G0 ]5 y/ q, f' H

  45. ( U  G- j3 b- ~) U/ Z
  46.     lcd_show_string(30, 130, 200, 16, 16, "QSPI FLASH Ready!", BLUE);5 J# S+ ]$ W& n# d/ ~
  47. flashsize = 16 * 1024 * 1024;   /* FLASH 大小为16M字节 */
    8 d' X" ]9 P& p5 {( `: i

  48. $ Y+ k! C2 i" `; N% M8 ]6 f
  49.     while (1)
    & ^7 ?4 _- j& o# P: q2 T7 C, n
  50.     {
    % |7 c$ a# e& Q0 ^) W: Y! X- ^) _
  51.         key = key_scan(0);
    2 A, O: M/ G  W. X' C

  52. $ F0 W) ?( }/ P- E8 j
  53.         if (key == KEY1_PRES)   /* KEY1按下,写入 */, e: I6 }+ H1 b
  54.         {2 I2 ~" ], j/ T; E5 s' A* A4 ?: J
  55.             lcd_fill(0, 150, 239, 319, WHITE);  /* 清除半屏 */
    ; z* {8 J3 P. r1 a
  56.             lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", BLUE);7 c" ^# q$ M! z7 ~  P
  57.             sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
    * p" ~. N& a9 C- r& b
  58. /* 从倒数第100个地址处开始,写入SIZE长度的数据 */
      q" i7 j) h7 s+ m3 C4 K3 Z
  59.             norflash_ex_write((uint8_t *)datatemp, flashsize - 100, TEXT_SIZE);  
    & a6 b# y5 M; ^" e6 m
  60. /* 提示传送完成 */   
    & r! E  Q+ z& @5 |% i
  61.             lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", BLUE);  $ l7 _& h: w' Z8 U, B7 y4 e( u
  62.         }
    % u. d+ e. N7 \

  63. . J/ g1 C2 z* \1 r0 s- d" C7 `
  64.         if (key == KEY0_PRES)   /* KEY0按下,读取字符串并显示 */
    4 r; M. p/ k$ U# N
  65.         {
    5 u7 q' Z" ~7 M$ _4 i
  66.             lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH.... ", BLUE);. S+ g9 ^: c2 m2 I$ _/ G& E8 z' r$ |
  67. /* 从倒数第100个地址处开始,读出SIZE个字节 */
    # P6 B$ i6 {* Y' ?8 d" g( t# i) T! x
  68.             norflash_ex_read(datatemp, flashsize - 100, TEXT_SIZE);   
    , k# ]* H- Q* J  ~4 D
  69. /* 提示传送完成 */             ' E* z) ]# ^; ~: Z0 H
  70.             lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is:   ", BLUE);  
    ) C3 a* x3 @8 }, g3 t
  71. /* 显示读到的字符串 *// t9 R, H' I( `4 E' l, R7 c) l
  72.             lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE);         
    : K+ t% w' N0 Y* }2 L
  73.         }
    ) x- H( l: S1 q1 o7 o

  74. 9 ?$ D- r, f% s
  75.         i++;; e9 y$ Y' s+ U9 _# v

  76. 5 A& f2 ~* I4 l' A( l$ i9 k) X
  77.         if (i == 20)
    - k1 B& Q* y% U2 l/ k  F2 `
  78.         {
    ! f3 Z8 a5 ]5 U# p( F
  79.             LED0_TOGGLE();      /* LED0闪烁 */
    * F( ^& Q* t: o$ }2 M
  80.             i = 0;  X7 g/ O% X. H9 E
  81.         }9 K0 ]& r. P- G' W( o2 k

  82. ' \6 h1 K" G* p, Y* v
  83.         delay_ms(10);
    / f: P( v3 m2 q, E
  84.     }( x4 m& K+ E' p; K! K2 A
  85. }
复制代码

* C4 x8 S, _2 M7 [5 W在main函数前面,我们定义了g_text_buf数组,用于存放要写入到FLASH的字符串。在main中初始化外部设备NOR FLASH需要注意,这里不需要调用norflash_init函数了,因为sys.c里面的sys_qspi_enable_memmapmode函数已经初始化了QSPI接口。如果再调用,则内存映射模式的设置被破坏,导致QSPI代码执行异常!如果不使用分散加载,即所有代码加载到内部FLASH,才可以调用norflash_init函数。后面的无限循环就是KEY1按下,就写入NOR FLASH。KEY0按下,读取刚才写入的字符串并显示。0 _# g  ]5 z1 x9 E
最后,我们将norflash_ex_read_id、norflash_ex_erase_chip和norflash_ex_erase_sector函数加入USMART控制,大家还可以把其他的函数加进来,这样,我们就可以通过串口调试助手,操作NOR FLASH,方便大家测试。norflash_ex_erase_chip函数大家谨慎调用,因为会把NOR FLASH的程序指令也擦除掉,会导致死机。如果不使用分散加载,就没关系。  `6 @2 C! C% k' s+ W& L
36.4 下载验证
* S4 X0 N  `0 H/ g+ T! k$ d将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图36.4.1所示:
2 B. e5 l* m! l5 K4 Z
5 R) J: J6 k* B' u! R0 { f86852a731b6482185373842d548181a.png ) _/ D4 Z& S1 g7 A6 e
2 J2 s3 w4 J/ d- H* \
图36.4.1 QSPI实验程序运行效果图
0 V+ I& ~0 m4 v+ \7 O% M0 N; B: }# r% L通过先按KEY1按键写入数据,然后按KEY0读取数据,得到如图36.4.2所示:
' e- P  U: `/ J2 s% Y
! n% @: {+ z8 e  V, Q f2bb00e6374742d1acde3a42bfed9770.png
  a% d. y9 D! e8 A7 @- B6 O9 e7 v& C( I1 }1 p; m0 J  v
图36.4.2 操作后的显示效果图5 n$ X0 N. q/ E
程序在开机的时候会检测NOR FLASH是否存在,如果不存在则会在LCD模块上显示错误信息,同时LED0慢闪。
3 ?  z  x5 ]& J0 F* u+ a) f" G————————————————
4 A* l. |6 \, ?9 n版权声明:正点原子* T1 i: w4 }2 N
; X, z" C: f$ b3 u  @% ]$ l

& K  r6 a' O0 q0 \9 m4 ?7 N. }! i7 r: n4 x5 x
收藏 评论0 发布时间:2022-10-7 22:24

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版