Детектор Tayloe и цифровая обработка сигналов #2 LO на SI5351A

Автор: | 19.09.2025

Предыдущая часть.

В этой части расскажу как я приручил тактовый генератор SI5351A для своего проекта. Мне необходимо два перестраиваемых сигнала, один для схемы детектора Тейло, другой для автоматической коррекции разбаланса сигналов IQ по диапазонам. SI5351А полностью подходит под задачи, имеет три выхода, дешевый, по нему уже много информации, и я успешно применил его в своем синтезаторе.

Генератор выдает два сигнала на выходах CLK0/CLK1 только в режиме настроек, в рабочем режиме работает один выход CLK0, для чистоты сигнала на выходе. Вот исходники, для STM32, но не сложно и под AVR переделать, по сути надо только переписать функцию отправки данных в генератор по шине I2C

Хорошее описание данного генератора, есть тут. В сети также не мало и библиотек для данного генератора, но я как обычно, для понимания происходящего, лезу в теорию))). Основные определения в файле si5351.h.

Схема

С выхода генератора сигнал заданной частоты приема умноженной на 4, подается на триггер. С триггера выходят два сигнала QSD-0 и QSD-1, сигналы сдвинуты по фазе относительно друг друга на 90°. Например, нам надо настроить приемник на частоту 7,1МГц, с генератора подаем 28,4МГц, после триггера получаем два сигнала с фазами 0° и 90°, частотой 7,1МГц каждый. Пример синусоидальных сигналов с фазами 0° и 90°

Почему не использую сразу два выхода генератора? Ведь можно в генераторе задать фазу сигнала, тут все просто, во первых, грязный сигнал на выходе при использовании двух выходов, во вторых на высоких частотах нарушается точность фазировки сигнала, а максимальная требуемая частота будет 29,9МГц, значит генератор должен выдать 119,6МГц. С выхода триггера получаем чистый меандр с точной фазировкой на входах мультиплексора детектора Тейло. Есть еще решения вместо триггера использовать схемы на регистрах, но в данном применении это не оправдано. Триггер SN74LVC74 может работать и от 5в и от 3.3в, при питании 3.3в, вместо конденсатора С14 устанавливается перемычка, а резисторы R4 и R5 не устанавливаются.

Пройдемся по функциям, первая это запись 8-битного значения регистра в SI5351A по шине I2C, с защитой от зависания и перезапуском I2C при «отваливании» шины

HAL_StatusTypeDef SI5351A_WriteData(uint8_t reg, uint8_t value)

HAL_StatusTypeDef SI5351A_WriteData(uint8_t reg, uint8_t value)
{
HAL_StatusTypeDef status;
uint8_t retries = 3;

while (retries—)
{
status = HAL_I2C_IsDeviceReady(&hi2c2, (SI_I2C_ADDR << 1), 3, 10);
if (status == HAL_OK)
{
status = HAL_I2C_Mem_Write(&hi2c2, SI_I2C_ADDR<<1,
reg, I2C_MEMADD_SIZE_8BIT,
&value, 1, 10);
if (status == HAL_OK) return HAL_OK;
}

//Если ошибка, перезапускаем I2C
__HAL_RCC_I2C2_FORCE_RESET();
__HAL_RCC_I2C2_RELEASE_RESET();
HAL_Delay(1);
}

return status; //Если все попытки неудачны
}

Функция калькуляции PLL (ФАПЧ), в проекте выходы генератора CLK0 и CLK1 жестко привязаны к PLLA и PLLB соответственно, применена только целочисленная математика, как известно, в таком случае сигнал получается более чистый.

void SI5351A_SetupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)

