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

【经验分享】STM32H7的QSPI总线应用之驱动W25QXX(支持查询和MDMA)

[复制链接]
STMCU小助手 发布时间:2021-11-4 21:24
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
d158c855c3630d64e0d72336ffa1a440.png

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
43d24f69b56108e41724030a699d04ea.png
. }/ 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
d211eea6c41b7da28846a28c61bfebf4.png
/ 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
2da620aeaf02ba8b3035e0b864e140b2.png
( 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
77c20d25330bfc517d68b73c7751ddf3.png

, T" X5 }* I; v* L5 o( X
( P% R  t- ~  ^5 g
ee48eed7fa217e118947120ca34f5263.png
7 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
  1. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
      _# j& B- K# @" i9 b
  2. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  
    " u- ?+ M% I! b1 L
  3. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */ 4 ]. g* X1 l2 g; P& ]5 M* C
  4. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
    # `5 f1 d% @* @* k9 p( J. q; \
  5. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    : @+ N, B: }) ?
  6. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */
    7 J8 e6 u# O& P* W$ x: d* c8 ?
  7. #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
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        ' x/ }% e$ h/ {* B! U
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    ! n; r# r8 v; B' o$ ^
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    6 L$ J+ E; Q- D, x" @
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    ) [' o2 c9 z% z8 h  ~/ H" {

  5. & D) [0 B$ S% g% _8 I. [
  6.     for (i = 0; i < _usSize; i++)
    . L( }( ~& b3 q1 x
  7.     {! F6 Q7 P% p4 ]& b/ C& d2 Z
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    / P  y2 Y% s2 ~6 y# Q" m7 T
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   ' i5 u/ W  B/ l0 e3 ]# B) }  x
  10.     }
复制代码

( 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 ?
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};) M% o# I3 G0 p
  2. 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( _
56240b94a12eb9a067e63db9803b13b7.png

# 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 ^
e21b7448e777b6e8e376fb71ea3e75ee.png
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
9ab6f9e23e8cfb4e497e67913dda1d7e.png
+ ]  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
0edab8ae6a09033608ede0892279b72a.png

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
  1. /*. m* o) `  O& q# }- D& h) n, H" R+ B# }9 Z
  2. *********************************************************************************************************( S; u4 H9 J$ D
  3. *    函 数 名: bsp_InitQSPI_W25Q256
    " k. G5 [  F7 o8 U9 j9 s
  4. *    功能说明: QSPI Flash硬件初始化,配置基本参数2 f8 _: S/ y. ^0 Y
  5. *    形    参: 无
    % R" q4 B9 F% c! }! m$ L
  6. *    返 回 值: 无
    6 J! Z7 Y# b3 O4 ~; m
  7. *********************************************************************************************************$ D5 |& a" D$ z* \
  8. */6 W& s, C  O8 u8 ^
  9. void bsp_InitQSPI_W25Q256(void)3 q3 s# x8 o: O$ }
  10. {
    # p' |) v/ q7 L7 |; A
  11.     /* 复位QSPI */3 d, E& N' W% I5 j* R
  12.     QSPIHandle.Instance = QUADSPI;
    0 A# Y( N8 [$ r1 f( w  G2 `/ t
  13.     if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)4 j2 z4 G) f  Q+ M$ k: E+ G4 {
  14.     {
    ( E' E7 s8 o" j7 N; F- u
  15.         Error_Handler(__FILE__, __LINE__);6 v- Z2 [; c2 r. O- D  ^' u
  16.     }
    ) R( m$ }! D6 l

  17. 2 N# E9 c% H0 T0 p
  18.     /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */, b4 A' E) v* e
  19.     QSPIHandle.Init.ClockPrescaler  = 1;  4 p3 E5 |6 V( J& r3 W6 ^: q
  20. 8 b2 `$ w) H7 K
  21.     /* 设置FIFO阀值,范围1 - 32 */7 R7 j2 ~0 v" d2 h! s  z! [5 h; t
  22.     QSPIHandle.Init.FifoThreshold   = 32; $ U) d9 l" r+ L5 l) T; f& @0 u7 _

  23. ; d: d/ W& w  w0 ~; c  T3 t
  24.     /* 4 [5 L; L* C1 G! X8 f& m/ p
  25.         QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。" [. |9 h7 Z7 o& A2 Y
  26.         在外部信号延迟时,这有利于推迟数据采样。
      T; q$ A* M6 m& `6 {9 l
  27.     */
    1 |4 [* I1 ]: Z, E3 r+ G% o
  28.     QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
    0 d2 W; Q4 P1 p/ ]) I/ a
  29. / C! q, N$ c, i& x" j9 J" U( G
  30.     /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */  ]8 K5 e3 g% w7 ]2 D# C: w0 H
  31.     //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。1 _, {3 C7 P% A( q+ N& Q
  32.     QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE;
    + J# V3 T5 Z0 R" q. U
  33.   z# Q2 [) F* X8 Y$ h- M( [
  34.     /* 命令之间的CS片选至少保持2个时钟周期的高电平 */( C6 G& Y: m) z; _1 U
  35.     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
    + t8 k2 R# ^! F

  36. 1 P- V' K( f) }0 d, Y
  37.     /*
    : J# _& J( ]  R5 ]
  38.        MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平, K7 L+ Y" ^7 m6 T; f
  39.        MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平. I8 O- ~! S5 y" W; }/ V. M( }
  40.     */
    4 D0 w/ I7 J: L
  41.     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
    ! r; u9 s$ x0 E3 H; E- E

  42. 6 M, z* I& U' {& P" C
  43.     /* QSPI有两个BANK,这里使用的BANK1 *// H3 N: }) a5 E! C2 w0 i0 V
  44.     QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_1;
    + X' l: A# i2 {* R/ {: t6 t
  45. 1 c. B/ Z/ M+ ^0 S) Y
  46.     /* V7开发板仅使用了BANK1,这里是禁止双BANK */) U* E" w) r! j$ e( [7 F2 x
  47.     QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
    . {4 f$ U) e( J0 `8 D
  48. , A) E/ `: z- _4 i1 n$ X
  49.     /* 初始化配置QSPI */
    % E6 V% M9 R+ Y+ a
  50.     if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)0 F, s8 _% q$ S" S/ w0 d
  51.     {/ q9 P! h4 e8 X" Y3 L
  52.         Error_Handler(__FILE__, __LINE__);
    + M! M; s- }. x7 R2 g
  53.     }    ' U4 c" O: R: \4 g5 E
  54. }
