This commit is contained in:
david rice
2026-04-09 10:29:53 +01:00
parent be7658b54d
commit 82e6efbcad
7 changed files with 488 additions and 71 deletions

View File

@@ -7,6 +7,7 @@ VERSION: 0.3
AUTHOR: D. RICE 25/03/2026
© 2026 ARRIVE
"""
import json
import vxi11
import time
import sys
@@ -19,10 +20,11 @@ import analyze_captures
import rigol_scope
# --- Configuration ---
URL = "http://192.168.45.8:5000/display"
DEVICE_BASE = "http://192.168.45.8:5000"
URL = f"{DEVICE_BASE}/display"
SCOPE_IP = "192.168.45.4"
PSU_IP = "192.168.45.3"
MGMT_INTERVAL = 60 # seconds between management runs (set to 3600 for hourly)
MGMT_INTERVAL = 3600 # seconds between management runs (3600 = 1 hour)
# --- Capture settings ---
# Pass 1 — signal quality: resolves individual bits at 140 Mbit/s (7.1 ns/bit)
@@ -278,54 +280,59 @@ def _restore_hs_config():
time.sleep(0.1)
def _fetch_registers(ts: str, iteration: int) -> None:
"""
GET /registers from the device Flask server and save to data/ as JSON.
Reads MIPI DSI PHY timing registers via memtool on the target.
Non-fatal — a failed fetch prints a warning and returns without crashing.
"""
try:
resp = requests.get(f"{DEVICE_BASE}/registers", timeout=5)
resp.raise_for_status()
data = resp.json()
if data.get("errors"):
print(f" REGISTERS: device warnings — {data['errors']}")
DATA_DIR.mkdir(exist_ok=True)
reg_path = DATA_DIR / f"{ts}_reg_{iteration:04d}.json"
reg_path.write_text(json.dumps(data, indent=2))
n = len(data.get("registers", []))
print(f" SAVED: {reg_path.name} ({n} registers)")
except requests.exceptions.RequestException as e:
print(f" REGISTERS: fetch failed — {e}")
except Exception as e:
print(f" REGISTERS: error — {e}")
def dual_capture(iteration):
"""
Two-pass capture per test iteration:
Pass 1 — signal quality (SIG_SCALE / SIG_POINTS)
Pass 2 — frame structure (PROTO_SCALE / PROTO_POINTS)
Restores the original 5 ns/div timebase when done.
Three-pass capture per test iteration. LP is captured FIRST so it catches
the SoT transition at pipeline startup — the moment flicker can occur.
HS quality and frame structure passes follow once the link is stable.
Pass 1 — LP / SoT startup (no settle delay — fires immediately after display ON)
Pass 2 — signal quality (HS differential, rise/fall)
Pass 3 — frame structure (HS differential, jitter/freq)
"""
capture_done.clear()
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
print(f"DUAL CAPTURE #{iteration:04d} [{ts}]")
print(f"CAPTURE #{iteration:04d} [{ts}]")
# ── Pass 1: signal quality ─────────────────────────────────────────────
print(" PASS 1: SIGNAL QUALITY...")
_set_timebase(SIG_SCALE, SIG_POINTS)
if _arm_and_wait():
_save_pass("sig", iteration, ts)
else:
print(" SKIPPING PASS 1 SAVE.")
# ── Pass 2: frame/protocol structure ──────────────────────────────────
print(" PASS 2: FRAME STRUCTURE...")
_set_timebase(PROTO_SCALE, PROTO_POINTS)
if _arm_and_wait():
_save_pass("proto", iteration, ts)
else:
print(" SKIPPING PASS 2 SAVE.")
# ── Pass 3: LP / SoT structure + 1.8 V supply monitoring ─────────────
# Widens vertical range to capture LP-11 (1.2 V) and falls-edge triggers
# on the LP-11 → LP-01 SoT transition. Saves Ch1 and Ch3 single-ended.
# Rigol is armed first (non-blocking) so the LP→HS current step droops
# the 1.8 V rail and triggers the Rigol while the Agilent captures.
print(" PASS 3: LP TRANSITION...")
# ── Pass 1: LP / SoT startup transition ───────────────────────────────
# Fired immediately after display ON (test_worker has no settle delay).
# Catches the first LP-11 → LP-01 → LP-00 → HS SoT sequence, which is
# where violations causing screen flicker occur.
print(" PASS 1: LP STARTUP TRANSITION...")
_configure_for_lp()
_set_timebase(LP_SCALE, LP_POINTS)
if rigol_scope.is_connected():
rigol_scope.arm() # arm Rigol before LP trigger so it catches the droop
rigol_scope.arm() # arm before Agilent so 1.8 V droop is captured
if _arm_and_wait(timeout=30):
_save_pass_channels("lp", iteration, ts)
else:
print(" SKIPPING PASS 3 SAVE.")
print(" SKIPPING LP SAVE.")
# Collect Rigol 1.8 V waveform.
# The Agilent LP acquire + save takes ~35 s, so the Rigol will have
# long since auto-captured by now. read_waveform_csv() sends :STOP
# before reading to guarantee the acquisition is finalised.
if rigol_scope.is_connected():
DATA_DIR.mkdir(exist_ok=True)
v18_path = DATA_DIR / f"{ts}_pwr_{iteration:04d}_1v8.csv"
@@ -337,6 +344,27 @@ def dual_capture(iteration):
_restore_hs_config()
# ── Pass 2: HS signal quality ──────────────────────────────────────────
# LP pass takes ~510 s total; the HS link is fully settled by now.
print(" PASS 2: SIGNAL QUALITY...")
_set_timebase(SIG_SCALE, SIG_POINTS)
if _arm_and_wait():
_save_pass("sig", iteration, ts)
else:
print(" SKIPPING SIG SAVE.")
# ── Pass 3: frame/protocol structure ──────────────────────────────────
print(" PASS 3: FRAME STRUCTURE...")
_set_timebase(PROTO_SCALE, PROTO_POINTS)
if _arm_and_wait():
_save_pass("proto", iteration, ts)
else:
print(" SKIPPING PROTO SAVE.")
# ── Fetch DSI register snapshot from device ───────────────────────────
# Display is still ON here; registers reflect the active pipeline state.
_fetch_registers(ts, iteration)
# ── Restore original timebase ─────────────────────────────────────────
_set_timebase(5e-9, 500_000)
scope.write(":RUN")
@@ -364,7 +392,7 @@ def mgmt_worker():
print(f"[MGMT] TRANSFERRED {copied} FILE(S) TO DATA FOLDER. {failed} FAILED.")
if copied > 0:
try:
analyze_captures.run_analysis()
analyze_captures.run_analysis(last=30)
except Exception as e:
print(f"[MGMT] ANALYSIS ERROR: {e}")
except Exception as e:
@@ -394,7 +422,7 @@ def test_worker():
requests.put(URL, json={"state": "on"}, timeout=2)
except requests.exceptions.RequestException as e:
print(f" WARNING: display ON failed: {e}")
time.sleep(DISPLAY_SETTLE_S)
# No settle delay — LP pass fires immediately to catch startup SoT transition
dual_capture(count)
count += 1
try: