定时器的重要性我就不多说了吧,请往下看定时器(Timer)最基本的功能就是定时了,比如定时发送 USART 数据、定时采集 AD数据等等。如果把定时器与 GPIO 结合起来使用的话可以实现非常丰富的功能,可以测量输入信号的脉冲宽度,可以生产输出波形。定时器生产 PWM 控制电机状态是工业控制普遍方法,这方面知识非常有必要深入了解。. [3 x% w. A' `8 s$ P$ x3 p
STM32F4xx系列控制器有 2 个高级控制定时器、10 个通用定时器和 2 个基本定时器。
0 J5 E7 D7 c! @6 Y n
& ?$ a% D; b) b& u
! p6 N2 e1 s3 o; F5 f% g8 v/ ^这里通用定时器的时钟频率是由APB1的分频系数决定,如果APB1的预分频系数是1,则通用定时器的时钟频率等于APB1的时钟频率,否则为APB1时钟的2倍。
' m& G& m' g6 W, }
* O! W0 c8 r0 u( _3 A B时钟源 S$ P" B; K" u4 |/ Y/ W) A
定时器要实现计数必须有个时钟源,基本定时器时钟只能来自内部时钟,高级控制定时器和通用定时器还可以选择外部时钟源或者直接来自其他定时器等待模式。我们可以通过 RCC 专用时钟配置寄存器(RCC_DCKCFGR)的 TIMPRE位设置所有定时器的时钟频率,我们一般设置该位为默认值 0,使得表中可选的最大定时器时钟为 90MHz,即基本定时器的内部时钟(CK_INT)频率为 90MHz。基本定时器只能使用内部时钟,当 TIM6 和 TIM7 控制寄存器 1(TIMx_CR1)的 CEN 位置 1时,启动基本定时器,并且预分频器的时钟来源就是 CK_INT。对于高级控制定时器和通用定时器的时钟源可以来找控制器外部时钟、其他定时器等等模式,较为复杂。
0 i4 g( `6 P: g/ Z8 S# i9 o
6 B9 M' o" q" m( m$ p
2 }, |" @% \8 U1 ~4 r; c( @. P W F% N1 Y
使用SystenInit函数初始化的时候,各时钟频率如下:. B. i* m3 \2 r+ y! y! A
SYSCLK = 72M
9 m' D H3 e2 kAHB时钟 = 72M
5 p: v1 G8 m, b: t" k, |6 IAPB1时钟=36M
5 ?1 e( L' \0 C3 b所以APB1的分频系数=AHB/APB1=2" b2 t& T% B) d
由此可得CK_INT的时钟频率为2*36M = 72M.
% }1 c6 V5 U: v: \计数器的最终的频率还需要经过PSC预分频计算才能得到
q3 V/ K8 ]. Q! {! x9 b4 _0 E
5 `7 P* A' _5 O% e
4 z9 i1 z: B5 G: t' i5 G7 l计数器基本定时器计数过程主要涉及到三个寄存器内容,分别是计数器寄存器(TIMx_CNT)、预分频器寄存器(TIMx_PSC)、自动重载寄存器(TIMx_ARR),这三个寄存器都是 16 位有效数字,即可设置值为 0至 65535。! p! k9 A7 z+ t
0 J2 S6 q' o M, I8 C S) \ Q
- M# P: E8 Q$ \- j F" v- k, W" n9 {7 b# B3 R8 W) u
定时器周期计算定时事件生成时间主要由 TIMx_PSC 和 TIMx_ARR两个寄存器值决定,这个也就是定时器的周期。比如我们需要一个 1s周期的定时器,具体这两个寄存器值该如何设置?
4 P9 I1 r1 I* t/ S! n假设,我们先设置 TIMx_ARR寄存器值为 9999,即当 TIMx_CNT从 0开始计算,刚好等于 9999时生成事件,总共计数 10000次,那么如果此时时钟源周期为 100us即可得到刚好 1s的定时周期。. q# H0 `) e/ [) D7 _, W5 p
接下来问题就是设置 TIMx_PSC寄存器值使得 CK_CNT 输出为 100us 周期(10000Hz)的时钟。预分频器的输入时钟 CK_PSC为 90MHz,所以设置预分频器值为(9000-1)即可满足。 $ e3 D5 K- l; \" o
定时器初始化结构体详解
" H& V1 i: [% }; x1 [6 I( O- <div><span style="color: rgb(0, 0, 0); font-size: medium; font-family: Tahoma;">typedef struct</span></div><div><font face="Tahoma"><font size="3"><font color="#000000">{! `; R: Z% L) \6 V/ \- v a
- uint16_t TIM_Prescaler; // 预分频器 p. \& B3 k$ V# P& [
- uint16_t TIM_CounterMode; // 计数模式- I$ x3 B. w4 |5 ]/ W- s
- uint32_t TIM_Period; // 定时器周期; y) Y+ N4 g. h. ~' q8 a* T0 x
- uint16_t TIM_ClockDivision; // 时钟分频
2 M4 v* ?3 J1 Q) b( W b, b+ O - uint8_t TIM_RepetitionCounter; // 重复计算器% i; h) h! Y) t4 [; a
- } TIM_TimeBaseInitTypeDef;</font></font></font></div>
复制代码 % G* u2 r2 r7 R9 r6 C
6 n$ r$ z6 Z9 ~' K8 \
(1) TIM_Prescaler:定时器预分频器设置,时钟源经过该预分频器才是定时器时钟,它设定TIMx_PSC 寄存器的值。可设置范围为 0 至 65535,实现 1至 65536 分频。为啥要搞一个预分频器,那是因为系统时钟频率太快了,90MHZ啊,这一般人定时器可顶不住这么快的速度,所以分频一下,让他的给定时器的时钟频率少一点,仅此而已。
. {6 o$ t. i" v& p4 m(2) TIM_CounterMode:定时器计数方式,可为向上计数、向下计数以及三种中心对齐模式。基本定时器只能是向上计数,即 TIMx_CNT只能从 0开始递增,并且无需初始化。
: E) p7 P, r# K3 `2 @(3) TIM_Period:定时器周期,实际就是设定自动重载寄存器的值,在事件生成时更新到影子寄存器。可设置范围为 0至 65535。
# `* o9 Q) i8 M* U* P c自动重载寄存器的值:举个例子,你要往桶里面放水,水满了之后把它倒掉。那装满需要多少水呢?就给他设定一个值,滴水滴100000滴才满,拿去倒掉。倒掉之后,在重新设置滴100000滴,满了再倒掉……( W, c/ i7 Z& @
(4) TIM_ClockDivision:时钟分频,设置定时器时钟 CK_INT 频率与数字滤波器采样时钟频率分频比,基本定时器没有此功能,不用设置。
# W0 \- b! w. @; y0 H& y% P, l/ H(5) TIM_RepetitionCounter:重复计数器,属于高级控制寄存器专用寄存器位,利用它可以非常容易控制输出 PWM 的个数。这里不用设置.
u3 w5 e: n, }1 g: O9 u# p 程序配置设置通用定时器,并产生相应中断,主要分为以下几个步骤(以TIM3为例) TIM3时钟使能设置TIM3_ARR和TIM3_PSC的值 设置TIM3_DIER允许更新中断 允许TIM3工作 TIM3中断分组设置 编写中断服务函数 + V R" P3 ?3 I0 {5 w# [6 ^% f
- void TIM3_Int_Init()
7 E# R& V! z! |- O - {
" S- q+ N5 C& g. N - TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;' K5 u" _& _0 Y6 W: `0 [
- NVIC_InitTypeDef NVIC_InitStructure;
5 C2 v3 ~2 p q- ?3 n) Z - ) g0 R. W) X, X- F0 o
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
4 L. y5 t1 j6 ^3 i7 T3 H, Y - TIM_TimeBaseStructure.TIM_Prescaler =7199; //设置用来作为TIMx时钟频率除数的预分频值 10Khz的计数频率
$ e. T9 J' H- ^( |4 N( A3 t - TIM_TimeBaseStructure.TIM_Period = 4999; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值 计数到5000为500ms
3 p' l7 L& a+ d8 Y: p - TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim- @7 C- O* h* c
- TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式" c0 c% o9 ]+ c. e+ d
- TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位 9 Q) G: X2 ~' I2 b7 s* B
- TIM_ITConfig(TIM3, TIM_IT_Update,ENABLE);. [6 @* K; ~" P5 s" z+ k
- 4 Q& K2 g6 F! |0 c# W5 T
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断5 U5 T) W* A8 |" P
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级( G9 y" U( U: l* ]# @4 A% A1 ^/ F
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
N2 c: z1 q) `$ q - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
3 x3 o) f2 l J* ]$ p6 b - NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器" h' W9 ~# H _2 ~! v& t
- TIM_Cmd(TIM3, ENABLE); //使能TIMx外设 1 {# t% l: d h2 B
- }
复制代码 # w# P( o+ I9 c3 n4 a6 z
使用定时器之前都必须开启定时器时钟,基本定时器属于 APB1总线外设。APB1总线外设时钟=72M。我们把定时器设置自动重装载寄存器 arr 的值为4999,设置时钟预分频器寄存器psc的值为7199,则驱动计数器的时钟:CK_CNT=APB1_CLK/(7199+1)=72M/7200=10K 则计数器计数一次的时间等于: 1/CK_CNT=0.0001s=0.1ms=100us, 当计数器从0计数到4999时,产生一次中断,则中断一次的时间为:100usX5000=0.0001sX5000=0.5s=500ms也就是半秒钟。
4 A4 v% P; `# W6 ~3 M: T# \- m- void TIM3_IRQHandler(void) //TIM3中断
2 N5 ]2 x5 X+ e - {& G. z! z) R+ N2 B3 _
- if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
' j \( x; e; ~7 r9 h8 j - {5 z, [* r$ R3 D+ a$ T9 M. N" k5 l
- TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源
( t) R, [. f+ } - LED1=!LED1;
* x+ t/ O+ p& z' S - }
) `* M& v* r! L$ q3 \$ |" p6 I - }
复制代码 9 A, k8 R5 m/ N$ h
这个中断服务函数开始用if语句和TIM_GetITStatus()函数判断是否TIM3发生了中断,如果发生了中断就清除TIM3的中断标志位。让LED1灯反转。 - int main(void)# A, C7 k s9 z/ l) _3 I9 |9 D1 G
- {
9 p1 j ]% Z/ P, A - delay_init(); //延时函数初始化
0 q% m) `# F, k* y8 C5 ?1 Y - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);// 设置中断优先级分组2
7 |- d# a. K F) P - LED_Init(); //初始化与LED连接的硬件接口
$ Y' K' K; I! j6 o1 o# g - TIM3_Int_Init(); //10Khz的计数频率,计数到5000为500ms 4 U' z4 S) b+ p; d9 I6 {
- while(1)3 A7 z" u% R8 u( n0 P( u
- {% `( Y( ^. n) R3 _, v* z
- LED0=!LED0;1 D8 c( x! }0 g1 O
- delay_ms(200); 1 c% H/ }4 I, w6 t) K. s/ C
- }
- T2 b0 ?; ^3 Z' U) r2 q - }
复制代码
/ D) p3 ?8 |/ E, Y3 i; }主函数首先延时函数初始化、设置中断优先级分组2、初始化与LED连接的硬件接口、定时器3初始化。至此LED0就会每个0.5秒翻转一下。同时我们为了比较在while函数中让LED1灯0.2秒翻转一下做对比。现在我们来用keil仿真一下,看看是不是LED0是0.2ms翻转一下,LED1是0.5ms翻转一下。 X9 [! V3 |, _2 u# t# W7 G
软件仿真1.配置keil仿真调试工具
: I* D4 W! ?4 b( P3 } S
% m5 ^2 W9 R5 C# |( I, q
+ h7 H2 _3 N- f% U& n1 Q
) T+ k$ p% U- b7 z% Y! S% p2.打开调试, 进入调试界面后 ,打开logic analysis窗口,并设置PWM输出引脚
( P- S1 x: C, f/ }
+ e4 v F# d6 ?, T5 e$ c
- X' x$ W$ W) ]; i8 |+ F
) D& F0 X0 N% l: [! D6 r n
1 o6 o# s+ g1 g# o/ n( L' Z% ^7 B5 ?
& l% V* [6 N" N$ m; Y! R
9 P6 J1 p3 ^& ?0 v2 H. c7 s
1 f, D7 M+ X8 m2 d
3.点击全速运行,观察示波器) ^8 _( e5 ?% T
4 m# @; K9 Z3 a7 m' x! o- }
3 k6 o4 a$ O$ l7 P2 F' H4 |* i8 R0 R( ], V. y
I" L5 {: r1 h. h; h$ ^" k& j% W4 |& } E; y7 z4 a n
可以清楚到看到LED0是0.5ms翻转一次。1 R3 l! Y" U. B) K3 S
4.用同样的方法配置另一个LED1,发现是0.2ms翻转一下,和程序配置的一样4 O+ F" N3 X0 s$ L
$ s; v- s' u# Y2 n6 a5 G
; U2 c; k- A/ I6 C3 h
9 k4 a3 E' }, I( z" C
0 {4 c' n- {/ D' ?4 E) J总结:我们使用定时器的时候主要是要清楚分频系数和重装载值。这两个决定了你要定时多长时间,另外如果你想使用stm32定时器的定时功能的话,需要定时多长时间做相应的操作,直接在中断服务函数里面写你的代码就行了,用中断也一样,要干啥事直接在你的响应的服务函数中做操作就行了。还有定时器和计数器的分别,定时器就是计数器啊。你先计数,计数到一定程度溢出不就是实现了定时吗?所以初学者不要纠结。
1 U+ G) z+ D- Z, T$ y) _& `8 |' Z9 e
转载自: 果果小师弟 2 @( ]+ E- u9 Y& ^
0 \- m- t" v: D( {* M0 t. Z |