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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示4 [" l- r- `5 x
  W25Q64FV属于NOR型Flash存储芯片。! A: v9 j. O( B& Z; p3 Q5 e
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
9 K* L9 w. v* ^4 v1 D8 \* I1 f( K2 `
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
' b" N; X0 S( ?) h
1 ]) l/ j7 f6 M) j  _$ n
  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。- a3 O  O' l: [% ^! R- }. c
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。1 _) A) h" ~* ^  n+ p# Y
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。! t7 R  a  B% }# h" ?
  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
+ Y2 j% Y& O( s+ x" ^# b6 ^' h* j  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。
+ ]. N: ^" |5 {( r
  m' G- g% X/ w1 v* E+ c' F73.2 W25QXX硬件设计+ z+ l6 P* v8 I! M* [1 r7 X0 e
STM32H7驱动W25QXX的硬件设计如下:
$ H( f! [, ~& d; Q$ }* ^; Y' _
: `( L# V0 I2 S5 B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

: @( S2 r; @* N* U1 y2 b( ]4 h6 m6 B, i3 {7 v4 F$ _
关于这个原理图,要了解到以下几个知识:
, I0 N: W6 a' A. q9 Y3 |5 `! M
  V7开发板实际外接的芯片是W25Q64JV。' a! @9 |5 N' L. E: K1 k
  CS片选最好接上拉电阻,防止意外操作。
, n- O/ ], S7 W- ]  P  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。$ K8 g) C2 P' v
  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
) H, }* ?# r! e  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
. p+ F! q. i  [73.3 W25QXX关键知识点整理(重要)7 J$ j' x5 k) E& w- o( w
驱动W25QXX前要先了解下这个芯片的相关信息。
; n* e0 l& b2 P) a, ]( c2 h6 u1 @1 I- p% Z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
( P5 j  @8 D) A0 I2 f  _- ^

0 q9 c* [9 [; z1 t73.3.1 W25QXX基础信息
; p% A3 q1 }7 Q, C
  W25Q64FV的容量是8MB(256Mbit)。4 q4 o1 O! R) V' w7 D+ h
  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
& a( X( a+ z, Y支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
. B6 Z0 s3 o6 X( k6 ^
8 [: o8 \3 H7 C) g2 Q支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
% e% p8 n. E3 [+ k1 @
4 P0 m' y9 `1 E% P( B5 I0 L(注:这里几线的意思是几个数据线)。
( M, w' {' R! i" v4 E0 n- W
: @/ `5 p+ W6 v; k  W25Q64FV支持的最高时钟是133MHz。9 o& l" F7 l! x! {; {. x
  每个扇区最少支持10万次擦写,可以保存20年数据。
. m9 l# V" e& c6 a. @* {. u6 N; \2 `  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
' ~) g4 k2 [5 v! m! e+ ^9 Q  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
* Z, c0 [, a; |6 y整体框图如下:
8 `, N# g3 S0 B: f. E2 R( o: x( C0 x
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

3 e7 `, [! q2 U/ T  U# K) ^; q: Z. \2 u; S2 ~- k- c
W25Q64FV:
, n# ]* [) j% l* A7 J
# ^) y5 B* j/ Y, `3 Y4 F  有128个Block,每个Block大小64KB。
% T! L9 L4 O4 M' t: ?$ B* r! |  每个Block有16个Sector,每个Sector大小4KB。
; t$ b) I8 V. H  每个Sector有16个Page,每个Page大小是256字节。, n, M  P9 i* F% I; w
73.3.2 W25QXX命令
8 t! N( r; k" y6 u使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:& t/ i7 F7 |2 H  b% |
6 U* C9 Y1 L1 [' I4 {! T) a3 ]; }' k
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
3 w. ^' U9 @5 f7 R' a3 B4 N( T# Z4 @

- A) M! z; N1 D% P7 N( |: g# _' p当前主要用到如下几个命令:
  d/ F8 t8 o" e8 @3 o, U) m
; e9 }& q* K+ Y- ~
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    / v# ]" M$ B& t$ H. |
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */9 e* ?2 ]5 t8 w; x* S2 x
  3. #define CMD_WREN      0x06    /* 写使能命令 */
    $ q) l- r6 n6 J! _
  4. #define CMD_READ      0x03  /* 读数据区命令 */
    ) I4 g" T; Q5 b" C
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */5 a8 @4 G! y% G% j4 P
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */9 X" z- T/ B! H
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */# p0 j% d- v8 X8 k/ R
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */
    # U  @; g, t5 b' K8 t, ?
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码

. e( H/ C% y  `1 I) T/ p) P6 }73.3.3 W25QXX页编程和页回卷
* w% V# i# m. aSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
) z* n% X* N4 q; a% W+ Q' \) i$ v3 ?, L; _: I: P
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    9 [8 U% e  q0 S$ q$ t
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   7 t. ?% T% W# K# y# p+ I
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    - k% p) R, t% Q, \6 P  L
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               9 D5 Y( M5 U, b* u& k' B
  5.   b" l8 Y  W6 X' z% B
  6.     for (i = 0; i < _usSize; i++)
    + S6 S. O2 u# L: A  ?
  7.     {% b# x8 L' O' ~: B
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    1 M# R$ ]6 P) Z  P2 t3 G
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    $ b$ Z$ o$ g, ~7 N$ J/ z4 m8 C
  10.     }
复制代码
+ |- ]4 d2 E& e. E' [# r+ O6 L
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。: L3 h: |9 @& J8 n, c4 P5 h

1 [) @0 W" J1 v; y: B4 H/ M比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
) N2 V4 `  v2 B3 p! W! q  a/ f9 F; [8 Y0 l
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    ) J. m- W# L8 ]( A) i* m: _. ]: ~8 Q
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
9 a; O/ ]) Q* d. L# l; S
  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
! h' E6 [; J. F2 n# R. w  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);  Z' |) e* W( E" _4 o2 b$ r
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);
; Y/ m! j- y  Z3 ?- D  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)' P& y" C! U$ v, t7 r
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
5 l% v( M! d/ D+ v" ]9 k0 n! N$ W7 \) \7 [8 j0 x9 F
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

# i; m) h$ Y" k, i
7 M8 `+ D+ ~- Y& Z& |" j& E8 B
% x$ z7 q5 B3 B0 F) s* I, l73.3.4 W25QXX扇区擦除
5 Y5 k' p- Z* F; W: ^- H% a& [2 QSPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
, [6 _' _7 j7 o( U+ C5 \! C0 |* V9 k8 l9 H4 ?- Q3 u* o7 v
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
1 Y3 i# E3 B' }. }/ z/ C; o5 L" c+ D$ g- \8 Z) G: G8 B; G$ V
73.3.5 W25QXX规格参数
* [! M  @1 M3 o  b3 j" c; n% }
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:9 [$ {" C$ n. P, Y  p7 x

9 A3 ^/ z; x0 m5 Q  t8 p: s! I
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

/ p2 J% m2 k5 A6 q/ I' u+ o$ U' N! G9 X: L6 e* f& [8 ]
  页编程时间:典型值0.4ms,最大值3ms。9 Y' t  P- w( m- S
  扇区擦除时间(4KB):典型值45ms,最大值400ms。* ]/ r, J4 T$ \! B* E
  块擦除时间(32KB):典型值120ms,最大值1600ms。3 Y! k$ S' ?- c! d8 {7 s" _3 K* g4 }
  块擦除时间(64KB):典型值150ms,最大值2000ms。
: ?- M* \2 y( T4 y, v  整个芯片擦除时间:典型值20s,最大值100s。; Y5 {, ^1 M! w$ W+ l( I  K1 C. F4 p

/ x" j4 k2 z; ?( P* t
6 B  v: p1 B6 |2 T支持的速度参数如下:& H) b% ?5 @# X  b( ~/ }" u
: y. c* v8 W, B6 E: X; o# n. s
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

( W3 ^6 }0 B" O8 w& _" A
+ Z, c% c' j! ^' R6 W: f可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。2 @3 ~- o5 W( D  T$ W' o
/ \( y( V7 s8 N: ?4 A  z5 X
73.4 W25QXX驱动设计/ P6 U5 E( `/ y2 q; m- e# Q, W
W25QXX的程序驱动框架设计如下:# m" y$ u  p7 A- l

7 ?# d& x: F# I9 v+ p8 K/ T( ?
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
3 S+ O4 D" h! D- Z/ v

9 L) f+ G. v4 ?有了这个框图,程序设计就比较好理解了。2 L' t5 V  F* |- E0 k4 ~

. [* Z% g2 s2 D73.4.1 第1步:SPI总线配置; j$ E5 T' B) e/ a0 }
spi总线配置通过如下两个函数实现:2 m! l+ [: p: n" m# j
' Z7 H  D" [$ v4 o# ^7 o; d
  1. /*
    . C4 B1 r' l* \: y9 E8 }" X; e
  2. *********************************************************************************************************
    - M6 m7 _% M& d, b( m& O
  3. *    函 数 名: bsp_InitSPIBus: \) M' J/ i, b4 d  R; B! e+ t
  4. *    功能说明: 配置SPI总线。1 c- k. Q% J  K) c$ W8 J1 W
  5. *    形    参: 无' ?4 y7 \; S6 v) L6 T9 ?
  6. *    返 回 值: 无
    ! m' S) B1 @- j/ I4 R8 B! M
  7. *********************************************************************************************************4 @* [$ j4 [8 R8 l4 l
  8. *// W* X" D- \! x: V" R, L
  9. void bsp_InitSPIBus(void)
    $ [/ E& U) t0 ~/ R$ }0 S' \/ O
  10. {   
    ' a6 V" l* X% M" R$ S# W
  11.     g_spi_busy = 0;
    * S8 C$ k2 @& y1 b4 M

  12. 4 z: R) N& q$ J6 D2 i
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    3 N# y" l% r% V% T' W6 i2 o! r0 @
  14. }7 p0 s1 \; {! j! h$ k; z1 [8 \4 K
  15. " h: v- K% R: r  A! G; L( c
  16. /*7 G6 g# ]' N* j9 D! F
  17. *********************************************************************************************************: l. k* a7 a. x0 e( K6 i0 Y
  18. *    函 数 名: bsp_InitSPIParam0 {4 z3 s6 {. l+ C$ w) c
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    & B. n% D! N, F& {5 W4 A
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:( Y3 e! _8 T- u/ s
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频" l6 d2 X. g. d& V& ~- V1 }
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频: w: Z8 U6 N9 R; l7 G
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频3 M% x# y0 g3 m5 ^! T
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频7 F4 g# i0 F4 j0 E8 H
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频( g- ]5 g, z- l+ h
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频0 A" i1 E' g4 C9 p
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频4 U0 o2 J+ K1 J# D
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频
    ' g! x& ]8 E# ^- N; z1 Y' u6 F
  29. *                                                        
    2 O: Q* r9 k2 V" X) Z
  30. *             _CLKPhase           时钟相位,支持的参数如下:6 N0 o/ M0 S2 w$ z% `
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
    / m' E8 k- [+ @
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    / H8 q: Z' R+ w, n7 m+ A
  33. *                                 ' K$ A: e( Y; Y6 Y4 j
  34. *             _CLKPolarity        时钟极性,支持的参数如下:
    ' ]2 h" D, Y* x  g+ u
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    * R; s0 W( y+ h* t: s8 B
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平  m( F2 T" \6 z- B
  37. *
    : o) R2 k3 K$ Q8 J: ?% x
  38. *    返 回 值: 无. m/ Y+ m; n5 D3 I) C
  39. *********************************************************************************************************
    : f  Z! n8 I4 ?8 h/ X6 Y
  40. */9 i9 ^; W$ a# W( F) P! d9 |
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity); n( B* h  q( x! |
  42. {- r; ]: A+ |. l" r5 m4 K
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    ! @1 N4 C& W" ^
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)& h+ v, u- U3 O2 q! y5 Y
  45.     {        
    6 h, z$ J/ u7 h: j
  46.         return;
    ' Q0 I0 q2 k' F3 _: @. ^
  47.     }
    % `) F7 B( z; z3 ?+ e4 j! c# b

  48. ( h3 Y6 p3 C5 `% E- ]- V
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;    % _8 j  G, Y( H
  50.     s_CLKPhase = _CLKPhase;
    7 {1 E7 A2 G2 M
  51.     s_CLKPolarity = _CLKPolarity;
    ! t2 m! y! ~! U' S# V3 l- i7 w2 \& h
  52. 4 V$ l* [% U2 ~0 m# u. L; O' `

  53. " F" o- x+ }: p0 i
  54.     /* 设置SPI参数 */; q; h0 i! S& K/ I' x+ s, g
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */; l3 S  c6 B" k0 @' _, I  W5 G
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    5 G4 s4 R6 f- R# @( Z
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */5 B0 d1 m7 `9 s5 _! k$ P1 ?
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */6 _+ E/ R, g$ y
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */5 `5 g: Y1 r  S! ?( B+ ^
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */' I5 A( H' d4 P4 H7 p- A
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */4 _4 u* _( I- j8 ]$ G( F8 d
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */  r- n: [$ `5 J9 U$ b4 n
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    9 ~) t( ]% a! y7 V
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
    8 @4 h7 O& b8 e3 z  [- O3 w0 i
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */
    8 S0 Y4 N7 O1 U% `0 I
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */( ?) I6 g3 D& W
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */6 x  V1 `) N# }) t
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    / g* J* T7 d2 g4 z% u
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  * K  D0 c0 Y& @8 e6 u2 ^; P5 p# Q
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    ! e; G0 ?& O1 f$ _

  71. & C2 v7 X3 Q8 ]* q$ A
  72.     /* 复位配置 */
    . t$ K% u7 ~, I: u& L# b4 i
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)- V  o/ Q4 J9 D
  74.     {2 t7 p" o' v+ J" C  Y$ ~9 W& y
  75.         Error_Handler(__FILE__, __LINE__);/ m( F& O8 I' ^/ T0 f: t! L
  76.     }   
    $ l8 X6 E$ W" ^: ?* o0 e5 {

  77. ( U' x; n) e0 s/ p
  78.     /* 初始化配置 */
    - A& F+ C! |: B3 Q" E# m
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)- D* I, H% M5 z
  80.     {
    , ?- M) U, `  J3 ~
  81.         Error_Handler(__FILE__, __LINE__);0 a7 T$ Z% |% Y6 R2 R8 p
  82.     }    " h' R+ Z0 X2 r0 @
  83. }
复制代码

# f5 A. Q0 g* h, l7 z9 K5 e关于这两个函数有以下两点要做个说明:
" O/ \2 t! Q8 Q3 R# i, d' C1 Q- Z- @8 d2 q
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。, {3 i  }3 H7 g: f$ P& X/ C  [0 q
  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。9 S9 c0 x1 a; X$ y8 B
73.4.2 第2步:SPI总线的查询,中断和DMA方式设置2 E2 `) y/ g" @! f/ V9 Z
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:) M' n; B3 c- \5 W4 s
8 m) D' H% g0 ^
  1. /*( y! E7 l" D0 B( J
  2. *********************************************************************************************************& @& R6 r2 k% Q) _& l7 }+ a& D6 G
  3. *                                 选择DMA,中断或者查询方式
    - ~5 k: h8 v1 Z8 r3 J
  4. *********************************************************************************************************
    ' F0 B; ?% @' {* O" M
  5. */
    + b  D# `; G6 z! S7 N( D0 k
  6. //#define USE_SPI_DMA    /* DMA方式  */  X( U2 s/ s. E) r8 Y/ G; {
  7. //#define USE_SPI_INT    /* 中断方式 */% I' \5 F# R' D
  8. #define USE_SPI_POLL   /* 查询方式 */8 }7 l/ I  V0 l* k: C; o: Z0 |4 K

  9. 4 @+ H7 [( S5 s
  10. /* 查询模式 */
    , l: _6 j- m( E* R
  11. #if defined (USE_SPI_POLL)
    8 ?6 A# K5 f* E7 b$ y: t  G
  12. ; S6 P: B' P6 T4 j3 G' B3 ~1 A) ]
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    7 ]& S  i  I1 h# o! V1 ~) g
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];- N) c% {4 d$ ~- b: j+ c
  15. & s  V# o. c, O" b6 K
  16. /* 中断模式 */
    * \9 ~5 f" t3 u" }5 |
  17. #elif defined (USE_SPI_INT)
    * m2 d3 `2 @& O! p+ Z
  18. + z- J, l$ T6 c+ q
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   / d% m7 g! _# z- f* k: x
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];9 O1 s' C+ V: @+ W% z5 L, b6 E% s
  21. / V" _6 b3 ^8 {' d. u7 g
  22. /* DMA模式使用的SRAM4 */
    6 X6 O6 ^2 S. e+ A* |8 s& ~2 ?$ Q
  23. #elif defined (USE_SPI_DMA)
    ( X* o! k7 P2 K
  24.     #if defined ( __CC_ARM )    /* IAR *******/
    + q& B" F3 g: l0 `1 ~. m8 m
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    + t. u5 S& p( G( A. Y9 f$ x2 l" c0 e) }
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];9 f- K  H& V7 f/ w/ J3 y6 h
  27.     #elif defined (__ICCARM__)   /* MDK ********/
    4 i' `! g7 c- N) ?  r4 V
  28.         #pragma location = ".RAM_D3"
    ( o2 J2 m( C: [0 C8 ^
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    0 x" A! A3 {  J; _& N9 p6 ?, v
  30.         #pragma location = ".RAM_D3"
    8 t) N; q& h& a2 e. o
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    ( ^  O# Z/ u: X8 E
  32.     #endif  f! @0 J2 b; X( D
  33. #endif
    7 ^+ k1 I7 G# M: Y0 d' I! D

  34. . N' r- D3 @; |4 U5 K
  35. /*
    3 N. Q. W5 C5 U( C5 k* T3 L
  36. *********************************************************************************************************( t, X3 T# ?8 ?& I/ H' I( c
  37. *    函 数 名: bsp_spiTransfer( z  G$ `8 @* ^7 c  ~2 Z+ F# S3 U" _
  38. *    功能说明: 启动数据传输* k7 q# B! C3 D. S& p' |+ \6 v
  39. *    形    参: 无1 @0 P- {% m6 s& ^+ Y5 m
  40. *    返 回 值: 无
    / t# g" W7 x5 H8 O0 z
  41. *********************************************************************************************************) S" ^) N$ y: v2 d0 z
  42. */" `8 b( w2 d+ ]- Q; c1 L$ S2 w
  43. void bsp_spiTransfer(void)
    7 R; S+ e) ~: V' `" j
  44. {# ]6 x  _' W$ a' F
  45.     if (g_spiLen > SPI_BUFFER_SIZE)5 V. z: p2 G  O' X  B
  46.     {
    * E5 n, Y7 [6 h' L% F
  47.         return;
    7 g, r  C* ^; ^
  48.     }
    : W  ?3 {$ ^! a6 T

  49. ; p7 k8 R8 u* |& q
  50.     /* DMA方式传输 */
    2 O5 p- S7 U2 u1 u
  51. #ifdef USE_SPI_DMA, V$ D: o6 @2 E1 ^9 s. l
  52.     wTransferState = TRANSFER_WAIT;' l' d% X+ F+ t% j9 \6 K
  53. " ^4 A) A) J, o8 L- |
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
    0 G/ y* h3 g$ |- Q$ b
  55.     {1 C" c  y- g: i5 w0 m$ U; ]
  56.         Error_Handler(__FILE__, __LINE__);2 t: I+ i" h5 K! @
  57.     }
    ! H1 Q9 _- c, X/ t

  58. 3 ^, R% j0 T8 J2 N# E% {' n% ?
  59.     while (wTransferState == TRANSFER_WAIT)" y: S0 o9 V5 w
  60.     {, o4 f5 _, l8 }% W+ G- K8 |; R
  61.         ;, [$ Z: z  S5 x( ]
  62.     }
    # f  y' C6 S, L) e6 c3 \- {; H
  63. #endif
    . }8 t' s+ }+ O4 `

  64. & S; I/ ^) H/ S  u
  65.     /* 中断方式传输 */   
    * J/ v) j( E9 ~  c- j, [; K% M
  66. #ifdef USE_SPI_INT
    $ {+ O* {. |2 e8 L
  67.     wTransferState = TRANSFER_WAIT;$ {/ e5 \% s7 ~

  68.   q6 S& ?/ I$ a. u3 c7 h3 b2 O5 q
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    / p5 V) k9 K1 P
  70.     {7 L0 _- Q: u6 d5 Z4 m7 }$ L7 F
  71.         Error_Handler(__FILE__, __LINE__);
    6 M* ?# j6 z6 M& f. f5 e  J! s
  72.     }9 e1 J. Z1 V9 Q8 L/ U4 x( ~( \
  73. " G4 B8 Z7 ~# c3 A1 E/ s' L
  74.     while (wTransferState == TRANSFER_WAIT)
    * d$ Y$ A4 m" T9 i
  75.     {" f+ j, \5 z$ a
  76.         ;9 J: y3 @! J  m* j' m( Z
  77.     }
    9 r% C* W- V* [# W& n  f
  78. #endif
    ( z, C2 `" @" n' }8 ~2 A1 n: N$ f

  79. 2 c  v0 `, M4 a2 @. B1 u
  80.     /* 查询方式传输 */   
    0 n+ w1 q# w- f) @3 s
  81. #ifdef USE_SPI_POLL# ?7 u# }% X% y! P0 F7 Z
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)   
    / z. A( ?! U- c1 E
  83.     {
    $ y" U: f. k0 N
  84.         Error_Handler(__FILE__, __LINE__);
    7 G% [# N# z4 P0 H, w3 {3 V$ ~% K
  85.     }    1 T1 P0 [$ s' h# S1 F" T
  86. #endif
    ( i: B4 W, ], i1 k, @
  87. }4 g5 |6 h, i, N2 D1 J: H
复制代码

* h' ^5 N) C4 r通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
( K0 n) U4 L$ ]- F3 N. A
, F5 ~2 h2 h$ M# {/ U9 F. p% m  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
( `" T  `: I$ E3 S7 Z8 e  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。! ^% v4 q7 H: Y! }
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */; k  i* Q; e! z0 v% D: M5 l1 s
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;% L3 |2 V2 C5 P$ R2 Y, V" L! ~( }
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;
    # ^. y4 C+ W& V1 q. ]
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    $ ~+ [+ f! w& ?+ I2 Q
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;; j3 B% d, \2 T9 v6 g7 A
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;! W7 P1 c9 I' {& b" P
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;% ?8 X7 A7 F0 o; `5 \( \
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;; t. K7 |. p. }1 B
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    8 v4 `8 V& m/ B6 X
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;- Z. k2 R( i. H, M- w9 h8 ^
  11.     MPU_InitStruct.SubRegionDisable = 0x00;
    / z4 x& t8 K' Y$ Q
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . a: `2 k+ Z/ j( ?: j- J+ z
  13. 4 t* ^) l! v! Y1 |- D
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

% I. {4 l3 ]2 j- w. O7 G% a73.4.3 第3步:W25QXX的时钟极性和时钟相位配置
5 U8 Q$ O  N# l' i8 x' a$ X
首先回忆下STM32H7支持的4种时序配置。
. Y, P# {9 {+ w$ n: F) v+ Z' {
4 Y4 [; s1 D+ k' R  当CPOL = 1, CPHA = 1时
; L' u6 [+ _$ l7 B# z& f, H! F) i( D9 G) @
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。* S2 |. K0 I( w
, F/ ?8 N) \, f( O5 [: L' M' v
  当CPOL = 0, CPHA = 1时) f0 L% K9 L' C0 Y- k, q
8 g8 e6 \; _# u( p& a' `; J; ~* t
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
6 D0 H% D; j% V& D
' _0 W0 _6 p: @% g4 g$ z  当CPOL = 1, CPHA = 0时7 E4 {9 @2 Y" x8 k, d7 l# ?

% t. z6 d3 V  W) e4 M! XSCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
1 s$ E, P2 r" ^( W* X& g/ Y* E: z! G6 E8 S% X; j, P( R
当CPOL = 1, CPHA = 0时5 a4 A9 c4 i3 T2 [
7 m3 p% F: i* Q7 q" K: Y5 o
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
3 x- P5 |0 W0 M4 }& `1 X
; Z  \$ p  K9 ]1 k; o; f! B2 n6 E
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

! ?+ x! N7 W. F; r) s' i; A% c* R
有了H7支持的时序配置,再来看下W25Q的时序图:+ F! s# e2 s' v, m  f0 @
1 u8 _% A, W$ G$ e8 j% f
Mode0 : 空闲状态的sck是低电平。3 e9 ]; }) e0 h, ~

8 o, j+ q  e0 H. N+ Y' F8 E, gMode1 : 空闲状态的sck是高电平。
5 `# h3 h. V" L  o& g7 d  X% |! L) I! Y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  f$ V0 ]) a6 y% O
1 @( J/ e# I: c. ]
首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:9 r7 A  k3 q) b

! [- h+ Z; Z! R- W+ JCHOL = 1,  CPHA = 1) V* c- C0 F4 X$ w/ a: T% y# r
9 X8 V# \2 }4 K8 d. \
CHOL = 0,  CPHA = 0
3 L" k, P% ]% k7 H9 R) w  m$ S3 }3 z9 R4 T# D3 Q8 e, e7 r
对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。
& ]6 t. \) p- u0 B, n; |' I3 \7 w, r/ }# _2 S, [
73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制
4 [- N9 n2 u' a! u8 `0 o' A# V$ i单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:
$ u* @! W! A* r, ]  p6 s1 E+ P' R. q* w* I' `
  1. /*
    . P$ g2 D! y- y$ p+ o
  2. *********************************************************************************************************, m5 S, E  i4 r) A
  3. *    函 数 名: sf_SetCS
    / K. l: r) G3 Z5 n1 [" D
  4. *    功能说明: 串行FALSH片选控制函数5 @! c7 {7 \/ s. x8 G
  5. *    形    参: 无
    4 G* a3 {0 S$ L* I7 r# N
  6. *    返 回 值: 无1 y' U/ ^+ U% B- b! Q
  7. *********************************************************************************************************
    1 a' L8 ?4 P+ ?* x! @
  8. */
      T% i/ Q3 Z) _7 b* ], ~
  9. void sf_SetCS(uint8_t _Level)
      Z# R4 K* `4 G- i
  10. {1 ~" J  |8 @! O; y! v1 K: Y
  11.     if (_Level == 0)3 P, f, U* f0 J# x. Z. |
  12.     {3 P( h. C2 a+ V! d" J* R" y' Y1 j: [
  13.         bsp_SpiBusEnter();   
    - L8 H: k9 w) h5 k# U# F& V
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    + I8 w! Y- C& f8 d# g
  15.         SF_CS_0();
    5 t; u* ]! J" S% S, ?9 {
  16.     }
    ! v6 Y) h( D5 j* |: R
  17.     else
    9 j4 n! Y" z# A0 w; R- n; z
  18.     {        ! F  }+ |/ p' v4 Y7 m
  19.         SF_CS_1();      F" Y! e4 J  N2 |1 U; [
  20.         bsp_SpiBusExit();        
    : w. A' }' @. v7 Z/ b2 c% D( a
  21.     }( o8 f! N& h" J2 J! ?' ^
  22. }
    6 u* e! r9 Y; L9 a" M
复制代码
7 m( p- U; ~. G0 ?+ m  L
1 x( S% Z* g' B! P" k6 }: V
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。4 ~& e" N6 W2 S
$ G& L- k5 X" Z
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。6 m5 [: X/ L6 Q# {' {0 d; i
7 _4 I+ R5 B7 {2 c; R& X$ y2 D
73.4.5 第5步:W25QXX的读取实现

  S- t2 E7 \* j- u! G; M/ iW25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。7 k2 Q, s7 f: r; D9 T
7 I/ i- ^. z  {; |  C) N
  1. /*8 ~5 n3 M6 @+ W) n/ V0 e
  2. *********************************************************************************************************
    , ]* V0 Y+ j' A. e
  3. *    函 数 名: sf_ReadBuffer7 Z8 s; O9 ]' [' t
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    * B$ d+ }, E* S! k# _6 w$ |
  5. *    形    参:      _pBuf : 数据源缓冲区;
    7 \8 l) E. I) L0 C( |( C* k8 v
  6. *                _uiReadAddr :首地址5 F. s) |# H' I5 q
  7. *                _usSize :数据个数, 不能超出芯片总容量3 k; u8 \5 D3 [7 S3 y1 Y
  8. *    返 回 值: 无- j, ?7 R( e7 ~; @- }* m
  9. ********************************************************************************************************** g; C5 H) j( S- g, ~1 T6 i
  10. */
    9 f4 T5 S- U" L# t; Z: m
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    ' c: q- X# p- G( T3 @2 v& c* K- u
  12. {/ @. l% M) l; p0 Y4 B2 h! }; K
  13.     uint16_t rem;- i. k' c! `$ W; h9 Y5 K
  14.     uint16_t i;
    ) `8 X4 R% {; w0 g

  15. 8 T" `, S5 J4 y% N- X* r% _, p. h
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    ' ^0 C2 w+ N  t. {+ P* }/ T
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize), n# E3 {3 P) F7 L9 y5 V8 q
  18.     {/ T( ]9 _' C! D9 \
  19.         return;
    ) I, S, G* u8 K# C* _  W6 g
  20.     }
    5 V+ w1 o) ]0 O- v1 u' J7 ?

  21. " m% t' A6 Z. G" Q- M# [+ l
  22.     /* 擦除扇区操作 */4 L2 x; {; C! O. ]) l5 X: }
  23.     sf_SetCS(0);                                    /* 使能片选 */
    4 Y6 \. k' Z! S3 r7 x; ?- {
  24.     g_spiLen = 0;& a3 b. z  {- h: Z+ z; N, L" a
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
    & s5 ?2 G* A2 M5 D/ U( d& U5 i
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    # z9 b. h$ o( U' `( f. I
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */
    % W# J9 ?% u( B) n
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */3 S5 R- L6 Q' w+ n; |
  29.     bsp_spiTransfer();& c/ j  d, @9 y

  30. & H9 a* Z  J; k& N
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */4 s! _8 ^2 j  x, @" l! l7 x1 z
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
    4 M( D* P) l, T7 @
  33.     {7 \/ k: f; Q8 j: ~, e& I& J
  34.         g_spiLen = SPI_BUFFER_SIZE;
    9 [" i2 W4 T$ [' P: e+ S
  35.         bsp_spiTransfer();
    9 j0 |$ ~: x) [. q' `

  36. 3 M/ O7 p! B$ }  _
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    4 a0 H! `; Z2 R4 T* `  l
  38.         _pBuf += SPI_BUFFER_SIZE;2 m0 s( V* {1 \4 s' ]" f# H! P, q
  39.     }+ N5 B  m4 u& ], S3 B7 w: q: ?

  40. : S) I4 T! O2 }, e
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */& k& C" M: H4 c5 L' {
  42.     if (rem > 0)8 p, m6 f% A& P6 Z4 q  W
  43.     {
    % N% [# F0 t$ U( A; o
  44.         g_spiLen = rem;
    ' T( ?# e1 C& j/ M2 j
  45.         bsp_spiTransfer();, v2 {- n* D6 N0 |

  46. : W5 i2 j( x* p) b' {5 E
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    & v& X/ b. ^' M
  48.     }
    & e7 B$ V% W5 p8 D

  49. 7 Y  B+ E) _" z5 c1 c& f- x& I
  50.     sf_SetCS(1);                                    /* 禁能片选 */0 S: o- t! [" J; I5 O8 q& W6 P$ Z
  51. }
复制代码

. i3 F; t; `: U9 k$ X; |  _- b7 V
2 x# A* w2 x/ [这个函数对DMA传输做了特别处理,方便分包进行。. f- s7 q9 h1 q* X

# ]; e, `' w' t" q1 y4 K73.4.6 第6步:W25QXX的扇区擦除实现
! m: }/ ^6 Z5 r9 o3 @9 ?, ]4 L0 b2 X4 ?  ]
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。8 g7 \) q. }1 E$ N; ]

- ~2 J" V1 I, R
  1. /*$ o9 m4 ?# O, O( S3 t
  2. *********************************************************************************************************) e/ M  B$ g9 L
  3. *    函 数 名: sf_EraseSector( Y7 X  U9 l% O2 Q9 Z/ V3 a
  4. *    功能说明: 擦除指定的扇区
    / v2 _  I  `) z) b4 E: k
  5. *    形    参: _uiSectorAddr : 扇区地址* d8 ~" _9 }1 C) L2 q, j* a# K+ V
  6. *    返 回 值: 无( c. B9 @# E# v
  7. *********************************************************************************************************
    : j& e$ _/ M/ s. M" [+ E: j4 @* j
  8. */
    2 l# ~7 `( `: `  G
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    1 g! T! U+ l3 i  r7 @
  10. {
    ! z4 Q  q' _7 D& l1 N" n
  11.     sf_WriteEnable();                            /* 发送写使能命令 */+ x' T5 N$ i" u8 |% \/ W

  12. # I0 i6 [' D+ K) H* v& w7 g7 u
  13.     /* 擦除扇区操作 */
    " O7 D& U& `# E8 A. @& s
  14.     sf_SetCS(0);                                /* 使能片选 */
    : W( G! k6 r! a+ \2 Q
  15.     g_spiLen = 0;1 d: `8 P; f+ a8 ]7 e( P+ R
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 *// T' k9 r$ M" V- J+ {) O. R2 R" \8 Z
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */& Z: Z& Y. s1 b9 z  ^. `8 v
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */
    # B: q: [% q/ W/ I$ F, n
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    5 K9 Y; R1 O7 E4 }' W" ]' M: v
  20.     bsp_spiTransfer();
    ' {* e; R3 ?6 u, Q4 `
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    3 M2 Y" u  I) j$ `5 w1 f$ b9 l
  22. 7 j3 m& h7 t1 B9 F3 w) i
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    + J# x- k* u- d* T  o4 ~( G! F
  24. }: f3 f' @& e* ?, @4 N
复制代码
4 s& K4 e2 o6 A

) Q) N4 i/ q' A* [. d! a3 q( i整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:0 t. M2 h7 y8 N
) F6 b, e# Z* ^. ~2 A  _: M( p
  1. /*
    ! S( G9 F. F0 j$ ^+ }" S1 X
  2. *********************************************************************************************************9 E: W) h1 i" i2 x; H9 m3 `
  3. *    函 数 名: sf_EraseChip9 z+ s8 `$ c, U3 R& E9 r# f* v
  4. *    功能说明: 擦除整个芯片* c% R; q1 b& Q
  5. *    形    参:  无/ c$ i- ~7 B. n: c3 d; E0 N
  6. *    返 回 值: 无4 I* Z! I3 A0 }! _0 d
  7. *********************************************************************************************************
    8 c' H7 H9 K9 p8 Y* j+ A/ P
  8. */3 a$ R. ]5 F6 w. v) ^7 J- L
  9. void sf_EraseChip(void)
    " Q$ c5 E7 W5 V5 }" [' _
  10. {    9 e: n( d; t0 w$ ]
  11.     sf_WriteEnable();                                /* 发送写使能命令 */
    & m' \9 y& W1 i* g5 J4 v, M; }
  12. 2 H; k1 W6 A, @1 ]/ s3 `- c
  13.     /* 擦除扇区操作 */
    - ~$ L2 B1 W4 @! e. x. r" Y( N+ {
  14.     sf_SetCS(0);        /* 使能片选 */0 p! u' j3 G. R1 _3 a+ h. w5 Y
  15.     g_spiLen = 0;9 m- q" l2 k! K* t+ l& ^
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */" Y. [' I6 R- J! B4 S7 E
  17.     bsp_spiTransfer();; ], W  M& L9 j7 _
  18.     sf_SetCS(1);                        /* 禁能片选 */6 e2 ?  M3 T) q# l* R9 p
  19. 6 K% p7 I3 [9 j1 }
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    . P0 c3 K$ l8 B$ }# v, x) d
  21. }) e. r& j% Z; y" X
复制代码
- b: B1 Y6 W1 c5 o& J" X1 G0 {: h
73.4.7 第7步:W25QXX的编程实现! \+ M) ]* }4 K5 a2 }
W25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:4 o9 R% f$ w4 f5 L' b& ]

  I( p) C5 m5 t' V  V& {  W
  1. /*% d: }6 r& G1 b
  2. *********************************************************************************************************: s) s7 }* i; D9 ~2 C
  3. *    函 数 名: sf_WriteBuffer0 X+ T" G: U' \+ Q" k$ ]( J
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
    $ S+ s. p  @+ x: v
  5. *    形    参:  _pBuf : 数据源缓冲区;/ O- y) V0 b4 }
  6. *               _uiWrAddr :目标区域首地址, w+ F" X+ t1 i
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。8 N4 R4 V+ g  o3 ^* r$ G0 ~% Q
  8. *    返 回 值: 1 : 成功, 0 : 失败1 T* s( P; }  s  n6 \% u2 A/ S
  9. *********************************************************************************************************, b' C) V9 r: W! Y
  10. */7 L$ }* R! k2 S0 v& S4 }
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)8 l) B) K8 W! a  J5 k, {
  12. {1 i) w- @" S% G0 @  Y# x3 I' E
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    ; F  {* J" A; X- g
  14. ' Z2 B3 I7 @0 Q) D
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;
    " T4 \" Z0 L* ^8 G( t  s2 B
  16.     count = g_tSF.SectorSize - Addr;/ H) D- B9 x. K* W5 O% k6 f
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;/ G( o4 D- `; @' A2 n' X" j. ]- I
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;+ o' |! \# k7 Q  F1 I

  19. ! t: n- I: N+ }; c& l; V, D
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */8 F: u9 G2 m8 ^
  21.     {. W  F$ y( Y( T* \! W
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */6 x8 U; D, x3 X+ W; r( q" ]
  23.         {; v1 F% j* n& X, v- g: ~: t6 v
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    0 \2 ?) r0 }5 D( {% ~* R6 h
  25.             {
    4 e' ~. z: K& N
  26.                 return 0;
    $ c3 n5 I/ b! Y% W
  27.             }
    % m* f( L3 V2 [
  28.         }2 m6 @! ^; i; V- Q; t
  29.         else     /* 数据长度大于等于扇区大小 */7 G2 L3 _1 h9 W1 e; P
  30.         {3 z" J& f! p+ V/ p) Z% @
  31.             while (NumOfPage--)/ E  Q' r/ w% t, @
  32.             {" i! c3 _; T- ?' j; w- u$ m
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    ) u  _* `3 T5 w# ~" l5 \: ~( ~
  34.                 {
    # s5 W8 e. b  l/ }
  35.                     return 0;4 h  S9 p' Q" }' \# `
  36.                 }8 W0 {/ n/ ~# s) G+ L  ~3 q! f- X& X
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;6 V2 u7 ~/ _5 ]- f  j+ p4 Z
  38.                 _pBuf += g_tSF.SectorSize;
    8 F$ }1 p8 U  |6 X/ P
  39.             }
    2 B$ V$ X  j- p4 s
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0): i" O% B3 L! w& m3 y& i) B
  41.             {
    8 I# Q" c- |/ S# O" q, Y
  42.                 return 0;4 s3 n0 H* w* [8 L8 \2 q" j
  43.             }) U; Z* {  |+ q! _
  44.         }* k) n9 ]" M$ o2 i
  45.     }
    ; [5 D: G/ g2 ^) w7 t& E5 E% D4 Y
  46.     else  /* 起始地址不是扇区首地址  */! q  U8 s6 ~2 l
  47.     {
    " U/ `" I! ?" _, n" _" P
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */! l: y6 C6 F' }& Q
  49.         {( r* ?0 A8 f1 I8 F& B5 D
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE *// |, ^5 `+ S8 l' Q
  51.             {  j0 U' a( ^  g! Q7 q7 u
  52.                 temp = NumOfSingle - count;
    3 v- c0 B2 {7 @* ]* ]

  53. ! J) \- C) t4 Y: E, E$ }4 \. {
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)( D& L" M/ s) A  w2 `' d, Z
  55.                 {
    : [4 `) |6 U0 _, u& r, `
  56.                     return 0;$ m' p' z! p' E/ B7 D/ I* n
  57.                 }2 O; D. V8 L4 k9 I" D
  58. 1 p, @5 p. a/ u) |
  59.                 _uiWriteAddr +=  count;
    . d% q& ?$ V) v2 m6 v
  60.                 _pBuf += count;
    . k; y- P) m6 `3 K2 T4 o

  61. - R2 ^% L3 R0 v  e
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0); ~5 ?/ N/ n; X+ i3 ^* O
  63.                 {
    + w: t) D$ p5 i3 Y7 k
  64.                     return 0;
    2 {) Q! d* x% `
  65.                 }) I- i: K) |, x- X& ~) \6 F7 B
  66.             }6 H4 c6 d8 l  c7 t* w
  67.             else  h0 r: h4 x1 h! S9 d& B, O
  68.             {6 {  F0 |% @6 _. t% w3 g2 Q
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)$ f# C# a2 i/ _" e
  70.                 {
    # [# m& K" `7 X; S) Q0 I* i5 s1 L
  71.                     return 0;
    8 p3 Z7 E' }3 R1 i5 Z
  72.                 }
    4 A5 L! f2 |9 b; D2 j* t) e) D6 x0 z9 v
  73.             }  z. G" _' Q3 ~1 ~# t
  74.         }& Z: B% g; F3 d+ I+ v) |) j
  75.         else    /* 数据长度大于等于扇区大小 */
    & |5 \2 N. C5 m/ t/ R: T0 y
  76.         {. B' A3 Q& i8 p+ q
  77.             _usWriteSize -= count;# |6 Y3 ?& r: H( g+ e6 i" n, n  |
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;4 R, \/ g9 b, k) G5 o
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    $ i8 N2 Q5 q  r# \& S7 B$ ^
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    : h# V! v9 q* R0 p' L$ S
  81.             {' U2 W+ p' Y2 B3 f) D) [
  82.                 return 0;
    ! ?6 X& F* n/ ^, f) k: x: O0 N
  83.             }
    : G  s4 E9 Q, Q6 \
  84. " m) y# p5 Z" D  T! }
  85.             _uiWriteAddr +=  count;
    . e6 W% H% O* O
  86.             _pBuf += count;  M: [# q/ l: s+ z+ n
  87. 4 Y/ [& U2 \9 J: o! s
  88.             while (NumOfPage--)
    ' K3 ?( B4 B" p* ]  a+ ~
  89.             {
    - k# m: R3 ]: I# x. L0 i. `+ m$ S
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    ) j! z& Q0 a9 z( z6 }
  91.                 {/ ~* X6 X  q/ b, G! C
  92.                     return 0;" w8 C: l4 C" t5 P. A! C2 A
  93.                 }/ v# K; Y3 t" ]: {8 ?- P+ q
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;; y6 D- f1 O+ j! A% e* U
  95.                 _pBuf += g_tSF.SectorSize;
    7 M2 G7 J6 K, o( n. N; K: E
  96.             }8 _5 q3 H! n# j; F0 ]
  97. ' |/ I" b; M( |, i
  98.             if (NumOfSingle != 0)9 }( |- H$ k. P( F
  99.             {3 W0 Q2 I. r6 ]+ t+ w
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)6 Y$ }' |+ c, W2 ~! X. t! {
  101.                 {7 x* \$ w; j6 o# v8 M6 t  {
  102.                     return 0;6 ?$ k' O, D6 V  ?
  103.                 }
    ) E/ l5 H, o9 X7 @  n# J
  104.             }: ^' ^4 l/ N$ v/ U8 d" u1 t
  105.         }
    + H" r& }& W+ Z; R
  106.     }
    1 w! J/ L# x# g
  107.     return 1;    /* 成功 */& q/ I( N! q- A8 b+ J% g
  108. }
