Commit Graph

13 Commits

Author SHA1 Message Date
Jason acfbbb1d4d sim(antenna): probe_fed_array v3 — multi-port DRIVEN_PORTS env override
Previously the array sim only excited a single inner element
(DRIVEN_X / DRIVEN_Y). Adds DRIVEN_PORTS env var accepting a
comma/semicolon-separated list of "i,j" pairs that are all excited
in-phase with equal amplitude — models a perfect 1:N corporate
splitter feeding an N-patch sub-array.

Example: DRIVEN_PORTS="0,0;1,0;2,0;3,0;0,1;1,1;2,1;3,1" excites a
4-cols × 2-rows sub-array anchored in the corner.

S-parameter post-processing reframed for the multi-driven case:
each excited port reports active-S11 (uf_ref/uf_inc with all driven
ports active); each non-excited port reports S relative to a
representative driven port's incident wave (all driven ports have
equal amplitude so any reference works).

Backwards-compatible: empty DRIVEN_PORTS reverts to single-port
DRIVEN_X / DRIVEN_Y behaviour.
2026-05-04 21:06:46 +05:45
Jason 38ee73a05c sim(antenna): verify production beam tables — setBeamAngle() is dead code
Audit of how the ADAR1000 is actually steered in production. Reproduces
main.cpp:initializeBeamMatrices() (PHASE_DIFFERENCES, matrix1, matrix2,
vector_0) verbatim in Python and runs the patterns through the same array-
factor pipeline used for the firmware-vs-correct comparison.

Key findings — context for fixing the setBeamAngle() bug without regression:

1) setBeamAngle() is DEAD CODE in production. grep for callers across the
   whole tree returns only the definition itself (ADAR1000_Manager.cpp:215),
   the header declaration (line 58), and one comment in ADAR1000_AGC.cpp
   referencing the sign convention. main.cpp does NOT call it. The 4-phase
   broadcast bug exposed in commit 2f4d45c is therefore latent, not active.
   Fixing setBeamAngle() has zero risk of changing production behaviour.

2) Production path: initializeBeamMatrices() (main.cpp:467) computes
   matrix1[bp][el] = degTo7Bit(el * phase_differences[bp]) for all 16
   elements properly (no 4-broadcast). main.cpp's runRadarPulseSequence()
   then calls setCustomBeamPattern16(matrix1[bp], TX/RX) → 16-element
   progressive phase reaches the chips correctly. This part is right.

3) HOWEVER — the production tables themselves have separate concerns:

   a) SIGN CONVENTION MISLABEL: comment says "matrix1 = positive steering
      angles" but math+sim show positive phase_diff steers to NEGATIVE θ.
      matrix1[bp=0] (phase_diff=+160°) → actual peak -62°.
      Either the comment is wrong, or hardware wiring inverts what's
      labeled "+elevation" — needs a hardware test to confirm.

   b) ASYMMETRIC INDEXING: matrix2 uses phase_differences[bp + 16] which
      gives matrix2[bp=0]=-3.4° while matrix1[bp=0]=-62°. So as bp goes
      0→14, matrix1 zooms in toward broadside (-62°→-4°) while matrix2
      zooms out (+4°→+62°) — they're NOT mirror images. Symmetric mirror
      would be phase_differences[30 - bp].

   c) NON-UNIFORM COVERAGE: phase_differences[] follows 160/n pattern
      (160, 80, 53.33, 40, 32, ...). After sin⁻¹ this gives 17 unique
      scan angles spanning -62°..+62° with a 36° GAP between -62° and
      -26°, but 2° spacing near broadside. May be intentional (oversample
      near nominal target line) but flag for confirmation.

   d) WORST-CASE SLL at extreme scan (matrix1[0] → -62°): only -2.9 dB.
      Main beam barely clears sidelobes — typical at near-grazing scan
      due to embedded element pattern roll-off.

   e) initializeBeamMatricesWithSteeringAngles() (main.cpp:1611) is also
      dead code. It computes the same thing two different ways. Safe to
      remove or merge during cleanup.

Recommendation for fix sequence (low → high risk):
  i.   Fix setBeamAngle() to call setCustomBeamPattern16() with proper
       16-element table (or mark deprecated). Zero production risk.
  ii.  Add unit test that runs setBeamAngle and verifies the resulting
       phase codes match a known-good 16-element progression.
  iii. Update the misleading comments in initializeBeamMatrices() to say
       what the matrices ACTUALLY do (the sign convention).
  iv.  Hardware test BEFORE touching matrix2 indexing or phase_differences
       distribution — these may be deliberately tuned to platform mounting.
2026-05-04 02:05:34 +05:45
Jason 2f4d45caa7 sim(antenna): nf2ff far-field + ADAR1000 array-factor verification
PART 1 — edge_fed_row_nf2ff_aeris10_v3.py:
Far-field analysis of the 1x8 series-fed row at 10.520 GHz to discriminate
broadside from scanned operation. Adds an nf2ff probe box to the verified
TX-centered design point (CONN_LEN=8.15) and computes the radiation pattern.

  Result: E-plane (along array y) main lobe at θ=0.0°, BW3dB=14°
          H-plane (perpendicular) main lobe at θ=0.0°, BW3dB=44°
          First sidelobe -12 dB at ±18° (theoretical N=8 uniform: -13 dB)
          → BROADSIDE CONFIRMED at the operating mode.

  openEMS NF2FF API notes baked into the script:
    - CalcNF2FF expects theta/phi in DEGREES, converts internally.
    - Prad/Dmax are sign-buggy in this version; pattern shape (E_norm) is
      what we use for broadside identification.
    - EndCriteria is 10·log10 energy ratio (so -40 dB → EndCriteria=1e-4).

PART 2 — array_factor_adar1000_aeris10.py:
Phased-array beam-forming verification using the ADAR1000 firmware's actual
phase-shifter codes. Combines the cached single-row embedded-element pattern
with a 16-element x-axis array factor at d=λ/2=14.286 mm pitch (matches
firmware's `element_spacing = wavelength/2` constant).

  Replicates ADAR1000_Manager.cpp:714-729 (calculatePhaseSettings) and the
  setBeamAngle() loop that broadcasts the 4-phase pattern to all 4 chips.
  Compares against the correct 16-element progressive phasing.

CRITICAL FIRMWARE BUG SURFACED BY THE SIM:
  setBeamAngle(angle) computes only 4 phases (one per chip channel), then
  applies the same 4-phase pattern to all 4 chips. The intended 16-element
  beam-steering never actually happens.

  At cmd 15°, the firmware writes: [0, 17, 33, 50] × 4 = repeating pattern
  Correct 16-elem progression:     [0, 17, 33, 50, 66, 83, 99, 116, 5, 21,
                                     38, 54, 71, 87, 104, 120]

  Sim consequences (H-plane peak vs commanded):
    cmd  +0° → fw peaks  +0° (correct)        / correct  +0°
    cmd  +5° → fw peaks  +0° (NO STEERING)    / correct  -4°
    cmd +10° → fw peaks  +0° (NO STEERING)    / correct -10°
    cmd +15° → fw peaks  +0° (NO STEERING)    / correct -14°
    cmd +20° → fw peaks -28° (locked grating) / correct -20°
    cmd +30° → fw peaks -30° (coincidence)    / correct -30°
    cmd +45° → fw peaks -30° (locked)         / correct -44°

  Why: the same 4-elem pattern broadcast to 4 chips = effective super-pitch
  d_super = 4d = 2λ, which generates grating lobes at sin(θ_g)=±0.5±sin(θ_0).
  For |cmd|<18° those grating lobes coincide with the fixed broadside peak,
  so the array radiates broadside regardless of commanded angle. For larger
  commands the beam jumps to ±30° (the grating-lobe direction), not the
  intended target.

  The proper function setCustomBeamPattern16() (ADAR1000_Manager.cpp:636)
  exists but isn't called by setBeamAngle(); fix is to either route through
  it from a host-computed 16-element table, or extend calculatePhaseSettings
  to produce 16 phases. Tracking under a separate beam-forming PR (not
  antenna sim).

Other radar-engineer checks (all PASS for the antenna with proper phasing):
  - Phase quantization: 7-bit (2.8125°/LSB) adds <0.2 dB SLL degradation
  - Grating lobes: d=λ/2 → none in real space at any scan angle 0..90°
  - Scan loss: matches cos(θ) ideal up to ~45°, then EEP roll-off dominates
  - Beam pointing: sign-flipped cmd↔peak (cmd +X° peaks at -X°) — wiring
    convention; either negate phase_shift in firmware or invert in host
  - Null steering: phase-only synthesis places -30 dB null at θ=20° while
    keeping main beam at broadside — confirmed feasible
2026-05-04 01:55:04 +05:45
Jason abc7e3c66b sim(antenna): center 1x8 row dip on radar TX 10.520 GHz (CONN_LEN 8.0->8.15)
Sweep CONN_LEN at PROFILE=balanced to land the operating-mode dip on the
chirp-band center instead of 60 MHz above it. Sensitivity is df/dCONN ≈
-0.20 GHz/mm (longer line → lower op freq).

  CONN=8.00 → dip 10.560 GHz, S11@10.520 = -12.6 dB (above TX)
  CONN=8.15 → dip 10.520 GHz, S11@10.520 = -18.8 dB (TX-centered)
  CONN=8.20 → dip 10.510 GHz, S11@10.510 = -18.4 dB (TX low edge)
  CONN=8.25 → dip 10.500 GHz, S11@10.500 = -18.4 dB (LO-centered)

CONN_LEN=8.15 wins because the radar TX 10.510-10.530 GHz sees -17 to -19 dB
symmetrically across the band, with -15 dB margin at the LO frequency. -10 dB
BW spans 10.470-10.580 GHz (110 MHz). Pitch 15.10 mm vs old Gerber 15.01 mm.
2026-05-04 00:41:28 +05:45
Jason 087a0563c0 sim(antenna): add 1x8 series-fed row — covers radar TX 10.51-10.53 GHz at -10 to -14 dB
Daisy-chain validation for 2-layer 0.508 mm RO4350B stackup. Row of 8 patches
edge-connected via 8.0 mm microstrip segments (pitch 14.95 mm matches old
Gerber). With direct edge feed on patch 0 (no inset; inset would drop the row
input Z to ~6 Ω), the natural input impedance at the operating mode is ~80 Ω,
close enough to 50 Ω for direct match without a quarter-wave transformer.

Topology behaves as a finite periodic resonator with N=8 modes (~0.5 GHz
spacing) and a stopband centered on the patch self-resonance. Operating point
is the top-below-stopband mode at 10.56 GHz: S11 = -22.5 dB, Zin = 79.9 - j3 Ω.
-10 dB BW spans 10.51-10.61 GHz (100 MHz), covering the 10.510-10.530 GHz
chirp band with S11 = -10.4 to -14.6 dB across that interval.

Mesh sensitivity: sanity profile (lambda/18) gave a misleading deepest-dip at
11.4 GHz; balanced (lambda/25) is required to land the operating-mode
characterization correctly. PROFILE=balanced is now the documented run mode.
2026-05-04 00:22:54 +05:45
Jason 178cb26abd sim(antenna): add edge-fed (inset) single-element on 0.508 mm RO4350B — 180 MHz BW
Validates the "Option C" hardware path: keep the old 8x16 Gerber's series-fed
edge-fed topology, just thicken the patch substrate from 0.102 mm to 0.508 mm
RO4350B. Single-element edge-fed with inset notch matched to 50 Ω microstrip.

Verified at PROFILE=balanced (λ/25 mesh):
  W = 7.854 mm   (preserved from old Gerber → array compatible)
  L = 6.95 mm    (tuned for f_res = 10.5 GHz on 0.508 mm sub)
  inset_depth = 3.40 mm (~49 % of L)
  inset_gap   = 0.30 mm (each side of feed line, in the inset notch)
  feed_W = 1.16 mm (50 Ω microstrip on 0.508 mm RO4350B)
  feed_lead = 15.5 mm (= 1·λ_g at 10.5 GHz → port sees true antenna Z)

Result:
  f_res = 10.509 GHz, S11 @ 10.5 = -18.5 dB, VSWR = 1.27
  Z @ 10.5 = 61.8 + j3.2 Ω
  -10 dB BW = 180 MHz (10.41-10.59 GHz, 1.71 %)

This is identical BW to probe_fed_v3 — confirming BW is set by substrate
thickness alone, not feed method. Edge-fed Option C is therefore the simpler
2-layer hardware path: same series-fed-row architecture as the old Gerber,
single PCB stackup, no probe vias / antipads / back-board splitter complexity.

Next step: extend to a 1x8 series-fed row to verify the daisy-chain topology
still gives in-phase feeding at the new substrate's λ_g.
2026-05-03 18:24:05 +05:45
Jason eb9be337b1 sim(antenna): add 4x4 probe-fed array model — mutual coupling characterisation
Single-port-driven, 15-port-terminated FDTD sim built on the same v3 patch
design point (W=7.854, L=6.56, FEED_OFFSET=2.14) at the Gerber pitch
(14.27 mm X / 15.01 mm Y). Reads out S_jd for all elements when port
(DRIVEN_X, DRIVEN_Y) is excited. Configurable array size + driven port via
env vars; default = 4x4, drive inner element (1,1).

Result at PROFILE=balanced, drive (1,1):
  Active S11 @ 10.5 GHz : -16.7 dB,  Z = 45.7 + j13.5 Ω
  Active -10 dB BW      : 190 MHz (10.44-10.63 GHz; ≈ single-element 180)
  Nearest-neighbour coupling:
    -x edge neighbour (0,1): -18.3 dB  (strongest — edge element loads less)
    -y/+y in-array        : -22 dB
    +x interior neighbour : -24.7 dB
  Diagonal               : -32 dB
  Far corners            : -45 to -53 dB

Outputs: coupling_grid.png heatmap + S_matrix.csv (all S_jd full sweep) +
S11_data.csv (driven-port only).

Known limitation: port box must align with mesh lines; SmoothMeshLines may
sub-cell-shift seed lines, causing some DRIVEN_X/DRIVEN_Y choices to give
"Unused primitive" warning + zero excitation. Default (1,1) is verified;
other choices are best-effort. Documented inline.
2026-05-03 16:18:42 +05:45
Jason 3f3846b514 sim(antenna): add probe-fed 2-layer patch model — 180 MHz BW vs aperture-coupled 60 MHz
Single-element OpenEMS sim for a 2-layer probe-fed patch on 0.508 mm RO4350B.
Verified at PROFILE=balanced (λ/25 mesh): f_res = 10.51 GHz, S11 = -21.8 dB,
VSWR 1.18, -10 dB BW = 180 MHz (10.40-10.58 GHz). Direct 50 Ω match — no port
matching cap needed.

Design point baked into defaults:
  PATCH_W      = 7.854 mm   (preserved from old 8x16 Gerber → array compatible)
  PATCH_L      = 6.56 mm    (tuned for f_res = 10.5 GHz at 0.508 mm sub)
  FEED_OFFSET  = 2.14 mm    (probe via, from -y radiating edge)

Why probe-fed: aperture-coupled v2 (4-layer Stack_Hybrid) capped at ~60 MHz BW
because the 0.11 mm L4 acts as a near-short reflector — beneficial for slot
coupling but creates an L2-L4 cavity that's the BW ceiling. Tested removing L4
(open-back aperture-coupled): coupling collapsed, R stayed >1000 Ω regardless
of patch tuning. Probe-fed has no slot bottleneck; physics BW = 1.7% on h=0.508
matches sim 180 MHz directly.

Hardware change required to deploy: 2-layer stackup (patch on top, ground on
bottom, probe vias with antipads). Old 8x16 Gerber was edge-fed; v3 is probe-
fed → top-layer feed network goes away, ADAR1000 carrier on a separate board
with SMP RF launches. Stackup signoff with antenna designer needed before PCB.

aperture_coupled_v2 retained as the 4-layer fallback (with the 0.043 pF cap)
if 2-layer redesign isn't approved.
2026-05-03 15:28:57 +05:45
Jason e01c2ae424 sim(antenna): tune aperture-coupled v2 to design point — R matched, X residual documented
10-iteration analytic-tune sweep (no DE optimizer — that was a wrong
direction for this topology) on the Stack_Hybrid 4-layer stackup. Key
findings from the sweep are baked into the script defaults and header.

Best design point @ 10.5 GHz (now the env-var defaults):
  patch  W=9.55 mm  L=7.77 mm
  slot   L=3.00 mm  W=0.50 mm  (centered under patch)
  stub   L=4.16 mm  (= λ_g/4 in feed sub at f0)
  feed   lead=12.34 mm  W=0.25 mm
         total feed length = 1·λ_g at 10.5 GHz, line transparent at f0

Result: Z ≈ R + j350 Ω at 10.5 GHz, R = 33–51 across reruns (sanity-profile
convergence drift; X is stable). R is within matching range; the +j350
inductive residual is fundamental to the topology (L4 backshort under the
antenna footprint), not a tuning artifact. Two production-grade fixes:
  (a) Series cap at port C ≈ 0.043 pF — drops S11 to ≈ -40 dB
  (b) Open L4 backshort under antenna — restores standard open-back
      aperture-coupled, stub naturally tunes X

Three real bugs fixed in the underlying sim (without these, the baseline
script was measuring artifacts, not the antenna):

1. Z mesh under-density. Default SmoothMeshLines at λ/18 gave the 0.11 mm
   feed substrate ~0.1 cells vertically — microstrip Z0 was a pure mesh
   artifact. Now built explicitly with ≥5 substrate-interior lines per
   dielectric (bypassing SmoothMeshLines collapse for z-axis only). Fine
   substrate cells visible via MESH_DEBUG=1.

2. SLOT_Y_OFF_MM env var was read but never applied to the L2 slot box,
   silently making the parameter inert. Slot is now correctly offset in y.

3. FEED_LEAD_L was hardcoded at 14.0 mm, making total feed length 18.16 mm
   = exactly 1·λ_g at 9.5 GHz (in feed substrate). This created a parasitic
   feed-line full-wave standing wave in-band that masked the patch as a
   persistent "9.4 GHz resonance" regardless of patch dimensions. Now
   parameterized via FEED_LEAD_L_MM with default 12.34 mm so total feed =
   1·λ_g at 10.5 GHz instead — line is transparent at the operating freq
   and sim measures the actual antenna impedance at the port.

Sweep grids updated to span ±1 step around the iter #6 design point.
2026-05-03 00:26:41 +05:45
Jason 42056b8331 sim(antenna): add OpenEMS aperture-coupled patch model for Stack_Hybrid v2
Re-simulation infrastructure for the new 4-layer aperture-coupled antenna
stackup (Stack_Hybrid.png, committed 1de2296). Renders the full multilayer
geometry (L1 patch / L2 slot ground / L3 microstrip feed / L4 backplane)
on the actual substrate stack (RO4350B 0.508 mm + RO4450F 1.2 mm + RO4350B
0.11 mm) and runs FDTD via openEMS to characterize S11 vs frequency.

Geometry interpolated from existing 2-layer Gerber:
  patch W from Gerber (7.854 mm, set by εr only)
  patch L re-tunable for new substrate (env var PATCH_L_MM, default 7.25 mm)
  slot/feed/stub fully parameterized via env vars

Run modes:
  PROFILE=sanity    : single run, λ/18 mesh, ~30 s
  PROFILE=balanced  : single run, λ/25 mesh, ~60 s
  PROFILE=sweep     : 5×5 grid over slot_L × stub_L, ~25 min

Env overrides for parametric exploration:
  PATCH_W_MM, PATCH_L_MM, SLOT_L_MM, SLOT_W_MM, STUB_L_MM, SLOT_Y_OFF_MM

Setup: openEMS Python bindings built from source against /opt/openEMS for
Python 3.12 in radar_venv; run with
  DYLD_LIBRARY_PATH=~/opt/openEMS/lib ~/radar_venv/bin/python
under cwd != openEMS-Project/openEMS/python (avoids CSXCAD shadow import).

Status: simulation infrastructure verified end-to-end; patch resonates at
10.4 GHz with W=9.0 / L=5.5–6.0 mm. Full 50 Ω impedance match not yet
converged — the L4 backplane (non-standard for aperture-coupled) reduces
slot coupling vs textbook formulas; needs proper EM optimizer (scipy
wrapping or CST/HFSS handoff) to fully tune. ~150 MHz baseline BW
predicted from substrate physics; 30/40 MHz chirp target comfortably
under any plausible match outcome.
2026-05-02 22:37:25 +05:45
Jason 2106e24952 fix: enforce strict ruff lint (17 rule sets) across entire repo
- Expand ruff config from E/F to 17 rule sets (B, RUF, SIM, PIE, T20,
  ARG, ERA, A, BLE, RET, ISC, TCH, UP, C4, PERF)
- Fix 907 lint errors across all Python files (GUI, FPGA cosim,
  schematics scripts, simulations, utilities, tools)
- Replace all blind except-Exception with specific exception types
- Remove commented-out dead code (ERA001) from cosim/simulation files
- Modernize typing: deprecated typing.List/Dict/Tuple to builtins
- Fix unused args/loop vars, ambiguous unicode, perf anti-patterns
- Delete legacy GUI files V1-V4
- Add V7 test suite, requirements files
- All CI jobs pass: ruff (0 errors), py_compile, pytest (92/92),
  MCU tests (20/20), FPGA regression (25/25)
2026-04-12 14:21:03 +05:45
Jason 11aa590cf2 fix: full-repo ruff lint cleanup and CI migration to uv
Resolve all 374 ruff errors across 36 Python files (E501, E702, E722,
E741, F821, F841, invalid-syntax) bringing `ruff check .` to zero
errors repo-wide with line-length=100.

Rewrite CI workflow to use uv for dependency management, whole-repo
`ruff check .`, py_compile syntax gate, and merged python-tests job.
Add pyproject.toml with ruff config and uv dependency groups.

CI structure proposed by hcm444.
2026-04-09 02:05:34 +03:00
NawfalMotii79 4b7eb54ee8 Add files via upload 2026-03-09 00:05:18 +00:00