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

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

[复制链接]
STMCU小助手 发布时间:2021-11-3 10:26
73.1 初学者重要提示! ^  Z( K  D7 X" F( J$ ?
  学习本章节前,务必优先学习第72章。
1 N9 q4 ?4 D# ?. |  W25Q64FV属于NOR型Flash存储芯片。
, Y- L4 m8 E. X! w; ~+ W  W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。2 U- H& I# `& ]4 Y- v+ d% V# h
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
+ j5 S* }/ v" C% J+ z/ f% J

: @. i3 H+ N! K( W5 _3 y- g6 Z5 P  本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。  P( e6 @% T) R0 D
  对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。8 }) Y- o/ z$ e6 c( y; \
  W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。
$ ?1 B3 Q' l, A+ j- u  文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
' i; u# v) r/ P" j  函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。6 Y/ I* I5 G( B& Z" K$ [6 z4 l
73.2 W25QXX硬件设计
! q5 O9 L6 a6 hSTM32H7驱动W25QXX的硬件设计如下:
7 M# F8 y% O+ U" f: p. K; P7 G+ u: H! d# b: P
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
( ]- B7 M3 i0 K3 D" q; s) ^

  u" n' l' T* t关于这个原理图,要了解到以下几个知识:
# V8 L; M: g, W+ q0 f" J6 P8 ]4 B
  V7开发板实际外接的芯片是W25Q64JV。2 y! i' h2 c5 d8 I
  CS片选最好接上拉电阻,防止意外操作。
7 u  y% s; {) s5 |) Z  这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。+ j. A- c- q8 j+ u4 G% u
  W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。
