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