# MPPT Testbench

Unified tool for testing and tuning MPPT (Maximum Power Point Tracking) converters. Combines three bench instruments with direct STM32 firmware access for closed-loop parameter optimization.

| Instrument | Role | Interface |
|---|---|---|
| **ITECH IT6537D** | DC power supply (solar panel simulator, 80V/120A/6kW) | USB-TMC / SCPI via PyVISA |
| **Prodigit 3366G** | DC electronic load (600V/420A/6kW) | RS-232 (115200 8N1 RTS/CTS) |
| **HIOKI 3193-10** | Power analyzer (efficiency measurement) | GPIB via UsbGpib / PyVISA |
| **STM32G474** | Converter firmware (LVSolarBuck64) | Serial 460800 baud (debug protocol) |

## Wiring

```
                        +------------------+
  IT6500D  --(+/-)-->   |   MPPT Tracker   |  --(+/-)-->  Prodigit 3366G
                        |      (DUT)       |
  HIOKI Ch5 --(sense)-- |  Input    Output | --(sense)--  HIOKI Ch6
                        +------------------+
                              |
                          STM32 debug
                          serial (COM4)

  HIOKI EFF1 = P6 / P5 x 100%  (output power / input power)
```

## Installation

Requires Python 3.12+, NI-VISA runtime, and [uv](https://docs.astral.sh/uv/).

```bash
git clone --recurse-submodules https://git.b4l.co.th/B4L/mppt-testbench.git
cd mppt-testbench
uv sync
```

The debug console (inside `code64/`) has its own environment:

```bash
cd code64
uv sync
```

## Step-by-Step Guide

### 1. Connect and verify instruments

```bash
# Check all bench instruments respond
uv run bench identify

# Check STM32 responds (reads params + telemetry)
uv run bench stm32-read --stm32-port COM4
```

### 2. Configure instruments for MPPT testing

```bash
uv run bench setup
```

This sets wiring mode (1P2W), DC coupling, auto-ranging, efficiency formula (EFF1 = P6/P5), and display layout on the HIOKI.

### 3. Basic measurements

```bash
# Single reading from all instruments
uv run bench measure

# Continuous text monitoring with CSV export
uv run bench monitor --interval 1.0 --output data.csv
```

### 4. Launch the GUI

```bash
uv run bench-gui
```

The GUI provides:
- Real-time readouts from all three instruments
- Supply voltage/current control with ON/OFF indicators
- Load mode (CC/CR/CV/CP) and setpoint control
- HIOKI channel range selectors + degauss buttons
- Meter format selector (scientific/normal)
- 2D sweep panel with time estimate
- Live-updating power, efficiency, voltage, and current plots plus STM32
  Vfly and vfly_correction/vfly_ofs_applied plots (selectable) — all graphs
  show the last 20 seconds
- STM32 telemetry panel ("Link STM32", COM4 @ 460800 8-O-1): every field of
  the 100 Hz broadcast (V/I/P + net efficiency, temps, Vfly group, control
  mode, HRTIM compare registers, status flags, fault registers, param echo),
  with Ping and Clear Flags buttons; auto-reconnects on serial errors
- Data logging: instrument rows (with the latest STM32 snapshot merged in as
  `stm_*` columns) to the chosen CSV, plus the full-rate 100 Hz telemetry
  stream to `<name>_telem.csv` alongside. Logging is ON by default: every
  launch auto-starts a log at `logs/data_<timestamp>.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
  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.
- 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
  the load off). The reachable maximum of the selected range is verified
  empirically -- the sweep max is programmed with the load off and read
  back; if the readback comes back clamped, steps above it are rejected
  instead of silently clamped, and a sweep that fits nothing aborts up
  front.
- Thermal sweep guard (needs the STM32 link): before every sweep step the
  heatsink/board temperatures are checked against the firmware trip limits
  (60 C / 80 C). At 57 C / 77 C the sweep pauses, holds the load at 1 A
  (CC; ~1 A worth of W in CP), and waits until both temps drop 5 C below
  the pause thresholds, then resumes at the same step -- no points are lost.
  If the STM32 link is down the sweep still runs, with a console warning
  that the guard is inactive.

### 5. Run efficiency sweeps

#### Voltage sweep (1D)

```bash
uv run bench sweep \
  --v-start 30 --v-stop 100 --v-step 5 \
  --current-limit 20 \
  --load-mode CP --load-value 200 \
  --settle 2.0 -o voltage_sweep.csv
```

#### Load sweep at fixed voltage (1D)

```bash
uv run bench sweep-load \
  --voltage 60 --current-limit 20 \
  --i-start 0.5 --i-stop 15 --i-step 0.5 \
  --settle 2.0 -o load_sweep.csv
```

#### 2D voltage x load sweep (efficiency map)

```bash
# Constant Power mode
uv run bench sweep-vi \
  --v-start 60 --v-stop 100 --v-step 5 \
  --l-start 50 --l-stop 500 --l-step 50 \
  --load-mode CP --current-limit 20 \
  --settle 2.0 -o map_cp.csv

# Constant Current mode
uv run bench sweep-vi \
  --v-start 35 --v-stop 100 --v-step 5 \
  --l-start 0.5 --l-stop 15 --l-step 0.5 \
  --load-mode CC --current-limit 20 \
  --settle 2.0 -o map_cc.csv
```

### 6. Analyze sweep results

```bash
# Generate efficiency overlay, heatmap, and power loss plots (no instruments needed)
uv run bench plot-sweep map_cp.csv

# Save plots without displaying
uv run bench plot-sweep map_cp.csv --no-show -o plots/
```

Produces three PNG files:
- `*_efficiency.png` -- efficiency vs load, one line per voltage, best point marked
- `*_heatmap.png` -- 2D efficiency surface (voltage x load)
- `*_loss.png` -- power loss vs load, all voltages overlaid

#### Efficiency vs Vin vs current from any logged CSV

`bench-plot` auto-detects all three CSV formats the tooling produces -- sweep
CSVs, GUI data logs (`data_*.csv`), and full-rate telemetry logs
(`*_telem.csv`) -- and draws an operating-point map (x = Vin, y = current,
color = efficiency) plus efficiency-vs-current curves grouped by Vin bin:

```bash
uv run bench-plot                    # no args -> file-picker dialog
uv run bench-plot data_20260703_140000.csv
uv run bench-plot run1_telem.csv run2_telem.csv --vin-bin 2 --save eff.png

# options: --current iout|iin, --source auto|hioki|instr|stm (data logs),
#          --min-pout W (default 5), --vin-bin V (default 1), --save PNG
```

Also reachable via the GUI's "Plot Eff..." button (Logging section, opens the
same dialog preselecting the last log) and `plot_eff.bat` one level up
(double-click for the dialog, or drag && drop CSV files onto it).

For GUI data logs the efficiency source defaults to `auto`: HIOKI EFF1 if the
meter was connected, else supply/load power ratio, else the board's own
`stm_eff_net_pct`. Telemetry logs always use the board's net efficiency
(`(P_out - P_sys) / P_in`, iout_slow). Points below `--min-pout` (default
5 W, same as the GUI display gate) are dropped.

### 7. Tune converter parameters

The tuning commands combine the testbench instruments (ground truth efficiency from HIOKI) with direct STM32 parameter writes to find optimal settings.

#### Read current STM32 state

```bash
uv run bench stm32-read
```

#### Write a single parameter

```bash
uv run bench stm32-write --param dt_10_20A --value 20
```

#### Sweep a parameter to find the optimum

Sweeps a parameter from start to stop, measuring HIOKI efficiency + STM32 telemetry at each step. Plots the result.

```bash
# Optimize deadtime for the 10-20A bracket at 300W
uv run bench tune-param \
  --param dt_10_20A --start 14 --stop 40 --step 1 \
  --voltage 60 --current-limit 20 \
  --load-mode CP --load-value 300 \
  --settle 3.0 -o dt_tune.csv

# Tune Vfly proportional gain
uv run bench tune-param \
  --param vfly_kp --start -2 --stop 2 --step 0.1 \
  --voltage 60 --current-limit 20 \
  --load-mode CP --load-value 200 \
  --settle 3.0
```

#### Optimize the global dead-time

The firmware uses a single global dead-time (`dt_normal`). `tune-deadtime` sweeps
it from `--dt-start` to `--dt-stop`, optionally at several `--load-values`, and
picks the highest-efficiency value.

```bash
# Sweep at a single load and report the best value
uv run bench tune-deadtime \
  --voltage 60 --current-limit 20 --load-mode CP \
  --dt-start 14 --dt-stop 50 --dt-step 1 \
  -o deadtime_results.csv

# Sweep across several loads, then apply the best dt_normal to the STM32
uv run bench tune-deadtime \
  --voltage 60 --current-limit 20 --load-mode CP \
  --load-values 100,300,500 \
  --apply
```

### 8. Shade / irradiance profile simulation

Simulate cloud passing or partial shading with a CSV-driven sequence:

```bash
uv run bench shade-profile \
  --profile samples/cloud_pass.csv \
  --settle 2.0 -o shade_results.csv
```

Profile CSV format: `time,voltage,current_limit,load_mode,load_value`

### 9. Real-time debug console (TUI)

For live monitoring and parameter tuning via the Textual terminal UI:

```bash
cd code64
uv run debug-console COM4
```

Keybindings: `p` ping, `f` toggle EMA filter, `s` shutoff, `x` reset, `t` 50% duty test,
`c` relay on, `d` relay off, `h` hold converter, `g` toggle precharge, `q` quit.
Parameter writes are auto-retried until ACKed (the MCU's RX is EMI-lossy while switching).

### 10. Direct instrument control

```bash
# Supply
uv run bench supply set --voltage 48 --current 10
uv run bench supply on
uv run bench supply off

# Load
uv run bench load set --mode CP --value 200
uv run bench load on
uv run bench load off

# Emergency shutdown (load first, then supply)
uv run bench safe-off
```

## CLI Reference

```
uv run bench [-h] [--supply-address ADDR] [--load-port PORT] [--load-baud BAUD]
            [--meter-address ADDR] [--timeout MS]
            [--stm32-port PORT] [--stm32-baud BAUD]
            {command}
```

| Command | Description |
|---|---|
| `identify` | Show identity and status of all instruments |
| `setup` | Configure all instruments for MPPT testing |
| `measure` | Single measurement from all three instruments |
| `monitor` | Continuous text monitoring with optional CSV |
| `live` | Real-time 4-panel matplotlib graph |
| `sweep` | Voltage sweep with efficiency recording |
| `sweep-load` | Load current sweep at fixed voltage |
| `sweep-vi` | 2D voltage x load sweep (efficiency map) |
| `efficiency` | Averaged efficiency at a fixed operating point |
| `shade-profile` | Run shade/irradiance profile from CSV |
| `plot-sweep` | Generate analysis plots from sweep CSV (offline) |
| `stm32-read` | Read all STM32 parameters and telemetry |
| `stm32-write` | Write a parameter to the STM32 |
| `tune-param` | Sweep an STM32 parameter while measuring efficiency |
| `tune-deadtime` | Auto-optimize deadtime for each current bracket |
| `supply` | Direct IT6500D control (on/off/set) |
| `load` | Direct Prodigit 3366G control (on/off/set) |
| `safe-off` | Emergency shutdown (load first, then supply) |

### Global Options

| Option | Default | Description |
|---|---|---|
| `--supply-address` | auto-detect | IT6500D VISA address |
| `--load-port` | `COM11` | Prodigit 3366G serial port |
| `--load-baud` | `115200` | Prodigit 3366G baud rate |
| `--meter-address` | auto-detect | HIOKI 3193-10 VISA address |
| `--timeout` | `5000` | VISA timeout in milliseconds |
| `--stm32-port` | `COM4` | STM32 debug serial port |
| `--stm32-baud` | `460800` | STM32 debug baud rate |

## Tunable STM32 Parameters

Names, IDs, types and ranges mirror the firmware (`code64/debug_console/protocol.py`).

| Parameter | Type | Range | Description |
|---|---|---|---|
| `VREF` | uint16 | 2340-3500 | ADC reference voltage |
| `vfly_kp` | float | -10 to 10 | Vfly proportional gain (mode 1, duty asymmetry) |
| `vfly_ki` | float | -10 to 10 | Vfly integral gain (mode 1) |
| `vfly_kp_phase` | float | -10 to 10 | Vfly P gain (mode 2, master-phase offset) |
| `vfly_phase_clamp` | uint16 | 0-10000 | Clamp on the master-phase offset (mode 2) |
| `vfly_clamp` | uint16 | 0-10000 | Vfly integrator clamp (mode 1) |
| `vfly_loop_trig` | uint16 | 1-10000 | Vfly loop counter trigger |
| `vfly_active` | uint8 | 0-3 | Vfly mode: 0 off, 1 duty-asym PI, 2 phase P, 3 manual both |
| `test_corr` | int16 | -3000 to 3000 | Manual duty-asymmetry correction (mode 3) |
| `phase_ofs` | int16 | -3000 to 3000 | Master-phase offset: manual (mode 3), readback (mode 2) |
| `cc_target` | float | 0-60000 | CC target (mA) |
| `cc_gain` | float | -1 to 1 | CC proportional gain |
| `cc_min_step` / `cc_max_step` | float | -1000-0 / 0-1000 | CC step clamps |
| `cc_loop_trig` | uint16 | 1-10000 | CC loop counter trigger |
| `cc_active` | int32 | 0-1 | CC loop enable |
| `mppt_step` | float | 1-200 | MPPT P&O step size |
| `mppt_duty_min` / `mppt_duty_max` | float | 0-6800 | MPPT duty search bounds (CMP ticks) |
| `mppt_loop_trig` | uint16 | 1-50000 | MPPT loop counter trigger |
| `mppt_active` | int32 | 0-1 | MPPT loop enable |
| `cv_threshold` / `cv_hysteresis` | float | 20000-30000 / 0-5000 | CV corner (mV) |
| `cc_threshold` / `cc_hysteresis` | float | 0-55000 / 0-10000 | CC/Iout limit (mA) |
| `dt_normal` | uint16 | 14-200 | Single global dead-time (dt register ticks) |
| `override_duty` | uint16 | 716-6442 | Manual fixed-duty base (CMP ticks, D=10..90%) |
| `manual_duty_en` | uint8 | 0-1 | Enter manual fixed-duty mode |
| `precharge_kp` / `precharge_ki` | float | 0-100 / 0-10 | Closed-loop precharge PI gains |
| `precharge_reg_en` | uint8 | 0-1 | Enable closed-loop precharge |
| `dither_en` | uint8 | 0-1 | Enable duty dithering (de-stack-band avoidance) |
| `dither_band_lo` / `dither_band_hi` | uint16 | 716-6442 | Forbidden duty band edges (CMP ticks) |
| `dither_anear` / `dither_afar` | uint16 | 716-6442 | Out-of-band dither anchors |
| `dither_dzero` | uint16 | 716-6442 | \|e\| fold center (D=0.5) |
| `adc4_trig_phase` | uint16 | 3-14313 | HRTIM master CMP3: iout_slow sample instant |
| `iin_zero_sum` | uint16 | 0-32760 | IIN software zero offset (sum-of-8 counts) |

## CSV Output Format

Sweep CSV files contain:

| Column | Description |
|---|---|
| `voltage_set` | Supply voltage setpoint (V) |
| `current_limit` | Supply current limit (A) |
| `load_setpoint` | Load setpoint value (A for CC, W for CP) |
| `supply_V/I/P` | Supply measured voltage, current, power |
| `load_V/I/P` | Load measured voltage, current, power |
| `input_power` | HIOKI P5 -- power into MPPT tracker (W) |
| `output_power` | HIOKI P6 -- power out of MPPT tracker (W) |
| `efficiency` | HIOKI EFF1 -- P6/P5 x 100 (%) |

Tuning CSV files additionally contain `param_name`, `param_value`, and STM32 telemetry columns (`stm_vin`, `stm_vout`, `stm_iin`, `stm_iout`, `stm_eff`, `stm_vfly`, `stm_etemp`).

GUI data-log CSVs contain the instrument columns plus the latest STM32
broadcast snapshot per row (`stm_counter` … `stm_age_s`; flag/fault registers
as hex). While logging, the full-rate 100 Hz telemetry stream is additionally
written to `<name>_telem.csv` with every broadcast field (one row per fresh
publish, `pc_time`/`t_mono` timestamps, computed `p_in_W`/`p_out_W`).

Note: STM32-derived `power_out_W`/`efficiency` now use `iout_slow` (the
PWM-synchronous ADC4 output current) instead of the fast protection-path
`iout` — tuner numbers shift slightly vs. old logs.

## Project Structure

```
mppt-testbench/
+-- IT6500D/              git submodule -- DC power supply driver
+-- PRODIGIT-3366G/       git submodule -- electronic load driver
+-- HIOKI-3193-10/        git submodule -- power analyzer driver
+-- testbench/
|   +-- __init__.py       exports MPPTTestbench
|   +-- bench.py          orchestrator (sweeps, measurement, auto-range wait)
|   +-- cli.py            unified CLI entry point
|   +-- gui.py            tkinter GUI with live plots
|   +-- gui_workers.py    background instrument I/O thread
|   +-- stm32_link.py     STM32 debug protocol: 114B broadcast RX + CRC-framed TX commands (8-O-1)
|   +-- plot_eff.py       efficiency vs Vin vs current plots from any logged CSV
|   +-- tuner.py          automated tuning routines (param sweep, deadtime opt)
+-- code64/
|   +-- Core/             STM32G474 firmware (C)
|   +-- Drivers/          HAL drivers
|   +-- debug_console/    Textual TUI for live debugging
|   +-- pyproject.toml    uv-compatible package config
+-- samples/              shade profile CSV examples
+-- pyproject.toml        package config, entry points: bench, bench-gui, bench-plot
```

## Dependencies

- Python >= 3.12
- [PyVISA](https://pyvisa.readthedocs.io/) + [pyvisa-py](https://pyvisa.readthedocs.io/projects/pyvisa-py/)
- [pyserial](https://pyserial.readthedocs.io/)
- [matplotlib](https://matplotlib.org/)
- [numpy](https://numpy.org/)
- NI-VISA runtime (for GPIB/USB-TMC communication)
- [Textual](https://textual.textualize.io/) (debug console only, in code64/)
