继续学习《Practical SDR: Getting Started with Software-Defined Radio》,通过上一篇了解过信号处理基础知识后,接下来就可以深入分析一下之前搭建的 AM 接收器的工作原理啦,比如 AM 接收机是如何调谐到输入数据中的特定无线电信号的、无线电信号如何被解调以提取音频信号、音频信号如何被重采样以便可以通过电脑声卡播放
打开之前构建的 AM 接收器流程图

我们从无线电信号开始分析,在 File Source 后面接入 QT GUI Freq Sink(此时接口是蓝色,表示输入输出均为复数,所以不用改类型),运行查看波形,可以看到很杂乱的波形,但是认真分析的话可以看出来有几个比较明显的峰值,这些峰值就是实际的广播台信号,但是当你想去看这些频率具体是啥的时候可能会发现一个问题,横坐标的频率看起来不太对,怎么还有负的!?而且范围也不合理啊

这是因为 QT GUI Freq Sink 的 Center Frequency 设置的是 0,而整个无线电信号的带宽是 400kHz(无线电接收设备可以获取数据的频率范围),所以在频域图上显示的是 -200kHz 到 200kHz,我们把 Center Frequency 设置为变量:center_freq 再次查看流程图就正常了
调谐 Tuning
可以看到其中一个峰值为 880kHz,这就是我们当前收听到的频道(在 QT GUI Entry 中设置的 freq)通过 QT GUI Entry 我们不再需要像传统收音机那样转旋钮来查找电台,可以直接通过调整 freq 的值来调谐到其他峰值

可以将调谐理解为聚焦于一个特定频率信号,同时排除其他信号,在这个 AM 接收器例子中调谐分为了两步,第一步是使想要的信号在频域上居中于 0 Hz(这可以通过将输入数据乘以特定频率的正弦波来实现的)第二步使用滤波器滤除掉所有不处于中心频率的信号
通过图示大概是这样的:假设说我们想要的目标频率是三个峰值中间的那个

首先需要将输入的无线电信号频率进行偏移,使目标信号以 0 Hz 为中心

然后滤除掉以零为中心的目标信号之外的所有信号

那通过 GNU Radio 如何操作呢,调谐的第一步是频移,相信你通过观察 AM 接收器的流程图也猜出来了,频移是通过将无线电信号乘以一个复数类型的正弦波实现的,但是先暂时放下 AM 接收器,创建一个更简单的项目来理解这一概念
使用 File Source 打开 ch_06/rf_input_c0_s32k.iq 文件,通过 QT GUI Frequency Sink 来观察频率

添加一个 Signal Source 与 File Source 相乘,然后也通过 QT GUI Frequency Sink 来观察频率,为了区分,给每个 QT GUI Frequency Sink 设置一下名字,这样一来可以很直观地看出,当原始的信号乘了一个 1kHz 的正弦信号会往右偏移 1kHz(我发现原书中虽然是叫正弦,但是他用的一直是默认的 cosine,可能因为俩函数本身形状一模一样,只是平移了 1/4 个周期,所以在实际信号调制解调中看没啥区别)

那么更改正弦函数频率会发生什么呢,为了方便的修改频率可以增加一个 QT GUI Entry 模块,通过修改不同的值多次实验可以发现:任何一个复杂 RF 信号,乘以频率为 f 的复正弦波,得到相同的复杂 RF 数据,但在频率上偏移了 f

如果你乘的是一个负的信号比如 -1kHz,那么就会向左偏移 1kHz,当乘以 -3500kHz 的时候整个就会左移偏移到 0Hz 为中心了。

最早书中介绍 Throttle 模块可以节省 CPU 资源时我完全不在意,以为现代计算机已经不需要了。结果在实际处理复杂信号的时候发现 Throttle 模块是真的可以节省资源,不放 Throttle 的时候 CPU 会跑到 36% 可以听到风扇疯狂转的声音,加入这个模块之后 CPU 占用率只有 7%,像没运行流程图一样
因此补充一段关于 Throttle 模块的解释,像我们这种纯软件的流程图,GNU Radio 会将其视为一项纯计算模拟,默认会以最大速度运行整个数据流,占满 CPU 资源,加入 Throttle 模块的意义就在于使数据流按指定采样率受控运行。如果是从 SDR 硬件设备中接收到信号就不需要 Throttle 模块了,因为 SDR 设备具有自己的采样时钟,它们通过驱动程序持续、稳定地把数据按硬件时钟推入流图,而不像纯软件 Signal Source 那样一直产生数据
思路再拉回来,完成调谐第一步 频移 之后,就需要进行第二步 滤波 了,让我们来换一个更有意思的信号:ch_06\tuner_test_c0_s1M.iq
这个信号是 1M 的采样率,因此把流程图中的 samp_rate 改为 1M,然后新增 QT GUI Entry 用来设置滤波器的截止频率 cutoff_freq(注意类型改为 float 默认值 100e3),添加一个 Low Pass Filter,截止频率设置为 cutoff_freq,书中提到一个经验是过渡带宽(Transition Width)可以先设置为截止频率的 1/10,然后根据实际情况去调整,因此过渡带宽是 cutoff_freq/10
再添加一个 QT GUI 时域接收器(QT GUI Time Sink)并设置 Autoscale 为 Yes,现在 Signal Source 的频率是通过 freq 来设置的,表示偏移的频率。不妨改为 -1 * freq 这样就直接成了想要调谐到的频率了,同时把 freq 改个名字,改为:tuner_freq
执行后会看到三个比较明显的波形,分别是:230kHz、350kHz、435kHz

