你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32F7QSPI学习笔记——读写N25Q128

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍
& i% j% m3 v+ \' P3 `6 F9 _
* J! L6 Z1 X" U# c. X# L* k% n' SQUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:  C  L9 x% H# u* k$ ~, P$ h' b3 G

& ?) V7 p$ l" D" ~" ]. v, y1 A$ E& E 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。
) I  q- ?' m, @7 j
5 @9 ^) B# K2 V 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)8 W' s" k) [! c, r
/ b9 Y: ?9 y2 X% ]2 `. P
 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器
, ]4 [7 T) ~5 p8 y, n, M8 w2 H. J  s3 E$ s* a' a, w
采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。7 ~- z, k) V) i8 [! y0 q
0 x: \. a3 I0 Y4 m
内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。. p, }6 G6 Z' @5 [

: t. }1 b& {9 _内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。) s0 _* B9 T% {8 H- |

* i# \0 Z. ~) V+ y8 s/ W二、 STM32QSPI 功能框图
# R4 R5 t) [$ _4 C! I- {2 ]! Z7 n
5 B+ a7 b1 N0 c
20191006091636349.png
' g' i0 P8 e9 F" U+ q) k( E, D2 o  _( u
% h7 n' ?$ }8 \2 x, C3 t& ^# C
STM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。' v/ g3 M1 I$ R( u- {' X) ^

% Z- N1 j/ V0 T2 C5 n2 W4 {  b+ ?三、QUADSPI  命令序列
; }& J+ ^" g- |; |, ~! j# C4 l% Y. F+ X$ [1 D; ?
QUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五
, d: s+ s- X8 X3 \& h个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。
/ s" i2 G& j3 d8 }4 c. AnCS 在每条指令开始前下降,在每条指令完成后再次上升。4 i3 v! s  l, H( Z  Y: n# m) w

5 |+ l: q  a1 U! v; w2 P
20191006092242666.png
( J9 E: B; `; B
6 [  o0 L' y- @
上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.* I# W; p# Q' V1 \8 m0 d, A
/ V% T) i( y! z+ o) {( z# }" k; k. R
交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);9 }1 ], R8 i% G7 k) z& \
( @2 |1 m. o" F" l# ?
空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高& i  U" ]$ F3 W& t" M" L1 Z
的时钟频率时,给 Flash 留出准备数据阶段的时间。, E3 J5 W* J2 \8 c+ p2 Z
若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
6 v( p( G4 L0 |( b) V
/ f% p! S" j0 G. c# M; W- N4 c此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。0 L9 \8 N; x( Z# m/ g

% ?! q6 x8 ~# M, B# I. P四、QUADSPI  信号接口协议模式* c) h0 R& ?7 R  W, d, W

: }8 v8 I* ?* E: s下面提到的几线模式都是指数据线的根数3 @0 H; t& r# M" i5 c3 z, f' a7 {

. v/ B# {( l7 x0 }' c1 W! d单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):& l! G% a" O* M$ D( ^2 M
传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与
5 g6 s4 v' B( f! W% ~: p" fIO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。
1 h2 W3 y0 |& e! {) c通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可
+ h/ s, A* \; @& i& O9 M8 G1 I对不同的命令阶段分别进行配置,以使用此单个位模式。: P- b) w( z7 a# q$ f
在每个已配置为单线模式的阶段中:4 y1 P" f, z5 x) G1 l) F9 t/ N
 IO0 (SO) 处于输出模式/ n1 b6 Y. z0 P: o
 IO1 (SI) 处于输入模式(高阻抗), P$ h9 M1 r3 w$ ^
 IO2 处于输出模式并强制置“0”(以禁止“写保护”功能)4 G/ `' w% Q4 L$ u
 IO3 处于输出模式并强制置“1”(以禁止“保持”功能)
0 E- l3 p+ |+ g- k3 X. l* u; z5 }& l* Y$ a2 a+ U% D4 d% X
双线 SPI  模式:3 \; ^. `# G3 A( P- I! I
在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。  s6 s) t  h' \6 Q1 z) N
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可. S' p6 f0 V5 H- V
对不同的命令阶段分别进行配置,以使用双线 SPI 模式。  E" H9 n3 H8 c' m; r/ s
在每个已配置为双线模式的阶段中:! e3 E2 x# k9 b8 e* c& x7 t
 IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出5 T1 O& {9 \/ E
 IO2 处于输出模式并强制置“0”
( Q: z  U; _* Z0 ] IO3 处于输出模式并强制置“1”) Q& a, F7 `3 M; _
在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。! E( p$ N  h- E4 N) E. A5 O
! `0 W  P% a6 P! E

) g, ]' t" C: J& M四线 SPI  模式:
+ u# O! E9 I/ \8 G+ y在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。: _% c: I( Y9 _% T  B
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可
1 S! ^6 A9 b- V. W9 x! A8 G8 H, P对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
" q9 b* A+ z0 ]+ |+ F- H; g' O在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高( Q, z3 n$ \5 L0 k4 a0 x1 H; y
阻态(输入),在其他情况下为输出。
9 A+ u) K& y2 L. F9 L; G在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
4 `  w/ `8 r7 P/ p) J4 cIO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI9 H% d0 v9 j( j
激活,对应 IO2 和 IO3 的引脚也可用于其他功能。3 o' k8 l# ]( ]3 ?

& h- O/ G  h3 E, X' m/ ~
9 ]5 N* V. q1 `# XSDR  模式(默认工作模式)" N* a" h# G3 r2 \
默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
2 n: I2 c3 T# x+ [1 Q( O& R0 l, ~在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK+ V$ J, C9 p2 Q0 V6 j+ A, j" J
的下降沿发生转变。
3 ]; S" m% S! Z" l在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
1 t2 ]! t; x1 ?3 U况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。
% p! J0 B/ W3 j" B6 ?
: s1 K7 U8 [& U5 e- X: T
6 y) n$ z5 j) s( J/ KDDR  模式& k5 i2 ]7 z+ C1 i
在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信
' w; M& x) x- k* w- H号时,将在 CLK 的每个上升沿和下降沿发送 1 位。
& o/ ^$ ?6 b' x指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。% m5 n1 g& d+ q, j* `" R# m) S( ~
在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数
' d3 \8 {2 P5 G( I, k( ~- c" s2 @据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期
2 S8 t% h% S  H1 _后(下一个反向边沿)对信号采样。* q2 y$ y  ^! {1 ]& g% ^1 a' d) k# i

