OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 2362|回复: 1

通用定时器原理及代码讲解

[复制链接]

28

主题

28

帖子

0

精华

初级会员

Rank: 2

积分
116
金钱
116
注册时间
2019-10-16
在线时间
18 小时
发表于 2019-11-22 17:54:50 | 显示全部楼层 |阅读模式
向下计数模式(时钟分频因子=1
向下计数模式.png

向上计数模式(时钟分频因子=1
向上计数模式.png

中央对齐计数模式(时钟分频因子=1  ARR=6

中央对齐计数模式.png

计数器当前值寄存器CNT
该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。

CNT.png

预分频寄存器TIMx_PSC
该寄存器用设置对时钟进行分频,然后提供给计数器,作为计数器的时钟。
PSC.png
这里,定时器的时钟来源有4 个:
1 内部时钟(CK_INT
2 外部时钟模式 1:外部输入脚(TIx
3 外部时钟模式 2:外部触发输入(ETR
4 内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A B 提供时钟)。
这些时钟,具体选择哪个可以通过TIMx_SMCR 寄存器的相关位来设置。这里的CK_INT
时钟是从 APB1 倍频的来的,除非APB1 的时钟分频数设置为1,否则通用定时器TIMx 的时钟是APB1 时钟的2 倍,当APB1 的时钟不分频的时候,通用定时器TIMx 的时钟就等于APB1的时钟。


自动重装载寄存器(TIMx_ARR
ARR.png


控制寄存器1TIMx_CR1
控制寄存器.png
首先我们来看看TIMx_CR1 的最低位,也就是计数器使能位,该位必须置 1,才能让定时器开始计数。 从第 4 DIR 可以看出默认的计数方式是向上计数,同时也可以向下计数,第5,6位是设置计数对齐方式的。 从第 8 和第 9 位可以看出,我们还可以设置定时器的时钟分频因子为1,2,4


DMA中断使能寄存器(TIMx_DIER
这里我们同样仅关心它的第0 位,该位是更新中断允许位, 本章用到的是定时器的更新中断, 所以该位要设置为 1,来允许由于更新事件所产生的中断。

中断使能寄存器.png


1) TIM3 时钟使能。
TIM3 是挂载在 APB1 之下,所以我们通过 APB1 总线下的使能使能函数来使能 TIM3。调用的函数是:

  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
复制代码
2) 初始化定时器参数,设置自动重装值, 分频系数,计数方式等。
在库函数中,定时器的初始化参数是通过初始化函数 TIM_TimeBaseInit 实现的:

  1. voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
复制代码
第一个参数是确定是哪个定时器,这个比较容易理解。 第二个参数是定时器初始化参数结构体指针,结构体类型为 TIM_TimeBaseInitTypeDef,下面我们看看这个结构体的定义:

  1. typedef struct
  2. {
  3. uint16_t TIM_Prescaler;
  4. uint16_t TIM_CounterMode;
  5. uint16_t TIM_Period;
  6. uint16_t TIM_ClockDivision;
  7. uint8_t TIM_RepetitionCounter;
  8. } TIM_TimeBaseInitTypeDef;
复制代码
这个结构体一共有 5 个成员变量,要说明的是,对于通用定时器只有前面四个参数有用,最后一个参数 TIM_RepetitionCounter 是高级定时器才有用的,这里不多解释。第一个参数 TIM_Prescaler 是用来设置分频系数的,刚才上面有讲解。第二个参数 TIM_CounterMode 是用来设置计数方式,上面讲解过,可以设置为向上计数,向下计数方式还有中央对齐计数方式, 比较常用的是向上计数模式 TIM_CounterMode_Up 和向下计数模式 TIM_CounterMode_Down。第三个参数是设置自动重载计数周期值,这在前面也已经讲解过。第四个参数是用来设置时钟分频因子。针对 TIM3 初始化范例代码格式:
  1. TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  2. TIM_TimeBaseStructure.TIM_Period = 5000;
  3. TIM_TimeBaseStructure.TIM_Prescaler =7199;
  4. TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  5. TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
  6. TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
复制代码
3) 设置 TIM3_DIER 允许更新中断。因为我们要使用 TIM3 的更新中断, 寄存器的相应位便可使能更新中断。 在库函数里面定时器中断使能是通过 TIM_ITConfig 函数来实现的:
  1. void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
复制代码
第一个参数是选择定时器号,这个容易理解,取值为 TIM1~TIM17。
第二个参数非常关键,是用来指明我们使能的定时器中断的类型,定时器中断的类型有很多种,包括更新中断 TIM_IT_Update,触发中断 TIM_IT_Trigger,以及输入捕获中断等等。
第三个参数就很简单了, 就是失能还是使能。例如我们要使能 TIM3 的更新中断,格式为:
  1. TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE );
复制代码
4) TIM3 中断优先级设置。
在定时器中断使能之后,因为要产生中断,必不可少的要设置 NVIC 相关寄存器, 设置中断优先级。之前多次讲解到用 NVIC_Init 函数实现中断优先级的设置,这里就不重复讲解。
5) 允许 TIM3 工作,也就是使能 TIM3。
光配置好定时器还不行,没有开启定时器,照样不能用。我们在配置完后要开启定时器,通过 TIM3_CR1 的 CEN 位来设置。 在固件库里面使能定时器的函数是通过 TIM_Cmd 函数来实
现的:
  1. void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
复制代码
这个函数非常简单,比如我们要使能定时器 3, 方法为:
  1. TIM_Cmd(TIM3, ENABLE); //使能 TIMx 外设
复制代码
6) 编写中断服务函数。
  1. ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t)
