|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
STM32的时钟树; `8 U5 m9 u( J, H6 K' N1 @& K
对于广大初次接触STM32的读者朋友(甚至是初次接触ARM器件的读者朋友)来说,在熟悉了开发环境的使用之后,往往“栽倒”在同一个问题上。这问题有个关键字叫:时钟树。
# o# ^ B( {/ x" o3 s5 y( I 众所周知,微控制器(处理器)的运行必须要依赖周期性的时钟脉冲来驱动——往往由一个外部晶体振荡器提供时钟输入为始,最终转换为多个外部设备的周期性运 作为末,这种时钟“能量”扩散流动的路径,犹如大树的养分通过主干流向各个分支,因此常称之为“时钟树”。在一些传统的低端8位单片机诸如 51,AVR,PIC等单片机,其也具备自身的一个时钟树系统,但其中的绝大部分是不受用户控制的,亦即在单片机上电后,时钟树就固定在某种不可更改的状 态(假设单片机处于正常工作的状态)。比如51单片机使用典型的12MHz晶振作为时钟源,则外设如IO口、定时器、串口等设备的驱动时钟速率便已经是固 定的,用户无法将此时钟速率更改,除非更换晶振。
# R) \+ d! G/ B' f7 [1 i; ?# J 而STM32微控制器的时钟树则是可配置的,其时钟输入源与最终达到外设处的时钟速率不再有固定的关系,本文将来详细解析STM32微控制器的时钟树。图1是STM32微控制器的时钟树,表1是图中各个标号所表示的部件。6 R h: u$ a. F4 q9 j& V5 a7 O$ x* z8 q3 e
标号 图1标号释义
" D7 F4 Q- @% g, B! w7 b1 内部低速振荡器(LSI,40Khz)
/ O$ T! b/ p! c# k2 外部低速振荡器(LSE,32.768Khz)
! a V& _! a0 w; y& N+ C) |3 外部高速振荡器(HSE,3-25MHz)1 e" q0 y& u7 R# W; ^3 F
4 内部高速振荡器(HIS,8MHz)2 B- _0 e0 Q# k# H. _' }
5 PLL输入选择位
& K) G8 Q6 i$ K& I& @6 y; J6 RTC时钟选择位
1 D1 g. b, w; `7 PLL1分频数寄存器
) Y, {2 v( c/ P# V0 ?8 PLL1倍频寄存器
( k P" f: q: D* z2 i/ a- g, v9 系统时钟选择位
( y! D" i- t8 J- ?$ ?/ H) l$ [10 USB分频寄存器
9 Q& w) ^9 d/ Q" [9 r9 X11 AHB分频寄存器7 R) w/ n" ]4 _0 y3 ?" W
12 APB1分频寄存器
! ~9 n; ]% J$ [. r+ ~: g, c% `13 AHB总线) K# G& Q: o; j: g7 W6 c- G
14 APB1外设总线
8 R7 H1 l* r9 O, \9 J3 e15 APB2分频寄存器4 Z) g" I9 L% I7 K; @
16 APB2外设总线
' y+ W5 t" o" K# ^7 |17 ADC预分频寄存器
9 i5 ?. I( { U* T18 ADC外设3 n7 W2 M5 o0 k
19 PLL2分频数寄存器
5 s6 E$ v7 A/ Q) P# ?; [7 W20 PLL2倍频寄存器
2 `3 O1 K0 v, y- p% v21 PLL时钟源选择寄存器
( s" S( u: h" ?! e22 独立看门狗设备
+ _' b2 ]: ]. Y: p1 O& ]% L23 RTC设备
$ z$ @4 h) P; e4 zSTM32时钟树.jpg
g% U2 h1 M$ d% S6 o( q) m& p4 L* B图1 STM32的时钟树) K5 Y: T" o$ ^8 T8 A/ M# @. Z
在认识这颗时钟树之前,首先要明确“主干”和最终的“分支”。假设使用外部8MHz晶振作为STM32的时钟输入源(这也是最常见的一种做法),则这个 8MHz便是“主干”,而“分支”很显然是最终的外部设备比如通用输入输出设备(GPIO)。这样可以轻易找出第一条时钟的“脉络”:
1 a6 l, J$ Y! Y5 @$ b3——5——7——21——8——9——11——13
5 q; O$ B( ^, ~( v) m! H6 F对此条时钟路径做如下解析:
9 T3 M( H' g) A对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;
; S" P9 ^$ h/ k$ R$ q9 ]对于5,通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);
. S% ]& w+ G `* s0 p# J8 r( ]3 M对于7,设置外部晶振的分频数(假设1分频);
$ ]8 D5 N b/ W对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);
, d% {" \ n" {7 Q& g! g对于8,设置PLL倍频数(假设9倍频);
# h6 O- ?6 O/ @对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);* E+ T* O6 R! j. [ E! y& b
对于11,设置AHB总线分频数(假设1分频);
% u, c3 g7 \' i9 Z2 J* C对于13,时钟到达AHB总线;
* r x- o' ~5 c7 r& P5 g4 X在上一章节中所介绍的GPIO外设属于APB2设备,即GPIO的时钟来源于APB2总线,同样在图1中也可以寻获GPIO外设的时钟轨迹:' I4 _6 N4 F5 M! `9 Q- P. ~6 L9 e
3——5——7——21——8——9——11——15——16
8 J3 {% }6 W Y5 |" {* t, i对于3,首先是外部的3-25MHz(前文已假设为8MHz)输入;
# Q8 p2 J( Y2 |$ _1 f对于5, 通过PLL选择位预先选择后续PLL分支的输入时钟(假设选择外部晶振);
! f+ m* j4 s# E对于7,设置外部晶振的分频数(假设1分频);) I; W$ S7 a6 L; w7 C
对于21,选择PLL倍频的时钟源(假设选择经过分频后的外部晶振时钟);
5 T9 X0 `. C* s8 L3 o1 `+ \, J对于8,设置PLL倍频数(假设9倍频);
5 [1 d) f) C2 n2 L9 {对于9,选择系统时钟源(假设选择经过PLL倍频所输出的时钟);/ C) T1 n% A5 _5 Z9 K* n+ Y. b
对于11,设置AHB总线分频数(假设1分频);2 C( u4 R6 o* b+ k
对于15,设置APB2总线分频数(假设1分频);3 ]% [0 c3 T3 u z- q
对于16,时钟到达APB2总线;: S! q! N2 g3 Q& T7 Y; E: y
现在来计算一下GPIO设备的最大驱动时钟速率(各个条件已在上述要点中假设):
) [% t* b* F- }1 w' _3 k1) 由3所知晶振输入为8MHz,由5——21知PLL的时钟源为经过分频后的外部晶振时钟,并且此分频数为1分频,因此首先得出PLL的时钟源为:8MHz / 1 = 8MHz。
, [& T8 i" W1 F p* d/ @2) 由8、9知PLL倍频数为9,且将PLL倍频后的时钟输出选择为系统时钟,则得出系统时钟为 8MHz * 9 = 72MHz。, J' J/ u) R! O- F7 t* ]
3) 时钟到达AHB预分频器,由11知时钟经过AHB预分频器之后的速率仍为72MHz。
# ~# L1 ?" \! Y4) 时钟到达APB2预分频器,由15经过APB2预分频器后速率仍为72MHz。
, j1 _5 [8 v' W: x; |5) 时钟到达APB2总线外设。4 [" F% R8 z( {+ l6 K% `" N3 r4 r* i8 O
因此STM32的APB2总线外设,所能达到的最大速率为72MHz。依据以上方法读者可以搜寻出APB1总线外设时钟、RTC外设时钟、独立看门狗等外设时钟的来龙去脉。接下来从程序的角度分析时钟树的设置,程序清单如下:
. r- r/ n J/ y; ivoid RCC_Configuration(void) K5 [- Y1 T3 |+ {; J; G
{
8 f d5 X ]- p. D ErrorStatus HSEStartUpStatus; (1)
( Q; s9 m- B% }* ~9 C* v RCC_DeInit(); (2)
3 r" A2 E; y, O# H* V" I: r5 Q! G RCC_HSEConfig(RCC_HSE_ON); (3)
. |+ Z+ D+ N$ w! `0 v1 s" F HSEStartUpStatus = RCC_WaitForHSEStartUp(); (4)
. W6 ?; T! P+ k7 G$ T if(HSEStartUpStatus == SUCCESS) (5)* E- [; }* ?5 a9 S) g
{- i9 F8 Z# ?: M$ C6 a' ^
RCC_HCLKConfig(RCC_SYSCLK_Div1); (6)
& ~& V! k! N. ]! N& Z! ` RCC_PCLK2Config(RCC_HCLK_Div1); (7)
, g2 l+ B, k* Q. {$ q& \ RCC_PCLK1Config(RCC_HCLK_Div2); (8)
/ {6 ^1 J6 x% Q0 \3 X* | FLASH_SetLatency(FLASH_Latency_2); (9)
) A3 a% R% J3 u3 }! i2 y FLASH_PrefetchBufferCmd(FLASH_PrefetchBuffer_Enable); (10): h2 U' W3 `0 r( g% W# E* \- c9 o- Y
RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9); (11)6 g. C9 |% w4 ?7 e3 r
RCC_PLLCmd(ENABLE); (12)' q' U8 x) `7 [* N1 I
while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET); (13); V/ }& Q! x; l: K
RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); (14)
/ R Q/ M& d r8 F' F; | while(RCC_GetSYSCLKSource() != 0x08); (15)5 x$ e4 B6 d. `2 r w$ o. b1 l
}
" o" I2 t8 v* U# T |
|