[其他ST产品] STM32F103详细频率测量过程

[复制链接]
4621|36
 楼主| c17 发表于 2023-6-27 10:37 | 显示全部楼层 |阅读模式
前言
本项目是做一个测量频率的仪器设备 ,初衷是为了测量正弦、三角、方波等信号的频率,由于设备限制,该文章仅限于方波频率的测量。本次测量频率的范围在5M以内,误差大约在0.005‰。

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

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

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

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

评论

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

外部中断测频的缺点十分明显:当频率过高时,MCU频繁进入中断,导致其测量精度下降。
(可以如此理解:当第一个周期的方波来了,系统进入中断,此时刚准备开启定时器,第二个周期的方波也来了,此时就会错过该周期,直接到第三个周期,最后算出来的结果就是不正确的。
有人可能就说,既然算出来的是第一个周期到第三个周期,那你将算出来的T取一半不就行了??这就是本末倒置了,你事先并不知道会错过几个周期!!)。
 楼主| c17 发表于 2023-6-27 10:38 | 显示全部楼层
本次重点说明外部计数器是如何实现的频率测量。依据测频原理:采用两个定时器,一个用作定时记为①,一个用作计数记为②。①用于频率相对较低的信号,进行精准定时,②用于频率相对较高的信号,用于得到定时期间测脉冲值。
10700649a4ba049cd6.png
在被测信号上升沿来临时开始计时,同时开启计数(两者顺序无要求),下一次上升沿来临时先停止计数,再停止计时(此时的顺序必须先停止计数再停计时)。由此便可计算频率。

 楼主| c17 发表于 2023-6-27 10:39 | 显示全部楼层
二、代码实现
1.定时器初始化
定时器使用的TIM2的外部脉冲计数

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

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

 楼主| c17 发表于 2023-6-27 10:41 | 显示全部楼层
重点部分解释:TIM_ITRxExternalClockConfig(TIM2,TIM_TS_ETRF); //配置外部触发,否则不会计数
79208649a4c5d0e5f6.png
 楼主| c17 发表于 2023-6-27 10:41 | 显示全部楼层
本次采用的3),此处若想继续了解,可联系我一起进行学习探讨,下图摘选的系统外部中断配置的解释。

98604649a4c6e964d0.png
 楼主| c17 发表于 2023-6-27 10:42 | 显示全部楼层
2.计数器的配置
代码如下:
  1. void TIM3_Int_Init(u16 arr,u16 psc)
  2. {
  3.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  4.         NVIC_InitTypeDef NVIC_InitStructure;

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

  6.         TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值         计数到5000为500ms
  7.         TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值  10Khz的计数频率  
  8.         TIM_TimeBaseStructure.TIM_ClockDivision = 0; //设置时钟分割:TDTS = Tck_tim
  9.         TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
  10.         TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位

  11.         TIM_ITConfig(  //使能或者失能指定的TIM中断
  12.                 TIM3, //TIM2
  13.                 TIM_IT_Update ,
  14.                 ENABLE  //使能
  15.                 );
  16.         NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
  17.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  //先占优先级0级
  18.         NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;  //从优先级3级
  19.         NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
  20.         NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  21.         TIM_Cmd(TIM3, ENABLE);  //使能TIMx外设
  22.                                                          
  23. }

该处采用的定时器3进行配置。
 楼主| c17 发表于 2023-6-27 10:46 | 显示全部楼层
