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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示
# c; `4 P9 k  v/ ^" \  W25Q64FV属于NOR型Flash存储芯片。
! |* N/ b" H2 n$ f8 k3 C  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。' W- P* z0 W7 @4 B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

: P  B& R4 U: S+ C1 c/ K) Y% S; }3 c) `
  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。1 p! _2 r6 L# [! h* |
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。2 I* a' \$ o  q5 r% t
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。4 N$ M9 A) u! R$ u7 S
  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。4 Z7 V- Z: d' p1 ]9 m
  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。
7 U6 |6 e# e+ q8 s% g  K' B, h" E
0 {) _6 y; V! L7 K) W73.2 W25QXX硬件设计* y& k/ P5 k4 n- W3 \9 d' S0 ?
STM32H7驱动W25QXX的硬件设计如下:9 q0 x! ]4 a1 i

% [3 o! O  i8 N0 F. N
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
$ X$ F- U  s) ?# V8 K6 O0 ^
# P2 v+ T5 y# J' Z* F6 n
关于这个原理图,要了解到以下几个知识:
$ g- V+ _$ ^- ]) Q4 H! C+ w# p0 ^/ C0 z% g/ F
  V7开发板实际外接的芯片是W25Q64JV。
( R$ O! g- i% ~& B  CS片选最好接上拉电阻,防止意外操作。& S* Y: N) O& S& z6 o
  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。1 |, B/ `& y9 p) ?7 Y' l
  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
2 c  D+ f; l, g" o  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。/ f% x0 \2 l0 T% x# V; U
73.3 W25QXX关键知识点整理(重要)
4 ?9 Z7 b% I. L$ e驱动W25QXX前要先了解下这个芯片的相关信息。8 W  V; m" l! ]$ }0 Z; G4 R. D0 [9 l
- R8 `+ h, G! `1 s! H/ k
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
, L2 B7 V3 I$ _7 V" O

, O: ~- S3 l& C; X: I# }/ f73.3.1 W25QXX基础信息
: T0 z8 J3 l! a2 K" D$ A5 R9 N- C
  W25Q64FV的容量是8MB(256Mbit)。) v5 P5 |9 J8 ?5 e5 c# k
  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
" V$ ?" ^4 t4 A6 a) a0 ]  l! m支持两线SPI,用到引脚CLK、CS、IO0、IO1 。' u. n0 M3 r; {( F7 I% T

( L- l/ m. A, c8 l支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。! B3 e- D8 U! d* C. ]

( F, A0 \2 v9 g0 H5 E8 u(注:这里几线的意思是几个数据线)。
( R& t0 `7 o, l9 t
9 O% P" W" y1 A* d6 a  W25Q64FV支持的最高时钟是133MHz。7 x) X! Q2 m7 I6 {: ~2 s
  每个扇区最少支持10万次擦写,可以保存20年数据。
+ p  b- E* `; n  f1 x/ l* `1 h  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
" `( x+ L, j8 A: Y! @. d. `  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
- Z9 h9 c3 }8 Q- r6 S8 Z整体框图如下:
% X. s5 p5 T9 d$ W. n- m/ @
- S: i; e) C' ^! g& d, \
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

# U3 [3 m& y. |- E! C
4 V4 ]* b3 i' V, \7 t) t) nW25Q64FV:: Z0 H; r3 N7 E- Y9 b2 j4 L4 G

4 |+ j- W. K1 {6 ^* r8 ~  有128个Block,每个Block大小64KB。6 M4 p4 _6 z8 |& z% D( F* `) ^
  每个Block有16个Sector,每个Sector大小4KB。
' }' C8 T7 ^4 |, I6 P% ?; y5 ]  每个Sector有16个Page,每个Page大小是256字节。, [6 O7 m  D8 S( }- ^3 V
73.3.2 W25QXX命令
" C  ?$ h4 g9 ]; W  a( u2 E, Q使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:
, k# J$ ]3 A& G3 C
" x. s2 M1 d- U; H5 c0 _# u9 A
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

$ S* w/ F; D4 L. J0 @7 \
' K0 }& l6 ^3 h2 E" @3 l4 ]当前主要用到如下几个命令:
/ G) m6 o2 c) u  v) i/ x/ P8 ^2 X' R, j3 R$ Y, p  K: a
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */8 E0 P: _3 V6 y/ `
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
    * [5 ^: P  r) |% b
  3. #define CMD_WREN      0x06    /* 写使能命令 */
    ; I% k7 y! Y- `& }
  4. #define CMD_READ      0x03  /* 读数据区命令 */2 F/ R3 F2 o8 `2 J0 e8 a3 q
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */9 e) f, Z" ^- O' U6 g. @
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */2 d9 N# N* v: }
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */
    9 u3 s2 m4 ~+ X- W5 N9 y0 [
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */% c  E& }4 E1 p7 i- M
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
/ D0 ?7 b( J; }; ^9 |, C/ f
73.3.3 W25QXX页编程和页回卷
9 z7 Z0 \; I% W9 I1 v* E' f% j" r- xSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):8 C% C8 o) _+ [: V' J- J, K5 c

& i  p3 a/ V0 K) @  x
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    0 x6 ^( ?% E6 K' c
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   ) X6 e. ^% H8 }
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   5 Q0 @" W' |0 L+ ?
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    0 Y  K+ ?2 C- E9 z9 w: X+ J
  5. # u/ c' `5 W8 ?( a) y
  6.     for (i = 0; i < _usSize; i++)( ^. o8 w$ c% d( i" c/ T
  7.     {5 ]2 O. n$ ?# z2 Z
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,
    1 y; K& k1 B2 P3 e
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    * i; K. ]1 B$ }: @7 _8 q) s& K
  10.     }
复制代码

+ f# o, C: h0 H4 n3 S4 y. v页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。$ D8 Y/ k- _$ Z$ @. X

# C# H4 H& X0 ~比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
- A6 N. B# d  [- `$ P! M( G1 c' ]3 c; P3 W
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    5 v6 c- T6 Y5 A* {. i' w" E7 G
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

3 U8 z4 @( g% n, q$ b, k  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
# I; h& K3 w8 m0 u$ M9 s  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);  M7 R0 A6 T. F& i/ p$ F
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);/ Z1 W: z/ g, h( B
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了): \# L! {3 }- F$ K& b5 ]
下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。4 |0 W* P1 v- S9 j
# t. h' w+ G. ?5 n, q* Z  ~/ V
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

6 h7 a% F0 U+ P0 _4 \& ^
% \% }4 Q  K4 Y" i, T
3 C$ _2 S2 [6 l% D3 D73.3.4 W25QXX扇区擦除4 v3 b4 U! r' M$ [
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
) o$ a$ E& L; y! s' N( x
. C; d( L1 \7 N* V& D如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。' c' Z' ?5 q7 V: \- N( \$ o

' y# t+ N4 |$ f% g' V$ N73.3.5 W25QXX规格参数

: W( D4 P3 U$ X这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
$ s& c* ^" u/ o& e$ q6 `1 a
' [. w* y, r% v
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

2 f2 ]* }2 o% W" R2 V5 ]6 l5 u- G5 G5 c
  页编程时间:典型值0.4ms,最大值3ms。$ I% y6 d& K: g! ~
  扇区擦除时间(4KB):典型值45ms,最大值400ms。8 q) @4 J8 _7 u# l  s9 n
  块擦除时间(32KB):典型值120ms,最大值1600ms。
6 O# a) V! S, g" G  块擦除时间(64KB):典型值150ms,最大值2000ms。: m0 P6 J3 B3 J% k  J) {; f( Q
  整个芯片擦除时间:典型值20s,最大值100s。
  c5 ]2 d" A5 t# X- u- _' O% m) P- `( U; v8 k& h% y
  @# ]7 g1 S7 _: U) U& K( F" k# I
支持的速度参数如下:' j$ [7 |7 y/ M3 m  u3 I* {- a

  b& P) K" T9 r$ u/ n2 c
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