! T$ f' y2 J  h+ V五、QSPI 配置(HAL库)
/ ^% {5 t- }) m" `0 n" j
4 e2 }7 F2 F: N5 e( U* c1 W1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟
7 ~& v1 x- C' |  q& G
/ D/ x& g8 X# {7 d
20191006121236849.png
' G* K+ n0 L1 l: l# ~; `: u
& K! N: K& @% I) s4 G% |
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)5 p/ h! i9 U: f  O( {6 O. X5 T; r
  2. {
    9 g% u( Q0 z: i

  3. ) D  s5 H! V4 j+ H
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};+ V& b, {3 F- \# @' f- q8 z. d
  5.   if(qspiHandle->Instance==QUADSPI)
    " b/ ~% {# ~3 F# \' ^
  6.   {
    4 ?0 |8 G2 z4 }
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */- {+ ?6 l% W0 ~0 E& O1 n3 R/ k

  8. 6 M; A4 g+ E3 g) `# c
  9.   /* USER CODE END QUADSPI_MspInit 0 */
    * S7 U' L% X2 l! X3 x! X. T9 ~6 d
  10.     /* QUADSPI clock enable */, J" a2 R, g" k
  11.     __HAL_RCC_QSPI_CLK_ENABLE();9 v4 T. {3 F1 c6 R6 E6 ^
  12. 7 _3 y* n/ G* m/ h0 _3 X4 f: j6 t
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();5 O# V% t2 `4 ]/ J+ x
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();/ Z% B: f& t, X% D
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();2 b4 m' ^! Y! \* f- k
  16.     /**QUADSPI GPIO Configuration    ' I+ O) G/ F) ^" E" J
  17.     PE2     ------> QUADSPI_BK1_IO2# `% K; \4 s3 y
  18.     PB6     ------> QUADSPI_BK1_NCS
    & K, q, c$ _$ ^" ^$ C
  19.     PB2     ------> QUADSPI_CLK
    ) ?3 p1 w* z+ ]- Q7 N- O
  20.     PD12     ------> QUADSPI_BK1_IO1; @2 r  x6 H2 Z" p$ Q
  21.     PD13     ------> QUADSPI_BK1_IO3/ t. N3 p9 o0 f. J
  22.     PD11     ------> QUADSPI_BK1_IO0 - Y; ^" s1 f6 I
  23.     */
    6 t  @2 l% J; Z6 ^- W
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    ( y$ ^" g# U0 R% t& d9 U. ?$ _2 X
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;' l' U% {& D$ D0 }* X' i
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    5 F! L4 G# R+ f1 u
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;% m/ `# A1 N/ z5 n' F: u
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;$ g; w9 k7 N: X# A, t/ K
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);! z  q' ~8 q$ s' b' R" _  g
  30. : @/ g( A$ h0 x  }- _0 @; Z
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;& T' e* O' w0 E! C& C9 w! U
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    ! B' w* ^- A+ l) b* P: R9 ?
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;6 c, |- [* t) i+ A
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    5 G" u! l6 C, z6 }
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    5 R. e" w$ W8 K% _- q
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);: l$ s4 V/ H/ ?; Y

  37. * P* `) s2 `9 A4 @
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;) A* `1 _9 A* N- _; J
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    % I* o7 L0 V0 ]) W4 Q9 S/ D% Z& g3 G
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    * K3 R& O& z; E1 d0 c
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;' [7 ~# ^' ]4 c: R2 D9 l! r
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;2 u: V( ^6 M" K3 t
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    ' [8 A8 A( c6 h4 M9 R# L
  44. * b* C) J4 b  `. y) f" W" Y6 l
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;
    ! k. s& `$ @- t: i
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    3 O$ r8 v/ o( o% l# |( m
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    # E  p! T  J9 Z/ u. K
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;/ F3 X, @0 S, m% H1 p( m( h
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;  b  O8 O1 m' B5 A
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
      s" K4 @8 E9 t- A, C
  51. . Z: Y9 z* ^/ Q4 h% N
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */
    * w7 F/ K2 o6 L0 U
  53. ; S2 m5 V, F* I4 L
  54.   /* USER CODE END QUADSPI_MspInit 1 */3 P7 ^. f" k; _* v
  55.   }! ^( j* c  T9 M4 H; `
  56. }
复制代码
+ w, q6 [  d5 a" J- R) |7 `
2.接着初始化SPI外设9 }  B- `: g5 `6 x; g$ @' c

  1. . u# m9 p. `* S! v
  2. QSPI_HandleTypeDef hqspi;$ |' f7 J, \1 T3 n7 U
  3. ( p- d, q& i6 ]
  4. /* QUADSPI init function */
    % P5 o, Z) ~0 D
  5. void MX_QUADSPI_Init(void)1 f3 Q  ]1 u# X! |4 v* [8 p
  6. {) ]( t* z1 z4 z  z( }1 c
  7.   hqspi.Instance = QUADSPI;//QSPI外设
    * j9 S7 S7 R/ k. R9 @6 g- Y
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M, ~# }! B, ^* c) e5 h7 m) l% C
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
    - _6 m" A/ y) s. n0 Z* C! P
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)6 n1 U& f  h- @! ^( o1 q( B
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
    9 |) J5 I$ K3 v8 A
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数
    : D0 \+ E  p# X: I& l* G% O+ ?  P9 \
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0) G7 v/ C7 b% I, f' S
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
    / M0 ?% \( m% _' S6 J2 C
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式
    1 x% m6 I( ]$ u- L
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设# n# w( z$ J6 z5 c/ k$ w$ S
  17.   {
    4 ^! K8 h* ~9 `& `8 s  }2 ]
  18.     Error_Handler();+ X. _4 P/ ^3 _- l: A% _. ]
  19.   }
    0 y" B( C  n) b- k) n. m7 v2 k' h" [
  20. }
