Когда-то давно, аж 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 был готов я приступил к созданию CIC фильтров. Я так же использую готовый компонент из библиотеки Gowin IP Core:
Поскольку я собираюсь вести оцифровку сигнала с антенны на частоте 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 фильтра, потом сделаю и поставлю измеритель амплитуды, то потом можно просто отображать измеренную амплитуду к примеру на семисегментном индикаторе платы.
И я сделал такой вспомогательный проект. Более подробное описание проекта можно почитать здесь. А его исходники вот здесь, на гитхабе.
На компьютере я запускаю мою программу на питоне, которая передает команду установить частоту на NCO:
Мне нужно установить разные частоты в NCO в диапазоне и смотреть амлитуду сигнала после фильтра на семисегментном индикаторе на плате, потом записывать значения в таблицу:
С помощью этого проекта я измерил реальные АЧХ трех разных CIC фильтров: 2-4, 2-5 и 1-4, где первое число это Diff Delay, а второе число это количество Stages.
Вот что у меня получилось при экспериментальном измерении АЧХ:
На самом деле я рад, что эксперимент подтверждает теорию. По крайней мере практически похоже я двигаюсь в правильном направлении. Для SDR приёмника я выбираю исполнение CIC фильтра Diff Delay 2 и количество Stages 4. Это тёмно-синий график. Так и крутизна фильтра хорошая и при этом не очень много ресурсов в ПЛИС забирает такой фильтр. Фильтр 1-4 занимает меньше всего ресурсов в ПЛИС, но его АЧХ не очень крутая (жёлтый цвет на графике выше).
В CIC фильтре есть цепочка интеграторов и цепочка гребенчатых фильтров. Количество звеньев в этих цепях это и есть Stages. Каждое звено состоит из линии задержки и сумматора или вычитателя. Z в степени (-m) это задержка на m тактов. Это параметр Differential Delay.
После CIC фильтра обычно рекомендуется ставить корректирующий FIR фильтр. Он должен исправить спадающую АЧХ, сделать её более прямоугольной. На Хабре уже была статья по этой теме от автора @hukenovs. Я же честно говоря по своей возможно лени просто нашел готовый скрипт для Matlab который вычисляет коэффициенты FIR фильтра. Кстати говоря, в былые времена в среде САПР Altera Quartus II при генерации визардом CIC фильтра сразу генерировался и такой скрипт.
В общем, я запустил скрипт в GNU Octave и он мне всё посчитал:
И даже нарисовал вот такие АЧХ:
Как видно из графика итоговая АЧХ связки CIC-FIR фильтры должна быть почти прямоугольная, это жёлтый график Total Response.
Ну а я теперь же уже могу измерить и реальную АЧХ связки фильтров CIC-FIR своим проектом:
Получилось вот так.
Теперь, чтобы сделать приёмник осталось совсем чуть чуть поднапрячься. Добавил в проект умножители для переноса спектра, добавил логику для передачи пакетов выборок данных (которые получаются после FIR фильтра) из платы Марсоход3GW2 на компьютер через последовательный порт. И еще добавил в проект модуль beeper, который эмулирует АМ модулированный сигнал. Я могу нажимать на плате на кнопочку KEY0 и тогда данные на умножители приёмника будут поступать не из АЦП платы, а с бипера. Кнопочка KEY1 меняет тон сигнала бипера. В бипере используется таблица синусоиды из 32х элементов. Если перебирать эти элементы на частоте 50МГц, то несущая частота бипера получается 50000000 / 32 = 1562500 Гц. С помощью бипера можно понять вообще проект работает или нет. Я честно говоря пока делал проект вообще не был уверен поймаю ли я хоть какую-то станцию или нет.
Самый тонкий момент - антенна, антенный усилитель, входной фильтр. Сказать по правде, ничего этого у меня нет. Я знаю, что без входного фильтра оцифровывать нельзя, но хотелось испытать приёмник хоть как ни будь побыстрее.
Поэтому, я просто прикрепил кусок провода ко входу АЦП платы Марсоход3GW2 через делитель напряжения:
А вот и сама антенна, просто свисает с балкона:
Весь этот проект для 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 есть на гитхабе.
Комментарии (12)
Gudd-Head
21.11.2024 07:53АЦП на плате Марсоход3GW2 имеет восьмибитную ADC1175-50. Если я с NCO так же возьму 8 бит и перемножу их, то получится 16 бит входные числа на фильтр
Если ограничить NCO и/или АЦП до ±127, то после перемножения получится 15 бит.
nckma Автор
21.11.2024 07:53Хм.. а почему?
sinc
21.11.2024 07:53|127*127| < 2^15
nckma Автор
21.11.2024 07:53Мне уже разъяснили. Как я понял, это трюк ПЛИСоводов. Ограничить сигнал с АЦП до -127 не допуская значения -128 (saturation). В этом случае при перемножении никогда не возникнет переполнение и действительно можно снизить разрядность входного сигнала на один бит, а значит далее сэкономить ресурсы в CIC фильтре.
Правильно я понял?
vadimk91
21.11.2024 07:53Сейчас в городах из-за помех (привет цифровизации всех устройств вообще и к примеру китайским источникам питания в частности), даже на профессиональную технику практически ничего не принять, а тут автору удалось реально проверить работу приемника - зачет. Антенна в виде одного и того же хвостика и для длинных и коротких волн конечно крайне неэффективна. И да, и в прошлом веке прием СВ/КВ диапазонах зависел от времени суток, состояния ионосферы и много чего)
mozg37
21.11.2024 07:53Отличная работа. Вот это отличный пример, в тч для учебных целей. Пожалуй, если не поленюсь - напишу про связку с ад9361.
sdy
21.11.2024 07:53Параметр D определяет задержку скользящего среднего гребенчатого фильтра. По сути чем больше окно, тем выше эффект сглаживания
Stages - это порядок фильтра, вводится для улучшения его характерисики затухания. Чем больше порядок, тем выше скорость нарастания затухания
Stages влияет на разрядность сумматора input_width + Stages * log2(down_sampling_rate). Как раз в параметрах CIC видно, что выходная разрядность получается 16+4*log2(500)=52. Откуда еще 4 бита взялись я не знаю, но подозреваю что они всегда будут добавляться, может особенность реализации компонента просто
Суперская статья, вспомнил былое. Спасибо
yakov_cyb
21.11.2024 07:53У вас не совсем корректно подключена антенна ко входу АЦП. В такой конфигурации вы собираете все помехи имеющиеся в вашем доме. Используйте РЧ трансформатор для согласования с высоким импедансом АЦП, как это показано в даташитах. До антенны нужно довести коаксиальный кабель. Далее для качественного приема на 8бит АЦП необходим предусилитель, хорошим выбором будет транзистор bfg591.
yakovkovach
Отличный проект, молодцы!