32单片机基础教程:定时器定时中断详解

 接线图如图所示

如何初始化定时器呢?下图是定时器框架的所有结构,我们只需要把这里面的每个模块都打通,就可以让定时器工作了,

第一步:RCC开启时钟,这个基本上没=每个代码都是第一步,在这里打开时钟后,定时器的基准时钟和整个外设的工作时钟就会同时打开了。

第二步:选择时基单元的时钟源,对于定时中断,我们就选择内部时钟源。

第三步:配置时基单元,包括预分频器,自动重装器,计数模式等,这些参数用结构体配置

第四步:配置输出中断控制,允许更新中断输出到NVIC.

第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级

第六步:运行控制

整个模块配置完成后,我们还需要使能一下计数器。要不然计数器是不会运行的。

当计数器使能后,计数器开始计数了,当计数器更新时,触发中断。

最后写一个定时器中断函数

这样这个中断函数每隔一段时间就能自动执行一次了。

 

打开相对应的库函数,可以看到这个库函数非常多,我们把本节要用的函数讲一下

1.void TIM_DeInit(TIM_TypeDef* TIMx); (不重要)

恢复缺省配置,

2.void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);(重要)

时基单元初始化,它就是用来配置这个图里这里的时基单元的。

两个参数,第一个TIMx,选择某个定时器,第二个是结构体,里面包含了配置时基单元的一些参数

第一个按钮,添加书签,第二个,跳到上一个书签,第三个,跳到下一个书签,第四个,删除所以书签。 

3.void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

这个函数可以把一个结构体变量赋给一个默认值

4.void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

这个是用来使能计数器的,对应下图所示,两个参数,TIMx选择定时器。 NewState,新的状态,也就是使能还是失能。使能,计数器可以运行,失能,计数器就不运行

5.void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

这个是用来使能中断输出信号的,对应下图位置,看一下参数,TIMx选择哪一个定时器,TIM_IT,选择要配置哪一个中断输出。 NewState新的状态。这个ITConfig函数以后还会经常遇到

下面这六个函数对应时基单元时钟选择部分,对应下图这一块,可以选择RCC内部时钟,ETR外部时钟等

1.void TIM_InternalClockConfig(TIM_TypeDef* TIMx); 

选择内部时钟,调用一下,就可以这样连接

2.void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

选择ITRx其他定时器的时钟,参数是TIMx,选择要配置的定时器和 InputTriggerSource选择要接入哪个其他定时器。调用一下,连接如下图

void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                           uint16_t TIM_ICPolarity, uint16_t ICFilter); 

选择TIx捕获通道时钟,TIM_TIxExternalCLKSource,选择TIx具体的某一个引脚。TIM_ICPolarity,ICFilter输入的极性和滤波器。对于外部引脚的波形,一般都会有极性选择和滤波器,这样更加灵活一些,调用一下这个函数,连接如图

void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,  uint16_t ExtTRGFilter); 

选择ETR通过外部时钟模式1输入时钟,ExtTRGPrescaler外部触发预分频器,这里可以对ETR的外部时钟再提前做一个分频,

void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

选择ETR通过外部时钟模式2输入的时钟,

对于ETR输入的时钟而言,这两个函数是等效的。如果不需要触发输入功能,那这两个函数可以互换。

void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,   uint16_t ExtTRGFilter); 

这个不是用来选择时钟的,就是单独用来配置ETR引脚的预分频器,极性,滤波器这些参数的。

以上就是对最上面那个图的具体配置了,这样初始化基本就OK了。

下面我们再来看几个比较关键的函数,因为在初始化结构体里有很多关键的参数,比如自动重装值和预分频等等,这些参数可能在初始化之后还需要更改,如果为了改某个参数还要调用一次初始化函数,太麻烦了,所以这里有一些单独的函数,可以方便的更改这些关键参数

如:

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);

