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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示
$ m  R# G) h7 x/ |  e2 D6 s! z  W25Q64FV属于NOR型Flash存储芯片。2 o4 y+ N" j3 \/ q* P+ y: Q2 @
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
: y+ v( I* m" A& ~) |
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
1 |" I9 f9 B& Q
/ W- ~$ T; M% o/ ?/ U
  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。# v" e8 v. S. z( d: H: g1 v
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。
( L. S4 N4 _/ Y& L! \" m! c2 H  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。' ~" t) N8 j8 P, D1 Y8 ?, n
  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。7 i/ j: s5 ~; i0 C6 y) `4 x
  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。" j& j: p+ q& ~6 b7 m9 {1 h8 e

9 Z# ^8 d4 w$ k4 I73.2 W25QXX硬件设计3 E& `3 ]! {; w1 i8 X5 ^1 o/ p, Y
STM32H7驱动W25QXX的硬件设计如下:
: T6 O1 O' F8 H5 u1 d
% P  f/ n8 r: c) T" A
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

4 x( |5 j$ R; l. d# _0 e- F
& J1 e' D0 l" ?$ M关于这个原理图,要了解到以下几个知识:
# K- z$ j( B6 Q$ _% _
8 |" ~6 b6 d; k* b, R% D' U  V7开发板实际外接的芯片是W25Q64JV。* f$ g. \$ k7 c$ ~) y
  CS片选最好接上拉电阻,防止意外操作。
( C+ _% m% v/ y; E  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。
5 r& s) l9 W  o+ o: J  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
7 u, P2 r' |$ m, K. B  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。$ z. t( r# \3 X6 V' A9 q8 Y1 c
73.3 W25QXX关键知识点整理(重要)
/ i# n6 h& P1 M$ U) w7 d: y! n9 @" F驱动W25QXX前要先了解下这个芯片的相关信息。
* I* Y8 u8 t' S/ w) d/ V  g9 H8 u( P
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
* ~" p* [1 f& h% K+ j
9 A6 d0 ?- T7 X3 B, j% H
73.3.1 W25QXX基础信息
; P* B; _1 [, \! Q$ V
  W25Q64FV的容量是8MB(256Mbit)。
* s2 _1 [. e- i) y# u# r' c  O$ _  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。( {3 i; L7 |% C/ Q" [) y
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。3 l: U3 I* `3 _1 W

. p4 p% z4 X" {支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。
3 q5 I- n; B# L
7 M% F# Y* W* j5 ?0 [) R7 b(注:这里几线的意思是几个数据线)。
/ R$ B  s  \* y
7 l5 I" z- C* [( B# k: Y! \- C  W25Q64FV支持的最高时钟是133MHz。7 C4 f$ h$ |' R4 D
  每个扇区最少支持10万次擦写,可以保存20年数据。
- W* C4 l& `  o# w2 O- }. y  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
, W9 L1 d# D4 _% }1 {& h, [  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
. O- b/ y$ c6 C4 f# o5 n7 ~整体框图如下:# @; B7 E  A6 }0 T1 N

4 p2 u- k+ ]( H
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
7 j* p1 G9 @3 p, R& Y

1 C. t' g, ?( P4 n! ?% G6 m* EW25Q64FV:
6 K; ]; h) v  s; J) x4 ^
% @% |/ X- n2 v8 H& h: E' X  有128个Block,每个Block大小64KB。
# a! `; |$ G9 J7 T2 B  每个Block有16个Sector,每个Sector大小4KB。
" g+ x) U! b. F5 K! V  每个Sector有16个Page,每个Page大小是256字节。
4 O3 J0 |! g) {& E73.3.2 W25QXX命令, G5 O  i7 p; R" G- s
使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:0 l/ `5 V4 N* M; P4 H

* D' d6 m( o- m: N, ]. H
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 X3 ]8 ~) S" r0 r! H

7 z2 {4 S# y+ K( D' a+ {5 C当前主要用到如下几个命令:
+ P5 R4 Q* x# O' u6 w" h' @- K& n: m$ P& s6 L: y" g; T+ o5 ^: e2 M  s
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    ( ^/ A5 L* S* |. k5 l% G* ]
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
      }" u8 E* `" {) \% {. O) k4 t
  3. #define CMD_WREN      0x06    /* 写使能命令 */6 s7 Y' `) X6 O- U0 |% H$ ?9 R
  4. #define CMD_READ      0x03  /* 读数据区命令 */! X- ?$ m) k) I/ f
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */1 m6 u7 F# ?6 r
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */
    % |2 F5 j, z* }6 _1 f& T9 X# S/ K, I+ M0 R
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */
    ! n! n2 C6 Z$ R; ?5 W' _$ G9 H  o1 j2 _( `
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */
    & z9 R) L  Z6 F, `2 ?2 E9 e
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
) O; t+ u6 _  ^# Y! R/ ~
73.3.3 W25QXX页编程和页回卷
5 T8 V0 F9 R" g! z+ X) ISPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):" Z4 r; Z5 g) M- x' _1 H; n

6 R7 L6 A0 w5 y4 \
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        - p' Q% ~, x0 {
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    $ }8 S9 h& L( ^. F$ }* w+ b6 J
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    2 [5 W2 f6 l  `) P
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    6 I0 k5 i- W3 O: D2 M) Y

  5. # I. H) P. S( s7 i* Q4 e
  6.     for (i = 0; i < _usSize; i++)
    ; d: ~- L0 A& L! D
  7.     {, u% J  O2 j0 c" R. E9 a8 d
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    7 ?0 r$ T4 G5 [8 I; @/ d
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   2 L+ d7 c3 x! g" d/ U1 L
  10.     }
复制代码
8 y3 @9 i- j  u" T9 v& l
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。' o+ R. D- [% s9 C0 n
5 n+ k& b7 g" E' w! c+ [
比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
0 O7 l* z! y0 a/ S; ^: D
, z# h$ {+ B. Y3 Q
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
      B5 O4 L% D: R
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
- L6 J$ C; m* \6 J* ?6 d8 N
  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。) U' D% ~0 {0 C: e
  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);& t& ~" z" p7 ?) s! j" `/ J, {
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);
- q/ G  X  O/ T3 I1 O& z+ r4 m  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
- c9 S! _$ ^4 ^( \* q/ j下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
' \& A# ^3 C& x4 H
* L& G2 R& C/ i0 Q. G
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
) h7 P+ A" a8 `$ h- I  v
$ Z9 ]1 U: N$ s$ @

