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