【DSP】DSP5509A的FFT演算法實現(附:完整程式碼及疑點解惑)
傅立葉變換及FFT原理
說起傅立葉變換,每個人第一反應都是從時域轉換到頻域的手段,如下圖所示:
但除了這一點之外呢?原理呢,推導呢?大概都是一頭霧水……
而FFT並不是一種新的變換,它是離散傅立葉變換(DFT)的一種快速演算法。
DFT的演算法速度:
由於我們在計算DFT時一次複數乘法需用四次實數乘法和二次實數加法;一次複數加法則需二次實數加法。每運算一個 X(k)需要 4N 次複數乘法及2N+2(N-1)=2(2N-1)次實數加法。所以整個DFT運算總共需要4N^2次實數乘法和 N*2(2N-1)=2N(2N-1)次實數加法。如此一來,計算時乘法次數和加法次數都是和N^2成正比的,當N很大時,運算量是可觀的,因而需要改進對 DFT 的演算法減少運算速度。
根據傅立葉變換的對稱性和週期性,我們可以將DFT運算中有些項合併。
FFT的演算法速度:
我們先設序列長度為N=2^L,L 為整數。將N=2^L的序列 x(n)(n=0,1,……,N-1),按N的奇偶分成兩組,也就是說我們將一個N點的DFT分解成兩個N/2點的DFT,他們又重新組合成一個如下式所表達的N點DFT:一般來說,輸入被假定為連續的。當輸入為純粹的實數的時候,我們就可以利用左右對稱的特性更好的計算FFT。
“FFT其實是很難的東西,即使常年在這個領域下打拼的科學家也未必能很好的寫出FFT的演算法。”所以,也不需要很頭疼這個演算法究竟什麼意思,這個演算法怎麼手寫,能用就行了。
關於傅立葉變換的原理和計算過程,比較複雜,如果想要深入理解,可以參考文章:從頭到尾徹底理解傅立葉變換演算法。本文由於文章篇幅就不展開講述了。
DSP5509A的FFT演算法實現
下列FFT演算法中的變數說明:
輸入的訊號為INPUT陣列,FFT輸出的訊號為DATA陣列,進行的是128點的FFT運算:
#include "myapp.h" #include "csedu.h" #include "scancode.h" #include <math.h> #define PI 3.1415926 #define SAMPLENUMBER 128 void InitForFFT(); void MakeWave(); int INPUT[SAMPLENUMBER],DATA[SAMPLENUMBER]; float fWaveR[SAMPLENUMBER],fWaveI[SAMPLENUMBER],w[SAMPLENUMBER]; float sin_tab[SAMPLENUMBER],cos_tab[SAMPLENUMBER]; main() { int i; InitForFFT(); MakeWave(); for ( i=0;i<SAMPLENUMBER;i++ ) { fWaveR[i]=INPUT[i]; fWaveI[i]=0.0f; w[i]=0.0f; } FFT(fWaveR,fWaveI); for ( i=0;i<SAMPLENUMBER;i++ ) { DATA[i]=w[i]; } while ( 1 ); // break point } void FFT(float dataR[SAMPLENUMBER],float dataI[SAMPLENUMBER]) { int x0,x1,x2,x3,x4,x5,x6,xx; int i,j,k,b,p,L; float TR,TI,temp; /********** following code invert sequence ************/ for ( i=0;i<SAMPLENUMBER;i++ ) { x0=x1=x2=x3=x4=x5=x6=0; x0=i&0x01; x1=(i/2)&0x01; x2=(i/4)&0x01; x3=(i/8)&0x01;x4=(i/16)&0x01; x5=(i/32)&0x01; x6=(i/64)&0x01; xx=x0*64+x1*32+x2*16+x3*8+x4*4+x5*2+x6; dataI[xx]=dataR[i]; } for ( i=0;i<SAMPLENUMBER;i++ ) { dataR[i]=dataI[i]; dataI[i]=0; } /************** following code FFT *******************/ for ( L=1;L<=7;L++ ) { /* for(1) */ b=1; i=L-1; while ( i>0 ) { b=b*2; i--; } /* b= 2^(L-1) */ for ( j=0;j<=b-1;j++ ) /* for (2) */ { p=1; i=7-L; while ( i>0 ) /* p=pow(2,7-L)*j; */ { p=p*2; i--; } p=p*j; for ( k=j;k<128;k=k+2*b ) /* for (3) */ { TR=dataR[k]; TI=dataI[k]; temp=dataR[k+b]; dataR[k]=dataR[k]+dataR[k+b]*cos_tab[p]+dataI[k+b]*sin_tab[p]; dataI[k]=dataI[k]-dataR[k+b]*sin_tab[p]+dataI[k+b]*cos_tab[p]; dataR[k+b]=TR-dataR[k+b]*cos_tab[p]-dataI[k+b]*sin_tab[p]; dataI[k+b]=TI+temp*sin_tab[p]-dataI[k+b]*cos_tab[p]; } /* END for (3) */ } /* END for (2) */ } /* END for (1) */ for ( i=0;i<SAMPLENUMBER/2;i++ ) { w[i]=sqrt(dataR[i]*dataR[i]+dataI[i]*dataI[i]); } } /* END FFT */ void InitForFFT() { int i; for ( i=0;i<SAMPLENUMBER;i++ ) { sin_tab[i]=sin(PI*2*i/SAMPLENUMBER); cos_tab[i]=cos(PI*2*i/SAMPLENUMBER); } } void MakeWave() { int i; for ( i=0;i<SAMPLENUMBER;i++ ) { INPUT[i]=sin(PI*2*i/SAMPLENUMBER*3)*1024; } }
FFT演算法實現的驗證
啟動除錯,開啟Tools->Graph,分別建立兩個Singal Time(一個是原始的正選波形,一個是程式FFT得出的波形)和一個FFT Magnitude(CCS軟體用FFT得出的波形),然後對比程式得出的和CCS幫得出的是否一致,就可以檢查程式有沒有問題。
顯示FFT演算法實現的結果,設定如下:
顯示INPUT的輸入波形(時域),設定如下:
顯示INPUT的輸入波形(頻域),設定如下:
最終得出的波形圖如下(從上到下分別為FFT演算法實現的結果、輸入INPUT的時域波形、輸入INPUT的頻域波形):
我們從程式程式碼中看出,輸入波形INPUT的表示式為:
INPUT[i]=sin(PI*2*i/SAMPLENUMBER*3)*1024;
它的週期是:42.6666667,幅值是:1024。這與第二幅圖的波形是保持一致的。
由於週期是42.6666667,所以它的頻率是0.0234375,這與第三幅圖的橫座標保持一致的。但是縱座標卻不一致!
再看第一幅圖,除了只有一個峰,表示只有一種頻率存在,峰的橫縱座標更加都是亂七八糟的!!
這是不是代表我們的FFT演算法是錯誤的呢?
FFT演算法實現的解惑
解惑一:FFT變換之後,橫縱座標的意義是什麼。
先看一下結論:
- 橫座標的變換:假設取樣頻率為Fs,取樣點數為N,那麼橫座標第n個點代表的頻率 X(n) = (Fs/N)* n。
- 縱座標的變換:假設原始訊號的賦值為A,取樣點數為N,那麼縱座標第n個點的幅值應該為 X(n) = (N/2)* A。
對照一下上面的程式,取樣頻率為1(作圖的時候設定裡面預設為1),取樣點數為128,峰值的賦值為1024,那麼有:
第三幅圖的橫座標為3,可得到頻率為3/128=0.0234375;縱座標大約為65000,可得到原始幅值為65000/64=1015.625,與1024相差不大。
而輸入INPUT的頻域波形,由於CCS內部的機制不太清楚,所以不能確定有沒有已經對橫縱座標進行了變換。
參考文章:FFT變換後,座標單位是什麼?
疑惑二:上面的演算法是128點的FFT計算,要是想要其他點數的FFT計算,怎麼修改呢?
先說一個辦法:
#define SAMPLENUMBER 128
修改這裡的128成為自己想要的FFT的點數。這是不行的!
原因:這裡的128表示的樣本的長度,並不是FFT的點數,點數是在FFT演算法中x0-x6來表示的。也就是說,直接修改128是不能改變自己想要的FFT點數的。那怎麼辦?
可以參考文章:FFT演算法的DSP實現(與本文同樣的思路)、FFT演算法的完整DSP實現(另外的演算法思路)。