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

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

[复制链接]
STMCU小助手 发布时间:2021-12-16 21:00
一、QSPI接口介绍  O* I9 p. T+ v! d& z; x

/ s8 B' y* S2 A9 S9 Q, u+ K+ V. LQUADSPI 是一种专用的通信接口,连接单、双或四(条数据线)SPI Flash 存储介质。该接口可以在以下三种模式下工作:
8 x' ^1 U* @1 |* Q$ l$ C) ]/ R2 I
, F7 z/ ?; l5 ]- [8 |6 {9 X 间接模式:使用 QUADSPI 寄存器执行全部操作,支持对FALSH进行读、写、擦除、锁定等操作。
4 O! K. b- w, }: [8 m7 S0 t' @( f* T, u8 x0 U& x+ p
 状态轮询模式:周期性读取外部 Flash 状态寄存器,而且标志位置 1 时会产生中断(如擦除或烧写完成,会产生中断)
4 ^( p; E6 r1 B7 Q/ Q3 C" C8 R  P' o9 Q, o1 V
 内存映射模式:外部 Flash 映射到微控制器地址空间,从而系统将其视作内部存储器& G8 O0 o9 B, a& B/ p

2 ]1 s# s% z: B8 M采用双闪存模式时,将同时访问两个 Quad-SPI Flash,吞吐量和容量均可提高二倍。  L9 A) m" ~9 P, C2 P
, W, `( O5 m" G  Y% q7 p- C# V
内存映射模式支持XIP,即可以将代码放在外部FLASH运行,缺点就是有延迟,没有内部运行速度快,且外部FALSH只能读,不能写入或擦除。
9 w( T- E6 W, H. T( U+ Q
  d, q- s7 H& ?4 i5 K1 m内存映射模式下QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。
$ [8 s. i0 {% a7 s' s
; Z' n1 h3 F* x! ~: R7 e二、 STM32QSPI 功能框图
  n3 p' h6 e7 O; {) U2 Y8 G
/ a, h6 [% m. R8 }+ j" F" l
20191006091636349.png
9 H. W4 x0 S5 s$ f" e' }
% U( S6 a1 D/ k
STM32F7系列支持双QSPI接口,可以接两个外部FLASH,本文只介绍接一个FALSH的情况。QSPI使用6根线通信,其中数据线4根,1根时钟线,1根片选线,但是程序里是可以配置工作在几线模式。5 e) E2 A, y) ~  o: Q( l) B7 t

. O$ m* e$ R% x" P% Y1 ]三、QUADSPI  命令序列
' P8 t2 l$ O. l7 R5 @, a" C/ x% c! j+ l3 i; G) e; [+ J& D
QUADSPI 通过命令与 Flash 通信 每条命令包括指令、地址、交替字节、空指令和数据这五1 |1 ~# [5 `7 V9 U4 F6 ?2 Q
个阶段,任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。9 m% C5 ^8 u$ v2 `4 N- q4 v
nCS 在每条指令开始前下降,在每条指令完成后再次上升。
2 A% s- ]! J0 O# }! `4 y5 G4 A
20191006092242666.png
" Z; w/ ^" S2 C5 [8 G
4 S0 L! K) \  g# J
上图清晰的展示了一次QSPI通信过程,它完整的包含了通信的5个阶段,但不是每次通信都需要包含这5个阶段." F' t6 x9 J3 H" W) R# i. Z7 X

$ Y$ z6 I* W. c! [4 Y4 I+ m! T交替字节阶段,将 1-4 字节发送到 Flash,一般用于控制操作模式。|正常读写是不需要的,所以这个阶段可以省略(结构体中该参数置0);
  R  {5 T1 i( ]* V7 y( t. s' x& P$ u7 L9 J3 c5 j6 {
空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高! P& z. Y1 U+ t# U: }; _
的时钟频率时,给 Flash 留出准备数据阶段的时间。
/ _) W- |+ E+ t7 P& J; a/ V  |若 DummyCycles为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。: e  S$ x8 A9 r3 O) I

) y4 H9 ^1 O. h5 R! D- `此外,并不是每次通信都需要包含以上各个阶段,例如,打开写使能指令,只有指令阶段,其他阶段的参数都置0.再比如,读芯片ID,只有指令和数据阶段,具体包含哪些阶段需要参照芯片手册配置。/ Z$ L# U) n$ P% t2 P8 B6 P. E

: U3 }7 @" |+ G8 l四、QUADSPI  信号接口协议模式
# f/ h6 |7 A" U* p; Y" G1 T; T( h2 s% y
下面提到的几线模式都是指数据线的根数
* X5 }; D! D( t
0 m8 N- K2 U* w  l单线 SPI  模式(即普通SPI通信,等同于使用MISO、MOSI、CLK、CS方式通信):
# o, q! }2 E$ `- {$ z" n5 x$ ^) u' f传统 SPI 模式允许串行发送/接收单独的 1 位。在此模式下,数据通过 SO 信号(其 I/O 与
& m. f3 [. L8 D. g, T+ N2 qIO0 共享)发送到 Flash。从 Flash 接收到的数据通过 SI(其 I/O 与 IO1 共享)送达。
, J$ o! k. h1 F  B/ f' |通过将(QUADSPI_CCR 中的)IMODE/ADMODE/ABMODE/DMODE 字段设置为 01,可/ K4 _- f9 U3 L4 K) v
对不同的命令阶段分别进行配置,以使用此单个位模式。7 z$ l' o& R& I7 W
在每个已配置为单线模式的阶段中:, T7 p/ C7 o- [5 ?  X
 IO0 (SO) 处于输出模式
1 w& j5 E9 Z  P9 n0 W- N IO1 (SI) 处于输入模式(高阻抗); c/ }+ w) m/ P  F  {9 m1 x2 e' H
 IO2 处于输出模式并强制置“0”(以禁止“写保护”功能)
! n7 _( _3 r8 s1 m+ u0 Q8 f2 J IO3 处于输出模式并强制置“1”(以禁止“保持”功能)
# [0 D! S3 w0 k, E9 I' i3 N2 [; U" N' }% ~2 X
双线 SPI  模式:
! Z4 e( k5 L) o1 B6 k在双线模式下,通过 IO0/IO1 信号同时发送/接收两位。0 g3 ?$ H9 L" J/ b8 ]0 f
通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 10,可! p. ~; `) |% u6 W
对不同的命令阶段分别进行配置,以使用双线 SPI 模式。
% }2 {& ~: Z$ P在每个已配置为双线模式的阶段中:
( N) U" v7 N. O0 Q% u1 V: r. f IO0/IO1 在数据阶段进行读取操作时处于高阻态(输入),在其他情况下为输出
: T3 ~$ O! \- Q IO2 处于输出模式并强制置“0”6 `) ^5 L: E+ Z+ j0 C+ L- e
 IO3 处于输出模式并强制置“1”( y1 t# ~9 p2 D4 g8 q
在空指令阶段,若 DMODE = 01,则 IO0/IO1 始终保持高阻态。& v+ p4 m) C2 l' l

9 u% i( ?$ w% M: k+ o
' L' J4 Q) g# U* h四线 SPI  模式:
+ R8 w1 v& a8 X5 t: v6 L$ L- I3 S在四线模式下,通过 IO0/IO1/IO2/IO3 信号同时发送/接收四位。
2 N6 v/ ^) T$ C4 T7 [6 D9 u& k通过将 QUADSPI_CCR 寄存器的 IMODE/ADMODE/ABMODE/DMODE 字段设置为 11,可  V3 h& C6 R1 \4 _2 ~
对不同的命令阶段分别进行配置,以使用四线 SPI 模式。
. Q/ S# ^3 h: |' k) S* O8 S5 G在每个已配置为四线模式的阶段中,IO0/IO1/IO2/IO3 在数据阶段进行读取操作时均处于高! h, Z: ?. R; |# T
阻态(输入),在其他情况下为输出。
4 I# h" S2 k/ K) U3 W) T: P% D. d/ R* Y) b在空指令阶段中,若 DMODE = 11,则 IO0/IO1/IO2/IO3 均为高阻态。
0 s! \9 t; Y! \" |7 aIO2 和 IO3 仅用于 Quad SPI 模式 如果未配置任何阶段使用四线 SPI 模式,即使 QUADSPI& t1 _5 |0 `: X( Q8 Y
激活,对应 IO2 和 IO3 的引脚也可用于其他功能。
% R9 s& E5 w! l( q2 C4 y+ X5 K7 k3 x1 x: t1 E, _: t
7 p2 R; Y# r  g; [/ E2 F+ d# Q" C
SDR  模式(默认工作模式)
0 o& s* z( N5 T# }/ |: Q: P& C默认情况下,QUADSPI 在单倍数据速率 (SDR) 模式下工作。
; N; v- k  ?5 K6 W( H, _/ D/ _在 SDR 模式下,当 QUADSPI 驱动 IO0/SO、IO1、IO2、IO3 信号时,这些信号仅在 CLK
) H; K7 q4 p0 [$ j的下降沿发生转变。
6 o: T( M5 R* }1 o( u在 SDR 模式下接收数据时,QUADSPI 假定 Flash 也通过 CLK 的下降沿发送数据。默认情
5 y! i0 C* Q# {$ _况下 (SSHIFT = 0 时),将使用 CLK 后续的边沿(上升沿)对信号进行采样。% C6 w4 Z/ ?2 i+ F0 R/ \

1 F- _' h/ |6 \% `) e4 W& q' \8 |
; O. j% [) L- H% n( DDDR  模式
6 ?0 D/ C, i' r% G在 DDR 模式下,当 QUADSPI 在地址/交替字节/数据阶段驱动 IO0/SO、IO1、IO2、IO3 信, N8 y* V5 Q$ o& P3 X, C' o
号时,将在 CLK 的每个上升沿和下降沿发送 1 位。
3 s# |& E! K/ E指令阶段不受 DDRM 的影响。始终通过 CLK 的下降沿发送指令。2 j4 [- P1 r) W4 o
在 DDR 模式下接收数据时,QUADSPI 假定 Flash 通过 CLK 的上升沿和下降沿均发送数. Z5 M( R; ^  r9 ~
据。若 DDRM = 1,固件必须清零 SSHIFT 位 (QUADSPI_CR[4])。因此,在半个 CLK 周期
$ Q- j5 P' Y' M8 f4 k0 T后(下一个反向边沿)对信号采样。
8 D2 F' b. G- p& X+ T: l3 E2 C
8 e! F! l7 i8 ^" t' @3 w五、QSPI 配置(HAL库)
. D0 L9 J5 ^2 ^3 v( E3 E5 ~$ ?0 l5 e
1.首先根据硬件电路,配置相关引脚,开启引脚和QSPI时钟2 F% [1 N  D* L4 L$ a

' n: X' K" P$ s! I" V8 N' u/ u# e1 H
20191006121236849.png
: Y  E) `: z9 w. ~3 o

! ?; ?. T& x( w) A. ?5 H' \' I! F
  1. void HAL_QSPI_MspInit(QSPI_HandleTypeDef* qspiHandle)( w$ ~2 b+ S3 _1 A  K
  2. {' J1 ^0 U5 [. p. X( o: {- t5 L
  3. 7 [1 a0 m, m$ F' e
  4.   GPIO_InitTypeDef GPIO_InitStruct = {0};
    ! j' P, q7 n! [2 m$ p+ D) j
  5.   if(qspiHandle->Instance==QUADSPI)
    ) o! o! b# ?6 B( ?+ n* _
  6.   {; f& h- g' ?" P0 p- A
  7.   /* USER CODE BEGIN QUADSPI_MspInit 0 */
    / U4 q6 o9 X3 I4 s4 t

  8. ! m) V5 ?% ^; t6 ^
  9.   /* USER CODE END QUADSPI_MspInit 0 */8 f4 y, R% y) @. H$ U! `0 P
  10.     /* QUADSPI clock enable */
    # D7 ?5 B. q5 _. d
  11.     __HAL_RCC_QSPI_CLK_ENABLE();, f, u3 n2 D3 J8 M8 }8 n4 U

  12.   X% h' Q& k3 T) ?0 Q5 c0 E
  13.     __HAL_RCC_GPIOE_CLK_ENABLE();
    . `: z6 p( q( r0 t. h+ A0 W6 x6 ?
  14.     __HAL_RCC_GPIOB_CLK_ENABLE();3 {& D/ \, J7 |% ?, U
  15.     __HAL_RCC_GPIOD_CLK_ENABLE();) z3 i6 h+ K3 y
  16.     /**QUADSPI GPIO Configuration    0 {3 u5 f& r- S1 ~$ d
  17.     PE2     ------> QUADSPI_BK1_IO2
    " u9 |  }. ?8 U
  18.     PB6     ------> QUADSPI_BK1_NCS; P7 p6 n0 K5 _) N& M+ @6 \
  19.     PB2     ------> QUADSPI_CLK- _/ u; E- z# h6 S# C+ ]0 g3 r' y
  20.     PD12     ------> QUADSPI_BK1_IO1, h7 l& R# ]5 Q+ l6 a& g
  21.     PD13     ------> QUADSPI_BK1_IO3
    " y9 w4 t' c  X8 ~
  22.     PD11     ------> QUADSPI_BK1_IO0 8 R, j- }9 r$ p0 h" n1 w( t) i, J
  23.     */. X0 A% E0 \, |3 x# s
  24.     GPIO_InitStruct.Pin = GPIO_PIN_2;2 N; F! k2 c5 E0 H3 ?4 q! Q
  25.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;" S$ q# I" T8 Q' I& C* K. V: p
  26.     GPIO_InitStruct.Pull = GPIO_NOPULL;
    , K( e" {, ^* E( x$ Q* a
  27.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    0 H3 A& g" w5 `# S7 x, U
  28.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;" \/ C& e9 O: p& G3 C; T
  29.     HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);  x% m# H+ R& @8 A# N& K. X5 u
  30. 2 w- ^4 ~' n/ U% l: b3 [. a
  31.     GPIO_InitStruct.Pin = GPIO_PIN_6;( }; r' `' J/ c5 @7 L1 I
  32.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;) p! I) `) s/ \
  33.     GPIO_InitStruct.Pull = GPIO_NOPULL;2 r! j# f  e* E
  34.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;4 r! \& m5 C8 ^& r
  35.     GPIO_InitStruct.Alternate = GPIO_AF10_QUADSPI;
    ) ~3 t6 ~% g0 R6 D6 D4 ]) O$ C
  36.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);6 M7 ~/ }6 F- Z+ z0 s; M

  37. ( L0 R1 [* |5 t' a* L. \) d8 t
  38.     GPIO_InitStruct.Pin = GPIO_PIN_2;
    - l! K9 l: m1 i" k( \3 K
  39.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    : ^9 L- L. Q( y
  40.     GPIO_InitStruct.Pull = GPIO_NOPULL;. _7 b. I, ?/ X0 {0 d. W
  41.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    9 {1 x9 }; @2 i1 Z8 o0 v
  42.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;
    4 |/ x( z% C# w" F' i9 D: [) y
  43.     HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
    ; e$ R+ n8 z' j( C* k) u2 C' S* V

  44. ; B- g% h: S8 ~2 C5 U
  45.     GPIO_InitStruct.Pin = GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_11;. v2 I; g) [+ B# Y8 u
  46.     GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;) j/ `$ g$ P( T/ L
  47.     GPIO_InitStruct.Pull = GPIO_NOPULL;5 t% T! S4 M0 G# Q  {
  48.     GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;, x- y8 l& P# a+ L% {
  49.     GPIO_InitStruct.Alternate = GPIO_AF9_QUADSPI;3 l; r& R' P4 ]
  50.     HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
    8 o( ?" ?  G4 A# E) ~- c

  51. / f( ?4 L! C* M
  52.   /* USER CODE BEGIN QUADSPI_MspInit 1 */9 h9 p6 k4 Y7 I9 \4 \' s

  53. . E3 ?1 Q# R. W( a
  54.   /* USER CODE END QUADSPI_MspInit 1 */& I% s' B! Y, |$ [; U% x& z! h
  55.   }
    - L* Z8 R6 G) s$ q8 w# P- z
  56. }
复制代码

: }% x' U2 G: m2.接着初始化SPI外设
* \! O! |: p5 K; a

  1. + N/ A4 \$ E" D" d
  2. QSPI_HandleTypeDef hqspi;
    6 N4 O1 _$ e( q+ |  f! `

  3. , {# X* m# w: R, Q/ @) w$ Y; H0 M
  4. /* QUADSPI init function */0 ]2 }1 r% L  `$ P- u. U# N+ q: T
  5. void MX_QUADSPI_Init(void)
    # k# Z$ `! Q" W8 h3 A- n
  6. {
    6 s5 ], w$ J9 J3 [' {
  7.   hqspi.Instance = QUADSPI;//QSPI外设' e3 A7 M3 Z* F" a9 }, L
  8.   hqspi.Init.ClockPrescaler = 2;//QPSI分频比,N25Q128最大频率为104M,此处216/(2+1)=72M
    2 @* V* F' D* v0 I) W; t8 t4 l
  9.   hqspi.Init.FifoThreshold = 4;//FIFO阈值为4个字节
    9 r5 m5 K* X. V, K, ~
  10.   hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;//采样移位半个周期(DDR模式下,必须设置为0)
    * ^' y/ d( i1 Z) z) F" j& q
  11.   hqspi.Init.FlashSize = POSITION_VAL(0X1000000)-1;//SPI FLASH大小,N25Q128大小为16M字节
    4 n: g" |) ?7 p* G
  12.   hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//片选高电平时间为6个时钟(1/72*6=55.2ns),即手册里面的tSHSL参数
    ; ^, N* P: {" t5 n- O% L. F
  13.   hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;//时钟模式0
    ; o( ~" j# J: w  m8 F0 ?
  14.   hqspi.Init.FlashID = QSPI_FLASH_ID_1;//第一片flash
    5 a& W/ n1 g! Z1 q6 c
  15.   hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;//禁止双FLASH模式
    ) o+ H$ L+ c) b/ J2 B# h
  16.   if (HAL_QSPI_Init(&hqspi) != HAL_OK)//初始化QSPI外设
    . }: s: I$ J4 G' q/ x# u) q
  17.   {9 \$ m2 b) h; v/ Z
  18.     Error_Handler();1 S8 g3 G% o; r8 l& N
  19.   }
    + V6 u8 R6 p$ J' V) T1 x
  20. }
复制代码
+ C1 |3 {; R- B/ H- ?9 A1 q: d
3.由于使用的是间接模式,我们对FLASH的所有操作都需要通过FLASH内部寄存器来完成,也就是通过向FALSH发送对应的指令来获得我们想要的结果,此时的FALSH就是一个普通的SPI协议外设,所以接下来所有对FALSH所有的操作都需要先发送相关指令,我们需要配置指令参数(指令类型、地址、数据宽度、地址宽度等等)。; v: p. e/ y7 z( N0 @
2 e  y( z, _9 x9 Z
我们首先需要配置下面的命令参数结构体:" V' ?6 s, }0 N6 ~3 |7 O. _' z
" {& B$ \# \. Y/ Z% ?  O7 M
20191006122437823.png

+ I' X8 K$ B1 T- \* s* y: e4 O1 p  o6 {8 h" J/ O
Instruction:指令类型(读、写、擦除等等)
/ g" j3 n; R- D4 b
6 S! i# t  H( GAddress:需要读取或写入数据的起始地址* g, G! U+ c" ?5 `7 c6 k
# h- E* Q( ^# g0 s  r6 C; I
NbData:需要读取或写入数据的长度
, I) `6 {5 }* z0 b
+ h- ?5 N: S/ T2 ODataMode:数据传输模式,就是数据用几根线传输(1、2、4线可选),后面的AddreMode类似,就是用几根线传输地址,InstructionMode就是用几根线传输指令。. w* A: u( C& }, Y! y4 S+ n3 p% }0 V
% g' y; B3 R2 e8 F
这三个参数都需要根据芯片数据手册来选择,手册上说明支持该模式才可以选择,否则会通信错误,另外,数据传输模式的不同,对应的指令类型也会不同,比如N25Q128 READ指令对应单线传输模式,Quad I/O Fast Read则对应四线传输模式,这些对应关系手册上都有详细说明!!!
5 d0 E- c0 ~( p+ }- j9 V; O: f
" `1 \1 X6 j4 z 4.读取芯片JEDEC ID
% y7 Y  K# E- k3 x
# K; @. }. v/ a首先查看手册上读取JEDEC ID的时序:% Q, X7 |9 ^! d1 t, ^; F
# ]4 w+ [* p( G, z2 B) t3 v: U5 ^
20191006131644114.png

1 w& R! a- k% A! S0 ]  k" K/ D( s3 F7 e: [$ L6 F. Z
可以看出,读ID这条指令是只有指令阶段和数据阶段,且都是单线传输模式,所以 3 Z0 m* @5 Q2 ]- k* u' H
1 t0 f) g9 Z3 L7 V$ J
InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令单线传输模式! [0 M# o1 m' O' t* n

" w5 u" n0 s6 n1 p- DDataMode = QSPI_DATA_1_LINE;//数据单线传输模式
4 ?' o! B8 J/ z6 Y! F% M% v' U8 z) M; Y1 N! ]
Address和AddressMode 这两个参数的值都置0,表示无地址。$ L* V. T; z( j$ A# M+ v$ w2 w

$ L( y( e4 a1 CNbData :是想要读取的数据长度+1,ID是3个字节,这里令NbData=3; e  r" q) S! o% |
3 z* w7 J8 N$ G& v
其他值和下面代码保持一致即可。 6 i$ g% r, h; E- o
6 G9 ^- @5 @3 d. O2 P
然后 使用HAL_QSPI_Command函数将读ID指令发送给FALSH,等待发送完成1 b. `) i: F8 m# a# y

) _& h$ I( V: a+ D* o最后 调用HAL_QSPI_Receive,接受FALSH返回的数据,即芯片ID, y( b* F  k0 u+ Y4 M0 L% Y/ a. t7 o
4 B4 O8 P) X5 {
  1. /**
    . I  A1 q8 p4 b& H/ A6 x4 N# j1 d
  2. * @brief  读取FLASH ID0 N& c6 q( L; Z! K; |  ]
  3. * @param   无$ T/ z/ T. M( h8 J
  4. * @retval FLASH ID
    0 _) S4 y. @- k% y) S  f* C
  5. */
    * I, ?6 P% O5 v/ ?
  6. uint32_t QSPI_FLASH_ReadID(void)! c3 H' f! x! [
  7. {. M9 ~, |! B- h$ H
  8.      QSPI_CommandTypeDef s_command;: h+ p1 V: A: Q* e+ n

  9. - n, Q- N1 L/ b" `! S
  10.      uint32_t Temp = 0;
    , S. f" O/ _7 e7 a% \9 P: s# t4 i

  11. $ k7 M$ j0 r: Z8 v% I+ ?
  12.      uint8_t pData[3];
    , K* z3 g% @6 O0 S
  13. 6 o6 X) C/ C, @% C0 y  K
  14.      /* 读取JEDEC ID */* C: w3 d# t/ ?3 w0 Z

  15. 9 k  y' a) l9 U# y1 e
  16.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,00:无指令;01:单线传输指令;10:双线传输指令;11:四线传输指令。
    0 n6 F" x! P8 i$ g& ~0 W

  17. + D* i: c: G6 m2 [
  18.      s_command.Instruction       = READ_JEDEC_ID_CMD;//读取芯片ID指令
    1 T* u1 c! x) S  X& Q
  19. 0 U8 H* j3 W6 X. x7 n! K1 Q2 n
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;//定义地址长度
    ! U8 R5 z) W, w3 U

  21. % o+ `9 C. @! e; L
  22.      s_command.DataMode          = QSPI_DATA_1_LINE;//定义数据阶段的操作模式,00:无数据;01:单线传输数据;10:双线传输数据;11:四线传输数据。
    5 z% [% m8 O% f5 F% O8 \
  23. ; z% Q# g. E& _' A  J5 z# K; J
  24.      s_command.AddressMode       = 0;//地址阶段的操作模式,00:无地址;01:单线传输地址;10:双线传输地址;11:四线传输地址。  r2 f1 H& o5 H: L" B7 }
  25.           5 M! ^8 f) M* o0 b
  26.      s_command.Address           = 0;   
      w5 Z  Q" `9 a- `& @  n! D& G
  27.         
    5 U6 ~3 e, n! M: {+ u/ t
  28.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节长度# v6 g1 |; o6 a- p& l% E9 C
  29. ! q) I; v0 P( s3 S3 G  @0 ^2 S# u
  30.      s_command.DummyCycles       = 0;//空指令阶段的持续时间,在 SDR 和 DDR 模式下,它指定 CLK 周期数 (0-31)。
    ; v$ p* O; r! ^& S2 z% j6 b% S

  31. " o2 ?' o5 r+ I+ D; R5 O
  32.      s_command.NbData            = 3;//设置数据长度,在间接模式和状态轮询模式下待检索的数据数量(值 + 1)。对状态轮询模式应使用不大于 3 的值(表示 4 字节)。
    6 q) j3 T  u; s# |( }5 `2 ^( d0 M( g) \

  33. - z  L, q: q* F& U7 l7 J# a2 x
  34.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;//地址、交替字节和数据阶段设置 DDR 模式,0:禁止 DDR 模式;1:使能 DDR 模式。8 }8 w3 W6 m" W) V7 w2 e
  35. 1 m5 `1 d( {7 X
  36.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;//设置DDR 模式下数据输出延迟 1/4 个 QUADSPI 输出时钟周期,0:使用模拟延迟来延迟数据输出;1:数据输出延迟 1/4 个 QUADSPI 输出时钟周期。
    0 v& w3 m6 T. S: m& G7 C; u
  37.                  
    4 U( u. y) O: x& m# e/ w6 |, ^
  38.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;//设置仅发送指令一次模式,。IMODE = 00 时,该位不起作用。0:在每个事务中发送指令;1:仅为第一条命令发送指令。6 A; y+ Y* g! U% T- [) [8 M
  39. ; Z9 c1 F9 }! [4 C! e. d/ A8 A
  40.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    % B( }7 q/ x# B* b2 c$ S
  41.                  {7 w( o3 T+ h7 R
  42.          #if  QSPI_DEBUG1 O# p( B; I# X& e# {* `
  43.                      printf("send read flash ID command error!\r\n");9 F% i2 f  P8 m) ~/ D
  44.                #endif
    0 t. y9 ]( @! W; B* I0 X% s
  45.          /* 用户可以在这里添加一些代码来处理这个错误 */
    % v% m$ @2 Y9 S8 h
  46.          while (1) {}; T- y6 T# ^+ @7 z9 c$ }+ b
  47.      }
    6 C" ?- L* z2 b/ w
  48.      if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK) + K: `0 P) f* Z+ w( F) p( K9 L! G
  49.                  {+ y: I9 G( r8 P+ k2 K
  50.          #if  QSPI_DEBUG; Q' k: e: D3 I2 X
  51.                      printf("read qspi flash ID error!\r\n");
    " s8 Y, Z: J, J  B& V9 n& I0 F
  52.                #endif
    " \8 [9 v4 L, u* H( i
  53.          /* 用户可以在这里添加一些代码来处理这个错误 */; R  R" p5 E8 i2 k6 ~' L* u
  54.          while (1) {}
    5 d1 k! K& c9 L* B+ W. y* B
  55.      }; l+ k- b$ v. Z  \0 y% L
  56.      Temp = ( pData[2] | pData[1]<<8 )| ( pData[0]<<16 );: I  J$ V  S. N- k7 ]
  57.      return Temp;- z' ]( S3 I# d
  58. }
复制代码

. _9 j7 X2 F" E% q 5.FALSH 写使能6 i* S- f7 f0 K/ a9 _

+ p2 _1 u2 t- E0 f5 B/ }在对FALSH进行写之前,需要打开写使能开关(实际上就是发送写使能指令):, ^6 {) E1 M+ M

+ @  s: ]/ F" x. A" [类似的,需要查看芯片手册,写使能的时序& V* O0 c% w/ W7 L7 J
2 f5 B2 _% r5 F5 C
20191006134057767.png
5 I% p8 G( V' ~2 |+ u! m' ^6 B
2 }; z7 r: \. i4 B
可以看到这个指令时序更简单,仅仅有指令阶段,所以地址和数据的参数都应置0
5 o5 c5 a0 M- S# L) i$ e$ F; ]3 ]6 I: f' s
  1. /**, W" v* G: T3 W! [$ l
  2.   * @brief  QSPI FALSH写使能6 `" i, d& o. _& E+ P9 ]
  3.   * @param  None
    + M" r, f0 i5 S% ~' x
  4.   * @retval HAL_OK0 }0 _2 T# \4 l4 I3 F
  5. */$ e/ n3 M: u6 x: Y8 P# _
  6. static HAL_StatusTypeDef QSPI_WriteEnable(void)
    / ?1 b1 M% v) C% X8 J1 k3 o
  7. {6 T! Q* [6 G) J+ C  V. v4 Y
  8.   QSPI_CommandTypeDef     sCommand;
    / f' |; Y. R4 g. @0 ^5 F
  9.   QSPI_AutoPollingTypeDef sConfig;
    ' G' [* u& s3 M& N$ b

  10. , {8 n: T1 U! _
  11.   /* Enable write operations ------------------------------------------ */
    9 j$ X1 }4 x. G, I. B$ H' o2 w
  12.   sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;/ q/ c- [, s2 b8 Q0 M$ T; p
  13.   sCommand.Instruction       = WRITE_ENABLE_CMD;
    ' r& L" J) R* e7 i
  14.   sCommand.AddressMode       = QSPI_ADDRESS_NONE;
    7 N" b& |) x; c) ^: f! P
  15.   sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;' A3 S0 [% x, V$ n; R$ {
  16.   sCommand.DataMode          = QSPI_DATA_NONE;
    & c. A: l; q" V! S) T
  17.   sCommand.DummyCycles       = 0;
    / t+ f( t8 ?9 ?1 {
  18.   sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;# H) ~7 U/ j; X$ H# H
  19.   sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;5 B! _/ [8 q* \7 t8 m8 _
  20.   sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;- ^8 s" L$ c& \- q# C8 P/ M

  21. - d. k/ ?  k) S' h  \! K
  22.   if (HAL_QSPI_Command(&hqspi, &sCommand, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)0 p+ y( R' }7 D; `
  23.   {5 H: [$ Z8 [) s) @+ B6 d
  24.     return HAL_ERROR;
    ! Y+ o( T& ?  u; P' o
  25.   }
    - U5 o( r; w4 M0 z

  26. 9 n  z4 [# o2 S
  27.   /* Configure automatic polling mode to wait for write enabling ---- */  " ?4 L; f' F% Y, l
  28.   sConfig.Match           = 0x02;$ U$ v% f+ E6 d: W+ O5 v7 l, l
  29.   sConfig.Mask            = 0x02;
    * |* ^3 i0 \! g
  30.   sConfig.MatchMode       = QSPI_MATCH_MODE_AND;
    * Y; O  @9 c6 A9 k$ W
  31.   sConfig.StatusBytesSize = 1;
    9 J* f! b0 y1 P" [0 b( M1 k5 F
  32.   sConfig.Interval        = 0x10;' n7 ?* C9 B7 n( s
  33.   sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;- X1 h( w1 ^9 Y! [
  34. : w' i# g3 |4 L1 h1 A0 U. d9 e
  35.   sCommand.Instruction    = READ_STATUS_REG1_CMD;
    8 b0 n! M$ {7 b
  36.   sCommand.DataMode       = QSPI_DATA_1_LINE;
    $ C0 b( F1 {$ j( q' Y  o

  37. / ^6 F0 b% O% y8 t0 I# B
  38.   if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)2 ]* _; L0 z" e
  39.   {4 _2 l8 n' J8 `8 q: @9 `
  40.                 return HAL_ERROR;        7 Y( w7 ~4 |* P5 y
  41.   }
    ! @: B- s$ Z9 D0 `
  42.         return HAL_OK;* f/ S3 F0 n1 f" ^/ @
  43. }
复制代码
7 b( [4 t5 ~& u8 J1 l4 O; B
这里需要讲下 QUADSPI  状态标志轮询模式:
+ E# ?- D. V, C在自动轮询模式下,QUADSPI 周期性启动命令以读取一定数量的状态字节(最多 4 个)。0 U1 K: d, L8 v; n. f
可屏蔽接收的字节以隔离一些状态位,从而在所选的位具有定义的值时可产生中断。; @0 D1 ~1 n" j1 \
对 Flash 的访问最初与在间接读取模式下相同:如果不需要地址 (AMODE = 00),则在写入
: b" h: a7 r' M& x: {9 RQUADSPI_CCR 时即开始访问。否则,如果需要地址,则在写入 QUADSPI_AR 时开始第
$ {1 p9 _: n0 }5 i8 ?一次访问。BUSY 在此时变为高电平,即使在周期性访问期间也保持不变。' W6 i' j" D' T. c: f4 ?% J
在自动轮询模式下,MASK[31:0] (QUADSPI_PSMAR) 的内容用于屏蔽来自 Flash 的数据。
8 }# m, }/ O+ R2 l' I8 f7 a如果 MASK[n] = 0,则屏蔽结果的位 n,从而不考虑该位。如果 MASK[n] = 1 并且位 [n] 的内7 g+ z- Y1 r% S" a
容与 MATCH[n] (QUADSPI_PSMAR) 相同,说明存在位 n 匹配。. Q% j4 G0 @4 _' y, k5 q
如果轮询匹配模式位 (PMM, QUADSPI_CR[23]) 为 0,将激活“AND”匹配模式。这意味着
8 |* J% P& G' N状态匹配标志 (SMF) 仅在全部未屏蔽位均存在匹配时置 1。  V7 N4 @4 Z/ `7 ~& {8 z: O8 \
如果 PMM = 1,则激活“OR”匹配模式。这意味着 SMF 在任意未屏蔽位存在匹配时置 1。
' M8 h5 _/ j7 f; g) O0 U如果 SMIE = 1,则在 SMF 置 1 时调用一个中断。- ]5 x' h. f7 F5 F7 Z" S
如果自动轮询模式停止 (APMS) 位置 1,则操作停止并且 BUSY 位在检测到匹配时清零。否, j: J$ U9 Y' |  V# g
则,BUSY 位保持为“1”,在发生中止或禁止 QUADSPI (EN = 0) 前继续进行周期性
" i9 ?3 p, f& n8 ~' j访问。3 \0 N2 n+ x6 q5 t
数据寄存器 (QUADSPI_DR) 包含最新接收的状态字节(FIFO 停用)。数据寄存器的内容不7 z8 o  o! o. D% ], K4 v
受匹配逻辑所用屏蔽方法的影响。FTF 状态位在新一次状态读取完成后置 1,并且 FTF 在数$ ]$ _$ x; y2 n  O' `7 h8 ~5 M
据读取后清零。" d/ ^5 P. h' K8 \5 Z

' D9 A& r, o1 u8 k" ~+ R简单的说,就是CPU自动查询FALSH指定的标志位是否置位或清零,通过调用HAL_QSPI_AutoPolling函数,CPU将自动查询FALSH写使能标志位是否被置位,成功返回HAL_OK,否则返回HAL_ERROR,这样就省去了我们自己发读寄存器指令查询的麻烦了。
( q5 l- z: Q" n8 o% O+ `) Z* Q9 |0 K1 e4 ~- S# Y: `8 n- J
6.读FALSH
& [- Q4 D0 `5 ~- q/ ?; X% M; C0 t8 u* c& _, S4 w3 Y( l
这里仅介绍4线模式下的读FALSH流程,首先查看手册4线模式读时序
8 K4 K6 |4 H# ^/ i9 ?/ D2 b/ @" m; g: H! w8 k6 M$ @
20191006135244693.png

/ h6 N5 S, I& H- ]: c" |. x4 k& v+ `  o
从时序图可以看出,指令是1线模式,地址是4线模式,空周期(DummyCycles)为10,数据也是4线模式,还有此时的读指令是QUAD_INOUT_FAST_READ_CMD。所以相关参数配置如下:
5 x0 `5 E1 @* G. O: O) s
2 ~: V5 v7 S6 H& ~4 i1 G% P9 R7 @% T4 p
  1. /**2 [: [0 u( b) b- i/ \
  2. * @brief  从QSPI存储器中读取大量数据.
    8 `2 q, a- ^& c4 b9 w
  3. * @param  pData: 指向要读取的数据的指针) a6 {( p) k$ [3 g8 F2 _( T1 c
  4. * @param  ReadAddr: 读取起始地址
    # w  s  H' g7 h% k9 @: O( p
  5. * @param  Size: 要读取的数据大小# B6 z2 P! c$ z0 L! \: |* o
  6. * @retval QSPI存储器状态
    , m+ l- w/ v4 X: l5 ]% A
  7. */
    + M5 S& e+ X0 v7 |  [' _
  8. HAL_StatusTypeDef BSP_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size)
    7 n+ B* K3 N7 X- S* m4 V" w
  9. {9 R$ x  Y5 J3 b/ p/ }6 Q; h% U9 k' t
  10.      QSPI_CommandTypeDef s_command;
    + e6 w2 F6 p; v( a7 k
  11. , J- S8 L$ B/ w* S- `' {8 W, J3 w
  12.      /* 初始化读命令 */$ B4 K- a8 `( I9 M( g+ l
  13. ( K6 L- G5 x4 Y9 P' a4 a. ^/ _
  14.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;4 f: ]2 P( Z5 i& ?( I
  15. # u) o. C0 H$ `( O
  16.      s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;* k$ c9 ~  u  D: h& h
  17. 9 L. c& c( {) P' |& P4 _
  18.      s_command.AddressMode       = QSPI_ADDRESS_4_LINES;3 y: f5 u: p# \' Z: X

  19. 8 u6 s2 h8 D0 O
  20.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
    ! v: T# g2 L) P9 r. m$ ^6 h
  21. 6 a+ _! E- H' ~) b0 |
  22.      s_command.Address           = ReadAddr;5 e: G. e- |' x8 l* P( d: y
  23. 3 T# `( D. v$ r/ B# V, T# r# T0 {
  24.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;$ I; V) O, `0 B) K$ W
  25.   O4 |5 l; H+ @/ N" s" j4 c  Y
  26.      s_command.DataMode          = QSPI_DATA_4_LINES;5 I5 s) |3 M$ Y; h6 X0 S

  27. + k$ ?6 v2 `3 @/ B& ~) _
  28.      s_command.DummyCycles       = 10;4 F$ r* E! q* S( u
  29. , H. I# H5 F- K+ S1 X
  30.      s_command.NbData            = Size;3 D2 ~# G( c, @1 K1 A# u
  31. $ L* v6 F7 o4 |$ C
  32.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;  J% P2 v" Y4 q; ^2 ?

  33. * V9 @, S7 s; t5 o
  34.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;4 i- g! G2 w# \! N

  35.   D( N  J' K9 k! [
  36.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;- N7 X2 m, \) A8 E$ u9 x, q
  37.      /* 配置命令 */1 @2 l* }, k! [" W
  38. ; R$ `' M2 D8 ]! Y: h
  39.    if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)& f9 V; r: N" I0 e9 B- l
  40.          {: g5 q' ?, o; j3 o- `7 u
  41.                 #if  QSPI_DEBUG
      z# F4 B, i7 d1 S
  42.                 printf("R-send read command error!\r\n");& I: o, t( V# Z  q2 Y
  43.           #endif
    ; U; I3 e1 Z9 H3 X
  44.     return HAL_ERROR;4 I$ Z  h3 r9 B( T
  45.    }  
    / {7 E5 g. Y+ z* J/ q
  46.    /* 接收数据 */' s$ K- i$ R2 A4 r
  47.    if(HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK)   ~5 I# ]8 ?8 Q, I4 S
  48.          {
    ' x. y8 u# w  h: n8 t5 R
  49.                 #if  QSPI_DEBUG( p5 g0 A& g% I% I8 i3 x
  50.                 printf("R-read data error!\r\n");% e& V0 y( Z+ E# j! Q( N
  51.           #endif( ]7 Q4 r; @- B# e( m) d
  52.     return HAL_ERROR;7 q! ?7 E0 \! J. E4 j
  53.    }
    3 I, y3 M# w" D9 f
  54.    return HAL_OK;8 J! |, G% w6 @3 q1 g8 t
  55. }
复制代码

& i; T2 v" ~) T  B6 E本函数实现了从FALSH指定地址读取指定数量字节的功能
6 `/ S5 m; n) Q
" k1 Y  K8 g& p% G. [+ S 7.写FALSH0 M+ T& K! T2 Q% t+ ]$ H
8 C2 d+ o8 c8 X/ @+ d
同读FALSH类似,我们需要在手册中找到4线写FALSH的时序:
6 F) K8 i5 J$ J% N/ B
5 p: `& b. L" g/ g' l  `, l: I
20191006135836784.png

/ I; t% {/ u) ^, w0 L  r" j
) O2 G7 [2 E5 H+ {# @) d根据时序图配置如下:1线指令,1线地址,24bit地址,4线数据,没有空周期
1 l( E, ]" @/ ]9 R6 m# _7 P% _0 \* P  z
" k5 L6 V) I6 X& g2 v2 T
  1. /**
    : h" F# g: P/ d% v0 u' y! I+ \0 ^" o
  2. * @brief  将大量数据写入QSPI存储器
    ) s' G! ?$ g. j+ M  b6 ]
  3. * @param  pData: 指向要写入数据的指针2 j  S; Q# {3 x: u6 d2 v1 K
  4. * @param  WriteAddr: 写起始地址7 o, R3 O' U8 @& |% W" M
  5. * @param  Size: 要写入的数据大小( }: {% _, G* w) ]; ^. r3 w
  6. * @retval QSPI存储器状态
    2 @& B. F% s/ o: }' `
  7. */9 k. z# D2 T' L+ Q3 U" p
  8. HAL_StatusTypeDef BSP_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size)
    # B; I2 C  t3 l9 B) ]
  9. {9 v3 @' P' L* @; X
  10.                         QSPI_CommandTypeDef s_command;* z$ T% O3 P+ v! ?8 e
  11.                         uint32_t end_addr, current_size, current_addr;6 ]4 N+ `8 p2 r; @  p0 q- c
  12.             unsigned char sector_start,sector_end,i;: d& F% Y4 J) H) z& g2 X( c, g; @
  13.         # Q* x% U' n! W' H  o
  14.                         /* 计算写入地址和页面末尾之间的大小 */4 O3 V( o6 S& ]
  15.                         current_addr = 0;
    ) _2 b" Y: G7 e  y
  16.         
    5 S: b1 L* T$ b: P# Y0 h
  17.                         while (current_addr <= WriteAddr)
    ' y4 T* s. }, [( F7 v1 L
  18.                         {9 B1 z' A* N( C6 _( \/ v
  19.                                         current_addr += N25Q128FV_PAGE_SIZE;
    2 o  F5 I) c3 y3 R6 p
  20.                         }
      E; h3 l& e" k' D0 {5 q' |
  21.                   current_size = current_addr - WriteAddr;: _4 n/ ]1 z6 K2 ~- |- I$ T( p4 z
  22.      /* 检查数据的大小是否小于页面中的剩余位置 *// E3 L. R6 I5 T5 @5 e
  23.      if (current_size > Size) / V$ K/ @% b+ b
  24.                  {
    $ q. b% E! n$ Y. A/ g
  25.          current_size = Size;
    ! p, Y0 Z+ u  L
  26.      }: ?3 E  h4 P  r/ N6 W( K8 J
  27.      /* 初始化地址变量 */, B5 M: h6 k; r
  28.      current_addr = WriteAddr;
    . L  _  o( w. i. R/ y; L% L  L& |
  29.      end_addr = WriteAddr + Size;6 }* V5 T; \* q4 q6 b, d
  30.                  ) ~+ V  C  I8 i+ K# H9 ?# x
  31.                  sector_start = current_addr >> 16;1 `4 H9 K6 |$ A1 o
  32.                  sector_end = end_addr >> 16;$ i1 L; c, l  @
  33.                  : l' K, T0 M6 U0 R! ?9 s
  34.                  for( i = sector_start; i<=sector_end ;i++)4 }) [  Z# ^- y  C; `
  35.                  {
    ( f6 k+ n& E& c" h
  36.                     BSP_QSPI_Erase_Block(i); //擦除对应删除
    3 I$ m/ |4 ?1 [- b. K' W" ?" a
  37.                  }
    ! g7 C+ G+ h" P* a- T, w- |
  38.                  * e& t3 I0 ?6 ?
  39.      /* 初始化程序命令 *// O# K: U# |9 ~+ c6 Z$ b3 f
  40.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;% k% |* c1 M& h% q" I" _

  41. 9 u$ x! X2 {( y8 n
  42.      s_command.Instruction       = QUAD_INPUT_PAGE_PROG_CMD;6 ?  b5 K# x! G5 x) F2 ~

  43. $ I$ W. u: |+ `# I0 c7 A# q8 }! ^% E3 x
  44.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;! S/ N! E. G( x. h) B
  45. ( [' u$ }; J, a5 \. E2 _
  46.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;; r0 `- O: ~! n/ y& z. J- ?/ d

  47. & [* \2 `# z: @. k& i  F
  48.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    8 D& Q% y$ V: y

  49. 7 [( \7 f( N; @+ K' R0 \" i
  50.      s_command.DataMode          = QSPI_DATA_4_LINES;
      I$ e/ ]2 B. Z" t$ b

  51. 1 H) [: `$ E1 T+ J# L
  52.      s_command.DummyCycles       = 0;9 O8 h) B/ N% f6 x1 ~' ?2 N
  53. * y( P2 O, E6 Z+ d
  54.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    / V( I+ V1 n. S: Y9 t

  55. : y/ R- l" ]% z. z8 ~
  56.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;2 `. Y: n9 N: J" A; y( a; Q) f

  57. 4 a( z' c3 y% j* g
  58.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;. x+ ^; M( `+ v
  59. 7 z/ W$ t( W) d( ]# R
  60.      /* 逐页执行写入 */
    + b! `) g) |% X! K# H. W
  61.      do {$ a  X9 O7 N3 m2 b
  62.   |  I1 M+ H  Z% [5 X# \7 j) D
  63.          s_command.Address = current_addr;. p, {, r; ^; }0 F
  64.          s_command.NbData  = current_size;7 A* O, }( D, n9 ^, r* K
  65.          /* 启用写操作 */
    1 D5 v5 r! P/ b! U* z, ]7 P

  66. * O( c7 Z) C2 K' y
  67.          if (QSPI_WriteEnable() != HAL_OK) / L& |0 m- d3 L2 ~! M# X5 `. B
  68.                                  {
    % S, k8 \5 y3 v- J
  69.                                                 #if  QSPI_DEBUG' ^  f( e) b+ T2 O0 M. I# a$ S
  70.                                                 printf("W-Qspi write enable error!\r\n");
    1 P  h3 Z3 |6 I( c% A. q+ R
  71.                                                 #endif) {- r0 Y0 d: U/ t6 D
  72.             return HAL_ERROR;
    8 J  P. Y2 }( {: M' A, Z9 T
  73.          }  m, Z( }+ }1 g1 s/ |4 p
  74.          /* 配置命令 */
    ( f! r& G4 U. Y
  75.         if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    3 H; {) l0 u4 f. I( [, W" W
  76.                 {                                 ( w4 C, \2 b- m$ h
  77.                                           #if  QSPI_DEBUG6 S  \# s: ^: L: a! p6 U
  78.                                                 printf("W-Qspi send Command error!\r\n");
    * F, t- A! }) K* H( a) G. D7 [
  79.                                                 #endif
    5 H$ e( N- b8 ~& z
  80.             return HAL_ERROR;- Y. E5 L- N5 d* F5 }! }; N, g
  81.          }
    3 E5 O' s; F3 t
  82.          /* 传输数据 */) q- X3 |4 ?* r- a8 i
  83.          if(HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) # I; m( G5 k) F0 C
  84.                                  {
    5 O( J) F; a) ^- {
  85.                                           #if  QSPI_DEBUG
    # u) |6 q( M: S! _
  86.                                                 printf("W-Qspi Transmit data error!\r\n");
    % K3 k3 X7 Y2 t2 i' Z0 s
  87.                                                 #endif
    / d. l6 W; j6 ~* Y2 o9 y
  88.             return HAL_ERROR;
    8 N  O; f7 H/ b# B( r7 C8 o
  89.          }# \& E# J5 X( ~1 X/ s
  90.          /* 配置自动轮询模式等待程序结束 */
    ; ^+ b5 W' ]- h0 ~; }
  91. / ?1 K5 ]0 h; i. R' s
  92.          if(QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)" w4 S  t- |+ z; J- W* |2 A8 T
  93.                                  {                                         " x7 y- H+ ~- {, C% k7 a+ N
  94.                                                 #if  QSPI_DEBUG
    ( D- w2 _: }" T0 d. \6 v7 N
  95.                                                 printf("W-AutoPollingMemReady error!\r\n");5 C" S7 U- `; `0 K) i
  96.                                                 #endif
    ; e6 Q% ?5 B. x+ M% Q
  97.             return HAL_ERROR;
    # G, b( }* K8 x4 k) w+ l* [
  98.          }
    5 \  ^! I( r8 p& B3 y, L# f0 O
  99.          /* 更新下一页编程的地址和大小变量 */( H: R0 ~" y9 H
  100. ! Z$ |, _) n# ~: D# C# i. {
  101.          current_addr += current_size;
    + }# H0 c7 g% g5 q8 o" o

  102. 6 m2 h2 f, M7 t. n" x6 k# w
  103.          pData += current_size;
    2 ^0 r6 Q2 O; G: A

  104.   h! M5 V+ `6 w( }$ o/ I4 l' z
  105.          current_size = ((current_addr + N25Q128FV_PAGE_SIZE) > end_addr) ? (end_addr-current_addr) :
    5 {& i% c, N2 \

  106. % {( D' Z* R1 ^" J9 f
  107.                         N25Q128FV_PAGE_SIZE;
    8 T, r+ o  x" T5 O/ v' N+ t

  108. : z, Q) y9 S% u
  109.      } while (current_addr < end_addr);
    5 S1 I6 u& r( K: _5 c$ F. [
  110.      return HAL_OK;  Z1 T, c/ @' @2 g+ R! P
  111. }
复制代码

% r7 d* l4 f0 R* a7 R" n该函数实现从指定地址写入指定数量的字节。3 T( R$ Q$ \+ ~9 K( l( Z' }1 _, }

  P% p" L$ i- ]8.擦除FALSH$ Q1 i1 ?4 o; a  P, s
# {8 i6 Y* E5 y2 L2 t
第一种情况是按扇区擦除,这也是N25Q128最小的擦除单位(N25Q128共有256个扇区,每个扇区64KB)0 |- Y2 `$ S" F" z
) |- S8 I$ ?# N9 ?3 ]
20191006140420479.png

) r# i  A; t  G5 G# l6 g
; B. X9 L' v# o4 S  e0 n参数设置为1线指令 ,1线地址,24bit地址,无数据阶段
  O* Z; U1 W# _( K- j( e
7 f* e0 l3 j) e& R
  1. /**
    0 ~, l! z6 C; _; x+ @
  2. * @brief  擦除QSPI存储器的指定扇区5 i. |6 r7 C) k& Z: U& b9 G( L7 y9 ~
  3. * @param  BlockAddress: 需要擦除的扇区地址  h; R# o* E  w, f
  4. * @retval QSPI存储器状态  s0 n3 p' }9 P. f7 j1 u
  5. */9 [1 q/ M* W9 @3 m2 @
  6. HAL_StatusTypeDef BSP_QSPI_Erase_Sector(uint32_t Sector)
    & |+ Z3 j+ C' S6 q1 z
  7. {
    $ q- A' k- ~& v5 H
  8.            uint32_t SectorAddress=Sector << 16;//Sector*65535算出该扇区对应的地址% {* a3 ^/ J4 i4 w. b/ x* S  J
  9.         
    / j3 F$ t0 p" M" P; I
  10.      QSPI_CommandTypeDef s_command;
    7 F8 A7 x% Y, k# H; f
  11.      /* 初始化擦除命令 */1 |, b- Q$ Z; ?- B& o
  12.         
    . n0 C$ ?3 T4 D" i+ ^$ r1 _
  13.      s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    4 l7 w* l) ^- {( A$ F
  14. 8 I! u6 B  m) g2 l% Y
  15.      s_command.Instruction       = SECTOR_ERASE_CMD;//按扇区擦除
    9 t; ^( m  r0 y9 Y. a
  16. 2 S6 d, M9 F2 \9 @& v4 k
  17.      s_command.AddressMode       = QSPI_ADDRESS_1_LINE;
    # l8 [0 m/ ^* L& _- {

  18. ; q: ~' [1 P: K* m
  19.      s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
      w4 @) y) n9 t" c4 M" [
  20. 3 w0 e; D! W! L6 S3 z
  21.      s_command.Address           = SectorAddress;% `7 G+ v, j. X3 N$ [( p

  22. 7 h/ R9 P1 q/ Z8 ?! |$ d
  23.      s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    * r. p# |5 {0 _; K% s% M% F9 w+ x
  24. 5 V8 }& A( P' G6 C& f- P/ h6 X
  25.      s_command.DataMode          = QSPI_DATA_NONE;
    0 b2 q/ z( P% d! P% u# K
  26. 8 y2 F, |0 S4 V) |# k7 y
  27.      s_command.DummyCycles       = 0;% D* ?, s' {; y: l5 Z( y! l
  28. ) }3 K( [6 A$ h* x+ B' _! r6 @$ m5 I
  29.      s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
    $ o6 u! x8 O% D" d9 a4 k4 @
  30. ! v0 n1 b5 M! c. }
  31.      s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    7 S- D  c% f. R6 H
  32. # {% N7 w, {0 O( v1 w! h# ?
  33.      s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;# V  p* b  n1 Q$ C' d
  34. " s  ]- i( N* [- t2 Q
  35.      /* 启用写操作 */9 M8 m7 f3 n. h/ F% O

  36. 7 u. L: r) D9 Q. R# h
  37.      if (QSPI_WriteEnable() != HAL_OK)
    4 O! q, L/ {) a! R0 J) z% G
  38.                  {
    - E% ]0 h8 g* J$ \: Z* ^; K- u
  39.          return HAL_ERROR;
    , ~8 e! y; s7 I! R' t% x4 b& b
  40.      }
    ' M5 H( U8 Q8 _! Z1 Y1 H2 t# o5 [
  41.      /* 发送命令 */
    # \) l- t4 j3 U* ]5 E+ F
  42.      if(HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) 4 U/ R8 d+ }: @4 M* y) d
  43.      {
    . _: Q3 V" k" ]
  44.          return HAL_ERROR;' S; O( V! r. f) f# }  O6 Z
  45.      }
    * g# l. D5 ^# b2 p/ T- E1 U
  46.      /* 配置自动轮询模式等待擦除结束 */: P- u+ l/ _- U" q3 y- P( k

  47. 1 j) C. V$ b- H' k; f( Z/ m: Z
  48.      if (QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    * ]9 O! A7 u& O. {+ z
  49.      {
    ) Z' V. o. _$ J( q  K% l. Q: ?
  50.                                 #if  QSPI_DEBUG
    - n; |) [/ R5 n3 e  B* x
  51.                                 printf("erase sector error!\r\n");- d% N% a! X! \6 |5 J( K* v5 b* k  U
  52.                                 #endif
    2 N& ?& m5 y( s* @5 Q. F  F
  53.         return HAL_ERROR;( ]: I( z/ i; M9 B) B5 v7 g
  54.      }3 J# i+ T  Z1 U8 \$ @; e
  55.     return HAL_OK;* N3 v( |" U1 \! ?/ l- c
  56. }
复制代码
2 d& |: Z5 ^# \* Z7 H/ h
第二种情况是全片擦除,时序如下:
& X9 N2 m) o& i" S$ J. g& y) g& M$ R
20191006140919805.png
8 o0 m6 b. D$ S& G, M+ d5 A
  h4 t& M9 |( D( \: D
全片擦除仅包含指令阶段,对于N25Q128来说,全片擦除耗时可达48s。
0 f( b# c2 T- x/ J2 c5 m. [8 \5 Z  `1 b9 |: t# p, ?% i5 ?  V
  1. /**
    1 R2 R/ \- ^2 Q6 O
  2.   * @brief  全片擦除  N25Q128全片擦除耗时48s* ]5 k6 z/ M7 l7 y9 s
  3.   * @retval QSPI memory status
    % G% @" t( s  P& {
  4.   */& b- T) d: G' T
  5. uint8_t BSP_QSPI_Erase_Chip(void)
    % ^$ ^9 @: N/ Z4 U' a
  6. {* k7 `! ~2 u/ I) }5 J  N
  7.   QSPI_CommandTypeDef s_command;/ H+ S, |& K. v

  8. 2 K2 h+ J8 z$ G! F' R
  9.   /* Initialize the erase command */
    - _/ v; c. _7 K# N
  10.   s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    ! B! I3 G$ @( z
  11.   s_command.Instruction       = CHIP_ERASE_CMD;
    & I+ c' {9 Y' ?6 y
  12.   s_command.AddressMode       = QSPI_ADDRESS_NONE;
    + M6 S" i3 S% m9 ]/ ~) S
  13.   s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    0 H8 ^& a. }4 b8 x) z2 d
  14.   s_command.DataMode          = QSPI_DATA_NONE;2 |# B5 n2 u" t# _/ w
  15.   s_command.DummyCycles       = 0;
    4 z8 m  J8 T3 T. ^- N
  16.   s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;# J" p$ @  z! y5 O# A
  17.   s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;/ d1 H7 J! p! w, N4 l) q
  18.   s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;8 u: Z, u; Q* I2 j/ N4 d
  19. 1 H/ N" f+ Q" V. w- `+ C
  20.   /* Enable write operations */
    5 o" `( T& O4 q9 @9 b
  21.   if (QSPI_WriteEnable() != HAL_OK)" o* B" E: W6 W3 _. @7 h: l. h
  22.   {
    $ j( O: ~. M+ }! ?9 h
  23.     return HAL_ERROR;% u# o2 v1 Y$ [# R
  24.   }
      F/ S0 X; h# [/ w

  25. / L5 D) u, M9 s6 c3 S7 c: F
  26.   /* Send the command */3 J1 Q) V) m- Q& R7 \4 ~
  27.   if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
    * b7 {8 o& c1 O4 {" K+ @3 S' E
  28.   {, j9 G0 B' F. P9 i( C1 z+ E
  29.     return HAL_ERROR;9 O# a) F! z! W$ c9 r' \: G
  30.   }1 S* S# e5 t$ F* a

  31. : V' B+ p- P* H
  32.   /* Configure automatic polling mode to wait for end of erase */  
    ; C+ e+ Z3 S+ I" H# ?' `7 O8 P
  33.   if (QSPI_AutoPollingMemReady(N25Q128A_BULK_ERASE_MAX_TIME) != HAL_OK)7 J# ?/ U: x9 b) X9 N
  34.   {
    / r7 x/ x" K4 s% [- ~
  35.     return HAL_ERROR;2 |% i% s: M; j2 G9 t4 n
  36.   }# q( M6 R% e& F' @! @* U' ~
  37. 3 y* C3 U( i1 m& i6 Q6 }4 ^
  38.   return HAL_OK;
    9 o( u( h$ n0 Z8 m
  39. }
复制代码
: J; A- f: e3 i* M8 l5 P
9.测试程序
4 H, g' [% d6 |  T; q* g1 o7 e, r- f7 V
测试芯片写入和读取是否成功,首先向0x1FFFF地址连续写入4096个字节,然后再读取出来,对比两次数据是否一致。
6 S! _6 ?* q" [& B- H4 c5 G: ?; H/ f5 \/ Q! C3 r1 Z: v" h) u% b
  1. unsigned char write[4096];
    9 E. `/ I7 K- W$ f$ j. A" Z6 C
  2. unsigned char read[4096];   $ ]' b, |4 u4 r
  3. void N25Qxx_Test(void)
    & j/ r, a/ C) z) h
  4. {
    ; B# a& G3 u9 L+ v, x! Z3 m: R
  5.   uint32_t i = 0;6 y9 V/ W* K+ g+ l& |/ [

  6. : Y/ H0 j4 I$ v. u8 ]  x" [' j2 P
  7.         for(i = 0;i<sizeof(write);i++)
    0 q* n( H, d; n/ s3 w& m( U
  8.         {
    % X. V7 b) }/ z% ?5 d  [
  9.     write=i;1 B) S3 m1 v- ?$ c" e$ H3 S
  10.         }
    " R" h. ^% v2 `% P  r0 t- y
  11.         
    8 S1 D3 r, w* d+ n( I1 _3 ]
  12.         printf("write data:\r\n");        
    6 F! a: ~! X; @9 k
  13.   for(i = 0;i<sizeof(write);i++)
    6 M. }1 a$ T: r
  14.         {" A) o9 j1 d+ s9 V5 Y, X: v
  15.                 printf("%d ",write);5 F! l2 b% U0 n) P
  16.         }
    , P) l1 [. m. D# {( D  H( u
  17.   printf("\r\n");& i- r3 v5 f/ |" `" B
  18.         printf("\r\n");
    / W: U1 m9 h/ }' x7 o2 i; Z3 C# Z
  19.         printf("\r\n");9 w" K& z: u! B* @& d. w
  20.         
    + m0 @2 g6 ]* u7 M9 o+ A
  21.         BSP_QSPI_Write(write,0x1FFFF,sizeof(write));: R- z+ h% V7 k; y, _# o
  22.   BSP_QSPI_Read(read,0x1FFFF,sizeof(write));        $ j( w( V8 {/ v; Z
  23.         
    6 \& @! I" T% {$ h1 n! x+ r) |
  24.         printf("Read from QSPI:\r\n");  [) a: a0 M: |% N! q* R# u
  25.         / u# p. p$ T" ]8 a7 L& @
  26.   for(i = 0;i<sizeof(read);i++)0 ?4 _, p+ f9 Q7 G
  27.         {
    ; I# Y) [! K' E3 z+ a
  28.                 printf("%d ",read);4 g2 i. n. X# B8 S& W( k
  29.         }
    + \8 E0 l4 w- h& s* e
  30.   printf("\r\n");7 _: B) w  F3 o+ u1 i2 C
  31. }
复制代码

. u# U! _  w: e1 s3 ~' n写入的数据(部分截图):
2 l$ }3 P% R8 |! R$ e7 {9 @( Z0 P5 K' P' V* S$ g
20191006141636369.png
' i3 e: O1 t% N. q- d" m

( N6 O" A$ P* q7 z读取的数据(部分截图):
2 C  O- L0 p% f5 O3 |0 g, K; d8 C! x6 U, ]: c: r, g
20191006141537697.png
" D9 d& P. u2 D

& l1 S" o5 M; V, d' p. H截取相同的部分,两次数据完全一致,写入和读取成功。+ u% ^$ A  ?+ J" W! |1 p
& F6 U9 y% ?! ~
最后,讲几点需要注意的地方:
* J8 u7 T* {6 ?( t( T  p2 D
( R6 B  Q% H  Q1.N25Q128 与  W25Q128 有很多不同的地方,特别是4线模式下,二者很多地方不同,代码不可混用,比如W25Q128的很多指令都是支持4线的,但是N25Q128的指令阶段均不支持4线模式。  Y% l  h2 O/ ~% Z/ F% \& ^, D0 ?) V. ~
1 t" x& t* U# x$ b: S3 D; ]
2.硬件上使用了4线SPI,但是仍然可以使用单线SPI操作,只是速度慢了点,其他的没什么影响。
' I1 n. b" I" C; S3 }1 V8 ^% \' V
( Y+ v% Y6 d  k- r3 y0 E5 t3.N25Q128在配置为内存映射模式时,外部 SPI 器件被视为是内部存储器。QUADSPI 外设若没有正确配置并使能,禁止访问 QUADSPI Flash 的存储区域。即使 Flash 容量更大,寻址空间也无法超过 256MB。如果访问的地址超出 FSIZE 定义的范围但仍在 256MB 范围内,则生成 AHB 错误。此错误的影响具体取决于尝试进行访问的 AHB 主设备:& J( C3 s- j$ P$ X( r. R
 如果为 Cortex ® CPU,则生成硬性故障 (Hard fault) 中断
9 l' {1 y3 Y" o) m: o7 M8 f) c' S 如果为 DMA,则生成 DMA 传输错误,并自动禁用相应的 DMA 通道。
( ^$ k% y& z& u, W) E  R8 F  w9 o. f1 r9 L9 D; m& c+ |
4.N25Q128包含248个主扇区,每个扇区64KB,还有8个64KB的启动扇区,这8个启动扇区又可分为128个子扇区,每个子扇区大小为4KB,所有的扇区擦除都可以按照一次64KB大小擦除,但是这8个启动扇区也可以按照4KB一次擦除(需要使用SUBSECTOR_ERASE_CMD指令擦除)。2 l; `0 G- ], m& q5 w/ b  G

! O1 l  n. K; `- g' v5.N25Q128分TOP和Bottom架构,两种架构的区别就是启动扇区的位置不同,TOP架构的启动扇区是248~255(地址范围:F80000 ~FFFFFF),0~248为主扇区。Bottom架构的启动扇区是0~7(地址范围:0~7FFFF),8~255是主扇区。这两中架构可以通过完整的型号区分。: ?6 h; G% x9 A

$ z/ a, R; U' l; K
20200316162249870.jpg

( z  N2 f: Z- ?% Z$ c: \4 _* Z- W/ W5 D& }! z) _" P+ o. Q
6.N25Q128和W25Q128 不可以相互替换,除非使用单线SPI通信,且只能使用F80000 ~FFFFFF或者0~7FFFF范围内的内存。3 a- f3 V) d- P4 _6 V$ |, E7 n
- \  b3 D9 q( z9 ?
7.N25Q128的指令在不同spi模式(单线、双线、四线)下有所不同,使用时一定要特别注意!!!
% X% {" {% B. L1 ~  @) [( G+ s, N! v$ G# P4 f

/ S: I0 F* W# v7 L7 m# G% B
. P3 q; E; J+ j5 C7 B
收藏 评论0 发布时间:2021-12-16 21:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版