8 V  r# W+ i% i# J/ ^  HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。
( ^$ o1 q5 k! l* B' o$ l2 p73.3 W25QXX关键知识点整理(重要)
5 T! V- P8 {! @5 _驱动W25QXX前要先了解下这个芯片的相关信息。
. l2 M( o3 i- H2 A1 W% ^( `4 x- |0 {* V2 W8 Y1 @& p
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
, e0 o+ y" Y8 K" y0 y
. p- @- o4 C, [- |
73.3.1 W25QXX基础信息7 Z5 g/ m3 X1 `# I, u1 ^7 y
  W25Q64FV的容量是8MB(256Mbit)。
# N; _+ m" L& m8 y, G/ S2 D- K  W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。6 x) ^; M: a( Q5 n7 U
支持两线SPI,用到引脚CLK、CS、IO0、IO1 。- W4 I  w" K) a% _% u

4 t* R2 E1 C! I& Y& l2 X. H8 T支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。+ k9 K& l0 F1 x0 @5 w& a2 u
; x5 s* l) m' Y/ H; k. a8 ~
(注:这里几线的意思是几个数据线)。& ]( P5 c3 Z2 o! c% e
( x3 R! v# @4 N4 d8 r- d8 X/ u
  W25Q64FV支持的最高时钟是133MHz。- n" J7 T% r0 @* W! p6 G5 `
  每个扇区最少支持10万次擦写,可以保存20年数据。  f4 U, u) Y: N! u, Q& r
  页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
0 K) l' K: Z+ l6 }  支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
+ V" k" O  ]; `9 X' I  h3 k- m3 F整体框图如下:
/ j8 S  h, b" q6 ]& k: y7 z
" o7 x; T& @5 E! M* |3 ?6 T3 x. i
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
* h, h8 z- X2 ?. y
: \# a5 y7 J7 b, N/ B
W25Q64FV:3 q- ]8 V' B2 q
2 P) }- T1 E7 }1 [7 ?- W' J
  有128个Block,每个Block大小64KB。# F6 ^$ t/ i/ j5 N$ @0 D
  每个Block有16个Sector,每个Sector大小4KB。0 D, g4 @5 E8 W7 J# \
  每个Sector有16个Page,每个Page大小是256字节。7 H* `, v4 o( k. ~! r; I7 z
73.3.2 W25QXX命令) _4 {* q4 B7 a. P
使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:
5 e3 r* I$ p$ d6 O" t5 e0 g4 n  ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  ?: `: S+ B  t. Y. O" b/ w

% u7 O6 x- M8 g0 W5 {# k当前主要用到如下几个命令:
7 B6 F' r* X; c0 q: O% W* T8 q/ w) ?# v; ]
  1. #define CMD_EWRSR       0x50  /* 允许写状态寄存器的命令 */
    ( P$ v3 o/ z- N9 P' m
  2. #define CMD_WRSR      0x01  /* 写状态寄存器命令 */
    % e- \. n7 g( v; x! `& b5 ^0 T
  3. #define CMD_WREN      0x06    /* 写使能命令 */2 A; V# j: j) F; x. U. R" p0 q: |
  4. #define CMD_READ      0x03  /* 读数据区命令 */
    4 V/ L$ F8 ?) |' b- h
  5. #define CMD_RDSR      0x05    /* 读状态寄存器命令 */4 w2 m* L- G- Z( n$ D6 q  ?
  6. #define CMD_RDID      0x9F    /* 读器件ID命令 */. M- X9 ]( Q3 P7 r
  7. #define CMD_SE        0x20    /* 擦除扇区命令 */
    ) d* \$ ~( H6 r
  8. #define CMD_BE        0xC7    /* 批量擦除命令 */1 B, ]# R2 j. v3 ]+ T5 E& ~, ^
  9. #define WIP_FLAG      0x01    /* 状态寄存器中的正在编程标志(WIP) */
复制代码
2 R8 w9 p4 c5 x4 H
73.3.3 W25QXX页编程和页回卷
, _- a' g  Z" A8 Y# A; h. PSPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):6 d: F8 ^; \! c& k, S4 M

! y/ T% [0 A# }! x& e# S; m
  1. bsp_spiWrite1(0x02);                               ----------第1步发送页编程命令        
    7 c3 a' q! h7 y: C$ P$ Y1 k
  2. bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16);    ----------第2步发送地址   
    6 x: \( K2 k  w7 n7 u2 I" Q
  3. bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);   
    ( n! M! q, T; I. D
  4. bsp_spiWrite1(_uiWriteAddr & 0xFF);               
    $ X0 e3 T" S  h2 {

  5. / ^6 p5 H0 V8 P, D  z, {
  6.     for (i = 0; i < _usSize; i++)
    ! u9 p6 J( A  w8 M/ @; w! e! ]  I+ B
  7.     {
    ) `1 @( ^, U: c3 z, M
  8.         bsp_spiWrite1(*_pBuf++);   ----------第3步写数据,此时就可以连续写入数据了,, T, W/ U6 v! j: c' n9 b2 B0 j
  9.                                              不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。   
    # a7 _: L. z7 U$ y- m0 h
  10.     }
复制代码

" e) y; n2 H6 o/ P2 Z7 G: t7 Y页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
% |. S8 [3 @  F. s; o2 T: R3 V+ l+ n1 q5 [
比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
) G) N0 V0 h" Y5 n9 |3 w6 W
- d/ C( Y3 s7 t2 i% l3 ?) x
  1. uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};
    7 }2 Q9 \* O) j# P' S
  2. uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
# z- j$ q+ J. T8 H, U) |
  从250地址开始写入10个字节数据 PageWrite(tempbuf,  250,  10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
7 u0 v: o8 E: B% g4 h' r  向地址20写入1个字节数据:PageWrite(&temp1,  20,  1);- [% t1 _- s- N! Z# C+ Z: X3 t5 I
  向地址30写入1个字节数据:PageWrite(&temp2,  30,  1);- I$ X$ M+ u. a7 ?
  向地址510写入1个字节数据:PageWrite(&temp3,  510,  1) (这里已经是写到下一页了)
; B8 T& F3 [5 c7 [6 ]. X  y3 L下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。$ j; a: ]3 n8 E6 e0 n2 d
( ?4 O8 G. G: B8 l6 U: q
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
( g, w( p& S, x3 P) S1 [

( W( j3 L: e4 a) m5 K73.3.4 W25QXX扇区擦除- i, w! r. r2 f0 s2 U% a
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。
0 a0 z; y2 O8 m7 K  T* ]5 t, N8 C2 f1 ]3 b2 J  p) a
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
# X$ O9 @; L; _& Q9 p9 `6 J1 C0 ]0 _
- {2 \0 ?! W8 z) d# ?73.3.5 W25QXX规格参数1 J" L" Z* Q6 m4 g
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
- N) E8 g! h2 E. a" e! n4 y/ ]0 n  o0 ?- ~& B8 O- y3 R/ Z
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

  o, M. l0 S3 o! n2 d2 n0 c- ~; E
% q7 ?# k6 ]" L" k( A  V, Y9 n  页编程时间:典型值0.4ms,最大值3ms。
% K2 w$ }" y' q) H$ r+ B7 W  l# s+ R  扇区擦除时间(4KB):典型值45ms,最大值400ms。
* ^9 |6 I( i, H* \, f  块擦除时间(32KB):典型值120ms,最大值1600ms。
, m  E6 g1 o& Q  块擦除时间(64KB):典型值150ms,最大值2000ms。1 z" w7 Z; l+ @  j7 ?
  整个芯片擦除时间:典型值20s,最大值100s。) m2 j: _7 H* A1 a, {9 [
支持的速度参数如下:
1 G) ?  c) Y( w! V. |/ q
- `/ m5 L' z# [3 S( B, w
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

, v$ g* j9 D' K5 l1 ^. x# z5 W& S- f! B$ l7 v
可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。
0 l+ g; Y6 d" G6 Z9 p3 @) G+ ~& E1 y) H
73.4 W25QXX驱动设计
  Q$ D5 H8 A% d! P: \W25QXX的程序驱动框架设计如下:
" F$ `4 X( H3 w- Q$ a* p4 N# Y2 D
: b% e/ u1 ^/ O- X* [9 C6 Y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
1 n5 M; }( @: Q8 q) e" }; L
5 ~2 L  L! ~+ v4 n
有了这个框图,程序设计就比较好理解了。
5 f5 r1 f4 P& W7 @+ X8 G+ v$ a1 E
73.4.1 第1步:SPI总线配置
2 g% G+ a5 w" d$ b& e% y7 Pspi总线配置通过如下两个函数实现:
+ Q2 n# \4 b' j5 Q. b% j  W# g$ ]
! a( J% G% j2 S  a# g$ G' L9 W6 S
  1. /*. f/ @4 }* R! A
  2. *********************************************************************************************************
    # R6 {  z5 l& y3 t4 Y9 d# y, e
  3. *    函 数 名: bsp_InitSPIBus5 d" Q5 t% m6 K5 i9 b# C
  4. *    功能说明: 配置SPI总线。
    # m# t/ q; ]. Q( c5 k' z& I1 u
  5. *    形    参: 无9 X. I- [) w7 x
  6. *    返 回 值: 无
    % m+ w8 y+ T+ @7 x, v& q; p
  7. *********************************************************************************************************  H4 d, C' X0 U
  8. */
    ( s  ^7 d+ W8 l( W
  9. void bsp_InitSPIBus(void)
    ) G2 j7 [; m, h( j$ R
  10. {    + z9 `$ P5 N7 Y4 c
  11.     g_spi_busy = 0;
    " a% H) z+ d% J& A& K

  12. 4 u% N; B# j& W. L! w! M; `
  13.     bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
    & {' Y5 f6 J( z( i' V# t0 R
  14. }
    5 {! j8 n5 x7 |% v
  15. 7 q" V3 ]1 ~; Q) ~8 o7 s, A, d
  16. /*1 w) m, o: o/ U- O/ `1 K; P0 Z7 m
  17. *********************************************************************************************************, g/ D' t# \9 \. P7 i, R/ O
  18. *    函 数 名: bsp_InitSPIParam) L# g2 u: c" _8 j( {0 U) ?
  19. *    功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。( ]- j8 k" q$ O/ n* G" B
  20. *    形    参: _BaudRatePrescaler  SPI总线时钟分频设置,支持的参数如下:0 a& M" F$ `' L6 M
  21. *                                 SPI_BAUDRATEPRESCALER_2    2分频2 b) P' m* b& Q
  22. *                                 SPI_BAUDRATEPRESCALER_4    4分频
    ( J8 K( ]9 u7 Q: w" l
  23. *                                 SPI_BAUDRATEPRESCALER_8    8分频' M  A& @6 m+ _: i, R) O
  24. *                                 SPI_BAUDRATEPRESCALER_16   16分频- M% S0 E' Z/ ~5 G
  25. *                                 SPI_BAUDRATEPRESCALER_32   32分频
    3 W1 b+ _: q2 X' |* g7 k) G; E4 J
  26. *                                 SPI_BAUDRATEPRESCALER_64   64分频
    , _- e5 {/ t: F- z3 G; t
  27. *                                 SPI_BAUDRATEPRESCALER_128  128分频
    9 A7 `: h' X9 f  C2 f
  28. *                                 SPI_BAUDRATEPRESCALER_256  256分频
    , F# [) C. b0 C$ l
  29. *                                                        
    9 y% q4 x- l, y/ G7 }
  30. *             _CLKPhase           时钟相位,支持的参数如下:9 M8 o; B, x* n% n! R. L( d
  31. *                                 SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据7 e. y" ~% X: _3 x( |/ g* H
  32. *                                 SPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
    ; t3 X3 O  k  y% h! h( _
  33. *                                 8 x$ M- {7 Z3 d) }) W
  34. *             _CLKPolarity        时钟极性,支持的参数如下:' i: D1 `( ?; m) x% c7 N/ `& j
  35. *                                 SPI_POLARITY_LOW    SCK引脚在空闲状态处于低电平
    # f( t8 Y: O8 Q" Z
  36. *                                 SPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
    ( X7 W$ L: H0 M. C
  37. *
    ) G4 y3 b$ j% _0 s
  38. *    返 回 值: 无
    & g1 A8 m' o' k0 w9 U
  39. *********************************************************************************************************  O5 h5 f0 S+ `' ?6 T0 H& Q
  40. */
    ' K& q5 W. _  \" c! Z% Y
  41. void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)8 r8 l0 D* R3 [- K
  42. {
    # m0 B  a7 }( f9 U2 \6 W& I
  43.     /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
    , k$ N/ H) A: E/ _
  44.     if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
    8 x) Y; v0 u/ ]5 ]5 @
  45.     {        
    # e: ]7 c  o! d! r6 `( M
  46.         return;
    $ y: B5 X) f2 P
  47.     }
    % V* M2 [$ V  {- F+ j5 D
  48.   r+ @  L3 U+ s+ j
  49.     s_BaudRatePrescaler = _BaudRatePrescaler;   
    & s6 [: ?! `4 H* x3 q& A
  50.     s_CLKPhase = _CLKPhase;" e2 h' l. ~! Q- E1 h
  51.     s_CLKPolarity = _CLKPolarity;
    1 h# X6 ]& I  P, k, F2 j

  52. # m% Q3 x: ^" @5 i+ z- o* E

  53. $ T) v& d# R# I$ a
  54.     /* 设置SPI参数 */9 F  s% J1 Y9 G" @- g% Z/ q' e
  55.     hspi.Instance               = SPIx;                   /* 例化SPI */- `0 n9 U  D! O6 o
  56.     hspi.Init.BaudRatePrescaler = _BaudRatePrescaler;     /* 设置波特率 */
    % c7 @, d) @2 k; w- c9 _& {
  57.     hspi.Init.Direction         = SPI_DIRECTION_2LINES;   /* 全双工 */
    3 W' B! g% O- m; Z! q  U+ W
  58.     hspi.Init.CLKPhase          = _CLKPhase;              /* 配置时钟相位 */
    " T2 p% S3 O$ H/ b/ I. g
  59.     hspi.Init.CLKPolarity       = _CLKPolarity;           /* 配置时钟极性 */
    " a* k1 \8 K; k( q4 D. f
  60.     hspi.Init.DataSize          = SPI_DATASIZE_8BIT;      /* 设置数据宽度 */* V; E( d4 a$ D8 ?: s
  61.     hspi.Init.FirstBit          = SPI_FIRSTBIT_MSB;       /* 数据传输先传高位 */
    6 P8 v" R" {6 h- c
  62.     hspi.Init.TIMode            = SPI_TIMODE_DISABLE;     /* 禁止TI模式  */
    , N' k: A+ G2 Y9 C6 g& j: {
  63.     hspi.Init.CRCCalculation    = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */! b( s* f, ^9 F- ^, ^, V. Y
  64.     hspi.Init.CRCPolynomial     = 7;                       /* 禁止CRC后,此位无效 */
    " [! ~7 F  G4 B
  65.     hspi.Init.CRCLength         = SPI_CRC_LENGTH_8BIT;     /* 禁止CRC后,此位无效 */5 ~+ J" P: d) @0 S
  66.     hspi.Init.NSS               = SPI_NSS_SOFT;               /* 使用软件方式管理片选引脚 */5 d3 V+ _1 M+ l# R1 a5 O1 N% m
  67.     hspi.Init.FifoThreshold     = SPI_FIFO_THRESHOLD_01DATA;  /* 设置FIFO大小是一个数据项 */4 w: A# T' {0 q' j" Q
  68.     hspi.Init.NSSPMode          = SPI_NSS_PULSE_DISABLE;      /* 禁止脉冲输出 */
    0 ]# j: ?2 P0 \5 [4 j  s
  69.     hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */  
      m: _7 |, a( I  i- g( o7 _
  70.     hspi.Init.Mode             = SPI_MODE_MASTER;            /* SPI工作在主控模式 */1 k, [: l: ?: H' \
  71. & w# |7 m, }- v, ^/ m
  72.     /* 复位配置 */
    ' H  u; Z, P) S! L2 a+ V; o
  73.     if (HAL_SPI_DeInit(&hspi) != HAL_OK)2 B2 F$ \. i2 m2 }/ S1 ~
  74.     {9 _, G! @: O/ D$ h0 n6 J
  75.         Error_Handler(__FILE__, __LINE__);: l$ l) y% A# {7 j, H5 t, g6 F
  76.     }   
    # ?7 N% b( C. P! h% R+ N
  77. ( J" V0 m# z8 C5 P, \6 m
  78.     /* 初始化配置 */
      u( D2 G: r  g9 c9 |: u
  79.     if (HAL_SPI_Init(&hspi) != HAL_OK)2 e4 e* K. a" A* Y
  80.     {
    8 j, E0 C8 [( X# Y, N
  81.         Error_Handler(__FILE__, __LINE__);
    . U2 U' R2 K" F: D4 E5 B, [
  82.     }    0 z3 n( o* W1 I+ @  _( b
  83. }
    9 s, f, o) M- B+ i
复制代码

/ }- `) E8 m% l, p关于这两个函数有以下两点要做个说明:0 t" n' N0 q- b0 E4 l
( B5 k1 c+ L- r$ i. {. o- \
  函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
, ?5 N* c! u' U/ N3 |  函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。
4 r* K  y- |4 x; |73.4.2 第2步:SPI总线的查询,中断和DMA方式设置1 [4 X! A9 C5 L0 a8 v
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:& l5 F$ }3 u6 E. s- r' w
, A7 ^. g# i4 x7 e' W
  1. /*
    8 K- [' k& M1 f, o- {) g+ @& p
  2. *********************************************************************************************************
    7 h: H) o8 o, O. H/ U# _* u5 f  y- x& |, S
  3. *                                 选择DMA,中断或者查询方式2 P3 A) _! c; b  E  X8 t( }
  4. *********************************************************************************************************
    ' D) x) J# b" ^" W1 Q  H! g1 H2 ~
  5. */% ^( R# E  I$ N9 C. O, m
  6. //#define USE_SPI_DMA    /* DMA方式  */$ e$ Z% F9 M7 e
  7. //#define USE_SPI_INT    /* 中断方式 */5 G( }9 ~# f* q) ?- |) J) |9 Z
  8. #define USE_SPI_POLL   /* 查询方式 */- F8 M% Y. I' Q3 f

  9. . r3 [4 O) v5 F5 _/ n# Y$ z
  10. /* 查询模式 */
    & y- `5 _4 p  E  ^0 e
  11. #if defined (USE_SPI_POLL)
    2 {+ J9 y' i* a2 f
  12. 7 f: W2 g- Y. {, ~" Z6 n
  13. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];  
    & e2 J, z; w: j9 u: H
  14. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];+ ^1 z, E. F- c4 p& y. ]7 y& K. ^+ s
  15. ( `$ G/ m  z- i1 i
  16. /* 中断模式 */
    3 Y9 I- k$ Q2 S* q
  17. #elif defined (USE_SPI_INT)
    ' X' G9 t. j. A  l4 w

  18. 3 _) ?! k( @5 ~. w1 \
  19. uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    . P7 @0 B4 ^# R# `) l" b  K
  20. uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];. T! B& Q# D- {% H

  21. * ?9 B8 s0 R0 l( i8 ]
  22. /* DMA模式使用的SRAM4 */3 }2 V4 ?- t, v: C+ B; V' ~* o/ Q: V
  23. #elif defined (USE_SPI_DMA)
    8 N* J9 R+ ?4 H  w. I+ m
  24.     #if defined ( __CC_ARM )    /* IAR *******/( Z; x' I9 a0 M. j- `9 Z) Z: I
  25.         __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    % Q- |% q- E) j; f) Y: \
  26.         __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];! U- m* f$ v9 l7 z% Y" L: m- [8 J
  27.     #elif defined (__ICCARM__)   /* MDK ********/
    * a' W; u% {0 C* M, g( L! i
  28.         #pragma location = ".RAM_D3"
    3 I/ \" D3 H% H% b
  29.         uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];   
    / Y  m9 o6 O5 f
  30.         #pragma location = ".RAM_D3"' ^7 w0 p, F' K
  31.         uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
    8 {2 w5 [  b) e
  32.     #endif
    . R  c- m7 H( x; n1 P) ?
  33. #endif, }! i+ l8 S# m2 ?; [

  34. + N2 W. G& @0 ?% U& S
  35. /*$ M& F0 R9 i9 J' @2 ]8 u4 U
  36. *********************************************************************************************************
    6 Y% V- T9 k1 R+ L' W- i
  37. *    函 数 名: bsp_spiTransfer% L7 N* P. r$ l6 S
  38. *    功能说明: 启动数据传输
    2 K  K7 }: M* Y
  39. *    形    参: 无
    " O8 ]: @7 ?8 i
  40. *    返 回 值: 无+ v# R" ^, t( `" S- ~3 P$ B
  41. *********************************************************************************************************2 M3 }! X; }( j3 D$ P9 e& h1 a
  42. */
    4 D. E/ e- ]3 h+ F3 A2 }
  43. void bsp_spiTransfer(void)0 k* O  U9 x! R; f6 [
  44. {7 V2 B4 l+ z; @  q. F8 H& F
  45.     if (g_spiLen > SPI_BUFFER_SIZE)* S  B- e9 b9 M" V8 H# X4 C- f
  46.     {
    - U. O# [6 E  K# \. m5 f4 v
  47.         return;
    . v( M4 }, H) v: q- j
  48.     }
    ' P% P: m/ }7 k+ F! q8 P! d
  49. 5 m* ?3 c8 M# Y4 [
  50.     /* DMA方式传输 */" A. S4 O. ~3 h1 O! `; C
  51. #ifdef USE_SPI_DMA; Q; m* y, d! q, V
  52.     wTransferState = TRANSFER_WAIT;
    " K5 ?' O1 ?5 k+ b6 p% C4 I. D
  53. 2 S1 e0 S0 n* \$ f5 |! K- X
  54.     if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)      s2 V# [$ p4 i, H) ~
  55.     {
    # V5 l! [7 [) l1 C- p3 v3 m- X# F# i
  56.         Error_Handler(__FILE__, __LINE__);
    7 g/ o/ S2 h" M. h
  57.     }- x- G! o; N( V

  58. ) K2 `" q: H5 b& v3 ^2 h3 L- a
  59.     while (wTransferState == TRANSFER_WAIT). L3 O- N' a! b" \% O
  60.     {! ~% s! K9 o' i% r4 y
  61.         ;6 F. D% q% E. b% \
  62.     }$ A/ b2 [  @* S2 K# A: p" A
  63. #endif
    7 t6 [* ~5 {+ d# O. K
  64. + f. {4 Q) y' o" @! ?
  65.     /* 中断方式传输 */   
    . R" N5 t" W' A2 E) A! G
  66. #ifdef USE_SPI_INT4 l6 t$ j! ^1 z# V2 c: ?3 v- @0 L
  67.     wTransferState = TRANSFER_WAIT;, _  f  T  C# V" C- d7 x
  68. / H. @9 D  t0 f8 y
  69.     if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK)    & C" o; z7 ~) q0 ~. `) p0 d2 S
  70.     {, z; g* K& i2 @; |- W  J
  71.         Error_Handler(__FILE__, __LINE__);" r7 P. {' E! \5 K) b! R
  72.     }
    ; C, x$ L) u4 G. K4 S& D8 {  J
  73. / t. Q0 }: F2 ~: m) q) L
  74.     while (wTransferState == TRANSFER_WAIT)5 x" O3 L% g: y  O1 `4 |4 u$ b
  75.     {% K/ X7 Q" F, I6 m/ d# l. I9 t& e& ^
  76.         ;# ~% J0 Q- v% m& X0 h+ V" b, L# }
  77.     }
      j) w' A5 I! O3 X1 x
  78. #endif
    8 G8 v$ T( U  k* ]5 K/ G' ?) c
  79. * n+ F: R  X; X; {
  80.     /* 查询方式传输 */    * g- m( {/ e& i  z2 }
  81. #ifdef USE_SPI_POLL& g, y$ G" Y( {. n% C
  82.     if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK)    8 v- j, F1 E2 P- J: F' G
  83.     {) |) |3 E6 b8 D" f
  84.         Error_Handler(__FILE__, __LINE__);
    ! N8 Y, P5 _( x0 m
  85.     }    - I+ j& b1 I9 Z$ v4 L
  86. #endif& F4 s" W) n- x8 D: W  h6 D8 n
  87. }
复制代码

. ~+ o; z9 u) K9 i6 o1 X  @9 I通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
4 X( U5 f2 Z/ z7 a( b3 {: e4 [5 A" m# m6 ]
  通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。- I/ Z& ~$ Z9 u3 n3 P  E$ q5 k, j
  由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。! W( z  D% V3 ?7 \
  
  1. /* 配置SRAM4的MPU属性为Non-cacheable */
    : \8 m/ s. n8 `- e" I
  2.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;& k  m0 M& b; l7 f+ r# F+ \+ t
  3.     MPU_InitStruct.BaseAddress      = 0x38000000;* w3 a6 M3 ~  a4 |1 c
  4.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;. f. o% N+ c3 j) H6 A6 z
  5.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ) @4 D( _2 R- k
  6.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ( Y  \( B" f3 y: Z/ |
  7.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;4 P3 h& L/ m6 F0 z
  8.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    " I' S- z$ ^% G) {
  9.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;- d+ }* @/ i& J) O
  10.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    : f# o% w1 G8 ~! ?0 O! U$ j" U1 m
  11.     MPU_InitStruct.SubRegionDisable = 0x00;
    ! R  E8 K" A3 {; e
  12.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    ' g: h9 a" }  X6 i
  13. * K% z! e/ s+ q3 s
  14.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
1 q0 u- Z- i. r9 m; Z: ]; m2 L

1 Z, @" A8 h, O# x& C3 r$ P& H0 H/ Y  ~) U$ @8 R
73.4.3 第3步:W25QXX的时钟极性和时钟相位配置
$ c/ |/ V% h! X: A* t  c+ i$ ]首先回忆下STM32H7支持的4种时序配置。
9 @  U  n# c1 ?  @0 V. r+ _" `6 u5 j
  当CPOL = 1, CPHA = 1时- A: ^, ?. v$ q* t8 B9 J5 q
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。
4 d8 R9 Y' E6 v
9 O' \& ?1 n  E; t7 P. z  当CPOL = 0, CPHA = 1时( w$ B1 t) s( H- ~* q3 m
SCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
) o7 x" ]8 ]1 Y
6 ?- t6 i& U$ ]' X  当CPOL = 1, CPHA = 0时
$ I9 k7 h$ p: o7 sSCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。
8 e1 E+ Q, s* b8 O0 S' T1 _, I( \* z1 k
当CPOL = 1, CPHA = 0时* X2 W+ Q7 {/ |, G1 B( A) v- ~
SCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
) e' j, w2 j/ m" c4 X! }+ m1 D+ ?
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
6 T: {6 x+ c+ ]( W% }6 }5 ~
! h- t6 X2 `1 j- ^2 @
有了H7支持的时序配置,再来看下W25Q的时序图:4 ~1 Y- y" v( H
  Y. r. N4 F4 N$ q, o( c3 }% S/ t
Mode0 : 空闲状态的sck是低电平。3 X; N) D) L: ^' ?
1 \3 Z& i; ~) O4 F/ E
Mode1 : 空闲状态的sck是高电平。
6 i8 i7 v/ v0 U) I1 u9 y+ J# h# y
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

! z5 y3 F4 U1 K# Z  K/ S5 _' E: {) l- b5 ^) a
首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:
( ]& a0 i6 y7 W+ ^% x$ i4 A) M) E9 b1 c+ \6 `# H) g
CHOL = 1,  CPHA = 1
2 d0 w4 h$ O1 f7 |- o  I! q! [! f, Q, y) D% N0 [1 C
CHOL = 0,  CPHA = 0+ B+ m1 l$ {1 \. i

" Z  L0 a" W0 @- T0 {对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。* _: @% p. z. q3 c  A
9 y- i) T+ q! a6 T. @
73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制
3 S( ^; B4 ?' N( X8 c单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:' _% g3 q7 i+ R1 R- a- M

. A' f6 e2 Y2 d* @
  1. /*
    ) ^( x* M8 A; ?# `6 i2 `
  2. *********************************************************************************************************
      l! O+ ~0 C  |( P2 ?
  3. *    函 数 名: sf_SetCS$ i! J; e* N- b8 j; V; O. U
  4. *    功能说明: 串行FALSH片选控制函数
    0 f2 U% T6 f# M
  5. *    形    参: 无
    5 D3 u( y  O1 @
  6. *    返 回 值: 无4 w) ]' b! h( }) a9 M) A
  7. *********************************************************************************************************; ^6 B; n/ F6 N  @/ _
  8. */# j7 @  E; e9 T% p! F) m4 I
  9. void sf_SetCS(uint8_t _Level)
    6 O4 v2 ]) |1 D" e5 Q) y
  10. {& e7 }  V$ G' W5 y0 }5 Q
  11.     if (_Level == 0)
    0 P2 P9 b, j, K. \. Z
  12.     {
    # F: q+ R& O+ R& K8 w- w
  13.         bsp_SpiBusEnter();   
    + _2 u+ R# C4 `/ a) L3 d1 V! `, B
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        2 o. b! l. I2 f& j: V- T
  15.         SF_CS_0();3 C; Z+ Q. E* k. P2 m% B
  16.     }5 A. d& }$ D: J( b' q7 s" F
  17.     else
    * _6 t7 M9 ]: ^6 f/ {
  18.     {        7 V" s7 U( c4 |7 {
  19.         SF_CS_1();   
    ' D/ d/ O; M4 j
  20.         bsp_SpiBusExit();        $ ~: ]+ R+ j; L. s
  21.     }
    / x) R/ @7 \1 B
  22. }
