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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示. L# x% ~/ O. B% `; M# C
  W25Q64FV属于NOR型Flash存储芯片。* g& ^& g7 J; D3 G  {( n
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。2 |5 }. f3 G4 U# G
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
5 G9 X$ [5 p6 X3 y0 W) Q

: R2 z, G  R+ E4 K  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。7 @# {1 d" h3 X% c
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。, R3 `" y- A) c, C  z
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。) f, Y1 C1 w! A, C/ m
  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
& A% I2 y4 g. E& B: H  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。; t9 N5 w( B& F2 G, y! y1 Q
" Z, b" W( Z: K$ ^
73.2 W25QXX硬件设计
9 `( S, R2 z* d' C( C/ ?3 ?STM32H7驱动W25QXX的硬件设计如下:
( X' o6 w, r% j$ R% {9 b
  T9 G% U! c6 D
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
+ ?; R# d+ y; R
8 i7 r  d& o0 A! S& @
关于这个原理图,要了解到以下几个知识:; ], `! m! I6 _+ B9 i

0 j8 J' a6 g! T3 k: r5 \9 x( b# I- F  V7开发板实际外接的芯片是W25Q64JV。
2 K  b% {2 J5 \( D2 f' v  CS片选最好接上拉电阻,防止意外操作。
8 m+ Z4 |5 Y; Z+ T  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。
5 @6 R5 b$ \: Q4 n  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
# Q+ e7 L  z; X3 u3 _$ I  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
! U5 m% X+ g& ]6 m, q, H9 t& i4 P73.3 W25QXX关键知识点整理(重要), A" }# w2 Z8 V1 @  r" D
驱动W25QXX前要先了解下这个芯片的相关信息。4 w4 _1 b, ~8 g9 h

' Y- t8 Z5 n+ R
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

. a$ D/ j: `' _! G; g( \$ b
4 @% L. ^' A, N6 P73.3.1 W25QXX基础信息
6 n6 K& }3 }5 K2 R& B
  W25Q64FV的容量是8MB(256Mbit)。
" p& \  t& t( y, _9 r' o  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。/ ]+ |" |  x. j# E9 c" V
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
& p# Y9 F* f. ?8 m  s, ?4 }$ z" X7 ^4 Y  ?7 m) U
支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。% l6 @8 ?) _: `: o0 \' N

* W* d- d' w  G8 w(注:这里几线的意思是几个数据线)。
- a$ ^/ H3 O+ E# \; k8 v  K. l2 P! p' Y+ p. n0 J! }- D, U
  W25Q64FV支持的最高时钟是133MHz。
; Z5 |. S( j, h3 @  每个扇区最少支持10万次擦写,可以保存20年数据。( e- B' I  N5 {4 o
  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。3 [5 z9 g! N4 ~2 @' B1 U, y
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。1 t3 M! A# L, [  t7 @3 {
整体框图如下:
7 Y2 a! K7 t* N8 n7 `
3 ?4 d9 H$ ^: B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

4 w0 ]1 ?+ V4 H/ r
( W3 g) c5 _* t4 ~- M3 MW25Q64FV:, ?' _" d5 X. \# Z
3 a* V0 r/ W3 G) @1 B
  有128个Block,每个Block大小64KB。
: }7 J1 }8 w. f: \  O  每个Block有16个Sector,每个Sector大小4KB。: C* G) W' g7 {- ^* g
  每个Sector有16个Page,每个Page大小是256字节。5 d' H! @! w) b8 g( y/ P
