一、通用定时器基本介绍

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

二、基本定时功能

1、定时器时钟来源分析

1、1 STM32部分时钟树

首先我们我们的系统时钟(SYSCLK 72MHz) 经过AHB分频器给APB1外设,但是APB1外设最大的只能到36Mhz,所以必须要系统时钟的二分频

1、2 定时器时钟计算方法: 

 1、3 分配给我们定时器的时钟是72MHz,我们可以根据自己的需求再设置定时器的分频,设置它的定时值

    * 初始化定时器的时候指定我们预分频系数psc,这里是将我们的系统时钟(72MHz)进行分频

    * 然后指定重装载值arr,这个重装载值的意思就是当 我们的定时器的计数值 达到这个arr时,定时器就会重新装载其他值.

        例如当我们设置定时器为向上计数时,定时器计数的值等于arr之后就会被清0重新计数

    * 定时器计数的值被重装载一次被就是一个更新(Update)

  * 计算Update时间公式
    Tout = ((arr+1)*(psc+1))/Tclk

 公式推导详解:
        Tclk是定时器时钟源,在这里就是72Mhz 

        我们将分配的时钟进行分频,指定分频值为psc,就将我们的Tclk分了psc+1,我们定时器的最终频率就是Tclk/(psc+1) MHz


 *这里的频率的意思就是1s中记 Tclk/(psc+1)M个数 (1M=10的6次方) ,每记一个数的时间为(psc+1)/Tclk ,很好理解频率的倒数是周期,这里每一个数的周期就是(psc+1)/Tclk 秒

然后我们从0记到arr 就是 (arr+1)*(psc+1)/Tclk
    举例:比如我们设置arr=7199,psc=9999

    我们将72MHz (1M等于10的6次方) 分成了(9999+1)等于 7200Hz
    就是一秒钟记录7200数,每记录一个数就是1/7200秒
    我们这里记录9000个数进入定时器更新(7199+1)*(1/7200)=1s,也就是1s进入一次更新Update
 

2、常用库函数

定时器参数初始化:

void TIM_TimeBaseInit(TIM_TypeDef* TIMx,,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);

从0到溢出的时间是由两个决定:

1、时钟频率

2、自动装载值

Tclk: 时钟频率=72MHZ

若想定时500ms

PSC预分频系数设为:7199               ARR自动装载值:4999

定时器中断实现步骤  

 能定时器时钟。

      RCC_APB1PeriphClockCmd();

②  初始化定时器,配置ARR,PSC。

      TIM_TimeBaseInit();

③ 开启定时器中断,配置 NVIC 。
      void TIM_ITConfig();

      NVIC_Init();

④  使能定时器。

      TIM_Cmd();

⑥  编写中断服务函数。

      TIMx_IRQHandler();

3、代码区

 题目:通过定时器中断配置,每500ms中断一次,然后中断服务函数中控制LED实现LED1状态取反(闪烁)

*timer.c
 
#include "timer.h"
#include "led.h"
#include "stm32f10x.h"
 
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_init(u16 arr,u16 psc)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitTStrue;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //时钟使能
	
	//定时器TIM3初始化
    /*在这里说一下这个TIM_ClockDivision 是设置与进行输入捕获相关的分频
	设置的这个值不会影响定时器的时钟频率,我们一般设置为TIM_CKD_DIV1,也就是不分频*/
	TIM_TimeBaseStructure.TIM_Period = arr; 
    //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	
	TIM_TimeBaseStructure.TIM_Prescaler = psc;//设置用来作为TIMx时钟频率除数的预分频值
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数模式
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
	
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//使能指定的TIM3中断,允许更新中断
	
	//中断优先级NVIC设置
	NVIC_InitTStrue.NVIC_IRQChannel = TIM3_IRQn;//TIM3中断
	NVIC_InitTStrue.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_InitTStrue.NVIC_IRQChannelPreemptionPriority = 0;//先占优先级0级
	NVIC_InitTStrue.NVIC_IRQChannelSubPriority = 3;//从优先级3级
	NVIC_Init(&NVIC_InitTStrue);				//初始化NVIC寄存器
	
	TIM_Cmd(TIM3,ENABLE);//使能TIMx			
}
 
//定时器3中断服务程序
void TIM3_IRQHandler()//TIM3中断
{
	if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
	{
		TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除TIMx更新中断标志 
		LED1 = !LED1;
	}
}
*timer.h
 
#ifndef _TIMER_H
#define _TIMER_H
#include "stm32f10x.h"
 
void TIM3_init(u16 arr,u16 psc);
 
 
#endif
 
 
*main.c
 
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "timer.h"
 
int main(void)
{	
	Led_Init();
	delay_init();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	TIM3_init(4999,7199);
	
	while(1)
	{
		LED0=!LED0;
		delay_ms(200);			
	}
}

三、定时器输出PWM

3.1基本介绍

