软件无线电-它如何工作? 第4部分

哈Ha

第三部分介绍了如何使用Python访问SDR接收器。 现在,我们将熟悉GNU Radio程序-该系统可让您创建无线电设备的相当复杂的配置,而无需编写任何代码。



例如,考虑在一个接收机上并行接收多个FM电台的问题。 我们将使用与接收器相同的RTL SDR V3。

继续下割。

安装方式


首先,需要安装GNU Radio,可以在此处下载Windows发行套件。 该系统是跨平台的,也有适用于Linux和OSX的版本(似乎GNU Radio已在Raspberry Pi上成功启动,但我不能百分百保证)。

实际上,GNU Radio是用于数字信号处理的整个框架,其中程序是从单独的模块“组装”而成的。 有很多现成的块,如果您愿意,也可以创建自己的块。 模块本身是用C ++编写的,Python相互交互。 那些愿意的人可以更详细地查看API,但实际上这很可能没有用-所有操作都可以在GNU Radio Companion程序中直观地完成。

该系统专注于处理数据流,因此每个块通常都有一个输入和一个输出。 接下来,连接编辑器中的块,我们得到一个现成的系统。 GNU Radio接口本身非常简单,难点在于了解块在做什么。 如前所述,使用SDR的低级工作具有较高的输入阈值,并且需要一些DSP和数学知识。 但是我们将考虑一个简单的任务,不需要任何专门知识。 因此,让我们开始吧。

开始使用


我们启动GNU Radio Companion,创建一个新项目,选择项目类型WX GUI,添加到屏幕并连接两个模块,如屏幕截图所示。



我们看到两种类型的块-源(源)和接收器(输出,“漏极”)。 RTL-SDR是我们的接收器,FFT GUI是虚拟频谱分析仪。

采样率变量设置为2048000,这是我们接收机的采样率。 默认的RTL-SDR频率为100 MHz。

我们开始这个项目-一切正常,我们看到了一系列FM电台。 GNU Radio的第一个程序已经准备好!



如果我们查看日志,将会看到这样的行。

生成:'D:\\ MyProjects \\ GNURadio \\ top_block.py'
执行:C:\ Python27 \ python.exe -u D:\ MyProjects \ GNURadio \ top_block.py

是的,我们可以看到GNU Radio Companion为我们生成的top_block.py文件。 True J​​edi可以直接用Python编写,但是如我们所见,所需的代码相当大。 我们在1分钟内创建了它。

top_blocks.py
#!/usr/bin/env python2 # -*- coding: utf-8 -*- ################################################## # GNU Radio Python Flow Graph # Title: Top Block # Generated: Wed May 22 22:05:14 2019 ################################################## if __name__ == '__main__': import ctypes import sys if sys.platform.startswith('linux'): try: x11 = ctypes.cdll.LoadLibrary('libX11.so') x11.XInitThreads() except: print "Warning: failed to XInitThreads()" from gnuradio import eng_notation from gnuradio import gr from gnuradio import wxgui from gnuradio.eng_option import eng_option from gnuradio.fft import window from gnuradio.filter import firdes from gnuradio.wxgui import fftsink2 from grc_gnuradio import wxgui as grc_wxgui from optparse import OptionParser import osmosdr import time import wx class top_block(grc_wxgui.top_block_gui): def __init__(self): grc_wxgui.top_block_gui.__init__(self, title="Top Block") ################################################## # Variables ################################################## self.samp_rate = samp_rate = 2048000 ################################################## # Blocks ################################################## self.wxgui_fftsink2_0 = fftsink2.fft_sink_c( self.GetWin(), baseband_freq=0, y_per_div=10, y_divs=10, ref_level=0, ref_scale=2.0, sample_rate=samp_rate, fft_size=1024, fft_rate=15, average=False, avg_alpha=None, title='FFT Plot', peak_hold=False, ) self.Add(self.wxgui_fftsink2_0.win) self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' ) self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(100e6, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) self.rtlsdr_source_0.set_dc_offset_mode(0, 0) self.rtlsdr_source_0.set_iq_balance_mode(0, 0) self.rtlsdr_source_0.set_gain_mode(False, 0) self.rtlsdr_source_0.set_gain(10, 0) self.rtlsdr_source_0.set_if_gain(20, 0) self.rtlsdr_source_0.set_bb_gain(20, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) ################################################## # Connections ################################################## self.connect((self.rtlsdr_source_0, 0), (self.wxgui_fftsink2_0, 0)) def get_samp_rate(self): return self.samp_rate def set_samp_rate(self, samp_rate): self.samp_rate = samp_rate self.wxgui_fftsink2_0.set_sample_rate(self.samp_rate) self.rtlsdr_source_0.set_sample_rate(self.samp_rate) def main(top_block_cls=top_block, options=None): tb = top_block_cls() tb.Start(True) tb.Wait() if __name__ == '__main__': main() 


