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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示
8 m- }. r5 F: ?2 Y  W25Q64FV属于NOR型Flash存储芯片。( F$ C& B4 i2 B" r% j" W
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
5 I  k: [  q, P- G2 I, K
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
- i5 C6 y; x" l3 P
3 n/ `' `8 {% X9 J. ~* n( i& i
  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。- K* V( s& B& T6 p1 D! ?$ `+ n7 s
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。' K  Q( p: R4 f) g$ }$ i1 `3 P
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。. E- k5 I( F0 L* C0 J
  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。1 k( E6 b  U+ O+ A4 T/ J
  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。
; g0 |2 @/ V1 Y# a
* B0 c* v2 v5 w+ k+ o73.2 W25QXX硬件设计' l  P( e8 w- G6 e: Y! ^6 a. {
STM32H7驱动W25QXX的硬件设计如下:
. o( r7 F% H2 {% Q# `* z/ [4 {3 V; T5 u2 J, i
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
, A" l; ?/ r5 a6 B( ~

5 V& E& b2 R. D  @% O/ A2 {6 S& H关于这个原理图,要了解到以下几个知识:3 ?% t7 r; J3 [" K
: v1 ]! O6 e* d8 V4 S
  V7开发板实际外接的芯片是W25Q64JV。7 `" s2 g% U0 |0 L
  CS片选最好接上拉电阻,防止意外操作。4 t) Z1 `* e+ j% F4 G& X$ o( w
  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。; e% A& x; \# {
  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。0 ]7 b# S+ L6 _& D
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
) Z4 O0 s* B3 D7 u' g73.3 W25QXX关键知识点整理(重要)
, t; _( j9 I  U9 S  X驱动W25QXX前要先了解下这个芯片的相关信息。8 b2 V& `4 A1 y8 h9 W& [9 G

& Z; s% h9 S8 w% B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

9 m1 e/ ~4 U& n+ i$ o
  M" n% }. s- s5 w73.3.1 W25QXX基础信息
# E6 V% `; P1 L3 K
  W25Q64FV的容量是8MB(256Mbit)。
) J% y: i2 e3 s( @, c. D8 k  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。* k% [' n: G' _2 ?0 }+ e* z/ @
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。
5 D  f4 }1 K! o( H, p* U# \- D4 Y; O8 x) Y5 V* `& G  |. b
支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
  k! h! B/ O' H- S' a  g# C. r! K' s; u% z4 w* [
(注:这里几线的意思是几个数据线)。0 T9 F: [8 g' {/ {* k
7 D( Q4 h2 t' o9 Z( z
  W25Q64FV支持的最高时钟是133MHz。* `0 w6 l' |! N& x- J9 `( K' E
  每个扇区最少支持10万次擦写,可以保存20年数据。
3 C1 C. k8 D8 f+ U4 {5 H* g  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。: N, L9 Q3 ^* L7 S8 ?9 T
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。+ g' \5 c, y6 U& a
整体框图如下:. \: M2 P; s3 `8 S
  k+ N+ R* H) c# `4 e
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 d* |6 b2 g3 A! k7 ^9 r
- P) d7 o& C* f  J4 FW25Q64FV:
* q4 ^( k* ~: Y  l5 L. E- L
- }  @" C" e& a* L6 X  有128个Block,每个Block大小64KB。# s1 ]8 y$ c# D4 O! y+ M" k
  每个Block有16个Sector,每个Sector大小4KB。" i* V8 ^5 c- @
  每个Sector有16个Page,每个Page大小是256字节。
: M4 u) Q  w" Y% A73.3.2 W25QXX命令; b. ~6 b% k$ \! K
使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:$ e: R+ H" I, y) h, n

( B7 i! K. f' b( e. j( C
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

' w8 J) f1 o: r( E
. l8 f. B* Z& B# w+ m( j; \+ N当前主要用到如下几个命令:. l9 j5 [. A4 w$ T5 v9 @

$ y1 f% z/ L+ }/ _' `
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    8 T5 w( _5 B: a: x) W+ p1 i
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */1 Y" J1 D' K6 Z+ w- N
  3. #define CMD_WREN      0x06    /* 写使能命令 */
    1 t' _0 G% z# |/ m' t! m1 s1 V& \
  4. #define CMD_READ      0x03  /* 读数据区命令 *// }6 j: y" v5 C4 b8 A/ R
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */9 h6 }, b8 t+ |; z% I6 K
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */% |- G) B8 j& i$ R. G! k. d
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */& R9 J% a" n& Y+ H" O
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */
    $ ^( X; O. i, F* x8 Y% s4 W& N6 p
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
! h' a  X0 b1 C( H0 {9 c
73.3.3 W25QXX页编程和页回卷# p* {5 f# F# y; l; F& ?# O( [
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):9 C8 M0 A0 V) m" L  C+ s
2 K+ s  Z8 q! z* d
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        3 x& D& f3 n: x' Z9 b/ r
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    ! R% W  m) T  E! z
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    / Q8 I3 C  S1 k4 {* L
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               . p) U/ o. h% T% N7 h
  5. , z5 Z% b  y+ m$ j
  6.     for (i = 0; i < _usSize; i++)9 p/ o7 R8 u/ J' C
  7.     {
    1 x) d: ^& G9 B
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,3 H/ \2 u& d# G) |9 m3 Y
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    7 |3 N* m2 z7 |8 R1 J( O
  10.     }
复制代码

% K% X' f% w; ~4 b5 {页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。5 O3 h2 Y+ A4 G! J# n0 q8 G2 o

; j5 P* U* [3 g: s% K比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):1 T& T6 C: F5 S7 D
) U+ }; K7 m" v& m4 }
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    % x/ ~6 o5 W  {5 |$ q
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
5 X4 h/ @+ B3 J' f
  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
: |" I6 G1 V# A" F9 d5 j2 E  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);5 ^) X, ?+ i6 O0 x* ?* |
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);# N+ V. y9 {8 v1 D9 R7 X" I
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
! I$ Z$ c, n, b! M. P/ G0 Z9 [下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。9 E( v* l7 Q4 l4 b7 B8 Z
  ^7 s' s1 c6 V4 J# W5 {% f
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
) d( S" f: J$ f3 Q5 N
, ]3 M0 V% \1 L+ _9 V& e+ \- S
) R$ ~6 g/ ~6 ~9 I+ c
73.3.4 W25QXX扇区擦除) |9 ]5 y! {  A. I2 V, E& ^7 X, S
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。6 B" w' f( i) P" H
7 J! w5 o, ^9 P/ T; l
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
9 R7 y( {6 X4 [+ d! r9 K
& R2 x5 t$ U" q73.3.5 W25QXX规格参数

. N2 f4 |/ X. D# L这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:$ L- ?4 C+ Q. G+ \5 h

6 i2 y! G) @! m3 U! z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

' E- W' r- n" Z1 ]6 C0 z
4 b& i$ W/ X+ |  页编程时间:典型值0.4ms,最大值3ms。: _0 r" a- l, ~% R. ?+ _0 e2 B& N
  扇区擦除时间(4KB):典型值45ms,最大值400ms。
" r; V; y" U4 R" w& k  块擦除时间(32KB):典型值120ms,最大值1600ms。2 Y0 |+ V3 Y* `8 C/ U+ P7 G
  块擦除时间(64KB):典型值150ms,最大值2000ms。
, Y) D* p: L5 k4 I; {7 g  整个芯片擦除时间:典型值20s,最大值100s。, m7 o+ M: p8 @( T
; @2 {3 T4 O9 R$ _8 a

7 \0 s% n" N/ u6 g& a8 ]. v  A支持的速度参数如下:
) h7 k& U/ u6 t3 g. ^1 Z
, Y& H( J* M4 T7 O
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

0 |, K% {9 I8 J) C3 k5 E0 Q7 r2 v8 x! S: B# V
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
/ x3 S) }7 M( m/ g9 {; }8 f5 s, k- z, T4 w' n0 a9 a6 ^8 V+ G
73.4 W25QXX驱动设计
' R. ^' ^" i' S! ZW25QXX的程序驱动框架设计如下:
) R  |' q: f! H3 W! {; k& e: w( z) R4 w9 a
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

) A. X% [) c5 S4 D4 h
; c* y- q, t; g; m有了这个框图,程序设计就比较好理解了。
9 b8 C3 l0 E' {4 b# x, \* X" ]* `, _5 p8 q6 ^  a# c7 N. n
73.4.1 第1步:SPI总线配置% S: p" ^2 F- m7 X* V- W5 u; u
spi总线配置通过如下两个函数实现:- B, [7 \; z) K0 G* j: P2 Q
8 s; Y( v7 \, G8 U' H# y  V( k
  1. /*7 M% \  P  @: Z& M7 `' t8 k
  2. *********************************************************************************************************+ T, o8 D8 k9 f- |9 @
  3. *    函 数 名: bsp_InitSPIBus2 h! p8 ]6 o  [4 J" f
  4. *    功能说明: 配置SPI总线。- b( z; s& s5 V, `: a) u, e( Y7 B
  5. *    形    参: 无8 ~0 T( w5 r. f3 f
  6. *    返 回 值: 无( {9 V. S" b# e' `& P" _
  7. *********************************************************************************************************
    , r3 o  L8 @) a. I
  8. */
      c  a. @7 m# {. t0 a) G+ S
  9. void bsp_InitSPIBus(void)
    : t/ o" e9 _7 z+ x* Z  T/ q
  10. {    & ~: T. F3 M/ n9 ~
  11.     g_spi_busy = 0;
    6 u, X1 k/ @& o7 z( p/ ?# @
  12. % k  Y$ U. D; m$ J2 F7 v* a) ?3 P5 ~
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);. S* p) W5 d9 [; \
  14. }
    # d# |6 c' r7 Y2 a& E- @. J
  15. . y4 e$ Y- ^, N+ M
  16. /*
    9 x; d) l3 F# |: {3 T
  17. *********************************************************************************************************
    6 y, [3 N# l7 m6 g/ i
  18. *    函 数 名: bsp_InitSPIParam
    ( b! ?9 D) j1 G  a$ y* `4 {
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。7 k5 C& T8 E% ]( ~
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:; V& h0 O0 p% H8 n' F1 H, q7 E; u
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频
    3 G4 h% [7 |3 i7 h; H6 a4 U6 B
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频
    9 ^6 X- F" i0 d0 N- b
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频/ V& G* q7 V* C' \) A
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频
    ; w5 a+ P& n7 N
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频8 D* u) [9 d1 ]
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
    * X6 p8 t( u7 Z  z* a+ p. \
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频( P# E, p9 n* W3 o+ L$ z1 t  Y
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频( d6 i% `. g6 l" ~
  29. *                                                        
    1 a6 E# g2 E  o, Y+ o8 _' a
  30. *             _CLKPhase           时钟相位,支持的参数如下:. x7 ~1 r- S7 E0 S6 W* Y$ O) o! @1 p
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据2 y- V. [! t$ P) N% J; f7 G
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    6 O* x. ~* ~& H+ a6 J4 p/ y
  33. *                                 
    $ r8 R5 i8 ]' o6 M* p- V
  34. *             _CLKPolarity        时钟极性,支持的参数如下:
    ; F4 p6 d. p4 G7 {' p
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平7 `" z. B/ o0 q( q
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    # E3 [1 T, h& L8 H- I
  37. *
    ! R- I6 c! a* m
  38. *    返 回 值: 无
    1 `# z* u( K& F+ |. {1 M5 o
  39. *********************************************************************************************************
    & r" j) s, N; b* Q
  40. */- j/ ]1 ?1 v, h6 E, X/ T
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)2 P4 U: S0 Q% f2 M
  42. {, W) U) g5 t7 J' @2 u& L
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */1 P5 j. k1 j- _8 p0 M0 {
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    9 n0 L* U2 K8 [! P4 N5 K0 p" }
  45.     {        
    * C: D9 k. l0 b" w% P* W4 v- @
  46.         return;
    ! ?0 Q) J7 D0 ?  L$ w
  47.     }
    7 N# z! \. c2 A9 C& t

  48. : }/ T$ H8 A2 V/ \
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
    ' X: k7 |: h) y5 n" a4 }
  50.     s_CLKPhase = _CLKPhase;8 Y9 V  A: Q2 ]2 |
  51.     s_CLKPolarity = _CLKPolarity;
    , x! N4 t. q, u" u9 w% Q
  52. . c- N2 [2 i: Q8 m8 H

  53. 9 b; r  s  B. x8 H( d
  54.     /* 设置SPI参数 */" ?3 O3 H+ B5 D  W9 E$ ?" \% E
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */
    " p* V4 z5 J$ J: _  q
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    4 H) X/ M) p+ Z1 f6 A& _
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */. c+ G& \+ k6 N5 \0 h6 J8 h8 }$ J
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
    $ Q/ Q% b; A4 b0 v2 @
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    # C  C5 i# F' W: p% V5 P
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
    + R" F" @- e$ v' A
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
    , m9 Y9 E9 c$ a( f, g2 U8 Q
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    / n9 _* ?8 u/ k) e1 p# @7 O
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    * L, f5 ]; x) A$ n/ l4 c
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */  Q, Y1 _+ t  r% \- k) W. q8 k
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */
    : }  o. v* ], h8 e! U
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */% C/ I  u' T) P* ?/ W
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    / U! z9 b! k3 z. p, R
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    3 b9 ?3 Y7 p) t) t$ Q: m
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  & W/ x9 h# a+ S% m
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    ' _' u) |$ f3 R

  71. ( Q& S# T2 A) t) c
  72.     /* 复位配置 */& V4 a4 W8 n8 Y$ F9 B) H/ V4 s
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    : B, m" d9 ?, u- g: R1 x+ P1 D
  74.     {) B8 x) X3 w0 ?! V1 p. f
  75.         Error_Handler(__FILE__, __LINE__);9 |3 C: b' V# T$ _$ d! p" z
  76.     }    3 H" r4 @2 E! d
  77. ! U0 G$ p5 n: z7 X: x( n: d
  78.     /* 初始化配置 */
    2 Q; N% w* x  O
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)) t& k0 r& E0 ^# G" m" S# W2 h
  80.     {! p1 {% R  W& C6 L! X# X+ B
  81.         Error_Handler(__FILE__, __LINE__);
    ( U" Z2 g1 S: Z, g1 u9 l* q
  82.     }   
    5 Z5 ^& k8 \  v. Q0 P
  83. }
复制代码
' d: M/ W$ O! g# n8 O
关于这两个函数有以下两点要做个说明:
! e. w- o0 A6 \; X, ?6 Q
) x7 h$ m$ p+ a5 W  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。% A1 F9 y: {$ X( W9 A
  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
; l$ N; q0 w7 d0 z3 i" j4 i& [1 |73.4.2 第2步:SPI总线的查询,中断和DMA方式设置7 B; M: k  A- U# @# z( }
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
, g1 c$ E0 ~' m( y2 W% q' p; T' M2 C& U8 j5 G9 f
  1. /*
    % T! g) @/ W7 j- X
  2. *********************************************************************************************************, q. q( b( d6 W, l+ ]1 w' y* n& f1 p
  3. *                                 选择DMA,中断或者查询方式6 R* q4 C- U6 W/ H: E1 o' u
  4. *********************************************************************************************************- F  x4 n  {1 {
  5. */
    9 X7 @6 C9 i3 j) E
  6. //#define USE_SPI_DMA    /* DMA方式  */! x7 Q+ Q  F$ e0 [4 }( M$ f: k$ `& O
  7. //#define USE_SPI_INT    /* 中断方式 */" x3 N6 ~' H! ?! K
  8. #define USE_SPI_POLL   /* 查询方式 */
    * L0 y0 C$ M2 j  H( V) N7 Q8 ^
  9. ) ~8 W" q: }; l0 ~. Q/ C$ R0 y1 {5 N
  10. /* 查询模式 */6 k; Z4 _! b0 I, Y9 ^+ ~
  11. #if defined (USE_SPI_POLL): P8 Z5 I3 \7 Y
  12. 8 x0 z# _' q* h4 J7 }  N3 C
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    5 q3 O1 l, }% J: L3 s/ C$ w  R
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    & n0 t( U% o1 K8 M9 k

  15. , \: a; B1 m# Q8 r- L8 T
  16. /* 中断模式 */9 X8 }0 J5 H; T# _+ ^* w; U- n9 r7 e
  17. #elif defined (USE_SPI_INT)' |! `+ L; o% y$ p1 E: G2 }$ k! K& A
  18. 8 E4 [. G3 T+ z) }3 [/ t) _% X
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    ( y: V  o! g* g4 h
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    5 Y' l6 U" v6 M. U7 x9 j
  21. 2 H- z9 f3 V9 o9 @9 h
  22. /* DMA模式使用的SRAM4 */
    7 x1 q; Z6 v9 a6 D
  23. #elif defined (USE_SPI_DMA)
    ) X2 ?- t! E' {7 w
  24.     #if defined ( __CC_ARM )    /* IAR *******/1 {" e; B" M$ t4 z+ u
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    9 l4 i. E$ a* l; t8 R" d
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];' c& ?  y+ i! P  B* Q$ b# G3 l. {
  27.     #elif defined (__ICCARM__)   /* MDK ********/5 m+ I6 z. Q4 t% i5 P- V
  28.         #pragma location = ".RAM_D3": v: v3 T' t+ h! {
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   ) f# ?; j! {. P) S) o; m* L  W
  30.         #pragma location = ".RAM_D3"1 w5 V* w0 o( [1 w. w
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];: y5 Q0 I$ p5 _. G7 y- t
  32.     #endif
    $ |* r  Z' c2 a) j
  33. #endif, z" W" V" }" A9 A2 T5 |4 A

  34.   P7 n4 ?7 s% {8 P$ ^
  35. /*
    ' ]2 t  s0 y7 R9 ^
  36. *********************************************************************************************************
    : Y( g1 N# m; u# X
  37. *    函 数 名: bsp_spiTransfer  [0 S/ z7 q; y* B- l9 h
  38. *    功能说明: 启动数据传输
      ]! n$ C+ U% X: l: S6 C! a
  39. *    形    参: 无
    # Z) E4 z3 f; ^& O& i  O
  40. *    返 回 值: 无
      R+ {3 F7 U( \1 ~6 ?7 K
  41. *********************************************************************************************************4 U5 s$ y3 ]) E3 z9 ~0 H
  42. */
    * n$ X- C$ s: m) u5 C* D
  43. void bsp_spiTransfer(void)
    8 G$ \3 `* g& e( x, \4 q
  44. {7 A+ P9 L& Z" U3 |( n0 i' B
  45.     if (g_spiLen > SPI_BUFFER_SIZE)( k7 x/ `% E7 F" `' O7 y
  46.     {
    5 h; R/ t7 v2 ]- D! f
  47.         return;' L) h) X9 y* o
  48.     }
    " Y2 Z8 G% y' |0 c

  49. 0 s. t& V+ y8 n  k7 ^# n1 S- R! T' e; V
  50.     /* DMA方式传输 *// w# j. d7 f2 ]1 @
  51. #ifdef USE_SPI_DMA& g8 M( \% B/ t1 r  y0 C5 c7 ^
  52.     wTransferState = TRANSFER_WAIT;2 m% T1 I% M3 y$ G# Q4 B: E
  53. : Z$ s# e7 u( C
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    . x- k. b8 Y5 C
  55.     {% z; Y% |* d9 q1 z7 w* C( ?5 H
  56.         Error_Handler(__FILE__, __LINE__);
    ! j! R! w9 ~, ]
  57.     }# {+ c; B! U6 r9 w6 u0 T
  58. " ?1 Z, l3 X* Y7 S
  59.     while (wTransferState == TRANSFER_WAIT), G/ x, i; p5 S. N8 J
  60.     {
    7 l7 T* Q7 H9 M1 `
  61.         ;
      l2 Z4 }% y: K' j; e% R+ W
  62.     }
    1 M# \5 X7 k  j  T. q! J
  63. #endif
    7 ~& K) n& q- s5 Z% A5 R$ E7 t

  64. - ~; b* k4 W) I/ C% t) {
  65.     /* 中断方式传输 */   
    0 L; _& R+ Q+ i% A
  66. #ifdef USE_SPI_INT+ V% s+ e1 A- w! ~* A4 I
  67.     wTransferState = TRANSFER_WAIT;# U& s% X, A; `2 w( [
  68. 0 V+ B# H  z4 F: u( @2 s
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    ! O2 V' F1 T) a7 ~+ r& v
  70.     {- O+ s, V. y  s- L: j# a
  71.         Error_Handler(__FILE__, __LINE__);
    2 G+ b+ h2 k* ^+ [* @- [; O
  72.     }* y0 K! \  {# w: V% u9 C/ Y8 @

  73. - A9 Y+ l0 H5 q- A
  74.     while (wTransferState == TRANSFER_WAIT)
    " ~9 t0 w0 ~/ H/ a
  75.     {
    ( I! ~' g) y2 p3 b  i7 K  ^1 E
  76.         ;" v$ {" ^6 I( ?7 ?) H# B
  77.     }  ^7 L5 c0 n# p1 ?) m% t/ X) C" j
  78. #endif
    % {) W8 [% j6 H: P- V

  79. 8 t& G8 C6 W! r! j! Y, [0 C
  80.     /* 查询方式传输 */    - M0 f6 k# j$ G" K" U# y
  81. #ifdef USE_SPI_POLL
    " {9 C- a# m: Y. D
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    / m: K: U( Y# V$ ?0 k$ M+ Q4 a
  83.     {
    / U& D) t7 U6 s1 H* R- p- d8 |
  84.         Error_Handler(__FILE__, __LINE__);' P6 E' s+ P6 S8 \9 a; l- S$ d
  85.     }   
    9 C5 `/ D- _8 E
  86. #endif
    6 f7 b- B4 j3 F
  87. }
    / ~0 k- A, J; @9 u
复制代码
; J4 f, ?4 U( E) C: n! ?
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:. \! H! ]  o+ K3 T2 [* [, m
- v( y, }, P/ d3 [5 x
  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。0 u, f5 T/ R  @; K* j+ F$ E
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。( v$ T$ I- @3 K- F
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */
    ; E( l8 b8 X3 w# z5 `# v
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;: ]% P# r6 q# r
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;2 n% V& p) ^# B# c3 f: F
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;$ D0 R6 m; `4 t, ^3 F, F
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    % q* D+ B' r7 Q2 T0 M) J$ c7 b: I; X5 `
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;9 D2 B- `' c5 i
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;3 K7 ^0 @& r, \8 U# r9 q7 w* k
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;! N. E7 r4 b' A. I* p  x2 m
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;$ T. f# `+ |; K& R& \& k! ~4 ]" E
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;: ^! r3 T9 l2 ^! z
  11.     MPU_InitStruct.SubRegionDisable = 0x00;7 c& E6 I  l/ A0 S
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    3 I  `) }0 ^0 x9 b5 L. E( ?
  13. 5 v9 ~6 f8 \1 m2 H
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
% `- t4 t0 a+ |2 ~' b5 z3 x) t, `4 F
73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

5 I& _! f: x1 a0 A6 l+ R2 _首先回忆下STM32H7支持的4种时序配置。& ?. p2 B; N: Y* e# i

# J+ T& x# r  z2 n  当CPOL = 1, CPHA = 1时6 U" [  H: j' ^! b

1 l, c  Y( k/ a4 M2 CSCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。' k1 l: G$ F- g

* l, J# `, H2 E! Z2 X8 r4 `  当CPOL = 0, CPHA = 1时
3 ?( C: l% Q  P3 q+ @) j
: u: i  o( w# p% mSCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。4 @: H/ a0 I* B3 Z% @; j) p

* {8 Y# ~7 i4 p6 P. f; |1 q  当CPOL = 1, CPHA = 0时8 q1 F4 a* a+ G6 R% u1 f2 R0 m
, ]  _( C: k; s3 m
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
! B7 z; h1 L+ e/ y
; S0 Z1 y" q3 {' w% N7 b 当CPOL = 1, CPHA = 0时3 R' t; X: r! R
& k$ ^$ s6 Q0 c
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
; w% w# r! L* K" O* G' P) U) G: i$ k5 l- `3 i
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
) ]' t3 K4 u+ @) z. J7 Z
6 ?% p, O0 k9 R6 N- U
有了H7支持的时序配置,再来看下W25Q的时序图:
2 [/ s7 {* ^! J
: Q' [  G8 O4 q* L3 g6 aMode0 : 空闲状态的sck是低电平。9 }' M/ u; Y: E! s+ J8 k9 A
# Z" Q3 G7 b+ B1 X* z" E9 L1 m0 E8 `$ t% e
Mode1 : 空闲状态的sck是高电平。
5 C1 B" H' y3 g  {" ^5 e9 N$ [' w* K, _0 g& g" e: M$ s1 D  A. {
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
3 L+ \7 _2 E) e3 @# l& w

/ X0 T8 W: @, E+ F1 a首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:) C8 X+ m/ k7 T% I
4 n: ~+ w3 x# l4 s( h* E
CHOL = 1,  CPHA = 1
& Q, g% Q4 k! k/ {2 K5 Q% D1 c; F; p8 h* A4 _
CHOL = 0,  CPHA = 0
# U" x! A: J, |) U9 L" B
' B2 T% t0 v0 w" g对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。% F" Y& I* x; ^# T  X" ^. K
7 N( {. p/ ?# F$ w$ A6 h1 h
73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制: V/ Y4 I: H: b$ H1 m
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:! \  g& {5 E/ L' M( ~
! W8 Y# Z1 j* u$ u5 y% O2 h
  1. /*' X7 t6 M7 I! p% d) W
  2. *********************************************************************************************************
    ; E0 f) S; s9 h
  3. *    函 数 名: sf_SetCS0 w" ?' v& V2 F, h4 ]4 ~
  4. *    功能说明: 串行FALSH片选控制函数
    0 L& H* o1 C4 E1 ?  A
  5. *    形    参: 无
    ' o9 l; }. k9 x7 i
  6. *    返 回 值: 无! F8 K  V. c, Z8 l$ g+ B4 X
  7. *********************************************************************************************************
    6 P6 I+ X" O; m3 }- C
  8. */
    3 a% Z0 ~# e& X( t. b, K& K5 i
  9. void sf_SetCS(uint8_t _Level)
    ' z2 c9 x3 Q- A/ ^, j% G/ T
  10. {4 r$ d: ~' n4 {: ~  u& X  `' `
  11.     if (_Level == 0)9 E$ r, Q) p7 Z3 |  e2 @
  12.     {
    ' s: y0 p3 B2 j: M) L# G" ^
  13.         bsp_SpiBusEnter();    " i) k0 X4 \- t
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        5 @' q/ s) _) X" @  ?/ y3 W: q# f
  15.         SF_CS_0();0 [0 m/ Y' ^8 S( s  U4 e, e) s
  16.     }* t; W/ h5 D6 g
  17.     else
    ( R' a. C9 k& A! z8 C1 w
  18.     {        
    % m9 r3 `) S6 Q
  19.         SF_CS_1();    6 v9 t8 n* c3 ?$ J  P
  20.         bsp_SpiBusExit();        4 }* _9 E, L7 i
  21.     }5 Y- O/ u3 b  L) F. ?
  22. }
    # E" ^, H9 Z  q4 [5 I5 J
复制代码
+ c( B# J2 C' m& X* B& w# k7 Q

9 [  S( n  ~# {! Z7 j2 q1 c8 q通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。4 |- w2 n8 ^9 Q0 q
) ~. m9 k; ~( r: a0 \% w, W9 @
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。/ Y/ U, s0 B$ T1 }. D- B/ y
0 O3 ]& P1 @$ S" Y3 S$ N
73.4.5 第5步:W25QXX的读取实现
5 g+ c9 j2 g$ ]3 t7 ?
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。! g% n  O9 U7 O( ~
! b& M3 v& n! g% q9 U8 e
  1. /** `2 E! h8 U2 C
  2. *********************************************************************************************************
    + Z, r( T3 W+ I* \* i
  3. *    函 数 名: sf_ReadBuffer% O8 {& H9 }' u! W
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    ' F) e, }/ [8 O& R7 [5 ~* x' S% ~0 P
  5. *    形    参:      _pBuf : 数据源缓冲区;. {6 e4 p2 C1 N
  6. *                _uiReadAddr :首地址: ~9 l1 A. I, ~$ h' \) E
  7. *                _usSize :数据个数, 不能超出芯片总容量
    ( a8 |& R, k) P0 T8 g) M
  8. *    返 回 值: 无
    % n" I2 j& G* s$ e* |; p
  9. *********************************************************************************************************
    ( I2 L9 R7 G+ [+ c
  10. */
    + U& n4 b4 a3 P  k
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)! ~: c! f7 c  E- u+ T/ V
  12. {2 X& U( `5 ^1 y6 M# d
  13.     uint16_t rem;
    1 T- E* ~( k0 K! h; t
  14.     uint16_t i;5 U, N$ F9 a0 M" m. v7 V

  15. 3 k7 G3 p0 n! ^) w
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */9 d  ]$ h; u5 a; Z: `+ k' h
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)9 u: G, ?. J; k1 a, v+ W" Z, B' v$ f1 @
  18.     {
    - D' N+ G: P+ {& V! A
  19.         return;2 M; o# B1 K  g* U  T; s: d
  20.     }: K4 O2 A; p7 c& N9 ]
  21. - q# w/ {$ i1 F0 t4 O5 ?- o9 w( B4 T
  22.     /* 擦除扇区操作 */# W: |2 L! h0 Q8 [2 w( \7 T7 t7 {
  23.     sf_SetCS(0);                                    /* 使能片选 */- v2 U$ L; \- L: S$ _& v
  24.     g_spiLen = 0;/ i  l3 v7 N) t3 n
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
    % X" o. \# ?$ ]- N, H$ G  e; I
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    : Y4 K3 a7 y, F# V, t; u
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */  h, i) V* C4 J/ c# b& g$ n1 @
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
    % o" ]7 X9 Q3 Y+ C
  29.     bsp_spiTransfer();
    * f/ F* U4 P2 u: u

  30. 0 s  ?9 C2 I& W* d5 g
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
    - h9 v# N$ J) l; }5 Q  ]. i
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)% |( R% p  j9 x& x6 ?; Q3 e
  33.     {
    ( Z$ C# @  y- x. a( H, [
  34.         g_spiLen = SPI_BUFFER_SIZE;; Y" p, H; \' Y% M3 D& S
  35.         bsp_spiTransfer();8 N4 ~2 Z# Y3 |
  36. ; Z& ?, Z6 ?2 c7 O  j! {
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    9 T; `% ^' R; h$ `2 c
  38.         _pBuf += SPI_BUFFER_SIZE;
    & [- W6 a: p% H( t* v" N6 b
  39.     }. u, _% p0 b4 S0 t$ D
  40. - q1 x4 Y/ T- J% ^" d- E) h& |" S
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */, F4 H- ~4 `0 x) b% n
  42.     if (rem > 0)
    7 p1 _5 Z' z, M7 I2 u8 w
  43.     {" o: p. G1 x: A- i* b
  44.         g_spiLen = rem;
    ) J5 B; X) b0 L
  45.         bsp_spiTransfer();6 \0 v9 `6 T! L* x( e! z
  46. ( s6 H# ?) i/ |( q9 t3 T+ L% E; L& P" Y
  47.         memcpy(_pBuf, g_spiRxBuf, rem);; h; J: S7 j5 p2 R. L
  48.     }4 t: e0 L. n( i) Y1 }) @

  49. + j' _0 u2 n+ ^; K
  50.     sf_SetCS(1);                                    /* 禁能片选 */
    4 N8 r! y; Q. _! @' B5 v
  51. }
复制代码
2 P1 F2 L; A* @
$ t) ~/ [* u) B
这个函数对DMA传输做了特别处理,方便分包进行。
& L/ G- R4 c9 H$ J4 V4 {1 m& i
, M/ C. {1 V+ j' w73.4.6 第6步:W25QXX的扇区擦除实现

$ g. S* p* O5 t. n扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。. ^; e: u% a! x# S

; Q9 u. h# d+ W2 B8 T" `
  1. /*
    7 ~) J/ V6 ~2 g7 ?) {- V- {
  2. *********************************************************************************************************; U, h; T- Z# G' s' u- e; H
  3. *    函 数 名: sf_EraseSector
    : `: X1 [2 C; C0 ]$ p
  4. *    功能说明: 擦除指定的扇区$ C+ E0 o* I9 a: m9 P1 H) C; i7 m
  5. *    形    参: _uiSectorAddr : 扇区地址  g5 _! V) ]# s. I, N1 \
  6. *    返 回 值: 无
    # @9 R+ r6 ^8 [$ g: F% ^
  7. *********************************************************************************************************
    1 `8 \2 t6 j: j0 H8 }
  8. */! c: z, @( {: [, Y6 W) _& X
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    + k4 N9 ?, Y6 k' a0 j7 V7 g
  10. {
    ( t  ?% z4 s$ {2 s
  11.     sf_WriteEnable();                            /* 发送写使能命令 */5 E0 a5 Z$ q7 l6 ^' \

  12. ( `% ]1 S: v- L, ~# k
  13.     /* 擦除扇区操作 */5 o+ I; ?4 X8 K
  14.     sf_SetCS(0);                                /* 使能片选 */
    " m5 |# W0 h) l( P: J
  15.     g_spiLen = 0;
    & q& j4 E; ]8 R/ L
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    9 n, A" l' m8 h: E' L$ t
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    0 X1 y3 Y. k2 F; _, @; W
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */: T" E- n6 }2 V: r% [
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */   
    7 n% N% f$ E4 a' i* A! N
  20.     bsp_spiTransfer();
    . y% {* N- f6 j* P8 M4 b/ Z
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    6 z- Q& }2 b8 A3 M/ j6 R, Y0 t5 C

  22. ; O2 x2 y9 a. h. f( _5 O
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */) e% }* H* _0 I4 L' k# j0 A  @  d
  24. }
    : h# Q! Z7 @, Q
复制代码

8 I$ y6 \! a) b5 W: g
+ h& u0 n- ?+ R/ O整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:2 Q) z+ V( X  F! }* D2 p# C

. m+ ^/ y# E8 V# P- K: w" T% s5 v
  1. /*
    4 H. L$ Y( `! H  q/ B- ?
  2. *********************************************************************************************************$ j. K- W  ]9 x, J+ T4 W' _
  3. *    函 数 名: sf_EraseChip
    ) j4 K( w1 Z7 i6 ~5 g
  4. *    功能说明: 擦除整个芯片) k, d& G8 H9 F0 G
  5. *    形    参:  无
    2 Y; i  W- }3 I$ A0 J" z
  6. *    返 回 值: 无; P4 L% `5 ?' ]& G/ E
  7. *********************************************************************************************************
    & s! `& E  y! s8 O: }5 E5 U
  8. */+ Q% ?, {4 r$ l: x9 X
  9. void sf_EraseChip(void)
    6 y2 \! C8 _# l/ j) ^0 }
  10. {   
    6 N; b6 I5 K* b5 S, i0 k& e7 `$ q
  11.     sf_WriteEnable();                                /* 发送写使能命令 */+ h# ?/ J1 A3 {( g8 @, H' B) D

  12. 7 m3 |( n% n9 c* x( }3 {/ a( ^! y
  13.     /* 擦除扇区操作 */
    + Q' v" E/ X) ~9 M! L" o
  14.     sf_SetCS(0);        /* 使能片选 */1 L, \5 z( E- r4 T3 q; ]4 g/ m
  15.     g_spiLen = 0;1 S" E4 U# ^4 x) @
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */
    + P0 ^& b7 a" j$ u% e$ X7 s: _0 o
  17.     bsp_spiTransfer();
    $ j+ F( h7 E2 b& D' v: A' g
  18.     sf_SetCS(1);                        /* 禁能片选 */" b6 @4 p6 d7 ]4 r0 f3 G/ Y

  19. ; V! C$ k1 j6 s1 Q
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */1 k; K4 u8 C, E4 t8 A" `& W" ?
  21. }' ~4 p5 K8 T/ p& o/ z
复制代码
- Z2 D* d# K# {7 R' Y  M5 @" F1 T
73.4.7 第7步:W25QXX的编程实现2 o9 s( A# @4 h: ^) Y- s  I
W25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
2 p$ ]+ l+ ?5 t5 r; n2 J! {7 D5 T5 E  H3 Z
  1. /*7 n2 Z9 U: O  C5 O4 @
  2. *********************************************************************************************************
    * ^) S: H% Z& \) M8 `
  3. *    函 数 名: sf_WriteBuffer
    - c& l: E" r" k* F' [! r- \
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
    * B2 R9 R: ]/ m* N5 }* m  R
  5. *    形    参:  _pBuf : 数据源缓冲区;" m( m! _1 [+ ?' @) t' z+ ~
  6. *               _uiWrAddr :目标区域首地址
    * }3 |/ F+ ~- s* l0 u1 i0 ^
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。% V: G4 j' `) |# Y( b
  8. *    返 回 值: 1 : 成功, 0 : 失败
    % a9 Y* v) _( O0 q& e$ f
  9. *********************************************************************************************************
    ! `! y4 I  S( Z; @3 x9 b& i
  10. */
    9 O  Q& q' k8 m4 d% [. I* X
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)! M/ x& t3 ]8 K8 }
  12. {9 T3 {2 }4 w9 G( s8 }
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;, N& |4 \) o9 r1 p. ~& x! o; L

  14. 9 f5 c7 L- p- l- p/ }% N
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;7 x# c" i3 @) ?- [1 Z
  16.     count = g_tSF.SectorSize - Addr;
    $ `' g3 X& o8 A3 {! M. a
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    ( |- y' g3 a( ~7 |# L" J6 C
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;0 ~+ _) R, ^0 T, `
  19. 2 o' H3 A( W8 ^
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */
    ( y' R4 F9 U; R/ T# x
  21.     {2 P9 E2 S6 ?. M* f
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    4 H6 U- K" g8 Y
  23.         {: N9 j9 }/ G: r  N( a
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    " o3 F8 W% J( X' `! ^5 P
  25.             {
    $ @# g/ r( d( v, a/ |
  26.                 return 0;. f& s# C2 Q& e( K1 u+ d) z' v. t
  27.             }8 X$ M6 R3 u& ?5 U7 e
  28.         }
    * ]* j% l' m& C4 \
  29.         else     /* 数据长度大于等于扇区大小 */
    6 P7 y5 _$ ^" l) x; Q# n* M: W) H' ~0 Y
  30.         {
    3 b+ f$ d# ^6 h8 T/ P5 l
  31.             while (NumOfPage--)
    * |4 d4 i2 ]! S( d( j& a
  32.             {3 j6 U% I  T& ?
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)2 G' n+ H0 E* T( u5 R5 b7 E, a% _
  34.                 {
    * T" Y& j: a6 e: N
  35.                     return 0;
    4 g# U+ V& |6 N: `: Q
  36.                 }4 V9 X& [* s0 N" o2 f+ l
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;9 |: q+ p1 [0 g; H+ W( _6 \3 T
  38.                 _pBuf += g_tSF.SectorSize;3 H4 l8 @( }7 c# {9 o
  39.             }! d* Z: E  ]4 q, z( v( F
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)! _8 O% Z( r0 ]
  41.             {: X7 J8 V9 P% i! w8 X
  42.                 return 0;
    ; F. [' r/ W. u; d' t- |# X$ K
  43.             }
    3 S4 |  X! w  }( s; l1 \) r! J$ M
  44.         }* C$ U! N2 G0 s& R
  45.     }
    2 f' J: \( T+ _; Q
  46.     else  /* 起始地址不是扇区首地址  */1 ^. x: d  M* [; Z; j5 h
  47.     {3 L2 @; D$ a; G( l
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    / Y6 J6 w, x2 f" \; H5 A+ k5 H
  49.         {
    * ]9 l$ p( F* @, }- i  u
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */$ B0 t; H( _* R0 m9 e) p- Z
  51.             {& o/ v" k+ \+ y) \6 H* ^4 m
  52.                 temp = NumOfSingle - count;
    & a. B1 Y+ S% Y( h
  53. 7 X) l6 n! K! n8 V" m( b5 b8 l6 t% g
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    ! S( r. B- r$ @$ \
  55.                 {
    2 @' N( V' u( u8 L6 B
  56.                     return 0;# [- L/ q' w) R( b2 `5 h1 g/ P! V. r
  57.                 }( ?0 b- |; d: Y4 \% {

  58. % v& S' |+ @/ G5 a' w
  59.                 _uiWriteAddr +=  count;
    ! C  j" O- b. ]7 A# P4 R1 w6 I1 A
  60.                 _pBuf += count;
    : \$ Y: S2 A: f/ ^6 V' a
  61. ! E5 I3 |; H2 Y2 q/ o. K0 ^* Z6 `
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)
    ' R; Z# k& N# d! v8 F
  63.                 {; H2 M4 o; ~( h$ K3 Q) m$ p5 C
  64.                     return 0;
    ( D! f/ w4 r. c# d' d% L' _- S/ l
  65.                 }
    ' y) a0 T& g! K3 \* Q- d. ^
  66.             }/ H  S- b1 p& t& A5 K9 K" W1 `
  67.             else. f& \( G% [2 \5 J5 D
  68.             {
    + i8 H* ~- @3 p+ B& m
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    ! _! V+ E, {: @
  70.                 {1 A) q" l3 ^+ g) v; u& G" c
  71.                     return 0;1 A7 C; u% r* ~
  72.                 }
    . Y# ?3 [1 J0 H* x4 v) c; B" u1 B
  73.             }
    $ C+ h' ?! P4 S3 b
  74.         }
    , g+ `# M- U8 ~7 i& w
  75.         else    /* 数据长度大于等于扇区大小 */
    : ~" A1 G+ J! n8 `2 V! R* ]
  76.         {
    ) z( |) g  C/ X1 Z  m; B9 x4 f: l* O9 P
  77.             _usWriteSize -= count;0 ?5 M5 b1 ?, f9 U. F1 J! F5 A
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    / z0 {# x; Q& M: K3 y# i
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    1 |# k; Q# J9 s  U
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)" x! X% E0 r( Z' p) @$ h
  81.             {& O* _- t4 U% A3 C. v
  82.                 return 0;9 a2 R* S3 l% @# S: `
  83.             }$ |" d+ [8 b* A# j. ]8 o

  84. 9 ]8 h4 v! ]1 }* H
  85.             _uiWriteAddr +=  count;' `, Z5 S2 n. B8 u' J$ V7 a& @
  86.             _pBuf += count;8 ]+ ~0 j( {! w$ |+ a: }/ }
  87. ' B9 L* l/ x7 [, N) u- N3 B3 }
  88.             while (NumOfPage--)9 L: g% j# R6 c% q. o9 P8 }1 |
  89.             {
    7 W9 g5 w" T3 }& l6 b9 `/ l* V* W
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)2 V! F8 ]0 f1 L/ q+ ?, v
  91.                 {
    : p8 V7 A* D& }8 {9 I
  92.                     return 0;. f, o! c1 H# p% g0 s/ B- x2 I
  93.                 }
    5 L4 i; u5 o% E( O) W& x9 Z
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;
    5 }7 u) l' o8 X( \
  95.                 _pBuf += g_tSF.SectorSize;
      Q* V1 O$ C2 Y* F
  96.             }+ ^9 h! {% B/ M* m$ I4 _; ^4 {
  97. * A+ F( N) @" t; F( H) C
  98.             if (NumOfSingle != 0)- a$ H" a5 S0 h& i4 |$ K
  99.             {
    + Z; O$ G& R% b8 `& A4 o
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)9 C( U5 h" F7 _2 p& z7 |8 S4 n: r
  101.                 {5 V8 x, P! L1 ~# t" T
  102.                     return 0;$ i7 V3 m9 M, O' B6 F+ o, X9 D
  103.                 }' {7 `. g8 L% p2 c3 W" h1 G
  104.             }6 c: @2 `1 Y7 t/ t
  105.         }5 B" d+ y- Z5 g# ^0 T
  106.     }# t2 k. m5 V9 w# B! S& i2 B0 ~
  107.     return 1;    /* 成功 */7 n; n% r4 H+ j% r1 `
  108. }
复制代码

9 ?9 g5 Y6 w) S6 P" Y& h73.5 SPI总线板级支持包(bsp_spi_bus.c)
" ^7 j( P1 ]- G& f# F7 _' \SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
9 j" C+ V* m/ d+ S4 }) i8 f9 A: z! K4 a/ D
  bsp_InitSPIBus1 G0 |9 n* R9 h( t( {  y5 ?
  bsp_InitSPIParam
3 N* F2 v9 i2 k2 _+ F( Q  bsp_spiTransfer$ m, u( n" w2 W! R  e
73.5.1 函数bsp_InitSPIBus
) I3 {, X0 @3 y! ~( x1 l9 t函数原型:
( d, }/ s6 N+ e- ?: U( K$ k6 j0 L* ?; h! ?2 ^- v. e! R
void bsp_InitSPIBus(void); d7 n6 n( u) @6 _

6 L1 z1 \' p2 _8 [% H% v7 G函数描述:
8 p7 l2 l% {" J" j+ r9 X5 f" _9 y- n. n3 e+ d! z# C. v
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。  O; x: U5 l' C  J. N4 H

) |& k. P) P% Z7 B( X( a: ?- Y( x73.5.2 函数bsp_InitSPIParam
! B/ S# M& d( E函数原型:
* ^  u  m7 g5 Q, b! _, T) V) p2 [" _% y" Z% o% c
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)2 S! d) ]7 k' p* W

1 W0 E) @1 v, p* S函数描述:, ~  f/ k8 A' o/ ]  @
, ?/ v2 _; {9 j9 w0 U
此函数用于SPI总线的配置。6 v' Q2 _6 O* g$ w5 v

9 _! P- W' }8 n* N7 P, Y函数参数:7 k" h* C$ x3 c6 u

* V9 R$ e+ l: s0 A  第1个参数SPI总线的分频设置,支持的参数如下:
: ?% G. I% ~1 HSPI_BAUDRATEPRESCALER_2    2分频+ h- S$ P. c% |- d/ S2 a8 T1 d
( ^3 m: ]7 a, l. p' E
SPI_BAUDRATEPRESCALER_4    4分频
. O/ P" J. G# ?; U: `8 c# U
# Y# i: k1 R" X, T+ m* m; lSPI_BAUDRATEPRESCALER_8    8分频, ]2 t. M3 C- |+ O6 |
) W" |8 }8 k% C+ k$ O6 g
SPI_BAUDRATEPRESCALER_16   16分频
7 {7 a) W4 l! k8 Z' L9 b; o1 ~" N/ F. r4 u  y
SPI_BAUDRATEPRESCALER_32   32分频- I' i* F; O; V* B! K4 r5 ?
9 j7 A/ }, C+ {6 g" E  n
SPI_BAUDRATEPRESCALER_64   64分频6 j$ a1 Q' i5 h9 h! X/ S1 y( W
1 C% |$ M" Y( h3 W
SPI_BAUDRATEPRESCALER_128  128分频
2 f( G  z3 X% T
/ `% K5 O; F  G2 P. {% I( h3 T  OSPI_BAUDRATEPRESCALER_256  256分频$ ^( A+ m5 @( R- _; t
# j9 `7 _; v+ W! E4 O
  第2个参数用于时钟相位配置,支持的参数如下:
. X- v  P/ w9 r# d9 xSPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据4 U1 T' {( l3 X0 e# @9 o; a

: |# w% X7 L- A! Q  R3 w, G0 S% SSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据& y3 _! O# D3 O' W3 Q7 ]9 p' b6 k

( @, f$ v# c1 p* O3 w  第3个参数是时钟极性配置,支持的参数如下:7 n! S' M( ^* o
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平; n) H: [- C- E0 l6 q" e
" |) f5 E- f. D; a6 r# K
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平" b7 b2 v$ R" ^* Y3 J( l

* ?3 }  J! {5 n* z* _. i73.5.3 函数bsp_spiTransfer
/ n" w$ G& Z2 ^* Y! f4 f7 O
函数原型:  N* }  m! r% n0 x; @
7 |! x, G0 w& {
void bsp_spiTransfer(void)
. W0 _0 I: z) @, a6 O; ^
% ]7 k0 {( _9 E* `) Y函数描述:& e& I5 }( ]& t% ^
, s! d4 o3 |% R& r. `1 O& f) G- w
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。: t( ^8 O, Y7 o+ |  Y

1 G5 p: a+ N6 K- m3 u( S73.6 W25QXX板级支持包(bsp_spi_flash.c)
  {% p# [9 A2 e; E- {" FW25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:4 M/ V" m: m" v- V" D8 n

) Q: S  ?- p: N* m! E  sf_ReadBuffer% `5 y5 Q0 q2 l3 ]- B( _- |5 N9 S
  sf_WriteBuffer
, K7 b) C; \! N9 }; }5 G3 i! E  sf_EraseSector$ J4 B- F) n7 S6 S8 v  c8 A0 Y
  sf_EraseChip- L- W+ i2 o8 g
  sf_EraseSector
5 Y" |* p3 g1 z+ ]: K% d. C73.6.1 函数sf_ReadBuffer7 \! P. F* w2 w
函数原型:
% Z' S; ?( V. o6 \: D, k' K
  L7 O3 k" q: v: d9 T, w/ t( zvoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize): C# q, R2 \7 }- _5 Z; D: W

: K2 a0 x3 S" e函数描述:" h0 {) O: [3 ~" S& |% L( B1 M3 [
3 \/ D9 a& J; m: n7 q1 D: ^
此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。
" R6 N( ~' N6 Y& I
& r( G& o1 }* P4 Q) S8 `2 m函数参数:
! Q3 q1 F0 Z1 \9 }9 r- V( V
7 \5 q% S, L; f9 [# V  第1个参数用于存储从SPI Flash读取的数据。/ a; Q8 n. @# S" Z8 `
  第2个参数是读取地址,不可以超过芯片容量。
5 M1 e- x( L: p* w: f* _( |  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。. L9 R- [% W6 o5 h* U% H7 C- S
73.6.2 函数sf_WriteBuffer(自动执行擦除)3 C* W/ v9 c" V' ]& [% J$ f
函数原型:
! ^, T3 \" j& n. k: [- V& ]
+ W) t8 P1 O& g$ [uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)9 R& `+ n" H: ^1 }4 }& f
/ X. k; d! p% B7 W3 y! [
函数描述:/ M- j: c. x. s$ w
2 l/ |/ a2 o3 r4 E
此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。. J5 Q# s9 Q% f

  T" M/ q) s1 d! k3 j  U函数参数:% F1 G; F! {8 S. N$ k4 |
4 h7 ~" F! ^  a0 V8 Z
  第1个参数是源数据缓冲区。# T5 c# p; `5 {3 c3 G6 {
  第2个参数是目标区域首地址。+ H! t0 n, C0 s5 H
  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
. j! o6 }$ f0 a0 L! {( D+ I  返回值,返回1表示成功,返回0表示失败。! c8 |8 e5 j4 d$ [+ b9 [0 s
73.6.3 函数sf_EraseSector& k! [" Q, _+ i& m
函数原型:
, ^6 _: C5 \7 p& A/ x+ p2 j5 z
- `0 B. g  Q1 X7 u! M9 Lvoid sf_EraseSector(uint32_t _uiSectorAddr)- p7 V/ n: N4 V9 m; e

- k& V8 g7 v6 m/ v$ ^函数描述:8 B2 y5 z5 A  D4 S# Q- y
: ]; g! f3 b' v6 j. P
此函数主要用于扇区擦除,一个扇区大小是4KB。
4 u" b0 S4 T1 @0 r# K% t5 B" T  g, [) V9 O! K/ E/ V
函数参数:
; M/ C: ~& c/ K- l( I  u
& E4 h! N2 @5 l+ h; V* N+ `  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。2 d. n0 a/ ~: L) X8 q& H# j
73.6.4 函数sf_EraseChip
0 S- x& z9 A& W; b% u' ]# J函数原型:2 |" b/ R& X! m5 r$ a. G; e4 d

$ q9 M/ y/ V7 D5 g5 V6 Evoid sf_EraseChip(void)/ `; D3 R( O9 H4 ~9 B  T

7 K& ]  n+ S: L' w. l0 ]函数描述:
: i9 D" W$ A6 |3 ?: k3 E0 k4 }4 |. y' C6 |& b
此函数主要用于整个芯片擦除。
8 ^3 ?% O! s/ j" {% `: P; q" s* z- L2 Y' R* `
73.6.5 函数sf_PageWrite(不推荐)& U) |- _! o$ ?9 A) f2 q  ]
函数原型:& ]9 w! a( I' K
! _1 w. Y+ J* \/ z% O, \
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)
! E+ {% ]9 K' P  v3 w  O
/ s, V) Y; f% \2 u6 F9 x3 _# B函数描述:! k) U) Z# ]$ T- w. m' L% Q+ X6 _
% `; Y: z* c. e" _; N" s
此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。0 R9 c  C$ z/ `% R
# }$ Y: B' }9 |0 w( [
函数参数:
8 @+ R+ b, d; ^' p- c2 S, ^7 D8 a; `& i
  第1个参数是数据源缓冲区。2 l, k2 L0 @6 D0 n# ^7 J8 @$ S
  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
3 ?1 e. j# R  a$ o& f% Q  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。7 a( z8 K+ k6 I: [! T+ `  M
73.7 W25QXX驱动移植和使用
, q; L+ P: L& \; ~0 e. S3 vW25QXX移植步骤如下:- p, ?3 q; w4 u; ^8 j; R

/ F# z! C' l1 s; y, q3 X  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。
' g: T' ?4 w' Q1 i* B9 R2 j8 T' m. g  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
+ ^- \% j* D+ _) o0 v6 s0 N9 e
  1. /*9 G- Y( o; `6 f9 P8 i! O
  2. *********************************************************************************************************
    % Y2 Q- r' O7 `; t" q
  3. *                                时钟,引脚,DMA,中断等宏定义
    % u' D. h& e8 \9 u7 {& _
  4. *********************************************************************************************************
    4 b) G, Q4 P6 K/ D. c) O+ h
  5. */
    % Z- B/ L& @' {6 d' [) V* }$ o
  6. #define SPIx                            SPI1
    9 K" W6 q" q7 i+ F+ H) k/ R  u: e
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()  ?# v2 d5 T1 G. b3 U- P
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()
    " z: ?: n" S) o7 q
  9. ' Q1 O- q" p/ F0 @
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
    + X$ g! {. o! E3 B7 r2 U8 h, X
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()4 q( n8 W2 `2 P6 M8 \' i! d, Q
  12. * [6 T, I9 `7 J8 b
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    6 L; ~! z5 ~5 _* I8 H8 ?# X
  14. #define SPIx_SCK_GPIO                    GPIOB
    9 V8 i  A' [0 k& C$ o; |0 r
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3. c) w5 u6 f1 U6 ~- k
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1
    1 j: w+ F0 l& S* V6 o

  17.   O0 A' Y' ?( R: B4 |0 j
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    ( S2 W, ~3 W! O1 }/ ]9 h9 s5 ~& j
  19. #define SPIx_MISO_GPIO                    GPIOB* S$ k5 r3 j; W0 C7 N/ v  f. f7 a
  20. #define SPIx_MISO_PIN                     GPIO_PIN_47 V/ n% T8 a, ]# w' S
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1& [$ x4 _1 [8 h( t& P- I
  22. 8 Z/ ?9 X6 h& J+ D' E
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    - Y" b- m, [) B  d, W
  24. #define SPIx_MOSI_GPIO                    GPIOB9 h: \3 d  x" ^' \- @6 r% D
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_57 ~. [2 F( l' W! v
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI17 h' h5 M- Q, }7 A5 e( I

  27. + A7 \- k$ i; g3 K6 Q: x
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3, F3 M6 O- p7 F5 l) E
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2# W  V) M( Z& z$ M1 i, k
  30. 0 ^- C2 ]1 e6 r! s! p( H+ J
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
    . j) Y. H1 e) V! K- {: t# L
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX8 o1 ~  p3 Q- D' @% q
  33. . ?( Q% r* O( M& G9 J
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn; K3 G. t0 p' s. ?( m% a8 Y
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn
    # v+ `: [1 ^! C8 x# P" {! E
  36. 8 @, ?! Q# K- W4 H3 X3 T4 d
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler4 }5 e$ y2 V7 I' J/ @
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler
    , x4 I# v0 s. D7 R2 `3 P
  39. 3 a9 Q0 o9 ]4 q+ @, c% x
  40. #define SPIx_IRQn                        SPI1_IRQn
    7 G% C. [( U# D" r
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler4 q' f' T, o, H; o, p* }. V
复制代码
& f% v% y; s; `; i3 s4 @+ h3 q
5 Q  e8 H3 H+ ]1 d! o
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h" t3 i) e: d4 q9 q3 D
  1. /* 定义串行Flash ID */
    5 `5 F  f4 f" x) E. y, ~
  2. enum
    % G% W( _7 y$ ~# J8 I
  3. {9 @6 t: z6 D* U) n6 Z! G
  4.     SST25VF016B_ID = 0xBF2541,
    4 {( K, W( u- P
  5.     MX25L1606E_ID  = 0xC22015,
    . ], i3 N2 b" C& i9 D
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */4 R# ^$ o. G: l' @/ o5 X0 e  Z
  7.     W25Q128_ID     = 0xEF4018
    " r  M0 D4 L6 L# f) {+ z2 p6 _
  8. };
复制代码
; h  a% ]4 U+ e# l1 f  a0 h- N
  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
) U0 l  f$ |/ x! d* |
  1. /*0 f& m" d* ^4 W' Y- \
  2. *********************************************************************************************************8 O/ b  P! W+ s9 W7 e
  3. *    函 数 名: sf_ReadInfo
    8 s+ N# ?+ C0 U
  4. *    功能说明: 读取器件ID,并填充器件参数9 @8 h  W, r+ N3 e8 {
  5. *    形    参: 无4 N; C# l" _; q. n1 F
  6. *    返 回 值: 无
    % }* T3 n% P% T6 q
  7. *********************************************************************************************************& W; u9 ?. L! r* `/ _  Z1 z8 q0 X
  8. */
    * I# v. M9 y8 I# }: b3 G
  9. void sf_ReadInfo(void)* p( g  V! ~# E4 M3 b9 z: T
  10. {+ _9 O& s7 [* {0 W: |  P
  11.     /* 自动识别串行Flash型号 */# |  E( p3 n) }# R, J' ~) P' z" k
  12.     {
      E' n9 i7 p; J# U
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
      y$ I) n% ~/ X) V* l
  14. 8 F8 m2 I# j# n3 O) e: B8 A* q6 `) e
  15.         switch (g_tSF.ChipID)
    3 V8 i$ Q* q( a& H
  16.         {, v) r# W6 N$ v& O' A* b! x
  17.             case SST25VF016B_ID:( H7 [9 \+ l) s( y/ Z
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");5 a" p1 B! H: i( ^
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    " h7 v3 r. i9 g# K" s) ]8 u
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */% y8 R' |3 j& w& i
  21.                 break;, `, j6 _- a9 b2 c- @

  22. : b! T+ W" ~2 m- o0 o+ i0 r
  23.             case MX25L1606E_ID:
    ! G8 ^' f- q' b- z/ U3 v1 j
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");
    # F* L  S0 w2 }/ d( H
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    " D) }/ H2 v( d; j
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */3 ]; ^7 d! D- K# R( o9 c
  27.                 break;
    + x2 U  @- `6 L' @! b+ }+ S

  28. . _& k, B) n) k! n8 O# o
  29.             case W25Q64BV_ID:
    - B- s& D% e! Z4 z( e% J* o
  30.                 strcpy(g_tSF.ChipName, "W25Q64");, O* x7 C. T& \+ S# |; x
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */7 ^: H9 S' t# [4 ^
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */8 }4 Q) c/ P* g+ b- M' U
  33.                 break;
    8 B+ ?1 i$ C9 Q0 }. [' d# t, \

  34. 6 D% i0 H5 W8 u; z/ p; Y
  35.             case W25Q128_ID:
    ' a4 G/ u1 L7 v& G' Q- q! \
  36.                 strcpy(g_tSF.ChipName, "W25Q128");
      q/ R+ e1 q9 y; L' O
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
    ( J  a2 G6 K1 `' d$ {# ~, ?
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */9 n5 S0 ]( N; ?( I( E; L8 c
  39.                 break;            
    ' q! j4 c  m- B: U! \

  40. 7 F6 K0 t0 h3 Z: ~- c7 [
  41.             default:4 b; O$ _# b  ~5 M! D0 a, \
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");
    " `# l3 u# B2 s- M2 c" ~! R: F5 O* b1 B
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;
    8 W% K( Q4 U, Z' {  ?
  44.                 g_tSF.SectorSize = 4 * 1024;
    , ~: E  {" w# ]/ Q, S
  45.                 break;3 Z3 X6 J! I+ L# [. t7 R9 h- c
  46.         }
    , ^$ U6 h/ i7 B; Y8 @
  47.     }
    ! T  K- H0 E- h: o9 {' W
  48. }
复制代码
( g. B0 o1 |# o( p
  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。% ^0 ?6 E( @6 d
  1. /*' {0 m! u0 K8 c  V7 Z
  2. *********************************************************************************************************; @4 A/ H! E; J+ d
  3. *    函 数 名: sf_SetCS
    $ ?6 x+ L8 t& ]. _: o
  4. *    功能说明: 串行FALSH片选控制函数, w! l2 [7 F1 Y0 s6 `
  5. *    形    参: 无1 k/ x- h0 D2 F: u0 o) P
  6. *    返 回 值: 无* ^# x7 }8 z$ l8 F1 x
  7. *********************************************************************************************************9 i9 E5 ~( i1 h
  8. */
    ' ?+ X$ x# Z1 N; f1 `$ O& [) q
  9. void sf_SetCS(uint8_t _Level)
    . L% e1 B' g& S  ?. v$ K
  10. {
    3 P( |* G5 `+ r
  11.     if (_Level == 0)
    $ G% R& G; S7 F
  12.     {
    , `; D4 x* Y1 |* e/ L. v% J) s
  13.         bsp_SpiBusEnter();    % |* l( G- l' O/ N/ V( d" m
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    " j4 Z9 o: b; U8 r
  15.         SF_CS_0();
    & [; H0 i! B( B. v
  16.     }$ Y; l5 J9 S$ Z: \" x- Z1 {
  17.     else
    * v4 ?  w4 J9 [# r
  18.     {        
    9 Z8 ]! |# b/ W7 P# W( j
  19.         SF_CS_1();    ) M. [; e0 V  U3 h0 l5 i
  20.         bsp_SpiBusExit();        1 d. l- m' e7 C3 z: Q( L
  21.     }
    ! `, s% r( o, q7 L4 B! e
  22. }
复制代码

; h) T( m. b2 X9 p4 p, b& ]! ^; M  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
  s6 T: J* @* ]; m* x7 t7 ?
  1. /* 串行Flash的片选GPIO端口, PD13  */( T& H' V' j2 U& Y  Z, S6 e4 S
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()9 e2 t) C& `2 J
  3. #define SF_CS_GPIO                    GPIOD8 A5 t7 g- p& [4 s2 S% A8 p
  4. #define SF_CS_PIN                    GPIO_PIN_131 [# o) V8 m) V4 i

  5. - s% h. B% N3 p! y. V/ s0 l
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U)
    0 V: E+ h+ T3 I
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码
0 U$ n( O$ ^* }8 x8 {# r! N9 U
  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
" r! e9 s; O3 `5 O( S
  1. /* 配置SRAM4的MPU属性为Non-cacheable *// o% `$ D: U8 t  K. h7 ]
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    - ]1 {. `) `. g+ S0 d7 h# ]5 _8 P5 c
  3. MPU_InitStruct.BaseAddress      = 0x38000000;' v% A5 }) C2 l5 R; S
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    ; g' _% d' O# P& n7 u% x5 C* G+ V! _0 q
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    8 l5 y/ U7 i5 F0 h
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ( B# F" g% h% ^
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;3 w" I8 l4 C+ e: a9 ^7 R
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;9 E' [. N/ g2 I% @1 T  l# t
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    9 H' e$ I/ K; h; |- ^. y( l  S
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;- c+ b! L+ b2 M( W' s3 g0 |& N& k7 n- \
  11. MPU_InitStruct.SubRegionDisable = 0x00;) j- u0 N4 l$ i; Z
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ' P7 N5 E- ?8 S# b) V0 u& E* t- b! p

  13. ) E+ Q7 p. U! ~5 r7 _) A
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
: S. b* l: G# s9 `4 q
  第8步:初始化SPI。; {1 Q/ Z3 u  F( b. t
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
      E) J1 }: F2 [8 h, W
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    & o, U* g4 D6 P* h2 D/ p
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码

! O" o; g: i3 }- [  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。! J% A7 Y  [& z1 M; E
  第10步:应用方法看本章节配套例子即可。
  P6 h2 s  _4 ?73.8 实验例程设计框架
3 S3 L8 h3 K" b通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:* Y- D$ u$ R5 C$ i, e% V

* j5 l8 u, g3 J) u* B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
9 n4 l- r: @* ?" Q! B% o) t

1 A7 n1 ?! L) I5 K! F, N* L  第1阶段,上电启动阶段:8 q, w+ D* ]' `0 l" A1 D

! M0 b6 `# H/ g, L: }$ ?; Z- S这部分在第14章进行了详细说明。
, @3 V" l5 B8 ?5 K& R  J6 ^% D0 v  
  V" f9 ~$ K9 Y; x  X9 Y第2阶段,进入main函数:
% M! K, f1 p4 ]* P2 k/ |
) W. w+ I5 ?' B: m: g( z2 X" @第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。# Z, A5 _" d2 d( D( s
第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
9 o/ j4 N4 i; X/ U) h
3 R) w1 P: _% X7 m73.9 实验例程说明(MDK)5 d8 D% u$ o" C: u( p% h& J( ^' U
配套例子:: A# H9 S8 b& b8 q5 S; W6 `

) v/ d# G( l9 q( vV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
# ~, K5 ~  f  k% f# _! p. j
& S0 I; f2 h6 G4 ^V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
& c! R4 X+ T& k0 v; v) d/ A* e( u8 r
V7-051_串行SPI Flash W25QXX读写例程(中断方式)0 \0 N5 N, K& v; S1 I3 v
6 I" R- o1 h9 y" K
实验目的:
. C; C1 |+ t$ A4 A! m9 F( ]/ K6 A+ O  ~* p7 P4 \, M
学习SPI Flash的读写实现,支持查询,中断和DMA方式。
& a% p$ i& d. d& Y$ i3 t2 o
8 |7 q  M8 M7 y$ @' T6 n) s7 i0 R实验操作:
: x6 m- e6 e/ ^( }$ l& R8 F0 ]3 S) t' h4 X2 [
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
( y' \% H' Y) d6 q5 H! eprintf("请选择操作命令:\r\n");
3 S1 p0 n, b, c) i8 l* Wprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
3 H4 [* l, R% Zprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
: ?) l( z1 ]2 }% F$ ]- E7 a, eprintf("【3 - 擦除整个串行Flash】\r\n");
. S, X9 Z8 D9 p' i* i3 mprintf("【4 - 写整个串行Flash, 全0x55】\r\n");! W/ F3 q# p/ P" M- P/ d
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");
0 e) P; B& c0 X. A8 C2 Zprintf("【Z - 读取前1K,地址自动减少】\r\n");6 i, z& c/ `9 _8 S
printf("【X - 读取后1K,地址自动增加】\r\n");
* G# J2 s. b" C6 Sprintf("其他任意键 - 显示命令提示\r\n");
6 ~" i* `: ]! g( i- o上电后串口打印的信息:
* u) z* n. J1 Z3 E9 T5 q$ K
7 U0 B$ O/ j5 {+ R' h( k波特率 115200,数据位 8,奇偶校验位无,停止位 1。% h* Z8 F# ?- s8 O, x
4 v' W: p3 q+ x, n- R% X  u/ P
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

9 s4 Q' U! y2 [" f; B  F# A; H
- v" Q: X& A4 p5 j) P程序设计:
; \  m- X) G3 d* E7 s  J% a% e0 K4 _3 _/ R' z
  系统栈大小分配:
  Z4 m  ]7 x/ t7 h9 y7 n: a7 Z( ^% L+ `. {- l) D$ f9 V& {, n8 u
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
3 B- v) X4 ^* b9 \# W( H& }

; e" r/ d* E' e( Y  \9 K. W0 ?- F  RAM空间用的DTCM:
. Y7 n4 y& K5 u9 i$ |2 L" u% X2 N8 Z  c
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
7 S7 i& W& x; B
- I5 z# t( _! J  e5 t8 A; t
  硬件外设初始化
0 L- G% ?6 [  Y2 i% Y# L2 ?7 o5 H  ?( U% R5 o- D
硬件外设的初始化是在 bsp.c 文件实现:
! j, n/ K6 H( y0 e; K3 c8 P' b7 M8 D$ ?( B# A
  1. /*
    1 N% g0 E0 ?% c3 N
  2. *********************************************************************************************************
    # A7 \5 ], O/ M& m
  3. *    函 数 名: bsp_Init
    " A( O% z: b5 z% O( m$ h
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次" l, j9 v1 R- Z" R# S; f
  5. *    形    参:无
    4 L3 Z; p; ~# s
  6. *    返 回 值: 无7 V( h3 o, n7 a# e
  7. *********************************************************************************************************
    & ]9 d1 i& v9 t" [* H1 T5 B/ H
  8. */( m7 G9 D( m+ s4 Y. V
  9. void bsp_Init(void)
    8 l3 Y  Z4 R7 `: d7 j1 H
  10. {( }8 S; c' J4 K. @8 R- O8 |% Z* Q
  11.     /* 配置MPU */; Z& i! x$ V9 d8 B5 N0 g7 _5 L
  12.     MPU_Config();: M/ o7 U% W( S0 N+ w3 y
  13. 2 i0 j% S7 U7 P
  14.     /* 使能L1 Cache */
    9 ^9 K5 e8 q; N3 o/ g. ?; V
  15.     CPU_CACHE_Enable();! x7 k9 {4 k5 |. y9 a# J# s
  16. ' }  X1 f- j) t( C  ^% s7 P
  17.     /* ( d9 y6 G; U6 t( f) \
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:7 g+ q( D7 d3 c+ S
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。9 u; z+ r5 f. `4 t' Y- y
  20.        - 设置NVIV优先级分组为4。( ^9 t: Q( u! `8 N+ d
  21.      */
    $ r8 l# L0 N$ s: c8 `' i
  22.     HAL_Init();" E, S2 r1 P' ^& h8 ^" @0 X2 ]

  23. / h9 t6 W1 t( m4 N
  24.     /*
    # ]4 x2 S3 I" `3 q3 ~; Y  b
  25.        配置系统时钟到400MHz; Q6 @! n  L5 h$ Z' a& P
  26.        - 切换使用HSE。5 @8 I" r$ d, Y* j' U+ ~/ ?
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    6 u( d7 u: {1 l% X0 X4 i) ]
  28.     */5 @; ^3 U* e6 [, q6 Y4 D
  29.     SystemClock_Config();4 o4 g* U' _9 O- A4 @

  30. ' V+ e8 U3 N: [1 m, k9 q- D
  31.     /* 8 Z, d$ l1 h3 `
  32.        Event Recorder:9 b3 f, n4 N) h5 x5 i: `7 a, u
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。) e- f" T. ]$ D/ G
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章5 \% g2 P4 f* b# B$ L
  35.     */   
    ; h) I1 a; m0 b2 ?" d% Z
  36. #if Enable_EventRecorder == 1  
    ) G  F! ?2 {3 B! u0 {
  37.     /* 初始化EventRecorder并开启 */* p5 n( i, I, ^; c
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    ; P) d; x4 l6 w; }& [
  39.     EventRecorderStart();7 x1 }3 a, }8 F3 T5 Z  ^
  40. #endif
    5 G/ g; Z: |1 [2 J/ w4 O+ `8 Z
  41. ! F7 P5 I% U) Y3 @
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       ) X; L$ j/ l, Z0 g2 q) U; m4 t
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    $ B7 n- L! p$ o; G/ A) _! Y7 r
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */4 M- R/ ^$ s$ t8 I
  45.     bsp_InitLPUart();     /* 初始化串口 */
    2 `& x  b5 b# x
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    % l' z* p* b1 Y( c
  47.     bsp_InitLed();         /* 初始化LED */   
    1 F3 }1 c/ K' H
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */6 ^+ q  Q3 z; g8 Q- t" O
  49. 2 p! l  y+ t7 c1 M
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    $ v$ i4 J+ y" \' [
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        : y0 a5 Y; F" T  \! q2 G* B
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    $ H) C, @' f- r) c( s5 H6 Y; |
  53. }
复制代码
2 `9 {3 S# u$ w/ v
  MPU配置和Cache配置:* X8 {1 _3 Z- J* }* L
* ~5 w- w% W2 _6 U% F
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
8 y4 t, s. u3 u- N$ o, `, d  z
2 I+ }. p( p4 l6 f3 Q
  1. /*1 ?. b8 X9 `6 Z" @3 W2 g% |! i$ K" J7 {
  2. *********************************************************************************************************
    5 \6 N; L, {% @
  3. *    函 数 名: MPU_Config. p# r6 }- X* u2 }# e( _
  4. *    功能说明: 配置MPU
    . D8 r3 w' y' u/ o/ `7 l9 d
  5. *    形    参: 无
    6 u( X: U+ B) b4 K3 g3 s" `% V. i& H
  6. *    返 回 值: 无
    ( D. \2 o! s0 M- F6 l$ _
  7. *********************************************************************************************************
    * s5 r( `& R% a
  8. */
    5 j& N0 g: g, _  L6 ?
  9. static void MPU_Config( void )! N) {$ h$ C. T1 R9 Q
  10. {
    + W! G7 q- m2 s' ^4 s8 I# K: M$ H
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    , s+ B. d6 c* @: T3 g8 h
  12. ! v* b+ o* h3 m& b. ~
  13.     /* 禁止 MPU */( }, G5 s; \' l* O( e
  14.     HAL_MPU_Disable();
    + Q5 |: g& [9 A4 Q8 R( l( @

  15. 7 Z  B' c* X9 r
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    & V+ y, _  v/ D; ?( M
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;9 h8 U4 [% w# u/ C
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    6 @& [; m0 y# |
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;/ G' V& j4 @0 V1 c! P; k: {$ Y$ U
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    3 L* G7 c1 k/ w& N% S
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;$ M( e: u: Q: a5 C. F2 z: |
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;0 D2 p4 F* p& e$ O# z
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    - }7 y8 F" d0 H7 c1 Q9 z
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;' {' G8 U+ M1 ~$ ^% Q
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    2 h& h% P2 y) Q
  26.     MPU_InitStruct.SubRegionDisable = 0x00;' N7 G! r2 t% m3 ^: N. _
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;& j; G( |6 a. m8 P/ D' V, m
  28. - J; o/ p" Y8 M3 i; V$ l* @
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);$ g  x4 F8 e& x- E1 y

  30. 8 s4 g% k3 c3 m  }  w2 S0 W/ M

  31. 3 W5 \' G) O1 w! S
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */0 z: `# Q- b' Z
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ; F6 r4 ^/ h% s  x+ B
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;' h5 d; I0 `# z7 h0 Q
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
      {3 c' g7 {" x* W# @# ]
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;' B8 T; f" F$ W) M/ h; }& P" w
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    $ y0 S( k& f# B: y, R
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    ; F% N7 t$ h3 `; b$ l$ {& J
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;7 d4 f; F# c1 Q
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    8 F& c4 Y, w4 S( e; {$ L
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;  {1 d' {/ B' }$ Z2 O- N4 t
  42.     MPU_InitStruct.SubRegionDisable = 0x00;4 ]$ z. ^! S" e& S! B7 l
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;& b* E8 X4 j8 Z- Z4 f( j
  44. - Q$ D2 [4 G6 H  }, A
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);( z/ i' a+ H" J4 _+ c) q: A2 k( M6 i

  46. 8 f+ N3 F# W+ M+ }( w
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    * O! Q2 r1 s. X4 i; ?) v
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ; T2 d' _: A0 f. P2 R* P
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;9 k. Q# j2 C! V' D. a# D1 J9 [8 j
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    % O% d- e& _4 w' |& R
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;7 |/ c& q+ c/ D1 \8 H( C8 d, Y/ P
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    & }/ Z3 t) K1 p7 _* A: Z" }" m
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;  g: G4 K" n$ u  p
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;4 H/ j* }& [  R. ^0 t( A! O1 `) _* c
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;* J: n- P7 |/ y7 t( e8 i
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;+ g5 o7 d6 n, v: G
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    9 \3 M4 F$ C( k9 |7 k
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;! u# o5 o8 j( n2 x* b2 @4 t
  59. ( R! z' e5 K9 U+ n
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    0 u2 D- X& ]. f$ C* }7 i

  61. ( y5 W% v) p1 L7 I
  62.     /*使能 MPU */! \3 M6 L* S9 Q! a' Y4 C
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    . [! z& M6 Z8 W) E5 B
  64. }
    2 T6 D& g( Z" j9 b" R/ i  r1 R6 B

  65. ) k2 v0 A/ L5 m; }& I5 _" F. k& n
  66. /*1 R- I5 c' a- Q! k; ~% N% t
  67. *********************************************************************************************************
    8 ?' a2 T  v3 R
  68. *    函 数 名: CPU_CACHE_Enable
    ( ?- l- L0 \/ i& u# \" e. U  l
  69. *    功能说明: 使能L1 Cache
    + g# Y% s4 z7 M5 l- c5 ]
  70. *    形    参: 无
    ; q( ^  g# w3 w; H" @
  71. *    返 回 值: 无
    ! b: Y$ T9 }6 c/ ~9 y1 j0 ~
  72. *********************************************************************************************************
    ! ]. w/ N) y" M5 p
  73. */4 y: n% |4 ^$ L5 M' B) x( b
  74. static void CPU_CACHE_Enable(void)2 c- Z6 g- b" Q& r! K1 ^
  75. {
    * x/ D; t5 s5 z2 R! `" v9 p
  76.     /* 使能 I-Cache */0 H! P1 L% f4 N/ {
  77.     SCB_EnableICache();% U, h" D, F1 |% t" _/ ~
  78. . T8 K1 A3 [/ F# f3 S- q6 }6 }
  79.     /* 使能 D-Cache */6 A- x1 ]  F4 Q
  80.     SCB_EnableDCache();
    6 [4 T/ }: ~+ `, K) [# H
  81. }
复制代码

2 U' `) D% j. C7 P' D% z( \  每10ms调用一次按键处理:
$ ]" w4 ?# [" F6 M' j! C( k1 u( B# [  @1 r
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。9 j; v/ f* p7 P6 d& o* ?
# Y. O! T! `, \1 {5 f. I+ \
  1. /*
    ) E' J4 j. C6 J1 @; S- q9 G2 _
  2. *********************************************************************************************************
    - `1 a& l! q3 j- V. n7 E' S
  3. *    函 数 名: bsp_RunPer10ms. f, v; e' T& n# ?. ?/ T
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求* V$ }9 G* g. t( O
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    , j- m. A/ v- P
  6. *    形    参: 无
    2 r  ?% j5 ^5 }, d8 e9 w
  7. *    返 回 值: 无2 x7 [! \$ @/ C- Z# P5 F
  8. *********************************************************************************************************
    ( u1 w2 G. u3 x( ?
  9. *// Y* a* J; L, n* n3 v' S
  10. void bsp_RunPer10ms(void)
      k& r( n# S4 }6 K$ c
  11. {
    1 b, E. j( A3 ]* F
  12.     bsp_KeyScan10ms();
    5 D$ R% s9 @8 F- t+ r' {* F
  13. }
    1 v+ k# E" f9 h8 F
  14. 2 H; `7 ~/ D+ Y# E1 U# B9 d1 V* J
复制代码
, M6 y( B6 p0 E, A( y/ Z
  主功能:9 F4 w; |, j+ X8 _, w, t

+ r* v+ W  n& V$ G8 q+ W- z& h主程序实现如下操作:
, D* i# ?% a/ y8 ?9 B3 z
+ S7 w) ?8 B" y* s- y  启动一个自动重装软件定时器,每100ms翻转一次LED2。: D' d4 |% Q& K
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可. v3 X) @. t1 Q- H- d6 N2 r  [
  请选择操作命令:+ y/ O& ~2 G6 H% d' j$ Q* t
  1 - 读串行Flash
( }3 O, L* X6 r/ j' u  2 - 写串行Flash" x% I2 m0 i- P2 R
  3 - 擦除整个串行Flash
- s! |7 d: ^4 V4 k8 I  4 - 写整个串行Flash) o- T  ?  ^( P' R' B
  5 - 读整个串行Flash  z, P" _* R% Y) \$ Q
  Z - 读取前1K! G  a' N5 a! n2 S7 e
  X - 读取后1K
  s# b, t/ v0 U6 o$ D* F
  1. /*  g6 Y4 N7 L' J* c& }6 r8 P
  2. *********************************************************************************************************5 w- W: ~4 D$ y2 q
  3. *    函 数 名: DemoSpiFlash
    , n" H$ ~2 F4 M& L
  4. *    功能说明: 串行EEPROM读写例程! q9 r% [# t$ ?$ K
  5. *    形    参:无
    ( u) N  D; g; m+ `. G9 t
  6. *    返 回 值: 无; e. G5 \/ r, i1 O5 x1 Y
  7. *********************************************************************************************************
    " F* R! v$ n, A* N
  8. */
    0 B0 G/ T7 J/ K' H( @
  9. void DemoSpiFlash(void)
    8 e% ~- a; c+ p$ c7 l6 P* H+ M6 o
  10. {; C, [5 a$ z0 c# K0 X& c
  11.     uint8_t cmd;
    + c9 @7 S' P% t# V/ b) {" P2 r
  12.     uint32_t uiReadPageNo = 0;
    4 i& `" s& x2 [& |* s3 m! Q# {

  13. # J1 f, s9 \; c6 r; \7 b) E% i

  14. - s( _8 w' ~# a
  15.     /* 检测串行Flash OK */: n/ w3 n5 T& M
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    # U' f3 }, y/ u& \; x; V
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);6 b! Q8 |: h9 N( V

  18.   Q. U  a+ N) S* n  \0 ]6 p
  19.     sfDispMenu();        /* 打印命令提示 */
    0 [0 C# ?, E! R1 X1 C9 A
  20. " L" @7 S4 U1 s( ~0 D1 k
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    ! o; _0 s! r7 |  {( E
  22. 1 F- G; e4 O" P, e9 z0 r
  23.     while(1)' ?/ i; j! B- B
  24.     {
    ( Y! l2 J/ r' y& P' i" N
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    # T" j+ i! A+ m& _- s
  26. : z4 j  A/ n0 a+ a
  27.         /* 判断定时器超时时间 */# L' b( h8 q' N  ]
  28.         if (bsp_CheckTimer(0))    / a+ {* n$ [1 B& J  D# N& f( i
  29.         {5 L4 F9 c. E& ^( I
  30.             /* 每隔100ms 进来一次 */  # J) e2 D0 h% m. |% b9 r
  31.             bsp_LedToggle(2);; a/ @& v. {  A. F
  32.         }
    6 Y+ D2 P  b: P: a

  33. - i/ s: C. n3 S# H4 ]; ]1 B2 s
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    * n' @& l( J. ^
  35.         {
    * W8 b- R( p7 K7 z2 c
  36.             switch (cmd)
    + S( R+ D, M* V+ R" s
  37.             {- G, R  q9 E4 E
  38.                 case '1':) j8 `2 G  }8 y
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);, w: O1 m% Y* e5 o. X+ z6 s
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    % q% \) i; y& _1 N( T
  41.                     break;7 |: [& ?4 I* {, R. ]7 a
  42. 0 J4 d5 F( g  b  e
  43.                 case '2':
    3 @( V( h! `& @9 ~
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
      ?" H& Q. r& }0 c; `8 ~% a
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */. m/ |! O$ l% @5 U7 G( Q
  46.                     break;
    ; K* d6 n1 e6 a, e5 B! j

  47. " e  `: x4 q- z5 j/ g6 m: ^6 |
  48.                 case '3':$ B. e" V8 Q2 z. e- Q
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");% {5 A4 O& D% t
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");0 L4 C$ ^- E7 y. l( D" u
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    / S  I9 c4 e5 r* v. E% K9 {' ~$ _
  52.                     break;
    3 H5 q3 m4 y& Z" }5 N
  53. & P# ?9 Q! K" @" c' ?$ p$ d1 K
  54.                 case '4':9 [& C$ T: n; j8 y  @- L
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    + }8 O) B& P2 K% ~, L
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");' e: I, w6 F. C2 g
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
      y6 i6 h: k5 q- ~# q
  58.                     break;
    * k, ?+ T8 P% F5 x# c0 z
  59. + f! P& k& T% K0 R& x7 s, e
  60.                 case '5':
    , s5 h, k% S3 l" g5 m. J
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    2 ^3 `* I! j1 c3 f" \% ?- h! f
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */+ g8 R8 Z% S; l; ~5 Y
  63.                     break;
    , n1 I! O2 l0 n% ]

  64. $ J/ X2 l& V. L" p" i' j
  65.                 case 'z':9 Y! G! o1 I  J! V. v$ ^
  66.                 case 'Z': /* 读取前1K */
    $ `0 v0 n' O% x7 z- g6 ?, o
  67.                     if (uiReadPageNo > 0)
    " R. f1 z) ?! b: _5 e: q6 @( G+ h
  68.                     {- W' r3 h; m6 P# X* R
  69.                         uiReadPageNo--;
    6 q% W; d3 O/ D; |2 _8 D
  70.                     }+ I1 M* z! R/ L+ r/ K( l
  71.                     else/ ^8 h7 W4 {& w- J
  72.                     {* L7 J* [- r/ @6 K! R+ z: m& q& J
  73.                         printf("已经是最前\r\n");
    5 d* ~( {  X, J7 R+ R: [. n
  74.                     }7 ^) ?" T4 ]/ i( y6 ]; m
  75.                     sfViewData(uiReadPageNo * 1024);& {, y1 k* q( i- G7 S, c
  76.                     break;
    0 [2 ~/ o" s# U

  77. / N$ X2 I/ O. e! f
  78.                 case 'x':) A" N! Q" [/ w+ V
  79.                 case 'X': /* 读取后1K */
    & H' C0 R" D6 r$ A# U" e0 t9 B5 T
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    0 l* }6 Y& B- P; ^5 T
  81.                     {+ _1 T; `# [$ I/ i! g+ h+ n
  82.                         uiReadPageNo++;
    , J2 q- p  {! _6 S( `* Q. T0 h$ T
  83.                     }
      I$ w7 H* P5 Z1 F* A- |
  84.                     else! E1 b- v& {8 L4 W: R# W2 L
  85.                     {
    : U( @3 G/ R# F' {# X
  86.                         printf("已经是最后\r\n");
    ; L6 C* O+ f& E+ O3 o) ~9 Q  Z
  87.                     }
    - a8 b% z' F; e9 o+ J& `
  88.                     sfViewData(uiReadPageNo * 1024);
    + N8 F) M, A7 K; c+ X
  89.                     break;& w/ S( d' k! _3 {

  90. 7 a% m" T7 T$ G
  91.                 default:
    3 ^( M* i# O0 F4 h2 n5 m% V
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */  s2 J; D/ [3 N+ q" ~2 n/ x
  93.                     break;
    : P8 S) E! V. Z1 A& U$ v! L( W! s

  94. * I/ P( }& \% ~' ^4 y
  95.             }' [/ t! H+ U4 c2 I( a9 S5 ^
  96.         }
    8 F+ b% x) C+ m
  97.     }( P; r: n. c$ h% l3 ^' J% g
  98. }
复制代码

6 n: x* ?$ G8 |73.10          实验例程说明(IAR)

( j3 F+ I# b: t配套例子:1 x5 ^2 ?) t" I% m
$ w# j& q. _4 e0 Z( `
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
5 }9 V. S6 N6 Q% N4 B. h- M7 x6 [5 z9 g5 T
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)0 E$ i9 u0 Z" a3 h4 r: \
9 H7 Y' z& w/ _/ ^5 n. N
V7-051_串行SPI Flash W25QXX读写例程(中断方式)/ a/ _) z; Y7 Y. M5 E' v7 q0 m

+ D1 T! t$ \7 ]) e: h. a' @实验目的:
! N$ @5 ?! n( W% Q* Z
5 r, G* v8 e1 j学习SPI Flash的读写实现,支持查询,中断和DMA方式。1 ^/ |0 r" ~% D& Y% d) T) P' `
' w; g) b0 {# Y+ q! L! I
实验操作:
5 T, A8 K7 }1 m: V# m. q( S5 U) a) L! e
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可; Z* p/ Q( ]6 M
printf("请选择操作命令:\r\n");
+ ]$ ?) {0 A. c. ?printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);) {6 h; y: y5 x  u( P
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);; {% }; y% u! k( q
printf("【3 - 擦除整个串行Flash】\r\n");
# h+ N( R3 u# `* E( T- rprintf("【4 - 写整个串行Flash, 全0x55】\r\n");4 t1 ], Q0 m  q. I5 g
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");& Y8 Z5 W& n2 ^* d
printf("【Z - 读取前1K,地址自动减少】\r\n");
, P6 b3 s8 S7 f6 Bprintf("【X - 读取后1K,地址自动增加】\r\n");4 L, Y# Y8 G, e
printf("其他任意键 - 显示命令提示\r\n");: q) |1 O$ n) y8 u* v/ m+ z
1 k1 C/ ?0 u# }7 n' o, w; F
上电后串口打印的信息:
. r  K. O7 F/ a% B* J; B) U4 O% K# v3 E
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
6 {& t9 l9 z8 z  a, b
# h9 R0 L, u3 Z$ i3 d: C) x5 U: Q3 v
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
5 w- j, }1 L8 v; J0 M/ n& A, W
5 M) d$ g6 N! u( k) M2 ]/ m( S
程序设计:
& V  F$ a* ?1 w2 Y, F" N$ c( w# p. A7 \
  系统栈大小分配:, r; [1 c" x: Q+ T0 F$ D

. q5 ^7 L( T$ v1 ?3 S: T
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 a! b. X: w9 r3 z# J6 Z
6 `. b! ^1 j/ c" C3 |
  RAM空间用的DTCM:+ ]! Z  k8 E% D3 {3 V0 U& f% t
% Q- F3 B6 w' Z) I2 _9 t
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
/ A1 g& @- B7 {5 l5 V; R1 e- O

% g* D7 d1 b7 o9 `6 [9 T; T' J! e  硬件外设初始化
+ z$ \4 ?- C  s2 Z( A
: e. I( {4 ~! [: S0 R硬件外设的初始化是在 bsp.c 文件实现:
5 e6 i9 S, x/ Q4 O
( V  n6 l7 g' x
  1. /*
    8 w$ ^* _4 |4 ~' N2 a
  2. *********************************************************************************************************
    ' L/ g4 j5 B9 R* Z, D; @
  3. *    函 数 名: bsp_Init
    : `! O  f: b5 [0 t" ?9 Q5 K
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    # a$ d9 W/ d& v
  5. *    形    参:无- y3 N! G: F7 a
  6. *    返 回 值: 无
    / c! k4 _: G- y, u& V) K% O& P
  7. *********************************************************************************************************
      Z: V% b6 H5 m
  8. */' x7 r% Q: F9 v) t, k8 S
  9. void bsp_Init(void)
    . Z9 k; X  q* p5 n$ I' B8 y6 F
  10. {. _6 e, S  h3 E8 X
  11.     /* 配置MPU */* o/ H' M& E+ ?( q6 e0 y
  12.     MPU_Config();
    & i( @5 N6 S: V! u* R

  13. 2 \) E5 J, C0 m- ~) f$ i
  14.     /* 使能L1 Cache */
    1 s0 G8 w8 A5 n- n( E& O
  15.     CPU_CACHE_Enable();
    0 o% Q& J" r! I! l3 V- B( v

  16.   R0 H5 `* ^4 |5 b* _
  17.     /* & j/ V$ u& T2 K, c6 d+ G% z
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    ; T# T1 h( ]0 j; t6 f7 z
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    . C: ?2 _* R; v# x$ l2 T+ [
  20.        - 设置NVIV优先级分组为4。8 s. f: J" o$ g" p$ S
  21.      */6 N. T4 P2 `" }0 R4 a& p
  22.     HAL_Init();
    ) y4 f0 y2 @+ n( L! b% K7 Z* W- y

  23. & @8 X% [6 l1 m8 d/ p
  24.     /*
    6 Q& F. V# C* T% L- {: \
  25.        配置系统时钟到400MHz
    8 T) ~& K+ Q0 p5 |/ j! V% k, G+ Z
  26.        - 切换使用HSE。
    1 C* |4 B; y# S* H' s; F& W+ s
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。/ Y1 ]- d' g. a" d
  28.     */! v. I! Y2 p0 I! r. W7 U
  29.     SystemClock_Config();
    % `2 c. a9 E/ u1 I0 u

  30. $ J0 J6 G' l, k/ l
  31.     /*
    + T4 P8 `1 _( P! V  c$ O$ L$ N
  32.        Event Recorder:
    . x; _4 k2 I$ a4 m( T5 E
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    / K  T4 `& b& I0 S  v
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    . q& L$ e1 }  x' ?# d# h7 F
  35.     */    $ ?8 K$ }$ I! J& u+ Q1 l
  36. #if Enable_EventRecorder == 1  
    + Q# `) b! Q) u0 p& ~- H+ |; Q+ \- `
  37.     /* 初始化EventRecorder并开启 */
    , E( z+ C, y7 t' A( w9 U
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    . b* l/ ]; G% s% s$ z1 m
  39.     EventRecorderStart();; y# F5 Q$ X  z4 u5 {
  40. #endif
    * A" v2 ?6 w* |; L& h

  41. 7 l( b# E+ t/ D
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       , N' x* A0 B* u0 c
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    - Z. {* p+ k# w) `% i
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */' @. H" j0 _, F5 j
  45.     bsp_InitLPUart();    /* 初始化串口 */
    ( h# v/ d5 o  T; f: n
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    7 Q4 b' K% c5 Z) q8 c
  47.     bsp_InitLed();        /* 初始化LED */    * e; `3 t. ^9 Y4 x0 D( }  g" e8 v& |
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */- W" A8 M+ S+ E5 ~

  49. ) t/ Z! |) a9 Z0 D
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */' s, z( Z3 `; u2 a* ^3 h' u' z
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        : }. E% g0 X) \4 E
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */- L% `2 @+ i& h: ]: y
  53. }
复制代码
* `8 e. Y+ U! s! }% z
  MPU配置和Cache配置:
5 B' o6 n$ x5 B/ y8 z3 i# l& Y2 Y# a
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。+ K9 M( W# t, J
5 L: k# A3 L$ k  h4 r2 F" [' G
  1. /*
    6 M& X. D# H0 S# a7 Q
  2. *********************************************************************************************************
    ) T2 b5 D8 r3 U( |
  3. *    函 数 名: MPU_Config7 W$ k; [3 k; h2 p
  4. *    功能说明: 配置MPU' j4 g7 x  i* s# K
  5. *    形    参: 无
    ' g( v, @8 P7 c# j/ K
  6. *    返 回 值: 无% ~5 Z- Z0 p/ f( p' L
  7. *********************************************************************************************************1 G9 [% x- C" z2 S" Z
  8. */
    8 w7 D4 b* R  C# Q3 b+ O$ M
  9. static void MPU_Config( void )2 m! Y4 G$ w% x! \7 P
  10. {: k2 n/ X% A1 Y, I
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    ) g' X2 U6 E% C

  12. . p! t# W6 }3 h( _  D8 z! t
  13.     /* 禁止 MPU */
    , `3 h' c/ T2 J; ^4 H7 |
  14.     HAL_MPU_Disable();& _1 }8 {% w) V7 p! M

  15. 5 e/ R8 l7 P# Y2 P
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    / `3 r5 F. H4 r9 |
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;* s" x$ T2 u* D1 u  B
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    + Z$ n# b& K  V. f
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;, V& B+ f- d* y8 K+ q0 G
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;$ G! D0 ?( K! |3 |) L* c
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ! F: g: C, L7 f( A) l4 w
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    # k! L% Q1 v: X( J+ j
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 l6 G* p& d6 @& A; Y9 Y( x
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;/ ~* b) z5 q. h: H2 H
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ' H) I1 }  p& i0 Y* g$ k* h! c3 u
  26.     MPU_InitStruct.SubRegionDisable = 0x00;6 R+ C# W! g* Y2 @7 ]: z
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ( w: m4 Q$ \. s! O" V
  28. 8 @% B2 |2 U! _+ p7 O
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);  _6 U$ c8 n; ]3 {- e
  30. 2 z  S+ r7 c3 `$ H% u
  31. $ Q4 ~, R2 W: Y6 D' k6 c7 R
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    4 ^0 y+ o+ k. L
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    3 f( e! `) E+ l$ S6 Q: J! O! R: l
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;8 O9 x' N! m( h7 f: T
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    3 E# V& r; [* x" d5 U0 T" O& _: W  p
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    - n) g/ F7 u- @/ s/ B" K' q
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    8 d' R+ {+ \! f$ z4 J' x
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    & @) J4 U  r6 ^
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;5 F" i6 q# J& W
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;" Q% h" h/ U/ ~! r6 a
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;  c& G$ v2 L: G5 I8 ^) w
  42.     MPU_InitStruct.SubRegionDisable = 0x00;  C' e. }2 J, _) {
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;/ S5 Q% U7 q) s
  44. ! n  Y' {% K0 _. l" d4 H6 U
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);1 p+ I1 r5 S' t! N4 {9 \) q

  46. $ V) |( s4 h+ b% g" g  ]
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */& Y0 m1 W& M4 }4 _* H- A
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    7 M5 @- P/ ?$ P# p" y! Z
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    0 ~6 d7 h2 A3 Q% \9 _: A" c. D
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;& `. r% F( h* l8 P7 |! I
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    , o/ `6 B. n0 K& |: I
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;  A7 F, c7 V$ L
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;0 B' {& }$ p6 g
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;( S4 G1 ?/ p8 ]. g* I( G
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    - a% N8 ~2 @8 D) C! ]" l6 r
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    * R, \( }, c; |  U
  57.     MPU_InitStruct.SubRegionDisable = 0x00;9 n* h9 H6 P8 C- \+ z: T; L
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;" u% U* Y, d6 x0 s3 O
  59. . }7 t7 u% v) \7 ~" R: ?9 n- W- V% Y
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    . |* r; n% A' V) b: D+ R

  61. 0 u  j' Y% s  R2 z
  62.     /*使能 MPU */
    ( i8 u7 V8 ], z0 F& |
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);, b/ _2 O2 O1 e$ Q6 {, \
  64. }
    / I7 h! ~( e% X, ~

  65. , w: Z( p4 J! V2 t% S9 ^& n1 c
  66. /*
    9 {$ ]' [4 P1 f9 i  x* X
  67. *********************************************************************************************************
    3 i  P# R6 z) r, k0 n9 |9 s
  68. *    函 数 名: CPU_CACHE_Enable
    2 \2 ?1 ?( E, s' L
  69. *    功能说明: 使能L1 Cache9 Z# L2 k$ C. M* Z1 z
  70. *    形    参: 无2 q4 @' {7 _7 G3 S( Q9 U+ O# [
  71. *    返 回 值: 无
    1 {7 m4 O- s! y; d& u1 O
  72. ********************************************************************************************************** R, c, T4 B8 _, r, J5 U
  73. */* g. G4 |. x/ g! F0 o
  74. static void CPU_CACHE_Enable(void)
    . n. P, c; O/ _5 o" I
  75. {2 [" l- @# z9 }* A
  76.     /* 使能 I-Cache */) F' D- I5 V; t4 H+ W7 B
  77.     SCB_EnableICache();
    3 R# j- e. \: S: ^9 T
  78. - }& P8 |; d# q
  79.     /* 使能 D-Cache */: K! _' v) C4 t8 r0 B, F
  80.     SCB_EnableDCache();, c2 `0 J* X$ y1 H% S) o
  81. }
    6 n( F" A3 I4 }% P( q- o
复制代码
( s( w* M/ A: o8 {* O4 {

! D) C3 P; u: Q& c6 ~" S0 `; a$ L  每10ms调用一次按键处理:: k0 i$ F& |' _/ E% G0 l/ t
* G) |9 x5 Y% u1 w# k' [: w
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
) o5 D9 t  `  m5 C: H( ?& e
& d3 ~+ ~- a6 x. Z2 P8 V
  1. /*
    ' K# Q1 H" J, W3 N$ D% x
  2. *********************************************************************************************************
    6 {! U! l+ C$ L4 K1 W! }4 P
  3. *    函 数 名: bsp_RunPer10ms
    8 Z, s0 P  d& a/ Z$ c
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    8 h) W, v, i! b
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。( X0 d( p* u, a5 j0 U
  6. *    形    参: 无
    ; g, L3 ~+ j7 Q3 G/ }) C  d1 [! g
  7. *    返 回 值: 无0 B, }% B8 h: r  g8 N. d
  8. *********************************************************************************************************
    % j' G: l: W& r% @6 R# O8 O+ p
  9. */6 s& L1 g7 C: ]- J' U
  10. void bsp_RunPer10ms(void)$ |1 q( D3 b1 l8 }* A
  11. {
    9 D" B- l1 `5 }( u. C9 j; O4 ^
  12.     bsp_KeyScan10ms();$ T3 i* @; n. K; v
  13. }
复制代码
% L4 B$ D% d5 }1 \; w. \, S( ?- A
  主功能:: n, X. n* `" X7 U9 L9 d

8 {) E, z- {2 _: v" Q, ^; q主程序实现如下操作:
9 ^: ?. ?( }0 G8 F9 F! @: V/ ?% s* i, S9 s
  启动一个自动重装软件定时器,每100ms翻转一次LED2。
4 g/ }) h& _( u  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
) P# ^9 k- H7 c5 q) O2 c  请选择操作命令:7 P1 G4 h& F4 j9 Y  L
  1 - 读串行Flash
* @& u. B& W8 T! N6 M  2 - 写串行Flash' \+ {  n% t+ H7 t' D- R
  3 - 擦除整个串行Flash
  r4 ]5 w+ ~; K: I. O, F9 L5 q  4 - 写整个串行Flash
/ t8 N+ J- [/ p. D5 k  5 - 读整个串行Flash$ M! ^9 Y. g$ b) T( y) }' N; O
  Z - 读取前1K
' {, T+ X/ o0 A% O9 ]; L2 M# z  X - 读取后1K0 g/ q/ h1 {2 F! A/ J5 H- X
  1. /*. c7 F8 ]- F' W: Z: ]
  2. *********************************************************************************************************
    4 M0 J# a% z! n0 A
  3. *    函 数 名: DemoSpiFlash& H/ q% J# K+ E. j
  4. *    功能说明: 串行EEPROM读写例程
    1 u: K+ N  A! i2 I
  5. *    形    参:无
    ; U% @3 i! T' Q
  6. *    返 回 值: 无; g/ s' N( S. R. k8 a$ L4 j% z1 K
  7. *********************************************************************************************************+ v4 q, }+ ~3 E8 U+ b- X) a1 a9 m
  8. */% m. k( u$ g, H3 Z7 i
  9. void DemoSpiFlash(void)
    & Z/ b: j, \# ~5 _
  10. {
    8 l# v6 J6 Y- s, G  i: `, f3 i/ z
  11.     uint8_t cmd;
    % U$ W$ y; A  T' ?' ?0 x6 w5 \
  12.     uint32_t uiReadPageNo = 0;
    : N' G0 k5 z+ C6 d- o

  13. 5 i/ r- d5 {: X, g2 z

  14. / w9 F: {' ?( i& Q, x8 `3 N- D
  15.     /* 检测串行Flash OK */6 \1 P1 Z+ h6 g" \6 o4 R, Q
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);/ j# h& h3 x- n* o' f: K( f
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    / w' a4 n$ u% g" n
  18. ! B6 J' I$ p9 y# W9 |: }: \6 ~3 K
  19.     sfDispMenu();        /* 打印命令提示 */* A* I( ^& A) A

  20. # n& W/ |6 {6 b" L
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */4 p& H% f/ z/ E& i* j4 T  V0 R

  22. / a& ?* H/ ~+ \+ x5 G1 T
  23.     while(1)( ?4 ], Q# c* S8 ^/ F' q# T' J
  24.     {
    5 b- D5 X- ^* Q1 P
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */8 }& m/ e6 u& E  v' ~
  26. , Z/ [8 T+ ]9 D; z
  27.         /* 判断定时器超时时间 */$ i8 q' p* ~  e9 l9 t
  28.         if (bsp_CheckTimer(0))    ' t- ]- a2 B& s! J1 h7 {
  29.         {. B% e2 F6 [. l2 m0 i4 T& Z4 j
  30.             /* 每隔100ms 进来一次 */  
    " I- z) Y" e& |0 d# X" ~
  31.             bsp_LedToggle(2);2 s* L0 x( Y) K" H$ G' g5 n* X( \2 F
  32.         }0 q0 s) Y; g. C* v
  33. & K+ a8 J# P3 G. m4 g" p: d
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */$ }- o: e6 ]/ X' i
  35.         {
    , x. ^9 x" F, W# s' I
  36.             switch (cmd)- Y. Z7 k" S( H/ f
  37.             {
    6 N/ V4 g/ M4 e3 g7 ~2 j
  38.                 case '1':
    3 B" a' o4 b1 J) Q
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    4 A( F) Q2 w+ C3 [' @2 A
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    ( [+ r1 m/ m; q+ z( @4 P6 L/ u
  41.                     break;
    ' x, [, H; t7 G; \" _
  42. 5 G  C5 @+ T! p4 X+ y& W% x
  43.                 case '2':
      _; O% G  x- h( ?* P! @9 @: a
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);& n1 {4 y; j' i6 z- n& C4 w- _- G
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */0 Z# s' ]3 t% I  b* p
  46.                     break;
    $ q. f; v. y# h

  47. # Q( t2 X6 e4 o' S, a
  48.                 case '3':" O3 B! _/ x: f  E. g. c- p
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");/ _: ^7 u% b' K! q7 r" w% [; K
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");7 r# q1 {) T3 B& Z" @
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    2 N  c  e) A' b$ x* ?) `
  52.                     break;: i( D6 i6 K2 ^' c
  53. % M7 `- x9 M/ @0 n2 q) Q" v
  54.                 case '4':0 A- _" ~- u7 n4 Y4 }
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");8 A  r' V* h/ \
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    0 F7 h9 ^3 O- o+ q/ L! g& m
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */: \: W  c; `9 `( `
  58.                     break;& N/ S' D  a. ^2 ?$ A# A0 |+ L

  59. ' W& {4 q6 @- c: f% n/ b; W2 _
  60.                 case '5':
    ; L0 y; t) R- b' ?: W
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));7 ]: }$ G# k% i$ _% m# J
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    6 _4 K, A2 A2 z, V. ^& F$ c
  63.                     break;* N  `: s6 U9 v
  64. / m7 Q7 I$ b1 m+ |. o; }! H
  65.                 case 'z':
    0 t. g: `  Q! S# W5 J
  66.                 case 'Z': /* 读取前1K */, V; i6 \4 }( Y
  67.                     if (uiReadPageNo > 0)% X) }- x3 }! S- Q5 l. ^- X
  68.                     {
    3 g3 k, T! o9 Z9 \7 a% v" }, O
  69.                         uiReadPageNo--;  C# q  @, L4 Z9 ?5 S
  70.                     }5 O) J1 r# O3 P8 I
  71.                     else( N8 l6 q. [4 S! k9 S' X
  72.                     {1 p# ^1 j( o8 X; A
  73.                         printf("已经是最前\r\n");. ]/ \6 o7 _! J3 A) G
  74.                     }/ n( R" z+ @( R) [8 s* l
  75.                     sfViewData(uiReadPageNo * 1024);$ O( G0 G, x+ b" Y8 g( T
  76.                     break;
    ; b- O4 {0 }/ ~3 m- I- e( `

  77. 6 G! q! j: q- r6 u1 B+ `
  78.                 case 'x':! A. w, Z; A" b/ T$ i( f
  79.                 case 'X': /* 读取后1K */
    8 @2 D3 b$ g& A$ U" ~
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    & Z. L3 X' x0 j  ?- }
  81.                     {
    1 z* T& I! `" s4 p3 z( n8 f- i5 r
  82.                         uiReadPageNo++;5 L' u2 `. K) Z6 i% Y
  83.                     }; L" c: B4 \' f5 W
  84.                     else
    1 y7 l$ D+ V  f0 C
  85.                     {. b0 d  t7 o2 \; j' B0 W
  86.                         printf("已经是最后\r\n");
    6 h* T' l& Y# w5 i( u; G
  87.                     }* f4 ?. F( z3 J3 `4 M8 w
  88.                     sfViewData(uiReadPageNo * 1024);
    - U# [6 `, a# q
  89.                     break;, f1 f# N3 C$ O6 N' L, ~4 x

  90. ) q7 B3 v! G& I! ~1 U
  91.                 default:0 _2 a0 [1 E3 S
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */% q1 |* ^) O" X6 O. w$ b9 ~* S
  93.                     break;9 @7 P& Y, Y3 n; F, Y4 F
  94. 1 U% L: Z0 h4 e, E; W
  95.             }$ c. Y  j- l1 V9 X5 j  {: u
  96.         }
    # n" C! @* H* s! X
  97.     }
    ; V& n) }6 a. a
  98. }
复制代码

# Q, M" S3 m( ^/ I9 f9 x73.11   总结
0 H3 u' l, ^+ ~$ r6 [0 f& u( L! Q本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。
" p/ e; _" P5 @+ z$ M; y! E
$ w2 D+ j" m8 m* c5 P* R" _1 e  O% S, N8 a3 Y
8 |' M$ s! t/ n% t( f: s
收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似分享

官网相关资源

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