void SI5351A_SetupPLL(uint8_t pll, uint8_t mult, uint32_t num, uint32_t denom)
{
//Проверка диапазона mult
if(mult < 15) mult = 15;
if(mult > 90) mult = 90;

uint32_t frac = (uint32_t)((((uint64_t)num) << 7) / denom); //Замена floor(128*num/denom)

uint32_t P1 = 128 * mult + frac — 512;
uint32_t P2 = ((uint64_t)num << 7) — (uint64_t)denom * frac;
uint32_t P3 = denom;

//Запись в регистры SI5351A
SI5351A_WriteData(pll + 0, (P3 & 0x0000FF00) >> 8);
SI5351A_WriteData(pll + 1, (P3 & 0x000000FF));
SI5351A_WriteData(pll + 2, (P1 & 0x00030000) >> 16);
SI5351A_WriteData(pll + 3, (P1 & 0x0000FF00) >> 8);
SI5351A_WriteData(pll + 4, (P1 & 0x000000FF));
SI5351A_WriteData(pll + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
SI5351A_WriteData(pll + 6, (P2 & 0x0000FF00) >> 8);
SI5351A_WriteData(pll + 7, (P2 & 0x000000FF));
}

Функция установки MultiSynth с целочисленным делителем и R-делителем. R-делитель — это битовое значение, которое помещается в соответствующий регистр, в моем случае R-делитель всегда равен 1, так как мне не нужна генерация частот ниже 1МГц. По факту при использовании R-делителя можно получить на выходе сигнал с частотой ~2.5кГц.

void SI5351A_SetupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)

void SI5351A_SetupMultisynth(uint8_t synth, uint32_t divider, uint8_t rDiv)
{
uint32_t P1 = 128 * divider — 512;
uint32_t P2 = 0;
uint32_t P3 = 1;

//Запись в регистры SI5351A
SI5351A_WriteData(synth + 0, (P3 & 0x0000FF00) >> 8);
SI5351A_WriteData(synth + 1, (P3 & 0x000000FF));
SI5351A_WriteData(synth + 2, ((P1 & 0x00030000) >> 16) | rDiv);
SI5351A_WriteData(synth + 3, (P1 & 0x0000FF00) >> 8);
SI5351A_WriteData(synth + 4, (P1 & 0x000000FF));
SI5351A_WriteData(synth + 5, ((P3 & 0x000F0000) >> 12) | ((P2 & 0x000F0000) >> 16));
SI5351A_WriteData(synth + 6, (P2 & 0x0000FF00) >> 8);
SI5351A_WriteData(synth + 7, (P2 & 0x000000FF));
}

Функция отключения генерации частоты на выбранном выходе

void SI5351A_OutOff(uint8_t clk)

void SI5351A_OutOff(uint8_t clk)
{
SI5351A_WriteData(clk, SI_CLKX_OFF);
}

Функция включения генерации на указанном выходе, параметры — выход CLK0/CLK1/CLK2 и выбор силы тока драйвера. В данной функции как раз и происходит жесткая привязка выхода к конкретному PLL.

void SI5351A_SetDrvClk(uint8_t clk, uint8_t drv)

void SI5351A_SetDrvClk(uint8_t clk, uint8_t drv)
{
if (clk == SI_CLK0_CTRL) {SI5351A_WriteData(clk, drv | SI_CLK_SRC_PLL_A);} //CLK0 от PLLA
else if (clk == SI_CLK1_CTRL) {SI5351A_WriteData(clk, drv | SI_CLK_SRC_PLL_B);} //CLK1 от PLLB
}

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

void SI5351A_ResetPLLx(uint8_t pll)

void SI5351A_ResetPLLx(uint8_t pll)
{
if (pll == SI_SYNTH_PLL_A) {SI5351A_WriteData(SI_PLL_RST, SI_PLL_A_RST);} //Сброс PLLA
else if (pll == SI_SYNTH_PLL_B) {SI5351A_WriteData(SI_PLL_RST, SI_PLL_B_RST);} //Сброс PLLB
}

Функция инициализации генератора. В первую очередь отключаем все выходы через регистр 3, с этим регистром есть путаница. Коротко, этот регистр не отключает генерацию выходов CLKx, а отключает сами выходы, т.е. например, если нам надо просто отключить выход но не менять его настройки, чтобы в дальнейшем не тратить время микроконтроллера на калькуляцию регистров для данного выхода.

Далее отключаем уже генерацию на всех трех выходах командой SI5351A_OutOff(SI_CLKx_CTRL).

