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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容4 v$ X3 U' e$ ~4 A6 |
1)STM32L4 时钟树概述;
8 R3 D, W$ F8 e7 C0 r) @4 i5 v3 z' w2)STM32L4 时钟初始化配置;
; f+ R. D% ?  t3)STM32L4 时钟使能和配置。- a" @& ~) M" y2 I0 [& |/ Q

; V6 @$ ?0 U4 j0 {/ c. g( E一、STM32L4 时钟树概述
3 D7 f5 h: c% m7 j) f! Z% }时钟系统是 CPU 的脉搏。2 Y9 L- w- t, q- m4 E+ e3 i) K
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
, o* e7 u' {2 b( `' S  D2 ~" E同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
! t4 q, ?* _9 n1 v. o; w所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
" K5 N& [6 g8 K( r1 r  `7 [8 s1 _: y0 g* _5 c7 p8 {
STM32L4 的时钟系统图如下:
- a& l! s  \: l& Z4 i! A$ _) ?3 w8 J9 [9 y: g- P( h7 k
20201205162651825.jpg

# A9 G3 E0 H! _' V9 D- k
20201205162724607.jpg

# x, ~. {! l" @: c6 O) A5 @4 U6 \' `' a3 }7 @
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
0 X3 \- s' v" K+ m" r! c2 ^其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
% W6 b8 K8 b! h. Y/ S8 r' a* w* D按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
- E3 G, S% F0 R1 m0 U按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。
  A' `0 _  N' \/ u. i6 t  c( j# U按上图中红圈标示的顺序分别介绍这6个时钟源:
) W* ~' Q* `3 F9 |1 p8 `3 h+ t① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
4 ]) n) G/ ?4 ?; E% \4 t& L5 t4 f② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
, R3 F7 l7 G. u4 Y4 h③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
% ]# C$ |) C" x8 Q& _& \; K④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。
; a: t/ p6 X3 S; a* N7 g⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
; K0 n' B& T* I: H⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:$ x" J4 @" \4 s9 d! D
1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:6 d7 j$ J6 |1 q$ h; L0 @% E
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);1 s8 h$ w" a8 s
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;& a$ Y' n3 ]* a- u$ Y
第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
2 j$ D: }( @7 t6 h$ D  [2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:4 z/ k+ e1 ^4 J0 b4 A" v
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
/ U; _( h2 I$ z7 ]第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;" i5 ]5 x% R/ U4 @
第三个输出 PLLSAI1R,可为 ADC 提供时钟。' g1 i' ]- n  g
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
" s/ T& h8 `7 Z第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
8 ~$ e% \4 {5 Z7 u2 N. k9 ?% `+ P- J第二个输出 PLLSAI2R,可为 ADC 提供时钟。* o. X  \3 l; S' t

0 j" i1 F6 N5 Y$ o这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:  r( J* F$ _& ]7 Y
20201205163714587.jpg

# g9 ]" B. t) D2 E+ }
! m) Z; S# J, v/ \' W上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
% s8 B; d7 s9 L- o) i% p. J% X. `% F$ \5 ~3 E7 u+ Y
举个栗子:" G- }4 w4 @. H  ?1 w+ \
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
) d$ Y- Y* I3 Z) ]" r# A1 u  A# P" q" @7 L. ^' h; e, k6 l
PLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz, `7 o' d$ j6 \- J' s& I
如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。8 t+ M% W2 m, p- e4 [/ G) W2 l4 \1 O
% p$ M6 F* Y, B! s- H3 n( r
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的  Q$ U4 u5 d/ J& o4 Q6 c; }, T# y" y
观察之前给出的时钟系统图中标出的9个字母(A~I):
  U+ P6 e5 @5 Q. lA. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;! y: r% y9 \+ E! Z
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
, Y% T: X% @9 T4 w$ UC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
) S( T6 R) k0 I- wD. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
1 ^+ M2 {; U( XE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;; }! n# J8 h1 _% d, @9 u+ e3 L/ g
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
) E4 w+ z/ p7 D! dG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;' S+ K) |6 ]' e) X6 x
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
+ _) O' |% S; }# FI. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。  g  V, x3 u- v) ^* ^
备注
9 Z0 H4 I1 N4 J( X  Z1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。: H  C) ~* |. z' Y* D' F- q% ~1 S
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。. e2 P7 x! K! ~1 O9 r
' ^" i: q0 n& Q1 P( `' F$ _
二、STM32L4 时钟系统初始化配置8 s- j; M, e* B8 h, U# _0 m
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:/ C2 m: e2 W/ T' y. Y
8 N) n9 f! ~" V- }8 r; V, B
  1. /*********************************************************************# Q9 N1 p7 \$ D  K
  2. 函数名称:void SystemInit(void)2 v' i7 H6 @1 W
  3. 函数功能:1)  FPU 设置
    ; }5 @9 T; q" W8 C2 A9 p/ q- O
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    ; W) M1 O/ C& s( D% U# l' e
  5.             3)  中断向量表地址配置 * E" U8 b  D. P
  6. 入口参数:无0 l9 O6 Q) P( W7 a( m5 A. ]9 m# d
  7. 返回参数:无& p5 n: Q- S5 ?! H4 t
  8. **********************************************************************/
    3 s2 B0 R* l+ y
  9. void SystemInit(void): n9 O9 S: y* a2 V9 `3 }+ k+ U8 F. y
  10. {1 \: b) I" F% A' g$ @- W! W
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)1 W" n4 o9 n! j0 B" @
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 ! o: A% r: |( \
  13.   #endif4 t6 o2 z4 z+ R3 G
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/
    4 V: y! \9 Y' S% J" s
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
    9 _1 C8 y$ J1 _
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器 - T1 n# X3 @# y( h; f
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    ! B' N  c- e. @' J, o  ^; o
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器, h. @. ?9 {4 T
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
    ) h% E! F2 ?0 C7 x) Y
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断
    2 ]: ]# u9 [" ]: R
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    2 z, P9 {; n2 v/ d/ J: I8 l
  22.   #ifdef VECT_TAB_SRAM
    % i& Y5 _' h' h& y7 [# I4 O
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
    7 Q! |. _( D/ U: D$ A0 V
  24.   #else% P# {* b, m# x& x+ ^) j
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */* m5 D7 P$ [" O) s1 a. g/ R
  26.   #endif/ f2 i: |& q2 C, |2 \/ x& O
  27. }
复制代码

; G! Z4 \/ {( A  y! A+ I( Y7 kHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
- I* F; K3 P1 n3 c
: w& ~- h* ~# o* ^" D  {. D
  1. /*********************************************************************
    0 \$ L. m* @+ `8 o4 T* u% k4 d7 y
  2. 函数名称:void SystemClock_Config(void)
    % B; I  |0 {& j' H
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
    : C; M; p' Q+ b! I' H9 p3 P
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    # y* V9 X* g' R+ q9 N/ D- K: h
  5. 入口参数:无" q4 |- [" m+ Q8 C
  6. 返回参数:无0 J% O8 @( a8 ]4 A' P- O# ?
  7. **********************************************************************/# P: Q! s; I& K3 h- s, G5 {' A
  8. void SystemClock_Config(void)0 X  v9 H  k' L* F
  9. {4 U" ?2 Z' T% M+ {, J& d0 N" k
  10.     HAL_StatusTypeDef  ret = HAL_OK;
    % g& J0 q% V) n! |
  11. ) W& O& |/ Q/ w1 J  a" R2 U
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;7 M7 N4 s+ t8 w2 Q0 q$ P
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;
    0 a6 c" Y0 u' z" g
  14. ! F9 |' V. r3 R4 n: T
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟  a& d3 x& k6 j5 @/ O: S6 H
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    : p! V% Y" B9 e; F
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE
    5 s7 s! ]3 P$ z. h
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE
    9 Q0 n, j  }* P1 ~; |# e% C
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL / M- B' H; Y: ^6 @
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    6 l9 b, [3 X% D$ c- t
  21.     RCC_OscInitStruct.PLL.PLLM = 1;8 n+ H4 Z4 C7 G
  22.     RCC_OscInitStruct.PLL.PLLN = 20;
    ' D, ]& r$ H- I' ]! {2 x: X1 c
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
    0 R% p! m3 p/ V! O
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;* p- O  i$ V# v; `# T7 n5 l
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    $ V! e$ E' p: S' @: b+ H
  26. 7 m3 N$ b# \3 V5 @
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数
    - P1 n  B) t" d; |1 G+ |
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ & q6 L; R- Z. R1 a

  29. . L3 k1 Y& [) v8 I* E2 s
  30.     if(ret != HAL_OK) while(1);# M' W" p: o: @- B
  31. 6 ?. [3 ]  l2 n$ k  x: T+ O
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
    $ ?3 Q8 q4 ]5 \& u0 R
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK% q2 s  i7 a1 a) s
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    1 I1 v+ H2 v9 @& l
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    ) Q& Q" D% U. ]0 z# A5 h3 M
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
      q$ s7 a- J& E" I5 N' _
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1) |( W* A5 l4 ]! {" ?
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    ' \% C$ ~& U0 |% M2 F+ d7 f, k5 W

  39. % e/ Z! ?4 q3 v
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数* Z' @$ a% S$ o) n6 x. S; P
  41.     /* 第三步配置后,可以计算出:
    8 {4 u/ S: a3 M; X: B
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
    + ]3 K, \, a7 T% X  A1 \9 r4 w+ A
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    " [# W$ z: u1 {2 O
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;3 K! G- t! a5 L9 [$ @( F
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
    7 d7 ~- j8 H# `( g; K* h, x0 W3 ^
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/9 u7 c2 }) c! g( _( u
  47.                     
    ' E/ L* _& W" O* m* G: ]
  48.     if(ret != HAL_OK) while(1);# [4 q5 i4 R: m# o
  49. * I6 x4 {3 {9 l6 a* Y
  50.     /*配置主内部调压器输出电压级别*/. {* v" a; k7 V( H  r0 v' M
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
    2 H. v' ^0 [, v* H8 H( T/ y& a

  52.   C$ g% ?, \. a( T5 O
  53.     if(ret != HAL_OK) while(1);
    7 n* D% C& ~0 X
  54. }
复制代码
2 V" E% Z6 ]! q4 E7 T7 S
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。+ o0 F9 }! g- _4 A  f
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:3 G7 {. i/ ]4 R4 K

0 B+ L, J+ V( z- i7 V. T使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;4 v! k' g  [! ?5 u' _9 J! D& R# R
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
# G- ?. Z1 j8 T  F1 W% M+ Y配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。7 Z# K$ q7 k; a
设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
% i/ m# B! u9 C7 _对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:5 R4 u& C) A* N9 T. L
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码

* L! i& X( G' rHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
$ ?- R: M' P) q8 n& V: Q1 t: U9 w7 b. G4 V* F& ^/ K$ D" `
  1. typedef struct
    + f7 d+ ~2 c/ i8 o% g) @
  2. {
    3 q# [$ D( I/ f% `0 g6 X. l4 r
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      0 D" a7 w( x' O! M% ^: F5 I" i
  4.    uint32_t HSEState;            // HSE 状态       ) j1 R5 [: {* k: f0 f) I* K6 n
  5.    uint32_t LSEState;            // LSE 状态                  
      o5 i4 U4 H0 P5 j/ P7 ?& w
  6.    uint32_t HSIState;            // HSI 状态      ( I' _. C+ n# z
  7.    uint32_t HSICalibrationValue; // HSI 校准值
    2 @* r  l. j6 i
  8.    uint32_t LSIState;            // LSI 状态       - B3 X1 ^$ @8 y8 j8 L7 N
  9.    uint32_t MSIState             // MSI 的状态  0 h% d6 P9 W8 g# D0 u( P; ~# M/ e/ ^
  10.    uint32_t MSICalibrationValue; // MSI 校准值
    6 H+ m( ^! T0 R3 G2 \3 Q+ `0 J; L2 [
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  8 Q4 x/ j& t- i$ M  ]% N1 Q. c
  12.    uint32_t HSI48State;          // HSI48 状态
    ! g2 h1 X1 k6 {: C
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    ! t! _* x2 [1 @: s) ~
  14. }
    7 Y- Z4 S$ O  x! r9 N+ ^) J
  15. RCC_OscInitTypeDef;
复制代码

' x0 P2 X6 e9 K3 r3 |1 k( o该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
$ c- b. O+ z+ I' M该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
0 p5 b, K+ s* E" Z8 |
2 F; e5 w( \+ M/ |' h( _4 z/ Z& L
  1. typedef struct
    . k% J2 `2 I6 w/ \( E9 \1 J
  2. {
    * R% T2 P8 z1 D) j+ g1 C9 m* Y
  3.    uint32_t PLLState;     // PLL 状态
    " ^/ p3 ?  v& E" J0 o
  4.    uint32_t PLLSource;    // PLL 时钟源 . s; D/ r( y& k# h/ O* ?6 j
  5.    uint32_t PLLM;         // PLL 分频系数 M
    0 R8 B) c7 J6 u+ P) V; t
  6.    uint32_t PLLN;         // PLL 倍频系数 N
    * F9 ?) `, A) j3 F: M- {$ y4 k7 u
  7.    uint32_t PLLP;         // PLL 分频系数 P
    3 `. W4 ^" f  {. K) z4 E- {" j
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    + S) ]+ ?6 P' J/ D' ?$ P2 {
  9.    uint32_t PLLR;         // PLL 分频系数 R # [3 g0 O/ k4 ]. s  Y% `" E! p8 t
  10. }5 g7 t6 n+ H$ s: L2 L
  11. RCC_PLLInitTypeDef;
