Files
radar_system/data_acq_and_processing/rph_radar_collector

Коллектор радиофотонного радара (RPH)

Программа сбора для АЦП L-Card E-502, которой владеет приложение. Она принимает двухканальный поток IQ (основной и опорный сигналы) и передаёт целые развёртки в пакетах RPH1 через псевдотерминал. Поток читает и обрабатывает RphRadarService из python_app/hardware_full/rph_radar для процесса сбора python_app.scripts.rph_radar_raw_producer.

Это источник данных для radar.model: "rph_radar" с radar.rph_radar.source: "e502". Источник rfg_udp читает UDP-поток платы RFG напрямую и эту программу не использует; LibreVNA работает через драйверы на Python. Как радар устроен целиком — в главе Оборудование.

Состав

Путь Что
src/ коллектор (main.cpp) и запись в псевдотерминал
include/ заголовок псевдотерминала и switch_sequencer.h
vendor/lcard/ заголовки SDK L-Card X502/E502 (x502api.h, e502api.h, lcard_pstdint.h) без изменений, чтобы коллектор собирался без внешних путей

Коллектор использует общий драйвер ВЧ-ключей H7992 со своим набором заголовков и каталогом объектов build/rph/.

Переключение пар

Если ключами управляет коллектор, SwitchSequencer переключает ключи H7992 по настоящим концам развёрток (спаду DI_SYN2). Ограничение длительности и остановка ключи не переключают. При каждой смене пары коллектор отбрасывает оставшийся декодированный блок АЦП и цифрового входа и после max(1 ms, run.settling_ms) создаёт сеанс источника заново. Принадлежность развёртки новой паре по времени не определяется. Цикл считается законченным, только когда для каждой пары есть целая чистая развёртка. Если пара не меняется, сеанс сохраняется.

Перезапуск сеанса: остановка циклического вывода DOUT с WAIT_DONE, остановка и отключение потоков, возврат DO2 в высокий уровень, DO1 и DO8 — в низкий, закрытие и освобождение дескриптора SDK. После паузы установления создаётся, открывается и настраивается новый дескриптор, загружается шаблон DO1/DO8, коллектор ждёт OutCycleSetup(WAIT_DONE) и запускает АЦП, цифровой вход и вывод вместе. Накопленные отсчёты и фазовые накопители сбрасываются; номера развёрток и циклов идут без разрыва. Начальный высокий участок DI_SYN2 считается испорченным: сбор ждёт целую развёртку от фронта до спада. Поэтому при переоткрытии пропускается переменное число развёрток; постоянного числа пропусков или оценки паузы прошивки нет. Время переоткрытия, освобождение ресурсов USB и скорость при многих парах нужно измерить на железе.

Время съёма

Коллектор работает от внутренних часов АЦП. АЦП и цифровой вход запускаются вместе и работают непрерывно; развёртки выделяет программа по линии DI_SYN2. Время отсчёта — монотонные часы компьютера непосредственно перед StreamsStart плюс номер отсчёта, делённый на частоту выборки. Это оценка по часам источника, а не аппаратная метка: задержка запуска сдвигает начало отсчёта раньше. Очереди USB и псевдотерминала время не сдвигают. Если оценка времени отсчёта оказалась позже момента его приёма, сбор останавливается, чтобы не публиковать окно съёма из будущего. Расхождение часов АЦП и компьютера может сработать на этой проверке при долгой работе на одной паре; её предел и точность окон съёма нужно измерить на железе. Данные старого сеанса SDK после переключения ключа чистыми не становятся ни при каком расхождении часов.

Формат псевдотерминала

Пакет — 48-байтный заголовок в порядке байтов little-endian (<4sIQQQQHHI) и данные: RPH1, длина данных (u32), номер развёртки источника, номер цикла, начало и конец съёма в нс (четыре u64), положения входа и выхода (два u16), флаги (u32; бит 0 — развёртка испорчена). Без переключения коллектором оба положения равны 0xffff, а номер цикла совпадает с номером развёртки. Точка данных — четыре слова u16: метка (0x000a основной сигнал, 0x00a8 опорный), номер ступени, I и Q со знаком. Кадров границ и пар нет. При переполнении очередь выбрасывает целые развёртки; пакет, который уже пишется, не обрезается. Наибольший размер данных — 1 048 544 байта. Целые циклы собирает Python. При переключении ключей из Python прежний коллектор и чтение псевдотерминала закрываются, а после паузы установления запускается новый коллектор; состояние фазовой обработки сохраняется.

Сборка

make rph_radar_collector      # -> build/bin/rph_radar_collector

В make all коллектор не входит; start.sh собирает его сам, если в профиле radar.model: "rph_radar", кроме запуска с --skip-build. Сборка в Linux берёт общие исходники и заголовки из репозитория и линкуется с -pthread -ldl -lutil; установленный SDK L-Card для сборки не нужен.

Работа

Закрытые библиотеки libx502api.so и libe502api.so коллектор загружает при запуске через dlopen, поэтому их должен находить загрузчик. Сервис RPH ставит ~/.local/lib (их обычное место) в начало пути поиска, если профиль не задаёт LD_LIBRARY_PATH в radar.rph_radar.env.

Путь съёма один: USB, дифференциальные каналы АЦП 2 и 3, наибольшая частота от внутренних часов, выделение развёрток по фронту и спаду DI_SYN2, вычитание соседних отсчётов с разными уровнями DO1 с усреднением и наложение опорного сигнала по DO8. radar.rph_radar.args задаёт диапазон АЦП, внутреннюю опорную частоту, наибольшую длительность развёртки и период DO8. Остальные параметры из примера принимаются только как подтверждение этого пути; несовместимые режимы и неиспользуемые отладочные параметры отвергаются. Параметр tty:<путь> из radar.rph_radar.tty_path добавляет сервис.

Если оба ВЧ-ключа в driver_mode: "native", Python передаёт коллектору switching:<JSON> с уже прочитанными настройками ключей, парами и паузой установления; профиль коллектор не читает. Иначе ключи переключает Python и при смене пары после паузы запускает новый коллектор. Съём наборов калибровки тоже идёт этим путём.

Процессом коллектора владеет запустивший его сервис; вывод коллектора уходит в журнал программы. В Linux смерть родителя завершает коллектор сигналом SIGKILL; обычный Stop дочитывает и закрывает собственный дочерний процесс сервиса. Завершения процессов по имени нет.