STM32之输入捕获

今天来简单介绍一下STM32的输入捕获功能。输入捕获说的通俗一点就是用计数器(定时器)来记录某一个脉冲高电平的时间,或者我们只捕获脉冲的上升沿或者下降沿,这具体要根据具体事例进行分析。
它的基本工作过程就是先捕捉一次脉冲上升沿,然后计数器开始计时,等待着捕捉到脉冲下降沿,等到捕捉到下降沿的时候,计数器停止计数,计算计数器中的数值,这个数值就是高电平所持续的时间,然后再重现开始下一轮的捕捉。
今天我们就用输入捕获来实现计算某一个脉冲的高电平持续时间。
我们所用到的是STM32F103ZET6的定时器5的通道一,然后是通过按键按下去来产生一个一定宽度的矩形脉冲,其中按键是接在PA0引脚上,并且按键按下去,引脚会被拉为高电平,以此我们通过输入捕获来计算按键按下去的时间。
首先用到定时器,还是按照老套路进行相关的配置,代码如下

void time_cap_init(u16 arr,u16 psc)
{
    //定义结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    //使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);


    //配置TIM5
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period=arr;
    TIM_TimeBaseStructure.TIM_Prescaler=psc;
    TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);


    //配置TIM5 Channel1输入捕获
    TIM_ICInitStructure.TIM_Channel=TIM_Channel_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;
    TIM_ICInit(TIM5,&TIM_ICInitStructure);


    //配置GPIOGPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入模式
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    GPIO_ResetBits(GPIOA,GPIO_Pin_0);


    //配置中断分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
    NVIC_Init(&NVIC_InitStructure);


    TIM_Cmd(TIM5,ENABLE);//使能TIM5
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);//允许溢出中断
    TIM_ITConfig(TIM5,TIM_IT_CC1,ENABLE);//允许捕获中断
}

以上就是我们对定时器和GPIO进行的相关初始化,里面很多和之前博客中介绍的配置方法基本一样,我就不再多说,我们只来看一下配置定时器输入捕获功能的相关代码,如下

    //定义结构体
    TIM_ICInitTypeDef TIM_ICInitStructure;

    //配置TIM5 Channel1输入捕获
    TIM_ICInitStructure.TIM_Channel=TIM_Channel_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;
    TIM_ICInit(TIM5,&TIM_ICInitStructure);

这段代码就是配置定时器的输入捕获功能的代码,它也是用一个结构体来实现配置的。首先做的就是先定义一个结构体,然后结构体的第一个变量就是配置定时器的几通道来进行捕获,这里我们选用定时器5的通道1;第二个成员变量配置的是关于捕获滤波的,关于滤波,他其实就像我们对按键进行消抖一样,这里我不做过多说明,大家可以查阅百度,我们这里选择不进行滤波,所以第二个成员配置成为0x00;第三个成员配置的是捕获的极性,也就是我们要让单片机捕获上升沿还是下降沿,因为我们今天要测的是高电平的时间,所以初始化的时候,我们要设置成上升沿捕获;第四个成员配置的是我们检测了几个上升沿(或者下降沿)才产生一次中断,这里我们要让他每次检测到上升沿(或者下降沿)就产生中断,所以参数配置成TIM_ICPSC_DIV1,这个名字在32的库里面已经定义好了,它代表的就是0x00,如下

#define TIM_ICPSC_DIV1                     ((uint16_t)0x0000) /*!< Capture performed each time an edge is detected on the capture input. */
#define TIM_ICPSC_DIV2                     ((uint16_t)0x0004) /*!< Capture performed once every 2 events. */
#define TIM_ICPSC_DIV4                     ((uint16_t)0x0008) /*!< Capture performed once every 4 events. */
#define TIM_ICPSC_DIV8                     ((uint16_t)0x000C) /*!< Capture performed once every 8 events. */
#define IS_TIM_IC_PRESCALER(PRESCALER) (((PRESCALER) == TIM_ICPSC_DIV1) || \
                                        ((PRESCALER) == TIM_ICPSC_DIV2) || \
                                        ((PRESCALER) == TIM_ICPSC_DIV4) || \
                                        ((PRESCALER) == TIM_ICPSC_DIV8))

然后第五个成员变量我就不过多说了,因为我也说不太清楚。
然后就是调用函数TIM_ICInit(TIM5,&TIM_ICInitStructure);把各个成员变量导入到相应的结构体中,从而控制相应的寄存器。
以上是关于几个模块的初始化程序,下面介绍今天的重点,那就是定时器5的中断服务程序,如下

