A PID (Proportional-Integral-Derivative) controller implementation in Elixir. PID controllers are feedback loop mechanisms used in control systems to continuously calculate an error value and apply a correction. Common use cases include cruise control, temperature regulation, and robotics.
| Term | Description |
|---|---|
| Set Point (SP) | The target value (e.g. desired speed or temperature) |
| Process Value (PV) | The current measured value being controlled |
| Error | SP - PV — the difference between target and current |
| Output | The correction signal sent to the actuator (e.g. throttle, valve) |
The three terms that make up the output:
- Proportional (P) — reacts to the current error:
kP × Err - Integral (I) — accumulates past error over time:
kI × Err × dt - Derivative (D) — reacts to the rate of error change:
kD × (pErr - Err) / dt
Final output: P + integral_total + D
Add ex_pid_controller to your dependencies in mix.exs:
def deps do
[
{:ex_pid_controller, "~> 0.1.0"}
]
endCreate a controller with ExPidController.new/1, then call ExPidController.step/3 on each control loop cycle. Each call returns an updated struct with all state and output fields set — pass it directly into the next cycle.
# Initialize once
controller = ExPidController.new(
kp: 1.0,
ki: 0.5,
kd: 0.25,
cycle_time: 0.1 # seconds between cycles
)
# Each cycle:
controller = ExPidController.step(controller, 100, current_value)
# Read the output and apply to your actuator, then repeat next cycle
controller.outputSimulate a car cruise control loop targeting 60 mph from a starting speed of 40 mph:
# Target speed and starting speed (mph)
set_point = 60
initial_speed = 40.0
# vehicle_response simulates how much the throttle moves the car each cycle
# (a simplified stand-in for real-world inertia and friction)
vehicle_response = 0.5
controller = ExPidController.new(kp: 0.8, ki: 0.2, kd: 0.1, cycle_time: 1)
# Simulate 10 one-second control cycles
{_controller, _speed} =
Enum.reduce(1..10, {controller, initial_speed}, fn cycle, {controller, speed} ->
controller = ExPidController.step(controller, set_point, speed)
speed = speed + controller.output * vehicle_response
IO.puts("Cycle #{cycle}: speed=#{Float.round(speed, 1)}, output=#{Float.round(controller.output, 2)}")
{controller, speed}
end)
# Cycle 1: speed=49.0, output=18.0
# Cycle 2: speed=57.0, output=15.9
# Cycle 3: speed=62.0, output=10.04
# Cycle 4: speed=64.6, output=5.34
# Cycle 5: speed=65.7, output=2.04
# Cycle 6: speed=65.6, output=-0.07
# Cycle 7: speed=65.0, output=-1.27
# Cycle 8: speed=64.1, output=-1.82
# Cycle 9: speed=63.1, output=-1.94
# Cycle 10: speed=62.2, output=-1.79mix test