你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32通用定时器使用详解

[复制链接]
STMCU小助手 发布时间:2021-2-23 09:37
STM32通用定时器使用详解
#1.通用定时器基本介绍


※       通用定时器包括TIM2、TIM3、TIM4和TIM5
※       STM32通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。
※       每个定时器都是完全独立的,没有互相共享任何资源。它们可以一起同步操作。
※       定时器可以进行定时器基本定时,输出4路PWM,输入捕获
※       本文详细介绍这三个功能并且利用定时器3并且示例代码使用

#2.开发环境


开发平台:keil5
单片机:STM32F103ZET6

#3.基本定时功能


## 3.1定时器时钟来源分析
     STM32部分时钟树:
1.1.png

3.1.1 首先我们我们的系统时钟(SYSCLK 72MHz) 经过AHB分频器给APB1外设,但是APB1外设最大的只能到36Mhz,所以必须要系统时钟的二分频。下面又规定了如果APB1预分频系数为1则频率不变,否则频率X2至定时器27,所以定时器27的时钟频率为还是72MHz


3.1.2 分配给我们定时器的时钟是72MHz,我们可以根据自己的需求再设置定时器的分频,设置它的定时值
  1. /*
  2.   * 初始化定时器的时候指定我们分频系数psc,这里是将我们的系统时钟(72MHz)进行分频
  3.   * 然后指定重装载值arr,这个重装载值的意思就是当 我们的定时器的计数值 达到这个arr时,定时器就会重新装载其他值.
  4.     例如当我们设置定时器为向上计数时,定时器计数的值等于arr之后就会被清0重新计数
  5.   * 定时器计数的值被重装载一次被就是一个更新(Update)
  6.   * 计算Update时间公式
  7.   Tout = ((arr+1)*(psc+1))/Tclk
  8.   公式推导详解:
  9.     Tclk是定时器时钟源,在这里就是72Mhz
  10.     我们将分配的时钟进行分频,指定分频值为psc,就将我们的Tclk分了psc+1,我们定时器的最终频率就是Tclk/(psc+1) MHz
  11.     这里的频率的意思就是1s中记 Tclk/(psc+1)M个数 (1M=10的6次方) ,每记一个数的时间为(psc+1)/Tclk ,很好理解频率的倒数是周期,这里每一个数的周期就是(psc+1)/Tclk 秒
  12.     然后我们从0记到arr 就是 (arr+1)*(psc+1)/Tclk
  13.   举例:比如我们设置arr=7199,psc=9999
  14.   我们将72MHz (1M等于10的6次方) 分成了(9999+1)等于 7200Hz
  15.   就是一秒钟记录9000数,每记录一个数就是1/7200秒
  16.   我们这里记录9000个数进入定时器更新(7199+1)*(1/7200)=1s,也就是1s进入一次更新Update
  17. */
  18. //简单进行定时器初始化,设置 预装载值 和 分频系数
  19. void MY_TIM3_Init(u16 arr,u16 psc){
  20.   
  21.   //初始化结构体
  22.   TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
  23.   
  24.   //1.分配时钟
  25.   RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
  26.   
  27.   //2.初始化定时器相关配置
  28.   TIM_TimeBaseStructure.TIM_Period = arr;
  29.   TIM_TimeBaseStructure.TIM_Prescaler = psc;
  30.   
  31.   /*在这里说一下这个TIM_ClockDivision 是设置与进行输入捕获相关的分频
  32.     设置的这个值不会影响定时器的时钟频率,我们一般设置为TIM_CKD_DIV1,也就是不分频*/
  33.   TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
  34.   TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
  35.   TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
  36.   
  37.   //3.打开定时器
  38.   TIM_Cmd(TIM3,ENABLE);
  39. }

  40. /****************** 主函数 ********************/
  41. //在主函数中我们可以调用初始化
  42. int main(){
  43.   //定时器初始化
  44.   MY_TIM3_Init(7199,9999);
  45.   while(1){
  46.    
  47.     //检测更新标志位
  48.     if(TIM_GetFlagStatus(TIM3,TIM_IT_Update)){
  49.       //清除标志位
  50.       TIM_ClearFlag(TIM3,TIM_IT_Update);
  51.       //....(每隔一秒执行任务)
  52.     }
  53.    
  54.   }
  55. }
