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

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

[复制链接]
STMCU小助手 发布时间:2021-11-4 21:24
79.1 初学者重要提示; ]5 Q9 I+ P2 Y7 u3 g! r
1、 学习本章节前,务必优先学习第79章。) N- L) x7 J1 v9 v8 L/ v+ @/ Z
9 y$ J, A) V* n, b& u
2、 W25Q256JV属于NOR型Flash存储芯片。) t' y0 e( d, S( F

; N$ C4 Z% W) n5 Q' n1 b' `3 ] 3、 W25Q256JV手册下载地址。6 h0 z8 C: ?; N- B' f
* o! }6 L, w7 |1 b1 x' V% ]
d158c855c3630d64e0d72336ffa1a440.png

/ p& M4 N# w/ c" q1 w: h) x
' k- e% C* A  J1 T' }7 I 4、 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。* [- m1 D6 e+ }" c9 t; v* k9 D- ^$ y7 y
" X3 s# k& k! a/ }3 x7 J
5、 对QSPI Flash W25Q256JV的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。, S6 I% v- q; |4 W! j& K  W3 Z
% O$ u- A# b: k  O. a6 x
6、 W25Q256JV最高支持133MHz。, Q1 ]" x) Y8 o/ y) r
9 Y& X* M. ]4 I# _  |- K& \# Y
7、 STM32H7驱动QSPI Flash的4线DMA模式,读速度48MB/S左右。; F' g1 R* I. d

1 t) N, I7 K5 n' P. M6 t- v2 w7 i 8、 内存映射模式下,最后一个字节无法正常读取的解决办法。
# e( h/ g+ Z% ~! S1 r9 e
8 @+ Z: t* }3 K) R9 C9 I  _ 9、 本章配套例子的DMA是采用性能最强的MDMA。
, m. Q" ?  m+ [5 Q
: x1 V0 i* [& f, |* p9 i! m79.2 W25QXX硬件设计4 C. H) V8 N$ j- Z- s( i; i8 D% O1 J
STM32H7驱动W25Q256JV的硬件设计如下:8 G5 _/ i5 s. x
5 X" D$ i2 D; L9 E2 Q+ F% i
43d24f69b56108e41724030a699d04ea.png

9 P3 }3 n6 c' P5 F5 U$ m% e( t1 ~  {
: I1 ?% g+ M$ w( t: |) a  }关于这个原理图,要了解到以下几个知识:7 l0 m; ]3 n- u( ^5 k* U. M

, q3 D" a# D) ^, l5 n' P% m9 P  V7开发板实际外接的芯片是W25Q256JV。
0 M+ Z# A- I- H  CS片选最好接上拉电阻,防止意外操作。6 I& Y% u0 p* d  ]1 P* M' z
  W25Q256的WP引脚用于写保护,低电平有效性,当前是将其作为4方式的IO2。
0 C5 w. ]6 c, X9 x; r# t  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。当前是将其作为4线方式的IO3。
3 L4 m) [- |3 J4 C, P6 J8 F4 @1 b79.3 W25QXX关键知识点整理(重要)
& s9 ^  @4 R. B5 f+ E5 U驱动W25QXX前要先了解下这个芯片的相关信息。
! `) d8 ?- }: e) P" ^2 P- }( n6 t1 }, Q8 g0 a
d211eea6c41b7da28846a28c61bfebf4.png

" A  K/ }% v6 d& a& z" s, E5 o& W5 V3 t2 D- K
79.3.1 W25QXX基础信息
) x) l8 q9 W9 u/ L0 J  W25Q256JV是32MB(256Mbit)。
, U+ p2 ]- n6 j: f/ a: @' A  W25Q256JV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。/ U8 j* \5 N% T& [4 g
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
8 j9 g. v; ~1 ^' B% Z( D
6 i' x8 p9 Y+ ]支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
5 F( B' y; f* y4 {" a5 n/ m
. W. Z; K2 M; _; I" D(注:这里几线的意思是几个数据线)。$ ^' H' ~% S" @; g, p
2 v* f, l" N$ M3 ^) L. z$ j+ O) S: t
  W25Q256JV支持的最高时钟是133MHz。/ O- w: M+ q- P- m5 R
  每个扇区最少支持10万次擦写,可以保存20年数据。
" [* f. F! j' D  s. d  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。( x3 F: |# E1 `# I& Q' e: d8 Z
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。: h& V0 [9 z% `& F$ I! P
整体框图如下:6 P4 z/ m2 w6 b* v# O0 U5 P
7 b3 \5 T' v# t; P
2da620aeaf02ba8b3035e0b864e140b2.png

, j/ C6 O- H% e7 A4 c0 |/ J6 X& n. q2 w% G
W25Q256JV:. M- N- Y! L& B: @- w9 t8 Z
! P' Z. R" R6 f
  有512个Block,每个Block大小64KB。
+ J$ H$ I6 r  J5 {0 S8 j1 S  每个Block有16个Sector,每个Sector大小4KB。: h6 b' ?. ]8 F. ~7 @- V% n5 K
  每个Sector有16个Page,每个Page大小是256字节。+ i' f% w/ D2 G6 \9 x7 m  j# B
79.3.2 W25QXX命令4 Q- ]( \6 I" h9 t6 d1 Y. S5 \7 E
使用W25Q256的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的QSPI,即4线SPI,并且用的4字节地址模式,使用的命令如下:
+ k$ v& k1 I$ M1 r) B# T
9 p% n: ]# u3 _8 `& t
77c20d25330bfc517d68b73c7751ddf3.png

- Z  ~2 f; l% ~3 [% D3 A3 p" w# l! e7 j1 l3 `; W
ee48eed7fa217e118947120ca34f5263.png

) n+ i! D6 e$ x4 W+ P% P' e' `) @+ H7 ~- U& ]
当前主要用到如下几个命令:
5 G+ f+ k7 h2 O
8 t* F+ j* x, z" S' }
  1. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
      @* R' N, C& ^' c
  2. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  $ A! F9 r' _% z$ E; Y
  3. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */ 9 j+ H- f$ H6 V1 Z# f) p4 i* N
  4. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
    4 m4 N9 I. p4 E# }
  5. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    ! J1 w0 j! t: |
  6. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */
    ( q$ n/ c# A: o/ _5 k
  7. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码
, p2 s7 h3 \8 B3 m2 m2 z1 q
79.3.3 W25QXX页编程和页回卷
' U& @/ X" f) e9 h1 g; w3 Z$ J* ^& LSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
! y3 E( Z" Q$ e. y1 t# l7 }" _8 h% B# W5 \/ o" H
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        : J* {$ X+ V& M8 V  c8 V( n3 ]4 t
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    7 G, Q; `8 n/ w
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   1 S1 S& p% P" C
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               2 |  C6 }3 p# Y: z
  5. 1 V4 t7 a, y# d/ M
  6.     for (i = 0; i < _usSize; i++): z. n; R5 I( Y2 [1 V! [2 g
  7.     {
    , R* U; D1 b* v& u
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    3 _8 v1 P; G9 @( F% h: B' F( V: A
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   $ U4 C' l- U$ w. j
  10.     }
复制代码
7 r" j! h* s8 T  x/ ?2 ^
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
+ p6 k! ~- B3 h, q8 K
" X- g% k: v, m7 N0 J比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):( g: \% X7 p. j7 K

+ M! f2 \) z7 V. Q4 x# [
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    $ f3 L/ }8 D2 D& g
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

; _" g8 V; @: {  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
, }* X7 t' h3 M3 O& ~" }  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);6 c+ F( D5 \! e" K
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);) S, n' [5 b7 b% J, N. W5 P
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)4 ]0 I1 j: s# g. C0 X
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。5 B$ j1 i% N" _5 R/ v* J* U1 M, `
8 p! Y# o( K4 v( G8 n
56240b94a12eb9a067e63db9803b13b7.png

& Q$ v( d2 v9 }# A
% k; o2 i+ q) x79.3.4 W25QXX扇区擦除* h( ^- _9 X# k5 E0 q  Y" Y* Z$ h  e& c
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。! X; V( P# Z2 P5 _, e, r$ U2 c

. M2 F0 O, c3 }# r; b如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。4 s. z: Q& B3 _& _- u5 l

9 _/ _" h2 @' T" F/ h/ ?79.3.5 W25QXX规格参数
8 a' Z% R9 Q( n. C( o' Q& w这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
, Z% K: t9 T; i
+ R) G- K) k) \& A" L
e21b7448e777b6e8e376fb71ea3e75ee.png

: [1 Z1 B9 T5 m! ?' V' L' O5 ]
% B) A; N% b; b  |6 B% [
9 B% f' f5 s2 B2 k4 x  页编程时间:典型值0.4ms,最大值3ms。
$ p- g6 t, Z% V/ I+ {) v  扇区擦除时间(4KB):典型值50ms,最大值400ms。
/ D1 s/ F6 Q1 H  b% K( D  块擦除时间(32KB):典型值120ms,最大值1600ms。
7 g% n( S7 Z- c7 x! l0 v# i  块擦除时间(64KB):典型值150ms,最大值2000ms。; y% U( h4 K; {5 U
  整个芯片擦除时间:典型值80s,最大值400s。
( S  _/ O' ~3 L( G9 k+ t7 a
" R5 `9 x) R  @- N支持的速度参数如下:: O) P# s& _  l( g5 U: }) a0 O  I
) L4 M+ p6 u: j
9ab6f9e23e8cfb4e497e67913dda1d7e.png
0 P. }3 J. C; H+ G/ k9 O3 |. I5 c

6 s- f: l8 n/ K  }) f可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
/ K$ Y! ~; j9 \( {! ?5 u2 m" O3 [
7 k. Y6 s' ~+ g79.4 W25QXX驱动设计
* `; u4 E$ s" eW25QXX的程序驱动框架设计如下:& h3 [+ V  p+ F0 b7 g+ X1 Y
/ l7 a# Q4 X" C* Y
0edab8ae6a09033608ede0892279b72a.png

2 p- N" o  X+ g1 D, f
& p( I4 b; |2 F! T6 b6 R有了这个框图,程序设计就比较好理解了。  |+ \. w2 _& |- `
' C, T( y- k6 |  B
79.4.1 第1步:QSPI总线配置
2 u7 M9 P9 V' {/ \QSPI总线配置如下:
( g+ \+ k3 t: C& P3 k! p8 P  d7 h& @: d' o, I( ^$ P7 ?
  1. /*+ d8 X1 H; }0 z, U: p' Z
  2. *********************************************************************************************************: m- ]! E3 [; }4 _. `1 D
  3. *    函 数 名: bsp_InitQSPI_W25Q256
    " j; U7 L5 {2 K* _3 X3 ?3 X4 X
  4. *    功能说明: QSPI Flash硬件初始化,配置基本参数
    * d% M. l4 @- @' k& H
  5. *    形    参: 无
    : r2 w* v& Q( e: v3 u
  6. *    返 回 值: 无
    % e* i3 [+ m8 z* p- b( ~- V
  7. *********************************************************************************************************
    " [0 n* k, [& Z& R
  8. */* t( T( i8 ?0 M, e9 S. x3 i
  9. void bsp_InitQSPI_W25Q256(void)
    5 r6 g) l* Z4 j0 y
  10. {% \4 ?5 o4 V% Y: P4 p
  11.     /* 复位QSPI */
    * _, M' m; [6 S4 T" C6 g9 o+ L
  12.     QSPIHandle.Instance = QUADSPI;, e$ P7 J9 a9 t" E' W2 A$ O3 v2 A+ _
  13.     if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
    9 H2 T* }2 f( M6 A
  14.     {
    . F6 Q/ |' z' R5 P! A* _( o5 i  E+ C
  15.         Error_Handler(__FILE__, __LINE__);
    * D# j  g$ L  o* P' F; }
  16.     }
    # `; A: \7 Q' t

  17. & ?6 `4 p7 U& q2 j1 `
  18.     /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */
    . K" ?" R- K- M. ?2 k
  19.     QSPIHandle.Init.ClockPrescaler  = 1;  5 [# v4 }' Q1 C3 K
  20.   h( s$ `4 k% g' x
  21.     /* 设置FIFO阀值,范围1 - 32 */' K+ L( N& v( h# \
  22.     QSPIHandle.Init.FifoThreshold   = 32;
    / E( i) g( a& m$ m% ?

  23. 7 |: t" u# Y, A# ~/ L! h; q1 W
  24.     /* 4 s* i( p; R& A7 k
  25.         QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。
    / B! s5 d+ ?3 R  @* H" L
  26.         在外部信号延迟时,这有利于推迟数据采样。
    % _8 z) \/ d& D$ d* y7 w0 Y
  27.     */7 i% e- K7 k4 l2 f: l( h4 B+ O& @
  28.     QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
    0 B+ N2 h4 A3 |1 f! P( ^

  29. ; D/ U+ Q9 k; ?+ v
  30.     /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */
    2 }, k- I8 o  J9 Y! F/ s/ I" C) f
  31.     //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。! h( m2 t9 p* K8 @1 {
  32.     QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE; % h" @; l2 i# T5 Y

  33. ' {- m5 t( L9 C8 X8 l! j3 i; k
  34.     /* 命令之间的CS片选至少保持2个时钟周期的高电平 */9 X) a7 T  @4 |3 _
  35.     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;' l9 l% [: v. Y0 K* }

  36. ( U( ], f1 T1 [1 y$ S% ?
  37.     /*
    ) B! [/ l; L: [4 {, y2 F
  38.        MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平
    : ?, G2 f2 O! M( q( c) Q6 K  W
  39.        MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平
      h. W3 s/ ]9 M# X6 j  i, ]
  40.     */4 x  v2 H! B8 T
  41.     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;
    8 Q* j9 T* Q2 D
  42. : B3 W  r9 n! K0 A0 d
  43.     /* QSPI有两个BANK,这里使用的BANK1 */# g+ G- e9 k& ]6 ]( @5 L/ F
  44.     QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_1;
    $ s- z4 ~! W/ Z5 w0 T6 g$ f
  45. ; e8 D$ H7 a6 G3 l5 t
  46.     /* V7开发板仅使用了BANK1,这里是禁止双BANK */
    1 m0 T1 R( i! G/ `& B1 X; @+ a' y
  47.     QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
    1 x" y5 p9 z( i1 ]

  48. 0 y3 i# R7 y0 }/ [9 q" E! k) ^; S
  49.     /* 初始化配置QSPI */
    ; Y# A, ~4 l4 `' g, e- `/ ]
  50.     if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)! N! V5 ~! k. ^/ p
  51.     {+ M7 O, `" H2 m9 ?
  52.         Error_Handler(__FILE__, __LINE__);6 a( b: L3 k7 f9 h( p) o
  53.     }   
    7 ^# b, o" O) x( \( X
  54. }
复制代码

' Z7 v, o, j7 ~" e5 B/ ~( rQSPI这部分配置,要特别注意Flash大小的设置,这里做了特别处理,本来是应该填入QSPI_FLASH_SIZE-1,而我们实际上填入的是QSPI_FLASH_SIZE,主要是因为内存映射模式下,最后一个字节访问有问题。2 E' z6 C0 l' ^# y( G% H8 v; [

! o$ [4 g3 Z0 l" ?) E79.4.2 第2步:QSPI总线的查询和MDMA方式设置
7 @$ j& i0 _1 Z5 C本章提供了QSPI Flash的查询和MDMA两种方式的例子,驱动的区别是调用的API不同,查询方式调用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。' z4 A* w' b+ [/ A- h

7 k3 g& d% i- R  H/ _  D% g9 k9 G' W4 X79.4.3 第3步:W25QXX的读取实现2 p8 _' t; S) K& E* ^& f
注:这里以查询方式的API进行说明,DMA方式是一样的。
7 F0 }2 ~! k4 N- i  Z5 ?5 ?6 R- Y  O! t& D2 K# c0 y
W25QXX的读取功能是发送的4线快速读取指令0xEC,设置任意地址都可以读取数据,只要不超过芯片容量即可(如果采用的DMA方式,限制每次最大读取65536字节)。
: k2 Y3 p! }2 i" |0 T( _9 D7 T: n- n* L7 y! E$ S
  1. /*  r# j" s1 N8 w- J& u$ a6 ^( T
  2. *********************************************************************************************************& i4 z1 w, a, Q: K8 v. L
  3. *    函 数 名: QSPI_ReadBuffer
    1 G; P2 D! x* V1 c" h
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。3 B2 y- N5 }! e' P7 L: f( M
  5. *    形    参: _pBuf : 数据源缓冲区。
    0 r4 g4 T% h  b( b' d% {$ H
  6. *              _uiReadAddr :起始地址。
    ! u2 ~2 g  V) A* [. u5 H! l
  7. *              _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。
    + N4 s9 |. t0 S/ C/ E& m9 c
  8. *    返 回 值: 无2 B1 d; G% @3 k# n
  9. *********************************************************************************************************  k: A2 A9 D$ M3 V
  10. */. b& u( c6 s5 H* v3 m" I# X
  11. void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    4 _, ^3 P" n+ ?) a% r% k) v# w7 U
  12. {
    , ?' u* F( {6 W3 |' `' f
  13. 0 `! d$ L" t1 ~5 s
  14.     QSPI_CommandTypeDef sCommand = {0};
    2 Y- z# U' M6 ~9 x
  15. 4 [/ _! K; h3 a; I0 e8 C

  16. $ U7 z' C9 I; p/ B/ l
  17.     /* 基本配置 */
    - `% y' L& Y8 G7 w6 y0 D% e
  18.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;        /* 1线方式发送指令 */
    , M" X0 r/ y! p8 j" s
  19.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;          /* 32位地址 */6 N9 d4 _. J+ D) S8 d
  20.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;      /* 无交替字节 */1 k5 U. p+ E, `6 S1 ^
  21.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;          /* W25Q256JV不支持DDR */1 F" s% h! j  q, |1 p( _
  22.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;      /* DDR模式,数据输出延迟 */0 Z  o7 `- q; G% `: |2 @9 m
  23.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;        /* 每次传输要发指令 */   
    ( @3 L- K9 M7 ~2 W9 q4 f- n" k8 E

  24. * E3 G/ I. f( f, K  q+ E7 t
  25.     /* 读取数据 */5 E1 S. d4 w4 \% C
  26.     sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */
    $ {7 ]* \  g" h  x
  27.     sCommand.DummyCycles = 6;                    /* 空周期 */- A: C* L0 q. ^. }3 A
  28.     sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */
    : J6 N8 ^$ W, I- ?) I' S2 l
  29.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据 */
    3 J( o( b5 y/ y3 B
  30.     sCommand.NbData      = _uiSize;              /* 读取的数据大小 */
    % X/ t7 V9 g$ z6 n, i
  31.     sCommand.Address     = _uiReadAddr;          /* 读取数据的起始地址 */ % r  B  u( ^6 m4 P% p0 V8 w/ k
  32. / N) Q# Y' M/ m
  33.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)1 C$ Y' C1 E+ T0 j' q! E) X
  34.     {
    9 j9 v' D  `& U/ Y4 A  Q3 U$ r
  35.         Error_Handler(__FILE__, __LINE__);/ g0 `6 Y7 P& z
  36.     }
    8 K8 ?5 Z; y" Y. V( p, [
  37. / H/ A* d  Q! C+ R
  38.     /* 读取 */( K: I. h4 k7 c3 [. P
  39.     if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    8 O0 z0 B" j, g* v$ a: U4 a* n
  40.     {% v/ ^+ B0 t. P# {
  41.         Error_Handler(__FILE__, __LINE__);& \  W; k6 t  {5 c7 s  N
  42.     }    : B3 w' W. q4 ~- k( t
  43. }
复制代码

: b3 ~& X0 H" q; C% _此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:. `7 e0 [& M% L" |2 y  H' m

" ]0 V7 S4 L1 S" A, X! k
6c1787c10d01bbaf5602747a947053b6.png
, ~: t% v4 j" N  [& Y

; ~" _# H! o" h' X; B0 [左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:6 |0 G8 l/ r. g
" a' Z7 }4 a& c* r. ]: u6 [
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;. F: i* D" M" U0 S
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
1 v; L9 s7 |% t5 g' t; P- GsCommand.AddressSize = QSPI_ADDRESS_32_BITS;
/ i& L8 Y" k' Y: t. X9 Q0 WsCommand.DataMode = QSPI_DATA_4_LINES;
( L; z* J! j/ }  p( `4 H' g
& e1 ?+ U. n) e  |. b79.4.4 第4步:W25QXX的编程实现# w" a5 Z* x/ i- V3 o, |% g3 P
注:这里以查询方式的API进行说明,DMA方式是一样的。! Y  ]. u& v2 H7 p
! u1 Y. Q' ~/ }; {8 d; Z0 f
下面实现了一个页编程函数:! j7 i" M8 f" a; W0 P' O  y

# |" X' D/ }$ T- a0 x' v, h
  1. /*6 U: F9 b8 \2 k
  2. *********************************************************************************************************+ D& M1 y- @  r- B8 J5 T- Y" F
  3. *    函 数 名: QSPI_WriteBuffer
    . ?  F' U) i7 G0 a1 c
  4. *    功能说明: 页编程,页大小256字节,任意页都可以写入- m; Y" s! {: {' W% u
  5. *    形    参: _pBuf : 数据源缓冲区;
    , ]+ B5 a9 |- v1 G& d7 _5 B' A% G' S
  6. *              _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。
      o" l# X; @% Z6 v5 G6 x
  7. *              _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。5 N8 m$ S' n1 k5 I8 z
  8. *    返 回 值: 1:成功, 0:失败
    5 j7 i; |* T, z5 }* O/ J
  9. *********************************************************************************************************1 t- F$ R. G" T; \
  10. */( q, P3 r3 r) f
  11. uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
    - d- e* c4 A) w
  12. {. j" c5 s, J; J  }; p( |$ f
  13.     QSPI_CommandTypeDef sCommand={0};# c3 T7 R9 \, _2 m5 A

  14. 7 [! n; J/ |% W. f
  15.     /* 写使能 */! p0 H- I! M7 o+ Q
  16.     QSPI_WriteEnable(&QSPIHandle);    7 \  q+ S+ l8 a/ z6 q6 V- F+ j7 A

  17. 4 @) i, d/ S, \, t; b2 V. k2 L0 f
  18.     /* 基本配置 */
    ; Y5 j* b" w8 @2 {# f- R# V5 k2 |- R
  19.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */+ f" \+ a2 |+ e7 z6 a3 n0 l: S
  20.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    - ?3 j1 u- _' X, K$ f9 Q
  21.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */0 e, F) B/ T4 ^5 K+ U( ^) G! T
  22.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    5 j8 o8 x+ g1 M; Z- |+ Z
  23.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
    6 T. n7 }' a5 |. t" M' H4 M
  24.     sCommand.SIOOMode          = QSPI_SIOO_INST_ONLY_FIRST_CMD;     /* 仅发送一次命令 */   
    2 K8 S. A* i5 \1 y; T& j% `
  25. 4 u9 w9 o- E- _$ p. Z- i
  26.     /* 写序列配置 */# A) I: S3 G" V; o. l! |1 ~5 P
  27.     sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */, P- K* o- N; F. I! P
  28.     sCommand.DummyCycles = 0;                    /* 不需要空周期 */, K5 ]4 ?5 E3 i2 y
  29.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 4线地址方式 */( l: {! b' K9 d4 {+ S
  30.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据方式 */1 g) b$ O& j: t' n5 z
  31.     sCommand.NbData      = _usWriteSize;         /* 写数据大小 */   5 A- Y" v0 g" q! @, H
  32.     sCommand.Address     = _uiWriteAddr;         /* 写入地址 */7 |6 D; \5 [. m" g0 |$ D

  33. ; N2 G3 o' l- o  ?$ o% N
  34.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    + j1 \: Q) v! ~  ?. J* @. Z
  35.     {$ c4 p1 [& o1 e% U, F
  36.         //return 0;0 j4 x( D9 K+ Y: P, A/ `
  37.         Error_Handler(__FILE__, __LINE__);
    ; ]+ o* p7 f! ~$ C" [( d! |) ^
  38.     }
    + T7 G3 s! d8 l. [

  39. ; V  u8 t; {5 j, w$ Q$ ~2 q
  40.     /* 启动传输 */" r" O% A/ n* H; u# P1 z
  41.     if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)5 {4 x" _! K* Y8 w$ _
  42.     {
      b# @& L, W3 S4 c* I( `" k/ l% e
  43.         //return 0;+ F' x9 d4 F2 L3 E
  44.         Error_Handler(__FILE__, __LINE__);
    7 R% v1 X2 v" t6 P4 m8 T

  45. 9 ^& ]4 u3 G. W
  46.     }7 l) k. F/ N- Q0 t( o4 y$ D
  47. 7 ~' o8 H2 a3 o0 b+ W
  48.     QSPI_AutoPollingMemReady(&QSPIHandle);    3 d2 b* t0 E8 E/ z1 Y+ ]  v* r
  49. , ^) H& ~* }& W/ p$ O( @
  50.     return 1;" p& @5 `+ K, d( k: h1 P
  51. }
复制代码

. o- P, {! P6 L此时函数使用的指令0x34对应的W25Q256JV手册说明,注意红色方框位置:
, t. @4 ^5 F7 i2 @8 V& X$ W2 `. H, l3 b
8 y1 g7 @6 h& X; u3 c

; f- G# ~& v2 |! o4 d- g& i# w$ d* J' v5 w: R' |8 l# Q
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
7 e0 N  j( T% w% k5 o1 w3 u. g
. A: U0 [% Y: P% Y% O- n1 esCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;4 r1 s2 Z& R, w3 Y1 \- |2 L4 c
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
+ X2 `3 w/ `# |) }( e0 j$ zsCommand.AddressSize = QSPI_ADDRESS_32_BITS;# d7 I+ i- H+ L* c0 r9 x
sCommand.DataMode = QSPI_DATA_4_LINES;
# H' n. y/ c& b3 M: j9 g' ]1 ], ]: {
79.4.5 第5步:W25QXX的扇区擦除实现
* f; l" @" @1 P7 K+ y# ^: E8 z注:这里以查询方式的API进行说明,DMA方式是一样的。" p2 R( @$ ]/ M" Q) ?
2 O( b4 B; Q8 H$ C1 m" _
通过发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除,擦除的扇区大小是4KB。
$ F# y, x- `2 C7 I% l0 V9 E
6 V! c" t3 X6 y) t+ k! i' }4 ^( V0 ]
  1. /*
    3 x/ Y5 E* S! r. O: w
  2. *********************************************************************************************************
    ' E4 M% J- w( g) H5 y0 x3 t
  3. *    函 数 名: QSPI_EraseSector3 J% y. f; t  ^: v- h- m3 `
  4. *    功能说明: 擦除指定的扇区,扇区大小4KB; z, T, o& C( S7 `9 `2 Z
  5. *    形    参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,
    + c: ^+ y& B7 I6 X. s, _' E" \# [
  6. *    返 回 值: 无
    5 u7 Y. G3 K( l
  7. *********************************************************************************************************
    5 ?  k( t: S" n) ^
  8. */  Z' q6 z$ p  y1 G% `
  9. void QSPI_EraseSector(uint32_t _uiSectorAddr)
    : ?# d: t" p/ U  k+ y- g" H+ Z
  10. {
    4 G$ G; D- y' d3 P  M
  11.     QSPI_CommandTypeDef sCommand={0};0 r0 w4 Z3 C  k% S, s
  12. 9 C* K( P+ G7 @! Z
  13.     /* 写使能 */* P% k5 A7 s# s& F. Z+ x
  14.     QSPI_WriteEnable(&QSPIHandle);   
    * ~0 H! Q" F1 i7 J4 f" H, ]1 E
  15. 2 x8 `1 A3 _+ k! o5 v( h8 o
  16.     /* 基本配置 */
    - E5 @8 c+ q6 i( V: e! g+ @
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
    2 L1 b& l9 i$ }
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    3 h6 h+ k3 A8 p, |5 C; j$ }6 f. D4 D
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */* F' w0 J& w1 d& Z1 E1 Y
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    & Z3 z3 L$ H  Q1 R4 Y. M1 G1 u
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
    # _2 L% |! C( T1 t2 m
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */    & \5 l, Z: I- [2 P4 H

  23.   X# G. R: O) p1 F1 O, k
  24.     /* 擦除配置 */2 ]+ F% L" @9 ?3 M, W$ l" g. Y7 g" ]
  25.     sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;   /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/       9 v  {- q  C* |  f
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
    , t, V! F- O6 A
  27.     sCommand.Address     = _uiSectorAddr;        /* 扇区首地址,保证是4KB整数倍 */    8 k) ^$ K- g1 z
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  5 G- {  a# K- |& b1 v
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  
    1 O- |# _5 c) M9 l! c# ^6 r
  30. ( ]' o, j; z" \' a
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)' K! J! ?9 k2 S" Z" d
  32.     {0 u, I9 t/ M- _; _) @0 o
  33.         Error_Handler(__FILE__, __LINE__);7 m4 }1 ?- O# K/ }6 N( }
  34.     }
    5 d# T& e5 G" x
  35. ) D) H$ m: h. I
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);    . w" j2 E  f5 F/ g5 {5 r) s. X
  37. }
复制代码
" G0 y5 ^4 ?0 P  J) `, ]* x! K
此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:
6 t, t. B9 h/ F2 Y0 n% F% d: w  b; F% D; I/ Q. Y
a09f0f9fb051c5179f3e48824b99e875.png
2 q: V) Z! ~6 o# O# t& t

/ K5 P2 E; p/ }  K左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反映到程序里面就是:
6 f; i  y3 f, k/ c' [4 j7 i# {' ^$ [$ M
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
1 Z8 _: [4 R9 U, w2 c7 Y9 B- W) ]( t( w
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;2 K& z/ q: k6 N
* t& V: y: `0 h
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;0 q( y# l  J& s2 k0 o
- y3 J4 C- n9 b( \7 Q+ H
sCommand.DataMode = QSPI_DATA_1_LINES;6 j4 B; ?" `9 [6 ~8 s
% z0 V+ k( q) V
79.4.6 第6步:W25QXX的整个芯片擦除实现
" m  t1 o. ?( N4 `' K5 @注:这里以查询方式的API进行说明,DMA方式是一样的。
0 B  n8 y9 E6 j9 T* O- n
# X9 l, x& I9 R1 V整个芯片的擦除可以通过擦除各个扇区来实现,也可以调用专门的整个芯片擦除指令实现。下面实现方法是发送整个芯片擦除命令实现:
4 e% U1 k# B4 i- a; D& D" c5 V8 w) b9 I' e1 J
  1. /*6 ?2 B4 h6 k9 Q7 L* f! C& c' B8 `
  2. *********************************************************************************************************5 z/ o3 G4 G5 o) {. a$ e) P
  3. *    函 数 名: QSPI_EraseChip- t+ J; d( J3 H# S) d
  4. *    功能说明: 整个芯片擦除2 P1 ]! k0 b; H: \; n8 g" d, D' v6 e
  5. *    形    参: 无0 H  y; L( l+ ~  R/ K/ B: O8 L
  6. *    返 回 值: 无1 }: q  `$ C8 x# M/ `# a3 V2 ~$ C- K
  7. *********************************************************************************************************
    7 o% ^) p5 F. `4 G+ X
  8. */! L5 R5 C& \( K( B; |/ d
  9. void QSPI_EraseChip(void)
    9 ?, S4 k' D, n0 X( l# F2 q# Z) Z
  10. {
    3 N" D% k9 w3 y; q) [
  11.     QSPI_CommandTypeDef sCommand={0};
    7 I9 @! `1 t( h% u$ b

  12. 6 Y! P! p8 R) F, I8 B! }
  13.     /* 写使能 */; }3 g2 y! }% y* G* Y3 M' |
  14.     QSPI_WriteEnable(&QSPIHandle);   
    ! [- @; V$ ~" O+ A
  15. & i- `& l) \4 J5 Z* ]
  16.     /* 基本配置 */5 ^* _, y* G3 r$ a* e/ E) n$ x5 r
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */7 l& b3 v. e( k1 F, T4 s& ?1 e2 C9 C
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    * I( Q& ?3 N9 ^; B$ g7 G
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    / q- o/ V' Q* B: z& y% G% Y6 u
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */) j. `" o: m- E. k4 L* u
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */# R5 u8 ^2 i+ k0 Y2 H5 N; J
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */   
    : P$ O7 ?: _4 S2 [7 L
  23. ; W- a  |2 V- D# z( [# q" ~
  24.     /* 擦除配置 */: C* Z* t2 ], V" @
  25.     sCommand.Instruction = BULK_ERASE_CMD;       /* 整个芯片擦除命令*/       - \/ i; f( O/ j' n: g
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
    : f) K$ X) ]* C+ |8 ]7 w
  27.     sCommand.Address     = 0;                    /* 地址 */   
    + k/ y) c( D; j, ?$ A
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  & t3 `" A. `: q& N
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  
    / ~( P0 a! p. G
  30. 6 F7 P9 N7 {  H) P! D- x* N( {
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
      s, i* e  H8 d
  32.     {  h4 N  @% N8 F
  33.         Error_Handler(__FILE__, __LINE__);9 b8 m1 a3 C. v4 {( q4 p7 u' e2 z; U" |
  34.     }# C# }$ T) c6 H: ^7 r5 C
  35. * L& R1 F0 T9 w# k( Q: ~4 [/ {
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);    # F9 h, X: m: p& n5 O0 ^
  37. }
复制代码
9 h) X& Y9 b: F) [1 t
此时函数使用的指令0xC7对应的W25Q256JV手册说明,注意红色方框位置:
1 K5 R4 z* ?5 n6 s# U- i" r- ]; S' g  z
5 u/ k9 q9 g% g1 g

6 j: J: Q( [# H/ D- }% \左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反应到程序里面就是:
& y9 N7 \  P9 t8 ]. ^! H9 L1 Q
4 G; Q" L3 N9 g! B  T0 _sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
% K  {  E2 x# @, asCommand.AddressMode = QSPI_ADDRESS_1_LINES;
8 p* Q% |& h6 ysCommand.AddressSize = QSPI_ADDRESS_32_BITS;
! X7 {! j/ l, I0 `; p5 D8 T* RsCommand.DataMode = QSPI_DATA_1_LINES;$ n* H9 R4 [8 x# ~
  X" H1 d2 x. J( O) k
擦除用不到数据阶段,sCommand.DataMode  = QSPI_DATA_NONE即可。* R5 z- [1 r( S4 e/ U

+ c2 T' y' J. U: f, I79.4.7 第7步:W25QXX内存映射实现
$ G) G, }2 p' M1 s注:这里以查询方式的API进行说明,DMA方式是一样的。
+ h: L6 R. ]/ y. I' u) \/ Z+ f; |. H: e3 v7 I, I
通过内存映射模式,就可以像使用内部Flash一样使用W25QXX,代码实现如下:
4 z* I5 \  y, \& M% k% P
& Z1 x: ?6 U" u+ k) R* ]4 Z1 S
  1. /*9 h# C3 A5 ^. w# `# a. E: I
  2. *********************************************************************************************************& ~1 U$ _' H' ~2 y1 L& s* y& V
  3. *    函 数 名: QSPI_MemoryMapped9 _0 V. ?# Y  J0 [( b8 B
  4. *    功能说明: QSPI内存映射,地址 0x90000000* S" M* k) O. c$ a9 R
  5. *    形    参: 无
    ! o, Q6 o4 H. P9 b$ F) q% n( u9 ]0 B
  6. *    返 回 值: 无& J% J: v% \% D; X+ [7 o: ?
  7. *********************************************************************************************************- l( W, ]# Q' S1 K+ a) `1 |
  8. */
    / ^/ s) a# N+ v# X
  9. void QSPI_MemoryMapped(void)% a2 v  {3 F1 T; Z" q$ B
  10. {
      b: B0 w2 l8 D8 T6 G$ {
  11.     QSPI_CommandTypeDef s_command = {0};7 h, z3 l* n: @# I) R6 a
  12.     QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};) [( C3 p3 X) i8 @3 z( b' T( ?) F
  13. ( e) e8 M' G% d, s; |8 }2 T0 m- h1 X
  14.     /* 基本配置 */. J! k0 ]- d% i; L5 O* v6 M
  15.     s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;      /* 1线方式发送指令 */ 8 [  ~5 E9 Z/ i" J3 _
  16.     s_command.AddressSize = QSPI_ADDRESS_32_BITS;             /* 32位地址 */
    6 m0 t- w. k+ g& s6 E! }4 h
  17.     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */2 m3 l- o. m7 E
  18.     s_command.DdrMode = QSPI_DDR_MODE_DISABLE;                /* W25Q256JV不支持DDR */
    7 Q, I- l- u5 e; Z
  19.     s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;   /* DDR模式,数据输出延迟 */
    0 V( Z7 S( `$ D
  20.     s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;            /* 每次传输都发指令 */
    5 c! Q0 g) n' @% J; Y

  21. 2 R8 y* O* ]* M! Q- J5 t
  22.     /* 全部采用4线 */
    2 s  M3 {0 B4 L. u/ l! |
  23.     s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */
    : n' J* G' z$ O
  24.     s_command.AddressMode = QSPI_ADDRESS_4_LINES;                 /* 4个地址线 */
    8 Y8 ?2 A8 a  O
  25.     s_command.DataMode = QSPI_DATA_4_LINES;                       /* 4个数据线 */
    6 o. B8 k/ Y! _$ J) W
  26.     s_command.DummyCycles = 6;                                    /* 空周期 */$ q( R; V% F5 S- e

  27. 8 e& D$ Q9 x  d4 ~7 i
  28.     /* 关闭溢出计数 *// W/ }* I: j( n
  29.     s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;- G  a" Y* ]/ a6 L$ ^
  30.     s_mem_mapped_cfg.TimeOutPeriod = 0;
    7 q" a2 c7 o% ?3 s; ^! ?+ T

  31. , q# F6 P6 a1 Z1 _/ e  D+ |
  32.     if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
    % E/ }  Y0 W& ]! ~* q/ i# P
  33.     {. _  H2 ^8 t0 P% F" R7 I
  34.        Error_Handler(__FILE__, __LINE__);
    1 B1 T; V, G. R5 B  g% D
  35.     }
    * F# y$ B5 {; v9 ~- s( u
  36. }
复制代码

7 j7 |) v+ c  q# ^* ]  V6 }) f此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:5 E0 |( q" u4 W! s6 r
' Q+ n, R: S! O
bb245c5c5434f7ec1a828434a3915d8c.png
2 Z, E$ `- W% ]
3 g6 C+ o  _0 ~- K* N' B
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,采用的4字节地址方式,反应到程序里面就是:9 V6 C( z1 h- ?$ t  B7 I. X

# M: c" o+ ^' y- YsCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;6 b2 U3 U5 \" e! R: C& e2 y
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
8 o: _! |3 o2 D# z0 k1 rsCommand.AddressSize = QSPI_ADDRESS_32_BITS;8 ?' i$ ^1 o* ?1 Y6 S
sCommand.DataMode = QSPI_DATA_4_LINES;9 d1 a6 E9 L6 \& }4 f8 R
: r# L5 u* ?  G+ }; V& q3 P
79.4.8 第8步:使用MDMA方式要注意Cache问题& M8 t0 V4 C; }$ D* {
如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM,这样可以方便大家做测试,测试通过后,再根据需要开启Cache。2 L. r: ^- I/ U5 f
, M4 a0 }5 z" A# H" ?1 }
  1. /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */- a6 i$ U9 ]: y
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    4 e1 l. L- B1 n# j7 Y
  3. MPU_InitStruct.BaseAddress      = 0x24000000;
    ! l/ T4 Q( T0 k( U
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    , x) f9 }8 o) S
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ( Y% V9 ~9 M( [5 s
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    & s+ N% H9 m# g
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ! N' I3 E2 J, l3 U, @% K4 }8 o
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;! T" J) M8 T7 O) ~! B
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    + |$ ~4 H; ^! Y
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    7 |: m! _+ E5 K+ t6 D1 j
  11. MPU_InitStruct.SubRegionDisable = 0x00;! N$ I: f, r1 R1 g, m+ x* K7 c
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ! M$ L* V( d9 x! R- ?

  13. 9 p# Y3 O7 A7 a. x( H% v' _7 p
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

  ]# x: h& A4 r& l0 M" ]: u7 n) G79.5 W25QXX板级支持包(bsp_qspi_w25q256.c)" C  p0 Z  C# N' R8 A
W25QXX驱动文件bsp_qspi_w25q256.c主要实现了如下几个API供用户调用:
' {+ g6 m. ~* n/ A3 u7 R& Z( V( G( w; X
  QSPI_ReadBuffer; l5 a- l! I/ q
  QSPI_WriteBuffer. X9 ?. v1 q  x
  QSPI_EraseSector
4 |; x- Z7 a1 L$ b6 _2 M  QSPI_EraseChip
7 v& C$ C) c8 |& `3 s  QSPI_MemoryMapped
8 X, y0 Z% G: ~1 ?  [' e- F79.5.1 函数QSPI_ReadBuffer% Q: a+ A- ~, G- ~" m
函数原型:
$ n, q. `! c" A# m" F& _
* U2 N) _8 ~+ L( bvoid QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);1 ?) y* a1 ~% i& A

2 b5 N. P4 O: h& s1 E# E函数描述:
3 T7 d9 Q; `' F/ n; L+ h0 d
* m% M& u6 D' U) B此函数主要用于从QSPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可(如果使用DMA方式,每次最大65536字节)。
; [% S  s; t( X* X0 J) c. C9 l9 p1 }. X4 M% g
函数参数:. C, b! v" L  K6 o
9 |' D, O8 @* p) r4 A
  第1个参数用于存储从QSPI Flash读取的数据。- m; |% @* ?% a8 A
  第2个参数是读取地址,不可以超过芯片容量。
! {' V- V5 P8 R1 V- k" b2 |  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。. y5 \) Z1 H# t# ^1 U
79.5.2 函数QSPI_WriteBuffer
9 y  B6 Z) g0 e1 b+ \1 @% {函数原型:5 C) h1 U" d) b% }& B& R2 C

% h' S8 n. g# n" b/ euint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);& X- i  F# r5 L4 {, I

* G# A8 I" r8 Z2 T函数描述:1 g3 L$ l8 a( j) b6 |
9 Y7 @, @" [! k& V' z" S
页编程,页大小256字节,任意页都可以写入。注意使用前,务必保证相应页已经做了擦除操作。
, I  S- k) _% A& S: k9 Z
/ o! S; s7 _# H( J函数参数:" u) I  p* y/ x& Q
% N6 \% k# n$ `" t/ O5 z
  第1个参数是源数据缓冲区。
2 z3 p/ @' C, Y- L9 m) v+ Q  第2个参数是目标区域首地址,即页首地址,比如0, 256, 512等。% M. R$ J2 M9 L; H- O: o( S
  第3个参数是数据个数,不能超过页面大小,范围1 – 256,单位字节个数。7 ]# K1 V. k+ d$ d
  返回值,返回1表示成功,返回0表示失败。
) O2 C+ X$ X9 |9 @& {79.5.3 函数QSPI_EraseSector  L. b' F7 e# Q- X! R
函数原型:0 h% G2 S2 F; y5 d0 }3 A7 K( Q
4 E1 F' c! c9 @( Z: v! p3 \8 o9 y
void QSPI_EraseSector(uint32_t _uiSectorAddr)
# r* |& X8 h$ O/ ?
5 G; \$ o- j4 ^0 z函数描述:
* F! [% Q+ s2 t* }* S5 |" ~/ z$ I) P; R! V9 }) O6 `
此函数主要用于扇区擦除,一个扇区大小是4KB。9 W8 A2 G- r: j5 J3 Q2 ~" J2 T

; S' E$ F% T8 y% k# I  g  B函数参数:
- t0 ^  h; f/ n/ ?, `2 x
' W# m- [  q  r0 Q1 }! O  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
0 N5 F/ ~0 b3 G* I( O1 {79.5.4 函数QSPI_EraseChip- ?* B9 [6 h9 p" R
函数原型:- ~+ d. M& y$ |! _9 h  ~

1 i# N8 n2 o. o$ h; H' pvoid QSPI_EraseChip(void)
5 x7 ]6 p/ t) Y; `' ]' B' [* @
& i( ?( ^" Y8 Z; Z函数描述:
# @8 S6 _& B( V5 i$ Y" x! f% r9 y: k# N2 H
此函数主要用于整个芯片擦除。3 g" [5 u* d7 j/ e3 a

) C; {. p9 ?) B8 z, `0 ^, G6 k79.5.5 函数QSPI_MemoryMapped, ^* R9 Z8 v% h2 A* Z. }  ~  m
函数原型:
1 n. y) v! e4 l% _( g& t6 K& e% f3 o4 G/ H2 B
void QSPI_MemoryMapped(void): K+ E9 ?0 \2 i
$ P* h+ ^8 _" A' T- p
函数描述:
1 j1 r4 _) i) Z0 d2 N7 y4 ?+ z8 T  Y# [) f. y
调用了此函数就可以像使用内部Flash一样使用外部Flash。2 {" O, u% e- o/ v2 D
# U9 v$ I6 C" R; w4 p' v" `& R& g
79.6 W25QXX驱动移植和使用! `) v. X9 G  E- g
W25QXX移植步骤如下:
" [; z: T* Y. g1 r8 o; p: W& x. z3 {- w6 ]7 @+ {7 i9 G
  第1步:复制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目录,并添加到工程里面。
9 r' z; H+ L+ a7 g, f. E) h9 V7 l! R  第2步:根据使用的QSPI引脚,时钟等,修改bsp_qspi_w25q256.c文件开头的宏定义。
# X, p7 F& b2 ?" V2 H* d5 r
  1. /*
    0 u- `8 r5 c1 y& d1 _% K$ H+ R
  2.     STM32-V7开发板接线) P0 R1 x" `1 D% r

  3. " D! u0 v) l3 M' @+ J2 K
  4.     PG6/QUADSPI_BK1_NCS AF10
    $ B8 L* O' U& i" @( H
  5.     PF10/QUADSPI_CLK    AF9
    : h! J9 Q. ?! v/ ~( L  ]. R
  6.     PF8/QUADSPI_BK1_IO0 AF10
    , c! G- l8 g; J- R
  7.     PF9/QUADSPI_BK1_IO1 AF10
    2 S% K! x* d7 t% F
  8.     PF7/QUADSPI_BK1_IO2 AF9
    7 ]* ]' j# V5 @% u
  9.     PF6/QUADSPI_BK1_IO3 AF9
    6 U& T3 O1 |1 h

  10. 0 I0 S: M9 Q; A3 ^
  11.     W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB3 i" C0 Q8 K3 U. j& N$ q
  12. */
    2 q, @, O* u6 C8 n
  13. * V2 f, _$ s9 H9 @5 m
  14. /* QSPI引脚和时钟相关配置宏定义 */
    4 Y/ y9 g4 T$ P$ k3 }' }. W3 ^
  15. #define QSPI_CLK_ENABLE()              __HAL_RCC_QSPI_CLK_ENABLE()
    . t' |) [* R6 |# D
  16. #define QSPI_CLK_DISABLE()             __HAL_RCC_QSPI_CLK_DISABLE()
    0 J, U4 G- v( d3 c. e' y( a$ n
  17. #define QSPI_CS_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOG_CLK_ENABLE()8 B% k/ C3 D6 A$ ?) k$ ]
  18. #define QSPI_CLK_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOF_CLK_ENABLE()
    # L- b/ i* B/ ]1 U2 @6 k
  19. #define QSPI_BK1_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()* D6 a2 k# J' C" p7 C6 y1 O7 P
  20. #define QSPI_BK1_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    1 P% S/ K* x( ^5 y
  21. #define QSPI_BK1_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()% M2 ~9 c9 H1 C, N1 n( B- T
  22. #define QSPI_BK1_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE(); g- h8 x# g) v

  23. : _$ r2 q1 O+ U8 ~; o
  24. #define QSPI_MDMA_CLK_ENABLE()         __HAL_RCC_MDMA_CLK_ENABLE()
    ) s% a% }4 k: j2 [" w1 c% Y9 C
  25. #define QSPI_FORCE_RESET()             __HAL_RCC_QSPI_FORCE_RESET()
    8 B# J6 {& @4 @0 _. l; }* V
  26. #define QSPI_RELEASE_RESET()           __HAL_RCC_QSPI_RELEASE_RESET()
    8 D! p9 K) x. `  _) V1 r! ]* C

  27. % b/ G. R4 ]/ f* e$ U; t. f
  28. #define QSPI_CS_PIN                    GPIO_PIN_6
    6 v& b( ^* j( l, G2 y- D6 M
  29. #define QSPI_CS_GPIO_PORT              GPIOG7 b8 H. p0 Q6 p) t# Q) ]& D

  30. 9 @  k( A3 \  m. A( z$ w
  31. #define QSPI_CLK_PIN                   GPIO_PIN_10. u+ E$ d3 f2 `: v4 M$ X, g3 s" F' [+ i
  32. #define QSPI_CLK_GPIO_PORT             GPIOF' E4 P+ s7 F- K6 [; @: e  D
  33. , V8 l9 Q: e  G, X
  34. #define QSPI_BK1_D0_PIN                GPIO_PIN_8  H3 R/ o7 j: r; E* K
  35. #define QSPI_BK1_D0_GPIO_PORT          GPIOF
    % j; l& H; z! l! J% F, E

  36. 7 n, V: b1 l- y" c! y- {, E, R
  37. #define QSPI_BK1_D1_PIN                GPIO_PIN_9
    % t0 }. t; v) v, q; ]7 C% |4 Q' ^) x
  38. #define QSPI_BK1_D1_GPIO_PORT          GPIOF
    : R# Y5 _- T& r& w" X1 b* g
  39. 4 ~8 ]- y' R8 i4 V  M* f4 B  `
  40. #define QSPI_BK1_D2_PIN                GPIO_PIN_7# t9 r% @6 m& M2 e
  41. #define QSPI_BK1_D2_GPIO_PORT          GPIOF
    , i  p3 \& X& B. K
  42. 8 m5 i/ i* V& G2 G
  43. #define QSPI_BK1_D3_PIN                GPIO_PIN_6
    + m6 l6 o% r( `( `) v& d# k! P1 f
  44. #define QSPI_BK1_D3_GPIO_PORT          GPIOF/ q7 O3 v) L  a2 k+ ^# I; r
  45.   根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件* y5 _& G5 l2 v+ R" V
  46. /* W25Q256JV基本信息 */
    1 q% K" I7 W. Z  y4 |8 j
  47. #define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/
    " T: v7 V- H% O) l3 ^/ {, @
  48. #define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇区大小,4KB *// B1 M/ l5 f. [4 r0 E1 J2 c- f6 x* x
  49. #define QSPI_PAGE_SIZE      256                        /* 页大小,256字节 */
    # f* T- l' C( Z2 k4 a: b
  50. #define QSPI_END_ADDR        (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */
    ' Z; f4 v  ~, A. P# C5 }2 o9 `
  51. #define QSPI_FLASH_SIZES    32*1024*1024            /* Flash大小,2^25 = 32MB*/4 A& E+ w% W: @* f
  52. ! r, s( d# g5 x1 o# O, H& R
  53. /* W25Q256JV相关命令 */) k! h1 s6 B, D2 B# t! Y
  54. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
    . l. m/ h1 u) B$ {0 p
  55. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  
    9 o" w1 F0 G8 e, v4 W& N
  56. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */
    8 L. w8 K! n1 y2 d
  57. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */ ) o$ }1 `* b, e
  58. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    9 T. v5 P0 ?, v9 p: k- `
  59. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */! \4 h8 s% @5 M: [: Q
  60. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码
8 g7 ]0 o/ [& B, O* h+ o1 U7 J
  第4步:如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM:
- H, J) f- g# R! F" T9 C; A" D+ n/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
" o9 x7 a: a8 @+ k7 HMPU_InitStruct.Enable           = MPU_REGION_ENABLE;
* n+ k0 w$ g; r$ DMPU_InitStruct.BaseAddress      = 0x24000000;
* Y+ b- p' _) S( ^7 tMPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
9 G+ M, a- z" N9 sMPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;- t1 i4 P  ~# v9 B0 N( I4 F9 a
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;- p+ T; C: p0 C; B, D' @
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
* S9 Q% a) o  ^) L; w3 r7 jMPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;' D! l1 I' ?; N" Y: I3 f+ U
MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
3 {, q7 V0 q8 A8 {/ W& `$ bMPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
+ r3 x) D9 U. v& f- C, ~( w0 pMPU_InitStruct.SubRegionDisable = 0x00;
$ _3 W* W4 e0 J- w0 c3 C& _MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;5 g# I, c- p8 C9 R

1 t# \6 {3 R" Z6 u1 K8 _% Z! R; Y2 ]HAL_MPU_ConfigRegion(&MPU_InitStruct);
5 b, O  R4 q8 g$ v% `" [# |  第5步:初始化QSPI。
  j2 t% q6 o7 V9 L) L8 h3 c/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
( i+ m0 E1 D: C( U& J- y. B% W; sbsp_InitQSPI_W25Q256();  /* 配置SPI总线 */
9 y- j  k1 s+ Y! ]  第6步:QSPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
5 l; O9 b, w3 q# f: i- p$ [0 r" }  第7步:应用方法看本章节配套例子即可。
( ^( d+ I$ g" Y( k7 j79.7 实验例程设计框架
: O7 t: x: Z* P* K. C通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:# \1 W2 {0 A/ Y3 `% b% Z0 x

4 g& K% _' w7 ^5 C+ L3 l: Q
32c08174cec9edf62fb6e6103da5cea5.png
! _  w2 }& ^$ v) ~0 v5 j

8 \* T0 W+ Q2 C9 r' ~" B  第1阶段,上电启动阶段:2 r; `+ ]/ o3 ?# f4 \* T* c' u# A3 u
4 P# w2 @5 d& _1 M1 R
这部分在第14章进行了详细说明。
& a) E+ P1 S  e  G& r  第2阶段,进入main函数:
0 G; n! a9 U7 D' T* m+ S' y4 x) V- o- M& v4 C- Z" {" z
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。2 p& `( ^$ M7 h
第2部分,应用程序设计部分,实现QSPI Flash的查询和MDMA方式操作。4 w$ R0 V0 W! ^. N" I: g& z1 |
79.8 实验例程说明(MDK)
  U4 @6 C9 g: f, D配套例子:0 F( i" F) k) w( Q
, a$ i  E% x. c/ `6 O
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)4 Y5 e! I* c/ D  `) ^$ _, `( n

, @& V: p% y( ?V7-059_QSPI读写例程,查询方式4 k* w, w& E, @6 u* g$ X' n% e% m: K

4 j8 X5 f0 O# A7 r8 ?实验目的:
9 M* A: L! g* W1 Y" p0 ~! B$ K% q/ Q
学习QSPI Flash的读写测试例程
. Y5 Y8 a' e" r! r, b4 Z) N实验操作:
! _0 n3 m, B  n0 m% g3 p$ g, e5 y0 c2 O# M4 z
支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可# X4 ~6 E. W1 K$ l1 F, z
printf("请选择操作命令:\r\n");5 g. S8 F) ]) l- z/ n/ K, \$ R
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
( ~6 q  `* e9 z% _7 u& Q; Yprintf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);! D: ^9 k% k8 M& U% ?. Y* [& m
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");) j+ D; {0 f! B6 j) t% ^4 l) m
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");* C6 S( m$ V% \( L, f
printf("【Z - 读取前1K,地址自动减少】\r\n");/ U5 a8 U& V$ L6 c9 J& ^. R% A4 p
printf("【X - 读取后1K,地址自动增加】\r\n");
, I. X. V) }* D( B7 W4 u( Oprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
- j% @$ \8 B* O' m/ }printf("其他任意键 - 显示命令提示\r\n");
+ S9 p6 V* l2 L0 Y上电后串口打印的信息:
2 j" p6 Y7 O8 [; @; c" k, D- H  ~# T9 {+ z
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
+ I- |6 ^1 g; D3 b, d/ ?/ Y  t! z/ k- S( `9 N
71e953366d6cc128ca9c0ddeef6d23ca.png
4 \$ G0 J- C; d2 s' ]# A6 |

* U( m( Z6 m  P8 s9 B8 J程序设计:
" {/ l$ ]3 B  z- I8 l* b8 S/ g+ F0 Z
  系统栈大小分配:0 X; z6 G! C2 U8 ]

: L! }0 I% [) M& y
7011bff1036a0ffa03f21e1e5894ea0e.png

; b6 F+ r6 X/ U$ v; Q/ f. z) O
! y5 {, e9 D0 R. g! r- l% z  RAM空间用的DTCM:" H4 z9 Y" ^! P

7 @6 c0 l5 V: r$ n6 z1 p1 C3 r* ~
6c91b4eae6d726676a3e5990d73f448d.png
) b, v, }9 h3 @1 v& X& C
, C6 s7 x- K% l3 L. M
  硬件外设初始化/ w/ J, W  N, w

0 A: M9 F/ W# L- {0 Y; x6 `* o硬件外设的初始化是在 bsp.c 文件实现:; g9 a/ D( b2 k2 g" a) E/ B

: P& p2 o  {4 H$ f& A- Q5 u4 z
  1. /*! j& }+ u  K9 ]0 ]1 f) C
  2. *********************************************************************************************************0 T% o& {, c3 Q; y
  3. *    函 数 名: bsp_Init
      y. |! N7 P7 S/ G) b; b
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次: b+ ]/ C& U1 G# ^5 x% C7 v! E9 }) J. W
  5. *    形    参:无. p" c" h/ v$ y: z6 U' R7 {
  6. *    返 回 值: 无
    0 s; z0 t9 b* q2 Z4 z$ `8 J7 D
  7. *********************************************************************************************************
    " ?4 g( y* @/ K1 l/ M; g
  8. */
    - g4 b/ h! `6 k
  9. void bsp_Init(void)2 w4 H5 K6 W/ e) n+ V' l
  10. {" b2 P8 [: J" w8 q& c
  11.     /* 配置MPU */  ]& w( c* R, g* l  x
  12.     MPU_Config();/ _. U: E# X, f: A% y
  13. 3 I6 Z( |" t2 ?8 G
  14.     /* 使能L1 Cache */7 H" W( \2 v7 B+ R
  15.     CPU_CACHE_Enable();3 r+ U& A# L- `0 U, }9 Y

  16. + k# o& L% b8 J4 J& j. \1 Z$ n
  17.     /*
      l6 b+ e+ E# ~; I6 @7 l* z
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    2 c  x% B$ A: ^' h. I
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    - s6 x9 R% p5 a* E2 x
  20.        - 设置NVIV优先级分组为4。1 ]4 ]" y6 T0 B2 H. ^
  21.      */
    : d+ F4 e9 P+ j/ [1 ?, p3 l' n
  22.     HAL_Init();
    # G4 A, S8 s* ~9 |3 \

  23. 2 r: Z5 s/ i, t$ X, Q9 x" g
  24.     /*
    . H" a4 O) H; r; S6 D6 g
  25.        配置系统时钟到400MHz
    $ Q4 I8 x- \% t- [% I" {
  26.        - 切换使用HSE。% f* M% B. i: e' Y$ g  J0 A* J
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    : _& A6 L& s3 a+ W" D! R
  28.     */
    8 R1 U" I: F1 A& {
  29.     SystemClock_Config();
    4 C) W8 x# E% h3 X8 z& w7 z9 f
  30. # C/ @4 v0 E" ?1 E
  31.     /*
    7 T7 b: S' b3 ^( O
  32.        Event Recorder:; \" r: h) J* p/ q# Q
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
      j5 L  D" t, g+ C* U/ ~
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章" P/ C1 c4 c9 \& m
  35.     */    4 ]5 p  c& a7 r$ T
  36. #if Enable_EventRecorder == 1  
      S* X( E# B. O) U% \
  37.     /* 初始化EventRecorder并开启 */9 f( [, @; Q4 e. S
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    8 _  K- N9 N! L
  39.     EventRecorderStart();
    7 a6 o& Y, g4 v& G4 K4 G$ B9 e
  40. #endif
    ; B7 ~. ]- t' D4 C( d

  41. % V% ~4 s7 O( z( @
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       ) m! H+ k+ i# N. p" o2 g3 b
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */4 ^- V6 {: g& m
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */1 ~: W1 h# E: E( Q
  45.     bsp_InitLPUart();     /* 初始化串口 */
    - l5 M! c6 r2 ]7 i
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    3 R+ I4 @& U6 i  s
  47.     bsp_InitLed();         /* 初始化LED */    4 r8 l1 V9 I, y1 [1 v
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */2 q$ |- H- K4 b0 m. Z) f

  49. - w: _0 r! o* H9 J7 }2 E9 T
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    2 v# A7 ^: k3 g, v
  51.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码

% n2 F/ C3 Q& `' ^2 i- I4 u6 k  MPU配置和Cache配置:
7 j% e7 {# C2 o; @  `# R) k7 m
. x* Y; Y2 |2 m& }数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
0 p4 u2 D* M, c2 H1 u. U4 Q4 y5 Y8 R" w, g( H2 u  S. L# l
  1. /*3 C' N# E: |* h- t
  2. *********************************************************************************************************
    ! b& c3 |3 s) Q; B' l
  3. *    函 数 名: MPU_Config  N/ }# J! }% t$ Z3 q; }$ j% M+ l3 |
  4. *    功能说明: 配置MPU7 I: R6 N3 u9 d$ U
  5. *    形    参: 无! i$ c6 ^7 u' E5 T' E
  6. *    返 回 值: 无
    8 i8 Q! n' y7 w7 C8 _
  7. *********************************************************************************************************
    5 P; ^$ y: b5 E2 O
  8. */
    8 g0 [+ P- V/ J0 h
  9. static void MPU_Config( void )
      N6 ~. j6 n, |4 i1 Y0 w& b$ x4 h
  10. {) p( X: _% E* N& W
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    ; r6 r- p+ u6 E. }. C
  12. 2 R- h+ X8 @- N  k. a
  13.     /* 禁止 MPU */
    " g, e6 Z) O0 ]
  14.     HAL_MPU_Disable();. o8 E( i1 i& i0 m. y

  15.   c% h- k5 u2 V
  16. #if 0
    2 e# k) T' U1 J- ?( A0 a
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    9 ~; f( k' Z+ h3 e
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ) ^$ Q2 _4 T' L* m+ }
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;
    - E1 o7 v* H8 i- c0 t
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    + y7 ~2 Z' p5 P2 H1 z4 m
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    : g* N& u. V) I% [
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ( f; r, W' V( V- Z. j6 o, e1 ]
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    1 c1 S! ~7 a7 D! @1 k6 |, j
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;! h9 s4 e& O( Z/ q4 S! ~
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;- M5 x: O8 h2 a& X8 z
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;7 ]  M! e. Q0 t# p) [
  27.     MPU_InitStruct.SubRegionDisable = 0x00;
    + O3 L6 Q  I$ q$ J
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    1 n) W+ w2 E% E1 P' Y( O
  29. 3 j/ G8 R1 z; P# V2 i+ s6 L2 m
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    4 c6 J- l' `: |- O0 L  w% Y2 I

  31. ( ]; `& b- ]% M( a
  32. #else+ v8 K6 Z1 J& n, Q% \
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */( @5 s4 n- s2 S+ @/ Y9 W
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;: m9 L) W0 ?7 r, Y7 d
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;
    # V2 l7 w4 d; W4 S
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    ( R, Q9 n0 ~* E1 r0 _8 N' [3 K5 U
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;& i! B9 y0 ?+ {7 ^
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;6 d  K  I* i5 B
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;4 o9 X  _9 j+ u$ g
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    # Q3 n$ C) l; X  I$ Z
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;6 T( q  K8 z0 o0 \6 T9 @
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ! e7 z0 x. _* r6 G
  43.     MPU_InitStruct.SubRegionDisable = 0x00;
    ! K7 g4 e( @1 [, o8 E! _8 y/ b
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;  c7 p6 @" S2 u

  45. * q1 C2 V2 v- z* _
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ! u2 F! Z5 P4 N$ E3 \) g+ j7 v1 e8 v
  47. #endif    ; F6 ]' F0 ?, z4 o

  48. % G+ d% {8 m( x2 U* Z
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */  L8 s- w+ G& U6 z  u6 I% |1 E& Y& Y
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    5 j& l4 x3 R0 A# }% j: {
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;
    ( k7 T" H1 e' B
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    + I% k* z9 E8 s
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;$ ~, Z8 Y; d6 m( W" ]5 o
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;2 e" A7 D$ x1 T6 T7 n
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    4 b$ i8 t0 e" V/ I
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;$ ]; u& C; a* }7 a
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;0 L  E* Q/ }7 G% ~( ]* |( [
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    " K; ^2 [" E4 v- P
  59.     MPU_InitStruct.SubRegionDisable = 0x00;
    * E3 |- \( p4 H# c6 J2 @: |
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;3 B" H5 s0 d" t4 B. ?' p
  61. 6 w+ p1 V5 B: j. u* S6 p
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    2 x2 X$ Z. i- i
  63. ( \* S/ m) j1 ?& S- S8 h. W
  64.     /*使能 MPU */
    8 R; e" x; |/ E" g
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    # d: J6 U. V4 Z/ l% ]
  66. }3 Q9 K5 H3 k3 J; Y. {9 k

  67. & P9 e3 \( I8 G0 j. E) N9 K, Q$ Q
  68. /*
    0 }3 `' }% X6 ~- m, R
  69. *********************************************************************************************************
    - ]1 N' n' ~; e5 e- T' U
  70. *    函 数 名: CPU_CACHE_Enable; T1 Q! A. l* D
  71. *    功能说明: 使能L1 Cache
    ; H/ c9 f2 y) h. r% M
  72. *    形    参: 无
    , x/ N9 @% [# x( e9 z
  73. *    返 回 值: 无
    0 x# r6 D  B0 B: d& X9 c
  74. *********************************************************************************************************: X3 s/ ^& l7 P
  75. */
    " g$ d2 z5 |( \6 Y+ v! |  ?% H
  76. static void CPU_CACHE_Enable(void)
    3 D& F6 C# e# h/ |/ [0 ~& u& |
  77. {
    ( m+ \9 |# r- U+ d
  78.     /* 使能 I-Cache */
    3 z+ V8 ~6 P% j
  79.     SCB_EnableICache();
    & e7 r$ \, g$ b5 w( C
  80. 9 R$ S/ ~# O+ M/ w( }
  81.     /* 使能 D-Cache */3 F0 d2 _  R0 }: A
  82.     SCB_EnableDCache();
    ; Z& V1 a3 }! V, C+ x6 i9 M
  83. }
复制代码

& x* z% N2 R# y& l$ u! ~  每10ms调用一次按键处理:2 m( `+ `' @& \* N5 R

/ {9 W9 s" v- M2 t1 `) X3 _" b按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。% q  c% _. N) K$ Y) I0 C1 Q
9 n$ m& O. d& f! k
  1. /*
    + q3 F: D* m* L  l  J% M
  2. *********************************************************************************************************
    " U* F8 D7 `4 U# c# W$ [
  3. *    函 数 名: bsp_RunPer10ms
    . k+ x6 X3 ?: t* I
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求* o) P* s1 C5 a' X0 Z5 U
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。7 W  P7 E) t+ k4 W( C
  6. *    形    参: 无8 z$ ]% F- d2 `. J; p% O
  7. *    返 回 值: 无
    , R, z! i2 x: R) B
  8. *********************************************************************************************************6 S7 @5 Z) }, \% f
  9. */
    5 c! z: A8 n( k- i* k1 }4 h
  10. void bsp_RunPer10ms(void)
    - f! X6 ?9 U. c8 b7 d
  11. {; c0 `. l; `! \3 G1 z
  12.     bsp_KeyScan10ms();
    / ~2 L4 x  J0 h: ?
  13. }
复制代码
' v9 l- @+ G) |/ b  N' `
  主功能:
, \7 y! t. l; C9 `  x5 F" k/ P
7 d' n0 |; t9 d7 T: `+ B主程序实现如下操作:
& ^$ T5 H7 b) ]; Q' _- k
! `* A5 ?' P5 ]' c$ L4 }2 N  启动一个自动重装软件定时器,每100ms翻转一次LED2。3 J: z  b- T- l* l% p7 m" _( W$ j
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
1 `  U  Q( g9 x7 L0 q  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
6 W) ^; Y1 P& R1 {  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);* n1 Y# N. j8 h1 t% a" ^
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");& [( C8 m. |! |# v" o
  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");+ J& ^5 m. H4 n/ ]% ~* t1 h& t* U
  printf("【Z - 读取前1K,地址自动减少】\r\n");* A; K/ W. R( y' u: g+ u
  printf("【X - 读取后1K,地址自动增加】\r\n");) q& g% v: n7 A
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");2 {  e6 u" U3 l8 y9 m
  printf("其他任意键 - 显示命令提示\r\n");
