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

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

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍
4 w/ g" Y0 o5 @- j- n& w, d) @4 F# L: R
QUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:
( `# T$ b# r/ v2 Y! l3 q5 I; C0 L- M5 e
( {8 j  V0 e' D9 e% Y' d 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。
6 ~$ ^: g+ z# T! ]2 ~( y; q7 u+ i
 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)# v" u9 E2 `  A' F$ k3 T/ v2 j
  T0 L* ]% k% {$ w
 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器; s* `/ Q8 X7 c4 d

+ u& [( \' h3 q$ J/ N: Q, Y- L采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。
8 C0 m. o, M" O& ?) E% t4 {' E% P  I5 c! K+ [) y& F1 l
内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
! o; L5 m& X) T7 p+ _8 y4 O, C9 G1 D/ C0 I# D7 e6 P
内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。
0 a5 t, E2 P3 B; o7 t* V& j% w6 Y' a! {- l! D9 _
二、 STM32QSPI 功能框图
6 d# i" o- x( J3 \; M9 U  {, I! g4 \5 Y6 p  X; E/ j/ O4 f6 K
20191006091636349.png

: g+ M+ D. ]( z6 [5 x8 z9 F2 Y) _, ?+ A" x
STM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。
1 `! G" M7 P" Q) s& N/ Q
6 s  H, ~* ]' I! F三、QUADSPI  命令序列9 ]0 ]5 \; Y" G+ j- n: U

- B1 x) `2 l4 T5 f: Y* oQUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五
- O2 v: k4 U" E; p个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。
8 K; Q1 r5 |3 r  {! qnCS 在每条指令开始前下降,在每条指令完成后再次上升。
0 f' d- r% O6 L% [
  Q$ Q8 A, O2 n) K4 X, {3 S  q
20191006092242666.png

3 ^! F( r6 a' N; t$ P/ E& Q3 K' i
上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段., {6 Q: B$ _6 |- V

) I/ T6 z$ c$ n/ n3 h: B交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);
& h- V' Y, K: Z3 s
4 g! W# {" C; d( P8 G5 A2 A空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高$ D: t' _' R4 U2 K$ l' x
的时钟频率时,给 Flash 留出准备数据阶段的时间。
. e, u. h7 O5 |1 ?1 ]0 d若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
, H$ h# F8 `. C- ^
9 s0 O) h* Y/ {8 ]  D! M) \4 T% V此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。1 D- C- \" b6 K8 x
1 N* h& k7 n" ?1 |
四、QUADSPI  信号接口协议模式
' Z; W  i0 T5 S0 a0 L8 c2 c
, l) k; n# X/ [/ G7 P  |下面提到的几线模式都是指数据线的根数
# [# O8 o2 D& \6 l2 T+ Z- B# q8 [% S" P0 s
单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):( R) V( F2 \( i- e0 C5 S6 r3 e
传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与# [# P' `: i& C# H$ s
IO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。! w% |6 ~) U+ |' d
通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可
# O$ W- H$ n; b) b* l6 O" @对不同的命令阶段分别进行配置,以使用此单个位模式。' z) t# K" T# U3 O$ B( l+ x
在每个已配置为单线模式的阶段中:
) E! k0 @# A! V# } IO0 (SO) 处于输出模式
, R0 G, v; _2 v' U  Y IO1 (SI) 处于输入模式(高阻抗)
6 s6 j2 u; L" J IO2 处于输出模式并强制置“0”(以禁止“写保护”功能)) L& e4 P( o) h6 S
 IO3 处于输出模式并强制置“1”(以禁止“保持”功能)+ Z" s/ o, {2 K4 N
" B1 V6 @; K  @' }
双线 SPI  模式:# {' @+ `. a/ h
在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。- O6 b7 f2 S# U  V3 N8 s- K7 ~
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可4 a5 z0 F  g) L+ g+ ^$ v
对不同的命令阶段分别进行配置,以使用双线 SPI 模式。0 r* d( z. R8 p1 B. B" `
在每个已配置为双线模式的阶段中:
( Y% X& k& N+ ]4 Z IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出
+ ?% U6 X: X" ~" Q2 C) ~ IO2 处于输出模式并强制置“0”3 h/ F: V- Q1 {( ~3 G
 IO3 处于输出模式并强制置“1”, m0 y) p# @$ O% p
在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。
. v$ K: A; d8 g% t9 A/ {0 Q% K
2 N2 l4 K$ O3 e  N' l* U6 I- N
  _5 r: S! N6 a4 K四线 SPI  模式:
& r4 s' w6 E% [) K4 Z. I在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。
% f% _0 R4 j! k+ `  ~通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可
0 \0 S/ a  e' U8 n- X+ t对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
9 w9 m8 E' ~5 ?9 W4 u- X2 N在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高) A+ d% N+ [! p
阻态(输入),在其他情况下为输出。! q! ^) I" y1 B/ G2 i
在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
! z# R9 y8 S& ]% ^0 z" y  @IO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI, _) ^: u0 W* {1 g" b; |7 L
激活,对应 IO2 和 IO3 的引脚也可用于其他功能。
+ e9 k' a( V4 g: B/ g
$ O1 r3 \6 F$ m* \4 a+ E
2 H/ i/ ]1 F% R& d) rSDR  模式(默认工作模式)
. q. N/ z2 l$ I) l默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
" v7 s" k; O. \( Y) ?; `9 z5 ~在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK: j; z# [( |5 ^/ R  O
的下降沿发生转变。) _4 `- I* S9 e+ v9 f( F
在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
, P& J) A: F5 E9 `+ r况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。
5 H' h: f; n* V& Z1 X
& g& ~5 H- k0 n! X0 `9 Q" Z5 l1 D# f$ z  Q
DDR  模式
8 V" {& C/ h; W$ F4 Q& z4 v0 Q9 A在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信
# G3 y+ A0 s! {' F6 u5 ]( V号时,将在 CLK 的每个上升沿和下降沿发送 1 位。
- K  C1 }. V, ~1 }: N: T指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。5 J6 y9 v0 k1 Q% W
在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数
1 z) K* \; j' b据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期6 t) h: I/ B& \
后(下一个反向边沿)对信号采样。
, A5 Q- U, e) o& y
! s  U4 x. o" C9 Z6 X! M6 G9 i五、QSPI 配置(HAL库)- u6 L4 [) G- Z! Q3 |9 r8 F7 j
0 t/ H1 m. V% s& v7 ]! Z5 o
1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟, n1 x4 |9 D, ^3 n  ~  U$ g

; P) U% J( M0 ^5 R
20191006121236849.png
( A& V; e0 e+ s5 c

- @' n. [' B' c, C9 J
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)
    9 ~; Y* C) i7 [/ Y& X; E, P
  2. {$ X5 S- [, M* o' ~# e
  3. % {: {# J6 F) d0 B% R* [0 ^% t: t! ?, m
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};# a' P6 j' C; h$ ]! K' D
  5.   if(qspiHandle->Instance==QUADSPI)
    # }0 @8 z1 V  c1 d3 A8 Q
  6.   {. B* F8 n  S7 k' Z# y
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */! w9 h9 J, H% d+ o  ~" W+ U

  8. * c' c' _4 I# R0 x% x/ Y
  9.   /* USER CODE END QUADSPI_MspInit 0 */
    9 Y% d9 g* x. X$ j
  10.     /* QUADSPI clock enable */$ o+ k5 w/ Q) |0 u
  11.     __HAL_RCC_QSPI_CLK_ENABLE();
    - V+ C( s1 i3 E* A/ V

  12. # l! F$ C! r8 c2 `
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();; F$ f3 k6 s0 Z% Y* G; Z
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();/ F9 ^1 F0 N, {( j+ f
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();& x$ q0 T! c/ V3 F0 ]! ]# ]
  16.     /**QUADSPI GPIO Configuration    * d& P( j2 `! D2 X3 I7 V
  17.     PE2     ------> QUADSPI_BK1_IO2
    $ }: t8 m( f' K9 d9 Z0 X
  18.     PB6     ------> QUADSPI_BK1_NCS
    ; T; x; e/ Z3 Z& n) l8 w3 l
  19.     PB2     ------> QUADSPI_CLK0 S/ z( W' m8 R  G% A" J
  20.     PD12     ------> QUADSPI_BK1_IO1$ I; n3 ]) z6 N
  21.     PD13     ------> QUADSPI_BK1_IO3
    ; I% N3 c5 g8 ]+ G0 Q5 K' |
  22.     PD11     ------> QUADSPI_BK1_IO0 + ?: U: R; ?) R9 L$ Q: o" K3 |( @
  23.     */( u" C4 M7 L: K
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    % f2 J; E; V4 c7 ^, ^, Z; u9 f
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;: L. [6 n6 {' O" {  T+ S& Q
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;5 n% E  l4 d3 ^
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    0 y, r2 A0 |% c% _% Q! d" @
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;! t+ i) Q4 \( N* P) I; p
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);4 Q5 k$ _  x, r0 k3 Q
  30. 7 M/ C, o5 K1 h" g6 x6 q
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;: q$ \1 t  a2 R: |
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;! I$ a  u* B/ J# U- y+ e" _
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;' a8 b  ^6 R) u+ b
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;( \, p6 L4 a2 M7 A1 j8 [# `5 z+ q" j
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    0 a# }- p% ^' j
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    7 i( Z8 l* d" ]; S
  37. ! q, Y" L" @- u6 V
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;4 i+ @+ b' G7 B8 b' _; m) M( g
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    , _5 p! I; {. N  K' u! k
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;5 s$ s( z* ~) p
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;: U0 z* C6 R9 b1 n. d4 ?
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;/ S9 A4 P  U6 E4 [" x2 h/ c$ t
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);" _8 x: Z: ^6 [

  44. - W7 ~9 ~" W5 q5 ^# n" D
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;
    . F) |6 X  N9 F, I( U
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    ; H. ^# d# ]; H! ^. ~+ R4 x4 C
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;" G1 p; b  G2 i' I) h2 p
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;$ i" N1 ?$ \- `+ s/ q; Y& E  A
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;/ B! f" v, h5 h! K# h
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);# w! z; l8 L) V
  51. - h7 {" r  a* W1 k) C- v' t% ?
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */
    ! C- C! W' s$ [) N% h
  53. $ g+ B% X! a9 D
  54.   /* USER CODE END QUADSPI_MspInit 1 */# n. `0 m9 e5 |" ?" p* A
  55.   }$ p7 V. s! d& j5 e" U! K8 H- e
  56. }
复制代码
/ p" G( I/ G, c4 X1 h; x
2.接着初始化SPI外设9 P4 t8 r6 l8 q, s8 \7 ]. f' j

  1. 7 ^: Z4 Q9 }" V. D
  2. QSPI_HandleTypeDef hqspi;
    7 v& t* K  j! A2 ?1 z3 L

  3. " a! W$ \0 J& d' ^# G9 e1 f
  4. /* QUADSPI init function */( Z7 ^: N/ o' T
  5. void MX_QUADSPI_Init(void)
    * g8 S0 x' {6 E5 I1 [4 j/ D
  6. {
    3 [+ [  W( M' J: Y. h
  7.   hqspi.Instance = QUADSPI;//QSPI外设
    + G/ [5 t% ]5 D! ]
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
    " b: @' G3 Y. j' I2 v
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
    7 @) \- _: e; o% u8 T
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)" t. Y- L' g" e) N7 O4 |$ w# Z
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
    $ J8 j  ~0 R7 n/ R
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数
    * n' @/ j" a) F( u
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0$ Q/ O& j9 D/ l5 r
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
    - R0 n! Z. a9 Q( S4 t1 G
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式  Z- S9 d% F$ g1 Z6 M
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设# R4 U" A: D# W; t, F8 f9 y" S
  17.   {
    9 _8 n9 B6 ~* y* W! \9 C4 U9 O/ b8 @, n
  18.     Error_Handler();  Y& p: z/ X3 t5 o4 s$ f
  19.   }; L0 r  u( z, V: r+ I8 s) _
  20. }
复制代码
: B4 I- |+ i  {
3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。
7 @/ m0 O2 v* H* B4 R$ b& B5 d8 U& n  W* ^2 |$ p
我们首先需要配置下面的命令参数结构体:
8 A9 t* I+ n' G6 s, J9 ]. @! v9 w- F, V$ `0 E+ Z
20191006122437823.png
( c+ F- A3 k- A
; g4 m! \) _2 t. r
Instruction:指令类型(读、写、擦除等等)) K/ |3 s. m8 {+ _* W2 ^
* o4 l3 L& k! C: E4 A
Address:需要读取或写入数据的起始地址
8 e! z2 z& |  o5 H& _
" t& F9 @6 M& d* WNbData:需要读取或写入数据的长度
' E8 Z' D& ?- ]' w$ e4 ]8 ^
" O0 s7 F2 ~# ?( g% E* uDataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。
2 l8 ?  b6 k1 }% j$ b4 v% I
; n3 r+ I0 Q- B; A3 r% b" f: x1 W这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!+ w$ g5 d% G, O$ R* o" G
' o) T  `( _) |5 w9 [
4.读取芯片JEDEC ID
2 K; X5 f8 V! @
. T6 e% p4 v3 R3 h% l9 E首先查看手册上读取JEDEC ID的时序:
# L9 e. H$ L2 E: w7 N! P( h) N& u# N0 ]+ y7 O3 U; ]* L4 y- M
20191006131644114.png

( _3 C0 N2 o3 U# E- f& c/ G& _1 s$ m9 i* J* y4 r. e; ?
可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以 * k7 W  k4 K1 J9 a) c
* E6 |3 J9 d+ u7 p
InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式
, E4 \0 w, t# R0 H. F/ h/ X; X; c- H* J1 @9 G
DataMode = QSPI_DATA_1_LINE;//数据单线传输模式4 V  o4 B8 f- _% D# i6 B

9 g: [. n# x7 P6 P$ V7 i: i$ DAddress和AddressMode 这两个参数的值都置0,表示无地址。2 a" O6 e! N" N1 N3 s
6 `  a5 C9 V# Y: V6 L* U( k
NbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3" p% r2 v, g, A3 ?4 h

! C2 A$ l3 u  _1 }其他值和下面代码保持一致即可。
6 V+ H3 L3 D) K( u3 j+ V7 ?
$ g# m' _# ]2 C7 [, {! _/ M+ u. r6 h然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成
5 `7 c, Z* [' d+ Z/ j* `/ R
: _. O  w8 d! D1 j最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID
% S& ^. J  D$ N' E4 `) c. |( v- m% i" ~) E1 Z8 E* z3 E* l+ [
  1. /**
    , K4 ~) k9 x8 M. w0 V
  2. * @brief  读取FLASH ID
    $ H, O6 R) e" F5 K+ Y6 H& E) h, ^: ^
  3. * @param   无
    3 K  R+ L. Y! K  M1 T% }
  4. * @retval FLASH ID
    . F! u' @. v, i, y
  5. */7 p9 D0 u/ _' y* D
  6. uint32_t QSPI_FLASH_ReadID(void)/ g& V! h9 E- F) D  O# r/ q
  7. {
    , N. ^9 Y! H; S) k/ c" Y& n7 u
  8.      QSPI_CommandTypeDef s_command;7 U$ n2 m9 }" J, Q3 L7 C
  9. " `+ [. w. ~; \. x/ P# I
  10.      uint32_t Temp = 0;
    " {( X" l3 j! m3 P0 d

  11.   }9 a1 Y" s3 J9 l# Q8 h. c# Z2 R
  12.      uint8_t pData[3];5 W+ b0 `# V, i; W7 B4 x8 w
  13. $ T# ~9 I" m5 ?+ W! V7 l9 u- q9 `
  14.      /* 读取JEDEC ID */
    $ p! c( C. Q! U& t+ \$ x2 `
  15. ; Z0 l; L& q9 [
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。
    1 N% |/ ~9 ]5 N: B

  17. " V7 g5 U' n- l4 p. J
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令
    # B0 B# j1 }# {  C: u% b/ a0 ?* q: y
  19. & f: z8 k5 H2 j7 c% V8 i
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度
    + o: F/ F7 |! ?- _$ @% P

  21. ) Z" r+ f, E8 q( S9 d0 [
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。
    5 ^4 G$ h" @+ T+ q, R6 x( y: ]6 D) O

  23. " Z# a9 ?: q- e+ o' P
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。% v+ f: ^6 H$ D$ D$ t/ V0 W
  25.           $ @9 {6 u/ k( h& @  C
  26.      s_command.Address           = 0;   
    $ \' |; d4 p5 w: \' `' i
  27.         
    , H# l* n, H' O2 |
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度% i$ J1 A4 e- Q& @8 |
  29. 1 |/ L: x5 F7 _% l* B
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。3 d2 I% q4 n, K

  31. & P4 Q+ o5 u, h7 J: ~
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。3 F8 a0 y3 m+ ~. Y. e8 A1 }$ u

  33.   Q1 o5 T2 u5 {0 t
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。
    1 \& U3 o  `; s+ n
  35. + R2 `, A7 I3 W. W, q. T& f2 t/ Z
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。- d- n* b7 f/ ~( E" K; @
  37.                  ) I  \) R% T  F
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。' O3 x: F) r* ]4 F1 M
  39. + k' B! |, {4 ?
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)" h3 D: D$ h; ^( ]
  41.                  {6 e; `; \: a: r
  42.          #if  QSPI_DEBUG
    - o" M, T* D2 C9 j. S! R
  43.                      printf("send read flash ID command error!\r\n");0 F( w4 Q- V. n$ @; S& k
  44.                #endif
    ; F* l6 ]' g) q4 E6 @% h
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */- B/ S; B+ O1 F8 v' }
  46.          while (1) {}- k+ z2 j2 H4 K
  47.      }! t6 Y* `/ N) `; P3 G1 t6 o; e% U+ [
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) , G* i- k( S' k% @" y. W
  49.                  {( N6 ^. D  A% j8 v# _% g
  50.          #if  QSPI_DEBUG& T1 {1 G) S, Q
  51.                      printf("read qspi flash ID error!\r\n");! ^* f- P4 W) u
  52.                #endif
    2 j  N/ `8 i3 `& ~# R
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */
    . K3 u: ^# D, `4 Y* ^/ D% A
  54.          while (1) {}4 K; I8 i2 K  M% G2 B
  55.      }
    ) C" l1 u7 A$ b. P, @" f
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );. P) a9 c2 l$ M% U/ _) i& \. J
  57.      return Temp;
    ( G# @4 b* S( W  E! w# }% K
  58. }
复制代码

- W! V0 m4 z, [3 L3 ? 5.FALSH 写使能
/ Y. M0 S) K8 C; ]6 F& g
' K3 x% x% ~0 B. \# H$ q8 T在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):
; w  ?% B6 F& j2 N4 i9 j! p* X' X8 `0 O: z
类似的,需要查看芯片手册,写使能的时序7 R5 E  A4 L& v# w, g
2 F) g* i& k& F% G; a
20191006134057767.png
: j2 ~* z' i0 X8 i0 B

( f2 k$ _5 l5 N可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0
: k& \) @: e& Q5 @" e! Y) g4 f2 m8 X) a% C  R/ Z" z: u3 o- R0 \
  1. /**" {+ \. Q( e! u
  2.   * @brief  QSPI FALSH写使能0 }1 {2 E: c6 e- @: l0 F
  3.   * @param  None
    2 |- ^- V& k6 }$ O! l3 @. V) i! C# `
  4.   * @retval HAL_OK
    + \/ ~! S4 H) G  G
  5. */+ m& u; C0 U+ L7 ^( l+ I' B
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)
    6 u/ e4 ~* K# K! x  N% y: N5 a
  7. {
    8 @9 Y  b; t. U$ L0 C; q/ z& L' Q4 o4 U
  8.   QSPI_CommandTypeDef     sCommand;
    # f( k8 s' ]3 I' A
  9.   QSPI_AutoPollingTypeDef sConfig;- R) y2 s0 B, |" _" C; I2 p5 C  f

  10. ) `9 P3 d6 x- W' t2 C1 r. f' s
  11.   /* Enable write operations ------------------------------------------ */
    . E( ^, D7 D* o9 }
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    6 J7 W" q4 O7 j
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;
    9 _! v& c0 v2 s4 O* H5 t: y
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;2 G; @5 O% \0 Z, \/ y& P
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;; X( h) {# U0 z0 N' W$ J
  16.   sCommand.DataMode          = QSPI_DATA_NONE;$ I5 f: e( Z& d  A6 E- ?
  17.   sCommand.DummyCycles       = 0;
    7 H. D0 }2 t, J) G+ w7 W
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;
    % E* m' v- j8 T* a: Q( G/ r! f, E* K
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;% u+ u/ A$ e) l% ]* ]
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;) U* S$ }8 k" `/ F% p  d

  21. & b0 \* Z, j/ ?
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    7 q0 q/ c. O  Q- k6 d+ w9 ~
  23.   {2 y3 ?# A9 Y- w  C
  24.     return HAL_ERROR;$ P# O/ K; Q/ {' V& l! _' e
  25.   }4 D8 p  o; G" l- f

  26. * ^1 L4 `& M8 B; t) Z
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  
    ) T/ I* h3 a& |6 V) A; Q/ ^
  28.   sConfig.Match           = 0x02;
    / O" k# r; N+ G0 j9 G& X; ^1 x
  29.   sConfig.Mask            = 0x02;
    * ]4 P$ i$ l! F7 i; ~/ t
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;! ~) e* n3 w- X4 r
  31.   sConfig.StatusBytesSize = 1;& ~) J1 C0 U2 J8 I9 b. H* p
  32.   sConfig.Interval        = 0x10;
    ! A5 g3 J- v2 n/ o* i
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;
    7 s8 R1 p9 {# i( ]( t
  34. ) Q3 @" y3 n7 p4 M) L" l4 _( W
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;6 ]  @$ l! P1 J' K6 q
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;
    ' y; ~, W, E( Q+ j* I6 Z

  37. ) D0 P! @( f! ]% q
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    + x9 u; j5 T/ n# B( E
  39.   {
    : s7 U- ~. S' m
  40.                 return HAL_ERROR;        
    - Q' l  @6 l( z) p+ m+ x0 ~3 W
  41.   }
    9 L. c7 q* L7 s* I+ t
  42.         return HAL_OK;0 Y& f# m" j/ }
  43. }
复制代码
9 Y1 e: K8 s9 g) @( ]8 H
这里需要讲下 QUADSPI  状态标志轮询模式:: a6 K  P' ~8 i
在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。. F3 M* s% o- S
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。  A" y7 a4 {2 l5 o
对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入& i* ]7 I# H& k0 D& Y1 a3 h
QUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
' R4 E- q1 b, Q# L0 q一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。
7 J: E4 B+ Q; @; _; j- R- f在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。7 q- K1 i4 Z0 N. l
如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内
( x- p7 L: ]& R0 d6 w$ f; [/ A/ V容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。
' N" z/ }. [# {7 `如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着
% C( z3 U1 q. D8 N状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。* u, F0 d+ y' z  z2 Y
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。
- @/ q$ O6 V2 A( o' D; j4 ~/ Q如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。: |- D. q% W) U3 r; \
如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否
( X8 @2 ]! X9 F/ [( U2 M2 N则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性" i- T' ^1 g; r# q- P4 t: R9 r
访问。/ e1 l" s. q( o+ s
数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不
; O2 z$ `: w- q, t, F" N! v受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数/ c6 q) C$ U: b* b/ c+ m! ?
据读取后清零。4 g% j# S# G2 A! o. u& j) v
4 S& v3 o& W, n8 |
简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。& `- H3 h8 f! I5 r! t( A6 d1 V
% n9 q- }* H- r) g# t
6.读FALSH
+ {) D+ C- h/ T, i5 \* l9 e9 o- a4 l( c$ ]6 j) b3 u
这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序5 O& ~( y0 h( `$ O! n- Q" ]7 X0 |

% b) }8 G. {" p0 \1 h
20191006135244693.png
& K$ j1 q, Q$ q' \  b7 n1 f
! n1 g) `. s% Y; u3 l0 j
从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:/ C9 ]4 J  ]6 [
: v# F! x$ _3 O8 |
  1. /**( n* I( q2 i% E7 o0 k1 |
  2. * @brief  从QSPI存储器中读取大量数据.+ g% c) @1 E% U4 N; D! W
  3. * @param  pData: 指向要读取的数据的指针
    $ ^& f: V( R3 D
  4. * @param  ReadAddr: 读取起始地址7 S6 e. r+ T, M# M
  5. * @param  Size: 要读取的数据大小' D3 n- }0 C/ H4 L
  6. * @retval QSPI存储器状态# a3 s3 I- q8 r7 b0 U5 E
  7. */
      }( K1 ^% r& m8 h9 [5 U9 \
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
    , V1 O( @: }+ v" `/ S
  9. {" g! w7 g) i, _6 V- X6 w7 m& R
  10.      QSPI_CommandTypeDef s_command;
    ' s; s; c# `& k: ~$ @

  11. % T3 G. U; R/ R  p1 ^6 y6 C+ t2 r
  12.      /* 初始化读命令 */1 z/ K" k* ^! f
  13. % B+ U9 ]2 [7 k5 e1 n5 V' U! S
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;6 V, J$ M1 r8 B! B: ^2 z5 A
  15. , a; ]' y9 }0 P
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;2 V1 T; l2 ]+ ~0 J* n3 |
  17. # u* n9 r% `+ R7 S4 ?
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
    ( }! ^8 a- d) B7 D$ Q0 K& C) g

  19. ) q( o0 |! ^7 Q8 X4 ?8 d" w/ }
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    ( c7 t" K! l  W- p  x

  21. 6 D! T) H! O8 z& m
  22.      s_command.Address           = ReadAddr;) E5 x& K" i8 l3 W& ^: {

  23. " R' g3 O' C8 P0 F4 R  @+ K
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    ' G# s; t4 {, u$ k% K

  25. + ~9 v' C. r: e* _
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;7 b5 j7 T) j, [) e

  27. % Z5 v1 J, \( [4 \: M7 e. k7 h
  28.      s_command.DummyCycles       = 10;
    8 X9 `" [+ B3 L# f/ j6 P; C
  29. : N) R8 `1 U% c0 P% n
  30.      s_command.NbData            = Size;! X5 N4 ?# J7 y% U- y5 u8 |

  31. " a) N7 r' V2 i. f3 b$ R2 U+ S6 s5 S
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    7 b9 _- _  p; ]# ?5 ?) u6 r

  33. 8 C& r- V4 z' f. L+ [4 E$ L9 T
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;, n6 W, O4 r& e2 n- q) _: n  l

  35. , m0 H3 _9 R8 Y" r0 Z* H. A
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    0 J6 d: m8 ^) [) O
  37.      /* 配置命令 */3 y6 }7 _% B' d2 V4 `9 t
  38. ( v$ v( A- L- ^: J# ~2 }
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    " N! C2 e# m/ c1 @( Z
  40.          {
    ! z! s: H; u3 s/ [( \, R9 |
  41.                 #if  QSPI_DEBUG
    . |4 W  H' V' `* ?' o
  42.                 printf("R-send read command error!\r\n");( {/ ], V$ s. Q7 J( w
  43.           #endif
    ! r- C* k+ f) B& p4 K: y
  44.     return HAL_ERROR;
    0 s- s9 G( C3 S: f
  45.    }  ; d0 ~8 n. p2 L- f; B
  46.    /* 接收数据 */, G$ W0 [* a% u' f
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK)
    ) [" X. Z+ Y* w( P8 _" J
  48.          {
      k: G$ X% r! B" y
  49.                 #if  QSPI_DEBUG$ Y  q6 g3 a, x0 p' x. K
  50.                 printf("R-read data error!\r\n");; I0 q' j" o" o5 u4 v: ~
  51.           #endif4 a' k, C8 _  O1 o; u0 x
  52.     return HAL_ERROR;3 V0 _! g) K6 d7 [. A7 L
  53.    }, Z3 @% q) @/ k
  54.    return HAL_OK;
    ( l! P$ b- e. e% m
  55. }
复制代码
) t* ?1 y8 C$ C8 x( }: Q9 |5 c
本函数实现了从FALSH指定地址读取指定数量字节的功能4 V+ @) d, i7 y/ x
; f% T; f# l: t, l# |/ g
7.写FALSH
0 s  N- Y4 z2 u$ o% ]% N; c- d/ C& u# e+ }( H0 N
同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:$ j' D, E& G+ P$ @6 N8 o: g! J+ G9 j$ d
) V; x  o! e5 b/ d
20191006135836784.png
, |! k" C5 I% m1 u2 o+ v; @

' |, ]& w7 {) P( i' M. [  J根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期
  p4 k4 D/ k6 H& D2 _1 M" c4 h+ @
7 X, Z! O. K- x( e) F/ J
  1. /**
    . p5 E2 B+ D4 y- N6 K; e7 `. h: F
  2. * @brief  将大量数据写入QSPI存储器
    $ h$ R2 X8 T7 z: I0 o# v
  3. * @param  pData: 指向要写入数据的指针. y# C0 G$ W* a
  4. * @param  WriteAddr: 写起始地址
    & G; T' [6 F1 I* g5 }5 l- R
  5. * @param  Size: 要写入的数据大小
    * H& D  X2 a& I$ u) d, l
  6. * @retval QSPI存储器状态7 O; M; N9 {# F8 A5 E4 L/ q
  7. */' X( ^. i1 b( W- W& w5 F7 r
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
    0 _/ Y) q  d: p0 `+ N) k& b+ Z
  9. {
    + e$ d; M  l5 b- k% r1 s8 G
  10.                         QSPI_CommandTypeDef s_command;; R6 j% ^9 C! i- }' Q+ N7 S
  11.                         uint32_t end_addr, current_size, current_addr;: W) E1 l, |, b! S6 g* p
  12.             unsigned char sector_start,sector_end,i;
    * l: A9 a6 c4 U* I$ U
  13.         
    $ u& i& _% b. Z6 z5 l
  14.                         /* 计算写入地址和页面末尾之间的大小 */
    ( l1 k; d, Z* ]0 S' [7 q! L
  15.                         current_addr = 0;
    0 H' O. A# |& i/ M3 c6 T7 _
  16.         
    % @# O* V% r1 m( c
  17.                         while (current_addr <= WriteAddr)
    ) |' G2 c! S% m# n5 N+ Z/ @
  18.                         {& ]' a5 d; @% ^/ H1 [8 \' i& O: d
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;; g0 ^- F" {, \: T% u
  20.                         }
    ; X: C. u3 t0 @! Y$ L+ k
  21.                   current_size = current_addr - WriteAddr;
    - _$ A8 H! v; ^) D9 ]+ y; \, Z
  22.      /* 检查数据的大小是否小于页面中的剩余位置 */
    7 m; h5 u: j* R/ Z" u* c) e
  23.      if (current_size > Size)
    1 h3 D+ {9 d  A: r( L! {
  24.                  {* E3 D+ m+ m+ l2 A
  25.          current_size = Size;
    , S- I/ Q' i* t
  26.      }6 ]1 J/ x9 w. G6 a
  27.      /* 初始化地址变量 */
    9 }/ [% W3 a4 \3 i6 e
  28.      current_addr = WriteAddr;1 Q) J& ]7 L4 Q! g9 R6 Q
  29.      end_addr = WriteAddr + Size;
    $ N5 [9 i9 Z; Q/ i& T, U! M
  30.                  
    , P2 y8 c# L6 E
  31.                  sector_start = current_addr >> 16;1 G4 R1 E; M( f! a+ J
  32.                  sector_end = end_addr >> 16;; u3 |# p8 x+ j/ c3 L
  33.                  
    8 G2 ~$ z, Q8 k
  34.                  for( i = sector_start; i<=sector_end ;i++)
    . s. S) |0 q( x# _3 V9 K# B- ?
  35.                  {( C9 W" {' L# {' x, T4 _
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    3 A2 V& S( \" x0 m8 X: d; t- d
  37.                  }
    ( g" @5 N8 D3 @. H. Z* z0 H
  38.                  
    & c- G% x4 f; Z) {, F
  39.      /* 初始化程序命令 */
    ( v  s2 G' x; V) ~2 O( m
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    2 b* M2 L! ~! Y( r# X' E

  41. 6 T8 f+ h  s. h9 \' \' {
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;2 _5 B3 _6 V" N* ?9 `$ w: q2 `
  43. ) `$ b' C" U! v) X/ R4 q
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;8 B5 p7 s/ J* |9 \: _2 Z3 _0 Z

  45. + Y' {% U, B/ d2 a# M
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    + x# m+ N$ G" [$ F1 ^  _
  47. & R1 E0 N: j0 M* s. H" {7 O' M
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;. `8 m" T! E: F! y' t( S( e3 N

  49. 6 ?: m- f0 A- G' [
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;; ~6 [/ @9 ^* K

  51. 7 A- Y& _) @' P7 ?% M, J' G0 T. c  V
  52.      s_command.DummyCycles       = 0;
    ' `8 @: i2 S; O1 P' A- ]
  53. $ x5 \& Y5 k; l9 I
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    : u( t1 ?# B8 Z0 S' k/ e
  55. ' @( |2 C  E' g& E
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
      m' u! s) y5 M
  57. 7 p8 H3 O# N: v* N* ]! v' G9 O* A
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    2 ?. j& @# T( G# [; b8 c7 V4 H9 j

  59. $ J! F3 n6 l& ^! y
  60.      /* 逐页执行写入 */! ]$ [% G  i; ~' P
  61.      do {
    5 \* v4 u! k7 B+ z3 R

  62. 2 L% B1 D0 P6 ]* k: s
  63.          s_command.Address = current_addr;: H7 e  M3 W1 t# Z: N9 S
  64.          s_command.NbData  = current_size;
    / a, }* r1 T* {% m
  65.          /* 启用写操作 */+ c2 A5 k& b/ ~, u

  66. , r" J# F7 w/ O- T
  67.          if (QSPI_WriteEnable() != HAL_OK)
    , W9 a1 g& ~$ A9 F- {
  68.                                  {
      ?6 ?( P: i1 @0 [/ h& d: D; V
  69.                                                 #if  QSPI_DEBUG
    * ~* {) H7 X+ m" }  O: V/ o
  70.                                                 printf("W-Qspi write enable error!\r\n");
    5 B$ q1 o  x* [7 H
  71.                                                 #endif. J6 r& z: \& \3 h  e/ H
  72.             return HAL_ERROR;- f8 o5 k3 C1 k% q* g  ?' H
  73.          }
    , e, u3 B2 {- O9 I9 H5 J
  74.          /* 配置命令 */
    * L) E. u" N5 l  x+ M, s+ U: j% [: p
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    & `% a3 o0 A" r) Y$ X
  76.                 {                                 
    % q. m) P" z+ d/ c2 `# A
  77.                                           #if  QSPI_DEBUG5 e4 J9 z8 n$ d; @( F
  78.                                                 printf("W-Qspi send Command error!\r\n");2 o; t5 _5 z2 z6 G$ e
  79.                                                 #endif5 O9 U6 J4 p) y& |
  80.             return HAL_ERROR;: S! s2 T: C; k( W/ `4 C
  81.          }
    / }1 [. ?) r3 n4 g2 s
  82.          /* 传输数据 */& k- o# t' w. r; c: Y
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    % R" t. F. y: y/ _7 y- U
  84.                                  {
    4 M# }4 S5 T; M( [: B% H7 D
  85.                                           #if  QSPI_DEBUG
    * V- d" ^% g$ g8 @7 R
  86.                                                 printf("W-Qspi Transmit data error!\r\n");$ H. r! y2 t; `- r& c4 C1 B
  87.                                                 #endif) `9 x  }$ |2 m$ t
  88.             return HAL_ERROR;
    3 }) F1 L! C& z4 D, b! c/ k  a
  89.          }
    ! E+ g  b  g5 Y! s
  90.          /* 配置自动轮询模式等待程序结束 */7 w1 J, c4 r9 c7 h  [# P, a4 Y( b
  91. & E1 w$ F+ a# G/ g* a- S! E7 W
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    . `* }+ p2 E) l0 o; \  a
  93.                                  {                                         # k# Z( L0 S' J6 q( y
  94.                                                 #if  QSPI_DEBUG
    ) ^, C( l# R$ M( q% \
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");
    ! I- ^, s  n# L8 G6 H
  96.                                                 #endif
    8 r" X+ x6 l# L. t* G
  97.             return HAL_ERROR;8 M1 @* I) A( b, K
  98.          }
    $ Y4 Y, e) T- D: F( f
  99.          /* 更新下一页编程的地址和大小变量 */0 Q5 _' @2 N' g- D. U

  100. 1 M/ Z; {2 o3 _7 I
  101.          current_addr += current_size;
    2 ~* n, n- X8 g: b" P

  102. 0 ^8 c. H. b0 T
  103.          pData += current_size;
    ( g8 f( E; k2 d
  104. & V) g0 o# b4 k! ]7 J# G- g5 o8 v
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :4 E  s% C3 v% P5 v, o& J  S

  106. ( l" @2 G) I) ^; O" S% j- Q' ]& D
  107.                         N25Q128FV_PAGE_SIZE;
    : Z- r" k1 P$ u& m1 F$ s
  108. # N3 l1 Z- L' _# P7 ~0 N/ q
  109.      } while (current_addr < end_addr);
    " N8 J& r# {1 ?. N  y
  110.      return HAL_OK;  T5 B3 f. {/ t: G1 Q% k; R- c
  111. }
复制代码
1 `0 Q" \, u) [$ \) V5 E
该函数实现从指定地址写入指定数量的字节。5 e8 M- n+ t4 \$ A3 P( [
  [+ ?/ x. l2 X: Y* O) {; A
8.擦除FALSH  G3 Q, x. E* G# o
  q0 |# w% l2 @5 G
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)- K; d2 Q- A5 ?4 `- {' _- o

/ Y  [/ s7 b0 ~2 z) x+ ]. S' X5 h
20191006140420479.png

+ R( z9 ^9 w  ?* X3 ?" \4 n' ]
# D9 h% @0 ?/ D: v4 Q参数设置为1线指令 ,1线地址,24bit地址,无数据阶段
/ j) [9 @& @5 o3 f0 i2 c
3 Q. j9 c0 ?* L$ `- E' A* u7 {
  1. /**: v; o+ P5 h8 B$ U8 f
  2. * @brief  擦除QSPI存储器的指定扇区
    2 Q4 e1 k3 U% ^6 j" r- v5 J
  3. * @param  BlockAddress: 需要擦除的扇区地址" z, \: p+ f7 c: a: Q. Z
  4. * @retval QSPI存储器状态
    3 A# b: c2 K9 r( G: v
  5. */: ]( H: M6 ]0 E( U, u/ {: ~
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector): S& I* n- I: J* s) Q) S
  7. {, }: F2 P. y( s/ U4 _, ^: Z
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址
    ; a- V7 X& I# I7 X" W
  9.         
    6 W% d2 }* v" s  b
  10.      QSPI_CommandTypeDef s_command;& q5 Y1 V: Y! o- ]' N" c
  11.      /* 初始化擦除命令 */8 Q' X- [9 x$ Q' g! ?1 P
  12.         
    . [& c9 Q+ c8 \/ Z. _" w
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    0 G9 q/ V& f; e; V$ T. a( H0 I
  14. 8 {% U# J1 j- F( I( A# h2 b
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除* \; N6 U7 I) C- G0 {: {

  16. ( G' m3 ?- W2 @9 Y# ]
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    % y1 F: |6 P+ }) i7 N
  18. # U; `+ i$ S& C9 f
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    6 S# D5 D) }. u# o2 F

  20. ) e9 u  `4 z' i4 c" |
  21.      s_command.Address           = SectorAddress;
    * ?# @& t  ?9 v5 B
  22. , R- {2 h. F0 p" M% ^; ]7 d. n
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    . n1 a4 A3 Y1 u$ W. M. ^8 Q) Q

  24. 4 U1 j8 X' N( z% `/ i
  25.      s_command.DataMode          = QSPI_DATA_NONE;
    # c3 Y1 |8 y4 q& A, C7 B
  26. 7 K$ X' L9 H$ a! R! ~, I( f1 ^
  27.      s_command.DummyCycles       = 0;
    & y& a" ^2 B4 g5 n2 [, A

  28. 4 }- D7 T$ W: y3 l/ n+ ?% D
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    + ~9 J0 _, b1 n
  30. 2 w, d8 o: P% f3 r7 N5 B4 b
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    " R- S9 W7 n+ w; c+ d" T9 z
  32. 1 W/ x9 Z3 \. R9 P/ Z/ ?0 M! [
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;0 k' z- ~1 J) r
  34. 3 |# x# `* l& Y. V/ L6 F
  35.      /* 启用写操作 */
    - @  w; [6 q% J0 m

  36. & C. o) L! M( }% I) ?+ n1 i( |
  37.      if (QSPI_WriteEnable() != HAL_OK)
    6 ^  G8 G$ |2 [. H! W+ V
  38.                  {; O. {! Y2 n( J, v
  39.          return HAL_ERROR;
    # a; s' C$ {; {0 k' C
  40.      }
    " m& |1 t/ z6 W! m& ?
  41.      /* 发送命令 */, T% ^% |4 I# q7 v7 K0 i
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
      w/ j- O& K6 T% \/ \" s
  43.      {
    2 t9 j, [. b% q3 H: e9 g
  44.          return HAL_ERROR;
    " \( C2 D+ r- M# L# K% I2 P3 N4 J
  45.      }  |4 |5 G6 T; L! @% V- e
  46.      /* 配置自动轮询模式等待擦除结束 */8 c4 R7 G* d* v$ d7 f8 H! }6 Y: ~

  47. # I4 p& ]6 v, J4 ~* x, a7 [  Y$ k/ f
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    " R* Z. w7 ~  ]6 v! [
  49.      {
    3 x7 M  E' H- Z* d' }: x
  50.                                 #if  QSPI_DEBUG) k, ?4 f+ {0 t) {8 |1 X
  51.                                 printf("erase sector error!\r\n");
    4 y$ M+ X% q" U
  52.                                 #endif6 W: r; b6 Z$ P! F
  53.         return HAL_ERROR;5 i5 F) _2 {/ m4 U1 W- y- \) @
  54.      }% o& E9 Y* \. [5 N% }
  55.     return HAL_OK;( P# ~  t3 X; w6 n& b, I: @8 T# g" g( e
  56. }
复制代码
. s: A7 q& i+ i1 R9 k# q! S
第二种情况是全片擦除,时序如下:2 D9 i, S; U2 x4 Q

* z1 y+ M, Z) a8 [2 M: d
20191006140919805.png

1 Y- {' ^( @' X. D  w6 C6 p& o) d( |9 n4 F( D
全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。
% @9 F- Z' ^; ]9 ^4 p, ?. t7 T" S2 n" F/ X/ K0 @% }
  1. /**
    ) ?- s: b: J5 ]6 l+ `( x
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s
    ) X! @# L) U  e$ l2 O6 a: I3 i4 e% E8 I
  3.   * @retval QSPI memory status
    3 l2 d( ?, f6 I
  4.   */8 U! d5 t$ I( }
  5. uint8_t BSP_QSPI_Erase_Chip(void)
    5 ~$ N% O' d3 T: W2 \- M
  6. {: I+ h6 {1 U4 t  g0 Z6 l6 m  H, N
  7.   QSPI_CommandTypeDef s_command;! B3 T- X; S5 y7 K
  8. 6 k5 {7 l, \+ M4 n% g
  9.   /* Initialize the erase command */
    7 ]4 N2 O  U: Y- o) S, s
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;7 k5 f% |' L, k% ^6 M8 b' @* F, y
  11.   s_command.Instruction       = CHIP_ERASE_CMD;
    ) a0 W6 Z, t' h# e* ?
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;
    ; U# A. z- f, i
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    & H) R6 D: C: l- S4 Y
  14.   s_command.DataMode          = QSPI_DATA_NONE;
    ' l" L9 `8 K8 X
  15.   s_command.DummyCycles       = 0;* O4 s+ Y  e4 t8 `3 S' S
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;5 \% Q. U1 ?" X/ }! [0 \
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    % |0 o' g7 d  s( E
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;
    1 R) Y# Z' S+ y6 A+ d7 \- j, M! e

  19. $ k/ Z( Z3 p3 n# C, }
  20.   /* Enable write operations */
    7 t: V1 [- q1 r  v' r$ b8 y& m
  21.   if (QSPI_WriteEnable() != HAL_OK)% ^: W! _7 I8 O' t
  22.   {  m! ]- X" }4 }7 m/ v1 P" _
  23.     return HAL_ERROR;
    + ]7 g# `0 n$ w9 O, M9 b) J
  24.   }
    . _' T8 E* g7 s) A3 I( T

  25. - Q. C/ g% Y, S# ?% \3 s
  26.   /* Send the command */$ H0 D) U' G( m. z2 e4 Q
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)' d9 m+ b1 G3 N  A0 `
  28.   {
    9 t5 w3 `; U$ f$ S
  29.     return HAL_ERROR;7 a: h2 U4 X" o. |! c
  30.   }: e7 V5 Q' W- a/ Z6 a
  31. ( i$ V4 x7 N' a8 @* b. |
  32.   /* Configure automatic polling mode to wait for end of erase */  3 z' T5 h( h4 U+ K; j, ]4 D8 h
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)
    & @0 ?) N7 Z, G* c) p5 n3 E2 \; w
  34.   {. o; q. T; ~5 p
  35.     return HAL_ERROR;
    + p6 y5 l& X, {
  36.   }
    3 H9 k& Z& W  c& _; y! F

  37. & ]% h$ a( S" s0 V$ l
  38.   return HAL_OK;
    5 u: s- J. H* |. a
  39. }
复制代码
, y' q. G& O/ a; u8 s: u
9.测试程序
! w4 `8 U9 z! j% a
7 k) Z) I8 X) ~( [0 J0 n" a测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。
3 B$ V; b) S5 ~& y( @8 {9 T- A
7 _7 ?: m5 S: r
  1. unsigned char write[4096];$ C8 l6 L1 ]/ @7 Q8 ^
  2. unsigned char read[4096];   1 B, W7 Q/ l* L. W) o
  3. void N25Qxx_Test(void)
    ' m5 J1 r8 Z% F0 i+ \
  4. {
    ' A9 r, a2 r0 w; {3 {  S
  5.   uint32_t i = 0;
    / k, f. e; v5 G& H0 B0 S& a9 f
  6. 4 C# @1 p/ V# H# m
  7.         for(i = 0;i<sizeof(write);i++)8 M& W2 p1 X8 Q5 I+ E" F7 F
  8.         {
    ' l' w1 r0 [5 a8 E9 x
  9.     write=i;3 ^9 s4 i3 Y# V2 r1 s
  10.         }
    9 Z" O1 Z9 W4 L
  11.         
    6 {! L5 S  z, M" ]# }
  12.         printf("write data:\r\n");        3 s4 t( `- S2 ]
  13.   for(i = 0;i<sizeof(write);i++)
    % b7 b  M$ e7 r/ ]/ Q
  14.         {
    : H3 ]7 I6 r. P# U5 k8 o: @
  15.                 printf("%d ",write);+ r+ ]+ _- ~6 G( `5 \
  16.         }4 {+ Y/ O. ]+ i1 }. ?
  17.   printf("\r\n");4 O. m1 ~9 `) D: Q+ P
  18.         printf("\r\n");
    6 n6 W% _8 V, p2 q6 D  V( l
  19.         printf("\r\n");, n7 C7 e' c1 k4 f
  20.         $ t5 l4 l- L# W1 [9 k
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));
    . m3 l# o- x' S
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        
    4 n% a/ K9 }. Y# x  w+ A0 c
  23.         
    ; n' h. l* B# [7 q
  24.         printf("Read from QSPI:\r\n");; T- T( P! `8 j) c0 M5 l1 M
  25.         
    9 ]/ M: ]6 b' F9 |+ v: E% C
  26.   for(i = 0;i<sizeof(read);i++). h4 K! K8 _+ G- w# W5 K/ I
  27.         {0 z8 f+ u; h- V9 t8 h5 B
  28.                 printf("%d ",read);
    0 l" _, f/ w7 U: l5 Y2 R
  29.         }( w  c* q1 \2 G; N2 r
  30.   printf("\r\n");, I3 e: Y; D  v) E1 v4 g
  31. }
复制代码

( E! v& V1 q2 ?) w写入的数据(部分截图):
  R9 m6 L# [! V/ B5 X$ s8 D! W) J6 u7 M% `+ e
20191006141636369.png

# |- {9 Y6 c( e7 H& h/ F, ^+ y0 X, h1 P5 |" `% N5 f" z
读取的数据(部分截图):6 l8 n% v5 M' L! G9 `
5 s) c! l$ {( |% ]) G3 h
20191006141537697.png
' P4 x. a0 T/ O; @7 v

5 O2 e; u8 v3 ]* k6 M& J$ _/ d截取相同的部分,两次数据完全一致,写入和读取成功。
$ D' y. @7 H" O: D) G* k0 h5 n( b; U7 U0 E+ O, {
最后,讲几点需要注意的地方:
0 ~$ ~, S9 D* o' B0 U' b) m+ ~* o6 I
1 M! F! z; N$ Q7 N# j! x! z1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。
6 M* b/ o: M8 V% F- Y9 q2 o. Q" D) n! }
2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。; R* e0 n2 X' _2 E% U% j
7 \; w7 m+ W; N
3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:0 J/ Y7 [" j# R/ I- J  t
 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断2 f/ r7 z- @8 E) z3 B! U" B
 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。
+ ]1 z  l' K, u, K. a
: J' e4 J$ s3 n) v& X2 J; f( }4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。
+ h4 n. u2 J! z1 K5 y) H, C+ z$ L. K& S& ~5 s, e2 w/ L& ~5 [
5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。
! f0 |) C2 a  m3 P) \5 }, L6 k! E1 K, b8 g; C3 T
20200316162249870.jpg

/ q( w* L; T+ M- u+ w; W4 {( C4 M( E1 N* _5 |
6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。
; Z3 {0 f" q" g4 a0 I
$ h, O. H/ k) B0 `, i& \7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!7 v) b5 @6 b0 J# a! V
! `6 C7 ]: W6 L$ I# z% H3 v; ?

% l1 L# u" U; Y' e% j
; u6 Q7 k2 n& r" V
收藏 评论0 发布时间:2021-12-16 21:00

举报

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