复制代码

4 `: R  N% _6 Z73.5 SPI总线板级支持包(bsp_spi_bus.c)1 t& n, ?( S4 J
SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
# r* w6 M; r9 X8 x1 ^1 o: o3 b' c6 h, H5 o" x0 e! u7 `
  bsp_InitSPIBus* m. Z8 H! e6 F/ {9 t6 P
  bsp_InitSPIParam6 u; T8 P6 i* ^6 P) ]
  bsp_spiTransfer
' w' g- o& F& Q3 r8 d! a73.5.1 函数bsp_InitSPIBus* @; x. v+ Q" K1 [1 g
函数原型:
7 Z2 F& W0 V/ w) [' ~% x" d$ ^- [
void bsp_InitSPIBus(void)
5 P; h: D* h1 m  t; z$ r& S; P  t, z  E! S% H
函数描述:
1 `4 F# J4 Z. ]; z7 h- f+ @8 C. Y8 K* M; j7 f. r7 C( Q& g
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
' i( n8 a% U1 G) K- _/ y4 c
0 P- p* L5 ~: H+ A1 ?5 ]* W73.5.2 函数bsp_InitSPIParam
) Q3 V+ ]9 B' K# R函数原型:
( T* E1 b, R" ?# y% t2 `) s2 Z2 u, k. Q5 |1 E' S
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)% [+ X& N  ~! g# |* ^

0 u% O2 G' r  Y9 |函数描述:- E% F* T9 t& r' x/ F3 C6 Z- Q  O
) }6 d  v) K! E& v. e
此函数用于SPI总线的配置。1 G& J* v( T# N

4 z  ^; p# h6 K- Y1 m函数参数:9 Y5 @# d* {& ]' I- g

3 ]+ P0 m. s8 E7 b$ b; S" k  第1个参数SPI总线的分频设置,支持的参数如下:. [/ b: A+ G6 _8 E
SPI_BAUDRATEPRESCALER_2    2分频- w# K& P8 U7 L$ z
: O' ?5 h! u5 U8 T  L
SPI_BAUDRATEPRESCALER_4    4分频, m& F. z1 W6 ]