复制代码

+ d; P$ L5 H  B通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。
8 Q/ n, r( X3 R# b& P; G+ Q$ E7 ^2 H
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。
9 i* O1 h  f1 ^$ z, t; `3 n" Y
" I9 j/ B- @1 x3 j0 l+ }: m73.4.5 第5步:W25QXX的读取实现8 U! `, J" C8 M, [8 I% \5 v
W25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。
, F/ h4 b  d$ k, a( y9 A: E( P' M
, V2 \0 _; t& w# _' I
  1. /*7 @# K7 z4 i2 U$ n/ y9 n# L  E
  2. *********************************************************************************************************
    % h& a" w6 V; h% b
  3. *    函 数 名: sf_ReadBuffer
    % K; C! z( o, J! g4 D
  4. *    功能说明: 连续读取若干字节,字节个数不能超出芯片容量。
    $ z' S( n4 j9 M! ?% \& [5 f
  5. *    形    参:      _pBuf : 数据源缓冲区;
    6 Y& S- Z  O' J5 H* }9 o
  6. *                _uiReadAddr :首地址- `% K: `( R, k2 j6 X
  7. *                _usSize :数据个数, 不能超出芯片总容量3 r5 }& p  J* u! @' [
  8. *    返 回 值: 无) H7 D% M$ ?5 L7 I: q
  9. *********************************************************************************************************
    + o: M$ k2 |9 f4 ?1 u. U
  10. */
    / I: N9 a- n6 q
  11. void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
    5 A8 G; f  o( m8 m+ Q; v
  12. {0 l$ c5 i: Y1 a
  13.     uint16_t rem;
    2 v7 q5 {: A7 W8 Z( c6 R- l
  14.     uint16_t i;0 }3 O/ C3 L( J) `

  15. * K7 E2 p+ u2 p+ D5 L' I! F
  16.     /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */
    * b# S5 W% N4 ~9 T
  17.     if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize)
    ( |* K/ P5 x$ v, D7 y! Y# U, E
  18.     {
    / @5 c4 ~* O5 n8 |' s9 }
  19.         return;# c. j% ~, B6 Q( }3 p. M
  20.     }0 d5 S  ]. H  O
  21. 2 R9 e4 L7 A+ O
  22.     /* 擦除扇区操作 */" Y) U' ^- a. V
  23.     sf_SetCS(0);                                    /* 使能片选 */3 D5 R( {- w8 g/ E5 \$ }) n
  24.     g_spiLen = 0;! n5 h0 P* |5 W
  25.     g_spiTxBuf[g_spiLen++] = (CMD_READ);                            /* 发送读命令 */0 p% |1 D. ^/ P+ x' o0 y' }
  26.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */
    " }7 k' {+ `; x+ a
  27.     g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8);        /* 发送扇区地址中间8bit */# a3 l" j  r' a; {
  28.     g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF);                /* 发送扇区地址低8bit */! |$ E* P( K( K  B" Z' Q
  29.     bsp_spiTransfer();& s, s5 _* c0 U1 z* j

  30. 7 Q4 p2 d; S/ W( E- Y  H1 w  w
  31.     /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */
    ) N0 ?, Q& S; l! x5 @& P
  32.     for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++)
    6 R; d7 g+ q2 {  T- H$ O
  33.     {
    # D3 \+ X' Y' G/ Q7 Q$ S
  34.         g_spiLen = SPI_BUFFER_SIZE;/ k+ |% N5 N8 [3 H, W5 c+ z" z
  35.         bsp_spiTransfer();
      B6 Y' {# E9 {0 `9 B
  36. + a9 q. e, k/ l
  37.         memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
    0 s: e( B9 Q9 d0 E( T' I5 @% i6 ]
  38.         _pBuf += SPI_BUFFER_SIZE;
    # u! `' Q: f7 d4 b* d/ o" k* G  Z  G
  39.     }
    , O8 Y) {, d. N# y# L/ J/ k

  40. 9 D' V' l7 ^% s/ G( `' y6 I
  41.     rem = _uiSize % SPI_BUFFER_SIZE;    /* 剩余字节 */0 W0 `  ~5 V$ d9 m4 A3 N* o
  42.     if (rem > 0)% ]1 ^; a9 n/ m3 g! b
  43.     {
    9 a- L/ M# o( Y9 `
  44.         g_spiLen = rem;
    * s! Z# ^) W( `5 K" g
  45.         bsp_spiTransfer();1 W% P: y+ c- A+ E

  46. . c( D$ q' x1 `% p+ @3 N: p: B
  47.         memcpy(_pBuf, g_spiRxBuf, rem);
    6 S: `9 y9 h/ X$ t* e
  48.     }
    + d0 o+ ]+ N$ {

  49. ) L8 B  x( k+ @
  50.     sf_SetCS(1);                                    /* 禁能片选 */* |$ M' \3 A' x# z. Q7 H
  51. }
复制代码
这个函数对DMA传输做了特别处理,方便分包进行。9 ~. F: A; H* Z/ B# k

$ N+ d6 W7 m/ i. S! s# {6 s3 O73.4.6 第6步:W25QXX的扇区擦除实现
# Z. d+ Q& n) t7 u扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。1 d; a- Y2 O& m/ H8 d8 [& ]. @$ P0 E
; N% i- V* A8 v3 V
  1. /*
    # m' _) C* F, Z* P5 p  i
  2. *********************************************************************************************************
    & ]# q. n5 s1 k% V
  3. *    函 数 名: sf_EraseSector
    ( b7 E+ P/ Q6 f4 n7 r0 t
  4. *    功能说明: 擦除指定的扇区
    / R; S" y% j( q$ k3 B
  5. *    形    参: _uiSectorAddr : 扇区地址* k( T1 H- U" c, r) ]) K7 y
  6. *    返 回 值: 无. U" f- J8 ?, X7 K9 B0 d7 N  d! e
  7. *********************************************************************************************************0 x  y  E* q% c% U: A3 V
  8. */
    # I6 I/ h( `  Z% K. \
  9. void sf_EraseSector(uint32_t _uiSectorAddr)
    ( w; m5 l' G( P5 R
  10. {9 a9 v) l/ E: P/ v" |: s7 Z, f
  11.     sf_WriteEnable();                            /* 发送写使能命令 */
    # J& l) ~, J6 R9 g0 W( z, B

  12. * Y+ d# p/ \' Q, g+ e% W3 G: I
  13.     /* 擦除扇区操作 */
    : S, ]& j+ ^  l5 A8 P
  14.     sf_SetCS(0);                                /* 使能片选 */' \' l, c4 n* ~* A& U/ @1 k3 Z
  15.     g_spiLen = 0;! a7 O2 b( h& S( Z
  16.     g_spiTxBuf[g_spiLen++] = CMD_SE;                /* 发送擦除命令 */
    " {0 L3 ]+ y* _% ]
  17.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16);    /* 发送扇区地址的高8bit */8 D4 h0 m1 A9 f, M; x) Z
  18.     g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8);    /* 发送扇区地址中间8bit */
    ' E3 v* Y. D& ?
  19.     g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF);            /* 发送扇区地址低8bit */   
    ( F, u0 s! R3 e* s. B
  20.     bsp_spiTransfer();! f1 F& F: B) d, p/ H
  21.     sf_SetCS(1);                                    /* 禁能片选 */1 {: I0 t5 y' a8 M' d; C6 s

  22. - x. V% f: {* R* l8 h
  23.     sf_WaitForWriteEnd();                            /* 等待串行Flash内部写操作完成 */
    ; _8 _: M4 [- b' o, M. x
  24. }
复制代码

0 b; E" J/ m' d$ E3 F( S整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:6 M. F* F3 d* C" t
" A/ T& D: z, G" H" e  |. p
  1. /*1 c& r9 F* T( M- e# q
  2. *********************************************************************************************************
    ; v8 u  e: E% F/ q& r! |
  3. *    函 数 名: sf_EraseChip5 x, y& V6 w/ l
  4. *    功能说明: 擦除整个芯片
    2 G7 h: K  x' g2 H: I" u# I( S
  5. *    形    参:  无
    ; q# h$ C9 f+ I/ R; @/ W
  6. *    返 回 值: 无
    ( n6 z9 E  r/ R
  7. *********************************************************************************************************/ ]3 v" C! a9 _4 i8 g6 Y& |! m
  8. */
    * E1 m/ ~; t2 H* f6 `5 r  W  O& T
  9. void sf_EraseChip(void); Q, |$ E3 g! ?
  10. {   
    ! `4 o: J' y) x, H4 ]# d; G
  11.     sf_WriteEnable();                                /* 发送写使能命令 */4 |. N5 `; N+ f/ T& T

  12. ) y/ d% L2 u# K0 J
  13.     /* 擦除扇区操作 */
    * q8 R- B' A2 Y; k8 F1 m
  14.     sf_SetCS(0);        /* 使能片选 */- o; y1 }; T. h2 t4 v8 m
  15.     g_spiLen = 0;9 M' c7 c* w; R# U+ U! v$ u9 ?
  16.     g_spiTxBuf[g_spiLen++] = CMD_BE;        /* 发送整片擦除命令 */
    & j$ h; M8 H" p% h
  17.     bsp_spiTransfer();5 B- C6 C/ G. R- ~- d+ ~' T& ^
  18.     sf_SetCS(1);                        /* 禁能片选 */
    " X" j( G' t2 t4 `, R! C9 ^, w6 E
  19. 7 [0 J, [# s4 q; R
  20.     sf_WaitForWriteEnd();                /* 等待串行Flash内部写操作完成 */
    ) m5 a* A, c8 B6 ?0 d8 V: i
  21. }
复制代码

0 b- @! u/ s0 X8 L( Z6 A73.4.7 第7步:W25QXX的编程实现
: @+ T4 ?5 M- O1 l8 CW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:: N" f. Y5 e6 y4 @& `

9 z8 x+ V& J1 @* H$ [, @$ G& h3 G4 ^3 D
  1. /*
    : N! a" M- e0 ^$ K7 x8 c
  2. *********************************************************************************************************
    5 x% ]. k% V9 u# R. A# b
  3. *    函 数 名: sf_WriteBuffer
    ; T5 G, l6 {$ e& ?
  4. *    功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。' G  Z& v) v9 @/ W: m$ V% y# [3 c( ~5 f
  5. *    形    参:  _pBuf : 数据源缓冲区;, m! ?* b- ]7 _! b  T- d* v5 j
  6. *               _uiWrAddr :目标区域首地址, V1 k4 }$ d) v) C5 X; C: Z$ N
  7. *               _usSize :数据个数,任意大小,但不能超过芯片容量。- h1 t- F, {# G, }% b9 ?" L6 P
  8. *    返 回 值: 1 : 成功, 0 : 失败
    ) Q% P% j* Y- x& A( A- ]. g
  9. *********************************************************************************************************9 v. W7 y5 m% w& V
  10. */
    : }- P+ a$ Z" l
  11. uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)0 u: M; N, R# g1 S- |/ W
  12. {
    & p/ l1 o" X2 f' q
  13.     uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;- [7 ^7 @# p& K- e, L

  14. 4 D0 B0 E+ D1 p1 r# W+ e* l
  15.     Addr = _uiWriteAddr % g_tSF.SectorSize;
    & H6 F- j& ?# E7 k! A' ?
  16.     count = g_tSF.SectorSize - Addr;
    0 B! [; A6 H: }
  17.     NumOfPage =  _usWriteSize / g_tSF.SectorSize;
    + Z/ y% C: [' j  R2 T5 W# b2 n
  18.     NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    , i3 h. C* a- u! o: z/ j

  19. 7 n$ j* `% j! k- v1 d# V
  20.     if (Addr == 0) /* 起始地址是扇区首地址  */
    ) j' a$ S6 a5 \) i6 H/ v
  21.     {
    5 \& e8 \4 U( F8 b6 t3 q+ _; D
  22.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    ) T) F( x. z2 p
  23.         {
    + h( s' i5 p1 n9 F5 c
  24.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
    9 R/ l/ {! T8 p0 f
  25.             {
    2 o8 g/ U/ S! O7 d9 g; O* ]. J
  26.                 return 0;) K6 c- X) I2 M/ X7 o& [2 |
  27.             }' v- n% R& c  s; ^0 ~3 l, L
  28.         }
    ! K& c+ V8 J: }# G* C% M
  29.         else     /* 数据长度大于等于扇区大小 */; b' ?/ O: u, C7 l
  30.         {  k( n7 r$ d8 u- w3 {) D# t! l
  31.             while (NumOfPage--)
    , o: {, e9 w1 ^3 g- L
  32.             {, h+ M3 T- H2 a8 b3 p* h; j1 g
  33.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
    : Q. [6 @6 t1 f/ X
  34.                 {
    ' ?4 J/ `. t, B$ v' K
  35.                     return 0;6 X/ E& ]* V% F
  36.                 }
    $ F/ T- s$ U8 y9 ~
  37.                 _uiWriteAddr +=  g_tSF.SectorSize;# x' H4 z6 c0 {: f# J* |
  38.                 _pBuf += g_tSF.SectorSize;
    5 G% f& L/ y1 R! P$ g) ^
  39.             }
    ) H0 h) X* b: d, b6 v: p
  40.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
    $ q6 b1 C  u0 V' [# v8 L3 o
  41.             {& c, m$ A1 }; f- C- ^1 f8 M) O
  42.                 return 0;* X! l! a1 C, j: o6 l
  43.             }/ y3 D/ B- j% A+ c' a
  44.         }8 Y: U9 n! {; K
  45.     }
      E. G# g1 s* v4 }3 R
  46.     else  /* 起始地址不是扇区首地址  */
    0 C# ?/ b8 ]3 A& O( b  a% v" ?
  47.     {9 S/ Q" b9 {; p1 y9 P( b0 |
  48.         if (NumOfPage == 0) /* 数据长度小于扇区大小 */
    5 O5 h' o4 S2 \4 U8 y. `8 L
  49.         {8 A1 J7 U- ~9 X2 U  H3 \
  50.             if (NumOfSingle > count)  /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */' N) [3 R( \7 M
  51.             {
    6 n6 p2 R2 [. n: O# W: H4 D
  52.                 temp = NumOfSingle - count;
    ' @) Q7 t2 {) l: ]7 c; d

  53. ; o- D8 L9 @; }
  54.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)/ ^8 \; R0 O( B, B+ ]
  55.                 {
    ; X8 T1 ~1 d( j4 k
  56.                     return 0;
    * A" I7 q. e4 Z, c0 U
  57.                 }& G& b$ C) Q4 q9 ~( `9 O

  58. * V, n$ c+ ?4 ]3 `2 \
  59.                 _uiWriteAddr +=  count;
    + f1 C/ }$ {3 j9 l7 V
  60.                 _pBuf += count;
    / G- W" ^( @9 u4 R/ G
  61. ) e6 \  x. H5 H5 B$ f" w
  62.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0)
    ' @9 {# @- m5 \. Z% H( L4 Y$ j
  63.                 {" C4 z1 W. a+ m8 t
  64.                     return 0;
    * F3 h5 ^% r; ]; q* {+ ]
  65.                 }5 H9 O7 Q0 Q: O% w6 U  @  \
  66.             }
    5 W3 w- ^; u; ]7 s" N
  67.             else9 M9 G$ p/ s/ v. M7 Y5 T. u
  68.             {
    . `) ^- @8 D0 i# ^! y0 M; m
  69.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0), c  R& D9 E  U3 ?
  70.                 {
    % p) p6 h* t4 I4 G; L6 r, r
  71.                     return 0;
    ( M: x3 ^; ^# D8 p% e" U
  72.                 }, K& D4 d: z8 G
  73.             }
    1 N8 ~. H8 M1 q6 h% M
  74.         }5 @0 V* c/ j: x
  75.         else    /* 数据长度大于等于扇区大小 */
    - O- v( ]2 g$ g- I7 c# @6 `3 [
  76.         {2 U9 R: _; K/ I
  77.             _usWriteSize -= count;0 ?! d  V& H) }* B# O
  78.             NumOfPage =  _usWriteSize / g_tSF.SectorSize;+ j8 N8 a( ~3 O
  79.             NumOfSingle = _usWriteSize % g_tSF.SectorSize;
    4 O6 I5 H. J9 \; v8 C' |2 s
  80.             if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)
    & ~+ {/ a8 t" F0 V4 h5 g
  81.             {
    8 K5 L% F0 r' p/ q2 w
  82.                 return 0;
    # g; y4 t: X" Q. b
  83.             }
    # n5 w3 ^  ~5 D$ i7 R; \' j
  84. 3 v/ c: y9 |1 ^, g" z" T
  85.             _uiWriteAddr +=  count;
    " i$ b# _+ ]$ G* j: C! @) {
  86.             _pBuf += count;
    ! e/ v' ^$ T6 t* c" ]9 ?, i" a4 J
  87. / @9 A/ i: ~- D7 n. C0 U( F$ p, F
  88.             while (NumOfPage--)7 ]6 A, l- k( d& p' Q( Z
  89.             {! W: \& p( a/ `
  90.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)  ]2 a" I& n  P7 B. Y
  91.                 {
    ! T$ o% c8 Z% D6 C( q7 m
  92.                     return 0;
    , h6 ]' ^) w$ C/ y1 x
  93.                 }
    2 p* D: Y+ I+ v) R% |. p0 I
  94.                 _uiWriteAddr +=  g_tSF.SectorSize;. d1 B+ W! r  j# A- m7 k
  95.                 _pBuf += g_tSF.SectorSize;
    9 s1 d) a' p9 T, @. V3 T
  96.             }
    ) g5 _1 j  k: y  |/ H' s
  97. 7 n( W- ^& Q& D9 @7 ~
  98.             if (NumOfSingle != 0)+ B2 P/ R6 [0 Q: x9 p% C
  99.             {
    ! Q5 x; j' n3 o+ `8 U: U9 r1 v
  100.                 if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)/ J7 u* V  p6 j
  101.                 {
    4 {$ {, M) d4 \3 N
  102.                     return 0;+ J$ v2 N" x' \+ A
  103.                 }
    1 Z% s- J, M( x4 V. W
  104.             }3 d% Z: q7 J( w9 O( I, ^# y
  105.         }) s8 n) T, O; W1 _+ F% h, x
  106.     }
    5 n% Q# r# R; I% N0 Z* n
  107.     return 1;    /* 成功 */
    % B1 _- P# G% A6 j* C
  108. }
复制代码
+ {) R' E5 o% a: b* Y7 X* e2 e& Y

! ?4 u0 I4 p% k/ i% g& \/ A' \1 k& g3 y3 Z. m
73.5 SPI总线板级支持包(bsp_spi_bus.c)* {( |/ O8 H" m- L
SPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:5 r# i' e% b, c2 h& A' F6 k5 Z
* `1 n: F0 R" x8 F/ k% ]+ }0 z& A
  bsp_InitSPIBus  J) o, e  b, E
  bsp_InitSPIParam
' ?; I$ `0 E0 E1 N/ X2 n0 S, _. W9 ~  bsp_spiTransfer, s; {: I) ?  a- d* w$ H, w
73.5.1 函数bsp_InitSPIBus
% r& q! X! j" i- z" X0 D$ s函数原型:4 m3 b0 h2 b! L& Z# e5 I$ c

/ }2 Y. p/ c0 f: U1 Rvoid bsp_InitSPIBus(void)" j- s, S/ w! N) o
  T( g! S2 @9 p7 \" h
函数描述:
6 j$ p9 Q* p% H* J0 I
3 \, M7 L8 ^2 G- b) V此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。% ?" ^! g; z1 f6 w6 g
6 t1 ]) Z4 u- P% B
73.5.2 函数bsp_InitSPIParam. u& @% A( M( w/ P  P
函数原型:
- `1 |3 h, U6 |" H: i3 |% e5 r+ C3 W& v. Y) o
void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
! J: E  ^; W" _- s& a' S* o- X9 W0 Q! H& F
函数描述:
! M$ Z3 E- P) g* Q( E
2 w- i, B& S4 ^4 F1 N' @6 t此函数用于SPI总线的配置。. Y0 s: i# T/ M  }6 x
  L4 s9 l3 a" d: ~' X% ?; ]* ~
函数参数:" _4 A  k. g; y2 F4 }3 L

4 i' N- U. O) X  第1个参数SPI总线的分频设置,支持的参数如下:
1 S3 A9 f+ S5 L: w. W" J% r+ V! ]SPI_BAUDRATEPRESCALER_2    2分频
+ v: O. u8 X( H9 e& I. x: m1 h' o7 \: M' a, v& z
SPI_BAUDRATEPRESCALER_4    4分频$ {1 J& c, Q1 F3 T* [( I% A* G) v

; H7 ]( ^: d$ E* k7 [0 fSPI_BAUDRATEPRESCALER_8    8分频" U7 X+ i0 P. V+ `  H" B
% R# {5 ?- b6 M  @7 t: }
SPI_BAUDRATEPRESCALER_16   16分频' I! G1 _0 o7 O, N3 A# I5 C

' R# @2 B- Z! ~2 USPI_BAUDRATEPRESCALER_32   32分频
7 ^% p! ~& ]) g. M3 l. {
8 ~5 Z) H$ U# V7 W4 P  }SPI_BAUDRATEPRESCALER_64   64分频
& {. i  G' M/ {) _7 c  L" }5 l' t/ s! \; {$ G9 I6 @! A9 |
SPI_BAUDRATEPRESCALER_128  128分频* y6 s4 I3 q! c/ s
& Q, W9 G3 _: {9 ]9 Q1 l' Z
SPI_BAUDRATEPRESCALER_256  256分频
7 I) B9 t! t; A1 x* E
( Z9 ^- T" W* a2 P. v. F* c  第2个参数用于时钟相位配置,支持的参数如下:# Z2 H0 h6 l/ k: f8 S% ~- t
SPI_PHASE_1EDGE     SCK引脚的第1个边沿捕获传输的第1个数据# P* f$ Y) Y* |1 e. k

" _7 q3 V4 @5 S! BSPI_PHASE_2EDGE     SCK引脚的第2个边沿捕获传输的第1个数据
* {) P+ a7 ?8 V4 @( E3 u% q* G, g
: r5 H8 f  j2 z# Q+ F( k7 t  第3个参数是时钟极性配置,支持的参数如下:. Z7 u- H9 W  m% ^" m+ i
SPI_POLARITY_LOW   SCK引脚在空闲状态处于低电平
$ d, x/ X( a6 u& F+ {+ V
/ K/ p" g  R4 {2 F3 T. GSPI_POLARITY_HIGH   SCK引脚在空闲状态处于高电平
2 j7 C/ e( q# X8 k% r/ i: S% o2 }# E, m& v- E* o
73.5.3 函数bsp_spiTransfer
$ f. i0 [) c: a5 a, g函数原型:
( i) P% Q8 U5 b' x% T1 E" m7 g1 m" D  d9 a
void bsp_spiTransfer(void)8 J0 L" y3 k$ j5 j6 u6 X

0 z' c) l/ a9 T: _+ f+ c" p3 O函数描述:4 i. o- @* {, G8 w( ]
5 P! I) P% W9 b
此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
2 h8 p) w4 L" m, N2 m9 V, |6 X7 J2 U( Q/ k2 C+ J: D, F1 Z  e
73.6 W25QXX板级支持包(bsp_spi_flash.c)
9 i% u/ \, ]+ `, z, QW25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:1 C5 j) Z3 |) }1 W9 }

  B3 D- A$ o) T$ W3 M6 K6 q4 k  sf_ReadBuffer8 H3 O  H: z+ u4 G8 S' X& m
  sf_WriteBuffer5 x% o2 z( \4 @4 D0 m9 r
  sf_EraseSector1 c' |0 Z4 Q) P4 n2 e
  sf_EraseChip+ ^# g$ z9 U* I2 u/ K
  sf_EraseSector3 u! [5 `  [  n) R3 |. u5 l
73.6.1 函数sf_ReadBuffer
( r. Y- D- B5 c函数原型:
) x$ n* a0 _$ [/ ~8 P
) A3 \8 r  Z, b; {void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
7 i7 g* S) q) L4 L; A2 d$ L1 G, F
函数描述:
' y3 e% H! f2 G) Y+ o4 C0 F0 u% X* n
此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。- F( O1 |2 N* r
. U3 S8 h- w" \0 d
函数参数:: i6 o+ ^8 i8 s5 j( R
$ f* w8 T) s& R$ E( A
  第1个参数用于存储从SPI Flash读取的数据。
& B0 j  ?2 R) d$ C# V+ T" D" A  第2个参数是读取地址,不可以超过芯片容量。% b' O% P: T7 v3 B# `& n9 L1 S
  第3个参数是读取的数据大小,读取范围不可以超过芯片容量。+ q5 ^# s. P. _4 h" [! f0 g/ s  A
73.6.2 函数sf_WriteBuffer(自动执行擦除)
" Y% g2 @! t3 G$ }) p/ ]函数原型:9 r9 D% M3 g9 A) |) J

/ S6 D8 S8 y) huint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)8 Q/ n0 n2 n4 |2 S1 U
! V; {/ r0 n% M( n9 M. z+ B
函数描述:
+ V8 L0 E6 W& ]8 W7 g$ l6 R3 r+ @: X' A. a% e
此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。# ]7 J8 k. Y! o, z, {8 U. t" q
. z/ T& O/ U* A  a. Y
函数参数:2 D) L" v( X! q5 i" c

1 k9 B1 w2 P8 i* V5 k' l& R8 v  第1个参数是源数据缓冲区。
: u  g3 i3 T: A+ s  第2个参数是目标区域首地址。/ A! o! L- R4 g. R5 o8 L
  第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
3 p7 @6 ]8 I$ A. @7 F  返回值,返回1表示成功,返回0表示失败。) t" m9 K! R9 D$ R
73.6.3 函数sf_EraseSector; i/ \" J4 n& Q3 }" D
函数原型:
$ [* C; B( k8 }& P1 K  {' P( ]+ ?; m5 k/ S7 S
void sf_EraseSector(uint32_t _uiSectorAddr)0 q4 y1 V( m5 a2 d. z1 \% Z& l
% p0 g( q7 s0 ]9 \6 `. L8 k5 J
函数描述:- I* j2 f+ x) r- w, u; Z
4 ^" I! |; f. P& y1 \% U
此函数主要用于扇区擦除,一个扇区大小是4KB。
9 r" G3 ?  d& L4 B; p9 c: D) R' [5 p6 |2 Z' f  k' v
函数参数:
9 f6 S, J" ~: P6 S6 z* H2 }% l+ G1 [* j
  第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。
% @; T; _. b: T- Y) T73.6.4 函数sf_EraseChip
6 n, Y! N6 {: p( d5 o/ a函数原型:
" B$ r1 X+ o* ?; j2 E( `5 v& M6 G- A4 g# G0 T1 b
void sf_EraseChip(void)$ f: D8 D' Z# w0 _9 a$ E
4 E# H6 R: u- L/ Q5 k
函数描述:
$ S" t* @$ A  P+ h; c* j1 b: o! J) q1 {! a  O" s
此函数主要用于整个芯片擦除。
& Q( s! e1 \$ ~4 I. {* M
8 U. n6 B6 ]' R2 x: L% Z7 j73.6.5 函数sf_PageWrite(不推荐)
' _; p& y) U1 f- g! d: k$ V0 Q函数原型:$ ?% l4 E1 q- E) J1 @7 X7 ]: V
6 g9 s" ~' x* ^$ p
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)% C4 A* c1 r/ f6 I+ r+ h
, H1 D4 I- G$ \/ C
函数描述:& L9 S# ?: e, k+ w) C5 N- f' z

) z: Y7 I' [/ M- b- m4 N此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。
0 @3 v/ N5 _" W0 w5 _( |1 {
3 S+ M* A0 _1 {. o9 G, ]. q5 {. N函数参数:6 `2 V5 _3 x" o9 h

0 I9 c2 Y0 H5 O9 ^5 ^4 c3 _  第1个参数是数据源缓冲区。
$ y4 j5 u/ `9 r$ a# H  第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。8 X2 T4 P6 }+ D
  第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。
- |4 f! ], o! C2 F+ r0 y' g73.7 W25QXX驱动移植和使用
0 n9 V1 v- V( ], C! zW25QXX移植步骤如下:
4 ?3 ^7 G' q7 J- \( L
# [" m* E6 c  }9 x- z  第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。  e* Y! G1 I/ X8 @  Y- d
  第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义) A1 M0 R3 J+ ~$ c; d5 Z- K8 I# O
  1. /*
    9 M0 d; k3 I" v9 M$ V+ p7 y4 }  p- X; {
  2. *********************************************************************************************************
    , _0 y8 o7 y# ]5 x! S" f
  3. *                                时钟,引脚,DMA,中断等宏定义
    6 U( D' n; b: _% a: a' R* Z
  4. *********************************************************************************************************
      A7 e( ?5 g( S: Q2 i5 @, j1 L
  5. */# l1 c* M* e9 L; @* I% |! s, C  z
  6. #define SPIx                            SPI1# X# s3 ?  u( ?  J3 c
  7. #define SPIx_CLK_ENABLE()                __HAL_RCC_SPI1_CLK_ENABLE()
    0 ^# p+ o# p& d1 i
  8. #define DMAx_CLK_ENABLE()                __HAL_RCC_DMA2_CLK_ENABLE()$ K2 @& J+ A8 C# j- }4 W0 G$ E2 l

  9. 3 ]! p) A# `! r5 V" J' v5 M6 `
  10. #define SPIx_FORCE_RESET()                __HAL_RCC_SPI1_FORCE_RESET()
    1 T- p5 G0 G) m9 F; f+ n
  11. #define SPIx_RELEASE_RESET()            __HAL_RCC_SPI1_RELEASE_RESET()+ M2 g; F( P0 |
  12. 3 {2 q# ?, J) u( ]% o3 G( d
  13. #define SPIx_SCK_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    8 t% h5 O, V4 p" x
  14. #define SPIx_SCK_GPIO                    GPIOB
    3 P2 ]/ }9 B8 P  P0 n
  15. #define SPIx_SCK_PIN                    GPIO_PIN_3
    2 n& T+ K6 M: l" R3 g/ P4 C
  16. #define SPIx_SCK_AF                        GPIO_AF5_SPI1# U3 N# @: \5 Y

  17. 7 s2 [; v3 F4 @0 Y. O! P: [0 ^+ Y
  18. #define SPIx_MISO_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()4 h' o+ ^/ N* E3 l/ _3 b8 Q
  19. #define SPIx_MISO_GPIO                    GPIOB7 {/ h9 r7 W' P! v* |% A6 J& Z
  20. #define SPIx_MISO_PIN                     GPIO_PIN_4
    ; Z# z. Y. d+ L: ]1 V
  21. #define SPIx_MISO_AF                    GPIO_AF5_SPI1
    8 ]1 Y! {- U/ L; o2 ?. x) J
  22. & U& ?5 d3 h# c0 j/ S9 l* A, w
  23. #define SPIx_MOSI_CLK_ENABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()
    5 L& \( U8 c2 F/ M
  24. #define SPIx_MOSI_GPIO                    GPIOB4 j4 t4 M0 o7 O; O: k) U" Z
  25. #define SPIx_MOSI_PIN                     GPIO_PIN_5
    $ y+ C, f: Z4 s# t9 k
  26. #define SPIx_MOSI_AF                    GPIO_AF5_SPI1; s& v* T) e8 n1 u/ V* S
  27. / v/ _; Z1 g: x1 W+ h0 ?
  28. #define SPIx_TX_DMA_STREAM               DMA2_Stream38 |9 C# g# @/ ~, j' a: t" z, g
  29. #define SPIx_RX_DMA_STREAM               DMA2_Stream2
    0 d6 t2 ]: I, [" `5 }* [4 `* z
  30. " Q; ^' U- l! k6 u# u! v
  31. #define SPIx_TX_DMA_REQUEST              DMA_REQUEST_SPI1_TX1 W6 n, [7 h6 C/ o2 G
  32. #define SPIx_RX_DMA_REQUEST              DMA_REQUEST_SPI1_RX, T; N1 Y7 U1 o

  33. ; o- @1 w$ o' w5 e; w8 S" l
  34. #define SPIx_DMA_TX_IRQn                 DMA2_Stream3_IRQn
    % v/ q* H+ |% A2 G6 _) l6 x
  35. #define SPIx_DMA_RX_IRQn                 DMA2_Stream2_IRQn' o: ^. `: G- D: t8 U" }% N

  36. 5 d+ @; N% I& m4 f8 A
  37. #define SPIx_DMA_TX_IRQHandler           DMA2_Stream3_IRQHandler
    : Z' ^* y' }) R* M
  38. #define SPIx_DMA_RX_IRQHandler           DMA2_Stream2_IRQHandler4 ~/ w* n( ]9 d8 h. [1 n9 `
  39. , a1 o6 n' \- }- B3 q
  40. #define SPIx_IRQn                        SPI1_IRQn
    # Z0 s- R8 B; i2 J$ r
  41. #define SPIx_IRQHandler                  SPI1_IRQHandler
