IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32F103五分钟入门系列(十五)输入捕获(精雕细琢-.-) -> 正文阅读

[嵌入式]STM32F103五分钟入门系列(十五)输入捕获(精雕细琢-.-)

学习板:STM32F103ZET6

参考:

STM32F103五分钟入门系列(十二)定时器中断

STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试

STM32F103五分钟入门系列(五)按键实验(库函数+寄存器)

输入捕获

一、输入捕获简介

接触过Arduino的朋友应该知道,在Arduino中pulseIn()可以返回高电平、低电平持续时间,如以下代码:

int pin = 7;
unsigned long duration;
void setup()
{
  pinMode(pin, INPUT);
}
void loop()
{
duration = pulseIn(pin, HIGH);;
} 

本博通过输入捕获同样可以获取到输入脉冲的高低电平持续时间。那怎么来实现呢?

在STM32F1中,除了基本定时器TIM6和TIM7,其它定时器都具有输入捕获功能。通过检测对应通道TIMx_CHx 上的边沿信号,来获取电平跳变。如获取到上升沿后,记录下此时定时器的值(TIMx_CNT),再一次捕获到下降沿(TIM1~5、TIM8需要算法,不支持双边沿检测,本博(六)5中有说明)后记录下此时定时器的值,两者之间的差为定时器在一个高电平持续时间内计数的次数,这个次数除以定时器的时钟频率就得到了高电平持续时间。

检测到本次下降沿后记录定时器的值,到下次上升沿到来之时再次记录定时器的值,两者之差除以定时器时钟频率得到输入脉冲的低电平持续时间。

需要注意的是:当低电平、高电平持续时间很长,定时器多次重装载后才得到下一次电平跳变,此时就需要记录定时器重装载次数,否则会到导致计算结果出错。

基于上述思想,接下来从寄存器、库函数及举例测试来深入了解输入捕获的相关内容

二、输入捕获相关寄存器

1、自动重装载寄存器(TIMx_ARR)

在这里插入图片描述

该寄存器是16位寄存器,用来重装载计数器的初值,最大为0xffff。

2、 预分频器(TIMx_PSC)

在这里插入图片描述

该寄存器为16位寄存器,用来设置定时器时钟的。我们知道定时器2~7搭载在APB1外设上,初始状态下(系统时钟72MHZ、AHB预分频系数1、APB1预分频系数2、APB2预分频系数1),APB1时钟为36MHZ、APB2时钟为72MHZ。通过该寄存器设置预分频系数后得到定时器的时钟频率(默认状态TIM1 ~TIM8输入时钟都为72MHZ)。

该寄存器16位有效,即预分频系数为0~0xffff,即预分频系数可以为0 ~65535。如将该寄存器赋值为7199,则定时器的时钟频率为72MHZ/7200=10KHZ。

需要注意的是,当该寄存器为0时表示不分频,即分频因子为1,所以真正的分频因子是:该寄存器的值+1