73.3.2 W25QXX命令6 E3 u  g8 }( h# X2 p" }% x* ~
使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:) ]. I8 D  r/ y
; M2 e, _+ q+ @* Q2 m# V. S6 X
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  i( H5 R  D) w+ S
; ]- n3 U, P, F6 X+ m
当前主要用到如下几个命令:
, l! H, f4 l$ h( o+ o+ b. [6 Z2 g' Q$ B! C6 D3 [, [9 q
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */: P0 W- h/ l# T) A% |' u9 ^+ P) R4 D5 T0 h
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */% E( n$ {. m8 E# F: |0 p( F/ S
  3. #define CMD_WREN      0x06    /* 写使能命令 */5 x: Q# P3 O, k" f
  4. #define CMD_READ      0x03  /* 读数据区命令 */: G" P! j4 n$ H1 x. X( ^  K
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */
    3 F5 G8 l( a+ t8 E, c9 N! b
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */
    3 U0 J1 Y9 v! O7 o' ^
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */
    ! J( j3 @9 _. |# c9 X$ i! k
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */$ Z5 ^& T, N# P% G0 f
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
/ o; N7 X% n4 B5 L
73.3.3 W25QXX页编程和页回卷
( Q( I1 V" A/ b" o% d% XSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
1 B! E5 ]8 N# s6 ^* l
! s* c& D1 D3 F% ~0 Q
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        ' z# y5 B. r  @. \# p8 a" X
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    . F& ~# |4 s0 u( h* X
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    : d( B" c: I! y4 [6 W. k
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    - v* Z6 h% Y9 R" D* A
  5. : a6 Z, ^* r% W9 F$ R0 M
  6.     for (i = 0; i < _usSize; i++)' j) b5 |# R( n- B) y
  7.     {
    * p- p; p$ x% ~( q# {2 @
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,9 P2 [5 D! }' ?# v
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   ' H- a! D8 w( f! i  F
  10.     }
复制代码
7 [1 o( _  Y- c/ a+ P' r
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。5 f3 G; U" `2 ^

- o2 [8 N0 `( h% O! R( j8 d5 v- e* V比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
5 F. B$ a% n, U' ?1 y6 f( U% j, K# f2 D; a/ q
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};2 K( T/ N2 j" G& [# G: Y
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

( ^6 N" V  j+ e0 B  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
, o) f$ ^" M8 m( _9 E7 x) u3 [. p  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);5 w# Q- j* e, h, Q. A
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);
, V: z; l, ?  {9 f! q  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
2 S3 O% T! k: h$ e下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
: w4 u, }3 D% h* k' f, \3 V- F
3 F# G. |$ a$ u
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
$ u# T  U: _. ~0 \8 J# I

9 l$ X" C- Q" ?3 V) ~
1 b7 U- z+ Y6 |; l. \73.3.4 W25QXX扇区擦除/ F# h" n) l$ q( g1 t& Z& y! ^
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
0 S. n- K( ~% J$ r' j4 q9 g! @) u* C$ _( S. g8 t, I& Z
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
' X, k* ]7 z/ B3 R1 T. H& C  E- I3 I; q- V& {- ^
73.3.5 W25QXX规格参数
& n  z7 e: B! T4 @1 |3 ], `! j
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
# J% _) _, `8 N+ C1 d8 D2 R# i( c( G* ?" z& k/ ^" Y" K4 ]8 R9 M
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
5 i- B& d; Z0 ~, I; i4 l

  i1 u- i- D4 V: ?  页编程时间:典型值0.4ms,最大值3ms。, M4 k+ D6 B! u# C0 x
  扇区擦除时间(4KB):典型值45ms,最大值400ms。
' E0 t& R" u; S+ }5 o/ x- Q4 X  块擦除时间(32KB):典型值120ms,最大值1600ms。
9 b5 k& q8 {4 H1 I. O1 D! y  n: i  块擦除时间(64KB):典型值150ms,最大值2000ms。2 G; w) J( l9 ^- G2 ^$ ~
  整个芯片擦除时间:典型值20s,最大值100s。
3 m: x3 r, o9 u  S
( _+ I2 T& \& I3 Y. e. K, ~! \
% a  e' {7 A2 x/ l8 O0 W- H支持的速度参数如下:: E) R6 n, @+ U7 c8 l/ B# ^8 M
4 S( v# w# F' P, d" ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

$ ^" r* R" w7 u/ q, s3 {3 L
) B4 f9 n, N; v! F% A5 o可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。1 C* k% K8 ]: T1 E' ]

; ?' d6 c2 D* q5 `& b5 y3 X9 Q4 }9 s0 q73.4 W25QXX驱动设计
9 {& u# ^( Z8 I. XW25QXX的程序驱动框架设计如下:
3 `0 ^- N% I& ]5 R/ I* A) ]4 b9 {9 A( K5 f7 m4 y5 t5 U' t
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 F8 d' x! W$ @4 t4 M; V" o% b
: c# w0 i* V7 g  u  C3 v有了这个框图,程序设计就比较好理解了。
! V/ ]2 g, L2 A* l* \& V
. s& }% r9 @6 G73.4.1 第1步:SPI总线配置5 I) r9 m. F6 `6 P
spi总线配置通过如下两个函数实现:
# M+ R8 d& S6 A1 o) F2 L3 @9 R! H: S: [9 V7 n# `$ L( I0 G. y% J
  1. /*
    9 ~2 W% B2 T' e/ Y! C
  2. *********************************************************************************************************
    ( i' z5 [4 h& `* l) S  p! c
  3. *    函 数 名: bsp_InitSPIBus
    ' }/ d# d: R  I* `5 b/ f" r
  4. *    功能说明: 配置SPI总线。
    : j7 ], M: i9 w; C/ o7 `0 I
  5. *    形    参: 无
    % C2 ?3 K  q* C6 {2 N
  6. *    返 回 值: 无* n7 {1 W6 T. w6 B& \. s
  7. *********************************************************************************************************% R/ k# o; o5 s) N/ }9 L
  8. */! f' A* v. M0 X5 T8 }; o: o* n
  9. void bsp_InitSPIBus(void)
    9 `) k+ G) t( O8 m' u; `
  10. {    # W# I" S, \; ]6 Z2 i. I$ n
  11.     g_spi_busy = 0;4 ~, H2 a* M: c1 q# Z' r1 O
  12. , Q" l8 W: m" d: M& S$ i
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    * @/ F8 |9 c- e
  14. }
    2 s& S6 u- v5 f( l' P6 C
  15. # K7 F" h6 x: e6 W: F) G$ r
  16. /*5 r. }" [$ R& K
  17. *********************************************************************************************************! ?( j/ W) N9 b
  18. *    函 数 名: bsp_InitSPIParam0 p# O3 |. ?  S% `; ]
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    $ y/ A2 B" D% n% v6 _% g
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:6 f& X. w6 N. e) S) z' p8 ]
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频
    ( g; t% Q% V! y# q& ~* s$ ?8 |, ~
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频8 Y+ k" h# F2 U  u0 A1 z
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频
    / W1 v4 L6 m  p. D( t: N
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频6 I3 ]4 a' v! e5 {! c* C( A8 `
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频
    # G; v9 Z9 o# }; m. ^  W* D( }
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频6 Q4 F& a0 \- F7 i
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频+ J6 N; F: `" j  R( l
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频
    ( ^7 X! e$ L# W* L
  29. *                                                        
    " ^4 M9 o- K& Z3 o7 _* I) [
  30. *             _CLKPhase           时钟相位,支持的参数如下:' Z& X5 n# i7 E& w2 B9 \
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
    4 g  J2 G  E  Z0 B+ ^( F
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据/ o5 T8 O# \# H. C/ F: Y- ^2 ]
  33. *                                 / {% }, Y" w6 D/ }$ i1 z7 M4 T
  34. *             _CLKPolarity        时钟极性,支持的参数如下:
    8 f) r% \* B; b* |! b$ k
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平# J) r: w, v; O7 w1 G
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    : {% g4 j" C: I4 Z
  37. *
    2 u+ C! O  _" m* S
  38. *    返 回 值: 无8 I' d& C: `- F: B/ V+ O
  39. *********************************************************************************************************
      N9 N- p) o% A" M) t
  40. */
    0 m* L+ c* b. Y% M2 M$ ~/ A
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)4 h1 c: o7 j) ?1 p  H
  42. {/ m, R6 o7 f% \2 l% c9 Z
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    * j; z) ]# R4 {# S: |% |' f
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)- p4 K9 n) ?- N
  45.     {        ; w: b  s0 D3 L
  46.         return;; m+ {0 S. {* N$ a
  47.     }
    8 Z* Q  {% c7 m$ n  o5 r" @0 M( T- k

  48. $ W8 ?! ^2 b+ k9 p
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;    2 f' l6 d& ?# p9 e0 o
  50.     s_CLKPhase = _CLKPhase;6 K* G8 V- @: {6 U
  51.     s_CLKPolarity = _CLKPolarity;8 {" a8 R* Q/ j+ U. Y

  52. : u% H" J# P" V+ ]; M: r! U

  53. 2 x' U/ C7 a' [) K
  54.     /* 设置SPI参数 */
      b8 S2 M% r/ m
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */( e6 H) ~1 v4 l. O+ p# {+ ^4 g
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */0 G  {: r3 I( P# W5 ]) h% t
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */5 g7 l  C7 S+ c5 {4 _
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */; O' @! C5 k# z; V) ?% D$ a# e* N" I
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */: t, \) I& A9 H5 e0 d/ s
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
    ( b5 I, N0 U7 w9 Y. ^5 t' g! Y
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */$ R3 Y9 Y" y9 a  w
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */  n! w/ a/ d# t- v) W- z
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */) X( s+ V3 K: k" |: j# c4 Z
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
    , \/ L3 C* ?" S4 {- o3 j3 z! K
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */! h, k2 [6 o. P
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
    & A- m7 V6 N% K+ ~( [2 |  a
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    5 L( M, Y6 a" ]# d5 `3 N: U
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    6 y! T1 C- L- Z) ^
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  5 J9 z1 H/ A9 f, W
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
      z( r% R* u5 y2 e
  71. 1 w  h5 F% n' v
  72.     /* 复位配置 */: t2 k6 F. M. A
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    9 L5 I+ C( p+ _. y5 J% @% d! i" b
  74.     {
    / {+ c6 n/ m& b( R1 ~) `
  75.         Error_Handler(__FILE__, __LINE__);
    ' i7 x* f2 r" h  h, v5 l
  76.     }   
    # f5 [! G% x% X  V3 b
  77. ( A1 Z/ D$ r5 w$ J, s% m  _& Y
  78.     /* 初始化配置 */
    ' L; j6 E2 q9 g9 q! x! j
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)0 g& k5 \, R4 x- ]4 P
  80.     {% g; J0 X0 ^# p9 n: h. B
  81.         Error_Handler(__FILE__, __LINE__);$ J8 U! K! O; h; z+ T, G+ z6 U* }
  82.     }   
    , g. ]0 `! Z& o% t8 A1 e
  83. }
复制代码
7 F# L' g8 `3 [7 \! S* [) N/ m
关于这两个函数有以下两点要做个说明:* I! [( _) N6 C0 p3 r
( L, R. a3 q) m3 T, E# o! x$ b
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
! w) Z! M0 }9 Z' Y3 @  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
6 w5 o/ n  i3 u, K, a73.4.2 第2步:SPI总线的查询,中断和DMA方式设置
# b" R$ n2 A/ C# N6 @SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
6 Q, z8 n/ z( a6 t
* K1 Y) Q: I, g  O! p2 ?
  1. /*
    + |8 v- g, ~" k
  2. *********************************************************************************************************
    & I3 v+ [; D& e2 B# v3 o+ K0 N/ ]
  3. *                                 选择DMA,中断或者查询方式
    $ @& r$ ^2 i! a1 [4 i' k
  4. *********************************************************************************************************: p6 o# e3 J/ V# [/ G! t/ B
  5. */
    6 N5 J; K! U2 R8 x1 b4 T
  6. //#define USE_SPI_DMA    /* DMA方式  */
    * H# t6 l! w+ A1 Z" i: x
  7. //#define USE_SPI_INT    /* 中断方式 */+ T/ H$ m) J, X3 L9 g$ E
  8. #define USE_SPI_POLL   /* 查询方式 */
    : @- H) h7 s7 n6 N, g

  9. + o, |- Z  Z& B% ^! D- D
  10. /* 查询模式 */
    , a2 ~% p; h, F
  11. #if defined (USE_SPI_POLL)# u: [! F- @; f; W' Y. v
  12. $ R0 ?5 x; g* |; `% q
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    8 r) e( P' h$ d; o1 l$ \% f
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];; |) X3 C0 S) ?  a/ ^& F
  15. 7 e3 d/ E$ d! f, p
  16. /* 中断模式 */
    ! p- p4 S0 b) o4 k; I
  17. #elif defined (USE_SPI_INT)" x7 @( u* B" O1 I
  18. , o1 N  p/ u! `5 U8 W0 t
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   1 j4 t  v  g0 ]8 p
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];1 _9 W' k: |- p! m" F

  21. % j+ Z& P6 g! y5 p; j7 G( \
  22. /* DMA模式使用的SRAM4 */
    2 h, x( _2 ?+ d0 G" S
  23. #elif defined (USE_SPI_DMA)
    9 Z* Y* w0 r& ]& P- U
  24.     #if defined ( __CC_ARM )    /* IAR *******/5 G% M4 N5 r! M9 }+ k+ D
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   4 A; I0 }5 b  A& `+ p. d# |  ?0 Q
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];9 H( N3 n$ ]% ?- t
  27.     #elif defined (__ICCARM__)   /* MDK ********/0 L' g# m# {. n$ i1 O; `
  28.         #pragma location = ".RAM_D3"& E. Z# P7 L6 o% F% L  B% J
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    $ \% }; Q9 a  x
  30.         #pragma location = ".RAM_D3"
    ' O7 v- M  |" @% H# X% u' J
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];. K3 }6 I, O: d
  32.     #endif
    : |8 ^$ X6 G! L  V6 Z
  33. #endif
    - V" x% j5 L' }6 M; \6 p1 B

  34. " ?. @  N! K% ]! E% z
  35. /*
      ]- n% ]. z0 D3 @8 X1 V
  36. *********************************************************************************************************
    6 f! u' [# M' ?2 u4 w
  37. *    函 数 名: bsp_spiTransfer
    5 q1 t' f# _0 U! p& F; u
  38. *    功能说明: 启动数据传输6 b+ o" F: U# w2 s( G& _% y6 B, Z
  39. *    形    参: 无
    ' [& }: ?( O% {- Q- f2 o5 ~
  40. *    返 回 值: 无% j: s$ K. D5 @; Z; c" [2 O
  41. *********************************************************************************************************
    8 E/ s: X+ }0 A1 f. |
  42. */" Z5 d6 N) |% X$ B3 A& a
  43. void bsp_spiTransfer(void)+ R4 }2 j7 {( o$ g5 k9 f
  44. {; A5 d) _$ G- e9 {4 T: b( i
  45.     if (g_spiLen > SPI_BUFFER_SIZE)1 ]& Z3 n  \5 j1 Z
  46.     {+ f, I1 H  e" Y1 E
  47.         return;1 u! I- H' D+ h
  48.     }
    # |. N+ u; G, L
  49. $ e4 y- w: M" V
  50.     /* DMA方式传输 */
    - l3 {: u4 ]/ }% J
  51. #ifdef USE_SPI_DMA
    ; u- Z+ g: w$ m2 y) V4 L
  52.     wTransferState = TRANSFER_WAIT;
    0 T9 t8 \! C3 ]  m  Q7 ^3 \8 v1 W

  53. 1 O# h2 L2 R- G# u$ g
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    ; w7 T# b" D, F' E$ u
  55.     {
    & D1 u8 t. U: Z& o9 \% |8 i
  56.         Error_Handler(__FILE__, __LINE__);2 M1 C: P2 U2 ~4 }  j
  57.     }5 Y' j& n9 x/ V0 M$ g# q# M/ X

  58. " B& Q3 N5 `3 `/ _0 i- Y! h7 ~7 S
  59.     while (wTransferState == TRANSFER_WAIT)
    7 h7 ]) e% W- j
  60.     {
    5 u( X+ \1 o. Y8 P* e$ d4 Q
  61.         ;
    , d, l' q* }5 r% m1 H4 ]
  62.     }9 k6 v( ?8 D) {6 \9 m
  63. #endif
    ' P/ t( l0 z+ t3 \8 I

  64. ' r( E, U* O5 c2 @
  65.     /* 中断方式传输 */    " J8 S. k# p  q8 l, g
  66. #ifdef USE_SPI_INT
    4 p+ W: D% G7 g+ n$ D: p
  67.     wTransferState = TRANSFER_WAIT;
    % x1 h4 O. b( q* |: G: i* A/ v

  68. 2 C1 u; w' L' E* T5 E. y
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
    . G5 I) B7 J3 t- h
  70.     {
      }: r% j: v/ ?+ K
  71.         Error_Handler(__FILE__, __LINE__);
    ! a8 x$ B* J+ K6 x5 u
  72.     }0 |; [/ k( t: b9 F8 e& b! Q
  73. ) F# `; e8 O; M
  74.     while (wTransferState == TRANSFER_WAIT)5 X4 B9 @8 C0 L2 c, }# d* M
  75.     {
    : |7 X% a7 J2 j- K4 Y& E. f
  76.         ;2 P. |4 V* F! {* a8 K9 p& j$ e# c
  77.     }4 L7 b& h$ x; S: J( p: @
  78. #endif
    9 F, g0 j. P1 X) \, Z  X% n

  79. % a3 e& Z) [, V2 G# ?
  80.     /* 查询方式传输 */    ( X% e- V  _5 ~: n! k
  81. #ifdef USE_SPI_POLL
    $ k7 U3 ^4 ]. A1 R# L0 i* O, G
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)   
    . A; @1 {9 ^8 Z/ V- x
  83.     {* b. I! Y$ S( z  L! r# ~/ I
  84.         Error_Handler(__FILE__, __LINE__);& b$ }5 j% U( Z* y
  85.     }    ) Z2 d" b) }- W. ~2 x
  86. #endif
    8 R" L: r3 a) `0 @2 z
  87. }
    . x" M6 M8 U, v. l/ `5 [
复制代码

1 ^7 M1 g" \/ m/ p8 J通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:& ?; J. \* T6 {1 F) w! h! ^2 D
2 r& z- v5 a3 o0 H* b- M
  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
/ n( R- E" Y0 u, h  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
" V+ p+ f3 Y5 d+ y; {
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */6 {' `5 J8 |6 O1 @# j
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;6 \- i2 F! C( x( Z) i" ?: I
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;" b. X5 ~. S5 Y7 S; J6 a
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;: C: ]7 s$ x2 C8 U) C
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;: I. s/ t) W: ^. j2 y$ c  _- n
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;8 p( S) ^6 g. _
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    : _7 n/ f2 B6 t# E: E* s8 {
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;+ q- r( {: z* |9 `2 L0 f+ [
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    * k/ {8 Q* x2 j
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;6 w9 ?) d+ a6 y' @) O
  11.     MPU_InitStruct.SubRegionDisable = 0x00;3 o, u8 n2 e4 V
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    & O9 _4 p3 }" f. [1 S& c# Q! m- d

  13. 1 X. G& M+ `& G  x; z) g  W, p7 S
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

) G  Q- \0 e) T+ P3 p& j73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

. U7 D- f3 Q8 x" \% K首先回忆下STM32H7支持的4种时序配置。
9 H$ N8 h: D0 W( m3 d
& Y& D1 c# T7 b0 x& X9 _  当CPOL = 1, CPHA = 1时
8 J9 y1 A! @; ~& u  J- N
2 l" j) K8 q5 l" N0 dSCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
) r9 P: \) G2 j8 N: ^/ r
8 T+ Z0 `% g  e5 K7 p  当CPOL = 0, CPHA = 1时" P/ q# \( V6 v) M, k8 H. S
5 o& @3 z' {2 l4 M' B. h, I
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。% }' c) `  A- H! l& q
7 }& s' w1 _1 i. Y- }0 G9 u+ s  y
  当CPOL = 1, CPHA = 0时
" i3 w. {# Q. L7 x% ^; U% M3 |4 T( L& p) K% I% {( M6 s) N  O
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。) E1 W- j7 l: U/ l0 C8 O, H+ a/ a
+ b" b$ q* Y  ^2 F5 w) D( @( k
当CPOL = 1, CPHA = 0时
# b8 ~4 x5 r& p! [0 y  q% N0 N
6 ]$ p5 r3 ]8 T- ]; Z' bSCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。$ \2 s' ~% b& M) a. l

% r7 H* A! V4 ?: w! [
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 ^# i. G! ]* f5 [! h
4 c: d! q2 _+ Z$ R5 g
有了H7支持的时序配置,再来看下W25Q的时序图:
) i4 C) J: g' R3 F  y1 c9 L7 @5 A' U9 f8 C2 ]
Mode0 : 空闲状态的sck是低电平。, |" h! w, Z. H

% z4 l7 `& X+ M( _& OMode1 : 空闲状态的sck是高电平。
2 R3 E, |4 d1 g' A" D$ i& z+ }3 D8 {3 \( r! `( t
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
$ j( w% j9 d/ q; f5 |1 F

: t  T. p9 O9 o* S8 N首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:
9 A8 m( F0 R6 d( K: {8 }, k- s& @, |% h
CHOL = 1,  CPHA = 1' o. A7 \5 Q$ ?* R% A5 ~2 ^  ?
( ?/ c1 u/ q. I2 t; E6 Z8 z
CHOL = 0,  CPHA = 0
6 j/ F6 ]( `. ?% z( `. E
5 K3 p7 Z2 x, t( i% B对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。; t) l4 x, g1 m7 V5 P; [5 Y/ j% x( [

! S4 L/ r: Z( X$ Z; @73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制
1 R2 ]* m+ k/ p& N5 X7 ]2 t单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:! ]; w" l3 F+ |

. c; ]0 |* r3 S5 r
  1. /*7 K. [2 A' }+ a3 Q# C
  2. *********************************************************************************************************
    1 c- C' h  a, c$ ^6 z4 k! H
  3. *    函 数 名: sf_SetCS
    8 {: u" e& Z) G+ c% A
  4. *    功能说明: 串行FALSH片选控制函数: @, U4 ~0 f$ _2 E+ E
  5. *    形    参: 无; R' R- r# \, P5 ~3 f6 n
  6. *    返 回 值: 无
    0 Y3 G+ V3 O% d4 q: {$ W
  7. *********************************************************************************************************. c, u* N- i3 R+ e, ]7 c4 s
  8. */! c8 W# g2 r5 Q! v
  9. void sf_SetCS(uint8_t _Level), L( ^7 }0 S( I, {, H0 o( B; O1 F
  10. {
    7 F' Y- J6 U( k# T
  11.     if (_Level == 0)
    ( v, m' b* \4 c3 ]5 J0 k- n
  12.     {" P+ _4 y9 }; U, }' o
  13.         bsp_SpiBusEnter();    5 s* o% X4 A; Q* |7 k
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        8 O  o; v+ C- W
  15.         SF_CS_0();2 W) \$ R5 ^% x0 b( R. I) N% d
  16.     }$ _% J0 B( r! g
  17.     else4 _8 e' J- k% W8 j7 O: J3 Q
  18.     {        6 ^/ u9 P# R1 S4 o2 u; N
  19.         SF_CS_1();   
    . r/ F! i/ B6 s; T1 Z
  20.         bsp_SpiBusExit();        
    - n# u4 h/ o1 w+ R
  21.     }
    / e) \5 ^1 N8 b* V
  22. }1 e& f/ J& @8 h) t7 ?% S7 p. {$ S5 I+ o
复制代码
7 q& O1 t' r6 V! d8 V
3 D; g: g1 D8 g1 x& a7 y2 t
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。' V* |0 v; s4 i/ [

0 O6 I9 X( {% H( v" H$ F6 s7 U但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。6 w& K- W: F# L& y7 O; ]4 J2 c

& t6 z9 i; |( Q6 B( b) h# @% [73.4.5 第5步:W25QXX的读取实现

3 G+ Y- p2 M7 G8 T" i9 SW25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。0 c2 e9 B5 \1 o- v, F+ V1 j, m
- K- s, ^- r# v3 H% t! Z) E
  1. /*% a4 P* |+ e# n" }
  2. *********************************************************************************************************
    5 r5 Y2 M4 V* ?
  3. *    函 数 名: sf_ReadBuffer5 R) b( K+ b% K& f* g* a& W
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    , \2 y9 P& {6 P" q/ U
  5. *    形    参:      _pBuf : 数据源缓冲区;
    $ S+ P6 u# a  _6 C, m
  6. *                _uiReadAddr :首地址8 a4 z0 ~7 V5 Z# t5 @9 e
  7. *                _usSize :数据个数, 不能超出芯片总容量
    7 S. T6 ?8 U* B6 q. J2 ?4 I; O' g- K
  8. *    返 回 值: 无
      Q* m+ n' z$ a; r0 t! y& a7 t+ m
  9. *********************************************************************************************************
      J& C# X  w. n# @3 X2 ~+ F- j, p3 j
  10. */
    8 m; n: V- v& _# r
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    - K# S$ j  N4 v7 j/ R
  12. {
    9 O2 X9 ^! f& N* }; e
  13.     uint16_t rem;
    & z& h9 V  W3 F  j
  14.     uint16_t i;
    & P# Y8 k- {7 G2 b
  15. 2 R' A3 N& P. ]+ X" t8 G
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    / Q* ]' z% W& R: A+ A0 Y7 u  w9 h
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    ' j; q8 U3 Y" i4 {5 I
  18.     {4 h7 G0 ]0 O. l) D$ ]0 \: j
  19.         return;3 y+ S3 w% E% Q4 x: a/ J: K% A9 g& r
  20.     }
    . f4 x( R4 N% s8 |/ ^
  21. 7 v6 R5 h# c/ ]. e  J, R! l
  22.     /* 擦除扇区操作 */
    ( U* o) f1 M* L3 o
  23.     sf_SetCS(0);                                    /* 使能片选 */5 S* }% i, z: v6 r
  24.     g_spiLen = 0;3 A$ O- x! n2 B6 g
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */3 ?& B* c: P* r' `% I
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    ) d! B# p% x: f
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */1 A0 V& L7 [8 T: U0 l( {, {
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
    6 P: }8 m5 F% ^6 k
  29.     bsp_spiTransfer();
    / K& ?; S( I& i% f6 w1 @; M$ _# x

  30. ) m' Z0 C9 H7 T. Q, M6 H
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
    8 P2 U' m3 f! F: S* X) z
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
    ; |, y- F# G  M  s
  33.     {
    8 F8 d- W+ k) l
  34.         g_spiLen = SPI_BUFFER_SIZE;* P8 e; b9 _% K( ]: {
  35.         bsp_spiTransfer();1 z4 T6 e9 ?( Q- C7 x: l

  36. ( B( i% }& ~, k: c$ |- F  j2 i
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    0 U2 g& H/ @% W1 S4 y
  38.         _pBuf += SPI_BUFFER_SIZE;
      r# C) i- N2 t2 M4 i1 D
  39.     }3 l1 @# r* c, P, H
  40. * N$ B$ s# A& H; w2 V
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */
    $ u( t0 [6 G; h% m: k, u( X" m
  42.     if (rem > 0)* M8 D0 ], M: ~4 d2 P9 m" I
  43.     {* w5 X; z) K3 p3 M( F0 e, \
  44.         g_spiLen = rem;+ L7 n& l, }; L6 \
  45.         bsp_spiTransfer();: l3 q1 p2 W' R- E7 ~8 O9 W) Q+ U( h% g

  46. : }6 ~! W& c& s# \6 L( a" S
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    * ?0 Z) b3 F7 M+ v" e9 i7 Q
  48.     }
    * e: t1 o6 F2 z/ B" I& R' [

  49. & X9 X- S) u, P
  50.     sf_SetCS(1);                                    /* 禁能片选 */
    2 c0 |- k3 ]7 W' w/ l3 h. x
  51. }
复制代码

  Z. s) t/ n+ r$ o  Z$ f
* i4 A4 N* O) q- c7 W/ N. y, H/ s这个函数对DMA传输做了特别处理,方便分包进行。
: [& Z3 Y. P$ q* D& ^" w
& x9 u: `" ?: b! A0 O; C' a73.4.6 第6步:W25QXX的扇区擦除实现
2 C$ }; ^. R0 R+ `& F6 T9 s+ }2 y' r
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。
6 P- M; c7 `. j
" X& w6 G! f; ~1 N/ e
  1. /*: w7 ?0 @3 K9 Q! g% q; q
  2. *********************************************************************************************************
    ) D0 S$ _8 ~& h$ _; M6 s
  3. *    函 数 名: sf_EraseSector0 C* Q* ?; {. s6 @* \
  4. *    功能说明: 擦除指定的扇区
    ) y: o8 q) o4 g& q* U8 Z6 o
  5. *    形    参: _uiSectorAddr : 扇区地址
    - ^4 t! B; T  v7 F: B
  6. *    返 回 值: 无2 ?+ Z) B/ d$ W& B
  7. *********************************************************************************************************9 [9 H3 W! K4 P7 ]
  8. */
    7 M7 m% d2 ]/ X* z) B) C
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    $ M4 }) `( ^" n* k- `. z- l
  10. {
    % X2 J* c7 J$ n9 F/ I7 u
  11.     sf_WriteEnable();                            /* 发送写使能命令 */
    ; u7 r) {) V. c& G$ g8 ~. p

  12. ; z/ D& f* w- P: ~6 b
  13.     /* 擦除扇区操作 */7 q' K* L; s2 V) [/ U% m. D
  14.     sf_SetCS(0);                                /* 使能片选 */
    1 L) D2 i$ X, a" W: O6 L
  15.     g_spiLen = 0;
      ^" |2 B6 a3 F# S9 m
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    : K! }8 h% X5 l* Y1 T
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */, z  V, _% c& d$ @' s: ?5 B: Y
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */
    . f$ R) [. t( _8 M; W
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */   
    , m; p6 {4 U* |1 m0 ^1 \1 P
  20.     bsp_spiTransfer();9 H$ ~( A- _0 y% R
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    ; k3 f/ w# `8 T6 c

  22. & C9 x  j3 c# D; Y% u4 h
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    8 f9 \( L- `% ^) n4 j: ]# a" Z
  24. }" R0 W2 b) A; ]( n8 a+ h7 ~
复制代码
0 {7 n& h7 k- y& o3 T5 |1 c: X

. Z  }, U% Q4 n) u: y整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:; E; G/ ?8 Q4 ~3 c
4 ]4 W& G( O' @, H, }: ]
  1. /*) p0 M: S# Z8 P2 n1 |1 I
  2. *********************************************************************************************************3 A3 ~) d9 y; h6 y! C: B, P
  3. *    函 数 名: sf_EraseChip
    ! h" T( S4 B" h! p$ g
  4. *    功能说明: 擦除整个芯片
    ( g; _5 G" K; t& |! d0 ], F
  5. *    形    参:  无) N. D" K* b" \
  6. *    返 回 值: 无7 q: ]6 M/ @. w7 Y& w0 U; X! U
  7. *********************************************************************************************************
    8 j. c5 H9 {+ H4 j' u- R4 u
  8. */
    : j0 ~3 w( B! g! f
  9. void sf_EraseChip(void)
    7 A4 L3 j- x1 c  O
  10. {   
    5 h  c) W* s5 O+ @6 e
  11.     sf_WriteEnable();                                /* 发送写使能命令 */; E3 ]1 U: Y4 N7 D5 a6 O' y' U

  12. 6 m  E" @8 |8 z& f, T& [4 c( g% w
  13.     /* 擦除扇区操作 */
    ( p4 \: Y+ y* C5 K0 }# i
  14.     sf_SetCS(0);        /* 使能片选 */8 K. m2 S- g' w  K9 l  V: n
  15.     g_spiLen = 0;) X  _+ W% ?- c4 v  F. d% q+ `) C
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */$ y6 ]% p2 i0 W# Z1 y
  17.     bsp_spiTransfer();
    $ `2 }( q) ?7 U& w9 i) M, r% P
  18.     sf_SetCS(1);                        /* 禁能片选 */5 C" E; U; l- }- Y

  19. + D' }/ k1 H( t6 C/ r& {! C+ X
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    ' i% A7 T& D# u( m6 ~/ _
  21. }4 t5 {' c5 M6 C( u, a
复制代码
2 G0 ]1 [6 t4 T( W3 [
73.4.7 第7步:W25QXX的编程实现
4 ]3 N% l* ^% u5 \4 M/ wW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:7 G$ X$ O2 k! s5 v4 o3 |

+ J3 }4 u/ K) {# p( P0 x
  1. /*
    / L& h5 @7 A) n# }- H, m( F& ~
  2. *********************************************************************************************************
    9 H% s( I. d8 }- S3 t2 M: y4 I
  3. *    函 数 名: sf_WriteBuffer
    ( ~" z$ r) @: b4 I" o, O) n
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
      a1 Z( b  W+ m+ E$ e$ t
  5. *    形    参:  _pBuf : 数据源缓冲区;6 i4 U8 S# c: F! d" D; @
  6. *               _uiWrAddr :目标区域首地址) _/ b$ w! P/ ^
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。
    1 C& h: b# b( w* d- R  n9 b* Y7 f
  8. *    返 回 值: 1 : 成功, 0 : 失败; R. \/ p  V3 e# g
  9. *********************************************************************************************************
    ) E- ]' l/ o: h: }$ Y5 l
  10. */5 I; {/ [, u3 p# {- L# M, {
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
    1 G( n" [' C- S5 X1 U3 t
  12. {& t- H" c1 R; H/ T5 p  d9 E4 c6 S
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    ; T" ?1 O+ s6 `) l: f
  14. + C+ |7 k4 V9 t' S% C- o6 e
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;& p# k& z$ s6 s' }/ X
  16.     count = g_tSF.SectorSize - Addr;3 i$ L9 U; \5 v7 m( l
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    , X' @& i6 y) H; a: ]7 A! r
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;4 H  K+ @) m7 X4 r# N
  19. ' r6 E5 i. }8 l
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */
    ! R( l3 ?: k3 V
  21.     {
    9 U2 Z7 e' T" K) n, z# r
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */& t/ \" {$ l7 T- a
  23.         {
    7 Q. O/ e( ], A0 u1 n
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
      w; U! R) k) c/ f* ]5 [
  25.             {8 r* b, B( M9 j
  26.                 return 0;9 x9 y; T' E, f/ f4 v/ e( R
  27.             }
    : g& Y) B/ O4 w( Z/ M% a9 P8 G7 Q3 P
  28.         }$ `- k& p  n, ~8 L5 j2 B8 G$ z
  29.         else     /* 数据长度大于等于扇区大小 */
    % W. C5 \5 r2 ?! L$ i; ^& Z' u5 `
  30.         {4 W6 B- p4 ~4 b
  31.             while (NumOfPage--). Z( Y# K% {! j9 ~8 e8 m
  32.             {
    9 i( l$ W# P2 u5 ?
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)8 k: i7 d6 G: @5 w- N
  34.                 {
    1 S; S1 f. k! b/ n% y: v, I" _
  35.                     return 0;/ m3 S2 h5 @. P3 G9 ~- S
  36.                 }
    , H; s$ G/ z! ?" \8 `9 Y
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;6 Y6 ~. o, f8 D/ H, e1 ?% l
  38.                 _pBuf += g_tSF.SectorSize;
    & |  k4 G: z. o6 E1 Y4 i2 s
  39.             }: ?& w: p) e/ J! h' Q
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    + u+ j- e" f- V, l2 P# ]; C9 [
  41.             {$ f2 U+ ~; T% u: H# N) {) G3 Z' P4 z
  42.                 return 0;# ]3 w* v2 Z: Y& T. L+ b
  43.             }5 m) Z1 V$ j, h$ a) R, i
  44.         }0 ?/ N6 o; o" T1 V9 k
  45.     }1 X* d2 [  F, b0 S& D3 y
  46.     else  /* 起始地址不是扇区首地址  */) V3 E6 K& Z2 D
  47.     {3 j* o! D( O7 r# D$ L( o
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */7 w1 a) w- u) W, b9 A' K* H; \
  49.         {
    ' p* I; s8 e; W/ z6 j6 o" C
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */1 E. t0 T# T5 R& x" ]
  51.             {
    9 F1 c* \% y/ l1 R, Y# j, A" C
  52.                 temp = NumOfSingle - count;. E, {3 [5 r7 A0 j& {

  53. ; ~4 w" W0 S' F
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    + j" L, x; G3 w  a- N  |  x
  55.                 {
    ' D# l: ?6 v& M
  56.                     return 0;4 S0 }  D% e7 X$ G+ l
  57.                 }. B1 [9 B  `( z* A0 q) F
  58. . y' j/ p" x4 b- F
  59.                 _uiWriteAddr +=  count;+ `( b. V+ B' z$ _$ M# R
  60.                 _pBuf += count;6 K, j( N  Y: J0 U! k

  61. 6 g4 S6 p+ W) Z# c% C0 y9 A3 D
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)
    . j2 W* f. ]9 ?" n# v" U. o2 b
  63.                 {. T5 l# ~- Z" X( i0 v% F
  64.                     return 0;+ x  c+ R/ B0 Y
  65.                 }0 K, o8 b2 u  y/ Q1 [( i- {) Q6 v
  66.             }
    5 |, y7 i+ U4 ], b
  67.             else
    2 a' d& z: ^  }) z/ J1 @. F
  68.             {
    / i7 w4 o: C6 v  S
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)6 K' W. _" o) u6 {: ?" g. K! B, C
  70.                 {
    8 v* }7 v! t9 u0 o2 }: y' }
  71.                     return 0;
    8 X3 a% F' k" i8 k- b: r
  72.                 }5 |" c% @& k  G6 V& D
  73.             }
    $ z' B/ W* v8 v5 T, s/ J
  74.         }- A! a) _: R( g. C2 I; n  N, s
  75.         else    /* 数据长度大于等于扇区大小 */+ Y3 V0 r# E9 }2 `: P' C/ Y: w
  76.         {
    3 z$ l/ ~! d9 X2 z6 d* b: w
  77.             _usWriteSize -= count;" G( x1 s% R( e  \+ c
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    % Q" a3 c/ v5 @% l! t
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    * B9 h5 W$ T1 {) a" V# P
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)9 F% K- O3 P. q
  81.             {
    ! F/ Y# F3 v; Y) o8 E- m: u
  82.                 return 0;
    1 o2 ?" z  m5 d
  83.             }# B* t/ l  D) t1 C
  84. + S( ~6 `/ l6 ~5 [3 {/ ]' e
  85.             _uiWriteAddr +=  count;
    1 |; X. w& s0 Z6 Q% t! S2 @, t
  86.             _pBuf += count;
    3 W) ?" ~% k# b0 U
  87. & g# u. e0 i3 c$ T5 i2 C3 f0 |
  88.             while (NumOfPage--)
    / s+ a; y! j+ {/ g9 B. v6 G5 e
  89.             {
    3 P- `! t; p% h0 W; A5 s3 v
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)5 z# x, f4 N# ^6 t+ f
  91.                 {
    ( X8 s7 o- o" S: U. a# p, |" S
  92.                     return 0;8 j: I2 z) J2 B
  93.                 }
    % W" s' w! u9 `; z4 C- G3 [
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;! d3 D% s- \6 U$ c% x
  95.                 _pBuf += g_tSF.SectorSize;/ s' \: V: q3 v+ ^
  96.             }7 O: {  F, S/ V- I" V

  97. " B/ q5 w8 }% i2 F. p# |
  98.             if (NumOfSingle != 0)
    0 v; m* ?" c2 B
  99.             {
    ' r7 }1 [  k% `. r2 R5 S" d
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    + n5 N1 O* H- W- h4 i
  101.                 {
      P( w3 ?/ u5 J0 p1 S2 t+ F
  102.                     return 0;
    $ i8 P  @9 E. P$ q% ?5 F% B; h/ E" d& a
  103.                 }$ m6 n% ~; v# `$ c
  104.             }3 z( ?, W) \1 p- s8 y5 N" e' [: l
  105.         }! s8 Y. B. }' }. u3 j
  106.     }: V4 l6 n8 d1 U
  107.     return 1;    /* 成功 */
    ; p; C, m" B' k- v. C
  108. }
复制代码
3 z# y% ?0 s! I7 ^+ i( V
73.5 SPI总线板级支持包(bsp_spi_bus.c)
- j5 H7 e6 r0 U7 X7 Q6 nSPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
# U6 @) p3 F  G2 O$ }4 C, i$ c
! \4 G: k# A( O. S9 M, [  bsp_InitSPIBus3 m  G, \6 r% S  ?6 R( B4 a
  bsp_InitSPIParam
% q& j# y! h7 O7 M8 u  bsp_spiTransfer
( K) I0 U' z% s, z73.5.1 函数bsp_InitSPIBus0 C+ s* Q9 c$ V/ \& M4 G- u& M
函数原型:7 S6 Q- ^! F3 d

7 w9 f; N+ S& U* ]$ ^void bsp_InitSPIBus(void)
, w) p  Z+ e# j- A; ^; a7 Y: P) x( f* L
函数描述:+ O1 A6 N( f, j1 r9 ?2 ^+ W
: }% n+ g8 f# o% l+ p" O* e) t! C
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
: T3 @" C3 u6 @( V" v+ m6 M
0 b9 J1 P7 Q& h6 t& {8 k# q73.5.2 函数bsp_InitSPIParam; ~" s. O* v4 N( q$ f
函数原型:( q8 B" f3 Q  E" k
( m+ f# s& Q' k: W2 D% V
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
7 L8 T+ W) x% a# u, j
: {" R! m  X. G) E! @函数描述:7 E& p: U6 I* {% i$ \* W. Z

7 ]4 a* p3 e4 h& Y* s此函数用于SPI总线的配置。
9 _& C  M: R6 P8 d, m% y+ D0 C* |& J- y. u, g3 ~0 l' e
函数参数:% K8 H1 z9 x2 P8 z' H, B8 W

1 x* \3 \$ T- q( u0 {  第1个参数SPI总线的分频设置,支持的参数如下:( P6 E2 i& L' r2 s- c; \
SPI_BAUDRATEPRESCALER_2    2分频5 ^, Q! |: O: y5 V/ C

' a! r3 O* W/ DSPI_BAUDRATEPRESCALER_4    4分频
: T/ `1 a1 Z( t1 r, j
2 t7 S- `: c8 {% K5 G9 iSPI_BAUDRATEPRESCALER_8    8分频
$ o+ G! k$ K' N4 k3 c6 y  \  g. i$ f
SPI_BAUDRATEPRESCALER_16   16分频
' d' c3 V- v+ M: ]7 S4 @2 r% a4 T* M1 O! w* D, E
SPI_BAUDRATEPRESCALER_32   32分频1 t# ^. }" q- c% v
. h1 {/ c  P5 V  y. i4 g
SPI_BAUDRATEPRESCALER_64   64分频
& R/ `$ e7 y' X- z% T
7 G; e- a6 o8 [! K# zSPI_BAUDRATEPRESCALER_128  128分频
+ o. K% G( ?+ v) ]/ C& }' m) k) _1 \3 u2 h8 z
SPI_BAUDRATEPRESCALER_256  256分频
5 T/ Y/ [8 S+ g/ ^4 S+ Z0 z# G4 Q7 {! o
  第2个参数用于时钟相位配置,支持的参数如下:5 w# G. U' z/ p6 @, k
SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据; ~# D3 }  _" _8 o
$ C9 |& ^) b, {& s
SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
: L5 l+ o% |) A5 M- C& ~% O. M# V  z/ G; @  |6 |9 r
  第3个参数是时钟极性配置,支持的参数如下:* y8 Q( \) ?/ y: t
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平
; l2 ^% a# ?0 c3 ^7 p8 ?) s  X* w5 O
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平5 |$ p9 L& j" y- T6 U

7 o( @3 p) q3 r+ [/ g73.5.3 函数bsp_spiTransfer
% O6 c! J/ U. r6 _- z3 R' x& q
函数原型:
% I- ]8 J2 u( [% [) _4 E- B* V8 q( P+ a- ^6 Q4 ^; a8 f9 ~) q
void bsp_spiTransfer(void)$ i$ m; t1 j4 ^4 m

/ o) q7 |- |7 K函数描述:
4 G& W' t- k2 Y8 W2 o6 f3 v
. X; ?" h( x6 J& C# j4 P# n& `. ^此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
) A0 W% n2 Q& \$ o- \' P
. f: ~$ R3 G9 s73.6 W25QXX板级支持包(bsp_spi_flash.c)  i/ X( J$ L9 z  h5 F
W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:
+ G* s; E; _! P1 [; Q
  s/ V  U3 T+ s3 X, N. I  sf_ReadBuffer
7 c- t- v3 U0 S, x1 ~5 u1 X  sf_WriteBuffer
1 n  M9 R( g' W. e4 H9 Q( V3 b0 j2 e+ _  sf_EraseSector
0 X. w0 r# }  O2 j" T  Q6 N  sf_EraseChip  J7 o8 x' V1 Z& e5 n
  sf_EraseSector
+ A8 S- w6 Y7 T7 |73.6.1 函数sf_ReadBuffer1 W3 ^2 Q& E$ Z/ Q
函数原型:
7 Q( s! @8 \+ W& d" j
7 }( {9 c' h7 T! x) k3 Xvoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
, G+ d3 [+ M" t, f0 g4 s( V% _* L. o# V7 y
函数描述:
' U( G9 F8 ^) g9 B: M+ E6 Q" F( S" z$ D& I
此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。9 z6 d! V$ f& s. D; K

" P1 i4 H' M" P) P& Y函数参数:
! [% c- v; G! H% E& `% `* T: w$ u5 a! H$ `# ?4 j( ~" h) r; g2 z
  第1个参数用于存储从SPI Flash读取的数据。
. f0 N* e4 S; J0 H& s) @& l/ K( p: v  第2个参数是读取地址,不可以超过芯片容量。0 s7 Z, F. ]9 {" I+ V
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
) A; }( C+ J( g" ?& r- K  m73.6.2 函数sf_WriteBuffer(自动执行擦除)
' X- Q5 f+ P4 i函数原型:
3 e* d% \4 X/ l; x! ~# o$ S, p+ y5 R( W" g1 N- l
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)( L. }3 ]- O; D2 G, x: C
+ [8 o% n  }4 h, q
函数描述:
4 t) A1 s! K+ X4 H
4 o' Q/ j& J: W0 X$ S( R& q/ ]  h此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。% k& Z6 h+ o3 f0 \; M' [
, y- s3 ^" z# f  z1 Y
函数参数:- @7 f$ N2 J' n& J7 c

8 E( T  d% f* F8 i0 @4 X  第1个参数是源数据缓冲区。: v  a0 W$ s4 X
  第2个参数是目标区域首地址。$ O) b# ^( g+ O% M! l
  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。. l2 @" v$ P: H7 A. s2 R
  返回值,返回1表示成功,返回0表示失败。) z- v. d# K8 A  E! \9 p$ t
73.6.3 函数sf_EraseSector4 a4 r' j% L/ N; x/ F
函数原型:
3 }% G3 b5 |6 J# v" s" ~5 g6 N! E# F1 W
void sf_EraseSector(uint32_t _uiSectorAddr)4 j2 X1 Z! h# j

% h4 Q' I/ ]( v2 N9 T1 l函数描述:* b- [  m7 u* Q) H- {# o: @
. m& b3 V# O; u9 y4 A
此函数主要用于扇区擦除,一个扇区大小是4KB。) x- ~& u$ h/ F  I2 q5 X$ F

* I' Y. m) O! a5 s2 ^函数参数:$ @) N( n4 s# n4 G9 P( n1 C* q
3 I, L3 P/ o# c
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。  z. g: X2 r# B
73.6.4 函数sf_EraseChip
  v+ V# J8 @$ A, _& m7 T/ @" |! C函数原型:
7 S4 e' I5 J1 [& E! d  e' q, d+ }; ]# h6 j) i7 q4 |
void sf_EraseChip(void)7 r- P, I/ \" m3 x* q1 k! i/ |
5 G5 F$ q0 H4 x! q
函数描述:" _* F7 v  F* y8 W& G& a, x

1 L0 s8 b+ M1 X, R' Y+ J此函数主要用于整个芯片擦除。* Y2 R7 Z+ `. [3 ~' z0 g) P
" L5 b9 L' ^) {: b5 {
73.6.5 函数sf_PageWrite(不推荐)
6 P( x' e& E! \3 S' i/ `函数原型:5 |/ `0 P/ c# E% H- t, r* E8 @6 _

8 v/ \1 t- [+ S1 Lvoid sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)2 O. j6 `' `2 r: g) H* N. s# M' l
) S/ p  N- w. `/ D: ^7 ?
函数描述:
  b+ b  \( Y- R( P6 u4 l
) G9 h) \! D; y( ]# u% o此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。1 H( v% X- B9 O. s6 c
+ p. x) h' n2 v9 s
函数参数:
# U2 Q$ j7 _7 L. j1 Q" j4 y1 d: t- `* P
  第1个参数是数据源缓冲区。0 d0 E2 a+ G* A+ o
  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。* Z6 B" H, V0 f
  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。
/ V2 P# j; `9 \73.7 W25QXX驱动移植和使用
9 O3 L8 I0 o( _1 a7 ?& S; h. uW25QXX移植步骤如下:
. S9 l) R1 s$ n7 t1 b, Q, ~( ?0 S# d& p% G) C
  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。$ C3 s2 S& h( Z# B& R. A* q3 D. X
  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
' U6 i3 s" x. ?. ]' s4 @
  1. /*
    8 y6 i2 ?) s- Y
  2. *********************************************************************************************************/ d) E9 [, Z) d
  3. *                                时钟,引脚,DMA,中断等宏定义
      s) ]9 V* u8 H3 ^1 }+ ~
  4. *********************************************************************************************************$ c- M& J/ A6 q! ^$ W9 X7 `
  5. */
    $ ~6 p4 u# A/ ]8 k
  6. #define SPIx                            SPI1
    6 Q$ p% C$ |( X* i) {& R7 c% v
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()2 o6 B7 v& ^$ u8 R$ h
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()
    / j3 c5 h, o  Y' t/ U4 P3 ^1 w

  9. & y) ~1 {: c8 w' V5 X' r& M
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
    5 W- Y6 m' P/ C6 {  l( \
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    % m& {% B/ g$ F, x8 o, Q' o6 [, G( i2 q
  12. " {  J4 p: `4 {  o
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()0 Y" X7 [; A6 U. o6 P, I
  14. #define SPIx_SCK_GPIO                    GPIOB& w, z! w3 @# {
  15. #define SPIx_SCK_PIN                    GPIO_PIN_34 Y  @+ u4 I6 [6 H1 B- X9 T
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1
    0 w6 V6 U- }: ~
  17. % `& z1 l6 s1 R9 a9 y3 j
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()) m& e* E) c. `$ s
  19. #define SPIx_MISO_GPIO                    GPIOB
    # P- Y9 o2 H/ B
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    0 \" M5 N8 c# z0 F
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1
    4 e* u8 H- d: x9 Q6 g4 Z) e2 v. v
  22. 9 @% z6 V) E0 i% c8 [
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    & q2 w& T0 Z4 M* \" a: q- H7 Q
  24. #define SPIx_MOSI_GPIO                    GPIOB4 x" A: L+ \, e* H, M! A1 B
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5* G! [, e+ M& X/ ]1 p6 P
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1
    3 G8 I& y/ Z, V, ~+ ]
  27. " X7 d- B2 L$ B& r
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    / z. _( y( u3 S* c- s. ?/ y( W
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    7 J' H) D" H- M1 B4 m% T/ A2 V3 C* s

  30. $ u. q8 F. i9 E* w$ I' G- P6 r5 H' d
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX) x( O8 a$ p. Y% Y  w8 Q, c
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    $ ?3 X% a( T. L1 [7 Q/ U
  33. + z4 x' P% _4 a+ B) U
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn( i) s# a, J  I
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn$ j* v" `/ F5 w9 Z  |: r' ]

  36. : @& V8 m% o, G7 q0 H2 A" I/ f* o
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    0 d/ ^* T% Z0 o  ~8 _5 A# N' w
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler
    . |4 ~. f: y& c; w# B  t
  39. 9 t' p  r+ P# u! l
  40. #define SPIx_IRQn                        SPI1_IRQn; c8 }3 A0 a! a5 Y7 S; t1 |0 W
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler. T- t5 e$ F) Y9 S/ K1 O
复制代码

* v, E6 @; K* S) _  f0 K$ x) g6 V% ]0 Z' t1 q! v7 K. y
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h  i6 v8 P/ G0 k& ?
  1. /* 定义串行Flash ID */
    1 s# o. p" U) H' I
  2. enum
    1 `- [1 }) l' ^
  3. {. _+ x; M# u/ T4 h. [, W
  4.     SST25VF016B_ID = 0xBF2541,
    . n; K" V; z2 n" c( S, O
  5.     MX25L1606E_ID  = 0xC22015,
    , T' j: ]4 I* i% z3 m- Q
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    8 E9 ?  ?- d2 p: v2 D) C% l
  7.     W25Q128_ID     = 0xEF4018& r$ Y: C* R% M5 P1 C- m& o
  8. };
复制代码
4 V9 P( k2 L7 |' \  S& I7 R
  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。4 z, ?! q; i3 L. V% i  Y1 K
  1. /*% E7 X9 ^8 `' Z( M" o! [" M
  2. *********************************************************************************************************
    ! }1 }, G: _# t. W5 x- L, R2 m( D/ u
  3. *    函 数 名: sf_ReadInfo5 m8 `; r3 _, a( w4 j2 C3 m3 l1 B
  4. *    功能说明: 读取器件ID,并填充器件参数* V3 A% J% _( L; t5 q8 y, C
  5. *    形    参: 无, A# V* {8 r. e" q; F
  6. *    返 回 值: 无
    9 R, z) Y+ a/ Q: B: F9 r: I! G
  7. *********************************************************************************************************, G( i9 g  k: v7 z) P. {
  8. */
    & E( c4 c# r- [
  9. void sf_ReadInfo(void)' y% t) ]( j- ?1 M) o3 k
  10. {
    9 t$ X! G: m" I2 P3 U
  11.     /* 自动识别串行Flash型号 */
    9 t5 _% }# V& U7 J- u
  12.     {
    ( B, B' T" Y. r& z' e/ y: a
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */* {- t" s/ D/ o' h1 H. Z- S+ Y/ z

  14.   ^; z% b) ?! t# s4 J" W
  15.         switch (g_tSF.ChipID)
    * p6 T+ Y! Z' r/ n$ s% K
  16.         {: a) _  |7 y4 _" J2 ]5 l9 [, a2 G
  17.             case SST25VF016B_ID:: `( G0 \) A8 l' p' A
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");' D2 ]- i3 ^3 i: q$ M9 D1 H
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */; f9 ~% l% Q- H; v# i
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */  }; v& o7 q) R" x8 T
  21.                 break;
    1 Q. U8 P9 v' p$ {6 [7 w. w

  22. 2 f+ e" ~' h9 ?) V/ `1 u5 j  ~
  23.             case MX25L1606E_ID:4 U0 _2 v+ D9 Y: n6 d4 E' Q
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");
    0 p& U3 w& N$ H$ y) a  F# Z
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */0 [2 C- \. J& D
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    : J# v! N* }6 S+ L
  27.                 break;1 w" [/ Z) g5 G3 F( A: }
  28. 4 q8 }- c) L0 u! D0 y
  29.             case W25Q64BV_ID:
    : u$ U' q& S" n) Z0 P+ f3 u3 R
  30.                 strcpy(g_tSF.ChipName, "W25Q64");* M3 [2 m* G& h$ _: ?" T* P4 m
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    * a% B! S% X% v, N3 O- [
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */  d. e$ J+ ]( p6 L: f" j# n! w
  33.                 break;; U7 G' v7 I, e, g/ J( A% r2 W( l
  34. . _% F: H8 J" H9 s9 a
  35.             case W25Q128_ID:
    / D6 V5 b( ]7 k) P& ^& ^5 x" H( O
  36.                 strcpy(g_tSF.ChipName, "W25Q128");
    4 ]- Y. v, Q1 i" {! R# d
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
    : l" O) y8 }1 R5 h* T1 W5 ]: M/ i: t
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */! @8 N; ^# b1 x4 v) P
  39.                 break;            
    3 ~: @7 L, r% |$ J+ j
  40. . j- `. \4 a! i: l
  41.             default:
      P4 ~, g4 S/ _
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");0 t* \$ y& H. I  x, U8 E" M
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;
    / ?" L' j' S9 K( w1 n
  44.                 g_tSF.SectorSize = 4 * 1024;4 V. n: O4 r' W$ d/ ]" G* e
  45.                 break;. B; ^+ _0 f' g! b6 E
  46.         }
    8 K+ k* m3 J: S7 S/ j
  47.     }+ }3 H/ x5 s2 ?% S, v
  48. }
复制代码
7 W! @& d& B1 k
  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。
  [) ]* a4 R* r, f* w
  1. /*+ u8 s4 r% A' c- t
  2. *********************************************************************************************************& N1 w% v2 m5 u5 k6 w: B
  3. *    函 数 名: sf_SetCS
    : [! C! F" _9 S! o
  4. *    功能说明: 串行FALSH片选控制函数5 ~' R% m8 m1 A: L6 G
  5. *    形    参: 无  B, s. f& t. {# M4 r* n
  6. *    返 回 值: 无' r8 ?4 Q' ]" X7 J- p2 ^
  7. *********************************************************************************************************1 O. j, m: S4 A( T" Q& S0 L$ j3 m
  8. */
    + H9 ?# _4 \5 M2 [; x4 W
  9. void sf_SetCS(uint8_t _Level)
    8 U8 h9 `3 x' w& ?" I# D8 I/ w
  10. {/ r* g# \. k& O7 b
  11.     if (_Level == 0)& T1 `" _+ ~6 A" ~
  12.     {
    $ X2 H1 T& d. w$ Q. @6 I
  13.         bsp_SpiBusEnter();    2 b! G; s5 ]# e  z
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    8 }7 M0 e) f4 ~$ j4 O3 Y6 T
  15.         SF_CS_0();! J8 _; S8 i. E+ [( c; y
  16.     }
    0 ~* t% K& W& h4 I
  17.     else
    / @" v6 Q- N4 c1 P
  18.     {        
    % n/ Z/ z# j5 f) u; L. ?
  19.         SF_CS_1();   
      v$ A. _9 G- f5 s/ v
  20.         bsp_SpiBusExit();        
    0 s8 U( _: N6 n% Z
  21.     }
    $ l8 h3 I. `" ]
  22. }
复制代码
, P) u4 W( r# U) Q, Z. }# v& P, G# |
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
+ [1 o* x# ?3 w1 c
  1. /* 串行Flash的片选GPIO端口, PD13  */
    9 C, ]; e$ @1 J7 J
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    1 s$ A# j+ ~; a) _
  3. #define SF_CS_GPIO                    GPIOD
    " S+ B' h9 p% S' U# e; E
  4. #define SF_CS_PIN                    GPIO_PIN_13$ `' u8 Q! F+ e$ q. k/ R" I: N
  5.   z& t" ~; z! {6 y2 `* C" n9 ~. E* z
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) ; T: C1 _: _, T) j! E5 w
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码
, M2 i* {/ w9 N  f/ C9 v" s
  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
7 ]1 p) E7 F- j; C8 o
  1. /* 配置SRAM4的MPU属性为Non-cacheable *// _( ?8 X7 u9 y& P; S6 F5 Q+ W
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;6 R1 ^1 H& |( Z  F! O
  3. MPU_InitStruct.BaseAddress      = 0x38000000;
    ! L% L* b9 Q: D) F6 G: Z, J5 |" T
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    8 N! N, t9 K+ n9 A3 I
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ' b4 S+ c! ]8 G7 Y
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    $ K! F" B" v! \# y% L8 x
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;/ H( ?' F. l  [- l( s$ R
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;$ S7 c9 E, P, v0 R3 Y6 r  R
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;1 Y, y1 }1 {1 Q% O; Q
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ! {$ x; R! z; d& U) n) o1 u
  11. MPU_InitStruct.SubRegionDisable = 0x00;
    4 p# V  D1 g5 i
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    6 Z: L2 C( H+ W- i( [8 ?& Z1 {% }7 j

  13. 8 E7 E  y' \: J* S
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
7 q9 h2 ?- J9 W- n* {8 V, [
  第8步:初始化SPI。' X( \# e, F: o. \$ n7 c  [
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */  ]" N+ y: f! m7 a3 {
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    3 [, _. R9 f5 {9 b$ J
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码

2 p  a  ^4 \% O  I* ]2 k  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
. d/ q1 p2 R$ a2 Q9 w6 \/ x  第10步:应用方法看本章节配套例子即可。& D1 \, g, M( r  ?2 J6 M1 T
73.8 实验例程设计框架* M% k* ?  v5 M
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
6 k. c' h; i+ }  u6 z/ D& G& s- T) |2 N3 F
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
; R/ H) a3 `% p) a3 d1 a
) p5 |! ]8 I7 s/ }
  第1阶段,上电启动阶段:
5 V" |! @& v  \! @4 W; l# a4 |, M6 m/ W" H
这部分在第14章进行了详细说明。
( K3 _: p  i4 R  R  # m% T5 I: \, T8 G- a
第2阶段,进入main函数:
4 D$ Q' ^) y- {9 d& ~! Y/ Q! q
/ S# |6 L4 R6 t8 d  z+ l3 A) R第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。8 O' y: k: n7 q* x5 W
第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
  n' k) @( c0 ?! Z- e( i" e3 k: g* `8 |
7 W( \; b$ o. {5 u73.9 实验例程说明(MDK)
. c& ?8 L( O7 A" x; `配套例子:, H$ @+ R9 V! @

4 H6 A5 p; h! I! VV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)% Y! q8 U: u+ C; m
& p/ ?! R( L/ A. k
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)( K( l, G0 x% |# I+ o
5 T3 [% \$ ^" c5 g' M
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
- a2 Q) x% M& ^; ^: t
' }) b# w+ G6 X4 X. W+ O实验目的:% _1 v: t& h8 d
' y( e( u* I6 a. H$ C8 R, a
学习SPI Flash的读写实现,支持查询,中断和DMA方式。7 V1 {8 H: r* B8 @0 j; V' Z9 t. J- L
( {% m& [2 v5 H- f* E! l
实验操作:0 P) W- R- @& l7 S4 G& O6 ]6 A& s
) R4 g) s* n$ W* A- j  |
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可( l# m; p; d) y# |4 S% z
printf("请选择操作命令:\r\n");
5 }0 k  P# ^0 Eprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);. U! D* \% @6 @& L+ N  _
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
# ^* V! z! m" i$ o3 h8 |printf("【3 - 擦除整个串行Flash】\r\n");, y+ z8 L1 y, {! t
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
2 ?' f; l8 S1 T$ Sprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");  G: Z0 c/ n  l$ M
printf("【Z - 读取前1K,地址自动减少】\r\n");+ z& V2 n% E) d
printf("【X - 读取后1K,地址自动增加】\r\n");; S& ^, s: N4 _# q- S6 B  H
printf("其他任意键 - 显示命令提示\r\n");6 s+ d$ u6 T. D8 g) u
上电后串口打印的信息:: J* `$ b6 l+ _- R

4 W7 @9 s5 M( n; A8 P" F波特率 115200,数据位 8,奇偶校验位无,停止位 1。
; R( B/ \# Q! `' P
1 S1 L7 I5 x) P" _* I
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

5 v9 c1 m% r: k
0 L: T% z9 b, L0 o8 E$ M! |% I( r$ s程序设计:+ Z9 }; G$ n6 R4 ]/ A

2 k2 m* U* }8 X$ P" I: p  系统栈大小分配:
: D; V1 ?1 s, Z3 B+ L0 N$ R( B" R" l  |8 x$ M+ P4 ?$ l7 B7 a
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

3 i8 J; M6 Z# i3 [8 [: B! `' r/ s
  RAM空间用的DTCM:: n1 x! U6 p$ v/ s1 s) E! s

9 {( O+ Q4 I. I4 e
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 p' v# E" r) ~
7 t8 v7 d( g* J6 D) e  硬件外设初始化
' v# ~/ E8 {* R( g2 i. G
0 F7 P& }6 a( X! R: ?+ A硬件外设的初始化是在 bsp.c 文件实现:
1 S, T! o" a/ x9 _# Y( ^% H* I6 o) t& C: S, F
  1. /*
    + Y+ H4 H% n, C. w9 l3 C
  2. *********************************************************************************************************. n' W/ F, l% a5 J" ?6 N! K! P8 r
  3. *    函 数 名: bsp_Init5 ^- S3 Y; t  n3 U' o
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次& i* z0 R* u" m6 n$ |, ^
  5. *    形    参:无
    ' R1 n& J- q; U5 T% t
  6. *    返 回 值: 无1 n. n  h; {+ v, [6 a7 U* X( v1 f
  7. *********************************************************************************************************
    ! C9 l$ H+ x9 }$ I: }/ T0 A
  8. */, j" y& v/ @7 S; w6 P& h- p
  9. void bsp_Init(void)$ B' P; ~, H7 g" n' o
  10. {
    ( W) k' J) H& w# ]: z4 M# v/ A# s
  11.     /* 配置MPU */( w, H3 ^& y3 O5 j% z0 E) y
  12.     MPU_Config();* }+ G! ~0 B& I% f

  13. . ~% s6 B* c) y. ]
  14.     /* 使能L1 Cache */4 U& @( s  X- U. p. Z( L8 B
  15.     CPU_CACHE_Enable();
    6 J. S5 M+ K" V2 ^

  16. ( h3 b# \9 }* r4 }
  17.     /*
    % L$ B- G6 K4 _' i
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:* w0 q! @4 D( K% q8 T5 z/ W5 {$ h% U
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。% G- M* u0 }( l4 e0 ^0 F
  20.        - 设置NVIV优先级分组为4。& A5 N5 S8 R7 X# I# u& {6 n
  21.      */& u" U1 U" Q$ N; l/ w. P
  22.     HAL_Init();4 b7 t. h' U2 ]0 u' Y
  23. 9 ^; N* h- {7 ~+ Q
  24.     /* 5 q4 p2 w. T7 G% Y% _
  25.        配置系统时钟到400MHz
    ; k  ?" n4 d2 \$ n
  26.        - 切换使用HSE。8 A  ?5 z  a) J
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    . Z9 q: l* P/ k7 d" a
  28.     */
    ( ]1 a0 Y& ?, R+ k! _
  29.     SystemClock_Config();0 e2 O" R5 \4 F7 W0 J# h1 L) m

  30. ( A  s3 \  J1 Z+ }! ^/ z3 ?% L
  31.     /* 7 f% x$ w0 `) ^$ ^$ E
  32.        Event Recorder:3 c6 \! x/ h# C
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    * C2 t3 H% t3 [# r; A4 [
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章9 q6 H8 j( c- Y2 Q; @# k6 _  ~2 H
  35.     */    ) K5 D5 o! ?  }  s* D
  36. #if Enable_EventRecorder == 1  
    , j- z# Z& f! |/ O
  37.     /* 初始化EventRecorder并开启 */
    , N7 W! B  `3 n7 S; P" o5 M
  38.     EventRecorderInitialize(EventRecordAll, 1U);! ?& K) L7 p/ A! c
  39.     EventRecorderStart();
    * @8 G! r- }8 C5 _$ e' ~2 L
  40. #endif
    * _5 V2 j9 N( f- B
  41. 6 f+ s3 o% M" D5 w6 Z1 O
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       $ D& y* y: J" R% d6 i* B8 d
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    " r- V: Z5 D( \$ Q: l! |' s
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */5 |1 s* b" \' p" ?
  45.     bsp_InitLPUart();     /* 初始化串口 */
    8 u3 D* Q5 f( t" Y# t3 I
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ' g% x7 s! P. O' j( [
  47.     bsp_InitLed();         /* 初始化LED */    . J1 W3 R; F5 z5 Y6 ?
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    / }+ u/ d5 y/ `2 b1 @
  49. 3 R: L/ W; ]1 m
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    6 f5 v, j( N! \
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    4 S" S, C, R; @( p
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */4 G! h8 I+ w8 O
  53. }
复制代码
: v* g8 X3 _4 C9 f5 q) H. N; F
  MPU配置和Cache配置:
, K5 D1 q! A, c& |
  F, _9 ?: l5 y5 N+ v, b数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
" ?8 [( r7 T5 @) G9 A/ S, K0 \  Z6 Y" a; G: P& w( W' {5 j
  1. /*; O6 o: f+ K% L  C
  2. *********************************************************************************************************9 s) [& e  s' q2 {( x/ x/ M$ [
  3. *    函 数 名: MPU_Config
    # Y5 L: Z9 P, Q3 V/ Q/ v
  4. *    功能说明: 配置MPU! }  U1 Y/ h8 R0 a
  5. *    形    参: 无
    , R6 H- M$ l4 z6 f: h" V9 e
  6. *    返 回 值: 无3 F; B2 o; _. D) x1 ~! ^
  7. *********************************************************************************************************
    # y7 Q' q, e+ n+ f
  8. */
      B3 ]# o. C0 m) N7 F
  9. static void MPU_Config( void )9 B( _% `  F2 ?) {/ z) ^
  10. {7 h+ C3 w3 E" x! N! d' A3 n  L
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    # f9 A: x4 w" C$ S9 _  t

  12. ) o3 Z5 O* n; H* S0 [
  13.     /* 禁止 MPU */0 D, E4 F- I4 K1 Z' G4 e
  14.     HAL_MPU_Disable();, |7 ~6 g; @* [) R

  15. 0 S. B% t; ~8 x4 M; p% N7 T& ]5 C# R& i
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */3 K2 ^2 v. S5 e/ Q7 Z0 |* p
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;& |% s: b% j1 Q7 w" s3 p1 F% k
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    9 h! G/ m6 i8 J
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;, O" n( ?& C- e0 T! H
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    & Q& z% {; A0 p3 t( k$ r1 o
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;: p& ^+ A& U/ S- k! i
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;4 N4 f1 S# `' w$ B2 x
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ! M/ c& x0 V) j" [% b1 l5 d
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;& d/ j# n8 G+ Z# b5 v
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;+ ^! k! m1 B  z
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    & O4 u+ ~8 P0 X
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . P% N& |7 u' X" g" s3 `

  28. " D) k6 o1 m5 v5 @9 T
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);' n8 U0 x8 I) M# e) v/ Q

  30. & @+ Z5 M7 W. {1 J6 }6 l

  31. 9 {# J( m, [: Z' r# j
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    1 D6 Y- m9 G3 X
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;' O2 O3 c2 M4 \& L- U
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    % n/ K7 `3 w9 Q. j; x; d
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    , G# Z! r3 }" H$ A
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ! B2 _  b+ s2 g6 m% S) o
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ! l& \* w( M% N2 A8 G, ?: E
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    8 f. g8 }' G4 S) }% C
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;4 q! ]4 t$ d2 u
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    ! b3 ^  C8 n. |# o1 t
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    9 }, a2 T. h2 G' A) Q
  42.     MPU_InitStruct.SubRegionDisable = 0x00;% p) p/ x) c9 P4 H$ f: i7 q
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    2 A) C( H9 V- T2 J3 {

  44. * V% C/ r: d/ e2 {
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);8 g1 O0 Y/ r( Y8 X0 `; O
  46. . }9 `. [6 J% ]8 }4 i
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
      S1 H4 ]- f9 g7 D
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    3 L4 @2 J- c! @4 v3 |( L4 z# ~/ u* I
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ) E8 }4 y0 o7 l5 [
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;1 _* c$ z- G2 N9 E5 M( u  r! {
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;' _$ [+ f( D/ k4 F- A  c( W: S* J! a
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    / ~' H' E+ c, n4 o: e
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;5 i9 ?$ Y* N- j4 h! B1 g
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 J& k: p% i, }* q; Z" w4 Q# q& |  s0 u
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    + s5 g* J8 S; Y/ c& z. m6 v: g
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    6 C6 \) @4 z5 Y
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    5 |) c/ e$ }2 K3 \0 z$ ?
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    , C6 q& r# s6 R$ I2 @' T' Q
  59. 0 D8 k4 w! |! {% o7 W4 W2 U
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    # a- B) u  g+ {6 S! U* U
  61. + E4 v0 m) i# ^
  62.     /*使能 MPU */
    ( e) ~* v9 j; v1 O; B
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);7 v4 J+ O% ]; ?2 D# E( J! T
  64. }8 a8 a5 U# d9 d& H! m: f1 m' I7 P

  65. * {3 s+ k7 l6 s9 J
  66. /*- e! V# y  v( e6 P
  67. *********************************************************************************************************
    2 x4 p5 v6 J) S4 `6 A0 X( d+ g
  68. *    函 数 名: CPU_CACHE_Enable$ ]8 I& L( u( Y& U3 S
  69. *    功能说明: 使能L1 Cache
    . w4 |' F1 y/ R. R2 J7 @
  70. *    形    参: 无
    / {# B9 C2 P7 _6 L# ^0 ~; S0 Y
  71. *    返 回 值: 无; V% o7 r  a9 |( O% ~! O
  72. *********************************************************************************************************
    % y  s% }+ A1 c; G: T2 t
  73. */0 D1 y9 K9 u6 u
  74. static void CPU_CACHE_Enable(void)) p1 t3 |, }- Q
  75. {
    # v* O% G/ [4 _: k: m  }. ^6 `
  76.     /* 使能 I-Cache */# _+ v, l, {: x8 {, k! L  B1 q  l5 c
  77.     SCB_EnableICache();
    , i5 o# S) f3 D3 E9 @

  78. " S6 G" ?* B8 \, _  J
  79.     /* 使能 D-Cache */
    / u3 J1 V) j+ B1 O& M1 Z; x  `5 q
  80.     SCB_EnableDCache();
    - k1 u& C, Q$ a2 e& ^
  81. }
复制代码
+ J, }1 w$ _- ^3 c7 v
  每10ms调用一次按键处理:# T# M- _* J: m- W" b$ V7 B

) b! `: J1 Y4 W5 _" e按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
6 H6 q( F; r7 t# w7 t( A. U% |! ?, g. B& v
  1. /*
    8 y: X- C6 e7 \* m! v" m$ \
  2. *********************************************************************************************************
    * ~0 ^- M% O( E& z
  3. *    函 数 名: bsp_RunPer10ms0 D8 U2 d& M: b8 z$ w
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求' D6 v% f1 W7 u5 \( s4 C
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。; Z- t& c, P8 B* J2 r4 k! i' l
  6. *    形    参: 无
    & _5 f7 `1 x, ^4 m3 X* q* o
  7. *    返 回 值: 无
    # t3 x+ a! W# H6 {* W. R
  8. *********************************************************************************************************
    $ K2 B2 [6 H8 Y4 _
  9. */
    ) l/ j9 n2 B6 T% w4 [
  10. void bsp_RunPer10ms(void)) Q5 \8 y; J' i) {3 A
  11. {+ U9 z; `9 ?' [/ }5 x  ?; h( h
  12.     bsp_KeyScan10ms();
    2 g0 [2 x: B) s- N5 N
  13. }
    7 h0 l3 S" c. p4 R% }6 l

  14. " w8 w5 t, Y2 e
复制代码

2 `/ l* w3 \! u0 E- y4 E. W7 r* y3 T  主功能:
* X5 e  m5 g8 H. _% f
/ q0 C0 q4 n9 y主程序实现如下操作:2 z) q4 G% e  T5 W7 o( d
+ m- @+ ^' u7 O* e/ q
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
( {' a$ j! ~# J) D7 f' @  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可% D% T) H3 D+ T( K/ h) x
  请选择操作命令:
' I9 O) n2 t) @. c& o* H+ |' ?  1 - 读串行Flash' b; L( _9 M+ I5 e# A
  2 - 写串行Flash3 D0 S/ ~" y3 H' L  }2 H7 k
  3 - 擦除整个串行Flash# x0 P6 H* b! q, M, a( l
  4 - 写整个串行Flash7 N6 j7 B3 R9 @3 m5 o
  5 - 读整个串行Flash
1 t; g/ ^/ G& {  W  ]( r7 Q  Z - 读取前1K* Y. u+ U  b0 t# L( E
  X - 读取后1K# c1 g/ O; b! _- X; Z7 s5 r8 }" ~
  1. /*
    4 |7 N8 S4 a+ B% x
  2. *********************************************************************************************************
    ( O9 f0 }+ [+ O6 x0 ~; v" {
  3. *    函 数 名: DemoSpiFlash
    3 D$ s+ r; T; r  i2 n8 U* C' \. s
  4. *    功能说明: 串行EEPROM读写例程
    9 y6 d: n4 `% S
  5. *    形    参:无& b2 I) B* \- e
  6. *    返 回 值: 无
    . t% M- k6 T5 R: l+ {/ r
  7. *********************************************************************************************************) c6 J$ x! e2 p' H
  8. */: [! Y. V  f4 p; r* I3 U
  9. void DemoSpiFlash(void)
      P( S* w. u% p! G; }- M
  10. {! S) o. A: i$ g. w
  11.     uint8_t cmd;
    ; y6 O2 g7 @' h4 F
  12.     uint32_t uiReadPageNo = 0;4 K6 P$ b5 H6 S( m
  13. . `! I* ?: B( t

  14. % E& D5 T7 g% O
  15.     /* 检测串行Flash OK *// d: d1 R8 ~5 n  E% V
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    7 k0 T5 i1 Y; s+ _3 n
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);/ t: l6 @" d/ @# g% S, K

  18. : v9 j6 S) P) ]! e2 y
  19.     sfDispMenu();        /* 打印命令提示 */
    8 x3 ?7 t6 J& S- X4 X6 n. Q4 P) e
  20. # a# e* i% j/ Q2 G3 v) `$ S+ s/ E
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    6 G3 o2 ~' E8 ?' ]5 Y% @5 g
  22. 5 `9 U1 H7 I" y  t. `
  23.     while(1)
    ) y# e2 l+ l4 L5 L
  24.     {1 U* ^. T; c! Y
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */. {, |) X4 a( d! }# @/ T3 C* H( j
  26. & Q! G! V0 [3 `! u" x
  27.         /* 判断定时器超时时间 */3 h: f1 }" I* j* o9 [
  28.         if (bsp_CheckTimer(0))    * O: k3 P& S1 z
  29.         {9 |  M3 t  g# e' Y$ U+ l
  30.             /* 每隔100ms 进来一次 */  3 K% `( E7 I2 S0 B1 d, a5 g+ S9 g
  31.             bsp_LedToggle(2);
    , `/ @7 w& S3 a8 u9 v
  32.         }8 [. d8 A: r7 f: n

  33. ; G9 ]) G" b# H" I# D3 z2 p
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */& `, K5 _/ @  a; o. L
  35.         {
    + M3 n% Z9 e% j$ F: l: h
  36.             switch (cmd)
    3 s- Z& ]4 F/ s8 b& T- M: C5 k( k
  37.             {4 y7 \- b7 d3 r) P& z6 |
  38.                 case '1':
    7 c9 ~) [( o' A9 ^( Q$ E4 a
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    - P  @- ]( F+ ^2 s/ ?
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */9 w( @. ~8 \- X8 {: T* J! E1 A  e3 z
  41.                     break;  v& ?8 q2 ~; Z" {/ h

  42. # U1 Y. j; R. _8 M  G8 V2 _# |
  43.                 case '2':
    7 e- q, }$ O" A; D
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);+ N: |7 Z" r9 O6 S1 V  z0 ~
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    2 R* H1 l( F& l; m6 x4 W
  46.                     break;8 g( _  Q  X! U0 B+ G1 a( R

  47. ; K: P: t6 r& X
  48.                 case '3':
    ( C; S+ m4 m. K3 k1 l, J$ R1 }
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    % Y6 J) B/ s6 t8 D5 V+ L
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    : ?' z* F/ @4 L
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */3 S- H' W6 T* e: P$ M2 `% V
  52.                     break;
    . Q9 e) `& {& s4 E  Y
  53. 0 }+ b3 k; d( J  @- C, i% @
  54.                 case '4':" x2 u2 S+ m! j! [) s, P* y
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
      U$ g& t6 @, n0 V3 Y
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");! O% h6 G& S" n
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */9 C3 C4 O: z+ z* c. a
  58.                     break;) c3 v1 B: W; o
  59. 1 U1 z7 ?2 h# L0 h' T5 Z
  60.                 case '5':
    ' Y' g; p+ w* n0 g+ [
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    1 z& V! U- D% I! X+ B
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */0 o# \- @5 @' |- z) I7 W" k9 ~
  63.                     break;" p/ E: z/ R1 |! N" O$ o

  64. 0 Z( }7 `+ Z' ~% U
  65.                 case 'z':
    ( V6 N" E; i, b& j6 ~& X1 M8 w, h
  66.                 case 'Z': /* 读取前1K */
    5 f6 B2 y) r' Q5 q; ~! _( w
  67.                     if (uiReadPageNo > 0)
    2 u% R: }$ S* T  W
  68.                     {
    4 m+ e+ s- v2 L& l
  69.                         uiReadPageNo--;$ P7 ]* ]8 o6 t/ v% d4 b) Z; \5 v
  70.                     }7 ^+ {0 ?: S, ]  I! l* @
  71.                     else; O4 M9 s' b! G0 y8 f
  72.                     {
    ; b5 V3 B" |  B5 ]: ]. X
  73.                         printf("已经是最前\r\n");
    / [, r3 |8 Z6 n
  74.                     }
    + V8 |3 }; V2 g4 X/ \* q
  75.                     sfViewData(uiReadPageNo * 1024);6 h+ g8 `; y2 D. @
  76.                     break;
    ' p9 K2 \! W3 G$ p

  77. * C( E* }" Q0 B& r( h/ t/ M
  78.                 case 'x':
    ) x9 c2 l/ A; b( C* l
  79.                 case 'X': /* 读取后1K */
    , C0 d1 A% A9 H9 f  d; c
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    ; J7 k! K" Y# k& P; F* ^3 E
  81.                     {2 d  u3 g" b% d% j: C9 L1 p1 ]
  82.                         uiReadPageNo++;
    4 Y3 R2 J. E9 u) X
  83.                     }
    ' R5 V( R5 N9 \
  84.                     else
    " P+ P1 ~" [  z7 i  |$ o& w
  85.                     {- Y' H# A+ k# A* A, a. m
  86.                         printf("已经是最后\r\n");3 V3 S$ _# C" U9 Q
  87.                     }: M) Y+ q: I2 F, z2 G" W
  88.                     sfViewData(uiReadPageNo * 1024);
    % J* e+ ^' N( P- @  m; w
  89.                     break;+ p/ I8 B# r2 u6 |3 Z$ C
  90. $ @7 p* h% o5 ]  M7 p; k( K: |
  91.                 default:- Y0 }% H3 O: e! ]% X7 i
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */4 A! p: I0 v. {5 _5 H
  93.                     break;; A0 H3 N" V. `5 c/ s4 u

  94. ; c5 k" I4 A  B3 t5 f7 c7 v
  95.             }, u8 t0 _) B# n  }
  96.         }8 j! X$ y  ?, Z, Y4 l7 B' a
  97.     }& L2 ]" M. s- S, ?$ p
  98. }
复制代码
4 C! b5 S# p8 x8 V- L3 @* V0 ~( a& W
73.10          实验例程说明(IAR)

8 z! T) O4 X& X( s% n0 g配套例子:
) {+ M: A1 j- z! l3 D7 l4 x: P& j) K( m. D/ Y  Z& m& O2 i
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)# \! l9 k3 N3 ^

) n2 ^8 E9 J3 f, @" z+ b; _V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
9 }- H2 s8 V, P! b) _! ~' J: G5 a+ J. J/ C  q
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
! w: |8 I' G" j" X9 q: a- M, J. L- w" w. ^2 Q$ c6 e
实验目的:% C* }* x4 i2 j( T, c) S5 ]- w