3.1.1 PWM是脉冲宽度调制,我们是通过改变脉冲的宽度来达到改变输出电压的效果,本质上就是调节占空比实现的,STM32除了基本定时器(TIM6,TIM7)不能输出PWM以外,其它的定时器都具有输出PWM,其中高级定时器(TIM1和TIM8)还能输出7路PWM,通用定时器(TIM2,TIM3,TIM4,TIM5)也可以输出4路PWM,输出PWM是很有用的,比如我们可以通过控制电机来玩小车,或者通过输出PWM改变LED的亮度,制造呼吸灯等等
3.1.2 我们通用定时器能输出PWM的IO口是固定的,虽然我们可以通过重映射可以改变引脚,具体是哪一些IO口我们要通过查阅STM32的参考手册

--》使用某个IO口作为定时器(片内外设)使用时,需要到数据手册查看该IO口是默认有该功能还是需要映射-如需映射 需要AFIO时钟。

即对应的芯片手册中STM32F103xx引脚定义查看。

--》确定输出是否为复用。

到STM32参考手册8.3查询即可。

3.2 PWM工作过程

PWM,实际上就是通过CCR与ARR相比较来实现不同占空比输出 

 

TIM_ARRPreloadConfig可设可不设。

原因:(不一定正确)

  • TIM_ARRPreloadConfig设置为DISABLE 和ENABLE的问题,他的作用只是允许或禁止在定时器工作时向ARR的缓冲器中写入新值,以便在更新事件发生时载入覆盖以前的值。
  • 在开始初始化的时候你已经把" TIM_TimeBaseStructure.TIM_Period=2000;   //ARR的值  ",后来也一直是这个值,原因是你没有编写中断服务函数或者你在中断服务函数中根本就没有给ARR缓冲器重新写入新值,所以设置为DISABLE 和ENABLE都没有影响。 

3.3 常用库函数 

PWM输出配置步骤: 

 使能定时器 3 和相关 IO 口时钟。

         使能定时器3时钟:RCC_APB1PeriphClockCmd();

         使能GPIOB时钟:RCC_APB2PeriphClockCmd();

     初始化IO口为复用功能输出。函数:GPIO_Init();

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;      

③ 这里我们是要把 PB5 用作定时器的 PWM 输出引脚,所以要重映射配置,
       所以需要开启AFIO时钟。同时设置重映射。

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

        GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

④    初始化定时器:ARR,PSC等:TIM_TimeBaseInit();

⑤    初始化输出比较参数:TIM_OC2Init();

⑥   使能预装载寄存器: TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);

⑦   使能定时器。TIM_Cmd();

⑧   不断改变比较值CCRx,达到不同的占空比效果:TIM_SetCompare2();

3.4  代码区

题目:使用定时器3PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED【PB5]亮度由暗变亮,又从亮变暗,如此循环。

*timer.c
 
#include "timer.h"
#include "led.h"
#include "stm32f10x.h"
 
//TIM3 PWM部分初始化 
//PWM输出初始化
//arr:自动重装值
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitTStrute;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStrue;
	TIM_OCInitTypeDef TIM_OCInitStrue;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能定时器3时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB  | RCC_APB2Periph_AFIO, ENABLE);  
    //使能GPIO外设和AFIO复用功能模块时钟
	
	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);//Timer3部分重映射  TIM3_CH2->PB5 
	
	//设置该引脚为复用输出功能,输出TIM3 CH2的PWM脉冲波形	GPIOB.5
	GPIO_InitTStrute.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
	GPIO_InitTStrute.GPIO_Pin = GPIO_Pin_5;//TIM_CH2
	GPIO_InitTStrute.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,&GPIO_InitTStrute);//初始化GPIO
	
	//初始化TIM3
	TIM_TimeBaseInitStrue.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInitStrue.TIM_Period = arr;
    //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
	TIM_TimeBaseInitStrue.TIM_Prescaler = psc;//设置用来作为TIMx时钟频率除数的预分频值 
	TIM_TimeBaseInitStrue.TIM_CounterMode = TIM_CounterMode_Up;//TIM向上计数模式
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStrue);
	
	//初始化TIM3 Channel2 PWM模式	
	TIM_OCInitStrue.TIM_OCMode = TIM_OCMode_PWM2;//选择定时器模式:TIM脉冲宽度调制模式2
	TIM_OCInitStrue.TIM_OutputState = TIM_OutputState_Enable;//比较输出使能
	TIM_OCInitStrue.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性:TIM输出比较极性高
	TIM_OC2Init(TIM3,&TIM_OCInitStrue);//根据T指定的参数初始化外设TIM3 OC2
	
	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能TIM3在CCR2上的预装载寄存器
	
	TIM_Cmd(TIM3,ENABLE); //使能TIM3
 
}
*main.c
 
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "timer.h"
 
