前言 最近开发项目,对MCU主频要求比较精确,尝试了两种配置主频的方法,掌握这两种方法也就熟悉STM32系列主频的配置方法了。分别是,使用外部晶振作为时钟源;内部RC时钟作为时钟源。介绍两种时钟源的区别: ; \/ m: U c4 u- e
- HSI内部8MHz的RC振荡器的误差在1%左右,内部RC振荡器的精度通常比用HSE(外部晶振)要差上十倍以上。
- 内部RC频率受温度影响比较大,如果省电Sleep模式下内部RC会停止工作。4 \1 y) X7 ~% L) U
5 G! s) b* d2 Y9 Q' ^( W6 b 9 I. H3 R: d9 U5 m8 v5 z$ K
' k+ ^: ]1 m; w K, {- q1 . 时钟系统
7 X( K! p; e* ^. i 在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 : u4 F# D$ z3 _4 G9 M
- HSI是高速内部时钟,RC振荡器,频率为8MHz。
- HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
- LSI是低速内部时钟,RC振荡器,频率为40kHz。
- LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
- PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。( d: \& H% E- f' V6 u0 M$ Q
0 ?8 r+ n: k7 u; i% N6 w3 G) j
1 O! N0 ]! d0 z9 ]
; M7 d* u. v+ r. w! O# o 用户可通过多个预分频器配置AHB总线、高速APB2总线和低速APB1总线的频率。AHB和APB2域的最大频率是72MHZ。APB1域的最大允许频率是36MHZ。SDIO接口的时钟频率固定为HCLK/2。
! m! L' E* S& H4 k* y, Q 40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。
: y, y+ `* P/ }0 \$ \3 Y2 a STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。( ~/ I: a4 `$ e6 }8 u
另外,STM32还可以选择一个PLL输出的2分频、HSI、HSE、或者系统时钟SYSCLK输出到MCO脚(PA8)上。系统时钟SYSCLK,是供STM32中绝大部分部件工作的时钟源,它可选择为PLL输出、HSI或者HSE,(一般程序中采用PLL倍频到72Mhz)在选择时钟源前注意要判断目标时钟源是否已经稳定振荡。Max=72MHz,它分为2路,1路送给I2S2、I2S3使用的I2S2CLK,I2S3CLK;另外1路通过AHB分频器分频(1/2/4/8/16/64/128/256/512)分频后送给以下8大模块使用:
5 N; n) m9 z) ?- i" L8 P
- 送给SDIO使用的SDIOCLK时钟。
- 送给FSMC使用的FSMCCLK时钟。
- 送给AHB总线、内核、内存和DMA使用的HCLK时钟。
- 通过8分频后送给Cortex的系统定时器时钟(SysTick)。
- 直接送给Cortex的空闲运行时钟FCLK。
- 送给APB1分频器。APB1分频器可选择1、2、4、8、16分频,其输出一路供APB1外设使用(PCLK1,最大频率36MHz),另一路送给定时器(Timer2-7)2、3、4倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器2、3、4、5、6、7使用。
- 送给APB2分频器。APB2分频器可选择1、2、4、8、16分频,其输出一路供APB2外设使用(PCLK2,最大频率72MHz),另一路送给定时器(Timer1、Timer8)1、2倍频器使用。该倍频器可选择1或者2倍频,时钟输出供定时器1和定时器8使用。另外,APB2分频器还有一路输出供ADC分频器使用,分频后得到ADCCLK时钟送给ADC模块使用。ADC分频器可选择为2、4、6、8分频。
- 2分频后送给SDIO AHB接口使用(HCLK/2)。
( Z+ `' e3 s4 N5 \! l7 e E
; J* r% _% Y( C& k+ f3 x. E ; T3 Y5 U6 @* h; n! I2 {
2 . 外部晶振作为时钟源
* U4 H* E, e O2 y* u4 F0 E接下来,解决使用12M外部晶振时,如何配置作为系统时钟源。
5 J5 t1 o6 X, o* M4 X3 u! ~- {3 r第一步,修改stm32f10x.h中的HSE_VALUE为12000000
: h4 R: ~2 F) p h, h. W. O$ }0 {, E
- /**
4 C$ x4 a0 l2 K w9 C - * @brief In the following line adjust the value of External High Speed oscillator (HSE)% V$ \4 @+ R! A9 J6 }! c, f9 H4 M
- used in your application
8 m7 o8 U b \ -
1 i1 |( n% t2 ^# L& g6 R. O! D) ? - Tip: To avoid modifying this file each time you need to use different HSE, you
! Q8 A! |, @. a4 j - can define the HSE value in your toolchain compiler preprocessor.
, ^1 Y5 T- O ~9 R$ d - */ 8 G. c' g( I9 A% H% d
- #if !defined HSE_VALUE
& W* J4 {. {* _/ Z) P& H$ S - #ifdef STM32F10X_CL 0 d' m: G4 L/ R( O/ b9 M
- #define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */
0 ^& p# J5 G+ s" l" W5 H: b! D5 u( g- J - #else
, _( G6 h2 r0 K" u: E8 C) m6 y$ g# [ - #define HSE_VALUE ((uint32_t)12000000) /*!< Value of the External oscillator in Hz */5 _8 C; l, W @* i( c
- #endif /* STM32F10X_CL */
k8 I# g0 o$ r# D - #endif /* HSE_VALUE */
复制代码
- ~, ]) d- v/ Q" j第二步,修改system_stm32f10x.c中的时钟配置,先找到void SystemInit(void)—》SetSysClock()—》SetSysClockTo72(),将9倍频改为6倍频,12*6=72MHz ) ~: C- U4 v/ S8 H+ w
- /**
4 M- |5 T/ f2 |4 z _# G+ F6 Z: { - * @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2
" i2 g/ l) H# U8 A( A d1 V6 X) x - * and PCLK1 prescalers.
* w$ \- ]9 @4 `6 b$ k/ h0 }; }0 ?: I - * @note This function should be used only after reset.* f# D# |* v: H+ Z5 ~
- * @param None5 ?' Z! t* h: e
- * @retval None# |( p2 X- R( O0 @0 C
- *// {" @# O. N& o8 {
- static void SetSysClockTo72(void)5 v* @( x r% I6 f: [1 M2 \
- {
4 D% g$ z. G7 h! \- K - __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
7 ^+ T1 G* W t -
( \. }% s6 j' F. {( ]& Q) Q - /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/ ( W5 i5 x) Q z& A
- /* Enable HSE */
: e+ A4 a* i0 ^' X - RCC->CR |= ((uint32_t)RCC_CR_HSEON);/ U; W T, g5 k7 a8 }0 u, u
- # @6 E' e, s5 C8 C6 O
- /* Wait till HSE is ready and if Time out is reached exit */- Y8 [7 n* f( G5 A1 o
- do* {0 J( `8 {! `: Q
- {
# U4 P- ~. Q/ {- L: V1 @ - HSEStatus = RCC->CR & RCC_CR_HSERDY;
/ X' e; G: p& ~* S7 E% S - StartUpCounter++; # N+ p/ T) t3 q. d5 S5 ^4 r
- } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
1 r$ a$ ]5 N2 B _
$ T" j) x0 T* `6 K% J5 A- if ((RCC->CR & RCC_CR_HSERDY) != RESET)$ z' C" T" E% P* D$ k
- {
$ [. T/ \! \! @- R" R; G# M" d. F8 ~ - HSEStatus = (uint32_t)0x01;) }! q R! Q1 w; d
- }
9 K0 K+ g# ?9 q5 B) Z! }0 M - else
0 |" p3 A( f/ w* B$ J$ J, j - {% K$ k8 k; h7 T% V9 g* w
- HSEStatus = (uint32_t)0x00;& ?' R+ m% M" v. w+ _
- }
2 s+ D& [- ~! o* ^0 q( x9 p
' M) \% y+ C+ b0 ^9 _$ {- if (HSEStatus == (uint32_t)0x01)
+ C# _0 a* {$ ~/ X7 f$ a- n - {
: P7 `" ~. I- M& M* @6 v( A( O' h - /* Enable Prefetch Buffer */) d" m4 i" \+ k% A6 r
- FLASH->ACR |= FLASH_ACR_PRFTBE;$ @4 S: [1 Q. [8 X& P& {
) t4 D, I3 b0 B. j9 d- /* Flash 2 wait state */
4 h! O3 g0 J2 k6 k7 T4 K- U9 E7 R0 _ - FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);, n: _/ a) N0 X; S" l
- FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; : {3 N9 f" z: j# N
" l" e/ R d- S- 8 g' s2 h# N' D% M$ J5 I0 u( b
- /* HCLK = SYSCLK */. x& o5 B3 h0 B' a; _& S! @! f
- RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
- R( D; h0 H1 Y6 |4 z/ H -
- N m9 n5 F* P6 T - /* PCLK2 = HCLK */9 h. `" O7 o _% T
- RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;8 ^+ }" s1 O$ P) r6 T
-
+ O& E2 U+ c/ P0 C4 O, s. c# R! a3 J - /* PCLK1 = HCLK */
% }8 ]0 T$ a, ]: N8 z* } - RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;
: q9 J1 n$ A U3 J$ ?' @# _ - ; ~0 [) h; ^ X8 ]: U9 k' H
- #ifdef STM32F10X_CL5 B6 w& i0 h- D7 I( x. \- l
- // ...) w ^7 P1 p( P, e0 \, r
- #else
8 O9 ? a% g! I7 Y8 a$ l: u0 j& S; U1 N - /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */
6 g+ U7 e1 g9 \( V' L7 Y& T - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
" _3 V6 ?7 M/ E# b* u1 R) N! K - RCC_CFGR_PLLMULL));9 I# a; l* X" G6 H0 S$ J; i; Z% b6 I
- RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL6); // 12
: w& s5 O+ k- z7 g4 f: D; R - #endif /* STM32F10X_CL */
& s [' t" G1 T8 \ - # P* I7 c0 l |* y7 h! V. ~$ Y
- /* Enable PLL */
: |- w. Q1 E, U* j+ F8 l - RCC->CR |= RCC_CR_PLLON;
: E) U7 \! V- |" C' T& G3 i+ J! R - / k8 ?5 Y. g0 O/ L3 Y7 |
- /* Wait till PLL is ready */8 N- L/ J% A2 ^' S* Q9 r& y* Z
- while((RCC->CR & RCC_CR_PLLRDY) == 0)
' ~ W8 M% o6 m& s: ` - {" r- ?8 r# c& F& c% d8 y6 M
- }' b# F( f- c$ P) f* t2 u
- & Q3 i) e* e \1 b& q9 M
- /* Select PLL as system clock source */
1 ~, _$ ^: i1 `) R9 L) H - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
7 M0 ]2 o2 T" s) X4 ~2 X4 X - RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;
+ T! @5 T8 Y3 m# \0 a9 } - 5 z* e6 C' X) w. C) C5 H, k! V
- /* Wait till PLL is used as system clock source */; w) n8 i7 H& |/ H
- while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)8 d& r; X; W8 A+ a r1 \: |
- {
2 t0 s9 W+ d6 s8 ]7 ^& N% S - }
' ?# P. p+ ]; R% W - }* m0 m% Z. X' ^. f! N
- else, D5 W4 u! U- {
- { /* If HSE fails to start-up, the application will have wrong clock
; H+ }4 C; G5 {, y$ f3 m - configuration. User can add here some code to deal with this error */: [9 Y' G% \: X( W
- }
# k" r8 g4 _; x6 v! d - }
复制代码
% D$ m: f) w1 B) a* `5 M9 f8 w
4 x* ]/ h. o# _! e& z: @$ X3 . 内部RC作为时钟源
: {8 ^6 r, {* E3 ` { 实际开发中使用内部RC振荡器主频不能达到72,我使用的是STM32F103C8T6,库函数最多支持16倍频也就是8/2*16=64Mhz,实际测试芯片跑不起来功能没有正常工作。使用内部RC振荡最大能达到52M,不信大家可以试验一下。; a3 q, J B0 H1 E
在system_STM32f10x.c中,找到函数void SystemInit (void){} 注释掉所有代码,添加下属代码。 + J' K# P+ a( N% I. ~
- //开启HSI
' G0 Y; z8 W, y* p - RCC->CR |= (uint32_t)0x00000001; + E' U$ n2 p2 O
- //选择HSI为PLL的时钟源,HSI必须2分频给PLL2 W! }/ v: P8 j. G) u
- RCC->CFGR |= (uint32_t)RCC_CFGR_PLLSRC_HSI_Div2;
" W0 b2 F1 M: {, H" J - // 8/2 *13 = 52 8/2 *9 = 36 8/2 * 12,设置倍频% o( o! D7 M' H3 }
- RCC->CFGR |= (uint32_t)RCC_CFGR_PLLMULL12;
2 T/ k# _9 B, B - //PLL不分频+ _9 E& A* K2 y1 ]
- RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
6 v% s: G, O O4 x0 L4 d - // 使能PLL; b7 O( c/ K: }5 u- q
- RCC->CR |= RCC_CR_PLLON;
$ M8 J5 ]; |0 a9 _: c - // 等待PLL始终就绪3 b$ [- V# B* P. U' Y5 @) ~3 C
- while((RCC->CR & RCC_CR_PLLRDY) == 0){}
7 r) e4 z+ B Z- _, k - // 选择PLL为系统时钟源
% j( r+ k J7 O" X' ^7 Z0 w- @# c - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));8 E, H# B3 J: G! O1 H+ S6 t2 B
- RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;" A- D$ s$ }2 ?3 O
- // 等待PLL成功 z+ i/ ~% x3 G3 z6 { Z$ _9 N
- while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){}
复制代码
1 e8 s5 B5 x) K- O$ b3 g
# F4 E. _2 T4 @4 .Keil MDK中Xtal的作用
3 n5 e$ j8 R$ E
9 G9 E- y. T6 R: U' \' m
?4 G6 l/ J) W5 L# E( Z" u( |/ I0 x1 A: F( g4 w1 V# ~; O3 z' W; z* S
$ j7 A; d/ H) T9 H5 r( W- e
- l! c" h6 k& f5 a+ P- @- k7 h! A, ^3 `! c, v% B
在手动配置主频的过程中,想到Keil工程菜单应该提供了配置主频的选项,于是又看到这个。百度了一下,这个参数只用于软件仿真的,对于硬件仿真或者直接把程序下载到板子里是没有影响的。; L! T, e9 ]2 j) p# I
; G4 H6 o& b. z5 a7 V$ B Xtal 后面的数值是晶振频率值,默认值是所选目标 CPU 的最高可用频率值 。该数值与最终产生的目标代码无关,仅用于软件模拟调试时显示程序执行时间。正确设置该数值可使显示时间与实际所用时间一致,一般将其设置成与你的硬件所用晶振频率相同。, |+ k& j9 n% ^5 i/ ~
5 H" f g9 f6 L/ b& a- x" u$ P/ l6 k3 p9 R7 H1 S: g
1 j7 ]- B5 `6 E) \5 v, I; c" ^# F; [! g5 z0 o
|