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

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

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍
$ J/ |6 [% i  K3 }% `+ m
2 D% E5 {6 Q" @, H' q* V  u! G: S9 MQUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:# R* m- M; Y5 E1 }5 Q
" ]/ L0 y9 C: i- \; I8 V! K& p$ ~9 V
 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。7 w, P9 [/ k: P, z+ `( Y0 _

. v- A+ ~/ f1 U; a) }& H 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)
. B# v: @5 Q/ x0 a4 |
3 ^0 |  i1 N) @, K 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器
' C* y) K7 z+ Z/ F& n
( [; x0 G, R5 k8 ]" a采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。
# E- i4 g" C2 Y: O: l) h) l) g7 V3 L) J) N! _, W/ J
内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
) T# v6 u. w/ u! q1 K6 q
4 t# j9 M* V% K6 u  j5 x  ~& \( y" {: K内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。3 i( |7 E! U+ J" C3 s

6 s) H5 B+ s& ^8 z! T二、 STM32QSPI 功能框图; }$ `0 X, s8 _1 F) }# ^" q7 j9 p* r; A

0 p( g9 H7 S0 F1 E+ K/ B! S9 v
20191006091636349.png
: i' Y2 p8 f, y. K

+ I& l1 j9 m4 a; B, NSTM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。% q$ J; s! W* _7 S8 a
! a! W( e6 U: p& O! R
三、QUADSPI  命令序列
/ U0 I9 o. |5 Z7 m
+ ]$ h9 l, M8 y; P  R) xQUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五
6 o  |4 s) f  }. D个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。9 d" d2 b$ o8 f) F  H" Q0 i
nCS 在每条指令开始前下降,在每条指令完成后再次上升。4 f+ a$ q8 U2 x# _9 \

: U, q. t. T: y2 R! `- i: U
20191006092242666.png

