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

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

[复制链接]
STMCU小助手 发布时间:2022-5-7 11:31
前言' Y+ p0 C: {) T* t
        时钟系统之于单片机就如同与心脏脉搏之于人体,可见时钟系统的重要性可见一斑。然而STM32的时钟系统极其复杂,不像51单片机一样一个时钟系统就可以解决一切问题,这对于初学者来说很不友好,本文致力于讲解STM32时钟系统,使读者清晰了解STM32时钟背后的原理。4 B" X1 x  m3 X8 t0 j

  m4 S* `' y  r0 O: G4 w一、STM32时钟系统原理
1 @6 {/ C5 \8 b1. 时钟系统框图
, u8 _/ P1 P7 J$ q7 c# I9 h
以下是STM32时钟系统的结构框图,摘自STM32中文参考手册。
0 q8 T/ a" m  j; |8 \: A
1 z- \- W$ U! U% l `I0V5F~`8F5)2ECY1SP{}ME.png " q3 Q1 M3 |' s1 L8 b! w

' D7 G) L, }7 j  l2 b5 U可以看到,官方手册提供的时钟框图很复杂,这里我给出一个简化版的时钟框图:# O# ]& ~" f1 A: Y
  Y8 ]! M* C+ b; h+ k
ZWZ~EG~98NEH[NH61CMO5.png % b  L* j& P9 L
! @  C* p' j, b; E+ |
2. 时钟源讲解. a9 M- _2 b+ t* A( Y
从图中可以看到,STM32共有五个时钟源,分别是HSI、HSE、LSI、LSE和PLL ,下面分别对它们进行讲解:
2 d" _3 D* o! z
" j- g: p' W5 `4 L2 T+ Q6 [; f2 a        ①、HSI是高速内部时钟,RC振荡器,频率为8MHz,精度不高。
, `% c1 o$ J3 v2 ?1 y9 `' E   ②、HSE是高速外部时钟,可接石英/陶瓷谐振器,或者接外部时钟源,频率范围为4MHz~16MHz。( `! Z( T+ z9 J8 B0 U) e! m
   ③、LSI是低速内部时钟,RC振荡器,频率为40kHz,提供低功耗时钟。独立看门狗的时钟源只能是 LSI ,同时LSI 还可以作为 RTC 的时钟源。$ E! q" t- s$ v, r% K* U
   ④、LSE是低速外部时钟,接频率为32.768kHz的石英晶体。是主要的RTC时钟源。
$ ~" c9 V7 J% Y& f   ⑤、PLL为锁相环倍频输出,其时钟输入源可选择为HSI/2、HSE或者HSE/2。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。$ b! Z6 d2 ?$ X9 W$ T

: L. D/ D* o& T! I9 T# l" C6 n: G% s注意:高速外部时钟HSE的引脚是OSC_OUT和OSC_IN这两个引脚芯片是独立引出的,可以接外部的晶振电路,而低速外部时钟LSE的引脚OSC32_IN和OSC32_OUT两个引脚不是独立的,而是在PC14和PC15上,对应关系为OSC32_IN-->PC14 ; OSC32_OUT-->PC15
8 [/ T# r6 U9 ]2 w, d
, I) y6 B+ e0 w4 n- B6 [7 w3.时钟去向讲解
) Y6 [4 I( I/ s3 F- l9 v        上面我们简要概括了STM32 的时钟源,那么这 5 个时钟源是怎么给各个外设以及系统提供时钟的呢?这里我们将一一讲解。这里我们使用官方手册提供的框图(图一)进行讲解,图中我们用A~E 标示我们要讲解的地方。0 V- r# L/ a5 W% G$ ?8 P1 m0 R
4 Z0 l- v3 v" O3 i6 C6 I
A   MCO 是 STM32 的一个时钟输出 IO(PA8) PA8),它可以选择一个时钟信号输出 可以* `  f9 K, |, L) a
选择为 PLL 输出的 2 分频、 HSI 、 HSE 、或者 系统 时钟 。这个时钟可以用来给外) b/ C- F- D$ a
部其他系统提供时钟源。
- M6 b4 S1 }3 l6 R2 z- r3 nB   RTC 时钟源,从图上可以看出, RTC 的时钟源可以选择 LSI LSE ,以及
% n" K. v# W" R; }HSE 的 128 分频。
3 o- K7 G* ^9 j3 z, x/ l7 T/ ~1 UC   从图中可以看出 C 处 USB 的时钟是来自 PLL 时钟源。 STM32 中有一个全速 功能! {" I- {; j$ y4 }
的 USB 模块 ,其串行 接口 引擎需要 一个频率为 48MHz 的时钟源。该时钟源只能
  w7 X' T* H3 x& Q. n从 PLL 输出端获取,可以选择为 1.5 分频或者 1 分频,也就是,当需要使用 USB8 x  i( q! [6 N
模块时, PLL 必须使能,并且时钟频率配置为 48MHz 或 72MHz 。
% p& K3 D$ H* N6 x+ r  J& uD   STM32 的系统时钟 SYSCLK ,它 是供 STM32 中绝大部分部件 工作 的时
4 p6 c# H+ n( Z$ O钟源 。 系统时钟可选择为 PLL 输出、 HSI 或者 HSE 。系统时钟最大频率 为 72MHz
% d/ B) Q- W! F4 F, d6 _当然你也可以超频,不过一般情况为了系统稳定性是没有必要冒风险去超频的。
& }) v/ _) A  x& {E   其他所有外设。从时钟图上可以看出,其他所有外设的时钟最终来源都是 SYSCLK 。 SYSCLK 通过 AHB 分频器分频后送给各模块使用 。这些模块包括:, F/ V5 K1 W6 j* T& u: C
① AHB 总线、内核、内存和 DMA 使用的 HCLK 时钟。  i+ C  K$ R5 d5 o. m
② 通过 8 分频后送给 Cortex 的系统 定时器 时钟 ,也就是 systick 了 。
: |$ a  C' q5 v3 [$ U6 v③ 直接送给 Cortex 的空闲运行时钟 FCLK 。; A# s  i  m% ]) z4 J6 s" ]( e" O% E
④ 送给 APB1 分频器。 APB1 分频器输出一路供 APB1 外设使用 (PCLK1 ,最大频率 36MHz)36MHz),另一路送给定时器 (Timer)2 、 3 、 4 倍频器使用。
- k# y) p# h- f* y. O; e7 ?⑤送给 APB2 分频器。 APB2 分频器分频输出一路供 APB2 外设使用 (PCLK2最大频率 72MHz)72MHz),另一 路送给定时器 (Timer)1 倍频器使用。
' U0 J) {+ T! V2 }        APB1和APB2的区别: APB1上面连的是低速外设,包括电脑接口、备份接口、CAN、USB、I2C1、I2C2 、 UART2 、 UART3 等等, APB2 上面连接的是高速外设包括 UART1 、 SPI1 、 Timer1 、 ADC1 、 ADC2 、所有普通 IO 口 (PA~ 、第二功能 IO 口 等。 APB2 下面所挂的外设的时钟要比 APB1 的高 。3 a2 J0 g/ H6 R' a
1 B6 N7 N7 _4 T/ a; q  f; {2 I
在以上的时钟输出中,有很多 是带使能控制的,例如 AHB 总线 时钟、内核时钟、各种 APB1! M8 P( y: y4 F9 ^: S# Z. n
外设、 APB2 外设等等。当需要使用某模块时,记得一定要先使能对应的时钟。
$ F0 u/ K% @# m
  x/ f* \- h5 s% N% @& G% `5 H二、STM32时钟配置
$ t: o$ p6 @7 _1 m' ?4 n) G1. 时钟配置简介9 u% G' |1 d0 ~, u9 K3 p# a
STM32时钟系统的配置除了初始化的时候在 system_stm32f10x.c 中的 SystemInit 函数中外,其他的配置主要在 stm32f10x_rcc.c 文件中,里面有很多时钟设置函数,大家可以打开这个文件浏览一下,基本上看看函数的名称就知道这个函数的作用。在大家设置时钟的时候,一定要仔细参考STM32 的时钟图,做到心中有数。这里需要指明一下,对于系统时钟,默认情况下是SystemInit 函数的 SetSysClock() 函数中间判断的,而设置是通过宏定义设置的。我们可以看看 SetSysClock() 函数体:: Y1 L' S8 w2 o" |" N: o/ p; k
9 b  ~; C# S7 W3 q  G6 s8 R- i
  1. static void SetSysClock(void)
      R. W; _) f8 U9 t9 {* ^
  2. {4 g3 G5 d* G: o% F8 \, @& q
  3. #ifdef SYSCLK_FREQ_HSE5 G$ `, n/ F, E" E( S, T
  4.   SetSysClockToHSE();, P( Y! \8 C, Z  w# `- e& J
  5. #elif defined SYSCLK_FREQ_24MHz
    - S0 ^! ~& v$ S; i9 @
  6.   SetSysClockTo24();& n  F) v+ V, h( I  B; H( R1 O
  7. #elif defined SYSCLK_FREQ_36MHz" p, e- j6 q( a0 S
  8.   SetSysClockTo36();! K+ u# y9 ^6 k. @4 b
  9. #elif defined SYSCLK_FREQ_48MHz& E& T5 ]) ^; M  [/ h
  10.   SetSysClockTo48();5 T* p: t: u$ B5 v7 Q
  11. #elif defined SYSCLK_FREQ_56MHz
      k- R( ?+ v; F) p
  12.   SetSysClockTo56();  
    : Q, M# S9 [+ J) b# M
  13. #elif defined SYSCLK_FREQ_72MHz
    / O  r1 w' a* @4 K' i8 K7 X  f
  14.   SetSysClockTo72();
    3 ^# H* y+ K8 W. {6 \; N7 G
  15. #endif0 e/ _1 L, A, z( M! B& H% E
  16. }
复制代码
) I4 p. i  y. P) X8 u! r$ p+ R1 Y8 p( h
这段代码非常简单,就是判断系统宏定义的时钟是多少,然后设置相应值。我们系统默认宏定
6 G" }/ w7 c" }  _2 j' H$ B/ Y义是 72MHz:% y- W" D) `! ]# m$ H

. Y4 R8 ^4 i' [! Q; W+ f; a( n* i/ I3 j
  1. #define SYSCLK_FREQ_72MHz  72000000
复制代码
* |- O2 H( e7 p9 [1 M7 {7 {
如果你要设置为36MHz ,只需要注释掉上面代码,然后加入下面代码即可
- B; j4 E$ K% [6 a# P$ ~5 s" @; t
. V& t$ P5 H! N+ N5 x5 s+ X
  1.   #define SYSCLK_FREQ_36MHz  36000000
复制代码

+ U: p3 ^- s/ j( _" N, b9 Q) l 同时还要注意的是,当我们设置好系统时钟后,可以通过变量SystemCoreClock 获取系统时钟: J) _' U/ y- I' F$ Y: ]
值,如果系统是 72M 时钟,那么 SystemCoreClock =72000000 。这是在 system_stm32f10x.c 文件中设置的:; f# |% E6 O1 B' \
+ i1 m6 ^8 U  s9 E6 F7 E
  1. #ifdef SYSCLK_FREQ_HSE
    ( F: R( q  r# s, u
  2.   uint32_t SystemCoreClock         = SYSCLK_FREQ_HSE;      
    ' a7 y/ Y9 l7 Y) y0 N8 z- s) @$ }
  3. #elif defined SYSCLK_FREQ_24MHz) p& G( p; Z  P
  4.   uint32_t SystemCoreClock         = SYSCLK_FREQ_24MHz;        / T, l) f. {9 I
  5. #elif defined SYSCLK_FREQ_36MHz
    3 U" Y+ O) N. H  y
  6. #elif defined SYSCLK_FREQ_48MHz
    ( z! R# y6 V/ n' Z0 H# Y+ D- t7 D
  7.   uint32_t SystemCoreClock         = SYSCLK_FREQ_48MHz;      
    . ?; D7 l. z2 W$ r
  8. #elif defined SYSCLK_FREQ_56MHz
    * W; G1 D/ ]3 f" {- i6 }
  9.   uint32_t SystemCoreClock         = SYSCLK_FREQ_56MHz;        
    5 g- L) `. v9 ~: \
  10. #elif defined SYSCLK_FREQ_72MHz
    8 n3 Z7 u2 B. ~  T' `
  11.   uint32_t SystemCoreClock         = SYSCLK_FREQ_72MHz;      
    3 I9 a- x, s* z: ^4 {0 C8 A6 O
  12. #else
      r$ T1 ], _  F4 [+ L. X8 u, e
  13.   uint32_t SystemCoreClock         = HSI_VALUE;      
    7 K& A0 ]( I# O+ z, R
  14. #endif
复制代码

/ G' _7 d9 e3 x, N这里总结一下SystemInit() 函数中设置的系统时钟大小:
. k# N; V0 I3 r: d$ P. {& m3 l* m
4 u+ @, b# e3 z1 U' u) P0 ^* ZSYSCLK (系统时钟 =72MHz
2 v4 U2 A9 j% v3 C5 D$ z' zAHB 总线时钟 使用 SYSCLK) =72MHz8 C! C) B" ~: ?
APB1 总线时钟 ( =36MHz
$ a$ t8 P7 _( ^APB2 总线时钟 ( =72MHz4 V! q8 @2 ]" k; c' R' U1 m  Q
PLL 时钟 =72MHz
. Q1 E! ^: R' L8 t# c- T2.时钟配置寄存器介绍" M- D$ {$ A$ P, d1 F# z6 v
以上介绍了SystemInit()时钟配置函数,想必大家已经知道了STM32时钟的基本情况,如果你想要更深一步的了解底层原理,那么下面的知识可以帮你揭晓。/ J7 E4 ^2 S2 D: i: N' M" \6 Y6 ~( O
! }  a- f2 k" }/ z9 D
在STM32时钟配置的过程中使用到了RCC寄存器,它们分别是:
5 f) \4 [6 s( ?. L* R; ^
1 G0 H/ M5 ^, p! h时钟控制寄存器(RCC_CR)
& H) i8 J3 ?5 \, |时钟配置 寄存器(RCC_CFGR)2 f* X$ A9 X6 T8 n/ `3 M" R* F
6 {  ~4 H8 @+ I9 q
首先介绍时钟控制寄存器,先上一张官方手册的寄存器说明:' u1 a1 o; _: g0 m9 B7 K

8 _' t6 ^3 w% I W9}BM@EKI($@YHBW3%W592R.png
/ R$ L  k0 \8 M! w, _  N$ I, K9 ]' J
JMVM6BU0748@EI_DUI1]V(F.png ) _3 A! ]6 c1 ], n( S

, X5 |  R/ F0 f6 a+ x4 A6 q( ^! a V{985U[](IOP)~H_`QL1TTP.png + V4 j2 |! V- p* o
% d# U' P' z1 n) `7 B8 T$ G
这个是时钟控制寄存器,看似很复杂,实际上并没有使用到寄存器所有的位,而是红框标出的部分位。下表是RCC另一个重要的寄存器:时钟配置寄存器(RCC_CFGR)4 a3 P$ b4 U$ z) A
( c+ J- e! G" Q
(0D2`W~IS8UO]D9C@8PWP04.png % _; f$ H8 V4 ]
/ q: k4 u1 C1 S) O
BCFEDP_O6$XFDA`_5WK_7F7.png ' V9 Q& R; [8 S
  T5 \' Z4 P+ D4 y, I$ u
SM11[L]S[T39YDLHL5O)NIX.png
- U0 q! M% y; ~3 {; P* {% E1 Q" |: I3 o& b
X`[FB)R4S33D_W$GG]_1%(G.png 4 v% f: }: t8 F+ A  O0 Z1 \; R
% e7 x1 T7 V  ]' m2 s1 ]6 G
和时钟控制寄存器RCC_CR相比,时钟配置寄存器RCC_CFGR要复杂一些,同样的,重点使用的位用红色框标识了出来。$ s0 D1 Z) P8 K$ S

% f; {( U4 X0 l( N/ m. ?' x. p3. 时钟配置总流程
3 _+ [7 |3 q& A; _5 c在STM32单片机复位之后,首先进入startup程序:
! u, V7 m3 P! c/ q# T3 E# ?  f* K" E; B( g
  1. ; Reset handler" Y1 t; T' t$ L- b' q7 L' b
  2. Reset_Handler   PROC5 a0 \3 \3 R- |- @2 h
  3.                 EXPORT  Reset_Handler             [WEAK]
    5 z3 A, Q7 U. a) K9 {7 p+ d! f% c
  4.                 IMPORT  __main
    ( c# L" H) W9 X5 H7 L8 T
  5.                 IMPORT  SystemInit
    . F& V8 L  |3 e
  6.                 LDR     R0, =SystemInit
    ) T* G" _% e1 {% H- G
  7.                 BLX     R0               
    % W+ M0 X% ^. B" U
  8.                 LDR     R0, =__main; y, P9 d4 E4 ^' n: }( m1 z: x; T
  9.                 BX      R06 u; g4 B3 s* x7 @
  10.                 ENDP
复制代码

' W% I/ Q2 w& ?4 K+ t! W可以看出,在进入main主程序之前,先触发了SystemInit()函数,这样就可以保证不需要每次都把时钟配置程序写入main.c文件了,同样,当你想要执行自定义时钟配置程序时也可以改动这个部分。# {6 D# K8 H0 U3 g. @& q
7 f/ P, L& u% i, A
首先我们看一下SystemInit()函数
0 Y' A: r! d% \3 l/ C% m$ j! K' e+ P9 e& w& _3 L
  1. void SystemInit (void)* N9 G& B3 Z, M: f" }
  2. {
    # c: _1 V$ }9 }' {- [  [3 ^2 w3 o
  3.   /* Reset the RCC clock configuration to the default reset state(for debug purpose) */2 _" C7 ~9 ~: B1 }: W
  4.   /* Set HSION bit */
    $ b" [; |9 {5 c) A6 z, U
  5.   RCC->CR |= (uint32_t)0x00000001;: h9 G2 Y+ d9 C4 a8 a. P, p' z
  6.     SetSysClock();/ e7 f  P$ y4 e2 ~( F3 ?# Q+ T
  7. }
复制代码
$ P. u. `2 V4 M4 [3 _
可以看到,SystemInit()函数的作用就是使RCC_CR寄存器最低位置1即开启内部8MHz振荡器,然后转到SetSysClock()函数中。刚刚已经讲过了,对于系统时钟,默认情况下就是SystemInit 函数的 SetSysClock() 函数中间判断的
* L' L8 O! g: I5 \* x! u4 @7 K  f9 e4 x. |
下面是SetSysClock()函数:2 n- X6 _# ?' ?( K( P" a
; V; R$ g6 p: Q/ I7 R; z' u5 R. l  @! |5 o
  1. static void SetSysClock(void)5 U+ x. x" Y: I6 _  f
  2. {
    . B) n# R1 l) c" T
  3. #ifdef SYSCLK_FREQ_HSE+ ^5 y6 q/ i6 c4 b" s" t' H
  4.   SetSysClockToHSE();
    : D+ v/ {$ O9 |  z0 y
  5. #elif defined SYSCLK_FREQ_24MHz! ^4 D- P) m; q
  6.   SetSysClockTo24();
    & `' G: X( z- P! i7 F
  7. #elif defined SYSCLK_FREQ_36MHz
    1 H: c) }% C$ k! D
  8.   SetSysClockTo36();
    5 Y# N. G! l+ U; a: R
  9. #elif defined SYSCLK_FREQ_48MHz6 y1 s  Z( T5 y
  10.   SetSysClockTo48();
    * W. ]6 f( J0 ?9 u4 R. }
  11. #elif defined SYSCLK_FREQ_56MHz
    . x. b. K- O! g1 i2 Y5 f9 E3 L& {  e
  12.   SetSysClockTo56();  
    7 {% _1 E, b3 _+ q: s- q
  13. #elif defined SYSCLK_FREQ_72MHz1 W. s2 M3 U  R) Z
  14.   SetSysClockTo72();
    2 g2 `  Y6 z* [/ |2 ?. r
  15. #endif, k, F) \& v. J! {

  16. ' v% ^% |2 \' n& W2 U+ C$ E
  17. /* If none of the define above is enabled, the HSI is used as System clock
    . ~5 P/ o# d2 K9 A
  18.     source (default after reset) */ * u' j% W' m$ p/ Z4 V6 n; ~. n
  19. }
复制代码
- K& a: x: Q. [9 V. c& E5 K
由于我们把系统时钟频率通过宏定义设置为72MHz,所以这里直接进入最下面的程序即SetSysClockTo72(),这也是为什么有的程序把这一段语句直接写入主函数,原则上来说这是可以的,但是没必要,因为startup启动文件已经帮我们定义好了。' p7 u9 L4 ?. N+ l2 d
& A7 i9 L% P3 u7 ~
下面就是SetSysClockTo72()程序部分,也就是最关键的部分
  Z" j# E# P' d, q- ?5 ?! `  M. x
  1. static void SetSysClockTo72(void)
    . r; V+ s" k. y; ^, N5 U
  2. {
    $ u+ M3 x9 k3 _; v8 c# m0 Y6 o
  3.   __IO uint32_t StartUpCounter = 0, HSEStatus = 0;
    / z3 ^2 M: d: D$ G7 u5 r4 r+ |

  4. 5 Q; {4 B9 p: j7 D; Y5 s
  5.   /* SYSCLK, HCLK, PCLK2 and PCLK1 配置---------------------------*/   
    " D- V' o* K' r0 P5 `+ `& E
  6.   /* 使能 HSE */   
    ; J2 [: [) `# u
  7.   RCC->CR |= ((uint32_t)RCC_CR_HSEON);//RCC_CR寄存器第16位置1,开启HSE
    , v" R( J4 Q( w5 w1 E

  8. . p' G% b) {) j( T$ ]
  9.   /* 等待HSE就绪 超时退出 */" C0 s7 ^# J* I( [3 g
  10.   do
    1 `1 r4 A# o" Q  s* O0 G
  11.   {4 L' g6 ?& g% A
  12.     HSEStatus = RCC->CR & RCC_CR_HSERDY;  //读取RCC_CR第17位
    / |1 c2 a% L: R/ N" @% A5 V8 p5 |
  13.     StartUpCounter++;  ! n" r+ v( F$ H7 i$ E  \  D3 P
  14.   } while((HSEStatus == 0) && (StartUpCounter != HSE_STARTUP_TIMEOUT));7 ~7 O- D* L8 ~4 w

  15. % v# h( Y- \! N! y# [2 N
  16.   if ((RCC->CR & RCC_CR_HSERDY) != RESET) //读取RCC_CR第17位
    / [% z+ d/ Z0 k) U' U$ O) |% W
  17.   {
    : V. @1 r# n0 j8 A7 P1 R- l2 W! B; Y
  18.     HSEStatus = (uint32_t)0x01;   //HSE状态写1  e& @( o7 V. k& [- Y
  19.   }/ h% |* S) ], H$ g8 @
  20.   else. q1 B# e$ L7 Y
  21.   {0 c! l! a+ M6 w& p* R) k4 w; p
  22.     HSEStatus = (uint32_t)0x00;   //HSE状态写0
    - o, I# |! e6 v$ s5 j/ U. i4 r
  23.   }  ; n+ F: f* L/ P2 U; J$ n! W+ A) E
  24. $ r! ?: D" i" ?" q
  25.   if (HSEStatus == (uint32_t)0x01) //FLASH寄存器部分可以不用管
    # \$ h! O; ~6 S
  26.   {
    ( s( E2 [4 M1 w8 n9 N* G
  27.     /* 使能 Prefetch Buffer */8 w2 o$ y3 f# A1 V, c8 `
  28.     FLASH->ACR |= FLASH_ACR_PRFTBE;( j0 u2 @7 X8 ~5 A% k$ ]: s
  29. & f: X* V) s, v5 z" b( z
  30.     /* Flash 2 wait state */1 h) |" Y  i8 C9 B
  31.     FLASH->ACR &= (uint32_t)((uint32_t)~FLASH_ACR_LATENCY);) N- g1 }3 z* x- j5 {  M
  32.     FLASH->ACR |= (uint32_t)FLASH_ACR_LATENCY_2;    3 K) r3 o" e& Y8 m1 L" G# C$ o
  33. % a: G; u7 d4 d
  34. . M2 y% v2 ~; @/ k
  35.     /* HCLK = SYSCLK */. l7 I. j. j2 [, m
  36.     RCC->CFGR |= (uint32_t)RCC_CFGR_HPRE_DIV1;  //RCC_CFGR寄存器第4~7位写0,即AHB不分频,即HCLK使用72MHz时钟% @" ~; ]. y  g# {; }1 i

  37. / J6 ~' t6 V0 |3 N6 i
  38.     /* PCLK2 = HCLK */
    5 `! q. L6 {3 Z/ n3 @- Q
  39.     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE2_DIV1; //RCC_CFGR寄存器第11~13位写0,APB2不分频,即PCLK2使用72MHz时钟
    6 ]2 A5 K6 e- I1 D

  40. 5 Y" k- A( ~; d* r
  41.     /* PCLK1 = HCLK/2 */& O+ |/ g3 l. E; G2 D$ ~
  42.     RCC->CFGR |= (uint32_t)RCC_CFGR_PPRE1_DIV2; //RCC_CFGR寄存器第8~10位写100,APB1二分频,即PCLK1使用36MHz时钟
    5 ^% ^- h5 X5 R/ H' @
  43. / ?* P2 W9 j  V+ _# N" f0 _* [7 o
  44. #else    ; N2 e1 c5 l+ p( y/ X: C
  45.     /*  PLL 配置: PLLCLK = HSE * 9 = 72 MHz */
    4 V6 s  z! k6 X
  46.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_PLLSRC | RCC_CFGR_PLLXTPRE |
    ) a  w3 ~9 @! N. C0 i- v
  47.                                         RCC_CFGR_PLLMULL)); //RCC_CFGR寄存器的16~21位清零
    " y! t* {$ T3 Y! L& X# ?0 T  q
  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% |/ O+ z4 v" w) V* p
  49. #endif /* STM32F10X_CL */
    & b: B/ g+ M- m  E8 K2 W

  50. 1 T: z  E9 }) i% _
  51.     /* 使能 PLL */5 ]( d) p( w, V  u" ?
  52.     RCC->CR |= RCC_CR_PLLON; //RCC->CR寄存器第24位置1,使能PLL
    0 [+ I5 m) B) ~
  53. * V) {* `1 A% O8 m' g4 Z
  54.     /* 等待PLL就绪 */2 u* Z, C% @+ S. r7 ?; f+ E" c6 H
  55.     while((RCC->CR & RCC_CR_PLLRDY) == 0)  //读取PLL时钟就绪标志位(第25位)4 R! N# K- k0 k6 H( ^7 k) ^( t/ W+ g0 m
  56.     {; h9 m/ F# {7 a% @2 e, {9 P" D
  57.     }
    1 B* L! Q1 c9 P$ K* H4 x/ S! m
  58. 1 W, E/ E5 ^6 ]' l
  59.     /* 选择PLL作为系统时钟源 */
    " T5 w5 Q" y0 _1 ?) ]
  60.     RCC->CFGR &= (uint32_t)((uint32_t)~(RCC_CFGR_SW)); //RCC->CFGR寄存器第0~1位清零1 }; ~( ~  u# o, E; m, Z# K5 s2 B
  61.     RCC->CFGR |= (uint32_t)RCC_CFGR_SW_PLL;    //RCC->CFGR寄存器第0~1位(SW位)置10,即PLL输出作为系统时钟. ?) ?! W5 ^0 y; G' h6 |9 ~* u

  62. 4 e9 S0 ~9 x4 }# Z5 |
  63.     /* 等待PLL被用作系统时钟源就绪 */
    6 c$ p& S$ u" `. Y
  64.     while ((RCC->CFGR & (uint32_t)RCC_CFGR_SWS) != (uint32_t)0x08) //等待RCC->CFGR寄存器第2~3位(SWS位)为10即就绪: z! x8 s; C4 y- G6 {( \2 I1 M: }" m
  65.     {0 M$ v/ p% e8 I3 y# D  B5 H
  66.     }0 p0 q! S3 Y! O1 @3 J' g8 ~% y
  67.   }. W' k, E* E8 J! `9 u7 O
  68.   else( ?: N5 Z% F9 m4 T4 N$ y# U; o
  69.   { /* If HSE fails to start-up, the application will have wrong clock
    ; J* ~: G( \/ v
  70.          configuration. User can add here some code to deal with this error */3 M4 b2 n! i1 e$ w
  71.   }& {/ s' D6 a% C$ K
  72. }
    3 ^4 y: S* u: L% K" Y- d
  73. #endif
复制代码

2 Q+ u% l7 U& \4 s6 C6 x( ~" ^& N这里把所有的备注都写在了程序中,希望读者仔细查看并对照寄存器功能表理解。STM32的寄存器的每一个位都做了宏定义,这样直接查找头文件即可,非常方便。
6 L- {9 S( a) \1 u  ~' \1 b% w) P" f; O1 D; L/ T4 h  V4 p
至此STM32系统时钟的讲解就已经结束了,我们以大容量芯片72MHz的系统时钟为例来进行讲解,你也可以根据实际情况自己配置,原理都是一样的。下面的内容就是灵活使用STM32的系统时钟来实现演示功能了。
& S( G5 j! }$ g; a9 x' p' d; W2 U5 n' {! q( S) E
三、Systick定时器及delay延时函数
4 k& c1 u6 H0 h1. Systick定时器

$ e* \: b# [7 s1 q前面讲完了STM32系统时钟的基本配置,下面就是对延时函数进行介绍了,延时函数使用到了STM32的SysTick定时器,这是一个很简单的定时器,对于CM3和CM4内核芯片都具备。. G" a/ k3 t7 |- x! J

9 E7 p) O) h2 l& y& nSysTick定时器全称是System tick timer即系统滴答定时器,常用来做延时或者系统的心跳时钟。这样可以节省MCU资源,不用浪费一个定时器。比如在UCOS操作系统中分时复用,需要一个最小的时间戳,一般在STM32+UCOS系统中,都采用SysTick做UCOS心跳时钟。
1 G% m4 R& T/ ]# P" |( o, d
( K% F1 k7 n$ h% x. v9 {SysTick定时器就是系统滴答定时器,一个24位倒计数定时器,计数到0时将从RELOAD寄存器中自动重装载定时器初值。只要不把它在SysTick控制及状态寄存器中的使能位清除,就永不停息,即使在睡眠模式也能工作。
2 W& O6 I* G  _- }  \SysTick定时器被捆绑在NVIC中,用于产生SYSTICK异常(异常号为15)。+ e9 J+ @! M2 O
SysTick中断的优先级也可以设置: e0 g" J6 N& N( h% R
; M1 e1 I7 E; C# Q
2. 相关寄存器介绍
: f) c9 N$ q* X: T7 s( LSysTick定时器一共有四个相关寄存器:
! A% F% }5 T+ i' X( G' C( w8 D% L; L& c4 E: @" r$ X
CTRL              SysTick 控制和状态寄存器
' a$ x) }' ^- D9 v$ yLOAD              SysTick 自动重装载除值寄存器   & O; ?6 L: l0 k
VAL                 SysTick 当前值寄存器 . P. k( c& }; V! \/ Z( F
CALIB             SysTick 校准值寄存器      8 Q8 i; P% Q: _5 ^6 p9 r. `
下面分别介绍这四个寄存器
4 r  ]- M8 }7 q, Y. k
+ C1 I# x: n9 P. G5 X# B* j* T %OTG9{34YZE69O)CZ(1M`65.png ' L. t' a3 S5 g7 N
6 e4 L7 m" X# h* M2 Q3 ?5 p
LSDP4`]K2IT)I[AZRM]2K.png " H: R5 z1 d! L% \6 g
6 W' T; N3 D; |0 M1 V4 Z  E9 c4 s
({74TXQGSPLIP3Y@2CSGK~O.png
  a, x) n  V# n. p' n$ o/ K$ x6 ?  T$ |2 s& a1 y
12H`7TR4T5LG{([PNKJD`XV.png
9 @" n/ \9 u0 }- W9 s; v  u
, i9 p8 B; f# M, Q! ~ 这是四个相关的寄存器,实际上我们一般只用到前三个。
5 d$ y  k7 E6 z  H9 N4 w% z; k; f% c  V7 ]3 P( t
3. 延时函数配置: W- h- ~- B- }6 A" q; d- L
对于STM32,外部时钟源是 HCLK(AHB总线时钟)的1/8 ,内核时钟是 HCLK时钟。配置函数:SysTick_CLKSourceConfig();这个函数在misc.c文件中,这里把它粘出来:. T% ^  o" X2 ?$ S" D' m
  M9 X1 w6 Z! E. ^
  1. void SysTick_CLKSourceConfig(uint32_t SysTick_CLKSource)8 V1 n% n; Q! O! T( f8 N
  2. {& W% Z7 G) u, s; D
  3.   /* Check the parameters */
    ' X0 r2 a: |8 X+ S
  4.   assert_param(IS_SYSTICK_CLK_SOURCE(SysTick_CLKSource));
    1 }4 i) t; a$ }( S9 v, k$ G
  5.   if (SysTick_CLKSource == SysTick_CLKSource_HCLK)0 F- i7 ?) o2 Z- s" p) {
  6.   {# k1 Q& a* e- b" _
  7.     SysTick->CTRL |= SysTick_CLKSource_HCLK;
    " k; V- p' d1 J' F4 c; u; v
  8.   }3 P8 a' b/ n) M( o  u4 A/ c
  9.   else
    6 R  e* {( E8 T* X+ i
  10.   {
      Q: G  E0 z, U# y6 i
  11.     SysTick->CTRL &= SysTick_CLKSource_HCLK_Div8;# m# {) a* f$ x. H! }
  12.   }+ w2 k7 E& J6 t8 l
  13. }
复制代码
  w5 {. Q! f9 C) A6 ?5 u
根据入口参数有效性可知,时钟源可以选择HCLK或者HCLK的8分频。这个函数在下面的delay_init会调用一次,完成延时函数的初始化。6 P5 O+ N  O5 U5 Q! d+ ^
! E* ?4 q  g* k, [+ j! Y1 }
core_cm3.h文件中有关于初始化systick时钟和开启中断的函数,如下所示:9 \; e1 Q" N  [" C

/ I7 x' ?7 U( j+ r  W2 B
  1. static __INLINE uint32_t SysTick_Config(uint32_t ticks)
    . o. ]4 p3 h' }! X
  2. { / a3 l, X' i# _1 ]- M; L
  3.   if (ticks > SysTick_LOAD_RELOAD_Msk)  return (1);            /* Reload value impossible */
    , Q. O" d* @# c% s: m

  4. 8 I( H+ ~2 l4 l" b# I9 m, K/ ~
  5.   SysTick->LOAD  = (ticks & SysTick_LOAD_RELOAD_Msk) - 1;      /* set reload register */4 |. V5 g; m" o/ M, Y+ d
  6.   NVIC_SetPriority (SysTick_IRQn, (1<<__NVIC_PRIO_BITS) - 1);  /* set Priority for Cortex-M0 System Interrupts */  ]4 _* x$ b" e, t/ Q6 G
  7.   SysTick->VAL   = 0;                                          /* Load the SysTick Counter Value */% e2 M5 s' d6 [2 U$ O( B0 a
  8.   SysTick->CTRL  = SysTick_CTRL_CLKSOURCE_Msk |
    9 O. u# K: M) r( [( [
  9.                    SysTick_CTRL_TICKINT_Msk   | 5 p9 }$ N5 C8 Q
  10.                    SysTick_CTRL_ENABLE_Msk;                    /* Enable SysTick IRQ and SysTick Timer */
    " f2 l  x( _& }
  11.   return (0);                                                  /* Function successful */, y! `% K$ `' ]
  12. }
复制代码
$ Y3 W2 w$ d0 r# {5 i% p
需要注意的是第5行把重装载寄存器的值减1的目的是考虑到执行装在动作也需要一个时钟周期。
+ m/ ~5 N+ h6 }+ h9 c3 ~) ~0 \4 i3 b0 f: p# V+ p+ Y
下面是用中断的方式来实现延时:& I7 \, h8 Y! E; [: H8 i) ^6 g

4 C& f: `9 Z4 X$ `- \
  1. static __IO uint32_t TimingDelay;5 `1 W' l4 o4 }% O3 N- g
  2. void Delay(__IO uint32_t nTime)# ?) T* k4 U- n/ P. ?, ?3 z
  3. {
    6 N# H% l& W9 f- }/ v
  4.    TimingDelay = nTime;
    / v! p5 d+ l' p' v8 @, b
  5.    while(TimingDelay != 0);) O4 w* j2 g+ T( {, f2 x
  6. }
    0 J+ P. t9 ]9 `8 b
  7. void SysTick_Handler(void)
    2 {+ H' w$ o: i8 |
  8. {, c2 g2 n" s) i7 {
  9.     if (TimingDelay != 0x00) % }7 e  z! q: p& P
  10.      { 7 `7 V. x5 u+ j: S: s- V* Q
  11.        TimingDelay--;& U% t; ?' @; B) w
  12.      }$ h) |' V0 D/ X7 r9 B1 ?7 c0 t
  13. }
    5 z5 v7 g! H8 N7 L2 K$ |/ r  U
  14. int main(void)2 f6 V9 G  v1 R, K1 v! Y
  15. {  …1 `! @3 V0 e: J+ S" W5 x( D1 I7 D. C
  16.     if (SysTick_Config(SystemCoreClock / 1000)) //systick时钟为HCLK,中断时间间隔1ms
    8 s* w( A7 Z8 t7 @! {. {0 s' Z' J
  17.      {
    - M/ T+ l) ~3 G$ r; I, w
  18.      while (1);
    , h# ]) X" n" u9 s
  19.      }, |  C! X; h. _) S  V" F
  20.     while(1)
    9 n& L3 x* ^) W+ U- ?" U1 a
  21.      { Delay(200);//2ms$ W7 G2 b0 J0 L+ v$ z% e9 Z! l
  22.      …
    " I' J. [  O. C
  23.      }
    2 T  E" q( f: S. X: {/ O
  24. }
复制代码

) U9 B+ L; o" |' R) Q2 _
! y- D& Z5 `: L0 P程序中,SysTick_Config(SystemCoreClock / 1000)的目的是确保SysTick定时器每次发生中断的时间间隔都是1ms(因为使用系统时钟的时钟频率为72000000,SystemCoreClock / 1000等于72000,也就是给重装载寄存器值赋72000,这样计72000正好是1ms)
, V3 e& k8 k- N; X. W5 b
, v! e7 @: `9 E/ Z3 S一般实际应用中我们不使用通过中断来驱动延时的方法,因为这样会多一个中断线路,我们采用的 是单独的延时函数,下面就是延时函数文件:; ]# m9 G% g" t4 r2 {! n
( \1 J- Q- O% ]+ P& z0 I
  1. #include "delay.h"  ~# h1 b# x" G$ h! x. L4 U  }
  2. //         
    ' g( _7 y0 S" G$ C  n" }
  3. //如果需要使用OS,则包括下面的头文件即可.. S! M, t' K9 ^5 E) c+ n. a
  4. #if SYSTEM_SUPPORT_OS
    6 T$ i) u* @7 y& Q& @
  5. #include "includes.h"                                        //ucos 使用         
      U+ g# t; Z* l+ j# f7 x) R
  6. #endif2 w; h9 f. F2 D
  7. //         
    . K, \/ k5 v" D" [5 \
  8. + Z. h1 D5 w2 N& D2 W, v2 D
  9. static u8  fac_us=0;                                                        //us延时倍乘数                           
    ) j8 _5 V% J8 M% B* m
  10. static u16 fac_ms=0;                                                        //ms延时倍乘数,在ucos下,代表每个节拍的ms数# g4 Z3 t! k: q% \4 p3 a9 }$ B
  11.         5 Q/ e0 [. _; t
  12.         : V, b/ r  h& F
  13. #if SYSTEM_SUPPORT_OS                                                        //如果SYSTEM_SUPPORT_OS定义了,说明要支持OS了(不限于UCOS).
    % A. x5 f( X& [7 ^0 e% j& O, Y
  14. //当delay_us/delay_ms需要支持OS的时候需要三个与OS相关的宏定义和函数来支持
    7 B3 v' j9 z) X
  15. //首先是3个宏定义:+ p. T$ G' H" g2 I- L, S
  16. //    delay_osrunning:用于表示OS当前是否正在运行,以决定是否可以使用相关函数
      h: s9 z/ N( j9 b
  17. //delay_ostickspersec:用于表示OS设定的时钟节拍,delay_init将根据这个参数来初始哈systick
    " _* J0 A, R' [" y6 {( g+ @
  18. // delay_osintnesting:用于表示OS中断嵌套级别,因为中断里面不可以调度,delay_ms使用该参数来决定如何运行
    0 S3 I5 A4 P" y! U. ^" W2 {3 N
  19. //然后是3个函数:( c3 F; @2 b7 `* ~' I& k
  20. //  delay_osschedlock:用于锁定OS任务调度,禁止调度+ L" y9 r* ?  ~: d( A. |/ ~
  21. //delay_osschedunlock:用于解锁OS任务调度,重新开启调度- k6 j+ C$ M# g7 J5 t
  22. //    delay_ostimedly:用于OS延时,可以引起任务调度.
    1 {) T; |0 D0 Q4 @" _
  23. 7 I: C3 r+ |; c  A. H& ]& O
  24. //本例程仅作UCOSII和UCOSIII的支持,其他OS,请自行参考着移植% Q6 V3 o0 V: Y) ?( L! P
  25. //支持UCOSII
    0 l4 l% V& X" H4 ?
  26. #ifdef         OS_CRITICAL_METHOD                                                //OS_CRITICAL_METHOD定义了,说明要支持UCOSII                                % `# m$ e$ Q+ Y( D
  27. #define delay_osrunning                OSRunning                        //OS是否运行标记,0,不运行;1,在运行
    6 F% I! q8 }% Q; {% R9 c
  28. #define delay_ostickspersec        OS_TICKS_PER_SEC        //OS时钟节拍,即每秒调度次数
    , E) v! L2 K% }9 I5 \
  29. #define delay_osintnesting         OSIntNesting                //中断嵌套级别,即中断嵌套次数
    2 x" C8 u* j9 v
  30. #endif5 D7 i; K6 X8 b$ h7 Z; i7 B
  31. $ r" Y) |+ a  F! N+ n
  32. //支持UCOSIII5 P" ~. ]3 [! \0 `5 M
  33. #ifdef         CPU_CFG_CRITICAL_METHOD                                        //CPU_CFG_CRITICAL_METHOD定义了,说明要支持UCOSIII        , O4 W; @/ p& c, _( P, h7 a
  34. #define delay_osrunning                OSRunning                        //OS是否运行标记,0,不运行;1,在运行. G9 V5 F4 E$ l1 }6 q
  35. #define delay_ostickspersec        OSCfg_TickRate_Hz        //OS时钟节拍,即每秒调度次数
    # y3 v1 c( b: q* k! ]
  36. #define delay_osintnesting         OSIntNestingCtr                //中断嵌套级别,即中断嵌套次数
    8 \+ b5 q; i( W& C
  37. #endif
    " @0 r7 f; u, A( V7 m/ W

  38. ; |! D; B% e0 J: d4 a
  39. ( ~; R! }2 w. {7 x
  40. //us级延时时,关闭任务调度(防止打断us级延迟)
    : [. H$ ~" D* Z" w; W) C
  41. void delay_osschedlock(void)
    0 {& v2 v8 P- d0 m8 p
  42. {
    $ I' q& Z: }% |+ N+ T
  43. #ifdef CPU_CFG_CRITICAL_METHOD                                   //使用UCOSIII
    . Z! h/ F% r/ D. s: m" M
  44.         OS_ERR err;
    # X! n+ S. i& Y
  45.         OSSchedLock(&err);                                                        //UCOSIII的方式,禁止调度,防止打断us延时% ~' e3 c( Q9 l$ m* e  F2 ]
  46. #else                                                                                        //否则UCOSII& w. ^6 t1 |3 f* ]( |; \" U. B, ~
  47.         OSSchedLock();                                                                //UCOSII的方式,禁止调度,防止打断us延时
    9 S5 F4 X6 f& L: K5 H2 J1 \$ f
  48. #endif/ T; {( i. b6 P) b0 `
  49. }0 d( w$ r, b' l$ ^# ]  T" H

  50. ' H) A, z; e/ ~3 ~1 ~
  51. //us级延时时,恢复任务调度
    & S0 E( x. k1 P
  52. void delay_osschedunlock(void)
    % M" U; H4 a% b& F0 K7 H- t
  53. {        ' B1 C: T9 `* l- C
  54. #ifdef CPU_CFG_CRITICAL_METHOD                                   //使用UCOSIII8 P1 ^( \: J6 h4 B' R" }
  55.         OS_ERR err; 8 Z" i/ X0 ~# i1 ?! M1 D
  56.         OSSchedUnlock(&err);                                                //UCOSIII的方式,恢复调度2 G8 u3 W( `; N) h! K* R' r" [
  57. #else                                                                                        //否则UCOSII
    ( s5 m0 M$ t  S
  58.         OSSchedUnlock();                                                        //UCOSII的方式,恢复调度
    * V# q0 Q0 n" V
  59. #endif
    : y/ ]4 S0 X4 Q/ m
  60. }
    4 }6 ^6 o- r7 H# G8 Q0 u/ z
  61. ) B0 }, g1 ^6 ?6 E! ~
  62. //调用OS自带的延时函数延时
    & t5 }% h8 @2 Z  q
  63. //ticks:延时的节拍数3 p# l* H+ C- W  T. b4 \
  64. void delay_ostimedly(u32 ticks)
    ' R& \- G& p  p1 K7 w
  65. {2 A7 ], `5 C& u
  66. #ifdef CPU_CFG_CRITICAL_METHOD. o6 H6 I- o, }, [. s) U  A8 c
  67.         OS_ERR err; ' i6 X# Y% E9 e) |: _, k% r
  68.         OSTimeDly(ticks,OS_OPT_TIME_PERIODIC,&err);        //UCOSIII延时采用周期模式
    * m8 Z" J1 h8 p1 R" o
  69. #else
    ) N! l' Q* u3 U$ z8 m
  70.         OSTimeDly(ticks);                                                        //UCOSII延时# H/ k3 X! }1 z, x5 M* A
  71. #endif ; l' C4 y  T* r1 W! w  ~
  72. }
      U( A! g3 ^/ F! _( H" w
  73. ! L! y/ m* o; A7 `. y& k& ~3 J
  74. //systick中断服务函数,使用ucos时用到/ k, o1 Y3 t/ h. @+ n2 V7 g
  75. void SysTick_Handler(void)
    ' z7 p/ D, E4 t2 {% v$ ^$ ]0 Z
  76. {        # w& z- R" ~8 S0 P, ]( k- y5 m
  77.         if(delay_osrunning==1)                                                //OS开始跑了,才执行正常的调度处理
    % L% B9 s/ {" p8 O8 n
  78.         {7 m' ^) N1 [; X5 @6 K- z
  79.                 OSIntEnter();                                                        //进入中断& c4 z( d- Q; O) s, i. s
  80.                 OSTimeTick();                                               //调用ucos的时钟服务程序               - T( G' W% V9 t
  81.                 OSIntExit();                                                        //触发任务切换软中断
    ) p- @# e, G# f3 X
  82.         }
      M# F5 P5 o7 [
  83. }. C! u0 Z! S4 X
  84. #endif5 r6 {2 ]7 d) K' E
  85. 2 n8 F/ g) M2 O8 ]
  86.                            ! R" ^5 D9 a+ @! @
  87. //初始化延迟函数
    . o9 d( X4 W' J7 F
  88. //当使用OS的时候,此函数会初始化OS的时钟节拍* L5 M. J$ ?2 i* ^5 w9 ]% W' U+ b6 k2 Z
  89. //SYSTICK的时钟固定为HCLK时钟的1/8
    9 l& N; U  W: ]; ~% N' _
  90. //SYSCLK:系统时钟
    9 T! R8 k% z1 x$ f$ k+ R
  91. void delay_init()) y4 w& @3 N) ~3 I9 Q
  92. {0 T3 J! J7 G, [) N  l  M  F; S
  93. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.
    2 ^: H3 J% G  C" G  h
  94.         u32 reload;
    5 _# Q; M! E7 a: t9 e; q: N! L
  95. #endif3 [9 E/ G; D8 a8 l. y, h" o/ H
  96.         SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);        //选择外部时钟  HCLK/8
    1 e3 O7 k! o- L' B, o
  97.         fac_us=SystemCoreClock/8000000;                                //为系统时钟的1/8  , C4 h4 q# \, W4 r4 L9 B3 S( {
  98. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.
    ! `/ L& q- L- i- w, r( H) h  T9 z
  99.         reload=SystemCoreClock/8000000;                                //每秒钟的计数次数 单位为M  
    : y3 \8 N& @  |) S  ?
  100.         reload*=1000000/delay_ostickspersec;                //根据delay_ostickspersec设定溢出时间
    / C% k: p3 C8 @9 \$ t
  101.                                                                                                 //reload为24位寄存器,最大值:16777216,在72M下,约合1.86s左右        # {1 J" X' `) I5 m0 P( V' I
  102.         fac_ms=1000/delay_ostickspersec;                        //代表OS可以延时的最少单位           
    ; n; z3 n& [0 j; y
  103. * `/ M# `% f) x3 c: i7 O7 @2 m$ |% o' U
  104.         SysTick->CTRL|=SysTick_CTRL_TICKINT_Msk;           //开启SYSTICK中断1 ~; q# r+ X' v3 ~; _/ [. [
  105.         SysTick->LOAD=reload;                                                 //每1/delay_ostickspersec秒中断一次        
    - S7 H  \0 y0 \1 M; d1 z
  106.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk;           //开启SYSTICK   
    9 r( M8 x) l1 S: S  O2 l0 ~

  107. + R: w% s9 o% d# l. c
  108. #else. t' M6 W9 x6 M0 J6 x* `" b
  109.         fac_ms=(u16)fac_us*1000;                                        //非OS下,代表每个ms需要的systick时钟数   
    9 {6 |8 J8 l$ b3 X! O7 }8 |
  110. #endif0 [$ D1 C( i7 J& k5 l# ]/ N  G
  111. }                                                                    ) F, C& W- j: e( t: |
  112. . X$ T, {# o; n1 A! v# Z
  113. #if SYSTEM_SUPPORT_OS                                                          //如果需要支持OS.' y9 B* X# C* I0 Q
  114. //延时nus: d+ _) z! R1 L
  115. //nus为要延时的us数.                                                                                       9 n+ ?. U/ i6 E! \
  116. void delay_us(u32 nus)8 o* ^9 a. u; b2 k
  117. {                ' O1 A0 |0 f6 f5 r7 W
  118.         u32 ticks;9 J: }; [% z( B/ Y2 j( e/ V1 p7 b4 y
  119.         u32 told,tnow,tcnt=0;; l  w8 z( e/ N4 W2 O* U  o
  120.         u32 reload=SysTick->LOAD;                                        //LOAD的值                     
    ! }6 j  k! A$ Z$ {
  121.         ticks=nus*fac_us;                                                         //需要的节拍数                           
    3 \' g6 R0 U2 d* Q5 J
  122.         tcnt=0;
    7 J0 E/ V! ?# q& ^
  123.         delay_osschedlock();                                                //阻止OS调度,防止打断us延时. E$ R. Y4 Y, S& V
  124.         told=SysTick->VAL;                                                //刚进入时的计数器值
    6 U8 G" b2 }. ^3 Y; V; v
  125.         while(1)9 N: `+ d8 F- v
  126.         {
    ' O1 |5 O' C( o# Z8 `" Y  T# L
  127.                 tnow=SysTick->VAL;        + ]! r* l2 [5 Y* N) l
  128.                 if(tnow!=told)( E% \/ t# z3 }: p+ p1 [: ^
  129.                 {            
    ! Q9 }* p, m4 {& ~
  130.                         if(tnow<told)tcnt+=told-tnow;                //这里注意一下SYSTICK是一个递减的计数器就可以了.
    / T: `0 w1 g2 _  G3 B- G# y+ h3 _# B
  131.                         else tcnt+=reload-tnow+told;            ) {% F- I+ U, U% _0 m* n6 t
  132.                         told=tnow;
    6 @8 d0 t4 |0 X. H4 Q
  133.                         if(tcnt>=ticks)break;                                //时间超过/等于要延迟的时间,则退出.; A2 f. ~* ^) U  d
  134.                 }  ; p: h/ f7 n& q* Z6 A9 d6 i6 `/ X2 I) \) z
  135.         };% T8 L+ r4 P2 `$ Q* A
  136.         delay_osschedunlock();                                                //恢复OS调度                                                                            " c3 U- N: E0 t7 `& r) x" ~
  137. }# |; \8 L3 M% z% L  |0 g8 r5 h( p
  138. //延时nms
    & z' d+ O2 ]; Q0 m" o% G, A$ A  r& X
  139. //nms:要延时的ms数7 a4 P$ a0 o" r! ]* w: l3 ?
  140. void delay_ms(u16 nms)- D+ E: {9 Y+ L% d* G) _
  141. {        
    " f+ t: x; c/ ~% c+ u! l1 A
  142.         if(delay_osrunning&&delay_osintnesting==0)        //如果OS已经在跑了,并且不是在中断里面(中断里面不能任务调度)            
    # \3 X* r- i' r1 y
  143.         {                 - Y4 s/ [3 p9 }( O
  144.                 if(nms>=fac_ms)                                                        //延时的时间大于OS的最少时间周期
    7 M4 }1 p% c, O8 G" c! `& M
  145.                 { 4 Z/ r2 Q% f* U: S$ s* z! Z
  146.                            delay_ostimedly(nms/fac_ms);                //OS延时
    8 ]! I- c: w6 ^2 c* Q( E& A
  147.                 }' D/ I! e0 a2 b
  148.                 nms%=fac_ms;                                                        //OS已经无法提供这么小的延时了,采用普通方式延时   
    7 K" c% n8 a7 k7 ]3 q, ~- [
  149.         }
    5 ]2 T: X4 e: C7 U
  150.         delay_us((u32)(nms*1000));                                        //普通方式延时  % {& ~9 Z" \! T2 X) C0 b4 J
  151. }: \! ^0 P+ @% t/ s6 p
  152. #else //不用OS时1 l% i( K; G+ W7 x; |; ^
  153. //延时nus# X' X0 U7 J1 S4 P3 X9 Z
  154. //nus为要延时的us数.                                                                                       % r1 K" V8 s# J9 S# m' _1 ^4 Z
  155. void delay_us(u32 nus)
    ! M$ P7 b; |3 h9 _/ F1 B
  156. {               
    ) w2 c5 ]/ z0 |
  157.         u32 temp;                     1 P: N: N1 @# H
  158.         SysTick->LOAD=nus*fac_us;                                         //时间加载                           & ?2 H4 T( H+ ~7 R
  159.         SysTick->VAL=0x00;                                                //清空计数器/ h9 z3 l, t1 r5 b7 @
  160.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数          " f. x) K/ X& }, m* p' W
  161.         do, b1 G/ z/ ]5 n- N+ C
  162.         {/ y0 h1 d6 @8 B/ K1 _* m
  163.                 temp=SysTick->CTRL;
    3 F8 r) D/ d, t: X
  164.         }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   7 a. d2 J" T/ v" R# G+ _
  165.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器
    " K: Z8 ^- O+ t8 D
  166.         SysTick->VAL =0X00;                                               //清空计数器         $ p2 N5 b' B* y! W
  167. }
    ( F5 F  z  T, d. ~
  168. //延时nms
    # J0 V$ y# w1 i' p. f! y# Q7 }
  169. //注意nms的范围! n9 G6 r; l, H" x
  170. //SysTick->LOAD为24位寄存器,所以,最大延时为:
    + g3 N1 Q% ]8 X3 x4 _6 W) f" J% \
  171. //nms<=0xffffff*8*1000/SYSCLK  R' u- w; W% B3 t+ v! h% v+ A1 d
  172. //SYSCLK单位为Hz,nms单位为ms; D# I  l" w- a, I2 q$ d/ D
  173. //对72M条件下,nms<=1864 5 s% T8 ~+ a% R" W  L0 H
  174. void delay_ms(u16 nms)5 A$ j7 S/ M1 y* e
  175. {                                     : o' ]% E* y7 ?" r; l+ z
  176.         u32 temp;                   5 v  I. a# ^$ Q" n% V
  177.         SysTick->LOAD=(u32)nms*fac_ms;                                //时间加载(SysTick->LOAD为24bit)9 U4 [/ T0 q1 W  m
  178.         SysTick->VAL =0x00;                                                        //清空计数器- c9 l% a( O/ |
  179.         SysTick->CTRL|=SysTick_CTRL_ENABLE_Msk ;        //开始倒数    k1 s+ r6 U9 t8 `# @1 i$ Z
  180.         do. M: |3 U* M/ j) \5 A- |* G
  181.         {  E* T) w1 C+ q! p3 X
  182.                 temp=SysTick->CTRL;
    - a+ @7 S" m" ]$ o" c- d
  183.         }while((temp&0x01)&&!(temp&(1<<16)));                //等待时间到达   ( v. M$ b1 Z" g0 f& {0 x6 s
  184.         SysTick->CTRL&=~SysTick_CTRL_ENABLE_Msk;        //关闭计数器! Y+ ]9 _0 \$ t9 p
  185.         SysTick->VAL =0X00;                                               //清空计数器                     
    9 m9 l8 |4 n! |; o7 W- B
  186. }
    # S& n) y/ r6 P7 s
  187. #endif
