目录
一. 前言
二. 定时器的框图
三. 定时中断的基本结构
四. TIM定时器相关代码
五. 最终现象展示
一. 前言
什么是定时器? 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断。
TIM定时器不仅具备基本的定时中断功能,而且还包含内外时钟源选择,输入捕获,输出比较,编码器接口,主从触发模式等多种功能。
根据复杂度和应用场景分为了高级定时器,通用定时器,基本定时器。它们包含的编号以及分布总线和功能如下所示:
其中,我们可以看到高级定时器是连接在APB2总线上的,所以前面介绍的APB2性能更高是没有问题的。
值得注意的是:不是所有的单片机芯片都拥有所有的定时器。例如我们的STM32F103C8T6就只拥有四个定时器资源:TIM1,TIM2,TIM3,TIM4。
二. 定时器的框图
基本定时器的基本框图如下所示:
如上所示,我们基本定时器主要就是通过预分频器对时钟进行预分频,然后计数器就自增计数。当计数值达到自动重装值时,计数值清零同时产生更新中断和更新事件。
通用定时器的基本框图如下所示:
我们可以看出,通用定时器比基本定时器的框图复杂了很多,主要是因为我们上面所介绍的它们的功能。 基本定时器只拥有定时中断,主模式触发DAC的功能。而我们的通用定时器不仅具备基本定时器的全部功能,并且还额外拥有内外时钟源选择,输入捕获,输出比较,编码器接口,主从触发模式等功能。例如上面框图中左下角的部分---输入滤波器和边沿检测器就是为我们通用定时器的输入捕获而设计的。
高级定时器的框图如下所示:
高级定时器主要就是在通用定时器的基础上新增了重复计数器(这里后面会涉及的,只有高级定时器才具备),死区生成,互补输出,刹车输入等功能。
三. 定时中断的基本结构
我们想要配置好定时器,并使用它,只需要根据它的基本结构来一个一个进行相关配置即可。定时中断的基本结构如下所示:
总结一下,就是:
1) 首先配置好时钟,选择内部时钟RCC还是外部时钟ETR等等。这里需要的库函数在tim.h当中寻找。如果选择内部时钟,那么就需要使用TIM_InternalClockConfig()这个库函数。
2) 然后进行时基单元的配置。选择TIM_TimeBaseInit()函数。
对于其中的Period(重装载值ARR)和Prescaler(分频系数PSC)的取值都要在0~65536之间,因为它们都是16位的。这两个的取值不是唯一的,可以分频系数PSC给少点,自动重装给多点,这样就是以一个比较高的频率计比较多的数。
值得注意的是,为了方便更改其中一些具体的参数,如分频值等。ST公司提供了一些库函数,专门单独更改。例如TIM_PrescalerConfig()函数,就是用来单独写预分频值的。还有很多小的函数,这些大家看到都可以查询一下,很容易就能查到。这里我就不过多一一介绍了。
另外,在时基单元中的预分频器中,最重要的其实是预分频缓冲器。它可以防止计数中途更改数值造成错误。
3)配置输出中断控制,允许更新中断输出到NVIC。这里使用库函数TIM_ITConfig()。
4)配置NVIC。这个之前讲过,根据之前的进行相关配置即可。
值得注意的是,定时器相关的GPIO配置,需要根据产品手册来查看,例如定时器2的GPIO配置就为浮空输入,这个地方在产品手册中的通用I/O(GPIO)位置查看,如下所示:
四. TIM定时器相关代码
上面已经讲过了TIM配置的流程,所以我们根据它来即可。我们只需要明确一点,那就是使用定时器根据它的基本结构来配置就行了。将它的结构都打通,不就可以运行定时器了吗。
首先就是开启对应的时钟,这里我们选择使用TIM2。如下所示:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
然后选择时钟源,这里选择内部时钟源。如下所示:
TIM_InternalClockConfig(TIM2);
值得注意的是,如果有小伙伴对库函数参数配置不理解。可以在自己工程中找到该库函数,然后鼠标右键点击跳转到定义处,就可以查看库函数各个参数的作用了。
然后对时基单元进行初始化:
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
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; //这里是高级定时器才具备的重复计数功能,所以我们置为0即可
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
对于其中的Period和Prescaler值,我觉得还有必要讲下。这两个值就是ARR和PSC的值。由于我们单片机的主频率也就是72MHz。所以定时器的频率CK_CNT=72000000/(PSC+1)/(ARR+1)。
当我们的PSC为7200-1,ARR为10000-1时,那么最后CK_CNT的结果就为1Hz,也就是1s。
接着我们可以清除下定时器更新标志:
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
若是不清除这个标志位,那么开启中断后,会立刻进入一次中断。例如我们点击复位按钮,理论上是从0开始计数的,但是如果不清楚这个标志位,那么它就会从1开始计数。这是不符合常理的。
开启更新中断输出使能中断:
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
进行NVIC的相关配置:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannerPreemptionPriority=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)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
例如因为我们上面已经配置好了定时器为1s的计时。我们可以让它每次达到计数值的时候,进行数字的加1,最后显示在OLED上。如下所示,只需要加一行代码即可。至于显示到OLED上或者其他操作,大家可以自由发挥尝试。
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
{
Num ++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
五. 最终现象展示