Продолжаю цикл статей посвященных легендарному HackRF. В этом материале я хотел бы разобрать изнанку GNU Radio и описать широкими мазками то, каким образом оно устроено под капотом и сделать какой-нибудь минимально интересный проект с его использованием. Для себя я выбрал простую передачу аудиосигнала с микрофона с одного HackRF на другой через коаксиальный кабель с использованием WBFM-модуляции. Посмотрим на спектр, принимаемый сигнал и оценим возможности такого лабораторного сетапа.

Добро пожаловать под кат, будет интересно! ?

⚠️ Заметка об ответственности

Этот материал предназначен исключительно для образовательных целей, демонстрации и анализа возможностей обозначенных утилит и инструментов. Применение описанных техник в реальной среде при несоблюдении местных регуляторик, использовании частот и т.п. может быть незаконным. Все эксперименты должны проводиться только на собственном оборудовании и в изолированной среде.

С разбега в карьер: собираем стенд

В первую очередь озадачимся сборкой нашего стенда, на котором будем работать. В своем эксперименте я задействовал два HackRF, первый выступает в роли передатчика, второй в качестве приемника. Соединил их между собой по принципу коаксиальной петли по следующей схеме:

HackRF (TX) - [30dB ATT] - [30dB ATT] - coaxial cable - HackRF (RX)

По сути таким образом мы делаем закрытое RF-соединение с минимальными утечками в эфир, создавая самый минимум помех, контролируемым уровнем затухания в канале, без многолучевого распространения сигнала, что в совокупности дает возможность безопасно работать с нашим тестовым сетапом. 

Затухание рассчитываем таким образом чтобы принимаемый сигнал был примерно в районе -40… -60dBm, что в целом оптимально по отношению к приемнику и соответствует его чувствительности для нормального приема. После включения стоит отдельно убедиться что RF Amplifier выключен, начинать будем с небольшого BB-усиления (~0-5dB), и на приемной части подстроим LNA/VGA до оптимального уровня, чтобы минимизировать ошибки и исключить перегрузку, которая вероятно будет выражаться в “сплюснутых” вершинах сигнала на спектре. В общем поисследуем ?

Также на HackRF TX я водрузил экран и TCXO на оба для стабилизации частоты опорного генератора. Посмотрим насколько с ним стабильнее будет осуществляться передача.

Все любят картинки ?

Установка GNU Radio

Итак, стенд собрали. Перейдем к установке GNU Radio. Самый простой способ поставить его из apt-репозитория не утруждая себя сборкой из исходных кодов. Все инструкции есть тут: https://wiki.gnuradio.org/index.php/InstallingGR

На момент публикации статьи доступна версия 3.10.7.0 в apt-репозитории. Устанавливаем:

sudo add-apt-repository ppa:gnuradio/gnuradio-releases

sudo apt-get update

sudo apt install gnuradio

sudo apt install python3-pip

pip install packaging

Тем, кто хотел бы установить GNU Radio прям самой последней версии, все необходимое найдете тут: https://wiki.gnuradio.org/index.php?title=LinuxInstall

Возможно при первом запуске вы столкнетесь с ошибкой:

Нужно будет в .bashrc или ему подобный файл добавить прямое указание пути до директории с библиотеками:

export PYTHONPATH=/usr/lib/python3/dist-packages/:/usr/lib/python3/site-packages:$PYTHONPATH

И можно будет запустить GNU Radio из консоли:

$ gnuradio-companion

Появится главное окно программы. После этого можем начинать работу, но для начала все-таки немного теории, откуда берутся данные, как они проходят в программу и т.п.

GNU Radio изнутри

