【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实现(另外的算法思路)。

 

  • 8
    点赞
  • 74
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
下面是Java实现FFT算法的代码: ```java import java.util.Arrays; public class FFT { public static Complex[] fft(Complex[] x) { int N = x.length; // base case if (N == 1) return new Complex[] { x[0] }; // radix 2 Cooley-Tukey FFT if (N % 2 != 0) { throw new RuntimeException("N is not a power of 2"); } // fft of even terms Complex[] even = new Complex[N/2]; for (int k = 0; k < N/2; k++) { even[k] = x[2*k]; } Complex[] q = fft(even); // fft of odd terms Complex[] odd = even; // reuse the array for (int k = 0; k < N/2; k++) { odd[k] = x[2*k + 1]; } Complex[] r = fft(odd); // combine Complex[] y = new Complex[N]; for (int k = 0; k < N/2; k++) { double kth = -2 * k * Math.PI / N; Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); y[k] = q[k].plus(wk.times(r[k])); y[k + N/2] = q[k].minus(wk.times(r[k])); } return y; } public static void main(String[] args) { Complex[] x = { new Complex(1, 0), new Complex(2, 0), new Complex(3, 0), new Complex(4, 0) }; // FFT of original data Complex[] y = fft(x); // print results System.out.println("x"); System.out.println("-------------------"); for (int i = 0; i < x.length; i++) { System.out.println(x[i]); } System.out.println(); System.out.println("y = fft(x)"); System.out.println("-------------------"); for (int i = 0; i < y.length; i++) { System.out.println(y[i]); } System.out.println(); System.out.println("y = ifft(y)"); System.out.println("-------------------"); Complex[] z = ifft(y); for (int i = 0; i < z.length; i++) { System.out.println(z[i]); } System.out.println(); } public static Complex[] ifft(Complex[] x) { int N = x.length; Complex[] y = new Complex[N]; // take conjugate for (int i = 0; i < N; i++) { y[i] = x[i].conjugate(); } // compute forward FFT y = fft(y); // take conjugate again for (int i = 0; i < N; i++) { y[i] = y[i].conjugate(); } // divide by N for (int i = 0; i < N; i++) { y[i] = y[i].times(1.0 / N); } return y; } } class Complex { private final double re; // the real part private final double im; // the imaginary part // create a new object with the given real and imaginary parts public Complex(double real, double imag) { re = real; im = imag; } // return a string representation of the invoking Complex object public String toString() { if (im == 0) return re + ""; if (re == 0) return im + "i"; if (im < 0) return re + " - " + (-im) + "i"; return re + " + " + im + "i"; } // return abs/modulus/magnitude and angle/phase/argument public double abs() { return Math.hypot(re, im); } // Math.sqrt(re*re + im*im) public double phase() { return Math.atan2(im, re); } // between -pi and pi // return a new Complex object whose value is (this + b) public Complex plus(Complex b) { Complex a = this; // invoking object double real = a.re + b.re; double imag = a.im + b.im; return new Complex(real, imag); } // return a new Complex object whose value is (this - b) public Complex minus(Complex b) { Complex a = this; double real = a.re - b.re; double imag = a.im - b.im; return new Complex(real, imag); } // return a new Complex object whose value is (this * b) public Complex times(Complex b) { Complex a = this; double real = a.re * b.re - a.im * b.im; double imag = a.re * b.im + a.im * b.re; return new Complex(real, imag); } // scalar multiplication // return a new object whose value is (this * alpha) public Complex times(double alpha) { return new Complex(alpha * re, alpha * im); } // return a new Complex object whose value is the conjugate of this public Complex conjugate() { return new Complex(re, -im); } // return a new Complex object whose value is the reciprocal of this public Complex reciprocal() { double scale = re*re + im*im; return new Complex(re / scale, -im / scale); } // return the real or imaginary part public double re() { return re; } public double im() { return im; } // return a / b public Complex divides(Complex b) { Complex a = this; return a.times(b.reciprocal()); } // return a new Complex object whose value is the complex exponential of this public Complex exp() { return new Complex(Math.exp(re) * Math.cos(im), Math.exp(re) * Math.sin(im)); } // return a new Complex object whose value is the complex sine of this public Complex sin() { return new Complex(Math.sin(re) * Math.cosh(im), Math.cos(re) * Math.sinh(im)); } // return a new Complex object whose value is the complex cosine of this public Complex cos() { return new Complex(Math.cos(re) * Math.cosh(im), -Math.sin(re) * Math.sinh(im)); } // return a new Complex object whose value is the complex tangent of this public Complex tan() { return sin().divides(cos()); } // a static version of plus public static Complex plus(Complex a, Complex b) { double real = a.re + b.re; double imag = a.im + b.im; Complex sum = new Complex(real, imag); return sum; } } ``` 上面的代码实现FFT算法和IFFT算法,其中FFT算法使用递归实现,而IFFT算法则是在FFT算法的基础上进行了一些简单的变换。代码中用到了一个名为Complex的复数类,这里直接使用了作者提供的实现。 代码中的FFT算法输入为一个长度为2的n次方的复数数组,输出为进行了FFT变换后的复数数组;IFFT算法输入为一个进行了FFT变换后的复数数组,输出为进行了IFFT变换后的复数数组。 需要注意的是,这里的FFT算法只适用于输入长度为2的n次方的复数数组,输入长度不符合要求时会抛出异常。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值