2026-03-26 11:43:36 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
MIPI TEST APPLICATION - MIPI_TEST.PY
|
|
|
|
|
- ENTRY POINT OF APPLICATION
|
|
|
|
|
|
2026-03-26 11:49:18 +00:00
|
|
|
VERSION: 0.1
|
2026-03-26 11:43:36 +00:00
|
|
|
AUTHOR: D. RICE 25/03/2026
|
|
|
|
|
© 2026 ARRIVE
|
|
|
|
|
"""
|
|
|
|
|
import vxi11
|
|
|
|
|
import time
|
|
|
|
|
import sys
|
|
|
|
|
import requests
|
|
|
|
|
import threading
|
2026-03-26 15:18:58 +00:00
|
|
|
import os
|
|
|
|
|
|
|
|
|
|
# --- Configuration ---
|
|
|
|
|
URL = "http://192.168.45.8:5000/display"
|
|
|
|
|
SCOPE_IP = "192.168.45.4"
|
|
|
|
|
PSU_IP = "192.168.45.3"
|
2026-03-26 11:43:36 +00:00
|
|
|
|
|
|
|
|
test_running = False # Global flag to control the background thread
|
|
|
|
|
|
2026-03-26 15:18:58 +00:00
|
|
|
# --- Instrument Connection ---
|
2026-03-26 11:43:36 +00:00
|
|
|
try:
|
|
|
|
|
psu = vxi11.Instrument("192.168.45.3")
|
|
|
|
|
scope = vxi11.Instrument("192.168.45.4")
|
2026-03-26 15:18:58 +00:00
|
|
|
# Increase timeout for large image data transfers
|
|
|
|
|
scope.timeout = 5
|
2026-03-26 11:43:36 +00:00
|
|
|
except Exception as e:
|
|
|
|
|
print(f"ERROR: CANNOT CONNECT TO INSTRUMENTS: {e}")
|
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
2026-03-26 15:18:58 +00:00
|
|
|
def setup_scope():
|
|
|
|
|
"""Initializes DSO80204B for 210MHz MIPI Signals"""
|
|
|
|
|
print("Configuring Scope...")
|
|
|
|
|
scope.write("*RST")
|
|
|
|
|
time.sleep(1)
|
|
|
|
|
|
|
|
|
|
# Setup Channels (1&2=Clock, 3&4=Data)
|
|
|
|
|
for ch in range(1, 5):
|
|
|
|
|
scope.write(f":CHANnel{ch}:DISPlay ON")
|
|
|
|
|
scope.write(f":CHANnel{ch}:PROBe 19.2") # 910R/50R Divider
|
|
|
|
|
scope.write(f":CHANnel{ch}:INPut DC50")
|
|
|
|
|
scope.write(f":CHANnel{ch}:SCALe 0.1") # 100mV/div (200mV HS swing)
|
|
|
|
|
scope.write(f":CHANnel{ch}:OFFSet 0.0")
|
|
|
|
|
|
|
|
|
|
# Timebase & Trigger
|
|
|
|
|
scope.write(":TIMebase:SCALe 10e-6") # 10us/div
|
|
|
|
|
scope.write(":TRIGger:EDGE:SOURce CHANnel3")
|
|
|
|
|
scope.write(":TRIGger:EDGE:SLOPe FALLing") # Catch LP-11 to HS transition
|
|
|
|
|
scope.write(":TRIGger:LEVel CHANnel3, 0.15") # 150mV
|
|
|
|
|
scope.write(":TRIGger:SWEep SINGle")
|
|
|
|
|
print("Scope Configured.")
|
|
|
|
|
|
|
|
|
|
def save_screenshot(iteration):
|
2026-03-26 11:43:36 +00:00
|
|
|
try:
|
2026-03-26 15:18:58 +00:00
|
|
|
scope.clear()
|
|
|
|
|
# Use a very short, simple path. No subdirectories if possible.
|
|
|
|
|
# Ensure C:\TEMP exists!
|
|
|
|
|
filename = f"C:\\TEMP\\cap{iteration}.png"
|
|
|
|
|
|
|
|
|
|
print(f"ATTEMPTING SIMPLEST XP SYNTAX: {filename}")
|
2026-03-26 11:43:36 +00:00
|
|
|
|
2026-03-26 15:18:58 +00:00
|
|
|
# Try ONLY the filename and the format.
|
|
|
|
|
# Many XP versions hate the SCR,COL extra parameters via SCPI.
|
|
|
|
|
cmd = f':DISK:SAVE:IMAGe "{filename}",PNG'
|
|
|
|
|
|
|
|
|
|
scope.write(cmd)
|
|
|
|
|
|
|
|
|
|
# DO NOT use scope.ask() or any queries yet.
|
|
|
|
|
# Just sleep and check the folder manually.
|
|
|
|
|
time.sleep(5.0)
|
|
|
|
|
print("COMMAND SENT. CHECK C:\\TEMP")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"XP COMM ERROR: {e}")
|
|
|
|
|
|
|
|
|
|
def test_worker(on_time):
|
|
|
|
|
""" Background loop:
|
|
|
|
|
- Sets display ON for user-defined duration.
|
|
|
|
|
- Sets display OFF for exactly 1 second.
|
|
|
|
|
"""
|
2026-03-26 11:43:36 +00:00
|
|
|
global test_running
|
2026-03-26 15:18:58 +00:00
|
|
|
count = 1
|
2026-03-26 11:43:36 +00:00
|
|
|
|
|
|
|
|
while test_running:
|
2026-03-26 15:18:58 +00:00
|
|
|
# Arm scope
|
|
|
|
|
scope.write(":SINGle")
|
|
|
|
|
|
|
|
|
|
# Start display
|
|
|
|
|
requests.put(URL, json={"state": "on"}, timeout=2)
|
|
|
|
|
|
|
|
|
|
# Wait for the scope to trigger and fill memory
|
|
|
|
|
# We wait slightly longer than the 'on_time' to ensure capture is done
|
|
|
|
|
time.sleep(on_time + 0.5)
|
|
|
|
|
|
|
|
|
|
# Pull the image
|
|
|
|
|
save_screenshot(count)
|
|
|
|
|
count += 1
|
|
|
|
|
|
|
|
|
|
# Set Display OFF
|
|
|
|
|
requests.put(URL, json={"state": "off"}, timeout=2)
|
2026-03-26 11:43:36 +00:00
|
|
|
|
2026-03-26 15:18:58 +00:00
|
|
|
# Fixed 1s OFF period
|
|
|
|
|
time.sleep(1.0)
|
2026-03-26 11:43:36 +00:00
|
|
|
|
|
|
|
|
def main_menu():
|
|
|
|
|
global test_running
|
|
|
|
|
|
|
|
|
|
"""Main Application Loop."""
|
|
|
|
|
while True:
|
2026-03-26 15:18:58 +00:00
|
|
|
print("\n===== MIPI TEST CONTROL =====")
|
2026-03-26 11:43:36 +00:00
|
|
|
print("1. RUN IDN CHECK (PSU & SCOPE)")
|
2026-03-26 15:18:58 +00:00
|
|
|
print("2. SETUP SCOPE (RUN FIRST)")
|
|
|
|
|
print("3. CONFIGURE PSU (DEFAULT 24V/1.5A)")
|
|
|
|
|
print("4. PSU OUTPUT ON/OFF (CH1)")
|
|
|
|
|
print("5. START TEST & CAPTURE")
|
|
|
|
|
print("6. STOP TEST")
|
|
|
|
|
print("7. EXIT")
|
2026-03-26 11:43:36 +00:00
|
|
|
|
2026-03-26 15:18:58 +00:00
|
|
|
choice = input("\nSELECT OPTION (1-7): ")
|
2026-03-26 11:43:36 +00:00
|
|
|
|
|
|
|
|
if choice == '1':
|
|
|
|
|
print(f"PSU: {psu.ask('*IDN?')}")
|
|
|
|
|
print(f"SCOPE: {scope.ask('*IDN?')}")
|
|
|
|
|
elif choice == '2':
|
2026-03-26 15:18:58 +00:00
|
|
|
setup_scope()
|
|
|
|
|
elif choice == '3':
|
2026-03-26 11:43:36 +00:00
|
|
|
psu.write('CH1:VOLT 24.0')
|
|
|
|
|
psu.write('CH1:CURR 1.5')
|
|
|
|
|
print("PSU CONFIGURED...")
|
2026-03-26 15:18:58 +00:00
|
|
|
elif choice == '4':
|
2026-03-26 11:43:36 +00:00
|
|
|
state = input("TYPE 'ON' OR 'OFF': ").upper()
|
|
|
|
|
psu.write(f'OUTP CH1,{state}')
|
2026-03-26 15:18:58 +00:00
|
|
|
elif choice == '5':
|
2026-03-26 11:43:36 +00:00
|
|
|
if not test_running:
|
|
|
|
|
try:
|
2026-03-26 15:18:58 +00:00
|
|
|
sec = float(input("ON DURATION (sec): "))
|
2026-03-26 11:43:36 +00:00
|
|
|
test_running = True
|
|
|
|
|
t = threading.Thread(target=test_worker, args=(sec,), daemon=True)
|
|
|
|
|
t.start()
|
|
|
|
|
except ValueError:
|
|
|
|
|
print("INVALID TIME ENTERED.")
|
|
|
|
|
else:
|
|
|
|
|
print("TEST IS ALREADY RUNNING!")
|
2026-03-26 15:18:58 +00:00
|
|
|
elif choice == '6':
|
2026-03-26 11:43:36 +00:00
|
|
|
print("STOPPING TEST...")
|
|
|
|
|
test_running = False
|
2026-03-26 15:18:58 +00:00
|
|
|
elif choice == '7':
|
2026-03-26 11:43:36 +00:00
|
|
|
test_running = False # Ensure thread stops
|
|
|
|
|
psu.close()
|
|
|
|
|
scope.close()
|
|
|
|
|
break
|
|
|
|
|
else:
|
|
|
|
|
print("INVALID ENTRY. PLEASE CHOOSE 1-6")
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
# Optional: Run auto-config on startup
|
|
|
|
|
main_menu()
|