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

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

[复制链接]
STMCU小助手 发布时间:2021-11-4 21:24
79.1 初学者重要提示
, a1 w2 B( {- u6 m1 w0 C7 I 1、 学习本章节前,务必优先学习第79章。
- `( `8 t/ ]' J3 T/ ?) S' K* Y% F+ Z/ l7 M: B+ [7 A* O% h+ p. ~
2、 W25Q256JV属于NOR型Flash存储芯片。# O+ Z- [0 Z3 v; ], D: l$ X

) _4 c. n5 o" e: [8 [8 F% k 3、 W25Q256JV手册下载地址。- d4 M7 g8 [4 L2 y3 I
- B: u5 i  U+ J( @" \/ e2 a# f/ w
d158c855c3630d64e0d72336ffa1a440.png

: g9 ?. M2 Z* q- x8 s
; F: j8 a. E2 e4 k% j 4、 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。
2 X( [5 H* Z. q8 J0 z% M* n
# D8 K- v& b5 T 5、 对QSPI Flash W25Q256JV的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。! Z' M( T# ?/ w* x7 O  X( R0 ]
, E. F) O. c- v
6、 W25Q256JV最高支持133MHz。
+ x% s- Y- h6 K0 p" Z
, N, Z8 p( M& F# t7 i- Y 7、 STM32H7驱动QSPI Flash的4线DMA模式,读速度48MB/S左右。- _# P% M& L! p! B9 T. j, i
6 j( @, {! K  j; a- P2 C! M
8、 内存映射模式下,最后一个字节无法正常读取的解决办法。# J  G1 h: Z2 [/ H3 R. C

( h+ U4 _$ I9 i& P9 K2 ?. i( a1 z* Z 9、 本章配套例子的DMA是采用性能最强的MDMA。! J! |3 `0 N: g. Z2 D' M
' K# U7 q2 C  L2 D
79.2 W25QXX硬件设计2 T- E* ~4 h0 X* B
STM32H7驱动W25Q256JV的硬件设计如下:
$ C2 G/ a9 S; U* |) V- D1 L" M( C$ ]' S( w( S, a+ Y1 x$ `
43d24f69b56108e41724030a699d04ea.png

8 Y& r8 O! a% z) V9 r
, z# A  L0 |. n4 [2 _% G9 S5 g2 T关于这个原理图,要了解到以下几个知识:7 U) c$ z' H# [% H$ n

# F$ p" ^8 X) ^: n, \, t  V7开发板实际外接的芯片是W25Q256JV。, U4 a* I! R" K2 y0 H% H8 U
  CS片选最好接上拉电阻,防止意外操作。  I  Q9 C4 W9 i6 S8 u& B
  W25Q256的WP引脚用于写保护,低电平有效性,当前是将其作为4方式的IO2。* B/ g3 }( q% Q" N+ Z" a% [- A0 k* [
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。当前是将其作为4线方式的IO3。
% b% b8 f+ a0 b3 ?2 P% V. U79.3 W25QXX关键知识点整理(重要)- e- U( I$ S1 N# @* p! p
驱动W25QXX前要先了解下这个芯片的相关信息。3 t% e+ |! P# g+ ^( u' g! @

! U/ J' q7 q9 j
d211eea6c41b7da28846a28c61bfebf4.png
  w( E; g4 o1 j$ x* \, j4 a

7 O+ ^1 h4 B* X6 B* V1 {+ q79.3.1 W25QXX基础信息
  f; {( Z1 Y$ y; l/ t  W25Q256JV是32MB(256Mbit)。
+ V/ n8 k& J& Z! j  W25Q256JV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
" n/ I( F- y; r- [- ?, o2 i3 w6 R支持两线SPI,用到引脚CLK、CS、IO0、IO1 。8 _( R( v7 _( ]' _! Y" _! p0 x
* {2 ?, j# l; @0 N. {" b; |
支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
( y( J- q! g$ `* Z. W! ?8 z) q7 ^
(注:这里几线的意思是几个数据线)。
5 j2 w6 V; z+ c/ }
% u# P! r9 \6 v# w7 M. \4 R  W25Q256JV支持的最高时钟是133MHz。
+ o/ s8 n. p' Y$ X8 B  每个扇区最少支持10万次擦写,可以保存20年数据。
: R- ]) ^1 B& f+ D) Y/ y3 H9 Y  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
' L; r3 S( ^& R# b, s4 n  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
' P# o, y! U) W$ l5 Y3 j* D: t6 R整体框图如下:
3 u* I3 b# A$ \9 e1 x! C* l/ D/ T. I( D- Y) c
2da620aeaf02ba8b3035e0b864e140b2.png

5 c6 ?; o5 U8 X" L0 N. _4 D  `1 D, H1 \( b% ^7 K! _6 w1 I
W25Q256JV:; _: k) y6 l! m
! k9 J/ [+ C: s: `) D, _
  有512个Block,每个Block大小64KB。
; e1 u1 R! @$ x, r$ [" L# r  每个Block有16个Sector,每个Sector大小4KB。1 j8 [# I2 b8 ^4 ]/ D
  每个Sector有16个Page,每个Page大小是256字节。) M# i) \& e) g  ~9 }
79.3.2 W25QXX命令
: A. @+ M2 A0 ?+ k. R9 W9 Q使用W25Q256的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的QSPI,即4线SPI,并且用的4字节地址模式,使用的命令如下:
/ ~) {8 j  f' D0 P3 D2 Y
; E3 y. ~) U) T/ f7 s% ^$ o
77c20d25330bfc517d68b73c7751ddf3.png
" U/ `8 L' r: [1 ~, f4 H

3 Q% t4 z$ w+ t: x2 R, [0 h  R
ee48eed7fa217e118947120ca34f5263.png
' ?# d) D# }2 Y0 p

# |& e  c" W/ f% v; f. R当前主要用到如下几个命令:" a. n8 K: L% r
1 Y: K+ b( \2 [* \& @8 \
  1. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
    1 {- @" E/ K) k8 J8 t# A! `: c6 L
  2. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  
    . }( f$ ^# R  O' }
  3. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */
    ) `. w% G  Z% |
  4. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
    4 b2 j, p% `/ [8 \; \2 J6 n
  5. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */2 U& U( v; Q5 R/ v6 Q
  6. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */
    ! [5 _9 P; o' F: U9 H
  7. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码

% U9 B0 X. w' c9 e1 r79.3.3 W25QXX页编程和页回卷) p$ y1 b6 m. u& |, p
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):) m- J8 Q  r! i' U0 i" z2 T2 A
! ^" m- D9 s$ ^0 S
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        + h- o2 [% n; {  r! a
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    % F0 F; g  O0 Y$ f
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   0 A& \: n4 u8 d
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    - T( `) ~- I$ x4 O/ x

  5. " a" ]* S* |) D8 ^' m- C/ `
  6.     for (i = 0; i < _usSize; i++)3 G& D1 ^- y, M. i- G; w. q
  7.     {% F% T- U, e6 j1 q
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,& F( q1 L1 i, g8 c7 R' X0 x) Q- D
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   6 U, r7 h  o/ Q2 ?3 C3 D
  10.     }
复制代码

" ]# ], Y3 K" E页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
! E) J6 D( \9 {1 b6 I1 g$ S# p& @: D; Q* w" e0 z  _) [, W# t" k2 G
比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):. E) K% D9 e6 x1 w  S  \5 v4 ]8 P
$ S& ~  o0 Q% w" Z: ]
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};$ W- h* g5 r& y
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

. ~6 ~7 J1 t( g/ @: O  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
( s% S4 L: r9 |$ y( R  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);
' n0 Y8 x6 X7 w  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);: S" z) A* j6 y: T
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
: N8 ~4 g% b3 p2 B- B7 ]下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
, i( y9 J  W* y; |2 x
8 \, {3 P# Q7 z6 Z0 F' T
56240b94a12eb9a067e63db9803b13b7.png

. V. c3 a4 \% Y8 Q" R+ _% N6 O
2 D% X6 t( m- }( ?+ M! e79.3.4 W25QXX扇区擦除
7 i9 }3 _4 |) u9 E, m: q8 bSPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
- I$ W. e6 f) B+ T: F4 Y: u7 p% N3 F+ m% R3 g
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。/ ^4 Y$ M: v! y

1 G) b8 V7 b" g2 {# Q- R79.3.5 W25QXX规格参数
, ~- o" K0 ^  a( J0 F1 I, L这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
6 Z; E4 b/ n5 G  C* v, e1 w0 ^! ~) N" ?' z
e21b7448e777b6e8e376fb71ea3e75ee.png

8 \9 ]6 W% d& f& l1 F+ y, R
4 R  ~" m. C' y( g" [7 n) l8 G6 j4 H5 {$ X4 _$ ]
  页编程时间:典型值0.4ms,最大值3ms。
7 |/ D3 J( u& X7 b1 V% b# x  扇区擦除时间(4KB):典型值50ms,最大值400ms。
8 q9 B2 |0 K+ w( S6 d" p; p3 X1 \  块擦除时间(32KB):典型值120ms,最大值1600ms。
6 o- {: b$ k) h1 R  块擦除时间(64KB):典型值150ms,最大值2000ms。' ^5 y! r4 `# _
  整个芯片擦除时间:典型值80s,最大值400s。6 ]: \% c# l# T. B9 _3 {. E
" t5 q, n& F- L' [$ ?
支持的速度参数如下:
1 {1 `" f8 ~- k0 u, Y- X2 Q& k# e5 p, X" w% ]
9ab6f9e23e8cfb4e497e67913dda1d7e.png

0 m# N) }  S4 M) T" P7 m6 k  K4 f0 ^7 r+ @) @5 B' ?
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
8 n( K* U, _: ]
1 j5 l+ E# I% Z% @5 b' ~79.4 W25QXX驱动设计
; v3 f+ S! ?$ [W25QXX的程序驱动框架设计如下:
* }, Z$ a& i# X9 w4 ^9 D5 W, Q! r2 R7 s* R& b" M, ^% r( {; J- q" a
0edab8ae6a09033608ede0892279b72a.png
  d; l& o) l& c% c# q+ R

5 w/ R( ?# \  J( a( Q: f有了这个框图,程序设计就比较好理解了。4 x7 _+ M' m& A3 \

0 H6 d! W! F4 f8 P0 X79.4.1 第1步:QSPI总线配置
4 z+ G# K" e  N7 [( nQSPI总线配置如下:2 h) Q2 B, p9 P- q- P

. t+ @, {& v/ S* c; y4 L; b7 E
  1. /*
    ( e) d; t4 B/ i: ~& L; {
  2. ********************************************************************************************************** D; W& [7 B2 ^* |/ B( o
  3. *    函 数 名: bsp_InitQSPI_W25Q256
    . O1 _3 l. a% i" r; r
  4. *    功能说明: QSPI Flash硬件初始化,配置基本参数
    5 t  L7 H% Q2 g6 P3 P: q! K
  5. *    形    参: 无. v; }  J2 L  S) _
  6. *    返 回 值: 无4 K. H" N* }, ]6 d2 U
  7. *********************************************************************************************************
    - n0 p8 d5 O' @2 D" V$ y
  8. */
    2 p1 P0 ^2 }: B! [% f; J( g/ q# [
  9. void bsp_InitQSPI_W25Q256(void)
    + i. p0 Z0 n5 o0 f
  10. {7 k/ T+ @6 s, f
  11.     /* 复位QSPI */
    - j2 ]% I& R6 {9 b/ _9 _
  12.     QSPIHandle.Instance = QUADSPI;
    * z% z# j- K  e9 \: q  Z7 g9 Q4 ]1 N
  13.     if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)5 o9 l# n6 X5 a" a3 ]# d
  14.     {
      z2 h/ ]3 t5 U9 {9 y
  15.         Error_Handler(__FILE__, __LINE__);2 A: d- x7 C+ {+ m6 f
  16.     }+ w5 r) N: t( Z) T! n

  17. # b+ W! c% r; @9 u' ?. G% O
  18.     /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */
    8 k6 A" B; \; d8 ~0 p
  19.     QSPIHandle.Init.ClockPrescaler  = 1;  
    1 ]! G" ]! k$ r  u: J+ n1 O
  20. / i) e" j* _7 u, ]1 N
  21.     /* 设置FIFO阀值,范围1 - 32 */
    ; B# R" A9 Q( P: B" q( f; G: k7 p
  22.     QSPIHandle.Init.FifoThreshold   = 32;
    : f/ W- V3 C5 }  F
  23. ; e) t6 M3 G4 v
  24.     /*   Y( X- b% Y& p' j
  25.         QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。
    , u9 u) t- h9 N, T, A. a5 ?2 u
  26.         在外部信号延迟时,这有利于推迟数据采样。5 Q  e  M& t# O/ G1 }$ Z
  27.     */
    + x% W6 ~/ m4 _0 S
  28.     QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
      L+ N7 O7 M7 X

  29. , i/ r( Y) Q- @5 h. S9 r: a! `
  30.     /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */
    $ ]" k$ ]  J3 N4 S# U; y
  31.     //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。
    / m- ^; f' q4 }6 z$ @
  32.     QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE; - ?) M) o. I- i8 K9 l6 d& y3 g
  33. : n% w% r9 a% m! Z  m8 f
  34.     /* 命令之间的CS片选至少保持2个时钟周期的高电平 */- z' u; G2 w4 x# F7 a( a& X- T% m
  35.     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;2 P: F. `9 _8 D0 b& H

  36. ( B) ~4 ]& y6 F5 k1 D7 y
  37.     /*
    3 q$ M' O7 g' |- q4 o
  38.        MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平
    & }+ k4 h) b, M! W  D+ G, _
  39.        MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平
    0 L- J# O1 t- g8 `$ A
  40.     */
    $ y( w' u% _" ?7 J) S* C- k4 D7 P
  41.     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;2 ~. S! g& g3 l& Z/ {
  42. : J- f+ y5 Y4 u) {: Q, h5 c
  43.     /* QSPI有两个BANK,这里使用的BANK1 */
    ; l; q! g6 A8 g$ P# M
  44.     QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_1;
    & g7 e& E. R. f& u9 t! y

  45. , n' {7 R0 i! R
  46.     /* V7开发板仅使用了BANK1,这里是禁止双BANK */$ ^% j, d7 v" P/ y2 Y& u
  47.     QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;8 J; j0 v5 g( X& k- c1 K2 H/ h

  48. ! P" N. S5 v7 I) {9 E
  49.     /* 初始化配置QSPI */1 v+ Z" Q) J2 r5 V8 P' `+ Z
  50.     if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)+ E3 L; Y0 i/ I
  51.     {
    ( h0 C0 ]$ d# t
  52.         Error_Handler(__FILE__, __LINE__);
    / H, _0 N8 C! ]7 Q4 H8 h6 i
  53.     }    / Y/ @: _6 e" f7 n* w7 H
  54. }
复制代码

; ^3 z+ \* f, `/ P/ `QSPI这部分配置,要特别注意Flash大小的设置,这里做了特别处理,本来是应该填入QSPI_FLASH_SIZE-1,而我们实际上填入的是QSPI_FLASH_SIZE,主要是因为内存映射模式下,最后一个字节访问有问题。
; p9 x$ N% b" \: f8 }( g% l. h/ e" u( y, S3 g. @1 x7 c2 b4 B8 F
79.4.2 第2步:QSPI总线的查询和MDMA方式设置; U/ ?2 {  l. j& f8 H
本章提供了QSPI Flash的查询和MDMA两种方式的例子,驱动的区别是调用的API不同,查询方式调用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。
- l; j$ d  ?2 d! h9 [
4 e% m1 x+ I) C79.4.3 第3步:W25QXX的读取实现
6 ?; X6 j# B* @! C& `4 j; v. F注:这里以查询方式的API进行说明,DMA方式是一样的。, w' w0 g5 D0 F& |; g. K' S3 q
, b# J7 s! B+ W' O2 `  K
W25QXX的读取功能是发送的4线快速读取指令0xEC,设置任意地址都可以读取数据,只要不超过芯片容量即可(如果采用的DMA方式,限制每次最大读取65536字节)。* w# K- N6 R- f3 B4 W! U+ U

1 Z* M4 N6 e$ r: D; i
  1. /*
    * P, o+ }. P" ^# D# ^) H9 C9 t- D
  2. *********************************************************************************************************
    4 G2 g! c6 V( {+ w- C  `2 T
  3. *    函 数 名: QSPI_ReadBuffer
    8 Z/ B# T8 Q7 m0 D" w$ C' M
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。5 Q4 @" c3 M. V. ^* s; U9 K
  5. *    形    参: _pBuf : 数据源缓冲区。- Y2 n1 c0 k' a' c
  6. *              _uiReadAddr :起始地址。
    ( S8 a0 O- k' a
  7. *              _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。
    5 E; X/ ~' o9 j# ?. p4 u1 }$ w
  8. *    返 回 值: 无
    / x" q7 ?& B; |9 y+ C/ k2 \" }
  9. *********************************************************************************************************" _0 E9 k; ^( k6 A4 q7 a6 {
  10. */
    ; T' j0 m( g1 y8 Y
  11. void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    5 t8 P+ c( }$ i" S
  12. {
    . N( Q6 I: }4 Q8 y7 S' R
  13. 3 i1 W$ A8 t* L( o4 o3 \
  14.     QSPI_CommandTypeDef sCommand = {0};7 U- v7 l1 s5 H* o; @, M! i) R+ Q) h
  15. 7 _5 X+ p) k6 a; d' E
  16. - p/ p$ x& F/ V7 o4 d3 a$ D4 q: w
  17.     /* 基本配置 */
    6 |1 D. S, P% J
  18.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;        /* 1线方式发送指令 */
    , r' p$ F, y" [
  19.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;          /* 32位地址 */
    . ^, T- n2 L/ l7 s1 D
  20.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;      /* 无交替字节 */7 s0 Y+ L9 l& e! o1 k
  21.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;          /* W25Q256JV不支持DDR */3 N; e) |8 _! d& i6 {
  22.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;      /* DDR模式,数据输出延迟 */. b, _8 W. N- M7 A+ P
  23.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;        /* 每次传输要发指令 */   
    ) D; a* o4 \8 O6 z& \/ w

  24. ( n3 d1 q+ s' M* Z5 n
  25.     /* 读取数据 */7 O) D- |3 _; y( C9 a" L4 k& G! f
  26.     sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */9 e  F! r  e0 D- l
  27.     sCommand.DummyCycles = 6;                    /* 空周期 */- K: V8 @9 `% K& v
  28.     sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */
    9 i0 r$ c/ R# x4 j+ G% K3 i
  29.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据 */
    2 u8 S. N3 T5 h5 e* J
  30.     sCommand.NbData      = _uiSize;              /* 读取的数据大小 */ * f, O- s) J" v, Y6 l
  31.     sCommand.Address     = _uiReadAddr;          /* 读取数据的起始地址 */ 6 l+ L. \; V/ T3 d+ ?6 h$ s8 f
  32. 9 H6 ]5 d7 A* y' p& c) |
  33.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    & l) Q. S* l/ L; s5 k# J
  34.     {6 W, ?" O% I/ G% `, o4 o  Z
  35.         Error_Handler(__FILE__, __LINE__);
    ( T: `% Q3 N7 `) n9 Y
  36.     }) N, V4 I% G7 T8 ~+ V5 W
  37. 2 n1 }3 b$ y# X* M% W
  38.     /* 读取 */) u; Z! d. Q% Q
  39.     if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)9 d. S4 M2 N% e% d' {. C' r
  40.     {
    0 _0 u, p/ E* s' y! p6 L
  41.         Error_Handler(__FILE__, __LINE__);
    1 p- ?6 y4 B1 `3 {3 y9 e5 q7 T/ @
  42.     }   
    5 L1 Z+ x, p0 v' E3 n/ P! r
  43. }
复制代码
  t" f+ N7 u3 v3 ~5 k" H% ]6 P7 L* \
此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
9 e) u) R4 N: Y3 v$ |; i: H
/ ^: W3 D; [2 b3 _( |
6c1787c10d01bbaf5602747a947053b6.png
/ P2 ~1 h' ?, k# |! u
$ T5 Z& e0 `/ K4 P4 K/ \% {, ^
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
4 \( ~  v+ a, S& N9 S9 Z8 R
* l% v2 x* c, w9 W+ d; z' U7 Z2 esCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
' i7 ~8 A! N% RsCommand.AddressMode = QSPI_ADDRESS_4_LINES;
  X8 s9 l* N3 ]/ O( l+ L/ zsCommand.AddressSize = QSPI_ADDRESS_32_BITS;9 U; D# o: ^  d7 [4 H. ?
sCommand.DataMode = QSPI_DATA_4_LINES;' m9 h% C6 Y% C$ }, Z4 ^

/ I; s- X( r% _& L: C' @& r- M79.4.4 第4步:W25QXX的编程实现
. J9 g# [* `$ d$ @8 A8 _% a注:这里以查询方式的API进行说明,DMA方式是一样的。) x1 L: r9 r8 [& c4 X  E" E
  s/ T! S, p- H+ E! z( G9 `+ ^! R
下面实现了一个页编程函数:; m+ U7 @2 ]# {; Y
' j# E8 u' ]9 S
  1. /*
    # \" X6 z, G/ F$ E3 Y2 \8 d
  2. *********************************************************************************************************
    6 @( p3 k% h& i' Z
  3. *    函 数 名: QSPI_WriteBuffer, D, l  A5 |5 }
  4. *    功能说明: 页编程,页大小256字节,任意页都可以写入
    , e5 p/ v8 G. p  M: ]& z
  5. *    形    参: _pBuf : 数据源缓冲区;% `/ u! U. ^" u( l+ A% n
  6. *              _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。
    0 ]5 i' X/ e2 N; [4 ~
  7. *              _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。
    ; c( l) x, x0 X  ~9 O) z
  8. *    返 回 值: 1:成功, 0:失败
    + e* X7 V  }+ Q6 _
  9. *********************************************************************************************************/ U+ }% P+ g# E8 `2 ?
  10. */$ r" Y9 ~. l1 q; j8 U  A
  11. uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize)
    1 z) m; C- `  Z$ o
  12. {: D2 Z: {& p, d
  13.     QSPI_CommandTypeDef sCommand={0};) z6 [) c" p9 l: V

  14. 4 e/ D* H- X# f$ l% ^$ V- h
  15.     /* 写使能 */8 o7 V- ~) _! {
  16.     QSPI_WriteEnable(&QSPIHandle);   
    , m% ]% u. o5 a: J) ]) O: Y
  17.   s8 x, V2 K: g: g
  18.     /* 基本配置 */+ \& w/ V4 e1 H; Y, G& c
  19.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */0 p: n; m, L( u- ]) w/ v
  20.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    " Q( O6 q0 s. Z4 c
  21.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    7 C: |2 t2 _6 o1 H; ~
  22.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    / \5 Z  V* K& c
  23.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */1 o: f" @, Y5 ]* q, F+ G
  24.     sCommand.SIOOMode          = QSPI_SIOO_INST_ONLY_FIRST_CMD;     /* 仅发送一次命令 */   
    3 M: R4 A, e' b. _. h  B

  25. ; U# Q4 H; g, H% o5 a8 }
  26.     /* 写序列配置 */
    - P8 C  R& D2 i
  27.     sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */7 u2 Z; ]9 t3 ^
  28.     sCommand.DummyCycles = 0;                    /* 不需要空周期 */
    . P/ X+ ]; d0 o8 g' J  V2 f) G
  29.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 4线地址方式 */
    8 K  _4 R) t  T2 ?2 r
  30.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据方式 */, I9 l$ n1 l" n# E  q
  31.     sCommand.NbData      = _usWriteSize;         /* 写数据大小 */   $ V5 [+ Z% {( b+ U4 x9 l3 g& ?
  32.     sCommand.Address     = _uiWriteAddr;         /* 写入地址 */" t" g3 g# n+ x7 g1 j8 s2 M* G3 T
  33. 0 N5 Z7 a' D: N/ l/ [; Z' K1 r9 y
  34.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)/ a  n2 S. G+ M- N% O
  35.     {
    ) C3 n2 u( y! H$ K
  36.         //return 0;
    6 m3 @$ m/ S- T" n: q6 N
  37.         Error_Handler(__FILE__, __LINE__);
    0 `/ c9 F: X4 I1 l
  38.     }  m  P7 D0 i: w. w! B
  39. 9 G3 d+ p$ O+ Q9 A6 K+ x9 u
  40.     /* 启动传输 */
    - n, f/ T: Y5 l  S- w# P( \9 i4 H
  41.     if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    / K# |. s. v9 H
  42.     {
    $ S- q: _8 g( i: G8 I. }! }% R# O
  43.         //return 0;6 T6 T" I5 g  Y$ j
  44.         Error_Handler(__FILE__, __LINE__);
    / e2 h! Z# p7 e8 z( T0 a3 h
  45. / \/ a# h! r, l' Z+ M
  46.     }# E' ]0 k, r3 [% m6 ^

  47. + G1 m7 ?# H/ Y. L6 k6 U( Y
  48.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    8 g& S5 M/ \4 \, G8 R/ I
  49. ' W: R" W& Q  f. x3 g) i0 x
  50.     return 1;1 x2 M. o1 t" _8 l, e$ Y
  51. }
复制代码
- t$ l7 c0 y% i, X
此时函数使用的指令0x34对应的W25Q256JV手册说明,注意红色方框位置:
4 l0 c5 }/ a% D/ e$ @% @9 g2 r6 D: p% E, \

3 N: P8 S: k, C6 }% \* ~4 j- Z! ~  K0 c: S
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:* W5 W; [' _+ i% f! _0 Q
. g5 C; S( b/ _# v; k; |  h
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;( O, e5 E; m3 E8 `/ s9 n; \# ^
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;& m4 H  K5 ]6 v. |
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;, L; b8 P5 O+ b; u, t
sCommand.DataMode = QSPI_DATA_4_LINES;
! S$ |& `$ ~. T0 h; ^, K/ `( t- W. Q' f5 M- ?- P# A0 @2 [# f. l0 x
79.4.5 第5步:W25QXX的扇区擦除实现) M7 U/ q& q/ [8 U# p. `0 ~2 G: R
注:这里以查询方式的API进行说明,DMA方式是一样的。9 G. t* Q- V% b! A+ g

( p8 R) U9 `* u# B1 z通过发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除,擦除的扇区大小是4KB。
; t+ Z) Y( C9 Q5 J4 v
5 E! K; s( R: d& b% m
  1. /*
    / l5 a3 E2 o/ D% g0 V5 r
  2. *********************************************************************************************************
    - t' E4 q6 V0 N& b5 K& W
  3. *    函 数 名: QSPI_EraseSector
    9 E8 S8 ^4 m8 g* F
  4. *    功能说明: 擦除指定的扇区,扇区大小4KB" M% b' ]% ^; L
  5. *    形    参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,
    ; l! e2 T1 ?) p/ R7 b
  6. *    返 回 值: 无. b- u* x, y" r& O0 ^+ D
  7. *********************************************************************************************************
    9 {! ?9 D' U3 z6 _1 [
  8. */
    1 E- C8 z8 ]( n2 i! K- @0 }) z- o
  9. void QSPI_EraseSector(uint32_t _uiSectorAddr)& H) @0 x5 u2 w  C  X
  10. {
    : Z3 R2 G! c. @3 g' X' F) T% i
  11.     QSPI_CommandTypeDef sCommand={0};
    $ X2 }1 p% J0 Y
  12. " M% a$ ?1 K! C
  13.     /* 写使能 */1 p" t, n) x$ h" d
  14.     QSPI_WriteEnable(&QSPIHandle);    + {) R, x9 k! r9 A
  15. , b5 f, s# p6 ?" [
  16.     /* 基本配置 */# L' [" J6 i6 \& [. U3 U/ ^
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
    , z9 V+ V6 \& A$ h. n$ B
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */2 e) R' f% z/ \( ?- I3 D
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */5 ?  T$ J, p6 N! F
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */2 b" {( l! K* J+ S/ |
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */- g4 E2 w) R+ c. V& F2 c: d
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */    $ J+ C5 [. S. e8 k4 X4 I& N* F
  23. 6 w- g, |4 P8 I, u$ p
  24.     /* 擦除配置 */
    ) H6 p, v; Q; H  B
  25.     sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;   /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/      
    ( w, b  h; ^0 l( ?. x
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
    8 w. l3 n% y0 \7 x+ c; y& x% e2 B6 C
  27.     sCommand.Address     = _uiSectorAddr;        /* 扇区首地址,保证是4KB整数倍 */    ) K( }$ u+ d9 ^, m" K2 M! Q# z( I
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  
    0 ~# R, p- K- w
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  1 w" ]* q- s6 s/ d

  30. # _! {4 R! Z9 w2 b  u, L! o% ]1 c
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)! U$ Y5 ]- n2 |9 u
  32.     {
    8 F# F2 z7 e! Q$ I8 n5 H
  33.         Error_Handler(__FILE__, __LINE__);
    4 c" }. c7 T* s$ ~9 |, F: M
  34.     }
    1 ~% M* V( q: I* Z. G
  35. " A2 p# [/ H6 h! N+ Q3 L2 `
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    0 J, T2 s2 H9 a
  37. }
复制代码
) _# p" T: k  o+ o5 x, I: y( o
此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:
( E7 `% t" B8 T2 W
6 C' }: e" Q+ Z% ^7 W$ @7 u
a09f0f9fb051c5179f3e48824b99e875.png

4 z1 F% e3 W7 P6 c( q/ @0 g. A- L' F/ X4 d# F0 s( }) j- j
左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反映到程序里面就是:; O4 }5 a  `1 c2 s/ j* z2 T
/ o! D8 Q! S* R, u9 t
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;8 \0 y* n) ?8 A8 _6 @

; D; [' g/ M# D4 g sCommand.AddressMode = QSPI_ADDRESS_1_LINES;4 t6 d* Q9 R1 o7 i

5 D4 z' \: S1 @/ |" K4 f sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
$ M: Y8 A5 K, O: Q/ P5 T) O1 ^- j- o+ C. [/ B
sCommand.DataMode = QSPI_DATA_1_LINES;
3 W/ ]3 k$ \/ y3 p) {' J- i; p: V, e! _, T; c; S
79.4.6 第6步:W25QXX的整个芯片擦除实现& l2 Q6 J* M) t0 _
注:这里以查询方式的API进行说明,DMA方式是一样的。
! y- q1 j* U* W1 ]4 t7 x8 O% U$ r$ l+ E. z/ B
整个芯片的擦除可以通过擦除各个扇区来实现,也可以调用专门的整个芯片擦除指令实现。下面实现方法是发送整个芯片擦除命令实现:
5 z  S* `5 {9 g* Q! R8 W$ q+ V$ L
/ R, q1 J9 N3 ^5 E' R
  1. /*
    2 L) K# ?3 ~1 j1 `; X; O
  2. *********************************************************************************************************! q0 m& [5 i! h
  3. *    函 数 名: QSPI_EraseChip; S: d: [8 T- `5 h( N, S
  4. *    功能说明: 整个芯片擦除2 R; n/ n$ k* E6 Z* J
  5. *    形    参: 无
    : X, W& |3 r' T$ }
  6. *    返 回 值: 无
    - t! f, w. U( f* u" z" @; j
  7. *********************************************************************************************************
    7 F+ f- D# s" l( P+ P* z
  8. */
    & p: L! H% k) v: x
  9. void QSPI_EraseChip(void)4 g2 o4 ?/ ]$ b! s, {  s
  10. {
    0 Q" [. s) q, h5 \
  11.     QSPI_CommandTypeDef sCommand={0};
    - A* R$ j7 q+ D0 n, T
  12. & s$ i( _& [7 I8 ?' ~+ E1 [
  13.     /* 写使能 */+ H8 U" T5 l7 l# }( H. s
  14.     QSPI_WriteEnable(&QSPIHandle);    " k1 A: g, N9 z8 Y) e: p7 X2 O8 o3 H) F
  15. 3 B' Y( z. O* ?4 |  C* ?
  16.     /* 基本配置 */8 P/ i" M9 A7 w! Z2 Q, D5 |" K) C
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */
    ' W" r" V! J' @  h' h8 Z
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    : H- c' k# X6 i: q2 o
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    4 v$ O6 [2 A" E8 {' X# v
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */' O# `1 N! w" V) r! l) S! N
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
    - b! M2 ]$ _% d$ G2 l6 q) j
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */    ' B3 d1 G# g  h8 \1 z# |! d, Z
  23. : T) U. e2 s& @; i/ Q4 _
  24.     /* 擦除配置 */- |: w; Z' T" C' w5 n
  25.     sCommand.Instruction = BULK_ERASE_CMD;       /* 整个芯片擦除命令*/      
    ; C  }$ B3 z; x3 ~; a5 s
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */       9 r/ f: c, b" l& K$ z( q% b! o
  27.     sCommand.Address     = 0;                    /* 地址 */   
    2 i1 F* _1 w6 h; R2 _7 A$ Q
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  ) @3 ]! R! `2 B7 l+ t
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  
    4 I3 _) k4 H: g* E4 j1 Y: a
  30. 2 L6 l' }& Z, }$ g( e
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    " v9 I: M2 u. U1 \
  32.     {
    " e5 J, q  k: Q4 p: t" |
  33.         Error_Handler(__FILE__, __LINE__);
    9 n2 V* n3 w, R) _6 Y# t4 c
  34.     }
    5 D3 Y- v8 v: m1 t! R
  35. 1 q1 Q- H8 g8 L' Z4 T2 G. A
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    . {8 b' {( m9 B- k- {' s4 _
  37. }
复制代码
6 @+ e& g' a( Q' F  N
此时函数使用的指令0xC7对应的W25Q256JV手册说明,注意红色方框位置:
6 i' n9 i. W" {0 h# F
2 Y% W; F+ @) |# A1 l$ H
0 I9 l6 V; r' N% H
" z- m2 r# i+ P# b& [" z, C; p5 p左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反应到程序里面就是:' u" V4 S, Q: d# C# G! [
) m9 I' @# q! Y& k; L. }! ~# V
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  f& N- M3 K. w4 D7 xsCommand.AddressMode = QSPI_ADDRESS_1_LINES;1 ]* C0 \: J+ \0 [
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;% M0 [, Z, E' s6 z) d
sCommand.DataMode = QSPI_DATA_1_LINES;" P0 \7 a- M4 ?& r- Z& d9 H( ?( @9 \
4 _1 k1 y5 w, D" y; i' y2 P
擦除用不到数据阶段,sCommand.DataMode  = QSPI_DATA_NONE即可。
. e' x+ U! I) b( ^% }  D) n1 s  m# W# G$ ?( ^- D/ |  k, S
79.4.7 第7步:W25QXX内存映射实现: D: A  L  [7 B% _' Y+ G" y1 J/ s
注:这里以查询方式的API进行说明,DMA方式是一样的。+ ]7 w0 Q* W; o4 i7 y8 H6 D  a

' h& w1 T- U8 D, W通过内存映射模式,就可以像使用内部Flash一样使用W25QXX,代码实现如下:
9 r% n/ C6 V1 h! K' E
/ t9 \% R% ~' i6 e4 [$ n% k, Y
  1. /*
    , v* E1 B$ Q% V: n
  2. *********************************************************************************************************
    ( {" E9 Z: Z( e- G) M5 M; J9 d
  3. *    函 数 名: QSPI_MemoryMapped3 v; {! ]! N! u
  4. *    功能说明: QSPI内存映射,地址 0x90000000* a) @! c3 p" S! s5 i% v
  5. *    形    参: 无$ ?( Q  P; }( G/ y
  6. *    返 回 值: 无
    ' _8 ~; Z% a  N# M: I' K' s
  7. *********************************************************************************************************
    / X/ o3 D3 o4 l* R  [1 l
  8. */6 T) C; K4 C9 o( J' t& A- T: |' r
  9. void QSPI_MemoryMapped(void)
    ) z1 D6 J! |/ i; U* y" c! k
  10. {) z# F* B5 i4 B0 P' Y
  11.     QSPI_CommandTypeDef s_command = {0};
    9 L4 k' v) X; [$ z% _/ A! k
  12.     QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};: x$ A( J, ]+ q. y! M
  13. 9 T5 _2 K6 a* i1 D
  14.     /* 基本配置 */
    % }# y: \+ Y6 e' g5 D8 V
  15.     s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;      /* 1线方式发送指令 */
    7 |) X+ W: d& c3 n$ b
  16.     s_command.AddressSize = QSPI_ADDRESS_32_BITS;             /* 32位地址 */
    $ a7 E3 P0 L. q5 ]
  17.     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    & O3 V/ `/ D4 _- q' Y+ {
  18.     s_command.DdrMode = QSPI_DDR_MODE_DISABLE;                /* W25Q256JV不支持DDR */# t& Z! {9 `8 c' U& }* p1 Q
  19.     s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;   /* DDR模式,数据输出延迟 */
    7 G7 Q8 t% `6 u: ]9 c: W
  20.     s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;            /* 每次传输都发指令 */
    : p' [5 z- z7 Z4 g7 E8 Q) h8 }
  21. - I; W2 V. C5 [
  22.     /* 全部采用4线 */
    # x5 C( l. F6 h/ v3 `% e
  23.     s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */" G& N0 G- y' }- ]8 g" u
  24.     s_command.AddressMode = QSPI_ADDRESS_4_LINES;                 /* 4个地址线 */9 y+ x5 |. r5 ^% h" B2 A
  25.     s_command.DataMode = QSPI_DATA_4_LINES;                       /* 4个数据线 */
    3 b# n8 y) m* t- p! n1 J9 d
  26.     s_command.DummyCycles = 6;                                    /* 空周期 */, ~9 X! L2 }% U4 q$ `( b
  27. ) x+ B3 ]  A8 V1 X  q3 n+ |
  28.     /* 关闭溢出计数 */
    / F# @& ?/ r# ?  n- h
  29.     s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    2 ~  N% {; @2 h# V9 Z3 b
  30.     s_mem_mapped_cfg.TimeOutPeriod = 0;  ]" a: _. C/ `* s: i- D

  31. - c) s; l* L4 K& R# |; j) B' q
  32.     if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)( a9 p% ?6 ?( ]: W2 l( b; I+ g
  33.     {
    1 ~8 ^" }/ R/ k6 `4 c% I
  34.        Error_Handler(__FILE__, __LINE__);( z5 o, V& e. ]7 a+ Q+ f! W9 o
  35.     }, o7 B1 G5 z0 s1 v
  36. }
复制代码

' p7 d: k! q2 r1 z+ w/ K+ v此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
0 A9 {( Z% L0 l' }4 X( I* }
  O7 g" R$ I8 y; W+ c
bb245c5c5434f7ec1a828434a3915d8c.png

/ `1 ~/ f  l8 t7 l
( A! J/ R" y1 a8 _3 d0 L左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,采用的4字节地址方式,反应到程序里面就是:
/ S$ q1 Y5 Z' o9 W7 u
+ Q7 T; c" T' C5 R3 |sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;) c) L: [$ i% W* J: O7 Z
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;& N, J  X: B9 I# u4 q
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
1 c6 m  i, r3 c* h4 \2 f" ~sCommand.DataMode = QSPI_DATA_4_LINES;
: {. }/ X' X6 Z; t/ r, j0 K) n) V" P7 x& s/ [. v6 M+ P
79.4.8 第8步:使用MDMA方式要注意Cache问题7 I* ~( T3 z6 m. a* ^2 Y( b  p
如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM,这样可以方便大家做测试,测试通过后,再根据需要开启Cache。
/ |& ]2 z0 }) G, j5 ^& Q( [* j
2 n! ]" a! g) Q* C
  1. /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    3 C, @$ @/ R5 ]* X! F: h7 A2 M
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    . l# t# v! e) o$ f0 w: W' D  k
  3. MPU_InitStruct.BaseAddress      = 0x24000000;8 x. M6 X4 [) y
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    , F& v5 F+ K3 v  X6 V8 P: V5 I8 I) U
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;  w! f3 R% ^  \% D
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ( A% Q4 B6 y2 a
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ( z2 Z6 F0 M4 A: H- A, D0 @
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;% _# r6 X" f# G( j' H$ ]$ x
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    # b8 L6 `; \- I
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;8 _: t4 }+ B' y6 O7 S3 Q  T3 {
  11. MPU_InitStruct.SubRegionDisable = 0x00;% w3 C9 ~) w' p# b8 ]! i
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    0 ~' _; k1 T* q( r& v5 W3 ^

  13.   m& @! ?- c% I! b$ }+ L
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

6 t/ A4 L& ^! U79.5 W25QXX板级支持包(bsp_qspi_w25q256.c)
% j3 [( R/ A% q8 E' A* jW25QXX驱动文件bsp_qspi_w25q256.c主要实现了如下几个API供用户调用:" g$ I, @0 [  W: x( ~) s

* \' o3 N& q! c: o5 \/ |6 p  QSPI_ReadBuffer
( g' E& t7 ?2 P  QSPI_WriteBuffer
3 g# l+ o) h+ G9 n, b1 T2 |  QSPI_EraseSector
" o5 k+ }' m( f  U) d  N; ]3 a  QSPI_EraseChip! q! ^" |2 ?. i  }+ z
  QSPI_MemoryMapped, C# G; o) F8 i& ^
79.5.1 函数QSPI_ReadBuffer
" D0 X5 J/ \3 v4 Q) [" l) m& z函数原型:/ s$ H6 I& H' J% T2 g! ~
7 Q6 q+ b- X+ G- Z% a; w# V
void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);0 L0 o' F, n. k/ e. E2 M! i
+ ^& b7 ?" j( C
函数描述:; x9 x+ M4 B8 q! d" f1 z# ^

  B& f# P4 J. K0 C# n% {$ y此函数主要用于从QSPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可(如果使用DMA方式,每次最大65536字节)。+ F) d# u3 X, l

9 v' V- r; V8 ~函数参数:. S5 P5 L4 F, y. o/ v9 j
1 g5 J6 q' [' Z: S  B
  第1个参数用于存储从QSPI Flash读取的数据。( T& R0 a8 R* j+ [
  第2个参数是读取地址,不可以超过芯片容量。
5 u( m' z9 ~$ M( ^! |. ^  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
/ P5 {! V  W# V; H3 B: h79.5.2 函数QSPI_WriteBuffer/ S/ W* D! c/ G' V7 b
函数原型:
9 _: H% o5 V4 H( I4 Z8 j) U# I, m( w. p+ @* t. l
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);* i# g) m, n/ {

% v6 U  e; f# d- p, s0 x; e8 O) E/ b& x  ^函数描述:
% U& B/ W) c7 L: i8 E/ Y- u3 `% y! i& y
0 v; Y- p. z6 k! t  R! ], K3 V页编程,页大小256字节,任意页都可以写入。注意使用前,务必保证相应页已经做了擦除操作。3 ?8 w# y! {# A
# q. P& T6 x- a' P
函数参数:
: e' X9 Z6 d; \/ N  w. ^5 M# d! `5 g& r6 t. y% C$ K
  第1个参数是源数据缓冲区。
; _" |6 n8 _* b  t+ M  第2个参数是目标区域首地址,即页首地址,比如0, 256, 512等。, z' Z1 `" Y% G+ k7 ~. v/ Z/ n
  第3个参数是数据个数,不能超过页面大小,范围1 – 256,单位字节个数。* Z/ Q0 i4 q& P4 m4 z" A
  返回值,返回1表示成功,返回0表示失败。+ M& c5 d/ z8 V- g. \7 x0 s
79.5.3 函数QSPI_EraseSector! A" r' M, i# t. X$ u$ l
函数原型:! \% p  v( E) V$ n5 @7 ?

+ g9 A" D. z- [3 T7 ?void QSPI_EraseSector(uint32_t _uiSectorAddr); N- c/ S9 ]; g- n1 G' \/ X

, h! v& c/ x8 g函数描述:
5 U# @/ x, y6 b, }
) x+ z; D: ?/ l- ]! U5 I4 z此函数主要用于扇区擦除,一个扇区大小是4KB。
) v  i& z4 [( T* K( K0 l8 N! r( m! k1 c
函数参数:6 m' g" i3 f5 g# m
8 A1 J9 [0 \: O8 \) x* K
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。: Z6 G* @5 X, ]6 t* o% w; u: @
79.5.4 函数QSPI_EraseChip7 d2 S! j, g; u3 h5 n7 x: [# N
函数原型:
7 v+ U- X6 B7 W& s$ e
# k$ v5 _) @* N- G* i5 nvoid QSPI_EraseChip(void)
" ~& a6 c* D' N# |
+ M4 _% J9 i2 f+ H& K8 j函数描述:
2 s0 G/ G# q; P: H
3 t0 W: G& g0 }. d& O$ A9 v: s4 B此函数主要用于整个芯片擦除。8 d! L) r8 p% w& n# \

% [; D8 B! e7 h5 H' V. m7 ^0 L79.5.5 函数QSPI_MemoryMapped
2 G5 [% D' Y+ z, l- C& x函数原型:/ V9 {. S4 ~" c
3 |: ], b$ C& m/ ]8 _" H" V4 A
void QSPI_MemoryMapped(void)
% n. C( H- p7 R1 U( `
' Z: L* Z! g/ F8 ?0 h% q: T! C函数描述:7 X5 Z# w/ e6 |2 C, k, X1 J
! x; T" h* Q' \0 H
调用了此函数就可以像使用内部Flash一样使用外部Flash。& O! {# \+ M% [- u, J
( F" w6 b- F- S
79.6 W25QXX驱动移植和使用- Y( b* y! K& N: i4 b
W25QXX移植步骤如下:2 x+ C7 {4 X4 ^
4 R) Z# T& k& @3 _/ k0 y& c
  第1步:复制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目录,并添加到工程里面。
6 q- ?1 X) W6 _7 s  第2步:根据使用的QSPI引脚,时钟等,修改bsp_qspi_w25q256.c文件开头的宏定义。$ p* {9 z# s; n1 N9 C6 ]
  1. /*
    ' u9 }% K! u! v6 q
  2.     STM32-V7开发板接线
    & L- f: r* n' `& x( q

  3. ; z5 e( b5 ]- g
  4.     PG6/QUADSPI_BK1_NCS AF10
    ! f/ w) @) I3 L7 h3 g# y
  5.     PF10/QUADSPI_CLK    AF92 o) ]+ D4 D* u+ W% e4 P
  6.     PF8/QUADSPI_BK1_IO0 AF10* N) M9 h4 m, r: u: }
  7.     PF9/QUADSPI_BK1_IO1 AF101 \- L; `  a* t# F: A  a8 z
  8.     PF7/QUADSPI_BK1_IO2 AF96 L$ P' Y4 a# Y2 P; ^. e& l$ u
  9.     PF6/QUADSPI_BK1_IO3 AF9
    3 O, D3 E  i9 \+ b

  10. / H) W2 r3 b* y( Y2 J0 I& D
  11.     W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB! {0 t( R7 W0 J; S
  12. */  ~9 k" d0 F2 s. r1 m

  13. $ J( L$ t' W; ~5 d6 _$ Z6 f2 P1 F
  14. /* QSPI引脚和时钟相关配置宏定义 */
    3 e) L6 ?" `) d- I: j2 P' P% W
  15. #define QSPI_CLK_ENABLE()              __HAL_RCC_QSPI_CLK_ENABLE()
    % x1 m* Z6 _' i- @, a
  16. #define QSPI_CLK_DISABLE()             __HAL_RCC_QSPI_CLK_DISABLE()
    7 f: G. I, P, ^+ _, p) [
  17. #define QSPI_CS_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOG_CLK_ENABLE()
    6 f; V# ?& J7 d% ^0 |  n: |
  18. #define QSPI_CLK_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOF_CLK_ENABLE()
    + @- p: v+ g" w0 y
  19. #define QSPI_BK1_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    8 W9 d' K4 N) z& J! U
  20. #define QSPI_BK1_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    % ?  }! o" R; X7 g
  21. #define QSPI_BK1_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    % n; ~- f1 ]; c/ ]6 T# F
  22. #define QSPI_BK1_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    # a2 _' [4 N% T, m% F  g$ m* s

  23. 2 j9 K8 M$ `  x. Q$ W% e
  24. #define QSPI_MDMA_CLK_ENABLE()         __HAL_RCC_MDMA_CLK_ENABLE()3 T0 Z1 ~5 M6 N" f* X2 G
  25. #define QSPI_FORCE_RESET()             __HAL_RCC_QSPI_FORCE_RESET()  s9 F! g; f- n5 H- C- c
  26. #define QSPI_RELEASE_RESET()           __HAL_RCC_QSPI_RELEASE_RESET()' f* n& e& J/ [
  27. ! r; ]3 v+ O1 N6 R: j. S( D* c( f
  28. #define QSPI_CS_PIN                    GPIO_PIN_6) [  _2 q; F8 i0 Q% k
  29. #define QSPI_CS_GPIO_PORT              GPIOG, }8 M: u; Q) J+ V. C- w
  30. . I$ M( h) U. l6 Z# R: l
  31. #define QSPI_CLK_PIN                   GPIO_PIN_10
    % ?3 |) l# V/ `' g1 k. H
  32. #define QSPI_CLK_GPIO_PORT             GPIOF. @6 e! S0 }/ E4 [3 L' x
  33. " T  i' a8 z# t4 U0 A) ^  T/ p
  34. #define QSPI_BK1_D0_PIN                GPIO_PIN_8& B7 z7 y5 M5 n0 {' V/ c" D
  35. #define QSPI_BK1_D0_GPIO_PORT          GPIOF
    $ ?  j1 s+ _! k
  36. ! h, d1 n) d3 t
  37. #define QSPI_BK1_D1_PIN                GPIO_PIN_9
    3 C9 B9 u6 p4 g1 A- `; f- y: \
  38. #define QSPI_BK1_D1_GPIO_PORT          GPIOF: a' l& \3 }0 F0 Q
  39. - o% e# p' o; P+ s7 R
  40. #define QSPI_BK1_D2_PIN                GPIO_PIN_7+ q' E( Y2 V% h7 H8 L
  41. #define QSPI_BK1_D2_GPIO_PORT          GPIOF
    # `+ }3 _0 ~: t. _: K7 }) R

  42. $ Y% D9 y( i% z) u. s+ b
  43. #define QSPI_BK1_D3_PIN                GPIO_PIN_6
    # O* [- g- P+ a9 b
  44. #define QSPI_BK1_D3_GPIO_PORT          GPIOF
    - K! C7 J4 W' k: \# T
  45.   根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件
    ) x/ \, P7 s4 R* M% i0 Q2 Z
  46. /* W25Q256JV基本信息 */' X: _7 b+ B# h+ E% O
  47. #define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/
    5 k5 u8 ~! ^5 ?+ O- ]
  48. #define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇区大小,4KB */: S3 N+ w/ s4 }$ t' m2 I; x
  49. #define QSPI_PAGE_SIZE      256                        /* 页大小,256字节 */5 l. U. x. [' ?3 M- G
  50. #define QSPI_END_ADDR        (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */
    - {- z3 E, A( S: r) Z6 f, ?# H. _- \
  51. #define QSPI_FLASH_SIZES    32*1024*1024            /* Flash大小,2^25 = 32MB*/
    # i5 \$ z4 l+ {* Q( r

  52. % O1 H6 `- F; q' `9 K3 l9 H
  53. /* W25Q256JV相关命令 */
    ' Z6 m  v9 y1 e/ D+ V1 I" {1 u
  54. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  , O" Z6 p  s7 d( p  w
  55. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  * F& d. K4 ~) h# ]0 T' {" Q: W
  56. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */ $ |' P% S, m) ^8 T
  57. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
    1 \0 T% V& l# x" x# v; S- r
  58. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    6 N$ p2 O- O) n0 f% F1 V% [
  59. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */; |/ X6 H* }* D) D# S- S+ \. k
  60. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码
