Software Defined Radio - wie funktioniert es? Teil 3

Hallo Habr.

Der zweite Teil untersuchte die praktischen Aspekte der Verwendung von SZR. In diesem Teil werden wir uns ansehen, wie NOAA-Wettersatellitendaten mit Python und dem kostengünstigen (30 US-Dollar) RTL-SDR-Empfänger empfangen werden. Der betrachtete Code funktioniert überall - unter Windows, OSX, Linux und sogar auf dem Raspberry Pi.



Wen kümmert es, fuhr unter dem Schnitt fort.

SoapySDR


Es gibt eine ganze Reihe von Herstellern verschiedener SDR-Geräte, und es wäre sehr unpraktisch, jedes einzeln zu unterstützen, und es ist teuer, Hardware zum Testen zu kaufen. Grundsätzlich gibt es für den einheitlichen Zugriff zwei Bibliotheken, die im Wesentlichen zum Standard geworden sind. Die erste ist die ziemlich alte ExtIO DLL- Schnittstelle, die wahrscheinlich nicht weniger als 10 Jahre alt ist, die zweite ist die modernere SoapySDR- Bibliothek, die wir untersuchen werden.

SoapySDR ist eine Reihe plattformübergreifender Bibliotheken, die in C ++ geschrieben wurden und einen einheitlichen Zugriff auf SDR-Geräte, sowohl Empfänger als auch Transceiver, ermöglichen. Wenn der Hersteller eine solche Schnittstelle herstellt, arbeitet sein Gerät automatisch mit einer relativ großen Anzahl gängiger Programme (GQRX, GNU Radio, CubicSDR usw.). Mit Ausnahme einiger adäquater Hersteller (bei dieser Gelegenheit sende ich meine Grüße an EE) haben SoapySDR-Unterstützung. Eine Liste der unterstützten Geräte finden Sie auf der Projektseite . Wie Sie sehen können, ist es ziemlich groß und umfasst HackRF, USRP, SDRPlay, LimeSDR, RTL-SDR, Red Pitaya und viele andere.

Die SoapySDR-Bibliothek ist plattformübergreifend, d.h. Der dafür geschriebene Code funktioniert unter Windows, OSX, Linux und sogar auf dem Raspberry Pi. Für Windows sind die erforderlichen Bibliotheken Teil des PothosSDR- Pakets, für andere Plattformen muss SoapySDR unabhängig kompiliert werden. Es ist notwendig, zwei Teile zu kompilieren - die Bibliothek selbst und das „Plug-In“ für den gewünschten Empfänger. In unserem Fall handelt es sich um SoapyRTLSDR (unter Windows kann die Bibliothek auch aus dem Quellcode zusammengestellt werden. Dazu benötigen Sie Visual Studio, Cmake und SWIG). Jetzt ist alles fertig und Sie können Code schreiben.

Wir importieren die Bibliothek und erhalten eine Liste der Empfänger:

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

Wir verbinden den Empfänger, führen den Code aus und sehen eine Liste der Geräte, darunter unser rtlsdr.



Der Rest der Geräte sind Soundkarten, wie wir uns erinnern. Historisch gesehen arbeiteten die ersten SDRs genau über den linearen PC-Eingang, und die Bibliothek unterstützt sie auch. Wir erhalten Informationen über das Gerät - Anzahl der verfügbaren Kanäle, Frequenzbereich usw.:

 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]) 

Wir starten das Programm und sehen Informationen über den Empfänger:



Wir sehen, dass der Empfänger einen Eingangskanal mit dem Namen "RF", den möglichen Abtastfrequenzen [250000.0, 1024000.0, 1536000.0, 1792000.0, 1920000.0, 2048000.0, 2160000.0, 2560000.0, 2880000.0, 3200000.0] und dem Frequenzbereich 24 MHz-1.7 GHz hat.

Life Hack - Die gleichen Daten können auch über die Befehlszeile abgerufen werden, indem Sie den Befehl SoapySDRUtil --probe = "driver = rtlsdr" eingeben .

Wenn wir das wissen, können wir den Datenstrom in WAV aufzeichnen. Wie im vorherigen Teil erwähnt, werden Daten vom SDR durch einen Strom von Signalen dargestellt, die als I und Q bezeichnet werden und Abtastwerte vom ADC sind. Sie können ungefähr als RAW-Daten von der Kamera dargestellt werden. Wer sich für weitere Details interessiert, kann hier zum Beispiel lesen. Es reicht uns zu wissen, dass wir diese Daten schreiben können und andere SDR-Programme dann mit ihnen arbeiten können.

Der Datensatz selbst ist recht einfach: Die Funktion readStream füllt den Puffer, wenn Daten vorhanden sind. Wenn keine Daten vorhanden sind, wird -1 zurückgegeben. Unten finden Sie einen Datensatzcode mit 10 Beispielen (nicht wesentliche Teile des Codes werden weggelassen).

 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 

Das Ergebnis im Screenshot:



Wie Sie sehen können, erhalten wir Datenblöcke vom Gerät. Die Größe eines Blocks beträgt 131072 Byte, was bei einer Abtastrate von 250.000 eine Dauer von etwa einer halben Sekunde ergibt. Im Allgemeinen haben diejenigen, die zuvor mit Soundkarten in Windows gearbeitet haben, viele Gemeinsamkeiten.

Schreiben Sie für den Test die Datei und überprüfen Sie, ob alles in Ordnung ist - sie kann in SDR # abgespielt werden. Es gibt noch einen Trick: Damit SDR # die Frequenzen der Sender korrekt anzeigt, muss der Dateiname in einem mit HDSDR kompatiblen Format in der Form „HDSDR_20190518_115500Z_101000kHz_RF.wav“ geschrieben werden (wie Sie sich vorstellen können, sind Datum und Uhrzeit zu Beginn in GMT angegeben, dann die Frequenz in Kilohertz) . Dies ist einfach in Python zu schreiben:

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

Überprüfen Sie zuerst das FM-Band. Alles ist in Ordnung, der Sender ist sichtbar, Musik spielt, RDS funktioniert.



Sie können mit der Aufnahme von NOAA beginnen.

NOAA-Aufnahme


Wir haben also einen Empfänger und ein Aufnahmeprogramm. Wir werden an den Wettersatelliten NOAA 15, NOAA 18 und NOAA 19 interessiert sein, die Bilder der Erdoberfläche mit Frequenzen von 137,620, 137,9125 und 137,100 MHz übertragen. Die Hauptschwierigkeit hierbei ist, dass Sie den Moment "einfangen" müssen, in dem der Satellit über uns fliegt. Die Flugzeit finden Sie online unter https://www.n2yo.com/passes/?s=25338 , https://www.n2yo.com/passes/?s=28654 und https://www.n2yo.com / passt /? s = 33591 .



Um nicht am Computer zu sitzen, fügen Sie dem Programm das Warten auf die richtige Zeit hinzu. Auf diese Weise können Sie das Programm auch auf dem Raspberry Pi ohne Display und Tastatur ausführen.

 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)) 


Übrigens müssen Sie den Befehl "nohup python recorder.py &" eingeben, um das Skript auf dem Raspberry Pi auszuführen und nach dem Schließen der Konsole funktionsfähig zu lassen.

Alles ist fertig, führen Sie das Skript aus und können andere Dinge tun, die Aufnahme dauert ca. 20 Minuten. Parallel dazu kann sich die Frage stellen: Ist es möglich, den Durchgang des Satelliten mit bloßem Auge zu sehen? Laut Tabelle beträgt seine maximale Helligkeit etwa 5,5 m, die Grenze des menschlichen Auges unter idealen Bedingungen beträgt 6 m. Das heißt, In einem wirklich dunklen Himmel, weit jenseits der Stadt, kann der Durchgang des NOAA-Satelliten theoretisch bemerkt werden. In der Stadt gibt es natürlich keine Chance (wie sie über Habré geschrieben haben , ist eine Generation von Menschen, die die Milchstraße noch nie in ihrem Leben gesehen haben, bereits gewachsen).

Das Ergebnis des Skripts ist eine aufgezeichnete WAV-Datei, deren Spektrum im Screenshot dargestellt ist.



Wir sehen ein vollständig unterscheidbares Signal, obwohl die Qualität mit einer speziellen Antenne zum Empfangen von NOAA natürlich viel besser wäre. Das Signalformat heißt APT ( Automatic Picture Transmission ). Daraus können Sie ein Bild der Erdoberfläche erhalten. Wenn jemand interessiert ist, können Sie dessen Dekodierung separat betrachten. Aber es gibt natürlich vorgefertigte Programme, die Sie mit WxToImg oder MultiPSK dekodieren können.

Es ist interessant, die Doppler-Verschiebung im Spektrum zu sehen, die auftritt, weil der Satellit an uns vorbei fliegt. Wahrscheinlich ist es nicht schwierig, seine Geschwindigkeit zu berechnen, wenn man die Zeit- und Frequenzverschiebung in Hertz kennt.

Natürlich kann das Programm nicht nur zur Aufzeichnung von NOAA verwendet werden, in den Einstellungen können auch jede Bandbreite und Frequenz angegeben werden. Für diejenigen, die selbst mit SoapySDR experimentieren möchten, befindet sich der Programmcode vollständig unter dem Spoiler.

Quellcode
 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 ist, dass dasselbe Programm mit minimalen Änderungen mit anderen Empfängern funktioniert, beispielsweise mit SDRPlay oder HackRF. Auch plattformübergreifend wurde bereits erwähnt.

Wenn Leser immer noch Interesse am Thema Funkempfang haben, können Sie ein Beispiel für die Verwendung von SDR mit GNU Radio in Betracht ziehen, indem Sie mehrere virtuelle Empfänger auf der Basis einer Hardware erstellen.

Source: https://habr.com/ru/post/de452390/


All Articles