天天看點

STM32實作等精度測頻

上一章介紹了利用STM32的TIM的捕獲功能實作頻率測量的方法,但測量誤差受被測信号頻率的影響,不适合測量頻率變化較大的 。本章将介紹等精度測頻的方法以及STM32的實作。

  • STM32硬體電路闆及仿真器(以STM32F072C8單片機為例)
  • Keil v5以上版本(MDK-ARM)

基本原理

首先看一張圖:

STM32實作等精度測頻

傳統的測頻方式,閘門放時間是固定的,閘門時間内被測信号的計數個數Nx不一定是整數個,是以會有一定的誤差,且誤差與被測信号頻率有關。而等精度測頻的方法,閘門時間不是固定的,而是被測信号的整數倍。 是以消除了對被測信号計數的±1誤差,其誤差隻與标準信号的計數個數Ns的±1誤差有關。可以看出,閘門時間越長,标準頻率Fs越大,Ns的計數值越大,±1誤差的影響就小。在相同的閘門時間内,被測信号的頻率Fx=Nx*Fs/Ns。

STM32的實作

​實作等精度測頻用到兩個定時器,其中一個定時器用于産生閘門時間,另外一個用于捕獲被測信号和标準信号計數。

實作步驟:

  1. TIM1設定約1秒的閘門時間。
  2. TIM3捕獲到被測信号上升沿後,将TIM1計數器清零。
  3. 閘門時間開始,TIM3開始捕獲計數同時本身計數器也開始計數。
  4. 閘門時間到後,标志置位。
  5. TIM3檢測到标志置位且捕獲到上升沿後,記錄目前捕獲計數的值Nx和本身計數器的計數值Ns。
  6. 計算被測信号頻率Fx=Nx*Fs/Ns,其中Fs為定時器時鐘頻率48MHz。

STM32CubeMX的TIM配置如下:

STM32實作等精度測頻

TIM1作為産生閘門時間的定時器,其計數周期設定為47999,時鐘為系統時鐘,1000分頻。則溢出周期即閘門時間為1秒,打開溢出中斷:

STM32實作等精度測頻

TIM3作為捕獲被測信号的定時器,其配置如下,通道1設定為捕獲模式,捕獲上升沿,定時周期設定為最大65535,時鐘為系統時鐘,不分頻。打開中斷(TIM3捕獲和溢出為同一個中斷)。

STM32實作等精度測頻
STM32實作等精度測頻
STM32實作等精度測頻

程式實作:

按照之前介紹的步驟設定其它外設,并生成代碼。初始化後,啟動TIM1和TIM3中斷。

HAL_TIM_Base_Start_IT(&htim1);
  HAL_TIM_Base_Start_IT(&htim3);
  HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1);      

編寫中斷回調函數。

#define Fs 48000000L  //定時器時鐘頻率
uint32_t TIM3_Cnt_Value[2];//定時器3捕獲值
uint32_t TIM3_Over_Cnt=0;//定時器3溢出次數
uint32_t TIM1_Over_Flag=0;//定時器1溢出标志
uint32_t Ns,Nx;//标志信号計數值  被測信号計數值
double Fx;//被測頻率

//定時器溢出中斷 
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{

  if(htim->Instance == TIM1)
  {
    TIM1_Over_Flag = 1;
  }
  if(htim->Instance == TIM3)
  {
    TIM3_Over_Cnt++;
    if(TIM3_Over_Cnt==0xffffffff)TIM3_Over_Cnt=0xfffffffe;
  }

}

//定時器捕獲中斷
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
  {
    if(TIM1_Over_Flag == 0)
    {
      __HAL_TIM_SET_COUNTER(&htim1,0);//TIM1清零 開始閘門時間
      TIM3_Cnt_Value[0] = __HAL_TIM_GET_COUNTER(&htim3);
      Nx++;
    }
    else if(TIM1_Over_Flag == 1)//閘門時間到
    {
      Nx++;
      TIM3_Cnt_Value[1] = __HAL_TIM_GET_COUNTER(&htim3);
      Ns = (TIM3_Over_Cnt<<16) + TIM3_Cnt_Value[1] - TIM3_Cnt_Value[0];//計算标準信号的計數值
      TIM1_Over_Flag = 2;//一次測量完成 标志置位
    }
  }
}
//計算被測頻率
void CalcFx(void)
{
  if(TIM1_Over_Flag == 2)
  {
    Fx = (double)Nx * Fs / Ns;//計算頻率
    Nx=0; //計數清零
    TIM1_Over_Flag = 0;//标志清零,可以開始下一次測量
  }
}      

在主函數中調用CalcFx()函數即可擷取被測信号的頻率。

繼續閱讀