5 U) t  A- F3 l+ X+ R  m0 V4 ~# T2 b! p, P, E& m
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
4 |' l5 Q; R+ V5 |! Q2 T" P6 g- s' t* Z
73.4 W25QXX驱动设计+ T( `7 u& ^4 }4 T
W25QXX的程序驱动框架设计如下:& g$ H& H" ?* l: R9 T& h

0 {; n' m! _' \/ V
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

0 |, B' m& L" X$ v0 J' t
% }2 `" x. ?' W有了这个框图,程序设计就比较好理解了。4 X) M5 k* F( U' _& ^
" D( f/ w6 l* P% J3 y! G2 W0 B+ P# Y
73.4.1 第1步:SPI总线配置
  h4 H( a/ N# ?7 H! N9 n" Y3 Dspi总线配置通过如下两个函数实现:
' ?6 q. y3 A8 |) O! p8 K! r# e8 I3 ^
  1. /*' C( h1 M6 o( b' h6 ~& a
  2. *********************************************************************************************************
    ; Z- H) O, x0 x0 x
  3. *    函 数 名: bsp_InitSPIBus* ~+ j4 ~9 N$ z5 I% E
  4. *    功能说明: 配置SPI总线。9 N: d+ c) A/ a- {! _" Z  a
  5. *    形    参: 无  K9 H, D5 z5 x  o; n; s- \) t
  6. *    返 回 值: 无) F, U- I  Q6 L$ A9 e: S
  7. *********************************************************************************************************: e/ y* U& |6 v0 ]0 z  }: y. O: ]
  8. */9 a+ {% P; S7 G. S/ _
  9. void bsp_InitSPIBus(void)/ u; j% d2 |7 C9 ?# G- |1 h
  10. {    % ]* k0 _: t% S6 J6 @4 ]0 H8 C* f
  11.     g_spi_busy = 0;
    : ?/ R& Z& h* A) Z. _' Q
  12. 5 t) d7 \1 y7 Q* \3 n* x
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);% s4 w& }3 w! T( e2 d
  14. }2 V2 Q2 c3 k+ E; s# ^: A1 x
  15. 5 O4 [5 I* X( Q/ I5 _
  16. /*# W, K5 V2 ]8 z3 \% W) p, N) g, t5 j
  17. *********************************************************************************************************' f9 z* Y! B& w/ y6 \# P4 P
  18. *    函 数 名: bsp_InitSPIParam$ {2 P# w' F6 J# T% ?
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
    ( q" x& a1 ~' Q! L% z" F7 Y& i8 }
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
    3 s* h4 @) T. t& {5 V
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频" ?2 V; }! |9 M7 F
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频+ o& [" [" s! U, R  W9 b
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频
    + ?* P% f- q% B; X* t9 W
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频9 q" G5 b: D% q& b) K
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频
    7 ?5 s/ u* |9 b
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
    $ P( V0 x- ]# j, q/ q
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频6 B5 ?- M! v. _% `
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频) u7 w$ J+ j$ C# u- J6 p% ?. c
  29. *                                                        . E& N0 {, @7 f! g/ l
  30. *             _CLKPhase           时钟相位,支持的参数如下:1 m, E# n3 t1 n* W
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据+ D' V. w& h! o! P* t4 D
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据" W% E1 q3 ~" X3 C
  33. *                                 ; ]. D" k4 a  M
  34. *             _CLKPolarity        时钟极性,支持的参数如下:
    ; p1 h, m% {8 `4 ^% c) g9 ~
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    0 Q" p0 ?! z7 ]9 {! |# p
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平+ M( j4 R/ z1 Z! D
  37. *
    . {$ q2 l3 S6 ?6 z8 Z" g
  38. *    返 回 值: 无4 y4 ]6 r7 a7 ~/ Y: m( P/ k2 |+ Y
  39. *********************************************************************************************************
    ' M4 p  s+ y# p: E: Q
  40. */$ Q: p7 d5 ~- M& h& b3 j2 o( N/ o+ j, l
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity), D& @5 G, g( `" ?( L9 U7 m
  42. {
    ' \: G3 p& i; E( S0 J5 L2 q
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    1 L9 T' D; u6 R- Y! e
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    3 d% ]0 d0 E0 I: I% z  a) h1 P
  45.     {        2 ?+ I7 s; v7 j& V! L1 P. F- t' |& o
  46.         return;! V5 A/ J$ s9 a0 w& O7 q1 n. ^+ u. g
  47.     }
    : c- c9 ^  Z. ~, i
  48. 2 ^% w+ \0 J: J! j
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
    : P, x" o9 f8 `, m. o
  50.     s_CLKPhase = _CLKPhase;
    6 l8 c( J2 z! ]; W# |# |* B; B
  51.     s_CLKPolarity = _CLKPolarity;
    . E0 z. j+ T+ y! @1 d5 t
  52. ' a: k5 i  E: [3 n/ x* ~8 v3 c
  53. % h+ y, u% o9 G) x( J+ R9 N( e! v
  54.     /* 设置SPI参数 */
    1 K4 c2 J# {2 B+ e
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */
    " G3 f% i# U6 [3 B
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */: h- T5 \4 n9 L4 {8 a9 x$ T
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
    7 W$ W, U& Q" _( P. z! K9 l6 w) c
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
    ' `9 x+ c' o( h9 `
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    $ U  a* m# W8 Q1 f+ K0 v9 Q
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */$ _- i+ {: J5 z& v; L6 I
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */& ]$ x6 y& _+ e  M* H, z3 l! U
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */) {3 M* N  G4 |% g& V
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    ' J- |( W, L+ q! c4 u
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
    6 o- T  j/ V; x: H9 z8 D2 I/ V9 l
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */1 q9 q) G& S4 A& \" f9 F* W. R
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
    ) i1 |$ j( L) U4 S: [0 x2 P, A. t
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
      L- U; F7 P4 [$ ]7 _: G  y3 Y' G/ m+ D
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */- i' L# M' L9 g, S: m( {6 F, s
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
    - [+ z3 t7 e% I) n; _4 g  _7 Q
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    0 b/ q  Z. g3 c: ?4 a" U

  71. . r" ?/ s, S0 k3 ~9 y1 Z
  72.     /* 复位配置 */. F* [5 t& X- G! y0 i5 L
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    4 p6 g4 S/ \8 S* x% P
  74.     {
    : a4 M: K6 [6 X) W+ Z/ i  ~
  75.         Error_Handler(__FILE__, __LINE__);
    9 Z! T$ V( o; m
  76.     }    / L  ]- E) t2 X1 m9 p+ X, r
  77. 7 p3 _' X! q6 \# T; B" n3 \
  78.     /* 初始化配置 */& W- N8 j# k6 H% a& D3 I
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)
    & `! `8 g$ A8 Y; y& Y) n2 j, W
  80.     {" E" H( `, P! J& |
  81.         Error_Handler(__FILE__, __LINE__);
    0 Q5 H8 d4 h: z
  82.     }    * I9 y# \, i5 h. k  a
  83. }
复制代码

9 C% S7 n1 h6 F# C1 h关于这两个函数有以下两点要做个说明:
: N. ~) E% i4 i# q. }# K6 M8 y- I- ^$ z/ n+ b
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
7 E/ w8 ^$ E9 ~0 Q. D, |( U  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
, ^  K* a$ {1 ~9 u; r5 Z73.4.2 第2步:SPI总线的查询,中断和DMA方式设置$ S  A& ~2 l7 p+ S) o1 P& k
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
9 p# ~$ v' ?) b) S1 R/ `0 Z3 T4 v8 @% S
  1. /*
    ! T6 X- F* g! ^. r' @+ @
  2. *********************************************************************************************************0 x1 j( q6 c, m9 v# B( e
  3. *                                 选择DMA,中断或者查询方式
    ' W; b; L% i( B+ I: I! }
  4. *********************************************************************************************************
    * f2 F' A& ?% _5 }* a5 v! j0 r
  5. */
    ! ~: V/ r! [2 A" X
  6. //#define USE_SPI_DMA    /* DMA方式  */) N1 \7 i* ~; C) }# z7 H: |# _
  7. //#define USE_SPI_INT    /* 中断方式 */
    - q4 h7 k; O8 B" ^8 r) D( e' n% w
  8. #define USE_SPI_POLL   /* 查询方式 */, A. d' U: `: ]$ e/ A8 x

  9. 2 G3 X+ T2 x. n' u$ ?* p# o1 `
  10. /* 查询模式 */
    . b: S8 X0 O7 @2 y
  11. #if defined (USE_SPI_POLL)
    : C9 C+ f1 Z; ^. H! S6 I

  12. 5 q6 ^9 Q7 r1 o
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  ! Z$ G) Q+ Y: {
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    & m# s1 J9 z; B# ~

  15. $ L. {8 l, v; j( N7 c# j. X9 Y
  16. /* 中断模式 */; f" f/ I- ?2 |6 v% v
  17. #elif defined (USE_SPI_INT)
    - i2 D! ~% J6 ^6 F9 [

  18. 0 r. o; S+ p8 q( |$ S( N' ~5 C2 L
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    + ?6 J; O/ P9 a: x5 m; V
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];/ p# `# ~! f3 k5 r  N, F, c
  21. 9 z# ]* \5 }: W. }$ [/ g4 X
  22. /* DMA模式使用的SRAM4 */
    : b) V2 T! B$ f8 f# N/ P
  23. #elif defined (USE_SPI_DMA)/ R& W# P2 z( ?& h* m* B# g
  24.     #if defined ( __CC_ARM )    /* IAR *******/
    # s5 H1 E7 H8 L" @6 K! a8 D/ j
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   - B: H, d* \4 j: K& n( d, G
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];6 @1 G! D5 U" N+ v/ `6 _9 [  x2 N$ Z
  27.     #elif defined (__ICCARM__)   /* MDK ********/$ h. c+ \3 @! X! ~. Z) Q2 c
  28.         #pragma location = ".RAM_D3"
    ' F# B) x3 \4 O) A" q7 K+ V
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   : V) D  U6 S; P  o
  30.         #pragma location = ".RAM_D3", ]+ U& J, m5 _4 |1 g
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    0 |, v5 j9 j; N% i4 C& }
  32.     #endif$ Y! k4 J; C, R, L
  33. #endif
    ( {- N- Y/ {+ V) T: r/ K5 H  x: f$ [5 C
  34. & N! O  Q( e$ N8 }
  35. /*: W' h& s6 n$ @! ~2 D
  36. *********************************************************************************************************! m) A8 H: \1 L/ @
  37. *    函 数 名: bsp_spiTransfer' [& r" l/ F8 C& y& w- [0 _
  38. *    功能说明: 启动数据传输( N- t- x: G5 v, ~- Z+ g
  39. *    形    参: 无& J+ m" k/ z% P3 F/ L# `
  40. *    返 回 值: 无
    . _% Z- g( @% i# m5 o
  41. *********************************************************************************************************9 z8 n9 s1 m- A1 s) d9 b: g6 s
  42. *// i* E3 b" z' q% Z
  43. void bsp_spiTransfer(void), ?5 G: a, Z" J* Q5 B
  44. {! u0 @, u2 }: V3 {9 B* i
  45.     if (g_spiLen > SPI_BUFFER_SIZE)3 C: S! D, O8 B5 {' T) L8 ]/ b
  46.     {
    . J$ r$ E* Q/ s: E& k
  47.         return;7 A6 C$ I9 B! {- Y1 m6 ?7 f
  48.     }0 \! s. w$ f6 M1 }/ V6 m
  49. + s% z1 q" q. x: G6 o$ C
  50.     /* DMA方式传输 */8 T2 {; \, Y$ ]! j
  51. #ifdef USE_SPI_DMA5 d/ S/ d. A7 i7 \; B
  52.     wTransferState = TRANSFER_WAIT;- B) j% N% s, ]  M9 j+ C* `

  53. 2 F* k% p! T3 t( O* Y, p5 a) `& W
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)   
    0 i' S! _) s2 J& l; b2 u4 I9 u$ |
  55.     {1 b) H. F. o2 n  R7 `
  56.         Error_Handler(__FILE__, __LINE__);
    - Y% c& x5 _( x" E! y# t
  57.     }5 R4 O: J# z( z8 q  b' K0 t
  58. & G7 |2 |7 E8 {* {7 J
  59.     while (wTransferState == TRANSFER_WAIT)
    & l& k% {5 H) p
  60.     {
    ; f; w0 j9 N8 b" Q; L' e
  61.         ;
    6 ~4 c% Z% s6 u7 u4 W! U8 f% t
  62.     }8 g+ Y" g" l$ T: u% z, d0 b3 n
  63. #endif
    " s4 P: Z7 A; U, @) [9 u  q" H7 |: k
  64. 2 Y# i( X! F8 V/ z
  65.     /* 中断方式传输 */   
    1 \0 H) H7 q" [' o3 F
  66. #ifdef USE_SPI_INT6 f7 G# [' ?) r* i4 A' W
  67.     wTransferState = TRANSFER_WAIT;
    % V6 {; w1 g7 @; V

  68. . p$ h) n/ f; h1 f3 ^& T
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    * o/ @& E- u' q1 a, |
  70.     {$ }) s( [( D% ~9 B, j" Z. A, }
  71.         Error_Handler(__FILE__, __LINE__);  ~& B8 F( B4 t1 b# w
  72.     }
    5 D8 B3 Y, s9 W$ e1 a0 ?1 k1 Q
  73. 3 }0 ~7 _9 \/ i( _9 b4 v
  74.     while (wTransferState == TRANSFER_WAIT)
      j5 H' T# n  z* ]/ ^
  75.     {
    ' q, i1 L3 |: A# m6 y
  76.         ;
    . u2 g# ~5 W) O" w$ \, G
  77.     }
    " [4 s9 v% ?8 m4 R
  78. #endif* Y- O) x7 w* G# X! |
  79. " U" F& ~' X  C  }8 s% {3 R
  80.     /* 查询方式传输 */    : Z# t7 ^$ i7 ]$ }
  81. #ifdef USE_SPI_POLL0 q+ Y; O7 \, X" p! C+ j; q* f2 f
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    + U  I- W1 Y+ Y/ D, d
  83.     {
    + w7 s$ A- a  r# j2 ]; e' O4 ]
  84.         Error_Handler(__FILE__, __LINE__);
    2 @, i( `4 C; S. f
  85.     }   
    ! U& S* m/ q5 ?
  86. #endif
    ; O* R6 b: {* g, D- P
  87. }
    " d! R; `) F$ o& D5 w
复制代码
+ d# T' y' e( q& X, i1 a
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:2 B; t  C) s6 ?! d

8 ]- u- {" r% O/ }  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。* s$ C* W, c0 B8 y. d: n3 E( r
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。2 J+ I! Q0 D# [5 C/ N. r" x* d8 `  B
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */7 H) \, i) F: w" F) S* l/ B
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    4 Z& O: ?$ X) n1 [
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;  G6 x% l5 a( q7 ]# L
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
      g1 t( K5 B% c# T+ \/ Q( X; k
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% ?. d" i* d, A3 P2 V0 J5 }3 c
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;$ d0 Y7 W4 @. U; q/ l
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ; E# ?! j% z" K( j% o  @
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    3 n6 `- W( w; G% v
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    , h  @# ]4 n: A% ^% z8 S
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
      |: j( {4 s5 Z" c$ x3 v
  11.     MPU_InitStruct.SubRegionDisable = 0x00;- e! @& t' m) e
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    7 w/ e, o! f9 g1 Q* _3 v6 Z
  13. 4 \9 l4 @1 V  \' \
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
! e6 C3 L0 ~1 X4 T+ b
73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

  }$ w8 o! Z5 Q首先回忆下STM32H7支持的4种时序配置。
& `/ K0 p- Z. U/ _7 [2 u% m9 i6 t; Y" m/ y5 t6 @( m/ v
  当CPOL = 1, CPHA = 1时4 T; B$ ]3 f6 h9 v$ Q2 k& N
! s& _" j7 u$ q% Y& o. B6 v( \6 n; W
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。. F& Q3 N& C* X  a- r0 N5 O8 d

' R  }2 q+ C( w7 `; s6 h" @  当CPOL = 0, CPHA = 1时
3 ~: x' F' W+ D: G# `$ g2 n; {3 S
, C7 s& Y9 a  d" R( G2 eSCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
+ w+ a* l7 j0 S! k
9 n- m- ]  m8 b5 M( {: A  当CPOL = 1, CPHA = 0时
/ ?/ P- f7 k6 U' e% s# _2 N% z" ~2 s( A8 G0 ^/ G5 I' ]0 s& }
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。' k0 g6 a. k* o
( N5 t7 C& {; N- S
当CPOL = 1, CPHA = 0时
/ ]4 }" Y  n) W1 \; N9 O" j/ z8 x7 {! I3 i( ^- e% e2 ~/ k
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。9 v# O0 `$ D  K1 e

! l: g1 ?# w0 }$ u3 G$ R
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

7 j% j" n, m  A( S* i; K9 n0 v2 P, a+ @
有了H7支持的时序配置,再来看下W25Q的时序图:
: `% Y/ v" B$ \8 H1 k8 S8 @) }: z, L( W& Y! |2 q1 z! b9 y
Mode0 : 空闲状态的sck是低电平。
9 {! a; h6 {' n7 ~" x- a$ U7 z7 D7 f+ F$ }- R
Mode1 : 空闲状态的sck是高电平。
/ ?! ^2 W- b- P6 t6 Z2 Z: T
, r, X; B7 V2 J6 F$ L
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

* T' X+ J% |6 X4 E
( e7 O7 p. C% w5 \* i: Z- C首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:3 H' R1 u( ?2 I- |

! h* h, k. U/ o5 M/ G( v9 dCHOL = 1,  CPHA = 1
# Y5 n/ {& F( [. k  o1 Y) i0 S
) X* O5 r+ Q0 ~5 c) ~) ACHOL = 0,  CPHA = 0
/ A1 l: l( k# A$ i( B: T3 Q+ P- K; V% k5 h4 j
对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。! \) T6 }. t4 j! l' |1 ~! a5 f

( P  \% o' T) X73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制
/ T" L3 I% a4 C5 F单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:+ u$ m; _  ?1 H' h

5 d$ C5 Z/ Q3 C, m
  1. /*
    - W+ }! H8 K: q9 Z7 z
  2. *********************************************************************************************************
    + O& @2 O7 q" B; i% K
  3. *    函 数 名: sf_SetCS2 Q9 q" a2 J# J! \
  4. *    功能说明: 串行FALSH片选控制函数
    9 o: C) a' o" u7 e, B. ]
  5. *    形    参: 无& ^3 M& N  f: k( c. `/ w7 [
  6. *    返 回 值: 无
    4 {* H; j# Z, C  `
  7. *********************************************************************************************************
    & w' J3 q8 h6 N8 _% c  M9 Q
  8. */! k1 o8 J7 }6 P1 |. @1 c
  9. void sf_SetCS(uint8_t _Level)6 r' J8 O  l0 p) X" B
  10. {
    # t1 m* p8 r* x$ `( ]1 ~# L
  11.     if (_Level == 0)7 J4 l9 \5 X5 P, P, F
  12.     {; ^7 I& l5 g& v6 p0 y* R2 U+ I6 [
  13.         bsp_SpiBusEnter();    1 p, U5 j' X' w0 y
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    6 D6 P: {7 L- c6 ]
  15.         SF_CS_0();; U# s: E3 B  z( f& n1 u
  16.     }
    / }; l/ c# a) [& S8 J3 m) ^0 W
  17.     else; c, ]$ N& ?  [7 K1 c
  18.     {        6 }! X6 ?# a2 `7 T! `: t
  19.         SF_CS_1();   
    9 @; R: v, Y9 [1 W, A
  20.         bsp_SpiBusExit();        
    9 G! ^! a% l3 z3 l" Y* B) c
  21.     }
    & ?- x! _# |/ F& u/ x5 ?8 }
  22. }
    + C- T6 U* E) {: K0 c
复制代码
( r" }' ~& U+ |
! k. Z  h. R+ P. T& p# e
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。4 z% R, l* h6 V1 B/ z1 l0 \- Z

0 h7 K# h+ g3 n: X6 w但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。
) ]" y1 ]0 D9 ]6 K; @- n  e
3 d$ N, t8 a* R- p6 o+ F# X+ d73.4.5 第5步:W25QXX的读取实现
! Q' m/ m+ p. G+ u5 {
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。" x8 r# U, a0 b& z1 N6 e
  o) u% p, M; O2 W& N. i8 v5 `
  1. /*
    3 z; h% p/ j6 H
  2. *********************************************************************************************************  X4 ^& Y( I3 W5 E$ L/ m" ]% ~/ Y
  3. *    函 数 名: sf_ReadBuffer
    3 W! }# r# R2 X  |+ l2 p
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    7 E% v5 y% q# L+ z
  5. *    形    参:      _pBuf : 数据源缓冲区;
    % C1 a! g% l, O: \! ]
  6. *                _uiReadAddr :首地址
    / G7 R, w# `& Z0 Y/ M9 u3 h
  7. *                _usSize :数据个数, 不能超出芯片总容量' `/ a% f# d9 A) M
  8. *    返 回 值: 无" M- a1 E5 G+ S9 j! h( c; W; |9 ^  ?- b
  9. *********************************************************************************************************
    3 r( s1 N5 c6 Z+ w9 s% [
  10. */
    ' f+ {& V$ E: k# X8 B0 t7 X5 x: R% T
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)5 I. W0 l. C( k+ U4 u: \. T/ \$ `
  12. {0 n# I; W* B" k7 z
  13.     uint16_t rem;
    7 `0 @1 V& |% m' |+ m
  14.     uint16_t i;
    4 F+ A( x% P4 j0 X, `; `

  15. , `+ }/ \5 N* m! j9 |( P
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    8 m1 |- c$ M0 V' p0 W1 [" U
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)' M$ m! J" f0 x# }' ~
  18.     {+ L/ \3 s( Y) i& X6 _
  19.         return;7 g4 c9 J4 O- ]' D
  20.     }
    8 f& _* b# B4 b8 D, s

  21. : p: n. G4 I* x; `/ r& O
  22.     /* 擦除扇区操作 */
    ! m) P3 A0 Y1 L  F( K" Q
  23.     sf_SetCS(0);                                    /* 使能片选 */, Q- Y9 \2 j0 j7 Y0 @8 x  }
  24.     g_spiLen = 0;, I) u/ d4 A6 ?1 n' X
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
    ( w9 C5 G0 D% S$ v' x
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    ( d; \  C- G# V: d6 Q
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */
    $ G/ b  I& B8 }9 s$ j, ^  u
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */- E! Q+ o# B. N7 l8 ], I9 m5 X
  29.     bsp_spiTransfer();$ X* }- e8 V4 S3 k+ ~$ W) ~9 E

  30. + c. ~& Y& X" |3 B9 C# c. w& h
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */; d9 p4 E0 m% X' U% a8 R
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)% f7 u7 b1 S. ?* C" W2 N( t
  33.     {7 G3 Q- G7 @4 L
  34.         g_spiLen = SPI_BUFFER_SIZE;+ W$ Z9 t+ |! A' u5 ^3 ^
  35.         bsp_spiTransfer();
    9 Q; M+ g1 u, ~3 J+ `; T1 M
  36. 9 V7 v( _' L) Y3 M3 Z# @
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    ; W3 y2 h# [, R3 l2 V
  38.         _pBuf += SPI_BUFFER_SIZE;1 A: |! I) t- c2 `9 @
  39.     }7 N+ G' |/ A2 h  e' S3 M& ~3 h
  40. . e! N& X: \6 _; r) ^, o
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */: L9 d, R2 f1 X( c
  42.     if (rem > 0)0 Q; d) f3 m* V, e0 A
  43.     {
    6 ~9 Y) q3 X3 [. R( r" B6 s
  44.         g_spiLen = rem;
      ~+ a/ ^0 R3 c# N, k4 _; s
  45.         bsp_spiTransfer();3 T* j# _2 X% V& K1 |% O$ L8 I4 N
  46. 2 ~* e8 ]% b5 y+ l# g
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    9 H/ i. A( x$ y! W! W; E
  48.     }7 I$ O; N* L% H0 ~; \% y
  49. 7 G% K9 f* s+ E! e3 f7 v7 T  L
  50.     sf_SetCS(1);                                    /* 禁能片选 */5 a- }- E# @$ s
  51. }
复制代码

9 y1 W! A% C; ^, e3 I; p2 U8 ^! l* C7 d+ S* B' S5 w% x
这个函数对DMA传输做了特别处理,方便分包进行。
4 P& j" c, x! T7 f
5 X9 E7 c2 s$ q73.4.6 第6步:W25QXX的扇区擦除实现
. p- J6 Y3 Q0 W  d
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。
$ M' ], r- K, ]# Y, B0 p( _
5 |( V. S6 `; s/ A6 v
  1. /*
    3 V. c8 i2 f  o4 \6 ^4 f8 \
  2. *********************************************************************************************************& A8 [1 x1 T) w- R0 @6 K& G
  3. *    函 数 名: sf_EraseSector
    , U, o$ e6 Z) Y% j. C9 S
  4. *    功能说明: 擦除指定的扇区/ s* ^1 y" ?( m6 x9 X/ X  P0 i* L2 Z
  5. *    形    参: _uiSectorAddr : 扇区地址
    # P/ }' A" P0 W3 Q1 R  {' W" y7 D
  6. *    返 回 值: 无3 N5 F! t9 T( _5 M- M, E  {/ E
  7. *********************************************************************************************************
    ) _3 ~0 t( @/ Z  k6 I% H  b, y
  8. */6 a* v4 n! ]; Q1 B
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    ( `0 h) T9 x2 M' h. z
  10. {
    3 c5 W2 b8 b( W
  11.     sf_WriteEnable();                            /* 发送写使能命令 */
    2 Q' i. R6 E1 I2 ], F+ h3 A. ]

  12. $ `+ A+ L1 r- i8 H5 y# a
  13.     /* 擦除扇区操作 */6 Z0 G. ?% L" u7 O9 G
  14.     sf_SetCS(0);                                /* 使能片选 */
    ; _# u" x/ N" r, L4 m: m
  15.     g_spiLen = 0;! x5 `% H2 e- o" A2 c' F
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    : ^% H3 @7 W" x$ U
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    % g& f, D! j" N6 F
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */
    : A& v. u, r. U& K
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    ! k0 c2 i4 s( {% k# f
  20.     bsp_spiTransfer();1 a' T! S9 T( l
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    % q0 @( F' D0 O0 E# d( ^

  22. ! C+ G, X# `: b% q3 D
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    " p( @' Z7 a4 _; ?; R
  24. }
    # [& N& k, p& D0 X- ~3 g9 n
复制代码

  |8 p: [! c0 p' f" j
* y2 y  W- B) ]- n1 \+ |整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:. C. G- g2 z9 Z; s( O
4 t* l. A4 G/ y' x8 O/ d$ d  e
  1. /*
    6 K) k$ e3 e, j) z
  2. *********************************************************************************************************
    / ~" k, H* p) X3 S& C7 Y' F" Z
  3. *    函 数 名: sf_EraseChip9 s- Y. J  r! k5 [) H3 `
  4. *    功能说明: 擦除整个芯片# t* u7 o; B. b
  5. *    形    参:  无
    3 h+ V: d2 z! q! _5 t% j
  6. *    返 回 值: 无5 K6 M* v# `) d2 c2 E, H7 U8 _
  7. *********************************************************************************************************
    $ n: F: S3 h. Y+ t
  8. */5 E% i: K- E5 q) d: H7 D
  9. void sf_EraseChip(void): ?6 s3 w( b7 V* M6 ?3 q% l- h. T
  10. {    . Q; Y! Z! R# q# x) j
  11.     sf_WriteEnable();                                /* 发送写使能命令 */
    5 g$ ^( S7 D# `5 c$ Q& w) y: v8 L- G

  12. # N6 Y1 H* l% v8 E( t
  13.     /* 擦除扇区操作 */  J) [2 a2 V0 M
  14.     sf_SetCS(0);        /* 使能片选 */
    4 v; H# A& {* E8 C! X
  15.     g_spiLen = 0;
    + l" B  N9 q1 m; B0 {7 g
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */5 u7 K9 O, l. |8 y4 |) B
  17.     bsp_spiTransfer();4 u& y( p1 U* i4 T8 n
  18.     sf_SetCS(1);                        /* 禁能片选 */0 W0 w- H0 l) L5 Z' i- `

  19. + u3 C7 Z0 F& m+ E4 f" _4 g( v$ d
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    2 [5 a. O2 b8 N; N3 G7 F: \
  21. }
    % }( C, C- o1 r! O0 ~
复制代码

1 E1 q# i8 B- h  _# a73.4.7 第7步:W25QXX的编程实现
) B& C  |2 _  |W25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
. ?2 h( }% r" q/ u/ a8 T5 g
4 E, c- p1 c: j8 m# R& c4 @  O; Z' W
  1. /*+ P7 Z3 k& }; F: i6 E4 |2 y3 j1 z
  2. *********************************************************************************************************
    4 d$ u3 D' N8 a  k
  3. *    函 数 名: sf_WriteBuffer$ h6 z# R! B+ k% l1 n) D0 @* N
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。/ p, T) `# z' P
  5. *    形    参:  _pBuf : 数据源缓冲区;- e! J3 G) z9 k9 j- T  Q$ s
  6. *               _uiWrAddr :目标区域首地址7 N7 `/ c, d2 \0 b  F9 J6 n
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。1 N5 D/ {( ^$ F9 C9 m3 Q
  8. *    返 回 值: 1 : 成功, 0 : 失败% ?0 m) U2 j: m) O: I
  9. *********************************************************************************************************
    % d2 t5 w% F8 c" a$ J
  10. */$ `% i( s3 T, c( ?# ?
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)# k( f& A( z+ e& ~
  12. {, A0 L2 P# t" {8 b
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;; m/ w) y3 H1 a8 u
  14. + }& f$ o+ d  z
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;
    8 k3 \2 D5 P/ T3 _
  16.     count = g_tSF.SectorSize - Addr;  I0 T2 z+ G+ N3 Y* _( ]1 v
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;8 \5 F7 ?( W. P5 h1 {; d! g
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    : F7 N+ r# h. u2 {  Z) S
  19. 3 a, W! Z* n  C! {
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */  ]1 L% j% c$ W1 f$ w
  21.     {) S2 R& }1 M* q
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */, s9 {: c* ?; e
  23.         {
    3 e. y! g: K( F: }
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    4 x; w; U# m, X
  25.             {
    0 `4 w) U# A' p' [* P8 x
  26.                 return 0;
    * m$ b8 a5 \* O" h5 t! ~
  27.             }! n1 E- t0 q- j  I" Y$ R3 a
  28.         }
    7 g# X! B6 y- d& R+ [; k6 h. Q) L1 g
  29.         else     /* 数据长度大于等于扇区大小 */
    * y6 O5 i! ~! F) m+ e3 }7 D
  30.         {
    ( _: a- e* e+ Z% ^0 s7 o
  31.             while (NumOfPage--)- d' d8 |; h' P+ Q5 ~" ^
  32.             {7 M2 u: ]+ A6 m& F: q9 i
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    ; B4 I; d4 n! w  u1 j
  34.                 {
    + }. C6 g% ^7 B: a; y, I
  35.                     return 0;
    7 |6 h; Q* n0 `) Z6 d8 V4 W3 e
  36.                 }/ A8 o8 ]  k" Z9 Q9 Q" w
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;
    6 S0 l1 x$ }' d  c2 S
  38.                 _pBuf += g_tSF.SectorSize;* `1 b) `  f! j6 G2 z0 l
  39.             }( W. [4 Q& U& g, y9 G8 S% h5 ^  c2 p" o/ s
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)0 |9 V3 h) ~: v
  41.             {# w. t) ?( g& _$ W6 L* i9 N
  42.                 return 0;/ w- ]: e0 ~! U1 L' v0 ^' O
  43.             }
    8 u# ~3 q% z/ F1 A5 d. Z9 C
  44.         }7 \+ w% ]5 y) p* W- ^9 x
  45.     }* V- v6 {( U+ G
  46.     else  /* 起始地址不是扇区首地址  */
    5 {3 R3 @5 ?0 s9 q8 m
  47.     {8 o& }3 K1 n/ f
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */, W/ F0 V6 u9 N( F' H, }3 d. A
  49.         {) W; S# S  p8 v) M3 l4 b% W
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */
    # O" t: R+ ]4 A0 U* h4 l
  51.             {
    4 v6 `4 {9 [! X# Y) j
  52.                 temp = NumOfSingle - count;
    5 @' |4 B9 S# F
  53. : n: x' ]6 a7 X! g0 i
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    : o$ w& ]0 u' C( B
  55.                 {7 G) P8 S7 [$ ~7 y9 W
  56.                     return 0;& K  j9 M: F* F6 B) P1 ^7 |
  57.                 }
    $ ^- E5 B" a; X$ e* W" Y( @& I' X

  58. : Z+ G5 A) ?+ t, m, _
  59.                 _uiWriteAddr +=  count;
    6 I; q9 R+ ?  y/ w# }, _& H
  60.                 _pBuf += count;) ^$ A/ [. u6 i

  61. . X. O; D- A9 B$ e; @2 v
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)
    ; f9 Y! b0 l4 n( o$ B
  63.                 {
    " a5 `# ?8 y5 R! w2 K
  64.                     return 0;
    & p3 t2 `4 B) u: d
  65.                 }* i  e/ s! _/ L0 E! p# n
  66.             }
      R8 e9 Y' _( x
  67.             else/ T: \  x& S2 p$ z7 r: h
  68.             {
    ) U+ I3 f- h; X( L
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)6 k" i( a# i0 b8 Q' u7 `! a$ s5 t
  70.                 {
    $ v  S  D0 w+ r$ W7 e  Z
  71.                     return 0;
    ) H$ c( L% p7 A2 r5 c2 y
  72.                 }1 s! m; f; D. X: L7 r4 N
  73.             }: L* ^3 Y+ v! ]' y" r' r. i
  74.         }
    2 q1 |5 k# N! {8 g/ ?0 b( I% d
  75.         else    /* 数据长度大于等于扇区大小 */0 i8 ^6 M! |9 U' u7 f- c; g( q8 C- w
  76.         {
    8 \% N; O- a- `
  77.             _usWriteSize -= count;
    6 u! F" m. b7 [
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;0 e4 M' H1 m' T
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;. Q2 d5 J3 V6 D/ ~$ z2 [1 r# @. o
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)1 b( J9 F* W: q7 z; {
  81.             {
    4 A% o6 R8 ]$ a: d
  82.                 return 0;
    + H2 A4 a' ?5 K
  83.             }8 e' \. [1 s9 M1 h8 m* I

  84. $ o0 v- Q0 D2 L- T4 A4 h' t
  85.             _uiWriteAddr +=  count;
    0 ~9 u0 x! y" p1 ^% H
  86.             _pBuf += count;2 |! A1 @2 X! b. e; O% i

  87. . @$ |7 y+ n! j7 n0 W9 u( `
  88.             while (NumOfPage--)
    - T  B+ q  b5 _6 Z# u3 r" {
  89.             {
    , \# Z! C& a, \( y
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    1 n( ?: F# |3 `& F" u
  91.                 {5 X4 |$ {# Z# |4 f6 C1 U
  92.                     return 0;
    2 ]: H0 X5 A, ]0 U% e3 {$ J6 a4 {
  93.                 }
    4 T  F# D+ @6 n; ~5 v3 N$ K
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;& _' Q4 h: q# f; S4 e# s3 G1 E
  95.                 _pBuf += g_tSF.SectorSize;
    , m& G) s# @3 S6 n6 Q" G+ y% j- o
  96.             }
    1 X3 }" u. W# {" {

  97. 8 N1 [! K. y, c: O$ H0 S" n
  98.             if (NumOfSingle != 0)8 F+ Q6 B: x( O1 y5 g
  99.             {
    " z9 I% S/ ?, K, V- I0 w8 }
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    7 I  o, Q* M" E+ R: l
  101.                 {
      N- B- y: b) E* E0 ^' _' N' u
  102.                     return 0;
    8 }' z. e* A; E8 B6 K
  103.                 }& }& N4 \, L0 S, ~' @- r* X; u6 g
  104.             }
    ! k; _8 H& y- K% F
  105.         }
    " J: n7 ~* V* A% Y3 L) b& c' t- R
  106.     }
    9 R+ R5 N( t- I( R, r
  107.     return 1;    /* 成功 */7 a& u" v9 o7 _. ?( a1 C
  108. }
复制代码

8 E& T% [7 O4 |$ B7 s: o$ Y0 N73.5 SPI总线板级支持包(bsp_spi_bus.c). ]: [" f% {2 Y$ ~8 m( e& K
SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
0 {* c- ]5 u2 b' ^7 ^1 Q* ]7 u/ c# A% y
, \# W7 J  u- A  bsp_InitSPIBus
! S. N( f  ~  p  bsp_InitSPIParam
/ q; ~4 k& ]. u# c- ]" T  bsp_spiTransfer
' l( R$ M0 i* A9 w0 y73.5.1 函数bsp_InitSPIBus, {9 M. L: z' l# n' X' ~
函数原型:
7 ~% q% h5 ?0 U0 ~- e. k. j4 g% f3 f/ D+ ~, m. _
void bsp_InitSPIBus(void)% Q) E2 G# m# ?3 |" o
. o, Q: o0 h0 K; n/ Q0 G
函数描述:8 e# i2 O% D, b% D8 Z; @
0 O4 o( d, E! a, d+ |! Q! B2 {& H
此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
5 |& j9 e+ g( x0 e) |% U7 ~; `+ N
/ t$ Y! w" r# e: @/ W73.5.2 函数bsp_InitSPIParam% o% R# b" B5 e8 l" a+ T$ T8 b
函数原型:9 S5 D* W4 D$ r+ B3 _, @

! V" D+ W# x! |7 Jvoid bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)* z! q: X& y; @5 m" i
, r1 h" h7 T( d. t1 h. t6 W$ j% `
函数描述:# S7 W9 P; R9 V. e
' Y# G( _* J" I0 e
此函数用于SPI总线的配置。9 @3 q  l4 v. z5 p
: h) t/ Z0 r4 O* P6 R
函数参数:
2 q. {( r: ^! z" Q7 h# m3 ^; W- }1 [5 a" b. l; q# s
  第1个参数SPI总线的分频设置,支持的参数如下:
2 ~" X" e- e" N+ u( ~' N4 Q/ ^SPI_BAUDRATEPRESCALER_2    2分频
% P, s! t3 l, T, v) W9 M- M0 r7 h7 Q' m8 \! q' A& g; ]
SPI_BAUDRATEPRESCALER_4    4分频* P3 q2 U' C$ E1 E- [+ Y- R" v3 f

8 G/ K, l2 q, t: ASPI_BAUDRATEPRESCALER_8    8分频
8 C3 k9 Y$ \# S. [, b8 w
! C! l# |, F5 Q- {) FSPI_BAUDRATEPRESCALER_16   16分频
# b, r7 s% o! ^( i
* e, t- H# N  Q' e  s" z! dSPI_BAUDRATEPRESCALER_32   32分频% W4 e. r  f: b, }8 y; k! c
6 ]" X9 i2 _! i4 [! M5 M* x
SPI_BAUDRATEPRESCALER_64   64分频2 d" k2 v# ?" `
2 G; G% }& H& w
SPI_BAUDRATEPRESCALER_128  128分频9 F% Q) ?$ u+ s% Z! G8 p2 d4 ^

- _* e; q. B; ]" {, x  pSPI_BAUDRATEPRESCALER_256  256分频1 b  _' o+ k* E" E

2 ], E2 f7 d8 [. j: [3 C% N# Z2 ^  第2个参数用于时钟相位配置,支持的参数如下:8 K; A# |, a4 q! F
SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
; Y. j7 o5 n% r6 ?0 t* \. t: z; k# W9 b  `6 ^
SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
8 g& s: M  L. u/ l
( `8 ], A8 v/ W. _3 Q, O  第3个参数是时钟极性配置,支持的参数如下:7 ]3 y& j4 H9 e& K) d
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平% a) V9 \2 N- C1 D) [
# `1 z0 M! j  H# {
SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
, W) O4 B- {8 ^4 I
$ d# p/ @# w6 F6 j; p* ?7 S0 p! ~" i73.5.3 函数bsp_spiTransfer

' t3 Q/ D/ t/ p6 a( c函数原型:
; J9 p$ s# K% l9 O; s3 ]1 S, d  P* v+ c0 C6 G; z  ~8 j+ t
void bsp_spiTransfer(void)
0 i' Q$ Q/ K: t4 }- j. f+ X" J, n" ?; v3 `: r' B2 J
函数描述:
0 |! R0 H$ h3 l1 @/ F) N) P  s) P9 z. I+ z, `! ~# i1 d& Y
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
* L* U  `3 Q; l- x/ T; X
5 F) [" m5 m: g# i% \9 L0 e73.6 W25QXX板级支持包(bsp_spi_flash.c)3 g7 f. D+ }. O; B
W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:+ l: T3 A+ F5 |% w
; {( V0 t, k) C+ W& |3 R
  sf_ReadBuffer  F' Z  A& N% F* n& C+ c9 \
  sf_WriteBuffer
0 y! Q) N5 l) V% }/ b( R% V  sf_EraseSector2 A& }  {/ ]4 N, W" c  n* c3 V
  sf_EraseChip
