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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示2 Z; n, r0 U  h$ s& d# a. D
  W25Q64FV属于NOR型Flash存储芯片。
  J! ?6 |- R. w  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
5 L( |& V, [: O) D! w5 {7 T
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

; p9 F# x% R' @" @* ~
( f  C: G- W6 K' m  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。
- Z) e5 |& G# D2 r' F7 e1 H0 V  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。
% v; |/ I! n9 r4 t  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。
6 L# m* i' x. x7 C& e3 m- h5 I; p  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。% s/ n, y2 D$ E# [
  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。
0 Y8 C) h" t, k2 x( r
  P( a# q9 Z5 Q- W# N" S6 ?73.2 W25QXX硬件设计
1 ?: A. f2 T  K1 V# OSTM32H7驱动W25QXX的硬件设计如下:
- V! y6 o9 r: t  m1 q$ ^
+ M* a% _& @7 K! w  p! {, N! m
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
+ W: d0 N% P2 V/ L

6 g; Y/ e& _/ c9 d关于这个原理图,要了解到以下几个知识:
# V" w9 A9 x3 t  F6 U% C( W% ^0 X! a. X2 c  I
  V7开发板实际外接的芯片是W25Q64JV。
( j/ ~# A1 u( V9 d- K9 c  CS片选最好接上拉电阻,防止意外操作。
9 J1 l' c1 s6 p1 C  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。& a6 V$ K/ o& w7 C
  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
, [: O7 {- l8 j' \5 J' Y  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
6 m$ D, W' Z3 e2 d7 v" S7 E73.3 W25QXX关键知识点整理(重要)4 p  l( e/ U3 ]. e, M& r
驱动W25QXX前要先了解下这个芯片的相关信息。% y" m& Z! \9 N! ]1 G( L

, |9 j/ \3 S! U8 P  e) D7 e1 ?( Y4 P
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

9 ]- a" Z+ j! ]2 e  K# B; O7 w; r9 e) t+ ?: B. M4 a) O* H/ T
73.3.1 W25QXX基础信息

% `% E# G$ |- u  W25Q64FV的容量是8MB(256Mbit)。
+ F# I& i1 c- Q- n/ N4 Q+ M4 M/ ?  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
4 ?% {" n. V) ~支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
& x' i! V: u8 l! H  V; G0 `/ @. R
支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
" n5 c4 g: Y3 `( a4 _1 M
! @8 B/ l0 u& g0 F: c% R(注:这里几线的意思是几个数据线)。
4 C/ c/ s. Z) D. x  ]1 Z: ~  k( }/ w$ Q
  W25Q64FV支持的最高时钟是133MHz。2 m0 D. n; b) _! H7 Y$ I% Y+ L
  每个扇区最少支持10万次擦写,可以保存20年数据。5 d2 @4 o+ |# F. }. `2 G5 Z7 w" }
  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。7 i  A6 i% L+ U2 @8 o# H
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
6 q  @( U4 _4 f: o# N, F整体框图如下:
' P) k  u& `  j6 [& S: O7 W$ [6 ^4 C2 [7 K" C! h0 I+ h
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
4 Y  x+ F& F+ K$ k# B
% W0 U0 \7 z/ [7 |
W25Q64FV:9 l5 o2 j0 P" E, O
( \4 v' ^9 C* A" r8 L4 i' `1 ?
  有128个Block,每个Block大小64KB。
, B# k2 a" }8 f) C8 v9 C  每个Block有16个Sector,每个Sector大小4KB。1 {1 E) P3 Z) X* t- b# o9 J3 ~; }0 j
  每个Sector有16个Page,每个Page大小是256字节。- y3 o, T& L1 o/ R- `
73.3.2 W25QXX命令  ~( f- a7 n0 M/ a
使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:
: D1 j) m3 d, ~7 b( Q) y' |( Y0 d& C$ d7 `4 Z1 t2 f) w# w
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 ]' T. Y& P! X1 n
9 {* N3 x* k  t: [当前主要用到如下几个命令:
* f  L6 X/ R( A* i, A& n+ W& X$ F1 H) R/ ]
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    ; {3 e) X- E! o
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */# A/ n3 ^- D9 Q  n* G4 u' @% a
  3. #define CMD_WREN      0x06    /* 写使能命令 */. [/ n# P5 F. ?
  4. #define CMD_READ      0x03  /* 读数据区命令 */6 p5 K0 J/ W$ h0 L% @9 N: b
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */5 y/ @- h& }3 n) z! ?3 i
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */  D0 P7 O4 k% V& E$ Q' o6 \% Q& l. |2 ]
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */0 x0 M+ ~) i" r* k! L
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */2 r1 y$ A+ ^+ e/ b& e
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
( F- v) E% b' T% w0 R% s1 `
73.3.3 W25QXX页编程和页回卷2 X- E1 y( T0 v) N4 I
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
9 E" g  ?. F1 R' T, @
* b7 V* b! J% m4 z) K+ [, K" @
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    , Q" V( \' B( b# k  L1 d; g
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   & @, ~7 r* Z& T  O) w% ?- Q  o
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   , K/ f" ]' d- q5 {2 j( h4 s
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    ; d0 [( o! s: r7 p' n
  5. , j( |  v' u% x2 q; E
  6.     for (i = 0; i < _usSize; i++)2 m9 t1 A7 f" n
  7.     {+ X$ b* _( p- S
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    . g( x9 c0 R, j$ {' R7 q
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   6 K% W0 v; e- f  V! T
  10.     }
复制代码
7 x) s  Y, [7 L/ m4 D
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
5 l9 \0 A( p' h  P  N2 l% R: u
  }" ^  ^  G& y8 N" [# s% q比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
. d  g5 n' E$ a9 O8 d) `( ~- t7 ^3 u0 ~: [4 Y% F  N# d% \; X; F0 A8 Y
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    / x' m: V) A0 M2 ?9 f/ S- J+ X
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

) T9 O) o! G5 g# D1 c! H1 w! r  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。+ J$ S; ?% _" [5 b
  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);
  ~- H: S) B$ R: S7 Z7 m( L  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);4 U  i/ L7 K- c7 j
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
$ X% h" h* m9 K9 e6 ^# V' `下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
3 h+ k" p3 n: m$ ~# g2 A; \% Q' w: W2 z! p7 `( F: C
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
- P4 S9 C5 R$ }$ W/ K

# s9 M  P* e% h' h& |& ]; }$ a& W) w* x- R: e5 S( `1 j; x* H* z
73.3.4 W25QXX扇区擦除
' Z0 G' G$ s8 |9 r) WSPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。  ?* d6 X2 ^/ [  {
/ o5 I3 |, {: h- P. `
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
3 L& F; v: [! ^( e8 o5 x+ S, L" L
73.3.5 W25QXX规格参数

) [# ^% X' ^5 ?# |, w这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
* H; |  J, d# Y
2 E" ^7 g3 n5 s7 T6 q/ Q8 d
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

3 N; i1 B9 v7 X3 r! x7 x5 A' @! w+ l' m1 |; ~
  页编程时间:典型值0.4ms,最大值3ms。$ S+ H, e& N; ?
  扇区擦除时间(4KB):典型值45ms,最大值400ms。
: ^( q4 r5 m7 q6 \5 q3 O& j9 P7 V" \- j  块擦除时间(32KB):典型值120ms,最大值1600ms。- d1 D* W# K4 A% p
  块擦除时间(64KB):典型值150ms,最大值2000ms。
! Q( Z; W# E+ b  r  整个芯片擦除时间:典型值20s,最大值100s。
- z) K8 Z/ D8 @' s. C1 V( U8 n; Z! D1 [6 B

' d* y! D- Z7 Q6 }2 @* Y: V! `支持的速度参数如下:  N$ C2 P8 |0 n' t

9 f6 T$ f+ f- }4 O8 D
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
1 T( F* @( v3 P( q
, f  L, I7 m  |
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
4 l8 C( F7 `/ B' v: P- ?' m
% P% c2 Y" l$ K) g9 P& S/ w73.4 W25QXX驱动设计  ~, @6 i7 \, l1 A# o
W25QXX的程序驱动框架设计如下:4 J- B1 Q+ B9 a7 k$ m

( l$ K& Q, J3 _
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
& L: P  y  U' h* c3 R( s
  g9 A; p4 w$ Z5 M$ P  u* w
有了这个框图,程序设计就比较好理解了。$ f2 [* X, I4 T" n8 |) p

. h, B( u  y( G) L  \: y73.4.1 第1步:SPI总线配置. F8 M1 W! N8 D; ^! m: G" I
spi总线配置通过如下两个函数实现:% h1 A5 y" a  w  e

3 u2 `7 |4 A2 |. P) v, o2 [8 H
  1. /*
    + Y: }+ b, D" l% @
  2. *********************************************************************************************************
      ]0 }. t, I2 V' C, A$ E
  3. *    函 数 名: bsp_InitSPIBus+ M/ d9 t  e9 p5 p: |
  4. *    功能说明: 配置SPI总线。
    # B: _: v6 o% X8 W3 c6 s
  5. *    形    参: 无
    # b0 i! y. C/ c- U
  6. *    返 回 值: 无
    3 p1 V* p- {# ]2 j+ x' K& K
  7. *********************************************************************************************************
    ( U' N/ ?( `- b7 |9 ^; c
  8. */: J, t+ r) K1 ~2 ?% x
  9. void bsp_InitSPIBus(void)
      ]* y; P7 m$ r: q1 |
  10. {    4 v- u% k& A3 @+ A0 ^. `
  11.     g_spi_busy = 0;# c3 f6 ~: D  K% X$ s4 o/ H" L

  12. & G7 ~8 {+ ~8 ^8 m$ j; E. ~
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    ! x1 Q" [) `0 P+ k
  14. }
    & i, t& k8 x% V. r7 H* k" q1 h
  15. ( [& K' c! w$ n, `
  16. /*% R: s5 b9 H$ t9 M% _
  17. *********************************************************************************************************
    * ]. G$ ^: i" A- P
  18. *    函 数 名: bsp_InitSPIParam" a9 Z# s+ N* I, C6 K, K
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    $ |% A" v5 R% R$ W4 \
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:0 P3 o9 S8 }3 \! d$ t
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频- u$ a  W; Q& c
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频. J7 d- u  H6 X8 Q. L
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频
    # v0 u4 a1 s; N* _! r- f
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频
    . S9 b9 Z3 M- G9 G9 `
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频6 j5 B8 X  z3 G
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
    $ O1 e0 B% _- Z6 Y
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频4 U- w0 \; y  w! @( f
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频4 {9 `7 o$ Z6 V! j" T
  29. *                                                        2 E- F$ N# j- p/ b# N. c
  30. *             _CLKPhase           时钟相位,支持的参数如下:
    : h: w, {# a2 z: d9 Y9 B% u
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
    6 Z+ t7 N3 N9 M2 d# {0 W
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据+ T( b! h1 I7 g% w$ Y) m
  33. *                                 
    9 r! j! t; u! C  Q# V/ R& a
  34. *             _CLKPolarity        时钟极性,支持的参数如下:5 u/ P! ?9 C& S- `' L; E" w' t
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    , E. H8 e  r. C! ^$ m
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平" M6 L, k; ], C
  37. *
    : W, I# p$ X* m9 k
  38. *    返 回 值: 无1 X% U+ c& h! R
  39. *********************************************************************************************************
    ! |+ H4 x* T( |! ]5 Q
  40. */  F7 T2 D0 z) d3 {1 O
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
    ' t7 @9 ^7 K7 Q8 Q+ b
  42. {
    ' S3 c( O! v3 U, t/ n" C  D
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    2 O) |$ m* |* F/ A! Y
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)) T' |- F% V1 P7 f
  45.     {        
    ) h4 I* X" d: ~; a+ H- W$ z
  46.         return;
    * d, w& q2 q4 P" y, a1 e
  47.     }. @% ]& u" }! F+ u- h4 A2 E0 e4 ^
  48. 7 |0 R, g' F& R! y+ D! Y
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
      G$ Q/ f# B) h4 |- H( o. m
  50.     s_CLKPhase = _CLKPhase;
    & f0 ]* d- J  D+ b, O& p
  51.     s_CLKPolarity = _CLKPolarity;, c- O: x: r9 ^, q; S0 ~& l; e
  52. 3 f/ Q+ V4 F9 b7 S3 J

  53. , }8 l' [: B( P6 x5 Q
  54.     /* 设置SPI参数 */
    : Y' J+ p' f; t4 B( E
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */, d; e' w8 {. w$ U" H
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    3 A5 V5 X  t/ A3 t$ X* x7 B- ^
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 *// A. ^4 O' {: t' L# T. M
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */, b8 u* ]7 u' m9 d% S4 b5 ]
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    $ S' i# u: ]+ }8 j" [) f& K- h
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
    9 S9 @; Y7 d7 x: R! c& L% p
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
    0 a, t  z; D8 f% u$ V
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    # O3 ]! {* r. j- D! i# O
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */! M+ @7 o% G: ?( }$ X2 y- N
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */- b( G" I# `& G/ ]7 i" ^- W! w
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */8 U8 ]" X5 V5 {, t. g- [
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */5 o% \3 Y5 H! B2 ^
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    . C2 H8 i5 X7 N3 E5 I- @, T
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    0 l1 I+ a  s, v% p! @3 Q% V3 A5 U: e
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  2 F* R& {2 P4 r
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */& R' A. T# Z5 k9 J, P
  71. & y  ]1 @1 z! G9 |; c3 h
  72.     /* 复位配置 */2 a9 c+ C9 W7 N* P$ r- N: b
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)% z* M4 a7 o, `9 {- n2 U
  74.     {& ]$ Q' ?# P8 p8 n* D4 y
  75.         Error_Handler(__FILE__, __LINE__);
    ' D7 v8 U4 r: K: `% X2 r) q
  76.     }   
    - l2 w/ [" [! L# f7 Y) w

  77. ; w1 A# z4 b8 c9 M3 W
  78.     /* 初始化配置 */1 t2 o% {  O8 Y$ o
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)
    & L2 ?; E6 }! n
  80.     {$ Z- J0 i+ w3 ]. w. z+ P9 v
  81.         Error_Handler(__FILE__, __LINE__);
    ( ~  i2 A  u. \# m6 u
  82.     }   
    0 j% E) g7 ^* R/ O' E
  83. }