3、捕获/比较模式寄存器 1、2(TIMx_CCMR1、TIMx_CCMR2)(滤波重点说明

以捕获/比较模式寄存器 1(TIMx_CCMR1)为例说明,TIMx_CCMR2类似。

在这里插入图片描述

这个寄存器上一博客STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试中涉及到了,所以输入捕获的内容也比较好理解。

位1:0

在这里插入图片描述

①位1:0=01,CC1通道被配置为输入,IC1映射在TI1上,如下图所示:

在这里插入图片描述

②位1:0=10,CC1通道被配置为输入,IC1映射在TI2上,如下图所示:

在这里插入图片描述

③位1:0=11,CC1通道被配置为输入,IC1映射在TRC上,如下图所示:

在这里插入图片描述

可见该寄存器用来设置IC1通道来源的,同理CCMR1高8位的位9:8设置IC2的通道来源、CCMR2的位1:0设置IC3的通道来源、CCMR2的位9:8设置IC4的通道来源。

位3:2

在这里插入图片描述

这两位是设置检测频率的。当CC1E=’0’(TIMx_CCER寄存器中),即关闭了OC1输入,这两位就会复位。

位3:2=00:无预分频器,捕获输入口上检测到的每一个边沿都触发一次捕获;
位3:2=01:每2个事件触发一次捕获;
位3:2=10:每4个事件触发一次捕获;
位3:2=11:每8个事件触发一次捕获。

我们知道,当在对应通道识别到上升沿或下降沿时就会发生一次更新事件。如果这两位被设置为00,则每一次更新事件都捕获一次,记录定时器此时的值,同01、10、11。

位7:4

在这里插入图片描述

理解IC1F[3:0]之前,先明白各时钟关系:

默认状态下TIM1~ 8输入时钟为72MHZ。再经过预分频寄存器PSC设置分频作为定时器时钟:72MHZ/(1 ~65536)

这个时钟是用来计数,也只用来计数,与滤波、采样没有直接关系。

接下来就是滤波器(采样)频率=fDTS和采样频率fSAMPLING

滤波(采样)频率=fDTS在CR1寄存器位9:8定义

在这里插入图片描述
可以看到:
①当CKD[1:0]=00时,滤波器(采样)频率=定时器时钟=72MHZ/(1 ~65536)

②当CKD[1:0]=01时,滤波器(采样)频率=定时器时钟×2=72MHZ/(1 ~65536)×2

③当CKD[1:0]=10时,滤波器(采样)频率=定时器时钟×4=72MHZ/(1 ~65536)×4

再回到CCMR1寄存器:

再强调一下,采样跟计数是两个线程,没有关联!

可以看到当IC1F[3:0]取不同值时,对输入信号的采样频率为滤波器(采样)频率/n。(注意是n,不是N)。而这个N也非常关键,表示采样几个周期。

为了方便理解,我们仔细的再写一遍:

1)位7:4=0000,无滤波器,以fDTS采样。
①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT,则通道1以定时器时钟×1的频率捕获输入。

②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟×2的频率捕获输入。

③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟×4的频率捕获输入。

2)位7:4=0001,采样频率fSAMPLING=fCK_INT,N=2

①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT×1,则通道1以定时器时钟fCK_INT的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fCK_INT)捕获2次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采样频率(fCK_INT)捕获2次,若都为低电平,才表示下降沿捕获。

②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟fCK_INT的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fCK_INT)捕获2次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采样频率(fCK_INT)捕获2次,若都为低电平,才表示下降沿捕获。

③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟fCK_INT的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fCK_INT)捕获2次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采样频率(fCK_INT)捕获2次,若都为低电平,才表示下降沿捕获。




3)位7:4=0111,采样频率fSAMPLING=fDTS/4,N=8

①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT×1,则通道1以定时器时钟/4的频率捕获输入。但是当捕获到上升沿后,还要以这个采样频率(fDTS/4=tCK_INT/4)捕获8次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/4=fCK_INT/4)捕获8次,若都为低电平,才表示下降沿捕获。

②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟/2的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/4=tCK_INT/2)捕获8次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/4=tCK_INT/2)捕获8次,若都为低电平,才表示下降沿捕获。

③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟×1的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/4=fCK_INT)捕获8次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/4=fCK_INT)捕获8次,若都为低电平,才表示下降沿捕获。




4)位7:4=1110,采样频率fSAMPLING=fDTS/32,N=6

①若CR1寄存器位9:8—>CKD[1:0]=00,即fDTS = fCK_INT×1,则通道1以定时器时钟/32的频率捕获输入。但是当捕获到上升沿后,还要以这个采样频率(fDTS/32=tCK_INT/32)捕获6次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/32=fCK_INT/32)捕获6次,若都为低电平,才表示下降沿捕获。

②若CR1寄存器位9:8—>CKD[1:0]=01,即fDTS = fCK_INT×2,则通道1以定时器时钟/16的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/32=tCK_INT/16)捕获6次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/32=tCK_INT/16)捕获6次,若都为低电平,才表示下降沿捕获。

