Rework radar system
This commit is contained in:
@@ -2,11 +2,11 @@
|
||||
|
||||
Standalone acquisition binary for the **L-Card E-502** ADC. It captures the
|
||||
dual-channel main + reference IQ stream and publishes it as 8-byte framed packets
|
||||
over a PTY, which the Python `kamil_adc` producer
|
||||
(`python_app/hardware_full/kamil_adc`) reads and processes.
|
||||
over a PTY. `KamilAdcService` in `python_app/hardware_full/kamil_adc` reads and
|
||||
processes that stream for `python_app.scripts.kamil_adc_raw_producer`.
|
||||
|
||||
This is the data *source* for `radar.model == "kamil_adc"`; it is the Kamil ADC
|
||||
counterpart to the C++ `sweep_orchestrator` used by LibreVNA devices.
|
||||
This is the data *source* for `radar.model == "kamil_adc"`, paired with the Python
|
||||
`kamil_adc_raw_producer`. LibreVNA acquisition uses Python drivers.
|
||||
|
||||
## Layout
|
||||
|
||||
@@ -16,15 +16,13 @@ counterpart to the C++ `sweep_orchestrator` used by LibreVNA devices.
|
||||
| `include/` | Headers for the writers plus `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. |
|
||||
|
||||
The sources started as a verbatim import of the original standalone tool, but are
|
||||
**no longer identical to it**: the collector now drives the RF switches itself
|
||||
(switch-aware mode, `SwitchSequencer`), stepping to the next combination inside
|
||||
the hardware idle gap between sweeps and flagging a sweep *dirty* when a readout
|
||||
backlog means the toggle could not have landed in that gap. It therefore reuses
|
||||
the orchestrator's switch drivers and the shared `run_config` loader, which is why
|
||||
its build has its own include set and its own `build/kamil/` object tree.
|
||||
|
||||
The wire format towards the Python producer is unchanged.
|
||||
In switch-aware mode, `SwitchSequencer` drives the RF switches, stepping to the
|
||||
next combination inside the hardware idle gap between sweeps and flagging a
|
||||
sweep *dirty* when a readout backlog means the toggle could not have landed in
|
||||
that gap. The Python reader
|
||||
drops dirty sweeps; the collector repeats that combination without another
|
||||
switch transition. The collector uses the common H7992 driver and `run_config`
|
||||
loader, with its own include set and `build/kamil/` object tree.
|
||||
|
||||
## Build
|
||||
|
||||
@@ -33,16 +31,25 @@ make kamil_adc_collector # -> build/bin/kamil_adc_collector
|
||||
```
|
||||
|
||||
It is **not** part of `make all`; `start.sh` builds it automatically when the
|
||||
active config selects `radar.model == "kamil_adc"`. The build is self-contained:
|
||||
it needs only the vendored headers and links `-ldl -lutil -lpthread`.
|
||||
active config selects `radar.model == "kamil_adc"`, 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 on `LD_LIBRARY_PATH`. The Kamil ADC
|
||||
service prepends `~/.local/lib` (their default install location) unless the config
|
||||
pins `LD_LIBRARY_PATH` in `radar.kamil_adc.env`.
|
||||
via `dlopen`, so those libraries must be discoverable by the dynamic loader.
|
||||
The Kamil ADC service prepends `~/.local/lib` (their default install location)
|
||||
unless the config pins `LD_LIBRARY_PATH` in `radar.kamil_adc.env`.
|
||||
|
||||
The command-line arguments (capture profile, clocking, the `do8_freq_ref`
|
||||
reference overlay, etc.) are supplied by `radar.kamil_adc.args` in the run config;
|
||||
the service appends the generated `tty:<path>` argument.
|
||||
the service appends `tty:<path>` from `radar.kamil_adc.tty_path`.
|
||||
|
||||
When both RF switches use `driver_mode: "native"`, the producer also passes
|
||||
`config:<path>` to the collector. After each sweep boundary, the collector emits
|
||||
an 8-byte combo tag `[0x00C0, input_pos, output_pos, dirty]` (four little-endian
|
||||
16-bit words). The producer accepts only clean sweeps for the requested combo.
|
||||
Otherwise, the producer selects and settles the switches itself; the collector
|
||||
runs without `config:<path>` and emits no combo tags. Calibration captures also
|
||||
use this Python-controlled path.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "shared_types.hpp"
|
||||
#include "switch_driver.hpp"
|
||||
#include "h7992_minimal_driver.hpp"
|
||||
|
||||
namespace radar::kamil {
|
||||
|
||||
@@ -47,8 +47,8 @@ class SwitchSequencer {
|
||||
|
||||
SwitchSequencer(
|
||||
Settings settings,
|
||||
std::unique_ptr<drivers::SwitchDriver> input_switch,
|
||||
std::unique_ptr<drivers::SwitchDriver> output_switch
|
||||
std::unique_ptr<drivers::H7992MinimalDriver> input_switch,
|
||||
std::unique_ptr<drivers::H7992MinimalDriver> output_switch
|
||||
)
|
||||
: settings_(std::move(settings)),
|
||||
input_switch_(std::move(input_switch)),
|
||||
@@ -130,15 +130,14 @@ class SwitchSequencer {
|
||||
|
||||
private:
|
||||
void apply(const radar::ipc::ComboKey& combo) {
|
||||
// Output first, then input — same order the sweep orchestrator uses, so
|
||||
// both acquisition paths drive an identical hardware sequence.
|
||||
// Set the transmitter before the receiver.
|
||||
output_switch_->switch_to(combo.output_pos);
|
||||
input_switch_->switch_to(combo.input_pos);
|
||||
}
|
||||
|
||||
Settings settings_;
|
||||
std::unique_ptr<drivers::SwitchDriver> input_switch_;
|
||||
std::unique_ptr<drivers::SwitchDriver> output_switch_;
|
||||
std::unique_ptr<drivers::H7992MinimalDriver> input_switch_;
|
||||
std::unique_ptr<drivers::H7992MinimalDriver> output_switch_;
|
||||
std::size_t index_ = 0;
|
||||
radar::ipc::ComboKey applied_{};
|
||||
bool upcoming_dirty_ = false;
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include "switch_sequencer.h"
|
||||
#include "run_config.hpp"
|
||||
#include "h7992_minimal_driver.hpp"
|
||||
#include "hmc349a_minimal_driver.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
@@ -1830,21 +1829,8 @@ void print_device_info(const t_x502_info& info) {
|
||||
}
|
||||
|
||||
// Build one switch driver (native GPIO or mock) from a parsed switch config,
|
||||
// reusing the exact driver implementations the sweep orchestrator uses so both
|
||||
// acquisition paths drive identical hardware.
|
||||
std::unique_ptr<radar::drivers::SwitchDriver> make_switch_driver(const radar::config::SwitchConfig& sc) {
|
||||
if (sc.driver_kind == radar::config::SwitchDriverKind::HMC349A) {
|
||||
return std::make_unique<radar::drivers::HMC349AMinimalDriver>(
|
||||
radar::drivers::HMC349AMinimalDriverSettings{
|
||||
.name = sc.name,
|
||||
.mode = sc.driver_mode,
|
||||
.positions = sc.positions,
|
||||
.default_position = sc.default_position,
|
||||
.gpio_chip = sc.gpio_chip,
|
||||
.pin_a = sc.pin_a,
|
||||
.invert_logic = sc.invert_logic,
|
||||
});
|
||||
}
|
||||
// using the shared H7992 GPIO implementation.
|
||||
std::unique_ptr<radar::drivers::H7992MinimalDriver> make_switch_driver(const radar::config::SwitchConfig& sc) {
|
||||
return std::make_unique<radar::drivers::H7992MinimalDriver>(
|
||||
radar::drivers::H7992MinimalDriverSettings{
|
||||
.name = sc.name,
|
||||
|
||||
Reference in New Issue
Block a user