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