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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容
( c# O" w' Z: R7 W4 e6 Q1)STM32L4 时钟树概述;/ [+ Q6 k8 I' J7 `
2)STM32L4 时钟初始化配置;
( [3 {8 L" m. c0 X6 }& N) ~3)STM32L4 时钟使能和配置。) K3 _/ f, I2 D2 @4 G
! x/ S) X3 T% X; @4 b
一、STM32L4 时钟树概述" ?9 W" z, |$ B# m# T% U. T, I
时钟系统是 CPU 的脉搏。) l7 C8 F& z9 B# _& P0 i  R5 h# U
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。: C9 ?, S0 t% S, Q# }$ e
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
/ N% @) O  D3 ^4 \所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
- I$ j. h2 f( M9 u% e6 o  R6 o
* C: M) r% B5 A0 s2 \  }7 J/ q/ HSTM32L4 的时钟系统图如下:; ^  r4 R/ n2 Y6 [9 B
" C+ Q( y/ v+ H! O0 B
20201205162651825.jpg
6 y; V3 _  r# J( j* O( q
20201205162724607.jpg
- i2 _% t: `# k) f( a+ u/ f8 k6 @

: Y/ U3 u) v- |$ D在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。+ c: x) m' h3 M* H& u: D- k6 b
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
6 ~6 S; H9 m: z按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
! n- T4 ]" k% g0 ?3 P8 Z0 T+ j2 x按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。; k* }9 p* a9 X0 |$ U' }
按上图中红圈标示的顺序分别介绍这6个时钟源:
* R* }3 _0 y4 c) }# ?① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。+ j" l& K, I. k/ D# P2 @8 g& s
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
' Z8 W1 [9 A  s6 d) x2 t) z③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。8 s& G; l; J) Q4 ]3 c: G
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。9 O7 K1 X) I% K/ G
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。( x2 x5 y9 D  ^- B
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
' l" K1 s5 S; W2 q* d2 E1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:0 I  r* ?- B0 T, {
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);' g9 Q* @% Y) E7 _  G; ?" ~9 [
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
" |$ E2 \; B2 E第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
4 `% R2 L# E5 ]5 Z  O8 q( ~0 S2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:" Z" e8 z: j6 j9 |. ^
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;: g' \5 F0 s$ h4 U2 C& l& }, V0 l
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
* O; F7 S  X) m  a7 [% T* U第三个输出 PLLSAI1R,可为 ADC 提供时钟。
' a7 g& Y5 e  b8 n. u3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
; C) c4 d; z7 ]第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
( f! [8 m8 C: j6 @# ?2 E3 c+ y第二个输出 PLLSAI2R,可为 ADC 提供时钟。
9 h  N7 x: d" _, ?
% ?' s, f% x( H& {( T  ?这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:9 f% J) K+ l, E3 A! S* w
20201205163714587.jpg

$ D$ L( p) G( T" Q. l) r$ u& g, _
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
9 `8 T/ j( \& u- y5 l9 a  |) T4 t% I6 t2 d2 U
举个栗子:! B" w, U+ W7 r( Z0 m
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
% z) \6 h3 |0 _: r1 Z4 H
" `2 b1 W+ J6 ~6 O4 m+ U. kPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
9 ~7 J6 ?0 S+ O如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。1 O' x  P# f& t0 X4 c" m5 i# y
. O4 P$ ]& Z& a+ _- A; e0 x
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
% a: n0 ^# u6 Q! |- y4 E观察之前给出的时钟系统图中标出的9个字母(A~I):7 o5 ^5 c9 A+ e4 K4 g
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
) F' x# X, x$ M* WB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
+ K9 ^% f- G. [! B# [C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;& |3 W) x1 e. k7 L! D8 d5 M' q
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
# h1 L4 r4 V# G* \; P" ]6 eE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;% f* K5 }' f+ V6 g. \
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;: {# R8 X. L/ D0 C  j- ~% Q
G. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
4 U3 t0 k) x2 Q3 Q; K' i3 OH. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
9 c/ P; k. r3 k- u6 WI. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
9 _- q& l: y" K( O+ O, Y/ `8 Q备注
9 I5 S" A6 W) h6 Q1 t! d7 t8 O7 e; z1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。
6 s! M8 \) X" T; g0 D- c! C  z& m/ m2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
- B$ X* y% ]! c! m7 Z  G) r- p5 K9 O
二、STM32L4 时钟系统初始化配置
" r/ j' F; h- s, f0 o在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:3 G& }1 L( s; Y

