前言+ R- e e3 k% N2 B; A
STM32 提供了灵活多样的外扩存储器访问实现。本文中,介绍如何利用 QSPI (QuadSPI) 外扩串行NOR Flash 存储器。首先对 QSPI 接口功能特性进行介绍,然后分别介绍硬件设计和软件开发。并基于 STM32CubeMX,提供访问 MICRON N25Q128A13EF840F 的实现参考。
- a/ P G% x0 t* W4 ^& b' `) J/ D* t$ ~5 S. R7 p
一 实现环境
5 t! f+ a- J. ^! u# g2 G+ q开发板:STM32F469G-DISCO
' I. o! _4 e7 F3 R0 `) G开发库:STM32CubeF4 v1.16.02 V8 e) p0 o1 o
STM32CubeMX: v4.22.0; o1 T& d! F9 ^, E/ r, B
集成开发环境:IAR v7.70.1.11486' Q: K9 S' [& W. j) Q
: }: Y7 ~1 ~/ \4 i% A4 G2 O2 c" _3 n实现过程在 STM32F469I-DISCO 板上展开,利用板上已有的串行 NOR Flash 存储器(MICRONN25Q128A13EF840F)。呈现整个开发涉及环节。在本文中,首先根据 QSPI 接口,介绍 QSPI 与外扩串行存储器硬件连接。另外,Cube 软件包中包含 QSPI 实现例,在本文对库中实现的 QSPI 例不做讨论,读者也可参考这些 QSPI 例进行设计。本文围绕由 STM32CubeMX 生成的工程,介绍如何实现对外扩串行 NOR Flash 存储器的访问。6 b i5 M7 Z9 Q# n5 H7 y
/ |- S" S* {. C& Q6 Y
0 J1 a9 a, M, d/ Q8 R: K
二 QSPI 介绍
/ J+ ^8 D2 u: i" a' ]在呈现 QSPI 访问外扩 Flash 的实现例前, 需要对 QSPI 有一定的了解,在此对 QSPI 进行简短的介绍。更多内容请参考AN4760。
. H: H* h1 p' m6 t
& [% D% V5 k+ y' _& q- f2 G6 o) ?5 C5 B. x; k: d0 k& G
QSPI(Quad-SPI)支持四线串行访问形式。同时,QSPI 支持传统 SPI 和 Dual-SPI 模式,Dual-SPI 模式支持两线串行访问。与 FMC/FSMC 比较,QSPI 支持更低成本、更小封装外部串行 Flash 存储器,更少的 IO 引脚占用,有效减少 PCB 面积,降低 PCB 设计复杂度。* x) @* r5 \3 f5 @# C: N- a
QSPI 在不同系列 STM32 产品线的支持情况(仅部分罗列,未涵盖所有支持型号)。
" V. w* d: q$ ^3 p0 K$ R5 d8 N
8 U4 h, Z, g& y. e7 l( J2 [
8 {+ W6 e j1 o6 l" f0 I; Z0 `0 h9 i- e7 Y( f8 ], n; W7 G. G7 w% K8 W
QSPI 接口提供了灵活可配置的 5 个阶段,如下图所示(仅用于理解阶段构成,时序图根据配置不同存在差异)。分别是命令阶段、地址阶段、复用字节阶段、Dummy 阶段和数据阶段。可以根据外扩 Flash 中命令时序对不同阶段进行配置。后续会以实例进行呈现。更多内容请参考 AN4760。
2 f+ L5 Y( V% L$ n; Q" Z2 d3 D
" b7 z4 [, T) ]
1 [. C3 n1 X6 P& P. j/ S* w. I. t1 X# O1 x! V8 U' Y7 n* ^. U, x
QSPI 支持三种模式,分别是:; z( J8 g& e( Y5 r: d
间接模式 所有操作通过 QSPI 寄存器实现,类似于传统 SPI,可以使用阻塞模式、中断模式或者 DMA 模式进行读写等访问。本文中提供的实现例为间接模式下的实现。2 ~! } z* d3 v+ N1 }7 M
状态轮询模式 接口自动轮询指定寄存器,直到回读寄存器内容与指定条件匹配。可应用于状态检测,从而实现忙等待等效果。本文不对此模式进行实现介绍,应用实现可参考 Cube 软件包中 QSPI 例程。
) i6 V# Q- f& a0 c3 ?存储器映射模式 外扩 Flash 被视为内部存储器,支持 AHB 主器件直接访问,CPU 能够直接运行位于 QSPI 存储器的执行代码。内部系统架构如下图所示(以 STM32F469/F479 为例)。本文不对此模式进行实现介绍,应用实现可参考 Cube软件包中 QSPI 例程 QSPI_ExecuteInPlace。
1 d5 M4 @/ I/ L3 T& E/ b' u$ ]. T: j, a2 ~! F2 @
' Z6 c+ Q; D. C6 `8 w1 g
. _& g8 S k: Z1 @0 ]. Y8 ^# [2 ~- z5 o& L V6 C" Q, a/ S3 M6 l7 E' D: o
三 QSPI 外扩串行 Flash 实现例 n' ]+ L. o8 ?$ F- B" ]9 j' K. |6 O/ Q* s
3.1 串行 Flash 介绍' F0 b5 A2 z- m: q# r+ d: H* h
以 MICRON N25Q128A13EF840F 为例,更多细节请参考存储器手册。N25Q128A13EF840F 引脚图、时序图和电气参数来源于 N25Q128A13 手册文档。
4 c2 A. j! R# V% i支持协议: SPI, Dual I/O(对应 Dual-SPI), Quad I/O(对应 Quad-SPI)4 y$ Q: D/ M; D5 N2 C7 M! o5 v
支持访问模式: 单线访问、双线访问、四线访问,得益于 QSPI 接口的灵活可配性,三种访问模式全部支持。
2 z- a# w% p- e4 y$ p9 X; E供电电压范围: 2.7 ~ 3.6V( ]" M* y2 i) d) s ~
最大时钟频率: 108MHz
1 P H- \4 N* P* T存储空间: 128Mb (16MB)/ u; D. X* v- L0 ^. g( `, y$ B% \
器件引脚示意图如下所示。由两根电源引脚和六根 QSPI 信号线构成。. p1 e, u3 V0 I" Y- _
0 O0 T& ?+ A# j D3 C
* Y3 o# b- n. O. |2 }/ y- p
7 D3 G" Q* D5 a# F1 L. H下表为存储器 N25Q128A13xxx 命令(未列出全部命令)。通过下表可知,存储器提供了灵活的访问实现形式,而结合同样灵活可配的 QSPI 接口,能够实现存储器命令全支持。而本文仅为提供设计思路,呈现了部分命令在 QSPI 上的实现。% p% B6 a8 U# R' o2 f" a8 I
' V) N6 ]6 z% f* p# u+ P; q6 }( B, S
% R1 b% e* i% |( I9 p6 u
( Z# w' K) U7 a2 Q其中,默认读写默认 3 字节地址,四线快速读命令默认 Dummy 周期数为 8。 o3 G. \4 h2 T
6 \1 O- K- n. B8 R1 ^
3.2 硬件设计3 P, V7 ?' ]6 \ |) h0 j$ w
涉及到的信号线少,硬件设计简单,只需直接将 QSPI 的六根信号线与存储器连接即可。考虑到可测性,可以增加串行电阻或者测试点。硬件电路图如下所示。8 Z$ Q& d+ H% n Z
4 ~0 c5 ~$ \- n: o
1 K+ w- u6 R9 p2 I, u8 O
& v5 e# x4 J) Q' w) u- N8 [
QSPI 接口 PCB 设计遵循如下几点,更多硬件设计内容请参考 AN4488。 _5 x9 |# f/ v+ W
a. 线阻 50Ω ±10%
9 }( b& A( P5 k% nb. 最大线长 < 120mmc. 避免在不同信号层走信号线
$ e! i) V; N: d6 A9 `d. 时钟线至少离其他信号线 3 倍线宽距离5 ?4 s. E2 v/ R' D) c
e. 数据信号线长差 ≤ 10mm
, w X2 D7 x- r( v! pf. 避免时钟线采用蛇形走线,同时尽量减少数据线上过孔。$ B7 E% q$ \/ W7 X1 q! A
% }0 y& i+ T# L" ~ k
. h8 Z, Q( g0 ]# ^% v$ i3.3 软件开发流程5 K C6 a6 q# J3 a. x, L
1 [+ l" v1 C3 k( a
6 F$ ~8 t+ l6 C4 y' _" B9 s8 C8 e
/ q1 {$ h2 P) C/ u) d; y% {3.4 软件实现例# x+ j$ h# W2 X1 d
在环境搭建完成后,就可以利用 STM32CubeMX 根据硬件连接情况,进行 QSPI 配置,获取 IAR 工程。具体软件实现流
, A8 M5 ?, R8 l$ a+ W程如下。 t- X% A# k. P8 U: Y: [. L6 I c
3 K. k. i$ \9 U G- W$ y- }
# j/ g$ F; s# v' Q. Z0 k
1 D* h. g6 e' g$ R& }
/ R& X0 X9 O! @7 X% D3 ~& o7 f
5 L, {& }6 }( w- A
a. 利用 STM32CubeMX 生成 IAR 工程
/ `4 d5 q& ^ z- ?& Z1 B3 f打开 STM32CubeMX - 点击”New project” - 在”Part Number Search’中输入 STM32F469NI - 点击”MCUs Liast”中出现的 STM32F469NIHx - 点击“Start Project” - 此时,基于 STM32F469NIHx 的 STM32CubeMX 工程被打开。如下,根据 STM32F469I-DISCO 板硬件连接情况(QSPI NCS, CLK, Q0, Q1, Q2, Q3 对应 PB6, PF10, PF8, PF9,PF7, PF6;外部高速晶振为 8MHz 无源晶振;调试接口采用 SWD 接口,其中 SWCLK, SWDIO 对应 PA14, PA13):选择” QuadSPI”为”Bank1 with Quad SPI Lines”(注:也可在开发过程中,先用 STM32CubeMX 查看 QSPI 接口对应的 IO 引脚,进行硬件开发) 。) d, C/ N7 t$ K. {& a6 Q- h$ w
注: 在如上选择后,右侧引脚图中 QSPI 对应的引脚会呈现绿色显示。需要根据电路图中所连接的 QSPI 引脚,进行复用引脚确认。例如,在默认情况下,QSPI IO0 对应到 PC9 引脚,而 STM32F469I-DISCO 板上的 QSPI IO0 与PF8 连接,并非 PC9。所以,需要在右侧引脚图中,按住 Ctrl 键,左键在 PC9 引脚按下,拖动至 PF8 处,松开左键和 Ctrl 键,实现 IO0 引脚的关联。6 V, d% ?+ ]0 }
选择“High Speed Clock(HSE)”为“Crystal/Ceramic Resonator”。
+ g5 `) o3 \4 N# S选择”Debug”为”Serial Wire”
1 f6 J/ R$ [4 v. L& D# c+ W" ]3 @% T* e* W7 u9 ^
7 B i* s2 S+ ?# K; C% N6 b2 A, @
- p& i' s& r- o3 R; P
0 o+ v) c3 z! V6 X, h时钟配置如下图所示。设置输入时钟频率为 8MHz - 选择”HSE”做为 PLL 倍频时钟源 - 选择”PLLCLK”做为主频时钟源 - 设置 “HCLK”为 180MHz (FAHB 为 180MHz)- 点击 Enter 键,自动生成对应主频的时钟参数(仅提供时钟配置参考,并不限制一定要设置 180MHz 主频)。0 e* C7 n) [; g& Q% `
( I" I/ r" _/ ~% Q3 `; {# Y9 Y
6 N- o S# A; Y* E9 V8 W* ~
) `+ c+ y# [" O5 `" N. I; y9 a
$ i% |. I# M( V) H6 `; @- J; QQSPI 配置如下图。参数的配置需要与存储器参数匹配。- F* \& F' P2 {1 O' M
• FIFO Threshold(FIFO 阈值) 配置为 4,并不严格要求。 w6 g- s/ H* {# O: H1 V6 H, w
• Smaple shift 选择“Sample shifting half cycle”。延后半个时钟,获取数据线上数据。可以使用在由于线路设计,数
! v' }$ \& }2 G* o5 \/ w7 M8 [4 P$ W据信号存在较大延迟的场景。
# |$ ?( w: [! Y( `6 Y( X7 O5 ~! T( H% |
- c q) h2 R4 y9 k) N' n
. Z W1 Z2 |; ~7 p' g/ U$ c5 t
+ c4 Q( ~" _7 O$ A& n0 i, {# z5 c
使能 QSPI 中断。# P2 `( J9 P& l) @
- V. L+ Q8 l- M- a" D# g( r: k5 x
- Y' f, T4 v4 k) Z' I o* \* h, }9 q0 r5 y! D$ g+ V, j4 J
点击菜单栏”Project” - “Settings” - 设置”Project Name” , “Project Location” 和 “Toolchain / IDE” 。其中“Toolchain/ IDE”设置为 EWARM 以便生成对应 IDE 的工程。其他选项保持默认。5 t6 K9 {, J/ F& O5 g: y& Y4 Z
点击菜单栏”Project” “Generate Code” 等待 IAR 工程生成,出现”Code Generation”界面 - 点击”Open Project”打开工程。- V9 u0 ]- {9 n8 h
9 h! u" f4 @# K3 _( p$ o7 F! V- j
b. 完善工程。5 m- v4 A2 l1 O
由上述步骤获得的 IAR 工程中,包含了时钟配置及 QSPI 接口的初始化。对于外扩 Flash 的操作,还需要 添加外扩Flash 支持的命令进行操作。N25Q128A13EF840F 支持的部分命令可参见本文 3.1 小结。
; k {8 G! P& N: G" o8 e: q5 e在这里出于简化考虑,仅提供了阻塞式读取 ID,擦除 Flash,块写和快读操作的实现。更多实现模式,可以参考Cube 软件包中提供的 QSPI 例程。% d) g# B! a+ p8 W
在 N25Q128A13EF840F 手册中提供了读 ID 命令时序,如下图所示。& P; r4 b; E+ J/ ~4 j5 }' P
6 v" N# T$ z3 @
3 x; f9 c5 W0 Q, I
9 g i, T5 k, U3 k' M" g5 g% @5 s0 k由时序图可知,读 ID 时序构成: 命令阶段 + 数据阶段。命令阶段和数据阶段线宽都为 1,读 ID 命令码为 0x9E 或者0x9F,ID 数据长度为 17 字节。% R/ U' ?; ]% |4 Y1 ^& T& [
在 N25Q128A13EF840F 手册中提供了写使能命令时序,如下图所示。
( ~& X1 [' I! m, \- U# d; l1 _
" t. s3 w" L) q0 a7 y- Z5 {7 }3 O
! z) P+ B0 B4 _# P& i: r; V$ L7 J7 a+ S; z" Z4 a
由时序图可知,块擦除时序仅有命令阶段。命令阶段线宽为 1,写使能命令码为 0x06。(注:这里仅呈现了单线命令模式的实现。除此之外,STM32 QSPI 接口和外扩存储器支持双线、四线模式)。1 r* q: B9 n H8 S
在 N25Q128A13EF840F 手册中提供了扇区擦除命令时序,如下图所示。# j5 ]5 k8 ?" N
# o3 E. T4 G+ G" O0 _6 D
6 M& X' U# M9 Z* G! y& W" L, ~4 C3 v# a8 k
由时序图可知,扇区擦除时序构成:命令阶段+地址阶段。命令阶段和地址阶段线宽为 1,扇区擦除命令码为 0xD8。其中地址为 24-bit,任一位于需要进行擦除操作的扇区范围内地址都有效。在此简单选择扇区 0 进行擦除,选择地址为 0。(注:这里仅呈现了单线命令模式的实现。除此之外,STM32 QSPI 接口和外扩存储器支持双线、四线模式)。在 N25Q128A13EF840F 手册中提供了四线快速写命令时序,如下图所示。
( a) V& j2 ~2 Q
' P9 W) n6 Q& y# r+ L
0 E- r& m; q% X5 T( D! `( l, L1 }
% b: e: W2 n8 f& U& @( @
由时序图可知,四线快速写命令时序构成:命令阶段+地址阶段+数据阶段。命令阶段和地址阶段线宽为 1,数据阶段线宽为 4,四线快速写命令码为 0x32。其中地址为 24-bit,对应写入起始地址。(注:这里仅呈现了单线命令模式的实现。除此之外,STM32 QSPI 接口和外扩存储器支持双线、四线模式)。
0 X9 l; X5 i* H* P1 b# U在 N25Q128A13EF840F 手册中提供了四线快速读命令时序,如下图所示。# k \6 | c0 U, }) n4 J( x" t, w
8 |) z0 ]6 D9 l" J) X4 |& v
' Q2 c' E9 B& u" ]
: _1 H) i. r' n m8 w
由时序图可知,四线快速读命令时序构成:命令阶段+地址阶段+数据阶段。命令阶段和地址阶段线宽为 1,数据阶段线宽为 4,四线快速读命令码为 0x6B。其中地址为 24-bit,对应写入起始地址。四线快速读命令默认 Dummycycles 为 8。(注:这里仅呈现了单线命令模式的实现。除此之外,STM32 QSPI 接口和外扩存储器支持双线、四线模式)。: ^; w& }0 m5 @: f/ ^
在 main.c \ main 函数中,增加代码如下。
2 q8 z3 P( {' [4 j$ P5 l$ G- A
- … //系统、时钟、IO 和 QSPI 初始化
4 {- ]& j/ t q - /* USER CODE BEGIN 2 */& H4 Z$ r9 ?9 s& V' X
- QSPI_CommandTypeDef sCommand;
- D/ t3 q7 Z& ?$ y - static uint8_t Buf_ID[17] = {0};. |1 {2 g* u U+ U7 Z5 v7 o8 i/ A( a
- static uint8_t TxBuf[0x10] = "Ext Flash", RxBuf[0x10] = {0};' m: a! C2 h' p5 u- z7 l5 b+ Y
- 5 q8 h. a4 W. C# d: S
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; //DDR 模式失能, V. d' n4 s9 b
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; //DDR 模式下,数据延迟输出
5 f# ], n0 Y4 C! \6 i$ F, T5 ] - sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; //每次发送都包含命令阶段8 I+ \6 P. M, M! A
- /***** 读 ID 操作 *****/
" U3 J- e( _9 F3 n - sCommand.Instruction = 0x9F; //READ ID 命令码
3 s, ?2 q6 \ s. U5 q - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; //命令线宽+ _0 r% M3 C K+ K4 W
- sCommand.AddressMode = QSPI_ADDRESS_NONE; //地址线宽。无地址阶段
* Q+ k4 R9 W y. V8 [1 h* G - sCommand.DataMode = QSPI_DATA_1_LINE; //数据线宽' g K3 A( s0 l/ c
- sCommand.NbData = 17; //读取数据长度。ID 长度为 17 字节7 M% H1 z1 g( g& X7 S( ?8 q- z
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无复用字节阶段
, i: {2 i9 _$ D2 a$ l7 J - sCommand.DummyCycles = 0; //无 Dummy 阶段, i8 Z5 z# P) E% y- F* L) E+ _: i
- //配置命令(在有数据阶段时,命令在后续发送/接收 API 调用时发送)
0 p: y- y# T) ~; w' I - if (HAL_QSPI_Command(&hqspi, &sCommand, 5000) != HAL_OK)$ o* {, u0 \8 H0 T: j
- {
9 v! j, f4 f9 K" `; D$ D - Error_Handler();
+ N; O# y1 a2 |) Q/ u# u - }
0 o, A. R! D/ T- |5 \( ]* g% _ B - //执行 QSPI 接收/ [" F- m, |# a4 u* n9 U# m
- if (HAL_QSPI_Receive(&hqspi, Buf_ID,5000) != HAL_OK)% `0 w7 b0 e9 X+ D
- {- y' e: ]6 @- Z+ L* f
- Error_Handler();4 {# x6 M( t) ^, x4 T: i4 F: @
- }
& A. Z" ^" T; V" k- c/ e - HAL_Delay(1); //延时 1ms. 单位为 SysTick 定时中断周期- a6 W$ }" v5 u' I- R3 u3 H) e9 \
- + u a1 K P4 B6 w9 E
- /***** 写使能操作(需要在块擦除之前,使外扩存储器处于写使能状态) *****/, B M) P0 A: ~$ J6 U4 c$ F
- sCommand.Instruction = 0x06; //写使能 命令码. b4 {- x, c" R0 b9 i G
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; //命令线宽
/ ~2 O! L; g. _% M8 f/ d, } - sCommand.AddressMode = QSPI_ADDRESS_NONE; //地址线宽。无地址阶段2 N! R1 [5 @- B3 O$ O1 J& D1 X# e
- sCommand.DataMode = QSPI_DATA_NONE; //数据线宽。无数据阶段$ z- j! C5 `; t" _' W; H9 l C3 S! Y
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无复用字节阶段 k+ e- u" Y: Y0 ^' c0 @( C/ E
- sCommand.DummyCycles = 0; //无 Dummy 阶段
% y, ~7 T3 Q' _- `+ y - //配置发送命令
) Y4 b6 \7 P u, ` u. A- W6 M2 Y W - if (HAL_QSPI_Command(&hqspi, &sCommand, 5000) != HAL_OK), X' D' G+ l# d) m3 O |; b& ~
- {
' L$ _4 w1 D# J# P5 ~ - Error_Handler();: O, A8 e" a7 h6 v3 Q. P; F* N s+ i
- }5 F2 K2 C k/ `& M( L2 @
- 3 O) _: W- j5 z4 H' E: \
- /***** 块擦除操作 *****/1 q8 N8 T7 D$ d5 U* ^% y
- sCommand.Instruction = 0xD8; //扇区擦除 命令码
- q4 }$ u0 ^4 ?" g2 L% w9 b' a) U - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; //命令线宽1 u+ L4 d O/ n; [
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE; //地址线宽。无地址阶段
, S K2 ]2 l: W+ Z( t% P, [ - sCommand.AddressSize = QSPI_ADDRESS_24_BITS; //地址长度
$ @# l+ m, x* ?6 \# g% ]# C - sCommand.Address = 0; //位于所需擦除扇区内的任一地址。
, X! y% F' ]; s# l3 b. A" V2 k - sCommand.DataMode = QSPI_DATA_NONE; //数据线宽。无数据阶段& D+ I1 _; R' F: @% b1 n: h8 Z- r
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无复用字节阶段
5 S* X6 R3 I4 g* [5 w5 Q - sCommand.DummyCycles = 0; //无 Dummy 阶段
. c' q4 @/ M3 U3 Y r# K: m - //配置发送命令' M. n) K$ y U6 T+ C' A
- if (HAL_QSPI_Command(&hqspi, &sCommand, 5000) != HAL_OK)/ m" e& P7 b: D! ^1 r- X$ F) _0 v0 W3 @
- {
$ ^0 e- Y f1 c; B% n! m: p/ z& W" T1 I - Error_Handler();
/ H1 K. v* b1 c2 h4 B6 u. q - }9 L. Y/ ^' w' x1 A" w& Y, q |
- HAL_Delay(3000); //延时 3s. 单位为 SysTick 定时中断周期
u# K. c, w% [8 |! y( F
5 [ k% p2 i7 h$ e$ B9 N( a3 W- /***** 写使能操作(需要在块擦除之前,使外扩存储器处于写使能状态) *****/) H$ n) m1 x6 t4 B4 b9 I1 f$ V
- sCommand.Instruction = 0x06; //写使能 命令码 r7 {( ~8 W; E: Z T1 s) T: u' r
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; //命令线宽
6 l, h+ s6 m! F! G" _& j - sCommand.AddressMode = QSPI_ADDRESS_NONE; //地址线宽。无地址阶段" h- u( m6 ^& v' e6 z4 D. ?3 `2 l7 j
- sCommand.DataMode = QSPI_DATA_NONE; //数据线宽。无数据阶段' Q) k8 U" G1 A9 {
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无复用字节阶段
: N6 u. a% F3 {" \ - sCommand.DummyCycles = 0; //无 Dummy 阶段
& }& \6 y' ^1 A - //配置发送命令2 f- N O$ i" x2 C( f
- if (HAL_QSPI_Command(&hqspi, &sCommand, 5000) != HAL_OK)+ }! h7 e4 s# f- N
- {
- l- j+ u: _$ R5 F& l - Error_Handler();
* Z: W1 G. @1 V6 Q; {- `2 l A: Y% h+ P - }. A) j; L. _4 \8 f+ w) e2 w8 y" P
- $ t, a0 ]; O; Q7 F- m- b5 N8 K
- /***** 四线快速写操作 *****/
3 i5 S$ g/ N) y* { - sCommand.Instruction = 0x32; //四线快速写 命令码
" W, N4 r- W9 M% o5 E" D1 K - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; //命令线宽
: _/ y0 g8 b1 ~) V - sCommand.AddressMode = QSPI_ADDRESS_1_LINE; //地址线宽5 r5 Z0 K& }. ] e3 S- B3 p& C4 ~
- sCommand.AddressSize = QSPI_ADDRESS_24_BITS; //地址长度: p3 T" i; q& u, v& I, m
- sCommand.Address = 0; //写入起始地址, W' p4 m% p- c+ K7 B& q
- sCommand.DataMode = QSPI_DATA_4_LINES; //数据线宽
# y" M, T! \" ^. I - sCommand.NbData = 10; //写入数据长度0 s1 q4 u0 i( F* f6 F' F2 x; c- u
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无复用字节阶段
, q* V v3 A9 t% \. B. V% B - sCommand.DummyCycles = 0; //无 Dummy 阶段# _2 g. F- ~' B! }& `/ m" V! t+ A
- //配置命令(在有数据阶段时,命令在后续发送/接收 API 调用时发送)
0 S/ o& e; y: l$ U2 ^. b( e - if (HAL_QSPI_Command(&hqspi, &sCommand, 5000) != HAL_OK)
$ q/ M" q" H$ s1 A& }, } - {; a$ a. Y$ U. S
- Error_Handler();
& U- m! k c) T' f3 C - }# r4 y/ D" k% [* F2 c* E
- //执行 QSPI 接收
- l) h( S* h7 J - if (HAL_QSPI_Transmit(&hqspi, TxBuf ,5000) != HAL_OK)
' j% G- I: O! \! P% E( @5 d/ { g1 p - {/ U$ o* x4 w$ x* _: `* m- ?, I
- Error_Handler();
2 n" ^. F& c& ~) P - }
! B: C K ~; K4 r. T; ^" j- J$ a - HAL_Delay(5); //延时 5ms. 单位为 SysTick 定时中断周期
# N! o9 h' r% s8 b - 7 V! i/ S: D1 `! J
- /***** 四线快速读操作 *****/
7 k0 u6 k' i+ X) Q% J( t5 M - sCommand.Instruction = 0x6B; //四线快速读 命令码( }! r. e. n+ U& K
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; //命令线宽4 ? K6 K' r0 @& K: X% z$ Y
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE; //地址线宽
3 d% J- Z/ e) i! J - sCommand.AddressSize = QSPI_ADDRESS_24_BITS; //地址长度
% {+ P6 a; u' v$ Q5 o$ M0 ~3 l" d. o - sCommand.Address = 0; //起始地址5 f6 a( B$ F, U0 C5 {; g- Q
- sCommand.DataMode = QSPI_DATA_4_LINES; //数据线宽2 d0 c2 l9 d; T: F4 F F% L
- sCommand.NbData = 10; //读取数据长度" I' `! ]* g& D
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; //无复用字节阶段- b5 R5 r7 a& l3 D" P
- sCommand.DummyCycles = 8; //Dummy 阶段。N25Q128A13EF840F
) F/ @5 [( d) C: ~1 B - Dummy cycles 默认为 15
4 H# z; d: E7 N, l* [) ^ - //配置命令(在有数据阶段时,命令在后续发送/接收 API 调用时发送)
* o$ m* o6 x% \; u4 {4 J7 W - if (HAL_QSPI_Command(&hqspi, &sCommand, 5000) != HAL_OK)$ \) j7 y/ ^1 p0 ^" Z9 o+ J$ ]. E
- {
+ T7 c6 ]- H- c" I# t0 \ - Error_Handler();; S6 {) K: R- D% `) G7 W; c
- }. y2 v* \ _# X0 L5 c/ ?
- //执行 QSPI 接收8 m/ \; p. e$ K8 p: p# H4 c
- if (HAL_QSPI_Receive(&hqspi, RxBuf,5000) != HAL_OK)
8 x6 b4 s6 V* n/ \% h+ U6 x6 f _ - {: X: u+ m) I9 R% A
- Error_Handler();$ X. b- C: ~- L
- }; r, a/ R1 X8 @ q% B) X( g
- /* USER CODE END 2 */
复制代码
9 Z% f& i9 l. } ^" M* C! ]
3 \( r3 O- d! f$ [/ o6 E# w) N' F四 小结" }+ \: A. }: i) c3 j
STM32 的 QuadSPI 接口灵活可配,对于命令阶段、地址阶段、复用字节阶段、Dummy 阶段和数据阶段都可以进行配置。基于这种灵活性,能够实现市面上 SPI、Dual IO、Quad IO 的串行 Flash 支持。但出于简化考虑,QSPI 支持的中断访问及DMA 访问等更多功能没有在本文进行介绍,更多实现可以参考 ST 提供的 Cube 软件包中的 QSPI 例程。另外,不同厂家的串行 Flash 命令及操作实现略有差异,具体以采用的 Flash 文档描述为准。7 l1 x! `5 U/ f$ b
, P8 x) {( `/ W. ^8 Q/ f
8 v7 l n/ V- Z5 {3 m4 c5 g. }/ d `$ D8 T& o" h
/ X' m. ]$ [" i4 l' y
+ t$ H- s2 A( t' b0 ?) Q2 J
; r/ V) }. E& k; e3 _4 x2 g) P1 o( u
4 {8 j$ x) x/ q7 q |