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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示
5 R  L: T- q6 Q2 \( ?  W25Q64FV属于NOR型Flash存储芯片。# r6 |; w7 N& S/ w
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
4 a! E; x" l/ K. [1 v0 u0 T3 R: @
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

) n3 e6 D) E& R0 I! b  M6 a* D
9 u* x  |) X6 r6 a0 u) W& K  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。
, n0 e% Z* }+ h- u, o  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。
2 N; {% S$ V9 y$ h  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。
; X& ?! @' p8 h* w8 e# y1 ~  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
5 T$ R8 P# j2 Y( u  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。% _9 B6 A$ m- }

: H! ]% U& E; M0 a8 i7 }, U73.2 W25QXX硬件设计* t! _. ]6 i& @- I1 ]$ a
STM32H7驱动W25QXX的硬件设计如下:# t$ s6 `1 i' C& O8 W) ~
" t! q; b% J' H* _
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
" [3 |3 e% v, F8 y" c; T8 ?

5 X: k: P* o0 f% T4 {! y关于这个原理图,要了解到以下几个知识:# S4 g6 U! C1 ~, r5 o- U

. X/ w" }0 e6 }* T2 Q4 c  V7开发板实际外接的芯片是W25Q64JV。
0 g* l6 D( M& q0 r# E  CS片选最好接上拉电阻,防止意外操作。# r9 e3 a# [" M) U/ ]" p& |& c* c
  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。
" b4 V; e9 B: N8 A9 ]  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。; `' N0 L' k  t; f9 w. w4 ^" K
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
, L5 A+ ^/ |+ ]5 X9 B7 k73.3 W25QXX关键知识点整理(重要)* r0 g2 I: S5 h& Z
驱动W25QXX前要先了解下这个芯片的相关信息。
$ Y7 F1 u, X3 E5 h5 R( b  t$ {. I0 p
; x- k: F) X9 W' w5 L
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
0 Q! q8 W4 a+ j$ k! x
- p2 M' ^- s" n" f( r  {' q
73.3.1 W25QXX基础信息

9 ?5 K9 b, c0 d: W$ _6 f) c% y  W25Q64FV的容量是8MB(256Mbit)。; S2 W5 h$ V! x" L  H+ z6 T1 t" ^& \
  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。) v* @: T5 N) \5 k& W( y
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。, m& z4 V& f% o& H

' K* H' E- D! i7 M7 g+ E# L+ q: L9 ?支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
) t% s% }4 q8 z4 f- {. G5 R& o6 `" H* z9 M+ `# B
(注:这里几线的意思是几个数据线)。- Q, p# m  e: w
: @/ D# V" P& H* W3 R; }9 r
  W25Q64FV支持的最高时钟是133MHz。
5 i9 d, t: i" j- k& q  每个扇区最少支持10万次擦写,可以保存20年数据。. m  |5 ~8 K; a
  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
# X" \/ R) @1 s* ?0 `+ i  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
/ o; t: A9 b+ J; V$ G7 D整体框图如下:
! ^2 y9 x; \8 ~  u  h1 I+ T$ p5 A/ t) D
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

+ U$ d( D* m, O. u$ ?8 ^6 K* c9 \. X
W25Q64FV:
. k0 i; c: c  a8 F* C
4 d2 Y* y% A3 N: d" }3 m1 D# W  有128个Block,每个Block大小64KB。& }) }! a1 a  P* i( b; |  Y  i: `2 S
  每个Block有16个Sector,每个Sector大小4KB。
4 w- U- M: }6 }  m' ]! s5 N  每个Sector有16个Page,每个Page大小是256字节。
9 y9 c) i% R# C; O4 r73.3.2 W25QXX命令
! A, W# h# W0 D: O4 V8 B" O使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:  M' z% l; k/ z# q- K
7 C& `" i: e, j+ q) Q0 M9 o
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

3 w7 d5 H. I$ }$ K: A5 R5 W- U! \5 ~) O) D  d- t. F4 I5 Y0 {7 v. b
当前主要用到如下几个命令:( K8 J5 m7 m( U4 ], N
1 Q; D" x2 s) a
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    ; T; P  R$ ]/ _
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
    / B, Z; E: D! l4 w; ?2 d& w5 n/ I
  3. #define CMD_WREN      0x06    /* 写使能命令 */
    ' _# j: R6 n; e& s# G
  4. #define CMD_READ      0x03  /* 读数据区命令 */
    , H0 _* U5 a. l: Q% u
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */' k5 V: [: d3 ]; f/ i+ i8 ~
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */
    5 R$ T, |/ I3 k8 ?
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */, p/ o/ t/ g7 G9 L4 E
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */+ a% j# E6 ?: w
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
' o# [0 M4 e% c& X  |" u" V: u
73.3.3 W25QXX页编程和页回卷
8 b* S5 W; a" g' y! M3 }7 jSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):
; M$ ~; d. `/ G% r/ G* q  p& y3 o) J, J
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    - a3 q, {2 S# g$ \1 a5 ?& n
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    $ p& {3 u/ ~. ~9 d1 J( m
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    5 F" }2 C8 F) _8 g+ a
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               & J; U, ?* S- H7 b% Q

  5.   e. F: v1 {, d
  6.     for (i = 0; i < _usSize; i++)
    1 z; _8 {1 O! j' H: o$ k
  7.     {! F9 K2 S# u# {
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    . P4 m. w- O( W8 n0 {, V& |
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    ; p  c/ y4 r' K& d& L+ T  q
  10.     }
复制代码
2 x# x: m* h3 z" t
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
/ ]+ T2 K/ d+ v0 ]6 q( }7 |
/ l) T+ s& T( `比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
, r7 o% C! v$ a2 ?! i" @* s) D9 y. V  `( p
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};8 u  z9 @+ L  D- W/ k
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

/ s. R7 D( `7 S. D" [  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
5 A3 b2 X+ T' T' v  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);
' v: I- [" n' D  W+ I  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);& p0 L0 K8 W' j4 U8 K
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)# g# B; Q/ C! H
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
2 b$ V  w- G( S* ?, a7 q; j
+ l5 ~. f" p/ V- H# g) h: r* a
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

- {. v$ m4 j: `( A; F
5 g# p3 {0 E/ a0 x/ b. W4 O9 I: @. \  w4 ?  X" `
73.3.4 W25QXX扇区擦除
9 {8 j1 W+ @. F' \5 D- A) MSPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
; B" t. e5 e8 P& K# x) ]) a0 F0 o/ J  A2 ~' B" e3 o  k
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
) D" f2 ?( Y4 I9 M9 F" X  [" n: l5 w* c$ P6 i7 f
73.3.5 W25QXX规格参数
# s9 a8 [  w2 Z- P0 a
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:7 b& @2 ~, u: \6 R* v' N5 q
  z0 C. t& }$ l( `% j; h
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

; F) v0 p7 A" B5 O
  l& L8 Z% b9 M- M- S# x+ u  页编程时间:典型值0.4ms,最大值3ms。
' s; W5 l4 T/ Q# ?* }( L  扇区擦除时间(4KB):典型值45ms,最大值400ms。
% A! b9 V( B. f- G7 h  块擦除时间(32KB):典型值120ms,最大值1600ms。8 o4 i$ e! w, U2 F
  块擦除时间(64KB):典型值150ms,最大值2000ms。, Y% |$ V& K3 d0 n3 T; ~, \0 F4 ^2 P! k
  整个芯片擦除时间:典型值20s,最大值100s。# j8 }1 I$ j- Y0 L

& X, `9 S: u- U; N) Y) j2 A0 B* {9 l- C
支持的速度参数如下:2 l; U2 w: l& E+ i+ s/ S3 W

; ^1 u9 A, m/ a
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
' O5 ]# ], n1 J' |

- [2 f+ s0 g6 V# ]可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。% f: K; `% s) n( n: h

" K# o- M% K/ j$ v) D4 ?73.4 W25QXX驱动设计
7 n/ y' }: C% e! c  kW25QXX的程序驱动框架设计如下:
$ W( u+ y  @2 v. n2 T0 A4 P
: V: ]0 K% {4 y5 }1 g7 u; t
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

% w4 z! o. |" y
( z  J+ ~. L% M1 l( p有了这个框图,程序设计就比较好理解了。
: @- e" V* l* W: d6 h/ W& J
" ]3 v8 n0 H; t73.4.1 第1步:SPI总线配置
& U, |0 x, h4 @# bspi总线配置通过如下两个函数实现:
7 B( B0 N8 s8 O# B5 C2 t4 I( J
. Y) f2 i( m: |! V
  1. /*
    # N3 b' V. F8 P, ~
  2. *********************************************************************************************************
    7 q4 Z! V# c! k# u- k3 ~
  3. *    函 数 名: bsp_InitSPIBus
    ! ^7 I% L6 y0 u& C, T+ V9 j
  4. *    功能说明: 配置SPI总线。
    # K0 n* D3 {* X! U* s. L) @1 W( S3 _
  5. *    形    参: 无3 }  u% ~" d. }) n, E6 e
  6. *    返 回 值: 无- d' F1 P2 `1 Z' B5 Y- Z; S7 A
  7. *********************************************************************************************************4 M. r- i: Y; d' Y: E4 s# ?0 R
  8. */( {8 Q3 O3 ^3 j4 x& m' N
  9. void bsp_InitSPIBus(void)0 f8 Z- ]" `( ], }
  10. {   
    3 ?0 W; [8 ^3 y0 H8 X5 T8 r: b
  11.     g_spi_busy = 0;5 f% l5 p& P) c

  12. * i6 F. \, T" g/ J6 D3 ~
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);3 w1 e2 u# E/ i8 F% _  X
  14. }
    ' Q( m: |3 Y; B

  15. 5 T, V1 @$ n, g) y
  16. /*: {/ s/ N6 E, w% n; P
  17. *********************************************************************************************************5 B$ l' H: F) A
  18. *    函 数 名: bsp_InitSPIParam3 U  t% ~8 m) |3 s; V& j6 ~
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    ) X! o$ R) k; q6 H1 I
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
    6 E. o; v6 L' A, \# S# m1 e
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频
    ; w! W2 h# [" {$ u) b
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频) R2 R0 B8 E/ a; F$ P; d% K
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频
    " U% z& K: Z3 W- B/ r2 ~# ^  H
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频
    7 V, m  Z  E; O+ ?8 A
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频
    , ^2 a2 b% {0 X( s1 M/ u0 Y
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频- ^8 |+ s$ S! Z, i: k
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频9 g( U4 g3 m* t/ j& R* V; ], V
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频; X$ N! `# v  D9 p% U6 \
  29. *                                                        
    2 F) @" l$ ^; U& \6 h) f
  30. *             _CLKPhase           时钟相位,支持的参数如下:
    8 c8 H% c. z2 V( e
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据9 h9 L9 V3 h. C# n) |& S3 }( N& q
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    . Y6 j2 q' r3 J: }# s0 ]2 n; O2 Q
  33. *                                 
    ! k- S3 l; x+ b' m4 ^! D
  34. *             _CLKPolarity        时钟极性,支持的参数如下:
    6 k2 N  _8 i/ V6 X/ K. T9 h
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平  A. r! y7 w+ m% z: ?
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平8 Y; Q* ]: C2 u. Z6 B: |5 C' G6 e- I6 b
  37. *
    ( q. g! h$ m9 a0 I- N5 f6 T& j% v
  38. *    返 回 值: 无
    . _, v7 x0 X9 ^9 L1 _
  39. *********************************************************************************************************
    7 h& ]1 t  ~. s' ?7 Z  j; ^" \- K
  40. */8 Z8 ]! D) y% w0 x( f. P  f
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)- y  Y$ S) @2 G
  42. {9 F, d- J6 _6 d- A$ k* d
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */: B! I+ k8 ?8 E+ v
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)9 e+ i9 T( L6 K, r+ x
  45.     {        
    / z3 _5 }  H9 U$ g4 @, }+ y
  46.         return;
    , U# P) U- N( z( T6 k1 G" p
  47.     }
    ! |, B$ K/ r4 Z+ @- }9 ^) e
  48. ' n  K$ b) \7 P, c- M
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
    + A7 p6 K& T3 N1 K
  50.     s_CLKPhase = _CLKPhase;
    0 j- O4 V0 w7 K1 l/ ^
  51.     s_CLKPolarity = _CLKPolarity;/ q/ P: {) Z& J. z+ q6 d" a" A

  52.   f- M8 c/ o' |0 w5 C
  53. 3 @* F" _* }4 y' n( |2 R6 X2 o7 e  J
  54.     /* 设置SPI参数 */
    3 ]7 g7 K5 M- B* v6 q
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */2 J. X& ^$ t) C6 a& q) A5 }
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */0 m! H& K% e: a9 x; h# V8 t
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */" S* J+ v, k& G& {
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */5 n+ J- c0 q2 T, F" m, E
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    ; i0 {- g# ^' c/ D
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */4 h+ ?% g+ `4 P) D  d
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */" |& h7 F# J  W1 e! P
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */# X8 ?$ r- a8 ]* C  W7 \. b4 U  i
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    - E3 a0 A2 f+ W" }( [% `. T3 A# e. B
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
    7 i- ]6 g! _/ P) ?2 ]5 P
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */$ \& y& P1 X0 y- L2 c6 J
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
    ' P, k* H* r( G5 h* G! i
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    ! T: r) m  X, N# s5 o, m
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    " T, V) ^9 q( ~
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
    9 a7 E; m. z. \' G! A
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */7 K) x/ k1 w" F7 i; G$ H2 |
  71. 3 U* i- V0 M/ j5 H9 t
  72.     /* 复位配置 */
    % `( C  h2 t4 ?/ n4 Q4 s
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    : H. @5 Z" Y( J, `" ?' J/ V$ q
  74.     {" U$ d) r  j$ G: F5 o: @# z
  75.         Error_Handler(__FILE__, __LINE__);
    9 }. L0 z0 G6 ?8 X
  76.     }   
    ! H, ~! k, Y" ~  d5 S
  77. / P: L) T( R$ ], V, a+ d3 W, W
  78.     /* 初始化配置 */9 t1 l. H: D- t6 g+ j
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)0 r  `4 l" b  m. w8 q' s& n6 ?
  80.     {
    ; t! E; f8 |1 U. C  b( B% K
  81.         Error_Handler(__FILE__, __LINE__);& r4 O$ d- {3 p. ^) C
  82.     }   
    % m6 j9 u0 b; F# b( o' `3 {
  83. }
