Embedded HandbookПриложения
Все разделы › III. Периферия
STM32F103 · уровень средний · 37 мин чтения
Открыть в интерактивном справочнике

18ж. Генератор тактовых частот Si5351 по I2C: кварц 25 МГц, PLL и дробные делители, выходы CLK0–CLK2, расчёт a+b/c, сдвиг фазы и квадратура

В разделе 18е синус получался из таблицы: DDS AD9833 считает фазу и выдаёт отсчёты через ЦАП. Si5351 устроен иначе: внутри нет ни таблицы, ни ЦАП, есть две PLL и делители — как умножитель PLL самого STM32 из 6.2 (кварц 8 МГц × 9 = 72 МГц), только множитель и делитель дробные. На выходе — меандр КМОП от 8 кГц до 160 МГц, три выхода с разными частотами, сдвиг фазы между ними. Это гетеродин приёмника, опорная частота для другой платы, пара сигналов I и Q со сдвигом 90°. Здесь — драйвер: какие регистры писать и в каком порядке, как из нужной частоты получить дробь a + b/c и уложить её в P1, P2, P3, почему делитель выхода лучше брать чётным целым, когда сбрасывать PLL, как получить квадратуру и как выправить уход кварца по эталонному частотомеру. Кварц как таковой и его нагрузочная ёмкость — в разделе 174, генераторы на столе — в 36к.

Опоры: RM0008 (DocID13902 Rev 17): приём одного байта ведущим, с. 764. DocID13587 Rev 17 (STM32F103x8/xB): PB6 — I2C1_SCL, PB7 — I2C1_SDA, оба FT, с. 32. Устройство I2C — раздел 17, здесь не повторяется. Документа производителя на Si5351 (описание микросхемы и AN619 с картой регистров) в проекте нет, драйвера на диске тоже. Адрес 0x60, номера регистров и биты, раскладка P1, P2, P3 по байтам, формулы с округлением вниз, пределы PLL 600–900 МГц, делители 15…90 и 4, 6, 8…2048, R до 128 и диапазон 8 кГц…160 МГц — по документации производителя, не проверено; они сверены только со своей моделью микросхемы, в которую заложены те же сведения. Все числа ниже — прогон драйвера _verify/clk18h/si18h.c, собранного arm-none-eabi-gcc 12.2.1 под Cortex-M3 и запущенного в QEMU 7.2.22, против модели Si5351A на Python 3.11; прошивка собрана armclang 6.24 под Cortex-M3. Кварц модуля, фаза после записи и время действия записанных байт — допущения модели: эти числа говорят о модели, а не о микросхеме.

Как это называется в документации

Оригинал Перевод
clock generator, frequency synthesizer генератор тактовых частот, синтезатор частоты
PLL — phase-locked loop ФАПЧ: петля, держащая VCO на частоте кварц × (a + b/c)
VCO — voltage-controlled oscillator генератор PLL, здесь 600–900 МГц
feedback multisynth MSNA, MSNB делитель в петле PLLA и PLLB: множитель a + b/c
output multisynth MS0–MS2 делитель выхода: d или a + b/c
R divider выходной делитель на 1, 2, 4 … 128
integer mode (MSx_INT, FBx_INT) целочисленный режим делителя
output enable (OEB) разрешение выхода, активный ноль
phase offset (CLKx_PHOFF) сдвиг фазы выхода в четвертях периода VCO
PLL soft reset сброс PLL записью в регистр 177
I/Q, quadrature квадратура: два сигнала со сдвигом 90°
ppm, ppb миллионные и миллиардные доли

Вверху — подключение модуля Si5351A к STM32F103: I2C1 на 100 кГц, SCL на PB6, SDA на PB7, адрес 0x60, подтяжки к 3,3 В; на модуле кварц 25 МГц, выходы КМОП; регистр 0 — SYS_INIT, его ждут до нуля, регистр 3 — бит 1 выключает выход, регистр 183 — нагрузочная ёмкость кварца 0x52, 0x92 или 0xD2, регистр 177 — сброс PLLA битом 0x20 и PLLB битом 0x80. Посередине — путь частоты: кварц, PLLA и PLLB с множителем a + b/c от 15 до 90 в регистрах 26–33 и 34–41, VCO 600–900 МГц, делители выходов MS0–MS2 в регистрах 42–65 — 4, 6 или 8…2048, чётный целый даёт меньше джиттера по документации, делители R на 1…128, выходы CLK0–CLK2 со сдвигом фазы в регистрах 165–167. Внизу — формулы P1 = 128·a + floor(128·b/c) − 512, P2 = 128·b − c·floor(128·b/c), P3 = c при c не больше 1048575; пример 35 + 21/25: P1 = 4075, P2 = 13, P3 = 25, восемь байт с регистра 26 — 00 19 00 0F EB 00 00 0D; для 7,074 МГц делитель 126, VCO 891,324 МГц, PLL 35 + 4081/6250; сдвиг CLK1 на d четвертей периода VCO — это 90°, он действует после сброса PLL


18ж.1 Устройство: кварц, две PLL, три делителя и регистры

Путь частоты в Si5351A с тремя выходами (по документации производителя, не проверено):

f_VCO = f_XTAL · (a + b/c)
f_out = f_VCO / (d · R)

Здесь f_XTAL — кварц, f_VCO — частота PLLA или PLLB, 600…900 МГц, a = 15…90; d — делитель выхода, R = 1, 2, 4 … 128.

Кварц 25 МГц кормит две PLL. Каждая умножает его на дробь a + b/c, где c — до 1048575 (20 бит). Каждый выход берёт одну из двух PLL и делит её своим делителем MultiSynth: на 4, на 6 или на любую дробь от 8 до 2048. После него — делитель R на степень двойки. Три выхода и две PLL: два выхода могут иметь полностью независимые частоты, третий делит PLL одного из них.

Микросхема отвечает на I2C по адресу 0x60. Регистры, которые пишет драйвер:

регистр что в нём
0 статус: SYS_INIT (бит 7) — ещё загружается, LOL_B, LOL_A — PLL не захвачена
3 бит k = 1 — выход CLKk выключен
15 источник PLL: 0 — обе от кварца
16–18 CLK0–CLK2: PDN (бит 7), MS_INT (6), MS_SRC (5) — PLLB, INV (4), CLK_SRC (3:2) = 11 — свой делитель, IDRV (1:0) — 2, 4, 6, 8 мА
22, 23 FBA_INT, FBB_INT (бит 6) — делитель PLL целый
26–33, 34–41 P1, P2, P3 делителя PLLA и PLLB
42–49, 50–57, 58–65 P1, P2, P3 делителей MS0–MS2; в третьем байте — R (биты 6:4) и DIVBY4 (3:2)
165–167 сдвиг фазы CLK0–CLK2, 7 бит
177 сброс PLLA (бит 5) и PLLB (бит 7)
183 нагрузочная ёмкость кварца, биты 7:6

Частоты драйвер держит в миллигерцах в uint64_t, как в 18е: 900 МГц — это 9·10¹¹ мГц, произведение с кварцем и знаменателем ещё помещается в 64 бита, а плавающая точка не нужна.


18ж.2 Подключение к F103 и запуск

Модуль сидит на I2C1: SCL — PB6, SDA — PB7, оба вывода FT (с. 32), но модуль питают от 3,3 В, и подтяжки идут к 3,3 В. Шина — 100 кГц, настройка CCR и TRISE — та же, что в разделе 99г, как в 17.3: CCR = 180, TRISE = 37. Драйверу нужны только запись и чтение одного байта статуса — приём одного байта, с. 764.

