В составе GnuRadio существует такой блок как M&M clock recovery. Его задача — восстанавливать семплы из сигнала с одинаковой частотой и фазой, отправляемые передатчиком. Это необходимо, к примеру, когда вы хотите извлечь символы из асинхронного цифрового сигнала. Блок позволяет определить с центры единиц и нулей в поступающем сигнале.
Ознакомившись с информацией в интернете, я обнаружил что данный блок является «черным ящиком» для большинства пользователей GnuRadio. Вот тут есть немножко конкретной информации о том, как он работает, а самое главное — как сконфигурировать параметры блока под ваш конкретный случай. Самое полезное что я вынес из этой информации — это советы и предположения, какие параметры следует попробовать.
Данная статья — попытка немного упорядочить полученную мной информацию. Я заранее предупреждаю, что не полностью понимаю как работает данный алгоритм. То что будет написано ниже — мое текущее понимание всех тех источников что я изучил. Научная работа, указанная в исходном коде блока, отсылает нас к оригинальной статье 1976 года описывающей данный метод. Я прочел лишь небольшую часть этой литературы. Вы можете понять, это плохая замена нормальной документации к исходному коду, которую все хотели бы видеть.
Имплементированный алгоритм расположен в методе general_loop(), а сам метод находится в файле clock_recovery_mm_ff_impl.cc Хочу заметить, что что сейчася я говорю о реализации алгоритма который работает со значениями типа «float». Там также есть файл, который реализует работу и с комплексными числами, но я не изучал его. Работать он должен аналогично.
while(oo < noutput_items && ii < ni ) { // produce output sample out[oo] = d_interp->interpolate(&in[ii], d_mu); mm_val = slice(d_last_sample) * out[oo] - slice(out[oo]) * d_last_sample; d_last_sample = out[oo]; d_omega = d_omega + d_gain_omega * mm_val; d_omega = d_omega_mid + gr::branchless_clip(d_omega-d_omega_mid, d_omega_lim); d_mu = d_mu + d_omega + d_gain_mu * mm_val; ii += (int)floor(d_mu); d_mu = d_mu - floor(d_mu); oo++; }
С первого взгляда мы сразу можем увидеть некоторые особенности алгоритма, реализованные в GnuRadio. Возможно они и покажутся специалистам очевидными, но увы они нигде не отражены явно в документации.
— Это децимирующий блок. Блок выводит один семпл на один или несколько входных семплов. Конкретнее, блок будет выводить семпл который, по его мнению, является серединой символа. Это может быть первой вещью сбивающей с толка, ибо ничто в названии блока не позволяет предположить децимацию.
— Коэффициент децимации не постоянен. Он изменяется в зависимости от того, какую символьную частоту выходных данных предугадал блок. Это очень важно для GnuRadio, поскольку многие блоки могут повести себя странно с потоком данных, который не имеет постоянной частоты семплирования (пример: блок Scope, когда у него несколько входов)
— Во входном сигнале должен быть переход через ноль. Алгоритм работает на обнаружении смены знака сигнала (метод slice() это по сути своей кусочно-постоянная функция)
— В реализованном алгоритме присутствует интерполяция. d_interp->interpolate() это КИХ интерполятор с 8 коэффициентами. Это значит что выходной сигнал может содержать значения не представленные во входном сигнале.
Исходный код блока также проясняет смысл параметров с таинственными названиями.
— Omega есть период символа, измеряемый в семплах на один символ.
Иначе говоря, это величина обратная частоте символов, или тактовой частоте. Это значение передаваемое алгоритму как параметр, является точкой опоры для предсказания.
— Relative это границы в которых может отклоняться Omega в течение работы от базового значения.
К примеру для omega равной 10, и omega_relative_limit=0.1, параметр omega может меняться от 9 до 11. Надо отметить что в GnuRadio до версии 3.7.5.1 параметр relative_limit был абсолютным, по причине программной ошибки.
— Mu это детектированный фазовый сдвиг в семплах между приемником и передатчиком.
Начальное значение полученное как параметр блока, не имеет никакого смысла, так как обычно нельзя предугадать фазу сигнала заранее. Внутри блока фаза отслеживатеся между 0 и Omega (что соотносится значениям 0 и 2π). Однако из-за того, как код написан, вы можете задать лишь дробную часть значения (т.е. до 1.0)
Mu gain и Omega gain это шаг в петлях автоподстройки, которые подстраивают частоту и фазу во время работы.
Эффект от этих параметров отличается для сигналов разной амплитуды. Оптимальные значения для сигнала с амплитудой от -1 до +1 не будут работать так же для аналогичного сигнала, но с амплитудой от -10 до +10
Oсновываясь на коде выше попробуем представить как блок М&М восстанавливает семплы из входного сигнала
Давайте представим синусоидальный сигнал показанную на рисунке ниже как представление бинарного потока нулей (<0) и единиц (>0) проходящий через фильтр.
Mu будет расстояние между первым семплом и (ожидаемым?) переходом через ноль. Omega будет расстоянием между двумя аналогичными семплами:
Взглянув еще раз на алгоритм полдстройки Omega и Mu во время работы (через значение mm_val) можно увидеть что:
— Петля обратной связи не работает на прямоугольных сигналах.
Метод определения центра символа полагается на то, что центром будет гребень. У прямоугольного сигнала на всем своем протяжении гребень отсутствует, и у алгоритма нет шансов определить центр сигнала
— Алгоритм не будет отслеживать фазу на абсолютно симметричном входном сигнале.
Взгляните на синусоиду на картинке выше. Если частота сигнала (Omega) безупречно настроена, остаточный член всегда будет нулевой, независимо от фазы семплирования (Mu)
Это важно учитывать когда вы будете разрабатывать синтетические тест для отладки, а так же может вызвать проблему в случае когда вы хотите синхронизироваться на периодический заголовок (много пакетных передач предваряют данные синхронизационным заголовком).
Подводя черту, могу сказать что требуется ловкость для того чтобы заставить работать блок корректно. По факту, после большого количества экспериментов мне не удалось заставить его работать лучше чем бесхитростный дециматор. Так что могу сказать, что при наличии точной оценки частоты символов, и ФНЧ работающим перед ним (который совпадает с символьной скорость), обычный несинхронизирующий дециматор будет работать для вас приемлимо. В случае коротких пакетных передач вы можете потерять некоторые пакеты если они прибудут с частично неверной разницей фаз, но в целом результат будет приемлим.
Подозреваю что большое количество отчетов о успешной работе блока М&М вызвано не его превосходной работой, а простотой сигнала. По моему опыту эвристическое восстановление частоты символов работает лучше (к примеру то что реализовано в захвате данных am433 и ec3k приемниками). Пусть оно и не научно, и вычислительно более сложно, его работа более последовательна, следовательно проще писать и отлаживать тесты.
Наконец,, я подозреваю что алгоритм релизованный в GnuRadio поражен некоторыми практическими проблемами его имплементаци. На мой взгляд интерполятор не работает так как надо,( в рассылке discuss-gnuradio я не получил ответа на мой вопрос). Пока я не исследовал этот глубже. Еще я подозреваю что код может терять отслеживание фазы между вызовами метода general_work() (остается только дробная часть фазы между вызовами метода). Все эти вещи могут происходить более-менее часто в зависимости от того где блок расположен на графе, и могут вызывать периодическую десинхронизацию.
Оригинал статьи: Notes on M&M clock recovery © Tomaž Šolc