复制代码
3 U' o1 ~: t" G2 Z' l; L
关于这两个函数有以下两点要做个说明:/ i9 |2 v  h6 @! r3 H+ {$ T8 V0 [
8 y$ Q# o% s5 q, F+ Z- C7 `! T" `
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
: ^  T0 N* T8 Y  T  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。+ e3 |$ `6 a0 C" Z
73.4.2 第2步:SPI总线的查询,中断和DMA方式设置7 x4 p) w. U/ e7 E. R
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:6 g$ G4 m: z0 y' W! R2 a0 i5 R
3 Q$ i. r  o, F) X- _' O0 ^$ `9 A
  1. /** U. y$ o) V' r+ O" t( j2 r
  2. *********************************************************************************************************
    + I! h" E4 P$ {# g* r5 i" s
  3. *                                 选择DMA,中断或者查询方式
    ' P% }/ U5 z8 n  y
  4. *********************************************************************************************************
    3 M; L! e4 c1 t9 @
  5. */$ o/ V$ G/ B) U% L+ v! k- Z
  6. //#define USE_SPI_DMA    /* DMA方式  *// d' _6 Q  L+ v4 W- n! \7 K+ g7 W
  7. //#define USE_SPI_INT    /* 中断方式 */
    ) j- _8 d" y3 a0 k- @; F/ ^
  8. #define USE_SPI_POLL   /* 查询方式 */
    ( m* x6 G. ~" _% G7 z
  9. ( U! I! r- H( v7 F& D- g
  10. /* 查询模式 */
    ; A& u0 f& ~: r+ b
  11. #if defined (USE_SPI_POLL)
    / f  i0 W  G6 G

  12. 5 I) a/ ?" n) q8 e& R/ [
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    * O4 w- h, A# Y0 u# U
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];% I5 X. v. B" p2 \6 P0 w+ v

  15. # B; v* Y- y7 `3 H, @. h
  16. /* 中断模式 */
    ! U2 `" R( _. ~# n1 {' J( C
  17. #elif defined (USE_SPI_INT)
    9 @/ ~9 H& R' E* G) i0 L! u% w

  18. / }' m) T( Z/ g
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   , _1 j1 m# N2 v8 _1 W
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    ) l3 I& [: O; G, c9 S* y

  21. . \: h& t, A; }
  22. /* DMA模式使用的SRAM4 */
    0 E$ f% z0 o4 g! n6 M, z0 K
  23. #elif defined (USE_SPI_DMA)6 c$ C( e3 j( O4 a( z) D( v
  24.     #if defined ( __CC_ARM )    /* IAR *******/; J- A0 b+ y+ @: B6 n- z
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   # X3 a/ E" C$ p+ ?  a
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];1 a- }1 \7 C4 S- D
  27.     #elif defined (__ICCARM__)   /* MDK ********/
    & D! p( a8 V  a
  28.         #pragma location = ".RAM_D3"; V& C' f1 r3 \, Y, `% B6 t% O9 W% i
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   : H5 D9 u: S  @; f. b
  30.         #pragma location = ".RAM_D3"
    7 @$ z  f- R9 P5 D" q9 f. ^$ f
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    ' ^3 Y/ K1 t' {" o2 X! Y
  32.     #endif0 D, }* t/ L8 f' @
  33. #endif+ ~, {- w6 C! a: Z: t9 u+ b7 h
  34. ! Y/ p  \4 q* j
  35. /*0 ~9 F$ S5 F6 @( o! d
  36. *********************************************************************************************************( O, A8 M; T- x, L/ l& x/ |
  37. *    函 数 名: bsp_spiTransfer8 x6 a- q, ?) h: K1 l; S
  38. *    功能说明: 启动数据传输! k% x0 T- ^4 y6 \% ^  ~& f
  39. *    形    参: 无$ _: N( @( Y1 p& }0 c/ w! Z6 n
  40. *    返 回 值: 无. q& s1 q* r+ q& ]4 {. m
  41. *********************************************************************************************************
    - W' D7 ?1 U  H8 E6 f
  42. */1 _1 j8 ]% M, f6 {& O
  43. void bsp_spiTransfer(void)
    ) J7 j# g- d4 k1 e' Q$ P
  44. {
    ! r5 t: |. t* k' Y2 \: l
  45.     if (g_spiLen > SPI_BUFFER_SIZE)
    % _$ U- _' J% H+ E; H9 A
  46.     {
    3 Z/ ~% m, A" }! b4 m
  47.         return;
    & j( _" |' s: H
  48.     }0 P8 a& G& f2 `" J" m/ S/ G7 T

  49. 9 @" v8 j9 m; a* t  w
  50.     /* DMA方式传输 */
    , G6 x( `9 A! T. i6 ~& s& x
  51. #ifdef USE_SPI_DMA: B" P, Y0 Y: t2 N0 g/ w' T
  52.     wTransferState = TRANSFER_WAIT;' Q8 j% E' I- b0 H* g
  53. " @1 ?( P: I4 P2 x: L  y3 T+ \
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    ; r  H0 i) @2 r3 _5 `. V- Q  X
  55.     {
    1 |8 [6 k& J) c
  56.         Error_Handler(__FILE__, __LINE__);
      e3 ~1 t' n/ M) ~
  57.     }
    ' K6 [" C# h3 v4 S$ S1 X

  58. * A5 P+ P* W6 A* a0 |
  59.     while (wTransferState == TRANSFER_WAIT)( {/ U5 y* O* y1 a
  60.     {7 ^! i/ ?; l, `/ `5 y/ G  R* v
  61.         ;
    - e& w1 [5 Y- r; G9 v! [% O0 q6 h
  62.     }  }' B3 p) j2 J6 o
  63. #endif
    - ^7 K" p8 Q1 X) o
  64. 6 P' I& `; o: m. x, }$ }# |9 z
  65.     /* 中断方式传输 */   
    * m, m9 `! j# a5 l& O2 U
  66. #ifdef USE_SPI_INT
    ) k" C0 F; A" E/ Y- q
  67.     wTransferState = TRANSFER_WAIT;
    : A! u6 T8 n! @+ L

  68. 4 h. J, @& J0 M0 {( j' A; {6 k% {
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
    $ m$ q  {1 t7 y! ^
  70.     {
    5 F5 G! d/ J) _# Y4 o% D
  71.         Error_Handler(__FILE__, __LINE__);
    0 n/ S, u3 F( q3 X
  72.     }7 P/ M7 K0 o( Z7 O4 {3 T- q
  73. 8 P2 d& `6 ^) {) `: Q
  74.     while (wTransferState == TRANSFER_WAIT)
    / {5 u! E% [6 u  b. f: l5 @- c
  75.     {% E+ p4 e; [7 w( P( K: y
  76.         ;4 g- I5 U; O* ]  ~+ n
  77.     }' V! |% u; H8 M* [+ j7 b( R
  78. #endif0 r, D# @! U7 h3 q# K5 q8 J: X2 ~

  79. # _: j% _0 S* [" e" t3 F7 n
  80.     /* 查询方式传输 */    6 u+ q5 s0 l3 v/ h6 n' q9 l
  81. #ifdef USE_SPI_POLL
    $ b; X" k  s# T# }4 O; V2 D" l
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)   
    , @( h$ _) s' k' L. O/ D! |
  83.     {
    , ]/ v$ k1 `% ]& y( ?/ d, H8 j0 y
  84.         Error_Handler(__FILE__, __LINE__);7 h( c& W0 p4 I
  85.     }   
    " z0 [- Y/ g3 b2 t9 W) ]3 j/ S
  86. #endif* I( H3 [1 C1 o" x4 O+ K
  87. }& n9 U7 k* m6 {
复制代码
$ D, R4 g( y& n3 i& r. @
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:- C( N1 a% }1 ?' B8 E" I
  T9 {! K& R& m" a! ?5 x
  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。$ e% z! E4 {2 Z
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
8 K8 ]: \0 O. W3 I! c0 T
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */6 p1 \+ K& e8 Q: U4 `
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;# P9 x. s* `/ h
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ! m% k: ]* ?5 h9 J3 Y* ]1 V/ a
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    , s, g" U5 }- w  Y. [7 R+ s  v
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    + u* t& c4 j. c5 o' G
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;& T: V, _9 |! _( s' k
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;: U" p; z/ P2 `1 a, L
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;2 L! t  C- v+ }7 r1 C4 A
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;* T, N8 ^- w7 O7 J2 J2 _8 R
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;6 H0 s7 ]. g1 V9 _+ E
  11.     MPU_InitStruct.SubRegionDisable = 0x00;* @& o- f/ q) b" p. W0 W/ {
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    8 k' ?/ F$ ~2 f9 d+ y( O: y

  13. $ {6 I+ H1 _" x: v6 E
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
& U& m0 E. L1 K* o. r7 M$ S! a
73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

; Y4 s3 G0 |& L# O% c首先回忆下STM32H7支持的4种时序配置。% D. ~' v6 j4 L
& R2 c; r; j* M1 V! Y, _
  当CPOL = 1, CPHA = 1时. ]  w. M9 V0 O( Q+ e% O* s/ c
$ ]6 ]( j5 I+ N2 N5 X! X9 {
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。7 j. f0 w3 L; W; Q" S5 {2 E9 O# N" ^
, r5 ]; G. k. m0 f5 F8 J; T5 j
  当CPOL = 0, CPHA = 1时8 u2 U) w* Q% b0 c6 X% w2 p( U9 b

) q) W, Y' H' ]+ |6 z" VSCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
8 [9 _/ S  x: V
+ j. b3 Q/ j+ `  当CPOL = 1, CPHA = 0时
  z( [3 A% W' R2 \3 }5 ~) l
3 C  n  O  r' q/ LSCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
/ c3 e; }- y6 J8 k  I7 k. }( G$ P1 F' O0 p) t
当CPOL = 1, CPHA = 0时; \# j( j& E, I  \/ W# N- M

2 K% T( A9 l2 @SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。6 b" C# w' L' T' r5 Y) G. \, W

7 k6 M: L7 f2 V7 E
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 P. e  G& ]7 U% I2 e
2 }+ v5 f0 ]' d( m3 {6 D
有了H7支持的时序配置,再来看下W25Q的时序图:) K/ f& C0 `7 X. @0 A3 X

! R" ]/ u7 n3 s" a$ K9 N& `7 @Mode0 : 空闲状态的sck是低电平。/ p* ?* A* x3 }) U& `0 C& u$ Y% i
4 ~3 X" o8 z0 p1 a9 ]
Mode1 : 空闲状态的sck是高电平。
  [+ |8 T0 o. V* T3 t9 A2 t8 n0 ^5 Z; A; f5 h4 \, ^" c
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
5 }( j3 K" N! o' e+ U/ j( d+ E/ E( u
. }# W0 C) t8 {2 C' X2 C
首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:; |) _6 r! r  y

