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