SDR радиоприёмник в FPGA плате Марсоход3GW2 (Gowin)
Говорит и показывает программа HDSDR
Когда-то давно, аж 2013-й год, на Хабре была статья «Простой SDR приёмник на ПЛИС» автора @iliasam. Я попытаюсь повторить этот проект на другой элементной базе: FPGA плата Марсоход3GW2, микросхема Gowin GW1NR-LV9QN88PC6/I5.
Законы Цифровой Обработки Сигналов кажется остаются всё те же, что и раньше. Посмотрим, что у меня получится.
Для реализации проекта в FPGA мне потребуются:
модуль последовательного порта для связи платы с компьютером, программа на ПК будет посылать в плату команды установки частоты приёма, а сама плата будет посылать в ПК данные оцифрованного сигнала;
модуль NCO, Numerically Controlled Oscillator, именно его частоту мы будем перестраивать при поиске радиопередач в эфире;
умножители, перемножая синусоидальный / косинусоидальный сигнал с NCO на оцифрованный с помощью АЦП сигнал с антенны мы будем производить перенос спектра радиосигнала в область низких частот;
CIC и FIR фильтры, позволяют оставить только низкочастотную составляющую сигнала ну и понижают частоту сэмплирования, значительно снижая поток данных в ПК;
модуль beeper, это простой эмулятор АМ модулированного радиосигнала, на случай, если моя антенна или АЦП по какой-то причине не заработают, работоспособность проекта всегда можно проверить с помощью бипера.
Все приведенные выше модули вы можете рассмотреть на структурной схеме SDR радиоприёмника.
Структурная схема проекта.
Итак, для связи FPGA платы с компьютером я буду использовать последовательный порт. На плате Марсоход3GW2 стоит микросхема FTDI FT2232H и она позволяет соединение виртуального последовательного порта на скорости 12Мбит/сек. Это более чем достаточно для моих нужд.
Связь осуществляется специальным пакетным протоколом, который позволяет по старшему биту в каждом принимаемом байте легко выделять заголовок пакета и сами данные.
Протокол передачи данных через последовательный порт
Таким образом организована гарантированная передача 32х битных слов с помощью 5 байтов.
Например, программа на ПК посылает команду перестроить приёмник на другую несущую частоту. Это всего одна единственная команда от ПК к FPGA плате, которую я реализовал для этого проекта.
Программа должна послать 32х битное число, я решил, что это будет заранее вычисленное «приращение угла».
Дело вот в чем. Модуль NCO я реализовал на базе Gowin IP Core Cordic. В среде Gowin FPGA Designer можно создавать экземпляры компонентов, из имеющихся в библиотеке Gowin. Вот так выглядит настройка компонента Cordic:
На вход модуля я должен подавать угол theta_i в формате 17ти битного числа с фиксированной точкой в диапазоне от -Pi/2 до +Pi/2.
Я решил, что пусть у меня будет 32х битное число «регистр накопления» в котором я буду накапливать угол.
NCO это же управляемый генератор. Из ПК я получаю команду — «приращение угла». По каждому такту рабочей частоты я буду прибавлять это «приращение угла» к «регистру накопления».
Например, мой NCO работает на частоте 50МГц.
Допустим нам нужно установить в NCO частоту 4651500 Гц.
Инкремент угла это 4651500 / 50000000 = 0,09303 (но не радиан пока еще, а доля от полного оборота). Это то число, которое принимает плата от компьютера. С таким шагом угла за 10 тактов вектор повернётся на 0,09303×10 = 0,9303 оборота, а полный оборот это 1.0. За 11 тактов вектор повернется на угол 0,09303×11 = 1,02333, то есть чуть больше, чем полный оборот. Но в этом и есть идея алгоритма. Переполнения регистра, те, что больше или равны 1.0 отбрасываем. А угол в регистре накапливается и поддерживает требуемую точность. Следующий цикл оборота начинается уже не с нулевой фазы, а с 0,02333. Все вычисления при этом проводим в числах с фиксированной точкой, где точка стоит выше 32-го бита.
При этом старшие два бита «регистра накопления» я рассматриваю как номер квадранта.
Углу в Pi/2 соответствует число 0,25 или 00.01 в формате числа с фиксированной точкой в двоичном представлении. Теперь останется только отмасштабировать угол, чтобы он был в нужном диапазоне чисел. Масштабирование производится умножением значения в регистре накопления на константу Pi/2 опять же в виде числа с фиксированной точкой. В двоичном виде это 01.100100100001111.
Простая программа на питоне поможет разобраться с числами с фиксированной точкой.
from fxpmath import Fxp
x = Fxp(1.5707963267948, True, 17, 15)
x.bin(frac_dot=True)
>'01.100100100001111'
На самом деле арифметика чисел с фиксированной точкой практически не отличается от целочисленной арифметики.
Более подробную информацию о том, как я реализовал NCO можно посмотреть в этой статье, а исходники проекта NCO на гитхабе.
Работоспособность модуля NCO была проверена с помощью Gowin Analyzer Oscilloscope. Эта программа позволяет захватить интересующие нас сигналы и экспортировать в файл для дальнейшего просмотра:
Выход NCO при частоте 3125700 Гц
После того, как модуль NCO был готов я приступил к созданию CIC фильтров. Я так же использую готовый компонент из библиотеки Gowin IP Core:
Настройки CIC-фильтра
Поскольку я собираюсь вести оцифровку сигнала с антенны на частоте 50МГц, то было бы разумно после переноса спектра из области радиосигнала в область низких частот произвести понижение частоты следования отсчётов. Для этого как раз и применяются CIC-фильтры дециматоры. Я собираюсь понизить частоту сэмплирования в 500 раз, чтобы она стала 100КГц. С этим параметром Down-Sampling Rate всё понятно. Так же понятна входная разрядность: моё АЦП на плате Марсоход3GW2 имеет восьмибитную ADC1175–50. Если я с NCO так же возьму 8 бит и перемножу их, то получится 16 бит входные числа на фильтр. Можно конечно и больше подать, но всё упирается в ресурсы FPGA. И так уже смотрите выходная разрядность CIC фильтра выросла до 56 бит.
На самом деле должен сказать, что я не очень уверен какие параметры задавать для CIC фильтра, в особенности Differential Delay и Stages. Конечно я прочитал всякие теоретические материалы по этому вопросу, например, вот http://www.dsplib.ru/content/cicid/cicid.html и вот https://github.com/hukenovs/dsp-theory. Но, как говорится, пока своими руками не пощупаешь, до конца и не поймешь.
Поэтому мне пришла в голову такая идея: я же могу исследовать реальные АЧХ характеристики фильтра! Если я соединю выход моего NCO со входом CIC фильтра, потом сделаю и поставлю измеритель амплитуды, то потом можно просто отображать измеренную амплитуду к примеру на семисегментном индикаторе платы.
Проект измерения АЧХ CIC фильтра
И я сделал такой вспомогательный проект. Более подробное описание проекта можно почитать здесь. А его исходники вот здесь, на гитхабе.
На компьютере я запускаю мою программу на питоне, которая передает команду установить частоту на NCO:
Программа установки частоты NCO
Мне нужно установить разные частоты в NCO в диапазоне и смотреть амлитуду сигнала после фильтра на семисегментном индикаторе на плате, потом записывать значения в таблицу:
Эксперимент по измерению АЧХ фильтра на плате Марсоход3GW2
С помощью этого проекта я измерил реальные АЧХ трех разных CIC фильтров: 2–4, 2–5 и 1–4, где первое число это Diff Delay, а второе число это количество Stages.
Вот что у меня получилось при экспериментальном измерении АЧХ:
Измеренные АЧХ цифрового CIC фильтра в трёх исполнениях
На самом деле я рад, что эксперимент подтверждает теорию. По крайней мере практически похоже я двигаюсь в правильном направлении. Для SDR приёмника я выбираю исполнение CIC фильтра Diff Delay 2 и количество Stages 4. Это тёмно-синий график. Так и крутизна фильтра хорошая и при этом не очень много ресурсов в ПЛИС забирает такой фильтр. Фильтр 1–4 занимает меньше всего ресурсов в ПЛИС, но его АЧХ не очень крутая (жёлтый цвет на графике выше).
Структурная схема CIC фильтра дециматора
В CIC фильтре есть цепочка интеграторов и цепочка гребенчатых фильтров. Количество звеньев в этих цепях это и есть Stages. Каждое звено состоит из линии задержки и сумматора или вычитателя. Z в степени (-m) это задержка на m тактов. Это параметр Differential Delay.
После CIC фильтра обычно рекомендуется ставить корректирующий FIR фильтр. Он должен исправить спадающую АЧХ, сделать её более прямоугольной. На Хабре уже была статья по этой теме от автора @hukenovs. Я же честно говоря по своей возможно лени просто нашел готовый скрипт для Matlab который вычисляет коэффициенты FIR фильтра. Кстати говоря, в былые времена в среде САПР Altera Quartus II при генерации визардом CIC фильтра сразу генерировался и такой скрипт.
В общем, я запустил скрипт в GNU Octave и он мне всё посчитал:
Скрипт расчета коэффициентов FIR фильтра
И даже нарисовал вот такие АЧХ:
Как видно из графика итоговая АЧХ связки CIC-FIR фильтры должна быть почти прямоугольная, это жёлтый график Total Response.
Ну, а я теперь же уже могу измерить и реальную АЧХ связки фильтров CIC-FIR своим проектом:
Реальная АЧХ связки фильтров CIC-FIR для проекта SDR приёмника
Получилось вот так.
Теперь, чтобы сделать приёмник осталось совсем чуть чуть поднапрячься. Добавил в проект умножители для переноса спектра, добавил логику для передачи пакетов выборок данных (которые получаются после FIR фильтра) из платы Марсоход3GW2 на компьютер через последовательный порт. И еще добавил в проект модуль beeper, который эмулирует АМ модулированный сигнал. Я могу нажимать на плате на кнопочку KEY0 и тогда данные на умножители приёмника будут поступать не из АЦП платы, а с бипера. Кнопочка KEY1 меняет тон сигнала бипера. В бипере используется таблица синусоиды из 32х элементов. Если перебирать эти элементы на частоте 50МГц, то несущая частота бипера получается 50000000 / 32 = 1562500 Гц. С помощью бипера можно понять вообще проект работает или нет. Я честно говоря пока делал проект вообще не был уверен поймаю ли я хоть какую-то станцию или нет.
Самый тонкий момент — антенна, антенный усилитель, входной фильтр. Сказать по правде, ничего этого у меня нет. Я знаю, что без входного фильтра оцифровывать нельзя, но хотелось испытать приёмник хоть как ни будь побыстрее.
Поэтому, я просто прикрепил кусок провода ко входу АЦП платы Марсоход3GW2 через делитель напряжения:
Подключение антенны к SDR приёмнику
А вот и сама антенна, просто свисает с балкона:
Антенна
Весь этот проект для FPGA платы Марсоход3GW2 можно взять на гитхабе.
Тут есть еще важный момент. В проекте есть исходники специального плагина для программы HDSDR, выполненные в Visual Studio. Это ExtIO_FPGA. После компиляции в Visual Studio получается ExtIO_Example.dll и её нужно просто скопировать в папку с программой HDSDR. Программа HDSDR используется для прослушивания радиостанций в эфире.
Этот DLL ExtIO_Example.dll открывает последовательный порт COM8 на скорости 12Мбит/сек и ведет обмен с платой FPGA. Она посылает команду перестроить частоту в плату и принимает выборки сигнала от платы для дальнейшей обработки в программе HDSDR.
Ну и вот результат испытания приёмника:
Здесь примерно 20 минут сканирования эфира. Я сканирую вручную от 800КГц до 9МГц. Не могу сказать, что результат отличный, но приёмник несомненно работает. Да, я согласен, что много шумов: сказывается отсутствие входного усилителя и антиалиасингового фильтра.
Тем не менее, я обнаружил следующие радиопередачи:
855 (музыка), 864, 999 (русский), 1026, 1035 (русский), 1071, 1188, 1377, 1413 (русский), 6015, 6040, 6070, 6145, 6165 (английский), 7220, 7255 (английский), 7265 (музыка, китайский на русском), 7300 (музыка), 7350 (музыка), 7420, 7445 (китайский), 9370, 9485, 9630 (музыка), 9760, 9820, 9860.
Некоторые передачи плохо слышно, даже слова не разобрать, некоторые вполне разборчивые. Где-то играет музыка. Иногда я отчётливо ловил французскую или итальянскую речь. Другие языки мне трудно идентифицировать, то ли арабский, то ли иранский.
Должен сказать, что количество принимаемых станций похоже сильно зависит от времени дня и ночи. Для меня эти станции появляются где-то после 20:00 по Москве. Утром же и днем почти ничего не слышно. Так же видимо влияет погода.
Тем не менее, я результатом вполне доволен. Можно продолжать улучшать этот проект. Надеюсь этот проект покажется интересным и вам.
Описания других проектов для FPGA платы Марсоход3GW2 можно посмотреть вот здесь. Исходники всех проектов для этой платы, включая простейший RISC-V процессор PICOTINY есть на гитхабе.