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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示% C: v& z, j* d
  W25Q64FV属于NOR型Flash存储芯片。) }. ^/ s; y. p6 @
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
9 e8 O: @0 [- q+ s
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

$ W. [4 [  `" A/ B2 G) X  Y$ u
$ v3 Q/ ^2 b1 ?2 x% Z4 p0 L  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。
$ _( T% a0 q$ E: g8 u6 T  s/ Z  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。1 y8 R) M% ^. h* \0 G' ~
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。
' _$ T. q  e1 }3 @5 P  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
1 N1 i' {" q6 y8 Y) C  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。( u, w0 X, H9 [3 z6 M7 j+ }" h6 h

7 W1 W$ N& u. ^3 D) k+ j73.2 W25QXX硬件设计
# F0 b: J- K) {STM32H7驱动W25QXX的硬件设计如下:, o8 z! U5 E; z

! h) ]1 x7 u/ U5 t$ I
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  W/ e" M0 ~* f& F) n. c
. \! P- s1 {; j- f, m
关于这个原理图,要了解到以下几个知识:" b: X4 B6 c/ @8 h9 D: U

0 {/ _# U( n! Y0 W  V7开发板实际外接的芯片是W25Q64JV。
6 V, f7 O$ Y% Z( c% q, d  CS片选最好接上拉电阻,防止意外操作。
5 E/ t* z/ i! m8 S) E$ k% [  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。
; }6 d" c2 A. j. B/ \$ F7 m" A  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。6 C5 U, O6 ]$ i+ u, P
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。) M6 O1 {3 l6 y* O1 Q: S4 _
73.3 W25QXX关键知识点整理(重要)6 q5 C  ^" `3 [7 v
驱动W25QXX前要先了解下这个芯片的相关信息。. t. S, x/ d: e: M% j2 m: i' j# t

1 U$ h4 m% Q# L1 y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

7 F, V! m& D0 \9 S7 }& @
8 k& z$ _9 d/ I+ D73.3.1 W25QXX基础信息

9 |( v# J, G  f( g7 ~( F  W25Q64FV的容量是8MB(256Mbit)。3 A# o( _* W1 B
  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。: t; n7 `* \& u4 x
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。$ R. }; E4 G1 I$ F- s) q, N. H
& a! T' U4 Q: \2 f
支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
: y% H7 l6 k2 K2 r2 Q  `, V4 M9 _/ ]8 f# o& W4 l& H: d: ~- ?
(注:这里几线的意思是几个数据线)。
+ q' J. Q" B& a) ]( q$ c3 P
: C- w9 C6 C% t; z: W  W25Q64FV支持的最高时钟是133MHz。
/ W: d8 J( |0 A6 Y# \% P  每个扇区最少支持10万次擦写,可以保存20年数据。
$ p- x! W+ q; Q2 u  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
  b) c, y. Y! ^: ]2 O  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。! U" b* ^% F; s
整体框图如下:
  P1 S$ i6 h6 B: D7 e# f+ \: ?
8 Z( a( G- Y3 z& i' x
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

0 \& j8 x" \' O8 g, `
$ W; k# I, S9 g( NW25Q64FV:4 H3 g& e0 M" ~# n' G/ i

9 m) U4 l. M( C2 s% i  有128个Block,每个Block大小64KB。
  c3 l& P$ B1 j- P( k1 p  每个Block有16个Sector,每个Sector大小4KB。' D, |4 g, V" j, i6 J. N
  每个Sector有16个Page,每个Page大小是256字节。
1 g: P. {# c7 v+ r73.3.2 W25QXX命令
$ c" X' d! w( O# h使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:
; {0 ~# H! Y9 N
8 p( t6 |$ _% }6 X7 u1 |7 r; _
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
) G% ~! x& E+ P) t, m
$ r; i* H1 }. P. S) c
当前主要用到如下几个命令:
) d9 Z8 h, ^7 U3 e2 d; W, @: p( N1 E) n. S
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */; |1 L2 e$ P1 U0 V
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
    5 @$ A5 c- O/ n. g
  3. #define CMD_WREN      0x06    /* 写使能命令 */# p& ?3 h5 d+ `! Q  h! u
  4. #define CMD_READ      0x03  /* 读数据区命令 */4 u4 G0 D& b+ J. `2 I: w
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */
    7 I  M/ T& V9 x
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */
    3 }! f( a0 ]" F( U# K% g
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */
    3 ?* L, v0 P$ s( ^; h' i
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */" p8 E# @$ `! x1 V7 N& g9 R8 f
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
# d+ p2 A& ^2 n- q
73.3.3 W25QXX页编程和页回卷
& r* ?& K; E6 YSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
8 s: O" h5 d; }9 d
- z/ o1 y5 C) V* x6 n+ O8 i
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    ; V5 s6 A4 H1 A4 A/ c: U5 q1 j; R
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    1 Y% N6 [- n8 `/ h% y3 Q
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   6 q% u& A3 F0 `! _; c( N6 R( M: d
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    . M: S' A( J8 {6 E+ G8 K2 `

  5. ! T! n3 }) b: ~5 u% K! w3 I. ^  e$ i
  6.     for (i = 0; i < _usSize; i++)
    - i4 ]. ^6 s2 u% Z
  7.     {
    % r( ]6 T4 C; }
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    ; e  c9 }4 \3 n/ k* [/ s% a* G2 w
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   ! G; U* B/ ^. d9 f. O4 M) ?  X, h
  10.     }
复制代码
3 J; n9 l/ G) ^% s" k
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
, U/ m  j5 l9 \* s9 a: Z: o7 s4 b) I# ]: |6 F* u! v  B& D
比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):8 E0 L  |$ ^' t

1 F0 L# d; u: }9 |4 J5 r
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    : L, q( P: b1 Z0 H
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
" E/ w9 c/ Z7 Q% q5 P+ L! Q
  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
, D' h9 @- `2 v+ m0 A  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);; j; @9 {1 @) K
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);; E; e, D5 y) O7 I5 v' Z, Y  t
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)9 q$ N& G& I! M5 u! @" D
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
& T+ R- Y; q% z  j) l/ o6 o
/ v  \) c& v9 F3 ?; m/ Q4 x
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

