Files
radar_system/data_acq_and_processing/rph_radar_collector

RadioPhotonic radar (RPH) collector

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.

This is the data source for radar.model == "rph_radar", paired with the Python rph_radar_raw_producer. LibreVNA acquisition uses Python drivers.

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.

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.

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.

The collector uses the common H7992 driver, with its own include set and build/rph/ object tree.

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.

Build

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.

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.

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.

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.

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.