动手玩 :RTL-SDR FM Radio Walk Through

 

写在前面

自从在OSX上装好GNU Radio之后,我一直在想能不能搞点有意思的刺激的东西。但是,饭总要一口一口吃,大的计划也要脚踏实地地从一些基础工作开始做起。在开始自己写代码前,最好用现成的东西先来一遍,看看标准的代码长什么样。俗话说,老司机给带带路呗。所以,这次我们就利用GNU Radio已经提供的blocks,搭建一个FM收音机出来。

准备工作

硬件方面,我依然钟情于40块钱的RTL,像X310这种板子,用来当背景什么的最合适了 XD。 其实最主要的原因是,万一玩儿脱了,也就40块钱,要是把X310烧了……呸呸呸

软件方面,在前一次配置GQRX的过程中,我们已经在利用macport 在OSX中搞定了GNU Radio的安装,但是为了稳妥起见,还是用macport把各种乱七八糟的东西更新一下。如果和我一样,有些组件已经给玩坏了,还可以强制重新安装一遍。

1.sudo port selfupdate2.sudo port update installed

如果更新过程中,收到这样的提示:

1.Warning: A file belonging to the 'xxxx_port_name' port is missing or unreadable. Consider reinstalling it.

这说明这个已经安装的包(xxxx_port_name)有文件挂了。这时候直接install 和 update,不能解决这个问题,最好强制re-build一下:

1.sudo port -n update --force xxxx_port_name

至此,准备工作已经完成了。

Let’s do it!

FM收音机工作原理

注意哦,这里是FM收音机工作原理,不是FM工作原理。FM的原理自己回去翻通原去吧。大体上,一个简单的FM收音机是这个样子的:

这几个部分的工作流程是这样的:

  1. RF电路把中心频点调到某个频率,天线接收信号
  2. 对接收到的模拟信号进行ADC,采用频率为
  3. 将ADC后的数字信号经过一个数字低通滤波器,剃掉一些没有用的高频噪声。
  4. 滤波器输出信号按照FM进行解调
  5. 将解调后的数字信号转换成适当的电平通过扬声器播放

大致了解了FM收音机工作原理和工作流程后,我们就可以开工了。

啊?这就开工了?

接上电源,打开电脑,找到苍老师……XD,瞬间小岳岳附体了。

接下来请出我们的主角。

GNU Radio Companion

GNU Radio 提供了一套可视化的搭建平台,这既是GNU Radio Companion(以下简称GRC)。如果你以前接触过Matlab的Simulink,你会发现GRC已经把搭建系统这件事情,简化到了“傻瓜级”的水平。 从GRC的角度看,基本操作就4个:

  1. 选择需要的功能模块
  2. 根据设计配置模块参数
  3. 把模块连起来
  4. 不满意?用自己写的模块啊(out-of-tree modules)

怎么样,简单吧?在这里,我就不给GRC做广告了,只额外再多说两句:SDR是engineering + coding 的结合。一方面,攻城狮们在通信原理、DSP、还有电路硬件方面的知识构成了任何一个SDR系统的backbone;另一方面,程序猿们的优化和扩展则是SDR的精髓,是真正让SDR与众不同的灵魂。

不扯这些,现在我们还是小学生,先学走路。

模块及参数配置

用MacPort 安装了GNURadio之后,就默认安装了GRC。不需要额外的配置,直接就可以用:

1.gnuradio-companion

GRC的默认界面是这个样子的(我这边MacPort提供的GRC版本使用的GUI是基于QT的):

模块的基本操作简答提一下:

  • 添加模块:从右边的列表里选中需要的模块,拖到左边的空白处;
  • 配置参数:在模块上双击或者右键点Properties。一般都会有Documentation Tab,没事儿多看看。
  • 链接:单击模块a的输出(out),再单击另模块b的输入(in),就会自动完成把模块a的输出数据作为输入送入模块b的操作。而对于变量,可以通过”Variable”模块或者GUI输入的各种输入模块进行变量ID声明和变量赋值,在其他模块中利用变量ID名进行引用。
  • 取值:可以直接输入值、引用变量ID,也可以是python运算表达式。
  • 简单调试:如果框图中有错误,Error按钮就会变亮,点击后可以看到模块级的出错信息。
  • 其他操作:看右键菜单。

RTL-SDR Source

RTL-SDR Source是RTL-SDR USB dongle的驱动接口,通过它我们可以完成对信号接收和ADC(前面提到到步骤1 和 2),还可以配置增益控制。