就是用来单独写预分频值的,看一下参数,Prescaler就是要写入的预分频值,PSCReloadMode写入的模式,预分频有一个缓冲器,写入的值是在更新事件发生后才有效的,所以这里有个写入的模式,可以选择时听从安排,在更新事件生效。或者是,在写入后,手动产生一个更新事件,让这个值立刻生效。不过这些都是细节问题,影响不大,你就知道这个是写预分频值的函数就行了。

void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);

用来改变计数器的计数模式,参数TIM_CounterMode,选择新的计数器模式

void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

自动重装器预装功能配置。

void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);

给计数器写一个值,如果你想手动给计数值,就可以用这个函数

void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);

给自动重装器写一个值,如果你想手动给一个自动重装值,就可以用这个函数。

uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);

获取当前计数器的值,如果你想看当前计数器计到哪里了,就可以调用这个函数,返回值就是当前的计数器的值,

uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

获取当前的预分频器的值,如果想看预分频器的值,就调用这个函数

FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

这四个函数就是用来获取标志位和清除标志位的,

OK,我们来初始化定时器,首先,我们用的是TIM2,也就是通用定时器

首先开启时钟,要使用APB1的开启时钟函数,因为TIM2是APB1总线的外设

 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);  //开启时钟

 我选择的是内部时钟作为时基单元的时钟,但是也可以不写这个函数,因为定时器上电后默认就是使用内部时钟,

TIM_InternalClockConfig(TIM2);   //选择时基单元的时钟

配置时基单元

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//定义数组
TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1 ;//使用1分频
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;//像上计数
TIM_TimeBaseInitStructure.TIM_Period=10000-1;
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;

TIM_Period:周期,就是ARR自动重装器的值

TIM_Prescaler:就是PSC预分频的值

TIM_RepetitionCounter:是重复计数器的值,是高级定时器才有的,这里不需要用,直接给0就OK。

这些参数就是时基单元里每一个关键寄存器的参数了。不过这里没有CNT计数器的参数,这个如果我们之后需要的话,可以使用之前说的SetCounter 和GetCounter来操作计数器。决定定时时间的参数,就是TIM_Period,TIM_Prescale。如果我们想定一个1S的时间,参考下图那个公式。

定时频率=72M/(PSC+1)/(ARR+1),定时1s,也就是定时频率为1hz,那我们就可以给PSC给一个7200,ARR给一个10000.然后两个参数都减一,因为预分频器和计数器都有一个数的偏差,所以这里要再减一个1.然后注意PSC和ARR的取值都要在0~65535之间,不要超过范围,而且取值唯一。在这里我们预分频是对72M进行7200分频,得到的是10K的计数频率,在10K的频率下,计10000个数,那就是1s的时间。

下面使能更新中断了,

TIM_ITConfig(TIM2,TIM_EventSource_Update,ENABLE);

NVIC,上一篇博文讲过,自己取回顾

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);

 这样定时器就可以开始工作了,

当产生跟新时,就可以触发中断,到这里,整个定时器初始化的代码就完成了

void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); 
    
    TIM_InternalClockConfig(TIM2);   
    
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1 ;
    TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;
    TIM_TimeBaseInitStructure.TIM_Period=10000-1;
    TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
    
    TIM_ITConfig(TIM2,TIM_EventSource_Update,ENABLE);
    
    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);
}

接下来写中断函数,打开启动文件

如图所示,这个就是定时器2的中断函数了,

老规矩,判断一下标志位,判断更新标志的标志位,

 if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)

别忘了最后清除标志位,最后的代码为

 void TIM2_IRQHandler(void)
 {
     if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
     {
         
         TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
     }
 } 

 使用跨文件的变量,用extern声明即可。

调用如下函数时,会出现一个奇怪的现象,按复位键,Num为1,这说明中断函数在初始化之后就立刻进入了一次

#include "Delay.h"
#include "OLED.h"
#include "Timer.h"

uint16_t Num;

