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 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,16 @@ The GUI provides:
|
|||||||
backstop additionally drops any point where the supply actually exceeded
|
backstop additionally drops any point where the supply actually exceeded
|
||||||
the limit and backs the load off. Manual CC/CP load setpoints are checked
|
the limit and backs the load off. Manual CC/CP load setpoints are checked
|
||||||
against the same limit using live Vin/Vout readings.
|
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
|
- Load range pinning: a mid-sweep auto-range transition on the Prodigit
|
||||||
momentarily unloads the converter, so at sweep start the CC range is
|
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
|
pinned to Range II for the whole run (auto-ranging restored after, with
|
||||||
|
|||||||
@@ -1715,6 +1715,22 @@ class TestbenchGUI(tk.Tk):
|
|||||||
return None
|
return None
|
||||||
return b.etemp, b.btemp
|
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,
|
def _thermal_hold(self, bench, load_mode: str, vout_est: float,
|
||||||
stop: threading.Event) -> float | None:
|
stop: threading.Event) -> float | None:
|
||||||
"""Pause the sweep while etemp/btemp are near the firmware trips.
|
"""Pause the sweep while etemp/btemp are near the firmware trips.
|
||||||
@@ -1792,6 +1808,26 @@ class TestbenchGUI(tk.Tk):
|
|||||||
max_load_setpoint=max_l,
|
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:
|
if self._sweep_temps() is None:
|
||||||
self._console(
|
self._console(
|
||||||
"STM32 not linked - thermal pause guard inactive for this "
|
"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:
|
if v_step < 0 and v < v_stop + v_step / 2:
|
||||||
break
|
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)
|
bench.supply.set_voltage(v)
|
||||||
ll = l_start
|
ll = l_start
|
||||||
rejected = 0
|
rejected = 0
|
||||||
@@ -1896,6 +1944,21 @@ class TestbenchGUI(tk.Tk):
|
|||||||
point = bench._record_point(v, current_limit, load_setpoint=ll)
|
point = bench._record_point(v, current_limit, load_setpoint=ll)
|
||||||
results.append(point)
|
results.append(point)
|
||||||
n += 1
|
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:
|
if point.load_voltage > 5.0:
|
||||||
vout_est = point.load_voltage
|
vout_est = point.load_voltage
|
||||||
# Measured backstop: the estimate can be off (eff, Vout)
|
# Measured backstop: the estimate can be off (eff, Vout)
|
||||||
|
|||||||
Reference in New Issue
Block a user