STM32F103系列的单片机一共有11个定时器:
8 W4 K1 ?" z- e- v. Y; K( `2 R7 m( A& M w' d+ @
2个高级定时器
* t1 D1 z: E- X; n3 U, A8 t' r/ n. ~1 I w U* X
4个通用定时器
. r. t& q* a. m' a5 m( _$ n% G# N9 X/ _+ ^0 H9 z( l
2个基本定时器' ^( Z5 p. D( E3 g5 b) Z
) ~2 L& Y7 D' d& ]' p) G2个看门狗定时器
) d$ T0 A1 v& u1 B( j
& ~# v# q3 e5 i& ?( `/ z1个系统滴答定时器
9 o+ u2 i; y/ L; u! t, G* w6 G6 m" Z0 w- R. a
除去看门狗定时器和滴答定时器,其他8个定时器列表:9 U7 }5 C" ]& ^5 ?4 S; z
. D' `3 w2 O: y; X
3 h, P) L7 Q1 u9 v' a
U* M. R3 a0 X2 e1 k1 b4 D其中,. x; x n K1 s! \6 l/ f7 y+ ]: {
' u$ \; ?8 v! L) U; c, w
TIM1和TIM8是高级定时器
" M. x6 a+ P9 F. j9 X
3 {5 a* K8 S, d% t' M5 t; b. j2 YTIM2 - TIM5是通用定时器
^; V! A6 N2 O# [, B& M
: _" _% X+ {& wTIM6和TIM7是基本定时器- ~3 f3 K- ]8 u2 z, e
5 o1 A! } f6 A) b
这8个定时器都是16位的,它们计数的类型除了基本定时器TIM6和TIM7,都支持向上、向下、向上/向下3种计数模式。: }0 k) `# @0 b/ G e/ y, u2 E
3 S. r% X% X% D9 j
# [: j: R$ B& G0 y. Z: }$ Z5 s3 e
计数器的3种计数模式:/ u( N- [6 h% Y; a
* Y. K5 O# j" k0 d, r& L2 h向上计数模式:从0开始,计到arr预设值,产生溢出事件,返回重新计时; s& X; h; V$ K5 q; l3 Q
N8 m0 {. c! s, i$ w
向下计数模式:从arr预设值开始,计到0,产生溢出事件,返回重新计时;2 D/ H! v) \2 t) O& ~( ~; p
( c3 X' b+ K5 G中央对齐模式:从0开始向上计数,计到arr产生溢出事件,然后向下计数,计数到1以后,又产生溢出,然后再从0开始向上计数(这种计数方式也叫向上/向下计数)。- s5 \* a6 _ {& v0 Z
9 Z8 a9 z! j, m+ p w9 O8 U" p, P+ p: C2 Y5 R
基本定时器(TIM6和TIM7)主要功能:2 h$ W" w# ?% f% Y
+ W! j" R" i2 n- ]只有最基本的定时功能。基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。; P+ B: H! U( j4 X% H
1 i5 T0 f: R# B0 R- n1 s0 ]
+ r4 p/ z% V4 {$ a通用定时器(TIM2 - TIM5)主要功能:
+ e @3 Q8 j6 Z# o) {/ V; f# ^2 o; V
除了基本的定时器的功能外,还可以测量输入信号的脉冲长度( 输入捕获) 或者产生输出波形( 输出比较和PWM)。; T. E0 J4 s/ r9 J0 j3 Z) v2 v
4 t5 X: j- P+ r; o0 c5 e- {: Y% w' q1 q: p+ I9 Z
高级定时器(TIM1和TIM8)主要功能:8 H6 t; B9 i: w- F
0 o3 Q7 p& e; h* T高级定时器不但具有基本、通用定时器的所有的功能,还具有控制交直流电动机的所有功能。比如它可以输出6路互补带死区的信号,刹车功能等等。
* U- a+ L7 g3 S5 r9 w
" j; c0 t7 T! G, h# y6 T( n; w5 g4 {
通用定时器的时钟来源:) ?4 a1 w2 [) y5 K
) O/ E: |* s5 J$ s3 n5 r8 J( h
a. 内部时钟(CK_INT)+ [; A M; G! E2 q$ T+ r& Z
8 [7 ~1 ~) w* |/ t% [1 S/ z4 |
b. 外部时钟模式1:外部输入脚(TIx)( ^: x& B* r! B0 }' `
& G, ] P: H! h" l- o4 K1 W3 g
c. 外部时钟模式2:外部触发输入(ETR)9 N4 s. `& o) ]# S) m( O/ F1 U+ r. k
7 S% b0 I3 P1 C& h+ N3 c8 N4 G& B+ Dd. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器5 [' l9 B# K/ y( y1 P8 | L
5 z" ^" X- q- {7 L8 _' {% Y& V# S
自动装载寄存器arr值得计算:
! e& g; i; I: n4 p U5 y7 H- u
Tout = ((arr + 1)*(psc + 1))/Tclk;
, r% I6 L. w$ y& m, Q" ?% Q! D, I F- m5 L4 F& b1 ?$ d3 |# x' T
Tclk:TIMx的输入时钟频率(单位:MHz)5 \8 N: L+ k8 x2 h
3 K' Z5 c6 B9 G# w
Tout:TIMx的溢出时间(单位:us)
3 y' O* N. x+ D6 `, f4 g' j! V3 G2 L3 Z
eg: 计时1秒,输入时钟频率为72MHz,psc预分频的值为35999,那么,((arr + 1)*(psc + 1))/72M = ((arr + 1)*(35999 + 1))/72M = 1s,就可以求解出自动预装载寄存器arr = 1999。
+ i) t8 O) A7 i) W) g- |* @* { O& r
: A5 v! a6 \- X3 D G定时器每次计数到预设值后,会产生一次中断更新事件,进入中断服务函数。因此,每个定时器有一个或者多个对应的中断处理函数的入口。
2 g2 V+ M) ^ t7 J4 s% H0 O( Y5 U# u: d0 b2 H, @
5 K) f! I8 c3 n/ d( {6 d: v对于STM32F103C8T6,需要注意的是,这款芯片没有基本定时器TIM6和TIM7。它的中断服务函数名如下:2 X8 U: L4 X; s ]
; |6 }6 u$ G1 L) O' u$ W5 u0 r
; ~1 f- E7 {) }. S$ e1 q/ O
; e3 l1 i$ X4 s不知小伙伴们还记不记得上一篇的精准延时?没错啦,精准延时那里就用到了系统滴答定时器。
3 n [3 n& g6 j3 z, a1 l' j8 H0 t4 u2 d
ARM Cortex-M3内核中有一个Systick定时器,它是一个24位(0~(2^24-1))的倒计数定时器,这个脉冲计数值保存在当前计数值寄存器STK_VAL(Systick current value register)中,当计数到0时,它就会从Load寄存器中自动重装定时初值,只要不把CTRL寄存器中的ENABLE清0,它就永不停。6 V" Y1 U4 u' j1 ^6 W! |+ t
! c9 y3 p7 m% M2 r
Systick定时器只能向下计数,每接收到一个时钟脉冲,STK_VAL的值就会向下减1,当减到0时,硬件会自动把重装载寄存器STK_LOAD(Systick reload value register)中保存的数据加载到STK_VAL,重新开始向下计数。如果STK_VAL的值被减至0时,会触发异常产生中断。所以,小伙伴们不要因为好玩,在程序里写一句 delay_ms(0) 或 delay_us(0)。这样会触发异常产生中断,到时你就找不着北啦~
* F5 |' d/ D! j6 _" L; m" @' Z& G- q: T3 ?7 g0 \. N y# K o
既然提到了精准延时,那这次小R给小伙伴们分享更加准确的LED灯闪烁时间,我们使用通用定时器中断来实现LED灯闪烁:
( L1 y$ |7 T( H1 N
[$ `7 B# y' G; b/ }2 C
& \0 }$ M( Z8 j- \1. 新建两个文件,tim.c 和 tim.h
. [ p& q7 p3 n' {0 D% w& C7 `2 ]$ G; v" W6 E
& V( Y2 L- |5 \) x/ @2 r' g+ j+ g
0 K5 U# E2 D3 F" Q1 w) \. |2. 在头文件 tim.h 添加下面代码:7 E& m" p2 a8 R8 F( \# Z7 C
" F: S$ I6 _/ ] P9 u4 A8 |- #ifndef _TIM_H9 m5 l- Y1 K* T1 V. b* ?7 [
- #define _TIM_H. G- Q/ }: R; d$ b* V% A
- #include "stm32f10x.h") U0 M/ ]+ ~1 @5 u. b4 l: a
. `2 W% J% z9 A! H( ?- void TIMx_Init(u16 arr, u16 psc);- y% P- F: i( Z1 I- Q* t, y$ a& j
- / u; [$ D) {( Q- F& r0 L
- #endif
复制代码 " F: q* Z# b7 y/ o0 |
7 f1 U! f& [( j9 s5 f5 X$ R
3 V9 F6 n; A- J0 Y2 x' V6 |) m' j; o1 H |+ q6 Q
3. 把 tim.c 添加到工程中
6 v0 m$ w0 `( t* a) ~9 j. s' V( ]7 t: ^; w9 h# ^
+ O' ]( k- B- c: G
' P2 m1 f3 |2 v! h+ L1 [: o% A. A
: _+ Q( N; {6 m5 P4. 在 tim.c 中添加以下代码:
- P* h3 e3 ]" v# e9 ]
e# |& _6 s \- #include “tim.h”
: G" D, o+ |# b6 L) H - ( F5 a d8 I( V8 t7 G3 p& @
- void TIMx_Init(u16 arr,u16 psc)3 }: m5 f/ Y' v5 [5 g) T
- {
4 k4 o3 P& ] _& P+ }) h - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //声明TIMx定时器结构体2 f' f; i" l$ g( B/ Q4 f; @4 w( r
- NVIC_InitTypeDef NVIC_InitStructure; //声明中断优先级结构体
# ]/ ?; o6 g% X) w. p9 { - 6 c( [+ T! p9 k, B
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先级分组0 ]8 o0 X7 y; _# ?# G$ F+ J, g
) |- {0 {" d) q, @8 x/ V9 ]; n- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能APB1总线上的TIM3时钟/ u- ^/ N8 t5 V# W! k* L
8 K- @, w# [6 |3 b2 a/ o. ^- TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件活动的自动装载寄存器周期的值
. S0 ^0 n1 V* B4 }* W - TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值 Y7 X% x) q6 ?5 H: L/ n
- TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
( g9 J7 b& y9 O: ], `8 Z - TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数8 x) P3 B" `% [' M# X5 e& U
- TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
) t) W) t% W4 H3 I) c5 u' R - / b$ I+ Y7 l! U: ?( ^& G4 G
- TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //允许TIM3中断更新
' F+ L) e2 ]7 p. e1 Y! ]
# c$ h* ^- G. F( K- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //配置外部中断通道
/ p6 ~3 Y' e. y/ { - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //设置抢先优先级为0% r5 r& ~6 W7 l5 w' Q9 m& G
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //设置子优先级为32 h; f+ [5 [/ r- d5 g* Z& B
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道$ o6 r0 o1 E/ G- S9 t9 F. s4 N* z
- NVIC_Init(&NVIC_InitStructure); //初始化中断优先级8 w% v w0 u4 X0 f0 [8 C
: }1 I2 l2 Y5 W. c- TIM_Cmd(TIM3,ENABLE); //使能TIM3
* x* `+ ?! m. T1 C9 k6 K; |) ]7 U - }- f. Q' K# l. K/ W7 K
1 ?/ H) a- B; U1 y
# C \0 U; V ^* n- void TIM3_IRQHandler(void) //TIM3中断服务函数5 x" R7 V; G8 ?* w4 e+ t
- {. ]2 S6 O* b& {6 ]1 V
- if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) //检查TIM3更新中断; r, n6 l! e8 E* R- ?. o z3 J' y& c
- {! h. F" `3 G9 O% [ [
- GPIOC->ODR ^= 0X0001<<13; //PC13高低电平交替翻转" M( c# ^4 G4 |' M. \
- TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIM3中断标志位
5 s5 r, I2 L- D' z7 z - }
( }. z% {- Z1 Q% Q) e - }
复制代码 # y$ U2 J$ v1 s; O6 S$ z
6 {) g( p" J3 b" x: h定时器中断配置步骤:
) A9 `( ]' y/ s* c& w9 V6 r
9 [# y& Z/ Z6 u- m1. NVIC优先级组配置* F0 |" a0 ?( ]
- R4 L, ~ P3 t9 D
2. 使能定时器时钟
7 R* T2 I9 i4 a+ j9 \+ o9 Q/ V2 |. D% c7 k9 Q( `
3. 初始化定时器
8 r0 G9 Z8 b7 E: ?% Z0 [% x
# i, f, _& x) o3 T- ~; P1 l" }4. 设置定时器允许更新中断1 Z. B0 w+ C" m
2 ^5 F: v6 R3 q+ Y! j! Y$ k5. 设置定时器中断优先级2 K0 o$ l# `0 U8 a
5 ?4 j/ H. D9 L( a2 L; B2 Q6. 使能定时器
. W/ J5 |2 p) V( O9 G0 W
' T/ e! @# E; V4 o* L% M% @7. 编写中断服务函数
8 D6 H( r( o2 f; V* L
2 { J$ {* E2 H9 f
" \ R: h, J6 g. c! u% D P; I0 {
* h5 G- _ |5 i5 ]9 T+ ^# @5 d# G9 M. y* P! Y$ o
* n% l9 o1 f& l& E$ s% G. Y* p5. 实现定时器更新中断LED灯闪烁功能- f3 j6 _. _5 A
0 v" {/ u; s5 p5 g- #include "stm32f10x.h"
4 |* f7 q4 g! V6 k. z. h; ` - #include "delay.h"* p0 z, i7 d2 j- L
- #include "led.h", p& `' ^: n" x2 |% s
- #include "tim.h"1 U2 L h& [8 g+ _6 J3 M! T. g
. M U% X. a& e! U- `9 S- int mian(void)7 L$ {4 }$ j7 y a, j: H# T
- {" J7 p: D2 p0 M1 a" t
- led_init();* H; e0 k% V, ^7 @' e
- TIMx_Init(1999, 35999);9 v# L$ p0 k& N. e/ {" w
0 C, ]2 t1 p" Y* p- Z' }& J- while(1): w. O$ L* `, B+ [4 A$ l
- {! \" o! R# J# R1 X0 H6 A
- }: W# |% L7 f9 u- d! N" s& A
- }
* {5 h+ |4 m9 \8 C! H. W& r0 z* N
复制代码 , x o% E7 D ?( n8 s0 t
, e. X, o3 o" H$ }7 M0 w0 u
" \& @0 h* j* L/ K1 n. J/ x
) X# k; L: X# ?5 L8 d8 r+ V& D. S0 H2 n
需要注意的是:在中断函数中需要检验一下标志位,因为定时器的所有事件共用一个中断。但只使用更新中断不用检验也是没有问题滴。
* g8 ?& @$ C9 q3 M6 l: p( J( _$ Y* \
/ C* C' H. F4 q2 U& @0 p |