③若CR1寄存器位9:8—>CKD[1:0]=10,即fDTS = fCK_INT×4,则通道1以定时器时钟/8的频率捕获输入。但是当捕获到上升沿后,还要以这个采用频率(fDTS/32=fCK_INT/8)捕获6次,若都为高电平,才表示上升沿捕获。当捕获到下降沿后,还要以这个采用频率(fDTS/32=fCK_INT/8)捕获6次,若都为低电平,才表示下降沿捕获。



滤波的目的:在接收到外部脉冲输入后,可能会因为噪声使得某一时刻有一个冲激,如果不多次检测,这个冲激就会被检测为上升沿,造成干扰。

剩下的位15:8位设置通道2,CCMR2寄存器位7:0设置通道3、位15:8设置通道4,与通道1设置一样,不在赘述。

4、捕获/比较使能寄存器(TIMx_CCER)

在这里插入图片描述

位0

在这里插入图片描述

使能通道1,该位置1后(文档错误)使能通道1捕获。

位1

在这里插入图片描述

该位很重要,设置捕获事件发生在上升沿还是下降沿

当位1=0,捕获发生在上升沿
当位1=1,捕获发生在下降沿

剩下的位5:4、位9:8、位13:12分别设置通道2、通道3、通道4,与通道1设置一样,不在赘述。

5、DMA/中断使能寄存器(TIMx_DIER)

在这里插入图片描述

该寄存器使能各类请求

在这里插入图片描述

上图位4:0从低位到高位分别:使能定时器更新中断、使能CH1中断、使能CH2中断、使能CH3中断、使能CH4中断。

在这里插入图片描述

位6使能定时器触发中断(更新中断、触发中断区分),剩下的就是对DMA请求的使能,之后博客会专门总结DMA,这里先不总结了。

6、控制寄存器 1(TIMx_CR1)

该寄存器上一博客STM32F103五分钟入门系列(十五)输出比较(PWM输出)+各类测试,主要是对定时器的配置,这里也不在赘述。

7、 捕获/比较寄存器 1~4(TIMx_CCR1 ~4)

这个寄存器上一博客也总结过,不过只总结了输出比较的部分,现总结一下输入捕获。

在这里插入图片描述

若将通道设置为输入模式,则该寄存器存储上一次事件(上升沿或下降沿)时计数器的值。

CCR2、CCR3、CCR4分别对应CH2、CH3、CH4,不再赘述。

三、输入捕获相关库函数

1、输入捕获设置函数TIM_ICInit()

在这里插入图片描述

参数:

第一个参数:TIM_TypeDef* TIMx,选择哪个定时器

第二个参数:TIM_ICInitTypeDef* TIM_ICInitStruct

在这里插入图片描述

①TIM_Channel

选择通道,四个通道:
在这里插入图片描述
②TIM_ICPolarity

选择是上升沿捕获、下降沿捕获还是上升沿、下降沿都捕获(TIM1~5、TIM8不支持双边沿检测)

③TIM_ICSelection
选择通道来源,第一个参数已经选定了通道,TIM_ICSelection选择通道的来源,如下图通道1的三种来源:
在这里插入图片描述在这里插入图片描述
若选择参数:TIM_ICSelection_DirectTI,则由TI1作为IC1来源。
若选择参数:TIM_ICSelection_IndirectTI,则由TI2作为IC1来源。
若选择参数:TIM_ICSelection_TRC,则由TRC作为IC1来源。

当然若第一个参数选择通道2,则:
若选择参数:TIM_ICSelection_DirectTI,则由TI2作为IC1来源。
若选择参数:TIM_ICSelection_IndirectTI,则由TI1作为IC1来源。
若选择参数:TIM_ICSelection_TRC,则由TRC作为IC1来源。

④TIM_ICPrescaler

设置捕获次数,是对CCMR寄存器位3:2的操作。
在这里插入图片描述在这里插入图片描述
参数对于关系一目了然,表示每X个事件触发一次捕获。

⑤TIM_ICFilter

选择滤波器,是对CCMR寄存器位7:4的设置

在这里插入图片描述

这个参数自己写就行,0~0xf之间的数。需要注意的是这个值不能随便写,比如捕获上升沿时,高电平持续时间很短,大概2个计数器时钟周期,此时就不能N>1了,毕竟还没采样完,高电平持续时间就结束了。