, v. o' g5 q; N8 {" G! \% g$ G  ~4 u/ Y" B" P0 E, e
上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段.
& X* K4 B6 k9 c. |$ W, D7 I  R, j# a6 |( @' W/ {3 J
交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);4 H7 m2 t% Y( m

. Z  H7 z7 f  ?5 q1 B2 I' o空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高
. b9 Z# u, _: \  i- [* C' `的时钟频率时,给 Flash 留出准备数据阶段的时间。8 n7 x5 i+ l4 Z0 t8 c$ @
若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
; W3 U# ]4 G8 N2 m9 Z1 c2 j2 J0 I! a/ C0 Z3 Q, o: f2 l: L
此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。
7 t8 n! w* |2 {5 ?7 l
! N! q1 [7 b& o" `5 K7 ]四、QUADSPI  信号接口协议模式
& I4 ^" O2 {7 j* l( b6 q- ~1 y4 Z" [8 h, Y, q" K
下面提到的几线模式都是指数据线的根数$ Q( I. ?  w0 r  ]# F
+ P# z0 ~4 F4 ]+ D% j
单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):) B* M7 N9 z/ t. ?' F6 g' O
传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与" h0 H4 S* }( I( P. r! e
IO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。5 G- j3 @' D" |4 L
通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可, V* d6 G9 m: N" i$ F* J; d
对不同的命令阶段分别进行配置,以使用此单个位模式。) b0 l+ h- u- V
在每个已配置为单线模式的阶段中:
% q- g3 q7 s: m$ @1 [3 N$ d IO0 (SO) 处于输出模式) D: z9 w% R$ I" @1 e- l9 i
 IO1 (SI) 处于输入模式(高阻抗)
$ a$ Y' X' U: O: k IO2 处于输出模式并强制置“0”(以禁止“写保护”功能)
$ u3 T$ {4 a3 q) O5 w IO3 处于输出模式并强制置“1”(以禁止“保持”功能)0 `6 j! u3 G/ @" V  N' p2 ?
) N, r$ L$ H. h7 K
双线 SPI  模式:
* Q; J6 e1 B3 v( h在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。6 G3 f) _6 s' x  i- T/ }
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可
, j$ T4 r) z8 x% M: D8 N: K对不同的命令阶段分别进行配置,以使用双线 SPI 模式。
! K3 j6 w: g4 {7 Q" y3 ^& S1 G在每个已配置为双线模式的阶段中:
6 J$ T5 n  Y8 j IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出/ ?! t5 W" s0 ?" e( y8 r
 IO2 处于输出模式并强制置“0”
- C9 t" [( J& l) r IO3 处于输出模式并强制置“1”! h$ Q( |5 s/ j6 n
在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。
7 D; s$ n  N  n% b5 k) I( C% p) E
5 a+ F# g2 k+ @$ N+ a$ Q# Q! s: c# R# f7 D7 S6 W; k# I# k+ R, ?
四线 SPI  模式:6 n6 I% d% \: r* T
在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。
2 d- ]. f, {) A& P9 t5 p- k通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可1 m4 y6 d3 \! f
对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
) g+ F7 F( \! W在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高
; I8 D7 Z0 C$ C. e8 m  w阻态(输入),在其他情况下为输出。
0 P8 }- g) B9 A! y2 I0 ^在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
5 T" e; D- n6 h  zIO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI
. h) _9 g8 t+ S3 ?8 r激活,对应 IO2 和 IO3 的引脚也可用于其他功能。
9 \$ e. a* J& g; n5 o& D/ F
6 F  p5 h" E7 }+ ~: {+ R
& _9 ^6 v, L) L% ~( t% I3 RSDR  模式(默认工作模式)
) t* C. u) x2 h9 t& o' J- n9 L) b/ V9 f: e默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
, j, ]* m+ j- X7 t在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK
0 L& d( F* e/ |# {/ L  ~7 U的下降沿发生转变。- x2 l9 `! t: }0 f
在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情9 J$ T) ]5 |9 a& ?6 A0 L3 ?
况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。
* r( N+ m" p: {0 C+ D- ^3 s' @2 [: m" t
7 R( [0 a' h( o$ `/ G
  l7 }' N4 \8 ~1 `" TDDR  模式! k& Y1 d. M. W4 ?3 ]% L
在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信
0 P! @8 u6 k$ {8 w4 \# _: r号时,将在 CLK 的每个上升沿和下降沿发送 1 位。
2 G4 ]3 ?6 b* q; ~3 J; L% ?指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。4 C8 P3 @$ p9 Z4 o7 }
在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数+ L! E% K* {: H1 p
据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期( p! Y5 Y2 i& Q' Y
后(下一个反向边沿)对信号采样。8 [- d1 F* u1 i. j  d  p
% w8 b3 v' I9 U; C: X+ v' s
五、QSPI 配置(HAL库)1 {+ d/ d* [8 _  r) j

# r7 d0 w$ s! r: a4 L1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟% F$ v* Q' T( Q4 r  L1 U  z$ d  i% r, C

. n$ ~# k$ I! ~$ K
20191006121236849.png

4 H& Q* C8 ?/ L6 U1 ^' A% Y: t' S* ?" h. G1 v. F0 |. k
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)3 m* x; a& B' t! a# ~" @
  2. {
    % n8 L; D: H! J( `' \

  3. 7 J& P* g. ?% z6 u  |& D4 _( Z# P/ z
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    0 }' T% k- t3 `" A4 m! Q% x9 D' f% X6 j
  5.   if(qspiHandle->Instance==QUADSPI)$ X: p( F% ~' ~" O
  6.   {2 |% j% x' L  w* a) ^, g$ E
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */1 A' V1 F, `, z9 h: U, `
  8. - U' ^8 [* D6 J8 ]1 [
  9.   /* USER CODE END QUADSPI_MspInit 0 */
    3 W1 l9 a' V4 |. T, E
  10.     /* QUADSPI clock enable */' U2 a: S2 \9 U
  11.     __HAL_RCC_QSPI_CLK_ENABLE();; G& e% `* I) ~. H

  12. 3 ^4 l8 z# {$ j: v3 h* d. `! j' s
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();3 c( j6 R9 Q& G/ n; ?  @3 j. G
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();
    # k% F: H( \1 k. S
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();
    " Y9 ^# U) D: q% X- ^
  16.     /**QUADSPI GPIO Configuration   
    ( V- ~0 D1 [& c9 u; C
  17.     PE2     ------> QUADSPI_BK1_IO2
    * X5 N/ W" ]1 e9 |) g. P. w
  18.     PB6     ------> QUADSPI_BK1_NCS7 r' k# T  n' X1 B/ U) y
  19.     PB2     ------> QUADSPI_CLK3 O0 M. x1 H+ c* |2 U5 n
  20.     PD12     ------> QUADSPI_BK1_IO1" F/ Z( [7 l$ Q# r0 Q
  21.     PD13     ------> QUADSPI_BK1_IO3$ `- i1 I" i! \( @' L$ O2 v
  22.     PD11     ------> QUADSPI_BK1_IO0
    6 C  F4 I; N& ?* V# m! z+ V
  23.     */3 t/ a' v% t1 T$ p# g
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    : u& v  z6 h- G- |
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    5 ]# W1 W' @% l/ J
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;* ?0 v  {3 s4 V+ I2 v& b- b
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    2 y( S" n  l) H! }
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    ' u- Z, W' ^% o* A2 o
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);7 |% _4 @6 J1 P0 {6 P: d

  30. & p7 ]+ b2 u4 v) Q2 c! u, c: V
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;
    ; T6 F& l3 T+ G  _# p
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    6 g+ S$ o& n6 o: n3 d! ]
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    1 X; Z; D2 i, S) c( e# ^# s
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    - R4 r3 q4 O* \. b, E0 O# u
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;! D9 G1 @" R" V( S) K" t
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);" ?% m1 Y0 f! H( G
  37. 9 [$ T! l0 i& s% j5 ~
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;, \2 D" ~; [2 l3 V
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    ! |8 i' w. f. h; [; R0 P7 i
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    - O+ ?' g! x) u7 b: J5 n4 ?
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;& {8 ?  j) u  {9 v' Y$ Z0 w5 U
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    % @8 z$ F' o$ F. q' _4 l3 I
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);* ?7 L: ]& P+ s, _6 d
  44. , K' }/ D+ R3 T0 o( G  ?1 X
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;
    5 `2 M2 m  L+ S2 |5 Z- g
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    3 Z& s! |9 w" Z' o
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;# {9 f2 R" z' f6 f+ J' d0 C
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;' T" l% T1 D: w# ]: D
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    4 i1 s7 E: ~% n% A
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    & u1 ^$ J- t4 M9 t9 H# u
  51. ' U& w# v7 ~4 v1 {4 ^' a
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */- j% ~* ?# a! [4 p, n6 Y+ G

  53. / u" z9 `8 k2 ]# c( s
  54.   /* USER CODE END QUADSPI_MspInit 1 */
    8 v0 L8 `9 t% I# X( G
  55.   }
    6 |% n5 x2 M* c2 g
  56. }
复制代码
) ^# ]+ i/ o8 C& |2 E: u  w
2.接着初始化SPI外设
8 Y; U" q0 {, x! p  L8 W; t
  1. ; v) W: D( I! K7 z) J' k+ `3 G  `
  2. QSPI_HandleTypeDef hqspi;% V$ C( R, J; v. a& K

  3.   d4 t1 }: |3 O' {, ^: K
  4. /* QUADSPI init function */( u8 Z) m* [. _. G: _
  5. void MX_QUADSPI_Init(void)
    0 @6 k3 W- K# R. B) }
  6. {1 _' G# b. Y$ h' [9 d% {. |, g% r
  7.   hqspi.Instance = QUADSPI;//QSPI外设
    - Q+ z5 Q7 B" E; l2 s
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
    / @1 a3 [/ `, F( u0 |5 @
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
    8 ~0 d  |: \( m" ]
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)4 Y$ L9 }6 @6 ~! v
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
    ) P* F/ q3 b0 t* Q* S$ E
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数
    ( H+ p% o4 i, R. K" y/ a
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0
    1 z6 z* q5 v/ B6 t" W3 k5 m! G8 ]
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash4 f) e+ t: G1 a( t( |
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式
    - Z/ _/ a4 M: w4 E" A
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设
    2 D5 ^% @! C% c+ j
  17.   {" h" N" c9 H: y2 R2 V) y
  18.     Error_Handler();  f1 Y) O" w4 A) z
  19.   }1 i* @( H9 V3 h8 x
  20. }
复制代码

# f! [  u" `- z! v- k+ k: \- k9 L3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。
. ^4 J- T' X% \3 Z1 A7 z. D9 H! O0 g' l
我们首先需要配置下面的命令参数结构体:
2 g' k; n  s- ?4 A1 S/ S7 Q
5 `$ I, S; @- ^+ n$ p
20191006122437823.png

; o) f7 W) R& f2 |) E! p4 o; l2 Y* M2 Q( I' b, S
Instruction:指令类型(读、写、擦除等等)9 m$ o, {- [/ u9 j/ R3 s! r8 }$ s

9 y. Y& s6 t. N/ s/ c2 O: e3 pAddress:需要读取或写入数据的起始地址+ D5 P$ j' h" T, Q9 R& P% Y1 \9 N
/ G5 B' v, u! J
NbData:需要读取或写入数据的长度
/ `8 D& f5 h9 L, i5 K! \6 F& x4 j) N8 _9 I! b9 K' w: y3 s1 ]" Y/ q
DataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。
* V( p4 I! _9 h( O- T3 p) q" i" z' X8 O" k: q% A( H4 V* |3 O
这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!2 Q0 |0 h# E0 C

3 }& b$ O; g, v, {8 ~, I5 L 4.读取芯片JEDEC ID8 a1 L2 K# l- H3 ?  m% Y8 ?! `- ~

! \5 `7 d3 N# w首先查看手册上读取JEDEC ID的时序:/ q' J# |) |- J  ~
. K! v" i$ J, B  H! p+ T" k
20191006131644114.png

2 _' M9 G5 z8 t# y4 s
% i4 B4 L1 ^, x- E' J" M1 |$ B可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以 $ h7 W7 G9 a3 T. J( `/ y

' {3 I+ Y) O2 U: n: q1 }: QInstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
- q' t8 F/ C2 E5 E8 s: G+ M! A1 B& a( i5 j: v
DataMode = QSPI_DATA_1_LINE;//数据单线传输模式. w' ~5 q* V. M' {1 ~" P
  m3 _9 v4 T/ C5 x1 x
Address和AddressMode 这两个参数的值都置0,表示无地址。0 L' U) }7 [, `1 a6 X) G+ e

9 h; b/ i1 t% T2 ]NbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3/ j; H1 a3 d: K# o7 E
6 [3 S4 }3 v& D$ f3 H2 h
其他值和下面代码保持一致即可。
. p. U  k' z1 u5 _! F6 v# z8 U. P( Q8 m2 I5 V, g) d6 e& R
然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成
9 ~  W+ ?, o' u* B3 ~2 V
. r# [% p  }* w$ B' g' G最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID. v7 W$ g1 M+ s' p

/ \* g1 z* J: d, }7 \; k, x- m
  1. /**
      M$ S2 c/ }& F1 G6 W
  2. * @brief  读取FLASH ID
    1 d8 j$ b! `& I2 a2 x( h4 r
  3. * @param   无0 D* x0 \6 ^$ I2 E1 K8 k: O
  4. * @retval FLASH ID
    ! t, B- M* f  E9 u8 ?2 Q
  5. */3 V- ]. F  r4 _4 c# [; T# Q
  6. uint32_t QSPI_FLASH_ReadID(void)
    . l0 X5 i8 p- ^; Q! {9 o
  7. {+ k  Y% \2 h9 q' [9 [+ \
  8.      QSPI_CommandTypeDef s_command;2 g7 m, a/ P6 m

  9. 3 k  G* H( @+ e1 Z: J
  10.      uint32_t Temp = 0;
    1 B/ F% e( C% r! N7 ?7 K  ?- B
  11. ) H. v  w: ~+ `0 M
  12.      uint8_t pData[3];
    + R1 C! ?8 r, ^0 j2 r
  13. ! p6 c3 K, ]4 g: ~1 j2 t
  14.      /* 读取JEDEC ID */( u/ [( a4 n6 q8 O

  15. 1 Q+ r4 u4 c9 ^1 ?$ j' a( I) V
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。
    7 J3 X- O& ?' o$ t! n
  17. + y' y! e- n7 F' y" Q0 \) h, e- q
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令5 C  w: C3 r" [  x* @4 ^

  19. 0 X- z1 i+ {0 Z; }; S
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度
    3 B0 w; w  T; e( ]' D  v

  21. 4 t$ d3 {* v( Z/ W, W# v
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。
    + _" h# v, X- @( a/ h8 M
  23. % J- m2 h. F3 @: `
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。
    # j8 q9 I& p5 t0 h; H* w
  25.          
    2 C- K; j4 F* \" |3 Z2 l7 U
  26.      s_command.Address           = 0;   
    ' S. ?, ]+ a3 K: x% L$ p2 j
  27.         - `( h, o4 f* `  k% X! H* E
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度
    0 q$ q' [8 V( j6 h, t- e4 s7 ~$ ]' Y
  29. : S6 z2 O7 @0 E' z6 A) G
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。0 a( D6 u5 d% m" E8 u, h
  31. 0 C, |; v9 K' d( e8 @5 }
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。
    9 l5 B$ L4 a* C  P) x: `  M/ G& i

  33. ! F3 ^. |9 [2 r( F3 y+ j* \7 Q
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。0 O( _; x% I2 m! R5 v! i
  35.   t/ O4 K! h; K- p3 [- n3 r
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
    $ N8 s6 E9 V1 o  Q% O+ i, E
  37.                  , u/ E: C% |1 w4 J. d) c+ I
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。
    0 _7 u  [1 o& n# h' G

  39. 6 Q2 P! g$ w' }0 I- Y- L, g  x
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    $ y$ k8 q9 k# q3 t4 B1 {9 v8 b8 F
  41.                  {
    7 ]2 w& e9 |- A9 q4 j( G
  42.          #if  QSPI_DEBUG
    ( R6 p5 c" g2 ]& S& H; l/ x3 p
  43.                      printf("send read flash ID command error!\r\n");
    - V, c9 K$ y2 w8 U8 M& M
  44.                #endif
    9 J- X- `( F# N+ O8 ~2 j
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */
    . p9 l* E' x2 O0 f: {" G1 c  ?
  46.          while (1) {}1 Y# `# Q. G1 f  {1 [# Z5 h' H
  47.      }
    ( N  T& N% Y0 [; p
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) 2 u. |* J- [. C2 {! Y) J) k( ?
  49.                  {
    9 A; d; T4 A% f: G3 k* g8 Y
  50.          #if  QSPI_DEBUG1 h  p" y) L8 h
  51.                      printf("read qspi flash ID error!\r\n");
    ) N5 d, t- @5 f( N9 f  ~2 R3 K& l
  52.                #endif
    7 H/ o2 S+ G/ z* X0 T
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */
    # d) G* @+ P8 s3 F! A% u9 b/ x
  54.          while (1) {}( J0 K  w& p9 `+ ~' k
  55.      }9 X4 P/ `) k: O6 q8 F! r
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );/ s5 F2 k% [( b. V- S/ f
  57.      return Temp;2 d/ W1 p  A' ^* k1 L: T* T1 |
  58. }
复制代码
5 u2 L+ u, h# V5 a$ D
5.FALSH 写使能0 I7 `/ r( {& C% C$ K. i+ J- v
8 n- Z% L; x2 T7 J
在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):
& f- s! j" d% q% ?$ i( ?5 `$ h/ E7 i
类似的,需要查看芯片手册,写使能的时序  B& f  R0 j+ _+ d
) m5 N8 G8 _! w# ~6 ^
20191006134057767.png

9 z: T! Y! @3 N+ q: M+ C( I6 _( ?$ @
可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0 8 L' d! l4 H4 L8 H. D4 P. y. A

" M% e  ]! j& u# ?% E
  1. /**- J8 @) h4 |$ ]) k
  2.   * @brief  QSPI FALSH写使能
    ( f) }. K" |" a# s) O' s: G
  3.   * @param  None
    + Z  S6 h+ E/ d( {
  4.   * @retval HAL_OK
    " \4 ?- |8 T6 J7 n. i. W7 x$ k
  5. */
    2 @! L: i! \* A, A
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)8 [$ R  A- R9 C$ s
  7. {
    * Q( \5 y; M# m0 W" {
  8.   QSPI_CommandTypeDef     sCommand;
    ; J% m0 J  J9 v6 b/ |& }" z% ]
  9.   QSPI_AutoPollingTypeDef sConfig;+ q, h; j3 h/ L: B+ w" c, G" r
  10. ! g6 m3 o1 q) S, y
  11.   /* Enable write operations ------------------------------------------ */
    6 o3 C) n8 J4 G$ h) Q/ y
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;( D' S; J& [" t! V- w" _0 \
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;- R* d4 y0 C, }& r1 D
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;) R3 w) K# ]( b# O* B. r" i2 k
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;# |: z& d( p6 n/ h$ M9 y& Y
  16.   sCommand.DataMode          = QSPI_DATA_NONE;. T: f: d: j9 {5 a$ ?( O
  17.   sCommand.DummyCycles       = 0;' d# A: c" A7 j# L6 [; Y
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;
    . z% T' M1 A5 [! v
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  y$ B5 q* Y8 q  B
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;8 H' ~/ {3 x3 U' t9 ^- Z* y* K

  21. / O: N' D  B: S& F( T( X
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    5 Y- f5 T0 G6 G" r/ Y
  23.   {
    ; w1 {- Q) V% g1 U# O
  24.     return HAL_ERROR;
    $ n7 a' ^8 C: z+ D
  25.   }; c& a! y: Z( o0 O6 M
  26.   D# Q9 V3 @8 J  b
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  
    % Q' N7 D' D; G# n! T0 U6 y
  28.   sConfig.Match           = 0x02;2 z8 X  r  h3 l- O
  29.   sConfig.Mask            = 0x02;
    9 \% o( S2 J2 G  w* t
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;3 F3 Q/ e1 x3 Z/ `3 A4 `
  31.   sConfig.StatusBytesSize = 1;+ }+ s! l9 {0 S' z4 Q# l
  32.   sConfig.Interval        = 0x10;
    - ~0 d0 @" G7 b: s4 o
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;/ l! d: @$ t" S
  34. 7 N* t% @: `2 a+ S
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;
    . S5 c5 q" R* b5 o* R
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;( X) x/ q+ V  d( l% v& B
  37. 0 O: B' U9 q+ j  A* K  W
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK); T4 r$ _& B4 h- l
  39.   {( @1 Z4 a' R3 i1 J, u
  40.                 return HAL_ERROR;        # w$ \5 O9 w8 `
  41.   }
    6 w8 Q  O0 l" X
  42.         return HAL_OK;
    9 X1 n  N- z* M6 y4 F
  43. }
复制代码
$ f# V8 l9 v3 W- ~: g
这里需要讲下 QUADSPI  状态标志轮询模式:
/ {+ X' [: m/ X, u5 m+ X" j在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。* ]( T& [% P$ J3 `2 B2 u  P7 ~/ e
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。
7 `2 ~4 F" D, H2 _: |对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入5 p( B  u, ~0 o* @
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第/ O8 {% `- g/ P" |  n, E
一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。
; a8 p) `1 D, u2 [# h$ T0 f在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。
, C) i1 }6 [5 v) w$ `9 s2 {% Z8 t1 t如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内
4 Y0 M# E, ?  S0 {7 \" T8 y; L容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。
& f) h6 b1 Q- C) c8 Y如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着7 M* ^) a/ o. o$ L  s( L9 ~5 j
状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。& @) O* M# ^5 V
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。0 W* E4 `7 [+ p5 B1 M
如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。
; n/ _; L4 e8 ?6 M0 j% A1 [. s如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否
. l0 O$ U% ?3 y6 {则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性
; \5 e% ]" C- o; Z% z2 K访问。
- c% I& H8 }, e- ~, G7 `数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不
7 a8 B9 M' L: h受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数
$ U. X. k& [3 Y; v1 D+ i: s据读取后清零。
+ `) E. j& |1 H! I% N1 [/ z, R6 Q; m# n! _* d, x
简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。
/ ~/ q0 {- h* ]4 v
; L) k! O1 e4 r+ m6 t5 v2 t6.读FALSH) c3 M7 X6 ~; s9 c. M5 h

, k  q: P' U+ q: l这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序+ K! A9 Q: c0 p9 B
4 [' g6 x3 X0 C
20191006135244693.png

+ X& i8 f4 e2 q/ b3 M1 A
! Q+ K% ], y% M) z从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:6 P" I& `5 M( V1 n" n2 X
2 c% `' K5 m) L! x7 P* f
  1. /**& p$ s/ b0 K/ X  a& {
  2. * @brief  从QSPI存储器中读取大量数据.
    ; i/ }* ?- W, G9 B
  3. * @param  pData: 指向要读取的数据的指针8 W; H) _! |* Z1 l. m" I
  4. * @param  ReadAddr: 读取起始地址; I9 e8 R5 e& z0 h8 c! A6 r
  5. * @param  Size: 要读取的数据大小
    1 n8 U+ M' j7 a# L0 Q* j9 j1 c
  6. * @retval QSPI存储器状态
    2 I# F; f7 i5 f4 G, j' D8 Q
  7. */
    5 z3 @2 i' G6 c2 ?$ u' P' Y
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)- S6 t/ M6 p; o9 i4 D
  9. {6 B* S( O1 t% l7 Z
  10.      QSPI_CommandTypeDef s_command;
    0 ^. k% b0 V  ~# @! |: X& g
  11. * v7 f( z$ T% O' W, c: B8 S
  12.      /* 初始化读命令 */
    ) C& \  P; v4 Z# `

  13. + s7 J* A4 b, J5 A/ W3 `& Z5 b3 v
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    3 ]  R# K* `: {5 Z( s

  15. - C" m5 w, e& L1 r4 M4 c9 i4 p
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;
    % D+ c% C' H- b/ v' u6 k/ j' w# D

  17. " u' z! R( ?* o5 t0 F* W
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;+ H) \4 u$ e8 s) J5 |3 _
  19. , m' i0 s' K) s! m8 I# ]
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;8 E. E0 H/ }% I7 r
  21. 3 V) K# t: N* U9 I! R
  22.      s_command.Address           = ReadAddr;( ]3 E. p$ R4 @7 I

  23. 1 ]7 b0 C* H0 }2 g! Q+ o4 M
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    3 A9 o% p4 V7 P3 L- o' E

  25. " C9 ]# u6 a0 V$ i+ F! R
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;
      c- ]. q; u% n9 }: @" E9 F# @# l
  27. * u7 B- l* P! v/ X3 s
  28.      s_command.DummyCycles       = 10;0 t" c' f% ?8 t  e& w

  29. 0 j) @( s1 j5 H- U0 E3 H
  30.      s_command.NbData            = Size;1 b3 d! z! c% l8 Z0 o

  31. * V, w! u% K$ x; a8 t- ]* v
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    / [2 q& Z  X: l/ v# G! m2 W: v0 B
  33. 2 M& m: O' }: ~" I
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    6 O0 \9 z+ j9 ]! b
  35. 2 j6 u* m4 N; x8 {& }
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    + b$ `, d6 h3 o+ d
  37.      /* 配置命令 */$ G! h: r9 U) f  _

  38. : C# f, u+ F% \5 M
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ) S- @7 o7 o& X0 O5 u# _
  40.          {6 V! ?9 B+ i( \9 g# a$ B
  41.                 #if  QSPI_DEBUG
    9 I* F) I+ _! E6 O: o
  42.                 printf("R-send read command error!\r\n");
    4 u0 x& i0 y9 p0 _( c
  43.           #endif
    7 `" H( N$ G6 z% i1 L: E- j4 {: j
  44.     return HAL_ERROR;
    2 r$ X& g& n" x5 D
  45.    }  3 G9 L2 s, \$ h1 r1 j1 |5 v
  46.    /* 接收数据 */  f% f# R: a; n$ ?+ f
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) - e& G, j" _* F
  48.          {0 N1 N$ q) w1 m. c
  49.                 #if  QSPI_DEBUG
    ! J* X% m2 \; y, a: S
  50.                 printf("R-read data error!\r\n");$ T; r& r  r3 W8 v, N
  51.           #endif
    / F/ p- r/ y: O8 b2 Z5 B; P, L
  52.     return HAL_ERROR;
    ' c# _4 e0 b' I8 p/ u* U% W. [
  53.    }; L% H2 l: N/ J
  54.    return HAL_OK;
    " b7 N' n  F8 `( y+ J4 h" r
  55. }