9 c- |) q( s6 A( [73.3.4 W25QXX扇区擦除( t: |& J* n( n
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。) ?! i/ d- k, a* _, R: N, Q) j
: u( p% e: I( v" f% ?
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。) S# V' G: G) V+ M& c7 ^

# k6 S% a( H; |* H73.3.5 W25QXX规格参数
% K9 m* \7 |: D# q1 j
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
. b$ m$ @7 R7 h$ N2 y9 g/ A# `# s8 l& P% V1 ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
0 `* I2 i3 |) D% w+ v1 [
" l7 E$ w7 z5 W2 b- K. M
  页编程时间:典型值0.4ms,最大值3ms。
4 |/ V/ k0 M; z" [$ X  z  扇区擦除时间(4KB):典型值45ms,最大值400ms。2 i4 v! l$ G% |' Y7 K2 ]- `- p! f4 H
  块擦除时间(32KB):典型值120ms,最大值1600ms。
  r! c6 Y, }8 Z# T# d$ x& W, ^% n% t  块擦除时间(64KB):典型值150ms,最大值2000ms。" [  A3 ?: {6 u6 k; n
  整个芯片擦除时间:典型值20s,最大值100s。
9 f# }* ]1 L: [8 C, L- {& K$ H6 X& f+ Y* {% {* l3 T

, Y2 N/ P5 @5 {8 j; U支持的速度参数如下:
) }9 b3 H  M/ D! R* f# G5 H
" v7 ~0 V* D# Q- [
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
6 @# O1 [- m: o1 i. W% A

8 M! I" ?  K4 }1 @8 \; o可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。3 B. v* k7 r& Z. [$ C% R* F' U" r( T3 K
+ D% w6 I6 e" W7 L. I3 C
73.4 W25QXX驱动设计3 O6 V% J8 u* K$ P8 \7 o
W25QXX的程序驱动框架设计如下:/ i  W  i3 R0 w/ V1 q3 z
% y3 O; m. q: i. |
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
& Z6 q- t; s/ R" T

% i" r/ n2 V4 D有了这个框图,程序设计就比较好理解了。- O) ?: x# N+ F) @! K
  @) D! }! V0 i/ k; f8 d
73.4.1 第1步:SPI总线配置
4 L- Q' W5 z$ \$ k5 |spi总线配置通过如下两个函数实现:
  F: b% B5 j# X& t5 A' Y' G, h" a5 v- w3 _1 s( ?' c
  1. /*. V4 t+ V, R2 `4 _
  2. *********************************************************************************************************2 z3 q( f9 I6 X
  3. *    函 数 名: bsp_InitSPIBus
    5 l. B- n6 t4 W( B
  4. *    功能说明: 配置SPI总线。% a& R- h+ ]( k, b
  5. *    形    参: 无
    / u; [; q7 _. [+ P) C
  6. *    返 回 值: 无. B2 T( M5 S( b$ P& U0 i" O& y* B
  7. *********************************************************************************************************
    9 f: W5 n7 r; X+ j$ o+ t4 G
  8. */
    $ S2 g- L( Y" x
  9. void bsp_InitSPIBus(void)
    % I0 \  P3 V; M$ G' k) v( O
  10. {   
    + a5 m( a( g3 A! A
  11.     g_spi_busy = 0;& l- w8 A0 k! U  V
  12.   F1 {# J. n" B- Z6 O& q0 C
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);; ]8 T" {! w0 x4 V. u5 u
  14. }
    $ k' h- ]& L2 X8 _+ w2 U5 }

  15. * D$ d* I  Q5 Q
  16. /*
    # M; g5 A0 p$ B; w( d, R, N
  17. *********************************************************************************************************
    " Z' _6 `# I9 X
  18. *    函 数 名: bsp_InitSPIParam
    ! F# M! p2 C2 j. Y, t- t0 U' |
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    ( n/ D0 g7 A2 s0 N
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:5 {; r4 B  K. S' V+ d
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频
    8 Q7 v7 R, w6 T+ N1 S' i8 y! l9 S
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频
    0 D1 H" e% F) d" L6 x0 ^
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频3 ^5 |$ t7 y, y+ v! W% N
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频! h8 \: Y/ y) ]" l$ ~" h
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频
    9 n7 O/ |, f3 N/ g3 g8 W
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频5 ?( U0 d2 Z( C! {, A* [! {
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频
    & f  Q$ U: M3 [
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频3 q2 J/ [& Z2 p+ j% d
  29. *                                                        6 s& {7 O, |# E  S5 N# x
  30. *             _CLKPhase           时钟相位,支持的参数如下:. I( w+ O+ c' e1 @0 I
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
    - c# p" Q6 S$ J! K0 d! c3 J" |
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    : v7 d. _! J" R9 {
  33. *                                 6 \. _8 u9 K2 \- A0 \
  34. *             _CLKPolarity        时钟极性,支持的参数如下:9 L  c! S7 Z! Y( ?1 _
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平" W4 [0 i/ m! e2 G& e
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平  ~& w) V) l( Q  K+ a& ]; n8 `& `
  37. *
    8 Z% e. }6 p3 x, p3 [9 S+ y. C
  38. *    返 回 值: 无/ E1 |' c* J" [; k" t
  39. *********************************************************************************************************. L0 y" H$ q$ z  B( Q- I$ u' q
  40. */$ C+ T. d1 u; P1 B
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
    6 S5 Q1 e0 D$ Q4 W4 G1 X
  42. {
    ! R& @3 s8 j4 q& H
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    + N2 U0 @; i" Y2 K) N' W
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    4 }. Q7 l- O: E1 v/ N3 K5 j
  45.     {        
    2 @/ U1 d- z9 D/ f
  46.         return;: w: F# l) P) m. c/ U6 D
  47.     }
    0 Z1 n5 Q2 q2 i7 ?8 Y2 n; T( Y  N& L

  48. / W" b) Q5 T: C  ?4 }
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;    ! j& R" `7 L+ X
  50.     s_CLKPhase = _CLKPhase;
    # I, ~; u3 H; [. G; k; P1 e
  51.     s_CLKPolarity = _CLKPolarity;
    8 h* r; I! |0 |+ a/ M6 s

  52. * F, \: a6 a: M$ ~9 ~  e

  53. : D( L6 S, O- n2 u* t' F
  54.     /* 设置SPI参数 */% w9 ~) [0 D" [& {3 p3 m
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */
    ! F8 ^8 ~( |; s  N
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    ) X5 Z& _  M4 M7 ~1 g, c1 K" T
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */0 N# G, V& _0 F/ @0 J/ c
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
    0 a. r) [, R8 O" g- \0 V
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */2 z: q$ K, e" e" e3 h
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */
    ( U# M' v$ ^* W, ?: D! O3 i  H' j
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */0 f# k) m' i. R6 L$ r+ Q
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */4 T. m& U8 s8 y4 w0 V: R
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    ; W3 U- Z& D  F) H# Z$ z6 G
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */9 N# @6 d' I4 e# T
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */8 a: g5 c2 J9 J6 C
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
    + v8 J" [9 x, A5 C- p
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    5 \) j6 }: f7 Y" m1 X1 \
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    3 \; S3 e5 R$ l2 P1 C
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  % A0 O) O1 i0 \
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */) J  T& S. _+ {/ z: F
  71. 2 _, i. L. I7 \& Z0 S- Q/ r
  72.     /* 复位配置 */
    ; @3 E3 c1 ^3 ?1 M) _5 K4 ]
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK); \* ^* h: ]" V: w; i" C0 ^; _
  74.     {
    ' y4 U0 w8 Y4 g$ s- f0 H: {$ A
  75.         Error_Handler(__FILE__, __LINE__);
    1 d- K; r2 b# S. T1 J" d. }
  76.     }    1 Y; m  p8 e9 {7 ^, \
  77. 9 _- p" U  i" z9 o
  78.     /* 初始化配置 */) p) j3 C. \4 E3 u8 J6 l: P1 E
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)
    ; W" t) e$ h4 G" U8 Y( ~# s
  80.     {% E. _3 e  A: {3 G* u/ i& p
  81.         Error_Handler(__FILE__, __LINE__);
    0 D$ L! {, m8 d9 r& H+ h
  82.     }    5 T( L  X$ \1 p1 q
  83. }
复制代码
- T8 a/ u* [; T% ^+ ~* E
关于这两个函数有以下两点要做个说明:" _3 E) X) a: D& A' W

8 o) g: C5 l" Z9 |9 q  _  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
. t! s3 j0 T# M+ S" w  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
" `( |/ X* c1 f5 h73.4.2 第2步:SPI总线的查询,中断和DMA方式设置
: V% p* O  g) R2 N1 |SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:2 ^4 q4 ]( N$ m; I; v: N
1 G6 \7 W2 L8 |6 F
  1. /*6 Q5 o$ w- h* ^/ Z$ I
  2. *********************************************************************************************************9 d) y( I) M2 v( T. M2 J( d7 q
  3. *                                 选择DMA,中断或者查询方式
    - q" E0 H  J* ~/ c
  4. *********************************************************************************************************
    6 J. y8 [. V* l( A. U
  5. */
    2 y7 @- \& h/ R7 M+ d0 E1 C
  6. //#define USE_SPI_DMA    /* DMA方式  */( g9 _5 h9 U" X& q: [
  7. //#define USE_SPI_INT    /* 中断方式 */
    2 I. O( r8 x; H$ Z7 k3 c5 ~' i
  8. #define USE_SPI_POLL   /* 查询方式 */. s/ @+ V# w. D7 q' S: F) C1 c1 Y$ I
  9. 2 X% i4 U! ]8 B, B* e; Y# c  {- `
  10. /* 查询模式 */" x3 g" X6 K; a' A/ G
  11. #if defined (USE_SPI_POLL)+ k; a' o* u" x3 m4 ~
  12. 7 @; z3 a, W( Z+ Y
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  ) I! K0 C6 Q+ U8 X
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    - I# P. f, a2 T) K, _/ p
  15. : h4 `) g/ F( [: c
  16. /* 中断模式 */
    4 ^) Q' k* z  E. |% O- D
  17. #elif defined (USE_SPI_INT)
    ! G( `/ K9 e4 O$ G

  18. 9 _% l1 F% h9 ]$ ^. _: g3 q
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   . I% Z! D% C0 R$ ]2 }* x. Z
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];. Z9 l5 b4 _) ]- |

  21. / G: G* P0 a% G7 M$ w
  22. /* DMA模式使用的SRAM4 */
    5 R7 Y. v6 o6 {' n$ @( Q% |
  23. #elif defined (USE_SPI_DMA)( Q- f3 A# f& ?% m) O
  24.     #if defined ( __CC_ARM )    /* IAR *******/9 P, e; o- X3 @. ]6 Z' i
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   ( R" r/ ^' M0 ~' t9 F0 a
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    ) V; _) [1 R/ h# P. r  v/ \
  27.     #elif defined (__ICCARM__)   /* MDK ********/9 F$ S& B; C4 [/ j) e+ V* }
  28.         #pragma location = ".RAM_D3"
    7 r% b4 m# K2 `# L0 n, L
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    , y* v; ], o1 z
  30.         #pragma location = ".RAM_D3"
    & ~2 l& z: ~9 ]5 z; Y
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    4 E: i( M7 e- O8 _$ {, J
  32.     #endif
    3 b' e. x( `  B- s0 I) L* C
  33. #endif  R, D1 i+ V. d  f) D1 ?

  34. " [4 \# T, o# n8 H' b* U
  35. /*
    * g6 f1 _) a* G! a: p5 e! y2 o
  36. *********************************************************************************************************6 H# j% }* p/ [- N5 a& F
  37. *    函 数 名: bsp_spiTransfer% C0 M% ?! x6 [& d: K) E
  38. *    功能说明: 启动数据传输
    3 ]9 e& ^" ], a3 w. J9 D& A( h
  39. *    形    参: 无7 L" E# e9 V, }* j
  40. *    返 回 值: 无
    . _2 f2 y& I, l" U' c4 I
  41. *********************************************************************************************************7 m6 L8 o" ~7 u6 |
  42. */
    ' a$ ?  B' J' U* T
  43. void bsp_spiTransfer(void)
      {, ^% L2 f8 L' |# t" c
  44. {8 U7 I7 e( |2 c8 G  Q# g
  45.     if (g_spiLen > SPI_BUFFER_SIZE)6 @* q# A7 q8 I8 M
  46.     {
    $ }: t( I0 T$ d$ d, h" i, e+ E" u$ ]
  47.         return;9 B6 d% G5 K' O0 z- X
  48.     }, R6 r: H4 Y6 K
  49. $ N) {- L. b( o1 `* a) G
  50.     /* DMA方式传输 */
    6 j, f. F2 T/ C4 a
  51. #ifdef USE_SPI_DMA$ q0 \% |. v3 S9 w2 z5 s
  52.     wTransferState = TRANSFER_WAIT;* K' Z; g" q+ A: a) s/ m( W6 _+ P

  53. 1 H% I$ P" w2 _. f) o
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
    0 m, k; v  B( O, [! P6 W  s
  55.     {/ Q% S3 f/ ^' {% k+ Z( w- e! C3 _0 Z( x
  56.         Error_Handler(__FILE__, __LINE__);$ q8 K4 }' H+ o, ?% n' k0 W
  57.     }  B- y0 A' L$ X8 T! w% w/ r3 y3 f
  58. 6 k) c) f) H8 C  u
  59.     while (wTransferState == TRANSFER_WAIT)" l% e+ K& @* C% i- ], \/ E+ H0 f
  60.     {
    ) M; h3 Y% y5 Z& ^& P" A- a  W
  61.         ;5 U% q7 h" r, C5 \
  62.     }  }( w. C  s( g! t( [
  63. #endif9 k$ ~6 a! @/ M
  64. ( s# R/ k5 y$ |, o: d4 V8 A$ \' @
  65.     /* 中断方式传输 */   
    ; o7 \  q1 X& M8 S, D7 L6 U2 N
  66. #ifdef USE_SPI_INT: c4 m& ?% Y" p2 E
  67.     wTransferState = TRANSFER_WAIT;
    5 W$ {+ V. R5 Q6 y

  68. $ _% c: R9 R+ r* i! U
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    # v" S0 i' D, o
  70.     {/ Y3 j! f4 s9 ^) }, H; L
  71.         Error_Handler(__FILE__, __LINE__);
    2 s4 b8 {% x+ u' s8 I# v; U* U
  72.     }
    . P% m2 a4 a6 c0 D

  73. - f6 f! J$ \# I) F) F5 i6 D
  74.     while (wTransferState == TRANSFER_WAIT)
    * v5 ~2 a" L5 n) S2 ^
  75.     {
    / C) F8 M7 k, ~  [$ V) D, Z
  76.         ;' x! r4 b, G( N" O% ~0 c& ~! u
  77.     }
    9 C1 \0 a; p, W" z
  78. #endif
    - d. X" m9 X6 p* T1 G% D

  79. 5 O$ [- U2 }; W4 ?; x
  80.     /* 查询方式传输 */   
    % y8 h. l1 ^8 F+ C8 j5 h& n9 b: _3 v
  81. #ifdef USE_SPI_POLL
    - u; h+ Q0 Y! L1 n" Y' ~
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    3 G  S4 B) D% E# g: R
  83.     {4 o0 b9 H& R0 g6 j/ S( W
  84.         Error_Handler(__FILE__, __LINE__);
    , R7 P7 A  x% t6 w' v
  85.     }   
    2 j/ N4 d* P  [$ [
  86. #endif8 Y! _* r- B& E% j7 `8 q
  87. }% ?# g' J! {, |) n# I9 w/ Z
复制代码
/ K5 H- r6 U4 M3 d5 X, y8 w
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
3 C8 V9 [# p0 R5 p6 I  n
  _) m5 L: N8 I2 f0 E% I  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。
) Z' a" V- x4 ?) a0 M. J  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。1 F: ?3 P& H& q" d! j$ `# U
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */
    # `3 y, P: Z: ]8 {7 V% h- {, }
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;1 s  L9 n0 E' ^$ t+ c! y
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ; Q) W" E4 S  B$ L% k1 J/ k, [
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
      t; E+ d: p. M, k! r0 o$ U9 m
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;. @/ I; a6 j9 V: c0 v, f
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;- X7 @' V1 e3 ^6 W
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;8 k# y3 Q0 ^( n5 h& p7 \% Q7 f
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ! S8 P: R  b" K( p( s; [& P: }9 \
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;+ B9 I% p) L' L/ P; B% N
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;$ P* p. q7 D: Q! M2 q
  11.     MPU_InitStruct.SubRegionDisable = 0x00;
    # j* d. F6 T" K3 a- x* A+ Z
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ) @% H3 n* Z0 y1 A  `7 \" @2 }2 T

  13. * h* a3 `4 O' x+ c% `) J; h
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
' R( n2 L) w. B/ t
73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

+ N/ [8 Y  m# u- n7 f首先回忆下STM32H7支持的4种时序配置。( [) Q% A' |9 V/ ]4 U' ?8 |
( B) m6 A7 Q0 L, w& z! A
  当CPOL = 1, CPHA = 1时' h1 a# A+ }3 N( g& s) E3 x

% @, o2 m- U+ u5 J% ESCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
, e& n5 y7 A/ I& l- e% z
" A% k/ m% M( R7 F& Q1 D0 Q% p  当CPOL = 0, CPHA = 1时
0 w4 G, }4 i) q7 I7 g
3 }2 N) o, N2 wSCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
  v! P" l& Z3 D% g& ~
/ l5 B$ s; _# v% c  当CPOL = 1, CPHA = 0时
# e# O) Q+ R7 R: D5 }% _' p$ V' ~7 o  ^$ h/ \) P3 ?" b! M( B
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。9 e2 T+ o8 N# U" V4 f* o" ^

5 P1 @6 }5 O1 m 当CPOL = 1, CPHA = 0时
" _7 z0 {" p' Z3 G! W- s7 w+ y! s  u1 ~4 i  q7 w7 V
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。! t0 A' `3 D; }
) Q2 v7 t  f. H4 R
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
0 l7 k# x% z/ N' n- e9 h

3 N/ d. Z+ c3 m' E, {有了H7支持的时序配置,再来看下W25Q的时序图:
4 B5 Z6 P3 ]1 W8 V/ g) M. }# A* }2 Q
; b$ n  j+ d$ s  e7 mMode0 : 空闲状态的sck是低电平。* O  z( ]( _/ ?
, o7 Z' V# q) j9 P7 Q7 h# j
Mode1 : 空闲状态的sck是高电平。
0 p) @6 h: f; ~- n0 S- L/ m) W
3 H0 q* l7 f5 H& G
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

