added docs
This commit is contained in:
@@ -1,101 +1,110 @@
|
||||
# RadioPhotonic radar (RPH) collector
|
||||
# Коллектор радиофотонного радара (RPH)
|
||||
|
||||
Application-owned acquisition binary for the **L-Card E-502** ADC. It captures the
|
||||
dual-channel main + reference IQ stream and publishes complete `RPH1` sweeps over
|
||||
a PTY. `RphRadarService` in `python_app/hardware_full/rph_radar` reads and
|
||||
processes that stream for `python_app.scripts.rph_radar_raw_producer`.
|
||||
Программа сбора для АЦП L-Card E-502, которой владеет приложение. Она принимает
|
||||
двухканальный поток IQ (основной и опорный сигналы) и передаёт целые развёртки в
|
||||
пакетах `RPH1` через псевдотерминал. Поток читает и обрабатывает `RphRadarService` из
|
||||
`python_app/hardware_full/rph_radar` для процесса сбора
|
||||
`python_app.scripts.rph_radar_raw_producer`.
|
||||
|
||||
This is the data *source* for `radar.model == "rph_radar"` with
|
||||
`radar.rph_radar.source: "e502"`, paired with the Python `rph_radar_raw_producer`.
|
||||
The `rfg_udp` source reads the RFG board's UDP stream directly and does not use this
|
||||
binary. LibreVNA acquisition uses Python drivers.
|
||||
Это источник данных для `radar.model: "rph_radar"` с `radar.rph_radar.source: "e502"`.
|
||||
Источник `rfg_udp` читает UDP-поток платы RFG напрямую и эту программу не использует;
|
||||
LibreVNA работает через драйверы на Python. Как радар устроен целиком — в главе
|
||||
[Оборудование](../../docs/hardware.md).
|
||||
|
||||
## Layout
|
||||
## Состав
|
||||
|
||||
| Path | Contents |
|
||||
|------|----------|
|
||||
| `src/` | Collector implementation (`main.cpp`) and the PTY writer. |
|
||||
| `include/` | PTY header and `switch_sequencer.h`. |
|
||||
| `vendor/lcard/` | L-Card X502/E502 SDK headers (`x502api.h`, `e502api.h`, `lcard_pstdint.h`), vendored verbatim so the collector builds with no external include path. |
|
||||
| Путь | Что |
|
||||
| --- | --- |
|
||||
| `src/` | коллектор (`main.cpp`) и запись в псевдотерминал |
|
||||
| `include/` | заголовок псевдотерминала и `switch_sequencer.h` |
|
||||
| `vendor/lcard/` | заголовки SDK L-Card X502/E502 (`x502api.h`, `e502api.h`, `lcard_pstdint.h`) без изменений, чтобы коллектор собирался без внешних путей |
|
||||
|
||||
In switch-aware mode, `SwitchSequencer` drives the H7992 switches at actual
|
||||
DI_SYN2 sweep ends. Duration limits and Stop never advance the switches. Every
|
||||
physical combination change discards the remaining decoded ADC/DIN block and
|
||||
recreates the source session after `max(1 ms, run.settling_ms)` has elapsed.
|
||||
Timestamps never decide whether a sweep belongs to the new combination. The
|
||||
collector completes a cycle only after a full, clean sweep for each combination.
|
||||
An unchanged combination keeps the existing session.
|
||||
Коллектор использует общий драйвер ВЧ-ключей H7992 со своим набором заголовков и
|
||||
каталогом объектов `build/rph/`.
|
||||
|
||||
A restart stops cyclic DOUT with `WAIT_DONE`, stops and disables the streams,
|
||||
restores DO2 high / DO1 and DO8 low, then closes and frees the SDK descriptor.
|
||||
After settling, a new descriptor is created, opened and configured. The collector
|
||||
reloads the DO1/DO8 pattern, waits for `OutCycleSetup(WAIT_DONE)`, and starts
|
||||
ADC/DIN/DOUT together. Pending samples and phase accumulators are cleared;
|
||||
source sweep and cycle IDs remain continuous. The initial high DI_SYN2 portion
|
||||
is dirty: acquisition waits for a complete rising-to-falling sweep. This can
|
||||
skip a variable number of physical sweeps while reopening; no fixed skip count
|
||||
or firmware-pause estimate is used. Hardware validation must measure reopen
|
||||
latency, USB resource release and multi-combination throughput.
|
||||
## Переключение пар
|
||||
|
||||
The application uses the internal ADC clock. ADC and DIN start together,
|
||||
continuously; DI_SYN2 gates sweeps in software. Source times are the monotonic
|
||||
clock immediately before `StreamsStart` plus the ADC sample index divided by the
|
||||
configured sample rate. These are source-clock estimates, not hardware timestamps;
|
||||
startup latency shifts the initial epoch earlier. USB and PTY queueing do not
|
||||
shift timestamps to the time Python reads a sweep. An estimated source timestamp
|
||||
ahead of receipt stops acquisition instead of publishing a future capture window.
|
||||
ADC/host drift can trigger this guard during long single-combination runs; its
|
||||
bound and the accuracy of capture windows need measurement on hardware. Drift
|
||||
cannot make data from an old SDK session clean after a physical switch.
|
||||
Если ключами управляет коллектор, `SwitchSequencer` переключает ключи H7992 по
|
||||
настоящим концам развёрток (спаду DI_SYN2). Ограничение длительности и остановка
|
||||
ключи не переключают. При каждой смене пары коллектор отбрасывает оставшийся
|
||||
декодированный блок АЦП и цифрового входа и после `max(1 ms, run.settling_ms)`
|
||||
создаёт сеанс источника заново. Принадлежность развёртки новой паре по времени не
|
||||
определяется. Цикл считается законченным, только когда для каждой пары есть целая
|
||||
чистая развёртка. Если пара не меняется, сеанс сохраняется.
|
||||
|
||||
The collector uses the common H7992 driver, with its own include set and
|
||||
`build/rph/` object tree.
|
||||
Перезапуск сеанса: остановка циклического вывода DOUT с `WAIT_DONE`, остановка и
|
||||
отключение потоков, возврат DO2 в высокий уровень, DO1 и DO8 — в низкий, закрытие и
|
||||
освобождение
|
||||
дескриптора SDK. После паузы установления создаётся, открывается и настраивается новый
|
||||
дескриптор, загружается шаблон DO1/DO8, коллектор ждёт `OutCycleSetup(WAIT_DONE)` и
|
||||
запускает АЦП, цифровой вход и вывод вместе. Накопленные отсчёты и фазовые
|
||||
накопители сбрасываются; номера развёрток и циклов идут без разрыва. Начальный
|
||||
высокий участок DI_SYN2 считается испорченным: сбор ждёт целую развёртку от фронта до
|
||||
спада. Поэтому при переоткрытии пропускается переменное число развёрток; постоянного
|
||||
числа пропусков или оценки паузы прошивки нет. Время переоткрытия, освобождение
|
||||
ресурсов USB и скорость при многих парах нужно измерить на железе.
|
||||
|
||||
## PTY format
|
||||
## Время съёма
|
||||
|
||||
Each packet is a 48-byte little-endian header (`<4sIQQQQHHI`) followed by the
|
||||
payload: `RPH1`, payload bytes (u32), source sweep ID, cycle ID, start/end ns
|
||||
(four u64), input/output positions (two u16), flags (u32; bit 0 means dirty).
|
||||
Without collector switching both positions are `0xffff`, and cycle equals sweep
|
||||
ID. Payload points remain four u16 words: marker (`0x000a` main, `0x00a8`
|
||||
reference), step index, signed I, signed Q. There are no boundary or combo frames.
|
||||
The queue drops complete sweeps on overflow; a packet already being written is
|
||||
never truncated. Maximum payload is 1,048,544 bytes. Python matches complete
|
||||
cycles. Python-controlled switching closes the previous collector and PTY reader,
|
||||
then starts a new collector after settling; phase-processing state is preserved.
|
||||
Коллектор работает от внутренних часов АЦП. АЦП и цифровой вход запускаются вместе и
|
||||
работают непрерывно; развёртки выделяет программа по линии DI_SYN2. Время отсчёта —
|
||||
монотонные часы компьютера непосредственно перед `StreamsStart` плюс номер отсчёта,
|
||||
делённый на частоту выборки. Это оценка по часам источника, а не аппаратная метка:
|
||||
задержка запуска сдвигает начало отсчёта раньше. Очереди USB и псевдотерминала время
|
||||
не сдвигают. Если оценка времени отсчёта оказалась позже момента его приёма, сбор
|
||||
останавливается, чтобы не публиковать окно съёма из будущего. Расхождение часов АЦП и
|
||||
компьютера может сработать на этой проверке при долгой работе на одной паре; её
|
||||
предел и точность окон съёма нужно измерить на железе. Данные старого сеанса SDK
|
||||
после переключения ключа чистыми не становятся ни при каком расхождении часов.
|
||||
|
||||
## Build
|
||||
## Формат псевдотерминала
|
||||
|
||||
Пакет — 48-байтный заголовок в порядке байтов little-endian (`<4sIQQQQHHI`) и данные:
|
||||
`RPH1`, длина данных (u32), номер развёртки источника, номер цикла, начало и конец
|
||||
съёма в нс (четыре u64), положения входа и выхода (два u16), флаги (u32; бит 0 —
|
||||
развёртка испорчена). Без переключения коллектором оба положения равны `0xffff`, а
|
||||
номер цикла совпадает с номером развёртки. Точка данных — четыре слова u16: метка
|
||||
(`0x000a` основной сигнал, `0x00a8` опорный), номер ступени, I и Q со знаком. Кадров
|
||||
границ и пар нет. При переполнении очередь выбрасывает целые развёртки; пакет, который
|
||||
уже пишется, не обрезается. Наибольший размер данных — 1 048 544 байта. Целые циклы
|
||||
собирает Python. При переключении ключей из Python прежний коллектор и чтение
|
||||
псевдотерминала закрываются, а после паузы установления запускается новый коллектор;
|
||||
состояние фазовой обработки сохраняется.
|
||||
|
||||
## Сборка
|
||||
|
||||
```bash
|
||||
make rph_radar_collector # -> build/bin/rph_radar_collector
|
||||
```
|
||||
|
||||
It is **not** part of `make all`; `start.sh` builds it automatically when the
|
||||
active config selects `radar.model == "rph_radar"`, unless `--skip-build` is set.
|
||||
The Linux build uses the repository's common sources and vendored headers and
|
||||
links with `-pthread -ldl -lutil`; no installed L-Card SDK is needed to build it.
|
||||
В `make all` коллектор не входит; `start.sh` собирает его сам, если в профиле
|
||||
`radar.model: "rph_radar"`, кроме запуска с `--skip-build`. Сборка в Linux берёт общие
|
||||
исходники и заголовки из репозитория и линкуется с `-pthread -ldl -lutil`;
|
||||
установленный SDK L-Card для сборки не нужен.
|
||||
|
||||
## Runtime
|
||||
## Работа
|
||||
|
||||
The collector loads the proprietary `libx502api.so` / `libe502api.so` at run time
|
||||
via `dlopen`, so those libraries must be discoverable by the dynamic loader.
|
||||
The RPH radar service prepends `~/.local/lib` (their default install location)
|
||||
unless the config pins `LD_LIBRARY_PATH` in `radar.rph_radar.env`.
|
||||
Закрытые библиотеки `libx502api.so` и `libe502api.so` коллектор загружает при запуске
|
||||
через `dlopen`, поэтому их должен находить загрузчик. Сервис RPH ставит
|
||||
`~/.local/lib` (их обычное место) в начало пути поиска, если профиль не задаёт
|
||||
`LD_LIBRARY_PATH` в `radar.rph_radar.env`.
|
||||
|
||||
There is one acquisition path: USB, differential ADC channels 2/3, maximum
|
||||
internal-clock rate, rising/falling DI_SYN2 gating, DO1 pair subtraction and DO8
|
||||
reference overlay. `radar.rph_radar.args` sets ADC range, internal reference,
|
||||
maximum sweep duration and DO8 period. The example's fixed-path options remain
|
||||
accepted as guards; incompatible modes and unused diagnostic options are rejected.
|
||||
The service appends `tty:<path>` from `radar.rph_radar.tty_path`.
|
||||
Путь съёма один: USB, дифференциальные каналы АЦП 2 и 3, наибольшая частота от
|
||||
внутренних часов, выделение развёрток по фронту и спаду DI_SYN2, вычитание соседних
|
||||
отсчётов с разными уровнями DO1 с усреднением и наложение опорного сигнала по DO8.
|
||||
`radar.rph_radar.args` задаёт диапазон АЦП, внутреннюю опорную
|
||||
частоту, наибольшую длительность развёртки и период DO8. Остальные параметры из
|
||||
примера принимаются только как подтверждение этого пути; несовместимые режимы и
|
||||
неиспользуемые отладочные параметры отвергаются. Параметр `tty:<путь>` из
|
||||
`radar.rph_radar.tty_path` добавляет сервис.
|
||||
|
||||
When both RF switches use `driver_mode: "native"`, Python passes
|
||||
`switching:<JSON>` with the already decoded input/output switch settings, combos
|
||||
and settling time. The collector reads no profile file. Otherwise, Python selects
|
||||
the switches and starts a new collector after settling when the combination
|
||||
changes. Calibration captures also use this Python-controlled path.
|
||||
Если оба ВЧ-ключа в `driver_mode: "native"`, Python передаёт коллектору
|
||||
`switching:<JSON>` с уже прочитанными настройками ключей, парами и паузой
|
||||
установления; профиль коллектор не читает. Иначе ключи переключает Python и при смене
|
||||
пары после паузы запускает новый коллектор. Съём наборов калибровки тоже идёт этим
|
||||
путём.
|
||||
|
||||
The launching service owns the collector process and forwards its output to the
|
||||
application log. On Linux, parent death sends SIGKILL to the collector; normal
|
||||
Stop drains and closes the service's own child. No global process-name kill is
|
||||
used.
|
||||
Процессом коллектора владеет запустивший его сервис; вывод коллектора уходит в журнал
|
||||
программы. В Linux смерть родителя завершает коллектор сигналом SIGKILL; обычный
|
||||
**Stop** дочитывает и закрывает собственный дочерний процесс сервиса. Завершения
|
||||
процессов по имени нет.
|
||||
|
||||
Reference in New Issue
Block a user