Указание источника опорной частоты, выполняется командой  SI5351A_WriteData(SI_XTL_SRC_PLL, SI_XTL_SRC_PLL_AB), по большому счету для корпуса генератора с десятью пинами 10-MSOP, где используется только три выхода, эта настройка не актуальна, т.к. для данного случая, тактирование всегда настроено на использование кварцевого резонатора, даже если подавать на пин XA сигнал от внешнего генератора.

Отдельно остановимся на нагрузке кварцевого резонатора, используемого для тактирования генератора, внутренними конденсаторами, чем точнее выбрана суммарная емкость внутренних и внешних конденсаторов, тем точнее генерация частоты, тут правило следующее, надо использовать значение емкости нагрузочных конденсаторов, рекомендуемое производителем конкретной модели применяемого кварцевого резонатора. При использовании выводных кварцевых резонаторов, следует учитывать паразитную емкость выводов. Возможно подключение внутренних нагрузочных конденсаторов со значениями 6pF, 8pF или 10pF, по умолчанию установлено значение 10pF. Обычно этим условием пренебрегают и даже не устанавливают внешние нагрузочные конденсаторы, а точность генерации частоты корректируют значением, используемым для калькуляции регистров, кварцевого резонатора, в моем случае это определение — SI_XTALL_FREQ_DEF. Тогда частота на всем перекрываемом диапазоне будет иметь погрешность не более 2-5Гц.

Следующий шаг инициализации это одновременный сброс обоих PLL через 177-ой регистр командой SI5351A_WriteData(SI_PLL_RST, SI_PLL_ALL_RST).

В завершении включаем только два необходимых выхода CLK0 и CLK1.

void SI5351_Init(void)

void SI5351_Init(void)
{
/*
* Запрещаем все выходы, значения:
* 0xFF — все запрещены
* 0xFE — разрешен только CLK0
* 0xFC — разрешены только CLK0/CLK1
* 0xF8 — разрешены только CLK0/CLK1/CLK2
*/
SI5351A_WriteData(SI_CLKX_DISABLE, 0xFF);

//Отключаем генерацию на всех трех выходах
SI5351A_OutOff(SI_CLK0_CTRL);
SI5351A_OutOff(SI_CLK1_CTRL);
SI5351A_OutOff(SI_CLK2_CTRL);

//Источник опорной частоты — кварц для обеих PLL_A/B, для SI5351A (10-MSOP) не актуально
SI5351A_WriteData(SI_XTL_SRC_PLL, SI_XTL_SRC_PLL_AB);

/*
* Нагрузка кварцевого резонатора внутренними конденсаторами
*/
SI5351A_WriteData(SI_CRYSTAL_LOAD, SI_CRYSTAL_LOAD_10PF);

//Сбрасываем оба PLL
SI5351A_WriteData(SI_PLL_RST, SI_PLL_ALL_RST);

//Разрешаем выходы, только CLK0/CLK1
SI5351A_WriteData(SI_CLKX_DISABLE, 0xFC);
}

Теперь основная функция, это установка заданной частоты на выходе CLK0 или CLK1. В функции применена также целочисленная математика. Реализован автоматический выбор частоты PLL в зависимости от заданного значения частоты, применен алгоритм который более рекомендован, т.е. при генерируемых частотах выше 80МГц используем для калькуляции частоту PLL равную 600МГц, при частотах в диапазоне 20…80МГц, используем частоту PLL 900МГц, а при частотах ниже 20МГц, начальная частота PLL 600МГц. На данный момент указанные границы 20/80МГц я еще не корректировал, буду деалать это уже на макете.

void SI5351A_SetFreq(uint8_t clk, uint32_t siXtallFeq, uint32_t freq)

