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

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

[复制链接]
STMCU小助手 发布时间:2022-1-1 21:00
73.1 初学者重要提示1 Y. _' K) y: `' @0 ^0 v3 C0 M
  W25Q64FV属于NOR型Flash存储芯片。! v" ^+ f) d8 E9 U; f/ Z  g5 K
  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。
% S* ]7 b( e4 y# d: v
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

9 |- A( A0 C' `
6 {& x$ u( e% J6 ~6 P# m$ N2 D- o  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。/ {, |' C8 C0 L; V/ E6 T) B: q; C! C! h
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。- {$ G' K- v+ Y) M! d1 Z7 W1 ^8 t' P
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。
" {. g5 ^; t# m2 o8 x& w  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。2 f" j: x+ z, ?" n# [- r
  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。
5 b2 t; b, n- M" O4 Z# `
) }/ q) m1 w( T5 S$ n73.2 W25QXX硬件设计) P0 x1 }. Y7 n$ Q; S& {1 s0 J8 C
STM32H7驱动W25QXX的硬件设计如下:
. D; l1 B. v7 L) ~7 c& E7 j) @& ^! c$ ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

1 o! m0 O; o# F* q/ w4 e9 q
# _! u0 q: G6 C关于这个原理图,要了解到以下几个知识:. D# j2 I8 X, s- T3 F
7 s: d+ c1 W8 r' Q% L- s( [
  V7开发板实际外接的芯片是W25Q64JV。- t3 N6 u7 U  x7 a1 k' R
  CS片选最好接上拉电阻,防止意外操作。
5 U& V' [* P) Y& q9 @  j0 ]  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。
1 R' |& |' o5 J) D6 ]' u  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。6 K+ A4 i4 a0 {# h
  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
& M3 A" j9 i( W  r3 y2 A73.3 W25QXX关键知识点整理(重要)
& p- W, F+ E+ r% R1 o' _驱动W25QXX前要先了解下这个芯片的相关信息。
  t# k/ y5 y, [, ?2 J2 }+ j' J) o- l1 U# Z! g( Z' n8 k% W
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
. G) z/ d/ e  @8 d4 n
! f/ t+ Y! j8 v; ^
73.3.1 W25QXX基础信息
! R+ d" H3 K, q' y; {) X+ |$ M
  W25Q64FV的容量是8MB(256Mbit)。