但是,如果我们删除了繁琐的初始化过程,则会发现没有那么多关键代码行。
 from gnuradio import gr from gnuradio.wxgui import fftsink2 import osmosdr class top_block(grc_wxgui.top_block_gui): def __init__(self): grc_wxgui.top_block_gui.__init__(self, title="Top Block") self.samp_rate = samp_rate = 2048000 self.wxgui_fftsink2_0 = fftsink2.fft_sink_c(...) self.Add(self.wxgui_fftsink2_0.win) self.rtlsdr_source_0 = osmosdr.source(args="numchan=" + str(1) + " " + '' ) self.connect((self.rtlsdr_source_0, 0), (self.wxgui_fftsink2_0, 0)) def main(top_block_cls=top_block, options=None): tb = top_block_cls() tb.Start(True) tb.Wait() 

因此,基本上,它可以手动编写。 但是使用鼠标仍然更快。 尽管如果您想添加一些非标准逻辑,更改代码的功能有时会派上用场。

接收调频广播


现在尝试乘坐其中一个车站。 从屏幕截图可以看出,接收器的中心频率为100 MHz,带宽约为2 MHz。 在频谱中,我们看到两个站分别位于100.1 MHz和100.7 MHz。

第一步是将电台的频谱传输到中心,现在右边是100KHz。 为此,我们回想起乘以余弦的学校公式-结果将出现两个频率,即和和差-所需的电台将移至中心,这正是我们所需要的(并过滤掉多余的频率)。

我们创建两个变量来存储freq_center = 100000000和freq_1 = 100100000频率,我们还添加了一个信号发生器,其频率为freq_center频率-freq_1。



因为 由于系统基于Python,因此我们可以在参数输入字段中使用表达式,这非常方便。

该图应如下所示:



现在,您需要一次添加几个模块-降低输入信号的时钟频率(2048KHz),对信号进行滤波,将其应用于FM解码器,然后再次将时钟频率降低至48KHz。

结果如图所示:



我们仔细考虑。 我们用Rational Resampler块将2048KHz的时钟速度除以4倍(我们得到512KHz),然后在低通滤波器之后有一个抽取为10的WBFM解码器(我们得到51.2KHz)。 原则上,该信号已经可以输入到声卡,但是音高会略有不同。 再次,我们将时钟频率更改为48/51,结果,时钟频率将为48.2 KHz,这一差异已经可以忽略。