3 K4 E9 |) Q6 g, O: {* p# D+ _5 s) S! hCHOL = 1,  CPHA = 1
3 P' ]' I  i8 W
0 H) l8 k6 z. e) R! TCHOL = 0,  CPHA = 0
9 g. q* o1 v) I4 \# a  y$ Q* A
6 T/ i0 X  ~6 x3 w对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。
6 J: Y9 e* s  ~* Z8 l! N8 F  M) U! M7 V5 S$ J5 ]4 y/ x
73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制1 Q$ r8 ^; d' v/ P; M' E2 Y( x9 w
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:6 Z2 _. f% Z. O6 ~1 f6 f
% Z. n- i" g5 R$ J# g& E4 p
  1. /*1 D) L9 N  ^: x
  2. *********************************************************************************************************+ v  ^* ^( e, G* a1 f/ ~( [
  3. *    函 数 名: sf_SetCS6 \+ ~/ f& G9 I# f! Y2 |  k
  4. *    功能说明: 串行FALSH片选控制函数* M' c3 b5 I4 r+ N, w2 d9 \3 d
  5. *    形    参: 无" \$ \3 v0 t+ T: X5 N
  6. *    返 回 值: 无
    - T/ e4 x. _2 s) h  o
  7. *********************************************************************************************************$ K* W7 N, |1 Q4 y
  8. */
    , ~% Q# C/ D3 y
  9. void sf_SetCS(uint8_t _Level)
    : ^: |3 n7 l( e/ X7 q/ F
  10. {
    $ A/ z8 E+ f9 ]& R
  11.     if (_Level == 0)1 y; `4 h3 H: \! r4 P
  12.     {
    5 b, R( n8 A) ~9 M: L
  13.         bsp_SpiBusEnter();   
    0 f$ ?, Q$ E3 ^0 f; z$ w1 }4 R
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        & x5 z1 X' Q0 }  I. n
  15.         SF_CS_0();/ \. C2 r3 R) g. z
  16.     }+ v7 y' a3 F6 ~$ D$ _) e
  17.     else
    - b2 T6 p- V0 N' R8 {
  18.     {        
    5 c: J$ z* U+ w
  19.         SF_CS_1();    9 g$ c! ~0 R3 V" `
  20.         bsp_SpiBusExit();        
    # e, k. y8 E9 }+ v: h6 p
  21.     }" {$ x: g2 y$ ]1 G! X
  22. }
    + [+ D4 M2 a3 u6 i9 C
复制代码

* @6 k, f* U% j0 q; Y- p" W  s3 _; [
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。
, C: ]% C( g% Z
7 \* Q% k+ C* a6 M: o! V7 K' [但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。9 Z8 h  j' _. _
; O8 p; U1 s( R2 {
73.4.5 第5步:W25QXX的读取实现
6 H9 H. f9 @* ]# H. G: G; N7 e
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。# b3 e& f) I4 W+ }3 c' J7 w" _1 {

5 z3 d4 B0 P5 N" h
  1. /*  \& J7 P; K2 Z3 @) u8 J
  2. *********************************************************************************************************
    ) w& C( M# O1 B: i  ^6 j
  3. *    函 数 名: sf_ReadBuffer
    0 j/ V8 K4 Q/ I+ _/ c3 T+ }) M
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    + [& d  K6 k$ U% n$ T- M4 m/ e
  5. *    形    参:      _pBuf : 数据源缓冲区;
    5 u9 w* t' e0 K: D! v2 P$ ?
  6. *                _uiReadAddr :首地址- _- p# @, f) a$ K; D
  7. *                _usSize :数据个数, 不能超出芯片总容量
    2 s- `( U4 f$ }6 a" H
  8. *    返 回 值: 无0 [: ^2 k$ T4 B6 Q$ |" s/ P9 N" L  T
  9. *********************************************************************************************************8 U0 ?% f+ j* A
  10. */7 C; f/ R- {7 u" K
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    0 |5 U  T3 N  V+ A; B
  12. {
    7 q8 b' r' w: N4 k* z
  13.     uint16_t rem;
    . c& o: g# s6 ~
  14.     uint16_t i;% s0 [) Y, W0 i, P" f' n/ q1 k

  15. # v, X# d3 w; F3 s, S
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */. X, U! C) s6 X5 k: @% `6 `
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    ' ^, W7 [3 Z) K/ q7 F
  18.     {" {) o: J$ o8 Q) Q* E
  19.         return;' W' }% v( r+ i; V2 D
  20.     }
    + v7 x7 Q1 G- L7 H
  21. : y6 ?$ Z5 b) d" ?& ~' k
  22.     /* 擦除扇区操作 */
    9 e' n7 w4 c0 Z2 q) g3 W# E+ c
  23.     sf_SetCS(0);                                    /* 使能片选 */  [, `1 G! F1 S$ D1 Z" H, Y: N& Y
  24.     g_spiLen = 0;
    1 ~* n/ }7 R9 x) r. ]
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */: Q% V* s- B' J8 S5 R/ c
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */7 }, Q2 h6 X6 `: X; v3 c4 D; a7 V( b
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */
    . S) K* c5 \0 }  n
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
    + P5 u( X  o4 Z( H+ C
  29.     bsp_spiTransfer();
    ; b, @/ [2 v& r& Z+ l

  30. 7 n8 K; d' Y9 E$ h# Z/ j6 H
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */% L, R+ ~! i& k9 i+ u) |, b6 `' p
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++), b- h2 ^' @! G0 O  Q$ x! q
  33.     {
    9 ~: d7 N& H% H3 u, \
  34.         g_spiLen = SPI_BUFFER_SIZE;2 Y- C3 U# `, e* J* x0 z
  35.         bsp_spiTransfer();! W! n1 G, c3 i9 i, L, P& h; c

  36. + E) J1 x8 C7 X& v3 \
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);* r0 ~( `! ?8 k% k. s' A. P
  38.         _pBuf += SPI_BUFFER_SIZE;" X& P' z0 |; f- W9 P& {
  39.     }
    % r0 Z: s: W0 H; P  n
  40.   c; t. @3 W+ D, }9 A# r9 J8 ^6 j
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */
    - ~7 M. i( x( T2 c  F4 G  [$ E
  42.     if (rem > 0)
    + z5 Z+ A& N5 }3 A0 D& Q% J
  43.     {, ?  L3 ]* _2 W/ u$ m# Y
  44.         g_spiLen = rem;
    ( ~) Z6 R8 z* W! z* J
  45.         bsp_spiTransfer();
    ! z2 U6 ]- g3 ~' {

  46. 7 S* o4 T1 H! E
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    / n/ k9 F: L! w& Z% M& C
  48.     }1 h0 [  M0 e6 p3 }
  49. ) i9 K1 Y$ i0 K! o7 H* z: d
  50.     sf_SetCS(1);                                    /* 禁能片选 */
    2 n- f) B4 P+ j* s4 s5 L
  51. }
复制代码

/ @; ]# g9 y5 Y0 V% |$ ]
& n# a) |, H0 E' Q* T这个函数对DMA传输做了特别处理,方便分包进行。$ L' C( G1 Q8 A+ y' B7 y4 L
; \; Z$ Q$ G. e
73.4.6 第6步:W25QXX的扇区擦除实现
2 g- M0 a0 V/ G' d$ Z1 R
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。
! ^2 Y% J0 o0 o+ R/ w( t* W# ?% ~: H9 y3 {! y1 S
  1. /*
    4 K! r; n. I! J2 f* d2 _# z" L
  2. *********************************************************************************************************, o% v& d$ R- F5 q! K- i
  3. *    函 数 名: sf_EraseSector7 f% D/ G$ F; q# g/ Y! h1 o3 k
  4. *    功能说明: 擦除指定的扇区  s9 {0 H5 c. ?3 `, H  p. q" G# e
  5. *    形    参: _uiSectorAddr : 扇区地址
    + D, w# ~7 r4 u( r6 E
  6. *    返 回 值: 无
    - f( Y% S: T6 d" w# n
  7. *********************************************************************************************************
    - j( p6 Y: r% `& H4 J2 q
  8. */2 b" g! W1 @; Z: Y8 Z, Z2 n( X
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    , g  H1 F) G. ^: j! S$ M+ [% F
  10. {* l6 [/ V: e8 p, u+ @
  11.     sf_WriteEnable();                            /* 发送写使能命令 */2 R9 [5 w+ P6 W, j8 T3 l7 n
  12. 1 x( L% Q2 E& }8 k, i2 |1 u
  13.     /* 擦除扇区操作 */
    % @9 K& ~: h) z; Y) e2 T
  14.     sf_SetCS(0);                                /* 使能片选 */
    1 {# v7 f/ h: R0 l9 l! E7 x& C
  15.     g_spiLen = 0;" W; }/ E! |5 V$ W& M7 d. S
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    % W/ T( x: J' o4 T3 v
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */- t, [  Y! ]  C
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */8 z6 r. L# I0 K: W/ H) I, w: E
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */   
    8 ~. d: s& O0 D+ J, i5 t
  20.     bsp_spiTransfer();8 f" ]; n* j9 e4 j7 I
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    % |. W+ K: U+ X  A! U

  22. 8 `( U7 [' L* X3 }
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    3 ^! v! a- A; J9 o3 u- m9 u
  24. }
      m( J, y; |* v2 S: o9 Z
复制代码

# f: M  ?; N2 f$ C' [4 |) {7 o& g9 ]  |1 k) B! m8 ~* N
整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:6 [3 o) J% t5 J; [7 ^

+ G* c' z- v9 N- m1 K& U# c
  1. /*
    % f4 N! |, J% s  i2 x
  2. *********************************************************************************************************
    4 L( P5 k& ]7 t, G
  3. *    函 数 名: sf_EraseChip3 B5 |$ @: E. K; h, o+ j
  4. *    功能说明: 擦除整个芯片
    * ^$ ?, g/ J, r2 r. T0 D- s& w7 H
  5. *    形    参:  无
    ; @# E+ j5 B6 P+ U2 R
  6. *    返 回 值: 无2 V$ F+ ?# g& M( V- j
  7. *********************************************************************************************************1 ?7 t0 W$ _( V! `
  8. */# Q' J& B% e3 L" R) @0 }/ J
  9. void sf_EraseChip(void)
    % ]0 u' `% P7 c8 ^+ m1 o
  10. {    # z3 ]' u+ T( m. H% [) f
  11.     sf_WriteEnable();                                /* 发送写使能命令 */( G0 M) w6 U9 w# \. U* N3 @: [
  12. # K; ~0 Q& U% z7 R& }# }
  13.     /* 擦除扇区操作 */
    ' |& F7 G% w  \9 z( M6 a
  14.     sf_SetCS(0);        /* 使能片选 */
    0 f2 O% c% O' E9 e9 w  p
  15.     g_spiLen = 0;
    ' f& y- l6 v# Y8 o- n
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */0 U# w% C, G0 }: M% f9 M6 y4 n3 P
  17.     bsp_spiTransfer();
    8 k4 Q3 I+ l) z: d
  18.     sf_SetCS(1);                        /* 禁能片选 */% S+ {3 T$ v# Z

  19. * M7 q2 M" w: m2 g; f
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    ' J* p5 {( `3 _
  21. }" ^& N9 N* p- }4 m4 o# y5 o2 g
复制代码

7 ^0 A! M3 {* b" A73.4.7 第7步:W25QXX的编程实现
, D- \' |/ y: LW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
" [2 j( Q' r2 u( i; I4 o9 R2 x3 M, G! I, F, X) f  B4 w2 b+ T
  1. /*
    - p6 X6 @0 n  V! d; n; ^  w
  2. *********************************************************************************************************
    % |1 e- |9 ?* G$ Y
  3. *    函 数 名: sf_WriteBuffer  Z; u0 ?- |2 b8 h! ~$ y0 n. [
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。5 r* W. \" ^( R  _: B; n6 J
  5. *    形    参:  _pBuf : 数据源缓冲区;
    2 X7 j: P2 V( h) L0 t
  6. *               _uiWrAddr :目标区域首地址
    % H: w  a: a0 n9 I! }0 O
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。
    * x% k; n( z  o$ K- B; o
  8. *    返 回 值: 1 : 成功, 0 : 失败
    - Q4 X% ?- \. p# C, w" O
  9. *********************************************************************************************************( k, j9 T( T  o
  10. */
    3 }0 _+ W- [8 S7 M. A8 L! s  T
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize), [3 \2 }3 R* p/ p* t2 v. g9 B
  12. {
    ( G, ~5 d7 t, s1 |
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    ; i& [! F( P, Z* \/ N7 o# k7 W# p
  14. ' a+ B0 g  s4 {
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;
    , g( E2 u" w6 w+ N4 O
  16.     count = g_tSF.SectorSize - Addr;- [/ E( o! }( }! R# Q" Z' V
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    + K0 G( y! @0 m! V0 c
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    ( S) r# F$ |7 v+ h3 M
  19. 1 ~$ b5 Z, X+ Y* S0 o5 s; @# n
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */' u) C' Z( w/ ^3 O8 [+ f
  21.     {
    ) X# {( q+ \: k" C8 d/ p
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */- b1 k4 D  m* t( ?+ U$ e. ]! ~
  23.         {# `6 c7 `, d' P* c/ S
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0). J8 j' ^0 \" t* ]' |
  25.             {
      x! L: Q. x# ~8 H5 C1 ~: O/ h
  26.                 return 0;# d0 w; w. K& H: T* D' Z% Z
  27.             }
    # Z: P, a7 f/ h( j7 x
  28.         }
    + s- ~: t: H1 j3 `
  29.         else     /* 数据长度大于等于扇区大小 */
    . h+ l2 ^0 h0 v. o, C% C6 J
  30.         {
    . j4 ~% }0 y4 c8 B) B! P; J/ o
  31.             while (NumOfPage--), l2 t5 T6 _& q* n4 x0 E
  32.             {' l6 n( q* j: r8 O9 u$ `- [( _; U
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0). ?3 [) P, q9 {/ F
  34.                 {
    2 f9 b  w. K  I) T! ~$ b- r2 L
  35.                     return 0;1 U7 i4 C0 A! I/ M
  36.                 }4 }& n6 S- H7 i4 n) b
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;/ ~: S/ }5 J% b3 J  i6 b$ v  G2 T
  38.                 _pBuf += g_tSF.SectorSize;
    " g% J' H3 Z5 s  J( I* I
  39.             }
    9 f' [1 n: \- W* b3 c
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    5 e8 Q* ^3 ~% T7 A2 O
  41.             {
    ' `0 L9 U! k: J5 _
  42.                 return 0;. g1 L4 N0 ^' S% E1 W
  43.             }
    1 K7 \5 P2 L6 m/ r+ u
  44.         }
    % L  b( d- h3 ^; P: _' O! T9 N
  45.     }; ?; h) [, r: {. e+ P8 G7 N
  46.     else  /* 起始地址不是扇区首地址  */
    6 R1 s- ~9 K9 }9 t" ^
  47.     {- V$ Z9 b) M* b! T- ?4 f$ |
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */* [# V  Z" p4 p* z+ n  |8 F
  49.         {
    ) O7 h9 U/ l6 [6 s' b
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */
    5 D- j- i" g- [% f
  51.             {+ T! d' c+ E; W* d. w0 L4 e+ ]! M3 ?
  52.                 temp = NumOfSingle - count;; L  Z7 y6 C1 z+ D/ f

  53. 4 m, R" h  t0 h4 p: E0 ~0 B, [
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    / \: f  }  n3 A& k# C( B3 `
  55.                 {& u7 V1 S, I+ Y6 ]1 B
  56.                     return 0;' |" u, D% w3 w7 v9 w
  57.                 }
    % F( a' O+ G2 B

  58. ( R, Q' c8 i7 N- x% ^" i; I
  59.                 _uiWriteAddr +=  count;$ W& ^1 Y2 w) ]0 u7 ^
  60.                 _pBuf += count;. O9 {+ u- ^# y* U2 Z) V7 g5 Y& Y
  61. 8 S. ^3 Y3 j5 e% Q
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)
    ; ?! E/ H2 Z% b" {/ z5 `) A
  63.                 {
    3 J( Q2 T, b! @! l5 }+ c* O% t
  64.                     return 0;/ X2 V4 Z4 w: Q- t+ e$ }; \2 o
  65.                 }
    0 t- `  }! k: `3 z- {
  66.             }
    4 W! f& x! l3 q& B2 P
  67.             else3 F% s& e7 [- V6 a
  68.             {
    3 n; e* I9 z9 i8 j" C
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    1 K3 p* F* u! l. w7 a$ n3 M
  70.                 {" t+ d0 i% N  x- y$ \7 t
  71.                     return 0;
    : `8 z9 r1 B1 |( W
  72.                 }
    " ]2 u9 V. V' F3 ?; R. c$ |1 }8 s
  73.             }0 d5 B! [( T% H* f$ F
  74.         }
    7 h% P6 ^: s  t3 B: `  `
  75.         else    /* 数据长度大于等于扇区大小 */7 f4 B$ w  V, s8 a& G( p
  76.         {7 o1 P+ s- c2 _7 |
  77.             _usWriteSize -= count;: ]" f( f4 o7 k' C
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;3 O3 ~. ^3 N: q  n: c9 x" h
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;0 O/ [& O1 o& ?8 ^4 L) V
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)6 D! C; J1 r* t, C
  81.             {
    : A) i* |; U/ Y) ?! V$ x# S; }
  82.                 return 0;: k) l: ^, X( t4 e8 u+ o: C: q! |
  83.             }6 m. w2 z+ k0 Q% l

  84. & D$ Y  n4 ~: \) R* e: X, F
  85.             _uiWriteAddr +=  count;5 `" @$ m% L: z: w
  86.             _pBuf += count;
    ; A5 n. K* M% Y8 Q# r
  87. # g" J( T% ~# t' q6 K& E# \3 o
  88.             while (NumOfPage--)4 H' S9 f0 K) K
  89.             {; ?$ s+ ^! ^# D. Z% h4 Y* h% V
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)4 k! D4 X" N+ m; ]$ m0 D
  91.                 {$ c1 R- N9 ^0 R+ y% `
  92.                     return 0;; e9 O- I9 j6 Z, v5 g
  93.                 }; P2 a/ `1 I, [8 C7 H
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;, a9 z( H/ V# m- |
  95.                 _pBuf += g_tSF.SectorSize;
    ! _( s& s$ l) x/ A7 d( Z
  96.             }
    + i) H( w: H1 m; O1 Q  e
  97. 9 g0 Y2 \4 |# w
  98.             if (NumOfSingle != 0)2 Q# F" S. Z, }' n( ?
  99.             {, h' k9 o4 |5 Z9 ?' s% ]7 y
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    3 c; E# M; K: c2 C
  101.                 {( b6 S! H, O) I/ k0 H. P& l' y, h; r
  102.                     return 0;2 i+ Y0 s6 d' A7 y
  103.                 }
    . t$ A* {8 Q/ k9 W
  104.             }
    : |) r" _  E* _& e* t
  105.         }" x+ h2 g% d. Y+ P$ d
  106.     }  M/ Q' S/ |( ~3 I# s& l
  107.     return 1;    /* 成功 */6 i, @/ E( T+ m* v/ y& \
  108. }
复制代码
% p2 D, z0 Z0 w  P
73.5 SPI总线板级支持包(bsp_spi_bus.c)
3 G) Z0 v8 v4 Y1 O4 F) x! tSPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:# a8 {; @$ r6 o! R1 p, n
' ?# {9 S; d( y6 {
  bsp_InitSPIBus
) P) J' E2 a1 l  bsp_InitSPIParam% a- P' w6 L) x4 Y6 \4 O
  bsp_spiTransfer# g4 K: Q# w9 H/ _
73.5.1 函数bsp_InitSPIBus
) ^' w; ]' u# L0 A: i函数原型:
, v8 l% Z, `. U) T% K2 {9 H, z3 J# G( B' J  I4 J$ ]; N
void bsp_InitSPIBus(void). H4 ~/ d1 v# H5 I2 \) e: G2 D
  c9 n) e8 t; x
函数描述:
, W6 G5 l8 c- N$ q" |, v# ^  f* }1 Q+ _) k- f
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
+ J4 X1 ^5 R# y- ]
" b& p8 z# _/ X/ L5 ~73.5.2 函数bsp_InitSPIParam7 Y4 h/ U) x4 K' E7 x8 F
函数原型:
4 ?& ]% Y: x% L2 S, J; L  R
. L2 s  e% f! I5 q% s+ jvoid bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
, _/ B  G+ G4 n" d$ f! [1 r2 w! }4 F4 K: p& T  V
函数描述:- T/ Z) A% D" v; T

; w6 I8 I+ i( f9 X此函数用于SPI总线的配置。- Z$ E) O7 c9 w9 H

$ d$ r( j* n# W: K$ w函数参数:
: ^: c# ?# ~& v
+ I3 `8 f2 C* m& l4 c; E4 K  第1个参数SPI总线的分频设置,支持的参数如下:
  i! ]0 I7 i5 p- K$ ^, fSPI_BAUDRATEPRESCALER_2    2分频
1 g; r) u, ]7 u: }
+ A4 U, {; k/ Q' k  |5 `SPI_BAUDRATEPRESCALER_4    4分频* c0 G8 I- q8 T4 @+ J) ^
4 d, R6 ^3 Z  v( O
SPI_BAUDRATEPRESCALER_8    8分频
$ _0 Q  _) H# ]2 P* [# E
$ H# i7 e; K8 R5 c* `SPI_BAUDRATEPRESCALER_16   16分频
3 q: X! t$ M; J4 U% |! y6 i7 h7 n+ Z5 L; q  _
SPI_BAUDRATEPRESCALER_32   32分频
! _1 w; I& B7 L: q1 o) g0 ?% T
) }$ b0 \" d6 a2 ?  [; TSPI_BAUDRATEPRESCALER_64   64分频
, a/ H! P# M: h" p& f
" |, a# p. i1 H) LSPI_BAUDRATEPRESCALER_128  128分频
* R6 P: y! w) G3 }' P. c; r
# B9 B- a5 ~+ q, _2 tSPI_BAUDRATEPRESCALER_256  256分频
$ m  Y7 m* ?1 j6 w
  w7 y; J" U5 n  第2个参数用于时钟相位配置,支持的参数如下:  e4 C2 X2 x+ Z. {; W
SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据" H$ l0 I! |/ q0 |% S. H
( D1 E+ g( C) Z5 d& P+ L5 }9 O
SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
+ `% e: l0 [* D0 n0 o6 ?* ]1 B2 V- z2 @, `
  第3个参数是时钟极性配置,支持的参数如下:
' u% k5 l* i7 M4 RSPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平
9 N1 O; Y$ ^; N3 z9 \/ F( H1 w+ S7 P- {- J3 m: A" ~% P
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平7 G9 r( V: |4 c& c4 D9 S% Z

( ?, H+ r) h6 U# b6 k# L73.5.3 函数bsp_spiTransfer
- g0 _! m- b8 P$ n, o- r
函数原型:% b4 m! ]' \2 i* F, z# A
  }2 f1 ^# L/ J
void bsp_spiTransfer(void)
+ X1 Q! M$ w9 J1 }+ n. t6 V+ z, v) S$ Q- Z
函数描述:
' z" Y  I7 v: Y
$ e+ V; j3 M& T' ~4 @) _此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。- J  P4 F0 w5 l) I4 r  c
" H- c, X, }; y
73.6 W25QXX板级支持包(bsp_spi_flash.c)! d1 N+ o3 T: z: Z3 p5 L
W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:
7 R9 m7 e% K) B( P, f7 V( T" {9 h4 S2 b  E; Q* F
  sf_ReadBuffer$ K* ~3 C8 L& i: V. E. n' Q3 P* p! W
  sf_WriteBuffer1 z! V* ^% N3 H: c
  sf_EraseSector
4 D. {. I7 W; e7 Q  sf_EraseChip) z2 s5 t- D) R* h6 m
  sf_EraseSector9 J( J& l) t; E% a" P
73.6.1 函数sf_ReadBuffer
2 N1 L5 T! K6 z% P4 ^! M% @7 ]% M! o函数原型:6 a" L0 m, b, H$ K4 L9 y
# M9 c! W/ F: Z4 n5 G$ ]5 @
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
5 e; _! E/ J. h$ I- h
. ]$ O# S5 p4 o" \3 h, a函数描述:. p8 l8 g0 A3 V2 E" ^( a7 c' Q

% e. H3 Z# D  {5 P8 m# W/ R( _此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。# w) _1 M+ S2 {$ N

