From 903fa785854f2e7e0f6c25c62a559e817dd4ef09 Mon Sep 17 00:00:00 2001 From: grabowski Date: Fri, 13 Mar 2026 15:51:59 +0700 Subject: [PATCH] Add tune-param-vsweep command for parameter sweeps across voltage range Automates running tune-param at multiple voltages, saves per-voltage CSVs compatible with plot-tune, and prints a summary table. Co-Authored-By: Claude Opus 4.6 --- testbench/cli.py | 98 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/testbench/cli.py b/testbench/cli.py index a6e9fd3..3d6abb6 100644 --- a/testbench/cli.py +++ b/testbench/cli.py @@ -631,6 +631,88 @@ def cmd_tune_param(bench: MPPTTestbench, args: argparse.Namespace) -> None: tuner.plot_sweep(results, show=True) +def cmd_tune_param_vsweep(bench: MPPTTestbench, args: argparse.Namespace) -> None: + """Sweep an STM32 parameter across a range of voltages.""" + from testbench.stm32_link import STM32Link + from testbench.tuner import Tuner + + with STM32Link(args.stm32_port, args.stm32_baud) as link: + if not link.ping(): + print("STM32 not responding!") + return + + tuner = Tuner(bench, link, settle_time=args.settle) + + # Build voltage list + v = args.v_start + voltages = [] + while v <= args.v_stop + args.v_step / 2: + voltages.append(v) + v += args.v_step + + n_param_steps = int(abs(args.stop - args.start) / abs(args.step)) + 1 + total = n_param_steps * len(voltages) + est_s = total * args.settle + len(voltages) * args.settle * 2 # settle + initial settle per voltage + est_m = est_s / 60 + + print(f"Voltage sweep: {args.param} across {len(voltages)} voltages") + print(f" Voltages: {', '.join(f'{v:.0f}V' for v in voltages)}") + print(f" Param range: {args.start} -> {args.stop} (step {args.step})") + print(f" {n_param_steps} param steps x {len(voltages)} voltages = {total} points") + print(f" Estimated time: {est_m:.1f} min") + print() + + # Determine output prefix + prefix = args.output or args.param + + all_results: list = [] + csv_files: list[str] = [] + + for vi, voltage in enumerate(voltages): + print(f"\n{'='*60}") + print(f" Voltage {vi+1}/{len(voltages)}: {voltage:.0f}V") + print(f"{'='*60}") + + results = tuner.sweep_param( + param_name=args.param, + start=args.start, + stop=args.stop, + step=args.step, + voltage=voltage, + current_limit=args.current_limit, + load_mode=args.load_mode, + load_value=args.load_value, + settle_time=args.settle, + ) + all_results.extend(results) + + # Save per-voltage CSV + csv_name = f"{prefix}_{voltage:.0f}V.csv" + tuner.write_csv(results, csv_name) + csv_files.append(csv_name) + + # Summary + print(f"\n{'='*60}") + print("VOLTAGE SWEEP SUMMARY") + print(f"{'Voltage':>8} {'Best':>8} {'Efficiency':>12} {'Temp':>6}") + print("-" * 40) + for voltage in voltages: + pts = [p for p in all_results + if abs(p.voltage_set - voltage) < 0.5 and 0 < p.meter_eff < 110] + if pts: + best = max(pts, key=lambda p: p.meter_eff) + print(f"{voltage:>7.0f}V {best.param_value:>8.1f} " + f"{best.meter_eff:>11.2f}% {best.stm_etemp:>5.0f}C") + else: + print(f"{voltage:>7.0f}V {'N/A':>8} {'N/A':>12} {'N/A':>6}") + print(f"{'='*60}") + + print(f"\nSaved {len(csv_files)} CSV files:") + for f in csv_files: + print(f" {f}") + print(f"\nRun: uv run bench plot-tune {' '.join(csv_files)}") + + def cmd_tune_deadtime(bench: MPPTTestbench, args: argparse.Namespace) -> None: """Optimize deadtime for each current bracket.""" from testbench.stm32_link import STM32Link @@ -1193,6 +1275,21 @@ examples: p_tp.add_argument("--no-plot", action="store_true", help="Skip plot") p_tp.add_argument("-o", "--output", help="CSV output file") + # tune-param-vsweep + p_tv = sub.add_parser("tune-param-vsweep", help="Sweep an STM32 parameter across a voltage range") + p_tv.add_argument("--param", required=True, help="Parameter name (e.g. dt_10_20A, vfly_kp)") + p_tv.add_argument("--start", type=float, required=True, help="Param start value") + p_tv.add_argument("--stop", type=float, required=True, help="Param stop value") + p_tv.add_argument("--step", type=float, required=True, help="Param step size") + p_tv.add_argument("--v-start", type=float, required=True, help="Start voltage (V)") + p_tv.add_argument("--v-stop", type=float, required=True, help="Stop voltage (V)") + p_tv.add_argument("--v-step", type=float, required=True, help="Voltage step (V)") + p_tv.add_argument("--current-limit", type=float, required=True, help="Supply current limit (A)") + p_tv.add_argument("--load-mode", choices=["CC", "CP"], default="CP", help="Load mode") + p_tv.add_argument("--load-value", type=float, default=200.0, help="Load setpoint (A or W)") + p_tv.add_argument("--settle", type=float, default=3.0, help="Settle time per step (s)") + p_tv.add_argument("-o", "--output", help="Output prefix for CSVs (default: param name)") + # tune-deadtime p_td = sub.add_parser("tune-deadtime", help="Optimize deadtime for each current bracket") p_td.add_argument("--dt-start", type=int, default=14, help="Min deadtime ticks (default: 14)") @@ -1245,6 +1342,7 @@ examples: "stm32-read": cmd_stm32_read, "stm32-write": cmd_stm32_write, "tune-param": cmd_tune_param, + "tune-param-vsweep": cmd_tune_param_vsweep, "tune-deadtime": cmd_tune_deadtime, }