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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容  y  t  g. x' \9 L& ~2 |
1)STM32L4 时钟树概述;
. c) D" x/ G/ n" C! X2)STM32L4 时钟初始化配置;$ \& D9 w( K& C3 V& u
3)STM32L4 时钟使能和配置。
2 ?1 k) o- B/ l$ K7 E$ S( {# o( I! \
一、STM32L4 时钟树概述
3 E$ W0 q5 J$ Z7 m% e) \  n/ k) P时钟系统是 CPU 的脉搏。3 |; e8 C8 K; \+ R, E. C1 ~) m2 z3 |- C
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
1 W& J' F( B: v' i8 d同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。' H& C: l% X- o9 Q' l1 c- u
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。  E4 B! ]" t) L, l4 T% Q6 [% ]. M. H; w
! @' H4 V6 `' U8 G; _" d, c
STM32L4 的时钟系统图如下:
4 [% [- i5 q; [* |, V5 H; z; B6 D9 n9 F! y+ P2 Z. k. p
20201205162651825.jpg
* ~3 D: G6 R! B% [
20201205162724607.jpg
2 v  h: R" y+ }& n; ^
2 @* O' _1 ~: i" Y9 P9 T7 g
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。+ i1 E. b& P5 G2 @' S
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。8 ^/ |! @2 }: r
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
( z( Q2 l3 J( {按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。- N! F' Y; Q7 H, `0 F9 W) M
按上图中红圈标示的顺序分别介绍这6个时钟源:
$ I) w  S0 d: ^2 u" ~* e( i① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。/ r& F) M) c* J: `6 R) Z6 |
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
  Q- j$ E9 J8 v7 r7 X  Z9 f) p: Y③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
# c" |3 `: n4 x④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。. z( q- [, w# a8 h$ a- `
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
; L* k% V  q' i) g) I  ^6 Y+ O⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
0 j* g' m( s+ D1 a0 x5 y; g1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:0 M8 |' }$ x+ x+ }
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
2 Z7 u3 q. d5 `  O/ G第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;" ^% f: V3 R6 Q8 B- ?
第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。. {' n/ c3 i% K+ L1 i) N, y, V
2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:# J( z  f4 o" r$ {
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
' F7 K5 g  [2 b" N: l3 M第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
/ ]( [, e( c4 T, l% C第三个输出 PLLSAI1R,可为 ADC 提供时钟。3 V. K7 e/ J0 ]& L: P  g
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:+ c, ]6 Q6 h9 Z; D& K
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;' M) c* d; F) s1 `. |. W
第二个输出 PLLSAI2R,可为 ADC 提供时钟。
* a' B/ J- @. i5 W1 [2 a7 Y5 K
7 @0 l0 {. m. q这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:. c! k& Q6 s; t5 w
20201205163714587.jpg

% R/ K: H0 Y  g; ?) N0 P
  h$ w8 M( a! _! d# E1 |8 O) l上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。0 I% }. L: l" f: K# m
# s0 I. X! f" d6 t
举个栗子:
0 R: x( h2 Z8 M3 F& l$ V: _" }+ [( T外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
. M3 w6 g2 {2 p% T
9 c  T9 I5 a5 L; Z9 {/ ePLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz$ Q1 g/ A) ?, A6 O5 J. {* C
如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。! K$ F1 ^. X  k  h
1 S( [6 s4 R. ~
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
: F- j% s0 A( J观察之前给出的时钟系统图中标出的9个字母(A~I):% R% E! o  K& B3 u- n6 y: @# ~
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;3 u9 o3 r+ D$ Q( T. v( |
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
' a, u. U- g3 VC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;* @+ c* e7 Y7 q1 d4 T3 z
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
8 Q# m  C0 d* nE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;
/ h2 ~$ N7 S; x, r2 kF. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
7 l( x1 u, g& M. `; @& pG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
* ]9 ~$ w: K& l0 ]H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
& N  n& d3 ?# @4 W$ G9 iI. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。7 y; N+ N5 V: F$ @8 [* g
备注  l2 a+ B9 a8 }5 Q4 @2 }2 m! k( @
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。5 Y: p9 j: Y2 P- p8 Q! a2 T7 i+ Y
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
( H, L( L% `. c- q, L
1 Q- l9 G) G+ H' y3 Q3 A* w6 G$ z二、STM32L4 时钟系统初始化配置2 D. e0 E6 @, @8 ~/ P: U
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
4 Q' @$ M/ l4 W% h, b4 F  k" L6 ?+ N; h
  1. /*********************************************************************' e# W8 x3 j$ z
  2. 函数名称:void SystemInit(void)
    9 H1 m4 b( V* Q2 z/ |% Z7 ~! v
  3. 函数功能:1)  FPU 设置
    / C; w. ]) K8 f1 m
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI) 1 @' X7 C2 }/ |
  5.             3)  中断向量表地址配置 . A! O; f1 r) C$ v4 o
  6. 入口参数:无
    7 n: O8 Z5 o, C- f
  7. 返回参数:无) p: Y5 M* U0 _- J, c6 K& {% _
  8. **********************************************************************/
    5 S( v  C+ x2 r8 @8 D
  9. void SystemInit(void)- m" t3 Z. c$ [$ T4 N
  10. {( ~/ U- @  c5 s' c) @8 ~: [
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    ! t; |/ q" r. a8 d" d( k. Q
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
      n$ V8 K& l8 Y
  13.   #endif
    : V2 R* b! i' K# h, r; p3 t& Y% K
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/
      N2 L# E3 F% W$ j% e9 i- g
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
    3 [$ g2 _* J, ^! ?# T
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    / I% S) A, N, {# `! a+ A+ h
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           6 c/ ^! Q, ^2 r# n% h
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器6 r( z, z8 |. V( g4 G
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位
    9 a6 T& q- K6 }. }2 _  n
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断 4 f, G! W7 v$ D# c; k3 m
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
      y1 d6 \/ j) K; l
  22.   #ifdef VECT_TAB_SRAM5 t2 ?6 M3 M  S- u2 s3 g
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
    4 d- V( K2 r( E( c; Z
  24.   #else
    ( j+ ?4 ~1 U1 X. D; D/ |
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    / I# A$ F$ l5 n5 d* N6 a5 |1 C& a. X
  26.   #endif! k" ^. t. }" F5 C" u0 _( e; W
  27. }
复制代码
4 I$ F! i. f1 U
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:: M: Y: i' C6 C5 |, o

9 E8 u8 ~. n6 ~
  1. /*********************************************************************. v! f+ l3 X) x) Z: w/ M
  2. 函数名称:void SystemClock_Config(void)
    # k$ B) N! Y$ u
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
    - ?- H9 P. @0 \* B+ X8 ^: L
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    ! |) P) I' c  O
  5. 入口参数:无
    9 _. m" S7 p( H8 o
  6. 返回参数:无
    $ {5 E& l7 K/ k
  7. **********************************************************************/
    % f0 s3 z" ]1 L# J3 ^
  8. void SystemClock_Config(void)
    . {  ~6 B+ e/ k. h5 Q  {5 k6 L7 Q
  9. {6 Q  p$ a6 l$ V+ q2 A* B+ i
  10.     HAL_StatusTypeDef  ret = HAL_OK;( J4 u) r  g: ^9 ]  s3 K$ o1 P

  11. ) s6 s4 [+ R' M5 s- b, b" E
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;  I6 A0 B5 b/ m4 _* {7 s+ Z
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;" k( E+ }! j. X4 m

  14. % Z: r$ ~8 Z/ ~* Z
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟# w+ Y1 J2 M. P1 w3 H5 ^
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    / \3 R0 N. Q7 R0 m0 j5 N
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE 9 y7 E9 V7 z) X1 b8 c
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE
    9 U. X4 S. X5 C) r
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL - K6 A2 C# E4 g
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE * L; U7 M( v! x
  21.     RCC_OscInitStruct.PLL.PLLM = 1;. d5 }# z, ^- }) l5 }6 D7 r! F
  22.     RCC_OscInitStruct.PLL.PLLN = 20;2 U8 P" B/ L3 w7 W1 ?* H4 V" M+ W+ F
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
    ' y& a* i) @" R
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    + S4 s+ }- P: H
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    0 B: P9 D7 ^7 Z3 t/ P

  26. ; n) S* ~; `' X" D8 @
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数" D4 y3 [* R  K/ C# O
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
    $ S4 |& }* `( `
  29. . Y5 t4 ~6 ^* \! N+ J' F/ x2 v1 s% X2 N
  30.     if(ret != HAL_OK) while(1);" F: w4 n' r; D5 V1 U" D

  31. - X- t) f  o) A* k0 W5 t  N9 k
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ 7 r! O: @# S$ G
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK4 j# _/ p% D& r  [& l0 l
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    - I) k) }  x- }
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL8 A+ A. [( P  E
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
    & ~1 F2 g4 I' K" ~; R7 `: f' u# F% d
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1
    , {1 W: k$ c' a, m
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 11 }. f6 O( h- d; ^& @% I
  39. : T' L& i& V& X2 f  L8 j
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    7 {4 S- P& F; E, m0 c
  41.     /* 第三步配置后,可以计算出:( u# }: N- h/ M1 c. T
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
    2 {: J/ Y$ D/ V" F
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    ) u& b* \% ?( F) l- ]6 q' F
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;% m6 @4 s- _; @- ?' ^
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;+ o0 |6 R( D8 y1 q& z2 c. Y
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    ( u8 u. C3 O) g& j/ F) F
  47.                     
    , @5 p+ d. L0 L4 K
  48.     if(ret != HAL_OK) while(1);
    & b& M: [9 F/ s. U

  49. 8 p& q9 W- x3 @0 j4 B
  50.     /*配置主内部调压器输出电压级别*/
    4 a, `3 p) U" i% n: B3 @- G$ h5 V
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别
      a8 B4 W% x" T3 I7 u% e
  52. + i4 d6 r& m( y) n! E
  53.     if(ret != HAL_OK) while(1);* q1 Y9 W1 c2 G* S: M- I. R
  54. }
复制代码
7 x; s; j! [! H9 r( W
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。
5 f- F) r9 @" W9 n! W, ]" S0 O使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
, F2 X9 H+ {1 Q" `. @/ K. e, m3 _
4 A0 ]- `9 V+ v$ z5 ?3 i$ T+ U$ A使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
  o! J; _/ Z: j$ z: Z% {: M+ G+ l配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。& V) `; ]3 Y  g! k
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。0 z- f  I. }0 L/ [
设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
5 Q9 e6 o9 c* O对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:5 ]& K9 Z0 J& ?: ~* C( K" G
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码
3 P7 m+ m6 ?/ _4 S, u
HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:0 b8 i$ t5 |3 x2 Q* K3 c
% m7 t: N" `( J0 c/ |
  1. typedef struct : l: g' y4 V2 n( ^( K/ e- a! c
  2. {
    . ?0 b8 `" g& i: c* x1 p) f: [
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      
    4 A1 \' |3 D+ `
  4.    uint32_t HSEState;            // HSE 状态      
    ; H  q9 r6 |* h+ S$ R3 T7 k
  5.    uint32_t LSEState;            // LSE 状态                  
    . U1 Q8 R* Y5 n! s+ v; s. A8 R( d
  6.    uint32_t HSIState;            // HSI 状态      
    " {$ o6 o0 f9 q
  7.    uint32_t HSICalibrationValue; // HSI 校准值
    * u& I/ Y# Z4 t0 B2 k2 @! S
  8.    uint32_t LSIState;            // LSI 状态       9 v3 N  N1 V8 V  G& l, C  ]; D
  9.    uint32_t MSIState             // MSI 的状态  ' _: b2 s" T& [/ z  A% W$ j
  10.    uint32_t MSICalibrationValue; // MSI 校准值
    9 \, |6 ^% D: v  B0 i
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    / H# {; Q9 {. [" T9 y
  12.    uint32_t HSI48State;          // HSI48 状态 * L7 e+ B9 r& K
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    $ P! V9 @. w$ f  r7 \
  14. }
    $ v4 T1 H# G: K: T
  15. RCC_OscInitTypeDef;
复制代码

3 U9 n7 w$ e* x* D) o该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。" ^, J2 c, N  A/ Q2 Y3 N( t
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
" g4 K: V# u- n+ L. f. ?& K* ?
+ ^$ x3 ]4 }% {! e8 x" x
  1. typedef struct
    $ C8 C$ i! x% `$ h* L2 z+ Q
  2. {
    $ `/ ]$ k4 N! }- j5 ?
  3.    uint32_t PLLState;     // PLL 状态
    5 c, \8 L+ E& }2 \
  4.    uint32_t PLLSource;    // PLL 时钟源
    : S; D! m: X( V& m+ R
  5.    uint32_t PLLM;         // PLL 分频系数 M . ]# \' m/ v- S$ o. Y! U
  6.    uint32_t PLLN;         // PLL 倍频系数 N
    9 s7 ^- f& v) `( S. M
  7.    uint32_t PLLP;         // PLL 分频系数 P ( C" G% b! A# O
  8.    uint32_t PLLQ;         // PLL 分频系数 Q 0 D5 }8 W, R' _- t( E* f. w# ~
  9.    uint32_t PLLR;         // PLL 分频系数 R
    9 \9 H, o' [. ^8 O- a* |
  10. }. T( `$ ^  Y# ]& l
  11. RCC_PLLInitTypeDef;
复制代码

- k( v- m2 X6 Q5 m5 `6 @该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
# H; y  q+ v4 i4 s$ W* `  O# p  q9 H8 X8 m/ Y- A& S& n2 p
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:. K9 B4 h0 }* a1 j4 C

. z" F/ C+ d. F, G/ z" _* u
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码

3 W/ _, y% v+ J: g9 S: Z& R该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。8 k& B2 A1 i7 n

8 [0 m6 `; b/ g. V对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的: i7 @: e. |# X
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
" E9 z: \5 z% c4 ]. H2 E: Y首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:
4 _8 e" l) A# Q6 `" h8 r# e# ]+ U/ R0 v+ \. d* z1 E
  1. 位 15:14 VOS[1:0] # y, z, q1 C( ?
  2. 00: Cannot be written
    5 y) u) R( r! M% b) w5 E# p
  3. 01:Range 1
    1 n; d( s( s; q- Q/ W' ^1 J
  4. 10:Range 2
    : Y1 f5 S% G3 o& E6 ?3 L  \* E
  5. 11: Cannot be written  
复制代码
1 N# Q8 s% O6 t5 q/ E5 @) y
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。% Q8 N$ ~: i$ r: O0 G5 ?5 ^
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:6 H. C" U/ ]9 }( g& l! L3 |6 H

8 `2 ~7 q, b$ z5 n- r
20201205181115783.jpg
' H) W% v/ q( \4 ]7 @
9 N- c3 d0 o4 `0 f0 K
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。. W" O$ v' V: y7 q, D. @9 t$ x

) c1 k5 c6 u7 E! }三、STM32L4 时钟使能和配置9 {. u: E  L1 N& t0 H1 p* F
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。
. a: ~+ E1 o( c7 j* \1 s( L- I# O: k: F* P
通过STM32L4的HAL库使能外设时钟的方法/ \0 O( L. w  e& G6 l& I' f
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。) l* C: r" y" G5 j9 s
举个栗子
! T7 j+ ]$ {8 }  l看看 GPIOA 的外设时钟使能宏定义标识符:
) B; z) L1 R9 O! `9 T
" Q/ I) ]' U2 T. U- f
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \
    # [7 U! A* D. b& V/ `  G7 b6 e2 F
  2.                                                                      __IO uint32_t tmpreg; \
    / ~  g$ u- ?' |5 q
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ $ H6 J1 ?- F3 H% H
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    . g3 S/ g/ N" o% ~& ]: G6 r
  5.                                                                             UNUSED(tmpreg); \ - H; v/ }9 {/ ?' ]4 [9 U$ d+ N7 y
  6.                                                 } while(0)               
复制代码

( \" u6 F* y6 C/ }: w! j( G" A, l0 t/ R! g
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:; ~" z" _/ p, P2 v

3 R% s9 k  c4 @' q! f: r
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

4 ?! R3 W0 h7 g5 y& E% x这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
4 c# _) g8 h8 K: o; r- P# W
3 q$ G% W  o& N% U, F1 C, L& k
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
    1 ?# q+ q2 F8 r
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) 3 g# D8 c8 L& o$ V  w3 p( J
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码
4 ?; V* C2 b/ ?1 I1 b6 @' v
上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
- h; ]: f; X$ l+ K8 a描述如下:
: l# W- o8 j4 l$ d3 l5 U
  F) g' n) @/ R- Z$ g
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 5 z7 B0 r% n3 H( R" p" H
  2. Set and cleared by software.                       // 由软件置 1 和清零
    ! z  g# z8 d8 N! Z  L6 G4 v8 R
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
    & u5 H; U5 m9 u1 v' r0 s
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
! k9 f' }: v& r- z- D+ q+ T& z
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
& V( _6 ]' u0 U) p
6 Y1 t8 l$ ^1 `: `* w! i
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码

9 n% E& r; a1 o! t对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟
    8 A" f* g* r: I: x; N5 i- L+ d6 z
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
    + T1 P. V) ^6 _! s; P) m
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
2 c7 u' `: s* }, {* L
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            . {/ f& F& y) ?( X0 d
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
7 }# }) E' L- s3 q3 j/ C
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:2 Z3 `( J6 r' O* f( h
9 E: M! R9 r8 v& w% q
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    " i; J9 E( J; m
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟 7 F6 v. e- l2 E4 o1 p! q, A/ j$ B
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码

# n8 F- y: Y& s" L: k) a, _. L+ n9 l- |/ C+ n4 u
6 n/ y& |4 x# B- }8 D

4 S7 h  Y* \$ ~% r) F! q( c( w1 }
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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