之前有用过STM32的F4系列单片机,该单片机的强大之处可以度娘一下,因为打算系统的学习下,所以自己做了一个板子用作学习,把资料都准备好,主要是官方的标准库及例程,学32第一件事情就是要搞清楚时钟,相对传统的51,32的时钟相对复杂很多,M3内核如此,M4内核更甚。下面贴出M4的时钟树:
: { ]! H0 ?5 f4 ] L- R3 B
/ J* W& N4 O& e; a0 U
& _5 g7 {1 `& T3 b" D
( u( {* g) Y3 Q3 g& Y- O可以看到时钟树很复杂,我用的芯片是STM32F401系列的,官方给的数据是最高时钟能达到84M,我也有朋友直接超频到120M用的,不过在产品设计上还是要以稳定为主。上图的注释是我后来加的,这里主要记录一下我如何配置系统时钟及踩到坑。在最开始的时候使用官方的例程,发现时钟不对,查底层的时候发现官方测试的目标板使用的是25M的晶体,而我们常用的是8M,我这里也使用的8M晶体,所以我进行了如下修改:
2 H. o5 t( N% ?* p3 c) E o9 s9 u4 v7 B) I3 }( s
, T3 j4 q2 L( e: i0 R8 _
2 s) g' q9 O2 Z5 O2 @首先常用的系统源是PLL,而在M4内核上PLL时钟是经过内部16M或者外部晶振经过M除频、N倍频和P除频来的。这里我自己写了一个时钟函数,可以选择外部时钟和内部时钟。代码如下:5 \: @2 K: J5 E! O7 k& P
: r. p4 y9 D% T. G" A4 y) Q
- #define HSE RCC_PLLSource_HSE% F- V! W1 [% I" @; u
- #define HSI RCC_PLLSource_HSI
5 g( Z5 t O+ W
5 Y8 [6 Z. t3 R: _4 ^- G- K8 E- //RCC_PLLSource:PLL时钟源 RCC_PLLSource_HSE、RCC_PLLSource_HSI
+ _+ [* ~ K3 l& U0 s - //PLLM:主 PLL和音频PLL输入时钟的分频系数 范围2-63
* |# K/ @' c+ ?* ^) k1 @ - //PLLN:倍频系数 范围2-510* R' g, W. G K1 v
- // 小心: 软件必须正确设置这些位,确保 VCO 输出频率介于 192 和 432 MHz 之间。
, i% e' ~& ?2 D; ? - // VCO 输出频率 = VCO 输入频率 × PLLN 并且 192 <= PLLN <= 432
6 g; Z( V; E$ u$ p6 `! l - //PLLP:主系统时钟的主 PLL (PLL) 分频系数 范围2、4、6、8; Y6 L! ~5 i; |9 L
- //PLLQ:主 PLL (PLL) 分频系数,适用于 USB OTG FS 范围2-157 d* `7 n- u' M) [
- //使用时确保晶振频率PLLM分频为1MHz即可
% D' `1 Y, P6 g, D - void SysClock_Configuration(uint32_t RCC_PLLSource, uint32_t PLLM)
$ h/ F& p/ @) N' l - {
7 l' M$ D& N8 {2 T" s - __IO uint32_t HSEStatus = 0;% a S( N1 C! G0 ]6 y1 @* I( Y) K
- ( `' g0 c& @9 b% ?5 s0 c
- RCC_DeInit(); 8 g! y$ s& S, A, i3 l# |
- 6 V0 N, A: k6 I1 u. C( C% e6 d
- if(RCC_PLLSource_HSE == RCC_PLLSource){ //选择外部时钟& u# Z% b# N, \9 k6 Z; r/ H
- RCC_HSEConfig(RCC_HSE_ON); //打开外部时钟 h7 }: p# p8 P% d6 p1 A8 P6 B
- if(RCC_WaitForHSEStartUp() == SUCCESS){
4 L; \& |2 _3 n/ V* `6 L5 n - HSEStatus = 1;4 ?# K. G, M: j! f+ D1 u( m4 J
- }
4 J. {2 V, v1 j9 E& f; A - else{: }9 Y& I5 h, k3 A8 U* \
- RCC_HSEConfig(RCC_HSE_OFF); //关闭外部时钟+ s" a! f) d' l5 Q- w
- RCC_HSICmd(ENABLE); ) ?6 b$ [8 Q( Q# Q
- }
. ~. {: q9 z' F - }! @6 {5 ^4 [9 S0 n h* i
-
; t1 j/ K( p5 h% s8 p7 n+ b - RCC_HCLKConfig(RCC_SYSCLK_Div1); //HCLK(AHB)时钟为系统时钟1分频
% @6 q# {# d% h - RCC_PCLK1Config(RCC_HCLK_Div2); //PCLK(APB1)时钟为HCLK时钟2分频
, {4 Q$ J. t: u# ^7 e- o3 r8 _8 X4 L - RCC_PCLK2Config(RCC_HCLK_Div1); //PCLK(APB2)时钟为HCLK时钟1分频 6 J8 l# s- Y& p' J- ^; x
4 r0 Z) \8 g& |. j! |) K- if(HSEStatus == 1) {
6 j2 N8 a t% U1 j# \9 ]' j - //PLL时钟配置,外部晶振为8MHz,系统配置为8/PLLM*PLLN/PLLP N T: ~8 j) O/ E0 u: }
- RCC_PLLConfig(RCC_PLLSource_HSE, PLLM, 336, 4, 7); - ]8 q' h" D1 P! @- J
- }
- x% `1 b7 k+ s9 D - else{6 D+ r" [# U+ K$ c3 z1 \( c
- //PLL时钟配置,内部晶振为16MHz,系统配置为16/16*336/4 =84MHz usb=336/7=48
. ~. S7 d1 Z9 R/ l) S& V; ?$ M4 i2 l3 N - RCC_PLLConfig(RCC_PLLSource_HSI, 16, 336, 4, 7);
0 F+ }1 T2 o6 K q6 c9 l* s; G - }
: L! A7 X/ U8 z0 y- z4 s8 { - , o2 E5 y# y& G- D8 N% u! N
- RCC_PLLCmd(ENABLE); //开启PLL时钟,并等待PLL时钟准备好; J& G6 b+ u' d9 ]2 |: R4 r: }; c6 e
- while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
; K5 C ~* p6 t' G6 k; k - RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择PLL时钟为系统时钟
% T7 |3 c$ T. @8 r
7 V4 o* i% W' R2 F/ m/ Z/ I6 M- ^6 j- while(RCC_GetSYSCLKSource() != 0x08); //Wait till PLL is used as system clock source" @( J: H0 r: k; O" g/ }
- RCC_ClockSecuritySystemCmd(ENABLE); //打开时钟安全系统8 ?. d* n h1 B% c4 j* ]; t1 p
- }
复制代码 4 l6 k1 Q& i2 x: r }+ }) p! U Z: _
这个函数在主函数的入口处执行,注释的也很详细。传入参数有两个,第一个是选择内部时钟或者外部时钟,代码中有定义,第二个是M除频,在使用外部时钟的时候需要根据目标版的晶体频率选择,以保证经过晶体频率经过M除频后是1MHz的频率。如果是选用的内部时钟,则第二个参数无效,可以看到代码里面直接是16的除频而没有用到输入的参数作为M除频。这是因为内部时钟是固定的16MHz,所以就固定死了。亲测这个函数是有效的。但是我在使用的时候开始就出现了问题。在一些外设的配置中时钟就不对,这里拿串口作为例子。通过串口的配置底层可以看到一个函数:
; }) q) t( @+ Y4 `4 y
1 R. o+ t. h a" o% w7 Z+ ]
) V+ f; ^1 P' O# P0 A- e, j0 [1 D! F2 h3 T. a5 u
可以看到串口的波特率是系统根据时钟去自动计算的,所以在计算之前需要得到时钟频率,此时会调用上图的获取时钟函数。这个地方也是我采坑的地方。花了点时间终于找到问题,跳进去时钟获取函数去看发现了一个宏定义:+ ? Y6 b$ R1 [1 M* K% A* \# }& I
* h8 ]) A" h, T" G
( g9 ~: M' i/ T P
# G* e3 d# y! {, i% b) M
就是这个,一个是内部时钟一个是外部时钟,继续跟进会看到:
% u4 N3 {. N2 p) N
) @& b* s+ a0 P- V( D$ N2 v
' p4 d' | h2 [ [
9 J2 w! m' d- ], I9 X' J它上面标的是25Mhz,这就是外设时钟不对的地方。其实时钟那条线都是完全ok的,只不过在配置这个串口的时候调用了这个错误的宏定义,然后就导致外设输出结果不对。找到问题就好办了,把25M改成自己的8M,问题解决,世界安静了。0 P* P, j- T: K, ~* ^% ^# H
( Q' e! j6 @7 \, I* ?: h9 y( u' c' h( X( P
|