定时器之输出捕获

简介

IC Input Capture )输入捕获
输入捕获模式下,当通道输入引脚出现指定电平跳变时,当前 CNT 的值将被锁存到 CCR 中,可用于测量 PWM 波形的频率、占空比、脉冲间隔、电平持续时间等参数
每个高级定时器和通用定时器都拥有 4 个输入捕获通道
可配置为 PWMI 模式,同时测量频率和占空比
可配合主从触发模式,实现硬件全自动测量
在图中红圈位置

 定时器之输出捕获_第1张图片

         因为输入捕获和输出比较的CH1的输出和输入引脚是共用的,所以同一个CH通道只能使用输入捕获和输出比较的其中一个

频率测量

定时器之输出捕获_第2张图片

对比测频法和测周法

测频法:(适合测量高频信号)由图可知,每接收一个上升沿计一次,所以会测到多个周期,所以在闸门时间内,最好要多出现一些上升沿,计次数量N多一些,可以减小误差。

(特性:测量结果更新慢一些,数值相对稳定;自带均值滤波,在闸门时间内波形频率有变化,得到的是这段时间内的平均频率,值比较平滑)

测周法:(适合测量低频信号)低频信号,周期比较长,测周法计次就会比较多,有助于减小误差

(特性:结果更新块以下,数据跳变也很快,出结果的速度取决于测量的频率)

这两种测频方法都存在这正负一误差,要想减少误差,就只能多计点数,当计次N足够大时,正负一误差的影响就会很小

         当有个频率测周法和测频法计次的N相同,就说明误差相同,这就是中介频率。

        对应图上:当待测信号频率小于中介频率时,测周法误差更小;当待测信号频率大于中介频率时,测频法误差更小。

详细介绍电路图

定时器之输出捕获_第3张图片

        从CH1等通道输入频率信号,(异或门是给三相电路使用的,暂时不介绍)然后经过输入滤波器中,过滤毛刺信号,边沿检测器即选择上升沿触发或者是下降沿触发,这里的输入滤波器和边沿检测器有两套,如果我们从CH1中输入信号,接下来可以选择输入TI1FP1到IC1或者输出TI1FP2到IC2中,这样设计的目的主要有两个:

        ①可以灵活切换后续捕获电路的输入,可以通过CH1输入IC1,也可以通过CH2输入IC1;

        ②可以把一个引脚的输入,同时映射到两个捕获单元,这是PWMI的经典结构,可以分为第一个捕获通道,使用上升沿触发,用来捕获周期,第二个捕获通道,使用下降沿触发,用来捕获占空比,这样就可以同时捕获周期和占空比

        接下来就来到了预分频器,分频后的触发信号就可以触发捕获电路进行工作了,每来一次信号,CNT的值就会向CCR转运一次,转运的同时会产生一个事件,这个事件会在状态寄存器值标志位,可以产生中断(如果需要在捕获到信号后采取行动,就可以开启这个捕获中断)

        采取上升沿触发捕获,没触发一次上升沿就会把CNT的值转运到CCR中,每传递信号两次,就会产生两个CCR的值,这两个CCR的差值就是两个相邻上升沿的时间间隔,也就是周期,取倒数就是测周法得出的频率了。

注意:每次我们捕获完一个周期后,要把CNT的值清零,这样才能捕获下次的频率长度(可以使用主从触发模式实现)

定时器之输出捕获_第4张图片CCMR1寄存器中的ICF位可以控制滤波器的参数

定时器之输出捕获_第5张图片

 工作原理:以采样频率对输入信号采样,当连续多个值都是同一电平值时,输出才为这个电平值,如果信号出现高频抖动,导致连续采样的N个值不全都一样,输出则不会变化,这样就可以达到滤波的效果,采样频率越低,采样个数N越大,滤波效果就越好;

CCER寄存器:

        CC1P位:可以选择极性;

        CC1E位:控制输出使能或者失能;

CCER寄存器:       

        CC1S位:可以对数据选择器进行选择;

        ICPS位:可以配置分频器;

硬件电路自动在捕获之后完成CNT的清零工作:

        TI1FP1和TI1的边沿信号都可以通向从模式控制器,这个从模式中就有电路可以自动完成CNT的清零。(完成自动化操作的利器)

主从触发模式

主模式,从模式,触发源选择的简称

定时器之输出捕获_第6张图片

主模式:可以将定时器内部的信号,映射到TRGO引脚,用于触发别的外设;

从模式:接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制;

触发源选择:就是选择从模式的触发信号源的;

假如我们想要CNT置零,我们触发源选择会选择TI1FP1,得到TRGI去触发执行从模式中的Reset

 手册中14.4.2有详细介绍

输入捕获基本结构

定时器之输出捕获_第7张图片

注意:①CNT清零和CNT传入CCR1是有先后顺序的;

②CNT的值时有上限的,即ARR最大可设置为65535,则CNT最大也是65535,所以如果频率太低,可能会导致CNT计数溢出,不能精准计算出频率;

③从模式的触发源只有TI1FP1和TI1FP2,没有3和4,如果要使用CNT自动把CNT置零,则只能使用1和2

