Микроконтроллеры STM32: пишем драйвер для Si5351

7 декабря 2020

Si5351 — это управляемый по I2C генератор частот от 8 кГц до 160 МГц. Чип имеет три канала с выходным импедансом 50 Ом. Уровень сигнала может регулироваться примерно от 2 до 11 dBm. За счет сочетания цены и качества Si5351 очень популярен среди радиолюбителей. В частности, он используется в КВ-трансиверах uBITX и QCX, антенных анализаторах EU1KY и NanoVNA. Сегодня мы познакомимся с данным генератором поближе, а также поймем, как он может быть использован с микроконтроллерами STM32.

Существует несколько вариантов Si5351. Различия между ними описаны в даташите [PDF]. Характерно, что это даташит для Si5351‐B, более новых модификаций чипа на частоты от 2.5 кГц до 200 МГц. Они обратно совместимы с Si5351 без «‐B» в названии. В рамках этой статьи мы рассмотрим лишь базовый функционал, общий для всех модификаций.

Эксперименты будем проводить на следующей плате:

Модуль на базе Si5351

Такие модули предлагают многие продавцы как на eBay, так и на AliExpress. Цена вместе с доставкой в Россию составляет менее 5$. На плате имеется вся обвязка, необходимая Si5351, а также регулятор напряжения и преобразователь уровня сигнала. Модуль может быть запитан от 5 В или 3.3 В. Он совместим с 5-и и 3.3-волтовой логикой.

Из готовых библиотек под STM32 было найдено три варианта. Во-первых, библиотека из кода прошивки антенного анализатора EU1KY. Библиотека умеет все необходимое, но распространяется под GPL. Также реализация показалась мне подозрительно сложной. Во-вторых, библиотека из кода прошивки NanoVNA. Код на первый взгляд нормальный, но лицензия также GPL. Наконец, в-третьих, библиотека si5351-stm32, которая является портом библиотеки под Arduino от Adafruit. Обе распространяются под лицензией BSD. Код мне преимущественно понравился. Но в нем не нашлось процедуры «генерируй сигнал с такой-то частотой на таком-то канале». Также в коде была обнаружена пара ошибок.

Fun fact! Драйвер для Si5351 есть в коде ядра Linux. Хотя и не очень понятно, зачем он там нужен.

Было решено взять за основу последнюю библиотеку и доработать ее. Вся необходимая информация была найдена в документе AN619: Manually Generating an Si5351 Register Map for 10-MSOP and 20-QFN Devices [PDF]. Хотя написан он немного запутанно. Отчасти, потому что одновременно рассказывает о разных модификациях Si5351, а отчасти, потому что использует одинаковые обозначения для разных вещей.

Рассмотрим упрощенную структурную схему Si5351:

Диаграмма внутреннего устройства Si5351

Чип имеет два встроенных ФАПЧ (PLL). Каждый может быть настроен при помощи трех целых чисел a, b и c. Частота внешнего кварцевого резонатора Fxtal преобразуется по приведенным выше формулам, в результате чего может быть получена любая частота Fpll от 600 МГц до 900 МГц. Для наших модулей Fxtal составляет 25 МГц.

Сигнал с частотой Fpll идет на делитель MultiSynth divider (MS). В отличие от PLL, MS уже не два, а по одному на канал. MS также настраивается при помощи трех целых чисел x, y и z, но он не умножает сигнал, а делит. На выходе MS может быть любая частота Fms от 500 кГц до 200 МГц.

Наконец, последний блок в схеме — это R divider. Его по одному на канал, как и MS. Этот блок выполняет деление Fms на степень двойки от 1 до 128. Блок нужен главным образом для получения частот ниже 500 кГц. Выходной сигнал CLK может быть в интервале от 8 кГц до 160 МГц.

Характерно, что a, b и c чипу мы не передаем. Вместо этого вычисляются:

P1 = 128 * a + (128 * b)/c - 512
// P2 = 128 * b - c * ((128 * b)/c)
P2 = (128 * b) % c
P3 = c

… и уже они передаются Si5351. Таким же образом происходит передача x, y и z.

Управление Si5351 осуществляется путем записи значений в 8-и битные регистры по I2C. Запись происходит точно так же, как ранее мы это делали для EEPROM. Нужно только знать, как интерпретируются данные, записанные в регистр с тем или иным адресом. Это описано в AN619 и уже реализовано во взятой за основу библиотеке.

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

// Calculates PLL, MS and RDiv settings for given Fclk in 8K..160M range
void si5351_Calc(int32_t Fclk,
                 si5351PLLConfig_t* pll_conf,
                 si5351OutputConfig_t* out_conf) {
    if(Fclk < 1000000) {
        Fclk *= 64;
        out_conf->rdiv = SI5351_R_DIV_64;
    } else {
        out_conf->rdiv = SI5351_R_DIV_1;
    }

    // Apply correction, _after_ determining rdiv.
    Fclk = Fclk - ((Fclk/1000000)*si5351Correction)/100;

    const int32_t Fxtal = 25000000;
    int32_t a, b, c, x, y, z, t;
    if(Fclk < 81000000) {
        // Valid for Fclk in 0.5..112.5 Meg range
        // However an error is > 6 Hz above 81 Megs
        a = 36; // PLL runs @ 900 Meg
        b = 0;
        c = 1;
        int32_t Fpll = 900000000;
        x = Fpll/Fclk;
        // t = ceil(Fclk/0xFFFFF)
        t = Fclk >> 20;
        if(Fclk & 0xFFFFF) {
            t += 1;
        }
        y = (Fpll % Fclk) / t;
        z = Fclk / t;
    } else {
        // Valid for Fclk in 75..160 Meg range
        if(Fclk >= 150000000) {
            x = 4;
        } else if (Fclk >= 100000000) {
            x = 6;
        } else {
            x = 8;
        }
        y = 0;
        z = 1;

        int32_t numerator = x*Fclk;
        a = numerator/Fxtal;
        // t = ceil(Fxtal/0xFFFFF)
        t = Fxtal >> 20;
        if(Fxtal & 0xFFFFF) {
            t += 1;
        }
        b = (numerator % Fxtal) / t;
        c = Fxtal / t;
    }

    pll_conf->mult = a;
    pll_conf->num = b;
    pll_conf->denom = c;
    out_conf->div = x;
    out_conf->num = y;
    out_conf->denom = z;
}