void TIM5_IRQHandler(void)//中断处理函数  (尤其要注意中断服务函数名字的写法)
{
    if((time5_capture_sta&0x80)==0)//标志位最高位为0,说明还没有成功捕获到高电平,此时捕获中断和溢出中断可被响应
    {
        if(TIM_GetITStatus(TIM5,TIM_IT_CC1)!=RESET)//发生捕获中断
        {
            if((time5_capture_sta&0x40)==0)//标志位的次高位为0,说明还没有捕获到一次上升沿,所以这此发生的捕获中断,是捕捉到了一次上升沿
            {
                time5_capture_sta=0;
                time5_capture_val=0;
                TIM_SetCounter(TIM5,0);//设置计数器中的值,在这里将计数器的值设为0
                time5_capture_sta|=0x40;//把标志位的次高位置1,标记为已经捕获到了一次上升沿,这里必须要用或运算,因为time5_capture_sta这个变量的第六位保存着计数器溢出的次数,我们在改变这个变量的最高位和次高位的时候,不能改动后面的第六位数据
                TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);//设置为下降沿捕获
            }
            else if(time5_capture_sta&0x40)//标志位次高位为1,说明上次已经捕获到了一次上升沿,那么这次发生的捕获中断就是捕获到的下降沿
            {
                time5_capture_sta|=0x80;//把标志位的最高位置1,标记已经成功捕获到了一次高电平
                time5_capture_val=TIM_GetCapture1(TIM5);//获取当前计数器中的数值
                TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);//设置为上升沿捕获,为下一次捕获做准备
            }
        }
        if(TIM_GetITStatus(TIM5,TIM_IT_Update)!=RESET)//发生了溢出中断
        {
            if(time5_capture_sta&0x40)//判断是否在溢出之前已经捕获到了高电平,其实是肯定已经捕捉到了一次高电平
            {
                if((time5_capture_sta&0x3f)==0x3f)//高电平时间太长了,已经溢出了2的6次方-1=63次
                {
                    time5_capture_sta|=0x80;//把标志位的最高位置1,标记已经成功捕获到了一次高电平,此时为强制结束高电平检测
                    time5_capture_val=0xffff;//强制结束后,将计数器的值设为最大,其实程序运行到这里,它已经是最大了
                }
                else
                {
                    time5_capture_sta++;//如果溢出次数没有超出标志位的低六位所能表示的范围,那么就让标志位直接加1,以此来记录溢出的次数,溢出次数一直加下去,啥时候超出了标志位的低六位所能表示的范围,那么就强制退出检测,直接认为此次检测高电平已经结束,然后直接开启下一次检测
                }
            }
        }
    }
    //清除中断标志位
    TIM_ClearITPendingBit(TIM5,TIM_IT_CC1);
    TIM_ClearITPendingBit(TIM5,TIM_IT_Update);
}

这个程序也比较多,细说也比较麻烦,我已经基本上把每一句程序都给写了注释,我就大概讲一下整体的思路。
这个中断中我们使用到了两个变量:time5_capture_sta和time5_capture_val,这两个变量都是我们自己定义的,来帮助我们进行输入捕获功能的实现。其中time5_capture_val就是用来记录当前计数器中的数值,这个比较好理解,主要就是另一个变量,time5_capture_sta,我们来看一下它的各数据位所代表的的含义

从图中我们可以看到,time5_capture_sta的最高位是捕获完成的标志位,如果我们目前已经完成了一次高电平的捕获(即捕捉到了一次上升沿和一次下降沿),那么该位置1,否则置0;time5_capture_sta的次高位是捕获到高电平标志位,如果我们现在已经捕获到了一次上升沿,那么就把该位置1,然后再把捕获的极性改为下降沿,然后就等待着下一个中断的发生(每捕捉到一次边沿就发生一次中断),我们就这样一直依靠这两个标志位来进行上升沿和下降沿的捕获。然后还有一个问题就是,如果某一个脉冲高电平的时间太长了,已经超出了我们定时器(计数器)所能记录的范围,那么计数器就会产生溢出,而这个溢出的次数我们要记录下来,我们一会计算高电平时间要用到这个溢出次数,所以我们就用time5_capture_sta的后六位来进行记录计数器溢出的次数,如果高电平持续时间仍然提高,导致溢出的次数也超过了time5_capture_sta后六位所能表示的数值范围,那么这个时候,就让程序强制结束此次高电平的捕获,即认为这个脉冲的高电平时间是计数器的最大值。
这个中断服务函数里面包含了两个中断,一个是捕获中断,一个是溢出中断,在进入到这个中断服务函数里面,我们首先进行判断是发生了哪种中断,然后再进行相应的操作。
以下是整个time.c文件的程序

#include "time.h"
u8 time5_capture_sta=0;
u16 time5_capture_val;

