定时器定时中断&定时器外部时钟

发布于:2022-12-14 ⋅ 阅读:(262) ⋅ 点赞:(0)

一、步骤

1、RCC开启时钟,此时定时器的基准时钟和整个外设的工作时钟都打开

2、选择时基单元的时钟源,对于定时中断选择内部时钟源

3、配置时基单元,包括预分频器,自动重装器,计数模式等

4、配置输出中断控制允许更新中断输出到NVIC

5、配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级

6、运行控制,配置完成后,需要对计数器进行使能,否则计数器无法运行

计数器更新时,触发中断,最后再写一个定时器中断函数。

二、函数的调用

1、基本函数

  •        TIM_DeInit(TIM_TypeDef* TIMx);,恢复缺失配置
  • TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);,时基单元初始化。
  • 两个参数,第一个,TIMx选择某个定时器;第二个,是结构体,包含了配置时基单元的一些参数
  • TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);把结构体变量赋一个默认值 
  • TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);使能计数器,对应图中的运行控制;两个参数:第一个,选择定时器;第二个,选择使能还是失能 
  •  TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);是用来使能中断输出信号,对应图中的中断输出控制,三个参数:第一个,选择定时器;第二个,第二个,选择要配置哪个中断输出;第三个,选择使能还是失能 

2、时基单元的时钟选择

接下来的6个函数,对应时基单元的时钟选择部分,可以选择RCC内部时钟,ETR外部时钟,ITRx其他定时器,TIx捕获通道。如图2-1所示

 图2-1

  • 1、TIM_InternalClockConfig(TIM_TypeDef* TIMx);选择内部时钟,RCC内部时钟—>内部时钟模式—>时基单元
  • 2、TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);选择ITRx其他定时器的时钟,两个参数,第一个,选择要配置的定时器,第二个,选择要接入哪个其他的定时器,ITRx其他定时器—>外部时钟模式1—>时基单元
  • 3、TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
  •                                 uint16_t TIM_ICPolarity, uint16_t ICFilter);
  • 第二个,选择TIx具体的某个引脚,输入的极性和滤波器,对于外部引脚的波形,一般会有极性选择和滤波器,这样会更灵活;TIx捕获通道—>外部时钟模式1—>时基单元
  • 4、TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);选择ETR通过外部时钟模式1输入的时钟;ETR外部时钟—>外部时钟模式1—>时基单元   参数,外部触发预分频器,可以对ETR的外部时钟再提前做一个分频
  • 5、TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
  •                              uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter); 
  • ETR外部时钟—>外部时钟模式2—>时基单元  

4和5函数,对于ETR输入的外部时钟而言,这两个函数是等效的,参数也一样,如果不需要触发输入的功能,两个函数可以互换

  • 6、TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);不是用来选择时钟的,是单独用来配置ETR引脚的预分频器、极性、滤波器这些参数

3、单独的函数

  • 在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等,这些参数可能会在初始化之后还需要更改,下面是一些单独的函数,可以方便的更改这些关键参数
  • TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode); 用来单独写预分频值,参数: Prescaler,就是要写入的预分频值;PSCReloadMode,写入的模式。  预分频器有个缓冲器,写入的值是在更新时间发生后才有效的。

  • TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);

  • 用来改变计数器的计数模式,参数CounterMode,选择新的计数器模式
  • TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);,自动重装器预装功能配置
  • TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);给计数器写入一个值
  • TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);,给自动重装器写入一个值
  • TIM_GetCounter(TIM_TypeDef* TIMx);获取当前计数器的值
  • TIM_GetPrescaler(TIM_TypeDef* TIMx);获取当前的预分频器的值
  • 三、程序(1)

  • 1、Timer.c

  • #include "stm32f10x.h"                  // Device header
    
    extern uint16_t Num;
    
    void Timer_Init(void)
    {
    		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//注意是开启APB1的时钟函数,因为TIM2是APB1总线的外设
    		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//选择时基单元的时钟
    	
    		TIM_InternalClockConfig(TIM2);
    	//定时器上电后默认是使用内部时钟,如果不调用,也是使用内部时钟,可以不写
    	
    	
    		GPIO_InitTypeDef GPIO_Initstructure;
    		GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IPU;
    		GPIO_Initstructure.GPIO_Pin = GPIO_Pin_0;  
    		GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
    		GPIO_Init(GPIOA,&GPIO_Initstructure);
    	
    		//TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
    	
    		TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    	    TIM_TimeBaseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1;//指定时钟分频
    	    TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up;//计数器模式
    		TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //周期,ARR自动重装器的值
    		TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值
    		TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,是高级计数器才有的
    	  TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    		//配置时基单元
    		
    		
      //TIM_ClearFlag(TIM2,TIM_FLAG_Update);//手动把更新中断标志位清除
    		TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断,Update更新中断
    	
    	    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    	  
    		NVIC_InitTypeDef NVIC_InitStructure;
    		NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//中断通道
    		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
    		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
    		NVIC_Init(&NVIC_InitStructure);
    		
    		TIM_Cmd(TIM2,ENABLE);
    }
    
    uint16_t Timer_GetCounter(void)
    {
    		return TIM_GetCounter(TIM2);
    }
    
    
    //void TIM2_IRQHandler(void)
    //{
    //		if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位
    //		{
    //				Num ++;
    //				TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
    //			
    //		}
    //}
    

    2、Timer.h

  • #ifndef __TIMER_H
    #define __TIMER_H
    
    void Timer_Init(void);
    uint16_t Timer_GetCounter(void);
    
    #endif
    

    3、main.c

  • #include "stm32f10x.h"                  // Device header
    #include  "Delay.h"
    #include  "Buzzer.h"
    #include  "Timer.h"
    //#include  "Flame.h"
    #include  "OLED.h"
    #include  "usart.h"
    
    
    uint16_t Num;//定义一个16位得全局变量Num
    
    int main(void)
    {
        OLED_Init(); 
    	  Timer_Init();
    	
    		OLED_ShowString (1, 1, "Num:");
    		OLED_ShowString (2, 1, "CNT:");
    	while(1)
    	{
    		OLED_ShowNum(1, 5, Num, 5);
    		OLED_ShowNum(2, 5, TIM_GetCounter (TIM2), 5);//看CNT计数器值的变化
    	}
    }
    
    void TIM2_IRQHandler(void)
    {
    		if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位
    		{
    				Num ++;
    				TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
    			
    		}
    }
    
    

    本次程序需要一个OLED屏幕,OLED屏幕的程序可以参考一下下面的链接

  • https://blog.csdn.net/weixin_62353329/article/details/126042132?spm=1001.2014.3001.5501

 四、程序效果(1)

 图4-1 运行效果

