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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容
5 K% G  ]5 a, m& c1)STM32L4 时钟树概述;
+ E8 w9 L3 D* M! j" b+ n2)STM32L4 时钟初始化配置;7 F  u8 @! F2 O6 d# Y5 J
3)STM32L4 时钟使能和配置。
3 }# N; V. h2 t/ W5 x4 R! I& V9 J# W: w3 E5 o& }
一、STM32L4 时钟树概述
' c6 v+ V- C1 \时钟系统是 CPU 的脉搏。3 I( X) o, r3 X1 E/ G" q7 N' |8 }
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。  K$ M+ O' l# L# ]% d
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。; e' A; l/ u. i
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。% Y4 @* a, Q. O2 ?5 c
6 U! T' n; Q( m3 J3 t$ j! T5 C
STM32L4 的时钟系统图如下:- P5 p: l$ F: E
' C; R$ ^0 M# o$ [
20201205162651825.jpg
1 h+ ~' o4 B! v% T7 u1 N  o4 K
20201205162724607.jpg
! h  ~% n: p9 v: a4 B

# t5 K8 B8 g3 e- \' n( x在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
$ e3 f' O) E# i6 ]其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。" N! A  r6 m4 g
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。9 ?6 ?" Q" b& P, T0 T7 R
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。
- i% J6 B( i: D* \2 }$ {按上图中红圈标示的顺序分别介绍这6个时钟源:- A# j2 ]% x2 Q7 v9 }
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
, _1 r6 N. g7 `3 h: G- b, A9 j② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。+ H) `2 `& x" O7 C% _- G
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。# l, D7 M! V# m6 z* z- q. a
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。
3 |+ k* P. m- G8 x; O* O& k⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。: `: Y7 B, W& J. S- d( D
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:6 H; b$ `; s' C, [1 r$ Z9 \
1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:1 |( B2 s0 q4 ^1 ?( h
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
2 i+ c5 @$ b; ~$ ~" }* }: u  Y第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
9 t- I. |0 ]* C+ F2 M) M第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。0 V7 B  v7 F/ m# k
2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:. X* l" v  e: G* [$ v0 M
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;0 U1 r0 l* d7 w1 ?, k8 f
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
' L3 d; e* |/ H: o+ k0 G第三个输出 PLLSAI1R,可为 ADC 提供时钟。9 j3 `1 ~+ \9 L9 o+ d$ s
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:+ e: h4 c; H( |" w; j
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;6 ]1 D  m, v8 k4 @/ U  `: n5 A
第二个输出 PLLSAI2R,可为 ADC 提供时钟。
7 k* b9 _: G! Q  u" o
' m7 z0 N2 {6 x% m/ K  j1 o这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:5 x( s2 u* v2 h* a( x+ U1 R
20201205163714587.jpg

5 s2 q6 b" z7 O$ m, }) E: \$ H
$ K, y0 p6 T; Y  Z0 A# j$ O+ H上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。( o' ^2 a7 i' g+ \/ m& j$ M3 l

' I! v- T3 J, G/ Z; X9 j- m举个栗子:. Q* g3 R3 D+ F2 t
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
$ ?  ^4 x- s- q1 {9 \/ X! ~. U9 B( R: h; T+ o
PLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
. H  D  v- Y# F% h& T如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。
( z! |3 d" Y4 S! w- S" L) d
- R( N% C$ _. r7 Y0 S' W5 G下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
( |/ m3 {% U, H8 M0 \观察之前给出的时钟系统图中标出的9个字母(A~I):
3 d! r6 V/ Q6 u4 ZA. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
9 l/ v% ~4 f4 tB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;* |0 Q0 w, v9 B& `5 v
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
* y4 X  T! {) Y7 Z4 Z" VD. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;/ r% V. y! n" ]" m+ ~5 l+ U" j
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;
, Q/ [& H( z2 t! R; jF. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
7 S0 Z# n2 d: x0 `* gG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
: k# Y$ w, |: U  Y8 s. jH. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;$ R* P  ~4 R8 y0 R9 a. q7 f6 N
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
6 L) u2 S! i5 p0 u备注
; z" t# J/ z2 n2 F# m1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。
5 f  l- e9 k: P# `5 C  [* ?2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。& b; \, G4 t8 k6 e# V8 S9 U) i

% v* B0 ]; c! }" W3 p二、STM32L4 时钟系统初始化配置( C' w( L. o$ q; C; k: w
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
6 U0 J1 v' k6 P' Q  l" l  E. y( ]2 U$ ]- n: S4 f+ a" w
  1. /*********************************************************************# @. t3 n; {$ k* ^+ [5 M  A
  2. 函数名称:void SystemInit(void)' g# ?+ Z- l( b
  3. 函数功能:1)  FPU 设置 ) K. A, y. G( q3 S" N5 i
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    3 H  j& Z% }. X" m
  5.             3)  中断向量表地址配置
    2 O8 h+ O8 ~" `8 B/ L, ?8 ]+ R
  6. 入口参数:无
    7 z# N7 e" Y- N' A
  7. 返回参数:无
    8 i- r5 S+ _* i4 I. S% d
  8. **********************************************************************/7 V) o; D5 P# }- E
  9. void SystemInit(void)/ i" B3 b* X: p: R' C% R
  10. {1 N1 K4 v6 N- ?6 I& ~( Q( j- Z3 U
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    : f4 T- u; Q# z, K0 |5 Z7 F
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
    ) u$ U  m# Q9 W& X. P( z2 D
  13.   #endif/ u4 e% {, c" ^; ?
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/ ! o2 ?3 z4 y4 s( w5 Q# n8 X  s5 V
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
    ) z$ n+ w% W. T- Z9 F
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    * U8 v7 {: u7 r2 }
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    3 }& I9 I* N: }
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器
    8 E( c6 V& R# x8 b( _: ]
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
    5 F  c3 @9 F! |" @
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 ; J6 i% C/ _) n6 h1 n" d5 x
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    : t$ g" v! u5 H$ A7 ~# z: o( f
  22.   #ifdef VECT_TAB_SRAM9 P9 N/ n6 ]/ R# ?9 K4 d; @
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */% {( n5 o- p% I: ]. x$ t
  24.   #else
    - B4 q" x7 E# j4 j( F, z; A! `
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    ( Q" V2 |- g+ f' U  M8 Z
  26.   #endif* \* `5 r( y! t; n. |3 R
  27. }
复制代码
8 F9 K. ?; ^1 o; ]7 H0 T0 u$ W2 _0 n
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
9 L  }4 A1 @" t  Z" g* U: V: i# J
0 ~* ~5 M" b. H, G; Z
  1. /*********************************************************************
    . x; a/ ?: }7 A3 N" v
  2. 函数名称:void SystemClock_Config(void)4 ^/ E* Z1 G; G0 `4 w+ e4 H4 ^
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR6 A7 H7 h8 E' g/ c% D
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    - i2 j% \/ ]' L. r+ Z3 n! ]
  5. 入口参数:无; F5 t# C% W0 f. v
  6. 返回参数:无
    6 G7 S0 V' B. y$ a6 u
  7. **********************************************************************/# t. {! }: \" s' g; B
  8. void SystemClock_Config(void)
    7 ?6 B; u/ Q$ R* p
  9. {1 Z& A' ]4 g# Y. t" G) J4 v
  10.     HAL_StatusTypeDef  ret = HAL_OK;
      }; b. Z! \6 t. b

  11. ! R  }' a! E( g9 Q; S; v9 [
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;& U5 k+ V/ ]. }: o' m9 M
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;5 x: O2 K% S' O. ]7 a2 H0 N

  14. 5 U0 M- E( t% z# q& K- X( g) i% c
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟  `0 }" v1 M: Q' L6 t
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    ! D" H# F% u, ]0 @
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE " `& R, m+ c# v
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE & Z2 K" a: E! q- G
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
    0 H- _1 v( h" B. }0 {4 N% `
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    0 F* c, C- q" f$ t
  21.     RCC_OscInitStruct.PLL.PLLM = 1;
    , Z1 L2 y% O/ z4 A5 m1 r
  22.     RCC_OscInitStruct.PLL.PLLN = 20;+ l; s% x" C" U1 e& }/ _) F
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
    ) N/ f; B; k: j% l- G, H# \4 c, j8 {
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
      R3 J: P! P1 S
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    4 o% N. k8 a% m
  26. - O- \9 O+ m+ S8 U$ X8 l1 l
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数6 y* T: Y- j, A9 n; `
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ ! \2 D" E5 W2 G* d6 x$ _# f

  29. $ s! r6 T# t' [# A# D: d4 m+ W
  30.     if(ret != HAL_OK) while(1);" s% k5 N" x1 X3 S- L$ V; X0 R( U, x6 h
  31. " D1 ^0 _4 B$ W8 _; m2 O2 `
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ # W. C4 ~$ J! d- \8 p8 u6 ^& T
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK+ U& C$ T& }6 l8 \( T6 S
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 7 e! u2 I( I$ l& E( z
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    : a- J$ h) j4 {8 s/ v
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1( f  b! L6 _9 d+ n+ Z) I+ C
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1
    4 t# h, W$ ?9 j! R8 u; g4 u0 M
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 1
    3 ]! o1 H, q9 E5 L7 o- j6 ^

  39. - o  V* u/ ?* J/ Y
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    3 v  k# V: g9 F1 Q) h
  41.     /* 第三步配置后,可以计算出:& Q% h, ]  o& [$ P' i, Z4 K9 t
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
    $ ^1 k& c% g& ^  o1 d  Y
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    % m: q! i' A4 J  ~7 [
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;+ @1 y' f3 O4 |; S
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;+ e. h' m8 ^, @  Z& ]
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    3 c, J" G4 J# o: b9 _
  47.                     
    ' D; q# e/ n6 G, v3 H+ H3 ?
  48.     if(ret != HAL_OK) while(1);- p# V3 C& b6 P
  49. : E; u+ R6 R, I0 @/ E
  50.     /*配置主内部调压器输出电压级别*/
    ! h9 K( b! y3 p. G
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别" I  U4 g7 ]0 c3 d# ~& B; E' k0 p

  52. 3 q" O/ \  E4 D3 }8 V: `) z7 k
  53.     if(ret != HAL_OK) while(1);
    + I9 f' K# N) S
  54. }
复制代码
) X' t4 b$ \- h
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。9 v4 f/ i" q9 P( d+ D# J# M
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
: t+ J0 y8 q% d( a, _* W; a  U8 M% r5 v" p; E3 I0 u
使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;6 @- ~8 L+ D: |" y9 n: j5 l* v
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
! I! F' H) n+ e$ P- J配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
0 H. l" I$ C5 M8 Y/ y6 L1 C1 K设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
  W7 G2 q4 Y3 X- E对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:" C' s! f, C9 r% g$ T# h2 ~! G
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码
' u6 g6 |2 o' V. q7 s5 f2 N, G
HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
  i0 M- R$ G. g' D: A
- M# _, B7 l& ^' i4 H  d. m. N
  1. typedef struct 3 O; Z$ k  A$ w: _% l
  2. { 8 g4 M" m7 T/ @. j+ H% E' t
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      # A" l6 A7 N  q2 ~
  4.    uint32_t HSEState;            // HSE 状态      
    / [% ^$ z3 |% m# h% e* J, c
  5.    uint32_t LSEState;            // LSE 状态                  
    - f+ m- D% d4 a, H/ `
  6.    uint32_t HSIState;            // HSI 状态      
    , d$ m) g7 K9 F
  7.    uint32_t HSICalibrationValue; // HSI 校准值 3 L4 a, [) C6 A: @  x
  8.    uint32_t LSIState;            // LSI 状态      
    & w4 C  k$ V/ h8 U" ]" |2 }5 v2 h
  9.    uint32_t MSIState             // MSI 的状态  0 [  x6 F0 e0 |( T
  10.    uint32_t MSICalibrationValue; // MSI 校准值 9 ^$ h  O. U+ I) J$ _  I9 O
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  7 u; [3 n1 U( K2 k  D( @1 `0 J" [
  12.    uint32_t HSI48State;          // HSI48 状态 ' n/ I5 ?8 W0 [7 n0 V2 N
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置 ( Y; ]4 X% m5 T0 Q2 u' c" k* y
  14. }
    - g$ `$ @8 p* B& N2 A
  15. RCC_OscInitTypeDef;
复制代码

' {) y1 C( ?! N2 g- b该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
# Y2 X6 G: e1 k4 X1 {: H该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:/ y8 x3 B8 ]5 y( h% H, q
3 {; S) ^  G/ R; J9 d( c
  1. typedef struct ( c. h/ V1 m- M2 E3 U7 f
  2. { $ a+ z; f+ {$ S: u
  3.    uint32_t PLLState;     // PLL 状态 7 {$ _* D" \/ W+ ~
  4.    uint32_t PLLSource;    // PLL 时钟源 7 _8 l. \6 p" x8 _
  5.    uint32_t PLLM;         // PLL 分频系数 M 5 ~" k6 Q# t0 L2 W% H/ {
  6.    uint32_t PLLN;         // PLL 倍频系数 N 6 A0 l+ p/ i! [2 t# p
  7.    uint32_t PLLP;         // PLL 分频系数 P 1 {5 ]5 [; H9 k% F, I+ |- a
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    ) z) K' d  y* w1 O
  9.    uint32_t PLLR;         // PLL 分频系数 R
    $ p4 s) E, p4 h7 {5 Q, N+ T! G
  10. }7 p1 f# B) H/ V2 L: U
  11. RCC_PLLInitTypeDef;
复制代码

6 M, L+ d' k& ^+ ~5 _) |5 `该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。+ J1 d# b  F! Q/ v( [# H, _
+ g% w: i* s4 S& w" V) h
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
6 L0 a1 P% Q( W: v' X0 _5 K) {7 A* S3 m( W$ g+ v
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码

, d1 Y  V4 ]4 y该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
5 N- X! h2 x7 J  S% J' f0 f, m, }8 ]7 M* @2 s' N
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
. @; H! r8 }: j$ `8 f7 @3 F$ C1 @延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
, s% j( s8 G" ]8 q2 a2 V首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:: ]( @6 G% d/ Q6 M3 I4 y

- k, J# {# D9 [! s- g+ ^
  1. 位 15:14 VOS[1:0]
    ! T' E* d  ~  e& d& s
  2. 00: Cannot be written ; T) D; q+ s+ r: i
  3. 01:Range 1
    5 d. `% ~' i1 h: o. @- }7 S6 }
  4. 10:Range 2 4 r5 z3 `# |8 H- q6 V# _0 Z
  5. 11: Cannot be written  
复制代码

7 B* y7 k$ M& n级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。( g9 k" u8 H+ f- }3 u3 K& d9 C
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
1 z' ]# O7 w; D6 t/ N" M  n9 Z* b. e
20201205181115783.jpg
7 W& m6 M9 ^8 Y
8 v$ J3 X: B" T- t3 g" |; s
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
; l. F- T8 W. [9 J7 ^. B: @5 J; t) n3 Q5 |5 D/ A6 m3 ]
三、STM32L4 时钟使能和配置9 \# X0 Y0 u2 v/ \0 h
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。2 w% D' _) r$ Z

0 e9 e% C6 |" X5 }1 B通过STM32L4的HAL库使能外设时钟的方法; }- t4 x0 S! n% e
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。$ n% I" m  \" N% ]- H. t+ l9 {) A
举个栗子
% C, J( R" y  v4 K( C看看 GPIOA 的外设时钟使能宏定义标识符:  z5 _" t$ i4 S0 m  q7 G, C/ t. P
* _4 a5 L  @1 X& M3 J8 g
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \
    " C, X" r$ n) y6 v* Z
  2.                                                                      __IO uint32_t tmpreg; \ 9 Y1 W: R/ i9 ^/ z0 B
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    9 ^$ ]. d4 r, S
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    " A' k) X( [% A* b2 L: N. g
  5.                                                                             UNUSED(tmpreg); \ 1 w* C4 f- V- c6 r8 w$ ^3 R( F6 C
  6.                                                 } while(0)               
复制代码

+ e7 |$ ?) Q- Z& K1 @7 f/ A! X2 j/ e+ e+ C
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
' U" ^' u" T  I' Z& @2 x0 }# ]/ ]
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码
$ D4 G$ _; V$ h
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:7 c3 F$ Z5 d1 W

5 L. ?4 D. k7 N  ^/ h: q/ g0 d
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U) , A3 [" h: J. G% ~  v5 M
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
    % O' u/ O& D' c* d4 J: K
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码
% p2 ?6 ]" o1 _1 r3 ]1 X
上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 01 d3 k6 y0 v6 E# Q) A! c  j
描述如下:% t. m( d( V' P: a% G/ I# l) w

8 G- J$ w: {2 A+ ^% {, V
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 3 Q; C0 n5 C! ]5 ]  l
  2. Set and cleared by software.                       // 由软件置 1 和清零
    4 l0 z' Q% R# B; n8 A( \) Y
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
    ; `1 Z& _2 L* \! y: B
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
7 K. ~& c4 S5 i8 ^
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:- _+ s" S5 G3 [+ Q
5 {: t* ~$ ]4 j$ I1 u$ b
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码
6 s, N; m0 d/ |$ s! W8 p2 Q
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
    1 p% ]! J& Q* y' R) M
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
    ; b% j, w- Q/ V4 v$ g/ n* H3 C+ i6 V
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
8 R; q9 Y5 ?( K
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()              Y# ?, }+ B/ v. k2 z' g* A
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码

9 J6 U( C0 N/ W& c4 Y. Q. S同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:8 n; c4 ?% j3 g( U8 D+ @5 L

& O' N) j8 M! ~2 ]6 R; i
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    5 p& K2 G5 |5 O: I- v
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
    8 ]+ M% j4 \3 w0 @
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码

' a' Q" U8 D: [* Y! {  W% R# z2 e0 U* Q& Z2 c6 s6 s5 o
) L8 H% C1 P$ ^* A
$ G5 }; L0 S- T% v# A$ ^* V+ O/ U7 }
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似分享

官网相关资源

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