• 2
    回复
  • 收藏
  • 点赞
  • 分享
  • 发新帖

STM32单片机测量方波频率方法总结

一、测周法:通过一个方波的两个上升沿或下降沿触发中断,然后定时器计数,计数的总个 数乘以计数单位时间即该方波的周期,具体可通过单片机输入捕获功能实现,以下为参考代码

//输入捕获初始化函数
void input_frequent_init(void)  //采用TIM4的Channel_1通道作为输入捕获通道
{
        //声明结构体变量,用来初始化定时器
	TIM_TimeBaseInitTypeDef TIM4_TimeBaseInitStructure;
	TIM_ICInitTypeDef TIM4_ICInitStructure;
	NVIC_InitTypeDef TIM4_NVIC_InitStructure;
	/* 开启定时器4时钟 */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);	 
	TIM_ClearITPendingBit(TIM4,TIM_IT_Update|TIM_IT_CC1); //清除捕获和中断标志位
	
	TIM4_TimeBaseInitStructure.TIM_Period = 0xffff;	 //设定计数器自动重装值(设置为最大)	
	TIM4_TimeBaseInitStructure.TIM_Prescaler = 1;   //设置分频系数
	TIM4_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;	  //设置时钟分割:TDTS = Tck_tim
	TIM4_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;	 //TIM向上计数模式
	TIM_TimeBaseInit(TIM4,&TIM4_TimeBaseInitStructure);//根据结构体参量初始化定时器
	
	TIM4_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择输入捕获的输入端,IC1映射到TI1上 
	TIM4_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //设置为上升沿捕获
	TIM4_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
	TIM4_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
	TIM4_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器,此处不滤波
	TIM_ICInit(TIM4, &TIM4_ICInitStructure); //初始化TIM4通道1	
	
	//中断分组初始化
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	TIM4_NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn;	//打开TIM4的全局中断
	TIM4_NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;	//抢占优先级配置为1
	TIM4_NVIC_InitStructure.NVIC_IRQChannelSubPriority=1; //响应优先级配置为1
	TIM4_NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	  //使能
	NVIC_Init(&TIM4_NVIC_InitStructure);   //初始化中断
	
	TIM_Cmd(TIM4,ENABLE); //使能中断
	TIM_ITConfig(TIM4, TIM_IT_Update|TIM_IT_CC1, ENABLE );	//使能捕获和更新中断
}

需要注意的是,如果所测信号中存在尖峰干扰信号,则

TIM4_ICInitStructure.TIM_ICFilter = 0x00; //IC1F=0000 配置输入滤波器,此处不滤波

这一行应根据干扰信号的高电平时间来赋予合适的滤波器的值,具体计算方法参考芯片手册或自行百度。

void TIM4_IRQHandler()	  //输入捕获中断函数
{
	static u8 state;  //存储捕获状态,state=0表示未捕获到第一个上升沿,state=1表示已经捕获到第一个上升沿
	static u32 TIM4CH1_CAPTURE; //存储TIM4计数寄存器溢出次数
	u32 timecount;  //存储总的计数次数
	
	if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)  //发生计数器溢出更新中断	
	{
		TIM_ClearITPendingBit(TIM4, TIM_IT_CC1|TIM_IT_Update);  //清除中断标志位
		if(state==1) //在捕获到第一个上升沿后
			TIM4CH1_CAPTURE++;	//溢出次数加一				
	}	
	
	if(TIM_GetITStatus(TIM4,TIM_IT_CC1)!=RESET)  //产生输入捕获中断
        {
               TIM_ClearITPendingBit(TIM4, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
               if(state==0)    //未捕获到第一个上升沿
	        {
		    state=1;  //置1
		    TIM_SetCounter(TIM4,0);   //将计数器清零     
	        }
		else if(state==1)  //已经捕获到第一个上升沿
               {
                    state=0;  //置0
                    timecount=TIM_GetCapture1(TIM4)+TIM4CH1_CAPTURE*65536;   //计算两个上升沿之间的总计数
		    TIM4CH1_CAPTURE=0; //清零溢出次数
		    TIM_SetCounter(TIM4,0);  //清零计数器
		    frequent_input=36000000.0/timecount;   //计算频率
		}
   }
}

