PB2 now drives the switch on master port 1 and PB6 the one on port 2, both updated in a single BSRR write so they move in the same cycle. The CDC report gained a gap histogram, which showed the point boundary sits at 70-79 us against 62 us between stages, so the resync threshold only fires on the longer inter-sweep pauses; the misleading "stages per point" counter is renamed to match what it actually counts.
Контроллер выходного ключа
Прошивка STM32, которая переключает выходной СВЧ-ключ внутри частотной точки —
вторая половина режима, описанного в ../per_point_switching.md.
Плата и тулчейн те же, что у пульта тележки: WeAct STM32G431CB, PlatformIO.
Подключение
мастер TRIGGER_OUT ──► PB0 [STM32] PB1 ──► слейв 1 TRIGGER_IN
PB2 ──► ключ выходного порта 1
PB6 ──► ключ выходного порта 2
PB5 ──► отладочный выход
Контроллер встаёт в разрыв линии между мастером и первым слейвом, остальная
часть кольца (слейв 1 → слейв 2 → мастер) не трогается. Это безопасно: в режиме
Mode::Trigger прошивка LibreVNA уводит собственный STM32 прибора в Hi-Z на обеих
триггерных линиях, так что и вход слейва, и выход мастера свободны.
У каждого ключа своя нога: PB2 — ключ выходного порта 1, PB6 — ключ порта 2. В штатном режиме оба идут в одно и то же положение, потому что положение зависит только от номера стадии, а не от того, какой порт сейчас излучает. Раздельные линии нужны по двум причинам: не вешать два входа параллельно на один вывод и иметь возможность развести ключи независимо.
Оба вывода лежат на порту B, и это существенно: положение меняется одной
записью в BSRR (младшая половина слова ставит биты, старшая сбрасывает),
поэтому ноги переключаются в одном такте, без взаимного сдвига. Если менять
распиновку, оставляйте оба вывода на одном порту.
Если у конкретного ключа положению A соответствует логическая единица, инверсию
можно включить отдельно для каждого — константы kSwitchPort1ActiveLow и
kSwitchPort2ActiveLow. Если у ключа больше двух состояний, лишние входы
замыкаются джампером.
PB6 выбран как свободный вывод порта B без отладочной функции. PB3 (SWO) и PB4 (NJTRST) занимать не стоит — они мешают отладчику.
Прошивка рассчитана ровно на два положения ключа (
stimulus_switch_positions: 2на хосте). Хост технически допускает и 4 положения, но тогда положение перестаёт быть простым чередованием одного бита и потребуется контроллер на две управляющие линии — эта прошивка для такого режима не годится.
Если положению A соответствует лог. 1, а не 0 — поменяйте kSwitchActiveLow
в src/main.cpp.
Как это работает
Прибор измеряет каждую частоту в 4 стадии: порт 1 в положениях A и B ключа,
затем порт 2 в тех же положениях. Положение ключа — это младший бит номера
стадии, а период 2 нацело делит 4 стадии, поэтому нужная последовательность —
непрерывное чередование A B A B A B …, не прерывающееся на границе точки.
Контроллеру достаточно инвертировать ногу на каждый цикл триггера;
абсолютный счётчик стадий не нужен.
Переключение делается по спаду триггера. Спад означает «оцифровка стадии
закончилась»; сразу за ним прибор уходит в состояние успокоения следующей
стадии, и это окно (dwell_us плюс собственная задержка PLL прибора, ~62 мкс
при dwell_us = 2) целиком покрывает установление ключа. Переключение по фронту
заставило бы удерживать кольцо на время установления и удлинило бы каждую стадию.
Восстановление чётности
Чередование самодостаточно, но один пропущенный или лишний фронт инвертирует чётность навсегда и молча меняет антенны местами. Поэтому контроллер дополнительно распознаёт границу точки по длине паузы:
| Пауза | Длительность | Из чего состоит |
|---|---|---|
| между стадиями | базовая | только время успокоения |
| между точками | базовая + | перезагрузка регистров PLL мастера |
| между развёртками | базовая + + | ещё и работа хоста между свипами |
Пауза длиннее межстадийной означает, что следующий триггер начнёт стадию 0, то есть положение A. Контроллер выставляет A принудительно, независимо от накопленной чётности — то есть чинит рассинхрон на каждой точке, а не один раз на развёртку.
Порог подстраивается сам: межстадийная пауза — это минимальная регулярно
повторяющаяся пауза, поэтому за порог берётся минимум по скользящему окну,
умноженный на kBoundaryFactorNum / kBoundaryFactorDen (по умолчанию 1.5). Пока
минимум не набран, восстановление молчит — чередование корректно и без него.
Измерено на железе 2026-09-11: коэффициент 1.5 мал
Первый прогон с замкнутой петлёй (мастер + 2 слейва, dwell_us = 2, IFBW 50 кГц,
201 точка, 4 стадии на точку) дал по CDC:
trigger cycles : 6432
point boundaries : 96 <- ожидалось бы 6432 / 4 = 1608
parity corrections : 0 (ok)
stages per point : 124 (ожидается 4), отклонений: 80
gap min/max, us : 62 / 14364445
inter-stage gap, us : 62
boundary above, us : 94
Межстадийная пауза 62 мкс — точно как считалось (dwell 2 + задержка PLL 60).
А границы точек распознаются примерно раз в 67 стадий вместо каждых четырёх.
Значит пауза на границе точки обычно не дотягивает до 1.5×, то есть до 94 мкс:
перезагрузка регистров PLL и ожидание захвата добавляют к успокоению меньше
половины от него.
На работу это не влияет, и parity corrections : 0 тому подтверждение.
Переключение идёт на каждом спаде безусловно, а стадий на точку чётное число (4),
поэтому чередование A B A B само собой возвращается к A на новой точке.
Распознавание границы — только страховка от уже случившегося сбоя чётности.
Но страховка сейчас почти не работает: пропущенный фронт исправится не через
четыре стадии, а через несколько десятков. И диагностика stages per point
бесполезна. Чтобы починить, нужно измерить осциллографом фактическое
распределение пауз на PB0 и выбрать коэффициент по нему, а не по догадке. До тех
пор значения stages per point и point boundaries в отчёте читать нельзя.
Надо ли перекалибровывать паузу под каждую настройку
Нет, порог подстраивается сам, и вот что на него влияет, а что нет.
IFBW не влияет. Он меняет длительность оцифровки, то есть интервал от фронта до спада, а контроллер меряет паузу от спада до следующего фронта. Это разные интервалы. Плюс переключение делается по спаду, когда оцифровка уже закончилась, а детектор границы во время оцифровки вообще неактивен — так что сдвинуть ключ посреди измерения он не может ни при каком IFBW.
Число точек, полоса, мощность не влияют — паузы от них не зависят.
Влияют dwell_us и задержка PLL прибора: они и есть время успокоения, то
есть сама пауза. Но подстройка на это и рассчитана — окно минимума короткое (16
отсчётов), поэтому новое значение подхватывается за считанные точки. Ручной
калибровки не требуется.
Смена диапазона внутри развёртки безопасна. На точках нижнего диапазона прибор останавливает развёртку и работает с Si5351 по I2C — такие паузы намного длиннее обычных. Логика от этого не ломается: остановка всегда происходит на границе точки, перед стадией 0, а значит «длинная пауза → следующая стадия нулевая → положение A» остаётся верным.
Множитель намеренно щедрый. Ложное срабатывание посреди точки испортит
данные, а несрабатывание всего лишь отключит страховку. Если по отчёту видно,
что границы не распознаются вообще (point boundaries близко к нулю при
ненулевом trigger cycles), множитель можно снижать — но не ниже реального
отношения межточечной паузы к межстадийной, которое видно там же в отчёте.
Задержка
Зеркалирование занимает ~50 нс и на измерения не влияет: мастер стартует
оцифровку по своему TRIGGER_IN, который приходит с конца кольца, то есть тоже
после контроллера. Задержка общая для всех трёх приборов, взаимные сдвиги
мастер/слейвы задаются только межFPGA-передачей, и в отношении
«приёмник/опорник» она сокращается. Единственный эффект — развёртка длиннее на
~2 × задержку × число стадий.
Диагностика
PB5 — импульс 2 мкс каждый раз, когда чётность пришлось чинить. В исправной системе эта нога молчит: предсказание чередованием уже даёт нужное положение на границе точки. Постоянные импульсы означают, что фронты теряются или порог распознавания границы занижен.
PB2 и PB6 — положение ключей, смотрятся осциллографом вместе с триггером: на
одной частоте должно быть ровно 4 стадии и два полных периода A B A B. Оба
вывода обязаны меняться одновременно и в одну сторону — расхождение между ними
означает ошибку в распиновке или в константах инверсии.
USB CDC (115200) — отчёт печатается один раз, когда на линии триггера
тишина дольше 200 мс, то есть когда развёртка остановлена. Во время работы
печати нет: Serial блокирует зеркалирование, а пропущенные фронты сбили бы
чётность. Ключевые строки:
parity corrections : 0 (ok) <- в норме всегда 0
stages per point : 4 (ожидается 4), отклонений: 0
inter-stage gap, us : 62
boundary above, us : 93
stages per point — самопроверка распознавания границ: если оно верное, между
двумя границами всегда ровно 4 стадии. Любое другое число означает, что порог
подобран неправильно.
Сборка и прошивка
cd device_firmware/switch_controller
pio run # сборка
pio run -t upload # прошить через ST-Link
pio device monitor # отчёт по USB CDC