using System; namespace RealTimeGraphX { /// /// A (P)roportional, (I)ntegral, (D)erivative Controller /// /// /// The controller should be able to control any process with a /// measureable value, a known ideal value and an input to the /// process that will affect the measured value. /// /// public sealed class PidController { private double processVariable = 0; public PidController(double setPoint, double OutputMax, double OutputMin) : this(1.2, 0.5, 1, OutputMax, OutputMin) { SetPoint = setPoint; } public PidController(double GainProportional, double GainIntegral, double GainDerivative, double OutputMax, double OutputMin) { this.GainDerivative = GainDerivative; this.GainIntegral = GainIntegral; this.GainProportional = GainProportional; this.OutputMax = OutputMax; this.OutputMin = OutputMin; } /// /// The controller output /// /// timespan of the elapsed time /// since the previous time that ControlVariable was called /// Value of the variable that needs to be controlled public double ControlVariable(TimeSpan timeSinceLastUpdate) { double error = SetPoint - ProcessVariable; // integral term calculation IntegralTerm += (GainIntegral * error * timeSinceLastUpdate.TotalSeconds); IntegralTerm = Clamp(IntegralTerm); // derivative term calculation double dInput = processVariable - ProcessVariableLast; double derivativeTerm = GainDerivative * (dInput / timeSinceLastUpdate.TotalSeconds); // proportional term calcullation double proportionalTerm = GainProportional * error; double output = proportionalTerm + IntegralTerm - derivativeTerm; output = Clamp(output); return output; } /// /// The derivative term is proportional to the rate of /// change of the error /// public double GainDerivative { get; set; } = 0; /// /// The integral term is proportional to both the magnitude /// of the error and the duration of the error /// public double GainIntegral { get; set; } = 0; /// /// The proportional term produces an output value that /// is proportional to the current error value /// /// /// Tuning theory and industrial practice indicate that the /// proportional term should contribute the bulk of the output change. /// public double GainProportional { get; set; } = 0; /// /// The max output value the control device can accept. /// public double OutputMax { get; private set; } = 0; /// /// The minimum ouput value the control device can accept. /// public double OutputMin { get; private set; } = 0; /// /// Adjustment made by considering the accumulated error over time /// /// /// An alternative formulation of the integral action, is the /// proportional-summation-difference used in discrete-time systems /// public double IntegralTerm { get; private set; } = 0; /// /// The current value /// public double ProcessVariable { get { return processVariable; } set { ProcessVariableLast = processVariable; processVariable = value; } } /// /// The last reported value (used to calculate the rate of change) /// public double ProcessVariableLast { get; private set; } = 0; /// /// The desired value /// public double SetPoint { get; set; } = 0; /// /// Limit a variable to the set OutputMax and OutputMin properties /// /// /// A value that is between the OutputMax and OutputMin properties /// /// /// Inspiration from http://stackoverflow.com/questions/3176602/how-to-force-a-number-to-be-in-a-range-in-c /// private double Clamp(double variableToClamp) { if (variableToClamp <= OutputMin) { return OutputMin; } if (variableToClamp >= OutputMax) { return OutputMax; } return variableToClamp; } } }