A discrete implementation of a PID controller for building closed-loop control into embedded systems.
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"}
]
end
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.
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
end
kp
,ki
,kp
- Parameters for each term in the PID. Any left blank will be set to0
tau
- Low-pass filter parameter for the calculatedd
term. A value of1
will bypass the filtering. A value between0
and1
will filter out high-frequency noise in the derivative term.t
- Time factor for calculating thei
andd
term of the PID. A value of1
will ignore any time parameters and treat eachstep
as a single time unit. A value of0.1
, for example, will treat eachstep
as a tenth of a time unit. Ifuse_system_t
is set totrue
, this value is ignored in favor of the system time (in seconds) between calls tostep
.output_min
andoutput_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 thei
term. Default is-1
and1
, respectively.use_system_t
- When set totrue
, the time used to calculated
andi
terms 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 oft
will 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. Settingtau
to a lower value will lessen this effect, but settingzero_d_on_set_point_change
will 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
- Iftelemetry
is set totrue
, this value defines the name that the event is published under. Defaults to[:pid_control]
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 valuet
unlessuse_system_t
is set totrue
.output
- Most recent output (which will be the sum ofp
,i
, andd
values)
This is really useful for manual tuning when combined with something like PheonixLiveDashboard
.