PIDControl
A discrete implementation of a PID controller for building closed-loop control into embedded systems.

Installation
The package can be installed by adding pid_control to your list of dependencies in mix.exs:
def deps do
[
{:pid_control, "~> 0.1.0"}
]
endUsage
The PIDControl.new/1 function returns an initialized PIDControl struct with the given configuration:
iex> pid = PIDControl.new(kp: 0.5, kd: 0.2, ki: 0.03)
%PIDControl{...}
Calling PIDControl.step/3 performs one discrete cycle of the PID loop for the given set_point and measurement
values. The new PIDControl state is returned and the output can be accessed via the output key in the struct.
iex> pid = PIDControl.step(pid, 0.3, 0.312)
%PIDControl{
output: -0.11448221
#...
}
The step function can be called in each successive input-output cycle of the underling sensor/actuator.
Example
defmodule Controller do
use GenServer
# ...
def init(_) do
Sensor.subcribe()
pid = PIDControl.new(kp: 0.5, kd: 0.2, ki: 0.03)
{:ok, pid}
end
def handle_info({:sensor, measurment}, pid) do
pid = PIDControl.step(pid, @set_point, measurment)
Actuator.command(pid.output)
{:noreply, pid}
end
endConfig Options
kp,ki,kp- Parameters for each term in the PID. Any left blank will be set to0tau- Low-pass filter parameter for the calculateddterm. A value of1will bypass the filtering. A value between0and1will filter out high-frequency noise in the derivative term.t- Time factor for calculating theianddterm of the PID. A value of1will ignore any time parameters and treat eachstepas a single time unit. A value of0.1, for example, will treat eachstepas a tenth of a time unit. Ifuse_system_tis set totrue, this value is ignored in favor of the system time (in seconds) between calls tostep.output_minandoutput_max- Minimum and maximum values that will be output from the PID. Any other values outside of this range will be clamped. These same values are used for anti-windup of theiterm. Default is-1and1, respectively.use_system_t- When set totrue, the time used to calculatedanditerms of the PID will be derived from the measured time since the last call tostep(usingSystem.monotonic_time/0). When set to false, the configured value oftwill be used. Defaults tofalse.zero_d_on_set_point_change- When the set point changes rapidly, the d-term will suddenly spike, causing a sudden change in the PID output. Settingtauto a lower value will lessen this effect, but settingzero_d_on_set_point_changewill force the d-term to zero for the step when the set_point changes. This is useful for situations when the set point changes suddenly and occasionally, as opposed to situations where the set_point is gradually changes (as when following a profile curve). Defaults tofalse.telemetry- When set totrue, the PID will automatically emit its own telemetry after eachstep. Defaults tofalse.telemetry_prefix- Iftelemetryis set totrue, this value defines the name that the event is published under. Defaults to[:pid_control]
Telemetry
If telemetry is set to true, telemetry for the PID state will be emitted each time the step function is called.
By default, the event name will be [pid_control] and will contain the following measurements.
set_point- Current set pointmeasurement- Most recent measurementerror- Current error that is being fed to the PIDp- Proportional component of the outputi- Integral component of the outputd- Derivative component of the outputt- The time value used for the step function. This will always be the configured valuetunlessuse_system_tis set totrue.output- Most recent output (which will be the sum ofp,i, anddvalues)
This is really useful for manual tuning when combined with something like PheonixLiveDashboard.