( ]! o1 Y2 @; y6 V/ q
; Z. ^2 ^8 T) U: Q5 p$ J" K5 Y0 m首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:) O6 t8 u. t& E7 k! V
( A6 N1 J- W5 @7 S- r* y$ u
CHOL = 1,  CPHA = 1
: a# I' Z* h( m8 w: m! _2 @" I3 s6 G5 f/ [, M4 O* I
CHOL = 0,  CPHA = 0$ y) K2 g% ]+ G  d0 i" ?: e9 T* Y4 r
- S$ f( S+ |# _' E  V. i
对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。- u8 s3 a6 p2 o* C+ R
1 I0 ?3 E/ N$ F
73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制
  W( U; |1 h- x: q9 _单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:
, ?$ ?9 L) i2 O! O3 b
9 w& a. l3 T, ^; `
  1. /*% K2 l0 z- O7 D: U/ o& n
  2. *********************************************************************************************************
    9 E1 T3 z, X' Y: b! Z) r
  3. *    函 数 名: sf_SetCS7 G( U/ ?2 M$ n. w7 J2 g) F
  4. *    功能说明: 串行FALSH片选控制函数# t5 x+ ?; V# C
  5. *    形    参: 无/ G$ ^( ^9 f$ o
  6. *    返 回 值: 无) q3 T1 f& F4 g! x
  7. *********************************************************************************************************8 [$ G, n- F; k' S2 t3 `2 N
  8. */; F$ C4 O* X5 C, Q7 a" `2 o  k
  9. void sf_SetCS(uint8_t _Level)
    0 x4 m# f% z6 ~/ T. f' t
  10. {
    ( k1 V' Q1 a4 v; o, X0 n  Q9 v2 H5 F
  11.     if (_Level == 0)
    9 k0 x; R6 V$ E+ L
  12.     {
    . k% _4 l+ r5 N: R7 T, k8 f
  13.         bsp_SpiBusEnter();   
    : q+ d/ t' q  Q0 e
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        - s1 O; M4 e* b; H7 Y" h3 X$ O( ~3 F
  15.         SF_CS_0();
    ; g- I( _( W" a* Y- m% E6 t
  16.     }' t; ^  e6 g+ [- J% f4 G
  17.     else) ?0 H; R8 i/ S. Z2 |7 x
  18.     {        
    3 t7 D# Y1 l5 b. m1 S
  19.         SF_CS_1();    - o2 U7 i1 \: g1 T& @; j  b
  20.         bsp_SpiBusExit();        4 t2 x$ ^% \. g# [$ z
  21.     }1 P- S, e8 \% D" O) w
  22. }
      K9 g  J3 M% F8 V, F
复制代码
9 ]" A) y( C+ ]% w  [" [, o

' C9 w# V7 c' w9 f通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。" g" x, H$ `- |5 x2 L2 ]% T- J8 K

9 H2 L' E) M5 Z但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。6 {8 b' Y! O9 g. k

' g: V! m* e" n/ f! M9 O. \2 W/ i73.4.5 第5步:W25QXX的读取实现

) [2 k$ X: y- N5 x5 W3 N' }4 fW25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。
: [0 {0 A! l- D& N' c% R- d7 J
- F" O" f8 O0 E7 }
  1. /*
    " D9 P+ f# f# U6 g- }* |3 O
  2. *********************************************************************************************************; q$ Z, u; g: {# A& p  M+ R- T0 q# p4 u
  3. *    函 数 名: sf_ReadBuffer4 b& ?/ F/ P: q- S* Q
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    5 o7 H6 r4 ?* A- w. f% c1 Z# }
  5. *    形    参:      _pBuf : 数据源缓冲区;$ [7 A" x' K' c6 {
  6. *                _uiReadAddr :首地址$ ^- C6 r1 Z7 y0 J
  7. *                _usSize :数据个数, 不能超出芯片总容量
    + [  Z0 r6 I1 e1 ~
  8. *    返 回 值: 无8 s% T5 z% I" c. n
  9. *********************************************************************************************************
    : Q9 S! G& z; I; j. q
  10. */
    8 m: R0 C2 j2 F! x% J" Y
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)- G# G% z0 r1 y9 k* [5 Q
  12. {. X2 K% ~8 c) x5 Z) L; ~$ X
  13.     uint16_t rem;  x& f( Z( g- g1 ?$ J: N8 c; n- ~
  14.     uint16_t i;% m! Y7 a7 v5 e3 e9 u* S0 K

  15. $ X, u. V) M2 I( {9 K* }1 I0 M
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    2 I+ e3 u1 k" J' w0 e
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)0 i# f( L; A5 f; t
  18.     {! [5 D* a! ]- j4 X* F
  19.         return;
    % l/ X# [4 z' k, L
  20.     }6 Z4 f. L7 x) u0 w7 [
  21. , t% ~6 Y5 z3 R* i  W/ a
  22.     /* 擦除扇区操作 */  s" M# n% P6 k4 _3 F! z- s
  23.     sf_SetCS(0);                                    /* 使能片选 */
    & H9 d! i$ X9 a/ d! z
  24.     g_spiLen = 0;; K8 w% f$ \& R- Q# q
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */  d- g5 @, s$ t' T( |8 j: p* r3 `
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */3 Q0 [& l+ q3 V5 \1 P
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */- G2 M' M4 ]- x& K) M7 N$ F7 h
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */
    % k1 i( P8 h9 d
  29.     bsp_spiTransfer();
    & k: `' c$ R* N" k$ p  ?$ [; K

  30. * J+ O  o7 ], q/ X/ r( s  R, K
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
    ! ]4 f# u' @. k6 A- v4 P
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
    / D% c2 g( S( `3 d5 `$ z/ D
  33.     {
    2 ]4 O) I, J1 P, x, S0 @
  34.         g_spiLen = SPI_BUFFER_SIZE;& G$ Z/ {" ?$ f/ Q% |1 `0 l
  35.         bsp_spiTransfer();
    * V4 r! y4 j' k8 Z

  36. 8 ^1 J7 @5 V  j' d0 L5 E. t
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    4 I5 j* N* v1 c/ v
  38.         _pBuf += SPI_BUFFER_SIZE;
    ' l- j4 P$ u  L" {
  39.     }4 d4 q' H. i# v3 r

  40. 4 q5 ]. X, h; `3 Y
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */8 F- _' O3 n# e! l4 P3 P2 W
  42.     if (rem > 0)& Q$ b# L4 E1 p9 e+ H3 `
  43.     {$ F! A6 c) f/ S1 n& H/ |. U9 x. t  X
  44.         g_spiLen = rem;
    6 {8 l* U1 z3 }% O# |) @
  45.         bsp_spiTransfer();
    . Q( o4 l2 i- _
  46. 0 \3 u& ~6 E) R; k6 L
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    9 `. _* L0 P* H% E2 G( L  L! n
  48.     }# `1 M; j$ X1 A4 u) Q! Y- I

  49. 2 D7 a* |, q/ n. N& a6 v$ h
  50.     sf_SetCS(1);                                    /* 禁能片选 */0 p1 I: {. p3 u9 X) m. z
  51. }
复制代码

5 h8 K! F8 m% w' j# q8 D- u3 L, n
$ s$ d3 w; Y; F' w5 b( w1 c8 t这个函数对DMA传输做了特别处理,方便分包进行。
9 S  t0 Q, f$ c2 Y# _" F$ e5 {+ I6 \' V, \; t/ B" p
73.4.6 第6步:W25QXX的扇区擦除实现

8 Z- P  J; t* o0 T扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。) u# W+ O: ~, ]& g5 {/ o! d

) A5 d7 m' g0 X# t- C
  1. /** i" l! c, `5 X0 Y
  2. *********************************************************************************************************& t5 M1 p& k2 ~2 p, i
  3. *    函 数 名: sf_EraseSector
    0 }" l4 k( V( e3 u
  4. *    功能说明: 擦除指定的扇区# r0 o) N/ q6 G" s5 @# X
  5. *    形    参: _uiSectorAddr : 扇区地址8 J) W9 r8 ~# o$ p' A4 n
  6. *    返 回 值: 无
    / j/ r( i& d9 I
  7. *********************************************************************************************************
    . f& T" b. H; `& L& \8 i3 f
  8. */
    $ [& M' z7 U3 u% d7 I
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    ( N: V# V$ }- i: n7 T2 b
  10. {
    3 D" t5 L1 G; n, j
  11.     sf_WriteEnable();                            /* 发送写使能命令 */1 X$ X3 V/ g' ?& b9 K2 }: R3 i

  12. * B5 r& A9 ]" ^+ g: W1 y3 V) m
  13.     /* 擦除扇区操作 */& ?; Y( T& B( @8 O$ L# P: R0 r
  14.     sf_SetCS(0);                                /* 使能片选 */& R6 P' X# z8 C7 L/ T4 V
  15.     g_spiLen = 0;( j0 S* U0 z$ D5 s0 \8 ]1 q
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    $ c9 J! R8 O6 C" ~/ ~8 `
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    3 J7 _( _  J! q1 Z& X
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */$ W% G& d8 P9 m/ }) f
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    9 E8 s' R% j# V
  20.     bsp_spiTransfer();
    # L, K8 P/ ?  Q: d  _7 `. v
  21.     sf_SetCS(1);                                    /* 禁能片选 */5 v7 ]( {/ @0 t! \; Y
  22. ; y% q3 W# b7 h( A! z
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    ( p& f* J0 y" r- A9 ]! z/ c
  24. }
    ( n2 C3 P1 O0 A# b* F0 n: v
复制代码
9 W  G7 W3 b4 w# H+ G
7 Y5 h/ s: I9 o1 _
整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:! \+ p% x5 L, a
! n& e' z. S2 _1 R8 v& V
  1. /*4 T2 `$ {$ |# W# T. d1 Q) G
  2. *********************************************************************************************************  ^: H) U- ~" {' \9 P- H: u7 w/ M
  3. *    函 数 名: sf_EraseChip
    " R0 Y9 ?+ O  ~
  4. *    功能说明: 擦除整个芯片
    / ]; _# V, t9 k  \' [$ R
  5. *    形    参:  无
    0 s8 F6 `/ j1 q. _1 g
  6. *    返 回 值: 无. ~1 X+ [- E4 [; J
  7. *********************************************************************************************************0 q' ~1 e# s( w6 p+ ~4 i- F  F
  8. */
    6 K6 j. \4 b' _) u# {
  9. void sf_EraseChip(void)
    6 {4 q, ~7 h! t$ U  _; G
  10. {    / _" O% O2 v) Z3 i7 I
  11.     sf_WriteEnable();                                /* 发送写使能命令 */+ P7 M* @4 ~1 j, j  ^

  12. % U% Q/ F9 F; Q+ b: u, @! z! p$ ?
  13.     /* 擦除扇区操作 */
    1 g! E3 J5 k& U, @: C5 z
  14.     sf_SetCS(0);        /* 使能片选 */
    & r4 \8 D1 F6 J+ R& w9 _
  15.     g_spiLen = 0;
    0 o1 ]" \/ T% a
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */# R7 e% _  I4 n: @! }( a. v" I7 p
  17.     bsp_spiTransfer();
    2 k. X' d1 |& g! a
  18.     sf_SetCS(1);                        /* 禁能片选 */8 E$ G" b. t' L4 k5 B- _7 ^( `

  19. 0 I- i$ D' L: F8 n/ e
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    & ?. X$ Z8 o+ U) @" Q
  21. }% ^  S5 y' a# n0 Q4 ~4 K4 K
复制代码
8 n9 B, L/ j. c6 p6 r0 L  H9 a
73.4.7 第7步:W25QXX的编程实现
2 f9 |' J! l4 m! j! R  d1 y" Z5 RW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
" H9 q1 Z, O" W
! h0 C& A$ E6 ]( ^- G
  1. /*  F6 x1 Q6 {, B1 w& X- [
  2. *********************************************************************************************************
    ( ]0 \7 C" R+ D2 h
  3. *    函 数 名: sf_WriteBuffer
    ! s6 W! [7 k3 p: N: u" b
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。. j; ]; Y- ]3 u  M/ ~
  5. *    形    参:  _pBuf : 数据源缓冲区;& k3 G, d7 Q4 p
  6. *               _uiWrAddr :目标区域首地址
    9 k7 \9 R7 I5 D
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。4 ^% `4 v! _6 Q
  8. *    返 回 值: 1 : 成功, 0 : 失败* M3 l$ e) v: ]
  9. *********************************************************************************************************( f8 ?6 i0 t; V8 j
  10. */
    . s$ _' x7 n  ]' D7 S, u
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)0 V2 B+ ^* ~/ Y) X2 Y& O4 l1 W  h5 ~
  12. {
    ) V: l( U4 r! B  Q9 \% t% p6 O
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    4 }( H: A$ A! J6 @

  14. ) J0 `( |4 {' s8 k# x1 a
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;- m( n8 i% A- l' R3 V0 h" D+ h
  16.     count = g_tSF.SectorSize - Addr;
    - C1 X4 a9 B3 A
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    5 z& X. j: B0 p  I
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;1 J& n! g' d* K1 ^* q+ z
  19.   u1 t  Z6 D  I: M
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */
    . e8 B/ r3 z6 S
  21.     {! ]) ]; [  k' y
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    / |- |- P5 w. Y# s9 j/ f! [% b9 [4 M
  23.         {. I: f6 G% \' I& P" [' y2 K
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    : X" ~: g$ K4 |" u
  25.             {1 s- Z+ {& X. B  L4 M
  26.                 return 0;
    3 z' ?# p4 W7 G) `& x8 X1 A
  27.             }( a4 q) E( T7 ~( @
  28.         }
    ' n' x) l% n+ `! s1 U  Z
  29.         else     /* 数据长度大于等于扇区大小 */
    % {* w  d8 T9 f( ?- p) P
  30.         {
    * i2 k& ~: _. L# E+ \8 j- G( E+ O
  31.             while (NumOfPage--)
    ! m( m  @; |$ s; K, _
  32.             {  M5 ^: f" ~% V+ {
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)# r8 K4 ]5 y7 K# u+ z$ e( I/ ]
  34.                 {: [. E8 X2 Q8 R# A% V
  35.                     return 0;8 b8 x% g  b! `' W, u0 j
  36.                 }
    ) p* V( M9 M, e# ]4 j/ O' X3 Q
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;
    & B4 i2 q- {: R6 \2 m
  38.                 _pBuf += g_tSF.SectorSize;
    & T# M, \! G, c* z6 I2 E% K" t6 G4 }
  39.             }
    9 M* y, J2 Z+ ?+ A. Q+ O
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0); q' e! M& n! O
  41.             {; e6 z. Y  Y( l7 y  M
  42.                 return 0;
    4 d8 g. z- E. `) a1 B
  43.             }. J) X: G8 x7 z4 Z, G8 |
  44.         }
    ! Z' ]1 p: l$ l3 h. o- o% U& y
  45.     }- x$ r9 O2 G. J: z) k
  46.     else  /* 起始地址不是扇区首地址  */
    0 O' n! Z" Q9 g5 x
  47.     {
    9 m! A. |# q3 ]3 y! X
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */1 R  x- ]& C0 C+ @  H$ A
  49.         {
    . Z8 P  l' J) h4 s# H% x. m, a, j
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */
    - D' T9 s: ^* F7 I( F2 b; V5 P
  51.             {6 V( s$ Z9 g: h$ c; W* b
  52.                 temp = NumOfSingle - count;- N5 h2 ?; ?7 n7 D0 M
  53. . ?! C+ l1 o9 j8 ~" a8 k6 P
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0). s6 }  d$ N; L8 A  `
  55.                 {
    ) K4 ~4 Q3 |4 t
  56.                     return 0;/ C1 h5 }  S# H
  57.                 }
    . w/ J9 l, R0 v; p/ r

  58. 9 A# J. c5 E2 P
  59.                 _uiWriteAddr +=  count;1 u! E; N; E6 R4 r' X
  60.                 _pBuf += count;7 u- x; ~4 T. R. Q% H7 b; m# v- \
  61. : Q* o/ `4 V! b0 w' s4 |; o0 \
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0), O. ?9 A; r# @1 q) K/ a5 B' ?9 h
  63.                 {$ C* I6 A' n5 i( P% y. Q1 e/ N8 u1 g
  64.                     return 0;) A/ A* L: |. R% V8 Q! T  b
  65.                 }
    , @2 S/ Q1 C( a6 i4 R* f
  66.             }. d/ R- V8 e% M# h  P$ N! h
  67.             else! p( U% B. q6 T
  68.             {  S5 v4 g+ E: s/ x( @# X
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    0 a2 p& ]- P, E4 Z% d) D/ \  B% f
  70.                 {, q6 N) _9 ]( p* Z$ h
  71.                     return 0;
    & a: O! _# e0 R% M* V) r+ v
  72.                 }; S1 y' l4 F; t& P/ R% \6 n: ^+ m
  73.             }
    - @, {, M0 v8 i: ^7 `+ O
  74.         }
      ?: O1 m# ?5 B# n
  75.         else    /* 数据长度大于等于扇区大小 */$ `+ n7 _  a! |
  76.         {( I- w: p- [% W$ ^$ B3 \
  77.             _usWriteSize -= count;
    / S2 t# _) y2 f, {4 f8 G# P9 u
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;- W- S4 r/ {( O4 v7 a
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;0 g8 e( ^* T, H- Z2 o! p
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)4 N  \. W& u* S- A8 C0 w  y( J
  81.             {, P5 o. J3 [0 e; `6 x
  82.                 return 0;! V1 e% l2 M0 W9 l: V' V1 Q
  83.             }0 x) p& t+ d9 }. K7 ~$ \

  84. % T! J2 Q  C/ K9 _9 K3 a
  85.             _uiWriteAddr +=  count;) Z+ ~. r' f/ Q  i( y: H
  86.             _pBuf += count;6 J$ R2 J) r7 ~) J

  87. 0 T- ~) H; Y5 v7 ?( _/ ^
  88.             while (NumOfPage--)! N1 j/ L6 s% y. N5 A
  89.             {9 y+ R( L: f( V+ j: c3 \1 l
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)1 L- S; _& v6 I) W' ?
  91.                 {* y  O: K! F- C. h7 |1 s+ A1 |! [6 }
  92.                     return 0;3 u4 Y, ]$ C4 o0 P
  93.                 }
    ' z; b8 M# e+ @/ p/ q& O  J
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;6 Q5 w0 Z/ H0 ]3 s' L7 _/ E
  95.                 _pBuf += g_tSF.SectorSize;
      o$ G7 [% F' a& A0 @
  96.             }& S. Y( E' V  F- m; ?" V' Y. j
  97. 9 R+ D( \. W  G% S0 R$ i
  98.             if (NumOfSingle != 0)) Y1 m* Y8 D3 e2 j. p3 @" E' V
  99.             {
    - G* q! B& Y% `$ ?9 U& q4 v9 U
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    0 U# S& b& C' G7 O: e; x
  101.                 {
    : i3 ]0 R: ~2 w$ ~& f/ ~
  102.                     return 0;* t4 R, D8 q* ]" P8 u
  103.                 }
    4 Q; N0 t! Y  G( `9 f& e+ `
  104.             }
    * ^( Z3 b( ~) \( }0 ~/ |
  105.         }
    9 \$ T8 Y0 p/ m" o- w
  106.     }
    5 D2 E2 X& Z+ H$ B! a
  107.     return 1;    /* 成功 */
    1 L* ~% S* ?8 t" D# @4 o( Y
  108. }
复制代码

' F% y* \+ B# @2 t73.5 SPI总线板级支持包(bsp_spi_bus.c)! x  [# }  [/ u- }7 d
SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
0 G# C+ k' o+ V  w, K8 c- _5 `! }: h$ v$ s' j) E  ~
  bsp_InitSPIBus
; f8 ]) c8 h$ K' L5 O  t2 L  bsp_InitSPIParam& Z' y3 M/ W/ K2 @
  bsp_spiTransfer
