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