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

STM32MP1 M4裸机CubeIDE开发指南

[复制链接]
STMCU小助手 发布时间:2022-9-21 16:06
第九章 STM32MP1时钟系统
* g5 M5 U* o' m如果让你用一句话来形容时钟,你会怎么说?
# @/ x4 |& }( x' _3 KA同学说:“时钟是单片机的心脏”;; x9 ?: z6 C7 z3 n8 O* {
B同学说:“军训方队训练时的121”;
+ ]( ~/ f1 v' B% i: l: [C同学说:“时钟是单片机的能量”;
$ D2 E8 s4 d( Z8 l/ C# K6 B' s9 eD同学说:“早上的闹铃”;; W4 @$ |% y" `# K# N
3 x6 }8 v/ b$ @  n( }( o+ |
可见,时钟是多么的重要。
  P  a' v/ i' q3 f9 H+ b2 T" e本章节我们来了解STM32MP1的时钟系统,并分析HAL库中和时钟相关的API函数,然后通过STM32CubeMX插件生成时钟初始化代码,通过分析工程代码去学习STM32MP1的系统时钟初始化步骤。8 q7 W' R4 R- d2 Z3 }( J
本章将分为如下几个小节:# t0 f) h$ L( o8 f
9.1、认识时钟树;
0 p6 b  ^( @3 ?9.2、RCC寄存器介绍;% A8 D# t0 s  F2 V- J: x* W
9.3、时钟相关API函数;& ?0 t* ^: C. x' m6 O2 o2 I
9.4、配置系统时钟实验;
" e- X5 c6 k" {8 Z6 O, Q7 T% [  @9.5、实验代码分析;6 w. {5 x# V) k3 z+ P
- {( s  R* ?& D3 N+ w" y
9.1 认识时钟树
: |. h' j2 R# V5 ?9 B* E$ b时钟是单片机运行的基础,相当于心跳,没有时钟,单片机就无法工作。为什么这么说呢?我们知道,寄存器是CPU内部的元件,由锁存器或触发器构成,触发器的置1、清0和置数的功能是靠其外部提供的时钟脉冲来产生跳变的,如果没有这个脉冲信号,电路就没有数字跳变,也就没有时序,单片机也就没有工作。时钟就像是人的心脏,这个恰当的比喻把时钟的重要性概括的淋漓尽致。
- u& c: K( K% U. U听说过时钟,但时钟树又是啥?前面我们说单片机的运行需要时钟脉冲来驱动,这个脉冲由源始晶体振荡器提供时钟输入,经过各级电路的处理,最终传输给各个设备,设备获得一定时钟频率以后开始工作。时钟的这种传输过程就好比一棵大树的养分供给给其它分支,树上的主干和树枝就好比时钟的总线,树叶就像是外设,时钟就是通过总线传输给设备的。
. \: t2 A$ t. R; A9.1.1时钟源8 f+ e7 P" |4 A, `$ E7 A: Q8 O4 C
51单片机的一个系统时钟就可以解决一切,但STM32不一样,STM32有5个输入时钟源(Input Clock)可以用,分为2个外部时钟和3个内部时钟。
+ X- _4 L' E+ \( ~下面我们看看这几种时钟源。
8 r7 p+ w5 u& @5 r' D! T1 a0 t(1)2个外部时钟源:+ U9 r. K9 J( O
高速外部振荡器 (HSE)2 c; V3 r5 v  O$ _, E  o
可通过外接有源或者无源晶振驱动,支持 4 MHz 到 48 MHz 频率范围内的晶振。如果要使用外部晶振,在STM32CubeIDE上配置的时候,请根据板子上外部焊接的晶振频率来配置,正点原子STM32MP157开发板的核心板上外接了24MHz有源晶振。
5 s; o: K3 _. b% Z" \低速外部振荡器 (LSE)
. l+ G) H6 Z/ `可通过外接晶振驱动,固件库中默认配置32.768 kHz(见7.4.1小节)。正点原子STM32MP157开发板的核心板上外接了32.768KHz无源晶振,主要用于驱动RTC 实时时钟和唤醒功能。+ G/ k6 x/ V* f. U1 L
(2)3个内部时钟源:
! Q6 [" a4 x( |2 W高速内部振荡器 (HSI)
/ m  U' d% W: w% L0 |0 Y5 |9 o1 L频率约为8、16、32、64 MHz,在固件库中默认配置为64 MHz。HSI比HSE具有更快的启动时间,大约几微秒。: j1 H" s& B+ O$ P8 K
低速内部振荡器 (LSI)& j. b/ y8 u7 s
频率约为32 kHz,实际值可能会因为电压和温度而变化。主要用于看门狗。
" B: {/ D9 D7 I* w) }8 [低功耗内部振荡器 (CSI)1 M8 h" A1 A4 }) U
频率约是为4MHz,主要用于低功耗。6 I+ S8 b6 O: D6 z: b/ f5 a
以上2个外部时钟源都是由芯片外部晶振产生时钟频率,其精度较高,而内部的时钟源可能因为受到温度的变化,频率发生变化,也就是存在“温飘”的情况,即使经过校准以后,精度也比外部晶振低,对于日常普通的短时间计时,使用内部时钟相差不大,对于较长时间计时就不建议使用内部时钟了。使用内部时钟可以降低成本,例如在一些白色家电中,一些家电产品为了降低成本,把电路板上外接的晶振去掉,程序改用内部时钟来工作。' k7 [' ]# ^' a7 _9 p
大家可能会问,为什么STM32 要有多个时钟源呢?( W  b, T- S; ]9 ?6 U7 ~- `
我们知道,在同一个电路中,外设开的越多以及频率越高,功耗就越大,同时抗电磁干扰能力也会越弱。在实际的使用中我们并不是所有外设都会用到,有时候只是用到其中的某几个功能,如果打开用不到的外设,功耗会变大,而对于用到的外设,有的外设可能用不到系统时钟那么高的频率,例如看门狗或者RTC只需要几十KHz的时钟就可以工作了。ST在设计芯片的时候也是考虑到了这一点,对复杂的MCU采用多时钟源的方法来解决,并用时钟树来管理,将所有的外设时钟都关掉,用到什么外设,就使能对应的外设时钟即可。下面,我们来看看STM32MP1的时钟树。
& |. v5 |( K, C5 u7 G9.1.2时钟树$ B" K. F4 g' y3 |: P

; t4 B- v& _& O4 H8 J2 P 5834ee9f87254d6995051288551d0480.png
6 N  w2 X& Z: _) J$ X8 R' `
- U, q7 \6 E9 O; h$ C/ Y图9.1.2. 1时钟树框图
: E9 k' a1 V: q( V! f8 o注:
7 i0 [" k  e, V  v  ?2 A①图中的 表示动态时钟选择器,图中的D符号表示动态选择开关,即时钟选择器允许即时重新配置选择输入的时钟源,两个输入之间的过渡无干扰。仅当目标时钟源准备就绪(启动延迟或PLL锁定后时钟稳定)时,才从一个时钟源切换到另一时钟源。如果选择了尚未准备好的时钟源,则在时钟源准备就绪时进行切换。* c- j+ `) B3 s  ^' ?" ]( w
②图中的部分时钟命名:  r9 c8 v. A- _, B4 a0 j0 [

) M/ f5 t1 A1 F; n( ? 9015248081b84a95a9cfd0daa9f938fe.png 7 y& C8 `3 D' [: I# S

9 q& X7 t( X) n  C/ F  @表9.1.2. 1时钟命名
4 }1 i$ O! u( s/ ~! q; {7 ~$ R2 F- r. y9 G7 e# o
外部时钟输入, w) ?' X$ w4 o6 t
图中①处是外部时钟输入引脚,当使用HSE时,HSE在OSC_IN引脚接收外部时钟源,当使用LSE时,LSE在OSC32_IN引脚接收外部时钟源。
6 q6 F" Q" f/ C( ?  o. [: `4 r" B0 o. V- Q) D" J' N. i
159bfd153fe24136867eee11802a7a4a.png
1 p  n. R9 C; T* x5 @( s1 V! y# e5 Y- ]  ?4 |# x
表9.1.2. 2外部时钟输入引脚. l8 s0 _8 W; ?
2. MCO时钟输出
/ Y; p# E0 `- J* F( m- N②处是MCO微控制器时钟输出(microcontroller clock output)部分。* e% m6 u" D: V! [
MCO1和MCO2是时钟输出,通过芯片引脚可以给外部的芯片提供时钟,可以节省晶振,节约成本。MCO1和MCO2时钟输出通过配置RCC_MCO1CFGR寄存器来实现,具体配置可以参考手册详细说明。5 C) {& k* h3 ?" I, r; [
时钟输出 Pin name
; |) q1 W- l' h5 U$ Z; E: B: R' R$ n8 a  X! {
c2fe0b3f25364b109577848446977758.png
2 o- Z# z% l7 D0 O; x1 {/ D5 l6 g# f& C$ f
表9.1.2. 3 MCO时钟输出引脚
* [) |: K- _+ ZMCO1SEL和MCO2SEL是时钟源选择器,分别选择MCO1和MCO2的时钟来源;& S8 d+ O% u7 Y7 \" Y' E% I
MCO1DIV和MCO2DIV是时钟分频器(也叫预分频器),取值范围是1~16;, l; G  l4 B9 ~' K
MCO1时钟源来自hse_ck、lse_ck、his_ck、lsi_ck和csi_ck,用于输出HSI,HSE,CSI,LSI或LSE时钟。
6 m, c7 G; b8 l  H: Q# J& y2 ~3 ZMCO2时钟源来自mpuss_ck、axiss_ck、mcuss_ck、pll4_p_ck、hse_ck和hsi_ck,用于输出MPUSS、AXISS、MCUSS、PLL4(PLL4_P)、HSE或HSI时钟。$ Q3 E( E# u; C$ V& j/ {
2 {; v  R$ A) k+ E+ W& N
3. 锁相环PLL" }" R* `; `; Y; Z, ?0 F
③处是和锁相环有关部分。
4 E5 J& _, s: NPLL(Phase Locked Loop) 锁相环路,其作用是使外部的输入信号与内部的振荡信号的相位同步,在STM32中主要用于分频和倍频。STM32MP1有4个锁相环,分别为PLL1、PLL2、PLL3和PLL4:
) r- f! l6 P6 l# H# Q1 l6 y. C/ Z' uPLL1,用于向MPU子系统提供时钟;
$ ^" ~1 P$ z7 wPLL2,用于向AXI子系统、DDR和GPU提供时钟;: R4 E8 \" C9 K
PLL3,用于向MCU子系统提供时钟并为外设生成内核时钟;; m  D0 J/ k- C7 A
PLL4,用于生成外设的内核时钟。; `' A8 M: p7 [. q0 }1 b
④是PLL时钟源选择器。
9 I& D+ d6 u# j9 ?1 n' Y& t; ^锁相环输入时钟源有三个:his_ck、hse_ck 和csi_ck,分别对应HSI、HSE和CSI的时钟。我们可以配置某个锁相环选择对应的时钟源。. ]1 T$ [6 [6 l, h
⑤是PLL时钟源分频器
7 Z4 a7 n3 w  U: A6 W/ g1 m9 a$ f4 CDIVM1、DIVM2和DIVM3和DIVM4分别是PLL1、PLL2、PLL3和PLL4的分频器,取值范围都是1~64,表示将输入时钟源进行分频。
; W! I7 j4 C, N) z, E# B- ^⑥是锁相环
8 L' Q+ |& C. G6 Y- x# p/ \  B: p其中PLL1是一个输入一个输出,PLL2、PLL3和PLL4是一个输入三个输出。! h, d5 o1 W0 I5 t+ |# r0 M5 S5 R
我们以PLL3为例子分析一下锁相环的内部结构:
% ~& X; b) b2 L0 e& B: I$ JVCO表示输出频率;
& G# @- L4 r' M0 kDIVN表示VCO的倍数;2 I6 ^/ C" C! q
DIVP、DIVQ和DIVR表示预分频器;: g( y$ _. L8 }. \$ l' {
SSCG扩频时钟发生器,可以减少EMI峰值的数量,一般不用设置;
8 ~/ g( r% e1 ?3 z% g  g% G( PFRACV是分数倍频系数,它和DIVN一起组成PLL的倍频系数,当FRACV等于0的时候表示在整数模式下,大于0的时候表示在小数(或者说分数)模式下。
5 U! l9 o3 Y$ }) EPLL输出的频率有两种计算方法,我们以PLL3输出pll3_p_ck为例,其它锁相环的输出计算方法类似,时钟源默认使用HSI,即64MHz。. j5 P; |2 n, T1 X
在整数模式下使用PLL:
) a- j3 F0 t% o$ f
1 L& Z; G! U" s$ M2 \- R在小数模式下使用PLL:& k3 |3 Z* i5 n
注:% m5 R/ n$ p- P) [+ g
手册中公式的(DIVN + 1)其实就相当于我们上面公式中的DIVN,因为STM32CubeMX插件中是这样计算的,我们的公式中就跟STM32CubeMX插件的计算方式一致。
: A' o. ~) a3 Y/ N& g. c- I- W6 I% ]$ f; K* ]5 f
0d50aa4946f749cda862d03ed5de9102.png 7 R3 M! m* [  Z2 }' m: b0 |1 \- g) y( v

# P: y& K6 H9 F1 B; n3 Y+ F- q; j1 g图9.1.2. 2参考手册上的公式! j/ |; V2 i+ [3 K4 j6 ?
例如,在整数模式下(FRACV为0),当设置分频器DIVM3为4,DIVN为26,预分频器DIVP为2时,计算出pll3_p_ck为208MHz。如果再设置FRACV的值为10,根据上述计算出pll3_p_ck约为208.009766MHz。在STM32CubeMX插件上,我们手动配置这几个参数,结果和我们计算的结果一致,如下图所示:
6 n6 F/ E# v* G+ l% j) G' d7 u4 E  G1 n1 {" _
2c757d68de5d4be5ab7c9849dfa4a2bd.png - K( Q$ i$ ]" z4 M6 b! N

+ N$ Y% m5 ?' j0 P7 U* l图9.1.2. 3 整数模式6 K8 o* {( k$ f6 [" p
4 w$ ~0 {; }4 a3 K" x" s" O) I
02085802999446128087f9e153456aaf.png 0 n9 ]/ `3 L2 w* R' V$ h

! y6 H0 v4 z5 f5 r% r/ R7 u图9.1.2. 4分数模式' z" W9 d- U. x* C: y
4. 子系统时钟
  _8 v5 N5 P. `8 `# T⑦处是子系统时钟,RCC处理三个子系统时钟:mpuss_ck、axiss_ck和mcuss_ck。其中mpuss_ck给MPU提供时钟,axiss_ck给AXI互联矩阵提供时钟,mcuss_ck给MCU提供时钟。上电复位后,所有的PLL和CSI以及HSE关闭,默认将HSI选择为整个系统的主时钟,因此,子系统时钟默认来自HSI。当系统运行时,用户可以选择子系统的时钟源。: }6 R1 e" ?, k) ]8 q
我们来查看子系统部分的详细框图:. K$ u0 Z; p0 ~% b8 i

* i' r" a% C7 n; }8 [+ V 01aedfc913394910b8750534ef319d3c.png ) C" U! U2 }/ \9 n

! T) q( R; p% R7 t! \' x3 `5 j图9.1.2. 5子系统框图
+ h+ `; h5 ?- _+ Ampuss_ck的频率最大可以是650MHz或者800MHz,这个根据所用的器件型号决定,STM32MP157A的MOU主频为650MHz,STM32MP157D的MPU主频为800MHz。 axiss_ck时钟最大是266 MHz,其经过AXIDIV分频器分频后分流为几路,给总线提供时钟。mcuss_ck时钟最大为209 MHz,经过MCUDIV分频器分频以后也是分成了几路。5 J& p5 l0 `( M& }6 ?" {
pclk4和pclk5最大为133 MHz,pclk1、pclk2、pclk3最大为104.5 MHz。
& Q9 I; |3 t* _1 h子系统时钟汇总如下表格所示:7 U0 s5 I9 R& m

% r3 Q6 i; J* ~/ n) }( E" W 8b7fbfe7c4d54e4faf569af39d07f2d0.png 8 D& t4 D  d! J  ^( C; H
% w2 O+ Q! G  ]$ J
表9.1.2. 4子系统时钟信息表0 p) J. N4 X, y3 i, p
5. CPU、AXI总线矩阵和外设时钟
0 b5 l4 ^  M0 c- ~0 k在STM32CubeMX插件上,我们可以看更具体的时钟树框图,如下图:
3 h0 L1 O; O0 h& J0 a  D1 h4 Z+ qA处是时钟源,可选外部时钟或者内部时钟,要用外部时钟的话,必选先使能外部时钟;! ~! q- e0 c! _* _) g. b, \
B处是锁相环PLL;! a- ]! g- L( D' t/ J9 R# Q! s, n/ J( d
C处是内部时钟输出MCO1和MCO2;
4 t) b0 }$ g6 o# V1 U黄色区域是子系统时钟;3 `3 M9 a* h- A0 Z9 E1 c; H
紫色区域是专门给CA7外设提供时钟的总线时钟,分别是AXI、AHB5、AHB6、APB4、APB5,;
/ q/ b6 A  T. p8 v蓝色区域是给MPU和MCU提供时钟,其中,MPU时钟最大是650或800MHz,MCU时钟最大是209MHz,MCU有一个内核外设Systick,频率最大也是209MHz;
" u1 i7 N& h8 _& x7 M; e8 B绿色区域中AHB1-4、APB1-3 是提供给 CM4 外设使用的;
9 s/ h3 W; A1 {5 i) [7 R) y6 D红色区域是内核外设时钟具体配置部分,默认没有使能,是灰色,只要在 STM32CubeMX 使能了对应的外设,对应外设的时钟就会由灰色变成蓝色。
/ j1 q9 Z  {# V+ ^$ u5 ?; S( q注:' L+ q2 V0 L) O; R- S7 m5 N1 R
RCC为整个电路提供时钟,为了避免造成误解,和参考手册一致,使用以下术语:2 p( Z% t/ y" O4 @4 ~- U
•外设时钟:& s1 o' b# G; j+ P4 H
外设时钟是RCC提供给外设的时钟,可以为外围设备提供两种时钟:6 \; U2 W5 Q' Y6 J# Q9 {7 ?
–总线接口时钟4 u( Y7 U2 s! `  T/ x0 d0 b
–内核时钟
: l7 U+ E" t6 Y5 r6 x: x0 z/ d, Q这里的外设有分普通的外设和内核外设。外设将从RCC接收一个或多个总线接口时钟,该时钟通常是AHB,APB或AXI总线接口时钟,具体取决于外围设备连接的总线。6 f/ z4 v4 ?! C8 s7 q& G8 U
某些外设仅需要总线接口时钟(例如GPIOx、ETZPC、EXTI 、IPCC 、WWDG1、DMAx …)。1 j- o# C  D; Q$ {# N
一些外设可能还需要专用时钟来处理接口功能, 该时钟称为内核时钟。例如,诸如SAI、ETH、FMC、GPU等这些的外设需要生成特定且准确的主时钟频率,这需要专用的内核时钟频率。: e" W& `2 K$ N' G; |4 B2 ?
•CPU时钟
, V. U9 z6 e: S9 R3 P3 {( |CPU时钟是提供给CPU的时钟mpuss_ck。MCU时钟是mpuss_ck。8 s4 Y5 m7 U' g: R4 {; c
•总线矩阵时钟
. m7 B1 ?0 q2 C+ i, R% [1 ?2 a$ |总线矩阵时钟是提供给不同桥(APB,AHB或AXI)的时钟。
+ C$ P" r: D# @7 R& c图中,表示APB总线的分频器,里边的数值表示分频因子。表示是时钟源选择器。
+ Z- Z. E0 q9 |" D6 USTM32CubeMX插件上的时钟树比较形象,很好理解,图中的一些符号可能和我们参考手册上看到的不一样,不过根据字面上的意思可以知道大概对应了哪些设备。我们可以通过STM32CubeMX插件来配置时钟,然后自动生成时钟的初始化代码。
, f& T1 ]1 |$ @+ I3 c: D0 a" g% S6 ?# S
476fda53b1b54ea28526a3556a7d7ee5.png
5 Y8 _* p( ?- E
- G# c& {- z1 N4 g  e# B! `, e) k0 l9 C图9.1.2. 6 STM32CubeMX时钟树
& n* b% H2 A% d& ?! I对于内核外设时钟,我们以SPI为例子来说明一下:* m% [5 @; S- c* @6 d4 L7 ^1 f# U

! N% H4 Q8 {6 Z4 @: j# W 696b414bc19f4f4b8c57fa6fb55ba668.png $ E% F. T/ H7 ]" q- E( o
$ k- u4 |0 G) r- p/ D. v3 O$ }
图9.1.2. 7 SPI时钟框图
0 A6 s$ e+ U: Z4 y7 ]PKCS (Peripheral Kernel Clock Selection)是外设内核时钟选择单元,用于选择某个外设的内核时钟来源。PCKE(Peripheral ClocKs Enable)是外设时钟使能单元,用于是否启用时钟输出到外设中。
8 p" U* o. ?$ z0 k% D# L如上图,SPI6的内核时钟源来自于APB5总线(pclk5)、PLL(PLL3和PLL4)、HSI、CSI和HSE中的某一个,具体是哪一个由PKCS决定。其中,HSI和CSI用于低功耗中,PLL4在需要时具有更高的灵活性,例如,它允许通过PLL3更改MCU总线的频率,而不会影响某些串行接口的速度。) s0 X6 K4 M) O
SPI6的PCKE有两个时钟部分,一个是来自于APB5总线接口的spi_pclk,一个是来自于内核时钟spi_ker_ck。PCKE决定是否启用SPI6的时钟,其它内核外设也是通过PCKE来开启的。由此可见,各个外设时钟基本都是可控的,不用的外设,就关闭对应的时钟,用到的外设,就根据分频器来配置,选择一个适合频率的时钟源,然后再开启外设时钟,这样的设计大大降低了功耗。
  A2 o( L8 f( j2 m- [; c! e) x, Z根据STM32CubeMX插件上的时钟树,ST给出了一张总线框图,如下图,从图中可以看出哪些外设挂在哪根总线上,以及总线的最大时钟频率是多少等信息。
& D& }+ ^0 b8 q; o* u* }9 ]下图中,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等)之间的连接,也称之为外围总线。0 Z% I8 Q0 t9 W! b

* A$ h7 E5 J4 L! }/ Y& a6 z6 ] 2cb852a2a04e4280a317c6f134b1bf0d.png
) W( ~) c4 s3 r( n# R. j* n- V3 l8 J; B3 j( H! c( N; n6 B/ ?
图9.1.2. 8 STM32MP157A/D系统总架构图
2 g" Q  ?2 p6 [) `6. 时钟安全系统(CSS)
9 e1 \9 }. s% B( _. D9 {如果启用外部时钟HSE和LSE的话,硬件将启用HSE或者LSE上的CSS。我们在上面的框图以及STM32CubeMX插件上的时钟树均可以看到CSS模块。如下图,右边的是框图中的部分截图,右边的是STM32CubeMX插件上具体的截图。图中配置HSE为PLL3的时钟来源,在并将pll3_p_ck(PLL3P)作为MCU子系统的时钟源,可以看到Enable CSS模块自动变为深蓝色,表示使能了CSS。而没有使用的LSE,对应的Enable CSS模块颜色为灰色。
% o$ G; K# J% b
3 X: _5 n5 W" z! S: p e5d007b9b90a4ff6b501e362013506d3.png / b0 ~* ~+ S! \$ y) C4 p
: R4 e  {  j; w# u: l' d
图9.1.2. 9外部时钟CSS
; M6 y0 e" B7 jRCC具有时钟安全系统(CSS),允许应用程序检测外部时钟(HSE、LSE)是否由于意外原因而停止翻转。* P, l. I: G+ `, N" b
如果使用的是HSE,在检测到故障的情况下,CSS将生成系统重置,并保护BKPSRAM的敏感数据。时钟安全系统可以由应用软件通过HSECSSON位激活,(但无法通过软件清除HSECSSON位),即使将HSEON设置为0,也可以激活HSECSSON位。 当启用并准备好HSE并且HSECSSON设置为“ 1”时,硬件将启用HSE上的CSS。当禁用HSE时, HSE上的CSS会被禁用。
- ^+ S$ t( z  R2 m  v: z8 k如果使用的是LSE,如果检测到故障,CSS将生成故障事件,禁用提供给RTC的时钟并保护BKPSRAM的敏感数据。也可以通过应用程序将RCC_BDCR寄存器的LSECSSON位置1来激活LSE上的时钟安全系统(CSS),但必须按照以下顺序在LSE上激活CSS:) |% B- J# N7 N$ V* f( @
①将LSEON设置为’1’,并等待LSERDY =‘1’;% b+ R' w$ J; G( S0 \
②通过RTCSRC字段选择LSE时钟;: C& ^7 P* s! b4 }) |% G, }
③设置LSECSSON 到“ 1”。1 e* P: c2 f% ?
关于CSS,我们这里不做深入研究,感兴趣的可以查阅参考手册以及ST官网相关介绍。
& s3 S1 h  ~+ [2 D1 J" r9.2 RCC寄存器介绍; M0 F: ~2 Y  [# z  p" \
下面我们来看看RCC相关的寄存器。RCC寄存器分为RCC TrustZone相关寄存器,RCC振荡器时钟相关寄存器,HSI、CSI、MCO1和MCO2、AXI、AHB、APB、PLL、MPU和MCU相关寄存器,还有外设的内核时钟选择寄存器。这么多寄存器,我们要配置哪个寄存器就查看对应寄存器即可,这里就不一一介绍所有的寄存器了。而且HAL库已经将这些寄存器封装好了,调用HAL库即可控制这些寄存器。
( Q0 ~5 u5 j; ]1 s6 P9.2.1 寄存器分类介绍6 Q  `7 L+ y" \. J. I
( m3 `6 Z5 m! E8 [4 g" n+ e
RCC振荡器时钟相关寄存器
+ `2 V! ?$ L9 R7 c! iRCC振荡器时钟寄存器有RCC_OCENSETR、RCC_OCENCLRR、RCC_OCRDYR。
% i9 K* j2 r0 D' [3 S( h(1)RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)
1 ^7 `  v; t6 {/ c. N  w% d1 J# O2 X. p1 M% a; X3 g4 I
8876affd70514e3196944e77e73d8e4b.png 3 f" C8 N! a3 Q! N  |& X6 \& ]

3 ^7 y; v1 Z2 A2 w$ w% g7 }图9.2.1.1 RCC_OCENSETR寄存器
" {9 x. k2 k3 t7 W! J; z' j, DRCC_OCENSETR时钟用于使能(HSE、HSI、CSI)时钟,向该寄存器写入0无效,将对应的位置1则开启对应的时钟。如果TZEN = MCKPROT =‘1’,则只能在安全模式下修改此寄存器。在时钟恢复序列期间,不允许对该寄存器进行写访问。
# k$ b  R+ _6 ^( ^1 H8 s该寄存器配置也比较简单,以HSE为例,如要开启HSE,则先将第8位HSEON置1来开启HSE时钟,再根据是否使用旁路模式来配置第10位HSEBYP,如果使用安全模式,则配置HSECSSON。; d2 b5 y, q/ T0 D8 c) G& R* R% J
(2)RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)/ ^  m( F- U* |, M" x# _: U

" z1 ]) }! d) ]. F 1ba11a85ae33462d984e1a6322b69750.png
8 w+ D5 b9 }2 h% ^  T! @  c0 e9 c, o/ s; J3 u
图9.2.1.2 RCC_OCENCLRR寄存器
+ i$ X& [: t' n该寄存器用于清除寄存器对应的位,对位写0无效操作,写1表示清除设置的位,也就是关闭对应的时钟。如果TZEN = 1,则只能在安全模式下修改此寄存器。 在时钟恢复序列期间,不允许对该寄存器进行写访问。
0 t# X( z" h8 R例如,要关闭HSE,则将HSEON位写1即可。其它寄存器操作也类似。1 i* Q/ G2 y2 @8 Q3 h
(3)RCC振荡器时钟就绪寄存器(RCC_OCRDYR)
0 d! a% D& t6 E4 T- T* p: O+ G; W  X0 o* x( M, I
e02b509ccb6145208864d74bf14e7849.png
- G) d8 t4 B7 U- g5 s+ q# e* W5 _* h1 L1 O" y' t
图9.2.1.3 RCC_OCRDYR寄存器) J+ I" x. n+ Y% b$ J8 C, c
该寄存器是一个只读访问寄存器,它包含振荡器的状态标志,写该寄存器的话是没有效果的。如果读取某位为0的话,表示该时钟未准备就绪,如果读取某位为1的话,表示该时钟已经准备就绪。例如,如果读取HSERDY为0,表示HSE时钟未准备就绪,如果为1,表示HSE时钟已经准备就绪。
8 H# \1 a+ ]7 o4 H) m$ _1 U8 E/ ^2. MCO1和MCO2配置寄存器
' ^: q" [3 G  q9 BMCO1和MO2用于配置MCO1和MCO2输出,两个寄存器的配置类似,我们以MCO1为例:. u2 r# ~1 l- c7 C/ S

. [4 M/ Y0 H3 b4 V: Z5 V0 p! j 3c60b54c322441b7aad85a0b09004367.png
( v' d6 R# X. ]0 |: z2 x9 b! i0 z8 V7 X' f$ |3 W) |7 y
图9.2.1.4 RCC_MCO1CFGR寄存器
" N! N/ C. T) y9 U0 r9 _MCO1SEL[2:0]:为MCO1时钟输出选择位,由软件设置和清除。时钟源选择可能会在MCO1上产生毛刺,所以建议仅在复位后配置这些位,然后再使能外部振荡器和PLL。将该位:" H; Z; P6 F3 B: E3 }% ^7 D0 l1 k
0x0:选择HSI时钟(hsi_ck)作为输出;0 }& E  [6 j* o$ ^7 u  w
0x1:选择HSE时钟(hse_ck)作为输出;1 E" X# C6 {( U; Q- V- U
0x2:选择CSI时钟(csi_ck)作为输出;* F0 e& s/ A* D/ `1 p# V
0x3:选择LSI时钟(lsi_ck)作为输出;  p$ }( ], n5 E
0x4:选择LSE时钟(lse_ck)作为输出;
3 b; L( |% X2 tMCO1DIV[3:0]位用于配置MCO1的预分频器,对该位配置:
3 q- v2 M) |9 }- D0x0:旁路模式;: D8 N3 y, j: \$ z$ O+ [
0x1:分频值为2;
! @% \, Y$ J6 U/ z0 S8 i6 J0x2:分频值为3;
. F8 ?; J, o# m2 O6 X+ M4 m2 s' }7 m/ B8 t' j, z/ }- g
0xF:分频值为16.+ g% `& ~/ [% V3 T
MCO1ON位用于控制MCO1输出,将该位写0则关闭MCO1输出,写1则开启MCO1输出。7 g3 ]% P! T, t0 Y: ]
3. MPU和MCU时钟源选择寄存器
$ r6 r* O7 \) R4 G- [STM32MP157有两个A7内核和一个M4内核,RCC_MPCKSELR和RCC_MSSCKSELR分别用于选择MPU和MCU的时钟源。我们以MCU为例:
8 t. d* w7 ^! r5 M(1)RCC MCU子系统时钟选择寄存器(RCC_MSSCKSELR)# `/ K' x+ _5 d5 a( c& U

3 o* W) v" `% Y6 S; j4 J3 U f8c5c012d33f48c99a4f794f41a7a5de.png 3 R$ Q  s$ m+ z: p
1 w/ Y6 u' T! d7 _
图9.2.1.5 RCC_MSSCKSELR寄存器
5 r1 U) B- H) |- AMCUSSRC[1:0]位用于选择MCU子系统的时钟(MCUSS)来源,通过配置该位可以选择MCUSS可以来自:5 O. @6 H( d7 f+ r4 Z7 H
0x0: 配置MCUSS时钟源来自HSI时钟(hsi_ck);. j/ {( h" P- G3 i/ S- M9 Q' [
0x1: 配置MCUSS时钟源来自HSE (hse_ck);
$ G6 f4 o# \+ g4 }  l, d0x2:配置MCUSS时钟源来自CSI (csi_ck);
0 a! G" X2 [4 S# a5 q! B0x3:配置MCUSS时钟来自PLL3 (pll3_p_ck)( x, J( h# @5 J+ P
MCUSSRCRDY属于只读位,读取该位为1,表示MCUSS已经准备好,可以切换到MCUSSRC配置的时钟;读取该位为0表示MCUSS未准备好。5 N; p4 `4 S5 k/ |
4 h: {3 Y2 B; q
通过软件置1和清除,以配置MCO1的预分频器。 修改该预分频器可能会在MCO1上产生毛刺。 强烈建议仅在复位后才更改该预分频器,然后再使能外部振荡器和PLL。; [! a+ d5 @$ L4 @6 l6 x# }
4 D3 p& J$ @8 x
有关详细信息,请参见第10.4.4节:时钟输出生成(MCO1和MCO2)。% n& w% {% I! Y1 T
(2)RCC MCU时钟分频器寄存器(RCC_MCUDIVR)& T$ [! l; O$ @# n* l

( m9 Z1 a$ C8 A( g 9b2c9771d316418c82485db761ca7e7f.png 2 Z+ s; g( V( P6 U

8 W( [* {& r. D+ F) ~+ Q" _+ i图9.2.1.6 RCC_MCUDIVR寄存器
% H6 @) m" {4 nMCUDIV[3:0]用于配置MCU时钟的分频值,配置该位:: I8 F- y; H, X
0x0:MCU子系统时钟(也可以写为MCUSS或者mcuss_ck)不分频;" p5 f& \5 |. n4 J0 N( P3 G
0x1:2分频;
! t5 s: L2 N" a" S4 d7 C4 t0x2:4分频;
7 L1 ?5 X% h3 E, |1 E7 b, @; b, U9 o0x3:8分频;
+ E+ m0 E6 G. \% t3 f, w  s0 d9 Q6 j. l2 u4 _1 @- O- d3 F" i
0x8:256分频。
8 f/ f) j! A, b9 V$ E) U其它配置:512分频
3 }4 H6 `: U& \/ I' `MCUDIVRDY属于只读位,用于指示是否考虑了新的分频系数,该位:
6 M8 b$ Y/ p, g. E# y6 j. ~% x5 {. ?# B0:尚未考虑新的除法系数;
9 u! E4 R" q( E, l  K1:考虑新的除法系数。(重置后的默认值)$ ?. A+ M! s4 l8 |" ?
4. HSI/CSI时钟配置寄存器
( W% q) t$ _& {HSI和CSI时钟配置寄存器由RCC_HSICFGR和RCC_CSICFGR来控制,用于配置HSI和CSI的时钟源选择和分频器分频值等。) q9 M# r  R: E% v
(1)RCC HSI配置寄存器(RCC_HSICFGR)
7 a1 ?  g. _4 C+ c( ]1 t. x( A  ~, w7 x6 M. C
a083fa8395a24d2a9613262e76b15983.png ; \; r! |; S( R* J3 j

+ x+ ?# I, [$ t, ?. k图9.2.1.7 RCC_HSICFGR寄存器
$ A3 c. F: o9 T+ t% KHSIDIV[1:0]用于配置HSI的时钟分频值:
1 h: k; @" F1 F0x0:配置HSI为1分频,hsi_ck (hsi_ker_ck) = 64 MHz;
; i  W( Z# K& o0 H# v- p0x1:配置HSI为2分频,hsi_ck (hsi_ker_ck) = 32 MHz;
3 f) P) ~5 [5 H" {3 x! N, Z0x2:配置HSI为4分频,hsi_ck (hsi_ker_ck) = 16 MHz;% n& d. q9 t* P+ L4 W4 p% E0 {
0x3:配置HSI为8分频,hsi_ck (hsi_ker_ck) = 8 MHz;
3 H- Y$ @2 Y! M& U/ X+ K+ Y$ FHSICAL[11:0]和HSITRIM[6:0]:用于配置时钟校准调整值,这两位我们可以不用管。; ]5 o8 F2 Z* S4 `5 M. Z6 g
5. AXI/PLL1/PLL2/PLL3/PLL4时钟选择
. m+ R2 Z  g& \8 p+ @' v: uAXI/PLL1/PLL2/PLL3/PLL4都有对应的时钟选择寄存器,用于选择其时钟源。- R% p3 y$ ^; f4 Q* C: i" |5 D
AXI子系统的时钟可以选择HSI、HSE、PLL2;
# R. [  w& ]; P  dPLL1和PLL2时钟可以来自HSI、HSE;/ e6 L; _3 C" y2 {: C% b, k
PLL3时钟可以来自HSI、HSE、CSI;; L& i* j* F  ]+ a! @' Q; e4 E
PLL4时钟可以来自HSI、HSE、CSI和I2S_CKIN。$ e; x5 X$ j6 a# z$ q. W; [( z
以上相关时钟源选择配置寄存器的配置也比较简单,我们就不一一举例子了,如需要,大家按照要求来配置即可。
; J- w, j/ |3 w$ B5. APB、AXI分频寄存器
# `0 w" A! T: f) ~- g3 BAPB1APB5以及AXI都有时钟分频器,可以设置时钟分频值。AXI时钟分频器可以配置分频值为1、2、3和4。APB1APB5分频器的分频值可以配置为2、4、8和16。相关寄存器的配置也比较简单,我们这里就不一一介绍了。
1 |& _, }8 {# T' f  ]3 q6. PLL控制和配置寄存器; ~: k+ u5 }0 F; r$ O( D
PLL1~PLL4有专门的控制寄存器和配置寄存器,我们这里以PLL1为例进行介绍。' A% ~8 d. X- Y4 z4 p$ h
(1)RCC PLL1控制寄存器(RCC_PLL1CR)2 t1 c: i/ s0 P5 ?2 `
* x: R7 _5 D$ i! ^7 t
0742699e9828475fad4b380e502f2de7.png
3 D. S, c  n7 ?" G! E. Q' [1 G( C5 L. q( ~5 b/ ]# D
图9.2.1.8 RCC_PLL1CR寄存器
7 _7 y$ H- y) R5 k5 `PLLON位用于使能PLL1,将该位置1表示使能PLL1,复位表示将PLL1关闭;
: y. X& b8 @1 E1 k' G, fPLL1RDY表示PLL1时钟就绪标志位,属于只读位。该位为1表示PLL1处于锁的状态,为0表示PLL1处于未锁的状态;; q9 _# {; y8 [. A: N5 I- \; V
DIVPEN表示PLL1 DIVP分频器输出使能位,通过软件置1和复位,以使能PLL1的pll1_p_ck输出。该位为0,表示pll1_p_ck输出被禁用,为1,表示pll1_p_ck输出启用。为了节省功耗,当不需要pll1_p_ck时,必须将DIVPEN和DIVP设置为’0’。
6 s0 v: p9 U2 g8 l# f4 XDIVQEN是PLL1 DIVQ分频器输出使能位,该位为0,表示pll1_q_ck输出被禁用;该位为1,表示设置pll1_q_ck输出已启用。
' U- e: ?/ p& ~1 GDIVREN表示PLL1 DIVR分频器输出使能位,该位为0,表示pll1_r_ck输出被禁用;该位为1,表示设置pll1_r_ck输出启用。' q) o- K  J; r
(2)PLL1配置寄存器(RCC_PLL1CFGR1和RCC_PLL1CFGR2)3 Z6 f2 x: m3 L2 C
PLL1配置寄存器有2个,其它PLL配置寄存器也是有2个。PLL1配置寄存器1(RCC_PLL1CFGR1)如下:
. ?6 A$ G$ A4 a6 m* A2 q1 N1 G$ j" y# g
bf2944a136fa46ac8ad778b863fad17f.png 0 H) E# \1 |- O8 y# k

. N0 X- \* }. ~  y# w图9.2.1.9 RCC_PLL1CFGR1寄存器
+ Y( R# m0 V3 R2 h! VDIVN[8:0]位用于配置PLL1 VCO的倍数,将该位配置为:
5 |6 I1 X+ G& s8 ^0x18:分频比为25;1 N  p& n7 T0 Q/ a  D
0x19:分频比为 26;
6 R* X) F" |/ m% J8 _
, g' @# b' m0 L6 w5 X0x31: 分频比为50 (default after reset);
9 V: d! V% k$ m3 m  K% G' m' r( i3 C! B9 j! d( j
0x63: 分频比为100。) |, n' q9 R6 H' Q+ ^4 G" l
PLL1配置寄存器1(RCC_PLL1CFGR2)如下:
: J) a+ z$ T; T4 G- z- x( J
2 M( n1 s7 x. A* H 3ccaafe4c3da48839d9b589ca41a2adb.png
9 h$ c6 O0 z% P9 [
5 R% R: z+ P! @* g! J/ U图9.2.1.10 RCC_PLL1CFGR2寄存器
0 J5 @2 N! c5 m6 ]" X) {DIVP[6:0]位、DIVQ[6:0]位和DIVR[6:0]位分别用于配置PLL1 DIVP、PLL1 DIVQ和PLL1 DIVR的分频系数,要配置为什么样的分频值,根据手册来配置即可。
/ @! s+ k* W/ o$ A7 m7. PLL小数寄存器
3 r5 v- M) j* `( K# _) F: ~我们前面也说过,锁相环PLL的 FRACV是分数倍频系数,它和DIVN一起组成PLL的倍频系数,当FRACV等于0的时候表示在整数模式下,大于0的时候表示在小数(或者说分数)模式下。每个PLL都有其配置小数寄存器。我们以PLL1为例子。RCC PLL1小数寄存器(RCC_PLL1FRACR)如下:& Q: X/ R) n& Z3 w1 o( C6 z& P$ O
2 @* w; n+ C# w* g
d440d6ac05174c66a848598dde6f8bbd.png 5 S; u* ?/ R) y1 j3 ~, Z
: v0 k% x5 k4 h: R4 M" q0 P
图9.2.1.11 RCC_PLL1FRACR寄存器
& w0 I4 N! H0 zFRACLE是PLL1分数使能位,该位必须先设置为0然后设置为1,这样,通过从0到“1”的转换将FRACV的内容传输到调制器中。0 v8 ]2 x  |# A6 W/ W* |: t
FRACV[12:0]是PLL1的乘法因子的小数部分。FRACV可介于0和213-1之间。9 A; [  O. {! x, T1 p9 h
9.2.2 外设时钟使能和关闭
: _* U- m: `/ ], b: K0 I9 e我们要注意,STM32MP1外设的时钟在上电复位以后默认处于关闭状态,即这些外设都不可用,这么做的目的就是为了降低功耗,如果我们要使用某个外设,必须要先打开外设的时钟才可以对外设进行操作。
3 p5 J: e. ^1 M, M; k在RCC外设相关寄存器中,我们会看到有两种寄存器,一个是以“SETR”结尾的寄存器,一个是以“CLRR”结尾的寄存器,前者是使能寄存器,后者是关闭寄存器。8 L# i: c# j. q+ }" A
例如,APB1总线挂载的设备有窗口看门狗(WWDG)、I2C1~ I2C3、USART2、UART8和TIM2~TIM7等等外设,如果要使用WWDG,就必须先开启WWDG的时钟。对于MCU,APB1外设的时钟由RCC_MC_APB1ENSETR寄存器来开启,其第28位是WWDG时钟使能位,对该位:' ~: m! v& `2 Z  i" l. |% o
写’0’表示无效操作,读取该位,读到’0’则表示该外设时钟已经被关闭;
4 U& G+ X2 D+ ]  U) B写’1’表示开启外设时钟,读取该位,读取到’1’意味着外设时钟已经开启了。3 y& u4 I* ^$ z+ C
# t, [! J0 v6 I5 p8 ?2 y+ i
c6fba1244076415d909b1f6471e41527.png / I. v3 a* X/ w: H- d% N  ~+ I. H1 T

3 D" k$ l& B  X0 P图9.2.2.1 MC_APB1ENSETR寄存器
& S4 B% E; B6 A3 `- P5 g2 }另外一个寄存器RCC_MP_APB1ENCLRR用于关闭APB1外设的时钟,我们看看此寄存器:8 [* _; |9 n- W: @1 j2 l5 r

! |0 j: R5 I7 \* u6 ]8 N0 F 6c94498bbff3468fbace7418e34faa6b.png # i2 @3 q3 h; t" E0 R+ ~& m
& t; j% R$ S: ?5 S2 X, F+ u2 g+ ~
图9.2.2.2 MP_APB1ENCLRR寄存器  Y$ P) ^; l8 N: P9 R  A$ t
可以发现,并没有看到控制WWDG的时钟位,第28位是保留的,而其它外设,例如I2C1~ I2C3、USART2、UART8等对应的位有看到,对这些位:
9 @% q6 a' Q0 U# K写’0’表示无效操作,读取到’0’表示已经关闭外设的时钟了;+ [) s0 m7 Z7 C* h
写’1’表示关闭外设时钟,读取到’1’表示已经开启外设时钟了;
; D( u- l0 @5 t8 t2 X: L" z( N为什么窗口看门狗(WWDG)在RCC_MC_APB1ENCLRR寄存器中没有对应的时钟操作位呢?因为WWDG这个外设比较特殊,只要开启窗口看门狗以后就不能将其关闭了,必须等到系统重置以后才可以禁用窗口看门狗,而其它外设就不一样了,其它外设可以通过对时钟操作位写1来将其时钟关闭。
5 n( s( a+ \: E2 h在HAL库的stm32mp1xx_hal_rcc.h头文件中有所有外设时钟开启或者关闭宏定义:
6 a$ m4 e6 L, {& A8 r
( n1 [; s6 p0 }! D' v f3442a0ad69149bab724a23b0567a31c.png , t1 b. Q6 C- z* @  i
7 \( z+ ~- x/ Z' ]
图9.2.2.3 外设时钟使能/禁用宏定义) G: A; W/ K0 u
例如,WWDG部分:
! M( D6 ]2 q  D7 b2 A1 #define __HAL_RCC_WWDG1_CLK_ENABLE() (RCC->MC_APB1ENSETR = \ RCC_MC_APB1ENSETR_WWDG1EN)- N* t# [3 T; n1 b; A1 @
2 #define __HAL_RCC_WWDG1_CLK_DISABLE() (RCC->MC_APB1ENCLRR = \ RCC_MC_APB1ENCLRR_WWDG1EN)
) }, j( u) A' Y% [RCC_MC_APB1ENSETR_WWDG1EN和RCC_MC_APB1ENCLRR_WWDG1EN在stm32mp157axx_cm4.h头文件中有定义:
2 c3 T+ D3 b4 k5 t; d; B4 ?3 {- b3 z#define RCC_MC_APB1ENSETR_WWDG1EN B(28)" r: k( e) D& r
#define RCC_MC_APB1ENCLRR_WWDG1EN B(28)" E2 H* u* a) ]. H, B, n5 E
B(28)则表示将第28位置1,所以(RCC->MC_APB1ENSETR = RCC_MC_APB1ENSETR_WWDG1EN)就表示将RCC_MC_APB1ENSETR寄存器的第28位写1,也就是开启WWDG这个外设的时钟。在代码中,要开启WWDG的时钟的话,直接调用__HAL_RCC_WWDG1_CLK_ENABLE()这个宏就可以实现,其它的所有外设的时钟开启都可以类推。
+ b( m5 X- u2 V8 V4 r' r同理,(RCC->MC_APB1ENCLRR = RCC_MC_APB1ENCLRR_WWDG1EN)表示关闭WWDG的时钟,不过因为WWDG特殊,系统运行的时候是不能将WWDG时钟关闭的,所以寄存器RCC_MC_APB1ENCLRR并没有针对WWDG的操作位,所以调用__HAL_RCC_WWDG1_CLK_DISABLE()这个宏是没什么实际意义的,不过对于其它一般的外设,例如I2C3、USART2、UART8等,调用对应的宏就可以将外设时钟关闭了。5 W, K: a2 l  z- @' M- _
又例如GPIOA~GPIOK是挂在AHB4总线上的,在HAL库的stm32mp1xx_hal_rcc.h头文件中同样有GPIO时钟开启和关闭宏定义,操作方法也和上面的外设类似。
) i, ^+ t0 d0 G8 X. z! M  `
' U: U% ~) P! }0 b# R! p# g8 W d4ede78f54cb41a29fc35fda2fb541fd.png
- W* H# d7 c- d' a) [6 j" i% r, }2 J% E) ]9 C
图9.2.2.4 外设时钟使能/禁用宏定义
1 h1 B2 t5 ^) L& g' `% G事实上,用STM32CubeMX插件生成的初始化代码中,也就是通过调用这些宏来开启外设时钟的。
, O: k6 @0 E7 Y% q9.3 时钟相关API函数0 @3 A( U7 R* ^8 i
头文件:stm32mp1xx_hal_rcc.h。3 e8 a5 h1 _" N* p- p
stm32mp1xx_hal_rcc.c是RCC相关的HAL模块驱动程序文件,主要管理复位、初始化、取消初始化和外设时钟控制的功能。
/ U% l% k4 v4 U% m: F& @* A复位后,设备从内部高速时钟(HSI 64MHz)运行,并且所有外设均关闭,但内部SRAM1,SRAM2,SRAM3和PWR除外。高速(AHB)和低速(APB)总线上没有预分频,这些总线上映射的所有外设均以HSI速度运行。
6 M  w6 T- y8 T" x$ m7 {设备复位后,如果用户需要更高的频率/性能,需要进行如下操作:
3 D  l! A. A; G9 j" ~% u- F①选择用于驱动MPU、AXI和MCU子系统的时钟源;
. V. E3 i1 c( T  G1 x②配置AHB和APB总线分频器;+ z( R! U! A; f
③对于那些不是从总线获得时钟的内核外设需要配置内核时钟源;
0 c+ |& W0 Y4 s9 W: [④启用要使用的外设的时钟。
( `' X7 @3 S& j  s2 l2 V1 N不管使用哪个时钟源,都是通过软件对相关寄存器的操作来实现的,我们直接通过HAL库的API函数来实现控制寄存器操作,下面,我们来分析一下stm32mp1xx_hal_rcc.c文件中的API函数。
  q3 R( G# N& e2 {9.3.1 函数HAL_RCC_DeInit7 Y9 O. h2 y# F+ G; T$ I( }! S
●函数功能:主要就是将RCC时钟配置为默认重置状态,主要做了以下操作:
4 P. Z# `1 o9 {1 y% ~3 y' C①将HSION位置1,将HSI用作系统时钟源;
$ I! i; F; o3 C: c# M8 c②将MCO1、MCO2HSE、PLL1、PLL2、PLL3和PLL4关闭;* A4 f: j9 G( p0 J- _8 O
③AHB,APB总线预分频器设置为1;
0 B' o/ y* L# p( h④禁止所有中断(从CSTOP中断允许唤醒除外)。
' P, ?1 F% n4 C. D4 Q●函数返回值:9 j6 ]/ @' }' ?; F9 ]5 S7 [
枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
! D& N1 {1 K' _8 O7 R●注意:此功能不会修改外设、LSI、 LSE 和 RTC时钟,不会更改HSECSS(HSE时钟安全系统HSE Clock security system)和LSECSS(LSE Clock security system)。9 i* c: t5 y; ~
函数HAL_RCC_DeInit的部分代码如下,因为代码很多,我们省略掉了部分代码。
6 t3 W( X6 x8 s' ~% |+ @, H+ y: \9 M' w8 h6 }  O- T
  1. 1   HAL_StatusTypeDef HAL_RCC_DeInit(void)7 L" N/ j( S# v+ L8 I" u5 \
  2. 2   {
    6 C3 s* I. Y5 Z* }: A6 y
  3. 3     uint32_t tickstart;1 Q% j% R. ]0 |" a
  4. 4 1 b' N( P% F) I( U
  5. 5     /* 将HSION位置1,使能HSI振荡器 */. M1 _) L# a, F. I, I7 t
  6. 6     SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSION);; C5 U( X* G. D
  7. 7
    : X! \- Z3 f6 M& p2 {
  8. 8     /* 获取全局变量uwTick当前计算值 */% p3 W6 K! F* e& n) J% b8 S5 u
  9. 9     tickstart = HAL_GetTick();0 }* W# k0 X  T+ w/ ?. n
  10. 10
    3 _& V/ z& Y0 y) [( [
  11. 11    /* 等待HSI准备就绪 */
    # ^6 |7 {8 d; k; ^! d  ]6 n2 W
  12. 12    while ((RCC->OCRDYR & RCC_OCRDYR_HSIRDY) == 0U)) X, J) F/ K/ m$ }6 i
  13. 13    {7 c: d8 R7 _1 n  V2 q1 C% l
  14. 14      if ((HAL_GetTick() - tickstart) > HSI_TIMEOUT_VALUE)
    9 S' a! N, {3 h4 B& k3 X
  15. 15      {
    " F! Z: n2 E+ t' i5 R
  16. 16        return HAL_TIMEOUT;
    7 I; Q. w# i# Q
  17. 17      }/ Q1 B. I  m0 U2 I  S
  18. 18    }) V+ Y# m, R6 l( s
  19. 19    / p/ a4 ^3 p, l, K( G0 t, i: A, r; e
  20. 20  /*
    4 V7 v  z" Z& W3 m8 z3 N5 W0 t: }2 E
  21. 21  *省略掉代码......7 D' s8 `! s& o
  22. 22  *关闭MCO1、MCO2HSE、PLL1、PLL2、PLL3和PLL4% L, s& ]+ T8 W+ G! z
  23. 23  */6 Q0 ^3 W& O, J8 t6 V( q3 x( J: C
  24. 24   
    6 Y. ?, e. n) o, a6 W1 w1 Q
  25. 25    /* 等待HSIDIV分频器就绪*/
    / A' Q* D. Q% R+ h
  26. 26    while ((RCC->OCRDYR & RCC_OCRDYR_HSIDIVRDY) == 0U), G+ y' N; ~  H) `9 J
  27. 27    {
    ' w( v5 _" s( Z8 j, a9 q6 }# b2 `2 G1 S
  28. 28      if ((HAL_GetTick() - tickstart) > HSI_TIMEOUT_VALUE)
    & q  x* ]0 U4 n' Y3 P
  29. 29      {1 }3 A: Q) z7 n/ G6 K
  30. 30        return HAL_TIMEOUT;
    ' M; K4 E! J4 b- L2 X; i% K& `
  31. 31      }
    / N% X0 b: ?' T3 K
  32. 32    }2 {7 Z( i; K* l  U$ p. e
  33. 33  /******省略掉代码******/
    , v. a* N" F  s% H$ Y
  34. 34    /* 更新SystemCoreClock全局变量 */
    . X6 N' J8 W6 G4 D/ \0 A
  35. 35    SystemCoreClock = HSI_VALUE;
    & n3 Q* L4 d- v
  36. 36
    6 Y9 i) z. B. p  P' e3 c" W
  37. 37    /* 调整Systick中断时间 */
    - ~' x9 i- ?) D: Y% b
  38. 38    if (HAL_InitTick(uwTickPrio) != HAL_OK)) Z: `! B1 g! W+ G8 \! p; S; }$ [
  39. 39    {
    ; ]1 M( |5 q- {9 X
  40. 40      return HAL_ERROR;
    ; @# Z8 v; N6 g$ @9 I5 T6 W
  41. 41    }! S) u) C0 J/ ^- S
  42. 42
    $ S( _0 d9 Y% x% b; K2 Q
  43. 43    return HAL_OK;
    - r+ S9 U8 w3 x! E9 Y) E' @
  44. 44  }
复制代码
. W+ P6 q) H6 U
我们简单分析这部分代码:
, O; G  Z2 D9 E$ u- C: x& Q! b第6行,通过SET_BIT置位操作,将RCC_OCENSETR寄存器的HSEON位置1,表示使能HSI振荡器。* F5 M  m0 m% Z) X, [& R2 Z
第9到,先获取全局变量uwTick当前计算值。' T% h0 z2 O% z9 o+ @
第12行,等待HSI是否已经就绪。
; e/ c( @5 {0 G: b0 T1 C: c& F1 J: }3 \" l$ ^4 i0 F2 s" B9 {
注:RCC_OCRDYR是振荡器时钟就绪寄存器
- E; p1 F8 ]0 b! i5 X①第0位HSIRDY是HSI时钟就绪标志,由硬件置1,以指示HSI振荡器稳定,如果此位为0,表示HSI时钟未准备好,如果此位为1,表示HSI时钟已准备好。3 ?1 I& ?2 \4 p, k. o9 @, ]
②第2位HSIDIVRDY是分频器就绪标志位,由硬件置位和复位。如果此位为0,表示新的分频比尚未传播到hsi_ck(hsi_ker_ck)(复位后默认),如果此位为1,表示hsi_ck(hsi_ker_ck)时钟频率反映了新的HSIDIV值,即分频器已经准备好。5 V- d5 G+ M: ~6 g) G
③第4位CSIRDY是CSI时钟就绪标志,由硬件置1,以指示CSI振荡器稳定。如果此位为0,表示CSI时钟未准备好(复位后的默认值),如果此位为1,表示CSI时钟已准备好。
6 D: Y3 F' F' Y- \6 z④第8位HSERDY表示HSE时钟就绪标志由硬件置1,指示HSE振荡器稳定。如果此位为0,表示HSE时钟未准备好(复位后的默认值),如果此位为1,HSE时钟已准备好。
+ p& S" l6 _. Z# ^) E7 c
! P/ w3 K% s: k# @* p* Q, q 3a5b32650887435b967fd4886a783761.png 5 w# h. ^5 N) @4 |- q+ U; X
; L% U( M- p  K% i$ y
图9.3.1. 1 RCC_OCRDYR寄存器" s  K# V1 s& _- I' U7 K' K
代码中,RCC_OCRDYR_HSIRDY值为0x00000001。如果(RCC->OCRDYR & RCC_OCRDYR_HSIRDY) == 0U,表示HSI振荡器未稳定,程序会卡在while循环中。如果两次取得的全局变量uwTick的差值大于HSI_TIMEOUT_VALUE(100ms),表示HSI超时,程序返回HAL_TIMEOUT标志并退出while循环。0 P: i; {. k  \, W
第26到第32行,表示等待HSIDIV分频器就绪。5 H; v/ n0 g8 _& T0 y) W2 A8 @+ `$ R
第35行,更新SystemCoreClock全局变量的值为HSI_VALUE。SystemCoreClock变量用于存储系统时钟频率,可以用来设置SysTick定时器或配置其他参数,每次时钟变化时,都应该更新它,这样也是为了保证SystemCoreClock的准确性。我们在第6.3.4小节system_stm32mp1xx.c文件中有分析过。
6 _4 d  @4 ~  p+ S第38到第41行,调用HAL_InitTick函数更新Systick中断时间(1ms),因为很多地方要用systick作为时基源,所以时钟源变化后,要将其更新。如果HAL_InitTick函数没有运行成功,程序将返回HAL_ERROR。HAL_InitTick函数我们在7.4.2小节的stm32mp1xx_hal.c文件中有分析过。
  u! G- M: _; i" o9.3.2 函数HAL_RCC_OscConfig
  J5 ~9 t' b, n+ b* m●函数功能:主要就是配置 HSE、HSI、LSI、LSE 和 PLL(PLL1、PLL2、PLL3和PLL4)。
, u2 J* e) `3 ^/ H" U& _●函数返回值:+ z# W# J, Z! L% b1 Z+ c( @
枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)8 ]: I4 v1 `$ ?" G9 G1 K
注意:当PLL用作系统时钟时,PLL不会被禁用。
) y6 S! z) S. w. }* O6 k1 k函数部分代码如下:0 f0 t6 Z" u. Z5 P