+ W9 u2 h* b5 i1 r3 l3 u: r% D; w4 U73.5.1 函数bsp_InitSPIBus" T) C" D3 [1 g. c  S% ]8 p
函数原型:; h/ ^. m. |" j$ W
. C5 V& m+ R' Q8 w
void bsp_InitSPIBus(void)
$ `1 W5 i# F3 w. T2 G. r) m/ ~8 G9 K( p* ~
函数描述:
" l& _) F$ [8 i( s: g6 o; e7 D: f. l: U# F/ B4 e4 l2 f
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
2 ^- \. Q; w1 q5 r) m3 ~: a) T# p3 U) P8 `  e5 f
73.5.2 函数bsp_InitSPIParam# p* S( s7 s! O. l2 r. R1 J- c
函数原型:
( q4 ?5 S3 y( J: S+ K; [7 I5 M! I5 F$ `2 ]" a
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
5 {9 L/ U/ e3 O5 m" O$ J" h/ e$ d. T" c/ M. I
函数描述:2 {+ F4 ^+ R/ ?; r% l4 J5 b, U' A; @

2 M. ~" E) n4 c. r" j9 b6 E; w; E此函数用于SPI总线的配置。3 @  e% y. f* e" A
+ ]* Z4 u5 V/ |; T. \
函数参数:
+ Y3 r8 N& ]* H, G4 B* Y
( D, U5 L' C! y* Q6 \5 J' S  第1个参数SPI总线的分频设置,支持的参数如下:  g# e! F- {5 P3 R* E
SPI_BAUDRATEPRESCALER_2    2分频. @+ b: I$ C5 M1 n) G+ I# t1 m
  P' o9 c# M: Z9 m2 K) E
SPI_BAUDRATEPRESCALER_4    4分频  w, A, t3 ]5 _/ m

' T9 B4 F  w* v& `9 `SPI_BAUDRATEPRESCALER_8    8分频
" V8 \, @0 j) |7 ?8 I
* u( Z4 N& o- W0 z' HSPI_BAUDRATEPRESCALER_16   16分频: o" `: ?0 V( L: E. y6 A/ Y
1 u5 r5 C9 C% ]+ r' E
SPI_BAUDRATEPRESCALER_32   32分频- h5 M7 o6 h9 X: O/ \

0 ^8 F! ]# y% ~: x- G6 XSPI_BAUDRATEPRESCALER_64   64分频4 h* W9 Y( z4 R) d1 ]. `4 X

! b  ~* T* t' Z% t' @SPI_BAUDRATEPRESCALER_128  128分频
; F8 W( k" i4 h: G& e' c% C2 T: g2 w
SPI_BAUDRATEPRESCALER_256  256分频& t. O) T' ?% r

' R/ K- B# h/ D+ G: z  第2个参数用于时钟相位配置,支持的参数如下:
% v' H' r' J9 N' Y* t3 \% W; ESPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
# L7 r! i; @# r1 U( S$ q
, W/ q& r, b9 u  r% D3 CSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
8 R) Y. G- P9 \, _2 I
% ]6 u0 J% x' u; ]; A/ u  第3个参数是时钟极性配置,支持的参数如下:; V+ }* d- l0 Y0 k3 l- D' W9 j
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平- a6 b  W( M" g  N  r; D. z0 U& N

5 t& b6 G1 A' G5 T  NSPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平+ r: q' Y9 a1 O& G7 n: Z

& d: b& ~  p: _7 S- A4 w# l73.5.3 函数bsp_spiTransfer
4 t. b0 N! Y8 i8 ~' `
函数原型:1 Q2 ?6 C- H5 C/ {# {+ t4 j
# }+ g3 k/ b( Z/ C* Y# s' r4 V9 o
void bsp_spiTransfer(void)" J* `7 a+ o: o4 W: a# X4 U. u

/ I: j/ X5 p, ~$ d函数描述:9 f1 |) v* n/ f0 x! ^0 y$ N
. a+ k( D: C0 n0 x) B4 v* l  J
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。5 G+ {, t: h! m9 ]! _0 K
5 M) ^; P3 |) A" z  v0 |
73.6 W25QXX板级支持包(bsp_spi_flash.c)
* z+ d5 _- n" U( C( u8 o+ rW25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:
: l; Q# z4 |8 R! D1 Q0 R/ K: k- C. U2 N/ l+ a
  sf_ReadBuffer' O& U. ~& f& e$ B6 H% r
  sf_WriteBuffer4 p( ]/ w7 r' e$ k4 |2 a
  sf_EraseSector
' ]& |- D, F9 Q% D  sf_EraseChip
* P; X& {7 Z$ `7 q' |+ |  sf_EraseSector. B/ Q# k/ O, g2 `/ D' H7 o5 n
73.6.1 函数sf_ReadBuffer, M' b5 O3 K: W; ^) a
函数原型:# i# }% p' x# o7 H7 N1 T

# V) J: C& j9 F: ~- S" `/ v1 z1 r  {- svoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize). ?0 f. }+ D' u0 [* y- O+ v

) D- X7 c$ d, {1 G函数描述:; }; R# [" o) E8 L. k7 |  `# y
2 x% F  \1 P1 o: M' t. C# ^& l, J
此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。
# h, _$ Y% b: B7 Z& p# ?3 j; F' C; O2 _6 X
函数参数:
" m7 [  d& I1 |; c3 k6 {- e6 c% k8 Z2 c/ X
  第1个参数用于存储从SPI Flash读取的数据。
- v. g8 G5 B/ G9 |* b  第2个参数是读取地址,不可以超过芯片容量。
' _9 ]: ~; ^: r1 ^* z  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
  x% _& T& p7 ^& W6 ^3 P' I( b73.6.2 函数sf_WriteBuffer(自动执行擦除)
0 p4 M; x/ F6 O3 |; {! d  V5 J函数原型:/ T% X' B$ i* k2 \+ N/ e

6 f3 X3 R5 o3 _3 t! ]: `9 u% auint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
3 [6 J2 t6 z5 D1 m) ]7 p% ?" o8 u3 w6 N0 y# ]
函数描述:
1 B+ s! k( C) }; ?! W% ?  E: T4 C
- N0 E4 E' C% q此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。
: D/ v# R$ l/ Z9 [. ^6 J# }4 ]2 y/ Y3 x! Z* B8 @% [* H7 l9 q
函数参数:
. ]; V, N; L% R; p
  ]! M1 ?  R, K7 a" j& `  第1个参数是源数据缓冲区。( U$ }* N+ P8 c6 A, n1 |
  第2个参数是目标区域首地址。
