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