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