一、QSPI接口介绍2 U6 h( g" B2 H, z5 _
4 a/ V; C7 e1 ?" t
QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:
! ^3 ?( `; |7 K' n# m! m' H0 @# n) o, z0 j; _9 L1 M* h9 g' R) O* F
间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。# ?( }/ H5 z& g- B9 ?1 C
# n* F0 A, @/ R1 t5 C
状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)- g. S( G6 p: k! B) m" V
6 ]" S$ V: q( k 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器 Y$ m8 `" m5 v: R. r) i1 ?. j
" H6 a/ I5 J1 t( w2 O采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。$ H( d7 s0 k1 n* ?) \6 i
( ^1 d# W$ S1 g( F/ X- M
内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
& S. E" t* ^/ x/ d& q$ C3 N
# K4 |& G3 I9 X6 {& ^内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。
7 B7 V, L, G) ^. t& c( [5 z9 ?9 p. {& Z9 ], C: @: t
二、 STM32QSPI 功能框图: {! T1 w/ C5 {1 \7 E. `; a
8 @1 f4 H: R! I# G& j
# f* t$ X1 j2 s+ i, N6 M8 m9 A: p& N& v3 C, m" e5 Q
STM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。, M, L& e F, b( F3 O" T
`$ [# I5 H( E2 v
三、QUADSPI 命令序列( G: e# E# F, `# c) g$ a
0 V4 q* ?( S+ w2 h9 aQUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五
_& b. b/ p. M个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。4 i( z- _0 W1 P; a; m3 `; }" i* E
nCS 在每条指令开始前下降,在每条指令完成后再次上升。
! e$ @3 r$ s- _ A0 f/ c4 O) N0 a! a$ Y2 g4 y6 n% [
( D- M4 t! r$ ^* T Q# s r& ~
- Q7 B3 d: X& Q8 g上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.
$ y2 F. ^, \( c/ `, Q2 Y# j7 Y- M" e' q# ~
交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);
) ?1 I7 e5 o: O' e$ W8 m7 f9 b! P7 t- F5 n3 I
空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高% [0 U) z' B4 }7 E1 H% _; S
的时钟频率时,给 Flash 留出准备数据阶段的时间。$ @0 ]/ @1 r9 x/ b' {4 O" H+ ~
若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。4 E7 I4 P8 u% G5 o. G' d: b
h& Q" F- b5 ^0 M: n# W
此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。7 Y" C/ F8 [/ x4 G
/ n( N9 B" c( N7 h: ?四、QUADSPI 信号接口协议模式
8 @1 k# b- b# x0 l8 C5 ^8 _: u* }4 n# Z# `( R% N
下面提到的几线模式都是指数据线的根数
7 v3 v+ w3 U* B( n1 e S! O9 \4 r( p# e3 i* ~
单线 SPI 模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):
5 q4 C8 h! X" s2 D# f: g* W传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与
" u! g0 q! _; d# BIO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。
% G+ T6 C& S' \7 r9 V通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可7 i$ ]& R0 W# a _4 F, D
对不同的命令阶段分别进行配置,以使用此单个位模式。1 C. D' O; J! \6 n4 f; H
在每个已配置为单线模式的阶段中:$ s4 Y) @* J% u5 w; r! s
IO0 (SO) 处于输出模式# R' K* K) I P8 C0 m
IO1 (SI) 处于输入模式(高阻抗)
6 y: T7 S- g% s: @" R! N0 H IO2 处于输出模式并强制置“0”(以禁止“写保护”功能), w( Y4 T' q7 {3 P6 A; a, w
IO3 处于输出模式并强制置“1”(以禁止“保持”功能), I3 i! Q: w& \* G0 m; R C
, A% b3 t; H. w* e" o0 l: f& p7 m& x双线 SPI 模式:5 D/ o/ n' y3 @; E
在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。1 T7 c8 c' o. U
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可
- c9 R4 s* g" ^$ p, Z2 z对不同的命令阶段分别进行配置,以使用双线 SPI 模式。& e7 D( V4 z' }* T4 s
在每个已配置为双线模式的阶段中:& ]$ Q! U% i3 M
IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出
2 K; \/ d. Y" ? IO2 处于输出模式并强制置“0”
# ^5 O4 [8 J$ l8 [' r$ X8 o( ` IO3 处于输出模式并强制置“1”
7 f: H2 }* l2 i l0 g8 F# W* Q在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。8 e% D; i' o5 G$ |) O: c% {, U, H
' u) n S) L5 Y$ k; W# T9 }0 x
! Y* F1 B6 l) n: D四线 SPI 模式:* F2 T: O4 f, f& p0 N% H
在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。4 x0 I6 d5 T) s2 N0 k* l
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可
l- r) X- Q& z( }1 B" [* L对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
! ?/ ~9 }5 \7 m9 @- ^- ^在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高
C% ?9 y5 ~7 E5 a1 f' B阻态(输入),在其他情况下为输出。9 j/ `1 U3 `* V$ v0 t4 _
在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。7 Q M- y* \! F# T; j
IO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI
, c: u6 j* `5 Y+ }+ P i激活,对应 IO2 和 IO3 的引脚也可用于其他功能。
1 P8 v, P( I: }+ t6 h
8 u: G" q* H2 Q C# C6 X4 L+ H% N0 y& p: M4 Q9 Q1 j
SDR 模式(默认工作模式)
3 R6 C/ \) l2 g( C A4 P默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。$ g2 |$ X a3 e4 ]8 J; q7 B
在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK# K( U2 k$ ~6 r3 K9 k8 c$ i. N$ T% E" D
的下降沿发生转变。
( V8 R6 V( `$ a7 y5 W/ B: k在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
# ?4 N6 [$ n! M, u9 R+ l况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。
3 U9 g& y5 Z, q* m! n' q8 }
8 L# v6 U! Z# B3 i% h7 X# t; X7 _& C# D! }
DDR 模式- p3 H* v" [8 m7 |+ L- N/ M
在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信9 J' X7 |% f: G% S9 _: e" d
号时,将在 CLK 的每个上升沿和下降沿发送 1 位。( S' u2 p8 g/ D
指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。
% G/ A( W9 }: M- o2 r, x在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数$ [4 M: _( }7 d5 ]6 D
据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期5 I* f7 t# E- V4 h! |$ ~) N% d
后(下一个反向边沿)对信号采样。
# X1 N# T* Q3 J4 l q2 j- p2 U$ I$ F. p+ `2 m
五、QSPI 配置(HAL库)
7 [# y, B+ d4 d/ Q3 S1 P9 v- _& e
5 x' H+ q7 n# |& c1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟: N; {3 G. }% a, [# N
- E% N9 L! b2 |- c2 U/ c# h- [
& T ~/ Q; U* @& I8 ^# Z- void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)! ?" P) F+ |' \% T2 H3 V
- {
) S1 D" ^/ e3 y) R1 |4 K
4 z5 v- @8 C$ j: j- z! n- GPIO_InitTypeDef GPIO_InitStruct = {0}; d$ y) `, I0 f$ `# P. ?
- if(qspiHandle->Instance==QUADSPI)+ E5 A3 W6 \7 Y v
- {
) ?5 F$ i3 _0 c; Y - /* USER CODE BEGIN QUADSPI_MspInit 0 */
3 e1 X$ X! j. m1 K - 5 n) H- p$ f0 c& F. ]
- /* USER CODE END QUADSPI_MspInit 0 */
2 T" P+ E* G4 b; d4 U2 s5 }2 w) R - /* QUADSPI clock enable */+ x+ N# x% o+ a; e) Q+ f
- __HAL_RCC_QSPI_CLK_ENABLE();
$ u$ c( q7 l, P* T# A$ l$ q
, X+ A. `5 [( c1 A. w- G \- __HAL_RCC_GPIOE_CLK_ENABLE();4 n4 A5 m: g- |2 ]' W
- __HAL_RCC_GPIOB_CLK_ENABLE();
+ n1 q8 y. F# B# X7 W2 y# ] - __HAL_RCC_GPIOD_CLK_ENABLE();4 s* F( ]7 c& t3 t
- /**QUADSPI GPIO Configuration
?3 }+ E9 @* E/ ^- _( A- a - PE2 ------> QUADSPI_BK1_IO2% F$ L8 l" T( S/ H0 `! E4 I
- PB6 ------> QUADSPI_BK1_NCS4 L* Q1 a, u2 m* n) y! `. ^
- PB2 ------> QUADSPI_CLK. s+ K3 P4 j w; U" n8 e; |0 O
- PD12 ------> QUADSPI_BK1_IO1
7 _2 w! N; _" w9 T5 r" O - PD13 ------> QUADSPI_BK1_IO3
% B: |) I" |9 O9 K - PD11 ------> QUADSPI_BK1_IO0
0 s+ j+ b3 F% G7 Y - */0 |# N; s' a& j7 o9 G7 S; C
- GPIO_InitStruct.Pin = GPIO_PIN_2;! v" H2 x- W" _
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;9 O' G* u0 E2 _) j9 |
- GPIO_InitStruct.Pull = GPIO_NOPULL;( k/ }2 L+ D* O, g5 {+ u2 L$ b
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
1 ?. J$ {" }9 E1 W5 ] - GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;$ p* G: I, m4 ~% k
- HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);$ g7 K. Y" f) V
- $ W4 c3 e. a: a( Q9 {0 C% a
- GPIO_InitStruct.Pin = GPIO_PIN_6;- I1 g3 s# j, U$ L L3 b# ~7 `3 `
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
I! V( G) A# C; L; {. _ - GPIO_InitStruct.Pull = GPIO_NOPULL;& |0 O! K- s* V) [% d0 S+ r" P
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;: r$ ~/ V" ?9 q' g: ~
- GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
$ {. s, U& y7 {% g' m* h6 w - HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
0 Q9 r7 Q( r* Y" s* g8 S$ H
$ e' v4 ]5 b Y$ Y" Z- GPIO_InitStruct.Pin = GPIO_PIN_2;0 [- [& s4 x, W( z, M8 {0 }
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
) E/ E* L1 v! y+ K% T - GPIO_InitStruct.Pull = GPIO_NOPULL;
/ p5 W( L2 Z% S( {! }# u - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;- ?1 D: l$ n/ T. Y$ l; t
- GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;% j- m2 O2 r5 b# h5 E! N5 d
- HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);& J- `$ _* Q! z+ P, Y! R/ j; c
- ' \# T, B) L: t& \* F: l. v) l
- GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;
; D. Z V( w/ @ - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;0 c: o5 Y. V J- U
- GPIO_InitStruct.Pull = GPIO_NOPULL;2 x% M: u- { b
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
$ X, T. b; b# I. X+ M7 L. \ - GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;2 c# y4 M' o8 D* f2 V4 y6 y) ]
- HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
: s4 ?! y9 ?& y8 U
$ u( d( _2 A G- /* USER CODE BEGIN QUADSPI_MspInit 1 */
( B5 d' I( o- i$ M
: l7 R( D) a$ h, G: A- /* USER CODE END QUADSPI_MspInit 1 */
8 {. G" S! X2 i& P7 c - }' |0 H* g" l+ d1 ?) r( p+ X
- }
复制代码 , j( q t! g& G, V4 z
2.接着初始化SPI外设$ O) p& v9 t0 j
- 6 s0 l, x* e0 B& z& N
- QSPI_HandleTypeDef hqspi;
* t2 |" ~* k2 ]
* P" C' Y- t3 X- /* QUADSPI init function */
& n6 ]* S% t( X5 V - void MX_QUADSPI_Init(void)3 ]) N4 V( \/ i5 b( j
- {
7 s$ y" n1 B3 s9 w - hqspi.Instance = QUADSPI;//QSPI外设: [7 K- B) ^& n" N4 [4 }+ e' j
- hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
|3 C( `: E4 E# N" ~4 D/ Y: ] - hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节8 R: Y" k- x( p
- hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)
/ t' Q% B4 b0 i( B( N - hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节' a+ _' n+ F c2 W
- hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数" F+ g) t# }3 e$ K% ]
- hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0! A8 W$ u& A$ @5 B3 ]& k* t
- hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
# Q ^! |( n0 v$ @2 j' n e4 v, o0 A - hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式" q6 z3 M% q# S% Z2 z$ M p1 U
- if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设& H7 n, z3 m$ K% b( u
- {; t z( o# }( _+ y9 b
- Error_Handler();0 V) C$ L8 |& u. n
- }: n1 u+ h) ]9 u0 c& v: f {0 E
- }
复制代码 ' o& z1 m; [7 k/ q6 |
3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。- m1 `+ }1 @; e$ _1 D3 A, y
. M! V& [6 O8 e d8 W6 y( _" z我们首先需要配置下面的命令参数结构体:2 u! b! P# R# d* D2 J" s
0 {9 r% @3 g9 p9 v
* P6 v3 S9 y5 Z# f8 }* | `2 ^, i+ k0 Y' W
Instruction:指令类型(读、写、擦除等等)
/ m- X0 ?9 D) R6 Q& d- z) n: C' l; l/ p, L8 s5 x
Address:需要读取或写入数据的起始地址& [) K; J* ~0 D! f
/ t8 Y+ p* Q6 oNbData:需要读取或写入数据的长度$ E$ L5 _$ Z* v7 q0 e4 I, O
0 d6 `8 m" X4 R! A t/ r4 H
DataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。
8 T1 M6 n0 p$ \( t) R3 m; M5 w& o
$ ?3 @! a' y* y- h1 r' z2 Y这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!
1 {% \! G! M' G( K) d' }+ s' V5 |2 a2 Y7 O
4.读取芯片JEDEC ID
; X S" R" R! z8 J, R) @8 l; }0 S, c+ L1 Z: x) h. v4 D* S
首先查看手册上读取JEDEC ID的时序:* S' {3 L9 s" }8 r+ U
+ ]2 f K/ O+ R
4 j; v& ^; {2 e$ q& J3 y/ w( T, H! I( \1 f; ^* i5 |
可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以
& A3 n' I+ F5 k; j9 d/ b
9 z1 A. U* ?! P8 k: o: f dInstructionMode = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
5 l4 @, A; E n% B0 l7 b" B7 n0 h0 o4 ]# {0 j$ b
DataMode = QSPI_DATA_1_LINE;//数据单线传输模式( {3 G9 f3 X7 s& e1 \9 v' w0 H
! Y( L2 ]+ k4 Q* k( [5 q" GAddress和AddressMode 这两个参数的值都置0,表示无地址。# ?: Y: ~4 g7 ~9 x9 Q8 y; a* i
. S* U: ]2 x0 \) E9 S
NbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=36 _ F0 b" Z6 R) M4 ^2 R
3 i. |! w2 O9 w6 O4 A+ x其他值和下面代码保持一致即可。
* z. l) [3 v2 m3 V. N
3 X2 q" f& F6 b然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成: B7 T2 O8 u2 `" z& I4 c5 T$ F
+ y2 ]0 {7 m/ T
最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID
- e4 G# _. d( W+ ]& [5 E- o/ y. J6 l/ j
- /**
/ q; {' v4 U) b4 J! e4 g) n2 Y# G - * @brief 读取FLASH ID
* m" [$ W$ e9 |4 F# e - * @param 无& a. O7 J3 j8 s2 C+ x% H2 @/ w7 ]
- * @retval FLASH ID
* i! q3 i, c3 C% f4 P - */
" W1 x& _3 Z: w* o) J/ _ - uint32_t QSPI_FLASH_ReadID(void)
8 A: z% N% M+ v4 X- _+ b y - {; x) I b2 `% i! J2 R
- QSPI_CommandTypeDef s_command;% ^" U& R5 }+ F8 m, x1 \
( H' c, }' K9 z$ T, n) y4 w- uint32_t Temp = 0;
, \$ ?. J' e" `- {: B4 i n, E% z - 9 J: {6 R4 h- V7 m0 R
- uint8_t pData[3];4 a' O; V& J6 |: O' i. H
- # ?8 [8 h2 i; q P2 M
- /* 读取JEDEC ID */- c6 G- Y% E0 H0 U h
5 w3 w7 G ? Z) B2 F- s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。
3 [0 J0 ?1 |' x% v% b, H0 W! u9 V
7 y/ j: _ {; S5 O7 f; ^9 e- s_command.Instruction = READ_JEDEC_ID_CMD;//读取芯片ID指令2 d0 @. U4 a8 Y/ N% z3 m% F2 M, F
- 6 B* v( M& T5 }. p/ H, A1 T* c
- s_command.AddressSize = QSPI_ADDRESS_24_BITS;//定义地址长度
/ J* \! S* z+ c- g) L# E
6 N& e0 h7 C1 j- s_command.DataMode = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。
5 Y" h2 P- }3 R9 I+ s. G* J5 j - , R. K; A$ x+ g A
- s_command.AddressMode = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。6 U- Z+ ^* J& ~7 V% H+ b/ c
-
- r7 f# z0 r" p% ?) z, w - s_command.Address = 0;
* i$ \2 H% R) b& z- q+ Z% p2 `0 b -
) w/ y+ O7 V+ ?6 t) E, A' C8 A - s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度1 E! A7 a( ?2 y" A5 p4 G$ R
& I8 n ]! W* }8 a0 m P+ g- s_command.DummyCycles = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。
0 W+ S: w, y& C2 t3 x8 g
+ j: W6 n0 K$ R0 Y- s_command.NbData = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。) W/ Z4 a" N g; b! c8 H2 I$ G
- " z" [: z/ P4 c! L
- s_command.DdrMode = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。0 h7 g6 L3 s, @& K
- ; {$ m* t& z% R P+ q
- s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。7 w: |$ b$ ~( [% A( q3 d* J
-
/ ?9 H* S+ e: A: S/ z& z - s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。. o6 y& M& Q R0 b+ B3 y2 ]
- 3 d; Y5 n- H. ~" a( X+ l
- if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
. Y* _' {7 I% K6 _* B* E. t - {
9 I% r/ W# \) i3 o9 C4 M - #if QSPI_DEBUG
3 r! N' p ]/ U" |) S |9 l2 F9 U - printf("send read flash ID command error!\r\n");$ [' J: @- P2 [
- #endif- A$ N6 {3 r( D1 S$ A
- /* 用户可以在这里添加一些代码来处理这个错误 */' J2 W) ~/ N7 J9 g9 ~1 Q9 x. l, c9 D+ S
- while (1) {}
% u4 E4 y- f# `+ @% ~" U' O/ C - }
4 S# Y4 o; N. Z) o- w' W - if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) 3 A& P: N% h3 q& {5 b1 Z/ `
- {
5 _" l% h2 a0 w8 u3 ~ - #if QSPI_DEBUG* } B3 n7 W! Y4 W+ O+ B* V
- printf("read qspi flash ID error!\r\n");
\4 Y/ s9 r1 v$ t; S - #endif/ N! ~" b4 r2 Y& b
- /* 用户可以在这里添加一些代码来处理这个错误 */) |8 v. `) U# x' F; `
- while (1) {}7 g6 e1 `0 p6 g9 K( c& @' U
- }9 e- Z5 m g3 U
- Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );9 h' _. ^/ U4 v- D5 s& L
- return Temp;
3 [* s6 \5 X5 t1 W4 m - }
复制代码
) T) p! j( l9 F 5.FALSH 写使能
0 [, N; x8 J9 u! W: }
6 e- K: P# S" C$ H7 c/ j在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):
) e2 U& p6 E% j3 d% h! |0 |8 t8 _
类似的,需要查看芯片手册,写使能的时序0 D4 _) j1 K' D4 M7 m; g
- C$ |- g+ G$ e* F- }' f. R
" I# I* s0 f- I6 V e3 t
3 W) I% P3 |4 i6 j# M
可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0 : d- e e2 k1 B
2 D4 [( }* ?+ s+ _$ W. U3 D
- /**
/ \& N+ p, V: h( {, S2 @% g6 v J8 l - * @brief QSPI FALSH写使能
5 s7 @) j4 U: O: e - * @param None
. m/ a+ ?; K0 L1 G5 ? - * @retval HAL_OK
( {; o, E0 e, A* D- o4 W/ {# { - */
0 O3 |8 }! l; L q - static HAL_StatusTypeDef QSPI_WriteEnable(void)" e, h5 W- |/ u8 V3 w
- {
: P2 T( _8 f! v, U4 B V* w( E' u - QSPI_CommandTypeDef sCommand;5 O/ v F0 V( O. N. M( t
- QSPI_AutoPollingTypeDef sConfig;2 A1 d, `4 D6 U" `! k' v
- ) i! d7 X- _, l _' i& b. q
- /* Enable write operations ------------------------------------------ */
. l3 h& r+ T6 W" B6 `! W6 ~ - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
2 K3 I6 I. L+ j# I - sCommand.Instruction = WRITE_ENABLE_CMD;( G/ J3 D9 T/ n. G5 k+ v* X
- sCommand.AddressMode = QSPI_ADDRESS_NONE;
k5 T5 f. l( Y3 m( N W8 n! y; w - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
, `% }' |( Y0 V8 u/ f5 L - sCommand.DataMode = QSPI_DATA_NONE;
& x/ o" B+ e+ V+ r& r: W1 a - sCommand.DummyCycles = 0;1 s; J5 z% g X- [$ d
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE;) X' N! D& U/ `! M P% G/ W
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
/ w( B9 i4 Q8 {6 g% ^ - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
+ T2 [1 `4 X. t( ] - ; l- p& @: f( h3 E) c6 Q/ Y1 [
- if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
( H4 c) ?5 C; B - {
3 Y5 j1 `$ s5 s - return HAL_ERROR;
1 p- T# q( k, F) |/ Z- y$ } - }
( M2 a$ |. A. } - " d$ W1 ~% L5 S* j
- /* Configure automatic polling mode to wait for write enabling ---- */
! s. W+ B ]- }! M - sConfig.Match = 0x02;
1 A K( K0 O9 W5 f- i1 Q - sConfig.Mask = 0x02;
+ n- L) T, r6 e! G8 ^( ?" S4 D2 L - sConfig.MatchMode = QSPI_MATCH_MODE_AND;
( }8 N& R2 _; S1 v/ F1 p8 K8 j7 @ - sConfig.StatusBytesSize = 1;' d# a$ \; Y5 P7 b
- sConfig.Interval = 0x10;! j6 ]% r. O1 P5 _9 W% P2 ^
- sConfig.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
+ y1 W- z5 H: n" m5 u% U - 5 o* y7 `2 Z( ]8 w: C
- sCommand.Instruction = READ_STATUS_REG1_CMD;6 X& l+ I% Q5 b/ p* D
- sCommand.DataMode = QSPI_DATA_1_LINE; _- d$ ~3 X4 \! T5 g: v
1 t" ?5 d8 i: U- if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
) z- F# f# J/ @1 ~8 j3 V# T - {7 |2 v+ F9 P% f; ? H4 Z( F9 a
- return HAL_ERROR;
, n0 y2 S0 P( @5 A H, ~ - }
1 E, Z: [ |% z; d - return HAL_OK;- K2 ]6 i8 v" r. T$ I0 X1 K, g
- }
复制代码 8 S# I5 F3 B; q e r
这里需要讲下 QUADSPI 状态标志轮询模式:# V( D" `$ H% d/ B P
在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。) `& a( f5 ^5 l" w; a
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。' y! `& [- H4 [( h" t q2 ?* m
对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入% Q6 H5 Q2 n2 k2 S/ e
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
$ t* T- i$ e1 C' o2 `4 w一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。
! X2 ?& O0 ~8 u! n) A. d在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。
2 B( f7 D# |% V) e. S" u5 F如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内
* G) a; I% f; [& n( }容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。/ d+ d" u8 u, n6 q
如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着$ f- u3 s+ X- X, `: Y
状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。7 K7 L# p5 l6 I
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。9 l7 O. _8 D: a8 o
如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。+ E t% y% H+ @9 F
如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否
+ f F) y6 e/ G1 @8 c/ Z则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性4 D6 F1 y3 G/ o) s+ u! C
访问。# t2 E* H, }, m6 p- `6 M
数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不 C0 y' f ]: X# \$ A4 r8 r
受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数5 ]6 z* f" M) Z! x# p
据读取后清零。8 d; F7 T6 t4 q6 w6 H* A' e" h0 g) l
9 }3 J# d5 v* N" ]: i, e简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。" x: e8 q( B: v1 h
+ K( j0 J8 Q( ?
6.读FALSH
- [* Y$ V0 {) \9 W& j
9 n6 m3 I- ]% [/ r( a$ |* W这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序! @6 ~3 E5 o; i. q) w$ ^1 i
& J5 b: s: I9 V# c+ `
1 w+ c( [* u; N4 F) L1 s) ^( u6 m
3 o; t$ P* u- c" U( s. i0 U: V4 Y
从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:
# j: k5 Q( Z1 Z q& }( W4 _* }
8 L: h6 T0 F( ^% W. Z( _! k- S: x" y- e- /**
+ V+ ^5 [) z j# _& N- b9 l - * @brief 从QSPI存储器中读取大量数据.6 M) I$ `' I$ C* K+ o
- * @param pData: 指向要读取的数据的指针
; K: X/ [; K! E - * @param ReadAddr: 读取起始地址( d/ U7 H9 Q5 v4 ~
- * @param Size: 要读取的数据大小4 [4 N, ^; a1 M2 I5 [4 l
- * @retval QSPI存储器状态! I' k9 V) D; x! N, a' Q3 @6 D
- */0 I/ q# `$ n9 F: j, k" x( ^6 w9 w* h
- HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)0 |' Q: F3 W9 b5 u, E$ ]' h& f
- {
% l& z$ M. g7 v+ c; H7 e: s - QSPI_CommandTypeDef s_command;, p1 r/ P2 `+ j& T
- . K" X5 M5 j3 w8 o1 s: M
- /* 初始化读命令 */4 l) @" @4 C6 G; w m* }
- * Y" I5 A2 F$ s/ v
- s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;! a3 Z# A3 @+ I0 f
4 u8 T7 o g0 k1 P% _8 P+ J- s_command.Instruction = QUAD_INOUT_FAST_READ_CMD;3 b) H; E7 b8 {) j; |& }8 t
! R+ n+ _1 A3 _3 d q- s_command.AddressMode = QSPI_ADDRESS_4_LINES;
, b: h+ R. B: \! e) T3 E9 k - , a5 {# V: `9 i0 C5 e
- s_command.AddressSize = QSPI_ADDRESS_24_BITS;7 k: z7 X+ h- W q
- / \7 U+ v& w2 {* y9 N+ F
- s_command.Address = ReadAddr;
9 m. x% Y8 l4 o; G* n+ ]
8 ^) o* W0 v; h B8 B3 h- s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;8 r% c. \$ p9 m# g
- + n8 a1 r' I8 j; W
- s_command.DataMode = QSPI_DATA_4_LINES;7 Z, I$ C4 p' K. W) R
& i, y5 r* M9 J5 ] J) P. l- s_command.DummyCycles = 10;
. e" e+ d. h3 Y. ~ - ( ^$ C9 ^/ x# d1 r3 T1 @% R
- s_command.NbData = Size;1 P& C: y- y4 `9 j6 _9 I! `; M% W
- 4 h' [. @) k) q: J& A3 E% h0 q W
- s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
, c- N% f$ B. z, R% S2 v - : W t" X! q' A0 s2 N
- s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;7 h" h7 }. _1 ]! N1 o
- / T6 [" k8 d1 X0 `6 I; p, ?
- s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
9 l) S* _5 c* T! ?0 x3 m1 D% L; g - /* 配置命令 */# f0 N" ~8 a' N2 `% f# I1 }. z
- - k9 A4 V: D8 H; e6 o! c2 D, r! j' W
- if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
4 X5 d4 f* }$ f! Z) f* Y; ?* U" \ - {1 r3 q1 {! `- E( y H; ~
- #if QSPI_DEBUG
\% Q8 f! }+ s, M Y8 q - printf("R-send read command error!\r\n");, m7 T' o) d; F. Q
- #endif
2 C& f# q8 T) z$ V0 k, z9 n1 \ - return HAL_ERROR;0 D! s! |' a( q7 s ?
- } + V: k) o3 ~8 ?" H3 \: J5 F
- /* 接收数据 */+ j @0 w1 I2 O1 G/ ^ `5 _/ |6 }: O
- if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) # ]7 P2 {$ U6 b ]! E
- {
/ T0 k; d( K9 Y o7 t - #if QSPI_DEBUG+ x' G: ] Z9 @
- printf("R-read data error!\r\n");+ L" a1 ?" z$ k: X" [% c6 V2 M
- #endif9 Z: F! V6 t2 K4 o. o
- return HAL_ERROR;
. J1 k! y' c4 T: ?2 {- H - }, W- [8 Q X; J# a7 y8 e
- return HAL_OK;
& m* a# e3 f3 \4 ^8 T+ I( p - }
复制代码 % W% I/ a5 H" L: ]+ @; R, n+ Z
本函数实现了从FALSH指定地址读取指定数量字节的功能
9 z. O3 B u4 U: ~( _
' ~4 c, R3 M6 f8 s8 e 7.写FALSH
' U e, D5 ^8 \- c2 E6 [6 u1 P8 x4 J1 D% L. X3 p
同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:' l: F0 g- l: q' i8 c" C9 z
" |8 ~2 C2 X! Z1 _. [
- A* J+ [3 W8 y) a) V
0 u s# N M1 [: A. ~& Y( Y" M
根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期 ! ?) m% B6 H! T5 i1 C; Y/ R' U5 o
8 N6 @6 z+ ]3 I) y' M) u& B- /**
$ a2 D2 G0 G9 S, D9 Y8 Z( f - * @brief 将大量数据写入QSPI存储器
; D# ` b( L/ k# q' _ - * @param pData: 指向要写入数据的指针
# a: c r* r( a - * @param WriteAddr: 写起始地址' f* d2 P9 Y: h9 A" z" G
- * @param Size: 要写入的数据大小0 P; U- a) Q8 ~, b/ i; u+ t
- * @retval QSPI存储器状态
8 V$ g* o7 ]( b0 p1 ?# E; i) z7 e. q - */
0 l2 j3 @3 F9 j! Q - HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
( \0 i5 f( }/ b - {7 f5 X0 l. O. ]* B4 N. [% J2 E2 f
- QSPI_CommandTypeDef s_command;. I% x; q" S3 Z P* c
- uint32_t end_addr, current_size, current_addr; q& l1 M: R6 P& T# o. A8 |0 u; l
- unsigned char sector_start,sector_end,i;
8 ]" N& I+ H% t& n - / S/ W3 k, {' W! q4 S3 ~
- /* 计算写入地址和页面末尾之间的大小 */( Y3 R- w( N I+ \$ n) C8 l, N' \0 b
- current_addr = 0;
, j+ Y: Y) w( C" B$ Z* |& ?* C _ - 0 h* {. {# S; I- B0 e3 z: {6 ^
- while (current_addr <= WriteAddr)
- Q6 M* |9 J8 A" p - {
: J$ n$ U, c& s$ [" }" ~" C - current_addr += N25Q128FV_PAGE_SIZE;7 T1 @% D$ P3 _
- }7 M- P! B5 ^. Q1 h$ m! G3 U/ Z
- current_size = current_addr - WriteAddr;
$ ?. R* P3 I2 \% L5 f' K7 J9 y4 F - /* 检查数据的大小是否小于页面中的剩余位置 */6 @5 R) |" x+ T& E5 d, j
- if (current_size > Size) ' _7 P+ K+ b( X( }/ Q& P. A
- {
2 V d8 Z+ y1 Y! L - current_size = Size;& w1 v7 }; n) n
- }; Y) f: d3 M" w
- /* 初始化地址变量 *// c# p2 v! F4 m8 D; P
- current_addr = WriteAddr;) j+ Q: ?; N b6 y; j
- end_addr = WriteAddr + Size;
; O$ o9 l" b) g4 w, f( {! ] -
1 ?6 p# k* L1 M: t7 J1 B - sector_start = current_addr >> 16;2 ?- k& _1 `2 h; p
- sector_end = end_addr >> 16;# Y5 u' Y0 r0 L& W- _
-
/ u$ w7 F# u- J - for( i = sector_start; i<=sector_end ;i++)7 V ]1 e. |2 l8 Y% p' F
- {
" ]& o" N" ?' q2 G9 P3 w! R& B - BSP_QSPI_Erase_Block(i); //擦除对应删除
3 f6 j+ s7 z" U - }
. Q) }$ Q3 ^5 d/ N5 B - # x1 }1 T5 o! M1 I# w
- /* 初始化程序命令 */" `# L$ o2 z4 n) W
- s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
s: Q+ N5 Q9 g& [- L; t
7 i; Z w* i3 m( o- s_command.Instruction = QUAD_INPUT_PAGE_PROG_CMD;5 b7 O& O5 S* i( N7 {; Q
- % t9 N6 f9 o! z" B! q6 T
- s_command.AddressMode = QSPI_ADDRESS_1_LINE;8 h2 ^* ^- z# F& h6 M$ l r
- 5 e0 }; s$ A- m5 I0 X( x$ p
- s_command.AddressSize = QSPI_ADDRESS_24_BITS;
0 f. h9 p# T" G d) P' \
& I* Q/ f' \. M- s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;# ^, U: F* d# X
% }; H; N# i0 j. b |' P- s_command.DataMode = QSPI_DATA_4_LINES;, K2 s& f. _4 N3 _. b/ h
0 y9 P$ f+ g, I9 M' W- s_command.DummyCycles = 0;
, c; h$ e* X' z1 ~2 k - * ~6 f1 m- h8 A9 T7 b( @
- s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
+ P- g" C+ _; `& |- L4 g. n - ) [( p* L; a& D, [! Y3 C
- s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
& U. o: X- N3 t" H. a
6 } O4 r8 f4 I( p+ j! Y# `- s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
6 S2 a. c- }6 n) X - / @- m" O0 I- ^; J7 I. h, `2 T
- /* 逐页执行写入 */# n' s8 ~# _8 I7 B7 a
- do {6 H( t( {3 e, @- Q' K8 w1 e( F1 q& k
( _8 p& p4 V8 B% [& t1 b- s_command.Address = current_addr;; u; C- u3 [7 }7 P' C: k
- s_command.NbData = current_size;: o* d( I" V5 ~( c7 g+ M
- /* 启用写操作 */- c. M7 t; O$ e2 m3 Q- s
- ! O! A8 W3 T/ S r
- if (QSPI_WriteEnable() != HAL_OK) + C- O0 u6 B! [6 |6 J
- {- y* D6 X' D8 {. L, n2 k
- #if QSPI_DEBUG" A( E2 @3 U! N7 G& Y ?0 Q2 ?
- printf("W-Qspi write enable error!\r\n");# k, o! r, Q, q& [7 p0 Y2 u, i* j
- #endif" T! `! _' ] t9 e& V. U
- return HAL_ERROR;& x. Y4 g2 ^# W% b( E
- }
0 T1 v7 G/ a$ y/ [1 u- S - /* 配置命令 */' ^8 w% R! W' M
- if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)* ]' o) y5 V2 v3 Z7 ^
- {
0 L- g7 h% G- t2 {( W) Q) r7 X2 b7 r+ ~ - #if QSPI_DEBUG
1 \5 C: `. B7 e" o* r - printf("W-Qspi send Command error!\r\n");7 S6 b( h4 i4 b7 A0 v
- #endif
$ c7 |4 `# g1 @5 G$ D0 g- W/ i. Q - return HAL_ERROR;6 _! z9 ~6 F$ O9 ~1 ?
- }
l- s1 \6 V- U5 g5 X9 z1 T - /* 传输数据 */
% ?1 f* R# ~3 z0 `: I - if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) + Q' v( G& r# F- Q# k* K9 O, s
- {6 ]2 g8 J8 N/ ]- z5 N2 ?
- #if QSPI_DEBUG/ Z- q! u6 _5 q0 G" O, w! p5 P! P; J
- printf("W-Qspi Transmit data error!\r\n");
$ h6 b/ \% E5 \$ y K! A+ d* ?4 Y - #endif& s. Z# Z2 B- G6 J4 m! M2 I
- return HAL_ERROR;
8 C4 w8 g5 Y3 g5 [0 P$ a5 d( ~5 U - }
$ L. q R/ w" K0 D. z0 G - /* 配置自动轮询模式等待程序结束 *// V/ v' I& b- ?; B! {" M' W2 @
- ' W3 K2 U8 H q8 ]& r
- if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)# f8 C2 [% O. D1 M) J1 G
- { 4 \; C* v" y. B
- #if QSPI_DEBUG- A% d/ S0 z9 V) h9 [
- printf("W-AutoPollingMemReady error!\r\n");* Q, Q% K) S* R
- #endif
4 h) p$ k% s* f5 g& h4 i - return HAL_ERROR;
& w, d3 x; M: w* _4 ^& s - }
$ M2 _" S( c/ `; D, W+ w8 N - /* 更新下一页编程的地址和大小变量 */
. `0 ?- N8 B; o- r
: d% L% E; m; z. F% K" K: _' ?- current_addr += current_size;) U* H9 M+ z' r
8 E: u7 D0 ^$ g$ a- pData += current_size;
0 T7 ~- Y# O* O. i! g - $ q+ l& M0 [9 r0 `* F6 Y( ~
- current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :
* Y1 ?# \; F* G8 ^4 I3 g
8 ?* E! C4 G3 E- G2 A% Z. Z/ `8 ^- N25Q128FV_PAGE_SIZE;
. `- J% M, R1 M
; i, f" C; @: v8 k6 v/ @# t- } while (current_addr < end_addr);
0 p" V) B- X$ _4 L4 c6 f - return HAL_OK;
( o5 s) V# q1 u$ V - }
复制代码 : |8 z D3 R- r) t3 Q
该函数实现从指定地址写入指定数量的字节。
; Z+ ]: X: T9 u- `3 b+ j; i3 ^0 ~5 E) Y3 L% ?
8.擦除FALSH0 ?( f+ b! T% E" M( ?
- N' e9 g3 h% g
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)& W5 \) }2 k* A E- d% @' `
/ S* i* F. z2 |# N9 w
; R5 X1 w3 n! Y
9 V$ g1 j. w5 @- w/ m( J# N参数设置为1线指令 ,1线地址,24bit地址,无数据阶段
6 _( m4 n. ?8 ^5 \6 A0 U4 ?+ B# I$ I/ t: h) L% u; s% j+ m
- /**
. [+ \( N0 B) K, Z. `: s' [ - * @brief 擦除QSPI存储器的指定扇区3 N: G2 p+ m R" ~- G/ ~8 i
- * @param BlockAddress: 需要擦除的扇区地址7 m4 x5 h% j/ E
- * @retval QSPI存储器状态
/ x8 A& b. x1 w - */
- U7 {, b- s8 } \ - HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)+ ?& {( X6 {* h0 v) T9 |4 _
- {
% ^- W0 K- _: W* G, o! Z - uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址" [3 f2 }% S# t7 Y3 ^( X+ J
- # ^ f1 \% q+ N0 r7 O& ?
- QSPI_CommandTypeDef s_command;+ d) c+ d2 Q+ h% [7 t
- /* 初始化擦除命令 */
S2 R! ~6 ^) A% Z - 7 X* U f# c& P0 d! I
- s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
# J+ W4 T$ [6 q5 D) E1 |( { M
8 J; s0 h. y$ ~' w! }0 O9 G- s_command.Instruction = SECTOR_ERASE_CMD;//按扇区擦除/ ^) X) f! A9 t+ H8 a) ?5 w/ c
- 7 t' ~( |6 ?6 n1 Q# Y3 c ?
- s_command.AddressMode = QSPI_ADDRESS_1_LINE;/ R, K# x1 J$ E0 @1 L9 i5 D
- % d/ t5 M/ W; l5 Y
- s_command.AddressSize = QSPI_ADDRESS_24_BITS;
j. y; W6 {- q5 B - / u5 s" \' D0 C! N6 `1 Z0 P
- s_command.Address = SectorAddress;: ^- M3 s8 k* F9 r. i- }
- / K' q$ i5 n4 w& q! H) Z
- s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;+ D5 y; I4 C' [+ u% H( ]
- / T5 W- x: n- s9 z% n0 W
- s_command.DataMode = QSPI_DATA_NONE;
0 I0 u F! n' _
7 F& ^! w8 w0 P8 r C4 C- s_command.DummyCycles = 0;
7 `+ W4 \+ n! g$ t3 ^" P$ e2 O - ) N- ^, k3 w9 q+ ], y+ e
- s_command.DdrMode = QSPI_DDR_MODE_DISABLE;: f s" {+ p2 b
, `2 r* e1 y% ^! D4 U4 j* P- s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;: l$ j' c; P6 r8 F3 U X: l! R
( |& E5 n4 ^/ i, r6 r( u7 t- s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
. z1 K! o% y! K+ A) M
& O, U9 a N9 Z6 i5 ]- /* 启用写操作 */5 x, b' {4 g# l% V( F( f+ c
! {3 v% W; I" c6 H" U$ l$ }- if (QSPI_WriteEnable() != HAL_OK) - ^1 ]. L Y6 L2 W$ h( d h- _9 _
- {7 B8 a1 i& k/ G6 M6 r
- return HAL_ERROR;
. b, w# b1 T! E5 \2 K" `( \ - }8 ^) o4 P* }) K- @* o0 e/ d
- /* 发送命令 */
; p( H8 T& \: V! k - if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
$ y' y0 D" D8 z. |' |: m# E - {
9 q& O5 j A) T+ U4 c) G9 k) T - return HAL_ERROR; W4 ]4 ~* ~+ w7 k
- }9 j2 v# @ {/ K' F ?: T
- /* 配置自动轮询模式等待擦除结束 */0 n& b+ O. x+ b }& P% a4 |
1 E# G6 o2 c; }/ A/ t- if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
" q6 @/ U# @2 y d; |8 { - {
. z& _8 I5 T9 }" K$ t# u% m - #if QSPI_DEBUG* X0 D U* |1 S' y
- printf("erase sector error!\r\n");
6 x. ?9 _9 @9 k: P) ` - #endif
5 i( C3 C3 C- l" r; r4 c$ S - return HAL_ERROR;
2 {* L& ^' X% d3 x$ T6 I" q - }
- k& |+ X! B6 l- h - return HAL_OK;
% ^5 O9 Y6 v; q% C1 H+ e - }
复制代码
. a; [" ]% |* C% z第二种情况是全片擦除,时序如下:
# S( E: w5 o* J' U: I. @/ U+ K" C' O% [6 ~6 }
# D, I: H' k" M! c# o A+ }, k: y- {9 ~5 `. r- b/ G9 B
全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。) Q1 O0 ~; N( h) _
i( c% B9 x8 w1 L5 ^# m7 D" X- /**; c1 r2 V; w) e5 i
- * @brief 全片擦除 N25Q128全片擦除耗时48s$ Q1 q8 F5 V: r, p. N
- * @retval QSPI memory status7 ^% v8 B7 v" g a' W
- */
0 ^. N2 K' Q, f) x( n - uint8_t BSP_QSPI_Erase_Chip(void)! I6 y9 S, O: h# b& C# f
- {+ k9 t: j0 n6 M- h5 w$ f [
- QSPI_CommandTypeDef s_command;/ b0 r2 Q: j. W! F
- % s2 U0 C( j' _
- /* Initialize the erase command */
8 I; p- E6 I3 Y: E! H% a3 i, a% J - s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;7 B- C; x8 d) K2 `
- s_command.Instruction = CHIP_ERASE_CMD;
6 g' p3 k8 p, S0 i; { - s_command.AddressMode = QSPI_ADDRESS_NONE;
: T8 z1 _% y% r; b - s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
$ Q% [, S3 c, {8 P; t3 t - s_command.DataMode = QSPI_DATA_NONE;8 i6 N/ @, J9 K h# A& J
- s_command.DummyCycles = 0;8 x! y, i" f% u, K2 I9 {% a7 V
- s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
6 J. }/ S; d% q# v. L - s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;, ~; |7 C. Z! J6 X* s( G# r
- s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;5 `1 n r/ D1 E" @" E
& L2 Q! h! L/ W T+ u- y- /* Enable write operations */
- j2 @! q I. k7 x - if (QSPI_WriteEnable() != HAL_OK)
+ E! U' C" w3 c5 a - {
4 C3 A$ L" M; o4 K; N j% q - return HAL_ERROR;
) q. k$ [' F1 I! ]" v7 h# F- q - }% G0 V( k# D9 ^4 U8 e: t
2 [; N' }' G" u; t+ s- /* Send the command */9 x8 \% s$ ?( X, ^( F( a3 l, Z
- if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)! a4 w, y' x' D# n) [6 l/ g W
- {
; I; w2 R% |0 F2 j q/ ` - return HAL_ERROR;
* t: O! f2 ?. v% j& n2 M* K3 E - }
+ Q1 O* g- g7 `, ?3 @
6 M. }, {% P8 j0 ^5 u$ _- /* Configure automatic polling mode to wait for end of erase */ # i# O' e4 i3 v- x
- if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)
0 R5 k" X/ T3 @/ G - {' w8 c3 }) V- Z
- return HAL_ERROR;. B% ~$ m0 H, b6 f) [
- }% s- z% Y& S/ p b) F
% B, b7 k. u& d3 s1 C4 p- return HAL_OK;
5 f3 E @6 y- ~, Z: d5 h3 A - }
复制代码 / K; W6 s6 p' y$ t
9.测试程序- H& K6 t( S5 B; k; ?
& z5 z% X j' ^: K/ y4 O5 ~/ s6 i3 I测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。. d2 M, l& n' x/ g
" S. I8 r6 S0 p: V
- unsigned char write[4096];
+ S! l6 u/ e" X. F' W2 ]# B - unsigned char read[4096]; ) F4 N5 ^5 C; v9 K* B+ G
- void N25Qxx_Test(void) Y+ e* B' C- b! }/ l& K6 Z0 U# P
- {
$ T, q6 G. X. X. d9 X# W - uint32_t i = 0;
, ^5 n, L/ I; ]& r( s: S# t; g - 8 q' g5 } A* X4 f5 g2 S
- for(i = 0;i<sizeof(write);i++); o, B+ Q) }0 [1 O
- {6 K/ {9 y, @3 T$ v; _
- write=i;
$ ?& b& ~$ j5 M - }, r% {0 c5 [0 m) l; C! V
- ) E" {1 `; K, o$ C
- printf("write data:\r\n"); 6 X; F) x8 t, i) j. G
- for(i = 0;i<sizeof(write);i++)
7 I3 j+ m6 n# V/ Y A - {( ~3 G+ i: [: b4 I1 }- P2 D
- printf("%d ",write);+ Q% W9 x- ?" p o6 k$ V' z
- }* u M2 @- b% b; S& X- c( |
- printf("\r\n");
6 _; _" k5 c7 ~7 E! `9 k ] - printf("\r\n");
: {0 F( W9 m: C w \+ c3 a - printf("\r\n");2 X, H8 C+ w9 Y* C6 m
-
! j8 T O/ |2 Y1 L2 g% ~1 x - BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
1 @4 S3 {2 @6 a$ v - BSP_QSPI_Read(read,0x1FFFF,sizeof(write)); 0 F. w0 q9 V# {! g& A
- / m7 u) { j9 h- y. v
- printf("Read from QSPI:\r\n");
" O& ^' N1 b4 e1 X' R8 e( l5 G -
8 k- P [% M- D; t; j4 v - for(i = 0;i<sizeof(read);i++)
! T( a7 Y+ X1 R0 Z - {
) X& N2 q( s! S9 l: z2 m+ L, \/ L - printf("%d ",read);2 ?* d* B* B- l7 X$ E
- }
+ m* o" p8 k9 [ - printf("\r\n");; U2 @1 O; ^; E+ I& u, e
- }
复制代码
9 l8 X" S" M8 C; f& Z写入的数据(部分截图):
5 o6 j& _5 `, l* w
" N0 a* P0 R; \9 Q9 y. t3 [+ j" y( M. a; b0 \1 b+ f
4 A9 I) t3 B* U9 e( [( X4 j8 [
读取的数据(部分截图):2 }4 d) A1 P+ D/ i8 D; L) {, n. y
7 K7 k% }. `: o( j5 A. `- ^ ~# d
9 j$ V0 `: S8 A. _: K3 S2 V ]! U) \" L
截取相同的部分,两次数据完全一致,写入和读取成功。5 F, E% i- \5 N8 r; I' z/ S
/ R) m4 N3 I0 P# ~$ \最后,讲几点需要注意的地方:! @9 v; ]) B8 \: H
# R2 E% Q' N" |+ P4 u1.N25Q128 与 W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。
8 b% |: H- [7 R
5 I% k" z7 ?2 {2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。: a5 p, G/ I/ @1 t
2 P3 u9 S0 s" \" H; }3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:! ?7 C$ @3 n$ V2 i7 ^5 B: [
如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
% P4 j6 d; R! c1 [% ?# X! ? 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。
& o6 M; m# O# }; I0 X5 D' X0 R# s- ~0 f
4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。
$ q9 W7 R" ?1 w/ a* e+ X! f3 d8 ]: i" {7 @, |% t
5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。 b+ I. Y' |( I$ [6 h$ v9 a5 O
$ }7 K$ |0 ~- Z5 [3 w7 B! ]7 C" F/ Q% A7 C* g$ [4 S) W, s) C
* f& _% N+ I e& l8 F* H# ^9 D6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。
4 [! k5 W0 u4 I3 ]# J( D/ F5 Q9 f3 ^* Z. u: G, d+ W( I" \
7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!
1 p1 _; {: a& ^4 n2 P
+ `' E; F$ S. H- j6 x d8 L r3 k5 L
) N# z# N: N6 V" ?$ l1 t+ V8 X
|