8 }7 w! T$ w( [6 [% H  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
- T) D4 [% a/ o6 ^. [) g, n$ F! a/ [支持两线SPI,用到引脚CLK、CS、IO0、IO1 。, c" T+ c# V0 s3 U$ U

5 j8 P4 R% [' t' U, Q- b- t% _" L: h! O支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。9 O9 `& H. f* f2 ?. H& G
6 w1 O) {( I/ _
(注:这里几线的意思是几个数据线)。
$ f! D3 |$ u- s$ m6 M
7 P+ Q$ o5 [+ o3 W" b) H  W25Q64FV支持的最高时钟是133MHz。
6 \3 I+ ~" c& T- G9 w  每个扇区最少支持10万次擦写,可以保存20年数据。
! m2 R4 Q0 h  Q; A6 s  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。- T$ j7 u- ~) O7 C8 V
  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。  H# O6 s& _! F
整体框图如下:& v* R5 K$ _5 P  j0 R
0 f1 b8 ?- N2 L# k# I
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

; w9 j1 O. [' o9 R1 W# i( {" ]$ u+ G) I9 a; {7 j3 S1 T: k
W25Q64FV:+ R0 o  |  a2 n" V+ _9 |
% T. Y0 x. E" K0 l* t0 Q
  有128个Block,每个Block大小64KB。
0 t/ p" V$ I: f1 g" i  每个Block有16个Sector,每个Sector大小4KB。( H7 N8 v$ u* {/ ?5 u( g
  每个Sector有16个Page,每个Page大小是256字节。% K, ]7 p* v# j6 ^7 W
73.3.2 W25QXX命令3 D# D( S) `5 m7 l4 i
使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:& ]3 F+ x8 f) ~  I( n0 U
: s7 {& H0 V  m5 l7 ~& ~6 Z  f% M
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

5 S' ?0 q, [. i/ `
+ A( k1 s) `- ~) Y. g% d8 q当前主要用到如下几个命令:
* R- i5 d/ Y% C  G' P2 y1 r1 P4 A0 Y9 q+ k/ j& m
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 *// @% r: X. t; M% B3 [
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
    + x0 Z8 P( q! s$ E
  3. #define CMD_WREN      0x06    /* 写使能命令 */
    . \) ~1 z7 j7 c& m: ^" f( Z1 i' W
  4. #define CMD_READ      0x03  /* 读数据区命令 */
    ! L/ |: y; m3 S) M. ]
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */
    * w1 Q) Y% v+ F# k8 _
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */
    " |5 _5 H  y; j( a5 N5 G  h9 R
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */
    1 e/ t  p7 S  h: b
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */
    : x" j/ d" B1 H  M+ U5 ^, g
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码

/ Z& ?/ b: B1 N1 I73.3.3 W25QXX页编程和页回卷. x. s0 X$ }9 v
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):  v& N% n3 v; s, \/ |" K4 k' D
! b6 M" `7 v2 q0 ^
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    % m, {$ \8 m+ H) `3 R2 C
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    , p9 d" ?" C$ f/ M: c) u
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   3 U, L3 b: f3 H* V: x8 T
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    # A9 C) J# u2 N2 q$ [9 v1 n1 _

  5. 5 q4 u, W$ B; F9 L$ K
  6.     for (i = 0; i < _usSize; i++)
    ( W/ R" \' @* O! c, C
  7.     {5 J! H8 A* c1 W0 Q) E
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,- ]1 }) j* \$ K+ V% U1 B
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    0 i4 z4 ?7 n$ _
  10.     }
复制代码
* I7 Y  W. z2 ~+ p% p; C9 g
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。: e7 P7 A1 A6 o. f8 M  x+ Y. L$ `
6 K: ]8 r7 j) t; F
比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):2 ?# v, c: |' z
0 ~  J( t/ y0 I  k
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};  c2 E& C* t1 L& h8 _5 M
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码

& V) A7 U  t! `( B  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
6 z2 V$ Q, D& ]) @  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);
' [: q. N! Z6 @: f& _- A+ _  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);
$ {+ ^( p. {" }1 r; |8 r  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
% g; L8 ?  R" l: g9 @" I下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。0 }, m: G/ P  S& a. y  P# r0 c. F

3 r- s  X- L7 ~, U
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
0 W- ^% g7 r4 T: t; v
5 w" t9 i( c0 @3 r+ n  b
# W- m4 N) @8 ]! q7 t7 C
73.3.4 W25QXX扇区擦除
% s6 T! S# Q6 D& l, N* P. ySPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
7 u, ]! O9 [* Q3 }! l$ b) Y+ L3 H, r1 H# ~1 K, `9 E/ A
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
& A' b- W2 T* f3 p+ G0 _+ s* ~* Q
& v# K, u/ s7 l3 z* j2 ]# `73.3.5 W25QXX规格参数

& V: K/ T5 I0 _; \( P这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:/ q% q6 q( Z0 O4 {5 `4 M( U- y

1 K' U) _9 }+ ?* N& j3 R
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

" _1 k" b( u$ b1 N1 g
9 J6 J2 ?' ^( y  E3 W7 ?8 c  页编程时间:典型值0.4ms,最大值3ms。
: g5 [8 ^, f, z0 u) _/ S. g  扇区擦除时间(4KB):典型值45ms,最大值400ms。! `9 Z: y$ v2 b8 U3 E. K# p- a4 ^
  块擦除时间(32KB):典型值120ms,最大值1600ms。% v5 Z. M7 j) x
  块擦除时间(64KB):典型值150ms,最大值2000ms。' ^, G( f/ L+ G
  整个芯片擦除时间:典型值20s,最大值100s。0 ~; K8 S5 M9 A

) R+ e. B% T8 ]; B
0 W8 D8 `3 B  g! o" P支持的速度参数如下:
9 n% R7 ^/ T9 |+ s
% a" _$ W4 m: ~7 {& F+ F- y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
% S. q+ g; d% H  S* O& E
* [' E: Q- ]+ t! G7 K* H
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。% E) B- q% a6 x- p, p

1 x3 p0 |8 t- r' a4 y73.4 W25QXX驱动设计3 H( r- b+ ~, j5 [: M. R- J; A5 v
W25QXX的程序驱动框架设计如下:
( [7 b  X" q" Q
' G; M- D9 a1 x; y- E  R0 T
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

9 ]* x3 Z: \9 n2 {! e6 b9 p
; p1 }! L; g: o% j3 I6 `" m有了这个框图,程序设计就比较好理解了。
3 n" `( a6 }( o" S& f# c, U* z
9 W+ N" E# g  X; ~* i5 g( v) c73.4.1 第1步:SPI总线配置/ U+ m% F9 r$ e: ]; N* d
spi总线配置通过如下两个函数实现:
- w% @& ]% _* Q4 }# ?" I  K! F/ C" F) {6 t
  1. /*4 \- k* R6 L8 P% q
  2. *********************************************************************************************************& a6 s9 Z5 r# O7 H5 d  j* ~# l
  3. *    函 数 名: bsp_InitSPIBus
    6 O. L- g4 H) l, K
  4. *    功能说明: 配置SPI总线。
    0 \1 S+ l1 G% U6 a8 Z$ U6 C2 @
  5. *    形    参: 无  k$ H" S# c% S0 A; w
  6. *    返 回 值: 无
    # c' m: @  l2 K/ Q. a
  7. *********************************************************************************************************8 K8 a2 b' e: O6 H7 F
  8. */# M" \) G) {; z1 v7 W) Z! R1 t4 u
  9. void bsp_InitSPIBus(void)
    & J4 Y: B8 h( M  q7 o* M
  10. {   
    & [, e/ l7 W$ g: X9 d: m
  11.     g_spi_busy = 0;+ y6 ]! N4 O! @( n3 A1 t. R3 Y& W
  12. 8 s! K, w# u' k4 z6 g% s
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    3 ^( w- s  X( w/ m
  14. }5 c4 Z" J$ o- c: Q' R9 |; P( O1 v

  15. # ^8 G' t7 E" |8 q& F8 E- g4 u
  16. /*2 f0 F: _8 r% c
  17. *********************************************************************************************************
    3 t: e# o' _% a0 t* O% J/ g
  18. *    函 数 名: bsp_InitSPIParam( [4 b, S5 j1 w4 P
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。' U6 _( e" e3 y- e) J! ~
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:
    . o( c4 x1 F( ~: i+ `* g
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频) C* r+ L; N7 f% ~) p
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频" w& r7 j# `8 B" ~$ r
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频
    % c: t& s3 c% f/ P7 B& h0 `
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频
    / D. R* v5 `+ E" n) Y3 d: L
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频( `: t/ P5 u; L4 f
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
    + C$ M. ~4 }* W
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频
    5 S3 d9 B% S  v1 T
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频# ^- }, U1 O$ D2 S1 b
  29. *                                                        & U4 k& F: w. m" i
  30. *             _CLKPhase           时钟相位,支持的参数如下:& C1 Z8 g! C& ^+ Z9 x2 g: V" P
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据! b/ H# p4 d" ^* E+ H
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    % @0 U2 K: n$ C. {* N8 V
  33. *                                 8 T, t' F& p  o1 Z1 h" i5 ]
  34. *             _CLKPolarity        时钟极性,支持的参数如下:. S/ d  G9 f4 y- ~
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平# i/ {5 y4 }, I) G
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    2 N+ r0 f5 R* T/ L
  37. *6 Q, F# Z( H2 x0 H) |
  38. *    返 回 值: 无
    6 u7 I8 K* F1 W: v9 ]
  39. *********************************************************************************************************4 N* t' ?1 D% \# S5 f1 d1 z
  40. */
    , y: X% I4 P: N* o. J0 l, ?
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
    0 K# @6 O: f: K4 d
  42. {
    5 C6 h8 Q1 t$ [1 y! ]( I+ v: y
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    . n, e; X* ?: H/ r
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)& B6 i* Q; H6 r) |  h
  45.     {        + Q$ _! j9 _& m% x9 W& x3 a  O
  46.         return;+ f+ \. u; f( _5 ~) c
  47.     }$ j. r0 E9 ?3 P% J" W7 e) e$ W9 z
  48. " Q. j3 _6 y, u3 c3 f! s
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
    0 D, q- }! y" F. z9 Z' Z( R
  50.     s_CLKPhase = _CLKPhase;6 F5 O3 ]% C& U3 L5 S) e% B# L
  51.     s_CLKPolarity = _CLKPolarity;
    ' X- l8 n- C# e6 @6 V
  52. 5 u8 G$ N) ?1 [3 U

  53. ( F7 D5 z2 f8 k: d
  54.     /* 设置SPI参数 */
    * x; G- w* ~7 A2 ?8 {' R
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */) ^- z8 p/ P. G  _* I0 C8 l4 E
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    & @% [  \% \( p
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
    1 P$ y6 [7 v: a+ P# F
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */% s) k) f4 y1 d. Q/ B, o) f# a
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */0 |8 |2 n5 t. Y) J! C: P; z7 n" Y
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */. h% \- X/ L3 _/ ~: G% W
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */. @, Y& Q& e9 s: Q6 e# v
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    8 k3 S( m3 h2 p! O
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */
    # _$ `2 y% Q2 v3 l  R& O" J
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */2 }" J, D2 c7 E& t. j* V+ |$ s' M; n
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */
    " F) a) \) ^! z  C; q
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */
    : g7 |9 u3 h: E; J5 F5 ~2 `5 w
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */
    7 v+ L; C4 a4 e: f, `
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */( s+ [2 R5 i1 K: G( V$ x
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  + @% J$ k/ j4 r, r( k  E9 J
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */
    $ T+ ?8 {- J8 o' v8 w! N- ?
  71. 0 C5 }2 F; y" D, e6 }$ {$ \7 I
  72.     /* 复位配置 */0 l: v0 N$ }0 d
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)
    ; o( r. f7 Z. Q7 g  J# O
  74.     {( R7 Y" V) R( Z. F' a3 w2 R6 x
  75.         Error_Handler(__FILE__, __LINE__);
    6 R' e* ~4 l! a
  76.     }    9 G! e  s' h' o. \" M
  77. $ b& Z" a: d4 F7 c7 J
  78.     /* 初始化配置 */
    6 S% D3 L- A3 h3 K
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)- c! F7 E2 Z4 G/ z" t* P
  80.     {/ Y' Q$ A) e3 g3 p
  81.         Error_Handler(__FILE__, __LINE__);
    ; l, Y; e# i8 }. C( L
  82.     }   
    & H. q( [, @: x) _. p& K, @2 F
  83. }
复制代码

( q+ ]+ J; @2 f* ], Q9 s关于这两个函数有以下两点要做个说明:9 ?9 V' T3 _  j" C
9 }$ Q, o7 r* i
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
  ?# V" l4 Q/ R( E' n5 b' }' c  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。7 T2 y  Z5 q  e
73.4.2 第2步:SPI总线的查询,中断和DMA方式设置. o% g7 z+ I9 Z6 ?4 c+ R
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:0 y4 N1 D+ i; U* ]! J

% M. v! U" g8 c) _. l
  1. /*. L) C# s' W5 Z6 T2 r0 i! z+ B
  2. *********************************************************************************************************" I4 Z' u. l1 j3 R* w  w
  3. *                                 选择DMA,中断或者查询方式" \5 D, x, A  ^8 @8 E5 a, d
  4. *********************************************************************************************************
    " o0 q& k" m% D( r# |
  5. *// W+ P! }& X: A6 e% @$ K' Z
  6. //#define USE_SPI_DMA    /* DMA方式  */
    & t6 z8 |4 R. i& O$ J& `
  7. //#define USE_SPI_INT    /* 中断方式 */
    ( }9 @) Z2 e/ o5 K5 u/ O
  8. #define USE_SPI_POLL   /* 查询方式 */" U8 N! B( |7 S1 t- }3 C$ ^
  9. ) M4 C- j, C% l  L! t
  10. /* 查询模式 */; R. f0 ~, [$ {
  11. #if defined (USE_SPI_POLL)  ^" g3 [( o! X
  12. + U$ [3 N* E- B4 B
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    2 Z3 O0 W$ K( y% A: o+ g8 h* Z
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    ) g  x9 D( V  y0 X$ A

  15. 6 o& e2 h5 q7 E- }9 h) P. Z4 k( U% n
  16. /* 中断模式 */( c- E& @) g3 G6 T9 a: \
  17. #elif defined (USE_SPI_INT)
    4 j' N/ M7 Z+ o7 |3 M

  18. ; N$ N; g5 u- N( ?( G6 [' `
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    7 @8 X  }; n2 u6 L- @2 v" _$ p' Y! t+ [
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    * T) q( F8 ?4 U: I
  21. 5 b0 v  N8 X( E. Y
  22. /* DMA模式使用的SRAM4 */
    ( y; E1 p) g9 b  W+ L8 q8 o) W# `
  23. #elif defined (USE_SPI_DMA)7 Z& D" ^2 f; n. G
  24.     #if defined ( __CC_ARM )    /* IAR *******/
      H. o/ B3 V6 C0 Y7 z8 U
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    / Q5 ^7 m9 k  u& R* r* ]4 K
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    * t# C' ~: U7 H  N+ w7 }
  27.     #elif defined (__ICCARM__)   /* MDK ********/
    8 D3 h8 p, b6 j  A+ W9 z$ w
  28.         #pragma location = ".RAM_D3"6 }1 }' U9 H. k+ Z! Z# C& S! m
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   ' T% C" R% N3 l, K  b9 ~0 ?. o
  30.         #pragma location = ".RAM_D3"
    8 K5 L# I! A% r1 y: B3 X
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    ! ]7 U, ]6 p  o, m
  32.     #endif
    ' p" G9 @* i( G1 T* \; n$ ?
  33. #endif6 |' H" w4 q9 `0 X' D

  34. : x- J5 T4 G6 f0 s% ]9 T1 G( g
  35. /*9 p- n+ p# [+ b! a* o
  36. *********************************************************************************************************
    % J+ L3 W8 a  T" q; f0 H
  37. *    函 数 名: bsp_spiTransfer
    . B" O5 V" N5 A
  38. *    功能说明: 启动数据传输( s" t. I; c& x7 {1 ^# h* _2 J: d4 f! y
  39. *    形    参: 无+ o' j# P; [6 @* H
  40. *    返 回 值: 无
    3 y+ F6 \3 ^' z' C2 J
  41. *********************************************************************************************************
    % C' n6 x- n1 Z) M1 v5 w/ e
  42. */- K# s0 j! v. h+ t
  43. void bsp_spiTransfer(void); Z, ^, D7 i: M8 z1 H
  44. {
    3 X! q5 `7 s% k- g7 V: T6 Q
  45.     if (g_spiLen > SPI_BUFFER_SIZE)0 P  r3 C+ m3 f- O, q- x  |8 a
  46.     {
    ! [* x, r4 }, [5 }: ^9 ~5 Q; ^- `
  47.         return;3 M7 T; ~1 J, w3 v% m8 I1 u
  48.     }: j1 @1 U/ E8 P: s3 ~7 t3 Z( r
  49. # o+ s4 l) p# O2 v: w8 g2 X9 f) k- d
  50.     /* DMA方式传输 */
    / \8 ]! n+ R' @7 f* C8 z
  51. #ifdef USE_SPI_DMA8 _' G7 S" G$ W* x: L
  52.     wTransferState = TRANSFER_WAIT;0 v4 A# g0 h' k. o. }( R* O. ]

  53. 0 S& J' a. `* {1 H+ [* U) [
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    + N- Y. ]8 ?$ l# A* f: `4 s, H" R
  55.     {
    4 N* i1 t; H9 G
  56.         Error_Handler(__FILE__, __LINE__);
    2 ]7 W) c. V. }8 ~  H
  57.     }
    1 A0 ^3 l" o0 [" Q( v. d

  58. % r: e& v2 z3 v, E7 s9 {
  59.     while (wTransferState == TRANSFER_WAIT)
    + W& @: S8 s8 `1 |/ j' _( v
  60.     {
    2 o- R0 Q& V8 @& Q9 u
  61.         ;
    # ?! e# Z$ \3 b8 j; W
  62.     }
    / q1 o$ ]7 u6 t& w* I
  63. #endif
    5 Q" ^6 @: h! k3 u4 \

  64. " g# o& L: v/ u3 q( k+ d! `
  65.     /* 中断方式传输 */   
    * g: Q2 `7 E- [$ y; s1 |
  66. #ifdef USE_SPI_INT$ l3 o" K) I* ]: q
  67.     wTransferState = TRANSFER_WAIT;
    9 v  i$ l/ d2 J& A

  68. / B8 A- }/ d$ I. Q) N3 o
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    ( Y$ m  n* x1 ~( S& u
  70.     {( L& h% E) o$ H' J* i1 h; |
  71.         Error_Handler(__FILE__, __LINE__);2 q! ~& _8 M# \5 E0 H; z2 G
  72.     }
    3 W6 o0 b! c. i

  73. ; v. U7 `9 F2 m$ G: x
  74.     while (wTransferState == TRANSFER_WAIT)
    7 K9 C' X% j2 [& f" J3 R
  75.     {
    2 [1 S( D3 Y3 F  I
  76.         ;* x9 ^' [; S* T. c/ X  [3 H
  77.     }
    & O, c6 ]! ~* `: H
  78. #endif; T7 K5 L; k+ ^! }# k5 y/ ]

  79. ' g  m0 ~" q3 }) a
  80.     /* 查询方式传输 */   
    4 {) i2 B4 |$ q" y. r) B! [+ {- n
  81. #ifdef USE_SPI_POLL- b& {, G/ J1 d! H" i% l
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)   
    8 S: ^% A$ B8 D2 n% c2 T
  83.     {/ d& y6 E& i4 W
  84.         Error_Handler(__FILE__, __LINE__);( f, ^1 d/ m$ X9 ]2 P! w. q
  85.     }    - B( O  |* Q8 l  ?$ P1 [! [
  86. #endif
    ; p2 j' y: ~% ?2 S
  87. }
    4 j* S" a2 J) A  U% \5 g
复制代码
( n* [8 b4 `! m! w/ T6 ~" l
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
; {6 I" Y/ G$ v2 M. @7 E6 Q. j: a! P* s8 W. P
  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。! t" {4 t$ I. ^+ @! t
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
8 Y2 d% C9 S/ A6 C  _, E
  1.     /* 配置SRAM4的MPU属性为Non-cacheable */
    % m5 V: n- J9 O' H9 s) F- @, J
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    " E0 _7 ^& G8 Z; x) _2 M( y
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;) q; D3 W+ d8 w$ G! T* J# F+ ?
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    % S$ [: F" `- C8 `- ^* w: q* ~
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
      s9 T# L% f/ k' k6 I
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;( v9 X$ P* G% Q  Y. F
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;6 p- l# n* P3 s
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;7 m- N0 {# }9 c) W3 D1 }. N
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    * {0 Q% g+ `( p1 p& `
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    7 i. F+ J! p; J
  11.     MPU_InitStruct.SubRegionDisable = 0x00;9 X9 p: U' Q4 p, k2 |9 F
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    2 k+ b# x. e- O; q. y

  13. - e* a% ~7 g+ _  D7 Y6 f
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码

+ y6 b7 t4 B+ H73.4.3 第3步:W25QXX的时钟极性和时钟相位配置

+ Y# ^; f9 ~) e$ \. o首先回忆下STM32H7支持的4种时序配置。3 C+ A9 g' A/ c% {8 \8 t

# P+ z5 N- i  R! y  当CPOL = 1, CPHA = 1时
% |/ {" y) E: y6 V) }+ m
+ r$ F& @( s1 }. ZSCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。5 F# m& B* C. b# K

% E9 w: C- l+ K5 O$ Q+ e4 K  当CPOL = 0, CPHA = 1时
6 q0 }! k' z) [: g  |% D. f# L% k: Z# C' q" {" D) k+ T, Q3 r
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。8 o3 y3 E1 W- n+ Y1 O( m5 b

  l' \* g9 u/ p, w# L6 k  当CPOL = 1, CPHA = 0时
. d5 U- s1 a- M9 n* f. j2 i8 Y1 z7 S5 ]# ~0 E! F% }) U0 e
SCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。* Y8 B4 Z6 Y8 ~5 W/ A

* A' ?, }" x. h6 U  a5 Q; E% t 当CPOL = 1, CPHA = 0时
  o$ N( T9 s9 Y5 ~
& W; [/ \% l% G3 ~9 l' U5 |: lSCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
& t# n' R, S4 h/ u& |* x' H/ h
# U; v* i& G8 u+ T  e; J
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

* T7 ^/ a( H! k4 D  h6 [
) v$ o7 X$ C* I有了H7支持的时序配置,再来看下W25Q的时序图:
$ [/ W: m6 t+ G/ p0 B9 G6 v" t* l* S0 G  s
Mode0 : 空闲状态的sck是低电平。
# j4 h# D" E9 z, c6 i0 j' I/ F9 z, r: v& k9 I8 z. P6 X
Mode1 : 空闲状态的sck是高电平。$ `( I( K: K. u# N6 s! i& K% P* E

* p- @- X0 s/ D- t/ y: L
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
2 e( ~, P2 T! Y* F0 o( _3 D

, g6 D$ G* f4 [1 D  W7 _首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:
" o2 a- F. _* X! r
+ p+ K7 {# g, i5 x" V5 M2 _CHOL = 1,  CPHA = 1! k. j1 g2 w# c8 C) A# O
# Y) p9 I3 N, `: T' x9 s, J
CHOL = 0,  CPHA = 0
0 O- h! ^% L+ N6 _9 {4 W. Z- k
# N# G( j, O; c% v+ u1 _对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。
  T& ^( B" R* p/ k, H7 i5 @4 m  v- \% X: Y% E' J" w
73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制  m: J2 [  f4 \! P& }
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:. f) d: `8 S( S# A

9 o6 ~1 z( J- I0 i' W
  1. /*
    $ {/ N. _. A+ ?0 w
  2. *********************************************************************************************************
    , O  Y, W, A. q2 h
  3. *    函 数 名: sf_SetCS
    ( l/ B- O  c  J+ _
  4. *    功能说明: 串行FALSH片选控制函数
    & I( z, V* C/ Z+ _5 J, n
  5. *    形    参: 无
    ! c0 Q# C2 L; Z1 s2 `3 J$ s
  6. *    返 回 值: 无
    ! \, t5 y: W9 c, Q; q- @
  7. *********************************************************************************************************
    4 _! E3 c/ Z5 z
  8. */
    ) X  C$ N9 {- u$ m
  9. void sf_SetCS(uint8_t _Level)
    ! q# k$ C- j& `; M# S, e% n
  10. {
    6 Q- J" @) ?/ T5 d
  11.     if (_Level == 0)
    6 s9 B. a: Q! s7 t1 o
  12.     {
    * Z0 ?9 G# Y  k+ X
  13.         bsp_SpiBusEnter();    1 s7 ?: O5 k$ v+ J, T' T, Q( b
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        9 S( F, I& j6 l/ y- t7 M1 z  @
  15.         SF_CS_0();1 C7 k. q6 h& q
  16.     }( k& \7 i, w0 l0 t4 c
  17.     else0 P8 r5 S$ s5 H+ ?0 z3 ]2 S% Z% {3 r
  18.     {        
    6 S6 a, a- b/ J0 O: r! i, {* d( n
  19.         SF_CS_1();    6 @, n9 E2 F8 Z& p0 h
  20.         bsp_SpiBusExit();        5 Z' c- w( B0 v9 t
  21.     }9 w1 d+ G% E; Q. f0 F! G" F$ D
  22. }
    / b' f2 p; d; d9 e. a+ l0 V
复制代码
; S" {/ a. ~5 X" O1 i
& F0 k1 S* Y8 _
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。) g9 f# W$ M7 v/ t8 T) M
6 w4 g* d" ?8 r4 I7 z( g& d
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。
, [8 M" e& I" F( t9 _8 U$ g, P  m$ e! j. X) w
73.4.5 第5步:W25QXX的读取实现
! B$ |' {# o) X2 i  e, J
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。) X9 l0 p5 y% w+ d  y; r# ^
/ M4 ~5 ?6 i" [) T
  1. /*
    ! p( l8 b! P+ G% o0 z
  2. *********************************************************************************************************
    % k4 a" w; m% F( }  K2 T# a
  3. *    函 数 名: sf_ReadBuffer+ s" i! y7 n9 F* C7 [
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。: }  E1 i  A$ h' N% E3 ?
  5. *    形    参:      _pBuf : 数据源缓冲区;/ Q+ e- J! `2 Q# s
  6. *                _uiReadAddr :首地址
    ! z: _% N: R5 P, C( R1 @
  7. *                _usSize :数据个数, 不能超出芯片总容量) i2 o. R5 R" t, ^% Y6 j
  8. *    返 回 值: 无1 }2 b: l3 |- g. r( k( [
  9. *********************************************************************************************************6 M4 N3 C- Q& Y! c* b! Z
  10. */
    6 o8 {/ v5 e/ R3 P/ O
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    ! W7 P- O9 y0 G
  12. {8 }8 [/ r1 S# ]$ O6 B, r. `
  13.     uint16_t rem;. j- Y. [1 P) n" h# J4 {
  14.     uint16_t i;' ]4 O  E, _' h  P* I/ _8 {& C: W2 @
  15. + T- l6 U' @0 R1 B! y) P3 Q
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    4 U' a; c6 q. p5 `
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)8 l- `+ I0 m8 o
  18.     {
    8 P1 t2 K$ z, G) p) r
  19.         return;
    * j/ O1 W& ~% X3 q+ `
  20.     }  u, ~  L  u0 m

  21. 2 T# q$ v. g8 M4 s, E
  22.     /* 擦除扇区操作 */* {0 f2 e/ l! y5 A  w
  23.     sf_SetCS(0);                                    /* 使能片选 */+ S- i0 |4 Q& @9 o7 q$ e7 _2 K5 N
  24.     g_spiLen = 0;* S% l) w- T% A3 g4 ^/ R+ N1 A( \
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */
    6 c% ^  [6 K' N8 ?4 X( M. h0 ^
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    + V! C; s1 W2 q2 v# y
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */# R4 O# j; x4 D, t7 I; w- }; U5 Y
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */  L5 z+ j. s, l) q: s8 x
  29.     bsp_spiTransfer();: V; q- o- U# P: C2 V1 ~6 B, a) j
  30. ! F2 ~$ T% {3 M" R
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */) ~) q9 h- e% z, I
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
    2 Z) y% `+ I3 B9 L
  33.     {
    # x& c: B! E$ R
  34.         g_spiLen = SPI_BUFFER_SIZE;
    % V, w, a9 Z' `1 F
  35.         bsp_spiTransfer();
    % O8 F# {  B$ H, ^

  36. ( \/ B5 k( n% Q+ M, I& V* F
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    4 {1 l% z: g* S; h
  38.         _pBuf += SPI_BUFFER_SIZE;* U1 x* Z! n, \" b" C7 ~
  39.     }
    ! S# X. r$ W2 U$ k2 w+ d3 i
  40. # k! }% \& i; a# u6 H% _0 b
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */1 v( [( f- s$ i  S7 k9 N- D: z
  42.     if (rem > 0)0 f# t5 R9 }" {3 _4 m/ I% e! Q2 q
  43.     {" j' N3 I$ }6 S1 W4 b
  44.         g_spiLen = rem;' G$ T$ R' N) |; w' @5 E# T
  45.         bsp_spiTransfer();
    6 J7 j) F+ |8 h5 F6 R6 C
  46. 4 r) K+ q0 ^6 z
  47.         memcpy(_pBuf, g_spiRxBuf, rem);% O6 M9 L* I& |8 c4 d3 g& ~
  48.     }
    % p. q% |5 h& U, W! R

  49. 9 H4 D! Y( I+ H/ [: b
  50.     sf_SetCS(1);                                    /* 禁能片选 */
    2 S! S  r% T& P, b( M* a( S5 Q
  51. }
复制代码
9 [" r$ d! W/ B3 W9 L+ C6 R) o
* r" E% ]  q4 z1 a
这个函数对DMA传输做了特别处理,方便分包进行。( d, D% a# }. Y# ]. @
, u1 r+ x8 p) S& _( A1 y2 N  y
73.4.6 第6步:W25QXX的扇区擦除实现

% T5 n1 K( X7 c( V扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。4 N" V+ s: a8 y, C( ]
* q: o! F0 a7 V7 T( v1 t
  1. /*& k; x7 [( r7 l6 U5 H3 t- x/ T
  2. *********************************************************************************************************
    9 F# p# |+ q5 v7 t, L+ P
  3. *    函 数 名: sf_EraseSector3 L/ M; E4 `& a' s
  4. *    功能说明: 擦除指定的扇区5 |+ n$ P0 g6 |1 n7 ^
  5. *    形    参: _uiSectorAddr : 扇区地址
    ' ?# W* G7 X' _- M2 _
  6. *    返 回 值: 无
    * ]4 A. ^! [% P9 }5 b
  7. *********************************************************************************************************; l3 v+ z; g9 J8 M/ Z0 @
  8. */- B% S6 f7 p. x$ S; Q! L  u
  9. void sf_EraseSector(uint32_t _uiSectorAddr)3 L2 c; n% B# ^* c" L/ ?$ Z) y
  10. {
    * G, M- C  p) W2 c1 I
  11.     sf_WriteEnable();                            /* 发送写使能命令 */
    ) g5 V1 G- T! T: m
  12. 5 t4 O- q+ c0 \9 J* K+ \) o0 ?. I1 A
  13.     /* 擦除扇区操作 */
    4 k5 o7 }. n2 a# @5 u3 [
  14.     sf_SetCS(0);                                /* 使能片选 */" ^' d" j2 f) O: X; j
  15.     g_spiLen = 0;
    , h' x8 K/ U% X
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    ; ~, P- f" F, M# V, b* N& h
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    # u; X8 G8 x  z+ b/ v
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */$ O) O% a  L' g9 o1 @3 K
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */    . `5 d  r/ n) C( m: ~; L# ^! C
  20.     bsp_spiTransfer();/ p( M, s( N. Q( B- v) G
  21.     sf_SetCS(1);                                    /* 禁能片选 */
    $ T8 i1 Z1 ]0 r

  22. 7 b! }% R, q6 F4 u+ `: h* ~# g
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */9 R) U3 U* F7 `2 I, L! |# p# F
  24. }8 L# j& j7 S' ~: ?- d0 n
复制代码

8 h) V" ~9 t, U/ B$ G! j7 z" d" x
2 h/ S5 s; p3 x# h4 P3 c* N整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:& P; T- k& c. w5 T% V# }% h7 l

* q' G" A7 k, O/ ~! M
  1. /*
    , ?1 a% o9 l7 h1 p5 q' }3 @' c
  2. *********************************************************************************************************) p: h9 j5 Z6 S+ x  U
  3. *    函 数 名: sf_EraseChip0 d' A# \) A) E% P5 d# }, e, b/ m: L
  4. *    功能说明: 擦除整个芯片
    $ d3 f7 `, G. n" Q0 Q7 V1 H
  5. *    形    参:  无
    & o7 d7 ]* e" |7 V. a
  6. *    返 回 值: 无
    8 V& G" ?& ^- B
  7. *********************************************************************************************************
    6 ?6 e, H! e- `9 |( ?
  8. */
    & J: O( D! s/ V% Y" e2 ]
  9. void sf_EraseChip(void)
    ; {2 P, l3 s& Z$ L8 w0 G! c) b5 k
  10. {    + S+ C; H+ g, a7 s4 S9 j" n- i
  11.     sf_WriteEnable();                                /* 发送写使能命令 */
    6 w1 P! a  W4 o+ G0 T6 L* A
  12. ! w( f3 a+ j9 N6 U% X
  13.     /* 擦除扇区操作 */# l2 @$ ~" ^4 J) z4 d
  14.     sf_SetCS(0);        /* 使能片选 */  h4 I  }+ |: j. D
  15.     g_spiLen = 0;
    # g) ?3 z. j5 h4 G
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */# u5 D; S2 W, w8 |6 |7 s
  17.     bsp_spiTransfer();
    $ `, b, q' H6 M& }1 [$ m# t
  18.     sf_SetCS(1);                        /* 禁能片选 */$ k4 K; W2 L' i4 b
  19. ' p3 K. ]9 ~! X' j& a. N
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    - z1 E$ D+ h7 Z  J1 x9 p, z1 A
  21. }
    , x) S& K2 D$ c' E
复制代码

" m6 o8 P2 l! k3 u& Q73.4.7 第7步:W25QXX的编程实现) D- ^7 e; [% G( {0 t2 u. I
W25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:3 h/ J* k2 ^  t$ U$ q
6 L' S. l4 E% D2 r/ V: e
  1. /*
    ( y5 O7 l" x: G* }8 {
  2. *********************************************************************************************************
    1 h' Q' B3 [! K" `  s+ Z" B
  3. *    函 数 名: sf_WriteBuffer
    & j# @8 i4 m7 f* j: D7 i
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
    ! P( N8 h- [2 j, U* o
  5. *    形    参:  _pBuf : 数据源缓冲区;: |& T5 M: U' J2 W0 e1 ^
  6. *               _uiWrAddr :目标区域首地址% U# T, ]% |5 I, K+ P
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。" S- ^1 A' p2 Q# {
  8. *    返 回 值: 1 : 成功, 0 : 失败& f8 A+ j. ~" b& q% [/ J
  9. *********************************************************************************************************
    " }4 G* _# A6 _; x8 n" n6 h9 ]
  10. */
    / y5 @4 \/ i/ w% k: G1 ~: E  k, l% c, Z
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)2 h% {; j) [. R  h  W
  12. {
    , n( x0 p5 m! J2 @) ?6 R  L+ O
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;0 F- q0 p/ z' k& j) M2 n0 |
  14.   K) c: B) A) I, w7 N
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;9 |- l! f5 f7 W7 F7 ~. j3 @
  16.     count = g_tSF.SectorSize - Addr;
    ; _" h* r' B7 w; |; s
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    % V& S0 z  _! y5 {* I8 G
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;  s( H5 H( h/ c6 l0 X

  19. 3 t8 l2 O* a7 N/ g! _9 K! \; p
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */; {2 K1 m2 i# m- w" l
  21.     {
    6 k  o+ j* x5 v
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    ) p% i' j% G+ Z. @
  23.         {
    2 J  w, ^2 k1 [( A% x9 |
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    3 ~$ |* q# E) V1 N1 w; Z
  25.             {
    9 J8 F8 {- U3 v1 d# }
  26.                 return 0;) q* }! G) _; T! L8 @* G
  27.             }
    : ]' ?  B. w2 a
  28.         }) ]& A) w) W6 @2 _  |  E4 v8 a
  29.         else     /* 数据长度大于等于扇区大小 */' c& S6 p9 O0 P' {% c
  30.         {
    , E/ a7 t* {0 I/ w  t! _; m+ }
  31.             while (NumOfPage--)
    & h* V7 w+ s4 {: c/ A2 ^9 Q
  32.             {/ D9 v' b% S! ]* M$ ?& M" [
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    $ L  O$ Z$ U: k* q
  34.                 {
    9 z  c0 S' @  e7 `$ Y* v8 X/ T  c( s
  35.                     return 0;
    9 S; E) f" f4 A8 k7 Y/ P
  36.                 }
    0 _2 k, k) J. `  Y9 f) _- B
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;
    # q' O3 G7 D) X* p# p+ v, W: n9 q: N& I
  38.                 _pBuf += g_tSF.SectorSize;8 u2 M7 c- s( b
  39.             }( W, O, u# M+ S& \3 U
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0). Z0 l- g7 o# E+ ?5 w( W
  41.             {+ z6 a: u" w8 F/ g& q
  42.                 return 0;6 i; V2 p1 s& S; d% C, g# i
  43.             }
    4 B$ d, }* d! u" e" P9 w
  44.         }
    5 t$ L6 e0 P+ D% V! T
  45.     }# o0 u/ w& X& D5 Y% F$ ~
  46.     else  /* 起始地址不是扇区首地址  */' @* B: y! Q0 ~2 R8 A
  47.     {7 k5 F6 B9 c3 s2 k
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */8 E  ]) a- ?/ {& q" L- b
  49.         {% x1 H" r& v7 B! v/ N
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */
    : h! E) s- p; U- k& B0 Z
  51.             {6 e, [0 d: m  o: _. q( f
  52.                 temp = NumOfSingle - count;/ x+ P: W: ~. ?; t% ~& y# z
  53. % m: P2 E" e( s6 [- U6 c
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    6 k9 U& V* y, k! {
  55.                 {
    7 B- J3 J0 C' H. X! o% U5 W
  56.                     return 0;' X( I" H8 m/ }" n8 [# w1 x2 P
  57.                 }
    5 h9 {6 h* G' o
  58. * m# d2 O7 E6 h+ H" P9 P  Y( P/ R
  59.                 _uiWriteAddr +=  count;( m5 u. d, r! A$ v1 n9 `
  60.                 _pBuf += count;& z4 X1 a+ Z3 H  z3 e, F( x- _

  61. / r3 `& H  P1 k, E, Y* @  \/ o0 o* A; I
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)" r- y8 e; U: }+ |/ k/ t
  63.                 {
    ! N  L, W7 e9 ]- f
  64.                     return 0;
    3 }0 {/ s; v% `7 O( q
  65.                 }
    " }% K1 s6 m4 B) S6 {
  66.             }
    ' V% r5 z8 E' D% }
  67.             else/ |. s1 v$ |! U, R/ N* j
  68.             {) q% k% t; p) l' o: c
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    1 B( J' T+ J0 h: A
  70.                 {+ a- Z' t, d/ q6 o8 R! y
  71.                     return 0;
    8 |* z0 e) |! ]7 F8 B: ^
  72.                 }
    ; m" V. a* N1 k7 Q4 \; J/ Z
  73.             }* [- Y9 K: k$ `9 ?) }' D1 i: U
  74.         }
    7 [2 y4 M* b& D& k& ~; p
  75.         else    /* 数据长度大于等于扇区大小 */  s/ @  _7 c0 H6 }8 R
  76.         {% z, y, K0 r& R8 z0 a, T' |0 ]
  77.             _usWriteSize -= count;
    : M3 g8 o7 E9 Y2 Y) k( s# J' B* A
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;( H6 W0 W0 g/ {+ {' l) h2 Z
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;. F& g9 V& ]: g5 |
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)& E* W  i) `# j4 g! N  C1 L1 f
  81.             {& J9 H5 A9 i" ?7 a. @
  82.                 return 0;
    % o* L: ^, k  F! ^
  83.             }
    : p' s% E1 {  Q8 H+ |- q: R
  84. 1 \+ b" [/ g- }) ^4 v5 a8 a
  85.             _uiWriteAddr +=  count;
    " o  t1 P/ |9 R
  86.             _pBuf += count;
    ( A' b7 z+ p8 Z) v* M9 s+ g, H
  87. 7 W/ X0 R9 h0 e6 G9 S6 S2 U
  88.             while (NumOfPage--)
      n  A7 \  j' L7 c8 {, `/ a$ f
  89.             {% f8 n4 b& A  U
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    3 f5 x3 P4 |6 w/ j% U
  91.                 {
    " j; K, e: T4 @& \. b# R  M
  92.                     return 0;  `& |( g" J' R- ]% ?. C* C# S
  93.                 }
    0 j4 U7 }) K* l. {/ y; j0 `
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;
    : o: u6 o" g9 r* D
  95.                 _pBuf += g_tSF.SectorSize;
    - I8 N, s- i  K+ ^( w
  96.             }
    ; _, z7 }( f% Y/ o5 M- ]' r

  97. 8 z, k$ A: r& D( ]' w0 ]
  98.             if (NumOfSingle != 0)
    $ y) M/ ]+ x/ T* J3 Z# o
  99.             {5 ]  w7 a, r5 Z9 m, ^  H
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    ( r3 A  p! j! k, ~# }0 S
  101.                 {; e  R& t9 H# a, b" P4 t
  102.                     return 0;; x, u4 F6 [! i. U2 k8 O
  103.                 }' a  N) ?9 K1 t) E# T
  104.             }
    8 L: d) m/ s8 x# q- a9 e# _2 ~
  105.         }. X9 }! a& M- G* t+ w2 t
  106.     }2 n9 {  `# ~  z7 @4 i1 z) r
  107.     return 1;    /* 成功 */
    7 [0 y! f8 P, y2 A
  108. }
复制代码
  T, R8 e  r/ L8 l8 ]& d& ]
73.5 SPI总线板级支持包(bsp_spi_bus.c)
; ?% Z0 v7 g; N/ M% s" S. vSPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:5 |' g! k1 t5 P5 ?9 N4 P

! g- o  H/ `7 r  bsp_InitSPIBus
% d6 I% h* {0 f( `0 [& h  bsp_InitSPIParam1 \2 o3 W* I  o" G8 `/ z9 s
  bsp_spiTransfer- W' c4 A$ c$ d; `. @& x
73.5.1 函数bsp_InitSPIBus: M- ]; R' T; F# u
函数原型:; T4 [1 \) E; ?5 J8 ?( O; j( [+ ~
3 h9 j( y" _) a" D0 t. A& k
void bsp_InitSPIBus(void)
1 z* y4 a1 _7 H: c$ U  `
) G) E+ K, c  i函数描述:% [  g6 r6 w  ~3 y, J3 D

, O. }3 L; m& s- _- |. c此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
8 o4 d* W  V( i1 E% H( T; k4 c# \& K5 |' n7 B/ B, k* u; A6 c& {
73.5.2 函数bsp_InitSPIParam
5 C; Z/ b! _; Q' g/ L% M函数原型:
8 O' n1 ^! D# j! p6 k9 W
+ B2 Q/ W( ]4 ]' I7 ?3 fvoid bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)2 v+ [1 s% _- }8 t& m
7 S+ L8 q. f9 Q; R
函数描述:7 F+ O. ^, v$ @2 \' X

) `3 \2 b6 ~" K) G7 `" Z* O7 w1 I此函数用于SPI总线的配置。# A, R) K9 c2 a* N! W" h* [
( v" Q! ]* L/ e" D$ z& Y; l
函数参数:6 h1 z" n- J# x# c

1 y7 U* V8 \3 p: p  第1个参数SPI总线的分频设置,支持的参数如下:
4 b' r3 x! B% @& f. |5 U, WSPI_BAUDRATEPRESCALER_2    2分频% {) p) F. l. N7 q6 |4 i

: Z' i; l2 J8 d0 }5 v, |1 lSPI_BAUDRATEPRESCALER_4    4分频. g' w& K+ ^2 ?+ b4 j

& w: B) t1 t9 v) F2 ~SPI_BAUDRATEPRESCALER_8    8分频! s! m) D+ R# x5 p- F) L* u. g, X1 k2 C* L

0 N+ o$ P, a& ~  ESPI_BAUDRATEPRESCALER_16   16分频
' d7 `1 p; i( b* W0 n. a* }; u7 |0 y- J
SPI_BAUDRATEPRESCALER_32   32分频) r) s- U1 d. M4 H

% o6 n& v: M8 l( y2 v9 ^5 eSPI_BAUDRATEPRESCALER_64   64分频
8 Y' f( f  a4 W3 F1 G8 a- m
! H7 v" [2 G+ [, x) \  Y4 ^SPI_BAUDRATEPRESCALER_128  128分频! l/ V# J6 }- n, x
$ T5 r( A9 f. l- S' _( _0 }7 C
SPI_BAUDRATEPRESCALER_256  256分频
5 T1 c- d& x5 N; I' o- l
' U0 w- c$ X9 B6 q3 V  Z+ M  第2个参数用于时钟相位配置,支持的参数如下:# E! Q" N( _1 r; J: F# O
SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据
$ x! q  @: z: @3 c
+ [: _3 [' l2 RSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
9 z0 b" h" O+ i' a0 {6 r: b+ C* m3 T8 S+ B3 h9 v. \" N# O* }8 |
  第3个参数是时钟极性配置,支持的参数如下:! S) _" f# M; n7 v6 j
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平
. u, M4 T4 ]- m4 X$ `; l
# p# o  h& \% O/ TSPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平: p9 `3 Z4 R8 A" z4 P" `) s3 C3 S

0 d$ Z  h4 p: C- o73.5.3 函数bsp_spiTransfer
- W, b, \. I6 O" n! M0 R
函数原型:
8 X/ [  A* u; b
; S4 g8 V; f, O1 [void bsp_spiTransfer(void)4 U0 k7 B% t4 a& n) L6 g; X
  ]  k' W' w* `5 \& ^
函数描述:
1 y' ^* l  C  m8 V, h$ y* I- d$ c
, a+ j6 O8 A! W3 k% T' r0 m此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
% F0 ^( y/ \- j! e) o' j* |" b8 J1 c: a7 i, p
73.6 W25QXX板级支持包(bsp_spi_flash.c)
! T, g4 \0 i1 [9 V  T) K0 l0 [W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:4 Q& q; Y5 I4 X/ @  F6 e
7 Z9 z: t( K8 \* ]4 B+ Y
  sf_ReadBuffer
6 L; I! ^- {2 P; O3 D6 A+ Y! {  sf_WriteBuffer0 n2 Z4 P% q/ B2 S0 g
  sf_EraseSector
4 E) Y' G9 f7 @4 a0 }5 F. O  sf_EraseChip
  \2 g% T& Q3 e: \. W- d1 j' M- E  sf_EraseSector
) Y) V' O0 y* X5 C+ n. p$ A( z; N73.6.1 函数sf_ReadBuffer
0 r' ~- K/ g; N' R函数原型:4 w4 H+ A- m# Y/ P

7 U5 z1 v9 o% j. y4 k& @) kvoid sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)( {% ?& _( p* o- |' U9 ]
: D8 B# Q" I2 N
函数描述:+ Y. d$ s( b' w$ R( {

( |# k+ f$ J/ g/ N此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。$ |/ f4 r% H! x3 F# B& K4 }

6 H9 G- M# o. d0 v& O$ T0 e函数参数:' |( A/ V% x% t2 e; I# ~2 i

9 k1 X( O' {) H) j& F  第1个参数用于存储从SPI Flash读取的数据。
9 f! B4 V4 H0 h) }2 \; Q; Q  H# o2 Z  第2个参数是读取地址,不可以超过芯片容量。
, Y  l5 m- T7 I' t$ Y  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。1 o5 @6 C6 _+ s4 c9 G
73.6.2 函数sf_WriteBuffer(自动执行擦除)5 t( J+ a9 w+ H0 e, H) B- g! O
函数原型:5 ^3 f" B6 ~6 y5 P
, s7 e* {4 I( L3 T& O2 p
uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)6 B7 W; k0 f1 w" s8 e8 T

4 k- r# t( L" |4 q2 ]1 K函数描述:  r7 C  g+ Q( I) \* r

2 \9 q+ u0 h, e; n) U此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。( Y: ]2 u9 E8 _2 u( b1 t

2 E+ h  t; R' W% R) @' ?- v函数参数:
* t" p, c) a0 U( B; H7 X" A$ J/ F5 c+ G9 n7 v, D" y* e1 l
  第1个参数是源数据缓冲区。
0 [8 P1 q% i( R. j  第2个参数是目标区域首地址。
  S/ d) J, }4 |4 O9 J0 ^  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。; q7 {# O% b; S- R
  返回值,返回1表示成功,返回0表示失败。! H' \/ [4 m  B8 W( R3 O' T0 V
73.6.3 函数sf_EraseSector+ Z3 Y6 {4 @+ Q6 h5 J) R. b
函数原型:
  m% }$ M9 x1 H& p6 Q# h, e9 n7 \' W" V2 r) k% u
void sf_EraseSector(uint32_t _uiSectorAddr)
8 x+ s4 a, |" \
5 i' r# d, \6 C( R/ ]函数描述:
" V6 O8 K0 S2 N# H' W
7 H9 y; @8 f8 x' O3 b: I此函数主要用于扇区擦除,一个扇区大小是4KB。" t% U: [0 X/ e% A& \+ j

. |0 H1 X% x' G' X" m函数参数:
3 Y" H0 `/ V9 u6 K9 Z
8 [  J! w" G7 N  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。; ]5 Q" A' j" _* s
73.6.4 函数sf_EraseChip/ s2 U- l8 N% h0 c8 K
函数原型:
/ V4 J7 O1 X! N( V5 l1 K( f6 J, W4 j  c( X6 a) {
void sf_EraseChip(void)
1 q$ y+ o  F9 R# M" C$ r2 p
$ ?' w- J! E- K5 d- X+ e) ~7 P函数描述:
6 s" n* a; H" {: r* J2 W" @
# g  z8 h, t2 P/ Y! e- \此函数主要用于整个芯片擦除。! H; G- ]8 e% R- e+ J, L

! L+ {& B  }9 r: h2 @$ t# B+ m1 y73.6.5 函数sf_PageWrite(不推荐)
+ M0 h& R- n8 \' k5 u) |# \, ^函数原型:$ P3 u' v! b* I# ^+ Y

- f6 s" m0 N0 l2 R1 e+ m! kvoid sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)0 d# [/ X; e4 n
" m0 n8 ?+ F0 Y& t
函数描述:
, _* _& A2 G" M' }8 ~. y3 S5 j
+ p( T3 Z& r0 c  }" M此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。
& C6 G+ j3 g# ~: @
6 A1 W! I- W5 p. h3 ~0 P" A函数参数:
, y1 L0 z7 q* V7 a9 b( z2 t
, T) j; O1 _! |2 D8 T! H  第1个参数是数据源缓冲区。
9 A7 n1 W+ _! y5 x! v  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
6 D1 \9 z, A) ^1 Q5 h  _  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。  A4 s' E$ d% |" J# d
73.7 W25QXX驱动移植和使用5 L3 {: a7 }5 Q: j5 f3 X3 M
W25QXX移植步骤如下:
* {8 w, T9 [5 w& O$ A) t: |' U8 U/ X: h# L0 m
  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。
$ {! }/ o! F1 a& u" g7 u& P  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义
) R5 n) }1 O4 r
  1. /*
    1 s7 z" C4 n2 t/ Y* ?
  2. *********************************************************************************************************
    + i5 ~& H5 H  C# g  b2 ?4 x
  3. *                                时钟,引脚,DMA,中断等宏定义
    ) s" L! K# ^0 P9 Y! I9 e" F
  4. *********************************************************************************************************; P- c$ M8 f7 D" y! w$ ?
  5. */7 O0 F6 J' }2 L; U3 C4 N  ^
  6. #define SPIx                            SPI12 }4 A" u  j7 ?; m( R) s6 u
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    9 l: ]& d; I5 S
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()7 p( ]  p0 Y/ h5 N3 q2 F: ~$ T

  9. , M& ~/ |* ]2 j; J& |' K3 }
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
    . |' P- ?& z; H$ l3 C0 m
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()
    & n  l9 E9 f  v+ O3 j6 f
  12. + G4 e& k3 O$ z* o( e7 b6 a
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    ( V  }9 D5 [+ J' m! u, r. j1 E
  14. #define SPIx_SCK_GPIO                    GPIOB
    7 O& E: P8 S/ c' l
  15. #define SPIx_SCK_PIN                    GPIO_PIN_35 C1 S3 X' G# v
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1( J: @" L+ }0 Y; h

  17. , Y3 \1 c0 [: c, {  h( V
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()/ v0 P+ s; n1 d( c1 v2 n5 D9 x
  19. #define SPIx_MISO_GPIO                    GPIOB& R0 M2 d7 l' c3 z! E
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    7 u( z5 C" C: b
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI16 x) v( T# a  }8 w0 F

  22. , o& ?. r$ N/ h# E: Y
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    . I/ e/ l6 \! D1 n0 R' @# P
  24. #define SPIx_MOSI_GPIO                    GPIOB
      z3 M5 ^8 h" p5 d
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5% }. ~- i1 @% Z, [6 B
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1$ I9 p" y5 [7 X1 |% [

  27. % O* o' T. m6 e  a: A
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream3  }' o; S0 B: a1 u3 q
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2+ [+ ~2 L+ D9 N: [

  30. 2 i  z1 N: p$ p9 _# W6 _; O. T1 {) _0 T
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX0 R! I/ p/ S9 p2 _( m" m5 l7 F
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX6 \* i9 k6 [$ {3 ]* `! P; r) G
  33. & |3 O  N2 Z4 n$ e1 q+ g
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn+ x1 W- r" d  T# k; G+ W
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn$ B( h% F. x  \& Z4 V6 W

  36. ( K  z. i# m2 g8 S1 c5 [4 j
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    . i# T) a2 q1 J4 c4 _
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler. D5 ~! C: h  M3 X! t9 H

  39. 2 J, z0 Y7 z3 S& m  N
  40. #define SPIx_IRQn                        SPI1_IRQn
    1 ?1 _, |# J# Q
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler
    ! H# P) X+ @- w
复制代码

: M( }( t% L# H1 P1 K1 J% D! C5 I- ~# P) b# {
  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
  v# N0 t6 g, T
  1. /* 定义串行Flash ID */4 M' h' J: i- j; s4 D4 O% {
  2. enum0 {: }& q, ~) J1 y
  3. {$ J& C8 k* H. q; W7 ^& k4 y
  4.     SST25VF016B_ID = 0xBF2541,
      q0 n6 G& v8 |9 f
  5.     MX25L1606E_ID  = 0xC22015,
    ' i4 e$ l- |4 }8 `4 r
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    7 g; @; u9 F1 V5 g: E
  7.     W25Q128_ID     = 0xEF4018# P6 F5 e, B) O
  8. };
复制代码

2 @, ]& s" K. \' {  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
" k- L* r3 P- w& i
  1. /*
    ) Y3 V- U4 x% w& s$ @* J) x
  2. *********************************************************************************************************
    6 X6 y- S+ z1 N( J& o6 R
  3. *    函 数 名: sf_ReadInfo! Y! B1 Y4 X) W4 R$ N
  4. *    功能说明: 读取器件ID,并填充器件参数  m9 m" X0 r( p* _  J' E, }& O4 _
  5. *    形    参: 无1 `0 _# Q- A4 t4 f
  6. *    返 回 值: 无
    8 @$ o3 ?. E" ?+ @0 g& K( s, C6 t
  7. *********************************************************************************************************+ Y# U# M% H9 _9 y1 F2 M$ C0 P/ ~
  8. */
    $ o0 H. m4 r9 j9 F( p
  9. void sf_ReadInfo(void)
    ' `& i$ V, i0 M! o
  10. {% n; A. ^. ?$ x4 M
  11.     /* 自动识别串行Flash型号 */
    + r, p+ s- a$ N1 S* ]; L0 H
  12.     {9 i8 X4 e( p' T6 l( ^5 T  s' `
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
      o. R& t& r  F/ z! i# h- s( D

  14. 6 q9 M5 e4 E% w6 v4 b9 V+ ~  Q
  15.         switch (g_tSF.ChipID)
    & x: [) L0 ^( Z6 F2 I7 J- ~
  16.         {) S. i, V7 ~% R6 \# P8 |7 b7 Z
  17.             case SST25VF016B_ID:
    8 W1 ~. l. {9 K, \; O* F
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");* {  Q  w4 t% P% z/ [$ W6 }3 A
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    7 f0 ]6 u6 w& z
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */$ u' t" h7 G9 q" r: o( z4 Z
  21.                 break;2 e% `/ u4 }5 @( {5 V: `

  22. & p% b8 {1 r6 ]
  23.             case MX25L1606E_ID:
    % }7 X+ d' X4 K" ~) ^8 p9 e
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");- m* c0 a) g( e% }- p7 G
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */- n3 ~5 i$ s* y1 R' s
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */% u) Y$ ?. E3 }  [; r
  27.                 break;7 {6 ]3 W7 @4 w
  28. ; X/ x  D7 [* O4 C# N
  29.             case W25Q64BV_ID:- ?; v  f9 ^1 I2 p! {- Q) X; y
  30.                 strcpy(g_tSF.ChipName, "W25Q64");) O4 }& ?8 V% i) @$ }/ k
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    . @: \4 L6 x& Z- Y
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    # p" f, G/ z1 _/ R
  33.                 break;
    : {, f8 H1 ^' p+ N. Q
  34. 8 |. |0 ]0 P' C5 n$ D" p
  35.             case W25Q128_ID:
    $ ^) d$ T( P& k4 M$ Y9 |' q" u! i) \
  36.                 strcpy(g_tSF.ChipName, "W25Q128");  _- I2 V5 o: p# v
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
    , D1 v$ l2 g  u3 N9 c1 ?
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    ( i- f, e9 n' v5 w; z+ `
  39.                 break;            
    : g2 o( B' I1 {

  40. % w+ N; V& i5 f9 G
  41.             default:9 v( a+ z9 q9 n5 n# n) |1 i
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");
    ( u2 b% `; \1 I$ Y4 z5 e4 ~2 i- Q
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;) |* b5 G: v: {
  44.                 g_tSF.SectorSize = 4 * 1024;1 s! ]" d- ], ]  Z4 a
  45.                 break;% V" p/ X! d$ x0 p
  46.         }+ c4 i! X" U. Y, ^1 i
  47.     }
    6 l' N. z  U' _- s
  48. }
复制代码
0 Y$ Y1 F2 [/ H' y$ L8 |5 P
  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。/ K1 \3 e  p4 b
  1. /*
    # q' S' y8 m2 l/ K8 K7 K
  2. *********************************************************************************************************
    ' b- G/ l* ?$ {9 x
  3. *    函 数 名: sf_SetCS6 _& B1 A5 U; k+ a( U8 q
  4. *    功能说明: 串行FALSH片选控制函数# @5 |2 Z4 u0 J' [6 j3 H4 Q
  5. *    形    参: 无
    5 O5 ]0 g- y4 q8 F! r8 Q
  6. *    返 回 值: 无
    : Y0 x, r: V6 {$ ?7 C$ C6 @/ e
  7. *********************************************************************************************************" I7 ~! ^( z0 @) y/ [% B
  8. */3 o+ N" U& P: C, ?% W3 u
  9. void sf_SetCS(uint8_t _Level)
    1 ]( B' l; c0 S9 ]
  10. {0 o$ G' M7 s* S6 S" ?" {; u
  11.     if (_Level == 0)4 y- d4 ^1 V- F- Q( J% [; U$ I* u+ B
  12.     {+ X5 H8 D$ ^' @
  13.         bsp_SpiBusEnter();    ; }% s# Z6 z. Y( Y0 X
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        
    , v. {3 U/ L+ a5 V" `' m* X" `
  15.         SF_CS_0();6 G6 h6 N1 l" W+ _* S6 ~7 V# U
  16.     }1 |* `5 l+ S- g6 ^
  17.     else3 g( n( i4 \6 K2 _# O3 b- z
  18.     {        
      [/ B+ ~2 P* G, E6 K
  19.         SF_CS_1();    9 Q* ]2 r& F( r! L  v
  20.         bsp_SpiBusExit();        0 F- i9 O" U. g+ l0 v
  21.     }! m" i% g" g' y
  22. }
复制代码
0 @) D* w' u# S- K) a: l& y
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
( k- A$ d; S9 L$ [6 d3 s
  1. /* 串行Flash的片选GPIO端口, PD13  */
    . X& ~3 [1 j* H) ~3 P# w: U/ `6 d
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()% m8 O, e1 p" E3 Q
  3. #define SF_CS_GPIO                    GPIOD& H( h+ Z4 S! k1 {! K  T" v
  4. #define SF_CS_PIN                    GPIO_PIN_13
    8 a0 d2 N5 g& }) p9 \% ]
  5. . I6 C8 q* h% |5 ^
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) 5 \/ @7 A+ i- @! p0 z6 A
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码

! C+ B- r+ c" l% R3 Q1 H- {" ]  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:6 y  B6 _* Y: e1 f5 z% Z% r$ K/ \
  1. /* 配置SRAM4的MPU属性为Non-cacheable */* @: w/ N) e5 g2 t* c$ U
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    0 g7 s. A5 n6 S: u
  3. MPU_InitStruct.BaseAddress      = 0x38000000;
    , M1 I" a- |, }6 C
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;4 Z, g( d( m. ^$ r+ [1 ^# _
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    6 E  C& N4 h; M. {' ^" s% w
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;2 B' v; e. M- Q4 i  P% c1 T
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    ; |; e9 K& L4 z8 F! r% d. n
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;7 B# _; a# s( b4 Y
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    & Z% L4 U9 t+ o! x' \* m- j
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;, P  s5 |/ E- M& N" L
  11. MPU_InitStruct.SubRegionDisable = 0x00;
    ; {' r. e2 g8 c3 x  h- I
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;( A8 l9 ^& R7 q4 B  q, A
  13. % q; `1 J6 r) B& i% l$ `
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
8 w' w7 g  J0 ]9 }0 C
  第8步:初始化SPI。" {' C9 Z8 r  h; |. X) W: A3 F
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
      K9 g+ v! U2 }7 E# d
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    ' A6 o! [. I5 U# @
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码

: ]& q% [8 I; U0 H6 v& U  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。+ Q9 ?3 t/ i+ q( u8 V& M
  第10步:应用方法看本章节配套例子即可。
/ G" l: r5 ], P4 l4 ?7 m73.8 实验例程设计框架
3 ^, v4 b6 \+ g通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:
7 @' H/ F2 @( G) ]9 I1 s) \2 j/ N& v
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
9 ~$ p: u- O& m2 }

# K7 r) U+ q/ t  第1阶段,上电启动阶段:
: y* W2 U! F7 }' z+ Z: f$ x+ }( T& C8 q3 C
这部分在第14章进行了详细说明。- B, B4 }7 D5 W* V
  + I: ?) }5 G+ S, x7 U8 Q+ j( ^
第2阶段,进入main函数:
4 x# m0 C$ n4 {" K" b) Y& ^2 l1 o. x0 m/ N7 b/ A
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
  a/ K: L. I; p/ e& @; x- W. U第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。) c8 C) g" @4 y2 b* b2 ^/ M
6 R1 s; ?! l( a/ Z4 g. ?
73.9 实验例程说明(MDK)9 ^2 c- N& o( \5 W
配套例子:7 C8 g; M5 t7 l' R  e- ^/ p( c( k
% k/ B8 c* Z: ^/ @
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
" z- i# q; R& T6 t2 R. ]! d, W
  N- M6 H% E0 v' x5 ^: O& r* y) CV7-050_串行SPI Flash W25QXX读写例程(DMA方式)
/ S' Q9 \. d1 v) S. w
9 _1 I& q( g, `9 |+ fV7-051_串行SPI Flash W25QXX读写例程(中断方式)
0 M4 u4 h; Y" r. h" A2 e" X  C$ O) ]1 K; g- a- g
实验目的:
: X! c0 X- {; s- x, A$ z. g, Z" W# k& Z: j1 Y1 I& J& c6 a' Y
学习SPI Flash的读写实现,支持查询,中断和DMA方式。: f' g! ]' o" W7 |

! D( S3 j5 m: c) e  r2 V" q实验操作:5 R! S2 \- D3 L. F% @3 Z5 u
# ?* l2 C5 G6 J3 `9 z
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可' d9 p% N2 ]  u( N5 i" y+ E3 {
printf("请选择操作命令:\r\n");
0 W- v3 r) J) t+ d8 @0 kprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);8 m5 P* n- O$ x; M; M6 p- }
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
2 q+ Y+ X' `! _7 d7 M3 ]printf("【3 - 擦除整个串行Flash】\r\n");
* u& j9 f2 {) t: m# E4 V1 o1 ]( R, Wprintf("【4 - 写整个串行Flash, 全0x55】\r\n");
7 ~! v: r0 T8 {/ wprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");
1 `5 `! `- i" O. pprintf("【Z - 读取前1K,地址自动减少】\r\n");
/ r' r/ y  n" w* Y+ aprintf("【X - 读取后1K,地址自动增加】\r\n");
, ?- \) L+ E+ a# U" s' ?: @3 c* j% Nprintf("其他任意键 - 显示命令提示\r\n");% p$ }5 C. R, S' d" h) P# q" W
上电后串口打印的信息:
/ U( ]+ G0 v: Y* {* g' z* B
4 f8 e1 n- B" v波特率 115200,数据位 8,奇偶校验位无,停止位 1。
) n$ U* \" p5 |3 L  H0 Z  F8 J5 Z1 S; r' S- _; R& p% o  S% f$ C0 z! `
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

7 K2 I1 u4 M/ n4 A/ v4 `% b/ Q! O" b, h! h) j3 n
程序设计:2 [* c6 N8 K& a( s

( m+ P6 m  O, K; M2 Q9 J  系统栈大小分配:
, ^- H9 K/ W( Y( {, I! F8 y3 @2 C# m6 {' ~/ Z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
# M' i- a' k) F) q* V: P" j
, \/ t) }: P' a% r( C  n
  RAM空间用的DTCM:* k6 n2 e7 _% }, V

( o1 n3 y  I5 q! b2 L% d" Z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

! r7 n3 _/ p- ]7 G$ g$ `4 t8 ~/ a6 f( `+ P9 w: l
  硬件外设初始化( I8 j2 r2 N5 b- N- K
# f8 ^' g0 k, Q9 j7 ]
硬件外设的初始化是在 bsp.c 文件实现:5 a# K5 o$ H' e9 M
* I6 r: [3 |, d* K" }2 Q9 [: \
  1. /*  {9 ]9 O( z+ y6 @! F5 h% o
  2. *********************************************************************************************************
    ! a0 G/ w& l- z" H% y9 d8 A- M
  3. *    函 数 名: bsp_Init& N+ }1 P; M/ e+ B: s* _% k% i
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    & B1 K" J7 k7 }9 U' V
  5. *    形    参:无
    5 O' P* U/ y8 p, p+ Q  c
  6. *    返 回 值: 无
    ) s# S# I) k2 }! W& b# ^- i
  7. *********************************************************************************************************$ J3 C: ]& S% Y2 j% G3 p
  8. */
    / W5 Z. i0 i1 ^
  9. void bsp_Init(void)
    7 T6 }1 O! Q* j
  10. {6 L# u$ v% j3 j, r: D* F
  11.     /* 配置MPU */, @# U. m( n7 o0 \0 z
  12.     MPU_Config();
    4 G7 S# F( V8 j. [* u, T0 d

  13. 7 V( A! K4 N; K' o
  14.     /* 使能L1 Cache */" T& F9 L& Y! y. T* K, }) T+ w2 g
  15.     CPU_CACHE_Enable();
    7 V5 g4 k4 l9 p3 F/ d7 l4 h
  16. ; ]9 G. k- w5 F4 Q% r0 X
  17.     /* 6 n- J% `( \' P9 T5 r1 P
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:7 W( q1 f8 s' M5 A1 X2 ~, r7 M% D
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    $ f- v' s+ u- Q+ N; p( Y
  20.        - 设置NVIV优先级分组为4。
    ! C0 M' w3 _) w
  21.      */
    $ ?0 \* R# j, R' V) K8 Z- d7 I
  22.     HAL_Init();5 p- K/ L" |1 A: L8 u0 z* ^
  23. , \* P) S% c) S' j& h. J
  24.     /* & ?8 @9 R* J, |) B( b
  25.        配置系统时钟到400MHz5 A1 \" x, q0 D( g, X" b1 l) }
  26.        - 切换使用HSE。
    , A  e# `6 a9 p$ o+ u
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    9 I9 E* ^/ [) _% `4 _
  28.     */  J( R% t& C8 y) m  o% m' i
  29.     SystemClock_Config();
    ! r1 o; P5 G; A
  30. % \* m- [' G6 K- w" p  z0 i6 Y
  31.     /*
    3 r2 X: R$ J2 T5 P# L; w
  32.        Event Recorder:. ]7 w. Q8 [& y/ v- {
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    " D0 A. L! p1 o4 G: G" ]% m
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章! F, C7 k% a% X; M1 s3 F* k5 `- m
  35.     */    2 G6 E5 H: C  _% k7 h! v  Q
  36. #if Enable_EventRecorder == 1  
    9 v7 [9 a6 t/ c+ F7 l9 t0 M
  37.     /* 初始化EventRecorder并开启 */
    * q6 E! Z* [% G4 Z5 F( d$ M1 {" |
  38.     EventRecorderInitialize(EventRecordAll, 1U);5 ~6 ]5 g8 |/ g) x  p; U
  39.     EventRecorderStart();: [+ z. O5 m' Y' ~, I* \# w/ a
  40. #endif
    % ^  x; u, ^8 g8 l3 X+ m

  41. 3 }) t0 n. T9 k, U
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       3 B% z5 M7 @+ [( {8 G$ W  Z7 O  o
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */6 \: P( ?7 a5 y
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */7 d! [2 }' r7 l1 h; v
  45.     bsp_InitLPUart();     /* 初始化串口 */) B; `$ i" D; O+ \
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */    8 j* ~' L. Y& }9 m" D
  47.     bsp_InitLed();         /* 初始化LED */   
    7 |+ p8 r2 a7 {/ B+ O" [4 d4 G
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    6 K7 Q+ X7 r; f; u

  49. 1 Y8 V9 D# I- ^/ ]" H; Z0 }6 k
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    7 ^  G& U. @+ ~0 q/ c, I; ]
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        1 @. U5 t, K. {* P6 |
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */, ~: l2 F9 o. ~0 n4 S' O" \, I7 D
  53. }
复制代码
' r+ p2 F! ?$ U) E
  MPU配置和Cache配置:
3 V. a/ S" A( z& @/ S
1 w( ~; u( S  G7 `数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
1 S( A1 y& g9 K; \6 W' S! b( m% V  U- W& d- ?3 J1 z& k5 O
  1. /*
    . m: J7 T5 c$ C
  2. *********************************************************************************************************! a# J1 X, d9 D. Q( c
  3. *    函 数 名: MPU_Config% X* n0 Q9 R+ h0 D
  4. *    功能说明: 配置MPU# ^% ?+ J0 e  A. l+ S$ V3 j
  5. *    形    参: 无% q% z; j: J4 q& K1 P0 E
  6. *    返 回 值: 无3 J, G4 i$ V, r! l% x) R7 E
  7. *********************************************************************************************************
    5 N" O' b- d# \3 C: ^% E3 e
  8. */) Z& i; M/ p3 h, C  ]. T4 w+ x* F
  9. static void MPU_Config( void )
    ) f" Y8 [% ?; o, w( ^  K% K
  10. {
    $ `. V. e% Z+ |5 K; N; V5 ^
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
      Q4 x& m) ]5 J9 C6 m
  12. 5 B& z) V8 j& {
  13.     /* 禁止 MPU */% J: W. ]' }7 W6 u% G9 O/ ?
  14.     HAL_MPU_Disable();/ D/ x. I5 G2 y  c

  15. ' B3 q* W: V6 v7 {$ }. m4 o# B0 C
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    ( F' L1 z1 R! t( e
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;6 W7 [4 Q( ?' g& Y& g% W$ w! L
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    & X( r6 L& n1 T- o% h- q5 U! h
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;' J8 I3 W# ~6 D2 D# h( d% X
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ) h2 Y) u6 d/ p+ z
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ; P: `) X3 x( W2 u
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    ' Y( x, ^1 ^9 n% u
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;1 q$ J$ v/ ~. U- e% w5 R6 G( w
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;+ |! U4 C7 y* R$ e
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    6 K: |/ L' e3 [: Q" j
  26.     MPU_InitStruct.SubRegionDisable = 0x00;9 B& i' K& E0 f9 v6 \% L; q
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;/ c+ z2 Z1 Y2 ?6 Z2 P. m- `

  28. 3 I3 ~: Y# g- T
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    % b5 v2 E- _3 i: P

  30. ) @' x, `  {4 J9 P  O6 i2 U
  31. 9 u/ f3 K6 B* H* e: _
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */; h3 s, |" P+ D7 i6 a2 p9 q
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;" U! _/ t* T3 D
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;9 P" Z& i; q; O/ D9 v8 E
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    ( y. D9 d  `, l) B
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    % e4 {2 T% r6 j) y1 i- W, u* @
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;9 ]. H6 g  h' M- r
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    ! h0 ]- ~2 t) S5 L9 {; I
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;% P0 @. m, r# L+ l; O/ R
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    - x6 x# A) D( E3 ?$ L
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;/ H$ l& A7 a  L
  42.     MPU_InitStruct.SubRegionDisable = 0x00;: w9 Q( y1 u3 d9 {
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;1 d! P8 W- W/ O, r& a
  44. 3 j( @. p# H; K& R8 C
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    $ `: D) A: ~$ F+ `& t0 k0 ]

  46. 9 a+ h% J8 @/ Z% x" i
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    3 P/ h5 B6 ]. K1 J9 [/ M8 [
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    8 W& u7 w) _& u6 s& Z$ Y
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;8 W4 [# j# A# a7 w# p/ ~- e+ Z
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;* K9 f5 j" O! @4 q% S) P: B
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% R2 y8 s# |8 C1 }; y& @5 R
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;* d  I) g2 q" @7 j0 T+ V
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;8 L& g) m" |, _' `7 F. Z# _, U
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;# S1 [6 k' A: P$ z
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    # \" A1 E4 T7 y3 r8 X: d
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;/ ]4 ^# f% m; Z2 I/ b5 e
  57.     MPU_InitStruct.SubRegionDisable = 0x00;5 V: P4 F4 o( j3 T8 x1 d  ^" c, x5 q
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    7 w" @( G1 d# m

  59. 0 F7 ]3 g8 ^9 d
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    8 X, T8 ~5 N& j. ]; L
  61. ; O6 k" c# x# v$ b- @; K# ~
  62.     /*使能 MPU */  ?4 f6 ^5 b0 k! J
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    0 E' d* Z4 t+ c2 j9 p+ \" {% [
  64. }' h% |) E9 @) C
  65. 5 s; |# q/ a( a, ^3 r
  66. /*
    9 o+ S: X: a' A$ U+ j& v
  67. *********************************************************************************************************
    $ m' H; k* E# U8 f7 M, |# A
  68. *    函 数 名: CPU_CACHE_Enable
    # m7 p4 T/ K  r
  69. *    功能说明: 使能L1 Cache; W. l" E$ [3 `# V  [. S
  70. *    形    参: 无# I6 A/ x2 x4 o
  71. *    返 回 值: 无
    ! T7 Q; J9 {- S' F+ a4 T4 Q; p7 |
  72. *********************************************************************************************************
    : M; L. f; }9 f6 Q9 M1 P' j
  73. */. Z& J- G" x0 Y0 c9 p! q
  74. static void CPU_CACHE_Enable(void)2 f8 O$ M( G8 T
  75. {
    ) @& [8 g- A1 e
  76.     /* 使能 I-Cache */
    . w5 W* I. U$ {) s0 S0 h( @( L+ A, q
  77.     SCB_EnableICache();
    " O, a" T$ F( u1 R, _' H7 {

  78. " `+ Y- C7 y1 _
  79.     /* 使能 D-Cache */
    4 {# f1 d8 J9 H5 T( l! ~
  80.     SCB_EnableDCache();1 T+ u+ s7 ^; O- a- z# D% v
  81. }
复制代码
$ i6 d' q$ K) o5 t5 W  R
  每10ms调用一次按键处理:$ n% R# n" \- l" o! S

9 a4 [3 ~8 Y, |4 C按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。, k4 J5 Y$ f4 D$ ~' u1 L

% i0 l+ a3 {5 _
  1. /*2 `2 p1 v( a+ F' v6 _* o
  2. *********************************************************************************************************
    $ x: ?# N7 V( m- h
  3. *    函 数 名: bsp_RunPer10ms
    7 C0 M; j# i. T5 Z% D
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    / P) m& X( t- ^. T4 m: r
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。) o& F+ x+ o7 n. _) B6 s
  6. *    形    参: 无
    4 \- x  F% K& ]; }* C2 A! E: E
  7. *    返 回 值: 无( ]! E8 E/ l: D- k" }- p
  8. *********************************************************************************************************9 `+ ~! j7 Q; S3 R  W) v$ T7 m: z% b
  9. */* X% E2 R( U4 o. o" W, [( @5 h
  10. void bsp_RunPer10ms(void)
    9 [& i% Y1 C' S, ~: V' Y
  11. {
    7 z3 w% P8 k* j, h0 o
  12.     bsp_KeyScan10ms();9 f1 ]! h( i9 c' G
  13. }6 d& w3 v3 n' X% X8 ]
  14. 5 b5 b( V) Y7 U! i- ~6 k
复制代码
- }1 H% P) u3 l2 ?4 H9 [+ v
  主功能:1 O0 z/ O4 C2 s8 \  h

$ d; C  S3 _7 e# Z' d主程序实现如下操作:
0 ]$ Z" Y. W7 ~3 d
% S' b! J, T. T4 ?! S& }  启动一个自动重装软件定时器,每100ms翻转一次LED2。/ h8 o' T7 a) @, S% m! l$ f
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可5 Z- u9 T- W) F& J$ U9 H
  请选择操作命令:
! U1 l  b1 x" c2 q2 y6 I- v  1 - 读串行Flash& W$ r8 }5 {) ^# }4 ?# G6 I
  2 - 写串行Flash
2 p& A) t/ i/ T  3 - 擦除整个串行Flash
2 f" y. Z5 V  }8 w% z4 @  \  4 - 写整个串行Flash% G' j4 H. }* E  M% C3 ~3 v  u
  5 - 读整个串行Flash
: R3 i% j( ]; r, J; {: A  Z - 读取前1K$ Z. H& r% C: ~! x
  X - 读取后1K
7 T& Z& l0 K& v8 a7 U# H4 Y) U
  1. /*6 R" J# O8 V1 C7 k- N. s. z2 K
  2. *********************************************************************************************************
    2 ^2 E' C, X; y9 G% n
  3. *    函 数 名: DemoSpiFlash7 ?/ t1 I( \. V; `. x
  4. *    功能说明: 串行EEPROM读写例程
    2 V( @& G3 L: Z5 t: ~" o& T
  5. *    形    参:无; z% n& P# Z# d; t/ V# v3 ]" L
  6. *    返 回 值: 无+ @' e) E. g# E1 ]5 l7 N( h1 n% M
  7. ********************************************************************************************************** R1 H! _# f  W- }% l; e
  8. */
    ; [% R  a8 h( l
  9. void DemoSpiFlash(void)9 k' b# v, z6 A+ K) ]; [* r
  10. {# a3 R; p& p8 F. e  R0 D9 N3 \
  11.     uint8_t cmd;" R6 D* i8 V6 y' _6 T( o
  12.     uint32_t uiReadPageNo = 0;
    + E% I# m7 W, {9 [9 P$ ?8 N
  13. " m0 u! a2 P6 i. D* X3 Q
  14. 7 z; X3 j  Q) n# t- ]
  15.     /* 检测串行Flash OK */, g6 R+ C: R6 X! w& l4 q
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);7 l! f' [) {5 X2 _" q" j, r* ^( {
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    " o& E7 G/ ~$ w8 Y

  18. + B: w2 o% T$ W1 i
  19.     sfDispMenu();        /* 打印命令提示 */
    : J, h& u/ N) q0 ]) v7 Z1 z3 R, ~

  20. . g0 [' Z& z8 Z
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    , j6 e3 K# R6 _2 v: G

  22. 7 y' Q$ y" H3 ^/ j
  23.     while(1)
    - w7 k4 u6 Q" H: v, q
  24.     {
    1 `1 w5 Q8 j9 r8 u( m( o* X" e2 X
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    7 k( f' D7 O4 D! E
  26. 0 T  h4 a* P& D/ `6 U7 C; N
  27.         /* 判断定时器超时时间 */! x9 `( o0 o" ?; q
  28.         if (bsp_CheckTimer(0))    " ?, K* z$ H( C* P4 m
  29.         {1 F0 K% s% o# T) L1 r
  30.             /* 每隔100ms 进来一次 */  
    * Q* M8 X! y7 y4 i% {
  31.             bsp_LedToggle(2);3 H) Y6 J) O6 T$ T+ c
  32.         }
    ' J8 G% p! C7 [. Q9 T
  33. # p) j5 \* m" p" A" i" Z2 z
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */$ j- {6 E  e. @3 S  e* `8 e
  35.         {
    2 c% D! z0 z  e- q9 H, F6 {* F
  36.             switch (cmd)
    , e) }8 j) Z  `0 n, _0 L
  37.             {
    ( \1 J" v/ ?$ r0 n3 Z9 M  ^5 D
  38.                 case '1':" U: u% h0 F* }2 E# c  X
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    * e) y% _1 A2 f& O) _0 g. l1 u% x
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    ' m5 z- M9 f* M; F3 C" i
  41.                     break;
    - H6 s$ _) O" U# q8 K+ p% d

  42. ( _" H2 S: z5 b1 S" B' ?9 R
  43.                 case '2':# C: \; F$ r5 l6 |4 m3 y5 ^6 L" T
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);) j+ I. ]; l3 |) z$ C' N+ q0 c
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 *// Z5 w$ w" Z6 H; c/ L
  46.                     break;' J/ ?' G7 h& y: W5 h8 f* n* l
  47. . i) R: c5 F' m
  48.                 case '3':0 l3 K) D- b/ t3 F4 e4 o. _% n
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    : E5 z4 ^( ~4 H
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");2 \' ?5 g! Y6 {
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */7 R* B* M' h& a7 {9 U) ~
  52.                     break;2 s' o0 ~( U! v7 K. C* S( f

  53. 6 ~3 H% Y# t& V3 b- ~
  54.                 case '4':8 d& @' W$ S% S& I
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    4 W* H5 o3 E2 l8 ~4 D6 _
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");' k  a+ R" [0 s! C$ S
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */1 u# H& A; b$ {4 |# }0 j" x
  58.                     break;
    * `9 }" C# Y. T7 }8 R

  59. & m2 c) d* N4 w9 B, {' C7 s- Z4 a  P
  60.                 case '5':% @$ N0 X% T/ \/ }# [) Y
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));  D) @" \; D0 {4 c$ F
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */  d/ Y8 F( [' q6 j+ [$ F
  63.                     break;
    , G( f- U0 P" F$ M
  64. 3 c. {2 H5 z8 P. X+ a" P, ]
  65.                 case 'z':
    + i& j* N- G/ ^5 h9 u
  66.                 case 'Z': /* 读取前1K */' C+ _1 ?* ~& O
  67.                     if (uiReadPageNo > 0)
    0 X' J' y* d$ z7 M8 j0 D, R
  68.                     {7 ]5 |. m, N% @! X8 ]' F8 ~: c$ e
  69.                         uiReadPageNo--;
    $ R( c- ~1 [5 G8 T' Y
  70.                     }( r% H- g/ l% d8 m3 d4 t
  71.                     else
    ' `+ z+ \. A! @; `' K
  72.                     {
    / G' v3 d5 C, b$ |$ H  F- I
  73.                         printf("已经是最前\r\n");
    % O& x5 E* T1 D6 z) w. Q5 P
  74.                     }
    4 s+ a  L/ k- y7 B, q, [2 V
  75.                     sfViewData(uiReadPageNo * 1024);
    ) r& x  P1 b6 Z1 Y
  76.                     break;
    4 Q: L  W5 G3 x/ \2 k+ c

  77. 4 r: ?  o6 {* z# B5 C
  78.                 case 'x':
    ' P( ]! W% Q. T8 I  s
  79.                 case 'X': /* 读取后1K */
    4 y! Z, S0 ?8 \2 a+ E( `! G# g
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    ' g7 S- X6 |! c5 j8 g& Q$ d
  81.                     {% A9 p# N# w6 f5 q7 `
  82.                         uiReadPageNo++;
    7 U7 Y% B) I; }7 C! G- U) ]* T
  83.                     }
    # o& c% z/ z0 v
  84.                     else
    2 g7 i: S* P! `7 E5 @6 a, h6 O; C+ I
  85.                     {, }% o) h* V/ F4 F1 P: S- V; [6 }
  86.                         printf("已经是最后\r\n");- E  M8 c5 E& o) i$ Y+ b
  87.                     }3 a# b- Z- l% O$ @; K6 [
  88.                     sfViewData(uiReadPageNo * 1024);6 t: s7 p- h! H: p0 d
  89.                     break;
    / m: l3 e& J$ u. [2 R  Q" p
  90. # M" N' m" s2 Z. c% n
  91.                 default:
    9 a8 t& W/ {) B8 g
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */' ^! ]: P" J( x
  93.                     break;
    % E) n" F% q; |3 {
  94. + U; p( ^6 W" t$ B
  95.             }& l! ?# h! f7 `7 W
  96.         }
    $ i, _9 z/ S2 ~" P: n( @1 T; t
  97.     }
    ) @6 O. C8 q0 F/ K4 H4 ]2 b, W3 J
  98. }
复制代码

% s  K; f7 l7 A( E( T73.10          实验例程说明(IAR)

7 Q+ R. T( a* F8 s配套例子:
  u: B* t* D2 v6 [% V+ L
9 A# w4 a. K4 c( HV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)$ U$ x' |+ S0 b/ j% o8 P0 G
% m3 o0 f: ^3 D: o
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
- O& g: j5 ?, W- H7 g% g
% m, D2 n. _' f  b  b% xV7-051_串行SPI Flash W25QXX读写例程(中断方式)
: j5 S/ ]( Q# v% x* l9 X1 |$ e  g& Q; r) Q1 P( {8 P" U  p
实验目的:$ ~' Z# Z* D9 h  t7 h
$ Z5 l. L( U, x1 p: U3 n( c5 {
学习SPI Flash的读写实现,支持查询,中断和DMA方式。! |! W  J8 j  {& n5 \

) C" t, Y3 q! p- n实验操作:
( W1 S* o- s$ {0 M$ B6 S2 A- L$ X/ ]- w3 s* o9 H
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
% G8 e) h7 s3 c# m5 P. R0 Fprintf("请选择操作命令:\r\n");# l8 q" \5 h9 M6 u0 z
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
4 n. e# r2 a; a! l; z, Aprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
& V+ o  O% K, P$ _" fprintf("【3 - 擦除整个串行Flash】\r\n");; C- e2 \7 `& _9 z5 p# M. U. Q& ^5 Z
printf("【4 - 写整个串行Flash, 全0x55】\r\n");' v& V% J' O* h2 L& {6 x
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");
6 W7 I0 j% y8 w% f" _printf("【Z - 读取前1K,地址自动减少】\r\n");% z, x) t) f* s+ L% c2 ^
printf("【X - 读取后1K,地址自动增加】\r\n");
- d7 B; w2 o0 ^/ O$ ~0 {, oprintf("其他任意键 - 显示命令提示\r\n");
1 t8 Z4 s. q1 _* O5 f0 i8 `% p3 ]# L: Y
上电后串口打印的信息:
$ d4 l5 c. i7 X; u6 M' ?" L! T6 E# b1 f! e7 o
波特率 115200,数据位 8,奇偶校验位无,停止位 1。9 L& J5 ^  e  w

7 G$ J8 A" D5 D3 B
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

3 D& W, ]5 k- r! j, {+ a) n/ m) m6 H) i5 {9 v  z& O: {: j
程序设计:( T0 d% ^" P- q: H
- l2 ~- U3 P  e0 I- m9 O( F
  系统栈大小分配:
2 A% Z9 n6 r% Q6 l' p/ U
" g5 @/ U& O! q' J2 x
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

- @% Q/ P% l8 U% r+ _! H& n) J4 D, E- k' w% k9 e# V5 k0 S$ p
  RAM空间用的DTCM:: ^( y8 O3 E" ?
9 r0 v/ `3 \) ?$ ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
* B- i9 ~# r6 `

+ I; b6 I/ n, \  n" ~0 P  硬件外设初始化
& ^! C/ w; }9 I% {. ], a
4 u9 F2 h: q% E) @4 q6 n硬件外设的初始化是在 bsp.c 文件实现:/ v- P4 @9 z3 L* S/ }

4 T' i. z& s) G7 f  |% e
  1. /*4 Z- h- o4 D( A& y: M) G0 }
  2. *********************************************************************************************************& Q7 T. Z3 h+ C) X
  3. *    函 数 名: bsp_Init
    5 g8 E$ w  J3 z. ]& x* P' Z3 s4 f8 v) D6 H
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次4 O* d) Y7 i, A
  5. *    形    参:无
    ( J- e! v' W) g7 N  ?
  6. *    返 回 值: 无
    % k" D  j. S: \/ P5 g
  7. *********************************************************************************************************
    " B4 {$ A+ [1 ?8 a' j8 z* W% X
  8. */
    8 f* p1 n' ?% w, r& B( `! [
  9. void bsp_Init(void)7 B. K# W3 ~' H: o, Z' e4 E
  10. {
    ' R5 F) x- {/ f1 S( \1 X: |: O
  11.     /* 配置MPU */3 \( o) ~6 h& h
  12.     MPU_Config();( c5 x0 W' \' ?3 i
  13. 5 D" ^+ a: ?: W1 L3 ^
  14.     /* 使能L1 Cache */
    0 j% ]( l8 a  |6 k4 G
  15.     CPU_CACHE_Enable();' O+ E4 j9 v5 W5 w: x  d

  16. 3 Z$ H. u+ s, [
  17.     /*
    + q) G" D9 h) @& k; t* C3 R& m, b, Q7 d0 m
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:6 e. O% [/ [- {4 C/ V
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
    . X& p( O7 F, L+ u& V: F  p2 ~
  20.        - 设置NVIV优先级分组为4。3 V- D& v+ G, i$ t+ N
  21.      */0 C. w! U: l8 H4 Q0 N8 X" ~2 @( c) j
  22.     HAL_Init();  r& t6 ]' u# G

  23.   A# B6 C8 r$ a$ [5 A$ M6 M
  24.     /*
    ) O4 e! |2 I9 k3 O# o& i% X% N
  25.        配置系统时钟到400MHz+ o% |( t7 M! O4 ^
  26.        - 切换使用HSE。
    $ o6 {6 S3 e8 k; {% s" I
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。+ |! ?" t4 A# |  K/ t; i; K
  28.     */
    5 E: x; U% ~% m
  29.     SystemClock_Config();
    ; h) p8 V4 }9 B: L. ~

  30. ' J% K) l4 A7 ?
  31.     /* 0 n4 V" Y8 ~" P% W4 Q$ m8 P
  32.        Event Recorder:
    : H3 n+ ^5 y1 _4 j/ b! Q
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    5 S! W- _6 ]5 [# i" ~  ~- L& k
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    " j. K" ]" k: e) l
  35.     */    ' |. s' T# ?: T6 h% n
  36. #if Enable_EventRecorder == 1  
    4 l" ^4 h2 ~7 n; |" h
  37.     /* 初始化EventRecorder并开启 */
    ! m. U4 p1 B% U- R$ T
  38.     EventRecorderInitialize(EventRecordAll, 1U);8 M" E( z  c; ?6 t/ @$ _
  39.     EventRecorderStart();! r, O3 @* G' K, }* O+ W
  40. #endif- g$ r3 h- o7 o9 L3 f
  41. 4 Q# }  [' j7 T- I
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       . b- q1 y5 E$ L! ~5 U2 v# B& W9 ~
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    & W5 w* p5 L  Z2 y; M
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */
    2 W" W; B0 W) a! ~& d/ m& P0 _
  45.     bsp_InitLPUart();    /* 初始化串口 */
    / H5 ~" Z0 h6 F" H! l, z  ^/ Y5 Z
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
      z" h. n0 J! Z
  47.     bsp_InitLed();        /* 初始化LED */    , l$ |  v' x. F
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    2 A, u6 t% d3 @' b4 S$ K

  49. % b, d/ w) [& X$ S- W) w- Q) P2 E( t
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */3 L7 b% m: R  Y+ w
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        # y0 D3 U! t4 d
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    9 w4 S* C% J0 {$ e& n
  53. }
复制代码

8 v- ]0 L6 X+ p; J; i' Y9 V1 F  MPU配置和Cache配置:  G/ \% q+ c+ _0 y/ N( a' P
' T- }- D6 A: k  v8 G
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
( v# R; B; m0 |% f+ x0 o* c% A& W3 R, |  e9 c
  1. /*
    7 a5 B6 y+ H+ ^7 E
  2. *********************************************************************************************************
    0 G# r& ]3 B0 W% J
  3. *    函 数 名: MPU_Config7 H+ O) l) [3 {$ p
  4. *    功能说明: 配置MPU* G' p6 ~% p7 A
  5. *    形    参: 无
    , k! `8 t6 E# x5 a7 r% Y
  6. *    返 回 值: 无
    $ X8 z. J, Y( ?
  7. *********************************************************************************************************7 P8 u# V! _% d
  8. */
    ; K$ c. j( R* J. {* T
  9. static void MPU_Config( void )4 [  \8 o2 O! J  m
  10. {9 M; K: Z6 i& ]; }' e
  11.     MPU_Region_InitTypeDef MPU_InitStruct;$ J$ g+ a' q( H7 w& l. l3 G1 c
  12. 5 w( ?1 f5 d  O% p3 S! ^2 }
  13.     /* 禁止 MPU */
    - H5 b. _. b9 M6 X9 j9 q
  14.     HAL_MPU_Disable();. n* ~! ^0 n* ]3 I* ]8 s! W4 Q

  15. ' y5 x; ^5 z+ {' B
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */$ {" L. G- I$ I7 k% n
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;" x* u4 ^! `9 [: F$ o) n( w$ q7 u+ Q
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    ( Z# [# V% K6 F0 L
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;" I- w  U& r5 d$ [
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;4 u# J1 z" R5 Y
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    9 \; D* B5 Q7 M
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    ' t* x, J0 H4 _1 T( E) P, v, t& A
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;' i8 H% M1 X' A: R3 V% t
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;3 y, S3 e  [% u2 t
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;5 k) @  X. v) U: t
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
    1 l$ ^8 t4 W* N% r+ s: m  `
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;8 m" B: U( V1 w' D& y
  28. - m$ ~2 A$ B' |! W) }6 J, r- c
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);2 K+ A) Z4 A  T; M

  30. # L, N( \4 _% f% Z3 l+ \

  31. + k2 x5 y+ y6 B. e7 T
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    : s8 w3 {( z4 G7 t2 y
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    , j$ a2 B) f% r
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    7 w" `3 |; b) s9 `
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    3 W- l$ G/ Y5 ~% @
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    : j& K7 q0 ]9 g1 W& I- J  |6 m/ r
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;3 {) i" T: b! f' t( e+ E
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    0 E& @, B# D6 b7 _8 {: t7 p
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    ; j- q1 x9 ?; S: k0 _
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    , f9 @1 P( Z6 Z, B# _, N
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;( u9 J0 I& M" U  ~) P# \1 _
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    7 \/ D; h) I: k/ s1 o% Z; M
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ' n5 Q: S5 n& |# m$ P* ^; }; ~

  44. 9 O$ U+ x+ T2 Y6 w% @: B
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    7 e% R/ W4 m: r( b# ^3 U( N" P8 {
  46. 1 H# O) C( [& ~- h/ Y4 p
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */& G* L: E& W& Y; V; T3 @
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;; S8 Y! S9 A% y
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    ; l" i: [5 _" N
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;
    : ?5 o3 H8 S& X& O
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;; D1 j$ \! H( f$ C) k: r
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    2 x% V  e1 x0 w, v0 v
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    # m6 h  L, f4 o- f( G/ ]8 v
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    + }- r1 ?; X6 {
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    + x' E! n: C/ J( o/ s- s
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;! d6 i# Z# m7 I' c  @2 h
  57.     MPU_InitStruct.SubRegionDisable = 0x00;( `7 V5 e. f/ M9 h5 b
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    % G7 t3 K& _% p5 D$ P
  59. 4 f2 Q" ~- z6 `
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);* R; _4 D" W" g; u. c5 \
  61. % w: A4 ~' S0 }: e3 j) J
  62.     /*使能 MPU */
    $ f, e* e  c$ f3 J
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    3 b5 M' N" ^: c5 a) D2 c
  64. }
      Q' x& k5 {2 I# r+ K. j
  65. , Y; t$ z  I6 W
  66. /*
    6 ^$ E# N6 l9 f4 l  a6 l
  67. *********************************************************************************************************
    . n: X! i7 \: u/ U7 }6 l3 {" M0 n
  68. *    函 数 名: CPU_CACHE_Enable
    ' Q2 E8 c* G2 U4 i4 S7 N
  69. *    功能说明: 使能L1 Cache
    + w  W6 q2 |( Z& S/ W+ N% ^
  70. *    形    参: 无
    - c1 W3 C* \& p2 p3 n, r
  71. *    返 回 值: 无5 I( I2 n' x( ?  u! ]1 R$ w9 M, ]
  72. *********************************************************************************************************
    - ~; ]% f" i2 V+ G4 m
  73. */; ]  ^. X2 B" H
  74. static void CPU_CACHE_Enable(void)
    ( W$ J5 [% {, ~! X& `7 T
  75. {
    ) D( S* ^! v$ H( W( W7 R
  76.     /* 使能 I-Cache */
    % h' u* \  B5 j) q! T
  77.     SCB_EnableICache();
    ! U: ~( W& H2 \
  78. * X7 c! E5 _/ b* z9 ~' g
  79.     /* 使能 D-Cache */" N6 d4 d: H& s5 c: Z+ n4 Q& |
  80.     SCB_EnableDCache();- |4 e+ r8 R& R* t
  81. }0 K4 h5 ]2 j5 F# r4 a
复制代码

* R  w0 ^, B" k& m
# ]+ u% z; b8 u/ u* {5 P  每10ms调用一次按键处理:
# }/ f3 O! b6 Z- d3 p
( B5 r/ ]( D0 J0 A按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。4 v' D% s; u; u0 c& f

, v/ z9 B' W& ~
  1. /*
    8 P! {/ a" _( Z$ w  m" K
  2. *********************************************************************************************************" I( k9 Z- ~/ F+ {$ H
  3. *    函 数 名: bsp_RunPer10ms
    2 V6 q3 i4 G1 t, A; r
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
    6 a' H; A+ `8 p
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。+ J% J8 k6 G. B4 ~( E0 I: u  o+ }" [7 l
  6. *    形    参: 无
    / t  S  M, m) U4 y
  7. *    返 回 值: 无
    ) B2 I/ a  t5 s7 V+ n
  8. *********************************************************************************************************
    4 U* E4 _0 d6 I0 X
  9. */
    ! u, e( ]( n8 U- v8 l
  10. void bsp_RunPer10ms(void)
    " [/ ?6 O. x4 I3 {, n
  11. {
    - g- g6 A& R& D% v  S# U9 t
  12.     bsp_KeyScan10ms();
    # Z! i+ B& f% n2 E# b, ?/ y( l. X4 L
  13. }
复制代码
7 V# s! M+ ~; Q& ]
  主功能:
7 K; Y: O3 X! Z/ Q- x+ P$ ~9 ]0 l* D' S( t& E6 B, |8 g
主程序实现如下操作:
# ^  J, @% U$ o  T: t
+ A- b1 u0 o) P% s) z% ~  启动一个自动重装软件定时器,每100ms翻转一次LED2。
; }5 W) \" E5 |. Z/ E1 U: u  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可3 d' B6 C) `/ O% I
  请选择操作命令:- G: F$ @. \) c5 P' j; `
  1 - 读串行Flash
8 x: p+ `- [' r  Y% @/ j7 p) C  2 - 写串行Flash
3 P, K8 ^) b, {  3 - 擦除整个串行Flash
" b4 `6 ^" S" S0 E" g' C( S' r  4 - 写整个串行Flash
" I% l2 k' R3 V) `% G5 `6 W3 Y. `  5 - 读整个串行Flash) J. v2 l: T$ q- d$ s1 a
  Z - 读取前1K' N- S( T6 i6 [. q" x
  X - 读取后1K' _3 q  s: I; q) U
  1. /*
    ( T/ ]! L' b3 ^7 q1 J# ]
  2. *********************************************************************************************************
    ' I; F( F9 U  ]
  3. *    函 数 名: DemoSpiFlash# U8 v) n. @7 v3 T
  4. *    功能说明: 串行EEPROM读写例程5 ~! T/ S* u( }) t3 ^
  5. *    形    参:无9 }: Z+ S! k& P
  6. *    返 回 值: 无; r" P( X, e. w2 u: T3 \
  7. *********************************************************************************************************
    ( W1 y) v4 V( }, G& G1 {( i
  8. */
    1 c4 @7 Q7 o" [3 g1 T
  9. void DemoSpiFlash(void)
    9 l3 O% m% i8 f3 L9 r  }1 n" `
  10. {+ R. i( y' n- h9 j" G
  11.     uint8_t cmd;# r7 @  N, {5 r  i8 [$ _6 s- S4 N
  12.     uint32_t uiReadPageNo = 0;6 {: [! X6 a0 h- B2 t/ ]

  13. / J5 y% Z' [# m* o% P

  14. $ t' f. N! b+ }2 z8 d4 s* W
  15.     /* 检测串行Flash OK */7 F4 C' n9 `% Q* I2 L$ |
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    " t9 j6 X+ n+ h' T
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    1 j1 q; W7 [: A5 ^$ m" V* F" W
  18. 6 E/ N# Q. g' N: r. f
  19.     sfDispMenu();        /* 打印命令提示 */
    ( d/ U( R4 @: o: o( C( j+ o5 C- x2 j

  20. . x: x: i7 f" U9 a; r9 }1 w
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */
    8 Y, J* c7 N$ F2 A4 B; H

  22. & N# k  h0 V5 \
  23.     while(1)
    + T# }: W6 y6 n, Z' W
  24.     {0 D: A( J9 ^- }3 V& G, L' N
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    $ {6 P* N0 e* `' A/ L+ ~( Q3 a

  26. 3 d* z: o8 h. z, J/ M
  27.         /* 判断定时器超时时间 */% d1 g1 M8 i2 ]
  28.         if (bsp_CheckTimer(0))    1 f% o8 Z3 N$ t' Z6 }
  29.         {
    ! Z4 A: N# C% g( y0 p$ B3 ^% |
  30.             /* 每隔100ms 进来一次 */  
    4 z8 A( D( B8 t5 H' F' {) Z
  31.             bsp_LedToggle(2);
    6 g  T: `0 \5 m* o) c0 t
  32.         }- p) Z) `" W8 ?, U; a' q5 r( V

  33. ; R: j" K  p* w
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    % N. e0 C9 e4 T2 l6 a
  35.         {
    - \2 _- d+ M2 M; \
  36.             switch (cmd)
    , R9 Y' \8 i- j; {- n4 A# P
  37.             {, d2 L0 u8 v2 I1 }4 W# P% G
  38.                 case '1':
    9 |9 R; M; E' Q
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    ( @, q1 r+ ~1 C+ r4 X
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    ; E* d5 _0 L: ?9 i9 q) x& ?' }0 n
  41.                     break;
    & W, i0 V+ n" {8 h* i
  42. ' b: n) k7 O8 x4 F
  43.                 case '2':$ E+ F$ l& }/ d$ f3 O3 f
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);6 P/ G5 E! s1 ]) h9 b6 _5 I
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    ! t( p8 \/ \3 X4 U
  46.                     break;
    - F" \6 ~: Y8 g+ ^3 Q" o
  47.   y0 x. ]; V& w9 q! _" s
  48.                 case '3':
    / J0 @8 Z+ I3 }" n; _$ ?# D9 @  o
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");* ]( U# d+ M- X% u$ p! s& n6 F
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    1 I+ _: G+ R2 ?- f+ \0 K* Q
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */5 n" O* E' I5 L9 d$ S6 f& e; v$ v
  52.                     break;9 Z+ k, w* a7 G% Z& o* O, q( ]1 u
  53. / Q8 H3 W& E& D& K$ D  e- E0 P
  54.                 case '4':' T2 m  W, Z* n  N
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");0 }: ^' q$ e9 F  }. g( {
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
    ' G# w3 Y9 g1 _5 B' t. l  E
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */: O" ^; a) e6 P& m4 q
  58.                     break;
    5 M/ E8 q( N% G' }! p9 D! e, t

  59. 1 ]  o" D; _) k  Q6 L( \7 P6 D8 X
  60.                 case '5':7 {4 K/ j1 l& h
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));" k) ~5 [' w( r2 i: V; x2 ^
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
    2 G' x# T1 H& ]) W6 {( [
  63.                     break;
    % a5 I4 j9 `# A/ ~  N* u
  64. 9 P" A2 q" c" s. G0 H5 }
  65.                 case 'z':) a: N# {7 ^2 d7 n- i
  66.                 case 'Z': /* 读取前1K */7 D$ ~' W$ T/ t& ^) ~
  67.                     if (uiReadPageNo > 0); s$ C" u5 C+ e* r: q$ h% d. t0 m4 O
  68.                     {7 K. Y  |! I  h# |5 j! l: ?
  69.                         uiReadPageNo--;; Q4 J, ?! `6 h3 }1 w% i
  70.                     }
      _9 e7 f7 A9 I% Q
  71.                     else
    5 G0 N5 S& {" Z% W: e
  72.                     {
    5 j+ j# ], Y  x3 `1 u1 q3 s& d- F8 p* E
  73.                         printf("已经是最前\r\n");
    * C3 D+ j; u7 I, U$ G
  74.                     }7 S, u/ Z3 b7 ?3 G- y+ G9 g
  75.                     sfViewData(uiReadPageNo * 1024);& _1 |( E2 K+ \# ^/ e. k# t6 u
  76.                     break;% T- w( z6 _2 y# |% x7 |* T# _! e

  77. 0 Z0 v- a- R. K/ q" H9 {! k
  78.                 case 'x':
    , m% s% q; D( b/ ]& u6 \, b
  79.                 case 'X': /* 读取后1K */, |( x' `( ~3 D4 l% H# e4 K
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
      X2 M) _7 g! L2 R% @) A
  81.                     {
    ' Q4 x$ m# [! M( l
  82.                         uiReadPageNo++;
    % F  X0 l/ e; p; r% l% S
  83.                     }
    : B( P) A9 A) V4 `- D# b( t' C
  84.                     else+ G" \& z, T4 ]& T8 w
  85.                     {
    7 L8 n' L  Q5 M) Z7 C  C
  86.                         printf("已经是最后\r\n");
    & N# j. D5 M8 b* f
  87.                     }
    - j+ e& f& S& g/ q+ l2 X% _$ e2 b
  88.                     sfViewData(uiReadPageNo * 1024);
    : @+ X5 H* ]3 l8 }* D7 t
  89.                     break;3 V& K: W; k& R4 t( Z6 J- w
  90. ) H: {& Y6 H% x9 N8 J* h
  91.                 default:! s, c/ @9 S( H8 u1 D4 m
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */4 j6 _1 M4 I8 z$ S
  93.                     break;
    ( A* t: @" i8 Q; L7 Y

  94. - E( }7 Y/ {, s1 S3 h
  95.             }
    % @; U9 Z% U1 [( S5 p( |
  96.         }: N. j  l, r2 A/ M; c: e- s- D
  97.     }( [" w# X' w/ `$ P6 a! n
  98. }
复制代码

7 L% @* k4 M* [73.11   总结, s1 i: A9 h0 Y- _- |# T
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。
+ ]/ y, g# z( e  h# w- O( i4 ~
8 g1 N# J) z' `2 G$ Z$ b6 P! Q0 v2 j: n/ n/ [- g& L
' V1 J& O3 I4 G6 y* a, H0 w
收藏 评论0 发布时间:2022-1-1 21:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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