! O+ F- p. }9 a" C' L+ a0 R+ ~" E
  1. 1   __weak HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef \                                                                                                                                  *RCC_OscInitStruct)% j& D, B& W- k% R$ D
  2. 2   {( T% P3 H; q! C1 R! {1 X" z
  3. 3     uint32_t tickstart;
    ( _, `$ A6 C! G& J" P
  4. 4     HAL_StatusTypeDef result = HAL_OK;& B. V, g) X, K5 Z3 Z1 j
  5. 5 0 U; q, z. t1 T# X, l
  6. 6     /* 检查是否是空指针 */
    ; B% Q! {' _2 F  x
  7. 7     if (RCC_OscInitStruct == NULL)+ \% p, z6 X% a2 _/ [# v# n
  8. 8     {
    . c6 Z. Z: t) L+ B
  9. 9       return HAL_ERROR;
    $ u! T6 k0 M, X$ G0 K
  10. 10    }) @0 t& {/ V% f3 |3 F& K
  11. 11
    ' K/ K# J% U9 T0 F7 N0 v2 S7 l
  12. 12    /* 使用断言检查参数 */1 H: s3 q4 @9 z1 U5 E
  13. 13    assert_param(IS_RCC_OSCILLATORTYPE(RCC_OscInitStruct->OscillatorType));
    " _& Q7 f& ?  }- o: h
  14. 14  1 p( i  x& L- T: C4 q, c' U
  15. 15    /******省略HSE、HSI、CSI、LSI、LSE、PLL配置代码 ******/ , [2 f9 {. c% J: d2 M& U0 c
  16. 16    return HAL_OK;
    % }& q- U; I$ @3 T: B5 v$ N  V
  17. 17  }  n6 |) L8 I6 ~+ ]" f
  18. 18
复制代码

+ M! D( m5 q# A# n% Gweak表示函数是弱定义,用户可以在其它地方重新定义一个同名的函数。函数参数是RCC_OscInitTypeDef 类型结构体变量,主要对RCC内部/外部振荡器(HSE,HSI,CSI,LSE和LSI)配置的结构定义,RCC_OscInitStruct是指向RCC_OscInitTypeDef结构的指针,RCC_OscInitTypeDef 类型结构体声明如下,通过指针可以操作结构体中的成员变量。例如,如果要选择HSE为振荡器:
. _1 @, d9 G: a0 V9 Y# }8 V. [$ u①设置OscillatorType的值为RCC_OSCILLATORTYPE_HSE;
+ ^8 L2 ]7 K- m5 K3 Y+ v②然后设置HSEState的值为RCC_HSE_ON开启HSE;
. F3 a; A3 q2 r9 m③如果用到PLL,则配置对应PLL的参数。
0 _/ ^7 e& k- `( {& X. P- l对于其它的时钟源(HSI、LSI、LSE和CSI)配置方法类似。
. Q; g; R3 B6 R3 ?" W" c
2 e4 S4 B2 c" k( i1 e
  1. stm32mp1xx_hal_rcc.h文件代码
    0 c6 t& ?+ o- w. p9 v$ x
  2.     typedef struct( j& g2 f( E8 h! K
  3.     {
    8 C' V! v/ I+ q# S4 ?0 L3 n
  4.       uint32_t OscillatorType;                                 /* 要配置的振荡器 */                                                     
    - i- p% Y" A0 w8 X# A
  5.       uint32_t HSEState;                                           /* HSE的新状态 */                                                                  
    : U% [% X' X9 M' C; d. w
  6.       uint32_t LSEState;                                                 /* LSE的新状态 */                                                                       , z/ j9 a+ o; e2 O: Y
  7.       uint32_t HSIState;                                           /* HSI的新状态 */                                                                  
    1 ]8 T- u* H' L* A: @9 q' ]
  8.       uint32_t HSICalibrationValue;                         /* 校准调整值 */                                      - \" m* o  e/ A8 v* ^
  9.       uint32_t HSIDivValue;                                          /* HSI的分频系数 */                                             - O1 V: f5 C# v- A
  10.       uint32_t LSIState;                                          /* LSI的新状态 */                                                 - _; h. f' ^% R. y2 b3 U4 M
  11.       uint32_t CSIState;                                          /* CSI的新状态 */                                                 # O5 w( f5 W: \
  12.       uint32_t CSICalibrationValue;                         /* 校准调整值 */                                       7 o& m0 i, ?; q0 c
  13.       RCC_PLLInitTypeDef PLL;                                 /* PLL1结构参数: */      % L) K% G) K" e, a& \
  14.       RCC_PLLInitTypeDef PLL2;                                /* PLL2结构参数 */     
    % Q& g2 w+ u& y0 F2 r% X$ A4 o
  15.       RCC_PLLInitTypeDef PLL3;                                 /* PLL3结构参数 */     $ b( h( w- X# }4 i0 e
  16.       RCC_PLLInitTypeDef PLL4;                                 /* PLL4结构参数 */                                                  
    0 h0 a2 R9 d/ S7 E2 C% y
  17.     } RCC_OscInitTypeDef;7 G% z  L$ P8 [" G1 {
复制代码
+ t" B( `" q/ ?
我们查看PLL的RCC_PLLInitTypeDef结构参数有哪些,如下,此结构体定义了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量,通过给这些结构体成员赋值即可配置PLL的时钟。
4 n8 [8 l; q) l
! {$ S+ F% ?; ^/ G$ h: s$ f/ I
  1. stm32mp1xx_hal_rcc.h文件代码* J; Z( [! Q' H9 M2 j6 u8 V
  2.         typedef struct+ a* K) x+ c3 y
  3.         {. _7 _7 y# E" f& ^  \
  4.           uint32_t PLLState;                   /* PLL的新状态 */                                             
    2 n! Q1 u! k+ r0 W
  5.           uint32_t PLLSource;                  /* PLL输入时钟源 */                           
    3 J6 ^: G. Y" r
  6.           uint32_t PLLM;                       /* PLL VCO输入时钟的分频系数DIVM */                           
    8 f' l+ a5 B" n0 t
  7.           uint32_t PLLN;                       /* PLL VCO输出时钟的倍数DIVN */                            : b) C' D- L' a6 O$ `/ e! F. z! B2 ?
  8.           uint32_t PLLP;                       /* 分频因子DIVP */                          
    ; w5 w/ y6 T6 _+ C7 {! a
  9.           uint32_t PLLQ;                       /* 分频因子DIVQ */                           ( Z6 v* |: l6 I2 Q" _3 G
  10.           uint32_t PLLR;                       /* 分频因子DIVR */                           
    ' N9 N$ `; q) }) N4 ~( \
  11.           uint32_t PLLRGE;                     /* PLL3和PLL4的PLL输入频率范围 */                          
    7 m" a8 S& O; @0 {, H# \1 Q. [" k
  12.           uint32_t PLLFRACV;                   /* PLL1 VCO乘数的小数部分FRACV */                          # z6 j3 h2 @' j: W# N5 ^/ H; q
  13.           uint32_t PLLMODE;                           /* 使用PLL模式 */                           
      B1 y0 k7 P5 B+ u: X4 D
  14.           uint32_t MOD_PER;                           /* 调制周期调整 */                          ) h) L  Q/ @: z" c6 _2 F5 i
  15.           uint32_t RPDFN_DIS;                  /* 抖动的RPDF噪音控制 */                            / T3 n6 N! U: |0 ~# I: h
  16.           uint32_t TPDFN_DIS;                  /* 抖动的TPDF噪声控制 */                          
      y6 `' B8 m) B! W7 R
  17.           uint32_t SSCG_MODE;                  /* 扩频时钟发生器模式 */                     % {* s, Y6 Q- s. I  n1 a
  18.           uint32_t INC_STEP;                   /* 调制深度调整*/                           
      x- r6 W2 J" A! K  }9 }) S6 d8 |/ @
  19.         } RCC_PLLInitTypeDef;
复制代码
' R1 F$ k5 K' G& M1 I! ~
在HSE、HSI、CSI、LSI、LSE、PLL配置代码部分,我们主要分析HSE、HSI和PLL配置部分。
+ f% t1 r+ U/ `* J4 L/ E, G
$ S- d  V" Q# l. {( V' \1 `1 p: eHSE配置部分/ c( y: T; |0 {/ o5 i
  1. 1   if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSE) \( k9 f9 F/ H5 i+ q
  2.                                                                                                 == RCC_OSCILLATORTYPE_HSE)
    4 E: u' x4 ~; @( f; m  n5 ^2 F
  3. 2     {3 j2 v5 _% \( e: \+ e
  4. 3       /* 使用断言检查检查参数 */" A5 [$ n3 U  m; O2 E4 J+ j
  5. 4       assert_param(IS_RCC_HSE(RCC_OscInitStruct->HSEState));" j( b6 H8 t( C* X  p
  6. 5       /* 当HSE在系统中的某处使用时,将不会被禁用 */
    0 Q+ i4 G: i+ d  [7 H7 l
  7. 6       if (IS_HSE_IN_USE())
    - |! u  A$ y3 \8 f# W6 g
  8. 7       {
    # {* f0 M  |# M0 ?
  9. 8         if ((__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET) && \                                                                         (RCC_OscInitStruct->HSEState != RCC_HSE_ON))
    + c' d( N" y, x! m6 P
  10. 9         {) ?3 W$ f) ]9 P0 ]$ u3 H* c
  11. 10          return HAL_ERROR;
    & r4 r, p' k0 R3 ^8 H
  12. 11        }) k- S( g: l8 g
  13. 12      }9 ]9 O! C! U+ U) s6 Z2 H
  14. 13      else* ]& ?; O) D! @
  15. 14      {
    # {" _" I- v+ c& y: C7 M& W# [
  16. 15        /* 配置HSE振荡器 */, g+ X2 I. u& {9 b$ m/ N; O
  17. 16        result = HAL_RCC_HSEConfig(RCC_OscInitStruct->HSEState);
    . n# t$ ?8 U* }5 c( U  ?% [
  18. 17        if (result != HAL_OK)
    + D3 t" n+ p& R
  19. 18        {
    " V" z9 T1 ]6 o2 V" w' s
  20. 19          return result;  Z0 S& X4 h8 e' U
  21. 20        }
    6 W- _* d5 M  \: ~6 s# C/ o
  22. 21      }
    , S# P: c6 G/ _$ v  m
  23. 22    }
