STM32定时器TIM详解及代码示例
1.什么是定时器
定时器 (Timer) ,是一种用于计时和计数的硬件设备。定时器通常由计算机系统中的微处理器或微控制器提供,用于进行各种定时和计数操作。
2.定时器的常见应用
在单片机中,定时器常常用于测量时间间隔、计算频率、实现定时中断等。可以根据需要执行周期性计算或触发特定事件。
3.定时器的工作原理
当定时器开始工作时,定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断或事件。
补充:
中断:中断会通往NVIC,再配置NVIC的定时器通道,那么定时器的更新中断就能够得到CPU的响应了。
事件:更新事件不会触发中断,但是会触发其他电路(外设)的工作。不用CPU干预,实现了硬件自动化。
4.定时器的基本单元
定时器的基本单元也称时基单元,由计数器(CNT,Counter)、预分频器(PSC,Prescaler)、自动重装寄存器(ARR,AutoReloadRegister)组成。
计数器:用来计数定时的一个寄存器,每来一个时钟,计数器加1。
预分频器:可以对计数器的时钟进行分频,让计数更加灵活。比如72Mhz的时钟,不分频就是72Mhz,1分频,就是72/2=36Mhz,依次类推…
自动重装器:就是计数的目标值,计多少个时钟申请中断。当计数值=自动重装寄存器的值时,就是计时时间到了,自动重装寄存器会产生一个中断信号,并且清0计数器。计数器自动开始下一次的计数计时。比如自动重装器设置为10,当计数器累加到10时,申请中断,并且计数器清0。
下图是时基单元的简单结构图:
5.定时器的分类
定时器主要分为高级定时器、通用定时器、基本定时器。
6.基本定时器原理图
基本流程:基准时钟→预分频器→计数器(计数器计数自增,同时不断地与自动重装寄存器进行比较,它俩值相等时,即计时时间到,这时会产生一个更新中断or更新事件。CPU响应更新中断,就完成了定时中断的任务。)
注:UI表示中断或DMA输出,U表示事件;基本定时器的时钟源只有内部时钟(72MHZ),没有外部时钟。
7.通用定时器原理图
与基本定时器相比,通用定时器的时钟源就比较多了,分别有:RCC内部时钟、ETR外部时钟、ITRx定时器时钟、TIx捕获通道。
1.TIMx_ETR引脚上的外部时钟
stm32F103的单片机的ETR(External)引脚的位置对应的是PA0。可以在ETR引脚接一个外部方波时钟,然后配置下内部的极性选择、边沿检测、和预分频器,再配置输入滤波电路,对外部时钟电路进行一定的整形。滤波后的信号兵分两路,上面一路ETRF进入触发控制器,紧跟着就可以选择作为时基单元的时钟了,如果想在ETR外部引脚提供时钟,或者想对ETR进行计数,把这个定时器当做计数器来使用,那就可以配置这一路的电路,这一路也叫“外部时钟模式2”(橙色线)。
2.TRGI(Trigger In)
除了外部ETR引脚可以提供时钟外,下面还有一路可以提供时钟,就是TRGI(Trigger In),主要是用作触发输入来使用的,触发输入可以触发定时器的从模式,TRGI也可以作为外部时钟使用,当这个TRGI当做外部时钟来使用的时候,这一路就叫做“外部时钟模式1”(红色线)。
3.ITR信号
这一部分的时钟信号是来自其他定时器的,图右边可以看出,主模式的TRGO可以通向其他定时器,通向其他定时器的时候,就接到了其他定时器的ITR引脚上了,ITR0~ITR3分别来自其他4个定时器的TRGO输出(定时器与定时器相连,实现定时器级联的功能)(绿色线)。
如:初始化TIM3,使用主模式把TIM3的更新事件映射到TRGO上,接着初始化TIM2,这里选择ITR2,对应的就是TIM3的TRGO。(TIM3的TRGO接到TIM2的ITR2上)这样TIM3的更新事件就可以驱动TIM2的时基单元,就实现了定时器级联。
4.TI1F_ED:
ED(Edge,边沿)连接的是输入捕获单元的CH1引脚,也就是从CH1(channel 1,通道1)引脚获得时钟(紫色线)。
补充:还有两个时钟源:TI1FP1连接到CH1引脚的时钟,TI2FP2连接到CH2引脚的时钟。
8.定时器的整体工作流程
1.选择时钟源(内部时钟或外部时钟)
2.对时钟源进行分频
3.分频后得到的新的时钟源作为计数器的输入,驱动计数器计数
4.计数器计数时,不断对比自动重装寄存器的数值,如果计数值=自动重装寄存器的值,计数器清0,产生中断与事件。
下图是定时器流程的简单结构图:
9.定时器的相关公式
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
CK_PSC为时钟源的频率,(PSC + 1)分频系数,如:CK_PSC为内部主时钟72Mhz,PSC + 1分频系数为2,得到的计数器计数频率为72Mhz/2=36Mhz。
为什么是PSC+1而不是PSC?因为规定分频是从0开始,如0分频就是不分频,PSC=1代表二分频,依次类推…只是大家都习惯这样,这里需要注意下。
计数器溢出频率:CKCNTOV = CK_CNT / (ARR + 1)= CK_PSC / (PSC + 1) / (ARR + 1)
举例:现在预分频PSC=1,即二分频,主频72MHZ,二分频就是36MHZ(1秒跳36M次),ARR+1=10,就表明,跳10次,自动清零,并从零开始重新计数。计数器的溢出频率为72M/2/10=3.6Mhz,溢出时间为1/3.6Mhz。
10.定时器相关代码
内部时钟代码部分:
流程:RCC开启时钟→选择时钟源→配置时基单元→配置输出中断控制(允许更新中断输出到NVIC)→配置NVIC(在NVIC中打开定时器中断的通道,并分配一个优先级)→运行控制(使能计数器,否则计数器不会运行)
#include "stm32f10x.h" // Device header
//定时器中断初始化
void Timer_Init(void)
{
//本代码初始化的是TIM2的定时器
//1.开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//2.选择时基单元的时钟源(是内部时钟还是外部时钟)
TIM_InternalClockConfig(TIM2);
//3.配置时基单元
//声明一个时基时钟的实参
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//配置分频,滤波时选择的频率
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//计数器模式
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//ARR自动重装器的值,10KHz计10k个数,就是1s。 (0~65535)
TIM_TimeBaseInitStructure.TIM_Period = 10000-1;//ARR
//PSC预分频器的值.72MHz/7200 = 10KHz (0~65535)
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;//PSC
//重复计数器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//清除更新中断标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//4.中断输出控制--使能更新中断(开启了更新中断到NVIC的通路)
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//5.配置NVIC IRQ:interrupt request 请求中断
//配置优先级分组:先占(抢占)优先级or从占(响应)优先级,主要引用于中断多,有中 断冲突的时候。这个分组的方式整个代码工程只能用一种。
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC分组
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);
//6.启动定时器
TIM_Cmd(TIM2,ENABLE);
}
//中断函数
//当定时器产生更新中断时,这个函数会被自动执行。
void TIM2_IRQHandler(void)
{
//判断中断标志位是否处于中断状态
if (TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
定时器外部时钟(时钟使用外部给的传感器)
#include "stm32f10x.h" // Device header
//定时器中断初始化
void Timer_Init(void)
{
//本代码初始化的是TIM2的定时器
//1.开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//初始化GPIO
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);
//2.选择时基单元的时钟(是内部时钟还是外部时钟)
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);
//3.配置时基单元
//声明一个时基时钟的实参
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//配置分频,滤波时选择的频率
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//计数器模式
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//ARR自动重装器的值,10KHz计10k个数,就是1s。
TIM_TimeBaseInitStructure.TIM_Period = 10-1;
//PSC预分频器的值.72MHz/7200 = 10KHz
TIM_TimeBaseInitStructure.TIM_Prescaler = 1-1;
//重复计数器的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
//清除更新中断标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//3.使能更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//4.配置NVIC IRQ:interrupt request 请求中断
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);
//5.启动定时器
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)
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
注:由于该篇幅过长,PWM将在下一篇更新~~~
作者:trust me 6