7. BladeRF en Python

El bladeRF 2.0 (tambi茅n conocido como bladeRF 2.0 micro) de la empresa Nuand es un SDR basado en USB 3.0 con dos canales de recepci贸n, dos canales de transmisi贸n, un rango sintonizable de 47 MHz a 6 GHz y la capacidad de muestrear hasta 61 MHz o hasta 122 MHz cuando se piratea. Utiliza el circuito integrado de RF (RFIC) AD9361 al igual que el USRP B210 y PlutoSDR, por lo que el rendimiento de RF ser谩 similar. El bladeRF 2.0 se lanz贸 en 2021, mantiene un factor de forma peque帽o de 2,5鈥 x 4,5鈥 y viene en dos tama帽os de FPGA diferentes (xA4 y xA9). Si bien este cap铆tulo se centra en el bladeRF 2.0, gran parte del c贸digo tambi茅n se aplicar谩 al bladeRF original, que sali贸 en 2013.

bladeRF 2.0 glamour shot

Arquitectura del bladeRF

En un nivel alto, el bladeRF 2.0 se basa en el RFIC AD9361, combinado con un FPGA Cyclone V (ya sea el 49 kLE 5CEA4 o el 301 kLE 5CEA9) y un controlador Cypress FX3 USB 3.0. que tiene un n煤cleo ARM9 de 200 MHz en su interior, cargado con firmware personalizado. El diagrama de bloques del bladeRF 2.0 se muestra a continuaci贸n:

bladeRF 2.0 block diagram

La FPGA controla el RFIC, realiza filtrado digital y enmarca paquetes para transferirlos a trav茅s de USB (entre otras cosas). El codigo fuente de la imagen FPGA est谩 escrita en VHDL y requiere el software de dise帽o gratuito Quartus Prime Lite para compilar im谩genes personalizadas. Im谩genes precompiladas disponibles aqui.

El codigo fuente del firmware Cypress FX3 es de c贸digo abierto e incluye c贸digo para:

  1. Cargue la imagen FPGA

  2. Transfiera muestras de IQ entre la FPGA y el host a trav茅s de USB 3.0

  3. Controlar GPIO de la FPGA sobre UART

Desde una perspectiva de flujo de se帽al, hay dos canales de recepci贸n y dos canales de transmisi贸n, y cada canal tiene una entrada/salida de baja y alta frecuencia al RFIC, seg煤n la banda que se est茅 utilizando. Es por esta raz贸n que se necesita un interruptor electr贸nico de RF unipolar y bidireccional (SPDT) entre los conectores RFIC y SMA. La T de polarizaci贸n es un circuito integrado que proporciona ~4,5 V DC en el conector SMA y se utiliza para alimentar c贸modamente un amplificador externo u otros componentes de RF. Este desplazamiento de DC adicional se encuentra en el lado de RF del SDR, por lo que no interfiere con la operaci贸n b谩sica de recepci贸n/transmisi贸n.

JTAG es un tipo de interfaz de depuraci贸n que permite probar y verificar dise帽os durante el proceso de desarrollo.

Al final de este cap铆tulo, analizamos el oscilador VCTCXO, PLL y el puerto de expansi贸n.

Configuraci贸n del software y hardware

Ubuntu (o Ubuntu con WSL)

En Ubuntu y otros sistemas basados en Debian, puede instalar el software bladeRF con los siguientes comandos:

sudo apt update
sudo apt install cmake python3-pip libusb-1.0-0
cd ~
git clone --depth 1 https://github.com/Nuand/bladeRF.git
cd bladeRF/host
mkdir build && cd build
cmake ..
make -j8
sudo make install
sudo ldconfig
cd ../libraries/libbladeRF_bindings/python
sudo python3 setup.py install

Esto instalar谩 la biblioteca libbladerf, los enlaces de Python, las herramientas de l铆nea de comandos de bladerf, el descargador de firmware y el descargador de flujo de bits FPGA. Para verificar qu茅 versi贸n de la biblioteca instal贸, use bladerf-tool version (esta gu铆a fue escrita usando libbladeRF versi贸n v2.5.0).

Si est谩 utilizando Ubuntu a trav茅s de WSL, en el lado de Windows deber谩 reenviar el dispositivo USB bladeRF a WSL, primero instalando la 煤ltima versi贸n usbipd utility msi (esta gu铆a supone que tiene usbipd-win 4.0.0 o superior), luego abre PowerShell en modo administrador y ejecuta:

usbipd list
# (find the BUSID labeled bladeRF 2.0 and substitute it in the command below)
usbipd bind --busid 1-23
usbipd attach --wsl --busid 1-23

