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