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

【经验分享】STM32L4系列一、时钟系统简介

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容
! a9 x9 J* e/ g4 `9 F1)STM32L4 时钟树概述;
2 |" v* I' ]5 m! F7 }9 p8 P  x: _* h2 ?2)STM32L4 时钟初始化配置;  L6 L, d$ L& W
3)STM32L4 时钟使能和配置。$ a6 D  @6 ^3 h* r; X) q% o2 e
) H) S- S% U' r1 ~/ S
一、STM32L4 时钟树概述
; N+ O" k6 s. b, x- Q时钟系统是 CPU 的脉搏。
( h/ W: Y5 e( X3 T7 L& q2 P不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
9 ~( {2 ?( ^: M5 ?3 A, L3 h同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
; j8 s2 V# V" }- `9 a所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
' W4 A0 A( k( _3 t
' R0 a: e9 b3 `- C$ `STM32L4 的时钟系统图如下:
8 Y, [, o  i& |# w
/ r4 C, A" E* F7 K+ j% m
20201205162651825.jpg

( f7 @' |) }2 W3 e5 _
20201205162724607.jpg
+ Q1 E) i( A' L3 x8 f
+ F2 y4 k, E6 T  F2 a
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。' p8 ^1 ^: u( m/ K* \
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
3 b) }  \2 M  C( N: B) ~按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
7 t7 C" g9 T7 }5 X# S7 d) W% @按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。/ D' ?" R; M( ]; t4 w
按上图中红圈标示的顺序分别介绍这6个时钟源:( Z, K: C. V1 G; F0 [: b* u
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
( N# @* E8 W$ B( s" N4 U$ Z1 c② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
7 @- S2 i9 y- N* O% k③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。6 l2 b9 H. ^6 k. r. Y# }+ i" v
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。$ R! j0 D8 s) N, B+ c2 I" e
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
* N! ]; s; ~: c* ^& y⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
$ u' V9 ~2 O' g1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
) H4 t# Q7 U$ N! P$ `第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
- N4 u/ U! |8 L' s0 D# K! I* i# K/ i第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
6 `, m1 A. P& c' F第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
4 q) s0 n& j9 X9 _! p2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
  O+ ?$ D9 F( b& b; j第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;+ i+ }2 _, q6 e: j
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;4 q) ]8 B4 V+ m% O
第三个输出 PLLSAI1R,可为 ADC 提供时钟。# X# \5 P/ f: G% F
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
9 o: k) c' N( N! Y4 L- d7 F0 ]第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;; T9 k7 b# W/ k* {) E$ n
第二个输出 PLLSAI2R,可为 ADC 提供时钟。- ~% L( v3 o" O7 \, _

* H' ~/ W. U  [2 |9 S$ n这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:
* Q+ \$ e, s' }$ _5 X7 S
20201205163714587.jpg

$ `. \6 k/ G' \! i  R# ?' m& ~1 d7 e* U
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
9 b3 y) B6 N: [  n! S3 p( ^+ h- M) l& i/ N" Y5 w$ p
举个栗子:1 ~' s4 z  Q/ J! e# h: x
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:4 n+ _3 b$ N; Z. L; @

4 o. ~$ ^8 u, N) t/ F' y  B' sPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
. w* r% W  H8 N& D/ V如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。: @& }% |5 }6 H6 O$ X: N: u
" }; o, d  f( K4 R2 D$ X) R1 ]& s
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
3 T0 ^. I( b) G' B$ l( y0 @9 p观察之前给出的时钟系统图中标出的9个字母(A~I):
4 }3 K# q' I' Q) V. `4 D  f5 R- zA. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
- j* W9 J) C. ^* o( V2 k" WB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;4 Q' V% A& Q3 W6 Q' P& A- N7 U
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;6 ~  M- X) X( s! @$ k
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;: I) M1 b' P2 w' H1 a# ~( O, Z9 Q6 {
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;
: |+ J" S7 ?+ ?5 j. C! AF. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
& ^* g1 h# f4 r" k4 oG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;5 @9 Z. y5 K( O$ @. B
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
  ?& F  w# @/ d1 N- I$ p# _I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
' I' D4 L$ z# L' A+ t) Z. K' p( @' r备注1 U. V, F: V! i+ D4 H+ W5 Z1 f
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。, ?: i+ ]5 _$ T( z" y- W
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
$ S! i& a0 E  M+ W9 H/ ?  t) i$ `( M1 b6 T( h4 W8 g+ F) e$ _/ _" ~! ^
二、STM32L4 时钟系统初始化配置
: Q/ {% K, F! _$ s) b8 Z在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:7 J. b+ D; Q# `3 e- l. h

