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 миллионные и миллиардные доли
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 драйвер считает так:
- ниже 500 кГц — включить R: удваивать частоту, пока не станет не меньше 500 кГц (R до 128);
- выше 150 МГц — делитель 4; иначе — чётный целый делитель, при котором VCO как можно ближе к 900 МГц сверху вниз;
- дробную часть отдать 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°. Что ломает квадратуру в модели:
- запись в делитель выхода — те же байты MS1 ещё раз: выход перезапустился с другой фазой, 243,32°; сброс PLLA вернул 90,00°;
- дробный делитель — от PLLA 900 МГц на 7,074 МГц выходит делитель 127,226463; сдвиг целый, 127 — это 89,84°;
- инверсия — бит INV на CLK1 добавляет 180°: 270,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 Итоги
- Si5351A: адрес 0x60, кварц 25 МГц → PLLA/PLLB × (a + b/c), 600…900 МГц → делитель выхода 4, 6, 8…2048 → R 1…128 → CLK0–CLK2.
- Запуск: SYS_INIT = 0,
03 FF,10 80 80 80,B7 D2,0F 00— 7 посылок, 2510 мкс. - P1 = 128·a + floor(128·b/c) − 512, P2 = 128·b − c·floor(128·b/c), P3 = c: 35 + 21/25 → 4075, 13, 25.
- Ниже 500 кГц — R; делитель выхода — чётный целый, дробная — PLL; 7,074 МГц: d = 126, VCO 891,324 МГц, 35 + 4081/6250.
- c = 1048575: шаг 23,8419 Гц на VCO, ошибка до 1,475 Гц (16 ppb); лучшая дробь — до 0,954 мГц.
- Перестройка 7,0…7,2 МГц: 2 сброса PLL вместо 201, 920 мкс на шаг.
- I/Q: сдвиг CLK1 = d, сброс PLL — 90,00°; от 4,762 МГц; дробный делитель — 89,84°.
- Калибровка: +17,48 ppm → поправка 17 500 ppb, остаток −0,2 Гц на 10 МГц.
Полные исходники
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, ®, 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» — интерфейс для связи между платами и узлами.