STM32F4_定时器输入捕获详解

目录

1. 输入捕获简介

2. 输入捕获框图

3. 输入捕获模式

4. 相关寄存器

4.1 TIMx_ARR、TIMx_PSC

4.2 捕获/比较寄存器1:TIMx_CCMR1

4.3 捕获/比较使能寄存器 TIMx_CCER

4.4 中断使能寄存器 TIMx_DIER

5. 库函数配置输入捕获高电平脉冲宽度

6. 实验程序

6.1 main.c

6.2 IntputCapture.c

6.3 IntputCapture.h


1. 输入捕获简介

        STM32定时器可以分为相关时钟、时基单元、输入捕获、输出比较。在上一节我们已经学习了STM32的输出PWM比较功能,本节我们将学习STM32的输入捕获功能

        输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32F4系列拥有14个定时器,除了TIM6和TIM7,其他定时器都有输入捕获功能。

        输入捕获功能就是通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如说上升沿/下降沿)的时候,将当前定时器的值TIMx_CNT存放到对应通道的捕获/比较寄存器TIMx_CCRx里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA等。

2. 输入捕获框图

        输入捕获的过程:输入阶段对TIx输入进行采样,通过滤波器生成一个滤波信号TIxF,然后通过一个带有极性选择的边沿检测器(极性选择就是确定高电平有效还是低电平有效)生成一个信号(TIxFPx),信号一方面可以用作模式控制器的触发输入,另一方面可以捕获命令,通过分频器分频以后传给捕获/比较寄存器。

3. 输入捕获模式

        在输入捕获模式下,当相应的ICx信号( ICx称作输入捕获、OCx称作输出比较 )检测到跳变沿(上升沿、下降沿)后,将会使用TIMx_CCRx捕获/比较寄存器来存储这一时候计数器的值。发生捕获事件时,会将状态寄存器TIMx_SR的CCXIF位置1,并且这一时刻在使能的状态下可以发送中断或者DMA请求。如果发生捕获事件时CCxIF标志已经处于高位1,那么会将重复捕获标志状态寄存器的CCxOF位置1。可以通过写程序的方式给CCxIF位写入0来将CCxIF位清0,或者读取存储在TIMx_CCRx寄存器中的已捕获数据。

发生输入捕获时

        发生有效跳变沿时,TIMx_CCR1寄存器会获取计数器的值,保存下来。

        将CC1IF标志位置1。如果至少发生了两次连续捕获,但CC1IF标志未被清0,则CC1OF捕获溢出标志位会被置1。

        根据CC1IE位生成中断。

        根据CC1DE位生成DMA请求。

输入捕获测量脉宽的原理

        假定定时器工作在向上计数模式,图中的t1~t2时间,就是我们要测量的高电平时间;首先设置定时器通道x为上升沿捕获,这样在t1时刻会进行第一次捕获,记为CCRx1,记录当前计数器CNT的值;立即清0 CNT,设置定时器通道x为下降沿捕获,在t2时刻,会进行第二次捕获,记录这时的计数器值,记为CCRx2,这样,根据时钟值得到计数器的计数频率,就能算出t1~t2的时间,从而得到高电平脉宽。

        如图,在t1到t2之间,可能会产生N次定时器溢出,为了防止高电平太长,导致数据不准确,需要计算CNT计数的次数:N*ARR+CCRx2,用计数次数乘以CNT的计数周期,就可以得到t2~t1的时间长度。

4. 相关寄存器

4.1 TIMx_ARR、TIMx_PSC

这两个寄存器在过去学习定时器功能时,常常用到;分别是自动重装载值寄存器和时钟预分频寄存器。

4.2 捕获/比较寄存器1:TIMx_CCMR1

捕获/比较寄存器1:TIMx_CCMR1(capture/compare mode register 1)

在PWM输出中,已经介绍了该寄存器的输出位配置;这次我们着重看输入部分:也就是第二行ICxx;

第二行的输入部分的高8位是2通道IC2x;低8位是1通道IC1x;3 4通道显然是TIMx_CCMR2捕获/比较寄存器2控制的。

位1:0  捕获/比较1选择  00:CC1通道配置为输出    01:CC1通道配置为输入,IC1映射到TI1上 这里我们配置01,因为框图上通道1对应IC1

                                     10:CC1通道配置为输入,IC1映射到TI2上     11:CC1通道配置为输入,IC1映射到TRC上。

IC1PSC输入捕获1预分频器配置为 00:1次边沿就触发一次捕获。

IC1F是用来设置输入采样频率和数字滤波器长度的。

4.3 捕获/比较使能寄存器 TIMx_CCER