PWMI基本结构

定时器之输出捕获_第8张图片

CCR1就是整个周期的值,CCR2则是高电平时的值,且把CNT的值赋予CCR2不会导致CNT清零,则CCR2/CCR1即为占空比

本节内容主要对应数据手册中的14.3.5和14.3.6

代码实操

输入捕获模式测频率

这里是输入PWM信号

定时器之输出捕获_第9张图片

逻辑:初始化TIM2的通道1,产生一个PWM波形,再通过函数修改频率和占空比,在OLED模块上实时显示此时的频率。

        这里我们在之前写的PWM控制LED呼吸灯的程序上做修改,因此需要多加一个修改频率的函数。

        这里我们需要修改ARR、PSC或者CCR的值来实现修改频率和占空比,因为调节ARR会影响占空比,所以我们通过调节PSC的值来修改频率,通过修改CCR的值来修改占空比。

要单独修改PSC的值,需要用到

void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);

 第三个参数即是指定的定时器预分频器的重装载的模式,一个是更新事件后生效,一个是立即生效

这里我们选择更新事件后生效

void PWM_PSC(uint16_t PSC)
{
	TIM_PrescalerConfig(TIM2, PSC, TIM_PSCReloadMode_Update);
}

这样我们就写好了修改PSC和CCR的值的函数

接下来写配置输入捕获的函数(按照下图的顺序)

定时器之输出捕获_第10张图片

先查看有关IC的配置函数

可以看到,OC的配置是有四个函数的,而IC也有四个通道,却只有一个函数配置,这是因为可以在这个函数的结构体参数中配置选择哪个通道(可能存在交叉配置,所以合在一起会更方便)

void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

 这个函数可以快速配置两个通道,把外设电路结构配置成PWMI模式

void TIM_PWMIConfig(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);

给输入捕获结构体赋一个初始化值

void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct);

 选择输入从模式的触发源TRGI

void TIM_SelectInputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

选择输出主模式的触发源TRGO

void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource);

选择从模式

void TIM_SelectSlaveMode(TIM_TypeDef* TIMx, uint16_t TIM_SlaveMode);

分别配置通道1、2、3、4的分频器

void TIM_SetIC1Prescaler(TIM_TypeDef* TIMx, uint16_t TIM_ICPSC);
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);

 分别读取四个通道的CCR

uint16_t TIM_GetCapture1(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture2(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture3(TIM_TypeDef* TIMx);
uint16_t TIM_GetCapture4(TIM_TypeDef* TIMx);

输出比较的模式下,CCR是只写的;输出捕获模式下,CCR是只读的;

写程序过程

1、打开GPIO和TIM 的时钟

我们计划用TIM3的通道1,所以GPIO需要初始化PA6,TIM需要初始化TIM3

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	//配置TIM3的CH1对应的GPIO口PA6
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
2、配置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);
3、配置时基单元,让CNT在内部时钟的驱动下自增运行
	//选择时基单元的时钟,选择为内部时钟
	TIM_InternalClockConfig(TIM3);
	
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
	//指定时钟分频(与本次操作没太大关系)
	TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式
	TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	//ARR(防止测量的频率太小,导致计数溢出)
	TimeBaseInitStructure.TIM_Period = 65536 - 1;
	//PSC(此时频率为1Hz)
	TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
	//重复计数器的值(高级计数器特有的,我们没有直接赋0)
	TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TimeBaseInitStructure);
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
4、配置输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStruct;
	//选择通道
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	//配置滤波器(不会改变原信号的频率)
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	//边沿检测,极性选择
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
	//触发信号分频器(不分频就是每次触发都有效,二分频则是每隔一次有效一次,以此类推)
	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	//选择触发信号从哪个引脚输入(配置数据选择器——直连通道和交叉通道)
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
5、选择从模式的触发源
	//触发源选择
	//配置TRGI的触发源为TI1FP1
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
6、选择触发从模式后需要执行的操作
	//配置从模式为Reset
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
7、打开定时器
	//启动定时器
	TIM_Cmd(TIM3, ENABLE);

 初始化函数总体

#include "stm32f10x.h"                  // Device header

void IC_Init(void)
{
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	//配置TIM3的CH1对应的GPIO口PA6
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	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);
	
	//选择时基单元的时钟,选择为内部时钟
	TIM_InternalClockConfig(TIM3);
	
	//时基单元初始化
	TIM_TimeBaseInitTypeDef TimeBaseInitStructure;
	//指定时钟分频(与本次操作没太大关系)
	TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	//计数器模式
	TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	//ARR(防止测量的频率太小,导致计数溢出)
	TimeBaseInitStructure.TIM_Period = 65536 - 1;
	//PSC(此时频率为1Hz)
	TimeBaseInitStructure.TIM_Prescaler = 72 - 1;
	//重复计数器的值(高级计数器特有的,我们没有直接赋0)
	TimeBaseInitStructure.TIM_RepetitionCounter = 0;
	TIM_TimeBaseInit(TIM3, &TimeBaseInitStructure);
	TIM_ClearFlag(TIM3, TIM_FLAG_Update);
	
	TIM_ICInitTypeDef TIM_ICInitStruct;
	//选择通道
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	//配置滤波器(不会改变原信号的频率)
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	//边沿检测,极性选择
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
	//触发信号分频器(不分频就是每次触发都有效,二分频则是每隔一次有效一次,以此类推)
	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	//选择触发信号从哪个引脚输入(配置数据选择器——直连通道和交叉通道)
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	
	//触发源选择
	//配置TRGI的触发源为TI1FP1
	TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
	
	//配置从模式为Reset
	TIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
	
	//启动定时器
	TIM_Cmd(TIM3, ENABLE);
}

