单片机
返回首页

STM32输入捕获实验示例详解

2022-03-28 来源:eefocus

STM32输入捕获实验

寄存器部分讲解(以TIM5_CH1为例)

TIMx_CCMR1.ICF[3:0]的作用

滤波器的作用就是“采集取样以便于确定准确的电平状态”。我们以ICIF[3:0] = 0010为例:

 

实例应用:假设输入信号在最多5个内部时钟周期的时间内抖动,我们须配置滤波器的带宽长于5个时钟周期。因此我们可以(以fDTS频率)连续采样8次,以确认在TI1上一次真实的边沿变换,即在TIMx_CCMR1寄存器中写入IC1F=0011。


TIMx_CCER.CC1P的作用

这个寄存器很重要,它决定了“上升沿/下降沿触发输入捕获 “,而且最重要的是,它是用来配置极性的唯一寄存器,这说明输入极性与输出极性都要经过它进行配置,因此我们在下面调用函数时,你会发现无论是PWM输出还是输入捕获,改变极性都是配置的相同函数。

TIMx_CCMR1.CC1S的作用

以上是通道配置,CC1S[1:0]的存在是为了配置“触发事件/中断的信号种类“,例如CC1S[1:0] = 01,那么在TIM5_CH1中触发中断/事件的信号是来自TI1FP1的。

TIMx_CCMR1.ICPS的作用

预分频器的作用就在于配置“我们需要几个有效的电平变化才触发一次中断?“。

TIMx_CCER.CC1E的作用

使能相应的输入捕获通道,使得当IC1PS端口输出有效的电平变化,能立刻被捕获触发相应的事件/中断。


编写程序的技巧

如何判断是上升沿还是下降沿?

脉冲沿+前一时刻的电平状态=电平变化。例如:前一时刻的电平为高电平而且脉冲沿被捕获,那么我们就可以知道“此时的电平为低电平而且脉冲沿为下降沿脉冲“。


如何进行脉冲宽度测量?

我们知道“输入捕获的原理就是:当有效的脉冲变化被捕获那么计数器的值就会自动被捕获/比较寄存器捕获“,我们可以这样做:如果我们想要测量高电平的持续时间,我们可以当上升沿出现时我们将计数器的初始计数值置0,下降沿出现时我们将此时的计数器的值加载到捕获/比较计数器中进行捕获读取。最后”(捕获的值-计数器初始值)*计数器的单位计数时长=高电平持续时间“。


如果高电平持续时间过长定时器发生溢出该怎么办?

我们知道每个定时器都有一个对应的中断服务函数,我们可以使能两个中断:计数器溢出中断+输入捕获中断。我们可以记录当输入电平为高电平时,定时器溢出的次数。最终计算时间时,“高电平持续时间 =(溢出次数*(计数器的MAX值-计数器的初值)+(捕获值-计数器初值))*计数器单位计数时间“。


如果定时器溢出次数过多,该怎么办?

当高电平持续时间过长导致计数器溢出次数过多,我们就没有必要在等待“下降沿脉冲“,直接置位”捕获完成标志位“,并且将捕获值置为其数据类型所能表示的MAX值。


库函数配置原理

总框图

复用引脚以及GPIO的配置

使能相应的总线时钟与GPIO初始化:

我将PA0引脚复用为TIM5_CH1用于进行输入捕获。

我们看KEY_UP的状态就可以得知,GPIOA.0必须进行下拉输入,只有这样按键按下才可以触发输入电平的变化。


GPIO_InitTypeDef GPIO_InitStructure;  

      

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 使能GPIOA与TIM5的时钟进行引脚的复用

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  

      

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  

GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  

GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA0为下拉输入

 


计数器的配置

计数器是定时器的灵魂,我们无论配置PWM输出还是输入捕获都要先配置定时器:

上图就是“IM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB->APB1不分频”的作用。


TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    

IM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB->APB1不分频  

TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式  

TIM_TimeBaseInitStructure.TIM_Period = ARR;  

TIM_TimeBaseInitStructure.TIM_Prescaler = PR;  

TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); // 初始化计数器  

输入捕获通道属性的配置

按照输入/输出两种不同的模式来对TIM5_CH1进行进一步配置:


TIM_ICInitTypeDef TIM_ICInitStructure;    

TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;   // 使用TIM5_CH1进行输入捕获实验

TIM_ICInitStructure.TIM_ICFilter = 0;   // 不进行采集滤波

TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 上升沿触发

TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  // 一个有效上升沿触发一次输入捕获

TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;  // 选择通道1的信号作为触发输入捕获的信号

TIM_ICInit(TIM5, &TIM_ICInitStructure); // 配置TIM5_CH1的输入捕获属性  

 


中断优先级配置

配置NVIC中断向量优先级:


NVIC_InitTypeDef NVIC_InitStructure;      

VIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;   // TIM5中断使能

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   // 抢占优先级 = 1

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  // 响应优先级 = 1

NVIC_Init(&NVIC_InitStructure); // 配置NVIC嵌入式中断向量优先级  

 


TIM5_CH1中断类型选择

进一步配置TIM5_CH1中断触发种类:


