STM32F103系列的单片机一共有11个定时器:
$ o. {% w% O. U; C. W3 E8 @2 c$ Y Z) c# N9 a) b
2个高级定时器: a/ K, D ]; C9 \" O2 a4 I
2 n0 K7 C! }3 j; ?' B3 g. S/ T4个通用定时器 ^8 J) q g* t0 {
1 S: q) q% C: |) ]$ S$ W: q( {
2个基本定时器
1 ^5 {/ K/ E1 w V- \" Y! y" n4 d8 t) S
2个看门狗定时器
& J" h' p' c( X a6 y5 o9 `, E( g/ Q v
1个系统滴答定时器
: U5 r# u2 P' c) ~$ L& J2 O' G { T! p% E
除去看门狗定时器和滴答定时器,其他8个定时器列表:8 r Q. B8 R) ]2 F0 h/ ~ B+ S$ w! [
8 `7 {$ ~8 f& |2 i j* W3 |
! B9 U: n" U9 I7 ?3 H3 p) a9 Q( f1 K1 ]- H$ D
其中,
t' s. P- ?6 Z: I/ l V" M# V9 P. l
TIM1和TIM8是高级定时器5 [! k# U, x4 q# ^: e
3 d9 O2 ~2 {# F, ATIM2 - TIM5是通用定时器* c; c3 b* |0 R0 n
% S5 d9 o" F+ e6 D: y
TIM6和TIM7是基本定时器
5 E$ D- b: V" K Q/ d: Y i4 D
2 Y% Z% O+ O, c* J0 d这8个定时器都是16位的,它们计数的类型除了基本定时器TIM6和TIM7,都支持向上、向下、向上/向下3种计数模式。
* D2 Y) ?% J( m" H( c3 u9 X' u% V( L2 q2 o* k7 v/ |$ ~
/ k4 _7 L# n g% z6 C0 L
/ R: w1 W/ @9 \, R计数器的3种计数模式:
, T ]' B! s7 i8 b; T$ @- h, V! O% s( T' ~- B
向上计数模式:从0开始,计到arr预设值,产生溢出事件,返回重新计时;
. s: W, E! C7 F2 j- d/ b! i$ _" d
3 S4 o8 F8 E0 N6 U. d向下计数模式:从arr预设值开始,计到0,产生溢出事件,返回重新计时;+ U* }" f' E/ Y7 G$ X' Y) B* K
4 L3 o4 R! U: l3 F+ j中央对齐模式:从0开始向上计数,计到arr产生溢出事件,然后向下计数,计数到1以后,又产生溢出,然后再从0开始向上计数(这种计数方式也叫向上/向下计数)。+ H1 m" W% c6 _8 s0 u
8 v/ Z2 N, {6 R6 P2 b. i. ]3 |# ^$ m) l- l7 R
基本定时器(TIM6和TIM7)主要功能:
! y# P& x" A8 _& f }. m
/ I1 p8 W- J* s只有最基本的定时功能。基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
+ g7 P3 q! i9 z0 F) J9 t# B( q
3 ?. u1 R4 f+ i9 J6 I
( v+ [' _& `0 q, L, C5 j- O: ^通用定时器(TIM2 - TIM5)主要功能:
4 Q* D3 {" h% q) [9 g& {4 C! e- c" K7 v% w. n; f" D
除了基本的定时器的功能外,还可以测量输入信号的脉冲长度( 输入捕获) 或者产生输出波形( 输出比较和PWM)。
& b0 H& z5 K, O$ L4 ]7 q
3 c$ l: q2 D) C8 j! y, N8 }$ o& p, m! \$ w! D
高级定时器(TIM1和TIM8)主要功能:
7 @& [1 A% Q( }9 b$ I6 e1 r
, r: I$ a5 ?, G; g y0 o+ c高级定时器不但具有基本、通用定时器的所有的功能,还具有控制交直流电动机的所有功能。比如它可以输出6路互补带死区的信号,刹车功能等等。
' b b3 v' c, `$ a g) j( ]3 C
2 b6 q2 @" n8 N. k# |$ P6 E: J+ I0 X1 A3 P1 K; \
通用定时器的时钟来源:; Y7 c3 \3 D* p$ U$ g+ X4 j
! N' d4 M( n/ |% B" @' I
a. 内部时钟(CK_INT)- q/ j. J) H8 V; A- w
" ~: B& i% f, y7 j0 t8 O0 }3 jb. 外部时钟模式1:外部输入脚(TIx)0 Q0 }! k% Y/ F& d- _
* D6 |& X, k/ a, @% ]c. 外部时钟模式2:外部触发输入(ETR)
8 g9 n8 _# H/ u Q! \3 C# N) n
: L; F/ x: v9 L d" }8 ld. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器9 R F* y& E/ o# B
2 e2 Y& w9 U, Z. P% o0 y
1 N$ a' t! b( Y9 V自动装载寄存器arr值得计算:% w6 ]+ E/ {3 A: E: i2 b' _4 B
! G& Y# J" F% |* g# { n
Tout = ((arr + 1)*(psc + 1))/Tclk;
( F1 W- q2 K1 W W' d, K: \# W
3 N5 H2 H! S! d, v" ^. O; bTclk:TIMx的输入时钟频率(单位:MHz)0 M5 A! G- f% `2 g
4 X) X( i8 U0 ^$ f8 VTout:TIMx的溢出时间(单位:us)7 [* f! o+ {0 z' f9 f; ]
0 x) ~% n8 P& M) ?+ O. j
eg: 计时1秒,输入时钟频率为72MHz,psc预分频的值为35999,那么,((arr + 1)*(psc + 1))/72M = ((arr + 1)*(35999 + 1))/72M = 1s,就可以求解出自动预装载寄存器arr = 1999。
* W! c I% X2 f0 v+ i- z2 ?$ u. z$ P& |5 K0 R3 Z
* Y7 v$ w( } j) o4 [& x/ s C
定时器每次计数到预设值后,会产生一次中断更新事件,进入中断服务函数。因此,每个定时器有一个或者多个对应的中断处理函数的入口。
2 i4 n4 ?6 b) _- b D
9 i, k! t% E- `
- s2 x+ C5 m. {5 V6 H对于STM32F103C8T6,需要注意的是,这款芯片没有基本定时器TIM6和TIM7。它的中断服务函数名如下:/ N' V/ J5 m+ M- E# u" ~
+ c5 h! o! ?4 W7 p; G4 p# V4 \0 @7 n
, k u0 y2 n" r/ Y( w+ x3 t
+ c; V' A/ i+ D9 f6 |3 i6 a/ `不知小伙伴们还记不记得上一篇的精准延时?没错啦,精准延时那里就用到了系统滴答定时器。" ~& C9 o5 y4 H
7 \* c( z$ C3 eARM Cortex-M3内核中有一个Systick定时器,它是一个24位(0~(2^24-1))的倒计数定时器,这个脉冲计数值保存在当前计数值寄存器STK_VAL(Systick current value register)中,当计数到0时,它就会从Load寄存器中自动重装定时初值,只要不把CTRL寄存器中的ENABLE清0,它就永不停。 _4 O5 @) C' a& A7 ~% C
b% h- S" ^ \% N$ Q' ?Systick定时器只能向下计数,每接收到一个时钟脉冲,STK_VAL的值就会向下减1,当减到0时,硬件会自动把重装载寄存器STK_LOAD(Systick reload value register)中保存的数据加载到STK_VAL,重新开始向下计数。如果STK_VAL的值被减至0时,会触发异常产生中断。所以,小伙伴们不要因为好玩,在程序里写一句 delay_ms(0) 或 delay_us(0)。这样会触发异常产生中断,到时你就找不着北啦~
: X* x- a9 u2 i( ]- p3 v& o+ ^6 L7 w2 @3 O' x# b! G% J
既然提到了精准延时,那这次小R给小伙伴们分享更加准确的LED灯闪烁时间,我们使用通用定时器中断来实现LED灯闪烁:
. H! E' o' S* L( X, S, E. c# N: P8 X8 \1 S+ f
- G( Y; C9 i( p, U! q
1. 新建两个文件,tim.c 和 tim.h
3 d- ?9 Z: C! _& F
3 I6 O" o/ T' @' W/ R/ K
+ Z1 V4 f8 P/ \7 {" B2 F
/ H$ T1 H# z! W) i6 u2. 在头文件 tim.h 添加下面代码:# j, ^7 ]( j6 U0 K8 i+ E. X
) j$ m8 q2 G# |. h1 E+ L- #ifndef _TIM_H
0 z4 V4 i" k8 t) E8 l& _2 g- g! [ - #define _TIM_H
1 g& E+ @6 z& S- v - #include "stm32f10x.h"
- O7 u8 {9 Z& B, L - " p9 l: E1 k0 O( ^
- void TIMx_Init(u16 arr, u16 psc);
+ k8 G# a' E. z
) _3 J3 x8 n: C( Y/ v' `. }% `- #endif
复制代码
/ T, z" Q3 |" E2 c3 @, u
& i% A, c1 q( S9 X8 U a; h% C; P$ {) S! V& F
( }# s4 ^7 _- G
3. 把 tim.c 添加到工程中
5 E% I' H% [, \3 T; S6 W0 f5 q' h! h
% W1 z: x) S% b
7 g1 O+ K. u6 d K) i% E& E( s
& _* t4 P9 V$ O& r4. 在 tim.c 中添加以下代码:: d: v9 M# @/ e; T# ?6 Q( ]/ u
+ s; U: @1 ~" {- U" u
- #include “tim.h”
$ I* ?4 G. ~8 n F7 v - 6 r6 k( C" q# |6 C" b$ ^8 |) w/ K
- void TIMx_Init(u16 arr,u16 psc)
- n. \# w% \0 r" Y8 o d P - {
' f) {3 e5 l& i - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //声明TIMx定时器结构体
: b) C+ j L( Z6 r - NVIC_InitTypeDef NVIC_InitStructure; //声明中断优先级结构体0 C- R" z) u/ W) H* G( u
: Z/ W( [; p' x5 c6 m9 p- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先级分组
: [; U7 X2 x& V W, z/ O - 7 s @% Z% r+ \1 B# C2 _
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能APB1总线上的TIM3时钟9 Q P( P; {0 a
- 3 c% M( D" S) u& L6 s9 g% w) y
- TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件活动的自动装载寄存器周期的值0 x0 \9 U! l- u0 a
- TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
" O8 N/ ?# m- e - TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
# {) N" ~7 d. ^* \ - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
% E G" A _2 p7 k& E. I, A: V - TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
7 f9 p3 z ~ C) r) J4 q9 L - + ^' g6 e R! Z% \* V# T! `
- TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许TIM3中断更新
+ K% \! `- H$ j1 V" x" k- ^
6 o7 |( } O& Q1 I- R; f6 L# a- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //配置外部中断通道
7 @/ K- S4 f! p: q - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢先优先级为0
# L# d O! U h0 X k8 B0 G - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置子优先级为3) l; i' |/ q' I" w* V
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
0 c0 V9 w8 D: J1 j- ?; Y$ T- O$ K6 t6 j - NVIC_Init(&NVIC_InitStructure); //初始化中断优先级
" A0 j& G4 a. j' Y- U' M# x- e* t - 3 g; j/ h3 W. k' N( U. O2 H5 n
- TIM_Cmd(TIM3,ENABLE); //使能TIM3% I' l/ W4 e& g- G% ^* q* P' o
- }
. j) e3 ~: t" t+ }" e( S I4 n
/ {6 O: ~, o1 x$ T$ A& g7 U- 0 {- Z ?. g" L8 ~; ]. [& e0 f
- void TIM3_IRQHandler(void) //TIM3中断服务函数& J9 {5 l' t5 b3 U2 z, G/ {
- {9 `# A+ S2 K- h/ d
- if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) //检查TIM3更新中断
$ u& Z7 u0 _4 E - {
H' w/ j9 m$ k/ m; D - GPIOC->ODR ^= 0X0001<<13; //PC13高低电平交替翻转 i: D5 C/ k3 m, h m& v
- TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIM3中断标志位
5 w: v# V6 f! O& h - }
7 l( B) W3 ] v1 k# S - }
复制代码
3 T- X) p& m: w: L
! R+ C' N5 y. }3 g! v3 V2 |$ g$ {7 y定时器中断配置步骤:
; y3 X% U0 c3 E0 G- N: k8 f, `8 O: w, ~3 q6 P. L9 g0 \, H$ y
1. NVIC优先级组配置
/ L$ z6 T% p+ ]8 o5 l6 \7 B$ B: o& r* K
2. 使能定时器时钟
3 O/ ~( _8 E; i' i% C9 ]2 |% i! [( a# c- F. m& K
3. 初始化定时器
3 T( _5 {2 ?+ Q$ A& n5 a! _, }( g" C0 w8 t
4. 设置定时器允许更新中断5 Y1 C- Z/ u) e
! Y% V$ S- j/ |' s6 Q% x5 @
5. 设置定时器中断优先级
* y' E! t5 K0 R, z4 t% T8 O# T1 f! \) A
9 E3 K$ `1 v% c9 [! H6 F* K6. 使能定时器. N1 Q7 f9 m7 H# _( ^6 @
+ a4 K5 Y- r2 `: F" A* N4 s) j
7. 编写中断服务函数
0 F# g4 d& f9 N, n( T+ M1 M5 r8 N/ t3 F. ?
; J; W% h: j" q% v- L
# k% F+ b9 {2 f0 A- E0 U+ V6 x
" D7 B8 c" u& U2 ?3 `+ _) q, ?
* E$ V \. M9 R/ I* a
8 z& }1 e+ u. X5. 实现定时器更新中断LED灯闪烁功能$ p' p$ v+ r& {7 x5 W5 N
6 _: k* T ]( \/ \
- #include "stm32f10x.h"
+ B [7 w' W- u6 e& k - #include "delay.h"* r; r e# ~% D1 ?, I' C
- #include "led.h"
/ e9 z4 ?6 ~, x - #include "tim.h") `3 y5 B) Y) c- w0 F
% ^ N; Q4 s, Q, i$ r- int mian(void)+ @: k! `9 |, C6 z& n( u
- {
! x7 F7 E; w8 T+ ~$ s- q- o" p - led_init();
) N# _1 u: k1 v4 x/ z - TIMx_Init(1999, 35999);# g Z7 |; h5 z! q% Z
- 1 p# t7 R! j) E3 T% {
- while(1)
/ @0 E; k# H- r) r% `7 `" g - {
l# R2 E y9 C - }
\5 q; v8 C7 r o. E$ T - }
/ D4 b- f$ P' _: S- _4 G3 @
复制代码 & Q6 R% E7 Z- d, c2 G9 A7 W1 T
! I+ g% W3 `! D2 _
8 Y7 U8 l0 v c& O) V( w: g
. b: R8 n: n% V# V5 m. r需要注意的是:在中断函数中需要检验一下标志位,因为定时器的所有事件共用一个中断。但只使用更新中断不用检验也是没有问题滴。
* z/ _7 h* A1 b o
0 I4 v+ \- R7 @% \. S7 ?* `4 P- {
( ]' R/ V8 [7 A8 H9 T: {' N4 u& P |