2、设置捕获通道极性函数TIM_OC1PolarityConfig()

在这里插入图片描述
参数:
第一个参数:TIM_TypeDef* TIMx,选择哪个定时器

第二个参数 uint16_t TIM_OCPolarity

设置极性,即设置捕获发生在上升沿还是下降沿,是对CCER寄存器位1的操作。
在这里插入图片描述
在这里插入图片描述
位1为0时捕获发生在上升沿
位1位1时捕获发生在下降沿

3、使能捕获和更新中断函数TIM_ITConfig()

在这里插入图片描述
参数:

第一个参数: TIM_TypeDef* TIMx,选择哪个定时器

第二个参数: uint16_t TIM_IT,选择通道x中断、更新中断,是对DIER寄存器的位0和为4:1、位6(触发中断这里不用)操作。

在这里插入图片描述

在这里插入图片描述

第三个参数:FunctionalState NewState,使能ENABLE

4、获取中断和捕获状态标志函数TIM_GetITStatus()、TIM_GetFlagStatus()

这个函数在STM32F103五分钟入门系列(十二)定时器中断
中总结过,而且在那篇博客中还强调了相比于TIM_GetITStatus(),TIM_GetFlagStatus()函数多了是否重复捕获。不过TIM_GetITStatus()更加严谨。

函数体这里就不再总结了。主要总结一下捕获相关的用法:

判断是否为更新中断:

if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET){}//判断是否为更新中断

判断是否发生捕获事件:

if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET){}//判断是否发生捕获事件

5、清除中断和捕获标志位函数TIM_ClearITPendingBit()、TIM_ClearFlag()

这个函数在STM32F103五分钟入门系列(十二)定时器中断
中也总结过,且强调过,重复捕获标记只有TIM_ClearFlag()函数拥有这个功能。其它东西两个函数都一样。

主要看一下用法:

清除中断标志位:

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

清除捕获标志位:

IM_ClearITPendingBit(TIM5, TIM_IT_CC1);//清除捕获标志位

四、输入捕获编程顺序

1、使能定时器时钟RCC_APB1PeriphClockCmd()、RCC_APB2PeriphClockCmd()

定时器2~7挂载在APB1上,定时器1和8挂载在APB2上。如使能定时器5:

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

2、使能GPIO时钟RCC_APB2PeriphClockCmd()

如使能GPIOA:

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

3、使能复用时钟

4、重映射设置(如需要)

5、初始化定时器TIM_TimeBaseInit()

用TIM_TimeBaseInit()设置重装载、预分频系数、向上、相信计数模式

6、输入捕获参数设置 TIM_ICInit()

使用 TIM_ICInit()设置输入捕获通道、输入捕获极性、通道来源、分频系数、滤波器

7、使能捕获中断、更新中断TIM_ITConfig()

通过TIM_ITConfig()来使能定时器更新中断、通道捕获中断

8、设置中断分组NVIC_Init()

通过NVIC_Init()函数设置中断优先级、初始化中断。

9、使能定时器TIM_Cmd()

10、编写中断服务函数TIMx_IRQHandler()

五、例1+测试

1、题

使用定时器5通道2(PA1复用)检测PA1引脚输入PWM在1s内上升沿次数。其中输入的PWM为定时器4通道2输出500HZ、占空比为70%的PWM信号。(PB7复用)

硬件连接:将PA1与PB7引脚用飞线连接起来。

2、分析

定时器4通道2输出500HZ、占空比为50%的PWM信号参考上一博客,这里不再分析。

检测1s内上升沿次数,则可把定时器设置为1s。定时器5通道1为PA1的复用功能引脚。定时器5输入时钟为72MHZ,若将预分频系数设置为7199,则定时器5时钟为10kHZ,计数一次的时间为0.1ms,则可将重装载值设置为1s/0.1ms-1=9999。

