高级定时器-输入捕获应用
输入捕获一般应用在两个方面,一个方面是脉冲跳变沿时间测量,另一方面是PWM输入测量。
测量脉宽或者频率
测量频率
当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器CCR中,而且还会进入捕获中断,在中断服务程序中记录第一次捕获(可以用一个标志变量来记录),并把捕获寄存器中的值读取到value1中。当出现第二次上升沿时,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器CCR中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value3中,并消除捕获记录标志。利用value3和value1的差值,我们就可以算出信号的周期(频率)。
测量脉宽
当捕获通道TIx上出现上升沿时,发生第一次捕获,计数器CNT的值会被锁存到捕获寄存器CCR中,而且还会进入捕获中断,在中断服务程序中记录第一次捕获(可以用一个标志变量来记录),并把捕获寄存器中的值读取到value1中。然后把捕获边沿改变为下降沿捕获,目的是捕获后面的下降沿。当下降沿到来的时候,发生第二次捕获,计数器CNT的值会再次被锁存到捕获寄存器CCR中,并再次进入捕获中断,在捕获中断中,把捕获寄存器的值读取到value3中,并消除捕获记录标志。然后把捕获边沿设置为上升沿捕获。
在测量脉宽过程中需要来回切换捕获边沿的极性,如果测量的脉宽时间比较长,定时器就会发生溢出,溢出的时候会产生更新中断,我们可以在中断里面对溢出进行记录处理。
PWM输入模式
测量脉宽和频率还有一个更简便的方法就是使用PWM输入模式,该模式是输入捕获的特里,只能使用通道1和通道2,通道3和通道4是用不了。与上面那种只使用一个捕获寄存器测量脉宽和频率的方法相比,PWM输入模式需要占用两个捕获寄存器。
当使用PWM输入模式的时候,因为一个输入通道(TIx)会占用两个捕获通道(ICx),所以一个定时器在使用PWM输入的时候最多只能使用两个输入通道(TIx)。
我们以输入通道TI1工作在PWM输入模式为例来讲解下具体的工作原理,其他通道以此类推即可。
PWM信号由输入通道TI1进入,因为是PWM输入模式的缘故,信号会被分为两路,一路是TI1FP1,另外一路是TI1FP2。其中一路是周期,另一路是占空比,具体哪一路信号对应周期还是占空比,得从程序上设置哪一路信号作为触发输入,作为触发输入的那一路信号对应的就是周期,另一路就是对应占空比。
作为触发输入的那一路信号还需要设置极性,是上升沿还是下降沿捕获,一旦设置好触发输入的极性,另外一路硬件就会自动匹配为相反的极性捕获,无需软件配置。
一句话概括就是:选定输入通道,确定触发信号,然后设置触发信号的极性即可,因为是PWM输入的缘故,另一路信号则由硬件配置,无需软件配置。
当使用PWM输入模式的时候必须将从模式控制器配置为复位模式(配置寄存器SMCR的位SMS[2:0]来实现),即当我们启动触发信号开始进行捕获的时候,同时把计数器CNT复位清零。
下面我们以一个更加具体的时序图来分析下PWM输入模式。
PWM信号由输入通道TI1进入,配置TI1FP1为触发信号,上升沿捕获。
当上升沿的时候IC1和IC2同时捕获,计数器CNT清零,到了下降沿的时候,IC2捕获,此时计数器CNT的值被锁存到捕获寄存器CCR2中,到了下一个上升沿的时候,IC1捕获,计数器CNT的值被锁存到捕获寄存器CCR1中。
其中CCR2+1测量的是脉宽,CCR1+1测量的是周期。这里需要注意的是:CCR1和CCR2的值在计算占空比和频率的时候都必须加1,因为计数器是从0开始计数的。
从软件上来说,用PWM输入模式测量脉宽和周期更容易,付出的代价是需要占用两个捕获寄存器。
脉宽测量输入捕获实验
根据开发板需求,我们选用TIM5的CH1,就PA0这个GPIO来测量信号的脉宽。
当按键按下的时候IO口会被拉高,这个时候我们可以利用定时器的输入捕获来测量按键按下的这段高电平的时间。
GeneralTime.c文件
#include "./generaltime/bsp_generaltime.h"
TIM_ICUserValueTypeDef TIM_ICUserValueStructure = {0,0,0,0};
//中断优先级配置
static void GeneralTime_NVIC_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure = {0};
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = GeneralTimeX_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
//定时器的GPIO配置
static void GeneralTime_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure = {0};
//开启时钟,由于这里我们只用了一路通道,所以随便一个就行
GeneralTimX_CH1_RCC_CLKCMD(GeneralTimeX_CH1_RCC_CLK_Periph,ENABLE);
//GPIO配置,这里配置TIM5_CH1
GPIO_InitStructure.GPIO_Pin = GeneralTimX_CH1_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GeneralTimX_CH1_GPIO_PORT,&GPIO_InitStructure);
}
//定时器的Mode配置
static void GeneralTime_Mode_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure = {0};
TIM_ICInitTypeDef TIM_ICInitStructure = {0};
//开启定时器时钟
GeneralTimeX_RCC_CLKCMD(GeneralTimeX_RCC_CLK_Periph,ENABLE);
//时基配置
TIM_TimeBaseInitStructure.TIM_Period = GeneralTimeX_Period;
TIM_TimeBaseInitStructure.TIM_Prescaler = GeneralTimeX_PSC;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(GeneralTimeX,&TIM_TimeBaseInitStructure);
//输入捕获配置
TIM_ICInitStructure.TIM_Channel = GeneralTimeX_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(GeneralTimeX,&TIM_ICInitStructure);
//清除更新、捕获中断标志位
TIM_ClearFlag(GeneralTimeX,TIM_FLAG_Update|GeneralTimeX_IT_CCx);
//使能中断
TIM_ITConfig(GeneralTimeX,TIM_IT_Update|TIM_IT_CC1,ENABLE);
//使能定时器
TIM_Cmd(GeneralTimeX,ENABLE);
}
void GeneralTime_Init(void)
{
GeneralTime_GPIO_Init();
GeneralTime_NVIC_Init();
GeneralTime_Mode_Init();
}
在这个里面,设置了计数器计数一次的时间是:(72-1+1)/72Mhz = 1us。捕获周期设置位0xFFFF,即最长的时间是:65536×1us = 65.536ms,当超过这个技术周期的时候,就会产生中断,然后在中断里面做额外的处理,需要记录好产生了多少次更新中断,最后把这个更新中断时间加入到脉宽的时间里面。
GeneralTime.h文件
#ifndef __BSP_GENERALTIME_H
#define __BSP_GENERALTIME_H
//通用定时器TIM2-T3-T4-T5
#include "stm32f10x.h"
typedef struct
{
uint8_t Capture_StartFlag;//捕获开始标志位
uint8_t Capture_FinishFlag;//捕获结束标志位
uint16_t Capture_CcrValue; //捕获寄存器的值
uint16_t Capture_Period; //自动重载寄存器更新标志
}TIM_ICUserValueTypeDef;
#define USE_TIM5
#ifdef USE_TIM2 //使用通用定时器2
#define GeneralTimeX TIM2
#define GeneralTimeX_RCC_CLKCMD RCC_APB1PeriphClockCmd
#define GeneralTimeX_RCC_CLK_Periph RCC_APB1Periph_TIM2
#define GeneralTimeX_Period 0xFFFF
#define GeneralTimeX_PSC (72-1)
#define GeneralTimX_CH1_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH1_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH1_GPIO_PORT GPIOA
#define GeneralTimX_CH1_GPIO_PIN GPIO_Pin_0
#define GeneralTimX_CH2_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH2_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH2_GPIO_PORT GPIOA
#define GeneralTimX_CH2_GPIO_PIN GPIO_Pin_1
#define GeneralTimX_CH3_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH3_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH3_GPIO_PORT GPIOA
#define GeneralTimX_CH3_GPIO_PIN GPIO_Pin_2
#define GeneralTimX_CH4_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH4_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH4_GPIO_PORT GPIOA
#define GeneralTimX_CH4_GPIO_PIN GPIO_Pin_3
#elif USE_TIM3 //使用通用定时器3
#define GeneralTimeX TIM3
#define GeneralTimeX_RCC_CLKCMD RCC_APB1PeriphClockCmd
#define GeneralTimeX_RCC_CLK_Periph RCC_APB1Periph_TIM3
#define GeneralTimeX_Period 0xFFFF
#define GeneralTimeX_PSC (72-1)
#define GeneralTimX_CH1_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH1_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH1_GPIO_PORT GPIOA
#define GeneralTimX_CH1_GPIO_PIN GPIO_Pin_6
#define GeneralTimX_CH2_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH2_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH2_GPIO_PORT GPIOA
#define GeneralTimX_CH2_GPIO_PIN GPIO_Pin_7
#define GeneralTimX_CH3_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH3_RCC_CLK_Periph RCC_APB2Periph_GPIOB
#define GeneralTimX_CH3_GPIO_PORT GPIOB
#define GeneralTimX_CH3_GPIO_PIN GPIO_Pin_0
#define GeneralTimX_CH4_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH4_RCC_CLK_Periph RCC_APB2Periph_GPIOB
#define GeneralTimX_CH4_GPIO_PORT GPIOB
#define GeneralTimX_CH4_GPIO_PIN GPIO_Pin_1
#elif USE_TIM4 //使用通用定时器4
#define GeneralTimeX TIM4
#define GeneralTimeX_RCC_CLKCMD RCC_APB1PeriphClockCmd
#define GeneralTimeX_RCC_CLK_Periph RCC_APB1Periph_TIM4
#define GeneralTimeX_Period 0xFFFF
#define GeneralTimeX_PSC (72-1)
#define GeneralTimX_CH1_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH1_RCC_CLK_Periph RCC_APB2Periph_GPIOB
#define GeneralTimX_CH1_GPIO_PORT GPIOB
#define GeneralTimX_CH1_GPIO_PIN GPIO_Pin_6
#define GeneralTimX_CH2_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH2_RCC_CLK_Periph RCC_APB2Periph_GPIOB
#define GeneralTimX_CH2_GPIO_PORT GPIOB
#define GeneralTimX_CH2_GPIO_PIN GPIO_Pin_7
#define GeneralTimX_CH3_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH3_RCC_CLK_Periph RCC_APB2Periph_GPIOB
#define GeneralTimX_CH3_GPIO_PORT GPIOB
#define GeneralTimX_CH3_GPIO_PIN GPIO_Pin_8
#define GeneralTimX_CH4_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH4_RCC_CLK_Periph RCC_APB2Periph_GPIOB
#define GeneralTimX_CH4_GPIO_PORT GPIOB
#define GeneralTimX_CH4_GPIO_PIN GPIO_Pin_9
#else //使用通用定时器5
#define GeneralTimeX TIM5
#define GeneralTimeX_RCC_CLKCMD RCC_APB1PeriphClockCmd
#define GeneralTimeX_RCC_CLK_Periph RCC_APB1Periph_TIM5
#define GeneralTimeX_IRQn TIM5_IRQn
#define GeneralTimeX_IRQHandler TIM5_IRQHandler
#define GeneralTimeX_Period 0xFFFF
#define GeneralTimeX_PSC (72-1)
#define GeneralTimeX_Channel_1 TIM_Channel_1
#define GeneralTimeX_IT_CCx TIM_FLAG_CC1
#define GeneralTimX_CH1_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH1_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH1_GPIO_PORT GPIOA
#define GeneralTimX_CH1_GPIO_PIN GPIO_Pin_0
#define GeneralTimX_CH2_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH2_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH2_GPIO_PORT GPIOA
#define GeneralTimX_CH2_GPIO_PIN GPIO_Pin_1
#define GeneralTimX_CH3_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH3_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH3_GPIO_PORT GPIOA
#define GeneralTimX_CH3_GPIO_PIN GPIO_Pin_2
#define GeneralTimX_CH4_RCC_CLKCMD RCC_APB2PeriphClockCmd
#define GeneralTimeX_CH4_RCC_CLK_Periph RCC_APB2Periph_GPIOA
#define GeneralTimX_CH4_GPIO_PORT GPIOA
#define GeneralTimX_CH4_GPIO_PIN GPIO_Pin_3
#endif
extern TIM_ICUserValueTypeDef TIM_ICUserValueStructure;
extern void GeneralTime_Init(void);
#endif
中断服务函数文件
void GeneralTimeX_IRQHandler(void)
{
// 当要被捕获的信号的周期大于定时器的最长定时时,定时器就会溢出,产生更新中断
// 这个时候我们需要把这个最长的定时周期加到捕获信号的时间里面去
if(TIM_GetITStatus(GeneralTimeX,TIM_IT_Update) == SET)
{
TIM_ICUserValueStructure.Capture_Period ++;
TIM_ClearITPendingBit(GeneralTimeX,TIM_IT_Update);
}
//上升沿捕获中断
if(TIM_GetITStatus(GeneralTimeX,TIM_IT_CC1) == SET)
{
//第一次捕获
if(TIM_ICUserValueStructure.Capture_StartFlag == 0)
{
TIM_SetCounter(GeneralTimeX,0);//计数器清0
TIM_ICUserValueStructure.Capture_Period = 0;
TIM_ICUserValueStructure.Capture_CcrValue = 0;
//第一次捕获到上升沿之后,把捕获边沿改为下降沿
TIM_OC1PolarityConfig(GeneralTimeX,TIM_ICPolarity_Falling);
TIM_ICUserValueStructure.Capture_StartFlag = 1;
}
else //第二次捕获
{
//获取捕获比较寄存器的值,这个值就是捕获到的高电平的值
TIM_ICUserValueStructure.Capture_CcrValue = TIM_GetCapture1(GeneralTimeX);
//当第二次捕获到下降沿之后,再把捕获边沿配置成上升沿,开启新一轮的捕获
TIM_OC1PolarityConfig(GeneralTimeX,TIM_ICPolarity_Rising);
//开始捕获状态标志清零
TIM_ICUserValueStructure.Capture_StartFlag = 0;
//捕获完成标准置1
TIM_ICUserValueStructure.Capture_FinishFlag = 1;
}
TIM_ClearITPendingBit(GeneralTimeX,GeneralTimeX_IT_CCx);
}
}
main.c文件
#include "stm32f10x.h"
#include "bsp_led.h"
#include "./generaltime/bsp_generaltime.h"
#include "./usart/bsp_usart.h"
int main(void)
{
uint32_t time = 0;
uint32_t TIM_PscClk = 72000000/(GeneralTimeX_PSC+1);
LED_GPIO_Config();
GeneralTime_Init();
USART_Config();
printf ( "\r\n野火 STM32 输入捕获实验\r\n" );
printf ( "\r\n按下K1,测试K1按下的时间\r\n" );
while (1)
{
if(TIM_ICUserValueStructure.Capture_FinishFlag == 1)
{
//高电平时间的计数器值
time = (TIM_ICUserValueStructure.Capture_Period * (GeneralTimeX_Period+1))+
(TIM_ICUserValueStructure.Capture_CcrValue + 1);
//打印高电平脉宽时间
printf("\r\n 测得高电平脉宽时间:%d.%d s\r\n",time/TIM_PscClk,time%TIM_PscClk );
TIM_ICUserValueStructure.Capture_FinishFlag = 0;
}
}
}
学有所成,以图国强!