目录
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
STM32定时器可以分为相关时钟、时基单元、输入捕获、输出比较。在上一节我们已经学习了STM32的输出PWM比较功能,本节我们将学习STM32的输入捕获功能。
输入捕获模式可以用来测量脉冲宽度或者测量频率。STM32F4系列拥有14个定时器,除了TIM6和TIM7,其他定时器都有输入捕获功能。
输入捕获功能就是通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如说上升沿/下降沿)的时候,将当前定时器的值TIMx_CNT存放到对应通道的捕获/比较寄存器TIMx_CCRx里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA等。
输入捕获的过程:输入阶段对TIx输入进行采样,通过滤波器生成一个滤波信号TIxF,然后通过一个带有极性选择的边沿检测器(极性选择就是确定高电平有效还是低电平有效)生成一个信号(TIxFPx),信号一方面可以用作模式控制器的触发输入,另一方面可以捕获命令,通过分频器分频以后传给捕获/比较寄存器。
在输入捕获模式下,当相应的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的时间长度。
这两个寄存器在过去学习定时器功能时,常常用到;分别是自动重装载值寄存器和时钟预分频寄存器。
捕获/比较寄存器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是用来设置输入采样频率和数字滤波器长度的。
捕获/比较使能寄存器 TIMx_CCER(capture/compare enable register)
CC1P位配置为输入时,由输入捕获的框图可得:1通道CH1对应TI1,又对应TI1PF1/TI1PF2;配置极性就是高电平有效,还是低电平有效。
CC1E位配置为输入时,捕获/比较1输出使能。
中断使能寄存器 TIMx_DIER(DMA/Interrupt enable register)
位1 CC1IE: 捕获/比较1中断使能
0:禁止CC1中断
1:使能CC1中断
本程序获取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
最后因为用到了串口输出结果,所以还需要配置一下串口
本实验程序通过输入捕获TIM5_CH1(PA0)上面的高电平脉冲宽度,并从串口打印捕获结果。
注意:
该程序中的TIM5CH1_CAPTURE_STA是一个状态位,前6位表示捕获高电平后定时器溢出的次数;第七位是捕获到高电平的标志;第八位是捕获完成的标志。TIM5CH1_CAPTURE_VAL用来记录捕获到下降沿时CNT计数器的值,因为该程序是捕获高电平脉冲宽度,每当捕获到下降沿的时候,就意味着捕获到了一个高电平脉冲信号。因为一个高电平是由一个上升沿和一个下降沿组成的。
#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; //开启下一次捕获
}
}
}
#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);//清除中断标志位
}
#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