Запись — номер первого регистра, затем данные подряд: адрес внутри микросхемы растёт сам. Порядок запуска (по документации производителя, не проверено) — дождаться SYS_INIT = 0, выключить и обесточить выходы, задать ёмкость кварца и источник PLL:

    {
        const uint8_t oe[2] = {SI_OE, 0xFF};                /* 1. все выходы выключить */
        const uint8_t pdn[4] = {SI_CLK_CTRL, SI_CLK_PDN, SI_CLK_PDN, SI_CLK_PDN};   /* 2. и обесточить */
        uint8_t cl[2] = {SI_XTAL_CL, xtal_cl};              /* 3. нагрузочная ёмкость кварца */
        const uint8_t src[2] = {SI_PLL_SRC, 0x00};          /* 4. PLLA и PLLB — от кварца */
        if ((rc = wr(oe, 2)) != SI_OK || (rc = wr(pdn, 4)) != SI_OK || (rc = wr(cl, 2)) != SI_OK)
            return rc;
        return wr(src, 2);
    }

Обмены запуска в модели — 7 посылок, 2510 мкс на шине:

посылка что
00 → 80, 00 → 80, 00 → 00 статус: SYS_INIT держится первые три чтения (допущение модели), пауза 1 мс между ними
03 FF все выходы выключены
10 80 80 80 CLK0–CLK2 обесточены
B7 D2 ёмкость кварца 10 пФ
0F 00 обе PLL — от кварца

Нет ответа на 0x60 — драйвер возвращает SI_E_BUS, −2; SYS_INIT не снимается — после 100 чтений SI_E_TIMEOUT, −3. Первая частота, 10 МГц на CLK0 — ещё 6 посылок, 3000 мкс: 1A 00 01 00 10 00 00 00 00 — делитель PLLA, 16 40 — FBA_INT, 2A 00 01 00 2B 00 00 00 00 — делитель MS0, 10 4F — CLK0 от PLLA, целый, 8 мА, B1 20 — сброс PLLA, 03 FE — CLK0 включён.

Нагрузочная ёмкость. Регистр 183: биты 7:6 — 01, 10 или 11, это 6, 8 или 10 пФ, младшие шесть бит — 010010 (по документации производителя, не проверено): 0x52, 0x92, 0xD2. Ёмкость выбирают по кварцу модуля, как в 174.3: не та ёмкость сдвигает частоту. Сколько именно — модель не считает; уход в любом случае снимает калибровка (18ж.6).


18ж.3 a + b/c → P1, P2, P3

Делитель не пишут как a, b и c. Его пишут тремя числами (по документации производителя, не проверено):

P1 = 128·a + floor(128·b/c) − 512
P2 = 128·b − c·floor(128·b/c)
P3 = c

P1 — 18 бит, P2 и P3 — по 20. Обратно: a + b/c = (P1 + 512 + P2/P3) / 128. Целая часть 128·b/c уходит в P1, остаток — в P2: так P1 несёт делитель с точностью 1/128, а P2/P3 — то, что мельче. Для целого делителя b = 0, c = 1: P2 = 0, P3 = 1.

void si_params(si_frac_t f, si_p_t *p)
{
    uint32_t fl = (uint32_t)((128ull * f.b) / f.c);         /* floor(128·b / c) */
    p->p1 = 128u * f.a + fl - 512u;
    p->p2 = 128u * f.b - f.c * fl;
    p->p3 = f.c;
}

Пример: 35 + 21/25 (PLL на 896 МГц). 128 · 21 / 25 = 107,52, вниз — 107; P1 = 4480 + 107 − 512 = 4075; P2 = 2688 − 25 · 107 = 13; P3 = 25. Восемь байт ложатся так: P3 старшим байтом вперёд, в третьем байте — старшие два бита P1 (и R с DIVBY4 у делителя выхода), дальше P1, в шестом байте старшие четыре бита P3 и P2, потом P2:

void si_pack(const si_p_t *p, uint32_t r, int divby4, uint8_t out[8])
{
    out[0] = (uint8_t)(p->p3 >> 8);
    out[1] = (uint8_t)p->p3;
    out[2] = (uint8_t)((r & 7u) << 4 | (divby4 ? SI_DIVBY4 : 0u) | ((p->p1 >> 16) & 3u));
    out[3] = (uint8_t)(p->p1 >> 8);
    out[4] = (uint8_t)p->p1;
    out[5] = (uint8_t)(((p->p3 >> 12) & 0xF0u) | ((p->p2 >> 16) & 0x0Fu));
    out[6] = (uint8_t)(p->p2 >> 8);
    out[7] = (uint8_t)p->p2;
}

Модель собрала из байтов a + b/c обратно:

a + b/c P1 P2 P3 байты
36 + 0/1 4096 0 1 00 01 00 10 00 00 00 00
35 + 21/25 4075 13 25 00 19 00 0F EB 00 00 0D
35 + 621/625 4095 113 625 02 71 00 0F FF 00 00 71
28 + 1/1048575 3072 128 1048575 FF FF 00 0C 00 F0 00 80
89 + 1048574/1048575 11007 1048447 1048575 FF FF 00 2A FF FF FF 7F
8 512 0 1 00 01 00 02 00 00 00 00
126 15616 0 1 00 01 00 3D 00 00 00 00
4 0 0 1 00 01 00 00 00 00 00 00

Деление на 4 — особый случай: P1 = P2 = 0, P3 = 1 и DIVBY4 = 11. Формула даёт те же P1, P2, P3, но без DIVBY4 микросхема на 4 не делит (по документации производителя, не проверено).


18ж.4 Делители: R, чётный делитель выхода, дробная PLL и ошибка частоты

Один выход на своей PLL драйвер считает так:

  1. ниже 500 кГц — включить R: удваивать частоту, пока не станет не меньше 500 кГц (R до 128);
  2. выше 150 МГц — делитель 4; иначе — чётный целый делитель, при котором VCO как можно ближе к 900 МГц сверху вниз;
  3. дробную часть отдать PLL: a + b/c = VCO / 25 МГц.

Целый делитель выхода с чётным значением — это MS_INT = 1, и по документации производителя так меньше джиттер (не проверено: модель джиттер не считает). Дробная остаётся только PLL.

int si_plan(const si_t *s, uint64_t f_mhz, si_plan_t *pl)
{
    uint64_t fo;
    if (f_mhz < SI_F_MIN || f_mhz > SI_F_MAX)
        return SI_E_RANGE;
    pl->r = 0;
    while ((f_mhz << pl->r) < SI_R_FLOOR && pl->r < 7u)     /* ниже 500 кГц — поднять частоту делителем R */
        pl->r++;
    fo = f_mhz << pl->r;
    if (fo > 150000000000ull)
        pl->d = 4;                                          /* выше 150 МГц — только деление на 4 */
    else
        pl->d = (uint32_t)(SI_VCO_MAX / fo) & ~1u;          /* чётный, VCO как можно ближе к 900 МГц */
    if (pl->d > SI_MS_MAX)
        pl->d = SI_MS_MAX;
    pl->vco_mhz = fo * pl->d;
    if (pl->vco_mhz < SI_VCO_MIN || pl->vco_mhz > SI_VCO_MAX)
        return SI_E_RANGE;
    pl->pll = si_ratio(pl->vco_mhz, s->xtal_mhz, s->method);
    if (!pll_ok(pl->pll))
        return SI_E_RANGE;
    pl->f_mhz = si_out_mhz(s->xtal_mhz, pl->pll, pl->d, pl->r);
    return SI_OK;
}

