Radio définie par logiciel - comment ça marche? 3e partie

Salut, Habr.

La deuxième partie a examiné les aspects pratiques de l'utilisation du SDR. Dans cette partie, nous verrons comment recevoir des données de satellites météorologiques NOAA en utilisant Python et le récepteur RTL-SDR bon marché (30 $). Le code considéré fonctionnera partout - sur Windows, OSX, Linux et même sur le Raspberry Pi.



Peu importe, a continué sous la coupe.

SoapySDR


Il existe de nombreux fabricants de divers appareils SDR, et il serait très gênant de les prendre en charge séparément, et cela coûte cher en termes d’achat de matériel pour les tests. En principe, pour l'accès unifié, il existe deux bibliothèques qui sont devenues essentiellement la norme. La première est l'interface assez ancienne de la DLL ExtIO , qui a probablement pas moins de 10 ans, la seconde est la bibliothèque SoapySDR plus moderne, que nous examinerons.

SoapySDR est un ensemble de bibliothèques multiplateformes écrites en C ++ qui fournissent un accès unifié aux appareils SDR, récepteurs et émetteurs-récepteurs. Si le fabricant crée une telle interface, son appareil fonctionnera automatiquement avec un assez grand nombre de programmes populaires (GQRX, GNU Radio, CubicSDR, etc.). Presque tous les fabricants adéquats, à l'exception de certains, (saisissant cette occasion, j'envoie mes salutations à EE) ont le support SoapySDR, une liste des appareils pris en charge peut être trouvée sur la page du projet . Comme vous pouvez le voir, il est assez grand et comprend HackRF, USRP, SDRPlay, LimeSDR, RTL-SDR, Red Pitaya et bien d'autres.

La bibliothèque SoapySDR est multiplateforme, c'est-à-dire le code écrit pour cela fonctionnera sous Windows, OSX, Linux et même sur le Raspberry Pi. Pour Windows, les bibliothèques requises font partie du package PothosSDR ; pour les autres plates-formes, SoapySDR devra être compilé indépendamment. Il est nécessaire de compiler deux parties - la bibliothèque elle - même et le «plug-in» pour le récepteur souhaité, dans notre cas, ce sera SoapyRTLSDR (sous Windows, la bibliothèque peut également être assemblée à partir de la source, pour cela, vous avez besoin de Visual Studio, Cmake et SWIG). Maintenant, tout est prêt et vous pouvez écrire du code.

Nous importons la bibliothèque et obtenons une liste de récepteurs:

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

Nous connectons le récepteur, exécutons le code et voyons une liste d'appareils, parmi lesquels se trouve notre rtlsdr.



Les autres appareils sont des cartes son, comme nous le rappelons, historiquement, les premiers SDR fonctionnaient précisément via l'entrée linéaire du PC, et la bibliothèque les prend également en charge. Nous obtenons des informations sur l'appareil - le nombre de canaux disponibles, la gamme de fréquences, etc.:

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

Nous commençons le programme et voyons des informations sur le récepteur:



Nous voyons que le récepteur a un canal d'entrée avec le nom "RF", les fréquences d'échantillonnage possibles [250000.0, 1024000.0, 1536000.0, 1792000.0, 1920000.0, 2048000.0, 2160000.0, 2560000.0, 2880000.0, 3200000.0] et la plage de fréquences de 24 MHz-1,7 GHz.

Life hack - les mêmes données peuvent également être obtenues à partir de la ligne de commande en tapant la commande SoapySDRUtil --probe = "driver = rtlsdr" .

Sachant cela, nous pouvons enregistrer le flux de données en WAV. Comme mentionné dans la partie précédente, les données du SDR sont représentées par un flux de signaux appelés I et Q, qui sont des échantillons de l'ADC, grossièrement ils peuvent être représentés comme des données RAW de la caméra. Toute personne intéressée par plus de détails peut lire, par exemple, ici . Il nous suffit de savoir que nous pouvons écrire ces données, et d'autres programmes SDR peuvent ensuite travailler avec eux.

L'enregistrement lui-même est assez simple - la fonction readStream remplit le tampon s'il y a des données, s'il n'y a pas encore de données, alors -1 sera retourné. Vous trouverez ci-dessous un code d'enregistrement de 10 échantillons (les parties non essentielles du code sont omises).

 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 