void SI5351A_SetFreq(uint8_t clk, uint32_t siXtallFeq, uint32_t freq)
{
//Для CLK0 — умножаем на 4, так как сигнал идёт на триггер для формирования 0°/90°
if (clk == SI_CLK0_CTRL) {freq *= 4;}

uint32_t l;
float f;
uint8_t mult = 0;
uint32_t num = 0;
uint32_t denom;
uint32_t divider;
uint32_t pllFreq;

//Автовыбор оптимальной частоты PLL
if (freq >= SI_PLL_BOUNDS_80)
{
//80 МГц и выше, SI5351A генерирует ВЧ
pllFreq = SI_PLL_VCO_MIN; //Минимизируем divider, лучше стабильность
}
else if (freq >= SI_PLL_BOUNDS_20)
{
//20–80 МГц, максимизируем чистоту PLL, ниже фазовый шум
pllFreq = SI_PLL_VCO_MAX;
}
else
{
//Ниже 20 МГц, большой divider
pllFreq = SI_PLL_VCO_MIN; //Уменьшаем divider, лучше точность и стабильность
}

//Ррасчет коэффициента деления
divider = pllFreq / freq;

//Целочисленное деление
if (divider % 2) divider—;

//Расчет частоты PLL
pllFreq = divider * freq;

/*
* Расчет множителя для получения заданной pllFreq
* состоит из трех частей:
* 1: mult — целое число, которое должно быть в диапазоне 15..90;
* 2: num и denom — дробные части, числитель и знаменатель,
* каждый из них 20 bits (диапазон 0..1048575);
* 3: фактический множитель mult + num / denom,
* для простоты знаменатель установлен на максимум = 1048575
*/

mult = pllFreq / siXtallFeq;
l = pllFreq % siXtallFeq;
f = l;
f *= SI_MULTIPLIER;
f /= siXtallFeq;
num = f;
denom = SI_MULTIPLIER;

//Настройка PLL-A/B с рассчитанным коэффициентом умножения
if (clk == SI_CLK0_CTRL) {SI5351A_SetupPLL(SI_SYNTH_PLL_A, mult, num, denom); SI5351A_ResetPLLx(SI_SYNTH_PLL_A);}
else if (clk == SI_CLK1_CTRL) {SI5351A_SetupPLL(SI_SYNTH_PLL_B, mult, num, denom); SI5351A_ResetPLLx(SI_SYNTH_PLL_B);}

/*
* Установка делителя на выбранном MultiSynth с рассчитанным делителем,
* последний этап деления R может делиться на степень 2, начиная с 1..128
* представлен константами SI_R_DIV1….SI_R_DIV128,
* если необходимо вывести частоты ниже 1 МГц, вы должны использовать SI_R_DIV128
*/

//Установка заданной частоты на заданном выходе
if (clk == SI_CLK0_CTRL) {SI5351A_SetupMultisynth(SI_SYNTH_MS_0, divider, SI_R_DIV_1);}
else if (clk == SI_CLK1_CTRL) {SI5351A_SetupMultisynth(SI_SYNTH_MS_1, divider, SI_R_DIV_1);}

//Сброс текщего PLL, при значительной смене частоты, например при переключении диапазонов
/*if (ChangeBand)
{
if (clk == SI_CLK0_CTRL) {SI5351A_ResetPLLx(SI_SYNTH_PLL_A);}
else if (clk == SI_CLK1_CTRL) {SI5351A_ResetPLLx(SI_SYNTH_PLL_B);}
}*/
}

Продолжение следует…

73!

Детектор Tayloe и цифровая обработка сигналов #2 LO на SI5351A: 3 комментария

  1. Дмитрий Руднев

    Если не затруднит, посмотрите, как это всё несколько лет назад было реализовано в проекте Селенит:
    https://github.com/dmitrii-rudnev/selenite-lite/ (файлы si5351a.c и rxtx_if.c)
    и можете спокойно применять у себя со ссылкой на мой проект

    С уважением, Дмитрий Руднев (RD9F)

    1. Radio9OFG Автор записи

      Здравствуйте, Дмитрий! Ваш проект знаком, изучил вдоль и поперек! Спасибо, 73!

      1. Дмитрий Руднев

        Спасибо на добром слове! Не зря, значит, публиковался :)))

        С уважением, Дмитрий Руднев (RD9F)

Добавить комментарий для Radio9OFG Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *