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

STM32L4时钟系统简介

[复制链接]
STMCU小助手 发布时间:2022-11-22 13:51
主要内容' Y7 G- g& B$ P" u; H
1)STM32L4 时钟树概述;9 e) [5 B0 j/ W' ^8 \4 |- G
2)STM32L4 时钟初始化配置;2 c0 M- K% L1 H; y8 o" M1 u" x, c
3)STM32L4 时钟使能和配置。
# }: T! r1 T6 V4 I. a
7 N# g/ g7 j/ q一、STM32L4 时钟树概述' A, W7 e7 s( {
时钟系统是 CPU 的脉搏。2 J. g  }+ P, H. m: X
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。# b$ k/ Y  ^7 ]* l
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。$ w+ L; }; n/ |1 E6 i
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
4 Y  W# f' U6 X9 x$ n4 B; p; y5 _
# z9 Y6 T. A4 g. QSTM32L4 的时钟系统图如下:& y! A" q. K+ U0 Z/ {6 F9 b

; ]9 o& V% V2 } 20201205162651825.jpg 20201205162724607.jpg 3 j) T! o+ U* w# p5 w

) C# [& K% S0 v在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。! l9 s0 k8 l3 J2 ]& ]! d0 H6 e
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
7 g9 j9 b$ B  n按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。; H: [! |/ I' W2 i" W
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。; h- x7 g$ G3 N6 b- L# C$ `8 E7 j
按上图中红圈标示的顺序分别介绍这6个时钟源:3 g- r( W9 J# N' h8 ?3 k9 Y
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。- n. U+ |2 w$ t! v, `
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。: G, e1 f0 x: F# R
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
* q/ W  X% z  t9 l6 N④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。  {* [. c* j! N: h/ O; {
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。) O# P* u& w: T) z2 F5 L
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
! h# y. Y6 V+ X* x1 l1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:. J' H1 e7 H8 g# p
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);! A3 c. b1 d: `* Q9 Q2 n" @
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
. F$ g. J: ~5 l1 u+ p3 }  f1 ^第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
, w; F3 R0 _: J$ ]2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
; m* j7 K" r* W0 t) R  `' k5 o第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;+ D+ g& Z' y" N
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
$ K5 f/ ^: r6 D  ]7 k0 ~8 R8 I* K第三个输出 PLLSAI1R,可为 ADC 提供时钟。
# [- Y$ V5 X0 Y+ \3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:. W1 @$ }# {* r& ~
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
8 q- u4 b9 {! i第二个输出 PLLSAI2R,可为 ADC 提供时钟。, C$ T% c; d7 o& n' L4 ~
" v/ u& [6 p5 \
这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:, Y3 D+ f! w2 ~
) s0 h' f1 j! S: T8 n- Q0 P* H
20201205163714587.jpg 0 ^" `8 V; x, m
! v- h- `+ R% P+ k+ ^
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。1 p' L' k! V5 g* v. E/ `
) |" G. t+ x3 E5 ~/ g: t# K
举个栗子:3 Y0 A% {' Y6 T9 j' Z
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:5 B- Y$ ?, n2 E$ S7 D9 ]
PLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz- n. `* e* ~9 V5 E  V

! C8 |: A' g( u5 g& r8 _如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。
6 b0 x3 Z; m+ S3 P1 u" {: _/ R" Q
( b- B. G* S; U0 @- O: s下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的% T. P% }/ z$ c, R
观察之前给出的时钟系统图中标出的9个字母(A~I):9 ^. ~4 W" c. b
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;) q5 a; {- o% k) {" N+ `+ r  S
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
, W! [! Z! v/ B- @$ @- BC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;; D: K  }% t. e  e
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;% Q0 r% g& i6 ~
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;6 N9 D  `& u5 h/ P8 F1 P3 M
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
9 O8 G7 Q) p0 t5 VG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;, ?% ^. d- s- E
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
; G3 W5 j& ?2 gI. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。+ ]) v, Z" x1 u2 a3 R0 i# e8 y

! w: r6 z" F  V备注
" j/ l3 l2 u/ i# A1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。  Q4 `; z, Y& Q7 P& j
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。! R' z+ v8 q  U) }: ?( R
. }" n* u: f7 ?; o+ Q& s# }$ e, ?
二、STM32L4 时钟系统初始化配置
" b% Q7 C" M4 U/ F
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:4 F1 r3 w& E0 O- X

