主要内容
$ {1 u( H6 u( M2 r+ _1)STM32L4 时钟树概述;" @6 n. h9 f5 T- S1 q6 V9 j# u
2)STM32L4 时钟初始化配置;9 z0 T7 O4 \0 e. B$ h u, r
3)STM32L4 时钟使能和配置。0 v( B0 ?) c6 H8 X* N
/ {0 l' h( D- l( g+ B6 N6 E4 c2 y一、STM32L4 时钟树概述" s( @% P; t, K5 W6 ?
时钟系统是 CPU 的脉搏。( [: n4 r4 i' T x, O5 v/ }, F1 t8 G
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。* T9 K% {* E* r' ]3 T6 t. k
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。
3 G% ~/ H3 U3 _ n所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
4 f' X5 T/ S e: \5 [) V
. h! n. _8 P* S$ ]: ?STM32L4 的时钟系统图如下:6 j L; Z0 ~( ?" i1 S3 ]" q
, d) o; W3 L) |2 m' q/ c8 u
6 g L/ o5 A0 h0 E3 j* B8 \5 }3 Q
3 W" M" B! ]+ X& B& P( c, N8 z/ j: r$ J* f; T, l
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
6 J# K) W, k$ }' T其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。! j, y. r. e2 r
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。. e F' [! y: s0 O4 D5 E; @
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。8 q$ T; `. U2 H* V# ?# w" U: R% l: u
按上图中红圈标示的顺序分别介绍这6个时钟源:
' U8 B4 Y8 ?: W% k- j) H2 s( j$ t① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。7 V6 E1 N+ n! G. b4 W" x/ `2 z
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。
) k% U% ?. w9 w) E p8 o③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
]3 Y: Y L/ m0 |0 B1 k1 ?9 T8 K9 X④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。
: s! ~5 W' l2 u& C; y; d! O⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。
8 V! j6 z7 a$ z; i⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:, q2 T$ Y" L$ R" T2 G
1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:* [8 e5 U+ ~( c H4 s6 J
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);6 p: w2 s) ], Y
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;; U1 _5 n4 ~; \& k b: y( `2 x% q
第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
. O+ J0 N1 L7 T$ B1 Y2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
6 T+ O- r+ x9 a, B第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;
0 }, f5 x# Y2 P第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
' B/ {- K5 b* g* w" l第三个输出 PLLSAI1R,可为 ADC 提供时钟。9 P# `, | y" }& i! g$ c
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
# z' J% c! Z) R/ Y, F第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;1 ~3 n( Y: ]5 s: ^! Z2 f8 i
第二个输出 PLLSAI2R,可为 ADC 提供时钟。4 d3 V* z6 i) y& g
0 d0 d. G7 n; e+ i) V' y这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:8 i' @. V1 |) v2 V# H
- j( O8 w/ s% ]" I. m( C$ G
& f$ F; R# {- H9 X/ V上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
* O$ z1 ^) b% K% t9 y) P/ h
" Y8 r% {7 d. |$ T6 O7 N举个栗子:0 W# D e1 F+ z
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:- J k; M" F' v
PLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz0 R7 b M# r) `# L
4 w5 c( I1 O5 E' L1 A
如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。3 _6 @9 b6 g" [+ J% u
' e4 F# @3 _6 s4 n下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
3 N: Q r' ?; C" w, Z观察之前给出的时钟系统图中标出的9个字母(A~I):
; n% z1 i+ c$ B7 N1 j* g# U: ?A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;
( q( D8 \; T# n6 v8 r8 V2 OB. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
2 B' X% d8 @7 D! l4 [: sC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
* p1 U; G* V% zD. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;2 ~2 [: |' s; i+ V% z/ ?
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;) s. O% z7 w4 L% v0 r0 _! b
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;- D9 U& _+ _9 `/ [; q/ d; L) h$ E
G. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;
/ ]. h" G7 V6 f7 O' c6 {7 d& |H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;9 ~! q1 e. ] H& a
I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。9 o) |0 c. E/ L* }- h# t
9 E" D0 Y4 Z/ r J7 ?备注
; P, N9 g# K' V; [ s* T* M( Y1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。" n9 B' J9 c% J+ u3 q5 Z( k
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。- X1 A, \3 j6 J9 X& y' |
6 ^' l' t) Q! e& E- d
二、STM32L4 时钟系统初始化配置6 x9 \; G. H1 ?" t
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:
9 \% x5 h9 Q" K' T5 A h7 Q% J9 v9 R4 o! l7 g/ }
- /*********************************************************************: j) `1 k! f& p+ P6 B/ s
- 函数名称:void SystemInit(void)
3 n( y' Q3 K8 l2 F9 c" b1 D - 函数功能:1) FPU 设置
" p# c5 R7 _1 B/ u4 @) @; j- p* q - 2) 复位 RCC 时钟配置为默认复位值(默认开启 MSI) ! ?9 t- x( ]9 {( k0 j" V. t
- 3) 中断向量表地址配置
: l# y# G; H; v) Z: p# T! x - 入口参数:无8 g/ O6 M! [& D. k
- 返回参数:无7 Y8 E, ^) T; z- J3 r9 Y# k
- **********************************************************************/
; U: N/ M4 W7 S+ d. I/ z2 U! A& j - void SystemInit(void)6 j, A8 X6 b; U- S5 z5 W- m# `5 F) _
- {0 t2 n: d3 T/ N/ k8 a
- #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
' n6 z" L# ]- R( X0 @ - SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 . X9 \( e* p) y$ p$ W
- #endif
) w0 N9 o/ a% A: X8 S - /* 复位 RCC 时钟配置为默认配置-----------*/
" j* G2 T7 z$ c( e9 p - RCC->CR |= RCC_CR_MSION; // 打开 MSION 位 ; l* L, \$ C5 M6 F y% |
- RCC->CFGR = 0x00000000U; // 复位 CFGR 寄存器 6 m j# |5 X( F5 o; z( S; R
- RCC->CR &= 0xEAF6FFFFU; // 清除 HSEON,CSSON,HSION ,PLLON 位
7 V& Z; G, A$ ]3 p6 f% g" ?, c5 F - RCC->PLLCFGR = 0x00001000U; // 复位 PLLCFGR 寄存器
0 M+ X6 X8 ^ t7 y; u- N" h( o/ T - RCC->CR &= 0xFFFBFFFFU; // 复位 HSEBYP 位 ; N A( d( U9 V2 q, W" c$ x. y
- RCC->CIER = 0x00000000U; // 关闭所有的中断
, I9 @( j( }0 j$ { - /* 配置中断向量表地址=基地址+偏移地址 ------------------*/
7 `; T9 |& }5 G - #ifdef VECT_TAB_SRAM
a3 S! [- h a" k% S9 c. H - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */0 N/ C( ]( @6 A$ @) `+ K8 o$ U
- #else
7 i/ n, q& V- \) P5 l* K - SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
- l" D4 o+ x" c3 b4 y - #endif
; c, b0 A6 N ? - }
复制代码 6 h, J; z& a0 N: x
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:& H3 B, `3 G: n2 p3 R6 _$ k- z/ n% E
# ^3 j6 X1 y1 k4 w) Z( {6 l7 o& R' D4 M- /*********************************************************************/ h+ Q4 C, j7 o- V
- 函数名称:void SystemClock_Config(void)6 ?, S, J8 T- `( A
- 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR$ x2 V) K* `5 g$ h3 b
- SYSCLK = 8M / 1 * 20 /2 = 80M' ~& a) o J/ V6 Z" K: o
- 入口参数:无, C+ T) g6 o% y1 X+ G" b6 n+ D
- 返回参数:无% F1 c$ ^3 y- g8 g8 x2 C
- **********************************************************************/
) @- _3 @4 Q# }8 M" k& u - void SystemClock_Config(void)
9 {4 P+ e5 a3 e Z) q - {+ [7 q6 t9 j5 y8 O* h7 A# |9 S
- HAL_StatusTypeDef ret = HAL_OK;9 z! L6 x& x% a7 J
- ' ?7 Z8 a* `* \
- RCC_OscInitTypeDef RCC_OscInitStruct;7 q3 r9 @3 n( [- b6 ?3 @: F
- RCC_ClkInitTypeDef RCC_ClkInitStruct;& R& z n/ q# p0 f- F" \3 l
0 L" z3 N7 H v- __HAL_RCC_PWR_CLK_ENABLE(); // 第一步,使能PWR时钟1 Q7 v* p9 n' J) C# K: F
- /*Initializes the CPU, AHB and APB busses clocks*/% z0 Q3 y0 h4 |4 c! C( H
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE
' T7 N' _: u: X) B) w$ f6 g - RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开 HSE
" W+ x+ H" r: T4 j/ ~7 l( w& M, E6 Z - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开 PLL
" r4 v3 }' ?& K - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 时钟源为 HSE
" E0 K3 j% e9 O* e& Q1 A" r$ E/ l - RCC_OscInitStruct.PLL.PLLM = 1;
4 d* h- v! h) A - RCC_OscInitStruct.PLL.PLLN = 20;
8 E6 [- {! I0 C& u# D u - RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
# j9 K+ N) v1 t - RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
4 I. W/ K1 o l - RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
' d2 B1 G' `+ t3 I2 F - - V& Y5 q" f8 ~* j
- ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); // 第二步,配置时钟源相关参数( z2 I) `1 Q9 H/ `
- /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
+ _. f+ c+ H" g/ f3 O1 p - " h( {$ y& O5 i7 T; F
- if(ret != HAL_OK) while(1);
5 d5 I2 q/ f+ {( j$ O) k* ~6 G
. c: o5 H1 ]$ V. H- l0 @0 s7 ?- /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
6 h: l, b2 J/ W4 W [) Z - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
& r* n) w; M1 L - | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 5 M% ]+ _' C, z! ^# e" L2 T# |
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 选择系统时钟源为 PLL" s# e$ k, q( a* n, d5 P5 M
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 分频系数为 1( M4 C1 j: w" C) O5 D
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 分频系数为 1- U. K3 U' x3 t# {9 K6 A Z; J+ M
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频系数为 1
& {. A7 Q A1 F6 w# O' l) A
' J& d" O i/ h& i9 H: B, Q0 q& `7 T- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数! E9 U j+ l: a* j3 ]! t9 n
- /* 第三步配置后,可以计算出:
+ |2 M) i6 Q" q# O) O - PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
- G8 M( m: d9 c9 j' E3 ?9 v - 选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
T. L9 C4 Q. r - AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;2 v: g: w' K5 Z% s$ E
- APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
% z: ~, x8 T7 ^( m% |/ e1 |8 \ - APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/" D5 d8 x; a1 {8 o. k
-
: Q& i; I0 j0 A. L8 y5 M - if(ret != HAL_OK) while(1);+ l- G) h$ R8 U" Y9 V, U
' G% O. X$ O4 l5 x- a+ ]; z- /*配置主内部调压器输出电压级别*/
! _' E9 O( Y6 ?& ]% C - ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); // 第四步, 设置调压器输出电压级别$ Z5 x) j6 U' x- ^7 @
- ^% G; N2 p/ A! C' A( ~4 n- if(ret != HAL_OK) while(1);
& U% T3 A! k2 H+ w- \: i) G - }
复制代码 * G$ u: r0 J7 b' Y7 i
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。9 }" a7 l8 b' p! Y* B
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:
: b2 h1 ~" ?5 x8 c1.使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;& V' J. [% c w5 v8 ~: H0 i( I+ K" B% g
2.配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。" U6 M/ o0 r+ B
3.配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。
, V* `( q# f- `4 ^0 k+ x4.设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
0 O" L( B3 I: l# Q1 \对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:+ Y5 a0 O9 N8 q; n2 h* c
- HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
复制代码 6 N; h8 R- L1 v! ?- r" g6 [( v8 G
HAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:; w I9 k) W$ q3 N5 e* Z2 ^
) P8 @4 S) w {- typedef struct 4 f _$ ^8 H& i) P9 j0 T( j: @
- { L/ v0 O& `6 c3 e9 C6 c, n! k; r9 M
- uint32_t OscillatorType; // 需要选择配置的振荡器类型 O4 l( U i3 O9 D+ N! J
- uint32_t HSEState; // HSE 状态 # G. o" D$ e& x2 W
- uint32_t LSEState; // LSE 状态
2 B' f9 {! r. H0 ] - uint32_t HSIState; // HSI 状态
# [/ {9 E0 U) X1 j r5 `! ^ - uint32_t HSICalibrationValue; // HSI 校准值
6 D" T5 t& K& G0 j: E - uint32_t LSIState; // LSI 状态
/ R) i; o- U2 T. ^; x2 S - uint32_t MSIState // MSI 的状态
( `3 c, i( {! N7 m - uint32_t MSICalibrationValue; // MSI 校准值 * M7 S( t: }& z# ?3 y0 S3 \- @
- uint32_t MSIClockRange; // MSI 时钟范围
; C9 z# k9 U$ N" q - uint32_t HSI48State; // HSI48 状态
) ]0 C. {( L- h) g2 N- x' c - RCC_PLLInitTypeDef PLL; // PLL 配置
$ @4 ~# S: F% O! w0 k M, k7 l - }3 [1 x* j: y2 L# }& Y3 c7 r
- RCC_OscInitTypeDef;
复制代码 ; M& K0 m* q1 T/ ]2 B
该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
9 Q0 W6 Y% n3 C2 Y& d6 k( G* I该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:( x8 M3 P6 X, c* I. f
( e, I/ p8 p3 g1 p* I- typedef struct
1 B# F# y( J! V2 ] - { ' j9 z/ q3 ^ B, M
- uint32_t PLLState; // PLL 状态 4 B, a1 L5 N# X. _
- uint32_t PLLSource; // PLL 时钟源
; Y- A: M# J/ o0 i8 g( D1 j) i - uint32_t PLLM; // PLL 分频系数 M 2 w/ I# Q1 r: B, @6 C0 k' s
- uint32_t PLLN; // PLL 倍频系数 N - P q; ~9 v+ c3 {% v. V
- uint32_t PLLP; // PLL 分频系数 P
& ~* t2 a( E" J/ K x - uint32_t PLLQ; // PLL 分频系数 Q
: ?/ V9 D+ _1 c1 ?: x; n - uint32_t PLLR; // PLL 分频系数 R , n% T% ^3 |+ n6 Q9 [& j
- }' |+ D) x9 U6 i6 l
- RCC_PLLInitTypeDef;
复制代码 5 x: a h8 c( v
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
( Y8 W' }; f+ T+ K6 c& X5 Z) ~4 p" M6 V/ f( A
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
) L5 a1 R. F3 @5 ^- w' d0 Y, j# k% l6 `# W
- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
复制代码 / P3 o4 ^# b( K0 m
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
, L: S- n ?8 M" q t6 m) r: A; r& P, G& v5 k& [) s
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的
7 m- h: W" a- q% m7 E延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
2 u" E4 k! H7 i. o首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:
0 o! ]6 F, |! U. G# R/ O- v: J4 z- R; `6 J. T6 [' U
- 位 15:14 VOS[1:0] p& e# F" O) c- \, B7 `% n
- 00: Cannot be written
; q- H* }- k! d9 ~5 [ ~ - 01:Range 1 _$ R3 h% P5 j4 h1 C' ]
- 10:Range 2
( m8 e! i& Z1 o# \) M - 11: Cannot be written
复制代码
5 \: k$ y2 y2 k2 u' r3 O级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。" j& r+ P% S) h
配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
5 i7 `/ \. v; _% J
1 a* @. E* W; g9 k
; x* q& n. A0 f6 X
2 t: A' d/ h* H4 H- A3 o2 M可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。, v$ i' ^4 I. J0 `5 m) z
' Y) r. M: f# D; c三、STM32L4 时钟使能和配置
6 X3 c! J6 P! z% L6 C在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。& C. U# {2 P* \% C; J* z' ~; Y
, p2 ^5 k' T% ~# d/ a/ o+ I通过STM32L4的HAL库使能外设时钟的方法! u* W, B4 |% q( X" D
在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。- P! _+ V; [- j- z3 w$ p5 o
. r% S. j9 `- c" v( ?: G举个栗子
: w+ C N9 d; p. T看看 GPIOA 的外设时钟使能宏定义标识符:
2 d! z V/ J1 C
: g8 X+ G F3 x4 N( r- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \ # t9 j% F$ H3 V% R0 B: U8 U
- __IO uint32_t tmpreg; \ & s# o3 `7 z; K- f2 _; K
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
+ W+ a9 W" t6 F: Q# [0 ^ - tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ " f2 o2 y0 W, {3 ^' W
- UNUSED(tmpreg); \
% @6 ^1 K# _) `4 n. w - } while(0)
复制代码
% J) p6 {3 ^8 J这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:2 W8 b& t, Q. t
7 \0 H# u) M6 @0 E4 H
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码 2 M: [" C$ r! p/ M% j2 I1 `2 X
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
( L; [0 u5 u Z; s: H
, q' u+ X: l1 @. b* r- #define RCC_AHB2ENR_GPIOAEN_Pos (0U)
7 V7 U3 j1 A+ F( L, t# @ - #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) 2 t9 U: K! Q# Z9 [# Z& X. h
- #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
复制代码
- N! z w! Y; o( q8 x0 B9 L上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
' b1 u7 x% o+ G, ]0 G+ j! d8 b描述如下:
1 D9 K) j4 H+ f/ y
6 G% q% Z: l# I; Y1 a7 Z* q- Bit 0 GPIOAEN: IO port A clock enable // GPIOA 时钟使能
* b c4 h3 f9 V+ e- g. e& S: s - Set and cleared by software. // 由软件置 1 和清零 " t5 m( ~! N+ }% ^3 d
- 0: IO port A clock disabled // 禁止 GPIOA 时钟 ! E9 L8 }/ ?( e
- 1: IO port A clock enabled // 使能 GPIOA 时钟
复制代码 1 W ?6 y# |# m
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
5 S- g7 u7 Z4 W( V( ?2 _, ^. j$ v* F& T; b5 w( }" Z
- __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
复制代码
/ J7 E( |1 y7 C# R" M对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
, n' }. |( p0 ^/ n- z
! `* ] u [5 B8 H4 p$ L- __HAL_RCC_DMA1_CLK_ENABLE(); // 使能 DMA1 时钟
6 Q$ w) e* P: E. o - __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 # U: E( F" O7 x5 ~8 ^4 O! F
- __HAL_RCC_TIM1_CLK_ENABLE(); // 使能 TIM1 时钟
复制代码
$ N$ t9 F5 h U7 w使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
^' `1 ~ S5 z
( [7 e9 f$ t5 `5 q U% f2 K- #define __HAL_RCC_GPIOA_CLK_DISABLE()
+ f! ?7 [2 {; I1 e - CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
' y- i4 M# g" n# a7 H% J同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
9 o9 |3 W" i: ?, c5 D
" \' S2 [6 @4 C4 k% `; `7 d& m- __HAL_RCC_DMA1_CLK_DISABLE(); // 禁止 DMA1 时钟 : I0 a6 ?! t0 Y- r; \) z1 x
- __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
' G* C9 M. @( ?. Q6 O: \ - __HAL_RCC_TIM1_CLK_DISABLE(); // 禁止 TIM1 时钟
复制代码 6 K, O, s, |( V1 E
————————————————
6 x6 u. T3 Z- I版权声明:天亮继续睡
8 r9 j" g: ^! e; r
( _+ M5 d# c0 l: J7 `7 P5 i( }" W" w5 b4 |& e8 n# T; l- V, a
7 V8 ?' c& N* G) B
|