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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容: e0 k! t6 B& K5 Y! @& c) f) u+ m/ b. u8 `
1)STM32L4 时钟树概述;
2 w5 m2 }: M% l6 S2)STM32L4 时钟初始化配置;
7 s3 a; p% V% n: S2 x* ~! D# w3)STM32L4 时钟使能和配置。
9 H1 T6 b6 U* J- g, i& M$ \+ A
  y/ E6 S% F! ~5 D% h& ^一、STM32L4 时钟树概述
0 R$ L, r3 [6 C. E时钟系统是 CPU 的脉搏。
; L; _1 Y" h% q0 F3 w0 @6 [6 I. y不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
- F& V7 a: c; H2 V/ W- c6 b同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
; F- Q8 R3 r# i- S! c1 o3 n所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。+ A; [% L' P# z
! m- ?$ X& t1 V4 f1 W
STM32L4 的时钟系统图如下:
) f/ p! A2 h8 O4 T  G+ F7 o% H
" k$ `* {* h) a9 |: J  v7 Y
20201205162651825.jpg
, Z  l, `9 t* }+ M0 G  A
20201205162724607.jpg
0 \% V2 n$ V  H6 X8 C8 s( z' o

# c* r7 I4 y& k' E. w  x在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。3 i9 U2 x2 x3 R8 G5 n
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。' t% J/ l$ p( D; r
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
6 h8 y7 P5 W' e) R4 g按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。, N: S0 @! j( y/ \% M
按上图中红圈标示的顺序分别介绍这6个时钟源:: U6 i' ~+ {4 z8 `, O
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。6 B+ K4 J# g* h) c  L
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
' H2 k% T# m' o③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
8 H, g) N/ f0 `④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。3 q" J. k  L. J! ^* x1 p
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
" S  p7 }; l% C  y( N( @' H; {) `) {⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:6 S/ }' @& c: x0 A
1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
1 \+ c; J$ G7 G3 d7 [第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
' V' S$ @8 f1 U3 c8 N9 O第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
9 {- U2 {4 ?, _0 s! |$ B第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。$ `' I: |6 A: e& q2 |3 X& N- Q/ Z7 B
2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:* R6 \1 A4 J" n
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
# y4 F* q" U8 I* \第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;& f7 D! k9 S; [
第三个输出 PLLSAI1R,可为 ADC 提供时钟。
; o: u# f, X  G. B8 I3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:9 s8 n7 x% c# Y( p6 k" H% l5 R
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
6 N. w3 {" A. {* z第二个输出 PLLSAI2R,可为 ADC 提供时钟。
* A  I, |8 E8 k1 X2 Y3 g2 ?
& y, ]8 @4 A: F3 y1 V: i: K' q这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:
8 K  P0 w# Y7 K  ~  u
20201205163714587.jpg
0 j% l9 x4 }9 ^

( m+ u; a: x: g上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
7 i( d4 O: Z2 m- N
; C# D, n0 F6 b1 \0 O) P$ f0 A举个栗子:
) I1 P, y$ ^3 p, G: u7 C! J' T外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
4 G8 E/ D) O% u
; n  \- y7 \7 kPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
% m. P/ C. S/ M/ F  T( k如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。7 ~5 H  {) C, Z- Q- O' `

5 ?  A+ t$ |8 b1 H3 p2 F下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的$ O# h7 h1 A' a; Z7 j
观察之前给出的时钟系统图中标出的9个字母(A~I):
: p, ?4 K% v" X& C8 s7 C3 B2 q: `A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;3 w) A: _; G0 y+ m" f0 x
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;* Z$ L5 ], s% y5 x9 ~) a& u
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;3 g6 ?5 `& v( X
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
0 Q2 X+ ?' {) C* R% YE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;4 ^' y& w/ m5 V' R2 W, N; c
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
3 u" U. c# P5 X9 S& B/ yG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
0 `" ]7 N$ C+ @' nH. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;+ @8 U% C* T- P( Z- t
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
6 V4 L: P/ G! T3 n; f* c备注
6 Z- X2 q/ }, N/ d% v# R  I& Q1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。
) M7 @8 `4 B) p, j( h: r" ?5 z2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。2 Y" Y. O  K0 C; d8 d
) E- j6 X8 {2 k7 `1 i. S% F
二、STM32L4 时钟系统初始化配置
; d8 F3 d" R' e9 H3 M$ z5 o: J8 K在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
$ R9 I, n5 |9 A& N/ v- Q
+ n9 |5 E! A+ a6 j
  1. /*********************************************************************
      M& `( U, e8 N
  2. 函数名称:void SystemInit(void). p1 a& L* v6 V3 t& Z) z) _
  3. 函数功能:1)  FPU 设置
    , A% f" {/ h  n/ O0 B- ?5 ]
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI) 4 I* |3 K- J8 \+ G3 s8 ^2 e
  5.             3)  中断向量表地址配置 9 |! {2 r% S' o. j
  6. 入口参数:无
    ! o* _, M% H/ q: F8 b* q8 C
  7. 返回参数:无# e. ]( ~# C- y5 g5 v
  8. **********************************************************************/$ ?) N  t: E4 \. z
  9. void SystemInit(void)* u. H" H! e) t7 A
  10. {
    ) X+ a5 L# P+ }& T) W) _) P+ ]! X4 e
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    . O; i- A) n# V, v8 a/ g+ F/ S
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
    ( c* I" m! C8 v: t
  13.   #endif
    5 ^) M( C1 W/ G( F- a
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/ 0 j- `2 L) G- r& E. `
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位 1 {5 K4 y- n4 F4 M: V
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    8 m/ y  q1 @5 h$ j0 y9 \
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    ) ^8 V: D9 z0 q! y7 g
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器" F" [# C: @: K4 r! L8 Z
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
      ?1 \! F& U9 i  G4 |+ _  R* w
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 1 j$ m7 v3 _9 j# @
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    1 j+ q1 O$ `8 N
  22.   #ifdef VECT_TAB_SRAM5 x/ e4 x8 o/ Q- i% \, c7 @* p/ O
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */9 a- x8 ~# Z4 ?' b- n  X; g0 `
  24.   #else
    " Q& J7 q( \4 C' y# K
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    ! e1 p8 ?$ @' x4 a, f
  26.   #endif
    9 v+ n8 q/ |& L% _
  27. }
复制代码
+ V, b. [' k' ]
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
! g" y! z2 C' v' D8 y6 D5 v3 R' n+ e* U3 o8 v5 g
  1. /*********************************************************************5 h! O: c3 r6 u( u- k
  2. 函数名称:void SystemClock_Config(void)+ S4 i6 G2 k7 @! h/ M6 i/ _/ j
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR2 z* f, X! S( @4 _
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M) Z5 _' _: I0 V$ U+ `1 y" z
  5. 入口参数:无/ G( G/ I- ?  m& I7 i/ E
  6. 返回参数:无
    9 l6 J- y& d% s1 z$ {
  7. **********************************************************************/
    7 a8 b/ E* _' Y$ t8 z' U8 F
  8. void SystemClock_Config(void)
    " }# C* D( M( m' N( b
  9. {
    & u! V2 S: \. l' w) B
  10.     HAL_StatusTypeDef  ret = HAL_OK;8 o! j4 g( W6 {6 C0 f; _

  11. * h6 u( H$ w7 b. ?/ U9 U6 [
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;
    ( ?% f4 I  c8 D( B0 Z9 B7 e
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;' s2 h9 w' l* w0 n: D0 w" ?+ a

  14. % X- _& g$ Z6 _* C
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟
    ! l' f- R( {9 z* e  ?) y) C
  16.     /*Initializes the CPU, AHB and APB busses clocks*/% w8 z9 P2 [/ T5 G4 d2 v  _
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE ' P* @. s% J3 B) @2 J( g0 ?# Y0 \' F
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE
    4 q3 f$ v6 r) K  M* Z
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL 0 [2 L; D# Y' b
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE / a: S# L1 g9 c# U) N3 `; Q& l9 O
  21.     RCC_OscInitStruct.PLL.PLLM = 1;, q9 p9 I" @* \# f2 e
  22.     RCC_OscInitStruct.PLL.PLLN = 20;( s% y/ U" {3 ~
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;3 M/ S1 h1 s) j
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;) |, C, H# p- Q* ?* p7 q% N! _! M( Q
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    5 N# E8 y" D0 Q- ?

  26. - N8 h$ F4 _5 e& |
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数
    9 R( [! e! b* o3 _% I* d% z
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ $ P; E! v' M# J1 s
  29. 6 X) t$ U, Q, ~/ j) y
  30.     if(ret != HAL_OK) while(1);) o) \+ p; C# P: @
  31. % x0 ?3 G' d5 e2 P
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
    6 V: K0 M1 I" i( n( Y' c: F0 g
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
    : Y7 W& |+ q& H$ _% c
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 2 r4 S  v  w, z- Z4 m
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    " [1 [$ |. k0 S$ [
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
    . q* w) i% E1 \7 J
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 13 L" E$ j. d2 X# L! M$ X  k
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    " w5 C/ g& N# w# M! u8 x

  39. ) D0 w4 l1 m: F3 N: @2 U6 ]1 g
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    2 I/ h* M; ^" G) D/ O5 V- \$ s
  41.     /* 第三步配置后,可以计算出:
    6 k# X: @7 Y( t7 B4 w( }1 k0 y1 l
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;1 w; ~9 H$ [, K3 f
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    4 J3 O. k1 z, C' w8 ^$ G
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
    ( Y* |, K( B, a9 m
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;5 b: ^+ o: t0 I7 g5 R9 \
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/! m3 ?0 Q- u. m( I0 Z" F# T- |
  47.                     
    9 W1 C, {" s: M% y
  48.     if(ret != HAL_OK) while(1);
    6 l1 G- d+ z' O7 u5 R2 b3 |& p
  49. ! ^8 d3 i4 r! {- _6 V" v+ P
  50.     /*配置主内部调压器输出电压级别*/$ q5 V, H( \/ ~* a6 l
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
    + w" Y% J4 \2 r9 {8 u& ^. O& [% ^
  52. $ l+ o7 z5 I4 L+ |8 S0 \: x3 G
  53.     if(ret != HAL_OK) while(1);9 ?+ f0 w/ u2 x
  54. }
复制代码
* ^+ z8 T, J8 W, w. Z
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。
) F7 c# P5 o) v7 E( K5 e& i9 ^9 K使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
3 e9 J$ t  |% h7 o( w8 ?7 U
) y1 J. V# }" V/ r2 {# f- G: v使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;/ ]3 q  B' M/ ?6 {' P: u
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。4 d2 U* C% Q! b  [" G
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
" G! q2 ^8 b9 r! h* z) c设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。$ |4 ~3 A# }$ O" {- o4 u0 P; n) Q7 I
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
" y/ e% M" Z/ T/ T9 L3 T1 `8 Z
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码

  [" _' O- H2 }! N" ^5 ~, wHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
- Y, K# j- f6 R/ r6 v4 ?1 `
) Y% T! q: N/ N" p& J
  1. typedef struct 9 [# k" x2 N3 S  }) l
  2. { 0 X5 A2 Z9 W  n2 I
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      ! t# @  _: c/ \; w; W& K) `
  4.    uint32_t HSEState;            // HSE 状态       ( i7 h, c( a) i: q) Q  h9 R, o" B3 q
  5.    uint32_t LSEState;            // LSE 状态                   5 j! M$ P+ ]% R
  6.    uint32_t HSIState;            // HSI 状态      , v( `. U, I* S
  7.    uint32_t HSICalibrationValue; // HSI 校准值
    3 M8 C$ q. ^# F+ [5 ]
  8.    uint32_t LSIState;            // LSI 状态      
    7 U/ a0 n* h( @* @; ^
  9.    uint32_t MSIState             // MSI 的状态  
    4 m' C$ O% }5 \/ j* N
  10.    uint32_t MSICalibrationValue; // MSI 校准值
    ) m( v+ Y2 x' D. \2 F) M) V4 ^
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    ( z3 x0 ]3 T3 L
  12.    uint32_t HSI48State;          // HSI48 状态 ' s. Z, `  w" w6 p
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置 % [( [  x9 g1 j/ m/ k
  14. }
    1 f* n" t% A5 |) ?! P- r) Z, G
  15. RCC_OscInitTypeDef;
复制代码

3 u5 Z, Y+ `- ?. M. G该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。; [, }: b, q& D6 P
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:4 o$ b: _' I( A+ F- Z6 K( O

: S9 z5 J/ `  q& H9 [- o
  1. typedef struct * z' Z) s/ ^8 T' a0 |$ G
  2. {
    2 \* j$ y0 y/ R% I3 r
  3.    uint32_t PLLState;     // PLL 状态
    / j1 f. U! B" s1 q
  4.    uint32_t PLLSource;    // PLL 时钟源
    : v% J3 \$ y0 Y1 K% l# c7 a
  5.    uint32_t PLLM;         // PLL 分频系数 M
    1 f) T; {: G; q
  6.    uint32_t PLLN;         // PLL 倍频系数 N ! M- u2 L' k6 t1 `
  7.    uint32_t PLLP;         // PLL 分频系数 P
    / y* V. ^- T* Q9 ^
  8.    uint32_t PLLQ;         // PLL 分频系数 Q 0 Z5 d: s0 j% K( ^
  9.    uint32_t PLLR;         // PLL 分频系数 R
    / x/ L" M' Z; e6 ~. J* q8 N
  10. }
    " z# T4 }; f8 R+ u, K
  11. RCC_PLLInitTypeDef;
复制代码
4 r; V8 J1 i) S
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。. a1 c  b0 `. i
- M. h4 O$ z4 p" s
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
; t  A$ D9 @* o! h4 n; F
9 j5 {4 a2 V8 B6 l0 X
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码

$ ~; L, N: X: `4 @5 _该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。  M: _% A6 X# c5 \& U* P8 `5 M

! i& j4 f5 ^/ m7 {6 j7 l对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的  }  j/ u8 o3 x% P% l- E
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
( ^' T$ k8 @9 o! k首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:  Y* `  J$ M& g* D; V2 b5 U
. K! d/ e* g& N4 G
  1. 位 15:14 VOS[1:0] " _. h+ l2 q, Y
  2. 00: Cannot be written   Q' s8 u1 w  h/ S
  3. 01:Range 1 * w$ M- o8 _) y- S- J+ t
  4. 10:Range 2
    7 c* ]8 Y6 a+ ~9 d, V& ^
  5. 11: Cannot be written  
复制代码
  ^# M8 U/ }3 i+ {
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。+ Z' a6 G0 L  |3 Z
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:1 S$ |0 V  m- i) i2 ~$ m1 D& g

! Z! m1 T" Z5 I& S# b4 O( s
20201205181115783.jpg
( }3 \+ {, ?8 U" j- Q# N

: s9 Q+ d, R( G8 M可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
9 }9 e7 {  E5 z+ i" _5 w/ \; x9 p  \' p3 E. ]
三、STM32L4 时钟使能和配置7 M9 x8 F; M' V, i: H5 P
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。' ~$ L  M; q  }' {: `( x

4 \/ A2 z: ?: l; T通过STM32L4的HAL库使能外设时钟的方法4 N+ j! u- u3 P  s$ }& Z4 F0 E
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。) k6 X" S9 a8 `/ Q' ^3 L8 O
举个栗子2 P7 @/ `4 A- l9 ~
看看 GPIOA 的外设时钟使能宏定义标识符:
1 v0 X  O" z  b& \6 o- i& J; @& K% i7 q. j7 e9 o, l  j, }
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \
    - d6 Y' u+ U  |( W8 V
  2.                                                                      __IO uint32_t tmpreg; \ ; t( R' B& F% B
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ + l- v) y/ \6 d2 [% ~
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ & q. Z0 ~: x5 q7 v5 r. \: r/ o1 P/ j
  5.                                                                             UNUSED(tmpreg); \
    2 e: I$ @* U9 e. q7 g4 I! a
  6.                                                 } while(0)               
复制代码

9 Z% ?( i: |/ w
! B. E% Q  h9 H, K这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:* R" g5 V  _5 I0 c1 j+ \7 U
% e2 ]: m6 [8 @2 ?0 ?. C
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

8 k3 \5 V0 t# `8 H) s# Z& k这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:+ u$ K/ r- l7 R- f0 L  e( k
# b" r& q1 ~5 k
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U) 4 y2 m$ x& K# ^$ O0 e
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
    ' e1 H1 D# Z) b8 F) E
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

+ j+ h9 ?. _& }# u上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 08 r9 C! t& S8 N2 S% T
描述如下:
$ f: }1 x9 [& C
. b( A! x' Z, F
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 ! Y/ G  T7 C; F; {% O
  2. Set and cleared by software.                       // 由软件置 1 和清零 8 f( c; @. N1 x, _
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
    . Z8 h" h9 @; {
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
  T1 M0 `. P* g8 l
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
  ~5 K9 Q, l  N/ x1 c; I2 ^" y7 c) w0 \4 m1 M6 v
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码
4 N. z( f# x8 Y5 B! F* z% \$ A/ [
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟 $ K& A; C% c; n3 Q1 n: @/ m4 G
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
    " W7 E  ~9 O$ `
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码

/ `. w! k/ W) ^+ d+ p# ^使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            . Q5 i& T" l( \9 a
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码

& _% R& M2 N% U8 S# T同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:7 r! N+ C( g5 l

) _9 [- i( D' g& `) |$ j- }
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    . D3 ~# J# e3 l. v( Q
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟 3 C! a- e0 {6 b" R. t! m6 [" W# i- i5 a! h
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码

' C0 @* k( _; q! j. |8 \8 F
0 q/ P! l: z% [0 K# B3 ?. ?! q. I- D4 \- o5 w" Z8 q) N

4 U6 w+ a4 D# z
赞 收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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