Motor Control Lab is a small, experiment-driven control laboratory for developing, analyzing, and tuning low-level motion controllers.
The core idea:
The same experiment runner, metrics, and artifacts should work for simulation today and real hardware later.
This repository is designed to grow into a practical toolkit for tuning toy trains, rovers, drones, robots, and (eventually) swarms, without turning into a one-off script or a PID tutorial.
- CLI tool (
mcl) built with Cobra - Deterministic simulation runner (fixed timestep)
- Structured run artifacts per run directory:
samples.csv(time series)metadata.json(configuration + environment)metrics.json(objective evaluation)out.log(human-readable summary)velocity.png,control.png(plots)
- Clear separation between:
- controller
- system/plant
- experiments
- analysis (metrics)
- artifact generation
Requirements: Go 1.22+ (see go.mod).
go build -o bin/mcl ./cmd/mclmake build # Build binary
make help # Show all available targetsRun help:
./bin/mcl --helpRun a step response simulation:
./bin/mcl sim step \
--target 1000 \
--duration 10 \
--dt 0.001 \
--kp 0.02 \
--ki 0.05 \
--kd 0.0 \
--deadzone 0.0Test disturbance rejection by injecting a load disturbance:
./bin/mcl sim step \
--target 1000 \
--duration 10 \
--disturbance-enabled \
--disturbance-start 5.0 \
--disturbance-duration 2.0 \
--disturbance-magnitude 50.0Artifacts are written to --out (default: runs). A run directory looks like:
runs/2026-01-16T09-05-29Z_sim_dc-motor_step/
metadata.json
samples.csv
metrics.json
out.log
velocity.png
control.png
Motor Control Lab CLI.
Run simulations.
Run a closed-loop step response simulation with PID control on a simulated DC motor.
Flags:
--kpproportional gain (default:0.02)--kiintegral gain (default:0.05)--kdderivative gain (default:0.0)--targettarget velocity in RPM (default:1000)--durationsimulation duration in seconds (default:10)--dtsimulation timestep in seconds (default:0.001)--deadzoneactuator deadzone threshold in volts (default:0.0)--disturbance-enabledenable load disturbance injection (default:false)--disturbance-startdisturbance start time in seconds (default:5.0)--disturbance-durationdisturbance duration in seconds, 0 means infinite (default:2.0)--disturbance-magnitudedisturbance magnitude in RPM/s (default:50.0)--outbase output directory (default:runs)
The current simulation is a first-order DC motor speed plant:
- steady-state velocity proportional to applied voltage (gain)
- exponential approach to steady-state (single time constant)
- voltage saturation
This is a baseline model used to validate the experiment harness and controller behavior.
Non idealities that can be simulated
- Deadzone: Actuator deadzone threshold that prevents small commands from affecting the system
- Load disturbances: Step load disturbances can be injected to test PID disturbance rejection. The disturbance is modeled as RPM/s deceleration applied to the plant dynamics. Use
--disturbance-enabledto enable, and configure timing and magnitude with the--disturbance-*flags.
Load disturbances can be injected to test controller disturbance rejection. The disturbance is applied as a step function:
- Model: External load disturbance
d(t)modeled as RPM/s deceleration in the plant dynamics:dv/dt = (1/tau) * (K*V - v) - d(t) - Configuration: Use
--disturbance-enabledalong with timing and magnitude flags - Example: Test disturbance rejection with a 50 RPM/s load starting at 5 seconds for 2 seconds:
./bin/mcl sim step \ --target 1000 \ --duration 10 \ --disturbance-enabled \ --disturbance-start 5.0 \ --disturbance-duration 2.0 \ --disturbance-magnitude 50.0
- Logging: Disturbance values are recorded in
samples.csvunder thedisturbance_rpm_per_scolumn
Real systems have effects not yet modeled here (intentionally staged):
- static friction
- continuous or time-varying load torque (only step disturbances are supported)
- supply sag (battery voltage drop under load)
- encoder quantization and noise
- drivetrain backlash or slip
Each run computes objective metrics and writes them to metrics.json:
- overshoot (percent)
- settling time (within a band, currently +/-2%)
- steady-state error
- IAE (Integral of Absolute Error)
- saturation fraction
These metrics are designed to support automated comparison and future autotuning.
cmd/mcl/ CLI entry point and commands
internal/control/ Controllers (PID)
internal/system/ Simulated plants and future hardware adapters
internal/experiment/ Experiment runners (e.g., step response)
internal/analysis/ Metrics and evaluation
internal/artifacts/ Run directories and file outputs
internal/plotting/ Plot generation
runs/ Generated run artifacts (gitignored)
The project includes a Makefile with common development tasks:
make help # Show all available targets
make fmt # Format Go code
make lint # Run linters (golangci-lint or go vet)
make test # Run all tests
make build # Build the binary
make clean # Clean build artifacts
make ci # Run CI checks (fmt + test + build)make sim-step # Run a step response simulation with default parameters
# (outputs to ./artifacts/)
make plot-latest # Show path to latest run artifactsThe sim-step target runs a simulation with sensible defaults and writes artifacts to ./artifacts/ (instead of the default runs/ directory).
Releases are created automatically when a git tag is pushed.
-
Tag the release:
git tag v1.0.0 git push origin v1.0.0
-
GitHub Actions will automatically:
- Build binaries for:
- Linux (amd64)
- macOS (amd64, arm64)
- Windows (amd64)
- Generate SHA256 checksums
- Create a GitHub Release with attached binaries
- Build binaries for:
Use semantic versioning: vX.Y.Z (e.g., v1.0.0, v0.2.1).
To build release binaries locally (for testing):
make releaseThis will:
- Check git state is clean
- Read version from current git tag
- Build binaries for all platforms into
dist/ - Generate
SHA256SUMSfile
Note: The make release target requires a git tag on the current commit. For actual releases, push the tag and let GitHub Actions handle the build and release creation.
Near-term:
- Additional disturbance shapes (ramp, sinusoidal, custom profiles)
- Sensor quantization and noise
- Batch runs / parameter sweeps
- Baseline autotune (constrained search over gains using metrics)
Hardware:
- Hardware system adapters (PWM + encoder)
- Run identical experiments on real motors
Later:
- System identification pipeline (open-loop tests -> model fitting -> initial gains)
- Cascaded loops (position -> velocity)
- Fault injection (sensor dropouts, delays)
- Multi-agent / swarm experiments using the same runner + metrics backbone