/ i# @9 R: X* d4 ~; D  sf_EraseSector, r$ R  u$ L2 s: ^4 A7 X8 v& J
73.6.1 函数sf_ReadBuffer1 g4 ]/ i0 f5 \
函数原型:, e9 @3 a9 s9 W3 {7 s( \8 f

$ M4 c" [! g/ x8 m. xvoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)! V1 w  O+ Z3 J

3 b6 O3 d. ~% c% v函数描述:
+ T6 p  E9 J& d* K/ G5 g4 t
1 G0 X' c. w$ }此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。
" Z5 b5 R5 V5 @0 c7 r, t2 W3 A7 O# _- H" w* I% J: T! H: v9 J
函数参数:
9 K* k8 r+ @% _/ V! S
) F8 Q. V  I$ z' w  z% `4 @9 j  [1 ^- Q  第1个参数用于存储从SPI Flash读取的数据。4 p. X5 B& C  j8 P: m
  第2个参数是读取地址,不可以超过芯片容量。# A8 J0 ]# [% f0 q8 e; u1 p, x5 M
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。. T$ U3 ~5 g, m: i
73.6.2 函数sf_WriteBuffer(自动执行擦除)
$ A4 h) j$ Z" x  ]. K1 o2 L0 K% W函数原型:5 m. V, I( `3 w9 v: p" a" ?3 P. I, u
8 S* F6 j' P  @* M. r0 c
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
* W! w4 n  p5 V7 a) q) t& v
+ @6 h1 }8 `% B. D! O函数描述:8 h, x0 L) Z6 d- l1 z

* h" [7 n4 [( K此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。- S" `" W6 c; k) F2 k* Q
0 R0 K& X8 I- v' i* U- l( P
函数参数:
2 N* L1 \/ D! g0 s% r) H0 M% Y9 Y
  第1个参数是源数据缓冲区。% |3 `7 ?! w* B" c
  第2个参数是目标区域首地址。% a. {% g9 H3 t
  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
6 {9 y/ c% H! z8 c  返回值,返回1表示成功,返回0表示失败。
, ~: j2 D8 I; _- l: p% S73.6.3 函数sf_EraseSector% Z) s. H0 f- }6 a1 i! V
函数原型:& S! Y0 f; P. F! q( ?" b
5 a. q0 L$ B8 |; X/ ^5 t0 ?
void sf_EraseSector(uint32_t _uiSectorAddr)- R' X; L+ J( Z8 V2 F

% z$ D. V. P2 u" L函数描述:
% O/ l" ~) \  _: f0 L0 R: n! q) W0 w: e& f- ?. t3 N- k. f% R+ H
此函数主要用于扇区擦除,一个扇区大小是4KB。
- T) i# H/ X+ {5 r" b
. y/ M( Y+ ^6 ?! h% W6 ?9 ?2 n函数参数:& |/ j3 y3 w& ]0 S% K  |

6 n( h" `7 f. S4 S/ o; q  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。5 j+ m% [3 N* Y6 B# F$ F: W
73.6.4 函数sf_EraseChip2 Y0 h+ ?7 M+ Y6 Y
函数原型:
" a2 j* i4 R, b/ |
, Y) r! L, m6 Kvoid sf_EraseChip(void)
& m9 y7 d$ s  A% X# y) O& C
4 g8 z" o. X0 k3 v5 U! Z, A- U& C5 U0 Z& b函数描述:: ]% u/ b/ r2 p  u$ z
6 G1 Y  o* a4 l. U* I
此函数主要用于整个芯片擦除。0 a' A1 h9 j& G+ g4 g# x3 h
5 a  S( t2 F, i1 f$ M
73.6.5 函数sf_PageWrite(不推荐)
* k( h# `  a4 b) ]4 Q2 o函数原型:
, L% @- z2 Q  c* w4 B9 U1 s) P$ _" Y! b5 a( K4 g
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)" `: |. S% f5 A% y/ d+ Q
5 Y( u  M; s# y8 t. ~
函数描述:
3 p% P; T) b8 w6 r& ?
$ i! z" @0 K& l  A此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。
% f9 S/ S2 O" E% T0 d! y* }
6 U. \# g5 x  p3 y1 ^* c+ M% n( A函数参数:3 D) k2 o" m( n

