fix: range calibration, demo/radar mutual exclusion, AGC analysis refactor

Bug #1 — Range calibration for Raw IQ Replay:
- Add WaveformConfig dataclass (models.py) with FMCW waveform params
  (fs, BW, T_chirp, fc) and methods to compute range/velocity resolution
- Add waveform parameter spinboxes to playback controls (dashboard.py)
- Auto-parse waveform params from ADI phaser filename convention
- Create replay-specific RadarSettings with correct calibration instead
  of using FPGA defaults (781.25 m/bin → 0.334 m/bin for ADI phaser)
- Add 4 unit tests validating WaveformConfig math

Bug #2 — Demo + radar mutual exclusion:
- _start_demo() now refuses if radar is running (_running=True)
- _start_radar() stops demo first if _demo_mode is active
- Demo buttons disabled while radar/replay is running, re-enabled on stop

Bug #3 — Refactor adi_agc_analysis.py:
- Remove 60+ lines of duplicated AGC functions (signed_to_encoding,
  encoding_to_signed, clamp_gain, apply_gain_shift)
- Import from v7.agc_sim canonical implementation
- Rewrite simulate_agc() to use process_agc_frame() in a loop
- Rewrite process_frame_rd() to use quantize_iq() from agc_sim
This commit is contained in:
Jason
2026-04-14 03:19:58 +05:45
parent a16472480a
commit 609589349d
5 changed files with 270 additions and 131 deletions
+41
View File
@@ -96,6 +96,47 @@ class RadarTarget:
return asdict(self)
@dataclass
class WaveformConfig:
"""FMCW waveform parameters for bin-to-physical-unit conversion.
Defaults are for the ADI CN0566 phaser (10 GHz, 500 MHz BW, 300 µs chirp,
4 MSPS ADC). For the PLFM FPGA waveform the values differ — but the FPGA
pipeline hardcodes its own bin widths, so this config is only used for
Raw IQ Replay (host-side FFT processing).
"""
sample_rate_hz: float = 4e6 # ADC sample rate (Hz)
bandwidth_hz: float = 500e6 # Chirp bandwidth (Hz)
chirp_duration_s: float = 300e-6 # Chirp sweep time (s)
center_freq_hz: float = 10e9 # Carrier frequency (Hz)
# --- derived quantities (need n_samples, n_chirps from file) ---
def range_resolution(self, n_samples: int) -> float:
"""Metres per range bin for an N-point range FFT.
range_per_bin = c · fs / (2 · N · slope)
where slope = BW / T_chirp.
"""
c = 299_792_458.0
slope = self.bandwidth_hz / self.chirp_duration_s
return c * self.sample_rate_hz / (2.0 * n_samples * slope)
def velocity_resolution(self, n_samples: int, n_chirps: int) -> float:
"""m/s per Doppler bin for an M-chirp Doppler FFT.
vel_per_bin = λ · fs / (2 · N · M)
where λ = c / fc, N = n_samples (PRI = N/fs), M = n_chirps.
"""
c = 299_792_458.0
wavelength = c / self.center_freq_hz
return wavelength * self.sample_rate_hz / (2.0 * n_samples * n_chirps)
def max_range(self, n_range_bins: int, n_samples: int) -> float:
"""Maximum unambiguous range for the given bin count."""
return self.range_resolution(n_samples) * n_range_bins
@dataclass
class RadarSettings:
"""Radar system display/map configuration.