主要内容: c; M2 O; O2 F9 R T/ {
1)STM32L4 时钟树概述;1 N2 H" ~! `/ |- f: z9 _: R' L
2)STM32L4 时钟初始化配置;
; @* B5 Y* H1 K' l: r3)STM32L4 时钟使能和配置。: V' Z% ?) m/ j+ g( j
+ d3 K0 P5 V- f, v' _1 Q
一、STM32L4 时钟树概述, c6 C+ I9 i0 ?
时钟系统是 CPU 的脉搏。
0 w" g: ~, `2 ^/ K不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。
/ _$ W. Y& R& A) _1 e0 V同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。- \+ B; A- k0 r# o4 r
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
0 `( T% C" Q; M( p1 t3 {4 H0 j
0 t) Z9 o1 i/ k, b0 pSTM32L4 的时钟系统图如下:* l0 }7 N6 V! ^ _; @& G
6 g! A, C6 L# A
$ R2 ]7 `- j. T* [; G5 c! e$ S* k# l% A% j7 @0 \( Z! P) c( [
在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。
& V4 Q) a0 G3 @其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。. A6 ?- n6 M4 {* M
按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。
' l8 w; J! S; |1 ~/ m" N按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。
! M9 F) a- F2 p n按上图中红圈标示的顺序分别介绍这6个时钟源:
( ?6 x$ l5 e' Y. J) M9 K3 e9 w" ~4 E① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。
$ S. R2 K7 P# F) S* b6 O$ L② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。1 h' S* j% \9 u/ g8 h4 }
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
( l0 Z' t5 k) g. k, G④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。7 l+ }' U" ] t! `2 I% y0 e
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。: D% f6 `: Y/ S, ]) c
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
$ g) g- J1 D j% `( R9 N1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:+ }4 D% B0 T+ h, P$ z+ z. t* z
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);& k( E3 |3 B# r5 D' b
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
' n6 ? K; v0 k7 c. l2 k第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
. S. h7 @, O' U2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:4 n3 z; r, x4 }& s8 F
第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;+ \4 |, J( L) N# l' N: \2 B; d/ h
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;2 u9 k0 |+ p5 ?) q+ {5 Q) H. F
第三个输出 PLLSAI1R,可为 ADC 提供时钟。$ p# G8 ~( F' o" J5 Z) w3 @8 \
3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:
- o/ l1 l+ r! W第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;. D* m* s: ?1 S8 H C
第二个输出 PLLSAI2R,可为 ADC 提供时钟。4 x4 Z+ W3 E% T* W% `
8 _" E* A! n. X4 C- @# J6 l
这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:" v* u* s" n: Y. H; ~0 M/ Y
$ c* W& a9 `2 P
( @$ D l# j# K, b& H* U. Q ~
, f7 h9 h' H- p5 Y+ a& K上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。
5 B" r) P+ ~+ k, e* B9 @
' T: I/ k+ a% s7 u+ T举个栗子:# R; G# J8 \; p. F1 n7 D+ A. S7 ]
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:
9 J' x" T, Y5 g: S, n, ^5 OPLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz. { \1 U. O0 O3 G2 y- s
0 N' d z4 I) R' ?
如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。, `/ y# k: v2 m5 [& o
% H! R% c! b5 r( ?# a- s1 ?) F下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的
q) h5 N7 D, K2 h观察之前给出的时钟系统图中标出的9个字母(A~I):
}) g. O# A* h/ U- QA. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;: G! s7 ~- e. p: M5 R7 b
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
T2 k6 G5 B& f6 p0 h: sC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;
! o6 f: z1 i7 X. Q( O; T$ ^D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;, Q' ?4 M7 z- Z4 r r
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;0 @% ?2 V% R' b( b$ L1 A! y
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;, H" ^& p+ |9 R& A+ Z
G. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;! ~- G" a+ ~) @$ @% H6 ^
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
# m `% C0 Z/ g! L, }I. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。4 d1 Z$ j# h0 j8 J% g6 y
2 @9 y3 u e" \% Z$ }
备注
0 [2 ^3 E1 T# b" z4 N1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。# d! z, X3 J+ S' S6 a0 I
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。. t/ s* {3 ]: y" L6 ?
* I8 f# U, t- B* i: m% \
二、STM32L4 时钟系统初始化配置
1 H# R9 R$ j+ H1 ]& }; O在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:7 m* x$ [( t! v% c# {2 O
, X# ~$ K# }) @+ }& t, j, U/ r
- /*********************************************************************
# [' B; Z. R, U$ R% v' d+ H - 函数名称:void SystemInit(void)
" Q% ^+ d9 x+ c" c - 函数功能:1) FPU 设置
+ { `/ ^/ B( \" u }$ v: s - 2) 复位 RCC 时钟配置为默认复位值(默认开启 MSI)
4 ~8 I8 D% }3 i/ K - 3) 中断向量表地址配置
1 B) h1 y3 D, N3 X# a - 入口参数:无
" }$ l4 H- F7 k; W) ] - 返回参数:无
, \7 Q7 d1 }; v @9 k- _ - **********************************************************************/- w" G+ S: T& o/ Y7 J! R
- void SystemInit(void)
& L& T' O m: V+ C - {
G# V4 c* y4 F4 A - #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)) X/ t1 m* X6 X% q6 |3 T' L, v
- SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
8 {+ |# ^, h" [! v+ [ e - #endif
8 x( E5 w( e/ [4 R - /* 复位 RCC 时钟配置为默认配置-----------*/ % d. [# Q5 Z2 w9 @: y2 T, K
- RCC->CR |= RCC_CR_MSION; // 打开 MSION 位
. q# I, y& j" j6 R1 I - RCC->CFGR = 0x00000000U; // 复位 CFGR 寄存器 . F# x5 s; s! `
- RCC->CR &= 0xEAF6FFFFU; // 清除 HSEON,CSSON,HSION ,PLLON 位 ) g; p- S F4 \4 A& E% t7 I
- RCC->PLLCFGR = 0x00001000U; // 复位 PLLCFGR 寄存器
3 o6 F O( H1 j, m* ]' H - RCC->CR &= 0xFFFBFFFFU; // 复位 HSEBYP 位
. \, L& w; X/ c' S( O. C - RCC->CIER = 0x00000000U; // 关闭所有的中断
. R9 `' L- t! k6 |+ G - /* 配置中断向量表地址=基地址+偏移地址 ------------------*/ 9 n( M8 B, ]) }# Y. p, S! R2 n6 u
- #ifdef VECT_TAB_SRAM
/ e8 W7 v; A7 j Y4 H, e2 ^ - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */( g6 }# U- r1 s- z: M2 _
- #else' p/ L% P3 W: f! m0 G4 V) Y @& t
- SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */' \" E/ e5 Q7 n7 ~7 e4 U6 Y
- #endif
2 G( F% P9 ]- c - }+ @ R* i, D* r' [$ o
复制代码 * d/ }% O) v3 T6 g5 n2 o) b! V
HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:. V, E& d0 A% a& P9 b* p/ D
6 a# W" H' E6 ^; s* K& B- /*********************************************************************- f6 c; m3 t4 V
- 函数名称:void SystemClock_Config(void)
Y- L6 F. K& M& D5 {! f - 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
2 i. m2 E- ^& M( P( `" P - SYSCLK = 8M / 1 * 20 /2 = 80M3 e( O5 T+ O+ T0 r: f$ o4 r! }
- 入口参数:无8 w0 W' O) B. {. H Z# n- j; Q* ]
- 返回参数:无2 ]9 H( z- k4 }" ]8 C1 ]+ Y
- **********************************************************************/
( t; p4 L# X) W# O7 b u2 F) } - void SystemClock_Config(void)2 B V' k: @9 r2 i' q3 W, ~1 X8 q
- {
& W) E2 y) L* B$ I+ A% ` - HAL_StatusTypeDef ret = HAL_OK;
7 L( ]: S3 s- |$ I7 ^4 ^" U) I0 f. e - ( u7 v" E5 H# p4 H
- RCC_OscInitTypeDef RCC_OscInitStruct;
6 i) B. A4 Q- V: l( o - RCC_ClkInitTypeDef RCC_ClkInitStruct;
6 i* r) X$ w4 s* a) Y% K
; v$ Q5 ?' [; G9 o- __HAL_RCC_PWR_CLK_ENABLE(); // 第一步,使能PWR时钟
& T- Y8 E% }$ |# ^6 ?' K - /*Initializes the CPU, AHB and APB busses clocks*/ h4 W4 a$ b7 q4 d, l I4 b
- RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE " O7 a% v+ o: |* a K
- RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开 HSE
9 r/ h; E% P( C& E - RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开 PLL 9 S( x6 c7 Y, N" u; D
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 时钟源为 HSE ; k3 D. L: Y) c( L5 ~9 w/ ~
- RCC_OscInitStruct.PLL.PLLM = 1;1 ^. w7 h, j/ l' a3 A2 [# f
- RCC_OscInitStruct.PLL.PLLN = 20;: ^. n. `, }5 Q' N
- RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
4 z! a: q* v' W$ s }$ I - RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;2 w% I8 w L" h) O
- RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
5 N) |+ E9 ?1 b* V: t5 L" R" o3 p- c2 h - 2 O4 p! e" _, s4 V" g( S+ @
- ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); // 第二步,配置时钟源相关参数8 }+ g! z$ I% Q" M6 z/ q5 i
- /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */ 2 K- ?$ w/ S0 \) [
- ! B8 w+ k9 `+ S
- if(ret != HAL_OK) while(1);; O3 \. m3 G8 Y$ E5 u
; S q; i& E, s8 D M( }- /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ 4 y( q% j* o9 V
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK) B1 I9 g4 Q" m% _4 G) W$ T+ [5 o
- | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
" L( `% _1 ?* b+ d$ b+ b2 V - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 选择系统时钟源为 PLL+ s0 ^5 i3 f4 e: ~7 \
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 分频系数为 1( i) D: E3 C! J& M$ L" }2 _
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 分频系数为 1' R9 r* V9 Z- f" T
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频系数为 1
1 e k) l9 n- A2 Q - , q: b$ M* i% X/ G
- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数# }$ I- M+ t0 s' R- X% z5 E
- /* 第三步配置后,可以计算出:9 x: f( @( _( H R- P
- PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz;+ S: r. _6 N8 @7 |
- 选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
, o6 R8 ] t( ]+ ^, K; H - AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
0 ]8 t4 E+ A. M( _ - APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;
4 m; O/ B, Z1 O- S$ Q. b: n% t - APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
% t/ o; B |2 s8 k& y - i( H# n, w, P/ e; |
- if(ret != HAL_OK) while(1);8 I. a# O0 [" l' P
3 O( F: X2 {9 f4 f( J) M- /*配置主内部调压器输出电压级别*/) M5 G M( n1 I
- ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); // 第四步, 设置调压器输出电压级别
2 J4 M4 O* V) a3 a- ^
" ~ ~. B. ]3 b- if(ret != HAL_OK) while(1);
7 T' _# T. f* B' i; {4 J - }
复制代码 i# s/ I' ^4 j+ ~- r5 X. N8 _' k
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。- | l$ T- L! h6 V+ A3 U
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:+ D, x4 B) O& g( ~
1.使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;2 ]! f; l; j s
2.配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。6 {1 d- b. g+ P5 m
3.配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。, g! b* m. Q4 z! [* }8 _$ p
4.设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。
* G* l l6 n. @2 f* h1 m对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:0 M0 G( Q" n' s H8 P5 W' k
- HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
复制代码
' U+ A2 d0 C( K, N! S! q- |, oHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
- Y& e0 x" |$ i7 |
" x8 N) c% Y- F d5 M$ L- typedef struct
6 Z/ V% z- E9 N& y: k. l( M - { 2 b. a0 I$ k o+ x' L
- uint32_t OscillatorType; // 需要选择配置的振荡器类型 " {: l! k/ r: ]) Q! G5 |
- uint32_t HSEState; // HSE 状态 2 \' }/ ^$ c/ O, d8 r/ J
- uint32_t LSEState; // LSE 状态
# P$ t9 w* ~$ z7 q8 b" z/ f# D - uint32_t HSIState; // HSI 状态
5 ]7 @. j1 Z0 E& w+ s6 C' J - uint32_t HSICalibrationValue; // HSI 校准值 - Y4 }! K" Q9 o
- uint32_t LSIState; // LSI 状态
) k7 \" z. \9 k- W) [9 B - uint32_t MSIState // MSI 的状态
+ A) x$ z9 }5 R+ L( v2 F2 j7 K - uint32_t MSICalibrationValue; // MSI 校准值 % O. X" z3 N. ^) s/ H3 E; y) g
- uint32_t MSIClockRange; // MSI 时钟范围
9 ^! U* ?# h1 E* L) M$ \ - uint32_t HSI48State; // HSI48 状态
7 g A2 t! x; O4 X! I9 b - RCC_PLLInitTypeDef PLL; // PLL 配置
# ?7 B# v+ G+ b5 }' R3 S, n5 A( ] - }
1 K5 z( m8 C/ L. G0 i# b) j( M - RCC_OscInitTypeDef;
复制代码
) m$ _7 f: N0 R: T该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
- h/ M* ~4 S, n% @3 `1 q$ r! V/ {该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
+ B3 S; Z6 q1 q4 D! ?$ d1 m& O9 u' ^* p
- typedef struct ) q: w# t% s& l% c1 A6 X
- {
; F- r$ l9 r h: x - uint32_t PLLState; // PLL 状态
5 l; k8 s% }' }" x5 u7 C o" V - uint32_t PLLSource; // PLL 时钟源
1 y) T, o% z9 U" G z9 M# t8 K z* h - uint32_t PLLM; // PLL 分频系数 M
" S. S' J3 D) H# G9 N/ S - uint32_t PLLN; // PLL 倍频系数 N
" v: v% u8 ~7 k - uint32_t PLLP; // PLL 分频系数 P
+ ^ q# h" e( W$ o. a; J - uint32_t PLLQ; // PLL 分频系数 Q 2 v6 ^9 I/ k' s: b+ b- M
- uint32_t PLLR; // PLL 分频系数 R
! N2 B% V& G% V7 ? - }
. x7 G* v! v6 x, a! V; R7 y - RCC_PLLInitTypeDef;
复制代码 + y4 V& p9 @5 J5 q
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
; i+ a2 X6 I: Z" P
7 v- @! \+ c$ ~对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
; G6 i. f! g; R6 C2 M- f6 H, I
; v+ N0 ~5 u( l- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
复制代码 }5 E5 c' E! c- t: E) e' g
该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。
' ~' Z" G$ K p& C# V; P3 B' }; u( T N" h, Y5 X
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的- ^2 x8 A( ~: ~
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
* d4 ]& \( o' u' W1 Q" S+ ^首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:
2 i# A9 @( h; y6 g* n8 C% O
9 G, l2 o0 G! m; {( b; V& t- 位 15:14 VOS[1:0]
- [. a/ e, g2 B0 T: R$ | - 00: Cannot be written
| m6 q* c) H U$ N$ C3 U - 01:Range 1 * V$ |7 f+ Y5 ~2 t v2 Q# x, c
- 10:Range 2
! a" T; u- g2 X - 11: Cannot be written
复制代码 ' { z. t5 Z5 }) W m: n
级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
9 O" k5 i% {) A1 P配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:
% c) i. W5 J0 i/ k" Z! q* P
u+ [& c2 m. T+ @+ F4 w
' t2 t+ _; i$ G' @1 ?/ ]# ?$ i
z+ ]6 g, I4 e可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。
9 ?( B/ ~, L) R$ [) _8 d
( [. d" ?' H, ^: Y, }三、STM32L4 时钟使能和配置" ~% p) \$ C- r: S5 g' z/ B5 p# R
在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。
5 U% t! C" X- e- E" J
' S2 |0 y, y0 ~通过STM32L4的HAL库使能外设时钟的方法
7 A/ a! s4 S/ t8 O在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
8 l* F8 `8 h8 k5 J; X. Q
$ {3 A% }9 X, y3 z+ H! i举个栗子
7 B3 ]4 X2 N) Y" U; R! {看看 GPIOA 的外设时钟使能宏定义标识符:" e" t; L7 j* @0 l4 A
6 G p( d F9 [5 A- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
6 O% `: B4 p6 U2 h - __IO uint32_t tmpreg; \
" s6 `9 _: J" n* D( ?! {4 Z( k5 V - SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ & @5 c, N0 v( v( H* s5 k; a
- tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ # h1 d2 _1 h4 c& ]# u- ?
- UNUSED(tmpreg); \ 3 G) z: a8 y& M l, _& U
- } while(0)
复制代码
" I/ q }8 t! X1 u2 L# _5 O( R! i- Q这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:' _" O' |4 i `+ ^& _
+ k1 [4 [9 F; I( B5 j& q/ A- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码
0 d9 @! y% I' y; j9 G# D" U, I这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:2 I x) q9 e9 _5 t* |, |
2 B, ~* S- c& ~3 Z
- #define RCC_AHB2ENR_GPIOAEN_Pos (0U)
, v7 w3 y( i) }8 ]7 G/ A% d - #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos) ) J3 H% M3 H2 w* u
- #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
复制代码 : y3 h% H) \. q) q9 C, G
上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
$ y* N. T$ k l. d; m描述如下:
2 r& q* R" f$ j4 E
$ n5 e: _% [5 ]- Bit 0 GPIOAEN: IO port A clock enable // GPIOA 时钟使能
, p5 M; E# b2 k) a. \& ]4 |4 l - Set and cleared by software. // 由软件置 1 和清零
7 s) t+ d6 Q/ u) A' g, K! C' g5 ~ - 0: IO port A clock disabled // 禁止 GPIOA 时钟
" y; \# F( ?6 R8 ~ - 1: IO port A clock enabled // 使能 GPIOA 时钟
复制代码 3 S1 E5 j8 u$ k, I1 y
那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:& x' P4 v8 j- B a' R/ f q8 l5 x5 C
( L2 e8 U# O+ J7 a& }- __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
复制代码
: T" Z* K/ H# A. D, a对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:
' M7 O7 e5 D1 R& s8 N& K/ z! N7 C6 P
- __HAL_RCC_DMA1_CLK_ENABLE(); // 使能 DMA1 时钟 & F% K4 F6 F o8 }2 Z z
- __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
J" U- ]5 X$ I+ h$ u+ S - __HAL_RCC_TIM1_CLK_ENABLE(); // 使能 TIM1 时钟
复制代码
6 o r* [1 ^+ F1 S$ L% ]3 ^' @+ Z8 y使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:2 X' b" s& m- a& z; ], C4 E
5 p3 h7 J7 {7 I- |2 f# z1 C- #define __HAL_RCC_GPIOA_CLK_DISABLE()
3 F5 A1 `. c' n3 P5 I - CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码
: d* J, J8 E) B1 W* v% J+ E( M同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:8 h) ~ t1 j* ?; g5 B! s( y4 J0 y
5 K6 W: Y9 i; @; }* q: ?" H
- __HAL_RCC_DMA1_CLK_DISABLE(); // 禁止 DMA1 时钟
. }! f6 J Y' ^, [ - __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟
8 K6 [! G0 m/ w - __HAL_RCC_TIM1_CLK_DISABLE(); // 禁止 TIM1 时钟
复制代码
" |- [4 t( ]# U+ i4 k————————————————
/ h( @) B+ D1 ?8 c! O' s0 e; a* O版权声明:天亮继续睡% P- L$ X* g$ K% V7 v3 S) Y8 j1 C5 A
; X5 B6 R2 |3 P' s- p! o9 T
" N4 h) t# r5 m# s |