1. 程式人生 > >【DSP】DSP5509A的FFT演算法實現(附:完整程式碼及疑點解惑)

【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實現(另外的演算法思路)。