复制代码
8 W9 U5 i( y7 B# F, n
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
) X  K1 k' [* ~# f9 `) v( ]5 X0 t, \5 p# Q
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:( y; h0 i6 F1 W7 U3 O. {

1 W' Y% P+ q/ U& X& P9 m! t
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码

4 O- X8 s4 n( E/ j该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
0 R: ]7 @  ~) ^( R% @) J6 J3 |
/ Y% N; f+ E4 Q对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
0 e' l, ?; o8 W" C0 u延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
+ B( Z' `& c6 a; }5 T首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:/ {1 z9 C. `& ^  @* t  D

9 _& j5 K4 j2 M5 Z" w  I
  1. 位 15:14 VOS[1:0]
      D; z& D+ N2 V
  2. 00: Cannot be written ! Q3 D9 B& U: w" _7 j' D
  3. 01:Range 1 + b- |; f( Y4 k7 k
  4. 10:Range 2
    ; t# @7 O+ j7 w  z( z$ t
  5. 11: Cannot be written  
复制代码
- d2 H0 B: B7 q( b" a
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
( m& `" ^8 b' Y/ U" X2 b  R. b0 U配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
- t2 n" c$ R# r3 m( m$ y6 G1 r& [# s5 m8 n3 C# A! ~' N
20201205181115783.jpg

1 Q, g* w- \9 v9 x
* ^3 f( a  O4 C0 R. q可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。  [/ g' Q7 W8 g9 ^" l

! }7 b: V0 S; E# r! u  u/ v三、STM32L4 时钟使能和配置$ b" G* _( y1 G1 p* r
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。
/ k6 E0 F! X9 _! s' O, y' ^6 E/ a. T* @5 S* J
通过STM32L4的HAL库使能外设时钟的方法
, F( L% ^. Y! z, c/ G在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。1 V& ?2 M7 q( F
举个栗子) f3 [( h6 {* A: V, |8 c1 e
看看 GPIOA 的外设时钟使能宏定义标识符:; Z7 Z$ C/ C. H0 ^% T+ W/ L
" P4 B. C! p+ r9 q$ P
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \
    & Z7 I9 J) [* E9 d1 C( g
  2.                                                                      __IO uint32_t tmpreg; \ : |' `' T( y0 }. r" z
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \   H! i* n! s5 J2 |+ ?) u; }
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    1 G3 b+ [( ?' M0 B4 n
  5.                                                                             UNUSED(tmpreg); \ ' ^+ p5 e4 E) r+ ^  ^1 q
  6.                                                 } while(0)               
复制代码

1 Q6 y" L. s. c# L' F: l( D! t- y' _
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
( u! u: J; `9 H, d' f  d: O5 r
2 a# _6 d- g, x( T) |2 \+ r* c
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

7 u$ [% N# X$ T6 J' F8 `% s这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:3 K# t7 [. v4 `8 X0 ?4 e, d
4 Q1 _% b- Y( |( P4 V
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U) : f, \- c4 H7 B
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) 9 g2 T9 ]& s5 u. c% R- L
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码
0 v  V5 K9 @' F) z
上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0/ T" |. d( b+ Y6 Q6 H1 I7 X0 I
描述如下:
* O6 |8 v- f  f. @
+ x+ c$ ?2 `, e( g8 i! O4 ]; S
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 / ], O7 Q" \4 p& I" ^) i
  2. Set and cleared by software.                       // 由软件置 1 和清零 - {: S1 D- F. F& J/ J$ i
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟 ) D) h" b6 d$ i' Q, A& l
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
  r% h' j4 D- z
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:- K: ]# q1 R6 ?: t7 Z$ k
/ V! g+ V( z0 g6 q- D
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码
: ^) e$ U3 c' E1 o* i# k9 T
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
    : r& e& ?! a$ T/ |  m0 q8 _
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 2 d" }4 ]. x& Q. U
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
! q5 @! Q# I2 |. e' y
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            5 q9 M" W% G  }7 m/ j6 u
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码

& z+ L, N% a; g+ b2 a同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
+ Y! n( ?$ _  z- q2 K* [( S
* T! P; j0 S. J5 K$ h& u* @
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    9 M7 c6 U* s/ C5 X: {3 U$ L; c
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟 , S7 o7 G+ U, o# n/ D
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码
: _" b: n6 o8 V5 D% E* R0 m
( f2 b' t% [' u& F( d
) ^# e9 n9 `( K

0 I4 m" u% X; }' @* i
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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