Arduino上的快速傅立葉變換 (FFT)


有幾個可用的庫可以幫助您在 Arduino 上計算快速傅立葉變換 (FFT)。我們將研究 **arduinoFFT** 庫。此庫可以透過庫管理器安裝(搜尋 **arduinoFFT**)。

安裝完成後,轉到:**檔案→示例→arduinoFFT** 並開啟 **FFT_01** 示例。

示例

此示例首先建立一個頻率為 1000Hz 的正弦波(以 5000Hz 取樣)。然後使用 **漢明窗函式** 對其進行加窗。之後計算 FFT,確定幅度最大的頻率,並將其作為基頻返回。如果該值接近 1000 Hz,則此程式碼有效。

讓我們開始程式碼演練。我們首先包含庫並建立 **arduinoFFT()** 物件。

#include "arduinoFFT.h"

arduinoFFT FFT = arduinoFFT(); // Create FFT object
We then define the variables specific to the signal.
const uint16_t samples = 64; //This value MUST ALWAYS be a power of 2
const double signalFrequency = 1000;
const double samplingFrequency = 5000;
const uint8_t amplitude = 100;

我們將使用訊號的 64 個樣本生成我們的時域陣列。此外,此樣本數應始終為 2 的冪。

稍後,我們定義兩個陣列,分別用於儲存 FFT 輸出的實部和虛部,以及最初的原始資料。

double vReal[samples];
double vImag[samples];

最後,定義了 4 個常量。這些作為引數傳遞到稍後定義的 **printVector()** 函式中,並幫助確定縮放因子。當我們遍歷 **printVector()** 函式時,我們將看到它們的用法。

#define SCL_INDEX 0x00
#define SCL_TIME 0x01
#define SCL_FREQUENCY 0x02
#define SCL_PLOT 0x03

在 setup 中,我們只需初始化 Serial。

void setup()
{
   Serial.begin(115200);
   while(!Serial);
   Serial.println("Ready");
}

在迴圈中,我們首先構建我們的時域訊號陣列。

void loop()
{
   /* Build raw data */

   // Number of signal cycles that the sampling will read
   double cycles = (((samples-1) * signalFrequency) / samplingFrequency);
   for (uint16_t i = 0; i < samples; i++)
   {
   /* Build data with positive and negative values*/
   vReal[i] = int8_t((amplitude * (sin((i * (twoPi * cycles)) / samples))) / 2.0);
   // vReal[i] = uint8_t((amplitude * (sin((i * (twoPi * cycles)) / samples) + 1.0)) / 2.0);
   /* Build data displaced on the Y axis to include only positive values*/
   /* Imaginary part must be zeroed in case of looping to avoid wrong calculations and overflows */
   vImag[i] = 0.0;
   }

然後我們列印訊號,對訊號應用漢明窗並再次列印它。然後,我們使用 **FFT.Compute()** 計算 FFT,並列印實部和虛部向量。之後,我們使用 **FFT.ComplexToMagnitude()** 從實部和虛部向量計算幅度,並列印幅度。

最後,我們計算幅度最大的頻率(使用 **FFT.majorPeak()**)並列印它。

執行此操作後,幅度最大的頻率變為 1004.225670,這非常接近 1000 Hz。

/* Print the results of the simulated sampling according to time */
   Serial.println("Data:");
   PrintVector(vReal, samples, SCL_TIME);

   /* Weigh data */
   FFT.Windowing(vReal, samples, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
   Serial.println("Weighed data:");
   PrintVector(vReal, samples, SCL_TIME);
   FFT.Compute(vReal, vImag, samples, FFT_FORWARD); //Compute FFT
   Serial.println("Computed Real values:");
   PrintVector(vReal, samples, SCL_INDEX);
   Serial.println("Computed Imaginary values:");
   PrintVector(vImag, samples, SCL_INDEX);
   FFT.ComplexToMagnitude(vReal, vImag, samples); // Compute magnitudes
   Serial.println("Computed magnitudes:");
   PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);
   double x = FFT.MajorPeak(vReal, samples, samplingFrequency);
   Serial.println(x, 6);
   while(1);          / * Run Once */
   // delay(2000);    /* Repeat after delay */
}

最後,定義了 **printVector** 函式。

此函式接收要列印的向量、要列印的條目數和縮放因子。

如果因子為 **SCL_INDEX**,則列印向量中每個條目的索引號。

如果因子為 **SCL_TIME**,則從 0 開始列印向量中每個條目的時間(使用取樣頻率)。如果取樣頻率為 100,則每次讀取需要 1/100 或 0.01 秒。因此,可以計算每次讀取的時間。

如果因子為 **SCL_FREQUENCY**,則列印對應於每個條目的頻率。請注意,這僅在計算幅度後使用。

PrintVector(vReal, (samples >> 1), SCL_FREQUENCY);

請注意,我們已將樣本右移了 1 位。由於樣本始終為 2 的冪,因此右移意味著將樣本數除以 2。因此,對於 64 個樣本,右移 1 位後的值變為 32。

FFT 給出從 0 到 **取樣頻率/2**(奈奎斯特准則)的頻率值。因此,每個索引處的頻率值為 **索引*取樣頻率/樣本數**。這就是我們獲取頻率的方式。

void PrintVector(double *vData, uint16_t bufferSize, uint8_t scaleType)
{
   for (uint16_t i = 0; i < bufferSize; i++)
   {
      double abscissa;
      /* Print abscissa value */
      switch (scaleType)
      {
         case SCL_INDEX:
         abscissa = (i * 1.0);
      break;
      case SCL_TIME:
         abscissa = ((i * 1.0) / samplingFrequency);
      break;
      case SCL_FREQUENCY:
         abscissa = ((i * 1.0 * samplingFrequency) / samples);
      break;
   }
   Serial.print(abscissa, 6);
   if(scaleType==SCL_FREQUENCY)
      Serial.print("Hz");
      Serial.print(" ");
      Serial.println(vData[i], 4);
   }
   Serial.println();
}

請注意,尚未建立計算出的幅度與原始訊號幅度之間的關係。建議您仔細閱讀此庫附帶的其他示例。

更新於:2021年7月26日

10K+ 次檢視

開啟您的 職業生涯

完成課程獲得認證

開始學習
廣告

© . All rights reserved.