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