注意事项:在图4-1中,我们可以看到复位后,Num值是从00001开始的,不是在00000开始计数,说明中断函数在初始化后,立刻进入一次计数。

 图2-1

TimeBase_Init函数生成一个更新事件,来重新装载预分频器和重复计数器的值。预分频器由一个缓冲寄存器,写的值只有在更新事件时,才会真正起作用,为了让值立刻起作用,在最后手动生成一个更新事件,这样预分频器的值就有效了。

副作用:更新事件和更新中断,是同事发生的,更新中断会置更新中断标志位,一旦初始化完毕,更新中断就会进入。

 图4-3

 图4-4

解决方案:在TimeBase_Init的后面,开启中断的前面,手动调用Tim_ClearFlag函数,就可以避免刚初始化完就进入中断的问题。如图4-3,图4-4为解决后的效果。

五、程序(2)

所需硬件:OLED屏幕、对射式红外传感器(火焰传感器,选择一个可以计数的传感器即可)

D0数字输出接到PA0引脚,这个PA0引脚就是TIM2的ETR引脚,在此引脚输入一个外部时钟,VCC连正极,GND接负极,D0接PA0引脚 

注意:D0一定要连接到PA0引脚, 在下面此文章的通用定时器中的外部时钟有介绍到。

https://blog.csdn.net/weixin_62353329/article/details/126128279?spm=1001.2014.3001.5501

1、Timer.c

#include "stm32f10x.h"                  // Device header

extern uint16_t Num;

void Timer_Init(void)
{
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//注意是开启APB1的时钟函数,因为TIM2是APB1总线的外设
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//选择时基单元的时钟
	  
		//TIM_InternalClockConfig(TIM2);
	  //定时器上电后默认是使用内部时钟,如果不调用,也是使用内部时钟,可以不写
	  
	
		GPIO_InitTypeDef GPIO_Initstructure;
		GPIO_Initstructure.GPIO_Mode = GPIO_Mode_IPU;//上拉输入
		GPIO_Initstructure.GPIO_Pin = GPIO_Pin_0;  
		GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
		GPIO_Init(GPIOA,&GPIO_Initstructure);
	
		TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x00);
	  //通过ETR引脚的外部时钟模式2配置
	
		TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	    TIM_TimeBaseInitStructure.TIM_ClockDivision =TIM_CKD_DIV1;//指定时钟分频
	    TIM_TimeBaseInitStructure.TIM_CounterMode =TIM_CounterMode_Up;//计数器模式
		TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //周期,ARR自动重装器的值
		TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//PSC预分频器的值
		TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,是高级计数器才有的
	  TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
		//配置时基单元
		
		
		TIM_ClearFlag(TIM2,TIM_FLAG_Update);//手动把更新中断标志位清除
		TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断,Update更新中断
	
	  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	  
		NVIC_InitTypeDef NVIC_InitStructure;
		NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//中断通道
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
		NVIC_Init(&NVIC_InitStructure);
		
		TIM_Cmd(TIM2,ENABLE);
}

uint16_t Timer_GetCounter(void)
{
		return TIM_GetCounter(TIM2);
}


//void TIM2_IRQHandler(void)
//{
//		if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位
//		{
//				Num ++;
//				TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
//			
//		}
//}

时钟部分不使用内部时钟,选择TIM_ETRClockMode2Cofig,通过ETR引脚的外部时钟模式2配置,第一个参数给TIM2,迭戈参数是外部触发预分频器,不需要分频选择TIM_ExtTRGRSC_OFF,第三个参数,外部触发的极性 

2、main.c

#include "stm32f10x.h"                  // Device header
#include  "Delay.h"
#include  "Timer.h"
#include  "OLED.h"


uint16_t Num;//定义一个16位得全局变量Num

int main(void)
{
      OLED_Init(); 
	  Timer_Init();
	
		OLED_ShowString (1, 1, "Num:");
  	    OLED_ShowString (2, 1, "CNT:");

	while(1)
	{
		OLED_ShowNum(1, 5, Num, 5);
		OLED_ShowNum(2, 5, Timer_GetCounter(), 5);
	}
}

void TIM2_IRQHandler(void)
{
		if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位
		{
				Num ++;
				TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
			
		}
}

六、程序效果(2)

图6-1复位效果图

 图6-2CNT值最高数

图6-3Num值+1 


网站公告

今日签到

点亮在社区的每一天
去签到