你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32MP1 M4裸机CubeIDE开发指南

[复制链接]
STMCU小助手 发布时间:2022-9-21 16:06
第九章 STM32MP1时钟系统1 z( _: l3 n# h( z4 ^/ m7 n
如果让你用一句话来形容时钟,你会怎么说?+ T1 B; |! R3 N; b1 E
A同学说:“时钟是单片机的心脏”;0 s  W7 d3 d+ {( m3 e2 s
B同学说:“军训方队训练时的121”;
; k- U% c9 u8 R4 \' R- `& ]1 EC同学说:“时钟是单片机的能量”;8 O! u6 U$ t: }9 F1 f9 L$ w3 t( h
D同学说:“早上的闹铃”;
# z" b: P4 {' U( V' \
* X. I. U: W. i2 s5 V可见,时钟是多么的重要。6 m" R: `) \- B: G/ N8 W
本章节我们来了解STM32MP1的时钟系统,并分析HAL库中和时钟相关的API函数,然后通过STM32CubeMX插件生成时钟初始化代码,通过分析工程代码去学习STM32MP1的系统时钟初始化步骤。+ N7 U3 D7 k: P$ h3 U. f$ |/ V
本章将分为如下几个小节:
- D8 k/ S* `5 F- ^, O7 T9.1、认识时钟树;
) g6 k, K8 x/ \5 f; U* S4 W; J9.2、RCC寄存器介绍;- I5 D  z! x4 {( i; S' i  S
9.3、时钟相关API函数;, k) O: S7 W- E% i7 E
9.4、配置系统时钟实验;/ O, |: M( r7 b, h7 H5 F
9.5、实验代码分析;
( _. L' U: \5 v. U9 i( i& A4 K/ N" M( o( I* ~4 K* Q2 W, [( O$ g8 l2 w
9.1 认识时钟树6 h  L+ U; u  ~
时钟是单片机运行的基础,相当于心跳,没有时钟,单片机就无法工作。为什么这么说呢?我们知道,寄存器是CPU内部的元件,由锁存器或触发器构成,触发器的置1、清0和置数的功能是靠其外部提供的时钟脉冲来产生跳变的,如果没有这个脉冲信号,电路就没有数字跳变,也就没有时序,单片机也就没有工作。时钟就像是人的心脏,这个恰当的比喻把时钟的重要性概括的淋漓尽致。" S% N4 s0 K- u3 E) Q& ]6 ~
听说过时钟,但时钟树又是啥?前面我们说单片机的运行需要时钟脉冲来驱动,这个脉冲由源始晶体振荡器提供时钟输入,经过各级电路的处理,最终传输给各个设备,设备获得一定时钟频率以后开始工作。时钟的这种传输过程就好比一棵大树的养分供给给其它分支,树上的主干和树枝就好比时钟的总线,树叶就像是外设,时钟就是通过总线传输给设备的。# `, o1 U, a, i7 w' s% ]; c
9.1.1时钟源9 [: V! d2 u! `1 T7 N! W2 @
51单片机的一个系统时钟就可以解决一切,但STM32不一样,STM32有5个输入时钟源(Input Clock)可以用,分为2个外部时钟和3个内部时钟。4 S6 t3 `1 I5 v* V9 x
下面我们看看这几种时钟源。
# z8 X2 B/ ?2 N  t0 M(1)2个外部时钟源:
4 B# @4 C- D* d( B8 L! r高速外部振荡器 (HSE)- U- z( ?2 |& Z8 y$ k7 @
可通过外接有源或者无源晶振驱动,支持 4 MHz 到 48 MHz 频率范围内的晶振。如果要使用外部晶振,在STM32CubeIDE上配置的时候,请根据板子上外部焊接的晶振频率来配置,正点原子STM32MP157开发板的核心板上外接了24MHz有源晶振。# R1 h6 {% f1 C/ h( A% F! r
低速外部振荡器 (LSE)
; i, y. Z( r! B可通过外接晶振驱动,固件库中默认配置32.768 kHz(见7.4.1小节)。正点原子STM32MP157开发板的核心板上外接了32.768KHz无源晶振,主要用于驱动RTC 实时时钟和唤醒功能。
" @. o  W- ~' K3 Q) q% A4 [. Y* v(2)3个内部时钟源:* }# S/ R' p3 h6 p  K1 c# Y7 p
高速内部振荡器 (HSI)9 J' S' D" h9 h
频率约为8、16、32、64 MHz,在固件库中默认配置为64 MHz。HSI比HSE具有更快的启动时间,大约几微秒。, l) p% L" }+ w6 @9 s
低速内部振荡器 (LSI)
. D. M3 ?6 ?. m$ P- h频率约为32 kHz,实际值可能会因为电压和温度而变化。主要用于看门狗。
& k$ n8 \' e* e4 W5 Y* z+ Z$ X低功耗内部振荡器 (CSI)' k) P; ^7 |- T% X/ U
频率约是为4MHz,主要用于低功耗。) v3 V# f' E: D' p- M
以上2个外部时钟源都是由芯片外部晶振产生时钟频率,其精度较高,而内部的时钟源可能因为受到温度的变化,频率发生变化,也就是存在“温飘”的情况,即使经过校准以后,精度也比外部晶振低,对于日常普通的短时间计时,使用内部时钟相差不大,对于较长时间计时就不建议使用内部时钟了。使用内部时钟可以降低成本,例如在一些白色家电中,一些家电产品为了降低成本,把电路板上外接的晶振去掉,程序改用内部时钟来工作。  Q4 w* [. b1 L4 S
大家可能会问,为什么STM32 要有多个时钟源呢?* v+ Z. i' J* G7 P4 l2 }
我们知道,在同一个电路中,外设开的越多以及频率越高,功耗就越大,同时抗电磁干扰能力也会越弱。在实际的使用中我们并不是所有外设都会用到,有时候只是用到其中的某几个功能,如果打开用不到的外设,功耗会变大,而对于用到的外设,有的外设可能用不到系统时钟那么高的频率,例如看门狗或者RTC只需要几十KHz的时钟就可以工作了。ST在设计芯片的时候也是考虑到了这一点,对复杂的MCU采用多时钟源的方法来解决,并用时钟树来管理,将所有的外设时钟都关掉,用到什么外设,就使能对应的外设时钟即可。下面,我们来看看STM32MP1的时钟树。
) P" g1 _! M7 Z9.1.2时钟树0 |: u5 |! k6 q

$ T! p" M$ x) F; e. U+ ~+ T 5834ee9f87254d6995051288551d0480.png
% a8 d' l. ~4 R  s% y' A! M0 G- Y  N0 G/ i2 }
图9.1.2. 1时钟树框图, Z# D6 t: z' s+ N* S6 t  U! ?
注:
% T. d5 h7 ~: |# P3 x6 c' h①图中的 表示动态时钟选择器,图中的D符号表示动态选择开关,即时钟选择器允许即时重新配置选择输入的时钟源,两个输入之间的过渡无干扰。仅当目标时钟源准备就绪(启动延迟或PLL锁定后时钟稳定)时,才从一个时钟源切换到另一时钟源。如果选择了尚未准备好的时钟源,则在时钟源准备就绪时进行切换。
$ I& }* K  @7 Q3 b. I( t& G②图中的部分时钟命名:' ]3 f' A3 P5 M* u) U
" \" b' W6 A: @. B+ p  I6 f
9015248081b84a95a9cfd0daa9f938fe.png # A, ~, P) k" R; A( d
8 f- B/ o6 t$ p1 Z* ~4 H" |
表9.1.2. 1时钟命名9 r/ `0 q' q+ c5 f% @: P4 t
3 C; L  L) G0 o$ f# A" {" \3 D
外部时钟输入
6 N: A' K1 X4 \# q% Y图中①处是外部时钟输入引脚,当使用HSE时,HSE在OSC_IN引脚接收外部时钟源,当使用LSE时,LSE在OSC32_IN引脚接收外部时钟源。0 {9 z% f/ G, f/ S  ^! s

2 ^- _0 V" i) g 159bfd153fe24136867eee11802a7a4a.png 5 s. T( T  _( y" o* X8 w
! D0 p6 B3 @/ \9 o: r8 ]
表9.1.2. 2外部时钟输入引脚( T5 H9 x* r9 K8 }
2. MCO时钟输出
4 I0 G+ H5 ~' G2 ]$ x0 X②处是MCO微控制器时钟输出(microcontroller clock output)部分。
# u; n; P7 ?; Z/ g. [6 bMCO1和MCO2是时钟输出,通过芯片引脚可以给外部的芯片提供时钟,可以节省晶振,节约成本。MCO1和MCO2时钟输出通过配置RCC_MCO1CFGR寄存器来实现,具体配置可以参考手册详细说明。
. |" h/ Q# x5 }1 L0 V0 a时钟输出 Pin name1 n/ ]+ A! G7 q1 d: Q: n

, u2 ~5 m( r* |+ T& I( v5 R c2fe0b3f25364b109577848446977758.png : C; V+ E* h2 U- r) Z4 ~4 d% e; m
: R: q6 A; e1 E- y/ r1 S+ Z0 Z. {
表9.1.2. 3 MCO时钟输出引脚. c2 z: D0 a. a& @
MCO1SEL和MCO2SEL是时钟源选择器,分别选择MCO1和MCO2的时钟来源;
8 ^0 H$ s! `9 \& |+ h# jMCO1DIV和MCO2DIV是时钟分频器(也叫预分频器),取值范围是1~16;% O8 {6 H9 K1 V' d
MCO1时钟源来自hse_ck、lse_ck、his_ck、lsi_ck和csi_ck,用于输出HSI,HSE,CSI,LSI或LSE时钟。% C: K# p: v! n: Y& ]1 a$ X2 R
MCO2时钟源来自mpuss_ck、axiss_ck、mcuss_ck、pll4_p_ck、hse_ck和hsi_ck,用于输出MPUSS、AXISS、MCUSS、PLL4(PLL4_P)、HSE或HSI时钟。
& N$ }% i3 ^5 q+ ?7 \! t. J# _) [. z! ^4 G) e; J. {
3. 锁相环PLL
: x5 i6 v' y1 }4 t+ |③处是和锁相环有关部分。& B' Z' C# N1 g( L  {4 e& e
PLL(Phase Locked Loop) 锁相环路,其作用是使外部的输入信号与内部的振荡信号的相位同步,在STM32中主要用于分频和倍频。STM32MP1有4个锁相环,分别为PLL1、PLL2、PLL3和PLL4:
8 E: L( l1 ]6 rPLL1,用于向MPU子系统提供时钟;
& J  R8 ^9 d9 G3 a& g* ?% h/ a8 DPLL2,用于向AXI子系统、DDR和GPU提供时钟;# t! ], }" P" o. L
PLL3,用于向MCU子系统提供时钟并为外设生成内核时钟;
* m) A2 m- S. yPLL4,用于生成外设的内核时钟。
* k! ]/ W6 L3 p; F; m  W; `9 _2 E0 {④是PLL时钟源选择器。( R6 Z: w1 o" p5 g8 k+ X4 u
锁相环输入时钟源有三个:his_ck、hse_ck 和csi_ck,分别对应HSI、HSE和CSI的时钟。我们可以配置某个锁相环选择对应的时钟源。5 N, ^" E1 \5 G: O0 l
⑤是PLL时钟源分频器7 I0 h% t" {' U
DIVM1、DIVM2和DIVM3和DIVM4分别是PLL1、PLL2、PLL3和PLL4的分频器,取值范围都是1~64,表示将输入时钟源进行分频。* N1 ~6 h: s( r* p
⑥是锁相环6 d# B, X  _5 h! i
其中PLL1是一个输入一个输出,PLL2、PLL3和PLL4是一个输入三个输出。) j+ |, ]* e, S& {2 m* \  k
我们以PLL3为例子分析一下锁相环的内部结构:/ c% D. c$ j4 N! Z* r: y
VCO表示输出频率;+ o" b, t) Q$ D' R. \
DIVN表示VCO的倍数;
9 F% Q. }1 e5 J' k2 QDIVP、DIVQ和DIVR表示预分频器;6 _/ O- y( T" o1 k
SSCG扩频时钟发生器,可以减少EMI峰值的数量,一般不用设置;- k6 G3 ~( D8 I' }
FRACV是分数倍频系数,它和DIVN一起组成PLL的倍频系数,当FRACV等于0的时候表示在整数模式下,大于0的时候表示在小数(或者说分数)模式下。# L1 M8 ?! c4 t9 h7 P  E) |0 D
PLL输出的频率有两种计算方法,我们以PLL3输出pll3_p_ck为例,其它锁相环的输出计算方法类似,时钟源默认使用HSI,即64MHz。$ Z3 ?& w, c+ Z: h5 [5 i0 L" r
在整数模式下使用PLL:9 ]# Y' F1 `* A5 b" a0 V) z1 \
: U9 l6 R) l) }4 \8 }
在小数模式下使用PLL:
/ W; q: r1 S; k2 ^1 ~9 [注:
8 U# w2 i; I4 |) U手册中公式的(DIVN + 1)其实就相当于我们上面公式中的DIVN,因为STM32CubeMX插件中是这样计算的,我们的公式中就跟STM32CubeMX插件的计算方式一致。, j" V4 Q9 s% t

" f0 c( m' V2 i# ^  F, P 0d50aa4946f749cda862d03ed5de9102.png
, b( X- R& {. {6 o8 ~0 h% {9 o$ ~4 X4 [) m! Z. \
图9.1.2. 2参考手册上的公式, Z' m; `  {4 C0 t+ ]: u
例如,在整数模式下(FRACV为0),当设置分频器DIVM3为4,DIVN为26,预分频器DIVP为2时,计算出pll3_p_ck为208MHz。如果再设置FRACV的值为10,根据上述计算出pll3_p_ck约为208.009766MHz。在STM32CubeMX插件上,我们手动配置这几个参数,结果和我们计算的结果一致,如下图所示:, e3 L+ p" q" o0 ~1 W2 Y, m

( h, N  k% _3 f# A 2c757d68de5d4be5ab7c9849dfa4a2bd.png
) N3 p# X2 z# |7 Z2 j# v1 h- o5 [# u9 s
图9.1.2. 3 整数模式
9 |8 d" F5 W7 E  g3 M' R% \/ q3 a/ P. a
02085802999446128087f9e153456aaf.png
' ?5 a7 ^8 r+ ]! c' ^. {: b, |' _8 M6 ?( E5 b: H" B  k
图9.1.2. 4分数模式, r/ x; ^' m6 Z, q; S# r
4. 子系统时钟
6 K' R, u3 d0 E  P⑦处是子系统时钟,RCC处理三个子系统时钟:mpuss_ck、axiss_ck和mcuss_ck。其中mpuss_ck给MPU提供时钟,axiss_ck给AXI互联矩阵提供时钟,mcuss_ck给MCU提供时钟。上电复位后,所有的PLL和CSI以及HSE关闭,默认将HSI选择为整个系统的主时钟,因此,子系统时钟默认来自HSI。当系统运行时,用户可以选择子系统的时钟源。
& w& }/ t0 L, R7 w. q4 h我们来查看子系统部分的详细框图:
3 O8 Z* ^, @! }/ \- x7 R) ~1 v4 }  M6 E7 z# t; X
01aedfc913394910b8750534ef319d3c.png
; R* W& T7 p" `& g3 i7 G' E/ z
7 D* ^8 u! b* ^7 {4 D+ \7 Q5 ?图9.1.2. 5子系统框图
6 L, _3 `! T* ~/ B, tmpuss_ck的频率最大可以是650MHz或者800MHz,这个根据所用的器件型号决定,STM32MP157A的MOU主频为650MHz,STM32MP157D的MPU主频为800MHz。 axiss_ck时钟最大是266 MHz,其经过AXIDIV分频器分频后分流为几路,给总线提供时钟。mcuss_ck时钟最大为209 MHz,经过MCUDIV分频器分频以后也是分成了几路。
3 b- K7 c& @2 p7 |# W" y/ ?9 \. cpclk4和pclk5最大为133 MHz,pclk1、pclk2、pclk3最大为104.5 MHz。" t7 }# E$ E) U& j7 h
子系统时钟汇总如下表格所示:
3 B! _9 I) u; q7 N( _; o& d- m# y% W* J$ @8 ]
8b7fbfe7c4d54e4faf569af39d07f2d0.png
8 t. r7 f) x+ i+ Q0 B$ n! W% K: o; I6 i$ Z$ e: d( w
表9.1.2. 4子系统时钟信息表
' u  q  E9 j2 E: z+ v5. CPU、AXI总线矩阵和外设时钟
# f$ u3 S& V$ o) h在STM32CubeMX插件上,我们可以看更具体的时钟树框图,如下图:
; i: u* `  U. }; D. p3 n6 _A处是时钟源,可选外部时钟或者内部时钟,要用外部时钟的话,必选先使能外部时钟;# b5 O. c! L2 g" N
B处是锁相环PLL;
9 L, L( B/ k$ Y* K. X( ?C处是内部时钟输出MCO1和MCO2;
; A0 ^' {) B; Q# V+ Q黄色区域是子系统时钟;: N$ {) e4 p2 V$ I( L; ^
紫色区域是专门给CA7外设提供时钟的总线时钟,分别是AXI、AHB5、AHB6、APB4、APB5,;
3 b' N6 u3 e& K蓝色区域是给MPU和MCU提供时钟,其中,MPU时钟最大是650或800MHz,MCU时钟最大是209MHz,MCU有一个内核外设Systick,频率最大也是209MHz;
' Z" q# g; X+ j0 \- `+ B绿色区域中AHB1-4、APB1-3 是提供给 CM4 外设使用的;
4 H( ]3 q% b) S0 |9 ?, T红色区域是内核外设时钟具体配置部分,默认没有使能,是灰色,只要在 STM32CubeMX 使能了对应的外设,对应外设的时钟就会由灰色变成蓝色。# Q) r0 Y! _- B
注:
: i# L6 G. v2 m! g: |0 \RCC为整个电路提供时钟,为了避免造成误解,和参考手册一致,使用以下术语:
' m: p  L6 w3 P; H# Y9 ~7 K•外设时钟:0 U5 X% Q6 u2 d7 t# R
外设时钟是RCC提供给外设的时钟,可以为外围设备提供两种时钟:
) h: W- V4 N- \8 Q–总线接口时钟
( b6 y/ f; U% Q- M7 ]–内核时钟
( p$ f) G4 V0 ]( l8 _5 S. o这里的外设有分普通的外设和内核外设。外设将从RCC接收一个或多个总线接口时钟,该时钟通常是AHB,APB或AXI总线接口时钟,具体取决于外围设备连接的总线。! N+ }& q9 D7 E& |* a, b# M# B
某些外设仅需要总线接口时钟(例如GPIOx、ETZPC、EXTI 、IPCC 、WWDG1、DMAx …)。
7 G$ C5 A; j" i- ]) c一些外设可能还需要专用时钟来处理接口功能, 该时钟称为内核时钟。例如,诸如SAI、ETH、FMC、GPU等这些的外设需要生成特定且准确的主时钟频率,这需要专用的内核时钟频率。
! q6 d9 B  B* X* ~" k•CPU时钟
' c! x) D" H: Z) c1 J3 n9 lCPU时钟是提供给CPU的时钟mpuss_ck。MCU时钟是mpuss_ck。, ^4 j8 T) n! Q% a$ l( _6 o& ?
•总线矩阵时钟6 X+ S% o! P- h8 ^' N9 p; ]
总线矩阵时钟是提供给不同桥(APB,AHB或AXI)的时钟。2 F2 O1 a. n: @: y2 t* b
图中,表示APB总线的分频器,里边的数值表示分频因子。表示是时钟源选择器。4 p4 p* H! |4 ~2 {+ |+ I; P0 ]
STM32CubeMX插件上的时钟树比较形象,很好理解,图中的一些符号可能和我们参考手册上看到的不一样,不过根据字面上的意思可以知道大概对应了哪些设备。我们可以通过STM32CubeMX插件来配置时钟,然后自动生成时钟的初始化代码。  y9 s! D6 z4 N" z+ x

, b2 P, f0 U. t, D. O# Z 476fda53b1b54ea28526a3556a7d7ee5.png 0 k6 x$ P% m! H8 B0 ~% N
3 i3 E: F  w& W* X$ o
图9.1.2. 6 STM32CubeMX时钟树
0 |5 M9 Q/ p: n( X. ^对于内核外设时钟,我们以SPI为例子来说明一下:& t+ N/ j. k5 m" }
: }0 U9 h1 h$ ]9 g
696b414bc19f4f4b8c57fa6fb55ba668.png
/ d" `* j; q) q, D% ~- v& D  N
3 _& }( }  @+ q9 C/ u  g图9.1.2. 7 SPI时钟框图% ~" J% F+ Q. C) h5 A7 y; a
PKCS (Peripheral Kernel Clock Selection)是外设内核时钟选择单元,用于选择某个外设的内核时钟来源。PCKE(Peripheral ClocKs Enable)是外设时钟使能单元,用于是否启用时钟输出到外设中。" ^; u4 o0 K& w
如上图,SPI6的内核时钟源来自于APB5总线(pclk5)、PLL(PLL3和PLL4)、HSI、CSI和HSE中的某一个,具体是哪一个由PKCS决定。其中,HSI和CSI用于低功耗中,PLL4在需要时具有更高的灵活性,例如,它允许通过PLL3更改MCU总线的频率,而不会影响某些串行接口的速度。& ]+ g" s! C0 u! ]- v) r, J  b
SPI6的PCKE有两个时钟部分,一个是来自于APB5总线接口的spi_pclk,一个是来自于内核时钟spi_ker_ck。PCKE决定是否启用SPI6的时钟,其它内核外设也是通过PCKE来开启的。由此可见,各个外设时钟基本都是可控的,不用的外设,就关闭对应的时钟,用到的外设,就根据分频器来配置,选择一个适合频率的时钟源,然后再开启外设时钟,这样的设计大大降低了功耗。
( b, M6 P' h2 `, N; n4 P. o$ U1 p$ O根据STM32CubeMX插件上的时钟树,ST给出了一张总线框图,如下图,从图中可以看出哪些外设挂在哪根总线上,以及总线的最大时钟频率是多少等信息。
" l+ t2 I! L  \( q" V0 }3 t% F  W下图中,AXI互联矩阵主要用于Cortex-A7 CPU子系统和高带宽的设备(USBH、ETH、SDMMC1/2、MDMA、GPU和 LTDC等)。MLAHB高性能总线主要用于Cortex-M4子系统和相关的设备(GPIO、ADC、SRAM、OTG、DMA1/2和SDMMC3等)。APB低速总线主要用于低带宽的周边外设(如UART、USB、SPI、I2C等)之间的连接,也称之为外围总线。
- u( d& S& v6 v5 e, [! F: D/ ]- @
, {1 Z# x5 }. k# o' A: e4 ` 2cb852a2a04e4280a317c6f134b1bf0d.png
2 d' U7 S7 q0 {; Q3 D
  O, j! d+ U: {. s! \& b图9.1.2. 8 STM32MP157A/D系统总架构图, `* x1 v) j9 v% c9 @/ j4 G
6. 时钟安全系统(CSS)
- ?% t+ {. K  d1 \7 |& ^9 t如果启用外部时钟HSE和LSE的话,硬件将启用HSE或者LSE上的CSS。我们在上面的框图以及STM32CubeMX插件上的时钟树均可以看到CSS模块。如下图,右边的是框图中的部分截图,右边的是STM32CubeMX插件上具体的截图。图中配置HSE为PLL3的时钟来源,在并将pll3_p_ck(PLL3P)作为MCU子系统的时钟源,可以看到Enable CSS模块自动变为深蓝色,表示使能了CSS。而没有使用的LSE,对应的Enable CSS模块颜色为灰色。5 e7 W3 Q5 v1 G/ M8 z. z7 ?7 ^4 `
' o& m4 l$ S* e% }0 W5 }! \5 W+ H4 _
e5d007b9b90a4ff6b501e362013506d3.png ) N8 l( o, a1 C5 y5 @5 A; s
: n: f1 ^* ?& [9 M; d
图9.1.2. 9外部时钟CSS
$ _4 X8 ^0 G2 }* O: Z  [RCC具有时钟安全系统(CSS),允许应用程序检测外部时钟(HSE、LSE)是否由于意外原因而停止翻转。
+ L8 q/ }5 i1 f/ X( V8 R4 J如果使用的是HSE,在检测到故障的情况下,CSS将生成系统重置,并保护BKPSRAM的敏感数据。时钟安全系统可以由应用软件通过HSECSSON位激活,(但无法通过软件清除HSECSSON位),即使将HSEON设置为0,也可以激活HSECSSON位。 当启用并准备好HSE并且HSECSSON设置为“ 1”时,硬件将启用HSE上的CSS。当禁用HSE时, HSE上的CSS会被禁用。- w/ s2 v8 g  j9 f2 X4 z  H
如果使用的是LSE,如果检测到故障,CSS将生成故障事件,禁用提供给RTC的时钟并保护BKPSRAM的敏感数据。也可以通过应用程序将RCC_BDCR寄存器的LSECSSON位置1来激活LSE上的时钟安全系统(CSS),但必须按照以下顺序在LSE上激活CSS:9 E& e! m+ }# m
①将LSEON设置为’1’,并等待LSERDY =‘1’;; \2 y0 g) K" d
②通过RTCSRC字段选择LSE时钟;
, w7 r6 z3 W2 \. ?③设置LSECSSON 到“ 1”。
( b" m  {" F2 F1 x% X) u关于CSS,我们这里不做深入研究,感兴趣的可以查阅参考手册以及ST官网相关介绍。
2 ]# C# [' G2 J3 ?9.2 RCC寄存器介绍
6 s# Z: Z; g, V2 g9 u* f3 W: J下面我们来看看RCC相关的寄存器。RCC寄存器分为RCC TrustZone相关寄存器,RCC振荡器时钟相关寄存器,HSI、CSI、MCO1和MCO2、AXI、AHB、APB、PLL、MPU和MCU相关寄存器,还有外设的内核时钟选择寄存器。这么多寄存器,我们要配置哪个寄存器就查看对应寄存器即可,这里就不一一介绍所有的寄存器了。而且HAL库已经将这些寄存器封装好了,调用HAL库即可控制这些寄存器。# X4 F9 c9 H8 F
9.2.1 寄存器分类介绍# `) p7 Z6 l) Q% l; `# k. G, h! a
2 G; b/ z2 k6 A# b* j) s6 m
RCC振荡器时钟相关寄存器
) P/ f9 B" ]3 W- q! A$ \+ HRCC振荡器时钟寄存器有RCC_OCENSETR、RCC_OCENCLRR、RCC_OCRDYR。; I8 t0 ?! y6 L
(1)RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)/ f8 s# H  v. o0 s& ~, B
4 U  x/ I2 K1 T1 I1 Q0 F
8876affd70514e3196944e77e73d8e4b.png
$ D: y# M" S- V2 M
0 s# v1 `, s( k, P) L- C$ `图9.2.1.1 RCC_OCENSETR寄存器) ?9 ?6 [3 h6 b( a. v
RCC_OCENSETR时钟用于使能(HSE、HSI、CSI)时钟,向该寄存器写入0无效,将对应的位置1则开启对应的时钟。如果TZEN = MCKPROT =‘1’,则只能在安全模式下修改此寄存器。在时钟恢复序列期间,不允许对该寄存器进行写访问。
! D. R9 B* c, f2 D% Y/ }该寄存器配置也比较简单,以HSE为例,如要开启HSE,则先将第8位HSEON置1来开启HSE时钟,再根据是否使用旁路模式来配置第10位HSEBYP,如果使用安全模式,则配置HSECSSON。
) V% n- f, }% u2 R(2)RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)" T" u# t$ N2 O9 F6 g4 Z

