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

哈Ha

第二部分检查了使用SDR的实际方面。 在这一部分中,我们将研究如何使用Python和价格低廉(30美元)的RTL-SDR接收器接收NOAA气象卫星数据。 所考虑的代码将在任何地方都可以使用-在Windows,OSX,Linux甚至是Raspberry Pi上。



谁在乎,继续减产。

SoapySDR


有很多SDR设备的制造商,分别支持每个设备非常不方便,而且购买测试硬件非常昂贵。 原则上,对于统一访问,有两个已实质上成为标准的库。 第一个是相当古老的ExtIO DLL接口,它可能存在至少十年之久,第二个是更现代的SoapySDR库,我们将对其进行研究。

SoapySDR是一组用C ++编写的跨平台库,它们提供对SDR设备(接收器和收发器)的统一访问。 如果制造商创建了这样的接口,则其设备将自动与相当多的流行程序(GQRX,GNU Radio,CubicSDR等)一起使用。 除一些外,几乎所有合适的制造商(借此机会,我向EE致以问候)都具有SoapySDR支持,可以在项目页面上找到支持的设备的列表。 如您所见,它很大,包括HackRF,USRP,SDRPlay,LimeSDR,RTL-SDR,Red Pitaya等。

SoapySDR库是跨平台的,即 为它编写的代码将在Windows,OSX,Linux甚至Raspberry Pi上运行。 对于Windows,必需的库是PothosSDR软件包的一部分;对于其他平台,SoapySDR将必须独立编译。 必须编译两部分- 库本身 ,以及所需接收器的“插件”,在我们的情况下,它将是SoapyRTLSDR (在Windows下,该库也可以从源代码进行组装,为此您需要Visual Studio,Cmake和SWIG)。 现在一切就绪,您可以编写代码。

我们导入库并获得接收者列表:

from __future__ import print_function import SoapySDR # Enumerate devices print("SDR devices:") for d in SoapySDR.Device.enumerate(''): print(d) print() 

我们连接接收器,运行代码并查看设备列表,其中包括我们的rtlsdr。



我们记得,其余的设备都是声卡,从历史上看,第一个SDR通过PC线性输入精确地工作,并且库也支持它们。 我们获得有关设备的信息-可用通道数,频率范围等:

 soapy_device = "rtlsdr" device = SoapySDR.Device(dict(driver = soapy_device)) channels = list(range(device.getNumChannels(SoapySDR.SOAPY_SDR_RX))) print("Channels:", channels) ch = channels[0] sample_rates = device.listSampleRates(SoapySDR.SOAPY_SDR_RX, ch) print("Sample rates:\n", sample_rates) bandwidths = list(map(lambda r: int(r.maximum()), device.getBandwidthRange(SoapySDR.SOAPY_SDR_RX, ch))) print("Bandwidths:\n", bandwidths) print("Gain controls:") for gain in device.listGains(SoapySDR.SOAPY_SDR_RX, ch): print(" %s: %s" % (gain, device.getGainRange(SoapySDR.SOAPY_SDR_RX, ch, gain))) frequencies = device.listFrequencies(SoapySDR.SOAPY_SDR_RX, ch) print("Frequencies names:", frequencies) frequency_name = frequencies[0] print("Frequency channel name:", frequency_name) print("Frequency range:", device.getFrequencyRange(SoapySDR.SOAPY_SDR_RX, ch, frequency_name)[0]) 

我们启动程序,并查看有关接收器的信息:



我们看到接收器有一个名为“ RF”的输入通道,可能的采样频率为[250000.0、1024000.0、1536000.0、1792000.0、1920000.0、2048000.0、2160000.0、2560000.0、2880000.0、3200000.0],频率范围为24 MHz-1.7 GHz。

生活技巧-通过键入命令SoapySDRUtil --probe =“ driver = rtlsdr”也可以从命令行获取相同的数据。

知道了这一点,我们可以将数据流记录在WAV中。 如上一部分所述,来自SDR的数据由称为I和Q的信号流表示,它们是ADC的样本,大致可以将其表示为来自相机的RAW数据。 任何对更多细节感兴趣的人都可以在这里阅读。 我们足以知道我们可以写入这些数据,然后其他SDR程序就可以使用它们。

记录本身非常简单-如果有数据,readStream函数将填充缓冲区,如果没有数据,则将返回-1。 下面是10个示例的记录代码(省略了代码的非必要部分)。

 device.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, "RF", frequency) device.setGain(SoapySDR.SOAPY_SDR_RX, channel, "TUNER", gain) device.setGainMode(SoapySDR.SOAPY_SDR_RX, channel, False) device.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, sample_rate) # Number of blocks to save block, max_blocks = 0, 10 block_size = device.getStreamMTU(stream) print("Block size:", block_size) buffer_format = np.int8 buffer_size = 2*block_size # I+Q buffer = np.empty(buffer_size, buffer_format) while True: d_info = device.readStream(stream, [buffer], buffer_size) if d_info.ret > 0: wav.write(buffer[0:2*d_info.ret]) print("Bytes saved:", 2*d_info.ret) block += 1 if block > max_blocks: break 

