QSPI实验+ v2 s! f9 x+ \7 f+ z
本章,我们将介绍STM32H750的QSPI功能,并使用STM32H750自带的QSPI来实现对外部NOR FLASH的读写,并将结果显示在LCD模块上。# p# P" w) S9 c) ~% a" W# f8 T
* J: n; a, ~; M' z- D: {36.1 QSPI及NOR FLASH芯片简介
0 [& B; G4 k- n* P, ?6 M) |36.1.1 QSPI简介' b: w% P* y2 h& V) e: F7 S
QSPI是Quad SPI的缩写,是Motorola公司推出SPI接口后的一种扩展接口,较SPI应用更为广泛。在 SPI 协议的基础上,Motorola 公司对其功能进行了增加,增加了队列传输机制,推出了队列串行外围接口协议(即 QSPI 协议)。QSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI FLASH存储器。STM32H7具有QSPI接口,支持如下三种工作模式:
+ G' o, i" b# B) l, D9 }9 f9 ?9 c1、间接模式:使用QSPI寄存器执行全部操作。# I2 J; |) ~+ q; j, {! a1 d* q( y
2、状态轮询模式:周期性读取外部FLASH状态寄存器,当标志位置1时会产生中断(如7 a6 S( p7 e; s [) c
擦除或烧写完成,产生中断)。
' R+ x& F9 M/ f6 r+ P3 y1 u" L v3、内存映射模式:外部FLASH映射到微控制器地址空间,从而系统将其视作内部存储器。
; F1 r v& o$ D0 c5 w! h1 X1 cSTM32H7的QSPI接口具有如下特点:: x. o( f! A3 W# [' x
支持三种工作模式:间接模式、状态轮询模式和内存映射模式。, }4 [6 Y) n. D) Y" D' H
支持双闪存模式,可以并行访问两个FLASH,可同时发送/接收8位数据。
) h! |7 L4 }' F. i4 q支持SDR(单倍率速率)和DDR(双倍率速率)模式。
& o4 ]) T; O& Y+ J. n针对间接模式和内存映射模式,完全可编程操作码。1 `: I6 j, U2 F! E( { e p& i
针对间接模式和内存映射模式,完全可编程帧格式。
: K5 p0 T2 ^, e集成 FIFO,用于发送和接收。8 L* u. ?6 B. r# X$ @- {8 n$ C
允许 8、16 和 32 位数据访问。
& X. `; H" k6 h7 ^ Y$ Q9 s' [具有适用于间接模式操作的DMA通道。4 _4 [) l# w$ `7 o7 J! N1 z
在达到 FIFO 阈值、超时、操作完成以及发生访问错误时产生中断。
; |' N. Z' z) `$ l+ N0 Q36.1.1.1 QSPI框图
5 l0 b2 R+ ^3 _. y/ D' ^STM32H7的QSPI单闪存模式的功能框图如图36.1.1.1.1所示:
% t+ O, X& U. F; A; R2 p
6 S" O) x/ k' f
% c- g9 _& ]# t0 G# R1 K0 K0 k+ W$ |8 d! Q- ]6 C
图36.1.1.1.1 STM32H7 QSPI框图
$ i% w) k) \4 O1 P( P" L4 d; u上图左边可以看到QSPI连接到64位 AXI 总线以及32位AHB总线上,此外还有5条QSPI的内部信号,如下表所示:
5 \ B1 F% j% |
& r! s9 \ v! L
. X( s1 J8 B' m J0 v$ ?9 _
/ J& M9 v' t9 u' K0 ^. }表36.1.1.1.1 QSPI内部信号
/ W( v j0 I7 }; n& Wquadspi_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。
0 |% M+ c% @# x. x/ \quadspi_hclk,用于操作QUADSPI寄存器的时钟。时钟源来自HCLK3,同样是经过sys_stm32_clock_init函数的配置,配置后HCLK3时钟频率为240MHZ。
3 `: q g$ V& B' Cquadspi_it,中断请求信号线。在达到FIFO阈值、超时、操作完成以及发送访问错误时产生中断。2 e) j( }* f$ Z5 v% O0 ~
quadsqp_ft_trg,达到FIFO阈值时触发MDMA请求。 z: j# S5 b1 W1 X. y# ]
quadspi_tc_trg,操作完成时触发MDMA请求
4 @: D" g6 E m' j# Q5 k# t) S+ q上图中间部分就是QUADSPI 内核。' Q& D; f8 L5 G; ^
我们重点看看上图右边的QSPI接口引脚,通过6根线与SPI FLASH芯片连接,包括:4根数据线(IO0~3)、1根时钟线(CLK)和1根片选线(nCS),具体如下表所示:; [- t, I" u7 ^, O+ l# J# O
1 D) |, v+ J5 h0 e C, o" z' r
u& b$ C) H/ T0 i: a表36.1.1.1.2 QSPI接口引脚
3 b+ R: S' X; |2 [我们知道普通的SPI通信一般只有一根数据线(MOSI/MISO,发送/接收用),而QSPI则具有4根数据线,所以QSPI的速率至少是普通SPI的4倍,可以大大提高通信速率。如果使用双闪存模式,同时访问两个Quad-SPI Flash,速度可以再翻一番。我们开发板板载了一个Quad-SPI Flash,所以我们使用单闪存模式,双闪存模式就不具体介绍了,感兴趣请自行查看手册。
3 b; j9 L# s3 |$ T3 U. q+ X' t1 i接下来,我们给大家简单介绍一下STM32H7 QSPI接口的的几个重要知识点。
u8 D$ a H) K$ C36.1.1.2 QSPI命令序列 B3 ]1 h9 o- X) O' O7 X( o
QSPI 通过命令与FLASH通信,每条命令包括:指令、地址、交替字节、空指令和数据这五个阶段,任一阶段均可通过配置QUADSPI_CCR寄存器相关字段跳过,但至少要包含指令、地址、交替字节或数据阶段之一。7 d1 f D8 \+ w
nCS 在每条指令开始前下降,在每条指令完成后再次上升。QSPI四线模式下的读命令时序,如图36.1.1.2.1所示:/ Z b+ b8 N. O
$ m( y9 U; L9 V$ q+ ?* X7 e8 f M
& f1 C( j5 e' M! D* C' [
6 T3 d+ v0 o! C
图36.1.1.2.1 四线模式下QSPI读命令时序
4 `/ x5 w9 ]$ O; y) ? u: |( Y从上图可以看出一次QSPI传输的5个阶段,接下来我们分别介绍。
! O- X% i+ q6 l% |① 指令阶段) O* J9 h8 Y, l) }8 B$ G7 l! S/ j1 f
此阶段通过QUADSPI_CCR[7:0]寄存器的INSTRUCTION字段指定一个8位指令发送到FLASH。注意,指令阶段,一般是通过IO0单线发送,但是也可以配置为双线/四线发送指令,可以通过QUADSPI_CCR[9:8]寄存器的IMODE[1:0]这两个位进行配置,如IMODE[1:0]=00,则表示无需发送指令。
* {& z; |; C7 j! Z- G, e②地址阶段
' p, P1 Y" u) w此阶段可以发送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,则表示无需发送地址。9 y2 {8 h8 t' H$ P. a: d- f5 c
③交替字节(复用字节)阶段0 _' g1 j1 U1 V5 d. E% M- _ ?
此阶段可以发送1~4字节数据给FLASH芯片,一般用于控制操作模式。待发送的交替字节数由QUADSPI_CCR[17:16]寄存器的ABSIZE[1:0]位配置。待发送的数据由QUADSPI_ABR寄存器中指定。交替字节同样可以以单线/双线/四线模式发送,通过QUADSPI_CCR[15:14]寄存器的ABMODE[1:0]这两个位配置,ABMODE[1:0]=00,则跳过交替字节阶段。
. C* P) i m" u1 F8 P④空指令周期阶段
6 t7 H( T8 \0 W在空指令周期阶段,在给定的1~31个周期内不发送或接收任何数据,目的是当采用更高的时钟频率时,给FLASH芯片留出准备数据阶段的时间。这一阶段中给定的周期数由QUADSPI_CCR[22:18]寄存器的DCYC[4:0]位配置。 若DCYC为零,则跳过空指令周期阶段,命令序列直接进入下一个阶段。9 B$ S8 U+ Q. T/ k1 s4 K% a
⑤数据阶段- \% h% i- O* ^# g
此阶段可以从FLASH读取/写入任意字节数量的数据。在间接模式和自动轮询模式下,待发送/接收的字节数由QUADSPI_DLR寄存器指定。在间接写入模式下,发送到FLASH的数据必须写入QUADSPI_DR寄存器。在间接读取模式下,通过读取QUADSPI_DR寄存器获得从 FLASH接收的数据。数据阶段同样可以以单线/双线/四线模式发送,通过QUADSPI_CCR[25:24]寄存器的DMODE [1:0]这两个位进行配置,如DMODE [1:0]=00,则表示无数据。6 G* |6 W2 r& ~' G+ s
以上就是QSPI数据传输的5个阶段,其中交替字节阶段我们一般用不到,可以省略(通过设置ABMODE[1:0]=00)。
% c I) C: g/ A8 P: I/ M另外说明一下,QUADSPI信号接口协议模式包括:单线SPI模式、双线SPI模式、四线SPI模式、SDR模式、DDR模式和双闪存模式。这些模式请大家自行参考官方手册。
- T! w0 X9 J/ H b. o% `36.1.1.3 QSPI三种功能模式
* S, c8 J+ j$ z2 L! g前面已经提到过QSPI的三种功能模式:间接模式、状态标志轮询模式和内存映射模式。下面对这三个功能模式分别简单介绍。6 {, h5 z! e2 c8 @/ K! ~5 C7 I% z
① 间接模式
& t# P0 S9 a6 o2 ~在间接模式下,通过写入QUADSPI寄存器来触发命令,通过读写数据寄存器来传输数据。
6 q H+ @, y4 @3 j* f当FMODE=00 (QUADSPI_CCR[27:26])时,QUADSPI处于间接写入模式,在数据阶段,将数据写入数据寄存器(QUADSPI_DR),即可写入数据到FLASH。
& ~+ O$ d1 S7 h当FMODE=01时,QUADSPI处于间接读取模式,在数据阶段,读取QUADSPI_DR寄存器,即可读取FLASH里面的数据。
* c6 f: K7 D4 b+ |( i读/写字节数由数据长度寄存器(QUADSPI_DLR)指定。当QUADSPI_DLR=0xFFFFFFFF时,则数据长度视为未定义,QUADSPI 将持续传输数据,直到到达FLASH结尾(FLASH容量由 QUADSPI_DCR[20:16]寄存器的FSIZE[4:0]位定义)。如果不传输任何数据,则DMODE[1:0] (QUADSPI_CCR[25:24])应设置为00。
8 ^% K% K* p% q& \7 _4 O( p" Y当发送或接收的字节数(数据量)达到编程设定值时,如果TCIE=1,则TCF置1并产生中断。在数据量不确定的情况下,将根据FSIZE[4:0]定义的FLASH大小,在达到外部SPI FLASH的限制时,TCF置1。- w% R- W k8 q' z: [$ y
在间接模式下有三种触发命令启动的方式,分别是:
& n/ t( t7 p: N ~% v3 ](1)当不需要发送地址(ADMODE[1:0]==00)和数据(DMODE[1:0]==00)时,对INSTRUCTION[7:0](QUADSPI_CCR[7:0])执行写入操作。 ~! c7 O+ |; t# d/ e7 E/ f3 m
(2)当需要发送地址(ADMODE[1:0]!=00),但不需要发送数据(DMODE[1:0]==00),对ADDRESS[31:0](QUADSPI_AR)执行写入操作。" M6 S& B4 q- R0 I$ { z
(3)当需要发送地址(ADMODE[1:0]!=00)和数据(DMODE[1:0]!=00)时,对DATA[31:0] (QUADSPI_DR)执行写入操作。
+ S) i- A% u% v2 X( T如果命令启动,BUSY位(QUADSPI_SR的第5位)将自动置1。& n4 E. k, D" b- k- d' s* I0 ]3 K
②状态标志轮询模式; U' h8 r7 M9 c# w1 w
将FMODE字段(QUADSPI_CCR[27:26]) 设置为10,使能状态标志轮询模式。在此模式下,将发送编程的帧并周期性检索数据。每帧中读取的最大数据量为4字节。如果QUADSPI_DLR请求更多的数据,则忽略多余部分并仅读取4个字节。在 QUADSPI_PISR 寄存器指定周期性。" o1 S, _. [( l: Z
在检索到状态数据后,可在内部进行处理,以达到以下目的:
. ]0 b" |$ t& |- `$ X- c(1)将状态匹配标志位置 1,如果使能,还将产生中断.
/ j/ Y3 D. @9 S I(2)自动停止周期性检索状态字节。) Q3 x2 Y2 y5 [
接收到的值可通过存储于QUADSPI_PSMKR寄存器中的值来进行屏蔽,并与存储在 QUADSPI_PSMAR寄存器中的值进行或运算或与运算。
) f- C% x7 D+ U; n若是存在匹配,则状态匹配标志置1,并且在使能了中断的情况下还将产生中断;如果AMPS 位置1,则QUADSPI自动停止。
+ s" m+ ~4 V0 }: o+ C% Z' j在任何情况下,最新的检索值都在QUADSPI_DR中可用。% d/ j9 g) {' m7 n( j, E/ Q5 F) z
①内存映射模式# X5 d% k z* F) Q
在配置为内存映射模式时,外部FLASH器件被视作内部存储器,只是存在访问延迟。在该模式下,仅允许对外部 FLASH 执行读取操作。将QUADSPI_CCR寄存器中的FMODE设置为11可进入内存映射模式。当主器件访问存储器映射空间时,将发送已编程的指令和帧。另外数据长度寄存器(QUADSPI_DLR)在内存映射模式中无意义。* Y; I6 _+ R5 {6 W
QUADSPI外设若没有正确配置并使能,禁止访问QUADSPI Flash的存储区域。即使FLASH容量更大,寻址空间也无法超过256MB。如果访问的地址超出FSIZE定义的范围但仍在256MB 范围内,则生成总线错误。此错误的影响具体取决于尝试进行访问的总线主器件:
+ P1 v& u, d% t. l8 M(1)如果为Cortex® CPU,则会在使能总线故障时发生总线故障异常,在禁止总线故障时发生硬性故障(hard fault) 异常。
/ t* h7 l2 d1 c) U3 B+ B(2)如果为 DMA,则生成 DMA传输错误,并自动禁用相应的 DMA 通道。
0 Q, w" U$ M& ~" V: ]' W内存映射模式支持字节、半字和字访问类型,并支持芯片内执(XIP)操作,QUADSPI接受下一个微控制器访问并提前加载后面地址中的字节。如果之后访问的是连续地址,由于值已经预取,访问将更快完成。: V+ Z* f3 D `
默认情况下,即便在很长时间内不访问FLASH,QUADSPI也不会停止预取操作,之前的读取操作将保持激活状态并且 nCS 保持低电平。由于 nCS保持低电平时,FLASH 功耗增加,应用程序可能会激活超时计数器(TCEN = 1, QUADSPI_CR 的位 3)。从而在 FIFO中写满预取的数据后,若在 TIMEOUT[15:0] (QUADSPI_LPTR) 个周期的时长内没有访问,则释放 nCS。BUSY在第一个存储器映射访问发生时变为高电平。由于进行预取操作,BUSY在发生超时、中止或外设禁止前不会下降。
, z3 V# d7 L1 R$ g# D9 `36.1.1.4 QSPI FLASH配置
$ [) ], J% I6 ASPI FLASH芯片的相关参数通过器件配置寄存器 (QUADSPI_DCR) 来进行设置。寄存器QUADSPI_DCR[20:16]的FSIZE[4:0]这5个位,用于指定外部存储器的大小,计算公式为:
- D H+ _ s+ s3 i1 iFcap=21( s* b" D) G, S; M! O" \. d
FSIZE+1是对Flash寻址所需的地址位数。Fcap表示FLASH的容量,单位为字节,在间接模式下,最高支持4GB(使用32位进行寻址)容量的FLASH芯片。但是在内存映射模式下的可寻址空间限制为256MB。
2 O1 \0 G/ _: N0 b# P, t; S' YQSPI连续执行两条命令时,它在两条命令之间将片选信号 (nCS) 置为高电平默认仅一个 CLK周期。某些FLASH需要命令之间的时间更长,可以通过寄存器QUADSPI_DCR[10:8]的CSHT[2:0](选高电平时间)这3个位设置高电平时长:07表示18个时钟周期(最大为8)。! S, i' i+ _0 S+ _8 X3 B
时钟模式,用于指定在nCS为高电平时,CLK的时钟极性。通过寄存器QUADSPI_DCR[0]的CKMODE位指定:当CKMODE=0时,CLK在nCS为高电平期间保持低电平,称之为模式0;当CKMODE=1时,CLK在nCS为高电平期间保持高电平,称之为模式3。/ e3 L3 g6 G3 I, A" x: \
36.1.1.5 QUADSPI寄存器
3 Z+ m$ F1 o8 C$ sQUADSPI控制寄存器(QUADSPI_CR)
# H- w6 i' P8 v( w) }8 WQUADSPI控制寄存器描述如图36.1.1.5.1所示:. P: z5 }7 k: l! I
7 w) C- t; N' V j- J1 J
5 a( i- |8 v1 \7 M: u9 g: |
6 n7 A0 ?- M% i) X图36.1.1.5.1 QUADSPI_CR寄存器
% X8 ^) N$ `3 x) @该寄存器我们只关心需要用到的一些位(下同),首先是PRESCALER[7:0],用于设置AHB时钟预分频器:0255,表示0256分频。我们使用的W25Q128最大支持104Mhz的时钟,这里我们设置PRESCALER=2,即3分频,得到QSPI时钟为72Mhz(216/3)。
( O! i2 \% v# q+ pFTHRES[4:0],用于设置FIFO阈值,范围为031,表示FIFO的阈值为132字节。
% ~# l, _) E7 H+ pFSEL位,用于选择FLASH,我们的W25Q128连接在STM32H7的QSPI BK1上面,所以设置此位为0即可。! O# |8 I1 E1 }4 L3 a% S1 a
DFM位,用于设置双闪存模式,我们用的是单闪存模式,所以设置此位为0即可。
3 m4 q4 j3 M' {1 lSSHIFT位,用于设置采样移位,默认情况下,QSPI接口在FLASH驱动数据后过半个CLK 周期开始采集数据。使用该位,可考虑外部信号延迟,推迟数据采集。我们一般设置此位为1,移位半个周期采集,确保数据稳定。% y p; J" {$ b& K0 k$ h% ]
ABORT位,用于终止QSPI的当前传输,设置为1即可终止当前传输,在读写FLASH数据的时候,可能会用到。) h2 S& G' o; a( C
EN位,用于控制QSPI的使能,我们需要用到QSPI接口,所以必须设置此位为1。. [& U7 B# `1 [( M+ p. i6 a
QUADSPI器件配置寄存器(QUADSPI_ DCR)
2 x# C3 b. G4 g3 WQUADSPI器件配置寄存器描述如图36.1.1.5.2所示:
+ F* E4 c" j3 ]& k) H# |) O" a% `- f
; J" ?: m" W9 c, I {, J M* U
; c. h9 u1 I1 r! W4 x0 o9 t图36.1.1.5.2 QUADSPI_ DCR寄存器
7 V6 X: _$ b3 F; C. X- |2 S9 C, _6 Z该寄存器可以设置FLASH芯片的容量(FSIZE)、片选高电平时间(CSHT)和时钟模式(CKMODE)等,这些位的设置说明见前面的36.1.1.4小节有详细讲解。
% P2 Z( Y2 W, |$ V# p0 m" eQUADSPI状态寄存器(QUADSPI_ SR)( c+ r- g0 l- u) Z* C+ b: M
QUADSPI状态寄存器描述如图36.1.1.5.3所示:
+ O8 X* L0 G1 r: I$ n% q; A
' d |. E; Y4 a W' Z; Y
2 h! X$ ~3 g3 o$ o- N4 p
5 I9 e$ R2 ~+ D图36.1.1.5.3 QUADSPI_ SR寄存器6 ]2 B8 m& U2 x9 {& U
BUSY位,指示操作是否忙。当该位为1时,表示QSPI正在执行操作。在操作完成或者FIFO为空的时候,该位自动清零。
; Y: h3 `! s: u5 m1 i* i1 IFTF位,表示FIFO是否到达阈值。在间接模式下,若达到FIFO阈值,或从FLASH读取完成后,FIFO中留有数据时,该位置1。只要阈值条件不再为“真”,该位就自动清零。
- ~4 q4 f( M6 n( q% t5 ^TCF位,表示传输是否完成。在间接模式下,当传输的数据数量达到编程设定值,或在任何模式下传输中止时,该位置1。向QUADSPI_FCR寄存器的CTCF位写1,可以清零此位。
' q+ ?/ y( `5 y& {+ OQUADSPI标志清零寄存器(QUADSPI_ FCR)
) k3 Z$ ^- f2 p& b2 fQUADSPI标志清零寄存器描述如图36.1.1.5.4所示:
; w/ i( }. z; p x" h+ \0 l% [2 n: d6 ^* [- d1 n4 ?) n" @
# {# \. Y$ N& n; s- x/ C0 a
. U- n; N/ H! `3 H图36.1.1.5.4 QUADSPI_ FCR寄存器
& r: Q5 r! |9 @9 G8 o; u该寄存器,我们一般只用到CTCF位,用于清除QSPI的传输完成标志。
4 E+ r( \; \3 S( |. qQUADSPI通信配置寄存器(QUADSPI_ CCR)# [/ O- D# P, K2 A* J
QUADSPI通信配置寄存器描述如图36.1.1.5.5所示:
" q7 j# u% }9 y6 p8 D. u8 R# m% A* b+ L. k- S$ c, n
/ D' s% p r) V/ ^' V* W, U: ?2 ^' l; {# F% L* L1 x" `! m8 t1 T
图36.1.1.5.5 QUADSPI_ CCR寄存器
6 f% |6 S* \& S5 T3 iDDRM位,用于设置双倍率模式(DDR),我们没用到双倍率模式,所以设置此位为0。! Y. j% E" r9 E% @
SIOO位,用于设置指令是否只发送一次,我们需要每次都发送指令,所以设置此位为0。
( t3 |% r& d' @. w s! a6 p- rFMODE[1:0],这两个位用于设置功能模式:00,间接写入模式;01,间接读取模式;10,自动轮询模式;11,内存映射模式;我们使用间接模式,所以此位根据需要设置为00/01。
* w$ _# Z* v& z. g- I( O. cDMODE[1:0],这两个位用于设置数据模式:00,无数据;01,单线传输数据;10,双线传输数据;11,四线传输数据;我们一般设置为00/11。/ s e% K( P r, j4 h: d/ ?
DCYC[4:0],这5个位用于设置空指令周期数,可以控制空指令阶段的持续时间,设置范围为:0~31。设置为0,则表示没有空指令周期。& K* Y8 W2 b& W; ]1 i) U
ABMODE[1:0],这两个位用于设置交替字节模式,我们一般设置为0,表示无交替字节。
r2 z+ X8 m. l/ q' gADMODE[1:0],这两个位用于设置地址模式:00,无地址;01,单线传输地址;10,双线传输地址;11,四线传输地址;我们一般设置为00/11。
, H6 t( t; }8 N% [9 U/ B% X% kIMODE[1:0],这两个位用于设置指令模式:00,无指令;01,单线传输指令;10,双线传输指令;11,四线传输指令;我们一般设置为00/11。1 f# l, d0 V+ _, |: v. b
INSTRUCTION[7:0],这8个位用于设置将要发送给FLASH的指令。
% n6 L+ m4 w0 g/ j: E* r' C$ g注意,以上这些位的配置,都必须在QUADSPI_SR寄存器的BUSY位为0时才可配置。
+ k+ @7 g1 S" d. R+ N" ~" l接下来,我们看QSPI数据长度寄存器:QUADSPI_DLR,该寄存器为一个32位寄存器,可以设置的数据长度范围为:0~0XFFFFFFFF,当QUADSPI_DLR!=0XFFFFFFFF时,表示传输的字节长度(+1);当QUADSPI_DLR==0XFFFFFFFF时,表示不限传输长度,直到到达由FSIZE定义的FLASH结尾。
4 n! t/ t4 \ u1 E0 c接下来,我们看QSPI地址寄存器:QUADSPI_AR,该寄存器为一个32位寄存器,用于指定发送到FLASH的地址。
- `1 J1 d3 @) ?/ _. O7 D) s接下来,我们看QSPI数据寄存器:QUADSPI_DR,该寄存器为一个32位寄存器,用于指定与外部SPI FLASH设备交换的数据。该寄存器支持字、半字和字节访问。* |" P# G! E* C: U/ ~: X) L. P
在间接写入模式下,写入该寄存器的数据在数据阶段发送到FLASH,在此之前则存储于FIFO,如果 FIFO 满了,则暂停写入,直到 FIFO 具有足够的空间接受要写入的数据才继续。
/ {; Y8 t3 K3 `, S+ [! W在间接模式下,读取该寄存器可获得(通过FIFO)已从FLASH接收的数据。如果FIFO所含字节数比读取操作要求的字节数少,且BUSY=1,则暂停读取,直到足够的数据出现或传输完成才继续。
5 y# {9 K, r. Y" I5 U. H7 z3 {# f36.1.2 NOR FLASH芯片简介# \$ Q9 W, |* u. J
NOR FLASH芯片有很多种芯片型号,在我们的norflash.h头文件中有定义芯片ID的宏定义,对应的就是不同型号的NOR FLASH芯片,比如有:W25Q128、BY25Q128、NM25Q128,它们是来自不同的厂商的同种规格的NOR FLASH芯片,内存空间都是128M字,即16M字节。它们的很多参数、操作都是一样的,所以我们的实验都是兼容它们的。8 Y. S, ^- i1 R% M( f! m
由于这么多的芯片我们就不一一进行介绍了,就拿其中一款型号进行介绍即可,其他的型号都是类似的。
$ W$ a5 B: ?3 x3 X1 k/ [; |" B6 AW25Q128是一款大容量SPI FLASH产品,其容量为16M。它将16M字节的容量分为256个块(Block),每一个块大小为64K字节,每个块又分为16个扇区(Sector),每一个扇区16页,每页256个字节,即每个扇区4K字节。W25Q128的最小擦除单位为一个扇区,也就是每次必须擦除4K个字节。这样我们需要给W25Q128开辟一个至少4K的缓存区,这样对SRAM要求比较高,要求芯片必须有4K以上SRAM才能很好的操作。# |5 W8 O$ ~0 e T. V u
W25Q128的擦写周期多达10W次,具有20年的数据保存期限,支持电压为2.7~3.6V,W25Q128支持标准的SPI,还支持双输出/四输出SPI和QPI(QPI即QSPI),最高时钟频率可达104Mhz(双输出时相当于208Mhz,四输出时相当于416M),本实验我们将使用STM32H7的QSPI接口来实现对W25Q128的驱动。
1 W# ~7 y' }8 c% l5 ^, f2 P接下来,我们介绍一下本实验驱动W25Q128需要用到的一些指令,如表36.1.2.1所示:
# G1 e5 O- ~* C* X5 `- y, d. z! f' e$ w* A9 I( Y
0 S* e% W6 v+ w4 i+ ~7 M: z2 G& b. A j+ s
1,在QPI模式下dummy时钟的个数,由读参数控制位P[5:4]位控制。
, X9 l: x( ?% \2 a* R2,传输的数据量,只要不停的给时钟就可以持续传输,对W25X_PageProgram指令,则单次传输最多不超过256字节,否则将覆盖之前写入的数据。
; H6 t* |$ F* z" W! i6 o表36.1.2.1 W25Q128指令
7 e1 Z0 e. q1 L; t- S1 V% V上表列出了本章我们驱动W25Q128所需要用到的所有指令和对应的参数,注意SPI模式和QPI模式下时钟数的区别,可知QPI模式比SPI模式所需要的时钟数少的多,所以速度也快得多。接下来我们简单介绍一下这些指令。3 b& P: D: Q0 a, T8 a+ ]7 L
首先,前面6个指令,是用来读取/写入状态寄存器13的。在读取的时候,读取S23S0的数据,在写入的时候,写入S23S0。而S23S0则由三部分组成:S23S16,S15S8,S7~S0即状态寄存器3、2、1,如表36.1.2.2所示:% q. T" {! f4 I M& P6 c
9 `# L9 C* W1 B' Q& y& s" g
2 x @: E- o* B0 t& N
4 l7 V0 T4 W4 [4 U/ B& Z表36.1.2.2 W25Q128状态寄存器% ~: X, Z3 }4 Z6 t) ]
这三个状态寄存器,我们只关心我们需要用到的一些位:ADS、QE和BUSY位。其他位的说明,请看W25Q128的数据手册。" t" c# X! i" E
ADS位,表示W25Q128当前的地址模式,是一个只读位,当ADS=0的时候,表示当前是3字节地址模式,当ADS=1的时候,表示当前是4字节地址模式,我们需要使用4字节地址模式,所以在读取到该位为0的时候,必须通过W25X_Enable4ByteAddr指令,设置为4字节地址模式。
. T5 m9 }! x- k# c( P; NQE位,用于使能4线模式(Quad),此位可读可写,并且是可以保存的(掉电后可以继续保持上一次的值)。在本章,我们需要用到4线模式,所以在读到该位为0的时候,必须通过W25X_WriteStatusReg2指令设置此位为1,表示使能4线模式。
9 p$ P4 }# }1 ]8 `7 CBUSY位,用于表示擦除/编程操作是否正在进行,当擦除/编程操作正在进行时,此位为1,此时W25Q128不接受任何指令,当擦除/编程操作完成时,此位为0。此位为只读位,我们在执行某些操作的时候,必须等待此位为0。: a: c$ I8 {& t V( r3 m! }% e
W25X_ManufactDeviceID指令,用于读取W25Q128的ID,可以用于判断W25Q128是否正常。对于W25Q128来说:MF[7:0]=0XEF,ID[7:0]=0X18。8 o n, P( s. v7 S1 y6 H# I5 h2 R: V
W25X_EnterQPIMode指令,用于设置W25Q128进入QPI模式。上电时,W25Q128默认是SPI模式,我们需要通过该指令设置其进入QPI模式。注意:在发送该指令之前,必须先设置状态寄存器2的QE位为1!!' B5 e8 _: x6 U/ M) s
W25X_Enable4ByteAddr指令,用于设置W25Q128进入4字节地址模式。当读取到ADS位为0的时候,我们必须通过此指令将W25Q128设置为4字节地址模式,否则将只能访问16MB的地址空间。) L3 U+ q. C1 ]5 N5 J
W25X_SetReadParam指令,可以用于设置读参数控制位P[5:4],这两个位的描述如表36.1.2.3所示:
2 \. `2 j4 G; W9 _& w, n" m* p" x: V8 z5 `+ l! r
@) u0 O" S: K, Y
5 i* z; K# ~6 _& Q8 m+ k# z2 G表36.1.2.3 W25Q128读参数控制位
1 @7 I' [* a3 F, ^. J为了让W25Q128可以工作在最大频率下,我们这里设置P[5:4]=11,即可工作在104Mhz的时钟频率下。此时,读取数据时的dummy时钟个数为8个(参见W25X_FastReadData指令)。, m5 W9 w- C) b# i8 s2 K
W25X_WriteEnable指令,用于设置W25Q128写使能。在执行擦除、编程、写状态寄存器等操作之前,都必须通过该指令,设置W25Q128写使能,否则无法写入。
4 h1 h& b; m" J- GW25X_FastReadData指令,用于读取FLASH数据,在发送完该指令以后,就可以读取W25Q128的数据了。该指令发送完成后,我们可以持续读取FLASH里面的数据,只要不停的给时钟,就可以不停的读取数据。$ u9 a* q7 m! B4 l7 m
W25X_PageProgram指令,用于编程FLASH(写入数据到FLASH),该指令发送完成后,最多可以一次写入256字节到W25Q128,超过256字节则需要多次发送该指令。
! Y0 F4 g" J% l5 ` ZW25X_SectorErase指令,用于擦除一个扇区(4KB)的数据。因为FLASH具有只可以写0,不可以写1的特性,所以在写入数据的时候,一般需要先擦除(归1),再写。W25Q128的最小擦除单位为一个扇区(4KB)。该指令在写入数据的时候,经常要有用。: ?$ y* p" F" d" p
W25X_ChipErase指令,用于全片擦除W25Q128。; U5 z/ m( Y; |/ H8 X
为了在程序上方便使用,我们把FLASH芯片的常用指令编码定义为宏定义的形式,存放在norflash.h文件中。
# b' J- L$ S+ ]; |+ o3 v8 e! `/ {9 p
36.2 硬件设计
" `! m# |2 |* Q& r$ O1 f0 u1.例程功能
& E* `5 Z& A: m7 n9 ^通过KEY1按键来控制norflash的写入,通过按键KEY0来控制norflash的读取。并在LCD模块上面显示相关信息。我们还可以通过USMART控制读取norflash的ID、擦除某个扇区或整片擦除。LED0闪烁用于提示程序正在运行。$ v: ^5 o3 _" X- I9 O
2.硬件资源& D5 B8 d* E" T2 p3 L1 N
1)RGB灯
; `& C" B8 I8 _3 I! ~8 tRED :LED0 - PB4
* M; Z1 t' O& p% O+ U' E, G2)串口1(PA9/PA10连接在板载USB转串口芯片CH340上面)1 C o' `! K8 k6 |- D: F
3)正点原子2.8/3.5/4.3/7/10寸TFTLCD模块(仅限MCU屏,16位8080并口驱动)& Y6 @* C5 z" y
4)独立按键 :KEY0 - PA1、KEY1 - PA15; d2 s1 w( r9 }2 F
5)QSPI(PB2/PB6/PD11/PD12/PD13/PE2)) y$ R! o: k9 }9 X8 @8 d
6)norflash(QSPI FLASH芯片,连接在QSPI接口上)# G8 {& @, z+ D7 H0 [: ?! D
3.原理图4 ]3 w9 }0 S9 T& A
板载的QSPI FLASH芯片与STM32H750的连接关系,如下图所示:
X! s- z1 v5 ~: v& J2 _, V/ Z! B s/ `
' i% N3 @5 v3 I9 `9 O1 ^' I0 t
图36.2.1 QSPI FLASH芯片与STM32H750连接示意图; L. k' x/ b+ ~0 n; q
本实验支持多种型号的QSPI FLASH芯片,比如:BY25Q128/NM25Q128/W25Q128等等,具体请看norflash.h文件的宏定义,程序上只需要稍微修改一下,后面讲解程序的时候会说到。( [& z% c0 v5 o& H" P: t
9 A8 m' w! F: |. W. o9 I! H e' k
36.3 程序设计8 K6 z3 C! E* g* q& C; r
36.3.1 QSPI的HAL库驱动
8 n/ g# y" B0 f% s; kQSPI在HAL库中的驱动代码在stm32h7xx_hal_qspi.c文件(及其头文件)中。
# j: u8 r$ q7 {0 q0 [1.HAL_QSPI_Init函数
/ M1 j: `$ ]4 {: a; iQSPI的初始化函数,其声明如下:
5 d, O0 }* U+ R' r! Q* v' jHAL_StatusTypeDef HAL_QSPI_Init(QSPI_HandleTypeDef *hqspi);/ f% N* L( B/ T8 w) n
函数描述:
8 b8 \! s5 f7 E3 T$ T; k+ a用于初始化QSPI。
: A$ K% G8 j1 t函数形参:7 W2 {: D0 w6 h% A* R `& ^. Z
形参1是QSPI_HandleTypeDef结构体类型指针变量,其定义如下:
! B# B( Q1 i$ T4 M- typedef struct
+ a8 Y6 z9 p; }$ e: m - {7 B( h3 x* A: ?6 f0 u& M4 _( r
- QUADSPI_TypeDef *Instance; /* QSPI寄存器基址 */ * B: q7 V% c1 Z
- QSPI_InitTypeDef Init; /* QSPI参数配置结构体 */
5 p. e; U: E- f$ u - uint8_t *pTxBuffPtr; /* 要发送数据的地址 */ ) ]7 b3 u4 ^2 L0 J$ a
- __IO uint16_t TxXferSize; /* 要发送数据的大小 */ W- R1 z b. z2 S
- __IO uint16_t TxXferCount; /* 剩余要发送数据的个数 */
, s% x% `4 e7 v9 |& j - uint8_t *pRxBuffPtr; /* 要接收数据的地址 */
( {4 {+ c* @" E; _. @) d; z4 p - __IO uint16_t RxXferSize; /* 要接收数据的大小 */
" j) i, [+ A1 ?* [8 D - __IO uint16_t RxXferCount; /* 剩余要接收数据的个数 */
/ }" b+ i9 j) {" C: U, ]& [ - DMA_HandleTypeDef * hmdma; /* DMA配置结构体 */
/ A; [. K4 y9 I D* ]( p - __IO HAL_LockTypeDef Lock; /* 锁对象 */
0 u/ b- {4 r4 F8 L2 \ - __IO HAL_QSPI_StateTypeDef State; /* QSPI通信状态 */ , U) }3 |* J$ K6 X/ G5 b3 c1 R
- __IO uint32_t ErrorCode; /* 错误代码 */ / M4 A: M. R6 F* {4 g
- uint32_t Timeout; /* 配置QSPI内存访问超时时间 */ ; I$ ^: U5 k" G6 b# a: i* Y* l
- }QSPI_HandleTypeDef;, Q7 ^5 b9 x% a; }* I# b
复制代码 6 h2 C5 A, K5 b8 H C
Instance:用于设置QSPI寄存器基地址,设置为QUADSPI即可,这个官方已经为我们做好了宏定义。
% l! P6 d- U5 F. j$ G* NInit:用于设置QSPI的相关参数,QSPI_InitTypeDef结构体下面再进行详细讲解。 Y% B1 ` e' p0 ]+ ?6 q7 n% ^
pTxBuffPtr、TxXferSize和TxXferCount:分别用于设置 QSPI 发送缓冲指针、发送数据量和发送剩余数据量。: M% q* t9 R) M* H2 @* U
pRxBuffPtr、RxXferSize和RxXferCount:分别用于设置接收缓冲指针、接收数据量和接收剩余数据量。
0 b3 X" g7 }0 N8 n. N d Phmdma:用于配置相关的 DMA 参数。
3 N9 V6 x c: F! ]/ ILock:用于分配锁资源,可选择 HAL_UNLOCKED 或者是 HAL_LOCKED两个参数。
6 G) m# ~. Y; |) n. {+ ~$ Y, gState:用于存放通讯过程中的工作状态。3 ~1 x+ d: d1 v
ErrorCode:通过该参数,用户可以了解到QSPI通讯过程中通信失败的原因。% O" ?) F2 i0 [6 m; k: z; U
Timeout:用于设置超时时间。QSPI访问时间一旦超出 Timeout这个变量值,那么ErrorCode成员变量就会被赋值为HAL_QSPI_ERROR_TIMEOUT,表示操作超时。/ g h; m! h3 W5 r1 S6 K& ^
下面重点来了解QSPI_InitTypeDef结构体的内容,其定义如下:
* z* B; M( N% q% {- A* I- typedef struct
/ T- C; c- `5 J1 @( e0 J, C - {) K7 ]# E% D1 _# `& G$ t
- uint32_t ClockPrescaler; /* 时钟预分频系数 */ _) x0 u& u3 e2 @6 S
- uint32_t FifoThreshold; /* 设置FIFO阈值级别 */
" T) x& Y7 I, } c G - uint32_t SampleShifting; /* 设置采样移位 */9 F5 P) t9 {& o
- uint32_t FlashSize; /* 设置FLASH大小 */
$ c: O4 b8 u5 ]# j* q - uint32_t ChipSelectHighTime; /* 设置片选高电平时间 */$ V) D2 Y* a4 ^; J& t3 o
- uint32_t ClockMode; /* 设置时钟模式 */
& W) J* @0 U" `* r* ^3 q - uint32_t FlashID; /* 闪存ID,第一片还是第二片 */
! L; z# X P$ q9 d x - uint32_t DualFlash; /* 双闪存模式设置 */ 5 G; f5 E9 x( B1 \
- }QSPI_InitTypeDef;
复制代码
7 i6 V' E; I" e8 m; X# AClockPrescaler:用于设置预分频系数,对应QUADSPI_CR寄存器的PRESCALER[7:0]位,取值范围是 0~255。仅可在 BUSY = 0 时修改该字段。 r" Y9 j9 \8 G2 b& S
FifoThreshold:用于设置FIFO阈值级别,可设置范围为 0~31,对应QUADSPI_CR寄存器的FTHRES[4:0]位。
* l/ ~& @# e0 _9 xSampleShifting:用于设置采样移位,对应QUADSPI_CR寄存器的SSHIFT位。使用该位是考虑到外部信号延迟时,推迟数据采样。可以取值 QSPI_SAMPLE_SHIFTING_NONE(即0):不发生移位;QSPI_SAMPLE_SHIFTING_HALFCYCLE(即1):移位半个周期。在DDR模式下 (DDRM = 1),固件必须确保SSHIFT = 0。
7 R& M5 g7 i; O( TFlashSize:用于设置FLASH大小,对应QUADSPI_ DCR寄存器的FSIZE[4:0]位,可设置的范围是:0到31之间的整数。FLASH 中的字节数= 2 [FSIZE+1]。在间接模式下,FLASH容量最高可达4GB(使用 32 位进行寻址),但在内存映射模式下的可寻址空间限制为256MB。
" n. c# O. V) @# g$ vChipSelectHighTime:用于设置片选高电平时间,取值范围:QSPI_CS_HIGH_TIME_1_CYCLE ~ QSPI_CS_HIGH_TIME_8_CYCLE,表示 1~8个周期,对应QUADSPI_DCR寄存器的CSHT[2:0]位。CSHT+1定义片选 (nCS) 在发送至 Flash 的命令之间必须保持高电平的最少CLK周期数。 U) L7 C6 M2 ~" x$ Q# x
ClockMode:用于设置时钟模式,对应QUADSPI_DCR寄存器CKMODE位,指示 CLK在命令之间(nCS = 1 时)的电平,可以选择的参数是:QSPI_CLOCK_MODE_0(表示模式0)或者QSPI_CLOCK_MODE_3(表示模式3)。模式 0是:nCS为高电平(片选释放)时,CLK 必须保持低电平。模式3是:nCS 为高电平(片选释放)时,CLK 必须保持高电平。
" k1 u; \. H2 Y: S% Q* X- ^FlashID:用于选择Flash1或者Flash2,单闪存模式下选择QSPI_FLASH_ID_1(表示Flash1)。' L7 c- t( O4 A; r* i6 ^# j
DualFlash:用于使能双闪存模式,QSPI_DUALFLASH_DISABLE:禁止双闪存模式;QSPI_DUALFLASH_ENABLE:使能双闪存模式。对应QUADSPI_CR寄存器DFM位。! j+ n! H4 V4 h3 A6 p$ {) u
函数返回值:
; |( P6 P+ q" A1 N# zHAL_StatusTypeDef枚举类型的值。6 C. j* |! L8 q$ L
注意事项:5 m* Q" `0 N/ U# [8 U3 Y f
QSPI的MSP初始化函数HAL_QSPI_MspInit,该函数声明如下:
+ N y: E3 o6 Fvoid HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi);
@) n5 b5 i( S- T; [, ]HAL_QSPI_Command函数
5 a& B A; j5 ]/ DQSPI设置命令配置函数,其声明如下:
; B6 y/ [! D! f1 K. wHAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi,
" }6 F% Z/ j! o4 u- |) p9 xQSPI_CommandTypeDef *cmd, uint32_t Timeout);
2 y+ H2 D; m) [2 V函数描述:
) S; _' X# {# W7 p% y% u该函数用来配置QSPI命令。9 x- \ q5 l o$ v* x# G- g
函数形参:6 u( {& v+ ]% f6 h2 a2 d, W0 ^
形参1是QSPI_HandleTypeDef结构体类型指针变量。
! k' ^3 K) t* y% d7 ]& G% r形参2是QSPI_CommandTypeDef结构体类型指针变量,其定义如下:) E) l# a) I( a6 ~$ g2 Y9 u
- typedef struct3 B9 J1 @! w3 ?/ U3 u z; T8 R
- {( E: c# k- n& r8 q
- uint32_t Instruction; /* 指令 */ $ @- | h! \: Q; @+ q
- uint32_t Address; /* 地址 */ + H* S. s/ s9 }4 f# v+ D& Z- C
- uint32_t AlternateBytes; /* 交替字节 */ + s4 `. c7 Q* x8 ^0 y
- uint32_t AddressSize; /* 地址长度 */
: f& o2 M. c9 b3 t+ O- b" v9 Y - uint32_t AlternateBytesSize; /* 交替字节大小 */
5 X8 B* K4 W6 v - uint32_t DummyCycles; /* 控指令周期数 */
- S! o2 k, [7 g9 P - uint32_t InstructionMode; /* 指令模式 */
1 f: t0 P/ q, ^2 [% F - uint32_t AddressMode; /* 地址模式 */
u' T# C" J% I( m& j9 { - uint32_t AlternateByteMode; /* 交替字节模式 */
% A- W5 [) P' J/ B* ]8 x7 Q - uint32_t DataMode; /* 数据模式 */ ( v' A* A# j2 ~4 T
- uint32_t NbData; /* 数据长度 */
. u) g8 l" d( d3 Q9 Y - uint32_t DdrMode; /* 指定地址、备用字节和数据阶段的双数据速率模式 */ / h2 O5 u! I) ]% Z
- uint32_t DdrHoldHalfCycle; /* 指定DDR模式下数据保持的周期 */
# ~% \! ?" @+ I: j s - uint32_t SIOOMode; /* 指定发送指令仅一次模式 */ / p; {; q3 l: n; O
- }QSPI_CommandTypeDef;
复制代码
' W9 r; h! [. }! m) RInstruction:设置通信指令,指定要发送到外部QSPI设备的指令,指令表定义在norflash.h里。
K8 f0 `% N1 I# p8 e6 f+ n3 oAddress:指定要发送到外部QSPI设备的地址,BUSY = 0 或 FMODE = 11(内存映射模式)时,将忽略写入该字段。在双闪存模式下,由于地址始终为偶地址,ADDRESS[0] 自动保持为“0”。; A! j( `4 {8 f) c& T1 A
AlternateBytes:指定要在地址后立即发送到外部QSPI设备的可选数据。8 q5 O8 \9 U }# A I1 M
AddressSize:定义地址长度,可以是8位,16位,24位或者32位。
0 o% I& C% {9 C0 I+ P/ OAlternateBytesSize:定义交替字节长度,可以是8位,16位,24位或者32位。
/ E( B) l* d* wDummyCycles:定义空指令阶段持续周期,SDR和DDR模式下,指定CLK周期数(0~31)。
- O2 Q+ b2 \" n. ?" ?' \InstructionMode:用于指定指令阶段模式,如下四种:$ N9 X6 h1 J* U/ Y$ \3 a
QSPI_INSTRUCTION_NONE:无指令;
- ~3 D. @5 l- \ Z- fQSPI_INSTRUCTION_1_LINE:单线传输指令;
, I k, I) k) G' Z$ m+ }- [$ x+ hQSPI_INSTRUCTION_2_LINES:双线传输指令;3 q) z- K" b% G8 p; i; k
QSPI_INSTRUCTION_4_LINES:四线传输指令。: O2 O& n# _) K5 B" r' M
AddressMode:指定地址模式,如下四种:- S" F: L9 G2 u1 b* N, f# c
QSPI_ADDRESS_NONE:无地址;QSPI_ADDRESS_1_LINE:单线传输地址;QSPI_ADDRESS_2_LINES:双线传输地址;QSPI_ADDRESS_4_LINES:四线传输地址。 m7 g. y' \6 [
AlternateByteMode:指定交替字节模式,如下四种:8 x* \0 Q$ Y6 [. _8 \
QSPI_ALTERNATE_BYTES_NONE:无交替字节;/ p _ W8 L3 k' Q
QSPI_ALTERNATE_BYTES_1_LINE:单线传输交替字节;' Z' S4 ^* {/ y
QSPI_ALTERNATE_BYTES_2_LINES:双线传输交替字节;
3 I4 x; y+ C, v8 J( X% dQSPI_ALTERNATE_BYTES_4_LINES:四线传输交替字节。
* g" e; j/ G2 TDataMode:指定数据模式,如下四种:
( p2 x1 y1 i) lQSPI_DATA_NONE:无数据;QSPI_DATA_1_LINE:单线传输数据;QSPI_DATA_2_LINES:双线传输数据;QSPI_DATA_4_LINES:四线传输数据。
N M- E' M# }0 s' p" d8 nNbData:用于设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。; p! b& T8 W/ D6 l5 F
DdrMode:为地址、交替字节和数据阶段设置 DDR 模式,可以选择的值是:
8 q ^7 m, ?2 b. R ~$ wQSPI_DDR_MODE_DISABLE:禁止 DDR 模式;& ?( O% l1 k0 \* V8 `2 P
QSPI_DDR_MODE_ENABLE:使能DDR 模式。7 T, D; I" m* e4 B! ?
DdrHoldHalfCycle:用于设置 DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,可选值如下:
8 v+ e+ `! U2 s+ [3 O: O5 e `2 BQSPI_DDR_HHC_ANALOG_DELAY:使用模拟延迟来延迟数据输出;' y9 q! B$ |: ]8 O8 U+ \1 h+ Z
QSPI_DDR_HHC_HALF_CLK_DELAY:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
) Z! G; j, K7 y, v7 zSIOOMode:设置是否开启仅发送指令一次模式,可选值如下:
+ z! u1 Y1 _4 Z; c( o2 }* H# IQSPI_SIOO_INST_EVERY_CMD:在每个事务中发送指令;; S$ f3 {# U" x3 B' B b
QSPI_SIOO_INST_ONLY_FIRST_CMD:仅为第一条命令发送指令。
1 }! R+ W( n/ }2 K& T- t8 c形参3用于设置超时时间。
. C8 p- y: H7 ~函数返回值:- r! ^9 V3 N {
HAL_StatusTypeDef枚举类型的值。- _+ _: W4 i% }8 B$ ~
HAL_QSPI_Receive函数- K7 s9 Z. b- d: K2 k7 F7 c* t3 R
QSPI接收数据函数,其声明如下:
& M" l c* z% ]4 D; I0 B) d$ XHAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi,3 x6 d+ Z8 X' ^2 P- S
uint8_t *pData, uint32_t Timeout);
* ^* f5 S8 s* y" {% `$ U* m; i1 S5 F函数描述:( L. u2 O0 n$ ?- W5 U; @1 H/ B
该函数用来接收数据。
) E7 Z$ j3 }2 c' F函数形参:' j6 Y; r$ o7 A; q
形参1是QSPI_HandleTypeDef结构体类型指针变量。9 s( |+ s# Z! Y N/ e" X |# ^- A
形参2是uint8_t类型指针变量,存放接收数据缓冲区指针。2 k: b& K' q9 O% w# }/ h# d
形参3设置操作超时时间。$ Q; W6 u3 }/ `; l4 p7 I
函数返回值:1 e: i# \2 D: t: _0 T$ V: [1 t
HAL_StatusTypeDef枚举类型的值。% o% X: l# X5 b5 H* x' Y) z
HAL_QSPI_Transmit函数4 ^- \* k p; i( v( I1 k
QSPI发送数据函数,其声明如下:1 r6 K4 k% Y' c6 w# ] v
HAL_StatusTypeDef HAL_QSPI_Transmit (QSPI_HandleTypeDef *hqspi,
0 I5 K' J( g2 Tuint8_t pData, uint32_t Timeout);
9 E" z/ P5 s+ f' _4 m4 ^& n# y3 I函数描述:
9 c1 i/ v- z; [5 }" f; A该函数用来发送数据。
$ o6 v& K# ]3 a6 @ A1 e( I4 ^函数形参:
! ?4 {: L6 I" [5 `形参1是QSPI_HandleTypeDef结构体类型指针变量。3 Z# m! K- ^1 _5 m" ?& `
形参2是uint8_t类型指针变量,存放发送数据缓冲区指针。
2 |: @# K3 W. c0 d5 O/ P形参3设置操作超时时间。
2 M a, J" @5 k1 P$ E9 O函数返回值:+ s, ?; F$ \( J& z( |# o
HAL_StatusTypeDef枚举类型的值。
% z: f5 R+ m9 ^# L. l1 [QSPI初始化配置步骤
' Q: S+ x2 h/ i+ M1)开启QSPI接口和相关IO的时钟,设置IO口的复用功能。
- R/ I7 `: ^ g2 ]; `/ q+ k/ V: ~5 k要使用QSPI,肯定要先开启其时钟(由AHB3ENR控制),然后根据我们使用的QSPI IO口,开启对应IO口的时钟,并初始化相关IO口的复用功能(选择QSPI复用功能)。
, _/ H7 l( _/ RQSPI时钟使能方法为:2 C' z$ a4 T: w. e' b. h6 S
__HAL_RCC_QSPI_CLK_ENABLE(); / 使能QSPI时钟 */7 r. ^& C5 U& e, R5 w- Y3 G. G7 d+ g
这里大家要注意,和其他外设处理方法一样,HAL库提供了QSPI的初始化回调函数HAL_QSPI_MspInit,一般用来编写与MCU相关的初始化操作。时钟使能和IO口初始化一般在回调函数中编写。" M3 j0 j4 ]9 Q0 s0 W
2)设置QSPI相关参数。6 }+ B+ e. V( D% R& k r
此部分需要设置两个寄存器:QUADSPI_CR和QUADSPI_DCR,控制QSPI的时钟、片选参数、FLASH容量和时钟模式等参数,设定SPI FLASH的工作条件。最后,使能QSPI,完成对QSPI的初始化。HAL库中设置QSPI相关参数函数为HAL_QSPI_Init,该函数声明为:
0 N8 ^ f1 U2 s* ?* GHAL_StatusTypeDef HAL_QSPI_Init(QSPI_HandleTypeDef *hqspi);7 U$ z7 Q6 e7 Z0 h( C0 u
QSPI_HandleTypeDef结构体这些成员变量是用来配置QUADSPI_CR寄存器和QUADSPI_DCR寄存器相应位,大家可以结合这两个寄存器的位定义和结构体定义来理解。
8 v/ w& }9 X( j对于HAL_QSPI_Init函数使用范例请参考后面33.3软件设置部分程序源码。2 }. q/ A% a& M% d ?0 [
QSPI发送命令步骤
4 d+ w, e/ X( Y3 _ _' Y! S( |1)等待QSPI空闲。7 B C9 p; D& U1 T
在QSPI发送命令前,必须先等待QSPI空闲,通过判断QUADSPI_SR寄存器的BUSY位为0,来确定。
% A6 V) }% E% [$ ]9 `# P. C- j2)设置命令参数。
2 s6 m& n- _7 M) A# ^% N% Z& {此部分主要是通过通信配置寄存器(QUADSPI_CCR)设置,将QSPI配置为:每次都发送指令、间接写模式,根据具体需要设置:指令、地址、空周期和数据等的传输位宽等信息。如果需要发送地址,则配置地址寄存器(QUADSPI_AR)。
" E# w1 |* R. P, ^) q, r% A7 }' j+ H: @在配置完成以后,即可启动发送。如果不需要传输数据,则需要等待命令发送完成(等待QUADSPI_SR寄存器的TCF位为1)。" M# B- w$ w/ E& y9 P+ r
在HAL库中上述两个步骤是通过函数HAL_QSPI_Command来实现,该函数声明为:, S4 y8 c) l1 f8 Y* j
HAL_StatusTypeDef HAL_QSPI_Command(QSPI_HandleTypeDef *hqspi,1 x& f: R R2 V# E f' Z
QSPI_CommandTypeDef *cmd, uint32_t Timeout);- {+ F a0 {# `, v; B7 a
QSPI读数据步骤
2 n+ L! |! n" x9 F: Q1)设置数据传输长度。
# j# y. g6 ]* `) W `# q通过设置数据长度寄存器(QUADSPI_DLR),配置需要传输的字节数。
0 r2 g6 s$ U+ \7 E2 [) x2)设置QSPI工作模式并设置地址。- |$ N5 l. x6 G6 h$ ~9 T
因为要读取数据,所以,设置QUADSPI_CCR寄存器的FMODE[1:0]位为01,工作在间接读取模式。然后,通过地址寄存器(QUADSPI_AR),设置我们将要读取的数据的首地址。
+ U ^1 @5 B% t$ R3)读取数据。; _4 ^% l, g! z6 H& m3 u9 T
在发送完地址以后,就可以读取数据了,不过要等待数据准备好,通过判断QUADSPI_SR寄存器的FTF和TCF位,当这两个位任意一个位为1的时候,我们就可以读取QUADSPI_DR寄存器来获取从FLASH读到的数据。* s) D, b4 f8 A3 u6 f
最后,在所有数据接收完成以后,终止传输(ABORT),清除传输完成标志位(TCF)。: G' F4 d J& Q: d9 F
HAL库中,读取数据是通过函数HAL_QSPI_Receive来实现的,该函数声明为:
0 k/ Z! a, O9 p3 r/ h/ y. d% W( LHAL_StatusTypeDef HAL_QSPI_Receive(QSPI_HandleTypeDef *hqspi,
3 a7 `; Q9 d1 P4 quint8_t *pData, uint32_t Timeout);
P, ?! i6 Z" S6 ]; F* }在调用该函数读取数据之前,我们会先调用上个步骤讲解的函数HAL_QSPI_Command来指定读取数据的存放空间。8 H- g. t0 p& `* ?* V! K
QSPI写数据步骤
, |( c Y! a; Q) Y1)设置数据传输长度。* y+ U; Y1 C4 N
通过设置数据长度寄存器(QUADSPI_DLR),配置需要传输的字节数。
) `( ?2 T) z" q5 E! f2)设置QSPI工作模式并设置地址。( g# O P# g, F* ^' z6 i
因为要读取数据,所以,设置QUADSPI_CCR寄存器的FMODE[1:0]位为00,工作在间接写入模式。然后,通过地址寄存器(QUADSPI_AR),设置我们将要写入的数据的首地址。
, _- p; V4 f) e/ y/ c2 m3)写入数据。
- C& y* S4 o& C3 T- ~* s8 V在发送完地址以后,就可以写入数据了,不过要等待FIFO不满,通过判断QUADSPI_SR寄存器的FTF位,当这个位为1的时候,表示FIFO可以写入数据,此时往QUADSPI_DR写入需要发送的数据,就可以实现写入数据到FLASH。# z7 K+ {3 N* l/ l e8 U, [% _; m
最后,在所有数据写入完成以后,终止传输(ABORT),清除传输完成标志位(TCF)。
) ]; m g2 S% C# V9 [. U在HAL库中,QSPI发送数据是通过函数HAL_QSPI_Transmit来实现的,该函数声明为:( T; ^$ @, o3 Z' C% H/ {& L
HAL_StatusTypeDef HAL_QSPI_Transmit (QSPI_HandleTypeDef *hqspi,4 N) l" `5 a; [. r1 n
uint8_t *pData, uint32_t Timeout);
& L7 \( l' Y- m* ^# [& ]/ M同理,在调用该函数发送数据之前,我们会先调用HAL_QSPI_Command函数来指定要写入数据的存储地址信息。+ ?( S1 C+ D) A9 {; s9 N
FLASH芯片初始化步骤/ A# A# P% A4 @2 z( }' y G" U& @# R
1)使能QPI模式。
; X! o+ Z$ J% m2 ?7 k3 ?5 a因为我们是通过QSPI访问W25Q128的,所以先设置W25Q128工作在QPI模式下。通过FLASH_EnterQPIMode指令控制。注意:在该指令发送之前,必须先使能W25Q128的QE位。- d3 @# u, @. q
2)设置4字节地址模式。! e7 J; O8 }! _5 ?# \
W25Q128上电后,一般默认是3字节地址模式,我们需要通FLASH_Enable4ByteAddr指令,设置其为四字节地址模式,否则只能访问16MB的地址空间。5 D% m7 |8 f+ C' t8 i) }5 N% O
3)设置读参数。
7 i* n1 W7 K- S& P8 Y这一步,我们通过FLASH_SetReadParam指令,将P[5:4]设置为11,以支持最高速度访问W25Q128(8个dummy,104M时钟频率)。
3 [% g5 X: d: ]36.3.2 程序流程图
5 t; o. E. y0 O# o# s7 ?( F5 x
; `( D! g0 s! M& g8 n: k3 ^
m: t8 g) @/ _7 U7 u0 u( O+ Q/ ]+ @. z( E2 q/ g8 H( L0 ~" d2 c( O( ?( U
图36.3.2.1 QSPI实验程序流程图7 c/ _8 u4 C& o* Y; @
! Y! [( [& j2 t; ?8 N36.3.3 程序解析
7 y' E& X9 T: y3 g3 Y' X+ J1.QSPI驱动代码
* |) J% N r3 _这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。QSPI驱动源码包括两个文件:qspi.c和qspi.h。
6 a: F5 i, H6 c, ?3 N) ]9 Cqspi.h头文件对QSPI相关引脚做了宏定义,该宏定义如下:
- D$ C6 T# W( x6 z! G/* QSPI 相关 引脚 定义 */2 K! y% W# \% Z9 ]
- #define QSPI_BK1_CLK_GPIO_PORT GPIOB
% Y4 z4 [/ s" ~; G - #define QSPI_BK1_CLK_GPIO_PIN GPIO_PIN_2
+ e6 N. W, n8 q- w4 U4 K6 k2 m - #define QSPI_BK1_CLK_GPIO_AF GPIO_AF9_QUADSPI, \# K' R! X! T0 _" R0 n t) o
- #define QSPI_BK1_CLK_GPIO_CLK_ENABLE() 8 v2 Y( J i% C
- do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0) /* PB口时钟使能 */
% j; y E5 T9 w# h9 t: q - 8 T, K1 v% K; a/ t) u
- #define QSPI_BK1_NCS_GPIO_PORT GPIOB7 h" c: s& `! i6 O9 ?
- #define QSPI_BK1_NCS_GPIO_PIN GPIO_PIN_6* n+ i* F1 W% T8 |, \. s9 Y
- #define QSPI_BK1_NCS_GPIO_AF GPIO_AF10_QUADSPI
8 K! h# h- h( [9 I% C - #define QSPI_BK1_NCS_GPIO_CLK_ENABLE() + P( M0 p4 z0 G7 z3 p% T- I
- do{ __HAL_RCC_GPIOB_CLK_ENABLE; }while(0) /* PB口时钟使能 */
) j. M: Q+ b* ~$ H2 s
4 P0 e2 a7 f* G+ x- #define QSPI_BK1_IO0_GPIO_PORT GPIOD
/ R- M8 n' K0 s" z1 j) Z3 N - #define QSPI_BK1_IO0_GPIO_PIN GPIO_PIN_11" T% z* w# Q F* N: I) O) j4 [ P
- #define QSPI_BK1_IO0_GPIO_AF GPIO_AF9_QUADSPI# \: A) [- a- s( w
- #define QSPI_BK1_IO0_GPIO_CLK_ENABLE()
6 M0 }/ J- z# S j0 F6 g - do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口时钟使能 */
7 \+ g) A* l( `5 F3 h1 j
3 c1 W/ b7 e0 k* E$ T- #define QSPI_BK1_IO1_GPIO_PORT GPIOD
( w) [6 u0 f( O) h4 `. n4 j6 D) V' t - #define QSPI_BK1_IO1_GPIO_PIN GPIO_PIN_12
( i. d+ o& T4 m& |# @& o: o - #define QSPI_BK1_IO1_GPIO_AF GPIO_AF9_QUADSPI
7 M3 \; ^3 T; V2 ^. l - #define QSPI_BK1_IO1_GPIO_CLK_ENABLE()
( l4 D5 z" A! ~# K - do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口时钟使能 */' |: ~5 X+ d/ C J
- 9 Y" Z* k0 D0 G6 H' q2 Y% W; n) P
- #define QSPI_BK1_IO2_GPIO_PORT GPIOD
8 H8 g& _0 T! [ F( {1 Y - #define QSPI_BK1_IO2_GPIO_PIN GPIO_PIN_13
5 {8 A& o* |6 |2 u$ |( n* m2 L; ` - #define QSPI_BK1_IO2_GPIO_AF GPIO_AF9_QUADSPI6 L: R0 {5 i2 B% {
- #define QSPI_BK1_IO2_GPIO_CLK_ENABLE()
$ d' E$ `0 S9 b: O6 w - do{ __HAL_RCC_GPIOD_CLK_ENABLE; }while(0) /* PD口时钟使能 */7 R _ R4 x8 J% u6 [
6 m, u/ `# I1 e( }! w- #define QSPI_BK1_IO3_GPIO_PORT GPIOE
4 M) S3 R( h& ~ - #define QSPI_BK1_IO3_GPIO_PIN GPIO_PIN_2/ k, ?4 R) v- } Y
- #define QSPI_BK1_IO3_GPIO_AF GPIO_AF9_QUADSPI5 u2 k5 T- b: d" M/ Z9 ]
- #define QSPI_BK1_IO3_GPIO_CLK_ENABLE() & o0 I" V- }2 B% \% \/ E t
复制代码
- j x$ i/ k. B$ b6 ]2 n9 z* ydo{ __HAL_RCC_GPIOE_CLK_ENABLE; }while(0) /* PE口时钟使能 */
7 N( d9 C+ f$ y& Y: a注意这6个GPIO都是用到复用功能,对应引脚的复用功能情况请看《STM32H750VBT6.pdf》数据手册79页之后的端口复用功能表格。9 l7 ?) Q* A2 s1 c
下面我们开始介绍qspi.c的程序,首先是QSPI接口初始化函数,其定义如下:
3 H; L+ p6 x9 M' g. U
0 u3 k6 z/ V' R2 t6 i$ x1 ~- /**
/ U2 X7 N8 x' h3 K - * @brief 初始化QSPI接口" V. x, Q$ @) X* M: C9 |
- * @param 无
: q, O% B/ Q6 v9 o9 m - * @retval 0, 成功; 1, 失败.% `" y! u O, V' f- H
- */, T5 _# o9 t* r4 j- u K
- uint8_t qspi_init(void)
2 f9 X" `3 N# D0 d% e% U - {" h* X9 ?7 Z* e
- g_qspi_handle.Instance = QUADSPI; /* QSPI */
- J0 w0 K' U# f( ?" W - /* QPSI分频比,BY25Q128最大频率为108M,所以此处应该为2,QSPI频率就为) P2 q' g! H- \: v. W
- 220/(1+1)=110MHZ稍微有点超频,可以正常就好,不行就只能降低频率 */& k! T( |# ^1 J8 s
- g_qspi_handle.Init.ClockPrescaler = 1;. G7 j: l; p6 j# ^9 P3 R
- g_qspi_handle.Init.FifoThreshold = 4; /* FIFO阈值为4个字节 */
) e) v- V& C6 C Z3 u9 J: [ - /* 采样移位半个周期(DDR模式下,必须设置为0) */" r# z- Z( r7 x7 V' H9 x ~2 @
- g_qspi_handle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;2 z2 H6 s; w, s- K: E: i
- /* SPI FLASH大小,BY25Q128大小为32M字节,2^25,所以取权值25-1=24 */
6 l/ v0 L+ Q6 G9 ` - g_qspi_handle.Init.FlashSize = 25-1;
# _! R, N& x$ B& x: t1 {( j - /* 片选高电平时间为3个时钟(9.1*3=27.3ns),即手册里面的tSHSL参数 */
+ ]/ O4 S2 |) o5 X: } - g_qspi_handle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_3_CYCLE; $ s" t0 ]# g7 ]. f
- g_qspi_handle.Init.ClockMode = QSPI_CLOCK_MODE_3; /* 模式3 */
0 `, N @0 Y5 t- m, v - g_qspi_handle.Init.FlashID = QSPI_FLASH_ID_1; /* 第一片flash */. o. X" h' b: N7 b' [1 W) l
- g_qspi_handle.Init.DualFlash = QSPI_DUALFLASH_DISABLE; /* 禁止双闪存模式 */! |: t$ u/ r9 Q; a0 w9 ~
- if(HAL_QSPI_Init(&g_qspi_handle) == HAL_OK) " h0 h; w% R) h4 B$ R
- {
, w# f! a- Q9 A0 G" V5 Z @ - return 0; /* QSPI初始化成功 */6 m- V# P. N( f! u! F5 v( N9 _' k
- }* ]/ A( l1 K( K4 F, O
- else
+ I& X9 x0 I( }6 x, [2 h- Q - {
4 y/ n0 x' [) s/ D - return 1;
! d9 m, l7 w5 S1 |; K4 z, b - }* f$ [$ I& Y7 s5 Y/ ~5 P5 O
- }
复制代码 这里我们需要注意的是,QSPI的时钟源在sys_stm32_clock_init函数中已经选择了PLL2R(我们设置为220MHZ),这里就不需要再选择时钟源了,只需要选择预分频系数就可以确定QSPI的时钟频率。时钟模式选择模式3,在未进行任何操作时 CLK 升至高电平。我们用单闪存模式,FlashID要选择QSPI_FLASH_ID_1。6 T' p3 z5 s. {5 ]5 X+ v7 P
我们用HAL_QSPI_MspInit函数来编写QSPI时钟和IO配置等代码,其定义如下:, [+ d- j1 Y j" d- s" g+ m
3 Y$ h# \8 ?3 ^. k- /**( f0 t [; n5 Q
- * @brief QSPI底层驱动,引脚配置,时钟使能
1 _, ?+ s' v0 Q3 l+ @$ k - * @param hqspi:QSPI句柄
6 I- P) _9 Y: C, V& ?( H' N5 ] - * @note 此函数会被HAL_QSPI_Init()调用
) n @# W# x P! s - * @retval 0, 成功; 1, 失败.: C: T5 j9 x6 d$ I3 n9 ? A% t
- */5 `) y2 c! p+ T+ P
- void HAL_QSPI_MspInit(QSPI_HandleTypeDef *hqspi)5 n5 ?/ L8 o. ?! P& L9 |7 |/ Q
- {% d1 S6 }; V; ^6 e$ e3 z; j
- GPIO_InitTypeDef gpio_init_struct;4 L/ i7 C' D0 Y1 T9 |- H3 s7 f, u
- , N( ]# G1 n5 @1 a6 _; Z# s
- __HAL_RCC_QSPI_CLK_ENABLE(); /* 使能QSPI时钟 */7 k2 l3 @2 {+ \3 k" I) S
- __HAL_RCC_GPIOB_CLK_ENABLE(); /* GPIOB时钟使能 */
; Z/ X x2 X, ~6 S9 l. k/ K1 F' M, O - __HAL_RCC_GPIOD_CLK_ENABLE(); /* GPIOD时钟使能 */
, \6 ]$ x- K" l! _& k7 O4 O$ _ - __HAL_RCC_GPIOE_CLK_ENABLE(); /* GPIOE时钟使能 */. ?6 o+ E3 }, T, }
* l# }( d& M, a6 x U8 a: ~' z4 q- gpio_init_struct.Pin = QSPI_BK1_NCS_GPIO_PIN;
' \. {/ i. X1 _$ ] - gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用 */; g. J: a$ t4 `+ {
- gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */
. W, V: @% i- {$ ` - gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */
, e; r" j6 T8 v% p - gpio_init_struct.Alternate = GPIO_AF10_QUADSPI; /* 复用为QSPI */
! V- T! k4 [* }& } N- } - /* 初始化QSPI_BK1_NCS引脚 */* U3 ]5 ^( L/ ~1 l
- HAL_GPIO_Init(QSPI_BK1_NCS_GPIO_PORT, &gpio_init_struct);
8 X4 w' F$ K* C3 R% {" p" { - ( ^5 r# e F1 [
- gpio_init_struct.Pin = QSPI_BK1_CLK_GPIO_PIN;$ ^( [; h* N f8 n6 m' N1 W' l
- gpio_init_struct.Mode = GPIO_MODE_AF_PP; /* 复用 */
. e8 I3 T, j' e; d( b( U [& D, P7 D2 c - gpio_init_struct.Pull = GPIO_PULLUP; /* 上拉 */# s( `& j) d4 B4 M! f6 N& ~+ H
- gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 高速 */2 b: p6 c/ s% ~6 G2 k
- gpio_init_struct.Alternate = GPIO_AF9_QUADSPI; /* 复用为QSPI */3 o% x. _, }/ u& t% T
- /* 初始化QSPI_BK1_CLK引脚 */
! H x) b3 ~3 B$ W. x - HAL_GPIO_Init(QSPI_BK1_CLK_GPIO_PORT, &gpio_init_struct);
8 F$ f. V( H2 Z' d. i) q - 9 E: h# v- _2 z( Z! u
- gpio_init_struct.Pin = QSPI_BK1_IO0_GPIO_PIN;
* m7 _8 a8 X' V( S& e1 R - /* 初始化QSPI_BK1_IO0引脚 */
9 I$ g' e. v& d( U - HAL_GPIO_Init(QSPI_BK1_IO0_GPIO_PORT, &gpio_init_struct);
' H; Z3 K) E7 Z8 ^, G3 j
2 Z/ V- c2 ^4 X3 E( K- gpio_init_struct.Pin = QSPI_BK1_IO1_GPIO_PIN;+ Z6 f G+ E" C
- /* 初始化QSPI_BK1_IO1引脚 */' |6 X' F( L( G6 J# Q f
- HAL_GPIO_Init(QSPI_BK1_IO1_GPIO_PORT, &gpio_init_struct); ) A& V/ K( b. S2 }6 W3 T/ {2 | O
- A) j- R! ?0 X5 w; I8 r- gpio_init_struct.Pin = QSPI_BK1_IO2_GPIO_PIN;5 s3 a7 Z; C# q1 p6 n
- /* 初始化QSPI_BK1_IO2引脚 */
6 ]7 t j! e7 T - HAL_GPIO_Init(QSPI_BK1_IO2_GPIO_PORT, &gpio_init_struct); 6 T5 y* a& e) q4 i; e$ g! M
- + ~/ u! I# [" F, H0 H5 x& |. F1 H
- gpio_init_struct.Pin = QSPI_BK1_IO3_GPIO_PIN;
, H: P6 ~3 ~" K. `* Q/ R; f - /* 初始化QSPI_BK1_IO3引脚 */
4 a' P8 N. V, X - HAL_GPIO_Init(QSPI_BK1_IO3_GPIO_PORT, &gpio_init_struct); 9 f0 t; j0 M7 U) I' g2 V: X y& ?1 ~% `
- }( w$ }2 \# R4 @, j/ w7 b
复制代码 - |! ?* S3 O9 Z# O1 `! G. x
这里初始化的6个引脚全部都要配置为复用功能模式,以及使能QSPI和相应IO时钟。0 a/ w. Y* a( O6 Z! |6 q% B g6 C
接下来介绍QSPI发送命令函数,其定义如下:
& B5 y! k2 p/ ~7 \/ ^- A) f U$ ?5 Q. N1 g3 P6 C: K
- /**
2 o) s' t1 y, u8 q; d - * @brief QSPI发送命令
7 }: Q0 w9 }( H+ a! ? - * @param cmd : 要发送的指令
! c: {- I( w- h% m+ o. _% e - * @param addr: 发送到的目的地址% V0 s5 \# y: I6 m8 p
- * @param mode: 模式,详细位定义如下:* C/ J. }9 k% X* i0 X( g
- * @arg mode[1:0]: 指令模式;00,无指令;01,单线传输指令;10,双线传输指令;11,四线传输指令.
: x, |3 u/ g9 p2 d/ I1 y& ? - * @arg mode[3:2]: 地址模式;00,无地址;01,单线传输地址;10,双线传输地址;11,四线传输地址.3 `% y! g5 H5 B
- * @arg mode[5:4]: 地址长度;00,8位地址;01,16位地址; 10,24位地址; 11,32位地址., x9 D% g% G: [, d8 F0 Y8 ^
- * @arg mode[7:6]: 数据模式;00,无数据; 01,单线传输数据;10,双线传输数据;11,四线传输数据.
) z8 [$ Z9 K" ?4 i0 |6 o% J - * @param dmcycle: 空指令周期数4 L. J. [4 Z7 f5 w: v9 F
- * @retval 无
0 w. p4 H8 [* R8 p - */& T! Q$ l& N4 ^, R7 P
- void qspi_send_cmd(uint8_t cmd, uint32_t addr, uint8_t mode, uint8_t dmcycle)0 u0 `6 h/ n6 B9 R8 V1 Z8 I
- {
& d8 a _3 }- D& H7 k& q9 B% l/ F - QSPI_CommandTypeDef qspi_command_handle;& G4 i- E. @' x. a2 A O) ~7 O
4 w, J2 ^1 \ K4 l- G& s' [; n- qspi_command_handle.Instruction = cmd; /* 指令 */3 \3 j+ b: Q: I* F$ y; D
- qspi_command_handle.Address = addr; /* 地址 */: }- v7 |1 [/ B( ^- c
- qspi_command_handle.DummyCycles = dmcycle; /* 设置空指令周期数 */
# [5 r3 Q; p! G1 ^
7 R; J! z8 ~& i2 ~) O- if(((mode >> 0) & 0x03) == 0)" r" J8 V+ k) p1 `
- qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_NONE; /* 指令模式 */9 ^& q1 K( P& O$ b0 e# _6 @
- else if(((mode >> 0) & 0x03) == 1)
6 B: j9 r Z% w4 X: J - qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 指令模式 */
|& A! C, u, U - else if(((mode >> 0) & 0x03) == 2)4 _( n5 Y6 n0 j# @/ W' F
- qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_2_LINES;/* 指令模式 */# Q, `( f7 Z6 v2 o n+ L! Z
- else if(((mode >> 0) & 0x03) == 3)
& C2 m/ P* T/ `1 x0 I1 y. p; J - qspi_command_handle.InstructionMode = QSPI_INSTRUCTION_4_LINES;/* 指令模式 */( e$ p: z( e! {* s
3 `( c9 l+ [ q- if(((mode >> 2) & 0x03) == 0)
) i2 Y9 K4 i4 A% K - qspi_command_handle.AddressMode = QSPI_ADDRESS_NONE; /* 地址模式 */
) _% g2 i& m) E( l" `# q - else if(((mode >> 2) & 0x03) == 1)" S& y4 X& N' U+ y1 f
- qspi_command_handle.AddressMode = QSPI_ADDRESS_1_LINE; /* 地址模式 */0 r' }8 {& c5 M! p! F! V
- else if(((mode >> 2) & 0x03) == 2)5 _. v( {1 w' W' b: U
- qspi_command_handle.AddressMode = QSPI_ADDRESS_2_LINES; /* 地址模式 */
9 [) N- U2 \& l3 i2 V- U$ M' P - else if(((mode >> 2) & 0x03) == 3)
$ H1 O0 ^( w$ y - qspi_command_handle.AddressMode = QSPI_ADDRESS_4_LINES; /* 地址模式 */0 S: M: W5 Z& T) {& L) |$ h6 T
- 4 y0 n$ z5 ^6 ~: Z- h
- if(((mode >> 4)&0x03) == 0)
$ D6 h+ H6 ~/ U( B6 R$ c% T# u - qspi_command_handle.AddressSize = QSPI_ADDRESS_8_BITS; /* 地址长度 */
3 ^. s5 V. W+ r% s: ~, ^ - else if(((mode >> 4) & 0x03) == 1): f6 w$ [2 a- {
- qspi_command_handle.AddressSize = QSPI_ADDRESS_16_BITS; /* 地址长度 */
) j% B# h |8 _' H - else if(((mode >> 4) & 0x03) == 2)
% `- b) N2 z) G; N9 x - qspi_command_handle.AddressSize = QSPI_ADDRESS_24_BITS; /* 地址长度 */
8 g7 m/ j8 x7 f6 Y2 T4 D - else if(((mode >> 4) & 0x03) == 3): `' K9 m1 k* s: y
- qspi_command_handle.AddressSize = QSPI_ADDRESS_32_BITS; /* 地址长度 */+ |3 q3 x) n7 ` ~0 N
- : u( S6 E2 V0 G) N# M: D
- if(((mode >> 6) & 0x03) == 0)
# F& j- H4 o7 X4 S5 U - qspi_command_handle.DataMode=QSPI_DATA_NONE; /* 数据模式 */; K2 u r. d5 K
- else if(((mode >> 6) & 0x03) == 1)
0 \& M, K) a+ V - qspi_command_handle.DataMode = QSPI_DATA_1_LINE; /* 数据模式 */- Z7 z9 i: x$ `( U5 o! _ u
- else if(((mode >> 6) & 0x03) == 2)
+ L1 {5 |/ t$ p - qspi_command_handle.DataMode = QSPI_DATA_2_LINES; /* 数据模式 */
3 J( [+ B$ r y - else if(((mode >> 6) & 0x03) == 3)4 G/ u: |5 D/ M C7 s
- qspi_command_handle.DataMode = QSPI_DATA_4_LINES; /* 数据模式 */
8 V0 k; A( o3 a. k
6 T" w" A/ a0 g+ j9 x- qspi_command_handle.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次都发送指令 */3 m; t' g) x, T6 r
- qspi_command_handle.AlternateByteMode=QSPI_ALTERNATE_BYTES_NONE;/*无交替字节*/ I: K. G o& D( H$ z1 C2 v$ B
- qspi_command_handle.DdrMode = QSPI_DDR_MODE_DISABLE; /* 关闭DDR模式 */
8 ?# z f: M; H+ b6 Q - qspi_command_handle.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
% f7 w2 [) M8 L; ~# p
/ t! W1 O1 t. O% e4 A- HAL_QSPI_Command(&g_qspi_handle, &qspi_command_handle, 5000);, N! N0 X# l8 ]" q, ?$ x5 V# K+ c
- }2 @. k( L* X& H; o) y- ^& V& \4 S
复制代码 3 o( y4 t. W a
该函数主要就是配置QSPI_CommandTypeDef结构体的参数,并调用HAL_QSPI_Command函数配置发送命令,是一个重要的基础函数。
" Z% ? S# p: m% Y0 v5 X4 |6 m接下来介绍的是QSPI接收函数,其定义如下:
( X2 e1 H( g4 A. c/ R; U1 H& W# g7 z+ n3 z
- /**
+ V/ o3 M# G" o1 b( Z - * @brief QSPI接收指定长度的数据
4 ^; N' G5 ? ` - * @param buf : 接收数据缓冲区首地址5 c1 n2 ]) l+ w: t" ]( g0 z
- * @param datalen : 要传输的数据长度8 q7 V: T1 i# {4 S) L
- * @retval 0, 成功; 其他, 错误代码.0 z/ _9 p# b7 g) q* \2 L. j; X
- */2 S& {9 O0 N E9 c9 c6 |* m
- uint8_t qspi_receive(uint8_t *buf, uint32_t datalen)
/ u; X* M7 M* a1 n ]+ F' S7 q - {
7 `4 \# n4 a7 x0 s - g_qspi_handle.Instance->DLR = datalen - 1; /* 配置数据长度 */
, l! K- C4 c+ B0 K p" \2 c/ W - if (HAL_QSPI_Receive(&g_qspi_handle, buf, 5000) == HAL_OK)
$ T' w, a# e1 b# i - {: t4 v1 G; g/ P4 _: t2 f
- return 0;
% a# t W( q) U( D - }
: }9 G+ ?6 f( h( V - else5 Y+ x, \% V: V* `% U
- {
$ X @$ s7 q" e; v- I& k9 d - return 1;6 r E" c# K7 h: t) v1 [; H
- }# A: z6 q4 t) {% B1 x& j6 X
- }
复制代码 ; d, M& \8 X& l- I+ J
该函数首先把要接收数据的长度赋值到QUADSPI数据长度寄存器(QUADSPI_DLR)中,然后通过调用HAL_QSPI_Receive函数接收数据。3 Y! {; X8 C$ k- z% h
接下来介绍的是QSPI发送函数,其定义如下:0 }/ c* R! W2 K: h0 z7 X
" h8 p; c' W% j
- /**5 X5 a I8 l4 K% `
- * @brief QSPI发送指定长度的数据
1 Y- ?- b: g5 C, d* A - * @param buf : 发送数据缓冲区首地址
2 k" K* e9 m' s+ x6 [( n - * @param datalen : 要传输的数据长度7 s8 J7 `) K8 U) g/ g
- * @retval 0, 成功; 其他, 错误代码.
. [9 A0 `; E, i9 B N - */
" a0 Q1 A3 s( O* i - uint8_t qspi_transmit(uint8_t *buf, uint32_t datalen)5 i% y) d8 V1 J/ ?
- {
. i `, W) f; h) v D - g_qspi_handle.Instance->DLR = datalen - 1; /* 配置数据长度 */
# u3 ]; I9 P. j+ ` - if (HAL_QSPI_Transmit(&g_qspi_handle, buf, 5000) == HAL_OK)
: h" v2 {! D$ u* {7 t! Y - {
5 w% t: V! p. ? - return 0;
$ S9 @# a6 H1 K, D - }2 G ]& m2 l! s9 e Z
- else
9 _/ x7 o) r9 a$ \9 ^( { - {& N1 g- z/ K4 B" z7 B5 ~# t! [
- return 1;
8 F4 C: i5 N) V( |1 n - }# p' g+ K6 Q* j
- }
复制代码
$ v# M0 j5 F3 @# X7 C该函数首先把要发送数据的长度赋值到QUADSPI数据长度寄存器(QUADSPI_DLR)中,然后通过调用HAL_QSPI_Transmit函数发送数据。
( Y& ~$ {0 l# l2 P! |最后要介绍的一个函数是等待状态标志函数,其定义如下:- K' p3 l; \/ W- i
c2 v% @) Y4 c& Q3 a# c
- /**
# w. O! k0 ]6 Q1 z# r4 G8 C - * @brief 等待状态标志2 I5 x% L) [4 { c" ?+ m
- * @param flag : 需要等待的标志位" L# M% o+ M! k4 P! y' R
- * @param sta : 需要等待的状态* V+ X# m f' v
- * @param wtime: 等待时间" ^3 P3 \( n7 i: ^. V% ?
- * @retval 0, 等待成功; 1, 等待失败.0 D& T- t$ {+ A+ L2 }
- */+ b9 P+ i5 a1 h* s5 Q6 v1 l7 F& c
- uint8_t qspi_wait_flag(uint32_t flag, uint8_t sta, uint32_t wtime)
% L+ s0 M& W+ ]7 x$ C - { p/ Y) y+ V k& ~) o8 ~
- uint8_t flagsta = 0;3 r. b- S5 U4 C' W# h
7 ^, u9 x( `7 O1 _, j O& ~- while (wtime)# Y6 R" g- h. ] s5 M
- {
) w9 G/ k' R) x" v - flagsta = (QUADSPI->SR & flag) ? 1 : 0; /* 获取状态标志 */
! K& ^( K5 \- T P h; p' `6 V - 0 a3 G) T+ [1 ~. b% ^0 H
- if (flagsta == sta)break;
& s+ Y) Q. e6 E: V/ `. y
/ i$ q0 W6 S* [) t- wtime--;
- Z( N2 |1 r! j' h# j' H - }
. M% v* A: G4 s9 g! m
; ~4 F7 I/ ~1 Z" k2 E$ ]- if (wtime)return 0;; n/ M5 ^: y" ?( s
- else return 1;* F$ c( U' i- j1 B1 H2 O' @
- }
复制代码 5 m: _5 h* Z1 g
该函数可以设置一段时钟等待QUADSPI状态寄存器(QUADSPI_SR)的任意位为0,或者为1。然后通过返回值,判断等待是否成功,0表示等待成功;1表示等待失败。/ t% A9 h$ n/ P: a/ k8 Y' E
2. NORFLASH驱动代码
3 C6 O( n7 X" J1 ~+ X! H& U; B, k' ~这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。NORFLASH驱动源码包括两个文件:norflash.c、norflash.h、norflash_ex.c和norflash_ex.h。* \/ z2 y1 I7 s* W k+ X F9 o
因为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数据写入。) A2 h& n) e3 a& v
由于这部分代码量会比较多,这里就不一一贴出来介绍。介绍几个重点,其余的请自行查看源码。首先是norflash.h头文件中,我们做了一个FLASH芯片列表(宏定义),这些宏定义是一些支持的FLASH芯片的ID。接下来是FLASH芯片指令表的宏定义,这个请参考FLASH芯片手册比对得到。norflash_ex.h头文件只是一些函数声明,就不介绍了。
: O2 v. H6 @% V8 m* k: H- j) t% Q下面介绍norflash.c文件几个重要的函数,首先是NOR FLASH初始化函数,其定义如下:
4 k3 J, r8 J& I/ P
1 W Q3 K, w2 l9 q# k0 a- /**
6 g) ]( ]8 J3 R( X! l* t1 ~ k" G - * @brief 初始化SPI NOR FLASH: X% H+ [, h0 U7 ?$ M7 V
- * @param 无( I/ ]$ d# k% N
- * @retval 无6 Q+ Q8 i. N; i5 A: u; t
- */
1 S2 o: }4 c% g9 n1 { z - void norflash_init(void)
5 q, ?1 |, A. S4 y5 A* R) | - {
4 Z$ A" N3 }, `$ u - uint8_t temp;& \! A9 a x( \8 p# a8 B
- qspi_init(); /* 初始化QSPI */
# J, L9 }( U5 e) z1 S - norflash_qspi_disable(); /* 退出QPI模式(避免芯片之前进入这个模式,导致下载失败) */; D0 }3 M3 f& W$ G1 B( ~9 M
- norflash_qe_enable(); /* 使能QE位 */# \- |: J6 w/ L8 k* u* L- e
- g_norflash_type = norflash_read_id();/* 读取FLASH ID. */$ E2 y$ m9 s. e0 y5 k
9 x6 A6 J& f, I/ e. ^5 g" ]- if (g_norflash_type == W25Q256) /* SPI FLASH为W25Q256, 必须使能4字节地址模式 */, ~/ U8 R* ^! t8 H8 c# V9 O& A
- {
/ E4 \0 T7 u5 }" Y5 J# P t9 k - temp = norflash_read_sr(3); /* 读取状态寄存器3,判断地址模式 */
- w1 K M. i' X7 X+ V - 6 F% a/ D1 ~0 j, U3 R
- if ((temp & 0X01) == 0) /* 如果不是4字节地址模式,则进入4字节地址模式 */
5 M( k/ H" r* U- B4 t3 O$ f, g% h - {4 W) a6 Y" X. C1 O; x6 A
- norflash_write_enable(); /* 写使能 */
. z0 C3 K* c- }# I) \. t( U - temp |= 1 << 1; /* ADP=1, 上电4位地址模式 */7 L V2 O2 @6 E
- norflash_write_sr(3, temp); /* 写SR3 */& J8 t$ ?' D8 }% X6 b
, D5 v" q, b% X/ w k, m- norflash_write_enable(); /* 写使能 */ Q; I1 h: f2 W% {: [ P4 G& y- Z
- /* QPI,使能4字节地址指令,地址为0,无数据_8位地址_无地址_单线传输指令,
{2 x# m. e! C3 g3 R3 ] - 无空周期,0个字节数据 */
6 ]4 O2 R8 D3 O4 q2 A2 i+ Q - qspi_send_cmd(FLASH_Enable4ByteAddr, 0, (0 << 6) | (0 << 4) + Z+ x y! F' @2 H ^
- | (0 << 2) | (1 << 0), 0);
+ U* Q, `3 T% N" ~( e# L8 M. t - }
; V) |5 @$ g; b7 H - }! k! J: z9 t3 u" n
- //printf("ID:%x\r\n", g_norflash_type);0 p* ?- l- f# P* H
- }
复制代码
( J9 H' S7 q0 G1 j3 f该函数用于初始化NOR FLASH,首先调用qspi_init函数,初始化STM32H750的QSPI接口。然后退出QPI模式(避免芯片之前进入这个模式,导致下载失败),使能FLASH的QE位,使能IO2/IO3。最后读取FLASH ID,如果SPI FLASH为W25Q256,还必须使能4字节地址模式。调用本函数在初始化完成以后,我们便可以通过QSPI接口读写NOR FLASH的数据了。
7 J$ Q' I7 i3 Z/ r1 ~接下来介绍读取SPI FLASH函数,其定义如下:: ]% `6 O! q, m' W* r
7 c b4 P, h+ Y; m$ m* H- /**
4 Z$ a/ N# C& P G6 U& g - * @brief 读取SPI FLASH,仅支持QSPI模式
9 D1 E1 h- v8 e$ h/ O7 T - * @note 在指定地址开始读取指定长度的数据) R2 Z# q; Z U. W! n" S/ [8 r
- * @param pbuf : 数据存储区
+ A4 @7 G R' T; o& d3 o3 C: ~7 {) | - * @param addr : 开始读取的地址(最大32bit)( F. e! I$ ^7 U6 g+ L
- * @param datalen : 要读取的字节数(最大65535)$ ^+ J+ j; c# o+ g5 `6 Y( s
- * @retval 无' k9 o4 c9 j0 w0 ]: c. r
- */+ M4 r' L5 O0 t8 C5 K, f' Q+ t) G
- void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
; [! j" |( U: z: i' Y w- O - {
: n1 {( A$ w, V- [" z - /* QSPI,快速读数据,地址为addr,4线传输数据_24/32位地址_4线传输地址_1线传输指令,
K% N6 Q( q* S" J - 6空周期,datalen个数据 */1 o- R2 j7 G. i0 a3 n& _- f- K! o
- qspi_send_cmd(FLASH_FastReadQuad, addr, (3 << 6) | (g_norflash_addrw << 4)
) T( I8 Z0 l$ h2 X2 w8 ]. p1 ^ - | (3 << 2) | (1 << 0), 6);
; @. [* d3 {! f" C0 Z - qspi_receive(pbuf, datalen);
9 [. T% g: x! N - }& O! {/ g9 \8 E# U; p; ?) n6 D$ ~
复制代码
/ r: g- E5 o9 Q L( x4 y5 l2 v该函数用于从NOR FLASH的指定地址读出指定长度的数据,由于NOR FLASH支持以任意地址(但是不能超过NOR FLASH的地址范围)开始读取数据,所以,这个代码相对来说就比较简单了,通过qspi_send_cmd函数,发送FLASH_FastReadQuad指令,并发送读数据首地址(addr),然后通过qspi_receive函数循环读取数据,存放在pbuf里面。
. Z K- A& g- [9 Y接下来,我们介绍写入NOR FLASH函数,其定义如下:
( ^* Y U: D& x* s
% y1 U/ i4 ^% e9 _- /**9 G( b; T. J& a# ?/ c
- * @brief 写SPI FLASH: ~5 y: ]) ?" O- e0 w
- * @note 在指定地址开始写入指定长度的数据 , 该函数带擦除操作!) v/ c& S/ @2 j% T9 A
- * SPI FLASH 一般是: 256个字节为一个Page, 4Kbytes为一个Sector, , I! C, p: C* L, Q9 r- t) r9 K
- 16个扇区为1个Block
% C1 @# z4 `# R( {* U - * 擦除的最小单位为Sector.
6 ^! `" O: a3 h - * @param pbuf : 数据存储区
# G( _4 E, F3 i+ T4 s - * @param addr : 开始写入的地址(最大32bit)3 `8 j( M0 u+ _& U4 l1 u
- * @param datalen : 要写入的字节数(最大65535)
% m! v* v3 q: Q" g - * @retval 无/ w; S& x( {: R9 X. T9 Z# B9 V
- */% G/ ]! p Q6 w5 X9 m
- uint8_t g_norflash_buf[4096]; /* 扇区缓存 */
7 i( F- @0 ~. k! Y7 V0 P - - s) w, Q& h0 V& w' h
- void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
8 M& X) \; [2 p9 Q - {
% G- d. r1 Q1 Z4 V9 n. V [ - uint32_t secpos;( i+ }# [- y9 G; q
- uint16_t secoff;
: l. j4 G# m/ K% { - uint16_t secremain;
~7 M! c& Z! K8 X9 H# u4 f) z - uint16_t i;( t. s a/ S1 F8 i' B u1 }
- uint8_t *norflash_buf; P% g5 n8 y; t& L' g5 h
1 X7 J( p7 x% p2 M- norflash_buf = g_norflash_buf;
- H3 r$ @' d. i8 p6 { - secpos = addr / 4096; /* 扇区地址 */- O0 e; {) x- F/ \' z L* t
- secoff = addr % 4096; /* 在扇区内的偏移 */
" U" _# s! R2 \) _) \4 f& [ - secremain = 4096 - secoff; /* 扇区剩余空间大小 */, F% V+ {' ~' \
$ p; n* ~% D: Y* L6 F$ ? g- //printf("ad:%X,nb:%X\r\n", addr, datalen); /* 测试用 */
. `. {7 s6 D3 E {3 T: k4 w - if (datalen <= secremain)
7 ~6 T( }% s: T* L! p' A. u3 Z( g - {5 J/ Y3 t' m7 P
- secremain = datalen; /* 不大于4096个字节 */ n4 l9 C# ^+ D' j2 O/ A/ J
- }
; S. I. Q6 G2 m- v# } - 0 ^+ _! l/ G3 t# o3 o" r% }
- while (1): X4 N' L6 T. u& d9 C; ?
- {, q) I& y* _- d2 G" f( o# h4 S. C
- norflash_read(norflash_buf, secpos * 4096, 4096); /* 读出整个扇区的内容 *// S; E$ v8 }1 @* k7 E
, q9 Y" ?3 D% o, U2 d$ ^4 u- for (i = 0; i < secremain; i++) /* 校验数据 */; \9 Z4 C0 d& r8 q
- {- T$ N" m3 c/ ?2 t
- if (norflash_buf[secoff + i] != 0XFF)+ _0 Z% ^* T; ?) H" D
- {
' Z. n, |' U2 u. l, P - break; /* 需要擦除, 直接退出for循环 *// z" @: w7 f6 u x2 Q
- }
' d# n! S; Y- \; v: o+ S. c - }7 `( D; u- G' n9 [& x
- ! \# `9 W0 w9 G) ~8 m! R& D
- if (i < secremain) /* 需要擦除 */
" P2 M/ h: X8 a% u( R. D1 L3 Y - {
6 P, y7 r1 N: z+ e! I, S - norflash_erase_sector(secpos); /* 擦除这个扇区 */& R+ @+ z8 a, K# w0 s/ H4 X' |
- for (i = 0; i < secremain; i++) /* 复制 */
O2 f: v( {8 `9 _! H: ~0 o8 ~ - {
* H u' R( r9 R6 e# C- M1 o" w8 N - norflash_buf[i + secoff] = pbuf<i>;
4 @) m. ^7 y" m* l; m6 W s7 \7 K - }6 g6 k7 X' T c0 f. @5 k; e) p2 u5 G' ~
- /* 写入整个扇区 */
* Q7 ^. Y1 Q) U. q( M - norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);
6 H; ~/ o6 n% q& _ - }; k, H! v- [( I8 O! o
- else /* 写已经擦除了的,直接写入扇区剩余区间. */
' K( g% |. C* X2 ]4 k - {& b' p3 l% s& x% h
- norflash_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */
; H( T9 r1 C. l' Z- h' h# ~' s - }
4 v: R* R2 h, A2 D+ q - $ x' y$ ]) ~+ o5 n. T2 n# u3 R L9 \: S
- if (datalen == secremain)
0 F! A3 l& E4 A# t( B$ ^6 R - {
( u% z$ N# {, W4 t - break; /* 写入结束了 */
- D6 F" M( j' c" b2 I - }
0 {# e2 h# E, | - else /* 写入未结束 */
' r ^1 R- A `- r4 [ - {0 ?$ c( w; N# z1 j6 h
- secpos++; /* 扇区地址增1 */9 c5 R7 k4 q& n# R* d0 P
- secoff = 0; /* 偏移位置为0 */
) n$ [% Z A- [9 ~
0 f0 q. [3 e8 @9 |5 D- pbuf += secremain; /* 指针偏移 */
$ z# {% Q* E* J5 m - addr += secremain; /* 写地址偏移 */, D, x, p5 d; z5 }9 K7 T2 Q
- datalen -= secremain; /* 字节数递减 */
* f# c+ U' R: I! I; M, N y - - j' o% Z/ i0 w4 B. O# \
- if (datalen > 4096)
6 d# D3 w( d& j6 { - {
- O3 x: H6 K3 ?" D2 P8 P - secremain = 4096; /* 下一个扇区还是写不完 */* d8 b. B& w+ D: b# A
- }7 j) g$ Q; d5 I- C4 C
- else
7 q, R4 i5 b5 B0 ~9 e- J( `9 S - {
* U, r9 O4 {2 n, y. D - secremain = datalen; /* 下一个扇区可以写完了 */
0 c; \% L; b, ?- ~/ k8 Y - }+ Q1 ?5 Q) h1 n/ b
- }0 _0 R8 v7 K) T1 v! b! V
- }8 h: A, q" ~# v# P. H* }$ O" r
- }</i>
复制代码
& s4 `9 \5 Q+ ?; V4 y该函数可以在NOR FLASH的任意地址开始写入任意长度(必须不超过NOR FLASH的容量)的数据。我们这里简单介绍一下思路:先获得首地址(addr)所在的扇区,并计算在扇区内的偏移,然后判断要写入的数据长度是否超过本扇区所剩下的长度,如果不超过,再先看看是否要擦除,如果不要,则直接写入数据即可,如果要则读出整个扇区,在偏移处开始写入指定长度的数据,然后擦除这个扇区,再一次性写入。当所需要写入的数据长度超过一个扇区的长度的时候,我们先按照前面的步骤把扇区剩余部分写完,再在新扇区内执行同样的操作,如此循环,直到写入结束。这里我们还定义了一个g_norflash_buf的全局数组,用于擦除时缓存扇区内的数据。0 j% Q2 O( E: `; D! z1 [* [3 w
norflash.c文件我们就介绍这三个函数,其他请大家自行查阅。下面再介绍norflash_ex.c文件的几个重要函数。首先是QSPI接口进入内存映射模式函数,其定义如下:- {& _& e# o( x, O
+ b1 s$ z. N9 ?" d- /**2 q. q& `4 z! T- ^! s, _8 p, v$ U
- * @brief QSPI接口进入内存映射模式1 V% y. R" Q9 Q) Q; X1 g
- * @note 调用该函数之前务必已经初始化了QSPI接口& B: Q2 A* ?& t, j
- * sys_qspi_enable_memmapmode or norflash_init, O+ a% L# T3 v4 f5 N( I0 V2 L
- * @param 无
0 {: E# f' }9 A6 J. l2 ? - * @retval 无
7 ]; ]2 `; ?9 r* a+ }9 X; I$ C - */1 c; g+ q4 S6 W L
- static void norflash_ex_enter_mmap(void)9 g2 m+ u, t( p5 `) y5 W
- {
3 {: `% Q4 c. ~ [ - uint32_t tempreg = 0;
0 z8 P& H: y/ R/ v' L1 u5 F/ ]$ v - , b# b( E3 H5 c( R
- /* BY/W25QXX 写使能(0X06指令) */* h; ? N) `0 P& R* G: ~: Y( I
- while (QUADSPI->SR & (1 << 5)); /* 等待BUSY位清零 */
B, t& S6 s: U) t9 D2 |7 M) a - - R* S, P3 z$ F6 E; S* ]+ ]) {
- QUADSPI->CCR = 0X00000106; /* 发送0X06指令,BY/W25QXX写使能 */
2 M2 ^7 y1 ^1 g6 Q - ; Q9 g/ p c1 w* ]
- while ((QUADSPI->SR & (1 << 1)) == 0); /* 等待指令发送完成 */) T% N: C" x* j% y/ U/ i
- 8 R# A' c; F# A$ c+ r
- QUADSPI->FCR |= 1 << 1;
. u% O% `4 f M+ C0 J' ~; w+ i* W! o
' K/ q: R# ^( y% n% D& O- if (qspi_wait_flag(1 << 5, 0, 0XFFFF) == 0) /* 等待BUSY空闲 */6 W0 J* `* x# }2 g% W
- {
6 |" [& ]2 W& l2 L- o S - tempreg =0XEB;/*INSTRUCTION[7:0]=0XEB,发送0XEB指令(Fast Read QUAD I/O)*/
# \ L8 h: C0 z( H4 C - tempreg |= 1 << 8; /* IMODE[1:0]=1,单线传输指令 */2 ? }1 c! U6 T
- tempreg |= 3 << 10; /* ADDRESS[1:0]=3,四线传输地址 */
( s& u4 K2 q6 X" { - tempreg |=(uint32_t)g_norflash_addrw<<12;/*ADSIZE[1:0]=2,24/32位地址长度*/
3 U$ [& i6 h- O5 @( ] - tempreg |= 3 << 14; /* ABMODE[1:0]=3,四线传输交替字节 */; F& }1 g6 `& c: f7 `" l1 y
- tempreg |= 0 << 16; /* ABSIZE[1:0]=0,8位交替字节(M0~M7) */& G# I% H c! K2 ^) l( K! d
- tempreg |= 4 << 18; /* DCYC[4:0]=4,4个dummy周期 */) `% e7 p! U' v* j6 v3 A! r& a0 v
- tempreg |= 3 << 24; /* DMODE[1:0]=3,四线传输数据 */
* p- g1 W# `: u% t% o" D- G% k - tempreg |= 3 << 26; /* FMODE[1:0]=3,内存映射模式 */, D) }6 r H+ A4 l: L( d7 D( }3 ?% I
- QUADSPI->CCR = tempreg;/* 设置CCR寄存器 */6 t9 O5 Z: c8 a. T9 ?
- }
$ @; T, E) P$ L Z9 Z7 z+ y/ R7 w - INTX_ENABLE(); /* 开启中断 */
& F, N2 i/ G- P2 W6 F - }
复制代码
9 J7 P" }! @' M. b0 }该函数使QSPI接口进入内存映射模式。内存映射模式:外部 FLASH 映射到微控制器地址空间,从而系统将其视作内部存储器。* H! ]) ]& `! Z, w
接下来要介绍的是QSPI接口退出内存映射模式函数,其定义如下:' X' ?& _0 j% p
5 Z( j3 l/ |$ a! V/ J- /**! h! F: S$ s0 n% w6 J( m+ d
- * @brief QSPI接口退出内存映射模式3 \: n3 E3 Y4 }
- * @note 调用该函数之前务必已经初始化了QSPI接口
6 {1 K4 w) b4 L" M - * sys_qspi_enable_memmapmode or norflash_init
5 }1 v5 ~- {3 Y& n - * @param 无
c. w/ k* S: i; Z# I - * @retval 0, OK; 其他, 错误代码% x9 s; S4 s. c- D. F. M
- */# Q- o8 R7 q/ }" J' w/ Y
- static uint8_t norflash_ex_exit_mmap(void)* L! _) ^: R4 L! ]3 @7 a/ S) H+ V; {
- {
; \7 G: h3 H* w8 n& H& W7 w5 {& A; p - uint8_t res = 0;
8 W6 W P7 Y: G5 W
+ N& f( y9 Y1 l- INTX_DISABLE(); /* 关闭中断 */- L6 }- |7 C; a" s8 e9 o
- SCB_InvalidateICache(); /* 清空I CACHE */8 D, U' T5 V. |
- SCB_InvalidateDCache(); /* 清空D CACHE */
) _; ~' r/ \, R( J3 Z) @1 j2 L - QUADSPI->CR &= ~(1 << 0); /* 关闭 QSPI 接口 */
% Q9 X/ w& i% e - QUADSPI->CR |= 1 << 1; /* 退出MEMMAPED模式 */
. I$ B- V8 J, H" ]. T$ ? - res = qspi_wait_flag(1 << 5, 0, 0XFFFF); /* 等待BUSY空闲 */
; _- u7 f, Q7 b! b% l
1 \. @6 @+ v0 X) y/ i6 q! Y" d! ]- if (res == 0)0 F1 s% u9 U8 s# o) x- u
- {2 _( v2 C* w N1 O/ k9 ]0 Q
- QUADSPI->CCR = 0; /* CCR寄存器清零 */
, g B" B1 g# q& y5 _ - QUADSPI->CR |= 1 << 0; /* 使能 QSPI 接口 */6 X) `! [/ O: S
- } u# G- Q! ~. y
- . }- D5 o+ @+ h
- return res;+ n2 X( _, h# f) h) c0 p, ]+ i! Y
- }
复制代码
9 n2 Y; I0 }8 f# X8 i该函数使QSPI接口退出内存映射模式。norflash_ex_enter_mmap和norflash_ex_exit_mmap是成对存在的函数,也是norflash_ex.c文件中最重要的函数。8 @; ]+ a- X3 z+ W" k
接下来介绍QSPI FLASH写入数据函数,其定义如下:
" K% t/ ?3 B/ | _) }+ Z/ o! F, k( e) L% ?
- /**
3 `% S+ @3 n3 X6 P% _, O - * @brief 往 QSPI FLASH写入数据
( M5 `( G# _& J+ G( v6 v, S - * @note 在指定地址开始写入指定长度的数据9 I. P2 D& E0 ]. |
- * 该函数带擦除操作!" l- `. ^9 J* L6 F
- * @param pbuf : 数据存储区. z7 U& Q6 c7 W, ]# E: L/ S
- * @param addr : 开始写入的地址(最大32bit)7 R- C' j8 Y: K m, l" W2 ?
- * @param datalen : 要写入的字节数(最大65535)/ {/ r& C; i% r
- * @retval 0, OK; 其他, 错误代码
* @9 V; u+ I- Y! Q - */ _* H8 [2 o: T5 ^8 D
- uint8_t norflash_ex_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
' f4 |/ T, ~0 L$ T - {
$ K0 Z+ A6 u* F) U t3 r - uint8_t res = 0;
' r0 |+ R, X( L5 m! h/ L - res = norflash_ex_exit_mmap(); /* 退出内存映射模式 */6 N2 \ E; C$ Y0 q' ]# d% Y
- 5 d k+ L" R# h+ y/ R4 Y' u
- if (res == 0)/ ]8 Z( Z; V) }9 o( s
- { V2 I* g, I# R: i
- norflash_write(pbuf, addr, datalen);0 i4 A3 w7 v5 A2 e+ n
- }
$ o \2 U! ?: O$ m, L2 a) t - 3 J7 t9 s. X3 m3 @4 |
- norflash_ex_enter_mmap(); /* 进入内存映射模式 */
( c( o6 H& d+ K+ \" w- o - return res;! M3 p9 w: h0 l5 w( `2 y9 ]2 R
- }
7 V7 q6 h! z J- g$ w
复制代码
8 d) x$ _/ D8 V$ j' E1 v! ^因为STM32H7不支持QSPI接口读时写,所以往 QSPI FLASH写入数据前,需要先调用norflash_ex_exit_mmap函数退出内存映射模式。退出内存映射模式,CPU就不会在QSPI FLASH里读取程序指令,即避免了QSPI接口读(指令)时写。写好后,再进入内存映射模式。该思路也就是norflash_ex_write函数的操作过程。( o, A' C$ K2 q, y
接下来介绍从QSPI FLASH读取数据函数,其定义如下:+ ~! L$ ?# P3 d; N, z) J
* S! N2 h+ [. m) e3 ? G0 @
- /**
9 \- @4 k; T. k* ` - * @brief 从 QSPI FLASH 读取数据- P: `2 V# _& a
- * @note 在指定地址开始读取指定长度的数据(必须处于内存映射模式下,才可以执行)
/ B" j# D' j# E% r& X/ t- C0 P3 I0 i - *
3 {8 e* j6 s9 `& x: V2 U1 U: a' X - * @param pbuf : 数据存储区
$ e/ H" ]& x, N& W3 }" n. K - * @param addr : 开始读取的地址(最大32bit)
- F. L6 O( H9 a - * @param datalen : 要读取的字节数(最大65535)
. X4 a: |# Y$ C7 }* ? - * @retval 0, OK; 其他, 错误代码; N5 D7 _5 C1 ]* t; O0 J# q1 ?) N$ z
- */
# ?- u5 |! h! `- {, M( |+ m - void norflash_ex_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen): A- k* U* a2 b$ m9 B
- {
$ e t6 l/ E& Y - uint16_t i = 0;* c, i6 L7 s* Q3 }; b* {8 Z
- /* 使用内存映射模式读取,QSPI的基址是0X90000000,所以这里要加上基址 */
. |$ C5 C; N: Q; `6 t - addr += 0X90000000;6 U# R& P) z5 C. [$ [8 Q1 V" g8 @
- INTX_DISABLE(); /* 关闭中断 */. Y) E- }9 |$ ~1 v8 I7 L X' T+ F" v
- ; @; U9 a F9 D7 P9 D' Z1 [' E2 Y
- for (i = 0; i < datalen; i++)
! t+ ]' j9 K6 M# ] - {
1 |8 l: i0 o6 z# F - pbuf = *(volatile uint8_t *)(addr + i);! X, [& C6 o7 i
- } }+ Y6 G; j) h$ P# d
- INTX_ENABLE(); /* 开启中断 */
+ U3 z( [. s e& L) d - }
复制代码 4 g0 H, B3 _7 x3 x6 k2 o* h2 \: s
从QSPI FLASH 读取数据就没有写入这么麻烦了,因为不需要考虑STM32H7不支持QSPI接口读时写的问题,但是仍然有要注意的问题。首先是我们使用内存映射模式读取数据的话,还需要加上QSPI的基址。QSPI的基址在qspi_code.scf文件中定义,是0X90000000,所以这里要在QSPI FLASH开始读取的地址上,再加上基址0X90000000。读取的过程是不允许被打断的,所以还要关闭所有中断,读取完成才打开所有中断。% J7 j2 d: ^; [, m; L$ L
norflash _ex.c文件我们就介绍这四个函数,其他请大家自行查阅。
# _+ M& a3 r) L* N3. main.c代码# e6 F2 y; t: f: {: g6 p, \( i
在main.c里面编写如下代码:
+ K- {. m2 }# W$ I2 G. t( Q0 U$ P/* 要写入到FLASH的字符串数组 */ x% x9 i1 B8 C3 ^
) c$ U0 H* r( k3 i8 R0 u: g
- const uint8_t g_text_buf[] = {"MiniPRO STM32H7 QSPI TEST"};
$ T# L' O4 ^; n( C1 b - #define TEXT_SIZE sizeof(g_text_buf) /* TEXT字符串长度 */; K: G( i, t8 N$ I& ~
' S. R! ~. a% G- w+ R# L- int main(void)$ D& ~1 s! a: N
- {- [& N# I* f$ [1 G2 M
- uint8_t key;
; f: ?8 M3 F& h1 I! n - uint16_t i = 0;
" f. ?2 U3 f, j( k - uint8_t datatemp[TEXT_SIZE];" i9 A6 P8 o# p
- uint32_t flashsize;
1 R/ ~) W3 v5 p% O/ C' l$ K - uint16_t id = 0;- h$ u7 a$ z: W$ Y" K
k& y4 _% Q0 Z: u3 f8 `- sys_cache_enable(); /* 打开L1-Cache */
' f; @* R' M0 Y: N* n - HAL_Init(); /* 初始化HAL库 */
0 Q [7 G2 T. b+ U - sys_stm32_clock_init(240, 2, 2, 4); /* 设置时钟, 480Mhz */
/ A9 @5 r o: Q0 E/ O - delay_init(480); /* 延时初始化 */
2 F8 P2 o* }+ b1 U2 E - usart_init(115200); /* 串口初始化为115200 */
1 X+ c9 s, A4 a2 j: I - usmart_dev.init(240); /* 初始化USMART */4 k+ i0 t* r3 V* ?! h2 J
- mpu_memory_protection(); /* 保护相关存储区域 */ {3 r% q8 }& I4 Z: q3 k
- led_init(); /* 初始化LED */4 X1 y! a+ k# D+ W, P0 V. i; B7 {
- lcd_init(); /* 初始化LCD */: [! h2 w$ x1 O: k
- key_init(); /* 初始化按键 */
( c4 I/ X0 v8 m) ]/ f6 j- e% H - /*
* H- } ~: X! f, ^ - * 不需要调用norflash_init函数了,因为sys.c的sys_qspi_enable_memmapmode函数已& y3 n+ W+ g" L/ N) y' E
- * 经初始化了QSPI接口,如果再调用,则内存映射模式的设置被破坏,导致QSPI代码执行异常!
! {: T1 ?/ n& o0 ~% I: y6 l - * 除非不用分散加载,所有代码放内部FLASH,才可以调用该函数!否则将导致异常!( v' `; W& o) A- V
- */* b! R: |* W1 h2 ^: H5 M& p1 [3 z
- //norflash_init();
2 N& [9 ^1 p3 e/ H: X4 S' r/ r - * ^' s# \7 K' h5 O; b$ [, t) `2 O: J
- lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);! N: V# J. T6 }! g2 Z
- lcd_show_string(30, 70, 200, 16, 16, "QSPI TEST", RED);
. j/ J: R& i) C' a3 J - lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);& {- e5 b( k8 r! d6 F; P
- /* 显示提示信息 */
* H% g( o5 D: ^+ e9 w6 T6 P: v3 k5 z - lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write KEY0:Read", RED);. U0 u# d, ~, W4 h, {% b
- 1 m1 F/ d, C8 |1 R+ {) o3 W
- id = norflash_ex_read_id(); /* 读取FLASH ID */
& A1 ~: s, s, K* B: w7 N - 2 e7 L+ c( V$ _5 L9 |
- while ((id == 0) || (id == 0XFFFF)) /* 检测不到FLASH芯片 */. o0 W: L5 V% V0 u
- {* B) P6 P" i0 F* u5 q
- lcd_show_string(30, 130, 200, 16, 16, "FLASH Check Failed!", RED);
6 H' s/ g9 i5 W( n8 ~9 N) I4 x - delay_ms(500);
. A/ _& g, d$ X' ~. Q8 b6 g - lcd_show_string(30, 130, 200, 16, 16, "Please Check! ", RED);3 M; R2 M6 e8 }2 e' w& v$ l
- delay_ms(500);
9 [7 G9 T3 Y. ^& h2 @ - LED0_TOGGLE(); /* LED0闪烁 */
2 r6 o# \! [, B - }
8 B6 `1 S, y* X4 Q$ D; `
2 r: Y+ m2 T: i8 m8 c# M- lcd_show_string(30, 130, 200, 16, 16, "QSPI FLASH Ready!", BLUE);& H! G" Y9 w2 Y6 j. s- d$ C
- flashsize = 16 * 1024 * 1024; /* FLASH 大小为16M字节 */ M. V5 X0 e& O0 x
- 1 r8 X X% ~7 Z; T5 e7 p" V
- while (1)9 b5 X4 V1 j1 R) g: `/ P# _0 Q
- {% c6 c7 s8 N. l! T, f; Y( O. E4 I, \
- key = key_scan(0);
: S; C- A4 c' _ b ?
- B U( b U. v* y/ o" B% }7 {- if (key == KEY1_PRES) /* KEY1按下,写入 */" G7 T; I0 N" }: I
- {
! m6 G4 r( @. v - lcd_fill(0, 150, 239, 319, WHITE); /* 清除半屏 */
* G/ l% V, c9 c; Q1 P* l; Y - lcd_show_string(30, 150, 200, 16, 16, "Start Write FLASH....", BLUE);
( H# p- t% W* F4 Z) ` - sprintf((char *)datatemp, "%s%d", (char *)g_text_buf, i);
3 _, E5 {* [3 ~4 z5 {, [ - /* 从倒数第100个地址处开始,写入SIZE长度的数据 */5 D( y3 Y l' p5 M- \/ E" G
- norflash_ex_write((uint8_t *)datatemp, flashsize - 100, TEXT_SIZE);
3 o# p) Y) {9 q X5 R" n5 Z# T, g0 ] - /* 提示传送完成 */ 0 j% ?7 S2 e3 I% }% L [$ k( ]+ b0 ]' w
- lcd_show_string(30, 150, 200, 16, 16, "FLASH Write Finished!", BLUE); : V) s0 S( r u6 z2 l
- }! O" N% k! X$ s C& `) P# c
- 2 j! c4 v5 V8 [! D/ e: h0 x
- if (key == KEY0_PRES) /* KEY0按下,读取字符串并显示 */6 M; E C0 {5 {1 p6 |
- {
4 K! ]5 e6 ~: H+ J% W% r2 |7 z& {5 d - lcd_show_string(30, 150, 200, 16, 16, "Start Read FLASH.... ", BLUE);
8 G, [* T& U3 H9 q& t; |" s9 Y - /* 从倒数第100个地址处开始,读出SIZE个字节 */3 S- n5 U: S; U
- norflash_ex_read(datatemp, flashsize - 100, TEXT_SIZE);
8 C1 ~4 Q0 U6 r - /* 提示传送完成 */
( R* u3 D+ s1 T8 m% l% c% \ - lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is: ", BLUE); 0 ]9 }- {' E0 s; \( C- I$ N% h
- /* 显示读到的字符串 */8 H# P: N7 ]1 ]+ A% _" E5 u
- lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE); ) ]9 U* ~2 ^$ n' k3 h. j* h, |6 a
- }+ J" L. A' G6 \( ~% \! j& U
- ) h/ Z, q8 S4 A3 G0 z0 J7 }
- i++;
! ]- V$ [/ o1 ~4 v$ R. p1 \ - 6 K/ v% N1 I* d" {( c" G" G
- if (i == 20)6 L5 o4 N( M1 x" A# ]3 Q9 u
- {, w' O3 `9 [, P4 N% K# y# f
- LED0_TOGGLE(); /* LED0闪烁 */" I1 W4 U) P" v. X& \
- i = 0;9 v5 J" e+ I& B4 |% [& I
- }9 s9 d9 ?2 r. \# B: h
- 3 \8 J/ y" p: _
- delay_ms(10);
: n' s2 }. L# H/ Z - }
/ W3 K$ I; H6 s& I - }
复制代码
# d! S" x4 y8 r5 Z- b5 Q! H在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按下,读取刚才写入的字符串并显示。
; n3 J9 a: ?# ]" M) X/ ?最后,我们将norflash_ex_read_id、norflash_ex_erase_chip和norflash_ex_erase_sector函数加入USMART控制,大家还可以把其他的函数加进来,这样,我们就可以通过串口调试助手,操作NOR FLASH,方便大家测试。norflash_ex_erase_chip函数大家谨慎调用,因为会把NOR FLASH的程序指令也擦除掉,会导致死机。如果不使用分散加载,就没关系。7 z' ~6 V% M8 q, `! r' S& s" T6 [
36.4 下载验证6 A0 G5 A6 {( o C, V- i: Q
将程序下载到开发板后,可以看到LED0不停的闪烁,提示程序已经在运行了。LCD显示的内容如图36.4.1所示:
* p* {" t3 x) V" N! N4 c
l3 `0 f' E/ A3 R" l7 ^4 J) J
4 n0 X4 e4 x+ g$ D7 K$ Z* \( N
9 O4 C8 h% Z5 S+ z( k
图36.4.1 QSPI实验程序运行效果图5 } j- ?2 B% Z
通过先按KEY1按键写入数据,然后按KEY0读取数据,得到如图36.4.2所示:6 `; f/ M) \& p7 R/ h! x' J4 h: C
% K% E- b& A# W1 q; K7 B9 j
! t2 S) @" Q, h) Y- b+ o5 k7 k4 J' M9 @% I
图36.4.2 操作后的显示效果图$ Y" E7 x" [$ S& @. h# i
程序在开机的时候会检测NOR FLASH是否存在,如果不存在则会在LCD模块上显示错误信息,同时LED0慢闪。: H+ [, M, V* z6 D& B4 N9 E% E- p) `, c
————————————————
# u6 d/ {' [9 b1 Q4 y版权声明:正点原子 ?- A8 W f6 X0 c9 ]
4 O0 G8 B- e( q Z+ L6 h
3 `8 ^0 h! `# G
2 o5 D& d6 v6 f' v |