Metadata-Version: 2.4
Name: power-converter-sim
Version: 0.1.0
Summary: A closed-loop switched-mode DC-DC converter simulator (buck, then boost) with a PI voltage-feedback loop, compensator designed to a target phase margin via Bode analysis.
Author: Nishad Suresh
License: MIT
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: numpy==2.1.3
Requires-Dist: matplotlib==3.11.1
Dynamic: license-file

# Closed-Loop Power Converter Simulator

**Nishad Suresh**

## Abstract

This project implements a closed-loop, switched-mode DC-DC converter simulator for buck and boost topologies, with a PI voltage-feedback loop whose compensator is designed to a target phase margin via Bode analysis. An averaged small-signal plant model, validated against the underlying cycle-accurate switching simulator, is used to design compensators that meet a 45-60 degree phase-margin target and to characterize closed-loop response to load and line-step disturbances. The buck converter's retuned compensator reaches a 1.7 kHz loop-gain crossover at 55.6 degrees phase margin, approximately 24 times faster than an initial conservative design, while the boost converter's compensator, informed by a Jacobian-linearized nonlinear plant model, regulates to 0.005% steady-state error at 59.6 degrees phase margin.

**Status:** Phase 5 of 5, complete.

## 1. Motivation

This project replaces an original plan to build an EMG gesture classifier, which a literature review indicated was not realistic at greater than or equal to 85% cross-subject accuracy using classical machine learning without heavy transfer learning (typical results are 50-75%). Power electronics and feedback control instead filled a genuine gap in this portfolio, since no other project in it touches power systems or closed-loop control, and it connects directly to the control-theory content of separate robotics work.

## 2. Scope

This project covers cycle-accurate switching models (ideal switch and diode, continuous-conduction mode only) for buck and boost topologies, a PI voltage loop with anti-windup, load and line-step transient testing, and compensator design to a target phase margin.

## 3. Engine

`src/converter.py` implements RK4-integrated state equations for inductor current `iL` and capacitor voltage `vC`, switching between ON and OFF dynamics each period based on duty cycle `D`. The buck and boost topologies share the same integrator via a topology switch.

## 4. Setup

```bash
python3 tests/test_phase1.py
python3 tests/test_phase2.py
python3 tests/test_phase3.py
python3 tests/test_phase4.py
python3 tests/test_phase5.py
python3 tests/test_compensator_design.py
```

## 5. Methodology and Results

| # | Phase | Acceptance test | Result |
|---|---|---|---|
| 1 | Open-loop switching buck model (CCM), fixed duty | Steady-state Vout = D*Vin within a couple percent | ✅ 0.495% error (buck), 2.14% (boost); ripple confirmed to drop ~100x with 10x larger L/C |
| 2 | Averaged small-signal model + Bode of the plant | Plant Bode matches the switching model's behavior | ✅ DC gain 0.07% match; resonant-frequency ringing 1.05% match vs. switching model |
| 3 | Closed PI voltage loop with anti-windup | Regulates to setpoint at <1% steady-state error | ✅ 0.023% steady-state error; recovers cleanly from integrator saturation |
| 4 | Load-step + line-step transient response | Recovers to setpoint after a step; overshoot reported | ✅ both recover to <0.5% final error; overshoot/dip/recovery time saved to `results/phase4_transients.json` |
| 5 | Compensator design to a target phase margin; boost mode; README | Documented phase margin; boost also regulates | ✅ buck tuned to 55.6° PM at 1.7kHz crossover (24x faster than Phase 3); boost regulates to 0.005% error at 59.6° PM |

### 5.1 Phase 1: open-loop switching models

The original ripple-vs-L/C acceptance check initially produced a backwards result, with the larger L/C case showing more ripple. The bug was in the test rather than the model: the larger-L/C case has an approximately 10x longer RC time constant, so comparing both cases at a fixed 400 switching periods caught the larger-L/C case mid-transient rather than at steady state. This was corrected by giving each case enough periods to settle relative to its own time constant before measuring ripple (see `tests/test_phase1.py`).

### 5.2 Phase 2: averaged small-signal model

