一,TIM输入捕获介绍(IC(Input Capture)输入捕获)
- 定义:输入捕获模式下,当通道输入引脚出现指定电平跳变(如上升沿或下降沿)时,当前定时器的计数值(CNT)会被锁存到对应的捕获/比较寄存器(CCR)中。这一过程可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数。
- 工作原理:预设分频系数和捕获沿,检测定时器通道输入的边沿信号。当信号发生跳变并且满足预设条件时,将定时器当前值寄存器CNTx的值存入捕获比较寄存器CCRx中。
- 作用:
- 可用于测量PWM波形的频率、占空比、脉冲间隔、电平持续时间等参数
- 每个高级定时器和通用定时器都拥有4个输入捕获通道
- 可配置为PWMI模式,同时测量频率和占空比
- 可配合主从触发模式,实现硬件全自动测量
二、TIM输入捕获的基本步骤
- 开启时钟:使能定时器和相关GPIO端口的时钟。
- 初始化GPIO:将GPIO端口配置为输入模式,并根据需要选择上拉输入或浮空输入。
- 配置时钟源:选择定时器的时钟源,通常是内部时钟。
- 初始化时基单元:配置定时器的预分频器(PSC)、自动重装载值(ARR)等参数,以控制定时器的计数频率和周期。
- 初始化输入捕获单元:配置输入捕获通道、极性、滤波器、分频器等参数。例如,选择捕获通道、设置捕获极性(上升沿或下降沿)、配置输入捕获映射通道、设置预分频系数等。
- 触发源选择以及从模式配置:选择定时器的触发源,并配置从模式(如复位模式),以便在捕获事件发生时执行特定的操作(如清零计数器)。
- 使能定时器:启动定时器,开始捕获外部输入信号。
三、TIM输入捕获的应用场景
1,频率测量:
通过测量一定时间内输入信号的上升沿(或下降沿)数量,可以计算出输入信号的频率。常用的方法包括测频法和测周法。
TIM测量频率时,测频法、测周法以及频率测量法(通常指直接的频率测量,不涉及等精度测量等高级方法)各有特点,适用于不同的场景。以下是这三种方法的详细比较及适用场景分析:
1.1、测频法
定义与原理:
- 测频法是在一个固定的闸门时间(如1秒)内,对被测信号的上升沿(或下降沿)进行计数,得到计数值N。然后,通过公式f=N/T计算出被测信号的频率,其中f为被测信号的频率,T为闸门时间。
特点:
- 适合于高频信号的测量。
- 测量结果更新较慢,因为每次测量都需要等待整个闸门时间结束。
- 测量误差与被测信号的频率成反比,即信号频率越高,相对误差越小。
适用场景:
- 当被测信号的频率较高时,测频法能够提供较为准确的测量结果。
1.2、测周法
定义与原理:
- 测周法是通过测量被测信号的一个完整周期的时间T,然后通过公式f=1/T计算出被测信号的频率。在实际应用中,通常使用一个已知的标准频率信号作为计数器的时钟脉冲,测量被测信号周期内的标准频率脉冲数N,再通过公式f=fc/N计算被测信号的频率,其中fc为标准频率信号的频率。
特点:
- 适合于低频信号的测量。
- 测量结果更新较快,因为每次测量只需要等待一个信号周期结束。
- 测量误差与被测信号的频率和标准频率信号的频率有关,信号频率越低或标准频率越高,相对误差越小。
适用场景:
- 当被测信号的频率较低时,测周法能够提供较为准确的测量结果。
1.3、频率测量法(直接测量法)
定义与原理:
- 频率测量法通常指的是在一段时间内直接对被测信号的脉冲数进行计数,然后根据计数结果和测量时间计算出被测信号的频率。这种方法可以视为测频法的一种简化形式,但不涉及等精度测量等高级技术。
特点:
- 适用于频率范围较宽的信号测量,但精度可能受限于测量时间和信号稳定性。
- 测量误差与被测信号的频率、测量时间以及信号稳定性等因素有关。
适用场景:
- 在对测量精度要求不高,且信号频率范围较宽的情况下,可以使用频率测量法进行初步的频率估算。
1.4、总结与选择建议
- 在选择测量方法时,需要根据被测信号的频率范围、测量精度要求以及测量设备的性能等因素进行综合考虑。
- 对于高频信号,测频法通常更为适用;而对于低频信号,测周法则更具优势。
- 如果需要进一步提高测量精度,可以考虑采用等精度测量等高级方法,这些方法通常结合了测频法和测周法的优点,能够在宽频率范围内提供高精度的测量结果。
在实际应用中,还需要考虑信号的稳定性、噪声水平以及测量设备的精度和分辨率等因素对测量结果的影响。
2,脉宽测量
通过测量输入信号高电平(或低电平)的持续时间,可以计算出脉宽。这通常涉及到两次捕获操作,分别记录脉宽的开始和结束时刻。
3,PWM输入模式
在PWM输入模式下,定时器可以同时测量PWM波形的频率和占空比。这通常需要使用两个捕获通道,一个用于测量周期,另一个用于测量占空比。
四、TIM输入捕获的注意事项
- 滤波设置:为了避免高频噪声的干扰,可以配置输入捕获滤波器。滤波器可以对输入信号进行采样和平均处理,从而滤除高频噪声。
- 中断处理:如果需要在捕获事件发生时执行特定的操作(如数据处理、状态更新等),可以配置捕获中断。当中断发生时,微控制器会跳转到中断服务程序进行处理。
- 资源共享:高级定时器和通用定时器通常拥有多个输入捕获通道和输出比较通道,这些通道共用CCR寄存器。因此,对于同一个定时器来说,输入捕获和输出比较功能不能同时使用同一个通道。
综上所述,TIM输入捕获是STM32等微控制器中定时器的一个强大功能,它允许处理器精确地捕获外部输入信号的时间特性。通过合理配置和使用输入捕获功能,可以实现各种复杂的测量和控制任务。
五,输入捕获通道流程
它允许用户捕获外部信号的特定事件(如上升沿或下降沿),并记录下此时定时器的计数值。输入捕获的基本结构主要包括以下几个部分:
一、输入通道
每个定时器都拥有多个输入捕获通道(如CH1到CH4),这些通道可以连接到微控制器的不同GPIO引脚上。当外部信号通过GPIO引脚输入到定时器时,输入捕获功能开始工作。
二、输入滤波器
输入滤波器用于滤除外部信号中的噪声和干扰,确保输入捕获的准确性。用户可以通过配置滤波器的参数(如采样率和判定次数)来调整滤波效果。
三、边沿检测器
边沿检测器用于检测外部信号的边沿变化(如上升沿或下降沿)。当检测到边沿变化时,边沿检测器会触发输入捕获操作。
四、捕获/比较寄存器(CCR)
捕获/比较寄存器用于存储输入捕获时定时器的计数值。每当发生输入捕获时,当前定时器的计数值会被锁存到对应的CCR中,供用户读取。
五、分频器
分频器用于控制输入捕获的触发频率。用户可以通过设置分频系数来降低输入捕获的触发频率,从而避免过高的中断频率对系统性能的影响。
六、触发模式和从模式
STM32的定时器还支持多种触发模式和从模式,这些模式可以与其他定时器的输入捕获或输出比较功能配合使用,实现更复杂的定时和控制功能。例如,主从触发模式允许一个定时器作为主定时器控制另一个定时器的计数和捕获操作。
七、配置与初始化
在使用输入捕获功能之前,用户需要对定时器进行配置和初始化。这包括设置定时器的时钟源、预分频器、自动重装载寄存器、输入捕获通道的参数(如滤波器、边沿极性、分频系数等)以及启动定时器。
六,输入捕获模式测频率
1,相关库函数解释
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//初始化定时器输入捕获功能的函数
void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);//通常用于配置定时器的PWM输入捕获(PWMI)功能。
void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);//用于主模式触发源选择
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);//选择输出触发源
void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);//选择从模式void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);//分别单独配置通道1,2,3,4的分频器
void TIM_SetIC2Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC3Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
void TIM_SetIC4Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);//分别读取四个通道的CCR
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);
2,输入捕获模式测量频率代码
我们需要根据输入捕获基本结构来进行配置,流程如下:
- 1,RCC开启时钟,把GPIO和TIM的时钟打开
- 2,GPIO初始化,把GPIO配置成输入模式,一般选择上拉模式或者浮空输入模式
- 3,配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
- 4,配置输入捕获单元,包括滤波器,极性,直连通道还是交叉通道,分频器这些参数
- 5,选择从模式的触发源,触发源选择为TI1FP1,可以直接调用一个库函数
- 6,选择触发源之后执行Reset操作,这里也是直接调用一个库函数
- 7,当所有电路配置好后,调用TIM_Cmd函数,开启定时器
PWM.c
#include "stm32f10x.h" // Device header/*** 函 数:PWM初始化* 参 数:无* 返 回 值:无*/
void PWM_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO重映射*/
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启AFIO的时钟,重映射必须先开启AFIO的时钟
// GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE); //将TIM2的引脚部分重映射,具体的映射方案需查看参考手册
// GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE); //将JTAG引脚失能,作为普通GPIO引脚使用/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //GPIO_Pin_15;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出 //受外设控制的引脚,均需要配置为复用模式 /*配置时钟源*/TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元/*输出比较初始化*/TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值//则最好执行此函数,给结构体所有成员都赋一个默认值//避免结构体初值不确定的问题TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性,选择为高,若选择极性为低,则输出高低电平取反TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能TIM_OCInitStructure.TIM_Pulse = 0; //初始的CCR值TIM_OC1Init(TIM2, &TIM_OCInitStructure); //将结构体变量交给TIM_OC1Init,配置TIM2的输出比较通道1/*TIM使能*/TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}/*** 函 数:PWM设置CCR* 参 数:Compare 要写入的CCR的值,范围:0~100* 返 回 值:无* 注意事项:CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比* 占空比Duty = CCR / (ARR + 1)*/
void PWM_SetCompare1(uint16_t Compare)
{TIM_SetCompare1(TIM2, Compare); //设置CCR1的值
}/*** 函 数:PWM设置PSC* 参 数:Prescaler 要写入的PSC的值,范围:0~65535* 返 回 值:无* 注意事项:PSC和ARR共同决定频率,此函数仅设置PSC的值,并不直接是频率* 频率Freq = CK_PSC / (PSC + 1) / (ARR + 1)*/
void PWM_SetPrescaler(uint16_t Prescaler)
{TIM_PrescalerConfig(TIM2, Prescaler, TIM_PSCReloadMode_Immediate); //设置PSC的值
}
IC.c
#include "stm32f10x.h" // Device header/*** 函 数:输入捕获初始化* 参 数:无* 返 回 值:无*/
void IC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入/*配置时钟源*/TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元/*输入捕获初始化*/TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性,选择为上升沿触发捕获TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕获预分频,选择不分频,每次信号都触发捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉TIM_ICInit(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_ICInit,配置TIM3的输入捕获通道/*选择触发源及从模式*/TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //触发源选择TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //从模式选择复位//即TI1产生上升沿时,会触发CNT归零/*TIM使能*/TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}/*** 函 数:获取输入捕获的频率* 参 数:无* 返 回 值:捕获得到的频率*/
uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1); //测周法得到频率fx = fc / N,这里不执行+1的操作也可
}/*** 函 数:获取输入捕获的占空比* 参 数:无* 返 回 值:捕获得到的占空比*/
uint32_t IC_GetDuty(void)
{return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化PWM_Init(); //PWM初始化IC_Init(); //输入捕获初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列显示字符串Freq:00000Hz/*使用PWM模块提供输入捕获的测试信号*/PWM_SetPrescaler(720 - 1); //PWM频率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50); //PWM占空比Duty = CCR / 100while (1){OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不断刷新显示输入捕获测得的频率}
}
3,PWMI模式测频率占空比代码
我们需要根据此图来编写代码
主要修改IC.c代码
#include "stm32f10x.h" // Device header/*** 函 数:输入捕获初始化* 参 数:无* 返 回 值:无*/
void IC_Init(void)
{/*开启时钟*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //开启TIM3的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟/*GPIO初始化*/GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA6引脚初始化为上拉输入/*配置时钟源*/TIM_InternalClockConfig(TIM3); //选择TIM3为内部时钟,若不调用此函数,TIM默认也为内部时钟/*时基单元初始化*/TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数TIM_TimeBaseInitStructure.TIM_Period = 65536 - 1; //计数周期,即ARR的值TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM3的时基单元/*PWMI模式初始化*/TIM_ICInitTypeDef TIM_ICInitStructure; //定义结构体变量TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择配置定时器通道1TIM_ICInitStructure.TIM_ICFilter = 0xF; //输入滤波器参数,可以过滤信号抖动TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //极性,选择为上升沿触发捕获TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //捕获预分频,选择不分频,每次信号都触发捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //输入信号交叉,选择直通,不交叉TIM_PWMIConfig(TIM3, &TIM_ICInitStructure); //将结构体变量交给TIM_PWMIConfig,配置TIM3的输入捕获通道//此函数同时会把另一个通道配置为相反的配置,实现PWMI模式/*选择触发源及从模式*/TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1); //触发源选择TI1FP1TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset); //从模式选择复位//即TI1产生上升沿时,会触发CNT归零/*TIM使能*/TIM_Cmd(TIM3, ENABLE); //使能TIM3,定时器开始运行
}/*** 函 数:获取输入捕获的频率* 参 数:无* 返 回 值:捕获得到的频率*/
uint32_t IC_GetFreq(void)
{return 1000000 / (TIM_GetCapture1(TIM3) + 1); //测周法得到频率fx = fc / N,这里不执行+1的操作也可
}/*** 函 数:获取输入捕获的占空比* 参 数:无* 返 回 值:捕获得到的占空比*/
uint32_t IC_GetDuty(void)
{return (TIM_GetCapture2(TIM3) + 1) * 100 / (TIM_GetCapture1(TIM3) + 1); //占空比Duty = CCR2 / CCR1 * 100,这里不执行+1的操作也可
}
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"int main(void)
{/*模块初始化*/OLED_Init(); //OLED初始化PWM_Init(); //PWM初始化IC_Init(); //输入捕获初始化/*显示静态字符串*/OLED_ShowString(1, 1, "Freq:00000Hz"); //1行1列显示字符串Freq:00000HzOLED_ShowString(2, 1, "Duty:00%"); //2行1列显示字符串Duty:00%/*使用PWM模块提供输入捕获的测试信号*/PWM_SetPrescaler(720 - 1); //PWM频率Freq = 72M / (PSC + 1) / 100PWM_SetCompare1(50); //PWM占空比Duty = CCR / 100while (1){OLED_ShowNum(1, 6, IC_GetFreq(), 5); //不断刷新显示输入捕获测得的频率OLED_ShowNum(2, 6, IC_GetDuty(), 2); //不断刷新显示输入捕获测得的占空比}
}