STM32(3)定时器
定时器
TIM(Timer)定时器 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
定时器的本质就是计数器,当这个计数器输入的时钟源是一个准确可靠的基准时钟时,它在对这个时钟进行计数的过程,就是计时的过程
预分频器,可以对计数器的时钟进行分频,让这个计数更加灵活,分频系数=预分频器的值加1,预分频器为1时,对时钟源频率进行二分频,将72M变为36M,这时每两个脉冲经过预分频器变为一个脉冲
自动重装寄存器就是计数的目标值,当计数器的值达到目标值时就可以申请中断
定时器时钟模式
在STM32中,TIM2可以配置为使用内部时钟或外部时钟,两者的主要区别如下:
-
内部时钟模式:
- 内部时钟模式是指定时器的时钟直接来源于STM32的内部时钟系统,如APB1或APB2总线时钟。
- 在这种模式下,定时器的时钟是固定的,由系统时钟经过预分频器(Prescaler)和自动重装载寄存器(ARR)来确定定时器的计数频率。
- 配置内部时钟模式时,通常使用
TIM_InternalClockConfig()
函数来设置时基单元的时钟源为内部时钟。 -
外部时钟模式(ETR模式):
- 外部时钟模式是指定时器的时钟来源于外部信号,如外部事件或另一个外设的输出。
- 外部时钟模式允许定时器根据外部事件来触发计数,这在需要与外部事件同步时非常有用。
- 在这种模式下,可以使用
TIM_ETRClockMode2Config()
函数来配置外部触发预分频器、极性选择和滤波器等参数。 - 外部时钟模式允许定时器与外部信号同步,例如,可以使用对射式红外传感器模拟外部时钟,当遮挡红外光线再移开后会产生电平变化,从而触发定时器
定时器类型
基本定时器只能选择内部时钟
通用定时器和高级定时器支持向上计数、向下计数和中央对齐三种模式
级联
示例:初始化TIM3,使用主模式将它的更新事件映射到TRGO上,接着再初始化TIM2,选择ITR2,再选择时钟为外部时钟模式1,这样TIM3的更新事件就可以驱动TIM2的时基单元,也就实现了定时器的级联
更新中断和更新事件
当计数器的值达到目标值时,可以选择产生中断信号或更新事件(更新事件不会触发中断,但可以触发内部其他电路的工作),并且清零计数器
主从触发模式示例 :单片机可以将更新事件映射到TRGO(触发输出)的位置,然后将TRGO直接接到DAC的触发转换引脚,从而通过硬件直接输出DAC
外部时钟复用
可以看到PA0的默认复用功能上有TIM2_CH1_ETR,说明TIM2的CH1和ETR都复用在这个端口
计数器各个参数说明
直接控制时钟分频的是预分频缓冲器,在预分频控制器写入值后,要等到本次计数周期结束后,预分频寄存器的值才会被传递到缓冲寄存器里面去
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
有无预装时序,就是有无缓冲寄存器的情况
时钟树
在SystemInit函数里,ST是这样来配置时钟的,选择8MHZ为系统时钟,暂时以内部8MHZ的时钟运行,然后再启动外部时钟,配置外部时钟进入PLL锁相环进行倍频,8MHZ倍频9倍得到72MHZ,等到输出稳定后,选择锁相环输出为系统时钟
定时器中断代码
1.先RCC开启时钟
2.选择时基单元的时钟源,对于定时中断,选择内部时钟源
3.配置时基单元
4.配置中断输出控制,允许更新中断输出到NVIC
5.配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
6.配置完整个模块后,还要使能一下计数器
7.写中断函数
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
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_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
TIM_TimeBaseInit配置时基单元
TIM_Cmd使能计数器
TIM_ITConfig使能中断输出信号
TIM_InternalClockConfig选择内部时钟
TIM_ITRxExternalClockConfig选择ITRX其他定时器的时钟
TIM_TIxExternalClockConfig选择TIX捕获通道的时钟
TIM_ETRClockMode1Config选择ETR通过外部时钟模式1输入的时钟
TIM_ETRClockMode2Config选择ETR通过外部时钟模式2输入的时钟
TIM_ETRConfig配置ETR引脚的参数
因为在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等等,可能会在初始化之后还需要更改,所以有一些单独的函数,可以方便地更改这些关键参数,
TIM_PrescalerConfig用来单独写预分频值
TIM_CounterModeConfig用来改变计数器的计数模式
TIM_ARRPreloadConfig自动重装器预装功能配置
TIM_SetCounter给计数器手动写入一个值
uint16_t TIM_GetCounter获取当前计数器的值
选择外部时钟源
选择TIM2_ETR所对应的GPIO口,通过外部接入信号给TIM2提供时钟
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数配置为外部时钟,定时器相当于计数器
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
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);
//将PA0引脚初始化为上拉输入
/*外部时钟配置*/
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
//选择外部时钟模式2,时钟从TIM_ETR引脚输入
//注意TIM2的ETR引脚固定为PA0,无法随意更改
//最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
/*时基单元初始化*/
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_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:返回定时器CNT的值
* 参 数:无
* 返 回 值:定时器CNT的值,范围:0~65535
*/
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2); //返回定时器TIM2的CNT
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
作者:狐璃同学