8 D/ c6 U; \. q) M/ S
  第4步:如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM:
4 t7 k' j+ a. G' u/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
3 P* g# `' a% D0 O8 I4 }& }" u5 C+ KMPU_InitStruct.Enable           = MPU_REGION_ENABLE;9 `. H% m$ h/ C8 H: E% A8 A, X( O# c
MPU_InitStruct.BaseAddress      = 0x24000000;
3 Y% F, d' w* I$ JMPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
* L5 L% K) K, a8 G4 c; p% d/ ~1 AMPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;8 [- @+ O4 G8 S) L# j2 H  i
MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
. i, D# X. x/ Y" X. rMPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;8 K  W3 h7 X; A4 ?
MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
" {) k' \2 z2 E; |MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
5 Y' r. E- R2 mMPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;. c' d5 [/ q$ h
MPU_InitStruct.SubRegionDisable = 0x00;
0 B  n4 i: n  u1 o  r0 ~' VMPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;6 i( O& Y- ^! D/ U# ~. U

! m+ o. R: V" M8 DHAL_MPU_ConfigRegion(&MPU_InitStruct);
  ?' w! W" Q% U  第5步:初始化QSPI。# Z/ p" [& W# P+ F3 f& A
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */( J$ l: E/ v& y" {$ I0 q
bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */) M( S$ h% E- Z5 V1 R1 u
  第6步:QSPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
- @& S; `" W4 ^. H( T  第7步:应用方法看本章节配套例子即可。
3 T# f3 \8 m0 P% ~79.7 实验例程设计框架- O8 H6 r3 W/ \4 a, Z5 A5 f3 m3 Z7 y2 c
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
: X8 L5 G% Y: h/ S
5 k' Z8 I% E: A
32c08174cec9edf62fb6e6103da5cea5.png

, n5 q2 G1 w3 S' {6 d( l8 m/ Q  b) k& M: w# \5 d& h
  第1阶段,上电启动阶段:
2 n# m% v8 S( w
* ]6 V, ~* k# x2 [! b$ y# h' X这部分在第14章进行了详细说明。
; Q5 @4 l. r5 C9 R0 t5 w0 k  第2阶段,进入main函数:
4 F. `" R* Y4 }; h9 E+ t, o1 u' q9 P6 }
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
- }1 z( r3 v2 f 第2部分,应用程序设计部分,实现QSPI Flash的查询和MDMA方式操作。+ u* H$ M: \3 U- y( `2 p5 L. L( P
79.8 实验例程说明(MDK)( G: }, k, t2 L1 t/ e
配套例子:
5 A6 e' ]3 F* [% Q* ^0 [
$ G4 ?3 O/ X. I& l8 d9 }V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)! V1 ~1 t! \- _4 M! D; L0 b

' `( P! Y7 P& v$ VV7-059_QSPI读写例程,查询方式
1 S2 N$ w* b& o' B+ V5 W
, j# v( Q  b5 u$ Y3 f实验目的:
" C; o" P: F0 ~% G: y: ^* D0 J: s3 J  v/ d* J" |7 W: H7 B$ x# L
学习QSPI Flash的读写测试例程
1 D/ S: p' a' H  S9 ?实验操作:
* f+ a( ^- m. X* N. T6 I: j; Y; T5 G9 d0 ]2 G
支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可3 f5 M6 a- B5 ]; P
printf("请选择操作命令:\r\n");5 R# k1 }/ c0 K  a
printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
4 m) R' v4 X( R6 F- L' fprintf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);9 L! Y7 @; ~  ~# l) s% L  O( x( Y  s
printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");2 G  A  r) [/ T1 G  X. f. Q
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");+ p8 i6 F5 b- q
printf("【Z - 读取前1K,地址自动减少】\r\n");
6 k* b; F6 M9 r7 ]! z$ y( aprintf("【X - 读取后1K,地址自动增加】\r\n");
0 F) R# k7 f/ R* dprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");& D, f9 [% ?& T/ b+ b" r
printf("其他任意键 - 显示命令提示\r\n");
- R$ A0 Q. B8 ]9 O0 c上电后串口打印的信息:
: s4 M9 K1 ?! ^4 H& n+ _: y' _# F$ @/ H' ]& V' ]3 |& U2 _, V
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
& H! @' f8 w3 ~$ O0 I2 a5 t4 _
; ?6 o! Y7 s# j8 U$ p' x% ?8 @
71e953366d6cc128ca9c0ddeef6d23ca.png

