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

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

[复制链接]
STMCU小助手 发布时间:2021-11-4 21:24
79.1 初学者重要提示
5 u0 Z) I6 A5 C! U5 l 1、 学习本章节前,务必优先学习第79章。1 Q8 h9 E& j. @) E: D. n  O: x# J
8 n' w# f. C/ ?  F
2、 W25Q256JV属于NOR型Flash存储芯片。
  S2 n7 b, |: L
& d+ U+ R: @! N. f- ^. w/ a* r 3、 W25Q256JV手册下载地址。  L6 Y: X& h; E3 w4 g5 F
6 r* L6 Y. M& I, E7 i/ g9 v6 r
d158c855c3630d64e0d72336ffa1a440.png

4 ~3 |- ^& {0 \2 P: ?; M$ c4 |
: p. i; u2 M, h. g  G4 O 4、 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。6 ~' |; V; R" K5 V) J: z7 I

/ t; |* r0 O7 k 5、 对QSPI Flash W25Q256JV的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。% F6 |7 O) P, k% u5 T
( V& V8 M& r; _" }) u+ @
6、 W25Q256JV最高支持133MHz。
: q+ i. N2 B8 D: y5 L) ]6 W8 P+ W4 ^  l5 I2 P' K
7、 STM32H7驱动QSPI Flash的4线DMA模式,读速度48MB/S左右。% W- {$ j- q0 Y: _2 m

) `; D/ `- D" @! Q 8、 内存映射模式下,最后一个字节无法正常读取的解决办法。, ?  B6 i7 A3 Y, ~) Y  [
) D7 A# G5 U% [) P* v2 C9 \
9、 本章配套例子的DMA是采用性能最强的MDMA。
# u+ T2 t! c1 M1 f  l* m) F. b/ \% R# O( }7 w# h# v& t+ \* t3 f: I
79.2 W25QXX硬件设计
; `' `  Y6 Z% B8 q* ^4 xSTM32H7驱动W25Q256JV的硬件设计如下:
6 S8 V2 W) E, g, S1 j+ B! T7 V- G$ ^+ F2 Z" C( f- ~" j# ]
43d24f69b56108e41724030a699d04ea.png

9 V! v5 z# t( M9 v$ i  u. ]! L8 ?1 {# X' `
关于这个原理图,要了解到以下几个知识:1 f/ I, |' R% m9 T% k) }) G2 T

# Y& [/ e: ]& l  V7开发板实际外接的芯片是W25Q256JV。! v8 f% c0 n2 D. G8 u
  CS片选最好接上拉电阻,防止意外操作。
9 B; ^; m6 ]  d- ]# a3 j! y9 i( a  W25Q256的WP引脚用于写保护,低电平有效性,当前是将其作为4方式的IO2。
1 a* m: G0 x$ [* ~  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。当前是将其作为4线方式的IO3。/ M/ J! |9 Y9 N5 `8 Q
79.3 W25QXX关键知识点整理(重要)
/ y9 q- [8 M! P2 h6 E. Y5 d4 }驱动W25QXX前要先了解下这个芯片的相关信息。
: J, f2 ^5 D- @* F, E4 Q8 Y2 {$ _5 L! o; K! @" T+ a
d211eea6c41b7da28846a28c61bfebf4.png

2 l8 Y* h, c+ t. @& i( M& X  S+ u2 X' X( Q
79.3.1 W25QXX基础信息
, v5 I6 G# P) \+ |- k; K) P  W25Q256JV是32MB(256Mbit)。
! r' k" M+ e. L! J  W25Q256JV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。1 N/ x' Q0 q% L" f& A8 [
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
( R$ |9 t0 g3 U9 v3 t: r- q
: L  S4 O. I) \5 O" |9 f0 u' q支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。. f( A! n- s9 V) q1 _4 H6 m

; d% Y- I4 d% t/ L4 w  r* `/ m(注:这里几线的意思是几个数据线)。
. r. w: l( Y* _
5 G& t1 H, h& ]  W25Q256JV支持的最高时钟是133MHz。
3 u& }- o1 w- n- ~  每个扇区最少支持10万次擦写,可以保存20年数据。
5 x6 l+ Q: y5 N6 S  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。+ Y/ k7 l+ m( j. d) J: [
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。4 E6 P! h. z. C) u7 x( q) {7 d! Q# H
整体框图如下:& A6 t. _: ], g
8 P: k- b8 i; ^. h; V! Q+ b& n, d( z
2da620aeaf02ba8b3035e0b864e140b2.png

( ]3 C4 \+ C! ~
! L3 d/ @5 c6 n7 q/ A( u& tW25Q256JV:
& ~, B) E$ A9 g0 b( O/ C4 R( ^
9 ?0 x- G+ B  M. |  有512个Block,每个Block大小64KB。
6 Q% S! ]9 X! T1 n$ n  每个Block有16个Sector,每个Sector大小4KB。
) [# |( a- T8 L$ A  S. q  每个Sector有16个Page,每个Page大小是256字节。
3 P. v8 `0 \, s7 |% [0 U79.3.2 W25QXX命令
1 |3 m" ^% i1 Z) s3 ]/ \0 a使用W25Q256的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的QSPI,即4线SPI,并且用的4字节地址模式,使用的命令如下:
' h/ r) P, W; d. ?  X8 m! n9 D" l
* m8 h; U8 Z/ c/ {* k
77c20d25330bfc517d68b73c7751ddf3.png

" B) d9 M2 W/ H4 v7 J+ a( q7 a( E; [% J1 L
ee48eed7fa217e118947120ca34f5263.png

/ s5 a0 F3 U% ]; \1 L# O" V8 g/ o9 w
当前主要用到如下几个命令:
+ g, c  F2 ^7 k! s' O$ t' W; P2 R
* R+ R+ P  A- E" X- s2 s* z
  1. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  3 V8 ^+ Z$ F+ s/ I  C) @( E
  2. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  ( g4 W; M" x- m4 [4 I$ D5 ^$ Q
  3. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */ # u; A) a8 R1 L5 V+ v
  4. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */ " I* @7 P& w# e6 h; N; ?3 U
  5. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    & p* B$ N' W7 F; ?6 J' {# x
  6. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */. x' K5 U/ }! G. ]- l& T; Y" F
  7. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码

7 G" \! X- B9 f6 [79.3.3 W25QXX页编程和页回卷
! X* U4 C  P  Y7 `% Y! {" KSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
0 q1 r+ p- ]- |9 A5 b' p
/ T3 b, v& ~$ B8 r. k3 M+ {
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    6 b. r& m; K; l6 P) i& `; v# B
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    ) U% p- e5 r% Z& y8 x
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   " ~& o9 G9 I0 [
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               & H1 ?8 G0 g) s. n9 A% t/ f/ Q4 N7 \. ^( p

  5. 6 y7 f% g* B$ P  I1 Z# G0 i- R6 K
  6.     for (i = 0; i < _usSize; i++)0 f1 g, _5 e" ]( S
  7.     {
    " T9 g" G4 T1 `' E; c1 H: R+ m" w
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    8 o9 `6 }3 I: n( h; j9 Z4 l3 e/ Y
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    / q: Q2 E5 D- f9 Z' Q) q
  10.     }
复制代码

- l- F8 H9 `& L. H( J页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。! q( X) n& }! `

: q, o  ?- w) D$ B比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
1 L2 @: U% ?# x  N' P1 u/ R
0 }* j+ `1 u) n9 \, n/ F7 a
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};& f' ~1 ]& F8 t' b7 |
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

) i( S' I9 B% L: u' A) d  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
& q+ c# W1 r( n+ ]  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);1 q, E- F6 Z2 X6 Z2 a/ [/ ^
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);8 m( A: ~9 x. F
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)' a3 m6 N: Q9 q4 u; i" {
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。- L: H, G  P' I5 ~
8 m; N2 t, u0 g: D
56240b94a12eb9a067e63db9803b13b7.png
) J$ @8 u4 A% p9 w7 {! G
6 r  @" B9 i0 g( w' O
79.3.4 W25QXX扇区擦除9 q4 N" k! e1 Z) R# g) V& i
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。( ]2 m5 w; v$ S1 N7 B
' [% C+ x, i% y3 c0 j
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。! |! v2 Z1 _7 J
, ]6 Y+ F6 Q* ^* ^5 |3 d
79.3.5 W25QXX规格参数0 e7 z& i+ {8 w: f+ q' d0 }6 j6 I
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:3 Y. o6 T7 I- x- e
! v( U! m7 p- W: d+ Z4 W+ p; k
e21b7448e777b6e8e376fb71ea3e75ee.png

! @* T- d, E& p3 ]
. Q+ G+ g. s2 @1 m" n$ m# G7 Y# s. ]. Y' A! T3 D3 Q' M  C# f
  页编程时间:典型值0.4ms,最大值3ms。
* P( U( B  U5 C9 P  扇区擦除时间(4KB):典型值50ms,最大值400ms。( r9 Y$ |7 h4 W
  块擦除时间(32KB):典型值120ms,最大值1600ms。
6 ]% D& B$ }1 O8 p  块擦除时间(64KB):典型值150ms,最大值2000ms。; J* j8 V- `' `" J- o- q
  整个芯片擦除时间:典型值80s,最大值400s。
2 g, @& O- N3 z9 E2 L4 q: |% H! @5 v7 k
支持的速度参数如下:
4 t/ X  ?- k3 x( Q* M' m0 C6 \2 F' \4 m% ]% X* m) M
9ab6f9e23e8cfb4e497e67913dda1d7e.png
/ {0 ~2 c1 U6 s% T. \: R( u8 |
& [2 m6 u$ K) Q8 E1 B" t" V
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。& [2 ]2 i9 e) P) }

" w+ C3 N: W* r$ S+ W79.4 W25QXX驱动设计
6 f7 B- B& @5 `% D8 V- ~W25QXX的程序驱动框架设计如下:0 {/ q: A- N8 h: y/ ~; |
& V4 Z2 G1 Q" p4 X2 X- V
0edab8ae6a09033608ede0892279b72a.png

) D! K# i! V7 }% F+ M( @  g8 A+ f6 s. a0 |; X1 A1 H% b
有了这个框图,程序设计就比较好理解了。
0 S" X6 J- {( o4 P  }6 @
2 m! t* T" V4 N/ v; ]; c- F79.4.1 第1步:QSPI总线配置8 [7 N# \( n6 n3 t3 \
QSPI总线配置如下:% p! N! W% h; ^/ R& e
" V6 }' T! [/ m& i7 _7 a
  1. /*/ J! Y8 c! G' g5 Y
  2. *********************************************************************************************************# V' {) T1 c: m( b$ u' `9 {8 H! t
  3. *    函 数 名: bsp_InitQSPI_W25Q256% l+ j. k9 t& H8 k
  4. *    功能说明: QSPI Flash硬件初始化,配置基本参数
    ' ^0 |2 B$ G0 x- L4 A* ?
  5. *    形    参: 无
    + h9 e2 H0 w% h1 N, b
  6. *    返 回 值: 无
    ( X& \1 I% E' C: o
  7. *********************************************************************************************************
    $ O. o! v3 V; Y
  8. */* a1 j5 L7 F. _  V/ k4 v9 ?1 k
  9. void bsp_InitQSPI_W25Q256(void)
    5 N  H6 ~8 I2 y3 D9 F4 _
  10. {" W: ^* Q% ?' ]: K( `! L
  11.     /* 复位QSPI */
    8 _- j9 q6 G+ B/ B8 v+ J- r
  12.     QSPIHandle.Instance = QUADSPI;
    6 [2 h( Z- a( Z8 C+ v
  13.     if (HAL_QSPI_DeInit(&QSPIHandle) != HAL_OK)
      L3 Q+ h: o% m2 U- ]  }
  14.     {
    4 {% i8 W2 `# a9 p% _3 B$ o
  15.         Error_Handler(__FILE__, __LINE__);
    & q$ x8 L( Q5 P# l
  16.     }* j4 [$ G6 W7 @
  17. ! @) N0 e; r8 ]# K  a
  18.     /* 设置时钟速度,QSPI clock = 200MHz / (ClockPrescaler+1) = 100MHz */$ p  a# j! \) C; K* w
  19.     QSPIHandle.Init.ClockPrescaler  = 1;  6 ^9 \% _( ?( F: q0 `  H2 V

  20. . [* ^/ Q$ p8 S
  21.     /* 设置FIFO阀值,范围1 - 32 */
    5 s- q; n8 V6 ~* k! q, O
  22.     QSPIHandle.Init.FifoThreshold   = 32; , r' x/ j( z$ K- D' P, C
  23. # D4 c. x& P  s' J7 D- f& j) B5 d
  24.     /*
      {( ~0 n; ^! n  m, E6 y# l" S- Z
  25.         QUADSPI在FLASH驱动信号后过半个CLK周期才对FLASH驱动的数据采样。8 A8 Z8 m% I) s2 K
  26.         在外部信号延迟时,这有利于推迟数据采样。
    # E+ W/ ^2 E$ ^2 j' x% F
  27.     */+ C. W, Q# O+ P3 f. `% \  t
  28.     QSPIHandle.Init.SampleShifting  = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
    6 ?3 D4 B! T! a; `' l% _! y
  29. 2 u# h+ N7 v$ K0 h
  30.     /*Flash大小是2^(FlashSize + 1) = 2^25 = 32MB */' a' ?( `0 a2 z+ l) j/ w$ U
  31.     //QSPI_FLASH_SIZE - 1; 需要扩大一倍,否则内存映射方位最后1个地址时,会异常。. @" g4 U( I* X% p
  32.     QSPIHandle.Init.FlashSize       = QSPI_FLASH_SIZE; % M" H4 @4 s: Z9 I& a

  33. 0 N  T7 V, u( a8 M
  34.     /* 命令之间的CS片选至少保持2个时钟周期的高电平 */
    $ M$ Q2 q3 s* F; [' _' V) E) t
  35.     QSPIHandle.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_2_CYCLE;
      X+ o: s8 Q! H4 F. x2 R. c
  36.   ]; ]7 L0 d7 m' r  w
  37.     /*) h/ I2 T( D0 Y
  38.        MODE0: 表示片选信号空闲期间,CLK时钟信号是低电平7 I+ e% c6 o, Q+ l# h, z0 Z
  39.        MODE3: 表示片选信号空闲期间,CLK时钟信号是高电平1 o0 _; ]0 x$ S% [! F# X5 l
  40.     */
    - X" A6 L2 E" I* f3 l9 D
  41.     QSPIHandle.Init.ClockMode = QSPI_CLOCK_MODE_0;$ C9 B! F4 D+ g1 e
  42. , g. t6 D" {/ l: W8 {/ |* n
  43.     /* QSPI有两个BANK,这里使用的BANK1 */
    1 a. C7 w& y, z7 o
  44.     QSPIHandle.Init.FlashID   = QSPI_FLASH_ID_1;  r3 W% G3 ~2 d9 I

  45. 9 b9 s: A, P) n9 [( ~& G" w
  46.     /* V7开发板仅使用了BANK1,这里是禁止双BANK */
    / x0 k3 j* J0 W- i$ A# a5 e
  47.     QSPIHandle.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
    5 p/ v' A5 z. j- d2 I& g0 s4 @

  48. $ H. i' S( V7 W, c+ v* y6 y
  49.     /* 初始化配置QSPI */
    $ d* P. ~% r" V
  50.     if (HAL_QSPI_Init(&QSPIHandle) != HAL_OK)+ w2 j9 ]. J' P
  51.     {6 f& d3 i6 [2 Z
  52.         Error_Handler(__FILE__, __LINE__);  J3 N' g# B* O( J+ ^% h- @
  53.     }    , B/ G* a' e9 {8 a2 r9 c
  54. }
复制代码
1 C* C9 }& c, u! o4 q
QSPI这部分配置,要特别注意Flash大小的设置,这里做了特别处理,本来是应该填入QSPI_FLASH_SIZE-1,而我们实际上填入的是QSPI_FLASH_SIZE,主要是因为内存映射模式下,最后一个字节访问有问题。9 a# V- \1 c" q0 N% ?% ?
# _5 h& ^; K! ]( j& W, L
79.4.2 第2步:QSPI总线的查询和MDMA方式设置
* E9 m1 m! @9 L) x! T本章提供了QSPI Flash的查询和MDMA两种方式的例子,驱动的区别是调用的API不同,查询方式调用的API是HAL_QSPI_Transmit和HAL_QSPI_Receive,而DMA方式使用的API是HAL_QSPI_Transmit_DMA和HAL_QSPI_Receive_DMA。) @1 P8 m0 B8 F. r

