当前位置:   article > 正文

STM32F103详细频率测量过程_stm32f103测量频率范围

stm32f103测量频率范围

STM32F103频率测量


前言

本项目是做一个测量频率的仪器设备 ,初衷是为了测量正弦、三角、方波等信号的频率,由于设备限制,该文章仅限于方波频率的测量。本次测量频率的范围在5M以内,误差大约在0.005‰。


提示:以下是本篇文章正文内容

一、测频方法?

测频一般有外部中断测量频率、定时器测频(PWM频率捕获)、外部计数器等。

外部中断测频是使用最多最容易实现的,其测量原理也十分简单:

当外部检测到上升沿时触发中断,此时开启定时器,进行计时;当检测到第二个上升沿时结束计时,该时间便是一个周期的时间,由此可以得到频率。

外部中断测量频率PWM输入捕获可查看STM32F103不完全手册,在此不做介绍

外部中断测频的缺点十分明显:当频率过高时,MCU频繁进入中断,导致其测量精度下降。
可以如此理解:当第一个周期的方波来了,系统进入中断,此时刚准备开启定时器,第二个周期的方波也来了,此时就会错过该周期,直接到第三个周期,最后算出来的结果就是不正确的。
有人可能就说,既然算出来的是第一个周期到第三个周期,那你将算出来的T取一半不就行了??这就是本末倒置了,你事先并不知道会错过几个周期!!)。

本次重点说明外部计数器是如何实现的频率测量。依据测频原理:采用两个定时器,一个用作定时记为①,一个用作计数记为②。①用于频率相对较低的信号,进行精准定时,②用于频率相对较高的信号,用于得到定时期间测脉冲值。
测频原理
在被测信号上升沿来临时开始计时,同时开启计数(两者顺序无要求),下一次上升沿来临时先停止计数,再停止计时(此时的顺序必须先停止计数再停计时)。由此便可计算频率。

二、代码实现

1.定时器初始化

定时器使用的TIM2的外部脉冲计数

GPIO口初始化及定时器初始化:

void TIM2_Cap_Init(void)                                        //配置 TIM2_CH1_ETR 为外部脉冲计数
{    
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);   
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_0;             
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;           //PA0 输入  
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    GPIO_SetBits(GPIOA,GPIO_Pin_0);                       //PA0 下拉
    //初始化定时器2 TIM2   
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF;              //设定计数器自动重装值 
    TIM_TimeBaseStructure.TIM_Prescaler =0;             //预分频器   
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);         //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
    TIM_ITRxExternalClockConfig(TIM2,TIM_TS_ETRF);          //配置外部触发,否则不会计数
    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0);
    TIM_SetCounter(TIM2, 0);        
    
    TIM_Cmd(TIM2,ENABLE );                                  //使能定时器
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

重点部分解释:TIM_ITRxExternalClockConfig(TIM2,TIM_TS_ETRF); //配置外部触发,否则不会计数介绍定时器
本次采用的3),此处若想继续了解,可联系我一起进行学习探讨,下图摘选的系统外部中断配置的解释。系统文件解释

2.计数器的配置

代码如下:

void TIM3_Int_Init(u16 arr,u16 psc)
{
    TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure;

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能

	TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值	 计数到5000为500ms
	TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  10Khz的计数频率  
	TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
 
	TIM_ITConfig(  //使能或者失能指定的TIM中断
		TIM3, //TIM2
		TIM_IT_Update ,
		ENABLE  //使能
		);
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
	NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
	TIM_Cmd(TIM3, ENABLE);  //使能TIMx外设
							 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

该处采用的定时器3进行配置。


3.中断服务函数

//定时器3中断服务程序	 
void TIM3_IRQHandler(void)
{ 
	
	if(TIM_GetITStatus(TIM3,TIM_IT_Update)!= RESET)      //检查TIM5更新中断发生与否
	{
		TIM2CH1_CAPTURE_VAL=TIM_GetCounter(TIM2); //读取单位时间内计数器计的CNT值
		TIM_Cmd(TIM2,DISABLE ); 	//失能定时器2
		TIM_Cmd(TIM3,DISABLE ); 	//失能定时器3
		TIM_SetCounter(TIM2, 0);
		flag++;//
		                  
		CNT+=TIM2CH1_CAPTURE_VAL+overflow*65535;
		overflow=0;
		//printf("%d \r\n",CNT);
	if(flag<200)
		{
		
		TIM_Cmd(TIM2,ENABLE ); 	//使能定时器2
		TIM_Cmd(TIM3,ENABLE ); 	//使能定时器3
		}
	else
		data_output();
	TIM_ClearITPendingBit(TIM3, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
	}
	 
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

需要继续说明的是:一定先关计数,再关计时,能够减小误差! 其中data_output函数是数据输出,我为了方便直接串口发送数据,若有其他需要可以在data_output函数中添加OLED或LCD显示。
CNT+=TIM2CH1_CAPTURE_VAL+overflow65535;* 进行解释一下:在一个计时周期内,最终的计数值=定时器内的计数值+溢出次数*65535。
在这里插入图片描述

由前面定时器的配置可以知道,每次定时为5ms,因此累加200次后的计数值,就是1s的计数值,那么此时的计数值就是所要测得的频率!

void TIM2_IRQHandler(void)
{ 
	
	if(TIM_GetITStatus(TIM2,TIM_IT_Update)!= RESET)      //检查TIM2更新中断发生与否
		overflow++;
		TIM_ClearITPendingBit(TIM2, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
	 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

4.误差说明

void data_output()
{
	if(CNT<10000)
		printf("%d \r\n",CNT);
		else
		{
			cnt=CNT*(1+0.00019);
			printf("%d \r\n",cnt);
		}
		TIM_SetCounter(TIM2, 0);
		TIM_SetCounter(TIM3, 0);
		CNT=0;
		flag=0;
		TIM_Cmd(TIM2,ENABLE ); 	//使能定时器2
		TIM_Cmd(TIM3,ENABLE ); 	//使能定时器3
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

按照测量原理,在测量过程中理论误差在《电子测量原理》一书中有所阐述,也可联系我进行浅显讨论,本文仅针对实际结果进行简要分析。

测量过程中发现在高于50K开始出现明显偏差,将实际值和理论值进行列表分析后发现,两者之间存在一定的线性关系,因此在data_output()函数中进行了线性修正cnt=CNT*(1+0.00019)。

产生原因可能如下:①频率过高时系统频繁进入中断,导致1s累计的误差越来越多;②频率过高时,在开启定时和开启计数器之间,遗漏了部分周期,且频率越高遗漏越多,因此在低频时不易发现;③系统本身会有一定的计数误差,并且存在的±1误差在累计200次后,也会有一定影响。

三、测试数据及现象

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

四、总结

实验能够测量较高频率,实验结果能够接受,后期我会将工程文件上传至云盘,有何问题可联系我QQ:2745498610,希望大家能点关注,谢谢大家!

五、2023.7.23更新

鉴于大家的疑问我更新回复一下

有朋友在其中遇到问题和我进行探讨:

1、程序输出一直为0,其原因可能有①使用的管脚或芯片型号不一致,这个时候需要根据开发手册自己更改;②输入方波信号幅度低于1.8V时,此时单片机管脚会认为是低电平,此时就可认为一直是低电平输入,那输出自然就是0了,推荐输入信号高于2V

2、有人就说如果自己的信号就是很小该如何?有条件推荐在输入信号前加一个过零比较器;也可以换一种测频方式(外部触发),大家可以去比较一下各种测量方式的优劣

3、关于误差部分,大家可以发现,里面有一个修正子函数,其实当你的频率较高时就会出现一定的误差,很推荐大家去看一下《电子测量原理》这本书,
我简单再啰嗦一下:程序中定时1s计算里面的脉冲值,其中的1s又被分解了是5ms×200次,也就是说程序其实是计算5ms的脉冲值,然后连续计数200次,累计下的值输出。
就会出现一个问题:两次计数直接的开关定时器会不会影响计数值呢?前面程序也叙述到了,当我频率比较低时,两次开关定时器速度很快,几乎不会遗漏脉冲值,此时我可以认为测量值就是实际值了;当频率较高时,此时两次开关定时器的速度和脉冲速度相当了,此时便不能忽略漏掉的脉冲值。
这个问题解决方式就是对程序进行一个修正,每个频率段漏掉的脉冲值是近似一样的,因此通过比较测量值和标准信号值,建立里面的线性关系就可以了。

4、很希望大家不要直接拿着工程直接运行,最好自己思考一下原理,然后大家可以发现这个程序会存在一个问题,就是只能测量整数段频率,例如我想测量1.1Hz、或者110.5Hz的频率,或者0.2Hz频率,这个程序是无法完成的。大家可以在我测频的基础上继续提升一下,试着继续完善这个程序

5、大家还有不懂的,或者其他可以一起交流!Matlab、Python(信号处理部分和数据部分哈)。还有关于模拟信号(RF)、和高速数字信号处理(FPGA)都可以一起交流讨论!

网盘连接:
链接: https://mail.ihep.ac.cn/coremail/common/nfFile.jsp?share_link=2A4684BC55B6469F8002FF4564A92E66&uid=xujt%40ihep.ac.cn
密码: sXgc

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/967764
推荐阅读
相关标签
  

闽ICP备14008679号