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