Почему чётный делитель всегда влезает: если частота не выше 150 МГц, то 900 / f не меньше 6, и VCO = d · f теряет до двух f от 900 МГц — не опускается ниже 600 МГц.

Как найти b/c. Простой путь — c = 1048575 всегда, b — ближайшее целое. Другой — лучшая дробь с c ≤ 1048575: подходящие дроби цепной дроби и последняя промежуточная, без float. Модель на кварце ровно 25 МГц:

заказано R d VCO PLL (лучшая дробь) ошибка при c = 1048575
8 кГц 64 1756 899,072 МГц 35 + 3009/3125 +0,022 мГц
10 кГц 64 1406 899,84 МГц 35 + 621/625 −0,032 мГц
32 768 Гц 16 1716 899,678208 МГц 35 + 385597/390625 −0,068 мГц
100 кГц 8 1124 899,2 МГц 35 + 121/125 +1,061 мГц
455 кГц 2 988 899,08 МГц 35 + 602/625 −5,309 мГц
1 МГц 1 900 900 МГц 36 0
3,579545 МГц 1 250 894,88625 МГц 35 + 15909/20000 +1,550 мГц
7,074 МГц 1 126 891,324 МГц 35 + 4081/6250 +88,556 мГц
10 МГц 1 90 900 МГц 36 0
14,074 МГц 1 62 872,588 МГц 34 + 5647/6250 −186,120 мГц
27 МГц 1 32 864 МГц 34 + 14/25 0
28,8 МГц 1 30 864 МГц 34 + 14/25 0
100 МГц 1 8 800 МГц 32 0
125 МГц 1 6 750 МГц 30 0
148,5 МГц 1 6 891 МГц 35 + 16/25 0
150 МГц 1 6 900 МГц 36 0
160 МГц 1 4 640 МГц 25 + 3/5 0

С лучшей дробью все 17 частот вышли ровно — модель собрала их из записанных регистров. Ниже 8 кГц и выше 160 МГц драйвер отказывает: 7999,999 Гц и 160 000 000,001 Гц — оба SI_E_RANGE, −1.

Ошибка от c ≤ 1048575. При c = 1048575 шаг PLL — 25 МГц / 1048575 = 23,8419 Гц на VCO, на выходе он делится на d: 0,1892 Гц на 7,074 МГц (d = 126), 0,2649 Гц на 10 МГц, 0,3845 Гц на 14,074 МГц. Ошибка — до половины шага. Модель прогнала 2000 случайных частот от 1 до 100 МГц с шагом 1 Гц:

способ худшая ошибка в ppb ровно
c = 1048575 1475,073 мГц 16,024 —
лучшая дробь 0,954 мГц 0,081 627 из 2000

Худшая при c = 1048575 — на 92 055 717 Гц, где d = 8: половина шага на выходе — 23,8419 / 2 / 8 = 1,4901 Гц. Обе ошибки меньше ухода любого кварца: частоту на выходе определяет он, а не дробь (18ж.6).


18ж.5 Три выхода на двух PLL, перестройка и сброс PLL

Свою PLL получают два выхода. Третьему драйвер даёт дробный делитель от уже занятой PLL — si_set_freq_shared. Модель: CLK0 = 7,074 МГц на PLLA, CLK1 = 12,288 МГц на PLLB, CLK2 = 32 768 Гц от той же PLLB:

выход PLL VCO делитель R частота
CLK0 A 891,324 МГц 126, целый 1 7 074 000,000 Гц
CLK1 B 884,736 МГц 72, целый 1 12 288 000,000 Гц
CLK2 B 884,736 МГц 1687,5, дробный 16 32 768,000 Гц

Сбросов PLL — 2, по одному на хозяина. Дробный делитель CLK2 PLL не сбрасывает: на ней чужой выход. Если у PLL ещё нет хозяина, si_set_freq_shared возвращает SI_E_ARG, −4.

Перестройка. Гетеродин на 7,000…7,200 МГц с шагом 1 кГц — 201 шаг. Внутри одного делителя выхода меняется только PLL, и шаг — одна посылка из 9 байт 1A …, 920 мкс на 100 кГц. Делитель сменился дважды: 128 → 126 на 7032 кГц и 126 → 124 на 7143 кГц. На смене — ещё делитель выхода, CLK0_CTRL и сброс PLL:

    if ((rc = write_pll(s, pll, pl.pll)) != SI_OK)          /* перестройка внутри одного делителя — только PLL */
        return rc;
    s->vco_mhz[pll] = pl.vco_mhz;
    if (changed) {
        si_frac_t ms = {pl.d, 0, 1};
        if ((rc = write_ms(clk, ms, pl.r, pl.d == 4u)) != SI_OK || (rc = write_ctrl(s, clk, ctrl)) != SI_OK)
            return rc;
        s->d[clk] = pl.d;
        s->r[clk] = pl.r;
    }
    if ((changed || s->always_reset) && (rc = si_pll_reset(s, pll)) != SI_OK)   /* новый делитель — сброс PLL */
        return rc;
сброс PLL сбросов посылок на шине
только при смене делителя 2 207 187 920 мкс
при каждом шаге 201 406 245 630 мкс

Частота на всех 201 шаге — ровно заказанная, в обоих режимах. Сброс (регистр 177: 0x20 — PLLA, 0x80 — PLLB) перезапускает все выходы этой PLL с нулевой фазой. В модели на каждом сбросе фаза выхода прыгала — до 180,00°; сколько выход стоит, пока PLL снова захватится, модель не знает (захват в ней мгновенный). В приёмнике это щелчок. Сбрасывать нужно, когда сменился делитель выхода или нужна общая фаза (18ж.6) — при перестройке одной PLL без смены делителя модель сброса не требует.


18ж.6 Сдвиг фазы, квадратура I/Q и калибровка по эталону

Сдвиг фазы. Регистры 165–167 задают задержку выхода в четвертях периода VCO, 7 бит (по документации производителя, не проверено). Период выхода при целом делителе d — это d периодов VCO, четверть периода выхода — d четвертей. Отсюда квадратура: CLK0 и CLK1 от одной PLL с одним и тем же целым делителем d, сдвиг CLK0 — 0, CLK1 — d. Семь бит — d не больше 127, чётный — 126, и VCO не ниже 600 МГц: квадратура только от 600 МГц / 126 = 4,762 МГц.

    ph[0] = SI_PHOFF0;
    ph[1] = 0;
    ph[2] = (uint8_t)pl.d;                                  /* CLK1 позже на d · T_VCO / 4 = четверть периода */
    if ((rc = wr(ctrl, 3)) != SI_OK || (rc = wr(ph, 3)) != SI_OK)
        return rc;

Сдвиг начинает действовать после сброса PLL (в модели — так; по документации производителя, не проверено), поэтому si_quadrature всегда кончается сбросом PLLA. Модель:

частота d VCO CLK1 отстаёт шаг сдвига
7,074 МГц 126 891,324 МГц 90,00° 0,714°
4,8 МГц 126 604,8 МГц 90,00° 0,714°
4,762 МГц 126 600,012 МГц 90,00° 0,714°
4,761 МГц — — отказ, −1 —
30 МГц 30 900 МГц 90,00° 3°
100 МГц 8 800 МГц 90,00° 11,25°
150 МГц 6 900 МГц 90,00° 15°

