初学STM32:定时器中断的探索

初学STM32之定时器中断


一、定时器概述

1.定时器的分类

STM32F10x系列总共最多有8个定时器分为高级定时器,通用定时器和基本定时器。三种定时器的主要区别如下图所示:

本文章仅介绍通用定时器的一些知识。

2.通用定时器功能概述

通用定时器的功能包括:

  • 16位向上、向下、向上/向下自动装载计数器;
  • 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值;
  • 位于低速的APB1总线上;
  • 4个独立通道:
    ─ 输入捕获
    ─ 输出比较
    ─ PWM生成(边缘或中间对齐模式)
    ─ 单脉冲模式输出
  • 使用外部信号控制定时器和定时器互连的同步电路
  • 如下事件发生时产生中断/DMA:
    ─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
    ─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
    ─ 输入捕获
    ─ 输出比较
  • 支持针对定位的增量(正交)编码器和霍尔传感器电路
  • 触发输入作为外部时钟或者按周期的电流管理
  • 通用定时器经常被用于测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)等。
    注:使用定时器预分频器和RCC时钟预分频器,脉冲长度和波形周期可以在几微秒和几毫秒间调整。STM32的每个通用定时器是完全独立的,没有相互共享的任何资源。

    3.通用定时器工作过程

    通用定时器的工作过程如图所示:


    如图所示,通用定时器的工作过程大致可以分为时钟发生器、时基单元、输入捕获和输出比较四个部分。

    在时钟发生器中,由内部时钟或外部TIMx_ETR等经过一系列处理产生时钟源,送入时基单元;在时基单元中经过预分频产生一个时钟,再由CNT进行计数(向上或向下),计数到自动重装载值时可以触发相应的中断;在输入捕获部分中,对TIMx_CHx中的信号进行捕获,再进行滤波等操作,再通过捕获比较寄存器中捕获到两次信号后的计数器的值,就可以得到脉冲宽度等;在输出比较的部分中,可以在捕获比较寄存器中设置一个数值,用计数器中的值与其比较,当高于该数值时,输出高/低电平,低于该数值时,输出低/高电平。

    二、通用定时器的简单应用

    1.定时器中断

    定时器中断主要应用到通用定时器的时钟发生器和时基单元两个部分。

    1.时钟选择

    计数器的时钟可以由以下时钟源提供:

    1. 内部时钟(CK_INT)
    2. 外部时钟模式1:外部输入脚(TIx)
    3. 外部时钟模式2:外部触发输入(ETR)
    4. 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。

    时钟计算方法如图所示:

    2.计数器模式

    通用计数器模式分为三种:向上计数、向下计数、向上向下双向计数。
    向上计数模式:计数器从0计数到自动加载值,然后从0重新开始计数并产生一个计数器溢出事件;
    向下计数模式:计数器从自动装入值开始向下计数到0,然后从自动装入的值重新开始并产生一个计数器向下溢出事件;
    向上/向下计数(中央对齐模式):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件;然后再从0开始重新计数。

    3.定时器中断实现步骤

    1. 使能定时器时钟;
    2. 初始化定时器,配置自动装载寄存器ARR,预分频器寄存器PSC;
    3. 开启定时器中断;
    4. 配置NVIC;
    5. 使能定时器;
    6. 编写中断服务函数;

    代码如下:

    void Tim3_Int_Init(u16 arr,u16 psc)
    {
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    	NVIC_InitTypeDef NVIC_InitStruct;
    	
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能定时器时钟
    	
    	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    	TIM_TimeBaseInitStruct.TIM_Period = arr;
    	TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
    	
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);//初始化定时器
    	
    	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);//开启定时器中断
    	
    	NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn;
    	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
    	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 3;
    	NVIC_Init(&NVIC_InitStruct);//配置NVIC
    	
    	TIM_Cmd(TIM3,ENABLE);//使能定时器
    }
    
    void TIM3_IRQHandler(void)
    {
    	if(TIM_GetITStatus(TIM3,TIM_IT_Update) == 1)//检查是否更新中断
    	{
    		TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除更新中断标志
    		LED1 = !LED1;
    	}
    	
    }
    

    2.PWM输出

    PWM输出主要依靠通用定时器的输出比较部分。

    1.工作过程

    在输出比较的部分中,可以在捕获比较寄存器中设置一个数值,用计数器中的值与其比较,当高于该数值时,输出高/低电平,低于该数值时,输出低/高电平。
    工作过程如图所示:

    由图可知,ARR控制PWM输出的周期,CCRx控制PWM输出的占空比。

    2.PWM输出的实现步骤

    1. 使能定时器时钟;
    2. 使能相应IO口的时钟;
    3. 使能AFIO时钟(本实验需要把PB5用作PWM的输出引脚,所以需要重映射配置,所以要开启AFIO时钟);
    4. 初始化IO口(GPIO的模式配置为复用输出);
    5. 重映射;
    6. 初始化定时器;
    7. 初始化输出比较参数;
    8. 使能预装载寄存器;
    9. 使能定时器;
      注: 可以通过改变CCRx的值达到改变占空比的效果[TIM_SetCompare2()]

    代码如下:

    void Tim3_PWM_Init(u16 arr,u16 psc)
    {
    	GPIO_InitTypeDef GPIO_InitStruxture;
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    	TIM_OCInitTypeDef TIM_OCInitStruct;
    	
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//使能定时器时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);//使能相应的GPIO口并且开启AFIO时钟
    	
    	GPIO_InitStruxture.GPIO_Mode = GPIO_Mode_AF_PP;
    	GPIO_InitStruxture.GPIO_Pin = GPIO_Pin_5;
    	GPIO_InitStruxture.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_Init(GPIOB,&GPIO_InitStruxture);//配置GPIO口
    	
    	GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3,ENABLE);//重映射
    	
    	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    	TIM_TimeBaseInitStruct.TIM_Period = arr;
    	TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
    	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct);//初始化定时器
    	
    	TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM2;
    	TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
    	TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
    	TIM_OC2Init(TIM3,&TIM_OCInitStruct);//初始化比较参数
    	
    	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);//使能预装载寄存器
    	TIM_Cmd(TIM3,ENABLE);//使能定时器
    }
    

    3.输入捕获

    输入捕获主要依靠通用定时器中的输入捕获部分

    1.工作过程

    通过检测TIMx_CHx的边沿信号,在边沿信号发生跳变(上升沿/下降沿)时,将当前定时器的值(TIMx_CNT)存放到相应的捕获比较寄存器(TIMx_CCRx)里面,完成一次捕获。
    注:完成一次捕获后,如果要记录高/低电平时长,不能忘记可能高/低电平持续时间过长导致中断再次刷新。

    2.输入捕获配置步骤

    1. 使能定时器和通道对应IO口的时钟;
    2. 初始化IO口(模式为GPIO_Mode_IPD);
    3. 初始化定时器;
    4. 初始化输入捕获通道;
    5. 开启捕获中断;
    6. 配置NVIC;
    7. 使能定时器;
    8. 编写中断服务函数;

    代码如下:

    TIM_ICInitTypeDef TIM_ICInitStruct;
    
    
    void TIM5_Cap_Init(u16 arr,u16 psc)
    {
    	GPIO_InitTypeDef GPIO_InitStruxture;
    	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
    
    	NVIC_InitTypeDef NVIC_InitStruct;
    
    	
    	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能定时器对应时钟
    	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//使能GPIO对应时钟
    	
    	GPIO_InitStruxture.GPIO_Mode = GPIO_Mode_IPD;
    	GPIO_InitStruxture.GPIO_Pin = GPIO_Pin_0;
    	GPIO_Init(GPIOA,&GPIO_InitStruxture);//配置GPIO
    	//GPIO_ResetBits(GPIOA,GPIO_Pin_0);	
    	
    	TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
    	TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
    	TIM_TimeBaseInitStruct.TIM_Period = arr;
    	TIM_TimeBaseInitStruct.TIM_Prescaler = psc;
    	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStruct);//初始化定时器
    	
    	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
    	TIM_ICInitStruct.TIM_ICFilter = 0x00;
    	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
    	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
    	TIM_ICInit(TIM5,&TIM_ICInitStruct);//初始化输入捕获通道
    
    	
    	NVIC_InitStruct.NVIC_IRQChannel = TIM5_IRQn;
    	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
    	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
    	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
    	NVIC_Init(&NVIC_InitStruct);//配置NVIC
    
    	TIM_ITConfig(TIM5,TIM_IT_Update | TIM_IT_CC1,ENABLE);//开启捕获中断
    	TIM_Cmd(TIM5,ENABLE);//使能定时器
    	
    }
    

    总结

    以上是STM32定时器的一些工作过程与应用,在实验过程中我用PWM控制输出实现了一个呼吸灯,紧接着再进行对按键的进行输入捕获,获取按键按下时长在编写代码时我发现,在while循环中如果不加一个delay的延迟函数,则呼吸灯无法正常工作,只能处于一个常亮的状态,而输入捕获却可以正常运行。加上延时函数则可以正常工作。目前原因还没有找到

    物联沃分享整理
    物联沃-IOTWORD物联网 » 初学STM32:定时器中断的探索

    发表评论