% A. E7 C* }# i
  1. /*********************************************************************
    ! z# l! a8 |, f4 D. U+ _
  2. 函数名称:void SystemInit(void)/ ~/ M- O* I, w; k& A% E
  3. 函数功能:1)  FPU 设置
    3 Q' y, v. `* |* h1 X
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    ) @& o3 e8 a! e7 S: O1 Y0 b' K. H
  5.             3)  中断向量表地址配置 , T: w* Z5 u7 l6 K" F
  6. 入口参数:无. s3 V" H" r* S/ Z8 I$ Z
  7. 返回参数:无. v- P8 T0 l+ @  F% j% ?
  8. **********************************************************************/. e5 B4 V; k5 _& F* N' j
  9. void SystemInit(void)6 e6 g' j6 b' U* E/ L  e
  10. {
    - }' A* g+ z2 H, L% G
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1): }5 h7 D3 S4 R$ _$ ?
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 % D1 @& `" R+ g: n
  13.   #endif: g; y2 R5 P5 K" M$ Q
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/ + i0 _5 q2 b! K( R4 H/ X( x4 ?
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
    9 b: q- @, `9 l  D# E& j9 }1 y- f1 s
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器 8 \  G% ?' J: [% q& I0 g1 D
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           5 Q" z- f6 U1 s! W& l! _
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器
    ; b7 o5 U* R* y
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
    ' j6 Y$ ?9 {0 i- e* e
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断
    5 j- K7 o& V( Z  m# F0 O5 n
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    7 H% _% e) `& d& u
  22.   #ifdef VECT_TAB_SRAM
    ; w7 I. f: C$ K, d$ u+ T
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
    ( y( Y, j5 J% O- q7 d
  24.   #else1 w" H* {7 e  m; U4 z
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    ; K% }7 ~7 r6 P9 C
  26.   #endif
    : O- E( V* A% [( T( u
  27. }
复制代码

7 w& ^8 d& t; y( U: `; XHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
$ k* Z' Z' g! `3 o/ A
! k' X; H$ h; E& c1 X7 ^6 K
  1. /*********************************************************************9 ]7 |8 B: j1 F0 V& |, C
  2. 函数名称:void SystemClock_Config(void)
    ; z" X9 r4 o! Q* `, M% c
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
    ! T% f9 |  B" q1 V7 C
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    7 f# b. G/ p3 L  M, u" t3 c
  5. 入口参数:无' n3 \% C  Z8 Z2 K0 y, q8 B1 g( R- P
  6. 返回参数:无
    3 R2 R) E3 @3 f5 f) m6 M
  7. **********************************************************************/9 C1 b0 E! J' s
  8. void SystemClock_Config(void)
    ; f1 i. Z5 Z) H5 }! q
  9. {. t" W: \" U3 T, x
  10.     HAL_StatusTypeDef  ret = HAL_OK;
    " L/ B5 E0 [6 L$ m; \
  11. ( K+ P- z1 V! V  _+ h+ d6 H( H: i1 u) m
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;
    : H- P- g, N7 k* B
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;' r8 \6 @: |: l+ J; v

  14. , `+ M7 W, ^- E! O/ H1 `3 B( o
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟$ K( L3 N9 P4 W7 Z
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    & Z6 P; L+ T; ]! t: ]7 B1 \6 p
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE " B8 b# \3 C' E& L0 L
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE % o/ p+ y* q7 F* `
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
    9 Q0 W/ l+ B7 e
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    7 ~$ i3 I2 Q% r$ y' B5 f
  21.     RCC_OscInitStruct.PLL.PLLM = 1;
    / b5 @- }8 K7 [) T, i2 f5 E- S
  22.     RCC_OscInitStruct.PLL.PLLN = 20;
    " F0 z5 `4 t- O9 ?: ^
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;0 h0 T/ k8 p. q* ?* [3 w# x* _
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;1 C+ M- A4 L- a7 u$ w0 H1 ?# ^8 [8 ]
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;5 H: c% l" [0 W5 S( |' ?8 I

  26. ; M* H2 h+ G  _3 k3 C6 L
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数3 g6 N- L% m! Y4 U! F6 Q+ Z
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ " O7 `) U9 c0 a

  29. . e8 k. k' |( f% G( o" s/ M
  30.     if(ret != HAL_OK) while(1);
    8 u% p) U) D" T9 g

  31. # V. F$ N9 ?- S2 G+ V; |
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
    - `$ N9 [- u1 d  e  E
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
    ! A6 Y' t1 r8 O: Q& y/ Z# K6 Q- ?
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 ) a; g0 ^, l! ?% D0 \
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    3 @9 P' f, a( G  |/ O$ _5 t! M& S
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
    8 }6 [2 n% V( N0 f) ]
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1) z! ~. v+ A6 V1 b9 k
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    ; S7 q& G0 b% t5 E  s2 e1 d' Z
  39. . C3 P8 M) f! Y3 z6 E
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    * I% m, w1 _" V% Z
  41.     /* 第三步配置后,可以计算出:" Q  V# D+ L/ }3 a) Z
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;: r% ]$ Q) S& t
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;7 P9 @6 Y! ^3 H% I) x( [/ s
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;7 H! |1 d0 I1 v- W5 X& {, H: M
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;, o( {: X" N8 f8 Y: S
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    ) I$ t# U5 ]. b9 |5 L
  47.                     - ^( I6 i' O' \2 }: {
  48.     if(ret != HAL_OK) while(1);
    ! q" B: z  l$ Y7 V& j3 b2 V
  49. $ n' W0 S- Y: W8 r( g; p7 n
  50.     /*配置主内部调压器输出电压级别*/
    & Q1 U8 A1 [) b
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
    ( x1 R8 d7 Z- k; L

  52. , A, B* k% D% U5 d7 f
  53.     if(ret != HAL_OK) while(1);
    ) X% f9 [) E% `
  54. }
