简介
因为输入捕获和输出比较的CH1的输出和输入引脚是共用的,所以同一个CH通道只能使用输入捕获和输出比较的其中一个
频率测量
对比测频法和测周法
测频法:(适合测量高频信号)由图可知,每接收一个上升沿计一次,所以会测到多个周期,所以在闸门时间内,最好要多出现一些上升沿,计次数量N多一些,可以减小误差。
(特性:测量结果更新慢一些,数值相对稳定;自带均值滤波,在闸门时间内波形频率有变化,得到的是这段时间内的平均频率,值比较平滑)
测周法:(适合测量低频信号)低频信号,周期比较长,测周法计次就会比较多,有助于减小误差
(特性:结果更新块以下,数据跳变也很快,出结果的速度取决于测量的频率)
这两种测频方法都存在这正负一误差,要想减少误差,就只能多计点数,当计次N足够大时,正负一误差的影响就会很小
当有个频率测周法和测频法计次的N相同,就说明误差相同,这就是中介频率。
对应图上:当待测信号频率小于中介频率时,测周法误差更小;当待测信号频率大于中介频率时,测频法误差更小。
详细介绍电路图
从CH1等通道输入频率信号,(异或门是给三相电路使用的,暂时不介绍)然后经过输入滤波器中,过滤毛刺信号,边沿检测器即选择上升沿触发或者是下降沿触发,这里的输入滤波器和边沿检测器有两套,如果我们从CH1中输入信号,接下来可以选择输入TI1FP1到IC1或者输出TI1FP2到IC2中,这样设计的目的主要有两个:
①可以灵活切换后续捕获电路的输入,可以通过CH1输入IC1,也可以通过CH2输入IC1;
②可以把一个引脚的输入,同时映射到两个捕获单元,这是PWMI的经典结构,可以分为第一个捕获通道,使用上升沿触发,用来捕获周期,第二个捕获通道,使用下降沿触发,用来捕获占空比,这样就可以同时捕获周期和占空比。
接下来就来到了预分频器,分频后的触发信号就可以触发捕获电路进行工作了,每来一次信号,CNT的值就会向CCR转运一次,转运的同时会产生一个事件,这个事件会在状态寄存器值标志位,可以产生中断(如果需要在捕获到信号后采取行动,就可以开启这个捕获中断)
采取上升沿触发捕获,没触发一次上升沿就会把CNT的值转运到CCR中,每传递信号两次,就会产生两个CCR的值,这两个CCR的差值就是两个相邻上升沿的时间间隔,也就是周期,取倒数就是测周法得出的频率了。
注意:每次我们捕获完一个周期后,要把CNT的值清零,这样才能捕获下次的频率长度(可以使用主从触发模式实现)
CCMR1寄存器中的ICF位可以控制滤波器的参数
工作原理:以采样频率对输入信号采样,当连续多个值都是同一电平值时,输出才为这个电平值,如果信号出现高频抖动,导致连续采样的N个值不全都一样,输出则不会变化,这样就可以达到滤波的效果,采样频率越低,采样个数N越大,滤波效果就越好;
CCER寄存器:
CC1P位:可以选择极性;
CC1E位:控制输出使能或者失能;
CCER寄存器:
CC1S位:可以对数据选择器进行选择;
ICPS位:可以配置分频器;
硬件电路自动在捕获之后完成CNT的清零工作:
TI1FP1和TI1的边沿信号都可以通向从模式控制器,这个从模式中就有电路可以自动完成CNT的清零。(完成自动化操作的利器)
主从触发模式
主模式,从模式,触发源选择的简称
主模式:可以将定时器内部的信号,映射到TRGO引脚,用于触发别的外设;
从模式:接收其他外设或者自身外设的一些信号,用于控制自身定时器的运行,也就是被别的信号控制;
触发源选择:就是选择从模式的触发信号源的;
假如我们想要CNT置零,我们触发源选择会选择TI1FP1,得到TRGI去触发执行从模式中的Reset
手册中14.4.2有详细介绍
输入捕获基本结构
注意:①CNT清零和CNT传入CCR1是有先后顺序的;
②CNT的值时有上限的,即ARR最大可设置为65535,则CNT最大也是65535,所以如果频率太低,可能会导致CNT计数溢出,不能精准计算出频率;
③从模式的触发源只有TI1FP1和TI1FP2,没有3和4,如果要使用CNT自动把CNT置零,则只能使用1和2
PWMI基本结构
CCR1就是整个周期的值,CCR2则是高电平时的值,且把CNT的值赋予CCR2不会导致CNT清零,则CCR2/CCR1即为占空比
本节内容主要对应数据手册中的14.3.5和14.3.6
代码实操
输入捕获模式测频率
这里是输入PWM信号
逻辑:初始化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的值的函数
接下来写配置输入捕获的函数(按照下图的顺序)
先查看有关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口PA6RCC_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的触发源为TI1FP1TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);
6、选择触发从模式后需要执行的操作
//配置从模式为ResetTIM_SelectSlaveMode(TIM3, TIM_SlaveMode_Reset);
7、打开定时器
//启动定时器TIM_Cmd(TIM3, ENABLE);
初始化函数总体
#include "stm32f10x.h" // Device headervoid IC_Init(void)
{RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//配置TIM3的CH1对应的GPIO口PA6RCC_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的触发源为TI1FP1TIM_SelectInputTrigger(TIM3, TIM_TS_TI1FP1);//配置从模式为ResetTIM_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=CCRreturn 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=CCRreturn 1000000 / (TIM_GetCapture1(TIM3) + 1);
}
这样就好了
PWMI测量频率占空比
按照下图配置
只需升级一下代码——把选择通道的代码修改一下即可
普通办法就是复制一份配置输入捕获单元的代码,把通道选择改为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);//CH2TIM_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,可以通过加大预分频的值,可以测得更小的频率;
高频应该使用测频法来测量;
误差分析:出来正负一误差外,还有可能由晶振造成误差