0 m! X6 d9 ?  v& T79.4.3 第3步:W25QXX的读取实现; w5 }  ?, A$ i9 S
注:这里以查询方式的API进行说明,DMA方式是一样的。0 }  V$ i& Q4 M( \( e/ \4 ^/ O

( W% b: m# l  z; I  y. `W25QXX的读取功能是发送的4线快速读取指令0xEC,设置任意地址都可以读取数据,只要不超过芯片容量即可(如果采用的DMA方式,限制每次最大读取65536字节)。
: H1 o5 a8 d+ B8 _/ r5 w% P, Y; P: r# B( v
  1. /*
    / b: ~; Q4 d5 A/ V9 K7 ]- N& m- Y
  2. *********************************************************************************************************
    % a6 J% B0 X) q- w
  3. *    函 数 名: QSPI_ReadBuffer
    : i0 N6 B- {8 }7 `7 u
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。* f5 T' l& l, c0 K
  5. *    形    参: _pBuf : 数据源缓冲区。# r" B% y. E( e
  6. *              _uiReadAddr :起始地址。
    5 _! A7 f. w6 d/ d
  7. *              _usSize :数据个数, 可以大于PAGE_SIZE, 但是不能超出芯片总容量。! M- i/ A/ i7 J1 i0 O
  8. *    返 回 值: 无
    " d% u, M) ~* G% L  x/ Y  {
  9. *********************************************************************************************************6 A0 d/ ]" x' M7 _& b4 f
  10. */! E% E' y  b- K" }8 t# C
  11. void QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    / S8 Z# h. H( U# N
  12. {
    5 b2 N! O! [# t# ]5 v$ x- b. e
  13.   _. F4 i) C8 S) [' P+ U
  14.     QSPI_CommandTypeDef sCommand = {0};* \8 b3 ]' c" z  A

  15. 3 O& [; t& @* M3 f5 |
  16. ( m$ O- P5 Y" f2 U( X( v
  17.     /* 基本配置 */
    , s/ }8 \! Z8 Q  j2 [. ^/ _% _; N
  18.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;        /* 1线方式发送指令 */
    $ T3 Y, ~% i% C) ?2 Y, U
  19.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;          /* 32位地址 */3 o$ F$ F6 F/ h& M1 P# d
  20.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;      /* 无交替字节 */
    . F! r; q/ `! }& D0 Y7 Y" @; @8 O
  21.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;          /* W25Q256JV不支持DDR */
    ( n- ~* |6 R2 p+ C6 y! r
  22.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;      /* DDR模式,数据输出延迟 */( [% i1 I* {' F( \
  23.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;        /* 每次传输要发指令 */   
    ! k1 |! }) T, @' W6 i) i

  24. 2 o/ ?- _$ I/ o
  25.     /* 读取数据 */1 H) t1 l! z: n( h* v
  26.     sCommand.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速读取命令 */
    ( x7 v+ z9 K0 X" W- L4 S
  27.     sCommand.DummyCycles = 6;                    /* 空周期 */4 d% l  g9 U* C- V0 b
  28.     sCommand.AddressMode = QSPI_ADDRESS_4_LINES; /* 4线地址 */- Q4 w& z) A- `/ O: v8 O* ?
  29.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据 */
    3 D1 W3 U' ~% A: o+ m9 Y4 }
  30.     sCommand.NbData      = _uiSize;              /* 读取的数据大小 */
    1 L6 ]0 F5 T" L
  31.     sCommand.Address     = _uiReadAddr;          /* 读取数据的起始地址 */
    ) I+ e, s3 {) N1 U+ Q

  32. / T  g; U+ r' g  y( D  K8 d
  33.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    * O( X% W) _: P6 b" G
  34.     {
    0 y. ]+ G; O5 }% ~8 L
  35.         Error_Handler(__FILE__, __LINE__);% y5 y" l6 \" }: t
  36.     }/ {/ z. B( ~$ W" R  a- R
  37. 9 H. w3 T  V3 B5 v* L4 G4 z$ G3 ]6 I
  38.     /* 读取 */
    5 c+ m' Q2 u% c# x3 K2 y
  39.     if (HAL_QSPI_Receive(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    % [$ y) _6 {" y* a+ y3 h
  40.     {
    0 s* Q( k( |8 F
  41.         Error_Handler(__FILE__, __LINE__);0 K' H' @" |0 J$ s/ n, Y% y
  42.     }   
    ( R7 u! N/ R; @) [! x4 t
  43. }
复制代码

2 G' S! Y' [4 s- J, S此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
  S$ ^4 A% n/ S$ z8 ^0 k
; f. G$ @; {  Z; b% V
6c1787c10d01bbaf5602747a947053b6.png
) A: x5 R0 S2 f0 k" \, J6 j% {
/ I1 y2 u: f0 G
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
  J. ]5 J$ b  q' _& K
/ e% G. m2 i* g6 }( Z' r4 dsCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
( j, i6 k1 U, }2 _sCommand.AddressMode = QSPI_ADDRESS_4_LINES;2 u  S4 N8 s0 b2 T- d
sCommand.AddressSize = QSPI_ADDRESS_32_BITS;: @1 j  H# j  {3 L) H
sCommand.DataMode = QSPI_DATA_4_LINES;
1 D# s! z( Y" F8 K" w& A: X$ H) d* t  F1 W& T+ j
79.4.4 第4步:W25QXX的编程实现& h; W9 {2 l* T2 x! L
注:这里以查询方式的API进行说明,DMA方式是一样的。
  F) ~8 y1 D* b% Z
" k$ T3 V" a& \0 ?: H8 k下面实现了一个页编程函数:8 [" s' H/ e: `6 ~' @% c
( ?  r3 |, B0 M
  1. /*, D' h/ P* [* t4 Y
  2. *********************************************************************************************************0 d; ]7 X2 `7 u. J2 \8 h8 ?
  3. *    函 数 名: QSPI_WriteBuffer
    ! r. }  }6 `( S; `
  4. *    功能说明: 页编程,页大小256字节,任意页都可以写入
    / N# \0 A$ K$ c' e$ l
  5. *    形    参: _pBuf : 数据源缓冲区;
    7 T+ H7 V* W" O2 u) h/ {5 i! `
  6. *              _uiWriteAddr :目标区域首地址,即页首地址,比如0, 256, 512等。
    " `( i6 o/ A( y/ r$ b& m
  7. *              _usWriteSize :数据个数,不能超过页面大小,范围1 - 256。. F2 _/ C( S; `7 ^1 Q8 W3 m, f
  8. *    返 回 值: 1:成功, 0:失败4 z  J" {6 K3 p, X9 |# ]3 T
  9. *********************************************************************************************************
    * V- ~* J6 o0 v- p' F
  10. */7 U# B% }! C0 I! |' B; G' g* m
  11. uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize): v  p' X1 }6 ~7 |
  12. {& M2 r: w4 Y: G3 v3 _& n
  13.     QSPI_CommandTypeDef sCommand={0};+ X5 S% [8 ]6 L8 F9 y. Q2 x7 r, h
  14. ' A$ f* a# h6 B9 K
  15.     /* 写使能 */1 a& M9 ^" I0 r# G+ T7 p; ~8 q
  16.     QSPI_WriteEnable(&QSPIHandle);   
    " \) h+ b4 S5 _$ y

  17. 9 j0 c- ^2 u) F0 R9 Y& @- \
  18.     /* 基本配置 */
    / ^$ O: y7 M) V
  19.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */5 k5 _2 I: o% X  l
  20.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    6 u5 l8 u& V4 y. x2 x  y
  21.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */) a" m; v+ Q: O7 j3 D+ O7 a
  22.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    9 h5 G  y" I2 q/ P! O3 Y* r( V
  23.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */6 X: w* [" c; Z+ h
  24.     sCommand.SIOOMode          = QSPI_SIOO_INST_ONLY_FIRST_CMD;     /* 仅发送一次命令 */   
    ; Q* ?' e6 L: u: l( A4 X

  25. 9 r, |5 c2 a: q* M/ E. t# A# s
  26.     /* 写序列配置 */
    ( j% _) c2 }! [' d. T5 I0 Z
  27.     sCommand.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD; /* 32bit地址的4线快速写入命令 */5 j0 Y9 C, O  F8 W
  28.     sCommand.DummyCycles = 0;                    /* 不需要空周期 */
    7 R& h- O1 x$ m% E  h
  29.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 4线地址方式 */" K5 _5 q* Z# f  J, z/ r- b
  30.     sCommand.DataMode    = QSPI_DATA_4_LINES;    /* 4线数据方式 */
    - O; ]3 i; X" \6 `) w- S
  31.     sCommand.NbData      = _usWriteSize;         /* 写数据大小 */   
    / @  Q1 l+ p, L! T9 j
  32.     sCommand.Address     = _uiWriteAddr;         /* 写入地址 */0 G8 b" [- I( t, }! X8 j* K
  33. 5 |0 U! G' O) c% Z3 K, Y) a( [. z
  34.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)3 L! ~: w  j/ G2 A6 N+ s( [3 g
  35.     {# K( G- S! M! Q6 A" n# `
  36.         //return 0;
    4 v: |  V5 ?. M; E1 d. T* C( W. H, \
  37.         Error_Handler(__FILE__, __LINE__);) S8 o( z+ w8 D: q! H
  38.     }/ v: t' z, {1 X, B# K2 |" G

  39. ( I% x0 f  ]# J) f: L2 s$ [
  40.     /* 启动传输 */
    8 o1 _  }/ X1 P. F
  41.     if (HAL_QSPI_Transmit(&QSPIHandle, _pBuf, 10000) != HAL_OK)
    ) e6 e( U& V+ e  B# m
  42.     {7 J) h# W5 R' f
  43.         //return 0;( M. I5 W5 v' m+ M! H) A  h+ z
  44.         Error_Handler(__FILE__, __LINE__);% H4 G6 O6 j* G8 f* n; b4 t; C

  45.   {; h! O; h% C7 H! j4 Q
  46.     }( C/ l) @' T$ D4 {! t
  47. 9 ]4 s2 {& b4 y9 d# H* |) X7 i9 y) ^
  48.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    ) f/ ^. C7 l# y1 m3 y

  49. ! o6 q5 }' a6 `
  50.     return 1;
    8 l, _  J1 v) V: |* }
  51. }
复制代码
% T' i/ U! p2 m5 s
此时函数使用的指令0x34对应的W25Q256JV手册说明,注意红色方框位置:
- F5 c5 h+ I  B& n9 }$ M1 |' \' D& j8 u" X- {; |9 E