复制代码
# a( w  u6 F1 s4 V! l1 x7 y8 N
第1行,通过RCC_OscInitStruct指针判断振荡器是否选中了HSE,如果选中了HSE,将进行后续的配置操作。
: t0 r, o0 ]7 n第4行,使用断言检查HSE参数的状态设置是否正确。
" @' P* _9 O0 G) s7 X第6到第12行,判断HSE是否有在使用中,如果有在使用,则不会被禁用。        (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)用于判断RCC_OCRDYR振荡器时钟就绪寄存器中的HSERDY位是否为0,为0的话表示HSE时钟未准备好(复位后的默认值也是0)。% \4 e0 b+ {2 g% g: W
(RCC_OscInitStruct->HSEState != RCC_HSE_ON)表示HSE的状态没有被开启。
2 c% c' Q* N5 v如果HSERDY位为0,且HSE的状态没有开启,那么HSE没有被使用,程序返回HAL_ERROR。是否已经开启HSE,这取决于用户,所以程序要通过判断HSERDY位以及用户的设置状态来判断到底HSE用还是没用。% W4 t1 P3 W: w) H3 Q( N! b
第16行,调用HAL_RCC_HSEConfig函数完成HSE振荡器的初始化。$ U! Z* f' P! B- N
程序中主要实现操作3个寄存器,一个是上面提到的RCC_OCRDYR,另外两个是RCC_OCENCLRR核RCC_OCENSETR寄存器。
: U# [+ E, g( a) \" x8 \- k  l/ t1 H
3 d: e/ S! ~( {9 l0 n" n" x9 Y5 Y注:
6 ]4 S. R% q) y. T& n9 A% o( fRCC_OCENCLRR寄存器用于控制振荡器,向该寄存器写入0无效,写入1将清除相应的位。+ x* O) c) H" @0 d& e% E
第0位,HSION:写1表示清除HSION位,禁用HSI。
8 |" L; |: o4 h8 V5 v: }1 d第4位,CSION:写1表示清除CSION位,禁用CSI。
1 r3 }& Z8 a. U& I0 |! N# @第7位,DIGBYP:当连接到OSC_IN的外部时​​钟为低摆幅信号时,软件将其清零。写1表示清除DIGBYP位(模拟旁路),禁用5 I7 |* D/ |" F" N2 C8 U
第8位,HSEON:写1表示清除HSEON位,禁用HSE。
/ j& i3 [2 d/ l2 F8 N: hRCC_OCENSETR寄存器的功能和RCC_OCENCLRR寄存器的功能相反,RCC_OCENSETR寄存器表示开启对应的功能。1 d3 |+ X, E4 B' g4 D
* v, U/ k1 v/ s- S! \3 m  h
298e5ac5fe91479dabc86261e4b7c2a8.png
6 m- a+ U, [" ]3 @
+ |& g) w. B* U9 q' H图9.3.2. 1 RCC_OCENCLRR寄存器
( O1 \$ l: L* [. l. P' i# L代码中附上了详细的注释,通过注释可以知道程序的实现过程:
# V8 C/ F* K: y" g- L+ V①在配置HSE前,先关闭HSE,如果已经使用了HSE,应先切换成别的时钟源,然后再关闭HSE,最后才可以配置HSE。; y  K5 U/ B% y; Z
②程序中通过将RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)的HSEON位写入“ 1”来禁用HSE。
8 B# n9 ]- r# S' T: D③通过验证RCC_OCRDYR的HSERDY位是否设置为’0’,检查HSE是否被禁用。4 l6 i+ k% H: _2 X6 J1 b2 V
④通过将RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)的HSEBYP位写入’1’,禁用HSE旁路模式。
' r, c9 `+ u; x: @; f' n3 u& F⑤通过将RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)的HSEON位写入“ 1”来再次使能HSE
4 J" I' |$ `& U8 {; t* W⑥检查RCC_OCRDYR的HSERDY位是否设置为“ 1”,然后准备使用HSE。; d- q  ?0 Y7 E1 l0 I