复制代码
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
  1. /*' S9 w% q9 A" B$ G
  2. *********************************************************************************************************5 A) h7 q* L/ \) c4 B' Q. }
  3. *    函 数 名: QSPI_ReadBuffer
    ) r: F2 K1 R8 @8 K5 i
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    5 L2 }5 M! u0 F+ i- L
  5. *    形    参: _pBuf : 数据源缓冲区。5 F: j  A+ M$ I$ \7 m! h
  6. *              _uiReadAddr :起始地址。) F6 q- J' A, [# G
  7. *              _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。
    " U7 h2 H1 a, q& Q# K( }' ~* e
  8. *    返 回 值: 无
    / P. A; v: I* `
  9. *********************************************************************************************************- ~; K- t& A; ~7 K8 s3 B$ H% e; u& O
  10. */
    * s: x6 W5 l' }) P4 y3 T+ Y3 {  g
  11. void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)+ U( q1 K$ e; I# P$ W
  12. {
    ) B7 G; [  z+ O6 H( m0 g0 w

  13. 7 x$ S$ n% |( }1 @
  14.     QSPI_CommandTypeDef sCommand = {0};
    # `7 |7 ^- `0 I& i

  15. & R" v& ~( M6 ]
  16. ' O) [7 b% _5 e' L' |4 }4 Y  R+ S
  17.     /* 基本配置 */
    & X6 x* ?* o7 S6 o* J
  18.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;        /* 1线方式发送指令 */; S: r' z1 f: g1 S6 J( X1 N
  19.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;          /* 32位地址 */% l4 z( y' k4 s7 @- D
  20.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;      /* 无交替字节 */& b5 `- ]6 ]9 h  v
  21.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;          /* W25Q256JV不支持DDR */' w/ a. l( h& D' N/ `) y% r
  22.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;      /* DDR模式,数据输出延迟 */! z* y( Z0 {: c% w* w% {
  23.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;        /* 每次传输要发指令 */    $ p8 W# D% k/ y  r* N
  24. 8 T$ Q+ N- l9 J7 _. F
  25.     /* 读取数据 */
    6 @+ r! i& w+ ]- c0 w2 z+ ^* D
  26.     sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */6 P1 ?6 @( n3 ^
  27.     sCommand.DummyCycles = 6;                    /* 空周期 */
    4 V* S. ]6 J0 ^" X
  28.     sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */
    , W3 y& v9 `% M* t4 c. [
  29.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据 */ . T- ~3 }7 w/ m
  30.     sCommand.NbData      = _uiSize;              /* 读取的数据大小 */
    8 m8 L) u$ p6 f5 [: {! B2 J
  31.     sCommand.Address     = _uiReadAddr;          /* 读取数据的起始地址 */
    6 f; a( o2 _. M  O& a6 n
  32. 9 V) s$ q7 r% h) _  A! f2 a  |
  33.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)2 Z7 r7 m' I9 f1 U: h. I
  34.     {& T6 }- D* C$ ?3 `7 t+ K
  35.         Error_Handler(__FILE__, __LINE__);
    " n8 x1 ~8 M7 g9 c& G  o
  36.     }
    # U% n# e: k" ?/ o  N6 q- W

  37. + ]# @: e: F4 V& U- G$ o
  38.     /* 读取 */) A6 C/ q8 F! P8 `7 o" F
  39.     if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    1 ]# k7 Z% i. O+ [" ~: K
  40.     {
    8 _) a. X" ^9 ]
  41.         Error_Handler(__FILE__, __LINE__);
    5 u' Y) A/ U6 ]' \
  42.     }    ! |" {! G; K( {  g
  43. }
复制代码
* E* l  m& |4 l/ Z/ r  P# o
此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
" S2 j, g# k! I/ X- r. G5 J) H5 j: y  }
6c1787c10d01bbaf5602747a947053b6.png

" 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
  1. /*; h2 t$ e3 C2 j1 E+ ~
  2. *********************************************************************************************************
    , w+ Y8 h; G& v7 v
  3. *    函 数 名: QSPI_WriteBuffer% q' a( {  i7 ~. i0 O6 g: \
  4. *    功能说明: 页编程,页大小256字节,任意页都可以写入
    : m; \0 s* D% b! D. k
  5. *    形    参: _pBuf : 数据源缓冲区;! U8 S  V2 ~) c5 [0 l
  6. *              _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。: W- e, S( m" f& `
  7. *              _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。2 y9 }5 C$ D' f/ {! p, A* U0 x* u
  8. *    返 回 值: 1:成功, 0:失败7 s: w6 l2 Y0 ~7 S9 R  R; I
  9. *********************************************************************************************************
    # Z7 ~! j& j9 S5 {" ~; _  s7 v
  10. */, M% y. ~0 R4 E. n
  11. uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)8 i, T. c9 F- l3 q" [; u1 [2 c
  12. {# r$ h% i7 M! n8 f
  13.     QSPI_CommandTypeDef sCommand={0};% U8 q1 g5 V* x7 D% \9 Q' J7 o
  14. / Z+ m3 k2 S: l; X
  15.     /* 写使能 */' j$ s/ M3 ^; Y
  16.     QSPI_WriteEnable(&QSPIHandle);    $ S$ W$ ^: O( d8 T

  17. ) y8 Z* R1 o& U* b5 V5 y: T/ z
  18.     /* 基本配置 */
    ' G% l# q! t  w5 n8 _' W
  19.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */0 l' P4 D' O! c% h9 F
  20.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    ( |; {9 _+ q$ P5 x$ |
  21.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */3 x" h9 Z! n1 P# J  ?# }
  22.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    1 G* L7 V9 ?  y' A* ]8 L: G8 F
  23.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */; d  i' [* O# }2 {" n+ T
  24.     sCommand.SIOOMode          = QSPI_SIOO_INST_ONLY_FIRST_CMD;     /* 仅发送一次命令 */   
    : O9 }9 _, o1 P- u! q
  25. , v8 Z' K- J3 u% ?! J7 Q
  26.     /* 写序列配置 */
    / D5 r9 h; [' O6 y9 o& {  Y- m- g
  27.     sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */
    " |! [! o, i# b1 {3 q$ Z. e1 G, }# r
  28.     sCommand.DummyCycles = 0;                    /* 不需要空周期 */4 f- N/ u7 g5 }
  29.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 4线地址方式 */# O: ?+ [( |% o4 M: n; R5 |
  30.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据方式 */0 }! w( [1 P& y4 b1 P' L  x
  31.     sCommand.NbData      = _usWriteSize;         /* 写数据大小 */   
    ( ^  U6 E: v: ~4 U
  32.     sCommand.Address     = _uiWriteAddr;         /* 写入地址 */! G- G0 p: Y9 Z9 R2 u
  33. - A7 D: G' Q: z- B1 [
  34.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    / Q+ B4 I0 Q8 x2 H
  35.     {( M7 W- m3 m) Y: e6 Q6 N0 A7 x
  36.         //return 0;9 N/ Q0 y  M1 A2 x$ `: H: ~0 x
  37.         Error_Handler(__FILE__, __LINE__);2 R  T  ~6 Z# ^. D1 B7 D+ S& M
  38.     }+ i% V% M) }% S

  39. : {- L- Q, z, S; f2 j
  40.     /* 启动传输 */1 z: \  \) d- r4 R  _2 k, J0 }' u7 H
  41.     if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    0 j$ @5 G3 M4 A* t' ^% k5 n( x. s
  42.     {
    # E4 ?7 R0 o( e% t5 k
  43.         //return 0;
    1 G5 k9 z1 M+ ~" _, H9 m6 g
  44.         Error_Handler(__FILE__, __LINE__);
    : v% `  K$ z$ |1 Q- \$ p: T4 W
  45. : i- ?4 i9 N: p  X2 T
  46.     }
    + y& t9 K) X- E; Y3 F5 X5 C1 e+ ~

  47. 4 y! p$ j8 t9 ]  Z( i2 z, ]$ c
  48.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    ) r! h( n; c% |: ^+ R5 c
  49. 9 V2 j% P" {3 `2 j! w
  50.     return 1;4 J) f$ |. P6 X* p) G9 |6 U
  51. }
复制代码
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
  1. /*" h+ r3 i% Y* W# k! I
  2. *********************************************************************************************************
    " U% |3 v  a: I
  3. *    函 数 名: QSPI_EraseSector- C5 p" [; ^6 u" ^  z# O8 Y
  4. *    功能说明: 擦除指定的扇区,扇区大小4KB
    ' E( h( |3 w, A2 ?- I$ v
  5. *    形    参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,
    ! h0 l1 N2 D! U: S3 X) m" ^+ |) C% f
  6. *    返 回 值: 无
      y( T" T+ ]! T. ?8 T8 H
  7. *********************************************************************************************************
    2 Q- F( \  |1 r! Z* K/ R
  8. */
    0 F; I1 `6 \2 [" c# r
  9. void QSPI_EraseSector(uint32_t _uiSectorAddr)7 p( u3 L% c5 Y
  10. {
      ~5 k6 b- w) Z* U& ~
  11.     QSPI_CommandTypeDef sCommand={0};
    4 D( @& t. Z* J  r

  12. ' N, D' Y/ S, \3 p
  13.     /* 写使能 */
    1 Z# V2 o) x8 |8 _/ m
  14.     QSPI_WriteEnable(&QSPIHandle);    2 ]$ j6 q9 K9 d( |* t2 c/ y+ K
  15. , B9 F5 G4 B: g/ G3 F8 y0 l
  16.     /* 基本配置 */
    ! T4 H# D+ {7 u/ ?' b+ ~# G
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */+ @7 x% T. f* d. u
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    " e4 Z6 _- j& v
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */! n' X4 G( Y7 }5 A# }8 s( A0 A. p
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    : F0 c; F/ \2 I1 ?- u% _) U
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */1 h2 h# J- ^4 [5 S- Z9 K
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */   
    8 b5 {+ v* A. q8 L

  23. / G8 d7 B! I, a) @
  24.     /* 擦除配置 */) r; ^; A0 h0 d. Y6 _
  25.     sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;   /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/      
    ) m- n0 I+ q3 Z6 [
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */       , a+ u! G2 P4 p$ c1 m; S
  27.     sCommand.Address     = _uiSectorAddr;        /* 扇区首地址,保证是4KB整数倍 */    ( I2 g5 p, V, p" Y' e
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  3 [1 _( e" x! L% I6 S
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  ! n( Y5 c- U7 f, i; e% X
  30. * G% z( _2 X9 s3 w. t0 d
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    1 k' ?$ ]! ?, o4 e8 W
  32.     {/ f- @- S5 n2 `) J* Z& t/ k
  33.         Error_Handler(__FILE__, __LINE__);
    7 [5 P8 W, Z$ k7 G
  34.     }/ ^! v7 ^' W. |

  35. / }% [3 g3 u* B
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);    / o) |9 N* m& z8 I: [
  37. }
复制代码
& H9 O8 ~  w, C% e& r
此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:
; k: t' H- S" l1 g: J. w3 l+ N
: L( n' Z* _2 E3 r
a09f0f9fb051c5179f3e48824b99e875.png

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
  1. /*  Z9 D, W% F* I7 s& `1 c0 K3 y4 G& h
  2. *********************************************************************************************************
    7 J5 J; w+ X$ L+ P0 s
  3. *    函 数 名: QSPI_EraseChip
    " `9 i' w( q+ ?, h: I2 ~) B5 T/ J2 M
  4. *    功能说明: 整个芯片擦除
    4 w& i* B6 a, v  P7 [1 G( b
  5. *    形    参: 无8 [, h4 u+ U0 _' T' z7 @! \4 Q
  6. *    返 回 值: 无
    ! Q0 g* G  p+ z9 r' j* p8 u; L/ N
  7. *********************************************************************************************************
    * G2 K3 |2 b+ j' l) H
  8. */
    % O$ S+ o7 w8 a- W2 |
  9. void QSPI_EraseChip(void)
    * O) W4 S0 U3 x6 q$ s
  10. {
    + t2 R+ K9 \& c5 j+ C$ Q
  11.     QSPI_CommandTypeDef sCommand={0};+ Y) a3 h! X1 b: J9 A4 i( N$ U

  12. 7 }! a- P0 ^) M( c
  13.     /* 写使能 *// W8 T, {6 q) ?9 J, N# ]6 z
  14.     QSPI_WriteEnable(&QSPIHandle);   
    1 b1 J0 t* D6 Z0 r# g$ Z
  15. . z  R9 _4 k8 b/ e/ `2 U( x1 F' }  t
  16.     /* 基本配置 */& b# X! o( t; _! v$ P5 R6 R* {
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */3 y9 A  D" h: p8 G  o5 }/ M
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */, E. b) k/ L+ k: l' K- H6 r
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    + {9 [2 r+ Q! @- u) t/ F# k& k9 l( D
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */2 ~  L- V! g- J( R% e
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */& @1 s+ b1 ?/ i. Z
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */   
    ) @8 o  N4 F6 [: t& u" R7 v" m. M
  23. - J) g( Y! D: t& K
  24.     /* 擦除配置 */
    2 Z7 l+ [& T) h% R' m& ?9 h; H
  25.     sCommand.Instruction = BULK_ERASE_CMD;       /* 整个芯片擦除命令*/      
    / a) I& Z. X. L. ?# J
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */       ' p+ c2 i& T' `! R: F. x
  27.     sCommand.Address     = 0;                    /* 地址 */   
    ( |; f7 @! ~+ [' ^3 O
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  
    6 _* ~- z* l3 _
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  
    " |8 o' R+ s8 c6 X, I; L6 x3 v! J; I
  30. 4 W3 x) g9 i9 W$ [4 a& q" [
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK). u0 G: \- m; c& g+ ^. W
  32.     {
    ( B' m3 ^8 X% n8 F/ v  N
  33.         Error_Handler(__FILE__, __LINE__);, B5 ^7 k; z* D7 r
  34.     }; L1 a4 z6 \. E5 a3 ]7 j

  35. 1 q* y8 \5 ~0 i# Y
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);      W" U) p% a2 E5 g! _% G1 A
  37. }
复制代码
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
  1. /*
    8 K! d5 r9 v. w0 P) |: o  ?1 w
  2. *********************************************************************************************************. ?  t/ f1 C/ c* V4 D& f  J
  3. *    函 数 名: QSPI_MemoryMapped' l% q, k. n; }5 G1 h$ w
  4. *    功能说明: QSPI内存映射,地址 0x90000000
    ) w  Z( E& n) T0 z0 C
  5. *    形    参: 无, Y4 c, z0 n, {. e6 Y
  6. *    返 回 值: 无' w# Z' d3 Z3 L8 Y6 ?' s+ M
  7. *********************************************************************************************************! t# a5 v& A2 w
  8. */( u9 ?* y8 j2 u, l, o' i. H
  9. void QSPI_MemoryMapped(void)
    6 T1 b! h/ |, c7 y- l% n
  10. {6 e( L& v, t/ H8 j! r
  11.     QSPI_CommandTypeDef s_command = {0};/ D/ I9 y# c- F4 ~2 z
  12.     QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};) A5 ^, R$ w0 H% V  Z9 Y% f& p

  13. & ?1 r7 D4 g, V1 f* M9 X: j0 A$ J
  14.     /* 基本配置 */- k" p! c- V1 h8 M
  15.     s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;      /* 1线方式发送指令 */
    # F, W2 Q, D# _6 k5 ]9 ^
  16.     s_command.AddressSize = QSPI_ADDRESS_32_BITS;             /* 32位地址 */
    ) ]0 B1 Z4 g0 o  N6 Z
  17.     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    - \$ V7 }% z. }' i# R
  18.     s_command.DdrMode = QSPI_DDR_MODE_DISABLE;                /* W25Q256JV不支持DDR */
      }5 U5 p, F3 Q2 \! [, z" H
  19.     s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;   /* DDR模式,数据输出延迟 */
    : U2 T2 D, V; b1 g$ K
  20.     s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;            /* 每次传输都发指令 */0 `* _* D2 s' h& U* X

  21.   r) K- C% x  T: W# |& G1 c
  22.     /* 全部采用4线 */* N; h( n' ^! P( q* t# W) t9 d
  23.     s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */
    , [+ }  ~9 `" S  B7 l
  24.     s_command.AddressMode = QSPI_ADDRESS_4_LINES;                 /* 4个地址线 */7 `! S9 L5 q( ?" z4 r) `* v( M1 Z
  25.     s_command.DataMode = QSPI_DATA_4_LINES;                       /* 4个数据线 */
      a- Q% f. U$ m/ ?6 X( G+ M. h
  26.     s_command.DummyCycles = 6;                                    /* 空周期 */) _+ y% r& D3 v3 {1 {! b) |
  27. 1 c8 m& i1 U, S% R2 d
  28.     /* 关闭溢出计数 */
    - r6 d3 j& i6 s
  29.     s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;6 L" G- O8 F7 P: i( ]6 W4 r
  30.     s_mem_mapped_cfg.TimeOutPeriod = 0;
    , }9 ?" g  Z/ b  h; T) w
  31. 8 @0 X& ~" |1 v6 O
  32.     if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)& u7 u  W- ~  w& ?& r
  33.     {
    ' l3 d6 X9 q. |- R# M( o  V
  34.        Error_Handler(__FILE__, __LINE__);% `% h5 W8 B8 C. [# C$ |, u* B
  35.     }
    , `' K2 p/ m  P& G* r& T5 A
  36. }
复制代码

! V& Q6 ]6 U  u此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:! _* A9 |, l# t! B# M: b

' D3 p; |  {6 V0 Z7 {% @5 l
bb245c5c5434f7ec1a828434a3915d8c.png

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
  1. /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    * l# \/ x3 {* c$ G# |$ S$ c
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;& e, B6 M% Y% v
  3. MPU_InitStruct.BaseAddress      = 0x24000000;* ?2 Q! Y! E. ?# u  Z
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    ) c1 H$ F9 k8 M
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    * v( G3 b1 q, M0 B: L' Z3 e- I
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    % Y  e2 o! x% S* H& M# B9 P3 e
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    1 c6 G# ?& `  {5 P$ _: [' ^8 Y
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    / m0 ]  S6 h1 c) _( \
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER0;" w1 |0 c) K' l) d% A3 I& v% |
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;# I7 N8 y; F% X* X5 V
  11. MPU_InitStruct.SubRegionDisable = 0x00;
    # M, ?, Z. J3 R  y+ g8 U5 Q
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    3 {2 j% A# I( a& {

  13. ( E# O) v) [0 w7 n" o$ B/ l
  14. 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 ^
  1. /* 2 m& l$ W& K" U1 Q6 r6 a* ?! U
  2.     STM32-V7开发板接线
    $ E, v  L0 _  e% J
  3. / V3 E# w) O% F6 ~$ G: C- \
  4.     PG6/QUADSPI_BK1_NCS AF10
    2 v! e4 a; U+ E5 ~. P+ ]
  5.     PF10/QUADSPI_CLK    AF9. ]: a8 a) g/ [/ ?: k& t! g, x
  6.     PF8/QUADSPI_BK1_IO0 AF10% B+ z# c* A6 h
  7.     PF9/QUADSPI_BK1_IO1 AF10' |" z5 o9 ~" w5 m' J7 ]" }
  8.     PF7/QUADSPI_BK1_IO2 AF9! _. C5 G1 K/ U5 K4 m
  9.     PF6/QUADSPI_BK1_IO3 AF98 P1 i% p: T# f, J7 }5 [

  10. 6 z! Z8 V  o6 m9 R/ w8 g
  11.     W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB
    9 z2 N; t% u/ B0 `) L( `: d; a
  12. */2 o. i( K. r/ {' J

  13. 5 d) M# ?( c1 |
  14. /* QSPI引脚和时钟相关配置宏定义 */
    5 h. Q" K2 F, l6 ~
  15. #define QSPI_CLK_ENABLE()              __HAL_RCC_QSPI_CLK_ENABLE()9 R4 v$ s  ]3 C! I
  16. #define QSPI_CLK_DISABLE()             __HAL_RCC_QSPI_CLK_DISABLE()
    4 h' h) F, A* s! i' `3 y; Z4 y) a3 K
  17. #define QSPI_CS_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOG_CLK_ENABLE()# B. y  E$ Q6 v5 Q2 R- a
  18. #define QSPI_CLK_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOF_CLK_ENABLE()
    2 b) }2 F7 J; |- u
  19. #define QSPI_BK1_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    . y5 b* x: j& N: ~
  20. #define QSPI_BK1_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()6 ^0 a: F3 `7 ?4 `6 c
  21. #define QSPI_BK1_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    9 @. q: A) S% v: A- H$ Z* i  `( K  {
  22. #define QSPI_BK1_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()2 ]7 I; a% J: O9 v

  23. ; H8 R+ {* V  g0 k' y; n! Y, _
  24. #define QSPI_MDMA_CLK_ENABLE()         __HAL_RCC_MDMA_CLK_ENABLE()
    " J+ U0 f! w. l
  25. #define QSPI_FORCE_RESET()             __HAL_RCC_QSPI_FORCE_RESET()+ g  A) H) G  c$ {1 Q! Z: ~. v5 ?
  26. #define QSPI_RELEASE_RESET()           __HAL_RCC_QSPI_RELEASE_RESET()8 Z4 u" d+ v8 q  |
  27. 9 l: Z* L0 o6 T9 e& `4 _
  28. #define QSPI_CS_PIN                    GPIO_PIN_6
    - [) S4 u: s- w' n1 P: Y
  29. #define QSPI_CS_GPIO_PORT              GPIOG
    " \' R# W4 s# i

  30. 5 e% Z# W8 w$ I2 i5 W; j
  31. #define QSPI_CLK_PIN                   GPIO_PIN_10& X* H& {6 l  [! |
  32. #define QSPI_CLK_GPIO_PORT             GPIOF  _& U9 B% q* y# w: \
  33. 2 R/ v! n9 w9 }  Z
  34. #define QSPI_BK1_D0_PIN                GPIO_PIN_87 A0 M( a2 j# {+ e! `8 r" C2 b  w
  35. #define QSPI_BK1_D0_GPIO_PORT          GPIOF3 ?7 j5 ?% q7 A- I  U
  36. , c9 x+ P/ L; q* Y# ]
  37. #define QSPI_BK1_D1_PIN                GPIO_PIN_9
    % y! w- J+ N3 E$ F
  38. #define QSPI_BK1_D1_GPIO_PORT          GPIOF" g1 k& x" ^* u

  39. ! ~3 _5 M* J" s, \
  40. #define QSPI_BK1_D2_PIN                GPIO_PIN_7  x8 i& I% d5 g
  41. #define QSPI_BK1_D2_GPIO_PORT          GPIOF
    * d6 s6 N$ ^! C. {2 |+ O1 ]

  42. 9 x9 F6 v' Y( c0 C
  43. #define QSPI_BK1_D3_PIN                GPIO_PIN_6
    " |! ~3 J! `9 U( d2 U
  44. #define QSPI_BK1_D3_GPIO_PORT          GPIOF
    0 e6 P% {7 D: A$ z* d
  45.   根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件
    1 v& o5 j) A$ @) w  \2 `& @
  46. /* W25Q256JV基本信息 */7 F5 y( _  D3 A: {
  47. #define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/0 ]3 a; ^+ W! t- k, e% C( Z
  48. #define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇区大小,4KB */
    6 }+ D, ^& H6 A% q, x# b- G4 t
  49. #define QSPI_PAGE_SIZE      256                        /* 页大小,256字节 */
    . X  o# o6 u  E% Z, {# f* ?
  50. #define QSPI_END_ADDR        (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */5 r% }' E+ N9 \9 y9 _( g
  51. #define QSPI_FLASH_SIZES    32*1024*1024            /* Flash大小,2^25 = 32MB*/# b! J* \! O% f9 {" b

  52. ) \  c1 g, ^. U6 Y( ?
  53. /* W25Q256JV相关命令 *// a* q& M: T8 s) Z, i# R# L
  54. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  : t- g4 f7 W4 \; ~2 m  d
  55. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  
    ! a' @" w" l. n5 l
  56. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */ + V; E/ U8 C0 `- {1 W% h8 n2 C* d
  57. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
    4 A5 ?. Q. ]5 j% o5 O0 o& |
  58. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    . V: q/ z9 M/ G& {3 t
  59. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */; o: k7 s2 u/ d% f6 Y
  60. #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& `
32c08174cec9edf62fb6e6103da5cea5.png

/ 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
71e953366d6cc128ca9c0ddeef6d23ca.png

  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
7011bff1036a0ffa03f21e1e5894ea0e.png

( ?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
6c91b4eae6d726676a3e5990d73f448d.png
: 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
  1. /*
    8 V6 I& l+ v5 }* ^
  2. *********************************************************************************************************) ~3 T. D' ~7 C% g
  3. *    函 数 名: bsp_Init
    5 j+ L: J1 k. I8 K) N" `5 M' W! Y
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次% S1 H7 m! v9 M: H- T
  5. *    形    参:无
    / f  L# p6 M. R4 u6 P  i# x
  6. *    返 回 值: 无
    2 m8 q, w2 H; p
  7. *********************************************************************************************************0 |+ ^& P. N1 l, l/ Y
  8. */
    : L8 U) a+ ^0 s- ^; J" c
  9. void bsp_Init(void)
    ; q  o! e& S6 U8 I* E- Y) n
  10. {; a9 X: y6 w% F0 l& u
  11.     /* 配置MPU */2 r. p+ u+ A! e  f( ^3 W4 \
  12.     MPU_Config();% M7 p" k+ f: B# q& Z- X4 a

  13. $ l: ]/ t5 i; v& n+ ~* F
  14.     /* 使能L1 Cache */2 R+ f) W* S7 Q' O9 y
  15.     CPU_CACHE_Enable();0 }5 @& c2 B9 x' r1 n

  16. - _* q, o: h. O( [9 t" W0 Z5 _5 c
  17.     /*
    ' t- V! i' {' _+ K
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    " _0 {7 R9 h$ \- @
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。* H# y6 x+ Q1 e& A' e  F
  20.        - 设置NVIV优先级分组为4。
    7 J2 X- u/ u5 z6 c: `5 M
  21.      */. }% Y; r. C/ t
  22.     HAL_Init();
    & _8 b, L; Y; z: N/ }/ _
  23. ( v- U- Q0 g! k5 \. s: w; ]
  24.     /* 6 A: [, I5 W) ?- y* N/ l
  25.        配置系统时钟到400MHz" x- B: N. |* F  Z
  26.        - 切换使用HSE。" P/ h# O6 S: R; Y% f, T; q  I8 `. k
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    ; x8 ~- v( K2 l- w
  28.     */1 F3 H3 D* w  H2 d& [( h! Y+ F
  29.     SystemClock_Config();' s7 B" _; d3 S1 Y
  30. ) J/ o3 F. S* E: z' h% ~
  31.     /*
    * p/ L: G% V+ C1 C% C3 g9 P
  32.        Event Recorder:! h5 u3 B/ g1 m7 y& v6 g( h
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。7 V  G. H  n4 u
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章5 j# @6 M, g  j
  35.     */    ; {1 q, T8 j& J2 D1 s0 ]! ?& t+ D
  36. #if Enable_EventRecorder == 1  
    # t1 @: U/ t, k0 T' [
  37.     /* 初始化EventRecorder并开启 */
    ; i$ p# R8 N3 m
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    * H, |! `/ P$ b7 v7 ~" r
  39.     EventRecorderStart();
    / }8 I' X' q2 w% k
  40. #endif
    9 f; l- j3 ]; s4 a4 W+ H7 p. H

  41. # @% ^; F- b+ d2 p
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       % q0 g" y5 v. r* s" z# n9 H
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    , s  \; O. }+ P; u. K7 s! f
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */$ \% R' q  ?8 z# l  Y
  45.     bsp_InitLPUart();     /* 初始化串口 */3 ^  V! U3 G# K
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    . Y8 [8 [* E3 F& K$ u/ x
  47.     bsp_InitLed();         /* 初始化LED */   
    " `- f# u# q0 M5 v+ c- G( H) E
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    ; E$ B# t9 b/ i0 h4 c2 e$ U

  49. 3 p* ^) B+ F0 z1 \* z# J
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    - [  V0 v7 e6 r0 g
  51.     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
  1. /*6 m: w8 U& a) y
  2. *********************************************************************************************************: P& n3 m0 E- _% O) M7 U$ ]: k
  3. *    函 数 名: MPU_Config
    # V3 s9 q! t" k3 a6 O4 [: r' `
  4. *    功能说明: 配置MPU7 _1 W2 v- _, D: ^
  5. *    形    参: 无
    % M* C0 A# ~7 K! X6 W6 h
  6. *    返 回 值: 无
    / k& v3 F, X- U. ?$ ~% F# r3 I
  7. *********************************************************************************************************
    8 h' K' k+ f, y( m
  8. */
      N' W, l% q% F+ V% H5 @
  9. static void MPU_Config( void )
    3 N. U; Y& i. T
  10. {
    ! v0 U2 s3 r  M. P" O4 r- B) P
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    # ]" W  V1 Z0 O/ D; H% i) v% w
  12. # _0 N5 {# i( `3 f6 G9 O0 k: U8 _
  13.     /* 禁止 MPU */
    " V! v* |3 _. V$ Z7 D0 _
  14.     HAL_MPU_Disable();
    1 m& l+ T! F) W9 S. ?% T, S
  15. ; a5 m" X! R# r
  16. #if 0
    7 ^, U, g7 u. p, V* f5 ]
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */$ U9 U2 @4 M. |' H2 E
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;0 b" s7 m- y( W6 F" j% X7 u
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;
    ! \- S' V( X2 m. S1 E: I) B* |( Q7 R
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;5 k3 W- {2 C' I3 z3 u7 j
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;, O2 C  X! p+ T$ |
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;% }/ K2 A0 `; d. \0 a/ ^
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    - d: j5 w6 \# S+ y
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;/ c" J) U3 ^) E! I  F6 p
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    8 w! y7 Q8 Q0 o5 @# \
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    / L+ g, j& b8 _. M
  27.     MPU_InitStruct.SubRegionDisable = 0x00;
    3 `% W2 E1 F! g6 K
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;0 z" g! q" I0 `0 L. j+ x9 [2 f) `
  29. - c  E$ R* ]0 B6 z! k: k5 g
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    7 J0 p  w# z: H4 a& M0 n9 y

  31. 8 Y, i' J$ Y. t
  32. #else! R/ e( p7 ]7 X8 @
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    3 t9 O' z! g/ G  r! n
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    & b; \% H6 h, P( z) V
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;; o+ x2 P- e' C6 J& F
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;" O* a' o& C& A' e) @& u# A
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;4 J: y; q3 {5 V* p8 Z
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ; c, q% j3 k1 P' ^, r1 {
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    + \  M/ A- u) Y0 f5 r  V- s  M
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;* v1 K& R0 n" ~8 g$ S" I; ^8 N4 ]
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    / u$ P/ G: e3 p% M
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;% _2 U  G. y% B
  43.     MPU_InitStruct.SubRegionDisable = 0x00;7 D/ x/ J- k2 N
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ! S5 L: K1 L1 n! u* K; I
  45. 1 [8 x$ X: V# k; `. j: ~' b
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);' U- K" C2 W; j1 o3 v
  47. #endif    ) o5 j* ?% B$ ^. A. v5 a& z# i: N1 A
  48. $ _- X6 M$ @9 z
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */, [! k  V1 c3 ~
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;) k* D' \9 H7 s3 l
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;# E2 u* a) H2 t3 F. L4 u! y
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    2 d. Y" K, f( \7 M  `0 h" P9 ~" M5 O
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;: p. B0 h5 X# l* _4 k
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    4 Y) |7 Q% t. c
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    7 @/ j# ^7 p3 y% R( E" H
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ! K: V" a# I' D) K9 G
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;+ P% ~5 `3 y' e0 R. G
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    , {$ }' i8 `" ]+ V! u
  59.     MPU_InitStruct.SubRegionDisable = 0x00;
    ) |4 K, |& k* @! C. g  U3 T
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    * r( r0 E1 u$ t. O
  61. 6 s( B0 k) c1 w, w2 \- }
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);3 m" H" D) E  K" F0 H* N
  63. 9 O: E: N; c* f; R8 g7 H1 M. g! |! ^
  64.     /*使能 MPU */
    7 {5 ~/ h! S7 X
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);4 Q- ?6 t8 _/ y( ]: G
  66. }
    , v: k/ H' U, e) R5 {, k6 t+ S
  67. : A3 G7 Z4 I8 p3 r2 r8 \
  68. /*# M- M+ k; P' T# j
  69. *********************************************************************************************************
    $ j! F  Q* n+ n* \
  70. *    函 数 名: CPU_CACHE_Enable
    3 x) K# i$ b( \/ ]" T2 _
  71. *    功能说明: 使能L1 Cache
    1 G$ a* t  a" S: X: J7 j1 R4 i
  72. *    形    参: 无
    * ^; V1 l- J% _9 ]
  73. *    返 回 值: 无
    * P; L: V( `. D! n- v: w
  74. *********************************************************************************************************$ F% i  E6 B  s
  75. */7 I3 D% K" N/ d. @: L% T
  76. static void CPU_CACHE_Enable(void)
    % H$ w' O" f4 `1 H- P( w8 d
  77. {0 g4 \* @: H+ i# T6 b
  78.     /* 使能 I-Cache */' ^0 `" x* ?& E
  79.     SCB_EnableICache();/ B; a! d  T2 D* ~# m
  80. , t3 J' ^' l4 o+ ~  D& @
  81.     /* 使能 D-Cache */
    1 S5 l% ~0 e- [# r; j. x  Q. k
  82.     SCB_EnableDCache();8 J, d5 ]& c$ r; V" {1 [0 ~6 F
  83. }
