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

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

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍4 z( t( Q% u! A& Y% W- @4 H
( ]4 u  I/ _- b; Y! J
QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:3 D" ?6 l0 ?" x/ G0 d4 B" S8 Z

- r* r" \% t  G7 r: p" U 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。
7 c, }- a( \) ^! d8 H& X2 e' x. a, C) T& {+ D" x( p9 ^
 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)# t1 t+ i! ?  k4 b) d+ ^" z- C

- `" [' U5 D# H 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器
" Z( f8 B# X" B% E4 b) C2 W+ s6 }6 q& q3 b/ A! A# m( N2 |
采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。
, Z: a. |# m* F: t, H
) H, @8 z% a4 e$ f内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
( O, F/ r* F0 n7 Q( _* Y) [- A& ?0 m  [
内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。
5 M/ L3 P7 R( r8 `+ l/ _1 V3 m% ~6 c$ f" P, e& u& D
二、 STM32QSPI 功能框图1 }1 L. V! q9 `' c, B' Y

5 }* R6 A% Z" l3 Z
20191006091636349.png

. G+ ^% ~% q+ O. ~/ Y
( I% \! R5 y- W% r) t* TSTM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。
. P$ |5 Q5 H+ T
' W: e2 c" r% F' t9 k: C三、QUADSPI  命令序列
( X) @% t) h  {  k! e$ h8 @3 ~2 Q5 E
QUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五
/ t" m! W' N8 x1 X( z) D个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。
8 P% t: N3 \7 R# n7 unCS 在每条指令开始前下降,在每条指令完成后再次上升。  s8 J4 T7 m2 [6 k

" p5 u) e% P4 J/ `# M6 ?- j
20191006092242666.png
! g1 v2 G2 M$ p" b: e6 W1 u
  J, |, U& ~, E. |! s9 P4 o
上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.
2 }( G! E" Q1 J% `  _" ~- f
) k0 D5 C, m/ A7 S交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);
9 |4 ]: R* Q( ~; s3 `' N4 x; j" Y( s" p9 X4 z  B) G% U
空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高
# D) Q) k  {2 K4 m2 {, ~& L+ w+ T的时钟频率时,给 Flash 留出准备数据阶段的时间。
9 [: p! m; M8 R! R1 b' o! z. n$ T若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。: z( a: M5 }) P2 h
3 f5 O  I) N1 \4 h+ I
此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。/ S( z; p. Y* ^; o# W  X
6 p' ~$ O- V& U7 S) n
四、QUADSPI  信号接口协议模式
9 c  G7 K, v/ f: `7 a; e) M* y8 K/ s+ e& `# S1 D' A
下面提到的几线模式都是指数据线的根数6 j$ G0 _8 E+ Q6 ]" O. L
. `6 W( `8 m  I6 D
单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):
- m" c" I( M4 \3 c$ i0 m8 s传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与: |5 i3 H. _% \" F! B
IO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。1 F' S! n7 ?$ T: r" E+ j/ f
通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可5 i  @3 W9 z$ u+ ?
对不同的命令阶段分别进行配置,以使用此单个位模式。
% c. c3 o: J" Y; W$ x5 P) i& p在每个已配置为单线模式的阶段中:
0 v& L) I4 L; T- V  b IO0 (SO) 处于输出模式
0 y0 e0 p/ ]1 K0 } IO1 (SI) 处于输入模式(高阻抗)
; T5 c# K. G5 U IO2 处于输出模式并强制置“0”(以禁止“写保护”功能)6 g, V$ d9 |6 `+ y9 b
 IO3 处于输出模式并强制置“1”(以禁止“保持”功能)
1 s& _0 H/ F, |4 U
: {4 }, O8 H* S7 J' E双线 SPI  模式:
3 ]! y- Z: R' ~: |! \在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。" B2 Z8 P4 R' O3 f
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可9 p: A" o5 b9 V  _6 J! N, E
对不同的命令阶段分别进行配置,以使用双线 SPI 模式。
5 f5 u- V9 l& u5 B$ A1 B( L在每个已配置为双线模式的阶段中:1 q/ u9 f2 g9 A  d
 IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出& I* T  a; M$ p2 _0 s2 |
 IO2 处于输出模式并强制置“0”
, l9 @9 O6 Q; [" j7 O IO3 处于输出模式并强制置“1”
8 j2 B$ z) {" U8 l在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。' h7 S) T6 k0 p! B5 N( K
' K* v* s- b9 Q3 ]5 u4 Y0 ^8 a/ s

% c: B; F: H6 o7 j8 W# j四线 SPI  模式:( H9 V" [  n& @% O# {6 y2 {* R
在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。, a! R! J. A0 H, e
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可. D9 d7 D2 ?3 Z- t9 R3 C' O
对不同的命令阶段分别进行配置,以使用四线 SPI 模式。: X( C" a: ^3 J/ C
在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高4 i! n5 e/ O9 Z4 X
阻态(输入),在其他情况下为输出。
; [+ A. g. i) U. C9 S! y1 T在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
* b5 J9 d) Y) k* eIO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI# J+ ~! q, U1 a' |3 c) p' Y
激活,对应 IO2 和 IO3 的引脚也可用于其他功能。- e5 S$ ?) \$ Y5 {" H2 z- j
9 g" a+ s$ _4 v8 _. f+ A/ t
" H) N' k) t1 T9 U% X+ U
SDR  模式(默认工作模式)
- v  Z  ]3 W% \6 V& W2 w默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
5 `+ T9 C$ k! g在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK
) P0 G+ E# F$ X' p3 d: P的下降沿发生转变。1 V8 b/ s! J1 D$ q8 R# _- S
在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
* v! B( D! k) A况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。
6 P! z7 H* ~2 w" m5 }# \& I0 B1 n7 P' b8 C) R3 y" n
  e! b2 {  x3 i5 h
DDR  模式
0 n8 }- k! ^. B" J5 t9 [' g在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信6 m3 L: Q7 ^. j
号时,将在 CLK 的每个上升沿和下降沿发送 1 位。1 d4 B; Y! R# N: |) @
指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。0 s9 T% C+ s6 J8 ?$ k
在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数! O* x9 ^4 q+ X/ v0 I" ~
据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期
! U  j) X# s+ f& u. I后(下一个反向边沿)对信号采样。5 f6 K) ?' w- C$ i

- B/ P$ q! b0 U" r, m五、QSPI 配置(HAL库)
- n3 e1 Y8 x4 v
3 {. U3 ~. q7 U$ a! o1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟
) J1 Z9 k! D7 u3 z" P4 ]) |' ]2 ^, t
20191006121236849.png

; l" E; ~! c; p* Z1 N! S+ W! Y. O2 X
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)
    ; b. U" T9 f" X: B% y7 j
  2. {
    - Y0 U* x1 u  m( i6 e7 T* J5 Y4 m
  3. , I2 q2 i; y# N. i
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};5 q& J& x1 a+ Z! W
  5.   if(qspiHandle->Instance==QUADSPI)
    % C% P: Y4 W" r& B7 m) G2 L5 {, k
  6.   {/ [$ ], d# Z1 I' j- d. q6 I
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */! k  J9 \6 G  D) _

  8. ; P) h  [8 O- \% C
  9.   /* USER CODE END QUADSPI_MspInit 0 *// J0 l& ]+ t8 t0 b' P6 ^0 S
  10.     /* QUADSPI clock enable */
    ! v2 d; W7 J% a
  11.     __HAL_RCC_QSPI_CLK_ENABLE();, H  M. k  d. }
  12. 7 }; O" P: N; A" U6 D
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();  `* H- i+ W: P" @& b  K
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();2 Z; v' _; U! _
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();* E0 {, ]" \3 c2 l0 y
  16.     /**QUADSPI GPIO Configuration   
    * h4 f9 a- a0 {8 Z! [
  17.     PE2     ------> QUADSPI_BK1_IO24 y) U6 @9 k8 {) r
  18.     PB6     ------> QUADSPI_BK1_NCS! ^3 y6 E' A& m: P6 P# j
  19.     PB2     ------> QUADSPI_CLK& |0 t" _. s2 n* A, n
  20.     PD12     ------> QUADSPI_BK1_IO1
    ; i2 v/ X! r4 N0 |
  21.     PD13     ------> QUADSPI_BK1_IO32 G5 Q. L/ G$ j* {0 n% }
  22.     PD11     ------> QUADSPI_BK1_IO0 ; K$ {- p& D2 {3 y" J: [# }
  23.     */
    ) Q6 @  i  Q3 q+ V3 {
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;/ J2 y  X# s( Y) v
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;5 c/ C: r7 P( o5 w3 n
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    0 f7 h- v5 a" C/ F7 M" m
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;2 G2 L& k! A7 @% H3 ]. W4 b3 s
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    % u, [1 B% ?3 H; l3 K* a
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
    + R$ T- z% h: Z. m  b8 C5 w* A4 r% r
  30. ( B* i& K. Q; P9 Y4 [; y9 Q: }
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;( N0 o% o% O5 ?- v& a- x
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    : R, h, `4 B/ p. n4 o
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    # [; O, t( ~; Y. S9 Z! i
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;2 j# \/ p/ ^4 }3 O
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;: o+ x9 ]* b1 o( B+ f6 s
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);8 ^7 }5 Q1 Y  c& d5 V
  37. " I5 q5 h) u2 a' o2 U9 p5 B, U% T
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;- ?3 v0 c) K8 h: j: B! r9 |) c- y7 n
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    * q$ E! }9 g& j% T* x. k& D: w6 W
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;& V+ j3 A$ h$ u9 w2 V
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;3 c  g+ M4 X8 S# k5 d
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    5 G- J/ [5 Q6 @3 J
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    . N1 D9 e$ D' ?" H7 [
  44.   P, |" ?* b) P) t& f5 N
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;# U4 I* F+ ?+ w* U
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    % b# s7 X5 M3 D1 W6 j( g  o7 Y1 W
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;1 s& A' b0 E, s" o! F: L( _! T. T
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
      Y5 b# @+ V1 t+ e8 s
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    ) I& y: E) X' t' @
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    ' E/ U4 \: W( x  i5 W
  51. 7 V& m+ o$ [0 m! p7 m, P
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */( i+ u& l& G4 h, F

  53. . n  Z' H$ r* g
  54.   /* USER CODE END QUADSPI_MspInit 1 */
    3 ^% d1 m) ?8 s1 }" v" t3 r
  55.   }
    3 V6 S& j" x  k; F* k7 p0 R
  56. }
复制代码

7 q* Z7 Z* z  U) O9 Z7 d2.接着初始化SPI外设
% S9 V) G2 D  B. z

  1. ( d0 M. U0 k* q% R
  2. QSPI_HandleTypeDef hqspi;0 e1 @" J7 \5 J& E
  3. 3 D; a; r3 [, S! z) M1 V
  4. /* QUADSPI init function */
    + H1 L; j( O; z8 x; R# N! F
  5. void MX_QUADSPI_Init(void)3 q  T& A4 w. s6 r: c; l9 D
  6. {
    " a. _1 q0 l; Z: n' G8 H
  7.   hqspi.Instance = QUADSPI;//QSPI外设
    - p) q) b/ `+ P
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
    0 J3 i$ O# s. T
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
    / x9 B( H3 D5 w1 w7 f. ~/ Y9 G$ i
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)
    - V6 `+ D- Z1 i6 I1 \! U
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节+ l7 @/ A$ G. S3 c" R( E7 ]) ]
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数- J; v! e) R/ a( }* T& @: k4 E6 J3 \
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0' f- d; u- v7 W5 Q. F' u! M5 r
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
    / x  Z5 e0 @+ n& y; H1 e' I  Z7 C
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式( @, J; {% H+ i3 a$ U8 G
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设2 D, ?$ g$ o  ^( D' s; B' S& t
  17.   {3 p4 o/ ]5 N" N8 z% ?# \( Y9 j
  18.     Error_Handler();
    # D7 N( ^* E# C! x0 Q
  19.   }6 d2 ]# c  b' {  ^7 y1 {2 c% h
  20. }
复制代码
) W6 e( `- J# j* D/ {6 M
3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。/ `- _' X2 |8 t; H

& O" K5 D7 C4 h0 j我们首先需要配置下面的命令参数结构体:1 }, r2 `: H0 ~+ \7 G$ |% ]

7 H0 {: C1 Q, F# [6 z- K- b3 ?, W' g
20191006122437823.png
- C3 H( i5 x$ p5 }; X6 R6 w7 z

3 L- @# T; w% n  H0 ^/ o; h* WInstruction:指令类型(读、写、擦除等等)( ]% J8 p, T# d7 c

$ b/ a. Y  W1 {+ hAddress:需要读取或写入数据的起始地址
) z+ _. j# H8 o% D" T/ k. b; ~& T% x1 a9 @2 U3 R
NbData:需要读取或写入数据的长度1 r$ K0 d2 A1 q5 j- {+ H5 P  s- G

' f! t' O+ w6 t; z9 WDataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。$ Y8 l" F+ G% g" L& U4 h

5 t' g1 v# h5 E这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!
3 |$ Q1 f# O( A& p3 y  ?( ~4 D( O3 Z/ D7 y/ ?+ o& l
4.读取芯片JEDEC ID
2 `- W  [  Q2 N4 z+ X  L; p7 w( r# k% l! ^
首先查看手册上读取JEDEC ID的时序:
: {, x0 T$ v* y, X1 }/ ~
3 R8 @) i& \2 B2 h9 b0 x
20191006131644114.png
8 @$ ^- Q3 o! d6 A5 [

, {+ ^8 A  z$ i( z& J& y1 S" N可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以
& b8 W4 }" ?, ^* Z
/ m% G; y; [  p. ~0 V7 D, d2 ~InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
4 x- d6 x& n3 E- \2 i6 K7 j5 c$ y, [6 ~2 k, @
DataMode = QSPI_DATA_1_LINE;//数据单线传输模式, I: G9 V6 [4 e/ m
/ k5 G6 m2 w. i, C6 W8 n  T
Address和AddressMode 这两个参数的值都置0,表示无地址。. b2 k) y/ W1 u  w2 }
8 k/ d6 ^$ S/ A& P! H/ T
NbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3, R. F8 |5 X  V1 h' z

/ a- v  f1 V; N4 W0 P. ~- ?, ?其他值和下面代码保持一致即可。 % r+ |) _+ ]5 m! q1 w/ F

/ D- E% p6 |" O9 @! \* o然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成5 C# R) N. s& F9 U( G" V

! P$ T) P9 H$ ?5 R1 i5 j/ e4 Y最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID1 z  e0 M3 v0 s& G1 Z. p
2 L+ ^$ K& m9 Y4 h
  1. /**( B6 o( d( I5 e& ?
  2. * @brief  读取FLASH ID2 E8 ?$ K5 B" F( J) L
  3. * @param   无
    # g) I! h4 V9 q, a+ v
  4. * @retval FLASH ID
    3 A/ z& V! {/ V/ q) \# r% P
  5. */) [3 A: g- e0 }$ `8 Y7 Q
  6. uint32_t QSPI_FLASH_ReadID(void)4 K( r9 a( |, p# n! D( A6 f* J9 i
  7. {: \# I4 t, ^" n6 ~2 f
  8.      QSPI_CommandTypeDef s_command;) m/ C9 Y. U9 m, t. l6 X0 ~. o: K# V% Y

  9. / }$ \& x" M0 p7 H1 t
  10.      uint32_t Temp = 0;( g% R$ |  f: H$ s. x5 B

  11. 7 T" ]9 n( l* ^7 v
  12.      uint8_t pData[3];4 E) @$ g2 C; ~/ l
  13. ; O# w; S8 h$ P
  14.      /* 读取JEDEC ID */
    $ ?7 g6 W- x6 f: E) v$ Z6 o" h

  15. ) J$ C. l6 w- M/ ]1 X
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。
    ; Q! h% ?8 H3 e$ D! B* C) c

  17. ! x, D( I9 ^6 Y; ~; i* h
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令
    5 ]" q: T) }  J
  19. / G  s0 o: g2 Q1 ~& Y+ W
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度
    ! c: g+ L# N- l# `0 T& z. y
  21. # K2 R7 {: k, b2 Y9 M" N8 a8 g  Y
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。
    8 k3 \/ Q% _5 e3 g+ \) P

  23. 2 z9 d7 Q$ C9 Q( j$ V
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。0 v; v5 m& Y* w% H! {
  25.           * e" G0 P+ q& L
  26.      s_command.Address           = 0;   ' v" j/ j- K: z* f; ^; D2 h! L
  27.         5 Y- B' C9 Q( n' j. U2 b1 j% n
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度  \4 v1 m. a+ ?" w# ]

  29. 6 i: x* T3 H' e- {
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。
    ( n. }5 R- A+ C  r
  31. + u, T+ `. B" Z8 G$ e, L- q
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。
    $ z' v3 R& z1 T: r
  33. 6 f6 j2 ]7 t/ x3 s8 H
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。1 |1 W' C3 m% t- S

  35. ( h1 _! H( [1 Q# d, E# w1 y
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
    7 v3 G+ |8 ?1 l+ c
  37.                  & I( C7 b' c/ c9 k+ I4 ?8 {5 _
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。
    9 K7 V7 p  b+ g' P) {. H- |

  39. 5 b! H5 W' H4 D; Z+ w9 o
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)+ Y5 M& {" s% F9 v# B
  41.                  {. u0 I0 @- D5 g: u
  42.          #if  QSPI_DEBUG5 {; R2 B( r2 z' z
  43.                      printf("send read flash ID command error!\r\n");- T4 [; A" n( ^; }- Q7 @- b
  44.                #endif! u6 j7 \" m8 z
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */, B- T3 j$ u( f1 J6 z6 W
  46.          while (1) {}* G! ~8 ?# ^" b: x# y
  47.      }! R) r2 a, K+ O- A
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) 5 h0 _9 r  p: e' K' B# ~+ [( x
  49.                  {0 \. o/ f: A5 Y' f
  50.          #if  QSPI_DEBUG
    ; [; [. d3 @9 d  s! D' _: l& [
  51.                      printf("read qspi flash ID error!\r\n");0 `& n* Y- q5 ^7 [
  52.                #endif
    ) e9 p/ _& E9 b) r) P0 O; X
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */
    & A- v/ T1 R8 D" B& e4 \; |8 `" q
  54.          while (1) {}( Q' H* B$ F4 ?; y+ {  N% E
  55.      }! |9 w$ g7 `" D8 m, T
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );+ P/ U: R8 E2 p5 L% L
  57.      return Temp;8 q- y; g4 ~4 j0 C# o
  58. }
复制代码
4 o, u- [! I4 _8 q4 |+ \( D4 ]
5.FALSH 写使能
' T3 K) a$ q# }: K; U0 y8 ^, |7 S
, T' Z. C8 H7 r- A) e在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):& h" z( [6 d5 d$ G! |# K4 j
: O" C! b+ _8 \6 W9 ]1 F1 r
类似的,需要查看芯片手册,写使能的时序
( K# T- @0 D% G/ O. c2 g$ E" B9 U5 c# B) k
20191006134057767.png
' b" _+ e. M6 I# k7 Z

2 M8 I$ y) {& q" r9 I, a可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0 % B% i, S1 o1 q+ \9 B" X
' v4 p6 g0 z1 c% D
  1. /**5 a" `. l% o9 ~$ K
  2.   * @brief  QSPI FALSH写使能
    ) z3 W$ G9 i, A* \4 I+ F
  3.   * @param  None# t( D# e, g' o/ [* l, ]
  4.   * @retval HAL_OK: c9 w$ O. _- b& w
  5. */' Q( w, \6 ~8 l4 d
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)1 G8 l: G5 v" I8 @
  7. {
    ' ^# ~+ Y- ?- I* @5 c" U& C+ G
  8.   QSPI_CommandTypeDef     sCommand;
    9 Q5 \' r4 J! }( m1 H  x
  9.   QSPI_AutoPollingTypeDef sConfig;
    9 Z3 p2 g( o- \/ O* L7 b
  10. " c& V; c8 p2 \. @" b
  11.   /* Enable write operations ------------------------------------------ */
    3 g: [* @( l2 k5 _: p
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;: o9 B& Y( t. C9 C
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;: h2 ~+ b# D5 H5 F& t, S
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;
    1 y9 P0 h4 {- W; L
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;, G; R8 P' y0 N
  16.   sCommand.DataMode          = QSPI_DATA_NONE;, ]2 g) C( ]) W( `( h
  17.   sCommand.DummyCycles       = 0;7 ]& Z+ a, k. t+ _8 W7 B0 y
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;, z$ H& M8 \8 r$ _
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    2 R, ]6 G4 q) j7 h' x- f
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    ) w8 m  L! I! |3 M& R

  21. " W7 x+ _+ K- A. }0 L1 X" Y
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    7 i* I0 g5 Y5 m3 G) f! Z7 `
  23.   {6 k8 e; \0 O5 Y
  24.     return HAL_ERROR;
    : D* y% v' `. C# t
  25.   }- Y* T" T% c& C$ H+ Z$ t% h

  26. $ f0 y9 Q3 k9 V4 w
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  
    6 L% e! \* A. K. \  A! R
  28.   sConfig.Match           = 0x02;
    6 m3 q# Y( b0 c& @$ [" I
  29.   sConfig.Mask            = 0x02;  y& C4 U! X. n4 ?  V
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
    ) K6 b3 ^+ k3 S* \" I7 X1 b
  31.   sConfig.StatusBytesSize = 1;, Y. c/ C1 {# v' q) @/ V
  32.   sConfig.Interval        = 0x10;) f6 _: x- \% q: }( F6 C1 z
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;$ R& v" ?5 ]( _1 m. K
  34. 1 [. S5 w8 }" j  C- K" T3 X* @# y
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;0 U" e. K% r8 G- L; L& B
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;6 _# y) i' u8 A- {9 E% r

  37. 7 m( H, i; F4 N6 s3 \
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ' P8 l% t9 k" \4 u
  39.   {" S. r6 i) E5 n; Z+ i! D0 ?6 m; W  f
  40.                 return HAL_ERROR;        
    6 f# V) `% e( b: J- I) {
  41.   }
    + ]9 D9 N1 F7 _" j
  42.         return HAL_OK;/ Y+ o; j6 q/ n/ W  z
  43. }
复制代码

& a7 s' A' }$ L7 e) e( r这里需要讲下 QUADSPI  状态标志轮询模式:/ O: y3 Y1 _* V* u
在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。
$ ~1 w5 M4 d* `2 K1 `$ R8 w9 ^可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。+ D4 x& ~/ A* `7 |! g( O% a
对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入& J; Y8 Q9 P- H. D7 k! ~3 ?/ s
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
% ?6 V5 W! b/ L1 |$ z一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。$ e4 w- T& Q) [9 m7 O$ \
在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。
) f/ [6 k, Z8 m+ h: k5 F如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内
: j( p/ [. J" ?' b# k, N容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。
4 U$ o# l% Q6 E! f0 r8 S0 A如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着. v$ `# H- o( V
状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。) c; \5 B6 P3 x2 v
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。0 D5 c( n+ ~8 H' X/ a
如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。
( z2 M$ k: x. m如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否! F5 C/ z& ~6 T, @1 f) Q9 o
则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性
: M4 S& s( y) Z% ?9 @访问。5 D; `4 _0 s$ \) G5 x, N
数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不
9 p5 K( Q: k4 C8 m' R- m7 s/ e受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数
8 M& w: D4 u9 X+ z: p/ V据读取后清零。
4 @. x6 I3 Y+ x8 s- A7 u3 d1 D- P) D. _& |8 |
简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。
0 [( p2 C* R$ `; o. p  I9 N
$ p' k. ]# T" W2 H4 U% h8 H6.读FALSH% w, U; h3 Y) D9 i$ C+ K8 \

  f6 l/ o' K- n这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序' t+ m: U' d$ {5 Y% f
8 j/ u- [7 @" P! E  U
20191006135244693.png

6 N# j5 n" |/ E
& s9 b* i: Z3 G) C# R从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:
3 j  [6 E9 q" a9 l0 J. V% R' m+ N0 ?3 V- L0 P' g' R
  1. /**  c6 S8 ^& m0 d# z; G3 P
  2. * @brief  从QSPI存储器中读取大量数据." u' R1 {3 G# i# T
  3. * @param  pData: 指向要读取的数据的指针
    & i6 C8 l0 m: @' q% g6 E
  4. * @param  ReadAddr: 读取起始地址/ f5 b0 S( w( p, D
  5. * @param  Size: 要读取的数据大小
    * l) n; P  F2 w! W: Q# C3 y
  6. * @retval QSPI存储器状态* u8 V7 @  U* ^& B4 s
  7. */
    7 X% d1 N5 @# r' ?: @5 I
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size); E$ z; a: c8 d1 S: H3 E3 ~) v: f
  9. {
    % M+ e3 l8 i6 L# W, ~; K# C% X4 }& ^& |+ [
  10.      QSPI_CommandTypeDef s_command;
    - ~0 n+ B8 o4 x
  11.   F9 c1 u( H7 O3 K7 Q& r
  12.      /* 初始化读命令 */
      i4 E2 G9 o% @$ R3 b; y
  13. & g) p4 ~. r2 C( x2 q/ p& K+ G0 [
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    & ?% T2 C: Q9 M" ]; ^

  15. : \1 d- j! C% v0 q
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;
    4 ?) [7 N/ g8 n0 D6 X. d
  17. 0 V6 Y6 a1 N7 U; X# @
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;, i0 u! I; l* Y3 k* B
  19. ( O0 v" p# h% E9 Y) P- m3 _2 ^
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    / a7 b0 K5 z$ D* `! `
  21. ) E( P! D$ X3 y2 L" O5 F) Q1 \
  22.      s_command.Address           = ReadAddr;( [8 l5 C3 [. K( ]; x/ d3 ?1 Q) M

  23. 2 K; g, I, V" s, w3 G7 o9 g
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    4 A% e' E+ N9 x  F  S& W
  25. " D- S6 q$ [1 {7 A3 F
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;
    - E& {' q( [; k7 t% F: H

  27. 7 [; t0 B/ f3 `$ \
  28.      s_command.DummyCycles       = 10;& d7 S, o( t, P  [1 N
  29. $ [' n7 y) u4 Z; m- M
  30.      s_command.NbData            = Size;
    8 b% s( F7 c) b1 p7 T5 r
  31. 2 W" N1 g, x6 q
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    6 E3 U6 x: T# N6 H9 ^' j4 L  Z. D

  33. % ?  G% h$ Z( t" l
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;1 q0 r( y0 b$ ?, t" P9 f

  35. + l# }4 I# _! K2 K* s7 H
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;$ U/ C& H1 `9 ?
  37.      /* 配置命令 */
    9 k8 f, F0 ]9 ?- s6 u- O
  38. . Q% f: E0 a! N7 N) ?) g0 d7 P% p
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    6 f7 [( ^7 E, E. ?( v% P
  40.          {
    ( A! M, ^. Y) }! o% H. i; h
  41.                 #if  QSPI_DEBUG) t7 w- {: _& c; S1 M
  42.                 printf("R-send read command error!\r\n");
    7 Z7 ~$ M5 n. \9 e3 Z/ U
  43.           #endif
    & p0 i5 X$ M5 ^
  44.     return HAL_ERROR;
    7 B/ [" p. l# K# D0 c. I& n
  45.    }  
    8 e. [! o+ t8 L; x7 R, n. ?2 i
  46.    /* 接收数据 */
    + Q' ?! U5 B# Y/ j
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) - i% a! ^, Z$ d) U% D+ h
  48.          {7 d; d& I9 D- \# h; u/ F
  49.                 #if  QSPI_DEBUG
    / C5 b5 [8 F! H' N2 [) i
  50.                 printf("R-read data error!\r\n");
    6 ~7 |- v) _  @& Z
  51.           #endif
    % T8 t! ?- |. d' E( j' T
  52.     return HAL_ERROR;
    . r% {( A- j6 d
  53.    }. S: _/ m; h; s& m
  54.    return HAL_OK;9 L1 r/ n. y* n7 I
  55. }
复制代码
! Q7 O8 K/ t  |, Z; n8 K9 a
本函数实现了从FALSH指定地址读取指定数量字节的功能
0 T% H0 Z) S; {' T. U  O8 A$ f) K* f1 P- ]& [  |
7.写FALSH2 ^# j9 I4 g! X$ M; _

3 M6 B, S$ E/ y6 m' y同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:
* F4 m( c+ I" r- O
7 [4 b2 o/ p. C0 I8 g8 h
20191006135836784.png
8 ^/ n2 Q+ U0 Q
4 p  O" H+ i# P- G& \
根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期 3 C' N& i; U8 H
/ l# R$ Z5 g* f0 N. ]5 o4 Q: O
  1. /**+ i% r# P0 H0 ]( u8 L- x3 s
  2. * @brief  将大量数据写入QSPI存储器+ F# u6 X2 E1 h" ?
  3. * @param  pData: 指向要写入数据的指针. }* F9 u' V% Q
  4. * @param  WriteAddr: 写起始地址+ q2 E9 {& ], Q0 ~3 a/ x) @
  5. * @param  Size: 要写入的数据大小2 O7 o' [, f2 e2 A
  6. * @retval QSPI存储器状态/ Y1 O/ i% |& A, w' J7 }" P4 F3 ~
  7. */
    % ]" v. M" u3 i' _! w
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size); S% C; Q! M. Z3 F( G8 p% [
  9. {
    . _) n8 d1 [4 s
  10.                         QSPI_CommandTypeDef s_command;" F$ B3 k2 R7 Q( U
  11.                         uint32_t end_addr, current_size, current_addr;. N2 R5 V8 B- }
  12.             unsigned char sector_start,sector_end,i;
    8 {$ F/ \& r' n7 Z
  13.         
    % l7 Z' e3 v# n- _' g' r
  14.                         /* 计算写入地址和页面末尾之间的大小 */  I  Q% o" z$ O1 m
  15.                         current_addr = 0;, L5 t; @& k  f* ~2 o
  16.         2 m3 v& z5 o+ L/ {0 [0 N9 F
  17.                         while (current_addr <= WriteAddr)
    . l& S- S8 C, M
  18.                         {
    , g% r  K  R$ ^4 p  H# T
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;' N% ]5 v3 L6 u( C. k8 j& W5 E( z( h
  20.                         }% y+ o" H6 ?( W6 l" l
  21.                   current_size = current_addr - WriteAddr;$ R$ _6 Z' D: _' F; w
  22.      /* 检查数据的大小是否小于页面中的剩余位置 *// }. Q  {& ~# W
  23.      if (current_size > Size)
    & d& T- e# M7 J8 t, {# R
  24.                  {
    5 i; \5 `5 `2 x4 h
  25.          current_size = Size;
    : `7 x* b* l1 h- c$ j+ f) A8 |
  26.      }
    4 _1 G( K7 Y% H; y. h: K
  27.      /* 初始化地址变量 */9 g+ b# H  ^, o! N+ q
  28.      current_addr = WriteAddr;
    9 }: A7 ]; U9 N# B! s6 d
  29.      end_addr = WriteAddr + Size;
    ) |1 K# j, `5 x: [# \
  30.                  % g- y- N0 ~8 J5 V$ x
  31.                  sector_start = current_addr >> 16;
    1 m! O! ~; I4 Y6 I0 a
  32.                  sector_end = end_addr >> 16;5 ~9 }4 g% i1 K& ?- f4 ]7 P) U! h! m
  33.                  
    $ ^4 p" f& \& \2 |
  34.                  for( i = sector_start; i<=sector_end ;i++)
    6 \- W- O# t, B* r2 F9 e/ F
  35.                  {
    # m* P0 f# Q. ]2 |0 @
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    / v5 i2 j' s) \# S/ @! a
  37.                  }
    * a6 d- K' X1 H
  38.                  & }7 |8 X9 J6 O% B: [6 V4 R
  39.      /* 初始化程序命令 *// h' ?& D  ~5 u0 ]! D( Z! O! z
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;; T/ J- v, \! `; x  f
  41. ; N# Y/ s3 j! z$ C8 n
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;
    3 s* l  G3 T* g" H' r
  43. + ~& T6 M; w3 e# C
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;% v6 B- A  u& ]! s
  45. 7 ]. B0 l! d2 [5 p! }% x& }3 B
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    ) ], R; h# B' ^' W% w8 T" \7 N( a
  47. 6 I4 A9 M' f4 K
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    1 W+ y7 j( z9 U8 M

  49. ' |' F+ d8 P- C8 X
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;
    / o# t  D6 Z: O) L

  51. & t4 J- A3 c3 N& m3 Y7 c
  52.      s_command.DummyCycles       = 0;
    % ^: A9 l$ {; e( {( l/ a! j
  53. " E; I- L* G" ~) l" a
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    9 n: g5 z; [5 R, \
  55. & R+ l: q/ T7 i* t( x
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;! V3 R& g/ ^" S- j9 w3 X$ c* q

  57. / C; J1 E) M. _0 N1 h0 j: s6 Y5 `
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;& U( m2 B! T7 T- W+ {. M

  59. # a& ~  E1 D- D% f
  60.      /* 逐页执行写入 */
    6 q; ]: f8 l% f/ ^% Z4 }. I
  61.      do {
    7 _) G3 ?# g7 U' X* W. @
  62. # r1 \: q/ v: S0 g3 E* q; Q
  63.          s_command.Address = current_addr;7 Z- G9 L: q- ~, Q, [" ^5 _
  64.          s_command.NbData  = current_size;0 A" N3 g3 D) X% t7 i, B
  65.          /* 启用写操作 */, _8 ]8 \3 @4 _" K: G0 L1 m+ n7 g

  66. / D$ a5 n+ r. K
  67.          if (QSPI_WriteEnable() != HAL_OK) 1 u3 v5 F* O+ O
  68.                                  {
    ( y7 }* J) f1 ~5 d
  69.                                                 #if  QSPI_DEBUG
    - k9 f; U) |7 P7 v( R1 [, B
  70.                                                 printf("W-Qspi write enable error!\r\n");
    / G& F( X' ?3 l# Y5 n, h2 B' d# J5 R1 X
  71.                                                 #endif% _! d& m8 H+ h$ m
  72.             return HAL_ERROR;/ J$ F% E# C( S
  73.          }; b3 w) ?& H2 g. a
  74.          /* 配置命令 */, \8 o/ E$ W, G. |
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ! u* Q4 f% h. D
  76.                 {                                 5 ?4 r9 b6 B+ `0 m
  77.                                           #if  QSPI_DEBUG% f' j7 b6 I* j
  78.                                                 printf("W-Qspi send Command error!\r\n");) V4 X! j. M' `6 ]5 d
  79.                                                 #endif- }7 _; ~+ D+ a
  80.             return HAL_ERROR;
    4 |) [8 w! b( e8 f: K6 d) R; g9 p
  81.          }
    / N+ S9 q4 n. Y5 d1 T9 V3 Y
  82.          /* 传输数据 */
    ) H" d- l1 W% _% `
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    0 X, O+ z4 r, n( |
  84.                                  {; P/ e( M7 g. }, a7 S. k( ?7 d1 M$ X" s
  85.                                           #if  QSPI_DEBUG
    # v1 U, Y  }8 ~# g8 Q9 i% u! N
  86.                                                 printf("W-Qspi Transmit data error!\r\n");. C8 `2 R# @5 z7 B5 I9 I" L
  87.                                                 #endif
      P) M7 j4 W7 \9 c7 J% K0 S
  88.             return HAL_ERROR;
    - {& G+ _# f0 Q5 s% e+ @
  89.          }
    . T: X1 l7 m4 o; m8 N8 n% `
  90.          /* 配置自动轮询模式等待程序结束 */
    / h: X9 V( c  z7 v$ \
  91. . u7 z1 S. r0 P6 e# r; Y
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)( H+ j! b% [5 l8 |; q( `/ r
  93.                                  {                                         
    % N, k+ J- \0 h2 U% R" S( D
  94.                                                 #if  QSPI_DEBUG! N- B+ c, W7 W6 F/ k. o' V
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");" `( B1 j- a0 Z7 T6 @- [1 d& k& w  s
  96.                                                 #endif5 }  a% a; B* j: @
  97.             return HAL_ERROR;: I# m6 _8 ^+ q* d/ M
  98.          }  p7 u7 l+ t' L9 z5 e+ j- C; f
  99.          /* 更新下一页编程的地址和大小变量 */
    8 k' o* m# Q' T  T" t$ X
  100. ; [9 v9 K7 c+ p/ W" V3 J8 C5 x
  101.          current_addr += current_size;4 ?% G" _" u: N- s

  102. ; [; C( c% h9 `6 i; n. J% p
  103.          pData += current_size;6 Q1 F# O6 \  u3 P* l* W: h1 z
  104. . Z. [8 x0 [  a& q/ _4 N
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :
      L: L3 u) X! X

  106. 7 [9 ~8 z( s$ l7 }, \
  107.                         N25Q128FV_PAGE_SIZE;
    0 M! l, t( W5 r1 G6 V

  108. 0 C# P( h( }+ @' {
  109.      } while (current_addr < end_addr);
    5 ^$ m( X- o8 n( G
  110.      return HAL_OK;9 b+ D1 U" @" z
  111. }
复制代码
, H2 S) b6 J" w
该函数实现从指定地址写入指定数量的字节。
- H+ I6 l/ J2 b/ x; k; o' Q) @! |+ g9 k
6 ^6 C+ }' o  F; T8.擦除FALSH# e/ p! U3 s2 h0 X6 _0 P0 Y
! G4 N) M! f8 R2 j, M$ A9 R: q$ z
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)
3 _* C/ N2 {& r# v5 V9 X' P4 g$ s- N/ o' K% n" G- L* v7 C
20191006140420479.png
6 Z' B  @9 k: A4 B# a
4 R9 e3 c. e7 T+ Y% }" H! M8 ]6 V
参数设置为1线指令 ,1线地址,24bit地址,无数据阶段
  N0 w; f! h. C. @2 l) b
) g( L5 p/ `! x9 J' R9 q9 Z
  1. /**/ ]0 [. a$ B8 F, [1 `& x) P- a, L
  2. * @brief  擦除QSPI存储器的指定扇区! t! a' j; V: J$ |
  3. * @param  BlockAddress: 需要擦除的扇区地址
    # A4 P2 Y2 r, q5 U# E
  4. * @retval QSPI存储器状态5 l8 N3 @2 X+ A: Y
  5. */+ v8 d: e3 ~8 m2 f$ S  \
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)9 B( L. ?1 T" g4 i- r3 K
  7. {, z! U! v% k) D! [  }9 _% d
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址0 w/ {& `, r0 A) U0 f. q# h& q8 o
  9.         / S: l4 P* }& O# t# i( y
  10.      QSPI_CommandTypeDef s_command;4 E% I" L. P7 |2 N$ y) K4 Z
  11.      /* 初始化擦除命令 */
    3 R0 r7 R  W% D% s& M$ K0 o; m
  12.         
      U# x! x0 l  D# `( |
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;, E  r4 a; t" J4 A, }# H

  14. 2 S, [0 Q  d# R
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除
    " Y4 j9 A  S$ J5 O5 T( w
  16. ( {5 }9 d9 `8 W: J
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;! }5 [# z( \% ?  {; E9 V
  18. , m- g- A1 U: }' i
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;" C4 x8 P# N4 s/ b1 d5 @
  20. 3 E: k' x: ]. g
  21.      s_command.Address           = SectorAddress;
    $ H5 x# K. g( _

  22.   z  f% y6 K- _9 v2 V+ _+ _% n' S8 O1 c
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;! K! s' N0 i# `/ m/ g
  24. , S+ ]) @7 Z! f# H
  25.      s_command.DataMode          = QSPI_DATA_NONE;! A  e) a; x3 t, d  _
  26. 9 z$ i: p) u; C
  27.      s_command.DummyCycles       = 0;
    9 B& k( b  P1 j% X( G

  28. 5 Y3 s5 A$ B. a/ g  \/ B
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    4 H6 |  W! M3 O1 Y& T

  30. 6 g5 m- h/ r0 u6 N; \( g
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    8 X$ {# C/ g" K) ]) d
  32.   m+ e  v! @' {9 w) A# {* ~
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;- a  Z+ S' y- T2 A4 ]* z9 ^
  34. % D: _. S0 A' `7 P
  35.      /* 启用写操作 */2 N4 e" P( ~: e: U6 y# ^
  36. 8 u) \/ I9 [. y
  37.      if (QSPI_WriteEnable() != HAL_OK) 5 f& W2 j# w1 `7 e5 e
  38.                  {
    8 f3 d& p* m) @3 t- Y5 d
  39.          return HAL_ERROR;9 i0 v" @$ ]9 j+ V( S
  40.      }  ^/ s- I6 Z+ }* ~; R
  41.      /* 发送命令 */4 g; W8 U, d" S, e6 h; }. }: e7 _
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    : }# s( J4 Y! m- o+ X
  43.      {9 Y* l+ m2 `( C- o
  44.          return HAL_ERROR;
    / i5 U! E+ G( x
  45.      }
    % U; T( Z. ~; D" j
  46.      /* 配置自动轮询模式等待擦除结束 */
    / a: F" ~- [( N5 J$ F# X) S
  47. 4 O" Q" C- B# o7 o* E$ S
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)# h! b7 Y3 p0 a  Z) r' N  o
  49.      {1 F  Y/ Q8 g0 @: c8 N+ ?
  50.                                 #if  QSPI_DEBUG( Y' Q* |/ l) g& O! ?3 w2 |
  51.                                 printf("erase sector error!\r\n");( e  N! |. T+ ]
  52.                                 #endif
    2 O: U& M$ _  v3 p6 y
  53.         return HAL_ERROR;
    ! t9 T+ _1 t  U$ R
  54.      }
    9 P2 P; g$ h7 [; {
  55.     return HAL_OK;* k3 F3 B  ~) c! e+ J; |
  56. }
复制代码

& c- ]( \6 A; T; g第二种情况是全片擦除,时序如下:' a" i. v9 p$ _4 }9 V! }) c
+ X) N5 [/ Q8 T' d# D# _8 d  V  g
20191006140919805.png
9 ^/ @: d/ G! V  Q, C  x4 G

* f  k0 J* S  l, a. ^/ n8 I0 f全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。( M; i  r2 S' I) H2 T8 ^, k$ D

0 ]1 w) U! g8 e
  1. /**9 p) `% |; t! v/ p5 Z6 Z$ M
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s; `. h/ w  }0 y! G" n) i
  3.   * @retval QSPI memory status8 [2 E" G" z4 M  f/ Z
  4.   */3 _0 N0 I- u* @7 y3 M4 @( S& H
  5. uint8_t BSP_QSPI_Erase_Chip(void)
    ) E( B- s1 [2 h! Q# s$ e/ N2 n
  6. {
    - }% `: O' U7 S
  7.   QSPI_CommandTypeDef s_command;
    5 |! o7 W2 T) p! v( }

  8. ' W! H$ L) R; U1 F' c) \
  9.   /* Initialize the erase command */5 R/ E! }# C" P: L& q: M5 T
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    ! e% `5 U' u6 l  h4 S% P
  11.   s_command.Instruction       = CHIP_ERASE_CMD;$ a0 O7 I  N( r6 c' w
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;
    . B8 O% ?: i2 U  b" b
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;+ C# m5 E$ Z: w, V: ]! t
  14.   s_command.DataMode          = QSPI_DATA_NONE;% o5 z, u( r! n. m% E* |0 ?% c
  15.   s_command.DummyCycles       = 0;- ]+ Z# E/ o& j5 l1 r( `$ [% _
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    4 m* B4 y2 |; p$ q: x
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;1 l" `7 S. k9 R
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    7 T2 H& q( C- \; J' o4 F

  19. / b5 u  }+ K* L6 N( S
  20.   /* Enable write operations */- i# a6 {; x; b1 c; Y$ j5 l  k
  21.   if (QSPI_WriteEnable() != HAL_OK)+ b& D6 A7 m" ]5 ]! u, ]' z/ c
  22.   {/ H! j; h5 c& z" c
  23.     return HAL_ERROR;
    + r5 }/ r# D' O3 u. Z- Q
  24.   }
    4 ?( M$ C/ U2 P. G9 ~- m% r# L1 I

  25. 6 i  |6 v1 {5 Z& l7 m4 S
  26.   /* Send the command */
    / U$ r. a( l) P8 q
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)0 v9 U+ K5 H5 P+ `
  28.   {
    - y* w; Z: J  t# p5 f
  29.     return HAL_ERROR;8 J; P0 O& \" H6 X
  30.   }5 @, O5 ]9 c# R4 ?
  31. ' g; q  u9 O% u3 p6 w+ P8 U
  32.   /* Configure automatic polling mode to wait for end of erase */  
    # h0 g2 @2 S7 U5 W. u9 c9 _0 S" i
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)
    9 n% K# o2 \# @" _6 ^, ^
  34.   {+ d5 |/ a5 O6 v9 k: A- S! B! b6 o
  35.     return HAL_ERROR;
    ; h  v5 T2 Z, [' X
  36.   }$ ?  n8 X- A" y1 y8 B
  37. : N& V: a5 h0 J6 x* r; g, E2 q8 F
  38.   return HAL_OK;; h( _9 S, U& I; v2 h
  39. }
复制代码
+ Q& t/ c) t  g+ a
9.测试程序. Q! h+ u" y4 P
& \; j, e  Q: h# W0 `
测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。
! w/ ?0 e% ^4 j0 a! o; j, p  c- |: J
  1. unsigned char write[4096];# Z- e9 j+ N# z9 s& a
  2. unsigned char read[4096];   * `! P, m4 p0 ~! L
  3. void N25Qxx_Test(void): M1 {' C6 G, B$ S& F
  4. {
    % c. E* y. Q/ y# w8 x2 b
  5.   uint32_t i = 0;3 p( \' w" a6 R" X' M2 p1 |) B+ q
  6. 6 n9 r+ f7 J, p5 O& Q
  7.         for(i = 0;i<sizeof(write);i++)
    * @  o% l" D7 R% k! @3 o
  8.         {
    1 W5 A3 ]4 X/ z3 s
  9.     write=i;. B7 W; D) ]9 @# @; N
  10.         }' X3 C7 l& o5 `. G1 x" I" u3 T
  11.         
    $ d+ p1 T( `7 j: t
  12.         printf("write data:\r\n");        2 G# g/ E4 p' Z# H
  13.   for(i = 0;i<sizeof(write);i++)+ k% D2 p. C! R0 H/ `6 ^
  14.         {
    0 s; v0 h6 a6 T; j
  15.                 printf("%d ",write);
    " S+ V  D7 c! C! Q  v
  16.         }9 F2 p0 F( b1 D; S5 o( Y3 [' M
  17.   printf("\r\n");! |6 F# Y+ i' v7 e5 i
  18.         printf("\r\n");
    1 b& N: Q4 A+ x4 g
  19.         printf("\r\n");
    ' n6 H' m" I8 q, L, K2 ?7 W! w' }
  20.         4 y3 i5 l+ R5 J8 ?, f
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
    7 W4 P2 q9 M4 F& l
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        
    ) _5 k+ B" {: g) z
  23.         0 y  {4 H. u+ N- R
  24.         printf("Read from QSPI:\r\n");
    # v  O: ^% x% M. s$ Q
  25.         9 B% G3 ]4 @/ V3 C6 U# ]7 H1 ~
  26.   for(i = 0;i<sizeof(read);i++)
    & s9 b! x; B  a/ k+ X- B' z/ ^7 i
  27.         {
    - {* w# K( C* M% U8 q2 S+ x
  28.                 printf("%d ",read);6 z& s0 {( \: q% u3 G+ q# I
  29.         }
    - M( y$ ]* a; i6 Z
  30.   printf("\r\n");
    " M! V- _& C  v! G
  31. }
复制代码

* z, v, j! C3 w1 f+ F写入的数据(部分截图):
  r4 ^$ x7 C% u
% D6 m4 \3 h2 @! X# U' Z# v
20191006141636369.png

: v8 o6 U$ P8 y; s* ~$ l! b
8 P/ B) k. M# ~( I  ^/ d6 [读取的数据(部分截图):. J: w8 X! e, n. a8 P
8 b# E5 U. T  r' X) T( _. ~
20191006141537697.png

- e# C2 V8 @+ U) e0 u
& M$ j( Q3 R. P截取相同的部分,两次数据完全一致,写入和读取成功。% B+ \8 K; j! P9 n4 }
5 o7 l9 a" Z+ V, P8 L' H9 @
最后,讲几点需要注意的地方:, n- P. f3 w! R; b

7 O1 f7 F' f2 e9 q2 f, x" I2 h1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。
% A4 Q+ |1 w3 r& B, c0 [  H' X+ M2 x# o8 q
2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。
3 \7 }$ e" S" k1 Q- v! H/ e
, e- O0 x- h* c3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:  n! ^$ ]; k6 ?$ C3 H7 Y" Y# l
 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断9 O" h. B0 k, F! _) n6 m
 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。
% l0 b) A2 L! w1 i9 ]
: j2 E. _6 q5 K- P& V" j8 g4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。1 l5 c) l0 v: a" l; R: G

  o0 t, H5 F. v' c5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。
9 V5 O6 P+ I% E7 n: I- D2 f1 G2 D) _  G) A& Q: `- S
20200316162249870.jpg
2 \1 p$ c7 q/ k7 b& [  t% @% {6 f
( J+ y0 f' g/ @7 X
6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。
  N4 z) Z+ G1 b( i; f
. X  W, {4 z. h4 J( k: x" L7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!
" v5 B" }6 C0 ?6 G+ ]) o. y  _5 @' }% U2 A
5 l. h, g- E1 f% p7 O. [
- T8 H- L1 v& q8 w5 p  m$ u5 \
收藏 评论0 发布时间:2021-12-16 21:00

举报

0个回答

所属标签

相似技术帖

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