. G/ v2 c9 J6 J: H# N$ b$ x
. M- g5 e3 z! ]左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,并且采用的4字节地址方式,反映到程序里面就是:
" r0 W  M7 z7 a4 G) H; A# R, k; H% A! W+ K& W
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
! t9 J7 Y  ~2 O/ S& }- N, BsCommand.AddressMode = QSPI_ADDRESS_4_LINES;
  x0 f" C" r0 A' {& H. m9 B  ~2 G7 BsCommand.AddressSize = QSPI_ADDRESS_32_BITS;
# U& f) p0 f! L7 S0 _0 qsCommand.DataMode = QSPI_DATA_4_LINES;
" t& T  c  [: `* L: L/ ^+ z1 j% ]- K' c: ?- P0 P9 I
79.4.5 第5步:W25QXX的扇区擦除实现# b' @5 O6 N$ |1 p( _7 w
注:这里以查询方式的API进行说明,DMA方式是一样的。( y; L- n* h, `( E% H0 W
* ^& T# A1 E! V$ C# Y! [: `
通过发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除,擦除的扇区大小是4KB。
$ r+ B8 L4 d  M
5 h0 |; M0 w/ ?8 _
  1. /*3 X0 ?7 z: H5 p; Y% j+ Y
  2. *********************************************************************************************************
    & `4 y+ L  t/ V5 J2 `0 y
  3. *    函 数 名: QSPI_EraseSector
    % t% y: Y% Q$ b% `
  4. *    功能说明: 擦除指定的扇区,扇区大小4KB% h6 }; P$ a: Y
  5. *    形    参: _uiSectorAddr : 扇区地址,以4KB为单位的地址,比如0,4096, 8192等,5 m( Y& h# w. G5 K4 g% {) a
  6. *    返 回 值: 无
    # y8 _3 A' h' E+ K5 {
  7. *********************************************************************************************************$ w$ z) m6 g* Q3 r& H8 O4 ^
  8. */
    # f" N: g! k. c4 C, U3 C/ E- |
  9. void QSPI_EraseSector(uint32_t _uiSectorAddr); I" R; i4 x+ j3 r- L/ V
  10. {
      `5 \1 m& q; G# u( e6 r3 Z
  11.     QSPI_CommandTypeDef sCommand={0};
    ' M/ A) R4 z0 B! ~; a
  12. 0 ?7 o# Y: U2 J1 u3 S+ [+ f+ M
  13.     /* 写使能 */
    % e; Z% j9 ^& V2 Q
  14.     QSPI_WriteEnable(&QSPIHandle);   
    . O3 h# x' p& R# o& |- w
  15. ! t$ v9 B3 N, B3 Q9 l
  16.     /* 基本配置 */  y/ X$ T# `. _: H
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */& R9 _) H8 x. A. s1 ~* }
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    $ `: s* W" N! h1 r; D6 X
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */" A! }# ?9 J8 v7 G; w& y) z, Z
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    : J, O/ b, O& |1 n: Y
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */
    ( m; x; H' C9 r# Q+ _7 Z4 W
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */    ' ^& y3 o- [0 ^8 X0 ?( _
  23. / d/ O1 `, N* _) ]2 }9 G
  24.     /* 擦除配置 */; [. L# a6 b0 L4 C: H
  25.     sCommand.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;   /* 32bit地址方式的扇区擦除命令,扇区大小4KB*/      
    # m3 j+ T; a3 V3 V* C- e8 E: ]/ V
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
    8 D7 S0 {. y  K1 q' K! ]+ t5 b
  27.     sCommand.Address     = _uiSectorAddr;        /* 扇区首地址,保证是4KB整数倍 */    3 t; L4 S1 M' I. E
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  
    3 {( L, P9 B( @* j  C
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  
    $ g* e! M  D8 \  B. }3 W+ N
  30. , Q, m. i( n2 N5 o
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK)
    ) s5 [, W+ E# m
  32.     {
    6 f9 d  R. S, p4 g2 O7 P. s0 d
  33.         Error_Handler(__FILE__, __LINE__);
    0 R) K* L2 z! K7 M. {  a/ e
  34.     }
    ( o" W# B8 D! [- a) _
  35. 0 X* g+ F  V  I) q- }% J
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    - n; Y5 H& S, g7 O, E$ Y
  37. }
复制代码

# f4 z* l" M: l  G  y  r& }) V此时函数使用的指令0x21对应的W25Q256JV手册说明,注意红色方框位置:: o, W4 U6 I* Z: _
: r# j. y% Q' P$ S& c/ d2 }
a09f0f9fb051c5179f3e48824b99e875.png
/ j% a' J  w+ j# r

( v# U: f9 `. z左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反映到程序里面就是:" }' O2 o% {- {/ [
# @1 y0 F: _0 n- A- J7 k
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;& d1 f6 Y6 A' M
; a# h  h% L6 ?# \( i) [4 c+ J
sCommand.AddressMode = QSPI_ADDRESS_1_LINES;  |, l9 `2 z) K9 z6 `

8 y3 l9 ]. w: {0 I3 W& F sCommand.AddressSize = QSPI_ADDRESS_32_BITS;
! A6 T8 ^4 i9 y; }  X0 w( f" o( Q3 W1 y9 T# I, Y0 d' i
sCommand.DataMode = QSPI_DATA_1_LINES;* C1 ?7 }7 B, ~6 t
2 f# a3 Y" j4 s
79.4.6 第6步:W25QXX的整个芯片擦除实现
! a  o0 J% E' \: a2 g8 e! s注:这里以查询方式的API进行说明,DMA方式是一样的。
! E5 o6 _) k0 N4 a& P( O5 m5 n0 p' I1 w
整个芯片的擦除可以通过擦除各个扇区来实现,也可以调用专门的整个芯片擦除指令实现。下面实现方法是发送整个芯片擦除命令实现:
2 L: Z% k% x- C" |" D+ Q# `- W
' t6 @" X; V- X- l( K5 [; ?* u% C
  1. /*& @5 G3 ?! Z+ P. t/ ~2 F5 O
  2. *********************************************************************************************************) Y+ k" r) [9 x7 h1 A7 S; q
  3. *    函 数 名: QSPI_EraseChip- N: z" _% O3 D+ e- Y( D6 K$ E
  4. *    功能说明: 整个芯片擦除
    ; h3 O& H7 s* T% U' b0 X
  5. *    形    参: 无
    . H: o8 z4 O: @/ B/ b) ~+ n1 ~" U
  6. *    返 回 值: 无
    ' M. \0 A) E5 F* v2 \
  7. *********************************************************************************************************; D; t6 d' J1 O/ e: u! N2 O* D
  8. */, V* y0 y" Y, c  l
  9. void QSPI_EraseChip(void)
    0 a2 O( V6 e3 n
  10. {: Y0 q. @4 r9 D$ N$ L4 l
  11.     QSPI_CommandTypeDef sCommand={0};( X% I; v* Y- O6 P  n

  12. 5 \+ _3 z% T8 N4 q  I& d& X
  13.     /* 写使能 */
    1 Q) E% e, }( Z0 Q! X! M- l
  14.     QSPI_WriteEnable(&QSPIHandle);    7 ^2 K9 `( Q* B* O/ G! X
  15. & n# L- O! n. g$ b
  16.     /* 基本配置 */
    / s0 s* _6 Y' d* U3 w7 ~$ h
  17.     sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;    /* 1线方式发送指令 */, z* R. d0 m: I9 G" F
  18.     sCommand.AddressSize       = QSPI_ADDRESS_32_BITS;       /* 32位地址 */
    0 k7 N; u, e: U- C7 D
  19.     sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    1 q2 b) b0 ~& t
  20.     sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;      /* W25Q256JV不支持DDR */
    + A  h$ M& P+ ]
  21.     sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;  /* DDR模式,数据输出延迟 */$ x- w& L1 h! J
  22.     sCommand.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */    ) o4 F9 y( Z# g/ L

  23. 8 K. x& \' r/ w
  24.     /* 擦除配置 */* [6 Q) m/ c+ I4 d. A  P2 M; ~4 l
  25.     sCommand.Instruction = BULK_ERASE_CMD;       /* 整个芯片擦除命令*/      
    / N8 `8 {& O; y. a: @7 s& f, V
  26.     sCommand.AddressMode = QSPI_ADDRESS_1_LINE;  /* 地址发送是1线方式 */      
    ) b9 ]: }2 i3 W: O; ?$ q  }
  27.     sCommand.Address     = 0;                    /* 地址 */    5 s; R$ h3 V" h; t0 I4 n, k
  28.     sCommand.DataMode    = QSPI_DATA_NONE;       /* 无需发送数据 */  & n& }4 g; r9 f6 y) X" V# d" Z
  29.     sCommand.DummyCycles = 0;                    /* 无需空周期 */  " s9 y4 q6 p# w9 h8 |  b: h4 _
  30. ' o+ j; `$ ^2 C0 \
  31.     if (HAL_QSPI_Command(&QSPIHandle, &sCommand, 10000) != HAL_OK): o" i: c$ D8 d  q2 p( Q+ R/ {
  32.     {
    , b0 h8 {  g  h
  33.         Error_Handler(__FILE__, __LINE__);
    3 x( ^9 N; N8 B% s% @* K; w" Q
  34.     }9 b) N% \; m, K7 K4 j3 f* ^

  35. / J2 ]$ P6 }" F4 T) ~8 Q% z( E+ p
  36.     QSPI_AutoPollingMemReady(&QSPIHandle);   
    . G) Q$ a& \. f7 R6 N& ~
  37. }
复制代码

