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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容
; a" P% @6 k: V7 x1)STM32L4 时钟树概述;& m: X8 ~) `( p
2)STM32L4 时钟初始化配置;; H% t' a2 n, Z0 g" u+ o- s
3)STM32L4 时钟使能和配置。- v( m  u. F6 F% S4 D
) k  u8 Z( J. Y. P) G
一、STM32L4 时钟树概述; }" r  F- x: @8 D
时钟系统是 CPU 的脉搏。3 A- I# g" \1 {/ @/ A; O5 d
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。7 O- k, n9 r7 ?/ L8 G5 u
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。1 x' J# |3 l+ A7 S  C
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。9 B; f3 c2 \( Q" B0 @
: r3 l: W* x0 g1 X- N4 X2 S1 P
STM32L4 的时钟系统图如下:7 k. `  h5 {7 m$ _: H9 S5 O9 L% z$ a( L: Y
! n7 g; j) p' u2 H7 |- R' R
20201205162651825.jpg

: b8 v3 S; a3 |/ R: Q6 v
20201205162724607.jpg

# m# q+ f# O: X4 F6 l4 g7 n$ P
* N( G$ I) P6 o2 |  G5 N! w8 r在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。4 |/ z, p, Y# y2 X
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。( j* O" c+ p& b
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。% |# Y+ K+ W7 X
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。1 _# u" u& X" V  k9 U. e  ], L: k3 h8 ~
按上图中红圈标示的顺序分别介绍这6个时钟源:
+ o5 }+ c6 @0 k2 V① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。* ^5 c- K- @3 j: V% a/ j
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
! \3 X8 t- J/ E* a0 [③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
) F. j$ o3 t8 {0 V: J% x: l④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。
3 G6 h& q0 ?. _' ~⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。) s- I7 b6 M8 F9 s, M$ `! A1 a
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
; f' X0 z+ ~5 G, M1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:0 N* Z8 L1 Z6 @( _) g
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
  f1 F- I; @7 }3 z9 H, S! \; m( {  v& I第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
9 B$ X! N2 [4 P# s第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
. f; @% A5 R1 C) y( q1 S2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
; V) d( Q3 j: ~4 S: k第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
/ C  N- D6 M) Y; F, ]+ A" B: J第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;. m* Z) h- n5 j+ a
第三个输出 PLLSAI1R,可为 ADC 提供时钟。
3 k, X$ N& J4 J" b2 d# l* q- ^# c3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:$ o" G# ^8 X( V4 d. ~6 i
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;% U5 I1 R5 J+ q# E, I; L
第二个输出 PLLSAI2R,可为 ADC 提供时钟。
; t  ]  N2 S+ r5 P5 I2 c: c
5 N0 X0 `1 t9 I* Z3 i5 h  s这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:' X. J9 [6 A5 C2 a: f5 P; M! E
20201205163714587.jpg
( S( e0 z/ O) Q

  A9 m% p; H- v上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。/ M; L+ R$ p. m  d. W* E/ J

& N* {3 f; s1 G+ K0 d. n& |举个栗子:
0 v$ I# c; [# |外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:) a( Z! m; u# G2 C0 m5 B/ d

; Y) F/ |+ u2 S8 c- f4 cPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
: n# d2 H) U: {# o& y如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。; a+ a0 {1 H# g+ P8 B

: X0 m( c7 e: N" P2 L下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
/ Z1 \) |+ Z! V8 y观察之前给出的时钟系统图中标出的9个字母(A~I):
1 s2 K9 H- P7 ]3 e; b9 ZA. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;5 O7 Y/ O( A2 z; U# h
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
( z# n: G" e& Y& e1 p2 g7 o* GC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
6 }$ s# l- u" ^- r) j9 K0 hD. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
2 c' u9 Y2 h$ O8 B1 d) }) ?* BE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;; C* s; B. }7 z& y
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
6 F, g/ h8 T- v/ D1 G8 fG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
8 }. B, P" K3 K* FH. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;) a3 H, X  N, O2 Y
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。. K/ u' X; Y% s& [" I. ]
备注& R4 z7 Z& p: x( C+ `
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。- t+ j6 o1 s# E' D/ t( ]. ]5 [
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。( u3 [. U( P) ^; B9 o: m
# q7 q; J& n6 q4 }7 ]
二、STM32L4 时钟系统初始化配置9 {% Z, I* o! ]$ V
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
6 q0 `0 ^/ B6 d6 `5 C( x
- x& V) }2 K! J3 i# l$ _. S% ^
  1. /*********************************************************************
      O+ H; z/ ?% g% k6 N1 h
  2. 函数名称:void SystemInit(void)
    5 H) ?1 v8 g) x' J9 r
  3. 函数功能:1)  FPU 设置 1 x* B0 V7 F' Y- q% f
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    " [7 o+ y4 M; H: p
  5.             3)  中断向量表地址配置 " e. r2 X7 r# F# ^
  6. 入口参数:无* Z" v- q& Z  n( D/ \# g4 F
  7. 返回参数:无
    " U( ~( V5 ]' v0 x3 @4 d
  8. **********************************************************************/$ S' s9 C9 N" J( Y# u/ h
  9. void SystemInit(void)
    ; V6 y* H! H6 Z+ y% c
  10. {
    " M( G) B  C& R! ^* |+ l
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    + P/ A' r6 l3 f5 m
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
    ! t8 e' M0 ?5 D5 o, `0 m* h5 m- q
  13.   #endif+ W# `3 i& W! M3 W1 \" @
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/
    7 U, u: D  ~1 z: O3 @$ F- R
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位 ! b- R" W- L4 |+ ]
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    7 p/ W/ B7 V2 b0 i+ U5 U  K
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    0 c! U/ i, h" H7 S; T: N
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器4 M1 `$ ?# _& u( f" J0 \( ^1 _
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位 9 z+ T/ e9 I; o& `; }* @" D
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 ! C( L6 J# r) a# L& I- _1 p
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  5 @6 l& \; y. F7 z$ Z' S) c# S
  22.   #ifdef VECT_TAB_SRAM0 e$ `5 G( ]0 i# B# e6 K
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */. I# O+ H( J  T0 e2 Z& v( m
  24.   #else
    $ L' D2 E: b7 r- F) ~* `' c7 c
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */  M1 b) R* \! H; H7 i
  26.   #endif, j- c9 H# l4 Y# E
  27. }
