之前有用过STM32的F4系列单片机,该单片机的强大之处可以度娘一下,因为打算系统的学习下,所以自己做了一个板子用作学习,把资料都准备好,主要是官方的标准库及例程,学32第一件事情就是要搞清楚时钟,相对传统的51,32的时钟相对复杂很多,M3内核如此,M4内核更甚。下面贴出M4的时钟树:) k7 b2 t! J9 t* R. R; a
1 M* t5 q. p8 o3 R' a/ k
- Z1 S- d6 i3 X( X6 ]) }$ @0 L, H( o9 A9 G
可以看到时钟树很复杂,我用的芯片是STM32F401系列的,官方给的数据是最高时钟能达到84M,我也有朋友直接超频到120M用的,不过在产品设计上还是要以稳定为主。上图的注释是我后来加的,这里主要记录一下我如何配置系统时钟及踩到坑。在最开始的时候使用官方的例程,发现时钟不对,查底层的时候发现官方测试的目标板使用的是25M的晶体,而我们常用的是8M,我这里也使用的8M晶体,所以我进行了如下修改:
( G w$ P4 u, p! @1 ~
- F ~- @& Y, W; R( y" U. f
" s5 F4 r7 t! s0 J
; c# D' o8 f) [9 S: @8 D
首先常用的系统源是PLL,而在M4内核上PLL时钟是经过内部16M或者外部晶振经过M除频、N倍频和P除频来的。这里我自己写了一个时钟函数,可以选择外部时钟和内部时钟。代码如下:& v! R3 [4 ]) p5 l' o
- k$ U3 d5 S4 d+ x O$ }% t- #define HSE RCC_PLLSource_HSE
+ C/ i& j. _5 ]4 \) r - #define HSI RCC_PLLSource_HSI; S5 m' |% u5 l% ]* R9 A
- " M1 e+ J2 u$ F
- //RCC_PLLSource:PLL时钟源 RCC_PLLSource_HSE、RCC_PLLSource_HSI) [7 k* ~" Z5 i, G8 Q( f0 Y9 p" y o
- //PLLM:主 PLL和音频PLL输入时钟的分频系数 范围2-63
) }3 h5 x/ s' P# h1 ^ U: W - //PLLN:倍频系数 范围2-5107 l+ W, _& S% @# u
- // 小心: 软件必须正确设置这些位,确保 VCO 输出频率介于 192 和 432 MHz 之间。
- j7 A( }: J" ]% ?" {1 ^' F# K - // VCO 输出频率 = VCO 输入频率 × PLLN 并且 192 <= PLLN <= 432! J. v& t, e T! p
- //PLLP:主系统时钟的主 PLL (PLL) 分频系数 范围2、4、6、8& N* k, |3 H1 `- ~+ m
- //PLLQ:主 PLL (PLL) 分频系数,适用于 USB OTG FS 范围2-152 Y9 n/ u, V! M5 w+ h
- //使用时确保晶振频率PLLM分频为1MHz即可) u- z' X* T8 J) C
- void SysClock_Configuration(uint32_t RCC_PLLSource, uint32_t PLLM)% C4 m3 @5 @0 {- Z- l
- {0 A7 R6 d; k3 f
- __IO uint32_t HSEStatus = 0;
/ O$ y, n5 h: s0 A -
9 Z5 j3 I4 @$ R3 `$ D' Z - RCC_DeInit(); . V k+ I7 _5 N; a' C
+ s. D8 p8 j k$ M: J$ a- if(RCC_PLLSource_HSE == RCC_PLLSource){ //选择外部时钟3 f0 M$ M% \: N5 l- U3 m. {
- RCC_HSEConfig(RCC_HSE_ON); //打开外部时钟
$ F0 Z" S4 V- |. d. @/ H: Y - if(RCC_WaitForHSEStartUp() == SUCCESS){: _5 B4 m. g8 i
- HSEStatus = 1;
/ L" l$ E; K2 G( O - }9 @5 h$ Z$ t2 p7 T8 t
- else{( E6 X a0 E& S
- RCC_HSEConfig(RCC_HSE_OFF); //关闭外部时钟
( c* K% O8 r, P$ z - RCC_HSICmd(ENABLE); 0 g, a+ Z5 F5 U" |
- }
& ^7 F q/ } {# T - }# y: N4 V g% ~( J4 t. l$ |, S
-
0 N4 o0 a9 O5 S% I) R/ W8 X! k4 ] - RCC_HCLKConfig(RCC_SYSCLK_Div1); //HCLK(AHB)时钟为系统时钟1分频 ) {. Z, Y0 j9 |5 b
- RCC_PCLK1Config(RCC_HCLK_Div2); //PCLK(APB1)时钟为HCLK时钟2分频
' Z4 D- x0 G; L1 _* A# _: K - RCC_PCLK2Config(RCC_HCLK_Div1); //PCLK(APB2)时钟为HCLK时钟1分频 6 _5 \# U7 Y2 r; D0 u. }1 `3 S3 R% p
1 M0 B; E$ K! X) Q$ }- if(HSEStatus == 1) {
# b, D; M4 S. \: `/ _6 A - //PLL时钟配置,外部晶振为8MHz,系统配置为8/PLLM*PLLN/PLLP* `( q5 g2 G8 J* F
- RCC_PLLConfig(RCC_PLLSource_HSE, PLLM, 336, 4, 7); }& q) ~) l K! F" U5 Z! j4 P" I
- }* A O2 [4 k! O7 W( K0 P
- else{# c/ D' Z6 d! Q2 h9 l
- //PLL时钟配置,内部晶振为16MHz,系统配置为16/16*336/4 =84MHz usb=336/7=48
4 y, N' x' a. s3 N - RCC_PLLConfig(RCC_PLLSource_HSI, 16, 336, 4, 7);
) T2 f8 ^: U' B8 z( ` - }
m/ c9 C% H" b7 L - ! Q& `; o0 Z7 ?4 b) P! o/ U/ W
- RCC_PLLCmd(ENABLE); //开启PLL时钟,并等待PLL时钟准备好: K* r8 I/ ~ A9 J, V7 u& j
- while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
- N( |9 u4 O, ?9 T% @ n - RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK); //选择PLL时钟为系统时钟6 K0 N) M/ @2 P5 A
# {2 E) c' B1 A, W- while(RCC_GetSYSCLKSource() != 0x08); //Wait till PLL is used as system clock source
* @( B2 }# O2 l" ^% ^ - RCC_ClockSecuritySystemCmd(ENABLE); //打开时钟安全系统
3 L4 C2 l* P8 l' f$ X - }
复制代码 ! c: d, p3 z$ } Y- g' t' Y
这个函数在主函数的入口处执行,注释的也很详细。传入参数有两个,第一个是选择内部时钟或者外部时钟,代码中有定义,第二个是M除频,在使用外部时钟的时候需要根据目标版的晶体频率选择,以保证经过晶体频率经过M除频后是1MHz的频率。如果是选用的内部时钟,则第二个参数无效,可以看到代码里面直接是16的除频而没有用到输入的参数作为M除频。这是因为内部时钟是固定的16MHz,所以就固定死了。亲测这个函数是有效的。但是我在使用的时候开始就出现了问题。在一些外设的配置中时钟就不对,这里拿串口作为例子。通过串口的配置底层可以看到一个函数:
* D8 s% X; b& W* W+ p8 {" C0 s1 i6 t& @5 L3 s
8 y& c$ A( z( g) |& |0 ?; Q
, L% |$ `2 ^6 r: y
可以看到串口的波特率是系统根据时钟去自动计算的,所以在计算之前需要得到时钟频率,此时会调用上图的获取时钟函数。这个地方也是我采坑的地方。花了点时间终于找到问题,跳进去时钟获取函数去看发现了一个宏定义:) \$ s* L: h, u' M3 P- J- B. O. U
7 W8 E" [1 d+ J* v8 m7 I
: l5 n6 H/ m; A- M0 h( O |3 n0 t8 S7 v: O- ]1 P8 h) r& e
就是这个,一个是内部时钟一个是外部时钟,继续跟进会看到:1 i" _4 @/ `- T6 c( D
' ~) t- C4 j* F1 y6 w r
1 ~8 C- L& n5 T& M) D
% a; l$ K( H, a: f9 I k& r
它上面标的是25Mhz,这就是外设时钟不对的地方。其实时钟那条线都是完全ok的,只不过在配置这个串口的时候调用了这个错误的宏定义,然后就导致外设输出结果不对。找到问题就好办了,把25M改成自己的8M,问题解决,世界安静了。
6 i! O! v6 U* x* N3 ]
5 o/ m; c. K; J
6 I) T1 d3 I/ r2 X1 x1 l. ^ |