`src/analyze.py` builds the standard continuous-conduction-mode buck state-space plant model (`A`, `B`, `Cout`) and evaluates its control-to-output transfer function `Gvd(s) = Cout(sI-A)^-1 B` at any complex frequency. For an ideal lossless buck converter, this small-signal model is exact rather than a linearized approximation, since the averaged equations are already linear in duty cycle. It was validated two ways against the switching simulator from Phase 1. The DC gain, measured as a finite-difference `dVout/dD` from two switching-model runs at `D ± 0.005`, matched the analytic `Gvd(0)` to 0.07%. The resonant frequency, measured by stepping the duty cycle and extracting the ringing frequency of the transient (after averaging out the 100 kHz switching ripple that otherwise swamped naive peak detection; see `tests/test_phase2.py`), matched the analytic `f0 = 1/(2*pi*sqrt(LC))` to 1.05%.

A Bode plot of the plant is saved to `results/phase2_plant_bode.png`, showing the expected second-order LC resonant peak (approximately +35 dB) at `f0 ≈ 1.6 kHz`, followed by -40 dB/decade rolloff, with phase collapsing from 0 degrees to -180 degrees through the resonance.

### 5.3 Phase 3: closed PI voltage loop

`src/control.py`'s `PIController` samples the output-voltage error once per switching cycle, matching a real digital or analog compensator, and updates duty via `Kp*error + Ki*integral`, clamped to [0.05, 0.95]. Anti-windup is implemented as conditional integration: the integral accumulates only when the unsaturated command is inside the duty limits, so a saturated output does not continue winding the integrator past a reasonable recovery point.

Gains (`Kp = 0.08`, `Ki = 10`) were chosen analytically rather than by trial and error, using the Phase 2 plant model to sweep candidate gain pairs and evaluate loop-gain crossover frequency and phase margin (see the loop-gain check embedded as a comment in `tests/test_phase3.py`). An initial guess (`Ki = 800`) pushed the crossover frequency up near the plant's 1.6 kHz resonance and caused sustained oscillation, producing 9.7% steady-state error and 3.26 V of ripple instead of settling. The chosen pair crosses at approximately 70 Hz, over a decade below resonance, with approximately 164 degrees of phase margin: a conservative design (approximately 80 ms settling time) but robustly stable, verified to 0.023% steady-state error. It was separately verified that a period of saturation (an unreachable 20 V setpoint) does not prevent the loop from recovering cleanly once the setpoint returns to a reachable value.

### 5.4 Phase 4: load-step and line-step transients

`src/step_response.py` starts the closed loop at its ideal DC operating point rather than from cold start, so each run captures only the transient around the step itself rather than Phase 3's already-verified approximately 80 ms startup. Two scenarios were tested, both regulated by the same `Kp = 0.08`/`Ki = 10` PI loop. A load step (6Ω to 3Ω at t = 20 ms, doubling the output current draw) produces a 0.49 V dip with damped ringing at the plant's approximately 1.6 kHz resonance, settling to 0.020% final error within approximately 1.3 ms of re-entering a ±2% band. A line step (Vin 12 V to 15 V at t = 20 ms) produces a 1.09 V overshoot with the same characteristic ringing, decaying over approximately 29 ms and settling to 0.43% final error. Both transients are plotted in `results/phase4_transients.png`, with raw dip, overshoot, and recovery-time numbers recorded in `results/phase4_transients.json`.

An environment quirk was encountered during this phase: `tests/test_phase4.py` segfaulted once, then passed cleanly on an identical retry with identical numeric output, consistent with an intermittent numpy/BLAS segfault under the development environment (WSL) rather than a logic bug. Available memory was confirmed sufficient at the time, ruling out memory pressure as the cause.

### 5.5 Phase 5: compensator design to a target phase margin, and boost mode

`src/compensator_design.py`'s `design_pi_compensator()` grid-searches `(Kp, Ki)` pairs against a plant model, retains only pairs whose loop-gain crossover falls within a 45-60 degree phase-margin target, and selects the fastest (highest-crossover) pair among those, implementing a proper target-phase-margin design rather than a hand-picked guess. `loop_gain_crossover()` (in `analyze.py`) was rewritten to accept a precomputed plant Bode response, allowing a several-hundred-candidate grid search to run in seconds rather than minutes.