int main()
{
	
	OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	
	while(1)
	{
      OLED_ShowNum(1,5,Num,5);
	}
}
void TIM2_IRQHandler(void)
 {
	 if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
	 {
		 Num++;
		 TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	 }
 }

打开这个TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); 函数,看到有以下注释,生成一个更新事件,来重新装在预分频和重复计数器的值,;立刻,为啥要加这一句呢,我们知道这个预分频是有一个缓存计寄存器的,我们写的值只有在更新事件时,才会真正起到作用。所以为了让值立起作用,手动生成了一个更新事件,这样,预分频器的值就有效了但同时,他的副作用是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,当我们一旦初始化完了更新中断就会立刻进入。这就是我们刚一上电,就立刻进中断的原因。

TIM_ClearFlag(TIM2,TIM_IT_Update);加入这个函数,就能避免刚进入初始化就进中断的问题了

第一个代码如下:

Timer.c

#include "stm32f10x.h"                  // Device header

//extern uint16_t Num;
void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); 
	
	TIM_InternalClockConfig(TIM2);   
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision= TIM_CKD_DIV1 ;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up ;
	TIM_TimeBaseInitStructure.TIM_Period=1000-1;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2,TIM_IT_Update);
	
	TIM_ITConfig(TIM2,TIM_EventSource_Update,ENABLE);
	
	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);
}

// void TIM2_IRQHandler(void)
// {
//	 if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
//	 {
//		 Num++;
//		 TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
//	 }
// }

Timer.h

#ifndef __OLED_H
#define __OLED_H

void Timer_Init(void);

#endif

 main.c

#include "OLED.h"
#include "Timer.h"

uint16_t Num;

int main()
{
	
	OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	
	while(1)
	{
      OLED_ShowNum(1,5,Num,3);
	  OLED_ShowNum(2,5,TIM_GetCounter(TIM2),5);
	}
}
void TIM2_IRQHandler(void)
 {
	 if(TIM_GetITStatus(TIM2, TIM_IT_Update)==SET)
	 {
		 Num++;
		 TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
	 }
 }

 我们再写一个类型的项目:定时器外部时钟

接线图如下:加入一个对射式红外传感器

在上面的那个程序稍微改一下,现在我们的任务仍然是定时中断,时钟部分,不再使用内部时钟

用TIM_ETRClockMode2Config这个函数,通过ETR引脚的外部时钟模式2配置。跳转到函数找到要的参数。

第四个参数就是以一个采样频率f采用N个点,如果N个点都一样,才会有效输出,那这个参数就是来决定f和N的,具体怎样的对应关系,手册有介绍。

 我们暂时不用滤波,就写0x00就行了。

引脚要用到GPIO,所以在这之前还是要先配置GPIO。

不过我没有选择浮空输入,而是上拉输入

代码如下:

main.c

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

uint16_t Num;

int main()
{
	
	OLED_Init();
	Timer_Init();
	OLED_ShowString(1,1,"Num:");
	OLED_ShowString(2,1,"CNT:");
	while(1)
	{
      OLED_ShowNum(1,5,Num,3);
	  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);
	 }
 }

 Timer.h

#ifndef __OLED_H
#define __OLED_H

void Timer_Init(void);
uint16_t Timer_GetCounter(void);

#endif

 Timer.c

#include "stm32f10x.h"                  // Device header


void Timer_Init(void)
{
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	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=10-1;
	TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
	TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
	
	TIM_ClearFlag(TIM2,TIM_IT_Update);
	
	TIM_ITConfig(TIM2,TIM_EventSource_Update,ENABLE);
	
	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);
}
//实时看一下CN计数器的值
uint16_t Timer_GetCounter(void)
{
	return TIM_GetCounter(TIM2);
}

 

作者:爱学C语音的猫

物联沃分享整理
物联沃-IOTWORD物联网 » 32单片机基础教程:定时器定时中断详解

发表评论