1.输入捕获介绍
在定时器中断实验章节中我们介绍了通用定时器具有多种功能,输入捕获就是其中一种。STM32F1除了基本定时器TIM6和TIM7,其他定时器都具有输入捕获功能。输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,通常用于测量输入信号的脉宽、测量 PWM 输入信号的频率及占空比。
输入捕获的工作原理比较简单,在输入捕获模式下,当相应的 ICx 信号检测到跳变沿后,将使用捕获/比较寄存器(TIMx_CCRx)来锁存计数器的值。简单的说就是通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。下面我们以输入捕获测量脉宽为例,通过一个简图来介绍输入捕获的原理。
CNT计数的次数等于:有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。
上图以及公式N*ARR+CCRx2,简单解释如下:
首先,定时器在以一个固定次数不断重载并且计数,比如,重载值为1000,那么它就一直不断地从0计数到1000,然后再次从0计数到1000。与此同时,输入引脚在监测上升沿跳变,假定某一次计数到500的时候,监测到了一个上升沿,那么,这个时候就产生一个内部触发,定时器就立即重载,于是定时器马上从0开始再次计数,(所以,上图中T1的位置其实是错误的!)同时监测改为下降沿触发,开始监测下降沿!定时器从0开始计数后,有可能1000以前,比如,计到800,就监测到了下降沿,那么很简单,高电平的持续时间,就是800*一个计数的计数周期。如果计到1000后,仍然没有监测到下降沿,那么就从0开始计数,一直这样循环,直到发现下降沿为止。
2.输入捕获配置步骤
接下来我们介绍下如何使用库函数对通用定时器的输入捕获进行配置。这个也是在编写程序中必须要了解的。其实输入捕获和前面定时器中断一样也是通用定时器的一个功能,因此还是要用到定时器的相关配置函数,具体步骤如下:(定时器相关库函数在stm32f10x_tim.c和
stm32f10x_tim.h文件中)。
(1)使能定时器及端口时钟,并设置引脚复用器映射和引脚模式等
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等
voidTIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*
TIM_TimeBaseInitStruct);
(3)设置通用定时器的输入捕获参数,开启输入捕获功能
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef*
TIM_ICInitStruct);
typedef struct
{
uint16_t TIM_Channel; //通道
uint16_t TIM_ICPolarity; //捕获极性
uint16_t TIM_ICSelection;//映射
uint16_t TIM_ICPrescaler;//分频系数
uint16_t TIM_ICFilter; //滤波器长度
} TIM_ICInitTypeDef;
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);
如果我们需要配置TIM5的通道1为输入捕获功能,并且为上升沿捕获、
不分频、直接映射到TI,可以如下配置:
TIM_ICInitTypeDef TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; //通道1
TIM_ICInitStructure.TIM_ICFilter=0x00; //滤波
TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//捕获极性
TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //分频系数
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;//直接映射到TI1
TIM_ICInit(TIM5,&TIM_ICInitStructure);
(4)开启捕获和定时器溢出(更新)中断
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);
(5)设置定时器中断优先级,使能定时器中断通道
NVIC初始化库函数是NVIC_Init()
(6)编写定时器中断服务函数
TIM5_IRQHandler
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
TIM_SetCounter(TIM5,0); //定时器初值为0
(7)使能定时器
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
3.硬件电路
本实验使用到硬件资源如下:
(1)D1指示灯
(2)K_UP按键
(3)串口1
(4)TIM5的通道1
D1指示灯用来提示系统正常运行,K_UP按键是接在PA0管脚上的,所以可以通过此按键输入一个高电平,通过串口1的printf函数打印捕获到的高电平时间。
4.编写输入捕获控制程序
本实验所要实现的功能是:按下key_up键,产生一个高电平脉冲,使用TIM5的CH1检测输入信号高电平脉宽,将检测的高电平脉宽时间通过printf函数打印出来,同时让D1指示灯不断闪烁表示系统正常运行。程序框架如下:
(1)初始化PA0管脚为TIM5通道1输入捕获功能,开启捕获和溢出中断等
(2)编写TIM5中断服务函数
(3)编写主函数
main.c:
#include "system.h"
#include "led.h"
#include "SysTick.h"
#include "usart.h"
#include "input.h"int main()
{u32 indata=0;u8 i=0;SysTick_Init(72);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级分组LED_Init();USART1_Init(9600);TIM5_CH1_Input_Init(0xffff,71);//自动重载值设为最大,且计数频率为1M,每次计数为1us,这样计数的总数就是高电平的持续时间while(1){if(TIM5_CH1_CAPTURE_STAUS&0x80){indata=TIM5_CH1_CAPTURE_STAUS&0x3f;indata*=0xffff;indata+=TIM5_CH1_CAPTURE_VALUE;printf("高电平持续时间:%d us\r\n",indata);TIM5_CH1_CAPTURE_STAUS=0;//清空以便再次捕获}i++;if(i%20 ==0){led1=!led1;//LED1闪,用来指示主程序循环是否运行}delay_ms(50);}
}
input.c
#include "input.h"
#include "SysTick.h"u8 TIM5_CH1_CAPTURE_STAUS;//这个标志不仅用来判断是否产生了中断,同时也用来记录溢出中断的次数
u16 TIM5_CH1_CAPTURE_VALUE;//保存捕获到下降沿中断时,计数器的数值void TIM5_CH1_Input_Init(u16 period,u16 prescaler)
{GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;//结构体变量声明TIM_ICInitTypeDef TIM_ICInitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//使能TIM5时钟GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //GPIO_Init(GPIOA,&GPIO_InitStructure);TIM_TimeBaseInitStructure.TIM_Period=period; //装入函数传过来的自动装载值TIM_TimeBaseInitStructure.TIM_Prescaler=prescaler; //装入函数传过来的分频系数TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//1分频(没有分频)TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //设置向上计数模式:从0开始计数到自动重载值后溢出产生中断TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);//初始化TIM5各参数:自动重装值、分频系统、计数方式等TIM_ICInitStructure.TIM_Channel=TIM_Channel_1; //通道1TIM_ICInitStructure.TIM_ICFilter=0x00; //滤波TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;//捕获极性设为上升沿TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1; //分频系数TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;//直接映射到TI1TIM_ICInit(TIM5,&TIM_ICInitStructure);TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//开启捕获和定时器溢出(更新)中断//设置定时器中断优先级,使能定时器中断通道NVIC_InitStructure.NVIC_IRQChannel= TIM5_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =2;NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_Cmd(TIM5,ENABLE );//使能定时器}void TIM5_IRQHandler(void)
{if((TIM5_CH1_CAPTURE_STAUS&0x80)==0)//在进入打印输出时,不检测、不捕获{if(TIM_GetITStatus(TIM5,TIM_IT_Update))//判断是否产生溢出中断 {if(TIM5_CH1_CAPTURE_STAUS&0x40){if((TIM5_CH1_CAPTURE_STAUS&0x3f)==0x3f)//处理高电平时间过长的情况{TIM5_CH1_CAPTURE_STAUS |= 0x80;TIM5_CH1_CAPTURE_VALUE=0xffff;} else{TIM5_CH1_CAPTURE_STAUS++;//保存定时溢出中断的次数}}}if(TIM_GetITStatus(TIM5, TIM_IT_CC1))//判断是否产生上升捕获中断{if(TIM5_CH1_CAPTURE_STAUS&0x40) //0x40 = 0100 0000,判断是否捕获到下降沿{TIM5_CH1_CAPTURE_STAUS |= 0x80;TIM5_CH1_CAPTURE_VALUE=TIM_GetCapture1(TIM5);TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);//重新改为上降沿触发}else //产生上升捕获后先进else,把定时器重载,以从0开始重新计数{TIM5_CH1_CAPTURE_STAUS =0;TIM5_CH1_CAPTURE_VALUE =0;TIM5_CH1_CAPTURE_STAUS|= 0x40;//这就是为在第二次捕获(即捕获到下降沿)时能进入上面的if语句里TIM_Cmd(TIM5,DISABLE );TIM_SetCounter(TIM5,0); //设定定时器初值为0以重新计数TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);//改为下降沿触发TIM_Cmd(TIM5,ENABLE );//使能定时器}}}TIM_ClearITPendingBit(TIM5, TIM_IT_Update|TIM_IT_CC1);//清除溢出标志}
input.h
#ifndef _input_H
#define _input_H#include "system.h"
extern u8 TIM5_CH1_CAPTURE_STAUS;
extern u16 TIM5_CH1_CAPTURE_VALUE;void TIM5_CH1_Input_Init(u16 period,u16 prescaler);
void TIM5_IRQHandler(void);#endif
程序烧写到开发板运行,实验结果如下:实验是成功的!