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