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

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

[复制链接]
STMCU小助手 发布时间:2022-5-7 11:31
前言' 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
`I0V5F~`8F5)2ECY1SP{}ME.png 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
ZWZ~EG~98NEH[NH61CMO5.png
/ 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
  1. static void SetSysClock(void)
      a, S" x( j7 M
  2. {
      B) P: s, D( F, g/ T: d4 _. n
  3. #ifdef SYSCLK_FREQ_HSE: ^, Y8 Q) @: t4 b4 D) m+ F9 I7 t
  4.   SetSysClockToHSE();
    2 C& e/ _7 ?' Q$ Q: ~( B
  5. #elif defined SYSCLK_FREQ_24MHz. U/ k6 ~; }# z
  6.   SetSysClockTo24();
    9 y1 M# W3 T- ?6 |: J
  7. #elif defined SYSCLK_FREQ_36MHz
    5 F4 U( J: Q0 X; O- r; {3 g
  8.   SetSysClockTo36();( b$ a% C# G; ]) U) T, h4 s
  9. #elif defined SYSCLK_FREQ_48MHz
    ' H, k, h+ Y- |: T0 a; E2 O" P4 j+ G
  10.   SetSysClockTo48();
    ' F* W* a  V- k& {7 E& c3 D
  11. #elif defined SYSCLK_FREQ_56MHz7 W0 b1 P# H3 ~7 C% L
  12.   SetSysClockTo56();  
    ' m" i: b3 }6 j1 L$ S' e# C' D2 h
  13. #elif defined SYSCLK_FREQ_72MHz
    + _8 g' w/ D- p* q9 f1 q
  14.   SetSysClockTo72();
    2 S4 d+ a" ^6 M2 j% O. v( E
  15. #endif, e0 V, d1 P- B4 {0 C1 _9 S, Z
  16. }
复制代码
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
  1. #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
  1.   #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
  1. #ifdef SYSCLK_FREQ_HSE
    & t0 E! x% X( H
  2.   uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;      
    + h7 R! h0 e/ h. Y
  3. #elif defined SYSCLK_FREQ_24MHz% [. l9 t) o; u2 S" y+ H- f$ j
  4.   uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        
    3 y, O5 @! J; V1 h$ l2 k
  5. #elif defined SYSCLK_FREQ_36MHz/ ^6 d  O$ x( p7 x# X! q1 L
  6. #elif defined SYSCLK_FREQ_48MHz
    8 O/ w! ?: [: d8 `* N- r
  7.   uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;      
    1 R' a2 D! F4 N6 _0 X
  8. #elif defined SYSCLK_FREQ_56MHz6 B1 b! S( D% }! c. I: n* \
  9.   uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        " B! N4 a' D4 f
  10. #elif defined SYSCLK_FREQ_72MHz  y3 E# r' l  z( Z5 E$ k  h) K9 I: d% a* k
  11.   uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;      
    2 \, G1 G+ s- y$ N; O
  12. #else
    0 b5 d1 G- H7 e. T# [
  13.   uint32_t SystemCoreClock         = HSI_VALUE;      2 g0 {4 X) J1 Q
  14. #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* ] W9}BM@EKI($@YHBW3%W592R.png
+ v  C( M  @0 P5 ]2 [& I: N- Y+ j) m# s2 W
JMVM6BU0748@EI_DUI1]V(F.png
4 U5 x% X& D2 H0 i8 P( g
+ Q: ~: d* u: o* ^6 ]& A V{985U[](IOP)~H_`QL1TTP.png ! 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
(0D2`W~IS8UO]D9C@8PWP04.png 3 V3 Q  V( b! o+ a
! ?" w# j& K8 y2 Q9 U
BCFEDP_O6$XFDA`_5WK_7F7.png 0 e' l0 i0 B; [7 h
' t7 K3 d/ U. Z, I. ^; {7 _0 R6 G
SM11[L]S[T39YDLHL5O)NIX.png
. |+ ^% ~  }; v0 z
! |2 L( N! b& \% F; Z5 R5 { X`[FB)R4S33D_W$GG]_1%(G.png
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
  1. ; Reset handler# b, O  W: P+ c. \0 U: r0 O4 H' ~
  2. Reset_Handler   PROC/ N! I0 W+ Q1 p9 Y1 c5 U
  3.                 EXPORT  Reset_Handler             [WEAK]$ c  t( v! C+ U% Y0 `5 C
  4.                 IMPORT  __main
    8 j+ m, ?6 [  d, t4 h- O/ y- `
  5.                 IMPORT  SystemInit
    . B- B" Y2 P( m2 |3 B9 I
  6.                 LDR     R0, =SystemInit8 [; }: i' {' r5 d' ^/ P
  7.                 BLX     R0               
    ! Z3 K# e$ x# M+ l% B+ \! K) V
  8.                 LDR     R0, =__main
    5 x. q" g" B* y" B* I
  9.                 BX      R0
    + \! z4 z) ~- W  ?
  10.                 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
  1. void SystemInit (void)
    2 C8 I  h( g( p) x3 I
  2. {8 c9 o  d# v* h% l1 @
  3.   /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
    6 V* ~+ }% q! f+ H( R9 a! P
  4.   /* Set HSION bit */
    $ P& s( Q1 |5 K9 d9 p4 b4 K
  5.   RCC->CR |= (uint32_t)0x00000001;9 t; F4 ?, P* N8 \; D) P1 l  D& L
  6.     SetSysClock();
    - d' c& `* }0 E$ c5 e& ~+ q
  7. }
复制代码
; 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. ]
  1. static void SetSysClock(void)
    + ^) p& L1 K- a! D0 a( _
  2. {) v5 D5 ]" Q% x& \' e" |9 A
  3. #ifdef SYSCLK_FREQ_HSE& N) ^: L- o" a: g
  4.   SetSysClockToHSE();
    " o: E" }( }  Y& }1 \- J
  5. #elif defined SYSCLK_FREQ_24MHz, v" v8 G  M+ ~/ p5 P
  6.   SetSysClockTo24();5 |/ Z; ~7 q0 P( S; g! V
  7. #elif defined SYSCLK_FREQ_36MHz
    ) J1 j3 U0 h" t
  8.   SetSysClockTo36();
    ) T% \: i5 y- {# z; s
  9. #elif defined SYSCLK_FREQ_48MHz4 I# A. q' _. X4 w; j* V
  10.   SetSysClockTo48();
    ( J/ Q2 L1 T9 l" T; |, w) A+ U
  11. #elif defined SYSCLK_FREQ_56MHz% W1 h4 b  f0 \! i7 |
  12.   SetSysClockTo56();  : K4 x5 |* I# t
  13. #elif defined SYSCLK_FREQ_72MHz. x1 Q; k, v6 O- M
  14.   SetSysClockTo72();3 v1 L+ b0 [; S; L# @
  15. #endif
    . u: t4 d7 ?& H7 S
  16. 0 [- b8 x- k4 s7 v) W
  17. /* If none of the define above is enabled, the HSI is used as System clock
    7 n$ e. v9 o  I& F
  18.     source (default after reset) */
    4 ~$ v# |4 r& L% [; s( E- s
  19. }
复制代码
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
  1. static void SetSysClockTo72(void)* N# A% B& }& }% l
  2. {
    3 ?- o+ {5 _# p1 m, M4 E0 A
  3.   __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
    + R2 E% s$ ^. L* y
  4. " b1 e! c1 k: F7 A, I* ~
  5.   /* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/   
    : y0 v8 p6 R7 {; {/ {) r, P3 ^
  6.   /* 使能 HSE */   
    : d1 C; }1 U0 ]) ?3 c* S" l2 D) P
  7.   RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE; _# G& U9 A. q- b! f: D

  8. / r( a% j" g) V$ e
  9.   /* 等待HSE就绪 超时退出 */% Y7 f' ?! a& c4 }$ r1 u4 M
  10.   do
    - T8 k* z& Y, a, c  e2 w
  11.   {0 a5 R+ ]+ X( l! \' k
  12.     HSEStatus = RCC->CR & RCC_CR_HSERDY;  //读取RCC_CR第17位$ I- X4 a; E, T2 Y$ o9 v0 L
  13.     StartUpCounter++;  
    4 Y" h$ j; A& w# T
  14.   } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));
    ) ?& V9 N6 \; q5 b, V1 `
  15. & v. f7 z# [& s2 K
  16.   if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位
    # Q, a0 Q8 G) S1 |+ o7 `9 d
  17.   {
    ! R2 d6 C1 {/ y2 A6 c/ |
  18.     HSEStatus = (uint32_t)0x01;   //HSE状态写1
    " X5 N6 j; }" S
  19.   }
    $ }7 `+ \* i; q$ Y- u0 H" k: H
  20.   else" o! n5 I8 q: b
  21.   {7 ^) H0 s; X( p( W5 e% i3 z
  22.     HSEStatus = (uint32_t)0x00;   //HSE状态写07 H7 E  M- x( m7 r# j3 c; U
  23.   }  
      x+ I. E4 K# _! A% f8 C
  24. 4 u% l9 t$ X! y- I# K3 q
  25.   if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管
    ' C. C$ f  d9 X% n3 _" j
  26.   {, j0 S' n+ m7 a: \' _  H! W3 U; o
  27.     /* 使能 Prefetch Buffer */
    $ ]5 D6 s" h8 x  b( X2 b
  28.     FLASH->ACR |= FLASH_ACR_PRFTBE;
    4 M3 W: v0 T6 G
  29. * A3 w' \7 _8 b9 N; b
  30.     /* Flash 2 wait state */
      n  b/ j# O! ^
  31.     FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);
    3 a( [4 T2 m% d/ ]
  32.     FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;   
    7 i, b* w( l2 A6 T# c' Y- N, _
  33. & W& {# V: Q6 G' ~, v( O% I" M
  34. ; x+ h7 F+ W( ^2 R
  35.     /* HCLK = SYSCLK */! C' k5 u1 N+ i: {5 o
  36.     RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;  //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟" @1 U$ k3 p; H( i0 w* a

  37. " s6 ]+ v0 f1 n1 Q
  38.     /* PCLK2 = HCLK */$ h$ W$ R1 J! a4 j( Y5 J6 ?& W
  39.     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟. m2 d2 d) z% l: a

  40. " w7 Q  R( q6 n. f* k) c
  41.     /* PCLK1 = HCLK/2 */
    1 M+ V7 }1 E( x* l: N& I: s' Y6 f
  42.     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟
    5 ?" V5 e- }6 g2 X

  43. ) b# }: X" ]& X/ U
  44. #else   
    $ X) ^) y' u8 w
  45.     /*  PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
    2 i: R, I$ F# l: L. d2 D( w
  46.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
    1 i+ u1 f' B7 F$ l  z
  47.                                         RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零4 w8 v! J: K  B+ x9 z2 K5 T9 p
  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! N6 x+ L4 {5 Z3 `6 ~' L
  49. #endif /* STM32F10X_CL */
    , i6 T& d- x+ c% {9 N, K  t* n
  50. 9 t) H- [7 \- E
  51.     /* 使能 PLL */- f. |: p! Z& e- _8 K
  52.     RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL
    - t' B3 n' e$ j4 N/ W- C

  53. 4 V1 x& [3 O- @: G5 \9 B
  54.     /* 等待PLL就绪 */
    ; \4 w% p: G' ~
  55.     while((RCC->CR & RCC_CR_PLLRDY) == 0)  //读取PLL时钟就绪标志位(第25位); E) A+ x% C8 Y, Y! \% F8 A/ b( F
  56.     {8 w) N' U4 _( H( w. t
  57.     }  x  J" q4 @1 J) Q- _- D+ W% k
  58. : P- i- `1 |$ I+ _  J# t  z+ V8 U
  59.     /* 选择PLL作为系统时钟源 */
      S6 h) w, G% l1 b1 g+ L
  60.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零% z/ @4 N( n, {/ ^" O
  61.     RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟
    5 q+ Y' }" g* M9 ^

  62. 8 o4 v5 j4 a3 R- k  w8 e
  63.     /* 等待PLL被用作系统时钟源就绪 */
    6 @- [0 ?) [7 M5 U
  64.     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
  65.     {
    ' ^# a3 {; C7 H, z# A4 G9 q6 Q; i
  66.     }
    5 U6 C% ?$ ]# B+ e4 I) M
  67.   }( E! w% |& `. Y1 f, E: ?
  68.   else& M' X" l" A8 d! v1 P
  69.   { /* If HSE fails to start-up, the application will have wrong clock , a7 S. v: I- t# M4 H$ s; B6 m8 H
  70.          configuration. User can add here some code to deal with this error */3 g. @  F4 `+ m6 n
  71.   }) G. Q( I+ {+ S+ ^2 Y( z
  72. }! z. T% k' m! C+ ?5 {1 z2 J$ L4 v( F
  73. #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 %OTG9{34YZE69O)CZ(1M`65.png * h! H% u# ]* H. F* E

+ B: P* M$ V5 ~: `. M* e6 Q; h LSDP4`]K2IT)I[AZRM]2K.png 7 z) K( Z* o+ @& E0 I0 N7 f
) L0 c) \$ X6 P
({74TXQGSPLIP3Y@2CSGK~O.png
6 }( U# Q4 A5 B& g. v3 m5 n: }
2 A$ ]- J- [& W8 B 12H`7TR4T5LG{([PNKJD`XV.png
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  }
  1. void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)) S, A3 d4 i' \1 t( D
  2. {
    - m0 f$ p- d" ?$ J4 ^: r" b
  3.   /* Check the parameters */8 \) S5 l" e9 r" D2 C  `
  4.   assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));$ v& J* D: p, A5 O. Z& H
  5.   if (SysTick_CLKSource == SysTick_CLKSource_HCLK)
    $ n9 C9 Z& g- b5 U1 X+ u6 ?( [
  6.   {
    ; t/ l1 Y' ?4 n, q! l7 g
  7.     SysTick->CTRL |= SysTick_CLKSource_HCLK;) \6 L; x- }. c' |) g7 A3 d
  8.   }
    " I1 C( w. `2 Z
  9.   else
    7 r+ l  v" A+ Y5 [
  10.   {
    - m, e- E/ k. J) K
  11.     SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;
    % i- u. [% y1 K/ [
  12.   }
    7 S9 v- G$ d4 f- v
  13. }