+ b" n* l1 S! S# o) S( w. m  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。) r9 y. o; C+ g- _
  返回值,返回1表示成功,返回0表示失败。( R2 c) K4 v9 B$ a8 y( g2 U7 k- ~
73.6.3 函数sf_EraseSector! W0 ^1 ~9 x% z
函数原型:
+ u! T, K* h* B/ T  u9 c/ {( ^3 L* ^. A' m# H
void sf_EraseSector(uint32_t _uiSectorAddr)# \9 m. p; M( `* B5 @9 M* J

/ Y1 w; B0 y+ B函数描述:
* a! l5 g6 Y7 R9 F& O  j- w
: Q, q/ d# f9 i此函数主要用于扇区擦除,一个扇区大小是4KB。: C( _6 p4 A) ], O2 i1 M

! I5 z) L8 `3 V( \函数参数:# r2 l/ e- f, l- _" Z) c9 j

# O0 E& B2 ]8 Y; d7 H  s: ?1 H  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。, R( Z" \' X3 m; K  n% x
73.6.4 函数sf_EraseChip
. d  S3 |2 x1 L0 ]2 C函数原型:8 n# J+ E# h+ d
3 o0 B6 C# e  t3 V7 ]
void sf_EraseChip(void)2 @3 C) F; f3 w2 b$ R- V0 I$ Q- |

% v5 b  }7 H2 [* n函数描述:
* |. B, v* L+ p8 B
2 u' w0 d$ L$ B$ `, Q  J' e/ t% U* r此函数主要用于整个芯片擦除。
) B- {/ |2 o. l: |0 q9 C& V, ^6 |6 d$ G" Y" P' _% m% R
73.6.5 函数sf_PageWrite(不推荐)% O( u) R2 ~7 V& {
函数原型:
: r( f& C: {7 d! z/ t6 ~3 b) F$ ]3 ^) X. o
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)
8 s3 w1 t. k* N# h8 n3 a; L! n
5 P/ Q' \0 L0 `2 B& d2 M函数描述:% j6 v; p8 e( a% j$ @) F

! |: Y2 r2 J( d此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。: Z/ x8 r/ w! L

$ ^" t+ l; j: [' H0 b$ q7 ]函数参数:9 g. L8 j6 X; k
( O8 E6 b" {8 I
  第1个参数是数据源缓冲区。
  }" ~5 V+ a/ e* r, }5 e1 y, D  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。0 {9 |* U  O5 l; w2 k) w& @
  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。, A- p9 |1 p- x$ [+ h8 y9 ^/ U: A
73.7 W25QXX驱动移植和使用
4 h- [& |1 _, [5 K7 a3 v! b& HW25QXX移植步骤如下:
% v& C) O5 p! Z; k' B2 x% U) R# x* V. X1 Q, B( n
  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。- C& {9 w/ F1 ~: O2 Z+ l; r
  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义* J% T) o. S3 n3 P* o# t, K# O
  1. /*. \. G; ]8 ~' m5 s
  2. *********************************************************************************************************
    9 m3 L- d$ F3 m, Z! e4 y
  3. *                                时钟,引脚,DMA,中断等宏定义; \; {& w% @' G
  4. *********************************************************************************************************1 ^' O# W8 K- e* {! O
  5. */& @1 U5 k) U. B. g0 l3 ]4 p
  6. #define SPIx                            SPI1
    ( T9 l, [6 t3 t9 G* H
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()& G4 w/ R! y" A
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()& n8 l9 }0 y8 j4 m0 J

  9. . T/ k# ^# c1 k5 ^
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()1 |3 q; L7 O: U5 p7 w3 Y
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    ' ~% m* ]6 g- i" l9 I7 Q

  12. - L% i9 x( ~+ M! @6 @- t
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()- s& R. z' v* q# \5 v4 H& ?- _
  14. #define SPIx_SCK_GPIO                    GPIOB4 A$ ?) F; E* K& S+ U
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3
    $ `/ k" D# l3 g" b* F$ K. L
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1
      U6 L* }) [* B9 q* ^

  17. 8 Y4 S# b1 u: d( r( H" H
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()- T! _/ G0 U4 }- ?
  19. #define SPIx_MISO_GPIO                    GPIOB5 \; `9 a+ M% ?
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4& T! @/ N# {% Q" O
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1
    $ v  I" \, Q" l6 n# P

  22. 5 `, P. `- @- ?, p  A0 k
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()  E3 A7 e) e$ }, F  p7 V
  24. #define SPIx_MOSI_GPIO                    GPIOB8 A2 J& z0 ]8 B' x( E# [
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5- w8 [4 J, Y; Q3 ^# ?6 t
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI18 `5 _; B( n" y+ K0 o) G7 g1 @

  27. . r! K$ ^& `1 [+ K, P: c
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream36 T% f/ m% C1 U( r! n
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2* ~/ v! l3 [; d/ d6 ~* g6 |. U

  30. ) S7 K' a' I3 X; _9 C& c
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX
    - x' f( w) \( u3 N
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    * \5 h# I+ X/ R/ \2 ?! G( \/ D

  33. 5 p6 d2 t1 k1 Y4 W& G' c8 |( z) ^
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn# @/ q6 a7 q9 M
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn# C, L( {& K: ]6 J. y

  36. $ l5 B' i9 U3 H3 I
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    ( Q$ K& K- |$ M7 O, L
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler  S, O/ C$ ~! d' M% t3 u
  39. 5 q2 i4 w+ t2 U& k' [3 Q2 C7 ~7 |
  40. #define SPIx_IRQn                        SPI1_IRQn/ G2 W/ z0 t. S1 e
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler' |9 M7 ^3 W3 [! Z5 X
复制代码

4 S6 t: c6 g  U+ ~/ i; ~: P4 p5 a
9 {$ M, u; s% O9 Q  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
# c! N9 V& E& f2 l$ s1 q
  1. /* 定义串行Flash ID */
    * f  h, f/ ?% v4 }  |& K
  2. enum- k: Y+ P, i! V7 J4 x
  3. {; K2 s" A  }# f4 L% T; G: }& y
  4.     SST25VF016B_ID = 0xBF2541,
    0 f5 G  K( `' `/ Y1 o# f# `
  5.     MX25L1606E_ID  = 0xC22015,) Q, y( r; r% O4 v3 Z# F9 B
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    9 }1 J7 C2 g  Z
  7.     W25Q128_ID     = 0xEF4018& ?* v0 _6 Z+ z7 ?
  8. };
复制代码
% b% b2 |; V, E
  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
( z4 c4 k+ m4 V
  1. /*
    ; k: B2 X0 f' b+ f; N  Y
  2. *********************************************************************************************************' ?. m/ P! n* y6 h% f* J
  3. *    函 数 名: sf_ReadInfo
    ! T/ V9 {" ]% L; o! T& Y: }
  4. *    功能说明: 读取器件ID,并填充器件参数
    2 Z' H# N- H& F$ J0 ^" l
  5. *    形    参: 无
    9 a( Q2 X: ?* I
  6. *    返 回 值: 无) z- z4 J( r% q
  7. *********************************************************************************************************
    0 h% h4 P" J' q2 F2 B' D  a4 Y
  8. */9 M7 O; d1 a" @' {- k9 R
  9. void sf_ReadInfo(void). \; F" k1 u( r
  10. {
    . {5 }3 @0 |) |7 B5 x
  11.     /* 自动识别串行Flash型号 */$ M4 F2 Z, K0 }" C# F: r9 n  j* H
  12.     {- Z% I! \, s+ s- V
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
    " B1 z$ Y/ V# V

  14. ' @( a( O, X' v* I$ y) {1 V) g
  15.         switch (g_tSF.ChipID)
    8 w! j! X6 x3 a& X. a6 s
  16.         {
    5 v2 G; B8 N/ a: E1 V! c. N
  17.             case SST25VF016B_ID:
    1 E0 X& G5 |( h
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");" C8 c, ]( z. }. u  _( ?2 F3 x
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */. H+ y9 e. j0 C( w& n8 U6 ?
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    5 i7 [8 `# q- |9 H5 c  _; V9 U& @
  21.                 break;
    0 a4 I7 O, V3 ^/ \1 u- v( c' M

  22. 3 h6 z; o/ {% Q2 B$ ]7 ~
  23.             case MX25L1606E_ID:; C0 D- O: x6 M- T+ i, G
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");
    / i' l6 p% l6 D4 n( |0 p: F
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    0 p7 A. g- m) T& A: k6 d3 w5 v
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */8 d* ]# x. K! p5 ]0 _
  27.                 break;7 ~! n" i/ x$ i; |7 Z! ?6 x

  28. - f+ J! m5 S  w- j! M' a1 c% \
  29.             case W25Q64BV_ID:/ s/ h9 |' b* k, P* c
  30.                 strcpy(g_tSF.ChipName, "W25Q64");, F; o4 ~( F4 i) z; c3 O$ V2 Z8 K
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    ' U0 f/ T9 p+ w, u% @( V
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    6 J3 {; T* D3 t( v1 ?$ ?+ V
  33.                 break;
    * m' D1 q" C) ~7 E* H( t# u
  34. # _5 J1 b% X2 \! T' n
  35.             case W25Q128_ID:
    / N0 I% S4 K9 ^. O! g. N
  36.                 strcpy(g_tSF.ChipName, "W25Q128");
      Z# l0 ]# D" Y* e  d
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */% S2 r  s( P6 T0 v, e5 S3 N
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    * |/ V1 u/ i& q" q$ X
  39.                 break;            0 ^/ Y4 [3 X' m: V  v

  40. + m1 d& L) {( [0 U6 {1 z2 h
  41.             default:
    & h+ W* a1 @/ }
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");8 [2 n1 S5 i0 i& K9 N3 Z0 U8 }
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;% h' Q* H+ J6 }# G! D
  44.                 g_tSF.SectorSize = 4 * 1024;
    % ], n% r9 h6 v4 T) a
  45.                 break;$ Y4 Y# T0 }# s5 I
  46.         }* {# b( j, w: f
  47.     }) W6 p+ ^2 Q/ H
  48. }
复制代码

8 m5 e" `! y2 P1 L  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。6 l, P0 a+ ?* `! Q8 Q" Z" z
  1. /*
    ( A7 q' j% G9 I, e" f8 P" l. M
  2. *********************************************************************************************************
    * P- ]! f/ @2 i) v+ t# {' i$ h
  3. *    函 数 名: sf_SetCS
    . v4 A) _: ]- N
  4. *    功能说明: 串行FALSH片选控制函数( J( Z5 ], X) U, p: @4 c
  5. *    形    参: 无
    7 C( n% k0 Y( j
  6. *    返 回 值: 无
    & _" Y# @. v( h( |& E7 L
  7. *********************************************************************************************************( G( R- m1 L- L$ ]3 w+ b% l9 G
  8. *// o& Q$ a6 P  }* I
  9. void sf_SetCS(uint8_t _Level)! X$ K6 }# U8 k( }
  10. {' z: J: Q9 p  A! Q. o6 H0 n
  11.     if (_Level == 0)+ ~( M$ S- A" P, s  g% W
  12.     {& h( k$ T9 I9 U; c0 O
  13.         bsp_SpiBusEnter();    ; w5 A- E% O( q# r& U5 w
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        4 g2 o3 G! o. ~
  15.         SF_CS_0();
    6 B8 {1 A* S: j  C8 F: X1 I
  16.     }
    ; [, ~  j0 W5 R. Y, D+ a9 e; x- Q
  17.     else
    4 G- y& p) v' B8 e! s+ h
  18.     {        
    ; z0 G# i% Y+ ?  H' J& K$ v* L- I
  19.         SF_CS_1();   
    9 m9 T( V$ n+ F6 K
  20.         bsp_SpiBusExit();        % t- J$ W+ p4 i( F- n
  21.     }, V& d: o' O9 a/ Z! c4 A6 N# L
  22. }
复制代码
% [; z& w  @# c' A! b
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
0 a2 L. i3 s4 V1 N4 N) {
  1. /* 串行Flash的片选GPIO端口, PD13  */" K% I0 a+ I/ D  n! L
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()9 c+ e  u& K. g% p  y. |
  3. #define SF_CS_GPIO                    GPIOD
    * Z5 J8 R% G. a
  4. #define SF_CS_PIN                    GPIO_PIN_13
    % I' v  R: N- v5 i" K

  5. 2 _; D1 y& q+ B; @9 L9 ?* q5 i$ y2 f
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U)
    5 A6 P3 `4 `/ K0 a; q$ n
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码

% r2 g# Z; L# h  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:; s3 C+ u$ A. t- I/ b' ?3 B
  1. /* 配置SRAM4的MPU属性为Non-cacheable */' k  A4 ]6 z2 U( _
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;$ a2 f6 @- U& n) y6 p9 h- ~; Q. }
  3. MPU_InitStruct.BaseAddress      = 0x38000000;- p) w2 J* U- O
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;- _4 N* Q( I0 z: O* o
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;/ j2 B0 F  j/ Q- @7 w6 z
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
      [, C) s' O  f5 v" _: l; l
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;  k3 O4 a; D, |/ n
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;8 W* t: K+ e: y- h% S
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    9 ]% D$ n  a) e; z
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;) @' Q$ z  N/ _
  11. MPU_InitStruct.SubRegionDisable = 0x00;+ s- V- ?  o4 U0 a* ~0 Z3 q8 ^
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . Q/ V7 j, D4 p, I$ U
  13. 1 ~) S& `7 |6 D0 G. C' E! `
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
$ ?- U. G6 Y/ G, G( P, `2 e# }2 G
  第8步:初始化SPI。+ G' `( |- \0 W$ \
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */8 ~6 \* q! E0 S* ^( D7 x9 t0 `
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        ' G* I7 M9 K. p# |. |9 M
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码
" a- B8 Y: W0 ?9 S4 o
  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。
' p& d$ _# A, B! k8 l6 v  第10步:应用方法看本章节配套例子即可。
: b" U/ H/ s1 S. ~73.8 实验例程设计框架. X9 O4 q7 _# J% ?' x$ g3 l6 _
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:; Q" d. B  I* s8 X* ]

! ^- S$ |7 \, ~2 I
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
; Y) q0 P# I8 g
, ?: E* I5 v( b" |1 B) S  j+ o, l9 p. R
  第1阶段,上电启动阶段:
, D  W4 a+ V: w- Y6 l1 @8 i% z, H2 D5 V* A- |# @
这部分在第14章进行了详细说明。
6 `8 s" R3 \1 u9 f2 d  
0 Y5 R+ s/ Z4 g/ Z  J7 }5 m第2阶段,进入main函数:
. S& ?9 O2 l+ n) o4 D4 K  c& O+ V' X' p9 b
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
- p& A9 I. d% J第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。7 N+ e1 R2 s) a+ g& J! W

6 m2 O- z# g: a$ c- d73.9 实验例程说明(MDK)6 f& ]) F. J0 \& z" `3 T3 V' m! P
配套例子:
1 E7 m  H& ]( Z) \1 E
6 k4 ]5 `0 C8 j4 T9 E! Q/ p+ ZV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)2 M0 f* ]; E! l) u' {1 Z$ v* N

  D  @1 h/ M+ A5 VV7-050_串行SPI Flash W25QXX读写例程(DMA方式)
8 ?7 c2 W( H! M2 Y
0 {8 R, I; V" WV7-051_串行SPI Flash W25QXX读写例程(中断方式)5 l7 q. H+ e$ c6 j. B2 b

; ~* T: I2 _7 C实验目的:
. C! f) s  ~  T8 m0 q+ @) B! q5 E% r
学习SPI Flash的读写实现,支持查询,中断和DMA方式。
7 C1 r- m7 ~& E- |8 W9 o- N! L* _* G$ @" v) {' t
实验操作:5 S5 Q7 k5 ]  M
2 v0 Z+ o7 M4 D" K* H4 X2 N
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
4 M& \/ P$ G% ~6 y0 ]printf("请选择操作命令:\r\n");
( D3 y- G0 S) M1 g* Hprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
3 j4 L* I- m9 d* a- l# Oprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);% _1 U( j$ k" k2 r
printf("【3 - 擦除整个串行Flash】\r\n");: A$ @% `! t$ P
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
; j' n1 j' r0 a2 i  Vprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");
' J- u) y0 Q8 t6 U# {5 T" dprintf("【Z - 读取前1K,地址自动减少】\r\n");
, S$ [, n4 g- f) B! g6 _' jprintf("【X - 读取后1K,地址自动增加】\r\n");
$ t! I$ v9 F$ p" D0 V! o4 ^" kprintf("其他任意键 - 显示命令提示\r\n");
; K# ?0 O+ b! U+ W* b  N# k8 k上电后串口打印的信息:2 K% g- X( W* M" U% `0 m3 b. r

/ ?5 E2 h$ H- [5 h- a1 c3 Z1 L% F波特率 115200,数据位 8,奇偶校验位无,停止位 1。0 V5 c3 s6 @; i4 H2 f, _8 h
8 [! w8 h- O$ B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

- b5 O) Z: `& _+ u
+ _( r& W( Y. Q" z$ g. k8 E1 j# A程序设计:
5 k* D1 o% Q) ]7 k5 g
) O; i0 [, u( b& \" U  系统栈大小分配:: R* C- q% R4 Y# k( D

$ a9 C+ f1 e/ V3 p& y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
7 ]+ l4 I! b+ d0 Z  k% D7 G
+ s& [: k* u4 V% G6 g7 G
  RAM空间用的DTCM:
3 A% A- c& e* q2 Y% }
! q! Q  C0 J) A& A6 _
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 {, P, V3 Y$ B6 V& D( V
& j+ m4 b9 m9 d: t  硬件外设初始化9 z" i) f7 ~0 W3 t$ x! t9 L+ h

7 L' G2 o" j4 J! V7 u硬件外设的初始化是在 bsp.c 文件实现:
: @& h* o% }+ ^9 S" e0 Q0 C1 x; ]* E- s2 q1 a
  1. /*8 \5 o6 W' e, o9 j9 r2 K
  2. *********************************************************************************************************
    ; K0 E0 h* h: ]0 \3 ~/ I
  3. *    函 数 名: bsp_Init
    1 @2 S' r# ?' ?' J) X
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次" ^6 ]$ x) n9 A6 t: F: g$ o
  5. *    形    参:无
    & V) g, I+ G9 z0 ~& L- a0 a
  6. *    返 回 值: 无0 [9 V! B' v6 H' O/ J! j
  7. *********************************************************************************************************" L! I. e. a" m  R  X$ H9 k
  8. */
    6 Y' k2 i2 A/ N' ~8 Y9 W
  9. void bsp_Init(void)
    / A7 ]; m$ I3 R# G9 j
  10. {  c8 b' K" ~; D6 p) O4 [! q
  11.     /* 配置MPU */
    6 z0 t5 b" b  s7 ]; B
  12.     MPU_Config();
    " Y( l. O7 V# c1 j& Y

  13. 8 L+ |' v. U, g) o
  14.     /* 使能L1 Cache */# w6 |8 g: [+ D4 ~- G7 Y9 v  L
  15.     CPU_CACHE_Enable();
    0 f1 u" s+ K& _3 ?5 ?% u. ^4 P- F- r
  16. ' N7 S) \% k. f* p2 X5 f5 Q
  17.     /* 1 p! p0 W8 }# |9 F
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    " g  z2 e: @% t7 k7 w# g
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。. k2 l$ T- g5 l) _5 O/ t' Y. |  T* q
  20.        - 设置NVIV优先级分组为4。2 n( g; r! ]" y: k# r  Z- J
  21.      */  d0 n3 g, t: V, X+ F9 \/ C! ~$ l
  22.     HAL_Init();
    & X0 U7 W0 W8 u
  23. 5 @' I. ~) z: S$ v1 f7 L
  24.     /* - ~1 V' a$ l6 ~# m( K! M
  25.        配置系统时钟到400MHz8 Z- c' v$ w* c% D* ~0 o; p$ s
  26.        - 切换使用HSE。
    : P: V8 j3 V( @; ^) C$ n  H
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。; q3 s# m) E4 G8 E* B# t
  28.     */* V4 j' C- C! D9 [
  29.     SystemClock_Config();
    $ s% d  z/ q" o

  30. : Y! H) j3 w$ h1 S6 N
  31.     /* % j7 h/ h4 J9 t( g3 O- P% `8 b
  32.        Event Recorder:
    , I! G8 V$ v* h3 i
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    / Q" P9 n) \3 d$ F+ C% B
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章3 g9 j1 }' d2 V" T* r3 I$ D
  35.     */   
    * {( h- o! m: j& |# |( s
  36. #if Enable_EventRecorder == 1  
    3 T. S( `& t; X7 F7 Q7 H) q
  37.     /* 初始化EventRecorder并开启 */
    # T  O, _/ u: ?0 L1 |
  38.     EventRecorderInitialize(EventRecordAll, 1U);2 q$ o1 L3 d/ s* A* |' ]5 A
  39.     EventRecorderStart();
    $ X6 s3 d' r. K2 p1 m9 S: @
  40. #endif1 [$ v1 d& K' B9 Z8 b8 Z) Z& ~. R
  41. , x! `% }  U1 X4 y1 ]7 p/ n
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       % v$ j2 n# O' G4 d
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */7 _7 v* Y; D" f5 ~2 p0 }
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */
    $ F" [( t! Y( e
  45.     bsp_InitLPUart();     /* 初始化串口 */
    ' `, B% m7 C9 A& g3 ^0 G2 q
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    2 ~- L; j! N9 _% G
  47.     bsp_InitLed();         /* 初始化LED */    3 _8 F' u6 P, f# w7 W
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    3 f( u- E! t' f, Q5 B
  49. ) |5 e& s( T- U' J6 l3 v
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    9 g0 X9 O3 @6 {3 q2 ^
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        # ]% l7 ?: _8 N1 N( \& Y1 r+ R
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */" Y3 C9 y! ?- f3 I* C' g4 C
  53. }
复制代码

8 W8 R3 H. y0 J$ n. d  MPU配置和Cache配置:
# E; c( \# ?# b3 k' L# a
' K5 _/ t2 G0 ~数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4) ~% T* I+ j8 h# n& U/ N! o' `
' Y2 A) A; v1 _. N
  1. /*
    8 F! k" O0 B' u. D4 H4 J# u
  2. *********************************************************************************************************8 @7 y  r4 x, I
  3. *    函 数 名: MPU_Config
    $ [9 |6 T7 H% D' m* v0 v1 g
  4. *    功能说明: 配置MPU' A4 P& h4 j% f/ F0 E
  5. *    形    参: 无. e. q7 M: C* ^+ O4 A
  6. *    返 回 值: 无
    1 [" _, y" ]+ i- F- H" l' W
  7. *********************************************************************************************************
    3 o# p8 s$ A# Q6 l2 R4 o, o
  8. */9 S% P6 B  s: |1 E" [
  9. static void MPU_Config( void )
    & f) `$ b) b2 l, m$ a- b% y1 u
  10. {
    9 X. C9 K2 b. x! S1 J
  11.     MPU_Region_InitTypeDef MPU_InitStruct;4 f" R- K2 V: H( x+ ^7 o+ n, L

  12. % j3 Y6 ~' k5 a  Q" f6 q/ h. {
  13.     /* 禁止 MPU */
    ( N5 m6 S8 T6 S- `4 E8 I* K& Z1 T
  14.     HAL_MPU_Disable();
    + H+ S* X- h; s# ^
  15. ' T# V6 p" Q3 w% x3 s
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */# }  X9 ?+ y/ s4 Q
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;2 m0 b' o+ F) ~
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    7 E/ ~  H! n1 ^" u2 T) b6 W/ _
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;. C; _8 }9 l4 h( Y# L
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;7 D, ~, ?+ F; V0 ?3 t
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    : o% S. A6 x6 t# A' u- y* N
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    * N% O$ [5 O9 w$ J1 [: u7 @: Z
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;# h5 p3 o" p, v2 r! @/ c# Q
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    # X  i8 j& `9 ~% P
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    1 v* I; V. g6 s
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    $ ]7 b7 }  l. S% @: T- C  m, v
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    4 i% B+ D) t8 U/ g

  28. / `" R1 \! k$ e9 w8 @, D
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);; {, j$ @: b% @3 |" c; a" x5 j- X2 r" q

  30. , g8 A- X4 e5 p
  31. 7 K/ G& k2 G) N( H5 h& w6 w
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */( H( d/ A$ k- Y& a; W0 \+ V& U2 d
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    # }& ]9 t. Y6 \& u9 ^( C
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    / A" q: D( ]; @  ?
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    ( p9 N0 [& k9 z! Q7 P
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    1 @" k. r. ~! K) K! F; _; [
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ! i4 c8 J- ~. Q8 W( i
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    1 h8 P+ c- U; T3 Y4 a8 I; w' @
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    : U( E1 r2 D0 J1 s  d
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;7 H0 p. c3 `2 J$ c, C/ X  f
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;) X" t: \9 e8 R( r8 j8 a" a
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    2 W; a4 z7 i2 i1 N6 h
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;2 C4 y4 h& l1 I/ J+ w( p

  44. & a7 L' |# [. f' [$ u/ C
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ! K! e  @2 T6 p$ X3 D+ r
  46. 6 d. ]) Z  c5 q5 J5 w
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    3 a/ c6 z4 [2 I1 v! ]3 B
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;5 M3 I9 n" l! l8 p" Z
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ( J- r" t  R5 Y
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;* F# W; H& i- P( m, j/ k& G
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    0 }/ |- b* L- a1 y% Z! K
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;( [' E1 J% \" _" F
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;7 v9 g* f  f6 p" ~& c1 |; y
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;, d8 k! T1 z6 O5 g* L! V% h  C1 @; j
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;& ~. ?+ v# ?- [. B# K  l2 m
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;% }: l, ]5 P& b0 ?
  57.     MPU_InitStruct.SubRegionDisable = 0x00;( I4 N, V' }5 e# O0 Z& n
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;4 g! ~% Z: _2 U6 r7 R. r; v' q
  59. , u8 T! j5 c6 n: J
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);$ r' V5 D; w2 x% h! E/ U  E2 A

  61. 3 U! T, e1 _$ |3 ?1 n* e) E! s
  62.     /*使能 MPU */
    , _: o! R. C' k/ K2 J" A( x2 Y
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    . X9 ~" Z0 P  n* ^
  64. }
    7 c. J, I& j) o: p" y- A$ `3 I4 G9 E
  65. # _1 q% }8 u# }- E! N5 C
  66. /*) B) j' l/ y8 X% J! z+ d4 W8 p
  67. *********************************************************************************************************5 n/ G0 [- D: ^/ \4 p, G
  68. *    函 数 名: CPU_CACHE_Enable
    ; Q- `( T# P* k% O2 \( l% G
  69. *    功能说明: 使能L1 Cache
    ' i$ M* f8 {4 m& S# d# f4 a2 M1 Y
  70. *    形    参: 无. D( X8 C  R9 W
  71. *    返 回 值: 无- u1 a" i3 F& B* ]) s  V# i& R
  72. *********************************************************************************************************
    ; u! t0 f' Z# T2 S( R' c
  73. */: }9 S1 c) x, S# r
  74. static void CPU_CACHE_Enable(void)/ q0 l: z% Z- A& S3 L
  75. {. _. z' E& k9 N/ u# B
  76.     /* 使能 I-Cache */; b4 x" K- b( c5 c1 X& k! f; _
  77.     SCB_EnableICache();
      N! }/ j/ x6 P! U8 g

  78. 4 M, Z% Z9 d4 [
  79.     /* 使能 D-Cache */
    ) N& d  S' A0 m0 L9 o
  80.     SCB_EnableDCache();" M7 R* {4 ?: b4 _  ?* W4 ~! D! s
  81. }
复制代码
2 M" F( ?7 ~3 k7 @" n) N3 R: c
  每10ms调用一次按键处理:8 R# `9 D+ I/ s( U- k4 F( Z

