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