复制代码
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
  1. static __INLINE uint32_t SysTick_Config(uint32_t ticks)  x! {) J8 h- k
  2. {
    7 M9 \  \+ F' V3 c3 ]/ C
  3.   if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */0 ]7 E) N, t; Q: b6 ]/ h! W
  4. 7 c- `$ Z! w' {; e
  5.   SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */
    , O! a/ C" w& h0 v8 O- y& Z5 L
  6.   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
  7.   SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */0 A" R0 k/ v9 P% R
  8.   SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
    6 w+ L( P5 q  l) {  M! w
  9.                    SysTick_CTRL_TICKINT_Msk   |
    ; J4 j+ G% O2 Y# v( v0 W; P/ B2 X4 i$ v
  10.                    SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */0 ^/ n. i+ ~( P+ j5 f
  11.   return (0);                                                  /* Function successful */3 n" t1 V* P3 W. B
  12. }
复制代码

; 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
  1. static __IO uint32_t TimingDelay;
    ; b- f* {( o& ^
  2. void Delay(__IO uint32_t nTime)
    / l, o' K: L' M6 i+ s
  3. {
    / R1 D" D8 s2 G5 K+ {, b
  4.    TimingDelay = nTime;
    5 T. ~, T" f& ], k8 a: y
  5.    while(TimingDelay != 0);
    % T. [4 D3 g5 V( y8 V: ~4 s) z* _
  6. }+ c1 v2 W$ I; c7 E" E/ B
  7. void SysTick_Handler(void)9 m& b) m( q0 R; H; r/ G0 Y
  8. {/ H! y6 y. `. j
  9.     if (TimingDelay != 0x00) 4 p# ?3 B3 L' @* i) C
  10.      {
    5 _. W. e; s" o* }( ]
  11.        TimingDelay--;
    6 v& ]- O( Y  W( Q, s- u8 C, ^. B
  12.      }0 U- [; ~7 s5 y9 K% E
  13. }
    3 d& b# T( S0 [, U1 }! j2 n
  14. int main(void)
    $ ~- l; F6 \2 h0 y! \7 H
  15. {  …
    % F  t, C0 b- _* i- \* R6 M3 O
  16.     if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms$ P- z$ }4 v7 q" o- j# z- K6 a
  17.      {  Z$ R! ^! o2 J6 v8 {* O
  18.      while (1);
    8 i& p" ~2 B" ^9 @9 s& ^7 r% W  b
  19.      }
    * X7 M, x' z# P, Q& h; u
  20.     while(1)
    ; c2 `7 R7 X: @# Q$ d$ p
  21.      { Delay(200);//2ms0 F) p. P; ~/ p" Q$ ?( {6 C2 d
  22.      …
    $ U, p' s. g" l  B8 t6 T" e6 t
  23.      }; B4 }( Z2 E* C3 G2 k8 t/ }; t
  24. }
复制代码
# 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
  1. #include "delay.h". Z+ p7 R# l  K  L8 s
  2. //          . Y5 K* h; [1 n  v  N: Y. h
  3. //如果需要使用OS,则包括下面的头文件即可.7 J0 y+ N. {9 s. C4 [) b* d8 w
  4. #if SYSTEM_SUPPORT_OS
    7 X" O6 q  r1 J
  5. #include "includes.h"                                        //ucos 使用         
    9 k* |9 V% y: K' ~: H, x/ @( D+ ?
  6. #endif8 e7 ^; b$ K; J
  7. //         
    / U- {) `- T3 K6 B- p

  8. : t3 X/ ~$ A2 F7 Q3 Y9 [
  9. static u8  fac_us=0;                                                        //us延时倍乘数                           : W) \  V1 t0 ^8 e! n+ X+ e0 O
  10. static u16 fac_ms=0;                                                        //ms延时倍乘数,在ucos下,代表每个节拍的ms数
    7 h  K- [# ~1 |2 M. u; I- `6 r9 P
  11.         3 a2 I! E1 z3 t( Q3 f( @2 J: K. l
  12.         
    9 F! c! S6 v6 n: r# f
  13. #if SYSTEM_SUPPORT_OS                                                        //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
    8 f$ _& p$ m$ z. f
  14. //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
    " N! T5 U& r8 P2 y! u" g( j# w
  15. //首先是3个宏定义:
    9 X/ J9 h$ ]4 \2 x: Y9 P. c
  16. //    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数& x6 W1 ]' r; M7 v8 b5 ?+ k
  17. //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
    * e7 q5 `# E0 U# }* w  s
  18. // delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行% c7 |% l+ M8 P& }9 S  h9 x2 O
  19. //然后是3个函数:2 n; R+ w% r1 `: A0 Y
  20. //  delay_osschedlock:用于锁定OS任务调度,禁止调度5 ~( A3 R6 K% f
  21. //delay_osschedunlock:用于解锁OS任务调度,重新开启调度& ~+ d3 L* S3 E) e! p
  22. //    delay_ostimedly:用于OS延时,可以引起任务调度.  ]. b; Q: a( P% O/ R, B

  23. & L6 t/ D# n+ u. x9 j1 |
  24. //本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植
    7 W( ~9 |, G( v% e$ d( X' f3 ?
  25. //支持UCOSII
    7 j- d; |* ^% O+ y2 Y4 d0 p& F' V
  26. #ifdef         OS_CRITICAL_METHOD                                                //OS_CRITICAL_METHOD定义了,说明要支持UCOSII                                
    ! d2 a" _) q6 \+ t3 E* P) d
  27. #define delay_osrunning                OSRunning                        //OS是否运行标记,0,不运行;1,在运行
    : N* z( t+ _$ |- V5 U$ g3 s- G
  28. #define delay_ostickspersec        OS_TICKS_PER_SEC        //OS时钟节拍,即每秒调度次数3 i* ]8 I* T7 k. `
  29. #define delay_osintnesting         OSIntNesting                //中断嵌套级别,即中断嵌套次数
    : }+ y& `/ a$ z$ `9 r- V  O8 }3 k
  30. #endif
    " D5 P- P2 D5 V1 j0 X# ?
  31. % \! v9 t1 r) c% d- \8 |- \' J
  32. //支持UCOSIII
    & [- y, [3 T" Y) ^- z/ ?/ Z+ G
  33. #ifdef         CPU_CFG_CRITICAL_METHOD                                        //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII        
    / r9 p2 _2 ]; B( J. w5 l
  34. #define delay_osrunning                OSRunning                        //OS是否运行标记,0,不运行;1,在运行
    " B2 O& M! t9 d% X
  35. #define delay_ostickspersec        OSCfg_TickRate_Hz        //OS时钟节拍,即每秒调度次数
    3 u' q+ y0 c% F) t2 W
  36. #define delay_osintnesting         OSIntNestingCtr                //中断嵌套级别,即中断嵌套次数
    7 @, d, R& L& Y$ I6 {( N2 ~
  37. #endif" V# O. }) y/ g' i8 }6 Z' R
  38. 6 u, u! o/ `6 K2 Z& C, q2 t
  39. * W3 W3 V5 J) }( X; g
  40. //us级延时时,关闭任务调度(防止打断us级延迟)0 s- d: {& w! [$ i5 Y9 j& o" P
  41. void delay_osschedlock(void)
    , Z( N# K/ n. o7 D& |; i1 X
  42. {' x. |  M- y! M$ f
  43. #ifdef CPU_CFG_CRITICAL_METHOD                                   //使用UCOSIII
    ; C& @& e8 q+ p4 X
  44.         OS_ERR err;
    2 ^. y6 b( a% b4 ]
  45.         OSSchedLock(&err);                                                        //UCOSIII的方式,禁止调度,防止打断us延时8 L1 I: I. }3 T" E6 C# e
  46. #else                                                                                        //否则UCOSII+ e/ ^/ h, t1 S2 ^) t( x1 k
  47.         OSSchedLock();                                                                //UCOSII的方式,禁止调度,防止打断us延时
    5 x1 ?+ B$ L$ L* J$ r8 C6 A
  48. #endif4 s; H  ^. {- e- j' ?( g
  49. }/ J% x9 J8 I9 m, i; W
  50. 4 X' A" V) v- z  F" Y! v
  51. //us级延时时,恢复任务调度
    & G$ M5 Z0 l4 E: L% [
  52. void delay_osschedunlock(void)0 |" m( D! ]& E# K0 K! V3 V* ?4 Q
  53. {        
    / S. f1 x: N( ]2 ~$ w) A
  54. #ifdef CPU_CFG_CRITICAL_METHOD                                   //使用UCOSIII
    + }; C9 o( R/ k! y9 s+ s6 z
  55.         OS_ERR err; # r/ r& h  U: n% B4 t
  56.         OSSchedUnlock(&err);                                                //UCOSIII的方式,恢复调度
    3 }3 I# h6 l/ t( R
  57. #else                                                                                        //否则UCOSII0 x; r6 E' H# E
  58.         OSSchedUnlock();                                                        //UCOSII的方式,恢复调度7 M  Q/ D7 K) k1 B8 t9 }& x+ {
  59. #endif1 B9 r" B! ?( V7 P
  60. }# t+ L* [0 N  ^

  61. + c7 J: a: @" d" D7 P0 V% j" Z
  62. //调用OS自带的延时函数延时  j/ ^9 F# P( u* q8 Y- U
  63. //ticks:延时的节拍数
    9 K5 q& B# M- B/ _
  64. void delay_ostimedly(u32 ticks)
    5 D, y) ~. t& T5 P& F; \; K: L
  65. {
    & w1 t7 o2 B. {2 ?: y9 z
  66. #ifdef CPU_CFG_CRITICAL_METHOD
    7 t$ ?7 _1 D& P1 I- C
  67.         OS_ERR err;
    : P( [" n8 k6 u. N2 _+ l) I4 {
  68.         OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);        //UCOSIII延时采用周期模式' C# a/ V* q2 N4 R
  69. #else
    ; I7 _* g  l0 g# ?! ^4 W
  70.         OSTimeDly(ticks);                                                        //UCOSII延时8 J4 ~8 X, U2 v- g8 c4 W
  71. #endif
    " r/ t/ [0 A- H# b
  72. }, j9 s# Z7 A) K' h

  73. ; M6 x0 [  D# t! x% @5 ]8 p) z4 T5 K4 A
  74. //systick中断服务函数,使用ucos时用到
      i  X2 l, M7 V6 _1 i: [# L
  75. void SysTick_Handler(void): S( m4 n/ J7 @; @8 i! C6 [
  76. {        
    7 z* k; i" P1 `# U
  77.         if(delay_osrunning==1)                                                //OS开始跑了,才执行正常的调度处理! ~& F/ T' P. z
  78.         {
    ! ?1 d! c2 E7 u9 F9 l
  79.                 OSIntEnter();                                                        //进入中断$ S, U3 p+ ~% E* _. Z3 S5 G% S6 I
  80.                 OSTimeTick();                                               //调用ucos的时钟服务程序               
      E8 L' x; K/ M1 L
  81.                 OSIntExit();                                                        //触发任务切换软中断
    * z! L* J$ K  ^: y( v
  82.         }
    5 ?0 }1 @( {$ B/ R, ~1 |
  83. }
    ( j  m- }; E; z
  84. #endif# J4 `4 P+ c: a4 ^$ f* F4 D) [! g- U

  85. % b5 e) c( Q  h$ g/ S
  86.                            ' ]8 I+ z, f- Y5 ?
  87. //初始化延迟函数
    + n5 U& O6 ~# r" F
  88. //当使用OS的时候,此函数会初始化OS的时钟节拍
    6 c( `  m) F7 Q! C
  89. //SYSTICK的时钟固定为HCLK时钟的1/8' a4 ]' h2 b2 t- a1 n7 s+ w
  90. //SYSCLK:系统时钟; Z9 D& Z; K. U+ [8 B8 j
  91. void delay_init()0 u9 `- e3 ?0 X+ N7 @, f
  92. {
    " x7 ^4 w6 [% f/ _! E2 G+ f
  93. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.
    6 {- V# g7 E# t  ?2 P* J- g: l
  94.         u32 reload;. L; k! ^1 [! v5 e4 k
  95. #endif- p' O) R/ c1 ~4 }" i
  96.         SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);        //选择外部时钟  HCLK/8
    7 ?# M+ h- m1 n; Z9 d9 I+ l
  97.         fac_us=SystemCoreClock/8000000;                                //为系统时钟的1/8  , _/ s$ f' P- y" F9 m. Y/ r
  98. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.3 N# Q4 `. W/ G( q
  99.         reload=SystemCoreClock/8000000;                                //每秒钟的计数次数 单位为M  
    5 ]" b. v9 e9 k  \/ X9 B$ H
  100.         reload*=1000000/delay_ostickspersec;                //根据delay_ostickspersec设定溢出时间, l6 W2 y  f$ M0 O) G
  101.                                                                                                 //reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右        9 s0 J- a; |; {: E
  102.         fac_ms=1000/delay_ostickspersec;                        //代表OS可以延时的最少单位           
    8 Y% i. t, Z$ a9 P

  103. - c  }8 I; N( L# V; ^
  104.         SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;           //开启SYSTICK中断
    7 s* |8 N4 K# Y+ F/ }
  105.         SysTick->LOAD=reload;                                                 //每1/delay_ostickspersec秒中断一次        ( X) S( q9 g1 I! ]+ Q
  106.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;           //开启SYSTICK   
    4 m1 ~3 d' R5 j* ~0 u
  107. 8 T' Z: V  S  U8 U; V5 T
  108. #else
    9 h. A3 N" W. H
  109.         fac_ms=(u16)fac_us*1000;                                        //非OS下,代表每个ms需要的systick时钟数   + I* A  `2 }% g( T
  110. #endif
    5 [1 J* S8 b2 c! f
  111. }                                                                    
    7 J% i( t; B3 ?. s  q1 m% s

  112. 9 D; c) L* q$ r( r
  113. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.8 L; T& z& c" }" P% Y* ?4 R
  114. //延时nus6 T! Q* a( _  f+ g4 |; p& a/ Y, C* F
  115. //nus为要延时的us数.                                                                                       
    + Q0 w) ]3 s0 v$ k
  116. void delay_us(u32 nus)0 q! J, h; ~. W* z
  117. {                " Z9 O0 E7 v. N2 ?1 A
  118.         u32 ticks;
    4 }3 I7 D* B7 r4 l
  119.         u32 told,tnow,tcnt=0;* n8 H' P" Q9 i3 g
  120.         u32 reload=SysTick->LOAD;                                        //LOAD的值                     
    % E. V  w7 H0 K  q3 X; v
  121.         ticks=nus*fac_us;                                                         //需要的节拍数                           5 m2 p2 u3 E: M( K' ^
  122.         tcnt=0;- c; J6 E' P, Z* T& D! S
  123.         delay_osschedlock();                                                //阻止OS调度,防止打断us延时* t: }+ K8 }3 V1 E2 U. c
  124.         told=SysTick->VAL;                                                //刚进入时的计数器值
    + e: |6 v1 r1 h6 Z# v
  125.         while(1)
    6 K* R1 L- i& I+ `1 q/ e7 w* j
  126.         {/ k( L7 ?: `1 x/ v
  127.                 tnow=SysTick->VAL;        
    7 {4 d7 M6 V/ r% x4 E
  128.                 if(tnow!=told)$ U, c4 k1 Q4 x3 Z) `
  129.                 {            
    : I; ]8 y7 X1 e0 a; V' ~- y
  130.                         if(tnow<told)tcnt+=told-tnow;                //这里注意一下SYSTICK是一个递减的计数器就可以了.
    0 S8 z2 H" W5 s! S1 |5 D! B
  131.                         else tcnt+=reload-tnow+told;            . ?7 d! o& e* N& ]5 T
  132.                         told=tnow;3 M1 L# I' s( ?9 `. \* \: I
  133.                         if(tcnt>=ticks)break;                                //时间超过/等于要延迟的时间,则退出.
    9 `( h0 p! l7 m& @5 u7 Z* c! _5 P
  134.                 }  $ y. [) i# Y7 T' n, ?
  135.         };) E3 M- {1 |0 h% s  u0 k5 l' N+ s$ X
  136.         delay_osschedunlock();                                                //恢复OS调度                                                                            0 j1 W0 y# w, ^5 H
  137. }. Y2 Q- O+ g  b9 e8 A
  138. //延时nms
    * \6 o7 \  r9 U, L2 E% u7 b9 f+ |0 F
  139. //nms:要延时的ms数
    4 d& m% c& p" I! _
  140. void delay_ms(u16 nms)1 x# Q4 S+ N& V" D
  141. {        
    4 Y7 u* X, r- F2 y6 i' h3 [% S# C8 H
  142.         if(delay_osrunning&&delay_osintnesting==0)        //如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)            9 J+ R, a* n: ]# M
  143.         {                 # y3 _. t/ M* B- ?9 F3 R
  144.                 if(nms>=fac_ms)                                                        //延时的时间大于OS的最少时间周期 & a7 o* P' g- q" r0 U+ A, u4 i$ N
  145.                 { ' }- l' x7 a+ h" H8 X( R( d
  146.                            delay_ostimedly(nms/fac_ms);                //OS延时* y2 `* o6 O8 s) |! X6 z5 a
  147.                 }
    " n; U6 [$ J, A) x/ }' w
  148.                 nms%=fac_ms;                                                        //OS已经无法提供这么小的延时了,采用普通方式延时   
    2 ?' j* M  P: S2 Q# B2 ?, y
  149.         }1 L' O) E- l; U0 p) k  w( j
  150.         delay_us((u32)(nms*1000));                                        //普通方式延时  ) l6 g$ V8 M* K* |- j. G, w
  151. }
    ; d' |; }0 m# B: w+ T
  152. #else //不用OS时, F1 d4 V# q" f; {% V% [6 D! s
  153. //延时nus. j2 f4 _( o) O1 z( U; S0 |, v3 I
  154. //nus为要延时的us数.                                                                                       
    , S: f3 Q% h! t: n& @
  155. void delay_us(u32 nus)
    % A+ _, t, r7 U# t! d2 E, d$ \: r2 v
  156. {               
    8 g. l$ B7 K: H% Y6 ?$ B) }/ v
  157.         u32 temp;                     6 v1 i2 c8 F( c/ J$ ]1 r
  158.         SysTick->LOAD=nus*fac_us;                                         //时间加载                           
    $ s/ P4 T7 k1 g5 ~8 N8 s
  159.         SysTick->VAL=0x00;                                                //清空计数器
    7 _2 q! R$ t1 u& k! t6 i
  160.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数         
    7 P: f+ o, P1 B
  161.         do
    7 I( K' f4 e8 ?, \7 n0 ?
  162.         {4 w# [! m- m8 w+ t( r1 Z
  163.                 temp=SysTick->CTRL;
    9 f2 p& X  H& ]
  164.         }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   ! M& [& z& B7 p4 ]& _6 b
  165.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器, i# N  ~+ T/ B# o+ A' l
  166.         SysTick->VAL =0X00;                                               //清空计数器         
    & q' _, R* S  M/ d( u/ a
  167. }0 |0 s* N0 {2 Q* c# |. k
  168. //延时nms
    % s1 M. }8 b- U0 q
  169. //注意nms的范围2 f4 _& h! i; r7 o, X
  170. //SysTick->LOAD为24位寄存器,所以,最大延时为:  C4 H2 U/ N8 @) f
  171. //nms<=0xffffff*8*1000/SYSCLK7 [+ J: Z: d* J8 i
  172. //SYSCLK单位为Hz,nms单位为ms
    % D5 P' \8 j, g* A$ Z6 ~
  173. //对72M条件下,nms<=1864
    7 F" F, D9 f# W, `3 y: v" l. \# _; V
  174. void delay_ms(u16 nms)
    + ]& f* d" R9 S$ d0 l: x
  175. {                                     , X/ S+ g+ \6 K5 V4 X; t
  176.         u32 temp;                   7 C3 j$ }6 _% X
  177.         SysTick->LOAD=(u32)nms*fac_ms;                                //时间加载(SysTick->LOAD为24bit)
    ) h+ u. W5 L8 o  d& F9 I  f
  178.         SysTick->VAL =0x00;                                                        //清空计数器/ z1 ^4 d6 v8 P
  179.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数  ' x  s+ R* U# J
  180.         do& C. F! n0 B/ s# p& M
  181.         {$ g$ s, [/ {8 a) r3 u
  182.                 temp=SysTick->CTRL;( v. z0 P, O7 R. t- T( D- Z
  183.         }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   : ~  y  M; c' _4 x% Z2 D1 l# r
  184.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器2 `1 @9 Q( f% g) C
  185.         SysTick->VAL =0X00;                                               //清空计数器                      $ V/ H% ]1 o4 S9 }! f) h8 e
  186. } " Y  `! f; O' }
  187. #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 `
收藏 评论0 发布时间:2022-5-7 11:31

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版