从Matlab平台进行FFT到ARM平台C语言FFT频谱分析

Posted “逛丢一只鞋”

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Matlab平台进行FFT到ARM平台C语言FFT频谱分析相关的知识,希望对你有一定的参考价值。

Matlab FFT 时域、频域分析

FFT是离散傅立叶变换的快速算法,可以将一个信号变换到频域。有些信号在时域上是很难看出什么特征的,但是如果变换到频域之后,就很容易看出特征了。这就是很多信号分析采用FFT变换的原因。另外,FFT可以将一个信号的频谱提取出来,这在频谱分析方面也是经常用的。

虽然很多人都知道FFT是什么,可以用来做什么,怎么去做,但是却不知道FFT之后的结果是什意思、如何决定要使用多少点来做FFT。

现在就根据实际经验来说说FFT结果的具体物理意义。一个模拟信号,经过ADC采样之后,就变成了数字信号。采样定理告诉我们,采样频率要大于信号频率的两倍,这些我就不在此罗嗦了。

采样得到的数字信号,就可以做FFT变换了。N个采样点,经过FFT之后,就可以得到N个点的FFT结果。为了方便进行FFT运算,通常N取2的整数次方

信号频率分析

假设采样频率为Fs,信号频率f,采样点数(序列长度)为N。那么FFT之后结果就是一个为N点的复数。每一个点就对应着一个频率点。 这个点的模值,就是该频率值下的幅度特性。具体跟原始信号的幅度有什么关系呢?

% 生成一个自定义正弦波+噪声信号,然后进行FFT运算

clc;close all;clear all;

Fs=8192;                %采样频率
T=1/Fs;  
N=8192;                 %序列长度
f1=400;                 
f2=1200;
f3=2400;
t=(0:1:N-1)*T;  
y=1+2*sin(2*pi*f1*t)+3*sin(2*pi*f2*t+pi/2)+5*sin(2*pi*f3*t+pi/4);  %生成输入信号
figure(1)
plot(t, y);
% filename = 'sine.wav';
% audiowrite(filename,y,Fs);

Y = fft(y, N); %作FFT变换
A = abs(Y); %计算幅值
figure(2)
plot(0:1:(N-1), A);title('Matlab N=1024');xlabel('N');ylabel('Amplitude');%作图

假设原始信号的峰值为A,那么FFT的结果的每个点(除了第一个点直流分量之外)的模值就是A的N/2倍。而第一个点就是直流分量,它的模值就是直流分量的N倍。由图可看出频谱图中第一个点的幅值为直流分量1的8192倍。

而每个点的相位呢,就是在该频率下的信号的相位。第一个点表示直流分量(即0Hz),而最后一个点N的再下一个点(实际上这个点是不存在的,这里是假设的第N+1个点,也可以看做是将第一个点分做两半分,另一半移到最后)则表示采样频率Fs,这中间被N-1个点平均分成N等份,每个点的频率依次增加。

例如某点n所表示的频率为:Fn=(n-1)*Fs/N。

由上面的公式可以看出,Fn所能分辨到频率为为Fs/N,如果采样频率Fs为1024Hz,采样点数为1024点,则可以分辨到1Hz。 1024Hz的采样率采样1024点,刚好是1秒,也就是说,采样1秒时间的信号并做FFT,则结果可以分析到1Hz,如果采样2秒时间的信号并做FFT,则结果可以分析到0.5Hz。

如果要提高频率分辨力,则必须增加采样点数,也即采样时间。

频率分辨率和采样时间

频率分辨率和采样时间是倒数关系。

clc;close all;clear all;

Fs=48000;                   %采样频率 即频率分辨率
N=48000;                %采样点数
f1=50;                 
f2=750;
T=1/Fs;                     %采样时间
t=(0:1:N-1)*T;  
% y=2+3*sin(2*pi*f1*t-pi*30/180)+1.5*sin(2*pi*f2*t+pi*90/180);  %生成输入信号
y=0.1*sin(2*pi*f1*t)+0.1*sin(2*pi*f2*t);
figure(1)
plot(t, y);
filename = 'sine.wav';
audiowrite(filename, y, Fs);

Y = fft(y, N);                                                %作FFT变换
A = abs(Y);                                                %计算幅值
figure(2)
plot(0:1:(N-1), A);title('Matlab N=256');xlabel('N');ylabel('Amplitude');%作图
grid on

通过一个例子可以比较清晰的验证,若采样频率Fs=48k采样点N=48k数值一致。

