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