Суть алгоритма в следующем. Если нужно получить частоту ниже 1 МГц, умножим частоту на 64, взяв соответствующий R, и этим сводим задачу к поиску решения для 0.5-64 МГц. Далее, если требуется получить частоту до 81 МГц, говорим, что PLL работает на 900 МГц. То есть, мы выбрали N и можем вычислить из него M. А зная M, мы автоматически получаем x, y и z, поскольку они — лишь способ представить дробное число с помощью целых чисел. Если же частота больше 81 МГц, говорим, что MS делит на 4, 6 или 8, в зависимости от того, насколько большую частоту нужно получить (см код). Таким образом, мы выбрали M и можем вычислить из него N, из которого сразу получаем a, b и c.

Представленный алгоритм находит такие параметры, что в худшем случае результирующая частота отличаться от целевой на 6 Гц, в связи с ошибками округления. Это в теории. В физическом мире все чуточку сложнее, особенно на частотах ниже 1 МГц и выше 100 МГц. Тем не менее, вооруженный частотомером я наблюдал ошибку не более 10 Гц на всем интервале от 8 кГц до 160 МГц. Для моих текущих задач такой точности хватит с лихвой. Но если в вашей задаче требуется большая точность, то, естественно, придется придумать другой алгоритм.

Окончательный интерфейс библиотеки был поделен на две части — базовый и расширенный. Базовый интерфейс выглядит так:

const int32_t correction = 978;
si5351_Init(correction);

// 28 MHz @ ~7 dBm
si5351_SetupCLK0(28000000, SI5351_DRIVE_STRENGTH_4MA);

// 144 MHz @ ~7 dBm
si5351_SetupCLK2(144000000, SI5351_DRIVE_STRENGTH_4MA);

// Enable CLK0 and CLK2
si5351_EnableOutputs((1<<0) | (1<<2));

Поскольку чип имеет два PLL, то совершенно не зависеть друг от друга могут только два канала. В базовом интерфейсе в качестве таких каналов были выбраны CLK0 и CLK2, поскольку в модуле Si5351 они физически находятся далеко друг от друга. К ним просто удобнее подключаться, чем к CLK0 и CLK1.

Расширенный интерфейс позволяет использовать три канала, но он сложнее:

const int32_t correction = 978;
si5351_Init(correction);

si5351PLLConfig_t pll_conf;
si5351OutputConfig_t out_conf;
int32_t Fclk = 7000000; // 7 MHz

si5351_Calc(Fclk, &pll_conf, &out_conf);
si5351_SetupPLL(SI5351_PLL_A, &pll_conf);
si5351_SetupOutput(0, SI5351_PLL_A, SI5351_DRIVE_STRENGTH_4MA,
                   &out_conf);
si5351_EnableOutputs(1<<0);

При использовании этого интерфейса можно и нужно использовать знание о том, что si5351_Calc всегда использует Fpll = 900 МГц для частот ниже 81 МГц. В принципе, этот Fpll подходит для частот до 112.5 МГц, если вас устраивает ошибка генерируемой частоты 13 Гц вместо 6 Гц. Вы можете пропатчить si5351_Calc соответствующим образом.

Наконец, вас наверняка интересует, что это за:

const int32_t correction = 978;

В реальном мире Si5351 генерирует сигналы, частота которых имеет ошибку. Эта ошибка зависит от частоты, притом линейно. Физические причины, по которым зависимость именно линейная, мне неизвестны. Однако любой нормальный драйвер к Si5351 поддерживает так называемый correction factor для компенсации данной ошибки. У меня он определяется, как ошибка при генерации частоты 100 МГц. Поскольку зависимость линейная, то ошибка может быть измерена на любой частоте. Например, если вместо 10,000,000 Гц без коррекции вы получаете 10,000,097.8 Гц, то коррекция равна 97.8*(100 МГц/10 МГц) = 978. Теперь с коррекцией 978 синтезатор должен выдавать ровно 10 МГц, а также любую другую частоту с погрешностью ~10 Гц в худшем случае.

Fun fact! Si5351 генерирует сигнал прямоугольной формы, который богат гармониками. С подходящими фильтрами и усилителями генератор можно использовать на частотах до 1.5 ГГц и даже выше.

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

Дополнение: В продолжение темы см заметку Генерация сигналов с фазовым сдвигом при помощи Si5351. Примеры использования драйвера вы найдете в статьях HBR: коротковолновый CW/SSB QRP-трансивер, AYN: телеграфный QRP трансивер на 20/40 метров и далее по ссылкам.

Метки: , , , .