6-1 定时中断

发布于:2025-02-27 ⋅ 阅读:(13) ⋅ 点赞:(0)

参考手册

定时器共四个部分,分为八个小节笔记。本小节为第一部分第一节。

在第一部分,是定时器的基本定时的功能:定时中断功能、内外时钟源选择

在第二部分,是定时器的输出比较功能,最常见的用途是产生PWM波形,用于驱动电机等设备

在第三部分,是定时器的输入捕获功能和主从触发模式,来实现测量方波频率

在第四部分,是定时器的编码器接口,能够更加方便读取正交编码器的输出波形,编码电机测速


TIM简介

TIM(Timer)定时器,定时触发中断

定时器本质上就是一个计数器。

定时器可以对输入的时钟进行计数(在stm32中定时器的基准时钟一般是主频72MHz,如果对72MHz记72个数,那就是1MHz也就是1us的时间(72MHz就是1秒记72M个数,可以理解为对72个数计数1M次,记72个数的频率就是1MHz,用时1us)),如果记72000个数,那就是1KHz也就是1ms的时间,并在计数值达到设定值时触发中断

stm32的定时器拥有16位(2的16次方是65536)计数器(计数器就是用来执行计数定时的寄存器,每来一个时钟,计数器加1)、预分频器(可以对计数器的时钟进行分频,让计数更加灵活)、自动重装寄存器(是计数的目标值,计多少个时钟申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时。

预分频值(PSC)、自动重装载值(ARR)。

定时器的计数频率 = 时钟频率 / (PSC + 1)

最大定时时间 = (ARR + 1)/ 定时器的计数频率

即,最大定时时间 = (ARR + 1) * (PSC + 1) / 时钟频率

不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型


   

基本定时器

仅支持向上计数;

时基单元:预分配器(PSC)、自动重装载寄存器(ARR)、计数器(CNT)

通用计时器

1.通用定时器与基本定时器异同


 首先,中间最核心的部分,还是时基单元,如下,这部分结构和工作流程和基本定时器是一样的,不过对于通用定时器而言,计数器的计数模式就不止向上计数一种了(向上自增),通用定时器和高级定时器支持向上计数模式、向下计数模式和中央对齐模式。(基本定时器仅支持向上计数模式)。最常用的还是向上计数模式。

* 向下计数模式就是从重装值开始,向下自减,减到0之后,回到重装值同时申请中断,然后继续下一轮,依次循环

* 中央对齐模式就是从0开始,先向上自增,计到重装值,申请中断,然后再向下自减,减到0,再申请中断,然后继续下一轮,依次循环

 2.内外时钟源选择功能

定时器库函数简介

// 恢复定时器初始配置
void TIM_DeInit(TIM_TypeDef* TIMx);
 
// 时基单元初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
 
// 把时基单元初始化函数所用的结构体变量赋一个默认值
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
 
// 使能计数器(对应定时中断结构图中的“运行控制”功能) 
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
 
// 使能中断输出信号(对应定时中断结构图中的“中断输出控制”功能)
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

时基单元初始化函数

// 时基单元初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

第一个TIM_TypeDef* TIMx选择  某个定时器

第二个 TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct 结构体,包含配置时基单元的一些参数

使能计数器(对应定时中断结构图中的“运行控制”功能) 

// 使能计数器(对应定时中断结构图中的“运行控制”功能) 
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

 第一个 TIM_TypeDef* TIMx选择  某个定时器

第二个新状态   FunctionalState NewState  使能或者失能

使能中断输出信号(对应定时中断结构图中的“中断输出控制”功能)

// 使能中断输出信号(对应定时中断结构图中的“中断输出控制”功能)
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

 第一个 TIM_TypeDef* TIMx选择  某个定时器

第二个 uint16_t TIM_IT         选择 配置哪个中断输出

第三个 FunctionalState NewState   使能或者失能

时钟源选择的6个函数

//选择内部时钟
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
 
//选择ITRx其它定时器时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
 
//选择TIx捕获通道时钟,对于外部引脚的波形一般都会有极性选择和滤波器
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
                                uint16_t TIM_ICPolarity, uint16_t ICFilter);
 
//选择ETR通过外部时钟模式1输入的时钟
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                             uint16_t ExtTRGFilter);
 
//选择ETR通过外部时钟模式2输入的时钟,如果不需触发输入功能本函数可与上面函数互换
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, 
                             uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
 
//单独用来配置ETR引脚的预分频器、极性、滤波器这些参数
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
                   uint16_t ExtTRGFilter);

 NVIC

NVIC_Init(&NVIC_InitTyStructure);

参数(PSC、ARR等)更改函数(在程序运行过程中修改

 比如TIM_PrescalerConfig 函数,单独写预分频值

TIMx 为                                    选择  某个定时器

Prescaler                                就是要写入的预分频器的值

TIM_PSCReloadMode         写入的模式

//写预分频数的函数
TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
// 预分频值设置,TIM_PSCReloadMode为是否应用输入缓冲功能配置
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
 
// 改变计数器的计数模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
 
// 自动重装寄存器预装功能配置(计数器有无预装功能)
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
 
// 手动给计数器写入一个值
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
 
// 手动给自动重装寄存器写入一个值
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
 
// 获取当前计数器的值
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
 
// 获取当前的预分频器的值
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
 
// 获取定时中断的标志位和清除标志位,使用方法与EXTI相同
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);

