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

【经验分享】STM32H7的SPI总线应用之驱动W25QXX(支持查询,中断和DMA)

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示/ {( E* ~# j7 A, Y
  W25Q64FV属于NOR型Flash存储芯片。) G; u9 z* x+ b7 \- |
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。  J$ q. w1 X3 D+ [' c6 s+ a  U
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 ~9 K) O& K+ F/ u3 b- i" z) S

) @$ K; e4 ]; L; o) v* H3 `7 u7 }  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。+ C7 A" l" p6 J! i
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。  V* j1 h& a) [3 b7 h4 d. U$ g
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。* `0 L: A7 I6 Q" A" w2 b
  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
3 }' v; K' Z' h7 j# A- H6 f  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。+ p/ Q0 E3 M% r0 U* _

7 W+ l, z+ C, T) [73.2 W25QXX硬件设计$ F: Z( q4 b" ~$ |, l
STM32H7驱动W25QXX的硬件设计如下:# n# V& n5 |+ G; B; ~

# m) [7 f: m8 y) {7 z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

' y  d' ^. K' K
0 _3 Q. z! Z# h" x$ i( h  n) m- {1 U关于这个原理图,要了解到以下几个知识:$ g9 ~) i7 X& ?# Y$ I) {, Q" ^
4 z, N. _) H1 r" {" @; q! w' t# N
  V7开发板实际外接的芯片是W25Q64JV。
6 M) I/ F2 o6 _1 |: ]7 N0 F  CS片选最好接上拉电阻,防止意外操作。
( O- v2 _* `: L! Z  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。; Z- \8 c5 u; y& b0 k9 ?! w
  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。6 w& e3 i. ^( t% c( Q  |% E0 T' s
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。, b. Q1 ^7 z# n  ]# O7 W% |
73.3 W25QXX关键知识点整理(重要)5 H6 O/ b( P# }/ f7 x3 _8 t
驱动W25QXX前要先了解下这个芯片的相关信息。, q, a& ]# q' _$ {/ i8 u
; x. X" A, P  Y! d: E- |) ?9 J
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

% o* c6 G& l/ B. n3 F+ J; y
- M! l2 C, t  z  W% B73.3.1 W25QXX基础信息

; l5 \0 \" x1 M: @( m  W25Q64FV的容量是8MB(256Mbit)。+ M+ a  b1 j3 n* _% j/ u& y
  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
, x2 ]5 s) G) |支持两线SPI,用到引脚CLK、CS、IO0、IO1 。( b. |* ^* }+ ?  a" x0 Z% C3 u

# t! q. i$ ?. F/ F" d* G+ T7 x支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。" i; x$ f8 l( e; Q

6 ?, j& q+ M, `" o' S(注:这里几线的意思是几个数据线)。
" m( A& _8 ?- w$ ]% u# Q) Y: ~/ P0 Y7 F
  W25Q64FV支持的最高时钟是133MHz。
& s2 P7 B) J- X& y) j! H5 P! D0 {  每个扇区最少支持10万次擦写,可以保存20年数据。
& _2 u' A7 S& U) P, z3 C+ X  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
3 Z# I9 n7 T+ |5 J- L9 w: X  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
1 |% r% U9 L: X/ q& e- r' A整体框图如下:
6 G$ {& O% J/ N! A  ^. f
: y/ z: r1 I# ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
. r# o/ {5 V# \# ?

- ~# a4 l0 d* @7 h9 x% N" ]W25Q64FV:
' g* R  M. U4 Y& b$ B3 u" {4 ^* p3 Z3 d! O8 O" x+ \3 H% O
  有128个Block,每个Block大小64KB。
5 K* U5 R: Q- ~6 }/ i  每个Block有16个Sector,每个Sector大小4KB。
: Z; u- @$ ^* _# R- T  每个Sector有16个Page,每个Page大小是256字节。
! B2 y$ r! t" m  O( ^0 V5 k73.3.2 W25QXX命令
( f& T% j, V: `- ~' T; `+ ~0 u& e使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:
% J$ @3 p# D# e. u/ L3 M
! C1 w/ {% W3 D( k6 \, y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
0 [8 x7 `  h. j: S% v

! h& s3 n! }8 z" o当前主要用到如下几个命令:
. L% H7 Y. F& y2 ~) f2 z7 G* d( M% k8 C
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */' n  v" V/ z# s! X# A
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */4 @# S4 B& n5 c0 H
  3. #define CMD_WREN      0x06    /* 写使能命令 */
    1 m7 N' ?# w, C. c0 N1 G* V
  4. #define CMD_READ      0x03  /* 读数据区命令 */! j3 I' b  ^- f/ D7 _
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */
    & W1 l) {! s/ {$ B! u6 J$ F
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */
    " `" M7 H# |. P
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */" R. O1 Y0 c5 p2 Y  h
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */
    9 N7 b* N" q7 ~
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
* k7 X; z/ G# B
73.3.3 W25QXX页编程和页回卷# a; Q8 F& L& C4 ]
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):3 e0 Y. _. C( D( ?" T- r. |

8 C, k" p- A: u/ ]+ l
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        : p/ Z' R! n% k( Y- q3 W
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    - Z* Y) i8 B8 |  `' H. k
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    ; X; U  x5 q" P& Q
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    ( d: R7 |' u- b. _, s  B

  5. 5 f0 Q8 N7 f5 Q4 z# D0 X
  6.     for (i = 0; i < _usSize; i++)
    % o  V( _+ x: H# C
  7.     {
      H- J8 M3 Z  J4 Y2 }/ R
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,7 w- c& A3 H) Q5 v) }0 t" X
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    9 W% }5 [8 G1 \. N' l9 }3 Q
  10.     }
复制代码
+ ?" m" B2 w4 x
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
6 [* H, s) v+ w+ D4 U1 E
6 r; p% [8 g9 V+ ?( Q比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
" Y; d! B' l( g( R# F! u, _$ s$ D, J* Z: \
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};+ I* X8 u! g7 u$ h& o+ r
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

( {. w# k5 I/ X1 o* n  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
, Z6 v. u# I% K7 k: C' z! e) {  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);8 G! ^; O" N) R- B
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);) o" w* W2 `+ d7 {
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
% V, f* i& s8 i; L1 W1 `下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
! s( T6 B4 g5 U* ], D2 |) x" t  M# [2 T$ e1 s+ H
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
. ~7 d& `# q0 x3 Y/ Q/ }3 W
+ ?" t" R4 n2 h

2 n$ B7 m4 L6 r5 M) _4 R& M7 ?: z73.3.4 W25QXX扇区擦除) e5 }8 D6 y+ @7 q# p6 ^7 d
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。8 x7 C( U. \* V6 z% S
! M# H* |" X$ ]% `9 d( c/ r+ B
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
1 U/ _5 |2 e0 y- q) d( \8 p
; T! n. r8 h4 Z) d" g/ H* ^73.3.5 W25QXX规格参数
) d& ]3 Q8 D. I8 m) g
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
. j2 _( H+ a* T  T* L1 q3 T, _# Q( D5 y1 L# u: T) B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
: K, f0 [. o7 }! l( l8 \* l

: o# z$ G- Q0 L7 r# ]0 Y  页编程时间:典型值0.4ms,最大值3ms。9 S* }! u* `: M9 h! b9 {
  扇区擦除时间(4KB):典型值45ms,最大值400ms。0 X$ a3 U- q, ^, ^- t- l
  块擦除时间(32KB):典型值120ms,最大值1600ms。
' Y3 X- Y! U4 I/ {/ J6 r' }  块擦除时间(64KB):典型值150ms,最大值2000ms。
6 _5 J8 h8 @1 O4 s  整个芯片擦除时间:典型值20s,最大值100s。
/ O1 I4 ]( D  f- V6 e+ {. ~; ^% [2 N+ N3 @8 `

/ c! Q4 L. Z" ?  k, u支持的速度参数如下:
6 b6 t; e) b9 r& k8 C( b8 F3 O4 i1 X# f, q. |
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

5 W: r/ t( V. L' g  i( {& e
' Q9 a& B2 E2 @; h/ i' q可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。) }3 A1 F. g: j# q* _

$ Z8 h8 d: b( g$ g; F73.4 W25QXX驱动设计8 \" C/ K2 p+ A: [0 w2 W
W25QXX的程序驱动框架设计如下:
: K) K, H( }( _' g8 @0 M0 c5 W# ~1 s
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 p* S) m& \$ w+ r' ^3 P+ _
  D+ E, r3 H: v6 n2 @有了这个框图,程序设计就比较好理解了。
0 P( N* v3 G$ r+ \; u+ P7 N- d6 _( T2 h! b' C- O
73.4.1 第1步:SPI总线配置) S7 h% X) t* n  d  Z
spi总线配置通过如下两个函数实现:
& W! z( {, M+ T5 Z  X( U" b0 ~6 e) s& o% X. E" b3 z" C- G
  1. /*2 [& H% a" d2 \8 v3 Y
  2. *********************************************************************************************************. @, d8 v( v9 o2 R" o
  3. *    函 数 名: bsp_InitSPIBus. K& [1 l) K9 N8 y! p" X* }
  4. *    功能说明: 配置SPI总线。
    5 b' N, F' a' j9 C9 ^# t
  5. *    形    参: 无3 m) N. c$ `; L2 R& S+ \" q8 t4 b
  6. *    返 回 值: 无3 N: w4 j+ K$ i2 p
  7. *********************************************************************************************************
    5 N# p# |8 w3 L( G/ h
  8. */
    3 J2 T: f4 o1 u; w8 H& q
  9. void bsp_InitSPIBus(void)
    3 y7 ?' l' @( S. h  y
  10. {    ( G; c6 V/ X$ N7 |' u1 b, ^
  11.     g_spi_busy = 0;
    1 f# I% P! s3 Q$ f: |

  12. : }+ V7 [! Y3 O% O3 L6 ^/ x
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    7 {: q+ n; G% j* x1 y
  14. }, L5 h) ]) n( x% i# O0 M& r0 Y6 n9 G( F

  15. 4 J, w1 o- b1 K! F5 _4 T0 S
  16. /*2 t* b; e7 x% R! l3 ^1 \/ o; r
  17. *********************************************************************************************************
    ; [( ]3 j6 F6 s. A$ k5 H
  18. *    函 数 名: bsp_InitSPIParam( W: k1 _& f: q8 G2 X0 M7 x" L
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    : S# U. V  ^: ^
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
    6 P5 C" x0 i3 H
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频
    1 v& I( M# k1 y$ g
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频
    & ]& f1 t8 ]4 \4 B
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频/ @+ h( p) K2 R" a
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频& f3 {) [' _# m4 E8 s
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频* Q: @* L  h( Q) _+ a# U, J2 n
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
    - F/ w6 ^, `: o/ W" i6 R/ F( H% W
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频  v1 C; A$ h. a0 u* y
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频
    " Q; Q8 |, h% y6 F: V5 t
  29. *                                                        ; E: D' ~3 A9 S' ~  e
  30. *             _CLKPhase           时钟相位,支持的参数如下:
    9 ~0 ~' Q2 H& c+ G3 w1 Z
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
    ( q: ^/ n! I: v) Y3 ?# H
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据. K2 l& _" g, C1 I- g
  33. *                                 . o5 |" ^' n" H: @+ C6 i, D
  34. *             _CLKPolarity        时钟极性,支持的参数如下:8 b' G5 N6 [( [2 Z* j. J! x& q! j
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    2 S2 |. I. h3 D9 d% P
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    7 i# u. n/ r) q* O% s5 N- P
  37. *: n. \) O2 U' n0 k
  38. *    返 回 值: 无
    4 W9 b$ p' H# g+ U: F; t1 ~- Q7 j
  39. *********************************************************************************************************2 D6 k7 h* V. U/ F& r; X
  40. */9 n5 a( w9 d) K- J, H" Q
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
      z% M7 d8 Q  A
  42. {
    : E9 r: O& W; [$ b
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    0 ?* B$ r9 j- W: h/ k, E0 D
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    : C/ |' p. r7 T+ ~
  45.     {        
    5 `# s! e* a5 T' z; F0 \" ~
  46.         return;# S6 L& K0 C0 S6 P4 \
  47.     }
    / l/ F& [! Q) |  g7 ?
  48. 6 m% a4 T8 U# \( N+ O
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
    6 o: D. P* b8 o
  50.     s_CLKPhase = _CLKPhase;, W) v9 ^6 U1 H2 y# V3 q
  51.     s_CLKPolarity = _CLKPolarity;
      m8 T& V8 k/ F$ q( {

  52. " }; A( X* W6 D/ W
  53. - _2 Q+ B6 \& `, P5 M- c
  54.     /* 设置SPI参数 */, k* `, B; s6 ^$ d/ E
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */. A8 \" v9 _& k- Y/ b8 u
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    $ m% S+ B4 j% t) t; g6 G( }/ B
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
    . P6 e0 E, A% L( F# k9 O
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
    # h1 _5 ]8 O5 ^
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    ; `  H/ U( ^! M; v% h" h
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
    . d/ A# n; i" C  I$ s- e
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */# N9 G( Z- t" A8 V' P+ w7 K
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    5 K& Z! [) H, j. f
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */. o" s2 Q, J4 O0 n7 ~# i* e
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */2 O/ ], {- j& M* }( y
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */' d6 l- u: n: r
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */' X' t; z% W' v' k, [8 Y8 g
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    ; }: c7 @; I( v( r$ d; V* B  @* W! }
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    * p  ^4 r1 ~! ^6 }- Y; V
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
    , A7 X/ q$ U! q7 c, Q; B9 Y% b# _3 R
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    / O6 z* Q* c4 D1 e# _% L# c

  71. + C1 C! f- z( G1 S1 G2 `( e
  72.     /* 复位配置 */
    # @  B: ^$ L$ i7 I
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    5 O; _) a8 u8 R" K# B/ u, ~) |8 _! V
  74.     {( I  T% x0 D6 L9 Y2 M3 t
  75.         Error_Handler(__FILE__, __LINE__);( D8 q: J. }. r$ W& }5 R) P0 R
  76.     }    6 o. G8 U  S9 v; Q' ~" i

  77. " C" O1 v7 M/ z' x) ?. G
  78.     /* 初始化配置 */) t% X+ L# l9 J% X; O( T* I( e
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)
    / S: H# C+ l$ H% K: ]- N3 L
  80.     {. I  o- G: b+ `' o
  81.         Error_Handler(__FILE__, __LINE__);
    , n+ \' V! t8 O: S- Y' |% r$ P
  82.     }    * G) V6 L3 H" ?
  83. }
复制代码
1 H4 D2 ^6 N! Z* ^
关于这两个函数有以下两点要做个说明:. w3 ?. M  c! l7 O
$ c& ]) d5 `* o& D4 z& M
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
. x9 {9 w, A) v$ r  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。) m/ }" v" b" q/ w& C
73.4.2 第2步:SPI总线的查询,中断和DMA方式设置! M8 V+ r- m/ ]7 n  e5 K
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
8 u+ Z; m& h/ [7 J9 S5 R  B* y; t- V, [( o; \; |
  1. /*: p1 I) h8 Y$ _$ l0 W
  2. *********************************************************************************************************) L8 ^$ Q# }2 [+ `
  3. *                                 选择DMA,中断或者查询方式/ O. j' t3 b1 _3 n  u+ g
  4. *********************************************************************************************************2 Z% F3 L" _: Y0 B
  5. */, \2 @/ D0 E1 G0 F: f
  6. //#define USE_SPI_DMA    /* DMA方式  */
    / q: `6 A- A6 @% C2 h/ d& x
  7. //#define USE_SPI_INT    /* 中断方式 */+ x' Y! r6 P6 w& ^# G& K
  8. #define USE_SPI_POLL   /* 查询方式 */
    ' s7 W, f* t* L

  9. * F: R0 C( \% D4 L- D# u
  10. /* 查询模式 */9 o" D3 A; Q3 @: }# q/ t
  11. #if defined (USE_SPI_POLL)
    $ \- u7 s; f( K, K0 L
  12. ; @+ q, D8 I3 E+ C0 Y
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    . W$ R' o; i( P) o9 C) Z7 y4 o/ t  p8 R( F
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];! j( `% m* \; j: q1 }7 x* y
  15. # n( P" M( k/ Y  c
  16. /* 中断模式 */+ D5 Z4 F! r( m* `5 V/ D
  17. #elif defined (USE_SPI_INT)1 d- v9 D0 b, k% B7 ~* M: v- x

  18. . P, e8 V0 C$ h& I1 M
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    6 M- P; ]  W$ J
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];9 _& m, U. {6 e  n) Z: N1 x/ T+ ?6 \
  21.   h# A- o% n; b# W
  22. /* DMA模式使用的SRAM4 */) ^2 g- A" {6 A$ H
  23. #elif defined (USE_SPI_DMA): }$ N6 K0 h: _" u; A& ]) x4 q
  24.     #if defined ( __CC_ARM )    /* IAR *******/; k  \: d9 w" E- L6 Q% u" f1 S. P
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    5 R3 }" Y. i0 F/ v& ]/ ~. Q
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    # ~7 `5 {* q. f$ f' `! h
  27.     #elif defined (__ICCARM__)   /* MDK ********/
    % P# o9 e; X4 c6 k
  28.         #pragma location = ".RAM_D3"
    2 ?( W  a2 l( ]6 r% @$ J# B
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   ! _  ~. g5 A% t( }8 z: x5 a0 ?
  30.         #pragma location = ".RAM_D3": ]6 J* p- M$ l( Z3 g
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    4 ?7 i4 U' p2 {  ]6 ]+ y
  32.     #endif
    2 l- z2 ]/ e5 _! h/ K( f
  33. #endif  k4 o6 {3 u% X& L7 K& n

  34. 3 j9 h' j+ h9 |; ?
  35. /*( ?. r5 G9 V" w3 s5 ~0 g; `# J
  36. *********************************************************************************************************
    * y3 n! B. s5 V7 o
  37. *    函 数 名: bsp_spiTransfer
    5 ]8 @- d9 o; j8 x
  38. *    功能说明: 启动数据传输" X5 `% `, ^& J3 ?7 e* C3 M
  39. *    形    参: 无% s! H! q1 x. x. t, O) t
  40. *    返 回 值: 无+ p, S( O  M4 P: M- t
  41. *********************************************************************************************************& Z8 v: \: d3 h& h* k
  42. */
    % B1 R% u+ J( _& u
  43. void bsp_spiTransfer(void)
    9 ]" ^: `/ [- N6 n0 b0 u4 A
  44. {
    8 S  ]5 ^8 ]/ V5 B" t. t" l- E. g
  45.     if (g_spiLen > SPI_BUFFER_SIZE)
    6 w: f! K1 Y) k% R$ [- [
  46.     {8 ]# B1 ~/ B* A8 m
  47.         return;& V& k2 W. e, [* G
  48.     }7 J/ i: d0 @6 O2 p5 w# o  F4 U1 M

  49. / u( M+ R" f: q: l/ z. T' a
  50.     /* DMA方式传输 */7 [3 ~1 O1 A+ x5 J, b+ p
  51. #ifdef USE_SPI_DMA
    3 G( ?, l! O4 ^
  52.     wTransferState = TRANSFER_WAIT;/ |3 C/ I7 E# D( ]  v7 a( v

  53. % _: P/ h* x: m6 y3 O0 w
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    ; r- c. g, B3 R  V/ X, [4 `4 |9 ^
  55.     {
    ! b9 I7 P) s# O3 P1 ]
  56.         Error_Handler(__FILE__, __LINE__);
    6 R: z( H8 w% b3 l+ B$ L4 }4 F! o+ g
  57.     }
    # R$ j2 P7 i' I7 z" N

  58. " X* A4 A0 n9 H- ^0 U; f
  59.     while (wTransferState == TRANSFER_WAIT)
    . C4 K$ ?+ @! [
  60.     {
    + \+ B0 Y0 c5 j" r. p4 b
  61.         ;
    / Q$ Z4 S$ z0 J+ O& ^$ t# S4 T
  62.     }
    / Z8 m4 h& E) z
  63. #endif
    # D0 ]9 R; p- ]) q1 d8 d
  64. - L/ Y( J. W# i" a) L' X/ g, ^
  65.     /* 中断方式传输 */    ; [3 j  q; k& o0 L, J; D
  66. #ifdef USE_SPI_INT
    3 A$ {( x" O0 y+ _1 O3 ~. G
  67.     wTransferState = TRANSFER_WAIT;; V) B9 g9 D9 y7 r% H
  68. ' g1 [5 {8 {5 ~
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    9 {  ?( p" L) N. G
  70.     {
    4 j. i& Q4 m7 p% W: p6 H
  71.         Error_Handler(__FILE__, __LINE__);
    0 a; z0 R/ W- G" k( T: _2 r0 r
  72.     }
    $ h8 C4 f4 I3 C4 Y. N
  73. 1 Y* C1 C! {$ P' m) E; a7 G* X( ?
  74.     while (wTransferState == TRANSFER_WAIT): g$ r* I6 d1 @) |0 T' P0 G% H8 `
  75.     {; S) t+ d/ m9 ^5 W
  76.         ;( {/ r6 q5 d' G( g* l
  77.     }
    + j" m' C' \9 b+ L. ]( f+ v9 {
  78. #endif' U& G: r# `5 v% E8 @/ Q4 K

  79. 1 o/ l9 Q; D7 R0 y9 ~; _( Q5 k" M
  80.     /* 查询方式传输 */   
    ) n* j( E/ O, q. ^
  81. #ifdef USE_SPI_POLL
    ' K. @& b* X3 p3 q$ Z" }  }, E
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    * }  a8 y$ b, A2 X
  83.     {) N6 m) B' l2 I; `" C1 v' @
  84.         Error_Handler(__FILE__, __LINE__);
    # X$ W& d& d" P/ y( C  P
  85.     }   
    8 A3 W9 b) x/ R
  86. #endif
    : e" G! z8 p4 H+ D* i" w: L  o
  87. }% g  B: P' R4 t: X! S2 h
复制代码
- G' x" ?1 F1 b2 e3 X5 o, f
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
# u) x6 U$ {2 Q9 j" T
- B) J6 v0 ?5 Q# S% n( ?  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。2 k. s- K( s0 Y' d1 B; V
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
. G0 M/ c8 Q" R& i. \  N+ V
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */9 y* a7 K, @; e# l+ e! L4 `
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ; Q* ~1 r9 D3 }+ t" j( s
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;
    / J2 k) M9 _8 c. S* K0 V6 i9 c- d+ `
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;) z3 f% a/ a" ~4 X% U3 P
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;- m2 I+ A" F$ R5 k) [5 M) O$ ^
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;; M: ^# \$ t4 }# ^
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    1 S& y. A6 j& t, V0 W- h
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;3 u1 m: E# Q! D7 M8 N$ l
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    & C  J) i  x# J6 l. |
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    , o, Y; B, }8 z1 |; E# z! E! }
  11.     MPU_InitStruct.SubRegionDisable = 0x00;: F6 k1 k, j1 M
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    : ^9 m6 y2 I; H7 b
  13.   }( {! G4 q5 g1 l  J( [9 k
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

4 a/ x0 z5 I* c9 ~) C73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

: H' d6 Y4 b" D8 |# Y首先回忆下STM32H7支持的4种时序配置。
2 S8 t, H/ Q( S  l
$ O7 B* o9 A9 y  当CPOL = 1, CPHA = 1时
  p+ Q( m7 p: x! h, F, H, \; V2 x, T5 m* z. D) S+ G
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。5 C. n8 B+ t9 S) I7 n

9 Y8 N% ~7 a8 S. d9 b" D  当CPOL = 0, CPHA = 1时  Z' X$ v9 N  r* J, w* p3 X

) C' H* w0 s3 {, R+ C4 _+ K, u! lSCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
$ M( X' h( B7 @2 j5 V0 ^0 w2 I% ^0 z' ?5 ]
  当CPOL = 1, CPHA = 0时1 |6 p% t2 G* Z+ |' \

, f% V) p# T0 [6 p1 @& [+ TSCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。' F; Y- Q( c5 D4 ~2 N+ [6 ^* q
- C2 m* B4 j2 M+ N! R
当CPOL = 1, CPHA = 0时# a; U: R7 f2 `4 U

# {8 g, F! v- Y+ E4 _1 a2 NSCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
0 e; j$ O# ?- k' a- P" \8 o5 r# C2 \
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
& b( x0 m/ n* H3 e0 r( `

7 i; l. a; _3 K' s9 Z有了H7支持的时序配置,再来看下W25Q的时序图:
, @* N: Y0 @7 x! r/ G5 @: q: R& }# {) \% U* C& G; S
Mode0 : 空闲状态的sck是低电平。7 k4 s& w* ?8 [- x) D( U
5 t7 l& E3 b! G, K
Mode1 : 空闲状态的sck是高电平。
. Y$ D% _4 E; f8 D3 ~( v2 W
5 v" @% {2 A1 E: P* r- X+ h3 I1 J
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
+ L7 b1 Q( A3 p' i$ l) o7 i

0 q0 C  F# Y+ S" h首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:# v+ |1 R% h" l& a) k

3 `& _$ {) c, \2 N- oCHOL = 1,  CPHA = 1
, Q( B$ ^. L% G- B' h; _. J
0 x9 S) \2 {) s9 Q) MCHOL = 0,  CPHA = 0
4 G) p8 i1 G3 C/ d  e! o* f
+ t: [9 X3 F. g; I+ k对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。7 N6 M* g" y& T9 y

# J9 i5 ?9 ]; W0 s% x3 Z73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制3 j  U$ q: S* \4 t$ O+ a- }  x" P
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:, x/ w8 a) j3 V9 m( M

' X+ s) E% A$ M9 S$ G& a& Q1 E
  1. /*
    * [/ j4 \' I7 u1 K) j
  2. *********************************************************************************************************. {1 h0 e) R- |3 m$ E8 l
  3. *    函 数 名: sf_SetCS
    & [- u7 Z! l& U) W( @$ D
  4. *    功能说明: 串行FALSH片选控制函数
    0 X# a9 Z; T5 t, v
  5. *    形    参: 无/ {) k+ t! M2 p. o) J
  6. *    返 回 值: 无# q6 d: x6 k0 k  j& ^, B6 ]
  7. *********************************************************************************************************
    ) ~& x7 x# ^& J  M
  8. */7 F- K9 j: t: j5 H
  9. void sf_SetCS(uint8_t _Level)3 R5 R3 f  E0 e) W! y5 r
  10. {, S* n* c6 `& n
  11.     if (_Level == 0)
    ( k9 V1 u: V9 q+ y8 r
  12.     {
    0 V8 x: X* Z0 Z3 g* V
  13.         bsp_SpiBusEnter();   
    4 x% g# a( A, c6 L% v1 ~
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        2 n) m" g3 `; m( K2 ?! ]
  15.         SF_CS_0();% u5 }1 T( {& C8 G: _, j
  16.     }$ Y0 e7 ?+ \; i  V4 t, r
  17.     else
    , G8 {5 x8 O( o( I
  18.     {        % R; W$ O' f, {1 R0 _
  19.         SF_CS_1();    $ G% T$ _* q* h
  20.         bsp_SpiBusExit();        6 i3 x- o- B- r$ W4 r$ `
  21.     }
      N7 U( s3 c: k( g& k$ P, a; K
  22. }
      F' }# U; s$ T" H
复制代码

5 K. b, g6 g# _+ A, c7 _! b7 f% Y1 u& O9 k; f- T) M
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。
4 H2 |) ~, ^& t" @5 a& G1 O4 b% G  Q, p* L1 Z$ k$ y2 v
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。" D$ H: K/ g6 c+ L
( b  c8 M4 j2 @" o+ R
73.4.5 第5步:W25QXX的读取实现

4 L3 o7 B2 [( o& V: {0 e# b% A/ Q" YW25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。6 D# B7 u. |4 }3 j* S9 Y! l

. k" f' v% D- K& _! }% m8 b  \: I
  1. /** A( h$ E2 x# ^5 O" R
  2. *********************************************************************************************************# P. l+ D1 W) V' L$ w4 |. i# \3 v
  3. *    函 数 名: sf_ReadBuffer) `8 D1 p& {* f( o6 b
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。* m, c  F4 W8 j4 [7 ^
  5. *    形    参:      _pBuf : 数据源缓冲区;
    4 E& l* m; d" I0 H% L0 M
  6. *                _uiReadAddr :首地址
    6 E; e5 J7 F) m1 L
  7. *                _usSize :数据个数, 不能超出芯片总容量3 v" p' n# g* G2 l6 m7 _1 l
  8. *    返 回 值: 无+ M- C$ p% ?2 k( q9 e; a
  9. *********************************************************************************************************- a1 t& i" q% h
  10. */
    8 _' i* D" k  B
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    , n- E: e5 y' H, ^& D2 d# z: t
  12. {
    % j' D9 m* r# d( b  C* H
  13.     uint16_t rem;* M* u; w& Q) j2 x% u0 R& K. r5 ~5 {
  14.     uint16_t i;1 G0 v% u1 D& `7 ?
  15. + t* }* k6 E( V% Y! B" N% y
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */! J' Y/ v# m9 E7 k% X( A
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize): `; {4 N" E$ v( k4 ]
  18.     {( ^/ \% P' l9 }5 L
  19.         return;
    7 \' V* ]+ s& u# S9 _9 @# @
  20.     }% b8 V0 A) E7 W0 n+ m

  21. , C( S9 q% y$ j& o3 i
  22.     /* 擦除扇区操作 */
    . [* W* {( O5 B% ^; Q
  23.     sf_SetCS(0);                                    /* 使能片选 */& H+ e# ~( U  M, d% G/ x
  24.     g_spiLen = 0;3 `) O, T2 j! S
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */2 Q4 t+ y8 K3 G
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
      w1 `8 V7 i6 H2 j
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */+ E  W% V3 v9 A
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
    % f" q2 }2 p' L; g/ Q  Q- Y
  29.     bsp_spiTransfer();
    5 k# N4 S% {5 i4 S; n+ m) l% |6 J

  30. : X$ H1 p8 B% q1 F/ F
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */' [- y: y8 R* b  G& N/ \
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++); d1 ^: J+ D. Y: ]1 z
  33.     {: R$ D7 J5 H- g4 M5 o
  34.         g_spiLen = SPI_BUFFER_SIZE;8 U; u8 Z( i  V
  35.         bsp_spiTransfer();
    . t3 D. ?- R# d! }: S& d' i1 i) J
  36. ' @3 F3 t4 h2 w9 C5 X1 p
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    6 D: B4 r3 x; v) }9 e
  38.         _pBuf += SPI_BUFFER_SIZE;
    - S3 C4 P1 C4 w) O1 ^1 E9 O" Z
  39.     }
    6 j. c4 @- Z& ]2 n

  40. / w6 e$ |- D/ c+ }8 ^
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */
    1 Q. c) k( T4 q+ c& F0 u+ i/ s4 p
  42.     if (rem > 0)
    + u& f& Z& m( D" }0 m1 m+ K
  43.     {
    1 X+ _( u' C) g7 t1 Y
  44.         g_spiLen = rem;
    2 q0 z/ r8 O+ }8 G
  45.         bsp_spiTransfer();- W& V! x" E0 l! Y# N( p0 T
  46. ! R" i% ~4 c) W2 h: ]) B# e( a3 T
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    0 b8 s8 W. U) m7 K+ B4 c9 l  u1 g
  48.     }
    3 m1 W" t  T& v6 n" O
  49. ( B. \0 F3 t7 h8 T3 ]# ^
  50.     sf_SetCS(1);                                    /* 禁能片选 */
    ) k6 d" w- ~( O/ x
  51. }
复制代码

4 O6 n! x7 ?; f5 U# _
1 D$ S' W% D' t6 P这个函数对DMA传输做了特别处理,方便分包进行。( F( W4 ?* M  o" K, g; G7 V
6 W2 p5 [( @( i- w! U  ^
73.4.6 第6步:W25QXX的扇区擦除实现
9 }6 p  [* h" B1 q4 `( x/ e2 G
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。
- ~! x/ G# ]" o* ^  c
7 ~" U% j! H! ?
  1. /*% L" o; C! }9 w3 Z5 a- z
  2. *********************************************************************************************************  o2 K4 a1 ]7 L7 w* s6 p) X
  3. *    函 数 名: sf_EraseSector
    2 b" r: P) S. h/ O  c
  4. *    功能说明: 擦除指定的扇区
    , }0 X" r, R) ^9 Y( s4 `
  5. *    形    参: _uiSectorAddr : 扇区地址2 E7 Y* j& j, {3 ~' W5 ~" G
  6. *    返 回 值: 无, ?+ i! J8 u2 A  H" c8 f
  7. *********************************************************************************************************9 C' @4 \9 ?& e8 R% X- e3 Q7 Z5 q
  8. */! {$ d  `" p6 d$ |, p6 s; N' z
  9. void sf_EraseSector(uint32_t _uiSectorAddr)9 P' c2 `* ?1 a
  10. {
    4 y7 c7 |) x4 B( l7 u
  11.     sf_WriteEnable();                            /* 发送写使能命令 */
    * M" u9 [# _+ G& a- Q6 x

  12. " y1 m$ \) a8 @) k8 g
  13.     /* 擦除扇区操作 */1 G9 F& E$ T: \( E% z
  14.     sf_SetCS(0);                                /* 使能片选 */
    % Y, b* ?" n, a" `, r
  15.     g_spiLen = 0;
    6 X% i" k9 i% M" I% a' W
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    4 u  I" `2 n( L( W( Q, Y& [
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */8 f. ~# i* I  k* r) H) H6 {
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */+ [4 S1 G1 ~4 v7 ?, F- h
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    8 y. Z9 D- [' s# _+ o  M
  20.     bsp_spiTransfer();) M: k0 M  \$ Y
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    6 A* w! M! n+ f: z( z( W, h( J) j

  22. - p: W1 L) S, {* E( e, z8 @! A
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    ' D) Q. e, m  c- t
  24. }5 N, O. j; N4 P' k7 k$ ^) F6 s
复制代码
8 t( @5 V0 P4 ?1 z' G) x: O
# D. \9 N$ n) J
整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:2 o1 E+ Z: H7 ], M5 @  J
- t) j' f7 P7 X" y
  1. /*
    % ~( h" D) j7 J) Q
  2. *********************************************************************************************************
    * l; h; {1 _% J: i1 K8 w, s( M, |4 C
  3. *    函 数 名: sf_EraseChip( {. x  G- d! n7 }
  4. *    功能说明: 擦除整个芯片
    # _* O" k/ e6 S
  5. *    形    参:  无) j1 ~# b& H; `( ^7 v
  6. *    返 回 值: 无; T) ]' ~$ M3 N7 [0 u
  7. *********************************************************************************************************
      u6 P& u* Z& a1 J/ D- r+ r, h
  8. */- x8 x( l2 A5 B5 l
  9. void sf_EraseChip(void)
    ) d- l: B5 _8 j9 Q5 B2 w
  10. {   
    ( ^3 R: Z* K/ z6 U. B
  11.     sf_WriteEnable();                                /* 发送写使能命令 */
    / Y5 l5 ?4 W; @3 V# N! F8 ?& U

  12. . ?+ q3 r% e0 n/ y0 R
  13.     /* 擦除扇区操作 */
    . Q7 E) o9 P: Q
  14.     sf_SetCS(0);        /* 使能片选 */
    : d, Q. C8 n, c2 H7 g* Z
  15.     g_spiLen = 0;- C" H0 Q2 i1 b- S- d$ c5 j3 s) ^
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */
    " a  I$ @0 y4 v+ ~3 \
  17.     bsp_spiTransfer();) d. g* v+ U' ~: z, T- ^' Z4 s
  18.     sf_SetCS(1);                        /* 禁能片选 */; r4 S! n2 p2 @+ F- |
  19. . @" V9 `; R9 p; J# z. O! _/ t
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    0 C. C# E/ |2 h7 i
  21. }3 T* k2 N; P: @
复制代码
% T( C: Y; U2 c8 j' H' e+ P6 Z
73.4.7 第7步:W25QXX的编程实现
* k2 Z1 H6 o9 S" f/ DW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
. ^8 L: {; h  M
! G( r! s0 @5 E' g8 h) Q
  1. /*
    ( U  [/ y& v7 ?* Y" Q' @- Q6 I
  2. *********************************************************************************************************
    ; ~6 Q7 v4 f/ N9 Y2 H4 `6 }2 l: [
  3. *    函 数 名: sf_WriteBuffer
    & f8 C1 @. o# H: w; q, n
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
    ( L" ^4 _0 A" i7 c3 D7 w& G
  5. *    形    参:  _pBuf : 数据源缓冲区;2 }' h6 @" ^+ U) d0 U
  6. *               _uiWrAddr :目标区域首地址! a# @4 s8 L2 P8 V# y: a
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。6 i7 {( p" d0 ~0 E& f. {( f) x1 |
  8. *    返 回 值: 1 : 成功, 0 : 失败  a- r6 a) \9 {, I7 F
  9. *********************************************************************************************************
    ; @/ _' c9 A# g6 V
  10. */
    6 F1 N# \4 ]4 R7 o) D
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
    - t- N* L2 p9 M; D' R
  12. {' t& ^% P4 u# w
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    4 _) i/ @- n4 S6 }

  14. . ]; M8 e, Z  s" z+ c% s+ `
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;
    3 ^8 K$ y# T) P
  16.     count = g_tSF.SectorSize - Addr;
    1 F9 h" K$ ?* N/ W$ f
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;/ B/ F0 {1 \- }# p1 b
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    * Y$ T4 a% P$ G/ ]# E: h, u

  19. 5 u5 t, y9 c% r. e9 c0 G
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */1 P3 D& P) x- i! N9 s
  21.     {
    5 ~' ^! x5 j/ Y( ^8 |
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */" d0 t. w3 }! S- D; u7 D
  23.         {
    6 `% h0 R9 }+ S- N# T
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    " c2 @+ }# \% j, V7 Y
  25.             {
    0 j" z* p6 K+ P% q9 G9 V$ g( j
  26.                 return 0;
    5 N2 S  x/ t, b6 K, s/ z
  27.             }
    3 j4 t5 q1 g2 u0 f( V
  28.         }
    ' \7 G" T" H  g2 L; `% C
  29.         else     /* 数据长度大于等于扇区大小 */
    & l+ g% }; H8 T2 d0 m
  30.         {
    $ e" h/ b# ]5 y# `
  31.             while (NumOfPage--)
    - g: E/ g' J  d+ o/ A( f5 E
  32.             {# s7 ]0 Q! F5 f  Q, E+ Y
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    8 m( h( m8 z/ G3 C
  34.                 {
    ; Q, c" x. k: @, n0 R
  35.                     return 0;
    7 w3 U. X/ X% u8 L" T9 s
  36.                 }7 p7 x$ h. M$ U0 u  _/ l; D
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;2 b$ A: Z0 }$ \/ ]* U3 n! _
  38.                 _pBuf += g_tSF.SectorSize;
    ( @. A7 V. E. @# h6 k- S# O, z
  39.             }
    $ M" F1 b. _  U7 u- X8 L
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    ( c! D4 x0 F' h( d4 w# S9 K  J. D
  41.             {) T. q, ^8 ~2 i9 p2 q' j
  42.                 return 0;
    ' w) @4 j/ O% W3 n6 R
  43.             }4 N1 r# Q+ ^* E- m; U) u
  44.         }* Q: y1 }" \6 B/ Z& K7 Y4 o( d7 h
  45.     }
    3 Q1 a3 K5 {. N& O( M0 _
  46.     else  /* 起始地址不是扇区首地址  */
    - W  x9 Y/ o: ]! g; L% z; i! R
  47.     {% J7 e: ^/ h' _6 U/ n* @
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */1 o8 x' x2 S6 E7 Y  K3 N
  49.         {) V/ `' l7 n, y, ~- ?% a
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */! M. ~; i6 s2 c9 Q
  51.             {; i- d9 v  F1 w
  52.                 temp = NumOfSingle - count;! h4 l" ^* @. N& _7 Z$ q
  53. - @5 ]# S; Y/ }; [# \
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)$ M; h! A/ Q1 C# U
  55.                 {
    7 M3 Q7 I; i: i! L! i
  56.                     return 0;
    5 W  r0 M$ f' ]5 s8 \) e% [0 ]
  57.                 }' N& D# M/ z/ \* y
  58. 2 G4 u0 n" j5 j! _3 V" f' f# m
  59.                 _uiWriteAddr +=  count;' q& c: g! W4 G, _
  60.                 _pBuf += count;
    % m( ~1 B6 c4 `6 M
  61. & u, }) w3 {- v9 q" I, g# n9 h$ a9 x
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0). Z4 W/ s+ Y' w* ~6 H0 ~
  63.                 {* G3 e; B3 G  F9 _- x
  64.                     return 0;
    / z- g$ G& n7 c, p( @
  65.                 }) t% e' l( e& Z  g+ S# k
  66.             }8 E) d5 E8 Y, ^: q( r
  67.             else9 p: y& o/ N+ w8 j/ e4 K% X& V
  68.             {) c% l4 `, [& q2 ~, k
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    7 f# j+ \3 W# [/ R1 h5 @- j
  70.                 {( f) ~- W" w; t+ x! ^
  71.                     return 0;
    ! Y; B" g( |5 ~4 q
  72.                 }( N1 b+ J9 j* x8 x( G# E
  73.             }4 N( b" t4 @& U$ W! T
  74.         }
    " y( ]7 h+ }" j) r. E1 q
  75.         else    /* 数据长度大于等于扇区大小 */1 d+ I+ v! z2 f. y
  76.         {/ i' D9 _4 D2 i- A3 Y
  77.             _usWriteSize -= count;
    ) I. |  W, E4 ~, t% m
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;9 k2 c4 f4 y' z+ |' z# k
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    * U3 J/ V* s4 a& f" h: t( j1 c
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)5 v- l. L' M( e% `" q: k& N5 Z
  81.             {
    * W4 \# N! F( \3 O9 \
  82.                 return 0;( l0 ]$ R; H8 `( ?1 u) q, ~8 G+ u
  83.             }  P0 m. A4 x) e+ O

  84. 4 R9 L" Y1 c; i% ~5 o, L: q
  85.             _uiWriteAddr +=  count;
    $ p- m) D( y3 U( l1 L1 h
  86.             _pBuf += count;0 [( K6 W8 L; Y2 o! u2 l  B+ o
  87. / n6 ], ]: p: a& m9 T
  88.             while (NumOfPage--)
    % W4 U9 }- F- O; Z) J4 |" D
  89.             {
    " f( d9 e8 N9 |7 I7 [2 y9 L/ s- b
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)( l4 f, L# q% m
  91.                 {0 R4 i7 q3 x" G8 q: I
  92.                     return 0;
    ) E! y/ y5 E5 o( `
  93.                 }
    ! a- k. J5 k, ~2 m" @( S2 D
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;
      P: k# W! P) H9 A
  95.                 _pBuf += g_tSF.SectorSize;* b- y) D& @( m/ B% s' G! p
  96.             }
    " _7 q* a) {, R4 \- W

  97. " j8 j! j  [2 l$ v* D
  98.             if (NumOfSingle != 0)$ U& d  ]$ D8 @+ y# t4 `
  99.             {
    4 g' Q% ?+ f- }8 b+ c+ D
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    5 m: v2 u& w) O/ {, E1 {
  101.                 {
    ' C( C8 k* V5 V$ Q( @
  102.                     return 0;
    5 u! P6 Q4 Q# \1 [
  103.                 }
    : m9 w2 O2 `0 O: I
  104.             }
    9 M; `' \6 T8 B
  105.         }
    3 i4 e" A2 H$ M/ \( h
  106.     }. S/ E+ V& s  L4 @9 K
  107.     return 1;    /* 成功 */
    , I, l# Y' Q- N+ S" d$ O; r
  108. }
复制代码
/ t# o! v2 n. E; o% l
73.5 SPI总线板级支持包(bsp_spi_bus.c)
1 M6 f* l% R3 h/ a$ JSPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
0 S' d, M5 ?5 R! i% k0 U, b  _" ]* \5 ~" z' b
  bsp_InitSPIBus! G( f! u/ v  y* y# s
  bsp_InitSPIParam
6 \! M+ ~$ H6 Z6 P  bsp_spiTransfer7 a  \0 j, K3 Q
73.5.1 函数bsp_InitSPIBus
9 R, _3 ~3 O, B0 o. `函数原型:
6 ~% H8 G# F% x! F6 l5 H
6 o# F7 i0 m2 I0 D: p# v0 vvoid bsp_InitSPIBus(void)
- |4 \0 w( W5 H3 j5 A
- l, r4 w- @: x  F( S函数描述:0 R8 Q8 M9 U* V9 v) S9 w; E7 l& y" d

2 o& y' L) L$ k: d, x0 y/ j' _此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
; O) u/ |& o2 ?
3 c; Y8 |/ i9 P; X4 \/ V8 d( v73.5.2 函数bsp_InitSPIParam
9 g( d1 b2 h4 U" G! g函数原型:
  u0 \  @4 g6 I& n, S( E5 z
' Y) l3 w, L2 U6 L1 u9 \: d' Mvoid bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
" m# }$ e1 }4 a+ c# f
4 N, W; T! c! d9 m0 w  ?- H+ T! ?函数描述:" g1 ]/ M+ M3 G' L# W7 f

' U  J( E# P& e! m此函数用于SPI总线的配置。
1 v) b* n* E" }) @2 Y4 `
' j. W, f: W- O: k3 B2 B函数参数:
) k) h# ?8 s  T8 V
% l' ^. V0 n6 ?, S1 g$ H  第1个参数SPI总线的分频设置,支持的参数如下:& N" K1 [( q0 U! c! S
SPI_BAUDRATEPRESCALER_2    2分频
# t9 o- v& s0 |8 \$ Z
) G  Q5 `% R( {, w, D, U! vSPI_BAUDRATEPRESCALER_4    4分频0 ]1 B6 M3 l# p8 @+ V

) ^5 L5 p$ N$ r: K) g# o/ iSPI_BAUDRATEPRESCALER_8    8分频6 R- C  R. z9 m0 ~4 K! @0 s6 p

, x( C. E9 q6 S6 U3 }/ ]2 |6 F0 oSPI_BAUDRATEPRESCALER_16   16分频
$ \/ h9 z$ ?& {5 l* K( `, p, t7 b7 s  @4 `2 D9 F
SPI_BAUDRATEPRESCALER_32   32分频; D: ~9 x9 Q, g5 Y7 ]9 ~
" s' a9 z& X5 }1 N" L
SPI_BAUDRATEPRESCALER_64   64分频
( a% F6 e7 k( ?& O0 x* E! x, o  k- Z3 F' {5 V7 o4 j& u
SPI_BAUDRATEPRESCALER_128  128分频
+ `- s! e( e6 t9 f' m; ~7 F: F3 s9 V7 w
SPI_BAUDRATEPRESCALER_256  256分频
" w3 n% o/ p  y* A' a% L. w% B/ f0 r4 ?% \0 u
  第2个参数用于时钟相位配置,支持的参数如下:
# X1 m0 W2 V0 H% N5 x% \* XSPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据# e( ^' J. ~! A- X4 E( I

6 h! H8 X5 N2 uSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据/ U& o' I& ~1 L' L+ U
9 N5 _; }4 \* k) t
  第3个参数是时钟极性配置,支持的参数如下:
8 ^7 A5 E, A9 oSPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平: ?6 A, d! k; l' y1 a
/ Q* O) b6 }4 I& F1 e) G2 ?
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平$ l. C9 G- d  w* H% r5 M/ _

+ v3 B" U3 ~# R: f: t73.5.3 函数bsp_spiTransfer

# m9 M3 D" O$ E- \3 Z# i8 v) q( Q1 R函数原型:3 E5 G0 z7 {2 G, s7 E0 I
8 Y, B0 z0 i/ x% o9 w! L8 q
void bsp_spiTransfer(void)3 m9 m' m% F( b( p$ @

& C, G; ]) u" J函数描述:
4 o! R: h  W. J! T/ I
' R0 i8 N5 g/ B7 {此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
7 S9 [& s- i1 a
  n( g& A! |- V0 `$ t9 B* |) |* U73.6 W25QXX板级支持包(bsp_spi_flash.c)
7 m3 j4 T% u# w4 a! |W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:
7 ?4 @' D& u6 l+ z/ S! S' ~' {9 _6 f( d
  sf_ReadBuffer
- x; j; K& [+ L  U  sf_WriteBuffer
6 P1 m' n3 B8 }7 m: Z  sf_EraseSector3 W$ l& Y# s8 I* Q8 C2 [% s
  sf_EraseChip" m/ G9 ^0 s" j, y+ P0 v) G0 C. @
  sf_EraseSector5 X: s2 r9 N( I' o
73.6.1 函数sf_ReadBuffer
3 R% e* Y: Y+ |& x) `( E/ {9 g! y) h函数原型:
+ q& M8 z/ y9 u& x( k" x+ ], t# ], ?8 x% Q3 v
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize): Q* q- ~& A2 `& q  t

8 j9 s* N3 H) h) B* S- S函数描述:
( Y& b" |/ r2 c: e# @; O  N
  z9 W" T/ a: l此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。; s- R2 t/ Z$ B* `' E8 _- A

( g4 C+ q0 B7 O! \函数参数:3 z; b! b/ w; L) X2 w: P- O6 x5 v( @
, g* K0 V: n6 x
  第1个参数用于存储从SPI Flash读取的数据。
2 ~( h1 T. t; V1 m( L$ n  第2个参数是读取地址,不可以超过芯片容量。
( H9 T1 K1 z7 m& a0 N! V9 x  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
% y  N  b% _1 {9 V73.6.2 函数sf_WriteBuffer(自动执行擦除)9 s( [" a# g( w5 ?  Q# Y: t' z% h  w
函数原型:
2 F- h/ ?! M9 }' F6 E! F! R
' Z, E/ D' @9 N: d4 f$ Duint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)$ g5 x  e5 H2 |& ~: M

; C* F( d, _: H: `函数描述:
0 o6 m$ x) o5 D9 T8 {* ~( I( W) T0 U; H$ M2 j) a' h
此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。- ]& o! T- I* }) r. x

0 {9 ?- r  ^7 x; O7 C函数参数:8 l  E) m2 S+ \' i' W2 Y- [+ V4 T

: X. @5 m3 y. q! {3 Q  第1个参数是源数据缓冲区。- J$ ]( Y- T  Y
  第2个参数是目标区域首地址。
6 \7 m" Z" M( }8 @( ]! n  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
; t4 |" a4 |, n( B3 t0 m  返回值,返回1表示成功,返回0表示失败。( @2 A* B$ \- J" E
73.6.3 函数sf_EraseSector
6 S$ q& q1 f7 N; E函数原型:
& W& Y. i8 y! I; U2 y: S
8 b0 W5 j: [* ~2 X% Y3 C$ Gvoid sf_EraseSector(uint32_t _uiSectorAddr)) \# ?! Q: n5 ^- S1 s# C& Q# j

, g% d! X2 O$ Q' _/ w- Q$ [函数描述:* j: o+ x5 u: `! K* Y
% \# E: j% E% t, l- O# D* f% U1 q
此函数主要用于扇区擦除,一个扇区大小是4KB。) n9 \  [4 p6 \5 W* R

) p+ ~+ Y  j- o- k, _: X, _) O函数参数:
. @! z) x/ f# G; z3 [4 j5 N; |  w5 Y+ o. k% d
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。$ z" V! v$ s0 {  ^- F! j
73.6.4 函数sf_EraseChip
0 D( K6 G" \7 _. h6 y. N0 ?9 V8 @9 a9 ]函数原型:8 N" |! @' @' `! R9 R

/ b, Z+ D  j+ J6 ]: [8 Qvoid sf_EraseChip(void)9 _: {3 \0 J2 |4 E# t
  g4 z/ F9 ^  k
函数描述:' d/ q: Y& z" \9 B4 f0 e

- ^. H' D. @# `6 f, y; S此函数主要用于整个芯片擦除。
# c2 \& h( I: {& l9 M  v. A7 y2 I* c7 `+ I* P2 j6 p
73.6.5 函数sf_PageWrite(不推荐)
* q$ B' I6 o$ x函数原型:" z: r% e2 u: |
. ?2 o9 Q/ [0 ?
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)6 v- j1 X) y/ [  e
" k7 K( h: G: z0 Y" u  b& a; }  r4 c
函数描述:; I; Y1 ^/ N' N, J$ s" w1 R7 }

2 e8 j. M( H& `( w+ c/ c% |1 X此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。7 |# r5 k% g; k7 I/ V) j* M
' U  P8 G# u$ j, A9 S
函数参数:
- w% V, V% O7 r5 ~& w3 Y! P' Z+ M
  第1个参数是数据源缓冲区。4 p- t# {2 V; Q
  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
: c! d: ~, b$ i4 ^  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。
$ u, M8 r/ J3 k) T( b) h9 L73.7 W25QXX驱动移植和使用
1 c1 _( |$ C* CW25QXX移植步骤如下:
0 q5 c* h4 M+ l/ }3 g* F" Z# g5 k6 y2 C" w" [( l$ S
  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。/ z( B7 g( s* W- y& O
  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义8 d# w* v! M+ E. _# N
  1. /*5 f4 X! w1 Z7 g1 D+ X* e5 s
  2. *********************************************************************************************************
    ! r/ m2 R9 A( l( T7 m
  3. *                                时钟,引脚,DMA,中断等宏定义
    ( b" K. S+ l* P8 p* s8 j6 S9 z* w
  4. *********************************************************************************************************
    0 ?9 W7 }3 v' X1 ?; |0 L0 C
  5. */
    , U, ?2 k( P% p; a+ q3 f
  6. #define SPIx                            SPI1  R$ l4 S" ~' B  [$ }' Y! E
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    ) R% B# m- N6 g4 w) y7 z2 O
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE(). p# D; D# \. r3 N0 i8 R
  9. . p* I  S* @1 ~# ~  _5 ^: }" n: F( T
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()( `$ s# a( w' c$ o6 U
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    + W+ f2 w3 E5 k7 q4 y8 R5 _
  12. 0 C. @- B' s7 D3 U  U) x
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    * M1 O: q. a: `$ u, `
  14. #define SPIx_SCK_GPIO                    GPIOB
    % h' Q4 m& h; m: F( s1 V% j6 G+ a$ ^
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3
    : b& Z- W' D7 z1 [
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI16 P; P7 L* _1 J/ T0 K8 R4 v
  17. ; x. f4 n  @/ ]3 Y' y6 c& x5 t
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()3 g4 d- q- T8 h  |
  19. #define SPIx_MISO_GPIO                    GPIOB5 d' W+ R+ }7 Q/ d
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4# O0 C7 E9 H2 `8 |- @. Y/ m
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1
      ?* i: _6 Q; i2 T3 v# G
  22. 3 P! i% P  ]! ]5 W
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    + [# O" K7 R! @- }0 \: Q
  24. #define SPIx_MOSI_GPIO                    GPIOB
    ' D4 X) _6 v) ?8 ]+ l
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5
    ' I$ E3 A% \. G
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1+ D, I) h5 `' W* f
  27. 9 _+ q* n# L* F& D9 b+ n
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    , M- n2 g2 i$ S" f) ~
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    # v4 L2 B' o7 g6 g

  30. 4 I) ~, O: ~2 e- K7 v5 L* q
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX3 ]+ h& H* T+ c: O3 {7 \
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    1 \% H! K# J! }9 X0 |7 X& c) U! Y

  33. / w( R9 a. Y/ g$ b# E4 E) q
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn" S1 m( N2 n- q  x8 r
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn& A0 ^1 t- l& g: g8 D4 I
  36. + Z0 K  Y9 C, G& [( y/ g
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler! a* ]5 q7 [* ?5 e3 W# T
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler+ P  z" F  z) Z0 ?2 e. Z

  39. ( q$ L% x0 d) F  U
  40. #define SPIx_IRQn                        SPI1_IRQn. f6 o# ^. `9 T* ]# R0 ^2 b
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler6 x8 ~9 z, I& n) I- p
复制代码

7 |+ |: w5 Y  \9 x% Z9 W; z/ L5 w3 X( S: i  C. @
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
- u6 V- K0 p3 g4 y! S
  1. /* 定义串行Flash ID */2 @! |9 G, Z, H4 a( w% j
  2. enum' {! o( J/ V' W3 Y0 L8 _$ P" N2 j
  3. {* F$ g1 }& v: i& @3 g
  4.     SST25VF016B_ID = 0xBF2541,
    ' V8 w0 a  s. O! n/ V
  5.     MX25L1606E_ID  = 0xC22015,0 [$ _$ g$ l; M
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */% O8 _* ]1 ^5 r. O4 {
  7.     W25Q128_ID     = 0xEF4018
    * s% x" m& [( L, O' h" o1 E) R5 N' i: J
  8. };
复制代码

5 V! ^" _* {0 {# u& u  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
5 K: m0 z5 }$ F3 G
  1. /*
    ) B4 L/ p& G! r) b/ A' J
  2. *********************************************************************************************************
    * G6 F1 m9 g- u. e1 ?% V9 G$ ~
  3. *    函 数 名: sf_ReadInfo: A: B( P/ E: W+ l3 Q! z6 @
  4. *    功能说明: 读取器件ID,并填充器件参数! l* T! P: c+ o9 u1 r# Z% u- m5 [
  5. *    形    参: 无
    6 G. j# A0 Z1 n
  6. *    返 回 值: 无
    % Q1 g# d: [, c) E/ t
  7. *********************************************************************************************************1 w& C& |% e# q+ i3 c% P
  8. */
    + r2 j( y: A0 D& c6 d. _
  9. void sf_ReadInfo(void)
    8 J, N: d( e8 n3 O5 C7 |
  10. {
    7 G+ Q! T' D$ m0 m9 n( g% x  x
  11.     /* 自动识别串行Flash型号 */
    0 K) Q, x8 `6 o& Q5 V( [3 [2 r
  12.     {
    7 n! J0 V  W4 B' a
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
    ) ^. g1 E) C; D3 X
  14. . Q* R! t7 ^  v/ N
  15.         switch (g_tSF.ChipID)- B- f' _; X2 W
  16.         {
    * U$ ?4 X" Y1 a; h9 o. Y* Z6 b
  17.             case SST25VF016B_ID:
      h8 c; E" w5 j* p/ b( L6 g
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");
    6 ]- S1 @- _% V. d4 a% R- P
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    $ n% r3 N9 \. D: n
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */: A  @. y  U" u8 {: u
  21.                 break;
    ! Y2 j) T' {  ?3 `$ U
  22. ' `" j8 x4 n! k" }! }7 x6 W
  23.             case MX25L1606E_ID:
    " Q$ u8 T8 L3 Z/ u
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");# E: l+ h# j; _$ W) z
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    " Q" \3 h2 }/ [6 A8 d9 x" v( Q
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    9 T! i7 i; `/ F& D. y) f
  27.                 break;7 k; s% c1 ]6 r& s1 i+ |+ i

  28. / j! U6 C9 i2 n. d) S, y
  29.             case W25Q64BV_ID:
    0 d9 A& A% ^* _# O, g' x! h' Y
  30.                 strcpy(g_tSF.ChipName, "W25Q64");, I+ x2 |8 J$ V  P2 b" S7 v/ Q
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    ( P" c' l5 c: X; j; ?( z
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    3 o8 W( e3 L3 E0 V) h! I
  33.                 break;
    ! X8 W2 K+ B/ \9 A
  34. ; e4 H1 G( B5 X  r* L1 s  r* {
  35.             case W25Q128_ID:
    ' a/ m9 |5 Y4 h0 j
  36.                 strcpy(g_tSF.ChipName, "W25Q128");+ r' j6 f& t5 w4 X
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */. J4 N8 v: ^* i% H+ g3 G
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    4 k3 \% K4 r2 M( {$ s7 a
  39.                 break;            . q& {" \6 H- N' d/ ]
  40. & n- o/ x' \8 c# h- O. O# n* |
  41.             default:
    ( E. {+ x1 ?/ n; J" [! t4 S
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");/ X% q, U8 ^9 [: M7 ~2 E$ K
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;6 A. c. J$ N) I7 ^) Y) x3 A: W
  44.                 g_tSF.SectorSize = 4 * 1024;
      S, m0 Q* }2 _# p/ W
  45.                 break;
    : X1 a/ {' R5 T- o8 T  |* e
  46.         }
    4 w  [/ s& @0 E7 I+ \
  47.     }; w, _: q$ R5 V5 U/ q
  48. }
复制代码
2 t; h5 O% C% E
  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。
7 F  a! p' [! \
  1. /*6 Q' P2 @0 X4 F$ @+ t& M# j) ~. s% O
  2. *********************************************************************************************************$ j/ m. i1 r; i) @* I4 q/ m
  3. *    函 数 名: sf_SetCS! a  V' v. @+ h$ Q3 Q
  4. *    功能说明: 串行FALSH片选控制函数8 Q3 r" _  e8 n- ]
  5. *    形    参: 无! _0 H6 Q6 z) V! g9 P6 V
  6. *    返 回 值: 无
    9 v9 V4 A. n3 \( W9 @. Z  D( B- L+ t
  7. *********************************************************************************************************
    0 o2 ?9 T; e! @  F
  8. */
    , Y& |1 }% h8 k* p# o
  9. void sf_SetCS(uint8_t _Level)( J, `) N( h; I9 f0 c9 y$ r
  10. {
    # I5 s8 e/ |) S# Z& b2 P9 G- @7 F
  11.     if (_Level == 0)
    ; x( H: a5 i' Q
  12.     {3 f+ X  E  w- Z3 a* U/ P
  13.         bsp_SpiBusEnter();   
    * A$ \7 i9 H+ V/ x3 @
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    9 f. Y2 i1 G  H6 a2 S+ U% L5 x. c
  15.         SF_CS_0();
    * B' C3 P% R9 W& z& w
  16.     }. G; A  U. r; {! [5 M6 {; @0 {
  17.     else
    / C0 w2 T- R9 \0 D
  18.     {        - j: |0 t: z* ?" ]8 ]. P
  19.         SF_CS_1();   
    8 t& M% f. g6 K3 k& M0 p
  20.         bsp_SpiBusExit();        2 o' e7 M5 o! n2 q
  21.     }
    $ i" v" j, G* Q7 a* p
  22. }
复制代码

7 u  T4 D$ _% |3 {  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。2 T+ L- [8 z2 P0 ?4 q7 L
  1. /* 串行Flash的片选GPIO端口, PD13  */
    # M8 a4 d" p1 d" z9 `* q: y) T
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()+ C  ^6 l& [. |! P
  3. #define SF_CS_GPIO                    GPIOD* ]" c7 [3 c) e3 v% l; {3 k9 g2 l
  4. #define SF_CS_PIN                    GPIO_PIN_13
    . I8 b+ N1 G# B9 M3 B7 @9 Z

  5. $ E) Z7 d- W0 r6 }4 R* e
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) # i7 S  a2 ^( b1 h/ D! o. T/ L
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码

$ H2 F& i+ i8 q$ C& r  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:" h1 _* W2 U0 }; S" b! ]: D
  1. /* 配置SRAM4的MPU属性为Non-cacheable */# C8 Q& a- p0 H
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    2 X8 ?! Q5 r* v  T( \* K) e
  3. MPU_InitStruct.BaseAddress      = 0x38000000;' C- R9 D: o! Q9 y& }
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;1 z7 E! W6 e8 {1 q8 p8 U5 M
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    , h. ]  r: y: ]# s- A! P
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    . P  [7 `; D) ]( L  e' V3 X; r& V$ }! D
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    2 n, n5 K5 K5 J
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;7 X/ [) d- a4 Y4 \/ l: y( N
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;/ \9 X1 d% V4 d! R8 q  Q& r( r
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    : g# Y# a& J, W/ @3 Y9 V1 z
  11. MPU_InitStruct.SubRegionDisable = 0x00;
    7 E" D! f* u7 P1 v! b9 u% T
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . K! w; Z1 x2 I3 b' I9 l0 L
  13. - g) i8 m& x, G% H; I% r6 d3 C* Y
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

- ?4 N4 F) v3 B  e" n  第8步:初始化SPI。+ c: G1 ]$ Y: A/ [8 y* @# U4 K; P
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    ! J8 W, E8 q- Z6 e( L0 {' S) c
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        . `( a1 C+ l! u
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码

4 y. [; p$ b+ E# r+ Z% f& H  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。& ~5 E. ?2 A$ C# b  Z- G- q# S
  第10步:应用方法看本章节配套例子即可。. J* @! I/ K- F9 ?" R/ p
73.8 实验例程设计框架
: L7 M& b. |$ ]" e' B% q  M1 e通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:  ?$ h$ e6 V! n3 O9 t
+ _: E  c6 A+ O
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

# N0 G4 Z( c, J7 V; ]% _# ~* K& k. N+ q& Y$ w3 W
  第1阶段,上电启动阶段:
! u2 D7 a# ^6 S1 b9 t( X$ d, E! t
$ d% T7 p/ r' S' e+ v6 d$ N/ }这部分在第14章进行了详细说明。
, T2 v8 D3 n- i3 u9 q/ S  ! W1 Y$ Q' M- Z& b2 c) u) I
第2阶段,进入main函数:
. ~, c+ A* y  V( L- E6 O$ X0 M. h7 |' J7 C
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。1 F+ {0 A8 Y- `. u2 p
第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
, S8 B5 u9 D( w  n) W* ^/ T8 v6 L9 h7 f# e4 G% l- {+ T2 f9 ?
73.9 实验例程说明(MDK)
9 f7 @, R# ^+ I: c  Z/ U配套例子:1 P7 I& L+ r9 w8 Y; \2 e3 s% i9 p
7 X' C  K8 |* M4 J( Y4 k& }9 x
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
$ _0 n' t6 [% P% o& h" U/ K1 t+ W0 t4 r+ M2 B6 p- ~7 J+ }% v* g1 @& e
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
# P- ]* f5 U6 ?1 T9 s; ?9 ^+ A% ?
V7-051_串行SPI Flash W25QXX读写例程(中断方式)8 q6 T5 m5 ?$ z; G

* g1 z/ U" g' ]* @- A5 G( |0 h实验目的:
- J) s( j$ K. b% T1 J) o* `& B
0 @2 A# S: Q! ^) O学习SPI Flash的读写实现,支持查询,中断和DMA方式。
6 j0 I% c; @! _% S9 I5 q. p9 K0 q3 a+ m* z2 U8 Z1 Y
实验操作:4 q: z/ W4 w# C6 ~& H

6 W8 v6 R- x+ i+ ]支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
. D6 U$ N2 r5 {printf("请选择操作命令:\r\n");
/ r4 R0 o$ L$ D- V5 C; p! xprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);  B! d( q1 _. ?  Z9 Z8 \' H
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);+ L% \3 ^; f" S2 i& i
printf("【3 - 擦除整个串行Flash】\r\n");
- \9 k6 q4 J# ^9 g2 L' a0 ^* C/ E( P2 Vprintf("【4 - 写整个串行Flash, 全0x55】\r\n");
' |7 W5 {" a1 ~  d3 _) b  gprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");. a* v! Y9 i8 I+ O7 m
printf("【Z - 读取前1K,地址自动减少】\r\n");( G9 N$ W4 Q8 A1 q7 ^2 ?
printf("【X - 读取后1K,地址自动增加】\r\n");9 o/ x" b- N7 ]- X! E' N
printf("其他任意键 - 显示命令提示\r\n");
5 c2 P  H0 f1 `" Y上电后串口打印的信息:* V& U$ Y; p! y* A; F1 U3 T
2 j, o0 L, S) N
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
& b. y8 V# f6 }/ C# T! X$ |
# S$ X# B. B3 }* ~% x! f1 x, Y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
. `; G, e' `  c" i0 [
* z2 f/ v  C4 ?7 r% I( @4 U2 y; G( J
程序设计:
$ m& d, J1 }/ O
: z6 ]/ b9 z& G2 T0 }2 k, [" m  系统栈大小分配:0 c, |( m9 Y5 B+ S; a

7 n; v+ M  R" `
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

6 P2 h' M. q& o' B: b, C5 M  Z; O( p% d: v% p
  RAM空间用的DTCM:
9 _4 B, k$ f( d, F, X7 q6 x1 i! \
5 f) z" i: O6 W1 N+ W6 p6 Q6 l! ?
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
& l; T$ V2 u4 j6 q3 _: {8 z0 l/ @
  T- y" B$ M$ T! [' `! {5 p
  硬件外设初始化+ u/ I8 U- j6 N% o$ T7 O9 ^

2 T, o: B+ d" p- j5 G" x& B硬件外设的初始化是在 bsp.c 文件实现:
' r9 u: J& }' y3 b
( k* r$ u- ^9 j9 W- y/ x9 }
  1. /*0 d* p, E! i# t1 N. _* O+ l3 S
  2. *********************************************************************************************************
    * S4 p( M: o2 z. u/ L7 C
  3. *    函 数 名: bsp_Init
    ( _  [/ w+ Y  d3 l0 H
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    ) A; j9 z% n+ o! F
  5. *    形    参:无
    5 G* D  g: s# {2 m
  6. *    返 回 值: 无
    - v- b7 g* ]. j* w, B7 t
  7. *********************************************************************************************************
    ; e3 |8 h% ^8 i: G8 ~
  8. */" R( [/ `; d9 i  e* Z7 Y
  9. void bsp_Init(void)
    # H+ @8 B4 c; [; o0 S
  10. {2 x0 r! K2 Q, w0 Q
  11.     /* 配置MPU */
    2 X. d: N; o$ j( \
  12.     MPU_Config();) Q4 N( [' U$ h5 Y
  13. - N6 e* L  L5 z2 w: P+ s: R
  14.     /* 使能L1 Cache */
    ; `8 c2 ?1 G0 v) W, @* V8 d" E$ I
  15.     CPU_CACHE_Enable();$ K8 A+ `) G' i& u! h
  16. & l- m/ `( u' F6 Z# ?* l, P
  17.     /*
    1 T, B2 T6 c$ I4 I  V
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    0 c, _" p5 n( K4 G6 S  e! S
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    + p" O" ~0 j' e+ D: R
  20.        - 设置NVIV优先级分组为4。! m9 z% x, l, A( g  R: f, {
  21.      */
    $ `$ V( o8 h# A" t. {
  22.     HAL_Init();
    & T' E: v* t- N8 |9 y9 T8 G

  23.   Y& B6 B/ t+ \  J
  24.     /* 3 W, T, r+ T9 J5 `  L
  25.        配置系统时钟到400MHz
    ) R4 g: O8 D1 [4 E
  26.        - 切换使用HSE。
    : Q$ Z+ \! N2 k) B/ ?7 l" J  _
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。) I# X, ~3 G9 f2 |$ x# g
  28.     */) k) e: o6 n! \2 X, S
  29.     SystemClock_Config();9 X/ y( k( ^: w, M6 p
  30. % U5 g6 s6 W+ J
  31.     /* ) R8 ~% p* {! d( f6 O* }# _+ T
  32.        Event Recorder:5 b! I4 n/ v4 m2 T( ]4 j! p
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    . R- Y- _" o( a* W# W: e
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    ) f; r* @  a$ Q/ G- v9 f( g
  35.     */    1 E  n$ k: m$ M6 ?& Y9 X/ Y& e6 a3 b$ a
  36. #if Enable_EventRecorder == 1  6 p7 k; L. B. e7 Q: B3 \, s
  37.     /* 初始化EventRecorder并开启 */
    6 K: y3 `7 F0 A4 G; C
  38.     EventRecorderInitialize(EventRecordAll, 1U);# T" n, C* l4 |3 {& X3 i1 s
  39.     EventRecorderStart();$ p$ h* }8 |* B8 j& p4 u& X
  40. #endif: u; e7 j1 r; M+ p  n4 `* z

  41. , E$ I: l" @2 |3 b/ t9 X
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       ) B) k8 F0 d7 b) {
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    $ t7 k- @) x( }$ ]; j, n
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */6 T8 n7 B+ e" r  o. u
  45.     bsp_InitLPUart();     /* 初始化串口 */2 o4 ~1 t5 X" W9 t! \% ]  C5 Y
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ' \+ `* C3 h0 G. m
  47.     bsp_InitLed();         /* 初始化LED */    ) L. I  f, N- d, d0 i
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    $ ?4 D0 d+ v3 U$ g# R( \

  49. # Y8 q% f* N3 R3 ~6 l
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    ( O) b  u; K$ W; o# I) ~. O
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        1 S5 E) o* f9 h" r) s
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */* }# [8 V+ Y. g
  53. }
复制代码
. o5 H$ w5 U9 Z) f; X& |- X2 z
  MPU配置和Cache配置:0 V4 e$ O$ c) X1 U. {0 a8 F/ Q9 v
: |8 K6 @5 Y, H& `  V" {( ~' [
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
# S9 a! |7 x. F
# A, E2 k2 l7 e- ]7 j7 Y
  1. /*
    , Q6 r" [. B, j: {5 e2 ?1 w
  2. *********************************************************************************************************) M. z2 K7 ^# C# w# f1 k; T- p. @; W5 y
  3. *    函 数 名: MPU_Config
    3 x. |& D& v- {. W% f6 a9 h
  4. *    功能说明: 配置MPU
    , X- l( ^: s+ B% w  |( ^( T: |
  5. *    形    参: 无
    / [% ~0 [- l2 l7 @; p* `6 i- ?
  6. *    返 回 值: 无4 h* e* R8 a. h- b- c+ P
  7. *********************************************************************************************************
    * J" V7 _  R, V* @: o/ G8 _, {; N
  8. */4 Z+ C3 X+ C$ k- m- j1 ]* P3 l
  9. static void MPU_Config( void )& t4 I# @0 U2 T. P
  10. {+ m$ e8 s# p0 O0 f1 `3 d' d
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    - G. s0 }8 W/ T' s" |
  12. 5 B# N3 P  m0 m: [" c, E
  13.     /* 禁止 MPU */' d0 c7 n/ r$ n5 W( H) O' J
  14.     HAL_MPU_Disable();
    # j$ h; P; h% q6 f6 z5 E

  15. 4 W1 b' k0 _2 @) `: u
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */+ d, Q0 x; s4 ?/ x/ r
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;, a$ ?9 b0 m  Y1 w% o1 x$ ?% L. v. `
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    + n; b5 J9 b8 L/ g3 F
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    / ~! M4 u% L. x0 J- p3 t0 w
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
      \# J3 b+ Q" v# Z
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;9 c9 T4 Y8 R1 W9 J
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    $ H0 d3 j" g% E: N7 f: Z1 n1 F
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;) }- R' k* G( G& t. S4 p0 S
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;* {# O0 [/ ~: D4 r
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    9 b, g% g/ E, h0 p% S$ V% ~  _
  26.     MPU_InitStruct.SubRegionDisable = 0x00;# ~- G0 |+ R: Y' n7 s
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;" [$ {* z+ o! n6 Y: J' Z

  28. 5 R- I1 |# G- Y6 j/ u  N1 A! ~1 n- N
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);  A# p, @8 e  O
  30. ( j5 I- l0 C) R: p# c$ Y$ S

  31. 3 t% i3 N* y& c; i3 G
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
      d+ X. B2 n9 T
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;. J8 H& f( I4 z( S, G3 E0 V
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;( h; j5 \4 |4 l4 `& t
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    3 ~6 h% `5 }% V3 O8 Y
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
      s3 F7 F+ ?  l  W( }+ y9 G1 Y
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;- V# \4 `) c" l! G0 l" N# P5 e
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    9 K4 ?5 L$ i0 I7 M6 m
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    9 B( H1 E- n% U6 X# P! u6 y
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;, e1 v* x" m5 X$ t
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;2 a( z% s' b; i
  42.     MPU_InitStruct.SubRegionDisable = 0x00;$ `2 S2 v/ @6 T' X$ W1 s" o# }2 b- ?
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    " D/ Z, \1 m! I8 S  S" n
  44. 5 u4 S; f( W$ u8 J3 t0 M3 G7 z
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);7 k1 n2 d9 T! l8 V% @

  46. 0 d- Q3 q' u: \! I
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */. R! Q4 U' u4 @( q, E% O
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    $ n+ ]) U" h( m
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    0 A% Q3 G3 J. a. b
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    - M7 q6 b: E0 y; ?. c7 l* d
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;  `) ]6 M' F* N# l! |$ E
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;5 Y3 ~. `. w; L: P% g* Y
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    * ^/ B4 \* @4 ^  |( F( a
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;+ w- b1 g% T9 ~& A6 S  o$ t
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    1 G: j& E9 B8 L! y
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    . r4 y1 A- w% z) V+ g9 f( j
  57.     MPU_InitStruct.SubRegionDisable = 0x00;- ]4 {( l, ?% @' F5 e4 D
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;7 j: h5 D4 f- t
  59. & f" ~1 {- j. l# m
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);* w9 r2 t0 ]1 x0 J
  61. 0 l; H3 J- O6 L! {, ~) J1 l# p/ l
  62.     /*使能 MPU */  n- U: u3 I+ R
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    ! I: j$ U' o% w  x9 W1 \
  64. }1 |/ p: H' o0 e3 Y
  65. 5 y8 f& {7 Y# U6 e% s4 o+ \" g! B
  66. /*
    ( V4 A5 K. I, `0 Q0 J
  67. *********************************************************************************************************
    + n) S' F8 R9 i7 n4 s9 X& p$ p" ~
  68. *    函 数 名: CPU_CACHE_Enable
    ) A; K; x  ], @
  69. *    功能说明: 使能L1 Cache, \: R9 Y+ L& o4 G( {
  70. *    形    参: 无% k9 ]0 M; R) ]$ b7 d8 R1 h, E
  71. *    返 回 值: 无
      a6 U- F' V, |7 U6 ^
  72. *********************************************************************************************************
    ( H# z" p7 v# R6 A( B  i5 y: C7 l& v
  73. */  Z' Z% F+ ^' m6 `) U. `: ?/ S( r! }
  74. static void CPU_CACHE_Enable(void)- r) ?! x) p2 X3 E8 g# L, y4 E
  75. {, q# Y  x# ]3 O, P: V
  76.     /* 使能 I-Cache */
    . e/ w: i3 O6 z; b9 `; N
  77.     SCB_EnableICache();0 P4 R$ X5 n; b3 ]

  78. 5 R0 |3 R' X' l7 z
  79.     /* 使能 D-Cache */
    $ @& C; H$ N" M
  80.     SCB_EnableDCache();2 {7 M3 C/ [  n6 f6 ?
  81. }
复制代码

4 Z) {4 j+ Q& l" p3 n6 z  每10ms调用一次按键处理:! t" t7 n% Z8 m* X" K" a0 m# U

4 i) _& b& O( L- s. \: w9 ^按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
4 l0 n: E/ ~. U1 s; k8 ?' w% E  f& ^6 s6 C6 h  Z
  1. /*/ \- {+ s7 N! z
  2. *********************************************************************************************************: `8 s0 ]! I. k: s( {2 @
  3. *    函 数 名: bsp_RunPer10ms7 [3 u1 V( I7 e' i
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求+ B6 \" ~6 E# b( A+ J
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    6 L3 F  w+ G9 B( C9 F
  6. *    形    参: 无/ f) i! ~. v; h2 e" [
  7. *    返 回 值: 无6 w. S9 Q& i8 V
  8. *********************************************************************************************************. D, ]9 V! g! c* W
  9. */
    $ D: @2 K) ?9 o" @* G+ W& p9 R3 ~3 N, Z
  10. void bsp_RunPer10ms(void)
    ! h; O. W( U$ z# p: k! E9 c
  11. {; O8 V4 O  N- E4 |. a9 L, Z1 s8 b
  12.     bsp_KeyScan10ms();
    , {* \7 J1 J! Q% u! J" O
  13. }; i$ E! o, Y/ @
  14. # O" b) \7 D# m1 |4 Z
复制代码
1 z% _( T( G+ X
  主功能:4 D/ ]$ V2 |0 H" w4 w7 \' {
  C2 P" S% T: h. {3 v& r  Z
主程序实现如下操作:
9 v: _% E- m  e7 u( c$ |5 p' z% C/ S  r
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
  V- |& J1 o% s: s5 Y  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可" ^, l. x3 \' ~* }% v
  请选择操作命令:
8 J4 b7 S# r( \  1 - 读串行Flash$ ?3 P) }8 f' \1 k
  2 - 写串行Flash
  g& e  _* y9 s8 U6 s8 T( d  3 - 擦除整个串行Flash
( r6 J- D& ?' \/ d2 |4 Q/ F  4 - 写整个串行Flash" F8 k) r2 Z6 @" Q' s3 {1 A
  5 - 读整个串行Flash0 \; I2 k1 S* o- m+ R: z  f
  Z - 读取前1K
/ V+ M! C3 i& [( b5 s* g$ n  \  X - 读取后1K
* U) e9 d5 o0 I7 }( y$ A5 a9 ^
  1. /*4 n  r3 [5 M0 [: H
  2. *********************************************************************************************************! z+ _6 _; E: A1 f& Q
  3. *    函 数 名: DemoSpiFlash( R- n) s4 E4 N0 S
  4. *    功能说明: 串行EEPROM读写例程# p* W6 I$ X) S  ~6 q% h
  5. *    形    参:无: P4 |4 g1 F) w$ Z. F  c
  6. *    返 回 值: 无
    ) C- I8 z' N* ~$ J! j7 s7 s, {& [
  7. *********************************************************************************************************
    ; G$ h( u8 `( [" n* Q7 K+ j8 l
  8. */, z6 P# Z7 s. {) f5 |5 }
  9. void DemoSpiFlash(void): T( ~2 d6 Q8 t3 g2 ^: B
  10. {
    7 K5 o7 A) W8 q: u$ O
  11.     uint8_t cmd;
    ' u3 I; S$ F/ f
  12.     uint32_t uiReadPageNo = 0;
    7 s2 l+ O, g0 S7 c3 G

  13. 6 u( i9 P- G( x8 A/ m, Y
  14. : L  D* d- ]8 D
  15.     /* 检测串行Flash OK */
    - S5 M0 b- g- |) B6 l; |
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);) _  G  {& U  `
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    7 X( q, B5 f7 z7 t  E

  18. ( H$ i' m) e! H/ [8 g" N# @
  19.     sfDispMenu();        /* 打印命令提示 */
    - g: @4 P* U1 @0 K2 l: r7 c6 N

  20. 5 h# N& y6 d( y6 J, _7 |3 n. |
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    ) l$ {' P8 J% S& I5 F, P) z; E

  22. 0 Z; j, w5 U& \+ j1 a
  23.     while(1)( o/ _- ~3 T5 |
  24.     {
    ; f- V8 C# N$ V2 r
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    3 u* s0 B- q- i0 W0 B9 j8 T* j! @
  26. 1 T  b( G9 |3 B2 ]2 ?
  27.         /* 判断定时器超时时间 */* P" M0 Z0 D% o* `
  28.         if (bsp_CheckTimer(0))   
      }) v3 b: S3 C( p/ ~9 g. x& P2 `- [
  29.         {6 ~0 j; W( X8 \" a# a
  30.             /* 每隔100ms 进来一次 */  : e9 y, x, Y! C/ L
  31.             bsp_LedToggle(2);
    9 b$ n0 q8 g: w, G2 g( s/ D& O
  32.         }
    " M) b2 @  c4 n' ~4 i
  33. + U5 r: B5 l0 L1 p
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */. W) S7 F8 M; o1 N" Y' f
  35.         {4 l  N, m, n) Q' ^5 }) N
  36.             switch (cmd)8 [; y1 S0 C6 e
  37.             {$ Z# M" |4 r  R7 U* D" ~2 b
  38.                 case '1':1 _' X& t8 r1 q8 V4 L+ V8 s9 C9 t, f. p  g
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    7 f+ M  _2 E! r5 L
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    - W8 T0 ~) J. l5 W
  41.                     break;
    * l4 `) N' j. c

  42. 8 I8 R$ \: R* E
  43.                 case '2':
    3 J. D  z7 d7 t5 `' C
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);) p/ C* J  X1 x% N2 ?
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */! r/ L" E1 u; `- R$ Y6 B. M- t
  46.                     break;+ i6 H( O) w4 s3 \* m. J  G

  47. # e- |8 W5 m7 W% q5 I6 q6 P
  48.                 case '3':
    ' D$ M0 s  }+ Z2 v" h1 S
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");7 W1 X; N# o' p0 j; e) H' Z
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    $ F. ?: Q: x) B) N  v1 Z& R$ L
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */' P+ \5 f1 C2 p, p
  52.                     break;
      S/ h" z# M- ~: g8 Q

  53. & S2 J3 h8 t; x- X
  54.                 case '4':1 U6 t  \  Z$ H8 G  ]6 r/ z! F
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    4 j' P: ^3 T4 k1 J) ~
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    , U& O& M4 U0 G9 m. X
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */, }( I/ t- p/ \* i3 R) B
  58.                     break;
    7 n1 H0 P% _" E  s! m* v, e

  59. ) n  \& j7 g* k
  60.                 case '5':
    7 M% h% x( E; v! C8 n
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));$ x( k  E) a! d+ }8 D, M
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    2 \  ~- `  T5 [/ v0 M- k/ E  G
  63.                     break;
    ' F; t" E4 _# x# C

  64. ' T9 E. J+ \' W2 J
  65.                 case 'z':
    5 \/ q# [6 K9 a8 z3 L' k
  66.                 case 'Z': /* 读取前1K */) l: V, Q2 g" @# D# a5 @* b0 u, v( L& U
  67.                     if (uiReadPageNo > 0)
    7 L9 o5 {6 t9 \; K8 e
  68.                     {
    , M1 M' K# N5 r0 a/ f
  69.                         uiReadPageNo--;0 X8 e3 J6 `2 J8 X! r! a; A3 O
  70.                     }' ?0 h, X3 W) Y
  71.                     else( h7 h$ O- |$ [. }# q, a9 T
  72.                     {
    . b: W1 g# m5 i& r5 E
  73.                         printf("已经是最前\r\n");; ]% t, I3 @6 L; R
  74.                     }
    * x  s% I: y! X
  75.                     sfViewData(uiReadPageNo * 1024);
    5 S9 p4 H2 f5 Y6 `/ S$ m8 [4 W
  76.                     break;
    / `" U4 E9 b; B; x+ M! i/ ~
  77. & P5 e8 S: I( X/ @9 s6 j, }+ w
  78.                 case 'x':
    4 @% A5 F: c; U" D1 ^8 T
  79.                 case 'X': /* 读取后1K */
    # t( v& \- s+ Z! Y1 x
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    ) p' {: s9 P5 k; v5 f" G
  81.                     {0 l. f$ O5 G% p2 H; [) W8 I' t
  82.                         uiReadPageNo++;9 o% l' Z9 Q; r: B' X
  83.                     }7 k( n0 \/ `+ ~, f; X6 V
  84.                     else- d7 `; s/ t  l5 s* x# h! R
  85.                     {
    2 u* W( J' c6 X
  86.                         printf("已经是最后\r\n");
    6 @% _# u) m/ H& k
  87.                     }
    / u5 `; f8 n+ L8 c! m% ^: e* r
  88.                     sfViewData(uiReadPageNo * 1024);
    3 I' k2 m; O3 Y# R, O! _/ S
  89.                     break;9 U, X8 k9 X- S* Q3 _# V' Y" W( y) C

  90. / a4 y6 M$ A) ?" Q" o5 Y
  91.                 default:
    ( Q$ X. j+ o, m" a, J7 `4 V5 E
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */. f( w+ w4 _- O, e6 `0 h' V
  93.                     break;) d" E; @, |1 J& g% N

  94. 9 t0 }; l8 R# r% |$ B/ b
  95.             }
    / N; w; d7 H: M- [
  96.         }& `, \: p3 N6 ~" P
  97.     }
    6 v, N2 D9 p+ g. H& \  ~' H
  98. }
复制代码

, h9 D. n: e# e4 L% U73.10          实验例程说明(IAR)
5 U$ }8 [+ f: M* `
配套例子:
1 b/ ^- ^, |8 V
5 X& l0 t8 Z9 A: K+ h4 NV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)  G$ Z- U9 k8 X3 y% p# T

. E& d  y& r5 p6 m/ {V7-050_串行SPI Flash W25QXX读写例程(DMA方式)) I, G( e$ o* w( u5 {
) r) _; V  Z5 y, n2 |
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
6 F4 U7 E% u, e) T
  S! g0 @, i( I( ~$ v  N, m& v/ l实验目的:- @: R* Z) j$ U/ S: _

2 O5 n) y" D2 }8 ]/ I( Z9 b& A学习SPI Flash的读写实现,支持查询,中断和DMA方式。
8 F" o( W/ I8 h7 H! g
8 `( z! v# Y. P( X6 f6 n实验操作:8 d; d/ _, E8 N, k6 p. L( a

2 }) T. W) @5 M" m支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
7 w( f1 y  t9 g0 L2 @. `& f. X/ hprintf("请选择操作命令:\r\n");! y5 ?# T- L) P- U8 e
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  p7 V2 w, C. W* ?% Oprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  `. I5 P7 t) S; @: @printf("【3 - 擦除整个串行Flash】\r\n");5 p2 p5 ~0 T: I: n. `, G
printf("【4 - 写整个串行Flash, 全0x55】\r\n");0 B: [0 `' V: i1 f, }- I/ ^8 o  n7 C. a
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");9 L% f" F" \' F: b' ?
printf("【Z - 读取前1K,地址自动减少】\r\n");( \! h) h- [" x# I5 q# _9 Q2 f
printf("【X - 读取后1K,地址自动增加】\r\n");- t9 T2 r' p! \6 `
printf("其他任意键 - 显示命令提示\r\n");
, Z! \2 v6 ]! }; A8 ]
4 f- i; G6 p% ]( b; [+ q8 Q9 k上电后串口打印的信息:6 E9 S1 J1 N$ k& t8 Q
. E3 E/ E1 V6 v4 M: R" f
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
# x/ ]4 K6 [8 H/ \5 w9 v2 a" x5 k7 `  j4 r
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
; u7 d. F/ S$ e& Y' V
, A( K% K2 H5 ]7 Z/ C" y/ V8 k% [
程序设计:
7 X$ t/ l$ U6 t! ?% J4 `! p
6 _' t- Q2 o7 S: j. ?1 k3 o. q; F) T  系统栈大小分配:
2 N: a# y" Z  J" M
+ y2 D. a, Q8 ]$ ?( g
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
) a7 e+ P4 ^, T2 L6 k

5 J% U# M. o! I, ?/ Q  RAM空间用的DTCM:
) t- N- ~' {, V3 r: Z5 g; Q3 K" D/ Z1 w. h
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
! M4 M% P+ \* f
3 e# r2 |! o: Q! S
  硬件外设初始化. z/ w) U! X+ y& `2 w

+ X5 k6 b2 T3 [7 `( j2 e4 }5 x硬件外设的初始化是在 bsp.c 文件实现:) K4 ]! ^! M2 B. n, w  F7 \

. A; L8 S6 ?, p0 K4 h7 c- O8 I
  1. /*
    , v! @- X1 X) X3 s0 w
  2. *********************************************************************************************************
    . z$ @9 ]+ c) S+ o8 u* b
  3. *    函 数 名: bsp_Init
    $ A5 L2 p' a$ _* g* P8 Q2 r
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    ' Q! f9 [  b6 k& u( O. r7 V
  5. *    形    参:无
    ( T- o+ \9 y& z& Q) p$ `6 i
  6. *    返 回 值: 无
    9 J6 d% [7 B& W) x$ u
  7. *********************************************************************************************************# z, a7 S7 L$ B1 z4 b
  8. */4 c7 g" [) i/ u  o3 D  j) @3 D
  9. void bsp_Init(void)* J! B) L, a3 L) X4 T8 ]
  10. {
    , F2 e. ~* Y* Z8 {* S# T
  11.     /* 配置MPU */
    8 |  \2 ?$ q( k6 _$ C: ]4 }
  12.     MPU_Config();' l  u2 Y2 F( Z- n% z" M4 M! I

  13. , S" X2 Z; c- T0 @) k$ C, Z: I
  14.     /* 使能L1 Cache */5 P( _/ |7 n6 u! S
  15.     CPU_CACHE_Enable();9 q( g, _% B: Z$ b8 b% b
  16. 1 W2 e+ m1 a: p: \
  17.     /* 4 I0 r: X4 M: Q( X+ d. J
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:: g# y; }& z- f' c8 I9 y
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。& T# o9 G! S, ]4 x5 a" |( `
  20.        - 设置NVIV优先级分组为4。) ]2 ]$ `$ r  Z* D! M
  21.      */
    # {2 R( a* h' P9 @# k
  22.     HAL_Init();
    ! N4 I* v& {  x* p3 k8 w, ?- @
  23. ( e) U3 [5 \! f0 ~% l
  24.     /*
      }, x) N( V0 v7 n
  25.        配置系统时钟到400MHz
    1 i$ d9 k1 d6 U; v8 q/ [
  26.        - 切换使用HSE。* y, w! p+ |4 k+ S8 ?
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。/ f# i! C; l& |) K$ n6 @
  28.     */! H+ g) B# }" w1 P7 w$ G
  29.     SystemClock_Config();$ e1 d/ c+ O& E+ B% G* B
  30. ! f0 @" M% R, k# T3 N
  31.     /*
    5 ?/ F. v# N) K/ o/ H
  32.        Event Recorder:
    2 m; s5 y  Z+ o; |/ Q  T& V
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    ' ~3 F6 m% m, e( b' o/ H
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章3 D. Z9 Z. S5 `7 l( v& h
  35.     */    ) r, I( L5 Q# z6 F. L, [
  36. #if Enable_EventRecorder == 1  " ]6 Y8 M  U' L4 X
  37.     /* 初始化EventRecorder并开启 */
    * B3 B2 n8 T8 U6 d4 @$ }" e1 _
  38.     EventRecorderInitialize(EventRecordAll, 1U);$ ~, H: D; r, a6 l# |8 z+ W
  39.     EventRecorderStart();
    6 L3 t  d2 f  H
  40. #endif) a) C7 W2 l! d4 L3 y5 x
  41. 2 w4 l7 U& B! w4 Z6 W: Q
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       ! g7 N/ d# {# o2 P1 H1 e( |8 ~
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    2 b, i" _3 H5 X* z4 Y9 I
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */" J4 t5 H2 P: H' k- ]% }
  45.     bsp_InitLPUart();    /* 初始化串口 */
    / d0 Y7 h* R! ]; |
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ; X5 }) o% ^6 _  P- Z. J
  47.     bsp_InitLed();        /* 初始化LED */    & y! t" J4 v, W* i
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    ( S0 |( y' j" \- G* f: j1 |
  49. 3 q) z8 s* q+ u) z; s, F
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 *// p/ y! ?1 w9 ]! L1 Z' ~) W& R
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    " U# W  X! c, A$ m$ T) Z  y# g
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    : Y5 F# X/ e. N# {! j: t
  53. }
复制代码

' I% w; x1 Y/ c* q  MPU配置和Cache配置:3 |- v# I- z5 F0 d; a) u
, |& @/ t  o& c9 {8 H; g% \2 t
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。% B- N, K& `$ i2 m/ A

/ x7 t5 z0 p' l% _
  1. /*+ N: E' S$ k9 F4 h  U9 V
  2. *********************************************************************************************************
    : d* O0 e0 U4 `1 g
  3. *    函 数 名: MPU_Config" ]3 R1 }3 @; _5 G4 z
  4. *    功能说明: 配置MPU7 J6 U0 d0 ~9 ?3 o  ^
  5. *    形    参: 无
    / s% R5 C; F; P( N4 u
  6. *    返 回 值: 无
    % y* x1 H4 r  L9 W* P
  7. *********************************************************************************************************1 q- z" [" _1 v% J
  8. */. P# h6 o9 d8 s8 h; Y" |4 g
  9. static void MPU_Config( void )
    * `/ y; J! f  t& k' S
  10. {
    . }! B$ `# }8 T; O, @% E5 Q) C. K
  11.     MPU_Region_InitTypeDef MPU_InitStruct;- r% Q/ w/ o: q5 H9 c7 L/ T' r4 @  x

  12. 8 Y1 Q+ l8 `: F6 M% n& ^
  13.     /* 禁止 MPU */# w$ Y* {2 S3 c$ T* P+ C  z
  14.     HAL_MPU_Disable();& e1 F/ _' R5 k& f' @' ~

  15. 1 w3 k- o7 {6 X- U
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */8 K5 f3 t! }# U
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    9 j! N% r5 d$ x2 W' x
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;( ^" h" z2 C2 V5 e1 u, o
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;) P& D7 o# }0 A" D1 u
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% j8 `, ?$ y1 q1 l* Q3 j4 _( C! i
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;6 A: S* R, N9 T" ^+ d* @
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;* ~+ b  U1 S( x, l5 `
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;5 L' E+ ]) P' s8 w( [* q
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;/ N, h' c+ z6 O7 k
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    % `* _  |1 M/ k  I$ @
  26.     MPU_InitStruct.SubRegionDisable = 0x00;- y/ s/ V5 r' ]) Q$ x7 `
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;# w% @3 A9 ]; H  P+ ~7 @
  28. ( b& i1 _) x8 q3 i6 F
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);7 m& U0 ]4 m4 D
  30. 7 g& I; \& I/ R' I
  31. " W& K8 T* R+ [% W8 T
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */! B" _. j0 r; Y
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ) j. v4 ^1 o- x
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    : m, n5 C4 c5 I: Z4 C1 m/ R6 w5 ]
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    8 }1 T6 n- ^5 Y4 C0 D$ Z5 Y* l
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;; Y4 n0 E( T6 W3 E# }
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;" I5 V: d" W0 H  y5 V" E" s
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    4 T# q0 E& i7 Q" V/ K
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ; J. ^! }. q; I" `" h" b
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;- C6 R$ j1 r+ d% Q. j
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    ) B* Q, j: K" B0 H3 i- x- F
  42.     MPU_InitStruct.SubRegionDisable = 0x00;  ~* X( n8 f+ ~1 F! p
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;* f/ L7 p+ U/ B9 b" J0 `# Y8 A8 O

  44. % @! ^  I! k1 [
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    + ]/ I* a1 o2 O" y7 J
  46. , @+ L* l3 n3 X. A
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */8 ?  _5 U# @# d1 ?3 r9 W8 M
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;% K/ e$ e+ N. {0 O0 W
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;. S* B  Y# h) [0 ]+ W4 j; V
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    & Q' g, l5 \4 v  ~6 w- q0 p% V
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% J7 l7 G/ X( {
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
      _0 @% h9 d  y: G: s  ~, e7 {: K5 w
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ; A! K0 g# z' o5 r6 ?
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;& G8 l7 s/ t# L& q2 j) _
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    - ?3 r8 s- l! {( N9 G2 s
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;: F& c; u0 \+ v( L& b( F) [
  57.     MPU_InitStruct.SubRegionDisable = 0x00;! f9 R6 \' @3 T/ ?. Y/ R. z
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    : H! ~# Y& G! {% g

  59. " L' n; h5 W8 N9 c  I* g& U/ e
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);" O6 F( O& I0 E) g' F; R  P& J
  61. # u# h3 R/ Q" `- L
  62.     /*使能 MPU */
    6 n# ]# L+ t9 v" Z" w1 O  r# Z+ x/ M) Q
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    0 r$ K& [  e* i
  64. }5 o" P9 c0 q4 ^

  65. , j* C2 g. K, U! S2 [7 a
  66. /*) a& E+ w; N. q1 X* U
  67. *********************************************************************************************************
    8 P, b& ?. G2 k- I
  68. *    函 数 名: CPU_CACHE_Enable( J4 J5 J$ d" e# o1 E: ~& ?' @/ C
  69. *    功能说明: 使能L1 Cache
    4 p5 j, m1 h# j
  70. *    形    参: 无' [6 M6 H# v, M, U7 E$ O$ Y
  71. *    返 回 值: 无- t: r: {* ~/ |  f
  72. *********************************************************************************************************
    . k) T' f) O% T  A1 P+ o
  73. */, m' G" R) P7 y$ Y9 H
  74. static void CPU_CACHE_Enable(void)3 F. q0 y" ]5 b$ P, V
  75. {
    $ o% _# m  o4 M
  76.     /* 使能 I-Cache */
    . p* Q6 D( }8 F0 ^# c- i0 q4 f; F$ s
  77.     SCB_EnableICache();: B) x- Z6 d: N5 i) B  a9 @

  78. : ~" O4 U: a: X8 w/ r( a
  79.     /* 使能 D-Cache */
    1 G. o0 ?3 `* U# _
  80.     SCB_EnableDCache();' ^( M6 ~) s! x& v4 W- K, \' }* r
  81. }5 D+ h1 w# N* C! s/ ~
复制代码

6 f4 c: \% n! g: W7 o# Y; p5 Z6 V; q* S
  每10ms调用一次按键处理:. a+ y' P1 E/ P# ~& H  O: m3 Y1 B: ~
8 t$ f/ l' [  l$ `  j9 j) s: i+ _# v
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。/ T/ I. `; u( G' H6 [, J* v! S6 R% ]
8 @, R/ _; x& ^& t# P" N: O
  1. /*
    * o- X# n5 _- a: D' t* Q( }( H
  2. *********************************************************************************************************
    - @7 \( U. ^, ?  c
  3. *    函 数 名: bsp_RunPer10ms
    + ^1 x& D# Y  p( r. |2 g2 S- ^* K
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    # s/ C7 w7 x: G* A2 h) l' ~$ R. N9 |$ l% [8 ^
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    ) e: s7 k$ ~7 W; R
  6. *    形    参: 无
    & r! H# `7 ~* B
  7. *    返 回 值: 无
    0 l8 C& D6 B/ d. B1 l6 t7 f3 N
  8. *********************************************************************************************************
    / m2 h0 d+ ]8 r) t; {% ?/ k+ `
  9. */
    0 V- V7 f: P! i+ x. y
  10. void bsp_RunPer10ms(void)
    2 r7 q' f( @7 Z& x6 s" N3 z
  11. {
    3 \' s" ~! w# F# s! ~
  12.     bsp_KeyScan10ms();9 M- w* O2 n' X( a; f( ^
  13. }
复制代码

$ c( e% y7 X& ~, W  主功能:6 `! }2 ]0 u  H

6 Z4 _+ T4 W) u: ~9 h0 k& ~主程序实现如下操作:
( `- r4 L- w+ P  v2 p+ b
1 ?0 Z0 A% z5 [# }  启动一个自动重装软件定时器,每100ms翻转一次LED2。5 \! W4 J, s, B: N; k
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可9 Z9 ]$ B* h0 |5 {- y; D
  请选择操作命令:
( U/ X7 j3 k0 f1 L7 K- X7 ]  1 - 读串行Flash6 K; k$ Z0 H1 z2 W' K
  2 - 写串行Flash$ R: Z0 e7 J( b$ d( }
  3 - 擦除整个串行Flash6 [1 K6 B7 {6 q: b/ X
  4 - 写整个串行Flash, f. m% |% D7 X- ]8 ^" |9 I
  5 - 读整个串行Flash
. q* z. z; c: ~  Z - 读取前1K
1 w. _4 i7 T- q5 Z3 ~% Y+ U# ^0 W( I  X - 读取后1K- g( D2 b* [8 z
  1. /*6 N2 A' K! q* w$ a
  2. *********************************************************************************************************
    ; y" j& f9 j4 U1 _: |6 U/ D
  3. *    函 数 名: DemoSpiFlash- e" n$ `6 F7 S+ A
  4. *    功能说明: 串行EEPROM读写例程+ _3 x* s" i1 p9 R5 u+ H2 t+ {
  5. *    形    参:无1 J! p% N; u- r1 P$ J
  6. *    返 回 值: 无6 p* f- @0 I# F: t7 k9 `
  7. *********************************************************************************************************
    0 M# x8 n& j3 |9 W& p
  8. */
    * G$ R0 a& _5 p+ M+ m, O2 F2 T
  9. void DemoSpiFlash(void)3 a* K  w- Z0 _! @1 _- _. I
  10. {
      E7 n  P% Z$ H& c
  11.     uint8_t cmd;% K% u( T% r0 u# I- a
  12.     uint32_t uiReadPageNo = 0;  k+ v9 G2 H) l8 F3 V* ]  X5 `

  13. , L9 j) v" M7 G" ^* t5 ?
  14. 9 k; `( B# l1 f: ~7 W
  15.     /* 检测串行Flash OK */" ?/ v: B% C2 I! b. |
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);* t2 D/ L' B  U% k3 ~& m
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);& E9 n7 h; _% T! c

  18. 7 K( d7 r$ M3 T* e" [. v/ s
  19.     sfDispMenu();        /* 打印命令提示 */
    * r* U+ j# f# }) O2 \  n

  20. + k& p4 F' Z8 Q7 W( B# T
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */( L  m/ u  y% O8 m; R: G* m
  22. " b& r; u, J2 |' H2 `
  23.     while(1); P( ^' i1 s" G& m
  24.     {: W3 G7 \/ t7 [$ L" D5 M
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    2 |( U+ K: g" s' g6 s5 b+ f2 C3 S
  26. ! h. D& \- g7 f
  27.         /* 判断定时器超时时间 */- z; t* }% X. m
  28.         if (bsp_CheckTimer(0))    ! m) C6 a7 J$ c! }2 f
  29.         {
    ) D# S6 o/ f. h4 g
  30.             /* 每隔100ms 进来一次 */  
    6 o* _# h5 Z- Z6 ]# k  v
  31.             bsp_LedToggle(2);8 y: d& y8 ?9 n* z1 Z: i
  32.         }
    / B, Y0 x6 `0 F2 ]( X. Z- q9 F
  33. 8 L0 w( r3 f) z! M7 ~2 F
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    8 w" i9 p7 o8 @
  35.         {/ N% ^+ m% e3 f2 R
  36.             switch (cmd)
    3 v0 [% {% e" N8 D) N. f  A
  37.             {$ `# L+ x, o" T* I% X" v5 [) S
  38.                 case '1':5 u* _5 ], [4 q1 B
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);/ Q8 O& m5 ~" }0 T
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
      ~2 \3 g; H3 j3 H- R5 B
  41.                     break;
    + a, e0 V( Z4 F6 h" \

  42. 1 ^1 g. h: w5 {6 ^$ \6 ]9 F
  43.                 case '2':; B. k8 @& e, g5 D9 f
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);2 N9 [9 M# v( x: C3 m+ B
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */1 H$ v5 ?3 J. A. H5 ~+ z: d
  46.                     break;0 ^* z. a9 {, r5 V

  47. % l1 S0 _, S0 e4 O2 d
  48.                 case '3':$ F1 w- H# J! G3 e3 ?# x
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    ; B2 U1 G8 o( {. a/ f& I
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");" u) ~' h' }: d0 q0 ]
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */& J! H( t2 M" e" D" H. ?7 K9 M
  52.                     break;# ?7 V9 R% N6 x$ }; f

  53. * E1 n& h, g) A% e5 S! Z# [2 L" W
  54.                 case '4':+ d3 \: E" e- u; Y8 K$ f7 ]
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    4 P" z& E; ]( ^" w. F
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    4 _' T, Q7 z0 v3 S$ Z
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */* r' c' Y% H8 A- R5 L, D* y- F; ~
  58.                     break;" z- |( j7 f' w6 p) ?
  59. . y, O. o7 m2 b% I
  60.                 case '5':
    ( _2 M5 V; {' g" t! A0 a) ], _- Y
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));7 E$ k. p+ i! I* e: Q( e
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    ! Z+ h! i- P1 |5 z7 W2 o7 ~# q+ r
  63.                     break;
    2 y# R! x) T6 [6 h4 _1 ^% N
  64. 5 K$ @+ g% y" F, ~
  65.                 case 'z':0 T; l- R, `' ?+ e+ ]
  66.                 case 'Z': /* 读取前1K */
    . x3 L4 \" o/ d
  67.                     if (uiReadPageNo > 0)
    % S3 C1 R! b+ ~3 w3 B1 T
  68.                     {
    ( q- p) T  j8 u$ o( x
  69.                         uiReadPageNo--;) I5 `& ]  ~2 z/ h
  70.                     }
    5 E$ B( P! X* U0 x( K2 M2 _& p
  71.                     else; q. {' n0 z- A$ d8 T
  72.                     {7 _, A5 {2 V3 `/ e7 V  q
  73.                         printf("已经是最前\r\n");* L) x% Z+ P& R. V$ A
  74.                     }- O% s+ M$ P0 L9 X
  75.                     sfViewData(uiReadPageNo * 1024);
    9 H# N  U& a3 z  C& g7 y! ~
  76.                     break;
    6 [% i+ R3 ?; v' S" t

  77. 4 r% j9 p- Q. G
  78.                 case 'x':
    / [7 `* L) X) Y, a9 O
  79.                 case 'X': /* 读取后1K */
    ' a0 L. b. [  B
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    2 {' W$ M5 o) |$ }% Y, R
  81.                     {
    / V: R, `) L! U7 t
  82.                         uiReadPageNo++;) V$ k$ V6 H0 p( t( V
  83.                     }: y5 J  F1 g) O$ k+ e, C1 }/ K" K; P
  84.                     else
    6 ?: |; w9 _: y1 v
  85.                     {
    3 Q" ?3 D3 U: R  L, J* j
  86.                         printf("已经是最后\r\n");
    $ v  }1 i% q/ K' z9 ?+ \3 C
  87.                     }
    ; g4 R3 V  {) C/ Q# d; J+ j
  88.                     sfViewData(uiReadPageNo * 1024);
    + A( E1 ~2 K  m1 I  e
  89.                     break;
    2 j) z3 ~% C5 D/ v
  90. % V$ b6 f4 Q+ Z# P3 J! |
  91.                 default:
    7 N7 l# _. H: ^! [
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */( M5 A# n# D: H& y- c. m" S
  93.                     break;
    : j+ L6 z. H2 C2 X" |) ^

  94.   Y+ h& E/ O' F7 V  c* l- M+ d
  95.             }
    6 W5 l! x: e* K( q+ h* D
  96.         }
    3 b% `; u- W9 [0 s: |7 O( w
  97.     }
    7 x" z2 R1 Y: l& v9 [( v1 t7 l/ h6 n, t
  98. }
复制代码
  N* c. l" d. d4 E
73.11   总结
* L- t! f7 _7 O+ X# n' d  R本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。" C, e" U) T  n  Z9 _: s6 ?
2 t5 @( y' ~+ Q

" l* h! _3 T4 k$ }7 d# Z. N; G
+ w, l1 G$ Y% b
收藏 评论0 发布时间:2022-1-1 21:00

举报

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