第二个重点是输入的类型。 从接收器接收到复杂的 IQ信号(输入/输出为蓝色),从FM解码器输出的是真实信号-输入和输出为黄色。 如果混淆了,将无济于事。 哈布雷(HAbré已经有了更多的知识,这足以让我们了解一般原理。

通常,运行,确保一切正常。 您可以运行该程序并收听广播。 我们将走得更远-我们还有软件定义无线电-我们将添加第二个电台的同时接收。

多频道接收


第二个接收器是通过您最喜欢的编程方法添加的-Ctrl + C / Ctrl +V。 添加freq_2变量,复制块并以相同方式连接它们。



结果非常超现实-您可以同时收听两个FM电台。 使用相同的方法(Ctrl + V),可以添加第三个工作站。

记录


以原始方式收听两个电台,但实际上并不是很有用。 我们将做更多必要的事情,例如,将录音添加到单独的文件中。 这可能非常方便-可以从一个物理接收器同时记录几个通道。

如屏幕截图所示,将File Sink组件添加到每个输出。



Windows版本由于某些原因需要绝对文件路径,否则录制将无法进行。 我们开始,我们坚信一切正常。 保存的文件很大,因为 默认格式为float。 以int格式输入将使读者留作功课。

可以在Cool Edit中打开生成的文件,并确保正常录制声音。





当然,可以增加已记录频道的数量,仅受接收机带宽和计算机功率的限制。 除了文件接收器之外,还可以使用UDP接收器,因此该程序可以用于通过网络广播。

从命令行运行


还有最后一个。 如果您自主使用程序(例如,用于多通道录制),则原则上不需要UI。 在“选项”的左上角,将“运行选项”参数更改为“无用户界面”。 再次运行该程序,确保一切正常。 现在我们保存生成的文件top_block.py-我们可以从命令行运行它,例如从bat文件或从控制台运行它。



如果有人感兴趣,则将生成的文件保存在扰流器下。
记录器
 #!/usr/bin/env python2 # -*- coding: utf-8 -*- ################################################## # GNU Radio Python Flow Graph # Title: Top Block # Generated: Fri May 24 21:47:03 2019 ################################################## from gnuradio import analog from gnuradio import audio from gnuradio import blocks from gnuradio import eng_notation from gnuradio import filter from gnuradio import gr from gnuradio.eng_option import eng_option from gnuradio.filter import firdes from optparse import OptionParser import osmosdr import time class top_block(gr.top_block): def __init__(self): gr.top_block.__init__(self, "Top Block") ################################################## # Variables ################################################## self.samp_rate = samp_rate = 2048000 self.freq_center = freq_center = 100000000 self.freq_2 = freq_2 = 100700000 self.freq_1 = freq_1 = 100100000 ################################################## # Blocks ################################################## self.rtlsdr_source_0 = osmosdr.source( args="numchan=" + str(1) + " " + '' ) self.rtlsdr_source_0.set_sample_rate(samp_rate) self.rtlsdr_source_0.set_center_freq(freq_center, 0) self.rtlsdr_source_0.set_freq_corr(0, 0) self.rtlsdr_source_0.set_dc_offset_mode(0, 0) self.rtlsdr_source_0.set_iq_balance_mode(0, 0) self.rtlsdr_source_0.set_gain_mode(False, 0) self.rtlsdr_source_0.set_gain(10, 0) self.rtlsdr_source_0.set_if_gain(20, 0) self.rtlsdr_source_0.set_bb_gain(20, 0) self.rtlsdr_source_0.set_antenna('', 0) self.rtlsdr_source_0.set_bandwidth(0, 0) self.rational_resampler_xxx_1_0 = filter.rational_resampler_fff( interpolation=48, decimation=51, taps=None, fractional_bw=None, ) self.rational_resampler_xxx_1 = filter.rational_resampler_fff( interpolation=48, decimation=51, taps=None, fractional_bw=None, ) self.rational_resampler_xxx_0_0 = filter.rational_resampler_ccc( interpolation=1, decimation=4, taps=None, fractional_bw=None, ) self.rational_resampler_xxx_0 = filter.rational_resampler_ccc( interpolation=1, decimation=4, taps=None, fractional_bw=None, ) self.low_pass_filter_0_0 = filter.fir_filter_ccf(1, firdes.low_pass( 1, samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76)) self.low_pass_filter_0 = filter.fir_filter_ccf(1, firdes.low_pass( 1, samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76)) self.blocks_multiply_xx_0_0 = blocks.multiply_vcc(1) self.blocks_multiply_xx_0 = blocks.multiply_vcc(1) self.blocks_file_sink_0_0 = blocks.file_sink(gr.sizeof_float*1, 'D:\\Temp\\1\\audio2.snd', False) self.blocks_file_sink_0_0.set_unbuffered(False) self.blocks_file_sink_0 = blocks.file_sink(gr.sizeof_float*1, 'D:\\Temp\\1\\audio1.snd', False) self.blocks_file_sink_0.set_unbuffered(False) self.audio_sink_0 = audio.sink(48000, '', True) self.analog_wfm_rcv_0_0 = analog.wfm_rcv( quad_rate=samp_rate/4, audio_decimation=10, ) self.analog_wfm_rcv_0 = analog.wfm_rcv( quad_rate=samp_rate/4, audio_decimation=10, ) self.analog_sig_source_x_0_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_center - freq_2, 1, 0) self.analog_sig_source_x_0 = analog.sig_source_c(samp_rate, analog.GR_COS_WAVE, freq_center - freq_1, 1, 0) ################################################## # Connections ################################################## self.connect((self.analog_sig_source_x_0, 0), (self.blocks_multiply_xx_0, 1)) self.connect((self.analog_sig_source_x_0_0, 0), (self.blocks_multiply_xx_0_0, 1)) self.connect((self.analog_wfm_rcv_0, 0), (self.rational_resampler_xxx_1, 0)) self.connect((self.analog_wfm_rcv_0_0, 0), (self.rational_resampler_xxx_1_0, 0)) self.connect((self.blocks_multiply_xx_0, 0), (self.rational_resampler_xxx_0, 0)) self.connect((self.blocks_multiply_xx_0_0, 0), (self.rational_resampler_xxx_0_0, 0)) self.connect((self.low_pass_filter_0, 0), (self.analog_wfm_rcv_0, 0)) self.connect((self.low_pass_filter_0_0, 0), (self.analog_wfm_rcv_0_0, 0)) self.connect((self.rational_resampler_xxx_0, 0), (self.low_pass_filter_0, 0)) self.connect((self.rational_resampler_xxx_0_0, 0), (self.low_pass_filter_0_0, 0)) self.connect((self.rational_resampler_xxx_1, 0), (self.audio_sink_0, 0)) self.connect((self.rational_resampler_xxx_1, 0), (self.blocks_file_sink_0, 0)) self.connect((self.rational_resampler_xxx_1_0, 0), (self.audio_sink_0, 1)) self.connect((self.rational_resampler_xxx_1_0, 0), (self.blocks_file_sink_0_0, 0)) self.connect((self.rtlsdr_source_0, 0), (self.blocks_multiply_xx_0, 0)) self.connect((self.rtlsdr_source_0, 0), (self.blocks_multiply_xx_0_0, 0)) def get_samp_rate(self): return self.samp_rate def set_samp_rate(self, samp_rate): self.samp_rate = samp_rate self.rtlsdr_source_0.set_sample_rate(self.samp_rate) self.low_pass_filter_0_0.set_taps(firdes.low_pass(1, self.samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76)) self.low_pass_filter_0.set_taps(firdes.low_pass(1, self.samp_rate/4, 100000, 500000, firdes.WIN_HAMMING, 6.76)) self.analog_sig_source_x_0_0.set_sampling_freq(self.samp_rate) self.analog_sig_source_x_0.set_sampling_freq(self.samp_rate) def get_freq_center(self): return self.freq_center def set_freq_center(self, freq_center): self.freq_center = freq_center self.rtlsdr_source_0.set_center_freq(self.freq_center, 0) self.analog_sig_source_x_0_0.set_frequency(self.freq_center - self.freq_2) self.analog_sig_source_x_0.set_frequency(self.freq_center - self.freq_1) def get_freq_2(self): return self.freq_2 def set_freq_2(self, freq_2): self.freq_2 = freq_2 self.analog_sig_source_x_0_0.set_frequency(self.freq_center - self.freq_2) def get_freq_1(self): return self.freq_1 def set_freq_1(self, freq_1): self.freq_1 = freq_1 self.analog_sig_source_x_0.set_frequency(self.freq_center - self.freq_1) def main(top_block_cls=top_block, options=None): tb = top_block_cls() tb.start() try: raw_input('Press Enter to quit: ') except EOFError: pass tb.stop() tb.wait() if __name__ == '__main__': main() 


该系统是跨平台的,这也很方便,并且生成的程序可以在Linux,Windows和OSX上运行。

结论


可以说,GNU Radio是一个相当复杂的系统,不是从绘制图块的角度来看,而是从理解它们的全部工作原理来看。 但是做一些简单的事情是相当可行和有趣的。 GNU Radio还可以方便地用作培训的“虚拟实验室”-您可以将虚拟示波器或频谱分析仪连接到电路的任何部分,并查看信号的外观。

如果没有任何个人意愿,则可以关闭SDR接收的主题-已经考虑了所有要点,并且从第一到第三部分的视图数量几乎成倍下降(尽管您仍然可以撰写有关转让的书,但这需要更昂贵的“硬件”进行比RTL SDR的测试)。 尽管如此,我希望读者能继续对它的工作方式有所了解。 好吧,所有成功的实验。

Source: https://habr.com/ru/post/zh-CN453038/


All Articles