主要内容
( 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
6 y; V3 _ r# J( j* O( q
- 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
$ 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- /*********************************************************************
8 R" ^/ N; k3 D6 ^$ {1 _$ _ - 函数名称:void SystemInit(void)
* O) S. b+ h. M5 G/ E - 函数功能:1) FPU 设置
+ V. o- }9 H2 g, y - 2) 复位 RCC 时钟配置为默认复位值(默认开启 MSI)
! }' A" Z" ^8 ?) w0 e - 3) 中断向量表地址配置
" E8 V' c+ t* T: |, W/ _ - 入口参数:无
& M* _: C$ u' m2 A - 返回参数:无9 Z7 z3 F: j+ T' u h; q3 _ f
- **********************************************************************/
4 I' |; w! |# g- u6 m0 N: z - void SystemInit(void)% e7 {! B5 X9 r* l+ r7 q
- {" j3 [1 b e# Y. q4 A
- #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
/ L* c, z( h7 Y4 @" \5 x0 A - SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问 $ b& ` {% d+ m G, X* d# \! o6 ]
- #endif
/ M, f. n9 B# d - /* 复位 RCC 时钟配置为默认配置-----------*/ , v4 u2 E8 D3 L5 U* p1 a
- RCC->CR |= RCC_CR_MSION; // 打开 MSION 位
5 ?1 @; ^4 P3 j8 _ - RCC->CFGR = 0x00000000U; // 复位 CFGR 寄存器
, C/ C; O+ h5 @# ~5 P8 V - RCC->CR &= 0xEAF6FFFFU; // 清除 HSEON,CSSON,HSION ,PLLON 位
7 a+ c5 x2 f/ _0 O5 y7 I - RCC->PLLCFGR = 0x00001000U; // 复位 PLLCFGR 寄存器
( i! d, U0 i* I# X$ K - RCC->CR &= 0xFFFBFFFFU; // 复位 HSEBYP 位 , Z, V. P* n2 r9 t: ?! z
- RCC->CIER = 0x00000000U; // 关闭所有的中断
; T5 w! a5 g5 O* a* U - /* 配置中断向量表地址=基地址+偏移地址 ------------------*/
; N6 [% ~" V6 A8 _# e - #ifdef VECT_TAB_SRAM
0 }# e2 ]5 [) ]. R; Q# @ - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */# a( Q- D( @6 E, I/ W
- #else
/ H$ _& @6 q, y5 K- A+ ? - SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
" `0 D( ]* E: S% Y) ` - #endif
" @# ]4 E- P% w6 [# T- i - }
复制代码
& ?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
- /*********************************************************************+ \0 k& k' u2 o# `$ w H
- 函数名称:void SystemClock_Config(void)/ Z- f9 a/ I1 p( p
- 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
- q+ f3 F9 [5 u- k( } - SYSCLK = 8M / 1 * 20 /2 = 80M
$ m2 U+ C7 i" ~) {6 \' [ - 入口参数:无
% p! \( C4 j3 @& G# g1 T - 返回参数:无
% k$ x8 F2 ?3 ?/ |, j - **********************************************************************/
7 w( W1 v! Z* ?* X1 h8 E - void SystemClock_Config(void). z$ @+ F9 z7 ~+ N( u7 J
- {* N! V% p [, X6 M' `; K+ M- I& ?3 G
- HAL_StatusTypeDef ret = HAL_OK;$ v( ~& Z! Q! @8 u
2 V. ~: s% K- y- RCC_OscInitTypeDef RCC_OscInitStruct;) _, c$ v$ V1 U9 o/ o. \& F- u& \
- RCC_ClkInitTypeDef RCC_ClkInitStruct;9 A$ f5 L1 W) w/ w ]7 o6 {
S$ O* Q0 [ j# |) i9 @( p$ O- __HAL_RCC_PWR_CLK_ENABLE(); // 第一步,使能PWR时钟( Q/ m1 V* G5 w
- /*Initializes the CPU, AHB and APB busses clocks*/
8 M& D. D6 {% g5 s' ^" [! h: b - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE * e7 \0 i7 ~4 d6 \- D
- RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开 HSE ; ?- ? p6 S, n4 y) c% k2 w( I
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开 PLL
( ]8 ]$ P; D& A# Y6 @+ ] - RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 时钟源为 HSE
" x4 ?6 o# n: n3 `. p' m0 t3 O - RCC_OscInitStruct.PLL.PLLM = 1;
+ ^$ b4 L3 L' {+ h7 r5 W - RCC_OscInitStruct.PLL.PLLN = 20;$ `- V6 \- S' {6 {8 {* H$ g
- RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;2 C/ Y, B! L4 W" W! J1 O
- RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
, p0 C/ c% }0 w) x9 K, i# `' M - RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
( y) b5 O J. X9 U* L7 n! X
+ L0 y6 y# l5 m U4 ~. d- ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); // 第二步,配置时钟源相关参数 n8 I6 I" F4 s; Z6 p# Y
- /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ * f/ U+ s N3 n: f& d1 E8 T- X8 Q0 v
7 Z7 t- O% h4 W) k! e) r- if(ret != HAL_OK) while(1);; z! T5 q7 j6 d" X9 n, v) J: `
- ! \; O- t* Z: N- P7 c
- /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/
6 A2 d( \3 F8 f& s* j - RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK( [% I; l) j5 H$ U8 R9 }* b
- | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。 7 F H1 m- l* G# C5 F4 p0 @
- RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 选择系统时钟源为 PLL9 x: e2 `9 d! y3 b. M: z: G
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 分频系数为 10 [! `+ I2 k: d, \- m+ l3 `
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 分频系数为 1
& F$ H% g' I( f" V7 S6 }: h - RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频系数为 16 ^: `1 `1 G6 l% u0 m
- . I/ d- G: ]5 [3 C& u: Z6 j
- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数
0 x6 t# K) [3 Q# e - /* 第三步配置后,可以计算出:3 Y1 E' i; X2 {+ G9 V7 \4 N
- PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;
R3 [: e- a' T* }" W2 s - 选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
' H# S2 N l4 B+ B9 C$ n! L$ m - AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
% H+ H z O C9 ?9 a1 {. B$ Y - APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;, n. h7 w" a( v' D! T
- APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/% c; K7 |3 V- D
- ) Z6 M) j) ?9 W! }; u+ a
- if(ret != HAL_OK) while(1);
% M* i1 c- M$ r" k% \! h
9 A1 T- }- [; \0 x1 _0 r: e- /*配置主内部调压器输出电压级别*/
* h ~6 c- K' V2 O. V- @ - ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); // 第四步, 设置调压器输出电压级别& z& s, @% Q# n0 K) s3 v: B; c
5 P) b8 Q; v' y' H" S, B. f) P5 A- if(ret != HAL_OK) while(1);
( U; ~) f4 r/ o; a: Z1 } - }
复制代码
' }/ 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
- 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- typedef struct : y0 C/ e, L' T! u3 X
- {
- q( h' `- N d9 z% T% {# _ - uint32_t OscillatorType; // 需要选择配置的振荡器类型 * E8 s P. ^3 L3 l; M& ]
- uint32_t HSEState; // HSE 状态 ; S# e" | J; t: J: J
- uint32_t LSEState; // LSE 状态 7 x0 b: g5 ?( Z, w3 ^
- uint32_t HSIState; // HSI 状态
3 t) [' Z2 [0 a x4 @3 o8 m - uint32_t HSICalibrationValue; // HSI 校准值
7 L0 s. Q% \6 L D( Y - uint32_t LSIState; // LSI 状态 2 J p/ j" a% S
- uint32_t MSIState // MSI 的状态 9 m( ?$ R8 K, k% \7 R
- uint32_t MSICalibrationValue; // MSI 校准值 6 ]1 d4 R$ S2 o8 L: k! k
- uint32_t MSIClockRange; // MSI 时钟范围
2 E( g' w+ ?1 [4 { h# x# b+ [) ]2 W6 a - uint32_t HSI48State; // HSI48 状态
' W+ D; { e: V3 @* [4 f - RCC_PLLInitTypeDef PLL; // PLL 配置
- Z! _, V' P8 L( j - }2 n7 A4 z5 G* t/ i4 d5 P" P' @! @
- 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- typedef struct
% d7 d' d6 c. v& h! f - {
2 h) r: {6 e. K. ~; t - uint32_t PLLState; // PLL 状态
: m1 ^+ q$ r$ K! F1 q0 |. h - uint32_t PLLSource; // PLL 时钟源 + y& i) n& J W/ f* W# |* I
- uint32_t PLLM; // PLL 分频系数 M
7 }+ ^6 u9 q6 ?7 H5 [ - uint32_t PLLN; // PLL 倍频系数 N
# q d0 v8 i4 {2 i/ G - uint32_t PLLP; // PLL 分频系数 P * Z4 {/ }' D1 C; f C$ [
- uint32_t PLLQ; // PLL 分频系数 Q
5 t( i- t1 g8 z% Y( G - uint32_t PLLR; // PLL 分频系数 R 5 ?3 y! ~+ {6 ~6 O& y2 ?
- }' J4 L I7 q4 O, e8 M
- 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- 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- 位 15:14 VOS[1:0]
# j2 x3 W6 F$ g - 00: Cannot be written 5 y% m* ]/ W! z$ X- \
- 01:Range 1 $ |+ `0 z; u( @+ \, L
- 10:Range 2 ( `7 O4 A& f9 h5 R
- 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 v4 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- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \ , \/ U/ w& m5 z7 a6 B3 b7 R
- __IO uint32_t tmpreg; \ 3 U5 t' o, K6 R- O9 C
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
5 h% B$ h6 ]% D/ l - tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
/ |5 W1 ]6 O, Q - UNUSED(tmpreg); \
3 |+ y7 e' ~1 d4 u# b4 R - } 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- 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
- #define RCC_AHB2ENR_GPIOAEN_Pos (0U) , |" l" t- J7 K& S: S# S- t
- #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) 2 L1 X7 m8 v, M2 Z. a# y7 L& [9 k
- #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- Bit 0 GPIOAEN: IO port A clock enable // GPIOA 时钟使能 ' L8 o Y' d" T! G4 _7 {; Y
- Set and cleared by software. // 由软件置 1 和清零 ; y" t: X/ T& F3 e- M" h' C
- 0: IO port A clock disabled // 禁止 GPIOA 时钟
+ i; j: D* {+ E- E/ E& ]& y9 ?( t/ t - 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- __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
复制代码
+ S6 R# t/ ^8 Z8 ]: V9 k对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:- __HAL_RCC_DMA1_CLK_ENABLE(); // 使能 DMA1 时钟 # F- z1 n/ f7 V1 j, p3 f
- __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟 # W4 H, X4 }' o& u: _4 ?
- __HAL_RCC_TIM1_CLK_ENABLE(); // 使能 TIM1 时钟
复制代码 / A9 Q9 U6 g. _) w1 o( X* n
使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:- #define __HAL_RCC_GPIOA_CLK_DISABLE()
% r" C) I2 U' e Q4 A$ ]$ ` - 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 ~- __HAL_RCC_DMA1_CLK_DISABLE(); // 禁止 DMA1 时钟
& @% t/ K3 F* w8 Y% }% u - __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
, F7 W. L7 B( m. \+ N' H - __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
|