( b2 G2 B6 `3 V8 U- ^0 ` 1ba11a85ae33462d984e1a6322b69750.png ) ]' l1 K" a3 z

# p6 o. c- W/ S3 l" L图9.2.1.2 RCC_OCENCLRR寄存器4 F* ?5 V' U. ?( A9 O/ U0 A2 `
该寄存器用于清除寄存器对应的位,对位写0无效操作,写1表示清除设置的位,也就是关闭对应的时钟。如果TZEN = 1,则只能在安全模式下修改此寄存器。 在时钟恢复序列期间,不允许对该寄存器进行写访问。
3 B  W8 _2 e! N$ S. B$ [1 t例如,要关闭HSE,则将HSEON位写1即可。其它寄存器操作也类似。
; a+ Q% z+ m5 P9 S7 L7 a(3)RCC振荡器时钟就绪寄存器(RCC_OCRDYR)" i: D5 I- k* @5 F! I( U

) p9 G. E- J! [; ^ e02b509ccb6145208864d74bf14e7849.png
" W# c, B" U' ^! i; @2 r) O+ j) p
图9.2.1.3 RCC_OCRDYR寄存器# V) n. C6 F8 @) X, _+ x
该寄存器是一个只读访问寄存器,它包含振荡器的状态标志,写该寄存器的话是没有效果的。如果读取某位为0的话,表示该时钟未准备就绪,如果读取某位为1的话,表示该时钟已经准备就绪。例如,如果读取HSERDY为0,表示HSE时钟未准备就绪,如果为1,表示HSE时钟已经准备就绪。" F8 W  K" U3 ]; W' x- g0 F5 y& m
2. MCO1和MCO2配置寄存器' U" }6 t! i5 R
MCO1和MO2用于配置MCO1和MCO2输出,两个寄存器的配置类似,我们以MCO1为例:; C) Q. j# R" u0 d/ X6 E8 O

9 D7 F- R6 f' }, o) X 3c60b54c322441b7aad85a0b09004367.png
& v( e; s! ?1 D; @* V& ^5 I
; ]& b( b1 G' S! t图9.2.1.4 RCC_MCO1CFGR寄存器
* k3 H8 T" n. G5 gMCO1SEL[2:0]:为MCO1时钟输出选择位,由软件设置和清除。时钟源选择可能会在MCO1上产生毛刺,所以建议仅在复位后配置这些位,然后再使能外部振荡器和PLL。将该位:: Y: M+ j- u" x  C0 C
0x0:选择HSI时钟(hsi_ck)作为输出;
" x  U" Z. L% N7 J) O0x1:选择HSE时钟(hse_ck)作为输出;
+ V7 K9 T9 N% B& C$ |1 [3 j2 m1 T# t& [0x2:选择CSI时钟(csi_ck)作为输出;
5 Z0 L! i3 J5 l7 ^* Q6 W& |0x3:选择LSI时钟(lsi_ck)作为输出;
0 Y# A3 B" z+ e0 j7 q0x4:选择LSE时钟(lse_ck)作为输出;
0 Y# C4 C% B5 c4 Y; H) ~4 QMCO1DIV[3:0]位用于配置MCO1的预分频器,对该位配置:
' m6 Q' X% ^* J- s0x0:旁路模式;! v" l; g- E; U) _( k
0x1:分频值为2;- r' \+ v" D$ ?, v8 k0 P. ]/ K- U
0x2:分频值为3;- o% o9 B4 f# z4 ~3 ?2 Z3 o2 t# k

, Q) T0 P" w( r" K  b& n0xF:分频值为16.
1 l- M2 g, Z+ N: t* Z$ [& bMCO1ON位用于控制MCO1输出,将该位写0则关闭MCO1输出,写1则开启MCO1输出。
0 H# v4 g: s5 P5 o3. MPU和MCU时钟源选择寄存器, n* `( @  F& j  y# z
STM32MP157有两个A7内核和一个M4内核,RCC_MPCKSELR和RCC_MSSCKSELR分别用于选择MPU和MCU的时钟源。我们以MCU为例:7 p, L% p5 e: u: H; m  c4 l
(1)RCC MCU子系统时钟选择寄存器(RCC_MSSCKSELR)
# L2 W# K; Y8 m% z% g' U9 W: m2 Z& _9 S: T/ w6 _; I$ d
f8c5c012d33f48c99a4f794f41a7a5de.png 5 G! C- E) _, h5 R9 t

3 H- K% W6 Y/ N2 e! v) X图9.2.1.5 RCC_MSSCKSELR寄存器7 j5 W- d1 ^" @  i& |7 z
MCUSSRC[1:0]位用于选择MCU子系统的时钟(MCUSS)来源,通过配置该位可以选择MCUSS可以来自:
) M/ x2 @- P6 m0x0: 配置MCUSS时钟源来自HSI时钟(hsi_ck);& E& ^5 I5 ^* J: c
0x1: 配置MCUSS时钟源来自HSE (hse_ck);4 O; l' D/ u! t- Q- M1 m" j
0x2:配置MCUSS时钟源来自CSI (csi_ck);
5 o4 V# r( t) t0x3:配置MCUSS时钟来自PLL3 (pll3_p_ck); u3 i4 E" b: Z
MCUSSRCRDY属于只读位,读取该位为1,表示MCUSS已经准备好,可以切换到MCUSSRC配置的时钟;读取该位为0表示MCUSS未准备好。6 A9 g9 K3 N/ m2 H3 y7 b

5 Z5 j# K" h( |$ V: h通过软件置1和清除,以配置MCO1的预分频器。 修改该预分频器可能会在MCO1上产生毛刺。 强烈建议仅在复位后才更改该预分频器,然后再使能外部振荡器和PLL。9 M1 f) r% A7 ]" P

5 o! C4 k# Z. G$ Y有关详细信息,请参见第10.4.4节:时钟输出生成(MCO1和MCO2)。7 H% H, K2 T6 J# P  ^7 H+ Z
(2)RCC MCU时钟分频器寄存器(RCC_MCUDIVR)
6 f  V3 ]2 e! ?0 J
/ [8 }. M9 [9 S. a/ \9 r* h- W 9b2c9771d316418c82485db761ca7e7f.png ) M- [' ^! M' X6 f
; r# i1 w' Y+ V) E
图9.2.1.6 RCC_MCUDIVR寄存器+ w7 Z' W! `) F+ Z; D
MCUDIV[3:0]用于配置MCU时钟的分频值,配置该位:
4 h( V7 w" Y+ `$ d9 @/ Y0x0:MCU子系统时钟(也可以写为MCUSS或者mcuss_ck)不分频;. ^9 H- r9 S$ d- V
0x1:2分频;
# n0 s, n/ g1 p! U0x2:4分频;- x8 y9 F) J) P- |% p9 R
0x3:8分频;
: w' s' \/ S. ]' X
5 a: V! E+ N/ Y0x8:256分频。
0 V- a$ ?" C* ^7 Z: J其它配置:512分频
. E8 Z3 s& i+ `2 ~% QMCUDIVRDY属于只读位,用于指示是否考虑了新的分频系数,该位:* w$ R# [" z' T, w2 [
0:尚未考虑新的除法系数;
8 X3 `& _5 b& n+ a7 f- y2 V4 e0 Y1:考虑新的除法系数。(重置后的默认值)
8 O+ Y  x6 s( w6 K! L4 _: G9 A6 p, v4. HSI/CSI时钟配置寄存器8 q6 \* @. a6 ]) z
HSI和CSI时钟配置寄存器由RCC_HSICFGR和RCC_CSICFGR来控制,用于配置HSI和CSI的时钟源选择和分频器分频值等。( E$ e+ f; d9 Q6 L0 z; R8 q
(1)RCC HSI配置寄存器(RCC_HSICFGR)
: j# c6 b& K: H1 i# Z  ~- Q. D; W" L6 h) J/ N
a083fa8395a24d2a9613262e76b15983.png
. g! n3 s  Z# C0 F; Y& P; _
& D/ H+ C& j- i, Z$ ]  ?图9.2.1.7 RCC_HSICFGR寄存器
+ f! o' m: |$ FHSIDIV[1:0]用于配置HSI的时钟分频值:
( L4 [4 r' h6 x0x0:配置HSI为1分频,hsi_ck (hsi_ker_ck) = 64 MHz;
* q8 L. |6 M6 [# r$ B7 \6 L8 ~8 ^0x1:配置HSI为2分频,hsi_ck (hsi_ker_ck) = 32 MHz;
3 f( N" Z' ~! E8 Q7 T. {1 N. I) R, q% K0x2:配置HSI为4分频,hsi_ck (hsi_ker_ck) = 16 MHz;
8 @; M) }0 s* f- D7 ^: l0x3:配置HSI为8分频,hsi_ck (hsi_ker_ck) = 8 MHz;' g/ Z. d  M; N4 U3 [- b3 [6 f7 N
HSICAL[11:0]和HSITRIM[6:0]:用于配置时钟校准调整值,这两位我们可以不用管。5 i+ t) `# B0 T. F/ M
5. AXI/PLL1/PLL2/PLL3/PLL4时钟选择
- Q6 J! M( e* J1 _7 [6 z% z( `4 _AXI/PLL1/PLL2/PLL3/PLL4都有对应的时钟选择寄存器,用于选择其时钟源。
! h6 {( f/ u; I& U; wAXI子系统的时钟可以选择HSI、HSE、PLL2;! `; o0 v2 i( a: t
PLL1和PLL2时钟可以来自HSI、HSE;
" A! D' q* O. |% G2 ]$ \$ mPLL3时钟可以来自HSI、HSE、CSI;) k+ g- B# Y/ D4 ~9 ]) ?# e! {" K
PLL4时钟可以来自HSI、HSE、CSI和I2S_CKIN。
, A) A) @; c- q* }8 g以上相关时钟源选择配置寄存器的配置也比较简单,我们就不一一举例子了,如需要,大家按照要求来配置即可。1 M+ Z* D) ^3 M6 d3 k+ j5 l$ I% C
5. APB、AXI分频寄存器, {3 P4 p; k, M+ z  z
APB1APB5以及AXI都有时钟分频器,可以设置时钟分频值。AXI时钟分频器可以配置分频值为1、2、3和4。APB1APB5分频器的分频值可以配置为2、4、8和16。相关寄存器的配置也比较简单,我们这里就不一一介绍了。, e2 p# c* F. R3 ?  U
6. PLL控制和配置寄存器
) w& J* f0 I* M2 n5 q8 G6 wPLL1~PLL4有专门的控制寄存器和配置寄存器,我们这里以PLL1为例进行介绍。! k7 k( {$ }0 K3 u) D
(1)RCC PLL1控制寄存器(RCC_PLL1CR). N0 X. q6 X1 C# i5 @
3 P0 Y# `- ?' q* N1 O, u
0742699e9828475fad4b380e502f2de7.png 8 }' x: O# c! ^
" r( L( F. ~' j! u* h2 b! {) L' y
图9.2.1.8 RCC_PLL1CR寄存器
) V& h6 o8 X$ j/ d' _0 @PLLON位用于使能PLL1,将该位置1表示使能PLL1,复位表示将PLL1关闭;0 C$ z& {3 q1 i
PLL1RDY表示PLL1时钟就绪标志位,属于只读位。该位为1表示PLL1处于锁的状态,为0表示PLL1处于未锁的状态;
- i4 l1 \3 B) `5 D; K+ vDIVPEN表示PLL1 DIVP分频器输出使能位,通过软件置1和复位,以使能PLL1的pll1_p_ck输出。该位为0,表示pll1_p_ck输出被禁用,为1,表示pll1_p_ck输出启用。为了节省功耗,当不需要pll1_p_ck时,必须将DIVPEN和DIVP设置为’0’。# a/ o& G; a* b( R2 c  L/ W
DIVQEN是PLL1 DIVQ分频器输出使能位,该位为0,表示pll1_q_ck输出被禁用;该位为1,表示设置pll1_q_ck输出已启用。
- R" y) E/ F' T' l4 K- FDIVREN表示PLL1 DIVR分频器输出使能位,该位为0,表示pll1_r_ck输出被禁用;该位为1,表示设置pll1_r_ck输出启用。
! u& o! w% @" e4 _2 w(2)PLL1配置寄存器(RCC_PLL1CFGR1和RCC_PLL1CFGR2)- [4 j$ l5 j% ?0 C+ R, _( w. b0 V
PLL1配置寄存器有2个,其它PLL配置寄存器也是有2个。PLL1配置寄存器1(RCC_PLL1CFGR1)如下:
% ]) K8 q0 r  Z( k& O3 j
4 c" T' M6 Q7 R( r! i( ^ bf2944a136fa46ac8ad778b863fad17f.png
8 i* p4 k7 v! |# \1 K, w. u+ b0 b. |
图9.2.1.9 RCC_PLL1CFGR1寄存器. }, Y) j. T* C: ~$ g  {0 ?- x3 r
DIVN[8:0]位用于配置PLL1 VCO的倍数,将该位配置为:
8 f7 v( T! B" Q0x18:分频比为25;$ f, H3 D- b0 B; q( C4 e
0x19:分频比为 26;! T* m$ E7 b! h$ B0 f  ^' K

8 z3 M5 L( _  \$ k  J( z6 Y0x31: 分频比为50 (default after reset);
8 `: a5 K: U& Q" L% x9 l1 e- s9 o* Q" ]9 ]
0x63: 分频比为100。
' `5 j( Y4 J( w4 P. n3 T- l& vPLL1配置寄存器1(RCC_PLL1CFGR2)如下:. _0 O3 ^' i& k" F% j

