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_TimeBaseInitTypeDefTIM_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 ); //使能定时器
}
重点部分解释:TIM_ITRxExternalClockConfig(TIM2,TIM_TS_ETRF); //配置外部触发,否则不会计数
本次采用的3),此处若想继续了解,可联系我一起进行学习探讨,下图摘选的系统外部中断配置的解释。
2.计数器的配置
代码如下:void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDefTIM_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外设
}
该处采用的定时器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); //清除中断标志位
}
}
需要继续说明的是:一定先关计数,再关计时,能够减小误差! 其中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); //清除中断标志位
}
由前面定时器的配置可以知道,每次定时为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); //清除中断标志位
}
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
}
按照测量原理,在测量过程中理论误差在《电子测量原理》一书中有所阐述,也可联系我进行浅显讨论,本文仅针对实际结果进行简要分析。
测量过程中发现在高于50K开始出现明显偏差,将实际值和理论值进行列表分析后发现,两者之间存在一定的线性关系,因此在data_output()函数中进行了线性修正cnt=CNT*(1+0.00019)。
产生原因可能如下:①频率过高时系统频繁进入中断,导致1s累计的误差越来越多;②频率过高时,在开启定时和开启计数器之间,遗漏了部分周期,且频率越高遗漏越多,因此在低频时不易发现;③系统本身会有一定的计数误差,并且存在的±1误差在累计200次后,也会有一定影响。 三、测试数据及现象
页:
[1]
2