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

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

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍& x$ ~* @7 G( C. t5 C' R/ i) ^# J0 x3 c
- F+ w6 S* n7 s; H  z8 x# n) C
QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:$ }% \: @# \7 i5 N! m

. s" n) C3 i& T 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。
7 r, U0 d* F) L2 y6 l2 j2 k5 K3 L% {0 x. ~' X/ r2 c
 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)
) c/ J0 f! O9 l/ e: e3 b* [
: U% `  Y6 |5 t* n0 `& l 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器1 M, r: b# K0 E

& e* N; W- m, E/ N采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。
5 N# x5 ^  D8 d% s% u
1 j( ?4 J! s% G3 L内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
/ D) Q; E: |% G6 i! m5 q8 ~$ e0 Y0 s
6 b/ L$ L6 d8 h+ q3 Z) h, p) M; N, g内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。1 _8 T4 ~( f' p% J, C3 K

4 D& B2 d' w/ ~+ p* ]二、 STM32QSPI 功能框图9 G1 m; k: X  L: B, L; j! {0 |

9 Z$ ]. o1 t" U2 S
20191006091636349.png
- N% I! O- E0 {  }1 q

9 L: J$ P6 S/ M, ^5 u$ NSTM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。
, H5 F4 v( b5 R% t: ^7 |) k( o7 I
1 y1 S4 e2 [) _三、QUADSPI  命令序列3 W) S; ?+ a( J, N

5 G0 L- ^, a: r2 cQUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五2 M$ o# S, X9 s+ e$ O' @, Z
个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。" _! |* ^. d% C# D+ ^  V
nCS 在每条指令开始前下降,在每条指令完成后再次上升。
; W* {( Y+ m0 c5 l0 u) q; {9 @4 k
/ }  D: f* o* p. v7 M
20191006092242666.png

0 h3 Z: S9 ^. ?* J
% v- B1 v* s0 p& h/ i上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.6 a1 `4 @* Y" d; f
2 u1 Y9 f3 S% k& g
交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);0 l0 F4 ]) q0 J, l& v
2 v7 y) n7 m5 `( @# \6 S5 f
空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高, M- n) q; K! l6 _
的时钟频率时,给 Flash 留出准备数据阶段的时间。
  ~% F' S) }; h6 d  C若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。$ y( i  H7 D# L/ B, j

# i5 Y. y! N' [此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。
) U7 v0 u8 B* V5 H* i: u: ^; [) R/ Y) O* k& x& A
四、QUADSPI  信号接口协议模式; u4 d  f: @0 Q. h

) X3 G* ]9 j9 s: g0 Z* q" W下面提到的几线模式都是指数据线的根数( m2 B7 s  e" S  Y( l$ p2 \
, {- p& T+ Y3 j5 Y6 f% F
单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):
6 }7 ^; o7 p0 b7 E3 b& f  K传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与4 `8 }8 r6 A+ Y
IO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。8 O% V: y  z& q
通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可
/ `4 H+ |% H7 o8 u$ G对不同的命令阶段分别进行配置,以使用此单个位模式。
- {: @- l: M! R4 q# f5 v# y在每个已配置为单线模式的阶段中:. ~) i' I' f1 i( R1 N' u
 IO0 (SO) 处于输出模式
: G5 z1 z) x" B4 {0 s IO1 (SI) 处于输入模式(高阻抗)
. z4 W* l; c1 ~2 k+ J/ @9 U+ g IO2 处于输出模式并强制置“0”(以禁止“写保护”功能), K* V# Z$ b! M, J  K# _8 q0 E
 IO3 处于输出模式并强制置“1”(以禁止“保持”功能)
, Q9 ~1 c" |( ?" i' O0 G0 {8 u: T( S* ?" F% T- i2 |+ k4 F
双线 SPI  模式:
  K/ Y+ M! ?2 h! l在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。5 Q  `' ], ]# d4 K. P$ R% F
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可
& s' d" e8 N: O2 V3 p& `对不同的命令阶段分别进行配置,以使用双线 SPI 模式。) v* A4 Y( y9 C  |7 c
在每个已配置为双线模式的阶段中:5 ]5 s' B) q+ F4 b
 IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出4 j5 D7 G1 [) _0 h
 IO2 处于输出模式并强制置“0”7 ]5 \- h+ d6 K
 IO3 处于输出模式并强制置“1”
7 [: y% l  S& ?/ {2 \, r# }9 {6 Y在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。! b3 m* p" |* A$ _& {3 w
/ b- m9 S5 i5 B2 \7 E

, j9 R+ U7 k. D1 Y7 J; w9 s四线 SPI  模式:
( h) _: F8 |9 _/ F; {4 G# z在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。  k" W  {3 J. Q+ r; @7 s6 v
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可
4 x2 \6 `$ J# f' m( _对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
. V, V, t0 g6 T, ?; p在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高
7 c; C! a& w) @. ~# Z( f( D阻态(输入),在其他情况下为输出。- I. J, `& r6 e
在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
& ]: y4 t* t# NIO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI
& d, y& g& Y. s* b! |激活,对应 IO2 和 IO3 的引脚也可用于其他功能。
4 ^) Q! \9 f# M( Q- _+ \  Z! J$ O

% V* \; D6 K- ?: oSDR  模式(默认工作模式)
* E6 ]" W0 M! L6 W+ W默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
* c6 D" h  [/ P, o5 }: s在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK
$ E1 D6 R+ X0 c1 {的下降沿发生转变。
1 x2 j( D) {' K! Z, b$ G在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情1 Q6 r) M8 f+ U9 q4 `. r$ h
况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。
; ]; P6 J* B: T2 b
% @+ o5 g4 L. W$ I# J
; B. ?4 \( I+ {; p+ U  FDDR  模式5 h* z% d$ ^5 Y1 b3 _
在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信) Y) W1 }2 ^9 c% P, B  k6 U
号时,将在 CLK 的每个上升沿和下降沿发送 1 位。; d* j- a' h  M2 S! y, a1 h7 \% i
指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。
, ~+ N  z4 o: |8 |3 f7 D3 J8 X0 l在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数8 I3 X% i0 f7 d) p0 J7 ?) c  m
据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期
& e1 m$ Z! c9 ?* S后(下一个反向边沿)对信号采样。! u3 R0 e4 m$ x4 ]) m
3 l) Z' i: f/ y5 Y6 N# L7 _
五、QSPI 配置(HAL库)' h$ r/ n) r! u% k- _) \5 A  c/ W