注意:根据所测频率大致范围来配置定时器(可提高测量精度)

该方法可精确测量较低频率,本人测试1k以下精确度高达0.1%,但随着频率的增加,误差也越来越大,故测低频时推荐此方法

接下来的程序还可测量占空比,思路是先设置为上升沿捕获,然后设置为下降沿捕获,在设置为上升沿捕获,根据两次捕获中计数次数算出占空比duty=捕获高电平时间/(捕获高电平时间+捕获低电平时间)

定时器输入捕获配置同上(改用TIM5,TIM分频系数改为143,TIM_Prescaler = 143),

不在重复,直接看中断函数

void TIM5_IRQHandler()	  
{
		
	if((TIM5CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
	{
		if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)	
		{
			if(TIM5CH1_CAPTURE_STA&0X20) //已经捕获到高电平
			{
				if((TIM5CH1_CAPTURE_STA&0x1f)==0x1f)//高电平时间太长了
				{	
					TIM5CH1_CAPTURE_STA|=0x80;	//标记成功捕获一次		
				}
				else
				{
					TIM5CH1_CAPTURE_STA++;	//溢出次数加1
				}
			}
		}	
	}
	if (TIM_GetITStatus(TIM5, TIM_IT_CC4) != RESET) //发生捕获事件
	{
		if((TIM5CH1_CAPTURE_STA&0X20)&&(!(TIM5CH1_CAPTURE_STA&0X40))) //已经捕获到上升沿
		{
			TIM5CH1_CAPTURE_STA|=0X40; //标记成功捕获一次下降沿 
			TIM5CH1_CAPTURE_VAL1=TIM_GetCapture4(TIM5)+(TIM5CH1_CAPTURE_STA&0X1f)*65536; //获得TIM5捕获通道一的捕获值(对应高电平时间)
			TIM_SetCounter(TIM5,0);  //清零计数器
			TIM_OC4PolarityConfig(TIM5,TIM_ICPolarity_Rising); //设置为上升沿捕获
			TIM5CH1_CAPTURE_STA&=0Xe0;  //溢出次数清零
		}
		else if((TIM5CH1_CAPTURE_STA&0X20)==0)  //未捕获到上升沿
		{
			TIM5CH1_CAPTURE_STA=0;    //清零标志位及溢出次数
			TIM5CH1_CAPTURE_VAL1=0;   //清零高电平计数
			TIM5CH1_CAPTURE_VAL2=0;	  //清零低电平计数		
			TIM_SetCounter(TIM5,0);   //清零TIM5计数寄存器			
                        TIM5CH1_CAPTURE_STA|=0X20; //置标志位
			TIM_OC4PolarityConfig(TIM5,TIM_ICPolarity_Falling); //设置为下降沿捕获
		}
		else if((TIM5CH1_CAPTURE_STA&0X40)&&(!(TIM5CH1_CAPTURE_STA&0X80))) //已经捕获到下降沿
		{
			TIM5CH1_CAPTURE_VAL2=TIM_GetCapture4(TIM5)+(TIM5CH1_CAPTURE_STA&0X1f)*65536;//低电平计数(对应低电平时间)
			TIM5CH1_CAPTURE_STA|=0X80;  //置标志位
		}			
	}
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC4|TIM_IT_Update); //清中断标志位
}

主函数

u8 TIM5CH1_CAPTURE_STA; //
u16 TIM5CH1_CAPTURE_VAL1;//
u16 TIM5CH1_CAPTURE_VAL2;//

float frequent;
float duty;

int main()
{		
        input_duty_init();	
	while(1)
	{
                if((TIM5CH1_CAPTURE_STA&0x80))	  //
                {
                       duty=(float)TIM5CH1_CAPTURE_VAL1/(TIM5CH1_CAPTURE_VAL1+TIM5CH1_CAPTURE_VAL2 );//计算占空比
                       frequent+=500000.0/(TIM5CH1_CAPTURE_VAL1+TIM5CH1_CAPTURE_VAL2 );//计算频率
                       TIM5CH1_CAPTURE_STA=0; // 清零标志位
                }
	}
}