4 W0 Y: U! Q& ^: ^5 @& ]* ^7 M  第1个参数是数据源缓冲区。
; l. G% Q8 C4 b& g, `2 x7 m  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
, @/ ^9 e7 M& \- h" v" {9 g8 D6 B/ P1 U  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。8 p0 Q1 b; G( G% q* K1 U7 U
73.7 W25QXX驱动移植和使用
% v$ \' t  \; K" G6 t' f* \W25QXX移植步骤如下:
  r8 i  H5 t% u; \$ t! ]9 d. `8 z
  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。
$ e0 A9 I; U# H. I  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
+ ~& @7 m* B; X
  1. /*
    1 y$ M. k' l# L& X: B! n4 `% D
  2. *********************************************************************************************************/ s& ?$ E" R/ o9 C
  3. *                                时钟,引脚,DMA,中断等宏定义
      d, ^/ y6 J; ?+ D& v
  4. *********************************************************************************************************' Q" ^1 s! o. m3 _' J
  5. */& _4 ]7 E. f- I7 M/ ^; T
  6. #define SPIx                            SPI10 u0 I( Y$ n( z2 F' i
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()+ V8 R9 k5 x8 u
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()1 {* ^+ F! ], y
  9. 5 B, u. Z" [- F6 ]0 X
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
      F9 c5 R& X7 S3 m
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()  A! {; ^5 O; l5 p5 H; a# S
  12. ! h+ w5 ^  b7 L) o5 I1 Z
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    ; Y$ u* H3 M, U) }0 O! n
  14. #define SPIx_SCK_GPIO                    GPIOB
    0 D- |1 q# q; G) X- I0 r
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3# M; \( }0 w1 B
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1
    $ t; L3 Y' o; X! K
  17. / @* d$ ?- P0 N7 s
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()3 Q+ Y0 p- |  @9 i6 }5 B. O
  19. #define SPIx_MISO_GPIO                    GPIOB
    " }; G, B8 u: d' z7 z
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    * t  t& h; e0 O( a0 \. x! t% c
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1
    # `1 H/ J8 a/ B, }" U

  22. % Q! v, r& P# P5 x
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()" L1 }+ X/ x, Q8 {
  24. #define SPIx_MOSI_GPIO                    GPIOB1 C, x$ F4 b* X) O1 d
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5
    8 V- O4 g* [' u3 D0 v, q
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1
    5 K/ e$ \( V* l
  27. 4 C$ q+ @7 Y( H% E
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3
    0 v! ]9 s& h8 P1 x( D7 m( q( H
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream29 q$ ^9 d6 J8 N% F% x. n
  30. ' Q/ y+ A& h# \- P
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX9 d' o* ~+ d1 S( o# N
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX
    0 R2 E* N- M/ s
  33. % f) y; t6 }! \/ j: z" M, d0 V
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
    % a$ f1 d0 r, ~( S
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn5 x) H; |4 N( i

  36. 1 K. G3 {( B7 E6 i! x
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler# s- D2 S& S: {
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler0 M; P0 Q$ V  P% L8 {

  39. - X+ V0 F8 Y% d4 i3 ~
  40. #define SPIx_IRQn                        SPI1_IRQn; P- p6 o- L8 K1 D' O; j
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler+ h) B- Z- W% `$ f! M. S% p+ Q
复制代码

9 B3 x8 A) Q" L) z7 o/ t8 d/ i. Y! @9 f" M. b( v
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h2 e( t: M7 r4 O% i& o
  1. /* 定义串行Flash ID */
    0 l' L0 L1 n; B  ]& y2 z
  2. enum5 [2 ]" K' S6 M$ K9 E# h: J
  3. {
    ; @& K9 z% q4 [" F% a* W% O1 j
  4.     SST25VF016B_ID = 0xBF2541,
    : Z& k5 L$ Q# h% Q5 R
  5.     MX25L1606E_ID  = 0xC22015,
    - d& p& w( U& y1 R! n9 [
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    3 p1 t( m6 W5 c, O1 W5 H
  7.     W25Q128_ID     = 0xEF4018+ D  U* J/ u* z. C  v" g
  8. };
复制代码

0 I+ i* u1 k: U! C+ T  e* @2 Y  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
. o" |5 S$ }& k5 D
  1. /*
    : B, k# o! ~7 j2 w, |+ n
  2. *********************************************************************************************************6 A) o* K6 b, d. J
  3. *    函 数 名: sf_ReadInfo. R0 P, F% B* ~! C
  4. *    功能说明: 读取器件ID,并填充器件参数
    - C6 J# [+ O+ B$ Z7 ]
  5. *    形    参: 无
    ; `2 a. o4 a# L! z# ~: `) e
  6. *    返 回 值: 无# |! u. `, {, Q  r; R
  7. *********************************************************************************************************
    - |' G, V3 S- z7 J
  8. */9 C" z; y; G4 {8 n
  9. void sf_ReadInfo(void)9 r& U) _: G" t" Z8 H
  10. {
    . Q. l" ]6 t2 N$ t0 S) Z
  11.     /* 自动识别串行Flash型号 */
    1 ?1 A4 g) }/ }" G3 N
  12.     {" N5 P% X8 ]/ g; i0 b1 }$ c: U9 Q
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */  t) Q3 c$ K5 `

  14. 6 c+ E" Q; _. B( ?
  15.         switch (g_tSF.ChipID)/ r. C1 Q5 W; y; J6 p
  16.         {, S8 d, U/ G9 \1 i/ J
  17.             case SST25VF016B_ID:
    7 L' u% ]& @- p3 A  @
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");- D+ w6 u/ H% w+ X
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */! p, Y9 {; B( x  ^* z9 p5 I
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
      {, `6 l, _/ O: L6 m* I: D
  21.                 break;9 V: S7 P# O  ]7 `+ E
  22. % D* o- l- s. u7 T$ R/ [6 G
  23.             case MX25L1606E_ID:
    : v$ o" ~2 G: L6 T; u2 Q6 @
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");7 w" N6 `" k! P
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */2 o" D6 V6 l1 J: R9 g# h0 f% {- o
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    * y. |8 }7 B4 f" I; l
  27.                 break;
    : o8 Z1 v2 X% a' U6 d

  28. : }" N+ I6 B9 R* u4 U
  29.             case W25Q64BV_ID:2 N( t' L% `+ o0 q7 q( L
  30.                 strcpy(g_tSF.ChipName, "W25Q64");$ V* Y1 Z8 \" R$ C- V; B- f
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    ' h( T5 I2 h  F8 L6 }( B: z
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    6 q5 K" L. ^" l3 P
  33.                 break;( W' X) |0 X5 ]/ _4 g
  34. 0 c* T- g' g" F: w* A
  35.             case W25Q128_ID:
    & n( _" ]9 R* b5 z; t6 U
  36.                 strcpy(g_tSF.ChipName, "W25Q128");
    5 t& a2 k/ ^5 ^, Z7 \1 V
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */  k& ]% \- u  C5 H! B! A! K
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    " V0 G% Z9 B2 P; ~
  39.                 break;            
    , y+ Q3 k, [; @8 R. \8 H$ o" r

  40. 6 T+ i) u5 X7 U0 p0 l! u
  41.             default:$ v) i! B$ V/ r1 Y
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");) I/ X) h0 [( G+ J& j
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;; H1 G% d8 Y* T
  44.                 g_tSF.SectorSize = 4 * 1024;! y6 s; J8 u! Q' q7 J
  45.                 break;
    , p  d- }4 }$ o. G
  46.         }
    6 a. Q8 U# C" u
  47.     }3 k6 f) U! [+ p7 W6 U: L
  48. }
复制代码
- B6 y4 x; P- N
  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。
7 E+ a0 R# N( y* D' y6 {% J# v, ?
  1. /*, e. y$ n# _6 R% C. g% n6 \0 R" c
  2. *********************************************************************************************************; b' ?- F5 N* @+ K1 e6 U. t
  3. *    函 数 名: sf_SetCS
    ! H$ E% o0 g) j0 N
  4. *    功能说明: 串行FALSH片选控制函数
    0 ~" k& w4 Y( O2 \9 [1 H# I( u, l
  5. *    形    参: 无
    0 V! \( [( ]9 Q, e2 P: e
  6. *    返 回 值: 无
    - T' `, ~6 Z3 L% [/ d& g, F: Z
  7. *********************************************************************************************************$ |" |  u+ ?" a2 z, E9 P" B
  8. */
    : U" m0 O6 D2 Z2 i, J
  9. void sf_SetCS(uint8_t _Level)
    & F# E9 X) |  k9 ^
  10. {
    : x3 I5 h3 U: H! Q% x. Z
  11.     if (_Level == 0)8 o- p/ I; j; x1 e
  12.     {
    , G$ V4 r7 U4 q  s3 C; k; |( d
  13.         bsp_SpiBusEnter();   
    " m! [$ k1 Y$ V0 Q
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    9 O, X' p. p" E6 J7 W% n: j0 T
  15.         SF_CS_0();
    1 p' E- j2 a0 h! A
  16.     }
    ; j- R. V: ~' M3 H  q2 t
  17.     else
    & l4 ?6 w9 `4 R: d% ]" ]. c$ T$ a/ V
  18.     {        
    # c" K1 g& U4 M6 @+ c  `
  19.         SF_CS_1();    - m, \: |2 ?9 U5 s# [
  20.         bsp_SpiBusExit();        - M, L. V2 G1 x
  21.     }
    2 `% K: ?) f& j, o
  22. }
复制代码

7 l5 J+ {3 P% E. n+ _  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。$ T. z% S! p* k, p+ u+ E
  1. /* 串行Flash的片选GPIO端口, PD13  */
    ! {4 J3 \3 c- @5 e+ Z% d" @
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    * U; |% A3 e* u8 V5 q* i0 Q4 s
  3. #define SF_CS_GPIO                    GPIOD
    4 y- K$ N: x( [! }- d4 f: B8 ]
  4. #define SF_CS_PIN                    GPIO_PIN_13
    ' l4 W! N4 g5 C0 w

  5. # `1 {- e' e% G& d. y/ X+ T4 m
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U)
    % H1 x. N) B5 v9 a* h( m) I# u
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码

* P& Q, L6 e  J2 ?  n  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:
3 C3 n: ~6 B% Z2 ]
  1. /* 配置SRAM4的MPU属性为Non-cacheable */4 Y7 B0 l, ?- B% P5 {+ w
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;: G5 N0 |0 {( j& Q
  3. MPU_InitStruct.BaseAddress      = 0x38000000;
    & h! y" Y) T, I4 L' f7 e8 T
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    % f+ D# f; Y3 o8 Q9 `; l
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    . ^  N1 t, N  o2 {
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
      q6 ?) M0 G1 x/ D9 K* U
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    $ Y, [1 m" b  K3 Y1 L) l( d; l2 l% R
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;6 R# B! j+ o& C
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;, Q$ j6 |. Z2 \- |! P% u7 y2 x
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;( I6 x  r. |7 C0 V* G
  11. MPU_InitStruct.SubRegionDisable = 0x00;" @: a5 G; e" n$ i; T/ t# a# y
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;4 a  E6 b* H" p" ?% G+ [& ~5 a4 p
  13. ' e/ h1 {: p5 z1 x
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
5 `+ T, v- O+ ]; o
  第8步:初始化SPI。' u& Q7 ^. z0 u  ?/ H
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    + K" G1 i0 E4 f" u+ y2 d7 n
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    ( J8 V  R, a$ A; E5 P
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码
& V; \( s8 B  n0 I1 m, G; [+ ]2 J
  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。: M. `) x0 U" a* @8 }! o- \
  第10步:应用方法看本章节配套例子即可。
1 V9 B4 r) X& ?1 ?9 a2 ]* f73.8 实验例程设计框架1 a! x! v9 g) O3 `/ V' [
通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:2 Z# R5 _- I. c% |8 _
' L/ j, k% V8 m. e: E8 l- }0 l6 K4 X
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

2 m' R9 O: e* Y7 P+ p1 Z  i" M1 k5 [' e3 l
  第1阶段,上电启动阶段:
, ?; c2 S2 i: W$ J; }: T
6 Q: c# t- M4 U4 C2 G8 g这部分在第14章进行了详细说明。! I' w7 i; u& @4 w
  9 m8 Y' X. o+ x  E- }1 b+ c
第2阶段,进入main函数:1 v' b9 j2 k8 M9 N
4 z$ q2 J# g/ _7 ?4 t
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
7 Z7 k! g5 O; M第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
% `! }/ X8 G5 }' x; V7 ^/ K* G* N2 x9 ~% m! h7 v8 M
73.9 实验例程说明(MDK)
% B' T" z6 U! T5 Z+ ^/ y( U* @配套例子:
. q9 q7 z! J" Z
2 _- a) N: b3 ~% j8 y: {8 M) hV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
3 G+ m8 G) a; {% I- \) d' a# q& S: {1 T5 @5 d- B
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
% T& `, T7 `" z, \$ a2 ]
) v: t! ?" w" Q; e0 Q4 `# cV7-051_串行SPI Flash W25QXX读写例程(中断方式)
: i1 N& e7 y3 s4 ]- Z
9 p! X1 e) Q3 x实验目的:
( ?4 S. q5 x$ R$ w3 b/ ?3 F3 T+ F. p* t1 ^6 x% {
学习SPI Flash的读写实现,支持查询,中断和DMA方式。: ]3 f$ v# M  v5 P; [9 O: h

) k! b/ o# L7 _9 v; v实验操作:9 H0 f5 n9 C) A1 e) ^5 M
& G, h9 J+ M  A, ]9 }3 g3 L, x8 Q
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
" I% c/ N5 L" |6 Iprintf("请选择操作命令:\r\n");9 p, g) @: h! B
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);- U* g$ w2 s- T2 F/ q/ N/ D+ O
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
  z8 J% r7 U  \; f4 j- p* A% Pprintf("【3 - 擦除整个串行Flash】\r\n");3 A9 A$ E- U& K! h( j9 ~# x
printf("【4 - 写整个串行Flash, 全0x55】\r\n");" p, t' A: t9 j3 D
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");% x( U2 ^4 E' U" d9 i9 S( X9 @& ?
printf("【Z - 读取前1K,地址自动减少】\r\n");
/ s$ V0 W! e2 E  F0 a7 wprintf("【X - 读取后1K,地址自动增加】\r\n");
# o: ~% S0 k# ?& lprintf("其他任意键 - 显示命令提示\r\n");# E$ W0 i7 A, ?  z4 }- T1 ~
上电后串口打印的信息:
+ J2 ?; k, F# D) J) t$ f# e( J6 M! w
8 G! l) J6 G5 F! q2 |! x1 j" d波特率 115200,数据位 8,奇偶校验位无,停止位 1。, ]% I. |: e' q5 i6 |& D* l

$ F1 C7 X9 \" x( f: W& I5 G& F1 W
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 L7 h+ B, E. _. Z2 }
! F( z) Y  M$ K' R9 [0 e3 {
程序设计:! m1 ~/ k& d6 S' I$ A

" D* n# _8 B: L& A3 a/ e  系统栈大小分配:
6 B  S7 ]( s- z2 b9 E* L6 _. D9 G% I7 l
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

! a! H0 N7 i: T2 A
' j$ I; x- X7 ^5 |7 U4 J) x. J  RAM空间用的DTCM:7 S' J" Y0 z+ v) K; Y  }( E4 ~0 X3 ~5 d

4 M( U: y, d  r, P+ V) T
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

: ]4 _" W) j/ v5 r; N& D
  q- V! A; ^* v  ~  N, Q  硬件外设初始化8 s& V/ G4 I/ U0 C5 r
, l& J* L5 n6 g+ D1 S
硬件外设的初始化是在 bsp.c 文件实现:
; F! s- y+ h& }9 j
0 h) S1 N* w0 y2 e. _
  1. /*
    5 W, I8 k3 Z. b; [/ p
  2. *********************************************************************************************************- A, s8 v& M/ S% r1 U, n
  3. *    函 数 名: bsp_Init
    ; Y$ Y6 n3 h( _% F3 A
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    $ G9 v" N7 y/ S$ U1 ?
  5. *    形    参:无: Z( q1 t( t( F6 ^* X8 {
  6. *    返 回 值: 无
    9 p+ r5 K. \' W! h3 E) }
  7. *********************************************************************************************************
    5 |' t$ T6 `6 h- U! A1 o" a
  8. */
    * X: [( U$ X7 S3 U) j% |$ X
  9. void bsp_Init(void)2 Y  @+ u4 p5 [2 T; L* N
  10. {
    2 b" d) Q' T5 d& o. z
  11.     /* 配置MPU */
    3 K" w3 B7 Q2 v) q9 _9 Y6 \" `, A
  12.     MPU_Config();3 S4 U/ p3 r+ ]
  13. : ^" r/ Q) l, L0 X2 i
  14.     /* 使能L1 Cache */) B" G5 C$ D$ j. N, y5 r
  15.     CPU_CACHE_Enable();
    ; ?/ J; Y1 Z; K. S+ p

  16. % ?8 R$ V% E8 _& S3 |# H9 g1 \
  17.     /*
    : b: U3 f4 C3 ^% R
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:, D* |6 S5 \* N* T& i
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    2 a+ h  {3 D# U6 Z
  20.        - 设置NVIV优先级分组为4。
    & L1 l1 [$ v/ F
  21.      */
    2 X, }0 Z9 {/ i5 |' b+ B
  22.     HAL_Init();" k+ R. I! q8 L5 l8 M1 L
  23. $ t9 f* j6 X0 j( J
  24.     /*
    $ \9 h& F6 c# h5 F3 W
  25.        配置系统时钟到400MHz- T& R* `4 ]& [" [
  26.        - 切换使用HSE。- X7 l) ], a8 c+ S  w& p! d
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    6 k2 U' ?( ~- X
  28.     */0 m# r9 f& \! L4 m# _
  29.     SystemClock_Config();$ k9 U/ P; l* t: K. w8 m- o* _4 Z

  30. # V) h! k  I/ @+ W/ T; C3 X
  31.     /*
    ; v; {# P$ x: \. W( m
  32.        Event Recorder:# K% j2 Y: j3 r4 O6 W% w
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。6 y. u, ?- o0 I1 ?0 l7 g
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    5 G5 n0 V& u/ L5 M6 j; f
  35.     */      a- Y- T3 m, L* b2 |
  36. #if Enable_EventRecorder == 1  
    , [. A3 o1 F! V2 y- j# R9 `
  37.     /* 初始化EventRecorder并开启 */
    1 g+ f" }/ ^; t* Z0 X
  38.     EventRecorderInitialize(EventRecordAll, 1U);! Q2 f2 y2 K" f
  39.     EventRecorderStart();
    7 v4 J1 E# {4 ?$ p3 M% V
  40. #endif! }" a# U# T# o
  41. 0 @* x) W$ T4 y# D
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */      
    $ i! ?' I: Y  a, z0 T  p
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    ; g( z/ Y& d; T5 r& T! d0 ?
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */# l+ y- }; f, m! U: _) N  t/ `
  45.     bsp_InitLPUart();     /* 初始化串口 */
    6 o6 M& d; Y% p
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    * w' t+ j0 Y. F; z: _
  47.     bsp_InitLed();         /* 初始化LED */   
    % _/ T- _5 q" w1 d0 b
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */- A9 C" f, }3 F- f( U, }' u

  49. / ~* Z8 o) n; C$ V
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */! A9 L' t9 s9 i4 i
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    & c, A6 v: S) t# j' L* ?* u- x
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */+ u# i4 t- w3 @3 R
  53. }