复制代码

& G+ Y8 h2 d; B9 m1 r# r9 j  第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
' M- B) A) Q3 \7 X& p$ m5 P
  1. /* 定义串行Flash ID */0 {' W$ Y" V0 j
  2. enum
    4 y' b4 A6 S3 A- [
  3. {
    0 ]7 Z, q! I, M3 H7 Y0 o1 a
  4.     SST25VF016B_ID = 0xBF2541,
    " c$ q  E& Y3 k: e( Q  N
  5.     MX25L1606E_ID  = 0xC22015,  J; C' \3 R' J
  6.     W25Q64BV_ID    = 0xEF4017, /* BV, JV, FV */
    ( Q( K( U& `5 M/ T2 H+ x$ ]
  7.     W25Q128_ID     = 0xEF4018
    # C' j% j3 D' f+ C/ D# q) w6 l
  8. };
复制代码

1 X6 N9 \: N+ U% N# g; L  第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
9 Z1 u8 m9 |4 n/ U
  1. /*/ S. R9 b6 |# q: e* e/ ?/ ~4 _
  2. *********************************************************************************************************# L7 w; N" c  O4 f
  3. *    函 数 名: sf_ReadInfo
    0 j, C! c/ s0 {" k$ t. }9 l
  4. *    功能说明: 读取器件ID,并填充器件参数
    8 c/ H' i& Q; V3 a* p7 l9 y
  5. *    形    参: 无" r0 T% n, q1 E5 p/ D
  6. *    返 回 值: 无9 h4 K  ]" g: S( t* v( x" X
  7. *********************************************************************************************************4 K) n7 T  q; [% P. r+ G
  8. */7 E% _5 d5 o- R% ?- E
  9. void sf_ReadInfo(void)( l2 d* P* A/ y
  10. {
    " D" v* ~. b5 s9 j3 R8 v$ U! W: s0 X
  11.     /* 自动识别串行Flash型号 */8 J5 ]9 l& b" i' l* l% ^/ q
  12.     {. f7 ~9 ^; i3 y% S
  13.         g_tSF.ChipID = sf_ReadID();    /* 芯片ID */
    , y8 x& }5 l1 I. R3 L2 T

  14. 4 z  H' ~1 z# X1 d- E, e% N) O3 P9 q* [+ l
  15.         switch (g_tSF.ChipID)  c7 k' z$ y- B, r4 t; t1 ]
  16.         {
    " w5 n' L6 u/ Z6 s
  17.             case SST25VF016B_ID:
    & V' T5 d+ P$ |5 W
  18.                 strcpy(g_tSF.ChipName, "SST25VF016B");
    " Z2 F( @6 A& }& a3 F9 }: D
  19.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
    % s- j& P, \5 |% C- F3 o8 v
  20.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    ' v$ s4 s- ^; B2 Y
  21.                 break;
    9 ]# X5 k, y" b( \1 ?

  22. 0 S( V$ `( h/ W+ K" {; n  m
  23.             case MX25L1606E_ID:7 A, B2 n/ h9 d# x! [
  24.                 strcpy(g_tSF.ChipName, "MX25L1606E");) S5 q, t) E3 \
  25.                 g_tSF.TotalSize = 2 * 1024 * 1024;    /* 总容量 = 2M */
      |, f( i; y" U( r
  26.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    / _' y# s8 r5 E
  27.                 break;# H- V- o3 n# p! l* q* g5 o

  28. ) m* u9 {- }) s3 G1 J
  29.             case W25Q64BV_ID:
    1 w* ^# X$ c% j( e: d" ?. H
  30.                 strcpy(g_tSF.ChipName, "W25Q64");
    . n+ s- K7 e- R4 d" t
  31.                 g_tSF.TotalSize = 8 * 1024 * 1024;    /* 总容量 = 8M */
    5 t( e- d, ?2 u6 y) i& n6 {! t
  32.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */
    1 h: w' e2 |( a4 r, ^1 b' ?
  33.                 break;" w. U! m. a. F0 U, i* A
  34. : b' X9 u% }3 Z$ ]% G* v
  35.             case W25Q128_ID:
    ! a  D+ o# d, f( |4 y
  36.                 strcpy(g_tSF.ChipName, "W25Q128");
    ) Z4 Q5 `+ r! C2 v
  37.                 g_tSF.TotalSize = 16 * 1024 * 1024;    /* 总容量 = 8M */
    + u; y% |4 G0 t' q! w
  38.                 g_tSF.SectorSize = 4 * 1024;        /* 扇区大小 = 4K */" b; \+ M* C. o, H
  39.                 break;              Z5 F; T" r3 n7 W
  40. + }. S7 k: |7 Q+ ^( x6 W( d
  41.             default:
    % o6 S/ a0 b% }4 r' \
  42.                 strcpy(g_tSF.ChipName, "Unknow Flash");
    8 E* G! n: T% O8 C! M! t
  43.                 g_tSF.TotalSize = 2 * 1024 * 1024;; z; ~- s" n- F* P% z& n
  44.                 g_tSF.SectorSize = 4 * 1024;3 ]# P6 _# ~+ }" X- O) M, {/ u) w) O
  45.                 break;
    ( ]) z5 |2 i" s! V5 V
  46.         }, a6 n: r( _1 F/ Z3 |2 w/ f$ [6 g
  47.     }! F; \$ ^4 [" X' P
  48. }
复制代码

2 h5 l- |$ W) I; P* e% ]% r8 @3 l  第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。  U* D+ _0 ^' g. l
  1. /*
    ! X7 U4 {# H' @$ a- K) V
  2. *********************************************************************************************************. U5 \, D6 I7 F% L! j0 }! I
  3. *    函 数 名: sf_SetCS
    . o6 A- j+ i' i1 @
  4. *    功能说明: 串行FALSH片选控制函数2 _; R6 ^9 Y8 @/ u- d. K- J0 [
  5. *    形    参: 无
    * J, L7 I+ R' _& l8 y( C1 L
  6. *    返 回 值: 无
    / ^1 I& V) c$ ]" I( ~
  7. *********************************************************************************************************
    + P. d1 L! w8 J2 _  k0 X
  8. */7 g# E( @! I7 u8 u
  9. void sf_SetCS(uint8_t _Level)* p4 l% P) T5 X+ s8 a4 U3 I
  10. {) N% P- r$ V& i4 o- F
  11.     if (_Level == 0)
    : v" w8 E6 P- Z
  12.     {
    / b2 }7 w6 q3 ~* D
  13.         bsp_SpiBusEnter();    & X6 D" \1 _  U+ d7 o( @$ @' v
  14.         bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);        9 {. \1 {  U3 Z' F8 i' E
  15.         SF_CS_0();8 e' W& @9 v) D- ~7 r; @) n& k. \
  16.     }
    / g- N- y" a  d% j3 }. D7 I
  17.     else
    ( |# G! S4 I: r7 E
  18.     {        ' l4 O8 @& _2 [/ ?- M/ u0 @' f
  19.         SF_CS_1();   
      e5 R: C: t. W- D& N3 P5 P* o
  20.         bsp_SpiBusExit();        
    8 M* T$ D$ f, L
  21.     }
    & c/ P" `( D3 ]( i! K
  22. }
复制代码
& D5 R- \; Y$ ~2 g# ?
  第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。
2 \5 J; B0 L% [" c2 ^$ Q
  1. /* 串行Flash的片选GPIO端口, PD13  */) M4 R% w- k7 U/ n
  2. #define SF_CS_CLK_ENABLE()             __HAL_RCC_GPIOD_CLK_ENABLE()
    ' a& x' N$ x$ l# A" N3 k4 x
  3. #define SF_CS_GPIO                    GPIOD
    . K* |2 B: \) x0 P& z7 c9 j' Y
  4. #define SF_CS_PIN                    GPIO_PIN_13
    , ?# D& W) t2 Z. [- {4 w6 `
  5. 1 c+ ~9 Z( L) G1 T* r
  6. #define SF_CS_0()                    SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) , m' T  w6 l1 E/ D1 a! b
  7. #define SF_CS_1()                    SF_CS_GPIO->BSRR = SF_CS_PIN# C. D6 H* p( }2 _7 ?* X; A
复制代码
$ Q, d. w% p% y; x3 d6 ]9 L
  第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:5 ?, \4 E# d7 ?) [8 A
  1. /* 配置SRAM4的MPU属性为Non-cacheable */1 j/ W! Z& _6 |/ q" O$ b, X6 M7 u
  2. MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    ( O9 s- m7 J$ }# {# q
  3. MPU_InitStruct.BaseAddress      = 0x38000000;* G* I7 v5 f6 J! k* Y. |+ Q. r. F
  4. MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;4 Z7 ?; u* e: `- t+ {
  5. MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;6 @$ a; E3 |5 v1 }
  6. MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;5 b  C2 w8 c3 ~1 C
  7. MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    : O6 M3 W' P- Z. ?
  8. MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;* E! ^% z- _8 o$ I. I) x3 w
  9. MPU_InitStruct.Number           = MPU_REGION_NUMBER2;! `4 s! S# H8 E3 f1 p; K! Q$ C4 S
  10. MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;) D) P6 y2 c# k% j1 m) S
  11. MPU_InitStruct.SubRegionDisable = 0x00;% s4 R, G9 ~& a
  12. MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;! g; b5 F1 u0 P/ Y# p4 J

  13. 2 D- O1 d- Z+ r, K5 F
  14. HAL_MPU_ConfigRegion(&MPU_InitStruct);0 }3 L; l9 }! y' _. @8 M6 R