Во время подготовки я задался вопросом, каким образом данные забираются или отправляются из/в устройство. И каким образом вообще работает вся эта прекрасная система и как с ней начать работу. Начнем с последнего вопроса. Общий принцип работы достаточно прост:

  1. Пользователь, который (желательно) понимает, что делает - собирает flowgraph. То есть описывает с помощью встроенных или своих разработанных блоков модель движения данных. Там могут быть всевозможные источники данных, преобразования, модуляторы/демодуляторы, кодеры/декодеры, приемники и прочие. Заготовленного инструментария - более чем достаточно для работы: 

  1. Этот граф в конечном итоге упаковывается в top_block, который в свою очередь запускает планировщик, настраивает всевозможные буферы и дёргает вычислительные функции блоков. В 3.10 уже используется подход по потоку на один блок с настройками приоритета. 

  2. Далее, если в блоке используется реальное устройство, тот или иной блок для реализации источника данных используется для получения/отправки данных прямо через драйвер в устройство, например для HackRF это Osmocom Source/Sink или Soapy Source/Sink. Идет прямое оперирование комплексными данными I/Q с/на устройство через libhackrf и оперирует данными через буфер GNU Radio. Формат данных у HackRF “сырой” 8-битный комплекс (sc8), и в Osmocom Source/Sink он превращается в 32-битный gr_complex (float32+float32), чтобы этими данными могли оперировать все остальные блоки.

Далее коротко разберем основные понятия и элементы внутренней механики, чисто для справки, чтобы было проще потом ориентироваться в этом всем и не ломать голову о том, как это работает.

Блоки: типы, интерфейсы, данные

Коротко остановимся на описании того, что из себя представляет блок. Потому что много проблем будет связано именно с настройкой их интерфейсов и передаваемых между блоками типами данных. Это очень важно понимать.

Блок - это по сути некоторый юнит, который реализует какую-либо функцию для обработки данных. Из цепочки таких блоков и строится обработка всех данных.

Блоки в GNU Radio реализуют минимум две вещи:

  1. Интерфейс (streaming), который представляет из себя входные и выходные порты с элементарным типом (напр., gr_complex). Поток идёт по графу и каждый блок обрабатывает N элементов, пишет M и сообщает планировщику, сколько потребил/произвел. 

  2. Сообщения (message passing), которые представляют из себя асинхронные message-порты, которые используются для пакетной логики передачи данных внутри графа, межблокового управления или обмена событиями.

Каждый из блоков свою внутреннюю механику DSP и язык общения с планировщиком, указания объемов выходных/выходных данных. Но не будем в это углубляться, это уже advanced-level для разработчиков.

Планировщик, буферы, выполнение

Для обмена данными используются буферы. На каждом ребре графа - это кольцевые, выровненные под векторизацию (VOLK) буферы, с учетом размера элемента. Планировщик следит за уровнем заполнения и держит поток данных ровным.

По умолчанию у блоков свои потоки. Можно управлять аффинити/приоритетом отдельных блоков. Если данных с/на устройство нет, то блок “засыпает” с таймером, чтобы не крутить CPU вхолостую.

Если потребители данных не успевают, то буферы растут до лимитов, источник притормаживает. Или наоборот - планировщик чаще даёт работу upstream. Это по сути и есть “пульс” графа.

Как строится top block и иерархия

В GNU Radio используется иерархическая модель, которая позволяет упаковать подграф как единый блок со своими входами/выходами. И в этой модели top_block - это верхний контейнер графа, который создает переменные, GUI-виджеты (если используются), управляет жизненным циклом и т.п.

Также если возможность динамической перестройки, за счет наличия в коде сеттеров переменных, и например, если пользователь меняет в GUI какую-нибудь крутилку - то вызывается соответствующий метод, который прокидывает новое значение.

При чем тут Python?

Сам флоуграф хранится в формате .grc в YAML. Описание каждого блока с параметрами/портами тянется из шаблонов блоков (YAML/XML) и “рендерится” в вызовы Python-API при генерации. Генерация кода использует шаблоны (Mako): они определяют, какие import требуются для работы, как вызывать конструкторы блоков, как формировать connect() и сеттеры и прочее. И в рантайме Python - лишь “клей” для всей структуры проекта: все основные блоки представляют из себя исполняемый C++ код, а биндинги с 3.9/3.10 выполняются через pybind11 (SWIG ушёл в прошлое). Поэтому GIL почти не мешает DSP - тяжёлая работа выполняется в нативных потоках.

RX: как данные попадают из HackRF в GNU Radio