复制代码
! e% x; l; u' i9 N2 w) m5 o, x6 d
关于这两个函数有以下两点要做个说明:
5 ~. |7 l3 v0 I+ j5 [. Q+ v- B0 e3 X; N0 u! v' y- ^! E# R* s
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
$ i/ k' c  T; H" J  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。/ v: J7 B% _$ S2 H$ k
73.4.2 第2步:SPI总线的查询,中断和DMA方式设置
* m3 _4 J1 J6 r0 U1 d& PSPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:1 d$ c, \! Y/ k5 D3 E* \7 Y" k
, T  A4 C. z( H7 f, P( b! J
  1. /*
    ) m% A" H; `) T) }/ \6 `
  2. *********************************************************************************************************. d( d% U, D9 H3 E/ ^) f& G7 z  r
  3. *                                 选择DMA,中断或者查询方式
    4 s$ ?! i+ r- q" v/ ~
  4. *********************************************************************************************************
    1 j7 X; X* r8 Q: H, }1 q
  5. */
    ; u# i- k9 K# D% A) Z! v
  6. //#define USE_SPI_DMA    /* DMA方式  */
    $ J& o- q$ [" K- @. ^6 B1 l' ^' M
  7. //#define USE_SPI_INT    /* 中断方式 */
    : G) E5 F  k- w5 u# G* m8 d& B
  8. #define USE_SPI_POLL   /* 查询方式 */
    5 ^' X. a! A) L& r6 Z) e& H. E0 w

  9. 6 l5 c- R6 s/ g+ A
  10. /* 查询模式 */
    2 m% V3 j) ?  T+ B* y& J  [
  11. #if defined (USE_SPI_POLL)
    + t! y* |2 u' G" r1 @2 j+ c
  12. + v% d9 k# H2 C& c, `2 H0 K" D
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  , ^+ E4 i8 Q) F) D1 t4 j7 T# m
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
      {& N* X4 M' M# ~
  15. 4 c, H( i8 f  U6 m8 |) [9 o
  16. /* 中断模式 */  ^  v( A2 f2 v0 J# A$ ^
  17. #elif defined (USE_SPI_INT)) u1 U0 h/ Q9 H, C  j
  18. ; @9 T& n* h' C$ q) I/ w
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    0 A+ o/ |. I5 M! A+ l) O( u0 R
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    $ |" e5 j8 O6 c# @9 }1 L( ~! n

  21. + O. ?, k0 ]3 r+ j7 L; i
  22. /* DMA模式使用的SRAM4 */
    2 l, e; }/ M+ v  P
  23. #elif defined (USE_SPI_DMA)1 r. \4 }6 E6 I% [! l2 W3 s) h
  24.     #if defined ( __CC_ARM )    /* IAR *******/0 r. A1 z" Y) T8 }  v; Q1 V7 o
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    ' S; c* e: Y$ m' H
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    : \, A% z: G% _) ]" n; Q+ E
  27.     #elif defined (__ICCARM__)   /* MDK ********/( i8 r) A1 T: [6 u; Q; s" v. S% C: i
  28.         #pragma location = ".RAM_D3"
    ' w' m& o/ q0 [4 ~1 h( `
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   ! }6 D5 v2 \6 n: E, P
  30.         #pragma location = ".RAM_D3"1 |: C6 E2 Z, B) `* U
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    , `7 y" P% ^' J, G# e
  32.     #endif: `+ R! w# F! t
  33. #endif
    * e, N9 I+ `) Y; S. _
  34. ! g: ^& o0 N4 `: W7 n6 F
  35. /*
    * C) T4 D: b2 h& c8 _% [9 E+ |
  36. *********************************************************************************************************6 K6 S+ n& D' v4 X5 a2 d( w+ h. l  j
  37. *    函 数 名: bsp_spiTransfer% g6 c7 K$ O+ L' W; M1 ^, \' F2 x
  38. *    功能说明: 启动数据传输: V) X3 w2 ^& }. o1 |0 v% u
  39. *    形    参: 无
    , J# a' r0 r9 m& h9 X: i
  40. *    返 回 值: 无  y$ g. W& l: S1 A3 B
  41. *********************************************************************************************************& H; k: r' R; X5 v. g2 ], c
  42. */
    6 @. x) p& I* O0 p" Z3 n8 B0 `  J8 V
  43. void bsp_spiTransfer(void)
    . O( F! v0 a2 y5 w* N. r" O
  44. {6 E/ s, V7 G1 x, t
  45.     if (g_spiLen > SPI_BUFFER_SIZE)* d. K2 x$ k" x8 p
  46.     {. z) f. }$ w* L& C7 ]% O* t7 e2 D
  47.         return;3 R# f1 [, Z, z& n* p+ [
  48.     }
    5 w; g( i1 j8 M: b
  49. + S% K5 F2 C5 E- q' ?4 W
  50.     /* DMA方式传输 */
    4 f' ~' h& B8 @. ]2 }+ W
  51. #ifdef USE_SPI_DMA
    1 u# b+ W  z6 r' N
  52.     wTransferState = TRANSFER_WAIT;/ _: @& R# z1 F0 N5 X3 t- ~
  53. ( E" B. ]6 R: ~9 o$ c& N$ q' `
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    5 @  L7 F% ?+ L) G& y
  55.     {
    : |# \* A# A! a5 y. x
  56.         Error_Handler(__FILE__, __LINE__);: ]% {# s9 g* C) I& e
  57.     }
    & r4 y" C2 M4 W% H9 N+ V# A
  58. & ^: q# m/ N! V! j" [9 x
  59.     while (wTransferState == TRANSFER_WAIT)
    6 p4 Q; [9 Z: r" D$ I- a; z" ^$ t
  60.     {
    ; u7 P) \, r) h0 n
  61.         ;% Z; S3 f: p7 r2 g# K' O
  62.     }
    . q" v6 X2 O: k% ?8 G
  63. #endif8 M: g) a& w: Q- ^

  64. 5 F7 F8 E5 V' H5 x# [1 ?
  65.     /* 中断方式传输 */    5 [% @4 _- Y3 Q; E6 y7 O
  66. #ifdef USE_SPI_INT( b: y2 ?9 K- p: i6 Y
  67.     wTransferState = TRANSFER_WAIT;/ L) Z% t) w% W2 g3 J& O9 Z* a

  68. # k6 m  t8 q3 |  T/ j+ Q5 Z
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    ' ]0 @% f, [- k; ~, s
  70.     {
    : M) W3 Z- l/ d# e  h
  71.         Error_Handler(__FILE__, __LINE__);
    - s( p  \# R+ J" ~- g
  72.     }! p$ r+ T9 D- X1 u; Q+ _. p+ N
  73. : G* V; p) U# }2 Q
  74.     while (wTransferState == TRANSFER_WAIT)4 \0 {3 R2 b1 x
  75.     {
    % w. t2 I& T& \: L3 g& G
  76.         ;4 E- z' D  m3 a! H2 X3 I8 h( Z
  77.     }
      s' ]' o" }9 f# V: e* F5 T
  78. #endif3 X# g% D% M7 u) K. o! e+ C
  79. 0 H- b' h% {" V: j$ d1 L
  80.     /* 查询方式传输 */   
    & x# ~  {3 G# {, r* W0 C' V2 A
  81. #ifdef USE_SPI_POLL% }0 @. D; J- A
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)   
    5 O; [: I0 w4 \
  83.     {$ U- z* b8 c* Z# {# s1 e
  84.         Error_Handler(__FILE__, __LINE__);
    7 g9 q7 L$ c/ H( ]1 @5 i
  85.     }   
    % M; ?8 m; _& w3 h1 [. n4 V
  86. #endif. C. F) ~+ b& B7 P# Q# O
  87. }
    1 v  q5 C5 B+ H4 M* b# {* ?
复制代码
! r1 b! h& ?* g7 x
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:, o. W+ n1 N+ p! ^! b3 R
/ M5 I/ `1 ~$ C* G' x1 R) h; k
  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。7 `% X* G9 p0 f" a& X
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。* Y& a, }3 }$ I0 Q
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */. L, N* c2 l  n7 i3 u
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;- o/ G4 s" h2 H0 U
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ) n5 P) C/ u  S1 q
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    # }; B- ^- J+ L" u/ F1 e6 v
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;' l$ n& `2 |8 h8 X( \
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;/ C/ k3 m, ]- C' p1 z2 q
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    - A7 ]- S9 q% @: J6 y
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ' A: s+ `& H. H: F1 g
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;  ^. Y' D/ o) |4 Z( t) C0 l* t8 {
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;4 W% i$ P: _5 Y) L" s" I5 e9 K; A
  11.     MPU_InitStruct.SubRegionDisable = 0x00;
    0 f7 n! \: P$ D  ~
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    3 Z& x" Z2 l4 x9 @. E

  13. # r4 W8 j% v1 r1 z# c; M  A3 A; A
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