复制代码

+ j  K& y5 k- ]: w: l9 w  第8步:初始化SPI。
7 a6 C0 |2 `9 K' Z, Y; E* B) Z
  1. /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    , q/ j; y$ ^8 b* G( m7 i
  2. bsp_InitSPIBus();    /* 配置SPI总线 */        
    ) X3 {9 v8 |1 }0 z
  3. bsp_InitSFlash();    /* 初始化SPI 串行Flash */
复制代码
. f5 T+ y+ U" R
  第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。/ V5 q) a! b0 Z" w/ K& [
  第10步:应用方法看本章节配套例子即可。! Q$ k; w6 C6 R2 F' ]# N
73.8 实验例程设计框架
5 Q; n7 I) _  l; M6 t通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下:: Z- k2 F1 ?' ?! c5 b! q+ Y$ y; g
9 M7 f# X" T. P
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
# Y' h" k4 r( r0 [# g) ]4 c8 g
7 _. V8 ^6 I8 C3 `
  第1阶段,上电启动阶段:
, c7 k$ \# Z2 ]5 |- o8 {* g
( [* A! s4 l. b- s/ m; O4 w$ {7 c这部分在第14章进行了详细说明。
1 A" k( C0 M5 e# A! G4 O  第2阶段,进入main函数:# P& M# z; e' i% [- h4 L
! n" N- j0 j5 X7 D
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。
" Z7 i6 E" T+ N8 g+ p1 ~, W第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。% [: ]2 R/ \- I( g1 M
73.9 实验例程说明(MDK)# Z! d1 v4 o5 b4 M5 K5 `
配套例子:
* _: c7 O6 O6 F$ t
, l9 s% j6 |/ s! sV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
2 d; }9 P+ L% t1 @# x
8 I2 k$ K) M" n% ]! u* x9 r$ ^V7-050_串行SPI Flash W25QXX读写例程(DMA方式). C  O+ N& j, `' @4 p+ j
1 `+ J$ h. K. w7 D6 `
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
, P5 s( n) v6 ~) ~1 q3 N4 Z; T2 [$ G! N
实验目的:
, L& x& N: m& w. f, g! b& C" u( n1 W/ u' _4 `2 O8 s4 j# @& w" D
学习SPI Flash的读写实现,支持查询,中断和DMA方式。/ ^  Q$ c8 k# h) }( v
实验操作:5 G- E) X4 ~9 x$ b7 a  T
4 J( [1 m% {7 G1 z
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可( w& f5 m* T& U; T$ i3 `
printf("请选择操作命令:\r\n");
6 r& e) @: H8 d: j( Q+ Oprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
6 B2 F- F6 H* tprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
( H: Z# k" N' y2 d. H% wprintf("【3 - 擦除整个串行Flash】\r\n");. E: \6 c9 t6 i' ?
printf("【4 - 写整个串行Flash, 全0x55】\r\n");
- V# b! @+ O/ T! vprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");
0 `, T8 d4 m% ]% ]! Z8 {' {5 pprintf("【Z - 读取前1K,地址自动减少】\r\n");
6 _! `* `& B5 [: j: d% R" \7 hprintf("【X - 读取后1K,地址自动增加】\r\n");
6 x5 v& r! t% U! w* c0 L+ kprintf("其他任意键 - 显示命令提示\r\n");
( ~+ b: Z# K. B$ X$ `' I( k1 K, h上电后串口打印的信息:; T/ Y3 H' n) V+ ^+ L. t

; t- h$ K- X9 A# O波特率 115200,数据位 8,奇偶校验位无,停止位 1。4 V2 @2 |) e2 ]+ X# S. R
6 ?' r2 [6 i* n1 x1 C4 N3 j
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  f8 e* f/ j4 X; [- e" D
+ e; C. a! p- b6 `1 `! ?" U6 F
程序设计:8 B  z* _9 _; V# `0 t  h
. k5 J' H) M2 J
  系统栈大小分配:
* o4 y  S9 \; K0 `$ z' l
# O8 Z8 y& L5 d$ ]
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

2 R/ q. F/ ]9 F/ l8 W: i' {! m, }1 \0 P5 L7 s) E; \
  RAM空间用的DTCM:# s9 z4 {: W/ m  }2 K/ l) ~

: p, e! R6 Z; k8 w& K6 w
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
9 V' o! M  A# D( s6 `) k; H; J0 ]- m
* P6 H% [+ N8 n' T3 n) @: i
  硬件外设初始化. Q) {* a: z! t$ S& ~9 d
9 w7 h& o- E$ c% D' T4 l
硬件外设的初始化是在 bsp.c 文件实现:
, i: n: ^1 O( S
! m7 s2 F: V9 F  y7 B
  1. /*
    % F2 n4 x6 i5 m
  2. *********************************************************************************************************: H; _, z+ ^9 u, ]
  3. *    函 数 名: bsp_Init
    ( O" h7 {# u# O! O( S0 n1 \1 ^
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
    ' j* n5 C3 C7 A% ^( _9 ^
  5. *    形    参:无' e- H9 j! e. ~1 u' |; h% J- X
  6. *    返 回 值: 无
    : p: \  W  ^( M9 w
  7. *********************************************************************************************************
    " v: {0 }0 K  @* \$ S
  8. */- Y5 K9 ^0 F$ r6 }/ Y
  9. void bsp_Init(void)- I( D$ X" v5 {5 E$ E4 T4 H
  10. {2 n9 j: q1 E9 I4 c
  11.     /* 配置MPU */
    $ W3 ]2 c4 s' n" g$ `$ {" M
  12.     MPU_Config();
    ; w% N# P- n" n8 V6 q+ R
  13. 9 q6 Z  D  y" v8 O2 N4 ?# }( X  q
  14.     /* 使能L1 Cache */4 x- B, |8 u0 F+ {
  15.     CPU_CACHE_Enable();
    % r5 F& q' ^* E

  16. * e. u; e7 R, g; Q
  17.     /* # F$ W+ Q9 t. t* ]  W/ w
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:. P( j2 L1 @6 m* k4 I
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。5 \, {) _; ?# L/ y
  20.        - 设置NVIV优先级分组为4。
    4 r. P' N2 r& M: K% H. [
  21.      */* W; f% P3 q) w! f+ \
  22.     HAL_Init();
    # o/ m1 u  V- i" l
  23. + ^- B$ W( v; z0 L2 l, s, z$ p' D. i6 W
  24.     /*
    9 U- l# u# Z0 N; e7 U6 O
  25.        配置系统时钟到400MHz& o% A  T4 F7 q' g0 o
  26.        - 切换使用HSE。0 k5 W3 T: `$ V( c2 O
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    * K, r  G# o7 I  |- i
  28.     */
    5 p: v0 `5 z/ d! k7 E% \2 r
  29.     SystemClock_Config();& d6 v# b0 o& i8 u6 y, i3 x

  30. 3 J4 z6 ]9 V- q; `3 I- W
  31.     /* - o  i, F7 q; [5 k
  32.        Event Recorder:
    : y5 l* S$ {/ @1 z0 `" l
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    + O- L# I' c3 K/ Q# _
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
    * e: T: o$ {6 p# Y; M$ ]
  35.     */    6 H/ z  b/ F* t4 r1 Z& M( [3 P
  36. #if Enable_EventRecorder == 1  & A0 d* `4 R7 m" Q
  37.     /* 初始化EventRecorder并开启 */
    . M' O5 I6 v3 U. c8 c7 P$ f4 m
  38.     EventRecorderInitialize(EventRecordAll, 1U);( i& h; E- U; W3 N" d) o
  39.     EventRecorderStart();
    4 S( r0 w+ B/ J5 P: e
  40. #endif
    ) p- s" x. s0 n* x- N* \( ]  N

  41. / ]8 b2 c' T2 c/ e
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       4 D) |9 x& l3 _* e
  43.     bsp_InitKey();         /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */* {; C. o1 {* u" A7 v
  44.     bsp_InitTimer();       /* 初始化滴答定时器 */& i) n. q! |- b( k  O7 ^
  45.     bsp_InitLPUart();     /* 初始化串口 */
    # k5 E- n; R. ]" ?, Z5 V+ w5 J& h
  46.     bsp_InitExtIO();     /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */      s4 ]! t1 R/ o) ~: a
  47.     bsp_InitLed();         /* 初始化LED */    3 V4 t. a" X4 R  I( i4 o! u$ ?
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    ! h7 b; @: z' t4 N
  49. 0 V3 p/ ]5 a: s
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    2 l  Z5 y7 z1 j# ^; j& a. a
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        
    $ ]$ R) s* r1 G8 s  m9 i
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */
    9 o, G" v7 A. f- [4 r% D
  53. }
复制代码

$ \( I) `* N# m  MPU配置和Cache配置:# p4 R. t% i0 I: a" j; @; S
, g4 N! ?, q! B9 g
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
( L& T4 S* Y. ~! i
( q! i* o3 D$ z: X; k" f9 B1 M
  1. /*! ?2 e: A- C& _
  2. *********************************************************************************************************
    ! o2 F6 y( Z3 o9 J, x9 o
  3. *    函 数 名: MPU_Config
    7 F' R+ a# L, ]+ r# E; k5 {5 U
  4. *    功能说明: 配置MPU
    ) z) l! h$ M) [9 L
  5. *    形    参: 无
    # Q( F3 `6 `6 E& @
  6. *    返 回 值: 无/ \3 k. Q0 `" U* s
  7. *********************************************************************************************************: }% g) f! S+ m. g+ M) v/ L
  8. */$ |0 ]9 m) }. d- i! z2 h# k7 f$ d) t. }" S
  9. static void MPU_Config( void )
    2 l. w# |1 ?: t
  10. {& N9 u/ w  y$ L1 e& B
  11.     MPU_Region_InitTypeDef MPU_InitStruct;+ m8 i* P: q$ m$ t$ L

  12. & P% t0 k# [$ ?- O
  13.     /* 禁止 MPU */
    0 Q& o6 k/ g* _
  14.     HAL_MPU_Disable();
    # I2 \5 O6 q# R& Q/ H# Z

  15. 8 h3 X% g1 I: K! T! f
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */
    $ i- D; j5 {( W8 {0 R
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    7 @$ H( `/ x- E% g
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;( {; s. O7 }: z' e& c. E* p
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    : J4 g& Q% n& u, ]/ @& g
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;6 I' W/ E3 W  c9 P( T
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;  l9 A# l- t' a. {6 i- P0 v
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    % L  }; U  Z* i9 N& t$ O, }
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;& a5 ~( O" ~3 Q* N' C) e. V  ^( ^
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
      y2 \' p' ~  q. z3 i9 w
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    $ [; L' N0 B: F
  26.     MPU_InitStruct.SubRegionDisable = 0x00;0 x5 x( P6 e) Z4 w3 K
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    3 ]" B& ?9 w( S9 W* n+ x" u

  28. 7 q' `# m, k5 b, @( g1 \
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    . s" N3 l+ f. k8 J2 }1 w3 N

  30. ; f& Y6 X  d8 R3 d. Y

  31.   M4 {* L! q. N, Q2 u- d$ Y9 V- D. Z
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
    0 l' U2 s! T0 u/ N( s
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    0 {- ?0 L8 o% ], q' v1 x! N) U" h6 c( k
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;* b: i7 t; @/ G% k
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;   
    2 F+ T0 M- s3 s
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;& `" r. X, e& w; z. L1 E
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    ) i+ X1 W# u' R0 l0 m( R
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;    3 U3 ~2 u& h+ ~  o
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
      j& H3 A- o& G( x  {
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;$ \  T6 V/ l7 f3 X: a* A% V
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;3 Y; [. P" ?* M0 i: a+ k0 L. g
  42.     MPU_InitStruct.SubRegionDisable = 0x00;
    ) s. p3 T  [$ ?6 `9 B& f$ O
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;5 B: f/ u5 Y& Y& e" U0 m4 g3 Q1 o- q
  44. * u5 B- _/ P$ O
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    9 w) \$ v1 Z1 ^
  46. % ]! \/ O8 g/ e  s( \% s7 F
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */
    4 _5 K9 P% k. c9 o  D: b+ b1 s
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;4 K: d. h; s/ E( U8 N- t' M+ J
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    / h  g  `+ m" G
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;" N1 j$ k3 Q# G$ x
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;6 @$ [; A7 ?) s# G
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;* {0 b0 F4 l# A# r! d- N
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    4 \) |( L+ y, c! v4 H
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    1 d# C: i, o( d2 ?4 @. h  z' C
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    ; K% s# u. K0 n- W( M
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    . G9 \* q; F5 D
  57.     MPU_InitStruct.SubRegionDisable = 0x00;0 A" C) E9 c4 l5 r! q& W; ?
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;/ `5 [6 {/ `* h

  59. 3 P, q7 M& j5 X: y) s# U
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    $ u2 H( k7 [8 P; V0 d( ^! T4 J
  61. ) I9 p- W, D, K
  62.     /*使能 MPU */* v4 l: ^; `, n0 D- T( o7 N
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);' `7 C3 Y8 T; g% L% O. L) n
  64. }. K& C2 F7 T! L7 p
  65. " O$ ^: g8 `7 Q  {2 ^2 @! F. ?/ U+ t
  66. /*
    9 [1 k7 R3 x- }, A# e; h8 e
  67. *********************************************************************************************************
    ) I, m  d$ o2 c' ^
  68. *    函 数 名: CPU_CACHE_Enable
    3 G, V7 X4 K# ]$ U
  69. *    功能说明: 使能L1 Cache* b# g0 D+ d+ m" q
  70. *    形    参: 无
    ) Y/ R: I/ o0 P/ H
  71. *    返 回 值: 无
    , w  z0 G) Y; s" Y& u
  72. *********************************************************************************************************
    4 U: i9 p8 R9 W  y3 P! U3 D
  73. */% C( z3 x- U6 R
  74. static void CPU_CACHE_Enable(void)* P! k! m% y2 T) w0 Y. y
  75. {; z3 t6 h' a# F! a+ a% @/ F7 ~
  76.     /* 使能 I-Cache */
    ( ?" Y4 M6 l9 L* Q/ I5 x
  77.     SCB_EnableICache();; W  j- v4 n/ E0 y

  78. " D- ^& a2 C% w" G9 {& P* w8 q
  79.     /* 使能 D-Cache */3 @1 G  l3 U5 U3 g9 s' @
  80.     SCB_EnableDCache();
      |+ p4 N- \0 ~2 E* Y  F. l
  81. }: E, G0 a& u' Y7 w$ q
复制代码
+ a* P& M) u/ F3 z
; U4 w! j1 o# ^: m* J7 @8 K
  每10ms调用一次按键处理:
0 v3 Z' R, w3 k" ^
9 \3 \" d4 e& z2 P0 E  e按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。  U& c2 a% R+ X2 d6 d8 ?( B
3 M3 b1 N& W6 J; |1 g' i
  1. /*$ R* C0 Q% a- N3 S4 k0 \: u
  2. *********************************************************************************************************
    ! f# j; T' Y. z, A7 l/ f& j
  3. *    函 数 名: bsp_RunPer10ms" J8 g6 L- c4 u2 `' ^# s
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求7 Y: v5 N; z0 L/ y! ^8 F
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。; O; r% c3 M3 S4 n; Q: W  P" ]& l
  6. *    形    参: 无
    * W! x+ p% Y  r0 j  S7 o3 g% c
  7. *    返 回 值: 无. z# U2 }' \; T. G7 U9 ]: U. m$ h
  8. *********************************************************************************************************
    6 t- Q0 m2 g2 U# y& E" r
  9. */: i' e/ o9 z4 J+ j- u
  10. void bsp_RunPer10ms(void)) T& m) I. j! u( u
  11. {0 q0 W% B% S; `
  12.     bsp_KeyScan10ms();
    . V# y. h( F6 S! B3 N' M
  13. }, i& ~1 z5 R$ D* z  m: @  y
复制代码

8 b6 A( ?2 h6 ^, J! J
# y3 }& ~, h+ m  主功能:# ~, {7 q) w2 \2 j. c

4 w, V. s6 E8 a- _( \主程序实现如下操作:" ~% L$ `2 e: h0 J5 g

0 Z7 ?, m) E1 r* f0 ]9 T2 G  启动一个自动重装软件定时器,每100ms翻转一次LED2。5 p6 _. e; O! ?* X2 Q! w; C0 q0 ^
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可( @6 `2 A( v3 |0 q
  请选择操作命令:4 B* q) G6 z5 r
  1 - 读串行Flash; H; g& t4 P1 ^" w; _. F6 K+ L, M. T
  2 - 写串行Flash
5 n, v" K* U: O  3 - 擦除整个串行Flash
$ O6 p/ z! C0 z# k# u7 }, O5 o  4 - 写整个串行Flash
( w8 U0 v6 d9 \  5 - 读整个串行Flash+ g" |- G  F( @0 Q+ E4 u3 H$ ?) T
  Z - 读取前1K
: V4 g; H& A% A' D  X - 读取后1K
1 i4 ]5 t# s$ d
  1. /*- y/ w5 U% E6 X9 @; y5 X( K
  2. *********************************************************************************************************4 p4 U- D7 z% p5 x
  3. *    函 数 名: DemoSpiFlash
    ; c& {9 s# t/ E7 c7 Z& b2 B
  4. *    功能说明: 串行EEPROM读写例程
    ( D& b- U( B% B: S
  5. *    形    参:无
    1 x5 W$ {4 K! i
  6. *    返 回 值: 无+ V* V' e) V0 F; H
  7. *********************************************************************************************************8 N5 O4 h( L$ K  m  p- Y1 ]$ l
  8. */
    7 |4 I5 x  o' h3 f* U* K
  9. void DemoSpiFlash(void)
    6 {- X! l6 J* }
  10. {
    ; \/ n. q( n. u# K; j7 z& X( u
  11.     uint8_t cmd;
    8 a. ?8 u* H0 b1 Y: l# m9 }- ~6 z  C
  12.     uint32_t uiReadPageNo = 0;% U* ~3 F' b" w: x$ p8 P- Y

  13. # v& i7 s0 O( k$ h) I) ?
  14. 1 |  I+ Y8 s# T' w, d+ ~! K8 I
  15.     /* 检测串行Flash OK */
    . E# I& k1 w8 [' {, C
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);: O4 U3 {, p4 Z3 ]2 U6 F0 c5 [
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    1 i8 G3 }; p: O0 G- j, ?6 L/ b9 C

  18. 2 z6 H5 P" U  P; W: E7 v
  19.     sfDispMenu();        /* 打印命令提示 *// X5 @% R4 I9 h' z2 x/ S9 l
  20. 3 E" J5 {+ b$ j4 u) e! s. R$ g
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */! ?" }2 S7 m/ b7 ~' j  k
  22. 2 n- o" x4 H# |' Y, e9 K9 I3 p
  23.     while(1), D7 O/ u% d5 I9 S
  24.     {8 i7 c/ y# j0 e$ h
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */8 n8 f8 v2 v* m  F2 p
  26. , a: h" e  H; v; z- y& W  u$ M5 b9 ~  m
  27.         /* 判断定时器超时时间 */  w" k) H/ F6 x: A$ O1 ^5 |, ?8 w! N
  28.         if (bsp_CheckTimer(0))   
    ) y% m+ o% A3 Z' {, S4 ~/ j
  29.         {
    $ B2 K$ P4 o$ E' y( Z
  30.             /* 每隔100ms 进来一次 */  
    + ^. ]7 L" p6 y
  31.             bsp_LedToggle(2);
    & k6 H  p( h4 t: x- h
  32.         }
    , _2 T, m8 i* s

  33. % J5 f$ o' v) l4 T/ {' a
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */
    0 J% Z) _6 I& C2 ?+ V3 B7 s% A
  35.         {
    8 ~7 L2 j; Y; J1 s) _- u
  36.             switch (cmd)  @6 S0 z( j; d8 z7 L" Q
  37.             {
    : V7 H& C- X* {5 g8 c% @
  38.                 case '1':
    & r+ Q8 g+ ~& z4 H* K6 q* T
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    4 N2 g3 b2 Y0 n; @
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    1 ]( h) Z6 O  S" B" ]1 U
  41.                     break;' e7 V9 R" ~2 M2 n4 E3 Y2 D

  42. 4 ?5 g& m/ x" v& Z* U- j
  43.                 case '2':
    # T) p6 T7 \7 N/ V& G
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
    / a4 I- W# G" n# \! \6 f$ V: ~
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */
    ; d* E, F5 E4 k+ Z& V- P
  46.                     break;
      g+ Z1 }7 i0 I$ p; \: K

  47. & I6 e5 s% I7 X( p1 }( |* \4 {1 S
  48.                 case '3':. T; t" t! ^1 X$ K
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");& w+ Z& z9 ^5 d" I
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    . c' n& Z6 [* D) B3 m
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    * b- u0 ?) L% x# v3 ~! }9 ?
  52.                     break;+ K2 f' L& r! k* P
  53. * T& i+ @+ c* _. b3 a
  54.                 case '4':
    % |# Y. G$ l" F4 O2 D9 }
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    4 E/ G0 o) Z4 I
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");6 B1 h/ Y5 d1 F
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */" d0 b+ l7 l3 U' e$ {1 q; P( p
  58.                     break;& ]/ V% K/ f$ K/ W7 j& G8 N" u
  59. + @1 h1 o2 y; U; g0 Q
  60.                 case '5':4 w4 r/ H' _6 q* P& ~' h# N) l7 c3 }- F
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));- W* {9 W( _: i7 b& m% }' y8 {" t
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */  V" V6 y3 C4 E9 h
  63.                     break;
    4 a# B* w" Y2 L+ Z2 z& Z

  64. " C% [; _9 r) a5 w
  65.                 case 'z':9 f5 W8 Z; U" U+ Z8 l
  66.                 case 'Z': /* 读取前1K */7 Z! ]3 }4 _! e1 x4 m1 b
  67.                     if (uiReadPageNo > 0)6 ?0 s- j2 G4 g  [( u+ T* z
  68.                     {
    4 X) H/ j4 Q: s. ]/ Q3 M# a
  69.                         uiReadPageNo--;5 H% |3 H" c3 a3 F3 i
  70.                     }" y2 z9 i/ k2 p
  71.                     else
    8 X# d, W/ k2 a6 s; R
  72.                     {! b1 t" X8 S3 t2 }
  73.                         printf("已经是最前\r\n");
    & o/ }0 T1 _6 a& h) I% @1 t* U! _
  74.                     }
    % w! W$ b4 T" v  r1 J% Y! K6 D6 n
  75.                     sfViewData(uiReadPageNo * 1024);7 a) Y7 C: z  W  j  X
  76.                     break;
    ) p, V/ ~7 j* h, Z- I$ W9 }; g) T
  77. 5 \& R* W, _* f% e1 [7 b9 X
  78.                 case 'x':8 g$ q! |' l5 L8 [$ M
  79.                 case 'X': /* 读取后1K */
    " @( |" X# m; Q4 J+ s/ K/ }
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
    ( C4 O& k. `( V2 n. n" d- J) k9 _
  81.                     {
    7 H& |% x( X  R4 e5 g2 }/ o
  82.                         uiReadPageNo++;+ B6 z# }7 O7 ?" p9 V
  83.                     }  z% o" I2 ?; b8 F# ~
  84.                     else
    . b8 p; L& s$ f; M" Y7 q* v
  85.                     {; ~$ w  G, @/ o8 c
  86.                         printf("已经是最后\r\n");6 N7 K$ @+ R: @+ I
  87.                     }; x9 o5 e+ l$ A2 N2 o
  88.                     sfViewData(uiReadPageNo * 1024);" ~9 F8 \8 L* N
  89.                     break;3 V2 I- M2 ?% |2 p' X, S' H: q5 N
  90. , N& ?' ~$ ^, d( D* W  C
  91.                 default:* u0 P8 |7 E- }; o' I
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    & g* }- l1 o" c3 B1 {, Y
  93.                     break;
    ( N- s; ]  C' x, e# \1 F

  94. / S1 j' U! X! F" u9 g  }6 q* \  S
  95.             }" W) U7 Q9 Y' q
  96.         }2 A) r: E7 |  Z' G
  97.     }, z/ `  q. j1 q4 m/ y) b
  98. }
复制代码

' k. }" G8 Y) s7 s73.10          实验例程说明(IAR)) |6 M- t2 Z* C
配套例子:
+ _- ?. {7 o, K  b  O/ L
  N% D2 k5 |( o: R  HV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
& ^7 B/ q: O. Y. @* Z1 H8 K, i! V( t; j; n( G" ~1 d8 Q) z& t
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
; E) o0 _6 `5 @* w$ F; L+ o( q) Y
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
4 ]2 a* e: V# a# t1 W$ a: [) P# b! T( k9 }" G
2 b4 U6 c: l% E8 L实验目的:8 ]/ E* I0 _8 X+ U. A3 \" B

