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();
}請注意,尚未建立計算出的幅度與原始訊號幅度之間的關係。建議您仔細閱讀此庫附帶的其他示例。
資料結構
網路
關係型資料庫管理系統 (RDBMS)
作業系統
Java
iOS
HTML
CSS
Android
Python
C 程式設計
C++
C#
MongoDB
MySQL
Javascript
PHP