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