# y4 O1 V1 `1 _0 _! E6 ]
  1. 1   /**/ a) b- z: z1 I5 D# c3 o
  2. 2    *@brief 根据指定的参数初始化RCC HSE振荡器。6 @8 g* [& ~- M# Y: K+ N  h; g6 t
  3. 3    * @note  在更改HSE配置之前,请注意不要将HSE振荡器用作时钟,也就是要先关闭HSE,
    % o& ~4 y, L- m  T0 x) l. L& H3 n# t
  4. 4    *         如果使用了HSE,则必须选择另一个源时钟,然后更改HSE状态(例如:禁用它)。       5 y5 Q; ^) d# A7 `+ c; @3 T2 s
  5. 5    *@note   进入STOP和STANDBY模式时,HSE由硬件停止。
    8 j. ^0 E. Q* h5 K, B8 C; ^* D
  6. 6    *@note   此功能会重置CSSON位,因此,如果先前启用了时钟安全系统(CSS),0 c1 b- u! _/ Q% j
  7. 7              则必须在调用此功能后再次启用CCS。
    7 e% E0 a/ u$ E- S4 ?& c3 g
  8. 8    *@param  状态包含RCC HSE振荡器的配置。) z  x8 M! W1 O- A2 v/ W" X$ K
  9. 9    *         此参数可以是下列值之一:- Q# M3 j* b3 ^2 p, x
  10. 10   *         @arg RCC_HSE_OFF:- m& ^0 z* o6 W7 T: k: d( i$ N
  11. 11   *         关闭HSE振荡器
    * s2 K$ S3 W" I4 J! g. }
  12. 12   *         @arg RCC_HSE_ON: 9 n" y4 w3 H3 m' L
  13. 13   *         打开HSE振荡器
    0 L4 D7 Z9 H/ H1 `2 V+ K3 T2 @8 Q8 c
  14. 14   *         @arg RCC_HSE_BYPASS:8 k# P$ E2 k% A; p# b: ^2 y2 p
  15. 15   *         使用提供给OSC_IN的低摆幅模拟信号(旁路时钟),$ h* I, ]. G2 w) W6 w
  16. 16             HSE振荡器被外部时钟旁路
    # H5 ~8 o, O6 ?3 p5 `
  17. 17   *         @arg RCC_HSE_BYPASS_DIG:
    - x% B8 O4 }, `/ [$ p, U
  18. 18   *         使用提供给OSC_IN的全摆幅数字信号(数字旁路时钟),
    9 C; t9 K) E, P) l/ e5 O( _
  19. 19             HSE振荡器被外部时钟旁路
    8 X+ K9 q3 @; r+ W) {
  20. 20   * @retval HAL status2 {6 @' ~4 Z" t& K) q: ]/ v5 y
  21. 21   */
    9 i+ h/ y$ N4 b) C* `5 p6 h- ~
  22. 22  HAL_StatusTypeDef HAL_RCC_HSEConfig(uint32_t State)1 p; ~/ F( @$ i
  23. 23  {
    ( m2 O1 R1 s, J3 y/ g7 f
  24. 24    uint32_t tickstart;" e( P7 L# B$ H$ h! |) _
  25. 25    4 L4 x. V4 D* M3 m. ^. H
  26. 26    /* 检查参数 */; O7 X) r/ ~+ W0 w& ]
  27. 27    assert_param(IS_RCC_HSE(State));: m0 c8 O# n& \  P2 {
  28. 28
      ]6 U- D( m% {  j: g) ]% D3 A: E! ~
  29. 29    /* 在配置HSE之前禁用HSEON */& ~% Z0 ]5 D" S% j* j
  30. 30    WRITE_REG(RCC->OCENCLRR, RCC_OCENCLRR_HSEON);
    # p. K5 E4 i% J5 K* I  r5 k! [8 l
  31. 31 $ w$ t4 R: S  l
  32. 32    /* 获取全局变量uwTick当前计算值 */
    " C) X) d( M9 ?7 J& V2 U: ~# N
  33. 33    tickstart = HAL_GetTick();
    . x% ^5 {+ I, u' J; S
  34. 34 2 L. L" i# u( b5 T4 G7 q. e
  35. 35    /* 等待直到禁用HSE  */
    ( z& A6 F" F0 p6 {( r: g% t! v
  36. 36    while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) != RESET)# M$ D6 K4 o! W& m8 N3 \
  37. 37    {: X0 \4 @' [, f* {% Y/ x2 R
  38. 38      if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
    0 m7 k1 D, Q; d$ y4 X2 U6 i
  39. 39      {. |& e: S3 T% d  @! H- u0 a
  40. 40        return HAL_TIMEOUT;# Y& r; o$ s( i9 h/ n
  41. 41      }
    " _. E# ?' i) m: B' V
  42. 42    }) m8 }2 }  _7 _( _: ?: |  }
  43. 43
    1 f5 P% D: U4 D* p
  44. 44    /* 清除剩余的位 */0 o! I0 I) S4 b0 a  A
  45. 45    WRITE_REG(RCC->OCENCLRR, (RCC_OCENCLRR_DIGBYP | \         RCC_OCENSETR_HSEBYP));
    , y% j  }! ?6 t
  46. 46
      V/ @+ M5 q/ c
  47. 47    /* 如果需要,启用HSE */
    5 R$ K' h+ I! [. x' M) F2 `
  48. 48    if (State != RCC_HSE_OFF)6 @0 Y: C6 D+ u8 C
  49. 49    {
    : @  k3 h! r0 t" L) x) U1 {
  50. 50      /* 如果设置的是旁路时钟,则启用旁路模式 */5 t. s- A) X  h5 S5 x! u4 Y
  51. 51      if (State == RCC_HSE_BYPASS)
    % s3 ]) z, F$ P
  52. 52      {+ r9 I/ e4 D9 P4 L7 K
  53. 53        SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEBYP);9 J) L. f& [2 f
  54. 54      }, f) B! C, w' P+ K( O" S( ^
  55. 55      /* 如果设置的是数字旁路时钟,则启用数字旁路模式 */
    ( E. H: y  _0 ~/ \9 Z9 ^* J
  56. 56      else if (State == RCC_HSE_BYPASS_DIG)4 S% Z( |: m0 g0 R' }: X4 @
  57. 57      {0 G: t& D8 {+ j& E" Q4 r
  58. 58        SET_BIT(RCC->OCENSETR, RCC_OCENCLRR_DIGBYP);
    # C. B% S! p0 L/ B7 z0 e
  59. 59        SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEBYP);
    : B5 R8 Q0 b$ C- m7 Z
  60. 60      }
    9 R; c7 N- D' P: S* G
  61. 61
    - {- E! r1 L7 G
  62. 62      /* 启用HSE */
    7 {0 ]' a/ ~+ U: V7 b! H. q
  63. 63      SET_BIT(RCC->OCENSETR, RCC_OCENSETR_HSEON);
    5 @& ]' ^* T5 X" E* u8 H( W
  64. 64
    , h7 e: V$ P4 h& H+ R
  65. 65      /* 获取全局变量uwTick当前计算值 */9 u: N, i/ C1 `" g7 h" o
  66. 66      tickstart = HAL_GetTick();7 Z, ]1 D( D. ^& K2 M6 _
  67. 67
    $ W. ]) Y6 z6 s/ ~  c) y1 [
  68. 68      /* 等待HSE准备就绪  */: n: C0 F0 A  k) k
  69. 69      while (__HAL_RCC_GET_FLAG(RCC_FLAG_HSERDY) == RESET), _) V. K, H! T
  70. 70      {
    8 `: u5 h+ u$ ^9 s1 d
  71. 71        if ((HAL_GetTick() - tickstart) > HSE_TIMEOUT_VALUE)
    " \- L. g. s! n  r
  72. 72        {" j  E8 _5 G$ y/ g, w3 k
  73. 73          return HAL_TIMEOUT;" M; H' [; C/ R3 d& p
  74. 74        }. e# q' v5 R: p% c
  75. 75      }6 Q/ ^; w3 o1 B: j
  76. 76    }
      i' h, u* Q$ u4 L' _5 o, `0 Q6 D4 k! Y& c
  77. 77
    : L' h3 s- V! l/ h7 i' d9 x& L
  78. 78    return HAL_OK;
    + ]  W2 S- N0 k6 p3 Z' O" a
  79. 79  }
    % v# `/ V! t' F0 e