复制代码

. S5 t2 J: m" {7 g. ?, Z本函数实现了从FALSH指定地址读取指定数量字节的功能, f7 m6 e4 N# U
& y( E. U; u* C+ U* R; F- Q- t
7.写FALSH  k' G4 P) F- a  K( L
# C) n% N' B4 {: S- U2 S: n" a5 L8 c
同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:
" g  m2 i; i( k* a9 q) i. {  D9 b7 N2 ]) m5 F# m$ O
20191006135836784.png
4 |2 z  H) t8 `" Y' U
" M9 h3 \3 J8 u$ E* s) [5 \; }2 G! s
根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期 / V$ D  i' }' N9 T% C
/ G. u2 v: ~# t
  1. /**0 N1 I$ p& ~5 D9 ~5 t
  2. * @brief  将大量数据写入QSPI存储器
    * {: L4 Q2 g* C
  3. * @param  pData: 指向要写入数据的指针+ t, d) ~6 @: A* |1 Y) O
  4. * @param  WriteAddr: 写起始地址1 M2 y7 B& l8 U8 k$ S. I6 b* K
  5. * @param  Size: 要写入的数据大小
    0 {* k* p1 j6 L
  6. * @retval QSPI存储器状态
    % b6 p3 Z1 s  e6 O& i; S
  7. */# P( |0 n7 m8 v0 R  Z
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
    3 E! _; X- ^: y7 C/ b" f2 |1 U
  9. {
    2 a7 j. p. j9 ~+ e: W( o
  10.                         QSPI_CommandTypeDef s_command;
    ( L, f, L# a3 O* r% T0 x/ E+ I, G
  11.                         uint32_t end_addr, current_size, current_addr;: I# F) m" d; Z" {7 M: r/ t8 K% Q6 a
  12.             unsigned char sector_start,sector_end,i;$ M! z8 u: d) m
  13.         
    # f- o- K" V8 \, [& x6 ?0 p- Y
  14.                         /* 计算写入地址和页面末尾之间的大小 */
    . q" U# P$ g( N; B
  15.                         current_addr = 0;
    3 w& s$ X( U) Q9 z: Y. p* |
  16.         
    . ~, N4 R+ z6 i5 w3 B
  17.                         while (current_addr <= WriteAddr) / j4 ?7 Y& a9 G( `- `
  18.                         {& `" w' E* a  _2 k& ?, j# g& l
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;+ R1 t0 r! [. d3 r' K/ u- y
  20.                         }0 y! j. @" q; Y; ?
  21.                   current_size = current_addr - WriteAddr;  ]4 U& `* e8 l/ V/ {# p$ m
  22.      /* 检查数据的大小是否小于页面中的剩余位置 */% j9 ?6 y, \5 p0 r  [' ?8 O; B
  23.      if (current_size > Size)
    4 x4 X# k; ?7 H! J. k
  24.                  {
    " M2 ^7 L- z# u. I% Y8 {9 I
  25.          current_size = Size;5 [5 ]; V6 \) P( t0 E( A( z
  26.      }/ I4 z& q) @+ `! \- ~% S5 l' l1 k
  27.      /* 初始化地址变量 */6 M) S! o8 Z1 e( b: ]
  28.      current_addr = WriteAddr;
    / U0 _1 a  U6 _3 H. o
  29.      end_addr = WriteAddr + Size;$ ^* F! V- L" U  ]
  30.                  
    / P" F7 e" t/ g2 v
  31.                  sector_start = current_addr >> 16;6 M3 o- {) ?9 p
  32.                  sector_end = end_addr >> 16;
    4 D/ G3 |- Q- _4 y
  33.                  - F  `6 E3 R9 l! g
  34.                  for( i = sector_start; i<=sector_end ;i++)
    5 z* K& Y. m# u! J8 \
  35.                  {( \" a/ F0 \) b7 d
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    & a; _6 K& p" N5 _
  37.                  }
    , N1 @/ @9 C! }8 C* V: [' m! Z
  38.                  & i0 D# |' w. d/ ]# \
  39.      /* 初始化程序命令 */! Z5 G/ X) K) a2 b" U' n
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    # F8 F  t( y  R
  41. ; h. D% M( n+ m
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;
    5 K4 R: _$ _1 P# D' Y9 u; w

  43. ) \6 l$ C+ y& F: z$ Y6 q. p
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;4 ]5 Z. N% P8 L& P- Y
  45. ( O; t8 a$ \; `% U
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    & V! D* |9 r2 x' w* o

  47. ) J) |/ T3 N2 O/ |
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;3 I; l1 e6 |. K+ V+ D4 h

  49. 6 E8 P2 L4 `. _$ F4 K
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;
    ! @8 J1 l+ a* K

  51. 7 D: R& z2 [" v% M; R+ m
  52.      s_command.DummyCycles       = 0;
    . U: z) l2 e9 k) D, k2 a

  53. 3 }8 [+ `  k$ S  h  }
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;- K9 v/ {3 z$ R2 N% N6 u

  55. $ A; L. K/ h+ N4 ~2 w. p2 C7 h
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;0 ^( _# |; N% K  b& }
  57. ; J: ~% D3 N- x! J$ I/ K! k
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;! f9 v( s! }$ R- F

  59. - y2 A, Y  s0 d, c- {& l
  60.      /* 逐页执行写入 */
    ! x0 @! D. U7 W, m( @
  61.      do {
    9 y9 x- {* D7 ]
  62. 4 l  e  @$ E$ J. s3 a" W
  63.          s_command.Address = current_addr;
    " s$ p+ Q% N0 o$ M, E) @$ ^2 D
  64.          s_command.NbData  = current_size;4 v& A4 s2 w, b- k/ Q; G
  65.          /* 启用写操作 */
    6 R- S2 ~6 {8 O3 {9 [7 ^# q6 j8 @
  66. ' K/ V5 C: E1 k7 S" U
  67.          if (QSPI_WriteEnable() != HAL_OK) 0 ^3 @3 {, M$ g: P! K/ T
  68.                                  {
    * _- b1 K# U- J1 u
  69.                                                 #if  QSPI_DEBUG
    1 Q; s1 A; v( W4 _
  70.                                                 printf("W-Qspi write enable error!\r\n");7 p6 D& K: E- n; P1 N5 u: g  i
  71.                                                 #endif
    % h1 l4 k/ I' N" e7 l" t( P; R1 D
  72.             return HAL_ERROR;. X, T" D5 u, g- ]# [% f
  73.          }
    . J7 y- G! Q! J: \
  74.          /* 配置命令 */
    6 Y* C/ ]7 [4 Z) U
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ( G# y  _! [7 u+ x9 ^7 Y
  76.                 {                                 ! f0 O( t9 t5 u- h% y) b8 g
  77.                                           #if  QSPI_DEBUG: J" ^5 O6 {1 @/ B7 K. i' z0 G( T' S! L
  78.                                                 printf("W-Qspi send Command error!\r\n");
    & c' X3 f, ~/ X8 B; Z, j
  79.                                                 #endif9 t" Z; M+ o# |- V. \  P- {) r
  80.             return HAL_ERROR;2 _( \3 E1 o6 a7 Z5 B
  81.          }+ \& l; o2 N$ i4 v
  82.          /* 传输数据 */
    ! Q3 O  l" J6 K1 p! D
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    ( W; [3 ~) @: P$ x' P
  84.                                  {! I+ o  O6 ^; t9 z
  85.                                           #if  QSPI_DEBUG
    , P! p9 h0 ?8 X; a
  86.                                                 printf("W-Qspi Transmit data error!\r\n");
    , f. x4 a- K' V# I
  87.                                                 #endif
    " Z# Z: U7 U9 S% x1 K; e
  88.             return HAL_ERROR;# `) |9 b3 x7 B& v. e- _) @
  89.          }+ u$ u" M% j  m9 t$ i5 M/ y, S
  90.          /* 配置自动轮询模式等待程序结束 */0 w7 g# ~) |+ B% d3 p) o* m; P3 ^

  91.   s, A0 h1 o) l) L1 R
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    : X9 J: Y5 X. X) F
  93.                                  {                                         ! O8 G# |; F) t8 i
  94.                                                 #if  QSPI_DEBUG
    / c1 G4 {+ ~% Y6 `6 O$ @3 q/ r  ^
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");% f' z* ]6 T, F& c
  96.                                                 #endif
    : U; o5 A5 j( S4 I3 {& J
  97.             return HAL_ERROR;
    0 U0 g- w  p, P# C9 H/ p1 Y
  98.          }
      C4 i/ X( L5 \$ g. {( Z9 c/ x6 a
  99.          /* 更新下一页编程的地址和大小变量 */+ O( U, A. t7 N% `5 F
  100. $ K1 N7 Z' m+ B% w6 w
  101.          current_addr += current_size;$ |( C, v% l7 m7 J

  102. - M! w' A' h, _$ I
  103.          pData += current_size;
    4 e$ ^8 E: X6 _: P  R5 n+ V% K! T$ w

  104. 0 n) C0 O$ X+ t8 D4 B
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :  f. l4 c- I, q" f2 L# t

  106. ( l" G. G8 o. \
  107.                         N25Q128FV_PAGE_SIZE;
      L/ [5 }! Q2 f7 s* P* k. y; A% Z
  108. & D% p; C2 C5 B4 k/ e( o4 l
  109.      } while (current_addr < end_addr);% }# C& y8 \: y# h! S
  110.      return HAL_OK;0 Y& m: H0 k. _0 S( X" }" [$ c; y) y
  111. }
复制代码
! `4 `) a! z8 M8 v- u1 n
该函数实现从指定地址写入指定数量的字节。1 g) \1 p2 X) [5 ~, |" D
# R2 F5 o9 w  Y& [+ o; T
8.擦除FALSH
2 a% [4 W4 l( a6 @# C; Z! @3 l1 }9 A. \8 X
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)1 R$ a: M7 ~. q# y+ Q

3 f5 c6 _: E: P$ ?( v2 t9 u% V4 r
20191006140420479.png

" J6 g' P% i/ B4 C- K) F- i" V/ t+ H# ~6 q
参数设置为1线指令 ,1线地址,24bit地址,无数据阶段$ F. C. {! G# F/ n. l

( x+ G4 x* f9 F0 \7 k
  1. /**0 i( _& h% W$ `* G* I* h/ ]; h
  2. * @brief  擦除QSPI存储器的指定扇区
    1 T) F2 F+ P% |# {3 Y! Q
  3. * @param  BlockAddress: 需要擦除的扇区地址
    4 K: {' U- P0 n' u2 _9 M* t
  4. * @retval QSPI存储器状态
    $ ?+ C9 o# M1 B/ X' X' @6 O2 Q
  5. */
    / ~' k* }7 M2 R7 e3 p! |1 l
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector), }* p; Q) Y9 H: \" k+ G: C4 q
  7. {+ o; f6 u3 g4 n
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址3 r2 `) ~& }0 E1 a. I
  9.         1 ]8 K9 A, l5 W  {! S
  10.      QSPI_CommandTypeDef s_command;
    1 w4 u6 r' G5 e, K7 |
  11.      /* 初始化擦除命令 */
    5 s7 ]9 t* T* w1 d+ F* ]
  12.         
    ) Q6 D2 L0 X3 m5 X) V4 O
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;9 |6 W/ S3 U" a  [3 e+ Y3 L2 M
  14. 3 I% K: Y2 N+ V, P8 t7 V
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除
    / @1 a: a4 b7 m2 z8 _
  16. - Y; t$ a8 J; l( ~; ?8 v. ]
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;! P; N$ P1 z) f: E8 k$ U  W) g

  18. - [! L& B0 j( f" Q; ~2 B; u
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;" {! R( N2 \( |$ n& D5 s$ z' Z

  20. - v! \- t. I3 `8 y5 X8 g" L
  21.      s_command.Address           = SectorAddress;
      i3 L; L& q* X# T( c$ c
  22. % P" @6 j! E2 O( |% b5 Y$ I! ]; V
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;3 R: C( y8 G& S: ]& m8 N

  24. : n, D5 i  N! l# S
  25.      s_command.DataMode          = QSPI_DATA_NONE;9 t4 U% m% l' h( @  m" t; G
  26. # t8 A5 E: U  p! b
  27.      s_command.DummyCycles       = 0;6 j- c9 n& f- a+ D( S( w) {
  28. + D4 e& U) ?4 `. p
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;) s) D' L& s8 L9 _$ y+ y, N* K

  30. : F: ^$ Y# i  y! d) P& N1 D* o8 t
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;) p4 v- h4 X5 g& J8 }. q
  32. ; ~, N6 Q* d! M, t
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;' [6 G; ^8 L( k; B& X3 H$ h

  34. ! j9 U! w! y% Q8 f
  35.      /* 启用写操作 */, m/ L( T! C5 e5 o* K2 F9 i

  36. & _* k' M7 m8 g9 B1 a2 q2 X
  37.      if (QSPI_WriteEnable() != HAL_OK)
    8 K! {/ g" z+ I
  38.                  {0 F8 c: |! }) E
  39.          return HAL_ERROR;
    0 B$ p/ B, e& f0 [9 [
  40.      }
    , N: U' S, k' u1 e5 O3 Z
  41.      /* 发送命令 */
    , B* ^6 U7 Z9 `, ?
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    9 |$ r' h; T1 O$ k, D
  43.      {
    ; h0 q6 ?2 W+ X. X6 R) W) A8 I  W8 y
  44.          return HAL_ERROR;
    ( B) s: J! R3 J+ |8 a& D: A
  45.      }
    / f% b- y! G, T$ g' Q6 ~
  46.      /* 配置自动轮询模式等待擦除结束 */- z5 b% S" s5 Y+ L
  47. 4 t0 ~$ O4 s  w6 e+ a
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    # I: O# P0 |7 H+ U5 E
  49.      {
    ; V! @1 v) a  z  n; ?2 T
  50.                                 #if  QSPI_DEBUG/ a+ O3 ~+ k9 Q; h9 `& r8 q% s0 ~( r3 p0 e
  51.                                 printf("erase sector error!\r\n");
    ! F7 K6 f$ p2 @% l+ s( [
  52.                                 #endif* |1 W5 x4 \+ [0 w7 q1 V$ A
  53.         return HAL_ERROR;" ]2 h: u% O& M( F6 c3 H2 x
  54.      }
    ' }5 U  C" L( o6 V/ m
  55.     return HAL_OK;( Q' W0 \7 _& H& C! S3 I  J. ?
  56. }
复制代码

9 z2 {( o3 J. C3 D' J: S: E第二种情况是全片擦除,时序如下:$ e7 b6 O/ h3 p/ E. ]5 |
- _- g% U! K7 V9 y+ x4 {! i
20191006140919805.png
' j1 V0 L1 l& N, k; p
$ k- I2 }, F+ {/ q1 ]
全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。0 x" e3 X/ }, |
2 ]& k& P7 f% l  _8 b4 @8 `" ]
  1. /**
    ) L, g  }) q. }) y8 @8 [
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s, N' l; G4 ?) M4 {2 P' C6 m/ f
  3.   * @retval QSPI memory status3 t! L9 p) l& K4 C
  4.   */* W# t0 A% M( ?8 m. w& |$ T
  5. uint8_t BSP_QSPI_Erase_Chip(void)
    % O* G8 w! `$ ?  ]) Q3 u3 a0 R( G
  6. {
    - ~+ y% L$ b! V' o4 o9 D
  7.   QSPI_CommandTypeDef s_command;7 D6 v* W* w1 z) R9 ?& p% g

  8. - ?* T3 y/ V6 O% X+ T1 z! f. }, s
  9.   /* Initialize the erase command */
    % N  u5 y% I: |" D4 {8 T
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;( Q9 V9 l1 z& E- ]/ E
  11.   s_command.Instruction       = CHIP_ERASE_CMD;6 A: N. Z6 N) W0 d0 m9 D
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;
    1 a4 a. ?! ?- P/ b0 c& P
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    ) ]( Z- J8 u' ]# N9 l1 `
  14.   s_command.DataMode          = QSPI_DATA_NONE;
    # }$ y) B2 U) L/ Y
  15.   s_command.DummyCycles       = 0;
    ; |$ O# i( t+ ^* \: q
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    $ g$ b# y) g) H. @) D
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    8 I: G9 b. @3 M8 k. E
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    & g2 {# u3 b8 _+ z
  19. 3 k- `" i0 y* S6 c
  20.   /* Enable write operations */
    # F0 ?. k2 k" i# J- }
  21.   if (QSPI_WriteEnable() != HAL_OK)
    ( @% W- L: c: d' v1 E0 W& |3 F, m
  22.   {/ T* |3 X5 Z- T9 D$ ~
  23.     return HAL_ERROR;
      w6 ]6 J" f4 U2 d! ^
  24.   }
    0 D* a% `% U4 U1 C
  25. ! p) n% a" t. n- n  }
  26.   /* Send the command */
    5 k- E# U) v/ q: F8 D
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)( s# T6 S, C# ]! J0 o
  28.   {
    9 o3 a8 ?$ s1 K+ O; u
  29.     return HAL_ERROR;" \2 U3 I* B3 a5 u5 _; \6 L
  30.   }) ]" p( o8 \2 A
  31. 4 R6 Y+ O& \1 P" ~2 G9 Q
  32.   /* Configure automatic polling mode to wait for end of erase */  
    ! M8 g& l$ l& S, _; l7 S% p
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)5 z8 k+ W! P; v0 f2 k0 T
  34.   {' z' N! ~9 e$ p# j( D
  35.     return HAL_ERROR;" {4 C: |3 U& n* @) r
  36.   }3 X6 j) z1 A4 l, n  G8 L

  37. : ~% O2 q! ^" T3 }/ [
  38.   return HAL_OK;$ n: h" \7 J: i) C6 u
  39. }
复制代码
  ~# W+ q$ R/ w& u* T
9.测试程序
- p! ~8 N7 h7 D- M1 A
, c% X/ T. K0 }5 L3 o/ q) `  i9 t  ^测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。3 n1 U5 C6 @7 ^3 i( s* s
( g3 L+ Q) ]! v: r
  1. unsigned char write[4096];
    ( D7 c' E+ R/ Z
  2. unsigned char read[4096];   8 R3 s  w9 g9 y( x( T
  3. void N25Qxx_Test(void), Q. E& M5 }1 n+ _4 i: y" ^. ~8 R
  4. {$ J0 h0 i+ Z  {0 b
  5.   uint32_t i = 0;: G. n+ w5 A& ]) O
  6. $ M' F: n. x  J
  7.         for(i = 0;i<sizeof(write);i++)
      ]/ T4 G' Z4 Y/ [5 J) H) P
  8.         {
    ! f  O2 K' S" y( J) ^
  9.     write=i;7 O1 M& p) R( z. J! Z" X
  10.         }: J7 F7 X) t0 \/ Z+ ~- C
  11.         
    - @9 {3 v: x; S4 w
  12.         printf("write data:\r\n");        
    % u3 U. S- o$ \( H
  13.   for(i = 0;i<sizeof(write);i++)& E9 d1 ]5 ]; l4 ?
  14.         {
    " E2 c) ?* ^# m5 e
  15.                 printf("%d ",write);
    1 |9 I6 f* F- F* u4 _
  16.         }
    ( P3 z+ K, v' F' ], u7 ~$ M# ]  c
  17.   printf("\r\n");4 b$ k: K1 m- j
  18.         printf("\r\n");
    , e. Y" S6 V5 G. U) S/ t, G
  19.         printf("\r\n");( \+ B; F0 q1 \+ m, s% [6 w
  20.         9 s9 Q" j7 N0 N* Z+ ?
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
    . m  [( R! B! q' m4 o4 U( x
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        5 e* N0 h  Q$ D4 O6 {/ d7 G
  23.         
    % t1 T- D* \% H. i
  24.         printf("Read from QSPI:\r\n");3 t' P2 k  B0 V
  25.         
      u* h1 l4 J7 J  J& C+ K" o/ c' N
  26.   for(i = 0;i<sizeof(read);i++)
    $ B" o& Z" s6 i6 _4 w
  27.         {7 a  G# z7 F7 ^+ q& U  D/ h
  28.                 printf("%d ",read);' v1 l: g2 b+ v4 w$ q
  29.         }( X4 o& t1 s6 ]3 V8 A
  30.   printf("\r\n");6 ]' M% F0 j6 E6 v+ {- n
  31. }
复制代码

. W7 O0 K# W, J" H- q. g写入的数据(部分截图):
! F/ R3 R* M/ C. c6 v3 o6 [
8 ~6 t" X  q7 Z* V8 }7 ~+ G
20191006141636369.png
' U1 P6 j* b+ ~
& h0 E, X4 l+ v! F% s; V6 I. u0 }3 M) H
读取的数据(部分截图):+ r) t( W8 u# `! n# d
. b9 q2 K. q5 }9 Z& _
20191006141537697.png
) y% [1 P' ?  z" a9 m$ `

8 m3 e! ^, [' Y4 E6 Q# T* b截取相同的部分,两次数据完全一致,写入和读取成功。
" K  z% T, m1 s& R1 p9 D: q# c
) U9 z& y, i( q最后,讲几点需要注意的地方:! n4 w; G  H( ]2 u2 y+ H/ o) G
: R4 a" x$ M' j0 A
1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。
* S5 x! F$ y8 o8 e: o7 l2 C# u: z0 c" |$ f
2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。, \  k% v$ L0 R, C6 w

: ~' K# y+ |! H& \1 G9 P3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:
2 e* \/ O# i* t 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
0 B* K& E; q: R 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。9 ^+ h' k6 A& |" w$ z. o2 y0 Z

) R) u4 o1 ?8 L4 k" e  R5 f4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。: G$ z) |) l8 t9 P# P6 j
. U9 R' n: h- Z7 r# N1 }
5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。
, v% S: G7 E! J, x! R1 w! X
9 f6 |1 p- w7 k) N" \5 o+ H
20200316162249870.jpg
* ^6 |9 p; `  J+ ^% l$ r+ E

3 \- b6 V6 ]7 }! T- L6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。3 @7 m! }% u0 H

9 Z( `* n$ A5 {% ]+ e- R$ |2 O7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!4 R+ z1 I, M2 o. f' F3 Y
3 K! @" h$ J3 ]2 o# ?1 W4 u6 F$ q
* }/ w9 O, b) w- v

  x* N# B2 k. p. }/ F' |# M$ k# f
收藏 评论0 发布时间:2021-12-16 21:00

举报

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