Когда вы запускаете валидный flowgraph (это я покажу чуть позже), то блок источника данных “открывает” дата-флоу с устройства через libhackrf, задает через API-функции частоту/FS/фильтр/усиления и вызывает hackrf_start_rx(cb, ctx). Это регистрирует callback на поток сэмплов и запускает поток обработки передач libusb. В libhackrf есть очередь нескольких USB-трансферов + “transfer thread” для обслуживания событий libusb (асинхронный I/O). И HackRF шлёт сырые выборки через USB 2.0 bulk IN эндпоинт (асинхронные bulk-передачи).

После этого в буфер hackrf_transfer приходят I/Q, 8-бит, знаковые, interleaved (I0,Q0,I1,Q1, …) в формате int8 (sc8). Далее callback внутри libhackrf получает указатель на buffer и valid_length и должен быстро переложить данные дальше (любая блокировка здесь = “дропов”). В драйвере gr-osmosdr выполняется векторизованное преобразование из sc8 в complex float32 (fc32): масштабирование к ~[−1, 1) и разделение потока на два, int8 I/Q в gr_complex. Поток укладывается в кольцевой буфер выходного порта блока, откуда планировщик GNU Radio забирает элементы вниз по графу.

Если нижние блоки не успевают, заполняется очередь у источника - то регистрируется событие RX overrun. И для этого в libhackrf есть счётчики/порог для оверранов . Типичный способ уменьшить дропы - увеличить глубину очереди/размер буферов (см. ниже), снизить частоту дискретизации (семплов в секунду) или упростить обработку данных.

TX: как данные уходят из GNU Radio в HackRF

Рассмотрим как данные уходят на отправку в эфир. Osmocom Sink или другой подобный блок принимает fc32 поток от вашего графа. Внутренняя механика блока преобразует fc32 в sc8 (масштабирование и клиппинг до int8), склеивает отдельные данные I и Q в сплошной поток I/Q и кладёт в собственную очередь.

Далее вызывается hackrf_start_tx(cb, ctx). Теперь libhackrf в своём callback забирает куски из очереди и отдаёт их в bulk OUT эндпоинт передачи libusb (асинхронно). 

Если GNU Radio не успевает наполнять буферы, возникает TX underrun. Современная прошивка при недозагрузке шлёт “нули” (в очень старой могла повторять последний буфер).

Что касается буферов, то с этим значением можно поиграться. libhackrf позволяет узнать/настроить transfer buffer size и queue depth (hackrf_get_transfer_buffer_size, hackrf_get_transfer_queue_depth). В osmosdr есть device-arg buffers=N для HackRF, чтобы увеличить глубину очереди на стороне драйвера. Бóльшие буферы создают латентность, но все же получается меньше дропов. Искать оптимальное соотношение - вам ?

Методы оптимизации производительности

Вообще, поскольку потолок в 20 MS/s даёт ~320 Mбит/с чистых данных (I+Q по 8 бит) без учёта накладных расходов USB, поэтому CPU, контроллер USB и RAM должны успевать, иначе будут overrun/underrun. Некоторые такие моменты я хотел бы проговорить отдельно.

  1. Первый пункт касается в первую очередь выбора частоты дискретизации устройства. Для большинства ПК и для не сильно быстрой шины USB2.0 рекомендую использовать скорость обработки данных до 10MS/s, выбрав минимальный семпл-рейт который покрывает нужную вам полосу. При увеличении потока вы можете заметить, что нагрузка на CPU сильно возрастает. Помимо можно поджать baseband filter через регулирование параметра bandwidth в настройке Source/Sink блока, чтобы не тащить лишний спектр в обработку. Помимо этого рекомендую подключать устройство к корневому порту USB минуя любые USB-хабы. Об этом я писал в статье-обзоре. Рекомендую прочитать ее если планируете плотно заняться работой с HackRF.

  2. В Flow Graph сразу после Source рекомендую сразу ставить “урезатель скорости потока”. Это либо Low Pass Filter (с полем Decimation) либо, ещё лучше, Frequency Xlating FIR Filter (он одновременно смещает частоту нужного канала в ноль и децимирует), либо Rational Resampler (для нецелых отношений). Так можно разгрузить поток данных еще на входе, разгружая весь граф.

  3. Большую часть работы по формированию сигнала делайте на “удобной” низкой частоте, а перед самым Sink поднимайте частоту до нужной. 

  4. Выбирайте оптимальное количество буферов для работы выбрав оптимальное соотношение между количеством дропнутых сэмплов и задержкой. 

  5. Используйте сразу после Source DC Block Spike чтобы убрать “палку” в спектре. 

  6. Не используйте Throttle с реальным SDR-оборудованием, т.к. это ломает тайминги это стоит использовать только в случае файлов/программных генераторов для ограничения потока.

  7. Rational Resampler polyphase предпочтительнее “каскадов” простых дециматоров/интерполяторов, если у вас нецелые отношения или нужна высокая селективность за одну стадию.

  8. Начинайте с выключенного RF AMP, поднимайте LNA, затем VGA, следя, чтобы спектр не упирался в клиппинг после конвертации sc8 в fc32.

  9. На TX оставляйте запас по амплитуде (клиппинг на int8 даст искажения и спуры). Лучше снизить уровень в модуле перед Sink и поднять мощность внешним AMP/снижением аттенюации.

  10. Обращайте внимание в консоли на RX overrun и TX underrun. В первом случае получается что источник “голодает” - тогда увеличьте buffers, уменьшите частоту сэмплирования, упростите обработку, количество GUI-виджетов. Во втором случае sink не успевает - добавьте больше буферов, уменьшите частоту сэмплирования или упростите цепочку перед Sink. При этом отслеживайте загрузку CPU по ядрам.