复制代码
$ r, Q1 m% I0 Y/ ?6 p
  MPU配置和Cache配置:7 l( s4 }5 \6 U9 i# l* a$ s1 j
3 ]- b2 q2 ?# G& V! J) M) `( h% L
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
- c3 _  z2 m) j9 t, {9 n; b" _9 x0 r) B. a" B" A+ n
  1. /*  g# ~0 [* Z. K! K+ G6 r1 f1 \! @
  2. *********************************************************************************************************
    4 j, @4 Q3 O9 s) _& H0 ?' E
  3. *    函 数 名: MPU_Config
    4 ?0 s1 j2 L7 P# |4 d3 Z
  4. *    功能说明: 配置MPU
    % F# ]; z' n3 d; M
  5. *    形    参: 无7 c+ C- [* h1 v9 g! T
  6. *    返 回 值: 无
    8 k, }' ]. m0 U+ \% ~
  7. *********************************************************************************************************4 w# t( l% V! N& [' Z% l
  8. */: }! k8 n2 B  `0 C+ }
  9. static void MPU_Config( void )
    ( S  j' ]: u$ c: E7 O& ~
  10. {
    ! q8 N8 p0 k5 t/ ?" @
  11.     MPU_Region_InitTypeDef MPU_InitStruct;& U: f1 C; V8 N; x$ R, ?
  12. & e5 R9 B0 t2 ~$ t* B; @+ Q$ x1 @* F
  13.     /* 禁止 MPU */
    ( L, r# Q* i8 ^1 h7 O% }9 f
  14.     HAL_MPU_Disable();3 g, L9 h+ q7 u% r& _5 D+ K. e

  15. , k4 \9 ^9 y8 J# D  i6 d/ S8 u) I
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */& [+ i( C/ S" Q; v. L- y
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    $ \1 M1 T; I- t! K
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;) Q; T8 g5 |8 ~) L9 I8 ~/ a
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;( ]! s9 K- W7 M, L$ L
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;) e9 w' t& P. ^& e$ b
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    8 t5 X4 m( M$ E/ E! n& @+ `; ^
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;' @% q6 Y. z! o, r. Z* Q+ Y9 q
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;$ _% _! ], @; _4 j, u
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;6 J$ x% B1 }% }
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;1 C0 `, o' s& f; Z2 n
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    7 A7 o% v$ T. J# {% B
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    . S+ F$ J; W; S( |4 V
  28. 3 z0 c: c5 ~! J# W9 N% }( S) x
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);1 L$ K. @% A  k' g- O; C4 w) ~

  30.   u% b0 J, Q/ L, ?0 _; W
  31. ) q' b/ l1 C0 V3 J# b8 m8 K" }* o
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
      _5 J* A0 R. i3 v$ Z' C4 A" Y
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    7 G; ]- P2 n  B+ I" G4 F8 `! i# u5 s! Z
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    3 o/ E7 @9 Y: Q' ?7 l
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    8 g! |4 j5 b$ R& k3 c
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;; |. G4 Z9 p7 O1 Z% t2 Y' ]( J
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ; Y6 p* y7 I+ p7 l6 p4 C/ S
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    $ }$ @; A& e, B9 o( M3 ]4 r8 ]
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;! {( v, P9 m7 p4 @9 _
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    ) n2 E* D! w2 Z& B
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    6 x# z8 M' j  c
  42.     MPU_InitStruct.SubRegionDisable = 0x00;8 a7 _5 P  W( d. z+ W
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;9 v2 _* |  Y5 Y' i" \, @: s
  44. # b& t: b* m. d/ f
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);/ o; C8 ]' Q, C+ a0 G
  46. " V! G" K# g. q  B2 \( W2 ^
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */! B5 a$ k1 E0 [' H( N8 B- E" K1 O- |0 c
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    9 Q3 u9 \* K2 |0 i% \2 S
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    - e. H+ t: l; @7 k1 K, j: o
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    + V# b- m1 B$ P" g
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;+ J4 k5 n2 o2 g2 k4 u( o& `0 F
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;- U- x0 o2 I& B$ c1 R
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;+ F0 v8 d: t2 s! M5 v  g
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    3 l, J3 N0 ?* q
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    8 v& @/ s! b- [& U: M/ U4 r
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;3 v6 K* B' n8 J5 g- P: v5 B6 g
  57.     MPU_InitStruct.SubRegionDisable = 0x00;$ h' O  z7 {& [
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;0 G3 y# H! e6 n

  59. & ]4 Z' [( f  s4 M1 f7 G8 s, V
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);$ s' g# W7 d$ L( s1 S+ j

  61. ; @9 N  @3 E" d" Y8 `6 t( I
  62.     /*使能 MPU */" J( X; _4 r2 J, I2 m4 `
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);+ h4 P( O3 C8 |2 A  b- V8 \
  64. }% X; n2 {2 u5 E; h& I* S" L, w
  65. ' T- o- H4 y# C% [$ W: Y; H7 j0 d! U
  66. /*. M. [: c" h6 T' W7 b! A
  67. *********************************************************************************************************
    8 \5 m- J2 t$ Y6 c6 Z5 b
  68. *    函 数 名: CPU_CACHE_Enable
    7 ?1 X( m' E: a% M% z) l2 j+ ^1 N
  69. *    功能说明: 使能L1 Cache  C! ]3 Q* ]6 u  Z; C, h& ]
  70. *    形    参: 无2 {- ^. A# O9 V, X# k$ L
  71. *    返 回 值: 无; ?' K2 l2 Z  \- u
  72. *********************************************************************************************************
    " m, E& q1 Q" X' d
  73. */8 ]8 [& g/ u" q' l8 b+ b9 M
  74. static void CPU_CACHE_Enable(void)
    8 X2 k. s) l9 b( K+ R
  75. {/ o$ {1 O- }" E5 g
  76.     /* 使能 I-Cache */% D0 w" D0 T6 k
  77.     SCB_EnableICache();
    % k. s/ F& y, D0 J% z
  78. 6 o8 \! R: e, n6 T0 P' L, O
  79.     /* 使能 D-Cache */
    " _" O! a8 T9 }& W+ N" ?" g  M
  80.     SCB_EnableDCache();
    ( H+ E# G  `5 _0 k; N4 O" g( Q
  81. }
复制代码

+ f- N) Y: L" c$ A7 y7 r  每10ms调用一次按键处理:+ G) p6 s9 Q" _. k9 t! L

3 ]! ?# _( p" ]8 J( \7 r# E按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
0 ~+ l( Y( }$ I& P8 z* G1 d$ C3 \# V4 n) x9 s9 M
  1. /*
    % K$ c+ W% ^$ i9 v- \7 X' Y
  2. *********************************************************************************************************
    7 _/ }( C3 x3 y0 `7 m
  3. *    函 数 名: bsp_RunPer10ms# F0 a+ }# ~1 e- X; v6 A
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    % D  l; D- p# x# j) L" n
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    6 N# J! D4 m% ?( M5 x
  6. *    形    参: 无
    . b$ @5 u7 d3 ~
  7. *    返 回 值: 无
    - S+ g. p7 t2 o  ]9 u% U& m+ B
  8. *********************************************************************************************************
    1 W1 p. C6 X7 r' T6 o
  9. */% R3 k2 S: j/ ?/ p7 ~. v
  10. void bsp_RunPer10ms(void); i2 O$ f2 _6 B  t
  11. {
    4 _. f# f/ f) N& w3 N% R8 C
  12.     bsp_KeyScan10ms();
    $ n" l. a$ g* v) O1 q
  13. }
    3 c: q5 g2 S! n+ V7 G' x

  14. 5 @/ R0 H! q/ y5 J
复制代码
9 E  `# f5 h* N1 U2 t6 R
  主功能:" P- T! r# ~% E/ b( `9 E7 G

2 [' i; n; y  r% X! B  H主程序实现如下操作:
4 w( A7 k" a* f! f6 p4 }
/ g" {* L0 v& e% O. x& m# L  启动一个自动重装软件定时器,每100ms翻转一次LED2。
) e/ i" f3 c' X9 ]4 s  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可! e) {. Y' B# U7 }3 n
  请选择操作命令:  P  U4 i# W9 R' H+ c4 u
  1 - 读串行Flash5 w2 s6 j3 f9 H! u0 Z1 c
  2 - 写串行Flash
  q/ w9 o4 j8 }. X) d+ @6 i3 c. R  3 - 擦除整个串行Flash& |: H2 _: z5 w- ^5 H
  4 - 写整个串行Flash2 `' T8 [: w* H) t+ V
  5 - 读整个串行Flash
4 R4 m6 U  c. I5 |4 Y  Z - 读取前1K
) s( L( i" z6 r$ _  X - 读取后1K
! M- @' m$ I$ X9 |
  1. /*/ q: G- i1 A' f. t7 o
  2. *********************************************************************************************************
    / D* F' B% f, v
  3. *    函 数 名: DemoSpiFlash- U  ^6 {2 c) a1 @
  4. *    功能说明: 串行EEPROM读写例程0 Z; R8 R5 d7 g. v& u
  5. *    形    参:无
    3 |$ ]" a: G- T3 b, ^& z9 ?6 C
  6. *    返 回 值: 无
    0 G* ~$ |; Q9 j5 b/ E9 ^
  7. *********************************************************************************************************/ A5 l( `& _* H5 a+ w
  8. */+ d4 \# H/ v% M' d+ b. M2 l- }
  9. void DemoSpiFlash(void)
    9 A0 ], C7 U2 C: l. W- M) A8 l
  10. {
    ) Y8 x" _- [. H% U, z" s
  11.     uint8_t cmd;
    ( c( C- T: i# L+ C5 V
  12.     uint32_t uiReadPageNo = 0;
    ! c4 j% G( b, M; `% e& J
  13. * m1 z: }' J& @. u
  14. # q  q7 R. t4 }/ e* u
  15.     /* 检测串行Flash OK */
    ( D3 L6 L* Z# L% |# {
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    8 v6 o# h% J! s
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);# v$ e6 M. N$ k1 u& K2 i
  18. % B# V/ |; `- p% j# Q
  19.     sfDispMenu();        /* 打印命令提示 */
    1 Y- R7 P# b1 ]# s

  20. # x8 h9 B9 [# J9 {! v
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */1 `7 J/ e' A. C8 ~, P

  22. 9 ^* D6 d1 M+ Y) v8 o
  23.     while(1)+ }$ a; ?$ _. S# N6 v
  24.     {  J& g  D. d+ Z9 a, P
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    8 Q$ @# e7 G  S: ]9 x

  26. 8 f; v% A( R- S. W
  27.         /* 判断定时器超时时间 */
    ; X0 A+ l& l# o3 C" _
  28.         if (bsp_CheckTimer(0))   
    5 K  o* L- Z! L% `
  29.         {
    ! B& `$ P$ V9 M, R3 \3 G3 g. p
  30.             /* 每隔100ms 进来一次 */  4 {7 F  C$ {! |2 n6 ^9 ^
  31.             bsp_LedToggle(2);2 M1 g; v9 E/ n  [+ m2 j( S0 z
  32.         }1 U# Q$ m- O' g" S8 w& W. i

  33. $ P0 B9 G: i+ {% `! R( C- j* ^
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    " V. ^5 e9 D& i5 k1 V5 r
  35.         {) e3 J  F; e2 o3 F+ K4 c
  36.             switch (cmd)
    / M% W4 k; R0 O) C( I, V
  37.             {
    ! |' Z9 t; l# X
  38.                 case '1':
    2 m& H' ~" [. L3 @
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    * N4 h% v  R1 Q0 B
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */! c9 S4 a0 N. T$ Z8 C# v
  41.                     break;2 G; I* ]& x, a, u/ o

  42. 3 b- [; Q5 L: [1 v5 Z" c3 `; J
  43.                 case '2':
    $ _/ @6 u3 o* t( Q. C
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);9 U; p  l" Y" Y# z
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */) F2 [) b" X) ?: d2 }3 R* \
  46.                     break;
    & T* ]3 H- U; f3 [; o/ n5 p

  47. ' e/ X: j) h8 i$ W
  48.                 case '3':
    9 U, P0 r' n5 f! L& M" j" H
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");8 m: q% f5 K$ s9 V+ w- a3 h0 i6 k/ O* B
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    1 W5 L; T( E" b/ T4 ~7 S
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */4 y1 I5 X  f5 P- A" ]
  52.                     break;
    7 d, Q, v# d1 m

  53. # [" q" g8 W6 ^% x* u+ g+ q
  54.                 case '4':0 m1 W- g# c& U0 K# \1 ^
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    - [) C  {8 z/ H% ]: W+ Y/ W
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");5 Q3 Q" G9 g# L! C% `; f  C# ?# q
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */
    : X) u. S' E  }# R
  58.                     break;
    2 u# d+ U6 _; `6 |' `. I+ \

  59. / R. w7 N  S4 y7 N, s4 p0 ^
  60.                 case '5':8 w8 Z# H8 m* S( Z
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));% n* F' w, y: w% F& D+ G3 N3 {
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    ) x( b" u  Z" ?/ g) ^
  63.                     break;- d7 S6 D2 C" l6 [
  64. + e: N0 k$ ]7 J: q2 U) @' q8 d2 e2 n2 M7 B
  65.                 case 'z':
    7 U1 s: d3 N5 ]
  66.                 case 'Z': /* 读取前1K */( M; E- L) Q; a. U/ ~
  67.                     if (uiReadPageNo > 0)! i5 U* g' n2 O! |* u
  68.                     {
    # H* R* B+ j3 `4 l+ ~
  69.                         uiReadPageNo--;3 u: H+ t9 L; Q* k  Q' w
  70.                     }
    1 W8 j' n4 C7 L) ^
  71.                     else
    ; p/ ^) {* ]) i; @. {
  72.                     {7 g% @3 l; [& W2 A
  73.                         printf("已经是最前\r\n");
    1 s) @" O* W" C2 S
  74.                     }
    6 U: c+ s. i9 f3 N5 J. K
  75.                     sfViewData(uiReadPageNo * 1024);
    + l% s0 G4 N9 {& Q8 a
  76.                     break;
    * ]1 _; V* }3 K* S

  77. 6 N3 X7 Z) d  {6 y8 w
  78.                 case 'x':# w% U  Y/ Y- R: w7 W8 @1 G
  79.                 case 'X': /* 读取后1K */1 d+ I6 W) O5 ?# H) {# d
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    9 S/ H6 p$ y4 G: ^
  81.                     {, F) \. ~# L6 e2 ^6 N
  82.                         uiReadPageNo++;
    ( S+ [6 F0 d9 F0 P; c# G" D
  83.                     }
    ( O3 k1 Z( ]0 R$ @
  84.                     else0 h, ~6 U3 ]7 O4 K! C& P  O
  85.                     {, h9 s3 ~& R9 g3 I9 j* C4 `9 E
  86.                         printf("已经是最后\r\n");1 x( ?/ A- U% |& O, V
  87.                     }  @, f; Q# I$ v* q$ D: h$ X
  88.                     sfViewData(uiReadPageNo * 1024);" Y1 k! |6 K" `6 K3 X! f9 {0 p
  89.                     break;
    + @3 o: J0 [: W: f( M
  90. 7 h  @0 J# ]1 M* l, A
  91.                 default:3 G, d, {7 }) `# W
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    0 y- ~5 W; Y; d; g
  93.                     break;4 ^) x+ J2 D; y0 D& V
  94. * R. Y; h* |. a0 t
  95.             }/ W: F* }& ^+ E, d5 k: A
  96.         }
    0 Q/ o! q1 |8 _( Z: @3 c! h8 {* Z
  97.     }
    , @0 \% C( ?( E# O
  98. }
复制代码

6 S2 q1 ~: M6 Y1 v1 t. ]) H2 V73.10          实验例程说明(IAR)

3 O' W8 l) P' f& W$ g配套例子:
% g& ~, c1 Y+ ^
0 m7 M/ y# U9 W, e# OV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1): a1 g6 G: ?/ n9 u1 P5 h/ b
, W( [5 ?1 Q$ a; ]
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
& s1 p& O$ C* @1 v7 a% c4 m* e: g
+ L# l; }# E+ OV7-051_串行SPI Flash W25QXX读写例程(中断方式)" z1 j* c) x4 c" o
. B5 g+ p3 v* c
实验目的:
1 }/ z. f; E4 g! @
* t* D/ ^3 K; q; s2 y5 a学习SPI Flash的读写实现,支持查询,中断和DMA方式。
- ~& A$ O8 a* _" }$ P# l: N+ n7 ]4 P+ V$ L8 D. Q+ T: \
实验操作:
) t' Y- c; I# `9 h7 A% ^# m/ ~% @, n- s: [- p$ V7 L
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
5 r& ^- i) Z% w- B* @! N5 Mprintf("请选择操作命令:\r\n");2 a: z' ]9 d5 M' s- @4 y! x3 X' x* R2 E
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
" ~  }- W; i, o) F8 Mprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);# M8 C% b9 \: Y, L
printf("【3 - 擦除整个串行Flash】\r\n");
; F4 j5 w. p1 f4 _$ \printf("【4 - 写整个串行Flash, 全0x55】\r\n");; E% F& x* K, D+ V/ E5 x3 g: i7 d
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");" D1 |" X. Z# U9 F$ X: `
printf("【Z - 读取前1K,地址自动减少】\r\n");
7 G0 W( e- m# w! T  q8 F4 Z+ oprintf("【X - 读取后1K,地址自动增加】\r\n");/ l3 x* x, c& D1 }7 T% r7 G
printf("其他任意键 - 显示命令提示\r\n");, ]' E9 A7 T! l7 f
2 ?& c8 A" v2 b
上电后串口打印的信息:/ |8 Q7 i. K: R

0 [4 F# b0 G% v4 k" _& g' d% }波特率 115200,数据位 8,奇偶校验位无,停止位 1。- ^5 G4 R+ z* Z8 o; o$ s1 I! U2 D
1 k8 b% F; j6 T8 X( p. A
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
, U% T9 K+ j+ k5 b4 M

( C5 j/ |8 l. b& ~& \& }, X程序设计:. j( \3 \# H- H" w1 w

9 R' L, E9 p/ @; _  系统栈大小分配:
$ F. H1 Q4 K4 }8 @5 }* i
4 R7 d0 @0 E+ u( K* \# U# i& g
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  B% x" J# h5 Q6 G

, C( c! Q: ?' S  RAM空间用的DTCM:7 r! L# f9 U7 i7 |* |7 U, D

$ R/ N6 C- N: I' x8 j5 m- ~
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

8 W* j( V1 v  r" w
  t; B& T4 V4 [2 O) G$ o5 l* d  硬件外设初始化  m" M4 B$ b. t' H

* B4 ?- F/ n/ B2 l7 ^硬件外设的初始化是在 bsp.c 文件实现:' n% o! U- r$ J
2 A( o( Y' R# B5 E
  1. /*7 M7 s" M+ D+ k; P. w+ `& v" ~
  2. *********************************************************************************************************
    4 n8 {; A8 C! X1 x
  3. *    函 数 名: bsp_Init/ t4 A: l1 Z) J( H
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
      A* `( b) ^, R. ~
  5. *    形    参:无$ o, q8 l+ X8 j4 P0 _; F
  6. *    返 回 值: 无; G; ]# W0 j& C" E7 b2 f
  7. *********************************************************************************************************
    ' E* ^1 ]% p5 i2 [0 z( ^$ L
  8. */
    6 U; p' p2 Q  {! ~  T. O0 q
  9. void bsp_Init(void)
    . W. p: ?6 w/ k' w3 r
  10. {
    ) l  c% [6 R- I( S
  11.     /* 配置MPU */4 o, ^! Y7 |5 h2 [: |
  12.     MPU_Config();8 \8 @- s0 l# A0 a, d
  13. 8 V7 y+ W: M. `, D7 |
  14.     /* 使能L1 Cache */
    # h" d) h: H5 O" _" h; @. g( o
  15.     CPU_CACHE_Enable();9 M3 V5 H& J+ K  S$ ~. A
  16. 9 C. f1 V9 T# W  I7 t' K
  17.     /* + n7 \# y6 o  e* \0 g
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:4 B8 v1 Z* U- J& ~
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    , a# A0 w9 m( J& V
  20.        - 设置NVIV优先级分组为4。
    ! n6 P3 Y) ]9 [3 R( l6 Y( _; F% v
  21.      */
    # T& A* T  m: K, ]/ u
  22.     HAL_Init();9 r8 `5 Y- g' \/ T' H' S
  23. $ b; G7 O5 V4 I' s5 t
  24.     /*
    1 t0 `$ T+ v% f2 U$ F
  25.        配置系统时钟到400MHz
    ; d  P( h2 S7 z! p8 b
  26.        - 切换使用HSE。, K* l& K9 J  N0 M
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    " q* ?5 E, i0 t$ k) \
  28.     */& p2 S0 p$ h( S
  29.     SystemClock_Config();5 _! a6 \1 u6 c/ g; I

  30. " y6 H* _0 {) {! f
  31.     /*
    0 T/ V7 C, b. f1 G( [( c# f
  32.        Event Recorder:- }2 ^1 d9 R" G; ~2 ?0 E( i8 k
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    . G; u  b+ n( J  e7 ]" _: u
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    * V% v* I- V$ s
  35.     */    ! k: u# `) U. u1 Q# f6 V' ~
  36. #if Enable_EventRecorder == 1  
    5 w9 |0 {% x# |$ [; T' d% N6 A
  37.     /* 初始化EventRecorder并开启 */
    & x6 y9 o( A9 {0 G8 D
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    6 }4 {0 }! x( g& S0 p4 S
  39.     EventRecorderStart();+ d& P! A1 M1 U3 y4 M: W9 O
  40. #endif( ^) P1 V& h+ C2 a) d+ [9 Y

  41. $ l, Q  q. K  o: }
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       ( A+ W5 l* ?) V5 t7 I) I
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */. I* {! v1 F. C/ j" [
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */
    % s. k& P8 Y0 \
  45.     bsp_InitLPUart();    /* 初始化串口 */4 @% j( v, H0 h1 Q* X0 _
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    4 H6 C( e. y. J* ~0 M
  47.     bsp_InitLed();        /* 初始化LED */   
    1 m% |# D1 w6 r+ ]* ?5 s% q7 y
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    + B: Q$ K/ p# f& u. ?8 j

  49. - ]" T& }: U/ J5 j; u/ y  J1 s
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */# n, ^% C: s) j5 R" J
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    9 F+ {' H  c, R# a* P6 X, w
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */0 J* w0 E% s$ J/ E9 B8 m
  53. }
复制代码
5 h: O4 g" ^- v7 h5 R# n
  MPU配置和Cache配置:$ }4 t- u0 j2 K6 i/ e3 T" x8 h

6 j* m/ d0 h- l* o( x% S数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
8 s; G7 G) G8 Z- V9 `8 U; L/ m8 W0 `1 P* E4 {
  1. /*! m8 J! }. ^6 Z* g* G
  2. *********************************************************************************************************: z) p* l3 D1 f% v9 }. L+ _
  3. *    函 数 名: MPU_Config7 \/ ~( |, T) c& G# o1 E+ f. s8 s
  4. *    功能说明: 配置MPU# `  o5 E) O( b1 x* Y& W
  5. *    形    参: 无- }" ]; S4 h' Y/ O: z/ L0 R
  6. *    返 回 值: 无/ L7 Z# N- j, v6 A
  7. *********************************************************************************************************
    # F4 q. N+ `8 G4 [* p. A, T: J
  8. */
    + A/ A7 g  L6 K( I3 q0 C
  9. static void MPU_Config( void )9 w$ G" ]8 I) @5 t4 q
  10. {
    8 w6 \) L4 U# X& G# o5 a  B
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    7 U. i' l# w) n! W$ S

  12. , @+ y+ D3 O, s9 q* ^( S! Z
  13.     /* 禁止 MPU */2 q7 }' g: ?0 ?1 K
  14.     HAL_MPU_Disable();" l( R) H' ?! Q8 R

  15. , A: F7 w5 R& J+ I" {* t
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    $ o$ G/ U& a+ }+ C6 U. w9 t
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    * x; h5 `" J# v
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    + l& e7 s6 @; S, G
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    - A/ o( L2 y% F7 m
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ; R, n0 U" u) i5 A
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ) V% X% W0 p) }" Z) v, N2 a
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;! d( B0 B0 h1 H3 J5 e  ]! o
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;9 B' s1 ?4 r. \
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    & |0 g  X5 A$ A, ~0 Q4 x8 n4 A9 r/ J; F
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    + P" f: ?- l: P& v
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    ( M3 b4 j* D+ {6 u
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    - {- q2 W; f8 F9 d6 m2 N: t
  28. 6 |) I$ m) e# Z( ~1 q7 V7 I
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
      j6 w3 S7 Z0 O
  30. 9 C* Q! i$ }4 b# K

  31. ) }9 [. B+ H( Q% u
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    # J9 F0 v! `! |% B5 D4 f; ~3 n
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;, R* T1 J2 T) W: b" Q3 l7 j
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;5 s0 D( R, M7 ?  r
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    6 Z6 t! r  N) ?3 M+ @$ C' N+ N
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    $ I3 z; Q+ r& ^
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;) Q! _. N& }* g6 ^
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    0 ]. A. ]) x$ I+ P
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;. k6 |% n  e# b9 N: F" U* q) p7 e
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;& F& D" {" e. I7 I' c0 D  V
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;1 z7 Y7 Y: C: g4 q, q) W
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    - s! t) b) m; W( Y+ R
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;1 Y8 x7 a2 h* P5 G
  44. # W  h; Z5 N* q* G
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);) [- _: w# W& p  z

  46. % v4 D3 \& b5 i* j" q0 I) N
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */& e/ {8 K4 r9 c+ Y2 C
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;0 `* n5 C( V. j2 l
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ; j. I. ~, Y5 u# L+ C% g) J
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;" p, a! i9 `/ q' ~9 n: ^! X
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    5 P2 L' v! b( h4 m+ L
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;" r( R- g* P9 i9 F  G; V$ U
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ! L& u4 c! {, }* b7 l' H! ?1 {
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;* O9 F, f* A8 t4 {( X+ I
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;6 V  P# O3 X7 \+ k( d. F8 V
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    ! f# j: p3 r# W2 E+ p$ W: j
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    , t  m3 K/ a. ]8 }& Y* r5 q% r5 R
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ( R0 X0 B, Q% T4 g% r9 e

  59. & o# C- E' n' r' `
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);3 y/ \; q$ J  E& ?: P

  61. ) O7 g* p4 c8 _4 g5 _
  62.     /*使能 MPU */  H9 d0 I5 `$ o1 U. x9 q! @8 }
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);% h7 y" |8 @. A+ q$ N
  64. }
    " N6 l- G0 v% [, X6 O

  65. 2 C$ W# C) @" K, e! E
  66. /*; I/ `) E/ R& @3 t
  67. *********************************************************************************************************0 |0 G, y8 R9 F) F1 g. z2 G$ m
  68. *    函 数 名: CPU_CACHE_Enable! g% H$ `1 d) w. C7 o
  69. *    功能说明: 使能L1 Cache6 l6 t" T( M  g7 F& T
  70. *    形    参: 无
    * N0 y1 d$ d! [% g9 |2 S+ z( {, n
  71. *    返 回 值: 无
    $ c" J1 p6 U! @
  72. *********************************************************************************************************+ O& ~9 Q5 G. {8 g. u8 |# A
  73. */
    # _* M* v. _( a" t2 p0 d& T2 c5 p
  74. static void CPU_CACHE_Enable(void)8 _  m6 s, O5 N
  75. {
    7 D; K2 V+ h) i1 N1 Y
  76.     /* 使能 I-Cache */: k! W! y& ^2 V$ g1 B& t; p  s
  77.     SCB_EnableICache();% M. v( u* i% m" U' h" j

  78. 4 P' T( H/ a# Q8 }0 D3 ]& x
  79.     /* 使能 D-Cache */
    3 Z( y( p7 K* F
  80.     SCB_EnableDCache();: {8 T) O/ e  O* j! l
  81. }) v1 X2 {5 d) Z! u- ^% D' B" h3 _
复制代码

& S) x# b; [' K% K: M, q6 `; T! W' [  F4 q
  每10ms调用一次按键处理:
5 D' h' e* j0 X* f3 R
% o- t8 D% o: v6 G. ]2 _按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。. b; |% z+ ]) P5 b% `+ p. Q9 K
8 {/ x( {6 Q4 w0 H! u
  1. /*
    " x2 F7 a( C' K& x. _9 ~4 S
  2. *********************************************************************************************************. P* ?' \; w$ R% R
  3. *    函 数 名: bsp_RunPer10ms
    " c8 M; u1 P! R% ~
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    * P0 Y2 m$ D, c# Z9 L$ P8 s
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
    - I/ Y+ H' a, X# ]3 v3 V
  6. *    形    参: 无/ f  `7 v4 g7 u% K# l, D
  7. *    返 回 值: 无" @4 d  m8 D0 w6 i: m; ?6 N
  8. *********************************************************************************************************
    . ?/ f  ^0 |! A& _5 b
  9. */
    + S! l6 J) A% f4 ]
  10. void bsp_RunPer10ms(void)
    ) x& h! l$ K% N: c( x% ^9 H3 c; D
  11. {
      q. w7 }  W$ `+ ^& |, h
  12.     bsp_KeyScan10ms();% K; v+ l3 b/ f/ B+ `. }. G; P& o- D
  13. }