En el lado WSL, deber铆a poder ejecutar lsusb y ver un nuevo elemento llamado Nuand LLC bladeRF 2.0 micro. Tenga en cuenta que puede agregar el indicador --auto-attach al comando usbipd adjunto si desea que se vuelva a conectar autom谩ticamente.

(Puede que no sea necesario) Tanto para Linux nativo como para WSL, debemos instalar las reglas udev para no obtener errores de permisos:

sudo nano /etc/udev/rules.d/88-nuand.rules

y pegue la siguiente l铆nea:

ATTRS{idVendor}=="2cf0", ATTRS{idProduct}=="5250", MODE="0666"

Para guardar y salir de nano, use: control-o, luego Enter, luego control-x. Para actualizar udev, ejecute:

sudo udevadm control --reload-rules && sudo udevadm trigger

Si est谩s usando WSL y dice Failed to send reload request: No such file or directory, eso significa que el servicio udev no se est谩 ejecutando y necesitar谩s sudo nano /etc/wsl.conf y agrega las l铆neas:

[boot]
command="service udev start"

luego reinicie WSL usando el siguiente comando en PowerShell con admin: wsl.exe --shutdown.

Desenchufe y vuelva a enchufar su bladeRF (los usuarios de WSL deber谩n volver a conectarlo) y pruebe los permisos con:

bladerf-tool probe
bladerf-tool info

y sabr谩s que funcion贸 si ves tu bladeRF 2.0 en la lista y si no se ve Found a bladeRF via VID/PID, but could not open it due to insufficient permissions. Si funcion贸, anote la versi贸n de FPGA y la versi贸n de firmware.

(Opcional) Instale el firmware y las im谩genes FPGA m谩s recientes (v2.4.0 y v0.15.0 respectivamente cuando se escribi贸 esta gu铆a) usando:

cd ~/Downloads
wget https://www.nuand.com/fx3/bladeRF_fw_latest.img
bladerf-tool flash_fw bladeRF_fw_latest.img

# for xA4 use:
wget https://www.nuand.com/fpga/hostedxA4-latest.rbf
bladerf-tool flash_fpga hostedxA4-latest.rbf

# for xA9 use:
wget https://www.nuand.com/fpga/hostedxA9-latest.rbf
bladerf-tool flash_fpga hostedxA9-latest.rbf

Desenchufe y enchufe su bladeRF para realizar un ciclo de energ铆a.

Ahora probaremos su funcionalidad recibiendo 1 mill贸n de muestras en la banda de radio FM, a una frecuencia de muestreo de 10 MHz, en un archivo /tmp/samples.sc16:

bladerf-tool rx --num-samples 1000000 /tmp/samples.sc16 100e6 10e6

un par Hit stall for buffer se espera, pero sabr谩 si funcion贸 si ve un archivo /tmp/samples.sc16 de 4 MB.

Por 煤ltimo, probaremos la API de Python con:

python3
import bladerf
bladerf.BladeRF()
exit()

Sabr谩s que funcion贸 si ves algo como <BladeRF(<DevInfo(...)>)> y sin advertencias/errores.

Windows y MacOS

Para usuarios Windows, ver https://github.com/Nuand/bladeRF/wiki/Getting-Started%3A-Windows, y para usuarios MacOS, ver https://github.com/Nuand/bladeRF/wiki/Getting-started:-Mac-OSX.

API basicas para bladeRF en Python

Para empezar, sondeemos el bladeRF para obtener informaci贸n 煤til, utilizando el siguiente script. 隆No asigne a su script el nombre bladerf.py o entrar谩 en conflicto con el m贸dulo bladeRF Python!

from bladerf import _bladerf
import numpy as np
import matplotlib.pyplot as plt

sdr = _bladerf.BladeRF()

print("Device info:", _bladerf.get_device_list()[0])
print("libbladeRF version:", _bladerf.version()) # v2.5.0
print("Firmware version:", sdr.get_fw_version()) # v2.4.0
print("FPGA version:", sdr.get_fpga_version())   # v0.15.0

rx_ch = sdr.Channel(_bladerf.CHANNEL_RX(0)) # give it a 0 or 1
print("sample_rate_range:", rx_ch.sample_rate_range)
print("bandwidth_range:", rx_ch.bandwidth_range)
print("frequency_range:", rx_ch.frequency_range)
print("gain_modes:", rx_ch.gain_modes)
print("manual gain range:", sdr.get_gain_range(_bladerf.CHANNEL_RX(0))) # ch 0 or 1

Para bladeRF 2.0 xA9, la salida deber铆a verse as铆:

Device info: Device Information
    backend  libusb
    serial   f80a27b1010448dfb7a003ef7fa98a59
    usb_bus  2
    usb_addr 5
    instance 0
libbladeRF version: v2.5.0 ("2.5.0-git-624994d")
Firmware version: v2.4.0 ("2.4.0-git-a3d5c55f")
FPGA version: v0.15.0 ("0.15.0")
sample_rate_range: Range
    min   520834
    max   61440000
    step  2
    scale 1.0

bandwidth_range: Range
    min   200000
    max   56000000
    step  1
    scale 1.0

frequency_range: Range
    min   70000000
    max   6000000000
    step  2
    scale 1.0

gain_modes: [<GainMode.Default: 0>, <GainMode.Manual: 1>, <GainMode.FastAttack_AGC: 2>, <GainMode.SlowAttack_AGC: 3>, <GainMode.Hybrid_AGC: 4>]

manual gain range: Range
    min   -15
    max   60
    step  1
    scale 1.0

El par谩metro de ancho de banda establece el filtro utilizado por el SDR al realizar la operaci贸n de recepci贸n, por lo que normalmente lo configuramos para que sea igual o ligeramente menor que sample_rate/2. Es importante comprender los modos de ganancia, el SDR utiliza un modo de ganancia manual donde usted proporciona la ganancia en dB o un control de ganancia autom谩tico (AGC) que tiene tres configuraciones diferentes (r谩pido, lento, h铆brido). Para aplicaciones como la monitorizaci贸n del espectro, se recomienda la ganancia manual (para que pueda ver cu谩ndo van y vienen las se帽ales), pero para aplicaciones como la recepci贸n de una se帽al espec铆fica que espera que exista, el AGC ser谩 m谩s 煤til porque ajustar谩 autom谩ticamente la ganancia a permitir que la se帽al llene el convertidor anal贸gico a digital (ADC).

Para configurar los par谩metros principales del SDR, podemos agregar el siguiente c贸digo:

sample_rate = 10e6
center_freq = 100e6
gain = 50 # -15 to 60 dB
num_samples = int(1e6)

rx_ch.frequency = center_freq
rx_ch.sample_rate = sample_rate
rx_ch.bandwidth = sample_rate/2
rx_ch.gain_mode = _bladerf.GainMode.Manual
rx_ch.gain = gain

Recibir muestras en Python

A continuaci贸n, trabajaremos con el bloque de c贸digo anterior para recibir 1 mill贸n de muestras en la banda de radio FM, a una frecuencia de muestreo de 10 MHz, tal como lo hicimos antes. Cualquier antena en el puerto RX1 deber铆a poder recibir FM, ya que es muy potente. El siguiente c贸digo muestra c贸mo funciona la API de flujo s铆ncrono bladeRF; se debe configurar y crear un b煤fer de recepci贸n antes de que comience la recepci贸n. El bucle :code:` while True:` continuar谩 recibiendo muestras hasta que se alcance el n煤mero de muestras solicitadas. Las muestras recibidas se almacenan en una matriz numpy separada, para que podamos procesarlas una vez finalizado el ciclo.

# Setup synchronous stream
sdr.sync_config(layout = _bladerf.ChannelLayout.RX_X1, # or RX_X2
                fmt = _bladerf.Format.SC16_Q11, # int16s
                num_buffers    = 16,
                buffer_size    = 8192,
                num_transfers  = 8,
                stream_timeout = 3500)

# Create receive buffer
bytes_per_sample = 4 # don't change this, it will always use int16s
buf = bytearray(1024 * bytes_per_sample)

# Enable module
print("Starting receive")
rx_ch.enable = True

