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