, I. a+ Z. t. s5 d6 y7 x. d4 K
  1. /*********************************************************************
    & p8 y, j: o* E1 Z  O" p
  2. 函数名称:void SystemInit(void)
    : _& P* k" y. p0 B* t
  3. 函数功能:1)  FPU 设置 5 V6 F( p  f2 \7 v8 [
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    0 p  K% V3 {: c7 @1 d7 o
  5.             3)  中断向量表地址配置 $ d! X; s6 n* I, @8 R; @
  6. 入口参数:无
    : [4 V9 H" p, t& S0 N+ u
  7. 返回参数:无
    # I4 P% i# b! J$ l9 b& z
  8. **********************************************************************/
    7 V: }8 C& |0 @2 p) V
  9. void SystemInit(void)2 `. ?# i. i. Q/ X1 k8 k9 L( q9 T
  10. {
    4 w& O' l2 P+ a+ b7 j) S
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    7 G, L  r9 B" R) D. \$ P- X
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
    5 T0 ?% ]# J' U, d) S" Q
  13.   #endif% M* x% e2 B0 \5 g
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/
    ) ?$ \: ~1 k/ y6 }7 T
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
    6 D  x. h: O4 T' I5 p' I- L
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    * S2 B2 I$ K7 z  q4 Z: N
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    - N6 Z& G* ?5 V) Z
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器' E8 u  h1 G+ P4 x+ ?& ?4 I
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
    ) k/ `) B7 {, S& u  o( \5 L# b
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 5 t+ h: }% ]; N3 |/ [
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    % X8 b. q7 K! c6 ~' _! P! W7 k
  22.   #ifdef VECT_TAB_SRAM
    ; ~* c, N4 F4 T( {) o
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
    + k' m0 x* @  C* u: V
  24.   #else: a) L+ E# q, P+ Q9 ?$ N
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    1 \9 Q. z# y3 B$ h# C4 W
  26.   #endif
    ) x8 Q+ P9 ]' c1 v1 M6 m0 Q
  27. }
    6 g- s) Y" k# E3 N2 h1 c1 e  ?7 c
复制代码

5 p, z: S+ \" g& \3 ?HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
4 Y8 q, L2 y: r2 x, ~6 n1 r2 K- B$ g6 l; R/ z. a# x
  1. /*********************************************************************
    " l- [5 {$ W9 F) A6 ?
  2. 函数名称:void SystemClock_Config(void)
    3 T7 _9 O$ M& k
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
    5 B3 ~8 N( ?: Q1 E2 D1 _' ]
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M; E9 V1 v0 g5 h
  5. 入口参数:无2 k' A9 N) P+ F. N- d/ `! ^
  6. 返回参数:无8 b5 |! g- ^$ j: k
  7. **********************************************************************/' s; x) _( d+ q4 P% W, J( R) K
  8. void SystemClock_Config(void)5 f& }* Y3 O4 |
  9. {( t8 o7 L- w# |/ B, Z; ?) y$ U
  10.     HAL_StatusTypeDef  ret = HAL_OK;* k, n5 Q. z: b+ t2 c7 F

  11. * L5 \' _5 E8 o2 s; K  \+ i& m- ~
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;  ~& d( z8 d- x( I6 x5 A
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;5 C6 M5 p; Z7 W5 ]# _

  14.   D! L* o: K  P" s& [3 B/ ~
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟
    + w9 z& e7 e! t+ k
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    % t( Y: C6 c6 F
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE # k9 w1 k2 s+ e5 i9 B6 w' i
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE - {7 k" @8 C7 Z1 s
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL " D; B8 s; E. x2 k
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    + K3 x+ q* ]8 p. q! C9 }
  21.     RCC_OscInitStruct.PLL.PLLM = 1;
    & [2 s+ K  h% L2 F
  22.     RCC_OscInitStruct.PLL.PLLN = 20;
      t* L/ @8 F" D4 o- J7 I
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
    , J( ^& a, O2 ?# j$ q/ o
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;: O) V+ J7 n/ p6 r
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    9 z& @- h9 X% q0 C
  26. 1 x6 T$ ]/ k6 j4 T/ h
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数
    / d5 P, x; _  Q" g' x
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
    8 t) O6 p3 C( Z- ?8 d% g* _

  29. 7 J5 ~/ w9 w$ f
  30.     if(ret != HAL_OK) while(1);
    $ q7 `; ]' l3 ~+ K7 ?
  31. 3 J. C$ J( r5 M8 C/ K% k2 ?
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ 4 o# p% g2 H9 E- O. k/ f2 @
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
    0 V% ^- n4 j2 C6 d9 O; I% H5 \
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    $ B& ?  M% \- R6 h4 E& ?! J
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL3 S- O) x; J6 y! p+ M/ {" V
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 18 d+ l, b5 e# s8 p% n
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 13 }% L8 j  P% x0 G2 E* c! h/ a
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    ( I# z! o% v, s7 U& R- i7 F

  39. 8 M2 g0 A, {/ W
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数$ J. H* }) N% R0 _; D- G; F
  41.     /* 第三步配置后,可以计算出:4 h% `* M. y  d1 w1 y) q
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;  A' K. e/ s8 h, B4 l( Y
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    ) w4 ]$ h1 m5 c+ ^$ R& l; D
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
    " q# O1 N6 y! |) O, V6 v5 {, C
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;' ]) z: u% ]4 |# z! ]
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    $ g4 }9 {) v, U4 [+ h6 L
  47.                     
    0 W- z: l8 S4 l, l2 v
  48.     if(ret != HAL_OK) while(1);0 v, ]! q# z3 t- P2 k! ~

  49. & [4 d! G3 j1 P4 y
  50.     /*配置主内部调压器输出电压级别*/
    8 \  z; f" ?2 }" ]) h3 D  w5 o
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
    8 y) W$ g( l- U/ ^* @. q; L* d
  52. / W: T8 l" ?- r  B- x# k) H
  53.     if(ret != HAL_OK) while(1);
    8 x! z* ]6 K  E! T& _
  54. }
