79.1 初学者重要提示
" V2 }* k4 F+ \1 T1 q8 q* l 1、 学习本章节前,务必优先学习第79章。. l% F! o& K" h/ n# Z
& ?7 U2 J3 V& ^ 2、 W25Q256JV属于NOR型Flash存储芯片。
6 h, |' |% P" {3 e4 {" p* S+ C8 t) y( O7 K& w5 t3 e% i" p" Z
3、 W25Q256JV手册下载地址。2 R5 [8 y- U' b; T
& k1 n# J" c9 }: I7 @4 e6 u
3 g" t# k+ _6 E. n+ g; f0 m7 k1 e
/ z! z% ^ @8 a) H 4、 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。
! v; }6 ?3 R) r. j G) i. A, b' ~7 l6 x
5、 对QSPI Flash W25Q256JV的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。
3 X! |6 q- R1 f: x
. \. v) q' n" c m1 J, k 6、 W25Q256JV最高支持133MHz。% s4 P+ N9 E% G+ e0 t$ ?# Q
( C' W' u" ~7 t* H# a+ m3 Y# P
7、 STM32H7驱动QSPI Flash的4线DMA模式,读速度48MB/S左右。% d1 H7 f- v5 }
" q; @0 }& v y0 u$ O) q
8、 内存映射模式下,最后一个字节无法正常读取的解决办法。
" b5 s' V$ I& G" ?( e5 i* s
' @ L g% C/ c/ U: k5 H/ h( h 9、 本章配套例子的DMA是采用性能最强的MDMA。
- [: } q0 L8 g$ ~# t: u0 ?6 M6 V6 @
79.2 W25QXX硬件设计! w1 r2 q, X1 k; V" j; r
STM32H7驱动W25Q256JV的硬件设计如下:
- M% [$ {3 q5 `( u5 }) D5 B6 G2 o
) k7 k* o& D' F Z- ]7 a/ B. }/ I l3 b- H/ ], O2 o& b
! m }, w( h; C) j关于这个原理图,要了解到以下几个知识:
$ s. S: D' ?# Z( M+ y, ^' o1 x& `7 k% f {. t
V7开发板实际外接的芯片是W25Q256JV。
# {$ o- g1 Q9 P) C CS片选最好接上拉电阻,防止意外操作。
) L" @6 G* Q5 p; S) f( ]5 d9 a W25Q256的WP引脚用于写保护,低电平有效性,当前是将其作为4方式的IO2。( C2 U7 |3 T5 o1 D' \+ l& J. v
HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。当前是将其作为4线方式的IO3。, U6 W& H' T) V4 L1 {: c1 H. P
79.3 W25QXX关键知识点整理(重要)/ K; b$ N' v6 A Y% M3 s( C
驱动W25QXX前要先了解下这个芯片的相关信息。 c% X" Q" H, [
6 t/ Q; F% o/ b& p
/ u# Y6 O* f) V" z8 i# u
2 q5 |6 B! G0 T' x7 s79.3.1 W25QXX基础信息
6 a0 x4 i* s" F" r2 z/ C% k& g W25Q256JV是32MB(256Mbit)。) I1 a+ G" ?: k9 i7 z$ k
W25Q256JV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
3 S1 S; |) Y" ^/ j! _( _支持两线SPI,用到引脚CLK、CS、IO0、IO1 。# n: O v3 N1 q, x; H, S+ S2 v* b
. j2 Q: y9 o- u/ [" d
支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。+ x- P/ J0 s: p. H9 ~$ t
9 K" a e4 ?' `+ W(注:这里几线的意思是几个数据线)。/ B8 w5 w1 G$ r' E
- s2 B7 o" B' G5 |- i9 {
W25Q256JV支持的最高时钟是133MHz。 K- P G! x1 I4 G
每个扇区最少支持10万次擦写,可以保存20年数据。
# @. X/ `6 z, n6 h8 K- [ 页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
# \7 t P* v% e5 |2 @+ S# K 支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。& E1 i! m: d4 M' N- m
整体框图如下:
# y$ A: c% Y$ D$ [5 z
# {6 h/ d7 K$ u8 }/ V. K) z& g3 y( G) K9 L+ i1 r! L
, ?% l" J% y% b( t/ Y- s/ W
W25Q256JV:7 q" I7 F% L2 q
) l& C/ b6 S7 ]. O" X: A/ n 有512个Block,每个Block大小64KB。/ T# @# f( z* t- m' P' M1 |
每个Block有16个Sector,每个Sector大小4KB。+ }% y) l2 M' Z
每个Sector有16个Page,每个Page大小是256字节。
3 p' x- d. ]8 H+ Y. u a# W6 [79.3.2 W25QXX命令$ R3 U, n8 H% k
使用W25Q256的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的QSPI,即4线SPI,并且用的4字节地址模式,使用的命令如下:
5 N, `" i& l: J; _& C6 R. m0 g8 V8 ]5 E3 ^; z3 O2 w6 R
, T" X5 }* I; v* L5 o( X
( P% R t- ~ ^5 g7 w+ r7 N. x. M( K! C9 f3 ?6 ]6 ?
0 b9 k, r9 b7 i当前主要用到如下几个命令:
4 V7 s. a3 Y9 v$ Z( o) y. \# N
& c8 E3 _0 c; {: M# ?4 {0 R- #define WRITE_ENABLE_CMD 0x06 /* 写使能指令 */
_# j& B- K# @" i9 b - #define READ_ID_CMD2 0x9F /* 读取ID命令 */
" u- ?+ M% I! b1 L - #define READ_STATUS_REG_CMD 0x05 /* 读取状态命令 */ 4 ]. g* X1 l2 g; P& ]5 M* C
- #define BULK_ERASE_CMD 0xC7 /* 整个芯片擦除命令 */
# `5 f1 d% @* @* k9 p( J. q; \ - #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit地址扇区擦除指令, 4KB */
: @+ N, B: }) ? - #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit地址的4线快速写入命令 */
7 J8 e6 u# O& P* W$ x: d* c8 ? - #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit地址的4线快速读取命令 */
复制代码 / `- g- u$ s4 e+ h9 m7 i
79.3.3 W25QXX页编程和页回卷
2 P, m. i! p/ u7 ISPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):- D( U" V6 I& F5 H
; K# b; ?1 P, U5 ]6 w
- bsp_spiWrite1(0x02); ----------第1步发送页编程命令 ' x/ }% e$ h/ {* B! U
- bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16); ----------第2步发送地址
! n; r# r8 v; B' o$ ^ - bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);
6 L$ J+ E; Q- D, x" @ - bsp_spiWrite1(_uiWriteAddr & 0xFF);
) [' o2 c9 z% z8 h ~/ H" {
& D) [0 B$ S% g% _8 I. [- for (i = 0; i < _usSize; i++)
. L( }( ~& b3 q1 x - {! F6 Q7 P% p4 ]& b/ C& d2 Z
- bsp_spiWrite1(*_pBuf++); ----------第3步写数据,此时就可以连续写入数据了,
/ P y2 Y% s2 ~6 y# Q" m7 T - 不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。 ' i5 u/ W B/ l0 e3 ]# B) } x
- }
复制代码
( a/ a- q, o1 @0 O: E+ A+ ^页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。6 K# _4 e7 Q5 ~& |4 M1 M
& t3 P6 @6 \/ l: s# J比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
- W q0 G: g! Y" C5 K& F4 {$ s! B( d6 ?
- uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};) M% o# I3 G0 p
- uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
! p7 o5 _1 i# _7 J+ N3 T8 m' R0 A/ d 从250地址开始写入10个字节数据 PageWrite(tempbuf, 250, 10);(因为一旦写入超过地址255,就会从0地址开始重新写)。7 a# b2 n m6 H9 D
向地址20写入1个字节数据:PageWrite(&temp1, 20, 1);, ~. r& e2 K: O
向地址30写入1个字节数据:PageWrite(&temp2, 30, 1);6 b& A( V; w/ e. H
向地址510写入1个字节数据:PageWrite(&temp3, 510, 1) (这里已经是写到下一页了)
6 o1 O- L! ~$ P下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。9 j2 L. G8 m2 _$ B; }8 ~; E
7 B/ P) y+ z8 ^; i$ v( _
# D, [. [% s5 \2 `: v; P& p) I& O. [( K* }& o* P4 v
79.3.4 W25QXX扇区擦除! p7 `2 e& S4 e0 Y A3 Z& E
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。8 f2 m4 K5 Q* `( f
5 V" K' D. P7 m( S2 E
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
6 M, z* M- p6 [: I* Q$ g2 P
" p. Y' E0 a, S, l! R( j79.3.5 W25QXX规格参数4 z0 @5 c+ S' D+ a2 [/ d; l
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
o, |) f( Z1 R0 i4 @# p4 f, J/ ^0 x. |+ {7 z0 ^
3 k; P2 ]* D8 |8 _" E
/ ^7 y& }1 O7 X; s d8 z1 t+ O# J
1 |( o. K% L0 p, ^: l 页编程时间:典型值0.4ms,最大值3ms。1 S! P; `) ?6 b+ z7 i8 z& k
扇区擦除时间(4KB):典型值50ms,最大值400ms。- A2 N5 X/ M# f6 m
块擦除时间(32KB):典型值120ms,最大值1600ms。
0 M) S0 ^8 |- X4 |1 P# U 块擦除时间(64KB):典型值150ms,最大值2000ms。" t, a5 `+ c7 \
整个芯片擦除时间:典型值80s,最大值400s。
1 H, r$ T) D& \) t+ Z$ C7 o* S( J2 a/ F* D% M1 A: H
支持的速度参数如下:
2 e0 s- N9 F8 V3 F9 J1 p& q: r @) n0 `0 S, i0 H( {, @( d5 Q
+ ] X ^: f/ Z
# a1 [( b/ D% \4 H可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
( o2 r$ b9 f, B/ U5 c! U" u
! C7 Y0 s% I) ]+ N" Y79.4 W25QXX驱动设计 u3 t( I: c3 g* s3 q9 V
W25QXX的程序驱动框架设计如下:9 Z! P7 d9 N3 c+ S$ j5 o9 n
. \) g8 K/ Z$ s
8 G: d2 f2 U1 `9 ^1 z2 X. w; w P- |( i5 z
有了这个框图,程序设计就比较好理解了。 O5 \' r1 a0 F0 [4 ?& P
; z( g7 J( y; \& h" r% ^
79.4.1 第1步:QSPI总线配置: }$ G0 p) W4 B3 I7 L- P( [
QSPI总线配置如下:
" j+ {7 V9 | S8 Y) Q: {2 ?9 x
% k- r& j. V+ d+ p/ N+ S1 B9 L. G- /*. m* o) ` O& q# }- D& h) n, H" R+ B# }9 Z
- *********************************************************************************************************( S; u4 H9 J$ D
- * 函 数 名: bsp_InitQSPI_W25Q256
" k. G5 [ F7 o8 U9 j9 s - * 功能说明: QSPI Flash硬件初始化,配置基本参数2 f8 _: S/ y. ^0 Y
- * 形 参: 无
% R" q4 B9 F% c! }! m$ L - * 返 回 值: 无
6 J! Z7 Y# b3 O4 ~; m - *********************************************************************************************************$ D5 |& a" D$ z* \
- */6 W& s, C O8 u8 ^
- void bsp_InitQSPI_W25Q256(void)3 q3 s# x8 o: O$ }
- {
# p' |) v/ q7 L7 |; A - /* 复位QSPI */3 d, E& N' W% I5 j* R
- QSPIHandle.Instance = QUADSPI;
0 A# Y( N8 [$ r1 f( w G2 `/ t - if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)4 j2 z4 G) f Q+ M$ k: E+ G4 {
- {
( E' E7 s8 o" j7 N; F- u - Error_Handler(__FILE__, __LINE__);6 v- Z2 [; c2 r. O- D ^' u
- }
) R( m$ }! D6 l
2 N# E9 c% H0 T0 p- /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */, b4 A' E) v* e
- QSPIHandle.Init.ClockPrescaler = 1; 4 p3 E5 |6 V( J& r3 W6 ^: q
- 8 b2 `$ w) H7 K
- /* 设置FIFO阀值,范围1 - 32 */7 R7 j2 ~0 v" d2 h! s z! [5 h; t
- QSPIHandle.Init.FifoThreshold = 32; $ U) d9 l" r+ L5 l) T; f& @0 u7 _
; d: d/ W& w w0 ~; c T3 t- /* 4 [5 L; L* C1 G! X8 f& m/ p
- QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。" [. |9 h7 Z7 o& A2 Y
- 在外部信号延迟时,这有利于推迟数据采样。
T; q$ A* M6 m& `6 {9 l - */
1 |4 [* I1 ]: Z, E3 r+ G% o - QSPIHandle.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
0 d2 W; Q4 P1 p/ ]) I/ a - / C! q, N$ c, i& x" j9 J" U( G
- /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */ ]8 K5 e3 g% w7 ]2 D# C: w0 H
- //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。1 _, {3 C7 P% A( q+ N& Q
- QSPIHandle.Init.FlashSize = QSPI_FLASH_SIZE;
+ J# V3 T5 Z0 R" q. U - z# Q2 [) F* X8 Y$ h- M( [
- /* 命令之间的CS片选至少保持2个时钟周期的高电平 */( C6 G& Y: m) z; _1 U
- QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
+ t8 k2 R# ^! F
1 P- V' K( f) }0 d, Y- /*
: J# _& J( ] R5 ] - MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平, K7 L+ Y" ^7 m6 T; f
- MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平. I8 O- ~! S5 y" W; }/ V. M( }
- */
4 D0 w/ I7 J: L - QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
! r; u9 s$ x0 E3 H; E- E
6 M, z* I& U' {& P" C- /* QSPI有两个BANK,这里使用的BANK1 *// H3 N: }) a5 E! C2 w0 i0 V
- QSPIHandle.Init.FlashID = QSPI_FLASH_ID_1;
+ X' l: A# i2 {* R/ {: t6 t - 1 c. B/ Z/ M+ ^0 S) Y
- /* V7开发板仅使用了BANK1,这里是禁止双BANK */) U* E" w) r! j$ e( [7 F2 x
- QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
. {4 f$ U) e( J0 `8 D - , A) E/ `: z- _4 i1 n$ X
- /* 初始化配置QSPI */
% E6 V% M9 R+ Y+ a - if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)0 F, s8 _% q$ S" S/ w0 d
- {/ q9 P! h4 e8 X" Y3 L
- Error_Handler(__FILE__, __LINE__);
+ M! M; s- }. x7 R2 g - } ' U4 c" O: R: \4 g5 E
- }
复制代码 9 I" K# H5 K5 p2 K# |. B$ B% s1 X
QSPI这部分配置,要特别注意Flash大小的设置,这里做了特别处理,本来是应该填入QSPI_FLASH_SIZE-1,而我们实际上填入的是QSPI_FLASH_SIZE,主要是因为内存映射模式下,最后一个字节访问有问题。
# B2 K- a3 v( e( K+ O; B, O" r# Y# ~! F, ^* W$ W: a4 ?* G
79.4.2 第2步:QSPI总线的查询和MDMA方式设置; ~$ G) y$ k5 B3 i) G' G
本章提供了QSPI Flash的查询和MDMA两种方式的例子,驱动的区别是调用的API不同,查询方式调用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。
1 o. x; U1 |( B! C! n1 Y' ^- u0 ]2 O7 ?9 r! w/ ?7 [
79.4.3 第3步:W25QXX的读取实现1 S. _3 P, S" _+ _' R/ U2 O
注:这里以查询方式的API进行说明,DMA方式是一样的。
8 a. F0 U" R& _* N7 H0 X! S* ?' Q2 f8 m8 }
W25QXX的读取功能是发送的4线快速读取指令0xEC,设置任意地址都可以读取数据,只要不超过芯片容量即可(如果采用的DMA方式,限制每次最大读取65536字节)。# M3 f; c, i7 N2 S
2 r: n$ S) T8 d$ P S3 p( q0 d- /*' S9 w% q9 A" B$ G
- *********************************************************************************************************5 A) h7 q* L/ \) c4 B' Q. }
- * 函 数 名: QSPI_ReadBuffer
) r: F2 K1 R8 @8 K5 i - * 功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
5 L2 }5 M! u0 F+ i- L - * 形 参: _pBuf : 数据源缓冲区。5 F: j A+ M$ I$ \7 m! h
- * _uiReadAddr :起始地址。) F6 q- J' A, [# G
- * _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。
" U7 h2 H1 a, q& Q# K( }' ~* e - * 返 回 值: 无
/ P. A; v: I* ` - *********************************************************************************************************- ~; K- t& A; ~7 K8 s3 B$ H% e; u& O
- */
* s: x6 W5 l' }) P4 y3 T+ Y3 { g - void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)+ U( q1 K$ e; I# P$ W
- {
) B7 G; [ z+ O6 H( m0 g0 w
7 x$ S$ n% |( }1 @- QSPI_CommandTypeDef sCommand = {0};
# `7 |7 ^- `0 I& i
& R" v& ~( M6 ]- ' O) [7 b% _5 e' L' |4 }4 Y R+ S
- /* 基本配置 */
& X6 x* ?* o7 S6 o* J - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */; S: r' z1 f: g1 S6 J( X1 N
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */% l4 z( y' k4 s7 @- D
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */& b5 `- ]6 ]9 h v
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */' w/ a. l( h& D' N/ `) y% r
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */! z* y( Z0 {: c% w* w% {
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输要发指令 */ $ p8 W# D% k/ y r* N
- 8 T$ Q+ N- l9 J7 _. F
- /* 读取数据 */
6 @+ r! i& w+ ]- c0 w2 z+ ^* D - sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */6 P1 ?6 @( n3 ^
- sCommand.DummyCycles = 6; /* 空周期 */
4 V* S. ]6 J0 ^" X - sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */
, W3 y& v9 `% M* t4 c. [ - sCommand.DataMode = QSPI_DATA_4_LINES; /* 4线数据 */ . T- ~3 }7 w/ m
- sCommand.NbData = _uiSize; /* 读取的数据大小 */
8 m8 L) u$ p6 f5 [: {! B2 J - sCommand.Address = _uiReadAddr; /* 读取数据的起始地址 */
6 f; a( o2 _. M O& a6 n - 9 V) s$ q7 r% h) _ A! f2 a |
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)2 Z7 r7 m' I9 f1 U: h. I
- {& T6 }- D* C$ ?3 `7 t+ K
- Error_Handler(__FILE__, __LINE__);
" n8 x1 ~8 M7 g9 c& G o - }
# U% n# e: k" ?/ o N6 q- W
+ ]# @: e: F4 V& U- G$ o- /* 读取 */) A6 C/ q8 F! P8 `7 o" F
- if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
1 ]# k7 Z% i. O+ [" ~: K - {
8 _) a. X" ^9 ] - Error_Handler(__FILE__, __LINE__);
5 u' Y) A/ U6 ]' \ - } ! |" {! G; K( { g
- }
复制代码 * E* l m& |4 l/ Z/ r P# o
此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
" S2 j, g# k! I/ X- r. G5 J) H5 j: y }
" A6 A$ M" Y2 ]# m) {' O1 Q
: k" [* a4 h8 y. X K! D/ m左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
5 B. n# s; X& v9 J' d/ ~" o4 [
/ k" j; S4 [$ c9 B( e" a! lsCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;# H3 d! C1 q8 V) t8 ~3 X" m3 j
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;/ R% s. Z* e1 _/ x4 u" X
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
6 z3 A9 J0 C: csCommand.DataMode = QSPI_DATA_4_LINES;
0 Y4 J; t- l; ^) d ^6 S/ X% r+ p; l: I+ E, q$ v3 B& ]
79.4.4 第4步:W25QXX的编程实现$ F$ A9 a \$ V5 l; h
注:这里以查询方式的API进行说明,DMA方式是一样的。
: }3 _3 h2 G" Z4 }8 i$ ]1 B
3 S& ]9 K5 b7 f+ i& T下面实现了一个页编程函数:$ h. Z u- L) d5 r5 m* m5 n' H
$ {, S- A% r& j* j( f5 Z
- /*; h2 t$ e3 C2 j1 E+ ~
- *********************************************************************************************************
, w+ Y8 h; G& v7 v - * 函 数 名: QSPI_WriteBuffer% q' a( { i7 ~. i0 O6 g: \
- * 功能说明: 页编程,页大小256字节,任意页都可以写入
: m; \0 s* D% b! D. k - * 形 参: _pBuf : 数据源缓冲区;! U8 S V2 ~) c5 [0 l
- * _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。: W- e, S( m" f& `
- * _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。2 y9 }5 C$ D' f/ {! p, A* U0 x* u
- * 返 回 值: 1:成功, 0:失败7 s: w6 l2 Y0 ~7 S9 R R; I
- *********************************************************************************************************
# Z7 ~! j& j9 S5 {" ~; _ s7 v - */, M% y. ~0 R4 E. n
- uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)8 i, T. c9 F- l3 q" [; u1 [2 c
- {# r$ h% i7 M! n8 f
- QSPI_CommandTypeDef sCommand={0};% U8 q1 g5 V* x7 D% \9 Q' J7 o
- / Z+ m3 k2 S: l; X
- /* 写使能 */' j$ s/ M3 ^; Y
- QSPI_WriteEnable(&QSPIHandle); $ S$ W$ ^: O( d8 T
) y8 Z* R1 o& U* b5 V5 y: T/ z- /* 基本配置 */
' G% l# q! t w5 n8 _' W - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */0 l' P4 D' O! c% h9 F
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
( |; {9 _+ q$ P5 x$ | - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */3 x" h9 Z! n1 P# J ?# }
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
1 G* L7 V9 ? y' A* ]8 L: G8 F - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */; d i' [* O# }2 {" n+ T
- sCommand.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD; /* 仅发送一次命令 */
: O9 }9 _, o1 P- u! q - , v8 Z' K- J3 u% ?! J7 Q
- /* 写序列配置 */
/ D5 r9 h; [' O6 y9 o& { Y- m- g - sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */
" |! [! o, i# b1 {3 q$ Z. e1 G, }# r - sCommand.DummyCycles = 0; /* 不需要空周期 */4 f- N/ u7 g5 }
- sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 4线地址方式 */# O: ?+ [( |% o4 M: n; R5 |
- sCommand.DataMode = QSPI_DATA_4_LINES; /* 4线数据方式 */0 }! w( [1 P& y4 b1 P' L x
- sCommand.NbData = _usWriteSize; /* 写数据大小 */
( ^ U6 E: v: ~4 U - sCommand.Address = _uiWriteAddr; /* 写入地址 */! G- G0 p: Y9 Z9 R2 u
- - A7 D: G' Q: z- B1 [
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
/ Q+ B4 I0 Q8 x2 H - {( M7 W- m3 m) Y: e6 Q6 N0 A7 x
- //return 0;9 N/ Q0 y M1 A2 x$ `: H: ~0 x
- Error_Handler(__FILE__, __LINE__);2 R T ~6 Z# ^. D1 B7 D+ S& M
- }+ i% V% M) }% S
: {- L- Q, z, S; f2 j- /* 启动传输 */1 z: \ \) d- r4 R _2 k, J0 }' u7 H
- if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
0 j$ @5 G3 M4 A* t' ^% k5 n( x. s - {
# E4 ?7 R0 o( e% t5 k - //return 0;
1 G5 k9 z1 M+ ~" _, H9 m6 g - Error_Handler(__FILE__, __LINE__);
: v% ` K$ z$ |1 Q- \$ p: T4 W - : i- ?4 i9 N: p X2 T
- }
+ y& t9 K) X- E; Y3 F5 X5 C1 e+ ~
4 y! p$ j8 t9 ] Z( i2 z, ]$ c- QSPI_AutoPollingMemReady(&QSPIHandle);
) r! h( n; c% |: ^+ R5 c - 9 V2 j% P" {3 `2 j! w
- return 1;4 J) f$ |. P6 X* p) G9 |6 U
- }
复制代码 7 h. X7 X+ d7 Q3 q: Z7 P- o
此时函数使用的指令0x34对应的W25Q256JV手册说明,注意红色方框位置:
8 k' H7 O6 y* Y/ \* g: y. @4 L; E* s" \
& e4 k2 a" a& R2 j" R# h
2 l. a4 @5 N7 I左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:& W* O& \. A! O. O4 v+ I6 i8 Z' p
$ o; P+ k3 z& M" S9 WsCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; q3 y* U& N# v+ X; C( m& L; K6 l
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;# b1 _$ |& p% \( [+ O3 {$ H
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;9 Q" [* h; o& c7 _) ]* H! n2 a
sCommand.DataMode = QSPI_DATA_4_LINES;
) Q1 k- X$ K+ v/ @/ D& F: _7 A* d9 ]- R
79.4.5 第5步:W25QXX的扇区擦除实现
6 X6 c/ |# B8 e) I! h5 U) g注:这里以查询方式的API进行说明,DMA方式是一样的。1 s" A0 }, q2 }3 Y/ Q& ?
; e% i3 ^0 H6 b6 U# n
通过发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除,擦除的扇区大小是4KB。; _0 k3 g9 v: p/ z5 F2 k K
+ J- U! s. k# g* s% X- /*" h+ r3 i% Y* W# k! I
- *********************************************************************************************************
" U% |3 v a: I - * 函 数 名: QSPI_EraseSector- C5 p" [; ^6 u" ^ z# O8 Y
- * 功能说明: 擦除指定的扇区,扇区大小4KB
' E( h( |3 w, A2 ?- I$ v - * 形 参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,
! h0 l1 N2 D! U: S3 X) m" ^+ |) C% f - * 返 回 值: 无
y( T" T+ ]! T. ?8 T8 H - *********************************************************************************************************
2 Q- F( \ |1 r! Z* K/ R - */
0 F; I1 `6 \2 [" c# r - void QSPI_EraseSector(uint32_t _uiSectorAddr)7 p( u3 L% c5 Y
- {
~5 k6 b- w) Z* U& ~ - QSPI_CommandTypeDef sCommand={0};
4 D( @& t. Z* J r
' N, D' Y/ S, \3 p- /* 写使能 */
1 Z# V2 o) x8 |8 _/ m - QSPI_WriteEnable(&QSPIHandle); 2 ]$ j6 q9 K9 d( |* t2 c/ y+ K
- , B9 F5 G4 B: g/ G3 F8 y0 l
- /* 基本配置 */
! T4 H# D+ {7 u/ ?' b+ ~# G - sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */+ @7 x% T. f* d. u
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
" e4 Z6 _- j& v - sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */! n' X4 G( Y7 }5 A# }8 s( A0 A. p
- sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
: F0 c; F/ \2 I1 ?- u% _) U - sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */1 h2 h# J- ^4 [5 S- Z9 K
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输都发指令 */
8 b5 {+ v* A. q8 L
/ G8 d7 B! I, a) @- /* 擦除配置 */) r; ^; A0 h0 d. Y6 _
- sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD; /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/
) m- n0 I+ q3 Z6 [ - sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 地址发送是1线方式 */ , a+ u! G2 P4 p$ c1 m; S
- sCommand.Address = _uiSectorAddr; /* 扇区首地址,保证是4KB整数倍 */ ( I2 g5 p, V, p" Y' e
- sCommand.DataMode = QSPI_DATA_NONE; /* 无需发送数据 */ 3 [1 _( e" x! L% I6 S
- sCommand.DummyCycles = 0; /* 无需空周期 */ ! n( Y5 c- U7 f, i; e% X
- * G% z( _2 X9 s3 w. t0 d
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
1 k' ?$ ]! ?, o4 e8 W - {/ f- @- S5 n2 `) J* Z& t/ k
- Error_Handler(__FILE__, __LINE__);
7 [5 P8 W, Z$ k7 G - }/ ^! v7 ^' W. |
/ }% [3 g3 u* B- QSPI_AutoPollingMemReady(&QSPIHandle); / o) |9 N* m& z8 I: [
- }
复制代码 & H9 O8 ~ w, C% e& r
此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:
; k: t' H- S" l1 g: J. w3 l+ N
: L( n' Z* _2 E3 r
7 G, R2 f4 v$ D: E! K. u
% s: Y7 z s3 \( \) n) f左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反映到程序里面就是:
+ m* Y6 Y; M* K+ w6 F; q
3 S2 J; Q' _+ ]" l) S6 ysCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;( Y4 ]& u3 P% ^& w
3 m6 G& l: C) S8 e" v# ]3 v
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;
; `: ~* f$ _2 M4 V9 g1 Q0 ]4 C6 L+ K) f3 [* s, ~4 u8 I2 s0 A* T
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;- }: k' b2 ?, j* z
# S3 k2 D9 g4 v: ?. S/ T' c
sCommand.DataMode = QSPI_DATA_1_LINES;
4 ~( L0 E% H6 _; k6 x3 z5 |7 _9 w, \; b
79.4.6 第6步:W25QXX的整个芯片擦除实现
& G, U# B K/ r" `注:这里以查询方式的API进行说明,DMA方式是一样的。
G3 ?4 U6 O8 c4 _
* ~* v3 E+ a! K整个芯片的擦除可以通过擦除各个扇区来实现,也可以调用专门的整个芯片擦除指令实现。下面实现方法是发送整个芯片擦除命令实现:# H Q& x) v3 B4 X) q) F0 h
/ X5 c# ^8 s& c# D/ `2 X `+ i7 i- /* Z9 D, W% F* I7 s& `1 c0 K3 y4 G& h
- *********************************************************************************************************
7 J5 J; w+ X$ L+ P0 s - * 函 数 名: QSPI_EraseChip
" `9 i' w( q+ ?, h: I2 ~) B5 T/ J2 M - * 功能说明: 整个芯片擦除
4 w& i* B6 a, v P7 [1 G( b - * 形 参: 无8 [, h4 u+ U0 _' T' z7 @! \4 Q
- * 返 回 值: 无
! Q0 g* G p+ z9 r' j* p8 u; L/ N - *********************************************************************************************************
* G2 K3 |2 b+ j' l) H - */
% O$ S+ o7 w8 a- W2 | - void QSPI_EraseChip(void)
* O) W4 S0 U3 x6 q$ s - {
+ t2 R+ K9 \& c5 j+ C$ Q - QSPI_CommandTypeDef sCommand={0};+ Y) a3 h! X1 b: J9 A4 i( N$ U
7 }! a- P0 ^) M( c- /* 写使能 *// W8 T, {6 q) ?9 J, N# ]6 z
- QSPI_WriteEnable(&QSPIHandle);
1 b1 J0 t* D6 Z0 r# g$ Z - . z R9 _4 k8 b/ e/ `2 U( x1 F' } t
- /* 基本配置 */& b# X! o( t; _! v$ P5 R6 R* {
- sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */3 y9 A D" h: p8 G o5 }/ M
- sCommand.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */, E. b) k/ L+ k: l' K- H6 r
- sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
+ {9 [2 r+ Q! @- u) t/ F# k& k9 l( D - sCommand.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */2 ~ L- V! g- J( R% e
- sCommand.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */& @1 s+ b1 ?/ i. Z
- sCommand.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输都发指令 */
) @8 o N4 F6 [: t& u" R7 v" m. M - - J) g( Y! D: t& K
- /* 擦除配置 */
2 Z7 l+ [& T) h% R' m& ?9 h; H - sCommand.Instruction = BULK_ERASE_CMD; /* 整个芯片擦除命令*/
/ a) I& Z. X. L. ?# J - sCommand.AddressMode = QSPI_ADDRESS_1_LINE; /* 地址发送是1线方式 */ ' p+ c2 i& T' `! R: F. x
- sCommand.Address = 0; /* 地址 */
( |; f7 @! ~+ [' ^3 O - sCommand.DataMode = QSPI_DATA_NONE; /* 无需发送数据 */
6 _* ~- z* l3 _ - sCommand.DummyCycles = 0; /* 无需空周期 */
" |8 o' R+ s8 c6 X, I; L6 x3 v! J; I - 4 W3 x) g9 i9 W$ [4 a& q" [
- if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK). u0 G: \- m; c& g+ ^. W
- {
( B' m3 ^8 X% n8 F/ v N - Error_Handler(__FILE__, __LINE__);, B5 ^7 k; z* D7 r
- }; L1 a4 z6 \. E5 a3 ]7 j
1 q* y8 \5 ~0 i# Y- QSPI_AutoPollingMemReady(&QSPIHandle); W" U) p% a2 E5 g! _% G1 A
- }
复制代码 0 x5 M7 @. H* Z( [+ {) t1 D
此时函数使用的指令0xC7对应的W25Q256JV手册说明,注意红色方框位置:: O: w% F, @8 |
1 Q- P5 W4 P+ M( L: B2 d/ h
# U3 p4 p+ r$ D) O" f
6 i) a, s& `# _左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反应到程序里面就是:3 c2 M$ i8 n `- i1 [3 O' `5 D
: a3 \, D6 A5 `, a+ wsCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
- z- h9 H* n- esCommand.AddressMode = QSPI_ADDRESS_1_LINES;
' N. I+ g7 ]& BsCommand.AddressSize = QSPI_ADDRESS_32_BITS;
: ]3 }) m1 H! r4 E$ @" ^sCommand.DataMode = QSPI_DATA_1_LINES;. E9 @0 B. D- i% i# g5 R" |6 d3 H- x
7 s( c# u, y3 e4 ~, n3 V2 H擦除用不到数据阶段,sCommand.DataMode = QSPI_DATA_NONE即可。
4 k3 a2 M M z8 d- Z4 C: A c6 \8 N: }7 d* q' f) L( M& j
79.4.7 第7步:W25QXX内存映射实现3 V7 A/ q, H. z) R- \% d) G
注:这里以查询方式的API进行说明,DMA方式是一样的。
' [2 x3 P7 q! {3 |* S$ \
8 b3 V( k* U5 C& Z) @& Y5 X通过内存映射模式,就可以像使用内部Flash一样使用W25QXX,代码实现如下:, e. m$ w9 m) U8 o( }/ w9 b
% z. U8 [9 l* g/ K0 ]; D3 S
- /*
8 K! d5 r9 v. w0 P) |: o ?1 w - *********************************************************************************************************. ? t/ f1 C/ c* V4 D& f J
- * 函 数 名: QSPI_MemoryMapped' l% q, k. n; }5 G1 h$ w
- * 功能说明: QSPI内存映射,地址 0x90000000
) w Z( E& n) T0 z0 C - * 形 参: 无, Y4 c, z0 n, {. e6 Y
- * 返 回 值: 无' w# Z' d3 Z3 L8 Y6 ?' s+ M
- *********************************************************************************************************! t# a5 v& A2 w
- */( u9 ?* y8 j2 u, l, o' i. H
- void QSPI_MemoryMapped(void)
6 T1 b! h/ |, c7 y- l% n - {6 e( L& v, t/ H8 j! r
- QSPI_CommandTypeDef s_command = {0};/ D/ I9 y# c- F4 ~2 z
- QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};) A5 ^, R$ w0 H% V Z9 Y% f& p
& ?1 r7 D4 g, V1 f* M9 X: j0 A$ J- /* 基本配置 */- k" p! c- V1 h8 M
- s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE; /* 1线方式发送指令 */
# F, W2 Q, D# _6 k5 ]9 ^ - s_command.AddressSize = QSPI_ADDRESS_32_BITS; /* 32位地址 */
) ]0 B1 Z4 g0 o N6 Z - s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; /* 无交替字节 */
- \$ V7 }% z. }' i# R - s_command.DdrMode = QSPI_DDR_MODE_DISABLE; /* W25Q256JV不支持DDR */
}5 U5 p, F3 Q2 \! [, z" H - s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY; /* DDR模式,数据输出延迟 */
: U2 T2 D, V; b1 g$ K - s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; /* 每次传输都发指令 */0 `* _* D2 s' h& U* X
r) K- C% x T: W# |& G1 c- /* 全部采用4线 */* N; h( n' ^! P( q* t# W) t9 d
- s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */
, [+ } ~9 `" S B7 l - s_command.AddressMode = QSPI_ADDRESS_4_LINES; /* 4个地址线 */7 `! S9 L5 q( ?" z4 r) `* v( M1 Z
- s_command.DataMode = QSPI_DATA_4_LINES; /* 4个数据线 */
a- Q% f. U$ m/ ?6 X( G+ M. h - s_command.DummyCycles = 6; /* 空周期 */) _+ y% r& D3 v3 {1 {! b) |
- 1 c8 m& i1 U, S% R2 d
- /* 关闭溢出计数 */
- r6 d3 j& i6 s - s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;6 L" G- O8 F7 P: i( ]6 W4 r
- s_mem_mapped_cfg.TimeOutPeriod = 0;
, }9 ?" g Z/ b h; T) w - 8 @0 X& ~" |1 v6 O
- if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)& u7 u W- ~ w& ?& r
- {
' l3 d6 X9 q. |- R# M( o V - Error_Handler(__FILE__, __LINE__);% `% h5 W8 B8 C. [# C$ |, u* B
- }
, `' K2 p/ m P& G* r& T5 A - }
复制代码
! V& Q6 ]6 U u此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:! _* A9 |, l# t! B# M: b
' D3 p; | {6 V0 Z7 {% @5 l
9 H, r$ L: y% L" |% L( s, T" K0 L9 T0 ^
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,采用的4字节地址方式,反应到程序里面就是:
# `& G3 k* s4 o7 ?1 p. s/ m/ o9 j$ Z4 R! F& w. I6 z( z$ u( G5 E
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
8 M8 e+ g5 d0 B7 O) T0 u; ~sCommand.AddressMode = QSPI_ADDRESS_4_LINES;2 ?3 d0 f, f' m8 [ |
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;6 X& q% X# b5 }" R& s2 G
sCommand.DataMode = QSPI_DATA_4_LINES;# C5 A! h8 g5 h- T4 s* }0 k
9 M# N/ v3 C" K& w* [5 Q: D+ \' X6 n
79.4.8 第8步:使用MDMA方式要注意Cache问题' d; g6 n2 B/ Y9 p$ `% \# U
如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM,这样可以方便大家做测试,测试通过后,再根据需要开启Cache。
$ w- A- `* W t
* ^4 c8 x7 K$ m+ ?$ m! w3 h- /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
* l# \/ x3 {* c$ G# |$ S$ c - MPU_InitStruct.Enable = MPU_REGION_ENABLE;& e, B6 M% Y% v
- MPU_InitStruct.BaseAddress = 0x24000000;* ?2 Q! Y! E. ?# u Z
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
) c1 H$ F9 k8 M - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
* v( G3 b1 q, M0 B: L' Z3 e- I - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
% Y e2 o! x% S* H& M# B9 P3 e - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
1 c6 G# ?& ` {5 P$ _: [' ^8 Y - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
/ m0 ] S6 h1 c) _( \ - MPU_InitStruct.Number = MPU_REGION_NUMBER0;" w1 |0 c) K' l) d% A3 I& v% |
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;# I7 N8 y; F% X* X5 V
- MPU_InitStruct.SubRegionDisable = 0x00;
# M, ?, Z. J3 R y+ g8 U5 Q - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
3 {2 j% A# I( a& {
( E# O) v) [0 w7 n" o$ B/ l- HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码 0 {. B5 l3 i: [4 f# C0 J. T) \
79.5 W25QXX板级支持包(bsp_qspi_w25q256.c)
4 S/ Y* M2 b L7 o, iW25QXX驱动文件bsp_qspi_w25q256.c主要实现了如下几个API供用户调用:
& ?+ G5 j; a U. _& U# x) C, u
( c# c8 {. u) x QSPI_ReadBuffer
* f8 N/ E/ }+ N3 i; n7 @ QSPI_WriteBuffer+ f- g4 x. E" A1 c4 ?# O
QSPI_EraseSector
5 Q' }3 R1 |( V( z5 R QSPI_EraseChip. w/ ]# v, r+ ^
QSPI_MemoryMapped
5 V# Z' `* K5 @: W: n9 @; M79.5.1 函数QSPI_ReadBuffer
: `/ N, z7 D6 V+ c( g2 P0 L0 M函数原型:
6 m) s2 E: ?# P* ~+ f3 S5 c
9 s. v. c& w7 O! V# Ovoid QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);
. `1 Q- a( S7 _& `# Q& e! j* j: ] Z6 w: g; f* [
函数描述:
! K% ^7 }7 C( Y* |$ e; H/ |; l- x/ E% I* r9 S( j7 G1 B
此函数主要用于从QSPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可(如果使用DMA方式,每次最大65536字节)。3 L# o7 J2 t9 g
+ D8 d9 L# |9 }1 n' B函数参数:
" B. I+ |) t8 r' Y1 _* M* `& j i U; C1 `
第1个参数用于存储从QSPI Flash读取的数据。
! e1 r: i6 @; i7 t9 k6 M 第2个参数是读取地址,不可以超过芯片容量。$ u' S8 S- O0 b8 { `# c7 k
第3个参数是读取的数据大小,读取范围不可以超过芯片容量。! o" D# E' n) {
79.5.2 函数QSPI_WriteBuffer
7 t7 B. h# t0 D函数原型:. B3 C0 }8 U7 z+ E
6 J5 s" l6 g5 M/ k5 quint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);$ x% t5 T/ T. G) e- L
, ]( C" i7 t) L8 C- l Z
函数描述:
* Y3 r6 \ c3 o+ C. ]/ b3 H6 M% Q3 \; v6 R: p( p
页编程,页大小256字节,任意页都可以写入。注意使用前,务必保证相应页已经做了擦除操作。# k' i5 E, m- N
$ L% i& Q. P8 U1 b$ I0 G
函数参数:
5 e. o. O( ]" i) D$ }; y* B' e* j
第1个参数是源数据缓冲区。
X& T/ M m& F 第2个参数是目标区域首地址,即页首地址,比如0, 256, 512等。
i% w0 V. c$ W$ ~ B 第3个参数是数据个数,不能超过页面大小,范围1 – 256,单位字节个数。5 f5 i9 D; z8 K6 u ^
返回值,返回1表示成功,返回0表示失败。1 ~3 Z. h/ w) u! X
79.5.3 函数QSPI_EraseSector
3 ~% m9 o3 n7 w* e函数原型:
d" D% q$ S- g! Q" x# J
6 ^ j- j% A: K" @1 b5 h5 N8 \void QSPI_EraseSector(uint32_t _uiSectorAddr), ]5 |0 M' a% y9 m; T- ^% K( z
3 f& P M* s! Z) F函数描述:2 [: f4 M; O8 R X) u
4 e! u1 r- X% d$ e
此函数主要用于扇区擦除,一个扇区大小是4KB。, V. I; Q: [+ Q" \4 z1 K
5 A. t7 m3 w( B# {) @( |, z- g6 t( @7 ^函数参数:3 {! @. X( H. z$ @0 e
$ b6 ^3 M8 h+ _1 V- a* H6 [ 第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。. l( ^# B; Y9 {/ s8 ~
79.5.4 函数QSPI_EraseChip4 e5 L' f( {1 d& x6 ~
函数原型:
/ G( M2 ?) S( p! N D
: Z3 q2 s8 {( t1 z: R Xvoid QSPI_EraseChip(void)2 F1 t- l6 t0 e0 u
$ g& R* B! G) O& a! z
函数描述:) B* s+ C! @; m% s2 P
1 l0 ^) g3 _( x' s
此函数主要用于整个芯片擦除。; A6 y# p3 @( f% h3 r
; f) Y0 Q. v, ?# d79.5.5 函数QSPI_MemoryMapped
$ Q, ?- h) B* I0 o& q函数原型:
3 V, Y X8 u/ p0 e! @4 ]2 B& c# l3 G0 h C
void QSPI_MemoryMapped(void)
! @/ x% L0 {: a% K: ]/ i0 F0 w+ K* Z1 f6 ?3 c# \! v% ^& B
函数描述:
9 v) f5 ?- L" g& Q9 D# R- C) u# c) s' a( @; q. [. ] e
调用了此函数就可以像使用内部Flash一样使用外部Flash。
" l& M7 t7 x1 L; R) h( X: p6 t' Z! c+ A7 y. Z5 s- W
79.6 W25QXX驱动移植和使用
$ T: b9 M$ G6 ?: n( QW25QXX移植步骤如下:
3 o5 T& X: z6 j% a, G! h( _/ y. [; s+ {) Y8 ^- J1 r
第1步:复制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目录,并添加到工程里面。
! V, f. N$ v7 y( h 第2步:根据使用的QSPI引脚,时钟等,修改bsp_qspi_w25q256.c文件开头的宏定义。0 B+ f7 R; R+ X. S; m& k7 ^
- /* 2 m& l$ W& K" U1 Q6 r6 a* ?! U
- STM32-V7开发板接线
$ E, v L0 _ e% J - / V3 E# w) O% F6 ~$ G: C- \
- PG6/QUADSPI_BK1_NCS AF10
2 v! e4 a; U+ E5 ~. P+ ] - PF10/QUADSPI_CLK AF9. ]: a8 a) g/ [/ ?: k& t! g, x
- PF8/QUADSPI_BK1_IO0 AF10% B+ z# c* A6 h
- PF9/QUADSPI_BK1_IO1 AF10' |" z5 o9 ~" w5 m' J7 ]" }
- PF7/QUADSPI_BK1_IO2 AF9! _. C5 G1 K/ U5 K4 m
- PF6/QUADSPI_BK1_IO3 AF98 P1 i% p: T# f, J7 }5 [
6 z! Z8 V o6 m9 R/ w8 g- W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB
9 z2 N; t% u/ B0 `) L( `: d; a - */2 o. i( K. r/ {' J
5 d) M# ?( c1 |- /* QSPI引脚和时钟相关配置宏定义 */
5 h. Q" K2 F, l6 ~ - #define QSPI_CLK_ENABLE() __HAL_RCC_QSPI_CLK_ENABLE()9 R4 v$ s ]3 C! I
- #define QSPI_CLK_DISABLE() __HAL_RCC_QSPI_CLK_DISABLE()
4 h' h) F, A* s! i' `3 y; Z4 y) a3 K - #define QSPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE()# B. y E$ Q6 v5 Q2 R- a
- #define QSPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
2 b) }2 F7 J; |- u - #define QSPI_BK1_D0_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
. y5 b* x: j& N: ~ - #define QSPI_BK1_D1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()6 ^0 a: F3 `7 ?4 `6 c
- #define QSPI_BK1_D2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
9 @. q: A) S% v: A- H$ Z* i `( K { - #define QSPI_BK1_D3_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()2 ]7 I; a% J: O9 v
; H8 R+ {* V g0 k' y; n! Y, _- #define QSPI_MDMA_CLK_ENABLE() __HAL_RCC_MDMA_CLK_ENABLE()
" J+ U0 f! w. l - #define QSPI_FORCE_RESET() __HAL_RCC_QSPI_FORCE_RESET()+ g A) H) G c$ {1 Q! Z: ~. v5 ?
- #define QSPI_RELEASE_RESET() __HAL_RCC_QSPI_RELEASE_RESET()8 Z4 u" d+ v8 q |
- 9 l: Z* L0 o6 T9 e& `4 _
- #define QSPI_CS_PIN GPIO_PIN_6
- [) S4 u: s- w' n1 P: Y - #define QSPI_CS_GPIO_PORT GPIOG
" \' R# W4 s# i
5 e% Z# W8 w$ I2 i5 W; j- #define QSPI_CLK_PIN GPIO_PIN_10& X* H& {6 l [! |
- #define QSPI_CLK_GPIO_PORT GPIOF _& U9 B% q* y# w: \
- 2 R/ v! n9 w9 } Z
- #define QSPI_BK1_D0_PIN GPIO_PIN_87 A0 M( a2 j# {+ e! `8 r" C2 b w
- #define QSPI_BK1_D0_GPIO_PORT GPIOF3 ?7 j5 ?% q7 A- I U
- , c9 x+ P/ L; q* Y# ]
- #define QSPI_BK1_D1_PIN GPIO_PIN_9
% y! w- J+ N3 E$ F - #define QSPI_BK1_D1_GPIO_PORT GPIOF" g1 k& x" ^* u
! ~3 _5 M* J" s, \- #define QSPI_BK1_D2_PIN GPIO_PIN_7 x8 i& I% d5 g
- #define QSPI_BK1_D2_GPIO_PORT GPIOF
* d6 s6 N$ ^! C. {2 |+ O1 ]
9 x9 F6 v' Y( c0 C- #define QSPI_BK1_D3_PIN GPIO_PIN_6
" |! ~3 J! `9 U( d2 U - #define QSPI_BK1_D3_GPIO_PORT GPIOF
0 e6 P% {7 D: A$ z* d - 根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件
1 v& o5 j) A$ @) w \2 `& @ - /* W25Q256JV基本信息 */7 F5 y( _ D3 A: {
- #define QSPI_FLASH_SIZE 25 /* Flash大小,2^25 = 32MB*/0 ]3 a; ^+ W! t- k, e% C( Z
- #define QSPI_SECTOR_SIZE (4 * 1024) /* 扇区大小,4KB */
6 }+ D, ^& H6 A% q, x# b- G4 t - #define QSPI_PAGE_SIZE 256 /* 页大小,256字节 */
. X o# o6 u E% Z, {# f* ? - #define QSPI_END_ADDR (1 << QSPI_FLASH_SIZE) /* 末尾地址 */5 r% }' E+ N9 \9 y9 _( g
- #define QSPI_FLASH_SIZES 32*1024*1024 /* Flash大小,2^25 = 32MB*/# b! J* \! O% f9 {" b
) \ c1 g, ^. U6 Y( ?- /* W25Q256JV相关命令 *// a* q& M: T8 s) Z, i# R# L
- #define WRITE_ENABLE_CMD 0x06 /* 写使能指令 */ : t- g4 f7 W4 \; ~2 m d
- #define READ_ID_CMD2 0x9F /* 读取ID命令 */
! a' @" w" l. n5 l - #define READ_STATUS_REG_CMD 0x05 /* 读取状态命令 */ + V; E/ U8 C0 `- {1 W% h8 n2 C* d
- #define BULK_ERASE_CMD 0xC7 /* 整个芯片擦除命令 */
4 A5 ?. Q. ]5 j% o5 O0 o& | - #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit地址扇区擦除指令, 4KB */
. V: q/ z9 M/ G& {3 t - #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit地址的4线快速写入命令 */; o: k7 s2 u/ d% f6 Y
- #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit地址的4线快速读取命令 */
复制代码 ! U! o, P/ a( E
第4步:如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM:
) i. @, Y8 e( @/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
$ T9 O5 G, _. i4 A7 ]MPU_InitStruct.Enable = MPU_REGION_ENABLE;
$ q/ ?" t2 @; zMPU_InitStruct.BaseAddress = 0x24000000;
. Q3 g+ g0 [% z. Q4 XMPU_InitStruct.Size = MPU_REGION_SIZE_512KB;$ n, x9 r" T' f: {- |8 M
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;* c% F5 T" e: G" l( E
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;1 D& x9 O% E W' N- j1 k" a" U
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;3 B* l6 k- i" G& @
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;: Z- e2 |4 \4 D3 d
MPU_InitStruct.Number = MPU_REGION_NUMBER0;0 ?/ x( L) U) v
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;; z3 D F; r( K7 u
MPU_InitStruct.SubRegionDisable = 0x00;. n% k( [9 n4 W' y2 [+ p
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;, @/ W# }( K% f& ]% s" H
9 I4 M. } P- u' ]
HAL_MPU_ConfigRegion(&MPU_InitStruct);! U' I1 b- u0 i
第5步:初始化QSPI。& K- [ U; G7 \7 d2 W
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
* p, `- x: g- a) _5 R* Q$ kbsp_InitQSPI_W25Q256(); /* 配置SPI总线 */, I$ W, D' r, \9 T2 K% u8 t: a
第6步:QSPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。+ |$ M: r4 u. A4 |3 y4 x! E% [
第7步:应用方法看本章节配套例子即可。
t. O& Q; s+ X2 ]6 s79.7 实验例程设计框架% Y# Q$ _" I% M' {& {
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
0 [. U" ?7 _8 Y3 a; x1 c7 ?
. w5 |8 D; `4 T6 ]5 e6 K* g& `
/ j! h' }7 N% ~4 g% n8 t* c& u2 ^! S% R# C
第1阶段,上电启动阶段:# S0 W' j& j# t0 ^; G
2 u* _7 n: {$ |% }
这部分在第14章进行了详细说明。% O* L3 j- O1 E! c- r) j
第2阶段,进入main函数:1 |& g2 S8 O9 W, X5 {* J. A
) V& B& Y) m' ^. H3 Q
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
7 X' O- m7 ^9 l6 }7 c 第2部分,应用程序设计部分,实现QSPI Flash的查询和MDMA方式操作。6 a3 r* d9 M H- F. d
79.8 实验例程说明(MDK)
* @( @1 P+ ^% y. x配套例子:! a1 u: s4 e! g0 U7 y" J7 k
. O& K. f+ z' hV7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)4 A2 M1 b! O0 y. h6 Z: U5 Y
" H7 o% k0 t# p/ \- h# r- ^V7-059_QSPI读写例程,查询方式, G% I. Y+ ?5 p. w! ^ q- p, s
; G9 u, w$ n) @+ C- g$ l# _2 i
实验目的:
}7 {0 K! y: Q' O
* K& s; t& {) x3 q2 M! [' d2 l学习QSPI Flash的读写测试例程
) E# M, z/ O% s1 B' O0 j$ t7 n实验操作:# |( A+ {* a( Q5 s
; `3 ^3 y; v8 I* b3 Y& u& r9 |
支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
" p! ^+ W n s0 G4 O4 f0 p5 Kprintf("请选择操作命令:\r\n");0 I! @9 k- b* v" y7 R# |
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);6 m9 O) Y- ?% \+ @2 p* ?1 H5 w
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);; T. x- Z' B' @: h/ f+ K/ Z
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
) T: l9 i9 D+ y/ j! b3 r' tprintf("【4 - 读整个串行Flash, 测试读速度】\r\n");
, N# `$ D( W, }$ {6 k" Qprintf("【Z - 读取前1K,地址自动减少】\r\n");% b: L' A+ L, f- {$ `
printf("【X - 读取后1K,地址自动增加】\r\n");
" `! i, c: M/ z6 D+ ?$ [7 U, Bprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");3 f1 n& ?, _* @) }9 z0 U# W* |6 g
printf("其他任意键 - 显示命令提示\r\n");
' _/ Y9 _# [2 b+ p$ T3 c/ d8 A上电后串口打印的信息:9 g! l5 h/ }) F" i8 C( u3 C
1 M7 d: q8 I9 v! H1 U3 \1 S波特率 115200,数据位 8,奇偶校验位无,停止位 1。. O% |# N: ~# Y' m- h
' m1 |5 i% i P2 }2 ?; D
J' u# { J G+ e, J! Q2 d
0 c! t5 b/ `* w6 W; W6 F程序设计:
/ a& \/ G$ m8 n: O& {$ e( C; \7 N' @2 s- d
系统栈大小分配:' f8 c1 I* m7 e/ q- f; H6 r
( q# A4 `8 |# ~8 J7 _- T+ q8 L
( ?9 i4 m% p' K; b& ~
8 U: j6 D8 ^0 N* v& y. n RAM空间用的DTCM:
$ s' t! m- ]1 y6 j( X5 u, X5 ?! j) [3 G. I) w" N
: k& f& v ^' ~$ @ ^: ?
9 p* R5 A! O! Q4 k7 V8 ~ | 硬件外设初始化
' L" b& K/ o; f1 v3 ~6 G; R! c4 |! ?1 G$ J, q; N% l
硬件外设的初始化是在 bsp.c 文件实现:+ C* j- q4 f7 _, c
1 J' }) k% L+ `0 v7 I
- /*
8 V6 I& l+ v5 }* ^ - *********************************************************************************************************) ~3 T. D' ~7 C% g
- * 函 数 名: bsp_Init
5 j+ L: J1 k. I8 K) N" `5 M' W! Y - * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次% S1 H7 m! v9 M: H- T
- * 形 参:无
/ f L# p6 M. R4 u6 P i# x - * 返 回 值: 无
2 m8 q, w2 H; p - *********************************************************************************************************0 |+ ^& P. N1 l, l/ Y
- */
: L8 U) a+ ^0 s- ^; J" c - void bsp_Init(void)
; q o! e& S6 U8 I* E- Y) n - {; a9 X: y6 w% F0 l& u
- /* 配置MPU */2 r. p+ u+ A! e f( ^3 W4 \
- MPU_Config();% M7 p" k+ f: B# q& Z- X4 a
$ l: ]/ t5 i; v& n+ ~* F- /* 使能L1 Cache */2 R+ f) W* S7 Q' O9 y
- CPU_CACHE_Enable();0 }5 @& c2 B9 x' r1 n
- _* q, o: h. O( [9 t" W0 Z5 _5 c- /*
' t- V! i' {' _+ K - STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
" _0 {7 R9 h$ \- @ - - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。* H# y6 x+ Q1 e& A' e F
- - 设置NVIV优先级分组为4。
7 J2 X- u/ u5 z6 c: `5 M - */. }% Y; r. C/ t
- HAL_Init();
& _8 b, L; Y; z: N/ }/ _ - ( v- U- Q0 g! k5 \. s: w; ]
- /* 6 A: [, I5 W) ?- y* N/ l
- 配置系统时钟到400MHz" x- B: N. |* F Z
- - 切换使用HSE。" P/ h# O6 S: R; Y% f, T; q I8 `. k
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
; x8 ~- v( K2 l- w - */1 F3 H3 D* w H2 d& [( h! Y+ F
- SystemClock_Config();' s7 B" _; d3 S1 Y
- ) J/ o3 F. S* E: z' h% ~
- /*
* p/ L: G% V+ C1 C% C3 g9 P - Event Recorder:! h5 u3 B/ g1 m7 y& v6 g( h
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。7 V G. H n4 u
- - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章5 j# @6 M, g j
- */ ; {1 q, T8 j& J2 D1 s0 ]! ?& t+ D
- #if Enable_EventRecorder == 1
# t1 @: U/ t, k0 T' [ - /* 初始化EventRecorder并开启 */
; i$ p# R8 N3 m - EventRecorderInitialize(EventRecordAll, 1U);
* H, |! `/ P$ b7 v7 ~" r - EventRecorderStart();
/ }8 I' X' q2 w% k - #endif
9 f; l- j3 ]; s4 a4 W+ H7 p. H
# @% ^; F- b+ d2 p- bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ % q0 g" y5 v. r* s" z# n9 H
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
, s \; O. }+ P; u. K7 s! f - bsp_InitTimer(); /* 初始化滴答定时器 */$ \% R' q ?8 z# l Y
- bsp_InitLPUart(); /* 初始化串口 */3 ^ V! U3 G# K
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */ . Y8 [8 [* E3 F& K$ u/ x
- bsp_InitLed(); /* 初始化LED */
" `- f# u# q0 M5 v+ c- G( H) E - bsp_InitExtSDRAM(); /* 初始化SDRAM */
; E$ B# t9 b/ i0 h4 c2 e$ U
3 p* ^) B+ F0 z1 \* z# J- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
- [ V0 v7 e6 r0 g - bsp_InitQSPI_W25Q256(); /* 配置SPI总线 */}
复制代码 # H5 b1 |1 W$ o A0 `' a$ @! R
MPU配置和Cache配置:
$ s# |& @: Y) B3 {$ t- m/ [) Y8 ` i# ]0 W3 Z; n/ ~# ]
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。, N6 I+ H& J7 b
5 R V9 Z6 g5 c5 S: u7 q% v- /*6 m: w8 U& a) y
- *********************************************************************************************************: P& n3 m0 E- _% O) M7 U$ ]: k
- * 函 数 名: MPU_Config
# V3 s9 q! t" k3 a6 O4 [: r' ` - * 功能说明: 配置MPU7 _1 W2 v- _, D: ^
- * 形 参: 无
% M* C0 A# ~7 K! X6 W6 h - * 返 回 值: 无
/ k& v3 F, X- U. ?$ ~% F# r3 I - *********************************************************************************************************
8 h' K' k+ f, y( m - */
N' W, l% q% F+ V% H5 @ - static void MPU_Config( void )
3 N. U; Y& i. T - {
! v0 U2 s3 r M. P" O4 r- B) P - MPU_Region_InitTypeDef MPU_InitStruct;
# ]" W V1 Z0 O/ D; H% i) v% w - # _0 N5 {# i( `3 f6 G9 O0 k: U8 _
- /* 禁止 MPU */
" V! v* |3 _. V$ Z7 D0 _ - HAL_MPU_Disable();
1 m& l+ T! F) W9 S. ?% T, S - ; a5 m" X! R# r
- #if 0
7 ^, U, g7 u. p, V* f5 ] - /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */$ U9 U2 @4 M. |' H2 E
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;0 b" s7 m- y( W6 F" j% X7 u
- MPU_InitStruct.BaseAddress = 0x24000000;
! \- S' V( X2 m. S1 E: I) B* |( Q7 R - MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;5 k3 W- {2 C' I3 z3 u7 j
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;, O2 C X! p+ T$ |
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;% }/ K2 A0 `; d. \0 a/ ^
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- d: j5 w6 \# S+ y - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;/ c" J) U3 ^) E! I F6 p
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
8 w! y7 Q8 Q0 o5 @# \ - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
/ L+ g, j& b8 _. M - MPU_InitStruct.SubRegionDisable = 0x00;
3 `% W2 E1 F! g6 K - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;0 z" g! q" I0 `0 L. j+ x9 [2 f) `
- - c E$ R* ]0 B6 z! k: k5 g
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
7 J0 p w# z: H4 a& M0 n9 y
8 Y, i' J$ Y. t- #else! R/ e( p7 ]7 X8 @
- /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
3 t9 O' z! g/ G r! n - MPU_InitStruct.Enable = MPU_REGION_ENABLE;
& b; \% H6 h, P( z) V - MPU_InitStruct.BaseAddress = 0x24000000;; o+ x2 P- e' C6 J& F
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;" O* a' o& C& A' e) @& u# A
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;4 J: y; q3 {5 V* p8 Z
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
; c, q% j3 k1 P' ^, r1 { - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
+ \ M/ A- u) Y0 f5 r V- s M - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;* v1 K& R0 n" ~8 g$ S" I; ^8 N4 ]
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;
/ u$ P/ G: e3 p% M - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;% _2 U G. y% B
- MPU_InitStruct.SubRegionDisable = 0x00;7 D/ x/ J- k2 N
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
! S5 L: K1 L1 n! u* K; I - 1 [8 x$ X: V# k; `. j: ~' b
- HAL_MPU_ConfigRegion(&MPU_InitStruct);' U- K" C2 W; j1 o3 v
- #endif ) o5 j* ?% B$ ^. A. v5 a& z# i: N1 A
- $ _- X6 M$ @9 z
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */, [! k V1 c3 ~
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;) k* D' \9 H7 s3 l
- MPU_InitStruct.BaseAddress = 0x60000000;# E2 u* a) H2 t3 F. L4 u! y
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
2 d. Y" K, f( \7 M `0 h" P9 ~" M5 O - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;: p. B0 h5 X# l* _4 k
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
4 Y) |7 Q% t. c - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; 7 @/ j# ^7 p3 y% R( E" H
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
! K: V" a# I' D) K9 G - MPU_InitStruct.Number = MPU_REGION_NUMBER1;+ P% ~5 `3 y' e0 R. G
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
, {$ }' i8 `" ]+ V! u - MPU_InitStruct.SubRegionDisable = 0x00;
) |4 K, |& k* @! C. g U3 T - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
* r( r0 E1 u$ t. O - 6 s( B0 k) c1 w, w2 \- }
- HAL_MPU_ConfigRegion(&MPU_InitStruct);3 m" H" D) E K" F0 H* N
- 9 O: E: N; c* f; R8 g7 H1 M. g! |! ^
- /*使能 MPU */
7 {5 ~/ h! S7 X - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);4 Q- ?6 t8 _/ y( ]: G
- }
, v: k/ H' U, e) R5 {, k6 t+ S - : A3 G7 Z4 I8 p3 r2 r8 \
- /*# M- M+ k; P' T# j
- *********************************************************************************************************
$ j! F Q* n+ n* \ - * 函 数 名: CPU_CACHE_Enable
3 x) K# i$ b( \/ ]" T2 _ - * 功能说明: 使能L1 Cache
1 G$ a* t a" S: X: J7 j1 R4 i - * 形 参: 无
* ^; V1 l- J% _9 ] - * 返 回 值: 无
* P; L: V( `. D! n- v: w - *********************************************************************************************************$ F% i E6 B s
- */7 I3 D% K" N/ d. @: L% T
- static void CPU_CACHE_Enable(void)
% H$ w' O" f4 `1 H- P( w8 d - {0 g4 \* @: H+ i# T6 b
- /* 使能 I-Cache */' ^0 `" x* ?& E
- SCB_EnableICache();/ B; a! d T2 D* ~# m
- , t3 J' ^' l4 o+ ~ D& @
- /* 使能 D-Cache */
1 S5 l% ~0 e- [# r; j. x Q. k - SCB_EnableDCache();8 J, d5 ]& c$ r; V" {1 [0 ~6 F
- }
复制代码 # [( Z- I' `% I& j% N
每10ms调用一次按键处理:1 ^" d# Z, b) O& }
/ \: b5 n) q# Z) A- K! A. w& U按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。- i9 X* F. O; K
( ~% o4 E) ^ h, c/ X
- /*
+ D6 O5 [+ J2 F, Z - *********************************************************************************************************
, b9 ?0 f' M# o n% N2 v" z1 U( H - * 函 数 名: bsp_RunPer10ms; J3 g( Y Q1 a7 P
- * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
( }9 P6 {" m) ^; q# V4 i& c - * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
; A) a9 t, a+ C- |7 f8 ]8 M# s2 u8 D - * 形 参: 无
9 w! h& g. `* g* T! P - * 返 回 值: 无
+ n; y! @$ M A* C' t: } - *********************************************************************************************************
8 ]/ |% T: h" k6 C) Q9 P6 c - */! ]" n: ]* j/ H2 k" S* F: H
- void bsp_RunPer10ms(void)
! c6 H/ S/ ]1 u8 } - {
0 x A4 E' Y$ \( j% W; G+ | - bsp_KeyScan10ms();
, X! l5 b' N6 }" g& G! x0 n - }
复制代码 8 G+ c( o2 `7 e( Z
主功能:) x5 u9 S I1 `
3 W. y1 c P( b主程序实现如下操作:+ X+ N$ M( F1 v- I/ e
Y4 }6 \; G7 v9 e0 ~( u 启动一个自动重装软件定时器,每100ms翻转一次LED2。
$ R) o4 b2 g" A% O9 J 支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
- Z6 L; ?. ^+ k D$ f printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
4 T' D( i6 k; h1 \5 s% D printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);, c; r. Q2 l0 t) e a; B
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");7 p9 _( b; O9 @: u' c
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
, V" D6 `7 R8 P, `( I printf("【Z - 读取前1K,地址自动减少】\r\n");/ V" T2 T& _8 g6 w0 P: f, Q
printf("【X - 读取后1K,地址自动增加】\r\n");( v' m3 q2 o$ u$ {
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");/ h4 C( g4 V0 v. O/ u
printf("其他任意键 - 显示命令提示\r\n");8 ~- p/ c9 I7 z+ \% x. |
- /*
& H3 {7 e3 R1 v9 h/ Q - *********************************************************************************************************
9 b T8 ] i6 h, M - * 函 数 名: DemoSpiFlash
; {" I! F) ~) ~- y - * 功能说明: QSPI读写例程1 l: d* a% J+ D- u, }3 M
- * 形 参:无
* |: o" l: y. v( _) Z; A - * 返 回 值: 无3 J, N! E0 Y5 U
- *********************************************************************************************************9 D0 r/ r6 a. X) M& c) a
- */
* }% P6 z) X2 L: ]5 L; T5 C - void DemoSpiFlash(void)9 O7 M# A1 h0 G! X: ?3 ]6 o- [
- {2 ]- c0 O- s; z& P6 e
- uint8_t cmd;
% w, X, b; Z, n* ` N - uint32_t uiReadPageNo = 0, id;
) @: C3 s5 r2 V+ C! Q- x - H9 p+ q4 t- l6 x2 I
- /* 检测串行Flash OK */6 w7 f* t( }) ~! z( ? h6 d( a
- id = QSPI_ReadID();
) e/ q9 ~% M( |7 l2 ?& Z - printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
) ?5 T" p# m, H7 ] - printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");
7 p' K+ ~; Q1 k7 t9 @) O - ; F6 {# `8 ?* Y( ?+ e8 w
- sfDispMenu(); /* 打印命令提示 */
" g$ m: n+ j% T9 m) n
* B) y/ f1 f5 }0 `2 P% a& u- bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */0 z8 {8 |" `' K; t& u3 X A( o
- while(1)
! ]+ c3 f1 S9 C+ k9 {/ d: b - {5 {! h- I$ h3 V: d
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
& p1 n' U9 W, z% ` - + C& J- B0 I3 v7 ?
- /* 判断软件定时器0是否超时 */3 A9 I* C- z# T# ? R
- if(bsp_CheckTimer(0))4 }8 u5 [ [3 g) ~- L4 L+ r
- {0 S8 G) L6 H0 F' c
- /* 每隔200ms 进来一次 */
1 b8 X4 E; K$ F4 c+ \1 c" T - bsp_LedToggle(2);1 L+ }. Y+ y& O: }1 c- g1 ]3 h
- }
6 |6 `; j9 H9 y0 b. s* i' `9 k - . c9 d( T2 y( e5 [$ a; j5 v# E
- if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */
( K8 ^, a. p; ]5 M9 t - {
3 ]( M/ X0 e& r1 ~7 j - switch (cmd)" U+ ?$ q) w+ b) Q5 s! A
- {
+ U- c2 Y+ [% G8 s) k( L - case '1':% `* o2 M5 d* \& {0 c
- printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
" ?* F2 M& ?) { F6 D - sfReadTest(); /* 读串行Flash数据,并打印出来数据内容 */9 e$ h: J3 f0 W8 z h# U8 N
- break;' b" v4 e5 W( w ~2 S
/ L$ P. C$ @2 O3 A! I; o$ D' O- case '2':
! I3 F* r( X0 o - printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);9 j+ X! g0 v R. L% P
- sfWriteTest(); /* 写串行Flash数据,并打印写入速度 */
0 a* O& ?; F7 d" ?7 }+ K* b - break;: @5 n0 }4 d! ^0 Z! ^; j
- N8 P c& i0 y% v* w8 N4 L+ B; _
- case '3':
@, l7 m! g8 N! G - printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
0 U0 d8 K6 b% T1 A S2 { - sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
6 C% l" { s" |: N7 `5 e$ r2 i8 R - break;" m3 ^1 }9 i) c* _2 L5 [% X# T
- 5 @' b- } Q0 G1 Y" J
- case '4':3 u8 Y* g1 Z/ S9 e: ^. N- I
- printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
% [, L2 u) {7 k/ H5 Z - sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */1 [2 M7 Y; l8 e! w: H8 }7 P6 |
- break;
: |. ?! w, N5 k! e3 f - 2 P7 K$ Y) c) Q- p4 N5 I
- case 'y':
& t1 b$ w4 f- F6 [% a/ d) n - case 'Y':
, f2 o: ]7 f3 i+ p - printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
: t8 B+ ^) ?9 Z4 I - printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
, F9 |7 Q; F5 y" G, i4 L/ M - sfErase(); /* 擦除串行Flash数据,实际上就是写入全0xFF */
5 s" ~& t$ e+ D x C8 i - break;3 X* M$ |/ d- @
$ K/ ^ p n: ~0 N1 x" e- case 'z':1 a% D) d! m/ p% ]
- case 'Z': /* 读取前1K */
b' |5 g4 k4 t$ T1 a- R& n, H - if (uiReadPageNo > 0)
5 [9 E2 `. b( ^* l( ?8 w L1 g( N% u - {- y% ]+ R8 G$ \
- uiReadPageNo--;8 L- x" u2 c* L0 Y2 E; r4 b
- }
1 B8 n- ]& F% G n2 E* t5 K - else7 P4 d0 _/ N( I$ w; y+ `, H
- {
+ z2 J2 s: j$ e9 X& D - printf("已经是最前\r\n");3 Y! R- C& \5 A: n& ^; T( T
- }
& E+ E O! R7 j - sfViewData(uiReadPageNo * 1024);' f6 [- f) |/ |. P Q
- break;
- E/ b; Q+ s6 v2 ~
. k% E- V& E" h! A" ^' o- case 'x':4 x3 q( Z& j8 _9 @2 R7 |
- case 'X': /* 读取后1K */
+ ]1 R! i5 C k7 |0 F' y - if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)7 G6 [" h% @3 Z: t) Q! E( b. Q
- {
' \; \. R# a% ^; j - uiReadPageNo++;1 f- Z, O, g$ u- ]7 a% i A. t
- }
~' S- H# P+ v- ~ - else
U6 m/ s$ ~# C6 ?3 M$ C - {# A, s3 ?5 S$ g3 b* o% a
- printf("已经是最后\r\n");
1 H6 }: Y/ a5 z4 q* j2 K - }" e7 y1 K3 \) {8 Z I& s
- sfViewData(uiReadPageNo * 1024);# @7 ?" c1 I: @. G
- break;; B8 S4 x1 p+ ~$ X& \; L
7 I; W; s) S$ s$ ]5 E6 `# p* J0 J- default:9 T2 t. U0 O! k- P. V# U
- sfDispMenu(); /* 无效命令,重新打印命令提示 */
: b: U) O6 T$ D: `" e. \ w - break;
9 T/ q: _2 i( X- j* j: z# W - }: _3 b6 V: c9 f% |& r9 U- n/ H2 W* A
- }/ F7 m; U4 Q$ a8 k+ p4 a
- }7 a7 f2 P5 [. L5 m: m9 V4 _+ c# O2 H
- }
复制代码
5 M+ O1 t+ P: ]% W' |79.9 实验例程说明(IAR): a. {2 X/ |9 @7 Q8 C
配套例子:+ y; ]) Y( a A$ `3 `
' O; c# m) ?7 X$ t. _1 G
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)3 O/ q! j+ f% N* S
6 O7 X( P- W6 }& LV7-059_QSPI读写例程,查询方式
3 _: K$ q5 ~' _ r0 L: b B$ N+ v( C1 |& _+ i8 {- ?8 K E
实验目的:5 U* p6 s" @( ~8 M
4 n( O$ e7 @% q9 j
学习QSPI Flash的读写测试例程
4 {) {, V9 G; u1 x4 V实验操作:# Y9 c/ i8 v0 Q) c
- X3 b5 b: e& ~6 k; \3 H
支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
0 U6 ^* T3 _4 [& o0 jprintf("请选择操作命令:\r\n");
- Z/ F6 }: ~7 j. X dprintf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);6 G# o; h4 \' ~
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);0 @5 Z; ]; y' m2 H* ~
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
6 C& o8 G( \* T9 uprintf("【4 - 读整个串行Flash, 测试读速度】\r\n");
4 w/ N$ V2 ]- T1 [8 L t4 ?printf("【Z - 读取前1K,地址自动减少】\r\n");1 p9 I1 d$ Z5 ~& e. W- }4 r) B
printf("【X - 读取后1K,地址自动增加】\r\n");
% T* E6 K; j6 B, O) yprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
- ]) @4 U _# s& D# Hprintf("其他任意键 - 显示命令提示\r\n");
8 Z6 ?! E* ]$ C4 j" C上电后串口打印的信息:
# _6 K8 q! {2 Y' A+ X- t! w/ h% f) P" [" y" C3 p5 I
波特率 115200,数据位 8,奇偶校验位无,停止位 1。2 c5 R+ l/ y1 T0 G4 v: Q' S8 t# v
+ V$ j1 p. r; U4 e Y0 s6 s" C q2 z/ ?" f O1 c
( v' Q( W% m9 \5 k+ O
程序设计:4 E1 N5 \* c! z+ h" f5 ]( m
* x+ o" W4 U) S! t
系统栈大小分配:
' K) N4 F) _7 g& x. D: L
- ?. |" C# o8 P
/ K: U7 O, j: a1 b+ {2 Q. z( i4 G# Q. I! }9 T' Y
RAM空间用的DTCM:* u, g2 s, ]' l% j* I( I
8 O! _5 U3 s( @3 W1 a% M7 v, |5 o$ v
) V' J& h7 \& }) p/ _4 Y" O& U6 i! e# }& y- h# e5 Q3 [/ i
硬件外设初始化
! Z9 R7 B" ~/ o7 `
4 V$ O( ?3 J! P! d# ]$ x' I$ I9 ?硬件外设的初始化是在 bsp.c 文件实现:
6 T" F# R: y5 B, `* F
# S0 c- W2 M* n6 l' b- /*
/ p" G: n( b+ F& \3 r% k" e- j0 p - *********************************************************************************************************9 V. X, C, w' p$ ?5 R
- * 函 数 名: bsp_Init
4 W5 |) b! F3 d8 I - * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次: [5 l# M9 t7 \* M' L* x
- * 形 参:无
4 B4 c- t# Z6 \! V, n* F" E! c - * 返 回 值: 无) Y3 n. _( a: ]8 i3 I# c/ H- X8 w! K
- *********************************************************************************************************$ X; l7 y5 V- y5 C
- */) }) m9 j% G2 {% i
- void bsp_Init(void)5 N% j5 |: e/ u( t; r" Z. M5 R* `- Q
- {
3 F. W+ D' d" O5 q+ m - /* 配置MPU */" l0 ] {* e3 Y6 r- [! L
- MPU_Config();
$ n2 m4 u; i# S - V7 I. }# C! o! `- ]9 u- f* ^
- /* 使能L1 Cache */' a0 ? h* w6 u
- CPU_CACHE_Enable();
7 h' O' q# H: n5 ]6 k
& u, C! c( w+ P" h+ e4 v- /* * b; n) q/ J3 i3 l- v: ?
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:" Y6 K7 I) p( m1 }- {! t% G
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
+ p3 L3 a3 C. ?4 y* E - - 设置NVIV优先级分组为4。
% g E! G( @% A - */
- A( z1 n' s8 m* R, q+ B/ _ - HAL_Init();1 W6 k/ U) J$ B# |& A' t
- 3 s8 E6 @# I$ R: h2 e
- /*
& }6 n6 F, D6 W9 j* n. {& K/ | - 配置系统时钟到400MHz
) Y: V. B3 R2 w+ | - - 切换使用HSE。7 z+ E# e1 E" R$ [8 ^8 T
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
% ^9 c9 I4 |1 }$ u4 q9 x - */
- V. O9 K' f- w, P9 z - SystemClock_Config();/ o+ e/ D+ o, `% A5 ]% B4 ^
- ' S. R* B9 _& X% M b' O
- /*
3 f v! B4 }' M3 e i i - Event Recorder:$ U( D1 I' @2 w
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
, {9 _. ?& M+ M, }6 `+ E) N - - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
* e( L7 C9 ^; o0 i+ u - */
) n; p8 X# f8 f9 x2 U - #if Enable_EventRecorder == 1 % y. D. w4 v; k# V# b0 f
- /* 初始化EventRecorder并开启 */' z% b$ C) N! A% M$ r
- EventRecorderInitialize(EventRecordAll, 1U);/ j h: P, O( o# c3 n/ \6 J
- EventRecorderStart();" E5 J9 |* w, n+ D! }9 f
- #endif5 l& B$ o% T5 c' V
' I* {% g& c2 k$ y8 j- bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ * T9 j' U; e! i6 r' b& h, g
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */2 A4 y3 b+ w- _8 \
- bsp_InitTimer(); /* 初始化滴答定时器 */0 y# V& d! w3 h! w0 V' Y$ C
- bsp_InitLPUart(); /* 初始化串口 */
- t4 v P8 ~# _( p& E+ E - bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
" v' [ h* I/ d4 M) ` - bsp_InitLed(); /* 初始化LED */
- T V6 K3 X8 f% a6 [ - bsp_InitExtSDRAM(); /* 初始化SDRAM */( T2 b$ k5 L! f, e0 X, f/ f
- ( M5 }6 R8 }- f! M! P, X
- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */+ \- D! ]0 v: o8 R$ Y2 T
- bsp_InitQSPI_W25Q256(); /* 配置SPI总线 */}
复制代码
. [3 ]7 V% @' ]; t* j# U* L MPU配置和Cache配置:
, w2 ^' f" P# _, B0 {0 z: y# B5 q, ]* Z5 W6 t e/ \( ]0 X
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。% M' `; C( I; a* V6 `& c& ?
+ P1 K! Q$ t2 R3 B( U1 i0 l) c5 i
- /*; c2 E+ }2 F: `4 l: B# w8 a4 d
- *********************************************************************************************************
1 S& U5 Y8 c5 _' {/ L - * 函 数 名: MPU_Config: w: V( O* p" f$ @# }
- * 功能说明: 配置MPU' K. {% N& P, O# H7 r& m1 M
- * 形 参: 无. D: ^7 `5 V4 W, R v. E( f/ B8 Q
- * 返 回 值: 无& M& V" X. H, `4 u* d
- *********************************************************************************************************
. ?/ [% F$ W7 Z2 o- ]5 a- ]4 Z% n3 h - */5 P! G8 ~+ c* }
- static void MPU_Config( void )
, V+ s8 @, l- I) A% ` - {
( B# V; u2 f$ z+ E - MPU_Region_InitTypeDef MPU_InitStruct;0 ~( P; \' t: o, ]
& z9 U- r# W; a a- /* 禁止 MPU */$ w1 W: l4 F3 @/ E! f W
- HAL_MPU_Disable();
/ ^6 C" p/ P2 w2 ~6 i: v
# {: W7 m9 i9 g" P, _1 j7 d# i- #if 0, b- D9 B9 n$ h O0 h
- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
5 u; N3 c6 e% M( D/ b/ y: m - MPU_InitStruct.Enable = MPU_REGION_ENABLE;, v* {+ V% I2 x' G% c) T' ~7 K, t
- MPU_InitStruct.BaseAddress = 0x24000000;0 N: A s( R* X+ F; s$ h% n
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
, r- d: d9 Z3 b5 q% T% Y - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
$ ^& _. i L/ ]" X& ^2 O - MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
0 F5 O( I; \4 k- O% k6 I$ D, S - MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
- ~- V* N* S* m, l - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;% M/ `- W# ^# I( H& \
- MPU_InitStruct.Number = MPU_REGION_NUMBER0; G* s! I* V* s
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;1 q- Y3 X* C2 I
- MPU_InitStruct.SubRegionDisable = 0x00;
/ j( s* O1 W5 V - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;. r ]" d& c, b5 V( g
- " c( T! M4 R4 t5 j! G" W$ ^0 |
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
; K2 r% F" m1 P - c( d9 b, j$ m/ W2 z
- #else8 w& @% j+ O1 v2 ]8 V
- /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */* ?# l/ S) S$ Q* p* O# e: }
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
6 p( {9 c5 T+ T3 A! b2 Q# L - MPU_InitStruct.BaseAddress = 0x24000000; q4 X/ e, o+ p% D+ Z
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;1 v% x# I7 z/ u0 A+ A( @; D
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
0 v: W$ \3 I) r. {$ l0 | - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
. E H) O `, s - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;6 r. R; D) F( O( N+ f3 _
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
3 Q! I/ D: L3 `/ m* l - MPU_InitStruct.Number = MPU_REGION_NUMBER0;2 J! h+ U9 O" Y; H# Z5 F+ t# Y
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
2 G9 I* E: I6 S8 V, ? - MPU_InitStruct.SubRegionDisable = 0x00;
4 b9 J+ Q* t, z5 S( g. ~ - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
6 c0 {+ U, U% H
0 ~% t1 ^/ t. c: C5 j7 K3 h7 l- HAL_MPU_ConfigRegion(&MPU_InitStruct);6 g2 g! q6 r# @, f
- #endif * P1 i! _: P: G0 B
- + U+ r- {2 |# ?
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */% L) Y) {* X# C
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
7 }) {* g7 `4 h, j0 r - MPU_InitStruct.BaseAddress = 0x60000000;
! b1 R5 k5 Z0 p$ B - MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB; 9 Z2 Y* i+ \! U- r+ l8 v+ F
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;( g% }6 F* @! f, A; q' M( \
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
( W/ X7 c$ Z8 y' H. w" ~! F# n - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; % V& ~- v5 e4 v1 ?* B9 j
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;" b- [& @, r4 K: e- e. w' h, {
- MPU_InitStruct.Number = MPU_REGION_NUMBER1;
, m7 g0 u! v% _. C( d$ j - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
% [+ I2 X' m$ c9 `* s2 p - MPU_InitStruct.SubRegionDisable = 0x00;) n) ^4 b5 c" W! L
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
% n2 s: M% ]3 h8 Y9 Z4 z; { - i- J) S4 W4 X1 l" o7 P
- HAL_MPU_ConfigRegion(&MPU_InitStruct); t0 w* z" g7 k1 D! i0 a
- o, @+ S6 [1 ~6 r/ B! b
- /*使能 MPU */
0 @* A9 F6 h, C, Y" ] - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);+ C/ Q0 w( P o# S& {
- }. L8 s6 C8 s s- a6 N. ^2 {
- % y2 Z' @' \0 ^
- /*! I6 q8 Y- d: @7 y
- ********************************************************************************************************** ?6 y E3 f, g4 v- ?& F; d
- * 函 数 名: CPU_CACHE_Enable& V7 ]4 r# y# [5 M2 c5 X9 a
- * 功能说明: 使能L1 Cache3 \0 s! b" W& k; w
- * 形 参: 无
( a, A% C H) b$ V! ` - * 返 回 值: 无
8 R8 q' \" \; z% p0 k- J - *********************************************************************************************************# m/ g' {. }' r
- */
; N n1 b- t3 R0 G - static void CPU_CACHE_Enable(void)' N/ E" x3 o8 C$ u- _. k) ?7 B
- {: _8 W R1 E- a0 \* @/ w
- /* 使能 I-Cache */0 `' W0 r1 S/ E1 V5 {! f
- SCB_EnableICache();9 A8 L1 z( R7 }/ _. n6 s: U5 i
- - O) U* x' X- o; A& X' y
- /* 使能 D-Cache */- e( {2 Y$ M. d( v# K
- SCB_EnableDCache();
" c v1 f: w7 G - }
复制代码
: L+ y% O! f& h1 D+ h$ q 每10ms调用一次按键处理:' ]% |- F4 x: ^3 P. Z
" b) |8 g; Z' g' i, L3 k按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。4 f; @! l t- a" q
+ l1 |) a; i. S7 D- ?8 J+ G- /*
$ E- W7 X) r& d - *********************************************************************************************************2 _! [. u# I/ T d9 K# L
- * 函 数 名: bsp_RunPer10ms. s% {: |2 @( Q1 Q* n% W0 Q( W0 ~
- * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
' ~8 x! D6 [$ b* G - * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
0 H) u; c9 Z! F - * 形 参: 无
# M& y( Z, l7 p( e$ B3 {1 \: n5 l9 B - * 返 回 值: 无) |( l* Y( \- |
- *********************************************************************************************************
\: Y3 q# d% T% u6 j - */) i( h9 U; I. x# b- I$ E* _% ^0 S1 C
- void bsp_RunPer10ms(void)9 d, X1 }' @* ~, W
- {! {! U9 I/ J2 `
- bsp_KeyScan10ms();
' W7 f8 _! b% Q1 \ - }
复制代码 $ w6 E: v, H! d3 i5 e
主功能:7 a5 f/ P/ I6 V! u+ Q
/ k2 h- I$ o; k- Y- |. U! I" }主程序实现如下操作:% C" M& t3 ^" o2 }" [& t) x
; p+ F+ V) p/ z# R C
启动一个自动重装软件定时器,每100ms翻转一次LED2。' Z+ h3 T0 b9 v8 {' \$ p* k
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可4 C/ ?+ k8 Q- [9 X
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
3 p8 [- E% e/ [ ^ ` printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
! e* @8 q' P8 j printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n"); S" I$ ~7 Q2 I1 n1 ?0 D7 ~
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
0 T$ n% s7 _5 x# E printf("【Z - 读取前1K,地址自动减少】\r\n");
" S2 O2 Q6 L# u j D printf("【X - 读取后1K,地址自动增加】\r\n");
8 J; G0 m7 X D8 s; a0 r) R printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
$ ]- r! i2 \ d9 P printf("其他任意键 - 显示命令提示\r\n");6 v" q M' w6 l1 Y# R f) F# X, o
- /* R- a" h. K0 [) h9 k% p
- *********************************************************************************************************
# ~% S( @0 m" n) V. o( g, q - * 函 数 名: DemoSpiFlash
" Z4 x8 f" |7 _/ A {6 P6 M# X# T+ r9 B - * 功能说明: QSPI读写例程
/ N! f) M- M$ o3 G. V8 A - * 形 参:无: p8 p5 v' F6 I
- * 返 回 值: 无
& c# U/ U9 ^5 X/ r) y0 j - ********************************************************************************************************** r: f) w2 u" k0 ~
- */$ K! k- o1 {( Y" L6 n
- void DemoSpiFlash(void)
{' m. z; l( j3 S2 e) V - {; e Y: Y7 Q% U+ K) W. z
- uint8_t cmd;
& A- i7 d1 x3 i) Z+ S3 Z% b - uint32_t uiReadPageNo = 0, id;. N' }9 s G& h( ?/ q& ?: ?
4 O! G6 v0 W7 ]6 v- /* 检测串行Flash OK */
) r" s- \9 p5 ?+ t* o - id = QSPI_ReadID();
; k6 B8 B% a9 @" P, i, j: T( G - printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);+ u5 r# g6 V6 ^5 r r
- printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");) S q8 _. [0 J/ k& L; L0 \- Q8 {
- ; z' D' k# _$ O3 z0 I! P4 I) R; _
- sfDispMenu(); /* 打印命令提示 */5 o; e" M$ x2 Q! \' s, C4 h! E
- $ [3 K9 @ b" t) j
- bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */; ?6 y" x1 X: z- m. d8 i6 [+ }2 C" p
- while(1)
9 z9 s0 {% r7 i; t$ [% T* C - {- L- @1 l( P% ?0 K
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
8 H2 D* H0 ?6 P - . y( X2 z; Z% h& m5 {& ]
- /* 判断软件定时器0是否超时 */" }# A; d0 s$ b, c
- if(bsp_CheckTimer(0))
, `, d8 X! C1 i1 l4 d z - {# f' z/ K, I7 Q1 _6 {
- /* 每隔200ms 进来一次 */ ! ^. T1 M2 u+ z" s
- bsp_LedToggle(2);
- J! c, d; _8 z( ? - }
9 x* `: p: _' L/ H7 k* X3 c6 J - ( o! b2 z- J" | T
- if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */
' Q. t$ U, p, {6 J2 _) C: A - {
$ W! n9 f; f2 O4 J/ \ - switch (cmd)
$ V7 W$ L# N/ W+ M# A - {
8 F c k7 ]( L' ?. [; }5 A. S& t - case '1':
# ?$ `. X( Y) h' F' H( A - printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);( M/ L- R3 ~: t% Y9 Q
- sfReadTest(); /* 读串行Flash数据,并打印出来数据内容 */
) H, p8 O: K+ h8 n* `" s" f5 X- u - break;
4 L7 d; _1 b/ B2 U7 A3 t - 2 }0 H8 a8 W" }3 U1 O& H3 n
- case '2':# T9 p, l; H9 G5 M
- printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
$ D# d8 j( m$ n5 g: o - sfWriteTest(); /* 写串行Flash数据,并打印写入速度 */- m9 }/ B9 F( o: F: X3 ?5 \0 \- O
- break;
8 P2 A! H7 K7 U7 v8 o* t
p8 {2 R- q! \: y- case '3':
) X2 H. C: N( j. |5 p! l - printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");9 H. `6 C' w$ q1 X7 O/ z: Y- |3 g
- sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */* t: O8 D* a6 E2 A& k( ^" H" L
- break;
! r: t+ o2 Z& L8 K
, q2 e5 w4 Z7 \1 p- p. p$ `- case '4':
1 `, j1 E, z2 ^: B+ M - printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
" @' u1 ?9 e! i - sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- s3 _* K$ a# }/ o- y3 n+ H% K! ?
- break;4 a: _; l( l/ }( Q4 d
- 0 H# F7 z# E9 y0 ]4 z
- case 'y':$ c) O" F( q6 E. u% w& g- w
- case 'Y':$ e0 N" k8 G. K u$ W& t
- printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
( f+ H; k8 C& v; B; U - printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
9 a( r+ N9 ~5 x2 G2 w - sfErase(); /* 擦除串行Flash数据,实际上就是写入全0xFF */2 J) X9 q, e" N( q8 q7 \; F1 \
- break;* G: Q# b" M, p" g
+ a; I6 a7 H) J& x, b( i; b l- case 'z':
) ]$ C( J8 q8 L0 }- _( K; B8 S - case 'Z': /* 读取前1K */
" X0 a( C1 e/ M - if (uiReadPageNo > 0)
7 P5 g& Q, f7 Z6 O; @, q - {& ^) P- D! ~6 J1 @1 c
- uiReadPageNo--;# ?7 G; ^! ~4 [5 U# E. w8 t4 E( C
- }8 @) F1 h1 a9 }! \- b
- else, I' M2 J1 _/ ]0 o! |) Q9 E
- {& l4 t! [+ t! p0 f! ?& E- e- W! j
- printf("已经是最前\r\n");
0 E) R" E u( K( Y; Z! h - }( a* U% P, p% U# A# T7 F' a( [/ ?
- sfViewData(uiReadPageNo * 1024);
$ o* I5 l' r8 G3 } - break;# u3 B) p( f q' h8 t
5 u* E/ d+ k& \# a2 e0 {# r- case 'x':( P; d2 C1 n8 r% A+ L8 H) G5 Y
- case 'X': /* 读取后1K */- L1 p/ d; X8 n) x7 s) Y+ n9 @
- if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)% n: p/ A/ a+ U% t# t
- {
/ E% p6 K6 R2 {1 O - uiReadPageNo++;
b1 L5 [5 @8 {6 n! m& B0 v6 y - }/ n8 f- V2 {+ p( {$ X
- else v) }* V" F8 K$ }7 v
- {' P( \% w( A Q/ F1 D
- printf("已经是最后\r\n");1 _+ X8 f! Q" ?- w3 k. p1 R% e, @0 R
- }( i4 ^* d3 c0 V& e9 K' b
- sfViewData(uiReadPageNo * 1024);2 _! w ^1 m; ?8 r* x
- break;
# Z% M# S3 q$ m l8 q- O
$ y6 n# X6 X, x8 p. V- }$ K4 k- default:
. t* z+ F6 \% a8 | w) t, V$ @ - sfDispMenu(); /* 无效命令,重新打印命令提示 */
% G! O/ X- m, Z" P M( o3 I1 E; g - break;. a0 }' Y: p/ I3 z) J
- }
9 r# L. R0 s) e. e& ~5 L" m - }
9 y" L4 H6 o- a& ~$ v9 n" \. q - }% L; z2 ` w% e2 m c6 l4 o
- }
复制代码 & @8 y0 T! \* e6 v* @
79.10 总结5 a/ |3 ]; \7 G* p
本章节就为大家讲解这么多,实际应用中根据需要选择DMA和查询方式。
$ q' a x* p# R: a& b5 W
) ?, H+ w1 V# |, p
* q# s* y5 _; ~# ]1 @7 j |