复制代码
该函数的作用是,判断定时器 TIMx 的中断类型 TIM_IT 是否发生中断。 比如,我们要判断定时器 3 是否发生更新(溢出)中断,方法为:
  1. if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET){}
复制代码
固件库中清除中断标志位的函数是:
  1. void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT)
复制代码
该函数的作用是,清除定时器 TIMx 的中断 TIM_IT 标志位。 使用起来非常简单,比如我们在TIM3 的溢出中断发生后,我们要清除中断标志位,方法是:
  1. TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
复制代码


timer.h
  1. #ifndef __TIM_H
  2. #define __TIM_H

  3. #include "sys.h"

  4. void TIM3_Init(u16 arr,u16 psc);

  5. #endif
复制代码


timer.c
  1. #include "time.h"
  2. #include "led.h"


  3. void TIM3_Init(u16 arr,u16 psc)
  4. {
  5.        
  6.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitstruct;
  7.          NVIC_InitTypeDef NVIC_InitStructure;
  8.        
  9.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
  10.        
  11.         TIM_TimeBaseInitstruct.TIM_ClockDivision=TIM_CKD_DIV1;
  12.         TIM_TimeBaseInitstruct.TIM_CounterMode=TIM_CounterMode_Up;
  13.         TIM_TimeBaseInitstruct.TIM_Period=arr;
  14.         TIM_TimeBaseInitstruct.TIM_Prescaler=psc;
  15.         TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitstruct);
  16.        
  17.         TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
  18.        
  19.         NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;                        //使能按键TIME3所在的中断通道
  20.   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;        //抢占优先级2,
  21.   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;                                        //子优先级3
  22.   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                                                //使能TIM3中断通道
  23.   NVIC_Init(&NVIC_InitStructure);

  24.         TIM_Cmd(TIM3, ENABLE);
  25.        
  26.        
  27. }


  28. void TIM3_IRQHandler()
  29. {
  30.         if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
  31. {
  32.         LED1=!LED1;
  33.         TIM_ClearITPendingBit(TIM3, TIM_IT_Update );
  34.        
  35. }       
  36.        
  37. }
复制代码


main.c
  1. #include "stm32f10x.h"
  2. #include "led.h"
  3. #include "delay.h"
  4. #include "time.h"

  5. int main()
  6. {
  7. delay_init();
  8. LED_Init();       
  9. TIM3_Init(4999,7199);//500ms  一开始有这个警告implicit declaration of function "TIM_Init"is invalid in c99,(也能编译成功)莫名其妙没有了
  10. NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);       
  11. while(1)
  12. {
  13.        
  14.         LED0=!LED0;
  15.         delay_ms(200);
  16.        
  17. }       
  18.        
  19. }
复制代码


在前面时钟系统部分我们讲解过, 系统初始化的时候在默认的系统初始化函数 SystemInit 函数里面已经初始化 APB1 的时钟为 2 分频,
所以 APB1 的时钟为 36M, 而从 STM32 的内部时钟树图得知:当 APB1 的时钟分频数为 1 的时候, TIM2~7 的时钟为 APB1 的时钟,而如果 APB1 的时钟分频数不为 1,那么 TIM2~7 的时钟频率将为 APB1 时钟的两倍。因此, TIM3 的时钟为 72M,再根据我们设计的 arr 和 psc 的值,就可以计算中断时间了。计算公式如下:
Tout= ((arr+1)*(psc+1))/Tclk
其中:
Tclk: TIM3 的输入时钟频率(单位为 Mhz)。
Tout: TIM3 溢出时间(单位为 us)。

根据上面的公式,我们可以算出中断
溢出时间为 500ms,即 Tout= ((4999+1)*( 7199+1))/72=500000us=500ms。

正点原子逻辑分析仪DL16劲爆上市
回复

使用道具 举报

33

主题

1628

帖子

0

精华

论坛元老

Rank: 8Rank: 8

积分
6666
金钱
6666
注册时间
2015-8-25
在线时间
1035 小时
发表于 2019-12-4 13:24:29 | 显示全部楼层
He who fights with monsters should look to it that he himself does not become a monster, when you gaze long into the abyss, the abyss also gazes into you.
过于执着就会陷入其中,迷失自己,困住自己。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



关闭

原子哥极力推荐上一条 /2 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-5-23 20:49

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表