STM32定时中断之TIM(一)- 时钟源选择详解
一,stm32f103的定时器资源
stm32f103有一个高级定时器(TIM1),两个通用定时器(TIM2,TIM3)。以及一个基本定时器(TIM4)。
对于通用定时器,是使用最广泛的定时器资源,有:输入捕获(可以测出输入信号的频率,占空比),输出比较(可以输出PWM波,驱动电机),编码器模式(编码器测转速),基本计数模式,主从触发模式。
对于高级定时器,其功能在基本定时器的基础上又加上了:死区生成,互补输出,刹车输入等功能
对于基本定时器,其功能只有计数定时功能
本节先介绍定时器最简单的功能:定时产生中断。
二,定时器硬件框图介绍:
(一)基本定时器:
核心:由自动重装寄存器(ARR)预分频器(PSC)和计数器(CNT)组成的时基单元
预分频器:预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频。它是通过一个16位寄存器 (TIMx_PSC)的计数实现分频。因为TIMx_PSC控制寄存器具有缓冲,可以在运行过程中改变它 的数值,新的预分频数值将在下一个更新事件时起作用
简单来说就是对输入的时钟信号(包括外部时钟信号)进行分频,
比如:时钟频率为72MHz,PSC=1,则不分频,一秒计数72000000个脉冲
若PSC=2,则一秒计数36000000个脉冲,PSC最大65536,以此类推。。。
计数器(CNT):就是对分频后的时钟信号的脉冲的上升沿计数,最多可以累积65535。
自动重装寄存器(ARR):ARR的值就是用户设置的值,当计数器计数的个数达到ARR的设定值是时计数器清零(向上计数),并且产生一次中断,一次事件触发
(二)通用定时器:
基本功能:
16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意 数值
● 4个独立通道: ─ 输入捕获 ─ 输出比较 ─ PWM生成(边缘或中间对齐模式) ─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
输入捕获
输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
先看上半部分:
外部触发输入(ETR):TIMx_ETR:定时器的输入时钟信号(在APB1总线上);
内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时 器Timer1而作为另一个定时器Timer2的预分频器,ITR0~ITR3:其他定时器的输入信号
内部时钟(CK_INT):RCC的TIMxCLK
其他本节的不做介绍,后面会说。本节对32的定时器的结构有个大概了解即可
下半部分:
输入捕获的通道(CH1~CH4)输入捕获的通道(CH1~CH4),这四个通道可以通对应的外部引脚和外部连接 ,用于输入外部的频率。
输出比较的通道(CH1~CH4)也是和外部连接的,和上述的CH1~CH4是共用的,因为单片机没发同时在同一个引脚输入捕获和输出比较。
三,定时中断代码:
如何开启定时钟中断呢?,如何使用外部时钟输入呢?
下面通过编程实现:
(一)初始化定时器:
1.开启RCC时钟
2.选择外部时钟(72MHz)
3.配置时基单元(PSC,ARR)
4开启中中断
5使能定时器
相关库函数:
void TIM_DeInit(TIM_TypeDef* TIMx);
初步初始化定时器
void TIM_TimeBaseInit(TIM_TypeDef* TIMxTIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);
配置时基单元
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
给时基单元赋初值初始化时基单元
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
定时中断开启或关闭
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
定时器使能
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
选择内部时钟源
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
选择其他定时器的输入作为时钟源
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
选择外部引脚输入的信号作为时钟源
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
选择内部时钟1信号作为时钟源
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
选择内部时钟2信号作为时钟源
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
手动改写计数器的值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
手动改写ARR的值
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);
库函数太多了……………………………………………..
下面吧库函数按照上面的步骤用起来就可以初始化定时器中断了!
void TImer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=10000-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0x00;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_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=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);
}
这里对72MHz的时钟信号7200分频,->每秒计数72000000/7200=10000个脉冲
然后设置ARR为10000,即计时器计数10000个脉冲清零,同时中时产生中断,顾中断周期为一秒。
然后配制好了定时器中断,就写一个中断服务函数(中断函数名在setup_stm32f10x_md.s文件里有声明)
extern int NUm;
void TIM2_IRQHandler ()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
NUm++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
这是模板,检查中断标志位后清除。
在主函数用OLED显示Num这个就可以看到Num每秒自增1了;
#include "stm32f10x.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
TImer_Init();
OLED_ShowString(1,1,"Hollow,Word");
while(1)
{
OLED_ShowNum(2,1,Num,5);
OLED_ShowNum(3,1,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler ()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
OLED_ShowNum(3,1,TIM_GetCounter(TIM2),5);
这个可以显示计数值,你可以看到计数值在1秒内从0-9999,然后变为零,变得非常快!
四,外部信号作为时钟源
前面介绍了内部时钟作为时钟源触发定时器中断的情况,用到了TIM_InternalClockConfig(TIM2);函数,若我们想从外部引脚输入的脉冲作为时钟源达到定时计数功能,也可以做到。我们需要先初始化外部引脚,后改变时钟源的选择,便可达到要求。(在对射式红外口之间遮挡一次变可产生一个脉冲)
如图所示。PA0引脚作为了时钟脉冲输入
需初始化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);
改变时钟源的选择:
TIM_ETRClockMode1Config(TIM2,TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0x0F);外部时钟模式
改变PSC,ARR寄存器值
TIM_TimeBaseInitStruct.TIM_Period=10-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=1-1;
我们不分频,ARR设置为10;那么一个脉冲就计数一次,10个脉冲产生一次中断
如果:
TIM_TimeBaseInitStruct.TIM_Period=5-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=3-1;
那么三个脉冲计数一次,计数5次产生一个中断;
除了上面说的要改,其他的都不改。
主函数:
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);
}
}
void TIM2_IRQHandler ()
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
遮挡一下,TIM_GetCounter(TIM2)就加一次,遮挡累积10下后,产生了中断,Num就加1;
这就是定时器中断的两种应用。