完整版请参考:
- https://mazhaoxin.github.io/2020/01/09/About_FFT/
- http://483v7j.coding-pages.com/2020/01/09/About_FFT/
傅立叶变换给我们了一种新的视角来看待世界,今天从应用的角度简单总结一下其使用上需要注意的地方。
我们都知道,对于周期性的信号,其傅立叶变换结果为离散值,称为傅立叶级数,即任何周期信号都可以被看做不同系数的正余弦信号相加的结果。傅立叶变换的结果表征了信号在频域的特征,因此说傅立叶变换把时域信号转换为了频域信号。如果只看频域信号的幅度信息,即为信号的频谱(spectrum)。通过观察频谱,可以很方便的判断信号中各频率成分的大小。
傅立叶级数中存在关系:信号的周期与前面所述的正余弦信号的最低频率(基频)呈反比。当信号的周期趋于无穷大时,基频趋于零。由于可以把非周期信号看作周期为无穷大的信号,因此非周期信号的傅立叶变换结果为连续值。
而在实际应用中,需要通过采样来获取信号的数值,因此离散时间域傅立叶变换(DFT)更具有实用价值。至于计算方法,将连续时间域的傅立叶变换中的积分
换成累加
即可得到离散时间域的傅立叶变换公式。另外由于采样时所用的冲击函数会使频谱出现周期延拓,需要注意一般情况下采样频率不能低于被采样信号截止频率的一半(奈奎斯特采样定理)。
由于DFT的计算过程中需要很多次计算(对于长度为 N N N的复数信号,每个采样点要进行 N N N次复数乘法和 N − 1 N-1 N−1次复数加法),基2的快速傅立叶变换(FFT)算法就被发明了出来。FFT的核心思想是分段(一分为二)计算,组合输出。这种算法可以在点数较多时大大减少计算量,但要求采样点数必须是2的整数次幂。
当被采样信号中有明显的周期信号,且做FFT的部分没有包含完整的周期时,由于其首尾处的相位不连续导致信号频谱中各谱线会相互影响,最终使结果失真。
如果被采样信号比较简单,可以观察到明显的周期,那么选取合适的采样范围是避免频谱泄漏的最佳方案。
否则需要通过加窗函数的方式减弱首尾处的幅度。而时域上的相乘对应了频域上的卷积,因此加窗函数会使频谱出现旁瓣,在窗函数的选择上需要注意,常用的窗函数有Hanning、Hamming等。另外,在计算功率时注意归一化问题。
由于频谱的周期延拓,当采样频率不是被采样信号频率的整倍数时,会在频谱上出现一些混叠信号。当观察有高功率谐波的周期性信号且采样频率不够高时尤其明显,会影响对频谱纯净度的判断。
解决办法是增加采样频率(即在频谱精度和频谱范围之间折中)或选择合适的采样频率。
例如,当需要观察一个频率为 f c l k f_{clk} fclk的时钟信号的频谱时,如果既不想看到频谱泄漏又不想看到信号混叠,需要如何选择采样参数?
- 时钟周期 T c l k T_{clk} Tclk,信号最大长度 t m a x t_{max} tmax,做FFT的部分信号长度 t s i g n a l t_{signal} tsignal,采样周期 T s T_s Ts,采样频率 f s f_s fs,采样点数 N N N;
- 为了不出现频谱泄漏,要求 K 1 = t s i g n a l / T c l k K_1 = t_{signal}/T_{clk} K1=tsignal/Tclk为整数;
- 为了不出现信号混叠,要求 K 2 = f s / f c l k = T c l k / t s = N ∗ T c l k / t s i g n a l = N / K 1 K_2 = f_s/f_{clk} = T_{clk}/t_s = N*T_{clk}/t_{signal} = N/K_1 K2=fs/fclk=Tclk/ts=N∗Tclk/tsignal=N/K1也要为整数;
- 由于FFT要求 N N N为2的整数次幂,即 N N N的素因子只有2,那么 K 1 K_1 K1与 K 2 K_2 K2也均为为2的整数次幂;
- 所以, t s i g n a l = T c l k ⋅ 2 f l o o r ( log 2 ( t m a x / T c l k ) ) t_{signal} = T_{clk} \cdot 2^{floor(\log_{2}{(t_{max}/T_{clk}}))} tsignal=Tclk⋅2floor(log2(tmax/Tclk)),而 N N N取尽量的大即可。
一般情况下,FFT的结果充满了由于各种因素导致的噪声,表现在图表上即为波动范围很大的谱线,为了得到更加准确(平滑)的结果还需要对其进行降噪。
比较常用的是Welch方法(MATLAB中有pwelch函数可以调用,输出的结果是PSD)。对于总长度为 N N N的信号,每次取 N F F T N_{FFT} NFFT个点为一段,加窗函数后做FFT,相邻两段之间重复取 N o v e r l a p N_{overlap} Noverlap个点(一般取 N F F T N_{FFT} NFFT的33~50%,当然也可以为0),最后对每段的结果求平均作为最终结果。
下面举例说明MATLAB中pwelch函数的使用方法:
function showPSD(w, fs)
% by:MaZhaoxin @2015/6/17
if nargin<2
fs = 1;
end
% 截取w,以保证其长度满足FFT的需求
L = 2^nextpow2(length(w));
if L~=length(w)
fprintf('\t***Warning: Samples will be cut(%d -> %d).\n', length(w), L/2);
L = 2^(nextpow2(length(w))-1);
w = w(end-L+1:end);
end
% 共计分为7段
NFFT = L/4;
[p, f] = pwelch(w, hann(NFFT), NFFT/2, NFFT, fs, 'twosided');
% 处理结果,将频率映射到-fs/2 ~ fs/2
p = fftshift(p);
f = fftshift(f);
f(f>=fs/2) = f(f>=fs/2)-fs;
% 转换为dB(pwelch输出为PSD,因此是10倍log10)
pdb = 10*log10(p);
plot(f, pdb);
grid on;
在Cadence的计算器中,可以使用dft函数完成类似的操作,并且它也提供了分段、平均的选项来平滑输出结果,在此不再赘述。