# m: f& N# P$ D, s% a( j1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟1 F6 q6 _/ g0 ?  u3 K$ C4 N
: s. @6 |/ F( m, L% v& T7 l; z
20191006121236849.png
4 q# H# W2 }+ N  I0 w! o+ j. J" L

* B' w! G( P+ [7 H( p! |! @
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)  a3 B: Z; ]7 c: V, t0 W/ F
  2. {
    $ _6 D$ v9 X" _1 B
  3. & m" _# ]% V: i; J
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    ; l( p7 p5 O# Y( y
  5.   if(qspiHandle->Instance==QUADSPI)% D7 S3 G& l- ?9 ^+ z% `4 v$ E
  6.   {2 C: O0 d3 p! @0 q% Y4 L; M, Y
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */
    2 ~+ t5 r" J0 x  [/ m4 x
  8. 4 P7 M+ a( Y- c  Q
  9.   /* USER CODE END QUADSPI_MspInit 0 */+ p1 C4 ~% k2 e6 O7 j/ C8 s
  10.     /* QUADSPI clock enable */2 ^) K$ P. H8 ?8 E( O$ r( u( ]) L" P
  11.     __HAL_RCC_QSPI_CLK_ENABLE();
    ) f, d" m# K* G9 V4 b* A
  12. $ c* V& K6 G0 h
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();
    - R* p* H( o1 P: ~
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();1 K$ l4 l. n$ i8 u
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();* M' V2 ^# C3 \* ]: D( e
  16.     /**QUADSPI GPIO Configuration    7 d# G+ y0 m* M
  17.     PE2     ------> QUADSPI_BK1_IO2$ f/ [2 w! d- l# I  [
  18.     PB6     ------> QUADSPI_BK1_NCS
    5 J; C5 w$ e* s, x% j
  19.     PB2     ------> QUADSPI_CLK7 F2 V3 k- M/ E: k& m: ]  D& C
  20.     PD12     ------> QUADSPI_BK1_IO1
    . i( v& e# c0 z3 l0 L, ^
  21.     PD13     ------> QUADSPI_BK1_IO3
    # t8 u7 E, c9 w6 \: |
  22.     PD11     ------> QUADSPI_BK1_IO0 & a3 t7 |" e2 V1 T
  23.     */3 {. @( U% O( @* m' d/ w- m
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    , x6 R3 W* O/ b
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    ) t  R3 q5 M* Z
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    3 t2 G9 [+ r. G8 h
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    $ [' \0 c% m) U* O1 I4 K
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;/ U$ u! E3 \3 U# [3 W4 {( {) g: r
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
    + l( Z) }* A8 S' u$ Q5 U- o) m
  30. 5 @/ d% x7 T6 W. D& V
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;$ r: `8 H- S  n% ?+ i
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    1 R) ^9 p# }* b3 M7 V
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    " P! b0 j2 |& E$ R& g
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    $ G5 Z. T$ X/ F) M1 M" G) p
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;0 b: f; O/ a. l0 Q0 S
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    - A; B( G- Q: T5 M( J

  37. 3 C: G; U  f3 b9 S6 p( `
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    ) v' \; z, R8 e$ z  g4 x
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    5 \1 P2 t0 y& M( \: _& U1 b
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    3 f( U# Y8 f" g9 u% e" z* r
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    1 F0 s  y! y- v3 r1 ]
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;& [" p5 q" E" \6 I  J, O
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    : S* V6 g& R+ N
  44. 9 S" ^  r) q$ r5 X
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;4 X- O) j# K9 P
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;) W8 n% h! S5 b9 k: [
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;, |) J0 m& E5 U' b
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;2 s; E, K! A+ m$ i$ Q
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;0 h" V& i, T! C
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    " N# k! a9 p$ }- S
  51. 5 f% c! T8 x# J, X# @2 \  s* \$ J
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */
    ; r6 e, t) j' u/ ?7 \
  53. " A( {& |$ E% x3 c
  54.   /* USER CODE END QUADSPI_MspInit 1 *// W- g( u1 c" ~; b# K8 S
  55.   }# U0 Z5 s0 c$ S4 M$ W
  56. }
复制代码
$ `& x5 L; H, p" ]3 X% W9 O2 a. K
2.接着初始化SPI外设) J5 k5 q8 Z2 P! m

  1. 1 H/ G) E  K% G/ k- v
  2. QSPI_HandleTypeDef hqspi;
    4 m& V$ \' C5 |4 d: \! o- u9 v+ X6 I
  3. ) m( w3 M# Z9 `# ]% b/ w
  4. /* QUADSPI init function */
    6 }0 g; b/ m5 N6 O5 U
  5. void MX_QUADSPI_Init(void)7 n6 e: L+ x2 s
  6. {  N1 Z% B% ]! ^8 U) ^2 o2 F$ X
  7.   hqspi.Instance = QUADSPI;//QSPI外设
    # c. F+ C+ k' B6 R
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
      d: Z/ d! w# Z; J# U5 f% e
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
    2 y/ I0 u! D4 \: J0 k6 i
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)4 F( U; _! [6 ?+ n
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
    0 W0 Z7 u$ `9 ]* x
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数' i* c- f1 F9 ?
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0
    : ]: H5 d4 z9 E* W$ K! c7 X
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash3 P& w, N9 P) ^2 O6 r7 ~
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式- k; A) H* M! d# T
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设
    1 l. {% [( U) ~7 A- x# e& Q* B# B- G
  17.   {
    4 s# y+ Q! C9 ]% @$ R8 Z
  18.     Error_Handler();
    2 N0 w4 O2 Y3 a5 c/ l
  19.   }1 y3 k  a  ?$ F) L
  20. }
复制代码

& j" h8 f. S) ^' t+ _) J3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。% d8 I6 h. k$ ?6 m5 \
" P1 _  c( d+ r- V2 e/ R
我们首先需要配置下面的命令参数结构体:4 m8 S9 o2 z0 @

/ W9 f8 q5 z7 y/ Y# h$ A5 s
20191006122437823.png

: |9 X& C5 @7 f/ o$ C
! _/ e7 K7 a1 D/ lInstruction:指令类型(读、写、擦除等等)  R" ?( \7 C$ x5 a6 P# w

2 L) b& C2 K9 T/ ^9 _8 tAddress:需要读取或写入数据的起始地址( L5 {/ k. d; q+ ?1 S. m6 E

+ Z7 L1 N1 a: C- I$ {$ u0 \NbData:需要读取或写入数据的长度
. ?( S2 H+ \! O. D, I: L5 A1 L" X. m% \' h' j
DataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。
- u9 o" l0 H3 _1 {) z4 s8 `0 ]7 ^; u4 N. y$ D2 y  N
这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!
# z7 r' @. B/ g) ?* Q7 i3 s, ?/ N+ o+ H6 B; _3 X! t
4.读取芯片JEDEC ID( B% ~" \8 M+ o& R$ T  ^; K

7 b; w# _& ~' y8 m$ h首先查看手册上读取JEDEC ID的时序:2 s6 E. n1 V. x; d  U. Y: }7 p# g

$ D& }- h, t) ~& F, O8 f
20191006131644114.png