8 i% i2 @) B/ D* F& ?
  1. /*( F( U2 \0 X% s1 w
  2. *********************************************************************************************************5 _5 Z. N& {+ C6 R4 w
  3. *    函 数 名: DemoSpiFlash
    # N7 B1 f* G7 _
  4. *    功能说明: QSPI读写例程5 m7 Q6 i: J0 O) P
  5. *    形    参:无
    ) Z) x+ C% N2 w
  6. *    返 回 值: 无) W' H( ^" o  Z) o8 y: B* Y9 x/ d
  7. *********************************************************************************************************; T/ T! V) P) X+ M. J
  8. */
    ! U- n5 ~+ ]. c
  9. void DemoSpiFlash(void)# a2 I6 K, y- e8 |" X! ]; [
  10. {, M& {8 f- S' `6 T
  11.     uint8_t cmd;
    . s7 q  ~. S- x) r
  12.     uint32_t uiReadPageNo = 0, id;) ]' D, a. @1 B

  13. ; c- e8 Z0 J2 k( I5 _
  14.     /* 检测串行Flash OK */2 ]  Y" H& }% D9 f
  15.     id = QSPI_ReadID();0 ^3 `3 {7 a* ~. F
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);- x" T" r9 @5 E& v; N  [/ O5 h
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");4 ?  A* l, _+ x; R# G1 W+ C

  18. ; F) d. u+ D+ T3 Y, @3 u
  19.     sfDispMenu();        /* 打印命令提示 */
    2 _( `0 h! I4 d- [: w) }4 J6 ~, l
  20. 0 A- x% v. o. G* p9 g: |: h3 |$ f
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */% u+ U0 M: @4 n/ E3 M
  22.     while(1)
    2 x+ F/ u7 X' T& p: T6 s5 _
  23.     {
    4 @, ]9 Z; Y2 q) g$ N- m3 h: {& e
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    + r7 g8 j6 l; z- Q
  25. ( N7 u3 L6 @, t9 {0 h
  26.         /* 判断软件定时器0是否超时 */
    ( w/ R+ J- J2 v
  27.         if(bsp_CheckTimer(0))2 k* t$ r" n& m: i- F2 T  ?/ m. r
  28.         {* o3 b2 ^+ O, Z# F. |& h$ w. y
  29.             /* 每隔200ms 进来一次 */  ) j; [3 |: d/ a0 W+ ~: U+ M
  30.             bsp_LedToggle(2);' a) e# V5 k. x' Z: D7 L
  31.         }/ h; _6 E$ x  I
  32. 6 t; f+ b3 {1 I% p9 Y
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    % _. V( D0 ], W
  34.         {
    6 X$ X% c' y- A+ K
  35.             switch (cmd)
    6 C* ]6 L/ \/ S2 V+ m$ s
  36.             {( @$ `; [  O% g. P! R, o
  37.                 case '1':
    ( c# M( ?4 X! U$ Y
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    / n* I! e+ z! p
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */2 p+ {# k* ^9 l5 F
  40.                     break;( N( r3 V& l* }& G. o
  41. ; B  M) O' A6 y
  42.                 case '2':6 n6 h2 b. f. k
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    5 A0 r: T# X' ?* |$ W  x
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */; |9 [' V* m% i- ~4 i: h2 C
  45.                     break;/ S2 e( b, v' C- j; w6 L% M% V
  46. " v; F8 J' r! P: |" P% F
  47.                 case '3':
    , j0 x$ P9 W& {) S% q" n/ Y
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");+ h) i) p% X$ q/ Q& k9 ?$ Z
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */: J! c6 \! P! l
  50.                     break;0 g1 _/ B2 X! F: b: c3 p# c7 @
  51. ; A4 l  T# \% w/ @% O
  52.                 case '4':
    " N' D) G0 T7 B! l
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));3 m& N8 m; l3 f$ Y4 H# i# ]
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    1 U# E" x$ s' a7 a
  55.                     break;
    ! e% f; n' u- Q% ~

  56. ! @( ^- _" Q5 J% _0 c) k6 V
  57.                 case 'y':. G; N5 j# d- \
  58.                 case 'Y':
    & n0 I9 Z, z/ l2 O$ e; q
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");0 G( S2 s3 T/ ~
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");" T& G7 ?# [; y# r( [
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */. u( D& A7 h1 C/ F; t. O0 g
  62.                     break;
    & {& s2 Z, L/ l9 d3 A) T+ b
  63. ! q% P8 H4 z# O; ~7 d, I8 e$ m
  64.                 case 'z':& U- ~! W6 I. ]3 a2 @
  65.                 case 'Z': /* 读取前1K */
    ! g  z0 ?! j, n
  66.                     if (uiReadPageNo > 0)
    & R# B/ ]# }; [! Q' K' c; e
  67.                     {
    / I  h0 m/ t: Q' a6 s  {. \6 y5 v
  68.                         uiReadPageNo--;, A, V( _9 K# G. K% J3 M0 u
  69.                     }- l# u; {" l# Z& U- X
  70.                     else% _5 {' c' l; V! g$ T
  71.                     {# ~. D: k  ~& e% p' t% f+ Q; \
  72.                         printf("已经是最前\r\n");: ]) O  \8 x4 c  q/ F- q
  73.                     }
    * ?1 t0 P" N. s' d/ {$ `6 ^3 y
  74.                     sfViewData(uiReadPageNo * 1024);$ K# c) @* Z9 i" n# ^, x7 Q$ S, _
  75.                     break;9 v8 b) H2 l6 B$ q; H
  76. ( o8 C7 v/ P2 s* j! l+ h5 v
  77.                 case 'x':0 O0 ~; K$ j* c- c: h7 i
  78.                 case 'X': /* 读取后1K */
    4 A8 A( s  Y3 d+ J
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)
    * D' a& m: g% E
  80.                     {+ d5 D; @3 p; l7 r* O/ y" d5 b
  81.                         uiReadPageNo++;# D6 ?) v6 [6 a" D/ g& F1 r
  82.                     }
    0 R" R/ w  M, ]( Y
  83.                     else
    $ V; W$ _# e7 c8 L1 V* O
  84.                     {
    ) M" _" ~' i, B) f- N
  85.                         printf("已经是最后\r\n");
    $ f- R8 e: Z; l
  86.                     }5 w* o9 ^! C& v$ T
  87.                     sfViewData(uiReadPageNo * 1024);7 z2 e5 ^7 ^6 ^
  88.                     break;1 i3 p" A: n  `/ L
  89. 5 U9 {/ Q5 Q6 y/ M8 e
  90.                 default:4 z, W0 v, E# ~$ P
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */8 c' }) V0 x- `* u- `# ?# r
  92.                     break;
    7 d  S3 f$ ]  f5 }* C+ C7 d
  93.             }- \  F3 Z2 _4 a2 H  |0 K
  94.         }5 v' x7 n& q. k0 B' ?( q
  95.     }
    5 ~! k1 A$ J0 I* P6 ?5 f. j
  96. }
复制代码
5 \+ b% E0 z- N
79.9 实验例程说明(IAR)
- r9 n2 Z( n6 J3 z) z: @1 \! M! N配套例子:* @* E2 m. `1 _' b% l" Z
+ `2 F$ S4 A; O) I. X& q" \  \
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)( C$ V8 z0 F, v5 w* g3 L% ]5 t0 j5 q1 u5 L

) Q' v3 H+ w3 T) y/ a+ J  _V7-059_QSPI读写例程,查询方式! I9 G  R' R* t
% q  L3 P' m! j& ]) S/ a0 Q
实验目的:( @. G! x- K  F' K
8 r* }6 R+ ~1 A2 M) V* ], o/ v
学习QSPI Flash的读写测试例程
5 p6 b( v3 X' A% g; u* c实验操作:
6 C# h" B6 M) w$ l8 V8 J8 N: S- B" i
: J, e9 T" z, ^8 f0 [( E1 f; H( r支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
: E& a9 O. N: b& c7 C7 k+ B* `printf("请选择操作命令:\r\n");1 e' i# b) N; A- x
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
' P; |& d) E6 [7 c6 Aprintf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
7 o$ O& x4 e' d9 l6 D/ u2 I/ \printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");8 m- N  x- r  [9 U5 _
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
- L( C7 e, u- T* @3 a6 z/ {printf("【Z - 读取前1K,地址自动减少】\r\n");* Q- W& d3 J9 c' U! Q/ l
printf("【X - 读取后1K,地址自动增加】\r\n");
5 s; \$ U3 d$ E' }7 U. ^; rprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");& O! r0 m5 ]. o/ U, j, L& |8 R$ i
printf("其他任意键 - 显示命令提示\r\n");1 t: q1 y9 n: b( D0 k
上电后串口打印的信息:
% ?7 m+ a' V3 D. O8 W6 {! j3 J; X: E' K: ?
波特率 115200,数据位 8,奇偶校验位无,停止位 1。- o9 S/ \7 U; Z/ ]. d
: l- _8 ^* V9 |5 ]* `# E, C; v# h/ t
3ff8536496d184105fc92ca093930e2b.png

/ f; `/ `4 {) v# B/ ~. c) L* e! C: o/ r
程序设计:1 e9 E. y* O3 W! D" @* i; ]
( v. M* u0 X- b& Y& b  b8 R
  系统栈大小分配:
1 ^4 P8 U/ b9 V: [+ H9 G
3 x) P2 `& u0 u! x  v! h, J
29c63afe41336006cacb94f46fe77b35.png
' e) c7 m9 i: b, Y6 B; ?$ N% D

7 U# U% @3 [! Y* A; g0 W! `+ K: `  RAM空间用的DTCM:
  b9 i) O' ]5 ^4 i  a
. b4 l3 H' ~" S& B2 ?" `# h& D
4dda996917dfda9ff3ebfb7ab53d3531.png

7 a( a9 p" e! S5 S& P6 ]+ L8 ]- l8 l3 K
  硬件外设初始化
! l; F. d* k4 H. [: x$ F2 J% R) j# d, _
硬件外设的初始化是在 bsp.c 文件实现:+ V2 S+ q9 j) K$ e' v7 W

+ f8 r1 c6 o, D- N
  1. /*
    4 f/ M5 \/ _4 M9 k& D! U0 k  }
  2. *********************************************************************************************************
    . q% q/ h6 v) z( {' L$ N) ?5 B+ J1 D
  3. *    函 数 名: bsp_Init/ e: |4 P7 r+ u6 m: G" q
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    & q- C3 _3 Q7 @. U' O) {
  5. *    形    参:无
    + O2 m- ]8 _+ c1 K
  6. *    返 回 值: 无
    / |/ L4 ?- x' C( d  F4 q
  7. *********************************************************************************************************
    - l( Y7 Z" N. l  P+ ^- x
  8. */( J* p2 L7 ?8 i7 w3 B
  9. void bsp_Init(void)
    9 t: {1 g% T0 Z# v! w& m7 B- b1 U6 |
  10. {
    . p& D8 V, V. h$ S3 Q$ m( c
  11.     /* 配置MPU */
    / d1 p/ O0 F6 ^9 C  J& k5 ?. a" d9 n
  12.     MPU_Config();/ o, `$ T) m5 s- d* p

  13. ! A6 Q  {% d; b
  14.     /* 使能L1 Cache */
    ( t+ E. b% g- |1 O3 i% l1 D3 ?9 B: G) C
  15.     CPU_CACHE_Enable();
    " Y. C6 G/ z3 @! x! Z) j  Y7 N
  16. * V7 _! @& G. R. F
  17.     /*   {- s! M( J( r4 p, o) O9 E
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:# F2 m; u! k  ~% L& f! ~2 s2 s
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    1 v* z; |4 W6 `* S1 C
  20.        - 设置NVIV优先级分组为4。; m& F- Q4 S4 L$ x# a" ~; P
  21.      */8 L7 Q% s3 H% b
  22.     HAL_Init();
    . Z: D' N$ d! i$ e) m' d

  23. . j( O3 j* c' K  h2 N
  24.     /* - W( O/ n5 ?7 m* T* q9 `  w
  25.        配置系统时钟到400MHz' D3 S' [4 m) H; W6 @  U
  26.        - 切换使用HSE。
    9 i3 Y) Q8 p8 h
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。  }9 x# c8 g; Z; |( V# P6 @0 A* J( X
  28.     */2 T% O* K- ^1 V" Q
  29.     SystemClock_Config();; w8 Y6 B8 T( Q+ Y
  30. * ]9 r4 \$ n9 ^* X9 A
  31.     /* - w7 E) `- Q* U# H& g3 W; z; i
  32.        Event Recorder:
    . Z/ V0 ~$ j% a* [0 d
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。, E1 S) \5 p5 O
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章8 L- W/ Q5 x2 U# w
  35.     */    ) {& K9 A# W' [6 B
  36. #if Enable_EventRecorder == 1  
    ( ?7 y" I( B, U
  37.     /* 初始化EventRecorder并开启 */
    . A  a. K3 N& k* I, p
  38.     EventRecorderInitialize(EventRecordAll, 1U);
      q( x. h5 |* F2 a- ?/ C, J
  39.     EventRecorderStart();
    " l, q) J  o9 i& @# {
  40. #endif
    3 a' N6 n" e$ u. d$ k( b; D
  41. ) @# l5 O+ b. k  h9 r
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       2 J/ ~! s* D: E+ ?6 T6 S
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */# l1 E- Q7 A* k9 ?2 W
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    / \. _2 L8 m7 G9 }# L
  45.     bsp_InitLPUart();     /* 初始化串口 */
    0 t3 I: o! G/ N# y2 g
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    . z8 V1 d9 w' m. ?& j  S2 p2 M" n' T
  47.     bsp_InitLed();         /* 初始化LED */   
    : ]# h0 f" `  e) R
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */, I/ d/ }! \# q" G

  49. ! `- u. {, F1 y: J- T0 d
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    ' K+ S2 }5 [+ K
  51.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码
/ \* u8 @5 A( ?
  MPU配置和Cache配置:
; y; F; f, A' h" ]& l$ e9 Y# A9 }% Z; l% f; k4 U) l+ T, Q% W
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。. w4 n% g* f  s* _
# V! D0 X9 R" W, o9 v$ g9 Y
  1. /*
    * [3 E# d) p* A/ l" y6 q
  2. *********************************************************************************************************/ W0 m' d, l' V4 b8 T' L
  3. *    函 数 名: MPU_Config6 J% N1 A" v' z* n( Q1 Q+ t7 |/ ^. n
  4. *    功能说明: 配置MPU- V; j1 }: o) D4 W7 W
  5. *    形    参: 无
    : W1 c; B; p' f2 o6 _6 ~8 k
  6. *    返 回 值: 无4 U( b5 W3 x3 ?+ x0 W/ M
  7. *********************************************************************************************************
    + ]9 c# b7 ]' X( F+ {5 R; ?+ r: U
  8. */
    2 W( {, S: b5 x2 D6 G7 _% g
  9. static void MPU_Config( void )8 j! f8 U: g0 n  @2 y% Y
  10. {# r9 ~. [6 h! m& q9 R6 Z! C
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    ) a2 o) Y7 \! B
  12. . T2 _% j6 M5 ~
  13.     /* 禁止 MPU */2 A6 U" d# V) g, z* ^1 N
  14.     HAL_MPU_Disable();
    8 e! V# e. o1 K$ s# n  v2 F
  15. " P& f, `8 _8 ]/ L5 k( {
  16. #if 0/ S2 z( l5 z( T
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */, K5 K0 `: f) ]( l0 E& ]& s
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    1 p* I: X, L- \% ]+ |: R9 O1 @
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;* Q" w' v& g, }- O9 T2 f! Q
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    . l# w7 k- @2 i! r
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;" L5 L0 `4 p0 A
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;% ?9 `" w+ F" x0 H$ ^# B
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    ( Y; f7 ]3 u6 z* D, T. J
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    7 o. _! c8 z; b' ]- a: U8 T6 ^4 d
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    7 l# K) f2 @8 ^* j
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;, B* n3 q2 @' C5 A  Y' R+ h& F" i/ o
  27.     MPU_InitStruct.SubRegionDisable = 0x00;" G6 |( r1 b' Y3 T4 j1 v
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;, \  F! m* {  C" d' i+ \* Y/ [

  29. 3 Z* m/ [) i4 }3 y/ P4 _
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);; Z, D! D0 A, `: d. R' q

  31. % ?) E" S9 r. u4 [1 D( a
  32. #else
    ' K/ C( A: d; {  M3 M7 t* R$ E
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */9 d  t0 l% ^* i. c
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;+ `7 R0 |& Y7 ]" r1 O
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;
    " z3 ~1 S# `1 m6 ^$ ]9 ~7 U
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    ( f* p" V( ?7 ~( U
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% A; o8 B6 S3 Q0 y0 Y% j8 s
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    " F( p; }! f( I6 A. B" R6 R! I
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;' o- q) g9 ]) @8 U+ @0 O( a
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;1 a8 F' z; O) p" N' h8 x8 O0 U3 z
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;6 n+ I; E& f/ ~' j. \4 ~
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    5 E- F& g0 }; \2 x
  43.     MPU_InitStruct.SubRegionDisable = 0x00;, z0 h$ }) B: W4 ?
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;3 f" c2 V& a) W- N5 Y- p$ M

  45. 6 a5 {! I! t8 T8 o8 j  E
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);3 j# y! d% \5 Z. G
  47. #endif   
    + v# b: J' M- {: k- b/ I( ~; z

  48. ( F* N, B9 z5 p. \; `" W/ B3 ~
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */9 |+ g6 d- g- l- K7 O- ]
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    + A2 l( N3 ^' ~. W1 f
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;, G" c" U' m1 [! h: ?8 }  t
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    1 ^: d  {( X0 l% C% l( n
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;4 N. ^- |9 A6 }/ j; S( ^$ Z- ]+ _3 Z
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ; ?$ \+ [; t' G8 g+ [. D% B% q& w
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    7 ~( X8 J- _1 A+ T
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;' w5 I+ ^6 b" d' x. m. p
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;6 V% f: y4 P6 Y7 _( H# f" \
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;8 w1 A& W  e1 C8 n: g1 O
  59.     MPU_InitStruct.SubRegionDisable = 0x00;
    / T6 j6 b9 b0 T& d
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ( Y, W' }  a0 }# k6 o# i

  61. / c: x8 F: p# z, @4 I# `& s5 b
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);) ?; v5 a7 |" Q# x: p/ C% N" H. w" |
  63. & q6 i. S3 V) W  w% _1 C
  64.     /*使能 MPU */2 u" a: [* l* H5 k9 Y; S& h$ s$ t
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);) Z) y* w8 l  _7 Q/ Z
  66. }
    ' [$ u' _$ q; r) n8 p  M# s/ D' I
  67. / s' w% a; i. k1 ?" c: R+ S% y* E
  68. /*% r+ f, D0 b1 {" P2 D4 H
  69. *********************************************************************************************************
    + K  U7 b' u: d- d1 X. w! M, l
  70. *    函 数 名: CPU_CACHE_Enable+ ~& u0 R4 q6 e/ N* t5 F
  71. *    功能说明: 使能L1 Cache: p* H; G7 m: G% n3 |, r4 m
  72. *    形    参: 无
    9 z/ e) Q- c# D  s! L0 i" ~! _
  73. *    返 回 值: 无
    ! V' q+ l# m6 }/ [1 M2 [
  74. *********************************************************************************************************6 E2 `9 P; k9 I4 V: O
  75. */
    / \$ U' _  E6 R$ |/ e! K
  76. static void CPU_CACHE_Enable(void): j& o6 p* R1 i7 H0 q
  77. {" @4 x/ e: ~& {; y9 {- k
  78.     /* 使能 I-Cache */3 K% ^- Z( O+ ?1 j0 A6 ^
  79.     SCB_EnableICache();
    : M$ q- b6 o: c' x# |! _0 ^9 ~
  80. 7 y5 N9 i1 W- J6 K, [1 p
  81.     /* 使能 D-Cache */
    / Z. K4 S0 V6 I
  82.     SCB_EnableDCache();% i" [8 @9 K- k* i
  83. }
复制代码

4 S* `" p0 s# x  每10ms调用一次按键处理:. p! I4 n$ [; Z7 h

: i; L1 X- k5 d2 ^; w& g" w按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
' w2 \1 C( J" t& O; P1 {) n7 S2 c# w4 q$ U
  1. /*
    0 Q: ]1 S$ w! \  k& @* V! F
  2. *********************************************************************************************************
    - ^* A# x9 [, v1 a) q% g1 g1 l
  3. *    函 数 名: bsp_RunPer10ms
    ( _8 o# `: u2 M5 O# g! r- t
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求" Y! w; W( M8 g% g& A
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    ( |* O, z0 w; D
  6. *    形    参: 无
    / T& ~) u7 J* a0 l& t  S
  7. *    返 回 值: 无
    ' Z3 H# ^8 S9 @/ ]
  8. *********************************************************************************************************
    . T. j1 I: k+ Y0 I' ^
  9. */
    2 y5 H$ f# T: Z) J- k9 X. K
  10. void bsp_RunPer10ms(void)
    $ h9 D: e( v" r& P6 s
  11. {* l  q: m$ j( R' l
  12.     bsp_KeyScan10ms();
    * y0 B1 O8 A, s) j+ g1 f2 ~
  13. }
复制代码

. @. ]5 @2 \1 g% S7 g& s: @7 K0 n  主功能:5 q8 ^( s. M6 p  @# J. O
$ I( _7 q$ u* h' i% r% \. ?
主程序实现如下操作:
) q9 p7 W& \8 E: S7 k/ x7 N) c1 [9 ^. n. Y  W' D0 Y
  启动一个自动重装软件定时器,每100ms翻转一次LED2。: c( s* ^  @' O; v! ]) \
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可( a3 b% G: Z8 `* Z9 L
  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
: r$ T# r- o  j2 B) X9 r% f  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);' s6 i" x/ F3 {' n
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
. z% {$ c. Z7 e& C9 J( n  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");5 y% q- |+ e/ A) @2 m, G
  printf("【Z - 读取前1K,地址自动减少】\r\n");( T! _( S/ Z+ h# S
  printf("【X - 读取后1K,地址自动增加】\r\n");/ |5 v4 b: T$ Q5 R& o- x
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
4 S# a2 p; z' P  printf("其他任意键 - 显示命令提示\r\n");
; a* z/ R/ Z! n
  1. /*( M5 J( i. s: B+ ?/ }  k+ |7 M
  2. *********************************************************************************************************
    9 W- Z; u: v( X4 U+ p5 h1 T* M' _  o
  3. *    函 数 名: DemoSpiFlash
    - I/ S3 |4 T0 G5 Y
  4. *    功能说明: QSPI读写例程
    ) Q; T5 u1 g* h, M
  5. *    形    参:无
    . P; q% Z+ s9 S+ t; A  N
  6. *    返 回 值: 无
    - |, ^. S& F8 |# M  V# C9 k
  7. *********************************************************************************************************
    + N6 x9 _8 r5 ~5 s# B
  8. */
    / d: i" H5 e5 B( C2 R: W2 ?
  9. void DemoSpiFlash(void)
    . c5 ~, T6 _% w. K$ [: Y+ U+ ^
  10. {
    4 ]: q  S2 }3 v0 _
  11.     uint8_t cmd;3 g  \: F- n: d9 K4 D6 Q0 f  T0 v, N
  12.     uint32_t uiReadPageNo = 0, id;
    % C5 T4 f* M# P; f! I

  13. 9 @% Y1 X, x- a2 z! x& Y2 j
  14.     /* 检测串行Flash OK */$ T& V) R# X  d/ E9 `
  15.     id = QSPI_ReadID();
    - {# b6 I; Y5 P5 A' h( v8 v
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);
    : q- p7 w+ ?4 l
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");$ \( A" d3 A6 F# \2 K0 d/ w8 k. X

  18. / R6 [; f% M( K- r& Q" f' q
  19.     sfDispMenu();        /* 打印命令提示 */
    3 Q  U9 L1 L! L4 u" E
  20. # L' ?( z1 n4 A  x0 P& B+ _
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */) _4 s8 S5 Q3 c/ ~
  22.     while(1)
    7 C3 `; e- d, D
  23.     {* n5 Z( y" U6 R) p9 e
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    4 D1 l, m4 G( d& g0 ?
  25. : Z" J; m  g8 b2 a
  26.         /* 判断软件定时器0是否超时 */- Z/ M2 \; ]  ~7 h( T. r/ R
  27.         if(bsp_CheckTimer(0))
    ! Z  T( L- a  F* Q
  28.         {( c( Z) x; o- r, O
  29.             /* 每隔200ms 进来一次 */  
    ) x; o1 P0 h1 h
  30.             bsp_LedToggle(2);  {# o; k7 P4 r9 y
  31.         }
    * ?: \  Y: s& z8 V! a) N
  32. 8 o  k  X1 s$ |5 J- n
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    + @7 B/ a3 [* f' @4 D+ J
  34.         {: Y& g3 o3 v5 r) Z/ w" o. K+ O6 S
  35.             switch (cmd)2 H' l( I+ y, Q& J  l  ~
  36.             {
    $ |+ {6 F* U7 ^1 z3 n$ V
  37.                 case '1':- ~0 @7 X# r: _1 ^7 ?
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);+ q0 [* L' z& P" `3 _- x
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */5 L6 k% z  U$ i. L; o2 X8 W
  40.                     break;3 G+ F2 P0 N; ]/ c+ W7 D4 V* k
  41. 9 W- F3 s, t/ e  |" e4 d! ]1 |
  42.                 case '2':
    3 v" @: |' t  l  \
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);. q+ A& U! Z$ G+ z
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */: N+ l9 M" E" A* O) p2 w2 ~, T) `
  45.                     break;& x, X; x* O, J0 @! Y

  46. 5 K! l% P! f, \5 k3 }
  47.                 case '3':/ y; v, }% V$ G
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
    0 z/ l0 [6 Z0 q8 l7 I
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    3 t$ y7 g5 o' _
  50.                     break;  e. t3 X/ S; {: R- B

  51. # |9 {" g, F+ M
  52.                 case '4':' N, H* L/ K/ F2 |8 v, H7 i9 z
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));% E( R) V. Y6 o9 {- G
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */+ k2 o, D2 |5 z& N- e8 C! _
  55.                     break;
    1 v) m; @/ c. T7 ?3 Q1 F
  56. & l  G& B  A& J( v
  57.                 case 'y':
    5 T! f- y( E# N" i& w% Q
  58.                 case 'Y':
    6 g! B, k. h& h6 M4 V
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
    0 Y7 w' \( z6 v8 n- g- k) r5 ?" L
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");7 P$ e7 k+ [( p" @+ ^5 o
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */& p$ A6 r* A1 L' E# R8 J
  62.                     break;
    8 D5 V5 q1 A  d  n7 T0 O

  63. 9 J2 ~3 h$ I& p( M
  64.                 case 'z':. m7 F+ }# T' B; T2 z
  65.                 case 'Z': /* 读取前1K */
    7 P  q/ H0 k9 I  q& O
  66.                     if (uiReadPageNo > 0)
    $ P0 U' @+ p3 L' v
  67.                     {
    ; B# |0 y+ P9 @7 O6 e/ K
  68.                         uiReadPageNo--;) T9 F! y) P& i4 }
  69.                     }
    : x! \4 O. Q. G+ h8 G
  70.                     else
    2 N: q* u- M4 l" {) _$ F- H
  71.                     {
    + E' y6 c- w1 ~
  72.                         printf("已经是最前\r\n");5 ^  x" D5 d3 t
  73.                     }
    : Y2 L0 W+ w- K& C( v# h
  74.                     sfViewData(uiReadPageNo * 1024);4 ]% L9 u% N$ W' F8 j
  75.                     break;) L+ A. B! ]/ Z0 {' M4 f2 O

  76. + b7 w7 t4 L. s2 ~
  77.                 case 'x':1 S6 f2 @; r" T- G# k) ?
  78.                 case 'X': /* 读取后1K */
    ; U8 S) r  h+ |$ D# B% l% f
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)% `- l. S7 G8 `3 c+ D( ]3 k: Y
  80.                     {9 `$ R6 D5 L. ^' j; X* R+ y
  81.                         uiReadPageNo++;  h  J0 @: H1 R  }* s$ ]0 [
  82.                     }
    ' E( k/ F# p$ T
  83.                     else9 k! E  E3 \' w& L1 N
  84.                     {/ S+ T6 U& y( X9 x  x1 b' I
  85.                         printf("已经是最后\r\n");# ^: V) M% Z4 H' f' ^
  86.                     }+ \( U; {5 b# @( }2 l
  87.                     sfViewData(uiReadPageNo * 1024);
    4 F# j1 r* G+ O. Y8 X+ |! s
  88.                     break;% ~/ P3 C; P# c4 x; W

  89. : [7 U8 z  u$ b
  90.                 default:
    + {' G% \+ b" g
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    $ n* G9 I( M  z* Y8 E
  92.                     break;
    , }/ i! l/ ^' J1 J" F- G  U
  93.             }
    % Q8 ^# B8 N1 t! V! u
  94.         }2 e2 v. r5 i% q1 a" `. W
  95.     }& o' S- p/ {. V9 Y+ T. K+ Y- `
  96. }
复制代码

9 }5 C) A" l6 O& P; g4 h( B79.10   总结$ T1 Q% h, [+ a" P( A  k0 p
本章节就为大家讲解这么多,实际应用中根据需要选择DMA和查询方式。
& U9 k6 D- u+ b( s7 w7 k% A
+ `4 \% ~6 Y2 z" G8 a4 n* L5 t+ k9 Y8 g& @! P
f9d118d4b7d97794c046ced058924277.png
940488dfa9f0e39fce107fbd8fd9ea16.png
收藏 评论0 发布时间:2021-11-4 21:24

举报

0个回答

所属标签

相似分享

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