. L0 D& `* C0 t3 ]% g0 |5 z
  1. /*********************************************************************
    8 R" ^/ N; k3 D6 ^$ {1 _$ _
  2. 函数名称:void SystemInit(void)
    * O) S. b+ h. M5 G/ E
  3. 函数功能:1)  FPU 设置
    + V. o- }9 H2 g, y
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI)
    ! }' A" Z" ^8 ?) w0 e
  5.             3)  中断向量表地址配置
    " E8 V' c+ t* T: |, W/ _
  6. 入口参数:无
    & M* _: C$ u' m2 A
  7. 返回参数:无9 Z7 z3 F: j+ T' u  h; q3 _  f
  8. **********************************************************************/
    4 I' |; w! |# g- u6 m0 N: z
  9. void SystemInit(void)% e7 {! B5 X9 r* l+ r7 q
  10. {" j3 [1 b  e# Y. q4 A
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    / L* c, z( h7 Y4 @" \5 x0 A
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 $ b& `  {% d+ m  G, X* d# \! o6 ]
  13.   #endif
    / M, f. n9 B# d
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/ , v4 u2 E8 D3 L5 U* p1 a
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位
    5 ?1 @; ^4 P3 j8 _
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    , C/ C; O+ h5 @# ~5 P8 V
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    7 a+ c5 x2 f/ _0 O5 y7 I
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器
    ( i! d, U0 i* I# X$ K
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位 , Z, V. P* n2 r9 t: ?! z
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断
    ; T5 w! a5 g5 O* a* U
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    ; N6 [% ~" V6 A8 _# e
  22.   #ifdef VECT_TAB_SRAM
    0 }# e2 ]5 [) ]. R; Q# @
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */# a( Q- D( @6 E, I/ W
  24.   #else
    / H$ _& @6 q, y5 K- A+ ?
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    " `0 D( ]* E: S% Y) `
  26.   #endif
    " @# ]4 E- P% w6 [# T- i
  27. }
复制代码

& ?7 E! H8 D6 a: X( EHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
& G* u$ s/ c4 y, ^: b/ U1 T. t& Y1 y! ?6 C& N6 j
  1. /*********************************************************************+ \0 k& k' u2 o# `$ w  H
  2. 函数名称:void SystemClock_Config(void)/ Z- f9 a/ I1 p( p
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
    - q+ f3 F9 [5 u- k( }
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M
    $ m2 U+ C7 i" ~) {6 \' [
  5. 入口参数:无
    % p! \( C4 j3 @& G# g1 T
  6. 返回参数:无
    % k$ x8 F2 ?3 ?/ |, j
  7. **********************************************************************/
    7 w( W1 v! Z* ?* X1 h8 E
  8. void SystemClock_Config(void). z$ @+ F9 z7 ~+ N( u7 J
  9. {* N! V% p  [, X6 M' `; K+ M- I& ?3 G
  10.     HAL_StatusTypeDef  ret = HAL_OK;$ v( ~& Z! Q! @8 u

  11. 2 V. ~: s% K- y
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;) _, c$ v$ V1 U9 o/ o. \& F- u& \
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;9 A$ f5 L1 W) w/ w  ]7 o6 {

  14.   S$ O* Q0 [  j# |) i9 @( p$ O
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟( Q/ m1 V* G5 w
  16.     /*Initializes the CPU, AHB and APB busses clocks*/
    8 M& D. D6 {% g5 s' ^" [! h: b
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE * e7 \0 i7 ~4 d6 \- D
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE ; ?- ?  p6 S, n4 y) c% k2 w( I
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
    ( ]8 ]$ P; D& A# Y6 @+ ]
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE
    " x4 ?6 o# n: n3 `. p' m0 t3 O
  21.     RCC_OscInitStruct.PLL.PLLM = 1;
    + ^$ b4 L3 L' {+ h7 r5 W
  22.     RCC_OscInitStruct.PLL.PLLN = 20;$ `- V6 \- S' {6 {8 {* H$ g
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;2 C/ Y, B! L4 W" W! J1 O
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
    , p0 C/ c% }0 w) x9 K, i# `' M
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
    ( y) b5 O  J. X9 U* L7 n! X

  26. + L0 y6 y# l5 m  U4 ~. d
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数  n8 I6 I" F4 s; Z6 p# Y
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ * f/ U+ s  N3 n: f& d1 E8 T- X8 Q0 v

  29. 7 Z7 t- O% h4 W) k! e) r
  30.     if(ret != HAL_OK) while(1);; z! T5 q7 j6 d" X9 n, v) J: `
  31. ! \; O- t* Z: N- P7 c
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
    6 A2 d( \3 F8 f& s* j
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK( [% I; l) j5 H$ U8 R9 }* b
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 7 F  H1 m- l* G# C5 F4 p0 @
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL9 x: e2 `9 d! y3 b. M: z: G
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 10 [! `+ I2 k: d, \- m+ l3 `
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1
    & F$ H% g' I( f" V7 S6 }: h
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 16 ^: `1 `1 G6 l% u0 m
  39. . I/ d- G: ]5 [3 C& u: Z6 j
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
    0 x6 t# K) [3 Q# e
  41.     /* 第三步配置后,可以计算出:3 Y1 E' i; X2 {+ G9 V7 \4 N
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
      R3 [: e- a' T* }" W2 s
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
    ' H# S2 N  l4 B+ B9 C$ n! L$ m
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
    % H+ H  z  O  C9 ?9 a1 {. B$ Y
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;, n. h7 w" a( v' D! T
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/% c; K7 |3 V- D
  47.                     ) Z6 M) j) ?9 W! }; u+ a
  48.     if(ret != HAL_OK) while(1);
    % M* i1 c- M$ r" k% \! h

  49. 9 A1 T- }- [; \0 x1 _0 r: e
  50.     /*配置主内部调压器输出电压级别*/
    * h  ~6 c- K' V2 O. V- @
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别& z& s, @% Q# n0 K) s3 v: B; c

  52. 5 P) b8 Q; v' y' H" S, B. f) P5 A
  53.     if(ret != HAL_OK) while(1);
    ( U; ~) f4 r/ o; a: Z1 }
  54. }
复制代码

' }/ X4 w& w: W, w$ w! d7 ~7 q函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。8 r- I$ `5 h( L4 S
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
  S* }' N7 f3 r
* k1 Q  c4 k8 E使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
$ a# z# V! R2 h' D, Z5 c配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。7 _; u7 n9 A3 v/ [. t
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
, }( k8 `: y7 z$ [( ~0 V' W设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
$ G- X: x8 \9 _2 L" Z! v对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:' o7 ?' X0 C+ i. D% X) c
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码

: ~! L6 N6 j2 E6 |  JHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
: ]8 b1 a0 D: T. R  r- O/ V0 p
: J1 [: `" o8 o3 X# C% Y4 \5 |, d
  1. typedef struct : y0 C/ e, L' T! u3 X
  2. {
    - q( h' `- N  d9 z% T% {# _
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      * E8 s  P. ^3 L3 l; M& ]
  4.    uint32_t HSEState;            // HSE 状态       ; S# e" |  J; t: J: J
  5.    uint32_t LSEState;            // LSE 状态                   7 x0 b: g5 ?( Z, w3 ^
  6.    uint32_t HSIState;            // HSI 状态      
    3 t) [' Z2 [0 a  x4 @3 o8 m
  7.    uint32_t HSICalibrationValue; // HSI 校准值
    7 L0 s. Q% \6 L  D( Y
  8.    uint32_t LSIState;            // LSI 状态       2 J  p/ j" a% S
  9.    uint32_t MSIState             // MSI 的状态  9 m( ?$ R8 K, k% \7 R
  10.    uint32_t MSICalibrationValue; // MSI 校准值 6 ]1 d4 R$ S2 o8 L: k! k
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    2 E( g' w+ ?1 [4 {  h# x# b+ [) ]2 W6 a
  12.    uint32_t HSI48State;          // HSI48 状态
    ' W+ D; {  e: V3 @* [4 f
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置
    - Z! _, V' P8 L( j
  14. }2 n7 A4 z5 G* t/ i4 d5 P" P' @! @
  15. RCC_OscInitTypeDef;
复制代码

9 t8 I0 G; C( _# i6 a该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。+ \+ j! J9 x! w: f5 P
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
. |; S# E" ~2 d9 R
6 P) F3 b  Y7 a6 W( X
  1. typedef struct
    % d7 d' d6 c. v& h! f
  2. {
    2 h) r: {6 e. K. ~; t
  3.    uint32_t PLLState;     // PLL 状态
    : m1 ^+ q$ r$ K! F1 q0 |. h
  4.    uint32_t PLLSource;    // PLL 时钟源 + y& i) n& J  W/ f* W# |* I
  5.    uint32_t PLLM;         // PLL 分频系数 M
    7 }+ ^6 u9 q6 ?7 H5 [
  6.    uint32_t PLLN;         // PLL 倍频系数 N
    # q  d0 v8 i4 {2 i/ G
  7.    uint32_t PLLP;         // PLL 分频系数 P * Z4 {/ }' D1 C; f  C$ [
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    5 t( i- t1 g8 z% Y( G
  9.    uint32_t PLLR;         // PLL 分频系数 R 5 ?3 y! ~+ {6 ~6 O& y2 ?
  10. }' J4 L  I7 q4 O, e8 M
  11. RCC_PLLInitTypeDef;
复制代码
& w2 d% g+ I# W  l
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
6 A; N( O. l; N% F/ ~- B' w! |) @! i6 c
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:- l+ c) B+ j" Q* n, N

  B7 g/ t. c1 f5 ~% M+ H
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码
$ P. A: j* F) l
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
! Q7 O0 @3 I+ U; E7 s! C% s8 r; p3 C( K+ R
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
! ^6 Q1 V* o* X$ M延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。9 ]# [6 @& E# u) s
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:
7 Z/ \$ ?- w; A0 r# p
! t0 a7 X5 ?& q/ c" `- {& Q
  1. 位 15:14 VOS[1:0]
    # j2 x3 W6 F$ g
  2. 00: Cannot be written 5 y% m* ]/ W! z$ X- \
  3. 01:Range 1 $ |+ `0 z; u( @+ \, L
  4. 10:Range 2 ( `7 O4 A& f9 h5 R
  5. 11: Cannot be written  
复制代码
/ {% p2 F0 ?) l' c; X4 l
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
& y; A( u: B" J8 {) K配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
; l5 T$ l7 y) A: a
" b* B6 {8 ~( u* a8 v
20201205181115783.jpg
4 E" Y8 G: ~+ B8 B2 ?. u; r& a

- H/ {: D3 @0 q5 l1 P8 ^9 _可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。' ?4 }0 L& C& S9 R+ Z
/ z  Y" R0 a  x; _) S9 H1 [
三、STM32L4 时钟使能和配置
0 }1 D) I3 L" P3 G* R5 @在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。% X( a$ l7 w! {% t; O
% ?9 G8 M" c+ W3 a, G: v
通过STM32L4的HAL库使能外设时钟的方法
3 h5 h7 f8 m" V; x: D在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。# k8 ]3 `7 G* o& s" W4 m9 e, _
举个栗子
0 N9 `% ~% v3 t: \! M; J8 k5 u看看 GPIOA 的外设时钟使能宏定义标识符:
& W4 `5 p, N/ l
6 r+ j0 P# g- I
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \ , \/ U/ w& m5 z7 a6 B3 b7 R
  2.                                                                      __IO uint32_t tmpreg; \ 3 U5 t' o, K6 R- O9 C
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    5 h% B$ h6 ]% D/ l
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
    / |5 W1 ]6 O, Q
  5.                                                                             UNUSED(tmpreg); \
    3 |+ y7 e' ~1 d4 u# b4 R
  6.                                                 } while(0)               
复制代码
- f5 s7 m% K3 z1 z+ {
& }0 p& j7 h0 ]/ ]! W
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
. \1 P% h0 S+ g% l0 s# F
# @1 F7 F6 u- W+ j2 S8 v
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码
$ @5 ^2 ]3 \* x' t, [3 ?& ~4 D) o9 j
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:: N8 e, J7 k" `" w& v; P9 `$ f
" M; Q. F6 K5 `6 z) o" y4 i
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U) , |" l" t- J7 K& S: S# S- t
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) 2 L1 X7 m8 v, M2 Z. a# y7 L& [9 k
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