6 m3 t( B  g1 o6 W. ^" F$ T* U# n 3ccaafe4c3da48839d9b589ca41a2adb.png
3 H" S$ P7 a# d8 {2 }+ v2 }* @) E2 n- T& l
图9.2.1.10 RCC_PLL1CFGR2寄存器
/ y' {; q$ w: y6 e7 J& }DIVP[6:0]位、DIVQ[6:0]位和DIVR[6:0]位分别用于配置PLL1 DIVP、PLL1 DIVQ和PLL1 DIVR的分频系数,要配置为什么样的分频值,根据手册来配置即可。! H5 y- F7 i8 H6 t3 m/ C* q
7. PLL小数寄存器
/ I  w9 \: l- M8 ]0 V* _+ c我们前面也说过,锁相环PLL的 FRACV是分数倍频系数,它和DIVN一起组成PLL的倍频系数,当FRACV等于0的时候表示在整数模式下,大于0的时候表示在小数(或者说分数)模式下。每个PLL都有其配置小数寄存器。我们以PLL1为例子。RCC PLL1小数寄存器(RCC_PLL1FRACR)如下:3 y& |( G) w) t7 z
6 X8 X7 o3 L+ o  L, \- l
d440d6ac05174c66a848598dde6f8bbd.png
4 m' q/ O5 g/ L2 T: L3 N$ O  o' H7 ^- K$ `$ r
图9.2.1.11 RCC_PLL1FRACR寄存器
# C" a. h4 N& `7 VFRACLE是PLL1分数使能位,该位必须先设置为0然后设置为1,这样,通过从0到“1”的转换将FRACV的内容传输到调制器中。
% w0 y" q4 }6 F3 G7 `  y0 PFRACV[12:0]是PLL1的乘法因子的小数部分。FRACV可介于0和213-1之间。* F" a% q! R* k/ r8 `$ S
9.2.2 外设时钟使能和关闭6 B6 m  g. P3 ~! a+ `
我们要注意,STM32MP1外设的时钟在上电复位以后默认处于关闭状态,即这些外设都不可用,这么做的目的就是为了降低功耗,如果我们要使用某个外设,必须要先打开外设的时钟才可以对外设进行操作。% g' R8 Y5 E5 ^. r! a7 [0 G8 J6 I
在RCC外设相关寄存器中,我们会看到有两种寄存器,一个是以“SETR”结尾的寄存器,一个是以“CLRR”结尾的寄存器,前者是使能寄存器,后者是关闭寄存器。
3 `5 ^8 X, n1 Z) `* r例如,APB1总线挂载的设备有窗口看门狗(WWDG)、I2C1~ I2C3、USART2、UART8和TIM2~TIM7等等外设,如果要使用WWDG,就必须先开启WWDG的时钟。对于MCU,APB1外设的时钟由RCC_MC_APB1ENSETR寄存器来开启,其第28位是WWDG时钟使能位,对该位:
' B# a4 }/ }! P; n, q- j% h写’0’表示无效操作,读取该位,读到’0’则表示该外设时钟已经被关闭;
4 }: }, L5 ]. a, q& r写’1’表示开启外设时钟,读取该位,读取到’1’意味着外设时钟已经开启了。
8 ^+ C+ n; K% C1 d# ?
% N' Y) X3 e3 y! L+ w% K c6fba1244076415d909b1f6471e41527.png
, W8 E  c* y7 Y4 j) e4 g- o, @' K6 i& h- @* F9 E6 J
图9.2.2.1 MC_APB1ENSETR寄存器
1 y+ f* ~' V; @- r* H1 [另外一个寄存器RCC_MP_APB1ENCLRR用于关闭APB1外设的时钟,我们看看此寄存器:. {5 J0 H/ d2 i! [5 I  O/ W
( ]# b- N9 l5 @6 b; X! f! d
6c94498bbff3468fbace7418e34faa6b.png 1 r- i5 q- ]. {# H
+ e+ l1 c- K9 _, B1 ]' P
图9.2.2.2 MP_APB1ENCLRR寄存器( w6 x( k( U% {
可以发现,并没有看到控制WWDG的时钟位,第28位是保留的,而其它外设,例如I2C1~ I2C3、USART2、UART8等对应的位有看到,对这些位:
* D% ?$ P/ {) i6 }写’0’表示无效操作,读取到’0’表示已经关闭外设的时钟了;; _" q6 r2 L; l
写’1’表示关闭外设时钟,读取到’1’表示已经开启外设时钟了;7 `8 o' s7 B: b1 `! X
为什么窗口看门狗(WWDG)在RCC_MC_APB1ENCLRR寄存器中没有对应的时钟操作位呢?因为WWDG这个外设比较特殊,只要开启窗口看门狗以后就不能将其关闭了,必须等到系统重置以后才可以禁用窗口看门狗,而其它外设就不一样了,其它外设可以通过对时钟操作位写1来将其时钟关闭。
8 [2 i$ n4 K$ T, n在HAL库的stm32mp1xx_hal_rcc.h头文件中有所有外设时钟开启或者关闭宏定义:8 |9 x1 {; ^8 U, [4 M1 V" [

1 J- E, f  R7 h, I- u f3442a0ad69149bab724a23b0567a31c.png
- X% E6 H$ v; J; Z/ C' N* D* i* R8 U5 C/ L6 c) ]% ]& @* \
图9.2.2.3 外设时钟使能/禁用宏定义
- l2 e: q1 m( [  m: b* ]* z; f例如,WWDG部分:7 X# N: ^% m7 `$ ~& e, Q1 ~, E2 p
1 #define __HAL_RCC_WWDG1_CLK_ENABLE() (RCC->MC_APB1ENSETR = \ RCC_MC_APB1ENSETR_WWDG1EN)* b; T7 k! y4 z% f2 @
2 #define __HAL_RCC_WWDG1_CLK_DISABLE() (RCC->MC_APB1ENCLRR = \ RCC_MC_APB1ENCLRR_WWDG1EN)4 ]. V. z. s5 n  H# W# `  w% y% `
RCC_MC_APB1ENSETR_WWDG1EN和RCC_MC_APB1ENCLRR_WWDG1EN在stm32mp157axx_cm4.h头文件中有定义:
- ]3 \+ @6 W2 k#define RCC_MC_APB1ENSETR_WWDG1EN B(28)
0 T3 t8 {- X  G8 W2 k#define RCC_MC_APB1ENCLRR_WWDG1EN B(28)
: o$ w. r1 M; b" w" uB(28)则表示将第28位置1,所以(RCC->MC_APB1ENSETR = RCC_MC_APB1ENSETR_WWDG1EN)就表示将RCC_MC_APB1ENSETR寄存器的第28位写1,也就是开启WWDG这个外设的时钟。在代码中,要开启WWDG的时钟的话,直接调用__HAL_RCC_WWDG1_CLK_ENABLE()这个宏就可以实现,其它的所有外设的时钟开启都可以类推。
. N7 O9 G& Y( e' H3 }同理,(RCC->MC_APB1ENCLRR = RCC_MC_APB1ENCLRR_WWDG1EN)表示关闭WWDG的时钟,不过因为WWDG特殊,系统运行的时候是不能将WWDG时钟关闭的,所以寄存器RCC_MC_APB1ENCLRR并没有针对WWDG的操作位,所以调用__HAL_RCC_WWDG1_CLK_DISABLE()这个宏是没什么实际意义的,不过对于其它一般的外设,例如I2C3、USART2、UART8等,调用对应的宏就可以将外设时钟关闭了。
. t$ d7 t6 s% B2 c/ s& P又例如GPIOA~GPIOK是挂在AHB4总线上的,在HAL库的stm32mp1xx_hal_rcc.h头文件中同样有GPIO时钟开启和关闭宏定义,操作方法也和上面的外设类似。7 o+ ]. Y- s% r
$ G* F: d! a8 g
d4ede78f54cb41a29fc35fda2fb541fd.png 0 t2 h$ I! Z, B- @$ S- @

& Y" O- @6 M" A( h4 ~3 q图9.2.2.4 外设时钟使能/禁用宏定义8 s& o2 r$ I1 G/ r0 n1 f
事实上,用STM32CubeMX插件生成的初始化代码中,也就是通过调用这些宏来开启外设时钟的。$ x6 j; ^, M. t% Y" j% |- \
9.3 时钟相关API函数% S4 ?# g$ X% K% E$ Q! S2 ]
头文件:stm32mp1xx_hal_rcc.h。
! C. V5 V* n+ Q; f. W) bstm32mp1xx_hal_rcc.c是RCC相关的HAL模块驱动程序文件,主要管理复位、初始化、取消初始化和外设时钟控制的功能。
! |2 S  K# m3 A3 J复位后,设备从内部高速时钟(HSI 64MHz)运行,并且所有外设均关闭,但内部SRAM1,SRAM2,SRAM3和PWR除外。高速(AHB)和低速(APB)总线上没有预分频,这些总线上映射的所有外设均以HSI速度运行。
4 C6 n2 K: q2 N& D3 K  c, j- H设备复位后,如果用户需要更高的频率/性能,需要进行如下操作:
6 g% F5 k( `0 \①选择用于驱动MPU、AXI和MCU子系统的时钟源;
: R8 ~8 a5 w; [, t8 j! b②配置AHB和APB总线分频器;) F* \3 X5 E, _  ?) B" S
③对于那些不是从总线获得时钟的内核外设需要配置内核时钟源;
0 `6 z: B+ J6 `; N5 f④启用要使用的外设的时钟。
' ^' J  m4 k2 @; F/ j) `不管使用哪个时钟源,都是通过软件对相关寄存器的操作来实现的,我们直接通过HAL库的API函数来实现控制寄存器操作,下面,我们来分析一下stm32mp1xx_hal_rcc.c文件中的API函数。7 M" t1 s* e! m* c
9.3.1 函数HAL_RCC_DeInit
; a: c% r3 R/ I, H●函数功能:主要就是将RCC时钟配置为默认重置状态,主要做了以下操作:/ Q% }* L. d$ Q
①将HSION位置1,将HSI用作系统时钟源;
% t' L% Z/ o; w2 e. y②将MCO1、MCO2HSE、PLL1、PLL2、PLL3和PLL4关闭;
) v3 r$ j. X, \" d- y  p③AHB,APB总线预分频器设置为1;- W* V/ j, T$ {
④禁止所有中断(从CSTOP中断允许唤醒除外)。, G! e! I# _3 g- n5 W* @
●函数返回值:
, J+ T: A( g: G% a2 l% k4 m5 m枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)! R! y$ U* \3 [  t# X" n0 Y4 i9 |
●注意:此功能不会修改外设、LSI、 LSE 和 RTC时钟,不会更改HSECSS(HSE时钟安全系统HSE Clock security system)和LSECSS(LSE Clock security system)。* {0 F- L1 v* L4 I1 ^4 A% J8 M% E3 e9 ^' ?
函数HAL_RCC_DeInit的部分代码如下,因为代码很多,我们省略掉了部分代码。
4 X& r: U0 a8 ~. n- ~, f" w$ @. L, p- G
  1. 1   HAL_StatusTypeDef HAL_RCC_DeInit(void)
    5 ]% Y4 `- O5 ~. f8 S
  2. 2   {& w$ D$ a( X# ]
  3. 3     uint32_t tickstart;0 j/ @* y* i2 ]) ?6 z) e: |, R
  4. 4
    # G7 u$ b6 I3 o" y6 s% x4 L  J
  5. 5     /* 将HSION位置1,使能HSI振荡器 */
    2 Y' n/ r& v3 z  U& \6 v
  6. 6     SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSION);
    : m6 S0 p1 P4 z( ~( W  v' a
  7. 7 6 V( D) M* H# V. f; P
  8. 8     /* 获取全局变量uwTick当前计算值 */
    8 s+ T+ U4 n2 m2 ^" m  g; B( z* P
  9. 9     tickstart = HAL_GetTick();$ \8 T* [% c) G8 H6 H, |
  10. 10
    , q7 J& Z, c: m6 ^! w8 W
  11. 11    /* 等待HSI准备就绪 */
    ; @+ Y7 g% e: k1 B) I% z' F9 d
  12. 12    while ((RCC->OCRDYR & RCC_OCRDYR_HSIRDY) == 0U). ~7 p0 l5 H) ~5 U$ u5 u
  13. 13    {  K/ L$ ~( i  L6 w8 D$ W% h( L* n
  14. 14      if ((HAL_GetTick() - tickstart) > HSI_TIMEOUT_VALUE)  T/ [  P$ U! L8 a1 Z
  15. 15      {
    4 J" G( s1 q( V! e# }  p
  16. 16        return HAL_TIMEOUT;4 {; \. S/ m$ o% o3 A
  17. 17      }7 W0 n' O) w* i" o* d* t
  18. 18    }7 v$ R5 c" x- ?$ Y' q' e% t$ z
  19. 19   
    . k- ]( r# x/ H! f$ f3 p% ~: i3 A
  20. 20  /*$ p& U& [# h9 _& Q
  21. 21  *省略掉代码......
    4 n( U4 b, T) Y' Y
  22. 22  *关闭MCO1、MCO2HSE、PLL1、PLL2、PLL3和PLL4' k& m; t3 J- X: U3 h: J2 \
  23. 23  */+ x& K, R2 m- {2 T
  24. 24   
    9 ]1 g. t% ~8 Y4 d% g3 c/ L: J
  25. 25    /* 等待HSIDIV分频器就绪*/
    0 Y7 Z7 L+ N, O- S
  26. 26    while ((RCC->OCRDYR & RCC_OCRDYR_HSIDIVRDY) == 0U)5 y7 ]5 l8 V# F) N7 @( o* j! o
  27. 27    {7 [# f" P! F4 Q: w, C, A
  28. 28      if ((HAL_GetTick() - tickstart) > HSI_TIMEOUT_VALUE)
    + F+ ~( h8 B% z2 {+ v5 i3 c7 z8 e
  29. 29      {
    ( k' b. \5 Y9 t2 v& v
  30. 30        return HAL_TIMEOUT;
    " @9 Z4 u" W% E
  31. 31      }$ j1 D2 b5 h  |/ ]. [: ?- L0 c
  32. 32    }4 o2 C, N  e2 Z% x
  33. 33  /******省略掉代码******/
    3 p7 o' L9 J9 {3 F+ B6 U
  34. 34    /* 更新SystemCoreClock全局变量 */
    ) Z. m! h& A" L& L/ b
  35. 35    SystemCoreClock = HSI_VALUE;' B% ^( x1 ?& B. a, g, P3 C
  36. 364 H: L) g8 j' M8 _
  37. 37    /* 调整Systick中断时间 */0 a  I6 h( c9 `$ q' @
  38. 38    if (HAL_InitTick(uwTickPrio) != HAL_OK)
    6 @# y; z, {& P
  39. 39    {' i+ M% C. ?9 T3 G2 M5 P
  40. 40      return HAL_ERROR;
    8 D  ?- K7 Q) O' ~$ c7 J
  41. 41    }
    : b1 M& u9 a$ [8 H1 m' B
  42. 428 m3 P, i1 _0 [# @+ ?, b; [
  43. 43    return HAL_OK;
    ( s  X& r$ w- i/ s8 ?
  44. 44  }
复制代码
7 \. i  C) C4 |: D5 d0 {
我们简单分析这部分代码:
* R3 |+ r1 y2 f1 N7 j1 r第6行,通过SET_BIT置位操作,将RCC_OCENSETR寄存器的HSEON位置1,表示使能HSI振荡器。, k+ d' M  b/ O7 m" N' ?
第9到,先获取全局变量uwTick当前计算值。& m( `1 ~3 A- i
第12行,等待HSI是否已经就绪。
+ _8 L$ U( c4 u4 U- @$ r( U, o* `, w, Q% m: n: |' T
注:RCC_OCRDYR是振荡器时钟就绪寄存器7 o* P6 V/ O- ?0 I4 p* \8 {- t+ T
①第0位HSIRDY是HSI时钟就绪标志,由硬件置1,以指示HSI振荡器稳定,如果此位为0,表示HSI时钟未准备好,如果此位为1,表示HSI时钟已准备好。
3 I: w0 e4 j/ G' I/ a7 k②第2位HSIDIVRDY是分频器就绪标志位,由硬件置位和复位。如果此位为0,表示新的分频比尚未传播到hsi_ck(hsi_ker_ck)(复位后默认),如果此位为1,表示hsi_ck(hsi_ker_ck)时钟频率反映了新的HSIDIV值,即分频器已经准备好。5 u* q; l( I6 ]0 w. p4 G
③第4位CSIRDY是CSI时钟就绪标志,由硬件置1,以指示CSI振荡器稳定。如果此位为0,表示CSI时钟未准备好(复位后的默认值),如果此位为1,表示CSI时钟已准备好。
& F8 H, f; I( W3 m7 Y4 l- K④第8位HSERDY表示HSE时钟就绪标志由硬件置1,指示HSE振荡器稳定。如果此位为0,表示HSE时钟未准备好(复位后的默认值),如果此位为1,HSE时钟已准备好。
, m, e$ A! R$ K* c# G) E( z" u1 j. Q* o% u9 T6 N' Y+ j! U
3a5b32650887435b967fd4886a783761.png
) c: Y3 `/ ^; P. P  t& {8 D  ]" _" R' C) r6 ~# Q
图9.3.1. 1 RCC_OCRDYR寄存器; h$ g( v, v+ m+ J0 U4 W2 f- C  j7 ^
代码中,RCC_OCRDYR_HSIRDY值为0x00000001。如果(RCC->OCRDYR & RCC_OCRDYR_HSIRDY) == 0U,表示HSI振荡器未稳定,程序会卡在while循环中。如果两次取得的全局变量uwTick的差值大于HSI_TIMEOUT_VALUE(100ms),表示HSI超时,程序返回HAL_TIMEOUT标志并退出while循环。$ M- U+ I! }+ Y* T7 Y
第26到第32行,表示等待HSIDIV分频器就绪。
+ t8 i) P: P8 }+ r第35行,更新SystemCoreClock全局变量的值为HSI_VALUE。SystemCoreClock变量用于存储系统时钟频率,可以用来设置SysTick定时器或配置其他参数,每次时钟变化时,都应该更新它,这样也是为了保证SystemCoreClock的准确性。我们在第6.3.4小节system_stm32mp1xx.c文件中有分析过。- N8 |8 s% I4 V
第38到第41行,调用HAL_InitTick函数更新Systick中断时间(1ms),因为很多地方要用systick作为时基源,所以时钟源变化后,要将其更新。如果HAL_InitTick函数没有运行成功,程序将返回HAL_ERROR。HAL_InitTick函数我们在7.4.2小节的stm32mp1xx_hal.c文件中有分析过。
+ O  ], ?6 T3 f  t# Y9.3.2 函数HAL_RCC_OscConfig8 L6 `/ v5 p: `# S4 R5 U4 E* f" q
●函数功能:主要就是配置 HSE、HSI、LSI、LSE 和 PLL(PLL1、PLL2、PLL3和PLL4)。
( r1 v- d" w6 X% n, ^8 r- d+ t●函数返回值:
6 K9 q' V, C% F0 R" V枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
. \, S. z4 L0 c8 P' a; T8 D$ C  Z; ^. z注意:当PLL用作系统时钟时,PLL不会被禁用。! n4 K) \% X! G# N
函数部分代码如下:
0 V* y' L2 ]2 ^
+ m: u% q! o# N3 C) N
  1. 1   __weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef \                                                                                                                                  *RCC_OscInitStruct)
    # h- j- c2 O* Z& R
  2. 2   {
    . @. r: v& [) Y. d/ P7 D
  3. 3     uint32_t tickstart;9 Q( L3 w8 q' y& s6 E4 r' \" G$ a
  4. 4     HAL_StatusTypeDef result = HAL_OK;  M) D  \. V- Q; c' t$ S
  5. 5 + [7 E" F( f0 M0 p0 D) D" _
  6. 6     /* 检查是否是空指针 */) Y* s9 A; m9 h
  7. 7     if (RCC_OscInitStruct == NULL)
    ' Z+ A2 u: ^" R7 t
  8. 8     {
    * `; d) K- m5 g* I# P8 x& v9 ~4 J
  9. 9       return HAL_ERROR;
    + g2 b: k6 Y! H9 e  [
  10. 10    }$ Y; |* \- C! v: p
  11. 110 _; F$ W1 o- v& T
  12. 12    /* 使用断言检查参数 */
    : K$ j# b+ {: H' m7 F- f( f
  13. 13    assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
    & y9 @$ V% u& v( Y- Q
  14. 14  
    + e+ `$ N7 _  B% }* \0 a5 g
  15. 15    /******省略HSE、HSI、CSI、LSI、LSE、PLL配置代码 ******/
    ' w' q/ z6 ^8 S
  16. 16    return HAL_OK;
    " b  J& D: M. G* b4 b+ D* P
  17. 17  }5 V1 K  [' K# i: H) a1 H
  18. 18
复制代码

" R4 O7 T2 C. _! Zweak表示函数是弱定义,用户可以在其它地方重新定义一个同名的函数。函数参数是RCC_OscInitTypeDef 类型结构体变量,主要对RCC内部/外部振荡器(HSE,HSI,CSI,LSE和LSI)配置的结构定义,RCC_OscInitStruct是指向RCC_OscInitTypeDef结构的指针,RCC_OscInitTypeDef 类型结构体声明如下,通过指针可以操作结构体中的成员变量。例如,如果要选择HSE为振荡器:0 X/ q0 w" z+ ~, w0 s+ \4 u
①设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE;4 C. P& U/ f6 I# h3 E& ?) N
②然后设置HSEState的值为RCC_HSE_ON开启HSE;( a: p, g" w! p( X6 c
③如果用到PLL,则配置对应PLL的参数。; |) i9 K, i9 Q$ O- z) i7 F* b( U4 e
对于其它的时钟源(HSI、LSI、LSE和CSI)配置方法类似。0 v* [2 q2 Y& z; |: M- \9 S, S7 l

: U; A# ^" j8 _: j- [
  1. stm32mp1xx_hal_rcc.h文件代码+ r3 `+ Y: Z; _) l* f4 ]- |
  2.     typedef struct4 v! d2 x" C, ^9 p5 q8 e
  3.     {
    ' a/ X- `3 A* Y. W: k% u$ ]
  4.       uint32_t OscillatorType;                                 /* 要配置的振荡器 */                                                     ; ], E- B; o" a) P( a
  5.       uint32_t HSEState;                                           /* HSE的新状态 */                                                                  
    , Q  k2 y4 \8 v* b
  6.       uint32_t LSEState;                                                 /* LSE的新状态 */                                                                       & v" S5 ?# X2 @2 ^' d. z3 R
  7.       uint32_t HSIState;                                           /* HSI的新状态 */                                                                  
    & ?/ S2 n  `1 l- H
  8.       uint32_t HSICalibrationValue;                         /* 校准调整值 */                                      & e$ t) t2 Q4 @& b, x
  9.       uint32_t HSIDivValue;                                          /* HSI的分频系数 */                                             0 t8 r9 n8 M$ g+ S8 O
  10.       uint32_t LSIState;                                          /* LSI的新状态 */                                                
    & ^+ n$ B2 F9 I6 i+ O
  11.       uint32_t CSIState;                                          /* CSI的新状态 */                                                 5 m, c8 M' ]- |& `+ A& c! d
  12.       uint32_t CSICalibrationValue;                         /* 校准调整值 */                                       . w  U* i! z; {
  13.       RCC_PLLInitTypeDef PLL;                                 /* PLL1结构参数: */      
    , j+ r4 G0 k' K. B5 N) z
  14.       RCC_PLLInitTypeDef PLL2;                                /* PLL2结构参数 */     
    : L7 \8 K. m7 d+ d# @. @4 m
  15.       RCC_PLLInitTypeDef PLL3;                                 /* PLL3结构参数 */     
    + }* U) T' n; O6 ^+ h
  16.       RCC_PLLInitTypeDef PLL4;                                 /* PLL4结构参数 */                                                  " }; h3 E( ~4 e$ V8 v
  17.     } RCC_OscInitTypeDef;
    - [/ M) u3 n: C: p" p* D$ I( [
复制代码
$ U# V9 D8 m# ~8 P6 G
我们查看PLL的RCC_PLLInitTypeDef结构参数有哪些,如下,此结构体定义了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量,通过给这些结构体成员赋值即可配置PLL的时钟。
  H0 L1 c; W, ^0 q4 M% J+ E6 [+ s- ~  W
( @5 e1 Y$ c5 s5 s, f* ]# ?6 O
  1. stm32mp1xx_hal_rcc.h文件代码. T! p% E0 z5 v; @+ I& Z3 X
  2.         typedef struct
    ' I8 ^+ C9 m. I, e' V2 Q
  3.         {
    # n( K  n; M/ h& c1 l, {
  4.           uint32_t PLLState;                   /* PLL的新状态 */                                             
    7 Z/ R1 ~9 Y7 Z# Y
  5.           uint32_t PLLSource;                  /* PLL输入时钟源 */                           
    2 Y% q( A5 x& B9 H+ n
  6.           uint32_t PLLM;                       /* PLL VCO输入时钟的分频系数DIVM */                           3 @5 v9 K3 k0 S& F% E
  7.           uint32_t PLLN;                       /* PLL VCO输出时钟的倍数DIVN */                            ; I: }+ o1 v& {; h7 v& Q* C
  8.           uint32_t PLLP;                       /* 分频因子DIVP */                          5 J$ u, G9 k+ p7 ?
  9.           uint32_t PLLQ;                       /* 分频因子DIVQ */                           ) v. z/ D. w5 h* Z2 k+ o  w. d$ M
  10.           uint32_t PLLR;                       /* 分频因子DIVR */                           
    ; J, V7 b* w7 X+ V; c
  11.           uint32_t PLLRGE;                     /* PLL3和PLL4的PLL输入频率范围 */                          
    $ Z7 n6 ~# j  v$ _
  12.           uint32_t PLLFRACV;                   /* PLL1 VCO乘数的小数部分FRACV */                          - e; H; V$ _7 u" f6 ^$ N' l. Y
  13.           uint32_t PLLMODE;                           /* 使用PLL模式 */                           
    5 J$ @- g3 c: G; G: G+ n9 G
  14.           uint32_t MOD_PER;                           /* 调制周期调整 */                          ( c7 o" u5 ~- ]0 u& T: q. m. S% H
  15.           uint32_t RPDFN_DIS;                  /* 抖动的RPDF噪音控制 */                           
    + B" ^1 \7 S* R
  16.           uint32_t TPDFN_DIS;                  /* 抖动的TPDF噪声控制 */                          
    . E8 H( E0 m' F
  17.           uint32_t SSCG_MODE;                  /* 扩频时钟发生器模式 */                     + W6 h5 h+ V8 u% U$ [& b8 l
  18.           uint32_t INC_STEP;                   /* 调制深度调整*/                           3 ?% K2 w& d. M2 N" t/ q# A
  19.         } RCC_PLLInitTypeDef;
复制代码
% p! ]3 ^# X- \  ^5 d
在HSE、HSI、CSI、LSI、LSE、PLL配置代码部分,我们主要分析HSE、HSI和PLL配置部分。7 y/ a: b. U3 t

+ R9 X4 d* }6 n; P$ h6 VHSE配置部分. T# T! Z0 D4 P: J" C
  1. 1   if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) \, K' q) F2 b4 I5 S' s7 u
  2.                                                                                                 == RCC_OSCILLATORTYPE_HSE)
    9 g& z' z4 \$ s% w3 l+ w9 a# Q* U- ?/ g
  3. 2     {0 i. x( D/ C. t
  4. 3       /* 使用断言检查检查参数 */
    $ _$ H  X+ Z/ R$ H
  5. 4       assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));
    3 [1 e' S9 d, k1 Y
  6. 5       /* 当HSE在系统中的某处使用时,将不会被禁用 */8 t8 |( c& U; n6 P$ a* Q7 ]
  7. 6       if (IS_HSE_IN_USE())4 m$ T  K, @. A5 p2 H
  8. 7       {
    % }8 c" P7 c6 c/ i1 ]6 c) M
  9. 8         if ((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && \                                                                         (RCC_OscInitStruct->HSEState != RCC_HSE_ON))
    4 H% l5 W  t9 y) @6 m$ A9 ~
  10. 9         {
    4 X5 m6 J% {8 h9 U- G, d2 H
  11. 10          return HAL_ERROR;
    ( X6 a: p/ q( b) ~$ l
  12. 11        }
    + ?7 C' o$ C7 N, o
  13. 12      }
    * @6 c3 R$ V- I' v3 Z" ?$ r
  14. 13      else
    8 `. F2 c! j3 h0 i9 r$ J
  15. 14      {
    ; @% {- `( t$ m. U
  16. 15        /* 配置HSE振荡器 */' Q" X: m9 V5 @9 J9 b
  17. 16        result = HAL_RCC_HSEConfig(RCC_OscInitStruct->HSEState);5 a4 ~9 i  b9 ^! {" j
  18. 17        if (result != HAL_OK)
      x0 l0 {6 M2 r, f6 d  f# E' l5 Q& W
  19. 18        {
    # Z8 P7 v. C/ L' M, w1 N" p
  20. 19          return result;9 U# P6 z" y7 i0 p/ A. z* K
  21. 20        }* T7 W; p8 P8 |5 Q0 s
  22. 21      }; D8 W! u6 R- j3 I3 O6 G. h, L
  23. 22    }
复制代码

: L* k$ }! m; A  Y2 Z第1行,通过RCC_OscInitStruct指针判断振荡器是否选中了HSE,如果选中了HSE,将进行后续的配置操作。
: |, {5 a5 T3 g* C: ?' C4 W第4行,使用断言检查HSE参数的状态设置是否正确。
( {3 V/ M; C0 N# `. ~& I0 D第6到第12行,判断HSE是否有在使用中,如果有在使用,则不会被禁用。        (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)用于判断RCC_OCRDYR振荡器时钟就绪寄存器中的HSERDY位是否为0,为0的话表示HSE时钟未准备好(复位后的默认值也是0)。
6 r0 y. ~  w7 w5 z(RCC_OscInitStruct->HSEState != RCC_HSE_ON)表示HSE的状态没有被开启。+ h1 z* C; ^5 s6 c& s1 r
如果HSERDY位为0,且HSE的状态没有开启,那么HSE没有被使用,程序返回HAL_ERROR。是否已经开启HSE,这取决于用户,所以程序要通过判断HSERDY位以及用户的设置状态来判断到底HSE用还是没用。
( O, y; ]7 {* [& `) V第16行,调用HAL_RCC_HSEConfig函数完成HSE振荡器的初始化。
. _7 w0 f8 e$ u' g- e5 m7 u& u程序中主要实现操作3个寄存器,一个是上面提到的RCC_OCRDYR,另外两个是RCC_OCENCLRR核RCC_OCENSETR寄存器。  C, ~: f+ c. R, R

* s. r) Q, v, ~注:
8 _6 b1 m: o: `RCC_OCENCLRR寄存器用于控制振荡器,向该寄存器写入0无效,写入1将清除相应的位。
9 B$ ]0 E  v3 w. S* D4 ?5 g, D第0位,HSION:写1表示清除HSION位,禁用HSI。0 s  t( T  _9 m5 V
第4位,CSION:写1表示清除CSION位,禁用CSI。2 `5 {# B% m+ D) y2 E7 V
第7位,DIGBYP:当连接到OSC_IN的外部时​​钟为低摆幅信号时,软件将其清零。写1表示清除DIGBYP位(模拟旁路),禁用  R: k$ e  C, ~! b- h
第8位,HSEON:写1表示清除HSEON位,禁用HSE。* u$ \" ?+ L( D4 f' J) M' y; ]
RCC_OCENSETR寄存器的功能和RCC_OCENCLRR寄存器的功能相反,RCC_OCENSETR寄存器表示开启对应的功能。8 z! _8 J, t3 o0 A" N. v; V8 a
$ q7 j1 a. t8 Y; d; e
298e5ac5fe91479dabc86261e4b7c2a8.png
, y0 P0 \) b% c) _
% ^7 \$ {/ y+ l+ [图9.3.2. 1 RCC_OCENCLRR寄存器' e; ]. Y  c2 b2 c; D1 C9 l
代码中附上了详细的注释,通过注释可以知道程序的实现过程:
/ b" g7 b/ S( n" C3 o% H5 B①在配置HSE前,先关闭HSE,如果已经使用了HSE,应先切换成别的时钟源,然后再关闭HSE,最后才可以配置HSE。
5 [6 o/ E& Z1 I7 S2 U②程序中通过将RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)的HSEON位写入“ 1”来禁用HSE。+ f% H. Y2 w9 ~3 E- q+ P
③通过验证RCC_OCRDYR的HSERDY位是否设置为’0’,检查HSE是否被禁用。
+ H) Q1 ^6 a8 T4 B+ ~$ v8 G/ o④通过将RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)的HSEBYP位写入’1’,禁用HSE旁路模式。2 v$ P1 m2 m1 W9 @' ?/ l/ {
⑤通过将RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)的HSEON位写入“ 1”来再次使能HSE2 F& y& W7 w/ H
⑥检查RCC_OCRDYR的HSERDY位是否设置为“ 1”,然后准备使用HSE。7 }6 `. |4 ?% }9 E3 r, p$ f
8 r( M, q& Z& G, I# q( h
  1. 1   /**
    1 S3 g; h# K+ m+ w5 s* Z3 u( k. C2 k4 ^
  2. 2    *@brief 根据指定的参数初始化RCC HSE振荡器。- N: Y( i) ^4 l9 x6 i9 f
  3. 3    * @note  在更改HSE配置之前,请注意不要将HSE振荡器用作时钟,也就是要先关闭HSE,& v! w  S4 a1 T
  4. 4    *         如果使用了HSE,则必须选择另一个源时钟,然后更改HSE状态(例如:禁用它)。       $ f- h" g: ^! |8 R2 Q( x( _
  5. 5    *@note   进入STOP和STANDBY模式时,HSE由硬件停止。4 K0 r# Z; B1 X: B& z
  6. 6    *@note   此功能会重置CSSON位,因此,如果先前启用了时钟安全系统(CSS),
    8 A# }. G* N# j( F# D! t
  7. 7              则必须在调用此功能后再次启用CCS。
    ' k7 M, s, x6 j) a
  8. 8    *@param  状态包含RCC HSE振荡器的配置。
      w7 n0 F) E% W& \, [& `
  9. 9    *         此参数可以是下列值之一:
    $ B2 S7 H3 H4 I8 ~
  10. 10   *         @arg RCC_HSE_OFF:" x0 A$ Y; H& O+ u$ V! w9 Y3 ]
  11. 11   *         关闭HSE振荡器* }, E7 _7 k! a. y* Z
  12. 12   *         @arg RCC_HSE_ON:
    " ^% |- ^: x! ], T0 s% M* ~
  13. 13   *         打开HSE振荡器
    : P7 C; M0 R8 [# ^1 ]
  14. 14   *         @arg RCC_HSE_BYPASS:
    ) x# v) W* ~# a. `4 M2 f- g
  15. 15   *         使用提供给OSC_IN的低摆幅模拟信号(旁路时钟),
    ; u  Q* V% _: y# e1 F, i6 |8 P; ?
  16. 16             HSE振荡器被外部时钟旁路
    ' U6 ?6 B1 g4 B: u( h
  17. 17   *         @arg RCC_HSE_BYPASS_DIG:5 M8 S4 u5 a+ `9 a& f* L
  18. 18   *         使用提供给OSC_IN的全摆幅数字信号(数字旁路时钟),
    ! J8 _/ E6 {- Q* W& d! O
  19. 19             HSE振荡器被外部时钟旁路
    + ?$ I& t3 W3 B. p8 |" {1 m
  20. 20   * @retval HAL status0 |( U: h, K' [8 O! z) {  P' F
  21. 21   */0 W( I3 e9 p3 j( c. _- Q
  22. 22  HAL_StatusTypeDef HAL_RCC_HSEConfig(uint32_t State)
    : J$ ?* V) v1 m: ^5 D0 T: F; r6 k
  23. 23  {9 W% _( j& L7 }  B
  24. 24    uint32_t tickstart;
    & y% Z3 _+ Y  ]
  25. 25   
    & D$ b2 i3 L# `/ Y4 X' C/ s
  26. 26    /* 检查参数 */
    ) C& n) {) n, a$ ?; B. l5 g
  27. 27    assert_param(IS_RCC_HSE(State));
    - D6 {0 W& L( V( U
  28. 28
    . N& o6 q/ G; X. t7 H! H8 ]0 Q
  29. 29    /* 在配置HSE之前禁用HSEON */. x5 X) E. W# a' ]+ d0 J8 s# X1 W. [
  30. 30    WRITE_REG(RCC->OCENCLRR, RCC_OCENCLRR_HSEON);) w; c, O/ _1 p& \
  31. 31 ! E* h' ]1 k& G" G9 U5 _
  32. 32    /* 获取全局变量uwTick当前计算值 */! y+ r3 J+ O) B2 p! o# R
  33. 33    tickstart = HAL_GetTick();
    0 f3 |: u( R* Y: M6 d' i2 [; r$ X9 H
  34. 34 3 x1 k; I* d8 q/ g! M" N
  35. 35    /* 等待直到禁用HSE  */
    ) u" I, P7 g6 M6 N) X
  36. 36    while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)/ a# a4 E( B* ^" q
  37. 37    {: w: h4 F. E& {1 S) t  D
  38. 38      if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)& @, a* J+ e' ?! f4 Q3 @1 M# Y
  39. 39      {
    & E8 [8 |3 @8 S7 [% @" J; a5 G
  40. 40        return HAL_TIMEOUT;
    3 G8 C5 |" t$ k
  41. 41      }
    7 L$ d8 ~$ A3 y# |' T5 J5 A5 p
  42. 42    }
    : n( h# Q  ]4 O: z" ~6 w
  43. 43
    9 @: _! |: L( ?7 L( l! Z
  44. 44    /* 清除剩余的位 */
    ! k) q4 G9 S# b3 h! e- ^- v
  45. 45    WRITE_REG(RCC->OCENCLRR, (RCC_OCENCLRR_DIGBYP | \         RCC_OCENSETR_HSEBYP));3 i! t1 q- }7 Y9 e6 a5 r. Q, Y0 i
  46. 46 + r9 L: r. d' s/ V
  47. 47    /* 如果需要,启用HSE */% W! L( Y6 d& e7 t; P" Q% `2 Q; T# f
  48. 48    if (State != RCC_HSE_OFF)& A0 W8 N/ ]- q0 \7 d/ [9 t. Z' |9 z
  49. 49    {
    * t. m- \/ }  l
  50. 50      /* 如果设置的是旁路时钟,则启用旁路模式 */7 R5 k9 u: O  R, y" K3 m
  51. 51      if (State == RCC_HSE_BYPASS)
    5 z) D6 O7 |; M) f# ~; C* K
  52. 52      {/ [& N  j- L$ U, B4 m# z% i) O
  53. 53        SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEBYP);
    ; M/ N* f2 i0 @( b3 e( M
  54. 54      }. w2 d8 ]+ t) r  B" {0 L' U
  55. 55      /* 如果设置的是数字旁路时钟,则启用数字旁路模式 */
    5 u; X7 W0 _; ^# Z% ~+ Z/ `4 G
  56. 56      else if (State == RCC_HSE_BYPASS_DIG); ~$ ^" @5 U  e6 X" t
  57. 57      {
    ( E7 E8 K& Y- p, ]) D
  58. 58        SET_BIT(RCC->OCENSETR, RCC_OCENCLRR_DIGBYP);
    - ^% k# y4 M7 R
  59. 59        SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEBYP);2 [. b& P8 p+ U) V" z
  60. 60      }
    3 a& ~9 t+ f9 U& M1 B* N( K: J# u$ s
  61. 61 . y+ O! s1 R; o7 t5 \: c
  62. 62      /* 启用HSE */
    % [! z7 H' _! N/ C* l  z* t
  63. 63      SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEON);
    + C6 g9 ^, A( _7 T1 @
  64. 64
    # H+ |5 c0 C% R' Y6 f
  65. 65      /* 获取全局变量uwTick当前计算值 */7 j1 Q! s( Q( S* l
  66. 66      tickstart = HAL_GetTick();0 b4 ?/ M- w/ m( X4 I+ h1 I- o
  67. 67
    4 C+ p* R7 |5 ]" {0 g5 d% d$ W+ t
  68. 68      /* 等待HSE准备就绪  */
    4 H  c5 M, E; W& }6 Q  {7 G
  69. 69      while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET)
    ( v0 C. j& k: d! O# X
  70. 70      {$ z1 f7 S% q3 v; B
  71. 71        if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
    4 N$ [9 R5 o: l  D: L
  72. 72        {
    6 T/ s6 G! C1 c5 Y2 J- Q: m/ T
  73. 73          return HAL_TIMEOUT;
    9 M% w4 j0 W5 W
  74. 74        }5 E; t: ^. g8 `
  75. 75      }  y4 ]  [( d) X4 `" L+ V# i( o- _
  76. 76    }
    0 b+ ]/ u  d" L# L% n% e
  77. 77
    + U7 R+ H, ?$ X/ S) B. @
  78. 78    return HAL_OK;4 e9 Z- L+ J7 g& d0 }! x
  79. 79  }
    5 G% R, c6 q8 y$ {; o
复制代码
5 G4 C+ f1 j" @/ y
HSI配置部分
5 w$ \+ D9 s7 ?3 {+ Z0 `3 X关于HSI的配置部分,有以下两点:
& I4 i4 p1 t# `2 f) K2 g( D' v①RCC_OCRDYR寄存器的HSIRDY标志指示HSI是否稳定。 启动时,在硬件将HSIRDY位置1之前,不会释放HSI输出时钟。应用程序还可以通过位于RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)和RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)中的HSION位来控制HSI激活。
) f# D+ ]% M7 [②HSI的预分频器由位于HSI配置寄存器RCC_HSICFGR中的HSIDIV位控制。RCC振荡器时钟就绪寄存器RCC_OCRDYR可以使用标志HSIDIVRDY位来检查硬件何时考虑新的分频比。HSIDIV值可以即时更改,并且HSI将考虑新的分频比,但是,必须考虑:. l3 T" y( F3 @5 ^9 S3 ~3 b% H" E
如果HSI当前用作PLL的参考时钟,则不允许更改HSIDIV。% r; C! T+ _. B* W
如果当前将HSI时钟用作某些外围设备的内核时钟,则应用程序必须确保HSI频率变化不会干扰外围设备。2 M7 ^5 @7 ]9 U( W! R
HSI配置部分的代码框架如下,代码比较多,这里省略了代码。
$ V8 k0 O+ ~% h; C. y
  1. 1   if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSI) \- \) `  t* \% p% c# V: Y
  2.                                                                                                  == RCC_OSCILLATORTYPE_HSI)
    1 v& W/ A: C: ?
  3. 2   {* k- V1 ~1 q7 e1 U! j
  4. 3      /* 使用断言检查检查参数是否符合范围*/
    7 Q7 b! {$ }" y3 _: V
  5. 4      /****** 省略部分代码 ******/
    9 j6 P' }9 J1 L9 n' ?$ ^5 K* a
  6. 5   /*
    0 s" H  ~" ~( O$ L8 h7 J
  7. 6    * 当HSI被使用时(在AXI、MPU、MCU和PLL1~pLL4使用),它不会被禁用,允许校准。
    $ H2 d0 r! P$ e9 O7 v
  8. 7    * 1、如果HSI当前用作PLL的参考时钟,则不允许更改HSIDIV的置。
    " L  s% r2 c9 h) h% {( E
  9. 8    * 2、如果HSI没有被当做PLL的参考时钟,则更新HSIDIV值,; w, s* X% o3 y" y9 a# k
  10. 9    * 同时,更新SystemCoreClock全局变量。. e: c. i4 g7 k$ {
  11. 10   */
    ( P/ h. t* \8 F9 j
  12. 11     if (IS_HSI_IN_USE())
    ) J8 v* R2 v& H4 ?3 w3 d4 f' y
  13. 12      {6 u7 x* D; B* ^' \# R' @& y
  14. 13        /****** 省略部分代码 ******/! S4 ?$ N- w* }0 C
  15. 14      }
    7 d! M* I8 N$ J" k
  16. 15  /* 8 {0 Y! Z) V) c) e  t; t/ W" m" [) S
  17. 16   * 如果HSI没有在AXI、MPU、MCU和PLL1~pLL4使用:( ^( I5 L4 Q9 P! u% m* q$ h+ R
  18. 17   * 1、确定HSI没有被使用,如果检查到HSIState为开启状态(一般是用户开启),先启$ w/ o/ M4 L" A$ R4 i7 v
  19. 18   * 用HSI,等待HSI准备就绪后,更新HSIDIV值,然后整内部高速振荡器(HSI)校准值。
    , x: K6 V! C! I/ p) Y. H' L+ X+ {
  20. 19   *
    & \* w- C6 E# \
  21. 20   * 2、如果如果检查到HSIState为关闭状态(一般是用户关闭),则禁用HSI。
    : A3 z7 z$ Z8 f
  22. 21   */
    # C7 _) ]7 o2 y8 l6 V, Q! g# [0 z
  23. 22      else
    - P* y: J: L! x2 }  r
  24. 23      {' I# h8 W1 i0 h  R% c
  25. 24        /****** 省略部分代码 ******/& Y6 l, [' n" l5 j. S
  26. 25      }( U2 i+ k& |3 A/ f4 [, [+ f! o0 i
  27. 26  }
复制代码

/ l2 P9 H9 w' w* z3 G  HPLL配置部分6 N) _! ?" L) H9 m
在启用PLL之前,必须先完成以下PLL配置:
/ o) f/ E, @- B4 B; y选择PLL时钟输入(HSI或CSI或HSE): U5 T+ G$ O( R/ Z$ B- Z7 U
PLL时钟频率输入范围(配置DIVM参数)7 _& n& Y6 P5 ~8 e
一旦启用了PLL,DIVMx,DIVNx,DIVPx,DIVQx和DIVRx这些参数就无法更改如果用户想要更改PLL参数,则他必须禁用相关的PLL(PLLxON = 0),并等待PLLxRDY标志为0才可以(这里的x等于1~4)。
" }/ X  j. A0 k配置PLL部分主要是通过调用RCC_PLL1_Config、RCC_PLL2_Config、RCC_PLL3_Config、RCC_PLL4_Config函数来实现,函数的参数RCC_OscInitStruct用于指定配置哪一个PLL。通过对PLL的RCC_PLLInitTypeDef结构体成员赋值可以配置PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数。
6 b. [+ N5 Z3 A0 Y7 }. A3 I
  1. 1   /* 配置PLL1 */, c  b3 o% _0 j$ p' P& K7 z! W3 x
  2. 2   result = RCC_PLL1_Config(&(RCC_OscInitStruct->PLL));
    - _/ \) W8 n) P, G9 @* z8 `* U- _3 B
  3. 3   if (result != HAL_OK)3 t9 f+ a9 z& i; v' a. p" s$ R
  4. 4   {9 Z1 z$ y% _/ c; W, w5 `
  5. 5   return result;
    9 A+ J( B% z4 l; B/ F0 |
  6. 6   }8 x7 X, ^  R- p$ e, {
  7. 7
      a; `2 Q/ P! I; L5 Q9 H- \9 _
  8. 8   /* 配置PLL2 */
    : A$ e. ~, ]' a, b' ?
  9. 9   result = RCCEx_PLL2_Config(&(RCC_OscInitStruct->PLL2));
    ; r6 Q" o; u, R$ ?( c2 S4 x% V9 p
  10. 10  if (result != HAL_OK)
    , h7 s+ W5 R9 |2 m, x% s9 y
  11. 11  {: \; f" N( @  L4 G
  12. 12  return result;
    6 `4 O- J) X! ]0 H
  13. 13  }
    7 r, E) V0 P4 e0 w
  14. 14; N' ~) s& w5 m" I( T: V
  15. 15  /* 配置PLL3 */$ }6 L' w- j6 g5 e2 D% ^4 W4 S
  16. 16  result = RCCEx_PLL3_Config(&(RCC_OscInitStruct->PLL3));5 }% D( ^/ J+ D7 @  A: I7 O  K
  17. 17  if (result != HAL_OK)# p; b( c% X. |4 N
  18. 18  {
    5 }) @# C8 m6 E/ C
  19. 19  return result;- Q% ^& I5 I* K
  20. 20  }
    ; b7 p1 J8 A" m1 [7 B- K7 d
  21. 21
    / ~+ l: R8 z- Y
  22. 22  /* 配置PLL4 */+ A1 n3 O* v; h3 n8 Q
  23. 23  result = RCCEx_PLL4_Config(&(RCC_OscInitStruct->PLL4));
    . @. G4 F1 Q6 v0 Q2 I
  24. 24  if (result != HAL_OK)
    8 Y' I" F! x% S8 O8 E
  25. 25  {
    6 ^) @4 f- \3 F7 e, H) K
  26. 26  return result;
    . n- L0 ]1 q7 O! F) y6 I! y9 s
  27. 27  }
复制代码

% [6 M4 Y1 \1 O9.3.3 函数HAL_RCC_ClockConfig% }9 |  m6 T1 A5 `# _. I- J. L
●函数功能:根据RCC_ClkInitStruct中指定的参数初始化MPU,AXI,AHB和APB总线时钟。) U6 y0 x6 f2 k+ O- H: A
●函数返回值:% J- c. e2 V5 Z! _
枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)" A! q) M# R; X* k* {- f) F
注意:
6 Q3 B) u4 u. E/ D' u①系统从上电复位、停机或者待机唤醒以后,或者在HSE发生故障的情况下,则直接或间接将HSI直接或间接用作系统时钟。9 V/ _6 x+ y' G
②仅当目标时钟源准备就绪(启动延迟或PLL锁定后时钟稳定)时,才会从一个时钟源切换到另一时钟源。如果选择了尚未准备好的时钟源,则当时钟源准备就绪时将进行切换。
; K( ?. u1 R. @, u+ U③根据设备电压范围,软件必须正确设置HPRE [3:0]位以确保HCLK不超过最大允许频率。
5 e4 T' P/ v% Q5 |5 q9 G: C& D- ?
8 `$ |$ Q0 q  I1 B6 f) U
  1. 1   HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef \         *RCC_ClkInitStruct)
    & G3 s: u3 E6 j. `" Q$ ?
  2. 2   {' a2 Q% `% I: a
  3. 3
    + S% l' M# w: [8 o: p3 b- t
  4. 4     HAL_StatusTypeDef status = HAL_OK;/ d! u& r0 J, S7 F" y
  5. 5     uint32_t tickstart;0 y% b0 N/ v( L7 G* g: D0 Z8 n
  6. 6 1 O+ {) t" c% K
  7. 7     /* 检查是否是检查空指针 */
    ) F& |0 s4 j* W. z' V
  8. 8     if (RCC_ClkInitStruct == NULL)
    1 E( c0 o3 @8 [+ Y7 H' n
  9. 9     {! W5 J% B" X1 _9 \: Y
  10. 10      return HAL_ERROR;
    % `% Z1 w+ c9 l" c: {
  11. 11    }
    ) F- R- |, A9 f7 t$ b* F5 ]
  12. 12
    1 R8 V2 T" Z' B
  13. 13    assert_param(IS_RCC_CLOCKTYPETYPE(RCC_ClkInitStruct->ClockType));8 w* w- v" m' H3 K
  14. 14    /******省略 MPU、AXISS模块配置代码******/9 O0 Q8 g8 k4 Y9 z2 @! T$ v
  15. 15    /******省略 APB1~APB5总线的分频器APB1DIV~APBDIV5的代码******/
    & x6 Z* G; b; E* A5 f* c
  16. 16   
    5 S. L! ?$ o* ^
  17. 17    return HAL_OK;
    1 B0 W7 m- f. |) r" _0 O
  18. 18  }
复制代码

8 A4 r  O, {  Q* l/ T参数RCC_ClkInitStruct是指向RCC_ClkInitTypeDef结构的指针,我们查看此结构体。如下,此结构体主要用于设置时钟源以及MPU、AXI子系统、MCU子系统和APB1~APB5的分频系数。
! Y, P0 ~# K- K) \6 R
% A) ~' G! B- J- C: Y# Istm32mp1xx_hal_rcc.h文件代码; g5 h$ q- e; a8 T# f. E1 T
4 d6 i5 {* e" A( @% w# i
  1. typedef struct
    3 Z, U# d+ U+ O7 t6 k& q3 D
  2. {
    $ z" b% S  p3 y# P! t1 e- \  y
  3. uint32_t ClockType; /* 时钟源选择 /
    . i) E3 B7 k8 I9 B/ q
  4. RCC_MPUInitTypeDef MPUInit; / MPU结构参数(时钟源和分频数) /
    " g/ `0 I5 B, Y% n6 e
  5. RCC_AXISSInitTypeDef AXISSInit; / AXI结构参数(时钟源和分频数)/' U. R# K+ i* l; _- I% i
  6. RCC_MCUInitTypeDef MCUInit; / APB4分频数 /% b+ @3 I4 M$ ], m, e( w, m  l6 A% |1 A
  7. uint32_t APB4_Div; / APB4分频数 /
    . y4 ^  f: _/ c; s* n
  8. uint32_t APB5_Div; / APB5分频数 /
    + m% R& P2 D. U- O" I
  9. uint32_t APB1_Div; / APB1分频数 /0 s: q7 O. G+ T( v3 S
  10. uint32_t APB2_Div; / APB2分频数 /% W* u4 }0 i+ x
  11. uint32_t APB3_Div; / APB3分频数 */
    ( x# g  X, B5 n" X
  12. } RCC_ClkInitTypeDef;
复制代码

5 J: J) a5 K* c+ V6 ?  `9.3.4 函数HAL_RCC_GetSystemCoreClockFreq5 ]! v$ G& D: f) e* V( g* i
●函数功能:根据所选的时钟源以及预定义的常量来返回系统核心频率。  |/ s. o; T, K; ^+ h" D
●函数返回值:2 a/ W" k2 h4 ^* b2 G/ u8 s& u2 B' G) B
MCU或者MPU时钟频率。9 i5 s" M) e' m
注意:
/ D( f- `: c' l% E! Q①我们说的系统时钟频率,其实就是MCU或者MPU的时钟频率。0 N' A9 T  w2 d9 y
②每次MCU/MPU更改时,必须调用此函数以更新正确的值,否则,基于此功能的任何配置都将不正确。我们前面说的SystemCoreClock全局变量就是通过此函数来获取最新的系统时钟频率的:
% o& j2 H) ~9 G- B1 \8 s' D, ESystemCoreClock = HAL_RCC_GetSystemCoreClockFreq();
7 ?. g, [) J# W( w1 w6 W我们后面的实验会通过HAL_RCC_GetSystemCoreClockFreq函数来获取系统时钟频率。,函数如下。函数中,通过宏定义CORE_CA7来选择MPU或者MCU的时钟,其中HAL_RCC_GetMPUSSFreq函数返回MPU的时钟,HAL_RCC_GetMCUFreq函数返回MCU的时钟。4 w( S: G  {6 u% K$ y0 O$ O& _. b' B

& M; [% @9 u& D, I# y! {! D' A
  1. 1     uint32_t HAL_RCC_GetSystemCoreClockFreq(void)
    ; _/ L, p: k7 n( g& E0 ^2 b% n
  2. 2     {: z& z- Q6 B% R8 X( G
  3. 3     #ifdef CORE_CA7: c5 V6 x/ H6 d! ~! f1 |. K& H( T
  4. 4       return HAL_RCC_GetMPUSSFreq();/ n! p( K4 Z8 ~
  5. 5     #else /* CORE_CM4 */
    4 o6 C/ y% u' M/ g% e
  6. 6       return HAL_RCC_GetMCUFreq();' }; u& V4 c. B! v6 E$ g) q1 U
  7. 7     #endif
    3 W# F- Z, h/ ^- L2 i1 ~( i
  8. 8     }
复制代码
$ Y: E% B6 a3 o% l' n7 s- B* p
9.3.5 HAL_RCCEx_PeriphCLKConfig; P, R7 X6 y3 F4 `
●函数功能:根据RCC_PeriphCLKInitTypeDef中的指定参数初始化RCC外设时钟。, A1 U6 D7 U, \
●函数返回值:
' E3 p6 P* ?9 w: W1 n9 R" E枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)2 e3 @- F( W; f- d
可以通过此函数可以指定配置哪个外设以及外设的时钟源是哪个,后面的外设实验中,我们会接触此函数。该函数声明如下:. w! r3 n! {/ q% b# d9 ^* X; `

  k/ H( G0 ?9 \4 Z3 B" ?6 D3 t7 f% \
  1. HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef! p4 d# D/ n) u
  2.                                             *PeriphClkInit)* f' I9 l. V' B. R2 m+ P
  3.         PeriphClkInit是指向包含字段PeriphClockSelection的RCC_PeriphCLKInitTypeDef结构的指针,该字段可以指定是哪个外设。例如,RCC_PERIPHCLK_UART24指定USART2和UART4,RCC_PERIPHCLK_I2C12指定I2C1和I2C2。
    9 n' E; I5 z. n& @$ D- [0 s( ?
  4. typedef struct; J3 C+ L! N! A" C& A7 _, `, g- v9 X$ U" n
  5. {3 x) A  c+ B) R' ~, `9 R/ c
  6.   uint64_t PeriphClockSelection;   /* 外设时钟源选择 */
    7 U6 G! m3 w5 i5 O9 A% u
  7.   RCC_PLLInitTypeDef PLL2;          /* PLL2结构参数 */      9 e$ z3 W. U; S: d6 c  D$ E
  8.   RCC_PLLInitTypeDef PLL3;          /* PLL3结构参数 */
    " @( B" M0 l( Y% W& n7 H
  9.   RCC_PLLInitTypeDef PLL4;          /* PLL4结构参数 */+ \! m4 \* K$ U7 e) {
  10.   /* 指定I2C1/2时钟源,该参数可以是RCC_PERIPHCLK_I2C12 */' K! x# n3 u7 A. r  q. j: d& `) `
  11.   uint32_t I2c12ClockSelection;   
    ) {% B9 ]! C( b# \% E* C6 C5 Q4 U
  12. /********* 省略部分代码 *********/$ {! [- b; P, u
  13. /* 指定UART2/4时钟源,该参数可以是RCCEx_UART24_Clock_Source */: u7 q- V" e6 Z! \& Q3 H" }
  14.   uint32_t Uart24ClockSelection;  
    8 R3 K6 _- G5 [' m0 u" t
  15. /* 指定UART3/5时钟源,该参数可以是RCCEx_UART35_Clock_Source */   
    0 J5 @9 {3 i3 W: A
  16.   uint32_t Uart35ClockSelection;  ( Z5 s" p/ d0 K; s! S0 A
  17. /* 指定UART6时钟源,该参数可以是RCCEx_USART6_Clock_Source */   
    0 o9 N+ b4 F" l/ ]' |! ^
  18.   uint32_t Usart6ClockSelection;
    1 |/ y5 F! Q" w" ]( Y
  19. /* 指定UART7/8时钟源,该参数可以是RCCEx_UART78_Clock_Source */     
    / [) @# m; y& Q7 X; R# ^
  20.   uint32_t Uart78ClockSelection;   7 I  z8 O- W$ B& j; B0 z6 i2 O5 D
  21. /* 指定RNG1时钟源,该参数可以是RCCEx_RNG1_Clock_Source */     / O2 U7 X- }, P) h
  22.   uint32_t Rng1ClockSelection;
      H+ _" i; j' z$ a# ^2 i
  23. /* 指定RTC时钟源,该参数可以是RCC_RTC_Clock_Source  */      " S9 _- K/ b6 J
  24.   uint32_t RTCClockSelection;      
    ) D+ S4 l+ J  j8 c5 Q. A
  25. /********* 省略部分代码 *********/
    9 e& i( z) h7 s
  26. /* 指定TIM2时钟源,该参数可以是RCCEx_TIMG2_Prescaler_Selection */ ( V# {4 P, A7 Z( l$ }2 J. [
  27.   uint32_t TIMG2PresSelection;        
    $ v, X  m( G# O! l
  28. } RCC_PeriphCLKInitTypeDef;' q6 W2 c, x+ R1 {
  29.         RCC_PLLInitTypeDef结构体我们前面已经分析过,其声明了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量。
    # b2 j6 N1 E7 {% [
复制代码

% y! G# ]# [: n: f; q1 }2 E9.3.6 其它函数- Q. e" }0 |% |: u: s
; x+ Q# j3 s4 R7 j' v( v8 L$ `. V# s
2755498c82314b3b84d2e6b8cf4d8678.png ; o* m3 H5 n9 M( D% [  j

8 Y( y- j- a/ c/ N表9.3.5. 1其它API函数9 W+ K7 p0 x& ?
9.4 配置系统时钟实验
+ I' P# V# n4 G8 U# F# \% s根据前面的分析以及ST官方给的总线框图,MCU时钟最大是209MHz,MCU有一个内核外设Systick,频率最大也是209MHz。下面我们分别使用HSE、HSI时钟源,通过配置时钟树来实现设置MCU最大的频率值,并通过程序读取MCU的时钟频率值和验证Systick的时钟频率。
  d/ Y$ Q# H( j* }3 g; W9.4.1 使用HSE$ I6 H' A" {8 G3 F
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\2-1 MCU_HSE。& L: d) k% }) n% i8 x
' [7 g) D( o6 p; i4 f: O$ ?' _: e
硬件原理设计$ x* c+ _7 V; N( ?4 {# ]- _
正点原子STM32MP157开发板的核心板上外接了24MHz有源晶振,所以HSE为24MHz。
+ A1 s( q( i1 W, W/ l5 X; `1 _0 _8 K  s/ l% Q9 r
0b041f7f9eba4caa822a62ac1581330e.png
, P2 d1 Q6 t  w2 j. ^+ ]6 q+ a2 l. y' F
7 e1 l: B( l- S( ]- ~图9.4.1. 1 晶振原理图截图
5 i6 {- |& s+ D2. 软件配置
7 q! e1 @! J0 D" Z我们新建一个工程MCU_HSE,然后在STM32CubeMX插件上开启HSE,注意,要用外部时钟源HSE和LSE的话,一定要先将他们开启。如下图所示,会有4个选项:% z5 t( H5 l4 R- \( Q7 |
①Disable 关闭外部时钟6 O5 @; ^& u4 I9 ^& L
②BYPASS Clock Source 旁路时钟
; A8 H: Z" D' e  H* s  C③DIGBYPASS Clock Source 数字时钟(数字信号提供时钟): P/ D4 A7 C  o2 I$ N
④Crystal/Ceramic Resonator 晶体/陶瓷谐振器
$ @% W6 X- [: L9 F* B0 e* {# R( D7 |5 _8 V/ `
a370c1a30632435da41d37a4e806a6e4.png 6 K# ^# q' |+ d6 Z. d! n  ?) z
& l% |3 q' k# M: n6 j( `
图9.4.1. 2 HSE时钟模式选择$ W; Z& v! j6 q3 H' w5 Q! Y/ w
旁路时钟是指使用外部晶振时,不需要芯片内部时钟驱动组件,直接从外部导入时钟信号,就好比芯片内部的驱动组件被旁路了,这种模式的话,一般接有源晶振。6 t  C, Q$ Q( F# O' J3 u! @
晶体/陶瓷谐振器选项是指,使用外部晶振时,时钟源是由外部晶振体与MCU内部时钟驱动电路共同配合形成,有一定的启动时间,精度较高,可以接无源晶振或者有源晶振。
: G" E. _# b: h& H( ^7 E1 Z$ t无源晶振自身是无法起振的,如果使用的是无源晶振的话,需要芯片上电跑起来以后,芯片内部的供电电路给无源晶振供电以后才可以起振。如果配置成BYPASS Clock Source模式的话,无源晶振就没法工作了,所以必须配置成Crystal/Ceramic Resonator模式。
8 e9 y8 S3 L1 c- l( \如果使用有源晶振,只要外部电路一上电,有源晶振就起振了,所以可以选BYPASS Clock Source或者Crystal/Ceramic Resonator模式都是可以的。
3 Y' v- V' P9 w6 B' `如下图,将HSE配置成旁路时钟和晶体/陶瓷谐振器的差别,旁路时钟是单向信号,晶体/陶瓷谐振器是双向信号。- |. w7 J# f0 s% z" s

2 w- f! n% |3 A/ H- N d8f4021f188641079f12df2319ed2a72.png
; L; N8 n& [" N, N+ H- j/ N9 g. Z# ]$ B: T" \4 }
图9.4.1. 3 旁路时钟! w2 F! P1 A( U0 m, c% N
! a; L: Q$ J# W& m5 I9 P9 ~6 T
1306098e9da44062bd534c7b1cccedcf.png / G# d& H3 H5 a+ }/ v/ v: s

( Q5 J* {  z; I8 I3 e3 c图9.4.1. 4晶体/陶瓷谐振器
# D4 h# _6 W; k2 _; V/ d我们这里就选Crystal/Ceramic Resonator模式(选BYPASS Clock Source模式也可以):
) g' }7 j1 x( O/ A2 X" |2 k7 M! m# ?4 ^& W4 L, m8 l
995f0411897b4e8e8f9410e1c4917580.png
) Q# N* z8 I3 @* {1 z2 Y( h
2 r7 x* w3 W0 s. _3 k图9.4.1. 5选Crystal/Ceramic Resonator模式
( S7 i: m  E# D- i2 w在Clock Configuration处选择HSE时钟作为时钟源。由24MHz倍频到209MHz的话,需要通过PLL3配置分频系数和倍频系数来实现。具体配置步骤为:2 K5 }+ t- l1 n8 R7 \0 V; O
①选择HSE作为PLL3的时钟源;
  H$ |: W( E* o5 T②选择PLL3P(也就是pll3_p_ck)作为mcuss_ck的输入时钟源;
: b8 x  E5 @+ M( r" n' x, L1 w③手动输入209并按下回车,最大也只能输入209了,如果输入大于209并回车以后,系统系会频率超出范围。$ H' B. N% {& b7 f7 W, h
④输入209以后,STM32CubeMX会动态计算PLL3的倍频和分频系数,其中:  }. |* P0 {, ]% s0 P
DIVM3=2,DIVN3=52,fracv3=2048,DIVP3=31 f' y8 X. U& N0 O6 q: P, P% U: ]
我们将上面的参数代入前面的计算公式中,计算pll3_p_ck结果也刚好是209MHz。
! K9 w+ K% t1 s7 h; G
7 s2 P% @+ q8 f. i5 J 13fed619c4c6406baea5ec00cc3eb2df.png : W* o% a0 z- P0 b& `/ Z, D' C
3 a- @2 R" l* Q5 _6 D4 u
图9.4.1. 6 手动输入参数
' I+ o7 u4 \' G6 Y⑤STM32CubeMX动态计算MCU、AHB1~ AHB4以及APB1~ APB3桥接器和外设时钟。我们看到PCLK1~PCLK3后面显示红色,红色表示数值超出范围,并且窗口有一个叉提示配置异常:! e% U5 d* _8 {. N; {# T/ t

) Z1 T6 Q4 w; h a43d4174b332462dabe4a5277cbb0ca2.png
8 D% a, Y# L) Q9 i1 L# c6 a
: s0 c5 E7 m+ P2 w5 ^) L  v9 S& Z图9.4.1. 7配置异常提示$ v% y$ O; {6 u0 l
根据前面9.1.2小节有提到,pclk1、pclk2、pclk3最大为104.5 MHz,所以我们要修改这部分的参数。APB1DIV~APB3DIV参数可选1/2/4/8/16,我们随便选其中一个,下图选参数2,刚好满足最大频率为104.5 MHz:2 z4 G3 k& z( `6 @9 p* w/ }

1 Q5 n) N- I! ]4 j/ ~/ y" A 808fe149b9c64fc099aa0a860cb3f0be.png : }3 ?! j0 H) w6 V
0 A+ a7 V0 y) I0 ~& r7 F9 w
图9.4.1. 8修改APB1DIV~APB3DIV
0 n: M8 d! e9 b$ f6 a查看上述配置,MCU和Systick的时钟频率均为209MHz,是否真的是这样呢?我们后面会通过HAL库函数读取它们的值。* Z$ c& W: {5 c+ a8 q
按下“Ctrl+S”保存配置,系统生成初始化代码。这里提一下,系统时钟(MCU)的代码默认是在main.c文件中的,所以在Project Manager窗口的Code Generator处勾选“Generate peripheral initialization as a pair of’.c/.h’ files per peripheral”以后是不会专门生成时钟代码的.c和.h文件的,这点我们在第4.1.2小节说过。所以这里就不选了:
, u& l" K" T: [5 x
5 J/ ?* u0 @" A; N 623a078b40dc4545a6ee74a2af5f07fc.png
, w: d6 O8 k- w" ]! z  t: {- I! H. m8 X* z4 b7 K( B5 ~4 L
图9.4.1. 9 Project Manager窗口配置( g: e0 _9 l0 @
系统生成工程如下:
6 w/ H9 r% d9 a$ k/ l4 u! F: I, i: D3 h6 a
d3be3dee605e4523b2e8dbafedc4bb40.png
3 ^( }; Z+ K5 |# ~4 M0 t2 k" ^7 @. i& E/ x9 M* J  b
图9.4.1. 10生成MCU_HSE工程5 k6 |5 U7 K/ \
3. 添加用户代码
9 L; J/ Y. n6 J我们在main.c文件中添加如下代码:9 ^# d; P8 a+ [8 K% H8 W5 X

: R: b0 J% X8 P: d7 G
  1. main.c文件代码1 w6 O; D  o0 {& ]2 k
  2.     static uint32_t mcu_freq = 0;
    1 F2 W+ j7 r4 _3 W" b
  3.     static uint32_t Systick_freq = 0;! q; A6 W+ P# c" Q+ v8 w
  4.     mcu_freq=HAL_RCC_GetSystemCoreClockFreq();                /* 获取MCU的时钟频率  */
    / Y! A( I. G% o) ]: J' n' I, k
  5.     Systick_freq=SysTick->LOAD;                         /* 获取Systick的LOAD寄存器的值  */
复制代码

& C* X& Y. |  ~; \8 m' B( C* D编译、调试
/ I! f, E# U; G4 P7 s0 a3 S4 x8 G+ k保存修改,编译工程无报错,先在while循环处添加一个断点,然后按照第4.1.6小节连接好开发板和ST-Link,进入Debug模式。
& `7 y' @9 O7 }4 a8 \$ f/ W进入Debug模式以后,程序停在HAL_Init函数处,此时变量的值为01 A' Q8 N) ]9 E/ Y8 h  p7 A: c- E  U
! ?4 d( V9 S0 m* V
6955f106e6234054a1ccc891b4884aaa.png
; U) o, B) T7 l
: q8 i7 @; ^1 p6 x5 x0 l4 }图9.4.1. 11进入Debug模式
- A6 A* L8 X! Q: Q4 z点击运行按钮,程序运行到断点处停止,观察此时mcu_freq的值为209000128,Systick_freq的值为208999。
; y9 w3 q5 y9 }# A. \3 Q5 K
$ X. Q6 N2 I3 x, P: w e0a5681b8420485398cb9ae23462c06a.png - y$ m" E2 {0 k
6 ~& d4 I' S4 j3 n
图9.4.1. 12 运行调试
; @7 C) M/ E# D" }5. 实验结果分析# T% ?' ~6 t2 H% L( a  D9 J! s
根据上面的调试结果,我们看到mcu_freq的值为209000128,约等于209MHz,说明此时的系统时钟(MCU时钟)是209MHz,与前面的配置一致。
$ M' G! C3 t6 y( [Systick_freq的值为208999,约等于209000,此值我们是读取SysTick定时器中的LOAD寄存器的值。我们根据此结果逆推一下,SysTick每次计209000下就发生中断,如果要确保每1ms的中断周期的话,那么频率必须为209MHz。
# s# J3 Z5 k1 S: y$ k0 l5 z! Z. O" }注:
+ I: [3 R+ s" `. g( X  o. aSysTick一个24位向下递减计数器,启动后,LOAD寄存器的值赋给VAL 寄存器,VAL寄存器递减,当递减到0的时候,会产生一次中断,然后再从LOAD寄存器取值,然后再从所得值开始递减,递减到0的时候又产生一次中断,如此反复,从而实现计时。我们在前面的7.4.2小节有讲过。
2 X5 ?+ Y6 I2 t, U6 x8 X前面我们提到,SystemCoreClock全局变量也是通过HAL_RCC_GetSystemCoreClockFreq()函数来获取最新的系统时钟频率的,那么他的值应该也是209000128。我们在STM32CubeIDE上查看此变量的值:9 X- n2 r3 X6 Y3 ^& c+ \1 a

& e! H9 a6 Y2 H. h1 a; w) m 2db311b16eee4daf8a86640a41d8aa35.png & h8 V# U3 l: a4 m( i

* T* t# w0 y  w图9.4.1. 13 打开表达式窗口
1 d0 }6 y# v. `如下图,添加要观察的SystemCoreClock全局变量,其值为209000128。4 {! K1 ?6 Z+ c4 @  r* D4 q
9 N( H% a  O# F  u, N4 O
2ba66ee9e36448458338e38380508285.png ; ]2 ?+ A, ^  V, z2 |
4 z. T. u) q$ E" f' ~( |3 `8 W
图9.4.1. 14观察值( c! N/ s9 @6 [
9.4.2 使用HSI
( A0 M) i  o7 @2 q8 ?- l$ _& j本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\2-2 MCU_HSI。/ g6 w4 Q0 i; M: |
新建一个工程,工程名字为MCU_HSI,进入时钟配置窗口以后,mcuss_ck默认是64MHz,我们手动输入209然后按下回车键,STM32CubeMX会自动计算各个参数,如下图所示:! @! s, q/ ~; [% S3 Z
6 J. V' X5 c  l) G- a+ e& M) W6 Z
39499f604dc540bca806c1c648135021.png - Y! \; v" r2 m+ m( A0 n% b

/ o, U4 o. z( Q/ x3 Y图9.4.1. 15手动配置参数
+ f: M- V( W0 a$ n( ]% X3 [. D红色部分提示pclk1、pclk2和pclk3的频率数值超出范围,我们调整一下,将分频值由1改为2:
  l& u8 g1 A. j  N6 u
! ]1 v3 N" m5 {0 D ea75f0b7b37a43f2a37c553ea9a968c0.png
6 l7 k. f  N& {! h- Y% C! }9 J; k! `/ N
图9.4.1. 16手动调整参数
8 }- V8 m% E3 a- A. k7 E. k配置好以后,我们看到各个参数:
, D- ]- R- A0 H# Q' k①默认选择HSI作为PLL3的时钟源;3 d9 @9 u; `6 r$ u, ^
②默认选择PLL3P(也就是pll3_p_ck)作为mcuss_ck的输入时钟源;# v6 u; |. V3 v! a+ j4 P+ [
④输入209以后,STM32CubeMX会动态计算PLL3的倍频和分频系数,其中:
! _) b% M- K* {# ?* iDIVM3=4,DIVN3=26,fracv3=1024,DIVP3=2
/ y& _- Z) f: T# F3 s1 U8 o我们将上面的参数代入前面的计算公式中,计算pll3_p_ck结果也刚好是209MHz。2 p  t2 L3 Z  u7 ]4 M+ w$ |& P
% |8 N  e1 D( k! Q% {
按下“Ctrl+S”保存配置,STM32CubeIDE自动生成工程:- Q0 B$ Q2 U4 c; M
我们在main.c文件中添加如下代码:
2 a! u$ i5 X9 V+ j% i. V5 {
* V6 v; H. G! I
  1. main.c文件代码: r! g3 K4 m& ]3 U: h% H/ _$ G- e) ^/ i
  2.     static uint32_t mcu_freq = 0;
    & S0 P. U: U- w! _
  3.     static uint32_t Systick_freq = 0;, m& E8 u: J2 n: P- k
  4.     mcu_freq=HAL_RCC_GetSystemCoreClockFreq();/* 获取MCU的时钟频率  */+ T+ n. |! H: \! y, ?
  5.     Systick_freq=SysTick->LOAD;                         /* 获取Systick的LOAD寄存器的值  */
复制代码
$ B# j# o. r3 h0 k
a19a92deb99448159f75414911bb5995.png
7 T# d) ^' F$ v- M
( w/ n) }; H; F* h' N0 h& N3 `图9.4.1. 17添加的代码
0 s7 F% K- f3 f7 y0 ^6 t保存修改,编译工程无报错,先在while循环处添加一个断点,然后按照第4.1.6小节连接好开发板和ST-Link,进入Debug模式。注意不要选错前面的MCU_HSE的工程,可以在Debug配置界面处确认一下:- {, [- G0 m2 ?" ?6 x
% F* y6 `: l- j0 Q5 ]3 M, O1 H- S* w
e6ca2bb672f44d749d83438f11a9758c.png ) k8 B% q: Q1 p  P$ O* Q" a
; w% o+ M: K7 t* i; {
图9.4.1. 18确认调试的工程
. b& W3 ]8 P2 `进入Debug模式以后,程序停在HAL_Init函数处,此时变量的值为0。
4 U: y9 V# `# f: a* j3 B$ W6 B3 J# w0 Y0 x
b75d0db21faf4292835fac100676ae60.png
4 i% `2 _, {- ]8 F1 `/ H/ z; ^$ N8 u1 v( k
图9.4.1. 19进入Debug模式) ]1 L3 ~* u1 g/ k6 n9 D3 `
点击运行按钮,程序运行到断点处停止,观察此时mcu_freq的值为209000128,Systick_freq的值为208999。实验结果和上面MCU_HSE工程结果一样。实验分析请查看前面的MCU_HSE工程部分。
9 ^" w* c) r4 l2 |5 d/ k+ s/ m0 ?7 e( E" C. ~
37018cdcdf1c4b86bba7262c9e00885b.png 5 F2 W+ [. s# M4 ?
; L* I& t7 W0 K7 ?# z2 s
图9.4.1. 20运行调试
( @& E( c1 t& Z( V3 R0 n: p9.5 实验代码分析
$ d( b3 {! R8 Z7 j: I! t实验虽然验证完毕了,虽然是STM32CubeIDE自动生成的初始化代码,但我们还是有必要分析一下工程代码,这对HAL库的理解还是很有好处的。我们以MCU_HSE工程为例子,分析一下代码的实现过程。
' x7 m  Q; r# W9.5.1 配置HSE相关宏定义9 g1 \0 i. E9 p5 b# ^4 {7 Y
此部分主要是在stm32mp1xx_hal_conf.h文件中完成的,在第7.4.1小节有分析过此文件。
. W. X7 N8 |* r# |7 ^2 F. T9 {3 i' J5 F/ q
选择HSE相关模块( E3 x7 X  G  B5 f) o5 i9 S/ g
在stm32mp1xx_hal_conf.h文件中使能HAL驱动程序中要使用的模块的列表,实验中用到了RCC:6 G' p& ~! v, j6 e5 x5 c2 l8 S
  1. stm32mp1xx_hal_conf.h文件代码
    : O: N" S& ~5 i2 y  F, l
  2.         #define HAL_MODULE_ENABLED
    ! u( J( t5 k( l$ g: g7 D
  3.     /*#define HAL_HASH_MODULE_ENABLED   */
    - W8 }4 k% n8 C# U9 S& L1 R
  4.     #define HAL_HSEM_MODULE_ENABLED0 Z7 Z2 [: F7 B: E1 w0 Y- a
  5.     /*#define HAL_WWDG_MODULE_ENABLED   */2 d. h; J# C6 ~+ A- x
  6.     #define HAL_GPIO_MODULE_ENABLED
    ; S1 T2 }% N+ g1 A
  7.     #define HAL_EXTI_MODULE_ENABLED& A8 V% i6 w% K
  8.     #define HAL_DMA_MODULE_ENABLED
    $ C% v- |5 U: y3 s" L: |9 V" T: M+ \
  9.     #define HAL_MDMA_MODULE_ENABLED
      e( c  H; s3 N+ W7 Z
  10.     #define HAL_RCC_MODULE_ENABLED
    % {5 `: U% c7 X* c+ ~- o
  11.     #define HAL_PWR_MODULE_ENABLED
    : N3 C2 U- r, |% ]6 E. C4 b) T  V
  12.     #define HAL_CORTEX_MODULE_ENABLED
复制代码

* P/ R# C2 |* a, P包含HSE相关模块的头文件
) E) Q' y8 d3 v% y* I& p/ V
  1. stm32mp1xx_hal_conf.h文件代码- p6 x; ^8 q9 H2 F3 K
  2.     #ifdef HAL_RCC_MODULE_ENABLED4 R' ^7 D! S2 V- i. w% F" V( C9 _
  3.      #include "stm32mp1xx_hal_rcc.h"0 a* P5 @$ z# z* }
  4.     #endif /* HAL_RCC_MODULE_ENABLED */' z1 w  z8 Z9 w2 A% d: U& a% t8 ~

  5. + _+ f& B7 g# ^1 l) G) c3 l% d
  6.     #ifdef HAL_EXTI_MODULE_ENABLED
    5 h; I2 G3 o7 r! x
  7.      #include "stm32mp1xx_hal_exti.h"
    : S& u6 q- }3 u" _4 r+ L7 H) f
  8.     #endif /* HAL_EXTI_MODULE_ENABLED */
    5 n" ^8 k( R9 N4 _( p
  9. 0 }# z$ \' S9 Y- ]- o' C$ J
  10.     #ifdef HAL_GPIO_MODULE_ENABLED: D- I9 r: |4 X1 G
  11.      #include "stm32mp1xx_hal_gpio.h"
    6 K/ _( J9 J: y3 @% K
  12.     #endif /* HAL_GPIO_MODULE_ENABLED */4 w5 W7 B( v  g; e/ S8 c8 @
  13. + @, U' ]7 z, l
  14.     #ifdef HAL_HSEM_MODULE_ENABLED8 P* F' O! h. ]! [9 `+ e
  15.      #include "stm32mp1xx_hal_hsem.h". V/ S+ x6 U* C5 ^# m0 {; ?7 e
  16.     #endif /* HAL_HSEM_MODULE_ENABLED */7 _2 A2 H3 r6 V1 c4 n
  17.         /****** 省略部分代码 *******/
复制代码
$ l$ K- u/ r/ R; J! ~
配置HSE_VALUE5 t8 v6 d+ f1 y$ }  u
宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是24MHz),代码如下:" k& r* e7 r. M8 P- \( i3 ^
  1. stm32mp1xx_hal_conf.h文件代码
    : e- X! t8 S# k$ W
  2. #if !defined  (HSE_VALUE)( N, `  n* w; c+ h' h5 _
  3.     #define HSE_VALUE    (24000000U)
      f! K7 a. b1 g; F; J; r1 ^7 D
  4.     #endif
复制代码

0 p0 P% E  X+ s+ z9 G9 h1 {9.5.2 系统时钟设置函数
8 e) u4 o+ S/ N: v* ]% [! n' C8 m% p1 r9 H
函数代码实现% y* q8 _6 q: T$ ?, t
gpio.c文件代码很简单,主要就是开启GPIOH时钟,因为HSE的两个时钟引脚挂在GPIOH上:# A0 b6 o, c3 b& a6 f5 [
  1. #include "gpio.h"+ n  S7 _  i% [4 V- h6 M. E
  2. # ^1 l' I; k4 R
  3. void MX_GPIO_Init(void)
    " [/ n/ q: L2 T) ?* n$ u3 I0 O
  4. {0 Q3 t3 w& v2 K" |* T" M$ p$ P' M
  5.   /* 开启GPIOH时钟,因为HSE的两个引脚PH0-OSC_IN和PH1-OSC_OUT挂在GPIOH上 */7 f' H3 n( m9 r$ [4 l5 `: p
  6.   __HAL_RCC_GPIOH_CLK_ENABLE();
    3 U. e- h5 c- ^0 F6 G% f8 A3 U
  7. }
复制代码
/ w+ R. I, V8 P# l2 f/ |
下面,我们重点来看看main.c文件中系统时钟初始化代码。如下图是系统时钟设置函数SystemClock_Config的代码。代码主要分成三部分:
9 M; V1 g0 l5 T, s  q( v% F①第一部分,是定义并初始化RCC_OscInitStruct、RCC_ClkInitStruct结构体变量,初始值默认为0,即将HSE、HSI、LSE、LSE、CSI和PLL1~PLL4均关闭;
9 ^  Y0 p  _) u: i②第二部分,通过RCC_OscInitStruct结构体变量开启时钟源HSI、HSE、LSI和PLL3,并设置PLL3的分频和倍频参数,从而使pll3_p_ck输出频率为209MHz。. o( e: Q! v3 h8 x. K
③第三部分,通过RCC_ClkInitStruct结构体变量完成AXI、MCU和APB1~APB5的配置,实现AXI、MCU、APB1~APB3和APB4~APB5时钟频率分别为64MHz、209MHz、64MHz和104.5MHz。+ w5 `6 E" W6 D( O5 j. n- T
main.c文件代码
( y8 y+ L! G. M3 C. ^9 K! O6 o
  1. 1   /**
    & U8 S, c% [' M6 S# L- Z: e1 X
  2. 2    * @brief     M4主频时钟设置函数,也就是设置PLL3
    7 ~3 J5 i: T/ v0 K' S  c+ c+ \! T
  3. 3    * @ note     plln: PLL3倍频系数(PLL倍频), 取值范围: 25~200." @" W. u. ]) C0 [+ \
  4. 4    *             pllm: PLL3预分频系数(进PLL之前的分频), 取值范围: 1~64.4 f1 [/ n, J9 X, z( K
  5. 5    *             pllp: PLL3的p分频系数(PLL之后的分频), 分频后作为系统时钟, 取                                        值范围: 1~128.(且必须是2的倍数)
    ; p9 j' O) q( Q( Z0 M
  6. 6    *             pllq: PLL3的q分频系数(PLL之后的分频), 取值范围: 1~128.
    * v# v! W+ A/ d; Z# H
  7. 7    *
    . b/ f, H% j. ^1 l
  8. 8    *              MP157使用HSE时钟的时候,默认最高配置为:9 ]' M" L# }$ I( d& u! |2 u7 b
  9. 9    *              CPU频率(mcu_ck) = MLHCLK = PLL3P / 1 = 209Mhz
    8 C. D3 ~- w5 R* b4 p  A
  10. 10   *              hclk = MLHCLK = 209Mhz: e' D3 D- C6 ^5 Y8 F  n
  11. 11   *              AHB1/2/3/4 = hclk = 209Mhz
    , l6 E/ t- v) G9 {* o& O
  12. 12   *              APB1/2/3 = MLHCLK / 2 = 104.5Mhz' H8 u+ X2 p7 t$ R
  13. 13   * @retval      无;9 x  l5 p8 f2 V/ ^4 t0 B( U
  14. 14   */6 n# x& d# `# Y3 {( w
  15. 15  void SystemClock_Config(void); e3 k& A" v' L( T# m2 U2 k
  16. 16  {; I; h. T6 T" {8 u1 s- q
  17. 17  /* 定义RCC_OscInitStruct、RCC_ClkInitStruct结构体变量,并初始化为0 */$ G6 P2 F/ e; [' b* M
  18. 18    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    * A4 C9 A' Q4 P5 w3 w" Y% e
  19. 19    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};# u5 a$ C$ g% H( w! k3 W
  20. 20
    8 V- \6 M5 }# u. u: R6 b& E
  21. 21  /*给RCC_OscInitTypeDef结构中的成员变量赋值完成初始化RCC振荡器 */: L* `2 }$ d/ O9 L! D
  22. 22    RCC_OscInitStruct.OscillatorType = \                                 RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI
    ( A; C; a" k% d0 C; f7 B
  23. 23                                |RCC_OSCILLATORTYPE_HSE;
    ( T3 t( F4 `+ h0 x( I& K8 X' g2 i0 p
  24. 24    RCC_OscInitStruct.HSEState = RCC_HSE_ON;                  /* 打开HSE */% }" p- ^6 ?" Q5 n6 n6 v
  25. 25    RCC_OscInitStruct.HSIState = RCC_HSI_ON;                  /* 打开HSI */: e# l: ^3 Z6 s/ e& b- f" _
  26. 26    RCC_OscInitStruct.HSICalibrationValue = 16;                /* 校准HSI值 */
    ) ^2 {' A. X2 V$ S
  27. 27    RCC_OscInitStruct.HSIDivValue = RCC_HSI_DIV1; /* 设置HSI分频值为1 */0 P/ ]0 @8 t+ y( t( c
  28. 28   
    ' H' s* T  M' J" ~' h& `& y
  29. 29    RCC_OscInitStruct.LSIState = RCC_LSI_ON;                  /* 开启LSI */) w" r" C* I7 ]% h8 u3 ^) g0 ^
  30. 30    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;        /* 没有PLL的状态 */
    4 V, {* B8 o' Z! ?* S4 G7 X+ W
  31. 31    RCC_OscInitStruct.PLL2.PLLState = RCC_PLL_NONE;        /* 没有PLL2的状态 */9 q3 Q1 z: {+ A/ }
  32. 32    RCC_OscInitStruct.PLL3.PLLState = RCC_PLL_ON;        /* 开启PLL3 */7 o3 `7 `7 n1 I0 o/ ~1 j! n
  33. 33  /* PLL3输入时钟源为HSE */
    : S( a9 P5 j: ?! d6 c
  34. 34    RCC_OscInitStruct.PLL3.PLLSource = RCC_PLL3SOURCE_HSE;
    : o  J' L* l" x# G( J0 z
  35. 35  /*
    ; H2 D# k# z, {: x: {4 {
  36. 36  * 配置锁相环PLL3的分频和倍频参数,也就是:  F0 \( b1 n8 l4 v: y
  37. 37  * DIVM3=2,DIVN3=52,DIVP3=3,DIVQ3=2,DIVR3=2,FRACV=2048& V: w6 c) Q/ `# j* b8 L
  38. 38  * 则PLL3的pll3_p_ck输出频率为:2 O9 \/ R8 w: Z; A# G
  39. 39  * pll3_p_ck==209MHz
    - D! F& @1 v3 V
  40. 40  */
    % b; V0 Z8 G: F: n/ ]' p
  41. 41    RCC_OscInitStruct.PLL3.PLLM = 2;                        /* DIVM3=2 */8 t; X: [3 f, |" P
  42. 42    RCC_OscInitStruct.PLL3.PLLN = 52;                        /* DIVN3=52 */
    : ~( |: R, U1 @- ]% L9 x
  43. 43    RCC_OscInitStruct.PLL3.PLLP = 3;                        /* DIVP3=3 */
    / M. e* {: O. G4 f3 l
  44. 44    RCC_OscInitStruct.PLL3.PLLQ = 2;                        /* DIVQ3=2 */
    3 T% K# u6 d5 O
  45. 45    RCC_OscInitStruct.PLL3.PLLR = 2;                        /* DIVR3=2 */
    ! ~& b+ U. u, T7 u9 f( ~
  46. 46    RCC_OscInitStruct.PLL3.PLLRGE = RCC_PLL3IFRANGE_1;
    1 y4 o& m! w; u
  47. 47    RCC_OscInitStruct.PLL3.PLLFRACV = 2048;        /* FRACV=2048 */
    3 M4 @% _( d' J+ h
  48. 48    RCC_OscInitStruct.PLL3.PLLMODE = RCC_PLL_FRACTIONAL;/* 分数模式 */' q7 ~7 d2 T  o, ?
  49. 49    RCC_OscInitStruct.PLL4.PLLState = RCC_PLL_NONE;        /* PLL4没有状态 */4 p; ]3 Q! s" j( a- s- {1 n, U
  50. 50   
    6 E7 T; w9 g" h2 j0 u$ ?: A9 c8 O
  51. 51   /* 调用的HAL_RCC_OscConfig函数用于判断 HSE、HSI、LSI、LSE 和
    - q0 ^+ a! }6 `/ E
  52. 52    * PLL(PLL1、PLL2、PLL3和PLL4)是否配置完成,配置完成则返回HAL_OK。1 t' p4 P( b9 j# |4 ~, Q8 y+ F1 G; T
  53. 53    * 如果没有配置完成,发生错误的话就会进入Error_Handler函数(空循环)。
    + F& V. A% J2 r* y6 v5 [& a
  54. 54    */5 A0 n4 |: Y# V& A) s
  55. 55    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
    7 F/ M% b0 _8 l7 Q  G
  56. 56    {
    ! \5 z! g& c# k5 k
  57. 57      Error_Handler();
    + S% V7 B' c2 O! n. S3 i
  58. 58    }$ V( Y& p- }' D/ s  d8 k& V5 e
  59. 59  /*1 \, Q' A5 J2 T
  60. 60   * 给RCC_ClkInitStruct结构体成员赋值来配置RCC时钟,也就是:
    ) s  W+ [# _1 H) O- K. m
  61. 61   * 配置AXI的时钟源和分频器分频值(也就是配置ACLK);" }4 O( K# ]: t$ D9 Y
  62. 62   * 配置MCU的时钟源和分频器分频值;
    ; x% F2 i0 h; {. \: k" E; B
  63. 63   * 配置APB1~APB5的分频值(也就是配置PCLK1~5)。
    * S* s, v* F/ h% f: Y  U
  64. 64   */1 M  t" Y% R, G; w6 H, T* I! |/ L
  65. 65    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_ACLK
    # P( [' e4 H9 _$ ?+ G7 h
  66. 66                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK28 g) Q# ^( ~; f
  67. 67                                |RCC_CLOCKTYPE_PCLK3|RCC_CLOCKTYPE_PCLK4/ t% T& @7 ~4 o+ G, O( L" {" {
  68. 68                                |RCC_CLOCKTYPE_PCLK5;" p6 D, q5 X0 U: @% y: Z' j% b# ]
  69. 69  /* 配置AXI时钟源为HSI */                            " L5 D- o( Q7 N% G/ W& w
  70. 70    RCC_ClkInitStruct.AXISSInit.AXI_Clock = RCC_AXISSOURCE_HSI;2 X( R3 ]% E7 t( @2 w( X  D9 \# y
  71. 71  /* 配置AXI分频器为1分频=ACLK=64MHz */     X) c; F! z/ V
  72. 72    RCC_ClkInitStruct.AXISSInit.AXI_Div = RCC_AXI_DIV1;
    8 g8 ~, D* g+ O. l
  73. 73  /* 配置MCU时钟源来自PLL3=209MHz */) S3 p; G6 i6 m# S2 s
  74. 74    RCC_ClkInitStruct.MCUInit.MCU_Clock = RCC_MCUSSOURCE_PLL3;7 a- W2 f1 c6 ?, N- T3 V/ W
  75. 75  /* 配置MCU分频器为1分频=MCU=209MHz */
    5 Y7 H8 v* ~% c
  76. 76    RCC_ClkInitStruct.MCUInit.MCU_Div = RCC_MCU_DIV1;
    5 o2 d' @+ T' l! [) ]# s/ G  Z
  77. 77  /* 配置APB4分频器为1分频=PCLK4=64MHz*/
    * ?  C, m4 f9 i. f& O9 o
  78. 78    RCC_ClkInitStruct.APB4_Div = RCC_APB4_DIV1;( i  A! W) W* J) z! b
  79. 79  /* 配置APB5分频器为2分频=PCLK5=64MHz */
    7 K( e  `& I7 b* I( ^
  80. 80    RCC_ClkInitStruct.APB5_Div = RCC_APB5_DIV1;" \: q! d% M1 s! G
  81. 81  /* 配置APB1分频器为2分频=PCLK1=104.5MHz */
    ( A* ]9 A; i! M7 r0 n, a2 [3 ]
  82. 82    RCC_ClkInitStruct.APB1_Div = RCC_APB1_DIV2;* u5 ~1 Z. N$ {9 Q4 B+ d
  83. 83  /* 配置APB2分频器为2分频=PCLK2=104.5MHz */
    9 ]" j6 @$ {/ X) {0 p  K
  84. 84    RCC_ClkInitStruct.APB2_Div = RCC_APB2_DIV2;0 _2 L% _# H9 K/ t
  85. 85  /* 配置APB3分频器为2分频=PCLK3=104.5MHz */8 }) R  n- R) \5 j: t. C: E# J. K5 p
  86. 86    RCC_ClkInitStruct.APB3_Div = RCC_APB3_DIV2;
    ' a4 `4 f# l+ v# g3 I
  87. 87  /*9 h9 S/ c/ x* n
  88. 88  * 调用HAL_RCC_ClockConfig函数,根据RCC_ClkInitStruct中
    0 [& M' R  n" B+ ?6 W
  89. 89  * 指定的参数初始化MPU,AXI,AHB和APB总线时钟,如果初始化
    7 w: t5 f9 _/ U6 D' g
  90. 90  * 不成功,则进入Error_Handler空循环函数。* h' G! W# _3 X
  91. 91  */
    % t$ @& t: s! M6 E; z
  92. 92    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct) != HAL_OK)
    9 f! y* }5 |' t6 v
  93. 93    {
    ; \  {1 R6 U' ?. s% C
  94. 94      Error_Handler();
    4 W* X  K+ l: d9 U- M) _& P  W
  95. 95    }
    * P* |1 P) v9 I9 Y
  96. 96    /* 设置RTC时钟的HSE分频因子 */3 S6 t3 p& X5 r8 _
  97. 97    __HAL_RCC_RTC_HSEDIV(1);8 r2 @3 L8 h% a2 ^6 o" }" N, [
  98. 98  }
复制代码
3 x$ Y6 I7 F2 E8 _/ Z! M: u" w( l
函数代码分析
" Q7 Y5 h7 t9 \/ R9 E; G; U下面,我们来分析SystemClock_Config函数的代码实现过程。
5 t, |2 w8 U7 P5 U; D第18行,定义RCC_OscInitTypeDef类型结构体变量RCC_OscInitStruct,并初始化为0,表示将HSE、HSI、LSE、LSE、CSI和PLL1~PLL4均关闭。1 o4 c, s) W  G2 h( \) Q
以上结构体在stm32mp1xx_hal_rcc.h文件中有定义,我们在前面也有详细介绍过,为了大家不总是跳跃看代码,这里还是贴出代码了,如下。( T. @, u+ N+ V- H
RCC_OscInitTypeDef主要对RCC内部/外部振荡器(HSE,HSI,CSI,LSE和LSI)配置的结构定义,主要是定义要配置的振荡器、时钟源的状态、校准值和PLL的参数。通过给结构体成员赋值即可开或者关对应的时钟,并可以配置PLL的参数。9 h* U5 B, B& E
  1. stm32mp1xx_hal_rcc.h文件代码! \1 p1 \1 |" i$ a4 u
  2.     typedef struct: k6 c6 r6 k4 Y; k7 U
  3.     {
    $ s3 c* E0 A7 _% d4 o$ K# a
  4.       uint32_t OscillatorType;                         /* 要配置的振荡器 */                                                     
    0 c5 f$ ^! y4 ]1 p8 }* p6 Z
  5.       uint32_t HSEState;                                   /* HSE的新状态 */                                                                  
    ) Z6 ?2 q* K; \1 p" n# u5 x
  6.       uint32_t LSEState;                                         /* LSE的新状态 */                                                                       
    7 n$ Z/ R3 Z1 I
  7.       uint32_t HSIState;                                   /* HSI的新状态 */                                                                   + g2 C( X: k2 ?) l$ b9 M# s
  8.       uint32_t HSICalibrationValue;                 /* 校准调整值 */                                      * w! x7 `5 f/ o% I/ ]* U$ Z. k
  9.       uint32_t HSIDivValue;                                  /* HSI的分频系数 */                                             
    " R9 H2 O' D) z& M% |
  10.       uint32_t LSIState;                                  /* LSI的新状态 */                                                
    8 g9 E9 D6 ?9 H1 A/ C" ]
  11.       uint32_t CSIState;                                  /* CSI的新状态 */                                                
    ; @) x0 m% |; r& p9 E7 W
  12.       uint32_t CSICalibrationValue;                 /* 校准调整值 */                                       # D. Y  I8 H: m- {
  13.       RCC_PLLInitTypeDef PLL;                         /* PLL1结构参数 */      . K% u! |# r$ ]7 @$ V) i
  14.       RCC_PLLInitTypeDef PLL2;                        /* PLL2结构参数 */     - l! i! R. n  M" _) ^
  15.       RCC_PLLInitTypeDef PLL3;                         /* PLL3结构参数 */     ) f1 E* s6 D5 X% b! w
  16.       RCC_PLLInitTypeDef PLL4;                         /* PLL4结构参数 */                                                  . O1 _+ T9 d& e# y# C
  17.     } RCC_OscInitTypeDef;
复制代码

8 ~  p# ]8 Q% j, O$ X4 R我们查看PLL的结构参数有哪些,如下,此结构体定义了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量,通过给这些结构体成员赋值即可配置PLL的时钟。
. I: k1 k- L/ u+ K9 [* D
; h* l) W7 q) _
  1. stm32mp1xx_hal_rcc.h文件代码
    ! b) `2 o. V. R! T

  2. ; {# _, v, n' _
  3. typedef struct
    & @' I5 Q  K4 B- j
  4. {$ y1 U! T# j# O$ r
  5. uint32_t PLLState; /* PLL的新状态 /2 j+ i0 x- S" H- h2 U# n0 n" {! G
  6. uint32_t PLLSource; / PLL输入时钟源 /
    + ]0 l5 u7 d! k& l0 n: J
  7. uint32_t PLLM; / PLL VCO输入时钟的分频系数DIVM /
    1 |9 \) K3 Z2 R/ E( n& s3 c% q
  8. uint32_t PLLN; / PLL VCO输出时钟的倍数DIVN /+ t3 l  j, z  E. {' D5 {
  9. uint32_t PLLP; / 分频因子DIVP /
    % b1 I. ?9 I2 a  h+ A7 _, K5 u
  10. uint32_t PLLQ; / 分频因子DIVQ /
    6 x7 l& R" q- b% z: e' F7 q
  11. uint32_t PLLR; / 分频因子DIVR /" _: _0 `* j! Y3 G2 |+ Y! M
  12. uint32_t PLLRGE; / PLL3和PLL4的PLL输入频率范围 /: a3 t; |$ G+ U! E
  13. uint32_t PLLFRACV; / PLL VCO乘数的小数部分FRACV /
    # q% v( a* y) x' p
  14. uint32_t PLLMODE; / 使用PLL模式 /
    5 K4 ]* w+ O1 _" ~5 w9 k
  15. uint32_t MOD_PER; / 调制周期调整 /; @+ g" j! y% S. E; H
  16. uint32_t RPDFN_DIS; / 抖动的RPDF噪音控制 /
    4 `" y+ ^7 t) o" h( b( D# a
  17. uint32_t TPDFN_DIS; / 抖动的TPDF噪声控制 /# f5 j. a! K2 z% e; E7 K$ K
  18. uint32_t SSCG_MODE; / 扩频时钟发生器模式 /
    : q* ^; t1 j! O7 u# y7 e
  19. uint32_t INC_STEP; / 调制深度调整*/
    6 y5 M* Q! Q0 q
  20. } RCC_PLLInitTypeDef;$ Q' H0 }3 o# M
  21. 第19行,定义RCC_ClkInitTypeDef类型结构体变量,并初始化为0,即AXI子系统、MCU子系统时钟源默认来自HSI,且AXI时钟和MCU时钟以及APB1~APB5时钟的分频器的分频值为0(没有分频)。
    * a, e9 ~0 Y0 t3 f1 I" l% D0 ^
  22. RCC_ClkInitStruct此结构体我们在前面也有讲解过。RCC_ClkInitTypeDef结构体定义如下,主要是定义AXI和MPU的时钟源以及分频系数,还有APB1~APB5的分频系数,通过给结构体成员赋值即可实现对MPU、AXI和APB进行分频。# Y: _: B* e0 u
  23. stm32mp1xx_hal_rcc.h文件代码
    ) _) J% b0 y3 f9 c, Q+ H1 k, U8 {
  24. - m, H% T: Z, L
  25. typedef struct
    ; j7 e2 `' S7 L# s0 b0 z
  26. {, S- U# C! R* e* h7 A
  27. uint32_t ClockType; /* 时钟源选择 /( v8 O9 |" A: r0 f1 {* j& a* I
  28. RCC_MPUInitTypeDef MPUInit; / MPU结构参数(时钟源和分频数) /
    ) @2 V. l/ f/ }: Z( B. r, P# ^
  29. RCC_AXISSInitTypeDef AXISSInit; / AXI结构参数(时钟源和分频数)/1 X& ~0 v: V& c
  30. RCC_MCUInitTypeDef MCUInit; / APB4分频数 /
    * X8 K/ S9 B1 s% C0 S
  31. uint32_t APB4_Div; / APB4分频数 /, C2 S$ r" y% Z+ m2 b" R
  32. uint32_t APB5_Div; / APB5分频数 /( L2 V) u; I  z) [
  33. uint32_t APB1_Div; / APB1分频数 /
    / H3 o! \2 ^8 I, p
  34. uint32_t APB2_Div; / APB2分频数 /0 I3 @  u* U4 i6 B# @2 y, I0 Q
  35. uint32_t APB3_Div; / APB3分频数 */
    8 _& B& @( B8 e
  36. } RCC_ClkInitTypeDef;
复制代码

3 A' }: ?# G7 k; K; |( L4 ?分析完了结构体,整个系统时钟初始化就大概明白了,需要哪个时钟源,要用那个PLL以及PLL的各个参数,要对那根总线进行分频以及分频系数是多少等等,我们都可以通过给结构体成员赋值来实现。SystemClock_Config函数其实也是这么做的,我们接着分析此函数剩下的代码。1 t5 p# n& @0 w' _) g) b0 q
第21到第34行,给RCC_OscInitTypeDef结构中的成员变量赋值完成初始化RCC振荡器,开启了HSE,配置PLL3为209MHz。, `. W0 h6 d4 U
第55到58行,调用HAL_RCC_OscConfig函数真正完成HSE、HSI、LSI 和 PLL3的配置。" Q" C: _# p/ b3 G6 l& m5 \
第65到第86行,给RCC_ClkInitStruct结构体成员赋值,完成AXI、MCU和APB的时钟分配。其中:
2 w) y2 c. E/ j" B5 @AXI时钟源来自HSI,经过1分频后最终频率为64MHz;
, ]0 v7 c- y' B  W* d0 S2 AMCU时钟源来自PLL3,经过1分频后最终频率为209MHz;
0 R* J. [+ Q! A$ Q9 d, u确定了AXI的时钟,那么APB4和APB5的时钟源也就确定了。APB4和APB5经过1分频后最终频率为64MHz;: D: e& h: m! ~* G5 A
确定了MCU的时钟,那么APB1APB5的时钟源也就确定了。APB1APB2经过1分频后最终时钟频率为104.5MHz。
  F1 ^. l7 L9 y  _+ e: T) J' B可能大家会问,为啥没看到AHB1AHB6相关的代码呢?这个系统就默认配置好了,因为他们不需要经过分频器。AHB1AHB4的时钟直接通过mcuss_ck获得,为64MHz。AHB5~AHB6的时钟直接通过axiss_ck获得,为209MHz。
* _. o. D3 h" C1 Q9.5.3 main函数调用
" \) e% R. @3 i* d  X% e在main函数中调用系统时钟配置函数的代码:5 C* y+ b" R5 z

0 Y0 Y6 A' G0 v3 e# D6 ?( K
  1. if(IS_ENGINEERING_BOOT_MODE())  e; Q8 ?/ H% x* W4 A
  2.       {
    + [4 Q# A. I0 \
  3.         SystemClock_Config();& s! h/ T9 O. `4 c5 ?
  4.       }
复制代码
( l: E0 ^, r( L! }
其中,先判断IS_ENGINEERING_BOOT_MODE()函数的返回值,如果为TRUE 则初始化系统时钟,如果为FALSE,条件不成立,if语句不执行。我们来看看IS_ENGINEERING_BOOT_MODE函数:
3 F2 H) G, J5 g: L# `6 Y+ h  M2 F% Q  ]0 C% D" ^% g
  1. stm32mp157dxx_cm4.h文件代码
    9 f' v9 l& Z* q8 j  L7 _7 ]
  2.   #define IS_ENGINEERING_BOOT_MODE()   (((SYSCFG->BOOTR) & \ (SYSCFG_BOOTR_BOOT2|SYSCFG_BOOTR_BOOT1|SYSCFG_BOOTR_BOOT0)) == \ (SYSCFG_BOOTR_BOOT2))
复制代码
函数IS_ENGINEERING_BOOT_MODE通过判断引脚BOOT2 是否为1,为1的话表示从MCU启动(ST官方叫做工程启动模式),然后调用IS_ENGINEERING_BOOT_MODE函数来初始化系统时钟。9 P2 U+ X8 O: _* l7 X% B/ i
大家可以分析一下第二个工程MCU_HSI的系统时钟代码,对比看看它们的区别。4 _4 k2 i) m5 u% j$ U& g7 Y9 d
关于外设时钟初始化部分,下面的章节小结有总,我们也会在对应的外设实验章节进行讲解。
+ N& u. l+ b( k2 B, P/ a( |7 B% g- z6 w, D9 E
9.5.4 HAL_Delay的计时' b' \+ c+ N- U6 S( y
关于HAL_Delay是怎么实现计时的,我们在前面第7.4.2小节有分析过,关于Systick我们后面会有专门的实验章节做讲解。默认情况下,STM32CubeMX使用Systick作为时基给其它程序提供计时,例如HAL_Delay延时函数,以及串口程序中的Timeout 超时机制等等。/ h$ |1 T' F' x. v

, W0 z3 {8 L9 ?# J2 O; ` 24f8092125994d2c9712417359e27dee.png 4 \  j' |; z* E- i# t" m

6 v' a% B4 o5 B, i' m! a. X) E图9.5.2. 1 STM32CubeMX插件默认配置) t. @6 L8 e- |% t& c
————————————————; D- ?: R! H/ {( |
版权声明:正点原子' J8 o7 \/ M7 u. e! p: R" P

. F0 a( Q9 S' F0 }, q7 ~; @/ v8 [4 X6 y  i" Z
收藏 评论0 发布时间:2022-9-21 16:06

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版