From 5b168439e8a3d288cac00e60c6c5700dfb47a951 Mon Sep 17 00:00:00 2001 From: janik Date: Mon, 6 Jul 2026 14:49:07 +0700 Subject: [PATCH] GUI sweep: supply-trip protection after the 80V OVP shutdown Run 2026-07-06 13:08: stepping 78->80V released the 46A load mid-ramp, the supply output overshot to 80.46V and a protection (OVP by all log evidence) shut it down; the sweep then logged garbage rows for the rest of the grid. Three fixes: - release the load down to l_start BEFORE each supply voltage step (down-steps only; descending load sweeps keep the gated path) - OVP preflight: query the programmed OVP level at sweep start, abort under 2V margin above the sweep max, warn under 5V - dead-supply abort: supply readback ~0V mid-sweep drops the garbage point, queries which protection fired (OVP/OV/OC/OP/OT) via _supply_trip_cause, reports it, and stops -- collected points are still ramped down and saved Co-Authored-By: Claude Fable 5 --- README.md | 10 ++++++++ testbench/gui.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) diff --git a/README.md b/README.md index 4b1e81e..25a9622 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,16 @@ The GUI provides: backstop additionally drops any point where the supply actually exceeded the limit and backs the load off. Manual CC/CP load setpoints are checked against the same limit using live Vin/Vout readings. +- Supply-trip protection: before each voltage step the load is released + back to I start first, so a heavy load release never lands mid-ramp + (that overshoots the setpoint -- a 46 A release ramping to 80 V peaked + at 80.46 V and tripped the supply OVP). At sweep start the supply's + programmed OVP level is checked against the sweep maximum (abort under + 2 V margin, warn under 5 V). If the supply reads ~0 V mid-sweep (a + protection shut the output down), the sweep aborts immediately, reports + which protection fired (OVP / OV / OC / OP / OT), and saves the points + collected so far instead of logging garbage rows for the rest of the + grid. - Load range pinning: a mid-sweep auto-range transition on the Prodigit momentarily unloads the converter, so at sweep start the CC range is pinned to Range II for the whole run (auto-ranging restored after, with diff --git a/testbench/gui.py b/testbench/gui.py index 18be2d1..8b8e041 100644 --- a/testbench/gui.py +++ b/testbench/gui.py @@ -1715,6 +1715,22 @@ class TestbenchGUI(tk.Tk): return None return b.etemp, b.btemp + @staticmethod + def _supply_trip_cause(bench) -> str: + """Best-effort query of which supply protection fired.""" + causes = [] + try: + if bench.supply.get_ovp_tripped(): + causes.append("OVP") + except Exception: + pass + try: + qs = bench.supply.get_questionable_status() + causes += [k for k, hit in qs.items() if hit] + except Exception: + pass + return "/".join(dict.fromkeys(causes)) or "unknown" + def _thermal_hold(self, bench, load_mode: str, vout_est: float, stop: threading.Event) -> float | None: """Pause the sweep while etemp/btemp are near the firmware trips. @@ -1792,6 +1808,26 @@ class TestbenchGUI(tk.Tk): max_load_setpoint=max_l, ) + # OVP preflight: a sweep whose top voltage sits at/above the + # supply's programmed OVP is a guaranteed trip, and transient + # overshoot eats into the margin (a 46A load release mid-ramp + # overshot the setpoint by ~0.5V and tripped OVP, run 2026-07-06). + try: + ovp = (bench.supply.get_ovp_level() + if bench.supply.get_ovp_state() else None) + except Exception as e: + self._console(f"OVP preflight skipped ({e})", "warn") + ovp = None + if ovp is not None: + if ovp < max_v + 2.0: + raise ValueError( + f"supply OVP {ovp:g}V leaves no margin above the sweep " + f"max {max_v:g}V - raise OVP or lower V stop") + if ovp < max_v + 5.0: + self._console( + f"Supply OVP {ovp:g}V is within 5V of the sweep max " + f"{max_v:g}V - transients may trip it", "warn") + if self._sweep_temps() is None: self._console( "STM32 not linked - thermal pause guard inactive for this " @@ -1861,6 +1897,18 @@ class TestbenchGUI(tk.Tk): if v_step < 0 and v < v_stop + v_step / 2: break + # Release the load down to l_start BEFORE stepping the + # supply voltage: a heavy load release landing mid-ramp + # overshoots the setpoint (46A release while ramping to + # 80V peaked at 80.46V and tripped the supply, run + # 2026-07-06). Down-steps only -- for descending load + # sweeps l_start is the heavy end and gets applied after + # the voltage step through the usual feasibility gate. + if abs(applied) > abs(l_start): + bench._apply_load_value(load_mode, l_start) + applied = l_start + time.sleep(0.5) + bench.supply.set_voltage(v) ll = l_start rejected = 0 @@ -1896,6 +1944,21 @@ class TestbenchGUI(tk.Tk): point = bench._record_point(v, current_limit, load_setpoint=ll) results.append(point) n += 1 + # Dead-supply abort: ~0V readback with the output + # supposed to be ON means a protection shut it down. + # Report which one and stop instead of logging garbage + # rows for the rest of the grid (collected points are + # still saved). + if v > 5.0 and point.supply_voltage < 1.0: + results.pop() # this point is garbage + n -= 1 + cause = self._supply_trip_cause(bench) + self._console( + f"Supply output is OFF at V={v:g}V (protection: " + f"{cause}) - aborting sweep, saving " + f"{n} collected point(s)", "error") + stop.set() + break if point.load_voltage > 5.0: vout_est = point.load_voltage # Measured backstop: the estimate can be off (eff, Vout)