复制代码
" h8 @3 `( M+ x8 X( J2 u
3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。
$ A5 q* i7 j9 `5 V0 g0 m: y% @8 n; s  \
我们首先需要配置下面的命令参数结构体:" E: V. q% o; p/ H- U: }
0 q- G3 ^1 E9 I' M. [$ @
20191006122437823.png
; D- B, P5 ]0 z
2 X( T2 @. {) Y! l
Instruction:指令类型(读、写、擦除等等)- w. f6 ^3 J5 t% Z7 r; T- {$ ^
# [6 ~/ n. q" r4 m
Address:需要读取或写入数据的起始地址. K1 N7 ~( U5 d

$ W, k- m1 [' A% Q4 d7 `+ K  i/ R. ONbData:需要读取或写入数据的长度
+ v+ {" ~) t- l* d8 @+ j, X
8 k8 E- @7 D5 ~% M: u: [DataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。$ N+ {) A0 |, ?. \5 G0 P  o, Z
- S. r1 Z3 U* j+ e" @
这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!: E! F0 y% s/ Z7 d2 E9 k5 Q' y

& z, P* J9 ?6 v9 u 4.读取芯片JEDEC ID$ [, C8 {- E9 h
; G0 U0 e- s6 c' v1 d8 M- U
首先查看手册上读取JEDEC ID的时序:
6 H. g1 P, ^' w* @  W8 y3 s% J
& L9 P! d2 m8 R. G
20191006131644114.png
7 f" F- P! ], I; @. q0 S7 v& t
& d2 G/ _3 [$ p# i8 E
可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以 0 m& L  G3 L9 Z# d- U4 y: `
4 S: y: ]1 W' L8 a
InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
; u) M9 `: d. D" A+ d# S2 P" n* A
DataMode = QSPI_DATA_1_LINE;//数据单线传输模式
  V( V* e1 {( r1 t- B4 a: V' P4 u1 |; q* b# M
Address和AddressMode 这两个参数的值都置0,表示无地址。$ H: Z" y2 D+ }1 T6 J2 W

/ }9 ?  |* U6 y6 ]* @4 qNbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3
* v$ d( p/ v6 K% @( W" ~2 M* \# p, _( _% i, _2 C! @6 R
其他值和下面代码保持一致即可。 % q3 s$ R$ ~7 ~, j5 r
. v1 R- P2 G; P
然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成. Z# h8 s% u4 q% }; ^  O3 T, b( @& b
. L" O; Q* s; x8 ^2 t& N4 ~( s
最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID0 `$ g& ^. d& ?: F& d
+ W6 I- ^/ c( i% N4 B; L
  1. /**; G5 s' H6 K* a, O8 s
  2. * @brief  读取FLASH ID
    , [3 o/ q7 M+ ^9 b2 i
  3. * @param   无
    $ _+ L7 _6 q9 y3 P
  4. * @retval FLASH ID0 @8 _0 r2 G  m+ P
  5. */
    - y7 U9 r- h/ w# O) N& q
  6. uint32_t QSPI_FLASH_ReadID(void)
    1 O  ~9 Y9 n9 V4 ~. ^" P
  7. {" N) p" k3 A  {
  8.      QSPI_CommandTypeDef s_command;, F" e8 e- B) g6 C9 F: \

  9. 5 u  e) y2 C) Y1 \* H7 h: _; q
  10.      uint32_t Temp = 0;
    0 C) l& s9 F4 Z8 u1 E& Z( P

  11. 8 z! \, V4 m2 U9 B# E1 p' p* l
  12.      uint8_t pData[3];
    & o! U8 y! ?8 r+ U8 U- s
  13.   ^  W, x) K: _% z4 A
  14.      /* 读取JEDEC ID */
    * ^  Y' q% _6 f

  15. - t4 @% U0 m( G. E7 @
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。% }8 Q6 A  l+ x9 z

  17. ! U  l! k; R( G3 ^+ H
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令' A' c6 ?6 y" [
  19. : w  {* f1 q' j: z* g8 X* s/ B# k
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度/ @! l& j! m, X! J" D9 m! j1 w

  21. ; ~4 [  c4 j) v0 G+ L4 H3 q8 E
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。; C( i9 V2 _: C5 v# H

  23. . p, R7 q# N' p% ~! O/ U$ j
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。* k$ B4 N" v+ w
  25.          
    6 h$ E8 U5 V6 B7 g/ z) ?  h6 M
  26.      s_command.Address           = 0;   + r% e( n  D& u/ Q* h
  27.         
    ( U- Q; b+ Y& d8 j( z3 P( g# v. y
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度
    9 X% N0 |" L) U- a0 w6 a/ E8 l
  29. % D2 w* G6 g8 b+ K
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。
    - `- G4 K5 q( }8 k4 k

  31. $ I% c" C8 [. X5 b- z
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。* E1 @5 h/ W$ S  @0 p& C& J( r
  33. : \+ j9 t1 F: m; u
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。+ o: f4 M" b4 O1 ?8 ?! x  e3 s
  35. ( c$ I3 Q( y# S7 d) H
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
    * z) `( i3 y. G
  37.                  
    7 Q0 ~0 R6 x% e+ t' A
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。4 {8 i3 e' H' {* T1 m( s% Q0 ^8 j* K' d
  39. 9 ^# B. U1 Y% c- x4 s7 @+ C
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    0 k# _& h8 }/ o; n
  41.                  {1 J6 h; C( l/ H: f& c1 j3 K
  42.          #if  QSPI_DEBUG
    0 H2 s4 R  q" N" d
  43.                      printf("send read flash ID command error!\r\n");
      |* z! j9 @4 L6 ?
  44.                #endif
    8 W+ a- |3 u7 d, f) l
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */* g2 p+ u' e4 X
  46.          while (1) {}
    - {7 K6 r) H& x1 k5 Y. Z
  47.      }4 i$ \7 ?1 Y5 M- z6 w% v
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK)
    : W8 M+ s; d  m6 [2 M
  49.                  {9 K0 |' l2 ]& f$ A1 f
  50.          #if  QSPI_DEBUG
    7 n* j$ W8 N: a- u3 x9 j
  51.                      printf("read qspi flash ID error!\r\n");
    # ^; i7 h4 ]# L3 z
  52.                #endif( h0 K' e, e; }5 ?8 P% |) b
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */
    - z% f* D, r: }! N
  54.          while (1) {}& k& e" U/ j6 E$ n6 U
  55.      }2 C& f& l" w: X7 B9 Z7 o- h' b
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );2 g' C2 ?) j9 J" v9 u' Y
  57.      return Temp;
    7 J' b& Z  R8 R5 O# Q  e8 A) |, [6 e5 E
  58. }
复制代码

1 u/ a4 c% `- ?5 Y4 v8 f; j# ^: N1 P 5.FALSH 写使能: X1 p9 f1 v/ o- j$ A' C( ]

) e* f+ `% t' R0 }8 L. w7 T在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):. k* r  ]: d9 A, o  p5 _$ J9 X

* A9 l3 A% p! W% g" _: w# @; J: R类似的,需要查看芯片手册,写使能的时序
* B# y! ^9 c! U* N/ {
# ]/ }/ F6 g1 C+ }2 J0 b" c' z
20191006134057767.png

( D( s9 o" [3 I3 M
9 c, E* V4 p# v; \6 L% l# k5 s6 \可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0
  v1 h+ m9 @  k, F& f  H& Y" ~4 L: t
  1. /**
    8 d% A; k1 I6 ~( b" K- T' o6 k
  2.   * @brief  QSPI FALSH写使能
    ! J4 \1 |4 [# X% d
  3.   * @param  None4 l/ k! x" z: z
  4.   * @retval HAL_OK
    ' _5 c' E' E; `+ N
  5. */3 K/ t( e2 T+ m. @2 _$ i
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)
    # R" n+ n0 z$ e: f2 h
  7. {( s1 }2 x+ o/ ?
  8.   QSPI_CommandTypeDef     sCommand;
      [3 a; g) W7 E: h+ ]
  9.   QSPI_AutoPollingTypeDef sConfig;
    1 R2 a( t: n: C7 {$ U; L
  10. 0 |+ i* C6 `* g6 b% r% w
  11.   /* Enable write operations ------------------------------------------ */
    , B! a- m' [* r( q7 _* ~% _
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;2 ~2 ~. I- H8 C, n0 T+ E
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;
    7 Q7 ^+ r' j# W) n9 w
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;
    # b+ c% N& B& h
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    , }# c3 E' c, C9 R
  16.   sCommand.DataMode          = QSPI_DATA_NONE;
    % G( G; P: n) {8 X6 K# x
  17.   sCommand.DummyCycles       = 0;
    , s( c- i8 Y8 Y8 x: y
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;
    & @' l( y8 l# G+ V- r
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    , L2 s! @0 ?- Q! `
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    . \- n) j' V0 n# R' f3 _
  21. / n4 x9 E% m2 o/ d  Q4 h( [- J
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)# b8 O5 \: |8 k& r; \" P
  23.   {9 |  H# ~' S9 ]- @
  24.     return HAL_ERROR;
    % t- g2 g) R- G, X1 F
  25.   }
    * E( u  R+ F$ L3 J. h9 N
  26. 1 ]' o, }  Z# m' b6 N5 I- S
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  
    8 ^! E/ \. j' }" \  H
  28.   sConfig.Match           = 0x02;
    , T: Y, A8 O. q6 [, A. I2 K  U8 _, C
  29.   sConfig.Mask            = 0x02;
    ! t, c5 h, n. z- s9 T' e
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
    7 y+ G) z# o" A
  31.   sConfig.StatusBytesSize = 1;% {8 t; E% ~% H- u% x7 ^
  32.   sConfig.Interval        = 0x10;1 n* C( C% c. l( M: ?
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;
    2 P/ E/ j# Z$ C( M2 P
  34. ) @2 S4 d7 [& {0 b3 Y) r
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;
    6 F- C8 h; @, e
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;
    ( t+ l+ x; A  V6 ]* W. z

  37. . n& K9 _0 ~4 R. \
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    & z7 j8 y% p1 d; e6 a
  39.   {; G9 w3 U6 }- J' o2 h5 T" C+ i
  40.                 return HAL_ERROR;        & U! L8 p3 y: p$ i: h
  41.   }( E8 `7 n$ Z! D4 _4 {- s
  42.         return HAL_OK;/ y& a" n$ V! _: |
  43. }
复制代码
1 F' U9 M3 m( }7 m
这里需要讲下 QUADSPI  状态标志轮询模式:# e  B3 J; T/ L
在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。2 H, r  }0 U% \; _2 U# G6 @
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。
' M# i* l1 d0 h9 M2 s7 u对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入* i2 T) P" K0 Q2 N% A# B& ~
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
) S: Y  r) r4 W- r% I5 d7 N一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。
7 ~4 W! j' G/ z! U2 E" @" E在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。7 u) k: x% z- c  J* f: _; Q% r
如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内" H& I: t0 E* {1 M5 E- h4 \
容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。0 f. ]% p2 N( V; e2 p  o
如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着
* `/ u- i' k2 i/ @状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。) N, f$ `: ?1 p) H% j  C( c
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。
! D! Z* ?3 i# `- a如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。
6 J* G- z" B3 @& V如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否% e9 p; \0 E# }4 ]0 g
则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性
6 O% S# S) c/ e4 Y" g7 y+ t  R访问。2 r. o% t- X' `9 L; Z4 |
数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不
7 c$ l7 S& o2 W5 e7 P7 Y* c受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数
* L9 T6 T. ]& u1 q; ?- G据读取后清零。0 V- ^* A! \( A2 c
$ ?1 H2 k8 {& {; K9 o7 ]2 r
简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。
6 y& ^9 W3 C( T
0 S; X3 x3 O$ z- l+ I) T' b+ r6.读FALSH) P/ e; C0 O/ {. B
2 l4 i: X& o# D$ Z) x
这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序* O/ X9 B' K1 @; e: w
) p4 {7 T! u+ t( U
20191006135244693.png

& V# j) V9 D/ k9 z! p/ I
5 g5 B- ?% a1 R1 ^% A8 O' H0 m* A从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:+ P6 s+ U7 ~6 W

  y6 _% c$ h% |1 F
  1. /**
    + R% |' @/ x! p, Y& j
  2. * @brief  从QSPI存储器中读取大量数据.
    * H8 w0 `2 C- J2 s) l: C
  3. * @param  pData: 指向要读取的数据的指针2 B: _- G9 m' I6 l
  4. * @param  ReadAddr: 读取起始地址$ R' P' D/ O9 _8 y, y: C6 j+ S
  5. * @param  Size: 要读取的数据大小. V4 H, i5 w3 V5 h0 t) [
  6. * @retval QSPI存储器状态
    ( A& N3 Z/ j  W1 b. i
  7. */! [/ e: \/ ?9 |: H9 h7 T* k2 ~8 w
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)  M6 ~5 t4 o5 w* S8 [
  9. {
    7 t5 Q- U4 V- m% n- y0 {
  10.      QSPI_CommandTypeDef s_command;# B* Z! _/ B; |; A) k) c2 U6 a2 z

  11. ) c6 E' V+ a/ Q( _" \
  12.      /* 初始化读命令 */
    ! A9 g0 z% I2 X% r% m% }

  13. ! z, L/ g  Q0 V
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    7 m8 i% j8 c* Q0 T# R7 K
  15. * G8 _( Z% L1 ]% y, k' q' n% q
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;( i" t6 ^# j4 y8 |9 ]% o
  17. 3 H% i' B- I. C8 S
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
    ' k2 ^% m! S8 [# U) S( Q5 x! U
  19. $ x& S1 S% B( M
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;& N: G. b$ S- h) I% k8 b3 {" c# Z7 q

  21. 0 \8 H# B1 A. \0 D+ y3 i
  22.      s_command.Address           = ReadAddr;/ |! ]5 g4 R: z* s

  23.   t0 c8 r3 |, i: V' G
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;) n" m) r+ ~+ \6 D8 x) @/ f. @3 w4 I

  25. 5 X7 c% w0 F  J1 k8 h& m
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;: F8 m0 Q" Q2 R3 y

  27. & V* E- J- a* D3 }
  28.      s_command.DummyCycles       = 10;
    & v! a7 ~7 ~& t# [
  29. 1 {& h  p9 y% Q! M! l/ V
  30.      s_command.NbData            = Size;7 e. u: A! k* @6 q  o
  31. & T5 H+ a8 m+ T8 Y, l8 {
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;. Y/ [2 u5 X9 o: J7 s0 e

  33. , Q$ [% S& p( X: G3 M
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;- Y' ^' r: x* t  V' U! ]

  35. % w" g+ M+ G2 _
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    0 ]8 O$ O- C- l3 N9 W
  37.      /* 配置命令 */
    ( a. g, x* U" p, y& L5 m

  38. ! X" X* U5 [, w
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    7 \4 k$ T8 c5 W& y) o; |
  40.          {' B( [& O0 e1 C& R
  41.                 #if  QSPI_DEBUG8 _: x7 q: I. `% G8 S/ h1 Y, j
  42.                 printf("R-send read command error!\r\n");' f" U( u' j' J! t
  43.           #endif
    / K/ Y) z  `6 i) U, A( H
  44.     return HAL_ERROR;
    . `% z; Z! i0 n" V1 d* H( L
  45.    }  
    : O3 ?+ Q! R- r1 V
  46.    /* 接收数据 */4 z9 ^0 X- \* z: ~/ w/ [6 ~# L, [
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) ( w1 b- R3 e+ L
  48.          {
    1 |# M1 d$ ~! J* \! \
  49.                 #if  QSPI_DEBUG
    1 ~5 z; a2 |+ S, X  \, U" m  O5 z$ f
  50.                 printf("R-read data error!\r\n");$ w- b: g8 U: w$ G5 P
  51.           #endif) j9 U- i# w: P1 S& q( ^
  52.     return HAL_ERROR;8 K3 m& Z- L% ^' f) Y
  53.    }% {6 R! B" {- C
  54.    return HAL_OK;* @5 W9 W" Q% }# Y8 y( }6 U8 s! u$ R
  55. }
复制代码

. U; J% L2 ~9 w9 ~- c, |6 L: z本函数实现了从FALSH指定地址读取指定数量字节的功能, `, Q) @. D( P* |0 i
. L# x; P4 x  @: Q- u2 u& R: F2 m
7.写FALSH1 g( ?9 o- Q9 Q9 v. T; f5 L

2 ?4 i7 I0 v# z同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:# U2 {9 r9 [7 d( Q/ ~$ Q/ }1 N
/ ~# V) m( m0 ?* z& O: g( _
20191006135836784.png
2 d6 B, n/ i$ F+ {% w) a/ K9 f+ {
; U# F5 x2 I1 l, g0 q8 x
根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期 6 Q7 d! A, c; h9 s) l- K+ q) x, [

) {! j# n7 H, f
  1. /**
    ' P2 N& I3 ]7 `* u; Q' I  F1 O
  2. * @brief  将大量数据写入QSPI存储器
    1 t; `8 @8 M! T2 q
  3. * @param  pData: 指向要写入数据的指针
    ; E8 c2 v$ z) H( x" @% a  v5 }5 |
  4. * @param  WriteAddr: 写起始地址2 M5 f9 ~4 t. n
  5. * @param  Size: 要写入的数据大小, w! S+ h, r$ [4 U3 }
  6. * @retval QSPI存储器状态
    ; W9 a6 G+ C$ j3 @! x7 y- d3 P$ a
  7. */" f- p! B" I6 J4 t
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
    ! U" {" E: \5 x7 c* n& Z
  9. {8 u' o1 m/ ?& b
  10.                         QSPI_CommandTypeDef s_command;
    $ x9 i8 w  x, _
  11.                         uint32_t end_addr, current_size, current_addr;" r$ [- x; b1 B: Z0 E
  12.             unsigned char sector_start,sector_end,i;
    ' j) w2 P( X0 M3 U) G, f
  13.         5 |& E* ^- l" P
  14.                         /* 计算写入地址和页面末尾之间的大小 */: j1 n6 \% l6 c/ D3 u9 X: @0 o" d
  15.                         current_addr = 0;
    3 p# H7 K4 g4 L
  16.         + L8 l0 f6 O! n! X
  17.                         while (current_addr <= WriteAddr)
    ) m0 s, m: o1 ~
  18.                         {
    " G+ }) T$ l# L
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;
      F( J1 k$ P; S( S; D7 t5 N) \
  20.                         }* m; a6 |6 Q& M0 k5 W0 F
  21.                   current_size = current_addr - WriteAddr;8 H7 ~7 f& t) Z
  22.      /* 检查数据的大小是否小于页面中的剩余位置 */0 D: o; |/ M8 m* `0 r% q+ ^* M
  23.      if (current_size > Size) , w! N, q# u+ r% N& _! W3 s
  24.                  {
    1 u& d5 ]4 X- }0 h4 p8 Y( y% x
  25.          current_size = Size;* X, l* d# n4 D' K: y/ {/ C
  26.      }* W9 J& ^9 Q; L1 |2 \/ R. a
  27.      /* 初始化地址变量 */. o0 |2 d6 Q1 t/ r5 P
  28.      current_addr = WriteAddr;
    . R- ]  a) ^- s0 Y; s2 E+ v
  29.      end_addr = WriteAddr + Size;
    3 Z2 ?; ~! X4 ?7 W. w
  30.                  - |" Q) m# X2 |3 }; {' q
  31.                  sector_start = current_addr >> 16;) P& t' u' {$ ~( r6 z8 l
  32.                  sector_end = end_addr >> 16;1 x0 j4 c3 M1 u' ]( ]
  33.                  $ |8 W, J; E. O
  34.                  for( i = sector_start; i<=sector_end ;i++)0 `/ C( f7 c! K
  35.                  {+ r4 ?: c! B) |3 ?! k& i! f4 G
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    1 ]5 m" ]% r. p1 [. v# W5 `
  37.                  }5 I! w0 U8 D" {- S$ f
  38.                  & b# D# a* I$ C  H
  39.      /* 初始化程序命令 */# h- S& c( o* S
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    1 _2 A% d0 O" X/ b3 l: s

  41. 5 c* X/ n  c6 @: {* z# M" d" \
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;
    ( E# |# i. {+ J2 B

  43. 0 \! v8 h" v7 j( ^) @
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    8 ~" [6 P' a7 r* N
  45. ! V! Y! r; h/ r4 q7 {
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    , R; ?/ b' z$ _+ B: J; A
  47. ' L9 t, ^1 a: ~6 o  W
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;3 l: |) J% `' [5 k  V1 N

  49. ) ?  S7 n8 b5 B/ L( K1 P0 u( ^
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;
    , U4 ]2 {! J& {6 }8 S, n( L$ {( m
  51. ' x8 S% x) v& a% k3 F
  52.      s_command.DummyCycles       = 0;* J6 ?: M/ j; D  V
  53. ! N8 G% N" M7 ]/ i
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    ! G! p- {7 B4 i, T2 @7 [3 w1 D

  55. 9 g1 N; x5 q6 H" u8 [  \
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;% m* s  M7 I# a5 R

  57. ( u  `5 x- I. G
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    * |5 p1 {7 ?% U+ G$ U
  59. + N4 d/ }: H; Y; s* d. ]. ]4 F
  60.      /* 逐页执行写入 */; E8 v0 R0 c% F! x: s3 f* k
  61.      do {
    9 j) Q8 W6 e1 N! X& O2 s
  62. 4 x9 a2 {% G, D" v3 k
  63.          s_command.Address = current_addr;% S& m0 s! F3 D; ~* s1 U
  64.          s_command.NbData  = current_size;  t' Z1 ]6 R3 A- i- ^7 `) r
  65.          /* 启用写操作 */
    6 L3 |; r! d" H' \
  66. ' b( O3 O* j( }
  67.          if (QSPI_WriteEnable() != HAL_OK) . g1 r( Q9 {# n8 D7 Z
  68.                                  {
    0 g5 b0 _4 }! _: R. |# o/ c
  69.                                                 #if  QSPI_DEBUG, V: g6 I" O% h* Z) n: P; |
  70.                                                 printf("W-Qspi write enable error!\r\n");" h& {) j% M0 H- Y
  71.                                                 #endif
    ! I+ O+ r4 X* d
  72.             return HAL_ERROR;# Q6 A% F: [3 P" }% N% I. t6 Q& u
  73.          }
    ) }% ?7 f& W' g: j
  74.          /* 配置命令 */+ h, r6 @6 S$ y
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    2 A4 J7 [/ J  `. N; K+ x" b
  76.                 {                                 
    / X, T, Q/ d- M/ ^  z0 ]1 J
  77.                                           #if  QSPI_DEBUG2 z- R* s3 y& V: v8 J; ~0 e
  78.                                                 printf("W-Qspi send Command error!\r\n");- v, U) k0 w" P2 i' W% _3 g
  79.                                                 #endif0 i% L3 p  H6 U' L% r9 G% F4 I
  80.             return HAL_ERROR;& c% f6 ~  ?: s- Y' `8 U* _
  81.          }
    . l+ Z+ K6 _( R+ Q) _+ @, E6 u
  82.          /* 传输数据 */
    & S* K/ K( F7 s
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) 7 r% Z3 Z* N$ I
  84.                                  {
    . {2 I1 _6 D$ }' g( _0 }7 Q. l
  85.                                           #if  QSPI_DEBUG
    6 D7 h4 j) y/ |6 |1 F0 T$ D3 L0 r
  86.                                                 printf("W-Qspi Transmit data error!\r\n");5 C+ F: j+ w* z# c* D7 J
  87.                                                 #endif
    9 I; O$ ]* R! R% q7 a# i7 C: W! a* S
  88.             return HAL_ERROR;
    & j2 n+ N0 H% N% R
  89.          }
    , Q' T1 q8 a% H1 Y0 D
  90.          /* 配置自动轮询模式等待程序结束 */
    , B/ |1 R2 C5 c- f1 ~. }* W& ]: @* W& ~

  91. % _) ~. J2 j: P3 `8 g
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)2 @6 g. p* W# j; R
  93.                                  {                                         
    ( p! N! [7 r6 f* n/ A4 b& C
  94.                                                 #if  QSPI_DEBUG
    ; ^: \9 H5 {# j  j; X  j1 j
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");" \9 I4 i: ?! E' q
  96.                                                 #endif( Q9 |% X+ _3 B) h% q
  97.             return HAL_ERROR;4 B/ g1 ?3 j, Z
  98.          }9 v6 t- D8 x8 `
  99.          /* 更新下一页编程的地址和大小变量 */
    / j2 {$ e6 ~5 q. ?. ]8 T
  100. $ K6 [" v  ]' \+ e8 c, }
  101.          current_addr += current_size;6 ?2 x4 M! {" Q( ~5 L# a/ y

  102. * j4 w4 d4 H, A& H
  103.          pData += current_size;
    $ A) g% s/ O5 F4 B2 b& ^7 _! u

  104. & U- S/ A( z/ B
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :
    8 r* g. ~' `9 Z+ q& R+ a
  106. 8 U. ]1 g8 U& s5 t7 \# O7 M+ O' r& c
  107.                         N25Q128FV_PAGE_SIZE;
    % }: C" y7 ^* Q$ N8 T1 x
  108. , q  _+ L- u& t# J1 N# V' v
  109.      } while (current_addr < end_addr);
    9 Y  C; w1 @5 v1 W
  110.      return HAL_OK;& L# k) t7 X. f6 s
  111. }
复制代码
, ?8 o2 D( }; Q3 u+ V+ R
该函数实现从指定地址写入指定数量的字节。* U; I  r1 ?3 B% {0 b
9 L' C: h) B) \, `5 Y. m& {
8.擦除FALSH1 ^6 z7 n! D# s4 L5 _- k8 \- m
  T: f1 M* V7 k7 [* x6 [
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)
" E4 J" \9 K: D' H. l$ i9 A
) Q& f' w4 K# I% n1 w% r
20191006140420479.png
' e; P0 k# R: X/ W. j
8 r" W1 B+ e! H
参数设置为1线指令 ,1线地址,24bit地址,无数据阶段
( v- m8 }. J+ t' t
# `1 I8 R' P, a5 K# p6 X- B9 l
  1. /**
    / h5 X& t, h7 H! r9 C
  2. * @brief  擦除QSPI存储器的指定扇区
    ! J- V1 ]% `( A4 Y( R
  3. * @param  BlockAddress: 需要擦除的扇区地址! f2 M5 v3 P. x6 \, z
  4. * @retval QSPI存储器状态
    ' W. f; I  _) [: Y" N$ i; U5 P0 P
  5. */
    " i$ q* E2 j# i5 k
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)9 N2 `& X/ s* v
  7. {
    - J5 s5 k$ `; b
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址
    6 ~6 X/ Y+ S3 r8 Q
  9.         0 J8 z8 V0 P' h/ o7 t
  10.      QSPI_CommandTypeDef s_command;' g: u5 \8 i. _; S! }
  11.      /* 初始化擦除命令 */$ \9 P& R2 O, L: g! I$ F
  12.         0 s1 \% W: N5 J, D
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;' x' w0 @0 {$ ?
  14. 0 G1 K9 u# _7 @8 T' G
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除
    - H" c9 D- i) I% ^/ M. i
  16. 0 |0 B. h9 S3 n9 o- [
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;( L2 u( a4 F5 i
  18. 9 G1 b7 }4 B9 W. f3 F
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    / m- @2 T  d& b8 m: [

  20.   g4 u3 M1 _1 n$ \2 u
  21.      s_command.Address           = SectorAddress;/ M% [3 c6 g* F# f% F5 C9 `0 r
  22. . |8 Y" \. C1 p3 n! c! A# y
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    0 }: o& O# q4 A5 ]4 E0 k% P
  24. % ?1 W2 E3 `3 J( h
  25.      s_command.DataMode          = QSPI_DATA_NONE;2 l- k7 K6 ]5 ^9 b; P2 K

  26. 3 D  x) U- ?- }% `' b
  27.      s_command.DummyCycles       = 0;4 ?' a& e0 `1 t& o+ S9 T

  28. 8 e( Z, _* J2 F# f% B" t$ T
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    2 M5 H- r& A4 t4 j* N6 m% c
  30. - U$ B+ }# E+ k  W, D
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  R3 }/ a6 ]- Y( U: O

  32. / d. G3 J- E/ H8 J  m* Y9 c
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;. x' J& J. D* j
  34. 2 x7 I( r5 I8 ~3 W0 K% f7 w
  35.      /* 启用写操作 */; y$ T0 I5 I8 f0 b8 z
  36. 6 i- [9 `. M8 Y! Z# ^
  37.      if (QSPI_WriteEnable() != HAL_OK)
    5 J* m& x+ @0 ?5 z% i7 t
  38.                  {3 t& X# W4 {7 ^$ O2 H
  39.          return HAL_ERROR;* G/ \& G- Y8 ^% B2 h0 \/ J
  40.      }: }# s9 d% N* w& c) o# F  C
  41.      /* 发送命令 */! _0 H/ `1 \1 e' K4 X6 K8 f- B
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    4 Q8 P: D3 Q' _/ t1 \
  43.      {
    & ]2 l% o7 ]' `& n6 e! D* k# R
  44.          return HAL_ERROR;
    + W0 g7 D9 Q0 ~- W8 E
  45.      }7 Y/ v0 e8 V8 R4 |
  46.      /* 配置自动轮询模式等待擦除结束 */
    " u/ \- Y7 P2 T1 A+ N
  47. 4 V2 I& I) z( k( H0 e6 \/ z
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)8 A) i! Z% t; p4 n" q& ?% n' T1 [4 ?: D
  49.      {
    % e$ W6 \7 ~# A; B8 M# d7 a
  50.                                 #if  QSPI_DEBUG' E# L! `9 S' Y4 s; G7 Y( d
  51.                                 printf("erase sector error!\r\n");3 F2 T  i6 c3 C' w* b, R
  52.                                 #endif
    . ?" A! p4 [" ]2 _
  53.         return HAL_ERROR;
    ; Z7 R+ I1 g! x4 [
  54.      }
    1 G2 ?/ E2 D# v% `; l
  55.     return HAL_OK;
    + ]# C; {" l' w  l; {' V# e
  56. }
复制代码
; t. Y+ c8 O4 L2 |" ~; s
第二种情况是全片擦除,时序如下:3 W! M- K. \3 y" W/ A: r7 d' D
; H. G: C1 o2 K0 r6 J8 h% C# S& Y
20191006140919805.png

, `3 a: m) H9 T' c! U" w, k& C& K! x9 b
全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。
0 P% v4 _( K3 r; x+ R' G, B7 e) _  o0 V9 O* B- x
  1. /**
    6 B( }" i. w: Z$ I8 U8 O/ a' c8 m
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s
    ! ]& E' H0 B  |9 E0 e1 ?1 g
  3.   * @retval QSPI memory status
    % j: t3 j/ B. R# k" y* w
  4.   */
    ! r, z/ h  L( m' w4 b- R# I
  5. uint8_t BSP_QSPI_Erase_Chip(void)/ X! \7 a4 u' \, B" [. u
  6. {
      n' F) Y2 G7 c- T, s7 N+ {
  7.   QSPI_CommandTypeDef s_command;
    1 g1 o6 k. _, s3 B
  8. 0 z# Y6 l$ P1 O- D+ p
  9.   /* Initialize the erase command */4 Q; }9 i6 k1 v1 T% e. Z
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;) W( ]$ |2 {$ h( g: r% n
  11.   s_command.Instruction       = CHIP_ERASE_CMD;
    4 j( m" u3 a+ Y  d. Q4 g: ]9 D
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;3 D& p" D6 X* l% U% N
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    0 u) d, ?6 f' X, A+ Y
  14.   s_command.DataMode          = QSPI_DATA_NONE;+ Z# {: o8 b9 x* `. I- I
  15.   s_command.DummyCycles       = 0;
    9 `' t1 @: w2 D8 N- R8 R$ U; \
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;* X9 m3 a8 V3 z; Q& N" _# B
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;+ F) d! b# }/ ?) X& y; x$ q
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;( j: k1 q8 Q0 k3 b* g
  19. & d. W- {7 L5 y- q4 R, A7 B
  20.   /* Enable write operations */
    2 Q+ M& o# p5 a' K6 x1 t0 m% Z
  21.   if (QSPI_WriteEnable() != HAL_OK)
    ( y) F2 d, O6 E2 w+ j5 V
  22.   {
    ; H4 h7 L8 @+ U% [0 ^* V
  23.     return HAL_ERROR;( B5 T! f' Y$ a* m' o; J6 n2 n4 f
  24.   }
    8 O. r- `3 f' [, [( `* h; L
  25. ' Y3 {3 k" L$ h# X) Z8 Y7 U
  26.   /* Send the command */
    6 E" g! ?. F+ b" s
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    9 P  B: ?* w  e- y9 F# y
  28.   {* x. D4 M# d& s/ }6 x
  29.     return HAL_ERROR;
    0 w9 `0 Z, u+ C3 G$ \% ~# m& r4 J
  30.   }: b/ ^/ t0 l2 w/ K

  31. ' k) R# c, Y: ?
  32.   /* Configure automatic polling mode to wait for end of erase */  : L" n6 L2 a) g6 Y/ G4 P
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)
    ! ~2 z+ G- H7 H# E6 q
  34.   {; S% e( i+ ~3 ?+ k% M/ k* D
  35.     return HAL_ERROR;
    ! p5 {, v& n; r: |+ r7 Z. H
  36.   }
    9 N2 I8 `2 f3 i9 y( L7 l
  37. ; Z& J& S8 k  o, ?% h) J6 q
  38.   return HAL_OK;
    / g' ^* |' G* r: Y3 y
  39. }
复制代码
0 [# d+ }; c- r* X5 K
9.测试程序9 Z1 y3 c5 [8 A. s. j/ c' X2 g

0 K, F" y3 O6 `6 d$ R3 t4 D测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。+ J3 C+ \3 V2 Z, E

, o2 ?9 s! o" p2 Y% @4 f
  1. unsigned char write[4096];) E4 k) R" d7 c
  2. unsigned char read[4096];   / s1 V+ t% ~1 h; t, R' Y6 A
  3. void N25Qxx_Test(void): N0 S7 a* g: r7 [  t  A6 t8 l& X
  4. {! b7 F0 x5 o- f6 I$ f( ~4 k! i- L: O
  5.   uint32_t i = 0;
    ( A9 Z( X/ P& R( R4 O+ T
  6. ( A) K" s+ s; g; m+ @
  7.         for(i = 0;i<sizeof(write);i++)% n& k& v% p# o% q
  8.         {
      I* |: R; M1 s
  9.     write=i;) H) y/ a' m0 u- U' Z) \4 ?
  10.         }1 g) S0 g' E/ {
  11.         
    - E, H9 g3 D; N
  12.         printf("write data:\r\n");        4 X/ k1 X- s( |% }' r: d" A# z
  13.   for(i = 0;i<sizeof(write);i++)* p2 J9 y0 ]5 b( t9 U
  14.         {( V/ S, j4 `; ~1 }! y/ ^
  15.                 printf("%d ",write);
    8 ^- k  l8 D; C4 Y/ E
  16.         }7 x7 J8 O8 p, _2 g! Y% o; Y0 P
  17.   printf("\r\n");
    2 d3 a: e8 I: v/ ?3 D
  18.         printf("\r\n");$ ?1 E1 W& c8 `; U: j; c& ]
  19.         printf("\r\n");, p6 O  w5 |$ W' p( H5 S
  20.         
    ) b7 b# q( c* l/ q2 M
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
    7 q5 ]  [) M8 p. Y; m# @$ O
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        $ q2 }4 s' n! S8 t5 [; t
  23.         1 c( I+ G3 J3 X5 V% r
  24.         printf("Read from QSPI:\r\n");
    ( {/ c+ H- \1 l
  25.         
    9 u: J, {/ Q7 r# E) q8 c* _
  26.   for(i = 0;i<sizeof(read);i++)( z# d$ {$ k, b$ V7 f) I) s: e
  27.         {
    - ?7 D7 ?  p  j
  28.                 printf("%d ",read);
    " _0 ]2 [  |, d
  29.         }$ m7 i8 {1 V' q! w6 ?; e9 n
  30.   printf("\r\n");
    7 u% u  ?3 H- L; K
  31. }
复制代码
5 x) Z( K3 [' z3 r6 K) M
写入的数据(部分截图):
, s; v6 X" i; b; R- `( J# J, Q7 W9 `7 Q( b
20191006141636369.png
# K# [' q5 }, m( ^4 |) y) ?( V
  @: w( {9 t9 c
读取的数据(部分截图):
" S" d: Z3 l, K* U% _
& R; d+ d  U2 `0 W6 y% ?+ q
20191006141537697.png

  @0 p2 l5 Y+ D/ O
7 Z2 X7 V8 }' k# m截取相同的部分,两次数据完全一致,写入和读取成功。
2 z) Y' ^% K; z
6 x6 w. [! l/ Q( L最后,讲几点需要注意的地方:
! {6 E* V; ]( X1 D9 \
7 Q0 U+ S( x- \; t; i% W1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。
# w: P! {$ B' v( j, |' G7 T. X& z4 i
2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。
' X: o, ?) [& P6 ~
' f: q- Y8 W: b6 p, K! U% E3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:
2 u( r* t1 s2 d/ e: g9 E$ {* \& F  ` 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
# _" Q. S' J- l; } 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。
: h5 E, l0 K# O! [" g0 S9 c
% K! Y5 ]: N2 L- s4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。
# j7 I# i3 z5 k7 z
0 o6 a; h; I: ?) P3 b5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。/ z1 b  y/ D0 f4 d
- c5 t$ [3 b" O4 K' X
20200316162249870.jpg

+ H! H. l- e9 ]9 O+ E  H7 O& o( V: ?- g" }
6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。
" C& G  L5 k. m! Y( y4 b4 |$ y7 \. D9 B; D6 E) o7 y
7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!+ r7 d" Q, I7 ^: \- p7 ^" f
$ c- j2 n; h" z
! I) v$ _0 i# a

8 d& [/ i& w# f- b- \
收藏 评论0 发布时间:2021-12-16 21:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版