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

STM32L4系列一之时钟系统简介

[复制链接]
STMCU小助手 发布时间:2022-11-17 18:57
主要内容
  k. \9 c) |1 {% {- I1 \1)STM32L4 时钟树概述;4 a' e9 H) S' l6 R, X
2)STM32L4 时钟初始化配置;
- ~( [3 Q8 q/ I+ s$ j' b8 B! b3)STM32L4 时钟使能和配置。
5 y+ W+ Y' m+ |4 G4 X! ^
$ n. r( y, n2 r; ^# x5 u3 t一、STM32L4 时钟树概述% l) a: w0 c/ ^. [  _
时钟系统是 CPU 的脉搏。$ ^$ y1 Y/ N: k& V7 W& d
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
6 f& p+ g$ |1 T同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
: A! ^& Z% a& f1 H3 ?/ l! @: ]8 I所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。) `0 J" {" c/ Q1 `( P5 R

) }3 P6 h" p; }STM32L4 的时钟系统图如下:, U& E9 }, j9 S+ h  P2 _

1 b) O/ u6 Q$ d2 Q 20201205162651825.jpg
8 N- r- _+ b8 F4 J0 B 20201205162724607.jpg
! O/ M1 X$ |- H. T( J! H5 F! u# H# M, f9 n- f' e
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
5 G5 s9 K, b( B0 ?+ t其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
" p* V7 ?: E4 Z  q: D' o3 `& H按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
4 `1 T7 Y9 o4 P按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。' g. G' z; i  q5 V' Z
按上图中红圈标示的顺序分别介绍这6个时钟源:$ J. ~8 M" x2 S% g- O/ ^1 V
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
  [' F+ p; r4 v② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。1 l; n* T; z% A4 P0 n! q
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
# p: b" O: b7 l) q9 a  S2 [3 p+ a④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。% x8 |6 ]8 h& t6 z
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。  Y8 z* z& V% @* ^0 N0 l
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
+ N! P1 Y; V% e1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
; F9 T5 u! I. B第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
) h( N6 V! w5 F. |1 i第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;( p% t. Z9 g+ m1 k: m2 q  g1 e
第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
4 f  U( \! a  H& b2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:) J& a: O/ k: l3 W% D: b; b5 \
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
  Y! D; ?" p: q5 G9 X第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
( [! \  H/ F" ]4 V$ a* I第三个输出 PLLSAI1R,可为 ADC 提供时钟。& U% F0 Y4 P0 l5 j
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
/ \5 c1 @6 t, ]5 a第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
. o, Y, T( y3 G/ ]; a9 D第二个输出 PLLSAI2R,可为 ADC 提供时钟。
4 W; I# D9 y, f" h4 {: @, m5 ~. Z/ h% n1 j3 ]3 m3 [7 U& g" e
这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:7 m$ S- Q. `: s0 h. ^5 o( K
20201205163714587.jpg 1 I: X9 j1 l" }9 m7 h) u# F
0 O: k1 ]; ]; `6 m
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
" ^) {! A" ]! `5 ]. S
5 K/ I0 F3 n7 [$ f举个栗子:6 q' B  D; {3 Z9 l, l! n
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
5 [: l) L) l- o2 OPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
0 H1 _$ x' m$ y; w2 ]+ i/ b
0 [( X! x8 _& w9 f! _如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。
# p; ?) K" C& P3 k( y
$ \! T9 A  A+ _  \  t9 I6 z下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
& H- G9 ^, d$ g( v  I: T观察之前给出的时钟系统图中标出的9个字母(A~I):1 v- ?4 _$ ^6 Q8 n( L: A
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
* h# ~6 Z3 ^# v5 j' p; n; fB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;4 S; d% g- H" u" T: X
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
) k. i/ h  g' F! G3 gD. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
; h6 b4 u6 z7 ]E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;( F) u' q/ z# H. E6 A0 X4 [- Z3 F8 K4 d
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
1 C, A; r" ?5 w. I8 D/ V* M4 ]0 YG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;% z6 m) G# }% J+ o5 V4 f. v/ G
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;! w' C) t) J% W/ C8 \
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
' i/ d- |- s( Q1 r+ [1 ^& W8 @4 q
( E8 W' p$ y5 y5 o4 K- n备注
+ O  C2 Y" ?' T# S( N
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。" f+ \/ w8 x! o
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
! y$ w) W" Z8 j3 l8 n- b% v+ \# X* W4 A! O4 ?, \
二、STM32L4 时钟系统初始化配置- [+ [% H6 _! p$ I: v$ Q0 Z
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
$ a% B; ~$ ^" o3 a3 X/ W( d6 P9 a/ {" S2 N% R
  1. /*********************************************************************
    $ d; i- W  K0 I0 l6 D9 `9 Q
  2. 函数名称:void SystemInit(void)
    8 Y2 ?! `: ^! B
  3. 函数功能:1)  FPU 设置
    & T% f' p4 x2 R* u  |# v
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    0 Z# r' d* ~% a, o" s" v3 l
  5.             3)  中断向量表地址配置 9 n" b6 U6 a% o  L6 }
  6. 入口参数:无
    0 q/ W+ o; Z) t
  7. 返回参数:无
    ; j% v4 C7 Q. J  F* R2 C
  8. **********************************************************************/+ v( A4 y- d* P, E5 j) B4 n7 g" C
  9. void SystemInit(void)8 x0 c; X; O4 b& d
  10. {& R/ |& e5 t% I, {
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    & N/ c1 M: T5 W1 L! c; U
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 ( v1 z- _: F* f* g: U# G* [; u& y
  13.   #endif, {1 g7 ~0 o6 ]5 |- t
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/ 6 r- M; V9 u- {% Q# A
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位 : D. T3 f; Q$ Q( t8 v% I8 \5 w
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    * t. m$ X* H' o+ X9 M. Q
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    * @% |8 C7 {4 r9 r3 z
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器% F% u. E9 x5 U. o' z0 h5 k
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位 2 @$ z7 y, n3 E4 g
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 ; g( q+ L+ h+ E) B9 [( q
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  % ~' h  @! w# y2 y, p/ ?; g
  22.   #ifdef VECT_TAB_SRAM
    5 r3 V' S6 v1 f; F9 v
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */1 B+ L. j* |1 X  _' v
  24.   #else, `" o. Q$ p  g; ]$ T5 D
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    ! P5 R! u/ \6 d
  26.   #endif
    5 O4 B$ o; I% S9 t5 k: y/ K0 `
  27. }
复制代码

, ^6 Q% I# F8 z! \# t: q1 \) zHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
& A3 o  ^% s& b9 X7 p) j( H  Y- T8 a
  1. /*********************************************************************2 q- ]3 C9 h$ g& v1 W9 A, b
  2. 函数名称:void SystemClock_Config(void)) H' G+ ?9 J7 W3 a
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR4 ^- P- W4 m. |: K$ U1 e" M, b( a
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M' x* a7 L5 n2 k! I6 Q
  5. 入口参数:无' M, Y, \7 Q7 m7 b- w. ]/ o
  6. 返回参数:无6 H; ?* i7 A4 I' l; c
  7. **********************************************************************/8 o  x2 _9 S: ^2 @. w
  8. void SystemClock_Config(void)
    ! I5 X# v. `* X: V  S
  9. {( E) @* B$ z5 _  r8 G5 x  C1 _
  10.     HAL_StatusTypeDef  ret = HAL_OK;9 O3 o+ e0 ~- U! O3 Z6 s5 ?! r
  11. " d% X% @3 W* T. ~$ k# H; M! R
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;
    & d7 _* i- O* ?- R' V' \
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;# w: h) j6 E  m8 j$ ^: T: f& g

  14. % B$ F4 R$ A1 b. ^( l
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟
    . N8 {2 i! A( [: j9 |, T8 w6 ]
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    $ x7 _# r( C' M* P
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE 4 o: h1 E; c8 d/ @
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE % H$ ?* Z  W1 i9 A4 C
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
    1 m9 _$ V1 X( z, [0 K& Z" C# s
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    ! Z2 z! `& n. N+ ]( w
  21.     RCC_OscInitStruct.PLL.PLLM = 1;) T5 L- r& h8 F2 K5 R5 \! B8 U' e
  22.     RCC_OscInitStruct.PLL.PLLN = 20;
    $ J  q2 R; s$ q3 W& V9 {
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;9 t( Q! \# @) R( b+ N, I) \) @
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;, ]" @  _8 ]/ k1 n5 }* `" \$ x
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;1 y$ u* A( Z. z; j# F, x

  26. / A* w& E$ z( r0 M3 c- P
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数5 R6 S8 T3 R# O- f# Y0 r
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
    ; X. I' c" M3 h. g
  29. ! ?" e2 c$ ~& W0 g6 L+ V1 l$ N
  30.     if(ret != HAL_OK) while(1);
    8 ]. o& W% i: f  ~, y/ f7 v
  31. * q8 P' ^7 ], b$ U' d. x
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
    7 H" J; C7 L1 c) R! [
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK0 V& f: R; K) s( u" g4 u
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    $ q& N- j* a/ P0 Q$ G) n  G
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    * H9 n0 G% w4 ]+ e  o
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 12 R; [* d- y4 o# X5 W( t
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 13 h) j' R' r& c/ j
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    5 S# e' q3 x% Y) f) E
  39. - A7 @! ~! ?( @& j7 a1 C' K: E
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    ; C  {  S0 w9 c$ B5 a7 L1 M0 K4 U
  41.     /* 第三步配置后,可以计算出:6 w* ?2 f) j. v. ?0 Q2 N
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
    # q7 ?3 N3 q' O" ]! s0 n; I0 h# S. t
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    $ K5 ~: S% C2 X% [) I$ K0 K
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
    8 v* U& l4 e# Z* E
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
    8 U) B. H3 z4 J0 V
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    ' o. S# y3 W. R( v/ Y) Q4 z" J
  47.                     ( J8 f3 T3 ~# ~) ]  [* S- p
  48.     if(ret != HAL_OK) while(1);  ?( ?# s; ~' P# d7 s
  49. ( u: x. e, P" }" [8 T, z9 y
  50.     /*配置主内部调压器输出电压级别*/  |: [, G) n) v2 }
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
    & y+ p- |9 @" {

  52. ) @# F; L2 {0 O' _  P; [
  53.     if(ret != HAL_OK) while(1);
    ; ?) A! s2 v% W) p4 u  H# ?
  54. }
复制代码

& [8 y4 w/ m4 O* t/ B  R函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。; \: a  b0 j4 b3 u2 \* b
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
+ g% S; ~8 h/ b1.使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;3 {. q& t# C0 K/ p* G4 b, J1 `
2.配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。/ I1 p. ^' l1 D
3.配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
' o# B+ Y3 \7 X# A+ M, f; ]- Y9 J4.设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。9 ?3 z7 _5 S* k- e
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
3 q' p% C1 ?- w8 Y2 O% u4 A
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码

: J& g8 s8 d$ C9 v  a' RHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:. T) @, o/ H$ F8 B" `" E
3 d5 w; W9 f" z& z
  1. typedef struct
    6 j( t5 f- M; ^3 M; K- h6 j, a
  2. {
    6 j; L5 b4 N& }& [+ ^
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      
    . y& A9 `7 I2 O
  4.    uint32_t HSEState;            // HSE 状态      
    + j* ^/ x1 Q2 E) ^5 k0 z
  5.    uint32_t LSEState;            // LSE 状态                   " D3 i* o% z: D2 e! @7 G3 r
  6.    uint32_t HSIState;            // HSI 状态      ! ?; A! [& N6 S( q' Q' R6 m
  7.    uint32_t HSICalibrationValue; // HSI 校准值 * F; I6 G; E5 F
  8.    uint32_t LSIState;            // LSI 状态      
    " l% N) n2 }+ r: |3 }. N& c
  9.    uint32_t MSIState             // MSI 的状态  
    $ m4 N, G0 _6 b% I; Q, }
  10.    uint32_t MSICalibrationValue; // MSI 校准值 & T* I! J- E2 |8 l7 S% o8 c, m# f3 l
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    * v1 g+ H& _4 B& ?
  12.    uint32_t HSI48State;          // HSI48 状态
    / G% l( B5 P/ T3 \; r# g+ f+ z0 T
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    . a' {4 q% m& T/ P
  14. }! c, i; G) v- R7 a0 j. F
  15. RCC_OscInitTypeDef;
复制代码
2 K5 q, u8 `' s* h
该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
3 b8 O# y# z4 h* ]! Z  b, T该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
% k1 o2 Z4 y  f4 l* H  s+ I: b2 O. t: M  f. M8 \
  1. typedef struct
    % y9 c2 Y/ m' ?" r% v% L2 s! `
  2. {
    , R7 }9 T/ t) `. I) B  _" d
  3.    uint32_t PLLState;     // PLL 状态 ) U1 q! I0 u7 @
  4.    uint32_t PLLSource;    // PLL 时钟源
    3 S  [' o0 \' U5 Q! N
  5.    uint32_t PLLM;         // PLL 分频系数 M ! h  C$ L  C- x9 b' W
  6.    uint32_t PLLN;         // PLL 倍频系数 N 3 I3 i& }( E  a: e! x( \* i* a' j
  7.    uint32_t PLLP;         // PLL 分频系数 P 2 f; H3 T3 x% E5 K$ N
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    / N1 a! z$ H6 D) X3 S2 ~. o
  9.    uint32_t PLLR;         // PLL 分频系数 R
    " _; e/ R* ^6 P) E- O
  10. }4 i8 \6 O' V5 _  \$ p6 L
  11. RCC_PLLInitTypeDef;
复制代码
9 c0 S' F" r0 V
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
+ E% I8 c8 N" X! k# h& ?/ p5 O$ p. \/ L' h: A# [# \7 h" f6 t
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:6 L. ?/ B# z1 z  _

9 ~) |1 m, V0 h0 [
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码
% Y, Z3 d2 X$ |2 S, S: F- a
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。; D7 r# v, y6 b! a2 X

# l1 @3 h  F: y4 e& P5 t对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
: \) H4 w; j) ?" m7 ?' W延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
7 @. @) c7 I, C) H9 M5 k! s% E首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:
& O" Z' Q9 Y! y) P: @4 m+ b2 k0 Q, z* m
  1. 位 15:14 VOS[1:0] ( c' @* b8 k& r
  2. 00: Cannot be written
    8 s: f$ u3 p+ D! e, i( c8 w
  3. 01:Range 1
    8 [' f! }) P' @4 a1 [2 z( R! ^
  4. 10:Range 2 8 a  V% K  m8 |1 r
  5. 11: Cannot be written  
复制代码
% E# I' Q3 z$ w, Y  S9 O( j2 O7 D
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。# n8 q$ ]* ^5 C" Z/ p& T) c5 v
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:6 v7 Y$ I+ M. T3 i% O7 _5 }

1 t  X" P4 A* V& m 20201205181115783.jpg ) Q7 o+ _: V" i7 t% ]5 Q% ~) I
# ?& ]' [' h5 G
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。1 K, g5 h2 u; s  }* }

  G6 M3 L2 q0 g三、STM32L4 时钟使能和配置
8 p8 S! j2 t9 o' g" i在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。
9 Q4 E) ^0 C0 J6 o* Y
& h9 O, m) _& u7 p. k  Y通过STM32L4的HAL库使能外设时钟的方法
+ S; ~) @" i: |在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。' Z4 D1 k8 B; A* `. {
9 v' a) j8 ?0 P9 [* A  V
举个栗子* J+ w4 ~6 _4 K* U
看看 GPIOA 的外设时钟使能宏定义标识符:
" Z( b/ R, g2 e; a& C6 Q5 s) U: c3 f4 e1 F3 ?( R7 u6 D  W
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \ ; l: Z1 o7 @9 ~4 N
  2.                                                                      __IO uint32_t tmpreg; \
    3 ]2 N3 `! z7 w. Q
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    ) \6 T0 w! D* U0 S: D
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ 2 U# b9 @/ Y5 ^7 Q4 m  J
  5.                                                                             UNUSED(tmpreg); \ ' b! _" u: g3 o, @8 ?0 A
  6.                                                 } while(0)         
复制代码

8 s5 b. k- s8 E* F4 V这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:- m" b8 T8 H) O0 }; F
8 t9 P( H* @5 M
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

* i/ Z. S% H9 q这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
! C( M1 o, C; J; o$ Z# P4 K) y2 w! R6 \, ^  ~0 }# x
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
    0 m4 q! k% ?( j2 K: b
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) % K7 m0 G6 d5 f- Z; [% B2 E* O( P
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码
7 l/ n$ V, m" c2 T# P
上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 04 D; o3 z: c( _$ c5 r& x
描述如下:
0 {( r* v( g' F1 A. Z6 g
: G, J- c9 _% y* w. J  |
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能
    * F1 r$ O( a5 q5 {& D! G
  2. Set and cleared by software.                       // 由软件置 1 和清零
    : ]8 B* F7 V9 Z
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟 % T4 j6 ~5 l" e1 _7 G: \
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
9 _7 [) E* N' T) u5 x' T5 l
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:( f9 A2 q9 p2 W' G" N

0 `/ }% b7 j) C0 d$ X- o2 P
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码

5 K4 Z# \5 z8 T  w) o$ R对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
6 K1 U5 N8 @) v! i& i: f7 a8 i& ~7 n: A  J1 m
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
    & K' E# _  }- [* S
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
    / I+ u. \7 @" z3 J* y0 q
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
% _# f' p# l. U2 _+ F1 j2 d
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
7 P$ z7 s& ]' X) V4 X. G# E# I# `7 x/ S4 e: Z, \2 l
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            ; L7 \5 Z9 ^" @& p, g3 m# O" r
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码

/ n0 C2 N! h7 v) k+ }7 d同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
& o0 v4 a; a1 i
" \7 S" L2 k; e: A& S- x
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    ) a, X0 ?* R- n/ c8 e
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
    $ {+ \" O7 K# M) o' j+ O/ @7 n
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码

9 X  E( ?( F! n8 u* d! O- B, s————————————————
7 |4 v* ?9 ^" A4 c. m4 `版权声明:天亮继续睡
' t2 w& f0 J/ z; B: k" e" Z, j, \9 H$ B. J
" l9 z  {. n8 i4 k) n" n& ^3 }+ k

& }# E  p! j# @/ |. g- R( x
赞 收藏 评论0 发布时间:2022-11-17 18:57

举报

0个回答

所属标签

相似技术帖

官网相关资源

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