73.1 初学者重要提示/ {( E* ~# j7 A, Y
W25Q64FV属于NOR型Flash存储芯片。) G; u9 z* x+ b7 \- |
W25Q64JV手册下载地址:链接 (这是一个超链接),当前章节配套例子的Doc文件件里面也有存放。 J$ q. w1 X3 D+ [' c6 s+ a U
2 ~9 K) O& K+ F/ u3 b- i" z) S
) @$ K; e4 ]; L; o) v* H3 `7 u7 } 本章第3小节整理的知识点比较重要,务必要了解下,特别是页编程和页回卷。+ C7 A" l" p6 J! i
对SPI Flash W25QXX的不同接线方式(1线,2线或者4线,这里的线是指的数据线),编程命令是不同的。 V* j1 h& a) [3 b7 h4 d. U$ g
W25Q64JV最高支持133MHz,但最高读命令03H速度是50MHz。* `0 L: A7 I6 Q" A" w2 b
文件bsp_spi_bus.c文件公共的总线驱动文件,支持串行FLASH、TSC2046、VS1053、AD7705、ADS1256等SPI设备的配置。
3 }' v; K' Z' h7 j# A- H6 f 函数sf_WriteBuffer不需要用户做擦除,会自动执行擦除功能,支持任意大小,任意地址,不超过芯片容量即可。+ p/ Q0 E3 M% r0 U* _
7 W+ l, z+ C, T) [73.2 W25QXX硬件设计$ F: Z( q4 b" ~$ |, l
STM32H7驱动W25QXX的硬件设计如下:# n# V& n5 |+ G; B; ~
# m) [7 f: m8 y) {7 z
' y d' ^. K' K
0 _3 Q. z! Z# h" x$ i( h n) m- {1 U关于这个原理图,要了解到以下几个知识:$ g9 ~) i7 X& ?# Y$ I) {, Q" ^
4 z, N. _) H1 r" {" @; q! w' t# N
V7开发板实际外接的芯片是W25Q64JV。
6 M) I/ F2 o6 _1 |: ]7 N0 F CS片选最好接上拉电阻,防止意外操作。
( O- v2 _* `: L! Z 这里的PB3,PB4和PB5引脚可以复用SPI1,SPI3和SPI6。实际应用中是复用的SPI1。; Z- \8 c5 u; y& b0 k9 ?! w
W25Q64的WP引脚用于写保护,低电平有效性,当前是直接高电平。6 w& e3 i. ^( t% c( Q |% E0 T' s
HOLD引脚也是低电平有效,当前是将其接到高电平。此引脚的作用是CS片选低电平时,DO引脚输出高阻,忽略CLK和DI引脚上的信号。, b. Q1 ^7 z# n ]# O7 W% |
73.3 W25QXX关键知识点整理(重要)5 H6 O/ b( P# }/ f7 x3 _8 t
驱动W25QXX前要先了解下这个芯片的相关信息。, q, a& ]# q' _$ {/ i8 u
; x. X" A, P Y! d: E- |) ?9 J
% o* c6 G& l/ B. n3 F+ J; y
- M! l2 C, t z W% B73.3.1 W25QXX基础信息
; l5 \0 \" x1 M: @( m W25Q64FV的容量是8MB(256Mbit)。+ M+ a b1 j3 n* _% j/ u& y
W25Q64FV支持标准SPI(单线SPI),用到引脚CLK、CS,DI和DO引脚。
, x2 ]5 s) G) |支持两线SPI,用到引脚CLK、CS、IO0、IO1 。( b. |* ^* }+ ? a" x0 Z% C3 u
# t! q. i$ ?. F/ F" d* G+ T7 x支持四线SPI,用到引脚CLK、CS、IO0、IO1,IO2、IO3。" i; x$ f8 l( e; Q
6 ?, j& q+ M, `" o' S(注:这里几线的意思是几个数据线)。
" m( A& _8 ?- w$ ]% u# Q) Y: ~/ P0 Y7 F
W25Q64FV支持的最高时钟是133MHz。
& s2 P7 B) J- X& y) j! H5 P! D0 { 每个扇区最少支持10万次擦写,可以保存20年数据。
& _2 u' A7 S& U) P, z3 C+ X 页大小是256字节,支持页编程,也就是一次编写256个字节,也可以一个一个编写。
3 Z# I9 n7 T+ |5 J- L9 w: X 支持4KB为单位的扇区擦除,也可以32KB或者64KB为单位的擦除。
1 |% r% U9 L: X/ q& e- r' A整体框图如下:
6 G$ {& O% J/ N! A ^. f
: y/ z: r1 I# ]. r# o/ {5 V# \# ?
- ~# a4 l0 d* @7 h9 x% N" ]W25Q64FV:
' g* R M. U4 Y& b$ B3 u" {4 ^* p3 Z3 d! O8 O" x+ \3 H% O
有128个Block,每个Block大小64KB。
5 K* U5 R: Q- ~6 }/ i 每个Block有16个Sector,每个Sector大小4KB。
: Z; u- @$ ^* _# R- T 每个Sector有16个Page,每个Page大小是256字节。
! B2 y$ r! t" m O( ^0 V5 k73.3.2 W25QXX命令
( f& T% j, V: `- ~' T; `+ ~0 u& e使用W25Q的接线方式不同,使用的命令也有所不同,使用的时候务必要注意,当前我们使用的标准SPI,即单线SPI,使用的命令如下:
% J$ @3 p# D# e. u/ L3 M
! C1 w/ {% W3 D( k6 \, y0 [8 x7 ` h. j: S% v
! h& s3 n! }8 z" o当前主要用到如下几个命令:
. L% H7 Y. F& y2 ~) f2 z7 G* d( M% k8 C
- #define CMD_EWRSR 0x50 /* 允许写状态寄存器的命令 */' n v" V/ z# s! X# A
- #define CMD_WRSR 0x01 /* 写状态寄存器命令 */4 @# S4 B& n5 c0 H
- #define CMD_WREN 0x06 /* 写使能命令 */
1 m7 N' ?# w, C. c0 N1 G* V - #define CMD_READ 0x03 /* 读数据区命令 */! j3 I' b ^- f/ D7 _
- #define CMD_RDSR 0x05 /* 读状态寄存器命令 */
& W1 l) {! s/ {$ B! u6 J$ F - #define CMD_RDID 0x9F /* 读器件ID命令 */
" `" M7 H# |. P - #define CMD_SE 0x20 /* 擦除扇区命令 */" R. O1 Y0 c5 p2 Y h
- #define CMD_BE 0xC7 /* 批量擦除命令 */
9 N7 b* N" q7 ~ - #define WIP_FLAG 0x01 /* 状态寄存器中的正在编程标志(WIP) */
复制代码 * k7 X; z/ G# B
73.3.3 W25QXX页编程和页回卷# a; Q8 F& L& C4 ]
SPI Flash仅支持页编程(页大小256字节),所有其它大批量数据的写入都是以页为单位。这里注意所说的页编程含义,页编程分为以下三步(伪代码):3 e0 Y. _. C( D( ?" T- r. |
8 C, k" p- A: u/ ]+ l- bsp_spiWrite1(0x02); ----------第1步发送页编程命令 : p/ Z' R! n% k( Y- q3 W
- bsp_spiWrite1((_uiWriteAddr & 0xFF0000) >> 16); ----------第2步发送地址
- Z* Y) i8 B8 | `' H. k - bsp_spiWrite1((_uiWriteAddr & 0xFF00) >> 8);
; X; U x5 q" P& Q - bsp_spiWrite1(_uiWriteAddr & 0xFF);
( d: R7 |' u- b. _, s B
5 f0 Q8 N7 f5 Q4 z# D0 X- for (i = 0; i < _usSize; i++)
% o V( _+ x: H# C - {
H- J8 M3 Z J4 Y2 }/ R - bsp_spiWrite1(*_pBuf++); ----------第3步写数据,此时就可以连续写入数据了,7 w- c& A3 H) Q5 v) }0 t" X
- 不需要再重新设置地址,地址会自增。这样可以大大加快写入速度。
9 W% }5 [8 G1 \. N' l9 }3 Q - }
复制代码 + ?" m" B2 w4 x
页编程的含义恰恰就体现在第3步了,如果用户设置的“起始地址+数据长度”所确定的地址范围超过了此起始地址所在的页,地址自增不会超过页范围,而是重新回到了此页的首地进行编写。这一点要特别的注意。如果用户不需要使用地址自增效果,那么直接指定地址进行编写即可。可以任意指定地址进行编写,编写前一定要进行擦除。
6 [* H, s) v+ w+ D4 U1 E
6 r; p% [8 g9 V+ ?( Q比如下面就是页内操作(使用前已经进行了扇区擦除,每次擦除最少擦除一个扇区4KB):
" Y; d! B' l( g( R# F! u, _$ s$ D, J* Z: \
- uint8_t tempbuf[10] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00};+ I* X8 u! g7 u$ h& o+ r
- uint8_t temp1 = 0x10, temp2 = 0x29, temp3 = 0x48;
复制代码
( {. w# k5 I/ X1 o* n 从250地址开始写入10个字节数据 PageWrite(tempbuf, 250, 10);(因为一旦写入超过地址255,就会从0地址开始重新写)。
, Z6 v. u# I% K7 k: C' z! e) { 向地址20写入1个字节数据:PageWrite(&temp1, 20, 1);8 G! ^; O" N) R- B
向地址30写入1个字节数据:PageWrite(&temp2, 30, 1);) o" w* W2 `+ d7 {
向地址510写入1个字节数据:PageWrite(&temp3, 510, 1) (这里已经是写到下一页了)
% V, f* i& s8 i; L1 W1 `下面是将从0地址到511地址读取出来的512个字节数据,一行32字节。
! s( T6 B4 g5 U* ], D2 |) x" t M# [2 T$ e1 s+ H
. ~7 d& `# q0 x3 Y/ Q/ }3 W
+ ?" t" R4 n2 h
2 n$ B7 m4 L6 r5 M) _4 R& M7 ?: z73.3.4 W25QXX扇区擦除) e5 }8 D6 y+ @7 q# p6 ^7 d
SPI Flash的擦除支持扇区擦除(4KB),块擦除(32KB或者64KB)以及整个芯片擦除。对于扇区擦除和块擦除,使用的时候要注意一点,一般情况下,只需用户给出扇区或者块的首地址即可。8 x7 C( U. \* V6 z% S
! M# H* |" X$ ]% `9 d( c/ r+ B
如果给的不是扇区或者块的首地址也没有关系的,只要此地址是在扇区或者块的范围内,此扇区或者块也可以被正确擦除。不过建议使用时给首地址,方便管理。
1 U/ _5 |2 e0 y- q) d( \8 p
; T! n. r8 h4 Z) d" g/ H* ^73.3.5 W25QXX规格参数) d& ]3 Q8 D. I8 m) g
这里我们主要了解擦写耗时和支持的时钟速度,下面是擦写时间参数:
. j2 _( H+ a* T T* L1 q3 T, _# Q( D5 y1 L# u: T) B
: K, f0 [. o7 }! l( l8 \* l
: o# z$ G- Q0 L7 r# ]0 Y 页编程时间:典型值0.4ms,最大值3ms。9 S* }! u* `: M9 h! b9 {
扇区擦除时间(4KB):典型值45ms,最大值400ms。0 X$ a3 U- q, ^, ^- t- l
块擦除时间(32KB):典型值120ms,最大值1600ms。
' Y3 X- Y! U4 I/ {/ J6 r' } 块擦除时间(64KB):典型值150ms,最大值2000ms。
6 _5 J8 h8 @1 O4 s 整个芯片擦除时间:典型值20s,最大值100s。
/ O1 I4 ]( D f- V6 e+ {. ~; ^% [2 N+ N3 @8 `
/ c! Q4 L. Z" ? k, u支持的速度参数如下:
6 b6 t; e) b9 r& k8 C( b8 F3 O4 i1 X# f, q. |
5 W: r/ t( V. L' g i( {& e
' Q9 a& B2 E2 @; h/ i' q可以看到最高支持的读时钟(使用命令03H)速度是50MHz,其它命令速度可以做到133MHz。) }3 A1 F. g: j# q* _
$ Z8 h8 d: b( g$ g; F73.4 W25QXX驱动设计8 \" C/ K2 p+ A: [0 w2 W
W25QXX的程序驱动框架设计如下:
: K) K, H( }( _' g8 @0 M0 c5 W# ~1 s
1 p* S) m& \$ w+ r' ^3 P+ _
D+ E, r3 H: v6 n2 @有了这个框图,程序设计就比较好理解了。
0 P( N* v3 G$ r+ \; u+ P7 N- d6 _( T2 h! b' C- O
73.4.1 第1步:SPI总线配置) S7 h% X) t* n d Z
spi总线配置通过如下两个函数实现:
& W! z( {, M+ T5 Z X( U" b0 ~6 e) s& o% X. E" b3 z" C- G
- /*2 [& H% a" d2 \8 v3 Y
- *********************************************************************************************************. @, d8 v( v9 o2 R" o
- * 函 数 名: bsp_InitSPIBus. K& [1 l) K9 N8 y! p" X* }
- * 功能说明: 配置SPI总线。
5 b' N, F' a' j9 C9 ^# t - * 形 参: 无3 m) N. c$ `; L2 R& S+ \" q8 t4 b
- * 返 回 值: 无3 N: w4 j+ K$ i2 p
- *********************************************************************************************************
5 N# p# |8 w3 L( G/ h - */
3 J2 T: f4 o1 u; w8 H& q - void bsp_InitSPIBus(void)
3 y7 ?' l' @( S. h y - { ( G; c6 V/ X$ N7 |' u1 b, ^
- g_spi_busy = 0;
1 f# I% P! s3 Q$ f: |
: }+ V7 [! Y3 O% O3 L6 ^/ x- bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_8, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
7 {: q+ n; G% j* x1 y - }, L5 h) ]) n( x% i# O0 M& r0 Y6 n9 G( F
4 J, w1 o- b1 K! F5 _4 T0 S- /*2 t* b; e7 x% R! l3 ^1 \/ o; r
- *********************************************************************************************************
; [( ]3 j6 F6 s. A$ k5 H - * 函 数 名: bsp_InitSPIParam( W: k1 _& f: q8 G2 X0 M7 x" L
- * 功能说明: 配置SPI总线参数,时钟分频,时钟相位和时钟极性。
: S# U. V ^: ^ - * 形 参: _BaudRatePrescaler SPI总线时钟分频设置,支持的参数如下:
6 P5 C" x0 i3 H - * SPI_BAUDRATEPRESCALER_2 2分频
1 v& I( M# k1 y$ g - * SPI_BAUDRATEPRESCALER_4 4分频
& ]& f1 t8 ]4 \4 B - * SPI_BAUDRATEPRESCALER_8 8分频/ @+ h( p) K2 R" a
- * SPI_BAUDRATEPRESCALER_16 16分频& f3 {) [' _# m4 E8 s
- * SPI_BAUDRATEPRESCALER_32 32分频* Q: @* L h( Q) _+ a# U, J2 n
- * SPI_BAUDRATEPRESCALER_64 64分频
- F/ w6 ^, `: o/ W" i6 R/ F( H% W - * SPI_BAUDRATEPRESCALER_128 128分频 v1 C; A$ h. a0 u* y
- * SPI_BAUDRATEPRESCALER_256 256分频
" Q; Q8 |, h% y6 F: V5 t - * ; E: D' ~3 A9 S' ~ e
- * _CLKPhase 时钟相位,支持的参数如下:
9 ~0 ~' Q2 H& c+ G3 w1 Z - * SPI_PHASE_1EDGE SCK引脚的第1个边沿捕获传输的第1个数据
( q: ^/ n! I: v) Y3 ?# H - * SPI_PHASE_2EDGE SCK引脚的第2个边沿捕获传输的第1个数据. K2 l& _" g, C1 I- g
- * . o5 |" ^' n" H: @+ C6 i, D
- * _CLKPolarity 时钟极性,支持的参数如下:8 b' G5 N6 [( [2 Z* j. J! x& q! j
- * SPI_POLARITY_LOW SCK引脚在空闲状态处于低电平
2 S2 |. I. h3 D9 d% P - * SPI_POLARITY_HIGH SCK引脚在空闲状态处于高电平
7 i# u. n/ r) q* O% s5 N- P - *: n. \) O2 U' n0 k
- * 返 回 值: 无
4 W9 b$ p' H# g+ U: F; t1 ~- Q7 j - *********************************************************************************************************2 D6 k7 h* V. U/ F& r; X
- */9 n5 a( w9 d) K- J, H" Q
- void bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
z% M7 d8 Q A - {
: E9 r: O& W; [$ b - /* 提高执行效率,只有在SPI硬件参数发生变化时,才执行HAL_Init */
0 ?* B$ r9 j- W: h/ k, E0 D - if (s_BaudRatePrescaler == _BaudRatePrescaler && s_CLKPhase == _CLKPhase && s_CLKPolarity == _CLKPolarity)
: C/ |' p. r7 T+ ~ - {
5 `# s! e* a5 T' z; F0 \" ~ - return;# S6 L& K0 C0 S6 P4 \
- }
/ l/ F& [! Q) | g7 ? - 6 m% a4 T8 U# \( N+ O
- s_BaudRatePrescaler = _BaudRatePrescaler;
6 o: D. P* b8 o - s_CLKPhase = _CLKPhase;, W) v9 ^6 U1 H2 y# V3 q
- s_CLKPolarity = _CLKPolarity;
m8 T& V8 k/ F$ q( {
" }; A( X* W6 D/ W- - _2 Q+ B6 \& `, P5 M- c
- /* 设置SPI参数 */, k* `, B; s6 ^$ d/ E
- hspi.Instance = SPIx; /* 例化SPI */. A8 \" v9 _& k- Y/ b8 u
- hspi.Init.BaudRatePrescaler = _BaudRatePrescaler; /* 设置波特率 */
$ m% S+ B4 j% t) t; g6 G( }/ B - hspi.Init.Direction = SPI_DIRECTION_2LINES; /* 全双工 */
. P6 e0 E, A% L( F# k9 O - hspi.Init.CLKPhase = _CLKPhase; /* 配置时钟相位 */
# h1 _5 ]8 O5 ^ - hspi.Init.CLKPolarity = _CLKPolarity; /* 配置时钟极性 */
; ` H/ U( ^! M; v% h" h - hspi.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置数据宽度 */
. d/ A# n; i" C I$ s- e - hspi.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 数据传输先传高位 */# N9 G( Z- t" A8 V' P+ w7 K
- hspi.Init.TIMode = SPI_TIMODE_DISABLE; /* 禁止TI模式 */
5 K& Z! [) H, j. f - hspi.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 禁止CRC */. o" s2 Q, J4 O0 n7 ~# i* e
- hspi.Init.CRCPolynomial = 7; /* 禁止CRC后,此位无效 */2 O/ ], {- j& M* }( y
- hspi.Init.CRCLength = SPI_CRC_LENGTH_8BIT; /* 禁止CRC后,此位无效 */' d6 l- u: n: r
- hspi.Init.NSS = SPI_NSS_SOFT; /* 使用软件方式管理片选引脚 */' X' t; z% W' v' k, [8 Y8 g
- hspi.Init.FifoThreshold = SPI_FIFO_THRESHOLD_01DATA; /* 设置FIFO大小是一个数据项 */
; }: c7 @; I( v( r$ d; V* B @* W! } - hspi.Init.NSSPMode = SPI_NSS_PULSE_DISABLE; /* 禁止脉冲输出 */
* p ^4 r1 ~! ^6 }- Y; V - hspi.Init.MasterKeepIOState = SPI_MASTER_KEEP_IO_STATE_ENABLE; /* 禁止SPI后,SPI相关引脚保持当前状态 */
, A7 X/ q$ U! q7 c, Q; B9 Y% b# _3 R - hspi.Init.Mode = SPI_MODE_MASTER; /* SPI工作在主控模式 */
/ O6 z* Q* c4 D1 e# _% L# c
+ C1 C! f- z( G1 S1 G2 `( e- /* 复位配置 */
# @ B: ^$ L$ i7 I - if (HAL_SPI_DeInit(&hspi) != HAL_OK)
5 O; _) a8 u8 R" K# B/ u, ~) |8 _! V - {( I T% x0 D6 L9 Y2 M3 t
- Error_Handler(__FILE__, __LINE__);( D8 q: J. }. r$ W& }5 R) P0 R
- } 6 o. G8 U S9 v; Q' ~" i
" C" O1 v7 M/ z' x) ?. G- /* 初始化配置 */) t% X+ L# l9 J% X; O( T* I( e
- if (HAL_SPI_Init(&hspi) != HAL_OK)
/ S: H# C+ l$ H% K: ]- N3 L - {. I o- G: b+ `' o
- Error_Handler(__FILE__, __LINE__);
, n+ \' V! t8 O: S- Y' |% r$ P - } * G) V6 L3 H" ?
- }
复制代码 1 H4 D2 ^6 N! Z* ^
关于这两个函数有以下两点要做个说明:. w3 ?. M c! l7 O
$ c& ]) d5 `* o& D4 z& M
函数bsp_InitSPIBus里面的配置是个初始设置。实际驱动芯片时,会通过函数bsp_InitSPIParam做再配置。
. x9 {9 w, A) v$ r 函数bsp_InitSPIParam提供了时钟分频,时钟相位和时钟极性配置。驱动不同外设芯片时,基本上调整这三个参数就够。当SPI接口上接了多个不同类型的芯片时,通过此函数可以方便的切换配置。) m/ }" v" b" q/ w& C
73.4.2 第2步:SPI总线的查询,中断和DMA方式设置! M8 V+ r- m/ ]7 n e5 K
SPI驱动的查询,中断和DMA方式主要通过函数bsp_spiTransfer实现数据传输:
8 u+ Z; m& h/ [7 J9 S5 R B* y; t- V, [( o; \; |
- /*: p1 I) h8 Y$ _$ l0 W
- *********************************************************************************************************) L8 ^$ Q# }2 [+ `
- * 选择DMA,中断或者查询方式/ O. j' t3 b1 _3 n u+ g
- *********************************************************************************************************2 Z% F3 L" _: Y0 B
- */, \2 @/ D0 E1 G0 F: f
- //#define USE_SPI_DMA /* DMA方式 */
/ q: `6 A- A6 @% C2 h/ d& x - //#define USE_SPI_INT /* 中断方式 */+ x' Y! r6 P6 w& ^# G& K
- #define USE_SPI_POLL /* 查询方式 */
' s7 W, f* t* L
* F: R0 C( \% D4 L- D# u- /* 查询模式 */9 o" D3 A; Q3 @: }# q/ t
- #if defined (USE_SPI_POLL)
$ \- u7 s; f( K, K0 L - ; @+ q, D8 I3 E+ C0 Y
- uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
. W$ R' o; i( P) o9 C) Z7 y4 o/ t p8 R( F - uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];! j( `% m* \; j: q1 }7 x* y
- # n( P" M( k/ Y c
- /* 中断模式 */+ D5 Z4 F! r( m* `5 V/ D
- #elif defined (USE_SPI_INT)1 d- v9 D0 b, k% B7 ~* M: v- x
. P, e8 V0 C$ h& I1 M- uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
6 M- P; ] W$ J - uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];9 _& m, U. {6 e n) Z: N1 x/ T+ ?6 \
- h# A- o% n; b# W
- /* DMA模式使用的SRAM4 */) ^2 g- A" {6 A$ H
- #elif defined (USE_SPI_DMA): }$ N6 K0 h: _" u; A& ]) x4 q
- #if defined ( __CC_ARM ) /* IAR *******/; k \: d9 w" E- L6 Q% u" f1 S. P
- __attribute__((section (".RAM_D3"))) uint8_t g_spiTxBuf[SPI_BUFFER_SIZE];
5 R3 }" Y. i0 F/ v& ]/ ~. Q - __attribute__((section (".RAM_D3"))) uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
# ~7 `5 {* q. f$ f' `! h - #elif defined (__ICCARM__) /* MDK ********/
% P# o9 e; X4 c6 k - #pragma location = ".RAM_D3"
2 ?( W a2 l( ]6 r% @$ J# B - uint8_t g_spiTxBuf[SPI_BUFFER_SIZE]; ! _ ~. g5 A% t( }8 z: x5 a0 ?
- #pragma location = ".RAM_D3": ]6 J* p- M$ l( Z3 g
- uint8_t g_spiRxBuf[SPI_BUFFER_SIZE];
4 ?7 i4 U' p2 { ]6 ]+ y - #endif
2 l- z2 ]/ e5 _! h/ K( f - #endif k4 o6 {3 u% X& L7 K& n
3 j9 h' j+ h9 |; ?- /*( ?. r5 G9 V" w3 s5 ~0 g; `# J
- *********************************************************************************************************
* y3 n! B. s5 V7 o - * 函 数 名: bsp_spiTransfer
5 ]8 @- d9 o; j8 x - * 功能说明: 启动数据传输" X5 `% `, ^& J3 ?7 e* C3 M
- * 形 参: 无% s! H! q1 x. x. t, O) t
- * 返 回 值: 无+ p, S( O M4 P: M- t
- *********************************************************************************************************& Z8 v: \: d3 h& h* k
- */
% B1 R% u+ J( _& u - void bsp_spiTransfer(void)
9 ]" ^: `/ [- N6 n0 b0 u4 A - {
8 S ]5 ^8 ]/ V5 B" t. t" l- E. g - if (g_spiLen > SPI_BUFFER_SIZE)
6 w: f! K1 Y) k% R$ [- [ - {8 ]# B1 ~/ B* A8 m
- return;& V& k2 W. e, [* G
- }7 J/ i: d0 @6 O2 p5 w# o F4 U1 M
/ u( M+ R" f: q: l/ z. T' a- /* DMA方式传输 */7 [3 ~1 O1 A+ x5 J, b+ p
- #ifdef USE_SPI_DMA
3 G( ?, l! O4 ^ - wTransferState = TRANSFER_WAIT;/ |3 C/ I7 E# D( ] v7 a( v
% _: P/ h* x: m6 y3 O0 w- if(HAL_SPI_TransmitReceive_DMA(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) ; r- c. g, B3 R V/ X, [4 `4 |9 ^
- {
! b9 I7 P) s# O3 P1 ] - Error_Handler(__FILE__, __LINE__);
6 R: z( H8 w% b3 l+ B$ L4 }4 F! o+ g - }
# R$ j2 P7 i' I7 z" N
" X* A4 A0 n9 H- ^0 U; f- while (wTransferState == TRANSFER_WAIT)
. C4 K$ ?+ @! [ - {
+ \+ B0 Y0 c5 j" r. p4 b - ;
/ Q$ Z4 S$ z0 J+ O& ^$ t# S4 T - }
/ Z8 m4 h& E) z - #endif
# D0 ]9 R; p- ]) q1 d8 d - - L/ Y( J. W# i" a) L' X/ g, ^
- /* 中断方式传输 */ ; [3 j q; k& o0 L, J; D
- #ifdef USE_SPI_INT
3 A$ {( x" O0 y+ _1 O3 ~. G - wTransferState = TRANSFER_WAIT;; V) B9 g9 D9 y7 r% H
- ' g1 [5 {8 {5 ~
- if(HAL_SPI_TransmitReceive_IT(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen) != HAL_OK) 9 { ?( p" L) N. G
- {
4 j. i& Q4 m7 p% W: p6 H - Error_Handler(__FILE__, __LINE__);
0 a; z0 R/ W- G" k( T: _2 r0 r - }
$ h8 C4 f4 I3 C4 Y. N - 1 Y* C1 C! {$ P' m) E; a7 G* X( ?
- while (wTransferState == TRANSFER_WAIT): g$ r* I6 d1 @) |0 T' P0 G% H8 `
- {; S) t+ d/ m9 ^5 W
- ;( {/ r6 q5 d' G( g* l
- }
+ j" m' C' \9 b+ L. ]( f+ v9 { - #endif' U& G: r# `5 v% E8 @/ Q4 K
1 o/ l9 Q; D7 R0 y9 ~; _( Q5 k" M- /* 查询方式传输 */
) n* j( E/ O, q. ^ - #ifdef USE_SPI_POLL
' K. @& b* X3 p3 q$ Z" } }, E - if(HAL_SPI_TransmitReceive(&hspi, (uint8_t*)g_spiTxBuf, (uint8_t *)g_spiRxBuf, g_spiLen, 1000000) != HAL_OK) * } a8 y$ b, A2 X
- {) N6 m) B' l2 I; `" C1 v' @
- Error_Handler(__FILE__, __LINE__);
# X$ W& d& d" P/ y( C P - }
8 A3 W9 b) x/ R - #endif
: e" G! z8 p4 H+ D* i" w: L o - }% g B: P' R4 t: X! S2 h
复制代码 - G' x" ?1 F1 b2 e3 X5 o, f
通过开头宏定义可以方便的切换中断,查询和DMA方式。其中查询和中断方式比较好理解,而DMA方式要特别注意两点:
# u) x6 U$ {2 Q9 j" T
- B) J6 v0 ?5 Q# S% n( ? 通过本手册第26章的内存块超方便使用方式,将DMA缓冲定义到SRAM4上。因为本工程是用的DTCM做的主RAM空间,这个空间无法使用通用DMA1和DMA2。2 k. s- K( s0 Y' d1 B; V
由于程序里面开启了数据Cache,会造成DMA和CPU访问SRAM4数据不一致的问题,特此将SRAM4空间关闭Cache。
. G0 M/ c8 Q" R& i. \ N+ V- /* 配置SRAM4的MPU属性为Non-cacheable */9 y* a7 K, @; e# l+ e! L4 `
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
; Q* ~1 r9 D3 }+ t" j( s - MPU_InitStruct.BaseAddress = 0x38000000;
/ J2 k) M9 _8 c. S* K0 V6 i9 c- d+ ` - MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;) z3 f% a/ a" ~4 X% U3 P
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;- m2 I+ A" F$ R5 k) [5 M) O$ ^
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;; M: ^# \$ t4 }# ^
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
1 S& y. A6 j& t, V0 W- h - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;3 u1 m: E# Q! D7 M8 N$ l
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;
& C J) i x# J6 l. | - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
, o, Y; B, }8 z1 |; E# z! E! } - MPU_InitStruct.SubRegionDisable = 0x00;: F6 k1 k, j1 M
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
: ^9 m6 y2 I; H7 b - }( {! G4 q5 g1 l J( [9 k
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
4 a/ x0 z5 I* c9 ~) C73.4.3 第3步:W25QXX的时钟极性和时钟相位配置
: H' d6 Y4 b" D8 |# Y首先回忆下STM32H7支持的4种时序配置。
2 S8 t, H/ Q( S l
$ O7 B* o9 A9 y 当CPOL = 1, CPHA = 1时
p+ Q( m7 p: x! h, F, H, \; V2 x, T5 m* z. D) S+ G
SCK引脚在空闲状态处于低电平,SCK引脚的第2个边沿捕获传输的第1个数据。5 C. n8 B+ t9 S) I7 n
9 Y8 N% ~7 a8 S. d9 b" D 当CPOL = 0, CPHA = 1时 Z' X$ v9 N r* J, w* p3 X
) C' H* w0 s3 {, R+ C4 _+ K, u! lSCK引脚在空闲状态处于高电平,SCK引脚的第2个边沿捕获传输的第1个数据。
$ M( X' h( B7 @2 j5 V0 ^0 w2 I% ^0 z' ?5 ]
当CPOL = 1, CPHA = 0时1 |6 p% t2 G* Z+ |' \
, f% V) p# T0 [6 p1 @& [+ TSCK引脚在空闲状态处于低电平,SCK引脚的第1个边沿捕获传输的第1个数据。' F; Y- Q( c5 D4 ~2 N+ [6 ^* q
- C2 m* B4 j2 M+ N! R
当CPOL = 1, CPHA = 0时# a; U: R7 f2 `4 U
# {8 g, F! v- Y+ E4 _1 a2 NSCK引脚在空闲状态处于高电平,SCK引脚的第1个边沿捕获传输的第1个数据。
0 e; j$ O# ?- k' a- P" \8 o5 r# C2 \
& b( x0 m/ n* H3 e0 r( `
7 i; l. a; _3 K' s9 Z有了H7支持的时序配置,再来看下W25Q的时序图:
, @* N: Y0 @7 x! r/ G5 @: q: R& }# {) \% U* C& G; S
Mode0 : 空闲状态的sck是低电平。7 k4 s& w* ?8 [- x) D( U
5 t7 l& E3 b! G, K
Mode1 : 空闲状态的sck是高电平。
. Y$ D% _4 E; f8 D3 ~( v2 W
5 v" @% {2 A1 E: P* r- X+ h3 I1 J+ L7 b1 Q( A3 p' i$ l) o7 i
0 q0 C F# Y+ S" h首先W25Q是上升沿做数据采集,所以STM32H7的可选的配置就是:# v+ |1 R% h" l& a) k
3 `& _$ {) c, \2 N- oCHOL = 1, CPHA = 1
, Q( B$ ^. L% G- B' h; _. J
0 x9 S) \2 {) s9 Q) MCHOL = 0, CPHA = 0
4 G) p8 i1 G3 C/ d e! o* f
+ t: [9 X3 F. g; I+ k对于这两种情况,具体选择哪种,继续往下看。W25Q有两种SCK模式,分别是Mode0和Mode3,也就是空闲状态下,SCK既可以是高电平也可以是低电平。这样的话,这两种情况都可以使用,经过实际测试,STM32H7使用这两个配置均可以配置驱动W25Q。7 N6 M* g" y& T9 y
# J9 i5 ?9 ]; W0 s% x3 Z73.4.4 第4步:单SPI接口管理多个SPI设备的切换机制3 j U$ q: S* \4 t$ O+ a- } x" P
单SPI接口管理多个SPI设备最麻烦的地方是不同设备的时钟分配,时钟极性和时钟相位并不相同。对此的解决解决办法是在片选阶段配置切换,比如SPI Flash的片选:, x/ w8 a) j3 V9 m( M
' X+ s) E% A$ M9 S$ G& a& Q1 E- /*
* [/ j4 \' I7 u1 K) j - *********************************************************************************************************. {1 h0 e) R- |3 m$ E8 l
- * 函 数 名: sf_SetCS
& [- u7 Z! l& U) W( @$ D - * 功能说明: 串行FALSH片选控制函数
0 X# a9 Z; T5 t, v - * 形 参: 无/ {) k+ t! M2 p. o) J
- * 返 回 值: 无# q6 d: x6 k0 k j& ^, B6 ]
- *********************************************************************************************************
) ~& x7 x# ^& J M - */7 F- K9 j: t: j5 H
- void sf_SetCS(uint8_t _Level)3 R5 R3 f E0 e) W! y5 r
- {, S* n* c6 `& n
- if (_Level == 0)
( k9 V1 u: V9 q+ y8 r - {
0 V8 x: X* Z0 Z3 g* V - bsp_SpiBusEnter();
4 x% g# a( A, c6 L% v1 ~ - bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW); 2 n) m" g3 `; m( K2 ?! ]
- SF_CS_0();% u5 }1 T( {& C8 G: _, j
- }$ Y0 e7 ?+ \; i V4 t, r
- else
, G8 {5 x8 O( o( I - { % R; W$ O' f, {1 R0 _
- SF_CS_1(); $ G% T$ _* q* h
- bsp_SpiBusExit(); 6 i3 x- o- B- r$ W4 r$ `
- }
N7 U( s3 c: k( g& k$ P, a; K - }
F' }# U; s$ T" H
复制代码
5 K. b, g6 g# _+ A, c7 _! b7 f% Y1 u& O9 k; f- T) M
通过这种方式就有效的解决了单SPI接口管理多设备的问题。因为给每个设备都配了一个独立的片选引脚,这样就可以为每个设备都配置这么一个片选配置。
4 H2 |) ~, ^& t" @5 a& G1 O4 b% G Q, p* L1 Z$ k$ y2 v
但是频繁配置也比较繁琐,所以函数bsp_InitSPIParam里面做了特别处理。当前配置与之前配置相同的情况下无需重复配置。" D$ H: K/ g6 c+ L
( b c8 M4 j2 @" o+ R
73.4.5 第5步:W25QXX的读取实现
4 L3 o7 B2 [( o& V: {0 e# b% A/ Q" YW25QXX的读取功能比较好实现,发送03H命令后,设置任意地址都可以读取数据,只要不超过芯片容量即可。6 D# B7 u. |4 }3 j* S9 Y! l
. k" f' v% D- K& _! }% m8 b \: I- /** A( h$ E2 x# ^5 O" R
- *********************************************************************************************************# P. l+ D1 W) V' L$ w4 |. i# \3 v
- * 函 数 名: sf_ReadBuffer) `8 D1 p& {* f( o6 b
- * 功能说明: 连续读取若干字节,字节个数不能超出芯片容量。* m, c F4 W8 j4 [7 ^
- * 形 参: _pBuf : 数据源缓冲区;
4 E& l* m; d" I0 H% L0 M - * _uiReadAddr :首地址
6 E; e5 J7 F) m1 L - * _usSize :数据个数, 不能超出芯片总容量3 v" p' n# g* G2 l6 m7 _1 l
- * 返 回 值: 无+ M- C$ p% ?2 k( q9 e; a
- *********************************************************************************************************- a1 t& i" q% h
- */
8 _' i* D" k B - void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize)
, n- E: e5 y' H, ^& D2 d# z: t - {
% j' D9 m* r# d( b C* H - uint16_t rem;* M* u; w& Q) j2 x% u0 R& K. r5 ~5 {
- uint16_t i;1 G0 v% u1 D& `7 ?
- + t* }* k6 E( V% Y! B" N% y
- /* 如果读取的数据长度为0或者超出串行Flash地址空间,则直接返回 */! J' Y/ v# m9 E7 k% X( A
- if ((_uiSize == 0) ||(_uiReadAddr + _uiSize) > g_tSF.TotalSize): `; {4 N" E$ v( k4 ]
- {( ^/ \% P' l9 }5 L
- return;
7 \' V* ]+ s& u# S9 _9 @# @ - }% b8 V0 A) E7 W0 n+ m
, C( S9 q% y$ j& o3 i- /* 擦除扇区操作 */
. [* W* {( O5 B% ^; Q - sf_SetCS(0); /* 使能片选 */& H+ e# ~( U M, d% G/ x
- g_spiLen = 0;3 `) O, T2 j! S
- g_spiTxBuf[g_spiLen++] = (CMD_READ); /* 发送读命令 */2 Q4 t+ y8 K3 G
- g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF0000) >> 16); /* 发送扇区地址的高8bit */
w1 `8 V7 i6 H2 j - g_spiTxBuf[g_spiLen++] = ((_uiReadAddr & 0xFF00) >> 8); /* 发送扇区地址中间8bit */+ E W% V3 v9 A
- g_spiTxBuf[g_spiLen++] = (_uiReadAddr & 0xFF); /* 发送扇区地址低8bit */
% f" q2 }2 p' L; g/ Q Q- Y - bsp_spiTransfer();
5 k# N4 S% {5 i4 S; n+ m) l% |6 J
: X$ H1 p8 B% q1 F/ F- /* 开始读数据,因为底层DMA缓冲区有限,必须分包读 */' [- y: y8 R* b G& N/ \
- for (i = 0; i < _uiSize / SPI_BUFFER_SIZE; i++); d1 ^: J+ D. Y: ]1 z
- {: R$ D7 J5 H- g4 M5 o
- g_spiLen = SPI_BUFFER_SIZE;8 U; u8 Z( i V
- bsp_spiTransfer();
. t3 D. ?- R# d! }: S& d' i1 i) J - ' @3 F3 t4 h2 w9 C5 X1 p
- memcpy(_pBuf, g_spiRxBuf, SPI_BUFFER_SIZE);
6 D: B4 r3 x; v) }9 e - _pBuf += SPI_BUFFER_SIZE;
- S3 C4 P1 C4 w) O1 ^1 E9 O" Z - }
6 j. c4 @- Z& ]2 n
/ w6 e$ |- D/ c+ }8 ^- rem = _uiSize % SPI_BUFFER_SIZE; /* 剩余字节 */
1 Q. c) k( T4 q+ c& F0 u+ i/ s4 p - if (rem > 0)
+ u& f& Z& m( D" }0 m1 m+ K - {
1 X+ _( u' C) g7 t1 Y - g_spiLen = rem;
2 q0 z/ r8 O+ }8 G - bsp_spiTransfer();- W& V! x" E0 l! Y# N( p0 T
- ! R" i% ~4 c) W2 h: ]) B# e( a3 T
- memcpy(_pBuf, g_spiRxBuf, rem);
0 b8 s8 W. U) m7 K+ B4 c9 l u1 g - }
3 m1 W" t T& v6 n" O - ( B. \0 F3 t7 h8 T3 ]# ^
- sf_SetCS(1); /* 禁能片选 */
) k6 d" w- ~( O/ x - }
复制代码
4 O6 n! x7 ?; f5 U# _
1 D$ S' W% D' t6 P这个函数对DMA传输做了特别处理,方便分包进行。( F( W4 ?* M o" K, g; G7 V
6 W2 p5 [( @( i- w! U ^
73.4.6 第6步:W25QXX的扇区擦除实现9 }6 p [* h" B1 q4 `( x/ e2 G
扇区擦除的实现也比较简单,发送“扇区擦除命令+扇区地址”即可完成相应扇区的擦除。擦除的扇区大小是4KB。
- ~! x/ G# ]" o* ^ c
7 ~" U% j! H! ?- /*% L" o; C! }9 w3 Z5 a- z
- ********************************************************************************************************* o2 K4 a1 ]7 L7 w* s6 p) X
- * 函 数 名: sf_EraseSector
2 b" r: P) S. h/ O c - * 功能说明: 擦除指定的扇区
, }0 X" r, R) ^9 Y( s4 ` - * 形 参: _uiSectorAddr : 扇区地址2 E7 Y* j& j, {3 ~' W5 ~" G
- * 返 回 值: 无, ?+ i! J8 u2 A H" c8 f
- *********************************************************************************************************9 C' @4 \9 ?& e8 R% X- e3 Q7 Z5 q
- */! {$ d `" p6 d$ |, p6 s; N' z
- void sf_EraseSector(uint32_t _uiSectorAddr)9 P' c2 `* ?1 a
- {
4 y7 c7 |) x4 B( l7 u - sf_WriteEnable(); /* 发送写使能命令 */
* M" u9 [# _+ G& a- Q6 x
" y1 m$ \) a8 @) k8 g- /* 擦除扇区操作 */1 G9 F& E$ T: \( E% z
- sf_SetCS(0); /* 使能片选 */
% Y, b* ?" n, a" `, r - g_spiLen = 0;
6 X% i" k9 i% M" I% a' W - g_spiTxBuf[g_spiLen++] = CMD_SE; /* 发送擦除命令 */
4 u I" `2 n( L( W( Q, Y& [ - g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF0000) >> 16); /* 发送扇区地址的高8bit */8 f. ~# i* I k* r) H) H6 {
- g_spiTxBuf[g_spiLen++] = ((_uiSectorAddr & 0xFF00) >> 8); /* 发送扇区地址中间8bit */+ [4 S1 G1 ~4 v7 ?, F- h
- g_spiTxBuf[g_spiLen++] = (_uiSectorAddr & 0xFF); /* 发送扇区地址低8bit */ 8 y. Z9 D- [' s# _+ o M
- bsp_spiTransfer();) M: k0 M \$ Y
- sf_SetCS(1); /* 禁能片选 */
6 A* w! M! n+ f: z( z( W, h( J) j
- p: W1 L) S, {* E( e, z8 @! A- sf_WaitForWriteEnd(); /* 等待串行Flash内部写操作完成 */
' D) Q. e, m c- t - }5 N, O. j; N4 P' k7 k$ ^) F6 s
复制代码 8 t( @5 V0 P4 ?1 z' G) x: O
# D. \9 N$ n) J
整个芯片的擦除更省事些,仅发送整个芯片擦除命令即可:2 o1 E+ Z: H7 ], M5 @ J
- t) j' f7 P7 X" y
- /*
% ~( h" D) j7 J) Q - *********************************************************************************************************
* l; h; {1 _% J: i1 K8 w, s( M, |4 C - * 函 数 名: sf_EraseChip( {. x G- d! n7 }
- * 功能说明: 擦除整个芯片
# _* O" k/ e6 S - * 形 参: 无) j1 ~# b& H; `( ^7 v
- * 返 回 值: 无; T) ]' ~$ M3 N7 [0 u
- *********************************************************************************************************
u6 P& u* Z& a1 J/ D- r+ r, h - */- x8 x( l2 A5 B5 l
- void sf_EraseChip(void)
) d- l: B5 _8 j9 Q5 B2 w - {
( ^3 R: Z* K/ z6 U. B - sf_WriteEnable(); /* 发送写使能命令 */
/ Y5 l5 ?4 W; @3 V# N! F8 ?& U
. ?+ q3 r% e0 n/ y0 R- /* 擦除扇区操作 */
. Q7 E) o9 P: Q - sf_SetCS(0); /* 使能片选 */
: d, Q. C8 n, c2 H7 g* Z - g_spiLen = 0;- C" H0 Q2 i1 b- S- d$ c5 j3 s) ^
- g_spiTxBuf[g_spiLen++] = CMD_BE; /* 发送整片擦除命令 */
" a I$ @0 y4 v+ ~3 \ - bsp_spiTransfer();) d. g* v+ U' ~: z, T- ^' Z4 s
- sf_SetCS(1); /* 禁能片选 */; r4 S! n2 p2 @+ F- |
- . @" V9 `; R9 p; J# z. O! _/ t
- sf_WaitForWriteEnd(); /* 等待串行Flash内部写操作完成 */
0 C. C# E/ |2 h7 i - }3 T* k2 N; P: @
复制代码 % T( C: Y; U2 c8 j' H' e+ P6 Z
73.4.7 第7步:W25QXX的编程实现
* k2 Z1 H6 o9 S" f/ DW25QXX的编程实现略复杂,因为做了自动擦除支持,大家可以在任意地址,写任意大小的数据,只要不超过芯片容量即可。我们这里就不做展开讨论了,大家有兴趣可以研究下:
. ^8 L: {; h M
! G( r! s0 @5 E' g8 h) Q- /*
( U [/ y& v7 ?* Y" Q' @- Q6 I - *********************************************************************************************************
; ~6 Q7 v4 f/ N9 Y2 H4 `6 }2 l: [ - * 函 数 名: sf_WriteBuffer
& f8 C1 @. o# H: w; q, n - * 功能说明: 写1个扇区并校验,如果不正确则再重写两次,本函数自动完成擦除操作。
( L" ^4 _0 A" i7 c3 D7 w& G - * 形 参: _pBuf : 数据源缓冲区;2 }' h6 @" ^+ U) d0 U
- * _uiWrAddr :目标区域首地址! a# @4 s8 L2 P8 V# y: a
- * _usSize :数据个数,任意大小,但不能超过芯片容量。6 i7 {( p" d0 ~0 E& f. {( f) x1 |
- * 返 回 值: 1 : 成功, 0 : 失败 a- r6 a) \9 {, I7 F
- *********************************************************************************************************
; @/ _' c9 A# g6 V - */
6 F1 N# \4 ]4 R7 o) D - uint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)
- t- N* L2 p9 M; D' R - {' t& ^% P4 u# w
- uint32_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
4 _) i/ @- n4 S6 }
. ]; M8 e, Z s" z+ c% s+ `- Addr = _uiWriteAddr % g_tSF.SectorSize;
3 ^8 K$ y# T) P - count = g_tSF.SectorSize - Addr;
1 F9 h" K$ ?* N/ W$ f - NumOfPage = _usWriteSize / g_tSF.SectorSize;/ B/ F0 {1 \- }# p1 b
- NumOfSingle = _usWriteSize % g_tSF.SectorSize;
* Y$ T4 a% P$ G/ ]# E: h, u
5 u5 t, y9 c% r. e9 c0 G- if (Addr == 0) /* 起始地址是扇区首地址 */1 P3 D& P) x- i! N9 s
- {
5 ~' ^! x5 j/ Y( ^8 | - if (NumOfPage == 0) /* 数据长度小于扇区大小 */" d0 t. w3 }! S- D; u7 D
- {
6 `% h0 R9 }+ S- N# T - if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
" c2 @+ }# \% j, V7 Y - {
0 j" z* p6 K+ P% q9 G9 V$ g( j - return 0;
5 N2 S x/ t, b6 K, s/ z - }
3 j4 t5 q1 g2 u0 f( V - }
' \7 G" T" H g2 L; `% C - else /* 数据长度大于等于扇区大小 */
& l+ g% }; H8 T2 d0 m - {
$ e" h/ b# ]5 y# ` - while (NumOfPage--)
- g: E/ g' J d+ o/ A( f5 E - {# s7 ]0 Q! F5 f Q, E+ Y
- if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)
8 m( h( m8 z/ G3 C - {
; Q, c" x. k: @, n0 R - return 0;
7 w3 U. X/ X% u8 L" T9 s - }7 p7 x$ h. M$ U0 u _/ l; D
- _uiWriteAddr += g_tSF.SectorSize;2 b$ A: Z0 }$ \/ ]* U3 n! _
- _pBuf += g_tSF.SectorSize;
( @. A7 V. E. @# h6 k- S# O, z - }
$ M" F1 b. _ U7 u- X8 L - if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
( c! D4 x0 F' h( d4 w# S9 K J. D - {) T. q, ^8 ~2 i9 p2 q' j
- return 0;
' w) @4 j/ O% W3 n6 R - }4 N1 r# Q+ ^* E- m; U) u
- }* Q: y1 }" \6 B/ Z& K7 Y4 o( d7 h
- }
3 Q1 a3 K5 {. N& O( M0 _ - else /* 起始地址不是扇区首地址 */
- W x9 Y/ o: ]! g; L% z; i! R - {% J7 e: ^/ h' _6 U/ n* @
- if (NumOfPage == 0) /* 数据长度小于扇区大小 */1 o8 x' x2 S6 E7 Y K3 N
- {) V/ `' l7 n, y, ~- ?% a
- if (NumOfSingle > count) /* (_usWriteSize + _uiWriteAddr) > SPI_FLASH_PAGESIZE */! M. ~; i6 s2 c9 Q
- {; i- d9 v F1 w
- temp = NumOfSingle - count;! h4 l" ^* @. N& _7 Z$ q
- - @5 ]# S; Y/ }; [# \
- if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)$ M; h! A/ Q1 C# U
- {
7 M3 Q7 I; i: i! L! i - return 0;
5 W r0 M$ f' ]5 s8 \) e% [0 ] - }' N& D# M/ z/ \* y
- 2 G4 u0 n" j5 j! _3 V" f' f# m
- _uiWriteAddr += count;' q& c: g! W4 G, _
- _pBuf += count;
% m( ~1 B6 c4 `6 M - & u, }) w3 {- v9 q" I, g# n9 h$ a9 x
- if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, temp) == 0). Z4 W/ s+ Y' w* ~6 H0 ~
- {* G3 e; B3 G F9 _- x
- return 0;
/ z- g$ G& n7 c, p( @ - }) t% e' l( e& Z g+ S# k
- }8 E) d5 E8 Y, ^: q( r
- else9 p: y& o/ N+ w8 j/ e4 K% X& V
- {) c% l4 `, [& q2 ~, k
- if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, _usWriteSize) == 0)
7 f# j+ \3 W# [/ R1 h5 @- j - {( f) ~- W" w; t+ x! ^
- return 0;
! Y; B" g( |5 ~4 q - }( N1 b+ J9 j* x8 x( G# E
- }4 N( b" t4 @& U$ W! T
- }
" y( ]7 h+ }" j) r. E1 q - else /* 数据长度大于等于扇区大小 */1 d+ I+ v! z2 f. y
- {/ i' D9 _4 D2 i- A3 Y
- _usWriteSize -= count;
) I. | W, E4 ~, t% m - NumOfPage = _usWriteSize / g_tSF.SectorSize;9 k2 c4 f4 y' z+ |' z# k
- NumOfSingle = _usWriteSize % g_tSF.SectorSize;
* U3 J/ V* s4 a& f" h: t( j1 c - if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, count) == 0)5 v- l. L' M( e% `" q: k& N5 Z
- {
* W4 \# N! F( \3 O9 \ - return 0;( l0 ]$ R; H8 `( ?1 u) q, ~8 G+ u
- } P0 m. A4 x) e+ O
4 R9 L" Y1 c; i% ~5 o, L: q- _uiWriteAddr += count;
$ p- m) D( y3 U( l1 L1 h - _pBuf += count;0 [( K6 W8 L; Y2 o! u2 l B+ o
- / n6 ], ]: p: a& m9 T
- while (NumOfPage--)
% W4 U9 }- F- O; Z) J4 |" D - {
" f( d9 e8 N9 |7 I7 [2 y9 L/ s- b - if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, g_tSF.SectorSize) == 0)( l4 f, L# q% m
- {0 R4 i7 q3 x" G8 q: I
- return 0;
) E! y/ y5 E5 o( ` - }
! a- k. J5 k, ~2 m" @( S2 D - _uiWriteAddr += g_tSF.SectorSize;
P: k# W! P) H9 A - _pBuf += g_tSF.SectorSize;* b- y) D& @( m/ B% s' G! p
- }
" _7 q* a) {, R4 \- W
" j8 j! j [2 l$ v* D- if (NumOfSingle != 0)$ U& d ]$ D8 @+ y# t4 `
- {
4 g' Q% ?+ f- }8 b+ c+ D - if (sf_AutoWriteSector(_pBuf, _uiWriteAddr, NumOfSingle) == 0)
5 m: v2 u& w) O/ {, E1 { - {
' C( C8 k* V5 V$ Q( @ - return 0;
5 u! P6 Q4 Q# \1 [ - }
: m9 w2 O2 `0 O: I - }
9 M; `' \6 T8 B - }
3 i4 e" A2 H$ M/ \( h - }. S/ E+ V& s L4 @9 K
- return 1; /* 成功 */
, I, l# Y' Q- N+ S" d$ O; r - }
复制代码 / t# o! v2 n. E; o% l
73.5 SPI总线板级支持包(bsp_spi_bus.c)
1 M6 f* l% R3 h/ a$ JSPI总线驱动文件bsp_spi_bus.c主要实现了如下几个API供用户调用:
0 S' d, M5 ?5 R! i% k0 U, b _" ]* \5 ~" z' b
bsp_InitSPIBus! G( f! u/ v y* y# s
bsp_InitSPIParam
6 \! M+ ~$ H6 Z6 P bsp_spiTransfer7 a \0 j, K3 Q
73.5.1 函数bsp_InitSPIBus
9 R, _3 ~3 O, B0 o. `函数原型:
6 ~% H8 G# F% x! F6 l5 H
6 o# F7 i0 m2 I0 D: p# v0 vvoid bsp_InitSPIBus(void)
- |4 \0 w( W5 H3 j5 A
- l, r4 w- @: x F( S函数描述:0 R8 Q8 M9 U* V9 v) S9 w; E7 l& y" d
2 o& y' L) L$ k: d, x0 y/ j' _此函数主要用于SPI总线的初始化,在bsp.c文件调用一次即可。
; O) u/ |& o2 ?
3 c; Y8 |/ i9 P; X4 \/ V8 d( v73.5.2 函数bsp_InitSPIParam
9 g( d1 b2 h4 U" G! g函数原型:
u0 \ @4 g6 I& n, S( E5 z
' Y) l3 w, L2 U6 L1 u9 \: d' Mvoid bsp_InitSPIParam(uint32_t _BaudRatePrescaler, uint32_t _CLKPhase, uint32_t _CLKPolarity)
" m# }$ e1 }4 a+ c# f
4 N, W; T! c! d9 m0 w ?- H+ T! ?函数描述:" g1 ]/ M+ M3 G' L# W7 f
' U J( E# P& e! m此函数用于SPI总线的配置。
1 v) b* n* E" }) @2 Y4 `
' j. W, f: W- O: k3 B2 B函数参数:
) k) h# ?8 s T8 V
% l' ^. V0 n6 ?, S1 g$ H 第1个参数SPI总线的分频设置,支持的参数如下:& N" K1 [( q0 U! c! S
SPI_BAUDRATEPRESCALER_2 2分频
# t9 o- v& s0 |8 \$ Z
) G Q5 `% R( {, w, D, U! vSPI_BAUDRATEPRESCALER_4 4分频0 ]1 B6 M3 l# p8 @+ V
) ^5 L5 p$ N$ r: K) g# o/ iSPI_BAUDRATEPRESCALER_8 8分频6 R- C R. z9 m0 ~4 K! @0 s6 p
, x( C. E9 q6 S6 U3 }/ ]2 |6 F0 oSPI_BAUDRATEPRESCALER_16 16分频
$ \/ h9 z$ ?& {5 l* K( `, p, t7 b7 s @4 `2 D9 F
SPI_BAUDRATEPRESCALER_32 32分频; D: ~9 x9 Q, g5 Y7 ]9 ~
" s' a9 z& X5 }1 N" L
SPI_BAUDRATEPRESCALER_64 64分频
( a% F6 e7 k( ?& O0 x* E! x, o k- Z3 F' {5 V7 o4 j& u
SPI_BAUDRATEPRESCALER_128 128分频
+ `- s! e( e6 t9 f' m; ~7 F: F3 s9 V7 w
SPI_BAUDRATEPRESCALER_256 256分频
" w3 n% o/ p y* A' a% L. w% B/ f0 r4 ?% \0 u
第2个参数用于时钟相位配置,支持的参数如下:
# X1 m0 W2 V0 H% N5 x% \* XSPI_PHASE_1EDGE SCK引脚的第1个边沿捕获传输的第1个数据# e( ^' J. ~! A- X4 E( I
6 h! H8 X5 N2 uSPI_PHASE_2EDGE SCK引脚的第2个边沿捕获传输的第1个数据/ U& o' I& ~1 L' L+ U
9 N5 _; }4 \* k) t
第3个参数是时钟极性配置,支持的参数如下:
8 ^7 A5 E, A9 oSPI_POLARITY_LOW SCK引脚在空闲状态处于低电平: ?6 A, d! k; l' y1 a
/ Q* O) b6 }4 I& F1 e) G2 ?
SPI_POLARITY_HIGH SCK引脚在空闲状态处于高电平$ l. C9 G- d w* H% r5 M/ _
+ v3 B" U3 ~# R: f: t73.5.3 函数bsp_spiTransfer
# m9 M3 D" O$ E- \3 Z# i8 v) q( Q1 R函数原型:3 E5 G0 z7 {2 G, s7 E0 I
8 Y, B0 z0 i/ x% o9 w! L8 q
void bsp_spiTransfer(void)3 m9 m' m% F( b( p$ @
& C, G; ]) u" J函数描述:
4 o! R: h W. J! T/ I
' R0 i8 N5 g/ B7 {此函数用于启动SPI数据传输,支持查询,中断和DMA方式传输。
7 S9 [& s- i1 a
n( g& A! |- V0 `$ t9 B* |) |* U73.6 W25QXX板级支持包(bsp_spi_flash.c)
7 m3 j4 T% u# w4 a! |W25QXX驱动文件bsp_spi_flash.c主要实现了如下几个API供用户调用:
7 ?4 @' D& u6 l+ z/ S! S' ~' {9 _6 f( d
sf_ReadBuffer
- x; j; K& [+ L U sf_WriteBuffer
6 P1 m' n3 B8 }7 m: Z sf_EraseSector3 W$ l& Y# s8 I* Q8 C2 [% s
sf_EraseChip" m/ G9 ^0 s" j, y+ P0 v) G0 C. @
sf_EraseSector5 X: s2 r9 N( I' o
73.6.1 函数sf_ReadBuffer
3 R% e* Y: Y+ |& x) `( E/ {9 g! y) h函数原型:
+ q& M8 z/ y9 u& x( k" x+ ], t# ], ?8 x% Q3 v
void sf_ReadBuffer(uint8_t * _pBuf, uint32_t _uiReadAddr, uint32_t _uiSize): Q* q- ~& A2 `& q t
8 j9 s* N3 H) h) B* S- S函数描述:
( Y& b" |/ r2 c: e# @; O N
z9 W" T/ a: l此函数主要用于从SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。; s- R2 t/ Z$ B* `' E8 _- A
( g4 C+ q0 B7 O! \函数参数:3 z; b! b/ w; L) X2 w: P- O6 x5 v( @
, g* K0 V: n6 x
第1个参数用于存储从SPI Flash读取的数据。
2 ~( h1 T. t; V1 m( L$ n 第2个参数是读取地址,不可以超过芯片容量。
( H9 T1 K1 z7 m& a0 N! V9 x 第3个参数是读取的数据大小,读取范围不可以超过芯片容量。
% y N b% _1 {9 V73.6.2 函数sf_WriteBuffer(自动执行擦除)9 s( [" a# g( w5 ? Q# Y: t' z% h w
函数原型:
2 F- h/ ?! M9 }' F6 E! F! R
' Z, E/ D' @9 N: d4 f$ Duint8_t sf_WriteBuffer(uint8_t* _pBuf, uint32_t _uiWriteAddr, uint32_t _usWriteSize)$ g5 x e5 H2 |& ~: M
; C* F( d, _: H: `函数描述:
0 o6 m$ x) o5 D9 T8 {* ~( I( W) T0 U; H$ M2 j) a' h
此函数主要用于SPI Flash读取数据,支持任意大小,任意地址,不超过芯片容量即可。特别注意,此函数会自动执行擦除,无需用户处理。- ]& o! T- I* }) r. x
0 {9 ?- r ^7 x; O7 C函数参数:8 l E) m2 S+ \' i' W2 Y- [+ V4 T
: X. @5 m3 y. q! {3 Q 第1个参数是源数据缓冲区。- J$ ]( Y- T Y
第2个参数是目标区域首地址。
6 \7 m" Z" M( }8 @( ]! n 第3个参数是数据个数,支持任意大小,但不能超过芯片容量。单位字节个数。
; t4 |" a4 |, n( B3 t0 m 返回值,返回1表示成功,返回0表示失败。( @2 A* B$ \- J" E
73.6.3 函数sf_EraseSector
6 S$ q& q1 f7 N; E函数原型:
& W& Y. i8 y! I; U2 y: S
8 b0 W5 j: [* ~2 X% Y3 C$ Gvoid sf_EraseSector(uint32_t _uiSectorAddr)) \# ?! Q: n5 ^- S1 s# C& Q# j
, g% d! X2 O$ Q' _/ w- Q$ [函数描述:* j: o+ x5 u: `! K* Y
% \# E: j% E% t, l- O# D* f% U1 q
此函数主要用于扇区擦除,一个扇区大小是4KB。) n9 \ [4 p6 \5 W* R
) p+ ~+ Y j- o- k, _: X, _) O函数参数:
. @! z) x/ f# G; z3 [4 j5 N; | w5 Y+ o. k% d
第1个参数是扇区地址,比如擦除扇区0,此处填0x0000,擦除扇区1,此处填0x1000,擦除扇区2,此处填0x2000,以此类推。$ z" V! v$ s0 { ^- F! j
73.6.4 函数sf_EraseChip
0 D( K6 G" \7 _. h6 y. N0 ?9 V8 @9 a9 ]函数原型:8 N" |! @' @' `! R9 R
/ b, Z+ D j+ J6 ]: [8 Qvoid sf_EraseChip(void)9 _: {3 \0 J2 |4 E# t
g4 z/ F9 ^ k
函数描述:' d/ q: Y& z" \9 B4 f0 e
- ^. H' D. @# `6 f, y; S此函数主要用于整个芯片擦除。
# c2 \& h( I: {& l9 M v. A7 y2 I* c7 `+ I* P2 j6 p
73.6.5 函数sf_PageWrite(不推荐)
* q$ B' I6 o$ x函数原型:" z: r% e2 u: |
. ?2 o9 Q/ [0 ?
void sf_PageWrite(uint8_t * _pBuf, uint32_t _uiWriteAddr, uint16_t _usSize)6 v- j1 X) y/ [ e
" k7 K( h: G: z0 Y" u b& a; } r4 c
函数描述:; I; Y1 ^/ N' N, J$ s" w1 R7 }
2 e8 j. M( H& `( w+ c/ c% |1 X此函数主要用于页编程,一次可以编程多个页,只要不超过芯片容量即可。不推荐大家调用此函数,因为调用这个函数前,需要大家调用函数sf_EraseSector进行扇区擦除。7 |# r5 k% g; k7 I/ V) j* M
' U P8 G# u$ j, A9 S
函数参数:
- w% V, V% O7 r5 ~& w3 Y! P' Z+ M
第1个参数是数据源缓冲区。4 p- t# {2 V; Q
第2个参数目标区域首地址,比如编程页0,此处填0x0000,编程页1,此处填0x0100,编程页2,此处填0x0200,以此类推。
: c! d: ~, b$ i4 ^ 第3个参数是编程的数据大小,务必是256字节的整数倍,单位字节个数。
$ u, M8 r/ J3 k) T( b) h9 L73.7 W25QXX驱动移植和使用
1 c1 _( |$ C* CW25QXX移植步骤如下:
0 q5 c* h4 M+ l/ }3 g* F" Z# g5 k6 y2 C" w" [( l$ S
第1步:复制bsp_spi_bus.c,bsp_spi_bus.h,bsp_spi_flash.c,bsp_spi_flash.h到自己的工程目录,并添加到工程里面。/ z( B7 g( s* W- y& O
第2步:根据使用的第几个SPI,SPI时钟,SPI引脚和DMA通道等,修改bsp_spi_bus.c文件开头的宏定义8 d# w* v! M+ E. _# N
- /*5 f4 X! w1 Z7 g1 D+ X* e5 s
- *********************************************************************************************************
! r/ m2 R9 A( l( T7 m - * 时钟,引脚,DMA,中断等宏定义
( b" K. S+ l* P8 p* s8 j6 S9 z* w - *********************************************************************************************************
0 ?9 W7 }3 v' X1 ?; |0 L0 C - */
, U, ?2 k( P% p; a+ q3 f - #define SPIx SPI1 R$ l4 S" ~' B [$ }' Y! E
- #define SPIx_CLK_ENABLE() __HAL_RCC_SPI1_CLK_ENABLE()
) R% B# m- N6 g4 w) y7 z2 O - #define DMAx_CLK_ENABLE() __HAL_RCC_DMA2_CLK_ENABLE(). p# D; D# \. r3 N0 i8 R
- . p* I S* @1 ~# ~ _5 ^: }" n: F( T
- #define SPIx_FORCE_RESET() __HAL_RCC_SPI1_FORCE_RESET()( `$ s# a( w' c$ o6 U
- #define SPIx_RELEASE_RESET() __HAL_RCC_SPI1_RELEASE_RESET()
+ W+ f2 w3 E5 k7 q4 y8 R5 _ - 0 C. @- B' s7 D3 U U) x
- #define SPIx_SCK_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
* M1 O: q. a: `$ u, ` - #define SPIx_SCK_GPIO GPIOB
% h' Q4 m& h; m: F( s1 V% j6 G+ a$ ^ - #define SPIx_SCK_PIN GPIO_PIN_3
: b& Z- W' D7 z1 [ - #define SPIx_SCK_AF GPIO_AF5_SPI16 P; P7 L* _1 J/ T0 K8 R4 v
- ; x. f4 n @/ ]3 Y' y6 c& x5 t
- #define SPIx_MISO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()3 g4 d- q- T8 h |
- #define SPIx_MISO_GPIO GPIOB5 d' W+ R+ }7 Q/ d
- #define SPIx_MISO_PIN GPIO_PIN_4# O0 C7 E9 H2 `8 |- @. Y/ m
- #define SPIx_MISO_AF GPIO_AF5_SPI1
?* i: _6 Q; i2 T3 v# G - 3 P! i% P ]! ]5 W
- #define SPIx_MOSI_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
+ [# O" K7 R! @- }0 \: Q - #define SPIx_MOSI_GPIO GPIOB
' D4 X) _6 v) ?8 ]+ l - #define SPIx_MOSI_PIN GPIO_PIN_5
' I$ E3 A% \. G - #define SPIx_MOSI_AF GPIO_AF5_SPI1+ D, I) h5 `' W* f
- 9 _+ q* n# L* F& D9 b+ n
- #define SPIx_TX_DMA_STREAM DMA2_Stream3
, M- n2 g2 i$ S" f) ~ - #define SPIx_RX_DMA_STREAM DMA2_Stream2
# v4 L2 B' o7 g6 g
4 I) ~, O: ~2 e- K7 v5 L* q- #define SPIx_TX_DMA_REQUEST DMA_REQUEST_SPI1_TX3 ]+ h& H* T+ c: O3 {7 \
- #define SPIx_RX_DMA_REQUEST DMA_REQUEST_SPI1_RX
1 \% H! K# J! }9 X0 |7 X& c) U! Y
/ w( R9 a. Y/ g$ b# E4 E) q- #define SPIx_DMA_TX_IRQn DMA2_Stream3_IRQn" S1 m( N2 n- q x8 r
- #define SPIx_DMA_RX_IRQn DMA2_Stream2_IRQn& A0 ^1 t- l& g: g8 D4 I
- + Z0 K Y9 C, G& [( y/ g
- #define SPIx_DMA_TX_IRQHandler DMA2_Stream3_IRQHandler! a* ]5 q7 [* ?5 e3 W# T
- #define SPIx_DMA_RX_IRQHandler DMA2_Stream2_IRQHandler+ P z" F z) Z0 ?2 e. Z
( q$ L% x0 d) F U- #define SPIx_IRQn SPI1_IRQn. f6 o# ^. `9 T* ]# R0 ^2 b
- #define SPIx_IRQHandler SPI1_IRQHandler6 x8 ~9 z, I& n) I- p
复制代码
7 |+ |: w5 Y \9 x% Z9 W; z/ L5 w3 X( S: i C. @
第3步:根据使用的SPI ID,添加定义到文件bsp_spi_flash.h
- u6 V- K0 p3 g4 y! S- /* 定义串行Flash ID */2 @! |9 G, Z, H4 a( w% j
- enum' {! o( J/ V' W3 Y0 L8 _$ P" N2 j
- {* F$ g1 }& v: i& @3 g
- SST25VF016B_ID = 0xBF2541,
' V8 w0 a s. O! n/ V - MX25L1606E_ID = 0xC22015,0 [$ _$ g$ l; M
- W25Q64BV_ID = 0xEF4017, /* BV, JV, FV */% O8 _* ]1 ^5 r. O4 {
- W25Q128_ID = 0xEF4018
* s% x" m& [( L, O' h" o1 E) R5 N' i: J - };
复制代码
5 V! ^" _* {0 {# u& u 第4步:添加相应型号到bsp_spi_flash.c文件的函数sf_ReadInfo里面。
5 K: m0 z5 }$ F3 G- /*
) B4 L/ p& G! r) b/ A' J - *********************************************************************************************************
* G6 F1 m9 g- u. e1 ?% V9 G$ ~ - * 函 数 名: sf_ReadInfo: A: B( P/ E: W+ l3 Q! z6 @
- * 功能说明: 读取器件ID,并填充器件参数! l* T! P: c+ o9 u1 r# Z% u- m5 [
- * 形 参: 无
6 G. j# A0 Z1 n - * 返 回 值: 无
% Q1 g# d: [, c) E/ t - *********************************************************************************************************1 w& C& |% e# q+ i3 c% P
- */
+ r2 j( y: A0 D& c6 d. _ - void sf_ReadInfo(void)
8 J, N: d( e8 n3 O5 C7 | - {
7 G+ Q! T' D$ m0 m9 n( g% x x - /* 自动识别串行Flash型号 */
0 K) Q, x8 `6 o& Q5 V( [3 [2 r - {
7 n! J0 V W4 B' a - g_tSF.ChipID = sf_ReadID(); /* 芯片ID */
) ^. g1 E) C; D3 X - . Q* R! t7 ^ v/ N
- switch (g_tSF.ChipID)- B- f' _; X2 W
- {
* U$ ?4 X" Y1 a; h9 o. Y* Z6 b - case SST25VF016B_ID:
h8 c; E" w5 j* p/ b( L6 g - strcpy(g_tSF.ChipName, "SST25VF016B");
6 ]- S1 @- _% V. d4 a% R- P - g_tSF.TotalSize = 2 * 1024 * 1024; /* 总容量 = 2M */
$ n% r3 N9 \. D: n - g_tSF.SectorSize = 4 * 1024; /* 扇区大小 = 4K */: A @. y U" u8 {: u
- break;
! Y2 j) T' { ?3 `$ U - ' `" j8 x4 n! k" }! }7 x6 W
- case MX25L1606E_ID:
" Q$ u8 T8 L3 Z/ u - strcpy(g_tSF.ChipName, "MX25L1606E");# E: l+ h# j; _$ W) z
- g_tSF.TotalSize = 2 * 1024 * 1024; /* 总容量 = 2M */
" Q" \3 h2 }/ [6 A8 d9 x" v( Q - g_tSF.SectorSize = 4 * 1024; /* 扇区大小 = 4K */
9 T! i7 i; `/ F& D. y) f - break;7 k; s% c1 ]6 r& s1 i+ |+ i
/ j! U6 C9 i2 n. d) S, y- case W25Q64BV_ID:
0 d9 A& A% ^* _# O, g' x! h' Y - strcpy(g_tSF.ChipName, "W25Q64");, I+ x2 |8 J$ V P2 b" S7 v/ Q
- g_tSF.TotalSize = 8 * 1024 * 1024; /* 总容量 = 8M */
( P" c' l5 c: X; j; ?( z - g_tSF.SectorSize = 4 * 1024; /* 扇区大小 = 4K */
3 o8 W( e3 L3 E0 V) h! I - break;
! X8 W2 K+ B/ \9 A - ; e4 H1 G( B5 X r* L1 s r* {
- case W25Q128_ID:
' a/ m9 |5 Y4 h0 j - strcpy(g_tSF.ChipName, "W25Q128");+ r' j6 f& t5 w4 X
- g_tSF.TotalSize = 16 * 1024 * 1024; /* 总容量 = 8M */. J4 N8 v: ^* i% H+ g3 G
- g_tSF.SectorSize = 4 * 1024; /* 扇区大小 = 4K */
4 k3 \% K4 r2 M( {$ s7 a - break; . q& {" \6 H- N' d/ ]
- & n- o/ x' \8 c# h- O. O# n* |
- default:
( E. {+ x1 ?/ n; J" [! t4 S - strcpy(g_tSF.ChipName, "Unknow Flash");/ X% q, U8 ^9 [: M7 ~2 E$ K
- g_tSF.TotalSize = 2 * 1024 * 1024;6 A. c. J$ N) I7 ^) Y) x3 A: W
- g_tSF.SectorSize = 4 * 1024;
S, m0 Q* }2 _# p/ W - break;
: X1 a/ {' R5 T- o8 T |* e - }
4 w [/ s& @0 E7 I+ \ - }; w, _: q$ R5 V5 U/ q
- }
复制代码 2 t; h5 O% C% E
第5步:根据芯片支持的时钟速度,时钟相位和时钟极性配置函数sf_SetCS。
7 F a! p' [! \- /*6 Q' P2 @0 X4 F$ @+ t& M# j) ~. s% O
- *********************************************************************************************************$ j/ m. i1 r; i) @* I4 q/ m
- * 函 数 名: sf_SetCS! a V' v. @+ h$ Q3 Q
- * 功能说明: 串行FALSH片选控制函数8 Q3 r" _ e8 n- ]
- * 形 参: 无! _0 H6 Q6 z) V! g9 P6 V
- * 返 回 值: 无
9 v9 V4 A. n3 \( W9 @. Z D( B- L+ t - *********************************************************************************************************
0 o2 ?9 T; e! @ F - */
, Y& |1 }% h8 k* p# o - void sf_SetCS(uint8_t _Level)( J, `) N( h; I9 f0 c9 y$ r
- {
# I5 s8 e/ |) S# Z& b2 P9 G- @7 F - if (_Level == 0)
; x( H: a5 i' Q - {3 f+ X E w- Z3 a* U/ P
- bsp_SpiBusEnter();
* A$ \7 i9 H+ V/ x3 @ - bsp_InitSPIParam(SPI_BAUDRATEPRESCALER_4, SPI_PHASE_1EDGE, SPI_POLARITY_LOW);
9 f. Y2 i1 G H6 a2 S+ U% L5 x. c - SF_CS_0();
* B' C3 P% R9 W& z& w - }. G; A U. r; {! [5 M6 {; @0 {
- else
/ C0 w2 T- R9 \0 D - { - j: |0 t: z* ?" ]8 ]. P
- SF_CS_1();
8 t& M% f. g6 K3 k& M0 p - bsp_SpiBusExit(); 2 o' e7 M5 o! n2 q
- }
$ i" v" j, G* Q7 a* p - }
复制代码
7 u T4 D$ _% |3 { 第6步:根据使用的SPI Flash片选引脚修改bsp_spi_bus.c文件开头的宏定义。2 T+ L- [8 z2 P0 ?4 q7 L
- /* 串行Flash的片选GPIO端口, PD13 */
# M8 a4 d" p1 d" z9 `* q: y) T - #define SF_CS_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()+ C ^6 l& [. |! P
- #define SF_CS_GPIO GPIOD* ]" c7 [3 c) e3 v% l; {3 k9 g2 l
- #define SF_CS_PIN GPIO_PIN_13
. I8 b+ N1 G# B9 M3 B7 @9 Z
$ E) Z7 d- W0 r6 }4 R* e- #define SF_CS_0() SF_CS_GPIO->BSRR = ((uint32_t)SF_CS_PIN << 16U) # i7 S a2 ^( b1 h/ D! o. T/ L
- #define SF_CS_1() SF_CS_GPIO->BSRR = SF_CS_PIN
复制代码
$ H2 F& i+ i8 q$ C& r 第7步:如果使用DMA方式的话,请不要使用TCM RAM,因为通用DMA1和DMA2不支持。并为了防止DMA和CPU同时访问DMA缓冲造成的数据一致性问题,将这块空间关闭Cache处理,比如使用的SRAM4:" h1 _* W2 U0 }; S" b! ]: D
- /* 配置SRAM4的MPU属性为Non-cacheable */# C8 Q& a- p0 H
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
2 X8 ?! Q5 r* v T( \* K) e - MPU_InitStruct.BaseAddress = 0x38000000;' C- R9 D: o! Q9 y& }
- MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;1 z7 E! W6 e8 {1 q8 p8 U5 M
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
, h. ] r: y: ]# s- A! P - MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
. P [7 `; D) ]( L e' V3 X; r& V$ }! D - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
2 n, n5 K5 K5 J - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;7 X/ [) d- a4 Y4 \/ l: y( N
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;/ \9 X1 d% V4 d! R8 q Q& r( r
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
: g# Y# a& J, W/ @3 Y9 V1 z - MPU_InitStruct.SubRegionDisable = 0x00;
7 E" D! f* u7 P1 v! b9 u% T - MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
. K! w; Z1 x2 I3 b' I9 l0 L - - g) i8 m& x, G% H; I% r6 d3 C* Y
- HAL_MPU_ConfigRegion(&MPU_InitStruct);
复制代码
- ?4 N4 F) v3 B e" n 第8步:初始化SPI。+ c: G1 ]$ Y: A/ [8 y* @# U4 K; P
- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
! J8 W, E8 q- Z6 e( L0 {' S) c - bsp_InitSPIBus(); /* 配置SPI总线 */ . `( a1 C+ l! u
- bsp_InitSFlash(); /* 初始化SPI 串行Flash */
复制代码
4 y. [; p$ b+ E# r+ Z% f& H 第9步:SPI Flash驱动主要用到HAL库的SPI驱动文件,简单省事些可以添加所有HAL库C源文件进来。& ~5 E. ?2 A$ C# b Z- G- q# S
第10步:应用方法看本章节配套例子即可。. J* @! I/ K- F9 ?" R/ p
73.8 实验例程设计框架
: L7 M& b. |$ ]" e' B% q M1 e通过程序设计框架,让大家先对配套例程有一个全面的认识,然后再理解细节,本次实验例程的设计框架如下: ?$ h$ e6 V! n3 O9 t
+ _: E c6 A+ O
# N0 G4 Z( c, J7 V; ]% _# ~* K& k. N+ q& Y$ w3 W
第1阶段,上电启动阶段:
! u2 D7 a# ^6 S1 b9 t( X$ d, E! t
$ d% T7 p/ r' S' e+ v6 d$ N/ }这部分在第14章进行了详细说明。
, T2 v8 D3 n- i3 u9 q/ S ! W1 Y$ Q' M- Z& b2 c) u) I
第2阶段,进入main函数:
. ~, c+ A* y V( L- E6 O$ X0 M. h7 |' J7 C
第1部分,硬件初始化,主要是MPU,Cache,HAL库,系统时钟,滴答定时器和LED。1 F+ {0 A8 Y- `. u2 p
第2部分,应用程序设计部分,实现SPI Flash的中断,查询和DMA方式操作。
, S8 B5 u9 D( w n) W* ^/ T8 v6 L9 h7 f# e4 G% l- {+ T2 f9 ?
73.9 实验例程说明(MDK)
9 f7 @, R# ^+ I: c Z/ U配套例子:1 P7 I& L+ r9 w8 Y; \2 e3 s% i9 p
7 X' C K8 |* M4 J( Y4 k& }9 x
V7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1)
$ _0 n' t6 [% P% o& h" U/ K1 t+ W0 t4 r+ M2 B6 p- ~7 J+ }% v* g1 @& e
V7-050_串行SPI Flash W25QXX读写例程(DMA方式)
# P- ]* f5 U6 ?1 T9 s; ?9 ^+ A% ?
V7-051_串行SPI Flash W25QXX读写例程(中断方式)8 q6 T5 m5 ?$ z; G
* g1 z/ U" g' ]* @- A5 G( |0 h实验目的:
- J) s( j$ K. b% T1 J) o* `& B
0 @2 A# S: Q! ^) O学习SPI Flash的读写实现,支持查询,中断和DMA方式。
6 j0 I% c; @! _% S9 I5 q. p9 K0 q3 a+ m* z2 U8 Z1 Y
实验操作:4 q: z/ W4 w# C6 ~& H
6 W8 v6 R- x+ i+ ]支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
. D6 U$ N2 r5 {printf("请选择操作命令:\r\n");
/ r4 R0 o$ L$ D- V5 C; p! xprintf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE); B! d( q1 _. ? Z9 Z8 \' H
printf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);+ L% \3 ^; f" S2 i& i
printf("【3 - 擦除整个串行Flash】\r\n");
- \9 k6 q4 J# ^9 g2 L' a0 ^* C/ E( P2 Vprintf("【4 - 写整个串行Flash, 全0x55】\r\n");
' |7 W5 {" a1 ~ d3 _) b gprintf("【5 - 读整个串行Flash, 测试读速度】\r\n");. a* v! Y9 i8 I+ O7 m
printf("【Z - 读取前1K,地址自动减少】\r\n");( G9 N$ W4 Q8 A1 q7 ^2 ?
printf("【X - 读取后1K,地址自动增加】\r\n");9 o/ x" b- N7 ]- X! E' N
printf("其他任意键 - 显示命令提示\r\n");
5 c2 P H0 f1 `" Y上电后串口打印的信息:* V& U$ Y; p! y* A; F1 U3 T
2 j, o0 L, S) N
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
& b. y8 V# f6 }/ C# T! X$ |
# S$ X# B. B3 }* ~% x! f1 x, Y. `; G, e' ` c" i0 [
* z2 f/ v C4 ?7 r% I( @4 U2 y; G( J
程序设计:
$ m& d, J1 }/ O
: z6 ]/ b9 z& G2 T0 }2 k, [" m 系统栈大小分配:0 c, |( m9 Y5 B+ S; a
7 n; v+ M R" `
6 P2 h' M. q& o' B: b, C5 M Z; O( p% d: v% p
RAM空间用的DTCM:
9 _4 B, k$ f( d, F, X7 q6 x1 i! \
5 f) z" i: O6 W1 N+ W6 p6 Q6 l! ?& l; T$ V2 u4 j6 q3 _: {8 z0 l/ @
T- y" B$ M$ T! [' `! {5 p
硬件外设初始化+ u/ I8 U- j6 N% o$ T7 O9 ^
2 T, o: B+ d" p- j5 G" x& B硬件外设的初始化是在 bsp.c 文件实现:
' r9 u: J& }' y3 b
( k* r$ u- ^9 j9 W- y/ x9 }- /*0 d* p, E! i# t1 N. _* O+ l3 S
- *********************************************************************************************************
* S4 p( M: o2 z. u/ L7 C - * 函 数 名: bsp_Init
( _ [/ w+ Y d3 l0 H - * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
) A; j9 z% n+ o! F - * 形 参:无
5 G* D g: s# {2 m - * 返 回 值: 无
- v- b7 g* ]. j* w, B7 t - *********************************************************************************************************
; e3 |8 h% ^8 i: G8 ~ - */" R( [/ `; d9 i e* Z7 Y
- void bsp_Init(void)
# H+ @8 B4 c; [; o0 S - {2 x0 r! K2 Q, w0 Q
- /* 配置MPU */
2 X. d: N; o$ j( \ - MPU_Config();) Q4 N( [' U$ h5 Y
- - N6 e* L L5 z2 w: P+ s: R
- /* 使能L1 Cache */
; `8 c2 ?1 G0 v) W, @* V8 d" E$ I - CPU_CACHE_Enable();$ K8 A+ `) G' i& u! h
- & l- m/ `( u' F6 Z# ?* l, P
- /*
1 T, B2 T6 c$ I4 I V - STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:
0 c, _" p5 n( K4 G6 S e! S - - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。
+ p" O" ~0 j' e+ D: R - - 设置NVIV优先级分组为4。! m9 z% x, l, A( g R: f, {
- */
$ `$ V( o8 h# A" t. { - HAL_Init();
& T' E: v* t- N8 |9 y9 T8 G
Y& B6 B/ t+ \ J- /* 3 W, T, r+ T9 J5 ` L
- 配置系统时钟到400MHz
) R4 g: O8 D1 [4 E - - 切换使用HSE。
: Q$ Z+ \! N2 k) B/ ?7 l" J _ - - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。) I# X, ~3 G9 f2 |$ x# g
- */) k) e: o6 n! \2 X, S
- SystemClock_Config();9 X/ y( k( ^: w, M6 p
- % U5 g6 s6 W+ J
- /* ) R8 ~% p* {! d( f6 O* }# _+ T
- Event Recorder:5 b! I4 n/ v4 m2 T( ]4 j! p
- - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
. R- Y- _" o( a* W# W: e - - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章
) f; r* @ a$ Q/ G- v9 f( g - */ 1 E n$ k: m$ M6 ?& Y9 X/ Y& e6 a3 b$ a
- #if Enable_EventRecorder == 1 6 p7 k; L. B. e7 Q: B3 \, s
- /* 初始化EventRecorder并开启 */
6 K: y3 `7 F0 A4 G; C - EventRecorderInitialize(EventRecordAll, 1U);# T" n, C* l4 |3 {& X3 i1 s
- EventRecorderStart();$ p$ h* }8 |* B8 j& p4 u& X
- #endif: u; e7 j1 r; M+ p n4 `* z
, E$ I: l" @2 |3 b/ t9 X- bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ ) B) k8 F0 d7 b) {
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
$ t7 k- @) x( }$ ]; j, n - bsp_InitTimer(); /* 初始化滴答定时器 */6 T8 n7 B+ e" r o. u
- bsp_InitLPUart(); /* 初始化串口 */2 o4 ~1 t5 X" W9 t! \% ] C5 Y
- bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
' \+ `* C3 h0 G. m - bsp_InitLed(); /* 初始化LED */ ) L. I f, N- d, d0 i
- bsp_InitExtSDRAM(); /* 初始化SDRAM */
$ ?4 D0 d+ v3 U$ g# R( \
# Y8 q% f* N3 R3 ~6 l- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 */
( O) b u; K$ W; o# I) ~. O - bsp_InitSPIBus(); /* 配置SPI总线 */ 1 S5 E) o* f9 h" r) s
- bsp_InitSFlash(); /* 初始化SPI 串行Flash */* }# [8 V+ Y. g
- }
复制代码 . o5 H$ w5 U9 Z) f; X& |- X2 z
MPU配置和Cache配置:0 V4 e$ O$ c) X1 U. {0 a8 F/ Q9 v
: |8 K6 @5 Y, H& ` V" {( ~' [
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区以及SRAM4
# S9 a! |7 x. F
# A, E2 k2 l7 e- ]7 j7 Y- /*
, Q6 r" [. B, j: {5 e2 ?1 w - *********************************************************************************************************) M. z2 K7 ^# C# w# f1 k; T- p. @; W5 y
- * 函 数 名: MPU_Config
3 x. |& D& v- {. W% f6 a9 h - * 功能说明: 配置MPU
, X- l( ^: s+ B% w |( ^( T: | - * 形 参: 无
/ [% ~0 [- l2 l7 @; p* `6 i- ? - * 返 回 值: 无4 h* e* R8 a. h- b- c+ P
- *********************************************************************************************************
* J" V7 _ R, V* @: o/ G8 _, {; N - */4 Z+ C3 X+ C$ k- m- j1 ]* P3 l
- static void MPU_Config( void )& t4 I# @0 U2 T. P
- {+ m$ e8 s# p0 O0 f1 `3 d' d
- MPU_Region_InitTypeDef MPU_InitStruct;
- G. s0 }8 W/ T' s" | - 5 B# N3 P m0 m: [" c, E
- /* 禁止 MPU */' d0 c7 n/ r$ n5 W( H) O' J
- HAL_MPU_Disable();
# j$ h; P; h% q6 f6 z5 E
4 W1 b' k0 _2 @) `: u- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */+ d, Q0 x; s4 ?/ x/ r
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;, a$ ?9 b0 m Y1 w% o1 x$ ?% L. v. `
- MPU_InitStruct.BaseAddress = 0x24000000;
+ n; b5 J9 b8 L/ g3 F - MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
/ ~! M4 u% L. x0 J- p3 t0 w - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
\# J3 b+ Q" v# Z - MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;9 c9 T4 Y8 R1 W9 J
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
$ H0 d3 j" g% E: N7 f: Z1 n1 F - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;) }- R' k* G( G& t. S4 p0 S
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;* {# O0 [/ ~: D4 r
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
9 b, g% g/ E, h0 p% S$ V% ~ _ - MPU_InitStruct.SubRegionDisable = 0x00;# ~- G0 |+ R: Y' n7 s
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;" [$ {* z+ o! n6 Y: J' Z
5 R- I1 |# G- Y6 j/ u N1 A! ~1 n- N- HAL_MPU_ConfigRegion(&MPU_InitStruct); A# p, @8 e O
- ( j5 I- l0 C) R: p# c$ Y$ S
3 t% i3 N* y& c; i3 G- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */
d+ X. B2 n9 T - MPU_InitStruct.Enable = MPU_REGION_ENABLE;. J8 H& f( I4 z( S, G3 E0 V
- MPU_InitStruct.BaseAddress = 0x60000000;( h; j5 \4 |4 l4 `& t
- MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
3 ~6 h% `5 }% V3 O8 Y - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
s3 F7 F+ ? l W( }+ y9 G1 Y - MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;- V# \4 `) c" l! G0 l" N# P5 e
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; 9 K4 ?5 L$ i0 I7 M6 m
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
9 B( H1 E- n% U6 X# P! u6 y - MPU_InitStruct.Number = MPU_REGION_NUMBER1;, e1 v* x" m5 X$ t
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;2 a( z% s' b; i
- MPU_InitStruct.SubRegionDisable = 0x00;$ `2 S2 v/ @6 T' X$ W1 s" o# }2 b- ?
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
" D/ Z, \1 m! I8 S S" n - 5 u4 S; f( W$ u8 J3 t0 M3 G7 z
- HAL_MPU_ConfigRegion(&MPU_InitStruct);7 k1 n2 d9 T! l8 V% @
0 d- Q3 q' u: \! I- /* 配置SRAM4的MPU属性为Non-cacheable */. R! Q4 U' u4 @( q, E% O
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
$ n+ ]) U" h( m - MPU_InitStruct.BaseAddress = 0x38000000;
0 A% Q3 G3 J. a. b - MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
- M7 q6 b: E0 y; ?. c7 l* d - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS; `) ]6 M' F* N# l! |$ E
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;5 Y3 ~. `. w; L: P% g* Y
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
* ^/ B4 \* @4 ^ |( F( a - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;+ w- b1 g% T9 ~& A6 S o$ t
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;
1 G: j& E9 B8 L! y - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
. r4 y1 A- w% z) V+ g9 f( j - MPU_InitStruct.SubRegionDisable = 0x00;- ]4 {( l, ?% @' F5 e4 D
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;7 j: h5 D4 f- t
- & f" ~1 {- j. l# m
- HAL_MPU_ConfigRegion(&MPU_InitStruct);* w9 r2 t0 ]1 x0 J
- 0 l; H3 J- O6 L! {, ~) J1 l# p/ l
- /*使能 MPU */ n- U: u3 I+ R
- HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
! I: j$ U' o% w x9 W1 \ - }1 |/ p: H' o0 e3 Y
- 5 y8 f& {7 Y# U6 e% s4 o+ \" g! B
- /*
( V4 A5 K. I, `0 Q0 J - *********************************************************************************************************
+ n) S' F8 R9 i7 n4 s9 X& p$ p" ~ - * 函 数 名: CPU_CACHE_Enable
) A; K; x ], @ - * 功能说明: 使能L1 Cache, \: R9 Y+ L& o4 G( {
- * 形 参: 无% k9 ]0 M; R) ]$ b7 d8 R1 h, E
- * 返 回 值: 无
a6 U- F' V, |7 U6 ^ - *********************************************************************************************************
( H# z" p7 v# R6 A( B i5 y: C7 l& v - */ Z' Z% F+ ^' m6 `) U. `: ?/ S( r! }
- static void CPU_CACHE_Enable(void)- r) ?! x) p2 X3 E8 g# L, y4 E
- {, q# Y x# ]3 O, P: V
- /* 使能 I-Cache */
. e/ w: i3 O6 z; b9 `; N - SCB_EnableICache();0 P4 R$ X5 n; b3 ]
5 R0 |3 R' X' l7 z- /* 使能 D-Cache */
$ @& C; H$ N" M - SCB_EnableDCache();2 {7 M3 C/ [ n6 f6 ?
- }
复制代码
4 Z) {4 j+ Q& l" p3 n6 z 每10ms调用一次按键处理:! t" t7 n% Z8 m* X" K" a0 m# U
4 i) _& b& O( L- s. \: w9 ^按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。
4 l0 n: E/ ~. U1 s; k8 ?' w% E f& ^6 s6 C6 h Z
- /*/ \- {+ s7 N! z
- *********************************************************************************************************: `8 s0 ]! I. k: s( {2 @
- * 函 数 名: bsp_RunPer10ms7 [3 u1 V( I7 e' i
- * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求+ B6 \" ~6 E# b( A+ J
- * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
6 L3 F w+ G9 B( C9 F - * 形 参: 无/ f) i! ~. v; h2 e" [
- * 返 回 值: 无6 w. S9 Q& i8 V
- *********************************************************************************************************. D, ]9 V! g! c* W
- */
$ D: @2 K) ?9 o" @* G+ W& p9 R3 ~3 N, Z - void bsp_RunPer10ms(void)
! h; O. W( U$ z# p: k! E9 c - {; O8 V4 O N- E4 |. a9 L, Z1 s8 b
- bsp_KeyScan10ms();
, {* \7 J1 J! Q% u! J" O - }; i$ E! o, Y/ @
- # O" b) \7 D# m1 |4 Z
复制代码 1 z% _( T( G+ X
主功能:4 D/ ]$ V2 |0 H" w4 w7 \' {
C2 P" S% T: h. {3 v& r Z
主程序实现如下操作:
9 v: _% E- m e7 u( c$ |5 p' z% C/ S r
启动一个自动重装软件定时器,每100ms翻转一次LED2。
V- |& J1 o% s: s5 Y 支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可" ^, l. x3 \' ~* }% v
请选择操作命令:
8 J4 b7 S# r( \ 1 - 读串行Flash$ ?3 P) }8 f' \1 k
2 - 写串行Flash
g& e _* y9 s8 U6 s8 T( d 3 - 擦除整个串行Flash
( r6 J- D& ?' \/ d2 |4 Q/ F 4 - 写整个串行Flash" F8 k) r2 Z6 @" Q' s3 {1 A
5 - 读整个串行Flash0 \; I2 k1 S* o- m+ R: z f
Z - 读取前1K
/ V+ M! C3 i& [( b5 s* g$ n \ X - 读取后1K
* U) e9 d5 o0 I7 }( y$ A5 a9 ^- /*4 n r3 [5 M0 [: H
- *********************************************************************************************************! z+ _6 _; E: A1 f& Q
- * 函 数 名: DemoSpiFlash( R- n) s4 E4 N0 S
- * 功能说明: 串行EEPROM读写例程# p* W6 I$ X) S ~6 q% h
- * 形 参:无: P4 |4 g1 F) w$ Z. F c
- * 返 回 值: 无
) C- I8 z' N* ~$ J! j7 s7 s, {& [ - *********************************************************************************************************
; G$ h( u8 `( [" n* Q7 K+ j8 l - */, z6 P# Z7 s. {) f5 |5 }
- void DemoSpiFlash(void): T( ~2 d6 Q8 t3 g2 ^: B
- {
7 K5 o7 A) W8 q: u$ O - uint8_t cmd;
' u3 I; S$ F/ f - uint32_t uiReadPageNo = 0;
7 s2 l+ O, g0 S7 c3 G
6 u( i9 P- G( x8 A/ m, Y- : L D* d- ]8 D
- /* 检测串行Flash OK */
- S5 M0 b- g- |) B6 l; | - printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);) _ G {& U `
- printf(" 容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);
7 X( q, B5 f7 z7 t E
( H$ i' m) e! H/ [8 g" N# @- sfDispMenu(); /* 打印命令提示 */
- g: @4 P* U1 @0 K2 l: r7 c6 N
5 h# N& y6 d( y6 J, _7 |3 n. |- bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */
) l$ {' P8 J% S& I5 F, P) z; E
0 Z; j, w5 U& \+ j1 a- while(1)( o/ _- ~3 T5 |
- {
; f- V8 C# N$ V2 r - bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
3 u* s0 B- q- i0 W0 B9 j8 T* j! @ - 1 T b( G9 |3 B2 ]2 ?
- /* 判断定时器超时时间 */* P" M0 Z0 D% o* `
- if (bsp_CheckTimer(0))
}) v3 b: S3 C( p/ ~9 g. x& P2 `- [ - {6 ~0 j; W( X8 \" a# a
- /* 每隔100ms 进来一次 */ : e9 y, x, Y! C/ L
- bsp_LedToggle(2);
9 b$ n0 q8 g: w, G2 g( s/ D& O - }
" M) b2 @ c4 n' ~4 i - + U5 r: B5 l0 L1 p
- if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */. W) S7 F8 M; o1 N" Y' f
- {4 l N, m, n) Q' ^5 }) N
- switch (cmd)8 [; y1 S0 C6 e
- {$ Z# M" |4 r R7 U* D" ~2 b
- case '1':1 _' X& t8 r1 q8 V4 L+ V8 s9 C9 t, f. p g
- printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
7 f+ M _2 E! r5 L - sfReadTest(); /* 读串行Flash数据,并打印出来数据内容 */
- W8 T0 ~) J. l5 W - break;
* l4 `) N' j. c
8 I8 R$ \: R* E- case '2':
3 J. D z7 d7 t5 `' C - printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);) p/ C* J X1 x% N2 ?
- sfWriteTest(); /* 写串行Flash数据,并打印写入速度 */! r/ L" E1 u; `- R$ Y6 B. M- t
- break;+ i6 H( O) w4 s3 \* m. J G
# e- |8 W5 m7 W% q5 I6 q6 P- case '3':
' D$ M0 s }+ Z2 v" h1 S - printf("\r\n【3 - 擦除整个串行Flash】\r\n");7 W1 X; N# o' p0 j; e) H' Z
- printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");
$ F. ?: Q: x) B) N v1 Z& R$ L - sfErase(); /* 擦除串行Flash数据,实际上就是写入全0xFF */' P+ \5 f1 C2 p, p
- break;
S/ h" z# M- ~: g8 Q
& S2 J3 h8 t; x- X- case '4':1 U6 t \ Z$ H8 G ]6 r/ z! F
- printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
4 j' P: ^3 T4 k1 J) ~ - printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
, U& O& M4 U0 G9 m. X - sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */, }( I/ t- p/ \* i3 R) B
- break;
7 n1 H0 P% _" E s! m* v, e
) n \& j7 g* k- case '5':
7 M% h% x( E; v! C8 n - printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));$ x( k E) a! d+ }8 D, M
- sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
2 \ ~- ` T5 [/ v0 M- k/ E G - break;
' F; t" E4 _# x# C
' T9 E. J+ \' W2 J- case 'z':
5 \/ q# [6 K9 a8 z3 L' k - case 'Z': /* 读取前1K */) l: V, Q2 g" @# D# a5 @* b0 u, v( L& U
- if (uiReadPageNo > 0)
7 L9 o5 {6 t9 \; K8 e - {
, M1 M' K# N5 r0 a/ f - uiReadPageNo--;0 X8 e3 J6 `2 J8 X! r! a; A3 O
- }' ?0 h, X3 W) Y
- else( h7 h$ O- |$ [. }# q, a9 T
- {
. b: W1 g# m5 i& r5 E - printf("已经是最前\r\n");; ]% t, I3 @6 L; R
- }
* x s% I: y! X - sfViewData(uiReadPageNo * 1024);
5 S9 p4 H2 f5 Y6 `/ S$ m8 [4 W - break;
/ `" U4 E9 b; B; x+ M! i/ ~ - & P5 e8 S: I( X/ @9 s6 j, }+ w
- case 'x':
4 @% A5 F: c; U" D1 ^8 T - case 'X': /* 读取后1K */
# t( v& \- s+ Z! Y1 x - if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
) p' {: s9 P5 k; v5 f" G - {0 l. f$ O5 G% p2 H; [) W8 I' t
- uiReadPageNo++;9 o% l' Z9 Q; r: B' X
- }7 k( n0 \/ `+ ~, f; X6 V
- else- d7 `; s/ t l5 s* x# h! R
- {
2 u* W( J' c6 X - printf("已经是最后\r\n");
6 @% _# u) m/ H& k - }
/ u5 `; f8 n+ L8 c! m% ^: e* r - sfViewData(uiReadPageNo * 1024);
3 I' k2 m; O3 Y# R, O! _/ S - break;9 U, X8 k9 X- S* Q3 _# V' Y" W( y) C
/ a4 y6 M$ A) ?" Q" o5 Y- default:
( Q$ X. j+ o, m" a, J7 `4 V5 E - sfDispMenu(); /* 无效命令,重新打印命令提示 */. f( w+ w4 _- O, e6 `0 h' V
- break;) d" E; @, |1 J& g% N
9 t0 }; l8 R# r% |$ B/ b- }
/ N; w; d7 H: M- [ - }& `, \: p3 N6 ~" P
- }
6 v, N2 D9 p+ g. H& \ ~' H - }
复制代码
, h9 D. n: e# e4 L% U73.10 实验例程说明(IAR)5 U$ }8 [+ f: M* `
配套例子:
1 b/ ^- ^, |8 V
5 X& l0 t8 Z9 A: K+ h4 NV7-028_串行SPI Flash W25QXX读写例程(查询方式 V1.1) G$ Z- U9 k8 X3 y% p# T
. E& d y& r5 p6 m/ {V7-050_串行SPI Flash W25QXX读写例程(DMA方式)) I, G( e$ o* w( u5 {
) r) _; V Z5 y, n2 |
V7-051_串行SPI Flash W25QXX读写例程(中断方式)
6 F4 U7 E% u, e) T
S! g0 @, i( I( ~$ v N, m& v/ l实验目的:- @: R* Z) j$ U/ S: _
2 O5 n) y" D2 }8 ]/ I( Z9 b& A学习SPI Flash的读写实现,支持查询,中断和DMA方式。
8 F" o( W/ I8 h7 H! g
8 `( z! v# Y. P( X6 f6 n实验操作:8 d; d/ _, E8 N, k6 p. L( a
2 }) T. W) @5 M" m支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可
7 w( f1 y t9 g0 L2 @. `& f. X/ hprintf("请选择操作命令:\r\n");! y5 ?# T- L) P- U8 e
printf("【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
p7 V2 w, C. W* ?% Oprintf("【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);
`. I5 P7 t) S; @: @printf("【3 - 擦除整个串行Flash】\r\n");5 p2 p5 ~0 T: I: n. `, G
printf("【4 - 写整个串行Flash, 全0x55】\r\n");0 B: [0 `' V: i1 f, }- I/ ^8 o n7 C. a
printf("【5 - 读整个串行Flash, 测试读速度】\r\n");9 L% f" F" \' F: b' ?
printf("【Z - 读取前1K,地址自动减少】\r\n");( \! h) h- [" x# I5 q# _9 Q2 f
printf("【X - 读取后1K,地址自动增加】\r\n");- t9 T2 r' p! \6 `
printf("其他任意键 - 显示命令提示\r\n");
, Z! \2 v6 ]! }; A8 ]
4 f- i; G6 p% ]( b; [+ q8 Q9 k上电后串口打印的信息:6 E9 S1 J1 N$ k& t8 Q
. E3 E/ E1 V6 v4 M: R" f
波特率 115200,数据位 8,奇偶校验位无,停止位 1。
# x/ ]4 K6 [8 H/ \5 w9 v2 a" x5 k7 ` j4 r
; u7 d. F/ S$ e& Y' V
, A( K% K2 H5 ]7 Z/ C" y/ V8 k% [
程序设计:
7 X$ t/ l$ U6 t! ?% J4 `! p
6 _' t- Q2 o7 S: j. ?1 k3 o. q; F) T 系统栈大小分配:
2 N: a# y" Z J" M
+ y2 D. a, Q8 ]$ ?( g) a7 e+ P4 ^, T2 L6 k
5 J% U# M. o! I, ?/ Q RAM空间用的DTCM:
) t- N- ~' {, V3 r: Z5 g; Q3 K" D/ Z1 w. h
! M4 M% P+ \* f
3 e# r2 |! o: Q! S
硬件外设初始化. z/ w) U! X+ y& `2 w
+ X5 k6 b2 T3 [7 `( j2 e4 }5 x硬件外设的初始化是在 bsp.c 文件实现:) K4 ]! ^! M2 B. n, w F7 \
. A; L8 S6 ?, p0 K4 h7 c- O8 I- /*
, v! @- X1 X) X3 s0 w - *********************************************************************************************************
. z$ @9 ]+ c) S+ o8 u* b - * 函 数 名: bsp_Init
$ A5 L2 p' a$ _* g* P8 Q2 r - * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次
' Q! f9 [ b6 k& u( O. r7 V - * 形 参:无
( T- o+ \9 y& z& Q) p$ `6 i - * 返 回 值: 无
9 J6 d% [7 B& W) x$ u - *********************************************************************************************************# z, a7 S7 L$ B1 z4 b
- */4 c7 g" [) i/ u o3 D j) @3 D
- void bsp_Init(void)* J! B) L, a3 L) X4 T8 ]
- {
, F2 e. ~* Y* Z8 {* S# T - /* 配置MPU */
8 | \2 ?$ q( k6 _$ C: ]4 } - MPU_Config();' l u2 Y2 F( Z- n% z" M4 M! I
, S" X2 Z; c- T0 @) k$ C, Z: I- /* 使能L1 Cache */5 P( _/ |7 n6 u! S
- CPU_CACHE_Enable();9 q( g, _% B: Z$ b8 b% b
- 1 W2 e+ m1 a: p: \
- /* 4 I0 r: X4 M: Q( X+ d. J
- STM32H7xx HAL 库初始化,此时系统用的还是H7自带的64MHz,HSI时钟:: g# y; }& z- f' c8 I9 y
- - 调用函数HAL_InitTick,初始化滴答时钟中断1ms。& T# o9 G! S, ]4 x5 a" |( `
- - 设置NVIV优先级分组为4。) ]2 ]$ `$ r Z* D! M
- */
# {2 R( a* h' P9 @# k - HAL_Init();
! N4 I* v& { x* p3 k8 w, ?- @ - ( e) U3 [5 \! f0 ~% l
- /*
}, x) N( V0 v7 n - 配置系统时钟到400MHz
1 i$ d9 k1 d6 U; v8 q/ [ - - 切换使用HSE。* y, w! p+ |4 k+ S8 ?
- - 此函数会更新全局变量SystemCoreClock,并重新配置HAL_InitTick。/ f# i! C; l& |) K$ n6 @
- */! H+ g) B# }" w1 P7 w$ G
- SystemClock_Config();$ e1 d/ c+ O& E+ B% G* B
- ! f0 @" M% R, k# T3 N
- /*
5 ?/ F. v# N) K/ o/ H - Event Recorder:
2 m; s5 y Z+ o; |/ Q T& V - - 可用于代码执行时间测量,MDK5.25及其以上版本才支持,IAR不支持。
' ~3 F6 m% m, e( b' o/ H - - 默认不开启,如果要使能此选项,务必看V7开发板用户手册第xx章3 D. Z9 Z. S5 `7 l( v& h
- */ ) r, I( L5 Q# z6 F. L, [
- #if Enable_EventRecorder == 1 " ]6 Y8 M U' L4 X
- /* 初始化EventRecorder并开启 */
* B3 B2 n8 T8 U6 d4 @$ }" e1 _ - EventRecorderInitialize(EventRecordAll, 1U);$ ~, H: D; r, a6 l# |8 z+ W
- EventRecorderStart();
6 L3 t d2 f H - #endif) a) C7 W2 l! d4 L3 y5 x
- 2 w4 l7 U& B! w4 Z6 W: Q
- bsp_InitDWT(); /* 初始化DWT时钟周期计数器 */ ! g7 N/ d# {# o2 P1 H1 e( |8 ~
- bsp_InitKey(); /* 按键初始化,要放在滴答定时器之前,因为按钮检测是通过滴答定时器扫描 */
2 b, i" _3 H5 X* z4 Y9 I - bsp_InitTimer(); /* 初始化滴答定时器 */" J4 t5 H2 P: H' k- ]% }
- bsp_InitLPUart(); /* 初始化串口 */
/ d0 Y7 h* R! ]; | - bsp_InitExtIO(); /* 初始化FMC总线74HC574扩展IO. 必须在 bsp_InitLed()前执行 */
; X5 }) o% ^6 _ P- Z. J - bsp_InitLed(); /* 初始化LED */ & y! t" J4 v, W* i
- bsp_InitExtSDRAM(); /* 初始化SDRAM */
( S0 |( y' j" \- G* f: j1 | - 3 q) z8 s* q+ u) z; s, F
- /* 针对不同的应用程序,添加需要的底层驱动模块初始化函数 *// p/ y! ?1 w9 ]! L1 Z' ~) W& R
- bsp_InitSPIBus(); /* 配置SPI总线 */
" U# W X! c, A$ m$ T) Z y# g - bsp_InitSFlash(); /* 初始化SPI 串行Flash */
: Y5 F# X/ e. N# {! j: t - }
复制代码
' I% w; x1 Y/ c* q MPU配置和Cache配置:3 |- v# I- z5 F0 d; a) u
, |& @/ t o& c9 {8 H; g% \2 t
数据Cache和指令Cache都开启。配置了AXI SRAM区(本例子未用到AXI SRAM)和FMC的扩展IO区。% B- N, K& `$ i2 m/ A
/ x7 t5 z0 p' l% _- /*+ N: E' S$ k9 F4 h U9 V
- *********************************************************************************************************
: d* O0 e0 U4 `1 g - * 函 数 名: MPU_Config" ]3 R1 }3 @; _5 G4 z
- * 功能说明: 配置MPU7 J6 U0 d0 ~9 ?3 o ^
- * 形 参: 无
/ s% R5 C; F; P( N4 u - * 返 回 值: 无
% y* x1 H4 r L9 W* P - *********************************************************************************************************1 q- z" [" _1 v% J
- */. P# h6 o9 d8 s8 h; Y" |4 g
- static void MPU_Config( void )
* `/ y; J! f t& k' S - {
. }! B$ `# }8 T; O, @% E5 Q) C. K - MPU_Region_InitTypeDef MPU_InitStruct;- r% Q/ w/ o: q5 H9 c7 L/ T' r4 @ x
8 Y1 Q+ l8 `: F6 M% n& ^- /* 禁止 MPU */# w$ Y* {2 S3 c$ T* P+ C z
- HAL_MPU_Disable();& e1 F/ _' R5 k& f' @' ~
1 w3 k- o7 {6 X- U- /* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate */8 K5 f3 t! }# U
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
9 j! N% r5 d$ x2 W' x - MPU_InitStruct.BaseAddress = 0x24000000;( ^" h" z2 C2 V5 e1 u, o
- MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;) P& D7 o# }0 A" D1 u
- MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% j8 `, ?$ y1 q1 l* Q3 j4 _( C! i
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;6 A: S* R, N9 T" ^+ d* @
- MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;* ~+ b U1 S( x, l5 `
- MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;5 L' E+ ]) P' s8 w( [* q
- MPU_InitStruct.Number = MPU_REGION_NUMBER0;/ N, h' c+ z6 O7 k
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
% `* _ |1 M/ k I$ @ - MPU_InitStruct.SubRegionDisable = 0x00;- y/ s/ V5 r' ]) Q$ x7 `
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;# w% @3 A9 ]; H P+ ~7 @
- ( b& i1 _) x8 q3 i6 F
- HAL_MPU_ConfigRegion(&MPU_InitStruct);7 m& U0 ]4 m4 D
- 7 g& I; \& I/ R' I
- " W& K8 T* R+ [% W8 T
- /* 配置FMC扩展IO的MPU属性为Device或者Strongly Ordered */! B" _. j0 r; Y
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;
) j. v4 ^1 o- x - MPU_InitStruct.BaseAddress = 0x60000000;
: m, n5 C4 c5 I: Z4 C1 m/ R6 w5 ] - MPU_InitStruct.Size = ARM_MPU_REGION_SIZE_64KB;
8 }1 T6 n- ^5 Y4 C0 D$ Z5 Y* l - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;; Y4 n0 E( T6 W3 E# }
- MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;" I5 V: d" W0 H y5 V" E" s
- MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
4 T# q0 E& i7 Q" V/ K - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
; J. ^! }. q; I" `" h" b - MPU_InitStruct.Number = MPU_REGION_NUMBER1;- C6 R$ j1 r+ d% Q. j
- MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
) B* Q, j: K" B0 H3 i- x- F - MPU_InitStruct.SubRegionDisable = 0x00; ~* X( n8 f+ ~1 F! p
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;* f/ L7 p+ U/ B9 b" J0 `# Y8 A8 O
% @! ^ I! k1 [- HAL_MPU_ConfigRegion(&MPU_InitStruct);
+ ]/ I* a1 o2 O" y7 J - , @+ L* l3 n3 X. A
- /* 配置SRAM4的MPU属性为Non-cacheable */8 ? _5 U# @# d1 ?3 r9 W8 M
- MPU_InitStruct.Enable = MPU_REGION_ENABLE;% K/ e$ e+ N. {0 O0 W
- MPU_InitStruct.BaseAddress = 0x38000000;. S* B Y# h) [0 ]+ W4 j; V
- MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
& Q' g, l5 \4 v ~6 w- q0 p% V - MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;% J7 l7 G/ X( {
- MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
_0 @% h9 d y: G: s ~, e7 {: K5 w - MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
; A! K0 g# z' o5 r6 ? - MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;& G8 l7 s/ t# L& q2 j) _
- MPU_InitStruct.Number = MPU_REGION_NUMBER2;
- ?3 r8 s- l! {( N9 G2 s - MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;: F& c; u0 \+ v( L& b( F) [
- MPU_InitStruct.SubRegionDisable = 0x00;! f9 R6 \' @3 T/ ?. Y/ R. z
- MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
: H! ~# Y& G! {% g
" L' n; h5 W8 N9 c I* g& U/ e- HAL_MPU_ConfigRegion(&MPU_InitStruct);" O6 F( O& I0 E) g' F; R P& J
- # u# h3 R/ Q" `- L
- /*使能 MPU */
6 n# ]# L+ t9 v" Z" w1 O r# Z+ x/ M) Q - HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
0 r$ K& [ e* i - }5 o" P9 c0 q4 ^
, j* C2 g. K, U! S2 [7 a- /*) a& E+ w; N. q1 X* U
- *********************************************************************************************************
8 P, b& ?. G2 k- I - * 函 数 名: CPU_CACHE_Enable( J4 J5 J$ d" e# o1 E: ~& ?' @/ C
- * 功能说明: 使能L1 Cache
4 p5 j, m1 h# j - * 形 参: 无' [6 M6 H# v, M, U7 E$ O$ Y
- * 返 回 值: 无- t: r: {* ~/ | f
- *********************************************************************************************************
. k) T' f) O% T A1 P+ o - */, m' G" R) P7 y$ Y9 H
- static void CPU_CACHE_Enable(void)3 F. q0 y" ]5 b$ P, V
- {
$ o% _# m o4 M - /* 使能 I-Cache */
. p* Q6 D( }8 F0 ^# c- i0 q4 f; F$ s - SCB_EnableICache();: B) x- Z6 d: N5 i) B a9 @
: ~" O4 U: a: X8 w/ r( a- /* 使能 D-Cache */
1 G. o0 ?3 `* U# _ - SCB_EnableDCache();' ^( M6 ~) s! x& v4 W- K, \' }* r
- }5 D+ h1 w# N* C! s/ ~
复制代码
6 f4 c: \% n! g: W7 o# Y; p5 Z6 V; q* S
每10ms调用一次按键处理:. a+ y' P1 E/ P# ~& H O: m3 Y1 B: ~
8 t$ f/ l' [ l$ ` j9 j) s: i+ _# v
按键处理是在滴答定时器中断里面实现,每10ms执行一次检测。/ T/ I. `; u( G' H6 [, J* v! S6 R% ]
8 @, R/ _; x& ^& t# P" N: O
- /*
* o- X# n5 _- a: D' t* Q( }( H - *********************************************************************************************************
- @7 \( U. ^, ? c - * 函 数 名: bsp_RunPer10ms
+ ^1 x& D# Y p( r. |2 g2 S- ^* K - * 功能说明: 该函数每隔10ms被Systick中断调用1次。详见 bsp_timer.c的定时中断服务程序。一些处理时间要求
# s/ C7 w7 x: G* A2 h) l' ~$ R. N9 |$ l% [8 ^ - * 不严格的任务可以放在此函数。比如:按键扫描、蜂鸣器鸣叫控制等。
) e: s7 k$ ~7 W; R - * 形 参: 无
& r! H# `7 ~* B - * 返 回 值: 无
0 l8 C& D6 B/ d. B1 l6 t7 f3 N - *********************************************************************************************************
/ m2 h0 d+ ]8 r) t; {% ?/ k+ ` - */
0 V- V7 f: P! i+ x. y - void bsp_RunPer10ms(void)
2 r7 q' f( @7 Z& x6 s" N3 z - {
3 \' s" ~! w# F# s! ~ - bsp_KeyScan10ms();9 M- w* O2 n' X( a; f( ^
- }
复制代码
$ c( e% y7 X& ~, W 主功能:6 `! }2 ]0 u H
6 Z4 _+ T4 W) u: ~9 h0 k& ~主程序实现如下操作:
( `- r4 L- w+ P v2 p+ b
1 ?0 Z0 A% z5 [# } 启动一个自动重装软件定时器,每100ms翻转一次LED2。5 \! W4 J, s, B: N; k
支持以下7个功能,用户通过电脑端串口软件发送命令给开发板即可9 Z9 ]$ B* h0 |5 {- y; D
请选择操作命令:
( U/ X7 j3 k0 f1 L7 K- X7 ] 1 - 读串行Flash6 K; k$ Z0 H1 z2 W' K
2 - 写串行Flash$ R: Z0 e7 J( b$ d( }
3 - 擦除整个串行Flash6 [1 K6 B7 {6 q: b/ X
4 - 写整个串行Flash, f. m% |% D7 X- ]8 ^" |9 I
5 - 读整个串行Flash
. q* z. z; c: ~ Z - 读取前1K
1 w. _4 i7 T- q5 Z3 ~% Y+ U# ^0 W( I X - 读取后1K- g( D2 b* [8 z
- /*6 N2 A' K! q* w$ a
- *********************************************************************************************************
; y" j& f9 j4 U1 _: |6 U/ D - * 函 数 名: DemoSpiFlash- e" n$ `6 F7 S+ A
- * 功能说明: 串行EEPROM读写例程+ _3 x* s" i1 p9 R5 u+ H2 t+ {
- * 形 参:无1 J! p% N; u- r1 P$ J
- * 返 回 值: 无6 p* f- @0 I# F: t7 k9 `
- *********************************************************************************************************
0 M# x8 n& j3 |9 W& p - */
* G$ R0 a& _5 p+ M+ m, O2 F2 T - void DemoSpiFlash(void)3 a* K w- Z0 _! @1 _- _. I
- {
E7 n P% Z$ H& c - uint8_t cmd;% K% u( T% r0 u# I- a
- uint32_t uiReadPageNo = 0; k+ v9 G2 H) l8 F3 V* ] X5 `
, L9 j) v" M7 G" ^* t5 ?- 9 k; `( B# l1 f: ~7 W
- /* 检测串行Flash OK */" ?/ v: B% C2 I! b. |
- printf("检测到串行Flash, ID = %08X, 型号: %s \r\n", g_tSF.ChipID , g_tSF.ChipName);* t2 D/ L' B U% k3 ~& m
- printf(" 容量 : %dM字节, 扇区大小 : %d字节\r\n", g_tSF.TotalSize/(1024*1024), g_tSF.SectorSize);& E9 n7 h; _% T! c
7 K( d7 r$ M3 T* e" [. v/ s- sfDispMenu(); /* 打印命令提示 */
* r* U+ j# f# }) O2 \ n
+ k& p4 F' Z8 Q7 W( B# T- bsp_StartAutoTimer(0, 100); /* 启动1个100ms的自动重装的定时器 */( L m/ u y% O8 m; R: G* m
- " b& r; u, J2 |' H2 `
- while(1); P( ^' i1 s" G& m
- {: W3 G7 \/ t7 [$ L" D5 M
- bsp_Idle(); /* 这个函数在bsp.c文件。用户可以修改这个函数实现CPU休眠和喂狗 */
2 |( U+ K: g" s' g6 s5 b+ f2 C3 S - ! h. D& \- g7 f
- /* 判断定时器超时时间 */- z; t* }% X. m
- if (bsp_CheckTimer(0)) ! m) C6 a7 J$ c! }2 f
- {
) D# S6 o/ f. h4 g - /* 每隔100ms 进来一次 */
6 o* _# h5 Z- Z6 ]# k v - bsp_LedToggle(2);8 y: d& y8 ?9 n* z1 Z: i
- }
/ B, Y0 x6 `0 F2 ]( X. Z- q9 F - 8 L0 w( r3 f) z! M7 ~2 F
- if (comGetChar(COM1, &cmd)) /* 从串口读入一个字符(非阻塞方式) */
8 w" i9 p7 o8 @ - {/ N% ^+ m% e3 f2 R
- switch (cmd)
3 v0 [% {% e" N8 D) N. f A - {$ `# L+ x, o" T* I% X" v5 [) S
- case '1':5 u* _5 ], [4 q1 B
- printf("\r\n【1 - 读串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);/ Q8 O& m5 ~" }0 T
- sfReadTest(); /* 读串行Flash数据,并打印出来数据内容 */
~2 \3 g; H3 j3 H- R5 B - break;
+ a, e0 V( Z4 F6 h" \
1 ^1 g. h: w5 {6 ^$ \6 ]9 F- case '2':; B. k8 @& e, g5 D9 f
- printf("\r\n【2 - 写串行Flash, 地址:0x%X,长度:%d字节】\r\n", TEST_ADDR, TEST_SIZE);2 N9 [9 M# v( x: C3 m+ B
- sfWriteTest(); /* 写串行Flash数据,并打印写入速度 */1 H$ v5 ?3 J. A. H5 ~+ z: d
- break;0 ^* z. a9 {, r5 V
% l1 S0 _, S0 e4 O2 d- case '3':$ F1 w- H# J! G3 e3 ?# x
- printf("\r\n【3 - 擦除整个串行Flash】\r\n");
; B2 U1 G8 o( {. a/ f& I - printf("整个Flash擦除完毕大概需要20秒左右,请耐心等待");" u) ~' h' }: d0 q0 ]
- sfErase(); /* 擦除串行Flash数据,实际上就是写入全0xFF */& J! H( t2 M" e" D" H. ?7 K9 M
- break;# ?7 V9 R% N6 x$ }; f
* E1 n& h, g) A% e5 S! Z# [2 L" W- case '4':+ d3 \: E" e- u; Y8 K$ f7 ]
- printf("\r\n【4 - 写整个串行Flash, 全0x55】\r\n");
4 P" z& E; ]( ^" w. F - printf("整个Flash写入完毕大概需要20秒左右,请耐心等待");
4 _' T, Q7 z0 v3 S$ Z - sfWriteAll(0x55);/* 擦除串行Flash数据,实际上就是写入全0xFF */* r' c' Y% H8 A- R5 L, D* y- F; ~
- break;" z- |( j7 f' w6 p) ?
- . y, O. o7 m2 b% I
- case '5':
( _2 M5 V; {' g" t! A0 a) ], _- Y - printf("\r\n【5 - 读整个串行Flash, %dM字节】\r\n", g_tSF.TotalSize/(1024*1024));7 E$ k. p+ i! I* e: Q( e
- sfTestReadSpeed(); /* 读整个串行Flash数据,测试速度 */
! Z+ h! i- P1 |5 z7 W2 o7 ~# q+ r - break;
2 y# R! x) T6 [6 h4 _1 ^% N - 5 K$ @+ g% y" F, ~
- case 'z':0 T; l- R, `' ?+ e+ ]
- case 'Z': /* 读取前1K */
. x3 L4 \" o/ d - if (uiReadPageNo > 0)
% S3 C1 R! b+ ~3 w3 B1 T - {
( q- p) T j8 u$ o( x - uiReadPageNo--;) I5 `& ] ~2 z/ h
- }
5 E$ B( P! X* U0 x( K2 M2 _& p - else; q. {' n0 z- A$ d8 T
- {7 _, A5 {2 V3 `/ e7 V q
- printf("已经是最前\r\n");* L) x% Z+ P& R. V$ A
- }- O% s+ M$ P0 L9 X
- sfViewData(uiReadPageNo * 1024);
9 H# N U& a3 z C& g7 y! ~ - break;
6 [% i+ R3 ?; v' S" t
4 r% j9 p- Q. G- case 'x':
/ [7 `* L) X) Y, a9 O - case 'X': /* 读取后1K */
' a0 L. b. [ B - if (uiReadPageNo < g_tSF.TotalSize / 1024 - 1)
2 {' W$ M5 o) |$ }% Y, R - {
/ V: R, `) L! U7 t - uiReadPageNo++;) V$ k$ V6 H0 p( t( V
- }: y5 J F1 g) O$ k+ e, C1 }/ K" K; P
- else
6 ?: |; w9 _: y1 v - {
3 Q" ?3 D3 U: R L, J* j - printf("已经是最后\r\n");
$ v }1 i% q/ K' z9 ?+ \3 C - }
; g4 R3 V {) C/ Q# d; J+ j - sfViewData(uiReadPageNo * 1024);
+ A( E1 ~2 K m1 I e - break;
2 j) z3 ~% C5 D/ v - % V$ b6 f4 Q+ Z# P3 J! |
- default:
7 N7 l# _. H: ^! [ - sfDispMenu(); /* 无效命令,重新打印命令提示 */( M5 A# n# D: H& y- c. m" S
- break;
: j+ L6 z. H2 C2 X" |) ^
Y+ h& E/ O' F7 V c* l- M+ d- }
6 W5 l! x: e* K( q+ h* D - }
3 b% `; u- W9 [0 s: |7 O( w - }
7 x" z2 R1 Y: l& v9 [( v1 t7 l/ h6 n, t - }
复制代码 N* c. l" d. d4 E
73.11 总结
* L- t! f7 _7 O+ X# n' d R本章节就为大家讲解这么多,实际应用中根据需要选择DMA,中断和查询方式。" C, e" U) T n Z9 _: s6 ?
2 t5 @( y' ~+ Q
" l* h! _3 T4 k$ }7 d# Z. N; G
+ w, l1 G$ Y% b |