/ @/ Q" \5 {/ o4 a) u按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。9 }# P: f! a: O4 r

) r4 t3 I. j/ W8 w
  1. /*
    ! g6 H- A  e9 N; }$ c
  2. *********************************************************************************************************
    ' x4 m$ ~; c) K# o% F3 X: k3 E  F
  3. *    函 数 名: bsp_RunPer10ms
    * G, Y6 v2 E; Y( r! x; I6 g
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求- w$ F: `0 j5 b# r, g5 k
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。! J7 f4 r4 j3 m* G* x
  6. *    形    参: 无
    6 ~9 v1 O6 E8 q% U
  7. *    返 回 值: 无
    - q. I! h) l( J* |' r9 @
  8. *********************************************************************************************************! e; X- T% U% m- O: G2 b- \+ L0 {2 {
  9. */% h) [' n6 `: a" {$ m! P
  10. void bsp_RunPer10ms(void)  c4 B& A( n- {
  11. {8 g- t; z! N6 H9 D$ u5 _. e/ {
  12.     bsp_KeyScan10ms();. ?3 q( f4 {- N( ]7 M0 J; j! r; O5 W
  13. }
    1 t( y- ]$ J: M" y; B

  14. ( n3 [  F  E- F! T5 G& A& y. ?" ]7 ?
复制代码
; v8 J3 C6 N/ |0 ^% n, D
  主功能:2 x5 |1 ~% |' G4 c0 O0 g; P
8 F( G2 y9 _! [4 O7 m5 R# G# m1 r
主程序实现如下操作:4 O1 m0 k3 L) S$ E

' k0 w% x" |; [* O/ p' p  N8 ]% B% q  启动一个自动重装软件定时器,每100ms翻转一次LED2。
+ ?" d7 p) C8 K1 K9 f  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可5 w+ g8 `5 X4 l7 L3 ~
  请选择操作命令:
+ H+ |! z7 ^1 [8 I! h5 B  1 - 读串行Flash: H5 w8 g3 V+ D% `) H& |5 S$ m
  2 - 写串行Flash
7 w+ V+ Y7 h! S  3 - 擦除整个串行Flash
- I9 k' ^" Q  Y8 s  [/ M  4 - 写整个串行Flash) [6 l& J$ n' ^+ a# c' a0 b
  5 - 读整个串行Flash( f2 ~0 E; `" V1 R; a. _+ z5 v
  Z - 读取前1K1 {9 o, W# a  s9 `
  X - 读取后1K
, ^9 I3 \+ J- g  a& q
  1. /*2 p) X& P! [& S
  2. *********************************************************************************************************7 H- _1 m* s+ g' A. t: F9 x8 V* S
  3. *    函 数 名: DemoSpiFlash
    ' }+ e  s  J# h1 _# f9 B7 N
  4. *    功能说明: 串行EEPROM读写例程
    1 \9 K$ x! \, S# R
  5. *    形    参:无
    ; u' h7 k1 I4 {# _# s2 L1 N6 U/ F1 O
  6. *    返 回 值: 无2 A" Y3 P6 D8 h1 B* P( ~9 n
  7. *********************************************************************************************************
    ' F2 q0 t! W% Q3 R4 [- q
  8. */+ ]) f9 ^0 L# j' Q' ~5 q: L! s" q4 t2 o
  9. void DemoSpiFlash(void)1 Y8 j: e* }, i* i# S8 b
  10. {+ c1 r" ^' I+ K2 [$ h/ g. ^# p
  11.     uint8_t cmd;
    : W7 b2 s4 |  _2 m
  12.     uint32_t uiReadPageNo = 0;
    " n1 p0 n# u1 ^1 T- d; V; q
  13. " ]8 g$ {/ [) \, F1 H) a

  14. , e. T# P1 g: \9 U/ E) \- B
  15.     /* 检测串行Flash OK */
    ) d1 ?0 C4 K# I0 M6 N5 b" T' @- O' Z% ~7 i
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);8 k- _5 g" Q- _: l
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    / _* ~- ~* G2 f* [, ]8 h) }  K
  18. 3 A1 }% ?( E$ N# P* D$ P" f
  19.     sfDispMenu();        /* 打印命令提示 */
    : k& o* O* T) G$ p( A

  20. 1 R! t' s& ^& u/ l
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */1 P- K& a4 `$ v2 ~  m3 x3 _) K

  22. . B# I; n" l8 y: a4 @4 M
  23.     while(1)4 H! l# \, Y2 i& e& Q$ x
  24.     {
    . Y) C. |+ S; \8 W- c! k
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    . \; ]0 q% ~6 _
  26. * R; j+ ]* p) o9 k9 D
  27.         /* 判断定时器超时时间 */
    % p' Z1 K% z0 g; z5 A2 q
  28.         if (bsp_CheckTimer(0))    8 l; G- z9 R) ?' B% d7 N7 p
  29.         {
    - a4 x9 `/ G! J8 N: P, Q& u
  30.             /* 每隔100ms 进来一次 */  : N  n6 z& M( O6 A
  31.             bsp_LedToggle(2);6 x' l4 ^# F; @6 ~7 A
  32.         }( M: q2 i, @! H5 I! E2 M+ J
  33. " s3 m# b* |; o! P  o! U  U5 i
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */6 s# m& l2 ~  }, v% x& ]9 E: K- i
  35.         {
    * |% r& f7 ]: E6 y5 g) }; f1 Y
  36.             switch (cmd)+ H- ^5 o5 O2 w, o/ I! ?. F" O% S
  37.             {
    + o* j! }/ K) d7 T+ b6 Y
  38.                 case '1':6 ~  r: A, V* Q8 p6 S5 E! z  q
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);4 V* S; V: I) f# w) f1 Y
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */6 g* n( s9 f0 f+ `
  41.                     break;$ i- e, [* x" p0 p4 t6 y

  42. : D+ @& K& {$ d
  43.                 case '2':
    - _+ P# f/ @$ k0 z
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    ; ?0 f7 C0 f- M  {6 W
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */1 f( V8 C0 J/ J, \* H- E$ c# t
  46.                     break;
    2 \' J% \( q) U3 a

  47. ; \; t" L# s% `6 @0 q) w
  48.                 case '3':
    3 S2 ?: r; m. r' L3 k' V/ D
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    . {6 @9 ~" u; v# n9 V9 Q+ _
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    " K  f; ?/ E& g  n" F
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    + l5 }! t- K5 {
  52.                     break;3 q0 z0 B% |/ S/ S. t# w
  53. & ^) x. ^/ E1 z
  54.                 case '4':
    ' O4 w) W  V& b
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");- d/ o( N- _. \/ x" t- [/ p
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");5 a3 F1 e3 L: a$ r4 P# d) P. @  Q
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF *// z1 l- n7 V/ Z; [2 I" u
  58.                     break;( C( z+ }) ~! n: @6 W0 G
  59. ) D  U" v( v( S" ^9 f3 b+ y
  60.                 case '5':; f( M! c: E8 h$ ~$ `3 S7 k7 J
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));, H2 g" S& g8 V. {( F5 G
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */- ~( I# q) _2 |
  63.                     break;; ^9 C" p5 X# i, ~5 A: C6 W( ^

  64. , g, s+ {: t1 W2 `
  65.                 case 'z':& Y0 e6 u4 u1 }- h' k3 W6 I
  66.                 case 'Z': /* 读取前1K */  {! n0 F( H$ h, Z& Q0 [3 g
  67.                     if (uiReadPageNo > 0)
    ) m: |7 |+ ]. O! Z* E. J
  68.                     {. j% R/ P) n* D5 {
  69.                         uiReadPageNo--;
    4 h' n/ O1 F! n
  70.                     }
    9 K/ X, i! c4 V( r
  71.                     else2 C) Q+ ?3 k& W$ u% l- a
  72.                     {" w. y, |: E" h  o5 m. u
  73.                         printf("已经是最前\r\n");
    # Q& O/ F3 V9 z- m. {
  74.                     }
    $ l) O' Y' D7 m
  75.                     sfViewData(uiReadPageNo * 1024);/ m+ \$ \& _) X4 g
  76.                     break;
    ) p" I# l& L. q6 N
  77. 9 ]& w: _4 x' L4 u) `
  78.                 case 'x':) W- `' k; j3 L+ M# B
  79.                 case 'X': /* 读取后1K */" I, N! l% I: n
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    9 e: v. B0 ~* d" J8 _, F+ |( m
  81.                     {
      C7 U7 B$ b4 h* }- r" C1 e
  82.                         uiReadPageNo++;
    4 l7 U0 l; g2 p0 x/ R, n
  83.                     }$ V  X: A! }3 T. H& ?
  84.                     else
    % Q# Q# V2 @( _2 B
  85.                     {
    1 U2 V- N  b; W5 z: p6 \7 G3 Y
  86.                         printf("已经是最后\r\n");
    / i# J+ k! E) M4 H4 k" T
  87.                     }' o3 ]' L' M, s+ |
  88.                     sfViewData(uiReadPageNo * 1024);1 V3 S, ~/ r& T6 a2 L# \
  89.                     break;5 y4 R& _. n+ n3 T6 l
  90. . l+ }/ }" D6 Q
  91.                 default:0 @9 O, d2 M  B) D4 N' H: `. W3 O
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */5 ~( D+ W3 N- O
  93.                     break;
    # t* U# X* h8 [

  94. ' ^1 x7 \& `  I& U2 t
  95.             }5 y% `4 |' @5 D8 a6 L
  96.         }
    & J9 s5 [$ _5 N* ?' Z/ g* t; y
  97.     }
    4 R" Q+ H. D7 A1 _+ f% y( }
  98. }
复制代码

' X" {7 _% V; c$ I6 |8 h7 r( n8 @73.10          实验例程说明(IAR)

# O* o" x0 t% |: X配套例子:; o- p  D8 u. }" [  v
8 p" N2 l% q5 F3 w
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1). ?& o. O3 A8 o% L( g. X! P

$ e( `) |# }2 t. ZV7-050_串行SPI Flash W25QXX读写例程(DMA方式)' K# l9 C6 O' W1 J: v+ T

