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