复制代码
# [( 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
  1. /*
    + D6 O5 [+ J2 F, Z
  2. *********************************************************************************************************
    , b9 ?0 f' M# o  n% N2 v" z1 U( H
  3. *    函 数 名: bsp_RunPer10ms; J3 g( Y  Q1 a7 P
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    ( }9 P6 {" m) ^; q# V4 i& c
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    ; A) a9 t, a+ C- |7 f8 ]8 M# s2 u8 D
  6. *    形    参: 无
    9 w! h& g. `* g* T! P
  7. *    返 回 值: 无
    + n; y! @$ M  A* C' t: }
  8. *********************************************************************************************************
    8 ]/ |% T: h" k6 C) Q9 P6 c
  9. */! ]" n: ]* j/ H2 k" S* F: H
  10. void bsp_RunPer10ms(void)
    ! c6 H/ S/ ]1 u8 }
  11. {
    0 x  A4 E' Y$ \( j% W; G+ |
  12.     bsp_KeyScan10ms();
    , X! l5 b' N6 }" g& G! x0 n
  13. }
复制代码
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. |
  1. /*
    & H3 {7 e3 R1 v9 h/ Q
  2. *********************************************************************************************************
    9 b  T8 ]  i6 h, M
  3. *    函 数 名: DemoSpiFlash
    ; {" I! F) ~) ~- y
  4. *    功能说明: QSPI读写例程1 l: d* a% J+ D- u, }3 M
  5. *    形    参:无
    * |: o" l: y. v( _) Z; A
  6. *    返 回 值: 无3 J, N! E0 Y5 U
  7. *********************************************************************************************************9 D0 r/ r6 a. X) M& c) a
  8. */
    * }% P6 z) X2 L: ]5 L; T5 C
  9. void DemoSpiFlash(void)9 O7 M# A1 h0 G! X: ?3 ]6 o- [
  10. {2 ]- c0 O- s; z& P6 e
  11.     uint8_t cmd;
    % w, X, b; Z, n* `  N
  12.     uint32_t uiReadPageNo = 0, id;
    ) @: C3 s5 r2 V+ C! Q- x
  13.   H9 p+ q4 t- l6 x2 I
  14.     /* 检测串行Flash OK */6 w7 f* t( }) ~! z( ?  h6 d( a
  15.     id = QSPI_ReadID();
    ) e/ q9 ~% M( |7 l2 ?& Z
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
    ) ?5 T" p# m, H7 ]
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");
    7 p' K+ ~; Q1 k7 t9 @) O
  18. ; F6 {# `8 ?* Y( ?+ e8 w
  19.     sfDispMenu();        /* 打印命令提示 */
    " g$ m: n+ j% T9 m) n

  20. * B) y/ f1 f5 }0 `2 P% a& u
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */0 z8 {8 |" `' K; t& u3 X  A( o
  22.     while(1)
    ! ]+ c3 f1 S9 C+ k9 {/ d: b
  23.     {5 {! h- I$ h3 V: d
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    & p1 n' U9 W, z% `
  25. + C& J- B0 I3 v7 ?
  26.         /* 判断软件定时器0是否超时 */3 A9 I* C- z# T# ?  R
  27.         if(bsp_CheckTimer(0))4 }8 u5 [  [3 g) ~- L4 L+ r
  28.         {0 S8 G) L6 H0 F' c
  29.             /* 每隔200ms 进来一次 */  
    1 b8 X4 E; K$ F4 c+ \1 c" T
  30.             bsp_LedToggle(2);1 L+ }. Y+ y& O: }1 c- g1 ]3 h
  31.         }
    6 |6 `; j9 H9 y0 b. s* i' `9 k
  32. . c9 d( T2 y( e5 [$ a; j5 v# E
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    ( K8 ^, a. p; ]5 M9 t
  34.         {
    3 ]( M/ X0 e& r1 ~7 j
  35.             switch (cmd)" U+ ?$ q) w+ b) Q5 s! A
  36.             {
    + U- c2 Y+ [% G8 s) k( L
  37.                 case '1':% `* o2 M5 d* \& {0 c
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    " ?* F2 M& ?) {  F6 D
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */9 e$ h: J3 f0 W8 z  h# U8 N
  40.                     break;' b" v4 e5 W( w  ~2 S

  41. / L$ P. C$ @2 O3 A! I; o$ D' O
  42.                 case '2':
    ! I3 F* r( X0 o
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);9 j+ X! g0 v  R. L% P
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    0 a* O& ?; F7 d" ?7 }+ K* b
  45.                     break;: @5 n0 }4 d! ^0 Z! ^; j
  46.   N8 P  c& i0 y% v* w8 N4 L+ B; _
  47.                 case '3':
      @, l7 m! g8 N! G
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
    0 U0 d8 K6 b% T1 A  S2 {
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    6 C% l" {  s" |: N7 `5 e$ r2 i8 R
  50.                     break;" m3 ^1 }9 i) c* _2 L5 [% X# T
  51. 5 @' b- }  Q0 G1 Y" J
  52.                 case '4':3 u8 Y* g1 Z/ S9 e: ^. N- I
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
    % [, L2 u) {7 k/ H5 Z
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */1 [2 M7 Y; l8 e! w: H8 }7 P6 |
  55.                     break;
    : |. ?! w, N5 k! e3 f
  56. 2 P7 K$ Y) c) Q- p4 N5 I
  57.                 case 'y':
    & t1 b$ w4 f- F6 [% a/ d) n
  58.                 case 'Y':
    , f2 o: ]7 f3 i+ p
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
    : t8 B+ ^) ?9 Z4 I
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
    , F9 |7 Q; F5 y" G, i4 L/ M
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    5 s" ~& t$ e+ D  x  C8 i
  62.                     break;3 X* M$ |/ d- @

  63. $ K/ ^  p  n: ~0 N1 x" e
  64.                 case 'z':1 a% D) d! m/ p% ]
  65.                 case 'Z': /* 读取前1K */
      b' |5 g4 k4 t$ T1 a- R& n, H
  66.                     if (uiReadPageNo > 0)
    5 [9 E2 `. b( ^* l( ?8 w  L1 g( N% u
  67.                     {- y% ]+ R8 G$ \
  68.                         uiReadPageNo--;8 L- x" u2 c* L0 Y2 E; r4 b
  69.                     }
    1 B8 n- ]& F% G  n2 E* t5 K
  70.                     else7 P4 d0 _/ N( I$ w; y+ `, H
  71.                     {
    + z2 J2 s: j$ e9 X& D
  72.                         printf("已经是最前\r\n");3 Y! R- C& \5 A: n& ^; T( T
  73.                     }
    & E+ E  O! R7 j
  74.                     sfViewData(uiReadPageNo * 1024);' f6 [- f) |/ |. P  Q
  75.                     break;
    - E/ b; Q+ s6 v2 ~

  76. . k% E- V& E" h! A" ^' o
  77.                 case 'x':4 x3 q( Z& j8 _9 @2 R7 |
  78.                 case 'X': /* 读取后1K */
    + ]1 R! i5 C  k7 |0 F' y
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)7 G6 [" h% @3 Z: t) Q! E( b. Q
  80.                     {
    ' \; \. R# a% ^; j
  81.                         uiReadPageNo++;1 f- Z, O, g$ u- ]7 a% i  A. t
  82.                     }
      ~' S- H# P+ v- ~
  83.                     else
      U6 m/ s$ ~# C6 ?3 M$ C
  84.                     {# A, s3 ?5 S$ g3 b* o% a
  85.                         printf("已经是最后\r\n");
    1 H6 }: Y/ a5 z4 q* j2 K
  86.                     }" e7 y1 K3 \) {8 Z  I& s
  87.                     sfViewData(uiReadPageNo * 1024);# @7 ?" c1 I: @. G
  88.                     break;; B8 S4 x1 p+ ~$ X& \; L

  89. 7 I; W; s) S$ s$ ]5 E6 `# p* J0 J
  90.                 default:9 T2 t. U0 O! k- P. V# U
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    : b: U) O6 T$ D: `" e. \  w
  92.                     break;
    9 T/ q: _2 i( X- j* j: z# W
  93.             }: _3 b6 V: c9 f% |& r9 U- n/ H2 W* A
  94.         }/ F7 m; U4 Q$ a8 k+ p4 a
  95.     }7 a7 f2 P5 [. L5 m: m9 V4 _+ c# O2 H
  96. }
