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