- n  g1 e$ b, j, o5 M- [0 N3 r73.4.3 第3步:W25QXX的时钟极性和时钟相位配置
- s; H7 S. E' [2 m6 y* n$ w2 C$ m- p. D
首先回忆下STM32H7支持的4种时序配置。0 s0 }5 g" O7 n! T) q, p, J2 Q
! l2 a5 p( n6 J7 D5 f+ X
  当CPOL = 1, CPHA = 1时
/ E  z  e. a( p# e% u% ?. M( k1 ?6 Z: l2 ^! y4 t6 s. w
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
/ V8 H5 f, T( x  A% x) @, a- V2 K0 [
  当CPOL = 0, CPHA = 1时
- t  t- z. K$ n9 [9 Q7 `2 o2 `( J  m: O$ ~2 L
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。6 L2 c3 x8 Z1 J" e* W

1 M! f% G2 F0 u" _. b$ j6 [  当CPOL = 1, CPHA = 0时
* p6 ?- I  Q% M3 T) q3 U3 Z4 f0 [
  D3 ?% {6 \; p, y. {SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
. T' _1 b3 G) E6 D! J) L
4 L5 f1 R: W; H- m1 P$ |- e 当CPOL = 1, CPHA = 0时
; X0 C% }. M% a" Q6 q8 n' T2 g  v  A  B; m
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
5 F: ]9 J6 a+ G; Z% p- d( U7 X, S9 |' c# W2 `& m
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 `2 |* c% _6 J1 \. i% T/ ^7 Y% ~5 _, ^# f5 i5 x$ G9 z
有了H7支持的时序配置,再来看下W25Q的时序图:
9 X$ c4 U8 |% W% k7 Y' ^9 L8 ?# v# W: m: C5 `0 ~# l* Z/ `7 ]. `* z
Mode0 : 空闲状态的sck是低电平。
  P6 A/ [  V. _2 G* v- i9 M, l" K  l4 r( s& ?7 _
Mode1 : 空闲状态的sck是高电平。' R- x" J2 J1 n# _) Q! k! a

! W- _2 C6 P& o5 T7 S$ T0 J
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 V& A! W; T) @6 F% e- a. A- S; T. [/ t  v! W9 O, F
首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:( X4 F' @1 d8 z3 j- U  d
+ o" \( W' |- {5 D$ ?
CHOL = 1,  CPHA = 1
# w( R# k8 H1 K! m: v. h' p: a. W/ u" P! W
CHOL = 0,  CPHA = 02 b( }) I+ b4 n1 r& R& {! q1 v
& {  o/ K: M3 i/ u
对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。2 q" k& M: r2 U$ M$ d

" A& b, }9 `8 w5 C73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制
* |& L7 f; y) a7 D单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:; O5 u6 ?& [) l' C! N5 K

# t$ \; j1 O5 c, K* }
  1. /*$ f+ x4 o7 r9 J: k0 T
  2. *********************************************************************************************************
    , Z) g6 Z$ A. y* I7 e3 b" v0 L' V+ \
  3. *    函 数 名: sf_SetCS
    2 k0 Q. V9 r' c  w1 ~
  4. *    功能说明: 串行FALSH片选控制函数
    ! N! H: g/ t; E4 \" S
  5. *    形    参: 无- V5 Z0 L/ m2 U* p- q5 a" |
  6. *    返 回 值: 无
      O1 E& E* S" X* U* [
  7. *********************************************************************************************************
    * {- D) ~. _+ k0 n
  8. */* n6 i1 y0 C4 D
  9. void sf_SetCS(uint8_t _Level)1 L( d/ z+ A  }6 M0 _/ a
  10. {8 ^/ \) k8 S0 I. `5 P7 ]
  11.     if (_Level == 0)
    - S1 O8 V  U8 W. K( R, D2 J: j
  12.     {- I9 v( o$ k. T* E5 d1 g
  13.         bsp_SpiBusEnter();   
    6 E' i4 |  _5 e
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        7 o2 j( J6 i0 u1 g
  15.         SF_CS_0();
    5 I7 j8 Q+ j! W
  16.     }
      k. u# h/ _) P% V. z
  17.     else0 O+ R! V8 C1 a8 H$ \# S/ H: e
  18.     {        2 d$ V+ l! i% L  k# q: Q  F
  19.         SF_CS_1();      M. g, f7 \3 q1 b3 @
  20.         bsp_SpiBusExit();          j" R! w1 f) \; l
  21.     }: t7 M. g! q6 E$ U0 w! T! a
  22. }
    $ L' \4 `8 N* _& ~7 ?2 l( [  W& O
复制代码

# X8 F$ P, f; |0 V  S: o" B( N
7 D2 N2 _* ^0 g* {' {% U. p通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。
3 D! r; j. D( f, S* T. ?! e3 b9 L. R( s. A$ _2 s8 ~4 M0 f/ ~
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。
" B3 U, F/ G- }, T1 V. g1 w" E* N( a- S9 ]; |+ u4 R! f) f
73.4.5 第5步:W25QXX的读取实现
- Y$ r. m' R2 K: s$ Z+ Q8 Y
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。
& w- j# i3 x8 O  C- A9 S( I! l0 V& [6 G0 {3 `  U" Q( E. l
  1. /*. T+ x$ D' _) C/ D) S, n
  2. *********************************************************************************************************; B+ F) P( F& f
  3. *    函 数 名: sf_ReadBuffer
    & s9 V& Q1 P9 _. |( @% X6 U
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。, ^+ P# y1 m: L6 U& d
  5. *    形    参:      _pBuf : 数据源缓冲区;6 p% g& o  r6 g! R
  6. *                _uiReadAddr :首地址6 k2 g6 a: O9 z  Q; B, b( b
  7. *                _usSize :数据个数, 不能超出芯片总容量
    4 f& M9 v) a, s4 k2 z5 a0 _
  8. *    返 回 值: 无
    ) c  v6 b/ o8 k* S, P1 t7 ]
  9. *********************************************************************************************************0 _5 t5 A6 [9 @# l
  10. */
    6 `* q7 n0 E; l) _9 T! c4 J' }
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize): n0 z5 P" X/ Y1 @( N# @
  12. {; ^5 w% b+ m, U; |/ L9 h
  13.     uint16_t rem;
    # u  t) K5 e  [: m$ b* B; s, J  ]2 u
  14.     uint16_t i;- Q3 ^1 o3 b3 I. ]: X3 V

  15. & r, _3 p' H" [7 O0 \  S% f9 t
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    8 q9 e; f; h& |% n) T# o. D& i
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    " m8 H( i' o1 [. s
  18.     {: H1 Z, J. }! @* ]
  19.         return;5 e/ I. Y1 @7 N9 L
  20.     }# n# f+ K' v" {6 U& s
  21. ' I" c9 h, t& @/ E8 B
  22.     /* 擦除扇区操作 */
    8 e2 Z4 u+ W0 l1 D+ A
  23.     sf_SetCS(0);                                    /* 使能片选 */5 z! p9 ^1 c& i# k4 O( `$ p' v
  24.     g_spiLen = 0;: K5 k; l$ g1 B: S
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
    . m  o& p. {; n  o1 v- [! o
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    1 q/ b; W2 _# v- w# D
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */, L" _- g5 L/ N. _
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
    7 T4 X( H& a" G7 I! B! Z, |' K1 o; S
  29.     bsp_spiTransfer();. z' a0 w" b6 G- U3 u# r
  30. 9 D; }# @4 g' |& r7 H) ~
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
    ( ^5 U. }' r+ B0 N+ [
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)  b" a3 I' Z/ w, @" O& B
  33.     {( w( _6 ~$ M( N# ]0 t. K
  34.         g_spiLen = SPI_BUFFER_SIZE;; q- j' M$ @! }
  35.         bsp_spiTransfer();
    1 I3 y$ m. s& O0 S# U. @
  36. 3 e% ^9 Q% ?; o
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    + N  L9 v5 K1 G" k6 V6 K/ f
  38.         _pBuf += SPI_BUFFER_SIZE;
    1 z, t9 ^. o# ^
  39.     }
    $ ~( F3 A0 D3 t7 y
  40. 2 x6 y* a) u+ r
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */
    + P' ?) ]7 Q9 ^
  42.     if (rem > 0)+ O5 ~" ]: P- ]! d; w5 {, M
  43.     {
    1 I$ J* P" e! r0 m( m  U- b
  44.         g_spiLen = rem;5 X1 ^! Z0 M, ?4 w
  45.         bsp_spiTransfer();
    / D4 u, s4 ^9 \6 f$ R- ?2 a

  46. 1 e+ S% g2 O( _1 W# ~# Z( {
  47.         memcpy(_pBuf, g_spiRxBuf, rem);) q& ~' b" M, N) ]. p, [
  48.     }  n/ ?# {$ V: M, [+ x) }$ j( N5 `
  49. * @- n: c- Z9 Z8 G
  50.     sf_SetCS(1);                                    /* 禁能片选 */: K1 e& C8 d" z$ M
  51. }
复制代码
9 ~) J' L( a" ~/ O/ D
; a' o3 V: y! x. `$ Z: P8 J2 o+ v
这个函数对DMA传输做了特别处理,方便分包进行。6 l% f9 I. Y. e+ Y/ G% {" T

' y8 i7 `: `7 Y2 F% [1 T( g73.4.6 第6步:W25QXX的扇区擦除实现

% d+ N2 b; ^3 @: ~7 X/ i扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。
8 l! ~  B7 ?5 _# `: J
, y' u! g+ v3 }1 P. G4 Z
  1. /*) ~: Y8 b" Q4 l8 f  D
  2. *********************************************************************************************************$ a; I+ t9 W, e) _3 j, N. t
  3. *    函 数 名: sf_EraseSector
    : l: N. e0 _. I
  4. *    功能说明: 擦除指定的扇区
    ! W* w) S6 ~5 b3 S# G$ P; p, t
  5. *    形    参: _uiSectorAddr : 扇区地址
    ) \+ m* O; M: ^; N' K( ~9 b2 J
  6. *    返 回 值: 无$ o$ b0 V. X& j
  7. *********************************************************************************************************
    & S  L6 D& M* `. z: [* p
  8. */
    : J' B( E" t5 B) Q9 \
  9. void sf_EraseSector(uint32_t _uiSectorAddr)+ e- Y. \2 J) P- u2 _
  10. {
    # Y9 f2 U3 y& s7 H2 ]1 _& r
  11.     sf_WriteEnable();                            /* 发送写使能命令 */4 [# P$ T# M5 W( N8 c) h2 J7 k* l, W
  12. : s7 T* u5 d( Q, v' q& F
  13.     /* 擦除扇区操作 */
    / u1 w  N& P3 Y  v, o2 E5 }! v8 Y2 K# S# v
  14.     sf_SetCS(0);                                /* 使能片选 */
    # L. n" e& K  B$ ^
  15.     g_spiLen = 0;* [2 V5 @# k. w4 J0 T- B
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    & E% L, T! K9 S2 m
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */5 F8 ^3 t! x+ |6 |, r+ I/ t
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */( @8 Z  m( G4 g# T2 Q
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */   
    $ \# q& D% ]. n7 N7 J/ L
  20.     bsp_spiTransfer();
      ~: l, g- a( k* A* T
  21.     sf_SetCS(1);                                    /* 禁能片选 */9 i3 ~  |9 E1 P; K, D$ C
  22. 1 g% d( [6 S- b: K- p9 g$ P( Z
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    4 n: q: y" m& P1 I; `
  24. }
    ( A3 x5 t2 g; m8 D- Z
复制代码

9 ^$ x; v/ C1 y$ A: M% j
1 U  o9 }8 j/ I整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:  z8 R& ?7 D. I2 D9 e

5 G+ `, c. B: e9 r+ @- F# c
  1. /*
    + r0 Y, j4 S3 W7 L0 f) a
  2. *********************************************************************************************************; z8 ?9 q/ \0 a
  3. *    函 数 名: sf_EraseChip9 ]0 s+ X) p: Y$ y8 W
  4. *    功能说明: 擦除整个芯片$ L" ^( ^$ @$ `2 p4 I& o
  5. *    形    参:  无
    4 ]" x( W8 C( q5 E- ~! Q
  6. *    返 回 值: 无
    " I0 O" ], c1 R' ?; L
  7. *********************************************************************************************************1 g( t* S6 W. A- P  p
  8. */5 w* B* A( y! {6 O" r
  9. void sf_EraseChip(void)3 ^8 A( ^$ V1 S/ @$ t
  10. {    ' Z# v5 `# G( ~1 V% ~
  11.     sf_WriteEnable();                                /* 发送写使能命令 *// P9 \  S- e% I9 z& L% ~% H
  12. ; I) n' T: I& m6 u: B8 m" c) \) Y
  13.     /* 擦除扇区操作 */* c6 ~: @& }9 P- Z3 a. Q
  14.     sf_SetCS(0);        /* 使能片选 */* s, }! H3 e  `4 E" w
  15.     g_spiLen = 0;* s; n& S' M* W6 b
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */4 ]# Q' |3 L  {( P. K
  17.     bsp_spiTransfer();; ?" B2 i) q# f' K9 [6 M9 e
  18.     sf_SetCS(1);                        /* 禁能片选 */
    " g% r1 f- ~0 _1 E) v

  19. ; K/ e# G9 H1 P
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    5 h; D" B/ u8 I5 P. N
  21. }
    0 v- x% H* ~$ Z8 H* x7 I( g
复制代码

( S$ i% l" c  o8 P* G1 j& d9 h( l73.4.7 第7步:W25QXX的编程实现
" x$ S% k$ ~# a9 W" E* rW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
) |) T: L1 P/ i3 Q7 k5 j
6 ?7 O; f, n, ?, {  a
  1. /*0 W* a: S/ m) x- R
  2. *********************************************************************************************************  U6 e- V) D; f2 v4 a9 f
  3. *    函 数 名: sf_WriteBuffer
    ; N  v, g+ A' b. G
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
    ) N; f' p9 J* X- [% l
  5. *    形    参:  _pBuf : 数据源缓冲区;) _# b5 E! g  c* S8 o
  6. *               _uiWrAddr :目标区域首地址
    9 D3 u9 t5 `2 d  D' T
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。
      V6 U7 T  H! m/ o6 X
  8. *    返 回 值: 1 : 成功, 0 : 失败
    ) B% V) N+ b0 P  S
  9. *********************************************************************************************************% c$ f2 o& E7 }! S4 f% T2 a
  10. */5 }  K: Z' j) T2 S
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize): l- Q( o0 Q' S8 K' N
  12. {( e0 K  B* g8 u( @. r' L( i
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;* z8 W. L! }/ c$ i$ ]

  14. / ~1 j. U1 r3 D" O* g% V9 Y
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;$ z9 w" A" [1 O+ e
  16.     count = g_tSF.SectorSize - Addr;
    3 G3 }/ {4 n4 @
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    8 @$ c+ r  D! `
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    0 \2 l; D) E8 h+ ~
  19. 1 K; w+ g! d4 O% L9 r. c- m# w
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */
    / {' O* d/ @, [0 ~
  21.     {* I" u, U- \, W4 t1 Z1 {
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */7 e! K! x% w, |! |* ^
  23.         {; {+ ^$ q5 D  Z
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    ! a7 Z9 r/ q. Z- F' Z# T
  25.             {
    $ R1 p. ]- W4 E* t
  26.                 return 0;
    - K" A/ }. B" _4 Q3 @( ^% U6 }
  27.             }+ a, H: \, d4 q! x
  28.         }
    8 {3 E" ^. z7 ~1 M  K, U
  29.         else     /* 数据长度大于等于扇区大小 */
    & l4 b7 i# c8 o& F
  30.         {# r; ^0 p/ ?; f* H
  31.             while (NumOfPage--)
    , l& w0 K7 g9 Y, ~
  32.             {: g, H$ A9 p6 S; N
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0): q5 U$ r5 B. i
  34.                 {
    4 a9 n2 g$ J8 G+ U/ R  {4 K, ^
  35.                     return 0;; ^7 n4 o4 S8 {1 l  M
  36.                 }1 P5 X/ F. N4 c! G& M% a* n
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;
    3 [  e( z- p; V" M9 o; D! m
  38.                 _pBuf += g_tSF.SectorSize;
    / w) M! G& K% X) j% U7 k
  39.             }- b+ a' w, q; f/ k
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0); e. n" K' u  a6 h
  41.             {
    , w; x  |* Y# c
  42.                 return 0;$ C6 L& a2 B9 p  e3 `
  43.             }
    7 O- ?$ p2 L4 V; t9 ^) |
  44.         }( c8 i) t4 D' Q# V
  45.     }
    3 c& [0 \) z7 |7 C) A8 e5 T" R: Z% k/ ~
  46.     else  /* 起始地址不是扇区首地址  */- [7 U; U$ b9 }; ?( j- Z
  47.     {7 {* L  Y2 o6 ?
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    . {: e7 u+ d4 L! b8 ]5 S; x3 y) y
  49.         {  F/ c/ w/ N: t" P; D/ g- i
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */# O8 S/ u. i5 |1 A7 K9 ^
  51.             {% O# V) i% n9 j: }8 l" q" w+ m
  52.                 temp = NumOfSingle - count;. [4 b4 @( _2 k  B# t' G

  53. ; A6 I8 d0 D* ~# d3 p
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)$ H0 i* h1 Y! _) h* k$ G
  55.                 {
    & a+ B) l, F# W
  56.                     return 0;
    6 V  _, Y; Z$ p# H
  57.                 }  Q; d4 j2 Q# Y# ]2 k
  58. * }+ S# j5 C9 ~/ I
  59.                 _uiWriteAddr +=  count;( T2 ~+ N, P0 ^& K  h2 D
  60.                 _pBuf += count;
    & B* {/ }# a& Y. Q
  61. 3 l$ ], f  K" A6 K# X1 ]
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)  Y/ z1 g' V3 a
  63.                 {6 u+ r+ K: f7 }8 l5 |( f+ ?
  64.                     return 0;
    3 R* O' ~; B5 U
  65.                 }
    * N$ K2 @1 ]7 w% A, T
  66.             }; ]' R6 T( e) @, Z( x2 b, C) P0 k
  67.             else
    4 H4 b! G, r  V8 O$ R) M+ \; ^
  68.             {6 U  a2 L9 _, X
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)9 q" M) T# S$ M3 m2 z) L8 M
  70.                 {
    5 `9 `6 }# K5 n9 ^
  71.                     return 0;! N% ]( Q# L0 r# e
  72.                 }
    & U) V% j0 Z. E- n7 D6 c
  73.             }7 ]8 D4 K. C, Y4 t
  74.         }
    ( z5 [& z4 p+ I4 z
  75.         else    /* 数据长度大于等于扇区大小 */
    * r6 n, _- O  O- i. H1 Q
  76.         {
    1 D5 J) s& f% z2 s, N( L, w, @
  77.             _usWriteSize -= count;3 s, {1 U9 {3 S( |2 s
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    : T% l6 }! b% v/ V. i
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;$ P# w9 P" ?3 D, U+ D) g! s
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)) z* P8 X2 h5 h$ @
  81.             {( [! G7 w$ ]6 `* n5 p" K
  82.                 return 0;" w  i5 A1 j9 c
  83.             }
    9 U' ~7 O. \; y4 I7 g1 p
  84. : c6 D3 L& F5 y( x6 ^$ W7 g
  85.             _uiWriteAddr +=  count;# r) x1 ~" W0 |. J
  86.             _pBuf += count;
    2 O' O" e) E1 x

  87. ; Y$ X+ X0 F- S; S4 I
  88.             while (NumOfPage--)
    & B# g- ~6 X: [: g; N# j: w) w
  89.             {: X1 y+ h0 J5 V7 _- v
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    0 V3 I5 d/ h5 s( |0 M" G$ a
  91.                 {
    6 ^0 y: R) T. K9 Q/ U
  92.                     return 0;# z$ ~7 Z! J) H8 B
  93.                 }, s0 ?+ m, g# N+ o9 L) {0 P1 k* v
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;
    7 Q" X4 ]8 d( v6 \3 b! d0 }
  95.                 _pBuf += g_tSF.SectorSize;
    + o/ n7 R) g; @( b0 S8 M
  96.             }
    / ?4 S, m: `3 u+ j4 a# D

  97. 8 k/ B; ~. h+ T& x/ |2 r3 T
  98.             if (NumOfSingle != 0)# T" u! ]/ T/ X! p$ `
  99.             {
    7 o$ R& s( Q: }% I! `* l
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)0 G2 ]5 g- V& I
  101.                 {  A! [' I/ j# x$ D
  102.                     return 0;2 X5 z  g0 |( W9 V
  103.                 }4 L# \% n+ Z' g- L, O; k
  104.             }$ u/ v% B2 [0 L% Q. x) h+ ~
  105.         }
    % B8 H3 ]9 R& D6 Q% A- ~
  106.     }4 u. j8 e7 y' e* \
  107.     return 1;    /* 成功 */
    % E: p! W; Z0 Z5 g  q) E
  108. }
复制代码
) L( T  W/ t+ n+ E3 t
73.5 SPI总线板级支持包(bsp_spi_bus.c), z  j& i4 z/ j: h
SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:  i+ f" G0 A( V3 ]( G/ d) I
! w+ a- [* [% n% u9 Y
  bsp_InitSPIBus
. [+ L6 b3 y/ ]" P& T: Z, ]  bsp_InitSPIParam( b8 E" s- {5 N, T- |
  bsp_spiTransfer
6 q2 L3 q- T6 j: ^2 X; M73.5.1 函数bsp_InitSPIBus) ]: I1 A* d+ I9 @8 D9 u
函数原型:
6 r  X% _7 P3 V/ }" h5 e; n& d  W9 |* ?
void bsp_InitSPIBus(void)
6 z+ f# [% C8 ^. R* ^: {  A4 R& b: w+ c" @2 u$ c7 h- {; s
函数描述:
& X. Y) S! X% o/ a
, h$ z9 z* r% u1 j- N+ {: w  I此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。( A8 @& n1 G6 \9 S5 p" T( s
- S$ Y/ L  I8 e! i
73.5.2 函数bsp_InitSPIParam4 N+ ]4 h: |$ _1 |
函数原型:' S: l5 [" u' A. }

7 S: a" I7 I3 v; n% ?8 U! k9 ?void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)+ L0 z" }+ d7 f1 ?# a: g
( V' y) ^1 o. l- A# o/ G, k5 ?
函数描述:
2 F6 {( e+ n+ U2 r5 x/ K0 L9 D0 H) y9 A
此函数用于SPI总线的配置。: Q' K* N$ _* R. X5 |, K7 G

( F* q8 r" f; f3 Z+ {函数参数:) h2 M, \* q& N3 r+ ~
, n( B$ K+ |1 M& l
  第1个参数SPI总线的分频设置,支持的参数如下:
! m0 }6 X; V3 u) O4 NSPI_BAUDRATEPRESCALER_2    2分频0 C3 Q" }7 ~- H0 x
' _- c9 w# c' k$ X
SPI_BAUDRATEPRESCALER_4    4分频8 G2 e* f" t( q6 X' r- O

" V/ G% u8 ?* @5 GSPI_BAUDRATEPRESCALER_8    8分频
  p+ k( J  m( z6 u) A1 |8 l8 E/ V/ h( q7 m% j$ @
SPI_BAUDRATEPRESCALER_16   16分频! i5 D- z5 Q5 Z$ ~7 w. J5 p

5 B6 [: Q4 F; k$ L' |: _6 ySPI_BAUDRATEPRESCALER_32   32分频
3 w' p4 @& f& R; \3 K2 H3 Y' i; Y6 o6 q2 Y
SPI_BAUDRATEPRESCALER_64   64分频
7 e+ r* }% R) C0 E) ]8 e/ h* v; U. a+ M  C+ ^) w4 h
SPI_BAUDRATEPRESCALER_128  128分频
/ x, Q3 c, r8 {$ S, O
( X/ o8 _: W" S! R. V8 ^6 {SPI_BAUDRATEPRESCALER_256  256分频9 ^/ \1 w$ k9 ?2 h
: P0 X$ H, |9 a
  第2个参数用于时钟相位配置,支持的参数如下:' l' }7 Z9 u9 ]& n: ?) @
SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
& x  X" b' J( M- i
+ L% @3 T) |" V) D4 [  |7 l' d* }$ jSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
+ J/ y# z5 A, `. y% d8 O( j% Z8 C1 \
  第3个参数是时钟极性配置,支持的参数如下:3 r$ A3 D, u5 P8 \
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平3 G6 N, p! W0 b+ j, F2 `

* V% E' ?* g% y  qSPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
) Q: z' q+ f4 E" O
( T7 z$ H% a, c" b73.5.3 函数bsp_spiTransfer
. H! F4 T' B; i8 Z
函数原型:
$ F+ U& u) ?% J$ N; R
; f* A* e0 S; P, kvoid bsp_spiTransfer(void)
. {& A2 k3 |0 o! e3 G( E0 ?$ Z
7 ~; s* x8 `& T- _函数描述:
; r# g, o' o# B5 Q- D  @3 T4 E/ g7 P& ~! @/ e( A7 A9 K) H
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
# Q9 G$ E4 `6 x5 U8 `4 P* T9 i; X/ Y% k0 S2 x
73.6 W25QXX板级支持包(bsp_spi_flash.c)# Z2 Q6 L+ m) m0 H
W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:
. O7 D: i0 @. e$ U( ~9 [
+ Z- Y3 x8 z0 x  sf_ReadBuffer" E0 ^9 y. l# q( P9 J
  sf_WriteBuffer# X/ U' m+ K- I. T4 C
  sf_EraseSector
7 V% E4 s0 L6 b# S9 S/ q. h  sf_EraseChip
2 u' x5 f3 V8 C/ l+ o8 y  sf_EraseSector* k9 T1 L7 Z' D. L. W; @
73.6.1 函数sf_ReadBuffer3 u, _( r+ ]# u+ T
函数原型:
9 {6 p- h9 f0 a: l" S
' s% X1 V7 e" F$ s% F" |; Nvoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
1 f" |0 B6 _) y# n- L
3 u+ F& D- `- I( x函数描述:
8 f, m% M# E( b
0 \2 V6 ]' t& X0 L0 z此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。
0 u2 R7 I6 e! s3 ]$ J
% y8 \7 ?8 i. f$ u- Z; {9 `函数参数:4 ?$ {# l( D. E
' U' h- ]% ^; L. z
  第1个参数用于存储从SPI Flash读取的数据。
$ o( _2 W  Q1 H" ?1 h3 B1 }* Z  第2个参数是读取地址,不可以超过芯片容量。( _! }: |& k5 }3 g
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。& q- c. ~7 a4 J3 W4 H
73.6.2 函数sf_WriteBuffer(自动执行擦除)
7 ?8 M4 M2 N/ Y7 F8 {' U: y" k函数原型:
: B' Q4 O2 z" v3 T! F6 |9 v# _2 j. \6 C/ M! b
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
  [# n0 K2 v+ Z1 d7 R, c
5 A% G- }5 a  h, B* ~+ z9 f) f- Q函数描述:
; K' D' B6 I- g7 b
: `  `; K2 k# |7 c1 v2 t此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。  @" x( ~3 B$ g% u0 Q/ f

1 z9 Z* l7 e& f! v: _+ j( q& {函数参数:
$ ^) t1 m2 b; Z' c5 }8 `* e6 y. t/ F- `1 j) \; Z! Y5 k. S
  第1个参数是源数据缓冲区。
. r1 X4 p2 Y" a+ u+ Z! s  第2个参数是目标区域首地址。2 i$ b2 x! W. m6 m3 f$ g
  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。* E* E% i( I1 z; I! U3 H
  返回值,返回1表示成功,返回0表示失败。
/ ?! m. N: Z$ Y73.6.3 函数sf_EraseSector
7 h3 m4 g* N& J, p0 m函数原型:
2 E. S( Y* Z0 N2 t6 ]# n/ ^9 z3 P' U, D$ N. _+ R
void sf_EraseSector(uint32_t _uiSectorAddr)- i0 R' V' T1 M: }, w$ p

9 i1 p7 i# A5 R+ B1 \6 p7 k+ i函数描述:
4 c) D7 [& z' \9 l6 W1 u9 z' e( p% N- @! N2 a0 G2 ^5 z
此函数主要用于扇区擦除,一个扇区大小是4KB。
& c( M& N5 g* J" m) {. B1 y0 P- M- @0 y2 g6 i6 M: j$ D
函数参数:
+ a# C" P1 ^4 P% J2 e6 `
  v) S! h/ K1 h3 G2 b: ~  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
* z2 \$ t& _( T: k4 q5 m: B73.6.4 函数sf_EraseChip* m( C+ K5 {4 K
函数原型:& h9 y3 Y- y2 u6 y% R" r9 s
9 u( E7 t- m  z: T
void sf_EraseChip(void)
  c8 R6 t2 r6 H: g7 Y# S: y. D0 R4 M
函数描述:0 h6 ^+ l0 [$ _0 g
5 I" S9 V9 p( E  n- a& M
此函数主要用于整个芯片擦除。! y" F# ^/ k6 C/ w8 `! ^5 g
/ b& M* p1 X6 {9 _
73.6.5 函数sf_PageWrite(不推荐)
; m4 V8 h6 ^8 C函数原型:
4 F! o! ^  P7 x8 N8 u: U+ t9 C& |% D8 S. j
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)
$ J* V# Z& A8 D9 q. p: ^* ^0 u2 d2 N9 \; B" W4 N
函数描述:3 w7 D; h0 u9 P

8 @' P! p. d# g% n" a/ A此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。( c+ L% S5 i0 Q% i# I0 E* `

" j# s3 E3 c0 J1 \函数参数:
& j  n; ]0 Q; ?7 H
+ `& m5 K! f+ i: w  第1个参数是数据源缓冲区。
& n, m2 C. Y! q! O4 L: @  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
0 b7 ?  i$ B2 E& r2 x  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。
, D9 K7 [. A- e) g7 e5 Z6 |' {73.7 W25QXX驱动移植和使用
) I/ h3 u; s) I4 EW25QXX移植步骤如下:: W; c  s# B' M+ `! x

4 h' y; E' D. z) J+ ?  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。
6 ?$ d: _* z  e* H: k  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
9 t( U5 I" U- _: M4 r2 m
  1. /*% c+ w3 A: Y7 v. c! h" V+ H
  2. *********************************************************************************************************
    ) _: D- U" _7 I4 t
  3. *                                时钟,引脚,DMA,中断等宏定义* {# q# h* A6 R4 o0 |; G+ c$ t( ^
  4. *********************************************************************************************************
    : @) j  e+ c# L& l# s7 ^
  5. */
    . {# L' j  Q8 Y* e$ ^1 t0 Y
  6. #define SPIx                            SPI1' @8 {" H/ D' J5 k. v4 Z% ]
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()( G- q4 \' v; y8 t1 y1 s8 S
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()
    ) y+ Z1 v4 H! H& W; n
  9. , r9 v/ s# R+ Y0 b/ e
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET(): Z, e* _( h; V$ |
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    8 m1 f+ M; ~  [

  12. 3 l# K$ O# F& P/ l7 b* F
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    9 W, c* q. N4 r1 N
  14. #define SPIx_SCK_GPIO                    GPIOB
    3 S+ Y5 ], d. F+ u- X5 v9 b
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3/ p% H8 N! H. N
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1/ o1 Z8 w+ s! ^* C1 H- `
  17. + m) w0 A/ m/ f; F
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()5 G& v; d! r& t
  19. #define SPIx_MISO_GPIO                    GPIOB6 p* s! B9 R1 S/ d! A4 x
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    ! n4 B+ p$ ~# C& r
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI14 I( Y  d# j; v8 u0 o
  22. ( E* g6 q8 o6 e5 S0 U" y& j/ z
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    ) h4 d( T! V0 P+ F% y1 h
  24. #define SPIx_MOSI_GPIO                    GPIOB  Y! I% b; F9 ]+ o5 v. {
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5& v- n. [3 X$ L0 X
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1# k! v) G, I) z
  27. # J* q) d6 F) D
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    1 j3 o; j' r4 S2 D9 \
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    - s. l- V2 q) {

  30. " r$ K$ G' E. X! ^
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
    8 a, D" D7 S% j, o: P
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX7 O# v( {, G- H: T4 c" ~
  33. + F$ K$ Y  z- X# E, `
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn7 n+ B3 A6 a9 c
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn  q1 V: ]' Y; S

  36. + N6 ?) H/ J* c4 v! ]2 l# I7 b/ f# j# g
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    6 x) J4 K; ~2 t) A( ]$ K
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler8 {+ O& ?5 N" n1 o$ ]$ ~: D% F$ ]
  39. 9 R* g1 V# x( S: `0 Q
  40. #define SPIx_IRQn                        SPI1_IRQn
    . e7 s0 W) r1 N# l" v
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler
    7 {+ A3 O. u1 u2 t+ k9 k3 D0 m
复制代码
: D4 B& |4 Y4 [

+ T8 k. d& u0 ]& e) y  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
. f+ O# e' K8 X9 T- M
  1. /* 定义串行Flash ID */
    9 B" v7 |, \; r5 d+ P. D
  2. enum0 Q, F" d2 _- o0 Y: [1 e; A9 L2 m
  3. {
    3 U6 l! c9 g! t8 R: }
  4.     SST25VF016B_ID = 0xBF2541,5 E8 S6 r* I. G! S
  5.     MX25L1606E_ID  = 0xC22015,' A# n/ _3 v3 }# f0 B/ h
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */" P* j; s7 C7 o+ A' e7 x- W* t) c
  7.     W25Q128_ID     = 0xEF4018
    9 \) l: \  z: b2 I, P& ~
  8. };
复制代码

# W( u! s( G) e  |; h  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。/ Z0 R; a9 f3 Z& C# b* q+ ?
  1. /*( Q3 j1 C) z4 c. N, p1 d& |$ }/ U' S
  2. *********************************************************************************************************
    9 L0 m1 T1 T# {; p
  3. *    函 数 名: sf_ReadInfo4 {+ b: L/ G( h  ?; j
  4. *    功能说明: 读取器件ID,并填充器件参数1 R2 _* S$ r1 G8 {) Q' H8 [8 ]8 E
  5. *    形    参: 无! ~8 d% I# E" Q5 z$ t' p0 C
  6. *    返 回 值: 无& y) F+ w8 E5 z" S$ ?
  7. *********************************************************************************************************
    . S& x& i$ R. @8 V1 q
  8. */, |/ I) I& ]% w; p. e9 u9 b( j) C
  9. void sf_ReadInfo(void)+ p" [6 \7 p% \: d# e" f
  10. {
    . u5 r5 k$ p6 ~  Q( ]& l
  11.     /* 自动识别串行Flash型号 */& H( Q2 ^+ _; _5 d( u
  12.     {: X; \: O0 J2 j
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
    & q+ H5 I: o) X; B9 q6 B

  14. $ e/ G, t1 G( C3 w4 ^2 f
  15.         switch (g_tSF.ChipID)6 A# ]( j; D/ f8 B, r" b( v
  16.         {
    , F" V' r1 x& G; {
  17.             case SST25VF016B_ID:
    & M) M' d0 O; q, F1 e6 \" A  L" [
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");
    0 y6 }% Z% L% m0 R' W7 y
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    ) |2 \" e8 _/ \$ A- E: t4 q2 v2 @! p
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    " N0 ?" U/ E' l+ J4 I% s
  21.                 break;
    / N% @8 z5 s  j# S! Y4 i

  22.   h4 p% T. l7 \& ]5 k
  23.             case MX25L1606E_ID:
    . n+ D; m' B% j* @9 p' v
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");
    " T; K& J6 a  Q9 q4 Y( h; h0 ~
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */, ]3 }5 E/ o& k) N- V3 [+ D
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    0 r+ s% i1 n5 p* S& }
  27.                 break;
    & n# s$ H9 b, W1 j* |# B" U' c9 Y
  28. 1 B/ a$ u, i$ o  {# _
  29.             case W25Q64BV_ID:
    : z- g# C' ^; ~5 Y1 ~3 X# r
  30.                 strcpy(g_tSF.ChipName, "W25Q64");
    # R3 P; Y6 o- _
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    , u8 r* G! ^' {) I5 h1 z
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    - A! L) L+ m8 L6 d9 v- e
  33.                 break;2 x. w& \+ N0 }) j3 l
  34. : J7 K  T$ ^$ B( G3 D: c
  35.             case W25Q128_ID:
    1 k# V  g* X) ~. S
  36.                 strcpy(g_tSF.ChipName, "W25Q128");& X5 Q) [; C+ }) x
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */  {: V6 G3 X9 |2 f+ L
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    3 O( \& v" `' b
  39.                 break;            
    6 {2 {% I" O4 A( @% u/ N) x; K
  40. 3 R( u9 w% d9 c5 h6 J- h
  41.             default:
    3 ?3 y7 ?0 O% Y$ p) u% P7 ]& @. P; X
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");: O: N: Z, @2 ], \2 W" F4 b
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;6 N5 J' m/ X; T3 K
  44.                 g_tSF.SectorSize = 4 * 1024;7 @  f( E  t5 c
  45.                 break;
    8 ~+ Q( [2 s; \
  46.         }
    ; I/ p6 \3 Y, u' w4 p1 u5 {4 w
  47.     }* z+ C1 u5 Y/ m' C, h( A: h5 [
  48. }
复制代码

1 w- U( Q* g  N$ R& U4 \  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。5 n; M1 k7 t6 |0 Y6 R6 R
  1. /*! `  T, O& Q* a7 i
  2. *********************************************************************************************************
    3 W5 d2 v# x2 d( W3 k, ~3 I* W
  3. *    函 数 名: sf_SetCS: ~) a1 g. {8 T/ Z! M8 {
  4. *    功能说明: 串行FALSH片选控制函数
    " B" A6 k8 t/ ~: N" V% |
  5. *    形    参: 无
    % q$ y4 T# q# i+ A
  6. *    返 回 值: 无$ b( l1 o: l4 L$ x$ S' t
  7. *********************************************************************************************************8 q$ |4 n1 n; N' B0 K0 R8 L& j
  8. */4 b# r+ j: F1 N/ }2 Y) a
  9. void sf_SetCS(uint8_t _Level). p+ r+ C, {0 z% p) V! P8 Z
  10. {3 c5 C* `  A, k! f$ t
  11.     if (_Level == 0)
    4 I+ O4 A$ B8 s% c! I
  12.     {1 D5 u' M; g6 e( c! q
  13.         bsp_SpiBusEnter();    ! g" ?. W4 Z+ O9 q# w0 }/ `. ?
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);          o4 D7 O/ q& J" @) y9 ?( x. f& y
  15.         SF_CS_0();1 ~. k/ `- _% g
  16.     }8 ^, J5 R1 r& A, V6 ^! h6 C# D8 o9 K
  17.     else
    : }$ F; @2 Q- x3 z- N  J
  18.     {        
    ; p% T$ t$ r1 P3 S" q
  19.         SF_CS_1();   
    0 L% V4 x' U) V2 o) `
  20.         bsp_SpiBusExit();        
    4 a" J. T+ }) ]! i9 d( |
  21.     }& x9 U+ m1 K. Q, X. ?
  22. }
复制代码
4 K- @; q& |0 g/ l: t$ d& c
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。& w* Q- X8 a, F
  1. /* 串行Flash的片选GPIO端口, PD13  */
    & ~: w# p* `- U+ C$ h
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    1 W1 @6 N/ e7 ^" K4 `8 o
  3. #define SF_CS_GPIO                    GPIOD- \0 }4 Y+ m" K
  4. #define SF_CS_PIN                    GPIO_PIN_13
    7 c4 S' }0 S1 O) G! Z# e  k0 e4 C
  5. ! J9 J- F7 a2 }2 w, E
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) 5 S0 w: ?/ z" z3 H; Z& L& i, e( k# x
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码

. E& {3 X8 K" R0 `. ?8 q7 B  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
0 k: i/ P8 Q$ e7 M7 w9 U
  1. /* 配置SRAM4的MPU属性为Non-cacheable */4 t. G  g% O1 G2 P  o
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;1 j, t" I2 ^2 H# H
  3. MPU_InitStruct.BaseAddress      = 0x38000000;, u7 A# U9 C8 Z
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    & d6 T9 O9 m$ l/ `, _+ [
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;1 l( q7 n) l% e' D
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;4 Q- P% v4 W% B- g& {
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    % V$ }8 _$ G& h, [; Z1 f
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;& z$ ?9 @5 t) K7 ?. H" x' F
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;9 q5 T& k7 X6 j8 ^' H5 g
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    & x  s5 t+ q; H
  11. MPU_InitStruct.SubRegionDisable = 0x00;; N' }: B5 ~: h5 r2 y" V
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;. I1 c( v& e: W' H& Y+ k+ O

  13. - ]# c2 a- e5 a' ~6 p* ?
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
9 _! j" ]# P9 r
  第8步:初始化SPI。
; ^  x: l7 P5 u' e5 f8 L0 m
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    ! H0 ~7 C' |0 c- Z
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    1 ?, Q. C. T2 w$ B% {
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码
, L6 Y, ~  u9 b, m; ~  y5 x
  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
! e% h+ p1 R& j- y' r) g6 z; p  第10步:应用方法看本章节配套例子即可。
' C" u3 O7 V: C% X% t8 K. F' i! N% h73.8 实验例程设计框架
6 a# q1 ^5 c0 q通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
9 |* T% }5 H1 W
! h* S8 D6 e! P6 q
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 ?/ Z3 N; Q8 u+ n& A8 W4 u  O. x/ E% x. b( \% S" c
  第1阶段,上电启动阶段:$ J% {: L0 h% n
- ]$ D. Q/ y! a0 B6 k* q
这部分在第14章进行了详细说明。
) C; w. G$ F) F6 M7 L  3 x6 K1 \9 @4 \, s$ p* `
第2阶段,进入main函数:' [, k* U3 B# k5 j- b* e6 r

# m6 A( H) B0 e& B, Z第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
. \) D( O; T7 C1 V/ m+ Y" N第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
& U2 F9 i( s2 Q% }4 G' D- k) u$ C0 _4 Q
73.9 实验例程说明(MDK)
7 {  V6 x; A' k- O+ c2 f配套例子:5 S$ k& G, g& P% z" h
9 Y$ L2 M% z3 E# J
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
0 m2 ^' l4 k+ \2 ~8 w' Q% _
& a  z2 t( Z; T! NV7-050_串行SPI Flash W25QXX读写例程(DMA方式)
9 J( R9 i8 t8 l1 s# O
+ L) V  d9 M' L8 I- ?; R3 Q0 EV7-051_串行SPI Flash W25QXX读写例程(中断方式)
. R! T: b/ b% O& {; m& b7 D1 ^) A& s1 Y" @5 a
实验目的:& ^# B1 Y5 Q* b7 w2 m# f$ Z* p

! G0 T8 F8 C! q8 E% v7 ~4 x学习SPI Flash的读写实现,支持查询,中断和DMA方式。
. k; {- {( @" R9 I
2 o/ S" K; e5 P7 R  y2 @5 P实验操作:
. H& U% B* T0 w2 |- g; R; S+ @
+ E; d( t* L) R7 p支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可% z' ^+ I, i/ O* q. Z5 O+ k
printf("请选择操作命令:\r\n");
  P8 W8 m) r. j# h3 F: i) y$ hprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
( S4 O; ^  A: P7 Z; iprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);% s" j9 r6 d# @, ~) ~" Q
printf("【3 - 擦除整个串行Flash】\r\n");
6 T" H- O! b; |; nprintf("【4 - 写整个串行Flash, 全0x55】\r\n");
, Z  T& W! c7 q2 _% B$ Qprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");
( ]: x# X  W# [1 ?+ F. }0 N: e2 l4 R% [printf("【Z - 读取前1K,地址自动减少】\r\n");
) q/ _7 n1 d+ d( n7 Iprintf("【X - 读取后1K,地址自动增加】\r\n");
5 m1 k; R* X- }" y4 D$ h4 o$ G: Hprintf("其他任意键 - 显示命令提示\r\n");2 z. N5 u3 Y) z+ J$ ?- Y, Z3 _* W
上电后串口打印的信息:& K: x: R9 ?# K
0 Q8 j% ]- u+ ~2 n: t; B
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
+ M% k3 K4 F0 b% z3 z) @( m: D) W" Q# B. E* X& g
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 }  x: k  ]3 p; A1 h6 `3 T* ?  d  E4 G
程序设计:; [- n$ y3 g4 {6 M2 q; h

& j9 n8 m. y5 D  系统栈大小分配:* @! [, Y! y2 k7 \: X5 u' R% _% x

6 l1 g/ e+ c8 B& l9 R
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
; B! l3 @1 U, J$ H* }" f  x: p+ Q
# Q: i& ?0 P+ }. ?4 u
  RAM空间用的DTCM:& Q; P9 U- n9 u. _  Z3 ~

6 s' h- X* Y& o
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
; _# q% J# l! U$ C) ^2 D

* B: f! k9 W' {- b5 L# {! c* ?  硬件外设初始化
- P+ S+ d) a$ |) N' {+ U  t4 f  u4 j* A
硬件外设的初始化是在 bsp.c 文件实现:* k2 z- i( e  Z
( @" q7 T, z/ X
  1. /*: `0 L* v) l  v6 N* P: s
  2. *********************************************************************************************************
    $ T5 I: O  I9 w5 ^5 x2 N
  3. *    函 数 名: bsp_Init% r; |. T% }" \# ^. z
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次# I  e( p5 ]* W2 {/ m* m
  5. *    形    参:无
    ' O. O1 v% q5 h7 v
  6. *    返 回 值: 无
    9 ]( J" b% j- ?8 o
  7. *********************************************************************************************************# h. V3 U( W& |3 E1 @
  8. */
    * W" ~! p1 _$ V6 Y1 p2 {$ v7 q
  9. void bsp_Init(void)
    " g$ x; M. B- D6 p5 v2 p, `
  10. {
    7 z5 O/ m& K: X9 T6 N# X
  11.     /* 配置MPU */
    . r' a. w) p9 C  m
  12.     MPU_Config();, m' u( J+ C5 j) E" M
  13. 9 h9 @+ M+ ?& j% J( }+ T
  14.     /* 使能L1 Cache */
    9 d6 ~$ C0 a  R* D; l  z
  15.     CPU_CACHE_Enable();
    ; ~. Z6 z* B" t& U/ e& G' U  W

  16. & t8 o% _2 N) F8 I9 H% k) t, \
  17.     /* # v! ~) i( K7 ~* T
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:% B8 x- H% E9 {
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    5 ~' t; U2 s6 @5 |
  20.        - 设置NVIV优先级分组为4。
    6 D( H  G2 Z9 p1 d
  21.      */7 w0 C, P7 c# A3 H& P/ Z9 O9 e
  22.     HAL_Init();
    * ]8 y* z* Y$ @0 c* P" H& O

  23. 3 u# L; `1 f$ g6 e! n
  24.     /*
    ! {) I* |' @$ K1 x: i  i; e
  25.        配置系统时钟到400MHz
      K0 Y( u6 H* ~. E
  26.        - 切换使用HSE。, \2 `* Q5 r6 o6 W5 n6 p$ G9 F
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。1 P( ~: U2 N1 \+ _4 r2 b( w" r1 t
  28.     */
    . a# ^/ z% W. V2 L
  29.     SystemClock_Config();' O" }; h1 K: w4 c
  30. ! t. L( d* L, n, _
  31.     /* : P  ]" p8 `* Y7 ]% @
  32.        Event Recorder:; y" O, k- V6 B4 t
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。* [8 J2 r; j$ w! d# K* I6 A/ O
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章. u2 k5 {( c- j
  35.     */    * x  z; j% _, i
  36. #if Enable_EventRecorder == 1  ! h3 X9 ~1 |3 y" u3 z; Y1 \! n+ q
  37.     /* 初始化EventRecorder并开启 */1 D. S6 U& g$ R6 a: L: g
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    0 t* h8 q& Y4 z
  39.     EventRecorderStart();( V0 h! s/ F2 f. K% W6 n5 g
  40. #endif
    5 G) p4 C& w1 y4 K3 Q

  41. . h" i" Q; r0 N* `: x% {
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    / x/ P9 C  Q$ ?% e
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    7 k  z! b3 ^7 a) R6 U
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    * a# i% f' k$ m  w
  45.     bsp_InitLPUart();     /* 初始化串口 */
    . s3 s7 K8 ?0 P2 X* N  K' Z4 j$ h
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    0 Y# Q7 z1 h2 f" J4 k" v
  47.     bsp_InitLed();         /* 初始化LED */    - Z" U& F! c' y" I$ z  G
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    5 O1 h. w- @, i' _2 V  y
  49. 7 P" A- w& y- L
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    , D' s1 j& Z1 C
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    5 N5 C! U1 s4 @7 L: i! A! V
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    4 S0 P1 X( o' Z/ @$ k' b* m
  53. }
复制代码
. h9 X! r& k8 w, j9 g3 X! L
  MPU配置和Cache配置:
. a- M/ h, `5 }8 n
$ w. {) i% [6 v数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
) S7 ]2 G9 F: Y; l# V3 ]- ]' [# `* A3 ~; f" L# D  ~& _9 m  }. p
  1. /*
    6 [; v! w! ^$ \0 }, K  Z
  2. *********************************************************************************************************
    # M6 {' k% P1 I1 X- m, k
  3. *    函 数 名: MPU_Config
    / c! R8 q" b  V* B6 |* G( d
  4. *    功能说明: 配置MPU
    % `5 A0 `9 b# o, p- c" u, J, g
  5. *    形    参: 无0 S5 g% X0 Y# a4 D7 L5 w
  6. *    返 回 值: 无4 x+ |# g) {* M
  7. *********************************************************************************************************
    # Q9 _% l; u& i4 Y: `
  8. */
    - b2 Q6 X/ O" n  D. a
  9. static void MPU_Config( void )
    % w- t4 F: m- U# P& U1 ?+ x* ]
  10. {6 ^/ Y: B1 o9 Z
  11.     MPU_Region_InitTypeDef MPU_InitStruct;5 Z" Q( W6 o  z; K$ b' Q9 \

  12. * k: e6 N& \/ a0 h) z% w
  13.     /* 禁止 MPU */
    * Z6 r  V; F  L! t4 r: r4 I( O  m. j
  14.     HAL_MPU_Disable();  L: f3 }& S- R3 @1 C: @! m  G

  15. & }9 b3 e- ?5 K4 H. f1 ]
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */1 N6 D! Z! F; T) R% D
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;. x" i+ E4 R: H6 p9 b3 y
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    6 u3 \: i! x4 q
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    . \+ l, u/ c6 e( v" O; ~0 }. }3 M
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    , m6 e. E/ H( F; [9 t
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;, I1 j/ ^7 A: F7 B) g8 Y: t& I
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    * j4 D% O6 A# O% A& M
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    3 V0 c0 j' f- m* ^- n3 M4 R7 J
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;: b' f3 N+ ^7 n( J
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;8 k: K0 h$ T8 h& `
  26.     MPU_InitStruct.SubRegionDisable = 0x00;5 s: u& Y% P  V* h" Z( v" I
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;) e  B' x4 s1 R4 k

  28. 7 L, O) U) `! d  K- @+ ]( z* P7 x
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);6 q1 i4 a: L4 M. G9 P4 n2 N  j9 r

  30. 0 D" v1 G9 R4 w* y6 ]( f  x
  31. 6 I# V; {, C, j) R
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
      {% L4 ]/ f" q& K% v1 L* I; e2 {5 [
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    % k6 r6 b4 P' A$ l3 q
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    % B0 V. P" a' d1 e
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    ) o& @' e7 N1 d' o& K4 ]
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;4 l: r6 c( N/ B* I6 p% |
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;: T  B( R) u4 f
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    * K; n' Y* ?( E' u4 b* S/ E
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;9 U0 K6 m% B( r% l0 C9 s
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;/ ~& S2 _1 U6 b% a# h6 `
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;! z8 r9 Q8 {  O, ]/ U& K
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    7 [# F; |  X- @+ P' W: ]8 g' X
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    6 m6 x3 N" Q, G" j& I& x4 {! k
  44. & n$ r4 L9 Q2 O3 N5 Q: i
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);) ]# P8 g$ L8 a8 b/ }; G" g
  46. 8 l5 y# b/ u* x. F- \
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    % ~" a/ E( X7 ?4 R- Y" d
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;, b0 Y8 L% ~7 o
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    7 D5 {8 r# u1 I
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    5 m/ y0 N9 |/ `+ w/ `. V9 b" l
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;9 p# G& y+ w0 u6 w% j
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    + r. @. Y+ F' [4 J
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
      v: c5 T. f0 k) R) @; B' {
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    5 L4 y! s: a' B6 W$ {6 {) R
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    & U! [7 M# I% a8 I
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;; _: ]9 H' o9 g: C1 W+ h& |
  57.     MPU_InitStruct.SubRegionDisable = 0x00;" M! }, K. \8 R4 F- {; V
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;; l. C' O8 ^0 g7 n' G0 S8 I. v; |
  59. 7 T1 F" |- d6 U7 T# u  b+ W: s
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    1 V9 V" {; j) n/ c) d

  61. * z- }" h. g$ [6 B# W% P
  62.     /*使能 MPU */: z) H# Q% E8 x- z) T- Q. j
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);7 m3 K+ ]2 H' G3 y6 Q1 L
  64. }
    5 q, |1 e' d* `+ v: a0 v! ]4 ^
  65. 8 k& t  _8 t/ ?" _1 H
  66. /*
    . P( c( x6 T' B  `7 g
  67. *********************************************************************************************************4 {% ~0 `. z, ], a5 n
  68. *    函 数 名: CPU_CACHE_Enable
    ; a9 }0 g! x, B" A# h* j% u4 U
  69. *    功能说明: 使能L1 Cache
    3 L  h( H: Z5 \& I
  70. *    形    参: 无
    : D' D0 v/ t" B3 f
  71. *    返 回 值: 无% s7 _0 w) N0 l, ^0 @8 ^
  72. ********************************************************************************************************** Y; K9 k$ \. t$ A4 t7 H  P! Z
  73. */
    # B, s) t9 a7 E  W" X
  74. static void CPU_CACHE_Enable(void)
      }2 ]; \; U9 |8 z! ]( \
  75. {$ h, O2 j  j( `+ e) L5 l
  76.     /* 使能 I-Cache */. ?  B) \8 H6 X- ]5 H0 J
  77.     SCB_EnableICache();5 m3 ~- a+ T5 S4 z* P- m* y
  78. : R; d' }& J' W# N
  79.     /* 使能 D-Cache */
    0 t& O5 ^1 Q4 Y
  80.     SCB_EnableDCache();/ s- V; l6 u  m3 N
  81. }
复制代码

: j  e6 P5 u" f- f0 G, z  每10ms调用一次按键处理:
# |& s, p5 y6 c& \+ W1 b( f, m1 m3 m9 j% b7 q0 G2 Z' s
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
. N4 ~8 j7 E1 j/ P% ~( U9 w- f# V* W: v4 `1 Z1 g# C
  1. /*- G3 ?. G9 ^( O1 k
  2. *********************************************************************************************************
    3 H. L  T5 w; ]  b, j$ E# N
  3. *    函 数 名: bsp_RunPer10ms7 z/ ^, Y8 G6 ^' N  @8 D9 l2 V
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求* C2 c+ J4 ?' \
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    / P( n9 e$ Z8 N6 D: k* Y9 U
  6. *    形    参: 无
    2 z. R: Z, [, o! M" d' k  b5 l' E
  7. *    返 回 值: 无: c. y: J5 S7 ~
  8. *********************************************************************************************************3 [8 ?' ^1 m+ Y
  9. */
    + z. h) k: c1 ?# P' p, `) s
  10. void bsp_RunPer10ms(void)/ z" v$ \$ v  }: |
  11. {' }# _/ f- m' D7 i2 |5 I
  12.     bsp_KeyScan10ms();
    : @( L  k6 Z$ t6 ^& V1 {
  13. }* G! |4 I! [3 ~' D

  14. # z5 X6 u; \# i' }( i( z
复制代码
# O: ~+ n& G+ r# d3 W/ j% {
  主功能:
4 N4 W. H' q3 v) X
2 t. M% M/ ~; {# D4 B" I2 d主程序实现如下操作:
6 f+ m6 p8 w3 J: n
, t, a" O. p0 _3 a; u5 t( }# V" X  启动一个自动重装软件定时器,每100ms翻转一次LED2。
& J3 L$ ^' M3 }1 C& q  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
# Z2 K: ?$ C4 ^: N/ K+ ]  请选择操作命令:. J/ Y6 Z8 W8 t% _
  1 - 读串行Flash
0 T8 b8 Z9 n, j  2 - 写串行Flash3 `) q$ L$ A. O4 h6 y' M! L
  3 - 擦除整个串行Flash
" X: B  L, [; w+ {8 i) Q: ?  4 - 写整个串行Flash
, b$ D6 B8 \3 v+ ?  5 - 读整个串行Flash7 K% t* Y- }8 h/ d1 @& E* ^
  Z - 读取前1K
9 p+ E. C7 t9 G, O# }' J  X - 读取后1K
8 `/ G, P. U5 I. I5 I1 V
  1. /*
    + N! S% U* b$ ]5 v0 C
  2. *********************************************************************************************************8 S) U& w9 \# y# d/ W6 ^. J1 [% C
  3. *    函 数 名: DemoSpiFlash
    ( b* k4 j8 W1 m
  4. *    功能说明: 串行EEPROM读写例程. t3 a/ k' p4 ^: A3 D- S9 x: n
  5. *    形    参:无
    : Z1 s& w1 s/ x' r) L! u
  6. *    返 回 值: 无8 I% n4 R. V2 f  @1 i: w6 l4 s: N
  7. *********************************************************************************************************6 o9 [1 r2 c* y7 S) M
  8. */6 _3 p+ @. j* }
  9. void DemoSpiFlash(void). u& [/ @- M" z# u! j# ?4 `
  10. {
    # Q2 x( U7 u  }/ |
  11.     uint8_t cmd;7 x0 m/ ?4 v. q/ |6 Z# Q" H
  12.     uint32_t uiReadPageNo = 0;# d* V+ a' C7 {: Y+ r

  13. 2 ?+ _% r& C( r$ v
  14. 4 f9 N% ]$ p4 G0 K( \0 `' y" M
  15.     /* 检测串行Flash OK */
    " s6 o2 v- w# w
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);+ W- Y: m  h# `6 g# [  L  T! p
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    0 ^+ h' C6 @' u& ?
  18. $ l+ J0 {1 X  t: f
  19.     sfDispMenu();        /* 打印命令提示 */6 _. P6 ~* U& h) K4 M# B

  20.   \# A8 B- z4 e
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    7 _2 p! k$ q4 |2 H( b

  22. 7 P( S6 B  ~  ~4 [+ M, g$ J- d4 d
  23.     while(1)
    1 j( c' O$ d) M/ [) v% ]# B
  24.     {1 O  l  V* q6 V9 t, s. }
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    ; r$ _3 W4 h' r2 n; Z& H4 ]% v+ J
  26. ) ~* B2 H! h! T/ V
  27.         /* 判断定时器超时时间 */
    + G5 T) U# R: c5 m
  28.         if (bsp_CheckTimer(0))   
    4 q+ n) P5 Z4 j+ l
  29.         {
    # c2 k# F* W& h/ G5 o6 @
  30.             /* 每隔100ms 进来一次 */  4 @5 Z7 b5 J9 @; y. \
  31.             bsp_LedToggle(2);
    . X- a$ d; \8 a' Y/ s7 X
  32.         }0 k% [: z* m2 ^1 O# j" ~. P
  33. 1 a7 z! S& S, Q( E1 b- O$ k
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */8 b' F( C% M% f8 [' T
  35.         {! ]2 K- p3 u4 O3 K0 }8 p
  36.             switch (cmd)# F" l& N) F" M1 K, Q0 h
  37.             {$ L: _# n$ q5 h4 E6 ?
  38.                 case '1':' m+ A) K, d1 P6 Q6 z
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    3 g% I" H5 s4 }% V  J% i2 \
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    / a5 K. B, C9 A. O6 G) G  l
  41.                     break;0 J* e7 J# l5 R( q: O& a  n

  42. ; {9 o9 ?' w9 j1 _1 v
  43.                 case '2':
    7 w* J& `$ Z+ Q1 S7 a3 U
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    ' P5 s9 y4 H6 O6 s$ R
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */: X: t$ `& K3 X/ E; [
  46.                     break;/ S- R" u" d  R( z. O' F; T

  47. ; W+ j3 g6 E9 I& r6 Q( U
  48.                 case '3':
    6 I5 F% V2 R3 J6 P
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");. U3 p/ B; e9 O& \  F" b$ F
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");5 p5 Y# _1 d& y2 x0 G( W0 G
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    & [. G$ ^' b- w
  52.                     break;" Y' X9 C$ U# X; e- q

  53. , U6 p, \, P' m4 `7 j
  54.                 case '4':
    " V; L1 s5 d( n1 m9 C1 m
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
      \; D- ^+ y' J2 Q
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");- T  K! s/ y3 \0 g4 H* H
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    ( R2 U3 k2 f5 r4 T; _4 ]5 [* i! s
  58.                     break;3 B1 f- s7 z- S! S' h* x

  59. ; g/ Q* N  M3 e$ |( z; ?" D
  60.                 case '5':& e  g2 ]  x" ^
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    + h+ m! o3 m; k
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    2 D2 H, a5 m, F! B# ?9 N# Z
  63.                     break;
    - P6 m: |  ^5 P
  64. 1 u- p/ i# B5 X! \6 p2 N3 ~$ J
  65.                 case 'z':1 p- ~3 V/ {" Q4 c6 U, ]
  66.                 case 'Z': /* 读取前1K */6 P5 C" m- H4 h- G2 S$ k+ a
  67.                     if (uiReadPageNo > 0)
    4 H7 W  v; w9 a
  68.                     {
    ! N. C' c8 r. h1 q% u4 O, h
  69.                         uiReadPageNo--;
    $ A, v3 c# e& ]' A2 G
  70.                     }& A- j+ \0 t/ J- Z
  71.                     else
    % N' a; V. I/ p: V! n
  72.                     {
    - U( F2 Q1 b% X5 ?
  73.                         printf("已经是最前\r\n");
    # N- \9 }3 m" s; m$ A' D7 P
  74.                     }3 K- A6 Z8 X( \! ]
  75.                     sfViewData(uiReadPageNo * 1024);, N! {$ i  h% ]8 D) V: E  a
  76.                     break;0 D. I2 w) J2 a: t: |, [- P

  77. ! ^; b- f- S3 G7 u- k
  78.                 case 'x':
    3 Y1 K! w* N" x, I. M; ?4 U
  79.                 case 'X': /* 读取后1K */9 K+ i( }6 |/ ]4 |- @. U- ~$ @# k
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
      Z/ [" M3 E, U6 C4 i# Q% y
  81.                     {7 [8 f* M! l! @3 c
  82.                         uiReadPageNo++;( G! H% C) a" K. p; t" `
  83.                     }3 e, g/ X: D$ O1 m9 u4 t) H
  84.                     else7 f/ d0 W6 z" D- l& ]
  85.                     {5 [' \* N8 `. {1 `1 \
  86.                         printf("已经是最后\r\n");/ U" u/ Q  S, Q8 N
  87.                     }; A( q% c4 P0 M& L" D
  88.                     sfViewData(uiReadPageNo * 1024);/ @, |# U0 S% F- H7 B1 n
  89.                     break;
    4 E6 q" h7 F) P# w
  90. " e) E) A: h! F
  91.                 default:
    : O2 [/ d8 j6 R
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    8 r, t7 g# @! r6 r8 ~
  93.                     break;: v$ D6 I' n2 X- a6 q' I$ X) P

  94. 2 z- ?2 k- _1 {  |% g& i8 z  F# l+ W
  95.             }
    $ c1 e' o  o* t$ T& _
  96.         }
    ) p3 N% V( C, K) \) P( U) [. l
  97.     }3 w, M* a$ U4 w3 l
  98. }
复制代码
) ]; w# W6 ^$ u( T8 c5 T" W0 L# p
73.10          实验例程说明(IAR)

3 X5 ^7 X) _  i& X3 ^! t! u6 s% \配套例子:
( m8 e2 w* `! U7 `' X# {$ E0 s1 N9 Y5 T, B2 L
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1); N+ n( d, a; T" @! U. O9 Z

' _0 Z9 t' a% cV7-050_串行SPI Flash W25QXX读写例程(DMA方式)2 G" N9 S6 `8 ~1 g4 T. }/ J% A

/ |5 Y% V+ B- W$ zV7-051_串行SPI Flash W25QXX读写例程(中断方式). _- o7 p- ~- K
1 s: o7 u# R7 M* W
实验目的:% R1 h6 C& }' t4 r- g
+ g5 Q  v1 t1 O" S7 A/ B/ u; z
学习SPI Flash的读写实现,支持查询,中断和DMA方式。3 T  `) l- S" d: d( o
5 J. P. }* h4 E
实验操作:
! A+ L+ ]" M3 \+ c2 N! u3 e' J
  o4 Y% w3 u  W; ]4 O$ W支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
) l) W- b* r' V& Dprintf("请选择操作命令:\r\n");2 D! v; v" g5 \: h
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);9 f- L# r2 E4 |8 b# j
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
' |+ W' m9 G+ e" Vprintf("【3 - 擦除整个串行Flash】\r\n");6 D+ _6 j6 ]7 F0 F
printf("【4 - 写整个串行Flash, 全0x55】\r\n");$ s) K1 d7 F' M+ r! n: S7 j
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");! B( H0 I: e. x  r% ]
printf("【Z - 读取前1K,地址自动减少】\r\n");' G* e) z) s- H- O
printf("【X - 读取后1K,地址自动增加】\r\n");& i0 e- V0 S: \  {
printf("其他任意键 - 显示命令提示\r\n");+ S8 v2 o5 P3 y
* X9 B" C3 D4 @( H7 k; s% c9 o' `/ p
上电后串口打印的信息:
& F# l2 u- f8 h9 M: M
3 |/ l2 C" v- w' p: V$ _波特率 115200,数据位 8,奇偶校验位无,停止位 1。
; l' b, q( k- I7 P! p) R. A
$ L- U: ?9 L) W( y' t( M' i
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

7 I( H5 w4 [6 K5 p! u4 V* M6 x) _
( M' U, D5 \8 K程序设计:4 m! m2 |% F7 T7 h/ |* N& z

9 }; W1 v% |7 f6 @7 o  系统栈大小分配:
! b% e3 k' H7 N2 j- @$ E8 l! o8 j: Z( l
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

, e; ~$ l' }, m2 @6 ?
- ~2 @; U. @7 B7 W, u: ^  RAM空间用的DTCM:
/ ]. h4 {/ L9 v+ n6 x1 a- [! W6 T: j) E
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

" Z2 Q( F7 s) h7 Q9 s+ [+ a$ \4 l
% f# y, z3 F8 |! U; k  {! A$ i+ w0 t  硬件外设初始化
: y' k' N. L" C7 h, t% j& e9 H
9 T) H6 Y& X2 Y- n0 A, a4 Q+ N- q硬件外设的初始化是在 bsp.c 文件实现:
, |; l; E: v# c+ f! v
' |. G) n! N% \/ j
  1. /*
    7 L& z* q, A, ?$ H
  2. *********************************************************************************************************
    9 J1 H5 j) U* f$ A) @
  3. *    函 数 名: bsp_Init
    3 ?' w! ]+ x( O( i
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次) o! N9 J  C! d& Y5 R0 L$ g  a
  5. *    形    参:无4 I* X/ y2 X9 j
  6. *    返 回 值: 无
    + ^( v$ z1 d. G" q& l
  7. *********************************************************************************************************
    # t( k5 s( e. y
  8. */0 X7 V4 O: E9 U" f; W9 }
  9. void bsp_Init(void)6 s5 X/ [7 {8 F( f8 P
  10. {
    * x; s2 }7 m. v" @
  11.     /* 配置MPU */
    8 i4 C: s2 U5 b
  12.     MPU_Config();
    0 W9 u, T' v5 G, d. L3 S# Q. @

  13. ' C6 u' f8 ]% p7 N
  14.     /* 使能L1 Cache */' F! l1 V  i# g4 N1 w7 F
  15.     CPU_CACHE_Enable();, T  J4 I- ?7 K$ L+ f  ^, _  w. h

  16. $ w( [9 A1 ?9 L
  17.     /*
    2 i  V% P5 b' H6 I) y- B
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:0 o! u; @. A! n) t6 C/ P
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。" V9 o% P' w5 w+ N# \3 @" W
  20.        - 设置NVIV优先级分组为4。
    $ x8 Z5 Q7 z; h
  21.      */- s9 G! ^) R- }  I  R
  22.     HAL_Init();7 a  M. H  W: W8 T

  23. * K$ `# }: k0 _0 P
  24.     /* % Z: j2 v6 J4 N
  25.        配置系统时钟到400MHz
    : v3 D" X! Z1 M8 G0 o5 ^8 N2 S
  26.        - 切换使用HSE。
    7 ~. P, y4 @4 b% I5 Q; x; W
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。3 Q9 M. @: Z, m
  28.     */
    + l0 ?' j7 Z6 S& D5 f$ L" f
  29.     SystemClock_Config();
    & h, D" E, V$ d% ]3 [
  30. " o4 m$ v6 m$ Y! M6 b8 N
  31.     /*
    & n9 I5 m# L8 b" [+ k
  32.        Event Recorder:/ X1 f1 b' p  B4 W
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    * S' h1 D# e4 Y$ ?- {% u$ k
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章- K. P) H+ I0 M$ }( n$ t* E
  35.     */    $ I1 N  N  R  q/ e2 z/ r. Q/ w/ L
  36. #if Enable_EventRecorder == 1  . c) A& n7 j8 i, H$ Q' i* Z6 ^
  37.     /* 初始化EventRecorder并开启 */
    & t* R/ N/ w% H. E
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    . b' g6 _8 z7 |" W, C' x
  39.     EventRecorderStart();9 Q1 ~) g* r! `$ d% F1 g# r
  40. #endif) a' B- i$ m3 A# ?' d

  41. 1 J8 m1 `6 y+ g  O3 H
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    + V3 W. r0 j4 p
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    . O$ C, [. A5 ?  i! V, w/ M0 W$ D
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */
    / T+ x* C0 B$ Q" q# ^6 o; F9 h: s* Q
  45.     bsp_InitLPUart();    /* 初始化串口 */7 l  w% d6 r* O4 t: s& |7 m- P
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ( N# v# n( F( m6 N7 Y3 q; ^
  47.     bsp_InitLed();        /* 初始化LED */   
    * Y6 @9 b9 Y1 ^" ~# L' w# b- E! u
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    7 g  u3 o" ]) d3 \$ p/ J) A9 e* F# |
  49. 4 ^$ B( r1 I7 {" c
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    # [9 l; Y# D, c5 D) |- N" ^4 B
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        " j# k6 }* \5 N7 |* v( O+ e
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */9 z6 z. h# ~' p2 T
  53. }
复制代码
- A' W$ y1 B& T) L" G
  MPU配置和Cache配置:
2 A* I/ c/ d6 f' A- G& m/ P2 [: l
- A% A! H9 ~7 w数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
$ ]" B, a4 Q" c* [" Y2 c6 _2 F0 b9 b) S. K8 @
  1. /*
    # }- _, P3 z) d" A; P# C6 W
  2. *********************************************************************************************************
    % R! y+ @# c3 m# ]
  3. *    函 数 名: MPU_Config; s$ F/ I  |/ w8 ]$ \' V- ^# z
  4. *    功能说明: 配置MPU
    . b6 R7 e( L3 h3 [7 a
  5. *    形    参: 无2 t; g) y  T3 O3 a
  6. *    返 回 值: 无
    7 H) D, `/ q( Q0 z) b. U
  7. *********************************************************************************************************
    - e! h4 \( d0 L1 Z$ o; r
  8. */
    ; R0 @3 C# N; S$ }8 Q9 M8 m5 V- ~4 P0 c
  9. static void MPU_Config( void )" r) j' K' j# I" l
  10. {, t5 x" Q% D- [
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    , B( s0 @6 y4 f5 g8 H' o
  12. ! q5 w( A8 e1 z: ~9 D; v- {0 Y
  13.     /* 禁止 MPU */+ C9 I3 z5 {) q% L2 P
  14.     HAL_MPU_Disable();* Z2 E3 T( ~- ]' J2 g" ?3 B3 P
  15. # o& r1 y* H$ s9 @( ?+ g
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */& P4 K; x# j# v2 ^8 l
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;( n  [; x6 a3 o( ^
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    & y; y4 n; N$ C  D: I7 T9 O
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    . T/ H4 l# ]( n; H( ~
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    / Q( l$ t/ c) g! G
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    $ l; S& X' H! S; {# G6 T
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;* @$ W" l2 I8 v$ g
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;8 e( F3 J+ n0 G9 n
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;& U& v! o" P. }- U" ^6 t2 w
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    0 {4 C( r8 {  _: v2 p
  26.     MPU_InitStruct.SubRegionDisable = 0x00;3 O2 q( }- e% u3 t# Y  @1 `# b
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . t8 l( ^- X6 n
  28. 9 C6 [+ b4 n/ w. K* Z* V' i/ }* _
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    + c; w3 Z1 w' P3 R
  30. # B2 a6 F' `# j8 W
  31. 5 i7 `" W! W9 W* ?8 G) Q
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    8 d2 k# \- M, e$ e2 p
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    . b+ t$ I8 ]& m7 s
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    8 b, B. z7 b' \4 ~& D9 Y
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    , P2 t; y# O% M. L1 ^4 c5 I: [+ T
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;" q  e  ~* D- f  B
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;, t1 I( ]+ D4 V1 L: R, |
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    ) l) _4 `. S1 h
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    9 G* d& w/ v2 n* t( L; O
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;' Z* c" W. a8 D# t
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    2 z4 |( t/ f- }. j8 i1 h. N
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    7 |# m8 D7 L, c
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;( p$ n6 n" `+ ^! C8 I
  44. 9 c3 G6 ]$ @' |
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ; [3 I! c0 a0 I" s& Y! V; \

  46. ' `+ x6 q- n; B* d% d8 t
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    4 O, a7 R3 B" T; H, q3 y7 h
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;9 T3 I& S! g8 }3 n
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    : K4 m! ^' ~: I3 K
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;& A. P+ c2 p, B5 z! J
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;* v4 w" g9 X$ `3 b: e# m6 x
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    & R' k- P7 K2 ~+ C6 w6 T, M( ]4 y3 o0 m
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;. {  m0 E7 I- N: m7 Q5 F
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;1 _/ t! Z* S5 e; n
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    : ^! P$ \/ K  c; p) N. y
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    0 {" B) N4 j' @& A" A! t4 V
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    3 T# T, [8 D' S) ]1 {' L
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;! d$ Q8 m2 h) i6 D2 V6 A5 C

  59. * [1 X. B3 l+ N; ?: Z; o; B, w
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);" b0 Y% K+ a, t2 T) y, Q) h+ Y
  61. 6 U! n$ c1 v# C( f/ |) b2 K
  62.     /*使能 MPU */4 `% Q; q1 V" }. M2 R/ |/ G
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);, h, a1 O7 ^  m3 C3 x( G
  64. }
    9 U9 `0 @- v0 T+ V6 L2 S
  65. . N5 P; s% W2 \: Z7 {/ r) D
  66. /*5 B$ b9 }2 `6 O8 f6 ?4 A5 ]9 H" c
  67. *********************************************************************************************************) t; l; Z& O6 u, d1 l9 Q
  68. *    函 数 名: CPU_CACHE_Enable
    2 u) g$ C- V+ R5 Z  `. a: A
  69. *    功能说明: 使能L1 Cache
    4 Q: L+ `( l1 l0 n! S& l* z
  70. *    形    参: 无8 Q" P& s' Y- p' C/ e$ V. c6 I
  71. *    返 回 值: 无
    2 G4 R4 r% ?% d" u" f, s* h5 x
  72. *********************************************************************************************************3 B8 n: T. ?' |/ d- G- N
  73. */
    3 C/ N6 h! \; P$ [/ E, a2 a
  74. static void CPU_CACHE_Enable(void)1 m/ d. }4 N; ]1 U* z
  75. {, i3 G3 I; o' C/ }) L# V
  76.     /* 使能 I-Cache */2 o) V1 a  w3 D; \- Q: k( A
  77.     SCB_EnableICache();9 `0 m  X6 R1 S# n" ^' A
  78. ; ~! U4 o5 @, }5 B  e( C5 T* I4 n* E
  79.     /* 使能 D-Cache */
      ?2 D3 e; O; Y) u: W
  80.     SCB_EnableDCache();- u$ \: P; J; u& X; L9 h& i. u! Y
  81. }
    * M4 C% c2 n: s. t/ C
复制代码
6 [4 x7 p8 o, b7 z

8 e' x- ]1 Z7 E0 x3 E  每10ms调用一次按键处理:
( w. o  o7 }6 S  g: s# t. M/ I  l% u  m+ f  j3 B0 c9 G. V
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
( O1 v9 j  {8 S9 t/ k0 Q; e# L" i: k/ Y7 f( o& @4 t3 w8 t
  1. /*
    0 ^' _6 I8 q: ]' S, n3 D
  2. *********************************************************************************************************
    % \1 `$ {$ c  h, ?
  3. *    函 数 名: bsp_RunPer10ms1 c) k1 b" h% g( J+ ~% b0 m
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求% ]- e2 L# p& V. [
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。: \2 |: r$ z& Y% T# {* _5 Q
  6. *    形    参: 无
    7 S9 {7 S# l- B) F% [) _' S% `
  7. *    返 回 值: 无& ~8 g, W6 d$ X  B0 u
  8. *********************************************************************************************************" p# H5 x4 }2 k! u( U- t4 u
  9. */) r  H& n* b0 {% j) m
  10. void bsp_RunPer10ms(void)
    / _4 _" V0 M. J, m
  11. {
    . G- @0 J9 C9 {3 v, T- K
  12.     bsp_KeyScan10ms();
    9 o2 |/ A4 [2 X" a9 r& T: R4 @
  13. }
复制代码

7 Q4 T, B0 L, u4 ~: y, p% L* M$ H  主功能:3 e8 `8 K, F9 J% x, x5 f( |/ [

$ F/ k+ ]/ Z( k6 _0 i主程序实现如下操作:
2 @( j$ G% a2 J% V9 g" R/ D4 }
$ M; u' @# ?: S4 a: \' C  启动一个自动重装软件定时器,每100ms翻转一次LED2。
( x, [, h! B; p  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
' c3 q5 [" W0 y  请选择操作命令:4 Q1 \9 v9 \- L9 }# m5 ?
  1 - 读串行Flash* X+ }, N4 ~( P. c4 t! q
  2 - 写串行Flash3 h/ P3 }# s6 k$ O8 a
  3 - 擦除整个串行Flash
# {& Z7 X: o' N- B0 ~% L  4 - 写整个串行Flash
, u. a7 @+ w" A: z4 L  5 - 读整个串行Flash
' P3 i7 R7 A  o  Z - 读取前1K
0 v) t. L; @" P% T. `( E  X - 读取后1K
9 ^) Z0 B/ Z7 `4 F; ?/ p: K# @
  1. /*
    . z4 d2 U; A+ P! z- d9 O8 n1 ]4 H
  2. *********************************************************************************************************4 t/ G$ V% S9 ]" y$ O8 }
  3. *    函 数 名: DemoSpiFlash+ |: `! l% j0 C
  4. *    功能说明: 串行EEPROM读写例程  X  ~6 u4 `. C+ u7 ]4 L' Z0 G
  5. *    形    参:无* e" ^8 L2 g( j# ~- ^' f
  6. *    返 回 值: 无6 F$ J2 D% g/ z% ^) K
  7. *********************************************************************************************************# J+ M' n3 K- e; p4 \
  8. */' [" F. J# q0 j0 `2 q7 L
  9. void DemoSpiFlash(void)9 b" Y# p/ d# x4 I/ D$ v
  10. {
    . h, n' A& e: J# S: r& j
  11.     uint8_t cmd;9 P1 r& k7 x( r
  12.     uint32_t uiReadPageNo = 0;5 V' D* T1 T0 b( G2 r

  13. - w$ i5 v  x) T- J

  14. , q% E, X9 w+ ?* J4 B
  15.     /* 检测串行Flash OK */; p) P. }" ~& B2 a
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);1 q/ J2 ?! t9 X3 G- L/ m
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    + }3 u' I1 o) D1 ^; B9 q
  18. , \# b4 U2 u! S( K3 V3 t: b2 D
  19.     sfDispMenu();        /* 打印命令提示 */- Q: t# z) @: h$ X! g& b
  20. ( _( |& ~# P0 m0 x
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    ! i5 \- a1 F/ `3 _5 D

  22. + R8 l' V' \9 w9 J7 U7 S0 T" Z
  23.     while(1)+ h  ?$ r) c& V" W, D& ^& U: W8 C2 t
  24.     {& }( n& B4 `% y9 @. P. f, ], S
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    : K- L% p+ i, L+ Z
  26. ' \8 `8 q8 R3 u: H3 S
  27.         /* 判断定时器超时时间 */0 C& x- U# @8 R, u
  28.         if (bsp_CheckTimer(0))   
    5 j9 j, s- R* j: B
  29.         {
    0 K7 b8 N, K  @  V; E
  30.             /* 每隔100ms 进来一次 */  0 h9 L& A1 _3 K6 _7 y
  31.             bsp_LedToggle(2);
    , D  Q& \. f% p' X. _/ w5 G& B
  32.         }" E  W& x& h+ d/ V2 z+ J9 ~

  33. * Z9 `# R! b) @
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */8 W. o  Y% n# a4 [# P8 @
  35.         {: l! _# W% I* v. z
  36.             switch (cmd); q* f+ u) K' m, m) }8 ~
  37.             {: l. J  b( K) G2 d0 x  x# u  O
  38.                 case '1':
    - H, Z5 f$ _: }" Z% q' _% B
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    " g5 p/ t0 a- u/ |; v5 N* P
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */0 j. r5 `0 i; ?! z  z: i/ O4 l
  41.                     break;
    8 x5 w+ `/ N5 v0 s6 ^

  42. ( z1 T0 \1 f0 ^" W! y
  43.                 case '2':
    & i3 I2 I+ l+ [( D
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    3 }7 _5 Q% [0 x; E) h
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */# t3 F7 O& g, `; y3 q% k2 g/ c
  46.                     break;: ?, C; G) G. V
  47. : e5 \3 }: B  s% y
  48.                 case '3':/ H- A  |& \% G
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");6 l  S: X9 b% u! O# V
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");7 j. G: l1 L! p" R) P1 S! B% p1 Z
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    ) l3 J8 Z/ c; n1 n, r' Y9 Y# \
  52.                     break;5 y( M0 ~9 E1 Q0 z" u
  53. 1 }$ s! ], h& @* w* T0 y
  54.                 case '4':
    / i+ I$ h) q. _; }
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");) ?- @5 v1 I, U, x
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");3 ?; |1 Y+ Y( H4 A9 P6 R5 C( T- W
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */6 c; X, S! _( _. v' Y
  58.                     break;& z& y% n2 i5 Z; d
  59. ; I# I2 N& l& k
  60.                 case '5':
    6 {7 z2 |* z  M/ \. \* X
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));* ^: |3 `4 I, b4 M; f7 j3 {4 ~6 Q
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */! C+ ]2 {8 N! y) [4 @( M
  63.                     break;/ L* {* p6 W4 B
  64. 8 V5 f3 |2 l2 ~' z$ T1 X0 H
  65.                 case 'z':
    / L2 [! K+ F' o! H
  66.                 case 'Z': /* 读取前1K */9 q8 E/ G  v$ ?6 O
  67.                     if (uiReadPageNo > 0)
    2 v, k; n& S5 b8 p9 a& J1 k
  68.                     {2 K! E- k$ k7 [8 i
  69.                         uiReadPageNo--;
    3 o! w% F+ r' t' ]
  70.                     }
    " H) i- F% Y/ q  N! \
  71.                     else- L3 S+ E4 a" ?) l4 f& j
  72.                     {
    : o) h' i, {" _
  73.                         printf("已经是最前\r\n");4 a$ r" s! J7 M
  74.                     }; \; V& h+ D- J; N2 q2 j7 m; A
  75.                     sfViewData(uiReadPageNo * 1024);
    : Y2 s! _5 X% \, S) ]( X. t
  76.                     break;- C' o6 r2 |3 f# s

  77. 5 I' l" g! D7 a# w+ y
  78.                 case 'x':
    2 b5 n. o0 Z, H1 @" [
  79.                 case 'X': /* 读取后1K */
    * k6 |$ B; |2 _8 g! {/ F9 h1 o1 r( E
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)! w7 Z; D/ W: l. v! o! C0 y/ G0 N
  81.                     {5 g  A$ @$ i, B
  82.                         uiReadPageNo++;
    1 l- _# B! C! }4 M0 P
  83.                     }
    * h- L1 l- N1 y% R' K3 W6 o
  84.                     else+ O7 s( x5 @. F+ @& s, I- N
  85.                     {
    , f5 `3 B, A3 Z0 V8 }4 I
  86.                         printf("已经是最后\r\n");
    1 a8 C3 f- }* g8 ^6 u# Y
  87.                     }7 M- V3 j! R3 K- s) ?! U
  88.                     sfViewData(uiReadPageNo * 1024);
    : H' S3 i4 X0 q& D
  89.                     break;
    & E. \1 n/ j( _+ {. A: M8 z

  90. # f, A6 {; j+ t/ W/ E- M
  91.                 default:
    5 N0 D4 x8 U0 }' i9 z0 [
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    ! D* \4 I8 m" f
  93.                     break;
    . y) ?, b" ^. N2 B, o) l
  94. * r8 F& Q; H. Y" H0 {' u3 I+ c
  95.             }
    0 e2 w4 R0 _  M
  96.         }
    6 m+ Z5 B; }6 L0 c, w
  97.     }) I2 w' p/ b* {
  98. }
复制代码
5 P6 b& x! k  k) m0 k- l. p
73.11   总结3 [' O. b% A3 W3 J+ N: l2 e- g9 w; q
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。; e) k* p  e/ g4 s8 Z; ?& ]

. S6 n: }1 C3 V* i3 y5 g
+ |4 T) n' d7 d& S6 B: p- F( r
: P/ M0 M2 G, v) M4 d7 e' t
赞 收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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