当我们需要读取最新周期的频率是,只需要读取CCR的值再除以N即可

所以需要在编写一个读取CCR的值的函数

uint32_t IC_GetFreq(void)
{
	//在之前我们设置了PSC为72-1,fc=72M/(PSC+1)=1MHz
	//Freq=fc/N,且N=CCR
	return 1000000 / TIM_GetCapture1(TIM3);
}

然后就可以在主函数中调用试试

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	OLED_ShowString(1,1,"Freq:00000Hz");
	
	//用于社会PSC和CCR的值
	PWM_CCR(50);
	PWM_PSC(720-1);
	while(1)
	{
		OLED_ShowNum(1,6,IC_GetFreq(),5);
	}
}

此时会发现Freq=fc/N,fc=72M/(PSC+1),N=CCR得出频率应该是为1000Hz才对,但是OLED上却显示了1001Hz,这是电路中某处发生了变化的问题,也在正负一的误差之内,可以接受。

改善

想要解决的话,可以把计算Freq值的函数改为

uint32_t IC_GetFreq(void)
{
	//在之前我们设置了PSC为72-1,fc=72M/(PSC+1)=1MHz
	//Freq=fc/N,且N=CCR
	return 1000000 / (TIM_GetCapture1(TIM3) + 1);
}

这样就好了

PWMI测量频率占空比

按照下图配置

定时器之输出捕获_第11张图片

只需升级一下代码——把选择通道的代码修改一下即可

普通办法就是复制一份配置输入捕获单元的代码,把通道选择改为CH2,极性选择改为下降沿触发,数据选择器改为交叉输入

这样是可行的,但是ST公司特地为这步骤封装了一个函数,使用这个函数可以快速配置为PWMI模式(即配置为相反的配置,原是CH1,上升沿,直连通道,函数则是CH2,下降沿,交叉通道)

	//配置输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStruct;
	//选择通道
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	//配置滤波器(不会改变原信号的频率)
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	//边沿检测,极性选择
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
	//触发信号分频器(不分频就是每次触发都有效,二分频则是每隔一次有效一次,以此类推)
	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	//选择触发信号从哪个引脚输入(配置数据选择器——直连通道和交叉通道)
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
    //CH2
	TIM_ICInitTypeDef TIM_ICInitStruct;
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_2;
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Falling;
	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_IndirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);

上下两组代码等价 

	//配置输入捕获单元
	TIM_ICInitTypeDef TIM_ICInitStruct;
	//选择通道
	TIM_ICInitStruct.TIM_Channel = TIM_Channel_1;
	//配置滤波器(不会改变原信号的频率)
	TIM_ICInitStruct.TIM_ICFilter = 0x0F;
	//边沿检测,极性选择
	TIM_ICInitStruct.TIM_ICPolarity = TIM_ICPolarity_Rising;
	//触发信号分频器(不分频就是每次触发都有效,二分频则是每隔一次有效一次,以此类推)
	TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1;
	//选择触发信号从哪个引脚输入(配置数据选择器——直连通道和交叉通道)
	TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM3, &TIM_ICInitStruct);
	TIM_PWMIConfig(TIM3, &TIM_ICInitStruct);

注意:该函数只支持CH1和CH2的转换,其他不支持

这样就编写好了PWMI的初始化函数

接下来编写获取占空比的函数

uint32_t PWMI_GetDust(void)
{
	//理论知识:高电平持续时间存在CCR2中,整个周期持续时间存在CCR1中
	//占空比即CCR2/CCR1
	//两个值都需要补一个数
	return (TIM_GetCapture2(TIM3)+1) * 100 / (TIM_GetCapture1(TIM3)+1);
}

在主函数中调用实践

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "IC.h"

int main(void)
{
	OLED_Init();
	PWM_Init();
	IC_Init();
	OLED_ShowString(1,1,"Freq:00000Hz");
	OLED_ShowString(2,1,"Dust:000%");
	//用于社会PSC和CCR的值
	PWM_CCR(50);
	PWM_PSC(720-1);
	while(1)
	{
		OLED_ShowNum(1,6,IC_GetFreq(),5);
		OLED_ShowNum(2,6,PWMI_GetDust(),3);
	}
}

性能分析

测频率的范围:因为计数器最多可计到65535,则能测量的最低频率为1M/65535,大约15Hz,可以通过加大预分频的值,可以测得更小的频率;

高频应该使用测频法来测量;

误差分析:出来正负一误差外,还有可能由晶振造成误差

你可能感兴趣的:(stm32)