Четверть периода VCO на 891,324 МГц — 0,2805 нс. Через 12,3 мс сдвиг тот же, 90,00°. Что ломает квадратуру в модели:

Перестройка I/Q — всегда со сбросом: si_quadrature на соседнюю частоту — 7 посылок, из них один сброс. Две микросхемы AD9833 для той же квадратуры требовали общего RESET (18е.2); здесь хватает одной микросхемы и одной PLL.

Калибровка. Точность выхода задаёт кварц, а не дробь. В модели кварц модуля — 25 000 437 Гц, +17,48 ppm (допущение модели). Драйвер считает с 25 МГц, и первая же частота 10 МГц вышла 10 000 174,8 Гц. До калибровки: +175 Гц на 10 МГц, +124 Гц на 7,074 МГц, +1748 Гц на 100 МГц. Эталонный частотомер (в модели — счёт 1 с с округлением до 1 Гц) показал 10 000 175 Гц; драйвер считает уход в миллиардных долях и поправляет частоту кварца:

void si_calibrate(si_t *s, uint64_t f_set_mhz, uint64_t f_meas_mhz)
{
    int64_t e = (int64_t)f_meas_mhz - (int64_t)f_set_mhz;
    int64_t ppb = div_round(e * 1000000000, (int64_t)f_set_mhz);   /* уход кварца в миллиардных долях */
    s->xtal_mhz = (uint64_t)((int64_t)s->xtal_mhz + div_round((int64_t)s->xtal_mhz * ppb, 1000000000));
    s->ppb += (int32_t)ppb;
}

Поправка — 17 500 ppb, кварц для драйвера — 25 000 437,5 Гц. После пересчёта делителей: −0,2 Гц на 10 МГц, −0,1415 Гц на 7,074 МГц, −2,0 Гц на 100 МГц. Остаток — 20 ppb: это разрешение частотомера, 1 Гц на 10 МГц, а не драйвера. Чем выше частота сравнения и длиннее счёт, тем точнее поправка. Кварц стареет и уходит с температурой (174.4), так что калибровка — не навсегда.


18ж.7 Грабли

Байты делителя по одному. Драйвер «регистр — посылка» пишет восемь байт делителя восемью посылками. В модели записанное действует в конце посылки (допущение модели), и переход 7,000 → 7,030 МГц прошёл через семь промежуточных частот по 290 мкс каждая: 6 999 243,482 Гц, трижды 6 999 238,281 Гц, трижды 7 029 755,859 Гц — и только потом 7 030 000,000 Гц. Здесь промахи малы, но для другой пары частот они могут выйти за пределы VCO. Восемь байт — одной посылкой, как write_pll; когда настоящая микросхема подхватывает новые P1–P3, не проверено.

Сброс PLL на каждом шаге. Многие примеры сбрасывают PLL после любой смены частоты. Это 201 сброс вместо 2 на 201 шаге перестройки, на 31 % больше времени на шине, и на каждом — скачок фазы (18ж.5). Сброс — только при смене делителя выхода или ради общей фазы.

Низкая частота без R. 100 кГц от VCO 900 МГц — делитель 9000, больше 2048. Наивный драйвер всё равно пишет его: P1 = 1151488, а в 18 бит влезает только 262143. Старшие биты пропали, модель прочла делитель 808 и выдала 1 113 861,386 Гц вместо 100 кГц. Ниже 500 кГц — делитель R (18ж.4).

Не тот кварц. Бывают модули с кварцем 27 МГц. Драйвер, который считает с 25 МГц, на 10 МГц выдал в модели 10 800 000 Гц — на 8 % больше. Калибровка по эталону поправила кварц на 80 000 000 ppb, до 27 000 000 Гц, и 10 МГц вышли ровно. Лучше прочитать маркировку кварца и сразу передать правильную частоту в si_init.

Квадратура ниже 4,762 МГц. Сдвиг — 7 бит, делитель для 90° не больше 126 при VCO не ниже 600 МГц: 4,761 МГц драйвер отвергает. Для КВ ниже этого I/Q делают иначе — например, выходом на 4f и делителем на триггерах; здесь это не проверялось.

Нагрузка и уровень выхода. Выход — КМОП с размахом от земли до питания выходов, на модулях обычно 3,3 В, сила — 2, 4, 6 или 8 мА битами IDRV (по документации производителя, не проверено). Это меандр, а не синус: у идеального меандра 3-я гармоника на −9,54 дБ от первой, 5-я — на −13,98 дБ, 7-я — на −16,90 дБ; смеситель или вход 50 Ом без фильтра получат и их. Как меандр выглядит на конце кабеля и зачем согласование — 36к.3 и 36к.5. Модель напряжений и токов не считает.

Диапазон. 8 кГц…160 МГц — по документации производителя (не проверено). Делители позволили бы и больше: 2,289 кГц снизу (600 МГц / 2048 / 128), но драйвер их не обещает и отказывает вне 8 кГц…160 МГц.

Выход не включился. После si_init все выходы выключены регистром 3 и обесточены битом PDN. Если писать делители без драйвера, не забыть CLKk_CTRL (снять PDN, CLK_SRC = 11) и бит в регистре 3.


18ж.8 Итоги

Полные исходники

si18h.h — биты и регистры Si5351A, структуры и интерфейс драйвера и платформы.

/* si18h.h — раздел 18ж: драйвер генератора тактовых частот Si5351A (3 выхода) без регистров STM32.
 * Драйвер считает делители и пишет регистры через функцию платформы si_port_xfer(): запись байтов, затем
 * (если nrx > 0) повторный старт и чтение. В прошивке fw18h.c её даёт I2C1 STM32F103, в стенде host18h.c —
 * модель model18h.py. Адреса регистров и биты — по документации производителя (AN619; в проекте её нет,
 * не проверено), сверены только с моделью. Частоты — в миллигерцах (мГц), чтобы считать без float. */
#ifndef SI18H_H
#define SI18H_H
#include <stdint.h>

#define SI_ADDR         0x60u           /* 7-битный адрес Si5351A */

/* ── регистры ─────────────────────────────────────────────────────────── */
#define SI_STATUS       0u              /* SYS_INIT, LOL_B, LOL_A, LOS, REVID */
#define SI_OE           3u              /* бит k = 1 — выход CLKk выключен */
#define SI_PLL_SRC      15u             /* 0 — обе PLL от кварца */
#define SI_CLK_CTRL     16u             /* 16, 17, 18 — CLK0…CLK2 */
#define SI_FB_CTRL      22u             /* 22 — бит FBA_INT, 23 — FBB_INT */
#define SI_MSNA         26u             /* 26…33 — P1, P2, P3 делителя PLLA */
#define SI_MSNB         34u             /* 34…41 — то же для PLLB */
#define SI_MS0          42u             /* 42 + 8·k — делитель MultiSynth выхода k, R и DIVBY4 */
#define SI_PHOFF0       165u            /* 165 + k — сдвиг фазы CLKk, 7 бит */
#define SI_PLL_RST      177u            /* сброс PLL */
#define SI_XTAL_CL      183u            /* нагрузочная ёмкость кварца */