对于正弦分量 0.1* sin(2* pi* f2* t)中的 t 含义如下

t = 时间序号(即采样点序号)* 采样时间间隔 = n* T = n/Fs = 采样点序号 / 采样频率

所以,总采样时间t = 48k/48k = 1s,即实现采1秒钟数据

此时对于采样频率 采样点在频谱图中的反应又会发生什么变换呢?

采样间隔和采样频率既然是一一对应,,那么原本第一个时域采样点对应频域第一个采样频率,依然是第一个时域采样点对应第一个频域采样频率

有了上面的详细分析,现在继续套用之前的公式来理解,某点n所表示的频率为:Fn = ( n - 1 ) * Fs / N

即采1秒钟数据,分辨率达到1Hz。

采样频率Fs=48k,而采样点N=48k * 2,因为,采样时间间隔是T= 1/Fs,也就是每隔T就会在频谱图中对应时域的对应采样点的一个频率值

对于正弦分量 0.1* sin(2* pi* f2* t)中的 t 含义如下

t = 时间序号(即采样点序号)* 采样时间间隔 = n* T = n/Fs = 采样点序号 / 采样频率

所以,总采样时间t = 2s,即实现采2秒钟数据

此时对于采样频率 采样点在频谱图中的反应又会发生什么变换呢?

采样间隔从上面1秒钟采样的例子中的 t = n / 48k n=[0, 48k],变成了 T = n / 48k n=[0, 48k*2],采样间隔增加了一倍,那么原本第一个时域采样点对应频域第一个采样频率,现在变成了第一个时域采样点对应第二个频域采样频率

有了上面的详细分析,现在继续套用之前的公式来理解,某点n所表示的频率为:Fn = ( n - 1 ) * Fs / N

采样点

在上面的信号分析中,只提到了时域中信号的采样频率,采样序列,对于fft中一个很重要的概念——采样点,其实没有说明。

对于fft的采样点,要和我们生成时域中正弦信号中的一些概念区别开来, fft的采样点,具体来说就是fft变换时实际参与的采样序列的数量

因为我们上面的程序中信号和分析都是自己处理,所以就生成了多少点,然后就对多少点进行了fft

为了方便进行FFT运算,这个采样点通常N取2的整数次方

如何决定要使用多少点来做FFT?

在实际中,我们假设实际的信号长度为48000* 10,那么我们fft其实没有必要取全部的点

首先,fft函数出来的应该是个复数,每一个点分实部虚部两部分。假设采用1024点fft,采样频率是fs,那么第一个点对应0频率点,第512点对应的就是 512 * fs/1024 = fs / 2 的频率点。

FFT点数N(也就是离散时间信号的记录长度):要根据所要求的的频率分辨率Fn来决定

频率分辨率F:能够用FFT算法分析得到的最靠近的两个信号频率之间的间隔F=Fs/N,也就是最小是能分辨到1还是0.5还是其他,1就代表时域中序列的一个点对应频域中序列的一个点,0.5就代表时域中序列的一个点对应频域中序列的两个点,显然后者在频域中的精度更加的高,也就是频率的分辨率更加的高。

对于采样点N和频率分辨率F的关系:N >= Fs / F, Fs为采样频率。

由于FFT一般要求N是2的整数幂,所以最后还要把FFT的采样点N扩大为最接近2的整数幂

实例FFT采样点分析

还是用一个实例来进行说明

首先也是生成一个y=2+6*sin(2*pi*f1*t-pi*30/180)+2*sin(2*pi*f2*t+pi*90/180)+2*sin(2*pi*f3*t+pi*60/180);作为输入信号

其中这个输入信号的采样频率Fs是48k,序列的长度N是48k*2

FFT采样点的选择根据上面的分析 N >= Fs / F,如果我们需要的频率分辨率为10,那么也就是采样点N大于等于48k即可,这里我们选择采样点N为8192。

上述分析具体到matlab程序中参数名称有所变化,请注意!

clc;close all;clear all;

M=8192;%fft采样点
Fs=48000; %采样频率,一秒多少个采样点
N=48000*2; %序列长度,总数据有多少个点,即对N个点FFT
f1=50;                 
f2=750;
f3=1500;
T=1/Fs; %单个点采样时间
t=(0:1:N-1)*T; %总时间T/Fs,每个点时间间隔t 
y=2+6*sin(2*pi*f1*t-pi*30/180)+2*sin(2*pi*f2*t+pi*90/180)+2*sin(2*pi*f3*t+pi*60/180);  %生成输入信号
% y=0.1*sin(2*pi*f1*t+pi/3)+0.2*sin(2*pi*f2*t-pi/6);
figure(1)
plot(t, y);
% 存储信号到wav文件
% filename = 'sine.wav';
% audiowrite(filename, y, Fs);

