Radio definida por software: ¿cómo funciona? Parte 3

Hola Habr

La segunda parte examinó los aspectos prácticos del uso de SDR. En esta parte, veremos cómo recibir datos satelitales del clima NOAA utilizando Python y el receptor RTL-SDR económico ($ 30). El código considerado funcionará en todas partes: en Windows, OSX, Linux e incluso en Raspberry Pi.



A quién le importa, continuó bajo el corte.

SoapySDR


Hay bastantes fabricantes de varios dispositivos SDR, y sería muy inconveniente admitir cada uno por separado, y es costoso en términos de compra de hardware para pruebas. En principio, para el acceso unificado, hay dos bibliotecas que se han convertido esencialmente en el estándar. La primera es la interfaz ExtIO DLL bastante antigua, que probablemente no tenga menos de 10 años, la segunda es la biblioteca SoapySDR más moderna, que examinaremos.

SoapySDR es un conjunto de bibliotecas multiplataforma escritas en C ++ que proporcionan acceso unificado a dispositivos SDR, tanto receptores como transceptores. Si el fabricante crea una interfaz de este tipo, su dispositivo funcionará automáticamente con un número bastante grande de programas populares (GQRX, Radio GNU, CubicSDR, etc.). Casi todos los fabricantes adecuados, excepto algunos, (aprovechando esta oportunidad, le envío mis saludos a EE) tienen soporte SoapySDR, puede encontrar una lista de dispositivos compatibles en la página del proyecto . Como puede ver, es bastante grande e incluye HackRF, USRP, SDRPlay, LimeSDR, RTL-SDR, Red Pitaya y muchos otros.

La biblioteca SoapySDR es multiplataforma, es decir. el código escrito para él funcionará en Windows, OSX, Linux e incluso en Raspberry Pi. Para Windows, las bibliotecas requeridas son parte del paquete PothosSDR ; para otras plataformas, SoapySDR deberá compilarse de forma independiente. Es necesario compilar dos partes: la biblioteca en sí y el "complemento" para el receptor deseado, en nuestro caso será SoapyRTLSDR (en Windows, la biblioteca también se puede ensamblar desde la fuente, para esto necesita Visual Studio, Cmake y SWIG). Ahora todo está listo y puedes escribir código.

Importamos la biblioteca y obtenemos una lista de receptores:

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

Conectamos el receptor, ejecutamos el código y vemos una lista de dispositivos, entre los cuales está nuestro rtlsdr.



El resto de los dispositivos son tarjetas de sonido, como recordamos, históricamente los primeros SDR funcionaban con precisión a través de la entrada lineal de la PC, y la biblioteca también los admite. Obtenemos información sobre el dispositivo: la cantidad de canales disponibles, el rango de frecuencia, 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]) 

Iniciamos el programa y vemos información sobre el receptor:



Vemos que el receptor tiene un canal de entrada con el nombre "RF", las posibles frecuencias de muestreo [250000.0, 1024000.0, 1536000.0, 1792000.0, 1920000.0, 2048000.0, 2160000.0, 2560000.0, 2880000.0, 3200000.0] y el rango de frecuencia de 24 MHz-1.7 GHz.

Hack de vida: los mismos datos también se pueden obtener de la línea de comando escribiendo el comando SoapySDRUtil --probe = "driver = rtlsdr" .

Sabiendo esto, podemos grabar el flujo de datos en WAV. Como se mencionó en la parte anterior, los datos del SDR están representados por un flujo de señales llamadas I y Q, que son muestras del ADC, aproximadamente se pueden representar como datos RAW de la cámara. Cualquier persona interesada en más detalles puede leer, por ejemplo, aquí . Es suficiente para nosotros saber que podemos escribir estos datos, y otros programas SDR pueden trabajar con ellos.

El registro en sí es bastante simple: la función readStream llena el búfer si hay datos, si aún no hay datos, se devolverá -1. A continuación se muestra un código de registro de 10 muestras (se omiten partes no esenciales del código).

 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 

El resultado en la captura de pantalla:



Como puede ver, obtenemos bloques de datos del dispositivo, el tamaño de un bloque es de 131072 bytes, que a una frecuencia de muestreo de 250,000 nos da una duración de aproximadamente medio segundo. En general, aquellos que trabajaron previamente con tarjetas de sonido en Windows encontrarán mucho en común.

Para la prueba, escriba el archivo y verifique que todo esté bien; se puede reproducir en SDR #. Hay otro truco aquí: para que SDR # muestre correctamente las frecuencias de las estaciones, el nombre del archivo debe estar escrito en un formato compatible con HDSDR, en la forma "HDSDR_20190518_115500Z_101000kHz_RF.wav" (como puede adivinar, la fecha y la hora están en GMT al principio, luego la frecuencia en kilohercios) . Esto es fácil de escribir en Python:

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

Primero, verifique la banda de FM. Todo está bien, la estación es visible, se está reproduciendo música, RDS está funcionando.



Puede comenzar a grabar NOAA.

Ingesta de NOAA


Entonces, tenemos un receptor y un programa de grabación. Nos interesarán los satélites meteorológicos NOAA 15, NOAA 18 y NOAA 19 que transmiten imágenes de la superficie de la Tierra a frecuencias de 137.620, 137.9125 y 137.100 MHz. La principal dificultad aquí es que necesitas "atrapar" el momento en que el satélite vuela sobre nosotros. Puede averiguar el tiempo de vuelo en línea en https://www.n2yo.com/passes/?s=25338 , https://www.n2yo.com/passes/?s=28654 y https://www.n2yo.com / pasa /? s = 33591 respectivamente.



Para no sentarse en la computadora, agregue al programa la espera del momento adecuado. Esto también le permitirá ejecutar el programa en la Raspberry Pi, sin pantalla ni teclado.

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


Por cierto, para ejecutar el script en Raspberry Pi y dejarlo funcionando después de cerrar la consola, debe ingresar el comando "nohup python recorder.py &".

Todo está listo, ejecuta el script y puede hacer otras cosas, la grabación dura unos 20 minutos. En paralelo, puede surgir la pregunta: ¿es posible ver el paso del satélite a simple vista? Según la tabla, su brillo máximo es de aproximadamente 5,5 m de magnitud , el límite del ojo humano en condiciones ideales es de 6 m. Es decir En un cielo realmente oscuro, más allá de la ciudad, el paso del satélite NOAA se puede notar teóricamente, en la ciudad, por supuesto, no hay posibilidad (como escribieron en Habré , una generación de personas que nunca han visto la Vía Láctea en su vida ya ha crecido).

El resultado del script es un archivo wav grabado, su espectro se muestra en la captura de pantalla.



Vemos una señal completamente distinguible, aunque, por supuesto, con una antena especial para recibir NOAA, la calidad sería mucho mejor. El formato de la señal se llama APT ( transmisión automática de imágenes ), de él puede obtener una imagen de la superficie de la tierra, si alguien está interesado, puede considerar por separado su decodificación. Pero, por supuesto, hay programas listos para usar, puede decodificar tales señales usando WxToImg o MultiPSK.

Es interesante ver el cambio Doppler en el espectro, que ocurre porque el satélite vuela más allá de nosotros. Probablemente, no es difícil calcular su velocidad, conociendo el cambio de tiempo y frecuencia en hercios.

Por supuesto, el programa se puede usar no solo para grabar NOAA, sino que se puede especificar cualquier ancho de banda y frecuencia en la configuración. Para aquellos que quieran experimentar con SoapySDR, el código del programa se encuentra completamente debajo del spoiler.

Código fuente
 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 es que el mismo programa con cambios mínimos funcionará con otros receptores, por ejemplo con SDRPlay o HackRF. Bueno, también se ha mencionado la multiplataforma.

Si los lectores aún tienen interés en el tema de la recepción de radio, puede considerar un ejemplo de uso de SDR con Radio GNU creando varios receptores virtuales basados ​​en un hardware.

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


All Articles