设置定时器5通道1为捕获通道,且每个上升沿都触发。输入信号500HZ,则一个周期2ms,占空比为70%的话,每个高电平持续时间为1.4ms,为定时器时钟的14倍,即在一个高电平持续时间定时器计数14、一个低电平持续时间定时器计数6,次。所以可以用定时器时钟的大小作为采样频率,且采样2次作为滤波。

定时器每1s进入一次中断服务函数,则可在中断服务函数中输出1s内上升沿次数,采用串口输出,利用串口调试助手检测串口输出。

3、输出比较代码

输出比较代码上一篇博客都总结过,现直接附代码:

pwm.h代码:

//pwm.h
#ifndef _PWM_
#define _PWM_
void PWM_Init(void);
#endif

pwm.c代码:

//pwm.c
//pwm.c
#include "pwm.h"
#include "stm32f10x.h"
void PWM_Init(void)
{
	TIM_TimeBaseInitTypeDef  TIM_TimeBaseInitStructure;
	GPIO_InitTypeDef  GPIO_InitStructure;
	TIM_OCInitTypeDef  TIM_OCInitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	
	TIM_TimeBaseInitStructure.TIM_Period=19;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseInitStructure);

	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM2;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;
	TIM_OC2Init(TIM4,&TIM_OCInitStructure);
	
	TIM_OC2PreloadConfig(TIM4, TIM_OCPreload_Enable);
	
	TIM_Cmd(TIM4, ENABLE);
}

4、输入捕获代码

(1)cap.h代码编写

cap.h固定格式:

//cap.h
#ifndef _CAP_
#define _CAP_
void CAP_Iint(void);
#endif

(2)cap.c代码编写

经查资料,发现定时器5通道2为PA1复用引脚,所以需设置PA1时钟和复用。

在这里插入图片描述

使能定时器5时钟、PA1时钟和复用时钟:

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);

设置PA1,为浮空输入:

	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

初始化定时器5:

	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period=9999;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);

输入捕获参数设置:

	TIM_ICInitTypeDef  TIM_ICInitStructure;
	TIM_ICInitStructure.TIM_Channel=TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICFilter=0x0001; //采样频率=定时器频率,N=2
	TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_BothEdge;
	TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM5,&TIM_ICInitStructure);

使能捕获输入中断、更新中断:

	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC2, ENABLE);

设置中断分组:

	NVIC_InitTypeDef  NVIC_InitStructure;
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//主函数
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);

使能定时器:

	TIM_Cmd(TIM5,ENABLE);

(3)中断服务函数

void TIM5_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM5, TIM_IT_CC2)!=RESET)//捕获到上升沿
	{
		Rise_count++;			
	}
		TIM5->SR&=~TIM_IT_CC2;//注意清零,当捕获事件发生时该位由硬件置’1’,它由软件清’0’或通过读TIMx_CCR1清’0’
		//这里没有读CCR1寄存器,所以需要手动清零

	if(TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)//发生了更新中断
	{
		printf("Rise_count:%d \r\n",Rise_count);
		Rise_count=0;
	}
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}


}

解释一下:
定时器5进行输入捕获,因为使能了通道2的中断,所以每次捕获后后发生中断,进入中断服务函数,并且对Rise_count++,需要注意的是:由于没有对CCR1寄存器进行读操作,所以捕获中断标志不能自动清零,需要手动去清零,所以才有以下代码:

	TIM5->SR&=~TIM_IT_CC2;

同时由于使能了更新中断,所以每计数1s需要自动重装载初值,发生更新事件,进入中断服务函数,所以当发生更新事件后,说明1s计数结束,输出上升沿次数Rise_count并重新归零。

无论是捕获事件中断还是更新事件中断,进入中断服务函数后都需要清除中断标志。

(4)完整代码

//cap.c
#include "cap.h"
#include "stm32f10x.h"
#include "usart.h"
u16  Rise_count=0;
void CAP_Iint()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	NVIC_InitTypeDef  NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPD;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);

	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period=9999;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
	
	TIM_ICInitStructure.TIM_Channel=TIM_Channel_2;
	TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICFilter=0x0001;//2次检测
	TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM5,&TIM_ICInitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC2, ENABLE);

	TIM_Cmd(TIM5,ENABLE);
}

void TIM5_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM5, TIM_IT_CC2)!=RESET)//捕获到下降沿
	{
		Rise_count++;			
	}
		TIM5->SR&=~TIM_IT_CC2;//注意清零,当捕获事件发生时该位由硬件置’1’,它由软件清’0’或通过读TIMx_CCR1清’0’
		//这里没有读CCR1寄存器,所以需要手动清零

	if(TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)//发生了更新中断
	{
		printf("Rise_count:%d \r\n",Rise_count);
		Rise_count=0;
	}
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC2|TIM_IT_Update); //清除中断标志位
}

5、主函数代码

主函数中直接初始化各类函数即可

//main.c
#include "stm32f10x.h"
#include "pwm.h"
#include "cap.h"
#include "usart.h"
 int main(void)
 {	
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	 PWM_Init();
	 TIM_SetCompare2(TIM4, 6);
	 CAP_Iint();
	 uart_init(115200);	 //串口初始化为115200
	 while(1)
	 {
		 
	 }
 }

6、测试

注意板子PA1与PB7的飞线连接!

在这里插入图片描述

可以看到,对应500HZ的脉冲,其1s内上升沿为500次。

六、例2+测试

1、题

通过定时器5检测KEY_U按键按下的时间,并通过串口打印出来。

2、分析

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

通过该查找资料可知,KEY_UP连接在PA0引脚上,而定时器5通道1刚好是PA0引脚复用。按下、松开KEY_UP后PA0引脚都有一个电平跳变,定时器5通道1可以捕获到这个电平跳变。并且按下后PA0引脚变为高电平,所以配置PA0为下拉输入,在未按下时,下拉到低电平;且按下后为PA0检测到高电平,所以捕获上升沿。

按下后,下次松开的时间无法控制,如果这个时间比较长的话,定时器5会经过多次重装载初值,所以要记录好更新中断次数。

定时器5挂载在APB1外设上,由下图可知,定时器5的时钟频率为72MHZ。

在这里插入图片描述

所以不妨将定时器5的预分频系数设置为7199,则定时器5的时钟频率为10KHZ,即0.1ms计数一次。若将重装载值设置为9999,则每1s发生一次更新事件。

3、KEY_UP代码

之前博客总结过,直接附代码:

key.h:

//key.h
#ifndef KEY_H
#define KEY_H
void KEY_Init(void);
#endif

key.c:

//key.c
#include "stm32f10x.h"
#include "sys.h"
#include "key.h"
void KEY_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct_A;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA , ENABLE);//使能GPIOA和GPIOE(PA0 PE2、3、4)
	
	GPIO_InitStruct_A.GPIO_Mode=GPIO_Mode_IPD;
	GPIO_InitStruct_A.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStruct_A.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &GPIO_InitStruct_A);//PA0 key_up  下拉输入
}

4、CAP代码编写

cap.h固定格式,代码:

#ifndef _CAP_
#define _CAP_
void CAP_Iint(void);
#endif

cap.c中需使能PA0复用功能,配置定时器5通道1为输入捕获通道,可以滤波,为了防抖,可以去滤波。采用频率就采用定时器时钟频率,N=8,所以TIM_ICFilter=0x0011。

在这里插入图片描述

#include "cap.h"
#include "stm32f10x.h"
#include "usart.h"
u16  Rise_count=0;
void CAP_Iint()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	NVIC_InitTypeDef  NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,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);

	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period=9999;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
	
	TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICFilter=0x0011;//8次检测
	TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM5,&TIM_ICInitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1, ENABLE);

	TIM_Cmd(TIM5,ENABLE);
}

为了记录更新事件次数(重装载初值次数),需要使能定时器5的更新中断,当然因为要捕获,所以也得使能定时器5通道1的捕获中断。这样,捕获到上升沿、更新事件后都会发生中断进入中断服务函数。