捕获/比较使能寄存器 TIMx_CCER(capture/compare enable register)

CC1P位配置为输入时,由输入捕获的框图可得:1通道CH1对应TI1,又对应TI1PF1/TI1PF2;配置极性就是高电平有效,还是低电平有效。

CC1E位配置为输入时,捕获/比较1输出使能。

4.4 中断使能寄存器 TIMx_DIER

中断使能寄存器 TIMx_DIER(DMA/Interrupt enable register)

位1 CC1IE: 捕获/比较1中断使能

                0:禁止CC1中断

                1:使能CC1中断

5. 库函数配置输入捕获高电平脉冲宽度

本程序获取TIM5的1通道上高电平脉冲宽度;

1. 开启TIM5时钟,配置PA0为复用功能(AF2),并开启下拉电阻

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

GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5); //GPIOA0复用位定时器5

…………………………………………………………………………

GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN;//下拉

GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;//模式设置为复用模式

GPIO_Init(GPIOA,&GPIO_InitStructure);//GPIOA初始化

2. 初始化TIM5,设置TIM5的ARR和PSC

TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频 
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; 
TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);

3. 设置TIM5的输入捕获参数,开启输入捕获

定时器有输出比较函数,对应的就有输入捕获函数;

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct)   //  输入捕获初始化

typedef struct 
{  
uint16_t TIM_Channel; //通道  有1 2 3 4 通道供选择
uint16_t TIM_ICPolarity; //捕获极性  有上升沿捕获和下降沿捕获选择
uint16_t TIM_ICSelection;//映射  //每一个通道都对应一个映射关系,1通道对应映射TI1
uint16_t TIM_ICPrescaler;//分频系数  有0 2 4 8供选择
uint16_t TIM_ICFilter; //滤波器长度 不使用滤波器,默认选择滤波器长度为0
} TIM_ICInitTypeDef; 


对于第二个结构体成员变量:捕获极性  有单独的
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);//通道1捕获极性函数

有TIM_OCxPolarityConfig()。x对应2 3 4 通道选择。

ag. 

TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端 IC1映射到TI1上  
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获  
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上  
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频  
TIM5_ICInitStructure.TIM_ICFilter = 0x00;//IC1F=0000 配置输入滤波器 不滤波  
TIM_ICInit(TIM5, &TIM5_ICInitStructure); 

4. 使能捕获和更新中断(设置TIM5的DIER寄存器)