复制代码

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
3ff8536496d184105fc92ca093930e2b.png
" 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
29c63afe41336006cacb94f46fe77b35.png

/ 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
4dda996917dfda9ff3ebfb7ab53d3531.png

) 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
  1. /*
    / p" G: n( b+ F& \3 r% k" e- j0 p
  2. *********************************************************************************************************9 V. X, C, w' p$ ?5 R
  3. *    函 数 名: bsp_Init
    4 W5 |) b! F3 d8 I
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次: [5 l# M9 t7 \* M' L* x
  5. *    形    参:无
    4 B4 c- t# Z6 \! V, n* F" E! c
  6. *    返 回 值: 无) Y3 n. _( a: ]8 i3 I# c/ H- X8 w! K
  7. *********************************************************************************************************$ X; l7 y5 V- y5 C
  8. */) }) m9 j% G2 {% i
  9. void bsp_Init(void)5 N% j5 |: e/ u( t; r" Z. M5 R* `- Q
  10. {
    3 F. W+ D' d" O5 q+ m
  11.     /* 配置MPU */" l0 ]  {* e3 Y6 r- [! L
  12.     MPU_Config();
    $ n2 m4 u; i# S
  13.   V7 I. }# C! o! `- ]9 u- f* ^
  14.     /* 使能L1 Cache */' a0 ?  h* w6 u
  15.     CPU_CACHE_Enable();
    7 h' O' q# H: n5 ]6 k

  16. & u, C! c( w+ P" h+ e4 v
  17.     /* * b; n) q/ J3 i3 l- v: ?
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:" Y6 K7 I) p( m1 }- {! t% G
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    + p3 L3 a3 C. ?4 y* E
  20.        - 设置NVIV优先级分组为4。
    % g  E! G( @% A
  21.      */
    - A( z1 n' s8 m* R, q+ B/ _
  22.     HAL_Init();1 W6 k/ U) J$ B# |& A' t
  23. 3 s8 E6 @# I$ R: h2 e
  24.     /*
    & }6 n6 F, D6 W9 j* n. {& K/ |
  25.        配置系统时钟到400MHz
    ) Y: V. B3 R2 w+ |
  26.        - 切换使用HSE。7 z+ E# e1 E" R$ [8 ^8 T
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    % ^9 c9 I4 |1 }$ u4 q9 x
  28.     */
    - V. O9 K' f- w, P9 z
  29.     SystemClock_Config();/ o+ e/ D+ o, `% A5 ]% B4 ^
  30. ' S. R* B9 _& X% M  b' O
  31.     /*
    3 f  v! B4 }' M3 e  i  i
  32.        Event Recorder:$ U( D1 I' @2 w
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    , {9 _. ?& M+ M, }6 `+ E) N
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    * e( L7 C9 ^; o0 i+ u
  35.     */   
    ) n; p8 X# f8 f9 x2 U
  36. #if Enable_EventRecorder == 1  % y. D. w4 v; k# V# b0 f
  37.     /* 初始化EventRecorder并开启 */' z% b$ C) N! A% M$ r
  38.     EventRecorderInitialize(EventRecordAll, 1U);/ j  h: P, O( o# c3 n/ \6 J
  39.     EventRecorderStart();" E5 J9 |* w, n+ D! }9 f
  40. #endif5 l& B$ o% T5 c' V

  41. ' I* {% g& c2 k$ y8 j
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       * T9 j' U; e! i6 r' b& h, g
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */2 A4 y3 b+ w- _8 \
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */0 y# V& d! w3 h! w0 V' Y$ C
  45.     bsp_InitLPUart();     /* 初始化串口 */
    - t4 v  P8 ~# _( p& E+ E
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    " v' [  h* I/ d4 M) `
  47.     bsp_InitLed();         /* 初始化LED */   
    - T  V6 K3 X8 f% a6 [
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */( T2 b$ k5 L! f, e0 X, f/ f
  49. ( M5 }6 R8 }- f! M! P, X
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */+ \- D! ]0 v: o8 R$ Y2 T
  51.     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
  1. /*; c2 E+ }2 F: `4 l: B# w8 a4 d
  2. *********************************************************************************************************
    1 S& U5 Y8 c5 _' {/ L
  3. *    函 数 名: MPU_Config: w: V( O* p" f$ @# }
  4. *    功能说明: 配置MPU' K. {% N& P, O# H7 r& m1 M
  5. *    形    参: 无. D: ^7 `5 V4 W, R  v. E( f/ B8 Q
  6. *    返 回 值: 无& M& V" X. H, `4 u* d
  7. *********************************************************************************************************
    . ?/ [% F$ W7 Z2 o- ]5 a- ]4 Z% n3 h
  8. */5 P! G8 ~+ c* }
  9. static void MPU_Config( void )
    , V+ s8 @, l- I) A% `
  10. {
    ( B# V; u2 f$ z+ E
  11.     MPU_Region_InitTypeDef MPU_InitStruct;0 ~( P; \' t: o, ]

  12. & z9 U- r# W; a  a
  13.     /* 禁止 MPU */$ w1 W: l4 F3 @/ E! f  W
  14.     HAL_MPU_Disable();
    / ^6 C" p/ P2 w2 ~6 i: v

  15. # {: W7 m9 i9 g" P, _1 j7 d# i
  16. #if 0, b- D9 B9 n$ h  O0 h
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    5 u; N3 c6 e% M( D/ b/ y: m
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;, v* {+ V% I2 x' G% c) T' ~7 K, t
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;0 N: A  s( R* X+ F; s$ h% n
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    , r- d: d9 Z3 b5 q% T% Y
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    $ ^& _. i  L/ ]" X& ^2 O
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    0 F5 O( I; \4 k- O% k6 I$ D, S
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    - ~- V* N* S* m, l
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;% M/ `- W# ^# I( H& \
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;  G* s! I* V* s
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;1 q- Y3 X* C2 I
  27.     MPU_InitStruct.SubRegionDisable = 0x00;
    / j( s* O1 W5 V
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;. r  ]" d& c, b5 V( g
  29. " c( T! M4 R4 t5 j! G" W$ ^0 |
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ; K2 r% F" m1 P
  31.   c( d9 b, j$ m/ W2 z
  32. #else8 w& @% j+ O1 v2 ]8 V
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */* ?# l/ S) S$ Q* p* O# e: }
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    6 p( {9 c5 T+ T3 A! b2 Q# L
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;  q4 X/ e, o+ p% D+ Z
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;1 v% x# I7 z/ u0 A+ A( @; D
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    0 v: W$ \3 I) r. {$ l0 |
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    . E  H) O  `, s
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;6 r. R; D) F( O( N+ f3 _
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    3 Q! I/ D: L3 `/ m* l
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;2 J! h+ U9 O" Y; H# Z5 F+ t# Y
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    2 G9 I* E: I6 S8 V, ?
  43.     MPU_InitStruct.SubRegionDisable = 0x00;
    4 b9 J+ Q* t, z5 S( g. ~
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    6 c0 {+ U, U% H

  45. 0 ~% t1 ^/ t. c: C5 j7 K3 h7 l
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);6 g2 g! q6 r# @, f
  47. #endif    * P1 i! _: P: G0 B
  48. + U+ r- {2 |# ?
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */% L) Y) {* X# C
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    7 }) {* g7 `4 h, j0 r
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;
    ! b1 R5 k5 Z0 p$ B
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    9 Z2 Y* i+ \! U- r+ l8 v+ F
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;( g% }6 F* @! f, A; q' M( \
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ( W/ X7 c$ Z8 y' H. w" ~! F# n
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    % V& ~- v5 e4 v1 ?* B9 j
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;" b- [& @, r4 K: e- e. w' h, {
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    , m7 g0 u! v% _. C( d$ j
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    % [+ I2 X' m$ c9 `* s2 p
  59.     MPU_InitStruct.SubRegionDisable = 0x00;) n) ^4 b5 c" W! L
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    % n2 s: M% ]3 h8 Y9 Z4 z; {
  61.   i- J) S4 W4 X1 l" o7 P
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);  t0 w* z" g7 k1 D! i0 a
  63.   o, @+ S6 [1 ~6 r/ B! b
  64.     /*使能 MPU */
    0 @* A9 F6 h, C, Y" ]
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);+ C/ Q0 w( P  o# S& {
  66. }. L8 s6 C8 s  s- a6 N. ^2 {
  67. % y2 Z' @' \0 ^
  68. /*! I6 q8 Y- d: @7 y
  69. ********************************************************************************************************** ?6 y  E3 f, g4 v- ?& F; d
  70. *    函 数 名: CPU_CACHE_Enable& V7 ]4 r# y# [5 M2 c5 X9 a
  71. *    功能说明: 使能L1 Cache3 \0 s! b" W& k; w
  72. *    形    参: 无
    ( a, A% C  H) b$ V! `
  73. *    返 回 值: 无
    8 R8 q' \" \; z% p0 k- J
  74. *********************************************************************************************************# m/ g' {. }' r
  75. */
    ; N  n1 b- t3 R0 G
  76. static void CPU_CACHE_Enable(void)' N/ E" x3 o8 C$ u- _. k) ?7 B
  77. {: _8 W  R1 E- a0 \* @/ w
  78.     /* 使能 I-Cache */0 `' W0 r1 S/ E1 V5 {! f
  79.     SCB_EnableICache();9 A8 L1 z( R7 }/ _. n6 s: U5 i
  80. - O) U* x' X- o; A& X' y
  81.     /* 使能 D-Cache */- e( {2 Y$ M. d( v# K
  82.     SCB_EnableDCache();
    " c  v1 f: w7 G
  83. }
复制代码

: 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
  1. /*
    $ E- W7 X) r& d
  2. *********************************************************************************************************2 _! [. u# I/ T  d9 K# L
  3. *    函 数 名: bsp_RunPer10ms. s% {: |2 @( Q1 Q* n% W0 Q( W0 ~
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    ' ~8 x! D6 [$ b* G
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    0 H) u; c9 Z! F
  6. *    形    参: 无
    # M& y( Z, l7 p( e$ B3 {1 \: n5 l9 B
  7. *    返 回 值: 无) |( l* Y( \- |
  8. *********************************************************************************************************
      \: Y3 q# d% T% u6 j
  9. */) i( h9 U; I. x# b- I$ E* _% ^0 S1 C
  10. void bsp_RunPer10ms(void)9 d, X1 }' @* ~, W
  11. {! {! U9 I/ J2 `
  12.     bsp_KeyScan10ms();
    ' W7 f8 _! b% Q1 \
  13. }
复制代码
$ 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
  1. /*  R- a" h. K0 [) h9 k% p
  2. *********************************************************************************************************
    # ~% S( @0 m" n) V. o( g, q
  3. *    函 数 名: DemoSpiFlash
    " Z4 x8 f" |7 _/ A  {6 P6 M# X# T+ r9 B
  4. *    功能说明: QSPI读写例程
    / N! f) M- M$ o3 G. V8 A
  5. *    形    参:无: p8 p5 v' F6 I
  6. *    返 回 值: 无
    & c# U/ U9 ^5 X/ r) y0 j
  7. ********************************************************************************************************** r: f) w2 u" k0 ~
  8. */$ K! k- o1 {( Y" L6 n
  9. void DemoSpiFlash(void)
      {' m. z; l( j3 S2 e) V
  10. {; e  Y: Y7 Q% U+ K) W. z
  11.     uint8_t cmd;
    & A- i7 d1 x3 i) Z+ S3 Z% b
  12.     uint32_t uiReadPageNo = 0, id;. N' }9 s  G& h( ?/ q& ?: ?

  13. 4 O! G6 v0 W7 ]6 v
  14.     /* 检测串行Flash OK */
    ) r" s- \9 p5 ?+ t* o
  15.     id = QSPI_ReadID();
    ; k6 B8 B% a9 @" P, i, j: T( G
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);+ u5 r# g6 V6 ^5 r  r
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");) S  q8 _. [0 J/ k& L; L0 \- Q8 {
  18. ; z' D' k# _$ O3 z0 I! P4 I) R; _
  19.     sfDispMenu();        /* 打印命令提示 */5 o; e" M$ x2 Q! \' s, C4 h! E
  20. $ [3 K9 @  b" t) j
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */; ?6 y" x1 X: z- m. d8 i6 [+ }2 C" p
  22.     while(1)
    9 z9 s0 {% r7 i; t$ [% T* C
  23.     {- L- @1 l( P% ?0 K
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    8 H2 D* H0 ?6 P
  25. . y( X2 z; Z% h& m5 {& ]
  26.         /* 判断软件定时器0是否超时 */" }# A; d0 s$ b, c
  27.         if(bsp_CheckTimer(0))
    , `, d8 X! C1 i1 l4 d  z
  28.         {# f' z/ K, I7 Q1 _6 {
  29.             /* 每隔200ms 进来一次 */  ! ^. T1 M2 u+ z" s
  30.             bsp_LedToggle(2);
    - J! c, d; _8 z( ?
  31.         }
    9 x* `: p: _' L/ H7 k* X3 c6 J
  32. ( o! b2 z- J" |  T
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    ' Q. t$ U, p, {6 J2 _) C: A
  34.         {
    $ W! n9 f; f2 O4 J/ \
  35.             switch (cmd)
    $ V7 W$ L# N/ W+ M# A
  36.             {
    8 F  c  k7 ]( L' ?. [; }5 A. S& t
  37.                 case '1':
    # ?$ `. X( Y) h' F' H( A
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);( M/ L- R3 ~: t% Y9 Q
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    ) H, p8 O: K+ h8 n* `" s" f5 X- u
  40.                     break;
    4 L7 d; _1 b/ B2 U7 A3 t
  41. 2 }0 H8 a8 W" }3 U1 O& H3 n
  42.                 case '2':# T9 p, l; H9 G5 M
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    $ D# d8 j( m$ n5 g: o
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */- m9 }/ B9 F( o: F: X3 ?5 \0 \- O
  45.                     break;
    8 P2 A! H7 K7 U7 v8 o* t

  46.   p8 {2 R- q! \: y
  47.                 case '3':
    ) X2 H. C: N( j. |5 p! l
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");9 H. `6 C' w$ q1 X7 O/ z: Y- |3 g
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */* t: O8 D* a6 E2 A& k( ^" H" L
  50.                     break;
    ! r: t+ o2 Z& L8 K

  51. , q2 e5 w4 Z7 \1 p- p. p$ `
  52.                 case '4':
    1 `, j1 E, z2 ^: B+ M
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
    " @' u1 ?9 e! i
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- s3 _* K$ a# }/ o- y3 n+ H% K! ?
  55.                     break;4 a: _; l( l/ }( Q4 d
  56. 0 H# F7 z# E9 y0 ]4 z
  57.                 case 'y':$ c) O" F( q6 E. u% w& g- w
  58.                 case 'Y':$ e0 N" k8 G. K  u$ W& t
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
    ( f+ H; k8 C& v; B; U
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
    9 a( r+ N9 ~5 x2 G2 w
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */2 J) X9 q, e" N( q8 q7 \; F1 \
  62.                     break;* G: Q# b" M, p" g

  63. + a; I6 a7 H) J& x, b( i; b  l
  64.                 case 'z':
    ) ]$ C( J8 q8 L0 }- _( K; B8 S
  65.                 case 'Z': /* 读取前1K */
    " X0 a( C1 e/ M
  66.                     if (uiReadPageNo > 0)
    7 P5 g& Q, f7 Z6 O; @, q
  67.                     {& ^) P- D! ~6 J1 @1 c
  68.                         uiReadPageNo--;# ?7 G; ^! ~4 [5 U# E. w8 t4 E( C
  69.                     }8 @) F1 h1 a9 }! \- b
  70.                     else, I' M2 J1 _/ ]0 o! |) Q9 E
  71.                     {& l4 t! [+ t! p0 f! ?& E- e- W! j
  72.                         printf("已经是最前\r\n");
    0 E) R" E  u( K( Y; Z! h
  73.                     }( a* U% P, p% U# A# T7 F' a( [/ ?
  74.                     sfViewData(uiReadPageNo * 1024);
    $ o* I5 l' r8 G3 }
  75.                     break;# u3 B) p( f  q' h8 t

  76. 5 u* E/ d+ k& \# a2 e0 {# r
  77.                 case 'x':( P; d2 C1 n8 r% A+ L8 H) G5 Y
  78.                 case 'X': /* 读取后1K */- L1 p/ d; X8 n) x7 s) Y+ n9 @
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)% n: p/ A/ a+ U% t# t
  80.                     {
    / E% p6 K6 R2 {1 O
  81.                         uiReadPageNo++;
      b1 L5 [5 @8 {6 n! m& B0 v6 y
  82.                     }/ n8 f- V2 {+ p( {$ X
  83.                     else  v) }* V" F8 K$ }7 v
  84.                     {' P( \% w( A  Q/ F1 D
  85.                         printf("已经是最后\r\n");1 _+ X8 f! Q" ?- w3 k. p1 R% e, @0 R
  86.                     }( i4 ^* d3 c0 V& e9 K' b
  87.                     sfViewData(uiReadPageNo * 1024);2 _! w  ^1 m; ?8 r* x
  88.                     break;
    # Z% M# S3 q$ m  l8 q- O

  89. $ y6 n# X6 X, x8 p. V- }$ K4 k
  90.                 default:
    . t* z+ F6 \% a8 |  w) t, V$ @
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    % G! O/ X- m, Z" P  M( o3 I1 E; g
  92.                     break;. a0 }' Y: p/ I3 z) J
  93.             }
    9 r# L. R0 s) e. e& ~5 L" m
  94.         }
    9 y" L4 H6 o- a& ~$ v9 n" \. q
  95.     }% L; z2 `  w% e2 m  c6 l4 o
  96. }
复制代码
& @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
f9d118d4b7d97794c046ced058924277.png
940488dfa9f0e39fce107fbd8fd9ea16.png
收藏 评论0 发布时间:2021-11-4 21:24

举报

0个回答

所属标签

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