int main(void)
{	
	u16 led0pwmval=0;
	u8 dir=1;
	
	Led_Init();
	delay_init();//延时函数初始化	 
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	TIM3_PWM_Init(899,0);	 //不分频。PWM频率=72000000/900=80Khz
	
	while(1)
	{
		delay_ms(10);	 
		if(dir)led0pwmval++;
		else led0pwmval--;
 
 		if(led0pwmval>300)dir=0;
		if(led0pwmval==0)dir=1;										 
		TIM_SetCompare2(TIM3,led0pwmval);			
	}
}

四、输入捕获功能

1.基本介绍

输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32 的定时器,除了 TIM6 和 TIM7(即基本定时器),其他定时器都有输入捕获功能。

STM32 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。

用到 TIM5_CH1 来捕获高电平脉宽,也就是要先设置输入捕获为上升沿检测,记录发生上升沿的时候 TIM5_CNT 的值。然后配置捕获信号为下降沿捕获,当下降沿到来时,发生捕获,并记录此时的 TIM5_CNT 值。这样,前后两次 TIM5_CNT 之差,就是高电平的脉宽,同时 TIM5 的计数频率我们是知道的,从而可以计算出高电平脉宽的准确时间。

2.工作过程

 

3.常用库函数 

输入捕获通道初始化函数:

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct); 

通道极性设置独立函数: 

void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity)

获取通道捕获值 

uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx)

输入捕获的一般配置步骤 

① 初始化定时器和通道对应IO的时钟。

② 初始化IO口,模式为输入:GPIO_Init();

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入

③初始化定时器ARRPSC

   TIM_TimeBaseInit();

④初始化输入捕获通道

   TIM_ICInit();

⑤如果要开启捕获中断,

    TIM_ITConfig();

    NVIC_Init();

⑥使能定时器:TIM_Cmd();

⑦编写中断服务函数:TIMx_IRQHandler();

代码区

 

题目:捕获WK_UP按键输入高电平时间(按键时为上升沿,松开瞬间为下降沿)

1、先捕获到上升沿才会进入中断

2、进中断后先执行捕获事件中的else把变量及寄存器值置零,置下降沿捕获

3、置完下降沿捕获后执行溢出事件 结束后进入捕获事件执行if捕获下降沿的时间 再置上升沿捕获

4、用捕获到下降沿时刻的时间减去捕获到上升沿时刻的时间即可得到此高电平的时间

*timer.c
 
#include "timer.h"
#include "led.h"
#include "usart.h"
 
u8  TIM5CH1_CAPTURE_STA=0;	//输入捕获状态		    				
u16	TIM5CH1_CAPTURE_VAL;	//输入捕获值
 
void TIM5_IRQHandler(void)
{ 
 
 	if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获	
	{	  
		//溢出事件
		if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)		 
		{	    
			if(TIM5CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
			{
				if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
				{
					TIM5CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
					TIM5CH1_CAPTURE_VAL=0XFFFF;
				}else TIM5CH1_CAPTURE_STA++;
			}	 
		}
		//捕获事件
		if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)//捕获1发生捕获事件
		   {	
				if(TIM5CH1_CAPTURE_STA&0X40)		//捕获到一个下降沿 		
				{	  			
					TIM5CH1_CAPTURE_STA|=0X80;		//标记成功捕获到一次上升沿
					TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
					TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); 
                    //CC1P=0 设置为上升沿捕获
				}else  			//还未开始,第一次捕获上升沿
				{
					TIM5CH1_CAPTURE_STA=0;			//清空
					TIM5CH1_CAPTURE_VAL=0;
					TIM_SetCounter(TIM5,0);   //清除计数器的值
					TIM5CH1_CAPTURE_STA|=0X40;		//标记捕获到了上升沿
					TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);		
                    //CC1P=1 设置为下降沿捕获
				}		    
		    }			     	    					   
		}
 
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
 
}
*main.c
 
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "usart.h"
#include "timer.h"
 
extern u8  TIM5CH1_CAPTURE_STA;		//输入捕获状态		    				
extern u16	TIM5CH1_CAPTURE_VAL;	//输入捕获值	
int main(void)
{		
 	u32 temp=0; 
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	 
    //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 //串口初始化为115200
 	LED_Init();			     //LED端口初始化 
 	TIM5_Cap_Init(0XFFFF,72-1);	//以1Mhz的频率计数 
    while(1)
	{	 		 
 		if(TIM5CH1_CAPTURE_STA&0X80)//成功捕获到了一次上升沿
		{
			temp=TIM5CH1_CAPTURE_STA&0X3F;
			temp*=65536;//溢出时间总和
			temp+=TIM5CH1_CAPTURE_VAL;//得到总的高电平时间
			printf("HIGH:%d us\r\n",temp);//打印总的高点平时间
			TIM5CH1_CAPTURE_STA=0;//开启下一次捕获
		}
	}
}