15.1 输入捕获概述
系统滴答定时器一般用来提供“心跳”作用,而STM32定时器最基本功能也是定时,可以设置不同时间长度的定时。定时器除了最基本的定时功能外,定时器与GPIO有挂钩使得它可以发挥强大的作用,比如可以输出不同频率、不同占空比的方波信号、PWM信号,同时做为输入捕获功能时,可以测量脉冲宽度、实现电容按键检测等等。
STM32的输入捕获,简单的说就是通过检测 TIMx_CHx(定时器X的通道X)上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。
输入捕获说的通俗一点就是用计数器(定时器)来记录某一个脉冲高电平的时间,或者我们只捕获脉冲的上升沿或者下降沿,这具体要根据具体事例进行分析。
15.2 输入捕获过程
输入捕获的基本工作过程就是先捕捉一次脉冲上升沿,然后计数器开始计时,等待着捕捉到脉冲下降沿,等到捕捉到下降沿的时候,计数器停止计数,计算计数器中的数值,这个数值就是高电平所持续的时间,然后再重现开始下一轮的捕捉。
例如,要配置向上计数器在TI2输入端的上升沿计数,使用下列步骤:
1、配置TIMx_CCMR1寄存器CC2S=’01’,配置通道2检测TI2输入的上升沿
2、配置TIMx_CCMR1寄存器的IC2F[3:0],选择输入滤波器带宽(如果不需要滤波器,保持 IC2F=0000即无滤波器,以fDTS 采样)
3、配置TIMx_CCER寄存器的CC2P=’0’,选定上升沿极性
4、 配置TIMx_SMCR寄存器的SMS=’111’,选择定时器外部时钟模式1
5、 配置TIMx_SMCR寄存器中的TS=’110’,选定TI2作为触发输入
6、 设置TIMx_CR1寄存器的CEN=’1’,启动计数器
当上升沿出现在TI2,计数器计数一次,且TIF标志被设置。 在TI2的上升沿和计数器实际时钟之间的延时,取决于在TI2输入端的重新同步电路。
15.3 输入捕获配置步骤
1. 新建两个文件,cap.c 和 cap.h
2. 在头文件 cap.h 添加下面代码:
3. 把 cap.c 添加到工程中
4. 在 cap.c 中添加以下代码:
#include "cap.h"
u8 TIM2_CAPTURE_STA=0; //输入捕获状态
u16 TIM2_CAPTURE_VAL; //输入捕获值
void cap_init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure; //定义GPIO结构体
NVIC_InitTypeDef NVIC_InitStructure; //定义中断优先级结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; //定义TIM2定时器结构体
TIM_ICInitTypeDef TIM_ICInitStructure; //定义输入捕获结构体
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置中断优先级分组
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //使能GPIOA时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入模式
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化GPIOA
GPIO_ResetBits(GPIOA,GPIO_Pin_0); //PA0下拉
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件活动的自动装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入端IC1映射到TI1上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000配置输入滤波器 不滤波
TIM_ICInit(TIM2,&TIM_ICInitStructure);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断源
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化外设NVIC寄存器
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC1,ENABLE); //允许更新中断CC1IE捕获中断
TIM_Cmd(TIM2,ENABLE ); //使能定时器2
}
void TIM2_IRQHandler(void) //TIM2中断服务函数
{
if((TIM2_CAPTURE_STA & 0X80) == 0) //还未成功捕获
{
if((TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET))
{
if(TIM2_CAPTURE_STA & 0X40) //已经捕获到高电平了
{
if((TIM2_CAPTURE_STA & 0X3F) == 0X3F) //高电平太长了
{
TIM2_CAPTURE_STA |= 0X80; //标记成功捕获了一次
TIM2_CAPTURE_VAL = 0XFFFF;
}
else TIM2_CAPTURE_STA++;
}
}
if(TIM_GetITStatus(TIM2,TIM_IT_CC1) != RESET) //捕获 1 发生捕获事件
{
if(TIM2_CAPTURE_STA & 0X40) //捕获到一个下降沿
{
TIM2_CAPTURE_STA |= 0X80; //标记成功捕获到一次上升沿
TIM2_CAPTURE_VAL = TIM_GetCapture1(TIM2);
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Rising);//CC1P=0 设置为上升沿捕获
}
else //还未开始,第一次捕获上升沿
{
TIM2_CAPTURE_STA = 0; //清空
TIM2_CAPTURE_VAL = 0;
TIM_SetCounter(TIM2,0);
TIM2_CAPTURE_STA |= 0X40; //标记捕获到了上升沿
TIM_OC1PolarityConfig(TIM2,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获
}
}
}
TIM_ClearITPendingBit(TIM2,TIM_IT_Update|TIM_IT_CC1); //清除中断标志位
}
其中,TIM2_CAPTURE_VAL:就是用来记录当前计数器中的数值;
TIM2_CAPTURE_STA:是用来记录捕获状态,把它当成一个寄存器那样来使用。 TIM2_CAPTURE_STA 各位描述下表 所示:
从图中我们可以看到,TIM2_CAPTURE_STA的最高位是捕获完成的标志位,如果我们目前已经完成了一次高电平的捕获(即捕捉到了一次上升沿和一次下降沿),那么该位置1,否则置0;TIM2_CAPTURE_STA的次高位是捕获到高电平标志位,如果我们现在已经捕获到了一次上升沿,那么就把该位置1,然后再把捕获的极性改为下降沿,然后就等待着下一个中断的发生(每捕捉到一次边沿就发生一次中断),我们就这样一直依靠这两个标志位来进行上升沿和下降沿的捕获。
然后还有一个问题就是,如果某一个脉冲高电平的时间太长了,已经超出了我们定时器(计数器)所能记录的范围,那么计数器就会产生溢出,而这个溢出的次数需要记录下来,后面计算高电平时间要用到这个溢出次数,所以我们就用TIM2_CAPTURE_STA的后六位来进行记录计数器溢出的次数,如果高电平持续时间仍然提高,导致溢出的次数也超过了TIM2_CAPTURE_STA后六位所能表示的数值范围,那么这个时候,就让程序强制结束此次高电平的捕获,即认为这个脉冲的高电平时间是计数器的最大值。
这个中断服务函数里面包含了两个中断,一个是捕获中断,一个是溢出中断,在进入到这个中断服务函数里面,我们首先进行判断是发生了哪种中断,然后再进行相应的操作。
5. 实现 TIM2 通道 1 输入捕获功能
我们使用 TIM2 通道 1 输入捕获功能来捕获 TIM3 通道 2 输出的 PWM 波形。因此,需要把 PB05 接到 PA0 端子上,切记断电接线!
#include "stm32f10x.h"
#include "delay.h"
#include "led.h"
#include "tim.h"
#include "key.h"
#include "pwm.h"
#include "usart.h"
#include "cap.h"
extern u8 TIM2_CAPTURE_STA;
extern u16 TIM2_CAPTURE_VAL;
int main(void)
{
u32 temp=0;
delay_init();
usart_init(115200);
PWM_Init(900-1,0); //设置 80kHz PWM波形
cap_init(0XFFFF,72-1); //以 1MHz 的频率计数
printf("SYSTEM Init Complete.\r\n");
while(1)
{
delay_ms(10);
TIM_SetCompare2(TIM3,TIM_GetCapture2(TIM3) + 1);
if(TIM_GetCapture2(TIM3)==300)TIM_SetCompare2(TIM3,0);
if(TIM2_CAPTURE_STA & 0X80) //成功捕获到了一次上升沿
{
temp = TIM2_CAPTURE_STA&0X3F;
temp *= 65536; //溢出时间总和
temp += TIM2_CAPTURE_VAL; //得到总的高电平时间
printf("HIGH:%d us\r\n",temp); //打印总的高点平时间
TIM2_CAPTURE_STA = 0; //开启下一次捕获
}
}
}
在 main.c 文件中我们主要就是进行相关初始化函数的调用以及进行高电平时间的计算。其中主要部分就是高电平时间的计算,它就是计数器溢出次数乘以计数器的最大值,然后再加上当前计数器中的数值,最后把标志位清零。