9 T' i+ X/ m# l$ X1 ~% x, f$ b# u. v$ F8 [# @  z7 T2 S0 h1 z" g
( y5 \) N$ a8 W% c, k) s8 p
73.3.4 W25QXX扇区擦除& Y" k  s3 g1 h+ K8 i6 S
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
( A2 P: [6 G3 \. Z2 I. E8 V' [
' p' l1 i5 l8 Z7 u如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。! R8 b9 B! a; R& M: |7 t
& O/ D, @1 U# L: E) M' e
73.3.5 W25QXX规格参数
) v/ H* G- O1 |9 {2 _% Y  i# l
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
* G6 k7 y* Y' ?: v3 n, Z( J1 E5 {% i! \1 Y4 X- ^
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

. u8 {3 [+ j/ G
) m6 i4 ^* x% K' V3 N8 {- ^+ F9 D  页编程时间:典型值0.4ms,最大值3ms。3 K1 |7 A0 A. w/ F! X
  扇区擦除时间(4KB):典型值45ms,最大值400ms。! @' A( K! S: M2 Q% A7 e+ h  H
  块擦除时间(32KB):典型值120ms,最大值1600ms。
7 `: b* e4 s2 Y  块擦除时间(64KB):典型值150ms,最大值2000ms。
) S$ w& b5 B0 P7 U, u. m' }  整个芯片擦除时间:典型值20s,最大值100s。: F+ P* S' C( N3 Y; w

+ y, C) D; b: A7 F: ^! ]) y: ^4 T  M( ?2 @
支持的速度参数如下:% ^1 u* ]/ X' v* T8 @( U

* D8 B4 M& }6 b% g
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

" }  J$ I9 n& M' x0 }6 M7 C
. `# g  [& d: P# X4 T' P! f可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。5 Y) D; R) W0 l% ?' f

' V/ g# P* Q* Q: v/ I73.4 W25QXX驱动设计( Q5 X8 M7 k, x; K" Q
W25QXX的程序驱动框架设计如下:; M7 K: ]/ L' O
5 w+ H' A  |; c6 y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 G2 q% ~6 q5 g. {7 E+ _
9 l/ D# P9 U; ^' Y. z) l0 j有了这个框图,程序设计就比较好理解了。
4 R6 N4 I8 O& E6 J% a' q# z5 i
  v0 f5 M9 U" m' b6 p73.4.1 第1步:SPI总线配置
6 l4 D% \+ \& D7 L; Yspi总线配置通过如下两个函数实现:
$ B& |7 y. v! Y/ ?+ X( \/ D- M$ [
  1. /*
    ! L" r& V; {( Y. Q
  2. *********************************************************************************************************% R( \) w1 L4 f$ l+ E7 @6 C. }( Y6 j
  3. *    函 数 名: bsp_InitSPIBus7 ]8 B; s- f" Q9 h
  4. *    功能说明: 配置SPI总线。
    1 R; Q! C! [+ F+ }
  5. *    形    参: 无
    1 y2 P# X$ n& ]  o* r# m2 D
  6. *    返 回 值: 无9 l% v( I/ }2 @) }5 ]
  7. *********************************************************************************************************+ |. S% N1 H/ Y) k
  8. */. U5 R. q6 M. |; c+ F
  9. void bsp_InitSPIBus(void)
    & H; y2 I3 n" a4 [
  10. {   
    1 ~" T, i- o' c0 L3 A
  11.     g_spi_busy = 0;
    " d- n" z( e' o8 [1 a; E/ t
  12. : `- Z- v0 R+ t7 a, z
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    9 `4 m$ [2 K* w( v  Z
  14. }
    - g& _: P) ~4 H- t5 ~- b

  15. * ]: j/ ^; [1 B" ~! U8 F' V% ]
  16. /*! M  m& W) E. c2 q
  17. *********************************************************************************************************
    7 @& h- i+ f' v3 w0 t; B6 j
  18. *    函 数 名: bsp_InitSPIParam
    & |6 d; d7 s5 g' ?
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。& p+ l+ M# K0 T/ \
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:2 Z1 s, a/ S( n9 J$ h/ S' C0 f
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频7 U0 m: q9 G- Y
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频. m- O: G3 i1 h9 [; T1 }+ M6 z
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频
    7 U- q/ g7 N' ^4 I0 V9 F
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频; s8 L9 e4 Q+ y2 A& z& Q
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频
    * i' T, v% D2 a
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
      _9 \. e( a% T) G, e
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频0 }9 _8 i/ E, ^+ m* ?" I
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频7 z6 B- _) U: U2 R* m5 j  ^4 x0 {, Z
  29. *                                                        
    . N1 _8 m" b" o( C
  30. *             _CLKPhase           时钟相位,支持的参数如下:2 T; v; L3 l. [  t- q3 ~" ]7 E
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据! a+ B, X: {/ j& d0 g
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    7 A8 t! n- S$ X: v2 ?: K
  33. *                                 
    ' z2 ]! V0 ]# o6 `
  34. *             _CLKPolarity        时钟极性,支持的参数如下:# S: \2 a& I1 G9 m
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    4 V4 v6 X% o' d7 M, O
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    ) N% ?" V2 A) R: N/ M; S; y- S
  37. *. {4 t% k) E% \+ S8 ~1 y' U: o' R
  38. *    返 回 值: 无
    , ]7 _" x2 b2 j3 l! O/ }  c
  39. *********************************************************************************************************
    ! C) c1 Z! r+ D
  40. */1 o6 ^' K# @" ~/ L6 m0 O4 H5 ]+ E( A
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
    2 D4 I5 E% ~8 ?- A& M9 M
  42. {
    5 H3 A& `8 d: g
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */& G3 Y" ?& D) t( F# x2 g
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)6 Z9 _% T5 ?6 x) J  q; g4 i4 H
  45.     {        
    " m; v1 V9 M. H% i6 P
  46.         return;
    * P/ S* F5 G! O4 I5 F! b) k& z
  47.     }
    : @4 X9 E3 P" x
  48. & M' Z8 Q% ?) l0 |2 U
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;    + b6 q5 C! E3 r- {+ |8 x
  50.     s_CLKPhase = _CLKPhase;
    ! D) e, `) ^' K( M' K8 u
  51.     s_CLKPolarity = _CLKPolarity;
    1 B- }4 ]3 \6 [& a9 e" X

  52. , ?* H1 v1 Z8 @$ K4 g  ]

  53. 5 |  U  q; v) m/ z! [- H' z
  54.     /* 设置SPI参数 */4 [3 }& j6 b. V- C
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */
    ! R9 z& y% u( o$ v8 L) Y
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    / T, g; y* _. Q9 Z( d9 L
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
    . ~5 Z+ R! m- u' ~
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */- Z" F& f& s" Y; l4 z# v" Q$ m. M( k
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    5 T9 B2 ^2 j0 y
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
    6 E3 R1 V1 D; d8 s) U
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
    6 }/ e0 m5 @, z, v' F2 s2 b+ l2 D
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    2 s( G% s+ Z( y  O+ C, a# |
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    9 F+ J5 m1 @9 G
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */' E6 \# v/ V6 x3 t, p- ?
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */* a4 W1 o6 ?* N
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
    6 R; f# d  x  N+ @4 S  U$ Z0 _
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */* j- Q! l4 U3 j& X4 j5 y0 ~
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    ; {& I. Z$ Z7 d# b- O  u3 ^( o& C
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  ) b5 V$ O# O( t* g) {) o
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    . j9 n* \8 ^8 R" N8 R
  71. " d& r. m# G# Y- N
  72.     /* 复位配置 */
    + l  i( u# F& _! a4 _
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    " X0 R9 Q+ C2 N- Q% e6 \" {) c
  74.     {- ~$ o7 D( q7 [- j8 O
  75.         Error_Handler(__FILE__, __LINE__);
    9 A( q) h* `+ t" a
  76.     }    $ [/ l2 F1 H  {3 N
  77. $ `6 z) I& {. b. D3 Z0 U
  78.     /* 初始化配置 */: D2 i2 t* O' Y/ j4 u  ^7 ~; G
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)0 h) P' E# U2 K$ a( P8 i
  80.     {" O) t; D) o! ]
  81.         Error_Handler(__FILE__, __LINE__);
    8 e( N% Y7 s  K  t0 {8 f( c- n( Z( g
  82.     }   
    - }, t$ {" |6 ]$ n6 r6 X; w
  83. }
复制代码

# f3 B/ b1 R" L) ^) g  y( n" w关于这两个函数有以下两点要做个说明:  [/ ?/ U8 z9 |) ?/ |6 y

) t  {4 q4 T" g* B  d  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
, a5 C8 o  P7 H+ C8 N  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
2 h& U5 c9 l3 x, f& e0 \73.4.2 第2步:SPI总线的查询,中断和DMA方式设置3 ?! V1 ^- K: O/ a  D
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:% M+ x+ x: U3 j0 s& h

/ K- I, a& b" O; T" t
  1. /*6 d; ?4 X& D/ ~7 K6 h5 c
  2. *********************************************************************************************************
    $ w9 E4 b3 J# E
  3. *                                 选择DMA,中断或者查询方式
    $ p$ r  R* O6 \2 y6 Y  `, {. ~) n
  4. *********************************************************************************************************
    ( X) d* u: `7 ]& q
  5. */
    0 J( |$ R7 s" M3 J/ \
  6. //#define USE_SPI_DMA    /* DMA方式  */# M* r9 t: p/ H
  7. //#define USE_SPI_INT    /* 中断方式 */
    $ q  h9 |: e% m) O: E! k
  8. #define USE_SPI_POLL   /* 查询方式 */
    $ H2 P5 b+ }! u7 r) C6 O
  9. ) F/ X* y/ x+ R  U- x
  10. /* 查询模式 */6 ?+ N$ G' c7 @: K" ~
  11. #if defined (USE_SPI_POLL)3 s/ I8 ~5 |) \; J) u* W4 O: G
  12. 9 S$ Y2 I1 i6 r) F8 m, ?. J
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  2 J% k' k* R: n8 r- E0 Q6 W
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    5 }  E( s3 f, `" V, ?& D
  15. & a; h; n" M* O- s* z, c
  16. /* 中断模式 */3 B' a, {' \0 \5 n4 _
  17. #elif defined (USE_SPI_INT)
    3 A! n/ A: r2 a( O8 L

  18. # b5 M% |# C% O; p! X3 |/ A% \
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   0 n" M$ k, Y% L3 o( s
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];, n+ l/ d( Q2 D0 I2 l( ~3 S: D
  21. & v, c. o  m0 U  |/ l+ v
  22. /* DMA模式使用的SRAM4 */
    & Z" m. E% E; P% C/ J' W
  23. #elif defined (USE_SPI_DMA)
    . _+ o5 g$ [" ]2 V2 d7 R# @) j
  24.     #if defined ( __CC_ARM )    /* IAR *******/; G+ g' t5 }# i; L9 ^/ S
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   - d7 k9 L! }5 U* b2 }& e
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    # e( D! G$ @4 f3 I* v2 A2 \9 |
  27.     #elif defined (__ICCARM__)   /* MDK ********/
    ) {) ?0 Q: W4 N8 }
  28.         #pragma location = ".RAM_D3"
    3 ^. {1 G* e2 N$ t, |
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   4 b) C1 M2 R) P/ d' N0 w
  30.         #pragma location = ".RAM_D3"  ]4 m: v: _5 O4 d# U" Z% a3 \( R+ v
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    3 p, n6 S* d7 K8 i5 r( V
  32.     #endif4 ^0 {) X* @( [% @8 c* ]
  33. #endif( a+ t2 s3 g2 l, \9 I) }5 j: X4 a- S

  34. ) [' b5 u, f& n3 c
  35. /*
    & B5 |8 m; d1 u; O
  36. *********************************************************************************************************
    # {7 ~$ x* g3 q' [' n
  37. *    函 数 名: bsp_spiTransfer
      w( H! F/ u! W7 ?& y- y
  38. *    功能说明: 启动数据传输
    & X: Y) o+ M1 |; W
  39. *    形    参: 无9 A4 d& O. W3 L: `1 k4 g: ?" V' L( t
  40. *    返 回 值: 无
    5 w- E6 s: s( _2 G* |$ L$ f% }
  41. *********************************************************************************************************6 k) I$ {: u' s2 v8 ]) ]
  42. */; B3 Q( b% J' Q$ Z& C& k; X
  43. void bsp_spiTransfer(void)
    - g1 v# W9 N) u
  44. {
    % N7 y/ o. _, ~# t! w* ~; u
  45.     if (g_spiLen > SPI_BUFFER_SIZE)4 N1 s, z- v9 q
  46.     {
    4 _) F+ M( ]3 T/ f' j2 c4 U
  47.         return;& i- t" h5 L& x5 \! l; Y$ }
  48.     }( Q; u) ?5 m: C/ N( r: a7 ]4 k
  49. ; f  U- f  z( t  Z: S6 G2 l
  50.     /* DMA方式传输 */
    / n5 A* j& k6 M2 _% Q' S# J  r
  51. #ifdef USE_SPI_DMA# e4 E. ?( f+ z  d
  52.     wTransferState = TRANSFER_WAIT;1 V( A$ E/ D5 D3 ~
  53. ' D  H8 C4 A( Y" c
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
      g& `# _) ~/ F) y9 C$ ^
  55.     {
    $ i. `, p3 ?' }, G9 g8 h
  56.         Error_Handler(__FILE__, __LINE__);
    0 b3 n+ q# `& z/ t4 _0 L! C, J. a
  57.     }
    % @! ?, O9 C6 X5 D* s

  58. ! \: k5 m) w6 e5 |- E
  59.     while (wTransferState == TRANSFER_WAIT)9 a1 N7 u# s+ y+ w
  60.     {
    7 c( r1 Q% R3 v, s0 M' p
  61.         ;0 ^7 ?3 R# R. V) `) p9 u) s+ U2 e! a
  62.     }) l& x' B$ }$ i2 `
  63. #endif* D; N. W8 ]6 j4 W
  64. ( q) S& ]' f1 w
  65.     /* 中断方式传输 */   
    . \$ ~& W$ x) r8 A! K
  66. #ifdef USE_SPI_INT
    . V* e% B; ]1 C2 C4 r' ?( o1 a
  67.     wTransferState = TRANSFER_WAIT;
    # B1 _' }: p) m) i* [# {
  68. ) g6 D# {4 |( _6 W* {" u( v
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
    ! m5 j+ M' Y! ~7 u9 @2 q: X- j
  70.     {2 A# M2 V4 W! i1 E7 X% R: G! |
  71.         Error_Handler(__FILE__, __LINE__);
    6 D# }3 M8 n$ S9 r1 @
  72.     }
    ( _' C9 v, {, R$ J

  73. ! |- f# K' o! j( Q
  74.     while (wTransferState == TRANSFER_WAIT)- a  n$ N& @$ p: }+ U! W
  75.     {6 f+ x( M7 A/ k* G8 S
  76.         ;! W- o* f* i$ v- L5 M, ?8 A6 y6 a- u
  77.     }  ^4 A( Y5 ]8 N$ {- l' Y* R
  78. #endif
    ' {. b7 I4 o: D) ^; K8 ^+ {

  79. ; W* S2 N4 E$ V( z! }$ x0 {7 X
  80.     /* 查询方式传输 */    2 R+ n) R9 A6 m. c! W; h
  81. #ifdef USE_SPI_POLL
    : B* r+ U- q2 ]  D* K
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    - _, ]3 E  M" G- R, B
  83.     {& B" f% X# ]! B9 h
  84.         Error_Handler(__FILE__, __LINE__);( q0 ?' @& N. H, H" S4 B) t7 m
  85.     }   
    6 R: I( I) F; q# L0 a/ O
  86. #endif
    & T7 `  m" @: R8 U
  87. }
    $ A" U, N) W" w1 C; C  t
复制代码
5 l/ X2 A/ i# F; M3 Q6 ]
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:1 m: U' s! e. D$ F0 c/ w

, e. P/ R6 @" P6 u( B+ N  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。+ d- ^" ^/ s' k% ~& w/ ^! x
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
  a! q/ N0 U  |3 ?( u# O& c3 @
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */
    5 R& `2 t8 L) V( N/ Y' Y+ Z3 J
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;& ?7 ?4 |% C7 g
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;/ N5 f) R' u9 S$ e4 F4 Z; b
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;! a# Q" \8 b7 _8 F
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    4 i- S) Z: b) j
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;# P9 K3 A* G; p2 z6 W* X
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;9 c0 j0 |2 f2 F# Z$ \+ p
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;7 |+ Y6 |; p+ K/ t  i
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    5 A) x, {* Q1 W3 w  Q4 q) l
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;( P* J; [+ [$ B4 x$ u( ?7 i. }
  11.     MPU_InitStruct.SubRegionDisable = 0x00;$ g+ f8 Q6 k: u6 u$ u
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;0 s7 o' f* g8 Y# g6 G' @8 }' Z

  13. 4 d' q- O4 i1 u2 r% K& p
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

9 t7 e1 L4 G4 ]% w- b73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

& o1 |* |' T7 s% v首先回忆下STM32H7支持的4种时序配置。
# @7 E( q  v5 ^4 V3 S8 d7 D  T+ |; s$ _) e
  当CPOL = 1, CPHA = 1时, U, S; J& [3 r/ `% S% i
0 R* b* }* Y# K" E6 N
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。3 h; \7 }# h' |% {8 C( D

6 J5 s5 n: x' @" S$ i! h; R  当CPOL = 0, CPHA = 1时) ?  ]3 x: r! \4 T& r8 F" F

4 c3 k* b% P' m1 K" ESCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
; C" R; f% J* E. k! V/ u2 U1 Z6 M0 c% V
  当CPOL = 1, CPHA = 0时
7 A2 M! ?3 W) H4 F2 m4 U/ S, z0 z/ a6 Z2 X! {
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。3 C- a% v. w4 u# W+ a. e- g, Z0 S

0 y. _4 V0 P) e 当CPOL = 1, CPHA = 0时
. g6 E: d+ m8 d- ?+ }9 D' V/ o  }$ Z" S, e$ o9 y' J' m
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
+ T: Y. I- e5 R0 n  I" R; b+ Z& H3 i" F9 I- q2 c* z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
7 o- Y/ T/ ]6 w( \' k
; S. _- G, p) L% D
有了H7支持的时序配置,再来看下W25Q的时序图:) V* m0 U  p& b! X

! |  s9 {8 v5 N: ZMode0 : 空闲状态的sck是低电平。4 f5 d: Z; Z: c. x0 |& d

5 e- M7 V) R1 \Mode1 : 空闲状态的sck是高电平。
: d: U: Z2 i, [& y3 m) l
% I3 S: ~& f; y. g3 ?
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
" {; E1 k! ^6 f- e
9 \9 y0 C! \( Y; x! j
首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:
$ L) _4 P1 d' o3 l
) {' i" G2 v2 i' y  A5 {CHOL = 1,  CPHA = 1& v" o: Z: n" v+ j6 H
# p& Y1 c" Z3 {5 j, x3 w: }4 c( I  C
CHOL = 0,  CPHA = 0
+ U3 ]* l6 J/ ^0 C1 a' W7 M. B" l, d% m4 f6 T0 H1 ?9 u8 u
对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。
4 ^3 l( S" g+ ]/ C: Y! k- ]
/ J2 F% c5 _5 f- C73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制) D! P9 B; I5 t6 [3 Q: Q7 H
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:1 p7 B/ F' d7 B; K+ z" J* W

4 D9 ~) n( N: s+ o
  1. /*; D1 ]1 h1 ~1 @
  2. *********************************************************************************************************
    . P0 R$ W* j8 b  B
  3. *    函 数 名: sf_SetCS
    & R- e% n, f7 q) n- k
  4. *    功能说明: 串行FALSH片选控制函数8 K. m! Y1 \9 n7 g7 L$ U  U
  5. *    形    参: 无
    $ R' j1 ~2 `* D& J2 r$ ^; o
  6. *    返 回 值: 无
    % }9 m. ~7 ^0 x" B
  7. *********************************************************************************************************
    + l& U& k3 V( u: a! q" ^
  8. */
    + z& A! v% [, ~0 e7 o! y
  9. void sf_SetCS(uint8_t _Level). `8 F/ ^- Z# ~3 L
  10. {
    7 P% q! }6 X7 s% |6 f, f
  11.     if (_Level == 0), ^3 o+ G  \9 M( I
  12.     {
    + q8 D+ y6 X: R
  13.         bsp_SpiBusEnter();    7 F1 }6 N: y* B1 c# r, |/ G
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    / R* c6 Q- ^$ h- H1 f5 s7 g
  15.         SF_CS_0();
    1 i2 Z: l+ {' A  ]" S
  16.     }
    * }3 A# b; u# U1 Y$ I
  17.     else! n1 _( X! W+ l  d' g
  18.     {        9 A! C9 o9 l* m2 L, F- h7 {
  19.         SF_CS_1();   
      y: s7 G  o5 H# {
  20.         bsp_SpiBusExit();        
    3 q% P5 ?6 U/ E! w: s
  21.     }0 t$ t- ~6 G0 t' o# M# ]) U* V
  22. }1 `: D! c* D, s, Z
复制代码

* r- {( Y% T' @# Z) o" s% d
& H( n4 c1 {! I  K3 Q2 n通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。4 C  \3 p, O' p( _1 e
, p) Z8 z( a( S5 W, a% u
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。
" \8 b& J' Q$ M
8 l- E. [: o9 y73.4.5 第5步:W25QXX的读取实现
2 q% [. R+ o4 K6 }
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。
3 E  _6 V5 x1 _3 {0 f; Y! A: \: C
) T0 d4 p5 G# \2 F
  1. /*
    , p( v" F, I" ~  `: O, _* A
  2. *********************************************************************************************************
    % s  N: h! {/ u( f. W
  3. *    函 数 名: sf_ReadBuffer
    ! t# C5 b, u0 m( d
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    6 x# o3 G; v: B* I
  5. *    形    参:      _pBuf : 数据源缓冲区;
    4 z  k  |0 C$ W- V; I8 Z( E4 C
  6. *                _uiReadAddr :首地址
    : ~; ^. B% C0 U" X( t$ ?( j0 c/ j( g
  7. *                _usSize :数据个数, 不能超出芯片总容量5 b, c1 p$ r' y5 _: r! s
  8. *    返 回 值: 无; w8 f1 Q$ R& X0 i6 q0 b3 x
  9. *********************************************************************************************************6 S5 S7 M( p/ R4 f
  10. */
    ; Y' m8 p0 A; }: E. }0 O! Y% M
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)2 u! L- G/ o  b  ^, n7 |# {) m
  12. {, h- Q) N& T1 `
  13.     uint16_t rem;; _( l4 z! L9 x
  14.     uint16_t i;
    # M- A% n0 s, Y  D

  15. ) P+ O8 I' M. o+ D( |( h
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */! r9 B2 K/ x& \+ B) C
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    , T" `" u' c. L5 j7 o
  18.     {
    , L8 H& w) y2 B; b6 v5 G
  19.         return;
    & m& O7 t  `% n* r$ t" V4 T
  20.     }  z& ~1 `4 t2 O: r0 C6 p* L, d0 z: p

  21. ; }6 X/ ]2 p: Y# R; J; q
  22.     /* 擦除扇区操作 */
    4 |' o7 r6 t6 \9 b5 S: B7 x2 Z
  23.     sf_SetCS(0);                                    /* 使能片选 */( V/ C) z6 F2 O/ o( c8 A/ N5 C
  24.     g_spiLen = 0;
    " p2 W8 e- h2 T* L
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
    : \0 N8 c' f& M6 ~
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */! _3 ~$ w8 k; ^; O  O5 t8 D4 Q
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */' s7 p& N* K- n5 L' {) O
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */& L3 a" B" r. t3 S
  29.     bsp_spiTransfer();' v+ F" j( k1 i8 V9 k$ O" `6 S) V
  30. 5 @5 h7 C" e, O3 ~* [- G
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 *// f% M+ {  B! U) U+ T5 L( ^5 B
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
    : A, y" m* t$ C) M
  33.     {$ G" o3 x  v1 n7 d# s6 l& R
  34.         g_spiLen = SPI_BUFFER_SIZE;& `/ |& n: a/ ?& y9 J
  35.         bsp_spiTransfer();. [! ?) \. B+ c. \5 K8 V3 W
  36. / A( e( o( L, u" T$ R) C
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    : c# v  z0 ^8 ^( D
  38.         _pBuf += SPI_BUFFER_SIZE;& c' q: k* p  R* W/ A  ~
  39.     }' a+ F6 I' z9 P- Q( `6 R

  40. # B( \  }$ I: v* Z: p1 m, _. K) V
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */
      v) Q# V- P/ S: t
  42.     if (rem > 0)4 ]5 e  V  k$ m5 t* w/ v2 m# }) B/ I
  43.     {
    8 `4 l& e& I6 c  v8 @% U: E: R
  44.         g_spiLen = rem;
    6 E) m9 y& e6 z
  45.         bsp_spiTransfer();9 g9 x  P) }- L. c- X  n
  46. * M: S( r- v! m7 `% U! g
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    ' b8 f8 ~# p6 L0 R( |  i+ f6 t
  48.     }, a; s# s( Y% [2 U  g

  49. 8 I- [5 V& Z4 f6 ?
  50.     sf_SetCS(1);                                    /* 禁能片选 */
    - M9 u/ a; @# i
  51. }
复制代码
4 |5 ?! R: |+ a5 d) Z  Z

: x; @* T& C0 N: _- h5 M! }$ A+ P这个函数对DMA传输做了特别处理,方便分包进行。
* w: o6 E3 p; y. K* V
- y$ K8 Z& U% ]1 N, T$ T73.4.6 第6步:W25QXX的扇区擦除实现
6 |7 J6 S9 G, n) U
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。1 Q0 F. H; w, p4 f8 p6 }5 ~

3 k  j2 y' J- \( ?( E
  1. /*
    4 c# V2 Q( I& S/ t# a
  2. *********************************************************************************************************
    . \) C( e9 n  d' ^& _
  3. *    函 数 名: sf_EraseSector" v+ k: y9 d1 ^7 L* p
  4. *    功能说明: 擦除指定的扇区, ?* V6 H7 f  @: t4 ^) k" T
  5. *    形    参: _uiSectorAddr : 扇区地址/ ]+ f! d& W0 d4 `
  6. *    返 回 值: 无! @8 Z+ w' J; L* C, n* _
  7. *********************************************************************************************************
    # {% d" z3 P( A+ a+ |5 `
  8. */! A. x" t& h8 A1 X) W
  9. void sf_EraseSector(uint32_t _uiSectorAddr)  }& K5 ^* O- C  z4 \& n7 L
  10. {" @$ l) ~/ e( |. E
  11.     sf_WriteEnable();                            /* 发送写使能命令 */2 p0 s) I! w) w

  12. 5 u! p6 M$ H+ v; c3 K& D% G
  13.     /* 擦除扇区操作 */
    & L4 m# T, T' p, T" _
  14.     sf_SetCS(0);                                /* 使能片选 */
    7 H& U( p2 Q/ o* t  w; f9 ]
  15.     g_spiLen = 0;2 N/ t1 W) Z; V2 L* ~
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */) Q0 C& A/ C' O
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    2 @$ z8 z5 Y4 V( e* J' J
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */
    ! L. k: O* ^1 f' a) X. v
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    ! \. V# {3 h& }; E2 d
  20.     bsp_spiTransfer();
    / Z( g. }$ w0 V3 n$ {/ t, f- s. S9 G" J
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    8 v* |( O6 J& m- t; y
  22. 6 u9 i" J9 s# V  ?& K
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    ; o7 V" ^! E3 `
  24. }
    8 H7 E) T, I+ n9 n+ A  R  q
复制代码
- i; w( B0 Y% G
  ~' U/ ]! x8 J& d& g( M
整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:
/ w/ l$ p! D6 Q4 n3 d) Y; G. N' A
  1. /*
    , Q+ }0 \8 ^  G0 @; p/ q* Z) s9 v" @$ t
  2. *********************************************************************************************************
    ! K8 Y2 z1 p( o
  3. *    函 数 名: sf_EraseChip
      t. N3 x' j* f7 T. L  f7 \
  4. *    功能说明: 擦除整个芯片
    8 D6 T) y; d* `& S5 Y
  5. *    形    参:  无( o5 g8 e. @/ P4 }3 }4 B3 {+ q
  6. *    返 回 值: 无
    ; d; N) T& F4 J; w
  7. *********************************************************************************************************8 J- U1 m* y+ W, x
  8. */
    9 y  X! H- U* I# S
  9. void sf_EraseChip(void)4 y/ M5 Q8 A! p4 O$ I$ x5 ~( @
  10. {    6 \3 q% a( J" P. U$ w
  11.     sf_WriteEnable();                                /* 发送写使能命令 */, O; b* ]! J7 P( H7 I0 d' u+ O
  12. 4 y& }& n% \  A: N* I
  13.     /* 擦除扇区操作 */3 x9 M' o6 n+ B) N& Q
  14.     sf_SetCS(0);        /* 使能片选 */
    ' a- _& x  W$ n5 p2 C
  15.     g_spiLen = 0;
    % ~6 T$ A$ j3 V  _" N6 R, H9 R
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */
    + e/ H9 |1 J0 L) a9 q
  17.     bsp_spiTransfer();* H/ d! {6 `# S( ^
  18.     sf_SetCS(1);                        /* 禁能片选 */, C# t, c5 ^" D5 B3 O; o
  19. ; I- }! U' M# c1 H2 J6 M
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    + C9 Q- `& G. M& v, J
  21. }2 O( j+ }6 \0 q1 F
复制代码

$ y1 E6 C4 f" _9 }73.4.7 第7步:W25QXX的编程实现$ i8 Q5 T* b, j9 X' H
W25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:) a( Y" C- t7 ^/ R9 Y* x# l5 X+ Q

8 \; @! k! u4 g. s& f
  1. /*/ u6 d% A6 M2 j
  2. *********************************************************************************************************
    & D# S/ y% o+ |; @
  3. *    函 数 名: sf_WriteBuffer
    ; B2 C* |% L) W+ t
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。' U2 z. ]1 ]4 R* [# p
  5. *    形    参:  _pBuf : 数据源缓冲区;& E# J% j- J  W# m0 I- z5 _
  6. *               _uiWrAddr :目标区域首地址
    6 U, [* N1 A0 `* l5 J6 e
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。
    . J3 N  p9 n4 n+ L
  8. *    返 回 值: 1 : 成功, 0 : 失败. A8 }# B* J5 S9 x* b" K3 b! a
  9. *********************************************************************************************************
    ; c. B/ o* S6 u; x0 ^8 Z
  10. */
    , u- d# b5 A# G
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
    * k2 c& H9 R$ I' J9 U+ [
  12. {( B& ?) f( ]. ]1 A
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;/ r6 b; u2 q& s- D5 _7 ~

  14.   M' W6 t! [* K2 Z- X
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;/ k, e+ p8 Y8 u& m/ Q
  16.     count = g_tSF.SectorSize - Addr;
    9 h) b. k; `3 w: `8 _
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;! N& T, F/ X7 g/ u# Y, E+ }( O# x9 P
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;
      t/ E9 v* b: a/ j! d/ w( c
  19. ; b: S2 c2 B- o6 J. G2 [0 b
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */
    . P- i. A. l2 z# x% h
  21.     {
    9 m, y0 a& L9 |6 X( m( U4 y) z
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */5 k' x$ w3 K0 K- @
  23.         {
    9 |% h: u5 I+ t$ c- K% c
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)4 {/ j6 g* @. E
  25.             {1 @5 `) Y, s! S/ b
  26.                 return 0;
    * ]. |$ m* l4 d- G& P7 S: P
  27.             }, k& l+ Z1 q. I( x7 b
  28.         }  q3 D3 _% m' T
  29.         else     /* 数据长度大于等于扇区大小 */& }% [( `( g* @) R, O) y+ z
  30.         {# U! w8 s+ b# e6 j7 ]
  31.             while (NumOfPage--)
    8 T% Y/ g' S/ w0 ?8 h
  32.             {( {* b" _( I& ^/ a+ B6 J
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    ! k( G2 y7 u9 z! L3 \
  34.                 {
    & Q3 t4 p" y1 V7 p2 [' I2 g# W, \
  35.                     return 0;
    : z! A2 \/ f$ A- M; {' L' h9 R
  36.                 }
    6 g% n* ~! h4 z5 y# L) i8 a1 m3 @
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;; }, z. X: r) c# M$ @2 q" V8 Y+ L
  38.                 _pBuf += g_tSF.SectorSize;/ Q+ `4 Q) v+ J* N! D
  39.             }
    ( y% t8 Y' ^! V" \
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    ! i1 p; ~! o9 W7 \) K- p+ \  s2 y9 A
  41.             {' B  P2 v1 l# ?1 L
  42.                 return 0;/ k6 {9 s, z8 X" d8 B2 S
  43.             }1 u- d/ e* r+ s) e4 G  O  l) |
  44.         }
    ) ?' v  K9 J! o. C! @4 D/ g3 {
  45.     }
    . p1 g+ i! X: H3 D
  46.     else  /* 起始地址不是扇区首地址  */1 e# B; G" J0 ?/ j! S
  47.     {: A% Y* n5 p# z6 H
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */0 V+ e& J9 j4 P; a3 m
  49.         {% }# T/ k5 ~" P: I9 S7 U2 E' k4 `
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */
    * w7 w) Q4 h5 e2 L  P6 s2 K2 h
  51.             {
    2 \  X9 s3 q  l: {$ x2 N: P4 F, B
  52.                 temp = NumOfSingle - count;, D% y* V) z+ i

  53. * @% Z# j5 W+ O0 n; U
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
      v1 K& U" e! B8 l& h
  55.                 {$ e: y# K- K" v# x
  56.                     return 0;$ V7 A* ^: Z2 @5 s" [
  57.                 }
    6 B6 w; V" q& K9 b; n0 W/ f. ?
  58. $ g; b2 z% `- {0 X
  59.                 _uiWriteAddr +=  count;$ m3 T" P2 ^% U' h2 C, [
  60.                 _pBuf += count;
    7 U' s5 l* ^4 }0 g
  61. & O" ]( U6 {9 G' y7 I
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)% W8 j2 l* C# J# q
  63.                 {
    % |9 t" x. k: W
  64.                     return 0;
    3 m8 }7 {% m0 d% G; w
  65.                 }
    7 a) P+ x' _. U, F* P, N
  66.             }
    / Q2 ~5 l6 r3 n# J, W
  67.             else
    ' Y, q* D$ v5 O+ c' j; Q
  68.             {4 u/ k' @" }% o8 z. f) g
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    " j) J# @; j+ d2 N: s) f
  70.                 {2 k; V! t6 k0 t% g" f
  71.                     return 0;# H% e" S" m9 x7 x4 h3 t5 Z
  72.                 }
    1 Y5 f/ A- J& N; u' _  S6 J
  73.             }' a' _. X  l# m$ q
  74.         }
    / k& G$ C  c# t' O
  75.         else    /* 数据长度大于等于扇区大小 */7 e( O& C/ d) R
  76.         {
    ) L( b8 l3 |6 L
  77.             _usWriteSize -= count;
    # {1 n' w7 V9 W9 n
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    5 k9 \( h+ r) A( x/ N1 H: G, w
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    7 q2 t" ^# U# s0 \  ~
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
      G6 P1 [2 C3 [. `: C
  81.             {5 u, k) l# R' u$ @; R, w3 m, X$ ^
  82.                 return 0;
    & ~* J7 T7 ]) g& i9 k2 f5 E  ^
  83.             }
    : w. w4 }0 ~: Z& p- \$ i' f
  84. / @) Z4 \7 A% i4 [, d' ~
  85.             _uiWriteAddr +=  count;
      L* f2 [! z1 f5 s3 F
  86.             _pBuf += count;; B! _; M- |7 |. a# O3 {
  87. & j& X( v+ @4 k  G6 M  f0 @( w1 z5 s
  88.             while (NumOfPage--)
    * O* e9 B5 x, ~$ v8 J: N% v: \" Y
  89.             {
    : J5 w' j- f) Y; k. R
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    9 S( f( c. Y# l2 ?9 c
  91.                 {
    2 z6 V/ K  a8 i. }2 P; [2 c
  92.                     return 0;# c) S  G- R' {+ x
  93.                 }
    / |/ F$ r: r# e$ X% k! o3 J
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;+ s# e+ X% J' b, _" {
  95.                 _pBuf += g_tSF.SectorSize;) @5 h- @  u% b8 z4 T# g  q& n
  96.             }
    7 t: a& G& o6 K8 U. l$ h, O
  97. ) Q8 j5 C% }: R. I) Z( q
  98.             if (NumOfSingle != 0)
    . W9 h/ N0 x& O, F( H6 }
  99.             {# {: I; b$ c8 L7 I  s5 T
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)0 x$ y8 t* q& @- g& X& _
  101.                 {
      W  L" P% H, V3 U
  102.                     return 0;
    * ?7 n/ V+ ]! T) Z, o  ~- b
  103.                 }, z( z3 G3 z5 p2 {
  104.             }# `7 ~. S) r: b7 V: h
  105.         }
    - v( P9 N# X$ e+ d2 n
  106.     }
    2 A4 E$ g3 x, m# ~1 s  C. J+ Q
  107.     return 1;    /* 成功 */
    $ D  w5 u- m2 S" q4 Z
  108. }
复制代码
+ x+ I0 ]. k* F9 g
73.5 SPI总线板级支持包(bsp_spi_bus.c)
8 U$ L5 R3 \+ rSPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
+ q$ k; `# k2 T& Z3 ~
# p' `. A- i! P& J1 {  bsp_InitSPIBus. P: k, s& U  s; `
  bsp_InitSPIParam
5 H9 }& [6 @' \8 O+ k2 m  p$ T  bsp_spiTransfer+ s* D! q( j6 D+ l' @' L
73.5.1 函数bsp_InitSPIBus1 W. ^) N: }9 i0 ?9 \
函数原型:$ k' ^' w$ v0 b& i+ R
9 v5 z# n3 b9 |4 T5 |6 Q
void bsp_InitSPIBus(void)
! F7 T* R1 |% w1 Z
; `6 T! y) w- ?4 f. C3 `- a函数描述:
: F8 _; V% a- r$ K! M0 j& _0 `; q' L# f
3 h  e! b) r/ X) a( R- P+ h此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
0 g& O" i1 @0 u5 c) k8 ]! u6 A1 O% v; b( q
73.5.2 函数bsp_InitSPIParam9 W: n& _  \" {/ Z
函数原型:
. o0 h  R5 p* ~" x, _6 c) }7 H3 ^  Y+ i# f% u( K/ c
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity). x% r+ N% C+ x7 r  t  G" m
2 Z  j. {4 A2 F1 [
函数描述:( b0 T6 b2 `8 V  D0 D) ~0 J) Z' K
, G4 l1 w2 c" _$ N# S
此函数用于SPI总线的配置。
$ [, O! V& l: O* n8 S! `8 n, E) I3 a- j! |) L! g( K
函数参数:% {; ]2 B5 D' v4 Y/ d: i
( y2 Z6 w+ [6 V, W7 I. x5 T
  第1个参数SPI总线的分频设置,支持的参数如下:5 Q7 l/ k6 g% g& M1 v5 F- j
SPI_BAUDRATEPRESCALER_2    2分频
' _- ]( |( ?# L4 V; Y3 L" b* u$ d( j  W
SPI_BAUDRATEPRESCALER_4    4分频2 E7 b2 X% O& w" ~! d
+ S) K4 B) [8 w0 O, }: M
SPI_BAUDRATEPRESCALER_8    8分频/ E/ l( {4 Y3 B' S- D. F
7 [2 s: t1 ]2 V; |$ [/ E
SPI_BAUDRATEPRESCALER_16   16分频  e) k2 z9 k9 \- b' {

' h7 D! C8 ^' R: {9 Y5 gSPI_BAUDRATEPRESCALER_32   32分频2 G  D$ @2 F# \& a5 i3 d
% e7 n, Y6 \# U- R6 _# {
SPI_BAUDRATEPRESCALER_64   64分频8 z' ]/ o1 l3 v) y$ z) [7 H) Q* W
$ ]" e+ i, Z3 [7 _6 Q, I, h
SPI_BAUDRATEPRESCALER_128  128分频
) x: b6 k4 h' f+ {4 S. t* `
1 o) R0 P. g3 V; F: TSPI_BAUDRATEPRESCALER_256  256分频# ~* T. f! V  S2 a! G$ [. x
9 n8 h" ?7 Q' b, E/ z
  第2个参数用于时钟相位配置,支持的参数如下:
0 A& V- ?  l! u2 I$ \8 S, TSPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
% F, K! E0 h8 }. H9 `5 r! V' z
" j7 i- ~1 S; ?) vSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
+ U1 c8 c6 d8 Y/ D0 Q
. k# e- Q9 F! H( u& s/ ^! ?  第3个参数是时钟极性配置,支持的参数如下:
* v8 e6 E8 K: t2 Z4 V! P( ^+ ESPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平: d' A$ b! S+ u. S9 y
% {; W# e- Z$ R0 w7 H
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平# H: o$ J' c7 u7 }$ ~

4 T% g: @) o' o. ~( V6 H  ~; Z* V& P73.5.3 函数bsp_spiTransfer

) U( U1 j/ D0 }函数原型:) Y# q% s# p" A4 g- G6 n+ ?* T0 J

9 X  o/ V# p3 W* M; j9 Qvoid bsp_spiTransfer(void)2 h2 u' ^. E4 b8 p5 K" }' u- Q

1 x- n$ F: F  w/ W! ?2 j! l6 g/ C- ?( J函数描述:3 k, r. v/ v) J6 ]7 e* n3 ~. [: O# r2 t
; K" [) j! Z) b) ^# u, `+ c  Q
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。3 D( F* |  O  \( J
. F$ z' h7 a5 J
73.6 W25QXX板级支持包(bsp_spi_flash.c)
7 D1 @9 Q8 `6 r3 }/ S8 h8 IW25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:7 Y* W7 h0 t$ y

; N/ N5 g; Z4 v- Q# Q1 I0 B7 n  sf_ReadBuffer' i1 w- b4 Y. I+ s$ v
  sf_WriteBuffer
, X9 Q- e  X! t3 ^$ _4 E  sf_EraseSector% `$ e) Z  H' g& n" ~, i
  sf_EraseChip  j' h6 l: p: L9 N0 h& j0 n4 _
  sf_EraseSector
$ J5 z$ s1 U) G9 K& J73.6.1 函数sf_ReadBuffer+ x2 t" _9 B/ y2 F
函数原型:
$ w, ?. g5 o6 u3 [$ ?) @% A! L
- K9 W, y( W8 w$ f7 x3 C- a- s, svoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)7 s! H# T& [, q1 u6 g5 @
8 K- Y0 m! |4 p8 d
函数描述:
* i4 h1 V0 g; q
9 I# z6 `0 S6 m- t此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。0 \* @+ c, B: H% b

9 H7 w4 a, j1 A/ l函数参数:
8 J. [! `# Y; n- N7 I9 X* X' R$ ^) m  z- g* f% X' i% n
  第1个参数用于存储从SPI Flash读取的数据。
/ `, L  O: O! d6 K  第2个参数是读取地址,不可以超过芯片容量。7 k# L5 _/ Y1 u( v# T3 |. j; m0 b, U* m
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
; z) ]+ C5 R) ]- n7 E( K6 a73.6.2 函数sf_WriteBuffer(自动执行擦除), ?/ x- T6 d! O6 }2 x# R
函数原型:
# h1 ~( v, Y& ?. M6 e3 j7 [
: y: g  q4 ?" x% N. u/ ~uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
8 A/ s5 o) U5 T* @6 X5 D
% S3 R8 l+ ]0 o3 a$ B+ ^( I7 t! e函数描述:2 I9 X: S2 A7 U3 g; C0 x

& t, Y# \5 x4 F# g此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。/ F0 v9 K. Q" _4 |

# V  n# y1 Z+ w# X# O函数参数:
9 U. j" N$ c8 z/ G
# M, w4 }- k& H  第1个参数是源数据缓冲区。
" n4 s8 j4 ^% A0 N" ^4 K0 o) m  第2个参数是目标区域首地址。
4 j, |! M! P. o0 T  n. u$ l" q  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
* {# U# u- A1 _0 `) Y' _4 l' i; T  返回值,返回1表示成功,返回0表示失败。. T* ^2 r; h8 P8 m8 |( L
73.6.3 函数sf_EraseSector# q$ P( g; V! _: i
函数原型:  Y9 a, ^# Z1 N5 y5 o" }2 C

9 y0 k8 Y( L$ J# e4 W* G6 Y$ _6 fvoid sf_EraseSector(uint32_t _uiSectorAddr)
/ `: d1 m! R$ A& `6 d
# a4 v) t0 X4 H% }函数描述:
9 C% |- r8 m* H3 h+ G& i6 ?3 o1 C; t$ O! F3 J9 W
此函数主要用于扇区擦除,一个扇区大小是4KB。+ j9 i6 P0 [) U& [7 _  E

9 {2 o2 Z4 H- K函数参数:
! _7 U. P& L7 z2 a3 |% m% u( Q$ ?( w4 ?7 r" q
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。7 ^2 Y% e5 Q! M% Y) y, E# W
73.6.4 函数sf_EraseChip
5 V# h& f( k. k3 y2 {4 a函数原型:
. s2 Q6 O# s( _& P) E- ^6 K. R9 [$ D* r& O/ ?* Z& t
void sf_EraseChip(void). \0 a8 y4 p! D$ S1 \
9 X" w; t6 _. k1 L+ @9 f. W
函数描述:
- p; n! ?0 T* s* l8 F# c+ Z$ c3 y# n6 g$ [
此函数主要用于整个芯片擦除。+ y- Z- P1 ~& u

6 ^5 K+ f+ o7 g2 n0 O7 t73.6.5 函数sf_PageWrite(不推荐)
2 x' D3 H' N: x* ?) q% O3 v函数原型:8 Z. C. Y8 y0 f8 q. {( u

3 q8 H, Q( T2 Z/ K6 b  w% _void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)2 \  K) z" ^2 J0 z
- V6 b4 d6 r; v  v6 {$ L6 a4 U
函数描述:
2 h5 d5 c" G+ Z
7 {- u: O5 E1 m; e( k/ H/ ^+ R此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。
3 E! @8 D2 U' F) K! V" t% W/ S- F) K% D  v0 w
函数参数:! @5 _2 W2 N$ P. k0 G* d) v) n

9 {, C& e- K' X( N- `  第1个参数是数据源缓冲区。
- Z1 R9 a+ W/ i/ I# H1 X! T  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
3 @! \& ~7 Y8 b$ U( O- v0 ~  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。
: |* S8 r9 [8 ?7 w/ H* G73.7 W25QXX驱动移植和使用7 {5 O# V) W3 |  f6 c$ v
W25QXX移植步骤如下:$ c4 I/ Q" k  ]2 k5 A" O
, A% }; n. C$ ^$ D. E8 A! m5 \
  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。% G9 `+ z! }# o: v( t
  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
" D6 E. U) L2 ~  W
  1. /*
    0 ]! E  z  X: I
  2. *********************************************************************************************************
    , M5 t5 ^( X) W
  3. *                                时钟,引脚,DMA,中断等宏定义
    9 \9 Y/ T/ C: m/ C* v
  4. *********************************************************************************************************: b( z  B9 v. \+ H  X
  5. */! _' s; @8 A! K3 t: d
  6. #define SPIx                            SPI1& S# i: |, n. {
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    8 J. D6 j- J. L% P% w/ z6 ~
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()
    * u8 E& D) e( @( H5 L8 Z( K, t! p
  9. 8 v, p* C( I% l/ o1 U" \* I. Z3 `
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()# p8 ^/ O# N2 b
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()) v( G4 Q! W% o& Q

  12. " g7 r9 n8 K4 d4 _( r, B1 w& ]9 r( z
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    5 p6 o$ Y- M' Q8 k( W
  14. #define SPIx_SCK_GPIO                    GPIOB
    % a7 @; A; G9 W" F8 q
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3& |% I* W- x, o7 c. y
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI18 `- M) u, X' E* \9 ]4 H

  17. $ Y) [. O0 L: D% R
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()/ m) a5 R& F. E% Z5 p, D; g4 X& F
  19. #define SPIx_MISO_GPIO                    GPIOB
    4 y, `" S! o4 f! V
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    * o6 f/ P5 g! K2 I2 P( \
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1
    9 i% {1 @9 s& o& @: ~9 O
  22. $ \8 D% e- a/ v7 W5 e
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()1 ^9 o1 O: l  Y5 R8 ~3 V
  24. #define SPIx_MOSI_GPIO                    GPIOB
      \4 {0 d( z0 d' N. u
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5) H. y4 n  Q) F5 L5 ]
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI17 `% J, V0 {+ v0 r5 P# k
  27. - e: z: {" V# N$ ]5 c
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    7 r/ a3 _( {1 {* l" a
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    ; [: Q$ O9 t' q5 u: X

  30. # F3 I7 ]0 G8 H; z4 V, k/ b0 ]6 F+ d0 Y
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
    1 ]" v- L# d# r' Z5 O3 E9 t1 h% e9 r
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    % a) R7 `. G. ~& Q8 Y5 h- e

  33. 1 p5 ~; V! _' r" @5 d9 P
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
    ! }: {, C0 m9 ]  P- ?* [) v
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn
    + {& n# N2 o* \; ~; E  C3 U

  36. 7 V, j+ u5 _3 i3 x' k6 l
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    : \6 J5 @$ `5 z) ?
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler
    0 h. u- z4 W  ^5 w  b1 ~

  39. 7 L* S6 i1 F% W6 o; c
  40. #define SPIx_IRQn                        SPI1_IRQn
    8 k# h7 g4 A. G3 a3 A
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler
    ( Z3 X. F+ N3 i' o3 }4 ^+ a& W
复制代码
2 @) ^2 L! K- r9 O' q- D
. c* ^2 y/ M; Y
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
/ U% k. n* e, ]+ t: T% ]0 H% {$ \5 `
  1. /* 定义串行Flash ID */
    % y+ S; T; O1 c& x& z+ p' h& _& a
  2. enum% P) w" c3 l5 x# F& M
  3. {6 o+ i7 n5 F: m( v( g4 P
  4.     SST25VF016B_ID = 0xBF2541,
    " R5 m" S; `* c2 Z' l2 r
  5.     MX25L1606E_ID  = 0xC22015,9 F$ ^/ l9 P8 ]0 ?+ j" W
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */: H: F6 H' ?% l% Z
  7.     W25Q128_ID     = 0xEF4018
    ( M5 q8 f  D5 @/ H( Y  l& _+ R9 G9 \
  8. };
复制代码
$ `; I7 ]  T0 W% b6 @
  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
/ g! L4 L5 G7 h+ x
  1. /*, A+ R" E# O- f  J/ i
  2. ********************************************************************************************************** g1 I1 |$ A! e% O4 C  r
  3. *    函 数 名: sf_ReadInfo! U. Y! }4 E' r  t% @! x& e
  4. *    功能说明: 读取器件ID,并填充器件参数
    ) D, ^0 T# Q9 l/ U9 R9 F
  5. *    形    参: 无
    ; r5 S- w( D  P
  6. *    返 回 值: 无
    ) [; h: c9 f' D2 ~5 T5 j5 }+ _2 ^
  7. *********************************************************************************************************
    / D( X4 c# \3 P6 E3 A+ r8 P- Z
  8. */
    ! o. B/ k( m1 s* Z
  9. void sf_ReadInfo(void)
    - u2 e& g1 O1 e  H/ k
  10. {" D* x1 {4 T" Y4 [$ _0 Z, D
  11.     /* 自动识别串行Flash型号 */
    + S& v3 F: G9 g3 S
  12.     {
    9 R3 y$ m0 R& ]
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
    ; |. b# ~. L* {& U- T& d2 K

  14. % Y, k4 o7 p2 T; {& o% K1 m
  15.         switch (g_tSF.ChipID)
    * i. `- y6 R/ a0 ]: l: N" _; j( x1 v
  16.         {
    , j! D7 W+ O! e' x1 _
  17.             case SST25VF016B_ID:2 f, Z$ d/ q; u$ p" z9 j
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");
    $ t4 ~- h1 g8 `- x+ b% P% b
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */5 i  U, [# F; U9 x; y
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    ! o* H0 M2 Y9 J- o' Z
  21.                 break;
    : N, x- k8 Y  H7 d
  22. * ]/ ?2 K# P$ l! G
  23.             case MX25L1606E_ID:
    ; _. X$ m: {7 F4 y5 u6 ?1 P0 R4 Y
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");7 B$ x' f0 T0 e
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    ) G- K$ e: J2 ~  A) l' r! a
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */. w3 Z4 L+ C! j6 v, T
  27.                 break;' |& N0 H8 |3 V, w. I, u% ]
  28. 1 z7 v' T0 s) S+ w9 a/ ]' ~/ I
  29.             case W25Q64BV_ID:( M+ H/ R/ a: Z) b+ O0 t8 s' d3 \
  30.                 strcpy(g_tSF.ChipName, "W25Q64");
    2 r- O! n% S/ e$ z$ W' N
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    * k! j" g  S2 ^* o7 h' S5 i  s1 [$ N
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
      P1 p7 F8 C$ k- }+ h
  33.                 break;# R% I- J; M$ C/ {; q

  34. 4 ?3 t6 M( f0 v; ~, x8 I- S* W
  35.             case W25Q128_ID:, n: V- N) x+ x+ s7 m
  36.                 strcpy(g_tSF.ChipName, "W25Q128");3 H% M( Y7 I/ N5 t
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
    7 T; d  P( w5 a& ]0 ]: y
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */2 N4 E) X7 ?( ^, B/ \& n
  39.                 break;            
    . ~5 e% r; M* t( u$ \+ `3 u

  40. ) w8 O8 n9 \. G& u3 P, M2 K& `. p
  41.             default:
      W+ R% e+ C& \- {) h  {
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");
    2 A, G. q( e3 U, B( L
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;
    ! e: E" `6 m1 L8 D( t; R. R
  44.                 g_tSF.SectorSize = 4 * 1024;% G0 i# \. v7 u& v
  45.                 break;1 s& W! ~1 W1 [  t3 F9 t
  46.         }- C* n  h" n* d2 V
  47.     }
    ) V# F3 B0 h- C
  48. }
复制代码

( {5 L- ^6 Y$ q, R7 m: L& V  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。! X3 @* B$ F0 A: {- B0 c9 [
  1. /*7 M7 A  h5 b: N( R( J2 I
  2. *********************************************************************************************************
    - Z# |' h& L" Y9 R" }, Q
  3. *    函 数 名: sf_SetCS
    2 K7 O# ~8 i6 p) d
  4. *    功能说明: 串行FALSH片选控制函数
    + v& M6 l: P" ?0 ~7 ?
  5. *    形    参: 无6 U4 h; G! ^. f5 k  V0 \, n, F
  6. *    返 回 值: 无
    1 j# \" h# K$ S0 \: E
  7. *********************************************************************************************************
    , h, ?6 C* ^) [0 L  y. y1 r8 L
  8. */8 X( I$ l8 q1 i7 J- ^+ p' q
  9. void sf_SetCS(uint8_t _Level)
    / P6 F4 m, B! A
  10. {
    / w4 ]3 d7 [: f. N& |
  11.     if (_Level == 0)$ y; |7 t" s6 d* ]" Q, H
  12.     {' J5 {  i* i) ^
  13.         bsp_SpiBusEnter();    - |" G  m* p& r1 ^
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        ' B* f3 {7 Z' K2 k% G
  15.         SF_CS_0();
    * ?7 }7 J: B- n6 u
  16.     }0 t* `. s' i3 N# L! Q: i
  17.     else
    ! F: D7 z+ m6 a1 `
  18.     {        
    % d( G* U) Q4 T! B
  19.         SF_CS_1();    % i) X2 m" U  x; H: n
  20.         bsp_SpiBusExit();        
    3 z  l$ A/ M+ N. d. _
  21.     }
    + J1 C" d$ X* \* B' }3 D# ?
  22. }
复制代码
$ w7 S( m4 k6 {; A9 I
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
5 p. }4 }4 p, u0 X
  1. /* 串行Flash的片选GPIO端口, PD13  */
    & w2 R% I4 h& Y
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()) v9 V0 ?2 [# a
  3. #define SF_CS_GPIO                    GPIOD$ D# T3 h8 p! M3 ]) Y) z. L
  4. #define SF_CS_PIN                    GPIO_PIN_130 t. Q6 h, _0 {7 I/ \: Y6 g, F
  5. % Z: I8 ~( i" g8 ?
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) 9 G* @, F. y+ s$ @+ z% h6 H. g
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码
" e2 I9 u5 Z: U4 f' _4 V( b: s1 v
  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
7 X3 J. c/ q" P
  1. /* 配置SRAM4的MPU属性为Non-cacheable */
    + R, x9 O; K- n4 K! L
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;9 O# r- {2 Q/ X( b
  3. MPU_InitStruct.BaseAddress      = 0x38000000;' h% k6 x7 ~" d7 B5 w
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    2 ]% m' n+ q: Q. U
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    / q1 o) Z: o! l0 ^: D/ u
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ' Z. v/ N$ @6 S
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    & ~8 E& o8 X' M1 `  G2 Q
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;$ B2 V7 _, }6 y  H6 e5 N  }
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;" Q/ T; j% \5 @2 N1 p( S) ~
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;/ T* d/ W+ s# r2 P! r
  11. MPU_InitStruct.SubRegionDisable = 0x00;& T! a! ~6 L* S# |1 r
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    / Z0 f/ b* ]2 G2 R' O: |, Q# T0 o
  13. 9 R8 B( u: c: Y3 B& i* ?( N+ Q* [
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

0 f5 J1 b" t' U# @) z+ b* |% [0 n) i( c  第8步:初始化SPI。
+ ?4 L0 J2 [4 u0 ^# d9 h
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */5 ^4 N8 l6 z- }# J& ^
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    : ]/ I2 P2 @& u0 c( [( g
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码

% v! `2 W1 L# I2 K3 @" H; h; M2 G: L* @) T  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
5 H9 S6 }4 k$ v1 [0 N! @  第10步:应用方法看本章节配套例子即可。
- u' O4 Y& P' r% a+ H3 O/ j% y73.8 实验例程设计框架* x7 P2 N1 X( r+ \5 h' _
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:7 h/ r, v  M% u2 [
/ w' [3 M. D( j( C% Y$ C2 \6 r' M" w) {
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
% K& I! q6 X2 Y9 e8 F% L0 h
2 Q2 {* u8 I' O" ~* [/ t6 w  ]
  第1阶段,上电启动阶段:; S3 y$ O# A: d9 p" u! w
5 w1 m: n6 J7 y4 e4 Y# k& E) X
这部分在第14章进行了详细说明。
5 |1 ~) X4 G( [8 w3 P/ b( z0 H  
: C) T6 Q# b- f1 w% k0 [第2阶段,进入main函数:
+ v) K: j" I/ S
: p# I: o  I; p: S* B: T第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
; z  C  i4 X5 U% b, i3 n第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。9 C4 H7 Y; @) |# l) K/ E/ v  o

% X' a" Y& D4 b73.9 实验例程说明(MDK)
& Y$ i0 G& ?8 i7 M  Y配套例子:
; ]* c1 J8 ~3 W& ^2 i+ ~8 [
* A/ _0 @0 d1 A$ w0 ^* V0 JV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)0 L9 j& x& J# ^3 i" M& l9 ^8 Q
8 l+ r; i$ G& p, P2 m$ E# M
V7-050_串行SPI Flash W25QXX读写例程(DMA方式): J3 }- T& \% F1 M9 e

5 r- P5 Q2 y$ B# e2 V0 AV7-051_串行SPI Flash W25QXX读写例程(中断方式)
7 _0 J1 n+ i# @3 P* T  d
- P: ~0 E' I0 O1 z, E0 H) t实验目的:# V7 ^0 c. B1 r4 u8 R
% o4 }* O6 Z! a
学习SPI Flash的读写实现,支持查询,中断和DMA方式。2 k% @/ p% {8 E. H* {
- k. t6 e; R2 G3 M3 x+ |3 ?9 {
实验操作:
0 E" F) @3 u8 T* y5 _9 t: Z
, Q  u- Q. v% Q支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
% A, a5 T1 g+ c+ j. x( j) a- _9 P3 r" [4 Lprintf("请选择操作命令:\r\n");. L( e/ A: Q! c( R/ b3 {1 ^
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);* g6 s/ O9 u5 G
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
* O0 N: [& T% `  j$ H! Gprintf("【3 - 擦除整个串行Flash】\r\n");% h, T2 Y5 A. u3 T1 e. B. b
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
. g/ n( X; W* |! F+ u2 Kprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");
) a7 H+ M# j: ?% S. xprintf("【Z - 读取前1K,地址自动减少】\r\n");$ ?) p' S  |* w/ d8 @0 G0 U
printf("【X - 读取后1K,地址自动增加】\r\n");
) k2 j0 a6 t: f& [$ f2 g3 A5 Sprintf("其他任意键 - 显示命令提示\r\n");( J2 q. v% r  d% d, I' x$ }/ y
上电后串口打印的信息:
$ n6 M. u( V9 g/ f6 k1 l% z' p4 ]- m1 x! G
波特率 115200,数据位 8,奇偶校验位无,停止位 1。. X# k" \* B$ G; S- t3 g" T% d5 d
  J& Q- ^1 F+ k7 n' o6 d
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

5 W1 A. u# Z$ W% K2 U3 {7 t" O9 D3 l, ^) U7 I; _2 s% d9 }; z- R% F+ M
程序设计:- m; P1 m& a. {

7 z" H5 H; Z5 N" R  系统栈大小分配:
# S* |6 ?* `* B4 I
+ x5 f( c/ C- d/ m1 U
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
, n; ^3 N3 p* {8 E0 O% _) E5 V  s
% n. @; n; q9 E5 i/ j, c/ G
  RAM空间用的DTCM:7 N8 r6 o! N" ^/ g& t; c$ u8 ]
: R" f$ ~* D4 F6 j  o- n& T, N: Y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
# H* h7 C; ?7 ~7 y& U

8 I( h) V% r& w& Y5 t7 ~9 N3 M. g$ x: n  硬件外设初始化3 A/ u" L) d7 \
$ i: K: W, K: P3 C2 n9 Z/ r9 X
硬件外设的初始化是在 bsp.c 文件实现:
) v% D6 b6 e, A# M- I; f" {* B5 j. B/ {" `# f+ F
  1. /*- Z" y/ i2 q7 w8 s
  2. *********************************************************************************************************
    8 }# D! h" |5 m/ M
  3. *    函 数 名: bsp_Init
    % N, L' A7 }, `$ _# R- T4 l
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    + {- l4 A+ |: m" o7 H' T. F
  5. *    形    参:无
    9 x, B9 `7 V6 |. \- ~  h
  6. *    返 回 值: 无
    " l2 v. k3 P: N1 u" y' f% I4 a$ K
  7. *********************************************************************************************************! c& @2 V# c9 T2 B% t0 A+ h* d
  8. */' d* a3 t6 g% r8 x
  9. void bsp_Init(void)( y" W! `- \: [7 ]
  10. {/ Y7 N8 h" r8 Y: d/ |$ f# z- K
  11.     /* 配置MPU */- u7 T# e! u: _1 P6 h
  12.     MPU_Config();7 `# M8 b8 `7 l! f: p& ^6 A) O
  13. 4 K4 G7 b9 n" m5 ?  a  U* k5 R
  14.     /* 使能L1 Cache */
    + E. Y1 j4 }9 u, h' L6 F, Q
  15.     CPU_CACHE_Enable();
    5 U5 f- n$ j8 D7 Y' f' w5 o
  16. 1 A1 p0 V* ~5 j; i9 I6 I# o
  17.     /* # A8 ?# x( c7 W. B
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    7 e/ s- P5 l6 D: O
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。; a6 Z7 e0 f/ r
  20.        - 设置NVIV优先级分组为4。. y' ^8 Y9 ^' H! t& P2 M
  21.      */
    . e  d5 Z: h+ f/ k2 H+ ?9 n2 B3 n& i
  22.     HAL_Init();
    0 S4 ^! d# ^  W5 m/ T
  23. " _5 @6 `, K- U9 p  b% F( u3 ?
  24.     /* + P% X5 Z* t( p7 S7 [$ R
  25.        配置系统时钟到400MHz
    ! |+ r; F6 p, v! F$ H
  26.        - 切换使用HSE。+ k1 u% e3 I2 i: U* t
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    6 `2 b7 b- d% b1 ^4 w
  28.     */
    - _% Y3 ~0 C! B6 e! K: w
  29.     SystemClock_Config();
    5 o1 _* O- p, n+ [! o0 Y

  30. 9 {! X6 r6 @5 X( Q, l- h, l
  31.     /*
    % {4 P7 {( j$ E3 X, y
  32.        Event Recorder:- {- G# d/ j$ N' _8 R
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    : Q; l, v6 o- U- J' }! ?& V' l
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    ; j3 E; H9 j8 U8 m! U& W# ]
  35.     */    $ w% X$ h7 f$ M& ^  I' [) ~
  36. #if Enable_EventRecorder == 1  - l; C+ R# j& C  E- N) v
  37.     /* 初始化EventRecorder并开启 */5 C5 k/ U0 a5 O5 z. I
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    . A* U/ q  F7 m2 L, ^: U% K+ u
  39.     EventRecorderStart();7 B. T, s) w1 ?2 x( ~( o
  40. #endif+ T- E4 _$ ]. X. d" L
  41. " z1 r, \1 g% P7 Q% w( o
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */         t. L9 f: a. X! t! M) d
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */3 Z& X4 a7 l- D1 S7 j
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    & ~8 J% V/ t( c; o& V
  45.     bsp_InitLPUart();     /* 初始化串口 */
    , I$ c6 L3 J( A6 g: z
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    ' o1 k% U" Z/ R: s$ x
  47.     bsp_InitLed();         /* 初始化LED */    4 q$ G0 o! K) b- M0 b; b; i
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    ( l0 Z* H& I- }  G1 e

  49. 6 `, Z6 I" K' d% Z9 j# d7 e( Z
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */+ D4 k: l7 P- y: k
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    5 u8 D8 H2 D! R1 T6 Q7 _1 A
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    5 _1 r9 q( H; q" w
  53. }
复制代码
7 K$ D/ v* W  D& D# b
  MPU配置和Cache配置:6 B' M% v: }5 w" V- [* E) f; o

. K. A( S4 R# F9 l# j4 }' p. r数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
5 ^' ]" J- ~% P0 M0 b
9 _5 Y3 @/ Z# S
  1. /*
    + x+ I4 t' q* T& _
  2. *********************************************************************************************************6 n% d$ t+ H& ~# a8 V
  3. *    函 数 名: MPU_Config
    ) h# Q: y4 m6 Y' N, W# Y
  4. *    功能说明: 配置MPU0 H" T" }: F4 A2 B
  5. *    形    参: 无! B- V4 d7 |9 V% W' G! |( g
  6. *    返 回 值: 无
    $ H3 b8 r! z3 d1 q# h
  7. *********************************************************************************************************
      n, K7 ?3 h/ g* E; V
  8. */- @9 ?/ I0 ^8 A! T& q4 D) t$ O
  9. static void MPU_Config( void )
    1 N, m! e; p+ \8 F( j) c  j/ f
  10. {
    " d% l/ Y& k$ Y8 T3 n9 Z# G/ C: [
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    2 C. W- h" s# C7 C9 X

  12. ; |# {0 C1 C0 A: f! l/ e, Y
  13.     /* 禁止 MPU */( R: Y/ V7 v  f& }( L6 @$ D
  14.     HAL_MPU_Disable();2 e& p. O7 l0 \2 r( U
  15. , D$ o% x& C$ l2 F
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    6 S; s* v% h$ p  p3 f! [0 s( k
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    2 M' [0 k% C) O, v. K2 s2 {7 O
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    % t1 s+ ~. f* m; F, {
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;4 M4 E) ~) y. l( q
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    - Y" |1 v3 D/ w. Q- {# R& k
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;; V- l8 M) k' m
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;+ e4 @4 _5 k9 r3 S/ `* M. t2 b
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    & {, C9 S, p+ t6 Q+ ~& v" Q
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    8 ~% ~& _4 r( z7 C/ ]) W
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;$ Y1 a+ K2 ^3 Y
  26.     MPU_InitStruct.SubRegionDisable = 0x00;$ q8 ~. @! Z" h* _; d
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    $ ]5 M0 t+ k& a( p

  28. ( L  k. W9 e! z7 B5 ?
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    3 m2 J. r' V2 Z9 r2 f% l  |( B5 e
  30. " y3 ]. m8 s, S! L

  31. 7 [) `* }" W9 o7 |6 F. }4 ]
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    9 [9 l& m. B( m$ H  o' A6 F9 ?: m8 `
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;9 r  r) j% e7 u  p6 x
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    " G$ ?# ~% U8 i# v0 y+ x! R
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    5 i: F; o# X; N6 H
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;) w  F8 A- e. z* L8 c5 e5 [
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;) G4 Y7 I! v, s
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    7 \: X% z3 I1 M) \" h
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;1 [8 }4 _; [. t7 @$ L& s* I  w
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    : Y9 N' Z4 a' ~$ s  q0 M
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;+ T! W  E& O: i3 b
  42.     MPU_InitStruct.SubRegionDisable = 0x00;4 ?& j; O4 z% S( k! g' H" S0 h
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ; u7 ?2 u3 a0 ]) Q7 L8 r

  44. 3 u# C$ t1 j& g' C& R
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    - W" w9 G, k$ P; u
  46. 4 s7 ?( [" O. T1 u0 y
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    ( G6 |+ W- @0 ?4 B4 U7 n& L5 A
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;6 m) J8 x2 M' r1 r' k  o
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;' c( F  [8 H% w
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;6 }2 @" S5 o2 y# O
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    1 e, k& b% v; g& x2 G2 \
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    # X; K6 i7 I5 O2 g0 e+ m
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;' m' c0 F3 Q, S( P
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    1 M8 {) N8 h" x& X0 K2 K
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    ; S8 U3 T. d4 l9 D1 n1 s: y6 N
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;  T  e6 R) ]/ k$ M4 ^: f
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    / P+ n: z4 N1 Q& c0 Q& ~1 B
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;) ]( K1 t' N' y. |

  59. ( C: i- a  E5 U3 \% I
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);8 u6 P4 c" M& F4 z

  61. 3 E" ]- q% y& l& M$ b) _; C
  62.     /*使能 MPU */
    9 ?5 ]( A: \" A5 q
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    " j) |' L* a3 f5 @5 @* D/ M
  64. }" N  }, o! P( G: u" w
  65. * r+ e' k# ^- b, l5 ~% h$ j
  66. /*( T) P6 k2 y4 ?( J' U6 I
  67. ********************************************************************************************************** Q" @5 x; _  }- a( Z9 U0 P
  68. *    函 数 名: CPU_CACHE_Enable
      G. M( y1 Q' K6 _" X( [4 {
  69. *    功能说明: 使能L1 Cache
    6 n( U+ K: k8 v& c) m) N
  70. *    形    参: 无
    + t0 h; C3 D1 T# {7 r
  71. *    返 回 值: 无# u" q; y0 E* W1 X" g+ x/ c
  72. *********************************************************************************************************. N: J: K2 L* x5 d
  73. */3 P8 p) S: B% E) S9 ~) Q
  74. static void CPU_CACHE_Enable(void)
    ' j7 s* Z& b5 `, }% E8 \
  75. {
    7 y+ w3 {, D9 o% ]3 h
  76.     /* 使能 I-Cache */
    & D- C; Q+ F# Q' d0 T
  77.     SCB_EnableICache();& z2 Q: p- w* ^0 p
  78. . @; Z% E' d5 W& D6 }. G
  79.     /* 使能 D-Cache */
    8 H% c0 W( ^  h, B# V' m% Z
  80.     SCB_EnableDCache();; r3 A- q  L3 X- M0 |$ g" y
  81. }
复制代码
5 j- X$ R) Z* s& B4 H  U
  每10ms调用一次按键处理:7 h  `, V9 r# [& {- [- j$ j. _

1 k, j) q) |$ I$ s% M, T4 A, u按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。& m( m0 o& v; C& ~

7 C1 V: V# x; c7 f/ _
  1. /*
    . P# e4 p9 i$ u
  2. *********************************************************************************************************, X7 a) H" m# U9 k. g
  3. *    函 数 名: bsp_RunPer10ms2 N0 k0 g% z6 a* R
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    - s) A; t1 V* r) L$ N' Y# m! s
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。, d/ N  p) i, U8 s# Z
  6. *    形    参: 无) G+ m4 e* M5 n4 g3 k3 `, ~8 Y
  7. *    返 回 值: 无
    + Z7 E  p% B% c7 r! y( q- i' `1 i
  8. *********************************************************************************************************7 Z1 E/ `2 M* q
  9. */4 v, Q6 J- z9 d( @) R
  10. void bsp_RunPer10ms(void)
    * O5 K1 d+ r' u$ S# K8 _$ J; d
  11. {! K6 E8 \1 h  X% |3 j! B
  12.     bsp_KeyScan10ms();
    ' j- o* i: P, j% ^) H
  13. }
    6 ?1 g; @+ d; u& `- h2 i

  14. $ R" k( \: F( f/ x; Q
复制代码
2 E4 n1 z  Z* q0 ]6 z
  主功能:, F3 E8 J/ a  I- i; D

7 x* m+ x! X+ q; g8 Q# D主程序实现如下操作:1 @* B9 y: \* F; t' F1 E% L7 H

- ~  t6 f+ s7 B! M  启动一个自动重装软件定时器,每100ms翻转一次LED2。
# I' L6 H2 t. F( f8 d  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
$ q9 W1 h' A3 s( a9 ^9 n9 B3 c  请选择操作命令:% q0 X" W, q5 S  E* J* D
  1 - 读串行Flash
8 N8 T3 S! R  @8 B5 w$ X. p0 [  2 - 写串行Flash' G. ]. [5 x4 a
  3 - 擦除整个串行Flash
' P5 [' {: v3 J7 z! q2 s. {  4 - 写整个串行Flash4 W: [; L) S6 F+ a; e' S0 D2 D/ N2 t+ L
  5 - 读整个串行Flash8 o/ C9 f7 Q- p; U6 R
  Z - 读取前1K4 ]8 f& M$ b/ \; Z; R4 u+ f/ E
  X - 读取后1K
% i, Z4 A  r* f
  1. /*& b$ x  t4 M( q- k
  2. *********************************************************************************************************
    - A! |. \' w) x  E
  3. *    函 数 名: DemoSpiFlash
    7 n, T: H5 c( K
  4. *    功能说明: 串行EEPROM读写例程8 E5 i- D  }$ `% Z& C
  5. *    形    参:无9 a  ]) p/ g3 l. M
  6. *    返 回 值: 无
    : U1 u0 s5 d9 s- e8 @8 G
  7. *********************************************************************************************************
    % s: y( s$ r9 E5 M5 [
  8. */
    ) T9 l- C  q4 m4 r, P
  9. void DemoSpiFlash(void)
    ; a; S' h3 O, ^" \* _1 j4 V
  10. {
    5 R4 h, A9 a  B5 `1 E7 |7 k
  11.     uint8_t cmd;8 C! `) \* C! I& z- w
  12.     uint32_t uiReadPageNo = 0;% F+ U: D7 ~) K" \1 s6 Q

  13. ; ~( L6 @6 q. }& g0 O) T

  14. . h1 T# `" i) C/ v; o" S
  15.     /* 检测串行Flash OK */' R+ p; V: G" }% y" m' @
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);  _8 T+ N- V4 y9 v+ ^8 e% b' J8 p
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);& x4 Y3 c0 z* p* R; _: P' x7 i0 N

  18. 5 u# h. i6 k! R+ f/ \
  19.     sfDispMenu();        /* 打印命令提示 */
    4 A# ^) g! b; D+ |/ k" q( n0 H9 X* ~
  20. , r7 l/ C2 L6 |# {, m
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    4 P( u& v" h% p

  22. 4 X, d# ]/ l( z0 N1 V& ~
  23.     while(1)4 g( H( B; @0 G7 n, o
  24.     {
    4 A+ b7 @: `6 z6 p& x! R
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    + X4 S6 @3 k8 C* m% W4 r

  26. - R1 h" ^" R3 K5 C) f. o) _( q
  27.         /* 判断定时器超时时间 */" |, D# A+ R, p7 @5 N6 T
  28.         if (bsp_CheckTimer(0))   
    # V5 o- B8 j8 m" j8 ?$ ]( F
  29.         {
    ' x) I9 B" B, K* H( S, b# w( o- W! w
  30.             /* 每隔100ms 进来一次 */  0 G, l, K. Z- ^% r2 j5 i! j; g
  31.             bsp_LedToggle(2);* m5 f5 J4 E. ^7 Y: ?- s1 w$ I
  32.         }
    + z! x0 I. P/ H9 r: O  ?6 Z# v

  33. 9 i4 n, g; }! r- @
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    0 p. M1 p; J0 P' ^: e- b+ P
  35.         {1 ~& D' m4 o) v: a2 u' h5 U
  36.             switch (cmd)
    - C( X% N/ T* b2 m' F
  37.             {! p+ `1 J( W' s
  38.                 case '1':9 m, A' K- Z/ W& z$ B6 [4 x3 }
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);# g+ ?0 J6 T  l' C2 ^
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 *// j, v+ w1 }! L, K
  41.                     break;# u1 A: ~8 X5 w( ^3 `
  42. 2 D6 ~2 p' \5 v8 n1 ]
  43.                 case '2':1 A& }0 _9 }5 a  w8 n
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    9 r9 ^4 T+ L8 T; H# P
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    ( s/ F- V) f, b& D+ g' M
  46.                     break;
    9 S4 d, F! g: B
  47.   `3 ~9 [5 C, E
  48.                 case '3':
    $ R/ b/ t! T6 X$ y, `: o! F
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    1 u: ]4 g; a  V
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");3 U2 k" \3 p) {) \
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */' i/ ~. J1 J+ P) D# p
  52.                     break;$ Y0 X$ Y! `6 o: Y+ m* l7 ?0 m

  53. $ L6 R. j8 ~, R( l9 C9 U" F
  54.                 case '4':
    / F& _* x- K+ q: j! q' k
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    . W& E. p. G7 T4 I: \
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");: r5 k: u# e$ q- _) @
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */  w- M- D7 ~" \$ [( c- {* k
  58.                     break;
    0 p3 Q) A! ^( G/ d' P
  59. , f* ?( I: }* p4 r* T5 u
  60.                 case '5':, ]5 ~2 s* M, U
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    . T2 q3 M5 z1 m. L# F
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    + M. F1 C# J: u: Z# O
  63.                     break;3 i1 {& N; L& p# ?+ j$ ~9 B, N
  64. 9 y/ W& b: e/ `4 j
  65.                 case 'z':
    # b. a' s+ |* C" W' _" l+ P+ J7 @
  66.                 case 'Z': /* 读取前1K */% @; H4 `/ O3 @
  67.                     if (uiReadPageNo > 0)( N) V! k+ V6 a7 e- s* e4 Y/ U
  68.                     {
    ) M, l- d9 _+ k. N! _7 s4 J
  69.                         uiReadPageNo--;9 Z) z* F+ C. c  c& g
  70.                     }
    ) C2 ?0 C! f+ R9 U/ L, Z2 I2 {: f
  71.                     else' F3 y# D% G. b$ }9 j
  72.                     {) a$ L2 K+ ?* T# A0 p" o3 H* X
  73.                         printf("已经是最前\r\n");
    # r6 U* j% T9 K8 y/ o7 S
  74.                     }
    3 B0 ?4 _! p2 H3 q
  75.                     sfViewData(uiReadPageNo * 1024);$ @- @! w. u  F
  76.                     break;' T2 h9 Q$ A# }& r2 @

  77. 6 y) e1 @. Q6 u! m  g. Z6 f
  78.                 case 'x':5 D( Z6 \! G' {0 B& e: V4 v4 c) {
  79.                 case 'X': /* 读取后1K */
    0 z- f" Y' s' D8 a/ @  [6 E# ?2 b
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    9 p2 v: J6 Q! G/ \5 W
  81.                     {0 m: D1 V5 K1 b% i0 J, ^% L% k
  82.                         uiReadPageNo++;! K  |/ l5 i6 P
  83.                     }
    ' ]% w, N2 s2 H0 L/ t( [
  84.                     else" V* p* l& ]8 o' G5 x1 m; f6 ?3 U
  85.                     {
    ! ?! g6 j8 A: E
  86.                         printf("已经是最后\r\n");
    ! d* \: ^0 c& @3 E6 G/ c: g
  87.                     }
    % K5 W1 ]5 |, q! `
  88.                     sfViewData(uiReadPageNo * 1024);
    ' O# c. _1 [" Z) }# c  {
  89.                     break;
    ! f5 F* b* ?1 Y5 a7 r

  90. 5 U) e& Y4 x+ J  M, a
  91.                 default:$ S3 Q: R: f; `0 b4 c& V
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    ' Y$ ]7 E/ S( Y. K3 w
  93.                     break;
    ( u% W3 C3 [/ v3 K8 A
  94. 6 E/ F7 @3 M7 L+ Q8 O9 @/ v
  95.             }
    * R; H1 w  T$ ?" f/ n0 _! S+ L
  96.         }) b* P1 P6 {7 @  T
  97.     }2 d2 g  D% h& m. D2 B7 [
  98. }
复制代码
  i# H5 n  u1 V! a" s% r( h  ]! l& y
73.10          实验例程说明(IAR)
5 L  H+ j: n7 H# R: D
配套例子:
7 w% U9 p1 ~3 i% N7 {0 W% a7 H3 e% {' _8 \
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)) l; {& t, z; W  O! f* u7 s
2 D0 K* Z1 d5 ^. @" M8 ~* w& Q
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)8 ^. J$ t4 Z% c: A' Z8 x
8 I7 C5 T( R$ w. S! T0 \7 W
V7-051_串行SPI Flash W25QXX读写例程(中断方式), l+ a- q1 h% O

4 L* D# }# B' m+ W实验目的:' A2 z2 [. e. \( S

- z- Y4 s1 K" x: @8 ^' w学习SPI Flash的读写实现,支持查询,中断和DMA方式。8 q/ d3 w: W9 e; Y" N

9 b2 b7 o: X( J( {. ~9 m实验操作:: @. R$ N8 n1 }# R3 `  Z& c
. Z  o" X2 f! N
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可' s4 @8 m* s) X9 H
printf("请选择操作命令:\r\n");
: U, E4 r1 Z2 ~, Z, c+ Tprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);$ i/ q" j7 n) I7 P
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
- _: ~& F# R$ |printf("【3 - 擦除整个串行Flash】\r\n");: G, W9 }, z  n/ H
printf("【4 - 写整个串行Flash, 全0x55】\r\n");! M; ^- H! l# k5 R6 _! {
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");+ Z( x+ Y, k& R) [; P6 i
printf("【Z - 读取前1K,地址自动减少】\r\n");
( M- C: u8 _5 H. q* \printf("【X - 读取后1K,地址自动增加】\r\n");2 B2 X6 D+ ^/ x+ D
printf("其他任意键 - 显示命令提示\r\n");
5 W- r0 c  ^5 Y5 o0 i1 o! G- L* G' |/ F* E/ y$ C9 B2 o
上电后串口打印的信息:
; i0 u5 L1 H. _, L7 ?. B9 m0 i- m1 m+ K- Z
波特率 115200,数据位 8,奇偶校验位无,停止位 1。% x7 c' ?0 @! `/ E. D( d, I, T) Q
" m% A2 Y; r) p* M* ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

7 U7 R* _' [4 n! m, z- U0 B5 h3 O: Q7 k
" |/ [$ W9 {% \7 T$ x3 [程序设计:' j" X. s0 R/ p0 ?  x4 u  `
% i" g& b! G4 D2 h/ t1 J8 A) _
  系统栈大小分配:5 Y  u" j5 ~0 T4 a+ ^5 e6 ]9 f

9 O- @, j4 S/ [1 q0 d5 T/ U0 k
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

/ e6 L) \# g1 i% \* b% C
5 d1 N( R& e' j) b+ h) q  RAM空间用的DTCM:
0 |2 V1 @9 D7 [8 g! @- {, u
: Q1 L$ G( B: I0 a
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

$ H/ p* ^* _& L, p. W; Y$ p7 U- T/ j& N/ I; g% @+ ^5 J
  硬件外设初始化
/ ~3 s" b0 R$ E( o9 |* S* u% R5 z3 G- W
硬件外设的初始化是在 bsp.c 文件实现:) J. Z- f+ W7 }& K. [
6 u; |  ^5 }0 S
  1. /*6 e! V6 b1 U7 j1 L  F  Y5 }
  2. *********************************************************************************************************
    2 G* h1 b9 ?8 W4 f
  3. *    函 数 名: bsp_Init
    " T6 I# l/ j8 L5 d9 E1 E
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    # g* Q5 U" x* ~+ Z
  5. *    形    参:无
    ; i, N/ L3 s' ]% }
  6. *    返 回 值: 无4 `8 P$ S' z4 X, g% V0 H
  7. *********************************************************************************************************
    . `2 H5 K) f" L
  8. */
    7 e6 W7 u' X7 {! \# k' ?
  9. void bsp_Init(void)
    ' H8 _9 R, b6 l% V; m/ v
  10. {& I% l. |& g* a. w8 D) L2 ^* d
  11.     /* 配置MPU */
    8 |1 ]/ ~5 e5 B- F" S6 o
  12.     MPU_Config();/ c6 ?8 {" P  k3 ~/ f% e# J

  13. ' A5 J% T# @3 D5 n' X8 G* w
  14.     /* 使能L1 Cache */! r7 i" p0 ~$ Q# V0 R
  15.     CPU_CACHE_Enable();6 y; G& c& v: Q6 g# @
  16. ) z; B/ p- @4 h/ r2 q; J4 t/ W' s
  17.     /*
    ) l# i0 x  V3 [& c5 x, e8 }
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:1 `6 g3 u1 [2 n% {: @
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    6 h; G' W# G+ r" I. C" y$ l
  20.        - 设置NVIV优先级分组为4。# |( Y4 C* }# v; f- ]: ^
  21.      */# h% w  [! p* G
  22.     HAL_Init();6 @# D; J* m! d1 t/ ?
  23. 1 S7 U+ @3 O3 `5 n; A
  24.     /* $ r8 k  t6 D. L  S. Z! D+ r- P! ^2 ^
  25.        配置系统时钟到400MHz
    ( d; {5 R# `1 e$ ~
  26.        - 切换使用HSE。6 n' P" }0 R& l. u4 }# M& N6 U
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    ' k, T9 m- C% Y" I+ N
  28.     */% B) g6 @. s; D7 r! Q8 J" {& U# y
  29.     SystemClock_Config();
    / t' a7 Z* A$ h
  30. $ l7 M8 D) O- X5 O  d, N
  31.     /* ! V5 P% Q# d' Z2 Q; X
  32.        Event Recorder:; U0 A1 Y+ }1 |% k* q) |
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    , k8 c! x. n8 @/ `5 y
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章& W7 ]  @1 a8 C& Y) T% Y
  35.     */   
    ! U( L2 s2 p2 I+ q
  36. #if Enable_EventRecorder == 1  # L+ s4 b: _# a$ D: B
  37.     /* 初始化EventRecorder并开启 */; h2 K$ g; j( O
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    / }# E6 A7 Q( j9 s# L1 F
  39.     EventRecorderStart();% e# [7 L( N9 ^& L. t
  40. #endif
    ! k1 E8 i/ O; p, p4 P9 P3 c

  41. 1 b! w9 w; h2 A0 w7 z8 D+ |; [& i
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       * d3 N$ ]: I! F
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */9 E  K& m  |' B1 }7 t
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */
    ( ]) F7 w, V& G1 C
  45.     bsp_InitLPUart();    /* 初始化串口 */  q- A; z% P9 a5 E
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    8 _( _  H1 M6 O  p6 _$ R8 A  C7 C
  47.     bsp_InitLed();        /* 初始化LED */    / G1 p. f. Q( l% w6 n4 \/ ]
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    ! ]! M* T2 Z2 {/ ~8 f3 b

  49. + I5 _# V+ r3 J0 D3 K! K
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */  F" R5 _- U7 Z3 ]
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        ; B6 t7 @, Q+ }3 x" E8 g9 D+ Y' v
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    , w/ s% d7 ?9 g. I% [( \# ?
  53. }
复制代码

" m* S1 w' u2 |: W  MPU配置和Cache配置:
0 u! z- w% ~' |! ]: d
" o  V1 y% I9 R# [5 F* t) R% R数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。) C' x& {/ D# O& L

, H3 e0 c  u& x& N! r# X# i
  1. /*: e" V, a" `7 j$ E- q
  2. *********************************************************************************************************& q: U$ C6 i# K8 ^! \. i
  3. *    函 数 名: MPU_Config% O7 x0 z3 B+ m* _! ?9 Y
  4. *    功能说明: 配置MPU) X. J: F" M% T0 l
  5. *    形    参: 无8 L, g3 ~! m  t  p% k! w) i
  6. *    返 回 值: 无
    : L/ o: E, d6 i% D0 k
  7. *********************************************************************************************************
    " E! P# k. L2 V4 j& ~8 B
  8. */
    ! I  Z8 l8 t5 s" u3 M+ d1 l7 P
  9. static void MPU_Config( void )
    ; i  Q$ j+ f9 K9 U# j# p
  10. {
    6 w0 O( ~: l. ~+ G
  11.     MPU_Region_InitTypeDef MPU_InitStruct;/ h  H5 d! m: T# c8 n, p% v# `
  12. $ c& f; e# d5 l% u4 C$ [
  13.     /* 禁止 MPU */
    ' H6 P, |! }! I2 C8 j9 F
  14.     HAL_MPU_Disable();
    - n$ y, w8 S" f# Z+ F) l
  15. / F7 ]4 o: _4 \' D
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */5 G6 S7 j  D2 q6 w! t+ Y
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    + R1 J! P% X4 P7 i$ T
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;% D6 J( U) ^( H5 P7 n
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;4 Z/ |9 D2 x, D% _/ }+ F% l
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    5 X1 x& J8 V3 k4 {; m
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;% Q- D1 }) I- Y! k
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    9 v6 i+ Z! S0 p, P$ o
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    " u- G- k$ y$ g
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    7 T& h  C0 e( R$ l
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    $ y% J  [/ ~5 Q! \9 O+ b2 n, A
  26.     MPU_InitStruct.SubRegionDisable = 0x00;, d  P2 j! |3 B
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;9 |, ~* m7 Y2 e, R

  28. 3 M: ^) A& T9 g- N1 a0 `
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    5 U4 Q! A7 K- v! `  n8 u0 g

  30. 5 T6 T0 g) t3 [, m6 |8 S, }
  31. 4 d, Y! A6 {" R1 h( o& @! A
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */5 Y8 D- \% l1 w  R
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;/ E& {* M; }1 Z
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    : J. r, ^2 \( g2 M
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    0 V, D* T8 k: l7 U8 l5 h
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    4 ^6 X$ ]/ `1 B) p
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    - Q, f$ l  ]2 ^0 u( ?$ |
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    $ V. n3 R0 d  l4 G0 [) R
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;- T" x# B& ]" g! R& x
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    ; G( j  B: B# r* W/ F# |/ d! P# E% p
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    ( |# F- M% J3 S, _& P% c4 b+ W
  42.     MPU_InitStruct.SubRegionDisable = 0x00;+ D6 i7 v$ ?9 M
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;2 g( t, d1 N, O- S! G6 L5 l
  44. ) u/ y; y0 |4 P' u7 m0 q& w- X% X/ R
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ( [( Y) B9 \% C; f! k  }/ n* k
  46. ( h) Q* B3 k) R# S3 b7 K6 g, M
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */2 x% p: e4 K; c5 s8 t: ?
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ! L. L3 U, I5 r4 ~8 D
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;* |2 y0 h: s+ n: S. B
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    8 e2 p, E# {7 {3 o
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;) y7 W+ Q# \7 J" q
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;+ R9 F& v! |- p* v
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;' E) M& a1 k7 r9 ^" f
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    # A: J) S1 T8 k! @& c
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    " Z. {: F+ q& M6 w3 }! m
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    & N0 s) p8 a, n4 l* ]
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    9 ^* C9 m% Y" b, \+ z% |/ u* S
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    + L& N9 c: `# `( F! K
  59. 2 G5 q2 C* Q9 t+ X- |+ s
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    # }  `; D0 D. ?4 R
  61. : a) x4 F0 n2 {  G/ u
  62.     /*使能 MPU */
    ' q+ \7 C' s* \4 x- j- P% R
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);( ?0 t! Y. k' T( F
  64. }
    8 [) q5 X* ^, i" M# R! B

  65. 6 x+ s  D: {; S8 v
  66. /*
    , S7 [+ J( F& N9 f. b6 m' B
  67. *********************************************************************************************************- a$ y  O. W0 Y4 t
  68. *    函 数 名: CPU_CACHE_Enable
    2 V0 y+ {: v+ L6 j  b3 F
  69. *    功能说明: 使能L1 Cache
      F' e" x8 Y" Y# {! `- X1 [
  70. *    形    参: 无9 P$ m; f3 y& q2 [' ~
  71. *    返 回 值: 无
    8 V8 a6 u+ s7 V
  72. *********************************************************************************************************) g4 l5 p+ h- v1 D4 y# i
  73. */$ G3 M( r5 y  A) e8 \
  74. static void CPU_CACHE_Enable(void)
    + O8 w' E+ L0 }
  75. {
    ! J; F' x& G' {8 J* p! t/ g
  76.     /* 使能 I-Cache */
    $ A# Y, e# U: K8 ~4 @6 l( I
  77.     SCB_EnableICache();2 ?# v, S: S. w4 ?# v  F8 O( E. [

  78.   q- a6 c2 g, V: Y; A
  79.     /* 使能 D-Cache */3 ^) R) y6 O( e8 G
  80.     SCB_EnableDCache();. Z* [8 a; c+ o7 Y9 }
  81. }
    4 C- u- f1 W. W6 v& v
复制代码
4 h, F0 C8 d3 o  t! e

) G; |* D, r' b/ J. x  每10ms调用一次按键处理:
$ p* Q* \5 g( I3 ?- g9 t) U/ U1 v- ]3 b% R, @
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
: z- C" ]: H- m$ `
: R: {5 @4 y1 v5 l
  1. /*1 A* x, Q  _9 y4 g  Z
  2. *********************************************************************************************************
    " p" V% v7 N6 y0 j* G
  3. *    函 数 名: bsp_RunPer10ms. s1 s9 I0 [# J8 }0 l
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求9 i& }2 O; Z0 ~2 \% h! C) v  U
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。, M' H: q6 |: ?* l! a7 c+ e/ b7 V
  6. *    形    参: 无# M5 {2 P+ G0 M
  7. *    返 回 值: 无' F; }! _/ a7 B- u6 L8 t+ Z
  8. *********************************************************************************************************
    4 D- v. p) v) n- K5 H( m' c
  9. */3 N$ @' \, v3 l8 _
  10. void bsp_RunPer10ms(void)/ B8 j5 F) j1 Q$ I9 g; s6 C
  11. {
    * [# o0 P# x# ?  O" V
  12.     bsp_KeyScan10ms();
    ' }$ ]% }1 _: |2 P9 S
  13. }
复制代码
& r* z# v/ w, ~5 u# `3 W; A( C4 h
  主功能:
: s1 F9 ~3 o7 C/ D3 f4 B8 \7 h, U6 b
/ |) i; l$ E: \8 n主程序实现如下操作:
% S( q  {4 m! ]8 S, D3 W$ V
) R9 F6 a% k$ |% y  启动一个自动重装软件定时器,每100ms翻转一次LED2。
9 T4 t# C+ _' e( u* k. L  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可' w( E1 D& `  @
  请选择操作命令:7 I2 ]- D3 Y5 l1 D
  1 - 读串行Flash. b8 W& @. _  l. U% g) l
  2 - 写串行Flash6 v6 A, M3 h! ?
  3 - 擦除整个串行Flash1 i+ g, R" b5 R4 m+ p5 j
  4 - 写整个串行Flash
- ~0 ~  U- s6 o) [- J6 `, z  5 - 读整个串行Flash
" Q" B) e+ d" j: E7 l$ k3 y  Z - 读取前1K
" V7 y  ]1 z5 z  ]; Z  X - 读取后1K8 v8 H2 g3 M) M, `
  1. /*
    ( h2 x6 y6 _1 y/ \, Q
  2. *********************************************************************************************************' i) A7 `1 p" i8 ^3 P
  3. *    函 数 名: DemoSpiFlash2 }; `2 E; T: d5 _5 V& C. R4 w: d
  4. *    功能说明: 串行EEPROM读写例程
    . x2 A/ b8 Q1 g# [+ n! E
  5. *    形    参:无! z( ]0 f! n3 [
  6. *    返 回 值: 无( T' p% R$ Y0 H' R4 s: `
  7. *********************************************************************************************************9 M5 r2 _' K* s2 O2 ?& J, j+ u
  8. */
    " ]$ ?1 h. E0 W! {( L. K
  9. void DemoSpiFlash(void)
    * {& [: K2 P7 k, Z/ {# A& q/ z
  10. {
    2 E* O  e9 D# F. y2 g) m" \+ `
  11.     uint8_t cmd;, b9 l3 E1 x; v; Y
  12.     uint32_t uiReadPageNo = 0;
    8 L! Y/ h9 K, P: `3 D7 B

  13. 2 H/ I$ G. @5 D, I. s
  14. / U+ P( X, D/ ]( E" `6 `( |
  15.     /* 检测串行Flash OK */
    / \% n2 i4 i' x# B
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    $ U& k4 R# u% m/ N" A
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);6 y7 C: Y1 M/ w, |4 ^5 |9 @; d
  18. 9 @0 [0 {& {8 s6 t3 [
  19.     sfDispMenu();        /* 打印命令提示 */
    9 K% I0 h! m) M
  20. , {+ j9 |4 E8 N3 W" y( _
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */% i1 b! c# `0 ~+ j) L* ?

  22. 4 {) p- A9 A! D, X0 o" ^
  23.     while(1)7 N+ K9 {8 Q1 H0 u0 ?9 u5 B* r
  24.     {
    $ K6 n! e- Y8 S7 J: d8 |( w0 N: }$ T1 Q
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */& g. v! K; }6 q4 s8 h9 `+ K. H3 K

  26. / n! G( j  p! H9 n# X3 P
  27.         /* 判断定时器超时时间 */
    ; N/ }; `* d, q0 v' k1 u, M9 d
  28.         if (bsp_CheckTimer(0))   
    " O: o/ x! e2 m5 W3 n  u8 Y6 Z7 o& R
  29.         {
    ; f1 u) x$ w1 R2 F: O  y4 j# t8 H
  30.             /* 每隔100ms 进来一次 */  
    ; X8 l' [' u9 E: A
  31.             bsp_LedToggle(2);
    0 Q! a5 J6 w% x% O- Q+ ^" Z+ i
  32.         }3 n* |7 e1 R) l& ?% b7 K1 r$ [$ ?/ H

  33. + I9 B4 W  O% L* y8 g
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    1 Z5 F! ~( r" ]5 D
  35.         {: a5 @7 J+ {; i5 `: @! [
  36.             switch (cmd)
    % ?2 t2 }' w2 S) X5 O+ B
  37.             {& g# C- |* A% Q, E. N& P$ R6 [
  38.                 case '1':
    % d/ m0 T. A; _: y, X: q
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);) j$ W7 v; S/ S  h; e
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    % ?' H( w8 C+ D) G! Y9 l- Z
  41.                     break;
    * V3 M; }7 P! v% N
  42. # U% ^# {! S+ z8 I8 {9 U0 g
  43.                 case '2':
    : ?* Q! z. `' j! ?1 H
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    : L9 [8 X/ K: K9 _' |
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */2 E& K5 z7 \! |8 z6 F
  46.                     break;+ ?, C. D  k' y/ T* Y" P" X: ?" f$ u

  47. 9 f* V2 M% b- T- c1 F  J- ]# q
  48.                 case '3':. f6 o2 @8 \' m+ i
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    9 `, L( p5 L9 ~6 G
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");/ I) l8 e0 p, k; m% s
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    . X7 V( p: `' {9 |
  52.                     break;
    9 e% x& l7 u( Z5 D
  53. . u0 M/ ?% ^6 i2 o& N) ]: N
  54.                 case '4':5 W& D/ L7 k# C+ m4 _& y0 I
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");1 ?% J; o4 t. j" b+ Q6 T8 s
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");9 ~) I' e1 }6 w6 b) H9 g
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */2 j7 }: G9 p0 |, b
  58.                     break;
    , }: k4 D5 j. P4 I" k$ t

  59. * d9 @, V0 s+ K" [9 E
  60.                 case '5':$ N! o2 K& t) E1 d5 |
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));2 A/ L& Q  [4 M4 s0 Y
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */+ C* x& t6 }' D4 [% s# p
  63.                     break;3 _% z0 x  N* Z( d. g
  64.   i' g# F/ S; i( n
  65.                 case 'z':
    & ^& W! H* S2 a( b. C, l$ b2 L( d
  66.                 case 'Z': /* 读取前1K */5 P' Q, R& i- a+ h! N5 @* i
  67.                     if (uiReadPageNo > 0)
      E: `2 l7 s  C3 @8 l7 B. O
  68.                     {9 I7 N  w4 k, o
  69.                         uiReadPageNo--;* f  y7 W/ r5 P( Y
  70.                     }, C) I3 y7 ?3 Z
  71.                     else% D0 @6 |4 o% ?: z
  72.                     {7 \1 x& b8 I/ F
  73.                         printf("已经是最前\r\n");
    1 F. m& `/ v. C" o  x; t) ]& \
  74.                     }0 @5 O1 e! w  e; ^  K8 T/ S
  75.                     sfViewData(uiReadPageNo * 1024);
    : ~$ t# |. t3 t7 O
  76.                     break;
    0 Q: c0 x2 [  Z! e9 e- e$ R6 [$ }
  77. ' n/ w0 t. }1 O" n  f9 ~
  78.                 case 'x':
    - X# O" n  M2 d+ ]
  79.                 case 'X': /* 读取后1K */( h( U& W& o* P; d( {
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)" u) J+ _& C+ k* \8 B
  81.                     {
    ) Y; Y, Q) Y6 E  O( f6 l' {" m
  82.                         uiReadPageNo++;6 v" @6 S- H6 n$ T9 X
  83.                     }5 s' F% M* y1 h+ f) L2 b5 p
  84.                     else
    ) H  F4 ?4 v# h7 V0 L1 e# j9 a
  85.                     {3 ^+ ^. ?% }3 P- m( J
  86.                         printf("已经是最后\r\n");
    7 L* }1 E, U5 p; Y
  87.                     }
    * c9 ~! B- L$ m" w$ E6 E% o5 y
  88.                     sfViewData(uiReadPageNo * 1024);5 D- E8 e( V& l% K
  89.                     break;% F9 i9 ~# U& U6 |1 o% e9 V2 v3 K

  90. + B* I" F+ u4 h
  91.                 default:
    & I: R% ^/ G: ?/ H2 j7 D( N+ B
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */$ U" G* R' v1 H  t* ~7 S
  93.                     break;, o- y2 z! k3 o3 y( C
  94. * g& C. K( q: J$ o) N$ P
  95.             }6 A/ }4 D5 m& N1 [1 S3 _
  96.         }: |7 }  Y. B6 F* r: U- O
  97.     }! |  o" y7 s" e9 m
  98. }
复制代码
& I$ K) x+ l" W# C! @' h/ o9 I8 L
73.11   总结# ]+ x+ \* r! M2 y; P  t' e
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。
/ [2 ]0 x: x$ F
4 H" x- I0 A1 H7 v' f/ O! j1 A2 H1 v+ s& P* u1 L4 m& _

5 l" t5 t# Q  N1 K9 t" N
收藏 评论0 发布时间:2022-1-1 21:00

举报

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