8 J2 i# H, J2 I学习SPI Flash的读写实现,支持查询,中断和DMA方式。7 N( h( F3 N% Y; u

3 _$ T. b8 F% y实验操作:
9 o3 E* g* B% ^5 U, u* N+ k( w  a3 n- {/ e8 B$ {9 O
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
1 ^6 j( }- H  w+ W* i) D; \* Aprintf("请选择操作命令:\r\n");
# Z3 O. d% s9 |  zprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);1 r! x. B# _! U
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);& f7 i' {) @' d- S9 ]
printf("【3 - 擦除整个串行Flash】\r\n");6 X" T2 H2 f$ f  w) n+ r. e
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
- y7 Z- w8 R4 c$ Sprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");& J$ B: F3 @" q0 U
printf("【Z - 读取前1K,地址自动减少】\r\n");* x1 G4 U/ m/ b. g, P5 A; J, F
printf("【X - 读取后1K,地址自动增加】\r\n");: e8 O6 }+ q6 _0 e) t
printf("其他任意键 - 显示命令提示\r\n");# W" C# I; [* b9 n6 _
  s4 ]! i4 C- r; L4 E. u# Q% u
上电后串口打印的信息:
: H& P- i* f8 X7 t) E# ?7 B2 V6 s" X
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
. w" T9 u/ K/ h# f
4 s5 r+ N6 H1 q* s
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