/* ── биты ─────────────────────────────────────────────────────────────── */
#define SI_SYS_INIT     0x80u           /* статус: микросхема ещё загружается */
#define SI_LOL_B        0x40u
#define SI_LOL_A        0x20u
#define SI_CLK_PDN      0x80u           /* CLKk_CTRL: выход обесточен */
#define SI_CLK_INT      0x40u           /* MSk_INT: делитель выхода целый */
#define SI_CLK_PLLB     0x20u           /* MSk_SRC: от PLLB, иначе от PLLA */
#define SI_CLK_INV      0x10u           /* выход инвертирован: +180° */
#define SI_CLK_SRC_MS   0x0Cu           /* CLKk_SRC = 11: выход от своего MultiSynth */
#define SI_FB_INT       0x40u           /* FBx_INT: делитель PLL целый */
#define SI_DIVBY4       0x0Cu           /* MSk_DIVBY4 = 11: деление на 4 */
#define SI_PLLA_RST     0x20u
#define SI_PLLB_RST     0x80u
#define SI_CL_6PF       0x52u           /* биты 7:6 — 01, 10, 11; биты 5:0 — 010010 */
#define SI_CL_8PF       0x92u
#define SI_CL_10PF      0xD2u

/* ── пределы (по документации производителя, не проверено) ────────────── */
#define SI_C_MAX        1048575u        /* знаменатель c — 20 бит */
#define SI_VCO_MIN      600000000000ull /* 600 МГц в мГц */
#define SI_VCO_MAX      900000000000ull /* 900 МГц */
#define SI_PLL_A_MIN    15u
#define SI_PLL_A_MAX    90u
#define SI_MS_MAX       2048u
#define SI_F_MIN        8000000ull      /* 8 кГц в мГц */
#define SI_F_MAX        160000000000ull /* 160 МГц */
#define SI_R_FLOOR      500000000ull    /* ниже 500 кГц — делитель R */
#define SI_IQ_D_MAX     126u            /* сдвиг фазы — 7 бит: чётный делитель не больше 126 */

enum { SI_OK = 0, SI_E_RANGE = -1, SI_E_BUS = -2, SI_E_TIMEOUT = -3, SI_E_ARG = -4 };
enum { SI_FIXED_C = 0, SI_BEST = 1 };   /* как искать b/c: c = 1048575 или лучшая дробь */

typedef struct { uint32_t a, b, c; } si_frac_t;          /* a + b/c */
typedef struct { uint32_t p1, p2, p3; } si_p_t;          /* то, что ложится в регистры */

typedef struct {
    uint32_t r;                         /* R = 2^r, r = 0…7 */
    uint32_t d;                         /* делитель MultiSynth: 4, 6 или чётный 8…2048 */
    uint64_t vco_mhz;                   /* заказанная частота PLL */
    si_frac_t pll;                      /* делитель PLL: vco / кварц */
    uint64_t f_mhz;                     /* что получится при заданном кварце */
} si_plan_t;

typedef struct {
    uint64_t xtal_mhz;                  /* частота кварца в мГц, с поправкой калибровки */
    int32_t ppb;                        /* поправка калибровки, миллиардные доли */
    uint8_t method;                     /* SI_FIXED_C или SI_BEST */
    uint8_t always_reset;               /* 1 — сбрасывать PLL при каждой смене частоты (как во многих примерах) */
    uint8_t idrv;                       /* сила выхода 0…3: 2, 4, 6, 8 мА */
    uint8_t oe;                         /* копия регистра 3 */
    uint8_t ctrl[3];                    /* копии CLK0…CLK2_CTRL */
    uint8_t fb[2];                      /* копии регистров 22 и 23: FBA_INT, FBB_INT */
    uint32_t d[3], r[3];                /* последние делители выходов: сменились — нужен сброс PLL */
    uint64_t vco_mhz[2];                /* что сейчас на PLLA и PLLB */
    uint32_t resets;                    /* сколько раз сбрасывали PLL */
} si_t;

/* ── платформа: прошивка или стенд ────────────────────────────────────── */
int si_port_xfer(uint8_t addr7, const uint8_t *tx, uint32_t ntx, uint8_t *rx, uint32_t nrx);
void si_port_delay_us(uint32_t us);

/* ── арифметика ───────────────────────────────────────────────────────── */
si_frac_t si_ratio(uint64_t num, uint64_t den, int method);   /* num/den ≈ a + b/c, c ≤ 1048575 */
void si_params(si_frac_t f, si_p_t *p);                       /* a + b/c -> P1, P2, P3 */
void si_pack(const si_p_t *p, uint32_t r, int divby4, uint8_t out[8]);
uint64_t si_out_mhz(uint64_t xtal_mhz, si_frac_t pll, uint32_t d, uint32_t r);
int si_plan(const si_t *s, uint64_t f_mhz, si_plan_t *pl);   /* R, чётный делитель, дробная PLL */
int si_plan_iq(const si_t *s, uint64_t f_mhz, si_plan_t *pl);/* для I/Q: делитель не больше 126 */

/* ── управление ───────────────────────────────────────────────────────── */
int si_init(si_t *s, uint32_t xtal_hz, uint8_t xtal_cl);
int si_set_freq(si_t *s, int clk, int pll, uint64_t f_mhz);  /* своя PLL: чётный делитель, дробная PLL */
int si_set_freq_shared(si_t *s, int clk, int pll, uint64_t f_mhz);  /* PLL уже занята: дробный делитель выхода */
int si_quadrature(si_t *s, uint64_t f_mhz);                   /* CLK0 и CLK1 на PLLA, CLK1 отстаёт на 90° */
int si_pll_reset(si_t *s, int pll);
int si_output(si_t *s, int clk, int on);
int si_invert(si_t *s, int clk, int inv);
void si_calibrate(si_t *s, uint64_t f_set_mhz, uint64_t f_meas_mhz);  /* по эталонному частотомеру */

#endif

si18h.c — a + b/c и лучшая дробь, P1–P3, план делителей, запуск, перестройка, I/Q и калибровка.

/* si18h.c — раздел 18ж: драйвер Si5351A. Только делители и регистры; шину даёт si_port_xfer() платформы. */
#include "si18h.h"

static int wr(const uint8_t *b, uint32_t n)
{
    return si_port_xfer(SI_ADDR, b, n, 0, 0);       /* b[0] — номер первого регистра, дальше подряд */
}

static int64_t div_round(int64_t n, int64_t d)      /* d > 0 */
{
    return n >= 0 ? (n + d / 2) / d : -((-n + d / 2) / d);
}

/* ── арифметика ───────────────────────────────────────────────────────── */
static uint64_t dist(uint64_t p, uint64_t q, uint64_t rem, uint64_t den)   /* |p/q − rem/den| · q · den */
{
    uint64_t x = p * den, y = rem * q;
    return x > y ? x - y : y - x;
}

