前言' Y+ p0 C: {) T* t
时钟系统之于单片机就如同与心脏脉搏之于人体,可见时钟系统的重要性可见一斑。然而STM32的时钟系统极其复杂,不像51单片机一样一个时钟系统就可以解决一切问题,这对于初学者来说很不友好,本文致力于讲解STM32时钟系统,使读者清晰了解STM32时钟背后的原理。4 B" X1 x m3 X8 t0 j
m4 S* `' y r0 O: G4 w一、STM32时钟系统原理
1 @6 {/ C5 \8 b1. 时钟系统框图, u8 _/ P1 P7 J$ q7 c# I9 h
以下是STM32时钟系统的结构框图,摘自STM32中文参考手册。
0 q8 T/ a" m j; |8 \: A
1 z- \- W$ U! U% l
" q3 Q1 M3 |' s1 L8 b! w
' D7 G) L, }7 j l2 b5 U可以看到,官方手册提供的时钟框图很复杂,这里我给出一个简化版的时钟框图:# O# ]& ~" f1 A: Y
Y8 ]! M* C+ b; h+ k
% b L* j& P9 L
! @ C* p' j, b; E+ |
2. 时钟源讲解. a9 M- _2 b+ t* A( Y
从图中可以看到,STM32共有五个时钟源,分别是HSI、HSE、LSI、LSE和PLL ,下面分别对它们进行讲解:
2 d" _3 D* o! z
" j- g: p' W5 `4 L2 T+ Q6 [; f2 a ①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
, `% c1 o$ J3 v2 ?1 y9 `' E ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。( `! Z( T+ z9 J8 B0 U) e! m
③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。独立看门狗的时钟源只能是 LSI ,同时LSI 还可以作为 RTC 的时钟源。$ E! q" t- s$ v, r% K* U
④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。是主要的RTC时钟源。
$ ~" c9 V7 J% Y& f ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。$ b! Z6 d2 ?$ X9 W$ T
: L. D/ D* o& T! I9 T# l" C6 n: G% s注意:高速外部时钟HSE的引脚是OSC_OUT和OSC_IN这两个引脚芯片是独立引出的,可以接外部的晶振电路,而低速外部时钟LSE的引脚OSC32_IN和OSC32_OUT两个引脚不是独立的,而是在PC14和PC15上,对应关系为OSC32_IN-->PC14 ; OSC32_OUT-->PC15
8 [/ T# r6 U9 ]2 w, d
, I) y6 B+ e0 w4 n- B6 [7 w3.时钟去向讲解
) Y6 [4 I( I/ s3 F- l9 v 上面我们简要概括了STM32 的时钟源,那么这 5 个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们将一一讲解。这里我们使用官方手册提供的框图(图一)进行讲解,图中我们用A~E 标示我们要讲解的地方。0 V- r# L/ a5 W% G$ ?8 P1 m0 R
4 Z0 l- v3 v" O3 i6 C6 I
A MCO 是 STM32 的一个时钟输出 IO(PA8) PA8),它可以选择一个时钟信号输出 可以* ` f9 K, |, L) a
选择为 PLL 输出的 2 分频、 HSI 、 HSE 、或者 系统 时钟 。这个时钟可以用来给外) b/ C- F- D$ a
部其他系统提供时钟源。
- M6 b4 S1 }3 l6 R2 z- r3 nB RTC 时钟源,从图上可以看出, RTC 的时钟源可以选择 LSI LSE ,以及
% n" K. v# W" R; }HSE 的 128 分频。
3 o- K7 G* ^9 j3 z, x/ l7 T/ ~1 UC 从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。 STM32 中有一个全速 功能! {" I- {; j$ y4 }
的 USB 模块 ,其串行 接口 引擎需要 一个频率为 48MHz 的时钟源。该时钟源只能
w7 X' T* H3 x& Q. n从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB8 x i( q! [6 N
模块时, PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz 。
% p& K3 D$ H* N6 x+ r J& uD STM32 的系统时钟 SYSCLK ,它 是供 STM32 中绝大部分部件 工作 的时
4 p6 c# H+ n( Z$ O钟源 。 系统时钟可选择为 PLL 输出、 HSI 或者 HSE 。系统时钟最大频率 为 72MHz
% d/ B) Q- W! F4 F, d6 _当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
& }) v/ _) A x& {E 其他所有外设。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK 。 SYSCLK 通过 AHB 分频器分频后送给各模块使用 。这些模块包括:, F/ V5 K1 W6 j* T& u: C
① AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。 i+ C K$ R5 d5 o. m
② 通过 8 分频后送给 Cortex 的系统 定时器 时钟 ,也就是 systick 了 。
: |$ a C' q5 v3 [$ U6 v③ 直接送给 Cortex 的空闲运行时钟 FCLK 。; A# s i m% ]) z4 J6 s" ]( e" O% E
④ 送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用 (PCLK1 ,最大频率 36MHz)36MHz),另一路送给定时器 (Timer)2 、 3 、 4 倍频器使用。
- k# y) p# h- f* y. O; e7 ?⑤送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用 (PCLK2最大频率 72MHz)72MHz),另一 路送给定时器 (Timer)1 倍频器使用。
' U0 J) {+ T! V2 } APB1和APB2的区别: APB1上面连的是低速外设,包括电脑接口、备份接口、CAN、USB、I2C1、I2C2 、 UART2 、 UART3 等等, APB2 上面连接的是高速外设包括 UART1 、 SPI1 、 Timer1 、 ADC1 、 ADC2 、所有普通 IO 口 (PA~ 、第二功能 IO 口 等。 APB2 下面所挂的外设的时钟要比 APB1 的高 。3 a2 J0 g/ H6 R' a
1 B6 N7 N7 _4 T/ a; q f; {2 I
在以上的时钟输出中,有很多 是带使能控制的,例如 AHB 总线 时钟、内核时钟、各种 APB1! M8 P( y: y4 F9 ^: S# Z. n
外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
$ F0 u/ K% @# m
x/ f* \- h5 s% N% @& G% `5 H二、STM32时钟配置
$ t: o$ p6 @7 _1 m' ?4 n) G1. 时钟配置简介9 u% G' |1 d0 ~, u9 K3 p# a
STM32时钟系统的配置除了初始化的时候在 system_stm32f10x.c 中的 SystemInit 函数中外,其他的配置主要在 stm32f10x_rcc.c 文件中,里面有很多时钟设置函数,大家可以打开这个文件浏览一下,基本上看看函数的名称就知道这个函数的作用。在大家设置时钟的时候,一定要仔细参考STM32 的时钟图,做到心中有数。这里需要指明一下,对于系统时钟,默认情况下是SystemInit 函数的 SetSysClock() 函数中间判断的,而设置是通过宏定义设置的。我们可以看看 SetSysClock() 函数体:: Y1 L' S8 w2 o" |" N: o/ p; k
9 b ~; C# S7 W3 q G6 s8 R- i
- static void SetSysClock(void)
R. W; _) f8 U9 t9 {* ^ - {4 g3 G5 d* G: o% F8 \, @& q
- #ifdef SYSCLK_FREQ_HSE5 G$ `, n/ F, E" E( S, T
- SetSysClockToHSE();, P( Y! \8 C, Z w# `- e& J
- #elif defined SYSCLK_FREQ_24MHz
- S0 ^! ~& v$ S; i9 @ - SetSysClockTo24();& n F) v+ V, h( I B; H( R1 O
- #elif defined SYSCLK_FREQ_36MHz" p, e- j6 q( a0 S
- SetSysClockTo36();! K+ u# y9 ^6 k. @4 b
- #elif defined SYSCLK_FREQ_48MHz& E& T5 ]) ^; M [/ h
- SetSysClockTo48();5 T* p: t: u$ B5 v7 Q
- #elif defined SYSCLK_FREQ_56MHz
k- R( ?+ v; F) p - SetSysClockTo56();
: Q, M# S9 [+ J) b# M - #elif defined SYSCLK_FREQ_72MHz
/ O r1 w' a* @4 K' i8 K7 X f - SetSysClockTo72();
3 ^# H* y+ K8 W. {6 \; N7 G - #endif0 e/ _1 L, A, z( M! B& H% E
- }
复制代码 ) I4 p. i y. P) X8 u! r$ p+ R1 Y8 p( h
这段代码非常简单,就是判断系统宏定义的时钟是多少,然后设置相应值。我们系统默认宏定
6 G" }/ w7 c" } _2 j' H$ B/ Y义是 72MHz:% y- W" D) `! ]# m$ H
. Y4 R8 ^4 i' [! Q; W+ f; a( n* i/ I3 j- #define SYSCLK_FREQ_72MHz 72000000
复制代码 * |- O2 H( e7 p9 [1 M7 {7 {
如果你要设置为36MHz ,只需要注释掉上面代码,然后加入下面代码即可
- B; j4 E$ K% [6 a# P$ ~5 s" @; t
. V& t$ P5 H! N+ N5 x5 s+ X- #define SYSCLK_FREQ_36MHz 36000000
复制代码
+ U: p3 ^- s/ j( _" N, b9 Q) l 同时还要注意的是,当我们设置好系统时钟后,可以通过变量SystemCoreClock 获取系统时钟: J) _' U/ y- I' F$ Y: ]
值,如果系统是 72M 时钟,那么 SystemCoreClock =72000000 。这是在 system_stm32f10x.c 文件中设置的:; f# |% E6 O1 B' \
+ i1 m6 ^8 U s9 E6 F7 E
- #ifdef SYSCLK_FREQ_HSE
( F: R( q r# s, u - uint32_t SystemCoreClock = SYSCLK_FREQ_HSE;
' a7 y/ Y9 l7 Y) y0 N8 z- s) @$ } - #elif defined SYSCLK_FREQ_24MHz) p& G( p; Z P
- uint32_t SystemCoreClock = SYSCLK_FREQ_24MHz; / T, l) f. {9 I
- #elif defined SYSCLK_FREQ_36MHz
3 U" Y+ O) N. H y - #elif defined SYSCLK_FREQ_48MHz
( z! R# y6 V/ n' Z0 H# Y+ D- t7 D - uint32_t SystemCoreClock = SYSCLK_FREQ_48MHz;
. ?; D7 l. z2 W$ r - #elif defined SYSCLK_FREQ_56MHz
* W; G1 D/ ]3 f" {- i6 } - uint32_t SystemCoreClock = SYSCLK_FREQ_56MHz;
5 g- L) `. v9 ~: \ - #elif defined SYSCLK_FREQ_72MHz
8 n3 Z7 u2 B. ~ T' ` - uint32_t SystemCoreClock = SYSCLK_FREQ_72MHz;
3 I9 a- x, s* z: ^4 {0 C8 A6 O - #else
r$ T1 ], _ F4 [+ L. X8 u, e - uint32_t SystemCoreClock = HSI_VALUE;
7 K& A0 ]( I# O+ z, R - #endif
复制代码
/ G' _7 d9 e3 x, N这里总结一下SystemInit() 函数中设置的系统时钟大小:
. k# N; V0 I3 r: d$ P. {& m3 l* m
4 u+ @, b# e3 z1 U' u) P0 ^* ZSYSCLK (系统时钟 =72MHz
2 v4 U2 A9 j% v3 C5 D$ z' zAHB 总线时钟 使用 SYSCLK) =72MHz8 C! C) B" ~: ?
APB1 总线时钟 ( =36MHz
$ a$ t8 P7 _( ^APB2 总线时钟 ( =72MHz4 V! q8 @2 ]" k; c' R' U1 m Q
PLL 时钟 =72MHz
. Q1 E! ^: R' L8 t# c- T2.时钟配置寄存器介绍" M- D$ {$ A$ P, d1 F# z6 v
以上介绍了SystemInit()时钟配置函数,想必大家已经知道了STM32时钟的基本情况,如果你想要更深一步的了解底层原理,那么下面的知识可以帮你揭晓。/ J7 E4 ^2 S2 D: i: N' M" \6 Y6 ~( O
! } a- f2 k" }/ z9 D
在STM32时钟配置的过程中使用到了RCC寄存器,它们分别是:
5 f) \4 [6 s( ?. L* R; ^
1 G0 H/ M5 ^, p! h时钟控制寄存器(RCC_CR)
& H) i8 J3 ?5 \, |时钟配置 寄存器(RCC_CFGR)2 f* X$ A9 X6 T8 n/ `3 M" R* F
6 { ~4 H8 @+ I9 q
首先介绍时钟控制寄存器,先上一张官方手册的寄存器说明:' u1 a1 o; _: g0 m9 B7 K
8 _' t6 ^3 w% I
/ R$ L k0 \8 M! w, _ N$ I, K9 ]' J
) _3 A! ]6 c1 ], n( S
, X5 | R/ F0 f6 a+ x4 A6 q( ^! a
+ V4 j2 |! V- p* o
% d# U' P' z1 n) `7 B8 T$ G
这个是时钟控制寄存器,看似很复杂,实际上并没有使用到寄存器所有的位,而是红框标出的部分位。下表是RCC另一个重要的寄存器:时钟配置寄存器(RCC_CFGR)4 a3 P$ b4 U$ z) A
( c+ J- e! G" Q
% _; f$ H8 V4 ]
/ q: k4 u1 C1 S) O
' V9 Q& R; [8 S
T5 \' Z4 P+ D4 y, I$ u
- U0 q! M% y; ~3 {; P* {% E1 Q" |: I3 o& b
4 v% f: }: t8 F+ A O0 Z1 \; R
% e7 x1 T7 V ]' m2 s1 ]6 G
和时钟控制寄存器RCC_CR相比,时钟配置寄存器RCC_CFGR要复杂一些,同样的,重点使用的位用红色框标识了出来。$ s0 D1 Z) P8 K$ S
% f; {( U4 X0 l( N/ m. ?' x. p3. 时钟配置总流程
3 _+ [7 |3 q& A; _5 c在STM32单片机复位之后,首先进入startup程序:
! u, V7 m3 P! c/ q# T3 E# ? f* K" E; B( g
- ; Reset handler" Y1 t; T' t$ L- b' q7 L' b
- Reset_Handler PROC5 a0 \3 \3 R- |- @2 h
- EXPORT Reset_Handler [WEAK]
5 z3 A, Q7 U. a) K9 {7 p+ d! f% c - IMPORT __main
( c# L" H) W9 X5 H7 L8 T - IMPORT SystemInit
. F& V8 L |3 e - LDR R0, =SystemInit
) T* G" _% e1 {% H- G - BLX R0
% W+ M0 X% ^. B" U - LDR R0, =__main; y, P9 d4 E4 ^' n: }( m1 z: x; T
- BX R06 u; g4 B3 s* x7 @
- ENDP
复制代码
' W% I/ Q2 w& ?4 K+ t! W可以看出,在进入main主程序之前,先触发了SystemInit()函数,这样就可以保证不需要每次都把时钟配置程序写入main.c文件了,同样,当你想要执行自定义时钟配置程序时也可以改动这个部分。# {6 D# K8 H0 U3 g. @& q
7 f/ P, L& u% i, A
首先我们看一下SystemInit()函数
0 Y' A: r! d% \3 l/ C% m$ j! K' e+ P9 e& w& _3 L
- void SystemInit (void)* N9 G& B3 Z, M: f" }
- {
# c: _1 V$ }9 }' {- [ [3 ^2 w3 o - /* Reset the RCC clock configuration to the default reset state(for debug purpose) */2 _" C7 ~9 ~: B1 }: W
- /* Set HSION bit */
$ b" [; |9 {5 c) A6 z, U - RCC->CR |= (uint32_t)0x00000001;: h9 G2 Y+ d9 C4 a8 a. P, p' z
- SetSysClock();/ e7 f P$ y4 e2 ~( F3 ?# Q+ T
- }
复制代码 $ P. u. `2 V4 M4 [3 _
可以看到,SystemInit()函数的作用就是使RCC_CR寄存器最低位置1即开启内部8MHz振荡器,然后转到SetSysClock()函数中。刚刚已经讲过了,对于系统时钟,默认情况下就是SystemInit 函数的 SetSysClock() 函数中间判断的
* L' L8 O! g: I5 \* x! u4 @7 K f9 e4 x. |
下面是SetSysClock()函数:2 n- X6 _# ?' ?( K( P" a
; V; R$ g6 p: Q/ I7 R; z' u5 R. l @! |5 o
- static void SetSysClock(void)5 U+ x. x" Y: I6 _ f
- {
. B) n# R1 l) c" T - #ifdef SYSCLK_FREQ_HSE+ ^5 y6 q/ i6 c4 b" s" t' H
- SetSysClockToHSE();
: D+ v/ {$ O9 | z0 y - #elif defined SYSCLK_FREQ_24MHz! ^4 D- P) m; q
- SetSysClockTo24();
& `' G: X( z- P! i7 F - #elif defined SYSCLK_FREQ_36MHz
1 H: c) }% C$ k! D - SetSysClockTo36();
5 Y# N. G! l+ U; a: R - #elif defined SYSCLK_FREQ_48MHz6 y1 s Z( T5 y
- SetSysClockTo48();
* W. ]6 f( J0 ?9 u4 R. } - #elif defined SYSCLK_FREQ_56MHz
. x. b. K- O! g1 i2 Y5 f9 E3 L& { e - SetSysClockTo56();
7 {% _1 E, b3 _+ q: s- q - #elif defined SYSCLK_FREQ_72MHz1 W. s2 M3 U R) Z
- SetSysClockTo72();
2 g2 ` Y6 z* [/ |2 ?. r - #endif, k, F) \& v. J! {
' v% ^% |2 \' n& W2 U+ C$ E- /* If none of the define above is enabled, the HSI is used as System clock
. ~5 P/ o# d2 K9 A - source (default after reset) */ * u' j% W' m$ p/ Z4 V6 n; ~. n
- }
复制代码 - K& a: x: Q. [9 V. c& E5 K
由于我们把系统时钟频率通过宏定义设置为72MHz,所以这里直接进入最下面的程序即SetSysClockTo72(),这也是为什么有的程序把这一段语句直接写入主函数,原则上来说这是可以的,但是没必要,因为startup启动文件已经帮我们定义好了。' p7 u9 L4 ?. N+ l2 d
& A7 i9 L% P3 u7 ~
下面就是SetSysClockTo72()程序部分,也就是最关键的部分
Z" j# E# P' d, q- ?5 ?! ` M. x
- static void SetSysClockTo72(void)
. r; V+ s" k. y; ^, N5 U - {
$ u+ M3 x9 k3 _; v8 c# m0 Y6 o - __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
/ z3 ^2 M: d: D$ G7 u5 r4 r+ |
5 Q; {4 B9 p: j7 D; Y5 s- /* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/
" D- V' o* K' r0 P5 `+ `& E - /* 使能 HSE */
; J2 [: [) `# u - RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE
, v" R( J4 Q( w5 w1 E
. p' G% b) {) j( T$ ]- /* 等待HSE就绪 超时退出 */" C0 s7 ^# J* I( [3 g
- do
1 `1 r4 A# o" Q s* O0 G - {4 L' g6 ?& g% A
- HSEStatus = RCC->CR & RCC_CR_HSERDY; //读取RCC_CR第17位
/ |1 c2 a% L: R/ N" @% A5 V8 p5 | - StartUpCounter++; ! n" r+ v( F$ H7 i$ E \ D3 P
- } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));7 ~7 O- D* L8 ~4 w
% v# h( Y- \! N! y# [2 N- if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位
/ [% z+ d/ Z0 k) U' U$ O) |% W - {
: V. @1 r# n0 j8 A7 P1 R- l2 W! B; Y - HSEStatus = (uint32_t)0x01; //HSE状态写1 e& @( o7 V. k& [- Y
- }/ h% |* S) ], H$ g8 @
- else. q1 B# e$ L7 Y
- {0 c! l! a+ M6 w& p* R) k4 w; p
- HSEStatus = (uint32_t)0x00; //HSE状态写0
- o, I# |! e6 v$ s5 j/ U. i4 r - } ; n+ F: f* L/ P2 U; J$ n! W+ A) E
- $ r! ?: D" i" ?" q
- if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管
# \$ h! O; ~6 S - {
( s( E2 [4 M1 w8 n9 N* G - /* 使能 Prefetch Buffer */8 w2 o$ y3 f# A1 V, c8 `
- FLASH->ACR |= FLASH_ACR_PRFTBE;( j0 u2 @7 X8 ~5 A% k$ ]: s
- & f: X* V) s, v5 z" b( z
- /* Flash 2 wait state */1 h) |" Y i8 C9 B
- FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);) N- g1 }3 z* x- j5 { M
- FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2; 3 K) r3 o" e& Y8 m1 L" G# C$ o
- % a: G; u7 d4 d
- . M2 y% v2 ~; @/ k
- /* HCLK = SYSCLK */. l7 I. j. j2 [, m
- RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1; //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟% @" ~; ]. y g# {; }1 i
/ J6 ~' t6 V0 |3 N6 i- /* PCLK2 = HCLK */
5 `! q. L6 {3 Z/ n3 @- Q - RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
6 ]2 A5 K6 e- I1 D
5 Y" k- A( ~; d* r- /* PCLK1 = HCLK/2 */& O+ |/ g3 l. E; G2 D$ ~
- RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟
5 ^% ^- h5 X5 R/ H' @ - / ?* P2 W9 j V+ _# N" f0 _* [7 o
- #else ; N2 e1 c5 l+ p( y/ X: C
- /* PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
4 V6 s z! k6 X - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
) a w3 ~9 @! N. C0 i- v - RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零
" y! t* {$ T3 Y! L& X# ?0 T q - RCC->CFGR |= (uint32_t)(RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLMULL9);//RCC_CFGR寄存器第16位置1,使HSE作为PLL输入时钟;17位置0不变,使HSE作为PLL输入时钟时不分频。RCC_CFGR寄存器第18~21位置0111,配置位PLL 9倍频输出给PLLCLK,即PLLCLK为72MHz% |/ O+ z4 v" w) V* p
- #endif /* STM32F10X_CL */
& b: B/ g+ M- m E8 K2 W
1 T: z E9 }) i% _- /* 使能 PLL */5 ]( d) p( w, V u" ?
- RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL
0 [+ I5 m) B) ~ - * V) {* `1 A% O8 m' g4 Z
- /* 等待PLL就绪 */2 u* Z, C% @+ S. r7 ?; f+ E" c6 H
- while((RCC->CR & RCC_CR_PLLRDY) == 0) //读取PLL时钟就绪标志位(第25位)4 R! N# K- k0 k6 H( ^7 k) ^( t/ W+ g0 m
- {; h9 m/ F# {7 a% @2 e, {9 P" D
- }
1 B* L! Q1 c9 P$ K* H4 x/ S! m - 1 W, E/ E5 ^6 ]' l
- /* 选择PLL作为系统时钟源 */
" T5 w5 Q" y0 _1 ?) ] - RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零1 }; ~( ~ u# o, E; m, Z# K5 s2 B
- RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL; //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟. ?) ?! W5 ^0 y; G' h6 |9 ~* u
4 e9 S0 ~9 x4 }# Z5 |- /* 等待PLL被用作系统时钟源就绪 */
6 c$ p& S$ u" `. Y - while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪: z! x8 s; C4 y- G6 {( \2 I1 M: }" m
- {0 M$ v/ p% e8 I3 y# D B5 H
- }0 p0 q! S3 Y! O1 @3 J' g8 ~% y
- }. W' k, E* E8 J! `9 u7 O
- else( ?: N5 Z% F9 m4 T4 N$ y# U; o
- { /* If HSE fails to start-up, the application will have wrong clock
; J* ~: G( \/ v - configuration. User can add here some code to deal with this error */3 M4 b2 n! i1 e$ w
- }& {/ s' D6 a% C$ K
- }
3 ^4 y: S* u: L% K" Y- d - #endif
复制代码
2 Q+ u% l7 U& \4 s6 C6 x( ~" ^& N这里把所有的备注都写在了程序中,希望读者仔细查看并对照寄存器功能表理解。STM32的寄存器的每一个位都做了宏定义,这样直接查找头文件即可,非常方便。
6 L- {9 S( a) \1 u ~' \1 b% w) P" f; O1 D; L/ T4 h V4 p
至此STM32系统时钟的讲解就已经结束了,我们以大容量芯片72MHz的系统时钟为例来进行讲解,你也可以根据实际情况自己配置,原理都是一样的。下面的内容就是灵活使用STM32的系统时钟来实现演示功能了。
& S( G5 j! }$ g; a9 x' p' d; W2 U5 n' {! q( S) E
三、Systick定时器及delay延时函数
4 k& c1 u6 H0 h1. Systick定时器
$ e* \: b# [7 s1 q前面讲完了STM32系统时钟的基本配置,下面就是对延时函数进行介绍了,延时函数使用到了STM32的SysTick定时器,这是一个很简单的定时器,对于CM3和CM4内核芯片都具备。. G" a/ k3 t7 |- x! J
9 E7 p) O) h2 l& y& nSysTick定时器全称是System tick timer即系统滴答定时器,常用来做延时或者系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如在UCOS操作系统中分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用SysTick做UCOS心跳时钟。
1 G% m4 R& T/ ]# P" |( o, d
( K% F1 k7 n$ h% x. v9 {SysTick定时器就是系统滴答定时器,一个24位倒计数定时器,计数到0时将从RELOAD寄存器中自动重装载定时器初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式也能工作。
2 W& O6 I* G _- } \SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号为15)。+ e9 J+ @! M2 O
SysTick中断的优先级也可以设置: e0 g" J6 N& N( h% R
; M1 e1 I7 E; C# Q
2. 相关寄存器介绍
: f) c9 N$ q* X: T7 s( LSysTick定时器一共有四个相关寄存器:
! A% F% }5 T+ i' X( G' C( w8 D% L; L& c4 E: @" r$ X
CTRL SysTick 控制和状态寄存器
' a$ x) }' ^- D9 v$ yLOAD SysTick 自动重装载除值寄存器 & O; ?6 L: l0 k
VAL SysTick 当前值寄存器 . P. k( c& }; V! \/ Z( F
CALIB SysTick 校准值寄存器 8 Q8 i; P% Q: _5 ^6 p9 r. `
下面分别介绍这四个寄存器
4 r ]- M8 }7 q, Y. k
+ C1 I# x: n9 P. G5 X# B* j* T
' L. t' a3 S5 g7 N
6 e4 L7 m" X# h* M2 Q3 ?5 p
" H: R5 z1 d! L% \6 g
6 W' T; N3 D; |0 M1 V4 Z E9 c4 s
a, x) n V# n. p' n$ o/ K$ x6 ? T$ |2 s& a1 y
9 @" n/ \9 u0 }- W9 s; v u
, i9 p8 B; f# M, Q! ~ 这是四个相关的寄存器,实际上我们一般只用到前三个。
5 d$ y k7 E6 z H9 N4 w% z; k; f% c V7 ]3 P( t
3. 延时函数配置: W- h- ~- B- }6 A" q; d- L
对于STM32,外部时钟源是 HCLK(AHB总线时钟)的1/8 ,内核时钟是 HCLK时钟。配置函数:SysTick_CLKSourceConfig();这个函数在misc.c文件中,这里把它粘出来:. T% ^ o" X2 ?$ S" D' m
M9 X1 w6 Z! E. ^
- void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)8 V1 n% n; Q! O! T( f8 N
- {& W% Z7 G) u, s; D
- /* Check the parameters */
' X0 r2 a: |8 X+ S - assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
1 }4 i) t; a$ }( S9 v, k$ G - if (SysTick_CLKSource == SysTick_CLKSource_HCLK)0 F- i7 ?) o2 Z- s" p) {
- {# k1 Q& a* e- b" _
- SysTick->CTRL |= SysTick_CLKSource_HCLK;
" k; V- p' d1 J' F4 c; u; v - }3 P8 a' b/ n) M( o u4 A/ c
- else
6 R e* {( E8 T* X+ i - {
Q: G E0 z, U# y6 i - SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;# m# {) a* f$ x. H! }
- }+ w2 k7 E& J6 t8 l
- }
复制代码 w5 {. Q! f9 C) A6 ?5 u
根据入口参数有效性可知,时钟源可以选择HCLK或者HCLK的8分频。这个函数在下面的delay_init会调用一次,完成延时函数的初始化。6 P5 O+ N O5 U5 Q! d+ ^
! E* ?4 q g* k, [+ j! Y1 }
core_cm3.h文件中有关于初始化systick时钟和开启中断的函数,如下所示:9 \; e1 Q" N [" C
/ I7 x' ?7 U( j+ r W2 B- static __INLINE uint32_t SysTick_Config(uint32_t ticks)
. o. ]4 p3 h' }! X - { / a3 l, X' i# _1 ]- M; L
- if (ticks > SysTick_LOAD_RELOAD_Msk) return (1); /* Reload value impossible */
, Q. O" d* @# c% s: m
8 I( H+ ~2 l4 l" b# I9 m, K/ ~- SysTick->LOAD = (ticks & SysTick_LOAD_RELOAD_Msk) - 1; /* set reload register */4 |. V5 g; m" o/ M, Y+ d
- NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1); /* set Priority for Cortex-M0 System Interrupts */ ]4 _* x$ b" e, t/ Q6 G
- SysTick->VAL = 0; /* Load the SysTick Counter Value */% e2 M5 s' d6 [2 U$ O( B0 a
- SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk |
9 O. u# K: M) r( [( [ - SysTick_CTRL_TICKINT_Msk | 5 p9 }$ N5 C8 Q
- SysTick_CTRL_ENABLE_Msk; /* Enable SysTick IRQ and SysTick Timer */
" f2 l x( _& } - return (0); /* Function successful */, y! `% K$ `' ]
- }
复制代码 $ Y3 W2 w$ d0 r# {5 i% p
需要注意的是第5行把重装载寄存器的值减1的目的是考虑到执行装在动作也需要一个时钟周期。
+ m/ ~5 N+ h6 }+ h9 c3 ~) ~0 \4 i3 b0 f: p# V+ p+ Y
下面是用中断的方式来实现延时:& I7 \, h8 Y! E; [: H8 i) ^6 g
4 C& f: `9 Z4 X$ `- \- static __IO uint32_t TimingDelay;5 `1 W' l4 o4 }% O3 N- g
- void Delay(__IO uint32_t nTime)# ?) T* k4 U- n/ P. ?, ?3 z
- {
6 N# H% l& W9 f- }/ v - TimingDelay = nTime;
/ v! p5 d+ l' p' v8 @, b - while(TimingDelay != 0);) O4 w* j2 g+ T( {, f2 x
- }
0 J+ P. t9 ]9 `8 b - void SysTick_Handler(void)
2 {+ H' w$ o: i8 | - {, c2 g2 n" s) i7 {
- if (TimingDelay != 0x00) % }7 e z! q: p& P
- { 7 `7 V. x5 u+ j: S: s- V* Q
- TimingDelay--;& U% t; ?' @; B) w
- }$ h) |' V0 D/ X7 r9 B1 ?7 c0 t
- }
5 z5 v7 g! H8 N7 L2 K$ |/ r U - int main(void)2 f6 V9 G v1 R, K1 v! Y
- { …1 `! @3 V0 e: J+ S" W5 x( D1 I7 D. C
- if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms
8 s* w( A7 Z8 t7 @! {. {0 s' Z' J - {
- M/ T+ l) ~3 G$ r; I, w - while (1);
, h# ]) X" n" u9 s - }, | C! X; h. _) S V" F
- while(1)
9 n& L3 x* ^) W+ U- ?" U1 a - { Delay(200);//2ms$ W7 G2 b0 J0 L+ v$ z% e9 Z! l
- …
" I' J. [ O. C - }
2 T E" q( f: S. X: {/ O - }
复制代码
) U9 B+ L; o" |' R) Q2 _
! y- D& Z5 `: L0 P程序中,SysTick_Config(SystemCoreClock / 1000)的目的是确保SysTick定时器每次发生中断的时间间隔都是1ms(因为使用系统时钟的时钟频率为72000000,SystemCoreClock / 1000等于72000,也就是给重装载寄存器值赋72000,这样计72000正好是1ms)
, V3 e& k8 k- N; X. W5 b
, v! e7 @: `9 E/ Z3 S一般实际应用中我们不使用通过中断来驱动延时的方法,因为这样会多一个中断线路,我们采用的 是单独的延时函数,下面就是延时函数文件:; ]# m9 G% g" t4 r2 {! n
( \1 J- Q- O% ]+ P& z0 I
- #include "delay.h" ~# h1 b# x" G$ h! x. L4 U }
- //
' g( _7 y0 S" G$ C n" } - //如果需要使用OS,则包括下面的头文件即可.. S! M, t' K9 ^5 E) c+ n. a
- #if SYSTEM_SUPPORT_OS
6 T$ i) u* @7 y& Q& @ - #include "includes.h" //ucos 使用
U+ g# t; Z* l+ j# f7 x) R - #endif2 w; h9 f. F2 D
- //
. K, \/ k5 v" D" [5 \ - + Z. h1 D5 w2 N& D2 W, v2 D
- static u8 fac_us=0; //us延时倍乘数
) j8 _5 V% J8 M% B* m - static u16 fac_ms=0; //ms延时倍乘数,在ucos下,代表每个节拍的ms数# g4 Z3 t! k: q% \4 p3 a9 }$ B
- 5 Q/ e0 [. _; t
- : V, b/ r h& F
- #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
% A. x5 f( X& [7 ^0 e% j& O, Y - //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
7 B3 v' j9 z) X - //首先是3个宏定义:+ p. T$ G' H" g2 I- L, S
- // delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
h: s9 z/ N( j9 b - //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
" _* J0 A, R' [" y6 {( g+ @ - // delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
0 S3 I5 A4 P" y! U. ^" W2 {3 N - //然后是3个函数:( c3 F; @2 b7 `* ~' I& k
- // delay_osschedlock:用于锁定OS任务调度,禁止调度+ L" y9 r* ? ~: d( A. |/ ~
- //delay_osschedunlock:用于解锁OS任务调度,重新开启调度- k6 j+ C$ M# g7 J5 t
- // delay_ostimedly:用于OS延时,可以引起任务调度.
1 {) T; |0 D0 Q4 @" _ - 7 I: C3 r+ |; c A. H& ]& O
- //本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植% Q6 V3 o0 V: Y) ?( L! P
- //支持UCOSII
0 l4 l% V& X" H4 ? - #ifdef OS_CRITICAL_METHOD //OS_CRITICAL_METHOD定义了,说明要支持UCOSII % `# m$ e$ Q+ Y( D
- #define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行
6 F% I! q8 }% Q; {% R9 c - #define delay_ostickspersec OS_TICKS_PER_SEC //OS时钟节拍,即每秒调度次数
, E) v! L2 K% }9 I5 \ - #define delay_osintnesting OSIntNesting //中断嵌套级别,即中断嵌套次数
2 x" C8 u* j9 v - #endif5 D7 i; K6 X8 b$ h7 Z; i7 B
- $ r" Y) |+ a F! N+ n
- //支持UCOSIII5 P" ~. ]3 [! \0 `5 M
- #ifdef CPU_CFG_CRITICAL_METHOD //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII , O4 W; @/ p& c, _( P, h7 a
- #define delay_osrunning OSRunning //OS是否运行标记,0,不运行;1,在运行. G9 V5 F4 E$ l1 }6 q
- #define delay_ostickspersec OSCfg_TickRate_Hz //OS时钟节拍,即每秒调度次数
# y3 v1 c( b: q* k! ] - #define delay_osintnesting OSIntNestingCtr //中断嵌套级别,即中断嵌套次数
8 \+ b5 q; i( W& C - #endif
" @0 r7 f; u, A( V7 m/ W
; |! D; B% e0 J: d4 a- ( ~; R! }2 w. {7 x
- //us级延时时,关闭任务调度(防止打断us级延迟)
: [. H$ ~" D* Z" w; W) C - void delay_osschedlock(void)
0 {& v2 v8 P- d0 m8 p - {
$ I' q& Z: }% |+ N+ T - #ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII
. Z! h/ F% r/ D. s: m" M - OS_ERR err;
# X! n+ S. i& Y - OSSchedLock(&err); //UCOSIII的方式,禁止调度,防止打断us延时% ~' e3 c( Q9 l$ m* e F2 ]
- #else //否则UCOSII& w. ^6 t1 |3 f* ]( |; \" U. B, ~
- OSSchedLock(); //UCOSII的方式,禁止调度,防止打断us延时
9 S5 F4 X6 f& L: K5 H2 J1 \$ f - #endif/ T; {( i. b6 P) b0 `
- }0 d( w$ r, b' l$ ^# ] T" H
' H) A, z; e/ ~3 ~1 ~- //us级延时时,恢复任务调度
& S0 E( x. k1 P - void delay_osschedunlock(void)
% M" U; H4 a% b& F0 K7 H- t - { ' B1 C: T9 `* l- C
- #ifdef CPU_CFG_CRITICAL_METHOD //使用UCOSIII8 P1 ^( \: J6 h4 B' R" }
- OS_ERR err; 8 Z" i/ X0 ~# i1 ?! M1 D
- OSSchedUnlock(&err); //UCOSIII的方式,恢复调度2 G8 u3 W( `; N) h! K* R' r" [
- #else //否则UCOSII
( s5 m0 M$ t S - OSSchedUnlock(); //UCOSII的方式,恢复调度
* V# q0 Q0 n" V - #endif
: y/ ]4 S0 X4 Q/ m - }
4 }6 ^6 o- r7 H# G8 Q0 u/ z - ) B0 }, g1 ^6 ?6 E! ~
- //调用OS自带的延时函数延时
& t5 }% h8 @2 Z q - //ticks:延时的节拍数3 p# l* H+ C- W T. b4 \
- void delay_ostimedly(u32 ticks)
' R& \- G& p p1 K7 w - {2 A7 ], `5 C& u
- #ifdef CPU_CFG_CRITICAL_METHOD. o6 H6 I- o, }, [. s) U A8 c
- OS_ERR err; ' i6 X# Y% E9 e) |: _, k% r
- OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err); //UCOSIII延时采用周期模式
* m8 Z" J1 h8 p1 R" o - #else
) N! l' Q* u3 U$ z8 m - OSTimeDly(ticks); //UCOSII延时# H/ k3 X! }1 z, x5 M* A
- #endif ; l' C4 y T* r1 W! w ~
- }
U( A! g3 ^/ F! _( H" w - ! L! y/ m* o; A7 `. y& k& ~3 J
- //systick中断服务函数,使用ucos时用到/ k, o1 Y3 t/ h. @+ n2 V7 g
- void SysTick_Handler(void)
' z7 p/ D, E4 t2 {% v$ ^$ ]0 Z - { # w& z- R" ~8 S0 P, ]( k- y5 m
- if(delay_osrunning==1) //OS开始跑了,才执行正常的调度处理
% L% B9 s/ {" p8 O8 n - {7 m' ^) N1 [; X5 @6 K- z
- OSIntEnter(); //进入中断& c4 z( d- Q; O) s, i. s
- OSTimeTick(); //调用ucos的时钟服务程序 - T( G' W% V9 t
- OSIntExit(); //触发任务切换软中断
) p- @# e, G# f3 X - }
M# F5 P5 o7 [ - }. C! u0 Z! S4 X
- #endif5 r6 {2 ]7 d) K' E
- 2 n8 F/ g) M2 O8 ]
- ! R" ^5 D9 a+ @! @
- //初始化延迟函数
. o9 d( X4 W' J7 F - //当使用OS的时候,此函数会初始化OS的时钟节拍* L5 M. J$ ?2 i* ^5 w9 ]% W' U+ b6 k2 Z
- //SYSTICK的时钟固定为HCLK时钟的1/8
9 l& N; U W: ]; ~% N' _ - //SYSCLK:系统时钟
9 T! R8 k% z1 x$ f$ k+ R - void delay_init()) y4 w& @3 N) ~3 I9 Q
- {0 T3 J! J7 G, [) N l M F; S
- #if SYSTEM_SUPPORT_OS //如果需要支持OS.
2 ^: H3 J% G C" G h - u32 reload;
5 _# Q; M! E7 a: t9 e; q: N! L - #endif3 [9 E/ G; D8 a8 l. y, h" o/ H
- SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //选择外部时钟 HCLK/8
1 e3 O7 k! o- L' B, o - fac_us=SystemCoreClock/8000000; //为系统时钟的1/8 , C4 h4 q# \, W4 r4 L9 B3 S( {
- #if SYSTEM_SUPPORT_OS //如果需要支持OS.
! `/ L& q- L- i- w, r( H) h T9 z - reload=SystemCoreClock/8000000; //每秒钟的计数次数 单位为M
: y3 \8 N& @ |) S ? - reload*=1000000/delay_ostickspersec; //根据delay_ostickspersec设定溢出时间
/ C% k: p3 C8 @9 \$ t - //reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右 # {1 J" X' `) I5 m0 P( V' I
- fac_ms=1000/delay_ostickspersec; //代表OS可以延时的最少单位
; n; z3 n& [0 j; y - * `/ M# `% f) x3 c: i7 O7 @2 m$ |% o' U
- SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk; //开启SYSTICK中断1 ~; q# r+ X' v3 ~; _/ [. [
- SysTick->LOAD=reload; //每1/delay_ostickspersec秒中断一次
- S7 H \0 y0 \1 M; d1 z - SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk; //开启SYSTICK
9 r( M8 x) l1 S: S O2 l0 ~
+ R: w% s9 o% d# l. c- #else. t' M6 W9 x6 M0 J6 x* `" b
- fac_ms=(u16)fac_us*1000; //非OS下,代表每个ms需要的systick时钟数
9 {6 |8 J8 l$ b3 X! O7 }8 | - #endif0 [$ D1 C( i7 J& k5 l# ]/ N G
- } ) F, C& W- j: e( t: |
- . X$ T, {# o; n1 A! v# Z
- #if SYSTEM_SUPPORT_OS //如果需要支持OS.' y9 B* X# C* I0 Q
- //延时nus: d+ _) z! R1 L
- //nus为要延时的us数. 9 n+ ?. U/ i6 E! \
- void delay_us(u32 nus)8 o* ^9 a. u; b2 k
- { ' O1 A0 |0 f6 f5 r7 W
- u32 ticks;9 J: }; [% z( B/ Y2 j( e/ V1 p7 b4 y
- u32 told,tnow,tcnt=0;; l w8 z( e/ N4 W2 O* U o
- u32 reload=SysTick->LOAD; //LOAD的值
! }6 j k! A$ Z$ { - ticks=nus*fac_us; //需要的节拍数
3 \' g6 R0 U2 d* Q5 J - tcnt=0;
7 J0 E/ V! ?# q& ^ - delay_osschedlock(); //阻止OS调度,防止打断us延时. E$ R. Y4 Y, S& V
- told=SysTick->VAL; //刚进入时的计数器值
6 U8 G" b2 }. ^3 Y; V; v - while(1)9 N: `+ d8 F- v
- {
' O1 |5 O' C( o# Z8 `" Y T# L - tnow=SysTick->VAL; + ]! r* l2 [5 Y* N) l
- if(tnow!=told)( E% \/ t# z3 }: p+ p1 [: ^
- {
! Q9 }* p, m4 {& ~ - if(tnow<told)tcnt+=told-tnow; //这里注意一下SYSTICK是一个递减的计数器就可以了.
/ T: `0 w1 g2 _ G3 B- G# y+ h3 _# B - else tcnt+=reload-tnow+told; ) {% F- I+ U, U% _0 m* n6 t
- told=tnow;
6 @8 d0 t4 |0 X. H4 Q - if(tcnt>=ticks)break; //时间超过/等于要延迟的时间,则退出.; A2 f. ~* ^) U d
- } ; p: h/ f7 n& q* Z6 A9 d6 i6 `/ X2 I) \) z
- };% T8 L+ r4 P2 `$ Q* A
- delay_osschedunlock(); //恢复OS调度 " c3 U- N: E0 t7 `& r) x" ~
- }# |; \8 L3 M% z% L |0 g8 r5 h( p
- //延时nms
& z' d+ O2 ]; Q0 m" o% G, A$ A r& X - //nms:要延时的ms数7 a4 P$ a0 o" r! ]* w: l3 ?
- void delay_ms(u16 nms)- D+ E: {9 Y+ L% d* G) _
- {
" f+ t: x; c/ ~% c+ u! l1 A - if(delay_osrunning&&delay_osintnesting==0) //如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)
# \3 X* r- i' r1 y - { - Y4 s/ [3 p9 }( O
- if(nms>=fac_ms) //延时的时间大于OS的最少时间周期
7 M4 }1 p% c, O8 G" c! `& M - { 4 Z/ r2 Q% f* U: S$ s* z! Z
- delay_ostimedly(nms/fac_ms); //OS延时
8 ]! I- c: w6 ^2 c* Q( E& A - }' D/ I! e0 a2 b
- nms%=fac_ms; //OS已经无法提供这么小的延时了,采用普通方式延时
7 K" c% n8 a7 k7 ]3 q, ~- [ - }
5 ]2 T: X4 e: C7 U - delay_us((u32)(nms*1000)); //普通方式延时 % {& ~9 Z" \! T2 X) C0 b4 J
- }: \! ^0 P+ @% t/ s6 p
- #else //不用OS时1 l% i( K; G+ W7 x; |; ^
- //延时nus# X' X0 U7 J1 S4 P3 X9 Z
- //nus为要延时的us数. % r1 K" V8 s# J9 S# m' _1 ^4 Z
- void delay_us(u32 nus)
! M$ P7 b; |3 h9 _/ F1 B - {
) w2 c5 ]/ z0 | - u32 temp; 1 P: N: N1 @# H
- SysTick->LOAD=nus*fac_us; //时间加载 & ?2 H4 T( H+ ~7 R
- SysTick->VAL=0x00; //清空计数器/ h9 z3 l, t1 r5 b7 @
- SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 " f. x) K/ X& }, m* p' W
- do, b1 G/ z/ ]5 n- N+ C
- {/ y0 h1 d6 @8 B/ K1 _* m
- temp=SysTick->CTRL;
3 F8 r) D/ d, t: X - }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 7 a. d2 J" T/ v" R# G+ _
- SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器
" K: Z8 ^- O+ t8 D - SysTick->VAL =0X00; //清空计数器 $ p2 N5 b' B* y! W
- }
( F5 F z T, d. ~ - //延时nms
# J0 V$ y# w1 i' p. f! y# Q7 } - //注意nms的范围! n9 G6 r; l, H" x
- //SysTick->LOAD为24位寄存器,所以,最大延时为:
+ g3 N1 Q% ]8 X3 x4 _6 W) f" J% \ - //nms<=0xffffff*8*1000/SYSCLK R' u- w; W% B3 t+ v! h% v+ A1 d
- //SYSCLK单位为Hz,nms单位为ms; D# I l" w- a, I2 q$ d/ D
- //对72M条件下,nms<=1864 5 s% T8 ~+ a% R" W L0 H
- void delay_ms(u16 nms)5 A$ j7 S/ M1 y* e
- { : o' ]% E* y7 ?" r; l+ z
- u32 temp; 5 v I. a# ^$ Q" n% V
- SysTick->LOAD=(u32)nms*fac_ms; //时间加载(SysTick->LOAD为24bit)9 U4 [/ T0 q1 W m
- SysTick->VAL =0x00; //清空计数器- c9 l% a( O/ |
- SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ; //开始倒数 k1 s+ r6 U9 t8 `# @1 i$ Z
- do. M: |3 U* M/ j) \5 A- |* G
- { E* T) w1 C+ q! p3 X
- temp=SysTick->CTRL;
- a+ @7 S" m" ]$ o" c- d - }while((temp&0x01)&&!(temp&(1<<16))); //等待时间到达 ( v. M$ b1 Z" g0 f& {0 x6 s
- SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk; //关闭计数器! Y+ ]9 _0 \$ t9 p
- SysTick->VAL =0X00; //清空计数器
9 m9 l8 |4 n! |; o7 W- B - }
# S& n) y/ r6 P7 s - #endif
复制代码 & n ?, ~" c: h% }* V! ~
这里需要注意的是我们使用了外部时钟即系统时钟的8分频,这样确保时间足够长。另外,毫秒延时函数有最大上限,最大为1864(72MHz时钟频率)。用这种方式来延时的好处是不用中断比较简单,但是坏处就是最大延时时长有限(不到2秒),所以当需要长延时时间的时候要多写几个delay函数。; f3 T- d# o3 r8 E
) ~' P' r( r) S4 Q- o
; M6 G6 [5 n" S
总结
8 l& L$ ^2 B& Y5 v& T/ x 本节主要对STM32时钟系统进行讲解,我们从原理框图入手,对每一个时钟线路进行来源和去向的分析,介绍了时钟配置的相关寄存器以及STM32时钟的软件配置,最后通过SysTick定时器来编写延时函数。STM32的时钟十分复杂,希望读者能够反复阅读,内化于心,这样在以后的STM32开发以至于其它高性能SOC的学习中才能游刃有余。
5 A- h+ r- l( i; m3 y" K# [
8 f* B( I+ e! W# m& S" l2 [. {3 E
/ `, k8 _% g: d. v1 z7 i+ |: w$ ?* y+ s7 i
|