复制代码
( M& [% J! {5 t8 D6 h% {5 u
  主功能:* e0 J1 Z# }, M1 ?* g% Z- @8 A
5 _( c3 O: q: n2 @( b! W
主程序实现如下操作:* h/ u3 e- p( W* ?1 [, i
; q4 v2 Q3 R9 _5 r8 V+ r
  启动一个自动重装软件定时器,每100ms翻转一次LED2。6 U* \1 Z; }9 a8 F& j! F- c8 S
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
' f1 d, |3 i3 a& a; H1 l  请选择操作命令:- p1 ^( I( W. u3 ]) ^% _
  1 - 读串行Flash+ H) l8 a+ Q' J% l" Y
  2 - 写串行Flash
' o9 C8 M7 \6 J" s, H  3 - 擦除整个串行Flash6 _- x5 v8 M- {3 s1 G
  4 - 写整个串行Flash6 j' q$ Z. P$ _; c
  5 - 读整个串行Flash8 d% @% r' h# N
  Z - 读取前1K
% M! M0 u% z7 y3 o7 _5 k  X - 读取后1K: o3 u) Y2 K8 W+ ~2 m9 I
  1. /*
    ' Y# M: L% r" d; ~0 B1 R
  2. *********************************************************************************************************/ W* p9 ^2 @5 ^" B7 x7 t, P
  3. *    函 数 名: DemoSpiFlash( }6 v* g+ Q: r% d/ r9 s  p  k1 k
  4. *    功能说明: 串行EEPROM读写例程
    & [: h: r) K0 K: T# A8 f
  5. *    形    参:无* Y8 G- g" ^# t* m# l
  6. *    返 回 值: 无
    2 o7 j  d4 U: z, J1 \3 m
  7. *********************************************************************************************************
    7 H2 N: v( L. ~, i  W: b
  8. */
    8 v' W3 j: z7 |) S$ r
  9. void DemoSpiFlash(void). J1 t- F! ]4 m3 A# V$ I
  10. {
    5 t2 g" T& r' k9 m+ e
  11.     uint8_t cmd;- x( u3 q" W* ~; V4 ^2 {
  12.     uint32_t uiReadPageNo = 0;  ]' d' l8 {0 Q3 h# ^$ S1 O

  13. 5 J$ C5 H9 u# I. `9 b
  14. 8 A1 _7 B: [; `) }8 c# R
  15.     /* 检测串行Flash OK */* _1 ?; Z! S. X4 C- i! D3 n
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    : y! r& v4 \' l4 [
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    1 m  w, H9 Y; S0 @3 @
  18. . S: j6 Y  G+ }( M# P
  19.     sfDispMenu();        /* 打印命令提示 */" \: [- p- X# U: A9 Y
  20. 9 O8 ?& H* Z6 g- ^  |% b
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    * F/ z" X# t: X5 l/ |: I& ?

  22. : s# h% v! g+ H
  23.     while(1)# F% N% |" I  q8 }+ |9 F6 J) ~/ I
  24.     {6 ~. G* ]& M; J
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 *// W* T9 H4 S- B7 y

  26. 3 e1 \$ S1 T1 K5 }! H
  27.         /* 判断定时器超时时间 */
    4 t" w0 s# X8 L# p, E
  28.         if (bsp_CheckTimer(0))   
    6 f" p' q, x9 j* q5 ^+ p2 l% H
  29.         {
    2 [1 B/ @( F1 r. X- f
  30.             /* 每隔100ms 进来一次 */  
    & [2 e: q9 x4 e2 `2 S( U" E
  31.             bsp_LedToggle(2);
    . u* v& v, A" K% [" F  A2 x" v" z" c
  32.         }% Q# Z3 i, ]) ^
  33. 7 s, F+ c5 W$ [8 @# T( a
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */" @% ^5 v  F2 r. {7 f
  35.         {
    5 X; S6 s7 f$ l& V: {
  36.             switch (cmd)" i( E8 G  X5 M: O
  37.             {# d" h, U. g7 m* \6 l- U' |
  38.                 case '1':  z1 x! m3 t, l* W4 [+ U  E2 ^
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    7 r6 c) B4 N: s
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    % l7 A2 n, y4 S
  41.                     break;1 m1 ?5 U6 g$ z2 R" u
  42. ) U; T' Y: m2 p+ y7 W
  43.                 case '2':
    9 e6 Z2 ]$ a6 E+ G" U4 _9 `
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    : c. ^7 q5 X. K- I& Q9 ]
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */: z# P6 {+ c% t; Y, S
  46.                     break;
    ! w! W2 C8 P& J5 Y$ \( R

  47. + t' B/ |& x, V6 H) \6 E9 U0 Y
  48.                 case '3':
    : }  t5 S8 w0 ~- F9 u  t/ |
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    + F1 q" W2 n0 `  W6 u
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");5 F8 T0 q: W% G: l' h: Z
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */2 C2 C( {  l& T7 f% K  N
  52.                     break;4 c' e1 F3 Y2 A6 a% S/ P4 t% M

  53. ; }( I3 U. S" p7 v. q, B/ ?: R4 r
  54.                 case '4':
    ' e, f( \2 J: S( w1 r
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    2 J8 C- r' D7 \- I
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    7 D& G5 }+ Y! L  t3 L) z2 {
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */* F) P7 W: \$ ^2 S) R8 f+ U
  58.                     break;
    , w# n1 }7 t9 {  y1 |& g' p7 Y
  59. . b& t. e, B) R* t" R1 s
  60.                 case '5':
    , w! M2 Z) @, Q
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    4 y# Z+ j& `3 Z! S5 d+ t2 L
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */+ E& X' N3 M' k% u# V0 t% y
  63.                     break;
    . n9 N3 J+ Y. P' D% D

  64. ! r! L! Y6 C* K1 G1 V0 _1 O3 h, f
  65.                 case 'z':) D( O$ F4 o  N/ {) `) y
  66.                 case 'Z': /* 读取前1K */" m" N" k) l  ~7 V( D+ |% @
  67.                     if (uiReadPageNo > 0)0 w. x( H7 f' T2 V' j. Q1 b. Q, c" d
  68.                     {
    & l) r7 X' W9 f0 a1 @9 V
  69.                         uiReadPageNo--;* j$ X4 P" O0 `# g' r5 Q
  70.                     }7 L$ ~  k# J0 e9 e  q. i0 \
  71.                     else
    9 N8 u7 C% Z5 ?+ U& O$ t3 E% a
  72.                     {5 W& z9 u: `/ L/ o# l
  73.                         printf("已经是最前\r\n");- `# }, J& G& S5 C. }
  74.                     }
    5 A9 r2 ~  A4 @8 ~
  75.                     sfViewData(uiReadPageNo * 1024);6 _# y* |7 q- [
  76.                     break;* E1 x3 M1 n6 f. ~( g. [

  77. ' c0 e! D& l, L. P4 o( x1 m1 e7 X* H4 ~
  78.                 case 'x':
    3 l) M, w; f$ b. x/ a! O
  79.                 case 'X': /* 读取后1K */' ~* N$ c7 N8 }- J8 U7 c4 o# @8 m
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    " o* |% \+ P2 P2 [, C: @
  81.                     {$ {: f  I) a( \; m/ G
  82.                         uiReadPageNo++;$ ~  @  j) Y6 Q- H% N
  83.                     }! ?# |$ c1 W5 D1 U6 x# |
  84.                     else
    + J+ d) ~+ m7 [
  85.                     {
    1 S# o! c/ D" o$ {+ ?, \2 ^
  86.                         printf("已经是最后\r\n");
    5 Q, B! @% a% S3 l  ], J: o( X
  87.                     }( [/ ^" K; @5 |' v& Y4 Z" J# b
  88.                     sfViewData(uiReadPageNo * 1024);( d4 j9 T4 i! W( f
  89.                     break;8 j5 [6 K; N/ N3 X8 j% L9 b
  90. 3 L& S/ o2 {. }: }& u
  91.                 default:
    % O- X& H1 ?; D( G% Y4 x
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    ; F8 Q% O- P/ r6 S
  93.                     break;9 \: `9 l! L1 f
  94. ) S' P; k$ C" f( {
  95.             }/ e  ]) }- |( I. f# a3 Y
  96.         }
    ; o5 U0 r7 ~8 Q; a" l
  97.     }* L0 q8 H# ]" H2 G$ l; x; F
  98. }
复制代码

' A$ e; w% \2 a# Q7 N' }1 m73.11   总结' p4 z3 {3 \% @: I7 n, j
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。
( l& T. `2 n( m# f$ I) I
& \, [: p5 A/ z* J+ i$ r+ O
& N2 x& \3 H2 K: m" _. A
9 z: U  U+ ]. Q' G% M: ?0 l/ y
收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似分享

官网相关资源

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