FFT相位测量基本原理
快速傅里叶变换(FFT)可将时域信号转换为频域表示,包含幅度谱和相位谱。相位信息反映了信号各频率成分的初始相位角,计算公式为: [ \phi(k) = \tan^{-1}\left(\frac{\text{Im}(X[k])}{\text{Re}(X[k])}\right) ] 其中 ( X[k] ) 是FFT结果的第k个频点复数输出,Im和Re分别表示虚部和实部。
相位解缠绕处理
FFT输出的相位值通常被限制在 ([-π, π]) 范围内(主值相位)。实际应用中需进行相位解缠绕以消除跳变:
- 计算相邻频点的相位差 ( \Delta\phi )
- 当 ( |\Delta\phi| > π ) 时,通过加减 ( 2π ) 进行修正
- 累积修正量得到连续相位
Python示例代码:
python
import numpy as np
def unwrap_phase(phase):
diff = np.diff(phase)
corr = np.where(diff > np.pi, -2*np.pi, 0)
corr = np.where(diff < -np.pi, 2*np.pi, corr)
return phase + np.concatenate(([0], np.cumsum(corr)))
窗函数选择
为减少频谱泄漏对相位测量的影响,推荐使用对称窗函数:
- 汉宁窗(Hanning):适用于大多数情况
- 平顶窗(Flat-top):需要高精度幅值测量时
- 布莱克曼窗(Blackman):高动态范围场景
窗函数应用公式: [ x_{\text{windowed}}[n] = x[n] \cdot w[n] ] 其中 ( w[n] ) 是窗函数序列。
频率分辨率优化
相位测量精度与频率分辨率直接相关: [ \Delta f = \frac{f_s}{N} ] 其中 ( f_s ) 为采样率,( N ) 为FFT点数。提高分辨率的方法包括:
- 增加采样点数(可能导致实时性下降)
- 使用Zoom-FFT技术聚焦目标频段
- 采用插值算法修正峰值位置(如抛物线插值)
系统延迟补偿
测量系统中硬件延迟会导致相位偏差,需进行校准:
- 使用已知参考信号测量系统群延迟
- 建立频率-相位偏移查找表
- 在实际测量中扣除校准值
MATLAB延迟补偿示例:
matlab
measured_phase = angle(fft(signal));
calibrated_phase = measured_phase - calibration_table(freq_index);
多信号相位差测量
比较两个信号的相位差时需注意:
- 确保两路信号严格同步采样
- 使用相同窗函数和FFT参数
- 考虑信号间的时间对齐误差
相位差计算公式: [ \Delta\phi = \phi_1[k] - \phi_2[k] ] 其中 ( k ) 对应目标频率的频点索引。
抗噪声处理技术
在低信噪比环境下提高相位测量精度:
- 多次平均降低随机噪声影响
- 使用窄带滤波预处理
- 采用锁相放大原理提取特定频率相位
- 基于最小二乘的相位拟合算法
实际应用注意事项
-
采样时钟抖动会引入相位噪声,建议使用高稳定性时钟源
-
对于非平稳信号,建议采用短时傅里叶变换(STFT)
-
高频测量时需考虑传输线效应和阻抗匹配
-
数字系统注意量化误差对相位的影响
`timescale 1ns / 1ps
module fft_2048 (
input wire clk,
input wire rst_n,
input wire [15:0] signal_1,
input wire [15:0] signal_2,
input wire signal_tvalid,output reg [15:0] fft_re_m, output reg [15:0] fft_im_m, output reg [15:0] fft_re_r, output reg [15:0] fft_im_r, output wire fft_tvalid, output wire fft_tlast, output reg signed [31:0] phase_diff_fft);
localparam FFT_CONFIG_DATA = {5'd0,10'b10_10_10_10_10,1'b1};
wire fft_tvalid_r;
wire signed [31:0] fft_data_r;wire fft_tvalid_m;
wire signed [31:0] fft_data_m;
wire [7:0] fft_tuser;xfft_0 fft_r (
.aclk(clk), // input wire aclk
.aresetn(rst_n), // input wire aresetn
.s_axis_config_tdata(FFT_CONFIG_DATA), // input wire [15 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata({16'd0, signal_1}), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
.s_axis_data_tready(), // output wire s_axis_data_tready
.s_axis_data_tlast(), // input wire s_axis_data_tlast
.m_axis_data_tdata(fft_data_r), // output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tuser(), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(fft_tvalid_r), // output wire m_axis_data_tvalid
.m_axis_data_tlast(), // output wire m_axis_data_tlast
.event_frame_started(), // output wire event_frame_started
.event_tlast_unexpected(), // output wire event_tlast_unexpected
.event_tlast_missing(), // output wire event_tlast_missing
.event_data_in_channel_halt() // output wire event_data_in_channel_halt
);xfft_0 fft_m (
.aclk(clk), // input wire aclk
.aresetn(rst_n), // input wire aresetn
.s_axis_config_tdata(FFT_CONFIG_DATA), // input wire [15 : 0] s_axis_config_tdata
.s_axis_config_tvalid(1'b1), // input wire s_axis_config_tvalid
.s_axis_config_tready(), // output wire s_axis_config_tready
.s_axis_data_tdata({16'd0, signal_2}), // input wire [31 : 0] s_axis_data_tdata
.s_axis_data_tvalid(1'b1), // input wire s_axis_data_tvalid
.s_axis_data_tready(), // output wire s_axis_data_tready
.s_axis_data_tlast(), // input wire s_axis_data_tlast
.m_axis_data_tdata(fft_data_m), // output wire [31 : 0] m_axis_data_tdata
.m_axis_data_tuser(fft_tuser), // output wire [15 : 0] m_axis_data_tuser
.m_axis_data_tvalid(fft_tvalid_m), // output wire m_axis_data_tvalid
.m_axis_data_tlast(), // output wire m_axis_data_tlast
.event_frame_started(), // output wire event_frame_started
.event_tlast_unexpected(), // output wire event_tlast_unexpected
.event_tlast_missing(), // output wire event_tlast_missing
.event_data_in_channel_halt() // output wire event_data_in_channel_halt
);reg signed [31:0]fft_amp_r=0;
reg signed [31:0]fft_amp_m=0;
always@(posedge clk)begin
if(fft_tvalid_m)begin
{fft_im_m,fft_re_m}<=fft_data_m;
fft_amp_m <= signed(fft_im_m) * signed(fft_im_m) + signed(fft_re_m) * signed(fft_re_m);
end
if(fft_tvalid_r)begin
{fft_im_r,fft_re_r}<=fft_data_r;
fft_amp_r <= signed(fft_im_r) * signed(fft_im_r) + signed(fft_re_r) * signed(fft_re_r);
end
endassign fft_tvalid = fft_tvalid_r & fft_tvalid_m;
wire signed [31:0]cordic_result1;
wire signed [31:0]cordic_result2;
reg signed [15:0] FFT_result_phase_m;
reg signed [15:0] FFT_result_real_m;
reg signed [15:0] FFT_result_phase_r;
reg signed [15:0] FFT_result_real_r;
wire cordic_tvalid1;
wire cordic_tvalid2;
cordic_1 cordic_m (
.aclk(clk),
.s_axis_cartesian_tvalid(1'b1),
.s_axis_cartesian_tdata({fft_im_m, fft_re_m}),
.m_axis_dout_tvalid(cordic_tvalid1),
.m_axis_dout_tdata(cordic_result1)
);
cordic_1 cordic_r (
.aclk(clk),
.s_axis_cartesian_tvalid(1'b1),
.s_axis_cartesian_tdata({fft_im_r, fft_re_r}),
.m_axis_dout_tvalid(cordic_tvalid2),
.m_axis_dout_tdata(cordic_result2)
);
always@(posedge clk)begin
if(cordic_tvalid1)begin
{FFT_result_phase_m,FFT_result_real_m}<=cordic_result1;
end
end
always@(posedge clk)begin
if(cordic_tvalid2)begin
{FFT_result_phase_r,FFT_result_real_r}<=cordic_result2;
end
end
reg signed [15:0] temp_phase_diff_fft;always @(posedge clk)
begin
if((FFT_result_real_r>=16'd5000||FFT_result_real_m>=16'd5000)&&(fft_tuser<(511+22)))
temp_phase_diff_fft <= FFT_result_phase_m-FFT_result_phase_r;
end
always @(posedge clk)
begin
if(/*(signed(temp_phase_diff_fft)>=16'b1110_0000_0000_0000)||*/(signed(temp_phase_diff_fft)<=16'd8192))
phase_diff_fft<= signed(temp_phase_diff_fft)*signed(9'd180);
endendmodule