这个程序中变量TIM5CH1_CAPTURE_STA的高三位作为输入捕获状态的标志位,具体每一位的作用不在详细解释,凭借自学能力完全可以解决。该方法测量误差同样在频率比较低时精确度很高,但随着频率的增大误差也越来越大。

全部回复(2)
正序查看
倒序查看
2022-02-24 14:53

二、测频法

通过在一定时间内检测跳边沿的个数可计算出频率 频率=上升沿或下降沿个数/统计时间

具体实现有两种方法。

1.利用外部中断统计跳边沿个数,配置一个定时器每隔一定时间对频率进行计算,部分代码如下

void exti_init()  //外部中断初始化函数
{
	GPIO_InitTypeDef GPIO_InitStructure;
	EXTI_InitTypeDef EXTI_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	/*开启GPIO和管脚复用时钟*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
        /*GPIO配置*/
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	 
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOC, GPIO_PinSource2);//选择GPIO引脚用作外部中段线路
	//此处一定要记住给端口管脚加上中断外部线路
	/*  设置外部中断模式*/ 
	EXTI_InitStructure.EXTI_Line=EXTI_Line2;
	EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;  //下降沿进中断
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure); 
	
	/*设置NVIC参数*/	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);		 
	NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn; 	//打开EXTI2的全局中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //设置优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;		  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;   //使能
	NVIC_Init(&NVIC_InitStructure); 		
}

外部中断中断函数

void EXTI2_IRQHandler()	   
{
	if(EXTI_GetITStatus(EXTI_Line2)==SET)
	{
   		EXTI_ClearITPendingBit(EXTI_Line0);//清中断
		if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)==Bit_RESET)	   //确定沿
		{
			cnt++;		
		} 
	}		
}

定时器中断函数

void TIM3_IRQHandler()	  
{
	
	frequent=cnt; //定时器设置时间为1s时
        cnt=0;  //清零计数cnt
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);    //清标志位
}

该方法在实测300K以下频率误差很小,大概在0-20Hz左右,根据规律可以进行线性分段补偿,补偿后误差可控制在0-2Hz,但是随着频率升高,误差将越来越大不可弥补。

2.采用定时器外部计数的方法,另外一个定时器负责每隔一段时间计算频率,部分代码如下

void time_init()
{	
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM2_TimeBaseInitStructure;	 
	TIM_TimeBaseInitTypeDef TIM3_TimeBaseInitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);

	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除TIM2中断标志位
	TIM2_TimeBaseInitStructure.TIM_Period = 0xFFFF;//设置自动重装载值
	TIM2_TimeBaseInitStructure.TIM_Prescaler = 0;//设置分频
	TIM2_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
	TIM2_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
	TIM_TimeBaseInit(TIM2,&TIM2_TimeBaseInitStructure);	
	  
	TIM_ETRClockMode1Config(TIM2, TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted, 0x00);  //设置为采用外部时钟计数,可设定滤波参数消除信号干扰
	
	TIM_Cmd(TIM2,ENABLE); 
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	TIM3_TimeBaseInitStructure.TIM_Period = 999;
	TIM3_TimeBaseInitStructure.TIM_Prescaler = 3599;
	TIM3_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; 
	TIM3_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM3,&TIM3_TimeBaseInitStructure);	
	
	TIM_Cmd(TIM3,ENABLE);
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE );	
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
	NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn; 
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;	
	NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;  
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;	
	NVIC_Init(&NVIC_InitStructure);	
}

定时器中断函数

void TIM3_IRQHandler()	 
{
	static u8 i;
	static u32 frequent_sum;
	TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   //清中断
	if(i<19)
	{
		cnt += TIM_GetCounter(TIM2);  //,获取计数器的值,累加减少误差
		TIM_SetCounter(TIM2,0);    //计数器清零
		i++;
	}
	else
	{
		cnt += TIM_GetCounter(TIM2);
		TIM_SetCounter(TIM2,0);
		cnt += cnt*0.000025;	//根据实际情况修改系数线性补偿
		frequent = cnt;
		i = 0;
		cnt = 0;		
	}
}

该方法在频率比较高的时候精确度高,在1k-10M左右测量精度比较高,适合测量较高频率。

