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

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

[复制链接]
STMCU小助手 发布时间:2022-1-2 09:00
主要内容
' X1 ]% `9 I7 q. D+ `1)STM32L4 时钟树概述;
3 W* X2 R7 _8 ]2)STM32L4 时钟初始化配置;
% Q, Z  |  @! @/ g2 s3)STM32L4 时钟使能和配置。! |4 n' _7 L( ?4 J6 E3 s* L

- Z1 x3 n/ {, Y: J. Q一、STM32L4 时钟树概述
: D  u5 `: B9 [! J时钟系统是 CPU 的脉搏。" c* ?& T+ m9 t5 T$ A8 ]
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。  X+ ~0 B! w' y2 Q) w
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
2 e. T( A6 D# K- U* K3 p# r: c) A/ @8 f所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。# }  _! P3 [' k$ s( t! I- l$ [

: j% p  [# T) y8 J! qSTM32L4 的时钟系统图如下:) d, Y& V3 B- |% `  V% c
  ]! @& a2 v, q) w, T4 D  a6 a( w
20201205162651825.jpg
- d" n- r: ]. K+ R1 Z% i) H7 T$ |
20201205162724607.jpg
( m7 [# P7 p3 V3 ^+ X

# X- z$ i1 o, n5 _. Q在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
0 I9 O  W0 g( G& c* s1 b! y其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。" ]7 O6 n# h9 O/ P: Y( t& y
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。$ b0 \3 \- r1 m# Z7 D3 x
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。; t. j$ Y+ e' E; a
按上图中红圈标示的顺序分别介绍这6个时钟源:
! n0 h1 w6 T) U* t0 O' J8 d① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。+ r# U! [* L, B1 n
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
8 w7 l6 ^% i% t5 V7 ~4 M③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。5 m: n# p$ W' \
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。
% b, `( ?% v$ b4 P; g/ p5 J⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。8 ~0 e3 [# G; E  a; @
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
; R; T: x) H+ `2 i, `1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
. N6 C. o+ Q0 G2 C2 `7 P+ }: a第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
5 c4 j" \; t! a. H6 P' Q6 B第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
; O, }: z/ |, b$ P4 D) b第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
- Z' O, b$ V+ k7 N5 U0 B2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
% I/ e+ m) H$ m, ]5 l  l$ {第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;6 j# Y! G- A% y2 c. O% g
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;0 a- N& f, i  p1 N0 X
第三个输出 PLLSAI1R,可为 ADC 提供时钟。9 [5 m5 M& M: D' O
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
0 C2 K3 U8 e7 u5 I- d第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
- E$ p$ @' u3 U8 {第二个输出 PLLSAI2R,可为 ADC 提供时钟。
- x! M, Z! x( e# {$ F3 K; L' o/ X0 O1 m4 W/ D: o. k! Z
这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:3 {5 `$ \) R) J: v. w% G; Q6 m( R
20201205163714587.jpg
$ i  a4 S2 w) M. H' Y
. @# R; E" J/ p' d. a; v0 C# g( e+ |- f
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
* P6 c1 ]# }, C( @, y3 H& j8 D' e3 ^7 }( u9 R8 f( ]
举个栗子:3 {; z0 ]! s5 f( Z! T4 o& @  e
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
- F8 ?2 W) q' I6 z* E$ F
6 [' v$ O, K+ k0 R3 \3 |' A/ RPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
+ I8 {6 q- A( s1 m0 O; l' w0 @8 G如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。
- D& G$ V( c: w9 Y4 {; s# s1 [, E9 U9 f0 ]9 f$ P6 {! q
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
6 E% `+ Y( ]' U$ ]* a+ a观察之前给出的时钟系统图中标出的9个字母(A~I):$ i  @9 h5 ~3 c' X' f7 M, t6 o
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;6 I6 v/ _; E# D$ c. o
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;8 [* ?# _$ A& X" a4 i% l
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;3 y6 \  D& I0 W* v
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;. n" r# |2 m& f
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;" D/ j( E: ]6 {
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;* V7 V5 g9 C  t5 P
G. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;0 h. K. `4 P  s4 i
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;5 n- G2 K" X3 {) E
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。7 O+ W* V* i" |1 |5 Z
备注; E  j" l7 F2 m4 G
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。6 p4 C/ j% d, F: `5 D  y
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。. X4 J: C- U4 `
# ^# n0 i# l/ l& n; h
二、STM32L4 时钟系统初始化配置. w3 m& t. G, E6 x  I6 O2 F
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:7 i+ w+ b1 w6 V# k
. [* Q5 J9 I6 L0 U: J
  1. /*********************************************************************
    4 q; Y+ a! M3 W% m& P; c
  2. 函数名称:void SystemInit(void)
    ! b' [6 P. c  u* ~, @
  3. 函数功能:1)  FPU 设置 2 O; D+ Y6 C) Z/ X3 t
  4.             2)  复位 RCC 时钟配置为默认复位值(默认开启 MSI) 6 t- @: E: }" a: c
  5.             3)  中断向量表地址配置 4 N& V4 k6 H5 R) I6 J* s
  6. 入口参数:无
    : K; s( Q0 G6 f* V& ]' F& p
  7. 返回参数:无
    8 }! S- t& u2 R0 w: V' T3 U
  8. **********************************************************************// I" k" V5 q9 k% N) e' G5 I
  9. void SystemInit(void)
    / d9 A8 q. ?- i, R
  10. {+ E) ~+ D% P. `2 _! w
  11.   #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    & r& o& P! h2 T
  12.     SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));   // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
    * r% }1 s. j0 F' \/ v  d
  13.   #endif
      a8 D' P8 i+ k( G, x, M: I+ \
  14.   /*  复位 RCC 时钟配置为默认配置-----------*/
    : W, l/ U% R  \7 G; J  b# t7 k/ {
  15.   RCC->CR |= RCC_CR_MSION;                                             // 打开 MSION 位 / i7 G6 u4 W2 E9 C7 w
  16.   RCC->CFGR = 0x00000000U;                                              // 复位 CFGR 寄存器
    + d. a0 C) J# C9 D
  17.   RCC->CR &= 0xEAF6FFFFU;                                              // 清除 HSEON,CSSON,HSION ,PLLON 位           
    ; {* K' k9 ~& `% J/ b+ N* K0 N' ?
  18.   RCC->PLLCFGR = 0x00001000U;                                   // 复位 PLLCFGR 寄存器* u1 @1 B1 j$ @3 o
  19.   RCC->CR &= 0xFFFBFFFFU;                                           // 复位 HSEBYP 位 % u& D' K& S/ k9 H* D; Q
  20.   RCC->CIER = 0x00000000U;                                           // 关闭所有的中断
    & ^3 Z+ u8 z* Y4 |8 m/ d& l' w
  21. /*  配置中断向量表地址=基地址+偏移地址  ------------------*/  
    $ v( @- M* y" j3 @  a- t6 h
  22.   #ifdef VECT_TAB_SRAM9 l1 Z5 c* x. ^' C! Y! E8 J& b
  23.     SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */+ R. ]0 g# ^+ ~$ Z: N" P* [7 t
  24.   #else0 ^$ D1 K) B2 o+ M" y) X/ O
  25.     SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
    6 Q9 [2 [! N, V. I- v6 m% h
  26.   #endif2 T! S& s: v8 {: z) l
  27. }
复制代码

& @5 |* G4 g( Y8 H& KHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
& {& j1 e+ e+ K0 z( d. t! s, j- e+ m4 M
  1. /*********************************************************************, [; t3 p  e  ]% v8 t5 e$ k
  2. 函数名称:void SystemClock_Config(void)
    8 V% ~" F  Y) P  M2 }4 E
  3. 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR& S. e" `! X8 h2 `# U9 K% w
  4.             SYSCLK = 8M  /   1  *  20  /2  =  80M! |# t- n$ U1 |- R& b
  5. 入口参数:无& \# Z& v) y: x8 P
  6. 返回参数:无
    5 V% P! Q% `. V/ n; ~( D
  7. **********************************************************************/0 r$ n6 `+ u9 V3 @* g! a1 N# b' h
  8. void SystemClock_Config(void), Y/ ?# X0 {, O6 R6 V2 z
  9. {0 j5 T6 {$ k/ X
  10.     HAL_StatusTypeDef  ret = HAL_OK;
    * j( s% U; X2 x$ Q' g& @
  11. * X9 _2 U& [% _, w  @$ X  t3 p
  12.     RCC_OscInitTypeDef RCC_OscInitStruct;
    : j7 m7 k$ l& e6 U4 f4 T7 I" P, t/ u
  13.     RCC_ClkInitTypeDef RCC_ClkInitStruct;
    2 X1 W0 p1 }& a$ Z- H

  14. # c3 G! M! ^% @9 p
  15.     __HAL_RCC_PWR_CLK_ENABLE();                                                         // 第一步,使能PWR时钟
    7 `9 ?) \6 B5 g0 L* ^4 ^
  16.     /*Initializes the CPU, AHB and APB busses clocks*/3 f. d" ~4 d; [. C9 Z: c
  17.     RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE * r4 t& Q! P% s1 i& F4 s2 K8 A
  18.     RCC_OscInitStruct.HSEState = RCC_HSE_ON;                                // 打开 HSE / j/ e- ]: q1 K: o0 y% j+ @; K. P
  19.     RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;                        // 打开 PLL
    3 ]& ~6 r  {1 T5 o
  20.     RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;      // PLL 时钟源为 HSE ( c* W0 [- U; k" j# p* `
  21.     RCC_OscInitStruct.PLL.PLLM = 1;+ ?$ x2 M" X5 u9 _/ P8 N
  22.     RCC_OscInitStruct.PLL.PLLN = 20;
    ' R, M; r5 h+ a7 J9 l! Z; i, T0 ^
  23.     RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;% o5 X8 s# m4 u) g7 o4 F' @6 |# @
  24.     RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;* ~' p, K2 i  B
  25.     RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;" I( @& o( V' ]2 m* W

  26. + W7 ^" n: _# s. F1 d' f& m
  27.     ret = HAL_RCC_OscConfig(&RCC_OscInitStruct);              // 第二步,配置时钟源相关参数
    ; ?3 \% h/ U7 |9 v% _, G
  28.     /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
    & Z3 t: p2 |# P% R
  29.   @, w! j* J3 m* @' W& X; k5 X
  30.     if(ret != HAL_OK) while(1);
    6 B1 U+ [* e7 Z
  31. ' j0 s! N, j0 y
  32.     /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ 0 a" f: j( v- G2 l/ f! U2 x" j
  33.     RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK6 |/ I  r9 z& F- j) I* |
  34.                                                         | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
    6 g' a1 U; K8 K. \) O% M
  35.     RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;                         // 选择系统时钟源为 PLL
    9 L! [9 Y/ c, |" ^& r
  36.     RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;                                          // AHB 分频系数为 1
    ! w! G6 g0 @' O: }
  37.     RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;                                          // APB1 分频系数为 1% \3 o- T! }1 \/ s6 y7 w
  38.     RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;                                          // APB2 分频系数为 13 C0 j, N5 t5 N5 ]& A$ @( U
  39. # N8 N3 b! e+ E
  40.     ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数7 M3 b0 s4 P- D, [# d0 |
  41.     /* 第三步配置后,可以计算出:
    ' ~; F" F4 ~3 j" G+ G
  42.                     PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;1 L$ `. c) Y" ~/ ?4 Y
  43.                     选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;+ u, G. {* z/ R& Y9 j7 f' n
  44.                     AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
    % j, b9 l+ h6 ?  A" z" \, a) Y
  45.                     APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;6 i2 i( m5 x$ V3 {! T# I
  46.                     APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
    / j# h% s+ b  ~' k# F
  47.                     
    / m( t( k- M3 e- W( w% d
  48.     if(ret != HAL_OK) while(1);
    1 C# Y" d$ w& Y% V: y% P, g

  49. + K+ ?1 C3 u4 c& ~. }8 S% a
  50.     /*配置主内部调压器输出电压级别*/3 e: e5 F. M4 X' W" [
  51.     ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);  // 第四步, 设置调压器输出电压级别8 }3 h; Q/ i! l

  52. 7 b! d! A. f. a3 ~* J
  53.     if(ret != HAL_OK) while(1);: f7 `; X" m3 G2 `6 ]
  54. }
复制代码

5 A9 H; c4 @# K" R4 `9 G0 J函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。+ B; a# C; k) X( }
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
0 N# S; K! R  x: I& e* u. k6 m* o% M/ j" [& y' m) S
使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
6 n: G/ Q' _, {* V0 `5 M8 x配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。! n7 o' w# M- O5 p3 e6 H
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
$ j  y& z/ p, c设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
" a& p: R) ~! K) s& E! ~+ m对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
0 F9 K5 O! i) F" e2 s, Y' B* v2 `  q
  1. HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef   *RCC_OscInitStruct)
复制代码
. v% N6 e7 Z, v( W  t! U2 j3 c3 K
HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:- E( D4 l# ^8 B2 C/ u+ w

8 [$ T* Y8 K- m
  1. typedef struct
    ' Z- M. G0 V/ `2 c8 r
  2. {
    . ]* E; Y: \& C0 l  G
  3.    uint32_t OscillatorType;      // 需要选择配置的振荡器类型      , c2 `7 M6 n* z+ r% b
  4.    uint32_t HSEState;            // HSE 状态       " k# r5 C% C" A# Y
  5.    uint32_t LSEState;            // LSE 状态                  
    3 v/ {& |7 V5 u0 S2 V
  6.    uint32_t HSIState;            // HSI 状态      
    ! e6 W, o* Y( Q1 |% L9 M
  7.    uint32_t HSICalibrationValue; // HSI 校准值 . @3 E# ^" J: m: N* g7 E# z2 L
  8.    uint32_t LSIState;            // LSI 状态       2 h8 w, h9 p$ D% T
  9.    uint32_t MSIState             // MSI 的状态  5 N+ }! G# Q3 p8 u
  10.    uint32_t MSICalibrationValue; // MSI 校准值 " p6 M/ ?5 f" {) j- y
  11.    uint32_t MSIClockRange;       // MSI 时钟范围  
    ; {; u1 O2 |$ y
  12.    uint32_t HSI48State;          // HSI48 状态
    - f! P; ~0 U* c; P
  13.    RCC_PLLInitTypeDef PLL;       // PLL 配置 * |$ C3 ]/ Y; l; G
  14. }
      S  j* z# B* F; U' L) ^
  15. RCC_OscInitTypeDef;
复制代码
5 e' o' k- \3 r0 w  z3 r; _
该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。7 I9 f  l8 {" u) w" ?
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
% T7 D  \1 n3 k2 s* X/ U4 O  H
- x. J9 i0 S5 t- q* w
  1. typedef struct & {9 w$ \' [5 U
  2. { % K0 x+ x- Q# V6 S6 D+ n
  3.    uint32_t PLLState;     // PLL 状态 ! P: @" Z) x% T( s1 f
  4.    uint32_t PLLSource;    // PLL 时钟源
    ( |0 |- S5 ^. f
  5.    uint32_t PLLM;         // PLL 分频系数 M / L2 s! G5 P. V8 Z6 C! ^
  6.    uint32_t PLLN;         // PLL 倍频系数 N 8 ^( }  m' ?& Y  N$ R
  7.    uint32_t PLLP;         // PLL 分频系数 P
    1 p& w$ J9 ~$ h! f
  8.    uint32_t PLLQ;         // PLL 分频系数 Q
    1 i; B5 [$ M: B- k" J( l* ^
  9.    uint32_t PLLR;         // PLL 分频系数 R
    " P: X( W6 T5 {- L6 {8 s6 C' B+ T
  10. }
    1 Q0 H7 G# x+ y
  11. RCC_PLLInitTypeDef;
复制代码
6 D" W& N: X5 i/ Y( @
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。/ E9 A% t/ r# ^. v8 t
, m' a) T+ l1 u/ [9 ]
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
% O% N! e. O: }! T# K( j- }, a* J6 J* ?' ]
  1. HAL_StatusTypeDef   HAL_RCC_ClockConfig(RCC_ClkInitTypeDef   *RCC_ClkInitStruct,   uint32_t FLatency);
复制代码
* a& s# Y$ @' Y0 ?' E
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。: Z9 J. g" k- U( F& P3 J

( W; f3 W/ [9 |2 M对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的! e6 ^% W; n9 F7 j+ `  z9 m
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。& Z0 h# l- D7 ?2 c% W7 p2 N
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:+ I: {- a% k( |$ U7 l

8 k$ t3 N2 s( {. m8 V+ F
  1. 位 15:14 VOS[1:0]
    ) t9 |' R" B4 d: X
  2. 00: Cannot be written
    8 r" x( l2 o5 _% H' v+ F6 E
  3. 01:Range 1
    1 R* R' i" o9 T8 v+ K6 N0 E
  4. 10:Range 2
    $ Q2 C. a+ x1 e: T5 Z- v
  5. 11: Cannot be written  
复制代码
  o5 B  D% K& P
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。3 A3 a9 `$ D3 S: e
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:, [$ b, H. R' X8 D

; M9 ~* D& u* S; W" W/ s
20201205181115783.jpg
: Z( t) ?# z. o7 {( C* Y( Q. ^4 U
( \# w! a" l( w" R1 O3 H
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
* W7 H  O9 ]' ~* l: l: G/ g' Y
/ D* w5 ~, R) F' j三、STM32L4 时钟使能和配置5 Y* o7 ]! ?' q% ]; a0 ~
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。  s$ N; C+ u! g4 E) ?% P4 h
; \, G5 X7 s7 V7 s5 l/ s
通过STM32L4的HAL库使能外设时钟的方法  I( p$ F) B4 ~3 q! `$ `
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。# l, n2 F1 ^' J' a# b
举个栗子* q+ t5 Y, _1 x; ]
看看 GPIOA 的外设时钟使能宏定义标识符:$ k9 Y6 k& R- f) i0 L

" T" Z  x4 n& z1 h  s6 y' K
  1. #define __HAL_RCC_GPIOA_CLK_ENABLE()            do { \ ' ~% T5 o5 u+ u6 B, Q! t6 B5 n
  2.                                                                      __IO uint32_t tmpreg; \
    ) V9 a( j; }1 H4 h6 {
  3.                                                                      SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ 0 z8 q8 h; ], r1 S4 O
  4.                                                                             tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ # M2 P3 r0 G. }5 R* ]
  5.                                                                             UNUSED(tmpreg); \ / ~% h4 [! J& C: X
  6.                                                 } while(0)               
复制代码
3 M1 c9 e2 ]; W, b7 j) F7 ]; H

7 @5 Q  h0 o4 I; y' Q3 i这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
1 F, n7 o) ]9 s2 f9 A: k# Y, C; f: ~) F0 U6 S% Y
  1. SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码

8 h" b" g. @% h5 }' |- C6 h这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
+ H' B- s. `* _) J
9 t1 p& s% G" ^, x
  1. #define RCC_AHB2ENR_GPIOAEN_Pos               (0U)
    5 Z5 Z! k- R+ E9 s" @9 x6 T- N3 q
  2. #define RCC_AHB2ENR_GPIOAEN_Msk     (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
    . @0 X! Y* f& p% n5 l
  3. #define RCC_AHB2ENR_GPIOAEN                   RCC_AHB2ENR_GPIOAEN_Msk
复制代码

4 t& R2 z8 }0 C& H+ {8 ^8 C上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 03 e! F% B! H, ~/ H
描述如下:
/ r  M* E+ U, [2 v8 S6 s8 s8 d9 s: \
  1. Bit 0 GPIOAEN: IO port A clock enable   // GPIOA 时钟使能
    7 p  J/ A- x6 W: C# w" V6 U
  2. Set and cleared by software.                       // 由软件置 1 和清零 5 A! Z/ M# w7 y" w6 k9 L! L3 s
  3. 0: IO port A clock disabled                       // 禁止 GPIOA 时钟 , b4 Q/ l) \" t
  4. 1: IO port A clock enabled                       // 使能 GPIOA 时钟
复制代码

( L; D) D. Q. E6 x那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:' Q( O$ g, S' ]. o/ H8 Z; p
3 f& Y3 \) w4 d/ K+ c3 r
  1. __HAL_RCC_GPIOA_CLK_ENABLE();        // 使能 GPIOA 时钟