复制代码

$ t+ W2 A% m9 R" K% |( NHSI配置部分
' C3 u! `; V6 V- o7 b关于HSI的配置部分,有以下两点:
/ u, k8 k, g5 `①RCC_OCRDYR寄存器的HSIRDY标志指示HSI是否稳定。 启动时,在硬件将HSIRDY位置1之前,不会释放HSI输出时钟。应用程序还可以通过位于RCC振荡器时钟使能设置寄存器(RCC_OCENSETR)和RCC振荡器时钟使能清除寄存器(RCC_OCENCLRR)中的HSION位来控制HSI激活。2 j( [/ p$ r7 @$ ^+ c0 L
②HSI的预分频器由位于HSI配置寄存器RCC_HSICFGR中的HSIDIV位控制。RCC振荡器时钟就绪寄存器RCC_OCRDYR可以使用标志HSIDIVRDY位来检查硬件何时考虑新的分频比。HSIDIV值可以即时更改,并且HSI将考虑新的分频比,但是,必须考虑:+ B6 P* S8 c- `
如果HSI当前用作PLL的参考时钟,则不允许更改HSIDIV。5 y4 P0 A/ w0 m- _
如果当前将HSI时钟用作某些外围设备的内核时钟,则应用程序必须确保HSI频率变化不会干扰外围设备。% ]7 u* X+ G1 W  s: B
HSI配置部分的代码框架如下,代码比较多,这里省略了代码。
6 j* g1 ~4 m& g. i' d- K
  1. 1   if (((RCC_OscInitStruct->OscillatorType) & RCC_OSCILLATORTYPE_HSI) \4 f6 r3 ~6 ^1 s6 [+ x& p3 _) r7 z
  2.                                                                                                  == RCC_OSCILLATORTYPE_HSI)
    - S; Y) A" F  `" U3 I
  3. 2   {
    3 W+ ~0 Y6 T. ^) k; H6 d
  4. 3      /* 使用断言检查检查参数是否符合范围*/" n: u% z+ l6 u( g" N2 v- {
  5. 4      /****** 省略部分代码 ******/1 W/ c% k/ \, V; s# f7 c
  6. 5   /* ; o: N& [& g$ J- y5 ^
  7. 6    * 当HSI被使用时(在AXI、MPU、MCU和PLL1~pLL4使用),它不会被禁用,允许校准。9 u- J! _1 ~- D, Y' `% V7 k6 c
  8. 7    * 1、如果HSI当前用作PLL的参考时钟,则不允许更改HSIDIV的置。
    1 V1 @: h9 m+ S7 K
  9. 8    * 2、如果HSI没有被当做PLL的参考时钟,则更新HSIDIV值,
    7 ^* i% ]( T. S+ M' o8 C
  10. 9    * 同时,更新SystemCoreClock全局变量。2 c5 |5 F7 U" A* P8 n
  11. 10   */
    / c) {# D, s5 N: g1 E8 l2 M
  12. 11     if (IS_HSI_IN_USE())
    * Q) ~+ O# j* `) H
  13. 12      {
    . X: q/ W$ H6 I) b' g
  14. 13        /****** 省略部分代码 ******/7 k. y8 N  R' z4 u) E
  15. 14      }, }9 N4 u5 T3 m( P5 G( S0 f) L3 f# A
  16. 15  /* . ~/ R# m( x: L" _, K9 |
  17. 16   * 如果HSI没有在AXI、MPU、MCU和PLL1~pLL4使用:
    0 n; O( i/ a. N! O
  18. 17   * 1、确定HSI没有被使用,如果检查到HSIState为开启状态(一般是用户开启),先启
    / Z1 m+ W/ N2 v/ d
  19. 18   * 用HSI,等待HSI准备就绪后,更新HSIDIV值,然后整内部高速振荡器(HSI)校准值。
    2 v5 `; i1 B4 ^6 P2 h" p  T" h/ q+ v8 j
  20. 19   * - r3 x8 a% e6 [3 w
  21. 20   * 2、如果如果检查到HSIState为关闭状态(一般是用户关闭),则禁用HSI。
    0 R; v; ]! W7 D: [7 V# \/ w- `
  22. 21   */8 ]* @$ v4 ?# [) I
  23. 22      else3 y# a- Q8 ]$ f# v
  24. 23      {
    5 ]) l; o/ [( o1 Q3 b6 u. d
  25. 24        /****** 省略部分代码 ******/1 e: n3 k9 R. n2 K5 r- a
  26. 25      }
    5 N0 h( b# L. b$ K+ X  n9 ~
  27. 26  }
复制代码

2 M, t% V5 t8 f% H% kPLL配置部分& w. c: K. N' s8 M/ @- o$ T9 d3 o
在启用PLL之前,必须先完成以下PLL配置:. }% U, G* Z  P6 A
选择PLL时钟输入(HSI或CSI或HSE)# ]+ E' e. R3 |) Y) c; ^5 g
PLL时钟频率输入范围(配置DIVM参数)2 f. I0 ^9 b. @  L2 r5 T
一旦启用了PLL,DIVMx,DIVNx,DIVPx,DIVQx和DIVRx这些参数就无法更改如果用户想要更改PLL参数,则他必须禁用相关的PLL(PLLxON = 0),并等待PLLxRDY标志为0才可以(这里的x等于1~4)。# x! s4 o3 b! e0 l6 H
配置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等参数。' d- d0 @) e1 U2 R+ X$ |( e: J
  1. 1   /* 配置PLL1 */* j7 X2 O2 u6 Q* O
  2. 2   result = RCC_PLL1_Config(&(RCC_OscInitStruct->PLL));' v- g) c3 Q: C9 `! e/ ?9 {" u) F, R
  3. 3   if (result != HAL_OK)
    ! o3 g; y! _& v9 o
  4. 4   {
    * h+ k; F; w1 R4 S% n+ x$ K
  5. 5   return result;
    ' o8 I9 i' k- J1 {0 |1 _: S* W4 T' Z
  6. 6   }
    0 E: J# |1 [0 N7 w# [5 F
  7. 7 % ^0 V4 _3 P& Z1 u
  8. 8   /* 配置PLL2 */& u3 ~- m, n$ }% l, N0 J
  9. 9   result = RCCEx_PLL2_Config(&(RCC_OscInitStruct->PLL2));1 M& F0 G0 v/ N4 H/ |$ L* H9 t1 m
  10. 10  if (result != HAL_OK)
    % R. |# l9 k4 s, x; Y! g, e
  11. 11  {) Y6 q9 g' q, a- E& Q
  12. 12  return result;7 [7 t( m$ f3 L. q: h/ h7 g+ b
  13. 13  }
    2 I3 f+ K5 C/ d/ Q" r  K
  14. 14/ N& @3 G. p- [2 v
  15. 15  /* 配置PLL3 */
    ! A' C5 M0 Y6 `; p" y
  16. 16  result = RCCEx_PLL3_Config(&(RCC_OscInitStruct->PLL3));
    $ k8 g! ]1 p- z; ?  s
  17. 17  if (result != HAL_OK)( O$ X( u2 l4 C2 x
  18. 18  {
    3 L! [* X) \8 w% K, u
  19. 19  return result;
    8 p5 V- y% e& r& g8 L. C7 B/ f
  20. 20  }# J* b. W6 M3 ]% G/ b8 J
  21. 21
    - k+ t' s2 e  K( w, s6 P
  22. 22  /* 配置PLL4 */  q" {+ m5 w3 K$ v1 D; y( I
  23. 23  result = RCCEx_PLL4_Config(&(RCC_OscInitStruct->PLL4));. O3 a& Z, N* l
  24. 24  if (result != HAL_OK)
    ) O/ m' Q! r# k/ {) H, ^
  25. 25  {
    - G+ w+ {4 ]( ]* H; C" o
  26. 26  return result;
    ( R$ I" z  e+ c% c# W
  27. 27  }
复制代码

6 R& ?" j- X; j0 S1 h6 K1 L* G9.3.3 函数HAL_RCC_ClockConfig
% Q! x% x' g1 c. E- N8 j/ |: h●函数功能:根据RCC_ClkInitStruct中指定的参数初始化MPU,AXI,AHB和APB总线时钟。# V4 G5 v8 M6 C  ]! W! z7 C" P. X
●函数返回值:' F( Y' m& x4 q8 c8 k& N0 F1 l
枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
( k/ [' g3 ~5 Q8 |" a  Y注意:; ], L. W% S( x. ~5 \
①系统从上电复位、停机或者待机唤醒以后,或者在HSE发生故障的情况下,则直接或间接将HSI直接或间接用作系统时钟。
, }+ b. S4 E3 o3 e5 L1 }( ^/ M②仅当目标时钟源准备就绪(启动延迟或PLL锁定后时钟稳定)时,才会从一个时钟源切换到另一时钟源。如果选择了尚未准备好的时钟源,则当时钟源准备就绪时将进行切换。
6 X# B1 z! X) ^③根据设备电压范围,软件必须正确设置HPRE [3:0]位以确保HCLK不超过最大允许频率。
( i6 C7 u( N! }! P. S  j) C
& G  y6 ^3 C- x! b
  1. 1   HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef \         *RCC_ClkInitStruct)
    1 }! I9 Z- F! P9 S2 b) Z
  2. 2   {
    % A0 E( |/ s% W! |
  3. 3
    7 P  s/ J& L8 u; D0 K
  4. 4     HAL_StatusTypeDef status = HAL_OK;
    0 U8 o0 L* o  k* ]3 }; d
  5. 5     uint32_t tickstart;
    ) b8 ]% w. p- X8 \
  6. 6 - F$ C. U/ e/ J; g2 n6 H9 w
  7. 7     /* 检查是否是检查空指针 */4 `+ e8 N+ k6 q: A, Y% X
  8. 8     if (RCC_ClkInitStruct == NULL)4 j8 G' l& h9 [$ a: S, g
  9. 9     {, z( p; V  ^- W0 R
  10. 10      return HAL_ERROR;
    7 k2 @9 L8 a) N. M1 F+ H7 }- a- {( c7 h
  11. 11    }1 B& ?. M* e; @2 }4 [, ]
  12. 12
    % v; W9 ~# F( `  [/ a
  13. 13    assert_param(IS_RCC_CLOCKTYPETYPE(RCC_ClkInitStruct->ClockType));
    # ^( O4 w: ]/ \* X
  14. 14    /******省略 MPU、AXISS模块配置代码******/
    ) O8 E9 t; u9 x8 I5 w! e
  15. 15    /******省略 APB1~APB5总线的分频器APB1DIV~APBDIV5的代码******/  G, v' e* c6 ~& x& F$ l) h
  16. 16    / V9 D; _, M+ L9 l
  17. 17    return HAL_OK;
    9 U# \9 p" e3 J% T2 i5 h
  18. 18  }
复制代码

/ ?, b  l, t4 k/ }) Y& U# R! F0 B参数RCC_ClkInitStruct是指向RCC_ClkInitTypeDef结构的指针,我们查看此结构体。如下,此结构体主要用于设置时钟源以及MPU、AXI子系统、MCU子系统和APB1~APB5的分频系数。7 c* w- e, s- n8 `- a: Z

