Add plot-sweep command, degauss buttons, sweep ramp-down, and time estimate
- Add offline `plot-sweep` CLI command: generates efficiency overlay, heatmap, and power loss plots from sweep CSV (no instruments needed) - Add degauss buttons to CH5/CH6 in GUI (sends :DEMAg SCPI command) - Gradual load ramp-down at end of 2D sweep to avoid transients - Live sweep time estimate in GUI based on grid size × settle time - Update HIOKI submodule to include degauss CLI command Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -410,6 +410,8 @@ class TestbenchGUI(tk.Tk):
|
||||
self._ch5_i_range.pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(rng5, text="Set", width=3,
|
||||
command=lambda: self._set_range(5, "I", self._ch5_i_range)).pack(side=tk.LEFT)
|
||||
ttk.Button(rng5, text="Degauss", width=7,
|
||||
command=lambda: self._send(Cmd.DEGAUSS, [5])).pack(side=tk.LEFT, padx=(8, 0))
|
||||
self._ch5_range_label = ttk.Label(input_frame, text="V: --- / I: ---", font=("Consolas", 9))
|
||||
self._ch5_range_label.pack(anchor=tk.W)
|
||||
|
||||
@@ -437,6 +439,8 @@ class TestbenchGUI(tk.Tk):
|
||||
self._ch6_i_range.pack(side=tk.LEFT, padx=2)
|
||||
ttk.Button(rng6, text="Set", width=3,
|
||||
command=lambda: self._set_range(6, "I", self._ch6_i_range)).pack(side=tk.LEFT)
|
||||
ttk.Button(rng6, text="Degauss", width=7,
|
||||
command=lambda: self._send(Cmd.DEGAUSS, [6])).pack(side=tk.LEFT, padx=(8, 0))
|
||||
self._ch6_range_label = ttk.Label(output_frame, text="V: --- / I: ---", font=("Consolas", 9))
|
||||
self._ch6_range_label.pack(anchor=tk.W)
|
||||
|
||||
@@ -570,6 +574,15 @@ class TestbenchGUI(tk.Tk):
|
||||
|
||||
self._svi_status = ttk.Label(frame, text="Idle", font=("Consolas", 9))
|
||||
self._svi_status.pack(anchor=tk.W, pady=2)
|
||||
self._svi_estimate = ttk.Label(frame, text="", font=("Consolas", 9))
|
||||
self._svi_estimate.pack(anchor=tk.W)
|
||||
|
||||
# Bind entries to recalculate time estimate on change
|
||||
for entry in (self._svi_v_start, self._svi_v_stop, self._svi_v_step,
|
||||
self._svi_l_start, self._svi_l_stop, self._svi_l_step,
|
||||
self._svi_settle):
|
||||
entry.bind("<KeyRelease>", lambda _e: self._update_svi_estimate())
|
||||
self._update_svi_estimate()
|
||||
|
||||
self._svi_thread = None
|
||||
self._svi_stop_event = None
|
||||
@@ -907,6 +920,37 @@ class TestbenchGUI(tk.Tk):
|
||||
self._svi_l_label.config(text="I start:")
|
||||
else:
|
||||
self._svi_l_label.config(text="P start:")
|
||||
self._update_svi_estimate()
|
||||
|
||||
def _update_svi_estimate(self) -> None:
|
||||
"""Recalculate and display estimated sweep duration."""
|
||||
try:
|
||||
v_start = float(self._svi_v_start.get())
|
||||
v_stop = float(self._svi_v_stop.get())
|
||||
v_step = abs(float(self._svi_v_step.get()))
|
||||
l_start = float(self._svi_l_start.get())
|
||||
l_stop = float(self._svi_l_stop.get())
|
||||
l_step = abs(float(self._svi_l_step.get()))
|
||||
settle = float(self._svi_settle.get())
|
||||
if v_step <= 0 or l_step <= 0:
|
||||
raise ValueError
|
||||
v_count = int(abs(v_stop - v_start) / v_step) + 1
|
||||
l_count = int(abs(l_stop - l_start) / l_step) + 1
|
||||
total = v_count * l_count
|
||||
secs = total * settle
|
||||
mins, s = divmod(int(secs), 60)
|
||||
hrs, m = divmod(mins, 60)
|
||||
if hrs:
|
||||
time_str = f"{hrs}h {m:02d}m {s:02d}s"
|
||||
elif m:
|
||||
time_str = f"{m}m {s:02d}s"
|
||||
else:
|
||||
time_str = f"{s}s"
|
||||
self._svi_estimate.config(
|
||||
text=f"{total} points, ~{time_str} (settle only)"
|
||||
)
|
||||
except (ValueError, ZeroDivisionError):
|
||||
self._svi_estimate.config(text="")
|
||||
|
||||
def _set_range(self, channel: int, vi: str, combo: ttk.Combobox) -> None:
|
||||
"""Set voltage or current range for a HIOKI channel."""
|
||||
@@ -1286,6 +1330,22 @@ class TestbenchGUI(tk.Tk):
|
||||
ll += l_step
|
||||
v += v_step
|
||||
finally:
|
||||
# Ramp load down gradually to avoid sudden transients
|
||||
cur_load = ll - l_step if n > 0 else l_start
|
||||
ramp_steps = max(int(abs(cur_load - l_start) / abs(l_step)), 1) if l_step != 0 else 1
|
||||
ramp_steps = min(ramp_steps, 10) # cap at 10 steps
|
||||
if abs(cur_load) > abs(l_start) and ramp_steps > 1:
|
||||
decrement = (cur_load - l_start) / ramp_steps
|
||||
print(f"Ramping load down from {cur_load:.1f} to "
|
||||
f"{l_start:.1f}{unit} in {ramp_steps} steps...")
|
||||
for i in range(1, ramp_steps + 1):
|
||||
step_val = cur_load - decrement * i
|
||||
bench._apply_load_value(load_mode, step_val)
|
||||
time.sleep(0.5)
|
||||
else:
|
||||
bench._apply_load_value(load_mode, l_start)
|
||||
time.sleep(0.5)
|
||||
|
||||
bench.load.load_off()
|
||||
bench.supply.set_voltage(IDLE_VOLTAGE)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user