STM32F103系列的单片机一共有11个定时器:
+ I" i |' Q& K- P9 z u2 T/ Q' f( A; T+ x; w3 W
2个高级定时器' U9 r8 w7 ^; |( a1 O/ k2 h M5 K6 ^8 |
) c. {- H/ \. |4个通用定时器( F9 F* p; n. O" P5 \8 t
# r, o' l, m% j8 H9 r2个基本定时器. U0 N3 g" q8 A) }% ^$ I7 `
. H0 S, F3 B) t5 F" r- s: B2个看门狗定时器
" |4 @) J/ K# i/ T; Y
1 b: [2 M+ }- l1 ^7 T% A1 J/ N1个系统滴答定时器# r9 z' q$ V: O6 ^. V+ J
5 W' G' ^2 A' Y# x除去看门狗定时器和滴答定时器,其他8个定时器列表:' q' `2 Z3 L! ]& n4 p
% [/ Z- p7 t; ]
- I, Q- E- }' x: t! C4 V% k7 t$ a' n+ o+ [: y8 P# a
其中,. x) C$ N+ Y7 n+ G* g
6 X& l# s. Z( \9 g+ U7 U
TIM1和TIM8是高级定时器
/ ]! W7 ^8 _7 }
" f3 Z' a% \: o1 z; r0 WTIM2 - TIM5是通用定时器
9 A- Q8 G* D: Q5 R- K+ N) t& `) S' U" x: K1 Z
TIM6和TIM7是基本定时器
- T8 Z* s, T/ b6 E8 Y8 D; o$ L. @6 E( h, t* V4 ^( F# k9 `
这8个定时器都是16位的,它们计数的类型除了基本定时器TIM6和TIM7,都支持向上、向下、向上/向下3种计数模式。
" r1 f3 @0 d4 ~! B5 ^* r$ r! H% f% H- D. ]
: a$ p& l0 S, z! W# s
7 n" ~; u. u2 r2 i7 W4 ` h计数器的3种计数模式:
! }0 O! s3 Q/ {- l: e( s, o; y
5 Q( d( W" |8 d# y ?) ?0 `向上计数模式:从0开始,计到arr预设值,产生溢出事件,返回重新计时;8 F% m* g0 h0 c
+ L t% ~, u/ P/ K6 P
向下计数模式:从arr预设值开始,计到0,产生溢出事件,返回重新计时;
s M! z, ]( k9 ?' a$ \' D
; w2 U8 H- Z: g5 f1 K' G中央对齐模式:从0开始向上计数,计到arr产生溢出事件,然后向下计数,计数到1以后,又产生溢出,然后再从0开始向上计数(这种计数方式也叫向上/向下计数)。- M5 i2 ~3 S3 M% Y
2 I" b! w2 |/ J% T4 x
& F" r, e2 ?: Z' ^" k; O1 s基本定时器(TIM6和TIM7)主要功能:
& Q! B$ h5 C4 k4 I e! s# O' k. h& I1 L- M! _# \% p
只有最基本的定时功能。基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。! m/ t+ F' |- X
, ]3 H {9 u4 f
; h8 {7 W& c* u6 E8 P5 U$ W- e通用定时器(TIM2 - TIM5)主要功能:
B" w$ ], S _7 T' e
. r: q; v% a- W, z/ k除了基本的定时器的功能外,还可以测量输入信号的脉冲长度( 输入捕获) 或者产生输出波形( 输出比较和PWM)。 S) {0 J4 w& z( {
1 y( L1 w1 h9 H; J6 c1 R: V: B# M; A/ C- j7 }" B" z# o7 J- T
高级定时器(TIM1和TIM8)主要功能:4 ?- C$ ^! D# `1 d0 }' I
! ~3 c' O* Q- [/ W2 G高级定时器不但具有基本、通用定时器的所有的功能,还具有控制交直流电动机的所有功能。比如它可以输出6路互补带死区的信号,刹车功能等等。
" q3 F1 g `. }# A; z0 @% k2 Y- `1 [ _( y( F5 D) n( t, C
# H8 n" a6 q$ ]2 o! e; }
通用定时器的时钟来源:
# ~: r8 s; z9 l" O, w1 }9 p; W: E. x$ V7 N' V/ P
a. 内部时钟(CK_INT)
' I3 `5 S( K& x6 p( s& }" q; l: l; ^7 J8 }6 W) z4 {
b. 外部时钟模式1:外部输入脚(TIx)
4 s9 C3 B3 O3 ^. B; D! q* P( |; A; o/ L, g9 K, r
c. 外部时钟模式2:外部触发输入(ETR)) Q1 c0 L& B8 \/ y0 S. H5 _
2 U8 d% }) R% S; K8 q
d. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器# }% B7 Q, W% H4 I% ?
) ^2 B# X, n% q, ]
$ f: ~6 q1 D" x: s; a自动装载寄存器arr值得计算:
* }# S3 H' \3 W5 J: ^, L6 I: X3 J. R" u, f/ u% J6 o4 E
Tout = ((arr + 1)*(psc + 1))/Tclk;% U! c. C- ^) @& q, N% e
7 f- [' i- w8 W" B2 d
Tclk:TIMx的输入时钟频率(单位:MHz)
, v6 d( h6 U {9 S b% u5 ?
9 q' X6 z, i- ?; _3 q, OTout:TIMx的溢出时间(单位:us)% x3 k2 w+ Z& [" I0 N' x
5 Y- u1 i' o o3 z1 v( q9 C2 `2 W( F) seg: 计时1秒,输入时钟频率为72MHz,psc预分频的值为35999,那么,((arr + 1)*(psc + 1))/72M = ((arr + 1)*(35999 + 1))/72M = 1s,就可以求解出自动预装载寄存器arr = 1999。
/ `8 y$ D9 Q0 Z* M
+ l' ?, I: y, k! x: G( n$ X6 S; e' `! ~: r, `; t
定时器每次计数到预设值后,会产生一次中断更新事件,进入中断服务函数。因此,每个定时器有一个或者多个对应的中断处理函数的入口。
8 I/ E; Z6 W8 [5 i
/ `3 K# B" i& S2 @* d- w" x
0 t( G4 a( w# B0 f4 u1 [: C& {. F对于STM32F103C8T6,需要注意的是,这款芯片没有基本定时器TIM6和TIM7。它的中断服务函数名如下:
! b# K( X) r8 z: n1 h: K+ Z' m v9 Z; b6 o
( E- `/ w: w, Q; N3 Y8 G9 [$ o+ K- `! g
: B6 ?. w. @0 ?0 Z; N: g
不知小伙伴们还记不记得上一篇的精准延时?没错啦,精准延时那里就用到了系统滴答定时器。
# @) Z" e: W( X3 e6 f% v$ k& p* }. r9 N# x# T1 k8 N
ARM Cortex-M3内核中有一个Systick定时器,它是一个24位(0~(2^24-1))的倒计数定时器,这个脉冲计数值保存在当前计数值寄存器STK_VAL(Systick current value register)中,当计数到0时,它就会从Load寄存器中自动重装定时初值,只要不把CTRL寄存器中的ENABLE清0,它就永不停。/ k" o, K! P7 t t6 h- Y ?
% E4 R4 x5 n) @" Y6 zSystick定时器只能向下计数,每接收到一个时钟脉冲,STK_VAL的值就会向下减1,当减到0时,硬件会自动把重装载寄存器STK_LOAD(Systick reload value register)中保存的数据加载到STK_VAL,重新开始向下计数。如果STK_VAL的值被减至0时,会触发异常产生中断。所以,小伙伴们不要因为好玩,在程序里写一句 delay_ms(0) 或 delay_us(0)。这样会触发异常产生中断,到时你就找不着北啦~$ E) W7 d6 v' n+ m, S
4 Z# U2 k# B+ E' D1 v
既然提到了精准延时,那这次小R给小伙伴们分享更加准确的LED灯闪烁时间,我们使用通用定时器中断来实现LED灯闪烁:3 H2 P1 C% V& F$ c' s
3 k0 l9 @& A. t
2 c3 R2 F% T6 \4 |5 j& W9 H6 w1. 新建两个文件,tim.c 和 tim.h
& ^& @2 H% A+ O% E+ L/ v) a% l4 D, [9 W; [, X9 z
9 k3 S$ H$ a- k, a
( _/ ^7 a, d$ k$ O+ S2. 在头文件 tim.h 添加下面代码:
" q+ S/ F, W4 d. A+ H& Q% |8 i% f& p' F: D$ D7 c
- #ifndef _TIM_H, w0 o8 t: f; q
- #define _TIM_H/ T q* }; g( `& E8 {$ G- r$ _6 H; k, h
- #include "stm32f10x.h"
1 u( t U. {8 C - ( K. u q; J! @$ c# J. {4 M
- void TIMx_Init(u16 arr, u16 psc);
# l' J) ?! c) T) B
& v* G# g4 o7 K& e, l- #endif
复制代码
( V' l8 i7 c c; l
( O& z( G5 B# q6 A4 g0 P
! M) e- z/ U- t' M" C
6 c* E1 c% @% c5 b) i" p) I
3. 把 tim.c 添加到工程中
, M: L# H. J- W* o
/ v: G% } v) ~6 y3 l
' |! T3 A, }3 s3 a5 O5 R0 |/ F
+ v4 o! m8 t% n! n
+ J$ @# d& M" h# W4. 在 tim.c 中添加以下代码:; W0 k. L2 q T' Q2 C# M$ i m
. X# F& c Q. E c6 K
- #include “tim.h”
U) v8 Y9 W/ z- \/ [9 @3 f# R - ( G9 c3 R" P0 W' e
- void TIMx_Init(u16 arr,u16 psc)8 O/ f& h9 a3 D f: b+ ^
- {
1 ~4 q0 f: N5 R( a, v - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //声明TIMx定时器结构体2 v8 g/ C, S; \4 t; G, e9 B: f
- NVIC_InitTypeDef NVIC_InitStructure; //声明中断优先级结构体7 l* F% c4 n; C, K. f
- - _. U8 Y* v" X |. W4 k* `: d
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先级分组* [8 h4 X5 q+ A0 a9 `: F0 j* h+ j/ w$ T) y
- 5 ?* s1 g) N2 w: z/ A& s; t
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能APB1总线上的TIM3时钟
. S; M$ Q1 B2 y - 8 M: f6 b/ v4 m- R( B( M, z
- TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件活动的自动装载寄存器周期的值( Q( N9 ]$ ?, K' j9 E J, m
- TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值9 Q! R9 \ s& m4 q! g% U
- TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
2 M1 Y4 o, ~ k7 y! c( R - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
0 h, `8 Q/ K! s9 H1 q - TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
1 k# I& ^1 P4 |
2 M, ^% G% w& v4 l( j$ m4 l) o, V- TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许TIM3中断更新; R1 f( {+ A$ J1 ?( c
- 1 y8 C) v" l+ n C7 W
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //配置外部中断通道0 \+ K& {4 A s: V
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢先优先级为0$ B( U9 X2 s; g$ o. d
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置子优先级为3. s. j6 J" s8 n2 _
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
+ e1 a' m+ o2 z6 L- l! J$ h$ o - NVIC_Init(&NVIC_InitStructure); //初始化中断优先级
+ ~4 R. S3 a7 k$ E0 T) X; a v - & M. W3 k/ m4 F5 o1 b
- TIM_Cmd(TIM3,ENABLE); //使能TIM35 i$ B6 `" b; K) U0 d: \) N. d3 t
- }: a! d) L0 t6 C, }" F! o- u
. _- n( E, h& b' X4 |
) ^' {, I* O2 o+ D, I- void TIM3_IRQHandler(void) //TIM3中断服务函数
! D( j! E. O- k3 w1 a - {' x$ ^: Z- @ k7 B4 ^% K, q+ J
- if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) //检查TIM3更新中断1 w8 r$ ]# g: t: G
- {
* J; b* o$ b. ]$ D - GPIOC->ODR ^= 0X0001<<13; //PC13高低电平交替翻转
: }& W- z9 Z3 x" f8 t# l - TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIM3中断标志位
0 v: |$ R7 i& \; O. z* y2 _' ~ - }( q8 O3 K7 z2 q7 f! i* U% ]2 _% I
- }
复制代码
. Y& v$ h0 o" Q1 V- @7 K7 U
" C7 {$ ?( |5 T, j. x, E y定时器中断配置步骤:2 Z6 f( {5 H1 f. X0 ~7 M
" d2 h5 s7 j2 A. K1. NVIC优先级组配置
0 t- x" Y, B. ~' ?; d( o1 r' G* J2 O( J% o. i3 x% [4 i
2. 使能定时器时钟
+ B/ i- @* Y7 W; \, ]. ~7 y8 H& g5 t6 P$ g5 _9 o
3. 初始化定时器
$ z6 K2 W8 C5 v! N# v8 A8 T( @/ C+ ]# N
4. 设置定时器允许更新中断
' r7 c0 Q9 h1 F5 _) P. c' q9 u' U9 F" b
5. 设置定时器中断优先级3 k2 r/ I# S( @. k& x
' I# V, R1 P- ]8 O+ o7 i6. 使能定时器. _) ]3 f% b1 b! G5 q
& V. u" o% [: ], M7. 编写中断服务函数2 _8 ^. ?2 _" Z0 G8 f- h
" @. Y) g/ q6 a9 m4 M& K
2 x/ Z0 F$ W/ Y1 B2 Z, D; M
+ J" i( { a' ]+ ^
% g+ n+ c+ d. q; | d( j
6 Z0 m6 \0 V% }! d# a7 `6 W1 R' I5 V e
5. 实现定时器更新中断LED灯闪烁功能+ y6 y) D# [& I4 Q( F
9 o" n0 }0 H( B* W1 Q- g- #include "stm32f10x.h"
7 m& e: \# r7 W/ a5 { - #include "delay.h") ?' O; B/ X* ~& c3 \6 M) L
- #include "led.h"4 V7 B, p; H+ _ M+ Z; {% s: W+ w! C
- #include "tim.h"( F. {' n0 a( T% a0 M: J
- . v% e6 G G" A" N
- int mian(void)9 H" G& Y! C K& I7 y
- {
! L9 m. \/ {0 p# N$ g3 s8 O - led_init();
$ ]- t5 N0 x5 b" [% e6 ~+ P - TIMx_Init(1999, 35999);
/ [; p; I M h. R: a, t
" C3 o' ]6 L2 C j2 _( M2 j- while(1)- I0 Q7 A* e0 ~: D
- {5 d! _ V" x$ ~
- }$ r. C% s$ m# X/ o8 `! H) \
- }! A! t8 n7 d3 L# ^2 s, `
复制代码
$ C8 ~: s: e* v) N1 l. D# D2 o
: W9 c1 \9 J' B" X8 ?" D2 x8 E8 q5 C8 J- |6 H: t/ P% w! w! m* ?* }
F3 O, Z) y4 K% o需要注意的是:在中断函数中需要检验一下标志位,因为定时器的所有事件共用一个中断。但只使用更新中断不用检验也是没有问题滴。1 ]0 J; c5 L0 f
, L/ s% @6 R2 `6 O; x# J
( m9 j# d3 I$ W7 ?: V |