% Matlab FFT
Y = fft(y, M); %作FFT变换
A = abs(Y); %计算幅值
figure(2)
% plot(0:1:(N-1)/2, A(1:N/2));
plot(0:1:M-1, A(1:M));title('Matlab N=8192');xlabel('N');ylabel('Amplitude');%作图
grid on


图中可以比清晰的看到,这次我们的fft点数为8192个

信号幅度分析

假设FFT之后某点n用复数a+bi表示,那么这个复数的模就是An=根号aa+bb,相位就是Pn=atan2(b,a)。根据以上的结果,就可以计算出n点(n≠1,且n<=N/2)对应的信号的表达式为:

An / (N/ 2) * cos(2 * pi* Fn* t+Pn),即 2* An/N* cos(2* pi* Fn* t+ Pn)。

对于n=1点的信号,是直流分量,幅度即为A1/N。

由于FFT结果的对称性,通常我们只使用前半部分的结果,即小于采样频率一半的结果

下面以一个实际的信号来详细做说明。

假设我们有一个信号,它含有2V的直流分量,频率为50Hz、相位为-30度、幅度为3V的交流信号,以及一个频率为75Hz、相位为90度、幅度为1.5V的交流信号。用数学表达式就是如下:

y=2+3*cos(2*pi*f1*t-pi*30/180)+1.5*cos(2*pi*f2*t+pi*90/180);

式中cos参数为弧度,所以-30度和90度要分别换算成弧度。

我们以256Hz的采样率对这个信号进行采样,总共采样256点。按照我们上面的分析,Fn=(n-1)*Fs/N,我们可以知道,每两个点之间的间距就是1Hz,第n个点的频率就是n-1。

clc;close all;clear all;

Fs=256;              
T=1/Fs;          
N=256; 
f1=50;                 
f2=75;
t=(0:1:N-1)*T;  
y=2+3*cos(2*pi*f1*t-pi*30/180)+1.5*cos(2*pi*f2*t+pi*90/180);  %生成输入信号
figure(1)
plot(t, y);
% filename = 'sine.wav';
% audiowrite(filename, y, Fs);

Y = fft(y, N);                                                %作FFT变换
A = abs(Y);                                                %计算幅值
figure(2)
plot(0:1:(N-1), A);title('Matlab N=256');xlabel('N');ylabel('Amplitude');%作图
grid on

我们的信号有3个频率:0Hz、50Hz、75Hz,应该分别在第0个点、第50个点、第75个点上出现峰值,其它各点应该接近0。


实际情况如何呢?我们来看看FFT的结果的模值如图所示。

幅度分析

从图中我们可以看到,在第0点、第50点、和第75点附近有比较大的值(注意:因为plot作图习惯的原因,把第一个点定义为了0点)。我们分别将这三个点附近的数据拿上来细看:

0点: 5.119999999999999e+02 + 0.000000000000000e+00i

1点: -2.596032278819034e-14 - 1.428167681322866e-13i

2点: -2.797762022055395e-14 - 1.407762795224699e-13i

49点:-1.249148148486610e-12 + 6.534772133137322e-13i

50点:-6.205653671318325e-13 - 2.170294576875076e-12i

51点:3.325537550532247e+02 - 1.919999999999996e+02i

74点:8.230832814121277e-14 - 4.724619872104589e-13i

75点:-2.185335025085591e-13 - 1.015196857708225e-12i

76点:3.439777178218570e-12 + 1.919999999999998e+02i

很明显,0点、50点、75点的值都比较大,它附近的点值都很小,可以认为是0,即在那些频率点上的信号幅度为0。接着,我们来计算各点的幅度值。分别计算这三个点的模值,结果如下:

0点: 512

50点:384

75点:192

按照公式,可以逆向计算,计算出时域中

直流分量为:

512/N = 512/256 = 2

50Hz信号的幅度为:

384/(N/2) = 384/(256/2) = 3

75Hz信号的幅度为

192/(N/2) = 192/(256/2) = 1.5

可见,对照时域的幅度,从频谱分析出来的幅度是正确的。

然后再来计算相位信息。直流信号没有相位可言,不用管它。

先计算50Hz信号的相位,atan2(-192, 332.55)=-0.5236,结果是弧度,换算为角度就是180*(-0.5236)/pi=-30.0001。

再计算75Hz信号的相位,atan2(192, 3.4315E-12)=1.5708弧度,换算成角度就是180*1.5708/pi=90.0002。

可见,相位也是对的。根据FFT结果以及上面的分析计算,我们就可以写出信号的表达式了,它就是我们开始提供的信号。

FFT总结: 假设采样频率为Fs,采样点数为N,做FFT之后

  1. FFT后某一点n(n从1开始)表示的频率为:Fn=(n-1)*Fs/N;
  2. 该点的模值除以N/2就是对应该频率下的信号的幅度(对于直流信号是除以N);
  3. 该点的相位即是对应该频率下的信号的相位。相位的计算可用函数atan2(b,a)计算。atan2(b,a)是求坐标为(a,b)点的角度值,范围从-pi到pi。

要精确到xHz,则需要采样长度为1/x秒的信号,并做FFT。

要提高频率分辨率,就需要增加采样点数,这在一些实际的应用中是不现实的,需要在较短的时间内完成分析。解决这个问题的方法有频率细分法,比较简单的方法是采样比较短时间的信号,然后在后面补充一定数量的0,使其长度达到需要的点数,再做FFT,这在一定程度上能够提高频率分辨力。具体的频率细分法可参考相关文献。

ARM FFT 时域、频域分析

Matlab有了原理的验证和解释,下面就是移植到ARM平台进行验证

依然是与上例中各项参数保持一致

#include "stdio.h"
#include "math.h"

#define PI 3.1415926535


void kfft(double pr[], double pi[], int n, int k, double fr[], double fi[])
{ 
	int it, m, is, i, j, nv, l0;
    double p, q, s, vr, vi, poddr, poddi;
    for (it = 0; it <= n-1; it++) { //将pr[0]和pi[0]循环赋值给fr[]和fi[]
     
		m = it; 
		is = 0;
		for(i = 0; i <= k-1; i++) { 
			j = m / 2; 
			is = 2 * is + (m - 2 * j); 
			m = j;
		}
        fr[it] = pr[is]; 
        fi[it] = pi[is];
    }
    pr[0] = 1.0; 
    pi[0] = 0.0;
    p = 6.283185306 / (1.0 * n);
    pr[1] = cos(p); //将w=e^-j2pi/n用欧拉公式表示
    pi[1] = -sin(p);

    for(i = 2; i <= n-1; i++) { //计算pr[]
     
		p = pr[i - 1] * pr[1]; 
		q = pi[i - 1] * pi[1];
		s = (pr[i - 1] + pi[i - 1]) * (pr[1] + pi[1]);
		pr[i] = p - q; 
        pi[i] = s - p - q;
    }
    for(it = 0; it <= n-2; it = it + 2) { 
    
		vr = fr[it]; 
		vi = fi[it];
		fr[it] = vr + fr[it + 1]; 
		fi[it] = vi + fi[it + 1];
		fr[it + 1] = vr - fr[it + 1]; 
		fi[it + 1] = vi - fi[it + 1];
    }
	m = n / 2; 
	nv = 2;
    for(l0 = k - 2; l0 >= 0; l0--) { //蝴蝶操作
     
		m = m / 2; 
		nv = 2 * nv;
        for(it = 0; it <= (m - 1) * nv; it = it + nv)
            for (j = 0; j <= (nv / 2) - 1; j++) { 
            
				p = pr[m * j] * fr[it +j + nv / 2];
				q = pi[m * j] * fi[it +j + nv / 2];
				s = pr[m * j] + pi[m * j];
				s = s * (fr[it + j + nv / 2] + fi[it + j + nv / 2]);
				poddr = p - q; 
				poddi = s - p - q;
				fr[it + j + nv / 2] = fr[it + j] - poddr;
				fi[it + j + nv / 2] = fi[it + j]

以上是关于从Matlab平台进行FFT到ARM平台C语言FFT频谱分析的主要内容,如果未能解决你的问题,请参考以下文章

从Matlab谐波失真仿真到C语言谐波失真应用

ARM裸机开发:C语言点亮LED

选带快速傅立叶变换ZOOM-FFT的matlab实现

移植 libevent-2.0.22-stable 到ARM平台

Windows下通过ARM目标板上的gdbserver进行远程调试的方法

如何学习ARM平台的嵌入式