На этом думаю базовых рекомендаций будет достаточно, все равно все проблемы будут решаться по мере их поступления. Поэтому давайте реализуем первый свой flowgraph, устремляясь на встречу опыту, проблемам и желанному результату.

Простой пример: “WBFM-сквозняк”

Давайте сделаем простую передачу аудиосигнала с микрофона через два HackRF. Первый будет в качестве передатчика, второй в качестве приемника. Выведем спектры генерируемого сигнала и полученного сигнала, воспроизведем его, посмотрим уровни сигнала и итоговую картинку.

Далее давайте сделаем простой источник сигнала и сразу проверим как он работает. Добавляем на flowgraph Audio Source и Audio Sink и соединим выход и вход для проверки:

Запуск, диагностическую информацию и остановку нашего flowgraph осуществляем через это меню:

После можно запустить этот flowgraph и услышать свой микрофон в динамиках. Лучше проверять в гарнитуре, иначе будет весело ?. Если все получилось - значит источник звукового сигнала и воспроизведение - работают. Можно смело идти дальше.

То есть по сути мы должны сейчас добавить между этими двумя блоками радио и DSP-компоненты и передать сигнал по кабелю/воздуху. Что ж, соберем наш flowgraph. В первую очередь добавим переменные на Flow Graph. 

Делается это в меню справа и выбирается Variable:

Добавляем сразу список переменных из 5 штук. На общем рабочем поле проименуем эти переменные в соответствующие названия.

Первая переменная будет использоваться для задания sample_rate, то есть частоты дискретизации HackRF (Tx и Rx). Сделаем ее в оптимальное для HackRF значение в 2 MSPS. В value записываем математическое представление числа: 2e6. 

Вторая переменная center_freq будет использоваться для хранения центральной частоты на которой мы будем работать. Я выбрал центр ISM диапазона, 2.45GHz, в котором не задействуются дополнительные преобразования и сигнал будет минимально искажен. Значение: 2.45e9

Третья переменная audio_rate будет задавать частоту дискретизации аудио с микрофона и на динамики, сделаем 48кГц, стандарт для цифрового аудио. Значение int(48e3).

Четвертая переменная quad_rate задает базовую полосу для Wide Band FM и будет превышать в 5 раз аудиополосу. Так же через эту переменную зададим “квадратурную частоту” внутри FM-модулятора/демодулятора. Позже поясню для чего это. Значение: int(audio_rate*5).

Пятая переменная tone_offset используется для задания смещения рабочего канала от DC, чтобы не передавать на “игле” в центре спектра. Сделаем значение +100e3.

Получится следующая картина:

Теперь приступим к формированию передающей цепочки блоков. Первый блок Audio Source у нас уже добавлен, разрываем связь между Audio Sink. Этот блок даёт float-аудиопоток в диапазоне примерно [-1; 1]. Это по сути источник сообщений для частотного модулятора. В идеале нужно держать выходной уровень из него так, чтобы голосовые пики были ≈−12…−6 dBFS (чтобы не клиповало).