5、中断服务函数(核心思想

首先要明白中断服务函数里面应该干啥。定时器计数到重装载值后归零产生中断,进入中断服务函数;定时器捕获到上升沿后会产生对应通道的中断,并进入中断服务函数。两种途径进入中断服务函数,所以都得考虑到。

再者,按下KEY_UP时间可能会很长,如10s…则定时器会多次重装载初值,这样计算时间的话,就不能单纯的下降沿计数器的-上升沿计数器值,这个多次重装载也得考虑到。

然后,还得判断下降沿,因为TIM1、TIM2、TIM3、TIM4、TIM5、TIM8不支持双边沿检测,可以看一下TIM_ICInit()函数:
在这里插入图片描述

在这里插入图片描述

所以要考虑下降沿怎么检测

最后还要考虑什么时候串口输出,要把握输出时机。

现附我自己写的一个代码(不是最优解 😐),然后再分析。

u16  updata_count=0;//记录更新中断次数
u16  TIM5CH1_RISE_VAL=0;//记录捕获上升沿时计数器的值
u16  TIM5CH1_FALLVAL=0;//记录下降沿时计数器的值
u16	 flag=0;//标记刚刚是否是高电平、为了判断下降沿

void TIM5_IRQHandler(void)
{
	if((TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)&&( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)))
	{
		updata_count++;//在高电平时间段发生了更新中断,需要重装载,记录重装载次数
	}
	if(TIM_GetITStatus(TIM5, TIM_IT_CC1)!=RESET)//捕获到上升沿
	{
		TIM5CH1_RISE_VAL=TIM_GetCapture1(TIM5);//将上升沿时定时器的值保存下来,该函数对CCR1读操作了,所以SR寄存器捕获标志位不需要手动清零。
		flag=1;//标记为发生过上升沿捕获了
	}
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}



//主函数中
 while(1)
	 {
		 if((flag==1)&&(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET))
		 {//现在是低电平,但之前发生过上升沿捕获
			
			 time=(int)(0.1*((abs(TIM5CH1_FALLVAL-TIM5CH1_RISE_VAL))+10000*updata_count));
			 //注意高电平期间计数器可能会多次重装载初值
			 printf("Time=%d ms\r\n",time);
			 flag=0;
			 TIM5CH1_FALLVAL=0;
			 TIM5CH1_RISE_VAL=0;
			 updata_count=0;
			 //注意完成一次输出后全部清零
		 }		
	 }

上面程序中先定义了4个变量:

u16  updata_count=0;//记录更新中断次数
u16  TIM5CH1_RISE_VAL=0;//记录捕获上升沿时计数器的值
u16  TIM5CH1_FALLVAL=0;//记录下降沿时计数器的值
u16	 flag=0;//标记刚刚是否是高电平、为了判断下降沿

第一个变量用来计算在高电平期间计数器重装载了几次
第二个变量用来储存上升沿时计数器的值
第三个变量用来储存下降沿时计数器的值
第四个变量用来判断下降沿

不管是计数器更新中断还是通道上升沿捕获中断,进入中断服务函数后,首先:

if((TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)&&( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)))
	{
		updata_count++;//在高电平时间段发生了更新中断,需要重装载,记录重装载次数
	}

即当有更新中断产生(重装载计数器),并且此时PA0输入是高电平,说明在高电平持续时间计数器重装载了初值,此时updata_count++。

接下来处理上升沿,当捕获到上升沿后,计数器的当前值自动保存在CCR1中,对CCR1读操作得到当前计数器的值,同时可以让SR寄存器的捕获状态标志清零,以便下次捕获中断。通过对CRR1寄存器读操作,SR寄存器捕获状态标志位自动清零,就不用专门去持续中清零了

因为没有下降沿捕获,所以无法直接判断产生下降沿的准确时间。所以定义变量flag,初始flag=0,当有捕获中断产生时,令flag=1,表示产生了捕获中断,此时是高电平。

if(TIM_GetITStatus(TIM5, TIM_IT_CC1)!=RESET)//捕获到上升沿
	{
		TIM5CH1_RISE_VAL=TIM_GetCapture1(TIM5);//将上升沿时定时器的值保存下来,该函数对CCR1读操作了,所以SR寄存器捕获标志位不需要手动清零。
		flag=1;//标记为发生过上升沿捕获了
	}

