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