TIM_ITConfig( TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//允许更新中断和捕获中断 

此时开启中断,我们需要在中断中设置:因为此程序是捕获高电平信号的脉宽,所以第一次捕获的是上升沿,第二次是下降沿,这两个时间间隔内就是一个高电平信号。

同时脉宽比较长,就会导致定时器溢出,需要在中断中对溢出进行处理。

5. 设置中断优先级,编写中断服务函数

NVIC_Init();

中断服务函数:需要完成数据处理和捕获设置等关键操作。    TIM5_IRQHandler

        在中断开始的时候需要进行中断类型的判断,中断结束时要清除中断标志位。

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

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

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

TIM_SetCounter(TIM5,0); //设置计数器的值  将TIM5的计数值设置为0

6. 使能定时器

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

最后因为用到了串口输出结果,所以还需要配置一下串口

6. 实验程序

本实验程序通过输入捕获TIM5_CH1(PA0)上面的高电平脉冲宽度,并从串口打印捕获结果。

注意:

        该程序中的TIM5CH1_CAPTURE_STA是一个状态位,前6位表示捕获高电平后定时器溢出的次数;第七位是捕获到高电平的标志;第八位是捕获完成的标志。TIM5CH1_CAPTURE_VAL用来记录捕获到下降沿时CNT计数器的值,因为该程序是捕获高电平脉冲宽度,每当捕获到下降沿的时候,就意味着捕获到了一个高电平脉冲信号。因为一个高电平是由一个上升沿和一个下降沿组成的。

6.1 main.c

#include "stm32f4xx.h"
#include "delay.h"
#include "LED.h"
#include "BEEP.h"
#include "Key.h"
#include "usart.h"
#include "exti.h"
#include "iwdg.h"
#include "wwdg.h"
#include "Timer.h"
#include "pwm.h"
#include "IntputCapture.h"

extern u8 TIM5_CH1_CAPTURE_STA;//外部声明
//该程序中的TIM5CH1_CAPTURE_STA是一个状态位,前6位表示捕获高电平后定时器溢出的次数;第七位是捕获到高电平的标志;第八位是捕获完成的标志。
extern u32 TIM5_CH1_CAPTURE_VAL;
//TIM5CH1_CAPTURE_VAL用来记录捕获到下降沿时CNT计数器的值


int main(void)
{
	long long Temp=0; //Temp表示溢出的次数,对于溢出我们这样理解的,因为计数器的重装载值是固定设置的,如果频率过快,可能在一次高电平期间,会溢出多次,因此我们需要记录溢出的次数
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//中断优先级分组
	delay_init(168);
	uart_init(115200);
	TIM14_Init(500-1,84-1);
	TIM5_CH1_InterCapture_Init(0xFFFFFFFF,84-1); //自动重装载值为最大  84/84M=1M,频率为1Mhz
	while(1)
	{
		delay_ms(10);
		TIM_SetCompare1(TIM14,TIM_GetCapture1(TIM14)+1);//设置占空比,占空比是TIM14输出比较来的
		if(TIM_GetCapture1(TIM14)==300)//占空比达到顶峰
			TIM_SetCompare1(TIM14,0);//清空TIM14定时器
		if(TIM5_CH1_CAPTURE_STA&0x80)//成功捕获一次高电平
		{
			Temp=TIM5_CH1_CAPTURE_STA&0x3F;//低6位的值给到Temp  循环次数
			Temp*=0xFFFFFFFF;  //溢出时间总和  循环次数乘以最大重装载值(最大重装载值就是一个周期的计数器值)就是循环这么多次的时间
			Temp=Temp+TIM5_CH1_CAPTURE_VAL; //总的高电平时间 TIM5_CH1_CAPTURE_VAL表示最后一次检测到低电平时的计数器值
			printf("HIGH:%lld us\r\n",Temp);//打印总的高电平时间
			TIM5_CH1_CAPTURE_STA=0; //开启下一次捕获
		}
	}
	
}


6.2 IntputCapture.c

#include "stm32f4xx.h"                  
#include "IntputCapture.h"

//AutomaticReload:自动重装载值    PrioritySendCount:时钟预分频数

void TIM14_Init(u32 AutomaticReload,u32 PrioritySendCount)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14,ENABLE);//使能TIM14_CH1 1通道时钟
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);// 使能GPIOF引脚
	GPIO_PinAFConfig(GPIOF,GPIO_PinSource9,GPIO_AF_TIM14);//引脚复用PF9引脚复用为TIM14的通道1
	//GPIO的初始化函数,设置初始化的模式为复用
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF; //设置GPIO模式为复用  对应上述引脚复用PF9引脚复用为TIM14的通道1
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9; 
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_Init(GPIOF,&GPIO_InitStructure);
	
	//初始化TIM14定时器,设置预分频值和自动重装载值
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数,也就是递增计数
	TIM_TimeBaseInitStructure.TIM_Period=AutomaticReload;//自动重装载值
	TIM_TimeBaseInitStructure.TIM_Prescaler=PrioritySendCount;//时钟预分频值
	TIM_TimeBaseInit(TIM14,&TIM_TimeBaseInitStructure);
	
	
	//设置TIM14的PWM模式
	TIM_OCInitTypeDef TIM_OCInitStructure;
	TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_Low;//输出极性低,也就意味着占空比中低电平有效
	TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;//PWM调质模式1
	TIM_OCInitStructure.TIM_OutputState=TIM_OutputState_Enable;//输出比较使能
	TIM_OCInitStructure.TIM_Pulse=0;
	TIM_OC1Init(TIM14,&TIM_OCInitStructure);//初始化TIM14通道1
	
	TIM_OC2PreloadConfig(TIM14,TIM_OCPreload_Enable);//使能TIM14在CCR2上的预装载寄存器
	TIM_ARRPreloadConfig(TIM14,ENABLE);//使能自动重装载寄存器
	
	TIM_Cmd(TIM14,ENABLE);//使能TIM14
}
//AutomationReload:自动重装值(TIM2,TIM5的自动重装载值是32位的)   PrioritySendCount:时钟预分频数

