6. USRP на Python

Сімейство USRP-радіостанцій від Ettus Research

У цій главі ми навчимося використовувати UHD Python API для керування та прийому/передачі сигналів за допомогою USRP - серії SDR від компанії Ettus Research (тепер частина NI). Ми обговоримо передачу та прийом на USRP в Python, а також зануримося в специфічні для USRP теми, включаючи аргументи потоку, субпристрої, канали, 10 МГц і PPS синхронізацію.

Встановлення програмного забезпечення/драйверів

Хоча код на Python, наведений у цьому підручнику, має працювати під Windows, Mac і Linux, ми надамо лише інструкції зі встановлення драйверів/API для Ubuntu 22 (хоча наведені нижче інструкції мають працювати на більшості дистрибутивів на основі Debian). Ми почнемо зі створення віртуальної машини Ubuntu 22 VirtualBox; можете пропустити частину про віртуальну машину, якщо у вас вже є готова до роботи ОС. Крім того, якщо ви використовуєте Windows 11, Windows Subsystem for Linux (WSL) з Ubuntu 22 працює досить добре і підтримує графіку “з коробки”.

Налаштування віртуальної машини Ubuntu 22

(Необов’язково)

  1. Завантажте Ubuntu 22.04 Desktop .iso - https://ubuntu.com/download/desktop
  2. Встановіть і відкрийте VirtualBox.
  3. Створіть нову віртуальну машину. Для розміру пам’яті я рекомендую використовувати 50% оперативної пам’яті вашого комп’ютера.
  4. Створіть віртуальний жорсткий диск, виберіть VDI і динамічно розподіліть обсяг. 15 ГБ повинно бути достатньо. Якщо ви хочете бути дійсно безпечними, ви можете використовувати більше.
  5. Запустіть віртуальну машину. Вона запитає вас про інсталяційний носій. Виберіть файл .iso для робочого столу Ubuntu 22. Виберіть “встановити ubuntu”, скористайтеся параметрами за замовчуванням, і спливаюче вікно попередить вас про зміни, які ви збираєтеся зробити. Натисніть “продовжити”. Виберіть ім’я/пароль і зачекайте, поки віртуальна машина закінчить ініціалізацію. Після завершення ВМ перезавантажиться, але вам слід вимкнути ВМ після перезавантаження.
  6. Перейдіть до налаштувань ВМ (іконка з шестернею).
  7. У розділі система > процесор > виберіть принаймні 3 процесори. Якщо у вас справжня відеокарта, то в розділі дисплей > відеопам’ять > виберіть щось набагато більше.
  8. Запустіть віртуальну машину.
  9. Для USRP типу USB вам потрібно буде встановити гостьові доповнення до ВМ. У віртуальній машині перейдіть до Пристрої > Вставити компакт-диск з гостьовими додатками > натисніть запустити, коли з’явиться вікно. Дотримуйтесь інструкцій. Перезапустіть віртуальну машину, а потім спробуйте переслати USRP на віртуальну машину, припускаючи, що вона з’явиться у списку Пристрої > USB. Спільний буфер обміну можна увімкнути за допомогою Пристрої > Спільний буфер обміну > Двонаправлений.

Встановлення UHD та Python API

Наведені нижче команди терміналу мають зібрати та встановити останню версію UHD, включно з Python API:

sudo apt-get install git cmake libboost-all-dev libusb-1.0-0-dev python3-docutils python3-mako python3-numpy python3-requests python3-ruamel.yaml python3-setuptools build-essential
cd ~{{}}}
git clone https://github.com/EttusResearch/uhd.git
cd uhd/host
mkdir build
cd build
cmake -DENABLE_TESTS=OFF -DENABLE_C_API=OFF -DENABLE_MANUAL=OFF ..
make -j8
sudo make install
sudo ldconfig

Докладнішу інформацію можна знайти на офіційній сторінці Ettus Складання та встановлення UHD з коду. Зауважте, що існують також способи встановлення драйверів, які не потребують збирання з коду.

