diff --git a/README.md b/README.md index d28cd9d..4b1e81e 100644 --- a/README.md +++ b/README.md @@ -97,14 +97,15 @@ The GUI provides: launch auto-starts a log at `logs/data_.csv` (relative to the working directory); use Stop Log / Start Log to switch to a custom path - Console log panel (STM32 fault flags are reported here as they latch) -- PSU capability guard: the HV supply can source at most 20 A - (`PSU_MAX_CURRENT_A` in `gui.py`). Sweep steps whose estimated input draw - `I_in = P_out / (0.90 * V_in)` exceeds that are rejected (skipped and +- Input-current guard: the sweep "I limit" field is the maximum input + current allowed for the test environment (set it to whatever the PSU on + the bench can source; defaults to 20 A, no hardcoded ceiling). It is + programmed as the supply CC limit, and sweep steps whose estimated input + draw `I_in = P_out / (0.90 * V_in)` exceeds it are rejected (skipped and reported per voltage), regardless of the requested step range; a measured backstop additionally drops any point where the supply actually exceeded - the limit and backs the load off. The programmed supply current limit - (sweep "I limit" field and manual supply controls) is clamped to 20 A, and - manual CC/CP load setpoints are checked against live Vin/Vout readings. + the limit and backs the load off. Manual CC/CP load setpoints are checked + against the same limit using live Vin/Vout readings. - 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 6803090..18be2d1 100644 --- a/testbench/gui.py +++ b/testbench/gui.py @@ -39,11 +39,13 @@ TELEM_DECIMATE = 4 # telemetry display decimation (full rate still recorded MIN_REDRAW_S = 0.4 # throttle canvas redraws STM_EMA_TAU_S = 2.0 # EMA time constant for displayed telemetry values -# HV supply capability guard: the PSU can source at most this much current. -# Load steps whose estimated input draw exceeds it are rejected, regardless -# of what the sweep asks for. The estimate is conservative (low assumed -# efficiency) so rejection kicks in before the PSU actually current-limits. -PSU_MAX_CURRENT_A = 20.0 +# Input-current guard: the sweep "I limit" field holds the maximum input +# current allowed for the test environment (whatever PSU is on the bench -- +# no hardcoded ceiling). Load steps whose estimated input draw exceeds it +# are rejected, regardless of what the sweep asks for. The estimate is +# conservative (low assumed efficiency) so rejection kicks in before the +# PSU actually current-limits. +INPUT_LIMIT_DEFAULT_A = 20.0 # initial I limit field value only VOUT_NOM_V = 48.0 # nominal converter output, fallback when unmeasured EFF_ASSUMED = 0.90 # conservative efficiency for input-current estimates @@ -667,7 +669,7 @@ class TestbenchGUI(tk.Tk): row.pack(fill=tk.X, pady=1) ttk.Label(row, text="I limit:", width=10).pack(side=tk.LEFT) self._svi_ilimit = ttk.Entry(row, width=7) - self._svi_ilimit.insert(0, f"{PSU_MAX_CURRENT_A:g}") + self._svi_ilimit.insert(0, f"{INPUT_LIMIT_DEFAULT_A:g}") self._svi_ilimit.pack(side=tk.LEFT, padx=2) ttk.Label(row, text="settle:").pack(side=tk.LEFT) self._svi_settle = ttk.Entry(row, width=5) @@ -1451,24 +1453,17 @@ class TestbenchGUI(tk.Tk): self._send_float(Cmd.SET_VOLTAGE, self._sup_voltage) def _set_supply_current(self) -> None: - self._clamp_supply_current_entry() self._send_float(Cmd.SET_CURRENT, self._sup_current) - def _clamp_supply_current_entry(self) -> None: - """Keep the programmed supply current within PSU capability.""" + def _input_limit_a(self) -> float | None: + """Max input current allowed for the test environment (I limit + field). None when the field does not parse (gate inactive).""" try: - val = float(self._sup_current.get()) + return float(self._svi_ilimit.get()) except ValueError: - return - if val > PSU_MAX_CURRENT_A: - self._console( - f"Supply I {val:g}A clamped to PSU max " - f"{PSU_MAX_CURRENT_A:g}A", "warn") - self._sup_current.delete(0, tk.END) - self._sup_current.insert(0, f"{PSU_MAX_CURRENT_A:g}") + return None def _apply_supply(self) -> None: - self._clamp_supply_current_entry() try: v = float(self._sup_voltage.get()) i = float(self._sup_current.get()) @@ -1490,18 +1485,20 @@ class TestbenchGUI(tk.Tk): self._load_value.config(foreground="red") self.after(1000, lambda: self._load_value.config(foreground="")) return - # PSU capability gate (CC/CP; live readings when available) - if mode in ("CC", "CP"): + # Input-current gate (CC/CP; live readings when available), against + # the user-set I limit for this test environment + imax = self._input_limit_a() + if mode in ("CC", "CP") and imax is not None: vin = self._latest_data.get("supply_V", 0.0) vout = self._latest_data.get("load_V", 0.0) if vout < 5.0: vout = VOUT_NOM_V if vin > 5.0: iin_est = _est_input_current(mode, val, vin, vout) - if iin_est > PSU_MAX_CURRENT_A: + if iin_est > imax: self._console( f"Load {mode}={val:g} rejected: est. input current " - f"{iin_est:.1f}A > PSU max {PSU_MAX_CURRENT_A:g}A " + f"{iin_est:.1f}A > I limit {imax:g}A " f"at Vin={vin:.1f}V / Vout={vout:.1f}V", "error") self._load_value.config(foreground="red") self.after(1000, lambda: self._load_value.config(foreground="")) @@ -1650,14 +1647,6 @@ class TestbenchGUI(tk.Tk): messagebox.showerror("Invalid Input", "Check sweep parameters.") return - if params["current_limit"] > PSU_MAX_CURRENT_A: - self._console( - f"I limit {params['current_limit']:g}A clamped to PSU max " - f"{PSU_MAX_CURRENT_A:g}A", "warn") - params["current_limit"] = PSU_MAX_CURRENT_A - self._svi_ilimit.delete(0, tk.END) - self._svi_ilimit.insert(0, f"{PSU_MAX_CURRENT_A:g}") - # Ask for output file default_name = time.strftime("sweep_vi_%Y%m%d_%H%M%S.csv") path = filedialog.asksaveasfilename( @@ -1808,9 +1797,10 @@ class TestbenchGUI(tk.Tk): "STM32 not linked - thermal pause guard inactive for this " "sweep", "warn") - # PSU capability gate: never command an operating point whose - # estimated input draw exceeds what the supply can source. - psu_imax = min(current_limit, PSU_MAX_CURRENT_A) + # Input-current gate: the I limit field IS the maximum input current + # allowed for this test environment -- never command an operating + # point whose estimated input draw exceeds it. + psu_imax = current_limit vout_est = VOUT_NOM_V # refined from measured load voltage as we go first_v = v_start if v_step > 0 else max(v_start, v_stop) if _est_input_current(load_mode, l_start, first_v, vout_est) > psu_imax: @@ -1967,8 +1957,8 @@ class TestbenchGUI(tk.Tk): ll += l_step if rejected: self._console( - f"V={v:.1f}V: rejected {rejected} step(s) - PSU " - f"input limit {psu_imax:g}A / load range", "warn") + f"V={v:.1f}V: rejected {rejected} step(s) - input " + f"limit {psu_imax:g}A / load range", "warn") v += v_step finally: # Ramp load down gradually to avoid sudden transients