si_frac_t si_ratio(uint64_t num, uint64_t den, int method)
{
    si_frac_t f;
    uint64_t rem = num % den, b;
    f.a = (uint32_t)(num / den);
    f.b = 0;
    f.c = 1;
    if (rem == 0)
        return f;
    if (method == SI_FIXED_C) {
        b = (rem * SI_C_MAX + den / 2u) / den;              /* c = 1048575, b — ближайшее целое */
        f.c = SI_C_MAX;
    } else {                                                /* лучшая дробь с c ≤ 1048575: цепная дробь */
        uint64_t p0 = 0, q0 = 1, p1 = 1, q1 = 0, n = rem, d = den;
        while (d) {
            uint64_t t = n / d, p2 = t * p1 + p0, q2 = t * q1 + q0, m;
            if (q2 > SI_C_MAX) {                            /* дальше нельзя: проверить промежуточную дробь */
                uint64_t k = (SI_C_MAX - q0) / q1, ps = p0 + k * p1, qs = q0 + k * q1;
                if (dist(ps, qs, rem, den) * q1 < dist(p1, q1, rem, den) * qs) {
                    p1 = ps;
                    q1 = qs;
                }
                break;
            }
            p0 = p1;
            q0 = q1;
            p1 = p2;
            q1 = q2;
            m = n - t * d;
            n = d;
            d = m;
        }
        b = p1;
        f.c = (uint32_t)q1;
    }
    if (b >= f.c) {                                         /* округлилось до целого */
        f.a++;
        b = 0;
    }
    if (b == 0)
        f.c = 1;
    f.b = (uint32_t)b;
    return f;
}

void si_params(si_frac_t f, si_p_t *p)
{
    uint32_t fl = (uint32_t)((128ull * f.b) / f.c);         /* floor(128·b / c) */
    p->p1 = 128u * f.a + fl - 512u;
    p->p2 = 128u * f.b - f.c * fl;
    p->p3 = f.c;
}

void si_pack(const si_p_t *p, uint32_t r, int divby4, uint8_t out[8])
{
    out[0] = (uint8_t)(p->p3 >> 8);
    out[1] = (uint8_t)p->p3;
    out[2] = (uint8_t)((r & 7u) << 4 | (divby4 ? SI_DIVBY4 : 0u) | ((p->p1 >> 16) & 3u));
    out[3] = (uint8_t)(p->p1 >> 8);
    out[4] = (uint8_t)p->p1;
    out[5] = (uint8_t)(((p->p3 >> 12) & 0xF0u) | ((p->p2 >> 16) & 0x0Fu));
    out[6] = (uint8_t)(p->p2 >> 8);
    out[7] = (uint8_t)p->p2;
}

uint64_t si_out_mhz(uint64_t xtal_mhz, si_frac_t pll, uint32_t d, uint32_t r)
{
    uint64_t num = xtal_mhz * ((uint64_t)pll.a * pll.c + pll.b);    /* VCO · c */
    uint64_t den = ((uint64_t)pll.c * d) << r;
    return (num + den / 2u) / den;
}

static int pll_ok(si_frac_t f)
{
    return f.a >= SI_PLL_A_MIN && (f.a < SI_PLL_A_MAX || (f.a == SI_PLL_A_MAX && f.b == 0));
}

int si_plan(const si_t *s, uint64_t f_mhz, si_plan_t *pl)
{
    uint64_t fo;
    if (f_mhz < SI_F_MIN || f_mhz > SI_F_MAX)
        return SI_E_RANGE;
    pl->r = 0;
    while ((f_mhz << pl->r) < SI_R_FLOOR && pl->r < 7u)     /* ниже 500 кГц — поднять частоту делителем R */
        pl->r++;
    fo = f_mhz << pl->r;
    if (fo > 150000000000ull)
        pl->d = 4;                                          /* выше 150 МГц — только деление на 4 */
    else
        pl->d = (uint32_t)(SI_VCO_MAX / fo) & ~1u;          /* чётный, VCO как можно ближе к 900 МГц */
    if (pl->d > SI_MS_MAX)
        pl->d = SI_MS_MAX;
    pl->vco_mhz = fo * pl->d;
    if (pl->vco_mhz < SI_VCO_MIN || pl->vco_mhz > SI_VCO_MAX)
        return SI_E_RANGE;
    pl->pll = si_ratio(pl->vco_mhz, s->xtal_mhz, s->method);
    if (!pll_ok(pl->pll))
        return SI_E_RANGE;
    pl->f_mhz = si_out_mhz(s->xtal_mhz, pl->pll, pl->d, pl->r);
    return SI_OK;
}

int si_plan_iq(const si_t *s, uint64_t f_mhz, si_plan_t *pl)
{
    uint64_t d;
    if (f_mhz < SI_F_MIN || f_mhz > SI_F_MAX)
        return SI_E_RANGE;
    d = (SI_VCO_MAX / f_mhz) & ~1ull;
    if (d > SI_IQ_D_MAX)
        d = SI_IQ_D_MAX;                                    /* сдвиг на 90° = d четвертей периода VCO, 7 бит */
    if (d < 6u)
        return SI_E_RANGE;
    pl->r = 0;
    pl->d = (uint32_t)d;
    pl->vco_mhz = f_mhz * d;
    if (pl->vco_mhz < SI_VCO_MIN)
        return SI_E_RANGE;                                  /* ниже 600 МГц / 126 = 4,762 МГц I/Q нет */
    pl->pll = si_ratio(pl->vco_mhz, s->xtal_mhz, s->method);
    if (!pll_ok(pl->pll))
        return SI_E_RANGE;
    pl->f_mhz = si_out_mhz(s->xtal_mhz, pl->pll, pl->d, 0);
    return SI_OK;
}

/* ── регистры ─────────────────────────────────────────────────────────── */
int si_init(si_t *s, uint32_t xtal_hz, uint8_t xtal_cl)
{
    uint8_t reg = SI_STATUS, st = SI_SYS_INIT;
    int rc;
    for (uint32_t i = 0; st & SI_SYS_INIT; i++) {           /* пока SYS_INIT = 1, микросхема не готова */
        if (i == 100u)
            return SI_E_TIMEOUT;
        if ((rc = si_port_xfer(SI_ADDR, &reg, 1, &st, 1)) != SI_OK)
            return rc;                                      /* нет ответа на 0x60: провод, питание, адрес */
        if (st & SI_SYS_INIT)
            si_port_delay_us(1000u);
    }
    s->xtal_mhz = (uint64_t)xtal_hz * 1000u;
    s->ppb = 0;
    s->method = SI_BEST;
    s->always_reset = 0;
    s->idrv = 3;                                            /* 8 мА */
    s->oe = 0xFF;
    s->resets = 0;
    for (int k = 0; k < 3; k++) {
        s->ctrl[k] = SI_CLK_PDN;
        s->d[k] = s->r[k] = 0;
    }
    s->fb[0] = s->fb[1] = 0xFF;                             /* ещё не писали */
    s->vco_mhz[0] = s->vco_mhz[1] = 0;
    {
        const uint8_t oe[2] = {SI_OE, 0xFF};                /* 1. все выходы выключить */
        const uint8_t pdn[4] = {SI_CLK_CTRL, SI_CLK_PDN, SI_CLK_PDN, SI_CLK_PDN};   /* 2. и обесточить */
        uint8_t cl[2] = {SI_XTAL_CL, xtal_cl};              /* 3. нагрузочная ёмкость кварца */
        const uint8_t src[2] = {SI_PLL_SRC, 0x00};          /* 4. PLLA и PLLB — от кварца */
        if ((rc = wr(oe, 2)) != SI_OK || (rc = wr(pdn, 4)) != SI_OK || (rc = wr(cl, 2)) != SI_OK)
            return rc;
        return wr(src, 2);
    }
}