复制代码
& n  ?, ~" c: h% }* V! ~
这里需要注意的是我们使用了外部时钟即系统时钟的8分频,这样确保时间足够长。另外,毫秒延时函数有最大上限,最大为1864(72MHz时钟频率)。用这种方式来延时的好处是不用中断比较简单,但是坏处就是最大延时时长有限(不到2秒),所以当需要长延时时间的时候要多写几个delay函数。; f3 T- d# o3 r8 E
) ~' P' r( r) S4 Q- o
; M6 G6 [5 n" S
总结
8 l& L$ ^2 B& Y5 v& T/ x        本节主要对STM32时钟系统进行讲解,我们从原理框图入手,对每一个时钟线路进行来源和去向的分析,介绍了时钟配置的相关寄存器以及STM32时钟的软件配置,最后通过SysTick定时器来编写延时函数。STM32的时钟十分复杂,希望读者能够反复阅读,内化于心,这样在以后的STM32开发以至于其它高性能SOC的学习中才能游刃有余。
5 A- h+ r- l( i; m3 y" K# [
8 f* B( I+ e! W# m& S" l2 [. {3 E
/ `, k8 _% g: d. v1 z7 i+ |: w$ ?* y+ s7 i
赞 收藏 评论0 发布时间:2022-5-7 11:31

举报

0个回答

所属标签

相似技术帖

官网相关资源

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