主要内容
! a9 x9 J* e/ g4 `9 F1)STM32L4 时钟树概述;
2 |" v* I' ]5 m! F7 }9 p8 P x: _* h2 ?2)STM32L4 时钟初始化配置; L6 L, d$ L& W
3)STM32L4 时钟使能和配置。$ a6 D @6 ^3 h* r; X) q% o2 e
) H) S- S% U' r1 ~/ S
一、STM32L4 时钟树概述
; N+ O" k6 s. b, x- Q时钟系统是 CPU 的脉搏。
( h/ W: Y5 e( X3 T7 L& q2 P不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
9 ~( {2 ?( ^: M5 ?3 A, L3 h同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
; j8 s2 V# V" }- `9 a所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
' W4 A0 A( k( _3 t
' R0 a: e9 b3 `- C$ `STM32L4 的时钟系统图如下:
8 Y, [, o i& |# w
/ r4 C, A" E* F7 K+ j% m
( f7 @' |) }2 W3 e5 _+ Q1 E) i( A' L3 x8 f
+ F2 y4 k, E6 T F2 a
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。' p8 ^1 ^: u( m/ K* \
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
3 b) } \2 M C( N: B) ~按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
7 t7 C" g9 T7 }5 X# S7 d) W% @按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。/ D' ?" R; M( ]; t4 w
按上图中红圈标示的顺序分别介绍这6个时钟源:( Z, K: C. V1 G; F0 [: b* u
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
( N# @* E8 W$ B( s" N4 U$ Z1 c② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
7 @- S2 i9 y- N* O% k③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。6 l2 b9 H. ^6 k. r. Y# }+ i" v
④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。$ R! j0 D8 s) N, B+ c2 I" e
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
* N! ]; s; ~: c* ^& y⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
$ u' V9 ~2 O' g1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
) H4 t# Q7 U$ N! P$ `第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
- N4 u/ U! |8 L' s0 D# K! I* i# K/ i第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
6 `, m1 A. P& c' F第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
4 q) s0 n& j9 X9 _! p2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
O+ ?$ D9 F( b& b; j第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;+ i+ }2 _, q6 e: j
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;4 q) ]8 B4 V+ m% O
第三个输出 PLLSAI1R,可为 ADC 提供时钟。# X# \5 P/ f: G% F
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
9 o: k) c' N( N! Y4 L- d7 F0 ]第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;; T9 k7 b# W/ k* {) E$ n
第二个输出 PLLSAI2R,可为 ADC 提供时钟。- ~% L( v3 o" O7 \, _
* H' ~/ W. U [2 |9 S$ n这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:
* Q+ \$ e, s' }$ _5 X7 S
$ `. \6 k/ G' \! i R# ?' m& ~1 d7 e* U
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
9 b3 y) B6 N: [ n! S3 p( ^+ h- M) l& i/ N" Y5 w$ p
举个栗子:1 ~' s4 z Q/ J! e# h: x
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:4 n+ _3 b$ N; Z. L; @
4 o. ~$ ^8 u, N) t/ F' y B' sPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
. w* r% W H8 N& D/ V如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。: @& }% |5 }6 H6 O$ X: N: u
" }; o, d f( K4 R2 D$ X) R1 ]& s
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
3 T0 ^. I( b) G' B$ l( y0 @9 p观察之前给出的时钟系统图中标出的9个字母(A~I):
4 }3 K# q' I' Q) V. `4 D f5 R- zA. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
- j* W9 J) C. ^* o( V2 k" WB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;4 Q' V% A& Q3 W6 Q' P& A- N7 U
C. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;6 ~ M- X) X( s! @$ k
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;: I) M1 b' P2 w' H1 a# ~( O, Z9 Q6 {
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;
: |+ J" S7 ?+ ?5 j. C! AF. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
& ^* g1 h# f4 r" k4 oG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;5 @9 Z. y5 K( O$ @. B
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
?& F w# @/ d1 N- I$ p# _I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。
' I' D4 L$ z# L' A+ t) Z. K' p( @' r备注1 U. V, F: V! i+ D4 H+ W5 Z1 f
1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。, ?: i+ ]5 _$ T( z" y- W
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
$ S! i& a0 E M+ W9 H/ ? t) i$ `( M1 b6 T( h4 W8 g+ F) e$ _/ _" ~! ^
二、STM32L4 时钟系统初始化配置
: Q/ {% K, F! _$ s) b8 Z在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:7 J. b+ D; Q# `3 e- l. h
% A. E7 C* }# i- /*********************************************************************
! z# l! a8 |, f4 D. U+ _ - 函数名称:void SystemInit(void)/ ~/ M- O* I, w; k& A% E
- 函数功能:1) FPU 设置
3 Q' y, v. `* |* h1 X - 2) 复位 RCC 时钟配置为默认复位值(默认开启 MSI)
) @& o3 e8 a! e7 S: O1 Y0 b' K. H - 3) 中断向量表地址配置 , T: w* Z5 u7 l6 K" F
- 入口参数:无. s3 V" H" r* S/ Z8 I$ Z
- 返回参数:无. v- P8 T0 l+ @ F% j% ?
- **********************************************************************/. e5 B4 V; k5 _& F* N' j
- void SystemInit(void)6 e6 g' j6 b' U* E/ L e
- {
- }' A* g+ z2 H, L% G - #if (__FPU_PRESENT == 1) && (__FPU_USED == 1): }5 h7 D3 S4 R$ _$ ?
- SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 % D1 @& `" R+ g: n
- #endif: g; y2 R5 P5 K" M$ Q
- /* 复位 RCC 时钟配置为默认配置-----------*/ + i0 _5 q2 b! K( R4 H/ X( x4 ?
- RCC->CR |= RCC_CR_MSION; // 打开 MSION 位
9 b: q- @, `9 l D# E& j9 }1 y- f1 s - RCC->CFGR = 0x00000000U; // 复位 CFGR 寄存器 8 \ G% ?' J: [% q& I0 g1 D
- RCC->CR &= 0xEAF6FFFFU; // 清除 HSEON,CSSON,HSION ,PLLON 位 5 Q" z- f6 U1 s! W& l! _
- RCC->PLLCFGR = 0x00001000U; // 复位 PLLCFGR 寄存器
; b7 o5 U* R* y - RCC->CR &= 0xFFFBFFFFU; // 复位 HSEBYP 位
' j6 Y$ ?9 {0 i- e* e - RCC->CIER = 0x00000000U; // 关闭所有的中断
5 j- K7 o& V( Z m# F0 O5 n - /* 配置中断向量表地址=基地址+偏移地址 ------------------*/
7 H% _% e) `& d& u - #ifdef VECT_TAB_SRAM
; w7 I. f: C$ K, d$ u+ T - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
( y( Y, j5 J% O- q7 d - #else1 w" H* {7 e m; U4 z
- SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
; K% }7 ~7 r6 P9 C - #endif
: O- E( V* A% [( T( u - }
复制代码
7 w& ^8 d& t; y( U: `; XHAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
$ k* Z' Z' g! `3 o/ A
! k' X; H$ h; E& c1 X7 ^6 K- /*********************************************************************9 ]7 |8 B: j1 F0 V& |, C
- 函数名称:void SystemClock_Config(void)
; z" X9 r4 o! Q* `, M% c - 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
! T% f9 | B" q1 V7 C - SYSCLK = 8M / 1 * 20 /2 = 80M
7 f# b. G/ p3 L M, u" t3 c - 入口参数:无' n3 \% C Z8 Z2 K0 y, q8 B1 g( R- P
- 返回参数:无
3 R2 R) E3 @3 f5 f) m6 M - **********************************************************************/9 C1 b0 E! J' s
- void SystemClock_Config(void)
; f1 i. Z5 Z) H5 }! q - {. t" W: \" U3 T, x
- HAL_StatusTypeDef ret = HAL_OK;
" L/ B5 E0 [6 L$ m; \ - ( K+ P- z1 V! V _+ h+ d6 H( H: i1 u) m
- RCC_OscInitTypeDef RCC_OscInitStruct;
: H- P- g, N7 k* B - RCC_ClkInitTypeDef RCC_ClkInitStruct;' r8 \6 @: |: l+ J; v
, `+ M7 W, ^- E! O/ H1 `3 B( o- __HAL_RCC_PWR_CLK_ENABLE(); // 第一步,使能PWR时钟$ K( L3 N9 P4 W7 Z
- /*Initializes the CPU, AHB and APB busses clocks*/
& Z6 P; L+ T; ]! t: ]7 B1 \6 p - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE " B8 b# \3 C' E& L0 L
- RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开 HSE % o/ p+ y* q7 F* `
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开 PLL
9 Q0 W/ l+ B7 e - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 时钟源为 HSE
7 ~$ i3 I2 Q% r$ y' B5 f - RCC_OscInitStruct.PLL.PLLM = 1;
/ b5 @- }8 K7 [) T, i2 f5 E- S - RCC_OscInitStruct.PLL.PLLN = 20;
" F0 z5 `4 t- O9 ?: ^ - RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;0 h0 T/ k8 p. q* ?* [3 w# x* _
- RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;1 C+ M- A4 L- a7 u$ w0 H1 ?# ^8 [8 ]
- RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;5 H: c% l" [0 W5 S( |' ?8 I
; M* H2 h+ G _3 k3 C6 L- ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); // 第二步,配置时钟源相关参数3 g6 N- L% m! Y4 U! F6 Q+ Z
- /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ " O7 `) U9 c0 a
. e8 k. k' |( f% G( o" s/ M- if(ret != HAL_OK) while(1);
8 u% p) U) D" T9 g
# V. F$ N9 ?- S2 G+ V; |- /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
- `$ N9 [- u1 d e E - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
! A6 Y' t1 r8 O: Q& y/ Z# K6 Q- ? - | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 ) a; g0 ^, l! ?% D0 \
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 选择系统时钟源为 PLL
3 @9 P' f, a( G |/ O$ _5 t! M& S - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 分频系数为 1
8 }6 [2 n% V( N0 f) ] - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 分频系数为 1) z! ~. v+ A6 V1 b9 k
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频系数为 1
; S7 q& G0 b% t5 E s2 e1 d' Z - . C3 P8 M) f! Y3 z6 E
- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
* I% m, w1 _" V% Z - /* 第三步配置后,可以计算出:" Q V# D+ L/ }3 a) Z
- PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;: r% ]$ Q) S& t
- 选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;7 P9 @6 Y! ^3 H% I) x( [/ s
- AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;7 H! |1 d0 I1 v- W5 X& {, H: M
- APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;, o( {: X" N8 f8 Y: S
- APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
) I$ t# U5 ]. b9 |5 L - - ^( I6 i' O' \2 }: {
- if(ret != HAL_OK) while(1);
! q" B: z l$ Y7 V& j3 b2 V - $ n' W0 S- Y: W8 r( g; p7 n
- /*配置主内部调压器输出电压级别*/
& Q1 U8 A1 [) b - ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); // 第四步, 设置调压器输出电压级别
( x1 R8 d7 Z- k; L
, A, B* k% D% U5 d7 f- if(ret != HAL_OK) while(1);
) X% f9 [) E% ` - }
复制代码
; ]% y. t2 \2 L+ A6 ^函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。
$ \1 G' p6 h" z+ _, y8 [使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
9 P- z }3 S. ~$ J% ?( G% I1 x* K9 Y! f# R9 U
使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;) a' i$ R9 d# V$ d- e
配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。/ G% t7 V, o1 E/ x5 |6 c. {! G7 f
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
! c* {& }9 p* g6 q6 G& s! Z设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。8 b$ I, Q* [9 [! u% }% s
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:. w/ Q2 d2 V7 n' ?& u" W5 O; }2 {1 Z
- HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
复制代码
/ s# C0 U3 y( b6 R/ U" w- ZHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
) j1 C4 E P* i9 k$ Y2 l3 g7 y4 p% S0 j" t, j! I1 _
- typedef struct
( f( N7 i; O2 [7 |% u3 k - {
: c, |3 ?% c, B( ~, d* G: j7 A) f - uint32_t OscillatorType; // 需要选择配置的振荡器类型
7 Z/ J6 ?1 v! q; s. i4 O - uint32_t HSEState; // HSE 状态 / z5 V- B' _6 b, H
- uint32_t LSEState; // LSE 状态 ) Z- Y# f, b' f7 ~: r
- uint32_t HSIState; // HSI 状态
; o/ m) a1 w0 U! ] - uint32_t HSICalibrationValue; // HSI 校准值 ) ]0 p. s6 Z3 ?2 m/ Y1 N
- uint32_t LSIState; // LSI 状态
) k2 v* |7 p* ]8 f: `/ L - uint32_t MSIState // MSI 的状态 3 h3 \1 Z, K2 G
- uint32_t MSICalibrationValue; // MSI 校准值 ! t2 J, s2 g. B! X. K. P2 x" K0 [
- uint32_t MSIClockRange; // MSI 时钟范围 # v [: P% s5 {/ z5 L- |' ?
- uint32_t HSI48State; // HSI48 状态
5 a, n. I1 v; u/ f! I - RCC_PLLInitTypeDef PLL; // PLL 配置
0 r5 P* [0 ]! k* n+ R* @# X$ v - }8 e F0 I% C4 c. j2 ?& G$ q
- RCC_OscInitTypeDef;
复制代码
$ o2 t$ {- l o/ r! S该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。5 w0 N; Z# \6 Y- n/ N
该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:- [ d! B8 ]4 H: `1 j
6 P0 D4 {/ _) B
- typedef struct
/ k Y$ S; D) P - { 2 Q3 f6 l5 u* g( d$ E0 a7 I1 ~
- uint32_t PLLState; // PLL 状态 % \( t2 O! H Q- L3 y
- uint32_t PLLSource; // PLL 时钟源
D( f1 Y) \) Z - uint32_t PLLM; // PLL 分频系数 M
3 v# `7 y9 O4 o+ y5 Y6 Y o! L - uint32_t PLLN; // PLL 倍频系数 N
0 c; n. o* ~2 \" z- ` - uint32_t PLLP; // PLL 分频系数 P , e L. [! d; N0 V- K
- uint32_t PLLQ; // PLL 分频系数 Q
1 ?/ J* Y6 I4 A - uint32_t PLLR; // PLL 分频系数 R
0 h: e- U( ?" ]. K' | - }
6 ]8 I4 D3 m. C; N) k* k - RCC_PLLInitTypeDef;
复制代码
1 p: D4 @' Z+ w: e4 N该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
! B* c1 [' P3 Q5 q
: O Q# x. [& c! Y( T对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:+ h6 b6 ^9 W( k+ f4 w# s
5 Z: U( W+ X. T8 k9 b2 [
- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
复制代码
! C$ z, Y. {6 G7 o6 ?* K该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
5 H' L$ b# }4 o( y! b& a
8 C; K& c2 Q+ U7 ]对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
5 W- K# t0 D3 x/ p0 M延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。1 ` e5 Z5 F5 y9 j, h; }
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的: D2 ^' q. p! t- K, h6 p
+ \2 S) y8 J: {0 }( X
- 位 15:14 VOS[1:0] # @, w2 T% p5 Y
- 00: Cannot be written . K" P9 O5 R* I- I
- 01:Range 1 4 }* Q- Y. |. t9 l7 P& X3 k
- 10:Range 2 9 E7 W$ J- K$ w
- 11: Cannot be written
复制代码
9 l/ G: v0 E4 G6 L$ A0 n3 b# ?* l1 o* H级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。5 G; j# M4 h' |4 Q8 u" f+ A; ?6 L @
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:- ~: q5 Y$ i; S* J; F
7 Z: B, @& `2 L* `1 B r9 a
) x! j- T% o5 k+ W# |6 m% T
, p( z5 j! l& X7 @可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
* W" i: x1 V. n/ n4 N% k0 q R# [& q
三、STM32L4 时钟使能和配置8 x4 q8 J7 s. p7 p& k/ l9 | B# ^
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。
6 R$ [. g+ j, f7 |" x( L
4 R$ [1 f( w; }9 V5 c1 ]1 T/ f通过STM32L4的HAL库使能外设时钟的方法' v" }6 L& h* Z( S" M
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
( v8 t, p5 k) [ u8 m8 v5 m举个栗子; q4 ^$ {# l- `6 ^! I
看看 GPIOA 的外设时钟使能宏定义标识符:/ v+ ]( w9 s0 ~8 c' j3 T' {6 o
0 ~; B& D4 S7 O4 y m
- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
) y! w, _/ s! f, f2 u! v+ O; A - __IO uint32_t tmpreg; \ $ W" J) P* [. i$ e2 P6 _
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
y P4 v1 e @/ ^ - tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ 9 V- i" x* F! _& Q1 E; p
- UNUSED(tmpreg); \
0 P6 G2 H/ U3 Y c - } while(0)
复制代码 + B# @: I( ]' v" Q
/ V* G* O+ F% d1 o7 I9 s
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的: l9 O4 x$ ?' p" v" \, O0 ?
/ r, T4 G+ \( `" h- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码
, } a s, g1 M这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
; n) g- r' p6 S+ i# K" K2 ]2 M( f& x; {( b! d
- #define RCC_AHB2ENR_GPIOAEN_Pos (0U)
+ G/ {9 r6 e# F1 @) W0 M2 | - #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
1 P* H: _4 h t - #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
复制代码
3 @/ ~8 a& ], S上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
7 q7 H7 m' ^; M' t" {描述如下:
) I% t: `2 v, `
5 T- l0 e) G) h- Bit 0 GPIOAEN: IO port A clock enable // GPIOA 时钟使能 1 r2 K3 \4 v# [7 c2 D
- Set and cleared by software. // 由软件置 1 和清零
+ y# q7 r0 s0 t - 0: IO port A clock disabled // 禁止 GPIOA 时钟 * }8 o% S/ k# r" p1 [! P
- 1: IO port A clock enabled // 使能 GPIOA 时钟
复制代码 . e5 Y7 x0 B$ B7 V% F* k2 O
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
# g; @2 _1 p( b6 Q! i2 I
! [9 D1 o( a# V+ I& }6 ?- __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
复制代码 7 r s. x5 n& t3 v7 A- B
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:- __HAL_RCC_DMA1_CLK_ENABLE(); // 使能 DMA1 时钟 * J+ H- u& T6 G8 l8 \
- __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 6 z X3 `. x: N/ R" S/ i! w# d3 Q
- __HAL_RCC_TIM1_CLK_ENABLE(); // 使能 TIM1 时钟
复制代码
) s3 r/ y) j! ]5 r& L4 b使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:- #define __HAL_RCC_GPIOA_CLK_DISABLE()
$ n, {+ w8 d6 S" o& I& j' K - CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码 - y$ b- F5 j) M8 J# R- r0 E8 q
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
6 e; i. h1 T- Q9 q2 f) K1 B% t: [8 {9 O" e8 S4 r6 ], h
- __HAL_RCC_DMA1_CLK_DISABLE(); // 禁止 DMA1 时钟 1 a4 h' M( J0 I' V" k: r
- __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
6 b; u" [6 z5 S; v4 n - __HAL_RCC_TIM1_CLK_DISABLE(); // 禁止 TIM1 时钟
复制代码 % H- @6 J* u" Y% j' _% P8 }4 j
% b4 z6 S& ]0 `7 S: a: r& g* u7 J4 G5 J( J& N2 T3 ?: h( Y
2 Q1 s! a+ O1 y9 z8 t |