可以手动调整 tuner_freq 到不同的频率比如 230kHz(GNU Radio 中有个问题,在流程图的 block 中是用科学计数法表示,但是在执行期间需要使用 k、M、G 等公制单位,所以调整的时候需要输入 230k),可以看到第一个尖峰确实到了 0Hz 位置

但是滤波效果不太好,截止频率还是太大了,除了想要的尖峰之外还有很多其他的信号,调整 cutoff 为 10k 效果就变得很不错了,且时域中的波形也变得清晰多了。鼠标中间滚轮点击时域图,选择 Number of Points 可以将横坐标改的大一点,看波形更直观和清楚,基本上是一个三角形状的波形

修改 tuner_freq 查看其它两个峰值,可以看到不同形状的波形


OK,我们回到正题,在 Multiply 和 Low Pass Filter 之后都加入一个 QT GUI Frequency Sink,运行观察波形,最下面的是 File Source 直接出来的波形,我们设置了中心频率为 900kHz;中间的是与 Signal Source 相乘偏移后的,因为没有特意设置中心频率所以以 0Hz 为中心频率,Signal Source 中正弦信号频率为 20kHz,所以大概可以在最下面波形中 880kHz 处看到现在 0Hz 处的频率,不过波形好像匹配不起来?不是说与一个正弦信号相乘只会偏移吗

估计是因为 CPU 处理时间差异的问题,只需要在 File Source 输出的时候加入一个 Throttle 再输出到 QT GUI Frequency Sink 执行看起来就大差不差了。最上面的是经过滤波之后的波形

这里补充一点关于 Center Frequency 的问题,正常来说我们通过 SDR 设备去捕获的都是正的频率,不会有以 0 Hz 为中心,左边是负右边是正的情况,但是 GNU Radio 操作的却是以 0Hz 为中心的,所以可以通过添加 Center Frequency 将现实频率映射到 GNU Radio 的 0Hz 中心频率,这样在我们看来就像是直接操作的现实世界的频率,Center Frequency 的作用就是做现实世界与 GNU Radio 的桥梁
解调 Demodulation
AM 是 Amplitude Modulation 幅度调制,指的是根据信号振荡的幅度来取值,按道理滤波结束后我们已经得到了一个比较简单的调幅信号,然而手动解调信号仍然相当复杂,GNU Radio 提供了 AM Demod 模块用来进行 AM 信号的解调,在流程图中可以看到 AM Demod 接收的是复数输入,而以浮点数输出,无线电数据通常是复数表示,而计算机运算基本都是浮点数
可以在 AM Demod 后添加一个 QT GUI Frequency Sink 查看波形(注意修改类型为 float),执行后会看到信号周期性的上下波动,这表示了音频时大时小,这是幅度调制的一个特性:音频信号越大,载波就越大;音频信号越小,载波就越小
接下来详细了解一下 AM Demod 的各个属性:
重采样 Resampling
流程图中的最后一步是对 AM Demod 解调出来的音频信号进行重采样,这样计算机才可以播放(400kHz 对于计算机来说太快了,声卡基本是 32kHz)。重采样意味着改变信号的采样率
重采样的方法有两种:抽取(decimation)和插值(interpolation),抽取会降低采样率,插值会提高采样率。感觉这俩从命名上还挺好理解的,一个把采样点往外抽导致采样率减少,一个把采样点往里塞导致采样率增加
比方说你现在的采样率是 1kHz,即每秒有 1k 的采样点,如果你想要将采样率减半,那直接每隔一个采样点扔掉一个就好了
下面直接新建一个流程图看一下抽取的效果,注意所有类型均为 float。新建一个 Signal Source 频率为 1kHz,其后添加 Throttle 模块,然后接一个 Keep 1 in N 模块用来进行抽取(意思是:在 N 个采样点中保留一个)其中 N 设置为 4
最后在 Keep 1 in N 前后分别插入一个 QT GUI Time Sink 模块,Keep 1 in N 前面的 QT GUI Time Sink 模块 Number of Points 设置为 128,Sample Rate 设置为 32kHz,后面的 Number of Points 设置为 32,Sample Rate 设置为 8kHz(除以 4 的效果)。在所有 QT GUI Time Sink 模块的 Config 中设置 Line 1 Marker 为 Circle,运行可以看到如下效果,看起来形状大体一致,但是由于采样点数的减少,变得没有那么"丝滑"

与抽取相反,插值是在信号现有样本之间创建额外样本来提高采样率。每个额外样本的添加都是通过估算原始信号在相应时间点的数值来实现的,最简单的例子:如果有两个样本,在 1ms 的值是 3,在 2ms 的值是 7,那么在 1.5ms 的值线性插值算法会估算为 5
插值实际上只通过数学计算增加了采样点,并没有对任何真实信号进行任何额外的测量。插值的操作还需要考虑一些其他的事情,后面再详细介绍
总结
再回到我们的 AM 接收器,如何对 400kHz 采样率的信号进行重采样变成 32kHz 采样率的信号呢,乍一看从高采样率降到低采样率,应该需要抽取,但是抽取应该是整数的,比如每隔 4 个信号抽一个,400 变成 32 却要除以 12.5,这没法操作呀,所以 GNU Radio 有一个专门的重采样模块:Rational Resampler,这个重采样模块通过结合抽取和插值实现了非整数抽取比抽取的效果
本例中 Rational Resampler 做的事情大概是先插值 32 倍,再抽取 400 倍,最终得到了 32kHz,但同时还进行了滤波、相位管理、抗混叠逻辑等步骤进行优化(from chatGPT),实际是相当复杂的,所以我暂时没了手动实现这个模块的想法,后面学的深入了再看看吧
到此了解了在 GNU Radio 中实现 AM 接收器的流程图大概的原理,下一节将会搭建一个 FM 接收器