3.中断服务函数
  1. //定时器3中断服务程序         
  2. void TIM3_IRQHandler(void)
  3. {
  4.        
  5.         if(TIM_GetITStatus(TIM3,TIM_IT_Update)!= RESET)      //检查TIM5更新中断发生与否
  6.         {
  7.                 TIM2CH1_CAPTURE_VAL=TIM_GetCounter(TIM2); //读取单位时间内计数器计的CNT值
  8.                 TIM_Cmd(TIM2,DISABLE );         //失能定时器2
  9.                 TIM_Cmd(TIM3,DISABLE );         //失能定时器3
  10.                 TIM_SetCounter(TIM2, 0);
  11.                 flag++;//
  12.                                  
  13.                 CNT+=TIM2CH1_CAPTURE_VAL+overflow*65535;
  14.                 overflow=0;
  15.                 //printf("%d \r\n",CNT);
  16.         if(flag<200)
  17.                 {
  18.                
  19.                 TIM_Cmd(TIM2,ENABLE );         //使能定时器2
  20.                 TIM_Cmd(TIM3,ENABLE );         //使能定时器3
  21.                 }
  22.         else
  23.                 data_output();
  24.         TIM_ClearITPendingBit(TIM3, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
  25.         }
  26.          
  27. }

 楼主| c17 发表于 2023-6-27 10:46 | 显示全部楼层
需要继续说明的是:一定先关计数,再关计时,能够减小误差! 其中data_output函数是数据输出,我为了方便直接串口发送数据,若有其他需要可以在data_output函数中添加OLED或LCD显示。
CNT+=TIM2CH1_CAPTURE_VAL+overflow65535;* 进行解释一下:在一个计时周期内,最终的计数值=定时器内的计数值+溢出次数*65535。 27234649a4d9cbcecf.png
 楼主| c17 发表于 2023-6-27 10:47 | 显示全部楼层
由前面定时器的配置可以知道,每次定时为5ms,因此累加200次后的计数值,就是1s的计数值,那么此时的计数值就是所要测得的频率!


  1. void TIM2_IRQHandler(void)
  2. {
  3.        
  4.         if(TIM_GetITStatus(TIM2,TIM_IT_Update)!= RESET)      //检查TIM2更新中断发生与否
  5.                 overflow++;
  6.                 TIM_ClearITPendingBit(TIM2, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
  7.          
  8. }
 楼主| c17 发表于 2023-6-27 10:47 | 显示全部楼层
由前面定时器的配置可以知道,每次定时为5ms,因此累加200次后的计数值,就是1s的计数值,那么此时的计数值就是所要测得的频率!
  1. void TIM2_IRQHandler(void)
  2. {
  3.        
  4.         if(TIM_GetITStatus(TIM2,TIM_IT_Update)!= RESET)      //检查TIM2更新中断发生与否
  5.                 overflow++;
  6.                 TIM_ClearITPendingBit(TIM2, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
  7.          
  8. }
 楼主| c17 发表于 2023-6-27 10:50 | 显示全部楼层
4.误差说明
  1. void data_output()
  2. {
  3.         if(CNT<10000)
  4.                 printf("%d \r\n",CNT);
  5.                 else
  6.                 {
  7.                         cnt=CNT*(1+0.00019);
  8.                         printf("%d \r\n",cnt);
  9.                 }
  10.                 TIM_SetCounter(TIM2, 0);
  11.                 TIM_SetCounter(TIM3, 0);
  12.                 CNT=0;
  13.                 flag=0;
  14.                 TIM_Cmd(TIM2,ENABLE );         //使能定时器2
  15.                 TIM_Cmd(TIM3,ENABLE );         //使能定时器3
  16. }
 楼主| c17 发表于 2023-6-27 10:50 | 显示全部楼层
按照测量原理,在测量过程中理论误差在《电子测量原理》一书中有所阐述,也可联系我进行浅显讨论,本文仅针对实际结果进行简要分析。

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

产生原因可能如下:①频率过高时系统频繁进入中断,导致1s累计的误差越来越多;②频率过高时,在开启定时和开启计数器之间,遗漏了部分周期,且频率越高遗漏越多,因此在低频时不易发现;③系统本身会有一定的计数误差,并且存在的±1误差在累计200次后,也会有一定影响。
 楼主| c17 发表于 2023-6-27 10:50 | 显示全部楼层
三、测试数据及现象 93369649a4e729cd03.png
 楼主| c17 发表于 2023-6-27 10:53 | 显示全部楼层
 楼主| c17 发表于 2023-6-27 10:53 | 显示全部楼层
 楼主| c17 发表于 2023-6-27 10:53 | 显示全部楼层
 楼主| c17 发表于 2023-6-27 10:54 | 显示全部楼层
 楼主| c17 发表于 2023-6-27 10:54 | 显示全部楼层
 楼主| c17 发表于 2023-6-27 10:54 | 显示全部楼层
您需要登录后才可以回帖 登录 | 注册

本版积分规则

c17

40

主题

312

帖子

1

粉丝
快速回复 在线客服 返回列表 返回顶部