. a: N8 U7 ]- Z8 ^  wV7-051_串行SPI Flash W25QXX读写例程(中断方式)4 |' X% Q4 Q/ W/ F& |, c+ `# Z
7 g6 p2 Z! S# v/ b! T8 k
实验目的:
8 E; _1 |9 e& {" E" e9 J
% Y# m) J, |9 n, z$ ]' K+ B- [学习SPI Flash的读写实现,支持查询,中断和DMA方式。& B; ~" \: ~7 t5 i0 {

* t( ^- Z" Q2 X) `4 Q实验操作:
- E' j9 z% j' K8 P9 K, u- E5 x
6 u* O/ `$ b! W2 h! f支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可4 k$ C. S" Y1 _
printf("请选择操作命令:\r\n");7 n4 r+ h! b: z# d5 p# q" E7 L
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);0 W, X6 \3 P$ o) D: X2 X. @; R
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);2 h( G# V4 d* e% x7 @5 a) x
printf("【3 - 擦除整个串行Flash】\r\n");) _/ [* T' ^  D. H/ `
printf("【4 - 写整个串行Flash, 全0x55】\r\n");* V# |, `& k8 C. t, Q7 i
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");$ |3 @' ~. y. {2 \+ F- `0 t
printf("【Z - 读取前1K,地址自动减少】\r\n");
: O* E" ?/ {) N' w+ ~! G2 n  kprintf("【X - 读取后1K,地址自动增加】\r\n");+ m/ ^8 d! o0 g# N% E& b9 c' k8 b
printf("其他任意键 - 显示命令提示\r\n");
; p  U8 Q$ Q0 I6 S8 m- ?' z& H8 c9 C2 ]- Z2 k% G: t8 F3 g$ v
上电后串口打印的信息:1 C' O( v0 L$ x, P* \& Q6 b5 A
* B& p# x* P& f( U* ]9 @, M% @
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
/ E% N4 }& K: y5 s- d0 C7 m5 @
% w4 v- k. _: [" k
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
! V* o0 o( f+ \8 v1 h
. i1 N; @! c7 l; d  Q
程序设计:
. `. P& p4 Z& w' _0 i  v, R- f, Q7 N! @7 S
  系统栈大小分配:
$ B: V% l; U- ]. U  Q& F
$ y1 s7 ]* `' D
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
9 E- A- _. e4 c0 d* s

- m  E9 l5 s* F  _& A+ Q2 t8 n  RAM空间用的DTCM:
* P5 E" {$ H: X! |+ }2 y3 w3 m# R% a# J7 z6 a# y5 c
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
" x" k4 S& w4 c
& c6 \1 G3 M# ~9 v" B
  硬件外设初始化
7 A% X+ j7 v1 d! j, z0 {5 |! H+ k, c
硬件外设的初始化是在 bsp.c 文件实现:
7 U; F+ J1 |5 h9 }& ^( k; v8 X9 g8 o7 @; v
  1. /*- P/ ~* Z* C. T' P
  2. *********************************************************************************************************
    7 _+ H# _! H7 ]: B" ^
  3. *    函 数 名: bsp_Init
    . q% q7 ~, X: P" O
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    , I3 o$ S2 N! ^) ]3 [7 Y
  5. *    形    参:无
    2 B- Q( L3 j9 [( h
  6. *    返 回 值: 无/ |0 O# V; z5 B
  7. *********************************************************************************************************
    ) F4 e4 [: M& K2 k7 U9 ]7 i
  8. */
    1 V" Z* P2 ]- V$ w
  9. void bsp_Init(void)
    7 P3 a% d4 b8 X: J2 @
  10. {
    8 P4 Q% l( h) @% Y8 ?8 @3 B
  11.     /* 配置MPU */& }3 k8 \* C# R; N+ K/ y7 E& v
  12.     MPU_Config();
    * n6 L4 b6 ]: r" |
  13. 3 y& U  U# G  l
  14.     /* 使能L1 Cache */
    2 |1 C6 A# y7 c. A9 N9 l
  15.     CPU_CACHE_Enable();' k3 U+ F3 H: n4 m& F
  16. 1 R. v/ X- Z& @) `% |6 n$ f
  17.     /*
    3 y/ r# z0 V. _1 _
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    2 D0 U! z/ {; V9 ?1 }* R
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    . g3 K, y+ N8 @+ P* \' N6 r7 w6 E
  20.        - 设置NVIV优先级分组为4。) f$ k( \5 s  w8 q! b7 _
  21.      */
      A; J1 w7 v- h2 ~, a0 u8 k9 ]
  22.     HAL_Init();; ?" G$ R% u8 z* v7 Q3 B( W" ^& V* f
  23. 2 {, A! _) Z) e( K
  24.     /*
    : `8 A* `6 a+ J/ j, y- `
  25.        配置系统时钟到400MHz
    * {2 |  |- o  K- f3 E2 e
  26.        - 切换使用HSE。
    % ?% E- M% D- Q- U
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。* s# o& ?( p' T  M6 ?# N
  28.     */8 A2 j  x" u4 M/ n8 b# P8 |
  29.     SystemClock_Config();
    , K* ?6 A/ B5 G9 V- F, n; f

  30. 6 b. K8 d& W0 `* K8 O4 M' _' N  `0 v) r
  31.     /* . N$ e- s4 |( q6 ?
  32.        Event Recorder:7 e9 w) R1 Y$ f; U
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    ) d8 N' E4 I7 O/ P8 E
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    6 A! K# e' }5 R
  35.     */   
    - Q' D* k' S& F' ]& O* M
  36. #if Enable_EventRecorder == 1  
    ) e" w9 i' _* I9 y2 W$ M
  37.     /* 初始化EventRecorder并开启 */, H  e  \, I& X3 k
  38.     EventRecorderInitialize(EventRecordAll, 1U);  t3 v& N' x7 b2 }
  39.     EventRecorderStart();7 E# _. d! Z! n4 Q+ B
  40. #endif
    + ]' Z% X# P" M, j
  41. + @- [6 W: F/ n# w; b0 I# H& u- ]
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       4 t2 L; R9 d9 u" n( G
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    6 v+ h6 v. [+ Z  r: g
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */5 T+ T& }/ T, K' a$ {( z
  45.     bsp_InitLPUart();    /* 初始化串口 */  O$ g/ w' U" H* T$ |+ Q
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    5 a( }7 @: g4 v/ ^( h1 k# U
  47.     bsp_InitLed();        /* 初始化LED */    0 a. k: D! ?2 o
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */# r; N2 t" X* ~7 D: a8 M3 Y
  49. : D! t, c6 D8 x! n7 j% d7 z
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    & z8 a: e% h, f+ T9 |6 _) p
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    , q- ^7 M7 O: y
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash *// M1 P7 F) G5 q
  53. }
复制代码