3 Q2 k! e- v5 [4 v6 v* n& v
' N- D( C0 J. Z. j) P可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以 $ S  \) {1 e4 U
( S1 F) y3 F* ]/ r7 k- r
InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
$ i) g( w4 w8 y$ Q5 W8 g- W( f& n
DataMode = QSPI_DATA_1_LINE;//数据单线传输模式
; h  H6 Z* \: u
) e3 P% b( |2 T1 N4 A  ?Address和AddressMode 这两个参数的值都置0,表示无地址。
, H3 }; e' W5 E) i0 R5 L7 `- T( n. A5 W  x1 ^8 k
NbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3
5 `$ R# l" w/ |9 b, Q7 `& g3 b8 }0 }9 @9 o2 m4 E
其他值和下面代码保持一致即可。 4 Z0 {5 n  W8 Q3 z# Q
4 L; k, D1 g$ z5 j" _: j
然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成# c! k7 E& M: s" u
- M4 A8 s& J) X
最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID
; {2 X5 ~$ i4 R- L' f% h
* |. A4 E8 l: M8 ~
  1. /**$ @- D# E9 u! a4 g- F) e. R
  2. * @brief  读取FLASH ID' B' b) z% k7 O7 f* k
  3. * @param   无; x; Z: ?6 i7 }, V: i1 D$ K
  4. * @retval FLASH ID$ }1 z, r" R& [8 Q
  5. *// T0 L9 h! ]/ v* C
  6. uint32_t QSPI_FLASH_ReadID(void)  ^& N. Z) P2 w+ J# I# M# h
  7. {2 `5 X' H" C/ _! J
  8.      QSPI_CommandTypeDef s_command;
    , @+ w: r6 M+ o5 o/ k9 y# [+ C: G; S
  9. - W% |; {5 b) D+ p
  10.      uint32_t Temp = 0;
    0 l/ a5 ~7 Q/ G9 W& Z' S
  11. ' |' w- Z2 A7 u. G$ m. y, D. A3 E
  12.      uint8_t pData[3];
    : o6 J$ |0 X) S+ a6 G' ]& h
  13. * g- q. Y) Q6 c* d
  14.      /* 读取JEDEC ID */
    ; E# C! ^. b7 Q$ a, p& s" r$ I% p5 E* A
  15. ! E: I2 P( U. q& y* c
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。, k' P6 S" \4 Q- \) B8 ^! x* @

  17.   w, r) |. ]9 ?
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令
    ' e. l2 Z* Q0 ~6 ~

  19. ; X+ m) [: }# D6 [5 H+ s4 ]
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度" ]2 x& {# L5 l/ d
  21. * A+ B* u8 ?' {) A5 _. a
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。. f4 g9 f3 C1 l0 S' ~

  23. 8 g  {2 G, L* L; u. t9 s. q
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。
    2 d" ~! {" q# u8 J- z1 }5 X
  25.          
    : c/ S# O$ z: `9 E% Q: }6 L( v
  26.      s_command.Address           = 0;   5 Y" s$ T* m# x5 B# b! O9 W* Z
  27.         . W/ \+ a( V- x
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度
    9 }; r/ j! J9 Q/ ~

  29. " y0 s; f) L) ~, j+ `
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。
    % ], w9 j1 F" v9 Y# `$ u
  31. % K8 J# Y% r! H8 V
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。
    / f: s% H7 s6 ?3 m

  33. # D+ c' k0 S4 W0 C- |+ p9 z2 D2 N
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。8 j: w( @/ C8 G3 `9 R! f; M8 D
  35. 7 J$ d: c! @% m$ ^; O
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
    , p% n* T% b* G- |4 J) c, b
  37.                  
    4 `# r( n, |) H* L
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。
    $ q" J- \$ }$ K( `& M( ^
  39. ; l8 N1 Q6 B/ ^" C6 O7 ]8 r# }
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)$ g  D+ V$ q, V0 t
  41.                  {
    $ S8 L7 d; |) E- x5 q2 p
  42.          #if  QSPI_DEBUG
    ' ~- A2 A; N6 F' Y7 d  \9 w2 G
  43.                      printf("send read flash ID command error!\r\n");/ s4 D- @' u5 d; s. ^: E( A* u1 y
  44.                #endif5 U2 a! I6 P1 u% e, [4 A
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */* ]- E9 R& }- S6 e
  46.          while (1) {}
      H; P$ n3 a9 b$ S
  47.      }% I2 D2 R- g8 D( V7 e2 \0 K: G9 y
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) ; a: ~- K# S; d( O2 N3 i) Z
  49.                  {1 g. X7 K" L! u, C2 H3 J* x
  50.          #if  QSPI_DEBUG
    # y! A% H. M" N# n2 }5 s
  51.                      printf("read qspi flash ID error!\r\n");- ^) t3 v% F' S. a2 @5 G
  52.                #endif/ P/ a% J7 O$ O0 Y. }
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */
    ' r4 O! P1 h+ Y$ R
  54.          while (1) {}# }# g4 D3 L4 ?2 ~
  55.      }
    . B6 o, i  B9 C5 _8 C5 p' [
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );
    # N! l! _$ L5 `
  57.      return Temp;
    . c' [2 e0 t: ~! ?- c5 ~# @
  58. }
复制代码
$ V; c5 O! N, U0 |' ]( n
5.FALSH 写使能: `1 r6 G, i5 k6 ]
1 n& W2 x. b1 f9 e; |  a9 U
在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):
* _! ^) W, p. r% N
) j6 M% o5 E* d% U, w类似的,需要查看芯片手册,写使能的时序' s7 G3 @! p! y. U, e, `5 W

5 z$ ?' F' |! x( x! J0 S, [
20191006134057767.png
. @1 {. p& z7 ^/ J0 S; ^

9 n* j9 T# p: E' D. e5 r3 x可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0 0 y  G6 R* L' H' A3 B: }9 v( k
) g7 K+ Q: }& j1 H6 L/ T* G0 d
  1. /**3 [* e2 ?6 b& d
  2.   * @brief  QSPI FALSH写使能
    ; \/ `4 w* c6 K- k4 a; v$ P9 T
  3.   * @param  None
    * U  b' S* ]$ n# g
  4.   * @retval HAL_OK$ |3 c4 D/ ]/ `% W
  5. */
    " H: B1 A  M: y! X# \
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)
    7 B# C1 ^2 ]/ i/ E) |, L* k- V
  7. {% n% @0 {- A4 z: F. M8 n
  8.   QSPI_CommandTypeDef     sCommand;7 Y5 C4 s2 R; e5 M, X8 t5 m
  9.   QSPI_AutoPollingTypeDef sConfig;. V: E2 L5 x4 p  G0 y
  10. ! Q: {" g& V; q+ m: r) Z
  11.   /* Enable write operations ------------------------------------------ */- t2 o; C# K  G. O5 ^
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    / t# [; U8 ^- Q" e9 u+ \. \
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;! V8 U1 R4 X' ^
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;0 q1 s8 k" c2 \2 s, H! W; @( l
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;9 o( c) j/ q* p4 ~, G; Y. y9 @
  16.   sCommand.DataMode          = QSPI_DATA_NONE;
    , b4 `! X. z3 n, m* P. ?3 `# x
  17.   sCommand.DummyCycles       = 0;
    # b# f, j1 x- y# r9 D
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;5 E9 q5 S$ X6 ]3 V$ W
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;' r# y2 X! _, ^( C4 \
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    8 F8 q2 x* |/ i+ M7 o' F7 T

  21. 2 O( J' T4 m' H: `
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)3 o( g2 d) k; O* N5 z
  23.   {
    2 V2 p& V1 Q! v* ?* v) @
  24.     return HAL_ERROR;
    ! g: [- u; r6 m: o! t
  25.   }
    ; j0 Y5 u9 B% w; q

  26. 2 r: q8 Z  P7 ]: ]
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  ( g$ h4 j6 f4 D4 B# `( y3 c* u
  28.   sConfig.Match           = 0x02;+ I- J0 v; ]# ]
  29.   sConfig.Mask            = 0x02;6 L" L% d' C; T1 H/ f
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
    - \5 Q9 L# f1 X, G) Q, A2 P
  31.   sConfig.StatusBytesSize = 1;
    $ R% M7 ~! N; K0 x/ D7 I9 y
  32.   sConfig.Interval        = 0x10;
    " C/ P7 x" h( ~1 T
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;
    * E  q4 `$ h4 d! D! j6 p3 S
  34. / P6 H( O4 ], x$ ~" f+ C
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;
    $ m3 M+ B& J+ @6 n
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;
    # p) x- u8 c7 q9 u/ x
  37. ; D! n) R* u& A# c3 e/ e
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    5 n/ }& ^9 U( F7 T6 U
  39.   {
    4 F! H# z7 x' m- M, g  q
  40.                 return HAL_ERROR;        
    0 I' W% S9 ?+ K! t
  41.   }
    9 O) S9 H, Z. D& D
  42.         return HAL_OK;
    * y" x6 X' ^) h& f# S
  43. }
复制代码

5 w1 \) Y( V7 L0 y8 P这里需要讲下 QUADSPI  状态标志轮询模式:
2 E, O) U5 b$ I( P- Z在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。
) r  Y0 P# P0 z7 W/ W: |$ r' a可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。2 O! R1 [, L0 U* ^4 Z' ]
对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入0 }9 j( k) i) k/ E. V
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
: O3 z% v" e0 K8 n一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。
+ g, @& F! _/ ?& b0 N3 y; y5 m在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。/ v+ k  S: W6 \* c- i# X9 _
如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内, V3 t+ z) z" d6 D4 J+ p
容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。
  I6 C. u% `  ?0 Z% D如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着
0 O4 K- h* y  \& H状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。
$ \2 U! [+ `+ ^" b8 ]6 v" `  M如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。! g: D1 T4 k9 v. t6 Z, f
如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。
& G, g+ W% S8 }, ^8 h如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否
- G1 B. r6 v* R4 a/ [) [' O则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性
8 v8 E6 c5 O  g1 B6 V9 G7 O访问。
4 @/ d8 o# V8 ~0 [) L: x5 h数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不
% F/ K7 Y9 j0 }0 O% K: p5 _受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数. s+ a% R4 H" X& K' ]5 g; ^+ \! I
据读取后清零。5 a/ s) Q( s6 O3 C
1 C# |0 y- r1 c( p/ s' i$ L
简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。
% p" y9 b6 A  K! q3 e6 s6 t$ z+ S7 U( r) e  v7 L9 {8 L3 H4 c5 m# y
6.读FALSH9 t3 N9 L( A3 S: t

  Z& k8 c# c: _9 L这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序
: A+ y$ q) x3 R" U6 L: A5 ?* Z2 y+ q, R+ W
20191006135244693.png

& e3 W3 L8 v5 I: ]% N( d, l) Q% J, \, @8 f
从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:: y( e" f; B- Z& Q/ |' g

/ t/ Y3 D$ }$ b5 r4 n
  1. /**1 H3 x5 G6 j9 w' N0 @: j2 U
  2. * @brief  从QSPI存储器中读取大量数据.
    : p" h: }  Y) q0 k4 f( n6 x
  3. * @param  pData: 指向要读取的数据的指针
    3 L. j6 z! a  |/ q2 m% r
  4. * @param  ReadAddr: 读取起始地址7 P. E& P/ ], {. ?
  5. * @param  Size: 要读取的数据大小
    % D/ a* p7 y  K; |" p! K
  6. * @retval QSPI存储器状态" @& B" W5 r- Y9 P0 e& u: Y: w) I+ m& j
  7. */
    * ~: r& n' v+ [$ f0 R, w
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
    # a5 d- h% a% |& q, B3 }
  9. {# }: y( `9 t% u6 j
  10.      QSPI_CommandTypeDef s_command;) O  l2 X  Z$ l- e

  11. $ g* J' n# `+ X1 q6 I
  12.      /* 初始化读命令 */, N' C& k0 d5 s- F% f
  13. / l( C, E" k8 w. f1 s: C6 c5 U
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    1 D+ t- N$ P, H; D" s6 I8 b/ {

  15. 8 Z, k: ~# i1 {: ]# E$ P! H
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;9 M+ i) y" H8 l6 E0 n) F
  17. ; f, k+ R( O. S2 {8 D1 ]
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;+ E) I0 o1 W( ^
  19. 2 n$ A/ |# M# v
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    0 K+ |  y+ u. p8 ]5 u* l
  21. & |: |" m% M: Y; D
  22.      s_command.Address           = ReadAddr;1 |- u8 d" D1 ?6 B$ g9 {4 U

  23. ! {, B: N, z. g# |; A* O: U7 s1 `* a
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    : S$ {- S* L8 Y3 x
  25. ; x% \) z* A" g) I1 P0 d& q; X, e
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;7 f. g: P2 v( M" S0 r7 N
  27. " z5 u- ?  o0 {% ]  t, @
  28.      s_command.DummyCycles       = 10;" J  Q5 h) h  Z: S$ y7 ?* O
  29. ' [  E( p( |3 n5 n  H7 b
  30.      s_command.NbData            = Size;* o( F% p6 U; m- j9 a- v
  31. 5 I0 [* t/ }( q' o" q4 v, T0 J) z
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    + g* f- n# A' y; O# i

  33. 7 m6 S. P" }: `' N  ^8 s
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    + }0 H" k4 r5 f9 N  q# o

  35. 9 K1 f8 z( e" _" m4 i% U
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;, K3 c# G- u0 p( {( E" n/ [0 P
  37.      /* 配置命令 */& g! j& H6 ~- b4 z+ ?9 H5 o6 I
  38. 4 M. {/ w6 s+ p/ Z- J
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)$ \+ b8 E2 r) R$ e2 S  G; f/ I. N
  40.          {
    9 }6 ~8 M8 F: V. m) x  Z
  41.                 #if  QSPI_DEBUG
    , v4 _% a0 |6 d5 _
  42.                 printf("R-send read command error!\r\n");( w1 M. w. ^& F, w2 }* F" f. `
  43.           #endif2 P5 ]) v: E, c" X
  44.     return HAL_ERROR;
    ' F0 H8 p$ q; W  q0 q- [5 f' k7 Q
  45.    }  2 W. ?8 p. w5 G
  46.    /* 接收数据 */
    ( Q4 }, a3 q9 }+ I/ j
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) 5 y% m5 a, @! ]5 S- ?+ ~  G
  48.          {3 p% G0 a) N# j/ W
  49.                 #if  QSPI_DEBUG, B+ q2 Y: A, n
  50.                 printf("R-read data error!\r\n");$ m# ~; W( ~" x) z
  51.           #endif( a5 T5 u! }# h! n3 }: R7 @
  52.     return HAL_ERROR;
    % ^1 y0 K2 l% q3 O
  53.    }$ D; ~$ h# B# l5 O3 Y: q0 t
  54.    return HAL_OK;
    ) P6 _1 R& x/ O5 V$ e
  55. }
复制代码

2 e- A% h# P3 t# p: y本函数实现了从FALSH指定地址读取指定数量字节的功能
4 Q+ n% F" c1 Y0 T0 c1 d6 F8 z' e+ j" I( p1 ?
7.写FALSH  W- k2 ~0 s9 I% b% D  f
+ k5 D! W1 m( P3 F) }8 l" P& D: J
同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:
# |+ l, M; S7 s) ^+ ~' T& y+ T
7 O! y$ _6 x( ^3 a  @) f
20191006135836784.png
1 Y2 E" W4 d+ l. d+ c" N) ~+ h

6 f* V9 i5 v7 Z; e, [根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期 $ h! {; Y+ L# P" B. S4 D  G
" [1 |- F6 p$ j  s7 ~4 @3 `
  1. /**
    , w6 p  H: l, o$ O3 _
  2. * @brief  将大量数据写入QSPI存储器
    ' C5 C4 B# Q  u- y. L
  3. * @param  pData: 指向要写入数据的指针1 ?  b4 G/ Y& K+ p8 ]
  4. * @param  WriteAddr: 写起始地址' a1 @0 P/ {- o$ t, Y2 [( [
  5. * @param  Size: 要写入的数据大小
    3 G, N2 B+ g3 e
  6. * @retval QSPI存储器状态
    ; ?' O' s( B6 D- }, j5 M) ^! V0 f
  7. */: t$ j6 }9 y5 B+ p
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
    # x2 H3 |( d3 L, c3 E+ h* A" N% ~
  9. {
    7 z( y; f8 B8 Y8 l) V; O
  10.                         QSPI_CommandTypeDef s_command;
    ! K$ K- D; ]( }1 A" C9 }! C2 O
  11.                         uint32_t end_addr, current_size, current_addr;/ C% z% e! k( Z( @9 S. \
  12.             unsigned char sector_start,sector_end,i;
    + K1 g1 ?0 I3 v& d3 |' p
  13.         
    5 U3 Z% V' r9 S  {
  14.                         /* 计算写入地址和页面末尾之间的大小 */9 K% r7 T% C4 r) U/ Z
  15.                         current_addr = 0;% |) N5 S% U, p+ d# t
  16.         
    0 {! [& {: k9 q
  17.                         while (current_addr <= WriteAddr) 2 W! @' A) G, \- N( b' ]5 N
  18.                         {
    5 n; d& E- e% }) S# ^, X& S" D
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;
    & V7 w7 g$ V, i' n
  20.                         }
    - s9 r# A! R0 j/ Z
  21.                   current_size = current_addr - WriteAddr;
    - ]8 }* P6 U/ h! T- s6 _2 K1 z
  22.      /* 检查数据的大小是否小于页面中的剩余位置 */7 `$ v! ]: s6 \6 w% s8 Y# z# B
  23.      if (current_size > Size) - d9 }! N3 B, Z. K; V+ B
  24.                  {
    9 q* f. l0 z  H+ E- p. x
  25.          current_size = Size;
    1 f2 E2 ?2 a0 K0 Z
  26.      }
    / s' R' P3 a  m; p
  27.      /* 初始化地址变量 */
    ! b# N; I" W. I, K5 x; Z+ _
  28.      current_addr = WriteAddr;
    / C/ }8 g/ V) y' F+ O
  29.      end_addr = WriteAddr + Size;
    1 q4 p' ]- x- Q0 u6 y4 H9 g
  30.                  
    / u  J& F; q# p8 a7 s# T
  31.                  sector_start = current_addr >> 16;
    6 q  i2 Z4 @5 g) m$ [8 n7 v/ s  |2 L
  32.                  sector_end = end_addr >> 16;
    % w" V; P* H7 p. O/ W
  33.                  
    ) b6 k5 K" e% K: r
  34.                  for( i = sector_start; i<=sector_end ;i++)1 C3 o+ G& G) O" I
  35.                  {
    . n5 {$ V9 K' Y% Y5 u
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    ! u( B& [$ y. I
  37.                  }8 H" D( q3 c$ ^
  38.                  
    / e/ Q% B. f# C% a8 ]
  39.      /* 初始化程序命令 */
    * d) v( t# g* R0 m& S" _1 ?; R
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;/ z% E- k+ }; s+ g* q  J* X
  41. 0 p, `$ F* Y! h4 g( p& C
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;+ b. J% P) S5 I# f/ Z

  43. 5 p5 g0 H6 E, f/ M7 M/ a( n
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;* W5 w& ]4 a9 _( j" Z8 A# U+ p3 U
  45. ) `$ X, T* r& U0 p. T( f
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;% G$ x& ?# Y2 Y5 w7 G

  47. 1 N) @. B! e/ P  r, O: Z5 Z
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;1 j( ?! V! s5 r) ~- i

  49. ' H# B" j5 @, D$ n+ J
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;  ^2 s7 [0 m# b! o, H+ w

  51. : |/ D, I- L6 O4 j
  52.      s_command.DummyCycles       = 0;1 _) D; N0 O+ j  \. C5 q( z

  53. " K  A* R2 s0 Y& `
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;8 m8 L* F+ B( h) E4 _: y

  55. . J' i& c" O9 a9 w' Z
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    $ I* ~, Y; k0 t0 M
  57. # Y; B1 @: W3 O7 f* K: c
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    % G  x& t9 M3 e
  59. / A; b. [4 d  g2 A
  60.      /* 逐页执行写入 */
    : X$ G8 U* K) K" u  K' `3 }9 j
  61.      do {
    7 _# [* l& k2 l- h1 ~( S, D7 D/ a- n

  62. ; x. j( z0 V/ w1 ~
  63.          s_command.Address = current_addr;
    ; Y, F3 }* N  [1 ]+ V/ M9 P
  64.          s_command.NbData  = current_size;' j4 p. x" _) [- P$ K1 S! d2 X
  65.          /* 启用写操作 */
    ) \: D3 G' a. ]7 d

  66. # O8 w$ E2 f+ n! _
  67.          if (QSPI_WriteEnable() != HAL_OK)
    * O  ?/ a7 ?) _
  68.                                  {
    * D/ S. o! ^- s: l" P
  69.                                                 #if  QSPI_DEBUG4 d9 h) @( ~3 d' X& d* M
  70.                                                 printf("W-Qspi write enable error!\r\n");) h+ \8 W7 E: ^( s
  71.                                                 #endif' i9 S& M* q0 Y5 m! U3 p/ A7 U
  72.             return HAL_ERROR;
    4 {! X6 z! E! h# I8 k
  73.          }
    0 F- u( l# f: ^+ y2 S4 I- {& ^
  74.          /* 配置命令 */& }; l- r# s! i7 O& A2 l0 j) G
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)8 u6 C: j' r& B, G' d5 \
  76.                 {                                 
    & E, s4 \" R: X, w
  77.                                           #if  QSPI_DEBUG& |5 T7 v; k2 o
  78.                                                 printf("W-Qspi send Command error!\r\n");# ]6 q" O2 [! S4 T/ I
  79.                                                 #endif
    5 f' ]4 J9 Q! {/ Q0 k! x6 A
  80.             return HAL_ERROR;! }! T1 j6 `' @' M
  81.          }
      M  q+ e! @1 h( m, y" d9 b
  82.          /* 传输数据 */, `  q( L% U( H% m( T) C7 g
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) " o3 O; J+ N  r/ G( v6 D8 s0 I
  84.                                  {
    % v; d" C5 x* s% v4 F
  85.                                           #if  QSPI_DEBUG& L* u; x$ v! i
  86.                                                 printf("W-Qspi Transmit data error!\r\n");% i+ `. D: z# |2 ~
  87.                                                 #endif
    5 I1 I( [- m! ]: v9 [; G! Z
  88.             return HAL_ERROR;; I' t' M$ A9 [
  89.          }
      l- @* A+ G# U7 |3 U
  90.          /* 配置自动轮询模式等待程序结束 */
    + j  ^% [6 E2 d' E8 a  G, |

  91. ) ?" z9 H( J5 f- h
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    - H- J1 P7 W+ E
  93.                                  {                                         0 E) l2 Z3 H5 v' b4 `" E) ^* |
  94.                                                 #if  QSPI_DEBUG
    ( _& v# L- c) t, A
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");0 L. T" e' ~/ R  S) c# z+ @8 n
  96.                                                 #endif
    - x' W) u" o9 ~1 o
  97.             return HAL_ERROR;
    1 i) ~& g. d! z2 D: [* s5 M% E- h
  98.          }5 I" \9 m. U$ w2 H1 `/ f$ }  x) D( D
  99.          /* 更新下一页编程的地址和大小变量 */
    9 _7 ^% Q4 i. ~* I$ r: V# E

  100. 1 _0 B, b$ S# D! e
  101.          current_addr += current_size;9 }: R( p* D/ N3 x! f3 x2 ~" P: @

  102. " ^- q1 j+ d; `( l$ n8 k
  103.          pData += current_size;
    " O$ O% Z! I$ ?9 L2 {
  104. ( w: F7 B, J0 p! H0 R
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :
    5 t, @+ ^! m4 V  e0 y/ Z( }2 M  @' L
  106. * |1 f/ ]  G! W( |! t: C/ h
  107.                         N25Q128FV_PAGE_SIZE;
    , |3 r; s# X1 r, Y$ F; d. g2 g. g

  108. - g, Q& M( U8 I' x9 E
  109.      } while (current_addr < end_addr);
    + N' S* h8 X; m) I
  110.      return HAL_OK;; Y# K" j  ]$ i
  111. }
复制代码
: `) b1 a7 t/ P) j6 Y2 c  y2 g
该函数实现从指定地址写入指定数量的字节。% Z* @) g) J5 v. b8 a; J7 i
- S; T7 e4 |7 N4 c, c
8.擦除FALSH8 o2 ~- `/ N% M9 m
6 B2 G( \6 P" h  O+ I
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)
8 q$ ?: p* g8 j  G
5 t3 r: N# E% u6 W' |' |, |( w
20191006140420479.png
* @: c& o% m. Q4 R# h
  C; ~) p' L- Q8 r+ j8 z; L
参数设置为1线指令 ,1线地址,24bit地址,无数据阶段: j0 D7 Q6 {) T8 j' d

; y5 {6 O0 k) k
  1. /**
    - k: H0 @, A* [% W* l. X; G
  2. * @brief  擦除QSPI存储器的指定扇区
    + \; q# F) j/ G% ?+ u( i; I! Z
  3. * @param  BlockAddress: 需要擦除的扇区地址' O/ p) @) Y9 u2 R- v
  4. * @retval QSPI存储器状态
    6 M: d6 w# g# G+ L% N( j
  5. */
    8 n' |/ `* T; e
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)
    ; @* p5 {' r3 V, q
  7. {
    ; o: M7 D- o* U, V" ^8 s- O
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址- q7 U; v* }$ I% ?
  9.         " t% z2 E6 z2 G. g7 j& F4 L
  10.      QSPI_CommandTypeDef s_command;
    ; S3 f' u7 ?. b. T% h$ i( }" ?. @
  11.      /* 初始化擦除命令 */1 e  h* v$ E' q; y
  12.         ; E7 g7 l- r. m9 Z# N! a
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;+ E: z1 }0 K+ g9 t9 t

  14. * V' Q' x- |# l
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除, ~% i( I9 d& F) m1 \3 [
  16. + @' j# T# n' l; \8 I
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;6 ^# \/ ~% H3 `  m, D" @% l( H

  18. : V3 O5 N" A* c
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    & Y8 T4 \6 |3 h- @6 [6 a6 i, k8 x$ l
  20. ( H) H# L( v$ d! E
  21.      s_command.Address           = SectorAddress;' Y! K7 ?, U2 f8 }# ]% _% `
  22. 5 C' J- [# r8 t
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;, t5 Y8 p3 }! u# j3 J1 V

  24. ) ~0 M7 ]  E& l  H( ]5 j. ], i6 v' N
  25.      s_command.DataMode          = QSPI_DATA_NONE;
    $ y) ]: A* |5 X/ r5 r0 V

  26. , X- j( a' B: P
  27.      s_command.DummyCycles       = 0;
    5 e6 |! w# G* t) `1 j" a  k- y- y
  28. 0 U% A3 F( l' f& D/ C
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;9 A: ~5 T7 p9 G2 i2 w, n8 D) K

  30. % r9 m1 M, W$ D, P5 q: b
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;+ j7 S( ^8 x2 I6 T0 K* }# c

  32. , V4 A/ E3 V! r& T, M
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    0 B* c- y, ?7 M! Z( H& S' i1 N
  34. * V) R% \& ?" j. b4 ]
  35.      /* 启用写操作 */
    8 A! ~4 s! K8 B# g
  36. 8 @5 I2 ?. O% b
  37.      if (QSPI_WriteEnable() != HAL_OK) 5 t! S( r! H; l0 C% p/ s* v
  38.                  {
    # A7 E2 Z8 W% v# `
  39.          return HAL_ERROR;8 l$ R) Y& u: N6 C. d) c$ ~' }, M7 J2 }
  40.      }
    * K% V1 R: F: I9 [) U8 L
  41.      /* 发送命令 */9 H' K  K' Y/ p- _* I
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    & A" ^& q# t( q- H( T! {: w
  43.      {( w" W! e% `' s' L) @- q1 V, f, D9 o
  44.          return HAL_ERROR;! v8 {+ H4 i1 _+ j
  45.      }
    2 o0 W( c; k. D% j; n$ g( k
  46.      /* 配置自动轮询模式等待擦除结束 */
    ! ]: J& u- o; `9 }) p3 A
  47. - Z: r0 ^! g: y" o1 k. n
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    0 u" k$ b. v% J: {- d- H. v" y
  49.      {" R$ W' O2 y* X. F
  50.                                 #if  QSPI_DEBUG2 S+ |! u  o4 s: Y" g2 R5 Z
  51.                                 printf("erase sector error!\r\n");. @7 `/ k3 d$ C" m9 v
  52.                                 #endif) ~" S) ]% k4 ]+ Z2 q% C
  53.         return HAL_ERROR;
    8 W1 [. n% E% R& L) ^! k
  54.      }1 {7 H. j+ H& w4 U  i1 T' o( ^
  55.     return HAL_OK;5 p8 c6 I0 T& S- M( q
  56. }
复制代码
/ o; e. ^# v5 ]9 l0 m6 C5 R# o
第二种情况是全片擦除,时序如下:( R( h; m2 a) i# z4 X8 }
4 L; v' L/ \( s$ ~+ v3 D9 z# [( r
20191006140919805.png
6 T( s+ i4 S- m# x' D# v% V2 Y

! m( @4 ^  O1 X  _+ ~5 ~全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。$ I/ ?+ Z; B' X" B+ g2 ~
6 E3 |( D  ?5 n$ g8 ]! H! C% @& t
  1. /**
    - t1 u+ j6 @" u1 o2 J
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s
    % |& I$ s/ m8 c; a1 h: V$ v
  3.   * @retval QSPI memory status
      d4 O3 l. L; x
  4.   */) I! i1 l# P) S! U" ?
  5. uint8_t BSP_QSPI_Erase_Chip(void)
    2 n) H8 \: L" Y, l8 g+ }
  6. {
    5 ^+ ?1 r3 q0 A* ^/ c! E. p  g
  7.   QSPI_CommandTypeDef s_command;" V& w0 b6 `, I4 W

  8. , R3 Y8 H0 f7 a6 m8 }* G' j8 Y5 s9 c5 H
  9.   /* Initialize the erase command */
      i) d+ k* N+ K
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;" Z  u; J2 `$ {1 ~& X7 i
  11.   s_command.Instruction       = CHIP_ERASE_CMD;
    , @& m. J! M( ^" X
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;! o: P0 w1 n) f- ]
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;/ }/ j* X5 A; M* {5 o& u- n
  14.   s_command.DataMode          = QSPI_DATA_NONE;
    . r% y2 U- I$ c( P7 y4 w
  15.   s_command.DummyCycles       = 0;  V1 M6 e9 x2 w% y: E7 X* z
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;2 S  D! o% L% T- Y2 {& @; v
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;9 |1 v0 C$ t7 s4 u  Y' G- K
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;* f) P% g3 S% K- {; S4 ?: h
  19. + v" I' G! f& z/ q: z( H" J
  20.   /* Enable write operations */& e8 c% p9 x, ~& @" n1 A% z
  21.   if (QSPI_WriteEnable() != HAL_OK)
    2 i9 W. S2 ]/ Z/ i, b) y
  22.   {8 k; r% }% `% V: Q0 Q. o( k
  23.     return HAL_ERROR;( e& d4 y0 M# u- B. H
  24.   }" E4 h: H4 O% K* C8 ~4 ?

  25. 7 y( y) D% T( b2 @
  26.   /* Send the command */
    # a# ?. j) x7 R( M1 i9 V' w: e
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK). }1 }, ~) w; j, W1 D. V
  28.   {
    ( ?$ M3 }9 Z3 d; e7 i. ?
  29.     return HAL_ERROR;
    + f+ P- l, r+ S/ ^
  30.   }- u2 s6 b0 ~' o8 o) H
  31. , y$ z- N9 ]# q; [0 X, j  n
  32.   /* Configure automatic polling mode to wait for end of erase */  : i: K. g7 Y) v' [: R5 I4 W" W; a
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)" d( y. g9 Z# f2 M$ `/ z$ o% O
  34.   {; s. I: v, k6 E0 M1 z( @
  35.     return HAL_ERROR;
    % {: s3 s  S; B6 N9 n7 T3 _2 D( j
  36.   }3 r0 x8 c; f7 |' C
  37. / o3 \# `, Y$ {' ?% |# a
  38.   return HAL_OK;; M( t/ b: l' T, x0 H2 e$ X; z
  39. }
复制代码
4 J+ N1 E& x) ~  d; ~& r% E" _
9.测试程序
& E$ j& x" Q* n: ?, F9 h" C) C- C* w6 b# I+ W8 `( C6 i
测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。
3 L1 O# w. t6 W- N# \% l# ]  _- I( D
  1. unsigned char write[4096];/ f" I) c' p  u$ O7 B5 ]! r2 ?
  2. unsigned char read[4096];   
    ; B, V9 P/ K' b9 ]0 N, C  _# U
  3. void N25Qxx_Test(void)  {! l, p; o4 g' w7 s
  4. {
    ( l' B: @% h9 @( A2 N
  5.   uint32_t i = 0;- R% F' y$ ~; T4 x* l+ R

  6. + c$ K! s( F! G( c- d9 O6 u' |
  7.         for(i = 0;i<sizeof(write);i++)  Z; f3 E* h* ~/ f! H3 z
  8.         {. e: t2 J2 G% o1 n/ @% s
  9.     write=i;
    . z) G& _  ?# j  e# b, {% c5 ^; i
  10.         }' @% s' E2 ^! {7 o- X
  11.         
    2 C; q: i$ L, G$ j$ [
  12.         printf("write data:\r\n");        * b" F% Q/ }/ x  K. F. G
  13.   for(i = 0;i<sizeof(write);i++)" A! d* }7 _9 l" ?9 Y1 L0 P
  14.         {( z8 M8 E: }$ [$ O' O) [+ }3 `
  15.                 printf("%d ",write);
    2 I5 \- {5 a- o, {
  16.         }0 Z/ j) f& {8 q4 k* g) R* B
  17.   printf("\r\n");
    ) f- [0 }9 ]8 X, }$ W
  18.         printf("\r\n");
    . P1 F, x* B9 q: S
  19.         printf("\r\n");% @# y9 H  p( y) I2 J. P
  20.         
      j, p0 G9 V. M  b# v6 m8 ]* s& t
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));- g  I0 T# W, D  q  e% l) f
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        
    6 l& ]" d4 n" X# w4 F! p3 J
  23.         
    ( S2 r, q9 F, ~: R; c( @9 b
  24.         printf("Read from QSPI:\r\n");
    , h& ], O# q; X+ {: F$ D
  25.         " H, Y, L/ L9 i7 }  ?3 r) L
  26.   for(i = 0;i<sizeof(read);i++)+ ]: u( ]% j+ O/ K
  27.         {7 }' H. K! u7 f6 h. b* O, h
  28.                 printf("%d ",read);7 a# v% @- s1 ]' }2 t
  29.         }* q* k+ x; q: X. ~# _+ t4 m! Q' K
  30.   printf("\r\n");
    ' _$ f: S# O7 q1 z5 Q4 Z
  31. }
复制代码

7 J" A5 Y$ F) y% r1 N6 @$ T1 b写入的数据(部分截图):
2 r% \. x- Q0 E' W
3 Z: o0 d2 {: h7 }, x: j' r- C
20191006141636369.png

& O+ O2 R2 N* p: C/ S7 j
, Q& O8 E$ r1 I' a  M$ q读取的数据(部分截图):. k; z* n9 w3 b$ x: C
4 @2 {3 T: Y, E) k2 {5 @
20191006141537697.png
3 u9 B- `# a/ L9 l% p$ d4 {
. f5 X  ]; T! r. O- C6 p' {  J
截取相同的部分,两次数据完全一致,写入和读取成功。2 M/ N/ O' h5 e2 w, q: J

- Q% `- k' R" V7 C8 s4 |最后,讲几点需要注意的地方:7 K" |( j2 Z6 W. c
( g& k; `4 T6 \9 Z9 L: }
1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。
- j0 f3 O+ e- b3 d4 d$ E) s7 J8 j
4 q1 [) Q7 ?1 m) w- y! ]4 m- Y9 r2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。
* n  Q# X, t5 W  [3 n' Y4 q0 T6 {1 J+ ~3 U% R) u4 H) r
3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:
3 W6 V" j5 |$ e8 j" Q  c 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
( M) q3 r( n4 y( E& @+ x, ^! c3 v 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。( [. I9 N2 N: N2 r) [

0 K/ H5 d9 p# H1 x9 H6 c4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。: t( D) y5 [1 i  F/ {
6 x8 Q6 g, U' A, l
5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。
  p. F" K& C: o8 ?, L1 g- b% e+ `' O( F8 l3 T
20200316162249870.jpg

8 p4 ], M* @1 m; a/ j
" E$ ]8 y0 r5 x" h% {. j7 r6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。
, {  k# P2 ~7 e/ ?7 D1 q$ S; p! d$ w8 J" k1 d- ^9 x3 r
7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!
+ ~/ R! V0 E* C# ]3 k5 z9 N% v1 ]
+ @0 O4 v+ m( [, j, l7 ~0 \( x- v% d8 V" d; z6 E

: e) S; a7 S% O# e% |% }+ d
收藏 评论0 发布时间:2021-12-16 21:00

举报

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