复制代码

+ E$ r8 H9 s: }' L对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
  1. __HAL_RCC_DMA1_CLK_ENABLE();   // 使能 DMA1 时钟 $ `) [/ \. E4 l0 x9 q, B/ U
  2. __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 ! B) |  f/ b5 U$ I. p+ [
  3. __HAL_RCC_TIM1_CLK_ENABLE();   // 使能 TIM1 时钟
复制代码
6 n$ n" ?$ h8 ~8 n% A! B
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
  1. #define __HAL_RCC_GPIOA_CLK_DISABLE()            1 _6 m7 c8 J7 {; ?& W" T6 V
  2. CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码

" f5 i0 v/ p  s' s/ p" I, f  Q1 z: w同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:& {5 N+ f+ o$ G8 R- D+ p

  f( G4 D( W' P# `( V/ |3 \
  1. __HAL_RCC_DMA1_CLK_DISABLE();   // 禁止 DMA1 时钟 + S+ W/ L) [  A  ?" ?) K" b$ S
  2. __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟 8 }$ \. t0 d: i4 k
  3. __HAL_RCC_TIM1_CLK_DISABLE();   // 禁止 TIM1 时钟
复制代码
6 Z# M) x% a/ o8 B' f5 [, p

( z0 b' x6 \6 X& J  T- c- N" Z* w
. |9 w1 G3 G9 ?1 b7 n& x2 p9 L
2 u% Q5 X2 E1 R, `( E5 n
赞 收藏 评论0 发布时间:2022-1-2 09:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

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