$ s+ A+ {: A7 q- e5 S0 @此时函数使用的指令0xC7对应的W25Q256JV手册说明,注意红色方框位置:5 ^: D7 \& J& |' j& H: R
0 d% c, Q0 o$ o5 S  e2 o
5 K. N, Q; a. o# y& z) {- t
0 h; R9 D- D7 l" |7 R9 y
左上角的1-1-1就是指令阶段使用1个IO,地址阶段使用1个IO,数据阶段也是使用1个IO,并且采用的4字节地址方式,反应到程序里面就是:
; I8 ^, a1 C# V9 R! v" Z5 G9 K4 W. e+ s6 T6 Z
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;
+ Q) a8 Q7 K# i0 Z6 z7 zsCommand.AddressMode = QSPI_ADDRESS_1_LINES;
. h9 Y" V3 h6 K) O# l2 M2 usCommand.AddressSize = QSPI_ADDRESS_32_BITS;
9 [/ }5 q" \" x# w8 CsCommand.DataMode = QSPI_DATA_1_LINES;
& z3 @8 Z$ }3 C, d2 S3 v( c4 F* C8 e  e, h3 Y
擦除用不到数据阶段,sCommand.DataMode  = QSPI_DATA_NONE即可。5 q3 V4 f8 h6 e1 b
7 N+ g% S  ^0 l# H! c
79.4.7 第7步:W25QXX内存映射实现
8 ?/ x+ ]7 U: _" I! z注:这里以查询方式的API进行说明,DMA方式是一样的。: b& D( ]$ f4 D$ _1 r" i
/ l6 g' n) N) {8 l  Z' q  g" e. s  Y
通过内存映射模式,就可以像使用内部Flash一样使用W25QXX,代码实现如下:
; h$ ~; U8 |' }( `' o1 S3 B, M+ ^" |0 |9 q$ m, K9 }8 x, O
  1. /*3 Y9 c0 i% y  u" J3 ~' ]# U  q
  2. *********************************************************************************************************
    6 d2 o! L) R1 F- S9 j+ r
  3. *    函 数 名: QSPI_MemoryMapped, s+ p/ W7 l' ~) @) j! _9 V5 R
  4. *    功能说明: QSPI内存映射,地址 0x90000000
    . s) m- M) P/ r$ t
  5. *    形    参: 无' q& o0 p; Y; m* H8 Z+ D
  6. *    返 回 值: 无+ q; c/ M& y  X' X9 o- w
  7. *********************************************************************************************************- L  l5 t4 z) Z& G- Y
  8. */
    + ~" Y+ K0 y( ~! f7 f6 ?
  9. void QSPI_MemoryMapped(void)
    5 w. L( A9 e" ~# T- |( c4 x$ X
  10. {, y# X) ^& I' P0 z
  11.     QSPI_CommandTypeDef s_command = {0};& ?! Y7 e/ D4 s& H7 T
  12.     QSPI_MemoryMappedTypeDef s_mem_mapped_cfg = {0};$ k) x- v* D4 |* c# P+ D" S; X

  13. ' A5 F6 q: ]9 C6 \. p; W
  14.     /* 基本配置 */+ X" p8 [. h2 E
  15.     s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;      /* 1线方式发送指令 */
    6 G4 n  X, o/ z, o
  16.     s_command.AddressSize = QSPI_ADDRESS_32_BITS;             /* 32位地址 */
    % h4 _; d. H. Z
  17.     s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;  /* 无交替字节 */
    6 t( e! m% e  |, r$ P4 M; p
  18.     s_command.DdrMode = QSPI_DDR_MODE_DISABLE;                /* W25Q256JV不支持DDR */7 Y; o) P# `; a
  19.     s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;   /* DDR模式,数据输出延迟 *// @7 u9 T. v! x5 e
  20.     s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;            /* 每次传输都发指令 */9 D5 `2 u& q0 `; E! \
  21. & u, y7 ~5 p  c0 g+ S/ q
  22.     /* 全部采用4线 */: S4 _2 J( v' n8 d" b
  23.     s_command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD; /* 快速读取命令 */( X: ^4 v0 l/ c2 B4 ]- \# g' r
  24.     s_command.AddressMode = QSPI_ADDRESS_4_LINES;                 /* 4个地址线 */
      D8 Z: M% B  O- u  `* s+ z$ l8 F
  25.     s_command.DataMode = QSPI_DATA_4_LINES;                       /* 4个数据线 */  X+ o* U% B0 J5 u- @$ L
  26.     s_command.DummyCycles = 6;                                    /* 空周期 */
    5 e8 ]+ @2 g: P

  27. ( s7 P0 S% B5 O: D4 L% F1 p
  28.     /* 关闭溢出计数 */
    % U  M; }9 C% W+ ]' m) t
  29.     s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    0 s& ]3 s  k1 z* a- i: n- R; f3 i
  30.     s_mem_mapped_cfg.TimeOutPeriod = 0;: m/ p" _1 \6 t
  31. 5 P/ X  K4 U/ B. v" m
  32.     if (HAL_QSPI_MemoryMapped(&QSPIHandle, &s_command, &s_mem_mapped_cfg) != HAL_OK)
    2 W! H( M% `( _1 l
  33.     {- F3 B* @, G0 ?. o! x
  34.        Error_Handler(__FILE__, __LINE__);
    9 T% X+ {7 k6 H+ E. z/ Y" \& j5 x
  35.     }
      ^; K! i* C8 Y" C0 p
  36. }
复制代码
8 s# A2 V9 l/ B2 ]
此时函数使用的指令0xEC对应的W25Q256JV手册说明,注意红色方框位置:
& G$ u; N4 @+ z' Q
9 X3 q( W  |5 J8 P' }
bb245c5c5434f7ec1a828434a3915d8c.png

7 ~9 S0 W& o' x. q% g( Q( r& z6 J# O4 u$ k6 s" `( H/ `
左上角的1-4-4就是指令阶段使用1个IO,地址阶段使用4个IO,数据阶段也是使用4个IO,采用的4字节地址方式,反应到程序里面就是:1 M! A1 t7 b( c, D
& F9 V2 C! }! `1 D% e
sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;/ X% _/ W+ }+ d3 W$ V8 \
sCommand.AddressMode = QSPI_ADDRESS_4_LINES;
* o( E7 h. \, ?, ~6 f9 }' OsCommand.AddressSize = QSPI_ADDRESS_32_BITS;: K& g3 \6 u( b/ x: [3 L* I
sCommand.DataMode = QSPI_DATA_4_LINES;
/ F+ X: Y1 t# d4 F2 ~
$ g3 [' C' u3 Q, X79.4.8 第8步:使用MDMA方式要注意Cache问题
* o0 V, S; `; h* f如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM,这样可以方便大家做测试,测试通过后,再根据需要开启Cache。
0 h. x, g, [" }. s5 B" G, a$ a; g+ C$ d  }$ j9 @1 @- w. Y) j& |/ N7 I
  1. /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    0 b8 G6 g" C4 \+ s  Q3 X5 d
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;- }. @5 f/ i8 T6 ~
  3. MPU_InitStruct.BaseAddress      = 0x24000000;0 i$ x5 G8 M+ [5 v4 I
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    " `$ n6 M6 v1 n3 M, G
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ) g% b9 b0 `3 ]! Y& x* D) {
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;# j. p! P9 @5 ~: G. ^. \, N
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;  |9 `$ _1 Q1 Q/ `: p  w
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    9 M$ Z0 [: f3 U$ v, j
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER0;& ^2 [! D7 @4 d8 ~7 H+ ?' a
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;3 u$ N1 z, v. S, F( d4 Y5 w% @
  11. MPU_InitStruct.SubRegionDisable = 0x00;  ]  j6 g/ r' g  P+ y+ _
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ) [4 [0 y) M& g3 ]% g& \
  13. # o- a8 B: C8 E5 e3 b1 p
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

$ H* _: L7 N# u( L2 U# a9 k79.5 W25QXX板级支持包(bsp_qspi_w25q256.c)1 p. ?2 x: f+ k* H
W25QXX驱动文件bsp_qspi_w25q256.c主要实现了如下几个API供用户调用:
% F' ?. @' L9 A! r" z/ b) Z
! d4 S! D+ S: f  QSPI_ReadBuffer
1 n; n7 z) R: L# w1 n. i$ _7 s  QSPI_WriteBuffer
/ [5 U0 }* W) i/ W  QSPI_EraseSector: x$ J9 O- ?* _- I: O. F& ]
  QSPI_EraseChip
# h9 C/ [8 U' f  i5 ?  QSPI_MemoryMapped" m; I( N9 i" C( s
79.5.1 函数QSPI_ReadBuffer
: W+ k4 _. T# k+ ]函数原型:
/ v+ t7 P2 h4 N& Q7 ?) j1 N/ c5 v
8 ~/ R2 Y$ X& Q# n1 N( u& w, uvoid QSPI_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize);
. S1 S- s/ Q+ S# e+ e% `/ X; K" L- Z8 Z& Y9 U( w( F
函数描述:
& G, C% O" t" c( ^
; g1 i9 ^2 ]2 I" q' M+ m/ ~4 ~0 y此函数主要用于从QSPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可(如果使用DMA方式,每次最大65536字节)。
& b5 |) J6 x. r/ l
, H- ?6 Y7 M% E! w, ?* e$ y函数参数:
  S" }& b( [7 t" m+ W* J9 S0 ?7 v
$ a! W/ h5 n4 A$ X# N; B7 _8 b  第1个参数用于存储从QSPI Flash读取的数据。
" }2 ~4 k* T! T, r$ |  第2个参数是读取地址,不可以超过芯片容量。7 ?) N. |9 P: u% P
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。8 j! g- [& W' j& [% Y3 n4 y9 d
79.5.2 函数QSPI_WriteBuffer
3 u* p+ H8 E; a. h函数原型:: J8 t: s: d* Y( `# x9 E+ j7 r7 a
$ ^3 p- ~+ K8 w1 H
uint8_t QSPI_WriteBuffer(uint8_t *_pBuf, uint32_t _uiWriteAddr, uint16_t _usWriteSize);
# z1 P# d7 V3 ^  u' \- g: l2 a6 E; y  @; r" T$ h& {/ C6 ^
函数描述:$ U) Q% t: y& h$ [4 B: M9 t

, ?4 f8 Y' E9 R2 F页编程,页大小256字节,任意页都可以写入。注意使用前,务必保证相应页已经做了擦除操作。$ m0 @# k# K) U0 G; Z
, f" ?% D9 w: w* e6 }) P
函数参数:% B6 {3 S$ T8 U. s# u
; f7 H8 Q; p9 ]8 r( d, ?8 T4 W
  第1个参数是源数据缓冲区。: |/ ?/ {( }# v, ^+ b* P
  第2个参数是目标区域首地址,即页首地址,比如0, 256, 512等。
% z6 Y  i+ N' E0 g  第3个参数是数据个数,不能超过页面大小,范围1 – 256,单位字节个数。
7 `3 F+ x/ w/ k/ {+ Q& C6 |  返回值,返回1表示成功,返回0表示失败。
! ?' o' W4 K% |79.5.3 函数QSPI_EraseSector( g$ B+ k# X/ r
函数原型:
0 e  y* x0 [) B, O) {. r: w
6 r  h- r3 q6 x1 x& qvoid QSPI_EraseSector(uint32_t _uiSectorAddr)
7 H& `3 m5 `9 z& O. c* k9 s; d; o
函数描述:
5 t! F0 C2 a* D! k, S
$ Y8 S1 M2 r5 p, A此函数主要用于扇区擦除,一个扇区大小是4KB。
* e( a" u3 d& d6 p/ `4 w
8 M/ V1 N% R% E0 K# s. m2 e函数参数:
. g2 r0 ^6 \0 q3 L4 B& m0 t; |# k! P4 L, q0 D% b9 T$ q: ^4 z
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
: q; l! A% f. J1 i; @79.5.4 函数QSPI_EraseChip1 i- Y. x% J6 E' d
函数原型:3 ?# U9 o. A7 U  w6 g9 Y" q. D

: N  X# o0 y1 y2 }' I# I: I2 kvoid QSPI_EraseChip(void)
" L5 h. Z. \$ U: e9 v) A1 T/ s" I! Y, t& G
; r4 @( b% {2 K' t8 _8 A  C函数描述:6 f/ S% O) \* n( X
% b+ C4 V$ s) g
此函数主要用于整个芯片擦除。
# h8 @; Y7 g, u5 n; N4 M/ L8 B
* T! K0 k" }9 C  x/ y3 B4 E+ ?79.5.5 函数QSPI_MemoryMapped8 o6 ^0 k7 l( ^3 M* |9 l* D. y
函数原型:
" ~: k* ]- l" n  V# W( z6 `8 H# n& S; Q
- p) M9 f6 w  L: H9 [0 m! `( Zvoid QSPI_MemoryMapped(void)! n* ?5 F/ _0 T/ ?% p

3 c6 L) f' y9 V# }. y0 f* v函数描述:
3 c& [% w" A& [! \
* O+ u+ \' A" R3 T" o; M调用了此函数就可以像使用内部Flash一样使用外部Flash。
" @1 i2 V# q1 e/ X. Y5 ~2 e9 I! |* B: _2 _. z4 a9 j
79.6 W25QXX驱动移植和使用
7 w+ v9 p$ }0 B/ ^7 _  p/ SW25QXX移植步骤如下:/ J% a3 R8 i" M( a

* n) Y* g0 H: K# K* P+ K4 C% L  第1步:复制bsp_qspi_w25q256.c, bsp_qspi_w25q256.h到自己的工程目录,并添加到工程里面。
% j6 f9 Y. ~7 q3 H8 f7 X1 B  第2步:根据使用的QSPI引脚,时钟等,修改bsp_qspi_w25q256.c文件开头的宏定义。( R# q6 Q2 U6 c6 V: I
  1. /*
    9 Y) @; Z( e, T2 n$ c3 m4 ^# l4 f
  2.     STM32-V7开发板接线
    7 w  _2 j* q  Q0 }7 y. M6 @% N
  3. 2 x! R/ M. V# j! X
  4.     PG6/QUADSPI_BK1_NCS AF102 s2 n" g5 E: c& p" f9 j: y0 s
  5.     PF10/QUADSPI_CLK    AF9
    ( S5 y* I% m# ?$ R/ {* E
  6.     PF8/QUADSPI_BK1_IO0 AF10
    - G- |! r% m- \( D! ?
  7.     PF9/QUADSPI_BK1_IO1 AF105 S+ j: \7 w! K8 m/ c0 {* h
  8.     PF7/QUADSPI_BK1_IO2 AF98 a/ r0 u1 J1 p( U/ D0 f
  9.     PF6/QUADSPI_BK1_IO3 AF9. `: U+ E, p6 _) n  P

  10. 8 {3 \# }! ]0 V- G* Z5 t5 U; _
  11.     W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB6 K- z6 R! O+ A( T3 u0 z! [+ D
  12. */
    ( ~1 l# {2 C* v; W, A7 A$ |

  13. ( N% }  ^3 ~5 z
  14. /* QSPI引脚和时钟相关配置宏定义 */
    + v9 a2 K. `! D) ~8 r8 M
  15. #define QSPI_CLK_ENABLE()              __HAL_RCC_QSPI_CLK_ENABLE()
    ; e: O5 J4 |! g
  16. #define QSPI_CLK_DISABLE()             __HAL_RCC_QSPI_CLK_DISABLE()
    0 `) ^1 K. g; b" r7 F. j6 C5 s- g( Z  I
  17. #define QSPI_CS_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOG_CLK_ENABLE()2 A2 N  `) W8 V+ Q0 N) K' T8 Z) p
  18. #define QSPI_CLK_GPIO_CLK_ENABLE()     __HAL_RCC_GPIOF_CLK_ENABLE()# w; }8 A5 i& c& A* \; R
  19. #define QSPI_BK1_D0_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()$ u9 c% v$ j0 G5 ]- \. m" e) v* O% U
  20. #define QSPI_BK1_D1_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()3 @, ]# k  \: b* w* |9 S
  21. #define QSPI_BK1_D2_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()
    - u" l) Q9 H) i2 V: ^, g% ]
  22. #define QSPI_BK1_D3_GPIO_CLK_ENABLE()  __HAL_RCC_GPIOF_CLK_ENABLE()* |; X/ q" N1 P
  23. + c1 W( q( y1 r6 c7 D1 U" s
  24. #define QSPI_MDMA_CLK_ENABLE()         __HAL_RCC_MDMA_CLK_ENABLE()8 S6 y9 p9 X9 f
  25. #define QSPI_FORCE_RESET()             __HAL_RCC_QSPI_FORCE_RESET()7 T( S4 A) r1 y
  26. #define QSPI_RELEASE_RESET()           __HAL_RCC_QSPI_RELEASE_RESET()( ^$ Y2 ?  E. ?+ k3 G/ j$ v, `

  27. ( R; x6 L% }- \+ ?3 U; i$ z
  28. #define QSPI_CS_PIN                    GPIO_PIN_6- u) F5 ?* N  F& x+ _
  29. #define QSPI_CS_GPIO_PORT              GPIOG5 @1 ?( V& M; i# T

  30. / z9 b  _, m9 e& P) c* ~
  31. #define QSPI_CLK_PIN                   GPIO_PIN_10  M% I5 G( W  F8 @1 m
  32. #define QSPI_CLK_GPIO_PORT             GPIOF
    7 h8 q! m2 @9 |; z- j4 o

  33. ; D2 p/ m/ H8 w, M
  34. #define QSPI_BK1_D0_PIN                GPIO_PIN_8
    ( b2 v7 h1 t& \% ~, I9 N
  35. #define QSPI_BK1_D0_GPIO_PORT          GPIOF
    ) D$ K; y9 S, J' J! Q7 D

  36. 5 Z* d  o& D$ U4 \7 O: c
  37. #define QSPI_BK1_D1_PIN                GPIO_PIN_94 _2 s  M+ l0 X3 H, d3 l
  38. #define QSPI_BK1_D1_GPIO_PORT          GPIOF; j% I) J5 A0 P" l; g
  39. + T. W' F& h  t' _
  40. #define QSPI_BK1_D2_PIN                GPIO_PIN_7
    ' g6 K% o- j* \6 y* D
  41. #define QSPI_BK1_D2_GPIO_PORT          GPIOF7 e+ d- p; z1 m. a+ Q
  42. 9 {2 Q# n7 `6 b
  43. #define QSPI_BK1_D3_PIN                GPIO_PIN_6- y! j" G% `  L. m2 j
  44. #define QSPI_BK1_D3_GPIO_PORT          GPIOF6 F2 O# _, q( Y  m' [, E9 e6 {! ?# C
  45.   根据使用的QSPI命令不同,容量不同等,修改bsp_qspi_w25q256.h头文件- o! l+ e  \+ L  E! L& Y- ]3 p
  46. /* W25Q256JV基本信息 */
    $ A9 W7 f( P5 ?' S. j/ Z
  47. #define QSPI_FLASH_SIZE     25                      /* Flash大小,2^25 = 32MB*/
    ! F8 v4 t; V, a' i/ s
  48. #define QSPI_SECTOR_SIZE    (4 * 1024)              /* 扇区大小,4KB */8 T- H2 k% r6 D0 E7 y5 D" i, K
  49. #define QSPI_PAGE_SIZE      256                        /* 页大小,256字节 */
    7 K$ N- W9 S0 v% l& D' w* |/ x2 l( @
  50. #define QSPI_END_ADDR        (1 << QSPI_FLASH_SIZE)  /* 末尾地址 */. @7 t" T9 y3 d& o- c) k# z1 C
  51. #define QSPI_FLASH_SIZES    32*1024*1024            /* Flash大小,2^25 = 32MB*/1 ~: F( W! ]2 w$ U. E

  52. 1 Q3 u$ Z5 r1 t
  53. /* W25Q256JV相关命令 */( a* [6 M( v9 e) }2 d2 p) L  b# @+ _! o
  54. #define WRITE_ENABLE_CMD      0x06         /* 写使能指令 */  
    - y2 y- X  {# ^+ C: j
  55. #define READ_ID_CMD2          0x9F         /* 读取ID命令 */  & g2 G8 a6 T9 C! _! q
  56. #define READ_STATUS_REG_CMD   0x05         /* 读取状态命令 */
    & ?& h% p3 k% e6 m' P1 y5 Y
  57. #define BULK_ERASE_CMD        0xC7         /* 整个芯片擦除命令 */
      W2 ?! z3 C7 G; f
  58. #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD      0x21    /* 32bit地址扇区擦除指令, 4KB */
    1 @$ b: k! \: q
  59. #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD    0x34    /* 32bit地址的4线快速写入命令 */
    # ]5 ~6 H6 j( A
  60. #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC    /* 32bit地址的4线快速读取命令 */
复制代码
9 _! @( h0 k) j' @7 j6 C) L' E# y' a
  第4步:如果使用MDMA方式的话,可以使用TCM RAM,此时不用考虑Cache问题。如果使用的是其它RAM空间,要考虑Cache问题。因为MDMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭读Cache和写Cache,比如使用的AXI SRAM:
  t- o0 J& h3 ]+ j# h/* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
$ P$ b7 L, y: @8 [MPU_InitStruct.Enable           = MPU_REGION_ENABLE;' ^/ m# M; P- b4 p
MPU_InitStruct.BaseAddress      = 0x24000000;  E% F+ j1 n- E  q7 ]/ N
MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;# _8 F  f  z$ K# Y3 c) O
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
! Z5 k; H9 Z9 ]2 n, nMPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;, p. L1 Q0 J1 N* u, ]
MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
+ N9 ~; W! A8 p) ]3 |" QMPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
0 a% b" R) D, W0 H/ }: p* v% NMPU_InitStruct.Number           = MPU_REGION_NUMBER0;
6 k7 p, N1 Y4 N8 N1 d9 _MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
. a7 r3 X) N8 q+ M8 ~MPU_InitStruct.SubRegionDisable = 0x00;
, |  U; ?& i: ^) [5 W/ H% m% u, t1 aMPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
! w& P% v" T4 @: j8 ]1 G& k
2 R/ U  x9 e1 S/ I8 L! M7 rHAL_MPU_ConfigRegion(&MPU_InitStruct);
4 p/ O. x! u6 z0 i  ^1 U. k  第5步:初始化QSPI。+ {+ a# Z' c* @# a8 W/ _
/* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
& J4 m& ^1 z6 u4 Ebsp_InitQSPI_W25Q256();  /* 配置SPI总线 */) H  a. c: {. Q; g# O& x* @( E
  第6步:QSPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。; S3 n$ {( [9 o2 [- W' R4 W
  第7步:应用方法看本章节配套例子即可。& H. T! M# l8 X1 D1 Y" G/ {9 [
79.7 实验例程设计框架
6 S% w9 R- m. A* _通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
6 d4 R8 {9 D( @; U$ H9 P( Q, a+ B: J+ y7 h5 D
32c08174cec9edf62fb6e6103da5cea5.png
! H# h8 p# {+ R3 l$ c0 J# D
( }3 s6 h) x4 c! @
  第1阶段,上电启动阶段:7 W; o- {2 b0 x7 a4 _
- n! n7 h" ?- h2 i- Q5 I
这部分在第14章进行了详细说明。  z3 z& i# F) u. @
  第2阶段,进入main函数:' q( S9 E1 `0 r
6 `" ?5 |8 ~& A7 @# c% Y/ \
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
1 }! q. y/ ~7 t% Q+ S% T/ O 第2部分,应用程序设计部分,实现QSPI Flash的查询和MDMA方式操作。
/ f3 w- r! X: I4 f7 E0 a) B79.8 实验例程说明(MDK)6 i* [9 \* g  n& W  J
配套例子:
, c. u9 ^2 q9 h- J( J4 J! d9 T* ^- M! H0 D5 n) J8 g9 n
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)
: m$ l) x0 i7 Z8 }1 }1 P1 I: H1 f2 M6 M: Q% j% X
V7-059_QSPI读写例程,查询方式
; W" g6 p) C5 u7 z9 P9 K+ x/ Y
! x7 t7 [' [" X, @4 p* U% _4 J实验目的:' T4 t( x) q4 `
/ S! X, g- a! V" B, D' X- R3 J
学习QSPI Flash的读写测试例程; D8 ]9 `* Q( U9 ]& B
实验操作:
5 |8 [% x- w) e' e
8 w" a: C9 T2 W& a2 g# t支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可/ Q8 m7 k# a, X! Q$ O) N+ c; n
printf("请选择操作命令:\r\n");
; k6 |! V+ c3 k$ mprintf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);; j& R& B' r  }" [6 m
printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
' `  c- O- G% n4 W( D: t3 Lprintf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
  e; _2 D  [- B) @* `: Rprintf("【4 - 读整个串行Flash, 测试读速度】\r\n");  P. k. h+ L0 L$ Y  c( V  ]
printf("【Z - 读取前1K,地址自动减少】\r\n");0 ^5 D8 R9 M2 s) P& ^1 ]
printf("【X - 读取后1K,地址自动增加】\r\n");
- w/ o" b) _! p: h* r& O& o8 Gprintf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
! Y, O/ m1 ^* u+ T2 ^printf("其他任意键 - 显示命令提示\r\n");7 ~) A' W0 u1 ?' R) y6 N
上电后串口打印的信息:
" j6 Y( M! B' m, o; P. T
" E  x& _& y4 @6 W" i1 v  c2 |波特率 115200,数据位 8,奇偶校验位无,停止位 1。2 P6 m( y- \% a2 l$ ?

5 |% j$ ?( O' ^% {; J$ h( t: F  x
71e953366d6cc128ca9c0ddeef6d23ca.png
/ d2 k+ e. t; i0 v1 r$ q, U
* q. `* R3 g  n8 g9 B6 W% Q
程序设计:
! v5 m2 b, Z$ S" G2 r
( Z4 l* f5 N$ |) o6 b: C; J) H& H8 U  系统栈大小分配:
5 X. a, ~' a/ U0 w. {8 |( E! g& y5 n; H9 V7 h
7011bff1036a0ffa03f21e1e5894ea0e.png

! i( O& O! w8 e: s9 [" E# [+ m$ v3 H7 k4 k
  RAM空间用的DTCM:4 \( y8 A2 e4 ~& u8 C

1 H" @- f! H( x" Q
6c91b4eae6d726676a3e5990d73f448d.png

. x. C" n5 n+ \' \) G7 O4 H( X: b. A0 N/ B$ V
  硬件外设初始化7 j/ Q/ i6 _9 v) f; K- W! F% R8 R: u

, J  x! @( ^. @/ R6 \4 y硬件外设的初始化是在 bsp.c 文件实现:2 R8 G- }$ R! }/ D3 j% j; `" e" T
" y1 a% ?! }8 ?/ N" t2 Q; ~" i- I
  1. /*. [1 m' |- O6 B2 F  ?; R! _
  2. *********************************************************************************************************. E& q" D( J3 t) x1 @2 I
  3. *    函 数 名: bsp_Init
    * W- t4 R! I/ K6 h  a) z9 Z; M$ u
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
      G7 g( o3 g9 W. d# L
  5. *    形    参:无1 H) t4 h; ~+ s6 T* M- w1 n
  6. *    返 回 值: 无- d, [# X" j, y+ Z) q
  7. ********************************************************************************************************** {7 l' ^. r; i/ R0 m2 n% C! r
  8. */% l# W# g" Q7 W2 P
  9. void bsp_Init(void)4 [& i/ B5 j' q
  10. {
    , a( d/ ]( c0 l* j' o( Q  _) B
  11.     /* 配置MPU */
    0 b: n: x% e( P
  12.     MPU_Config();
    2 ]" X: o+ m. K4 B
  13. ! L+ W: W% t& J( s$ \* w' v
  14.     /* 使能L1 Cache */
    - B1 S8 l: m& }; k
  15.     CPU_CACHE_Enable();& W$ D: ~* F" G0 f
  16. # Z9 L% k0 Q. V7 |1 J& ~
  17.     /*
    / N8 h  d. `  [9 s  w2 K1 N8 k
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:  @; a$ D) z- {* E! g. a, s6 {+ V
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    7 L8 P' O' ?' J0 M
  20.        - 设置NVIV优先级分组为4。
    6 `9 y$ w$ z8 K, x: j
  21.      */
    6 T) H3 p% ?- O; z$ Z9 B; |1 R
  22.     HAL_Init();( a2 x- c* d* p
  23. + _  A' n( w5 x! k) Z* Z
  24.     /*
    3 z1 U4 X* x% V# O5 {" B
  25.        配置系统时钟到400MHz6 z! Z# W' s* j: g" \
  26.        - 切换使用HSE。
    ! k& M4 ~) I/ Y) r2 }
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。$ D" W1 u9 @  a) m) z
  28.     */
    5 n5 [% u! P. g" Z# e  v/ c
  29.     SystemClock_Config();/ R* |8 z9 R' @! q$ Q0 F' c

  30. + J6 g4 u" s7 S  B4 W; M
  31.     /* 4 N' m7 x3 a! M  f+ {3 h
  32.        Event Recorder:) k9 p" p4 n' ~# @: }
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    ( V8 Y2 U$ x. j3 C
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    ' _5 Z9 L/ H' z
  35.     */   
    / W2 ]' B; W) s! @
  36. #if Enable_EventRecorder == 1  / q0 j& _- f2 q6 J! k9 ]
  37.     /* 初始化EventRecorder并开启 */1 W' Z- g% h$ G: R6 s
  38.     EventRecorderInitialize(EventRecordAll, 1U);7 c& D( ]/ ]  h6 x: w' [
  39.     EventRecorderStart();
    0 Y, F3 y3 p) _. ^2 _& r: O* w
  40. #endif
    * J7 E( I. N, A, A& ^: L2 \
  41.   k( Q1 g- Y8 @' B: J3 ?% Y
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       " n! h2 H$ x' |, T# x: M& g
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */1 v2 b! ]1 j- f8 u7 `: E
  44.     bsp_InitTimer();       /* 初始化滴答定时器 *// v+ O& c* v$ P9 U: e0 R9 f
  45.     bsp_InitLPUart();     /* 初始化串口 */5 D% d% Y- d( h" N* C$ C% g
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    * C# U7 P; ^( ?5 Z5 F& h* F- n
  47.     bsp_InitLed();         /* 初始化LED */   
    5 b/ c# U$ @4 b% n6 D/ z
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */+ z- A; W4 [4 n, v) A: I
  49. 8 z7 D! y6 e3 T4 B4 v
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    6 M* C  o* [9 C" y
  51.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码
+ R, Q: @# l) t8 I
  MPU配置和Cache配置:3 w  r# `, Q4 E
3 @1 N8 k1 f8 ]3 v) G2 P
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
. q7 i- _4 L5 ]: U+ k7 D4 ]' `6 M! T7 x  L- f
  1. /*8 C* s1 f; P4 `2 }; R
  2. *********************************************************************************************************% Z1 x4 i7 b. C- ^
  3. *    函 数 名: MPU_Config
      o5 w5 q  w. k. ]
  4. *    功能说明: 配置MPU
    5 [3 V) S7 a* N# r
  5. *    形    参: 无
    $ ~: l" }+ _2 q3 ^
  6. *    返 回 值: 无( z, G/ e4 X$ S" S5 u6 W* t
  7. *********************************************************************************************************
    6 w' d* M7 H8 n; k
  8. */
    2 ?& W# ]: f6 M7 O* }
  9. static void MPU_Config( void )
    - Y2 P  O, T' C6 P$ J
  10. {
    8 B/ b9 L9 [& N4 g
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    3 D  N/ }0 D7 W. L4 ^0 T/ K

  12. & w* c: D+ h2 c/ E/ \% ~
  13.     /* 禁止 MPU */, X7 Z6 i* A# [1 g0 ~0 @# x" `
  14.     HAL_MPU_Disable();
    0 E" m& M: O# n7 L

  15. + {$ c( E8 z- T7 F) N9 \# j
  16. #if 0
    2 n* [3 ]# g0 m9 r& y' G" w
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    1 B$ A9 M7 h0 d  |) K( e
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;* }, M) Q$ A! r! D
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;4 C$ g6 k" W0 |- k
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;- P/ i0 T" }* E
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    5 T& _7 \2 z8 v4 L) }8 Q2 n
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    , e* }; W9 c/ e- c$ A1 |, ]: Z
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;7 K! ]& z$ ]5 j4 O
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    5 n+ N) q; {7 S' F/ |9 p/ I3 {
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;; M8 j9 W, o1 ~  y
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;+ h! X. `. ]- f, @, o
  27.     MPU_InitStruct.SubRegionDisable = 0x00;
    / j% @7 q0 M* J( o. p( J5 t
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;6 i( V( V. K4 o3 f# z) r6 M" e
  29. " ^) R  O( e8 Y, J" X; {, |
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);. Y; i0 x4 C* R% g
  31. : i, D3 g& ^( k) E" m
  32. #else
    ; `" V3 I) Z2 q' d0 X3 q
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */
    / K. `9 m- L* z8 W% i2 @
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ( n) N9 C5 U) U# J2 x; i  Z: L
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;+ q' v5 \! u, G9 Z+ E
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
      M# A& C' A& B5 L5 x5 v
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    , {1 y$ O3 e4 Z9 T/ n* t* s
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;. z+ x) |1 R; P7 a/ I0 H* l
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ) t1 o% t& r0 x. j( U$ N
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 o$ j' [3 u$ V. E( s* B2 p4 r! X
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;5 s7 m/ \3 s5 ~/ M& r- I9 e; n" s
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;' c/ T* \" F+ _7 H! x
  43.     MPU_InitStruct.SubRegionDisable = 0x00;
    . p4 q5 X8 s* C, @; p* ]& n
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;7 N1 |3 J! M, H8 G; X
  45. , V. U6 u$ Z- ?
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);3 O1 F7 T0 t: B" Y
  47. #endif    + y7 H) |6 Y. E

  48. 8 K8 {, O( h" y2 f1 S
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    8 D) G8 f8 R" N: l- O  y
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;$ F5 V- t8 \2 p% h& d8 I
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;
    ! J1 E( }( Q+ J0 j! y
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    + ]  @5 u& ~& u& f6 l9 ?) L0 o2 j3 m
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;5 M  L2 F# J/ F
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    : `" I, F. ~; \" F3 ^
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    ( O9 P& e- F- X/ [& r. R
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 v5 Q! f9 R. \
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    ! C4 `" P  l8 u+ h9 C
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;0 q* w. h$ u  {; W; f
  59.     MPU_InitStruct.SubRegionDisable = 0x00;+ ?4 c, V# Z  |* I3 `- x
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    2 A4 p7 ~! j7 ~, u% \& O

  61.   P3 B# v* D5 u, I
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    + Q$ [% g. g1 W& x2 O

  63.   ~7 c. @# p! I2 ~
  64.     /*使能 MPU */' C6 T* [  l& o
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);% J& c5 o* Q* q1 p" L8 A
  66. }2 i" |) n7 h3 v! D; f9 y
  67. 1 h1 K9 B  @0 t8 V
  68. /*
    & t4 Y. w2 u4 s* J4 T! K
  69. *********************************************************************************************************- b2 y' J/ d, J+ H
  70. *    函 数 名: CPU_CACHE_Enable! r8 X7 Z2 @, D- o! ?: o8 _
  71. *    功能说明: 使能L1 Cache
    . G4 b8 l+ N# c
  72. *    形    参: 无/ _6 H, K1 v9 s% _1 z
  73. *    返 回 值: 无
    , Q8 g9 Y- {$ [6 t6 B
  74. *********************************************************************************************************8 H" k8 F9 z* _
  75. */( U4 f" a; E3 v. s, t: ^
  76. static void CPU_CACHE_Enable(void)
    0 q) _! c. W7 d) I, D
  77. {
    : [: S! ], Y: Y
  78.     /* 使能 I-Cache */
    - J( g$ T6 Y; O: S1 x# S8 E. h
  79.     SCB_EnableICache();
    * P, f' c; W. Y$ o: m( O- y
  80. # A' X) I1 {5 f8 M/ r/ O
  81.     /* 使能 D-Cache */8 _* W$ u$ ]: e7 H, g# R6 T
  82.     SCB_EnableDCache();
    2 z& t+ {8 w% w' L: S
  83. }
复制代码
. o8 r4 m7 R6 Q
  每10ms调用一次按键处理:
& o2 ]! f7 G) [% I: b/ J6 {0 R) w, }
) N  D7 f0 Z" v9 }6 m按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。8 R, ?) G1 J  R: I: [" f& \
, u% f6 k5 ]/ E2 v
  1. /*
    4 `) l. K- ]  s7 {! h  F/ L
  2. *********************************************************************************************************5 r3 y) Z/ }7 F) O
  3. *    函 数 名: bsp_RunPer10ms
    + X" T" B6 D* g  }
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    6 J7 ?$ B* ~: O  B! E
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。8 [. `+ u, ?( c5 m- y- O3 A0 g2 `
  6. *    形    参: 无
      ]6 N/ {4 l* D; Y4 H& G
  7. *    返 回 值: 无
    ! x1 L1 W1 `6 T! w1 q
  8. *********************************************************************************************************6 ]5 j" @$ ~. [5 }" v2 A& S
  9. */" w# A3 s! X& r' f  {+ r( T
  10. void bsp_RunPer10ms(void)
    - Y  U9 h+ H" }) C1 J- b/ Z: v1 ^" S% A
  11. {* p' S, l6 X$ q
  12.     bsp_KeyScan10ms();
    0 f% I+ m. L$ p$ y
  13. }
复制代码

& a. a; p. S; e  主功能:
* b% K) G5 M+ V: y
# w3 m; V$ [5 G( ?( @! G% n! h8 y1 J主程序实现如下操作:/ Z9 C9 k# Z/ S5 _+ _
; J. V) q  E8 D2 R6 m
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
6 z5 B3 t6 U4 e  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
( o2 C& [- j& E  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);, x2 l9 k, Q8 j9 H- E
  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
. _1 W0 u$ f+ m  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");" t+ a0 `6 J- L& j
  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");
$ d" ?* ?8 T8 H0 g+ Z  printf("【Z - 读取前1K,地址自动减少】\r\n");1 ~; K% C9 n6 \& B' d. V, G
  printf("【X - 读取后1K,地址自动增加】\r\n");: H# J% B0 A9 _. d
  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");' S9 Z9 U* H4 Z# |! ^# W/ ]
  printf("其他任意键 - 显示命令提示\r\n");0 P! I! D% ]* U
  1. /*) L4 r  [1 P: w* q* S* r0 L" }" m
  2. *********************************************************************************************************5 t* J6 ?  A3 H' r; i
  3. *    函 数 名: DemoSpiFlash# e+ p' a) Q; ?
  4. *    功能说明: QSPI读写例程! U/ E; D0 e3 a2 J! ^( S% l
  5. *    形    参:无
    4 i( s' b5 U. H
  6. *    返 回 值: 无/ }' b1 g9 S& G9 [& }( a
  7. *********************************************************************************************************, L: Q8 r- W, U0 j: t
  8. */0 O9 _1 M# v7 O; _
  9. void DemoSpiFlash(void)$ Q/ l- y" V  r# e
  10. {, b+ r6 Y: O* s  K7 I$ [: h! J0 u
  11.     uint8_t cmd;0 h* i; s- U7 N* X
  12.     uint32_t uiReadPageNo = 0, id;2 ]! D; ]& b8 j' r$ S8 m

  13. - k% ?% O( D6 z, y" j" Y
  14.     /* 检测串行Flash OK */
    5 i+ p3 o  A) K; r
  15.     id = QSPI_ReadID();
    + \7 K% U1 j* j: Q. e) e8 D
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);6 K0 G! z8 ~  i- M( i: m
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");
    4 W# S3 l; c8 y' S3 J: H$ ~0 _

  18. ( y" Y. l. T; ~0 h
  19.     sfDispMenu();        /* 打印命令提示 */) p2 z: N7 `& Q$ y: s
  20. ! x$ z2 T  r. @1 \
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */" l3 q# O5 i! H8 e  J
  22.     while(1)
    * `% c0 j! @) v" g+ P+ F( Q# j' J
  23.     {
    % m+ k" e3 }2 E; _3 ^
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    7 K6 W3 Q( z5 ?7 M3 _4 Z- ]0 s
  25.   g3 s  D8 @8 ~' i  K, S! h
  26.         /* 判断软件定时器0是否超时 */5 T+ T: B% t+ c8 j% O
  27.         if(bsp_CheckTimer(0))7 T4 L1 `. S$ _& b. O7 i5 Q0 O6 ]
  28.         {
    ; v* P% B8 ^* W$ c
  29.             /* 每隔200ms 进来一次 */  6 D9 B* d3 k% }3 r% U; f
  30.             bsp_LedToggle(2);3 ^  N. i" x5 z& j  M
  31.         }) v+ _1 R% s2 S$ z' F' q# u
  32. 0 b7 _. N1 o+ Z7 y# w9 z$ j* h6 O: G
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */. i6 i! H( p2 Q3 [5 J
  34.         {7 {% R% Q  O0 \" `$ l; W
  35.             switch (cmd)4 L; E' W, }1 Z4 f1 @
  36.             {: c$ ?, K* i9 M9 N0 M9 T: d7 ~
  37.                 case '1':
    8 E! ~2 c) e  j! o
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);3 m9 C) O( l7 K% I
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */) S- g0 Y9 y! }
  40.                     break;5 F8 P- q; `7 H
  41. 5 x  B! y8 p$ U
  42.                 case '2':
    9 z7 U7 _# j3 p, ~( l, J
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    / @* ^+ }9 a: O$ |
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    9 t( ]4 ?# m/ p4 m  j+ B$ n
  45.                     break;
    ) q. Y! R' B6 L" p
  46. 2 w; g0 Y1 D. J
  47.                 case '3':
    ' T/ i8 y0 L) ^3 F. H9 y/ S$ i+ j
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");7 t4 K; p  f1 {% `
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */. S9 j% Q5 ^  @$ f( P
  50.                     break;& D4 i* x. f* d& u
  51. ( Q2 v3 ?& o( D+ V
  52.                 case '4':8 a7 D7 [, N% O$ \
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));4 ~* R# X, l/ d7 x* v! v8 A
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- a$ O, P5 @4 F" M) h- X) w
  55.                     break;
    " x" f& T' G/ m7 N7 ~1 Y

  56. 1 {9 q0 o9 h. J: P. y% ^0 n
  57.                 case 'y':
    ! c) d& W" \: G: h
  58.                 case 'Y':: g' K0 P; \" o: X+ T( \3 C9 @
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");
    % o/ l7 z0 G# [  j$ D& q) [
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");. _7 H7 @9 }% N+ G4 t' m
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */+ c* s& v8 k! U0 w% F( E, P+ L
  62.                     break;+ u. Y7 f1 d, q& ~1 p4 [
  63.   ?, |$ C1 X1 b- \" r- n
  64.                 case 'z':) J8 s* ~$ |. r# j% w! K$ n1 ?* Y
  65.                 case 'Z': /* 读取前1K */: m% U' J' K1 U* p
  66.                     if (uiReadPageNo > 0)
    % E" x8 p! w+ M$ j/ s
  67.                     {
    0 `: E3 Z4 k  Q/ a# H7 @
  68.                         uiReadPageNo--;0 m9 w# A4 @% \! u
  69.                     }# p9 R$ }) u1 G; h- [
  70.                     else, K  K) N5 \- `
  71.                     {
    6 V# s; h5 x+ y' l) o+ L3 z
  72.                         printf("已经是最前\r\n");5 Y" \- v, W1 W0 s) w
  73.                     }. G2 `6 Q% p3 R3 Z9 X) h) }! K( \
  74.                     sfViewData(uiReadPageNo * 1024);
    0 o' I7 s2 C0 {! G% H! p
  75.                     break;2 e) Z: p5 j' P& |: q# ~' W
  76. 0 L/ F. X. E  ^9 h
  77.                 case 'x':
    . H+ g& B  F8 F. O
  78.                 case 'X': /* 读取后1K *// D; m/ H" S, g: p
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)! L2 d* _$ z6 u9 _/ N( r; F
  80.                     {9 ]& d( E0 U5 c3 K  L, Q
  81.                         uiReadPageNo++;
    5 o- C- {) y, T; ?
  82.                     }
    " K; @3 ]; G) B- f' J
  83.                     else. U; q7 c: B2 @! s
  84.                     {5 Z7 j7 ~; V# w0 B- @: B1 T
  85.                         printf("已经是最后\r\n");
    * x0 |  [: G5 ~: q/ J
  86.                     }
    , f" \8 F% q( c7 o, Z
  87.                     sfViewData(uiReadPageNo * 1024);: U) N; s4 \; Y" A" U" d9 c; r
  88.                     break;( {& t3 s9 ]3 Q( Z" T' A3 L5 V

  89. & w5 @( T6 y3 x$ S' @
  90.                 default:  G& r, q) V; y7 Z5 T4 D
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */6 `% _$ I- T  P! [) G/ Y! B: C
  92.                     break;
    1 @$ Z9 J1 r% \
  93.             }
    # _# z) Y9 W  N, Z
  94.         }
    ! T5 a0 O* q  H9 J
  95.     }
    ; V+ P4 U; ], J% U* X9 \2 c  i; |
  96. }
复制代码

, Q4 B/ G6 R0 o& x2 }1 T79.9 实验例程说明(IAR)
8 q( r- x  c( }配套例子:
, c4 S- Z* z) w6 `" c) L; Z9 h. `7 Y
V7-029_QSPI读写例程,四线DMA方式,读每秒48MB(V1.1)
3 E" H* P) Z2 i8 ]& n7 ~, x" k- H4 p
; q# _  D, c9 H4 \V7-059_QSPI读写例程,查询方式
  p3 f: o/ w' V4 D. Z
# Z# s0 w/ G1 v实验目的:; I( N9 a2 Y! {$ M! `
: u* B, V5 {8 R/ C( n
学习QSPI Flash的读写测试例程' a$ p' C" S8 @1 k* p* Z4 g
实验操作:9 [2 b  @. B8 v# x% _
0 i2 }; U) |' E1 }
支持以下7个功能,用户通过电脑端串口软件发送数字1-6给开发板即可+ T3 C4 c( J0 R7 `  B( ~* D
printf("请选择操作命令:\r\n");
6 v) B: k  t9 d5 A+ i3 Iprintf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
2 W0 [7 ]; g* dprintf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
3 A3 ^# `6 Q) r- o: {4 nprintf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");" ^0 @) o* k) M' G
printf("【4 - 读整个串行Flash, 测试读速度】\r\n");0 o% p! I8 S3 @- h
printf("【Z - 读取前1K,地址自动减少】\r\n");
7 q6 ?  h% A* Uprintf("【X - 读取后1K,地址自动增加】\r\n");. I% |. G" a% t5 M
printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
; ]$ X3 m$ n/ w, `3 g+ F# Tprintf("其他任意键 - 显示命令提示\r\n");! v7 ?+ O7 P& z; a1 C
上电后串口打印的信息:
" Y5 j- V. L$ ?- x( |
  d+ p: n/ T4 Y5 b- _1 u+ F' R波特率 115200,数据位 8,奇偶校验位无,停止位 1。
9 k. o) X2 o, ]9 f9 ~% K0 j# \/ j0 R" I% w' E6 i; ~( r
3ff8536496d184105fc92ca093930e2b.png

& P6 \* F7 }5 Q) {+ p
9 z+ \' e5 F" l+ {程序设计:0 h9 U7 d1 q, R' ?; C. j

: [9 }) ]! n9 G5 h4 ]3 ?  系统栈大小分配:
3 U6 F1 T% K, }1 {) S8 @/ K
( l4 ?; ^5 Q& J
29c63afe41336006cacb94f46fe77b35.png
* y. v' b& f1 `; E# m) w

4 @8 ~" r% i3 x  RAM空间用的DTCM:: t' `3 n$ p: J1 ^9 |+ P  Z
4 d# i4 J! d, a9 v: C9 p. D7 [
4dda996917dfda9ff3ebfb7ab53d3531.png

6 ?9 i5 Z7 k- @/ l, d: f
9 w2 P5 D% P' v" K  硬件外设初始化
9 h9 F" L5 S' _- k- A
4 N  p9 @8 s2 n! R+ g) b+ `硬件外设的初始化是在 bsp.c 文件实现:
& L: n5 @9 R, b# Q0 f+ V) L  ]+ T* Q* [1 {& f) T6 u
  1. /*# d6 t7 X. o; e# E: s6 k7 X* E# ~
  2. *********************************************************************************************************
    8 G' l" L; B" [  |% `. X/ D/ r
  3. *    函 数 名: bsp_Init
    ! [9 S  ~8 a+ u1 p
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次4 U/ X5 N# m: }/ @
  5. *    形    参:无
    + v/ p! z! v- Z( q4 h2 t/ r+ P
  6. *    返 回 值: 无6 S# i/ X* Y/ y- J. e0 j! S; i
  7. *********************************************************************************************************
    1 a* z  W- _& [, r7 }
  8. */) Z6 F  p# S! {/ H& I& `
  9. void bsp_Init(void)
    1 Z. l: T% w3 W/ e
  10. {
    3 F) F: i2 F; R8 N
  11.     /* 配置MPU */
    / {' O6 u1 J, ^( ^. i
  12.     MPU_Config();" r. f4 J' W' F4 o/ q
  13. % O, r& S. [" M+ x2 v* b  L
  14.     /* 使能L1 Cache */, Q6 M4 T& U& b% P, T; r/ q( p
  15.     CPU_CACHE_Enable();
    # g( ^$ r* m6 w! ~3 d9 r  {6 V* w: a
  16. , V* p; c9 i7 G! J& n/ X3 a
  17.     /*
    . }; A6 i* Y( o# I4 {2 L. E* n
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:1 g: ~: U+ [: t& j$ O
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    3 _8 L+ X3 j2 @& r  e, j1 h. x
  20.        - 设置NVIV优先级分组为4。/ ^8 x  F5 y6 s
  21.      */- U# H% d2 l7 v
  22.     HAL_Init();
    1 ?9 \: W4 G4 ~3 q2 j
  23. 9 D) B% f, ~$ Q4 H8 v" Y! I3 x& c
  24.     /*
    ! _; w$ \0 l: s: n' Z6 w5 q
  25.        配置系统时钟到400MHz
    # S7 X" k& D* ]$ l
  26.        - 切换使用HSE。  D' |) o( _8 y& C; A. ?" K
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。0 c3 O/ [5 |/ h
  28.     */
    6 W4 b* l% X: P" [
  29.     SystemClock_Config();
    $ C; a. u& e5 i8 A; {

  30. 3 q+ @6 G1 ?( B( z4 u4 n! o. z
  31.     /*
    . `% {+ }$ ]* q% y& S, m. R
  32.        Event Recorder:
    ) G( ~) ]' t" z! I9 Q0 ~3 M
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。% Y& ^% g& X. x: \  Z
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章9 p8 S. N! P0 A! p' `5 r
  35.     */   
    & N& Q) {- }3 T
  36. #if Enable_EventRecorder == 1  
    7 @+ G" B; \+ Y4 h9 F  `
  37.     /* 初始化EventRecorder并开启 */
      U" ?# e2 C2 Z4 ~7 n- R
  38.     EventRecorderInitialize(EventRecordAll, 1U);4 r" b9 U" t# P9 F" h1 n0 \
  39.     EventRecorderStart();
    2 r: _) `9 Z, W& o$ @+ T4 q% G2 n
  40. #endif
    ; d& w$ b& c# f0 j: f* J9 I; }$ l
  41. 7 @3 B0 k! A. |3 M1 ^
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       4 M' x% v! J4 Y. v5 |; Z
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    0 ?0 ^/ v; q  J
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    1 B- Z3 C7 Z  j; P; l
  45.     bsp_InitLPUart();     /* 初始化串口 */
    ! \. l# n5 r# K& z7 k0 [
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    . b) i% V5 W/ O$ b! @4 F$ }$ j
  47.     bsp_InitLed();         /* 初始化LED */   
    * E5 o2 R3 M: Q1 @# x
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */. `& G: b4 d8 g$ P+ r" H

  49. ; {6 n) v* J. P4 H1 s& b7 v' a, q
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    : ?& {7 M, F7 B/ a9 S9 i
  51.     bsp_InitQSPI_W25Q256();  /* 配置SPI总线 */}
复制代码
2 ?* V/ u! u8 c+ q
  MPU配置和Cache配置:
6 k; Q, k, _2 \7 Z) }5 ^  \9 C8 U2 ^9 X& ?4 F$ q
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
4 |9 B. s* _9 ?' Q/ X- U& }) N: E( u3 g+ \- l" G" y6 k, ^; g
  1. /*
    4 Q6 c6 L7 [- t+ A8 h
  2. *********************************************************************************************************# ?1 Q6 d; j' E8 K+ a
  3. *    函 数 名: MPU_Config
    7 F, f; c$ p' H2 I2 \& y5 b$ ~
  4. *    功能说明: 配置MPU
    2 d" ^4 M- t6 n7 Z& R" A# H" g
  5. *    形    参: 无
    % Q  }+ D# i1 h. j- L; U
  6. *    返 回 值: 无
    ! O4 y2 h! B8 X5 I% d+ S
  7. *********************************************************************************************************) E, Z* _5 I9 F
  8. */
    1 J0 b3 f) n: z' a4 X
  9. static void MPU_Config( void )
    & S( L% A# h) N* E) v0 H9 `
  10. {
    $ }* }# \# Z, ^9 C& m2 b0 \
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    ' A/ E- U" X/ K9 w+ P! @; H

  12. ' C% t7 b7 ]9 h; S4 H6 |: N
  13.     /* 禁止 MPU */
    0 r$ ?) c1 N! O, p3 V9 S8 Y
  14.     HAL_MPU_Disable();7 Q& C1 z% d7 F+ s% V4 f; e; o  J# I
  15. ! B2 R+ D# q; \, S) {1 A
  16. #if 0: p+ }( m+ f# \& w, S/ ^1 E
  17.        /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */- p9 h" e3 y/ o/ K9 F) E$ B
  18.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    $ G2 ]( p5 j" x( G% F
  19.     MPU_InitStruct.BaseAddress      = 0x24000000;
    ! r8 @" `' I0 V' d
  20.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    * b. C2 w5 ]" Q/ }: o1 h
  21.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;$ C: ?4 S. K. B4 L+ ^' e
  22.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    & K7 f7 r" m6 n; _2 l" ]
  23.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;  l+ _( r* ~$ U3 u9 ^
  24.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;' [2 @2 L9 d+ e
  25.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    $ ~. c( Y3 e/ T% }
  26.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    / k! J2 J, o( s1 G: V0 z+ J6 ^
  27.     MPU_InitStruct.SubRegionDisable = 0x00;4 n1 c0 r- ?1 S/ X6 \9 X4 W
  28.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;+ l7 |6 n9 M' B. g

  29. ' n8 C( U  o* M% A" @$ T
  30.     HAL_MPU_ConfigRegion(&MPU_InitStruct);! m: @) B" |$ ?! a9 D! {
  31. 2 L* @2 O4 n  D! [( s( z6 Q1 m$ X
  32. #else
    3 o! x) S: i7 a7 [
  33.     /* 配置AXI SRAM的MPU属性为NORMAL, NO Read allocate,NO Write allocate */! e" k$ B8 b4 H+ _5 Y% |
  34.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    0 B. ^, ?6 D. ?; P, ~; g+ o) d5 A
  35.     MPU_InitStruct.BaseAddress      = 0x24000000;
    5 s/ x% [$ \' `: e
  36.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    : ?* Y) l+ T6 N: E9 d: N
  37.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;* V% V$ |- ^+ r( |: V9 {2 z5 M" C
  38.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
      _9 K, e6 l0 Z2 T) ?7 e. U
  39.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ) m% M  t; V" @2 l
  40.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 ?' T+ `( H4 r5 _5 i
  41.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    - _7 M! `, T" C/ n/ f7 x4 C0 ^
  42.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    6 k. S+ D6 E! H( e4 c2 h
  43.     MPU_InitStruct.SubRegionDisable = 0x00;
    # @. P$ c6 L' d! y1 \
  44.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;" q  j: x  p+ q, F0 y# v" z) @% ^
  45. 1 L7 ]& c8 V* v& p# [: g7 T# |
  46.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    " ^: S9 s9 w3 a
  47. #endif   
    ) ^; s3 Z0 m$ E6 W! S$ R) a- }
  48. 4 [$ v. \$ {. Z
  49.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    4 q9 I/ {. r$ d3 {; x: R" s
  50.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ( W- x* \6 O2 ~' O$ F, R, G0 {( Q
  51.     MPU_InitStruct.BaseAddress      = 0x60000000;  `* ?" g! o, U9 R1 V- g4 J
  52.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    5 a' D9 r2 N# l$ z; J+ C% u0 K2 `9 T
  53.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    0 [* I& T( H4 i2 Y8 I
  54.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    . }# B3 `) C0 O* N
  55.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    - B* L4 @$ H- i" \) t, @& H
  56.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    $ F9 u( t, e% o4 C- i# [
  57.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    4 f: U. A. T; L
  58.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    ( W1 C9 F# y' c, ~
  59.     MPU_InitStruct.SubRegionDisable = 0x00;
    / `0 b0 Y% G3 X8 H( @" F! A
  60.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;+ m! `/ B1 O3 M/ O% u" Y# R: |

  61. : t# K% x7 R1 W( a/ G8 D  D
  62.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    5 T, Z7 M2 e' ~/ j; H  n+ M2 d" @

  63. 0 ^4 V" K! q5 V5 L& e+ T( V+ \8 ]
  64.     /*使能 MPU */5 T) E; b6 ~: q
  65.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    6 C5 Q3 k: W  u; J  Z9 M7 H! |* H
  66. }& a4 `+ `! ]) X
  67. 4 \  R/ @2 t) C0 G
  68. /*
    - U% k$ R/ w5 W$ e% P7 K. {
  69. *********************************************************************************************************
    4 H& ]! Z( {1 k+ Z/ e; r
  70. *    函 数 名: CPU_CACHE_Enable
    ; ^" m9 Z5 _# h4 K: K% U" k
  71. *    功能说明: 使能L1 Cache
    4 }. ?' o6 F/ X+ i0 e" E) I
  72. *    形    参: 无( D1 N- g2 K, O, y; G. M+ u
  73. *    返 回 值: 无
    4 f8 [5 h7 W5 c
  74. *********************************************************************************************************' k0 C  n8 x# F
  75. */
    ) }, ]! }% [$ h$ I
  76. static void CPU_CACHE_Enable(void)3 X2 ?, I3 E: w7 s: c
  77. {) J* u2 F' G; F9 y: K( T$ S
  78.     /* 使能 I-Cache */
    % u' P* i: E$ l" U, o$ Z) w5 E
  79.     SCB_EnableICache();. r& w% w1 O' K5 G

  80. ) r+ n. [; M7 d( E) [
  81.     /* 使能 D-Cache */
    & Z$ Y1 a* z$ [1 q4 n/ T
  82.     SCB_EnableDCache();
    % ]! I5 j. K/ C, q) A( T+ t5 f
  83. }
复制代码
8 e  V7 t9 Z' z5 t; ?
  每10ms调用一次按键处理:/ m0 K( z" E2 Y6 V/ O8 _! G3 B, ]
( w6 K5 _' G- G" K
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
/ D9 H* F3 J0 ^' x3 Q  `5 b6 U5 H5 t8 r; J
  1. /*
    - D$ j: K- _" i" `6 m/ W
  2. *********************************************************************************************************
    4 [/ F6 l, M- P- _* ^, I( Q
  3. *    函 数 名: bsp_RunPer10ms6 K. p3 I  q/ Z* ?" u
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    & c/ r; ], ]: N" W5 j) ]+ O6 P
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    ; B- ^: `0 a3 M8 x- Y* n
  6. *    形    参: 无
    . _) x! w3 [- _- l
  7. *    返 回 值: 无
    ' }1 B6 c" ?3 }* }9 c8 |: s. n
  8. *********************************************************************************************************
    / J+ }7 u% H1 `, r5 T- g' @/ m
  9. */0 C# K9 C8 L. r! h: }/ T
  10. void bsp_RunPer10ms(void): P5 P3 e; ?+ l& }; P3 N& d
  11. {6 `' h: M6 p1 x* i' H8 x2 O
  12.     bsp_KeyScan10ms();+ P: \7 k# G$ N' B# l
  13. }
复制代码

1 q% ^$ m: ]; m7 u! f7 S  主功能:; j2 E* v" B# d) x2 T' m! ^

0 L: O7 N0 m3 n# W& s0 u6 W$ ?* S主程序实现如下操作:, T) L) `! y6 {; J7 k5 ^+ P3 e

2 Y2 |2 b% p9 j( B# K  启动一个自动重装软件定时器,每100ms翻转一次LED2。4 I( ]# D$ ^2 z' a0 k2 _8 M3 \+ A
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
* A3 q# |6 Q7 S: b( H: [* V# K. C  printf("【1 - 读QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
7 O" A) S7 A- U% E  printf("【2 - 写QSPI Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);5 C2 N6 R3 u8 D  e- w: C
  printf("【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
9 k% Y3 J1 o1 b% {  }  printf("【4 - 读整个串行Flash, 测试读速度】\r\n");5 Z# w' D* \9 I. |: H; z/ U
  printf("【Z - 读取前1K,地址自动减少】\r\n");
$ K* D1 o# `1 Z( h3 s  printf("【X - 读取后1K,地址自动增加】\r\n");
" A) G, Y2 Z) |# d% z) T# e  printf("【Y - 擦除整个串行Flash,整片32MB擦除大概300秒左右】\r\n");
7 `) R* i% v0 N, L  printf("其他任意键 - 显示命令提示\r\n");. V) \7 S% a6 d* [8 Z: O% k' F! [
  1. /*9 v( B, s$ F5 Z- X! |
  2. *********************************************************************************************************
    6 {1 p) S  U$ H2 w! N1 E" O: a
  3. *    函 数 名: DemoSpiFlash
    ( a; u0 N0 d' B' v% q
  4. *    功能说明: QSPI读写例程
    % t) g/ S& M( r' O
  5. *    形    参:无
    , G8 A9 @" g$ k) _& ]0 H
  6. *    返 回 值: 无4 g" n4 K/ r$ Q% b
  7. *********************************************************************************************************0 |1 N2 \' ]  k/ t) d/ F3 ~
  8. */6 ^9 Q. o( {: l# ?9 M
  9. void DemoSpiFlash(void)1 Y7 U5 B1 m  c# e3 [0 e4 p
  10. {
    & k6 s( N' q5 R; Y& ]5 y! l& F
  11.     uint8_t cmd;
    . o" m. F, y1 u, h! R( o5 p4 s
  12.     uint32_t uiReadPageNo = 0, id;" O+ i" y4 r+ A( L, [2 s1 h! j

  13. . D8 W% d; @2 l0 j& F! p( a
  14.     /* 检测串行Flash OK */
    5 n' C; [3 K. K& A
  15.     id = QSPI_ReadID();! F% A1 [! j% y. ~
  16.     printf("检测到串行Flash, ID = %08X, 型号: WM25Q256JV\r\n", id);7 _+ H* A/ g: \+ e7 {2 W( Z. Y2 q
  17.     printf(" 容量 : 32M字节, 扇区大小 : 4096字节, 页大小:256字节\r\n");4 g1 ?1 \$ H4 _. c3 F4 R

  18. ( K% _5 w; J9 `7 }2 Q
  19.     sfDispMenu();        /* 打印命令提示 */
    / x- I( M1 y) q6 }* S8 `" i7 l
  20.   `$ j- w! b9 @9 ^
  21.     bsp_StartAutoTimer(0, 200); /* 启动1个200ms的自动重装的定时器,软件定时器0 */. p5 r1 l" `! @* C) z
  22.     while(1)
    * [, [7 n: c' |' [
  23.     {" B4 b5 o" K5 n
  24.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    ( M% n2 h& z' M' |

  25. + [- Y' l" M: @  c" \
  26.         /* 判断软件定时器0是否超时 */
    + |, e) f3 b- r3 G, q2 a9 Q% O
  27.         if(bsp_CheckTimer(0))
    $ i6 V9 R! O7 o5 t% t2 s
  28.         {
    7 w; }8 j  p. t- ?
  29.             /* 每隔200ms 进来一次 */  
    9 o8 y+ t5 b; a) ?, C
  30.             bsp_LedToggle(2);
    # P+ `8 z% i2 `& K# {
  31.         }% z) \  L4 {& C+ h* U& F4 ]8 Y

  32. / R+ Q; h) m0 r1 u
  33.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    / }! t4 s2 s2 k- i) v# a
  34.         {
    5 A( ^0 f4 g, T6 j+ j' p5 V
  35.             switch (cmd)5 M, {& G$ w/ Y/ _/ P$ h) N
  36.             {
    % j9 S, F% D9 b% }2 O
  37.                 case '1':
    : F2 ]1 w- g6 T5 a; t/ a+ }
  38.                     printf("\r\n【1 - 读QSPI Flash, 地址:0x%X ,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    4 X# B; Q$ p$ s* e* ]
  39.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    ; c) _) B; [7 ]- J  A9 z5 X5 [$ @  d
  40.                     break;
      ]4 A+ c3 g: u9 ^+ T
  41. ) ~5 \' o# ?( ^
  42.                 case '2':' j  x0 s" k/ k7 _. t+ g
  43.                     printf("\r\n【2 - 写QSPFlash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);- S  X. x+ D. @# r9 t/ B* W* R
  44.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    ( }. H' W/ l" i! w) v
  45.                     break;
    9 G% f- K" N7 R! {- z% }

  46. 7 ?7 m+ j0 W- |) f" {2 Q; R
  47.                 case '3':. P' N1 I7 w/ v8 T6 J; s2 c
  48.                     printf("\r\n【3 - 写QSPI Flash前10KB空间, 全0x55】\r\n");
    5 a$ J( }! N6 u  A& y
  49.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */0 Q- D0 V. R. J, w7 G
  50.                     break;4 d. e! m. _# o8 L
  51. 5 P# z$ k6 d& V; G) @
  52.                 case '4':
    9 k. t/ }" r) }$ i* Y
  53.                     printf("\r\n【4 - 读整个QSPI Flash, %dM字节】\r\n", QSPI_FLASH_SIZES/(1024*1024));
    2 C7 d3 x, l, P5 a6 I0 V, X- |+ S; |
  54.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- S( g( l% T% j- R1 Y
  55.                     break;1 I% w# S1 |2 j
  56. + k& ]( i0 ~0 ^! p# n0 l; a, b
  57.                 case 'y':
    - f* K! K! Z4 Q
  58.                 case 'Y':
    2 Q: u6 y! w+ V9 _2 ^, V- y
  59.                     printf("\r\n【Y - 擦除整个QSPI Flash】\r\n");& Q4 T* J: }0 Q  ^
  60.                     printf("整个Flash擦除完毕大概需要300秒左右,请耐心等待");
    4 j) _- c. ~" s9 M. n( f* u$ U
  61.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    + v+ f5 e" {. u6 y( L
  62.                     break;
    6 U0 I0 @! M( }- M- d/ M
  63. 9 `! ]8 l9 j0 T4 Z; J# G- @) I& T1 ^
  64.                 case 'z':
    ' W8 ]8 b# |/ s0 j0 Z# p. h
  65.                 case 'Z': /* 读取前1K */" F- z2 d  V7 Z5 }5 ~
  66.                     if (uiReadPageNo > 0)
    2 Z$ ~4 z# |- I9 T
  67.                     {
    , @$ i8 S4 [- T( T* g0 k
  68.                         uiReadPageNo--;% T% O. B% _( N) o- T
  69.                     }+ `  W! ?6 A2 {" _8 t
  70.                     else$ s) r! z# l+ `
  71.                     {
    * I# U" ?9 u, a/ w8 z( e5 ^. ?& B
  72.                         printf("已经是最前\r\n");
    3 v3 C; h8 B3 g
  73.                     }
    : z* }/ D' n$ {" h* B! L
  74.                     sfViewData(uiReadPageNo * 1024);- U0 E) F( g" S- s
  75.                     break;0 q0 {' u" P, Y/ S2 E- Y% B4 u

  76. 7 g$ L; C$ c) E: \( [# T
  77.                 case 'x':" I2 m2 j6 `5 P# B' m
  78.                 case 'X': /* 读取后1K */
    $ ]5 a9 @3 {3 w2 _; [. n1 Q( k
  79.                     if (uiReadPageNo < QSPI_FLASH_SIZES / 1024 - 1)0 R/ L/ v, q/ f! H! k; g$ \- w3 ?
  80.                     {
    " }3 h' d/ o" v4 K
  81.                         uiReadPageNo++;
    4 {# P7 p5 ~+ m# o) Y2 l1 [1 S
  82.                     }
    , y. E( J& B) M5 D9 ]$ r
  83.                     else
    - m1 {3 C$ r  K" ^  O# ?
  84.                     {
    6 U* T# T! {- O/ G: L/ O
  85.                         printf("已经是最后\r\n");
    ' j6 m/ f) O- j6 Q! q
  86.                     }* ?' z2 r% f2 A' [* q+ b
  87.                     sfViewData(uiReadPageNo * 1024);
    # R2 z8 Y' ?9 S6 G
  88.                     break;! W" t1 B' k7 [& E

  89. 7 `- |* l/ m  `* v1 g
  90.                 default:' s  s4 X+ r  Z: H/ ^' }5 U% v6 j
  91.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    ' {' W, o% v; H- y) g4 `
  92.                     break;4 ~' L. f+ D, K- R$ b. O
  93.             }( j# C: o! J2 j4 a; ~) Z3 c" m& P
  94.         }4 H5 m+ e5 w/ G" D: G3 a0 {7 o5 V
  95.     }
    # t- N+ m. |) B8 O& v; J
  96. }
复制代码
( e+ D/ P; ^7 e
79.10   总结
6 M; v4 i; I) p- Y: w9 M4 k7 G  n本章节就为大家讲解这么多,实际应用中根据需要选择DMA和查询方式。
2 U. v3 U6 }  i2 @+ o
# u* j( x% G% q# ]8 J# E8 \, N9 W4 T
f9d118d4b7d97794c046ced058924277.png
940488dfa9f0e39fce107fbd8fd9ea16.png
赞 收藏 评论0 发布时间:2021-11-4 21:24

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版