Updates
This commit is contained in:
102
mipi_test.py
102
mipi_test.py
@@ -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 ~5–10 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:
|
||||
|
||||
Reference in New Issue
Block a user