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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容. s- H8 J* S  n5 g4 A. F, R
1)STM32L4 时钟树概述;( c) o& ?( L& A& O
2)STM32L4 时钟初始化配置;  W, Q+ O4 c1 V+ R# L) B  ^
3)STM32L4 时钟使能和配置。
1 d  C+ F% z2 |5 e$ T1 i* ~2 E2 i$ |6 A" x
一、STM32L4 时钟树概述
+ R% g! T' c3 r' Y7 I时钟系统是 CPU 的脉搏。
2 z  `+ s+ O/ d6 I: P7 T不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
5 d# |! e, \5 \; y( ~同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
  Y, F  R1 ~9 i0 W; f' e所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
/ a9 ^& q4 X) l: R: _+ s. ~4 Q& ?, j1 Z- o% R7 g  e
STM32L4 的时钟系统图如下:
! n1 M2 N6 E! ?0 X2 c
4 n1 ^! \" p% h& Y+ F, ]+ M! K
20201205162651825.jpg

3 n8 l3 H, M) Z6 `& V( H
20201205162724607.jpg
7 v4 U2 G; E" @0 z- b0 e

, j0 v- E- y5 V在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。% b1 g6 g. ^2 A) u
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。1 O5 Y/ B6 N% I
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。2 L4 X" O. R1 I
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。
" F; F9 O9 R0 I8 R6 V. h按上图中红圈标示的顺序分别介绍这6个时钟源:
% t8 Y7 j; o9 {1 x  K! s! I① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。7 P* S8 u1 m$ L7 j( ]; k, T) y5 y
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。* Z, g( d( ~' p2 n
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。. \/ A, p. d6 I8 B+ R" [
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。6 q# L, e( [' |) b2 q( {6 b1 w
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。& I& Y+ f5 g$ ~# q+ X
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
, U) M' |% C! k* \3 f3 m+ Y( D" g1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
) S& ?( Z7 [' t( U' r5 x第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
* }0 l7 f" Z: ^7 e1 A第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;1 W7 B& T; R: U8 S& ~- K$ M% x
第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
4 E" \" O+ Z$ ~8 L7 S) X2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
4 D7 a4 ^7 d& o7 S7 j第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
9 Y8 u+ G8 [* ?1 N第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
. L; b3 D- Y1 w8 P5 k第三个输出 PLLSAI1R,可为 ADC 提供时钟。
! e6 P8 W  p+ Q" s9 v9 B3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:  Y/ T0 D" @& @8 k8 k/ E
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
% C2 Y. C# |! q* S第二个输出 PLLSAI2R,可为 ADC 提供时钟。
6 [' s, v( q/ @; j8 m$ V: p5 h8 {5 ^8 y4 U
这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:
& [: k- G) @) A, U
20201205163714587.jpg
/ ~" ?  N' Y& u/ f( c
6 C+ C/ e; S' U. x) h: z* z
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。( a6 H3 H& j6 E' v% r

) A* S, r( w7 v- G5 L& I2 J举个栗子:
4 N' g% I- P' i% s1 {" t外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:$ {; W( g* V0 P

$ A" E! n- n; \  H8 nPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
" @( m% e* q' `( ]6 U# A如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。; f  W" ]. b! z/ z! {/ ]3 P: g7 Q0 |
& n, z/ e2 l# ~; F3 T4 k
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的4 q: I* m9 }7 u3 l0 \1 D2 w
观察之前给出的时钟系统图中标出的9个字母(A~I):+ T* K& F1 M' q. r9 }5 R* _
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;& O) H; c: r9 v) U6 `
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;& Q. o' N2 i/ j) g
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;7 a5 b- @2 d  G6 d; v1 I! ~( S, x
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
5 s5 @* P& O# C9 f) DE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;
5 o$ K' f( Y: F6 o' G0 K, EF. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;9 T6 T7 A4 T# x" v0 b
G. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;; P3 X+ k% V! v: m4 ]& Q
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;: N, J  T9 F  \/ U
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
2 s8 J! x$ ^6 \, g. F/ e6 Y备注
( k; e1 M9 F. n* m/ b; k1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。3 Y9 p2 C3 r- ^5 R
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。% n6 X5 q6 q9 n# v! n

. x/ D, c* F9 `, u二、STM32L4 时钟系统初始化配置7 H- m# D2 P2 V1 A3 ~
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
  O5 R8 L& t& u- s" j) ]% h  ~0 J. g0 [& g
  1. /*********************************************************************
    ' Z) W. O6 y6 ?$ ]' ?6 }1 f
  2. 函数名称:void SystemInit(void)
    " M- D7 Y& e1 D" r+ _# m1 |
  3. 函数功能:1)  FPU 设置
    ; e5 p" k% U& s
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI) & k5 |0 n6 g; S/ S3 @8 u. T
  5.             3)  中断向量表地址配置 2 l8 w- h. M9 u( x
  6. 入口参数:无
    0 ^. K. M! q" F# g" n
  7. 返回参数:无
    ' [0 f) c" v1 G2 n2 E
  8. **********************************************************************/
    3 O8 @# r' J6 Y
  9. void SystemInit(void)/ Q( `+ N% z& e; \) P2 F! a
  10. {
    # a1 b! q  D! g) U
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1). p  b0 K8 j0 t' V# F4 A$ a
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 7 ~8 G; `  ^! F& t9 H
  13.   #endif
    ; r, A+ [/ j, ^' m* t/ V: T3 s
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/
    3 R/ H$ q& B% F4 L$ U
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位 3 c% Q% r* L1 r  A8 U) r; ?: D
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器 ; e9 p* }" q* W( G% B3 Q
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    & H, n; ]# H- V& p- b/ ?
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器
    7 p9 C9 b2 `, o* u; ?/ V8 u/ X
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
    6 G/ N4 S3 X5 Y3 z3 R1 y& ]; Y
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 / y8 K7 C, Y  W  t- b/ G
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  + X3 v0 z% e: }% R( U/ o* w
  22.   #ifdef VECT_TAB_SRAM
    : {9 Q; E9 A9 R4 D
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */$ J$ V4 c& ?% T7 J6 ?
  24.   #else
    5 b2 |2 C: k$ P# c6 S: S. O
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */. y3 W6 B5 F/ v; y8 [1 x7 }5 M
  26.   #endif! d5 z4 N) y; ?4 H% I# ?: p. Y% t
  27. }
复制代码
4 t' Y' P% c: S/ P
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
7 K& \" Y6 u. a) H$ \$ g* i" T. `
6 m7 F1 d6 `9 u* r
  1. /*********************************************************************
    ) H1 r- P2 d/ V- M* n8 k
  2. 函数名称:void SystemClock_Config(void)
    * N! P/ d4 l. j9 H1 F/ D3 E
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR  Q+ F3 Q1 [9 u9 C6 y* ]
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    9 J" u9 |  b4 `5 {' H/ C
  5. 入口参数:无
    & \( n8 h+ w5 O9 B) [% C
  6. 返回参数:无7 O3 N8 D' E: H/ i
  7. **********************************************************************/: z( x0 W" L, h; c' ?3 a5 O) r
  8. void SystemClock_Config(void)/ r. ], R% l) p2 R0 U7 e; N; w" q# i9 t
  9. {" L; L2 X7 V! Z. @' ]0 \  E
  10.     HAL_StatusTypeDef  ret = HAL_OK;- e7 d& m* r- V3 x( a; Z% h
  11. 2 o! X$ h; g( n
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;
    & V( j+ n# [6 g, J, H
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;
    0 G% S7 d$ ^# O+ ]% r; P$ v

  14. ) `1 ^" ^& M" g. ]9 l$ t" {6 n
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟
    2 {- j) P& A; o0 H9 X3 b
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    3 E! U5 b) A3 j9 a* {; k
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE
    # t' F' J) k5 L4 ^: u1 U: U
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE
    - I- r& U0 ]( {
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL 7 c# I8 `. I, w- O  I+ O7 \
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    + y$ `' o' K* x3 r* P: u- H" k
  21.     RCC_OscInitStruct.PLL.PLLM = 1;- `0 y* c6 X8 ~+ i- z
  22.     RCC_OscInitStruct.PLL.PLLN = 20;
    + B8 ]2 y9 i1 q+ k5 W4 @" K! S
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;# g8 z7 `7 ^. [# e" |
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    6 d5 x( `5 J: T4 D0 z- T0 v
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    + q3 W4 `6 |- `$ _6 O+ _3 Y

  26. 6 X4 W$ y8 k; y8 H  v6 }$ J- [
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数( h  C$ N6 w( Y0 l' v8 M# E
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ 1 Q' M( g% J( X7 @# M! X/ y
  29. # z5 V# M8 T  Y2 @% I. N; b* {$ L& h
  30.     if(ret != HAL_OK) while(1);6 ?  _; c' ~2 N- K; h# S
  31. + j7 b* @; ~9 `9 A3 ~
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
    ! N) [' A/ n( T# H) ]/ g5 |
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK+ M" W# }& }' V) Q7 ~
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    3 D1 ?0 s# V) O& k7 u, T
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    / @  G, v" e" s) M
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
    * W, D- u$ K1 O* ~4 P  z3 s
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1
    0 b( Q/ z, k8 M6 N1 C  R
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    " E, l& B0 h1 n5 v

  39. - D2 B  o! N; d( r& d+ W: a. C6 ?3 u. N
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    $ d1 w! Z# Z0 Y/ e! ^5 N
  41.     /* 第三步配置后,可以计算出:
    3 H- X4 i9 r' w  b& q9 O
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
    # E8 }$ L+ [. A' H  V
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    ) }/ w* X- w' v. r3 U. K0 z) o! m% R
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
    6 ~' A, H; v# F- g
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;( C$ e; A' }2 D
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    0 s# ?7 f, ]9 V% |
  47.                     
    6 f0 k/ C6 @) T& J) V7 b
  48.     if(ret != HAL_OK) while(1);/ k- ]& B8 p0 ^
  49. / A' k- Y9 ]* |5 J4 j& W4 {1 m0 s7 p
  50.     /*配置主内部调压器输出电压级别*/
    ) R* ~) h  j, |3 \! b% q
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别' h% z. m/ @: O- i/ R9 I9 W( ~

  52. ( Q) ?/ m) |$ S& w* D. n, J
  53.     if(ret != HAL_OK) while(1);& M8 [" {8 G* J4 B$ Q
  54. }
复制代码
$ }4 C" K+ r; A+ O0 p- n: t
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。
) o4 c; v1 @6 _) A$ i使用 HAL 库配置 STM32L4 时钟系统的一般步骤:' e) |& ]( O0 ~+ ]7 k
) _; b* d9 @2 _8 I+ d9 x: ~1 a; ^
使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;( ~9 v6 n! E6 ~; q& a1 c
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
- k7 {6 f! o; K& Q配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。3 Z! E+ ], X# ~1 y
设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
  N# q3 M4 S9 W. R+ q对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:# u3 J7 h, }7 L
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码
+ B3 d: Q0 D" \
HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
0 A/ [+ q! ]6 z5 _! j' @4 Y! ^0 s2 n8 u8 p, W
  1. typedef struct 7 ]/ g  N) f, Z# M0 \
  2. { 5 c# r# o5 i0 A
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      $ ^, z7 j1 _/ O  {5 M; A( r
  4.    uint32_t HSEState;            // HSE 状态       5 H7 r" o: M* C3 I& o
  5.    uint32_t LSEState;            // LSE 状态                  
    # t: {; d  B2 M' R! s0 W
  6.    uint32_t HSIState;            // HSI 状态      
    1 x! C0 y! {2 W& h3 s+ r9 a
  7.    uint32_t HSICalibrationValue; // HSI 校准值 $ _2 N) X/ z3 K0 O
  8.    uint32_t LSIState;            // LSI 状态      
    8 g3 q) ~2 P( L% d
  9.    uint32_t MSIState             // MSI 的状态  3 j3 t; k. m* q4 \; d/ ~
  10.    uint32_t MSICalibrationValue; // MSI 校准值 9 N# Z2 C$ K" y3 }+ g6 {) ]* @1 v
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    , S% Q+ S9 z7 y* R* V
  12.    uint32_t HSI48State;          // HSI48 状态 9 _$ F0 y  e2 ^# g3 w' O
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    # x& f3 O, l/ |  M, e6 [& s
  14. }, L5 B/ j* _/ z- t  N+ {0 ^% z' _
  15. RCC_OscInitTypeDef;
复制代码
# c/ M: b8 [: k& e* _0 ]- d
该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
: z/ Z& y4 k9 i0 t/ w该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
/ t6 E, \5 Q6 _
4 W& \# \& Z( S5 s) B9 O1 G; h
  1. typedef struct . n0 r; [0 F# E  O
  2. { 3 N, D7 j2 J7 f# V3 K
  3.    uint32_t PLLState;     // PLL 状态
    ! E( U% B$ W1 f1 R  B
  4.    uint32_t PLLSource;    // PLL 时钟源
    7 u, j; u# y& e5 {% t3 A& a
  5.    uint32_t PLLM;         // PLL 分频系数 M . f5 k' X& p0 Q2 |# U( `5 c
  6.    uint32_t PLLN;         // PLL 倍频系数 N
    1 R; a6 _/ e. y
  7.    uint32_t PLLP;         // PLL 分频系数 P . p9 O4 _% |$ ]5 P% \
  8.    uint32_t PLLQ;         // PLL 分频系数 Q & p6 Y0 E9 e+ k& C8 l% e5 s
  9.    uint32_t PLLR;         // PLL 分频系数 R
    . L3 }' d1 q2 A8 _6 b$ {9 H6 D
  10. }
    4 D: k' n# I+ Q# r4 j
  11. RCC_PLLInitTypeDef;
复制代码

( h+ q7 g5 L4 ~' d; j* ?! q! O' Y该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
3 @. F! K' Z# G3 Z3 I
/ l5 G2 z! H- W* K, w( m. o对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:6 N! G# M3 h# G1 F' a5 ~. ~6 N
5 [! u6 {$ `$ ~; u
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码
1 I! Z% ?% ^1 d/ S5 D  M  K! Q( U9 S
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。2 I0 V% w7 E6 L! J5 S
8 \( N1 e9 R3 Y) ?' ]% }$ `
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的  C' Q7 J0 D: c& `, [) h# Y
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。; m( l. D. @8 g. ~8 m5 l- s4 L' ?
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:* B, _; S) C; A6 i' i% R
" ^% |8 ?* s7 e: \8 p0 B+ b
  1. 位 15:14 VOS[1:0]
    ! v3 V: k6 c  W2 Z* e! h: a* J- @
  2. 00: Cannot be written
    ! A6 j" T) r: m9 B
  3. 01:Range 1 8 B+ F: c$ J; P/ _9 i6 v% O
  4. 10:Range 2 7 h3 a& f) V2 s
  5. 11: Cannot be written  
复制代码
0 |; n7 a  o" h* a$ P: U# C
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
8 v4 C1 y' ]) F! q9 ^4 C: `配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
3 }- `) R8 u* s, {7 P  _, z. v+ s1 K* U% E
20201205181115783.jpg
  t1 B# q5 C; K2 t$ L

  {3 C# k6 |8 U; i! |7 |* E# X3 G可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
- g& G0 X$ G5 Z) |; i; C- I/ O8 r3 K. x" t9 f5 k$ e3 k3 C
三、STM32L4 时钟使能和配置: K1 n& |( N' a* m- z
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。: F& R; o5 O: P2 L
- Z3 x( F3 R) V2 V* A( N# F
通过STM32L4的HAL库使能外设时钟的方法
1 @! h6 ]7 F7 D  X- w8 G在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
3 @4 o* q7 u6 T1 l, v- `. Y+ h3 b举个栗子
* q1 n% e3 P4 H  s% h9 T看看 GPIOA 的外设时钟使能宏定义标识符:% c7 }" d) l# U! K; l

( Y% L3 [' N; l9 n5 D
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \ + `) A9 g8 f6 \% P7 L8 v
  2.                                                                      __IO uint32_t tmpreg; \
    . ]& \+ o& v* N" L# f4 `/ ]7 x
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    ; G- P0 u3 i+ R! ]  G
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ " C5 g, h, B$ `
  5.                                                                             UNUSED(tmpreg); \ / }6 H: I  s$ Z3 Z+ w3 T" f
  6.                                                 } while(0)               
复制代码
* T) s' A( K: k# T) [
5 S/ V$ h: A* V+ u: [3 s8 p2 S
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
9 K  o) W4 h5 \9 {! c1 W1 {) `+ ]9 a  [" y8 ]( L8 m
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

1 C4 J5 E) p& L& \, ~这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
# B2 q/ q# k1 Q: U$ z* R
' l. g7 |, Q0 {) Z/ @
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
    + q3 Z3 R1 U6 p& L* E7 K2 ]
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
    ( P$ l7 l+ x$ r* D+ r9 r9 Z
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

: D4 B, a- S4 F* ]上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0; i  x- _- C9 o- Z) L# o* J2 U% U
描述如下:/ S5 m) K5 k1 R# e% V

) }) c: ]% X' r4 G* W7 d
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 $ W3 S: |  D$ @  n/ ?2 f+ _
  2. Set and cleared by software.                       // 由软件置 1 和清零
    6 g0 ?( M3 G8 o! Z( |3 K6 q
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟 1 t9 N! }+ \0 h7 f  B5 G2 D, ?
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
, m) n7 @9 z" b" v8 ^, [7 [
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:: ^* R7 r( a7 u+ H5 y" [" S/ P
7 b+ w( V6 C, X/ o
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码

$ l% O; L8 w2 \: _对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟 ; J& O: r" P; T5 H) F; `/ U, `
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
    & a" J; p4 K( x8 j/ o
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码

( D! z+ M: P8 j9 ~' {) j0 P8 o6 t使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            & o1 C0 i) @5 m7 P
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
, B8 `. m% P: ~; l9 \% C
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
, S* L# b0 @% [& e
9 j; w6 }' U0 f! L! u" f
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟 ( V. j/ N) e/ m
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
    + r6 N2 _- F( ~
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码

0 s  `3 |2 t* t0 l6 a7 h. j8 b, E1 L
7 B7 I; F. d0 c7 Z' h

6 d. Y& c4 b6 G( g* ?/ `5 j
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似分享

官网相关资源

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