# Receive loop
x = np.zeros(num_samples, dtype=np.complex64) # storage for IQ samples
num_samples_read = 0
while True:
    if num_samples > 0 and num_samples_read == num_samples:
        break
    elif num_samples > 0:
        num = min(len(buf) // bytes_per_sample, num_samples - num_samples_read)
    else:
        num = len(buf) // bytes_per_sample
    sdr.sync_rx(buf, num) # Read into buffer
    samples = np.frombuffer(buf, dtype=np.int16)
    samples = samples[0::2] + 1j * samples[1::2] # Convert to complex type
    samples /= 2048.0 # Scale to -1 to 1 (its using 12 bit ADC)
    x[num_samples_read:num_samples_read+num] = samples[0:num] # Store buf in samples array
    num_samples_read += num

print("Stopping")
rx_ch.enable = False
print(x[0:10]) # look at first 10 IQ samples
print(np.max(x)) # if this is close to 1, you are overloading the ADC, and should reduce the gain

Se esperan algunos Hit stop for buffer al final. El 煤ltimo n煤mero impreso muestra la muestra m谩xima recibida; querr谩s ajustar tu ganancia para intentar obtener ese valor entre 0,5 y 0,8. Si es 0,999, significa que su receptor est谩 sobrecargado/saturado y la se帽al se distorsionar谩 (se ver谩 manchada en todo el dominio de la frecuencia).

Para visualizar la se帽al recibida, mostremos las muestras de IQ usando un espectrograma (consulte Espectrograma para obtener m谩s detalles sobre c贸mo funcionan los espectrogramas). Agregue lo siguiente al final del bloque de c贸digo anterior:

# Create spectrogram
fft_size = 2048
num_rows = len(x) // fft_size # // is an integer division which rounds down
spectrogram = np.zeros((num_rows, fft_size))
for i in range(num_rows):
    spectrogram[i,:] = 10*np.log10(np.abs(np.fft.fftshift(np.fft.fft(x[i*fft_size:(i+1)*fft_size])))**2)
extent = [(center_freq + sample_rate/-2)/1e6, (center_freq + sample_rate/2)/1e6, len(x)/sample_rate, 0]
plt.imshow(spectrogram, aspect='auto', extent=extent)
plt.xlabel("Frequency [MHz]")
plt.ylabel("Time [s]")
plt.show()
bladeRF spectrogram example

Cada l铆nea ondulada vertical es una se帽al de radio FM. No tengo idea de a qu茅 se debe el pulso en el lado derecho, reducir la ganancia no hizo que desapareciera.

Transmisi贸n de muestras en Python

El proceso de transmisi贸n de muestras con bladeRF es muy similar al de recepci贸n. La principal diferencia es que debemos generar las muestras para transmitir y luego escribirlas en bladeRF usando el m茅todo sync_tx que puede manejar todo nuestro lote de muestras a la vez (hasta ~4B muestras). El siguiente c贸digo muestra c贸mo transmitir un tono simple y luego repetirlo 30 veces. El tono se genera usando numpy y luego se escala para que est茅 entre -32767 y 32767, de modo que pueda almacenarse como int16s. Luego, el tono se convierte en bytes y se utiliza como b煤fer de transmisi贸n. La API de flujo s铆ncrono se utiliza para transmitir las muestras, y el bucle :code:` while True:` continuar谩 transmitiendo muestras hasta que se alcance el n煤mero de repeticiones solicitadas. Si desea transmitir muestras desde un archivo, simplemente use samples = np.fromfile('yourfile.iq', dtype=np.int16) (o cualquier tipo de datos que sean) para leer las muestras, y luego convi茅rtalos a bytes usando samples.tobytes().

from bladerf import _bladerf
import numpy as np

sdr = _bladerf.BladeRF()
tx_ch = sdr.Channel(_bladerf.CHANNEL_TX(0)) # give it a 0 or 1

sample_rate = 10e6
center_freq = 100e6
gain = 0 # -15 to 60 dB. for transmitting, start low and slowly increase, and make sure antenna is connected
num_samples = int(1e6)
repeat = 30 # number of times to repeat our signal
print('duration of transmission:', num_samples/sample_rate*repeat, 'seconds')

# Generate IQ samples to transmit (in this case, a simple tone)
t = np.arange(num_samples) / sample_rate
f_tone = 1e6
samples = np.exp(1j * 2 * np.pi * f_tone * t) # will be -1 to +1
samples = samples.astype(np.complex64)
samples *= 32767 # scale so they can be stored as int16s
samples = samples.view(np.int16)
buf = samples.tobytes() # convert our samples to bytes and use them as transmit buffer

tx_ch.frequency = center_freq
tx_ch.sample_rate = sample_rate
tx_ch.bandwidth = sample_rate/2
tx_ch.gain = gain

# Setup synchronous stream
sdr.sync_config(layout=_bladerf.ChannelLayout.TX_X1, # or TX_X2
                fmt=_bladerf.Format.SC16_Q11, # int16s
                num_buffers=16,
                buffer_size=8192,
                num_transfers=8,
                stream_timeout=3500)

print("Starting transmit!")
repeats_remaining = repeat - 1
tx_ch.enable = True
while True:
    sdr.sync_tx(buf, num_samples) # write to bladeRF
    print(repeats_remaining)
    if repeats_remaining > 0:
        repeats_remaining -= 1
    else:
        break

print("Stopping transmit")
tx_ch.enable = False

Se esperan algunos Pulse parada para el buffer al final.

Para transmitir y recibir al mismo tiempo, debes usar hilos, y tambi茅n puedes usar el ejemplo de Nuand. txrx.py que hace exactamente eso.

Osciladores, PLL y calibraci贸n

Todos los SDR de conversi贸n directa (incluidos todos los SDR basados en AD9361 como USRP B2X0, Analog Devices Pluto y bladeRF) dependen de un 煤nico oscilador para proporcionar un reloj estable para el transceptor de RF. Cualquier compensaci贸n o fluctuaci贸n en la frecuencia producida por este oscilador se traducir谩 en compensaci贸n de frecuencia y fluctuaci贸n de frecuencia en la se帽al recibida o transmitida. Este oscilador est谩 integrado, pero opcionalmente se puede 鈥渄isciplinar鈥 usando una onda cuadrada o sinusoidal independiente alimentada al bladeRF a trav茅s de un conector U.FL en la placa.

La placa bladeRF es una Abracon VCTCXO (controlado por voltaje oscilador con compensaci贸n de temperatura) con una frecuencia de 38,4 MHz. El aspecto de 鈥渢emperatura compensada鈥 significa que est谩 dise帽ado para ser estable en un amplio rango de temperaturas. El aspecto controlado por voltaje significa que se usa un nivel de voltaje para provocar ligeros ajustes en la frecuencia del oscilador, y en el bladeRF este voltaje es proporcionado por un convertidor digital a anal贸gico (DAC) de 10 bits separado, como se muestra en verde en el bloque. diagrama a continuaci贸n. Esto significa que a trav茅s del software podemos hacer ajustes finos a la frecuencia del oscilador, y as铆 es como calibramos (tambi茅n conocido como recortamos) el VCTCXO del bladeRF. Afortunadamente, los bladeRF est谩n calibrados en f谩brica, como veremos m谩s adelante en esta secci贸n, pero si tiene el equipo de prueba disponible, siempre puede ajustar este valor, especialmente a medida que pasan los a帽os y la frecuencia del oscilador cambia.

bladeRF 2.0 glamour shot

Cuando se utiliza una referencia de frecuencia externa (que puede ser casi cualquier frecuencia hasta 300 MHz), la se帽al de referencia se env铆a directamente al Analog Devices ADF4002 PLL integrado la cuchillaRF. Este PLL se bloquea en la se帽al de referencia y env铆a una se帽al al VCTCXO (como se muestra en azul arriba) que es proporcional a la diferencia de frecuencia y fase entre la entrada de referencia (escalada) y la salida del VCTCXO. Una vez que el PLL est谩 bloqueado, esta se帽al entre el PLL y el VCTCXO es un voltaje de CC de estado estable que mantiene la salida del VCTCXO en 鈥渆xactamente鈥 38,4 MHz (suponiendo que la referencia fuera correcta) y bloqueada en fase con la entrada de referencia. Como parte del uso de una referencia externa, debe habilitar clock_ref (ya sea a trav茅s de Python o CLI) y configurar la frecuencia de referencia de entrada (tambi茅n conocida como refin_freq), que es 10 MHz de forma predeterminada. Las razones para utilizar una referencia externa incluyen una mejor precisi贸n de frecuencia y la capacidad de sincronizar m煤ltiples SDR con la misma referencia.

Cada valor de ajuste de bladeRF VCTCXO DAC est谩 calibrado en f谩brica para estar dentro de 1 Hz a 38,4 MHz a temperatura ambiente, y puede ingresar su n煤mero de serie en esta p谩gina para ver cu谩l era el valor calibrado de f谩brica (busque su n煤mero de serie en la placa o usando bladerf-tool probe). Seg煤n Nuand, una placa nueva deber铆a estar dentro de los 0,5 ppm y probablemente m谩s cerca de los 0,1 ppm. Si tiene un equipo de prueba para medir la precisi贸n de la frecuencia o desea configurarlo al valor de f谩brica, puede usar los comandos:

$ bladeRF-cli -i
bladeRF> flash_init_cal 301 0x2049

intercambiando 301 con el tama帽o de su bladeRF y 0x2049 con el formato hexadecimal de su valor de ajuste VCTCXO DAC. Debes realizar un ciclo de energ铆a para que entre en vigor.

Muestreo a 122 MHz

Proximamente!

Expansion de puertos

El bladeRF 2.0 incluye un puerto de expansi贸n mediante un conector BSH-030. 隆M谩s informaci贸n sobre el uso de este puerto pr贸ximamente!

Lecturas Futuras

  1. bladeRF Wiki

  2. Nuand鈥檚 txrx.py example