Далее необходимо добавить WBFM Transmit. Это по сути комбинированный блок широкополосного FM, который принимает на вход Audio с рейтом audio_rate и отдает квадратуры по заданному quad_rate . Чем он занимается:

  1. Предыскажение (pre-emphasis) с постоянной времени τ = 75 µs

  2. Ограничение полосы аудио до ≈15кГц перед модуляцией.

  3. FM-модулирование сигнала и преобразует амплитуду аудио в отклонение частоты несущей. Максимальное отклонение задается через параметр Max Deviation, который мы выставим в значение в 75kHz. 

  4. Создает поток квадратурных данных I/Q со скоростью quad_rate, т.е. 240 KSPS.

  5. Задает верхнюю частоту среза предыскажения через параметр Preemphasis High Corner Freq - то есть делает дополнительную Low Pass фильтрацию поверх стандартного подъема. Оставляем значение по умолчанию -1.

Приведу экран настроек данного блока:

Следующим блоком идет Rational Resampler. По сути представляет собой полифазный пересэмплер, который поднимает скорость квадратур с 240 KSPS до 2 MSPS для передачи их в устройство. Внутри него находится anti-imaging low-pass filter, который после повышения частоты дискретизации срезает спектральные образы, копии сигнала, появляющиеся из-за вставки нулей/ступенчатого ЦАП по сути пропуская только исходную базовую полосу и подавляя все остальные копии сигнала. 

Параметры данного блока:

  • оставляем поле Taps пустым, блок сам спроектирует фильтр по Fractional BW (~0.45 - подходящее значение).

  • Тип данных Complex -> Complex (Complex Taps);

  • Коэффициент Interpolation - 25, Decimation - 3. Необходимо подбирать правильно и с пониманием как происходит обработка сигнала, иначе из-за неправильно подобранных коэффициентов уровень сигнала просядет или пропадёт. Не буду тут вдаваться в подробности расчета этих коэффициентов, в рассказ о том, как работает ресемплер. Скажу лишь, что с учетом значенией частоты дискретизации в 2 MSPS, 240 KSPS в DSP при аудио 48 кГц и WBFM.

Дизайн приобретает следующий вид: 

Следующим блоком мы добавляем Frequency Xlating FIR Filter. По сути он представляет собой комбайн из трех функций - смеситель, ФНЧ-канализатор и дециматор. В нашем случае он необходим для осуществления сдвига канала на +100кГц от центра, чтобы не сидеть на DC-игле и LO-утечке.

Параметры данного блока выглядят следующим образом:

Поскольку децимации в этом блоке не производим - то оставляем значение Decimation в значении 1. 

Поле Taps - мы задаем фильтр для пропуска нужной нам полосы вокруг точки нуля и отрезаем все остальное передавая это просто аргументом в поле параметра: firdes.low_pass(1.0, samp_rate, 120e3, 30e3, window.WIN_HAMMING, 6.76)

  • первый аргумент gain в 1.0 - это амплитудный коэффициент в полосе пропускания, то есть ничего не усиливаем;

  • второй sampling_freq в 2e6 - это частота дискретизации потока на котором работает фильтр. От этого параметра зависят нормированные частоты среза и ширина переходной области.

  • третий параметр cutoff_freq в 120e3 - задает границу полосы пропускания примерно до какой частоты от нуля фильтр “плоский”). В нашем примере: пропускаем аудио-ЧМ полосу примерно до ±120 кГц. Всё, что дальше, будет подавляться.

  • четвертый параметр transition_width в 30е3 - задает ширину переходной области, т.е. как быстро фильтр спадает от “почти 0 дБ” до “глубокого подавления”. Здесь: от 120 кГц до 150 кГц АЧХ переходит из passband в stopband.

  • пятый параметр window в значении window.WIN_HAMMING задает метод аппроксимации идеального ФНЧ конечной длины (оконный метод). Окно Хэмминга даёт хорошее подавление боковых лепестков (~50–55 dB) при умеренной длине фильтра.

  • шестой параметр beta в значении 6.76 - используется только в для окна Кайзера и для Хэмминга игнорируется. Оставляем как есть.

В итоге получается массив коэффициентов FIR-фильтра, который канализирует сигнал.

