视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
FFT快速傅里叶变换设计
2025-09-25 17:54:44 责编:小OO
文档
中文摘要

数字信号处理 (Digital Signal Processing,DSP)是一门应用十分广泛的学科。数字信号处理是指利用计算机技术,以数字形式对信号进行采样、变换、滤波、估值、增强、压缩、识别等处理,以得到人们需要的信号形式。数字信号处理器也称DSP芯片,是一种用于进行数字信号处理运算的微处理器,其突出特点是采用多组总线技术实现并行机制,有的家发起和乘法器,灵活的寻址方式,实时快速地实现各种数字信号处理算法及各种复杂运算。

傅里叶变换是一种将时域信号变换为频域信号的变换形式。在频域分析中,信号的频率及对应的幅值、相位(统称为频谱)反映了系统的性能。快速傅里叶变换(Fast Fourier Transform)是实现离散傅里叶变换(DFT)的一种快速高效的运算方法,是数字信号处理中最为重要的算法之一。FFT算法的关键在于利用了蝶形因子的内在对称性和周期性,从而加快了运算的速度,使运算时间缩短1至2个数量级。

此次快速傅里叶变换的DSP程序设计,根据书上提供的C编程序在相对应的DSP工作环境下进行调试、图形分析、输入比较,来加深对程序的理解以及对所学内容的融合和巩固。程序设计所选择的头文件为< math.h >,完成的是采样次数N为128的FFT运算。为了更好的观察到快速傅里叶运算的结果,还另外设计了一组为方波的输入,从而得到设计要求的FFT运算结果图形。

关键词:  数字信号处理,DSP芯片,快速傅里叶变换FFT,C语言程序

1 设计任务描述

1.1 设计题目:快速傅里叶变换程序设计

1.2 设计要求

1.2.1 设计目的

1)理解FFT的算法以及利用DSP实现的方法。

2)能熟练的调试程序并能观察其结果。

3)熟悉TMS320C54x系列DSP芯片的软件设计方法。

1.3 基本要求

     

 1)研究FFT原理以及利用DSP实现的方法。

2)编写FFT程序。

3)调试程序,观察结果。

2 设计思路

根据此次课程设计的要求,采用的是C语言程序。本次快速傅里叶变换程序设计主要包括三大部分:初始化定义部分,主函数部分,子程序部分。其中调用的子程序由四个功能函数组成:FFT初始化函数、计算功率谱函数、波形发生函数、倒序运算函数。这四个调用函数在主函数运行到相应位置时进入操作中,实现一个完整的快速傅里叶变换。

由于在程序代码中调用了pow、log、cos、sin函数,该函数所在的C文件应包含头文件math.h。初始化定义部分主要是对程序需要用到的函数或者数据进行定义,这就需要我们熟悉前面知识所学到的各种基本数据类型的格式,如数组、结构、联合等构造类型数据。主函数部分就需要对FFT变换的整体过程有熟悉的了解,这样才能够知道应该调用的子程序的顺序。因为一旦程序开始运行,除了一开始的初始化阶段,直接进入的就是主函数部分。函数调用部分是整个程序的重点部分,在这里实现了波形的输入、FFT变换,可以通过CCS软件调试该程序,并用其中View→Graph→Time/Frequency菜单功能,显示变量INPUT与DATA图形,观察FFT的效果。

在此次的程序设计中,我设计了正弦波和方波两种输入情况。对于不同的输入,经过FFT变换之后就会得到不同的频谱图,在整个程序中,FFT的变换过程是最大的难点,完成这部分需要对数字信号处理课程中的快速傅里叶变换的知识有很大程度的掌握,这样才能知道函数中倒序产生的原理与方法,并且对蝶形图中的运算也能相当了解,这样才能理清思路,利用C语言的字符语句达到倒序处理的目的。

3.设计方框图

设计方框图显现的是在设计时主要实现的功能和流程,简单易懂,能清晰的体现整个设计的思路。

图  3-1 主程序流程

  

 4 快速傅里叶变换的的算法实现

4.1变换原理

     若给定由个信号样本{(0),(1),…,(-1)}组成的信号序列(),DFT可用式2-1给出:

=0,1,…, -1         (2-1)

式2-1中,称为旋转因子或蝶形因子, =。从中可以看出:当信号样本为复数时,计算单个需经过次复数乘法和-1次复数加法运算,相当于4次实数乘法和2(2-1)次实数加法。完成全部点DFT共需次复数乘法和(-1)复数加法运算。可见,随着不断增加,整个DFT运算量是相当庞大的,而FFT算法通过对计算过程的深入分析,利用旋转因子具有的周期性与对称性,实现了降低运算复杂度的目的。