参数配置很直白,就不一一介绍了,拣几个重要的说:
Sample Rate:我用的RTL2382U理论上最高支持3.5Msps的采样率,但是无损的采样率最高为2.56Msps。采样率过低会导致最后音频解调失真,但是过高又会造成处理速度下降。因为FM Radio有效的带宽也就撑死了100kHz,所以这个数值我设在1-2Msps之间,例子中是1.28e6sps。
Ch0: Frequency:中心频点的位置。如果我们打算收听帝都交通台FM1039就调到103.9e6
Ch0: Freq.Corr:设备的频偏修正。在前一篇文章在测量GSM基站频点的时候,测过我这套RTL2382U+R820T的频偏修正大约为-28ppm。每套设备修正值都不同,但是对于FM收音来说,偏个几十KHz都没问题,所以0也可以。

在这个部分,我用变量samp_rate来设置采样率;用一个QT GUI中滑块来动态调整中心频率,实现调台(Turn!Turn!Turn!)

LPF

低通滤波器。基带信号有用的就那1-200kHz带宽有意义,因此在采样后利用LPF把高频信号全都滤掉。核心参数有两个:Cutoff Freq和Transition Width。两个参数的含义看了下面这俩图你就明白了。

因为我们并不知道广播电台FM信号的带宽具体是多少,所以用一个滑块来调节Cutoff Freq,从1kHz到200kHz步长1kHz。数字低通可以令LPF的边缘非常陡峭,代价就是引入一些毛刺,因此对Transition Width也做了一个滑块,用来控制LPF的形态。

你大概发现了,还有个Decimation咋没提呢?这里先留下,后面单独介绍。

WBFM

WBFM是WideBand FM模块,对应的还有一个NarrowBand FM(NBFM)。WBFM模块有两个参数需要配置:

  • Quadrature Rate:采用正交解调方法,我理解Quadrature Rate就是I/Q支路上的符号速率,这里我们设置为320e3(320k)
  • Audio Decimation:和前面Decimation一样,最后再说。

Rational Resampler

顾名思义,这个模块的功能就是数据流的速率。在这里顺带手吧前面一直说的Decimation来解释一下。

数据流的速率

所有的Block都有一个输入,很多(但不是所有)Block也都有一个输出。输入数据流的速率可以理解为函数传递参数的个数,例如,有的是320k(320e3)个,有的是2M(2e6)个。如果模块A的输入数据流的速度是,输出的数据流的速率是,模块A要把这个数据流作为输入送入模块B,那么至少要保证的个数与模块B中定义的输入数据流的速率是一样的。换句话说,我们要使模块A的输入数据流的速率,在经过模块A处理变成速率为的输出数据流时,满足

为了达到这个目的,最直接的方法就是在模块A上定义一组系数,在模块A上对输入数据序列进行抽取,使速率下降为原来的,之后再用内插数据点的方式,使输出的数据速率增加到倍。这么说可能有点别扭,用公式表示就简单了:

 

就比如我们这个框图吧:

  1. WBFM输出的数据流的速率是320K,这个数据流作为Rational Resampler的输入,即
  2. 如果我们希望经过Resampler之后的输出数据速率变为32K,那么就可以设置Decimation为10(),设置Interpolation为1(),这样输出的速率就变成:

前面几个模块中的Decimation也是这个意思,依此类推。

数据类型

这是一个没有显性说明的因素,在我们这个例子中:RTL-SDR Source只提供Complex(32bit)的输出;LPF、Rational Resampler支持Complex和Float,但是输入输出数据类型要相同;WBFM Recieve没有任何选项,但是实际上他要求输入必须是Complex,输出是Float。

从写程序传递参数的角度看,不同的数据类型占用的存储空间是不同的:一个Complex包含实部和虚部,因此至少是两个Float。因此在不同的模块之间传递的数据流,不仅仅要个数匹配(数据流的速率),数据的长度也要匹配(数据的个数 *每个数据的长度)。这点在配置的时候要注意。

现在你可以思考的问题是:为什么WBFM模块在做FM解调的时候,输入输出为什么一定是从Complex类型到Float类型呢?Hints:WBFM把复信号解调成实信号输出。

Audio Sink

这个模块的作用是把数字信号转成模拟信号通过声卡播放出来。类似前面RTL-SDR Source中的采样率,这里需要一个采样率来进行DAC。

效果

除了上面提到的几个模块,我在LPF后面接了一个示波器;在Audio Sink前接了一个乘法运算模块和滑块作为音量调节。你也可以不加这两个模块。最后,把所有模块连接起来,整体的流程图是这个样子:

运行

通过调节Freq滑块进行频率调整,找到你想要收听的广播电台。调节LPF的两个滑块调节LPF的Cutoff和Transition宽度,调整送入WBFM的FM信号带宽,从而改变最后的解调出来的声音效果。

d(˙v˙)b

Enjoy!

 

I/Q 支路

记得上一次看到I/Q还是在考研的时候,俨然已经全忘得差不多了。现在只能一些记得一点点皮毛。

最近在用RTL-SDR搭FM Radio,如果只是玩玩,本来是可以囫囵吞枣的事情,但是因为要写出来,所以只能硬着头皮抠细致一点。不过,时隔这么多年再看,一来史海沉钩,想起来了很多当时的事情,另一方面,以前也是囫囵吞枣吃下去的东西,再看又有了更深入的理解,所以看着看着也就来了兴趣。

怎么就扯到I/Q支路上来了呢?其实就是因为看到了Quadrature。以前学的时候,没怎么想过为什么要用I/Q,为什么要用正交解调。In-Phase,Quadrature,在脑海里本能一般反应出来“同相分量”和“正交分量”(应试教育的一大好处),字面上的意思似乎就这么多信息量,当时对正交这件事情完全没有实际的概念,甚至连其数学概念本身也是模模糊糊。现在再看,“正交”可是一个大大有好处的性质,同相和正交分量刚好可以组成一组(正交)基,再直观点说,可以把它们看成是复平面,于是我么就可以把信号投影到复平面上去搞了。

Quadrature还在哪里见过呢?QAM(Quadrature Amplitude Modulation)里的Q也是这个。

说到这里,一个问题忽然冒了出来:正交解调是不是只能用于解QAM吗?答案是:NO!单从接收来说。当把一个信号矢量分解成I/Q两路的时候,实际上是做了一个坐标映射,把信号向量分别投影到了这组正交基上。那么为什么要这样映射呢,把信号矢量映射到复平面上的一个坐标,那么信号矢量的模(幅度)和复角(相位)就可以很轻松地表示出来了。频率呢?因为在分解到I/Q两路的时候,需要用一个频率已知的本振(LO,Local Oscillator),这时信号的频率信息可以通过LO和相位来获得。也就是说,这时我们用这个分解/映射/坐标变换(不论你叫他什么),把信号矢量中的信息用另一种(似乎更清晰、直观的)方式表示了出来,就如同世界有了光,可以把信号矢量描述清楚了。这时再进行进一步的处理,包括各种各样的解调,都是可以的。

这样做的好处是,可以用一套方案,适应尽可能多的需求。

 

关于Fourier、Laplace,Z transform的精辟解释

1. 厘清一个数学概念:

积分运算与内积(点积)运算在形式上是类似的(用等价可能太强了)他们都可以看成是对乘积的求和。

 

 

而在线性代数中,投影的概念也可以用内积的形式来表示。因此,积分运算可以看作是一种投影。如果我们称为系数,为基的话,那么显然对于上述内积表示,可以理解为将通过基线性表出,系数就是坐标,或者说将投影在了这组基上。

需要说明的是:可以理解为某个信号向量在以时间轴中的各个时间点上的投影。这一个一个的时间点就是基。的取值就是这个信号向量在时域(构成的集合)上的(坐标)表示。

2. Fourier, Laplace, Z transform

这几种变换在形式上,都可以表示为积分。其中:

  1. Fourier Transform:

    这里可以完全从数学的形式上看待这个式子,把看成是这样一个一般的函数,现在我们把目光关注到上,这是自变量只能来自复平面的虚轴(实部为)。

     

  2. Laplace Transform:

    换言之,如果傅里叶变化的物理意义是将信号分解为等幅正弦信号的叠加的话,拉氏变换就是讲信号分解为增幅正弦信号的叠加。类似的,把看成,这时自变量可以是复平面上的任意一点。
    还有一种理解方法:是一个复变函数,这个复变函数在虚轴上的取值就是的傅里叶变换。

     

  3. Z Transform: Z变换是针对一个采样序列来说的。假设一个时域信号为采样周期采样,那么采样后的采样信号可以表示为:

    这个采样信号经过Laplace变换后:

    进行代换,令,定义在Z域内映射为:

    Z变换就是对采样后的信号进行的一种在Z平面上的映射。

     

由于指数的存在,这些变换都和圆周有着千丝万缕的联系。