Тестування драйверів UHD і Python API

Відкрийте новий термінал і введіть наступні команди:

python3
import uhd
usrp = uhd.usrp.MultiUSRP()
samples = usrp.recv_num_samps(10000, 100e6, 1e6, [0], 50)
print(samples[0:10])

Якщо не виникло жодних помилок, ви готові до роботи!

Бенчмаркінг швидкості USRP на Python

(Необов’язково)

Якщо ви використовували стандартне встановлення з вихідного коду, наступна команда повинна протестувати швидкість отримання вашого USRP за допомогою API Python. Якщо використання 56e6 спричинило багато втрачених вибірок або перевиконання, спробуйте зменшити це число. Втрачені вибірки не обов’язково щось зіпсують, але це хороший спосіб протестувати неефективність, яка може виникнути, наприклад, при використанні віртуальної машини або старого комп’ютера. Якщо ви використовуєте B 2X0, досить сучасний комп’ютер з портом USB 3.0, який працює належним чином, повинен працювати на частоті 56 МГц без пропущених семплів, особливо з таким високим значенням num_recv_frames.

python /usr/lib/uhd/examples/python/benchmark_rate.py --rx_rate 56e6 --args "num_recv_frames=1000"

Отримання

Отримати вибірки з USRP надзвичайно просто за допомогою вбудованої функції “recv_num_samps()”, нижче наведено код на Python, який налаштовує USRP на 100 МГц, використовуючи частоту дискретизації 1 МГц, і отримує 10 000 вибірок з USRP, використовуючи коефіцієнт підсилення прийому 50 дБ:

import uhd
usrp = uhd.usrp.MultiUSRP()
.. _usrp-chapter:

7. USRP на Python

Сімейство USRP-радіостанцій від Ettus Research

У цій главі ми навчимося використовувати UHD Python API для керування та прийому/передачі сигналів за допомогою USRP - серії SDR від компанії Ettus Research (тепер частина NI). Ми обговоримо передачу та прийом на USRP в Python, а також зануримося в специфічні для USRP теми, включаючи аргументи потоку, субпристрої, канали, 10 МГц і PPS синхронізацію.

Встановлення програмного забезпечення/драйверів

Хоча код на Python, наведений у цьому підручнику, має працювати під Windows, Mac і Linux, ми надамо лише інструкції зі встановлення драйверів/API для Ubuntu 22 (хоча наведені нижче інструкції мають працювати на більшості дистрибутивів на основі Debian). Ми почнемо зі створення віртуальної машини Ubuntu 22 VirtualBox; можете пропустити частину про віртуальну машину, якщо у вас вже є готова до роботи ОС. Крім того, якщо ви використовуєте Windows 11, Windows Subsystem for Linux (WSL) з Ubuntu 22 працює досить добре і підтримує графіку “з коробки”.

Налаштування віртуальної машини Ubuntu 22

(Необов’язково)

  1. Завантажте Ubuntu 22.04 Desktop .iso - https://ubuntu.com/download/desktop
  2. Встановіть і відкрийте VirtualBox.
  3. Створіть нову віртуальну машину. Для розміру пам’яті я рекомендую використовувати 50% оперативної пам’яті вашого комп’ютера.
  4. Створіть віртуальний жорсткий диск, виберіть VDI і динамічно розподіліть обсяг. 15 ГБ повинно бути достатньо. Якщо ви хочете бути дійсно безпечними, ви можете використовувати більше.
  5. Запустіть віртуальну машину. Вона запитає вас про інсталяційний носій. Виберіть файл .iso для робочого столу Ubuntu 22. Виберіть “встановити ubuntu”, скористайтеся параметрами за замовчуванням, і спливаюче вікно попередить вас про зміни, які ви збираєтеся зробити. Натисніть “продовжити”. Виберіть ім’я/пароль і зачекайте, поки віртуальна машина закінчить ініціалізацію. Після завершення ВМ перезавантажиться, але вам слід вимкнути ВМ після перезавантаження.
  6. Перейдіть до налаштувань ВМ (іконка з шестернею).
  7. У розділі система > процесор > виберіть принаймні 3 процесори. Якщо у вас справжня відеокарта, то в розділі дисплей > відеопам’ять > виберіть щось набагато більше.
  8. Запустіть віртуальну машину.
  9. Для USRP типу USB вам потрібно буде встановити гостьові доповнення до ВМ. У віртуальній машині перейдіть до Пристрої > Вставити компакт-диск з гостьовими додатками > натисніть запустити, коли з’явиться вікно. Дотримуйтесь інструкцій. Перезапустіть віртуальну машину, а потім спробуйте переслати USRP на віртуальну машину, припускаючи, що вона з’явиться у списку Пристрої > USB. Спільний буфер обміну можна увімкнути за допомогою Пристрої > Спільний буфер обміну > Двонаправлений.