复制代码
/ z" S0 E. u0 y
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。3 V7 B8 H3 @) N1 P" k2 E. q4 a0 L
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:: |; i: {" a: p
1.使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
: F, t2 K1 q9 _/ E: n) B# o2.配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
% B0 Q! _+ Z8 S9 N  R, d/ h3.配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。; c+ S$ c9 I/ K* F) _
4.设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。6 Q2 D2 w/ `* Y. w" f
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
+ a( t+ ?+ g2 P, O3 I
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码

( H$ @8 |6 @' H0 LHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
) L' m# e& Y' m& H+ Y& J9 j5 X
7 Z& D4 b  S0 e& {; x3 ?4 O
  1. typedef struct , A4 ?0 r  c+ b. f* d
  2. {
    ' F5 U% C9 `+ E+ H8 j0 N, @
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      
    + T  t) h- J$ w; G- b
  4.    uint32_t HSEState;            // HSE 状态      
    # j+ p# F' c# }$ e4 c
  5.    uint32_t LSEState;            // LSE 状态                  
      ~9 {5 m( D9 |/ d4 w5 I
  6.    uint32_t HSIState;            // HSI 状态      1 h+ l; M2 Z- `% m
  7.    uint32_t HSICalibrationValue; // HSI 校准值 ) o2 k- N4 Z1 V- \
  8.    uint32_t LSIState;            // LSI 状态       " X9 e, G0 C& {' |$ ^& i" y
  9.    uint32_t MSIState             // MSI 的状态  * f8 M$ V0 s7 @3 g0 n8 Z& t
  10.    uint32_t MSICalibrationValue; // MSI 校准值 5 j0 b. s7 o& M  J4 ~
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    $ L* r( X% r3 L' C
  12.    uint32_t HSI48State;          // HSI48 状态
    8 H. y+ i* M" G- A3 B: x# l8 @4 U$ `
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置 : d  g1 O7 D2 l8 p1 d* E/ ]' w
  14. }/ \" L+ _8 q; B8 m) C
  15. RCC_OscInitTypeDef;
复制代码

( a  B0 M, l# n1 {该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
& r2 \) v: C2 m( g( U# I; g该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
' S1 l: a  k! Y8 o% {6 m( _8 W6 t) `8 ?" i. }% L+ M% A
  1. typedef struct
    ( d+ n. w) w8 I/ n
  2. { / }! _& q1 m, o0 M
  3.    uint32_t PLLState;     // PLL 状态 , ~/ y% B1 P' g. |- T2 z
  4.    uint32_t PLLSource;    // PLL 时钟源
    ) j) f1 T7 }: r$ m6 }0 ^
  5.    uint32_t PLLM;         // PLL 分频系数 M . d' {8 ~% x3 y- d
  6.    uint32_t PLLN;         // PLL 倍频系数 N / r% V1 _7 z, A, C2 t2 z/ ]
  7.    uint32_t PLLP;         // PLL 分频系数 P
    4 v; g  s. w+ V( W3 i5 q# M
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    # G% X- t/ S" z. l5 e2 x1 ~
  9.    uint32_t PLLR;         // PLL 分频系数 R 5 g( l' a+ X; h, c
  10. }. I: m7 Z5 ^: ~" O
  11. RCC_PLLInitTypeDef;