复制代码

; ]% y. t2 \2 L+ A6 ^函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。
$ \1 G' p6 h" z+ _, y8 [使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
9 P- z  }3 S. ~$ J% ?( G% I1 x* K9 Y! f# R9 U
使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;) a' i$ R9 d# V$ d- e
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。/ G% t7 V, o1 E/ x5 |6 c. {! G7 f
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
! c* {& }9 p* g6 q6 G& s! Z设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。8 b$ I, Q* [9 [! u% }% s
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:. w/ Q2 d2 V7 n' ?& u" W5 O; }2 {1 Z
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码

/ s# C0 U3 y( b6 R/ U" w- ZHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
) j1 C4 E  P* i9 k$ Y2 l3 g7 y4 p% S0 j" t, j! I1 _
  1. typedef struct
    ( f( N7 i; O2 [7 |% u3 k
  2. {
    : c, |3 ?% c, B( ~, d* G: j7 A) f
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      
    7 Z/ J6 ?1 v! q; s. i4 O
  4.    uint32_t HSEState;            // HSE 状态       / z5 V- B' _6 b, H
  5.    uint32_t LSEState;            // LSE 状态                   ) Z- Y# f, b' f7 ~: r
  6.    uint32_t HSIState;            // HSI 状态      
    ; o/ m) a1 w0 U! ]
  7.    uint32_t HSICalibrationValue; // HSI 校准值 ) ]0 p. s6 Z3 ?2 m/ Y1 N
  8.    uint32_t LSIState;            // LSI 状态      
    ) k2 v* |7 p* ]8 f: `/ L
  9.    uint32_t MSIState             // MSI 的状态  3 h3 \1 Z, K2 G
  10.    uint32_t MSICalibrationValue; // MSI 校准值 ! t2 J, s2 g. B! X. K. P2 x" K0 [
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  # v  [: P% s5 {/ z5 L- |' ?
  12.    uint32_t HSI48State;          // HSI48 状态
    5 a, n. I1 v; u/ f! I
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    0 r5 P* [0 ]! k* n+ R* @# X$ v
  14. }8 e  F0 I% C4 c. j2 ?& G$ q
  15. RCC_OscInitTypeDef;
复制代码

$ o2 t$ {- l  o/ r! S该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。5 w0 N; Z# \6 Y- n/ N
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:- [  d! B8 ]4 H: `1 j
6 P0 D4 {/ _) B
  1. typedef struct
    / k  Y$ S; D) P
  2. { 2 Q3 f6 l5 u* g( d$ E0 a7 I1 ~
  3.    uint32_t PLLState;     // PLL 状态 % \( t2 O! H  Q- L3 y
  4.    uint32_t PLLSource;    // PLL 时钟源
      D( f1 Y) \) Z
  5.    uint32_t PLLM;         // PLL 分频系数 M
    3 v# `7 y9 O4 o+ y5 Y6 Y  o! L
  6.    uint32_t PLLN;         // PLL 倍频系数 N
    0 c; n. o* ~2 \" z- `
  7.    uint32_t PLLP;         // PLL 分频系数 P , e  L. [! d; N0 V- K
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    1 ?/ J* Y6 I4 A
  9.    uint32_t PLLR;         // PLL 分频系数 R
    0 h: e- U( ?" ]. K' |
  10. }
    6 ]8 I4 D3 m. C; N) k* k
  11. RCC_PLLInitTypeDef;
复制代码