void time_cap_init(u16 arr,u16 psc)
{
    //定义结构体
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    GPIO_InitTypeDef GPIO_InitStructure;

    //使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);


    //配置TIM5
    TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period=arr;
    TIM_TimeBaseStructure.TIM_Prescaler=psc;
    TIM_TimeBaseInit(TIM5,&TIM_TimeBaseStructure);


    //配置TIM5 Channel1输入捕获
    TIM_ICInitStructure.TIM_Channel=TIM_Channel_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;
    TIM_ICInit(TIM5,&TIM_ICInitStructure);


    //配置GPIO口
    GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;//下拉输入模式
    GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
    GPIO_Init(GPIOA,&GPIO_InitStructure);
    GPIO_ResetBits(GPIOA,GPIO_Pin_0);


    //配置中断分组
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;
    NVIC_Init(&NVIC_InitStructure);


    TIM_Cmd(TIM5,ENABLE);//使能TIM5
    TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE);//允许溢出中断
    TIM_ITConfig(TIM5,TIM_IT_CC1,ENABLE);//允许捕获中断
}

void TIM5_IRQHandler(void)//中断处理函数  (尤其要注意中断服务函数名字的写法)
{
    if((time5_capture_sta&0x80)==0)//标志位最高位为0,说明还没有成功捕获到高电平,此时捕获中断和溢出中断可被响应
    {
        if(TIM_GetITStatus(TIM5,TIM_IT_CC1)!=RESET)//发生捕获中断
        {
            if((time5_capture_sta&0x40)==0)//标志位的次高位为0,说明还没有捕获到一次上升沿,所以这此发生的捕获中断,是捕捉到了一次上升沿
            {
                time5_capture_sta=0;
                time5_capture_val=0;
                TIM_SetCounter(TIM5,0);//设置计数器中的值,在这里将计数器的值设为0
                time5_capture_sta|=0x40;//把标志位的次高位置1,标记为已经捕获到了一次上升沿,这里必须要用或运算,因为time5_capture_sta这个变量的第六位保存着计数器溢出的次数,我们在改变这个变量的最高位和次高位的时候,不能改动后面的第六位数据
                TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);//设置为下降沿捕获
            }
            else if(time5_capture_sta&0x40)//标志位次高位为1,说明上次已经捕获到了一次上升沿,那么这次发生的捕获中断就是捕获到的下降沿
            {
                time5_capture_sta|=0x80;//把标志位的最高位置1,标记已经成功捕获到了一次高电平
                time5_capture_val=TIM_GetCapture1(TIM5);//获取当前计数器中的数值
                TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);//设置为上升沿捕获,为下一次捕获做准备
            }
        }
        if(TIM_GetITStatus(TIM5,TIM_IT_Update)!=RESET)//发生了溢出中断
        {
            if(time5_capture_sta&0x40)//判断是否在溢出之前已经捕获到了高电平,其实是肯定已经捕捉到了一次高电平
            {
                if((time5_capture_sta&0x3f)==0x3f)//高电平时间太长了,已经溢出了2的6次方-1=63次
                {
                    time5_capture_sta|=0x80;//把标志位的最高位置1,标记已经成功捕获到了一次高电平,此时为强制结束高电平检测
                    time5_capture_val=0xffff;//强制结束后,将计数器的值设为最大,其实程序运行到这里,它已经是最大了
                }
                else
                {
                    time5_capture_sta++;//如果溢出次数没有超出标志位的低六位所能表示的范围,那么就让标志位直接加1,以此来记录溢出的次数,溢出次数一直加下去,啥时候超出了标志位的低六位所能表示的范围,那么就强制退出检测,直接认为此次检测高电平已经结束,然后直接开启下一次检测
                }
            }
        }
    }
    //清除中断标志位
    TIM_ClearITPendingBit(TIM5,TIM_IT_CC1);
    TIM_ClearITPendingBit(TIM5,TIM_IT_Update);
}

以下是time.h文件中的代码

#ifndef __TIME_H_
#define __TIME_H_
#include "sys.h"

void time_cap_init(u16 arr,u16 psc);

#endif

接下来看一下main.c文件的代码

#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "time.h"

extern u8 time5_capture_sta;
extern u16 time5_capture_val;
int main(void)
{
    u32 temp=0;
    time_cap_init(0xffff,71);
    while(1)
    {
        if(time5_capture_sta&0x80)//标志位的最高位为1,说明已经完成了一次高电平的捕获
        {
            temp=time5_capture_sta&0x3f;//取出溢出的次数
            temp*=65536;//溢出的时间总和
            temp+=time5_capture_val;
            printf("HIGH: %d us\r\n",temp);
            time5_capture_sta=0;//清除标志位的最高位,以便能够响应接下来的捕获中断和溢出中断,开启下一次的捕获
        }
    }
}

在main.c文件中我们主要就是进行相关初始化函数的调用以及进行高电平时间的计算。
其中主要部分就是高电平时间的计算,他就是计数器溢出次数乘以计数器的最大值,然后再加上当前计数器中的数值,然后再把标志位给清零。
以上就是输入捕获的所有内容,第一次接触可能不太好理解这个过程,其实仔细慢下来思考一下这个过程,也就慢慢接受它了。

猜你喜欢

转载自blog.csdn.net/qq_36554582/article/details/81611272