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