当序列长度为偶数时,信号序列()可被分解为奇、偶两个子序列,相应的点DFT被分解为两个/2点的DFT:

             =0,1, …,/2-1           (2-2)

         =0,1, …,/2-1          (2-3)

式(2-2)和(2-3)中,和分别表示()分解后得到的/2点偶序列点奇序列的DFT。式(2-2)和式(2-3)表明,只要求出和,()前/2点和后/2点的DFT就得到了,整个序列的DFT也就得到了。这样做的好处是计算点DFT只需要约/2次复数乘法,总运算量约为直接DFT运算量的一半。同理,当/2为偶数时,每个/2点的DFT又可被分解成两个/4点的DFT,进一步减少了DFT运算的复杂度。依次类推,直到不能继续分解为止。分解结束时,最小DFT的点数称为称为基数,当=(为正整数)时,经过-1次分解,点DFT最终可被分解为/2个两点的DFT,即得到基数为2的FFT运算,使得DFT所需复数乘法次数降至。

4.2蝶形运算

       基于2FFT的蝶形运算流图如下图4-1所示,图中以N=8为例。

图4-1

     蝶形运算采用的是原位运算原理和倒位序规律,其中原位运算指的是某一列的N个数据送到存储器后,经蝶形运算,其结果为下一列数据,它们以蝶形为单位仍存储在这同一组存储器中,直到最后输出,中间无需其他存储器。也就是蝶形的两个输出值仍放回蝶形的两个输入所在的存储器中。 每列的N/2 个蝶形运算全部完成后, 再开始下一列的蝶形运算。而倒位序规律则是根据二进制的自然顺序和倒序得来的,其变换规律如图4-2。

   

自然顺序(I) 

二进制数 

倒位序二进制数 

倒位序(J) 

0

1

2

3

4

5

6

7

000

001

010

011

100

101

110

111

000

100

010

110

001

101

011

111

0

4

2

6

1

5

3

7

                                图4-2

5 各部分程序设计

5.1初始化程序

 #include"math.h"                 //数学函数头文件

#define PI 3.1415926

#define N 128              //采样次数N     

void InitForFFT();           //FFT初始化函数

void MakeWave();           //波形发生函数

void finv(int N1,float *xr,float *xi);    //倒序运算函数,对输入序倒序

int INPUT[N],DATA[N];           

float fWaveR[N],fWaveI[N],w[N];

float sin_tab[N],cos_tab[N];       //正余弦函数表

     int Mum;                      //蝶形运算的级数

这一段初始化程序是对需要用到数据和子程序段进行定义,其中包含了采样的次数、蝶形运算的级数,倒序运算正余弦函数表。

5.2 FFT的蝶形运算

     在进行蝶形变换的时候,其中还引入了倒序运算函数,即运算完之后的FFT的输出X(k)按正常顺序排列在存储单元中,即按X(0),X(1),…,X(7)的顺序排列,但是这时输入x(n)却不是按自然顺序存储的,而是按x(0),x(4), …, x(7)的顺序存入存储单元,看起来好像是“混乱无序”的,实际上是根据倒序原理:倒序数的加1是在最高位加1,满2向次高位进1,最高位变0,依次往下,从当前的倒序值求出下一个倒序值。

其蝶形变换的主要程序如下:

     for(k=j;k<=N-1;k+=(int)(pow(2,m)+0.5))

     {

     X=Xr[k+B]*cos_tab[S]+Xi[k+B]*sin_tab[S];

     Y=Xi[k+B]*cos_tab[S]-Xr[k+B]*sin_tab[S];

     Xr[k+B]=Xr[k] -X;         //运算结果的实部和虚部分别存储在原

      Xi[k+B]=Xi[k]-Y;        //实部和虚部的位置 

  Xr[k]=Xr[k]+X;

     Xi[k]=Xi[k]+Y;

     }

 蝶形运算展开采用的是原位运算,即某一列的N个数据送到存储器后,经蝶形运算,其结果为下一列数据,它们以蝶形为单位仍存储在这同一组存储器中,直到最后输出,中间无需其他存储器。也就是蝶形的两个输出值仍放回蝶形的两个输入所在的存储器中。 每列的N/2 个蝶形运算全部完成后, 再开始下一列的蝶形运算。 这样存储器数据只需N个存储单元。 下一级的运算仍采用这种原位方式,只不过进入蝶形结的组合关系有所不同。  

