主要内容
" |2 ~* b# i! J: f! t7 ^" Y' `1)STM32L4 时钟树概述;
7 k7 J) a1 @" x! t0 \2 @3 S9 I* X( Z2)STM32L4 时钟初始化配置;6 t- P6 m2 ?( H. t
3)STM32L4 时钟使能和配置。0 k, [( n1 S- f2 x# n9 d; ^+ C
( j; T* O* ^* F* e
一、STM32L4 时钟树概述
5 E0 @7 S( m H" p& }7 K0 m* p时钟系统是 CPU 的脉搏。4 E9 f9 J% y( Q+ j
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。9 ^- b- Y9 w0 G- H1 e
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。# c/ H* Q3 C, S% _. |' c
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。0 G1 V8 B' {4 \9 d3 a( o9 ]6 e
( i0 ]* N% y" N9 V5 ?% TSTM32L4 的时钟系统图如下:
8 W9 t G: b5 t: X- [& |6 S* U6 A2 F
" ^6 o. Q+ d! e* Z3 A) W
& \5 Y& l2 S5 e+ m5 c/ d5 j
9 M3 |/ { `) p, K. ^* N: \在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
9 P4 x1 X, `# I7 h0 d9 O其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
6 ]) j5 R3 _, N按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。* l6 P) ^( ], Y% ?$ H8 Q
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。
' l4 I) `- y9 h按上图中红圈标示的顺序分别介绍这6个时钟源:
/ b* b5 i9 r* Z' m y/ j① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
$ B6 V5 B# A& h9 |5 N# u② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
: N5 `' D0 V8 H0 t③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
, Y6 W( B7 Y8 u2 J! |/ o- w) `: n④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。. @& d9 A3 P& r* ]" g% u0 e
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
$ K" ]+ }, g/ g# y \* J& N⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:) N/ @; H [8 X9 y0 y# S( |1 x
1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:
7 b4 c$ B( F8 s% E第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);
+ o: i% w- P* L5 j. K第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
! ]# ~0 o' E1 t第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。' b9 N: Z$ o& c
2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
4 _9 k3 K' p, K/ d! }$ T! [- R第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;: g# N' P" P( y) G0 `
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;# ]7 g$ \/ B! \8 I
第三个输出 PLLSAI1R,可为 ADC 提供时钟。
' ^; k8 n# X7 ~% `! O3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:/ j- f; G+ q0 H& z+ {2 [6 H$ s7 k1 }
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
7 W9 ] E$ a( }- x% k& ?8 M第二个输出 PLLSAI2R,可为 ADC 提供时钟。
0 B, j/ ^) N$ L8 G1 J+ F9 P
; ?+ b- k, i% p+ ]' @这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:( B+ x% x* [6 B% H8 Q
( y* Y# Y8 p m6 l) Z6 O1 i
. b: ]3 D5 ^* l5 i上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。: L2 P# o+ O& _- Y% O
3 {! ?& q( _( r) z! m. o. ]
举个栗子:9 H" Z0 A M$ O5 E$ n
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
& ]3 `8 y) a$ _
: I$ p: _' U. U& JPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz
1 s6 R0 u6 h1 a4 e8 ?如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。3 J3 q) o8 K: S4 x# A
5 ^2 b3 O% |+ Z% ^
下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
3 K9 S& D1 S0 x! R观察之前给出的时钟系统图中标出的9个字母(A~I):0 @* y* E: @ {/ D
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
1 K4 F- P* U' V6 SB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
6 `/ }' t" G% U& A4 {" JC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;( L1 h4 j1 H9 X$ ^2 }
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;
- \! ~/ v7 x5 s2 [. }) g1 F# M# @* l7 XE. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;# O% c+ K6 Q8 B& ]3 @- X* u; g8 P
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
6 U# Y9 L; T, pG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;# m9 K# n M1 U8 |, a8 K
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
( c$ R) d, v0 w8 d i! x& TI. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。( F' A2 q2 P& T8 `
备注
M/ `5 y7 Q4 K0 t% E( y- i1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。
+ k! j5 J* j5 s+ P% S2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。' ^2 A! W+ Z z% N }
, L& l4 C0 K F1 R; `
二、STM32L4 时钟系统初始化配置1 W4 l) \6 }$ Y! W& y
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
) R' S. N. _6 h, `7 E8 Y6 K" w
9 {- y8 M. f1 d7 g/ j% n- /*********************************************************************
; G& ]4 d2 c# q - 函数名称:void SystemInit(void)
, i; U4 g1 E$ ?5 m( n$ E4 b4 u - 函数功能:1) FPU 设置 6 b. \+ K7 Z1 o G) N6 M
- 2) 复位 RCC 时钟配置为默认复位值(默认开启 MSI) " @6 y& ?) ^/ x. b! M1 _% i
- 3) 中断向量表地址配置
$ E* c1 b2 @" t4 E - 入口参数:无. t8 f* k# N. _7 B! [
- 返回参数:无' \+ j, {& b- o5 X. r7 `
- **********************************************************************/( W |/ \8 O9 B6 n/ K* F# R, T
- void SystemInit(void)
. f' a$ f# O3 Q; H3 _6 Y& a - {
2 B0 j/ f1 R2 q; K) n - #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)) c! o: s; u3 @
- SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
4 `: N% p- q' s. ^5 |( ? t: z - #endif
$ Q9 ~+ W+ i* ^+ { - /* 复位 RCC 时钟配置为默认配置-----------*/ ' x( ]+ e8 a) f: U
- RCC->CR |= RCC_CR_MSION; // 打开 MSION 位 $ Q. g- _7 j! W* t" B1 f' v" H
- RCC->CFGR = 0x00000000U; // 复位 CFGR 寄存器
: x1 X: n; c/ d8 k7 L8 E1 B; _- x0 q - RCC->CR &= 0xEAF6FFFFU; // 清除 HSEON,CSSON,HSION ,PLLON 位 % a# C4 p# _2 X
- RCC->PLLCFGR = 0x00001000U; // 复位 PLLCFGR 寄存器. l( v9 g) i C! O7 r m5 F
- RCC->CR &= 0xFFFBFFFFU; // 复位 HSEBYP 位
& ^( S9 }& H/ G- ?3 ]! [6 Z% ?, B - RCC->CIER = 0x00000000U; // 关闭所有的中断
* L& _5 y& f" d - /* 配置中断向量表地址=基地址+偏移地址 ------------------*/ ( X3 d# M& U r
- #ifdef VECT_TAB_SRAM
9 X' Q$ G" ?3 O, [, b8 u) E/ t - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */7 o) ~1 L6 K; _7 M, L1 \" n, e
- #else
* }5 x# c7 ~# r - SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */1 P) @: i3 L8 p, @! `2 q9 m0 e
- #endif
3 V) Z9 g/ Q8 u# Z( c - }
复制代码 ' L0 t' N- U/ P! }$ |1 ^( b
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:1 a6 c) u" E% O
' S" K0 a$ E& Q$ @' F+ m
- /********************************************************************* U% ^' `: m: e
- 函数名称:void SystemClock_Config(void)) J; I" r& z) [8 a9 e. u
- 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR0 Z" H5 y- s/ S( ]! ]5 b- v
- SYSCLK = 8M / 1 * 20 /2 = 80M
- S* ~" U4 M- b; l8 [- C - 入口参数:无+ ^1 L' k" o! W1 t+ B& E4 {6 u5 Z
- 返回参数:无
! @1 }# V2 l* A. \) Z! K - **********************************************************************/
2 d2 h( ~3 x* y/ x! V - void SystemClock_Config(void)
8 k" R3 m6 A2 Y7 [0 ] - {) U3 b# R1 b) C3 K
- HAL_StatusTypeDef ret = HAL_OK;* p8 s( o* Z% t0 u- Z' P5 G
' f# y2 @7 ?' f- g( q% u" y7 s- RCC_OscInitTypeDef RCC_OscInitStruct;
! f7 q2 L' M5 k. u, \ - RCC_ClkInitTypeDef RCC_ClkInitStruct;: N# A( z$ c9 V( u
- # D; x. P9 |1 E
- __HAL_RCC_PWR_CLK_ENABLE(); // 第一步,使能PWR时钟7 ~: f' L! u& J0 o1 }% q
- /*Initializes the CPU, AHB and APB busses clocks*/
6 b' |1 O! K) A. n: B$ g! {8 w. U - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE
5 u' ^3 Y) O2 b - RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开 HSE
# a* ^! \; v4 O' A% ^ - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开 PLL 5 p, ?$ E6 k+ I% a7 K8 I
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 时钟源为 HSE
7 V8 Q: F3 E2 p) F9 K* c2 l; ] - RCC_OscInitStruct.PLL.PLLM = 1; S3 {' C7 Y) d# u, t
- RCC_OscInitStruct.PLL.PLLN = 20;
$ l2 z/ l8 }! @( z0 o }5 V - RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
$ `6 S$ I9 I C- Q$ N/ N1 i - RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
! ]/ \1 c( A$ N# }) U2 @6 ] - RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;7 W8 C) u" D- `& {8 g
- 6 C: ^1 a2 ]1 J7 H3 w% M9 `) `
- ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); // 第二步,配置时钟源相关参数% X9 ~1 k* q2 S$ S, r+ L, w( N5 ~6 k
- /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
& ^" j/ b: h0 i4 V
' U! h1 {9 K2 T1 o- u8 n# n U- if(ret != HAL_OK) while(1);! Z) y2 g7 A1 d) Y8 W R6 n
, A( [0 v3 n @ Z' N- /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ # b; _0 i( d; ? t9 i- T
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
" I9 |! V- a0 y0 W7 E; K1 b: N b: x - | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 ( U+ H5 g. V. N) [& x! g" y
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 选择系统时钟源为 PLL
* {8 m( d+ \# X& q - RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 分频系数为 1
* h) F- N R; d @4 J/ @ - RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 分频系数为 1
* i+ Q+ S' W8 K - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频系数为 1
( K8 B" ]" c6 i3 a5 @; U
4 K8 b: v1 U4 w$ y% `- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数) n$ f3 {* v- u3 l! n6 P5 I
- /* 第三步配置后,可以计算出:
+ _& }( G6 U. \' @0 h7 G - PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
1 c. h5 j5 X6 c* j1 E- z - 选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
$ w0 M2 T5 G1 B ~. A0 T9 b: |( r2 { - AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz; g3 R/ o$ S b# c
- APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
9 Q# ]7 s6 d1 X5 d0 Q - APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/& }$ e: N" J8 C, o8 b
- 4 ?2 [2 e" p) Q, m! ~, O# D
- if(ret != HAL_OK) while(1);
7 b7 P: O6 r2 L! N& G
! {# k0 n. J; x+ t- /*配置主内部调压器输出电压级别*/8 h! D. X" m; v, ^; z5 a6 P
- ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); // 第四步, 设置调压器输出电压级别3 J# c+ S. |% X# O+ h* [
- 6 R+ N s+ u3 g/ N( T
- if(ret != HAL_OK) while(1);5 L8 C* O" p! L. B" s# K- A
- }
复制代码
4 ? A9 X0 @0 [4 T7 q1 f函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。, p. g0 A2 z3 G n; }6 h0 F
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
0 I: `% K1 q+ D/ f. w
" Y5 S* ^" o9 M. l# S使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
( ^5 x* a2 r3 K配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。2 o) \- n' } j( t
配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
& M8 A, Q; z1 ~( K9 y0 y0 e设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
/ R5 {' K7 o* E8 P p q对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
" N0 W4 [" ?" y0 V" o9 o0 y- HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
复制代码
+ ^, q- {' [0 M7 T: ^# bHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:; y' Q" P7 S' n
Z. _# K# M# y! Z$ j0 K- typedef struct
" L+ B6 I, R' d( G - { " z8 l1 C9 l" N7 S+ q# u
- uint32_t OscillatorType; // 需要选择配置的振荡器类型
9 m, V& c; |: a( ]* f0 b0 y - uint32_t HSEState; // HSE 状态 5 k3 M: R5 g# q" a7 A3 V
- uint32_t LSEState; // LSE 状态 ]. y: { W' ]% f! d
- uint32_t HSIState; // HSI 状态 + E$ a+ N* m; e6 t- H4 D5 [
- uint32_t HSICalibrationValue; // HSI 校准值
n5 n6 ]# I) w [ - uint32_t LSIState; // LSI 状态 9 f# L3 N g. D% G8 v
- uint32_t MSIState // MSI 的状态 ) R7 ~# J& E/ Q9 J0 {1 v
- uint32_t MSICalibrationValue; // MSI 校准值
6 K) r' |! R$ @5 m0 ?/ T# y' J - uint32_t MSIClockRange; // MSI 时钟范围
# L8 W3 S/ m' n4 `/ M) \5 N% z: Z5 P - uint32_t HSI48State; // HSI48 状态 ( N8 w; p a) V3 K
- RCC_PLLInitTypeDef PLL; // PLL 配置
3 g- X1 E7 `, ? G# m3 m - }
# ` d1 u7 N2 h' a& E8 ?$ R4 J - RCC_OscInitTypeDef;
复制代码
& \# V/ M9 m0 B0 A8 u d! x5 {8 P该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
. r6 X3 T+ ?8 k+ U& \% s该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
3 ~- J( d* Y" g1 o8 k2 O9 c
. g6 F* q7 |2 [: Z- typedef struct
0 _. L p6 s4 ^. f - { . p" L2 u/ y: o
- uint32_t PLLState; // PLL 状态
* V; T. i9 n* {# |) h6 j - uint32_t PLLSource; // PLL 时钟源 # Y$ e5 O! F2 z7 h( e
- uint32_t PLLM; // PLL 分频系数 M 3 M" J7 `5 @ H& T
- uint32_t PLLN; // PLL 倍频系数 N ' D7 G- y4 D4 A
- uint32_t PLLP; // PLL 分频系数 P
. v* [7 M% f' u, M1 G: r - uint32_t PLLQ; // PLL 分频系数 Q 6 d/ H3 L+ Y) F9 `& s
- uint32_t PLLR; // PLL 分频系数 R
@ P- ]) P( U) _( R" j7 q - }
9 \* ~3 K) H! l# o, w2 o, F - RCC_PLLInitTypeDef;
复制代码
( N- I, b A' B$ M" P4 `该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。, N. {9 {2 S( x
3 L9 T/ j8 f8 o! ^' A
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
~' c, n9 b1 a" p
) @! f: E/ W$ S0 x0 U# N2 ^- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
复制代码
2 R: M: i' x4 ], W$ _该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
- C- `( l- i* [9 o0 e2 B: J) d# k. e u2 O7 f( V+ A
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
9 n( j! t7 @" j' @$ W* c延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。3 Z5 x% h6 l/ a( V+ s% g; ?
首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:: |6 h. X* x4 Z7 K! ?" E1 W- S4 f1 u
/ s x& k# ]6 M& n6 f- 位 15:14 VOS[1:0]
$ O4 f, T+ n, r/ c$ ?1 \9 Q - 00: Cannot be written 2 ?' s& o' I: L, u6 W0 ?
- 01:Range 1 ; c" Q" H5 ~1 v9 I* C3 @
- 10:Range 2 . S$ Q5 u3 B/ i+ A0 x
- 11: Cannot be written
复制代码 2 o' ^& S$ ?6 M
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
7 @. ]( w% a5 A配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
/ Y9 P4 \6 g" P7 ?# e6 Z0 W. C% ^6 w" X# z" Y
: j! ^7 ~* M. h: `- E+ V, ?2 u2 Q6 t) `, A
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
1 e$ ~. v' h7 s) g. o' Y5 r7 M+ }/ R- r5 x. J7 ~' a& |! f
三、STM32L4 时钟使能和配置
5 A% d& ]1 X7 _在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。, o) L" X5 U2 I# ], P }) F! u# E
" f J: E, B( m$ W
通过STM32L4的HAL库使能外设时钟的方法
3 S. Q9 D% x7 [; A Q% |2 q* }' f( ]' x在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
" e; T6 z$ c* S* E. m2 D举个栗子
1 ?, K* {( V4 T( Z( @( V8 e看看 GPIOA 的外设时钟使能宏定义标识符:7 _* g( A- a6 v* M
8 G9 v- w+ k5 f/ j& |( C0 T5 I% d- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
( y6 f% E& g: _9 C( H4 r3 d( _' t4 k - __IO uint32_t tmpreg; \
1 q$ O9 E; D6 g - SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ & q& m" N* c2 I8 m1 ~
- tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ , l0 i0 X y: _. X- m! |
- UNUSED(tmpreg); \ 2 ~) z7 r: `! `6 H* J& }/ }$ I: D
- } while(0)
复制代码
9 J# c5 v! j2 v f! k% R( h& S# M( q7 j) ]$ c$ X5 t5 ^
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:5 ~0 b/ P0 I# i, A; n+ q
& n. X0 [# S/ e) G& \; s2 t
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码 " |0 l- Q4 G$ [9 k* ]
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:' N+ j7 V% J7 C+ d
: P; W5 v: I; k4 B- #define RCC_AHB2ENR_GPIOAEN_Pos (0U) # b. k! q: _9 C; X- F! _* ?- _
- #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
~. ]* x$ T( X7 i - #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
复制代码
; p7 D$ G1 e7 x: {# a; S( F上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
6 P; n+ N- S' T& ~; e9 Q描述如下:
, b8 y+ u4 q4 [( ?( ^* \" {7 C h/ L F3 F% P- C4 T
- Bit 0 GPIOAEN: IO port A clock enable // GPIOA 时钟使能
. t0 Z' f" W1 s6 J- L2 `- H4 h - Set and cleared by software. // 由软件置 1 和清零
, T8 W3 s9 _/ e - 0: IO port A clock disabled // 禁止 GPIOA 时钟 * v$ Y& {5 k( C. t; ~4 q
- 1: IO port A clock enabled // 使能 GPIOA 时钟
复制代码
& v7 g9 Y/ ?4 ]& O- l那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
# m4 r2 k3 G3 u% [5 X' Q) H% a3 o) I- H9 i8 a% f' @, S7 B$ ^7 o5 j: c
- __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
复制代码 ; a8 U: L2 Q8 P5 Y
对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:- __HAL_RCC_DMA1_CLK_ENABLE(); // 使能 DMA1 时钟
5 v. `, A2 C# t# z; N - __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 0 Y& @5 D1 q2 u8 V! Q! b8 {
- __HAL_RCC_TIM1_CLK_ENABLE(); // 使能 TIM1 时钟
复制代码
) Y. p. }6 b7 `) g" Z0 a使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:- #define __HAL_RCC_GPIOA_CLK_DISABLE()
" p$ J' N0 b$ g# W6 ^# a( @# @ - CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
4 }9 J8 E, P2 n1 [& E% f6 G8 L同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:# _- S6 t, g5 g8 M, R9 R
, y4 I# N0 K" @' M, H, ?/ w- __HAL_RCC_DMA1_CLK_DISABLE(); // 禁止 DMA1 时钟
& o# X2 V# {! \9 ], t0 W' Z - __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
* i+ h: e& W& B: k& t - __HAL_RCC_TIM1_CLK_DISABLE(); // 禁止 TIM1 时钟
复制代码 l: C' Y& O( r
# c# X7 V/ D) a
" U% N2 C, j+ [2 C) r
+ s F3 ~8 y9 a! b! X |