$ O1 k. n: t+ C3 G学习SPI Flash的读写实现,支持查询,中断和DMA方式。+ i1 A+ z- j4 S) t! Q) a& I
实验操作:2 S$ z# s5 ?- Y2 o( d% h" |+ _

# W2 F- u8 N$ O) N* h支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
: A- d" k& p) N6 h( x( [. uprintf("请选择操作命令:\r\n");( L: a) v# g1 p% v8 ]9 D
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);1 I, H, U# W& q& b; Q8 b
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
% g3 b& h" o+ [# Z6 y; |printf("【3 - 擦除整个串行Flash】\r\n");( {! Q6 K' e. a! Q. Z; l4 u/ V
printf("【4 - 写整个串行Flash, 全0x55】\r\n");3 `. K4 y6 T: z$ K9 C& z1 Z7 I
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");
% O# Y- ]- d/ @/ yprintf("【Z - 读取前1K,地址自动减少】\r\n");
5 {7 @: r+ A& v; r+ x) d) pprintf("【X - 读取后1K,地址自动增加】\r\n");
7 v* |: h4 L: }% Rprintf("其他任意键 - 显示命令提示\r\n");
% s* @; ?% X3 F1 C9 _上电后串口打印的信息:
# p) a% g9 \0 w" z' A波特率 115200,数据位 8,奇偶校验位无,停止位 1。
( W0 s" J' w: p+ o
+ L5 _  H5 |7 c( N+ S: H: G2 ~3 q
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png

' O+ k7 P1 B& B! w4 V2 `; Y/ }  h: y* A- X4 ]: ?0 N
程序设计:
) p% \% A4 o/ {0 T) l- ^9 J
6 H7 G2 X* D# e% l6 u  系统栈大小分配:4 T+ ^% C) Z  {* B
  i/ \6 g* x4 ?+ Q
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
: q: \* E) n  B( O. i
1 H2 A8 h0 I" J3 W6 N% ~. N& `( z2 V
  RAM空间用的DTCM:- G# ^0 k- |- l" h' Q9 o8 }

7 F  g' s6 y* ^6 N
aHR0cHM6Ly9pbWcyMDIwLmNuYmxvZ3MuY29tL2Jsb2cvMTM3OTEwNy8yMDIwMDMvMTM3OTEwNy0yMDIw.png
  D" ~! m( z) X3 T

  H4 y9 ~9 g* U5 f( a  \) q  硬件外设初始化
. t$ T: j# Y; x2 r7 T' P0 |& [
  q" S3 M  j: o% n硬件外设的初始化是在 bsp.c 文件实现:
/ A" N  ?: t: O. k- _& Y
0 `9 H* x% v& O4 m" A* j
  1. /*
    * X5 b# Y$ k6 x. D- o. \+ J
  2. *********************************************************************************************************  H4 X3 u  z/ a5 Y
  3. *    函 数 名: bsp_Init6 J3 R+ t$ P3 B
  4. *    功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次6 ]% M. j; Q; u# O* [7 U% X
  5. *    形    参:无
    9 @& a5 U! }# |* F' F" P* W
  6. *    返 回 值: 无5 q* x( X$ W8 F6 H' X  @7 S% v
  7. *********************************************************************************************************
    # V0 D6 [8 J  W" @. o, ]
  8. */
    8 K/ ?. W* c# ?" z3 l4 ^
  9. void bsp_Init(void)
    . k$ \$ D! L( R# x4 Z, X
  10. {
    ( l2 B6 n& i( x8 x8 f5 U. C
  11.     /* 配置MPU */2 c. |/ a  \; k& H, j6 L) L, R: @
  12.     MPU_Config();
    , k% I  e2 S  y

  13. 1 @4 u( B2 ?0 P% C! ]6 i
  14.     /* 使能L1 Cache */
    0 L& R  K/ @- k1 z2 w
  15.     CPU_CACHE_Enable();. O! d$ T+ D0 a0 M& u4 {( N8 @

  16. * W4 A8 S/ x7 |( q4 p' X0 H. F
  17.     /*
    ( s- O! k% U$ S8 u! f2 q# [
  18.        STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
    , k6 {6 }; h* Y" S: Q( V9 z  T9 a
  19.        - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。: I9 B3 u) B6 V; e
  20.        - 设置NVIV优先级分组为4。' c. B$ w( E, |8 C. Z3 j, d
  21.      */2 e; B$ c, ?: [( @! k) f
  22.     HAL_Init();- w" Z. I& e7 Z: g7 V
  23. + [! b8 d, p2 x
  24.     /*
    , t" a+ z6 |+ |' P$ f+ a# t$ S; w" i4 Q
  25.        配置系统时钟到400MHz
    5 H* r, t6 v& E/ x7 F! ]7 z
  26.        - 切换使用HSE。7 a" ?" z, O! v+ E& s# N. b! z8 u$ L
  27.        - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。
    1 e; k# ~" T8 j1 b8 `
  28.     */: w$ R  C$ ?3 T  y% A, e, D; F8 p
  29.     SystemClock_Config();1 b/ K) R( D1 s6 [  |8 p; `6 e

  30. * i' g9 u1 X: b' q8 u& @9 b9 o
  31.     /* 5 r. y8 \% A4 j7 D
  32.        Event Recorder:
    " v2 U4 T# p) G; ^% [9 h3 z! S( `
  33.        - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
    $ T6 n  S3 Y# P+ O7 P6 Y2 A
  34.        - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章+ m3 l) R# r  S
  35.     */   
    6 u3 _; d7 L' u, O, ~. J
  36. #if Enable_EventRecorder == 1  
    9 h0 E; ]- H6 K1 d. b
  37.     /* 初始化EventRecorder并开启 */
    , g$ u5 K* a# A. G: Z; |/ k/ ?* E
  38.     EventRecorderInitialize(EventRecordAll, 1U);
    % C* W& ?& [! M- U2 m
  39.     EventRecorderStart();5 V0 _" C1 g" p9 _) E7 q& \
  40. #endif
    8 l* W3 W/ Q2 K- b& F5 M6 o4 }

  41. ; H! L. ?! K2 y2 j" ?: b
  42. bsp_InitDWT();      /* 初始化DWT时钟周期计数器 */       9 ^' R! h- F. n6 x3 a/ J+ T
  43.     bsp_InitKey();        /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
    & v/ C1 q. ]& U* c, v5 Y# |: v
  44.     bsp_InitTimer();      /* 初始化滴答定时器 */
    & D1 X, T* o+ H( A. L# |2 ^
  45.     bsp_InitLPUart();    /* 初始化串口 */
    ( X% X# Q) |( W: H& n; P
  46.     bsp_InitExtIO();    /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */   
    ' L2 g. g6 e- J$ r, C# a+ f
  47.     bsp_InitLed();        /* 初始化LED */    ) s$ u) ]8 ~1 g9 V1 ^9 `
  48. bsp_InitExtSDRAM(); /* 初始化SDRAM */
    9 ~/ B5 s# A9 Y! x" [
  49. 8 Y2 V+ f! j  z4 {* E) S* t& I
  50.     /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
    - a# [* u6 p$ |8 f: C
  51.     bsp_InitSPIBus();    /* 配置SPI总线 */        6 |4 Y: |4 |+ Q1 L0 i8 }
  52.     bsp_InitSFlash();    /* 初始化SPI 串行Flash */1 p3 G. V1 Z7 Z9 L
  53. }
复制代码

: a( i* @" i# L$ E+ s0 _
6 K6 p/ |* v# n* f! r( V# a* T& y8 e3 r  Z( Q6 ^8 ^$ P
  MPU配置和Cache配置:
! Y# m2 p1 _+ u3 K
: k: c- Q9 [) L  A1 E6 F, C数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。
" ^' ]5 o+ O( {& n% }8 Y3 [5 P# q3 i  [! v6 X. b& `& N) u9 E
  1. /*
    , x. B- P( Z* y- s
  2. *********************************************************************************************************
    + _; A- r$ k; \% p7 M
  3. *    函 数 名: MPU_Config' ^( M6 T4 `) ~8 i$ Z
  4. *    功能说明: 配置MPU
    , p* e  U  X) r3 a2 ]) q* ]
  5. *    形    参: 无+ b1 M7 P' x$ e, j# c
  6. *    返 回 值: 无
    4 M9 a; L! f' t0 p5 M7 R
  7. *********************************************************************************************************. j7 i  h- w* n* G; H& k" {+ j3 v
  8. */' H# U# X- ~1 F2 l
  9. static void MPU_Config( void )3 W# l( p5 l8 c% h; K3 y% X
  10. {# T' u, W4 p- C0 x
  11.     MPU_Region_InitTypeDef MPU_InitStruct;
    + h" o4 J1 B. O8 {) j
  12. , m1 q8 C  ^  z0 T* [8 q) D
  13.     /* 禁止 MPU */; ?6 b8 U; Z- R3 i/ l5 S
  14.     HAL_MPU_Disable();, }( H6 W. @% l5 R0 e
  15. & x5 b4 w$ w$ ]' A7 |9 J7 U5 |/ W
  16.     /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */6 s1 E% [- }" ^& x( h
  17.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;# l& b. }) l( }& y+ R0 h
  18.     MPU_InitStruct.BaseAddress      = 0x24000000;
    + t/ b. k8 j" C% D3 ~% l& R$ K; m
  19.     MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;( G  I5 ~* G; V) O- h- `; X
  20.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    ; w; w9 @9 l5 y
  21.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    / s  V8 z% W9 A, u6 \( P
  22.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
      ~% ]; E( j+ e' w0 Q6 r
  23.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;! r1 P/ d: V/ l* \. g
  24.     MPU_InitStruct.Number           = MPU_REGION_NUMBER0;1 H& x5 k+ @, p! i
  25.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;. O+ S0 }$ c2 |1 P, P% G
  26.     MPU_InitStruct.SubRegionDisable = 0x00;
      @3 N1 l2 k* b% f$ q
  27.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    % h7 k+ N+ @6 j1 K1 f
  28. , j& \7 @  k5 Q) }* F1 D- W6 y# x
  29.     HAL_MPU_ConfigRegion(&MPU_InitStruct);4 L* G+ n/ s/ m+ P& Q
  30. 9 h: \/ i/ }% I" D% x# ?

  31. ! H" I4 g' c3 v& d
  32.     /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */1 v5 K, C6 |0 k5 [# F9 l
  33.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    8 R" J1 o7 A$ z
  34.     MPU_InitStruct.BaseAddress      = 0x60000000;
    . U/ w: B9 |' i5 m+ T! _
  35.     MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    $ d' ^+ Y1 G+ D% C& z
  36.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;' d2 t! r& V$ S
  37.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;  U3 s) ^. R, _2 K- y
  38.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;   
    , |; `$ L: Y* p# C7 l
  39.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;. [6 }. O+ k9 G0 u4 T) g
  40.     MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    " k3 B% R/ ~9 _+ f
  41.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    2 P: `& O+ @6 Q9 Y" l0 v3 @9 v
  42.     MPU_InitStruct.SubRegionDisable = 0x00;! y, Y8 \0 P- o
  43.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;+ S/ I, N/ X. }, S- }
  44. ( `9 ^6 h& \/ [1 Z+ l/ @. p9 {, P
  45.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    ' u; ^; p" C# g+ a& E
  46. # l( [' n0 G2 x
  47.     /* 配置SRAM4的MPU属性为Non-cacheable */4 E2 {% ?% b8 A: D3 ?" o
  48.     MPU_InitStruct.Enable           = MPU_REGION_ENABLE;2 ~2 f0 v1 D7 v! `' X8 ~) o" d
  49.     MPU_InitStruct.BaseAddress      = 0x38000000;
    8 ~) K; Y$ G) L" J  X+ u
  50.     MPU_InitStruct.Size             = MPU_REGION_SIZE_64KB;' Y7 k2 i+ t9 @/ C
  51.     MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;) u7 k5 P9 L. P3 b# x
  52.     MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    ) B2 b! m9 z/ c
  53.     MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;" j2 L/ t' e: ]3 f' l
  54.     MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;0 w6 l3 a5 \1 V' F( d1 b! A1 ^: M
  55.     MPU_InitStruct.Number           = MPU_REGION_NUMBER2;
    ; R2 `# U8 i5 T) \2 X( \
  56.     MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    + `+ \9 h; J) T, w2 K& Q
  57.     MPU_InitStruct.SubRegionDisable = 0x00;
    3 z; M5 _. K( {8 ?
  58.     MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;: o, \. b# L; F. J8 K0 B: @. p6 c
  59. % T" O4 {  c2 O
  60.     HAL_MPU_ConfigRegion(&MPU_InitStruct);
    # e& {7 F6 s. Y  L7 Q

  61. 1 C0 P0 c/ _( I$ P5 v1 N" E  u$ r0 }
  62.     /*使能 MPU */
    & M. V4 C- P( p* q% _4 k- V+ u& b% a, z; n
  63.     HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
    ) }8 D5 y, \8 p2 G- h0 o, }( c
  64. }/ O1 A4 G2 p% g( ^' q2 ]
  65. : p# c* U7 z, g9 K% C) _
  66. /*
    : E+ U2 f4 E$ [/ }
  67. *********************************************************************************************************1 M; n% u7 T3 k/ k+ A2 {
  68. *    函 数 名: CPU_CACHE_Enable4 b2 B+ l% [! @. J8 \5 i" j6 R% P
  69. *    功能说明: 使能L1 Cache" E# j; }% I4 B, e8 B1 x& l# B9 C
  70. *    形    参: 无
    6 X$ f5 z" F( j& {' i( g( }
  71. *    返 回 值: 无& k( u0 i2 K& B2 R7 D
  72. *********************************************************************************************************
    * z: e; r, o8 j+ `  C' Y: K
  73. */# e+ e! l1 {1 U; t* U2 t
  74. static void CPU_CACHE_Enable(void)" k2 b$ O- A/ J& }; ?% T3 j
  75. {* d' [- I4 q* n% B, V
  76.     /* 使能 I-Cache */; n* b0 M6 S4 U* c
  77.     SCB_EnableICache();( F9 z) |/ T; l& O
  78. 8 T- i  j6 U2 }% z( Q5 i) v9 L
  79.     /* 使能 D-Cache */
    : P5 l) {4 \8 b, L# j, [  _
  80.     SCB_EnableDCache();4 ^- m2 [2 t6 u' V0 P; T
  81. }
复制代码
" L- C5 l5 X* N% Y
  每10ms调用一次按键处理:8 \4 l8 s$ f8 _1 d, K. t5 `& U' p
) H- |% C& f% }1 d0 v5 l, ^
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。3 T/ Q  |$ Q0 E( o7 d4 t5 F& `
( {7 ]3 O# a& f- [' O
  1. /*
      m# d# V; h: i& K! J, G
  2. *********************************************************************************************************& _" [2 n3 U. c- R
  3. *    函 数 名: bsp_RunPer10ms9 }# p1 y7 b% g! R' O' t) _9 w
  4. *    功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求$ a# _$ P1 q0 f8 N! l( N+ z7 h
  5. *              不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。; M% f  `8 Z  o7 k8 f
  6. *    形    参: 无; {% p3 h# P! C7 k! `" v
  7. *    返 回 值: 无
    7 c0 T2 S  w9 c
  8. *********************************************************************************************************
    7 w9 P2 V" H, f+ B- [7 T; V$ U& }0 G
  9. */
    & ^5 I9 Y' V. `% E
  10. void bsp_RunPer10ms(void)
    " \/ n4 q8 K; d1 o2 B1 T
  11. {
    4 _% F  N, d4 L
  12.     bsp_KeyScan10ms();
    2 E) l' V5 x1 D( f) g3 g! R6 K  W
  13. }
    ' w) K( p4 J2 c) b3 S/ T
复制代码

/ [5 ]; i$ [4 M( Q2 l8 ^4 i) y  主功能:
' {2 ~+ B; g6 ^9 D- p. @* h( J  U4 r% }. y4 [& Y. a
主程序实现如下操作:
$ a- h( K5 |$ B, @
% h4 O6 J, `* n8 P  启动一个自动重装软件定时器,每100ms翻转一次LED2。3 a! y8 Y* m  s$ w" F: M* g
  支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
) S, u( Z2 Q7 ?8 T) v  v( |  请选择操作命令:5 @# f' h) F+ S2 O+ W. S/ [* j# W
  1 - 读串行Flash
' q: B+ H5 E- p8 h" T' `' `/ z  2 - 写串行Flash
" }, V3 p" M  A6 e) ], v  O  3 - 擦除整个串行Flash6 W' ^* Z5 t0 Y
  4 - 写整个串行Flash: q( d, B; f! g- A; a+ k0 C
  5 - 读整个串行Flash
+ V6 l3 e$ o, x+ b; w  Z - 读取前1K1 ~" D, }7 w0 N; t8 Q! w% B3 K& Y
  X - 读取后1K8 K! |& ]; _' o4 [0 }
  1. /*
    0 c# b8 N: J0 e* h/ i8 N
  2. *********************************************************************************************************
    ) H6 t9 D: U7 u; K. s
  3. *    函 数 名: DemoSpiFlash$ g0 m/ R9 F8 E% o' ^
  4. *    功能说明: 串行EEPROM读写例程
    ) t/ y* c$ o3 x& ]( G& V2 T
  5. *    形    参:无! h: O; r8 H  C
  6. *    返 回 值: 无1 H% X5 H' V/ }/ R1 U3 x
  7. *********************************************************************************************************0 ]) ?+ S: K) G9 X$ E) x" n
  8. */
      v3 a6 z5 t; A% I/ n; E7 m: ^
  9. void DemoSpiFlash(void)7 j* A3 F5 ]% p* Z2 a
  10. {* a$ r/ o/ K: f7 o& l1 _6 J
  11.     uint8_t cmd;
    8 _2 J, N" `$ l, S5 P( q
  12.     uint32_t uiReadPageNo = 0;* I" e, U3 @( u8 f+ R* g  ^

  13. 3 P3 L% l8 D" {. {7 Z. O# L
  14. : C3 v% \) u" l+ h3 l
  15.     /* 检测串行Flash OK */
    2 A6 g3 V! t7 D7 {
  16.     printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);
    3 L" A6 G! m1 d1 Q! S4 H9 k
  17.     printf("    容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
    8 @" y: @7 S5 Y+ ?* y2 O# c
  18. # B: W$ ?( T' m9 v6 \4 P
  19.     sfDispMenu();        /* 打印命令提示 */$ v6 T+ `2 g' y" Z3 C1 }. @. i

  20. 4 s6 d" p# u( g6 C9 Y0 i# X
  21.     bsp_StartAutoTimer(0, 100);    /* 启动1个100ms的自动重装的定时器 */6 m8 u5 P  G% \9 N4 D
  22. 5 a3 g' U% ~" M; s6 T8 u
  23.     while(1)1 C0 K* Z$ ], z; E5 Y  x$ a
  24.     {
    5 Q' |7 h- B( \! p
  25.         bsp_Idle();        /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
    ! w# o1 E. J: U- S' T

  26. # M7 g; w6 P& v( i* m! f8 y, `
  27.         /* 判断定时器超时时间 */8 E& L) V4 R3 Y
  28.         if (bsp_CheckTimer(0))   
    + g; [' f3 j4 W1 U0 d, n; p8 E2 h
  29.         {
    ) z6 W6 A9 w9 Q9 l/ R
  30.             /* 每隔100ms 进来一次 */  6 T- j, s' a1 a
  31.             bsp_LedToggle(2);: S; G( M& n3 `
  32.         }. f! R' }5 d3 s8 K; r0 I  b# m
  33. ; }! B' b" N& r# `" _
  34.         if (comGetChar(COM1, &cmd))    /* 从串口读入一个字符(非阻塞方式) */  E2 A, Z3 g# |. ^
  35.         {
    4 m* d* K& r/ M
  36.             switch (cmd)
    2 I; D1 w' e% A. ?; m
  37.             {% ?  T$ v: u- n! [
  38.                 case '1':
    & C, m, d4 n: w7 T2 M* |6 Z/ P8 f/ N
  39.                     printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);* a' B8 N4 p  b+ o8 g% D  k
  40.                     sfReadTest();    /* 读串行Flash数据,并打印出来数据内容 */
    # W9 o& w# Z. Q) e3 c6 _  c
  41.                     break;
    1 l% e' d2 Q, n/ ]- G1 n' w
  42. 4 i/ K2 x; U' {" Y+ I. S" u
  43.                 case '2':- D' L$ z6 ^  p1 {: Y* Y( F8 b% D2 C- J
  44.                     printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);1 y. z3 k; Q9 j% ^- c
  45.                     sfWriteTest();    /* 写串行Flash数据,并打印写入速度 */" n  b6 l. ?1 }  I% z
  46.                     break;) i( _/ U  a, q5 p
  47. & a4 C* i# D% h2 T
  48.                 case '3':
    8 o, ^! T& _& K) Z
  49.                     printf("\r\n【3 - 擦除整个串行Flash】\r\n");
    1 r, H1 A" l3 q* T* p- C% L
  50.                     printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
    2 `* e6 }. ~; }, l" I7 O3 G( R
  51.                     sfErase();        /* 擦除串行Flash数据,实际上就是写入全0xFF */
    - H! X2 g* v9 B- |1 A& x" b1 v
  52.                     break;
    + N7 M: u6 W; z. K! s" `1 Y/ y

  53. 1 Q! y: y6 ?) s$ {+ ]- a. v! n! C
  54.                 case '4':" b0 Z/ Y. X( A
  55.                     printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
    . p0 |4 t' s0 g! i. j4 {
  56.                     printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");6 |8 D- {4 \$ B& E) A) H0 U& H* d
  57.                     sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */, M! E$ V: L  M3 @1 m# f' q* Z
  58.                     break;$ n# h/ X6 u5 J, E. y/ X. k

  59. 7 F) j! C8 m0 P4 ?" l
  60.                 case '5':
    * n& v2 f9 g) x  D
  61.                     printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));
    9 M6 K2 }6 m1 R; u9 k2 Z+ U
  62.                     sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 *// K. c. E9 b; f# t
  63.                     break;) v5 v" R2 Z# D* U+ l9 ?3 m" E
  64. / y$ u3 N0 N5 M. X$ |
  65.                 case 'z':8 T" M. \; Z1 C
  66.                 case 'Z': /* 读取前1K *// B* d9 U2 L% j% V: T3 E
  67.                     if (uiReadPageNo > 0)2 R( T+ \. a0 Q, r: C7 G+ r' w
  68.                     {
    1 F+ h# P1 I1 w0 h# Z& j; T
  69.                         uiReadPageNo--;
    : d. v  e' n9 t9 [
  70.                     }
    7 s. N/ k% {7 d- r+ s: v
  71.                     else
    + g7 u0 c3 a5 ?" P# Q
  72.                     {6 K9 u. ?4 H5 h8 ?( C: L/ s- Z
  73.                         printf("已经是最前\r\n");
    / G  k# H2 m2 v
  74.                     }
    4 O! @# L6 U$ A& j
  75.                     sfViewData(uiReadPageNo * 1024);  n. F) `0 t. k% d3 y% ~% Z( s% O
  76.                     break;
    1 ~' Q# a7 {+ v% K- l1 R" x3 b# ]

  77. ' I3 x" C3 A, h6 Y; l, H2 ~
  78.                 case 'x':5 `  c# K1 Y7 w: H, h
  79.                 case 'X': /* 读取后1K */
    - H, l: [5 u5 w! P
  80.                     if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
      Z! H+ P9 ]8 c4 N! ]9 u4 w; Z- o
  81.                     {; n' j: |: m3 E/ w0 m( ]
  82.                         uiReadPageNo++;$ c5 h1 i& ~) x! L9 z7 i
  83.                     }3 {  K! ?4 b# g! Y' w
  84.                     else: f: A2 S3 X! s& z
  85.                     {
    ( H8 M/ R% e1 `9 I& h
  86.                         printf("已经是最后\r\n");
    5 _2 H' w2 u* b
  87.                     }
    & u3 C- n2 L/ F8 e
  88.                     sfViewData(uiReadPageNo * 1024);
    * K" @, Q5 L+ [6 s0 }8 [% s8 ^
  89.                     break;; ?5 ^( z% j8 x3 N) V5 E4 E
  90. ! B/ n( U$ X# T7 t+ M4 x2 M8 w
  91.                 default:' ?5 W+ g# j' {- d& ?8 U, K5 W3 l
  92.                     sfDispMenu();    /* 无效命令,重新打印命令提示 */
    ; Y4 w) ~, U; ^: ^, y
  93.                     break;. Z1 j) x: _; q. f2 N8 l/ z

  94. - g* O0 r; Z% U
  95.             }
    5 y" v& m4 d3 D4 J/ m
  96.         }
    ' R" ^- C% i/ R' m$ ^0 h
  97.     }
    4 `1 W& _4 C) V1 j
  98. }
复制代码

3 x8 g6 f0 x5 U0 q" ?& Q% ~- q" |. c7 C* x* i

8 u2 _! d4 {- e/ ~8 W9 u/ c73.11   总结  h$ E0 p, C( ^% j/ ?% K
本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。7 |4 P1 W' i4 Y( I

2 J3 a4 N" P/ a" C! p
: ]" t/ I0 Y$ U! Q- a
6 r$ J1 X  R% g: U: K
* o7 m; z5 @3 L& F* W3 `# z) K
赞 收藏 评论0 发布时间:2021-11-3 10:26

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版