总结:采用什么原理测量取决于所要求的精度和测频范围,根据要求选择适当的方法

若待测频率超过20M可采用可编程器件FPGA编程测量,可满足100M以上的频率测量

0
回复
2022-03-20 15:54
@lihui710884923
二、测频法通过在一定时间内检测跳边沿的个数可计算出频率频率=上升沿或下降沿个数/统计时间具体实现有两种方法。1.利用外部中断统计跳边沿个数,配置一个定时器每隔一定时间对频率进行计算,部分代码如下voidexti_init()//外部中断初始化函数{GPIO_InitTypeDefGPIO_InitStructure;EXTI_InitTypeDefEXTI_InitStructure;NVIC_InitTypeDefNVIC_InitStructure;/*开启GPIO和管脚复用时钟*/RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);/*GPIO配置*/GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOC,&GPIO_InitStructure);GPIO_EXTILineConfig(GPIO_PortSourceGPIOC,GPIO_PinSource2);//选择GPIO引脚用作外部中段线路//此处一定要记住给端口管脚加上中断外部线路/*设置外部中断模式*/EXTI_InitStructure.EXTI_Line=EXTI_Line2;EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;//下降沿进中断EXTI_InitStructure.EXTI_LineCmd=ENABLE;EXTI_Init(&EXTI_InitStructure);/*设置NVIC参数*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);NVIC_InitStructure.NVIC_IRQChannel=EXTI2_IRQn;//打开EXTI2的全局中断NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;//设置优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;//使能NVIC_Init(&NVIC_InitStructure);}外部中断中断函数voidEXTI2_IRQHandler(){if(EXTI_GetITStatus(EXTI_Line2)==SET){EXTI_ClearITPendingBit(EXTI_Line0);//清中断if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_2)==Bit_RESET)//确定沿{cnt++;}}}定时器中断函数voidTIM3_IRQHandler(){frequent=cnt;//定时器设置时间为1s时cnt=0;//清零计数cntTIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清标志位}该方法在实测300K以下频率误差很小,大概在0-20Hz左右,根据规律可以进行线性分段补偿,补偿后误差可控制在0-2Hz,但是随着频率升高,误差将越来越大不可弥补。2.采用定时器外部计数的方法,另外一个定时器负责每隔一段时间计算频率,部分代码如下voidtime_init(){GPIO_InitTypeDefGPIO_InitStructure;TIM_TimeBaseInitTypeDefTIM2_TimeBaseInitStructure;TIM_TimeBaseInitTypeDefTIM3_TimeBaseInitStructure;NVIC_InitTypeDefNVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStructure);TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除TIM2中断标志位TIM2_TimeBaseInitStructure.TIM_Period=0xFFFF;//设置自动重装载值TIM2_TimeBaseInitStructure.TIM_Prescaler=0;//设置分频TIM2_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;TIM2_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数TIM_TimeBaseInit(TIM2,&TIM2_TimeBaseInitStructure);TIM_ETRClockMode1Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);//设置为采用外部时钟计数,可设定滤波参数消除信号干扰TIM_Cmd(TIM2,ENABLE);TIM_ClearITPendingBit(TIM3,TIM_IT_Update);TIM3_TimeBaseInitStructure.TIM_Period=999;TIM3_TimeBaseInitStructure.TIM_Prescaler=3599;TIM3_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;TIM3_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;TIM_TimeBaseInit(TIM3,&TIM3_TimeBaseInitStructure);TIM_Cmd(TIM3,ENABLE);TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);NVIC_InitStructure.NVIC_IRQChannel=TIM3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0;NVIC_InitStructure.NVIC_IRQChannelSubPriority=0;NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;NVIC_Init(&NVIC_InitStructure);}定时器中断函数voidTIM3_IRQHandler(){staticu8i;staticu32frequent_sum;TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清中断if(i

此方式测量高频段信号频率准确率不错,也会大大减少中断次数。

 

采用外部时钟计数器的方式实现思路:

思路是配置两个定时器,定时器a设置为外部时钟计数器模式,定时器b设置为定时器(比如50ms溢出一次,也可以用软件定时器),然后定时器b中断函数中统计定时器a在这段时间内的增量,简单计算即可。

 

0
回复