1 p: D4 @' Z+ w: e4 N该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
! B* c1 [' P3 Q5 q
: O  Q# x. [& c! Y( T对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:+ h6 b6 ^9 W( k+ f4 w# s
5 Z: U( W+ X. T8 k9 b2 [
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码

! C$ z, Y. {6 G7 o6 ?* K该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
5 H' L$ b# }4 o( y! b& a
8 C; K& c2 Q+ U7 ]对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
5 W- K# t0 D3 x/ p0 M延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。1 `  e5 Z5 F5 y9 j, h; }
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:  D2 ^' q. p! t- K, h6 p
+ \2 S) y8 J: {0 }( X
  1. 位 15:14 VOS[1:0] # @, w2 T% p5 Y
  2. 00: Cannot be written . K" P9 O5 R* I- I
  3. 01:Range 1 4 }* Q- Y. |. t9 l7 P& X3 k
  4. 10:Range 2 9 E7 W$ J- K$ w
  5. 11: Cannot be written  
复制代码

9 l/ G: v0 E4 G6 L$ A0 n3 b# ?* l1 o* H级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。5 G; j# M4 h' |4 Q8 u" f+ A; ?6 L  @
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:- ~: q5 Y$ i; S* J; F
7 Z: B, @& `2 L* `1 B  r9 a
20201205181115783.jpg

) x! j- T% o5 k+ W# |6 m% T
, p( z5 j! l& X7 @可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
* W" i: x1 V. n/ n4 N% k0 q  R# [& q
三、STM32L4 时钟使能和配置8 x4 q8 J7 s. p7 p& k/ l9 |  B# ^
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。
6 R$ [. g+ j, f7 |" x( L
4 R$ [1 f( w; }9 V5 c1 ]1 T/ f通过STM32L4的HAL库使能外设时钟的方法' v" }6 L& h* Z( S" M
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
( v8 t, p5 k) [  u8 m8 v5 m举个栗子; q4 ^$ {# l- `6 ^! I
看看 GPIOA 的外设时钟使能宏定义标识符:/ v+ ]( w9 s0 ~8 c' j3 T' {6 o
0 ~; B& D4 S7 O4 y  m
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \
    ) y! w, _/ s! f, f2 u! v+ O; A
  2.                                                                      __IO uint32_t tmpreg; \ $ W" J) P* [. i$ e2 P6 _
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
      y  P4 v1 e  @/ ^
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ 9 V- i" x* F! _& Q1 E; p
  5.                                                                             UNUSED(tmpreg); \
    0 P6 G2 H/ U3 Y  c
  6.                                                 } while(0)               
复制代码
+ B# @: I( ]' v" Q
/ V* G* O+ F% d1 o7 I9 s
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:  l9 O4 x$ ?' p" v" \, O0 ?

/ r, T4 G+ \( `" h
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

, }  a  s, g1 M这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
; n) g- r' p6 S+ i# K" K2 ]2 M( f& x; {( b! d
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
    + G/ {9 r6 e# F1 @) W0 M2 |
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
    1 P* H: _4 h  t
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

3 @/ ~8 a& ], S上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
7 q7 H7 m' ^; M' t" {描述如下:
) I% t: `2 v, `
5 T- l0 e) G) h
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 1 r2 K3 \4 v# [7 c2 D
  2. Set and cleared by software.                       // 由软件置 1 和清零
    + y# q7 r0 s0 t
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟 * }8 o% S/ k# r" p1 [! P
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
. e5 Y7 x0 B$ B7 V% F* k2 O
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
# g; @2 _1 p( b6 Q! i2 I
! [9 D1 o( a# V+ I& }6 ?
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码
7 r  s. x5 n& t3 v7 A- B
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟 * J+ H- u& T6 G8 l8 \
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 6 z  X3 `. x: N/ R" S/ i! w# d3 Q
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码

) s3 r/ y) j! ]5 r& L4 b使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            
    $ n, {+ w8 d6 S" o& I& j' K
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
- y$ b- F5 j) M8 J# R- r0 E8 q
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
6 e; i. h1 T- Q9 q2 f) K1 B% t: [8 {9 O" e8 S4 r6 ], h
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟 1 a4 h' M( J0 I' V" k: r
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
    6 b; u" [6 z5 S; v4 n
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码
% H- @6 J* u" Y% j' _% P8 }4 j

% b4 z6 S& ]0 `7 S: a: r& g* u7 J4 G5 J( J& N2 T3 ?: h( Y

2 Q1 s! a+ O1 y9 z8 t
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32Cube扩展软件包
意法半导体边缘AI套件
ST - 理想汽车豪华SUV案例
ST意法半导体智能家居案例
STM32 ARM Cortex 32位微控制器
关注我们
st-img 微信公众号
st-img 手机版