使用相位导数的 IQ 信号 FM 判别

信息处理 解调 调频 正交 八度
2022-02-18 18:31:36

我正在尝试解调以 2.4Msps 从 RTL-SDR 记录为 IQ 样本的 FM 信号,其中 RTL-SDR 以 FM 电台的频率为中心。

所以输入信号是 2.4MSps 的 8 位 IQ 基带。

数据可在此处获得。

输入信号的 FFT 看起来很好,与我在 SDR-Sharp 中看到的相匹配,所以我认为数据很好。

我的理解是,我应该能够通过以下方式区分 Octave 中的 FM:

  1. 消除直流偏移

  2. 将其低通滤波至 240KHz 并抽取

  3. 计算相位分量的离散导数

FFT 在低通滤波和抽取之后看起来不错。
我的假设是应用FIR 低通滤波器不应该扭曲相位(不确定我是否正确)

然后,我通过将相位信号与 [1,-1] 的离散导数的脉冲响应进行卷积来计算信号相位的离散导数。

我期望衍生信号的频谱将包含 0-15KHz 的复合 FM 信号,由单声道 L+R 分量、19KHz 导频音等占据。
但是我在相位导数的 FFT 中得到白噪声(见下文)。

我的八度代码如下

% 2.4MSps IQ baseband signal of an FM station from RTL-SDR recorded in SDR#
  samples = audioread("SDRSharp_20191229_133205Z_102102000Hz_IQ.wav", "native");
  csamples = arrayfun(@complex,samples(:,1),samples(:,2));

% 1) - remove DC offset
  csamples_ac = csamples - mean(csamples);   

% 2) - low pass filter and decimate to get complex 240KSps (240KHz Widband FM)
  csamples_filtered = decimate(csamples_ac, 10, "fir");

% 3) - descrete derivative of phase
  filtered_derivative = conv(angle(csamples_filtered), [1,-1]);

  plot(abs(fftshift(fft(filtered_derivative, 32768))));
  axis("tight");

相位离散导数的频谱

编辑:感谢 hotpaw2 和 Dan Boschen,我现在知道我的错误是什么,并且有了一个有效的 Octave 脚本。主要的错误是没有展开相位,同样当通过卷积与 [1,-1] 进行区分时,需要丢弃结果中的第一个和最后一个样本,因为卷积的可能伪影会在序列的末端给出巨大的值,第三个错误是在使用 Octave 的音频播放器功能之前没有转换为 int16 并且没有去除声音样本中的 DC。

如果有人需要这样的脚本,下面是一个工作版本。
(请注意,基于相位的鉴别会受到低通滤波 1/f 滚降效应的影响,有关更多详细信息,请参阅此答案

pkg load signal;

#reading SDR Sharp recording (2.4Msps,RAW,8bit,baseband)
samples = audioread("SDRSharp_20191229_183956Z_99500000Hz_IQ.wav", "native");
#one channel real, other channel - imaginary components
unshifted_signal = double(samples(:,1)) .+ double(samples(:,2))*j;

#removing DC, this is important, 
unshifted_signal = unshifted_signal - mean(unshifted_signal);

#a 1550 Hz frequency shift to move the real carrier center frequency as close to 0 as possible (de-trending the phase)
#the frequency shift is not absolutely necessary, small diviations don't affect the demodulation
#this would be station specific, selected to minimized the slope on plot(unwrp_phase)
freq_shift = exp(-j*1550*2*pi*[1:length(unshifted_signal)]/2400000)';
#freq_shift = 1;
signal = unshifted_signal.*freq_shift;

#low-pass filter and decimate down to 240KHz
rcv_240 = decimate(signal, 10);
#calculating the phase, and unwrapping it (unwrapping is important)
unwrp_phase = unwrap(angle(rcv_240));

#doing the actual demodulation (discrimination)
#not including the ends because of possible convolution artifacts
phase_drv = conv(unwrp_phase, [1,-1])(2:end-1);

#extract L+R submodulated at 0-15KHz by low-pass filtering and decimation
monoLplusR_unnormalized = decimate(phase_drv, 8);

#normalize the range for 16bits for audioplayer, it is important to have no DC here (removed earlier)
monoLplusR = int16(monoLplusR_unnormalized/max(abs(monoLplusR_unnormalized))*32766);

#play at 30Ksps (0-15KHz)
player = audioplayer(monoLplusR, 30000, 16);
play(player);
2个回答

我只是使用展开的 atan2(IQ(i)) - atan2(IQ(i-1)) 来估计离散导数,然后将低通滤波器降至 15 kHz 以下。尽管坡度较浅,但 Boschen 给出的 atan() 的一阶近似值也可以正常工作。

您的噪音可能是由于没有展开相位增量,或者在进行相位鉴别后没有低通滤波。不确定您的 angle() 函数是否与 atan2() 相同。

在 240 KHz 时,每单位样本的相位增量非常小,因此您的导数是一个高通滤波器,其截止频率远高于 15 KHz。(查看 freqz([1 -1]) 以了解我的意思。)您有一个鉴频器,但它的相位斜率在将频率变化转换为感兴趣信号的幅度变化时非常小。

您可以尝试增加差异之间的样本数 [1 0 0 0 0 -1] 例如或低通滤波器,然后抽取您的相域波形以继续您的方法,如果您真的只想要 0 到 15KHz 频带中的内容.

还考虑使用波形的复共轭乘法的假想结果与本身的延迟版本直接实现,而无需计算角度(作为另一种选择)。

这将是

I[n]Q[nm]I[nm]Q[n]

其中 m 增加以增加鉴别器的斜率(增益)。请注意,当 m=3 低于输出幅度的斜率时,输入上的频率变化是如何增加的。所以我怀疑你的斜率太低了。

鉴别器