复制代码

3 H  K* d# `3 u  Z- x3 [5 {% b2 jHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
7 v1 _+ p. p" Q+ A+ B$ X
+ i4 l# C6 O! ^( B
  1. /*********************************************************************
    0 e1 L) Z3 e2 i0 d9 l
  2. 函数名称:void SystemClock_Config(void)4 ]( \6 F$ G: e* I9 J" X
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
    5 y. I4 D: j7 R3 @
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    ' S5 \) i8 F2 \% V) f
  5. 入口参数:无. X3 W8 `9 y7 O2 ?6 v
  6. 返回参数:无% n) H% Q' {  ?% a4 p+ R
  7. **********************************************************************/
    - J) l$ y0 V* h9 n" W
  8. void SystemClock_Config(void)6 ]* T5 r1 V$ e  R- R# j$ P
  9. {# ]9 ?& T" b, @6 o' {) y' \
  10.     HAL_StatusTypeDef  ret = HAL_OK;0 t+ a; X+ V" Z3 j5 i$ K
  11. 1 b* c% r/ K' N+ w6 x
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;
    ; H1 N; x1 C; O+ |, s
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;+ D/ q" g. y7 Y+ t- S$ j3 ^0 E
  14. & @7 B) Y8 V5 D6 I
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟6 V0 x! E  \6 }' U
  16.     /*Initializes the CPU, AHB and APB busses clocks*/$ v" \; b0 h$ _6 L* O; X
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE
    % y5 Y* U5 ]  y8 J( h$ F
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE
    ' h2 r7 S+ E* {# y4 ~0 W
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
    7 S4 X) N& [3 I9 N2 H) ]9 a
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    . B( \- ?- H5 M) J
  21.     RCC_OscInitStruct.PLL.PLLM = 1;- f' m6 K; f: P+ W$ Y2 Q0 J
  22.     RCC_OscInitStruct.PLL.PLLN = 20;1 G) e; {! n+ z+ m
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
    6 t! Z+ A' E+ K% e* o5 A
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    1 H3 W4 ^9 j+ L3 Z2 Z5 d
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    * n) n; m* z, ]& }$ [4 T

  26. 6 H! W0 x# z+ L, Q: _7 c
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数% C0 @3 C$ _8 ?: D, K. c
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
    ) v( G* F) K! v" C1 ~6 y, Z

  29. " e2 |# L* q8 c. f: [$ ^( a1 B
  30.     if(ret != HAL_OK) while(1);% D2 J- H1 Q6 u/ }

  31. 2 {! a" n+ |% b: L! P
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ + q7 e1 K' ^, t2 N9 y8 h
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK. k* |' K) w: |0 N
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    % g) d0 q9 ?8 U
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL/ p! M: c! W$ h: r, g
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 14 N$ X* V/ p) Q
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1
    5 V# d1 ^5 W1 D! S' J8 E
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 10 U3 r6 e# N; ~& l2 e$ Y- \
  39. " W# \- u1 K6 `6 N8 }
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    , D; v2 [7 _! S4 o
  41.     /* 第三步配置后,可以计算出:8 R$ J! q$ O1 f2 Y; m
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
    ) I" i1 \6 K6 f
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    # }2 Q8 x  }% W( w
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;8 p; q" V" \) M2 N9 i6 A3 @
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
    & c* M; a9 ^0 c7 G' L
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/( I# [$ E& \* _6 ?9 A- K
  47.                     
    . D0 x0 d3 M2 s, C
  48.     if(ret != HAL_OK) while(1);! ]& E+ j9 [& e

  49. / I1 ]% c3 s9 H4 d0 w8 F% m
  50.     /*配置主内部调压器输出电压级别*/" \# U! r7 ]2 V/ H
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
    # ]" ~+ G4 o, s7 G: O' |: n: |8 c

  52. / K' `, x, {" }4 ]8 ?( [
  53.     if(ret != HAL_OK) while(1);
    + Z" z$ N. g3 r3 p; B* {* F
  54. }