* \% O! G( k+ z7 X  ~; @stm32mp1xx_hal_rcc.h文件代码
- N/ I1 b# p+ O6 R% y. ]
4 L4 w1 m2 @' H# {. n* D, {- Q
  1. typedef struct( y. H+ G4 s7 N# R0 {& S1 p0 P
  2. {, B  ~( [7 b% U3 K6 B, k  A( f
  3. uint32_t ClockType; /* 时钟源选择 /3 Z+ l" U2 j+ H
  4. RCC_MPUInitTypeDef MPUInit; / MPU结构参数(时钟源和分频数) /
    7 U( ^& q7 E8 C) k9 i8 {! D
  5. RCC_AXISSInitTypeDef AXISSInit; / AXI结构参数(时钟源和分频数)/
    ! l$ i" I% _- \- Q# i7 ?
  6. RCC_MCUInitTypeDef MCUInit; / APB4分频数 /( S8 D+ R0 B- D9 l
  7. uint32_t APB4_Div; / APB4分频数 /
    : P8 L* ?, {) t, q" p: e
  8. uint32_t APB5_Div; / APB5分频数 /, w, k- ?; H7 p5 y; x0 k* a' c% h
  9. uint32_t APB1_Div; / APB1分频数 /
    # e+ n4 ^' ]7 H  @+ N. U8 m
  10. uint32_t APB2_Div; / APB2分频数 /
    . ~3 `$ l5 f: b) r( p" R3 k5 I; r
  11. uint32_t APB3_Div; / APB3分频数 */
    + B) A+ K' N6 a3 o$ D
  12. } RCC_ClkInitTypeDef;
复制代码
9 Q" K3 S6 F: x
9.3.4 函数HAL_RCC_GetSystemCoreClockFreq; i2 W  @0 ^& K# t3 C+ D
●函数功能:根据所选的时钟源以及预定义的常量来返回系统核心频率。
$ }5 A$ a& p; \( ?' h●函数返回值:
) d- W9 s# P1 P/ [' }' J' vMCU或者MPU时钟频率。6 N  d& n9 I! Y, f
注意:
' {, \4 x7 {& _①我们说的系统时钟频率,其实就是MCU或者MPU的时钟频率。% c- \) o* `6 @/ Q
②每次MCU/MPU更改时,必须调用此函数以更新正确的值,否则,基于此功能的任何配置都将不正确。我们前面说的SystemCoreClock全局变量就是通过此函数来获取最新的系统时钟频率的:
. V3 y, ]# i' T  F3 O; n0 TSystemCoreClock = HAL_RCC_GetSystemCoreClockFreq();6 Z5 O: T) }( T  F
我们后面的实验会通过HAL_RCC_GetSystemCoreClockFreq函数来获取系统时钟频率。,函数如下。函数中,通过宏定义CORE_CA7来选择MPU或者MCU的时钟,其中HAL_RCC_GetMPUSSFreq函数返回MPU的时钟,HAL_RCC_GetMCUFreq函数返回MCU的时钟。2 u0 k5 C% S) \# N' Y. m0 Q3 o
  w6 K& F* I, P1 |% [
  1. 1     uint32_t HAL_RCC_GetSystemCoreClockFreq(void)9 e! P# z- E# p
  2. 2     {
    " ~5 O7 b, Z8 \0 U7 ^' C3 Z" U
  3. 3     #ifdef CORE_CA7, k; Y& t: {+ H7 C% ^
  4. 4       return HAL_RCC_GetMPUSSFreq();
    : Y& a3 H& _  ?" U+ W# h; t
  5. 5     #else /* CORE_CM4 */
    9 a7 P- v5 }* v5 _
  6. 6       return HAL_RCC_GetMCUFreq();; \% x# ^+ C' q' Y) Q- N8 n# D! A
  7. 7     #endif. t  v* a3 n9 ~9 J
  8. 8     }
复制代码
3 ^2 _. y, e, @* |. a/ H  P
9.3.5 HAL_RCCEx_PeriphCLKConfig. G  _. c( p; c0 ~5 f
●函数功能:根据RCC_PeriphCLKInitTypeDef中的指定参数初始化RCC外设时钟。" D& m. E5 c" }- O2 B
●函数返回值:4 W8 j& l+ x7 q+ `' b( r
枚举型,HAL_OK(成功)、HAL_ERROR(错误)、HAL_BUSY(忙碌)、HAL_TIMEOUT(超时)
! c& Y: z% [3 z可以通过此函数可以指定配置哪个外设以及外设的时钟源是哪个,后面的外设实验中,我们会接触此函数。该函数声明如下:
, s2 l% P( @# U
# m) \. P4 k. r" [6 r
  1. HAL_StatusTypeDef HAL_RCCEx_PeriphCLKConfig(RCC_PeriphCLKInitTypeDef
    & d! ]* R1 V& f& b, B# ]! Z
  2.                                             *PeriphClkInit), s' C" t% D) p1 h* H
  3.         PeriphClkInit是指向包含字段PeriphClockSelection的RCC_PeriphCLKInitTypeDef结构的指针,该字段可以指定是哪个外设。例如,RCC_PERIPHCLK_UART24指定USART2和UART4,RCC_PERIPHCLK_I2C12指定I2C1和I2C2。
    8 Q+ ^6 F. b* V1 O/ H8 g
  4. typedef struct. b5 `4 H' M* g
  5. {& G$ C: s$ g6 z
  6.   uint64_t PeriphClockSelection;   /* 外设时钟源选择 */
    4 q( c: A& T: `$ a
  7.   RCC_PLLInitTypeDef PLL2;          /* PLL2结构参数 */      
    0 H- q7 x& Y0 w3 _7 X$ b. j6 o
  8.   RCC_PLLInitTypeDef PLL3;          /* PLL3结构参数 */- L$ l: p- a3 `0 s1 j" G0 _# ^! {
  9.   RCC_PLLInitTypeDef PLL4;          /* PLL4结构参数 */: W  M) d8 _- \- e( Y" J
  10.   /* 指定I2C1/2时钟源,该参数可以是RCC_PERIPHCLK_I2C12 */
    9 X3 e( i( {: d# I" h9 O1 Y- r3 _
  11.   uint32_t I2c12ClockSelection;    & ~: K+ [- q5 ]( q9 y+ u
  12. /********* 省略部分代码 *********// K) o" q- P2 w' d; N
  13. /* 指定UART2/4时钟源,该参数可以是RCCEx_UART24_Clock_Source */8 Z* {7 r: H) e/ U8 s
  14.   uint32_t Uart24ClockSelection;  
    ) a4 ?$ I; [! d; i; ?6 x8 M
  15. /* 指定UART3/5时钟源,该参数可以是RCCEx_UART35_Clock_Source */   , E; `0 {! E4 ?1 _6 q3 w; Y
  16.   uint32_t Uart35ClockSelection;  / d2 J: X* C! N. F5 `! A
  17. /* 指定UART6时钟源,该参数可以是RCCEx_USART6_Clock_Source */   8 l0 N5 \# g0 f( q
  18.   uint32_t Usart6ClockSelection;
    7 U" ~" Q% u: v" U) t3 E
  19. /* 指定UART7/8时钟源,该参数可以是RCCEx_UART78_Clock_Source */     2 C; i, S( z2 R: |9 @5 o
  20.   uint32_t Uart78ClockSelection;   1 R+ J- v( i  {* X8 S" V
  21. /* 指定RNG1时钟源,该参数可以是RCCEx_RNG1_Clock_Source */     
    ; V' S, w, a" y- e5 {
  22.   uint32_t Rng1ClockSelection;
    8 y5 A$ \  }, M# h' ~
  23. /* 指定RTC时钟源,该参数可以是RCC_RTC_Clock_Source  */      
    ' S2 q. }1 N! ~  g9 m
  24.   uint32_t RTCClockSelection;      : v) M2 D# c( K9 m% h, @
  25. /********* 省略部分代码 *********/! u0 l& s" G0 A8 A
  26. /* 指定TIM2时钟源,该参数可以是RCCEx_TIMG2_Prescaler_Selection */
    . o; {  a9 I# A4 W0 o& x) t2 ?9 s
  27.   uint32_t TIMG2PresSelection;        : `2 y# g8 q8 R' g
  28. } RCC_PeriphCLKInitTypeDef;4 [( F$ n! Z" u
  29.         RCC_PLLInitTypeDef结构体我们前面已经分析过,其声明了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量。+ ]$ M$ }4 N& f, W) `
复制代码
0 R6 f! W7 a; P; f
9.3.6 其它函数
  Z) I' ]7 S, _" _- r* o0 F! m; ]6 s
2755498c82314b3b84d2e6b8cf4d8678.png
& u6 w/ D$ {/ \/ ]- d, S# o# M+ g" ~- E8 P- D% o
表9.3.5. 1其它API函数3 _+ P, D5 j/ ?
9.4 配置系统时钟实验. J7 U/ ?  [8 e1 S( B  D6 i) I
根据前面的分析以及ST官方给的总线框图,MCU时钟最大是209MHz,MCU有一个内核外设Systick,频率最大也是209MHz。下面我们分别使用HSE、HSI时钟源,通过配置时钟树来实现设置MCU最大的频率值,并通过程序读取MCU的时钟频率值和验证Systick的时钟频率。, g. ^* ]# G& W1 \, Z
9.4.1 使用HSE
: j9 v: {8 \+ @/ }! I7 M本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\2-1 MCU_HSE。
  G0 w1 Z& n/ U# k. W8 \; d% R; k5 w6 S+ l8 z
硬件原理设计" T4 u( J5 e8 X% B8 `9 N9 c
正点原子STM32MP157开发板的核心板上外接了24MHz有源晶振,所以HSE为24MHz。
$ \+ m! F4 Z3 l5 |2 A0 n- L( K( e8 F; n, b
0b041f7f9eba4caa822a62ac1581330e.png
) P' k- Q* x4 Q2 Y6 L( o- e( \5 t! ~" v9 |
图9.4.1. 1 晶振原理图截图
1 m( w( x2 {) V7 l# U9 v$ h1 @* \# F2. 软件配置5 j3 D& p4 M) |( U, l
我们新建一个工程MCU_HSE,然后在STM32CubeMX插件上开启HSE,注意,要用外部时钟源HSE和LSE的话,一定要先将他们开启。如下图所示,会有4个选项:1 j3 f( @, W8 N% S" \0 G
①Disable 关闭外部时钟
' ]$ g" j$ P" y& w  Y  K②BYPASS Clock Source 旁路时钟$ L  _, @) R: h0 [1 b7 _
③DIGBYPASS Clock Source 数字时钟(数字信号提供时钟)
" {. @, g" x+ y' i/ f④Crystal/Ceramic Resonator 晶体/陶瓷谐振器
! M) X% q/ G5 t& I3 e7 X, b4 r2 S, B; H7 Y+ ~( y; H* [
a370c1a30632435da41d37a4e806a6e4.png ' ]: e: ~$ }1 P, B( m  I
9 w7 o) Z- R$ g
图9.4.1. 2 HSE时钟模式选择2 m8 |) Z2 _; z+ l+ L7 G/ V5 L' [
旁路时钟是指使用外部晶振时,不需要芯片内部时钟驱动组件,直接从外部导入时钟信号,就好比芯片内部的驱动组件被旁路了,这种模式的话,一般接有源晶振。6 `: S7 u5 }+ {; J0 `( [  J
晶体/陶瓷谐振器选项是指,使用外部晶振时,时钟源是由外部晶振体与MCU内部时钟驱动电路共同配合形成,有一定的启动时间,精度较高,可以接无源晶振或者有源晶振。
& j1 z; f; x( o; e5 e$ q: J无源晶振自身是无法起振的,如果使用的是无源晶振的话,需要芯片上电跑起来以后,芯片内部的供电电路给无源晶振供电以后才可以起振。如果配置成BYPASS Clock Source模式的话,无源晶振就没法工作了,所以必须配置成Crystal/Ceramic Resonator模式。
6 I6 D# o( m) W% s# B1 \如果使用有源晶振,只要外部电路一上电,有源晶振就起振了,所以可以选BYPASS Clock Source或者Crystal/Ceramic Resonator模式都是可以的。5 V. |. F+ f* J7 [
如下图,将HSE配置成旁路时钟和晶体/陶瓷谐振器的差别,旁路时钟是单向信号,晶体/陶瓷谐振器是双向信号。
1 X% V" `/ r3 w3 x! {$ y- j. w$ j8 X! r
d8f4021f188641079f12df2319ed2a72.png
% P' d2 c" i" x
* N9 B# }9 E; H% Y" p- E: M7 z$ {图9.4.1. 3 旁路时钟/ p9 N6 Q; y% I

, A4 h( C' X/ v4 _" W% @% [. ` 1306098e9da44062bd534c7b1cccedcf.png ) d# q  u# n- D3 n, m' Z
# L% n$ ^0 Q+ G  ^
图9.4.1. 4晶体/陶瓷谐振器/ W2 \, M. h& i' w' [0 Y% l
我们这里就选Crystal/Ceramic Resonator模式(选BYPASS Clock Source模式也可以):
5 w5 `5 y, t: p1 v' P
, U6 R- R( g& _( b 995f0411897b4e8e8f9410e1c4917580.png
" [9 K; d) v8 I2 X
3 R5 L2 O3 Q  K图9.4.1. 5选Crystal/Ceramic Resonator模式
) d* O$ w5 L2 H在Clock Configuration处选择HSE时钟作为时钟源。由24MHz倍频到209MHz的话,需要通过PLL3配置分频系数和倍频系数来实现。具体配置步骤为:
# u# ^) Y! d3 `# T/ W# Z①选择HSE作为PLL3的时钟源;' j" O: A$ y% W$ c2 T! G" }5 z
②选择PLL3P(也就是pll3_p_ck)作为mcuss_ck的输入时钟源;& V4 _' U/ t  i4 e
③手动输入209并按下回车,最大也只能输入209了,如果输入大于209并回车以后,系统系会频率超出范围。
0 y. Y& s/ F. S④输入209以后,STM32CubeMX会动态计算PLL3的倍频和分频系数,其中:
4 u2 Z, ?7 H" m1 EDIVM3=2,DIVN3=52,fracv3=2048,DIVP3=3' {9 ?$ F" K% S- c8 v7 Q  m+ d+ Y% S
我们将上面的参数代入前面的计算公式中,计算pll3_p_ck结果也刚好是209MHz。
9 e; ^4 O* |, H, t; m
" z, q6 l5 s5 s* V0 Y/ S# L( ~  M 13fed619c4c6406baea5ec00cc3eb2df.png 6 W9 f+ W! F( O8 A' k; G/ R
+ L1 S, U. v+ M2 N
图9.4.1. 6 手动输入参数
. @5 I0 O, z1 y2 @- Y6 V! h⑤STM32CubeMX动态计算MCU、AHB1~ AHB4以及APB1~ APB3桥接器和外设时钟。我们看到PCLK1~PCLK3后面显示红色,红色表示数值超出范围,并且窗口有一个叉提示配置异常:# z/ i/ T1 c; ~6 S

2 d. b- D" b* t* J a43d4174b332462dabe4a5277cbb0ca2.png
5 L  W0 H4 n, v7 r1 e& K. D) F& P; J8 u: [7 Y
图9.4.1. 7配置异常提示
$ }; u* N5 a3 {! O5 i$ \1 ^6 i根据前面9.1.2小节有提到,pclk1、pclk2、pclk3最大为104.5 MHz,所以我们要修改这部分的参数。APB1DIV~APB3DIV参数可选1/2/4/8/16,我们随便选其中一个,下图选参数2,刚好满足最大频率为104.5 MHz:. K& j7 E! k% S9 H/ N: E/ R
2 b6 A8 k1 r. @! D' h
808fe149b9c64fc099aa0a860cb3f0be.png   f2 ~7 Y) _' ?

- f; T+ f5 ~/ E+ h图9.4.1. 8修改APB1DIV~APB3DIV  D/ `! D$ u4 M1 h6 }0 o& _+ T
查看上述配置,MCU和Systick的时钟频率均为209MHz,是否真的是这样呢?我们后面会通过HAL库函数读取它们的值。
$ r, [& A& y2 H- ^# T3 Q6 e8 b按下“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小节说过。所以这里就不选了:; q6 G' d" p, m' j8 X
" i8 r5 z9 j% m8 U7 u
623a078b40dc4545a6ee74a2af5f07fc.png : f2 g  P& `/ P8 l
: r& L) ]% d) M% {/ c
图9.4.1. 9 Project Manager窗口配置8 v+ _: b( \5 r
系统生成工程如下:8 p, y' w2 w4 j- l. [& O
* z/ V% e8 n' K5 e6 j
d3be3dee605e4523b2e8dbafedc4bb40.png 8 M; Y3 U% u/ \6 G

. E1 N7 `0 ~6 P! P/ Z& u+ ^2 M/ i4 z图9.4.1. 10生成MCU_HSE工程2 Q- K: l5 _) b* v% {9 @
3. 添加用户代码
. Y1 c( ^6 A' D7 Q% u# R我们在main.c文件中添加如下代码:5 L  O) _3 N. @2 p. t& }( ]

# y7 Y) J/ V9 p% S" l# P, t$ o
  1. main.c文件代码
    6 Y: s. T  K9 v6 d& M- W. ~
  2.     static uint32_t mcu_freq = 0;
    7 }- p. B/ h, D1 P7 N8 ]% {& M+ C5 b
  3.     static uint32_t Systick_freq = 0;
    2 w7 _$ a9 S( k+ Z' W8 e. O
  4.     mcu_freq=HAL_RCC_GetSystemCoreClockFreq();                /* 获取MCU的时钟频率  */: i9 T, O( e& b* [7 \
  5.     Systick_freq=SysTick->LOAD;                         /* 获取Systick的LOAD寄存器的值  */
复制代码

6 B$ ]- j. s7 N编译、调试, u6 N- d& v. g
保存修改,编译工程无报错,先在while循环处添加一个断点,然后按照第4.1.6小节连接好开发板和ST-Link,进入Debug模式。
) J9 |1 C. W- {1 ^# V进入Debug模式以后,程序停在HAL_Init函数处,此时变量的值为0
2 j& N8 X1 r( K8 f# ]/ m
( p5 a( E" h8 P, k: t 6955f106e6234054a1ccc891b4884aaa.png   h% P. U% B3 ~/ c' b, k" M
# p) w. Q) B( d4 w, |1 t' N: _8 ]
图9.4.1. 11进入Debug模式
* ^& n8 x) n  m3 J2 e  u& A: i/ A+ R点击运行按钮,程序运行到断点处停止,观察此时mcu_freq的值为209000128,Systick_freq的值为208999。
/ q3 {; v3 o7 W4 j4 l' Y8 G
3 ^9 n! s- B$ w2 v0 E e0a5681b8420485398cb9ae23462c06a.png : W& r8 C2 M* ^* c& }% w- M( S2 B- v
) c* H( q- E0 k' ^8 u$ ]" p
图9.4.1. 12 运行调试
' H0 C% c- w5 \0 E! }9 w8 Y$ j' `5. 实验结果分析% J6 d6 L4 s6 V  ]* r, ?
根据上面的调试结果,我们看到mcu_freq的值为209000128,约等于209MHz,说明此时的系统时钟(MCU时钟)是209MHz,与前面的配置一致。
. o. t* i4 h+ T- q5 R4 wSystick_freq的值为208999,约等于209000,此值我们是读取SysTick定时器中的LOAD寄存器的值。我们根据此结果逆推一下,SysTick每次计209000下就发生中断,如果要确保每1ms的中断周期的话,那么频率必须为209MHz。
4 m+ P0 j) _" F- X  j注:
1 \. ]) }# w' j* ^* y; ^SysTick一个24位向下递减计数器,启动后,LOAD寄存器的值赋给VAL 寄存器,VAL寄存器递减,当递减到0的时候,会产生一次中断,然后再从LOAD寄存器取值,然后再从所得值开始递减,递减到0的时候又产生一次中断,如此反复,从而实现计时。我们在前面的7.4.2小节有讲过。
& D) F3 H3 y) X8 ?$ c前面我们提到,SystemCoreClock全局变量也是通过HAL_RCC_GetSystemCoreClockFreq()函数来获取最新的系统时钟频率的,那么他的值应该也是209000128。我们在STM32CubeIDE上查看此变量的值:
: V' n" P  W3 n  ^% u7 Y  r8 d+ r/ K0 [
2db311b16eee4daf8a86640a41d8aa35.png
1 j9 `; }# ?. }. Z, C0 y) [2 F# |' r
图9.4.1. 13 打开表达式窗口' B. y+ o4 l! \! b/ p" J3 ?
如下图,添加要观察的SystemCoreClock全局变量,其值为209000128。) @; u6 w* o' ?/ E

- R* L% p* i9 F 2ba66ee9e36448458338e38380508285.png
+ b3 X6 ^3 G+ J, B' Z# c* k& D) ?! q' J( Q7 I+ H2 Z/ Z
图9.4.1. 14观察值8 f* B4 z$ Q! t, j" E
9.4.2 使用HSI6 f/ n, }/ p2 _; k6 T7 ~
本实验配置好的实验工程已经放到了开发板光盘中,路径为:开发板光盘A-基础资料\1、程序源码\11、M4 CubeIDE裸机驱动例程\CubeIDE_project\2-2 MCU_HSI。; h* u, E+ a7 \2 }5 q, M) A& V, ~
新建一个工程,工程名字为MCU_HSI,进入时钟配置窗口以后,mcuss_ck默认是64MHz,我们手动输入209然后按下回车键,STM32CubeMX会自动计算各个参数,如下图所示:* ]1 t5 T. w# D* Z  D

* A! O+ h1 c& x6 Q/ n- f 39499f604dc540bca806c1c648135021.png ( x5 S& A# r3 {$ K! _

" f) c0 d* o  b- w图9.4.1. 15手动配置参数  x: Z7 f6 B: R3 D# A! W
红色部分提示pclk1、pclk2和pclk3的频率数值超出范围,我们调整一下,将分频值由1改为2:4 c# w  Z8 g; p
) e- N* j: C. x* q$ `) X4 j8 ^' U
ea75f0b7b37a43f2a37c553ea9a968c0.png
4 [  f' c1 W0 [, b1 M8 z) }3 g  u; f4 t- y- J5 y& h; O+ F4 j
图9.4.1. 16手动调整参数
6 B' K( W/ e" Q6 y) G, c) x配置好以后,我们看到各个参数:: V2 J& B( y  M+ r  h7 O
①默认选择HSI作为PLL3的时钟源;
. K! \/ N+ w2 K7 h②默认选择PLL3P(也就是pll3_p_ck)作为mcuss_ck的输入时钟源;
( l7 L) ]+ g& a* V' n; H0 q④输入209以后,STM32CubeMX会动态计算PLL3的倍频和分频系数,其中:1 T5 C5 y1 `8 B/ q- U5 I
DIVM3=4,DIVN3=26,fracv3=1024,DIVP3=2
7 c, Y6 p5 \; A% {9 M6 {" _我们将上面的参数代入前面的计算公式中,计算pll3_p_ck结果也刚好是209MHz。
! s( c4 g9 G8 U1 ?! F4 P. r1 l
% _) H! m! ^% ~0 ~* ~  C按下“Ctrl+S”保存配置,STM32CubeIDE自动生成工程:
, t+ a/ F! ^& a2 k; P. \5 k我们在main.c文件中添加如下代码:
' O7 e8 a+ D( }" i1 B4 `  X) }- w, R7 i( ?' _: P/ t/ Y
  1. main.c文件代码
    " s4 I) x& C8 j$ ?& F. y
  2.     static uint32_t mcu_freq = 0;" p; H; K3 m6 K7 Q$ B# f3 P
  3.     static uint32_t Systick_freq = 0;& Y! ?/ `3 r8 F) p5 d: q9 t
  4.     mcu_freq=HAL_RCC_GetSystemCoreClockFreq();/* 获取MCU的时钟频率  */' M' b  @4 I& l: O6 H: y" e
  5.     Systick_freq=SysTick->LOAD;                         /* 获取Systick的LOAD寄存器的值  */
复制代码
3 G/ j" X2 ^( u5 ^- {+ r: X* s
a19a92deb99448159f75414911bb5995.png
, X9 m( X9 r8 U" a" r9 h6 @3 Z8 F( t
7 Y" E) x8 q! v; {& ]2 j, N图9.4.1. 17添加的代码
" f$ X6 u; ^' k保存修改,编译工程无报错,先在while循环处添加一个断点,然后按照第4.1.6小节连接好开发板和ST-Link,进入Debug模式。注意不要选错前面的MCU_HSE的工程,可以在Debug配置界面处确认一下:; q0 W/ @  h3 |
; j) u2 F# Z8 M2 r. S$ q
e6ca2bb672f44d749d83438f11a9758c.png
- ]3 G( n  J" V! M5 I  y# O4 m  m6 c& \, Q8 t5 L# P* i6 |
图9.4.1. 18确认调试的工程* h: j! g  p3 ~3 \5 z
进入Debug模式以后,程序停在HAL_Init函数处,此时变量的值为0。
  C6 q6 T% o- R$ O0 {
0 _* `/ R( D+ l6 R! s  T( f3 _7 ] b75d0db21faf4292835fac100676ae60.png + Q4 b0 j- F7 N8 n9 z
- ~5 O7 }8 h, G
图9.4.1. 19进入Debug模式9 y0 ~9 y- r* e% ^' }$ S: k3 u
点击运行按钮,程序运行到断点处停止,观察此时mcu_freq的值为209000128,Systick_freq的值为208999。实验结果和上面MCU_HSE工程结果一样。实验分析请查看前面的MCU_HSE工程部分。7 M) r, ~4 S# A: a7 G6 R
8 z. l9 d- {7 G1 O/ t
37018cdcdf1c4b86bba7262c9e00885b.png
1 g6 {. P, c! Z! W% F$ Y$ h( K0 l: b* N! S+ a. w" ?5 H
图9.4.1. 20运行调试* s6 M) b/ C/ q" n' S, Y$ G! a
9.5 实验代码分析+ h# M3 e0 C' z
实验虽然验证完毕了,虽然是STM32CubeIDE自动生成的初始化代码,但我们还是有必要分析一下工程代码,这对HAL库的理解还是很有好处的。我们以MCU_HSE工程为例子,分析一下代码的实现过程。
( m8 v& A9 N  R9.5.1 配置HSE相关宏定义7 _. L. l- O( e  @; ]
此部分主要是在stm32mp1xx_hal_conf.h文件中完成的,在第7.4.1小节有分析过此文件。
+ H/ E" Z' a& G: ^- T" D. H4 v& M, l7 K/ o5 t* S$ V# ^& z
选择HSE相关模块4 q" }! w! O8 ^, T3 q
在stm32mp1xx_hal_conf.h文件中使能HAL驱动程序中要使用的模块的列表,实验中用到了RCC:
4 }" @+ X& ~9 x3 `" [
  1. stm32mp1xx_hal_conf.h文件代码
    : m; N# R+ A+ H8 W/ ^) G7 `
  2.         #define HAL_MODULE_ENABLED3 c# S, M) @' x) B# \5 s6 E0 P
  3.     /*#define HAL_HASH_MODULE_ENABLED   */
    1 b4 m& U# c" k2 J5 S# C
  4.     #define HAL_HSEM_MODULE_ENABLED1 w& c3 g3 m/ N4 m. A3 t4 e
  5.     /*#define HAL_WWDG_MODULE_ENABLED   */, [. H1 w2 Z& y; L7 [
  6.     #define HAL_GPIO_MODULE_ENABLED
    - e; p, M5 K- }* Z- C6 p  _, [' Y4 f
  7.     #define HAL_EXTI_MODULE_ENABLED
      V- g1 T0 G: {8 `
  8.     #define HAL_DMA_MODULE_ENABLED
    , A$ t5 H" U+ Q# Y
  9.     #define HAL_MDMA_MODULE_ENABLED
    ! I/ B8 F; C* g
  10.     #define HAL_RCC_MODULE_ENABLED
    ; X4 N! Q& o* l, s7 G0 I( W
  11.     #define HAL_PWR_MODULE_ENABLED8 R% I! Y6 W$ f; [! u5 r. w
  12.     #define HAL_CORTEX_MODULE_ENABLED
复制代码

! c. Q$ K* _  N包含HSE相关模块的头文件. ^  y  f& s0 l8 I# X
  1. stm32mp1xx_hal_conf.h文件代码
    3 E; p- B! u) s
  2.     #ifdef HAL_RCC_MODULE_ENABLED
    + B: e6 h8 {* X/ s
  3.      #include "stm32mp1xx_hal_rcc.h"
    4 |# X- s) j- L: }8 l
  4.     #endif /* HAL_RCC_MODULE_ENABLED */' K. `8 g3 W# m* T# |

  5. + b5 G9 D6 I1 U
  6.     #ifdef HAL_EXTI_MODULE_ENABLED; {; F1 d( \; w. J5 V
  7.      #include "stm32mp1xx_hal_exti.h"7 X5 ~' ~7 V" c1 K+ g6 a9 B
  8.     #endif /* HAL_EXTI_MODULE_ENABLED */
    0 F* }& r- l2 p: x) s6 O
  9. ; M) x0 |' U2 X% l
  10.     #ifdef HAL_GPIO_MODULE_ENABLED
    ' w1 R) _! A6 r, M! g) I
  11.      #include "stm32mp1xx_hal_gpio.h"6 G5 B; v0 ^) w2 r% j. g
  12.     #endif /* HAL_GPIO_MODULE_ENABLED */
    8 Q$ z0 S" Z1 K
  13. / ^" d1 G3 R1 Z. g3 a( K. j$ |
  14.     #ifdef HAL_HSEM_MODULE_ENABLED
    ; H4 u/ }1 S3 G+ p$ H; w& y" [
  15.      #include "stm32mp1xx_hal_hsem.h"
    5 W- b3 n# ?( ^0 ~; {
  16.     #endif /* HAL_HSEM_MODULE_ENABLED */: p1 G) u. }7 A3 J
  17.         /****** 省略部分代码 *******/
复制代码
. r- D. T) A: H( n( ~2 P* i
配置HSE_VALUE+ a' z2 Q5 a, N5 S  n: u
宏定义HSE_VALUE匹配我们实际硬件的高速晶振频率(这里是24MHz),代码如下:1 k0 J9 U' w# P3 {6 |
  1. stm32mp1xx_hal_conf.h文件代码1 k' H2 m( l+ Z
  2. #if !defined  (HSE_VALUE)
    7 T$ U- \/ H" Q
  3.     #define HSE_VALUE    (24000000U)
    / j! v6 p( ]9 T1 J/ S4 j
  4.     #endif
复制代码
& e+ g8 z% x" V% l/ I  `& n: E& a
9.5.2 系统时钟设置函数
! a! }5 G; A$ n% V1 V3 d3 u# j; V7 D& n* w
函数代码实现% a% y9 m- ^. U- b. n
gpio.c文件代码很简单,主要就是开启GPIOH时钟,因为HSE的两个时钟引脚挂在GPIOH上:
1 Q7 h& V4 z4 }
  1. #include "gpio.h"
    4 ^  v; R( R' I4 o* o$ p
  2. ! w2 n# z, W7 k6 @  z; |( f
  3. void MX_GPIO_Init(void)
    + s  {/ N) R9 @  \, `
  4. {# }0 V6 K4 L, [# R: R1 C/ k1 K
  5.   /* 开启GPIOH时钟,因为HSE的两个引脚PH0-OSC_IN和PH1-OSC_OUT挂在GPIOH上 */
    1 q& I& _  w( S! }3 i, M: j
  6.   __HAL_RCC_GPIOH_CLK_ENABLE();3 S1 ^* D; [9 r
  7. }
复制代码
, p  ~: F! F. S& r) ]
下面,我们重点来看看main.c文件中系统时钟初始化代码。如下图是系统时钟设置函数SystemClock_Config的代码。代码主要分成三部分:. p9 {( J; T; F9 W6 g! _6 D: \. ^
①第一部分,是定义并初始化RCC_OscInitStruct、RCC_ClkInitStruct结构体变量,初始值默认为0,即将HSE、HSI、LSE、LSE、CSI和PLL1~PLL4均关闭;6 v% x/ d% f6 L% l2 i* S- f. s
②第二部分,通过RCC_OscInitStruct结构体变量开启时钟源HSI、HSE、LSI和PLL3,并设置PLL3的分频和倍频参数,从而使pll3_p_ck输出频率为209MHz。& b9 _# w6 w+ n4 n5 k
③第三部分,通过RCC_ClkInitStruct结构体变量完成AXI、MCU和APB1~APB5的配置,实现AXI、MCU、APB1~APB3和APB4~APB5时钟频率分别为64MHz、209MHz、64MHz和104.5MHz。
1 W3 C/ h$ ?! i$ {! |main.c文件代码9 A4 J5 f! L( ?3 X. c4 x
  1. 1   /**. X8 g( n" N" X' C6 O9 a
  2. 2    * @brief     M4主频时钟设置函数,也就是设置PLL3: I  c2 d& s' H+ J, B' v- Y
  3. 3    * @ note     plln: PLL3倍频系数(PLL倍频), 取值范围: 25~200.$ `2 G9 H# u  \7 C8 `. M
  4. 4    *             pllm: PLL3预分频系数(进PLL之前的分频), 取值范围: 1~64.
    , x4 d. L7 ~+ K; {9 h  c
  5. 5    *             pllp: PLL3的p分频系数(PLL之后的分频), 分频后作为系统时钟, 取                                        值范围: 1~128.(且必须是2的倍数)
    . O0 B  J* T* ~% e7 W
  6. 6    *             pllq: PLL3的q分频系数(PLL之后的分频), 取值范围: 1~128.9 y' @" }: A9 Y% y
  7. 7    * + R6 m' I' Y7 X, f
  8. 8    *              MP157使用HSE时钟的时候,默认最高配置为:
    7 F- E$ ?3 |; D- Y
  9. 9    *              CPU频率(mcu_ck) = MLHCLK = PLL3P / 1 = 209Mhz
    4 s! t6 h0 b# K
  10. 10   *              hclk = MLHCLK = 209Mhz
    7 a* d$ t0 k6 n% @) S0 ~
  11. 11   *              AHB1/2/3/4 = hclk = 209Mhz
    1 z( L) f0 h* U7 d5 t
  12. 12   *              APB1/2/3 = MLHCLK / 2 = 104.5Mhz
    / G* K1 Z( T3 H9 e' d
  13. 13   * @retval      无;3 B; [/ L" R& z8 ?( z
  14. 14   */
    # B  G/ w+ J7 a/ h
  15. 15  void SystemClock_Config(void)4 }- I  p% g% v
  16. 16  {" N1 R/ M3 ?6 F* D+ f0 O
  17. 17  /* 定义RCC_OscInitStruct、RCC_ClkInitStruct结构体变量,并初始化为0 */
    ! _8 S: B+ D/ V2 K3 H2 ~( O
  18. 18    RCC_OscInitTypeDef RCC_OscInitStruct = {0};
    9 r3 f. ]3 V  S8 x) Q2 w' M4 Y
  19. 19    RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
    ; b3 H2 P. m0 b. E6 g" _) Y
  20. 20
    5 c( U8 W) [" w& P( {* Y
  21. 21  /*给RCC_OscInitTypeDef结构中的成员变量赋值完成初始化RCC振荡器 */
    ' o: j; H+ R$ G
  22. 22    RCC_OscInitStruct.OscillatorType = \                                 RCC_OSCILLATORTYPE_HSI|RCC_OSCILLATORTYPE_LSI
    : ?, _1 `# i5 ^; G9 ^* z! K) P" T
  23. 23                                |RCC_OSCILLATORTYPE_HSE;& r' {' V7 y5 ]
  24. 24    RCC_OscInitStruct.HSEState = RCC_HSE_ON;                  /* 打开HSE */
    5 S  a, F2 S# R9 F
  25. 25    RCC_OscInitStruct.HSIState = RCC_HSI_ON;                  /* 打开HSI */9 M  x& u/ H  W' R0 U5 j
  26. 26    RCC_OscInitStruct.HSICalibrationValue = 16;                /* 校准HSI值 */. N+ ]2 W1 I# s4 x5 m6 p( d  L7 a; j
  27. 27    RCC_OscInitStruct.HSIDivValue = RCC_HSI_DIV1; /* 设置HSI分频值为1 */
    $ o$ J- O1 c8 {! z7 o
  28. 28    . n& Z& |* v2 C  |
  29. 29    RCC_OscInitStruct.LSIState = RCC_LSI_ON;                  /* 开启LSI */' J) W  z5 u5 f# y1 n% B
  30. 30    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;        /* 没有PLL的状态 */0 z: M7 o9 q  N$ r0 R3 w
  31. 31    RCC_OscInitStruct.PLL2.PLLState = RCC_PLL_NONE;        /* 没有PLL2的状态 */
    - _/ E3 s% w* h& a
  32. 32    RCC_OscInitStruct.PLL3.PLLState = RCC_PLL_ON;        /* 开启PLL3 */+ [  v. J# U; i
  33. 33  /* PLL3输入时钟源为HSE */
    : w0 g6 P/ F. c! b) e/ E/ Z
  34. 34    RCC_OscInitStruct.PLL3.PLLSource = RCC_PLL3SOURCE_HSE;! x, W5 L8 L0 S1 [* Y
  35. 35  /*- s+ m' W; d& I$ _% ^9 M( E
  36. 36  * 配置锁相环PLL3的分频和倍频参数,也就是:! M1 ]) H  B) Y( e8 d
  37. 37  * DIVM3=2,DIVN3=52,DIVP3=3,DIVQ3=2,DIVR3=2,FRACV=2048
    8 ~. e* _6 n( q* K. g
  38. 38  * 则PLL3的pll3_p_ck输出频率为:
    ' z( V- y2 Z  `2 L% [
  39. 39  * pll3_p_ck==209MHz4 R! S5 U0 K- H8 r
  40. 40  */! {" ~& Y6 B, E( U$ i3 T3 m' e6 p; l
  41. 41    RCC_OscInitStruct.PLL3.PLLM = 2;                        /* DIVM3=2 */8 ~/ K% o5 g0 f) l9 v
  42. 42    RCC_OscInitStruct.PLL3.PLLN = 52;                        /* DIVN3=52 */
    + X5 h5 X) n+ H& S
  43. 43    RCC_OscInitStruct.PLL3.PLLP = 3;                        /* DIVP3=3 */
    : P( H5 G* {0 _; Q* |) a
  44. 44    RCC_OscInitStruct.PLL3.PLLQ = 2;                        /* DIVQ3=2 */
    ! G! q# ?: ]  @. `! m
  45. 45    RCC_OscInitStruct.PLL3.PLLR = 2;                        /* DIVR3=2 */. u2 c- x6 ?+ b; W! k, v: D
  46. 46    RCC_OscInitStruct.PLL3.PLLRGE = RCC_PLL3IFRANGE_1;
    * S6 A; E; v& [) T1 j7 s
  47. 47    RCC_OscInitStruct.PLL3.PLLFRACV = 2048;        /* FRACV=2048 */
    + ]6 w, @/ F& W% o
  48. 48    RCC_OscInitStruct.PLL3.PLLMODE = RCC_PLL_FRACTIONAL;/* 分数模式 */% j5 J2 e% |- Q7 {" c" o
  49. 49    RCC_OscInitStruct.PLL4.PLLState = RCC_PLL_NONE;        /* PLL4没有状态 */6 p: z. w# C1 C
  50. 50    ; @: \0 |. l( c; a
  51. 51   /* 调用的HAL_RCC_OscConfig函数用于判断 HSE、HSI、LSI、LSE 和
    & S9 A- _" r% w, m
  52. 52    * PLL(PLL1、PLL2、PLL3和PLL4)是否配置完成,配置完成则返回HAL_OK。
    6 Z0 p' R" M4 N( k* C6 t' g
  53. 53    * 如果没有配置完成,发生错误的话就会进入Error_Handler函数(空循环)。3 |: b% T) ?) X+ w7 w6 C
  54. 54    */
    4 c  V# g" J+ |5 `! {0 ]6 h
  55. 55    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)- G# S. h, w; L' G
  56. 56    {& A" M1 y, E1 o; \$ ^/ d! F( A
  57. 57      Error_Handler();
    : s/ \3 [  v/ m  I
  58. 58    }3 v* Q$ g* z+ @4 r7 Q- o. e
  59. 59  /*
    ; J# ]6 a4 y6 F: v$ A" ^) }
  60. 60   * 给RCC_ClkInitStruct结构体成员赋值来配置RCC时钟,也就是:
    : N9 x+ p: l# p1 ~1 z
  61. 61   * 配置AXI的时钟源和分频器分频值(也就是配置ACLK);" r5 D9 J/ G( ~/ V0 W: E8 X
  62. 62   * 配置MCU的时钟源和分频器分频值;! v( J0 d7 a& @' \' s
  63. 63   * 配置APB1~APB5的分频值(也就是配置PCLK1~5)。! X6 r/ G: _3 l5 J# W* \; z( S
  64. 64   */
    ) B+ i( j5 t( u5 J9 m( [* v5 O
  65. 65    RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_ACLK
    ; G& N' j( c( [3 o1 L
  66. 66                                |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK23 M6 T+ u) u4 Z$ s8 T
  67. 67                                |RCC_CLOCKTYPE_PCLK3|RCC_CLOCKTYPE_PCLK4
    . M" H* o: w# f0 C# C
  68. 68                                |RCC_CLOCKTYPE_PCLK5;2 Z( c$ h/ }: w2 C
  69. 69  /* 配置AXI时钟源为HSI */                            - g; d& g" S0 s  k6 M5 Z
  70. 70    RCC_ClkInitStruct.AXISSInit.AXI_Clock = RCC_AXISSOURCE_HSI;
    ; {: l! U# w. F4 V1 G! z
  71. 71  /* 配置AXI分频器为1分频=ACLK=64MHz */   / n: j' ^, T( H
  72. 72    RCC_ClkInitStruct.AXISSInit.AXI_Div = RCC_AXI_DIV1;1 y: T. Y# L# ^* |0 B. b
  73. 73  /* 配置MCU时钟源来自PLL3=209MHz */
    ' Z, W2 y- k4 _, A7 `3 L
  74. 74    RCC_ClkInitStruct.MCUInit.MCU_Clock = RCC_MCUSSOURCE_PLL3;+ P1 g5 n5 d, r% S
  75. 75  /* 配置MCU分频器为1分频=MCU=209MHz */. M! Q1 j) P- K7 I( [7 @4 n
  76. 76    RCC_ClkInitStruct.MCUInit.MCU_Div = RCC_MCU_DIV1;
    ( K9 \$ H, ~: k: E) ~) B+ J
  77. 77  /* 配置APB4分频器为1分频=PCLK4=64MHz*/6 H5 f$ @0 h# ~- [: S. K
  78. 78    RCC_ClkInitStruct.APB4_Div = RCC_APB4_DIV1;$ O! N  m# R" c; R) w1 r
  79. 79  /* 配置APB5分频器为2分频=PCLK5=64MHz */
    , |' q7 T# {( M% E5 C
  80. 80    RCC_ClkInitStruct.APB5_Div = RCC_APB5_DIV1;: s' c* y* o: d' I1 a% e/ [
  81. 81  /* 配置APB1分频器为2分频=PCLK1=104.5MHz */3 e8 g- }% ?% v  _, D
  82. 82    RCC_ClkInitStruct.APB1_Div = RCC_APB1_DIV2;" j% Q" }# P6 K. T' o4 l( k
  83. 83  /* 配置APB2分频器为2分频=PCLK2=104.5MHz */! ~( w5 a% |# O
  84. 84    RCC_ClkInitStruct.APB2_Div = RCC_APB2_DIV2;
    ( B& u' l# m; x; ~" f6 X
  85. 85  /* 配置APB3分频器为2分频=PCLK3=104.5MHz */2 n; H& V2 S2 }9 b( a& [3 l
  86. 86    RCC_ClkInitStruct.APB3_Div = RCC_APB3_DIV2;
    + o7 [- [1 C9 z3 a& t
  87. 87  /*
    & M( s- _) l5 X! j
  88. 88  * 调用HAL_RCC_ClockConfig函数,根据RCC_ClkInitStruct中
    % ^! c. \" v4 S2 K: e9 L3 m2 h# ]2 Z. p
  89. 89  * 指定的参数初始化MPU,AXI,AHB和APB总线时钟,如果初始化1 c" {" Z) a4 G+ b5 m- ]
  90. 90  * 不成功,则进入Error_Handler空循环函数。
    0 H" i4 f# v- |8 a* v) \
  91. 91  */% k0 N( Y7 e$ c4 I6 P! A1 L! X
  92. 92    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct) != HAL_OK)
    ( i6 y1 P% S& A3 G8 H: }* k& j0 f
  93. 93    {* L4 m3 ?6 {3 |1 ]8 U4 s( j5 |
  94. 94      Error_Handler();
    0 q, g5 r& g9 t
  95. 95    }. w6 _* b! m* I$ w6 H
  96. 96    /* 设置RTC时钟的HSE分频因子 */+ S' ~+ M8 q6 d7 x: V
  97. 97    __HAL_RCC_RTC_HSEDIV(1);
    " P4 h! X* o5 l) [% g# l* r1 |
  98. 98  }
复制代码
& k2 X2 X, {4 J
函数代码分析
) T3 q+ n! F# t8 A4 n. o/ z1 U3 v下面,我们来分析SystemClock_Config函数的代码实现过程。$ f6 a; d2 f. o- }, _+ i8 L, X
第18行,定义RCC_OscInitTypeDef类型结构体变量RCC_OscInitStruct,并初始化为0,表示将HSE、HSI、LSE、LSE、CSI和PLL1~PLL4均关闭。
/ O8 ?' M& l& J5 p& h# k; x以上结构体在stm32mp1xx_hal_rcc.h文件中有定义,我们在前面也有详细介绍过,为了大家不总是跳跃看代码,这里还是贴出代码了,如下。
# w+ W# K" f3 nRCC_OscInitTypeDef主要对RCC内部/外部振荡器(HSE,HSI,CSI,LSE和LSI)配置的结构定义,主要是定义要配置的振荡器、时钟源的状态、校准值和PLL的参数。通过给结构体成员赋值即可开或者关对应的时钟,并可以配置PLL的参数。' ~" b# {* A; u4 _, }
  1. stm32mp1xx_hal_rcc.h文件代码
    7 G* x' R$ }. Z( w
  2.     typedef struct2 K' L& Z- W$ J% f4 L
  3.     {0 F6 i# O9 j, _
  4.       uint32_t OscillatorType;                         /* 要配置的振荡器 */                                                     
    9 F6 }& q  _2 L  _' k
  5.       uint32_t HSEState;                                   /* HSE的新状态 */                                                                  
    3 S' f- Y/ Z8 F
  6.       uint32_t LSEState;                                         /* LSE的新状态 */                                                                       ; _8 A. v3 i- D
  7.       uint32_t HSIState;                                   /* HSI的新状态 */                                                                  
    ' X& ^3 I* Y" l  u& u9 D; l+ m% Z
  8.       uint32_t HSICalibrationValue;                 /* 校准调整值 */                                      0 q. T$ l$ i' U) T9 ~
  9.       uint32_t HSIDivValue;                                  /* HSI的分频系数 */                                             ' I" `) Y; |# u' g0 e+ K$ H* C
  10.       uint32_t LSIState;                                  /* LSI的新状态 */                                                 ' J' q' v( q! Q3 I* w. ?( A; E
  11.       uint32_t CSIState;                                  /* CSI的新状态 */                                                
    - H; F" j3 _; |) F/ a% ?, e
  12.       uint32_t CSICalibrationValue;                 /* 校准调整值 */                                       ( x& i/ A" r* @' G* |. J' y
  13.       RCC_PLLInitTypeDef PLL;                         /* PLL1结构参数 */      
    * b2 W" X5 [8 z5 Q
  14.       RCC_PLLInitTypeDef PLL2;                        /* PLL2结构参数 */     # `  A6 r+ _* K: w: b% ?
  15.       RCC_PLLInitTypeDef PLL3;                         /* PLL3结构参数 */     + r+ `& U( n- J6 J. r) y
  16.       RCC_PLLInitTypeDef PLL4;                         /* PLL4结构参数 */                                                  
    6 E; B$ H, w6 A1 P7 s; l
  17.     } RCC_OscInitTypeDef;
复制代码
0 w% S" P( _4 E
我们查看PLL的结构参数有哪些,如下,此结构体定义了锁相环PLL中用到的DIVM、DIVP、DIVQ、DIVR、FRACV等参数变量,通过给这些结构体成员赋值即可配置PLL的时钟。
  ?; r) o5 u% o
' e; ~, i8 P5 b  e
  1. stm32mp1xx_hal_rcc.h文件代码
    0 v6 {1 F8 b! P! y) F. N
  2. + h2 r. x9 u/ n- D8 M, o
  3. typedef struct
    $ o0 ~# s( x! S$ B
  4. {6 n! c! y6 w5 Y; \( |  |
  5. uint32_t PLLState; /* PLL的新状态 /, g/ G# E; ?5 ]1 |1 V& E4 L
  6. uint32_t PLLSource; / PLL输入时钟源 /
    & \: G8 F$ R8 t
  7. uint32_t PLLM; / PLL VCO输入时钟的分频系数DIVM /
    2 I6 x0 X% u. n+ r2 \1 w4 d; s# s- u
  8. uint32_t PLLN; / PLL VCO输出时钟的倍数DIVN /7 j. O+ J' P3 M( q! P" @
  9. uint32_t PLLP; / 分频因子DIVP /
    " M* \4 W5 ]5 @, j- {& T
  10. uint32_t PLLQ; / 分频因子DIVQ /
    ( A" S& D) ~7 y9 ?) Z8 x. G+ q
  11. uint32_t PLLR; / 分频因子DIVR /& n) e4 N' y2 d/ @. `2 _6 ?
  12. uint32_t PLLRGE; / PLL3和PLL4的PLL输入频率范围 /  E. C& y" N8 c! X( `
  13. uint32_t PLLFRACV; / PLL VCO乘数的小数部分FRACV /
    2 l, B* q7 \4 T- \) h. p3 Z
  14. uint32_t PLLMODE; / 使用PLL模式 // B+ C' b. r8 W9 C5 W6 C. }/ t
  15. uint32_t MOD_PER; / 调制周期调整 /9 E" h7 c* J# X& P+ ]$ m+ @
  16. uint32_t RPDFN_DIS; / 抖动的RPDF噪音控制 /' U8 K9 Z2 t$ w4 j, \/ Z
  17. uint32_t TPDFN_DIS; / 抖动的TPDF噪声控制 /
    9 {5 j9 O3 }6 x- u& e: L+ z. s
  18. uint32_t SSCG_MODE; / 扩频时钟发生器模式 /; ~  ~! h2 U5 C1 B( a& w
  19. uint32_t INC_STEP; / 调制深度调整*/
    ; f% N$ o9 s0 c# E8 u
  20. } RCC_PLLInitTypeDef;
    3 q: S' O" u4 \) x/ @# I
  21. 第19行,定义RCC_ClkInitTypeDef类型结构体变量,并初始化为0,即AXI子系统、MCU子系统时钟源默认来自HSI,且AXI时钟和MCU时钟以及APB1~APB5时钟的分频器的分频值为0(没有分频)。
    ( N1 Z! A4 I. q0 O3 q6 T
  22. RCC_ClkInitStruct此结构体我们在前面也有讲解过。RCC_ClkInitTypeDef结构体定义如下,主要是定义AXI和MPU的时钟源以及分频系数,还有APB1~APB5的分频系数,通过给结构体成员赋值即可实现对MPU、AXI和APB进行分频。2 C( C; a. _" W
  23. stm32mp1xx_hal_rcc.h文件代码& U) V. V+ @% J3 u/ P; C6 U

  24.   |( t! _. U; P
  25. typedef struct% b6 t/ l8 g% S1 n$ h2 u6 n
  26. {- r1 M" {& A: Z
  27. uint32_t ClockType; /* 时钟源选择 /7 Q" F3 {7 z, R+ x( ^2 b  T* L6 f/ p
  28. RCC_MPUInitTypeDef MPUInit; / MPU结构参数(时钟源和分频数) /
    7 |- y0 B/ C* E8 Q
  29. RCC_AXISSInitTypeDef AXISSInit; / AXI结构参数(时钟源和分频数)/
    & p8 ?; I. a0 @% E& \* j
  30. RCC_MCUInitTypeDef MCUInit; / APB4分频数 /
    ; u" ?% J; X& ~. w' C8 d( s
  31. uint32_t APB4_Div; / APB4分频数 /
    + n# f: n  M3 V. g
  32. uint32_t APB5_Div; / APB5分频数 /
    4 s2 c9 A9 Q+ `' }5 V
  33. uint32_t APB1_Div; / APB1分频数 /+ m; ~# h" g& e! C/ A
  34. uint32_t APB2_Div; / APB2分频数 /
    ' t0 {8 C. h1 O! d
  35. uint32_t APB3_Div; / APB3分频数 */
    " D; M5 |# W1 i
  36. } RCC_ClkInitTypeDef;
复制代码
* E0 b6 o! B! S3 {/ E4 f  u# N3 h
分析完了结构体,整个系统时钟初始化就大概明白了,需要哪个时钟源,要用那个PLL以及PLL的各个参数,要对那根总线进行分频以及分频系数是多少等等,我们都可以通过给结构体成员赋值来实现。SystemClock_Config函数其实也是这么做的,我们接着分析此函数剩下的代码。+ ]  I/ U+ y& s& r) M) U
第21到第34行,给RCC_OscInitTypeDef结构中的成员变量赋值完成初始化RCC振荡器,开启了HSE,配置PLL3为209MHz。; ~3 {/ l% E! j( i( Y
第55到58行,调用HAL_RCC_OscConfig函数真正完成HSE、HSI、LSI 和 PLL3的配置。
: z- y1 P" `$ ]( [第65到第86行,给RCC_ClkInitStruct结构体成员赋值,完成AXI、MCU和APB的时钟分配。其中:% Y) ~5 D  F! s
AXI时钟源来自HSI,经过1分频后最终频率为64MHz;
* \; M! X. e4 f. [+ {0 r$ MMCU时钟源来自PLL3,经过1分频后最终频率为209MHz;
% Q# K5 \  x2 ^0 ^: E8 m& i确定了AXI的时钟,那么APB4和APB5的时钟源也就确定了。APB4和APB5经过1分频后最终频率为64MHz;! J2 s: m; W1 X" S. F9 g. R
确定了MCU的时钟,那么APB1APB5的时钟源也就确定了。APB1APB2经过1分频后最终时钟频率为104.5MHz。4 ]0 i  v5 |6 S) J
可能大家会问,为啥没看到AHB1AHB6相关的代码呢?这个系统就默认配置好了,因为他们不需要经过分频器。AHB1AHB4的时钟直接通过mcuss_ck获得,为64MHz。AHB5~AHB6的时钟直接通过axiss_ck获得,为209MHz。9 j+ R( a0 t6 ~; g8 s& ~, e" C
9.5.3 main函数调用
8 ]/ D: i2 d& U在main函数中调用系统时钟配置函数的代码:
1 r6 b) z8 c1 l) \9 t; G, C
3 x; j: w! b; _  z3 h- `
  1. if(IS_ENGINEERING_BOOT_MODE())$ M4 Y) A# R! H% H5 K
  2.       {
    ; d3 E0 K  B; r" g2 X/ h
  3.         SystemClock_Config();
    . Z9 a' s1 A6 S( Y$ w
  4.       }
复制代码
( H9 @' b8 n) W$ x0 W; K. W' ]
其中,先判断IS_ENGINEERING_BOOT_MODE()函数的返回值,如果为TRUE 则初始化系统时钟,如果为FALSE,条件不成立,if语句不执行。我们来看看IS_ENGINEERING_BOOT_MODE函数:
# F& R# ^, N2 X, {# h
" q1 c: y8 }  j$ e7 p' }# q
  1. stm32mp157dxx_cm4.h文件代码
    ) N, m7 Q" R% ~5 i7 n
  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函数来初始化系统时钟。
$ M& }& d! x2 D5 [0 ~大家可以分析一下第二个工程MCU_HSI的系统时钟代码,对比看看它们的区别。1 A6 P# k  ]$ h; V0 H) L- Y% {2 I
关于外设时钟初始化部分,下面的章节小结有总,我们也会在对应的外设实验章节进行讲解。* x1 ^9 E) K% p: V, j( n

& h3 S2 ~! M8 G9.5.4 HAL_Delay的计时
# k/ M% H: |/ h3 m. d  t关于HAL_Delay是怎么实现计时的,我们在前面第7.4.2小节有分析过,关于Systick我们后面会有专门的实验章节做讲解。默认情况下,STM32CubeMX使用Systick作为时基给其它程序提供计时,例如HAL_Delay延时函数,以及串口程序中的Timeout 超时机制等等。
5 N$ X/ u. f" }6 {
# ]( x' v( o  x2 I: A 24f8092125994d2c9712417359e27dee.png
+ D) [' U+ a0 _3 {- Z
$ c6 v) O  G- `' }8 O图9.5.2. 1 STM32CubeMX插件默认配置/ X7 |$ ?8 J6 J# c
————————————————
- h. I" m; b" T/ U& N- Q6 x版权声明:正点原子
$ }9 J( ^) Q* F( x
/ Z2 q& u) c8 Y/ H: ~7 ~, i  v  I
; `5 @7 t; J/ Q7 g5 d& \5 o5 V7 t
收藏 评论0 发布时间:2022-9-21 16:06

举报

0个回答

所属标签

相似分享

官网相关资源

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