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.
192 lines
15 KiB
Markdown
192 lines
15 KiB
Markdown
# Контроллер выходного ключа
|
||
|
||
Прошивка 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
|
||
```
|