复制代码

+ t' \* f5 a. s" R9 j0 m3 Y3 b4 t函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。$ v, E5 N) k, P" Q7 F- v
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:% a8 M! S# x, e% r9 b3 R
' G$ r+ K6 M( J" e
使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;3 ~7 m* y- M7 A8 K6 a5 p4 S# p
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
) I; |6 l  N6 ~0 r1 N- j3 A5 V  R配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
, y% s( I/ A! {) ~+ z, Z$ C设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。7 e, @* F1 \+ E$ j. D: D
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
9 n1 R' s$ ^/ O4 O; X% g. M
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码
& }+ \! P& Q/ d$ ^+ `4 N3 ]' Y- {
HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:. R) i. [% H+ ]  Q+ o4 i
. Y2 A  A- w) P! p/ @5 L3 T. D
  1. typedef struct 5 ?% Y( V- H+ Q8 ?5 f6 U4 i# _8 {3 o
  2. { 2 ~( g+ c+ D) E7 x4 t0 F6 n  R
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      
    - i1 V0 I$ Y5 V" j4 [4 b
  4.    uint32_t HSEState;            // HSE 状态       $ O% q7 u  E8 D( }* X
  5.    uint32_t LSEState;            // LSE 状态                   . ]* E0 F$ Z0 f4 y3 h" l1 r$ a- |- ~
  6.    uint32_t HSIState;            // HSI 状态      
    ( N* V0 i; |! L
  7.    uint32_t HSICalibrationValue; // HSI 校准值
    ! r6 _6 j  O+ t( x5 s
  8.    uint32_t LSIState;            // LSI 状态       ) Z( |2 ?  T7 m4 `
  9.    uint32_t MSIState             // MSI 的状态  & J, e9 y- x1 _8 C" Z- T% |( v
  10.    uint32_t MSICalibrationValue; // MSI 校准值
    - l( }+ g6 q* B0 A
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  7 a$ C5 E- U0 t' h% }9 v" X; P5 D' k3 c
  12.    uint32_t HSI48State;          // HSI48 状态
    0 S% J. t2 T1 q- t/ V
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    4 C* R& Y1 m8 R  T$ Q
  14. }
    6 F) q5 O2 M- T5 ?5 f& I# h
  15. RCC_OscInitTypeDef;
复制代码
6 X; p- L# s/ E7 u) M
该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
1 J# n  l9 l& f5 h/ {3 e该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:& r& [; V7 H7 c) Y8 V

8 L1 R1 X9 s' g, r2 I' ~
  1. typedef struct + P# ?  H4 i3 s9 A6 a
  2. {
    3 B: K+ t3 b5 Q" }( ?' t
  3.    uint32_t PLLState;     // PLL 状态
    & p4 ]; r: A, K: G' T1 l, B
  4.    uint32_t PLLSource;    // PLL 时钟源 5 r2 Q- }; Y) Q+ ]
  5.    uint32_t PLLM;         // PLL 分频系数 M : r, R0 T( q1 T5 Y
  6.    uint32_t PLLN;         // PLL 倍频系数 N
    , L* e( y- z" f6 ]8 B% r
  7.    uint32_t PLLP;         // PLL 分频系数 P * J0 j/ \$ n$ x7 |! P2 W$ i8 U% v
  8.    uint32_t PLLQ;         // PLL 分频系数 Q 6 y+ a, |6 y. A5 }
  9.    uint32_t PLLR;         // PLL 分频系数 R
    9 @* v& b& s2 r/ C% k
  10. }
    7 S1 T% }* N5 c$ q
  11. RCC_PLLInitTypeDef;
复制代码

