80.1 初学者重要提示7 O) B: }( k- [+ k! S# p
QSPI Flash下载算法文件直接采用HAL库制作,方便大家自己修改。
6 B7 i! k! a8 c( e3 V, E80.2 MDK下载算法基础知识" p; G& u0 ]# U# a" b" s
Flash编程算法是一种用于擦除应用程序或将应用程序下载到Flash的程序代码。MDK本身支持的各种器件都自带下载算法,存放在MDK各种器件的软件包里面,以STM32H7为例,算法存放在\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软件包版本不同,数值2.6.0不同),但不支持的需要我们自己制作,本章教程为此而生。1 i" a' g9 P! J( m0 f; r
8 P. Q: w) m6 l7 g e$ C
80.2.1 程序能够通过下载算法下载到芯片的核心思想
+ n' \+ g6 j' V! T, ?认识到这点很重要:通过MDK创建一批与地址信息无关的函数,实现的功能主要有初始化,擦除,编程,读取,校验等,然后MDK调试下载阶段,会将算法文件加载到芯片的内部RAM里面(加载地址可以通过MDK设置),然后MDK通过与这个算法文件的交互,实现程序下载,调试阶段数据读取等操作。: k. h. }% Y! E1 E& @. b& _
9 a9 {& z* E1 M# p80.2.2 算法程序中擦除操作执行流程
: Z- h2 [: h5 e0 R擦除操作大致流程:
3 @* Q- [5 d2 P0 M# f. R0 P5 B# x3 D7 F; m/ y# g. y; I7 \: }) ?
' [# R9 E- A" K2 p: S
1 F" i! H* x; E" o 加载算法到芯片RAM。3 p6 ]7 P& }$ h: x7 H; y2 R
执行初始化函数Init。
0 o4 R/ i: h# C, Y' V7 G& S 执行擦除操作,根据用户的MDK配置,这里可以选择整个芯片擦除或者扇区擦除。
/ h7 [0 b' e1 r ^+ a% s" W3 T 执行Uinit函数。; A# C7 b7 P5 w0 T; |( U6 h( d) I
操作完毕。5 e, r1 [# ~+ S% @8 P
" H7 M2 |2 [# \/ s80.2.3 算法程序中编程操作执行流程2 m0 t4 R7 B' u" W: e
编程操作大致流程:$ s; n% E( i' L4 G5 A
9 k: e9 y; W+ }1 A4 @* c! G/ v$ E
0 t3 d/ ?3 Y' j$ M9 N( H+ Y$ _2 z& a6 Z- ~, P+ l4 C9 M
针对MDK生成的axf可执行文件做Init初始化,这个axf文件是指的大家自己创建应用程序生成的。
% _: _% a) s3 P, S 查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
% O* ^/ [4 \# i+ U 加载算法到RAM。
$ `1 w5 ?2 R7 L$ r! P4 A% j; X0 Y 执行Init函数。' V8 \% i$ W, @9 _- B
加载用户到RAM缓冲。
( m& b% ~8 T0 t& R, a. s' V, U 执行Program Page页编程函数。& @+ e0 f" g9 C" V1 Q
执行Uninit函数。9 f! D+ f" v5 I$ `" U$ g* }
操作完毕。5 j3 A& D7 B4 _" p# P
2 d9 A% A) J! l, a+ a80.2.4 算法程序中校验操作执行流程, K( Z; n* u: B. ]$ v
校验操作大致流程:
" C5 ]: j. u8 u# ?* x) b2 Y1 D6 \7 n, I3 z$ X; X( ^: S
0 ^5 H( t8 w! n) T* B/ H4 a( K) P; k8 D* S
校验要用到MDK生成的axf可执行文件。校验就是axf文件中下载到芯片的程序和实际下载的程序读出来做比较。
/ O- \7 R+ [3 ^8 J8 N8 n- F 查看Flash算法是否在FLM文件。如果没有在,操作失败。如果在:
1 }' f7 n! `: ?- [+ ], ^( b( o 加载算法到RAM。
* Y! L0 M: u. e9 f( e+ q2 _ 执行Init函数。
! j$ }/ Y/ }3 k# q# ~ 查看校验算法是否存在
0 ^; [/ f7 `# u+ E 如果有,加载应用程序到RAM并执行校验。. Z+ L9 N* y5 Z6 U$ Y5 \7 Z7 f! g& E
如果没有,计算CRC,将芯片中读取出来的数据和RAM中加载应用计算输出的CRC值做比较。5 f+ z9 V" B# k- h1 I' T5 @2 i
执行Uninit函数。1 s' |7 N) x! t9 m: E
替换BKPT(BreakPoint断点指令)为 B. 死循环指令。 l1 F* E8 k, ~( d
执行RecoverySupportStop,恢复支持停止。
* X: {- [ y; H/ I; n* F 执行DebugCoreStop,调试内核停止。
+ Y1 U5 F8 t' N" \ 运行应用:. z6 R" g1 a ]- I% h3 x) X
执行失败。
/ g! _' T! E% x" x0 \ 执行成功,再执行硬件复位。) U9 d/ y ^1 `" }# p
操作完毕,停止调试端口。
( G# F6 D/ h8 K- m3 l# Q+ \; d0 P) t1 c" m
80.3 创建MDK下载算法通用流程( j+ d- w( O+ Q9 `; @( W1 A
下面是MDK给的一种大致操作流程,不限制必须采用这种方法,自己创建也可以的。, t3 r# j) n0 ?+ E! A4 |7 n
; s( b B9 E9 F80.3.1 第1步,使用MDK提供好的程序模板, x8 y. w- u! J4 _" T
位于路径:\Keil\ARM\Pack\ARM\CMSIS\version\Device\_Template_Flash。# R+ x( ~/ D" w$ [
) H0 Y# q' \/ c: f- s s
效果如下:
: @# }! x* `; m) }/ k' c# \$ N; M' Z2 J3 t7 e, q* F
/ Q/ @/ u+ L" {# ?6 Y
- m6 `6 E- \) |3 j* E& |6 h* v80.3.2 第2步,修改工程名( m# e7 _+ w* Y8 c
MDK提供的工程模板原始名字是NewDevice.uvprojx,大家可以根据自己的需要做修改。比如修改为MyDevice.uvprojx。
+ L: S+ e+ s, L0 d6 P# ?
- L% u3 D4 Q9 g8 I; K80.3.3 第3步,修改使用的器件
4 M* _# e# x3 ~# S3 U, X在MDK的Option选项里面设置使用的器件。
# F: M* Q( z& H/ A9 k8 v: B$ o) y" n. _6 ^, V; F
7 ~: Y8 ~- W. W8 ]0 E& x+ d
0 N1 W7 @; g' m1 @80.3.4 第4步,修改输出算法文件的名字( J3 [. V4 N% A! `9 R( m" p
这个名字是方便用户查看的,比如设置为stm32h7,那么输出的算法文件就是stm32h7.flm。
' k& Z& Y1 A _. h) U% ?, i, b3 ?" X! M0 k1 y9 Q$ F# [( Q
. A) w8 l+ s8 J" ?
9 n- w6 r6 M0 k注:MDK这里设置的名字与下面位置识别出来的算法名无关:0 n3 A0 G3 D( E% N: M
6 K5 T+ o" E( @' c6 e+ z
7 w' p2 @; n4 c, K- D
3 T. ` }' N6 D7 R2 J$ i+ B这个名字是在FlashDev.c里面定义的。
9 V% a8 O) R9 K7 E+ a& n5 k$ t( I/ j3 \
80.3.5 第5步,修改编程算法文件FlashPrg.c
! S) t1 W. `: n) }3 u# F模板工程里面仅提供了接口函数,内容需要用户自己填。
1 b* j, a, u3 p% [: w, m8 l7 Y; x9 R O
- /* $ r' s5 [- _* D! g. U: Q5 ^+ T
- Mandatory Flash Programming Functions (Called by FlashOS):- h, I4 X- w: u' k
- int Init (unsigned long adr, // Initialize Flash0 b2 b" [- m J: Q" k
- unsigned long clk,
) A4 u: j7 Z% I/ s! [' P5 C - unsigned long fnc);
9 p' _9 y7 t8 e, [/ F - int UnInit (unsigned long fnc); // De-initialize Flash1 b$ v% s! e7 m: D8 B
- int EraseSector (unsigned long adr); // Erase Sector Function$ m/ o! J/ |! e# `; X! p3 r
- int ProgramPage (unsigned long adr, // Program Page Function
`1 j/ e; J* N% P - unsigned long sz,1 j6 w6 w! g5 N7 X
- unsigned char *buf);
6 F* O* b4 e% Q6 n! e
" O3 Q" |8 B( g8 C7 a: x- Optional Flash Programming Functions (Called by FlashOS):
1 B/ W+ K) C: F - int BlankCheck (unsigned long adr, // Blank Check
. C8 `( L9 U1 p" V; C - unsigned long sz, S/ J0 f* j+ M/ V8 P: H/ o0 C1 \& N
- unsigned char pat);( E3 N* C- O7 O/ y
- int EraseChip (void); // Erase complete Device
% k" ^) l/ Y& E5 L$ c - unsigned long Verify (unsigned long adr, // Verify Function2 G d) G4 E5 d! l) a
- unsigned long sz,
! ]- B" U' Z3 Q* `4 `, M - unsigned char *buf);
0 G2 I- |, G4 R* R" d2 a& W# k - 5 b$ s& o" m# _% h
- - BlanckCheck is necessary if Flash space is not mapped into CPU memory space( S6 D0 a* z! |, N" q4 H2 {# N% e
- - Verify is necessary if Flash space is not mapped into CPU memory space) p4 {2 V, X- P9 q) _
- - if EraseChip is not provided than EraseSector for all sectors is called
7 H! V8 E' ^3 k. E* A3 n - */5 N1 e, o1 h% d; s2 s* u! Y- P
- # u2 [% T1 q* F. `1 [
- /*3 j* A: t* m( n: w8 C
- * Initialize Flash Programming Functions4 @1 P! u& L& H5 c& H+ T% T
- * Parameter: adr: Device Base Address" K: a0 S: v# Y1 g" W
- * clk: Clock Frequency (Hz)+ Q6 q* {# m4 z1 w" z, g
- * fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)5 A# V7 U" P P3 S7 [6 d% p
- * Return Value: 0 - OK, 1 - Failed* K# l( e( [ e( @, \9 s
- */% ?: ]5 b ~/ @. @6 [2 b4 [5 Z
- / @/ f2 `) k+ M) c f. L6 e
- int Init (unsigned long adr, unsigned long clk, unsigned long fnc) {
, w m) ~ W1 f
( a+ t ?5 h: J5 E5 n; x- /* Add your Code */
# h, Y* D% C& f1 o3 w/ Y. E - return (0); // Finished without Errors
" Q. D% s: x! I5 c" B$ L, | - }
5 d' K' x9 |) Z; F' f. I* ]* k
/ m7 |0 A a: Y3 D
- i% l7 L2 _1 g% |- /*
- t: a! u: V- R: g, Q - * De-Initialize Flash Programming Functions' N( |" M: N* y" `3 I
- * Parameter: fnc: Function Code (1 - Erase, 2 - Program, 3 - Verify)
; H1 X# K0 g& h* J! F - * Return Value: 0 - OK, 1 - Failed
8 J9 I2 C9 V& O8 {0 L - */7 H1 [. K9 H; i( x' x$ }% L6 ~$ K
9 }9 ^" K3 A/ y- int UnInit (unsigned long fnc) {
# T, |; n, X: y) j4 K# v - ; m7 n% t6 I, ~1 m# n
- /* Add your Code */. ]) f3 |% W4 X. i H# f
- return (0); // Finished without Errors- q0 x( r; D) i* X7 H( ^2 \# Z& o. N; Q' @
- }0 M! S* `2 U3 m; r! k8 C
- ' \: h/ J7 G3 v- u9 Z8 S% E
- $ U4 }# f; t- O! G
- /*
" ~' f. i- S5 k# C# p5 m0 E - * Erase complete Flash Memory& b9 t2 u4 J7 m, K) R9 X7 A5 O
- * Return Value: 0 - OK, 1 - Failed" f+ T/ U, e+ {- ]
- */8 m2 T! W- U2 S' q, V
- Y# R, L; f* M0 o- int EraseChip (void) {
, ^. y$ Z0 e }/ V- t
]) ]! A! h6 r. G6 Q F- /* Add your Code */
! m' Q4 T+ y# L/ l- n+ |9 c - return (0); // Finished without Errors2 j3 R% P5 H: b& Q |
- }' i. ~7 [7 Q1 y7 m! L3 W
* U7 m2 ^, E) `% }. V3 I! v( ~- + F: r0 C& X; T) Z2 x3 B* }; V
- /*
% Q# a: }% ]9 `: b, Z - * Erase Sector in Flash Memory! r! U6 |( E( T* j0 @8 F) C
- * Parameter: adr: Sector Address, ~9 Q1 i2 A5 i" J4 c! z* e
- * Return Value: 0 - OK, 1 - Failed
" b6 q/ A$ o, D2 f - */5 X- X( I' {, a/ s: D, F
$ P9 a! X+ w5 k$ x$ W9 C6 C- int EraseSector (unsigned long adr) {
& A, ?/ C; }7 D; U! \
: x$ C0 V f& H! T; K' ?- /* Add your Code */4 r- l* e% U5 A6 `
- return (0); // Finished without Errors* a2 ~( G, {1 n, N+ @: m& T; r, d
- }
/ n- W9 M2 W! y
8 e) L5 H/ G; g! r) w
4 y1 \2 C- n( z( w$ j- /*- T/ V5 Q3 F, A9 W: }2 i
- * Program Page in Flash Memory6 P/ h1 m4 Y! u! ^3 S
- * Parameter: adr: Page Start Address# I, y6 [2 L2 f" O% W
- * sz: Page Size: f% }& Q3 ^' o! Z3 A/ K
- * buf: Page Data
6 ], ^- @# d( R! K0 Y* j- v6 S - * Return Value: 0 - OK, 1 - Failed) m2 B6 N7 u( f
- */# f; C8 |8 f7 j: E0 L& K! ?
" J9 ?8 o# F* A- int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) {
. ?0 N/ ?5 ]; v C' d; g0 P - ]3 d. }& @2 [7 E7 N9 z$ \
- /* Add your Code */ b, F9 X1 W7 f) `& R% n- m/ Y, r
- return (0); // Finished without Errors/ p: H* V7 `- }
- }
复制代码 : R; Y8 Y/ p8 a- q! h1 K
80.3.6 第6步,修改配置文件FlashDev.c1 \; r8 ^ N3 D$ b" O
模板工程里面提供简单的配置说明:$ n; I# \, c# B c y ]
# J5 ?3 E5 G6 I- F; o4 N* _
- struct FlashDevice const FlashDevice = {- v' H3 M& l6 @! s4 O e
- FLASH_DRV_VERS, // Driver Version, do not modify!
r4 w, t, i$ o0 p - "New Device 256kB Flash", // Device Name - A+ I/ V+ w! b' |/ p
- ONCHIP, // Device Type0 b9 ]+ \( J6 B' z# }' S
- 0x00000000, // Device Start Address
- [* H1 N: k; W4 W0 y# L - 0x00040000, // Device Size in Bytes (256kB)6 x0 ]2 r+ p7 N# v0 T, g$ N$ p
- 1024, // Programming Page Size; M2 u4 `4 B8 V0 ]
- 0, // Reserved, must be 0% z6 s9 w4 F) H o
- 0xFF, // Initial Content of Erased Memory0 {5 r& H, D) ^! c! X
- 100, // Program Page Timeout 100 mSec4 M) R) V# v* S. N
- 3000, // Erase Sector Timeout 3000 mSec1 c* Y+ }' z7 a! V
5 F9 ]. I# f5 G% t( {& ~ W7 u- // Specify Size and Address of Sectors
, [( y4 }3 M% c- X - 0x002000, 0x000000, // Sector Size 8kB (8 Sectors)9 g3 e" I4 a' C& {( j
- 0x010000, 0x010000, // Sector Size 64kB (2 Sectors)
1 w( h! }) i6 F. C8 T8 U - 0x002000, 0x030000, // Sector Size 8kB (8 Sectors)
- }" h4 {) g4 n/ i& j' y! d* Q - SECTOR_END6 j( C1 y/ M# }! x
- };
复制代码 3 c( f( Y% A% u( M. Q
注:名字New Device 256kB Flash就是我们第4步所说的。MDK的Option选项里面会识别出这个名字。
" i" }% `' r7 t6 c, u W9 o9 ^$ W( |5 A$ {4 D9 R0 n6 L6 U
80.3.7 第7步,保证生成的算法文件中RO和RW段的独立性,即与地址无关) v# [) `2 w& m; c; [& v
C和汇编的配置都勾选上:1 B. Z- H- G2 M3 [
+ H! O6 J% k- n1 I
3 e( H6 v; B6 v/ g( J
% r( f+ Z: l, y+ q汇编:
+ o( y$ v2 G' z+ V3 C* ?5 Z: {, [$ m" E3 F% h6 v/ O9 j7 I4 y- v- \
0 @3 z# n7 F, o6 I w0 Z" h! I8 G) H+ L/ k
如果程序的所有只读段都与位置无关,则该程序为只读位置无关(ROPI, Read-only position independence)。ROPI段通常是位置无关代码(PIC,position-independent code),但可以是只读数据,也可以是PIC和只读数据的组合。选择“ ROPI”选项,可以避免用户不得不将代码加载到内存中的特定位置。这对于以下例程特别有用:9 ^! L! b% v' }7 E
% U8 O. B: v N5 S' j% ?1 _
(1)加载以响应运行事件。3 T3 p( Y: F, y7 f& c" ]0 S: D
- J/ r' d M V3 R* W(2)在不同情况下使用其他例程的不同组合加载到内存中。" x9 N* P3 r( s) d* {
9 A: Q$ x8 W# e/ Z7 ?4 I
(3)在执行期间映射到不同的地址。. J: y3 `: X; E6 Z \& V7 u! o& l. F
* w. [, h! u8 W/ t
使用Read-Write position independence同理,表示的可读可写数据段。
$ X& D, Z% w8 d' l [4 A0 @' [7 Q( }2 c0 g0 l9 D+ M
80.3.8 第8步,将程序可执行文件axf修改为flm格式
% {. L1 c4 D9 _5 L X通过下面的命令就可以将生成的axf可执行文件修改为flm。
& \9 ]+ o% l; X! U
2 M8 D9 b/ f9 G. B; D( E/ m* X/ O+ W+ a# [: _+ `. N, P8 }
9 b2 ^& P$ b! P80.3.9 第9步,分散加载设置
. u$ T6 p! C, M' W我们这里的分散加载文件直接使用MDK模板工程里提供好的即可,无需任何修改。
7 X+ E) s! O: @/ K9 ?5 ]. |( u0 ^6 r ?( M% I- O% ]9 }
# }3 [* ]3 K' y- k& H- g
9 P2 e' T; B" L/ J; \) G分散加载文件中的内容如下:& n8 ?& w2 Z, \/ M( F& e
8 o. x$ r6 C* B' J) o0 J+ E
- ; Linker Control File (scatter-loading)
% _; X: ~( e# ?4 Z: U2 Z9 | - ;
' @6 t: O- N7 Q$ t1 f
* _- C, \& i! b( K8 b- PRG 0 PI ; Programming Functions
4 S# w+ V& [8 W& H - {
! I X+ D' I2 W, [/ b. T8 M - PrgCode +0 ; Code
& K4 B8 X& {- _: @; ] h: C - {3 K3 w" |' k( O( k' T0 E/ W
- * (+RO)
# u- B7 G" i% d, N( v; Q! w, X - } _& M- g- H% Q* [1 c4 t
- PrgData +0 ; Data$ S1 b8 Z2 v4 |5 N x. C6 Y
- {
2 t4 t: ]' a6 {% F! f1 I; A$ C - * (+RW,+ZI)" M. y" n! n/ I
- }
; ?9 b& e7 o) \ - }% S. y. X# v7 c
3 o" `1 W+ K( g( K, L0 T- DSCR +0 ; Device Description# L8 \0 _: `. f% i+ K: z
- {
8 y! i9 u7 i. @+ m8 b- ] - DevDscr +0
|6 E( S' n$ s3 B+ d1 f- C - {
, K% F$ Y% d# J; y- B- E - FlashDev.o
7 I" q0 s# p3 k - }& w8 v! c: R; b) h. j
- }
复制代码 6 @; R6 _8 f% y0 \/ m% i
--diag_suppress L6305用于屏蔽L6503类型警告信息。
/ D) s j- @, [% Y& ~# v: _
. i5 Z( F3 R7 s9 g; K5 V6 B特别注意,设置了分散加载后,此处的配置就不再起作用了:
# d$ B5 `( X o8 z
) [! C5 `: r7 j7 z9 I5 ~! o5 h. B* h- x3 T# z. U
+ |; F7 [, n0 o+ B0 g
80.4 QSPI Flash的MDK下载算法制作
) c; b1 g7 c6 s) x下面将QSPI Flash算法制作过程中的几个关键点为大家做个说明。' Z2 M- M0 D4 j3 t
+ |; {2 u5 V$ e! m1 p
80.4.1 第1步,制作前重要提示 C) |" r) z! @5 |% K
这两点非常重要:& G# E5 |2 ?) y0 b' W
A! A3 [- b+ A) X2 a/ q4 t8 ^ 程序里面不要开启任何中断,全部查询方式。. f! d* H9 I" a4 J |
HAL库里面各种时间基准相关的API全部处理掉。简单省事些,我们这里是直接注释,采用死等即可。无需做超时等待,因为超时后,已经意味着操作失败了,跟死等没有区别。- y' y% R8 D+ J8 h: z/ H5 k8 ?
1 q$ }& L0 }0 E) ?; R( s80.4.2 第2步,准备一个工程模板: G; F& z9 P$ w8 d4 {+ {
推荐大家直接使用我们本章工程准备好的模板即可,如果大家自己制作,注意一点,请使用当前最新的HAL库。
6 B% F' k8 o6 `6 Y ^
. q' I# Y; t8 p3 y3 ?5 K6 @6 A/ v9 N* D7 b9 b: n' R
/ }3 O: C* i) R2 M, k: q5 v. v
80.4.3 第3步,修改HAL库/ H, R3 e" p# Y2 `
这一步比较重要,主要修改了以下三个文件:% }1 h- D% I/ d5 S+ Z7 F
! E* T/ J8 Q+ L o$ S
+ _- c: \$ Q( A& t) ^
@+ y2 e& H7 M6 T
主要是修改了HAL库时间基准相关的几个API,并注释掉了一批无关的API。具体修改内容,大家可以找个比较软件,对比修改后的这个文件和CubeH7软件包V1.8.0(软件包里面的HAL库版本是V1.9.0)的差异即可。
2 T D8 ]4 E6 y( d* ]& N
6 A# ?# b( D5 e! G7 Q0 Z. K80.4.4 第4步,时钟初始化
: D/ G6 @& B8 ?* J我们已经用不到滴答定时器了,直接在bsp.c文件里面对滴答初始化函数做重定向:5 h9 z1 O5 Z( k: S
6 {5 N: `1 y4 n- /*$ D% K. z0 d8 ?3 U+ R3 _4 G; |
- *********************************************************************************************************" d% X, `, f: G0 w
- * 函 数 名: HAL_InitTick3 e- u( v5 F: ^$ h6 X8 d
- * 功能说明: 重定向,不使用
3 {& T" R3 D7 y- `8 n2 { - * 形 参: TickPriority
- b2 m7 D) o# l: z - * 返 回 值: 无1 b% v* V1 ? a6 `$ l
- *********************************************************************************************************
% z0 ~6 e7 C9 S4 `# \+ X% h6 a( s7 I - */) _5 P2 N/ C6 o# l ~: z/ S
- HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)4 c4 ^# E# e1 U, M1 d
- {) c3 e* b9 o2 ]& b) [. O
- return HAL_OK;
* {; r% O+ {7 ~9 u3 S5 ]. d - }
复制代码 3 p9 u6 d7 t; `7 T. L
然后就是HSE外置晶振的配置,大家根据自己的板子实际外挂晶振大小,修改stm32h7xx_hal_conf.h文件中HSE_VALUE大小,实际晶振多大,这里就修改为多大:3 X9 ?- f: s) _
" C4 X, t# A( t# _8 T- #if !defined (HSE_VALUE)
& D$ x. g, I5 ?2 ]- S; I8 V4 k! u' h - #define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */ h9 V/ w; t3 O' c- q
- #endif /* HSE_VALUE */
复制代码
/ y5 n" g' Q J4 A. H$ a i* a' W4 l最后修改PLL:
2 K8 k, P c4 ]1 G4 k7 l, d8 {% i8 } {, \+ c
- /*! j R% i1 v6 ?* s) `/ f
- *********************************************************************************************************
" `, J" G% N" s2 L0 |1 k) E - * 函 数 名: SystemClock_Config
: R+ W$ D r% ]/ j% o+ T0 z% \ - * 功能说明: 初始化系统时钟
2 m. A: V ~# i3 x - * System Clock source = PLL (HSE)$ M' X2 Z# E1 X3 O! J
- * SYSCLK(Hz) = 400000000 (CPU Clock) Y" f e/ a& K6 ^2 w8 g* W9 ?
- * HCLK(Hz) = 200000000 (AXI and AHBs Clock)
o) X5 m$ x7 `9 { - * AHB Prescaler = 2
3 W M' z. ?$ x4 S - * D1 APB3 Prescaler = 2 (APB3 Clock 100MHz)! W N0 ~6 [; O. ~, y
- * D2 APB1 Prescaler = 2 (APB1 Clock 100MHz)1 w+ |9 J) k ?9 x/ v* }) Z0 m
- * D2 APB2 Prescaler = 2 (APB2 Clock 100MHz)
0 l8 S0 Y, k# R$ j% _% ~7 ~8 Z* x - * D3 APB4 Prescaler = 2 (APB4 Clock 100MHz)! h2 a# U4 c9 C. J7 `3 V/ w
- * HSE Frequency(Hz) = 25000000
/ s, F5 l3 D9 f2 W/ u+ ` l7 b0 s - * PLL_M = 5; s1 s& X+ Z" ^* a
- * PLL_N = 160
4 l+ H/ ]" {% `; z - * PLL_P = 2
2 k; `% t8 T5 _/ O; R1 h! N - * PLL_Q = 44 y5 x9 ~) p- M& k! y
- * PLL_R = 24 v1 d+ m3 q7 x1 N5 [# \ K
- * VDD(V) = 3.3
. F( M+ A | }8 Z( J - * Flash Latency(WS) = 4
4 b/ F7 n7 V6 W: D! A3 q - * 形 参: 无
- P% a7 ~: q5 W/ Y& O: F - * 返 回 值: 1 表示失败,0 表示成功
& d2 Q( ~4 m( _& e4 z8 Y. t - *********************************************************************************************************% y: @$ M1 d% w. u
- */
1 z9 F, g! o+ c# Q/ c Z5 t - int SystemClock_Config(void)
& E7 w& W( } Y' ]+ D5 h, d - {
; G6 U" _8 ] v+ m - RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};$ D1 Z G% t/ f* x
- RCC_OscInitTypeDef RCC_OscInitStruct = {0};1 M- d% h1 I' b9 K, v* t. d* K" ?
- HAL_StatusTypeDef ret = HAL_OK;6 \7 r; I; x0 r* B
; g1 d1 ?. {! D u' W8 I- /* 锁住SCU(Supply configuration update) */
8 @! g9 n4 S' `; @- q% U! E - MODIFY_REG(PWR->CR3, PWR_CR3_SCUEN, 0);
& k1 @# v6 M3 m) v
$ q- d o8 {$ n5 M- /* : F3 b7 M1 I$ K0 s5 Z' I
- 1、芯片内部的LDO稳压器输出的电压范围,可选VOS1,VOS2和VOS3,不同范围对应不同的Flash读速度,
* S5 R2 a1 ]# E- G+ C$ y( d: A7 } - 详情看参考手册的Table 12的表格。
, C7 A2 |2 v, `6 y1 Z - 2、这里选择使用VOS1,电压范围1.15V - 1.26V。, j6 \3 s0 F6 h; o8 R" S
- */* L/ c! E! b" f: v* @% r
- __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);9 E2 c7 p. a! {! Y9 [% v
- % J& V u ~" \) P% p5 W/ w
- while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}
6 d7 C* \1 w* d6 m - & e9 @2 N5 M4 X3 B+ ^6 z
- /* 使能HSE,并选择HSE作为PLL时钟源 */. c# k, t% W) s
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
7 W% E. _3 C+ a( I# {/ T k - RCC_OscInitStruct.HSEState = RCC_HSE_ON;* a" a$ h- s9 |! k! g9 S+ c& X. \
- RCC_OscInitStruct.HSIState = RCC_HSI_OFF;
0 R4 s, D$ W5 A L - RCC_OscInitStruct.CSIState = RCC_CSI_OFF;
7 }9 J5 v+ r3 M8 E$ N! ` - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
! b! b& @+ Y9 R/ U H - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;3 p" s' M% {5 u
+ L Q( v1 O- E) J- n- `* k- RCC_OscInitStruct.PLL.PLLM = 5;
* e% Y: |) |" |) T8 O - RCC_OscInitStruct.PLL.PLLN = 160;
7 G7 @. V& K- g6 f) Q3 x - RCC_OscInitStruct.PLL.PLLP = 2;
4 O. l# T4 n* } - RCC_OscInitStruct.PLL.PLLR = 2; b2 W* I- F+ p- d" v3 t! L
- RCC_OscInitStruct.PLL.PLLQ = 4; * }6 O) t4 P* n6 x$ J
- # G0 J1 b: a/ E4 J- e
- RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;! E6 d; s% W3 a* c. r! ~2 G
- RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;
% H t) m! N& \6 ]' \ - ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);
% m3 X% q1 ?; ?# f& a. Z- `4 o( L7 q - if(ret != HAL_OK)1 d* l+ b8 D$ [8 G2 Q! o, U* ~& G
- {* U$ y8 h3 {$ T0 Z0 O8 s" C; ?2 ~# F
- return 1; ( M3 J& i; O( d' r
- }
) v9 K/ S$ R- ]* @% C: \3 H - 7 N) R+ r* v3 d" m$ w" @
- /* ; {# M# ]% ]; a" q8 ^3 M4 Z
- 选择PLL的输出作为系统时钟
' y3 U2 y0 X' _ - 配置RCC_CLOCKTYPE_SYSCLK系统时钟
$ N4 u0 b- X, Y8 X! _ - 配置RCC_CLOCKTYPE_HCLK 时钟,对应AHB1,AHB2,AHB3和AHB4总线% A8 G, I' S; i! y0 J% U. N
- 配置RCC_CLOCKTYPE_PCLK1时钟,对应APB1总线3 I0 k6 Y* s* O9 z' ?2 S
- 配置RCC_CLOCKTYPE_PCLK2时钟,对应APB2总线' c& r m4 T7 x+ p5 \# `0 O! m! I
- 配置RCC_CLOCKTYPE_D1PCLK1时钟,对应APB3总线
( [* C& K! `9 G. p - 配置RCC_CLOCKTYPE_D3PCLK1时钟,对应APB4总线
4 U- q Z F" k9 r - */; O' t4 V, Z6 ^7 r% v( d% E
- RCC_ClkInitStruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_D1PCLK1 | RCC_CLOCKTYPE_PCLK1 | \
* J: z4 _& J+ l/ Y, l5 v5 v7 ? - RCC_CLOCKTYPE_PCLK2 | RCC_CLOCKTYPE_D3PCLK1);) l! b4 Z |) Q0 W8 b* U4 q6 f* i
- & O& P# H, \ p( t% f( i" H+ ~
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
: w. J) {+ p$ [3 M6 b t2 m3 p - RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;
0 u. d9 p1 t/ } P - RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;) G O/ H% k6 e2 o: q9 I
- RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2; # g3 Q. H* i/ p' `/ G3 s
- RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;
4 M# _" ]) x6 ^% @8 E2 w7 x - RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;
: { p# ~" \4 q4 ~1 g - RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;
1 p! |: F# i/ z. l! m - 5 }5 Q; y% d, N* ?: u; F
- /* 此函数会更新SystemCoreClock,并重新配置HAL_InitTick */; r5 L% K. {1 H* \% Y
- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4);. p0 ?8 a& S4 _7 Q6 w7 y
- if(ret != HAL_OK) ^: X6 {- \8 ]! U
- {
4 @+ o( f' I" K2 R- e0 [ n, x X - return 1;
) v+ Z( V: {# B- }2 }' s - }
$ w" X: m( O- z
1 F+ q" K4 n# O0 S& L- /*4 z, s: K4 p1 U9 s2 Z# T; Y) n6 e6 ^
- 使用IO的高速模式,要使能IO补偿,即调用下面三个函数
" G. l H7 R( g, l# q) E; \ - (1)使能CSI clock
: m0 p4 }2 l/ M" \/ G: ? - (2)使能SYSCFG clock
9 N9 k: V+ M- @4 @# [; Z - (3)使能I/O补偿单元, 设置SYSCFG_CCCSR寄存器的bit0
f- r+ ? b9 D- ]5 o9 b - */
* h# Q- _! P; Q - __HAL_RCC_CSI_ENABLE() ;
& u; I; ^& m$ K8 u" \
* n; [3 F6 ]! Y5 ?( J- __HAL_RCC_SYSCFG_CLK_ENABLE() ;' y9 I+ N) X0 _6 C, R1 p! D
- . W' T9 |8 v7 o; A; j7 m+ @
- HAL_EnableCompensationCell();9 z9 x0 c" m8 l. x: A( z
- ! P3 p' s0 C& o
- __HAL_RCC_D2SRAM1_CLK_ENABLE();) |% C) ]; `) ]$ Z# O1 N7 o$ a
- __HAL_RCC_D2SRAM2_CLK_ENABLE();
- p. a/ o# Z( }" T/ N* E - __HAL_RCC_D2SRAM3_CLK_ENABLE();
0 F1 |7 ]# i/ d - 6 t x/ f. I4 S& l4 _- Z) L
- return 0;3 `9 S9 Z; T# d; F Z# H- C4 s
- }
复制代码 & `5 D& a$ X0 e
80.4.5 第5步,配置文件FlashDev.c的实现, l6 v( \% G: `/ h% B9 k5 w
配置如下:8 q; t* E8 @/ G1 ~) a _3 M9 m
/ a/ Y7 O/ n( B; T, R- ~- struct FlashDevice const FlashDevice = {
+ N% |5 F- B- D! ]) h0 k - FLASH_DRV_VERS, /* 驱动版本,勿修改,这个是MDK定的 */7 Y6 _; W! e' C% @4 Y
- "ARMFLY_STM32H7x_QSPI_W25Q256", /* 算法名,添加算法到MDK安装目录会显示此名字 */
) s9 i8 [/ K+ k6 b8 _8 o - EXTSPI, /* 设备类型 */, J1 g; t* d9 F' s+ r$ X& }
- 0x90000000, /* Flash起始地址 */
, F5 g" p; r) h% N - 32 * 1024 * 1024, /* Flash大小,32MB */1 w, Z4 t" R: K+ b
- 4 * 1024, /* 编程页大小 */
/ {; M; a Q: I4 q0 p { t - 0, /* 保留,必须为0 */1 c' a; g% P$ a9 f7 b
- 0xFF, /* 擦除后的数值 */
7 `0 K7 U: S& d T+ c D+ p: u - 1000, /* 页编程等待时间 */
2 x! T* E# o+ d - 6000, /* 扇区擦除等待时间 */' |4 R. z8 f9 {
- 64 * 1024, 0x000000, /* 扇区大小,扇区地址 */
1 g* E' J0 [/ {* M) ? - SECTOR_END
, L, i2 g2 G6 b7 ] - };
复制代码
b5 {- w0 @6 s( D+ I8 `注释已经比较详细,大家根据自己的需要做修改即可。注意一点,算法名ARMFLY_STM32H7x_QSPI_W25Q256会反馈到这个地方:
3 F8 f: L. Q4 B( z ^" a* T) P+ G# ^* r, H& c
# t; ^7 ^" g+ G' D Z
+ l5 C/ N* g0 c' |
80.4.6 第6步,编程文件FlashPrg.c的实现: g' o* A9 G& @
下面将文件中实现的几个函数为大家做个说明:; J0 |0 Y' x4 w4 Y" J
7 o$ J8 k- R+ g) R$ O 初始化函数Init- {5 _; S' n; a0 i! H! h* _* b
- /*1 V- ~ l" A; V! M4 F+ f
- *********************************************************************************************************
5 e1 M! y; G; ` - * 函 数 名: Init! z7 r& C; g( n+ l* O) G' s
- * 功能说明: Flash编程初始化
+ M$ X4 U7 a7 w" F3 G - * 形 参: adr Flash基地址,芯片首地址。' C+ j `$ J+ [2 }$ _5 Z
- * clk 时钟频率
- |0 y t3 I: J - * fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
5 E/ l( \7 Y3 s5 ]3 ^ - * 返 回 值: 0 表示成功, 1表示失败
: Q9 y, G8 ~9 j3 T - *********************************************************************************************************& } d L" I* v5 ~% Y2 B1 y& G
- */" p/ _0 ?. _! ?( ]5 i7 O' R
- int Init (unsigned long adr, unsigned long clk, unsigned long fnc)
+ G8 s' K' m5 p1 _ - {. X- u" v+ V; K7 v
- int result = 0;
# ]" X, @! k! @. M
9 F# o$ k; g3 x' _9 X" v- /* 系统初始化 */. F$ l+ k9 x/ y$ d
- SystemInit();
9 K0 u4 C) W' s% T+ ~8 _
* M& A7 R! j( z) P7 j u- /* 时钟初始化 */
z( @4 ~9 g% d* w - result = SystemClock_Config();
/ A0 v5 U7 p" L% D# B | - if (result != 0)1 I- S7 g+ _9 _' L5 p
- {7 E( W& m5 A$ ]4 V5 [4 k
- return 1;
( h- J1 T& m* G9 ^ w- l9 f - }
4 }2 N* N. i6 u" ?- B+ x# Z1 o
" J+ X; `$ X! ~5 S& [) J- /* W25Q256初始化 */" d* h; [$ H8 f* x! d6 N
- result = bsp_InitQSPI_W25Q256();
9 m' P0 j- g1 w* @ - if (result != 0)$ M2 h4 G# d s, H+ c3 t
- {5 ] r! L' z7 j, @ a
- return 1;9 N0 Z. ]: Q' J+ }2 w8 C! {* m& `: ^
- }- b9 C, O! R/ u! [
- 6 n+ j5 V4 K" z0 s( {; d
- /* 内存映射 */
7 X* U. U, R* @& N9 W% o0 }) p - result = QSPI_MemoryMapped();
, i5 n' X( J+ u& A9 w: r, P - if (result != 0)4 [* H8 d- K C. d" o1 X
- {* a8 L, k- j B( I0 p
- return 1;6 N) l! V" k2 w5 T6 \ ^4 ^8 }
- }
$ f- x. J$ G2 D$ z* t q - / q" U9 v, r2 ^" Q E7 m
- return 0;
! r) I, U5 ?# |. E - }
复制代码 ) d6 h5 Z! e- _1 W
初始化完毕后将其设置为内存映射模式。0 t% y: t5 x; H; F {
; h, C. X# H C
复位初始化函数Uinit$ \# f5 G9 Q! ?% ?/ a
擦除,编程和校验函数后都会调用此函数。) D' p8 v% J& Y
: Q$ Z+ j# x0 b* L$ s- /*/ u8 b' E0 N- c" ?9 D8 O( {$ A
- *********************************************************************************************************
) [! A i( r o2 ]0 C! h - * 函 数 名: UnInit/ S$ F% Y1 k C$ T! ]8 K; N9 b
- * 功能说明: 复位初始化! |; a) v4 o$ x4 L4 P* P6 Y- \6 D
- * 形 参: fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify
, ~# z w! t# @; Q - * 返 回 值: 0 表示成功, 1表示失败
9 q# F9 Y, a2 |5 H$ C M - *********************************************************************************************************2 z2 Z: s; m8 O. _
- */8 P* L+ {! \1 z7 h6 v$ S; g
- int UnInit (unsigned long fnc)
0 @2 f. [& d8 m( N) p, F - { ( d7 L( E$ x( f( ~
- int result = 0;; [9 l8 D- ?( v' d" t" i$ Z
- 4 @$ S( }$ x# `" U
- /* W25Q256初始化 */
% l; L' _# v s# |" e2 M" m% h j - result = bsp_InitQSPI_W25Q256();, ~8 l# u& I, E8 u. Z6 u
- if (result != 0)
* ~! i: k& E( H7 b# _0 O - {
# {6 {8 L2 r% ~2 z$ Z - return 1;
. V- I+ }0 s' S0 q# u - }
, b: K& Z; ? D1 D: t
}; w( v# e( n9 f* e- /* 内存映射 */ + p" W1 c+ x. O( O( a! D
- result = QSPI_MemoryMapped(); " }( V. {* @: V7 t* _4 q
- if (result != 0). Q8 Q' K8 ~ y$ R, H' J P
- {
1 B9 a" p6 s) K0 V5 I - return 1;/ F( J* X5 E) B
- }" {' B! t) A0 M/ J" d3 o- s0 }
- ! @8 J7 T/ F6 C+ u" u4 X8 B
- return (0);
5 V' x, {$ x- O! G - }
复制代码
; Y( u% A+ s, u2 W) N, R复位初始化这里,直接将其设置为内存映射模式。# g) J: p# W- u0 _
: ^: Q" _9 R" g" }! i
整个芯片擦除函数EraseChip( a6 U0 a% u3 |: A" O5 M5 i7 l h; c! F
如果大家配置勾选了MDK Option选项中此处的配置,会调用的整个芯片擦除:
' ^2 }1 s8 Y x. E8 O# [* k3 {. P
6 U. ^* e; I P! x& e" @. W
1 R: s. q5 k& a" |1 {5 |2 E# m' }7 s0 m% b; b
实际应用中不推荐大家勾选这里,因为整个芯片擦除太耽误时间,比如32MB QSPI Flash整个芯片擦除需要300秒左右。
$ o' Q6 D, W5 f
4 x7 m$ J# J( b9 y- M4 f5 a另外,如果大家的算法工程里面没有添加此函数,MDK会调用扇区擦除函数来实现,直到所有扇区擦除完毕。( T* R2 V9 q! N! T C
( G7 O' e" t$ k1 L3 X
- /*
0 e" o# g l$ K/ O9 v! ] - *********************************************************************************************************3 Y1 O# T! ~ ?- B% m
- * 函 数 名: UnInit
1 x5 _0 H |" r9 w - * 功能说明: 复位初始化 E, K4 C7 T0 E' v9 B2 f
- * 形 参: fnc 函数代码,1 - Erase, 2 - Program, 3 - Verify) @' i1 X" b4 Q& n( i$ f) ^
- * 返 回 值: 0 表示成功, 1表示失败
# Y3 d5 U* [& Q6 b6 o - *********************************************************************************************************
: y# P6 S. X8 x- E - */
; F/ N( l, y- m6 Y- w - int UnInit (unsigned long fnc)
0 H8 H9 k0 A' H - { ! E Z5 ^. E: C0 V( e( {" u# u# O3 k
- int result = 0;/ x2 w% o8 c' T& c
- $ K. h( }- h! e. o+ u, _
- /* W25Q256初始化 */
$ u6 f- M4 _6 X - result = bsp_InitQSPI_W25Q256();* ~1 ^ A0 x/ T9 K/ [4 L7 z8 _0 Y
- if (result != 0)( z7 n$ W9 y0 W, u# R' V4 t. u$ _/ h
- {
+ f: I9 ~! v+ R2 [ - return 1;/ m+ g8 p" g" L) P& ^( i
- }
% m* c9 ^' b( G& f
1 Y% @0 H: Q( r! Z: J# [3 ]# j- /* 内存映射 */ " q4 K; Z* j' h+ _% {" k
- result = QSPI_MemoryMapped(); 4 F; m- x2 k4 [# B; j6 R: z
- if (result != 0)
2 B+ s. e" g8 k/ h% W# B. E' T - {
/ E" u' J( O0 F' v8 Y" k$ G7 J - return 1;# c7 Q3 Z. o* }
- }
0 C# r" _0 q4 X! z# O1 j - : r5 o1 E8 Z- \( W
- return (0);
+ Y9 {6 t! X6 Z! [4 C - }
复制代码
2 I: _0 l1 E0 y# v& Q 扇区擦除函数EraseSector' Q/ c7 W# x; s
如果大家配置勾选了MDK Option选项中此处的配置,会调用扇区擦除:
$ @. t- }+ a+ p+ `1 g& W l- Z e# `/ ` ~
. H v4 P4 [. I$ q2 a; h8 K
' E0 q, E# R# o3 i6 x" o x
- /*
8 @0 ~# d' [+ q K4 q# F - *********************************************************************************************************! }1 o+ |+ A. l0 {) w- i( l* C
- * 函 数 名: EraseSector5 S% b$ |$ K1 v F: h4 a6 x
- * 功能说明: 扇区擦除1 k* c1 w0 f+ y3 x6 y: ^5 j+ N
- * 形 参: adr 擦除地址2 Z! N+ D( M9 [
- * 返 回 值: 无# E( a, g0 R! q3 V4 Z
- *********************************************************************************************************
0 K8 K% B4 c: B5 _) }6 y - */
( H, ? j$ @2 _ b, g - int EraseSector (unsigned long adr)
) r2 q. E" W2 |! W0 V - {
, j( A$ N/ K2 H1 w3 [& p; }7 ^ - int result = 0;
3 Q; V; F9 c$ w1 U. }% R! Q
4 y* g/ M3 @+ e/ p5 x. h8 L* S- /* 地址要在操作的芯片范围内 */
1 H: I, E3 T L$ G7 R5 P - if (adr < QSPI_FLASH_MEM_ADDR || adr >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)
7 N* V4 W# i( W3 E - {
) E9 W. _ ?/ w: c2 A6 | - return 1;1 {, q8 N4 u# T# S3 `6 h
- }
* h" U$ l6 ]( K/ r
0 l+ |7 ^5 Z- `/ P2 _; H0 k; s- adr -= QSPI_FLASH_MEM_ADDR;
5 _" t. B2 }. |7 m8 D7 ^2 e - B6 b; Z$ J. }1 B
- /* W25Q256初始化 */! E+ b! J3 T" ]+ {% d8 U/ j7 a
- result = bsp_InitQSPI_W25Q256();
: Y$ ^3 B# }( _+ R) q7 ~+ u3 ~% ? - if (result != 0)* X# b) @8 a; E" F0 f
- {
, P- B% z/ s0 f) Z0 F* V' S9 Z - return 1;
6 U E( Q6 z' M/ W5 f% ]7 V1 K6 e - }. h' i2 r* d& H+ ]
- 9 y4 y* x; N* \* \, J2 Q
- /* 扇区擦除 */
% _! ]/ J e6 A6 l' h - result = QSPI_EraseSector(adr); # d6 |* t+ O3 C' z4 o ]
- if (result != 0)& n2 J9 l+ z& E' H0 [6 h
- {
$ W7 i. @5 ^/ A( T) t& p& s - return 1;
, j/ Y0 A G7 E0 d h - }
: {* j* @* a+ @. o+ ?' E - : k4 i$ s8 j$ w5 f9 Q9 L, M% s' z
- /* 内存映射 */
4 I( m9 W# u2 o5 d7 _0 \ - result = QSPI_MemoryMapped();
, A8 p3 n$ `0 B4 e: L - if (result != 0)
0 _1 M3 r% W9 C! ?* a7 E - {
- L3 x' D0 S( [- E0 b. l - return 1;% s+ V( u5 }% O& K& R4 o
- }
3 M: ~! O5 e0 h* ?
+ l5 A) ^+ W$ @+ R% N- return 0;
) Q( g& A" C, l( q6 } - }
复制代码
. u8 T1 e$ t/ V# z这里要注意两点:
0 c$ ^; S7 {( k7 Z' F) c8 E% m" q; E
(1) 程序里面的操作adr -= QSPI_FLASH_MEM_ADDR,实际传递进来的地址是带了首地址的,即0x90000000。
- w9 E: L! i* b' K2 Q- Y: L2 W0 {- e7 e. f
(2) 这里执行的擦除大小要前面FlashDev.c文件中配置的扇区大小一致,这里是执行的64KB为扇区进行擦除。0 G% i- f( b' r# |
5 A( A/ ?8 B8 q0 x
页编程函数ProgramPage# k& Z( W6 X0 j# Q) M6 x0 W
页编程函数实现如下:' Y* \' q3 u+ r+ h6 N4 v
9 `' c9 K8 a+ {3 f% o
- /*1 y2 R+ E* y4 ]3 q# W9 G
- *********************************************************************************************************
# V$ X/ D* I: w+ K8 Y: U$ I: x5 l - * 函 数 名: ProgramPage
" d4 e! K0 Z. V9 f+ H) N" g - * 功能说明: 页编程
0 g# c; g: A5 m; F: g - * 形 参: adr 页起始地址, ]8 h, U% V) d; I$ b
- * sz 页大小
2 b9 X0 m0 X, g/ |7 k - * buf 要写入的数据地址
! Z4 R, }1 M' Y* i Q9 c - * 返 回 值: 无8 ^0 Q: u H$ u4 I0 m6 n, Q
- *********************************************************************************************************( A: }+ |4 q, ]+ O0 c/ n
- */" t5 q# l$ s7 i) G5 f6 a8 H7 V. S
- int ProgramPage (unsigned long adr, unsigned long sz, unsigned char *buf) ( s! n, Q# T7 }7 V
- {- f: Z6 ^% U; y$ M8 _
- int size;, M0 c7 k5 }/ ]6 q. {" `
- int result = 0;
' t# P& \% ?; l+ { - * V0 p8 M! m" y, j# q
- /* 地址要在操作的芯片范围内 */
9 }1 w9 l% P' b/ r( W3 |0 S) j7 j" X5 J - if (adr < QSPI_FLASH_MEM_ADDR || adr >= QSPI_FLASH_MEM_ADDR + QSPI_FLASH_SIZES)5 U7 k; O' x P q: x
- {
9 S& D, H9 i2 {# [$ w - return 1;
6 q; Y) T, ^8 N5 _ - }
+ {2 u* d- b* _/ T( Y' q4 Y
~( q, [5 I6 c, t( E- @6 ?- /* W25Q256初始化 */6 M6 q5 R- Q# Z* q2 K1 D
- result = bsp_InitQSPI_W25Q256();' `' M, c! R9 ?$ }8 l7 y
- if (result != 0)" }) r9 U. J8 c. z- e* s
- {
+ s. _' Y3 ^( V9 S2 V - return 1;3 |# V( \- \: e6 t
- }( D: w' Q$ K, T0 o( d
' o, }7 q" x0 g7 R( l, Y- adr -= QSPI_FLASH_MEM_ADDR;9 L: I) F+ l" L7 j
- size = sz;
* _# B+ }. {- u2 v) w
" O: Q L8 I& S- /* 页编程 */2 r# `/ W! Q# [ f* C z
- while(size > 0)
0 r4 _+ t# S3 n: E' z0 z - {
( G$ [3 R; W. y* O+ V$ _2 i/ U - if (QSPI_WriteBuffer(buf, adr, 256) == 1)
6 B b- w/ P g. K/ z - {9 G& v! J1 ?; B
- QSPI_MemoryMapped(); ( P+ k2 I9 e k9 d9 ]0 |) D
O! G1 U; q: M. g- return 1;
" ~5 H# y; d7 I4 [; L" m - }* P8 K$ G c/ {% T7 A* U
- size -= 256;& Z8 \. U0 a( ~0 y
- adr += 256;. A0 g5 g% @# J% R( P& r
- buf += 256;7 ~& I" T6 U7 P3 }0 w6 C* \
- }5 [, B! k# X9 p; N
- ) l6 e8 m, T T) A, k
- /* 内存映射 */ 2 ]2 @+ Y0 y- g9 e* {5 D
- result = QSPI_MemoryMapped(); , v8 q& a* S8 Z5 D* ~5 r* F
- if (result != 0)
- W6 A' Q5 N* [3 { - {) |- l/ U# a8 \
- return 1;
7 t& |5 h* ?% l) A - }; m" B. M v3 m1 y3 W
- - z+ I$ s4 H$ N4 e# H( A
- return (0); 3 v: j* E" \) f* m4 W" [8 g
- }
复制代码 / r, P+ E4 r$ i- j0 B
这里注意两点:
! S) o6 j; Z9 Z `8 Z" p2 ~( K# K$ Q U9 h
(1) W25Q256的页大小是256字节,前面FlashDev.c中将页编程大小设置为4096字节,所以此程序要做处理。1 W3 {3 `& u% y" |
% f8 h& t, E# F
(2) 程序里面的操作adr -= QSPI_FLASH_MEM_ADDR,实际传递进来的地址是带了首地址的,即0x90000000。* h: ~# @# r9 e& c
4 _ `9 p% z2 ]
读取和校验函数
+ _' H% L3 j" K# D- _1 H我们程序中未做读取和校验函数。
( i' j+ \3 }0 C
% f |: l& z4 N" s(1) 如果程序中未做读取函数,那么MDK会以总线方式进行读取,这也是为什么每个函数执行完毕都设置为内存映射模式的原因。4 J- W |" b2 ?/ I
% z" u2 m& W) |# I6 h
(2) 如果程序中未做校验函数,那么MDK会读取数据做CRC校验。
2 J' _5 W+ z; Z8 n. C' Q4 X$ |. ?
- p, }: c& G- U% W80.4.7 第7步,修改QSPI Flash驱动文件(引脚,命令等)
p* ?, S% q, T; z6 [' A最后一步就是QSPI Flash(W25Q256)的驱动修改,大家可以根据自己的需求做修改。使用的引脚定义在文件bsp_qspi_w25q256.c(做了条件编译,包含了H7-TOOL和STM32-V7板子):
. y v( j7 P& C1 b) c0 h, y' U- m9 n6 S; S5 y7 l7 t9 G/ a
- /* # t$ l3 C3 B1 A% A/ h( l$ Q
- STM32-V7开发板接线
4 L; L" \1 V; N2 B
# U& n. a" I X0 ]- PG6/QUADSPI_BK1_NCS AF10
* q# \6 ~% ?7 ]4 G; Q3 p' E - PF10/QUADSPI_CLK AF9* f( P( P$ ^, i' C
- PF8/QUADSPI_BK1_IO0 AF10
0 e s. ~; p9 f, j; M - PF9/QUADSPI_BK1_IO1 AF105 R8 A3 D# m, j8 R6 o1 q# L! m9 `
- PF7/QUADSPI_BK1_IO2 AF93 ~" O8 h8 {( u7 J
- PF6/QUADSPI_BK1_IO3 AF96 S( x+ R5 V3 P) B+ n9 ^
- ( }3 { n) `0 m
- W25Q256JV有512块,每块有16个扇区,每个扇区Sector有16页,每页有256字节,共计32MB
2 V# ]9 J( z7 o - ( ]& D8 W+ }$ `7 v
- H7-TOOL开发板接线* E& m0 s; u' ?
- x0 h7 Q$ E& M) O6 Y5 X$ {- } c- PG6/QUADSPI_BK1_NCS AF10 _2 x/ a' m7 a/ J
- PB2/QUADSPI_CLK AF97 R$ r3 p8 s. z$ X) B# _& y
- PD11/QUADSPI_BK1_IO0 AF10( q+ Y/ }: L- S) {! u" S8 @, @; o; x! i
- PD12/QUADSPI_BK1_IO1 AF10
7 q7 ^6 I' Z7 N0 |1 p/ x; F! u - PF7/QUADSPI_BK1_IO2 AF9
$ r( }$ p) a) U, A s - PD13/QUADSPI_BK1_IO3 AF9
) X1 Y0 D6 x9 i( J - */
: l- S; a9 f0 \0 ^ - $ }* B5 ?! ^( }' e
- /* QSPI引脚和时钟相关配置宏定义 */
# \1 r7 `* p" [+ c - #if 0, f8 x8 W# Y+ M
- #define QSPI_CLK_ENABLE() __HAL_RCC_QSPI_CLK_ENABLE()
% b( M3 `& j& b, P5 s - #define QSPI_CLK_DISABLE() __HAL_RCC_QSPI_CLK_DISABLE()- K) q2 \. M$ a4 u# V
- #define QSPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE()
7 U' ?* i1 G/ c9 T' u* J* G - #define QSPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()1 H% F; J- U$ y Y" c
- #define QSPI_BK1_D0_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
; {# @' M |; H. z! E4 u: Z - #define QSPI_BK1_D1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()9 v. k* o/ e0 f
- #define QSPI_BK1_D2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
0 e& n& w; M8 s0 z3 V9 B S - #define QSPI_BK1_D3_GPIO_CLK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()9 t5 z( J: [2 U3 p! u! u
- 6 Y/ n4 e' a* N3 O. O
- #define QSPI_MDMA_CLK_ENABLE() __HAL_RCC_MDMA_CLK_ENABLE()5 K& S; d ~* _! A1 T' d2 M
- #define QSPI_FORCE_RESET() __HAL_RCC_QSPI_FORCE_RESET()
" Y; _: Q8 V" x3 `" [2 \# m - #define QSPI_RELEASE_RESET() __HAL_RCC_QSPI_RELEASE_RESET()
7 H. G% \0 W5 Z& e: V
3 h+ H, B! }" G, ?2 C- #define QSPI_CS_PIN GPIO_PIN_6
: B4 ~. e5 Y) ?/ R* N) O5 ~$ a - #define QSPI_CS_GPIO_PORT GPIOG
9 o: I% C0 d, [4 x) B9 V3 U - #define QSPI_CS_GPIO_AF GPIO_AF10_QUADSPI
2 C% y0 Q0 G* _; @0 G T8 I - ! ?) d$ M- {' J S4 b
- #define QSPI_CLK_PIN GPIO_PIN_2! v8 U- D7 Y- O: N
- #define QSPI_CLK_GPIO_PORT GPIOB$ L: K, l! o. v! W( d" D5 N$ F0 t
- #define QSPI_CLK_GPIO_AF GPIO_AF9_QUADSPI$ |; w1 x! o0 i
$ d( l1 ]) g- F/ y- #define QSPI_BK1_D0_PIN GPIO_PIN_119 i7 U, |+ {) A2 n0 Y1 }! M- t
- #define QSPI_BK1_D0_GPIO_PORT GPIOD* q! V2 T' M. `! ^3 c! T a
- #define QSPI_BK1_D0_GPIO_AF GPIO_AF9_QUADSPI
( j; ^# B T2 U( }0 T$ \
( h U1 H& ^7 b5 v3 w, L9 V- #define QSPI_BK1_D1_PIN GPIO_PIN_12; v, z! X& t2 g" n
- #define QSPI_BK1_D1_GPIO_PORT GPIOD
1 k9 _8 `$ o M/ s( E - #define QSPI_BK1_D1_GPIO_AF GPIO_AF9_QUADSPI0 F% l s/ [2 b- _ C7 j& H' K
- " L" X1 f7 R+ a
- #define QSPI_BK1_D2_PIN GPIO_PIN_7
d( k2 P! q* F# v9 ]0 e, g, I - #define QSPI_BK1_D2_GPIO_PORT GPIOF4 y4 O) t6 |! x& T1 L
- #define QSPI_BK1_D2_GPIO_AF GPIO_AF9_QUADSPI
% C0 W4 d. G c( Y, o
X$ N* \ S% \* @- #define QSPI_BK1_D3_PIN GPIO_PIN_13' }: G! f/ Z+ W2 d! H8 S7 V
- #define QSPI_BK1_D3_GPIO_PORT GPIOD e1 ]+ D# \( F; Z* _$ [
- #define QSPI_BK1_D3_GPIO_AF GPIO_AF9_QUADSPI6 {+ ]; \/ D% x. d- s' { r
- #else
" O$ r3 ?/ ~" x. D* ^/ Q2 e; o - #define QSPI_CLK_ENABLE() __HAL_RCC_QSPI_CLK_ENABLE()3 I$ W+ g' ^/ G& ~' w. d1 r
- #define QSPI_CLK_DISABLE() __HAL_RCC_QSPI_CLK_DISABLE(): B# k6 D0 S! E' L7 Y- l' \
- #define QSPI_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOG_CLK_ENABLE()
- V+ X' j9 u2 } } - #define QSPI_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
, m& M. \% e; \/ f0 m( K0 I - #define QSPI_BK1_D0_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()2 q# P& v4 E+ `, X" P# l. T3 R
- #define QSPI_BK1_D1_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
. w3 {$ Y# r) a& K( ]" C2 S - #define QSPI_BK1_D2_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()4 x3 w* S# x4 H
- #define QSPI_BK1_D3_GPIO_CLK_ENABLE() __HAL_RCC_GPIOF_CLK_ENABLE()
; y0 I: V7 [! c0 ~* t5 I2 f - + Y1 o3 w( y. L/ q; u
- #define QSPI_MDMA_CLK_ENABLE() __HAL_RCC_MDMA_CLK_ENABLE()9 [# o" v1 D: b* i3 S
- #define QSPI_FORCE_RESET() __HAL_RCC_QSPI_FORCE_RESET()
8 c& h% V7 a& w/ m7 f1 Q. N - #define QSPI_RELEASE_RESET() __HAL_RCC_QSPI_RELEASE_RESET()2 x. i* V) b4 L. o/ C# T! Y& r" a
- - w# [4 `' O. U) X) k
- #define QSPI_CS_PIN GPIO_PIN_69 Z) J$ m; `2 d& r
- #define QSPI_CS_GPIO_PORT GPIOG) W0 Z0 B; y& X. E
- #define QSPI_CS_GPIO_AF GPIO_AF10_QUADSPI
# ?# x I( b# L - : Z o6 n# I& K1 u/ W, \* I2 E: l
- #define QSPI_CLK_PIN GPIO_PIN_10; D9 _- j0 `. B' ~" [; e
- #define QSPI_CLK_GPIO_PORT GPIOF, ]: X) Q z3 v0 @8 r$ u
- #define QSPI_CLK_GPIO_AF GPIO_AF9_QUADSPI5 |! u: Q1 U: d$ _3 y ?
+ \7 c9 U- J" B0 \* l- #define QSPI_BK1_D0_PIN GPIO_PIN_8
5 {9 U# o# B9 V* \' `9 o - #define QSPI_BK1_D0_GPIO_PORT GPIOF
( r! D1 x2 H# s/ p2 i& k - #define QSPI_BK1_D0_GPIO_AF GPIO_AF10_QUADSPI8 f& f/ s7 h6 I4 P' X3 e7 V: D
0 v2 S3 @+ z- Z4 w' [, N- #define QSPI_BK1_D1_PIN GPIO_PIN_9
* D: | [- |2 x2 \% }1 q5 K - #define QSPI_BK1_D1_GPIO_PORT GPIOF/ N2 ]" i S- K! `) y
- #define QSPI_BK1_D1_GPIO_AF GPIO_AF10_QUADSPI/ F. j; i# z3 ?& E3 Z
- - u) i6 ~, f4 i/ k* b' m
- #define QSPI_BK1_D2_PIN GPIO_PIN_79 \8 W5 S, _6 P$ e# t0 {; Y
- #define QSPI_BK1_D2_GPIO_PORT GPIOF
! ~3 j, E* D" q - #define QSPI_BK1_D2_GPIO_AF GPIO_AF9_QUADSPI }& H0 x5 x6 A# P+ \! f0 z5 ?: J
- ' b @8 O0 O$ O& v
- #define QSPI_BK1_D3_PIN GPIO_PIN_6
6 K P) I# g& V! q5 ?, }6 O9 D - #define QSPI_BK1_D3_GPIO_PORT GPIOF
( i# _% f! K3 _% w: z - #define QSPI_BK1_D3_GPIO_AF GPIO_AF9_QUADSPI2 {" E5 n. ?, X, r3 t( }
- #endif
复制代码
( b% S( r5 T) m3 ~. E) D硬件设置了之后,剩下就是QSPI Flash相关的几个配置,在文件bsp_qspi_w25q256.h:" X$ N1 K, s C% Y( V' a
, K0 e9 V9 D2 `8 n主要是下面这几个:
( Q! K# [5 w, g7 h+ i" B6 S' i' E3 i& {! C$ l" W1 Z
- #define QSPI_FLASH_MEM_ADDR 0x900000005 E( M) U5 ^* C) ?( H
9 G. u) {3 G$ a, T r- /* W25Q256JV基本信息 */( H0 i- y% e( q ?- @
- #define QSPI_FLASH_SIZE 25 /* Flash大小,2^25 = 32MB*/
: r6 M5 ?6 ~* h; y - #define QSPI_SECTOR_SIZE (4 * 1024) /* 扇区大小,4KB */
' V* K+ a, k8 Q - #define QSPI_PAGE_SIZE 256 /* 页大小,256字节 *// M' w% o8 d9 w- S
- #define QSPI_END_ADDR (1 << QSPI_FLASH_SIZE) /* 末尾地址 */* {- W# W2 N) K" y- v+ |
- #define QSPI_FLASH_SIZES 32 * 1024 * 1024 /* Flash大小,2^25 = 32MB*/
$ ]! M, w8 p8 q N+ Y' X5 m$ g - , H. A/ K. ^8 `: Z% `& r
- /* W25Q256JV相关命令 */
% u( M% W4 C8 p - #define WRITE_ENABLE_CMD 0x06 /* 写使能指令 */# g5 [ K/ A3 H! B3 S1 B
- #define READ_ID_CMD2 0x9F /* 读取ID命令 */
' ~/ n& Q2 R% s# v7 m - #define READ_STATUS_REG_CMD 0x05 /* 读取状态命令 */. R* F0 R/ q4 x
- #define SUBSECTOR_ERASE_4_BYTE_ADDR_CMD 0x21 /* 32bit地址扇区擦除指令, 4KB */
% ^5 N! V. t( B; }6 v0 z: b- g - #define QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD 0x34 /* 32bit地址的4线快速写入命令 */
# I( p% K0 m0 `( ]7 ?/ Z% ] - #define QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD 0xEC /* 32bit地址的4线快速读取命令 */
2 j2 F; g$ }4 O0 o5 i - 3 e6 `, N* Z/ u
- #define BLOCK_ERASE_64K_4_BYTE_ADDR_CMD 0xDC /* 4字节地址,64K扇区 */
! j0 U, Q* M1 A V; E# I - ! U* u t8 w2 u$ d# D* R
- #define BULK_ERASE_CMD 0xC7 /* 整片擦除 */
复制代码
0 Q4 M$ @9 R1 ^8 _, {/ K80.5 QSPI Flash的MDK下载算法使用方法
5 j% h0 ^7 n$ E3 k9 z& l' g3 G编译本章教程配套的例子,生成的算法文件位于此路径下:5 B- U, e! k% S" m3 }
* K6 a/ |# L# `# U( ]6 |/ {5 P$ s- f
! G; n' D+ g" l% Y4 z8 i3 h6 g# O* P/ A) X h$ [
80.5.1 下载算法存放位置
5 g o2 r) A. x! ~生成算法文件后,需要大家将其存到MDK安装目录,有两个位置可以存放,任选其一,推荐第2种:
/ W$ r) ^/ M+ P4 J) d! B4 w; G/ q6 J; A: X6 ~$ j
第1种:存放到MDK的STM32H7软包安装目录里面:\Keil\STM32H7xx_DFP\2.6.0\CMSIS\Flash(软包版本不同,数值2.6.0不同)。# o1 b( p! y! P! ~) H/ m
第2种:MDK的安装目录 \ARM\Flash里面。
& Z& y% p# l( L- E8 g+ D! O& c0 n) O2 a, H" [
" N& R) o/ d/ Y, W" i7 D7 j
1 r4 }& t6 C1 |80.5.2 下载配置
& h* b; [& b6 S$ P$ m" ~) X+ {注意这里一定要够大,否则会提示算法文件无法加载:
+ B1 q& E$ \' o& @: d0 a5 T* R, \2 z& g- Z
( M# E; q8 m5 }
: A1 \) z7 E3 \. s我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。推荐使用AXI SRAM(地址0x24000000),因为这块RAM空间足够大。% ?' X" k& c" G9 h7 z& O
/ C4 {" Y4 y$ A如果要下载程序到QSPI Flash里面,需要做如下配置:
/ j) N0 k) X: i; h8 _1 o
0 I- {: M5 i- @& I5 S6 J9 G# ?9 S6 A4 w
* O7 e; v8 f' ]: S- j" j1 \80.5.3 调试配置6 q+ {# ? V) V$ {. U
注意这里一定要够大,否则会提示算法文件无法加载:
! q6 m+ Y( i8 F5 P+ |4 H: Q [- [( w+ B! s* c3 Z
8 @3 o8 e! v5 g- E+ P+ m7 |+ |& E2 B1 V
我们这里是将其加到DTCM中,即首地址为0x20000000,大家也可以存储到任意其它RAM地址,只要空间还够加载算法文件即可。
& T3 l7 B3 `. u( _. o* {; E9 h' B3 ^% W
如果要做调试下载,需要做如下配置:
' {" M3 N A# ]. a' |4 B8 \
# ?3 { Q5 h" C6 J! W. b& Q; n/ C0 u! Z7 T/ s/ {- g
$ S) H+ _4 k( D
80.5.4 验证算法文件是否可以正常使用2 A. f! U9 m+ I+ X/ Y
为了验证算法文件是否可以正常使用,大家可以运行本教程第82章或者83章配套的例子。
. S, V2 ?/ _2 ?$ e% n: f2 I3 [$ n9 P4 ~" c' _" W! X* [
80.6 实验例程说明
# M5 p) B; l; C6 V6 [1 }2 n8 g本章配套例子:V7-060_QSPI Flash的MDK下载算法制作。
# B+ b# Y3 q- G3 @3 l
& U, Y9 C3 V& [9 ?: @* Z- b- ]! P+ V编译后,算法文件会存到此路径下:; X* S0 D' k. ~0 w1 H4 b8 N
1 k s: e( d2 F5 [
4 G K' K3 c; @, O. |* {" S, L1 i( r
9 I! B y+ u( y" W% m80.7 总结$ g9 g5 i8 C1 Q4 T% j1 }3 |
本章节就为大家讲解这么多,为了熟练掌握,大家可以尝试自己实现一个Flash下载算法。 C3 |5 [: P7 e& y$ L
% X: ^4 [3 S3 [ E }1 d& L# |, H
3 k0 I$ }& F% ]! `+ R
4 s9 e/ I1 a1 p+ }6 w J
) F& x7 R: I) c3 Y; D: ^0 P5 ^
e& K0 s/ \0 |( V; Z/ i1 M( U |