前言 最近开发项目,对MCU主频要求比较精确,尝试了两种配置主频的方法,掌握这两种方法也就熟悉STM32系列主频的配置方法了。分别是,使用外部晶振作为时钟源;内部RC时钟作为时钟源。介绍两种时钟源的区别:
8 H$ M; E* O! {/ ^ N, [
- HSI内部8MHz的RC振荡器的误差在1%左右,内部RC振荡器的精度通常比用HSE(外部晶振)要差上十倍以上。
- 内部RC频率受温度影响比较大,如果省电Sleep模式下内部RC会停止工作。8 h0 |1 X/ D1 k5 {4 N9 b/ d
+ q6 d& l* |) Y; V! d/ M7 ]
1 R' @4 ?& S* r7 @; x9 B3 ~# S. J# V) D! b" @+ d- {' h b
1 . 时钟系统
2 P. [' E# K7 H; D 在STM32中,有五个时钟源,为HSI、HSE、LSI、LSE、PLL。 ; O* x8 e1 R' [0 `; e, n8 A
- HSI是高速内部时钟,RC振荡器,频率为8MHz。
- HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。
- LSI是低速内部时钟,RC振荡器,频率为40kHz。
- LSE是低速外部时钟,接频率为32.768kHz的石英晶体。
- PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。
5 V& X+ m! p& q9 r3 G; [. _! L. | & \% E5 a* {5 |( W3 h. O, k S/ j
( Z& N7 _6 y# N- A" w+ u) m. P" D5 W1 J9 S3 P3 z' ]
/ `1 Q' Q' M% j; o0 `
用户可通过多个预分频器配置AHB总线、高速APB2总线和低速APB1总线的频率。AHB和APB2域的最大频率是72MHZ。APB1域的最大允许频率是36MHZ。SDIO接口的时钟频率固定为HCLK/2。" T/ I& M0 ~8 R3 }
40kHz的LSI供独立看门狗IWDG使用,另外它还可以被选择为实时时钟RTC的时钟源。另外,实时时钟RTC的时钟源还可以选择LSE,或者是HSE的128分频。RTC的时钟源通过RTCSEL[1:0]来选择。
- Y0 S; b0 M* q! b$ p, k& I3 j STM32中有一个全速功能的USB模块,其串行接口引擎需要一个频率为48MHz的时钟源。该时钟源只能从PLL输出端获取,可以选择为1.5分频或者1分频,也就是,当需要使用USB模块时,PLL必须使能,并且时钟频率配置为48MHz或72MHz。& D3 q& V& Z# `, w( Z
另外,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大模块使用: 6 [. O6 |8 i% \" W, S, ^5 [1 }+ 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)。
1 X" [ l f1 G6 J/ g- c& N0 J9 P8 J- l; R/ q; q. R& G
: `4 B+ R) U- A" K; C3 c
2 . 外部晶振作为时钟源+ l1 c7 u- f7 c8 n. i9 t4 m
接下来,解决使用12M外部晶振时,如何配置作为系统时钟源。) H$ k, L! z" T
第一步,修改stm32f10x.h中的HSE_VALUE为12000000
4 C2 |) v8 t2 i. |- z
- /**0 ~* k, h* Q5 S, h
- * @brief In the following line adjust the value of External High Speed oscillator (HSE)
" p2 h0 r; r6 t2 A a - used in your application 9 k% M+ z; a. I3 `
-
4 ?' C( F& p* b - Tip: To avoid modifying this file each time you need to use different HSE, you# W# k/ ^$ F( n- I% w4 w
- can define the HSE value in your toolchain compiler preprocessor.1 ~% E* }6 u* t5 B+ i
- */
( N0 q6 y& e% q. s - #if !defined HSE_VALUE ]. O2 F* |; L' f" O2 A5 `1 l
- #ifdef STM32F10X_CL
; F+ X6 l; ~: K6 k1 A3 \ - #define HSE_VALUE ((uint32_t)25000000) /*!< Value of the External oscillator in Hz */ x+ _0 A0 m% Y1 Y* z
- #else
) E8 s* S# s, ^5 Q5 C - #define HSE_VALUE ((uint32_t)12000000) /*!< Value of the External oscillator in Hz */
! c$ c( Y# d$ J0 S9 @5 ?$ E; t - #endif /* STM32F10X_CL */; t. I e+ W: k* d* g* {
- #endif /* HSE_VALUE */
复制代码 3 v% R" g6 p% I
第二步,修改system_stm32f10x.c中的时钟配置,先找到void SystemInit(void)—》SetSysClock()—》SetSysClockTo72(),将9倍频改为6倍频,12*6=72MHz ' \6 T. Y, W- B) F N) \+ \+ z8 t
- /*** e8 C' I4 W, e3 Z/ _9 _; r
- * @brief Sets System clock frequency to 72MHz and configure HCLK, PCLK2 $ k) j3 c: e$ w P) d& |
- * and PCLK1 prescalers. , X! y: P! R4 Y6 L u- C
- * @note This function should be used only after reset.
% O. i- z, \4 e - * @param None/ h: i4 a8 D" D6 n: P, n' }, ~
- * @retval None
3 l6 {' U( n6 C' w& y - */
# d& v w# V8 {7 |1 X& K+ M+ y2 k - static void SetSysClockTo72(void)$ R' y5 X" s' R5 v! x
- {
' \5 m1 t* |( I9 q! Y) U: [ - __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
8 p* r$ Q1 j& T9 i5 T; b -
0 t, u9 k( d9 z9 Y0 T/ ? - /* SYSCLK, HCLK, PCLK2 and PCLK1 configuration ---------------------------*/
" c7 ?& [, N( N: k e - /* Enable HSE */ : O( F! j0 ^' c) x8 E
- RCC->CR |= ((uint32_t)RCC_CR_HSEON);
) H2 g& x9 n5 p/ c' O7 n - " C: _7 v' T' {6 }0 S5 Y* J
- /* Wait till HSE is ready and if Time out is reached exit */
3 Q; V% }& Z5 J; c8 z - do9 l4 Z: ^6 F) j& W; ^: L
- {
+ L8 ^/ l$ A9 O/ D! t% t- C - HSEStatus = RCC->CR & RCC_CR_HSERDY;6 ~' U& D" K5 t5 K' L" l' `+ H
- StartUpCounter++; / P) d! ?" p* ^, e
- } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));/ T$ G; {. ^5 o! M- C, W' [: d& V5 s
. D+ _" j6 B) u8 M- if ((RCC->CR & RCC_CR_HSERDY) != RESET)- N9 o1 O0 ?$ |
- {
. C# J! e! F% N: @( D6 v# q' p6 p. b: ~5 R0 z - HSEStatus = (uint32_t)0x01;
2 V! s5 s3 L6 d - }* y1 |) @8 @8 I# D8 d
- else) I8 O% i8 O/ @' V1 B. g" L
- {
# a+ Y/ } |* d5 e- w7 P2 W4 t - HSEStatus = (uint32_t)0x00;
1 y& j. \2 E9 v- E - } / E% D# i) Q. H
- ) R: C- g8 j$ l1 p) i& a2 p0 H
- if (HSEStatus == (uint32_t)0x01)
* c6 t' F2 g9 d, ]& } - {3 P4 ~* b! _; }. T
- /* Enable Prefetch Buffer */1 O" D9 @. ]4 {+ J% N
- FLASH->ACR |= FLASH_ACR_PRFTBE;
, {3 i9 |' C" S. w0 e. N - ! c; l1 e) w2 `& J% |
- /* Flash 2 wait state */( ?% |+ o; G0 O* G
- FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
) Z0 n, ?# Z; t3 p" O - FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;
/ z9 c; P' t0 Q4 F# Y6 ]1 h) \ - % v. _* w b( q6 z m5 |/ @
- 6 `! x; `8 `$ {9 w( D
- /* HCLK = SYSCLK */8 V" r0 t, R; m! r6 B# z$ _' k+ y
- RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;5 W" o |; P5 E, ?5 P
- ; e0 N6 q4 a$ k9 S' v1 Y
- /* PCLK2 = HCLK */
' X' \2 N ]# k1 M7 O; P - RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1;2 l9 ?, d+ N& M9 X
-
- {# ]/ F% _$ h- G5 S, K: q7 } - /* PCLK1 = HCLK */1 W8 t$ O3 N" c1 {2 t" ?
- RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2;5 A$ L1 |$ u) x+ S) z
- 5 X: b; a9 o8 N8 A/ z9 C
- #ifdef STM32F10X_CL
' M. T3 m9 q+ B0 l; \$ x9 G+ n - // ...
% p I! k0 Z4 y, X8 S - #else / K6 V/ ^* h$ u4 y/ g1 a$ `
- /* PLL configuration: PLLCLK = HSE * 9 = 72 MHz */3 e4 [- i6 P7 y: o/ w4 U, _ H
- RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
0 w2 `0 [- B4 S7 @ - RCC_CFGR_PLLMULL));
- `7 g$ {5 @, P: M - RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL6); // 12/ ^3 g: h% _5 a- e6 I
- #endif /* STM32F10X_CL */
6 D/ C5 Z" ^5 o3 n3 O - , K( }9 e6 v& v0 y
- /* Enable PLL */6 p0 `/ Y0 C3 b H
- RCC->CR |= RCC_CR_PLLON;) e0 k( e# b/ ]" K( @: D, O
- 7 h8 l8 `9 S: U. i7 _
- /* Wait till PLL is ready */
3 ?, w6 f, T" i; i9 S* H - while((RCC->CR & RCC_CR_PLLRDY) == 0)& i }, T- ?& E7 h) c$ U
- {
6 q0 F# D C1 X: H0 S - }
% y' }- Y* T! Q' H* K1 U - m0 ?: a& ~' ^5 E3 x
- /* Select PLL as system clock source */
1 N: h: {, L+ S - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));
! e9 f, c: W, S6 { - RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; + O; `& |5 f" C( @
" `) V* c6 X* }6 u- E- {: o- /* Wait till PLL is used as system clock source */* ~: A2 [# C, I, [7 Z0 o
- while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08)
0 t' H$ H4 K7 v1 R) f9 H' Q - {
. U+ d8 I7 n; s( A5 K - }
: H: A7 I: q$ V& X - }, m+ k% q% w7 n' T5 w3 }2 A
- else8 K9 K! ?# {" H: m
- { /* If HSE fails to start-up, the application will have wrong clock 1 J: z0 b9 f- }
- configuration. User can add here some code to deal with this error */
/ q3 r$ M0 \" O9 B - }
8 Y9 }) i0 i$ W8 A2 L - }
复制代码 ; d; ^9 ]) r: Y$ z3 F6 K) ^ A
1 {" t$ `4 j7 {
3 . 内部RC作为时钟源 a2 h+ K# w7 ^# _7 s
实际开发中使用内部RC振荡器主频不能达到72,我使用的是STM32F103C8T6,库函数最多支持16倍频也就是8/2*16=64Mhz,实际测试芯片跑不起来功能没有正常工作。使用内部RC振荡最大能达到52M,不信大家可以试验一下。5 }6 G6 |$ a, [2 b2 _) h, b
在system_STM32f10x.c中,找到函数void SystemInit (void){} 注释掉所有代码,添加下属代码。 ?# s9 Q$ D+ O" Y; Z8 a! [
- //开启HSI
2 h" G: @ W! N/ w" N3 u; Q7 T - RCC->CR |= (uint32_t)0x00000001;
$ M6 t2 B# T5 k& n - //选择HSI为PLL的时钟源,HSI必须2分频给PLL# U1 p! R4 \. P2 ?
- RCC->CFGR |= (uint32_t)RCC_CFGR_PLLSRC_HSI_Div2; }# L' o, R* s x
- // 8/2 *13 = 52 8/2 *9 = 36 8/2 * 12,设置倍频5 Y# @- q+ _0 O2 z" [
- RCC->CFGR |= (uint32_t)RCC_CFGR_PLLMULL12;
5 E) `- y8 r4 ~: C - //PLL不分频
1 c9 g7 D2 M4 H& g: r" V T - RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;
d5 ?$ H3 W% _3 n$ a [5 f - // 使能PLL
1 V) b# ~5 K1 e+ f/ }4 v. q0 j - RCC->CR |= RCC_CR_PLLON;
& \3 @0 s) N+ N$ i6 W7 _6 _# G - // 等待PLL始终就绪
7 ]/ v6 b3 L( B) q1 `% o - while((RCC->CR & RCC_CR_PLLRDY) == 0){}2 _) k! ^/ m" @& q
- // 选择PLL为系统时钟源
. y- P! S5 Y0 H* I2 T6 |: T" b - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW));$ I! ?5 f1 ]; i5 {3 d' u
- RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;7 ?6 p8 l5 A: r: Y
- // 等待PLL成功
$ @) |' H7 ~# f+ F0 y- B" G - while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08){}
复制代码
& a/ ^5 g$ t5 w/ x2 ]/ v1 v+ Y: B( d$ L& Y
4 .Keil MDK中Xtal的作用
7 G. J4 Y9 Z0 J5 X2 e$ R0 U
: }, u; P/ L4 k+ h6 s4 ~
( w" w+ F, [! l9 D6 \# E: I
3 {4 ~* o% v# e$ q, s/ [
2 r0 R7 ~% M- @) @2 `9 m
: i. s0 Q) S3 y1 y h6 p% f% x2 u! D& }
在手动配置主频的过程中,想到Keil工程菜单应该提供了配置主频的选项,于是又看到这个。百度了一下,这个参数只用于软件仿真的,对于硬件仿真或者直接把程序下载到板子里是没有影响的。9 e, ]% e2 @; M0 W
# B8 z; j* Q0 t$ C
Xtal 后面的数值是晶振频率值,默认值是所选目标 CPU 的最高可用频率值 。该数值与最终产生的目标代码无关,仅用于软件模拟调试时显示程序执行时间。正确设置该数值可使显示时间与实际所用时间一致,一般将其设置成与你的硬件所用晶振频率相同。
, _+ w3 X, q3 J& H, a6 h2 K6 n0 O: Y( K! { n8 v
, r3 X4 T r4 v6 A8 L" m! c* O+ C* S: p7 y7 A9 R
2 Q" s8 }- }1 h8 x |