static int write_pll(si_t *s, int pll, si_frac_t f)
{
    si_p_t p;
    uint8_t b[9];
    uint8_t fb = f.b ? 0u : SI_FB_INT;                      /* целый делитель PLL — FBx_INT = 1 */
    int rc;
    si_params(f, &p);
    b[0] = (uint8_t)(pll ? SI_MSNB : SI_MSNA);
    si_pack(&p, 0, 0, b + 1);
    if ((rc = wr(b, 9)) != SI_OK)                           /* все 8 байт — одной посылкой */
        return rc;
    if (s->fb[pll] != fb) {
        uint8_t w[2] = {(uint8_t)(SI_FB_CTRL + pll), fb};
        if ((rc = wr(w, 2)) != SI_OK)
            return rc;
        s->fb[pll] = fb;
    }
    return SI_OK;
}

static int write_ms(int clk, si_frac_t f, uint32_t r, int divby4)
{
    si_p_t p;
    uint8_t b[9];
    if (divby4) {
        p.p1 = p.p2 = 0;                                    /* деление на 4: P1 = P2 = 0, P3 = 1 */
        p.p3 = 1;
    } else {
        si_params(f, &p);
    }
    b[0] = (uint8_t)(SI_MS0 + 8u * (uint32_t)clk);
    si_pack(&p, r, divby4, b + 1);
    return wr(b, 9);
}

static int write_ctrl(si_t *s, int clk, uint8_t v)
{
    uint8_t w[2] = {(uint8_t)(SI_CLK_CTRL + clk), v};
    s->ctrl[clk] = v;
    return wr(w, 2);
}

int si_pll_reset(si_t *s, int pll)
{
    uint8_t w[2] = {SI_PLL_RST, pll ? SI_PLLB_RST : SI_PLLA_RST};
    s->resets++;
    return wr(w, 2);
}

int si_output(si_t *s, int clk, int on)
{
    uint8_t w[2];
    if (clk < 0 || clk > 2)
        return SI_E_ARG;
    s->oe = (uint8_t)(on ? s->oe & ~(1u << clk) : s->oe | (1u << clk));
    w[0] = SI_OE;
    w[1] = s->oe;
    return wr(w, 2);
}

int si_invert(si_t *s, int clk, int inv)
{
    if (clk < 0 || clk > 2)
        return SI_E_ARG;
    return write_ctrl(s, clk, (uint8_t)(inv ? s->ctrl[clk] | SI_CLK_INV : s->ctrl[clk] & ~SI_CLK_INV));
}

int si_set_freq(si_t *s, int clk, int pll, uint64_t f_mhz)
{
    si_plan_t pl;
    int rc, changed;
    uint8_t ctrl;
    if (clk < 0 || clk > 2 || pll < 0 || pll > 1)
        return SI_E_ARG;
    if ((rc = si_plan(s, f_mhz, &pl)) != SI_OK)
        return rc;
    ctrl = (uint8_t)(SI_CLK_INT | (pll ? SI_CLK_PLLB : 0u) | SI_CLK_SRC_MS | s->idrv | (s->ctrl[clk] & SI_CLK_INV));
    changed = s->d[clk] != pl.d || s->r[clk] != pl.r || (s->ctrl[clk] & ~SI_CLK_INV) != (ctrl & ~SI_CLK_INV);
    if ((rc = write_pll(s, pll, pl.pll)) != SI_OK)          /* перестройка внутри одного делителя — только PLL */
        return rc;
    s->vco_mhz[pll] = pl.vco_mhz;
    if (changed) {
        si_frac_t ms = {pl.d, 0, 1};
        if ((rc = write_ms(clk, ms, pl.r, pl.d == 4u)) != SI_OK || (rc = write_ctrl(s, clk, ctrl)) != SI_OK)
            return rc;
        s->d[clk] = pl.d;
        s->r[clk] = pl.r;
    }
    if ((changed || s->always_reset) && (rc = si_pll_reset(s, pll)) != SI_OK)   /* новый делитель — сброс PLL */
        return rc;
    return (s->oe & (1u << clk)) ? si_output(s, clk, 1) : SI_OK;
}

int si_set_freq_shared(si_t *s, int clk, int pll, uint64_t f_mhz)
{
    uint64_t fo, vco = s->vco_mhz[pll < 0 || pll > 1 ? 0 : pll];
    uint32_t r = 0;
    si_frac_t ms;
    uint8_t ctrl;
    int rc;
    if (clk < 0 || clk > 2 || pll < 0 || pll > 1 || vco == 0)
        return SI_E_ARG;                                    /* сначала частоту получает хозяин PLL */
    if (f_mhz < SI_F_MIN || f_mhz > SI_F_MAX)
        return SI_E_RANGE;
    while ((f_mhz << r) < SI_R_FLOOR && r < 7u)
        r++;
    fo = f_mhz << r;
    ms = si_ratio(vco, fo, s->method);                      /* делитель выхода дробный: a + b/c */
    if (ms.a < 8u || ms.a > SI_MS_MAX || (ms.a == SI_MS_MAX && ms.b))
        return SI_E_RANGE;
    ctrl = (uint8_t)((ms.b ? 0u : SI_CLK_INT) | (pll ? SI_CLK_PLLB : 0u) | SI_CLK_SRC_MS | s->idrv | (s->ctrl[clk] & SI_CLK_INV));
    if ((rc = write_ms(clk, ms, r, 0)) != SI_OK || (rc = write_ctrl(s, clk, ctrl)) != SI_OK)
        return rc;
    s->d[clk] = 0;                                          /* не чётный делитель: следующий si_set_freq сбросит PLL */
    s->r[clk] = r;
    return (s->oe & (1u << clk)) ? si_output(s, clk, 1) : SI_OK;   /* PLL не сбрасываем: на ней чужой выход */
}

int si_quadrature(si_t *s, uint64_t f_mhz)
{
    si_plan_t pl;
    si_frac_t ms;
    uint8_t ctrl[3], ph[3];
    int rc;
    if ((rc = si_plan_iq(s, f_mhz, &pl)) != SI_OK)
        return rc;
    if ((rc = write_pll(s, 0, pl.pll)) != SI_OK)
        return rc;
    s->vco_mhz[0] = pl.vco_mhz;
    ms.a = pl.d;
    ms.b = 0;
    ms.c = 1;
    if ((rc = write_ms(0, ms, 0, 0)) != SI_OK || (rc = write_ms(1, ms, 0, 0)) != SI_OK)
        return rc;
    ctrl[0] = SI_CLK_CTRL;
    ctrl[1] = ctrl[2] = (uint8_t)(SI_CLK_INT | SI_CLK_SRC_MS | s->idrv);   /* оба от PLLA, делитель целый */
    ph[0] = SI_PHOFF0;
    ph[1] = 0;
    ph[2] = (uint8_t)pl.d;                                  /* CLK1 позже на d · T_VCO / 4 = четверть периода */
    if ((rc = wr(ctrl, 3)) != SI_OK || (rc = wr(ph, 3)) != SI_OK)
        return rc;
    s->ctrl[0] = s->ctrl[1] = ctrl[1];
    for (int k = 0; k < 2; k++) {
        s->d[k] = pl.d;
        s->r[k] = 0;
    }
    if ((rc = si_pll_reset(s, 0)) != SI_OK)                 /* без сброса фазы CLK0 и CLK1 не связаны */
        return rc;
    s->oe = (uint8_t)(s->oe & ~3u);
    {
        uint8_t w[2] = {SI_OE, s->oe};
        return wr(w, 2);
    }
}