TIM_ITConfig(TIM5, TIM_IT_CC1|TIM_IT_Update, ENABLE); // 使能TIM5_CH1的中断  

 


定时器的使能

定时器TIM5使能:


TIM_Cmd(TIM5, ENABLE); // TIM5定时器使能  

 


测量有效脉冲的中断服务函数

在这里我们想要测量按键按下的时长,也就是计算“高电平的持续时间“:


u8 TIM_CAP_STA = 0;  

u16 TIM_CAP_VAL = 0;  

  

void TIM5_IRQHandler()  

{  

    if((TIM_CAP_STA&0x80) == 0) // 捕获未完成  

    {  

        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  

        {  

            if((TIM_CAP_STA&0x40) == 0) // 上升沿  

            {  

                TIM_SetCounter(TIM5, 0); // 开始计数  

                TIM_CAP_VAL = 0;  

                TIM_CAP_STA = 0;  

                TIM_CAP_STA |= 0x40; // 高电平  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 捕获下降沿  

            }  

            else // 下降沿  

            {  

                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获当前值  

                TIM_CAP_STA |= 0x80; // 完成捕获  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 捕获上升沿  

            }  

        }  

        if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  

        {  

            if(TIM_CAP_STA&0x40)  

            {  

                if((TIM_CAP_STA&0x3F) == 0x3F)  

                {  

                    TIM_CAP_VAL = 0xFFFF;  

                    TIM_CAP_STA |= 0x80;  

                }  

                else  

                {  

                    TIM_CAP_STA++;  

                }  

            }  

        }  

    }  

    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);  

}  

 


重点:中断服务函数的编写逻辑

 

u8 TIM_CAP_STA = 0;  

u16 TIM_CAP_VAL = 0;  

  

void TIM5_IRQHandler()  

{  

    if((TIM_CAP_STA&0x80) == 0) // 捕获未完成  

    {  

        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  // TIM5_CH1捕获有效的脉冲沿

        {  

            if((TIM_CAP_STA&0x40) == 0) // 前一刻为低电平——脉冲沿为上升沿  

            {  

                TIM_SetCounter(TIM5, 0); // 开始计数  

                TIM_CAP_VAL = 0;  

                TIM_CAP_STA = 0;  

                TIM_CAP_STA |= 0x40; // 更新电平状态  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 捕获下降沿  

            }  

            else // 前一刻为高电平——脉冲沿为下降沿  

            {  

                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获当前值  

                TIM_CAP_STA |= 0x80; // 完成捕获  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 捕获上升沿  

            }  

        }  

        if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  // 计数器溢出

        {  

            if(TIM_CAP_STA&0x40)  // 如果为高电平,计数器溢出则会被记录

            {  

                if((TIM_CAP_STA&0x3F) == 0x3F)  // 如果计数器溢出次数过多

                {  

                    TIM_CAP_VAL = 0xFFFF;  // 此时的捕获值设为MAX

                    TIM_CAP_STA |= 0x80;  // 已经完成高电平的捕获

                }  

                else  

                {  

                    TIM_CAP_STA++;  // 计数器溢出次数+1

                }  

            }  

        }  

    }  

    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);  // 清除中断标志

}  

经过如下程序我们已经可以判断:此时的电平是否有效?


if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  // TIM5_CH1捕获有效的脉冲沿

        {  

            if((TIM_CAP_STA&0x40) == 0) // 前一刻为低电平——脉冲沿为上升沿  

            {  

                TIM_SetCounter(TIM5, 0); // 开始计数  

                TIM_CAP_VAL = 0;  

                TIM_CAP_STA = 0;  

                TIM_CAP_STA |= 0x40; // 更新电平状态  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 捕获下降沿  

            }  

            else // 前一刻为高电平——脉冲沿为下降沿  

            {  

                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获当前值  

                TIM_CAP_STA |= 0x80; // 完成捕获  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 捕获上升沿  

            }  

        } 

然后,我们根据是否为高电平来进行溢出次数的记录:当处于高电平,即有效电平时 ,计数器溢出被视为有效予以记录。


if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  // 计数器溢出

        {  

            if(TIM_CAP_STA&0x40)  // 如果为高电平,计数器溢出则会被记录

            {  

                if((TIM_CAP_STA&0x3F) == 0x3F)  // 如果计数器溢出次数过多

                {  

                    TIM_CAP_VAL = 0xFFFF;  // 此时的捕获值设为MAX

                    TIM_CAP_STA |= 0x80;  // 已经完成高电平的捕获

                }  

                else  

                {  

                    TIM_CAP_STA++;  // 计数器溢出次数+1

                }  

            }  

        } 

难点:当高电平持续时间过长,应该如何操作?

if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  // 计数器是否溢出

