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