& h' r1 r- D0 d+ j: O2 m3 n1 xSPI_BAUDRATEPRESCALER_8    8分频9 ~. f( `8 p- N! m

7 E5 ~1 x; A, q. o6 ?; q1 TSPI_BAUDRATEPRESCALER_16   16分频; K: k; g! J' E9 n! m
* q: y+ }$ x# ~2 R% V6 x' I
SPI_BAUDRATEPRESCALER_32   32分频
( G) N! c4 R9 q" y. X
* P* `( C  P& F% d7 }6 p* wSPI_BAUDRATEPRESCALER_64   64分频
- _& Q; G- T: w5 w, \6 G- W
$ n' i1 }6 }& O: u% K( N" LSPI_BAUDRATEPRESCALER_128  128分频1 C) n# |# q5 [5 L* Z
- t* }& m6 X) L6 P) B- P; ~4 c/ c
SPI_BAUDRATEPRESCALER_256  256分频
: Q% t, F# Z6 H" {
- e: q* R- V5 y6 `8 z6 _2 Q: n5 y) w, I  第2个参数用于时钟相位配置,支持的参数如下:
" h* z" V# M& l- S% {3 A) ?! KSPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据) Q0 ~  h2 e! `5 y; W

) @2 _& K+ L. e! USPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
; j3 g$ m! k- n- @8 S; W5 C4 q6 F3 F. n$ J
  第3个参数是时钟极性配置,支持的参数如下:
3 W3 c  x: P3 }; H' tSPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平$ t7 A, p% ~: v: l! n: ]3 P5 @- ~
  h! a! y! B: E$ A5 T' j0 v2 r- p
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
6 F9 I+ Z" v- k% R
8 q( \/ |0 }; U4 S73.5.3 函数bsp_spiTransfer

  W8 M9 f6 ?( ^) }1 t. B6 d5 Q函数原型:
- O5 C6 E8 t; g8 O! X/ n: o3 ~0 h
* f3 \8 s/ r" w' j/ F" Dvoid bsp_spiTransfer(void)6 [+ C9 Y5 }  r# M; d
# ]6 F( e9 e' r- N# R; M% t
函数描述:
9 U/ L; k) R3 H6 P: v5 y+ l* \$ `, h2 \6 x9 e/ m* X) u
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
4 L4 s& f3 ^8 f/ w( K* g  l. G) Y* u2 G; S. Z/ {
73.6 W25QXX板级支持包(bsp_spi_flash.c)& d0 b' K+ h: r+ Z
W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:2 U2 K2 C# c2 B* H( @2 E
- N; V# w0 V1 y+ `
  sf_ReadBuffer
/ f7 V6 |* ]- `; R, B  sf_WriteBuffer
$ _2 w, @& _1 [6 A) A7 |  sf_EraseSector  h7 K) R! ]6 N0 f$ _! |- c: x) i
  sf_EraseChip2 ]; u4 N; ?& d1 I
  sf_EraseSector! k8 J2 ]3 y' l  N) Z' x
73.6.1 函数sf_ReadBuffer5 _/ X# T5 K7 r
函数原型:, v/ s4 F% ~: }5 E9 A3 m

- q6 w- L" @% ~, C5 S$ X9 `void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize); a, q3 N# u1 b  p; }; R9 S

9 k0 a# `1 X- B* C  n+ R- z; M& @函数描述:7 Z/ P5 a: l5 Q. X

$ @  h9 l/ @% n! L' T2 W9 V. d此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。  Z* l* A4 W& X- }+ f+ Z7 G