Le résultat dans la capture d'écran:



Comme vous pouvez le voir, nous recevons des blocs de données de l'appareil, la taille d'un bloc est de 131072 octets, ce qui à un taux d'échantillonnage de 250 000 nous donne une durée d'environ une demi-seconde. En général, ceux qui ont déjà travaillé avec des cartes son sous Windows trouveront beaucoup en commun.

Pour le test, écrivez le fichier et vérifiez que tout va bien - il peut être lu en SDR #. Il y a une autre astuce ici - pour que SDR # affiche correctement les fréquences des stations, le nom du fichier doit être écrit dans un format compatible avec HDSDR, de la forme "HDSDR_20190518_115500Z_101000kHz_RF.wav" (comme vous pouvez le deviner, la date et l'heure sont en GMT au début, puis la fréquence en kilohertz) . C'est facile à écrire en Python:

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

Tout d'abord, vérifiez la bande FM. Tout va bien, la station est visible, la musique joue, RDS fonctionne.



Vous pouvez commencer à enregistrer la NOAA.

Apport de NOAA


Nous avons donc un récepteur et un programme d'enregistrement. Nous serons intéressés par les satellites météorologiques NOAA 15, NOAA 18 et NOAA 19 transmettant des images de la surface de la Terre à des fréquences de 137,620, 137,9125 et 137,100 MHz. La principale difficulté ici est que vous devez "attraper" le moment où le satellite survole nous. Vous pouvez connaître le temps de vol en ligne sur https://www.n2yo.com/passes/?s=25338 , https://www.n2yo.com/passes/?s=28654 et https://www.n2yo.com / passe /? s = 33591 respectivement.



Afin de ne pas vous asseoir devant l'ordinateur, ajoutez au programme l'attente au bon moment. Cela vous permettra également d'exécuter le programme sur le Raspberry Pi, sans écran ni clavier.

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


Par ailleurs, pour exécuter le script sur le Raspberry Pi et le laisser fonctionner après avoir fermé la console, vous devez entrer la commande "nohup python recorder.py &".

Tout est prêt, lancez le script et pouvez faire autre chose, l'enregistrement dure environ 20 minutes. En parallèle, la question peut se poser - est-il possible de voir le passage du satellite à l'œil nu? Selon le tableau, sa luminosité maximale est d'environ 5,5 m, la limite de l'œil humain dans des conditions idéales est de 6 m. C'est-à-dire dans un ciel vraiment sombre, bien au-delà de la ville, le passage du satellite NOAA peut théoriquement être remarqué, dans la ville, bien sûr, il n'y a aucune chance (comme ils l'ont écrit sur Habré , une génération de gens qui n'ont jamais vu la Voie lactée dans leur vie a déjà grandi).

Le résultat du script est un fichier wav enregistré, son spectre est montré dans la capture d'écran.



Nous voyons un signal complètement distinct, bien que, bien sûr, avec une antenne spéciale pour recevoir la NOAA, la qualité serait bien meilleure. Le format du signal est appelé APT ( Automatic Picture Transmission ), à partir duquel vous pouvez obtenir une image de la surface de la terre, si quelqu'un est intéressé, vous pouvez considérer séparément son décodage. Mais il existe bien sûr des programmes prêts à l'emploi, vous pouvez décoder de tels signaux en utilisant WxToImg ou MultiPSK.

Il est intéressant de voir le décalage Doppler dans le spectre, qui se produit parce que le satellite passe devant nous. Probablement, il n'est pas difficile de calculer sa vitesse, connaissant le décalage horaire et fréquentiel en hertz.

Bien sûr, le programme peut être utilisé non seulement pour l'enregistrement de NOAA, mais toute bande passante et fréquence peuvent être spécifiées dans les paramètres. Pour ceux qui veulent expérimenter eux-mêmes SoapySDR, le code du programme est entièrement situé sous le spoiler.

Code source
 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 est que le même programme avec des changements minimes fonctionnera avec d'autres récepteurs, tels que SDRPlay ou HackRF. Eh bien, à propos de multiplateforme, a également été mentionné.

Si les lecteurs s'intéressent toujours au sujet de la réception radio, vous pouvez envisager un exemple d'utilisation de SDR avec GNU Radio en créant plusieurs récepteurs virtuels basés sur un matériel.

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


All Articles