{  

    if(TIM_CAP_STA&0x40)  // 是否为有效电平(高电平)

    {  

        if((TIM_CAP_STA&0x3F) == 0x3F)  // 捕获次数已经为MAX

        {  

            TIM_CAP_VAL = 0xFFFF;  // 捕获值为MAX

            TIM_CAP_STA |= 0x80;  // 直接标记:已经捕获完成

        }  

        else  

        {  

            TIM_CAP_STA++;  

        }  

    }  

 


当高电平持续时间过长导致计数器溢出次数过多,我们就没有必要在等待“下降沿脉冲“,直接置位”捕获完成标志位“,并且将捕获值置为其数据类型所能表示的MAX值。


代码示例

Main.c

#include 'key.h'  

#include 'timer.h'  

#include 'usart.h'  

#include 'led.h'  

#include 'delay.h'  

  

int main()  

{  

    extern u8 TIM_CAP_STA;  

    extern u16 TIM_CAP_VAL;  

    u32 temp = 0;  

      

    delay_init(); // 初始化systick时钟  

    KEY_InitConfig(); // 初始化KEY_WAKEUP  

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组  

    TIM_CAPTURE_InitConfig(0xFFFF, 72-1); // 周期为65.535s  

    uart_init(115200); // 初始化串口  

      

    while(1)  

    {  

        delay_ms(10);  

        if((TIM_CAP_STA&0x80) == 0x80)  // 完成有效电平的捕获

        {  

            temp = TIM_CAP_VAL + (TIM_CAP_STA&0x3F) * 0xFFFF; // 计算脉冲持续时间  

            printf('%drn', temp); // 串口打印  

            TIM_CAP_STA = 0; // 清空TIM_CAP_STA内的数据以便再次捕获

        }  

    }     

}

  


Timer.c

#include 'timer.h'  

#include 'stm32f10x.h'  

#include 'sys.h'  

#include 'led.h'  

  

void TIM_CAPTURE_InitConfig(u16 ARR, u16 PSC)  

{  

    GPIO_InitTypeDef GPIO_InitStructure;  

    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;  

    TIM_ICInitTypeDef TIM_ICInitStructure;  

    NVIC_InitTypeDef NVIC_InitStructure;  

      

    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 使能APB1总线的TIM5时钟  

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能APB2总线的GPIOA时钟  

      

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  

    GPIO_Init(GPIOA, &GPIO_InitStructure); // PA0初始化为下拉输入  

      

    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB = APB1  

    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式  

    TIM_TimeBaseInitStructure.TIM_Period = ARR;  

    TIM_TimeBaseInitStructure.TIM_Prescaler = PSC;  

    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseInitStructure); // 配置计数器的属性  

      

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;  

    TIM_ICInitStructure.TIM_ICFilter = 0;  

    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); // 配置TIM5的输入属性  

      

    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); // 配置TIM5的中断优先级  

      

    TIM_ITConfig(TIM5, TIM_IT_CC1|TIM_IT_Update, ENABLE); // 使能TIM5_CH1的中断  

      

    TIM_Cmd(TIM5, ENABLE); // 使能TIM5  

}  

  

u8 TIM_CAP_STA = 0;  

u16 TIM_CAP_VAL = 0;  

  

void TIM5_IRQHandler()  

{  

    if((TIM_CAP_STA&0x80) == 0)  

    {  

        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  

        {  

            if((TIM_CAP_STA&0x40) == 0x00) // 上升沿  

            {  

                TIM_CAP_STA = 0; // 初始化  

                TIM_CAP_VAL = 0; // 初始化  

                TIM_SetCounter(TIM5, 0); // 计数器开始计数  

                TIM_CAP_STA |= 0x40;  // 高电平  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 改变极性/改变有效电平——捕获上升沿  

            }  

            else // 下降沿  

            {  

                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获此时计数器的值  

                TIM_CAP_STA |= 0x80; // 完成捕获  

                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 改变极性/改变有效电平——捕获下降沿  

            }  

        }  

        if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET) //  计数器溢出  

        {  

            if((TIM_CAP_STA&0x40) == 0x40) // 上升沿  

            {  

                if((TIM_CAP_STA&0x3F) == 0x3F) // 当溢出次数过多,就默认'捕获完成并且此时计数器的值为MAX'  

                {  

                    TIM_CAP_VAL = 0xFFFF;  

                    TIM_CAP_STA |= 0x80; // 完成捕获  

                }  

                else  

                {  

                    TIM_CAP_VAL++; // 溢出次数递增  

                }  

            }  

        }  

    }  

    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); // 清楚中断标志位  

}  

 


Timer.h

#ifndef _TIMER_H  

#define _TIMER_H  

  

#include 'sys.h'  

  

void TIM_CAPTURE_InitConfig(u16 ARR, u16 PR);  

  

#endif  

 


Key.c

#include 'key.h'  

#include 'stm32f10x.h'  

  

void KEY_InitConfig()  

{  

    GPIO_InitTypeDef GPIO_InitStructure;  

      

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能总线时钟  

      

    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  

    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  

    GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化KEY_WAKRUP为下拉输入  

}

  


Key.h

#ifndef _KEY_H  

#define _KEY_H  

  

void KEY_InitConfig();  

  

#endif  

 

运行结果

进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • 家用电源无载自动断电装置的设计与制作

  • 用数字电路CD4069制作的万能遥控轻触开关

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 开关电源的基本组成及工作原理

  • 用NE555制作定时器

  • 带有短路保护系统的5V直流稳压电源电路图

    相关电子头条文章