主要内容' Y7 G- g& B$ P" u; H
1)STM32L4 时钟树概述;9 e) [5 B0 j/ W' ^8 \4 |- G
2)STM32L4 时钟初始化配置;2 c0 M- K% L1 H; y8 o" M1 u" x, c
3)STM32L4 时钟使能和配置。
# }: T! r1 T6 V4 I. a
7 N# g/ g7 j/ q一、STM32L4 时钟树概述' A, W7 e7 s( {
时钟系统是 CPU 的脉搏。2 J. g }+ P, H. m: X
不同于51单片机一个系统时钟解决一切问题,STM32 有多个时钟源。这是因为STM32本身的外设非常多,但并不是所有外设都需要系统时钟这么高的频率。比如看门狗以及 RTC 只需几十 k 的时钟即可。# b$ k/ Y ^7 ]* l
同一电路,时钟越快功耗越大,同时抗电磁干扰能力也会越弱。$ w+ L; }; n/ |1 E6 i
所以对于较为复杂的 MCU 一般采取多时钟源方法解决这些问题。
4 Y W# f' U6 X9 x$ n4 B; p; y5 _
# z9 Y6 T. A4 g. QSTM32L4 的时钟系统图如下:& y! A" q. K+ U0 Z/ {6 F9 b
; ]9 o& V% V2 }
3 j) T! o+ U* w# p5 w
) C# [& K% S0 v在 STM32L475 中,有 6 个重要的时钟源,为 HSI、HSE、LSI、LSE、MSI、PLL。! l9 s0 k8 l3 J2 ]& ]! d0 H6 e
其中PLL 实际为三个时钟源,分别为主 PLL 和、PLLISAI1 和 PLLSAI2。
7 g9 j9 b$ B n按照时钟频率,可分为高速时钟源和低速时钟源,其中 HSI,HSE,MSI 及 PLL 是高速时钟,LSI 和 LSE 是低速时钟。; H: [! |/ I' W2 i" W
按照来源,可分为外部时钟源和内部时钟源,外部时钟源是从外部通过接晶振的方式获取时钟源,HSE 和 LSE 是外部时钟源,其他是内部时钟源。; h- x7 g$ G3 N6 b- L# C$ `8 E7 j
按上图中红圈标示的顺序分别介绍这6个时钟源:3 g- r( W9 J# N' h8 ?3 k9 Y
① LSI 是低速内部时钟,RC 振荡器,频率为 32kHz 左右。供独立看门狗、RTC 和 LCD使用。- n. U+ |2 w$ t! v, `
② LSE 是低速外部时钟,接频率为 32.768kHz 的石英晶体。这个主要是 RTC 的时钟源。: G, e1 f0 x: F# R
③ HSE 是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz-48MHz。本开发板接的是 8MHz 的晶振。HSE 也可以直接做为系统时钟或者 PLL 输入。
* q/ W X% z t9 l6 N④ HSI 是高速内部时钟,RC 振荡器,频率为 16MHz。可以直接作为系统时钟或者用作PLL 输入。 {* [. c* j! N: h/ O; {
⑤ MSI 时钟信号由内部 RC 振荡器产生。其频率范围可通过时钟控制寄存器(RCC_CR)中的 MSIRANGE[3:0]位进行调整。) O# P* u& w: T) z2 F5 L
⑥ PLL 为锁相环倍频输出。STM32L4 有三个 PLL:
! h# y. Y6 V+ X* x1 l1) 主 PLL(PLL)可由 HSE、HSI 或者 MSI 提供时钟信号,并具有三个不同的输出时钟:. J' H1 e7 H8 g# p
第一个输出 PLLR,用于生成高速的系统时钟(SYSTEM,最高 80MHz);! A3 c. b1 d: `* Q9 Q2 n" @
第二个输出 PLLQ,可为 USB、RNG 和 SDMMC 提供时钟源 ;
. F$ g. J: ~5 l1 u+ p3 } f1 ^第三个输出 PLLP,可用于 SAI1 和 SAI2 时钟。
, w; F3 R0 _: J$ ]2) PLLSAI1 用于生成精确时钟,同样具有三个不同的输出时钟:
; m* j7 K" r* W0 t) R `' k5 o第一个输出 PLLSAI1P,可用于 SAI1 和 SAI2 时钟;+ D+ g& Z' y" N
第二个输出 PLLSAI1Q,可为 USB、RNG 和 SDMMC 提供时钟源;
$ K5 f/ ^: r6 D ]7 k0 ~8 R8 I* K第三个输出 PLLSAI1R,可为 ADC 提供时钟。
# [- Y$ V5 X0 Y+ \3) PLLSAI2 用于生成精确时钟,具有两个不同的输出时钟:. W1 @$ }# {* r& ~
第一个输出 PLLSAI2P,可用于 SAI1 和 SAI2 时钟;
8 q- u4 b9 {! i第二个输出 PLLSAI2R,可为 ADC 提供时钟。, C$ T% c; d7 o& n' L4 ~
" v/ u& [6 p5 \
这里重点分析 PLL 时钟第一个高速时钟输出 PLLR 的计算方法。先把图局部放大,如下图所示:, Y3 D+ f! w2 ~
) s0 h' f1 j! S: T8 n- Q0 P* H
0 ^" `8 V; x, m
! v- h- `+ R% P+ k+ ^
上图中,主 PLL 的时钟源要先经过一个分频系数为 M 的分频器,然后经过倍频系数为 N 的倍频器,出来之后还需要经过分频系数为 R(输出 PLLR 时钟)、或者 P(输出 PLLP时钟)、或者 Q(输出 PLLQ 时钟),最后才生成最终的主 PLL 时钟。1 p' L' k! V5 g* v. E/ `
) |" G. t+ x3 E5 ~/ g: t# K
举个栗子:3 Y0 A% {' Y6 T9 j' Z
外部的晶振选择为 8MHz,同时设置分频器 M=1,倍频器倍频系数N=20,分频器分频系数 R=2,那么主 PLL 生成的PLLR 为:5 B- Y$ ?, n2 E$ S7 D9 ]
PLL=8MHz*N/(M*R)=8MHz*20/(1*2)=80MHz- n. `* e* ~9 V5 E V
! C8 |: A' g( u5 g& r8 _如果选择HSE为PLL时钟源,同时SYSCLK时钟源为PLL,那么SYSCLK时钟为80MHz。
6 b0 x3 Z; m+ S3 P1 u" {: _/ R" Q
( b- B. G* S; U0 @- O: s下面,介绍这 6 个时钟源是怎么给各个外设以及系统提供时钟的% T. P% }/ z$ c, R
观察之前给出的时钟系统图中标出的9个字母(A~I):9 ^. ~4 W" c. b
A. 这是看门狗时钟。从图中可以看出看门狗时钟源只能是低速的 LSI 时钟;) q5 a; {- o% k) {" N+ `+ r S
B. 这是 RTC 与 LCD 时钟源,可以选择 LSI、LSE 以及 HSE 分频后的时钟,HSE 分频系数可设为 2~31;
, W! [! Z! v/ B- @$ @- BC. 这是 STM32L475 输出时钟 MCO。MCO 是向芯片 PA8 引脚输出时钟。它有七个时钟来源分别为:LSE、LSI、HSE、SYSCLK、MSI、HSI 和 PLL 时钟;; D: K }% t. e e
D. 这是系统时钟。SYSCLK 系统时钟来源有四个方面:HSI,HSE、MSI 和 PLL;% Q0 r% g& i6 ~
E. 这是 PWR 时钟、AHB 时钟、APB1 时钟和 APB2 时钟。这些时钟都是来源于SYSCLK系统时钟。其中AHB、APB1和APB2时钟都是经过SYSCLK时钟分频得来,且这三个时钟最大频率为 80MHz;6 N9 D `& u5 h/ P8 F1 P3 M
F. 这是 48MHz 时钟,主要用于 USB、RNG、SDMMC 时钟。这里的时钟源来自三个方面:MSI、PLLQ 和 PLLSAI1Q;
9 O8 G7 Q) p0 t5 VG. 这是 ADC 的时钟,这里的时钟源来自三个方面:SYSCLK、PLLSAI1R 和 PLLSAI2R;, ?% ^. d- s- E
H. 这里是 SAI1 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI1P、PLLSAI2P 和SAI1_EXTCLK;
; G3 W5 j& ?2 gI. 这里是 SAI2 的时钟,这里的时钟源来自四个方面:PLLP、PLLSAI2P、PLLSAI2P 和SAI2_EXTCLK。+ ]) v, Z" x1 u2 a3 R0 i# e8 y
! w: r6 z" F V备注
" j/ l3 l2 u/ i# A1)Cortex系统定时器Systick的时钟源可以是AHB时钟HCLK或HCLK的 8 分频。具体配置参考 Systick 定时器配置。 Q4 `; z, Y& Q7 P& j
2)在以上的时钟输出中,有很多是带使能控制的,例如 AHB 总线时钟、内核时钟、各种 APB1 外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。! R' z+ v8 q U) }: ?( R
. }" n* u: f7 ?; o+ Q& s# }$ e, ?
二、STM32L4 时钟系统初始化配置" b% Q7 C" M4 U/ F
在系统启动之后,程序会先执行 HAL 库定义的 SystemInit 函数(该函数位于system_stm32l4xx.c源文件里),进行一些初始化配置:4 F1 r3 w& E0 O- X
, I. a+ Z. t. s5 d6 y7 x. d4 K- /*********************************************************************
& p8 y, j: o* E1 Z O" p - 函数名称:void SystemInit(void)
: _& P* k" y. p0 B* t - 函数功能:1) FPU 设置 5 V6 F( p f2 \7 v8 [
- 2) 复位 RCC 时钟配置为默认复位值(默认开启 MSI)
0 p K% V3 {: c7 @1 d7 o - 3) 中断向量表地址配置 $ d! X; s6 n* I, @8 R; @
- 入口参数:无
: [4 V9 H" p, t& S0 N+ u - 返回参数:无
# I4 P% i# b! J$ l9 b& z - **********************************************************************/
7 V: }8 C& |0 @2 p) V - void SystemInit(void)2 `. ?# i. i. Q/ X1 k8 k9 L( q9 T
- {
4 w& O' l2 P+ a+ b7 j) S - #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
7 G, L r9 B" R) D. \$ P- X - SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2)); // 如果需要 FPU 的话就使能 FPU,设置 CP10 和 CP11 为全访问
5 T0 ?% ]# J' U, d) S" Q - #endif% M* x% e2 B0 \5 g
- /* 复位 RCC 时钟配置为默认配置-----------*/
) ?$ \: ~1 k/ y6 }7 T - RCC->CR |= RCC_CR_MSION; // 打开 MSION 位
6 D x. h: O4 T' I5 p' I- L - RCC->CFGR = 0x00000000U; // 复位 CFGR 寄存器
* S2 B2 I$ K7 z q4 Z: N - RCC->CR &= 0xEAF6FFFFU; // 清除 HSEON,CSSON,HSION ,PLLON 位
- N6 Z& G* ?5 V) Z - RCC->PLLCFGR = 0x00001000U; // 复位 PLLCFGR 寄存器' E8 u h1 G+ P4 x+ ?& ?4 I
- RCC->CR &= 0xFFFBFFFFU; // 复位 HSEBYP 位
) k/ `) B7 {, S& u o( \5 L# b - RCC->CIER = 0x00000000U; // 关闭所有的中断 5 t+ h: }% ]; N3 |/ [
- /* 配置中断向量表地址=基地址+偏移地址 ------------------*/
% X8 b. q7 K! c6 ~' _! P! W7 k - #ifdef VECT_TAB_SRAM
; ~* c, N4 F4 T( {) o - SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
+ k' m0 x* @ C* u: V - #else: a) L+ E# q, P+ Q9 ?$ N
- SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
1 \9 Q. z# y3 B$ h# C4 W - #endif
) x8 Q+ P9 ]' c1 v1 M6 m0 Q - }
6 g- s) Y" k# E3 N2 h1 c1 e ?7 c
复制代码
5 p, z: S+ \" g& \3 ?HAL 库的 SystemInit 函数除了打开 MSI 之外,没有任何时钟相关配置,所以必须编写自己的时钟配置函数。这里,看下正点原子在工程SYSTEM分组下提供的sys.c文件中的时钟初始化函数 SystemClock_Config 的内容:
4 Y8 q, L2 y: r2 x, ~6 n1 r2 K- B$ g6 l; R/ z. a# x
- /*********************************************************************
" l- [5 {$ W9 F) A6 ? - 函数名称:void SystemClock_Config(void)
3 T7 _9 O$ M& k - 函数功能:SYSCLK = HSE / PLLM * PLLN / PLLR
5 B3 ~8 N( ?: Q1 E2 D1 _' ] - SYSCLK = 8M / 1 * 20 /2 = 80M; E9 V1 v0 g5 h
- 入口参数:无2 k' A9 N) P+ F. N- d/ `! ^
- 返回参数:无8 b5 |! g- ^$ j: k
- **********************************************************************/' s; x) _( d+ q4 P% W, J( R) K
- void SystemClock_Config(void)5 f& }* Y3 O4 |
- {( t8 o7 L- w# |/ B, Z; ?) y$ U
- HAL_StatusTypeDef ret = HAL_OK;* k, n5 Q. z: b+ t2 c7 F
* L5 \' _5 E8 o2 s; K \+ i& m- ~- RCC_OscInitTypeDef RCC_OscInitStruct; ~& d( z8 d- x( I6 x5 A
- RCC_ClkInitTypeDef RCC_ClkInitStruct;5 C6 M5 p; Z7 W5 ]# _
D! L* o: K P" s& [3 B/ ~- __HAL_RCC_PWR_CLK_ENABLE(); // 第一步,使能PWR时钟
+ w9 z& e7 e! t+ k - /*Initializes the CPU, AHB and APB busses clocks*/
% t( Y: C6 c6 F - RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;// 时钟源选择为 HSE # k9 w1 k2 s+ e5 i9 B6 w' i
- RCC_OscInitStruct.HSEState = RCC_HSE_ON; // 打开 HSE - {7 k" @8 C7 Z1 s
- RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 打开 PLL " D; B8 s; E. x2 k
- RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; // PLL 时钟源为 HSE
+ K3 x+ q* ]8 p. q! C9 } - RCC_OscInitStruct.PLL.PLLM = 1;
& [2 s+ K h% L2 F - RCC_OscInitStruct.PLL.PLLN = 20;
t* L/ @8 F" D4 o- J7 I - RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
, J( ^& a, O2 ?# j$ q/ o - RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;: O) V+ J7 n/ p6 r
- RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
9 z& @- h9 X% q0 C - 1 x6 T$ ]/ k6 j4 T/ h
- ret = HAL_RCC_OscConfig(&RCC_OscInitStruct); // 第二步,配置时钟源相关参数
/ d5 P, x; _ Q" g' x - /* 第二步主要功能,开启了HSE时钟源,同时选择PLL时钟源为HSE,同时也配置了PLL的参数 M,N,M,P和Q的值,这样就达到了设置PLL时钟源相关参数的目的。设置好PLL时钟源参数,也就是确定了PLL的时钟频率 */
8 t) O6 p3 C( Z- ?8 d% g* _
7 J5 ~/ w9 w$ f- if(ret != HAL_OK) while(1);
$ q7 `; ]' l3 ~+ K7 ? - 3 J. C$ J( r5 M8 C/ K% k2 ?
- /*选中 PLL 作为系统时钟源并且配置 AHB、APB1、APB2*/ 4 o# p% g2 H9 E- O. k/ f2 @
- RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
0 V% ^- n4 j2 C6 d9 O; I% H5 \ - | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; // 配置SYSCLK,HCLK、PCLK2、PCLK1四个时钟。
$ B& ? M% \- R6 h4 E& ?! J - RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; // 选择系统时钟源为 PLL3 S- O) x; J6 y! p+ M/ {" V
- RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; // AHB 分频系数为 18 d+ l, b5 e# s8 p% n
- RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1; // APB1 分频系数为 13 }% L8 j P% x0 G2 E* c! h/ a
- RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; // APB2 分频系数为 1
( I# z! o% v, s7 U& R- i7 F
8 M2 g0 A, {/ W- ret = HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4); // 第三步, 配置系统时钟源以及SYSCLK、AHB,APB1和APB2的分频系数$ J. H* }) N% R0 _; D- G; F
- /* 第三步配置后,可以计算出:4 h% `* M. y d1 w1 y) q
- PLL时钟为 PLLCLK=HSE*N/(M*R)=8MHz*20/(1*2)=80MHz; A' K. e/ s8 h, B4 l( Y
- 选择系统时钟源为 PLL,所以系统时钟SYSCLK=80MHz;
) w4 ]$ h1 m5 c+ ^$ R& l; D - AHB 分频系数为 1,故其频率为 HCLK=SYSCLK/1=80MHz;
" q# O1 N6 y! |) O, V6 v5 {, C - APB1 分频系数为 1,故其频率为PCLK1=HCLK/1=80MHz;' ]) z: u% ]4 |# z! ]
- APB2 分频系数为 1,故其频率为 PCLK2=HCLK/1=80MHz。*/
$ g4 }9 {) v, U4 [+ h6 L -
0 W- z: l8 S4 l, l2 v - if(ret != HAL_OK) while(1);0 v, ]! q# z3 t- P2 k! ~
& [4 d! G3 j1 P4 y- /*配置主内部调压器输出电压级别*/
8 \ z; f" ?2 }" ]) h3 D w5 o - ret = HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1); // 第四步, 设置调压器输出电压级别
8 y) W$ g( l- U/ ^* @. q; L* d - / W: T8 l" ?- r B- x# k) H
- if(ret != HAL_OK) while(1);
8 x! z* ]6 K E! T& _ - }
复制代码 / z" S0 E. u0 y
函数 SystemClock_Config 的作用是进行时钟系统配置,除了配置 PLL 相关参数确定SYSCLK值之外,还配置了AHB,APB1和APB2的分频系数,也就是确定了HCLK,PCLK1 和 PCLK2 时钟值。3 V7 B8 H3 @) N1 P" k2 E. q4 a0 L
使用 HAL 库配置 STM32L4 时钟系统的一般步骤:: |; i: {" a: p
1.使能 PWR 时钟:调用函数__HAL_RCC_PWR_CLK_ENABLE() ,因为后面要设置调压器输出电压级别是电源控制相关配置;
: F, t2 K1 q9 _/ E: n) B# o2.配置时钟源相关参数:调用函数 HAL_RCC_OscConfig()。
% B0 Q! _+ Z8 S9 N R, d/ h3.配置系统时钟源以及 SYSCLK 、 AHB,APB1 和 APB2 的分频系数:调用函数HAL_RCC_ClockConfig()。; c+ S$ c9 I/ K* F) _
4.设置调压器输出电压级别:调用函数 HAL_PWREx_ControlVoltageScaling ()。6 Q2 D2 w/ `* Y. w" f
对于步骤2,使用HAL库来配置时钟源相关参数,调用了HAL_RCC_OscConfig()函数,该函数在HAL库头文件stm32l4xx_hal_rcc.h中声明如下:
+ a( t+ ?+ g2 P, O3 I- HAL_StatusTypeDef HAL_RCC_OscConfig(RCC_OscInitTypeDef *RCC_OscInitStruct)
复制代码
( H$ @8 |6 @' H0 LHAL_RCC_OscConfig()函数只有一个入口参数,即RCC_OscInitTypeDef类型指针,定义如下:
) L' m# e& Y' m& H+ Y& J9 j5 X
7 Z& D4 b S0 e& {; x3 ?4 O- typedef struct , A4 ?0 r c+ b. f* d
- {
' F5 U% C9 `+ E+ H8 j0 N, @ - uint32_t OscillatorType; // 需要选择配置的振荡器类型
+ T t) h- J$ w; G- b - uint32_t HSEState; // HSE 状态
# j+ p# F' c# }$ e4 c - uint32_t LSEState; // LSE 状态
~9 {5 m( D9 |/ d4 w5 I - uint32_t HSIState; // HSI 状态 1 h+ l; M2 Z- `% m
- uint32_t HSICalibrationValue; // HSI 校准值 ) o2 k- N4 Z1 V- \
- uint32_t LSIState; // LSI 状态 " X9 e, G0 C& {' |$ ^& i" y
- uint32_t MSIState // MSI 的状态 * f8 M$ V0 s7 @3 g0 n8 Z& t
- uint32_t MSICalibrationValue; // MSI 校准值 5 j0 b. s7 o& M J4 ~
- uint32_t MSIClockRange; // MSI 时钟范围
$ L* r( X% r3 L' C - uint32_t HSI48State; // HSI48 状态
8 H. y+ i* M" G- A3 B: x# l8 @4 U$ ` - RCC_PLLInitTypeDef PLL; // PLL 配置 : d g1 O7 D2 l8 p1 d* E/ ]' w
- }/ \" L+ _8 q; B8 m) C
- RCC_OscInitTypeDef;
复制代码
( a B0 M, l# n1 {该结构体前几个参数用来选择配置的振荡器类型,如要开启 HSE,那么设置 OscillatorType 的值为 RCC_OSCILLATORTYPE_HSE,然后设置 HSEState 的值为 RCC_HSE_ON 开启 HSE。对于其他时钟源 HSI,LSI 和 LSE,配置方法类似。
& r2 \) v: C2 m( g( U# I; g该结构体还有一个很重要的成员变量是 PLL,它是结构体 RCC_PLLInitTypeDef 类型。它的作用是配置 PLL相关参数,定义如下:
' S1 l: a k! Y8 o% {6 m( _8 W6 t) `8 ?" i. }% L+ M% A
- typedef struct
( d+ n. w) w8 I/ n - { / }! _& q1 m, o0 M
- uint32_t PLLState; // PLL 状态 , ~/ y% B1 P' g. |- T2 z
- uint32_t PLLSource; // PLL 时钟源
) j) f1 T7 }: r$ m6 }0 ^ - uint32_t PLLM; // PLL 分频系数 M . d' {8 ~% x3 y- d
- uint32_t PLLN; // PLL 倍频系数 N / r% V1 _7 z, A, C2 t2 z/ ]
- uint32_t PLLP; // PLL 分频系数 P
4 v; g s. w+ V( W3 i5 q# M - uint32_t PLLQ; // PLL 分频系数 Q
# G% X- t/ S" z. l5 e2 x1 ~ - uint32_t PLLR; // PLL 分频系数 R 5 g( l' a+ X; h, c
- }. I: m7 Z5 ^: ~" O
- RCC_PLLInitTypeDef;
复制代码 9 S6 }4 U L' {9 \$ l. d
该结构体主要用来设置 PLL 时钟源以及相关分频倍频参数。
/ ^2 o6 l) ^6 y- J! c( v7 v7 _) L5 a: H& J- ]& E4 f+ j
对于步骤3,HAL_RCC_ClockConfig()函数,声明如下:
" G1 G$ P; B# Y7 J! D1 H9 G1 @3 L) b% v1 a8 U! g
- HAL_StatusTypeDef HAL_RCC_ClockConfig(RCC_ClkInitTypeDef *RCC_ClkInitStruct, uint32_t FLatency);
复制代码
( J, g- A) }; M: h- t该函数有两个入口参数,第一个入口参数 RCC_ClkInitStruct 是结构体 RCC_ClkInitTypeDef指针类型,用来设置 SYSCLK 时钟源以及 SYSCLK、AHB,APB1 和 APB2 的分频系数。第二个入口参数 FLatency 用来设置 FLASH 延迟。; V. S5 ^5 Q: Z
/ o2 X5 ~5 u. l& |" B5 i4 S( x" v
对于步骤3中函数 HAL_RCC_ClockConfig 第二个入口参数 FLatency的含义和步骤四,只需要知道调压器输出电压级别 VOS 和FLASH的2 H: l* _; H- t( H+ E
延迟Latency 两个参数,在我们芯片电源电压和 HCLK 固定之后,他们两个参数也是固定的。
, v/ o) Y+ c, x% F* C首先我们来看看调压器输出电压级别 VOS,它是由 PWR 控制寄存器 CR1 的位 10:9 来确定的:0 d- h9 n; a- P- h# ~/ N
( z7 o+ w, h0 \: V
- 位 15:14 VOS[1:0]
+ u, {0 y' Z* m - 00: Cannot be written 9 U! `0 R {( D
- 01:Range 1
6 p n) n: m0 q+ D: w& t. R - 10:Range 2 % `. Q( [/ y% T: n# J% m+ D9 u( q
- 11: Cannot be written
复制代码
: ?: z. Y* z4 _6 u4 k- x级别数值越小工作频率越高,所以如果要配置 L4 的主频为 80MHz,那么必须配置调压器输出电压级别 VOS 为级别 1。
. L! {0 M3 f" }配置好调压器输出电压级别 VOS 之后,如果需要 L4 主频要达到 80MHz,还需要配置FLASH 延迟 Latency。对于 STM32L4 系列,FLASH 延迟配置参数值是通过下表来确定的:9 s* {4 v l' m3 I7 `
& y% U+ G! E& n. x- U
9 T, y* ]* Y' |% x7 E: D, R0 [ }2 b' f
可以看出,在 Vcore Range 1 时如果 HCLK 为 80Mhz,那么等待周期要 4WS 也就是5个CPU周期。在SystemClock_Config()函数中,设置值为 FLASH_LATENCY_4,也就是4WS,5个CPU 周期。- G% x" C+ \* t/ P, C- E( M
2 t2 i2 ]0 M4 j3 ~+ N/ t三、STM32L4 时钟使能和配置
! E L! m3 b5 o5 t在配置好时钟系统之后,如果要使用某些外设,例如 GPIO,ADC 等,还要使能这些外设时钟。注意,如果在使用外设之前没有使能外设时钟,这个外设是不可能正常运行的。STM32 的外设时钟使能是在 RCC 相关寄存器中配置的。9 r0 z. _8 n$ x8 F
0 A1 W+ T: [$ X2 ~. T0 Z7 S; ~, m通过STM32L4的HAL库使能外设时钟的方法
$ j* @* S& J' T% ~/ _4 o5 S在 STM32L4 的 HAL 库中,外设时钟使能操作都是在 RCC 相关固件库文件头文件stm32l4xx_hal_rcc.h 定义的。设时钟使能在 HAL 库中都是通过宏定义标识符来实现的。
/ l1 ^, L9 X, h
% H7 t1 U9 l3 b3 t, f( `; \举个栗子' n0 f' b; w9 D' R
看看 GPIOA 的外设时钟使能宏定义标识符:9 x' u! X* [2 \& ~# U2 g7 O
$ i1 I t' }; U% I+ p( P! V
- #define __HAL_RCC_GPIOA_CLK_ENABLE() do { \ + S, P/ ^* G$ ?) E* q/ `: @
- __IO uint32_t tmpreg; \ 5 z6 z: @/ o% V& N
- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \
N4 y+ j' z$ X& X3 Y - tmpreg = READ_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN); \ 3 P; ]2 f. B* o/ m0 {1 h, T: ~4 B# g" Z
- UNUSED(tmpreg); \
, C/ L! A9 K" y0 Q - } while(0)
复制代码 . n9 {7 N- H$ k2 P
这几行代码主要定义了一个宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE(),其核心操作是通过下面这行代码实现的:
2 t% _+ h& ~* N4 d& \; E# v, x# r
1 O5 |5 L: V: S2 l# G4 R- SET_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN);
复制代码 - ~8 F5 v: K$ z" t
这行代码的作用是,设置寄存器 RCC->AHB2ENR 的相关位为 1,至于是哪个位,是由宏定义标识符 RCC_AHB2ENR_GPIOAEN 的值决定的,而它的值为:
4 Q, p& T. s$ q4 `7 ?6 k
- [' ?* b/ z" Y* k% [9 @- #define RCC_AHB2ENR_GPIOAEN_Pos (0U) # ?, ?+ S1 l% V- F" n% @' K. y
- #define RCC_AHB2ENR_GPIOAEN_Msk (0x1UL << RCC_AHB2ENR_GPIOAEN_Pos)
) {6 b9 M, O1 O" T6 r4 T - #define RCC_AHB2ENR_GPIOAEN RCC_AHB2ENR_GPIOAEN_Msk
复制代码
( T! W7 l- A8 g上面三行代码很容易计算出来 RCC_AHB2ENR_GPIOAEN= 0x00000001,因此上面代码的作用是设置寄存器 RCC->AHB2ENR 寄存器的最低位为 1。从 STM32L4 的参考手册中搜索 AHB2ENR 寄存器定义,最低位的作用是用来使用 GPIOA 时钟。AHB2ENR 寄存器的位 0
- o4 V$ V: `6 l4 y描述如下:
5 @4 J- n; j0 R% p- P/ Y' E3 |9 m8 H1 y6 p
- Bit 0 GPIOAEN: IO port A clock enable // GPIOA 时钟使能
: P# Y- z' @* D - Set and cleared by software. // 由软件置 1 和清零
4 u K# e/ I; V - 0: IO port A clock disabled // 禁止 GPIOA 时钟
# r( A$ s' ^" G9 v. n - 1: IO port A clock enabled // 使能 GPIOA 时钟
复制代码
2 P7 J4 I: I& F- R0 j, X9 t那么只要在用户程序中调用宏定义标识符__HAL_RCC_GPIOA_CLK_ENABLE()就可以实现 GPIOA 时钟使能。使用方法为:
1 A; x- U! `9 s1 H% a5 B3 Z5 ~8 M8 s
- __HAL_RCC_GPIOA_CLK_ENABLE(); // 使能 GPIOA 时钟
复制代码
% V I( L' P2 V( D6 _& q; P对于其他外设,同样都是在 stm32l4xx_hal_rcc.h 头文件中定义,只需要找到相关宏定义标识符即可,这里列出几个常用使能外设时钟的宏定义标识符使用方法:/ k: e7 N/ @$ A- q
* O2 c2 @, U4 D! |- __HAL_RCC_DMA1_CLK_ENABLE(); // 使能 DMA1 时钟
2 v. m+ y+ N2 y- n. P - __HAL_RCC_USART2_CLK_ENABLE(); // 使能串口 2 时钟
9 Y% A" \9 c7 F+ q. T1 } - __HAL_RCC_TIM1_CLK_ENABLE(); // 使能 TIM1 时钟
复制代码
3 r. `. D w- ^: E3 e使用外设的时候需要使能外设时钟,如果不需要使用某个外设,同样可以禁止某个外设时钟。禁止外设时钟使用方法和使能外设时钟非常类似,同样是头文件中定义的宏定义标识符。以 GPIOA 为例,宏定义标识符为:
2 K7 Q9 t+ U2 B+ e& n$ E+ B: c" X0 r# q
- #define __HAL_RCC_GPIOA_CLK_DISABLE()
/ M" l7 s1 w2 r2 o - CLEAR_BIT(RCC->AHB2ENR, RCC_AHB2ENR_GPIOAEN)
复制代码 4 G- T9 M; \- w) ]9 w4 A6 p
同样,宏定义标识符__HAL_RCC_GPIOA_CLK_DISABLE() 的作用是设置RCC->AHB2ENR 寄存器的最低位为0,也就是禁止GPIOA时钟。这里同样列出几个常用的禁止外设时钟的宏定义标识符使用方法:
* w4 ^( t: A$ d7 A' H. F' l$ b( c" b6 b5 k3 J! h) R& Y
- __HAL_RCC_DMA1_CLK_DISABLE(); // 禁止 DMA1 时钟 + ` p9 B0 B" H E% ~3 K
- __HAL_RCC_USART2_CLK_DISABLE(); // 禁止串口 2 时钟 + R1 N+ x" J) h) U
- __HAL_RCC_TIM1_CLK_DISABLE(); // 禁止 TIM1 时钟
复制代码 0 r: k; R0 C; K3 {
————————————————
$ L6 |6 |$ |' P版权声明:天亮继续睡/ u! `8 z! r$ D6 [9 b4 m
% e7 r+ A' L+ W) D% B1 A
5 I! K. q D9 Y# v8 O |