复制代码
9 S6 }4 U  L' {9 \$ l. d
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
/ ^2 o6 l) ^6 y- J! c( v7 v7 _) L5 a: H& J- ]& E4 f+ j
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
" G1 G$ P; B# Y7 J! D1 H9 G1 @3 L) b% v1 a8 U! g
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码

( J, g- A) }; M: h- t该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。; V. S5 ^5 Q: Z
/ o2 X5 ~5 u. l& |" B5 i4 S( x" v
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的2 H: l* _; H- t( H+ E
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
, v/ o) Y+ c, x% F* C首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:0 d- h9 n; a- P- h# ~/ N
( z7 o+ w, h0 \: V
  1. 位 15:14 VOS[1:0]
    + u, {0 y' Z* m
  2. 00: Cannot be written 9 U! `0 R  {( D
  3. 01:Range 1
    6 p  n) n: m0 q+ D: w& t. R
  4. 10:Range 2 % `. Q( [/ y% T: n# J% m+ D9 u( q
  5. 11: Cannot be written  
复制代码

: ?: z. Y* z4 _6 u4 k- x级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
. L! {0 M3 f" }配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:9 s* {4 v  l' m3 I7 `
& y% U+ G! E& n. x- U
20201205181115783.jpg
9 T, y* ]* Y' |% x7 E: D, R0 [  }2 b' f
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。- G% x" C+ \* t/ P, C- E( M

2 t2 i2 ]0 M4 j3 ~+ N/ t三、STM32L4 时钟使能和配置
! E  L! m3 b5 o5 t在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。9 r0 z. _8 n$ x8 F

0 A1 W+ T: [$ X2 ~. T0 Z7 S; ~, m通过STM32L4的HAL库使能外设时钟的方法
$ j* @* S& J' T% ~/ _4 o5 S在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
/ l1 ^, L9 X, h
% H7 t1 U9 l3 b3 t, f( `; \举个栗子' n0 f' b; w9 D' R
看看 GPIOA 的外设时钟使能宏定义标识符:9 x' u! X* [2 \& ~# U2 g7 O
$ i1 I  t' }; U% I+ p( P! V
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \ + S, P/ ^* G$ ?) E* q/ `: @
  2.                                                                      __IO uint32_t tmpreg; \ 5 z6 z: @/ o% V& N
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
      N4 y+ j' z$ X& X3 Y
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ 3 P; ]2 f. B* o/ m0 {1 h, T: ~4 B# g" Z
  5.                                                                             UNUSED(tmpreg); \
    , C/ L! A9 K" y0 Q
  6.                                                 } while(0)     
复制代码
. n9 {7 N- H$ k2 P
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
2 t% _+ h& ~* N4 d& \; E# v, x# r
1 O5 |5 L: V: S2 l# G4 R
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码
- ~8 F5 v: K$ z" t
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
4 Q, p& T. s$ q4 `7 ?6 k
- [' ?* b/ z" Y* k% [9 @
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U) # ?, ?+ S1 l% V- F" n% @' K. y
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
    ) {6 b9 M, O1 O" T6 r4 T
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

( T! W7 l- A8 g上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
- o4 V$ V: `6 l4 y描述如下:
5 @4 J- n; j0 R% p- P/ Y' E3 |9 m8 H1 y6 p
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能
    : P# Y- z' @* D
  2. Set and cleared by software.                       // 由软件置 1 和清零
    4 u  K# e/ I; V
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
    # r( A$ s' ^" G9 v. n
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码

2 P7 J4 I: I& F- R0 j, X9 t那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
1 A; x- U! `9 s1 H% a5 B3 Z5 ~8 M8 s
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码

% V  I( L' P2 V( D6 _& q; P对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:/ k: e7 N/ @$ A- q

* O2 c2 @, U4 D! |
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
    2 v. m+ y+ N2 y- n. P
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
    9 Y% A" \9 c7 F+ q. T1 }
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码

3 r. `. D  w- ^: E3 e使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
2 K7 Q9 t+ U2 B+ e& n$ E+ B: c" X0 r# q
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            
    / M" l7 s1 w2 r2 o
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
4 G- T9 M; \- w) ]9 w4 A6 p
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
* w4 ^( t: A$ d7 A' H. F' l$ b( c" b6 b5 k3 J! h) R& Y
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟 + `  p9 B0 B" H  E% ~3 K
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟 + R1 N+ x" J) h) U
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码
0 r: k; R0 C; K3 {
————————————————
$ L6 |6 |$ |' P版权声明:天亮继续睡/ u! `8 z! r$ D6 [9 b4 m
% e7 r+ A' L+ W) D% B1 A

5 I! K. q  D9 Y# v8 O
赞 收藏 评论0 发布时间:2022-11-22 13:51

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版