5.3 旋转因子    

     旋转因子是复数,可表示为:

        (4-1)          

由式(4-1)可以看出旋转因子的实部为余弦函数,虚部为正弦函数。为了获得FFT运算中需要的全部旋转因子,需要分别存储正弦表和余弦表,且每个表长度为,对应于0°~180°,同时,采用循环寻址方式对正弦表和余弦表进行寻址。

5.4 实现N点的FFT变换

实现N点的FFT变换需要知道采样的次数N值,且N为2的整数幂次方;同时还需要旋转因子的幂数,蝶形运算输入数据的距离,即各级旋转因子的个数;然后再进行蝶形运算,展开结果的实部和虚部分别存储在原实部和虚部位置。

FFT变换的程序如下:

     void FFT(float Xr[N],float Xi[N])

{

int S,B;

int m,j,k;

float X,Y;

finv(N,Xr,Xi);

for(m=1;m<=Mum;m++)

{

  

B=(int)(pow(2,m-1)+0.5);

for(j=0;j   {

    S=j*(int)(pow(2,Mum-m)+0.5);

for(k=j;k<=N-1;k+=(int)(pow(2,m)+0.5))

     {

     X=Xr[k+B]*cos_tab[S]+Xi[k+B]*sin_tab[S];

     Y=Xi[k+B]*cos_tab[S]-Xr[k+B]*sin_tab[S];

     Xr[k+B]=Xr[k]-X;

     Xi[k+B]=Xi[k]-Y;

     Xr[k]=Xr[k]+X;

     Xi[k]=Xi[k]+Y;

     }

    }

   }

for(m=0;m<=N/2;m++)

{

   w[m]=sqrt(Xr[m]*Xr[m]+Xi[m]*Xi[m]);   //功率谱计算

}

}

在FFT变换的后,经过功率谱计算公式得到频率域的频谱,根据输入信号的不同,得到不同的频谱图,当输入信号为正弦波时,输出的DATA数据如图5-1,当输入的信号为方波时,输出的DATA数据序列见图5-2。

                              图5-1

                             图5-2

5.5 倒序运算部分的设计

     倒序运算是在FFT变换的蝶形运算开始之前调用的,其作用是不断的从当

前的倒序值算出下一个倒序值,同时把输入序列的实部和虚部按顺序调换,是程序中需要仔细去理解的部分,其函数代码如下:

      void finv(int N1,float *xr,float *xi)

{  

   int m,n,N2,k;

   float T;

   N2=N1/2;

   n=N2;

for(m=1;m<=N1-2;m++)

   {

if(m  {

   T=xr[m];xr[m]=xr[n];xr[n]=T;

   T=xi[m];xi[m]=xi[n];xi[n]=T;

  }

   k=N2;

while(n>=k)

   {

    n=n-k;

    k=(int)(k/2+0.5);

   }

    n=n+k;

   }

5.6正余弦函数表与波形发生部分的设计

     正余弦函数表的建立的目的是为蝶形变换公式中的正余弦序列提供原始数据依据,而波形发生器的作用则是为整个程序提供一个输入序列,使系统根据输入的波形来得到与之相对应的频谱。

正余弦函数表的程序代码为:

  void InitForFFT()

{

  int i;

for(i=0;i{

  sin_tab[i]=sin(PI*2*i/N);

 

 cos_tab[i]=cos(PI*2*i/N);

}

  }

至于波形发生的输入,我用了正弦波和方波两种波形作为互相对比的输入,正弦波为f=3Hz的序列:

    void MakeWave()

{

 int i;

for(i=0;i{

 INPUT[i]=sin(PI*2*i/N*3)*1024;

}

  }

与之相对应的正弦波序列的地址为0X3F90C0,波形图如图5-3所示。

                             图5-3

若以方波作为输入,除了多定义两个变量之外则是把for循环里的语句变为

j=(int)i/20; 

l=j%2;

   if(l==0)INPUT[i]=1024;

  else INPUT[i]=0;即可。

其方波的波形图见图5-4

                           图5-4

5.7主函数部分的设计

 主函数main()是每一个C语言程序中有且仅有的一个重要部分,是一个程序的入口点,可以根据自己的爱好把它放在程序的某个地方。有些程序员把它放在最前面,而另一些程序员把它放在最后面, 无论放在哪个地方。

本次程序的主函数main()为:

   main()

{

int i;

InitForFFT();

MakeWave();

for(i=0;i {

   fWaveR[i]=INPUT[i];

   fWaveI[i]=0.0;w[i]=0.0;

 }

Mum=(int)(0.5+log(N)/log(2));

FFT(fWaveR,fWaveI); 

for(i=0;iwhile(1);

}

5 工作过程分析

     根据此次课程设计的要求,为了实现快速傅里叶变换的目的,首先要做的就是让程序有相应的输入信号,为此,在程序中,我用了正弦波和方波两种波形作为输入以形成对比。再然后就是调用程序各部分分支以实现相应功能,最终达到FFT变换的目的。

首先,需要对程序用到的函数进行定义,这部分就是程序是初始化阶段。由于傅里叶变换是一种数算,所以对文件类型进行定义为数学函数头文件,再来就是程序用了不少的函数,就定义了波形发生函数、倒序运算函数、FFT初始化函数和正余弦函数表等变量。

然后,进行main()主函数部分,这是程序的主干、入口,主函数内调用子程序的顺序决定了各部分子程序运行的顺序,所以这部分是很重要的,要根据实际FFT变换的顺序来编写。

再来,就是对各个子程序的编写,这些程序分支就包含FFT初始化函数,顺利建立正余弦函数表,倒序运算函数,波形发生及蝶形运算展开并计算功率谱。

最后,就是根据输入输出程序代码段的地址,通过CCS软件调试该程序,并用其中View→Graph→Time/Frequency菜单功能,改变相应的起始地址和显示方式显示输入变量INPUT与输出结果DATA图形,观察FFT的效果。

 其中,输入变量INPUT的起始地址为0X3F90C0,输出结果DATA的地址为0X3F9040,这样就能显示输入输出图形,顺利观察FFT的效果。

小 结

通过本次的DSP课程设计,让我对这个工作原理以及它编程的调试方法都类似于单片机的学科有了更好的掌握与理解。与次同时,通过此次的课程设计也让我充分地认识到了我在学习这门课程中存在的不足之处。

当刚知道有DSP这门学科的程序设计时,首先闯入我脑中的信息便是这次的课设肯定不好做,但是,在真正的知道了我要设计的题目在树上能找到类似的程序时,我就知道我把问题给想复杂了。在整个的设计过程中,我需要做的就是把书上的程序看明白,然后再对其中的某些部分进行小小的修改,算起来,我做的设计算是三组里最简单的一个了,程序调试很快,波形除了一些瑕疵之外算得上是不错了。这其中出现的主要问题便是对主函数调用的子程序内容,特别是蝶形运算那部分,是这次设计的瓶颈 ,经过几天的研究与讨论,让我对以前学的懵懵懂懂的FFT变换有了更深层次的了解,特别是蝶形图展开及倒序运算部分。             

 通过这次的课程设计让我学到了很多平时课上学不到的东西,让我知道了哪些方面是自己的薄弱环节,自己需要在哪些方面下一些功夫。更重要的是能过这次的实验我熟练地掌握了程序的调试,知道如果一个程序运进的时候出现了问题应该怎样找到错误并改正,也知道了该如何去利用图书馆和网络上的资源,这对我们以后的课程设计和学习是有很大帮助的。

总之课程设计对我们的帮助是非常大的,在提高我们的动手能力和开发逻辑思维之于也能在一定的程度上激发我们对DSP这门课学习的兴趣。让我们的学习从单一的理论升华到实践的高度。

    很庆幸能有这样一次课设的机会,让我学尝试了理论与实际的结合,让我学到了很多知识,让我认识到了自己的不足,知道光是掌握了理论知识并不代表什么,必须还要与实际结合才能更好的理解这些知道,使之掌握得更牢靠。在以后的学习生涯中我会不断的完善自己,用更多知识来丰富和充实自己,为自己以后的人生道路打下坚实的基础!

致谢

短短一周的DSP课程设计就这样结束了,很感谢吕老师和雷老师在这一周里给我带来的帮助和指导。还要感谢在这一周时间里与我共同讨论和交流的同学,感谢他们的细心指导和他们提出的疑点。吕老师在软件方面很厉害,只要遇到跟程序里遇到的问题就可以找他,老师肯定会尽责为我们讲解,而雷老师因为本来就是数字信号处理的任课老师,在傅里叶变换这个设计中,必然会有很多请教雷老师的时候,他都会细心地为我们一一解惑,知道我们听懂为止。

还要感谢为我提供课设资料的学校图书馆,图书馆的大量书籍为我提供了广阔的资源,让我熟悉掌握了定时器、外部中断的工作原理,由其是液晶显示器的原理开启方法,对我的帮助非常大。仅仅运用课堂所学的知识,靠自己的力量要在两周之内完成设计的题目是难以想象的。所以这两周我能够顺利地完成课设任务有很大一部分原因是大家的帮助和图书的资源。

再次,我要感谢指导老师和与我同一个课设题目的同学,因为一个人的思路是有限的,感谢大家的集思成就了我们的妙想。

最后感谢学校给我们提供这种自主研究性学习的机会,充分开发了我们的创新能力。每个学期的课程设计都让我学会了很多,让我对学习充满浓厚的兴趣!

              

参考文献

[1] 姜沫岐,许涵,俞鹏,段国强 . DSP原理与应用—从入门到提高. 北京: 机械工业出版社, 2007

[2] 李行一. 数字信号处理.重庆大学出版社,2002

[3] 张东亮. DSP控制器原理与应用.机械工业出版社,2011

[4] 李哲英. DSP基本理论与应用系统设计.北京航空航天大学出版社,2002

[5] 俞卞章.  数字信号处理. 西安: 西北工业大学出版社, 2002

[6] 王安国.  数字信号处理基础.北京: 电子工业出版社, 2003

[7] 何苏秦,王忠勇. TMS320C2000DSP原理及实用技术.电子工业出版社,2003

附录A1  程序清单

#include"math.h"

#define PI 3.1415926

#define N 128

void InitForFFT();

void MakeWave();

void finv(int N1,float *xr,float *xi);

int INPUT[N],DATA[N];

float fWaveR[N],fWaveI[N],w[N];

float sin_tab[N],cos_tab[N];

int Mum;

void FFT(float Xr[N],float Xi[N])

{

int S,B;

int m,j,k;

float X,Y;

finv(N,Xr,Xi);

for(m=1;m<=Mum;m++)

{

B=(int)(pow(2,m-1)+0.5);

for(j=0;j   {

    S=j*(int)(pow(2,Mum-m)+0.5);

for(k=j;k<=N-1;k+=(int)(pow(2,m)+0.5))

     {

     X=Xr[k+B]*cos_tab[S]+Xi[k+B]*sin_tab[S];

     Y=Xi[k+B]*cos_tab[S]-Xr[k+B]*sin_tab[S];

     Xr[k+B]=Xr[k]-X;

     Xi[k+B]=Xi[k]-Y;

     Xr[k]=Xr[k]+X;

     Xi[k]=Xi[k]+Y;

     }

    }

   }

for(m=0;m<=N/2;m++)

{

   w[m]=sqrt(Xr[m]*Xr[m]+Xi[m]*Xi[m]);

}

}

main()

{

int i;

InitForFFT();

MakeWave();

for(i=0;i {

   fWaveR[i]=INPUT[i];

   fWaveI[i]=0.0;

   w[i]=0.0;

 }

Mum=(int)(0.5+log(N)/log(2));

FFT(fWaveR,fWaveI); 

for(i=0;iwhile(1);

}

void InitForFFT()

{

  int i;

for(i=0;i{

  sin_tab[i]=sin(PI*2*i/N);

  cos_tab[i]=cos(PI*2*i/N);

}

  }

void MakeWave()

{

 int i;int j;int l;

for(i=0;i{j=(int)i/20;  

 l=j%2;

  if(l==0)INPUT[i]=1024;

  else INPUT[i]=0;}

  }

void finv(int N1,float *xr,float *xi)

{  

   int m,n,N2,k;

   float T;

   N2=N1/2;

   n=N2;

for(m=1;m<=N1-2;m++)

{

if(m  {

   T=xr[m];xr[m]=xr[n];xr[n]=T;

   T=xi[m];xi[m]=xi[n];xi[n]=T;

  }

   k=N2;

while(n>=k)

   {

    n=n-k;

    k=(int)(k/2+0.5);

   }

    n=n+k;

   }

 }

                  

目录

中文摘要    1

1 设计任务描述    2

2 设计思路    3

3.设计方框图    4

4 快速傅里叶变换的的算法实现    5

4.1变换原理    5

4.2蝶形运算    6

5 各部分程序设计    7

5.1初始化程序    7

5.2 FFT的蝶形运算    7

5.3 旋转因子    8

5.4 实现N点的FFT变换    8

5.5 倒序运算部分的设计    10

5.6正余弦函数表与波形发生部分的设计    11

5.7主函数部分的设计    12

5 工作过程分析    13

小 结    14

致谢    15

参考文献    16

附录A1  程序清单    17下载本文

显示全文
专题