9 `8 Q6 ~8 O( r" I  O' B  MPU配置和Cache配置:
3 t0 N9 b! o! L8 w# G& A! ^2 T9 V* Q7 L' ?
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。, n, l! R, m7 c. u6 s( \9 N1 H

. Y6 D2 r7 J& X) p8 O/ e
  1. /*: i" f- X0 N/ j* f
  2. *********************************************************************************************************4 x) X9 K3 S( j& L% K$ k% h
  3. *    函 数 名: MPU_Config$ D; O/ s$ X$ r2 b
  4. *    功能说明: 配置MPU
    9 J* [5 Y. i* u1 w# t$ K
  5. *    形    参: 无. q8 b# {/ W" i+ }/ A7 {7 ~
  6. *    返 回 值: 无
    2 s/ s6 j  P( \) i# ^" |; }& A
  7. *********************************************************************************************************
    7 i. f( Q5 c  P+ j% L+ ~
  8. */9 ~9 @- h4 O2 S; j
  9. static void MPU_Config( void )
    7 E& K5 p, w% V8 m7 Z
  10. {
    " y' j' l2 N3 f& |* Z: K
  11.     MPU_Region_InitTypeDef MPU_InitStruct;0 a8 Q" ?1 n  a  r; K; ?. Y; x

  12. , M2 |6 A, b3 s- H! H5 j
  13.     /* 禁止 MPU */
    ; y9 L+ J3 y7 M) V" j: U
  14.     HAL_MPU_Disable();) B1 V* [* z* z
  15. + Y: W7 W& k, o1 A
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */( [' b/ b% p8 |8 X8 X) T4 F( t) B  s
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;8 g! r1 D  O5 R
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    # q: b$ y* M, q' e/ ?4 H& q. l9 F; P- H
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    ; N) C- S6 f9 l/ x6 ?7 f2 G4 c
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;2 q: t1 m& I, v. }; N/ K0 B* ~
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    6 A  I' H/ B" r: g1 [3 V
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;! ^$ `  ~7 I# f0 ^% }& _7 u
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;) n/ u) Y# m2 _# Y! ^; H
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    6 ~* `! E8 x) b& v
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;# a! l$ ~8 u, R7 |
  26.     MPU_InitStruct.SubRegionDisable = 0x00;' ?1 q' W3 ^0 Q: w
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    - }  d# Z' [1 d* [! L+ }

  28. 2 a, A$ A" |' [" T' _
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);& c) O9 Z0 ^: k
  30. 7 K6 w" G. ?$ o2 D' H3 d

  31. " A0 P2 I+ g7 R
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */! s, q; x0 B* \) s( y" \: B) v
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    / k: O" b* W% e% j! i7 T& m
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;! B4 J' n9 g4 n: x$ j* L  c
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    1 h4 H$ {" L# ]2 N: S9 R5 c
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    % P( Z% L5 E& G- l/ s
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ( @! D( W2 |  e: G, U  K
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    % c) p; k+ h- \( a/ g
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;! i* n4 Q; `! O- F
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;# {- H+ M; A* n" c; o7 M8 z# t5 b
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;$ @; {. J0 o% d0 q; U
  42.     MPU_InitStruct.SubRegionDisable = 0x00;  Y, E! c9 M& E
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;/ h4 Z0 d, i) V4 s' X% K4 [4 N
  44. 0 y& v- v# T; d" P9 w8 l
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);5 a. [+ [( I( E7 Y# J
  46. 1 a; e5 T6 G4 E0 B% V/ q
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */8 @' V: g- {1 C* d& ~! x0 E6 u! I
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;( T9 m# g" U% ~- r' {% F
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    * O8 [0 U  c7 |1 H6 [
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;/ o- A  P3 C% G" p, l
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;! ?8 r; V2 g8 c
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ( n9 U5 i; ?: W3 H; z. {9 c
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;0 ~1 |8 @3 F- B4 h
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    , a* X: J4 S# c% Q. ~' L
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    / Y! p0 K- P; G6 }- W0 L- t
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;8 s6 `, Y1 ^6 W
  57.     MPU_InitStruct.SubRegionDisable = 0x00;+ Q8 K8 |& M; a! w
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    4 ^. T! E: i  K- F+ U% H+ V( d

  59. - j' `( x. c' T  D/ R# U
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);1 d: g" m6 o) Q0 Y. ?
  61. , l6 d$ x8 F& h
  62.     /*使能 MPU */5 n3 A8 }& @& H$ r1 @: u
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);9 t7 v8 @$ }( R$ i' j
  64. }! i4 d, L; Z4 _
  65. 6 `1 e$ J7 o* ~+ w/ D6 K! O
  66. /*
    / K1 i6 \8 F8 h2 b* G' j1 Y
  67. *********************************************************************************************************
    6 I# m* B! r- l8 ]0 H
  68. *    函 数 名: CPU_CACHE_Enable
    % C- i1 K  Q' c5 X" L3 d
  69. *    功能说明: 使能L1 Cache
    6 B( i; a2 m* w* |6 w+ a% P
  70. *    形    参: 无
    3 y7 |) G4 p, f
  71. *    返 回 值: 无
    7 j2 U9 M5 g9 a+ g" N9 M
  72. *********************************************************************************************************
    + O$ W' I+ f0 f& P' V! U
  73. */
    ( P/ K8 x$ K. n) [+ n8 o% Q, ^3 ?; i
  74. static void CPU_CACHE_Enable(void)
    4 n+ n  x! _0 Q: S2 o" E
  75. {( ~. h, z( z) {( w
  76.     /* 使能 I-Cache */
    0 w2 R' b; j+ z# ^, z( u1 `6 d$ D
  77.     SCB_EnableICache();
    % U4 d. p; C* Q+ ?* h5 Z/ ^( Y

  78. ; l' e& T% \3 h% h, o7 [
  79.     /* 使能 D-Cache */
    - f3 j4 f* D3 u8 w3 {; n6 c; w
  80.     SCB_EnableDCache();3 B% K/ d& U% _, a! B3 O
  81. }  `: ^, V/ g# t: D4 `+ o
复制代码
3 d) S3 \% c9 v% w# @" U- |8 Y, B
1 m4 j: `- ~! d! P4 N; ~
  每10ms调用一次按键处理:
2 E* p/ c8 J& D4 N& X
0 Z. q0 p" e. D+ M6 ?# i按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
# m/ ~$ Q* j3 v* X' N/ J$ v0 G4 W' L
  1. /*9 x  R5 q/ |' s& ?
  2. *********************************************************************************************************
    - v4 f2 s' {2 O& [
  3. *    函 数 名: bsp_RunPer10ms
    . Z8 w% E& E- K* t1 w+ _! @+ a
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求. w7 f1 B( J0 L
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    " ^4 Y6 `2 u' x+ g
  6. *    形    参: 无
    3 i8 k/ g: y3 ~8 {7 P$ }8 {( O
  7. *    返 回 值: 无  H" a- B* \0 X8 F& a! X9 H5 r5 ~
  8. *********************************************************************************************************5 @: p/ t) ~; p
  9. */" f8 d# W2 R0 G6 P1 _2 O% j
  10. void bsp_RunPer10ms(void)
    2 z* J4 ]2 Z$ [% {; E
  11. {
    " N- Y4 T; J% y6 Z. k! d5 A
  12.     bsp_KeyScan10ms();
    " Q5 K- R0 i% l1 w" L4 H- J6 ^
  13. }
复制代码
! d! z/ Y0 G/ k; A$ ]! p
  主功能:/ A2 R: D2 H7 \, a/ d( S  B7 @
' F) i2 J% ^. y3 b9 f% [
主程序实现如下操作:
+ p# j6 p/ F8 w, _5 ^
3 V+ v0 w9 k4 ?  启动一个自动重装软件定时器,每100ms翻转一次LED2。" W$ P: I; [' I, x
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可8 D6 Z$ w, ^2 }6 M, s; l; ]$ ^
  请选择操作命令:
2 ^+ R% V" T+ _: `  1 - 读串行Flash" t( q5 s9 h# n' T7 _
  2 - 写串行Flash7 u  K; a# `1 k: B; C: }3 A
  3 - 擦除整个串行Flash6 P8 s# G* _  I# J8 ^% W
  4 - 写整个串行Flash% p0 f" ]6 ]8 G9 c+ N: u5 Q
  5 - 读整个串行Flash4 L# K; I2 H. O$ E6 U% l
  Z - 读取前1K# k3 `  y& g# S* n3 X( L
  X - 读取后1K
% @3 {$ a( L/ J) T6 {1 {
  1. /*1 i/ ]& K& t- W
  2. *********************************************************************************************************5 H5 |: L* @; @% B0 x1 b6 ?
  3. *    函 数 名: DemoSpiFlash: }* t" U0 F7 n% r) a
  4. *    功能说明: 串行EEPROM读写例程0 `. q" H) e7 E; D( W
  5. *    形    参:无
    , `2 W8 P% o% _! B' E; k( z" Z
  6. *    返 回 值: 无
    8 z% Q: J8 [  K) P* r6 O6 i
  7. ********************************************************************************************************** Z' S* p- B5 n+ z, q
  8. */
    3 D3 B* j2 }" a" K. R! q
  9. void DemoSpiFlash(void)7 K: @5 d* ^7 G5 _! J/ t
  10. {
    3 y1 Q2 Q1 W/ w& v, o  E
  11.     uint8_t cmd;9 ~: U7 \0 O: _4 `7 N0 Q
  12.     uint32_t uiReadPageNo = 0;0 ?  j+ K3 u+ v7 E
  13. # |" u8 a5 Q! F7 Y$ i

  14. & ~# V' g$ y% \6 }& Q
  15.     /* 检测串行Flash OK */
    " L4 d9 h! }! |% t+ T
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    : ?4 F7 K5 z4 c# U$ l. B4 Z
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);: a) E( D) ~3 K
  18. ) y9 r1 s' B$ ]3 @) L& L
  19.     sfDispMenu();        /* 打印命令提示 */
    & u7 C, f& M# J5 G$ M0 s( l. D. X
  20. 9 L/ V. r9 \1 Z+ @" N5 f3 E2 v
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    ( R- @  d6 \( f7 M
  22. 6 A* _3 u5 R) ^
  23.     while(1)5 O6 t  B, x1 @, z( N: [
  24.     {
    ! p, \1 _; h& C! |4 v
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */3 |" C0 {8 q1 u3 f

  26. ! O# K. O& V' G
  27.         /* 判断定时器超时时间 */
    * \, L1 O+ A" p0 R: s
  28.         if (bsp_CheckTimer(0))   
    3 \7 k; `5 f6 m9 j9 I5 b9 K) x" W
  29.         {
    ; O+ ?0 h0 _* u& @# o
  30.             /* 每隔100ms 进来一次 */  ; w2 n5 [# ^- q1 U! t
  31.             bsp_LedToggle(2);0 Y2 q1 a: u6 {) g* M4 P
  32.         }
    3 K/ s- @: w( Q7 k( w1 T) t; \
  33. - Z7 q3 S  d& E& r( b# H
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */( ^. |7 V' f2 H4 D7 \
  35.         {
      ^9 S' m  Q8 Z1 F& m
  36.             switch (cmd)
    2 p; F. y3 E* M: b' B/ Q. C' u
  37.             {0 B! g) ?  e$ X* ^
  38.                 case '1':
    1 R* h. O1 y# K: A4 T
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);  R0 A; @* J  U' w/ ^: L4 Y
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    " W6 D3 X; @. H, c- h, P
  41.                     break;+ ~. I) t  L0 L( t$ i+ n6 k1 T. d- R

  42. 9 F  V2 a" @# D1 J/ g/ o
  43.                 case '2':
    ( `+ n/ O3 U$ F; p  i  H
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);. P9 K7 z! P$ J3 M" C5 y3 V7 I
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    6 n* S8 S) W# p( t3 [
  46.                     break;
    $ h: D. E: s7 S
  47. , s6 X8 ?+ O* H/ |
  48.                 case '3':9 K  n7 z- a6 O
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    , x1 b$ g* Q/ G/ S; T. p. u
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");& F) z. G( }( s& h2 X' S- S
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    2 Y: e8 u; z# D& C% Z; q* T
  52.                     break;4 r5 \( V3 ?% D! n' q2 `8 C$ _9 R

  53. % u9 r9 R2 Q9 _! y2 U
  54.                 case '4':* w5 s3 ?# u9 E
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");/ M7 \! h' V; o% P
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    5 h* |7 a! s, s) c5 ^! F* h% |. q
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    3 C  j! b9 a/ h0 z/ f% U0 Q& z$ K
  58.                     break;
    6 y9 y- R' f" r7 p, x, Y: L8 c

  59. 8 S  M- N8 ]( Y6 U( c& h2 ~" g
  60.                 case '5':: U$ o7 M. A3 c
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));8 w0 e' ^' i  R& I% u" r# J
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    8 a! M  {) R. E1 Z) X3 |- r0 t
  63.                     break;
    : C9 U4 B- W5 D& H

  64. 0 @- i" [; m0 I! _. K4 \
  65.                 case 'z':
    0 i; i3 a6 T* l+ H. g
  66.                 case 'Z': /* 读取前1K */6 w8 b/ U! \( ?# ?- [. R
  67.                     if (uiReadPageNo > 0)$ h, f5 K- V' I! d7 h* u+ Y
  68.                     {! h4 C1 W7 g, q# b+ z/ h
  69.                         uiReadPageNo--;6 s1 _# c; Q) J5 v) u: M% r
  70.                     }' ~7 V0 X* S. @3 U' u3 A. Z% T6 c
  71.                     else
    8 u) g( m* z" h/ a0 H* ~
  72.                     {8 A9 y" i# l2 p  N
  73.                         printf("已经是最前\r\n");6 @+ u8 l7 u5 t4 |" i
  74.                     }
      Q2 I3 _8 @+ N* c6 R# L% n
  75.                     sfViewData(uiReadPageNo * 1024);
    ) b0 e" E5 D8 N$ [5 ^; c% l
  76.                     break;
    ( b/ s3 v6 ~% ~( U# ~3 Q

  77.   s3 a# m$ y" `$ E
  78.                 case 'x':
    # i8 u* b- u1 [
  79.                 case 'X': /* 读取后1K */
    9 ?( Z* G, ~& Z4 K& D4 j5 C4 P
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)' c( b! z, i5 Q/ F3 o4 {  P
  81.                     {% `6 v. |( f  G
  82.                         uiReadPageNo++;# \( x; s/ L1 b4 `9 M4 N* y% Z) F8 Z
  83.                     }
    " H& \( Z7 I; R' V6 F$ C
  84.                     else
    ) M( d" {: Z( y  F7 Y% H5 G% ^
  85.                     {' c% ?& v+ Z5 `
  86.                         printf("已经是最后\r\n");% _9 H  {5 b' A" w# E3 N# [% E6 A
  87.                     }% n. [5 X% |8 h+ ^2 ^
  88.                     sfViewData(uiReadPageNo * 1024);
    2 j3 F; h' h* {4 P6 J
  89.                     break;# z' a9 N9 a1 m1 o3 F; f# a0 I0 ]
  90. 2 [/ v! ]2 d. d- {
  91.                 default:
    & m& c& Y; J1 c# L7 a! F$ `
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */4 `( z5 j# R+ I% @  k$ n" |
  93.                     break;. T' i6 F& \3 ]" Y
  94. . l5 E+ R, o9 Z8 @( a; a
  95.             }7 z' \5 N6 [% p5 v/ u
  96.         }0 S# s$ M2 a% k) O# s
  97.     }
    & H; M; O0 @" e' Y
  98. }
复制代码
# A/ R0 r( ^/ o: v
73.11   总结) a% T1 v) w, |, M+ v
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。& H' V2 d8 h' d% L
3 |* K7 p6 M  L5 c3 w

# B5 B8 g5 ]& \" q4 W+ f( c! c' c: z* z  I, |% K
收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似分享

官网相关资源

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