For the buck converter, the retuned compensator (`Kp = 0.0217`, `Ki = 0.491`) crosses at 1.7 kHz with 55.6 degrees phase margin, over 24 times faster than Phase 3's deliberately conservative 70 Hz/164-degree design, while remaining comfortably stable. `results/phase5_compensator_and_boost.png` (left) overlays both designs on the same load step: the retuned design's ringing settles into the ±2% band appreciably faster, though both are dominated by the same approximately 1.6 kHz LC resonance, so the difference in this particular metric is real but modest. The larger benefit of the higher bandwidth is improved disturbance rejection generally, not solely this one step.

A proper small-signal model for the boost topology was added in `analyze.py` (`boost_state_space`/`boost_plant_tf`) via Jacobian linearization of the averaged boost equations around a DC operating point. Unlike the buck converter, the boost converter's averaged equations are genuinely nonlinear in state and duty, so this is a distinct model rather than a relabeling of the buck case. The DC gain `dVout/dD = Vout/(1-D)` was verified to match the standard textbook boost formula exactly. A compensator was then designed the same way as for the buck converter: `Kp = 0.00282`, `Ki = 0.177` crosses at 974 Hz with 59.6 degrees phase margin, safely below both the approximately 955 Hz LC resonance and the plant's right-half-plane zero (approximately 11.5 kHz), which fundamentally limits how fast a boost converter can be controlled.

A real large-signal control issue was found and fixed during this phase. A boost closed loop started completely cold (zero state, zero integral) overshoots to 24.3 V and then becomes trapped near the duty floor, never reaching the 20 V setpoint: the small-signal gains, correct for perturbations around the operating point, do not carry enough proportional authority for the full 20 V startup error, and the integrator does not recover from the overshoot's negative-error phase in time. This is a known phenomenon (small-signal-tuned linear compensators can misbehave on large-signal startup), and the fix applied is the same one already used in Phases 3 and 4 for a different reason: pre-loading the integral to the expected steady-state duty, a "soft start." With that change, the boost loop regulates cleanly to 0.005% error; `results/phase5_compensator_and_boost.png` (right) shows convergence within approximately 50 ms, with ringing at the resonance that damps out rather than diverging.

## 6. A Reliability Issue Found During Review

An independent review flagged the cached `BUCK_TUNED_*`/`BOOST_TUNED_*` constants in `compensator_design.py` as a silent-drift risk: they were a snapshot of a grid search that would go stale with no warning if the plant model were ever changed. `tests/test_compensator_design.py` was added, which re-runs the search fresh each time and checks that it still reproduces the cached constants (currently an exact 0.00% match). An unused `last_error` field was also removed from `control.py`'s `PIController`.

## 7. Summary

This project implements a closed-loop buck/boost DC-DC converter simulator and designs its feedback compensators to a target phase margin, with regulation verified against load and line-step disturbances for both topologies.

## References

Sources used to design, validate, and cross-check this project's methodology:

[1] R. D. Middlebrook and S. Cuk, "A general unified approach to modelling switching-converter power stages," IEEE Power Electronics Specialists Conference (PESC), 1976, pp. 18-34. https://doi.org/10.1109/PESC.1976.7072895 -- the averaged small-signal modeling technique `src/analyze.py`'s state-space plant model is built on.

[2] Analog Devices, "How to Design a Control Loop for a DC-to-DC Converter," Analog Dialogue / technical article. https://www.analog.com/en/resources/technical-articles/how-to-design-a-control-loop-for-a-dctodc-converter.html -- basis for the PI compensator design and phase-margin targeting in Phase 5.

[3] ON Semiconductor, "DC-DC Converters Feedback and Control," Application Note TND352. https://www.onsemi.com/pub/collateral/tnd352-d.pdf -- reference for anti-windup and load/line-step transient handling in the closed-loop model.

[4] MathWorks, "Design Controller for Power Electronics Model Using Simulink Control Design" documentation. https://www.mathworks.com/help/sldo/ug/design-controller-for-power-electronics-model.html -- reference workflow for phase-margin-targeted compensator tuning, the same approach `compensator_design.py`'s grid search reproduces numerically.

[5] R. W. Erickson and D. Maksimovic, Fundamentals of Power Electronics, 3rd ed., Springer, 2020. -- standard reference for buck/boost topology equations and CCM switching-converter analysis underlying `src/converter.py`.