: v9 [( l( {% K: `  l函数参数:
& h$ `# R! o- s% p; m+ J$ {' j, o' B8 v' X& M- l
  第1个参数用于存储从SPI Flash读取的数据。
: F3 {" i5 P. E0 m6 }: O7 F) _  第2个参数是读取地址,不可以超过芯片容量。; u9 n6 S3 B; d7 ]
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
2 n( a( t/ i: n% e% G73.6.2 函数sf_WriteBuffer(自动执行擦除)+ G5 n, g4 {1 J3 ?$ B5 q: E
函数原型:
, }2 Y+ d8 z3 B, V
( A* i, x7 `3 J. k* auint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
9 e& B" p4 [9 }8 }2 l+ h1 Q) ]
, \8 H5 h6 P" x0 Z  V函数描述:
) b; Y; e! q9 p- G/ X- M) M* p6 n2 D6 T9 p/ D0 @/ I- p
此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。
: V% _( d9 ~" [/ m& E* l5 c0 ~
9 j& E; b/ w0 m, p: [" U函数参数:
6 }$ v6 P. t5 W& `' R) c* q1 ^8 B: y: v' z3 @
  第1个参数是源数据缓冲区。2 O+ o' i- Y. L
  第2个参数是目标区域首地址。
8 ~0 z' O  j! }4 M- g  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。5 u- {  J' b5 i4 _% C6 H# ~
  返回值,返回1表示成功,返回0表示失败。) U  f! V+ ^  C" L
73.6.3 函数sf_EraseSector6 b, P: v. k8 S( k! X; k
函数原型:
+ f4 c+ Z! I5 K* ?" }' c7 n- I
" ~* n: D5 i* E- o2 C. e$ L7 fvoid sf_EraseSector(uint32_t _uiSectorAddr). D1 k1 Q$ d4 v  f8 p
- ~+ G4 C) Q$ `2 Q2 f7 u
函数描述:* H4 }& G) L* ?4 |5 d! {7 I

% S! R1 p2 ~7 j% z$ r( ^& N9 S" n此函数主要用于扇区擦除,一个扇区大小是4KB。
9 j* m% O" k& e& ]" h$ T% q6 T6 R# ~3 P
函数参数:
! s* B# U. L6 O5 T' V" G
. ]8 ^2 t% E; n  C8 I9 \2 z  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
* ]7 G0 p  W7 u# M% h73.6.4 函数sf_EraseChip1 M, N& L( D9 m( s
函数原型:8 M9 [. Q7 ^' T- M/ H2 a, y
; c$ l+ s9 O) }* u
void sf_EraseChip(void)% W1 n) B7 E1 m  Y
- g& H, W: ?* I3 p" n
函数描述:; Y5 q. `4 Q# N" ?3 o
4 y$ b, V: Z* o' P7 Q
此函数主要用于整个芯片擦除。
, h" C6 k9 l6 L7 B' y) f8 \, i6 }  \, G( @
73.6.5 函数sf_PageWrite(不推荐)
9 x6 v$ B& s, H5 K. j- a/ `6 u函数原型:9 \+ E# `, |: L5 i4 e

5 ~) l1 j& G8 pvoid sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)
0 M! m2 Z% E8 h9 B0 h
: v7 u5 }, @$ ^/ P( Q& Z& X函数描述:
. [; j% l$ s8 b' T$ N1 I7 C
% r# o# S# q$ }' ?此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。
5 k& x1 T# h0 m4 t/ O  |9 Y. [1 @: _" O# r
函数参数:
% }' @2 V) j1 r# j# a; Q/ D% m- @
; H2 C0 e) j$ u& M6 r  第1个参数是数据源缓冲区。8 m) ?0 g4 ^0 l$ \2 A) [
  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。9 l! O& l/ @& C+ d) `7 [5 b0 p
  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。9 k4 }. F: [7 [+ F3 _$ H
73.7 W25QXX驱动移植和使用, n5 b+ |  R6 c* i2 Q
W25QXX移植步骤如下:
2 e& Z1 l) g( }; }+ J$ \6 _
& V  J6 o0 g  s  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。
' K3 k, Z- z8 U) ]* H  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义2 r+ o" T/ Q" B7 y7 N! J" j
  1. /*+ E2 ~5 A2 u# z
  2. *********************************************************************************************************# R* n3 `, g1 t0 ^
  3. *                                时钟,引脚,DMA,中断等宏定义
    ! \. Q! E' E+ i7 n. n
  4. *********************************************************************************************************
      E9 @7 \, b' m: n7 B- G* I# u5 Z5 j
  5. */- E6 B9 j, v  |! x- f2 K; t
  6. #define SPIx                            SPI1
    9 F+ S3 L2 D& D  x/ C* K
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    - v+ E/ k" ^  ~' G" R( {3 E
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()
    / [* ?. r9 |( D7 l+ G1 \

  9. ' p' {0 x: [) R6 t5 p  o, W) n% r
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
    - d( ~& U4 l0 J/ ^
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    2 m6 j' `  ^3 p' J" Y- _  A

  12. * l" R. i1 s( E: J" @6 ]
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()( U6 A: d8 U* ^7 `: o) E! ]' T
  14. #define SPIx_SCK_GPIO                    GPIOB: V" o  q% c) F* X4 [
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3
    # i( t" j0 H) a
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1; ^, x' Z2 U- T  ~5 X

  17.   |. s" A' F2 V- v# [$ C8 B' m
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()3 U; |" z, q* H5 t% k5 H) y
  19. #define SPIx_MISO_GPIO                    GPIOB
    4 b  T1 ^% g: [7 K+ B# v
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4; e% |# t! E  \7 y8 z
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI19 [* k' ]; G1 s6 o- @

  22. " l( H& |3 S8 m4 i3 N9 ?
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE(), W' X! I  P+ Q. t% U
  24. #define SPIx_MOSI_GPIO                    GPIOB
    / P- \9 D& @' J9 |1 x
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5
    % k9 ]9 ?8 _, G' V2 a0 D% R( t7 o5 s
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1
    9 v: Y- \( K/ @# n& n3 m% \% F. |
  27. 7 |2 T. j  U; |+ T1 j9 a+ d
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    ' @& `* Z. M1 B- ~8 N5 D0 Z4 l' z
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream27 X, ^* V0 r% L/ |
  30. 8 O6 T2 Z* r5 y: h  y( Q8 w; M
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
    % Q6 b1 L+ ~! s8 f7 h
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX* t5 O1 [' T7 e( T0 L; Y5 ]7 N

  33. 7 L, k/ h3 M: i# d
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn& y: j$ k- `3 w2 I3 X
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn
    2 [- B& h2 m5 [) f: r
  36.   O" E. I7 @, |; k- M4 p
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler0 z2 r" f9 W0 U; F  ^
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler
    - o: ?! n3 `( g4 K. q+ i/ w2 K& O

  39. 9 S4 w# C1 |9 E, b6 j( U: c. k
  40. #define SPIx_IRQn                        SPI1_IRQn
    ( K, J! \1 S1 N
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler8 f; e9 v, I. {2 l
复制代码

! A, P+ A! x& y, j' C/ P7 D2 ?) x+ @9 o
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
% X: O3 Z) x" d) w1 O
  1. /* 定义串行Flash ID */8 ^6 ^6 s/ W+ n( l- A
  2. enum+ s. G/ Q  P4 ?5 _- d, n5 V
  3. {$ n* L/ m$ e, b6 f9 P" Y, x* R
  4.     SST25VF016B_ID = 0xBF2541,
    1 J# @8 B8 D* _! o. ~) G+ ^
  5.     MX25L1606E_ID  = 0xC22015,
    % x, L" E* I  T6 G7 @" ~) J
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    3 k1 Y" _2 q- F
  7.     W25Q128_ID     = 0xEF40187 P4 y! d! s. N3 O9 t- A3 m
  8. };
复制代码
* a' ~7 h8 W' t# P$ B. J& Y. C
  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。: F* o8 a" k! D3 L" [
  1. /*
    2 S1 b+ ]7 {! e# }0 W( s" ^7 f0 t
  2. *********************************************************************************************************" q) l- E! B+ H6 o
  3. *    函 数 名: sf_ReadInfo
    4 `. @7 J; B/ w- l* L; k. j
  4. *    功能说明: 读取器件ID,并填充器件参数
    / g1 q* g7 R/ W  `+ Q3 @* P# O
  5. *    形    参: 无
    ) S) D6 H0 e5 V. M" S# [9 o
  6. *    返 回 值: 无
    5 k' G$ @% g" g; ?
  7. *********************************************************************************************************
    . \" Q5 {3 a1 y2 j! n
  8. */
    5 x! N6 }7 |% x! M8 z7 `6 G
  9. void sf_ReadInfo(void)! P  \& V: F+ c
  10. {
    1 r, r' j6 Y% `+ x4 S
  11.     /* 自动识别串行Flash型号 */
    ) r; i. H  f# h$ a) \0 w3 T1 D
  12.     {
    + {  B2 m. m( M
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
    3 _9 b0 _; `5 z5 d& ^
  14. - @0 p1 i2 ~2 ~$ x4 g4 b
  15.         switch (g_tSF.ChipID)
    ! d7 d; t# B8 z# @7 k; {6 c
  16.         {# Z. o' T5 x, P  e
  17.             case SST25VF016B_ID:
    * D% z% F+ _' l! P
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");; |, x! R# T" \$ E, A) t# w
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    ( d0 }+ @* o3 d5 K) C0 ~' h+ O- p3 b
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    2 O; w; R' g3 a
  21.                 break;
    / _& N7 E" n8 `
  22. & N& A8 R4 F# w# d. V
  23.             case MX25L1606E_ID:
    0 O: X  V# t/ X
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");/ [0 E. a! d4 d# u, \: }) H
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    5 h/ U8 x7 }8 V* m3 U" M
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */' h3 l. z9 ?- g& P8 ]& p
  27.                 break;
    , v- z6 r7 C0 [, p' G. J( v

  28. " ], N; ~/ D3 f8 R
  29.             case W25Q64BV_ID:5 t* ]) ~. l+ {2 r( {, H* \
  30.                 strcpy(g_tSF.ChipName, "W25Q64");
    ( J  Z! _+ w  a  T& X' a! E. j
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
      _$ e5 P6 p0 S
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    6 {% {/ }0 E2 o7 l. s
  33.                 break;9 V3 a& k7 ?% @- F

  34. ( A. n- ~. p, \; g4 w) Z$ o
  35.             case W25Q128_ID:5 @' ^  p- R. \9 a- S/ [
  36.                 strcpy(g_tSF.ChipName, "W25Q128");
    ; f9 L2 P! d0 W# \8 b
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */; _% s9 \4 q1 a$ U
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    4 U+ g2 I3 P8 x9 U( F: C+ z
  39.                 break;            
    % {3 b; z6 P7 j$ Q, D9 ?7 n6 \# q

  40. - V: ^) Z% d! F9 O3 G7 c8 w
  41.             default:
    0 j* Y+ i* {" _' m
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");. `  H! a; _6 o4 p0 L
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;8 i" P* q$ O$ a( ~% R. ?. |( M/ m
  44.                 g_tSF.SectorSize = 4 * 1024;* b% ]1 S# x: s6 I  B
  45.                 break;; G& \& M4 W  |% p: [# Z
  46.         }! w" i8 d& s) c0 s) R- ^, Y$ \1 l
  47.     }- r! W3 {; p" ]! a8 ]
  48. }
复制代码

0 A: K$ p' s( K' V/ r, ]( @1 W  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。; z% h% M4 [$ R
  1. /*& W; f2 X2 }' Y! Y: Q/ I
  2. *********************************************************************************************************
    6 x9 _3 z7 l# a. W) S$ `8 d
  3. *    函 数 名: sf_SetCS4 ^% `/ D+ T) P
  4. *    功能说明: 串行FALSH片选控制函数
    7 S1 ^/ e" o% r' c
  5. *    形    参: 无4 G$ }+ D) F. I* Z; Q$ I7 U
  6. *    返 回 值: 无0 u# l* N7 Q- Q9 C; n0 ?
  7. *********************************************************************************************************
    ' h/ J4 K5 j3 J2 D) j) C
  8. */6 G$ m9 ^% F& ]$ l" H
  9. void sf_SetCS(uint8_t _Level)7 t2 ^! j5 [9 y+ w- L. O, O, V) _2 _2 e& M
  10. {( B, \  z, F7 w4 C. r
  11.     if (_Level == 0)
    4 L( X  u7 R1 L) {7 w; r( ^- ?
  12.     {
    ! X8 {- Q  r7 g  U% p! a
  13.         bsp_SpiBusEnter();   
    ! I' o- ?+ L: L2 c# }
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        % H" V; c* s5 P4 S9 o# v/ Y
  15.         SF_CS_0();
    4 W! c- {  d7 J/ x0 x: t
  16.     }
    " |) e) N1 ]9 u3 d, Y  s% j# c
  17.     else
    - ^* f( T1 \4 l) o% V
  18.     {        ' x. R) w& o: y0 s
  19.         SF_CS_1();   
    $ Z  u9 t8 |( R+ r' G, |! r) x
  20.         bsp_SpiBusExit();        
    , S  T+ B$ ]' O. U6 [
  21.     }7 B3 X) @8 E, I. D4 B4 K+ o
  22. }
复制代码

* ~. k/ N2 @+ J7 P$ i  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。+ l: Y* i- x" m+ U, k. l
  1. /* 串行Flash的片选GPIO端口, PD13  */6 N- {& d& h2 [1 r
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    + y* }( P. S, ^; T( `
  3. #define SF_CS_GPIO                    GPIOD$ w6 _% B$ y' [" N9 x- B9 f: [  J
  4. #define SF_CS_PIN                    GPIO_PIN_13
    ) y% D  o* `0 I6 \2 l0 m
  5. 6 J3 a# i" e4 k/ |% X
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U)
    . p2 ~3 F1 Z& p! X1 f% ?
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码
3 L4 n6 Q5 l2 C# {
  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
- b# G$ N7 _# ]7 `5 R
  1. /* 配置SRAM4的MPU属性为Non-cacheable */& C3 {! |2 ~2 C, |6 _
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    1 b+ {( j3 S! C) c$ Z# [0 E
  3. MPU_InitStruct.BaseAddress      = 0x38000000;1 I' W6 a4 T7 [
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;% q: E! X4 f$ y
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;. y9 Z' Y5 ~) a; Z' x* O
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;/ `; Q% f% {8 L/ h0 q- u
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;3 |( ~1 v  Q* w  [! g& B- _7 v9 L  @
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;  M  I7 Z& \8 v3 i9 Q; |
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    5 O8 k0 }7 B; k3 s+ y6 h7 n
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ; ?7 g( r$ V1 f) c4 i
  11. MPU_InitStruct.SubRegionDisable = 0x00;
    2 B7 C; u2 L7 [( \
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    " f1 A8 T$ a% N7 T/ k  u+ @; C

  13. $ `. W! C/ _  d, B( u5 Z+ Y0 }
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

) F, Z1 d- l0 p; ]  第8步:初始化SPI。
$ r. V' L! Q+ t+ l7 H% a2 I  i
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    6 z+ P$ h9 t7 D2 @
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        9 T/ _; }! ~8 @7 B4 v& a" ^1 ~
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码

. M0 c4 @' ^# `( y, h- e  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。6 j- @1 a; w$ D* W& ^2 ^1 \: t
  第10步:应用方法看本章节配套例子即可。4 c/ `7 f6 L4 y
73.8 实验例程设计框架$ d' S+ D3 V2 ?, a! b: N
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:% [) T5 ?! m2 Y- n& D2 m

7 B8 o) o# ]9 L
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
6 \! m2 G  Q8 B( x9 a! q

1 k+ h9 \7 w0 e2 N5 |1 J& T" N4 A6 n  第1阶段,上电启动阶段:
! z7 z" v1 s& ?) C" O
8 m, I5 C+ m1 v& S这部分在第14章进行了详细说明。8 c. v! R+ d6 u3 P' V. P8 v
  
1 A+ s/ o! M6 J7 \第2阶段,进入main函数:
& N0 \$ W2 r' Y/ [; g+ J) [) ~! ~
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
8 |, \$ V( j! D: J' F% k, ^第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。6 k# }) M. p. H% i

9 ]" v% i: {  \73.9 实验例程说明(MDK)% W+ y9 P2 C) n9 v
配套例子:  g0 V! G: }) I0 e! K3 P; _# _+ j

, M* b6 z( Z9 r! jV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
  R( f8 B4 F; m" K* b: b3 r0 E7 B3 M: Z4 d& l
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
- Y6 |- Q, [) b( f( r$ {# R" n* l, g1 O1 B8 b1 r
V7-051_串行SPI Flash W25QXX读写例程(中断方式)" ]' @% T+ ]$ D* V, c! S

  j& U/ D! \, m* w1 U8 j7 F- D实验目的:' t2 |/ p8 W. _* z, u) s5 B% D

" {; L% ~, l6 i+ o1 J, i9 u" l学习SPI Flash的读写实现,支持查询,中断和DMA方式。1 |6 ~+ a( c: K; M0 e  k( t
" b+ _7 b/ e9 s* Z9 O
实验操作:
. R; A- h1 _  o9 J3 a$ b6 q4 W1 \) u4 W5 s: g) G
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可. r3 j' Z* |% d7 c3 b3 z3 o
printf("请选择操作命令:\r\n");
( J# o8 E" U$ aprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);" {2 F- ~$ G) {( n& D) Q# J
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);+ q2 M5 b" q* \, R% [- y5 A
printf("【3 - 擦除整个串行Flash】\r\n");. q7 B9 c" G  V& u5 Y2 b! ^
printf("【4 - 写整个串行Flash, 全0x55】\r\n");' w5 N$ N0 _3 p/ e, i# Q  a) }
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");
, @) @& ]" l0 ^7 \' _/ iprintf("【Z - 读取前1K,地址自动减少】\r\n");  P$ c" Y1 `* E3 \- O
printf("【X - 读取后1K,地址自动增加】\r\n");9 P& r; s* I5 e9 I5 p
printf("其他任意键 - 显示命令提示\r\n");
" P9 \' J+ s* i) s+ E! I上电后串口打印的信息:
! G% l* V" W- ]4 ^# |9 M7 I# Q6 d  U+ Q' D* d* w+ K- M* t' d
波特率 115200,数据位 8,奇偶校验位无,停止位 1。8 q+ e5 m% M6 [7 O/ z# {' w

$ V! Z( O9 Q  E4 p
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
/ C2 j0 e5 Z9 A: B$ o1 F! t3 _

# N. P# b9 ~  d' p' `程序设计:
! u7 {- y" @  r4 Z, h4 m, P
9 a" x( P/ S2 A  ]+ K5 o8 d5 A, t  系统栈大小分配:
2 {2 u8 S5 l9 Z3 \6 n  Y2 R2 Y- L, O* W$ G0 f+ n
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

/ @* u6 l+ U/ P% [5 d9 p( `/ w. [( T* B1 _$ j5 r0 ~7 ~: c
  RAM空间用的DTCM:  G4 f" J8 d3 u1 ~
7 B$ I) W. x9 L; V, {# O+ I+ X7 _
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
* Q9 x9 z  k; r# [; }2 F1 i& ~
% v1 e1 n" \; ]1 e
  硬件外设初始化
* s: k# t/ m) L/ Z/ ^
$ [- V8 _, Q3 D( x' l/ f3 t硬件外设的初始化是在 bsp.c 文件实现:
1 A1 a$ N5 G0 f* B' S& P* [# s% W. x
0 c; M" [1 E. Z: Q' ^
  1. /*
    * h* a6 `- h# j
  2. *********************************************************************************************************
    & \. Z/ u$ p+ g# v2 I% G
  3. *    函 数 名: bsp_Init" e/ ~3 }& c  m4 Q  w$ b+ @
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次5 Y7 P" z# X, y. J, I3 l* W
  5. *    形    参:无
    * p: R9 m- h" Q: q" K
  6. *    返 回 值: 无' Y' f0 {$ d. z9 I' w. t3 h
  7. *********************************************************************************************************
    ) |  x: H( S4 p6 `1 E9 ]
  8. */
    5 p3 w& S" j0 D4 u% X8 k  \
  9. void bsp_Init(void)) c9 ~# X4 O5 b* W- \7 B5 K  ]
  10. {6 |: D# {3 n, `
  11.     /* 配置MPU */
    2 [9 }: @% I9 z7 k8 B# W3 [3 H+ v
  12.     MPU_Config();% u* B  L$ E8 L3 P
  13. ; ~- u" ^  V- k  W7 G- j
  14.     /* 使能L1 Cache */# R. ?# b; S; `5 A. N
  15.     CPU_CACHE_Enable();
    ! X5 M+ }# U( @% i

  16. 4 F/ w1 s- F, H# F' f
  17.     /*
    & A& |5 s& X* M( b: q" v8 \
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:5 l6 |0 v1 I; d0 ?( R) Q& P+ C
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    8 y" T. N2 g2 Z1 r7 x
  20.        - 设置NVIV优先级分组为4。
    ; ?2 W& r! w7 |, o$ `8 t; G6 {
  21.      */8 J4 w+ f) M7 F# K
  22.     HAL_Init();3 `4 o0 C& x  J

  23. 8 N! S( i. F3 G- g* }  w! w
  24.     /* 2 U" \* g2 F9 V0 J: V8 k" Q" Q8 _
  25.        配置系统时钟到400MHz& k( D" M3 a/ L. i* z: m
  26.        - 切换使用HSE。( U7 n' N! u# L9 ?3 y2 h* D
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。6 p- P1 g& e+ Y0 P! m" ?
  28.     */. h& B4 |( _, a% ]: X
  29.     SystemClock_Config();  `- d! S+ _( Q7 n$ }5 Y/ g

  30. : W' v; N7 L6 v' q- _
  31.     /* ) F1 c! q; D. o. V+ w5 z2 q; X( @6 h0 c
  32.        Event Recorder:. m; b# O1 k* A
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。& F' ]* e+ U# b
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章. g( M3 ]8 i6 f! G
  35.     */    . R7 i: v: J! Y- \
  36. #if Enable_EventRecorder == 1  
    9 D' C& T6 z9 m6 f5 G9 {
  37.     /* 初始化EventRecorder并开启 */, W  [, C, _& j
  38.     EventRecorderInitialize(EventRecordAll, 1U);8 g4 k! y2 F5 o" G: G4 N
  39.     EventRecorderStart();2 W1 e; J" G1 e* p0 a
  40. #endif% Y, ?; d9 [8 U, }, I4 K

  41. : n* n$ J1 g  j: p% U
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    ) H8 l$ |. |! d+ V% ^9 L
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */" ~! t4 a/ s5 z
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */3 I$ S+ [) F& Q& A2 m4 j
  45.     bsp_InitLPUart();     /* 初始化串口 */+ q1 A3 ]3 I/ W, q% d: Q; n/ E
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ) _8 e% v6 t' ~% @. }
  47.     bsp_InitLed();         /* 初始化LED */   
    9 ~7 k  N9 @8 y
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */+ [5 W2 w5 @& }" e; S0 j

  49. ) n. O4 ?" i9 G# H8 k/ E* k3 I$ U/ r7 T
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
      E+ ~; V9 x/ Q$ m  A
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    + l* y6 f, ~! C; |8 \4 c, ?' P7 |
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */" e5 L6 i* J; q% X' z" \2 _, z2 ^
  53. }
复制代码
3 e0 \; p5 Q: v9 ?1 f2 V
  MPU配置和Cache配置:
2 I  m- U. S/ @6 X& ?# a- L* Z6 W$ z$ y' e+ K
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
- m! U6 C3 y. x
( z: F6 Y% L; {+ }0 q) p$ w$ A
  1. /*0 {, _0 R5 }* F! a( F$ m
  2. *********************************************************************************************************
    / g0 X, m, f% b/ i& ^
  3. *    函 数 名: MPU_Config5 w; s# c. D: p& W
  4. *    功能说明: 配置MPU
    * L6 o6 z# q8 c- V6 ~
  5. *    形    参: 无4 x# l; e2 s7 _# ~$ D
  6. *    返 回 值: 无' u. K) X: e  J  Q
  7. *********************************************************************************************************
    , B1 q9 ^7 q7 a' S7 P' P! ?
  8. */
    3 x! O& ^& v2 h. \7 Y
  9. static void MPU_Config( void )! n7 i4 N& a0 b1 M& }4 o. x
  10. {
    % M2 s' s2 Z4 B5 _
  11.     MPU_Region_InitTypeDef MPU_InitStruct;1 ^( D8 b0 T8 E; p$ n) X# L8 [5 }2 I7 _  E) o
  12. : s# J5 h' j$ `4 q+ B' X8 p
  13.     /* 禁止 MPU */! x( L" u8 }2 H7 x
  14.     HAL_MPU_Disable();: ^* p0 o2 ?* `! }) d. v

  15.   k# ]% d* e- f7 o' C
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate *// Q  x! A& M# I0 X  P
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    3 J! k3 @( v( ]# Z; z
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;) n* D4 _- Q0 o6 c2 Q3 k8 x9 L
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;  c3 B& r* J/ y& r2 w- j
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;. C7 `& x# _4 ^) s: x6 t% T/ b
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ; ^1 S6 [$ X0 y' b" V$ M! m7 Y
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    7 z8 j; X9 b" |$ K( u- J
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;1 {  D" r% ?( Q) t4 ~
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;5 o9 k- U) H8 ?3 j- X$ z1 Q
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;. @: I( f3 I; f& Y* w7 ~( v
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    5 ]/ K8 q" H. t2 n# ~  F
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    + P9 v2 ~) W' R( \7 Q5 }8 G! u

  28. 9 J8 E% X5 Q/ C5 q$ z! r+ q2 i3 k
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    / l, v# N' N0 F, `) ?

  30. * m7 ~0 l9 p( d

  31. 5 T2 {7 P2 \" s" @, [3 Z+ [$ @
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    ) j4 n* C% n( |1 z% `; B
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
      L  Z+ u& O) A+ s1 J- J+ J
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;/ }7 A5 P3 \# Z( N; _) A* {
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    . F% j; v. w$ H  a
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;2 W, X& h# d% J4 l$ m' L
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;+ n( p2 P1 R  Y- w: ]! C
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    6 f; N5 j5 n" ?* P1 G3 Z4 E
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;8 M9 |2 l, \, h, c1 n* s
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    0 s  I. R+ h0 k8 A% b+ r3 ?; U0 K
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;9 a4 Q2 h- B4 F4 p! B/ H
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    ; p2 B* Y% _' _# J7 P" I+ ^
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    & ~; O+ @, U& i" S/ r" i8 B

  44. 8 B+ C) a7 o/ j2 a9 z
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);" `4 }3 T) l* e
  46. ' z. i6 c- |4 ?4 I% P5 t/ L1 X
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    : b# \  [4 j0 N& x  p2 Q
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    2 I+ p, J/ j0 J" T" X
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    & p$ ]  ~& a- I" y- S. H7 T1 b
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    3 u1 J5 {4 Z, v8 Y, g* @
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ' r* h+ [+ Z+ T5 F
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ) f1 z& q/ C7 ~% v
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;! k- Z! Z: i) d
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    4 H, J3 X4 d/ y7 e* F8 F5 d
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;* `; V( U7 x$ F
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;+ V. G& b7 o# O. n' I. Z
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    . H2 `2 X. v. q/ z) x& w" g" w% Z
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    4 K1 @" A: _: T

  59. ) G+ A  \( [4 c8 l$ `- e
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);8 f* H  h. L. f) w5 t

  61. " l+ d" j$ L% P- E: y
  62.     /*使能 MPU */
    % N1 ^* Q5 {0 g" e  x* }4 ?
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    6 x! {0 z9 e" f2 P6 L3 t6 y
  64. }/ ]# K! W$ Z2 a3 c* k( }# E
  65. % V% X- e( x5 k1 y8 ~) o+ K% T; H
  66. /*
    7 q5 p: z; R, c8 _/ b( s
  67. *********************************************************************************************************. v: ], n  _% D
  68. *    函 数 名: CPU_CACHE_Enable; T; X+ X, S0 p- R. r/ c4 M
  69. *    功能说明: 使能L1 Cache- {8 d/ K/ Q8 A; `$ |1 e# o9 J. [& f
  70. *    形    参: 无
    $ r9 Y9 a9 U4 D) ]* w5 ?1 Z7 B
  71. *    返 回 值: 无
    7 G, |8 K- w4 C  K* X+ C0 G
  72. *********************************************************************************************************
    1 [! N) j2 P' n/ Q5 i) ]. s
  73. */# r4 o% _  i+ f
  74. static void CPU_CACHE_Enable(void)9 W5 }6 u: B# v) i4 u% {2 J4 y
  75. {0 u8 g( V) \. A
  76.     /* 使能 I-Cache */
    5 @2 B/ v* A6 ~/ I
  77.     SCB_EnableICache();# ]( k& X2 F/ e! e0 u

  78. ' p7 m/ G" j- K6 @6 F# {8 P- Z
  79.     /* 使能 D-Cache */3 @; i! g5 W. c4 K
  80.     SCB_EnableDCache();8 U! u' T5 K; {, ~( U
  81. }
复制代码

; L+ A- D$ ]5 A+ W  o4 ^# }  每10ms调用一次按键处理:
/ W0 ^+ k6 z: D% d8 |$ i9 X* ~4 P4 l; C1 ^
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
4 A- n- w; s; r# w% Y3 q( G
" g) F# y" A% O; i- b  K1 S% r: }% O
  1. /*
    ; z& O" e) n% o. l
  2. *********************************************************************************************************! O0 a/ J5 U* u, \. x
  3. *    函 数 名: bsp_RunPer10ms
    + y8 _- u$ j! Y( [
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求, F) w* M, G* c! v$ y
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    2 r" l' z6 E$ T# m
  6. *    形    参: 无
    % q4 j1 v: ~" z
  7. *    返 回 值: 无
    / R  X' N' |- R6 `9 ^1 e& q- m
  8. *********************************************************************************************************/ x9 H7 d3 _. I. m  I" r2 s" G
  9. */7 `9 O  M$ G# V0 f7 C8 x# @6 o- }
  10. void bsp_RunPer10ms(void)
    ; w5 m6 D1 V: k% b# M
  11. {. m! I% @* c% {
  12.     bsp_KeyScan10ms();0 L; A0 b7 m  b4 E( j) @# X
  13. }1 f: H! z9 q) y4 U* r7 s9 h
  14. 3 }2 G3 ]2 N* Z1 y! c
复制代码

* G: x. K1 A: ~0 g  主功能:( F/ d% _$ {5 R7 D) A2 L, d) r7 B

2 y4 I+ N! B0 P" E; z主程序实现如下操作:
) N8 j4 D5 }" i- @+ s9 F5 @4 L+ z! N$ W) n
  启动一个自动重装软件定时器,每100ms翻转一次LED2。( V2 I5 L3 ~0 z* C5 U- U
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
* n. y, c" j; O4 y  请选择操作命令:
" S* K$ T5 E  U' N7 [6 y# d  1 - 读串行Flash
! e$ s% B" C7 s2 r  2 - 写串行Flash
8 i/ o  w, {$ N! m: ?  3 - 擦除整个串行Flash
. _8 o5 W1 T% y  \8 L5 `  4 - 写整个串行Flash
% D; N# o! [% q, N0 `  5 - 读整个串行Flash  s9 S9 z( G8 h/ c1 u7 n+ u3 m: W
  Z - 读取前1K& ?8 a! y7 D# O; r8 q* Q
  X - 读取后1K
5 a' A  S& Q0 r( M1 ]  Y% X% I
  1. /*+ m6 s( M* g; _$ B; ?( O
  2. *********************************************************************************************************
    7 C1 F5 R' T7 j' G
  3. *    函 数 名: DemoSpiFlash
    4 H) {% Z4 w3 a- I; F0 i
  4. *    功能说明: 串行EEPROM读写例程
    # }1 I0 z+ _& t
  5. *    形    参:无% x) y1 m* x5 Q/ d% Y+ x& [  @
  6. *    返 回 值: 无
    - [  `; K$ `0 e1 H" ^9 t' \
  7. *********************************************************************************************************
      _* N% {9 U3 }
  8. */" n8 k& Z7 |9 [' Z
  9. void DemoSpiFlash(void): z  F6 d7 x1 O4 b- o7 f. y3 q  Z
  10. {$ G- D2 Q% r0 \% @, T+ [
  11.     uint8_t cmd;
    . P/ {4 K* v4 F9 r3 y0 @
  12.     uint32_t uiReadPageNo = 0;
    ' M. L# j* f6 l2 m: s5 Z7 ]

  13. % e) b( K/ M% T$ i- y" j

  14. % |% {3 C; d9 T
  15.     /* 检测串行Flash OK */
    + d+ J0 D, A  T9 x
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);& T8 ]2 w* T! h# o+ _- s
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    + _* C) t' L# Y2 ]  n
  18. - \0 }7 U1 y* e5 o
  19.     sfDispMenu();        /* 打印命令提示 */% m2 p# @# n' C. u7 f4 M5 d% {
  20. $ A- g# J  {9 s3 {  m
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */' [, x8 x( H0 g/ I

  22. * u0 }& T6 i" {5 y3 a+ b. z7 t
  23.     while(1)4 b# N# y7 k: V4 ]' |8 J% j
  24.     {
    $ @. [- `7 ~1 @& H
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    # V% x" e  p  W% j7 {7 R- [8 U4 K8 V

  26. # y/ q5 g% P2 f9 }* |# b. }( ]
  27.         /* 判断定时器超时时间 */
    4 x: @" `; [* Y0 ^$ h- z' b% [; v
  28.         if (bsp_CheckTimer(0))      e. S* ]/ I3 W
  29.         {
    # C! t) v" o% Y  q) y
  30.             /* 每隔100ms 进来一次 */  ! R( l4 X# m, a2 c. W; ^2 {/ o
  31.             bsp_LedToggle(2);1 ?4 i- j7 Y' G
  32.         }* o3 G. W3 @8 F9 |

  33. 2 [0 Z4 s& o: R7 u$ F" G
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    , Y8 B% n7 `) P2 z
  35.         {
    ( c1 v, R. p4 g; i$ o( X' C8 i# t7 ?
  36.             switch (cmd), Z; h3 I: v8 W7 t6 k
  37.             {
    * l$ |2 s2 _/ ]; K! V
  38.                 case '1':% G4 v  f2 P: ?- |% Q: \! ^2 Y
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    4 f! j/ m# Z7 R# T
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    % y/ \6 r- J/ d- G4 t6 k$ p' E
  41.                     break;: T& n7 W( _5 d3 j: O$ B' Q
  42. $ z; F) d: e* O& z, x. j/ i( A
  43.                 case '2':# c) m% G, A$ G, _
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);" i8 w" P6 W* \8 L6 o' y: ~
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 *// k  [; p$ b& y' |  o0 E3 z
  46.                     break;
    0 J; J$ u5 _6 h, ^5 R& c

  47. - G$ u# d( ^( Z3 j$ H- p
  48.                 case '3':
    - _; m% \* f. W2 O, L* ^$ @5 n, R
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    1 u* @, G$ l' ~5 S5 u
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    9 k6 y  Q( }( N; m1 `: f' Z7 y- ?( T
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    % c8 A4 E( i5 G5 {. Y$ C
  52.                     break;- M- Q% t7 k# r: G7 i. l( e

  53. 0 D& m3 i: [8 w1 h
  54.                 case '4':
    8 i# K2 p* k+ b+ ?  v! r
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");  Y7 H+ V* J7 T' Z/ P7 A5 U  Q
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    + l( i" \# ~$ l" G0 l6 b9 n9 l
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */: `; q7 v9 h9 j+ X( e
  58.                     break;" T8 o& h, w5 v" }  K6 @/ P

  59. 6 Y) Q# p# [! `+ j: @# }+ o
  60.                 case '5':
    1 a2 x. r' A& p9 h; @! i
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    5 h5 H! @' E. `1 Z9 d" c
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    8 ?! h& M; |4 O! E
  63.                     break;
    / _' l0 }; h& u6 C5 _9 r8 I

  64. % x  a9 Y  G5 Z1 q
  65.                 case 'z':% R6 H+ O4 W5 j8 A& C
  66.                 case 'Z': /* 读取前1K */
    ) s0 U7 w/ P; s( a
  67.                     if (uiReadPageNo > 0)
    ! }9 J1 t- _9 C
  68.                     {( N9 q7 a/ _/ l  F
  69.                         uiReadPageNo--;4 ?; ]- q5 Z& M, D+ ^  B$ W
  70.                     }
    * i" Q. l: E. a" v
  71.                     else/ ?% k9 O( J2 G3 u! K
  72.                     {
    0 z0 b$ z; B/ M( e
  73.                         printf("已经是最前\r\n");  @/ _5 O# I, F
  74.                     }
    # {) C6 b) ~% H( G/ X% p1 L
  75.                     sfViewData(uiReadPageNo * 1024);/ l! z1 K8 V3 o- T6 \5 i
  76.                     break;) p' C" r/ G4 T, Q, T: D

  77. ' v; A  p4 v, ~# m
  78.                 case 'x':7 P" H' z* h7 f; z  _# N
  79.                 case 'X': /* 读取后1K */
      c4 ^8 k) d1 d( m4 a. @2 o# k+ `1 A5 v, f
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    8 q0 e# ^5 ^9 X3 `+ U/ q% S
  81.                     {
    ! i2 g  a* X8 F1 i  v
  82.                         uiReadPageNo++;7 d/ z- B: ~* S# o
  83.                     }; D  Q. {& M: P$ w& U% @
  84.                     else3 v  v1 u. z5 A! E6 X
  85.                     {
    : |5 G  S0 O; {
  86.                         printf("已经是最后\r\n");- f9 ]8 W5 \- V5 X5 O- j8 ]' R2 ~5 J
  87.                     }
    - _  i& P5 `% j$ v0 A9 H7 M3 C3 R
  88.                     sfViewData(uiReadPageNo * 1024);
    % B. _+ i2 L, F. P! V
  89.                     break;
    , T3 i0 n9 l6 E6 \1 W  a3 m( H$ m
  90. + K6 [* o* R; v# j' Y
  91.                 default:+ _" F- H8 _5 E9 F6 \3 ^7 e% c
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    : V4 K- E2 t* _
  93.                     break;; \" I2 F6 A$ \* _. r+ m" h
  94. 4 Z/ d9 X8 O8 \7 e$ N  ]  [
  95.             }9 A# h9 U6 @: g% r! N
  96.         }
    ) l' G5 f  n( q8 O
  97.     }
    . e, A$ J4 z; b& Q% O; m1 y- k
  98. }
复制代码
; O: _7 {2 F+ d" I0 D3 m
73.10          实验例程说明(IAR)

+ @4 k2 J9 F, r' U: X  R+ K配套例子:7 _! j6 p, J: a

$ N8 l0 c& ?! z9 g8 M0 q! ~8 KV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
$ }7 N& U5 h. m' S
  M+ F7 ~# A$ j. P: S/ c5 h4 w: OV7-050_串行SPI Flash W25QXX读写例程(DMA方式)# X- ^5 r+ l- n) j" E' R" N, H
/ |! E3 R  q0 S5 J/ s
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
$ S- M: V; {3 I" J3 h, Q+ e5 U! v
& e8 G/ h2 B& [2 D5 _实验目的:! C  n; `! n. J

/ X5 X. W( }! u, f7 R7 g学习SPI Flash的读写实现,支持查询,中断和DMA方式。( }$ R5 u) H* r: R
2 C1 j% x3 ]% q: C; M
实验操作:
! i4 P% i1 ?: p4 J5 d( V6 ~# H9 G/ \
, j, m/ z. j0 u) ?* {9 Q6 `2 N$ I6 o支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可& m6 m& y6 m, |& L! C, r
printf("请选择操作命令:\r\n");
/ ^& g& I: {" o6 [. Tprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
$ ?' \7 W/ P0 P! `! P+ m$ ^! Vprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
% M% _. s9 j, b1 r! X: V; Lprintf("【3 - 擦除整个串行Flash】\r\n");  C3 D& H. r, V7 E; y
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
+ L+ m; I! H& Q$ |1 `9 e. Bprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");+ a. z4 w9 }  h1 t+ ~
printf("【Z - 读取前1K,地址自动减少】\r\n");4 |* }8 M: K2 L/ B
printf("【X - 读取后1K,地址自动增加】\r\n");
# Y% B; P' z) i1 c8 E1 `printf("其他任意键 - 显示命令提示\r\n");+ S5 O5 G; y6 R3 ^- J7 a
1 A9 g- S& A: V
上电后串口打印的信息:
* [, g& g/ j, z; d1 d8 O- c- m
# \1 p1 R3 m' u  J波特率 115200,数据位 8,奇偶校验位无,停止位 1。  g8 @# F) h7 H/ t4 k  n. y
7 n% V% S% P2 j' `/ ]+ o: y1 e2 S' s
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

+ v- k% E% s; n
& O) d- m* z" J  b程序设计:& V- P0 [2 O- R6 D( L

, b" l& K, X3 c+ _5 r0 h, _* j  系统栈大小分配:
  H2 ~0 \  b+ Y5 e0 |6 C
% q3 h+ H0 J# z3 d2 L; |1 v
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
- u. A! U& B& o  C

7 W; T  ~. |, z3 U; k/ k9 H6 A  RAM空间用的DTCM:. o' z' r8 U% W( X+ A) z6 C
& U: f) q; S; Y- z; e# J" B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 I( [+ t& U) A! s0 O
$ |& S8 K' e1 ^# b# M  硬件外设初始化+ b# w$ H5 ?) q- b) e  U+ e/ \- y
/ Y, D) Y! V# i" e( Y# K3 u
硬件外设的初始化是在 bsp.c 文件实现:6 T; _% s1 N) z9 g0 O+ s

. \! i; X% _- s. m4 m3 n
  1. /** P% F2 S: W9 D6 V# k! ?( ?. f
  2. *********************************************************************************************************
    7 r7 z, o/ O0 j( ~
  3. *    函 数 名: bsp_Init! O( I9 ?5 r1 J9 B! @% K5 k0 L
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次5 X( d* d3 Y9 c2 u
  5. *    形    参:无
    . q( P3 q7 p: A. b5 {
  6. *    返 回 值: 无
    7 q8 P! p* T% t/ K4 g, z
  7. *********************************************************************************************************
    ) {% i7 C6 y* N  a7 p
  8. */* i) Q: J  `' s; _$ \- T  P- Z* B" Y
  9. void bsp_Init(void)5 l2 {& ^* B. }
  10. {
    ; j. L" J/ E) T1 s5 D! T* i
  11.     /* 配置MPU */3 v8 {; t3 D* T; X  r$ Q0 c, f
  12.     MPU_Config();
    # E- ?5 X3 B' H4 S2 K
  13. : z& O2 I. q1 }: H1 j4 Q
  14.     /* 使能L1 Cache */
    8 `% z) T; C  H" f. m/ r  Y) R
  15.     CPU_CACHE_Enable();
    7 O/ a  U* ?3 r4 K
  16. , \% P* p9 ~2 T' V
  17.     /* 2 U( d- [/ c2 F9 M2 h1 q! ~
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    " h; v$ ]6 D( w8 W
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
      a) K) y5 P' P0 ^8 f5 h9 b
  20.        - 设置NVIV优先级分组为4。! `" c4 C  s- v6 M2 p$ b3 V
  21.      */  B8 z5 x" m0 l- R5 {% B; w
  22.     HAL_Init();
    & ]8 e( w: M& r  W2 Q; A
  23. " ~5 `) ~8 l+ [8 l' @1 U
  24.     /*
    * [/ ^5 w. f+ T
  25.        配置系统时钟到400MHz
    ; I, Y, W1 B) {4 M8 U4 {1 y
  26.        - 切换使用HSE。3 s6 Z" z/ C7 _7 l6 o% R8 m
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。$ f: T! b1 ?# d* r  R5 Q
  28.     */
    * I# b9 N4 _* j. i' x
  29.     SystemClock_Config();
    6 d# z. z( I( ]/ j4 Y4 Y6 w
  30. 1 e0 |3 j6 ~8 G: X+ y
  31.     /*
    2 x1 Y* e  Y! {8 W7 m' @! j$ V. l
  32.        Event Recorder:) r" I( Y6 K; @1 s# F; ^
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    5 G' K, u- d* t
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    7 ~) {% ~, k! r7 y; K  m  P0 w( H/ z. B
  35.     */    # |2 F* k: B" V( H! ~5 d/ m" \
  36. #if Enable_EventRecorder == 1  
    8 R+ p+ w! K. c0 a; \
  37.     /* 初始化EventRecorder并开启 */
    - Z# y; p8 B& z
  38.     EventRecorderInitialize(EventRecordAll, 1U);4 b  D, P! v/ Q
  39.     EventRecorderStart();
    ' P! N& S3 w- e$ j! H$ }8 i  o
  40. #endif5 o6 O5 D6 l5 h. L

  41. ( t5 H8 b; V8 h0 w
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       2 k- S9 E% R& E/ T" C& b' z
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */2 r! X: f* n1 K* d" ?5 S
  44.     bsp_InitTimer();      /* 初始化滴答定时器 *// v+ H* E8 B5 C; V/ L
  45.     bsp_InitLPUart();    /* 初始化串口 */; l7 R5 J0 ^5 y  o' P0 ?
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    6 J! @  f$ r8 \* O1 }
  47.     bsp_InitLed();        /* 初始化LED */   
    & v9 Y' W9 t( G) i  I2 R: V4 v
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */- X: N. |6 m5 U7 M2 K$ J4 H
  49. 5 w4 F- s/ O% |! o
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */( a& C& B  E5 v% Q
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    % f8 n  }9 A* ?. b* \
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */- J& N( T, H/ q
  53. }
复制代码

- A* i/ Q! m2 |  MPU配置和Cache配置:$ k! T4 v# ?& P0 L
" d) F0 H$ [- X
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
( T* A6 J5 `4 c( n4 v6 K. M  q
" X7 _' ]: W5 w, ]& m% L, ^
  1. /*
    : @2 L5 f/ e8 y5 a! o1 p7 e2 ?; O3 T
  2. *********************************************************************************************************  K) [4 j$ x( {3 c& r1 ~+ Y: H3 g- b
  3. *    函 数 名: MPU_Config
    ) c" t' z* f0 `; F0 k; ?- |
  4. *    功能说明: 配置MPU
    ! G0 M! O  d+ U" h
  5. *    形    参: 无4 k" E6 f  y' m5 s
  6. *    返 回 值: 无
    $ \  z+ B, |6 ]1 \! |
  7. *********************************************************************************************************+ B1 Y, N! a  p( \- ~; L
  8. */
    ! B( p& L1 N2 X4 F/ b3 A, f" t; }
  9. static void MPU_Config( void )
    5 p0 l  c: g0 x% @4 Q
  10. {
    ( _- p7 k. }: U  L0 Z
  11.     MPU_Region_InitTypeDef MPU_InitStruct;( x- b5 j4 C5 j  ~4 W/ N
  12. # E7 T" O3 K$ Q
  13.     /* 禁止 MPU */
    ) M& p# f! s* [& O4 |
  14.     HAL_MPU_Disable();
    7 m: }7 c/ d( a( |2 L% A
  15. , b9 `1 G$ d4 L+ F
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */( L5 b+ E7 s% Z  U/ k
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ( G% i% ^/ r$ U2 ]# X
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;5 I" M+ L, w4 @  L0 ?
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    5 R1 P, _4 {9 p# l$ I4 g
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    8 V9 t" p7 g9 V" N
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;% U6 d  ^/ M4 ?0 r
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    ; j8 V- l5 M. c( k+ o2 v" o2 R
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;. F3 \- u0 J# p7 U
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    ! O' P! a+ G/ @9 y6 y4 W2 N
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;; `, n2 f% w8 {, L( ?- D& }
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    * M% R1 s" K  N( G, l( M
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    - \6 [* }0 ^: a3 Q/ Y- I4 T

  28. / X7 N( @2 o* ?3 C
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    % K( l( Q, e2 ^% C+ g$ c- t0 B

  30. 3 j0 c2 y/ J3 K0 h* D% v

  31. ' s7 b" X9 V* ~# P- w$ j- f
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */2 n5 I, p$ }/ j: h$ Z6 W
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;; ?, a/ e8 }! Q2 z' ?
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;. d3 S/ P  R6 I( H+ J  s3 ^/ ]
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    , }& E, H* p& f5 I$ x) P
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    2 Z/ l' O+ ]0 M! h& N8 @$ V
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    / x9 C' x, H! B" z" z
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    ! n+ c% v" ~2 ^; q
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 M6 x* n( p8 L# F: q8 d5 q+ s% b
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    ' R# _7 j  ~% `
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;7 {. f2 z0 X4 G# \
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    # h* l$ m" N) n. X
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;2 `1 b2 c5 B0 l( p; d5 U
  44. # Z' w5 U& W# J7 \2 P3 r3 ~( h$ M
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);. S) A0 v' ~. W3 b  Y! Y* c' ~
  46. " K- \' @4 j/ L6 M! a# b1 b
  47.     /* 配置SRAM4的MPU属性为Non-cacheable *// S' k# p. I/ }0 q- j- V- G
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    % l( K2 e4 @8 G- c$ I5 O5 @% W
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;! c6 q9 q# F# F" k) l2 _" r0 a4 k. i
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;2 J. B0 }) k7 [+ X- q; D
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    % T& g- v9 z# |$ [: B* k
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;- j# g4 F* |% C
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    # w! S8 s1 g! ]; ?0 M' k0 E
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ! W6 m0 ~/ \4 S* O4 g: w
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    / d# J- R6 `! j% Z% W
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;  T9 Q# _2 }' w1 H( V3 ]
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    $ y8 M0 v& W7 b' i6 E' n2 r
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;  p5 W4 ^# p3 W, ?# _/ N- T- E" F9 p6 i, S
  59. - x# k4 \6 j4 k  X, U' x, k6 c
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    - z* K; \% _6 b5 A5 _
  61. ' }  h: T$ |- P" W+ p8 |9 f
  62.     /*使能 MPU */, W2 x" b2 X* @% ~  n7 ^0 S
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    3 {: N: V, J' M/ f
  64. }
    # N/ w! r# D! u) B
  65. 4 `9 j, ^8 x8 _" L9 Q6 p
  66. /*
    : M( B9 v9 ~$ S5 Y6 `: _0 T
  67. *********************************************************************************************************
    $ s1 O) }7 |4 i6 z  H: A
  68. *    函 数 名: CPU_CACHE_Enable
    ( J; L' i# t8 B8 w; ?* K
  69. *    功能说明: 使能L1 Cache
    ) R1 P5 C/ A) k& W
  70. *    形    参: 无
    % H2 Y( B6 m. i0 N/ i& n
  71. *    返 回 值: 无! y9 J) I  c4 R% `/ Q1 {; l
  72. *********************************************************************************************************
    8 J5 S& K5 q8 m7 [# F
  73. */
    ! ]" T1 B- J2 q5 ~6 J+ L/ ]" C
  74. static void CPU_CACHE_Enable(void)/ s, b' N2 s% F8 E
  75. {
    : P6 p# j$ Q- F8 C
  76.     /* 使能 I-Cache */
    6 `6 N) j5 S; L" C/ d9 N3 ?$ ^
  77.     SCB_EnableICache();
    % E8 a3 m* N, J3 o/ o# a9 T
  78. 8 m; ^3 Z; Z5 p0 X" D6 W9 C/ @
  79.     /* 使能 D-Cache */
    9 O! M4 I; d: i0 S  J  \+ P
  80.     SCB_EnableDCache();* @4 f/ c6 E6 Z. ?6 Z. l
  81. }$ x) ]3 g1 d1 U$ Z3 r2 ]
复制代码
& H/ X/ b$ a$ U; z7 u2 y! G
: B" ?% j2 k) n3 q
  每10ms调用一次按键处理:* ^  A( o7 e+ A8 P

! |, }+ U, }/ X按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
, V& |5 R9 z. Q% X- m, @! y
4 s$ y$ H- g+ l1 r( A
  1. /*5 J9 y* ~& M) s' t' d! t5 g
  2. *********************************************************************************************************
    6 m& L$ @1 l- ~2 F
  3. *    函 数 名: bsp_RunPer10ms
    + b: [# j! i- l$ C' V
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    2 \! n  R( Q' X/ E7 ], ~6 R
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    + t0 Z- H* ]# \/ G- [
  6. *    形    参: 无* h" n2 x' i% o, M5 y
  7. *    返 回 值: 无  _# C/ k: Q* J/ \) N
  8. *********************************************************************************************************5 i$ |' c7 v7 B: B
  9. */. Y% x4 W  @) x4 ?8 r
  10. void bsp_RunPer10ms(void)' ^% j" j1 i6 i  E1 }0 L
  11. {$ r5 J4 L7 h. U" E
  12.     bsp_KeyScan10ms();
    ; }1 l: i3 z+ q
  13. }
复制代码
) F, |" M! t- O8 Z4 z. U
  主功能:
2 E% n: c! o4 C& P* `) n0 w; r, {8 `  a% a' I
主程序实现如下操作:
, C% A; Y7 x8 W$ O. B. i5 g& I$ d4 j5 Q' F. i0 |0 ]
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
+ c* Y1 q$ p% P2 e2 N# @2 p2 y3 ?  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
. i! `9 }/ ]8 X  \- C! K  请选择操作命令:
0 ?* d' V5 }6 E% f6 [' q" ^% G/ H  1 - 读串行Flash
8 E$ r4 i' d4 b( z! N3 ~) ?  2 - 写串行Flash
* A2 ~3 k4 I1 ^4 `( J' Q  3 - 擦除整个串行Flash
% F( ~! n3 L: Y: \  4 - 写整个串行Flash
7 q$ c. r/ p! D- P* S8 h% K  5 - 读整个串行Flash
' W2 F, ]7 j7 x$ C$ y- |( L  Z - 读取前1K- U7 `: H. R. L$ @8 o& ~% w
  X - 读取后1K$ |0 ]% ?; m% E  u; s& w- |
  1. /*
    0 c" x" G! f% M/ u. i9 G( ^
  2. *********************************************************************************************************
    ! E2 {+ R$ t9 w' O' w% N
  3. *    函 数 名: DemoSpiFlash5 K4 S) R" V/ p
  4. *    功能说明: 串行EEPROM读写例程
    ; g2 m5 g6 ^' c6 `( b" I! \
  5. *    形    参:无' ^0 y. \7 e. q* ?' z
  6. *    返 回 值: 无/ T2 i, Z+ ~4 d
  7. *********************************************************************************************************: d% s, R" l0 i; k$ _( a+ c7 U2 W
  8. */
    0 }( k5 O7 s, m8 t
  9. void DemoSpiFlash(void)
    - K% }4 M+ D- B; k* c& d
  10. {
    ' ^; P( I( a% X
  11.     uint8_t cmd;8 u% Q2 G+ y) P4 \+ g3 i0 ^
  12.     uint32_t uiReadPageNo = 0;
    - {- O- a5 m: x) u$ j. n
  13. " e; r8 v# J" L# i
  14. % g1 o4 z1 @3 ^+ G) b7 K
  15.     /* 检测串行Flash OK */
    6 N" T- _7 m5 y
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    $ W4 J$ t. B1 k3 ]
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);: ]! c. b4 I' O: u1 ]
  18. 8 Y4 w& E' m6 o0 T0 M- p7 k) A6 P; H% L
  19.     sfDispMenu();        /* 打印命令提示 */8 R/ l8 L4 N1 f. z2 v! u  c

  20. . q' O  Y6 T5 D6 J+ [, a9 L5 N, v! R
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    5 W( j) S# _8 Y+ {& h: n

  22. ) m3 i9 f: }7 F5 ?1 U7 O$ u
  23.     while(1)0 S' ~  T4 n+ Q) I' ]
  24.     {
    2 W6 T8 p7 d" x) N. B* e
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */+ R2 }( H! I& B2 h, u8 R

  26. 6 }  q  j* k; }5 r" g5 A
  27.         /* 判断定时器超时时间 */' N9 s9 Z& v. L" _6 r% H, F
  28.         if (bsp_CheckTimer(0))      F) v; D0 ?# g  z
  29.         {; I: l: ?! k/ h  X  x
  30.             /* 每隔100ms 进来一次 */  9 k6 b3 Z  r8 k; B7 O; v- N/ |
  31.             bsp_LedToggle(2);" p+ M3 ^! C9 v! ~9 ?% b* H
  32.         }
    / b" a& Q5 q6 b" j, B; g

  33. $ H: r* s/ e& s- J+ ]" H
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    7 a& e, M1 g# i) k! A
  35.         {
    8 X: d- F: ]' S- {2 e4 `
  36.             switch (cmd)3 l+ K6 J" u9 e8 k
  37.             {
    8 Q, {' {/ w8 X- e3 [/ X
  38.                 case '1':
    ) ~& z7 I# g- k4 a  k
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    3 n3 W( z# y& A+ X) h" _
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    , X! _* R; r' Z0 j# C& K
  41.                     break;' x+ }% H8 H* h! o

  42. ' X5 b$ n- [5 _
  43.                 case '2':. M) H) V7 E2 W
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);) t& e4 _3 i2 x; C: C
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    / C$ i  [( j4 P* B4 B1 e; H
  46.                     break;5 F& Q! m7 g* y. K, X6 i: ?

  47. ; I" E7 y/ L$ W# @- h+ ~
  48.                 case '3':
    ( D- ~9 p6 w4 ?0 P1 j0 {
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    ( b3 A: N  ?+ `- \  P1 y- o2 h# @
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
      _/ D; ?# i2 _) S
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    : D8 v: y+ X( Y4 e
  52.                     break;
    , s8 V4 I$ @! b' w& v4 ]

  53. 3 e, m6 u+ M1 O, x3 F- h* I8 D2 ^
  54.                 case '4':
    # z4 @8 B/ d5 \, `; s
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    : }4 T7 X  J1 I( f
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    . }1 _" X" m. G( R. r* S3 Z
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    7 |% B8 }& [, I9 X+ `) Q9 w  c
  58.                     break;
    0 s- E0 S. S) k; k, _
  59. " z3 d, i  u; f: F" t; i
  60.                 case '5':
      S* i" R7 b& X8 m# h
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    - x. g2 ^# E$ t# Y9 ]" u
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */! S  b+ h) D5 d
  63.                     break;; g7 j. o, Z4 S* e$ {* C. d

  64. 4 m5 e# L% l5 b2 _$ U  P* k! O
  65.                 case 'z':4 U( Y7 K5 n& I7 _
  66.                 case 'Z': /* 读取前1K */2 w7 I# U/ Q# \1 k, _
  67.                     if (uiReadPageNo > 0)
    # X8 h# T4 D: u% E
  68.                     {
    ( M$ A" z+ P0 z! ~2 J
  69.                         uiReadPageNo--;8 _8 s+ I3 c3 g6 y( G
  70.                     }8 a) Q9 s. \0 M( l
  71.                     else3 \) `$ V( s6 }
  72.                     {
    $ _3 j* o# D/ a2 P6 Q
  73.                         printf("已经是最前\r\n");
    ( y8 M  g! o9 F) J3 e/ @+ Z- N
  74.                     }) n* Y+ u3 X1 t" `
  75.                     sfViewData(uiReadPageNo * 1024);  \" H8 w9 u6 t4 b
  76.                     break;
    1 e, L8 J& D  `) u$ C. W: ^* Q

  77. 9 O/ U! s3 P; x/ o- S
  78.                 case 'x':: A0 q: I. A9 }1 G5 |
  79.                 case 'X': /* 读取后1K */
    . [4 I4 R  R: u0 r- f0 X4 r
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    4 R8 \7 V- V& S
  81.                     {
    + G% z/ L4 J0 p( }7 ~
  82.                         uiReadPageNo++;% N, K, t3 o* R* K7 ]
  83.                     }
    . y3 g' j1 f$ M# J% y7 B
  84.                     else
    & t) e% Q% E( R+ o7 L) y& D
  85.                     {' h# a/ E2 I/ M- p
  86.                         printf("已经是最后\r\n");$ }3 F: S$ T# T( v8 R% m
  87.                     }  b* \+ S9 A  ]- S! r4 e
  88.                     sfViewData(uiReadPageNo * 1024);4 b# n: @! X* |: D" S
  89.                     break;, ~) d5 a: X' d- k1 Q

  90. - Q! A  G, F/ k) R, ?
  91.                 default:
    + r* H5 j; y" N
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    9 l6 X2 q7 M, r" D: y" l' c7 o
  93.                     break;
    6 C5 N) e% b  O1 g2 e
  94. " D  B  \; D! E; e, Z
  95.             }
    7 `6 r, `6 i8 ?' r2 N
  96.         }
    * U% M" R; i  f) m
  97.     }2 o! r( {: d3 N6 I; F
  98. }
复制代码
: D1 b1 V* Q2 b& u$ W" U& H
73.11   总结/ r. V3 i! O+ ?! b' p8 O- T
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。. v% L; {( Z5 }& @7 C6 [
/ {" K2 y% W. ?; }( a) Z9 b; K" |/ h

3 C+ v) F8 p5 ~
( l* r1 i# o$ L! K5 q; x
收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版