你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】STM32-时钟系统详解

[复制链接]
STMCU小助手 发布时间:2022-5-7 11:31
前言
' 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
`I0V5F~`8F5)2ECY1SP{}ME.png ) C! R- \& k$ _0 D
- c3 r* _+ @+ l
可以看到,官方手册提供的时钟框图很复杂,这里我给出一个简化版的时钟框图:' l; V* t% {* }$ U9 e
* w* g0 r, n( _+ a; Q, G
ZWZ~EG~98NEH[NH61CMO5.png
, 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
  1. static void SetSysClock(void)
    : T/ V' Y6 V8 v  _9 C
  2. {3 R. j: h# F$ C# L5 B) R
  3. #ifdef SYSCLK_FREQ_HSE( T+ L' R- J+ g( P: s. v: N
  4.   SetSysClockToHSE();
    & F' w$ u/ a5 ^) S* F
  5. #elif defined SYSCLK_FREQ_24MHz
    & K/ y+ E. ^. k  n3 {- i
  6.   SetSysClockTo24();( ~/ ~: O  E& e: j4 J) X7 a' f
  7. #elif defined SYSCLK_FREQ_36MHz
    9 r/ t% V+ O4 S3 R0 {( r
  8.   SetSysClockTo36();
    1 I& W; q. R: e2 s( h8 }
  9. #elif defined SYSCLK_FREQ_48MHz4 J: Y0 o$ h. ?0 L9 J, g9 j& u) _
  10.   SetSysClockTo48();7 @9 F5 v/ c; K' i7 g) l" v
  11. #elif defined SYSCLK_FREQ_56MHz% a! {: M8 ]0 r; K: a5 G# m
  12.   SetSysClockTo56();  $ i( ?$ v4 f+ X" Q6 w
  13. #elif defined SYSCLK_FREQ_72MHz# K: B) j7 K. x3 Z- ~6 ~
  14.   SetSysClockTo72();
    ( l/ J# m# P2 R4 v, J
  15. #endif. F, z% j) n7 d2 t8 l8 q. p2 I
  16. }
复制代码
- |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
  1. #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
  1.   #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/ }
  1. #ifdef SYSCLK_FREQ_HSE; W/ J8 }3 P) o3 U1 v# h' ?& x
  2.   uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;      
    / Q/ @& y9 k5 g1 u
  3. #elif defined SYSCLK_FREQ_24MHz8 g" C& d' e! j, o) l+ }
  4.   uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        
    8 ^8 I7 T2 w9 I* K1 D
  5. #elif defined SYSCLK_FREQ_36MHz
    ! C/ K6 K6 l1 w
  6. #elif defined SYSCLK_FREQ_48MHz! Y7 A6 d9 J, v3 ?+ X
  7.   uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;      5 G! I) T& Z8 F  V+ ~' d( y
  8. #elif defined SYSCLK_FREQ_56MHz( H1 ~4 C1 G; T$ `6 i* Z
  9.   uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        - H$ |8 x, V8 a/ P# z
  10. #elif defined SYSCLK_FREQ_72MHz
    / W' F6 g4 G0 h$ x# U
  11.   uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;       4 a0 j* Y* b( {3 A' r1 |2 X5 o
  12. #else 4 K3 L8 T. X5 Y0 j
  13.   uint32_t SystemCoreClock         = HSI_VALUE;      
    $ e3 Q$ R) |- P# H, G! Z
  14. #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 W9}BM@EKI($@YHBW3%W592R.png
+ l* e5 j" c& G; @( W* o% w. ~+ h9 s; G/ w+ O9 {+ `: R- E
JMVM6BU0748@EI_DUI1]V(F.png 8 F5 q2 I" B& [: c# @7 j6 e) j: `( F5 x

1 O1 E; g/ `; @( w V{985U[](IOP)~H_`QL1TTP.png 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 (0D2`W~IS8UO]D9C@8PWP04.png ! w4 p) W2 o* L# t

- ?$ g( x! {: c6 q3 ] BCFEDP_O6$XFDA`_5WK_7F7.png
* F" ^* {+ W: C1 D; ~6 j/ {! I: X# e- w8 A
SM11[L]S[T39YDLHL5O)NIX.png
  P( {3 h3 A0 G' {6 v9 \( ^( B
6 q" f8 A6 V# V! v X`[FB)R4S33D_W$GG]_1%(G.png
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 ?
  1. ; Reset handler1 L; ]1 U: v+ {3 ]
  2. Reset_Handler   PROC& d$ E% E7 Y, G6 ]# T2 M" Z
  3.                 EXPORT  Reset_Handler             [WEAK]
    * S+ I9 {6 _; u/ E5 w1 X% J
  4.                 IMPORT  __main
    8 c/ V5 j9 l8 q7 s( x- S
  5.                 IMPORT  SystemInit% h8 i, a% i: t% s
  6.                 LDR     R0, =SystemInit
    9 o& i2 a/ c) ]6 r) \# ]& ]
  7.                 BLX     R0               
    # H* K5 c  S5 |8 p: c6 M, M
  8.                 LDR     R0, =__main  o8 m6 `8 p; i# u( X
  9.                 BX      R0, _' E% w8 P" l0 F8 g
  10.                 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
  1. void SystemInit (void)7 i/ F6 D4 v. e5 O! N/ I" s
  2. {
    , @$ {4 Q7 y8 V
  3.   /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
    8 g$ A4 q( m! a. `# l
  4.   /* Set HSION bit */
    7 a; R8 T- X% L
  5.   RCC->CR |= (uint32_t)0x00000001;- s4 ~8 U  C' p6 e& A
  6.     SetSysClock();$ T/ c9 p: k- g& u! M3 F" n
  7. }
复制代码
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
  1. static void SetSysClock(void)5 V2 f+ Y9 T/ c3 G' [! x- j
  2. {
    1 L$ {, u" o% h  s/ K
  3. #ifdef SYSCLK_FREQ_HSE
    & F+ c7 ^; ?5 }0 E( `/ z
  4.   SetSysClockToHSE();
    1 |* d# B  l. U" W7 p
  5. #elif defined SYSCLK_FREQ_24MHz
    8 O6 N4 W. D7 s
  6.   SetSysClockTo24();
    # p8 ?3 b; q3 V% }
  7. #elif defined SYSCLK_FREQ_36MHz
    % h+ @' d" V7 I: ^7 _- m7 H
  8.   SetSysClockTo36();" ^/ ^! k$ h' n' w
  9. #elif defined SYSCLK_FREQ_48MHz
    ' D' t) c$ Z9 n/ f1 G) s7 z
  10.   SetSysClockTo48();4 r; I3 R( v$ c
  11. #elif defined SYSCLK_FREQ_56MHz  C( y5 U6 P" o3 A4 N; R7 F
  12.   SetSysClockTo56();  
    5 ?. O) a6 X- p2 d4 R9 B% t; ~
  13. #elif defined SYSCLK_FREQ_72MHz
    3 r# j3 y1 ^9 ?% {
  14.   SetSysClockTo72();
    1 x0 E% i+ V% C' u
  15. #endif
    ) g1 W' G  s. {. K7 ~

  16. / r$ p) E1 \2 m0 n: i
  17. /* If none of the define above is enabled, the HSI is used as System clock
    - E( z8 a6 y8 |' S  B7 l) ?: x
  18.     source (default after reset) */
    , {( d4 S8 f) f% B/ |
  19. }
复制代码
+ 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
  1. static void SetSysClockTo72(void)
    7 S5 K- U& c7 F- K
  2. {
    4 x) ]% ^! ~; p6 D% P: D- p
  3.   __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
    $ i8 D* G- C6 W! l3 v$ J/ l
  4. # p5 X/ Y  X, e# \( @& [5 M
  5.   /* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/   
    - L! H# U% w% Q6 F/ v; W/ i1 Z2 M5 p  X1 C
  6.   /* 使能 HSE */   
    4 m$ ^( P5 Q# B: ^  g5 I
  7.   RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE8 b' }) x, I( S! f; p0 `5 o5 a: j

  8. , t7 j/ n! b- d
  9.   /* 等待HSE就绪 超时退出 *// e$ j: E6 t* M1 r# d4 A7 D9 Z1 {
  10.   do) I% a3 j' {. k/ D
  11.   {' r( K7 m0 z' {
  12.     HSEStatus = RCC->CR & RCC_CR_HSERDY;  //读取RCC_CR第17位
    * g/ w" i0 @( P7 X& R& ~
  13.     StartUpCounter++;  9 Z- Z7 d/ G* G3 p9 @& {6 t
  14.   } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));5 d- I+ n6 f- `& `

  15. 4 H8 n/ Y4 p- n
  16.   if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位! m6 C8 V/ y) R; n9 M7 O* d9 d
  17.   {
    9 C  I% }$ r0 n; O
  18.     HSEStatus = (uint32_t)0x01;   //HSE状态写1
    0 q8 O) }  o; c  Z4 c
  19.   }' ~* u' z! H  d  s" m! g
  20.   else0 [% A, d( `) O+ M# A* `
  21.   {
    : H0 _- Q- k( X* m5 ?; C
  22.     HSEStatus = (uint32_t)0x00;   //HSE状态写0
    ; o! |* S* M1 ]# F% t: e5 y0 R
  23.   }  
    % H# H3 x* |5 J  v  b
  24. 8 M7 A" K7 A5 s
  25.   if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管& p; Z+ W( [& b/ p
  26.   {
    2 [1 o) `; l( q, ~, v- q9 \
  27.     /* 使能 Prefetch Buffer */$ w+ _- z# y5 e+ t3 \4 ^1 s: N: y
  28.     FLASH->ACR |= FLASH_ACR_PRFTBE;% ^6 W. L( w( P! x$ e2 U
  29. + W; J1 N) E  f. s+ U/ e6 U
  30.     /* Flash 2 wait state */
    ' M6 W" L% s7 p7 J1 v2 k
  31.     FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);1 Q2 J! s: r  k6 E" J" l
  32.     FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    & y) P5 Q' Y( Y! W" N3 J: k" U

  33. 3 d' I8 L7 t  H/ g

  34. 8 ?# X, T; G! \) _* H) A
  35.     /* HCLK = SYSCLK */# u. O5 N" G9 K( W, t
  36.     RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;  //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟
    . i' A/ d- y- n* Z3 D: x
  37. ; N  L9 r9 J% K6 R! j6 c  P
  38.     /* PCLK2 = HCLK */- X& i1 F9 k) i- \) a$ E
  39.     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
    ) V- @0 N5 I/ G2 `/ y( r
  40. # x. x4 V( Y1 N6 i* |( |( l/ T) o
  41.     /* PCLK1 = HCLK/2 */* O7 E$ H& a. k: u3 b5 W
  42.     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟2 C. i9 z5 ?4 o8 w
  43. & e! c, d# `9 f* i  w
  44. #else    ; {  Y* [( {$ P6 y; ^; ~8 b
  45.     /*  PLL 配置: PLLCLK = HSE * 9 = 72 MHz */) E, C1 l9 r" z1 ?# p
  46.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
    0 v  g- K7 q" ^  z( L8 X8 ?9 |
  47.                                         RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零
    ) B9 ]1 v+ T+ \5 y2 f
  48.     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 [
  49. #endif /* STM32F10X_CL */
    3 F/ O4 T% E4 i" d- f

  50. 9 w# d: }( a7 ?$ C9 z- u. @
  51.     /* 使能 PLL */
    ) \* Q5 H: W* |$ Z3 l( c, [
  52.     RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL/ [4 z. c* l* Q, [6 p' e; V% P: A% S7 n% N

  53.   M! f4 ~4 h( E2 Q* Y# X7 n+ a) w
  54.     /* 等待PLL就绪 */* i) T, t9 j; N  {) P1 Q
  55.     while((RCC->CR & RCC_CR_PLLRDY) == 0)  //读取PLL时钟就绪标志位(第25位)  Q3 X" x- A$ H5 f1 {1 q
  56.     {
    + o0 m. i; u2 q; u
  57.     }; o) Q% W8 r7 ~4 R+ @

  58. 0 C; p% q& V* `* L0 D
  59.     /* 选择PLL作为系统时钟源 */
    . b) M5 U) T7 L: P& m% ?7 F1 w
  60.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零- v+ M1 v( r1 t9 `; ]' Y
  61.     RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟0 `# ]7 x) N# |/ q8 ?) X

  62. % r6 @/ W3 |" i( t
  63.     /* 等待PLL被用作系统时钟源就绪 */
    6 t- _* a/ x/ s9 d& H; e
  64.     while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪
    ; ?* m% b$ x- O. b
  65.     {( D+ \( u9 r/ U0 O" y
  66.     }# S9 Z& i2 J3 A- g% K
  67.   }
    ) i  M  a. \, m5 i# s4 F* o: J- y
  68.   else1 j+ R2 U! ~" B: C1 Z2 A
  69.   { /* If HSE fails to start-up, the application will have wrong clock
    . d6 k* p/ n9 v: M  T. b
  70.          configuration. User can add here some code to deal with this error */
    1 z. {" Y* L+ q- ?  ?$ m8 m5 |
  71.   }
    9 r9 ?9 l7 w4 f: j! V" M! B
  72. }
    , h+ P( P/ |* w
  73. #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 ?
%OTG9{34YZE69O)CZ(1M`65.png ; f  S1 x0 W6 A9 E& u
* u8 J  B: f7 D5 R+ h4 G, I
LSDP4`]K2IT)I[AZRM]2K.png
2 h6 l1 A8 j# O* x9 @7 \3 \9 E# z( S; d1 d
({74TXQGSPLIP3Y@2CSGK~O.png " N9 Q/ q- X  I- f* ~4 ~
( P- g' X7 m2 A; t9 o/ ~8 K
12H`7TR4T5LG{([PNKJD`XV.png ) 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
  1. void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)5 o# X& f' e# ^, _
  2. {+ t- i" b6 G2 _% O9 @
  3.   /* Check the parameters */
    ) s3 i9 \  w! n' s, z: y; y5 W& `
  4.   assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
    2 E, Y8 \: M( S! ?3 L' V2 R" e
  5.   if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
      z: r7 C) w2 N" u
  6.   {; _) T+ s' j- z4 p3 n
  7.     SysTick->CTRL |= SysTick_CLKSource_HCLK;
    9 ]& \  D2 m; `; h( y  K1 a! Z% k
  8.   }
    & |! `( Y! {- A1 o4 j
  9.   else- ?3 O. k/ T  O
  10.   {6 N; a4 c1 \- d4 }0 b5 `! c
  11.     SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;' l, A5 ?3 d- H+ v8 w. ~, U
  12.   }  O) F( R$ Z: K; m+ s! _
  13. }
复制代码

& 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
  1. static __INLINE uint32_t SysTick_Config(uint32_t ticks)5 d) v+ ~3 c( w+ o/ s1 q$ T
  2. {
    ! L! C$ O/ c4 f9 n* _
  3.   if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */+ w2 U" p4 K) R  U3 c

  4. + a. v) ]5 r/ G& M9 u$ G. ~1 v( ^1 {
  5.   SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
    ( S. N3 G; s/ ?7 L! M& [
  6.   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
  7.   SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */1 Q3 _, u2 i( G1 b
  8.   SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
    ; t+ e4 [$ z( |* T1 ?) Y
  9.                    SysTick_CTRL_TICKINT_Msk   | : e/ a( O7 e. ^. ~& E  j  F3 A
  10.                    SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
    / y, M* \7 |8 T( Z4 f4 V
  11.   return (0);                                                  /* Function successful */& [$ S; Z4 _+ {9 T" ?4 R8 O8 F6 k
  12. }
复制代码

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
  1. static __IO uint32_t TimingDelay;
    ) M! {2 d& x: d$ }! @; I9 \
  2. void Delay(__IO uint32_t nTime)
    ( j, z- P+ \8 U  T9 i5 S0 h
  3. {
    : |6 ~$ z9 ?5 ~! T3 q2 F
  4.    TimingDelay = nTime;8 X. @! n: I+ i4 b7 q! ]1 h
  5.    while(TimingDelay != 0);
    - n% l$ w8 p9 p* }6 D- u, W9 [- a+ m
  6. }1 S7 s3 ^$ C  O! Y
  7. void SysTick_Handler(void)$ X$ w' E8 g  b8 K/ \( C' ^
  8. {* F4 x0 F( x$ h; h: D* X& A9 _
  9.     if (TimingDelay != 0x00)
    8 ?# ?2 Q9 d) N  e
  10.      {
    9 p/ t2 d3 P' h( i5 S
  11.        TimingDelay--;( n4 p, ~" _, K: c: r! U
  12.      }
    6 i+ W; b) E: t
  13. }
    , c" a( x2 s3 i1 e; k- U$ }
  14. int main(void)* {2 T7 t5 R$ P4 B/ {/ }
  15. {  …
    0 X) a8 \, J6 Q, I% q3 {+ I
  16.     if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms
    # N% _( ?! K) O2 p5 I3 w# X6 _
  17.      {# s0 I9 ]" V& ^2 s* {; c2 u
  18.      while (1);
    , u& D3 ?/ r' o7 s! c9 U
  19.      }: s; q7 v  P9 b
  20.     while(1)* k" N7 h  v0 K" H' _9 O
  21.      { Delay(200);//2ms6 f2 F7 s: W1 B' f1 j4 n6 h/ b
  22.      … ' O1 t) ~6 j, r. T- V' T8 x4 c( q
  23.      }/ J9 O/ h% v2 {5 u
  24. }
复制代码
" 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
  1. #include "delay.h"
    7 @, g2 J0 R2 a2 L! I; V
  2. //         
      T. }% |- B& T" [( l& F5 B( O
  3. //如果需要使用OS,则包括下面的头文件即可.: }! }# ~% o' r
  4. #if SYSTEM_SUPPORT_OS
    ' M) J0 w/ Q1 h
  5. #include "includes.h"                                        //ucos 使用         
    / w: T6 b" h1 J4 x( h0 ^
  6. #endif+ Q/ |0 ~8 W+ @% ~
  7. //          ' i1 e. U- F# r1 `
  8. $ {- b8 T$ h: S
  9. static u8  fac_us=0;                                                        //us延时倍乘数                           9 u' A% w. Y# z! ^5 o, h7 w
  10. static u16 fac_ms=0;                                                        //ms延时倍乘数,在ucos下,代表每个节拍的ms数6 [+ P9 X7 a0 E% G
  11.         
    . L6 D) |8 R$ c, q6 [5 {. [8 W- A
  12.         
    9 u1 D. f# l9 ~5 q5 C- I7 J) I
  13. #if SYSTEM_SUPPORT_OS                                                        //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
    ) H8 k! v( U0 P4 U
  14. //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
      ^8 F5 l. m/ u& {2 H' K8 \6 h: `
  15. //首先是3个宏定义:
    - w  t) z. q" Z* r2 c; p( ^
  16. //    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数* Q# e3 T- M, `2 h0 f
  17. //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick8 ^9 X" A9 [2 J: u# q  \
  18. // delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行+ o7 Z& ~0 D: l9 t& \% Y$ B- V
  19. //然后是3个函数:
    + R# ^' q2 [' H9 X! {% L  }  s, K) \
  20. //  delay_osschedlock:用于锁定OS任务调度,禁止调度' g: r4 [9 R; z0 f: a7 D
  21. //delay_osschedunlock:用于解锁OS任务调度,重新开启调度/ ^( \! f: {) ]* U( D# V0 F# y
  22. //    delay_ostimedly:用于OS延时,可以引起任务调度.* t" R7 ]8 [5 j" l: u, V

  23. 2 y4 Q6 M5 @0 l# W( V2 {  a
  24. //本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植. l, \& G% g; A/ T
  25. //支持UCOSII
    2 o: D8 G, u' }+ L9 b& j) b
  26. #ifdef         OS_CRITICAL_METHOD                                                //OS_CRITICAL_METHOD定义了,说明要支持UCOSII                                
    " W" k7 n; Q5 F" v+ Y' v3 p
  27. #define delay_osrunning                OSRunning                        //OS是否运行标记,0,不运行;1,在运行
    ; x" D* k% P+ t; _
  28. #define delay_ostickspersec        OS_TICKS_PER_SEC        //OS时钟节拍,即每秒调度次数7 `4 x; _. l- u# I
  29. #define delay_osintnesting         OSIntNesting                //中断嵌套级别,即中断嵌套次数' n# N) _) m+ P5 R: p* q, \6 |- b
  30. #endif6 N- Y; u  n8 J0 l" m/ h8 f

  31. ( l  Z* Y( I/ d& _3 R( D4 Y  f9 s
  32. //支持UCOSIII3 `* D& ~; @4 S( e' K' k( }9 L
  33. #ifdef         CPU_CFG_CRITICAL_METHOD                                        //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII        6 o" H4 b) ?( c$ @0 H/ ^
  34. #define delay_osrunning                OSRunning                        //OS是否运行标记,0,不运行;1,在运行' B4 S" W, s/ _" S% m% K
  35. #define delay_ostickspersec        OSCfg_TickRate_Hz        //OS时钟节拍,即每秒调度次数
    1 }4 ^6 l0 w+ _4 o! M
  36. #define delay_osintnesting         OSIntNestingCtr                //中断嵌套级别,即中断嵌套次数
    # ^6 e/ j" Z9 v7 r! e3 \
  37. #endif
    ; H. @# K2 b: X' E

  38. - W  n6 i7 ^0 A$ R* e( @) F6 x

  39. ' n+ k* v5 _0 W$ L
  40. //us级延时时,关闭任务调度(防止打断us级延迟)
    & F" E5 \' T9 b/ d+ }
  41. void delay_osschedlock(void)
    / E7 _& y% T/ c' {2 p
  42. {! t' }$ b& Z! v9 a7 `
  43. #ifdef CPU_CFG_CRITICAL_METHOD                                   //使用UCOSIII
      g- I: U5 V; F. L1 G
  44.         OS_ERR err;
    4 T3 w- c  F2 d
  45.         OSSchedLock(&err);                                                        //UCOSIII的方式,禁止调度,防止打断us延时
    1 q" [* x  [6 l% @
  46. #else                                                                                        //否则UCOSII
    ; E! b( L! d0 v% ~, `- @
  47.         OSSchedLock();                                                                //UCOSII的方式,禁止调度,防止打断us延时
    8 a: |7 r( D) [3 p, @. M9 [! L
  48. #endif
    ( j$ |/ {6 ^3 J/ l- a
  49. }- b7 u; I0 Y8 g

  50. 4 K- y5 I/ V6 p% R& ]" j  F
  51. //us级延时时,恢复任务调度! {, A4 E9 l7 w$ n
  52. void delay_osschedunlock(void)
    ( l1 B8 B( K! y* S
  53. {        
    3 F( t* I# R" o
  54. #ifdef CPU_CFG_CRITICAL_METHOD                                   //使用UCOSIII
    9 r7 A8 e6 o$ d2 z' m
  55.         OS_ERR err;
      m4 H; k- e* x( P0 Y
  56.         OSSchedUnlock(&err);                                                //UCOSIII的方式,恢复调度
    / _  T% G% ?' m, B  H( G3 V( Z
  57. #else                                                                                        //否则UCOSII
    8 R6 g, |; b3 n) s. D$ Q4 I- Q
  58.         OSSchedUnlock();                                                        //UCOSII的方式,恢复调度7 r2 O% @. j7 k) j. ?( a1 z. Y
  59. #endif
    - }4 |, v* B# A' S. R) D
  60. }
    % n7 _# j7 I2 n! Z

  61. % d- N: Q3 @8 i" o4 q
  62. //调用OS自带的延时函数延时
    5 K. G& N- k& a: y8 v6 U+ _
  63. //ticks:延时的节拍数- s0 X, V, y; S" N0 a5 l
  64. void delay_ostimedly(u32 ticks)
    3 e# A* z6 l( {0 i% M) T8 G
  65. {
    ( ~0 w2 m. |9 M9 h/ i; V+ n
  66. #ifdef CPU_CFG_CRITICAL_METHOD. e1 P  i, T# K5 D- m# Z$ c
  67.         OS_ERR err; / q/ [- ~3 Y4 y6 h+ m7 F
  68.         OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);        //UCOSIII延时采用周期模式
    2 B0 _' F* j, g' \
  69. #else
    5 P: r0 F5 z- }5 @3 Y( x/ {
  70.         OSTimeDly(ticks);                                                        //UCOSII延时) Z' v8 B! U- t3 O6 K0 G: L4 B( {, J
  71. #endif
    * q! i# f& b/ J( L/ g
  72. }: [' s+ C3 i* ^2 E. G# \6 h

  73. ; v& g+ w! x* T* L
  74. //systick中断服务函数,使用ucos时用到
    - T5 v+ Z3 Y8 ^
  75. void SysTick_Handler(void)1 x# o8 Q" W( j
  76. {        * ?3 k! x9 o% L3 r( M- K3 Q1 S! e
  77.         if(delay_osrunning==1)                                                //OS开始跑了,才执行正常的调度处理
    ( p9 u% N6 v5 ]- I
  78.         {' Y/ u: j8 h2 o8 s- ]1 m
  79.                 OSIntEnter();                                                        //进入中断4 ~3 k9 N2 b" T2 B+ T/ L' O' D1 C
  80.                 OSTimeTick();                                               //调用ucos的时钟服务程序               4 g4 Z9 f8 I. f5 q& {
  81.                 OSIntExit();                                                        //触发任务切换软中断) p$ o. E7 |" b0 k+ ?/ F
  82.         }
    " e) b8 k* F6 R7 T4 v  g
  83. }
    & W/ ]/ c" M+ Q; y# W  I1 I
  84. #endif9 S  s7 t" q* j

  85. % a$ u# n; d$ F. g# a
  86.                            4 o  q4 R* _/ E4 S
  87. //初始化延迟函数% O! A8 z6 i9 P6 c: q
  88. //当使用OS的时候,此函数会初始化OS的时钟节拍- J# O" w$ ?6 J2 T
  89. //SYSTICK的时钟固定为HCLK时钟的1/8
    $ V0 V! W! U/ H  ?6 Q
  90. //SYSCLK:系统时钟
    6 h# M! g' X# K( Q
  91. void delay_init()
    1 z, i. ~+ ^7 |& |) y, ?8 X0 C" |
  92. {. \  c7 t9 c7 d/ Q0 e
  93. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.2 A; _4 {$ N/ e4 M  Z; w8 L; y0 P
  94.         u32 reload;
    8 [3 t# G# E" O. w
  95. #endif- P7 p5 F* @! C3 k3 x
  96.         SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);        //选择外部时钟  HCLK/8( Z5 Y3 J3 t( l
  97.         fac_us=SystemCoreClock/8000000;                                //为系统时钟的1/8  * G3 m. O! f5 Q- z: j
  98. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.: e! \9 X" s, M- S/ }, ?& ~, a9 `
  99.         reload=SystemCoreClock/8000000;                                //每秒钟的计数次数 单位为M  7 z7 e. C7 g! u4 V
  100.         reload*=1000000/delay_ostickspersec;                //根据delay_ostickspersec设定溢出时间
    ' `6 y# }! N' ]( s. X$ \) k
  101.                                                                                                 //reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右        ' O/ [/ ^; [2 w
  102.         fac_ms=1000/delay_ostickspersec;                        //代表OS可以延时的最少单位           : z3 E% ?) ^/ D, k  B

  103. 3 o& f! W$ U( y. S
  104.         SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;           //开启SYSTICK中断
    7 P3 j* Q+ _; b! |/ ~7 n
  105.         SysTick->LOAD=reload;                                                 //每1/delay_ostickspersec秒中断一次        
    ; E) }# [- M/ _. A
  106.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;           //开启SYSTICK   
    ! b( w) k2 |: _7 C$ u5 @# ~

  107. ) c: p! }; y4 g: W1 o4 I5 x
  108. #else! h+ c' N; l  k$ J% c
  109.         fac_ms=(u16)fac_us*1000;                                        //非OS下,代表每个ms需要的systick时钟数     l$ c/ z( ^# M+ ~: y9 _) e
  110. #endif) Z4 J- k$ ^' i2 P4 N& Q9 Y
  111. }                                                                    
    2 |) N/ \$ Y5 x2 v

  112. , T8 H1 E) i. {& o: p4 A
  113. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.
    8 j; G$ k& c: K( J; C4 U
  114. //延时nus
    # p4 J: N# I2 X# g* H, e' p: m8 }- g
  115. //nus为要延时的us数.                                                                                       # M7 G) B8 Z# }1 n: N
  116. void delay_us(u32 nus)
    " o: V$ f" O7 F. j
  117. {               
    ( v& k/ C+ W+ }& H
  118.         u32 ticks;# {/ h1 _  s9 q+ y: s
  119.         u32 told,tnow,tcnt=0;
    7 R. L" q8 S  c+ D7 o
  120.         u32 reload=SysTick->LOAD;                                        //LOAD的值                     
    . o# ]8 O6 c& q$ x/ s2 Q+ }. g
  121.         ticks=nus*fac_us;                                                         //需要的节拍数                           
    $ z! w7 \6 _% Y# w* I" s. z& W2 F
  122.         tcnt=0;
    + V7 d& x4 c( I1 K- d1 z
  123.         delay_osschedlock();                                                //阻止OS调度,防止打断us延时, C$ W; a( n' J$ u
  124.         told=SysTick->VAL;                                                //刚进入时的计数器值, a( P9 @$ i5 y' @
  125.         while(1)
    6 w- X6 E; ]  t
  126.         {
    1 q6 x& j) R8 ]! p8 {+ ^  `
  127.                 tnow=SysTick->VAL;        
    * k* R- B; I$ e# y  A6 `. @
  128.                 if(tnow!=told)
    9 o6 u* B9 R0 S, L/ ~
  129.                 {            9 H) T3 t5 G5 x' X: j! |3 u/ |) \0 y
  130.                         if(tnow<told)tcnt+=told-tnow;                //这里注意一下SYSTICK是一个递减的计数器就可以了.7 d$ S/ W+ A. T7 S$ S6 R  h2 R
  131.                         else tcnt+=reload-tnow+told;            $ B5 \/ |7 Q% I! a2 t) \' u
  132.                         told=tnow;5 g. _2 S1 s3 t+ q. Z/ O$ B
  133.                         if(tcnt>=ticks)break;                                //时间超过/等于要延迟的时间,则退出.( N! l3 ]1 z) z
  134.                 }  
    ! n7 `4 X  m. \' C: _$ `% \, Z
  135.         };! z/ u9 S5 j$ `/ `+ ~9 m
  136.         delay_osschedunlock();                                                //恢复OS调度                                                                            / V* c; a* ~# D, c  @7 s- c% E
  137. }4 u; e% R' g. t
  138. //延时nms
    4 a0 u  }) t) D( A" Z' ?
  139. //nms:要延时的ms数
    " n8 c7 A6 h7 |* W% d3 U
  140. void delay_ms(u16 nms)* x3 @8 W! H4 H" t1 i
  141. {        
    / q1 \4 r% f0 c6 f* I0 y" g
  142.         if(delay_osrunning&&delay_osintnesting==0)        //如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)            # W- m. x0 J' U( b; l
  143.         {                 
    9 U" \# `3 B* \2 T+ K
  144.                 if(nms>=fac_ms)                                                        //延时的时间大于OS的最少时间周期 8 H% Z9 F$ @% s; n5 I: F
  145.                 { 4 W, @. d& K. P, Q
  146.                            delay_ostimedly(nms/fac_ms);                //OS延时% [; n' G  r) X2 [: _
  147.                 }
    ( R7 B3 o) j7 c9 n5 t! R* Z
  148.                 nms%=fac_ms;                                                        //OS已经无法提供这么小的延时了,采用普通方式延时    " X9 Y7 X1 y& Y1 |& k, T! \
  149.         }
    + V( \) a! g$ M; ]- z
  150.         delay_us((u32)(nms*1000));                                        //普通方式延时  . n# j1 J$ g# Z  y
  151. }* l, t8 O5 m/ U" k
  152. #else //不用OS时
    " W. E* f, `! N5 z6 J6 C
  153. //延时nus
    . u0 @! u3 e$ K" O+ o+ v
  154. //nus为要延时的us数.                                                                                       ) O% o6 m- K5 B$ i5 b* r; Z
  155. void delay_us(u32 nus)" k) m9 _. E. V5 {2 V
  156. {                " V8 x# _8 A0 ^* ^! B
  157.         u32 temp;                     
    % |7 `$ K2 a3 ]1 M: ]
  158.         SysTick->LOAD=nus*fac_us;                                         //时间加载                           
    3 `: g& a+ n8 k# n6 H$ ~& _
  159.         SysTick->VAL=0x00;                                                //清空计数器
    / }( k: d, j# V
  160.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数          - G" O5 u5 t& N8 q4 j
  161.         do4 A, U! s% g0 i" e
  162.         {- o& t1 L" j# u# z
  163.                 temp=SysTick->CTRL;& Q* L7 n/ P5 |
  164.         }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   - C5 g' n  |! Q& i* F. k
  165.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器0 d' y6 `4 R' L6 D5 I
  166.         SysTick->VAL =0X00;                                               //清空计数器         
    - i9 O: d: U4 ]1 y
  167. }+ ?; _+ ?' G  p" r  f$ T5 O
  168. //延时nms
    % `2 `" Y* R/ y, j
  169. //注意nms的范围, V0 K) r4 W" l8 p( h
  170. //SysTick->LOAD为24位寄存器,所以,最大延时为:
    ! a- K3 g% q; c8 m4 d& W0 r- [
  171. //nms<=0xffffff*8*1000/SYSCLK  k% i. S# r' N* b, K
  172. //SYSCLK单位为Hz,nms单位为ms7 \+ ]1 `1 B7 U9 W3 H
  173. //对72M条件下,nms<=1864 8 R% ^. j  B$ h1 D" g% b
  174. void delay_ms(u16 nms)/ y: P0 M+ F$ W% @8 x
  175. {                                    
    ) X$ _' ?1 Q+ r
  176.         u32 temp;                  
    ; w: e2 G  X2 S5 n: j4 V
  177.         SysTick->LOAD=(u32)nms*fac_ms;                                //时间加载(SysTick->LOAD为24bit)- M5 s" D! F, ~+ g
  178.         SysTick->VAL =0x00;                                                        //清空计数器
    & s/ w2 E3 {7 ~
  179.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数  ( C, s6 e4 [. O, e2 W5 D
  180.         do+ y5 s3 R. b' c2 G4 ?! m+ p
  181.         {& q4 V# k: F1 F5 [% r8 S, D- K4 P
  182.                 temp=SysTick->CTRL;+ W( s; w" c- R+ g  C
  183.         }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   0 ~5 H4 [8 u9 }* w1 M
  184.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器
    ( d- L& R% \1 m2 d- I
  185.         SysTick->VAL =0X00;                                               //清空计数器                     
    . ~3 x% o; Y. m) i
  186. } 2 b1 M' C9 J4 k/ N2 t
  187. #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
收藏 评论0 发布时间:2022-5-7 11:31

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版