Add CP load mode to 2D sweep and supply sanity checks
- 2D sweep now supports CC (constant current) and CP (constant power) load modes via --load-mode flag (CLI) and combobox (GUI) - Supply capability check before all sweeps: validates max voltage against supply range and prints V/I/P summary - Renamed sweep-vi args from --i-start/stop/step to --l-start/stop/step to reflect that the load setpoint can be current or power - GUI labels update dynamically based on selected load mode Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+73
-27
@@ -316,6 +316,9 @@ class MPPTTestbench:
|
||||
if v_step == 0:
|
||||
raise ValueError("v_step cannot be zero")
|
||||
|
||||
max_v = max(abs(v_start), abs(v_stop))
|
||||
self.check_supply_capability(max_v, current_limit)
|
||||
|
||||
# Auto-correct step direction
|
||||
if v_start < v_stop and v_step < 0:
|
||||
v_step = -v_step
|
||||
@@ -378,6 +381,8 @@ class MPPTTestbench:
|
||||
if i_step == 0:
|
||||
raise ValueError("i_step cannot be zero")
|
||||
|
||||
self.check_supply_capability(voltage, current_limit)
|
||||
|
||||
# Auto-correct step direction
|
||||
if i_start < i_stop and i_step < 0:
|
||||
i_step = -i_step
|
||||
@@ -553,6 +558,33 @@ class MPPTTestbench:
|
||||
|
||||
return results
|
||||
|
||||
# ── Sanity checks ────────────────────────────────────────────────
|
||||
|
||||
def check_supply_capability(
|
||||
self,
|
||||
max_voltage: float,
|
||||
current_limit: float,
|
||||
) -> None:
|
||||
"""Check that the supply can deliver the requested V and I.
|
||||
|
||||
Queries the supply's voltage range (max V) and verifies the
|
||||
requested parameters are within bounds. Raises ValueError if not.
|
||||
"""
|
||||
supply_max_v = self.supply.get_voltage_range()
|
||||
if max_voltage > supply_max_v:
|
||||
raise ValueError(
|
||||
f"Requested voltage {max_voltage:.1f}V exceeds supply "
|
||||
f"range {supply_max_v:.1f}V"
|
||||
)
|
||||
|
||||
max_power = max_voltage * current_limit
|
||||
print(
|
||||
f" Supply check: V_max={max_voltage:.1f}V, "
|
||||
f"I_limit={current_limit:.1f}A, "
|
||||
f"P_max={max_power:.0f}W "
|
||||
f"(range {supply_max_v:.0f}V)"
|
||||
)
|
||||
|
||||
# ── 2D Voltage × Current Sweep ─────────────────────────────────────
|
||||
|
||||
def sweep_vi(
|
||||
@@ -560,16 +592,17 @@ class MPPTTestbench:
|
||||
v_start: float,
|
||||
v_stop: float,
|
||||
v_step: float,
|
||||
i_start: float,
|
||||
i_stop: float,
|
||||
i_step: float,
|
||||
l_start: float,
|
||||
l_stop: float,
|
||||
l_step: float,
|
||||
current_limit: float,
|
||||
settle_time: float = 2.0,
|
||||
load_mode: str = "CC",
|
||||
) -> list[SweepPoint]:
|
||||
"""2D sweep: voltage (outer) × load current (inner).
|
||||
"""2D sweep: voltage (outer) × load setpoint (inner).
|
||||
|
||||
At each supply voltage, sweeps the load current through the full
|
||||
range, recording efficiency at every (V, I) combination. Produces
|
||||
At each supply voltage, sweeps the load through the full
|
||||
range, recording efficiency at every combination. Produces
|
||||
a complete efficiency map of the DUT.
|
||||
|
||||
After the sweep the load turns OFF and the supply returns to
|
||||
@@ -579,16 +612,25 @@ class MPPTTestbench:
|
||||
v_start: Starting supply voltage (V).
|
||||
v_stop: Final supply voltage (V).
|
||||
v_step: Voltage step size (V). Sign is auto-corrected.
|
||||
i_start: Starting load current (A).
|
||||
i_stop: Final load current (A).
|
||||
i_step: Current step size (A). Sign is auto-corrected.
|
||||
l_start: Starting load setpoint (A for CC, W for CP).
|
||||
l_stop: Final load setpoint.
|
||||
l_step: Load step size. Sign is auto-corrected.
|
||||
current_limit: Supply current limit (A) for the entire sweep.
|
||||
settle_time: Seconds to wait after each setpoint change.
|
||||
load_mode: Load mode — "CC" (constant current) or "CP"
|
||||
(constant power).
|
||||
"""
|
||||
if v_step == 0:
|
||||
raise ValueError("v_step cannot be zero")
|
||||
if i_step == 0:
|
||||
raise ValueError("i_step cannot be zero")
|
||||
if l_step == 0:
|
||||
raise ValueError("l_step cannot be zero")
|
||||
load_mode = load_mode.upper()
|
||||
if load_mode not in ("CC", "CP"):
|
||||
raise ValueError(f"load_mode must be CC or CP, got {load_mode!r}")
|
||||
|
||||
# Sanity check
|
||||
max_v = max(abs(v_start), abs(v_stop))
|
||||
self.check_supply_capability(max_v, current_limit)
|
||||
|
||||
# Auto-correct step directions
|
||||
if v_start < v_stop and v_step < 0:
|
||||
@@ -596,21 +638,25 @@ class MPPTTestbench:
|
||||
elif v_start > v_stop and v_step > 0:
|
||||
v_step = -v_step
|
||||
|
||||
if i_start < i_stop and i_step < 0:
|
||||
i_step = -i_step
|
||||
elif i_start > i_stop and i_step > 0:
|
||||
i_step = -i_step
|
||||
if l_start < l_stop and l_step < 0:
|
||||
l_step = -l_step
|
||||
elif l_start > l_stop and l_step > 0:
|
||||
l_step = -l_step
|
||||
|
||||
# Count steps for progress display
|
||||
v_count = int(abs(v_stop - v_start) / abs(v_step)) + 1
|
||||
i_count = int(abs(i_stop - i_start) / abs(i_step)) + 1
|
||||
total = v_count * i_count
|
||||
print(f" Grid: {v_count} voltage × {i_count} current = {total} points")
|
||||
l_count = int(abs(l_stop - l_start) / abs(l_step)) + 1
|
||||
total = v_count * l_count
|
||||
unit = "A" if load_mode == "CC" else "W"
|
||||
print(
|
||||
f" Grid: {v_count} voltage × {l_count} {load_mode} "
|
||||
f"= {total} points"
|
||||
)
|
||||
|
||||
self.supply.set_current(current_limit)
|
||||
self.supply.output_on()
|
||||
self.load.set_mode("CC")
|
||||
self.load.set_cc_current(i_start)
|
||||
self.load.set_mode(load_mode)
|
||||
self._apply_load_value(load_mode, l_start)
|
||||
self.load.load_on()
|
||||
|
||||
results: list[SweepPoint] = []
|
||||
@@ -625,30 +671,30 @@ class MPPTTestbench:
|
||||
break
|
||||
|
||||
self.supply.set_voltage(v)
|
||||
i = i_start
|
||||
ll = l_start
|
||||
|
||||
while True:
|
||||
if i_step > 0 and i > i_stop + i_step / 2:
|
||||
if l_step > 0 and ll > l_stop + l_step / 2:
|
||||
break
|
||||
if i_step < 0 and i < i_stop + i_step / 2:
|
||||
if l_step < 0 and ll < l_stop + l_step / 2:
|
||||
break
|
||||
|
||||
self.load.set_cc_current(i)
|
||||
self._apply_load_value(load_mode, ll)
|
||||
time.sleep(settle_time)
|
||||
|
||||
point = self._record_point(v, current_limit, load_setpoint=i)
|
||||
point = self._record_point(v, current_limit, load_setpoint=ll)
|
||||
results.append(point)
|
||||
n += 1
|
||||
|
||||
print(
|
||||
f" [{n:>4d}/{total}] "
|
||||
f"V={v:6.1f}V I_load={i:6.2f}A "
|
||||
f"V={v:6.1f}V {load_mode}={ll:6.2f}{unit} "
|
||||
f"P_in={point.input_power:8.2f}W "
|
||||
f"P_out={point.output_power:8.2f}W "
|
||||
f"EFF={point.efficiency:6.2f}%"
|
||||
)
|
||||
|
||||
i += i_step
|
||||
ll += l_step
|
||||
|
||||
v += v_step
|
||||
|
||||
|
||||
Reference in New Issue
Block a user