6-1 定时器定时中断程序撰写

1.按照定时中断结构图配置定时器中断

第一步,RCC开启时钟,定时器的基准时钟和整个外设的工作时钟就都打开了
第二步,选择时基单元的时钟源,对于定时中断就选择内部时钟源
第三步,配置时基单元,包括预分频器、自动重装载器、计数模式等,参数用结构体配置
第四步,配置输出中断控制,允许更新中断输出到NVIC
第五步,配置NVIC,在NVIC中打开定时器中断通道并分配一个优先级
第六步,运行控制,使能计数器,当定时器使能后,计数器就开始计数了,当计数器更新时,触发中断
最后再写一个中断函数,中断函数每隔一段时间就能自动执行一次

准备初始化TIM2(通用定时器):

第一步,RCC开启时钟                开启APB1的时钟,TIM2是APB1总线的外设

第二步,选择时基单元的时钟        内部时钟

第三步,配置时基单元   写出结构体,再引出结构体的参数

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,定时器开始运行
}

TIM2中断函数

/**
  * 函    数:TIM2中断函数
  * 参    数:无
  * 返 回 值:无
  * 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
  *           函数名为预留的指定名称,可以从启动文件复制
  *           请确保函数名正确,不能有任何差异,否则中断函数将不能进入
  */
void TIM2_IRQHandler(void)
{
	if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)		//判断是否是TIM2的更新事件触发的中断
	{
		Num ++;												//Num变量自增,用于测试定时中断
		TIM_ClearITPendingBit(TIM2, TIM_IT_Update);			//清除TIM2更新事件的中断标志位
															//中断标志位必须清除
															//否则中断将连续不断地触发,导致主程序卡死
	}
}

6-2 定时器外部时钟

在上一个定时中断实例程序基础上进行更改;基本任务仍然是定时中断,时钟部分就不使用内部时钟了。

本次实验要完成的现象是:用光敏传感器手动模拟一个外部时钟,定义一个 uint16_t 的 Num 变量,当外部时钟触发10次(预分频之后的脉冲)后Num + 1。

在写完TIM2定时器函数后,添加外部中断函数

给上拉输入也是可以的;什么时候用浮空输入呢?当外部输入的功率信号很小,内部的上拉电阻会影响到信号时,用浮空输入

  • Timer.c

#include "stm32f10x.h"                  // Device header
 
/*
定时器初始化
对应定时中断结构图
 
第一步,RCC开启时钟,定时器的基准时钟和整个外设的工作时钟就都打开了
第二步,选择时基单元的时钟源,对于定时中断就选择内部时钟源
第三步,配置时基单元,包括预分频器、自动重装载器、计数模式等,参数用结构体配置
第四步,配置输出中断控制,允许更新中断输出到NVIC
第五步,配置NVIC,在NVIC中打开定时器中断通道并分配一个优先级
第六步,运行控制,使能计数器,当定时器使能后,计数器就开始计数了,当计数器更新时,触发中断
最后再写一个中断函数,中断函数每隔一段时间就能自动执行一次
 
*/
void Timer_Init(void)  //定时中断初始化代码
{
	//初始化tim2,也就是通用定时器
	//使用APB1的开启时钟函数,TIM2是APB1总线的外设
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	
	//外部模式2需要用到gpio,进行GPIOA的时钟配置
	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);
	
	//通过ETR引脚的外部时钟模式2配置
	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; //计数器模式
	/*定时1s也就是定时频率为1Hz,定时频率=72M/ (PSC + 1) / (ARR + 1) = 1s =1Hz,
	那就可以PSC给7200,ARR给10000(1MHz等于10^6Hz),然后两个参数再减1
	在这里预分频是对72M进行7200分频,得到的就是10k的计数频率,
	在10k的频率下,计10000个数,就是1s的时间*/
	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函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值
	预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用
	为了让写入的值立刻起作用,故在函数的最后手动生成了一个更新事件
	但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次
	在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题*/
	TIM_ClearFlag(TIM2,TIM_FLAG_Update);
	
	//使能中断
	TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE); //开启更新中断到NVIC的通道
	
	//NVIC中断
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//NVIC优先级分组
	NVIC_InitTypeDef NVIC_InitTyStructure;
	NVIC_InitTyStructure.NVIC_IRQChannel = TIM2_IRQn;
	NVIC_InitTyStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitTyStructure.NVIC_IRQChannelPreemptionPriority = 2;//抢占优先级
	NVIC_InitTyStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级
	NVIC_Init(&NVIC_InitTyStructure); 
	
	//启动定时器
	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);//清除标志位
	}
}
*/
 
 

  • main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
 
uint16_t Num;
 
int main(void)
{
	
	OLED_Init();	//初始化OLED
	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计数器值的变化情况(变化范围是ARR从0一直到自动重装值(10000-1))
	}
 
}
 
 
 
 
 
//定时器2中断函数放在使用中断的main.c文件中;在startup文件中
void TIM2_IRQHandler(void) //当定时器产生更新中断时,这个函数就会自动被执行
{
	//检查中断标志位
	if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
	{
	//执行相应的用户代码
		Num ++;//定时器每秒自动加一个Num全局变量
		TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
	}
 
}