Встановлення UHD та Python API

Наведені нижче команди терміналу мають зібрати та встановити останню версію UHD, включно з Python API:

sudo apt-get install git cmake libboost-all-dev libusb-1.0-0-dev python3-docutils python3-mako python3-numpy python3-requests python3-ruamel.yaml python3-setuptools build-essential
cd ~{{}}}
git clone https://github.com/EttusResearch/uhd.git
cd uhd/host
mkdir build
cd build
cmake -DENABLE_TESTS=OFF -DENABLE_C_API=OFF -DENABLE_MANUAL=OFF ..
make -j8
sudo make install
sudo ldconfig

Докладнішу інформацію можна знайти на офіційній сторінці Ettus Складання та встановлення UHD з коду. Зауважте, що існують також способи встановлення драйверів, які не потребують збирання з коду.

Тестування драйверів UHD і Python API

Відкрийте новий термінал і введіть наступні команди:

python3
import uhd
usrp = uhd.usrp.MultiUSRP()
samples = usrp.recv_num_samps(10000, 100e6, 1e6, [0], 50)
print(samples[0:10])

Якщо не виникло жодних помилок, ви готові до роботи!

Бенчмаркінг швидкості USRP на Python

(Необов’язково)

Якщо ви використовували стандартне встановлення з вихідного коду, наступна команда повинна протестувати швидкість отримання вашого USRP за допомогою API Python. Якщо використання 56e6 спричинило багато втрачених вибірок або перевиконання, спробуйте зменшити це число. Втрачені вибірки не обов’язково щось зіпсують, але це хороший спосіб протестувати неефективність, яка може виникнути, наприклад, при використанні віртуальної машини або старого комп’ютера. Якщо ви використовуєте B 2X0, досить сучасний комп’ютер з портом USB 3.0, який працює належним чином, повинен працювати на частоті 56 МГц без пропущених семплів, особливо з таким високим значенням num_recv_frames.

python /usr/lib/uhd/examples/python/benchmark_rate.py --rx_rate 56e6 --args "num_recv_frames=1000"

Отримання

Отримати вибірки з USRP надзвичайно просто за допомогою вбудованої функції “recv_num_samps()”, нижче наведено код на Python, який налаштовує USRP на 100 МГц, використовуючи частоту дискретизації 1 МГц, і отримує 10 000 вибірок з USRP, використовуючи коефіцієнт підсилення прийому 50 дБ:

usrp.set_rx_agc(True, 0) # 0 для каналу 0, тобто першого каналу USRP

Якщо у вас USRP, який не реалізує АРУ, при виконанні наведеного вище рядка буде згенеровано виключення. Якщо АРУ увімкнено, встановлення коефіцієнта підсилення нічого не дасть.

Аргументи потоку

У повному прикладі вище ви побачите рядок st_args = uhd.usrp.StreamArgs("fc32", "sc16"). Перший аргумент - це формат даних процесора, який є типом даних семплів, щойно вони опиняться на вашому комп’ютері. UHD підтримує наступні типи даних процесора при використанні Python API:

Потік Arg Numpy тип даних Опис
fc64 np.complex128 Комплексні дані подвійної точності
fc32 np.complex64 Комплексні дані одинарної точності

Ви можете побачити інші варіанти у документації до UHD C++ API, але вони ніколи не були реалізовані у Python API, принаймні на момент написання цієї статті.

Другий аргумент - це “дротовий” формат даних, тобто тип даних, у якому зразки надсилаються через USB/Ethernet/SFP на хост. Для Python API можливі такі варіанти: “sc16”, “sc12” і “sc8”, причому 12-бітовий варіант підтримується лише певними USRP. Цей вибір важливий, оскільки з’єднання між USRP і хост-комп’ютером часто є вузьким місцем, тому, переключившись з 16 біт на 8 біт, ви можете досягти вищої швидкості. Також пам’ятайте, що багато USRP мають АЦП, обмежені 12 або 14 бітами, тому використання “sc16” не означає, що АЦП має 16 біт.

Щодо канальної частини st_args, див. підрозділ Підпристрої та канали нижче.

Передавання

Подібно до функції recv_num_samps(), UHD надає функцію send_waveform() для передавання пачки відліків, приклад якої показано нижче. Якщо ви вкажете тривалість (у секундах), більшу за наданий сигнал, вона просто повторить його. Це допомагає утримувати значення відліків між -1.0 і 1.0.

import uhd
import numpy as np
usrp = uhd.usrp.MultiUSRP()
samples = 0.1*np.random.randn(10000) + 0.1j*np.random.randn(10000) # створюємо випадковий сигнал
duration = 10 # секунд
center_freq = 915e6
sample_rate = 1e6
gain = 20 # [dB] починаємо з низького рівня, потім збільшуємо
usrp.send_waveform(samples, duration, center_freq, sample_rate, [0], gain)

Детальніше про те, як ця зручна функція працює під капотом, дивіться у вихідному коді тут.

Коефіцієнт підсилення передачі

Як і на стороні прийому, діапазон коефіцієнта підсилення передачі залежить від моделі USRP і може варіюватися від 0 дБ до вказаного нижче значення:

  • B200/B210/B200-mini: 90 дБ
  • N210 з WBX: 25 дБ
  • N210 з SBX або UBX: 31,5 дБ
  • E310/E312: 90 дБ
  • N320/N321: 60 дБ

Існує також функція set_normalized_tx_gain(), якщо ви хочете вказати коефіцієнт підсилення передачі, використовуючи діапазон від 0 до 1.

Одночасне передавання та приймання

Якщо ви хочете одночасно передавати і приймати за допомогою одного і того ж USRP, ключовим моментом є використання декількох потоків в межах одного процесу; USRP не може охоплювати декілька процесів. Наприклад, у прикладі txrx_loopback_to_file C++ створюється окремий потік для запуску передавача, а прийом виконується у головному потоці. Ви також можете просто створити два потоки, один для передавання і один для приймання, як це зроблено у прикладі benchmark_rate Python. Повний приклад тут не показано, просто тому, що це був би досить довгий приклад, а Ettus’ benchmark_rate.py завжди може слугувати відправною точкою для когось.

Пристрої, канали та антени

Одне з поширених джерел плутанини при використанні USRP - це вибір правильного ідентифікатора підпристрою і каналу. Ви могли помітити, що в кожному з наведених вище прикладів ми використовували канал 0 і не вказували нічого, пов’язаного з підпристроєм. Якщо ви використовуєте B210 і хочете використовувати RF:B замість RF:A, все, що вам потрібно зробити, це вибрати канал 1 замість 0. Але на таких USRP, як X310, які мають два слоти для дочірніх плат, ви повинні вказати UHD, чи хочете ви використовувати слот A або B, і який канал на цій дочірній платі, наприклад:

usrp.set_rx_subdev_spec("B:0")