Поле Center Freq задает частоту смещения канала. А поле Sample Rate - задает всё ту же частоту семплирования. 

Далее добавляем очень важную деталь в наш flowgraph: Soapy HackRF Sink, который занимается преобразованием fc32 в sc8 (масштабирует к int8), буферизирует данные и отдает по USB в HackRF. На железе формируется RF-несущая 2.45 ГГц, а наш комплексный baseband на 2 MSPS «крутит» её. Набор параметров выглядит следующим образом:

И финальный штрих - это QT GUI Frequency Sink для отладочных задач, чтобы четко видеть спектр и что полезный сигнал в итоге попадает на передатчик. 

Его параметры: 

После запуска Flow Graph появится окно спектроанализатора и наш спектр, который в целом выглядит как живая FM-модуляция речи после предыскажения и пересемплинга: 

Я конечно ожидал типовой спектр FM-сигнала с двумя половинками, но тут что-то странное. Вероятно до прохода через различные преобразования в реальном устройстве сигнал имеет именно такую форму. По итогу у нас получается следующий Flow Graph:

Все. Передатчик готов. Даже на этом этапе можно уже попробовать заценить то, что получилось. Открываем уже знакомый нам GQRX на частоте 2.4499 GHz и получаем сигнал с микрофона в довольно-таки неплохом качестве и с ожидаемой формой спектра:

Стоит отдельно отметить, что качество передачи, стабильность частоты гораздо выше при использовании внешнего TCXO. Без него - было гораздо больше свиста, шипения и шумов. Складывается впечатление, что без него вообще устройство не пригодно к использованию ибо частотное отклонение в моменте очень большое. А если говорить о точных способах модуляции, типа OFDM - то тут при работе с HackRF наверняка будет вообще беда. Это на будущее.

Реализуем часть приемника

Теперь переходим к реализации WBFM-приемника. В первую очередь надо добавить Soapy HackRF Source. Данный блок будет читать поток I/Q данных в sc8 со второго HackRF, преобразовывать его в fc32 и складывать в буфер исходного порта GNU Radio.

Далее нужно сделать обратный сдвиг частоты и сделать обратное преобразование сигнала для воспроизведения. Для этого после источника “сырых” данных добавляем Frequency Xlating FIR Filter. В параметрах необходимо указать следующие значения:

Децимацию не производим, поэтому ставим 1 в поле с соответствующим параметром. В Taps делаем такую же фильтрацию, подобно той, что у нас была сделана в Tx-части. В Center Frequency указываем размер сдвига, т.е. -100kHz, с противоположным знаком. Важно попасть именно в 0 Гц при приеме, чтобы не было свиста и искажений. Sample Rate - делаем из значения соответствующей переменной. Тут все просто.

Далее необходимо добавить Rational Resampler с обратным пересчётом из 2 MSPS в 240KSPS. Внутри еще будет встроенный антиалис-ФНЧ. Так же этот блок разгружает демодулятор и синхронизирует скорость с параметром Quadrature Rate. Набор параметров следующий (зеркальный к Tx):

После добавим предпоследний шаг к получению заветного аудио сигнала - блок WBFM Receive, который представляет собой демодулятор широкополосной FM. Внутри квадратурный детектор вычисляет мгновенную частоту по фазовому приросту, затем масштабирует ее обратно в аудио. Так же производит деэмфазис (de-emphasis) с той же постоянной τ, что на TX и восстанавливает естесственный спектр речи/музыки и откатывает SNR-трюк предыскажения. И производит ограничение полосы и децимацию до аудиосемплинга. Параметр Audio Decimation выставленный в 5 дает 48кГц на выходе. Набор параметров:

И остается присоединить все это к уже добавленному Audio Sink, который отдает поток в аудиосервер (PulseAudio/PipeWire/ALSA). Если включён “blocking” - так меньше “щелчков” (xruns).

В итоге вся схема должна будет выглядеть вот таким образом:

После запуска нам покажется вот такое окно с двумя спектрами, на выходной сигнал и на входной. Второй добавил чисто из любопытства, можно увидеть, что спектры отличаются:

Видим что формируем, какой сигнал и что в итоге приходит. Перед Tx, на графике снизу, видим WBFM на -100kHz. Для себя я пока не нашел ответа почему сигнал до отправки на передатчик так сильно отличается от традиционной FM-формы, которую видно на приемнике. После Rx - видим традиционный пик в 0 Гц (LO/DC) и поднимающийся над шумом спектр полезного сигнала. 

На этом этапе можно поиграться с усилениями четко отслеживая клипирования, превышения безопасного уровня. При искажениях, шумах, щелчках и прочих - отслеживайте перегрузы по уровням, ставьте Multiply Const для ограничений, и смотрите за точностью установки по частоте. Пишите в комментариях, что у вас получилось и попробуем разобраться вместе. Уверен, что у вас все получилось и вы услышали свой голос в гарнитуре, но уже через WBFM-приемопередатчик собранный в GNU Radio.

Заключение

Ну вот мы и собрали безопасный и воспроизводимый RF-стенд и разобрались, как GNU Radio живёт “под капотом” (блоки, буферы, планировщик, топология графа), и на практике построили сквозной WBFM-тракт: от аудио-микрофона через модуляцию, пересэмплинг и частотный сдвиг - к Soapy HackRF Sink; затем зеркальный приём с демодуляцией и прослушиванием. По пути отработали ключевые навыки: выбор семпл-рейта, управление усилениями, проектирование фильтров, борьба с DC-иглой, наблюдение спектра и диагностику overruns/underruns.

Так же во время подготовки материала я сделал несколько важных для себя выводов:

  • Внешний TCXO и общая синхронизация частот/тактов заметно повышают стабильность, без них дрейф мешает даже «простым» режимам;

  • Пересэмплинг и ранняя децимация разгружают граф и USB; правильно подобранные размеры буферов уменьшают дропы ценой увеличения задержек;

И данный материал можно было бы продолжить более интересными примерами раздув ее до пределов бесконечности ?. Например, перейти к OFDM и заменить WBFM-цепочку на цифровой тракт с формированием кадров, пилотами и CP (Carrier Allocator/OFDM Mod/Sync/Channel Estimator), оценить созвездия, EVM и BER. Туда еще добавить кодирование (FEC), интерливинг, компенсацию CFO/фазы; сравнить при работе «по воздуху» и по коаксиалу. А потом до кучи вынести узлы в иерархические блоки, включить логирование статистики RX/TX и автоматизировать подбор параметров и закончить чисто коммерческим продуктом на базе SDR ?. В общем потенциал для обучения, особенно для студентов радиотехнических специальностей - огромный, главное иметь время, желание на это и навыки. 

Но пока остановимся на достигнутом. У нас получился понятный мостик от теории GNU Radio и устройства HackRF к рабочему радио приложению. А на его базе уже легко можно шагнуть и к более сложным протоколам - уже осознанно и с хорошими инструментами отладки. До встречи в следующих статьях.

Комментарии (6)


  1. anoldman25
    15.10.2025 17:03

    Интересная статья, спасибо.

    Я некоторое время назад серьезно занимался SDR. Был один интересный с моей точки зрения проект. Запись голосов птиц на N микрофонов. Типа фазированная решетка микрофонов. Но сейчас забросил, лень. Хотя тема очень интересная.

    От себя добавил бы: я купил две из трех книг про SDR. Трехтомник. Field Expedient SDR vol 1, 2, 3 by Paul Clark, David Clark. Первый том есть где-то в интернете, а два других в бумажном виде. Может интересно.

    Я видел, что они по моему выпустили еще какую-то книгу про SDR.


    1. andreyzaostrovnykh Автор
      15.10.2025 17:03

      О! Спасибо за рекомендацию! Поищу :)


    1. andreyzaostrovnykh Автор
      15.10.2025 17:03

      Нашел все три тома на просторах тырнетов. Спасибо :)


  1. ivanovgoga
    15.10.2025 17:03

    Для начинающих это конечно интересно. Но все же не забываем, что Гнурадио это просто костыль для лабораторных работ и не более того. Для работы он не предназначен


    1. andreyzaostrovnykh Автор
      15.10.2025 17:03

      Какие инструменты используете для работы?


    1. NutsUnderline
      15.10.2025 17:03

      макетирование и прототипирование схем тоже не ценим?