void TIM5_CH1_InterCapture_Init(u32 AutomationReload,u16 PrioritySendCount)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);//TIM5时钟使能
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE);//使能GPIOA
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource0,GPIO_AF_TIM5);//PA0复用为TIM5
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF;
	GPIO_InitStructure.GPIO_OType=GPIO_OType_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_DOWN; //引脚设置为下拉,因为PA0对应KEY_UP按键,KEY_UP按键左侧接V3.3
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);//GPIOA初始化
	
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
	TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数模式
	TIM_TimeBaseInitStructure.TIM_Period=AutomationReload;
	TIM_TimeBaseInitStructure.TIM_Prescaler=PrioritySendCount;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitStructure);
	
	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上,也就是TIM5通道1
	TIM_ICInit(TIM5,&TIM_ICInitStructure);//初始化TIM5输入捕获
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);//使能捕获和更新中断
	
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel=TIM5_IRQn;//TIM5通道
	NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能中断优先级
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;//抢占优先级2
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;//响应优先级0
	NVIC_Init(&NVIC_InitStructure);//初始化NVIC
	
	TIM_Cmd(TIM5,ENABLE);//使能定时器5
}
//捕获状态位
//位7:0 还没成功捕获  1  成功捕获到一次
//位6:0 还没有捕获到低电平  1  成功捕获到一次低电平
//位5:0  捕获低电平后溢出的次数,当达到最高的溢出次数时,标记成功捕获一次
//这里需要注意,之所以设置低电平捕获状态和溢出次数,是因为初始化TIM5的时候设置的是上升沿捕获,那么就意味着初始化时上升沿就会进行一次捕获,想要获得高电平的持续时间,就要在中断中获得捕获低电平时的计数器值

u8 TIM5_CH1_CAPTURE_STA=0;//定义全局变量输入捕获的状态
u32 TIM5_CH1_CAPTURE_VAL;//输入捕获的值(注意TIM2/TIM5的输入捕获值是32位的)

void TIM5_IRQHandler(void)//两种情况 一种是溢出次数也就是循环了多少次,另一种是发生捕获也就是最后一次的时间,两个相加才是总的时间
//这个也比较好理解,因为我们不确定在我们捕获的低电平是第一次低电平,还是第n次低电平;
//如果只是单纯的记录低电平的计数器值,可能要比实际的高电平时间要小很多,因为中间掺杂着多个周期,我们都没有记录在内
{
	if((TIM5_CH1_CAPTURE_STA&0x80)==0)//输入捕获状态位的最高位为0,还未成功捕获
	{
		if(TIM_GetITStatus(TIM5,TIM_IT_Update)!=RESET)//判断是否为更新中断,溢出
		{
			if(TIM5_CH1_CAPTURE_STA&0x40)//已经捕获到高电平了
			{
				if((TIM5_CH1_CAPTURE_STA&0x3F)==0x3F)//低6位为1,表示溢出的次数达到了最高,默认标记成功捕获一次
				{
					TIM5_CH1_CAPTURE_STA=TIM5_CH1_CAPTURE_STA|0X80;//状态位的最高位置1,表示已经成功捕获了一次
					TIM5_CH1_CAPTURE_VAL=0xFFFFFFFF;//捕获值达到最高
				}
				else   //不是因为溢出次数达到顶峰而标记捕获,则捕获状态++;
					TIM5_CH1_CAPTURE_STA++;
			}
		}
		if(TIM_GetITStatus(TIM5,TIM_IT_CC1)!=RESET)//捕获1发生捕获事件
		{
			if(TIM5_CH1_CAPTURE_STA&0x40)//捕获一个下降沿,一次捕获已经结束了,我需要做以下几件事:
				//标记状态位的最高位为1,表明成功捕获一次,将捕获到的值给到全局变量VAL,
			//因为要捕获高电平的频率,所以先设置上升沿捕获,在设置下降沿捕获,这样一来,一个上升沿一个下降沿就会得到一个完整的高电平频率
			{
				TIM5_CH1_CAPTURE_STA|=0x80;//状态最高位置1,表示成功捕获一次
				TIM5_CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);//获取捕获值给到全局变量
				TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);//设置上升沿捕获
			}
			else //否则意味着还没有捕获到下降沿
			{
				TIM5_CH1_CAPTURE_STA=0;//清空
				TIM5_CH1_CAPTURE_VAL=0;
				TIM5_CH1_CAPTURE_STA|=0x40;//标记捕获到了一个上升沿
				TIM_Cmd(TIM5,ENABLE);//使能定时器5
				TIM_SetCounter(TIM5,0);//将定时器5清空
				TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling);//设置下降沿捕获
				TIM_Cmd(TIM5,ENABLE);//使能定时器5
			}
		}
	}
	TIM_ClearITPendingBit(TIM5,TIM_IT_CC1|TIM_IT_Update);//清除中断标志位
}


6.3 IntputCapture.h

#ifndef _INTPUTCAPTURE__H_
#define _INTPUTCAPTURE__H_

void TIM14_Init(u32 AutomaticReload,u32 PrioritySendCount);
void TIM5_CH1_InterCapture_Init(u32 AutomationReload,u16 PrioritySendCount);
void TIM5_IRQHandler(void);

#endif

物联沃分享整理
物联沃-IOTWORD物联网 » STM32F4_定时器输入捕获详解

发表评论