打印
[其他ST产品]

STM32F103详细频率测量过程

[复制链接]
791|35
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
c17|  楼主 | 2023-6-27 10:37 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
前言
本项目是做一个测量频率的仪器设备 ,初衷是为了测量正弦、三角、方波等信号的频率,由于设备限制,该文章仅限于方波频率的测量。本次测量频率的范围在5M以内,误差大约在0.005‰。

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

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

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

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


使用特权

评论回复
评论
c17 2023-6-27 10:37 回复TA
———————————————— 版权声明:本文为CSDN博主「是颗橘子哇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/m0_47779755/article/details/125582628 
沙发
c17|  楼主 | 2023-6-27 10:38 | 只看该作者
PWM输入捕获可查看STM32F103不完全手册,在此不做介绍

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

使用特权

评论回复
板凳
c17|  楼主 | 2023-6-27 10:38 | 只看该作者
本次重点说明外部计数器是如何实现的频率测量。依据测频原理:采用两个定时器,一个用作定时记为①,一个用作计数记为②。①用于频率相对较低的信号,进行精准定时,②用于频率相对较高的信号,用于得到定时期间测脉冲值。

在被测信号上升沿来临时开始计时,同时开启计数(两者顺序无要求),下一次上升沿来临时先停止计数,再停止计时(此时的顺序必须先停止计数再停计时)。由此便可计算频率。

使用特权

评论回复
地板
c17|  楼主 | 2023-6-27 10:39 | 只看该作者
二、代码实现
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 );                                  //使能定时器
}

使用特权

评论回复
5
c17|  楼主 | 2023-6-27 10:41 | 只看该作者
重点部分解释:TIM_ITRxExternalClockConfig(TIM2,TIM_TS_ETRF); //配置外部触发,否则不会计数

使用特权

评论回复
6
c17|  楼主 | 2023-6-27 10:41 | 只看该作者
本次采用的3),此处若想继续了解,可联系我一起进行学习探讨,下图摘选的系统外部中断配置的解释。

使用特权

评论回复
7
c17|  楼主 | 2023-6-27 10:42 | 只看该作者
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外设
                                                         
}

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

使用特权

评论回复
8
c17|  楼主 | 2023-6-27 10:46 | 只看该作者
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); //清除中断标志位
        }
         
}

使用特权

评论回复
9
c17|  楼主 | 2023-6-27 10:46 | 只看该作者
需要继续说明的是:一定先关计数,再关计时,能够减小误差! 其中data_output函数是数据输出,我为了方便直接串口发送数据,若有其他需要可以在data_output函数中添加OLED或LCD显示。
CNT+=TIM2CH1_CAPTURE_VAL+overflow65535;* 进行解释一下:在一个计时周期内,最终的计数值=定时器内的计数值+溢出次数*65535。

使用特权

评论回复
10
c17|  楼主 | 2023-6-27 10:47 | 只看该作者
由前面定时器的配置可以知道,每次定时为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); //清除中断标志位
         
}

使用特权

评论回复
11
c17|  楼主 | 2023-6-27 10:47 | 只看该作者
由前面定时器的配置可以知道,每次定时为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); //清除中断标志位
         
}

使用特权

评论回复
12
c17|  楼主 | 2023-6-27 10:50 | 只看该作者
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
}

使用特权

评论回复
13
c17|  楼主 | 2023-6-27 10:50 | 只看该作者
按照测量原理,在测量过程中理论误差在《电子测量原理》一书中有所阐述,也可联系我进行浅显讨论,本文仅针对实际结果进行简要分析。

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

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

使用特权

评论回复
14
c17|  楼主 | 2023-6-27 10:50 | 只看该作者
三、测试数据及现象

使用特权

评论回复
15
c17|  楼主 | 2023-6-27 10:53 | 只看该作者

使用特权

评论回复
16
c17|  楼主 | 2023-6-27 10:53 | 只看该作者

使用特权

评论回复
17
c17|  楼主 | 2023-6-27 10:53 | 只看该作者

使用特权

评论回复
18
c17|  楼主 | 2023-6-27 10:54 | 只看该作者

使用特权

评论回复
19
c17|  楼主 | 2023-6-27 10:54 | 只看该作者

使用特权

评论回复
20
c17|  楼主 | 2023-6-27 10:54 | 只看该作者

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

c17

36

主题

300

帖子

1

粉丝