; V; f4 |: Z: b; ~该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。+ h& d( ^# U+ d" i2 n2 t+ q! d

* y- [( i2 Y: w1 e! v对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:* _/ a  k. l( H' m8 _, s
0 ], c; {6 m: l  Q# j' u& V; e% J+ F4 s
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码
2 R' w! E! P, [/ M& t7 d
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
; Q9 W  R6 v$ }2 j! x, A$ l9 N9 d/ a, g# U& u6 M9 h) T# Z
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的4 s2 ?4 z( j& Z4 P5 _+ ^$ M; G
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
4 S& [$ ~( W- U8 e- x3 g首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:- I  b% }* r4 S6 Y, ~" D' u

9 \( \  B7 g  b9 ]2 R& f
  1. 位 15:14 VOS[1:0] , z: s# R3 D- B* ]/ p4 g
  2. 00: Cannot be written
    3 a  ]1 p5 }9 Q# V* S" s' D6 K
  3. 01:Range 1
    7 H4 \1 `- n3 d! o" P
  4. 10:Range 2
    . V- Y* ?  y$ @6 P0 }8 q, `
  5. 11: Cannot be written  
复制代码

. e5 s5 Z4 J! S1 _) H- H级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。8 W+ T7 N( k5 M
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:( G8 P7 O7 {, h
7 q6 M9 H: m5 `1 |& Q' O# u( P0 ^
20201205181115783.jpg

7 {4 B7 U; I9 @( H( ?
! R( k, b8 [. Y6 Y" W( D可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
- u/ w2 z& P/ s3 c2 k+ H5 p* _+ N( r5 s3 S% G7 O
三、STM32L4 时钟使能和配置" m5 `  _/ J) L9 s2 r+ N& w
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。) O6 P' [: Q/ a' q+ a6 F: n; ^
- C: {5 @2 y4 D  x' z. s
通过STM32L4的HAL库使能外设时钟的方法: r3 J' F1 }6 P* Y3 S: G
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。* L# c1 y( Y2 ?. |- b- `* Y
举个栗子
5 R; Y+ X: ~$ i- p/ O看看 GPIOA 的外设时钟使能宏定义标识符:
5 f4 Q5 S1 f$ d
% R. t6 m  m% i
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \ 4 k5 y- R- f' h) s
  2.                                                                      __IO uint32_t tmpreg; \ 9 i+ z( |% E4 \  u! Z
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
      \2 E" t) V2 z8 F  x- N# N3 b
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \   H3 ?0 y. R; a
  5.                                                                             UNUSED(tmpreg); \
    : |8 R. s2 |3 `3 R6 T( I; L
  6.                                                 } while(0)               
复制代码
- o; U# Q( ]+ e. v
' f! v! C1 q) V- g. ^
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:2 v0 k7 r- B' n1 }8 h* d

* }/ k( e& t2 T; f* d7 Y; E$ O
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码
+ {# A+ j9 N% m) ~
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
, K, P8 b9 e' r1 T! a5 b
/ t5 d: `1 O9 _, S
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
    + E5 w( E. K" ?
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) * Q, _! s) x% ]  r. f8 A3 R1 Z8 [
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

* _: y, e3 h. W! M4 a  B上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
9 |/ I; Y9 k  s8 v4 M8 O4 |描述如下:5 m) Q) H. T8 q# _2 w- X# r9 G6 x! O$ M
2 M/ _& a' a& `3 T& l) }$ n) g: Q
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能
    - r" d2 t5 e8 D
  2. Set and cleared by software.                       // 由软件置 1 和清零 6 {) h, ]& [" s  A& I
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
    ! J* ~0 B& q9 L  A* h) @
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码

* M: q5 Q$ z& g! U那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
' D' m; N! t$ o9 T9 T
- I- g* I/ g$ p% f
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码
2 G7 i, \+ {/ G; s0 g/ L8 D
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
    5 y7 F; d7 k5 I$ |* m9 E' f% ^4 {
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 . i3 B  P+ J) O! ^2 F
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
* d; w2 b1 z+ U( r1 s  _
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            $ W: a; _5 w- d+ F- K$ s" A( D
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
" K+ v- h9 i( c8 D* [- i
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:6 R6 W# i! I( N: A& k- J
* g/ a) c; O' r: G) O
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    9 e+ h6 f/ c1 z  k" d
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
    1 B1 M% P2 i$ C1 ?1 L! ~
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码
6 Z% T! H4 P7 l5 t; n

7 t0 G/ f' U1 L2 t. P1 h1 V  L* v8 T( n6 p' b# ^/ |3 N1 a

  `2 ]2 Y' F. \7 K/ x9 N
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似分享

官网相关资源

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