* x0 u! I9 S+ U  S( o) ]9 I% v2 E
程序设计:, {- j  w9 w4 a/ T& ^' w  |; d

: q( \! |9 c# L+ v2 W8 q  系统栈大小分配:9 x6 o7 A1 u' l3 l

. ~0 j( r8 ~3 v9 _8 w
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

! R1 P, l, h$ T3 G. F$ ]" h8 o3 Z1 z6 m
8 y2 N; a" R9 F8 w3 L+ ^  RAM空间用的DTCM:
# Z/ n1 y; L5 m, _) z8 X" G3 ~, f0 Q1 y; L4 a  s( B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
- k# B7 L. z8 l( m, @

0 ^0 F6 v. Z# U1 G$ ^( o5 Y# S! y  硬件外设初始化
. ^3 r" b% d) C9 K* X' }: ?, k8 S# v. g8 \6 I- n) g% s
硬件外设的初始化是在 bsp.c 文件实现:) w( [$ P$ g' y$ N2 w4 b3 w

; n7 @8 i3 T8 Y. Q* M  V6 \. w
  1. /*( M; `% N+ x! F0 ~( M
  2. *********************************************************************************************************
    : [" A4 k) v5 B- z: _  |8 R
  3. *    函 数 名: bsp_Init
    3 i% {/ _3 k: g
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次8 V3 Y" f% d) ^) ]/ }0 N
  5. *    形    参:无
    ( u6 O( Q  H. o! \: f8 L
  6. *    返 回 值: 无  v8 R4 T* l/ G3 p$ m$ X
  7. *********************************************************************************************************, g/ G3 X0 h3 {+ e9 k; Z* N
  8. */
    1 c, s# ]( z8 e' h
  9. void bsp_Init(void)5 q7 r! N! `' J0 j
  10. {
    . w0 ?" A3 m4 b: K; N; R* E8 v
  11.     /* 配置MPU */
    7 `. U5 y" b5 e, ?+ m' |
  12.     MPU_Config();3 w/ a8 Q! ]5 t* Y: l+ L6 P  A4 A
  13. ' R4 M$ I# I0 m
  14.     /* 使能L1 Cache */
    - O7 o8 z0 |3 g8 R& m
  15.     CPU_CACHE_Enable();
    ) @1 q0 ~/ q6 u6 f0 K
  16. * B9 e. J: D5 h  T6 y! _5 v, W
  17.     /* & _, c2 s4 l5 c2 L
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    3 B) j' {0 q1 e3 ~# @" c* s
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。2 s" F  v2 v% a) d( P$ ~) p
  20.        - 设置NVIV优先级分组为4。3 X9 O  ^' D; O
  21.      */
      P: R$ i* f, S
  22.     HAL_Init();' U4 \/ a( K6 w

  23. ! o) h7 K& L- k' L- J& W
  24.     /* + B6 S* t: q8 x7 @6 I; r
  25.        配置系统时钟到400MHz! d7 M; R0 Q$ l. c7 Y. q3 ^. [4 G0 N
  26.        - 切换使用HSE。
    , i- W( p  B9 J  W* A2 l; U% Y
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    : c- j+ Z3 o; o% w; `
  28.     */
    : J' p* ~$ a- i  N+ ^. r
  29.     SystemClock_Config();* u# F9 J/ g0 D" o
  30. / h  N: Z( ~0 m& L- z3 O. s
  31.     /* 2 J) L+ K/ Q+ d$ V$ n7 T5 A: R
  32.        Event Recorder:
    ( l# E/ N% ]$ H- V8 S% N  Z3 z: y
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。, s8 u5 i' ]( \3 R
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    " O+ o% l: L  \4 b! i5 F
  35.     */   
    9 A. T, H$ }; `5 B  {0 }  X4 y
  36. #if Enable_EventRecorder == 1  , r( v0 l' k7 M5 r
  37.     /* 初始化EventRecorder并开启 */
    ; x, F0 f/ o% g. [' ^& e
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    9 J1 h# q0 ~6 h5 L1 L
  39.     EventRecorderStart();' W! e- q% f& @) t2 H
  40. #endif
    3 Q) Y2 m) C1 \) V0 V* V& k8 N* w
  41. : n' W. G2 ?) [) `8 Q8 b
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       - s5 K6 ^. k. x* Q' R+ W
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    & ^" W) A' @, u; y( `, O! d, q/ Q
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */9 N' Q! h( `) {7 \1 r; h
  45.     bsp_InitLPUart();    /* 初始化串口 */+ L% _5 h! F+ u1 U( u
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ' J, i3 k/ \1 d, i- X; U& ^
  47.     bsp_InitLed();        /* 初始化LED */   
    7 i7 l- c/ v3 K( T! ]
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */( a( ]0 h5 G+ H; Z2 j
  49. ( K" I8 i2 l" E4 m* o7 w4 c
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    7 A( f  J; _  j) ~7 H4 P/ a
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        . M, U6 [5 J3 A( I5 S6 ~# Y
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */7 F+ P) w  u3 k
  53. }
复制代码

% c8 _$ Y4 z/ m  MPU配置和Cache配置:) i5 [# l/ i0 E) }* U
1 @7 r9 M1 J& @& t8 ]$ `, [, L
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
* g5 G+ f  H1 l3 y: z, D  L  i3 E+ H+ H
  j( H) `; q: Y' o5 G* v( o: v
  1. /*
    " V1 O: z$ E* b2 X3 v, `, E4 _+ ?
  2. *********************************************************************************************************, j6 Y" a) r, l! e8 k
  3. *    函 数 名: MPU_Config6 c( u7 f% L! J" N
  4. *    功能说明: 配置MPU
    % H5 m7 ?" F4 n
  5. *    形    参: 无
    * S* ?% |) v( {; @/ Y7 r2 A
  6. *    返 回 值: 无: l+ h5 F& o# @8 T/ Q! T2 m; l
  7. *********************************************************************************************************
    : N! w, q/ g9 n& T
  8. */
    : l/ x. }; [/ `
  9. static void MPU_Config( void )
    ) Z5 y+ t' X" \% C7 N
  10. {
    ( d+ V4 ^8 d8 d& s4 H; U# ?
  11.     MPU_Region_InitTypeDef MPU_InitStruct;( L. S0 C: s6 U1 Q

  12. 9 \, J  I2 `3 J" d
  13.     /* 禁止 MPU */
    . z7 g+ O4 H9 d
  14.     HAL_MPU_Disable();2 C7 a( w7 f- B" i

  15. . |/ f6 h+ c/ q9 J; a8 t  m0 T
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
      c7 N3 \. W8 i, H* o
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;/ Y4 P  a& L: E6 R/ ]! m
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    9 P+ d; w3 c$ f5 d
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    - N5 x# w; e6 O  @* q) L4 H
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    9 e% a5 [" P5 W$ j" b
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;1 M9 P( E" N5 A0 l3 o5 V8 i
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    ( M# U9 l2 o2 C; U# Y6 _5 m" M2 W
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ' y; \: A# @* D
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;. l% F7 Z) l2 d7 X9 I
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;) }& t. F, B4 G3 E5 j" t
  26.     MPU_InitStruct.SubRegionDisable = 0x00;7 |' o4 d3 u4 Y$ D; _% S  `9 J* @
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ' Q3 q+ `$ P/ ~5 R: Y+ B% \; r

  28.   r1 |# H* q& E& ^5 E' e
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);) z6 c0 }2 [8 i2 O- e! k. n

  30. $ k/ b) j- w6 ]: Y5 Y

  31. : Y# \, |& ~2 J# k% g2 P
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    $ k, u5 }5 m% n
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;5 Y% Z8 a1 F* }" u$ f5 w
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    & q. `+ @2 B  g6 T* o# B
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    7 Z- c9 C6 b# _; D% B
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;; J$ O3 }1 m2 |1 |
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;6 |& ^% Q' n* [5 C) O( }% [) e
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    * M0 R+ Z6 \1 [* [" S1 q
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    3 v% Y3 I0 W6 g0 l- n
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    # O. d  [6 K. V: H" P
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;: k& r. d  t) O2 W
  42.     MPU_InitStruct.SubRegionDisable = 0x00;* W4 O6 {. _8 }' t. v  R9 @6 W$ z
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    $ D& r& s$ B; E; ?2 F; A2 i
  44. & J5 V9 C& }* G: N) V
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    3 s7 w! I1 H* Z2 y, t

  46. ! H/ Z8 _8 j2 Q9 M
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */  c( t+ U) t  T7 J
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;8 @/ @; Z: h* [( R! D
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;- U7 P! Z4 w( Z) K/ C8 Y8 m" e
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    & d  s. e1 M2 y. F6 t+ a/ W" @
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;+ {4 A; r1 s$ W" S6 j* c+ i
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    & `, e! `3 O4 U3 M
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    3 Q3 B/ r2 c$ ~- X% t7 _5 x
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;+ w9 s$ _7 P( ]: ]2 G$ {+ }2 p
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;% B, V# m+ E3 r0 o& h9 |
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    " q( J; h  P/ T# _2 D
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    & E6 x% _* U, S* |0 O
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;. ]9 d- W- |6 Q

  59. ) f# D7 v, a# c! K' x
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);& j0 M" V4 m- ]; u4 D1 B0 c$ n* ~

  61. ) [$ K; S) @7 m0 d1 h
  62.     /*使能 MPU */& l5 V5 k5 n! v. T7 Q4 O" s8 k
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);6 \' b6 h5 \: i% ]( R$ I$ e3 L$ N
  64. }8 ?" ?& \! p0 H1 ~$ U/ H
  65. 4 D2 u  m9 n0 e- p
  66. /*  {# a9 t" _0 x( [4 h. J
  67. *********************************************************************************************************
    6 d3 A( P2 m+ Q& T
  68. *    函 数 名: CPU_CACHE_Enable
    6 w# X4 P2 t* M' u9 l- V# W7 F
  69. *    功能说明: 使能L1 Cache
    % d, D9 Y) H, o# Q
  70. *    形    参: 无
    0 \" {) G# w! ]+ I  X. v
  71. *    返 回 值: 无) }, u, X& I. b$ w% y& S
  72. *********************************************************************************************************. N; v7 i+ V! H% @4 B) a
  73. */$ U% o' X; G. o; j, E! N- z+ {+ g& {
  74. static void CPU_CACHE_Enable(void). e& F' u0 [+ d2 H  E7 _7 B* Q- u! P9 c
  75. {
    ) N+ _: t- Y( K: Z" G$ G# l' j0 q0 K
  76.     /* 使能 I-Cache */) j* y/ v3 I; T1 T
  77.     SCB_EnableICache();  w+ T' \) Z. S% w7 n4 F1 X* G/ w! q

  78. 8 c# B2 v. r( B! V% U
  79.     /* 使能 D-Cache */
    - x" o# q/ s+ M( _/ X
  80.     SCB_EnableDCache();. Q' M9 K2 T- S5 f
  81. }
    + I6 H; K0 \4 [
复制代码

. a7 ]+ K- I' w( V4 B4 ?' S/ G# Q2 B/ k! F4 Z. ?* n
  每10ms调用一次按键处理:; C/ G: I# R  l8 J
( C) c/ m  P" u" ?
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。( O" @; W! D" X. x4 z# [1 h' \
+ J# b$ k$ {" C" e/ [: Z6 Y+ k
  1. /*4 ]' o0 c+ M% q4 \2 B
  2. *********************************************************************************************************2 k3 w1 c* F: p
  3. *    函 数 名: bsp_RunPer10ms
    ! h: g7 ^2 e/ j% @0 Z# y4 w! [
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    ( ]% X, K% n1 a8 o3 A
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    + D; N$ T7 ^* p" P; h' M
  6. *    形    参: 无
    ! h9 q4 F2 y6 Y; T& E# T7 p7 J, W7 M
  7. *    返 回 值: 无1 I3 Q7 a7 n& {, O, w
  8. *********************************************************************************************************
    7 @& W6 k1 Z2 _8 B4 j7 f
  9. */
    $ l  j7 u8 G2 O+ f+ {3 V
  10. void bsp_RunPer10ms(void)
    + P& a1 Z9 T/ f6 d6 n
  11. {' {; d4 ~2 U; N- F/ r
  12.     bsp_KeyScan10ms();
    . x; o- ^) Y  o) x
  13. }
复制代码
7 L" l5 u5 v- j% Q0 ~7 z
  主功能:
) o, L) b0 ^( ^
2 N5 x" M* a" \5 t* W$ k主程序实现如下操作:# P) a8 \) a/ K9 M4 L- P# Z

" O# x( j2 D9 n3 X# Y3 i/ R: ^( H  启动一个自动重装软件定时器,每100ms翻转一次LED2。; K. ]+ i+ `/ ]! W
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
% T# X  x7 v$ B2 v4 ^. p' ~  请选择操作命令:1 B, _  {2 C+ e
  1 - 读串行Flash