5 ~9 y2 [, a: ?函数参数:' n9 \  K/ p( o4 w: \
( S3 T* b( f1 h
  第1个参数用于存储从SPI Flash读取的数据。
- @  E2 `. a7 k  第2个参数是读取地址,不可以超过芯片容量。
% m, e" }% ~3 L9 s7 ^! a  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
, J% w3 E$ h; ]- a) }4 \7 X73.6.2 函数sf_WriteBuffer(自动执行擦除)
2 ]: H; F$ J! y% J  b7 K函数原型:0 |4 e+ N- z# U8 m0 V. W$ u% T2 r
5 {4 R/ b0 O. \/ q5 G7 s
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)* l0 k' ]) i7 I3 X& o
) Z6 {9 u) q, `- b% {
函数描述:
& C& c, X; n; i& y1 {: d; y+ V5 u/ e+ r+ `6 L( _; H9 e: ]
此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。+ U  O# g% ]" S9 p

, w2 `. E4 |3 \! U8 S  r函数参数:, Q0 O, a+ g" F- C9 g1 B
( ?/ ~  {* [7 ^. d( T5 |7 x2 c, G
  第1个参数是源数据缓冲区。4 r9 p; x  ^2 M1 \3 n* z) |
  第2个参数是目标区域首地址。
/ J2 D2 d% v' V: e' i9 U8 X0 t  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。, B6 w7 o- G( f* p% L9 O
  返回值,返回1表示成功,返回0表示失败。
# a5 U; C' H! s3 G% Y73.6.3 函数sf_EraseSector+ _% H0 O1 t% q, _( B
函数原型:
4 R6 {2 [( z7 J7 \( L$ W8 A8 j
7 o5 c9 h3 N" k4 ~  I- X8 yvoid sf_EraseSector(uint32_t _uiSectorAddr)+ d* h" A; h5 k/ R8 d* [5 N) ^
- w( A  Z! |) R( r0 ^9 ~
函数描述:: q5 G5 W. B) K0 P
' U% |) ~% u( w, f: J
此函数主要用于扇区擦除,一个扇区大小是4KB。2 G+ Y, o) L4 ~- d; [

* X$ m. o- c3 i, l) m函数参数:
( T6 x7 m& ^7 k% ?$ `& A# I' E, A. w) Q0 Y' u& B2 x3 s
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
& D3 y8 ^' Z- ]: h# O' G7 i! [. U73.6.4 函数sf_EraseChip# |/ M8 t7 T# {" L) A( }8 C
函数原型:6 O, P8 [$ o7 s
; P, _9 y' S& x
void sf_EraseChip(void)$ C, h$ _* t# z: B
. `2 H: @. W( @0 ]8 S' d" S1 {. d6 s
函数描述:
8 @# T- I5 y* e& k: `  O8 r0 K% P& J" x. r# X* j. H5 c) t
此函数主要用于整个芯片擦除。
* n7 E- r9 W2 F, [6 p9 J
2 _6 b: K$ l! l73.6.5 函数sf_PageWrite(不推荐)( H3 R9 ?8 Z$ L
函数原型:
- T) B. \, Q- ~, }( ?  r0 _! O/ v& {+ n9 R! _0 N
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)( t/ ^) I+ g( d9 P

/ V8 @  m" M9 z3 ^函数描述:/ {. p7 T% q4 B8 L) S+ q

2 L0 f: [, i+ P5 \. `3 z; G此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。7 n  p- H5 A- N

8 K+ a  f1 z( s: ~) V9 d函数参数:
$ X$ m+ h3 W9 `4 c! ?. S- P1 m; j& M1 r- _  x/ }
  第1个参数是数据源缓冲区。
$ c/ E: h* H% r  c1 j- V  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。2 b9 ?* p* M, l
  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。/ F1 x) N" [- i7 f; H* L: w7 y
73.7 W25QXX驱动移植和使用% Y, @$ h% E# z, ?
W25QXX移植步骤如下:5 H3 u# a& G: R- W6 M

7 r4 S% [: A8 M( ]0 R7 ]/ d, }  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。% Z6 u- i0 G" r4 _( d' M# D8 @
  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
. P7 H" p( C+ S  Q7 b: y
  1. /*
    , l& h' b" s& A
  2. *********************************************************************************************************
    4 r) K* Y2 f; @% [) k2 p; O3 O
  3. *                                时钟,引脚,DMA,中断等宏定义
    . t* X! R+ z4 d. b: a
  4. *********************************************************************************************************  r6 c" x. }4 ~1 W% Y, l3 T% ]
  5. */
    , i# g( }, E( @4 y4 [5 G
  6. #define SPIx                            SPI1
    0 m! Z5 ^+ I- l* L- X% J% @
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    " ^0 s8 [- F7 ]
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()1 C1 d5 t- `2 e# ^# }* p
  9. # l; [3 y% E! D( l
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()3 f' S2 R! c% Z
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()! w- J, {9 z8 ~: i
  12. 7 Q# A' n' K& a7 U% F; P5 K
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()- F) g' c9 E- ?; v5 A6 h( D/ \5 s
  14. #define SPIx_SCK_GPIO                    GPIOB+ j9 Z' w- Q; j
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3
    : N; |* M1 g: E$ H' ]' {% v! k/ x
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1
    % k! Y* N/ x1 q( G/ Y8 M
  17. , S* ~# j2 |) M0 H- m6 a* o! w6 }
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()8 q& g/ h) L' R% Q2 u
  19. #define SPIx_MISO_GPIO                    GPIOB' Q  C6 p3 @7 G+ O( O1 a
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    ' ]8 m$ q, M6 A  |! L% T
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1- D0 }  e5 Z! b0 x: _8 E6 e

  22. - H9 \# E* o9 i! E3 K- h; M
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()2 }& h' O: X$ l# u( T
  24. #define SPIx_MOSI_GPIO                    GPIOB% f: z. ?5 |1 Y
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_58 S+ \* E- x: `/ G
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1
    , |) {1 s, L+ f- q% C

  27. 3 u2 i$ w+ _4 [& e: C  S
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream31 o/ d3 H% N9 J6 K4 U
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    / |" V: h: p: i* j8 g

  30. 9 Z4 a. J2 C2 a/ w* ~: b$ K
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX$ R* J$ q. @( Y9 y  P- D) I9 ~
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    " s/ M% q! [+ W1 Q
  33. 8 ~0 r$ x# k- o& B: }
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
    . W1 K$ v# A" a1 ]2 j
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn
    + Y! k# L, q: D: a) Q, g- l

  36. ( H, i! d0 I1 B5 L
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    4 D; `5 t  P6 f2 P
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler8 S* _2 {4 o; d7 h; t5 \

  39. 1 v! o9 w9 s  b0 H% J3 }5 \/ ?
  40. #define SPIx_IRQn                        SPI1_IRQn
    5 I6 c( a5 p, N
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler1 u2 ?5 p  l* o
复制代码

& U4 B- N! _; F) A! p3 ]- |$ C7 D# ^; S  P
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
* y( L* U- i" h) j/ U
  1. /* 定义串行Flash ID *// H+ v# D+ k: U/ n
  2. enum3 _( u6 ~8 F& {; c' ?
  3. {
    / K8 K+ O5 {  S1 Q2 J: R
  4.     SST25VF016B_ID = 0xBF2541,: T/ o; B  G; G
  5.     MX25L1606E_ID  = 0xC22015,( o7 h, ]) \9 f4 b/ K1 i
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    " `$ O7 @  t  t# N% X
  7.     W25Q128_ID     = 0xEF4018' c8 C) {8 O; d* ?
  8. };
复制代码

9 v8 V) \  x4 J  q( _; H  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
. Z$ a: J5 |# a! j
  1. /*
    : s2 X7 [# ?6 a# \! n  E" B
  2. *********************************************************************************************************
    9 J7 F$ I1 y  ~) [5 ]
  3. *    函 数 名: sf_ReadInfo8 M% D. b* Z: X  J
  4. *    功能说明: 读取器件ID,并填充器件参数
    4 o$ j4 ]- u2 c
  5. *    形    参: 无
    0 b# G3 Y2 n' R! _8 p% }" J
  6. *    返 回 值: 无' h6 ~6 Y% R% M
  7. *********************************************************************************************************
    + k% U! J8 D8 U; Y' i
  8. */
    3 }4 a2 y& W3 ]: }6 y
  9. void sf_ReadInfo(void)
    0 T7 b1 S5 q, t6 n7 f  D1 |1 ^* `
  10. {3 _/ v+ O, H+ t: r
  11.     /* 自动识别串行Flash型号 */4 D4 a: Q  m' z( F" `( t) B3 `
  12.     {
    + n1 r4 ?$ h7 y& ^' \
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */5 P+ |; Q1 V4 q, {5 n

  14. ; j2 b% c* |0 s
  15.         switch (g_tSF.ChipID)9 {. U- |. y& z  v% \3 R
  16.         {
    7 y! |' F. i) i# ?8 c: ?
  17.             case SST25VF016B_ID:; ]% w+ t- `5 p# [9 Q
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");  z/ n0 u# s/ X+ M( t
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */" H6 r5 r" N0 ]& x# C( q  `0 B0 x! A7 U8 f
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */: _& a; M# ^& y- f
  21.                 break;
      R! V/ I" G3 F, x) z+ ^
  22. & H9 V* `/ [  G/ X% u
  23.             case MX25L1606E_ID:9 T6 J& [( ~3 s# H+ ]) |
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");, ]* [4 a6 S1 [8 c
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    9 b( i, J- C% F; L0 h8 w8 N8 B
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    - X6 @. o7 L" S) d$ O" l; x
  27.                 break;
    5 y2 l) u) O; s$ B* C! O( o
  28. + p; S. k8 T- _/ V
  29.             case W25Q64BV_ID:
    # ~" A' W9 V" C; Q2 g- n
  30.                 strcpy(g_tSF.ChipName, "W25Q64");
    5 k) A; x# u# @2 H1 ~. J; k
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */2 N  o/ b' C4 a" j
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */0 R9 H, V  o7 ]- M
  33.                 break;6 A2 _& ]  a6 r- r  L9 D! H
  34. ) M, M7 G* v2 \  n+ w: I
  35.             case W25Q128_ID:, I6 X. U( u" w! C) N' K
  36.                 strcpy(g_tSF.ChipName, "W25Q128");$ p( H$ v0 K- t9 W% S$ [( [% A$ J
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
    1 s4 o1 Z8 ]$ V$ L/ l: C7 S
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */0 N# }8 }2 T" Z. q) a
  39.                 break;            
    " C/ c8 {1 c2 x8 A1 Q* u
  40. 0 h- G( o8 }& f! D0 y- N7 A
  41.             default:. ]0 U5 l$ S! w* r2 I0 g" b
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");
    ) @  S3 G/ Q/ ]' H
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;8 _, ~  h- R5 _. j, T# ~- c( l
  44.                 g_tSF.SectorSize = 4 * 1024;# u4 T; _7 f" Z. V
  45.                 break;
    + H6 a2 g! L- l- X3 g+ `' n
  46.         }+ }% T+ Z! v) M- R( I' d$ G) K, k
  47.     }
    4 ^# B0 K' @+ x# r6 N; g
  48. }
复制代码

. N1 {2 c$ u9 u+ Q0 |( c( R  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。4 b( g6 G5 Y5 l7 q7 ~1 W% X0 n4 f
  1. /*
      J# S/ c1 |2 _+ F% o+ l
  2. *********************************************************************************************************! a: q# }. Q) h: S# F9 J9 \
  3. *    函 数 名: sf_SetCS
    0 U4 R7 Q8 n% I% w/ t1 f4 J
  4. *    功能说明: 串行FALSH片选控制函数6 k( U5 Q7 O, q* x
  5. *    形    参: 无
    1 ?9 D! A# q8 E0 P1 ?
  6. *    返 回 值: 无
    * d- l$ r# |, q1 S
  7. *********************************************************************************************************
    & m4 \( R* q2 ]" m+ `" \2 N7 s
  8. */! c4 q9 D0 u0 U
  9. void sf_SetCS(uint8_t _Level)
    * s  l! ^2 g1 l/ E
  10. {
    / ?4 I1 \7 v0 Y+ o4 G
  11.     if (_Level == 0)
    $ ?# V7 }( Y+ Q
  12.     {
    1 G" ]3 r! j; Y3 `
  13.         bsp_SpiBusEnter();   
    ) F. h2 o4 p0 `* M" e) k7 Y
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    6 z3 R6 @8 v/ l/ y% K. T
  15.         SF_CS_0();" }$ S# X1 o% q. i% l
  16.     }
    ' j* u( \0 n; D( B2 Z
  17.     else5 D; w; [; d- F. g/ M
  18.     {        
    " U. w. c$ N7 E% F# R# z: U
  19.         SF_CS_1();   
    " C. \6 d% d+ X$ v) s. A: ~. W
  20.         bsp_SpiBusExit();        
    # M! Y! V2 r8 `" X) W5 f
  21.     }
    4 O  a. F. g. ~! E4 V6 U
  22. }
复制代码
9 V% n, ]. ^" \: W7 l
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。1 m8 N' Y6 ^' K/ P" m* m! I
  1. /* 串行Flash的片选GPIO端口, PD13  */
    % ~4 \, D# i' \' x9 L, s9 u
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    ' h' `; Q! F7 h; f. `
  3. #define SF_CS_GPIO                    GPIOD  n0 e" j, W; t5 g
  4. #define SF_CS_PIN                    GPIO_PIN_13
    4 b& e9 z5 L; P" X: F

  5. ' ~' o- A3 A* i2 ?( G
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) : c5 l" P' v$ N: j  A
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码

* m0 T2 k9 I/ h+ E: K  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
* r$ ~; t  B5 c
  1. /* 配置SRAM4的MPU属性为Non-cacheable */( {3 u# D' w: t. `
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    9 l. v4 j. `; V4 c% l
  3. MPU_InitStruct.BaseAddress      = 0x38000000;
    ' }9 w5 X- Z9 c- T+ |
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;1 g+ h3 t2 y, a. V6 Y
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    2 i3 f" f. c  S( l
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    7 i5 O/ B/ W5 H, C2 j( w' T
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    / y: X& O- `$ z/ W
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    7 i# ~; ?2 Z. F; I, |
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;4 K3 H0 Q& s/ i4 w  `: K& o- A
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    2 Q: X+ J1 r7 f/ W/ \
  11. MPU_InitStruct.SubRegionDisable = 0x00;6 @* S7 |9 R" e9 {3 M/ R
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . b/ Y' O' G5 }- n) R' t" O

  13. + Z6 |, H1 n/ t' N
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
8 r, f# F/ I' ]
  第8步:初始化SPI。3 m% A, @& Q; r
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    : Y& t3 |, S/ v! p# q$ C
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        + n! A- V2 [4 `$ U8 {0 u$ T
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码
+ \9 z1 U; e( H
  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。3 }" h3 F6 x" o$ S7 S
  第10步:应用方法看本章节配套例子即可。
" U' R" }: `* t5 Q1 ?- ^: l( L73.8 实验例程设计框架, L5 w/ o: g, a5 d
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:, s% J' @7 `) K* @! I& f

- O1 A1 {, O$ `' G1 c, n: P
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

5 m! [+ q$ d; u: K  F0 r# Q& x( z; V- L& R
  第1阶段,上电启动阶段:1 B' T9 p4 v/ b+ j# L
" g% Z6 w9 i4 D0 |7 h* \" i
这部分在第14章进行了详细说明。
+ [/ e; n& M7 ^( m, A  
" b" k- B& l/ w第2阶段,进入main函数:
8 r" [3 a6 n  M# s- J( j
4 z+ G2 g9 H6 o第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。+ l# v8 _+ T7 q2 z8 g. |
第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
  b; R/ k5 Z. _5 }+ F5 X9 `! P5 W; e
73.9 实验例程说明(MDK)
3 e3 [2 s; S' P1 h配套例子:: h4 T0 @  n' B1 h) M
, ]# p9 ^. b6 @( S
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
5 V4 z+ M3 I2 \7 X8 w& A/ R/ O) T5 p. V) B
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)6 y* ~  Q4 ]( H

! @8 v8 P4 r  ?. B% U; jV7-051_串行SPI Flash W25QXX读写例程(中断方式)
$ F( h, {. ?0 k6 N
; O2 S) o8 ^9 F0 |9 R实验目的:: W) w7 e% W. r

9 r, S7 ^% n" m+ Y学习SPI Flash的读写实现,支持查询,中断和DMA方式。% E( w. w% t! ^3 H! K3 U

3 L: r7 Q# A& m. i! u实验操作:" N6 v# q+ R% f) k: a0 w/ }/ R3 }
0 W; S+ T/ N& ~% L4 i( h
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
. V% w' y0 E. i, s" zprintf("请选择操作命令:\r\n");: Q' P  X% L$ M! l7 K
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
3 X1 E/ J- _6 `8 m$ a) R& k" kprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
8 X1 f# F# }5 `; l( w" [printf("【3 - 擦除整个串行Flash】\r\n");. i- Q+ L5 `6 E: x: c8 R% E
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
/ g5 k; b* I2 c! \! ~& g0 N) C9 aprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");4 i' K0 s# ^2 g; q
printf("【Z - 读取前1K,地址自动减少】\r\n");6 x8 E: K# k# d8 _0 ^
printf("【X - 读取后1K,地址自动增加】\r\n");
8 c- K) t, y4 ^: G- |* hprintf("其他任意键 - 显示命令提示\r\n");) t- f) d5 K2 R4 r: Z2 F# R2 d6 i  P
上电后串口打印的信息:
( f- E+ c% q2 Z/ l% ]
- _  m( r! Z5 \) X波特率 115200,数据位 8,奇偶校验位无,停止位 1。+ j1 h1 ?, t& F$ K- f8 ?& f
( K4 n% U+ C7 o5 L! p# q
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

' f; y% W, E$ D5 m2 b0 i8 z5 H8 t/ [9 e. C3 K+ l# ~4 ]+ E9 U
程序设计:# J( g' p& s+ q/ B+ L# R) V

0 F" h" y, c$ t+ D+ {  系统栈大小分配:
4 }. p4 Q/ X. U$ E
" r, l! s; N8 U$ d
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
: f# T2 r& u/ z: ~9 }

; y% S. U; f) n4 d$ F  RAM空间用的DTCM:
- t0 P" ]- ~2 j7 \
7 X( o( o9 n' x3 v$ j4 q
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
# @2 Z# y/ z- a/ m1 B1 Y! [
" z- t' |3 Q. `/ f$ q1 Q
  硬件外设初始化% u) V7 n- u: l4 E- K

1 q7 @7 l5 Q, @3 X5 y, Z硬件外设的初始化是在 bsp.c 文件实现:
& J: h: @$ C5 ^3 Z# f, I; Y1 e  ~/ p; K7 q+ \
  1. /*
    4 b0 `* r+ k9 A( i% Q
  2. *********************************************************************************************************+ t; m" M# T* j- D0 |9 ?; S0 v
  3. *    函 数 名: bsp_Init9 m5 o1 M! A* }/ r1 @8 ~7 i3 Z0 Y
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次/ r- Z( D. s( S' Z/ x
  5. *    形    参:无( N+ p1 s& N2 e
  6. *    返 回 值: 无
    ) s+ Q, o8 n; s! ^2 X  l6 y# ]
  7. *********************************************************************************************************9 W$ `; ~1 Y: I- ^3 N( ?$ @
  8. */; \1 `" ?. w; ^4 w5 D
  9. void bsp_Init(void)4 \. N9 K+ D/ a+ ~/ B
  10. {6 B7 P0 W! w4 m
  11.     /* 配置MPU */+ Y& v1 ^5 A( P& H+ D+ {
  12.     MPU_Config();
    1 y. q9 p- }7 u9 {

  13. ( C$ \/ v; P# I6 A. ~, Q
  14.     /* 使能L1 Cache */
    / g4 ^# a0 n8 L8 G  _- T3 f" U
  15.     CPU_CACHE_Enable();
    , a* o2 r5 O* I2 e) K- N# X( b

  16. ! i9 t) z) |* H
  17.     /*
    " }) z, B3 i3 P4 E
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:" u  m1 `9 p4 k
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。5 i4 T9 C3 u3 M2 ^5 N8 z
  20.        - 设置NVIV优先级分组为4。7 t4 e) ^2 a5 _
  21.      */
    8 N: X1 L4 r$ Q  Y2 ~! D+ O% U" S
  22.     HAL_Init();4 J- B% J+ z' r3 i# B! c
  23. ' [8 E7 u% @: J. D# e9 S5 f' y# O6 {: U
  24.     /*
    ' {% h5 P; r2 @4 L! M! A# P" A! _
  25.        配置系统时钟到400MHz
      ^; ^* Y! ?+ d* c; @8 K
  26.        - 切换使用HSE。$ w7 C# {5 Y2 w( S
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。& Z0 s6 u: k; C" N
  28.     */" @2 n$ T0 x3 Y+ R+ W! |" F
  29.     SystemClock_Config();* Z  \5 t. y5 ?7 w5 R) _# a: h. `

  30. - S( y" S# N. T
  31.     /*
    ! W0 L, `3 B7 m3 s0 o% d
  32.        Event Recorder:
    8 o0 l  a: F! |' ~1 ^9 f! w
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。5 f# M3 E$ t/ `! _8 v% ~1 c5 _+ K  Y; }
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章0 L  m" `. {3 g0 A8 R
  35.     */    - v8 K" t) _# Q$ Y0 g$ f( D1 k
  36. #if Enable_EventRecorder == 1  
    , x9 y% s+ E3 G0 x8 U% c' _
  37.     /* 初始化EventRecorder并开启 */+ H1 X8 k" Y. b1 k
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    ! ]$ b2 I! p7 e$ p- o* b% i$ D
  39.     EventRecorderStart();
      s+ n0 n8 h8 m8 {8 W
  40. #endif2 G5 Z- J! y* S* @' @; y+ q

  41. % [* ^9 _' `! x' C: t
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    % z) f, b6 L! U  E3 O+ k
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */: M3 n  a, D: y5 {6 t; F
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    " k0 a; V8 |7 w& v( o
  45.     bsp_InitLPUart();     /* 初始化串口 */
    . C1 I5 S: W8 Z
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    6 B; v) C) s5 K" X/ j
  47.     bsp_InitLed();         /* 初始化LED */    $ W# ?" |( ]4 |$ @
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    $ t5 d6 }7 R2 `
  49. 2 o% K. \, x4 D( j, j
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */( }! i2 F* g! t6 Q5 m' ?4 C0 @
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        5 t& y' N! X9 y" f6 y
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */$ f; v, H; ~- D; |; w' T) }; h/ [
  53. }
复制代码

* K  X# q# [3 R6 _: d. Y6 ]  MPU配置和Cache配置:
, A' x* M6 U2 Q* `( \+ ^% j
+ x+ X+ a1 E7 o& ]8 K0 h" V数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM41 ?) y+ I8 D1 r4 k3 a; I: J- F
+ V8 A1 C- e- s2 H
  1. /*& H" v+ Q- ^  J7 i- {4 k& ~
  2. *********************************************************************************************************
    . G/ U2 }. ]4 ^
  3. *    函 数 名: MPU_Config
    : Z; Z+ z* v7 s. J
  4. *    功能说明: 配置MPU
    4 l" t: e0 e, f
  5. *    形    参: 无
    + ^; a0 X( H" U2 e+ U% Q
  6. *    返 回 值: 无
    ; b. b, a6 {# x9 u3 J8 b
  7. *********************************************************************************************************! {- N7 h1 k$ d' _/ y, \8 f- Z
  8. */
    3 S5 |( e$ G$ D9 u2 G* m- Q& t3 \
  9. static void MPU_Config( void )
    1 m. j8 k( H+ k2 I  d  }% w. s
  10. {. u+ {* Q7 X2 V2 L/ l% a
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    1 s1 A6 k( \& Y- S+ t& k
  12. / ~, c6 S+ x& |2 w0 F5 W
  13.     /* 禁止 MPU */
      ?6 q  v. L9 B
  14.     HAL_MPU_Disable();
    9 w, n  e$ [- {# c, y$ K

  15. & a% W& S6 J3 |: c) `6 P
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */+ k! F* p, N' T- _- O
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;" W) |9 @2 X  m# N. g$ b
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    ' M/ q2 v0 u7 _7 P8 H+ l
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    4 [% [1 L- K7 ]4 Z% S" R7 K
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    / Z0 m5 C5 [+ t- T& ]
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;4 W$ f7 z6 O. {7 H. y2 l
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;5 i. P& t6 Y' [5 l
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    " d1 P! N5 E- _
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;( Z: m: }3 p8 o3 G' E* y
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ! d* h7 r& M/ }6 l
  26.     MPU_InitStruct.SubRegionDisable = 0x00;9 }2 R* ~7 }, G+ u" q% C
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    * f. h, x. }; I4 w
  28. 6 ?7 q; x8 i  I$ F5 Z! T8 d" c$ `
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    & I* t1 a5 P2 q# p9 u3 Z3 S
  30. 5 S7 ~8 ~! d& X# Z/ g$ P$ O

  31. & C' N) K8 _- `" P
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */+ @# _8 r' O# b  F9 z7 z) f
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    . E9 D3 R3 X; f# y
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    ' |4 U) H2 s0 P* N. q7 W
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    6 N& F1 f) t; u6 Q5 Z' e2 m$ c; ?
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    & F+ R: W: M+ N; \
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    - @# C. p  q. M
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    ; p1 S3 g: o  j& d" W3 J
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;# z+ t* D- @7 h" a9 Q( T
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    1 i  I: r- v9 w
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    1 L1 ]3 z3 [3 A9 q' T) X' {
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    * k- W! a$ ?" s
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;$ |% V0 I, i& ?

  44. - S9 ?2 L0 r9 ]/ q. Q6 x
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);2 `6 i& A9 @( R& O8 v

  46. , c! t# Z. y2 \8 _
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */% _5 B' K+ \% u7 R! \: ?
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    - e4 n; e7 ?3 r# P) K' Z! ~7 Q
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;! `* J4 ]" Z- p: k
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;% W- A4 R3 D1 a. T4 P- E& u
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ; l$ Y" g/ [. @' r: j, E/ h9 y
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;$ N3 ]8 O1 z. E: c& H
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;( k  b6 ?: x- @& q  E/ d
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;) V" b. v# N# e( b9 Y
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;( T8 l0 a0 }% O" G
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    / h* k1 D0 S# D5 B6 c! `
  57.     MPU_InitStruct.SubRegionDisable = 0x00;$ s; E% m7 o  H% r" l
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;( X1 o+ ^% E# X) p( s- u* r3 ?
  59. 1 Y6 `7 V) ~; F9 a
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);. ~- R% F1 q, U, _# G

  61.   T' `( A3 J4 Y2 u, ]! F8 L
  62.     /*使能 MPU */
    ! o# A0 G1 W. B6 c* g6 J* x4 U2 O0 k
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);! k1 ^7 @0 C9 Z  v+ T+ x
  64. }
    ( |% G, X& o" g6 F% G; r5 l

  65. ! C7 R$ K1 l+ n1 {" N
  66. /*
    # T/ [$ T7 w* O. c+ B  z
  67. *********************************************************************************************************4 Z; U, \  e/ m' s' P
  68. *    函 数 名: CPU_CACHE_Enable
    ( m9 N5 R+ V/ U
  69. *    功能说明: 使能L1 Cache1 [) j! }  Q2 ?3 R$ U3 l' {* W
  70. *    形    参: 无. ?& r+ N6 H' H  v8 ]6 w* A
  71. *    返 回 值: 无
    0 I& \. S  x* F7 a
  72. *********************************************************************************************************
    % g! I# c5 a: y/ J) P
  73. */, A* s9 s% {; y$ Z
  74. static void CPU_CACHE_Enable(void)' x) o# t2 t4 A; W4 @* _
  75. {
    $ [2 a- C/ U) {6 V1 O/ |
  76.     /* 使能 I-Cache */
    / t; o- x- F. N; [
  77.     SCB_EnableICache();! ~+ x, C6 O; N: |- e  k

  78. " ^1 a: `3 A3 u/ c9 o
  79.     /* 使能 D-Cache */
    : n0 h! Z* i7 a. l1 P4 H) M& [3 P
  80.     SCB_EnableDCache();
    - }) m9 |3 P( S1 y$ h
  81. }
复制代码
3 }# ~3 O1 ^) g8 G
  每10ms调用一次按键处理:& \9 M1 O2 m& w3 D0 v. q# X
- p9 M$ d- i7 [% `
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
- U/ v% X: ?' x. X2 ?0 F( z) K+ D& p
  1. /*( u% i$ y% o7 z% s, D- F
  2. *********************************************************************************************************
    0 @' l/ }# v0 K
  3. *    函 数 名: bsp_RunPer10ms  m# B# ?0 O" [. [$ l( c+ @
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求1 ~0 f% O' w4 t& n4 x' @
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。6 U) v, c9 Q/ p; ^+ t3 l) A: Y
  6. *    形    参: 无) H  L1 p5 R$ t- m# c+ V# ^
  7. *    返 回 值: 无; S* m. M1 {7 {' P
  8. *********************************************************************************************************
    0 L+ m  }% k, `3 m7 o
  9. */
    6 U; j. U! U; w
  10. void bsp_RunPer10ms(void)
    ( E" x# z/ h. o9 o/ D
  11. {1 _* S2 N7 X) K3 H
  12.     bsp_KeyScan10ms();
    ( K! W0 w3 E! f. }$ a# M( i, B
  13. }# P" X/ ^* A$ s
  14. 6 M6 U: t9 s2 G% \2 D$ y+ Z; l4 J; K
复制代码

. D2 ~% [) I9 y0 s3 q# |8 ?  主功能:
, p+ S; O. e+ ]: j
: I- }" P7 ^0 L; Q主程序实现如下操作:# r, Q6 D7 ?* H. H
5 U4 F1 q2 ]8 q+ p* `* I$ k6 u0 H
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
. i7 H2 I% r  Y8 L% F6 P0 Z: L( j  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
6 B" y7 p4 p5 e! v- W/ e3 P8 f  请选择操作命令:
" g3 T1 |( f  B" U+ p4 g7 G  1 - 读串行Flash$ e, i; n) j5 z# t
  2 - 写串行Flash* b/ d, X: X+ {. i! a0 `
  3 - 擦除整个串行Flash
/ F$ k8 d! r5 [. W$ P; F0 F" t6 [  4 - 写整个串行Flash" A$ E+ d/ C. N  g) z0 c
  5 - 读整个串行Flash
' U1 U# b' Q/ q" b1 O5 J  Z - 读取前1K4 K) T8 W5 x6 m" B
  X - 读取后1K
% V7 F- ~! y  H2 f
  1. /*
    . d1 k2 i* Z+ m0 @4 c
  2. *********************************************************************************************************+ k1 s; r2 z4 e' [3 |$ S/ G
  3. *    函 数 名: DemoSpiFlash
    ; u/ N0 T/ l# D9 N" X" h
  4. *    功能说明: 串行EEPROM读写例程: K+ G* t& q) s5 z5 \
  5. *    形    参:无
    ' N% B) G4 U1 q  h0 y! t( @
  6. *    返 回 值: 无' H0 k0 o# M& e- w8 j7 Q
  7. *********************************************************************************************************
    9 M4 F" G- z/ i8 k3 ^  w
  8. */; I) d4 |) ?1 [- \
  9. void DemoSpiFlash(void)4 |: C1 C; Z" G& j: M6 T+ ?
  10. {. o" g: a5 v' `1 ^) @
  11.     uint8_t cmd;1 c% e2 r3 H& I; m, Z8 S6 |$ B, G
  12.     uint32_t uiReadPageNo = 0;) d: ]9 I0 K1 d' k$ Q
  13. : c& z, }5 u6 }
  14. 8 o  z: E2 E; \' M6 w+ _4 T
  15.     /* 检测串行Flash OK */
    : m/ t* S6 v, y
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);: F0 t2 O2 [7 a
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    4 ]5 f) _, k2 _4 g0 [1 W

  18. 7 I( X; X" s; n$ y% r3 q4 t& [: H! o
  19.     sfDispMenu();        /* 打印命令提示 */
    ; O- X& `5 h) m5 z2 y& C: P' ^8 {

  20. # k' S* J5 L, N, @+ y
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    * f" H8 U; o( A& [, }

  22. ' z4 w. i" X) i/ w2 y0 x- ]
  23.     while(1)
    ' s" Z9 Z  s! H. _8 m9 A7 ~* [
  24.     {- Q# X: R. W% u
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    7 W& `  `- M2 r) {, m5 D7 f
  26. - Q% [1 M% W/ O$ |/ b/ A6 e
  27.         /* 判断定时器超时时间 */
    + a$ d2 w9 g, d8 p9 j% \) h$ R
  28.         if (bsp_CheckTimer(0))   
    2 e# ]  q/ V2 l/ b! z4 Z' E
  29.         {
    1 d% c( A! X" I1 X+ E
  30.             /* 每隔100ms 进来一次 */  
    ( E8 A: O/ _7 e( J' P7 C' w
  31.             bsp_LedToggle(2);6 }/ e/ x: n) O& L( W
  32.         }
    3 [% B) ^9 d5 h  m$ d. o
  33. 2 s, h1 f3 u+ n2 l7 @- \
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */7 D' r/ @# L% ]7 S
  35.         {
    # W: r4 p7 A. f% m. M8 Z
  36.             switch (cmd)( ?  t# B9 K+ ?5 C& j' y3 b0 E
  37.             {
    " D; {% y# }, X! r" L. `
  38.                 case '1':
    ! O; d6 H1 Q) M6 W# N+ Q2 I
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    & ]6 w8 }$ i* ?  T9 N9 v7 f
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    6 @% U/ i) B+ ?
  41.                     break;) s+ B; g- _7 C
  42. $ O3 }/ P4 b8 T- }$ J- n- Z
  43.                 case '2':  M* O' O3 N1 e. I: a/ E
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    5 ~* ?2 F# W/ d
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
      ]& R' t5 z+ }  m
  46.                     break;
    1 |: ]  h1 ?! w9 k; U' G) `
  47. 6 m2 r8 z: N1 M# a% U7 d6 y
  48.                 case '3':+ `( T3 a5 n( u3 U: B* n
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    4 h3 f' P3 K$ R0 D  @
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");8 o3 ]( I3 e5 ]/ i) y; ~" K! T
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */6 d; z, Q  f( n
  52.                     break;
    " y8 u7 R7 y2 n3 G2 m( F

  53. + I5 O; E" A6 o
  54.                 case '4':" o# C$ D+ N  @$ s" @! D4 H
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");/ N8 W) z3 r4 W% A1 y* ?( c
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");+ E8 ~6 K8 j# o
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */1 P' z1 B- l) @. u
  58.                     break;
    ( B: u4 \: W# _9 N5 C2 G
  59. 1 o  \% Y! }( Q9 h& O
  60.                 case '5':4 @* u! O( y' G: {, Y
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    - W7 n8 x+ R' Z1 }% `# G
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */. o& X. n. h7 P4 j) f5 U. q
  63.                     break;, ~+ N% ^" ^, J8 y+ S/ Y

  64. " Y' c/ k3 s" s7 r/ \
  65.                 case 'z':
    3 p5 E7 G' d# C7 g0 b
  66.                 case 'Z': /* 读取前1K */
    % B9 s- g7 _* D2 Q1 Y. A: m
  67.                     if (uiReadPageNo > 0)
    ) F) d) b! O+ z
  68.                     {2 z0 R* z% `1 E( z8 l+ \
  69.                         uiReadPageNo--;% q: H8 K' i8 c) R
  70.                     }
    2 O- X+ T+ X' I- y  R
  71.                     else6 R  G, J' \6 A0 {# S+ ^/ M* P$ ]+ ?# F
  72.                     {
    , [7 X4 D/ v% H, N, O  \
  73.                         printf("已经是最前\r\n");
    8 x1 t1 d: a+ r) g/ p3 _
  74.                     }6 i' f) l. V) V$ u0 w" h
  75.                     sfViewData(uiReadPageNo * 1024);& r( d' n, h' t" N  P# E$ u
  76.                     break;% p: q: P9 J, _( Z  E# p5 K0 g
  77. ' Q2 t* N& O0 o: r5 U" L$ M
  78.                 case 'x':
      F8 j1 x! [; n8 i
  79.                 case 'X': /* 读取后1K */+ M/ p2 w4 f* `8 H% q2 M8 t+ `! ]9 d( q
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    % y- t' |5 u$ I) X: H
  81.                     {- ^8 i) `) H" w6 I. F' G3 j
  82.                         uiReadPageNo++;% P; S  ]- p* }  T
  83.                     }
    1 @, n5 [5 ~. v9 j8 C3 w( D9 H
  84.                     else
    5 r; i2 ?1 c/ q. v. N" i( F  f8 [' |
  85.                     {1 j/ n& B4 W/ ~% ]5 S+ K
  86.                         printf("已经是最后\r\n");
    - h2 M0 m* k: Z# O5 X6 F. |7 P
  87.                     }
    9 n4 f% L' N5 Y2 I
  88.                     sfViewData(uiReadPageNo * 1024);
    . C# l+ z% \8 f5 v' r
  89.                     break;* U( E, o: s% }: f

  90. + F" P) H# T; V5 b
  91.                 default:% A- Q  i0 y% R+ p! T8 i: s
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    ; P1 X8 k; @4 I% V( ?; r0 x
  93.                     break;1 S8 X+ I' r0 h+ X5 J' ^

  94.   T) l' j0 N( b( v
  95.             }
    ) ^3 k/ e- ~2 s' ~
  96.         }, L- D* O8 {# v/ a+ y& H* O5 u9 [
  97.     }
    0 w* w1 n9 E- y5 i
  98. }
