# Контроллер выходного ключа Прошивка STM32, которая переключает выходной СВЧ-ключ внутри частотной точки — вторая половина режима, описанного в [`../per_point_switching.md`](../per_point_switching.md). Плата и тулчейн те же, что у пульта тележки: WeAct STM32G431CB, PlatformIO. ## Подключение ```text мастер 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`](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: ```text 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` блокирует зеркалирование, а пропущенные фронты сбили бы чётность. Ключевые строки: ```text parity corrections : 0 (ok) <- в норме всегда 0 stages per point : 4 (ожидается 4), отклонений: 0 inter-stage gap, us : 62 boundary above, us : 93 ``` `stages per point` — самопроверка распознавания границ: если оно верное, между двумя границами всегда ровно 4 стадии. Любое другое число означает, что порог подобран неправильно. ## Сборка и прошивка ```bash cd device_firmware/switch_controller pio run # сборка pio run -t upload # прошить через ST-Link pio device monitor # отчёт по USB CDC ```