7 J  v' ?+ @# l- f  2 - 写串行Flash. `4 _' \* ]8 G4 K; ]1 y/ a
  3 - 擦除整个串行Flash7 w/ r, W3 b* u
  4 - 写整个串行Flash# X: L* N# v5 U2 J* c2 e* I
  5 - 读整个串行Flash9 b4 S" d* e, R7 H( ?
  Z - 读取前1K! K  s$ A4 x3 v9 ?, l
  X - 读取后1K  C5 c( Y, R  O# {7 R; n; r- j; Q: {
  1. /*# ^! ~4 ?- _  x1 n( D
  2. *********************************************************************************************************
    3 w) A. D8 T/ X7 e, k+ u+ F
  3. *    函 数 名: DemoSpiFlash
      s; f+ }! H, k  `. [
  4. *    功能说明: 串行EEPROM读写例程; I- ^( T, t, c. }
  5. *    形    参:无' d) C  r* N8 K4 e3 @
  6. *    返 回 值: 无& r  Z& i7 ?+ x) P
  7. *********************************************************************************************************# c5 P1 P3 q) H2 y
  8. */& ^& t* C/ A+ A# b( w* ]6 g% W
  9. void DemoSpiFlash(void)
    9 n- _# a3 o' k' ?8 u+ p# `
  10. {
    / v4 _. I* b6 S4 B
  11.     uint8_t cmd;: t: l, K; @4 U/ v5 M7 a
  12.     uint32_t uiReadPageNo = 0;
    + X5 S+ j0 I$ v* {: E% n
  13. ) E8 a, h8 B' h' i

  14.   W" J8 v0 E' V& [
  15.     /* 检测串行Flash OK */
    1 m. H8 s9 A9 c; X1 R! \7 ~9 {
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    ! N) v, M% V9 U4 C  v$ m
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    ( Z& Q; i% T9 \' z4 G0 N

  18. 1 S/ }' l/ L/ d: \# s9 B; l2 y
  19.     sfDispMenu();        /* 打印命令提示 */$ s6 g" W) F) m( T( o$ l" `
  20. 7 r- g( |- t0 y  g# d
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    6 R/ A% J& A- _6 \9 s
  22. / H; f  u+ l* w, k4 X
  23.     while(1); u& l, g+ T6 m7 |. Q0 K; O
  24.     {
    ! h0 s% n, B7 z1 ?, P
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    - ?$ Y4 V7 X3 B) ^: i0 d

  26. 4 o' v8 F5 {9 ^# {
  27.         /* 判断定时器超时时间 */
    ; w! V9 B9 R/ }4 z7 S) M: V* w
  28.         if (bsp_CheckTimer(0))   
    ; `! C: b: g) Z9 D1 a) B
  29.         {( w0 M9 K8 Z7 d' f: w: F
  30.             /* 每隔100ms 进来一次 */  ) n: h# _6 @1 X' \
  31.             bsp_LedToggle(2);
    ( d+ e( I, \8 u
  32.         }
    8 D2 ^2 m* l# w: I

  33. 5 M$ [* k. V( ?$ O5 v: z$ J) t5 O
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */; I5 Z! H3 |& I/ L0 D4 a, a( J4 x
  35.         {8 z8 Q8 K* h% V5 I- C5 ~9 Y) H
  36.             switch (cmd)
    7 ]. e' g! A% S. U3 G0 ?' x6 J4 Q( [
  37.             {
    * s4 z& V2 z9 U/ j- r4 y6 d, M
  38.                 case '1':
    : j6 V+ w2 S* ^" Z( l6 v1 e
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    1 ?( i) T% ?: X, t& s
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    , u' D2 z. w, t
  41.                     break;
    ( S& I( `' w4 K8 q( r3 \( b% O

  42. 8 j8 v! ~' c6 \$ }) Z; r7 p
  43.                 case '2':, c/ {4 K( p! P4 B& U5 h
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);( m4 x& j; p7 ~1 y( T1 S: V# A
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */4 D$ q. c' w% F, Q
  46.                     break;
    , u& ~& z, F% O( t& x- W" j
  47. 3 b4 V8 P4 C" O  m0 `7 o2 {
  48.                 case '3':
    % u+ Y3 {" Q" f9 b# Y
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");2 ~/ o' a  S  d' e
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    / y7 [. t; F% v8 d
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    - c. F: K0 K7 ]" o. k
  52.                     break;
    ' h+ t7 m6 w' Q* x5 f
  53. 2 _9 }) h  V$ n4 Z
  54.                 case '4':: E2 I: ~  l) K9 T& C# A1 O8 k: y
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");( `+ g/ L$ o! N% x4 \" Q
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    , a6 `$ F1 y/ o6 P3 r" Y3 u
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    $ H0 U% t! P, T! k& X
  58.                     break;
    ( u9 Y* Y+ h8 D1 g
  59. ; i" f( ^9 ]- E+ S  Y: V0 G2 b
  60.                 case '5':( x" l  Y/ H! I- ~
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    9 b8 f" D* b4 S( z
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */' w  m" c" g& }* z% X
  63.                     break;. I& T+ T( \4 ~% R/ M1 O& M
  64. - c% F2 w; r% E, R) E
  65.                 case 'z':8 e5 a7 n, m5 [) B
  66.                 case 'Z': /* 读取前1K */
    9 v! |7 |" C1 j5 V& `# o! i* [
  67.                     if (uiReadPageNo > 0)5 J5 r! N, W1 f4 F! Z' `
  68.                     {+ c- K6 G( n2 h1 k* ^; {- H
  69.                         uiReadPageNo--;
    ! K4 G# y% s: I5 @% C/ e( O+ |4 A
  70.                     }7 a. X: E  Q' F& K3 S& H6 t" x
  71.                     else
    , A9 S" U1 B* B( _  K
  72.                     {% |9 w6 d) X. O
  73.                         printf("已经是最前\r\n");
    & H1 z, x8 A2 m; f" y
  74.                     }
    % q/ c: c& K! ?1 m) s2 d% ], K
  75.                     sfViewData(uiReadPageNo * 1024);( n0 I% V. G! r7 m4 X3 l
  76.                     break;& {# F4 ?/ D) M% k

  77. ) s# x$ ~/ \+ m4 m4 Z& P
  78.                 case 'x':3 B5 ^" z- g6 ^+ q" d$ D
  79.                 case 'X': /* 读取后1K */3 V: Y6 o/ l7 |# Q1 S" e7 Q: m
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    % Y7 h! z9 m% F+ ^3 l0 s
  81.                     {4 q7 v, M- f8 S1 g, E
  82.                         uiReadPageNo++;
    - n& e8 o2 g' ?  I; k
  83.                     }
    8 V2 _1 e( w: D& d  E1 E, v2 b* n
  84.                     else
    3 |0 L! E8 S1 p1 V3 {
  85.                     {0 P. z2 I# \. @! S' w/ x; C
  86.                         printf("已经是最后\r\n");- [) C1 ?4 C3 a* Y6 H
  87.                     }- M! ~: o  W! D* J
  88.                     sfViewData(uiReadPageNo * 1024);
    2 C1 T5 ]1 [4 F$ ?9 \# d
  89.                     break;/ Y5 T2 n; q8 R6 ^7 }) O. ]( `$ a! y
  90. : Q5 g& f! V" R, f
  91.                 default:6 r% h  `; b& s# P- n
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */, s* E& g, [3 L8 l
  93.                     break;7 G1 S" n1 q0 W, X. O  D
  94. ' |" v' y; C& C. C" k
  95.             }
    ' s9 ^7 m$ V/ j( _3 j+ P
  96.         }7 Q5 a5 s+ k6 R$ P  y; Z. }  c1 L
  97.     }" @% f7 v  h! V$ N, L
  98. }
复制代码
& K' X: |2 L# n$ \+ V7 n. d
73.11   总结- Z5 ?) k9 T/ P0 l9 I! |
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。
9 ]6 @- T$ d. m) b- G* N7 E. d  {0 }& a
, L9 {" t" o$ D' I: t

+ n2 b) ]8 I- |* r' j5 k8 s# ^: `
赞 收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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