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

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

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍
5 R+ {( C4 n5 v+ [" C( g5 W' i4 ~. F# \1 T( H
QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:. u4 `- H3 g# j" g  D2 s" f8 l

' E. J6 ]  B0 c" E 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。
. j* ^4 l* l0 r# e& D+ F4 ~" z$ C% p
 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)
$ b5 N& T! u5 e8 Q) w1 g
8 L' I2 i/ ]: X4 y 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器1 O+ l- ?/ u( P& I- B5 `2 \: u" H
8 G; j6 w; ]; f7 B) k
采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。
/ H5 n# c& b" Z2 f/ K$ ?4 \0 t1 A, W; S+ d4 m+ d+ C  Q* {
内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
* c4 j$ r/ i! q: u7 c) S. y. L. L
7 V  D! }% A( s1 E& \) u内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。
' m* @7 d1 z7 J4 D9 u% V/ R- u+ |+ ?! R/ D
二、 STM32QSPI 功能框图
: m, ^/ `( Z7 t3 A5 q3 B3 z! z4 f/ O& ^. g  [
20191006091636349.png
" m' n# X9 a' L2 A

; e4 J) L# ~8 W/ G9 ~STM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。, m. J- D3 u4 J1 G, l% r0 M
7 ^; L3 K. E/ q" {
三、QUADSPI  命令序列. B' g: [& M  h5 Z5 K3 u) c" w' \

5 _2 \) D  K$ z1 nQUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五! w0 ?  q4 J7 D& K$ c" C1 k9 o7 o
个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。! E0 C+ Z/ F2 X, r
nCS 在每条指令开始前下降,在每条指令完成后再次上升。
- u6 m# s& f: _0 l$ m
' t1 [" l* W7 r
20191006092242666.png
. A5 P9 b) S; }. F% q
0 b; S5 v. Q* L
上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.
5 T: B/ P& X/ |+ Z( T: W$ ?5 ^/ b& Y  {; Z0 Y: H7 Q
交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);! r3 c; I1 ~( _* r# [8 R
0 Q5 T$ ^* D2 u- b. e/ O
空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高
% d7 V/ V6 L/ Q1 m& |+ m: Y, K( J2 g的时钟频率时,给 Flash 留出准备数据阶段的时间。
$ V+ A! J$ H) W0 W. d3 }) _# n若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
, G$ z+ D; ]# R' B" `3 P$ h5 ]
" H; z4 Y* Q, \此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。0 B2 w6 Q3 ~" Z7 `" ]: Q

5 n2 e& A8 h; s四、QUADSPI  信号接口协议模式$ ~+ }- f# {9 Q) r
2 W" y6 Z/ Z+ W  D, x5 [. Y$ G
下面提到的几线模式都是指数据线的根数
- F) z  \7 z# U  X6 }( S6 |2 C
% Y- G9 d5 z! g- c单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):
& E4 B: Y3 \1 f* l传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与) u0 Q( p5 v* n3 f
IO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。
9 o+ k. W- m3 p. M, h4 u通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可
- [2 {- c. H+ E% j2 Z对不同的命令阶段分别进行配置,以使用此单个位模式。; A3 m7 Q) @4 t; u* U8 p8 i4 I
在每个已配置为单线模式的阶段中:# L$ [- U3 Y7 t
 IO0 (SO) 处于输出模式
8 i" b" H9 W+ W1 o; {3 D IO1 (SI) 处于输入模式(高阻抗)  y: i7 X3 a( Z! ]# O9 `
 IO2 处于输出模式并强制置“0”(以禁止“写保护”功能), m) K1 ~9 u0 I% u9 O/ q1 ~% v- c
 IO3 处于输出模式并强制置“1”(以禁止“保持”功能)# K) T! n* y8 T3 D
: Y- G/ h) n0 V
双线 SPI  模式:( m% e/ m- b& [# k/ U
在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。
3 z0 |! a: o1 N3 p通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可* i0 C1 R  ?9 `$ g9 _* V
对不同的命令阶段分别进行配置,以使用双线 SPI 模式。: l2 K, y  u  R+ \$ w$ O+ w
在每个已配置为双线模式的阶段中:2 B: {: S# ^& a' n' g" B# |
 IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出5 @+ u$ a  c1 `. |! P! Y
 IO2 处于输出模式并强制置“0”9 |! Z1 `* `) `; L$ Q2 `
 IO3 处于输出模式并强制置“1”$ }! _! s+ D5 d' B! z
在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。" g! z! R! U) ?, B2 k2 P' X

+ e6 ^3 F+ q2 A7 l, D) a
" u/ c$ e0 F3 w- ?7 {+ n四线 SPI  模式:3 U% }0 X" K2 t8 M' R
在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。# k* m" K' ?" C- o) j& s
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可& T* O  L& O3 }5 a( a, S
对不同的命令阶段分别进行配置,以使用四线 SPI 模式。2 Y6 F4 {7 b* W; u  W' l( d7 i- _1 s) o. G
在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高
* M! |2 H5 f0 m/ {! w& j! v阻态(输入),在其他情况下为输出。' }7 C  Y$ d+ ?/ F: {. @
在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
7 a4 I3 g! S* d0 s) _IO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI
' b% Z, g# T( w; A$ |激活,对应 IO2 和 IO3 的引脚也可用于其他功能。
( i1 H  R5 p+ W% p' E7 F/ [2 z) y, k( \; k2 G

6 {+ P" N" n6 W/ TSDR  模式(默认工作模式)2 l& F  K9 {! F8 |
默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。2 I/ a' {' E9 D+ v4 W4 J
在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK
3 \9 G" O- D' B0 K% @的下降沿发生转变。
+ D9 K$ m. `" T* S, e: q在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
! t. Z: B8 w3 @0 Z( _况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。. w2 Z. |4 V' Y2 g; w9 s! D  }

+ T. W, \& h: t* }
$ F) i- U5 t$ f# a; x4 D( IDDR  模式
0 m8 o! h2 t2 `- X( e在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信0 C: A2 ]0 u4 n
号时,将在 CLK 的每个上升沿和下降沿发送 1 位。
5 u. p0 q. J' V9 @0 Y) H9 d指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。
9 v& |' v5 i) L  r$ r# C在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数
6 Z; |+ x' d( R. T( n. O- d据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期
9 I0 T8 n" b: x; q- w) L/ c: x后(下一个反向边沿)对信号采样。0 t9 _, i3 T  t( {
) y/ h/ K+ b  ?8 F
五、QSPI 配置(HAL库)$ S( J' z. Q- M7 I* [

$ G, a6 ^# R8 E4 v0 n4 y% i" z- k1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟
7 \5 r1 J' r3 Z; H: `! a( h1 J: Z
20191006121236849.png
2 i  D$ U* W2 c$ B
* k1 W$ m. f" f8 d0 R5 I
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)
    1 o  y5 o+ D$ V# i  G; z. y# v: H
  2. {
      c1 [' S% R; y" R1 b9 o# ]* B
  3. 1 t" o6 H, T3 o! R1 C! F  r
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};; p: ]2 W& J' ]4 c; v9 m: H. [
  5.   if(qspiHandle->Instance==QUADSPI)* R/ P- }0 Z: b7 c. b/ \
  6.   {
    ) `% a" ^- ?3 b/ o
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */. i- ?+ O* u1 [. H4 k

  8. ) H* _* t2 {" z; A" X
  9.   /* USER CODE END QUADSPI_MspInit 0 */
    5 }4 g! A/ p0 |& J) \$ p- l
  10.     /* QUADSPI clock enable */
    6 @+ W$ c" L; Y
  11.     __HAL_RCC_QSPI_CLK_ENABLE();
    5 m6 R/ j+ k' O1 ~  L( I+ k

  12. ' s( t+ Y. y% X7 K) K0 J: V. S2 b
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();
    . |& R: ]5 d6 h: w! ~$ B/ T
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();  E) i/ I8 n# ?! }* h) |4 N
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();
    ; c$ g. I( O- l* u
  16.     /**QUADSPI GPIO Configuration    2 h" k2 Z9 k, i; B2 B) B
  17.     PE2     ------> QUADSPI_BK1_IO25 F3 b# u, |- b! p$ T' b( y
  18.     PB6     ------> QUADSPI_BK1_NCS( n% f$ \% u$ b; z) V- d( g- V
  19.     PB2     ------> QUADSPI_CLK/ j7 H: f, ^9 [
  20.     PD12     ------> QUADSPI_BK1_IO1# M" I$ U- `, N. O4 t( b
  21.     PD13     ------> QUADSPI_BK1_IO3
    5 ^4 O+ s( T, s( B7 w
  22.     PD11     ------> QUADSPI_BK1_IO0 : K4 [3 ?; _5 A3 |" A$ Y% y
  23.     */
    0 e0 ?* u) F7 E
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    + K" x: V! i4 _3 _; n$ C
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;8 s" u: u5 U; D  H; C
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    7 Q, e" B1 `* R3 U
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;/ ~) D) v  O1 D2 j
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    3 k( \' |* U: U( @
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);) g- n! C1 k1 c6 W+ P
  30. ! j$ S; v" T* Z
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;' B2 I: Q! `$ a+ Z
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    2 i) \. g# u, s' x/ T
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;0 T0 u% M! G! B, n. I8 N/ V
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;+ T( {) Y5 v0 I8 h0 v  ?' e2 ?
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;! i- u. ]5 u8 c: [5 A; |% t1 x
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);/ t2 S( A9 h) p% O7 x0 t2 J

  37. 1 m- Q6 o4 g  J6 ~6 E# ?% k/ C
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;% a. N1 c4 A  c/ I9 @; ^" O
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    ) Y0 L) k/ p) o9 ]% K% |& P" O
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;1 H; V% M) M: |8 W; G
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    5 e$ c3 U/ E* C
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;1 \  o  a6 Z8 d: ?: E( |: L* [
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);  ?/ \7 d: U1 P
  44. % b' T3 R0 p( t- d
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;
    8 n; I1 J2 I0 |* C" N
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    $ e. ~3 A. z( x" @
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    0 K2 A! @) I5 z7 g3 S0 D
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;: H( y! X- v! Q+ j$ J3 Z  P  Q/ n
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;- C4 H" V  p8 I, j% w
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    7 B4 u" S: X, }( N, \% c5 q, P

  51.   L& E6 t+ F8 `- x
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */
    6 z5 ~( F2 j1 M

  53. 4 d4 c* U. Q0 h, Z  B5 K
  54.   /* USER CODE END QUADSPI_MspInit 1 */" _9 P0 d1 o( Q) Q1 r! _+ b
  55.   }
    : T/ F! y) d( }" G% ^
  56. }
复制代码

6 ~6 H( x6 _* Z2.接着初始化SPI外设( o  G. s$ f8 t$ D

  1. 4 N" V8 _3 G2 ?
  2. QSPI_HandleTypeDef hqspi;
    ! B; `6 x0 o" O! V$ [
  3. - e' i# e+ r/ I) U1 j1 S
  4. /* QUADSPI init function */6 K! z" y7 X" }' f. |
  5. void MX_QUADSPI_Init(void)
    5 `0 @& L$ A2 \0 F  x
  6. {: b5 F' `9 C9 R- k5 n
  7.   hqspi.Instance = QUADSPI;//QSPI外设) `+ A  O! K- G8 ~8 L! W" a) v3 G
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M( n4 t- G4 P( h  h$ E. {
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节+ _9 G9 d# B1 k5 o: l) E3 O
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)% a8 S1 {! f7 J+ M' W/ K) r
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
      \6 t- b$ m8 A5 _
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数. R4 X2 ]) b+ G4 n# u1 x' L
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式02 w+ A# I! E( M- s! W: o* o5 E" Q
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
    % N: y; r8 K) r% Z7 b( M- S
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式: Q- L3 Y& Z- h" ?4 z
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设
    # c: w! I& g  b
  17.   {2 N- o" P9 r5 _* Z' H
  18.     Error_Handler();, T! y- C& b/ T
  19.   }
    ! E$ n4 {9 P* @
  20. }
复制代码
6 j. N$ c' f5 _4 U
3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。; B+ A7 G. D* g/ E3 n

$ L- L$ w- i0 e( U我们首先需要配置下面的命令参数结构体:
; u- L/ D5 F7 k1 G- N( t; y( {( k( ?  E3 h7 S1 b- x. z% k2 [6 S7 k
20191006122437823.png

( k9 y" S. a" ^/ |# e
8 r" j* {' _) l8 r# VInstruction:指令类型(读、写、擦除等等)
  X, M! V% f. E1 w9 O! _6 z2 O
$ C! `; [1 l! p* e/ L, _Address:需要读取或写入数据的起始地址
9 @& o# K+ U8 F) i0 B
' W6 u! x" r3 h) B- ]' `NbData:需要读取或写入数据的长度. z/ z5 L% F" j+ n4 c. o2 F
9 a# v; d3 ?2 `( ^$ `
DataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。
0 ?$ a, B# Y& s, W( l# w' X" J# W- l! i3 z" v( ]& D0 o
这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!
7 s  |( _; t1 Q
* K* V% k! w$ ]" N# B: [ 4.读取芯片JEDEC ID" K2 D7 f* Y( ^- r1 z

, P  k0 x/ N$ l* [5 J7 \- o首先查看手册上读取JEDEC ID的时序:
9 G. z4 I9 ~6 {! A
( k, q  t1 _, L
20191006131644114.png

( V0 R7 x4 H  F4 Z; E
/ l+ w/ g" z* j可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以
* |, w5 w+ X' _1 M2 d7 G- r! Z0 j
" a8 s% F* {2 N5 G7 FInstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
' {0 E+ G9 l) J' C5 J; _: f
1 U+ B' _! U6 E$ B5 q0 v$ X6 d0 xDataMode = QSPI_DATA_1_LINE;//数据单线传输模式
1 Y7 Z1 I$ e9 i- w8 A" H6 o
- g: w% p7 J2 X2 W/ O- WAddress和AddressMode 这两个参数的值都置0,表示无地址。
4 k9 U1 d& X8 e* c( }7 f' _: o* n  }
, p/ H* ]6 u% jNbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3
$ e. `4 v; L" C3 p
; \+ U: u. {* R4 ]$ d8 C其他值和下面代码保持一致即可。 2 b! U) P1 g# I: h

4 d" T- }; O; Z然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成
7 \1 l$ ?/ T# a' \/ K3 e1 w& i* w( u! z  Z& v0 i) u
最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID( o0 w% I. |3 G- p# @  A
6 d' Y0 u/ C9 M4 j( O
  1. /**+ |- Q+ c3 N8 q8 A4 L
  2. * @brief  读取FLASH ID. g2 g1 f* G$ _2 U$ J3 p
  3. * @param   无
    0 c: y( P$ p- Q  W/ H5 M5 E2 I+ j
  4. * @retval FLASH ID* c, d4 t3 I% O
  5. */9 i% x: x1 r. X% g: R# R2 O
  6. uint32_t QSPI_FLASH_ReadID(void)
    7 v; W, i3 E& u: \* i* Z; m
  7. {9 W; O% ^9 Y8 n6 {! b* K1 a( c8 H
  8.      QSPI_CommandTypeDef s_command;
    $ ~# ?' H7 k1 Z) ]  S

  9. 3 _: o0 r4 \" @
  10.      uint32_t Temp = 0;* q/ l5 q- K) t( ]* w# E
  11.   k) z# n( x# w
  12.      uint8_t pData[3];' z9 t: y' P; l* Y! v

  13. - @- r$ {3 l* h6 B
  14.      /* 读取JEDEC ID */4 z$ r# X3 N, x1 _
  15. ) ~% R, O$ b7 L, l! j
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。! q+ x5 i) Y" N

  17. ; s* ]$ m5 B( @+ Z6 k2 x0 F
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令7 f4 T: y' F2 l6 ~0 S1 Z2 D
  19. + T+ G7 ~3 O0 f' t/ g# `4 B  u
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度; ~) }) X* C8 I2 t: s

  21. 7 F8 ~9 \9 |( ~$ X3 M. U( f$ `+ R- H1 e
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。
    1 x* R) l( T! _8 m/ E0 P# M

  23. - Z+ a# z4 M# U+ ~
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。0 Q6 K  y6 F- j( M* `/ m) E
  25.           % ?* E$ Y, `8 V2 E
  26.      s_command.Address           = 0;   6 \2 N8 U$ [2 h
  27.         ' z; q& \6 R; g( }' y; T  e
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度) p$ m  x8 }$ F7 d6 k
  29. , I% @6 Y5 Q* t
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。
    0 w1 X" R( e; S9 T8 k6 |

  31. 3 \4 G, c+ H. b6 f
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。
    7 r' L2 {, @0 o- I7 D
  33. 9 t8 R; @, N) ]& p$ f
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。& T  n7 A* r6 P3 X& ~- {
  35. 9 ~  r/ T+ G. g
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
    6 b9 k! ^# G7 m0 J/ B+ `2 J
  37.                  
    3 l& z  b/ `5 `4 }
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。
    ) @) P* J# i  }" e& J1 e9 s' Q
  39. 8 |2 l3 D# f& ]1 g+ x
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    6 E% ?; ~0 R- }' _% V
  41.                  {
    3 w$ e% {3 v3 k  E) y8 A
  42.          #if  QSPI_DEBUG
    + H2 ]$ w- m& l$ d: w9 O
  43.                      printf("send read flash ID command error!\r\n");
    ( t* L3 @. K. p, ]& l! Y7 L
  44.                #endif- ?; M$ W( D$ V
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */
    . \; a" T' y  M) @
  46.          while (1) {}
    . h" U3 u9 z1 k
  47.      }4 ^& ?3 ?# i* e) _- p- u$ Y
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) $ U# Q$ `9 b5 ~5 I1 G
  49.                  {
    ' M. \# G( n/ E2 @
  50.          #if  QSPI_DEBUG
    ! f- o* O) `+ @4 O: \* I
  51.                      printf("read qspi flash ID error!\r\n");2 C# L/ M: M) d1 @$ n& e
  52.                #endif
    * R3 i& N- k+ Q) y4 F2 i. f
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */
    3 r7 v- Q# g  P3 m1 v! ?. |- ]
  54.          while (1) {}. W; u/ A  J3 [/ X0 r
  55.      }
    3 E1 h$ n4 ^4 b" g+ V8 l
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );, C, i$ H+ I3 t1 L
  57.      return Temp;
    % A; n  p1 x6 C
  58. }
复制代码

* A  w# X. k7 y; m: F: t 5.FALSH 写使能2 y* l) z. ?" l* w

7 t) s/ _- i* X8 D在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):
% V7 Z2 G1 M: T9 e( n. r1 Y. F1 ~( v4 p; W. n8 _' h" X2 z. `
类似的,需要查看芯片手册,写使能的时序9 K) Y5 Y. f! F7 _4 C
1 g+ {, |: B7 y4 n. ]
20191006134057767.png
9 G& @- P) d8 ?" Y  l

6 a/ P% ]5 }; @" v5 e9 J6 K可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0
4 B( j; m9 o% M4 T' M8 A- ]& t5 ~* a$ S  b
  1. /**# s2 a9 p. t8 t
  2.   * @brief  QSPI FALSH写使能& {/ A) O" l9 l
  3.   * @param  None
    9 I! F. T8 Y3 d7 R! |
  4.   * @retval HAL_OK5 A. }& @2 [/ X7 J5 ~6 z2 A$ j
  5. */
    / n. Q) C* J8 p% }
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)2 Z' M- i8 ^. _7 r7 _; ?
  7. {
    7 ~" a" ^; i0 T9 ^
  8.   QSPI_CommandTypeDef     sCommand;
    . T# ?# f: D& M+ s7 ?, d9 u
  9.   QSPI_AutoPollingTypeDef sConfig;
      d) }% E- s0 g, X2 m
  10. / x" [" I+ r4 x7 D+ X
  11.   /* Enable write operations ------------------------------------------ */
    7 T( }% l4 z& ?
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    + P: e4 F3 Y- l, m/ @
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;+ G$ R0 X/ b4 i7 m. L' T
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;
    : @2 S1 E# j8 Q# C. X; e2 b" m
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    3 c; f4 P" `0 g, {( q6 M' p
  16.   sCommand.DataMode          = QSPI_DATA_NONE;
    5 w2 O$ ~$ B+ g1 l9 S
  17.   sCommand.DummyCycles       = 0;$ i2 R+ g; Q) j0 k- z0 I( y# @, x
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;+ H1 \1 N2 y! e5 f/ a, X, O
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;& u8 @% @5 Y( G, A7 |: i& p! B
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;1 [2 ~8 E: c( V* y7 K" o

  21. ) `* `. S. i1 r; b
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    # I8 f0 D$ @# h  C( [/ L0 V
  23.   {- n$ ]2 B# s/ x
  24.     return HAL_ERROR;2 G0 R0 v4 L* r6 g/ [6 R2 d
  25.   }
    % \* \0 M' s5 @8 N& l4 A

  26. 2 r9 ?0 |" o2 m# `8 j! j
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  . k2 |1 B' y2 }
  28.   sConfig.Match           = 0x02;4 P* c; b* y9 l4 J0 U
  29.   sConfig.Mask            = 0x02;; m+ r( `( H* @6 {
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;7 ]/ W2 T, C% |% }
  31.   sConfig.StatusBytesSize = 1;1 ^, r, ^2 \7 H/ p7 x7 p+ M
  32.   sConfig.Interval        = 0x10;2 _2 ^/ W1 x3 `. y( G
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;0 p7 P+ z1 G' c9 y7 k7 }

  34. 1 O9 B' ?& a8 |" C
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;
    4 Y& R& n: A0 o. c$ ^
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;
    " K' u% |, I# ], w5 d4 ]

  37.   B2 K4 [# t0 T. z  X4 Q5 d7 h
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)" m1 d* c0 H9 X6 ~
  39.   {) ?8 O# y5 L  b
  40.                 return HAL_ERROR;        5 e/ C! b3 X5 Y/ n
  41.   }
    7 [, [: j4 T' ?  P6 H5 {3 F: O+ w
  42.         return HAL_OK;
    ( |" _. I; x. m) y3 R! C
  43. }
复制代码
- b( A7 M8 d9 k, m' X% d$ @
这里需要讲下 QUADSPI  状态标志轮询模式:+ }0 H% `* l: k4 Q- z( N" O
在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。2 _$ F3 {9 c% W8 }
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。
0 L- A7 t8 Q2 E- r$ e3 T对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入
5 f& z" k! t# t4 W: M4 hQUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
) k+ o8 n! e6 I# L& j一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。3 s$ Y5 V/ O/ y8 p6 \9 y  O2 Q
在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。
0 A+ G2 o& f2 N) G% l2 A如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内$ x1 r' `6 {" i. z5 t4 f
容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。
0 b) X0 [0 ]# n& h7 }5 G如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着
% E) {3 q; [0 O1 {2 `状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。
8 m) ]  I# B- b如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。; Q: d( G' V) Z; J+ s
如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。$ H2 S" l: k3 h: k- r* w
如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否
8 f& ?, z3 x. X: {则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性$ V- l' y' G3 e/ H$ H
访问。
; n# J% W6 z/ Q5 ~4 x& c3 t数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不7 @: O4 A& {4 }6 R6 {
受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数8 W% w/ l7 H2 _3 F
据读取后清零。" R) t; l/ L6 G# ~2 Y! I. @0 V

* Z$ x7 l: U5 G简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。* @! a  H! l8 Z% y+ b7 q

- ]- V' U0 R: I+ I1 M6 u6.读FALSH
. L9 }9 M4 s7 c7 Q
/ K- w" h4 W$ q4 ^这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序
( w6 h$ \* ^5 X8 Y* `3 ?: |: m/ B" D/ m. f! l; ]
20191006135244693.png

! \$ `' K, x2 H- t6 n3 L/ N* j& F2 [, X3 w9 }! J/ ]
从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:
$ n7 c% t! O% @# B1 o0 W2 p0 ^, P( X5 _, b, I. F
  1. /**$ H$ I9 ~  E: C* n& l
  2. * @brief  从QSPI存储器中读取大量数据./ u6 X9 K. V% V% e
  3. * @param  pData: 指向要读取的数据的指针
    3 f, _  d- U/ C( u6 g/ \* @
  4. * @param  ReadAddr: 读取起始地址: l  _) p7 X! C" ?, n, `
  5. * @param  Size: 要读取的数据大小
    # Q5 [( |" E2 f/ @! ]) E
  6. * @retval QSPI存储器状态
    . l6 {# K  F! N9 T- n
  7. */* \: l8 ]: s. c- s) V( \' ^
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)  J- X/ E9 ~- \
  9. {: r8 D; @- m& v/ b* w9 H- [
  10.      QSPI_CommandTypeDef s_command;  c9 Z$ ^  y& M& I( i% }

  11. + C; Q" x1 t8 d( s8 J
  12.      /* 初始化读命令 */
    - I! S$ Z) T* D% u0 x8 R8 w
  13. ) f5 P1 k$ }; \4 k) D
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;* \+ l! k* K7 D/ s0 ?; K

  15. " G" X$ B3 u9 G
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;4 u0 R% Q5 E& d& {3 f
  17. ; m! \, D2 q# R
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;- A) P: H" t% M; U/ I

  19. # P$ T$ v8 {# j. a" q
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    1 f& N  B( O* W, v
  21. " p+ |6 q0 l; U4 l
  22.      s_command.Address           = ReadAddr;: U, `, x8 Z+ a5 F& w

  23. + y( x9 w' z* J8 @2 x. L, E- I
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;2 H3 @. j% t; A- D' m, i1 I

  25. 8 z7 ]4 X! v- L9 v9 M
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;
    / s7 v2 Y5 k# ^2 H! Q7 ?  _9 b
  27. ) G) ~+ h2 r2 x* N/ x* u
  28.      s_command.DummyCycles       = 10;" u' p) y0 {: t8 g

  29. 0 m/ _# `& b# M
  30.      s_command.NbData            = Size;/ l* ?, Z% S) e4 \- v, Z

  31. 0 z( ]  M/ K% v
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;% ^  H+ [7 G, Y( m" i

  33. ' O- }  i1 Q2 @
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;! T7 D$ g/ B- I/ @  J: Y

  35. ' Z7 ?* k1 X8 f( b, g) p
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    ! ~5 U9 ?! M0 w3 j- ~6 E' X  x
  37.      /* 配置命令 */$ O; G% K' p+ V1 d
  38. ! |1 A' ?- L* ^  _8 a$ i7 J) \
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)/ ]" O1 S8 Z' S
  40.          {
    : }  x8 j. C+ c+ A1 v+ r. x2 D1 A: U
  41.                 #if  QSPI_DEBUG4 y- u) g; O/ L1 ?! u( o) `# c
  42.                 printf("R-send read command error!\r\n");
    7 _8 Z8 r7 q3 W) \7 R
  43.           #endif6 Q# p) A& `. Y; n$ p$ V- x
  44.     return HAL_ERROR;
    5 j, e! l4 Y8 U  y8 `) s2 T
  45.    }  
    1 H" N2 h9 p: s" C% t8 e3 p7 B- l
  46.    /* 接收数据 */
      x9 F" W+ [3 ]7 z5 D
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK)
    ' I8 R9 T8 N" s$ N+ s
  48.          {
    4 i+ T" O3 a- \$ u2 Y
  49.                 #if  QSPI_DEBUG( ?4 e  f) J/ T! u$ o- W& J4 ]
  50.                 printf("R-read data error!\r\n");  w" {& A  f( e  `$ E' c6 [. @
  51.           #endif8 X: Y# |+ e0 @0 z! t
  52.     return HAL_ERROR;
    ' `1 f- Y. l! J
  53.    }
    ) s0 k4 F5 c1 ~' ?5 N  c# |
  54.    return HAL_OK;
    6 J- d+ T5 s/ _* C- O5 @; j2 p" _
  55. }
复制代码
* j7 G5 u8 M: H) n8 X
本函数实现了从FALSH指定地址读取指定数量字节的功能
9 h" c2 T. \: C
( h3 O: Q! k8 G( N* Y- O- ^ 7.写FALSH# \: J$ h* @: P" |( n4 Y: h
4 D) Y( v1 d  c% Y( Q
同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:2 \9 I1 {& x/ b* Q2 s2 Q

( W7 k7 N! T# Y
20191006135836784.png

3 T" C7 L( _, k8 ^: R/ y
1 ^, u9 y! h/ W6 r& r/ _& T0 T! p3 z; R根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期 4 e5 L, U$ R! x6 ]" g

" `; Y5 ]1 X/ T
  1. /**
    + _) I! `/ A2 G" ~$ ~, h( [
  2. * @brief  将大量数据写入QSPI存储器
    ) D( z* U5 I5 z% o; m
  3. * @param  pData: 指向要写入数据的指针
    % Z7 E9 @  c" A0 i! Q, a6 {
  4. * @param  WriteAddr: 写起始地址+ J$ M0 ~0 k* B, y8 m: B
  5. * @param  Size: 要写入的数据大小
    + ~6 p7 d* J6 q& t4 k
  6. * @retval QSPI存储器状态
    % f, s2 `& A# D, ^- p
  7. */- }3 D, |' {5 i
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
      U* ~0 Y4 K, W, {6 j, H1 y
  9. {7 t: ?. E  G- o1 Y& l' c
  10.                         QSPI_CommandTypeDef s_command;3 P, l" a- j1 u/ v  ]8 U: s7 V
  11.                         uint32_t end_addr, current_size, current_addr;
    : ~  k' u1 b3 }- J; r
  12.             unsigned char sector_start,sector_end,i;/ N$ ]+ q. K& u( ^/ M* o7 B! [+ F
  13.         0 d- h/ u2 I; n6 Z0 `% \6 P
  14.                         /* 计算写入地址和页面末尾之间的大小 */" ?0 O$ J+ |& x" O# z# p
  15.                         current_addr = 0;
    - C' ^/ H  L$ j) c
  16.         
      r' J. L  J5 Z- t/ p
  17.                         while (current_addr <= WriteAddr)
    ! f) ~  ]3 V  t, F
  18.                         {8 A* ~% N6 f( Z9 n; d$ a4 l
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;
    6 b8 b8 R/ ?) |7 N$ Q# p
  20.                         }
    # o* b. b# `1 Y  j" J. T5 }& Q
  21.                   current_size = current_addr - WriteAddr;
    & U" Z) @3 g6 I
  22.      /* 检查数据的大小是否小于页面中的剩余位置 */( i& k$ ^) y: Z/ {, s8 ?
  23.      if (current_size > Size) 1 F( B9 O9 \  j) S8 K( v9 ?
  24.                  {
    + y1 R# p- ?* O4 H
  25.          current_size = Size;
    $ s" q/ S" j8 R( @5 N& w
  26.      }0 ?2 S- x0 z' x$ E* p; J
  27.      /* 初始化地址变量 */% Y# _) P5 T3 K9 ~0 p+ W; {
  28.      current_addr = WriteAddr;+ q( M- l: A, f, p3 ?
  29.      end_addr = WriteAddr + Size;
    ) d8 N- L" Z. `' K
  30.                  
    - q) L! p% A# }& V  M& `. E/ \1 r5 b
  31.                  sector_start = current_addr >> 16;
    $ w3 \; B4 x* r$ V* y- z; B" B
  32.                  sector_end = end_addr >> 16;6 u1 z8 m  \4 f( B
  33.                  1 g/ t1 q" _& ~" V5 I3 E
  34.                  for( i = sector_start; i<=sector_end ;i++)% X- z7 A5 @, b6 ^. D
  35.                  {1 ]2 h' K" J$ ]* {
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    5 d) O2 q/ l' B( v+ y. r% K
  37.                  }
    2 C: I5 w3 K' c# k: l6 _
  38.                  " }2 s- y8 T& A8 r; S
  39.      /* 初始化程序命令 */4 \9 x3 ^; R# M
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;7 B5 O% p' n0 D7 z
  41. 9 {1 t; |4 T0 R, T5 w( T. l0 w3 [/ x
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;
    # @8 F4 C) I; S( E$ B/ l1 U( S7 J
  43. 9 u+ Q3 V7 I+ M. ^1 H# [
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;5 B/ w) M7 V0 h+ h5 v9 z

  45. 2 S, Q0 s1 E$ E+ E7 d
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    / T7 E2 q' e6 a% U( {7 _8 z
  47. ( J' l) G( o1 F- n1 l- U
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;# z0 V" N' ~6 B
  49. % c7 ~5 Q+ s' o$ G( C) R" |; P$ _
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;. L! w; k& e& Q' u

  51. , `2 q# C" I4 E- J
  52.      s_command.DummyCycles       = 0;; H  }1 B. `9 p- \+ x, t
  53. + h: d" o+ b1 d6 S! w* T- `3 A
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    ) k- M2 C% g/ v% k" {' j( |
  55. : F& Y) Z6 O0 e; t1 e1 }
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    ! `8 i! n. J* V/ ~# r

  57.   x* V- f9 T: P: r* a3 o% X% G
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    / Y/ w2 V+ i, I: V/ i- Q; ]
  59. ) F& I4 J: R, g  A9 C  h; s4 a* F
  60.      /* 逐页执行写入 */
    7 u" m  K" [3 a5 F, z* @
  61.      do {
    # a; R# o8 d6 U

  62. + F# m6 f" O1 d9 Q! n
  63.          s_command.Address = current_addr;4 }$ F; f' r: f3 h: Z
  64.          s_command.NbData  = current_size;6 s/ S9 O& |" E* s, [, J# H+ j( l1 V9 F
  65.          /* 启用写操作 */% `2 s3 l4 @2 b/ c# X9 [& @
  66. 8 x( d  ]' v0 y9 i
  67.          if (QSPI_WriteEnable() != HAL_OK) 7 E5 f, L; ]* ?) j) X0 x
  68.                                  {5 B: x% z6 C6 K
  69.                                                 #if  QSPI_DEBUG
    ! V  W+ Y/ Y6 s$ _! c: \( P. p; Y
  70.                                                 printf("W-Qspi write enable error!\r\n");
      z. ]$ E1 ~0 a2 G- B
  71.                                                 #endif) n* }2 j$ J" w. s. U% O
  72.             return HAL_ERROR;5 g. p. }6 B' u$ u. t
  73.          }5 Y& @9 t" A- O- b; E; G: P7 q) O
  74.          /* 配置命令 */: S2 M  h& o% N- q# \: e1 K
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)# q5 x$ w1 o' |3 w: Z
  76.                 {                                 * Y1 N% e/ K- `( X5 k. g) Y
  77.                                           #if  QSPI_DEBUG
    1 w5 E3 H: a% v- J
  78.                                                 printf("W-Qspi send Command error!\r\n");2 r9 g4 k1 C. Q/ {
  79.                                                 #endif
    / n6 K7 o5 W% \( @( p* g- n
  80.             return HAL_ERROR;
    . x/ k" K+ c  ]% j) U9 A
  81.          }# S  o/ ~; u0 X: i2 H# @/ ?
  82.          /* 传输数据 */
    3 w+ \& @: r4 W! \
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    4 A; t8 N/ S' R. Q# Q* W% d
  84.                                  {% q, u+ T1 c* ?( ]/ B
  85.                                           #if  QSPI_DEBUG
    , a- ~3 j5 q* V( I
  86.                                                 printf("W-Qspi Transmit data error!\r\n");
    + w; j' j8 @% N. t
  87.                                                 #endif
    0 \3 s- u7 O2 h5 q" V
  88.             return HAL_ERROR;
    - Z/ \! d' R' ]; D8 s( [  `
  89.          }
    0 e1 T. R' l% Y: f
  90.          /* 配置自动轮询模式等待程序结束 */! H* A* k4 L2 Z+ A5 d8 i0 G% d
  91. $ B5 B* z: u7 T- S' Q
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ( [) _3 [3 d' g* u; O: l3 d9 B
  93.                                  {                                         ! c4 m/ b  G! a6 {0 E
  94.                                                 #if  QSPI_DEBUG
    2 F# q# P# _8 n
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");! w8 |- f, _1 m5 a
  96.                                                 #endif6 u& b$ e* c+ t$ W6 g( {
  97.             return HAL_ERROR;' }* _1 ?$ F1 k2 o0 G
  98.          }6 _( M$ o! G9 z  {
  99.          /* 更新下一页编程的地址和大小变量 */
    2 T' ?, J, `" e- m& ^2 ?# A

  100. 1 q. S1 b8 F- |8 k4 D) ?' s! s+ r6 ^' \
  101.          current_addr += current_size;6 e/ W5 K8 R; D& Z
  102. 3 z  [5 v7 _/ p2 g! I# I
  103.          pData += current_size;
    - ^/ B8 n6 U$ Z6 a. h  k7 J" O

  104. / a. G" F0 a: _* q8 k3 |' A
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :) b0 Z6 \, D% s: G

  106. % z/ f. k: T4 J( E) p' T
  107.                         N25Q128FV_PAGE_SIZE;
    % U% M# h+ ]: I3 ~- _" {0 E

  108. 9 T5 Z, u, D+ f2 S: p
  109.      } while (current_addr < end_addr);: j/ {2 z. D1 J8 G- I/ F5 D
  110.      return HAL_OK;
    # s2 M# x, u2 O7 ]
  111. }
复制代码

1 `2 Q$ l- T- d0 ^* G$ W# ^0 `该函数实现从指定地址写入指定数量的字节。+ U0 w; Y/ x  H* p3 s: K& x
. k' `. v7 E3 P) A
8.擦除FALSH& p5 D  J0 L" @6 n# T  o8 w

% \, M' R! Q, K: w- Y( {. v6 `第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)
& q; S# ]$ a; ~% `$ Z5 }  P$ e* ~" z: X. [6 P8 M% j
20191006140420479.png

0 \. ~: F3 c/ T
6 I) B+ ]" |8 S参数设置为1线指令 ,1线地址,24bit地址,无数据阶段1 a; g6 F, ?% C
+ C0 g5 W# i; H, R/ Z. K9 d
  1. /**
    + z; R3 B1 E, e+ T+ |$ L& v3 c5 }
  2. * @brief  擦除QSPI存储器的指定扇区' [3 R; c  ?8 L# a  z! r' ?
  3. * @param  BlockAddress: 需要擦除的扇区地址
    8 n, @5 w: O4 ?! \& d
  4. * @retval QSPI存储器状态
    ( t( [- A+ G6 u" R+ a4 D6 N$ w
  5. */
    / Y( p- a/ i+ n$ ~0 }( o
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)
    4 I' _( R5 j) F: u+ j
  7. {' H% V7 `5 K. P# u
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址6 g2 ]0 ]& s" N+ c  T
  9.         2 u% q1 Y# A! ^
  10.      QSPI_CommandTypeDef s_command;
    4 M1 L/ F* T9 B
  11.      /* 初始化擦除命令 */' {7 q% K: G2 Q
  12.         ' b/ b. [& B. v8 o. _: v
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;% g, f- ~+ ^2 v; ^7 z# ^: I

  14. 7 N( l0 t$ w  x# E) \
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除
    ' c& y9 c# _2 c" l( u2 n
  16. / B; B7 h1 d% i/ p
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    % q+ v) y8 j* r5 N

  18. - G, l! A: v* D$ l' ^/ d! l, m
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;1 M: M5 P; d- D; O; z
  20. 3 A0 o0 H8 C- T
  21.      s_command.Address           = SectorAddress;
    $ u& T2 d0 R) R
  22. # {2 p5 m; d+ B. O  f3 S6 w2 Z8 p
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;( {- R/ N' }1 O4 w, h# l

  24. ; T; e5 w1 B: A, ~
  25.      s_command.DataMode          = QSPI_DATA_NONE;
    ( \+ H# h; f0 n% R7 c
  26. - O: ^- ?6 c. N, J* D/ ^
  27.      s_command.DummyCycles       = 0;
    & t7 j/ i$ K6 T, T4 F' E

  28. 1 u6 q% e# y& i6 _9 E. V
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    ( B3 k# U3 {4 g8 Z9 |- s

  30. 0 y9 n& l( }8 U: R; Z  v0 ]0 P8 l
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;' |; I6 e+ b, K7 Y! B1 i
  32. 3 D) Y3 H  J) G  U% q! K6 R9 Y
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;7 y, Q. v: p4 Z# J

  34. , U  P% D' f/ \  k7 m7 R# F
  35.      /* 启用写操作 */
    ; B( ~) n0 I* {3 |4 ?9 }& Z! Q0 u

  36. 7 X; T9 F& ]& H% f" ]
  37.      if (QSPI_WriteEnable() != HAL_OK) ! r- x! K; M% G/ n
  38.                  {5 Q) D3 `! ?8 o/ P
  39.          return HAL_ERROR;
    % N/ r! ?& h' C- Z: ?4 u8 b+ q
  40.      }) n: S3 Y+ R  T6 C
  41.      /* 发送命令 */* z* m6 Z) _7 b% J. Z
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ; b$ j! A% f/ v% s, `
  43.      {
    $ Q/ G% i; V5 B7 `8 I
  44.          return HAL_ERROR;
    4 C# i8 o6 a0 j/ b- I3 d
  45.      }
    # C* e! x; X  K( Y2 I
  46.      /* 配置自动轮询模式等待擦除结束 */
    " V0 d' E) F' x  ^" H
  47. 1 |& t, d8 q1 w5 N8 u4 F" ?7 C! s7 H8 \
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    $ @8 m# [( Q/ w3 ^; q6 V
  49.      {$ |, b  v6 _. v" X5 X+ K
  50.                                 #if  QSPI_DEBUG
    & P4 Y1 n+ S' g# F7 V$ N4 G& i
  51.                                 printf("erase sector error!\r\n");
    5 _. O0 R4 z* _2 R2 p
  52.                                 #endif
    2 Y2 C4 F0 P( ?" g9 f9 L6 ?
  53.         return HAL_ERROR;
    + }( u1 ]% P  v7 C: }9 ?2 i6 g9 V
  54.      }4 u; P$ t) d& t7 J5 ]
  55.     return HAL_OK;. e& `# s2 R+ b: d! L* [7 P: Y& X
  56. }
复制代码
# \* Q! D% Z# o! }3 k* b* a
第二种情况是全片擦除,时序如下:
/ ~! q$ K1 T. w# _8 p8 m0 j3 P; t+ a! R
20191006140919805.png
; R. t6 w: A  R( p4 i! E
7 h( z, ^) r9 s2 K6 |
全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。
! _  u  ]( Q2 {+ c7 o3 b6 N" T6 Z
, t  M* {5 @- n
  1. /**1 S$ w# g: X! o
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s
    : d7 S8 o; d, h2 }" v9 p1 S. Y
  3.   * @retval QSPI memory status
    - M- Y" f  i! E$ i: [! N7 W
  4.   */
    , p1 D  S' \1 Q- j$ c0 H# \
  5. uint8_t BSP_QSPI_Erase_Chip(void); l( @0 r; Q! \) S0 _
  6. {
    3 K2 Q, x/ p% b+ c  D, Q
  7.   QSPI_CommandTypeDef s_command;. i1 Z7 s$ K; h& N
  8. - @9 L0 q& ?/ c7 b2 l; ?
  9.   /* Initialize the erase command */. @; j, E8 e; B+ V) m& d
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;/ a, X0 f5 W3 F0 v
  11.   s_command.Instruction       = CHIP_ERASE_CMD;9 t8 `) u2 V+ Q4 T3 L7 D) F
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;
    ( P% ?9 T3 J. \
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    ! Q: j- B! e  q, q
  14.   s_command.DataMode          = QSPI_DATA_NONE;/ @! u$ N0 `8 ~. y3 F' }+ m
  15.   s_command.DummyCycles       = 0;
    " `; W6 ?# f8 G$ N' D, o2 S
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    : u. ?* q  \7 g, @% m) N: }
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    & c4 I1 f5 v, H( e9 r
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;& @' [/ X5 ?% T6 Z/ ?/ z

  19. 2 B$ N% E3 q% p; w7 x
  20.   /* Enable write operations */
    ! I* L5 Z% j% L0 X: T$ @7 O
  21.   if (QSPI_WriteEnable() != HAL_OK): {( m* O8 G8 B6 V& W- `0 T, a6 h9 k
  22.   {
    7 Y; Z/ A5 n8 Y: J
  23.     return HAL_ERROR;  @8 H# i4 p7 H; `5 E
  24.   }7 U$ L  V; M' k9 `+ h! p5 |' h

  25. ) q4 M9 }" P+ [8 q5 D( ?
  26.   /* Send the command */. a/ L. c( W0 _( K" A$ C5 G  y
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)1 s5 S/ K% N8 J& i
  28.   {8 u; G, P1 U% R2 U
  29.     return HAL_ERROR;' T: Y5 T+ `  A
  30.   }0 _% T- W1 B1 e6 W2 m" m$ k( t4 {

  31. , p! P% L/ N, W* C1 [
  32.   /* Configure automatic polling mode to wait for end of erase */  & r( C1 z$ L4 ?; r" q9 |+ k
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)
    ; s8 g3 b4 S) H
  34.   {% t' O  l) f+ t; A( f
  35.     return HAL_ERROR;
    0 V. B8 H( C) d2 A% V5 f- E( V" ^
  36.   }' o, E% E! G0 A& Y4 U% }7 n
  37. & f, Q) W! G* U5 B) {, v
  38.   return HAL_OK;
    # Y0 }1 N. c1 ]; `/ H
  39. }
复制代码
# J, d: h+ C6 R5 e( W0 Q7 w# J
9.测试程序
; K( c% }. c% B% P8 {4 S/ _! a; g. \: Y5 s" H
测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。( P- `* F: _3 P1 @9 X! p( @% S7 ]5 D
( p5 ?8 C  b. w5 o3 ~' d$ q" Y
  1. unsigned char write[4096];0 H5 y+ b( b+ |' T1 E8 T/ o( |
  2. unsigned char read[4096];   
    $ \; H! d2 w# E. i
  3. void N25Qxx_Test(void)4 a8 ]- J& s. N% `! |7 o; C
  4. {
    $ m/ j" J1 E; Y3 R: d- V
  5.   uint32_t i = 0;
    $ C5 }$ j- l7 D  U( C1 n
  6. / M* J; ^2 P: W( p& l" A( L8 V" v2 F4 v
  7.         for(i = 0;i<sizeof(write);i++)( z: l) b$ Q/ n. W  o& X
  8.         {
    7 u0 n# a5 X' E5 b; X3 v" y  Q  V, x
  9.     write=i;
    $ \" i' n4 s& E; z/ e  [3 M
  10.         }" _8 Q8 Z8 a# S/ b
  11.         ) y; H5 l" j, ^9 X. B
  12.         printf("write data:\r\n");        : |- _" ], [6 R
  13.   for(i = 0;i<sizeof(write);i++)  S2 X. d. B) Z4 O0 R& N
  14.         {
    + t! A0 a% R1 @+ ?0 ~
  15.                 printf("%d ",write);
    7 E& b1 \1 e6 f5 X% y$ h$ O0 i
  16.         }* Y# `/ [% _- p- |
  17.   printf("\r\n");1 Y0 O- x) v! c( M0 g
  18.         printf("\r\n");
    ! P' ~- k4 a) ]" `& T* B5 [+ A2 G
  19.         printf("\r\n");
    # a/ z/ K' S* `, B7 d+ S+ _
  20.         
    5 V% T9 M' ^9 i
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
    0 d$ r1 }" w+ }8 I% }# J: M
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        " E8 n; w0 R+ g1 P" Y2 D( O7 }
  23.         
    ( |* l! f$ p# w- Z0 |: ^' q, t
  24.         printf("Read from QSPI:\r\n");
    4 {, {1 O" y) Z
  25.         1 o/ ~' l' k" D5 e4 H4 V
  26.   for(i = 0;i<sizeof(read);i++)
    ; V9 A4 N, d1 s5 g2 [* j
  27.         {
    + ?& e: q- q) h. {
  28.                 printf("%d ",read);% v9 N' O/ q% r1 {+ N4 e
  29.         }8 |. l4 I( h. b' u  i, Y6 l2 x
  30.   printf("\r\n");
      u7 a# ]8 [, B$ f% x3 y
  31. }
复制代码

$ h$ R. T1 `" b( C. `9 X, v写入的数据(部分截图):' v6 z) q. a8 M- H
. k( ]" Y& s$ Z, I
20191006141636369.png
) b% B5 y) P# T& r) C  C  J

1 B  }6 ^0 o4 e  P读取的数据(部分截图):
. i) T# f6 g, p$ m, {: @5 S& u- R. x3 e
20191006141537697.png

! [7 ?( K$ H+ H. T8 f! Z, D, V3 j0 b" C8 f7 S5 g
截取相同的部分,两次数据完全一致,写入和读取成功。3 [/ H- E: o: z$ f0 H5 o( M" R

3 x( ]. ]+ Q( t9 }6 \# K最后,讲几点需要注意的地方:* X9 z* T% l7 b2 a0 {6 s  R

& W# ?  I  J: u; N) H9 s  ]1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。- y: j5 m4 Q, e7 |) Y

! `% T( }4 H# @4 I# \: e2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。# W$ _. f9 P# q  V+ H# W( t7 {
, I* p  y( h# D0 V* q' X
3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:, u6 a: f0 R* i- O
 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
$ q. \/ B% |9 @8 R' s 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。
8 q5 b" C* {% p& ~' c/ G1 Z0 q0 }0 k' [" m- H& j+ B
4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。0 }. S- F( R6 X) t3 s

/ ~) S! A# ?1 V$ O5 N9 a) [: h  B5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。% e9 l2 I" j) n; c8 \- t2 d0 U, S

6 g9 v! \, ?( E0 b* l& V0 _8 s3 s
20200316162249870.jpg

. i3 ~. b1 a' E) `! @
4 O7 L% I% n3 D! q: Z1 b6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。
( ]" _/ Q# `% N; r( ?5 v7 s7 w0 s  s* w% e
7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!
, B# g' g: x$ i7 Y( X$ h* ~$ Z
7 T9 j+ p0 E2 R! P& T  K
/ a) O0 f! ^1 ~6 `
7 W; k- r, J1 Q1 n% q% I0 u
收藏 评论0 发布时间:2021-12-16 21:00

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版