void si_calibrate(si_t *s, uint64_t f_set_mhz, uint64_t f_meas_mhz)
{
    int64_t e = (int64_t)f_meas_mhz - (int64_t)f_set_mhz;
    int64_t ppb = div_round(e * 1000000000, (int64_t)f_set_mhz);   /* уход кварца в миллиардных долях */
    s->xtal_mhz = (uint64_t)((int64_t)s->xtal_mhz + div_round((int64_t)s->xtal_mhz * ppb, 1000000000));
    s->ppb += (int32_t)ppb;
}

fw18h.c — прошивка F103: I2C1 на 100 кГц, квадратура на CLK0/CLK1, 10 МГц на CLK2, калибровка через отладчик.

/* fw18h.c — раздел 18ж: генератор на Si5351A, STM32F103 (BluePill), 72 МГц, APB1 = 36 МГц.
 * I2C1: PB6 — SCL, PB7 — SDA (открытый сток, 100 кГц, подтяжки на модуле или 4,7 кОм к 3,3 В). При запуске —
 * ожидание SYS_INIT, выходы выключены, кварц 25 МГц с ёмкостью 10 пФ. Затем: CLK0 и CLK1 — квадратура I/Q на
 * tune_hz (PLLA, CLK1 позже на 90°), CLK2 — 10 МГц от PLLB. Через отладчик: tune_hz — новая частота I/Q;
 * cal_set_hz и cal_meas_hz — что заказали на CLK2 и что показал эталонный частотомер (после записи обоих — поправка
 * в ppb и перестройка). Тактирование до 72 МГц — как в разделе 6. */
#include "stm32f10x.h"
#include "si18h.h"

#define CPU_HZ          72000000u
#define APB1_MHZ        36u
#define I2C_TIMEOUT_US  2000u           /* байт на 100 кГц — 90 мкс */
#define XTAL_HZ         25000000u

static si_t si;
volatile uint32_t tune_hz = 7074000u, cal_set_hz, cal_meas_hz, bus_errors;
volatile int32_t si_rc = 1, cal_ppb;

static int expired(uint32_t t0, uint32_t us)
{
    return DWT->CYCCNT - t0 >= us * (CPU_HZ / 1000000u);
}

void si_port_delay_us(uint32_t us)
{
    uint32_t t0 = DWT->CYCCNT;
    while (!expired(t0, us)) {
    }
}

static void gpio_i2c1_init(void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN | RCC_APB2ENR_AFIOEN;
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
    /* PB6, PB7 — альтернативная функция, открытый сток, 2 МГц (1110) */
    GPIOB->CRL = (GPIOB->CRL & 0x00FFFFFFu) | 0xEE000000u;
    I2C1->CR1 = 0;                                      /* настраивать при PE = 0 */
    I2C1->CR2 = APB1_MHZ;
    I2C1->CCR = APB1_MHZ * 1000000u / (2u * 100000u);   /* стандартный режим: 36 МГц / (2 × 180) = 100 кГц */
    I2C1->TRISE = APB1_MHZ + 1u;                        /* 1000 нс × 36 МГц = 36 → 37 */
    I2C1->CR1 = I2C_CR1_PE;
}

static int sr1_wait(uint16_t mask)
{
    uint32_t t0 = DWT->CYCCNT;
    while (!(I2C1->SR1 & mask)) {
        if (I2C1->SR1 & I2C_SR1_AF)
            return SI_E_BUS;                            /* адрес или байт не подтверждён */
        if (expired(t0, I2C_TIMEOUT_US))
            return SI_E_BUS;
    }
    return SI_OK;
}

static int fail(int rc)
{
    I2C1->CR1 |= I2C_CR1_STOP;
    I2C1->SR1 = (uint16_t)~I2C_SR1_AF;                  /* AF снимается записью нуля */
    I2C1->CR1 |= I2C_CR1_ACK;
    bus_errors++;
    return rc;
}

int si_port_xfer(uint8_t addr7, const uint8_t *tx, uint32_t ntx, uint8_t *rx, uint32_t nrx)
{
    int rc;
    uint32_t t0 = DWT->CYCCNT;
    if (nrx > 1u || (nrx != 0u && ntx == 0u))
        return SI_E_ARG;                                /* драйверу хватает одного байта статуса */
    while (I2C1->SR2 & I2C_SR2_BUSY) {                  /* SDA держит ведомый — раздел 17.6 */
        if (expired(t0, I2C_TIMEOUT_US))
            return SI_E_BUS;
    }
    I2C1->CR1 |= I2C_CR1_START;
    if ((rc = sr1_wait(I2C_SR1_SB)) != SI_OK)
        return fail(rc);
    I2C1->DR = (uint8_t)(addr7 << 1);
    if ((rc = sr1_wait(I2C_SR1_ADDR)) != SI_OK)
        return fail(rc);
    (void)I2C1->SR2;
    for (uint32_t i = 0; i < ntx; i++) {                /* номер регистра, затем данные: адрес растёт сам */
        if ((rc = sr1_wait(I2C_SR1_TXE)) != SI_OK)
            return fail(rc);
        I2C1->DR = tx[i];
    }
    if ((rc = sr1_wait(I2C_SR1_BTF)) != SI_OK)
        return fail(rc);
    if (!nrx) {
        I2C1->CR1 |= I2C_CR1_STOP;
        return SI_OK;
    }
    I2C1->CR1 |= I2C_CR1_START;                         /* повторный старт: номер регистра уже передан */
    if ((rc = sr1_wait(I2C_SR1_SB)) != SI_OK)
        return fail(rc);
    I2C1->DR = (uint8_t)(addr7 << 1 | 1u);
    if ((rc = sr1_wait(I2C_SR1_ADDR)) != SI_OK)
        return fail(rc);
    I2C1->CR1 &= (uint16_t)~I2C_CR1_ACK;                /* один байт — RM0008, с. 764: NACK до снятия ADDR */
    (void)I2C1->SR2;
    I2C1->CR1 |= I2C_CR1_STOP;
    if ((rc = sr1_wait(I2C_SR1_RXNE)) != SI_OK)
        return fail(rc);
    rx[0] = (uint8_t)I2C1->DR;
    I2C1->CR1 |= I2C_CR1_ACK;
    return SI_OK;
}

static int start_outputs(uint32_t iq_hz)
{
    int rc;
    if ((rc = si_quadrature(&si, (uint64_t)iq_hz * 1000u)) != SI_OK)
        return rc;                                      /* ниже 4,762 МГц I/Q нет */
    return si_set_freq(&si, 2, 1, 10000000000ull);      /* CLK2 — 10 МГц от PLLB */
}

int main(void)
{
    uint32_t cur = 0;
    CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    DWT->CYCCNT = 0;
    DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
    gpio_i2c1_init();
    while ((si_rc = si_init(&si, XTAL_HZ, SI_CL_10PF)) != SI_OK)
        si_port_delay_us(100000u);                      /* нет ответа на 0x60: провод, питание */
    for (;;) {
        if (cal_set_hz && cal_meas_hz) {                /* эталон: что заказали и что намерили */
            si_calibrate(&si, (uint64_t)cal_set_hz * 1000u, (uint64_t)cal_meas_hz * 1000u);
            cal_ppb = si.ppb;
            cal_set_hz = cal_meas_hz = 0;
            cur = 0;                                    /* пересчитать все делители с новым кварцем */
        }
        if (tune_hz != cur) {
            cur = tune_hz;
            si_rc = start_outputs(cur);
        }
        si_port_delay_us(10000u);
    }
}

Дальше: раздел 19 «Шина CAN» — интерфейс для связи между платами и узлами.

Открыть в интерактивном справочнике