Add STM32 tuning integration and rewrite README with step-by-step guide
- stm32_link.py: synchronous serial interface to STM32 debug protocol (ping, telemetry read/avg, param read/write, frame parser) - tuner.py: automated tuning combining HIOKI + STM32 measurements (param sweep, deadtime optimization, multi-point sweep, CSV/plot output) - CLI commands: stm32-read, stm32-write, tune-param, tune-deadtime - README: complete step-by-step guide covering setup, sweeps, analysis, tuning, shade profiles, debug console, and parameter reference Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
168
testbench/cli.py
168
testbench/cli.py
@@ -550,6 +550,124 @@ def cmd_safe_off(bench: MPPTTestbench, _args: argparse.Namespace) -> None:
|
||||
print("Done.")
|
||||
|
||||
|
||||
# ── Tuning commands (instruments + STM32) ────────────────────────────
|
||||
|
||||
|
||||
def cmd_stm32_read(bench: MPPTTestbench, args: argparse.Namespace) -> None:
|
||||
"""Read all STM32 parameters and telemetry."""
|
||||
from testbench.stm32_link import STM32Link
|
||||
|
||||
with STM32Link(args.stm32_port, args.stm32_baud) as link:
|
||||
if not link.ping():
|
||||
print("STM32 not responding to ping!")
|
||||
return
|
||||
|
||||
print("STM32 connected.\n")
|
||||
|
||||
# Read params
|
||||
params = link.read_all_params()
|
||||
if params:
|
||||
print("Parameters:")
|
||||
for name, val in sorted(params.items()):
|
||||
print(f" {name:<20s} = {val}")
|
||||
print()
|
||||
|
||||
# Read telemetry
|
||||
t = link.read_telemetry_avg(n=20)
|
||||
if t:
|
||||
print("Telemetry (20-sample avg):")
|
||||
print(f" Vin = {t.vin_V:8.2f} V")
|
||||
print(f" Vout = {t.vout_V:8.2f} V")
|
||||
print(f" Iin = {t.iin_A:8.2f} A")
|
||||
print(f" Iout = {t.iout_A:8.2f} A")
|
||||
print(f" Pin = {t.power_in_W:8.2f} W")
|
||||
print(f" Pout = {t.power_out_W:8.2f} W")
|
||||
print(f" EFF = {t.efficiency:8.1f} %")
|
||||
print(f" Vfly = {t.vfly/1000:8.2f} V")
|
||||
print(f" Temp = {t.etemp:8.1f} °C")
|
||||
|
||||
|
||||
def cmd_stm32_write(bench: MPPTTestbench, args: argparse.Namespace) -> None:
|
||||
"""Write a parameter to the STM32."""
|
||||
from testbench.stm32_link import STM32Link
|
||||
|
||||
with STM32Link(args.stm32_port, args.stm32_baud) as link:
|
||||
if not link.ping():
|
||||
print("STM32 not responding to ping!")
|
||||
return
|
||||
ack = link.write_param(args.param, args.value)
|
||||
print(f"{args.param} = {args.value} {'ACK' if ack else 'NO ACK'}")
|
||||
|
||||
|
||||
def cmd_tune_param(bench: MPPTTestbench, args: argparse.Namespace) -> None:
|
||||
"""Sweep an STM32 parameter while measuring efficiency."""
|
||||
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)
|
||||
results = tuner.sweep_param(
|
||||
param_name=args.param,
|
||||
start=args.start,
|
||||
stop=args.stop,
|
||||
step=args.step,
|
||||
voltage=args.voltage,
|
||||
current_limit=args.current_limit,
|
||||
load_mode=args.load_mode,
|
||||
load_value=args.load_value,
|
||||
settle_time=args.settle,
|
||||
)
|
||||
|
||||
tuner.print_results(results)
|
||||
|
||||
if args.output:
|
||||
tuner.write_csv(results, args.output)
|
||||
|
||||
if not args.no_plot and results:
|
||||
tuner.plot_sweep(results, show=True)
|
||||
|
||||
|
||||
def cmd_tune_deadtime(bench: MPPTTestbench, args: argparse.Namespace) -> None:
|
||||
"""Optimize deadtime for each current bracket."""
|
||||
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)
|
||||
|
||||
load_values = None
|
||||
if args.load_values:
|
||||
load_values = [float(x) for x in args.load_values.split(",")]
|
||||
|
||||
results = tuner.tune_deadtime(
|
||||
dt_start=args.dt_start,
|
||||
dt_stop=args.dt_stop,
|
||||
dt_step=args.dt_step,
|
||||
voltage=args.voltage,
|
||||
current_limit=args.current_limit,
|
||||
load_mode=args.load_mode,
|
||||
load_values=load_values,
|
||||
settle_time=args.settle,
|
||||
)
|
||||
|
||||
if args.apply:
|
||||
tuner.apply_best_deadtimes(results)
|
||||
|
||||
if args.output:
|
||||
all_pts = []
|
||||
for pts in results.values():
|
||||
all_pts.extend(pts)
|
||||
tuner.write_csv(all_pts, args.output)
|
||||
|
||||
|
||||
# ── Offline plot command (no instruments needed) ─────────────────────
|
||||
|
||||
|
||||
@@ -726,6 +844,9 @@ examples:
|
||||
%(prog)s safe-off
|
||||
%(prog)s plot-sweep sweep_vi_20260312_151212.csv
|
||||
%(prog)s plot-sweep sweep_vi_20260312_151212.csv --no-show -o plots/
|
||||
%(prog)s stm32-read --stm32-port COM28
|
||||
%(prog)s tune-param --stm32-port COM28 --param dt_10_20A --start 14 --stop 40 --step 1 --voltage 60 --current-limit 20 --load-mode CP --load-value 300
|
||||
%(prog)s tune-deadtime --stm32-port COM28 --voltage 60 --current-limit 20 --load-mode CP --apply
|
||||
""",
|
||||
)
|
||||
|
||||
@@ -750,6 +871,14 @@ examples:
|
||||
"--timeout", type=int, default=5000,
|
||||
help="VISA timeout in ms (default: 5000)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stm32-port", default="COM28",
|
||||
help="STM32 debug serial port (default: COM28)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stm32-baud", type=int, default=460800,
|
||||
help="STM32 debug baud rate (default: 460800)",
|
||||
)
|
||||
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
@@ -845,6 +974,41 @@ examples:
|
||||
# safe-off
|
||||
sub.add_parser("safe-off", help="Emergency: turn off load and supply")
|
||||
|
||||
# stm32-read
|
||||
sub.add_parser("stm32-read", help="Read STM32 parameters and telemetry")
|
||||
|
||||
# stm32-write
|
||||
p_sw = sub.add_parser("stm32-write", help="Write a parameter to the STM32")
|
||||
p_sw.add_argument("--param", required=True, help="Parameter name")
|
||||
p_sw.add_argument("--value", type=float, required=True, help="Value to write")
|
||||
|
||||
# tune-param
|
||||
p_tp = sub.add_parser("tune-param", help="Sweep an STM32 parameter while measuring efficiency")
|
||||
p_tp.add_argument("--param", required=True, help="Parameter name (e.g. dt_10_20A, vfly_kp)")
|
||||
p_tp.add_argument("--start", type=float, required=True, help="Start value")
|
||||
p_tp.add_argument("--stop", type=float, required=True, help="Stop value")
|
||||
p_tp.add_argument("--step", type=float, required=True, help="Step size")
|
||||
p_tp.add_argument("--voltage", type=float, required=True, help="Supply voltage (V)")
|
||||
p_tp.add_argument("--current-limit", type=float, required=True, help="Supply current limit (A)")
|
||||
p_tp.add_argument("--load-mode", choices=["CC", "CP"], default="CP", help="Load mode")
|
||||
p_tp.add_argument("--load-value", type=float, default=200.0, help="Load setpoint (A or W)")
|
||||
p_tp.add_argument("--settle", type=float, default=3.0, help="Settle time per step (s)")
|
||||
p_tp.add_argument("--no-plot", action="store_true", help="Skip plot")
|
||||
p_tp.add_argument("-o", "--output", help="CSV output file")
|
||||
|
||||
# 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)")
|
||||
p_td.add_argument("--dt-stop", type=int, default=50, help="Max deadtime ticks (default: 50)")
|
||||
p_td.add_argument("--dt-step", type=int, default=1, help="Deadtime step (default: 1)")
|
||||
p_td.add_argument("--voltage", type=float, default=60.0, help="Supply voltage (V)")
|
||||
p_td.add_argument("--current-limit", type=float, default=20.0, help="Supply current limit (A)")
|
||||
p_td.add_argument("--load-mode", choices=["CC", "CP"], default="CP", help="Load mode")
|
||||
p_td.add_argument("--load-values", help="Comma-separated load values per bracket (e.g. 20,50,100,250,400,600)")
|
||||
p_td.add_argument("--settle", type=float, default=3.0, help="Settle time per step (s)")
|
||||
p_td.add_argument("--apply", action="store_true", help="Apply best deadtimes after sweep")
|
||||
p_td.add_argument("-o", "--output", help="CSV output file")
|
||||
|
||||
# plot-sweep (offline, no instruments)
|
||||
p_plot = sub.add_parser("plot-sweep", help="Plot efficiency analysis from sweep CSV (no instruments needed)")
|
||||
p_plot.add_argument("csv", help="Sweep CSV file to plot")
|
||||
@@ -872,6 +1036,10 @@ examples:
|
||||
"supply": cmd_supply,
|
||||
"load": cmd_load,
|
||||
"safe-off": cmd_safe_off,
|
||||
"stm32-read": cmd_stm32_read,
|
||||
"stm32-write": cmd_stm32_write,
|
||||
"tune-param": cmd_tune_param,
|
||||
"tune-deadtime": cmd_tune_deadtime,
|
||||
}
|
||||
|
||||
bench = connect_bench(args)
|
||||
|
||||
Reference in New Issue
Block a user