复制代码

# t6 @7 @- `6 t) ?73.10          实验例程说明(IAR)
3 u% S$ k; I1 k
配套例子:; Q: u0 n; R1 E  ?0 x  I! M

( T0 M! c/ D+ r; {# c6 U4 _- e: L- yV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)+ a2 I8 F$ i9 }1 {# A
0 @  J0 a& L% _2 X3 H
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)0 G$ h7 p% a4 g. Q+ T( p  G) L
% G% N( {' i5 w) Z8 D
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
. M) @9 l2 z- S- ?+ t, I
8 D' z% m% H, B. E' g9 c6 _! U实验目的:
) ~: V/ t  A9 o1 o% p- k% l( q7 U9 Z4 J- H7 ?& n
学习SPI Flash的读写实现,支持查询,中断和DMA方式。# W- k* x+ h/ `6 `' E# ?! {

5 x/ h9 L/ q6 A/ k2 t3 y实验操作:
$ c) e$ Z/ f  E& l9 D
$ t. G- p& c& [( d支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
- ~+ T6 P6 N+ Fprintf("请选择操作命令:\r\n");
- H7 ~# ^' R1 B+ M/ T8 X4 }; jprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);: i; T. Q& M+ U. L' C; e
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
" ]; X2 ?0 \- {4 g6 vprintf("【3 - 擦除整个串行Flash】\r\n");: a7 y4 L  j" b- u8 g& |
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
) }( `% `5 C* j, @3 xprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");
7 V* k% h) M6 i' V  P- pprintf("【Z - 读取前1K,地址自动减少】\r\n");0 X; Z! p1 T5 E
printf("【X - 读取后1K,地址自动增加】\r\n");
5 S( F0 q+ n7 wprintf("其他任意键 - 显示命令提示\r\n");( Q7 _) k, }* l! f
) Y7 A( O, T* d% V4 g  T& }
上电后串口打印的信息:
1 S* B( ^% J$ P# \
- h2 b0 q9 S( q2 F波特率 115200,数据位 8,奇偶校验位无,停止位 1。
$ m2 W: [6 [/ l. M+ J1 V7 a- [  F$ U$ B3 C, _4 w
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

2 {* c3 ^0 e1 F# v% L
6 I' u% j  M/ {' u; }9 P程序设计:
7 Y7 C1 m+ g$ n7 \* C
6 O+ O8 G" J5 H* K& Q  系统栈大小分配:( ?$ p; B: t' c) {: r2 n2 U# A
& a: z! j8 _/ a6 g, A
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
0 S9 G. B9 G; G$ i
  @- e1 N) m, O+ ?- G. f
  RAM空间用的DTCM:
) ]3 U, e# _( n+ y5 q" m* I6 W
/ \4 @6 X! ?, J5 `
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
! v2 B4 N; i- L5 r) x  }

$ T6 [* t9 j" J4 e" A  硬件外设初始化
2 O! x4 V2 l  M; x4 b5 J1 x/ P& [1 c3 o- n% F: E5 J  K  t' }2 F
硬件外设的初始化是在 bsp.c 文件实现:' C$ C2 U' ^5 p$ P& |
1 n; \7 r) S& `& F
  1. /*8 R$ B+ N1 h9 Q- f, e9 `
  2. *********************************************************************************************************: }, h7 x6 f6 x, i$ |
  3. *    函 数 名: bsp_Init9 ]- g7 ^" E* ~
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次8 A3 R7 [1 g7 r* l0 w  ^" o% w) z) \
  5. *    形    参:无
    # q2 X2 {# |% y) m4 S' L
  6. *    返 回 值: 无8 B: z2 i+ K0 x# h5 L6 _
  7. *********************************************************************************************************
    1 }2 _' \0 d6 R0 u# j0 a
  8. */$ x6 Y2 e) N7 E' Y/ v1 W7 q0 M
  9. void bsp_Init(void)( [( K# x0 l5 @9 b5 g! L: P
  10. {& g2 l( ~; B1 K/ D6 Z9 w
  11.     /* 配置MPU */1 `8 ]6 M* f- ~, Y/ ]9 b
  12.     MPU_Config();1 _2 y. c" k1 F" A4 f
  13. 6 u  w8 U% U; @& z) j
  14.     /* 使能L1 Cache */
    3 |2 f2 _' h' C" O9 T: }
  15.     CPU_CACHE_Enable();7 T( g+ T5 W  p
  16. ! R2 S! X9 q- u! K
  17.     /*
    - R& r1 \2 u+ ]( _0 L# R
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    8 Z; ]% G1 Y3 k
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    $ q" y( Z4 p8 t% [
  20.        - 设置NVIV优先级分组为4。
    4 _# K2 K- v- {$ Q' v4 n
  21.      */
    # d7 j: h% W# H* ~! E3 N0 r) D- b
  22.     HAL_Init();
    + _! K9 x0 _7 C7 Q( c

  23. 5 _. U: X$ b( M7 U7 ?+ r
  24.     /*
    , {6 O1 o  a7 H- l$ m/ G* C
  25.        配置系统时钟到400MHz" |& _; J" O+ n+ O6 P$ x( ]4 {  F
  26.        - 切换使用HSE。! M# N6 p, o. L% x
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。  x+ P9 q5 m% ~' k) R, G8 W6 p0 l# E
  28.     */
    . d# X- p. U8 C& [/ C& G
  29.     SystemClock_Config();7 M9 \# M. g! P/ @8 }5 B

  30. - D+ `2 C/ W3 Q  Y# c
  31.     /*
      L( }+ {2 ~# @/ l, l  |+ i, X- f
  32.        Event Recorder:
    ) p, {  y* \: c9 \& ~% |9 K2 m
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    2 }/ F- `9 n! ]& q$ a7 ^; Q
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章3 Q) z1 G* u6 s
  35.     */   
    + w7 P% C2 @' q* r! `& z* v
  36. #if Enable_EventRecorder == 1  ' ^! v, p  q- }" C5 b, M, X
  37.     /* 初始化EventRecorder并开启 */
    + ]' z  N6 j6 U) Q5 w$ e2 M
  38.     EventRecorderInitialize(EventRecordAll, 1U);/ z0 D+ R$ \; X3 I9 }. H1 @2 `
  39.     EventRecorderStart();
    0 P! {2 [$ }( q4 w; Z
  40. #endif
    ) F8 T3 E5 m5 I# g

  41. 6 Q" i& I/ x! k. f8 H- f2 C3 u( \0 _$ t
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       5 s* Q8 {# \2 _- ?& z
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    % P4 j2 _  ]+ [3 r
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */
    # N5 J" o  Z/ p- Y
  45.     bsp_InitLPUart();    /* 初始化串口 */! Z5 I7 j4 `- [' M
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    9 l- F% y+ f3 u
  47.     bsp_InitLed();        /* 初始化LED */   
    & v* S3 g! _& B* e8 i& ^' A3 ]
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM *// Z9 p( e* B' E* q$ o7 ?5 C+ u
  49. 9 ]. s( K" A+ L" J
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */4 `) w" n; V: ^5 }7 I
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        4 k0 W/ k" ]. T0 ^2 E# I! Z4 C
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    7 s- a  R. M; Q$ ]
  53. }
复制代码
2 n6 l5 v3 e5 C. t! {
  MPU配置和Cache配置:( Z1 E, v9 W9 k( P7 j

, Q& f, y: v9 ^# F数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。2 S7 I: ?. [6 |# c

/ o) h( O7 _6 U3 `, v0 ^
  1. /*
    * ]; z; m" v# \' ^# i2 k
  2. *********************************************************************************************************
    . t/ p9 z0 n) a* h
  3. *    函 数 名: MPU_Config
      Q5 a$ Z7 b; m8 Q% R! E& U1 Y" L  n
  4. *    功能说明: 配置MPU
    9 l, p" ?% T% W: w' Y4 E/ |
  5. *    形    参: 无
    . |! D1 n( F9 `% ?% ^. [  N
  6. *    返 回 值: 无6 b( ]% @! O- e  ]
  7. *********************************************************************************************************4 o, ]& A( i6 s' @6 G) G
  8. */7 H: r' S' w. j# \% p: A6 a
  9. static void MPU_Config( void )' h; r* z5 X4 X2 V2 _
  10. {
    : Q6 g" f; a4 Y: D* P" V
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    2 |0 A0 f: H! e1 O/ q
  12. 7 M! ?: d! }& v3 ^
  13.     /* 禁止 MPU */; F9 L& P) i% i8 {1 u8 Z
  14.     HAL_MPU_Disable();
    & O; q0 B( K' z2 w: m6 R
  15. 2 E; m# u; T' @; I# U% \
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */% D! t6 v. k$ ^) h$ F- J. ~
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;7 K/ w! q4 F7 g) \% `; r2 y3 k
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;% O1 ~0 h0 b( P! c  M6 c; q
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;+ Z! O2 l3 n" ]% D
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    , f2 f) C4 a7 r7 m* n1 y
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;( |  q0 Q/ ]9 @2 e
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;" i# }9 C( ]7 H$ Y& {  P9 o, i; @
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    , }) c( f, E0 a5 ]
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    $ S, c$ t' j; r7 Y, p
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;( \, _' \+ D! I9 ?, d3 P
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    : Q; M  }" |3 a; _7 e' q2 K* ~# K3 p
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;$ m2 m# F2 R! I: L) _$ w* j

  28. / `: p/ e' S5 y) V# x; ~# I
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ; g2 |& k4 i/ m  r$ C2 h

  30. 1 H4 `2 k) G- h

  31. 9 L- ^& p: _* D" g( K+ p
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    & w0 c" H# Z2 P* S
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;( Y# D2 X0 M4 F$ d1 W) P8 ]+ g0 [
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;* j' J$ K: t* |- a% s" w
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
      Y; W5 ~8 j  l6 F9 A1 P# m* L. w
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ' K& D5 `/ o, E' X* d
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;% f# @# a0 Y! T. Z5 K
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    % |; C  F7 J. E9 w4 m1 _1 t; k- y2 ]
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;: k0 {+ G! G) y+ U/ S$ o
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;# V  h* B+ v2 L' C; a; q/ T
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    3 {4 _; v; r  c4 T9 c0 Z/ F- o
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
      ~+ g& P# }) v0 F8 M$ O
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    & }( t& P6 @0 v
  44. ; k; Q! d; s0 G+ _- b
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);( x% N3 }3 g9 Z* X

  46. - w: o. d. {# r
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */" l& M. ~" Z, n: p
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    6 R& u4 N/ s3 b! O# b, Z2 L2 M
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    " m9 x5 h8 ?2 m3 v4 Y# `  ~
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;! Q% @9 x# {" H2 m: f3 g
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ) G; t" p( u9 Y/ M3 P" L% \
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    * ~1 _+ [- o$ Y
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;( e; @* v, D/ H, V/ C
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;% A4 K; P" T) X7 J, G: O( G" B
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;* U7 h4 @4 ~" K
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    1 Y# M5 w; c' w, }" _
  57.     MPU_InitStruct.SubRegionDisable = 0x00;- ?* c- f0 Z  I' j% f
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;( i" B3 F% e6 R; Y6 ^9 X& Z8 E: C

  59. , v  d4 z, ]& J3 o* N$ {- c
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);6 V$ C. m+ \5 p: a4 n  o4 t7 J

  61. 0 J  k$ n! I- Z. S9 }: D+ k
  62.     /*使能 MPU */
    , u/ g5 i. J& r. l; X. n- a
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    4 d, K! M' ]$ q7 y& |
  64. }
    $ w8 A! J5 C  h; x2 L: _

  65. $ \- d1 ~" }0 g
  66. /*% r& ?) {, k/ i3 [2 q3 C0 Y- O+ [
  67. *********************************************************************************************************
    5 H/ k$ m! R, _# `. l1 N/ {
  68. *    函 数 名: CPU_CACHE_Enable7 [: l5 F% p7 N+ b
  69. *    功能说明: 使能L1 Cache# ~% K3 U0 |4 H; g" f* x3 H
  70. *    形    参: 无
    ; s( T3 h* }. {$ @7 B; l
  71. *    返 回 值: 无: C* `# C2 S, C! j
  72. *********************************************************************************************************# d& e% Z& K. d7 M9 Q3 W. p8 d
  73. */' ]! j) Z3 |' ^) h$ L
  74. static void CPU_CACHE_Enable(void)" I; f/ ~+ J5 J' |: a7 t! n
  75. {
    5 k8 {  ~0 q6 X; o) \
  76.     /* 使能 I-Cache */
    4 N) h. Y3 q2 y6 L+ a- T* l. [
  77.     SCB_EnableICache();
    ! l( X! J2 l) U3 z

  78. 1 Z% \2 c$ o$ H
  79.     /* 使能 D-Cache */
    . i* C5 N( u0 J
  80.     SCB_EnableDCache();6 u7 H  c% k2 t- E4 n
  81. }, N# |$ A7 y( r
复制代码

- e; t+ l) ?# H, P9 B& J
3 J4 w" r0 W# K) e/ b/ }  每10ms调用一次按键处理:
8 ]- F( c$ I( x- u
' B) O& c2 d% P1 m. H7 W按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。8 g  K  C1 o2 h

  R: z6 }& p/ p9 {2 P3 A
  1. /*
    ; E9 o: ]- E- o# M7 [* w3 O
  2. *********************************************************************************************************4 A/ c2 L: x* I
  3. *    函 数 名: bsp_RunPer10ms
    9 z7 V: D6 O; L& \7 C. g
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求6 D; x. p8 B- ~6 X7 D. v' p
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    * @/ _3 W& [2 n9 O- Y5 A8 K
  6. *    形    参: 无
    1 s) L5 S4 [$ Y: g
  7. *    返 回 值: 无
    - @8 ^/ G+ n( V7 D
  8. *********************************************************************************************************
    . q- J! b' |$ [. N
  9. */
    . u. K0 T% a! P+ a. {( U
  10. void bsp_RunPer10ms(void)
    9 [6 t  v1 ?: K
  11. {
    ( ~) R  |1 y7 V! [$ v6 G# d
  12.     bsp_KeyScan10ms();
    0 K* J9 ?# m! a* q3 _6 h! f
  13. }
复制代码

7 l/ \; i) i$ Z# z8 L  A  主功能:: N( {" `2 S, s9 ^% }
- M" G2 o4 S: g5 {5 N
主程序实现如下操作:
6 n; F" b, N7 ~! ?. N8 g( C) b/ Q: w8 ]
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
9 o% E$ ^, ^6 \. ~  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可; j9 ~5 C1 e: S6 t+ e. F+ C4 L6 x
  请选择操作命令:" ?" o; c6 t6 ]: h- t" E
  1 - 读串行Flash/ s6 t% ?! C; `/ V& e! S
  2 - 写串行Flash# ]  _7 t- j) ^. W
  3 - 擦除整个串行Flash
0 a7 H3 q0 ^/ m- ~  4 - 写整个串行Flash
% o5 d3 k1 V9 `5 P  5 - 读整个串行Flash
% j( O3 f9 M' o7 W4 f2 g  Z - 读取前1K
6 [% a" `. Z6 E  X - 读取后1K1 f( w" b- k; @( E+ D
  1. /*' y/ J' c7 |' ]
  2. *********************************************************************************************************
    : E, z' O% N. Q# j$ W2 d" v
  3. *    函 数 名: DemoSpiFlash- M8 e) N/ r* y) D
  4. *    功能说明: 串行EEPROM读写例程) M1 A6 T, t, ]" |1 R% F( a6 n
  5. *    形    参:无* ]2 n7 c# B7 l, I' w$ Q
  6. *    返 回 值: 无  E9 f; }* ?" l5 y6 i
  7. *********************************************************************************************************# `5 N) k8 U' a' f; x% K* G
  8. */
    % z' Z7 k/ o- _3 v) A* n5 N  D
  9. void DemoSpiFlash(void). ?9 A" }) a# x; ]* [$ s1 L
  10. {
    1 i( {% f) l8 F+ a2 b
  11.     uint8_t cmd;4 s* n. N. T9 P- K# r7 ~5 W" b
  12.     uint32_t uiReadPageNo = 0;; E2 v2 V# O( s) Z, _, M% I) M

  13. $ P5 f: R6 w% S' D6 S

  14. 5 E9 A% p2 P5 s, L" _3 l4 w1 H
  15.     /* 检测串行Flash OK */" e: `9 R- a0 c2 q& p
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);1 B5 X' Z% v# F# ^1 G0 t
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);) @3 S' I6 H* C  @2 E+ ]3 p
  18. 5 g7 L1 s4 S" m! |: c, O& ]6 N: {
  19.     sfDispMenu();        /* 打印命令提示 */# o* `/ B' x- x, M; }7 A" l7 s& h

  20. , f( B0 ]/ E7 t: S' B  O& h  m: M+ p
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */4 Y9 Z, k4 s# E& R2 h: h' Z
  22. # V* v- X$ N1 b% v+ s& s4 r
  23.     while(1)6 v( B& M% e1 s+ z9 m7 z
  24.     {
    ' n$ m4 J9 o5 _' G
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    / W+ m: {: s" s7 _$ Z
  26. / a: H7 _6 T/ L' n$ D; U  o
  27.         /* 判断定时器超时时间 */" k/ L& q, n- v! B: \' _, N
  28.         if (bsp_CheckTimer(0))    - p, N" f3 N2 A9 D) p. p& a
  29.         {8 z! |) w! Q$ h) w' R
  30.             /* 每隔100ms 进来一次 */  & O5 P# m, F4 G3 [6 A
  31.             bsp_LedToggle(2);
    * e* ?! Z% C) }: |: E
  32.         }0 Z! r8 I+ ]3 k5 W

  33.   k( T& N- g4 R5 r. S
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    # Y: N5 `9 y/ o/ G6 F% T
  35.         {# E" X8 q; r8 M+ S9 l1 C- d  `
  36.             switch (cmd)9 a; i9 x. g# c! [: Q  Q' `! M+ r
  37.             {
    7 ^5 |3 S: N" i5 W2 \
  38.                 case '1':
    + I6 O! C2 W. a+ H2 W# X3 d- X  K
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);( v+ q7 h- ~/ o+ T& y4 |7 t& I
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    3 x) u. y+ O( P5 _
  41.                     break;. N" w1 f! H& U7 b" e, X7 `

  42. , r  g* |; B( m
  43.                 case '2':
    " |( h" Y' x; ?" m% |; X2 y, J
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);' Y% E' \; s2 h) s1 E3 \- |' B
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */7 _0 X$ f* Z+ ~6 ]" O" X& F
  46.                     break;
    7 ?: P# W9 S9 v' w, P
  47.   G. p* B5 J2 M; K3 m; i
  48.                 case '3':
    : X: u3 ?" A) d0 G, A7 _* \
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");2 O$ i. a. x& q4 K! }
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");7 `4 u9 P4 H, v- }, i
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */: [" T6 I' p- p3 ]
  52.                     break;! _3 x. H, \, k& T: U
  53. 3 f" w( J0 f. I" i2 T
  54.                 case '4':; |0 U2 Y- T1 Q% H6 m
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");# ~0 L( z3 J* @" [) U8 u
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    - L4 x4 r) H; _, O4 z. e
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    ) G( i+ [4 Y/ @. s* p. N
  58.                     break;. I4 u2 h- l$ A8 q8 ]/ @; f
  59. ; E: `) R% @8 L, y) ]5 d; H
  60.                 case '5':
    % `# I; h% ~( Q
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));0 p8 M3 R7 o; W  N( @! P, {( v
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */1 V# h: p# j/ l- L: [) t+ {
  63.                     break;
    4 r5 a( B4 j" r
  64. 1 b3 S8 e5 ~9 U8 t
  65.                 case 'z':
    . ?9 P" e( i. l! l( |
  66.                 case 'Z': /* 读取前1K */% X/ ^2 Q/ e- Q4 v
  67.                     if (uiReadPageNo > 0)7 a( Z9 d  Q' E! i1 ~4 T
  68.                     {
    5 S2 L& x) E) Z- i) n
  69.                         uiReadPageNo--;
    # z5 O3 ^9 ~" \# p0 r$ x
  70.                     }
    ; Y6 ]7 N- s3 \0 Y8 J  d: @  h
  71.                     else
    3 t2 t4 T9 b. W: W) b% S
  72.                     {
    & ?+ ~8 W& X6 [* a( P
  73.                         printf("已经是最前\r\n");# \' g, h) a7 k( Q" ~$ J
  74.                     }7 _  V5 [4 F1 t9 T" s
  75.                     sfViewData(uiReadPageNo * 1024);. U+ m0 v/ z$ W7 I$ ?7 Z9 T" h, I* U2 {
  76.                     break;
    , }2 j' `- W+ q3 ?1 D+ Y% F

  77. * k- [6 G- y" d1 E$ O  j* D# }0 ?
  78.                 case 'x':
    ; Q2 W+ F% H" @" u/ |1 u: O7 _
  79.                 case 'X': /* 读取后1K */
    1 X, R/ V5 [( b9 }
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)+ }# a$ _, {! y7 Y
  81.                     {& _3 p. z3 W4 b& t
  82.                         uiReadPageNo++;
    1 H9 v0 a4 Z% i6 k# B- L. x, f
  83.                     }
      ?: l( R, k2 E0 a* `% b6 o2 t
  84.                     else
    $ r3 t8 t4 H) A1 B5 M7 s  v
  85.                     {) g; P5 ~4 N4 j# k
  86.                         printf("已经是最后\r\n");
    & p+ p. h2 W, D" o( J2 V3 o/ X
  87.                     }* ?( x2 n% C$ F( G% Z
  88.                     sfViewData(uiReadPageNo * 1024);0 Y. M  z2 F, L1 L- H; ?
  89.                     break;
    # N6 _- T5 ^/ A2 ?

  90. ' D3 M0 j, l2 p8 {0 j
  91.                 default:+ v7 v, o5 i3 }8 n! {/ M; L
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */( J5 x. W7 F) h! V' v9 j- h
  93.                     break;  E8 Y1 y4 d2 }% O0 R) @' W
  94. + k1 D* e6 @$ @4 q
  95.             }- f: R& _  ~% b6 f; Q- W
  96.         }
    ! N. w2 B- D5 P0 t; Q1 p
  97.     }0 i' K9 C9 Q0 a* j+ k
  98. }
复制代码
7 g6 V/ k9 T  R" o
73.11   总结6 P  q) T7 G  C  h+ ?( I8 h: s
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。
( w- L2 ?" z0 C* |: V
. v1 Q. I2 {: t! y" J6 N1 s: u7 A2 C3 I( j4 S4 K8 F

. `- v( c3 S- X* s' L
收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似分享

官网相关资源

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