0 f, b5 K: @# p' ~. B0 j
7 _' w5 a# t/ D- F程序设计:  L" k( r2 |: i3 K: N

8 e* f9 X8 `6 O6 h; ?/ d  系统栈大小分配:' \) p. Q% ]$ ~" c0 S) D! l

" F3 O. S3 R; ?# n; V7 t& p1 T9 {
7011bff1036a0ffa03f21e1e5894ea0e.png
4 |* S: _1 ~  H0 n, O
% L0 j4 ]3 _) X- x
  RAM空间用的DTCM:
! e2 R3 x0 W# k% C% v8 A! [' P
% g1 }6 N, k9 ~7 ]$ ^' `# T
6c91b4eae6d726676a3e5990d73f448d.png

5 ]! I& p7 [" X; @) I4 ^
0 B! c0 Z, `8 {2 _7 O8 r  硬件外设初始化
/ ~% J  E& F3 i! I) [/ F* b$ s. M7 ^: O
硬件外设的初始化是在 bsp.c 文件实现:3 ?' w: I# t' [4 a0 a) \

! y' C- ?- s# ^
  1. /*
    , D. P' e8 L) Z8 D5 }4 w
  2. *********************************************************************************************************
    ! ]; I& k9 m0 ?3 }, J( j' t
  3. *    函 数 名: bsp_Init
    ( d  I( ~" R1 T! s0 }  U
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    - C: C7 |, L1 Q& l8 K/ S
  5. *    形    参:无
    ' Z6 _0 @4 V# v
  6. *    返 回 值: 无. x. S9 I! B* O/ t4 I5 }
  7. *********************************************************************************************************
    9 s6 b1 p& u2 y2 F
  8. */" K* t! u: E+ `% m" ^. X
  9. void bsp_Init(void). Z! K0 q6 j" I4 p+ j
  10. {
    # P( t* a; d0 X1 l6 n9 q! a, a+ `
  11.     /* 配置MPU */
    - v" l# X/ X4 |7 u
  12.     MPU_Config();* v, P* q1 G* c' X% D: S# i% y& E

  13. 6 y9 v# f3 H  r4 y
  14.     /* 使能L1 Cache */
    3 v) C- |  _! f8 m7 z
  15.     CPU_CACHE_Enable();# u+ l! R; G8 W

  16. - i5 n9 {& Z! k. \3 J; i' r1 p
  17.     /* # q/ O7 P+ Z! |8 A4 G: m) W
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:: p& y3 H4 o: T+ H+ z. a! S
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。( v; P* [+ _# v/ O& @( Q; u; ^
  20.        - 设置NVIV优先级分组为4。
    $ e, D! a. E- C1 q1 A" K2 D
  21.      */: P# p; @$ X6 b3 p! ]% A, L
  22.     HAL_Init();  P& ]+ {6 N) |  B1 t$ H
  23. 3 c8 H. j( H& ?# d! [$ G
  24.     /*
    6 g6 W- L# R1 w4 j
  25.        配置系统时钟到400MHz
    , }/ n. J& }- ~5 O7 B
  26.        - 切换使用HSE。; t3 a4 c, Z, o
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。; b1 o0 q7 D( _" \% ], w
  28.     */
    7 K+ G! a; i& Z! e; V
  29.     SystemClock_Config();
    ; h1 V/ l5 ~" G8 g$ W

  30. & c4 ^2 {/ _% \9 e0 |
  31.     /* ) h" c/ E2 B8 ^% M
  32.        Event Recorder:/ f& u1 p6 e. k
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。9 @' o( _2 D$ P4 ^9 }
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    & u( l) O% q' z; J
  35.     */   
    8 ~5 Z( R& V$ c: ~$ h, o0 W0 @: b
  36. #if Enable_EventRecorder == 1  
    8 P) Z2 v  B# [' s- z0 w* _
  37.     /* 初始化EventRecorder并开启 */& `; S( l. H0 _9 y3 }3 m' T; x
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    ( Z1 W: }" ~9 s- U) y
  39.     EventRecorderStart();4 }& f7 [9 G6 v  v- {
  40. #endif, ?) r7 R, M$ C( x1 w

  41. % ^; B/ }  I9 o2 k
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    " @9 h: r( f2 d  K8 C
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */  x2 k6 A' T( z
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */" Y6 X$ u% b5 k& p+ c8 z" H
  45.     bsp_InitLPUart();     /* 初始化串口 */
    & n; C, _1 C- e5 d' D( S4 c
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    8 M% G4 t* e6 B/ N9 F# f
  47.     bsp_InitLed();         /* 初始化LED */    5 B2 e' Q8 E* n5 W
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */: r2 r8 E/ S7 r7 _% N" s1 C! s% A

  49. ( v7 k9 [* }# ?6 b0 m  C: N
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    * @  R; l' f- d8 B# d! R) y
  51.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码
# P/ x' ?: K& {
  MPU配置和Cache配置:2 G# o2 I# }- b
# {8 G- s1 R& y9 y' f# {' e1 W
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。/ N; _$ T1 j  E) Y3 v

( w1 u- U1 A/ G# ]2 S; o7 A
  1. /*, u7 O1 L  F5 h# C* Z
  2. *********************************************************************************************************
    0 M* G5 v# s' A+ a. g, t
  3. *    函 数 名: MPU_Config: g/ J! j1 `5 d+ L
  4. *    功能说明: 配置MPU" r( E7 T9 \" U( W' g' `
  5. *    形    参: 无
    * x. r9 s0 i- R, F
  6. *    返 回 值: 无
    " O; s) _% [5 B% N7 l! v/ X. `
  7. *********************************************************************************************************
    ' J9 `- x6 |( h
  8. */
    # ~# h2 l; K, G
  9. static void MPU_Config( void )# ~4 L) R; H( [) p2 @
  10. {
    , e" Z* |! ]3 b. j# Q" p, x
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    8 L- z5 O, I, {: \  W9 o. p
  12. 0 \. S( _2 i5 J0 G. p% i
  13.     /* 禁止 MPU */  E& b4 E( y8 X4 f7 m  y
  14.     HAL_MPU_Disable();+ B7 e4 A4 Q, q% w
  15. ) j  n" \+ e# t; A) |) J9 T
  16. #if 0; z, C2 _) y- h; E, b2 |% R
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    $ P8 ~5 |' M% \* v1 c: a
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ' S3 L& H: Y1 r# U9 s) d
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;3 ]$ J+ `1 W) m
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    8 e: j8 H2 d5 m4 {
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;3 b6 D$ R4 O! ?6 l  U( k* g
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    % m. R8 ^, t, g6 ^5 Q2 k
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    % _/ L) }8 S! N! f0 \$ j2 J
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    + U' ]! B  a- r  c
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    7 H- Q) R, }" z8 ]# f  e
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;7 e7 u4 P) D* F( W
  27.     MPU_InitStruct.SubRegionDisable = 0x00;
    # W4 `+ p2 @5 p8 p7 i% b5 `6 Q
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;- J( W- j" c8 J* O. k; ]* ]

  29. . V6 Q$ J9 S7 C+ K
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    3 `% [& j! _* T; a3 f
  31. ' P: s: n) f% J" a
  32. #else
    5 V( J/ t, p" I0 ^2 P
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    ; Z" L4 }4 S- a- Q( u# S
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;0 l, j1 [' Q5 n4 g. e$ f
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;; f1 F8 i7 |2 V2 {' E5 `
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    / \' J2 q; ?) d+ m) @
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;$ q7 P* H* p3 a0 }( J
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    $ R, |8 q8 _7 b* F
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;7 `7 E) r% X$ P# O9 Y+ F, o1 E3 s* {9 _
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;7 i$ D2 E, V& [6 L: b  W1 N% K
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    6 F9 I( t+ h$ T( U4 i0 \/ n
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;& M0 F5 J& u/ Y: t8 d# r: t0 ~6 I
  43.     MPU_InitStruct.SubRegionDisable = 0x00;/ s; _7 ?  w4 [1 B; r- N
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    5 o& C. s  s2 Q' e0 p3 O$ r

  45. 4 \0 \. G! d1 l  g" x; O
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);; h" E% E! N! F- m( R8 W
  47. #endif   
    ) C; v+ v4 U( |" [; |* o: X* w
  48. ! j/ ^9 A- }+ o( f3 }& ?4 G* j
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */: ~7 @# }* c; b& C/ [$ b
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    - R1 k$ P+ G3 [% J. Z
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;
    ( s; y1 r7 o! B- i) x: ?( D
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;      h' a2 b* v1 k& @  O
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ; t( x+ A& r6 T0 y5 I/ b
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    # J) U$ O" M) c: P- H6 Q
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    ; X6 K% X. r$ W! m+ ]8 K
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    6 D2 _  r( ~  z! W8 U  w. m/ }
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
      S6 j- C& K+ r0 W8 R+ u
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;6 X! v# y% u9 w" g3 A7 b. R2 X" Y0 T
  59.     MPU_InitStruct.SubRegionDisable = 0x00;
    - P! s1 C! U2 f5 a& B6 [
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    " ]4 v. [7 G9 |6 m0 b2 S7 U

  61. / |  D/ K' _+ u1 o$ j
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);) {% X. E% J8 y' ~

  63. 2 l) o3 Z9 u( w  E7 U, p
  64.     /*使能 MPU */' y# O3 ~# y" o5 `/ |. T# k
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);- o3 a# _- L/ u( `8 m$ ?
  66. }) f' f4 d0 ~8 L* ]1 f

  67. . n5 c/ ?2 c' K" s# w7 ?
  68. /*2 k! F; E# c+ s2 S" R
  69. *********************************************************************************************************
    2 Y0 T9 r+ D3 b% Q
  70. *    函 数 名: CPU_CACHE_Enable
    : m" @% s# ^, f, R+ P
  71. *    功能说明: 使能L1 Cache
    5 e4 y0 P( c' x1 {  |9 J! H- v
  72. *    形    参: 无2 Y1 t$ @# x) `4 X/ e* R% K
  73. *    返 回 值: 无  N' J7 {) m) `7 `6 P
  74. *********************************************************************************************************2 b" a; [: f) d7 V% B! n  K$ u
  75. */
    # g+ [5 o5 @" n  ^" l" X& t
  76. static void CPU_CACHE_Enable(void)
    : A( _; J+ t4 a3 ^3 C
  77. {2 ?" d# g: @/ \% K
  78.     /* 使能 I-Cache */$ X5 r. X3 j4 R, |" g. I- L( f
  79.     SCB_EnableICache();6 q$ j0 S9 f* A; ~" g) V" l
  80. ' @) A+ c- h" z
  81.     /* 使能 D-Cache */
    . A" T' m! ?2 t$ P, j: K
  82.     SCB_EnableDCache();* J& S4 ~# R: C& V
  83. }
复制代码
& `" f, D% T* M5 D. s
  每10ms调用一次按键处理:8 [/ b. Q+ L1 t( w- j  Y) L+ P

; e8 k. y. I$ w( y# L7 |2 Z( M" l按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
1 r' ]) \3 n# k
3 H* i1 c2 J  z2 B3 K+ Y
  1. /*
    ; M1 B; C, p( [' @2 U$ _
  2. *********************************************************************************************************' E6 K" l" M( ^3 F3 Z
  3. *    函 数 名: bsp_RunPer10ms
    9 i: }3 n( t8 y& R4 V- ~$ u
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    5 F0 ~# p! i0 N/ x& ]2 H  q* ]
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。0 ?% Q2 _$ @2 C. l% A5 r
  6. *    形    参: 无5 W8 G! \7 C5 W3 I8 S9 H* i3 ]! p
  7. *    返 回 值: 无, M* P) m3 w' J) W9 z/ U
  8. *********************************************************************************************************' U, x1 P0 n3 [1 S1 V  q* z, B
  9. */
    0 T6 q! Q- g$ N! I/ i* R0 L9 n
  10. void bsp_RunPer10ms(void)
    - K1 S# x* S; d" v9 U+ b8 S
  11. {' o" n" U7 @' _' |% Z2 ~9 V" W
  12.     bsp_KeyScan10ms();1 N- K/ R5 I1 T: Z6 I& g
  13. }
复制代码
6 X1 j& ]7 f4 [; q8 u
  主功能:
; b) {, u8 y9 H9 t. C7 M5 o& V7 x7 |5 P  `3 {: t, y
主程序实现如下操作:
; n4 T+ ?! g1 y) J( y
& C* }% ~' s  L: t  启动一个自动重装软件定时器,每100ms翻转一次LED2。
9 O/ I) `* O' i  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
  N! R9 v* T9 n. D' d  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);, c! t: x  }8 ?) v2 Q3 w) Y
  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);' T8 Z" j; U( s  x( M7 e( ^$ {4 O% C
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
- f+ o% A+ D* d; p$ N7 Q  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
' t& j3 p3 m% a) ]  D6 ]( {  printf("【Z - 读取前1K,地址自动减少】\r\n");  N8 `$ C9 A( t0 r. Q
  printf("【X - 读取后1K,地址自动增加】\r\n");$ H- U- I. d1 q. K
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");* O3 U5 h" o( e' A
  printf("其他任意键 - 显示命令提示\r\n");
2 Z5 M4 j( P9 Y- @: M; D, w) a
  1. /*4 l% Y- ?9 h5 G$ h
  2. *********************************************************************************************************8 o! p' X! m7 s7 o! ^
  3. *    函 数 名: DemoSpiFlash7 k- A8 u8 D2 T/ w' A
  4. *    功能说明: QSPI读写例程! T, _' \) P. Y! W* M% ^
  5. *    形    参:无
    : N& ]% I$ T8 {+ h1 c' o4 n/ n' L
  6. *    返 回 值: 无
    . x9 {. A4 {/ q4 E" |2 h
  7. *********************************************************************************************************7 O0 p7 m( v* ]' `  h5 }' r
  8. */5 t2 ?& m- }9 E5 N% X- l
  9. void DemoSpiFlash(void)
    $ b* z$ y1 B; Z8 S0 J
  10. {
    , j3 q( e0 i, i; C$ _
  11.     uint8_t cmd;
    ' M+ A: ]2 t% m6 v  U8 c
  12.     uint32_t uiReadPageNo = 0, id;
    , L& k' U7 O. B& w

  13. ' D" s1 E6 \6 \$ ^' J
  14.     /* 检测串行Flash OK */3 z) J# o: K; m5 ^& Y7 o
  15.     id = QSPI_ReadID();3 z$ M% G& ^( p$ b$ g$ l( y
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);3 e( K- D" }3 E4 [/ v
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");1 c; s" @1 I: `% N! ]

  18.   t7 M  e1 v& F% F7 a) W; S
  19.     sfDispMenu();        /* 打印命令提示 */
    * Z2 L- N. h, `+ g; r" s. O. m
  20. ! h. {1 f+ R2 }8 ^/ Q! {( s
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */) F: l2 ]% p' ^6 w' p
  22.     while(1)
    8 ?, {2 H% c4 K1 I  `
  23.     {
    0 G/ {0 m) A4 Y3 i
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    6 J) ~4 b5 A( @  D
  25. ! i  `$ T* E/ I- _% D
  26.         /* 判断软件定时器0是否超时 */
    " |$ c* j: s% ]5 F, g3 D3 t
  27.         if(bsp_CheckTimer(0))
    3 G+ B, U& }( `% `+ x3 S: h8 u
  28.         {8 J* j. i; |; y& t' u
  29.             /* 每隔200ms 进来一次 */  
    1 L/ u& W2 Y0 E# V+ A7 s
  30.             bsp_LedToggle(2);) {  [2 y, W0 q' @" l; `
  31.         }
    * u+ J4 a9 |% d) M

  32. ( W* a* C$ y7 Y. p) F  L; p
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    9 D$ l+ Q3 |9 t* b5 y2 K
  34.         {/ P# T& L3 y2 y) T8 _  V$ q# j
  35.             switch (cmd)
    ' a- [/ J$ z% Q2 z
  36.             {
    ; l4 H$ G$ O1 R+ |
  37.                 case '1':. V# ~* _/ K3 l/ b) z7 e! `6 n
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);/ `6 D9 `) r3 a, q" ^, A
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    8 e- N$ K; q# C! `
  40.                     break;
    8 ]# z0 @+ ~& `- r; R. {* z
  41. / R2 m* f+ h/ I3 }# u- ^! n
  42.                 case '2':- K2 n3 S& {4 H' Q0 z8 B$ K/ s
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);# H& e8 ]9 V' b  R
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */& N  B% `! ]9 ]1 O; b
  45.                     break;. N: Z; F  G" K& G, w
  46. - y( U1 U. q/ X
  47.                 case '3':, P! d7 n: r, t. u* {
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");% ~; B3 @# y% R/ _, \+ e, P
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    & g7 W- \* J9 K1 f5 u) J% ]# P
  50.                     break;
    7 Z9 t3 j& u7 ~  e3 x( Z. q

  51. & c( ?, y. F2 I. ?( j8 u1 w
  52.                 case '4':8 ~1 _; O3 [7 n: n
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
    * D/ `- Z4 F& E0 ]6 J$ A/ |
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */. r2 q$ O7 M; }: P% x% B- a, w
  55.                     break;
    0 Y7 _8 u' t- G2 }5 a' S
  56. 0 i5 C$ v7 t8 z
  57.                 case 'y':, o8 c$ d* @  M
  58.                 case 'Y':7 ?4 A5 Y( S& q% t
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");5 w# @9 _& |; Z4 [
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
    2 x- l: i6 e% Y! V1 i2 H
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */" f8 X6 t0 Y, x8 L- b5 R
  62.                     break;1 C( `6 @( z+ {* r; j
  63. 1 ~: M' c) d( J5 m9 B& A# N
  64.                 case 'z':. |) M! f% d, _) j5 i6 u# ?
  65.                 case 'Z': /* 读取前1K */
    2 D; \% k* a/ P4 }0 n; ~# q; W/ c
  66.                     if (uiReadPageNo > 0)
    ( m  d: L' x# U9 n9 E  g
  67.                     {
    : p  i& n0 e: g
  68.                         uiReadPageNo--;
    8 [3 z* J# Z4 [
  69.                     }0 L5 L, H3 k: R% d1 Z; G# E
  70.                     else4 V  q& ^# h* s" k9 Q" E; V, ^" |
  71.                     {9 Y+ E( m3 n1 j" |4 }* u
  72.                         printf("已经是最前\r\n");& K4 E: ^9 }) m1 l) k, o& b
  73.                     }+ K0 n9 g  {4 b3 z, }" ~! R
  74.                     sfViewData(uiReadPageNo * 1024);% ~0 i/ A0 i+ E  w& r* L
  75.                     break;
    * e/ X5 I0 `& n* c  N; c7 b9 s! ~
  76. 0 `+ l* v- Q, J2 r, \( x$ u
  77.                 case 'x':. V7 z) ]6 _" W' K* W2 u+ c
  78.                 case 'X': /* 读取后1K */, C0 P/ Q+ F# B, p; k9 g, S
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)  o; }7 A* ?: I8 _) d2 [# \$ x0 T  S
  80.                     {' {1 q6 n8 n6 H  q9 |+ \) p# f0 K
  81.                         uiReadPageNo++;
    " ~! H9 w( p" _/ m( D4 U" ]& L: r
  82.                     }3 [. _. u; n0 h3 A9 u, B- c* P6 i% F
  83.                     else
    $ E' o% [/ D  W" E0 G- c! c
  84.                     {5 C% K# A8 I- F" ?+ G
  85.                         printf("已经是最后\r\n");$ G4 r" l- b$ W* K4 _8 A
  86.                     }+ ?  m$ r: d9 K* E) a
  87.                     sfViewData(uiReadPageNo * 1024);
    ; v7 Q! k* O1 Y; a% r
  88.                     break;
    4 P$ k! L4 y" y% j1 b  Q' z
  89. : `4 E; O; F7 z9 ?+ h, f; f
  90.                 default:
    9 k: H! M# }& V7 Z5 |/ v4 C5 p1 H- X0 P" G
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    4 C7 e, G/ v- W2 M4 `0 i7 g
  92.                     break;
    / d5 B9 X; l2 V+ s, Q, ?- z) |/ i
  93.             }
    7 t  j+ K7 f& o5 o" ?: v$ W
  94.         }# j' v6 U* A+ ]* C8 P  h3 a
  95.     }
    4 U) j& j/ w) B+ [$ b4 y3 Y$ O
  96. }
复制代码
, u/ M1 `( e. I4 }- B" E! |
79.9 实验例程说明(IAR)
  Y, p6 p2 O6 w/ F+ G配套例子:
2 q* g7 w1 g9 {1 \4 k2 f3 x- t: ^) u$ c/ K' o+ A8 u; V
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)2 J& H$ O% ?9 K$ d: U

% h& D! K1 V7 [! |/ @- D+ PV7-059_QSPI读写例程,查询方式: N: M' l: v5 r8 q( r% b+ w% w
2 k0 ~* U% G4 T, U& G- f! q7 H
实验目的:' o/ Z, x& x" i) f, M1 P1 z* M

( ], @* z5 N& N/ g% i学习QSPI Flash的读写测试例程
; T& e, L  ~% a4 L1 B0 t实验操作:4 ?  e+ p  t5 b6 P8 j" i3 S

6 H% B# P/ `5 r& u$ r* _. M. M支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可
- p3 H1 R9 r( |6 F. pprintf("请选择操作命令:\r\n");
/ W8 ?% ~+ g; ]+ h. Z  x, B$ _7 o) kprintf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
0 O# _- V2 F! Aprintf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  D1 C3 A5 z% }5 Eprintf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
1 n' ]0 z9 U1 W, b0 `7 q! qprintf("【4 - 读整个串行Flash, 测试读速度】\r\n");
% n/ T9 T  C$ i. N& Q/ I; A  i' tprintf("【Z - 读取前1K,地址自动减少】\r\n");- U; M' z( \/ D( ]
printf("【X - 读取后1K,地址自动增加】\r\n");  H6 w& K- K  V3 ^3 y! w; v
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");/ q8 I/ Y- ~6 r7 \+ u) {0 Z
printf("其他任意键 - 显示命令提示\r\n");3 Z( J# r1 ?1 ~* I/ d1 P$ S
上电后串口打印的信息:7 m1 H* C: K* v( }: j, m- ~5 h
. m! y0 U, ?8 e! a# @
波特率 115200,数据位 8,奇偶校验位无,停止位 1。( p4 z! E' J. O7 Y3 {3 f4 _
1 Y1 n6 r- L$ w/ A+ W! S- k0 C
3ff8536496d184105fc92ca093930e2b.png

  X# x# Z- p, H" d- ~
  i5 w% w9 e0 d5 N* b/ @' k程序设计:
$ M( C" Q, ?1 @" P  F9 @" W  ]9 C7 W/ \* L
  系统栈大小分配:
6 U( H% z% S; q7 {8 I7 ]: R& n! t
. K) t/ F+ N2 s8 K4 l8 B2 @* v
29c63afe41336006cacb94f46fe77b35.png
0 L  ]0 J5 V4 }  p" o" l

6 i: O& B! J# g- M0 C  RAM空间用的DTCM:
/ R; |! _! F) N7 ~9 V# V0 s0 F* Q& O; W* A, y9 t3 y
4dda996917dfda9ff3ebfb7ab53d3531.png
+ X: q! H5 Z9 J) |: p1 M

7 p1 Q# v% {( ^5 W* `% k8 d  硬件外设初始化3 N) R6 z) L0 }5 Q: o/ J- n' C

/ t$ T4 ~, v. K硬件外设的初始化是在 bsp.c 文件实现:6 g: a9 |- h- X+ T% e) c7 ~: s2 F
3 ^5 b7 k* Z/ {; E
  1. /*- Y; B' C% |) O
  2. *********************************************************************************************************/ ], q  P( B, r2 c$ {
  3. *    函 数 名: bsp_Init
    $ U3 [8 _6 n5 G$ Q) Z) {5 ]' R
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    2 i( R6 Q3 A% X7 a# n3 G
  5. *    形    参:无
    8 n  M$ n; X7 G5 Y
  6. *    返 回 值: 无1 O# Y7 e% R' @, ]1 i( \
  7. *********************************************************************************************************
    : X, B8 K9 k- }8 W9 ]/ V. ]5 a: Y# v
  8. */; \8 F3 w& e& A
  9. void bsp_Init(void)
    3 d9 k5 H' u/ q6 T
  10. {
    ) ?9 d- @2 Y, |( I0 y4 ?
  11.     /* 配置MPU */
    9 o! }( t! L- h9 c) Z* z
  12.     MPU_Config();* L, S; u8 e+ X. l3 @( G

  13. 1 v( }5 H* _6 ~6 F5 ?
  14.     /* 使能L1 Cache */
    # \9 n4 Y3 A+ k
  15.     CPU_CACHE_Enable();* a/ y) s, s9 q: D. `+ ^
  16. + w) F; C& C9 T8 b$ S* J5 B
  17.     /*
    2 G9 l/ J0 S# A' k. p
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    ! Z! R2 @- _& C5 B1 ~4 ^' V, V8 r
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。4 Z1 x' F: K; B
  20.        - 设置NVIV优先级分组为4。2 {+ ?: b5 \9 ^- t% ]8 F
  21.      *// U, \- u% H. e  [, a$ c
  22.     HAL_Init();
    ! W8 P* C4 t7 c' g

  23. 8 f2 l. b. m6 o# a  n6 P
  24.     /* * ~. U1 E4 ?8 _. T* l; o/ i
  25.        配置系统时钟到400MHz$ W  q7 P5 k2 o6 h' V0 R# k
  26.        - 切换使用HSE。% A4 M: S. N+ h! V' @1 v: R
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。( ?( ?% B0 [& F' v
  28.     */- o0 U! A2 x* C4 e* S
  29.     SystemClock_Config();3 j  p1 b$ i. F) o& o+ s
  30. $ V9 h. O0 E8 M5 Q; N) i: T
  31.     /*
    6 M: o# t' c- ?
  32.        Event Recorder:
    ) J7 X1 B, g2 s: U7 a
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。# p( P3 e4 V1 X% B- n  O# ]
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章6 e( _1 R6 m8 u) p" L* h7 R1 c
  35.     */    " }" p5 a5 }  e$ ]5 j( P/ D
  36. #if Enable_EventRecorder == 1  
    ( A4 A+ O# D! N. J; z
  37.     /* 初始化EventRecorder并开启 */
    2 G6 k0 \: f9 j
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    8 U) r# u6 l  w
  39.     EventRecorderStart();
    * \+ F* q) S5 B" c0 x& v
  40. #endif5 w4 V4 ]4 h' G% Y8 d! f

  41. ) i" i* X5 b8 K% w* r5 M
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    : i* d  `- V: d$ y
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */4 ?$ B2 f- ^) k' E
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    1 v' m6 ]. Y4 o) E5 ^  z
  45.     bsp_InitLPUart();     /* 初始化串口 */
    7 ?* _8 |" \; ~* A6 X2 m( ~( O, `
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    7 Z8 R" {0 m1 b, c+ b
  47.     bsp_InitLed();         /* 初始化LED */    3 N& G$ ^; _5 D& U& j/ F0 s( m% w  H
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */; W0 o) h) g( h! f- M* q

  49. ' Q& s0 w$ U: I! |8 f5 r8 O5 _
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */* e: I- U5 A; T
  51.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码
8 h" E! q7 }/ y9 W6 D% E' v& i
  MPU配置和Cache配置:
9 i' Z- s( J. h& L# \6 Y5 h
; P$ x8 S7 t* ^- x  C$ O9 M# y数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。: m- |, H* `  I4 P9 V3 a/ T% a! z

' ~, p+ k7 n! b8 L
  1. /*
    ; U# h4 C. J% F2 B
  2. *********************************************************************************************************
    1 V. J+ i% |1 G& Z" ?; x4 a
  3. *    函 数 名: MPU_Config) d0 s) g1 ]" B' S& X& R
  4. *    功能说明: 配置MPU  W# X! ~2 h# M) Z" h
  5. *    形    参: 无
    $ u9 n- o3 ~. a6 n
  6. *    返 回 值: 无5 [; ^! K+ ^- R0 B7 u3 H" ?7 |
  7. *********************************************************************************************************
      e! `3 k1 H0 e
  8. */- c7 u0 L1 O& E# R( {) t
  9. static void MPU_Config( void )
    0 \5 k+ a; n# c. n
  10. {
    2 K9 v4 o# q+ X+ n
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    - i5 ?* O  [9 m* R2 y$ ~' O
  12. 9 G9 ]& v7 _8 W+ Z0 W
  13.     /* 禁止 MPU */
    1 s- u% v4 [- M% e2 {# M8 ~" e. H
  14.     HAL_MPU_Disable();
    % U/ `* G& ]6 A, h9 y  E

  15. 7 {3 w" o7 A' H% q. U6 U
  16. #if 0! ]( A2 u- u4 s* j. r) S, X
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */0 c1 A8 R) ?7 M; y# @+ n
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    # C% e* N7 j! M
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;
    7 Z- ?: x; E3 G& i( M  }8 ~
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;, ~# u' f% j6 r! t6 w  h& X9 t$ @
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% L# u4 ~! y2 V
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    * [8 \3 i. r  H2 i0 w- q3 L
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;3 Y' _' [( Q7 h
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;; L2 `  |9 A. a; M8 Y
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    4 Z' o$ `) E+ W$ z- n
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    , s; u* T: h9 N, v
  27.     MPU_InitStruct.SubRegionDisable = 0x00;
    . H* n: o, F- [
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;# O6 @9 U* t+ V! C* T

  29. 8 C* z. O9 j. n$ \
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);% u. Q& i% w0 d$ S
  31. . A- Z6 K: Q9 |8 l8 E. }
  32. #else
    ; N) |2 `9 H* f# A3 L
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */$ q: P# ^. c4 |2 t, V& H
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;, k' n1 E  f! n/ e* k
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;3 h2 Q; K. R. O. z# x
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;0 q1 O0 _& u; E3 c
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;+ r0 w0 e6 b) D3 Y# K, g
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    , w/ e- |3 ]0 Q% E8 P6 ^5 B
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    , \) L6 i  u* j5 w4 D  |9 q
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;9 S1 Y  e" }) k8 B% E
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    " k/ A1 C& Y! @8 O- a3 Z
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ) J. |' i; y- K- T  K0 i; }' g" d/ ?
  43.     MPU_InitStruct.SubRegionDisable = 0x00;
    % Z+ j% D( E9 q/ a5 g
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;9 b" W5 B* |' Z
  45. ; F+ Z; p# Q1 R  s, W
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);$ L: k* R4 {7 \: n
  47. #endif    4 Z6 r7 D" d9 [2 E0 v0 m. B9 Z

  48. # _, }7 W$ `) n7 Z0 p9 w7 V
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */* ^. H9 A) d5 m' q* Y$ F: w4 x
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    $ F2 f" T! d3 z
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;4 [( E- f% m1 Z6 n) Q! u
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    * l8 |5 z" Q7 h, E
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;" z# d0 _( t/ {5 q3 D
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    * T( y; ~" H4 l8 y7 q" q1 t( e
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    6 `5 o, M# e. L" q. h
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;" \8 ]9 A' u  J+ G
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;7 e5 D; t6 P7 D2 K" \& w
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    6 ]. ]' }4 `; @
  59.     MPU_InitStruct.SubRegionDisable = 0x00;
    7 v; a$ t) I2 a
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;# P. [$ T3 N( E1 W# _
  61. & J/ K- V1 t+ \
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ; i* Z! R. u: w+ x
  63. ' _+ r! \& ?" Z% v( Q; l( y1 q8 t
  64.     /*使能 MPU */. L5 I# l" h5 M2 `+ W3 ~7 ?) ~7 P: g8 ~
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);& E8 S7 t7 a/ K" j& {
  66. }
    & P! |2 N9 Z2 s- n
  67. # c" D* c7 K6 `( N
  68. /*
    7 p+ j  ?* ?, \! z1 l6 `' Y: Z
  69. *********************************************************************************************************
    1 ~9 g" v2 R0 P7 Z7 a7 y
  70. *    函 数 名: CPU_CACHE_Enable/ w9 Q6 Z, B# Q  [6 L5 j* b) E
  71. *    功能说明: 使能L1 Cache
    9 V, c+ [+ a) ?! L
  72. *    形    参: 无& T1 S) m- V$ Z0 }2 H
  73. *    返 回 值: 无3 m) @# H/ ~; V: ]
  74. *********************************************************************************************************
    ( g$ {: |% p6 V6 C
  75. */: e/ H5 J( p. M0 a2 Q+ H% X" N
  76. static void CPU_CACHE_Enable(void)
    1 j0 ]& l; m4 T
  77. {+ N  a# R! c, H$ e% L7 J
  78.     /* 使能 I-Cache */. v% a% q1 X4 M+ u+ T6 u/ r
  79.     SCB_EnableICache();$ Q4 z9 V2 Y0 W9 W" e. o2 E  ?2 x

  80. - a& v; |8 ^% h8 i7 P7 [" [
  81.     /* 使能 D-Cache */
    - |' c8 Q& M4 `& E- q& ?
  82.     SCB_EnableDCache();- u6 ?0 c4 F$ z/ V, f
  83. }
复制代码

7 v( E! n# e' S) m  每10ms调用一次按键处理:6 g" |- P" V8 R# O! |$ g

1 V$ C# X9 i) p$ n! ]/ T' ^按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
& \" s# V+ b7 n* g  @# a9 ]' W" P' J! O0 ?! t6 F* q+ x
  1. /*7 f8 @& v! Y5 a
  2. *********************************************************************************************************
    0 ^! @; c& L( F; W% e7 s
  3. *    函 数 名: bsp_RunPer10ms" W7 j$ u9 e2 G# C% O
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求8 d' F7 f3 X* j# q5 z
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。. J) g, T" g# t1 q# C
  6. *    形    参: 无' f' J4 W) p) |, h5 K: g- S% }
  7. *    返 回 值: 无
    ; F$ N4 w7 F2 C+ T2 s0 K
  8. *********************************************************************************************************) R; z! D  b1 |; S2 w5 }  V4 U
  9. */
    , ^- u; [2 U) v$ x5 v. K4 I
  10. void bsp_RunPer10ms(void)
    ' E  U6 a% ^/ n2 A8 N" a
  11. {9 O( L9 }: l8 x1 c
  12.     bsp_KeyScan10ms();9 k1 O! T- d9 v& g# X. y
  13. }
复制代码
. I1 G) j% A5 d/ J; `
  主功能:
7 A, t; u0 @6 Z6 G1 S! Z+ P# A$ H" H; |
主程序实现如下操作:7 s( Z9 X. V* R# G

2 w# H# Z" P( Q8 Z- d  启动一个自动重装软件定时器,每100ms翻转一次LED2。
! Z& z1 _, s) o: S, W. s  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
  i3 u; }* y. ?  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);2 o5 B. k9 t% A& F
  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);8 z( y. }8 q6 c9 f, c, _
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
( [  T/ M) ~0 U/ w" B. j7 p  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");( Y& @# b" P% i+ u0 G. C6 M4 P
  printf("【Z - 读取前1K,地址自动减少】\r\n");
7 O+ M; k& @1 \8 V2 B# E8 K9 [0 R  printf("【X - 读取后1K,地址自动增加】\r\n");6 }4 P; B9 \$ f# K( B  A( e1 b
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");( t9 w& H8 d7 Q# B5 d6 o" O1 r* N
  printf("其他任意键 - 显示命令提示\r\n");
2 {) h" i5 W7 ?# L: j' t) ?
  1. /*3 x4 R7 N- f; N- [7 U! d
  2. *********************************************************************************************************
    0 X& k5 l0 S% y7 l
  3. *    函 数 名: DemoSpiFlash
    3 z4 [/ f) B9 W: j' {% R
  4. *    功能说明: QSPI读写例程
    8 P( b- N( t6 N5 V- t% ]9 _# S
  5. *    形    参:无4 Q9 h# s9 @1 y- b
  6. *    返 回 值: 无+ Z5 g6 w: _  z* g
  7. ********************************************************************************************************** Y, K9 {1 Y. P6 E4 m" q
  8. */
    9 Z3 X: A4 d( ^8 |7 ?) @! s0 M1 W+ O
  9. void DemoSpiFlash(void)8 ?, j! m' y/ U" `! |8 [# n
  10. {6 ]6 Y- L, a! w' m( R. n. k
  11.     uint8_t cmd;# Y8 g- h' e% ]% W  L2 ?" c% Y
  12.     uint32_t uiReadPageNo = 0, id;% T; O3 R" X% K) z" Q

  13.   w7 z- p9 N$ \! C
  14.     /* 检测串行Flash OK */! }1 |6 X3 X) o+ @2 K8 p
  15.     id = QSPI_ReadID();
    8 l2 n- P9 ~$ B5 u$ l) W2 m
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);$ Y3 Y8 k4 U' R/ D; t/ L
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");
    ! v! w0 j- E+ K

  18. 3 T' K+ o6 W+ b. m  ?
  19.     sfDispMenu();        /* 打印命令提示 */
    . Q# p" G/ _5 N1 R: Y1 R6 B
  20. 5 \# T* o3 _; j! c
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */
    ( m- m9 r8 C# p  O
  22.     while(1)
    4 T8 F2 x3 R2 J6 Y4 V6 V
  23.     {- U' R( ]6 m! t! ]5 Z; `
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */- g6 J9 ^* h: _# u( M& P

  25. $ L( u+ j  q5 Q  a2 Q# Z* j
  26.         /* 判断软件定时器0是否超时 */* i  _$ T: n2 i" N1 I
  27.         if(bsp_CheckTimer(0))' _1 Q" R$ p& x7 V' I7 M
  28.         {
    , L" \( ^) m) U( ^' ~. ]
  29.             /* 每隔200ms 进来一次 */  
    - b/ [' v+ H8 P9 k7 @8 v' y9 Z! a
  30.             bsp_LedToggle(2);8 S# z, h( I! H) g
  31.         }4 a/ Y9 \5 H3 B! w
  32. / s6 c' V8 B0 [( H- d: u! T, P( C- d
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    * y' Y  p; Z! |3 c
  34.         {/ w9 \) l  _; _) H9 S. H; P
  35.             switch (cmd)
    7 J: d1 r, Z5 B$ U: u3 V$ y
  36.             {* {1 m* R' B! [
  37.                 case '1':& W8 n" f6 V* R- a( l
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
      q6 D6 \* g" t$ p1 i! I( Y
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */% u# d  |1 l5 n, s' g" B2 g: C  A
  40.                     break;
    1 [9 H- n7 P5 n4 a" p1 P1 ]) y9 c

  41. 6 R/ o1 E1 F* s5 V4 V
  42.                 case '2':
    - V& D0 I& i- P8 i
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);3 ]9 r* K7 K( K8 Q
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */: K, a; S! Q) B" z  X4 j
  45.                     break;1 {; u, z. t3 S* J
  46. 1 c' @% x5 u7 e# G
  47.                 case '3':6 `- @7 @# m  w2 @9 `& q5 ?! \
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
    ' ?5 C2 o! u* P  W. ^$ _
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */' u) R) a0 s6 X
  50.                     break;3 ]7 x; k/ c% |) F: D$ Z* P3 K( z
  51. . \% N  ^* ^/ O6 f5 R8 R( y$ U4 f
  52.                 case '4':
    , a$ u3 v8 U. ^
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
    , M4 ~# ^  X" B" K4 g
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */$ Q* d4 M  K7 V# `; w
  55.                     break;" C0 a; }0 b" b# {
  56. + \( ?* Z9 J  M9 d( h) d" C+ Y$ S
  57.                 case 'y':
    8 J6 W' F; g. T0 \6 g
  58.                 case 'Y':
    , I# t1 g8 f4 g# L8 n, {3 O8 w
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");! l1 k) V6 O# N, t' o( [: e
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");0 w8 }/ D8 |6 p- r) I2 d
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    5 u9 E% ]2 f. [5 {( ]) M% Q
  62.                     break;# j: [' B$ E, w3 G2 Z# y

  63. ! b- V1 a) I# X
  64.                 case 'z':
    ; j6 x" b  \7 _3 J. ?( n
  65.                 case 'Z': /* 读取前1K */+ B0 E/ J: ~1 d7 ^0 J
  66.                     if (uiReadPageNo > 0): M# Y8 Z8 n; w! D6 |
  67.                     {
    , K1 l8 Z* ], Q3 L) t% V% L
  68.                         uiReadPageNo--;% ^. w4 o  s6 y% u
  69.                     }) z' t5 F6 N+ d" q
  70.                     else. p2 U" ^, F: b  V- a2 g! o# |* _
  71.                     {1 J0 H, u. t( _5 [# z9 W
  72.                         printf("已经是最前\r\n");
    ) [+ v6 P7 V: S
  73.                     }" @% V* ?1 r# \) F! Q
  74.                     sfViewData(uiReadPageNo * 1024);0 a/ d# l4 m& \' t3 U2 G
  75.                     break;" S8 S$ g5 q) C) Y$ b
  76. " O8 L1 L, H6 @0 Y0 V. N; x
  77.                 case 'x':
    4 r3 O+ w0 I3 N) q  D- ], X. _
  78.                 case 'X': /* 读取后1K */8 F7 t; k7 [/ Q' O
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)
    $ o& U; v* A) x) B3 I" |9 e
  80.                     {: o( V, ~- `$ E, @3 p. R
  81.                         uiReadPageNo++;& M/ [% }6 d0 S! l: Q% a
  82.                     }  }8 `% z4 k! d! \! K' u% m
  83.                     else8 a4 ?3 e1 y/ ]% K1 r" q, U
  84.                     {/ q1 R4 V. @# j9 B: |
  85.                         printf("已经是最后\r\n");3 y9 K; Q; p" g6 }. K8 y
  86.                     }3 A, q4 K. m' V/ x  |
  87.                     sfViewData(uiReadPageNo * 1024);3 p* ]' J& G8 s8 O
  88.                     break;9 }" @. }9 d, g2 C# e
  89. 4 R4 S) B4 K$ m  l
  90.                 default:
    . N' A2 o, N7 R( q
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    9 T9 u, D" @- P% _
  92.                     break;) E# P/ x6 t. i9 a+ |4 G& s+ J
  93.             }
    ' d4 }% C0 a, O% I
  94.         }
    . `+ p- F  r7 x' a- A
  95.     }
    . k4 g+ l+ e2 g, i7 y* {, Y# `
  96. }
复制代码

. F/ n7 o4 v, [79.10   总结# W" M% ]: L$ I% N) J: p
本章节就为大家讲解这么多,实际应用中根据需要选择DMA和查询方式。: D4 B+ m" p0 i' F

+ V! `2 ]. C2 Y" f1 [( h" G) G. b5 |7 r( f7 t% ^3 {, A
f9d118d4b7d97794c046ced058924277.png
940488dfa9f0e39fce107fbd8fd9ea16.png
收藏 评论0 发布时间:2021-11-4 21:24

举报

0个回答

所属标签

相似分享

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