% T$ N0 q& R7 g8 @/ _: ]上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
/ O1 H0 R9 c5 R+ n0 A4 t' h描述如下:0 S& _7 T8 z  ^4 R2 `$ Y! s

3 l. X; P; g0 u1 m9 o
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能 ' L8 o  Y' d" T! G4 _7 {; Y
  2. Set and cleared by software.                       // 由软件置 1 和清零 ; y" t: X/ T& F3 e- M" h' C
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟
    + i; j: D* {+ E- E/ E& ]& y9 ?( t/ t
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码
2 I, C! L# M5 A2 r" r9 V
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
; w, f; ~, i& e) V$ \3 x
* b; F$ G( H* @. r. A2 P% Y
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码

+ S6 R# t/ ^8 Z8 ]: V9 k对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟 # F- z1 n/ f7 V1 j, p3 f
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 # W4 H, X4 }' o& u: _4 ?
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
/ A9 Q9 U6 g. _) w1 o( X* n
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            
    % r" C) I2 U' e  Q4 A$ ]$ `
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码

2 T  x  u# G  \1 \5 v1 N3 r, I同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
; a$ S  d" s) n# `8 T& l; v5 i
/ N9 ^0 k5 ~( _: Q6 d4 ~
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟
    & @% t/ K3 F* w8 Y% }% u
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
    , F7 W. L7 B( m. \+ N' H
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码

/ b& `  M% S, N+ v
- n2 b; b: u* ^7 Y/ b; f5 a9 b" i2 ^/ M( y8 F
* _2 L& P4 ~% R) F( V
收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版