Якщо ви хочете використовувати порт TX/RX замість RX2 (за замовчуванням), це так само просто, як:

usrp.set_rx_antenna('TX/RX', 0) # встановити канал 0 на 'TX/RX'

який, по суті, просто керує радіоперемикачем на борту USRP, для маршрутизації з іншого роз’єму SMA.

Щоб приймати або передавати на двох каналах одночасно, замість st_args.channels = [0] ви вказуєте список, наприклад [0,1]. У цьому випадку буфер отриманих зразків повинен мати розмір (2, N), а не (1,N). Просто пам’ятайте, що у більшості USRP обидва канали мають спільний LO, тому ви не можете налаштуватися на різні частоти одночасно.

Синхронізація на 10 МГц і PPS

Однією з величезних переваг використання USRP над іншими SDR є можливість синхронізації з зовнішнім джерелом або бортовим GPSDO, що дозволяє використовувати декілька приймачів, наприклад, TDOA. Якщо ви підключили зовнішнє джерело 10 МГц і PPS до вашого USRP, вам потрібно переконатися, що ви викликаєте ці два рядки після ініціалізації вашого USRP:

usrp.set_clock_source("external")
usrp.set_time_source("external")

Якщо ви використовуєте вбудований GPSDO, замість цього використовуйте:

usrp.set_clock_source("gpsdo")
usrp.set_time_source("gpsdo")

Щодо синхронізації частоти, то тут більше нічого не потрібно робити; LO, що використовується у мікшері USRP, тепер буде прив’язано до зовнішнього джерела або GPSDO. Але з точки зору синхронізації, ви, можливо, захочете наказати USRP починати вибірку саме з PPS, наприклад. Це можна зробити за допомогою наступного коду:

# скопіюйте приклад receive вище, все до # Start Stream

# Дочекайтеся 1 PPS, потім встановіть час на наступному PPS рівним 0.0
time_at_last_pps = usrp.get_time_last_pps().get_real_secs()
while time_at_last_pps == usrp.get_time_last_pps().get_real_secs():
    time.sleep(0.1) # продовжуємо чекати, поки це станеться - якщо цей цикл ніколи не завершиться, значить сигналу PPS немає
usrp.set_time_next_pps(uhd.libpyuhd.types.time_spec(0.0))

# Запланувати Rx відліків num_samps рівно через 3 секунди після останнього PPS
stream_cmd = uhd.types.StreamCMD(uhd.types.StreamMode.num_done)
stream_cmd.num_samps = num_samps
stream_cmd.stream_now = False
stream_cmd.time_spec = uhd.libpyuhd.types.time_spec(3.0) # встановлюємо час запуску (спробуйте налаштувати)
streamer.issue_stream_cmd(stream_cmd)

# отримуємо вибірки. recv() поверне нулі, потім наші вибірки, потім знову нулі, даючи нам знати, що все зроблено
waiting_to_start = True # відстежуємо, де ми знаходимося у циклі (див. коментар вище)
nsamps = 0
i = 0
samples = np.zeros(num_samps, dtype=np.complex64)
while nsamps != 0 or waiting_to_start
    nsamps = streamer.recv(recv_buffer, metadata)
    if nsamps та waiting_to_start:
        waiting_to_start = False
    elif nsamps:
        samples[i:i+nsamps] = recv_buffer[0][0:nsamps]
    i += nsamps

Якщо здається, що це не працює, але не видає жодних помилок, спробуйте змінити число 3.0 на будь-що від 1.0 до 5.0. Ви також можете перевірити метадані після виклику recv(), просто перевірте if metadata.error_code != uhd.types.RXMetadataErrorCode.none:.

Для налагодження ви можете перевірити, що сигнал 10 МГц надходить до USRP, перевіривши повернення usrp.get_mboard_sensor("ref_locked", 0). Якщо сигнал PPS не відображається, ви дізнаєтеся про це, оскільки перший цикл while у наведеному вище коді ніколи не завершиться.