屏幕截图中的结果:



如您所见,我们从设备中获取数据块,一个块的大小为131072字节,以250,000的采样率,它的持续时间约为半秒。 通常,以前在Windows中使用声卡的人会发现很多共同点。

为了进行测试,请写入文件并检查是否一切正常-可以在SDR#中播放该文件。 还有一个窍门-为了使SDR#正确显示电台的频率,文件名必须以与HDSDR兼容的格式写入,格式为“ HDSDR_20190518_115500Z_101000kHz_RF.wav”(您可能会猜到,日期和时间以GMT开头,然后以千赫兹为单位) 。 这很容易用Python编写:

 frequency = 101000000 file_name = "HDSDR_%s_%dkHz_RF.wav" % (datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%SZ"), frequency/1000) 

首先,检查FM频段。 一切都很好,可以看到电台,正在播放音乐,正在运行RDS。



您可以开始记录NOAA。

NOAA摄入量


因此,我们有一个接收器和一个录制程序。 我们将对NOAA 15,NOAA 18和NOAA 19气象卫星感兴趣,这些卫星以137.620、137.9125和137.100 MHz的频率传输地球表面的图像。 这里的主要困难是您需要“捕捉”卫星飞越我们的那一刻。 您可以在https://www.n2yo.com/passes/?s=25338、https://www.n2yo.com/passes/?s=28654https://www.n2yo.com上在线查询飞行时间/分别通过/?s = 33591



为了不坐在计算机旁,将等待正确的时间添加到程序中。 这也将允许您在Raspberry Pi上运行程序,而无需使用显示器和键盘。

 import datetime def wait_for_start(dt): # Wait for the start while True: now = datetime.datetime.now() diff = int((dt - now).total_seconds()) print("{:02d}:{:02d}:{:02d}: Recording will be started after {}m {:02d}s...".format(now.hour, now.minute, now.second, int(diff / 60), diff % 60)) time.sleep(5) if diff <= 1: break wait_for_start(datetime.datetime(2019, 5, 18, 21, 49, 0)) 


顺便说一句,要在Raspberry Pi上运行脚本并在关闭控制台后使其运行,您需要输入命令“ nohup python records.py&”。

一切就绪,运行脚本并可以执行其他操作,录制持续约20分钟。 同时,可能会出现问题-是否有可能用肉眼看到卫星的通过? 根据表,其最大亮度约为5.5m 量级 ,理想条件下人眼的极限为6m。 即 从理论上讲,在远离城市的真正黑暗的天空中,从理论上可以注意到NOAA卫星的通过,在城市中没有机会(正如他们在哈布雷Habré)上所写的那样,一代人从未见过银河系

该脚本的结果是一个录制的wav文件,其频谱显示在屏幕截图中。



我们看到了一个完全可区分的信号,尽管当然带有用于接收NOAA的特殊天线 ,但质量会好得多。 信号格式称为APT( 自动图片传输 ),从中可以获取地球表面的图像,如果有人感兴趣,则可以单独考虑对其进行解码。 但是,当然有现成的程序,您可以使用WxToImg或MultiPSK解码此类信号。

有趣的是看到频谱中的多普勒频移,这是由于卫星飞过我们而发生的。 也许,知道它的时间和频率偏移(以赫兹为单位),就不难计算出它的速度。

当然,该程序不仅可以用于记录NOAA,还可以在设置中指定任何带宽和频率。 对于那些想自己尝试SoapySDR的人,程序代码完全位于扰流板下面。

源代码
 from __future__ import print_function import SoapySDR import numpy as np import struct import sys import time import datetime def wait_for_start(dt): # Wait for the start while True: now = datetime.datetime.now() diff = int((dt - now).total_seconds()) print("{:02d}:{:02d}:{:02d}: Recording will be started after {}m {:02d}s...".format(now.hour, now.minute, now.second, int(diff / 60), diff % 60)) time.sleep(5) if diff <= 1: break def sdr_enumerate(): # Enumerate SDR devices print("SDR devices:") for d in SoapySDR.Device.enumerate(''): print(d) print() def sdr_init(): soapy_device = "rtlsdr" device = SoapySDR.Device({"driver": soapy_device}) channels = list(range(device.getNumChannels(SoapySDR.SOAPY_SDR_RX))) print("Channels:", channels) ch = channels[0] sample_rates = device.listSampleRates(SoapySDR.SOAPY_SDR_RX, ch) print("Sample rates:\n", sample_rates) print("Gain controls:") for gain in device.listGains(SoapySDR.SOAPY_SDR_RX, ch): print(" %s: %s" % (gain, device.getGainRange(SoapySDR.SOAPY_SDR_RX, ch, gain))) frequencies = device.listFrequencies(SoapySDR.SOAPY_SDR_RX, ch) print("Frequencies names:", frequencies) frequency_name = frequencies[0] print("Frequency channel name:", frequency_name) print("Frequency range:", device.getFrequencyRange(SoapySDR.SOAPY_SDR_RX, ch, frequency_name)[0]) print() return device def sdr_record(device, frequency, sample_rate, gain, blocks_count): print("Frequency:", frequency) print("Sample rate:", sample_rate) print("Gain:", gain) channel = 0 # Always for RTL-SDR device.setFrequency(SoapySDR.SOAPY_SDR_RX, channel, "RF", frequency) device.setGain(SoapySDR.SOAPY_SDR_RX, channel, "TUNER", gain) device.setGainMode(SoapySDR.SOAPY_SDR_RX, channel, False) device.setSampleRate(SoapySDR.SOAPY_SDR_RX, channel, sample_rate) data_format = SoapySDR.SOAPY_SDR_CS8 # if 'rtlsdr' in soapy_device or 'hackrf' in soapy_device else SoapySDR.SOAPY_SDR_CS16 stream = device.setupStream(SoapySDR.SOAPY_SDR_RX, data_format, [channel], {}) device.activateStream(stream) block_size = device.getStreamMTU(stream) print("Block size:", block_size) print("Data format:", data_format) print() # IQ: 2 digits ver variable buffer_format = np.int8 buffer_size = 2 * block_size # I + Q samples buffer = np.empty(buffer_size, buffer_format) # Number of blocks to save block, max_blocks = 0, blocks_count # Save to file file_name = "HDSDR_%s_%dkHz_RF.wav" % (datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%SZ"), frequency/1000) print("Saving file:", file_name) with open(file_name, "wb") as wav: # Wav data info bits_per_sample = 16 channels_num, samples_num = 2, int(max_blocks * block_size) subchunk_size = 16 # always 16 for PCM subchunk2_size = int(samples_num * channels_num * bits_per_sample / 8) block_alignment = int(channels_num * bits_per_sample / 8) # Write RIFF header wav.write('RIFF'.encode('utf-8')) wav.write(struct.pack('<i', 4 + (8 + subchunk_size) + (8 + subchunk2_size))) # Size of the overall file wav.write('WAVE'.encode('utf-8')) # Write fmt subchunk wav.write('fmt '.encode('utf-8')) # chunk type wav.write(struct.pack('<i', subchunk_size)) # subchunk data size (16 for PCM) wav.write(struct.pack('<h', 1)) # compression type 1 - PCM wav.write(struct.pack('<h', channels_num)) # channels wav.write(struct.pack('<i', int(sample_rate))) # sample rate wav.write(struct.pack('<i', int(sample_rate * bits_per_sample * channels_num/ 8))) # byte rate wav.write(struct.pack('<h', block_alignment)) # block alignment wav.write(struct.pack('<h', bits_per_sample)) # sample depth # Write data subchunk wav.write('data'.encode('utf-8')) wav.write(struct.pack('<i', subchunk2_size)) while True: d_info = device.readStream(stream, [buffer], buffer_size) if d_info.ret > 0: data = buffer[0:2*d_info.ret] fileData = data if data_format == SoapySDR.SOAPY_SDR_CS8: fileData = data.astype('int16') wav.write(fileData) print("Block %d saved: %d bytes" % (block, 2*d_info.ret)) block += 1 if block > max_blocks: break device.deactivateStream(stream) device.closeStream(stream) if __name__ == "__main__": print("App started") # Forecast for active NOAA satellites # NOAA 15: 137.620, https://www.n2yo.com/passes/?s=25338 # NOAA 18: 137.9125, https://www.n2yo.com/passes/?s=28654 # NOAA 19: 137.100, https://www.n2yo.com/passes/?s=33591 # Wait for the start: 18-May 21:49 21:49: wait_for_start(datetime.datetime(2019, 5, 18, 21, 49, 0)) device = sdr_init() t_start = time.time() sdr_record(device, frequency=137912500, sample_rate=250000, gain=35, blocks_count=2100) print("Recording complete, time = %ds" % int(time.time() - t_start)) print() 


SoapySDR plus的优点是,相同的程序只需进行最小的更改即可与其他接收器一起使用,例如SDRPlay或HackRF。 嗯,关于跨平台的内容也已经提到过。

如果读者仍然对无线电接收感兴趣,可以考虑通过基于一个硬件创建多个虚拟接收器的示例,将SDR与GNU Radio一起使用。

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


All Articles