一、步骤
1、RCC开启时钟,此时定时器的基准时钟和整个外设的工作时钟都打开
2、选择时基单元的时钟源,对于定时中断选择内部时钟源
3、配置时基单元,包括预分频器,自动重装器,计数模式等
4、配置输出中断控制允许更新中断输出到NVIC
5、配置NVIC,在NVIC中打开定时器中断的通道,并分配一个优先级
6、运行控制,配置完成后,需要对计数器进行使能,否则计数器无法运行
计数器更新时,触发中断,最后再写一个定时器中断函数。
二、函数的调用
1、基本函数
- TIM_DeInit(TIM_TypeDef* TIMx);,恢复缺失配置
- TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);,时基单元初始化。
- 两个参数,第一个,TIMx选择某个定时器;第二个,是结构体,包含了配置时基单元的一些参数
- TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);把结构体变量赋一个默认值
- TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);使能计数器,对应图中的运行控制;两个参数:第一个,选择定时器;第二个,选择使能还是失能
- TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);是用来使能中断输出信号,对应图中的中断输出控制,三个参数:第一个,选择定时器;第二个,第二个,选择要配置哪个中断输出;第三个,选择使能还是失能
2、时基单元的时钟选择
接下来的6个函数,对应时基单元的时钟选择部分,可以选择RCC内部时钟,ETR外部时钟,ITRx其他定时器,TIx捕获通道。如图2-1所示
图2-1
- 1、TIM_InternalClockConfig(TIM_TypeDef* TIMx);选择内部时钟,RCC内部时钟—>内部时钟模式—>时基单元
- 2、TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);选择ITRx其他定时器的时钟,两个参数,第一个,选择要配置的定时器,第二个,选择要接入哪个其他的定时器,ITRx其他定时器—>外部时钟模式1—>时基单元
- 3、TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
- uint16_t TIM_ICPolarity, uint16_t ICFilter);
- 第二个,选择TIx具体的某个引脚,输入的极性和滤波器,对于外部引脚的波形,一般会有极性选择和滤波器,这样会更灵活;TIx捕获通道—>外部时钟模式1—>时基单元
- 4、TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);选择ETR通过外部时钟模式1输入的时钟;ETR外部时钟—>外部时钟模式1—>时基单元 参数,外部触发预分频器,可以对ETR的外部时钟再提前做一个分频
- 5、TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
- uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
- ETR外部时钟—>外部时钟模式2—>时基单元
4和5函数,对于ETR输入的外部时钟而言,这两个函数是等效的,参数也一样,如果不需要触发输入的功能,两个函数可以互换
- 6、TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);不是用来选择时钟的,是单独用来配置ETR引脚的预分频器、极性、滤波器这些参数
3、单独的函数
- 在初始化结构体里有很多关键的参数,比如自动重装值和预分频值等,这些参数可能会在初始化之后还需要更改,下面是一些单独的函数,可以方便的更改这些关键参数
TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode); 用来单独写预分频值,参数: Prescaler,就是要写入的预分频值;PSCReloadMode,写入的模式。 预分频器有个缓冲器,写入的值是在更新时间发生后才有效的。
TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
- 用来改变计数器的计数模式,参数CounterMode,选择新的计数器模式
- TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);,自动重装器预装功能配置
- TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);给计数器写入一个值
- TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);,给自动重装器写入一个值
- TIM_GetCounter(TIM_TypeDef* TIMx);获取当前计数器的值
- TIM_GetPrescaler(TIM_TypeDef* TIMx);获取当前的预分频器的值
三、程序(1)
1、Timer.c
#include "stm32f10x.h" // Device header extern uint16_t Num; void Timer_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//注意是开启APB1的时钟函数,因为TIM2是APB1总线的外设 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//选择时基单元的时钟 TIM_InternalClockConfig(TIM2); //定时器上电后默认是使用内部时钟,如果不调用,也是使用内部时钟,可以不写 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 = 10000 - 1; //周期,ARR自动重装器的值 TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//PSC预分频器的值 TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值,是高级计数器才有的 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure); //配置时基单元 //TIM_ClearFlag(TIM2,TIM_FLAG_Update);//手动把更新中断标志位清除 TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断,Update更新中断 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); } uint16_t Timer_GetCounter(void) { return TIM_GetCounter(TIM2); } //void TIM2_IRQHandler(void) //{ // if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位 // { // Num ++; // TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位 // // } //}
2、Timer.h
#ifndef __TIMER_H #define __TIMER_H void Timer_Init(void); uint16_t Timer_GetCounter(void); #endif
3、main.c
#include "stm32f10x.h" // Device header #include "Delay.h" #include "Buzzer.h" #include "Timer.h" //#include "Flame.h" #include "OLED.h" #include "usart.h" uint16_t Num;//定义一个16位得全局变量Num 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);//看CNT计数器值的变化 } } void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位 { Num ++; TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位 } }
本次程序需要一个OLED屏幕,OLED屏幕的程序可以参考一下下面的链接
https://blog.csdn.net/weixin_62353329/article/details/126042132?spm=1001.2014.3001.5501
四、程序效果(1)
图4-1 运行效果
注意事项:在图4-1中,我们可以看到复位后,Num值是从00001开始的,不是在00000开始计数,说明中断函数在初始化后,立刻进入一次计数。
图2-1
TimeBase_Init函数生成一个更新事件,来重新装载预分频器和重复计数器的值。预分频器由一个缓冲寄存器,写的值只有在更新事件时,才会真正起作用,为了让值立刻起作用,在最后手动生成一个更新事件,这样预分频器的值就有效了。
副作用:更新事件和更新中断,是同事发生的,更新中断会置更新中断标志位,一旦初始化完毕,更新中断就会进入。
图4-3
图4-4
解决方案:在TimeBase_Init的后面,开启中断的前面,手动调用Tim_ClearFlag函数,就可以避免刚初始化完就进入中断的问题。如图4-3,图4-4为解决后的效果。
五、程序(2)
所需硬件:OLED屏幕、对射式红外传感器(火焰传感器,选择一个可以计数的传感器即可)
D0数字输出接到PA0引脚,这个PA0引脚就是TIM2的ETR引脚,在此引脚输入一个外部时钟,VCC连正极,GND接负极,D0接PA0引脚
注意:D0一定要连接到PA0引脚, 在下面此文章的通用定时器中的外部时钟有介绍到。
https://blog.csdn.net/weixin_62353329/article/details/126128279?spm=1001.2014.3001.5501
1、Timer.c
#include "stm32f10x.h" // Device header
extern uint16_t Num;
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//注意是开启APB1的时钟函数,因为TIM2是APB1总线的外设
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//选择时基单元的时钟
//TIM_InternalClockConfig(TIM2);
//定时器上电后默认是使用内部时钟,如果不调用,也是使用内部时钟,可以不写
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);
//通过ETR引脚的外部时钟模式2配置
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_ClearFlag(TIM2,TIM_FLAG_Update);//手动把更新中断标志位清除
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能中断,Update更新中断
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);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
//void TIM2_IRQHandler(void)
//{
// if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位
// {
// Num ++;
// TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
//
// }
//}
时钟部分不使用内部时钟,选择TIM_ETRClockMode2Cofig,通过ETR引脚的外部时钟模式2配置,第一个参数给TIM2,迭戈参数是外部触发预分频器,不需要分频选择TIM_ExtTRGRSC_OFF,第三个参数,外部触发的极性
2、main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "Timer.h"
#include "OLED.h"
uint16_t Num;//定义一个16位得全局变量Num
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, Timer_GetCounter(), 5);
}
}
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//获取中断标志位
{
Num ++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
}
}
六、程序效果(2)
图6-1复位效果图
图6-2CNT值最高数
图6-3Num值+1