那怎么就是下降沿了呢?要实时检测PA0的电平,所以把判断函数放在主函数的while()循环中。当检测到flag=1(即发生过上升沿捕获)且PA0为低电平(原来高电平,下降沿后变为低电平),就说明一个完整的高电平持续时间结束。

再次获取计数器的值,根据前后计数器的值、重装载次数可以算出上升沿->下降沿总共计数多少个。而定时器时钟前面设置的10KHZ,即0.1ms计数一次,则可以求出上升沿->下降沿持续时间。

 while(1)
	 {
		 if((flag==1)&&(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET))
		 {//现在是低电平,但之前发生过上升沿捕获
			
			 time=(int)(0.1*((abs(TIM5CH1_FALLVAL-TIM5CH1_RISE_VAL))+10000*updata_count));
			 //注意高电平期间计数器可能会多次重装载初值
			 printf("Time=%d ms\r\n",time);
			 flag=0;
			 TIM5CH1_FALLVAL=0;
			 TIM5CH1_RISE_VAL=0;
			 updata_count=0;
			 //注意完成一次输出后全部清零
		 }		

6、完整代码

cap.c代码+中的服务函数:

#include "cap.h"
#include "stm32f10x.h"
#include "usart.h"

u16  updata_count=0;//记录更新中断次数
u16  TIM5CH1_RISE_VAL=0;//记录捕获上升沿时计数器的值
u16  TIM5CH1_FALLVAL=0;//记录下降沿时计数器的值
u16	 flag=0;//标记刚刚是否是高电平、为了判断下降沿
void CAP_Iint()
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_ICInitTypeDef  TIM_ICInitStructure;
	NVIC_InitTypeDef  NVIC_InitStructure;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,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);

	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitStructure.TIM_Period=9999;
	TIM_TimeBaseInitStructure.TIM_Prescaler=7199;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
	
	TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
	TIM_ICInitStructure.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInitStructure.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM_ICInitStructure.TIM_ICFilter=0x0011;//8次检测
	TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM5,&TIM_ICInitStructure);
	
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitStructure);
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1, ENABLE);

	TIM_Cmd(TIM5,ENABLE);
}

void TIM5_IRQHandler(void)
{
	if((TIM_GetITStatus(TIM5, TIM_IT_Update)!=RESET)&&( GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)))
	{
		updata_count++;//在高电平时间段发生了更新中断,需要重装载,记录重装载次数
	}
	if(TIM_GetITStatus(TIM5, TIM_IT_CC1)!=RESET)//捕获到上升沿
	{
		TIM5CH1_RISE_VAL=TIM_GetCapture1(TIM5);//将上升沿时定时器的值保存下来,该函数对CCR1读操作了,所以SR寄存器捕获标志位不需要手动清零。
		flag=1;//标记为发生过上升沿捕获了
	}
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}

main.c代码:

#include "stm32f10x.h"
#include "cap.h"
#include "usart.h"
#include "key.h"
#include  "math.h"
extern u16  flag;
extern u16 TIM5CH1_FALLVAL;
extern u16 TIM5CH1_RISE_VAL;
extern u16  updata_count;
u16 time=0;
 int main(void)
 {	
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	 CAP_Iint();
	 KEY_Init();
	 uart_init(115200);	 //串口初始化为115200
	 while(1)
	 {
		 if((flag==1)&&(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==Bit_RESET))
		 {//现在是低电平,但之前发生过上升沿捕获
			
			 time=(int)(0.1*((abs(TIM5CH1_FALLVAL-TIM5CH1_RISE_VAL))+10000*updata_count));
			 //注意高电平期间计数器可能会多次重装载初值
			 printf("Time=%d ms\r\n",time);
			 flag=0;
			 TIM5CH1_FALLVAL=0;
			 TIM5CH1_RISE_VAL=0;
			 updata_count=0;
			 //注意完成一次输出后全部清零
		 }		
	 }
 }

7、测试

按下KEY_UP键后一段时间松开,察看在串口检测器中的显示:
在这里插入图片描述

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 12:15:04  更:2021-08-07 12:15:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 20:29:29-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码