复制代码

#4.定时器输出PWM

## 4.1基本介绍   

  4.1.1PWM是脉冲宽度调制,我们是通过改变脉冲的宽度来达到改变输出电压的效果,本质上就是调节占空比实现的,STM32除了基本定时器(TIM6,TIM7)不能输出PWM以外,其它的定时器都具有输出PWM,其中高级定时器(TIM1和TIM8)还能输出7路PWM,基本定时器(TIM2,TIM3,TIM4,TIM5)也可以输出4路PWM > 输出PWM是很有用的,比如我们可以通过控制电机来玩小车,或者通过输出PWM改变LED的亮度,制造呼吸灯等等

4.1.2 我们通用定时器能输出PWM的IO口是固定的,虽然我们可以通过重映射可以改变引脚,具体是哪一些IO口我们要通过查阅STM32的参考手册

这里涉及到一个重映射的概念,重映射就是管脚的外设功能映射到另一个管脚,但是不是可以随便映射的,具体对应关系参考手册上的管脚说明。这样优点是可以优化电路设计;扩展功能,减少外设芯片资源

  1. 时器3,可产生四路的PWM输出,四个通道分别对应的引脚情况如下
  2.   TIM3_CH1,TIM3_CH2,TIM3_CH3,TIM3_CH4
  3.   没有重映像的对应情况:
  4.   PA6,PA7,PB0,PB1
  5.   部分重映像:
  6.   PB4,PB5,PB0,PB1
  7.   完全重映像:
  8.   PC6,PC7,PC8,PC9  

  9.   当我们的IO口不仅仅是做普通的输入输出使用的时候,作为别的外设(AD,串口,定时器等)的特定功能引脚,就需要开启外设.
  10.   这里我们还需要开启APB2外设上的复用时钟AFIO,同时IO口采用的是复用输出!

  11.   我们这里是没有使用重映射功能.
  12. */
  13. // 宏定义
  14. //判断当前是处于哪一种模式,以便于我们初始化IO口
  15. #define NO_REAMP   0
  16. #define PART_REAMP 1
  17. #define FULL_REAMP 2

  18. // ---> 这里是需要制定的参数

  19. //指定这里的 当前的模式,我们给她默认指定是 没有重映射
  20. #define CURRENT_MODE NO_REAMP

  21. //*************根据当前模式初始化IO口 函数
  22. void MY_TIM3_GPIO_Init(void){
  23.   
  24.   GPIO_InitTypeDef   GPIO_InitStructure;
  25.   
  26.   //1.开启AFIO时钟
  27.   RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
  28.   
  29.   //2. 根据当前的重映像的模式 配置时钟 和 初始化相关引脚
  30.   switch(CURRENT_MODE){
  31.    
  32.     //2.1 如果没有重映射
  33.     case NO_REAMP:{
  34.       
  35.       // 时钟分配
  36.       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_GPIOB,ENABLE);
  37.       // 初始化IO口
  38.       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  39.       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  40.       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
  41.       GPIO_Init(GPIOA,&GPIO_InitStructure);
  42.       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;
  43.       GPIO_Init(GPIOB,&GPIO_InitStructure);
  44.       
  45.       break;
  46.     }
  47.     //2.2 部分重映射
  48.     case PART_REAMP:{
  49.       
  50.       // 时钟分配
  51.       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
  52.       // 初始化IO口
  53.       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  54.       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  55.       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1|GPIO_Pin_4|GPIO_Pin_5;
  56.       GPIO_Init(GPIOB,&GPIO_InitStructure);
  57.       
  58.       break;
  59.     }
  60.     //2.3 全映射
  61.     case FULL_REAMP:{
  62.       
  63.       // 时钟分配
  64.       RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
  65.       // 初始化IO口
  66.       GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  67.       GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  68.       GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
  69.       GPIO_Init(GPIOB,&GPIO_InitStructure);
  70.       
  71.       break;
  72.     }
  73.     default:break;
  74.   }  
  75. }

  76. //***************** 定时器PWM输出初始化函数
  77. void MY_TIM3_PWM_Init(u16 arr,u16 psc){
  78.   
  79.   //初始化结构体
  80.   TIM_OCInitTypeDef TIM_OCInitstrcuture;
  81.   
  82.   //1.初始化定时器 和 相关的IO口
  83.   MY_TIM3_Init(arr,psc);
  84.   MY_TIM3_GPIO_Init();
  85.   
  86.   //2.初始化PWM的模式
  87.   
  88.   /**
  89.   选择PWM模式:
  90.     PWM1模式:
  91.       向上计数时,当我们 当前的 计数值 小于我们的设置阈值为有效电平,否则为无效电平,向下计数时与向上计数时相反
  92.     PWM2模式:
  93.       与PWM1模式向上向下计数时完全相反
  94.   */
  95.   TIM_OCInitstrcuture.TIM_OCMode = TIM_OCMode_PWM1;
  96.   TIM_OCInitstrcuture.TIM_OutputState = TIM_OutputState_Enable;
  97.   TIM_OCInitstrcuture.TIM_OCPolarity = TIM_OCPolarity_High;   //输出电平为高,也就是有效电平为高
  98.   TIM_OC1Init(TIM3,&TIM_OCInitstrcuture);            //这里是设置利用通道1输出
  99.   
  100.   //这里只初始化通道1,我们可以根据自己需求初始化其它通道
  101.   
  102. //  TIM_OC2Init(TIM3,&TIM_OCInitstrcuture);
  103. //  TIM_OC3Init(TIM3,&TIM_OCInitstrcuture);
  104. //  TIM_OC4Init(TIM3,&TIM_OCInitstrcuture);

  105.   TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能预装载寄存器
  106. }

  107. //*********************主函数调用
  108. int main(){
  109.   
  110.   //因为我们单片机引脚输出电压3.3V左右,我们设置预装载值为330
  111.   MY_TIM3_PWM_Init(330,0);
  112.   
  113.   //我们初始化的时候选择的是PWM1模式,当计数值小于我们的设定值100时为有效电平,这里是高电平
  114.   //所以对于的1通道(PA6)电压是大概就是 3.3 * (100/330) = 1V 左右,我们可以用万用表测量
  115.   TIM_SetCompare1(TIM3,100);
  116.   
  117.   while(1);
复制代码

#5.定时器输入捕获

## 5.1基本介绍

※       上面介绍了定时器的四路通道可以输出PWM,同样的我们也可以捕获该定时器这四路通道上的边沿状态(上升沿,下降沿)

※       由此可见基本定时器也不能进行输入捕获,没有思路通道

我们可以通过输入捕获的来测量高电平脉宽时间,首先捕获到高电平,记录下改时间,然后切换为捕获低电平,得到时间

## 5.2开发步骤
###  输入捕获 (捕获边沿信号,上升沿和下降沿)
   首先我们需要以一定的频率检测电平的跳变,然后对部分跳变(也就是部分输入的波形)进行过滤------ 这就是定时器里面的滤波器的任务

1.      指定输入滤波器时钟频率,首先是系统时钟分给定时器72Mhz,我们首先初始化定时器的时候指定了TIM_TimeBaseStructure.TIM_ClockDivision= TIM_CKD_DIV1; 没有分频,输入给滤波器的时钟频率还是72MHz,TIM_ClockDivision也可以指定为2分频或者4分频

2.      波形过滤(TIM_ICFilter),这里有一个指定过滤器的参数(参考芯片手册),例如我们设置参数为0101(二进制),采样频率(fsampling)为 滤波器频率/2 = 36Mhz,N=8.当检测到一个上升沿的时候,再以fsampling频率连续8次检测到高电平才确认是一个有效的上升沿,这样可以滤除那些高电平脉宽低于8个采样周期的脉冲信号,从而达到滤高频波的效果。

1.2.png

3.      配置输入分频(TIM_ICPrescaler),如果我们设置不分频,一个边沿(上升沿或者下降沿)就触发一次捕获,二分频就是两次边沿触发捕获,这里这个分频可以为1,2,4,8

  1. //定时器输入捕获初始化
  2. void MY_TIM3_Cap_Init(u16 arr,u16 psc){

  3.   //初始化结构体
  4.   TIM_ICInitTypeDef TIM_ICInitStructure;
  5.   
  6.   //1.初始化定时器 和 相关的IO口
  7.   MY_TIM3_Init(arr,psc);

  8.   //这里的IO口根据自己需求改成输入,我这改成下拉输入,具体代码就不展现了
  9.   MY_TIM3_GPIO_Init();
  10.   
  11.   //2.初始化定时器输入捕获
  12.   TIM_ICInitStructure.TIM_Channel = TIM_Channel_1 ; // 设置输入捕获的通道
  13.   
  14.   //不使用过滤器,假设我们想使用,例如上述举例使用0101
  15.   //我们就给TIM_ICFilter  = 0x05 ,(0000 0101),根据上表可以知道这个值范围(0x00~0x0F)
  16.   TIM_ICInitStructure.TIM_ICFilter = 0x00;
  17.   
  18.   TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
  19.   TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;    //配置输入分频,这里不分频,1次检测到边沿信号就发生捕获

  20.   /*
  21.     这里说一下定时器通道可以进行交叉捕获,通道1捕获通道2引脚上的边沿信号,通道2捕获通道1引脚,通道3可以捕获通道4对应引脚,...
  22.     但是只能相邻一对可以相互捕获,例如通道2不能捕获通道3引脚边沿信号
  23.     TIM_ICSelection_DirectTI 表示直接捕获,通道1对应通道1引脚,通道2对应通道2引脚
  24.     TIM_ICSelection_IndirectTI 表示进行交叉捕获
  25.   */
  26.   TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射捕获对应通道的引脚
  27.   TIM_ICInit(TIM3,&TIM_ICInitStructure);                          
  28.   
  29. }
  30. //****************主函数
  31. int main(){
  32.   //初始化输入捕获
  33.   MY_TIM3_Cap_Init(1000,0);
  34.   
  35.   while(1){
  36.     //检测是否捕获到上升沿
  37.     if(TIM_GetFlagStatus(TIM3,TIM_IT_CC1)){
  38.       TIM_ClearFlag(TIM3,TIM_IT_CC1);
  39.       //捕获到上升沿之后的任务...
  40.       //一般测量高电平脉宽,我们可以先捕获上升沿再捕获下降沿
  41.       //TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); 修改为下降沿捕获
  42.     }
  43.    
  44.   }
  45. }
复制代码


#6.定时器中断

1.谈及到中断,我们就必须涉及到NVIC,具体关于NVIC请参考我的另外一篇,这里是直接使用,我们使能定时器3中断并且配置完抢占优先级和响应优先级之后,再在主函数中使能其更新中断和输入捕获中断

  1. //使能更新中断和输入捕获通道1的中断
  2. TIM_ITConfig(TIM3,TIM_IT_Update|TIM_IT_CC1,ENABLE);
复制代码


2.我们使用中断的一个主要目的就是能够及时处理信息,不用在主函数的while循环里面等待

  1. //定时器3的中断处理函数
  2. void TIM3_IRQHandler(void){
  3.   
  4.   //1.判断是什么中断
  5.   
  6.   // 1.1定时器更新中断
  7.   if(TIM_GetITStatus(TIM3,TIM_IT_Update)){
  8.     //...处理定时器更新之后任务
  9.   }
  10.   // 1.2如果是定时器 通道1的捕获中断
  11.   else if( TIM_GetITStatus(TIM3,TIM_IT_CC1) ){
  12.       //处理输入捕获之后的任务
  13.       //TIM_OC1PolarityConfig(TIM3,TIM_ICPolarity_Falling);更改为下降沿捕获
  14.   }
  15.   
  16.   //2.最后将中断标志位都清理掉
  17.   TIM_ClearITPendingBit(TIM3,TIM_IT_Update|TIM_IT_CC1);
  18. }
复制代码


文章出处: 智能车杂谈

收藏 1 评论0 发布时间:2021-2-23 09:37

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版