之前有用过STM32的F4系列单片机,该单片机的强大之处可以度娘一下,因为打算系统的学习下,所以自己做了一个板子用作学习,把资料都准备好,主要是官方的标准库及例程,学32第一件事情就是要搞清楚时钟,相对传统的51,32的时钟相对复杂很多,M3内核如此,M4内核更甚。下面贴出M4的时钟树:
p+ Y6 `, Y, o( |+ j% V1 @0 W) S& y- l
8 K) X& t5 J5 K/ x! ]4 I& j
* s5 n' K' d1 @) K; m: ]) i1 B6 _+ J! o
可以看到时钟树很复杂,我用的芯片是STM32F401系列的,官方给的数据是最高时钟能达到84M,我也有朋友直接超频到120M用的,不过在产品设计上还是要以稳定为主。上图的注释是我后来加的,这里主要记录一下我如何配置系统时钟及踩到坑。在最开始的时候使用官方的例程,发现时钟不对,查底层的时候发现官方测试的目标板使用的是25M的晶体,而我们常用的是8M,我这里也使用的8M晶体,所以我进行了如下修改:
8 G0 ~( {) F5 C( ~9 Q6 O5 R
, |0 V) S6 W+ V) v" L2 ?* o8 p
8 L# P7 y+ |0 h- ^# u& ^
6 C8 g, V* e# {6 I首先常用的系统源是PLL,而在M4内核上PLL时钟是经过内部16M或者外部晶振经过M除频、N倍频和P除频来的。这里我自己写了一个时钟函数,可以选择外部时钟和内部时钟。代码如下:
9 n* V& h; |0 t5 f& x
# w& [/ H8 g% e2 d2 k" K: h- #define HSE RCC_PLLSource_HSE- H6 t: K5 \, W0 p T9 X
- #define HSI RCC_PLLSource_HSI
9 `& D2 i3 ?; e1 u% y - 9 B$ X6 ^ i) v
- //RCC_PLLSource:PLL时钟源 RCC_PLLSource_HSE、RCC_PLLSource_HSI* N3 r& ^8 d O$ j9 Q5 q
- //PLLM:主 PLL和音频PLL输入时钟的分频系数 范围2-63
' x1 {" M% e; H0 q+ _2 _0 f - //PLLN:倍频系数 范围2-510
" [. F; H/ f7 F' R' j; c - // 小心: 软件必须正确设置这些位,确保 VCO 输出频率介于 192 和 432 MHz 之间。3 z; P8 b# c2 e6 K' C4 ?6 Z
- // VCO 输出频率 = VCO 输入频率 × PLLN 并且 192 <= PLLN <= 432
0 V- f/ L. q( y# J% r! Z - //PLLP:主系统时钟的主 PLL (PLL) 分频系数 范围2、4、6、86 {; i1 a/ T3 o( F
- //PLLQ:主 PLL (PLL) 分频系数,适用于 USB OTG FS 范围2-15
5 i# h% ^# a4 ^ - //使用时确保晶振频率PLLM分频为1MHz即可* x5 O+ X" j% J4 S+ z9 @6 H
- void SysClock_Configuration(uint32_t RCC_PLLSource, uint32_t PLLM)# c, L/ m# l$ \/ g1 U+ O1 M
- {# U) r; A( s, G3 X0 B1 } x
- __IO uint32_t HSEStatus = 0;
3 ]% Z p1 S, d) U" q* `3 ? G - 3 C9 m, A) U! M5 M S
- RCC_DeInit(); & P$ n. _8 s |8 K. j7 D: }
2 v. `- F9 }: ]" M9 a- if(RCC_PLLSource_HSE == RCC_PLLSource){ //选择外部时钟0 a5 j/ z/ f/ q# A6 {7 i# g) ]
- RCC_HSEConfig(RCC_HSE_ON); //打开外部时钟
1 T( G; W) H, i6 h; X - if(RCC_WaitForHSEStartUp() == SUCCESS){! p6 K& z$ Q9 ~& \/ y
- HSEStatus = 1;3 r. k8 G8 {# p- ~: i
- }5 z( e& u7 L8 i" [
- else{# N$ \; F$ G& [6 X6 _
- RCC_HSEConfig(RCC_HSE_OFF); //关闭外部时钟3 p% ]. W; W K f; n
- RCC_HSICmd(ENABLE);
/ Z, e$ \2 F- r+ C. {# ^ - }
: D+ E+ A: u$ y - }9 G+ F- p) H3 Q
- 4 v5 U$ @$ V4 C
- RCC_HCLKConfig(RCC_SYSCLK_Div1); //HCLK(AHB)时钟为系统时钟1分频 ( B9 b2 E& A- c+ J% Y
- RCC_PCLK1Config(RCC_HCLK_Div2); //PCLK(APB1)时钟为HCLK时钟2分频 U3 e+ U! }# N# G0 w
- RCC_PCLK2Config(RCC_HCLK_Div1); //PCLK(APB2)时钟为HCLK时钟1分频
4 ^: n% U& F, ? - 7 T* m7 C" t* z( {$ z; r( E
- if(HSEStatus == 1) {
9 |& I1 Q9 A. q9 V. a3 t - //PLL时钟配置,外部晶振为8MHz,系统配置为8/PLLM*PLLN/PLLP
/ G3 Z8 c' k, | - RCC_PLLConfig(RCC_PLLSource_HSE, PLLM, 336, 4, 7); 0 p% w: e1 J' w% C2 C2 C
- }
3 @; i1 d; C! b# }. j9 j6 Y - else{: \( s# R' h2 o& R3 O
- //PLL时钟配置,内部晶振为16MHz,系统配置为16/16*336/4 =84MHz usb=336/7=485 Y R7 b0 q7 I( N3 o' ]7 P
- RCC_PLLConfig(RCC_PLLSource_HSI, 16, 336, 4, 7); 4 l* V" ]+ k i; }
- }" _8 i. ~* k8 y3 j
-
S0 v T: A* l. G. |& D8 T - RCC_PLLCmd(ENABLE); //开启PLL时钟,并等待PLL时钟准备好0 i9 j) l9 |2 s0 W1 w- D
- while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
7 @$ m) L$ z5 O+ R7 Q' q2 c" {- | - RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择PLL时钟为系统时钟& Q! o4 b! ?8 i; M0 b
1 W( X! A; N5 ?5 f- while(RCC_GetSYSCLKSource() != 0x08); //Wait till PLL is used as system clock source9 f' l: [$ O* `# {) v7 p
- RCC_ClockSecuritySystemCmd(ENABLE); //打开时钟安全系统
' L5 s+ Z& N6 ]- s- s5 t, q9 M: c - }
复制代码 ) ^' R( U% I3 C3 G, Z1 D+ D
这个函数在主函数的入口处执行,注释的也很详细。传入参数有两个,第一个是选择内部时钟或者外部时钟,代码中有定义,第二个是M除频,在使用外部时钟的时候需要根据目标版的晶体频率选择,以保证经过晶体频率经过M除频后是1MHz的频率。如果是选用的内部时钟,则第二个参数无效,可以看到代码里面直接是16的除频而没有用到输入的参数作为M除频。这是因为内部时钟是固定的16MHz,所以就固定死了。亲测这个函数是有效的。但是我在使用的时候开始就出现了问题。在一些外设的配置中时钟就不对,这里拿串口作为例子。通过串口的配置底层可以看到一个函数:! x, F8 p g7 u
. N; P0 q* v+ M& ?
3 C% H( ~+ B# y0 F: K2 @! o; E5 v
2 @3 L G4 U u% r可以看到串口的波特率是系统根据时钟去自动计算的,所以在计算之前需要得到时钟频率,此时会调用上图的获取时钟函数。这个地方也是我采坑的地方。花了点时间终于找到问题,跳进去时钟获取函数去看发现了一个宏定义:
& j& {. d/ t0 x! M7 \
5 N( \ Z: E2 @, c* c
8 [: y) W+ U" Y* M$ c( |( G
# x" T! l6 J9 D( v' ~) s就是这个,一个是内部时钟一个是外部时钟,继续跟进会看到:1 o' c% T4 f# h
6 \ i5 r( A5 }& R
2 [+ k; Z( P) j e% T) I; m
4 z3 N$ I# l+ W1 E+ a它上面标的是25Mhz,这就是外设时钟不对的地方。其实时钟那条线都是完全ok的,只不过在配置这个串口的时候调用了这个错误的宏定义,然后就导致外设输出结果不对。找到问题就好办了,把25M改成自己的8M,问题解决,世界安静了。
r" e2 F' S. T% H. o4 J. O) w, J/ h U x
) J& q8 h4 X+ Q$ C0 \
|